wip: improvements to arranger view

This commit is contained in:
🪞👃🪞 2024-07-18 21:59:45 +03:00
parent 2bb8979058
commit a6a8552996
11 changed files with 762 additions and 577 deletions

2
Cargo.lock generated
View file

@ -1044,7 +1044,7 @@ dependencies = [
[[package]]
name = "tek"
version = "0.0.0"
version = "0.1.0"
dependencies = [
"atomic_float",
"backtrace",

View file

@ -1,12 +1,13 @@
[package]
name = "tek"
edition = "2021"
version = "0.1.0"
[dependencies]
jack = "0.10"
clap = { version = "4.5.4", features = [ "derive" ] }
crossterm = "0.27"
ratatui = { version = "0.26.3", features = [ "unstable-widget-ref" ] }
ratatui = { version = "0.26.3", features = [ "unstable-widget-ref", "underline-color" ] }
backtrace = "0.3.72"
microxdg = "0.1.2"
toml = "0.8.12"

View file

@ -55,7 +55,7 @@
(:12 (44 100) (40 100))
(:14 (44 100)))
(phrase { :name "Trapping" :beats 8 :steps 96 }
(phrase { :name "Trap Pinging" :beats 8 :steps 96 }
(:00 (42 100) (36 100) (34 120) (49 100))
(:01 (42 100))
(:02 (42 100))

1
shell.nix Normal file → Executable file
View file

@ -1,3 +1,4 @@
#!/usr/bin/env nix-shell
{pkgs?import<nixpkgs>{}}:pkgs.mkShell{
nativeBuildInputs = with pkgs; [
pkg-config

View file

@ -6,13 +6,15 @@ pub(crate) use ratatui::buffer::{Buffer, Cell};
use ratatui::widgets::WidgetRef;
pub fn buffer_update (
buffer: &mut Buffer, area: Rect, callback: &impl Fn(&mut Cell, u16, u16)
buf: &mut Buffer, area: Rect, callback: &impl Fn(&mut Cell, u16, u16)
) {
for row in 0..area.height {
let y = area.y + row;
for col in 0..area.width {
let x = area.x + col;
callback(buffer.get_mut(x, y), col, row);
if x < buf.area.width && y < buf.area.height {
callback(buf.get_mut(x, y), col, row);
}
}
}
}
@ -63,7 +65,7 @@ pub trait Render: Send {
($T:ty) => {
impl Render for $T {}
};
($T:ty |$self:ident, $buf:ident, $area:ident|$block:tt) => {
($T:ty |$self:ident, $buf:ident, $area:ident|$block:expr) => {
impl Render for $T {
fn render (&$self, $buf: &mut Buffer, $area: Rect) -> Usually<Rect> {
$block

View file

@ -1,6 +1,8 @@
//! Clip launcher and arrangement editor.
use crate::{core::*, model::*, view::*};
use crate::{core::*, model::*};
use self::focus::ArrangerFocus;
pub use self::scene::Scene;
/// Key bindings for arranger section.
pub const KEYMAP_ARRANGER: &'static [KeyBinding<App>] = keymap!(App {
@ -69,28 +71,9 @@ pub struct Arranger {
pub focused: bool,
pub entered: bool,
pub fixed_height: bool,
}
render!(Arranger |self, buf, area| {
let mut area = area;
area.height = area.height.min(1 + if self.mode {
self.tracks.len() as u16 * 2
} else {
self.scenes.len() as u16 * 2
});
fill_bg(buf, area, Nord::bg_lo(self.focused, self.entered));
area = if self.mode {
self.draw_horizontal(buf, area)?
} else {
let area = self.draw_vertical(buf, area)?;
if self.focused && self.entered && self.selected == ArrangerFocus::Mix {
Corners(Style::default().green().not_dim()).draw(buf, area)?;
};
area
};
Ok(area)
});
impl Arranger {
pub fn new () -> Self {
@ -101,6 +84,7 @@ impl Arranger {
tracks: vec![],
entered: true,
focused: true,
fixed_height: false,
}
}
@ -119,138 +103,250 @@ impl Arranger {
_ => {}
}
}
}
fn draw_vertical (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let bg_hi = Nord::bg_hier(self.focused, self.entered);
let bg_lo = Nord::bg_hi(self.focused, self.entered);
render!(Arranger |self, buf, area| if self.mode {
self::draw_horizontal::draw(self, buf, area)
} else {
self::draw_vertical::draw(self, buf, area)
});
return Split::right([
mod draw_vertical {
use crate::{core::*, view::*, model::Track};
use super::{Arranger, focus::ArrangerFocus, track::track_clip_name_lengths, scene::{Scene, scene_ppqs, scene_name_max_len}};
// Scene column
&|buf: &mut Buffer, area: Rect|{
let Rect { x, y, width, height } = area;
for (scene_index, scene) in self.scenes.iter().enumerate() {
if y + 2 * scene_index as u16 >= height {
break
pub fn draw (state: &Arranger, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
let track_cols = track_clip_name_lengths(state.tracks.as_slice());
let scene_rows = scene_ppqs(state.tracks.as_slice(), state.scenes.as_slice());
area.height = 2 + (scene_rows[scene_rows.len() - 1].1 / 96) as u16;
let offset = 3 + scene_name_max_len(state.scenes.as_ref()) as u16;
let style = Some(Style::default().fg(Color::Rgb(0,0,0)));
for (_, x) in track_cols.iter() {
let x = offset + area.x + *x as u16 - 1;
for y in area.y..area.height+area.y {
"".blit(buf, x, y, style)?;
}
let active = self.selected == ArrangerFocus::Scene(scene_index);
let style = Some(Nord::style_hi(self.focused, active).bold());
let y = 1 + y + 2 * scene_index as u16;
let playing = scene.clips.iter().enumerate()
.all(|(track_index, phrase_index)|match phrase_index {
Some(i) => self.tracks[track_index].sequence == Some(*i),
None => true
});
if playing { "" } else { " " }.blit(buf, x+1, y, style)?;
scene.name.blit(buf, x + 2, y, style)?;
if self.selected.scene() == Some(scene_index) {
let selected = self.selected == ArrangerFocus::Scene(scene_index);
let area = Rect { x, y, width, height: 2 };
if selected {
fill_bg(buf, area, bg_hi);
if self.focused && self.entered {
}
fill_bg(buf, area, Nord::bg_lo(state.focused, state.entered));
match state.selected {
ArrangerFocus::Mix => if state.focused && state.entered && state.selected == ArrangerFocus::Mix {
fill_bg(buf, area, Nord::bg_hi(state.focused, state.entered));
Corners(Style::default().green().not_dim()).draw(buf, area)?;
}
} else {
fill_bg(buf, area, bg_lo);
}
}
}
let width = 2 + self.scenes.iter()
.map(|x|&x.name).fold(0, |x,y|x.max(y.len() as u16+1));
Ok(Rect { width, ..area })
},
// Track columns
&|buf: &mut Buffer, area: Rect|{
let Rect { mut x, y, width, height } = area;
for (track_index, track) in self.tracks.iter().enumerate() {
if x >= width {
break
}
let width = 16u16;
track.name.blit(buf, x + 1, y, Some(Style::default().bold()))?;
for (scene_index, scene) in self.scenes.iter().enumerate() {
if y + 2 * scene_index as u16 >= height {
break
}
let label = match scene.clips.get(track_index) {
Some(Some(clip)) => if let Some(phrase) = track.phrases.get(*clip) {
let icon = if track.sequence == Some(*clip) { "" } else { "" };
format!("{icon} {}", phrase.read().unwrap().name)
} else {
format!(" ??? ")
},
_ => format!("┊ ········")
ArrangerFocus::Track(t) => {
let area = Rect {
x: offset + area.x + track_cols[t].1 as u16 - 1,
y: area.y,
width: track_cols[t].0 as u16,
height: area.height
};
let hi = self.selected == ArrangerFocus::Clip(track_index, scene_index);
let style = Some(Nord::style_hi(self.focused, hi));
let y = 1 + y + 2 * scene_index as u16;
"".blit(buf, x, y + 1, style)?;
label.blit(buf, x, y, style)?;
if self.selected == ArrangerFocus::Clip(track_index, scene_index) {
fill_bg(buf, Rect { x: area.x, y, width: area.width, height: 2 }, bg_lo);
fill_bg(buf, Rect { x, y: area.y, width, height: area.height }, bg_lo);
let area = Rect { x, y, width, height: 2 };
fill_bg(buf, area, bg_hi);
if self.focused && self.entered {
fill_bg(buf, area, Nord::bg_hi(state.focused, state.entered));
Corners(Style::default().green().not_dim()).draw(buf, area)?;
},
ArrangerFocus::Scene(s) => {
let area = Rect {
x: area.x,
y: 2 + area.y + (scene_rows[s].1 / 96) as u16,
width: area.width,
height: (scene_rows[s].0 / 96) as u16
};
}
}
if self.selected == ArrangerFocus::Track(track_index) {
fill_bg(buf, Rect { x, y: area.y, width, height: 1 }, bg_hi);
if self.focused && self.entered {
Corners(Style::default().green().not_dim())
.draw(buf, Rect { x, y: area.y, width, height })?;
fill_bg(buf, area, Nord::bg_hi(state.focused, state.entered));
Corners(Style::default().green().not_dim()).draw(buf, area)?;
},
ArrangerFocus::Clip(t, s) => {
let track_area = Rect {
x: offset + area.x + track_cols[t].1 as u16 - 1,
y: area.y,
width: track_cols[t].0 as u16,
height: area.height
};
}
x = x + width as u16;
}
Ok(Rect { x: area.x, y, width: x - area.x, height })
}
]).render(buf, area);
let scene_area = Rect {
x: area.x,
y: 2 + area.y + (scene_rows[s].1 / 96) as u16,
width: area.width,
height: (scene_rows[s].0 / 96) as u16
};
let area = Rect {
x: offset + area.x + track_cols[t].1 as u16 - 1,
y: 2 + area.y + (scene_rows[s].1 / 96) as u16,
width: track_cols[t].0 as u16,
height: (scene_rows[s].0 / 96) as u16
};
let lo = Nord::bg_hi(state.focused, state.entered);
let hi = Nord::bg_hier(state.focused, state.entered);
fill_bg(buf, track_area, lo);
fill_bg(buf, scene_area, lo);
fill_bg(buf, area, hi);
Corners(Style::default().green().not_dim()).draw(buf, area)?;
},
}
fn draw_horizontal (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let style3 = Some(Style::default().yellow().bold().not_dim());
let style4 = Some(Style::default().dim());
let style5 = Some(Style::default().white().bold().not_dim());
Split::right([
Split::down([
&|buf: &mut Buffer, area: Rect|state.draw_tracks_header(
buf, area, track_cols.as_slice(), offset),
&|buf: &mut Buffer, area: Rect|state.draw_scene_rows(
buf, area, track_cols.as_slice(), scene_rows.as_slice(), offset),
]).render(buf, area)?;
for (_, y) in scene_rows.iter() {
let y = area.y + (*y / 96) as u16 + 1;
for x in area.x..area.width+area.y-2 {
let cell = buf.get_mut(x, y);
cell.modifier = Modifier::UNDERLINED;
cell.underline_color = Color::Rgb(0, 0, 0);
}
}
Ok(area)
}
/// Rendering with vertical time
impl Arranger {
pub fn draw_tracks_header (
&self,
buf: &mut Buffer,
area: Rect,
track_cols: &[(usize, usize)],
offset: u16,
) -> Usually<Rect> {
let Rect { y, width, .. } = area;
for (track, (_, x)) in self.tracks.iter().zip(track_cols) {
let x = *x as u16;
if x > width {
break
}
track.name.blit(buf, offset + x, y, Some(Style::default()))?;
}
Ok(Rect { x: area.x, y, width, height: 2 })
}
pub fn draw_scene_rows (
&self,
buf: &mut Buffer,
area: Rect,
track_cols: &[(usize, usize)],
scene_rows: &[(usize, usize)],
offset: u16,
) -> Usually<Rect> {
let black = Some(Style::default().fg(Color::Rgb(0, 0, 0)));
let Rect { mut y, height, .. } = area;
for (_, x) in track_cols.iter() {
let x = *x as u16;
if x > 0 {
for y in area.y-2..y-2 {
"".blit(buf, x - 1, y, black)?;
}
}
}
for (scene, (pulses, _)) in self.scenes.iter().zip(scene_rows) {
if y > height {
break
}
let h = (pulses / 96) as u16;
let area = Rect { x: area.x, y, width: area.width, height: h.min(area.height - y) };
self.draw_scene_row(buf, area, scene, track_cols, offset)?;
y = y + h
}
Ok(area)
}
fn draw_scene_row (
&self,
buf: &mut Buffer,
area: Rect,
scene: &Scene,
track_cols: &[(usize, usize)],
offset: u16
) -> Usually<u16> {
let Rect { y, width, .. } = area;
let tracks = self.tracks.as_ref();
let playing = scene.is_playing(self.tracks.as_ref());
(if playing { "" } else { " " }).blit(buf, area.x, y, None)?;
scene.name.blit(buf, area.x + 1, y, None)?;
let style = Some(Style::default().white());
for (track, (w, x)) in track_cols.iter().enumerate() {
let x = *x as u16 + offset;
if x > width {
break
}
let tracks: &[Track] = self.tracks.as_ref();
if let (Some(track), Some(Some(clip))) = (
tracks.get(track), scene.clips.get(track)
) {
if let Some(phrase) = track.phrases.get(*clip) {
let phrase = phrase.read().unwrap();
phrase.name.blit(buf, x, y, style)?;
if track.sequence == Some(*clip) {
fill_bg(buf, Rect {
x: x - 1,
y,
width: *w as u16,
height: area.height,
}, Color::Rgb(40,80,50));
}
}
}
}
Ok((scene.pulses(tracks) / 96) as u16)
}
}
}
mod draw_horizontal {
use crate::{core::*, view::*};
use super::{Arranger, track::*};
pub fn draw (state: &Arranger, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let area = Split::right([
// Track name
&|buf: &mut Buffer, mut area: Rect|{
area.x = area.x + 1;
let mut width = 0;
&|buf: &mut Buffer, area: Rect|state.draw_track_name_column(buf, area),
&|buf: &mut Buffer, area: Rect|state.draw_track_mon_column(buf, area),
&|buf: &mut Buffer, area: Rect|state.draw_track_rec_column(buf, area),
&|buf: &mut Buffer, area: Rect|state.draw_track_ovr_column(buf, area),
&|buf: &mut Buffer, area: Rect|state.draw_track_del_column(buf, area),
&|buf: &mut Buffer, area: Rect|state.draw_track_gain_column(buf, area),
&|buf: &mut Buffer, area: Rect|state.draw_track_scenes_column(buf, area),
]).render(buf, area)?;
fill_bg(buf, area, Nord::bg_lo(state.focused, state.entered));
Ok(area)
}
/// Rendering with horizontal time
impl Arranger {
pub fn draw_track_name_column (&self, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
area.width = 3 + 5.max(track_name_max_len(self.tracks.as_slice())) as u16;
let dim = Some(Style::default().dim());
let yellow = Some(Style::default().yellow().bold().not_dim());
let white = Some(Style::default().white().bold().not_dim());
let offset = 0; // track scroll offset
for y in 0..area.height {
if y == 0 {
//"MIX".blit(buf, area.x + 1, area.y + y, style1)?;
} else if y % 2 == 1 {
let index = y as usize / 2;
"Mixer".blit(buf, area.x + 1, area.y + y, dim)?;
} else if y % 2 == 0 {
let index = (y as usize - 2) / 2 + offset;
if let Some(track) = self.tracks.get(index) {
width = width.max(
track.name.blit(buf, area.x + 1, area.y + y, style5)?.width
);
if self.selected == ArrangerFocus::Track(index) {
"".blit(buf, area.x, area.y + y, style3)?;
}
let style = if self.selected.track() == Some(index) {
yellow
} else {
area.height = y;
break
white
};
format!(" {index:>02} ").blit(buf, area.x, area.y + y, style)?;
track.name.blit(buf, area.x + 4, area.y + y, style)?;
}
}
}
area.width = width + 1;
Ok(area)
},
}
&|buf: &mut Buffer, mut area: Rect|{
pub fn draw_track_mon_column (&self, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
let style4 = Some(Style::default().dim());
area.x = area.x + 1;
for y in 0..area.height {
if y == 0 {
//" MON ".blit(buf, area.x, area.y + y, style2)?;
} else if y % 2 == 1 {
let index = y as usize / 2;
} else if y % 2 == 0 {
let index = (y as usize - 2) / 2;
if let Some(track) = self.tracks.get(index) {
" MON ".blit(buf, area.x, area.y + y, if track.monitoring {
Some(Style::default().not_dim().green().bold())
@ -265,15 +361,16 @@ impl Arranger {
}
area.width = 4;
Ok(area)
},
}
&|buf: &mut Buffer, mut area: Rect|{
pub fn draw_track_rec_column (&self, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
let style4 = Some(Style::default().dim());
area.x = area.x + 1;
for y in 0..area.height {
if y == 0 {
//" REC ".blit(buf, area.x, area.y + y, style2)?;
} else if y % 2 == 1 {
let index = y as usize / 2;
} else if y % 2 == 0 {
let index = (y as usize - 2) / 2;
if let Some(track) = self.tracks.get(index) {
" REC ".blit(buf, area.x, area.y + y, if track.recording {
Some(Style::default().not_dim().red().bold())
@ -288,15 +385,16 @@ impl Arranger {
}
area.width = 4;
Ok(area)
},
}
&|buf: &mut Buffer, mut area: Rect|{
pub fn draw_track_ovr_column (&self, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
let style4 = Some(Style::default().dim());
area.x = area.x + 1;
for y in 0..area.height {
if y == 0 {
//" OVR ".blit(buf, area.x, area.y + y, style2)?;
} else if y % 2 == 1 {
let index = y as usize / 2;
} else if y % 2 == 0 {
let index = (y as usize - 2) / 2;
if let Some(track) = self.tracks.get(index) {
" OVR ".blit(buf, area.x, area.y + y, if track.overdub {
Some(Style::default().not_dim().yellow().bold())
@ -311,15 +409,16 @@ impl Arranger {
}
area.width = 4;
Ok(area)
},
}
&|buf: &mut Buffer, mut area: Rect|{
pub fn draw_track_del_column (&self, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
let style4 = Some(Style::default().dim());
area.x = area.x + 1;
for y in 0..area.height {
if y == 0 {
//" DEL ".blit(buf, area.x, area.y + y, style2)?;
} else if y % 2 == 1 {
let index = y as usize / 2;
} else if y % 2 == 0 {
let index = (y as usize - 2) / 2;
if let Some(_) = self.tracks.get(index) {
" DEL ".blit(buf, area.x, area.y + y, style4)?;
} else {
@ -330,15 +429,16 @@ impl Arranger {
}
area.width = 4;
Ok(area)
},
}
&|buf: &mut Buffer, mut area: Rect|{
pub fn draw_track_gain_column (&self, buf: &mut Buffer, mut area: Rect) -> Usually<Rect> {
let style4 = Some(Style::default().dim());
area.x = area.x + 1;
for y in 0..area.height {
if y == 0 {
//" GAIN ".blit(buf, area.x, area.y + y, style2)?;
} else if y % 2 == 1 {
let index = y as usize / 2;
} else if y % 2 == 0 {
let index = (y as usize - 2) / 2;
if let Some(_) = self.tracks.get(index) {
" +0.0 ".blit(buf, area.x, area.y + y, style4)?;
} else {
@ -349,31 +449,27 @@ impl Arranger {
}
area.width = 7;
Ok(area)
},
}
// Scene columns
&|buf: &mut Buffer, area: Rect|{
pub fn draw_track_scenes_column (&self, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let mut x2 = 0;
let Rect { x, y, height, .. } = area;
for (_scene_index, scene) in self.scenes.iter().enumerate() {
let active_scene = false;//self.selected == ArrangerFocus::Scene(i) || cursor.1 > 0 && self.cursor.1 - 1 == i;
for (scene_index, scene) in self.scenes.iter().enumerate() {
let active_scene = self.selected.scene() == Some(scene_index);
let sep = Some(if active_scene {
Style::default().yellow().not_dim()
} else {
Style::default().dim()
});
"".blit(buf, x + x2, y, sep)?;
for y in y+1..y+height-1 {
"".blit(buf, x + x2, y, sep)?;
for y in y+1..y+height {
"".blit(buf, x + x2, y, sep)?;
}
"".blit(buf, x + x2, y+height-1, sep)?;
let mut x3 = scene.name.len() as u16;
scene.name.blit(buf, x + x2, y, Some(Style::default().bold().not_dim()))?;
scene.name.blit(buf, x + x2, y, sep)?;
for (i, clip) in scene.clips.iter().enumerate() {
let active_track = false;//self.cursor.0 > 0 && self.cursor.0 - 1 == i;
let active_track = self.selected.track() == Some(i);
if let Some(clip) = clip {
let y2 = y + 1 + i as u16 * 2;
let y2 = y + 2 + i as u16 * 2;
let label = match self.tracks[i].phrases.get(*clip) {
Some(phrase) => &format!("{}", phrase.read().unwrap().name),
None => "...."
@ -389,124 +485,15 @@ impl Arranger {
x2 = x2 + x3 + 1;
}
Ok(Rect { x, y, height, width: x2 })
},
]).render(buf, area)
}
}
/// Track management methods
impl Arranger {
pub fn track (&self) -> Option<&Track> {
self.selected.track().map(|t|self.tracks.get(t)).flatten()
}
pub fn track_mut (&mut self) -> Option<&mut Track> {
self.selected.track().map(|t|self.tracks.get_mut(t)).flatten()
}
pub fn track_next (&mut self) {
self.selected.track_next(self.tracks.len() - 1)
}
pub fn track_prev (&mut self) {
self.selected.track_prev()
}
pub fn track_add (&mut self, name: Option<&str>) -> Usually<&mut Track> {
self.tracks.push(name.map_or_else(
|| Track::new(&self.track_default_name()),
|name| Track::new(name),
)?);
let index = self.tracks.len() - 1;
Ok(&mut self.tracks[index])
}
pub fn track_del (&mut self) {
unimplemented!("Arranger::track_del");
}
pub fn track_default_name (&self) -> String {
format!("Track {}", self.tracks.len() + 1)
}
}
/// Scene management methods
impl Arranger {
pub fn scene (&self) -> Option<&Scene> {
self.selected.scene().map(|s|self.scenes.get(s)).flatten()
}
pub fn scene_mut (&mut self) -> Option<&mut Scene> {
self.selected.scene().map(|s|self.scenes.get_mut(s)).flatten()
}
pub fn scene_next (&mut self) {
self.selected.scene_next(self.scenes.len() - 1)
}
pub fn scene_prev (&mut self) {
self.selected.scene_prev()
}
pub fn scene_add (&mut self, name: Option<&str>) -> Usually<&mut Scene> {
let clips = vec![None;self.tracks.len()];
self.scenes.push(match name {
Some(name) => Scene::new(name, clips),
None => Scene::new(&self.track_default_name(), clips),
});
let index = self.scenes.len() - 1;
Ok(&mut self.scenes[index])
}
pub fn scene_del (&mut self) {
unimplemented!("Arranger::scene_del");
}
pub fn scene_default_name (&self) -> String {
format!("Scene {}", self.scenes.len() + 1)
}
}
/// Phrase management methods
impl Arranger {
pub fn phrase (&self) -> Option<&Arc<RwLock<Phrase>>> {
let track_id = self.selected.track()?;
self.tracks.get(track_id)?.phrases.get((*self.scene()?.clips.get(track_id)?)?)
}
//pub fn phrase_mut (&mut self) -> Option<&mut Phrase> {
//let track_id = self.selected.track()?;
//let clip = *self.scene()?.clips.get(track_id)?;
//self.tracks.get_mut(track_id)?.phrases.get_mut(clip?)
//}
pub fn phrase_next (&mut self) {
unimplemented!();
//if let Some((track_index, track)) = self.track_mut() {
//let phrases = track.phrases.len();
//if let Some((_, scene)) = self.scene_mut() {
//if let Some(phrase_index) = scene.clips[track_index] {
//if phrase_index >= phrases - 1 {
//scene.clips[track_index] = None;
//} else {
//scene.clips[track_index] = Some(phrase_index + 1);
//}
//} else if phrases > 0 {
//scene.clips[track_index] = Some(0);
//}
//}
//}
}
pub fn phrase_prev (&mut self) {
unimplemented!();
//if let Some((track_index, track)) = self.track_mut() {
//let phrases = track.phrases.len();
//if let Some((_, scene)) = self.scene_mut() {
//if let Some(phrase_index) = scene.clips[track_index] {
//scene.clips[track_index] = if phrase_index == 0 {
//None
//} else {
//Some(phrase_index - 1)
//};
//} else if phrases > 0 {
//scene.clips[track_index] = Some(phrases - 1);
//}
//}
//}
}
}
#[derive(PartialEq)]
/// Represents the current user selection in the arranger
pub enum ArrangerFocus {
/// The whole mix is selected
mod focus {
#[derive(PartialEq)]
/// Represents the current user selection in the arranger
pub enum ArrangerFocus {
/** The whole mix is selected */
Mix,
/// A track is selected.
Track(usize),
@ -514,10 +501,10 @@ pub enum ArrangerFocus {
Scene(usize),
/// A clip (track × scene) is selected.
Clip(usize, usize),
}
}
/// Focus identification methods
impl ArrangerFocus {
/// Focus identification methods
impl ArrangerFocus {
pub fn is_track (&self) -> bool {
match self { Self::Track(_) => true, _ => false }
}
@ -527,10 +514,10 @@ impl ArrangerFocus {
pub fn is_clip (&self) -> bool {
match self { Self::Clip(_, _) => true, _ => false }
}
}
}
/// Track focus methods
impl ArrangerFocus {
/// Track focus methods
impl ArrangerFocus {
pub fn track (&self) -> Option<usize> {
match self {
Self::Clip(t, _) => Some(*t),
@ -562,10 +549,10 @@ impl ArrangerFocus {
}
}
}
}
}
/// Scene focus methods
impl ArrangerFocus {
/// Scene focus methods
impl ArrangerFocus {
pub fn scene (&self) -> Option<usize> {
match self {
Self::Clip(_, s) => Some(*s),
@ -597,4 +584,202 @@ impl ArrangerFocus {
}
}
}
}
}
mod scene {
use crate::{core::*, model::Track};
use super::Arranger;
/// A collection of phrases to play on each track.
pub struct Scene {
pub name: String,
pub clips: Vec<Option<usize>>,
}
impl Scene {
pub fn new (name: impl AsRef<str>, clips: impl AsRef<[Option<usize>]>) -> Self {
let name = name.as_ref().into();
let clips = clips.as_ref().iter().map(|x|x.clone()).collect();
Self { name, clips, }
}
/// Returns the pulse length of the longest phrase in the scene
pub fn pulses (&self, tracks: &[Track]) -> usize {
self.clips.iter().enumerate()
.filter_map(|(i, c)|c.map(|c|tracks[i].phrases.get(c)))
.filter_map(|p|p)
.fold(0, |a, p|a.max(p.read().unwrap().length))
}
/// Returns true if all phrases in the scene are currently playing
pub fn is_playing (&self, tracks: &[Track]) -> bool {
self.clips.iter().enumerate()
.all(|(track_index, phrase_index)|match phrase_index {
Some(i) => tracks[track_index].sequence == Some(*i),
None => true
})
}
}
pub fn scene_name_max_len (scenes: &[Scene]) -> usize {
scenes.iter()
.map(|s|s.name.len())
.fold(0, usize::max)
}
pub fn scene_ppqs (tracks: &[Track], scenes: &[Scene]) -> Vec<(usize, usize)> {
let mut total = 0;
let mut scenes: Vec<(usize, usize)> = scenes.iter().map(|scene|{
let pulses = scene.pulses(tracks);
total = total + pulses;
(pulses, total - pulses)
}).collect();
scenes.push((0, total));
scenes
}
/// Scene management methods
impl Arranger {
pub fn scene (&self) -> Option<&Scene> {
self.selected.scene().map(|s|self.scenes.get(s)).flatten()
}
pub fn scene_mut (&mut self) -> Option<&mut Scene> {
self.selected.scene().map(|s|self.scenes.get_mut(s)).flatten()
}
pub fn scene_next (&mut self) {
self.selected.scene_next(self.scenes.len() - 1)
}
pub fn scene_prev (&mut self) {
self.selected.scene_prev()
}
pub fn scene_add (&mut self, name: Option<&str>) -> Usually<&mut Scene> {
let clips = vec![None;self.tracks.len()];
self.scenes.push(match name {
Some(name) => Scene::new(name, clips),
None => Scene::new(&self.scene_default_name(), clips),
});
let index = self.scenes.len() - 1;
Ok(&mut self.scenes[index])
}
pub fn scene_del (&mut self) {
unimplemented!("Arranger::scene_del");
}
pub fn scene_default_name (&self) -> String {
format!("Scene {}", self.scenes.len() + 1)
}
}
}
mod track {
use crate::{core::*, model::Track};
use super::Arranger;
/// Track management methods
impl Arranger {
pub fn track (&self) -> Option<&Track> {
self.selected.track().map(|t|self.tracks.get(t)).flatten()
}
pub fn track_mut (&mut self) -> Option<&mut Track> {
self.selected.track().map(|t|self.tracks.get_mut(t)).flatten()
}
pub fn track_next (&mut self) {
self.selected.track_next(self.tracks.len() - 1)
}
pub fn track_prev (&mut self) {
self.selected.track_prev()
}
pub fn track_add (&mut self, name: Option<&str>) -> Usually<&mut Track> {
self.tracks.push(name.map_or_else(
|| Track::new(&self.track_default_name()),
|name| Track::new(name),
)?);
let index = self.tracks.len() - 1;
Ok(&mut self.tracks[index])
}
pub fn track_del (&mut self) {
unimplemented!("Arranger::track_del");
}
pub fn track_default_name (&self) -> String {
format!("Track {}", self.tracks.len() + 1)
}
}
pub fn track_name_max_len (tracks: &[Track]) -> usize {
tracks.iter()
.map(|s|s.name.len())
.fold(0, usize::max)
}
pub fn track_clip_name_lengths (tracks: &[Track]) -> Vec<(usize, usize)> {
let mut total = 0;
let mut lengths: Vec<(usize, usize)> = tracks.iter().map(|track|{
let len = 2 + track.phrases
.iter()
.fold(track.name.len(), |len, phrase|{
len.max(phrase.read().unwrap().name.len())
});
total = total + len;
(len, total - len)
}).collect();
lengths.push((0, total));
lengths
}
}
mod phrase {
use crate::{core::*, devices::sequencer::Phrase};
use super::Arranger;
/// Phrase management methods
impl Arranger {
pub fn phrase (&self) -> Option<&Arc<RwLock<Phrase>>> {
let track_id = self.selected.track()?;
self.tracks.get(track_id)?.phrases.get((*self.scene()?.clips.get(track_id)?)?)
}
pub fn phrase_next (&mut self) {
let track_index = self.selected.track();
let scene_index = self.selected.scene();
track_index
.and_then(|index|self.tracks.get_mut(index).map(|track|(index, track)))
.and_then(|(track_index, track)|{
let phrases = track.phrases.len();
scene_index
.and_then(|index|self.scenes.get_mut(index))
.and_then(|scene|{
if let Some(phrase_index) = scene.clips[track_index] {
if phrase_index >= phrases - 1 {
scene.clips[track_index] = None;
} else {
scene.clips[track_index] = Some(phrase_index + 1);
}
} else if phrases > 0 {
scene.clips[track_index] = Some(0);
}
Some(())
})
});
}
pub fn phrase_prev (&mut self) {
let track_index = self.selected.track();
let scene_index = self.selected.scene();
track_index
.and_then(|index|self.tracks.get_mut(index).map(|track|(index, track)))
.and_then(|(track_index, track)|{
let phrases = track.phrases.len();
scene_index
.and_then(|index|self.scenes.get_mut(index))
.and_then(|scene|{
if let Some(phrase_index) = scene.clips[track_index] {
scene.clips[track_index] = if phrase_index == 0 {
None
} else {
Some(phrase_index - 1)
};
} else if phrases > 0 {
scene.clips[track_index] = Some(phrases - 1);
}
Some(())
})
});
}
}
}

View file

@ -44,6 +44,87 @@ pub const KEYMAP_SEQUENCER: &'static [KeyBinding<App>] = keymap!(App {
*/
});
pub type PhraseData = Vec<Vec<MidiMessage>>;
#[derive(Debug)]
/// A MIDI sequence.
pub struct Phrase {
pub name: String,
pub length: usize,
pub notes: PhraseData,
pub looped: Option<(usize, usize)>,
/// Immediate note-offs in view
pub percussive: bool
}
impl Default for Phrase {
fn default () -> Self {
Self::new("", 0, None)
}
}
impl Phrase {
pub fn new (name: &str, length: usize, notes: Option<PhraseData>) -> Self {
Self {
name: name.to_string(),
length,
notes: notes.unwrap_or(vec![Vec::with_capacity(16);length]),
looped: Some((0, length)),
percussive: true,
}
}
pub fn record_event (&mut self, pulse: usize, message: MidiMessage) {
if pulse >= self.length {
panic!("extend phrase first")
}
self.notes[pulse].push(message);
}
/// Check if a range `start..end` contains MIDI Note On `k`
pub fn contains_note_on (&self, k: u7, start: usize, end: usize) -> bool {
//panic!("{:?} {start} {end}", &self);
for events in self.notes[start.max(0)..end.min(self.notes.len())].iter() {
for event in events.iter() {
match event {
MidiMessage::NoteOn {key,..} => {
if *key == k {
return true
}
}
_ => {}
}
}
}
return false
}
/// Write a chunk of MIDI events to an output port.
pub fn process_out (
&self,
output: &mut MIDIChunk,
notes_on: &mut [bool;128],
timebase: &Arc<Timebase>,
(frame0, frames, _): (usize, usize, f64),
) {
let mut buf = Vec::with_capacity(8);
for (time, tick) in Ticks(timebase.pulse_per_frame()).between_frames(
frame0, frame0 + frames
) {
let tick = tick % self.length;
for message in self.notes[tick].iter() {
buf.clear();
let channel = 0.into();
let message = *message;
LiveEvent::Midi { channel, message }.write(&mut buf).unwrap();
output[time as usize].push(buf.clone());
match message {
MidiMessage::NoteOn { key, .. } => notes_on[key.as_int() as usize] = true,
MidiMessage::NoteOff { key, .. } => notes_on[key.as_int() as usize] = false,
_ => {}
}
}
}
}
}
/// Phrase editor.
pub struct Sequencer {
pub mode: bool,

View file

@ -14,7 +14,11 @@
//! * [LV2Plugin::load_edn]
use crate::{core::*, model::*, App};
use crate::devices::sampler::{Sampler, Sample, read_sample_data};
use crate::devices::{
arranger::Scene,
sequencer::Phrase,
sampler::{Sampler, Sample, read_sample_data}
};
use crate::devices::plugin::{Plugin, LV2Plugin};
use clojure_reader::{edn::{read, Edn}, error::Error as EdnError};

View file

@ -12,6 +12,11 @@ pub struct JackDevice {
/// The "real" readable/writable `Port`s are owned by the `state`.
pub ports: UnownedJackPorts,
}
impl std::fmt::Debug for JackDevice {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("JackDevice").field("ports", &self.ports).finish()
}
}
render!(JackDevice |self, buf, area| {
self.state.read().unwrap().render(buf, area)
});

View file

@ -12,6 +12,7 @@ pub struct JackPorts {
#[derive(Default)]
/// Collection of JACK ports as [Unowned].
#[derive(Debug)]
pub struct UnownedJackPorts {
pub audio_ins: BTreeMap<String, Port<Unowned>>,
pub midi_ins: BTreeMap<String, Port<Unowned>>,

View file

@ -156,6 +156,7 @@ impl AppFocus {
}
/// A sequencer track.
#[derive(Debug)]
pub struct Track {
pub name: String,
/// Play input through output.
@ -366,102 +367,6 @@ impl Track {
}}
}
pub type PhraseData = Vec<Vec<MidiMessage>>;
#[derive(Debug)]
/// A MIDI sequence.
pub struct Phrase {
pub name: String,
pub length: usize,
pub notes: PhraseData,
pub looped: Option<(usize, usize)>,
/// Immediate note-offs in view
pub percussive: bool
}
impl Default for Phrase {
fn default () -> Self {
Self::new("", 0, None)
}
}
impl Phrase {
pub fn new (name: &str, length: usize, notes: Option<PhraseData>) -> Self {
Self {
name: name.to_string(),
length,
notes: notes.unwrap_or(vec![Vec::with_capacity(16);length]),
looped: Some((0, length)),
percussive: true,
}
}
pub fn record_event (&mut self, pulse: usize, message: MidiMessage) {
if pulse >= self.length {
panic!("extend phrase first")
}
self.notes[pulse].push(message);
}
/// Check if a range `start..end` contains MIDI Note On `k`
pub fn contains_note_on (&self, k: u7, start: usize, end: usize) -> bool {
//panic!("{:?} {start} {end}", &self);
for events in self.notes[start.max(0)..end.min(self.notes.len())].iter() {
for event in events.iter() {
match event {
MidiMessage::NoteOn {key,..} => {
if *key == k {
return true
}
}
_ => {}
}
}
}
return false
}
/// Write a chunk of MIDI events to an output port.
pub fn process_out (
&self,
output: &mut MIDIChunk,
notes_on: &mut [bool;128],
timebase: &Arc<Timebase>,
(frame0, frames, _): (usize, usize, f64),
) {
let mut buf = Vec::with_capacity(8);
for (time, tick) in Ticks(timebase.pulse_per_frame()).between_frames(
frame0, frame0 + frames
) {
let tick = tick % self.length;
for message in self.notes[tick].iter() {
buf.clear();
let channel = 0.into();
let message = *message;
LiveEvent::Midi { channel, message }.write(&mut buf).unwrap();
output[time as usize].push(buf.clone());
match message {
MidiMessage::NoteOn { key, .. } => notes_on[key.as_int() as usize] = true,
MidiMessage::NoteOff { key, .. } => notes_on[key.as_int() as usize] = false,
_ => {}
}
}
}
}
}
/// A collection of phrases to play on each track.
pub struct Scene {
pub name: String,
pub clips: Vec<Option<usize>>,
}
impl Scene {
pub fn new (name: impl AsRef<str>, clips: impl AsRef<[Option<usize>]>) -> Self {
Self {
name: name.as_ref().into(),
clips: clips.as_ref().iter().map(|x|x.clone()).collect()
}
}
}
macro_rules! impl_axis_common { ($A:ident $T:ty) => {
impl $A<$T> {
pub fn start_inc (&mut self) -> $T {