From 6f988e50726a06764706d705b3e0b7f1cacdd3f6 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 19 Sep 2024 00:05:31 +0300 Subject: [PATCH] put all of arranger/sequencer/transport in 1 file --- crates/tek_core/src/tui.rs | 6 +- crates/tek_sequencer/Cargo.toml | 6 +- crates/tek_sequencer/src/arranger.rs | 921 -------- crates/tek_sequencer/src/lib.rs | 5 - .../{arranger_main.rs => main_arranger.rs} | 0 .../{sequencer_main.rs => main_sequencer.rs} | 0 .../{transport_main.rs => main_transport.rs} | 0 crates/tek_sequencer/src/midi.rs | 35 - crates/tek_sequencer/src/phrase.rs | 200 -- crates/tek_sequencer/src/scene.rs | 113 - crates/tek_sequencer/src/sequencer.rs | 1951 ++++++++++++++++- crates/tek_sequencer/src/transport.rs | 487 ---- 12 files changed, 1877 insertions(+), 1847 deletions(-) delete mode 100644 crates/tek_sequencer/src/arranger.rs rename crates/tek_sequencer/src/{arranger_main.rs => main_arranger.rs} (100%) rename crates/tek_sequencer/src/{sequencer_main.rs => main_sequencer.rs} (100%) rename crates/tek_sequencer/src/{transport_main.rs => main_transport.rs} (100%) delete mode 100644 crates/tek_sequencer/src/midi.rs delete mode 100644 crates/tek_sequencer/src/phrase.rs delete mode 100644 crates/tek_sequencer/src/scene.rs delete mode 100644 crates/tek_sequencer/src/transport.rs diff --git a/crates/tek_core/src/tui.rs b/crates/tek_core/src/tui.rs index 9035c3ed..a1710f5f 100644 --- a/crates/tek_core/src/tui.rs +++ b/crates/tek_core/src/tui.rs @@ -106,6 +106,9 @@ impl Tui { } let size = engine.read().unwrap().backend.size().expect("get size failed"); if let Ok(state) = state.try_read() { + if buffer.area != size { + buffer.resize(size); + } let mut output = TuiOutput { buffer, area: size.xywh() }; state.render(&mut output).expect("render failed"); buffer = engine.write().unwrap().flip(output.buffer, size); @@ -114,9 +117,6 @@ impl Tui { }) } fn flip (&mut self, mut buffer: Buffer, size: ratatui::prelude::Rect) -> Buffer { - if buffer.area != size { - buffer.resize(size); - } if self.buffer.area != size { self.buffer.resize(size); } diff --git a/crates/tek_sequencer/Cargo.toml b/crates/tek_sequencer/Cargo.toml index e9a6c4d9..bbbb5d4f 100644 --- a/crates/tek_sequencer/Cargo.toml +++ b/crates/tek_sequencer/Cargo.toml @@ -11,12 +11,12 @@ path = "src/lib.rs" [[bin]] name = "tek_sequencer" -path = "src/sequencer_main.rs" +path = "src/main_sequencer.rs" [[bin]] name = "tek_arranger" -path = "src/arranger_main.rs" +path = "src/main_arranger.rs" [[bin]] name = "tek_transport" -path = "src/transport_main.rs" +path = "src/main_transport.rs" diff --git a/crates/tek_sequencer/src/arranger.rs b/crates/tek_sequencer/src/arranger.rs deleted file mode 100644 index 67843d0d..00000000 --- a/crates/tek_sequencer/src/arranger.rs +++ /dev/null @@ -1,921 +0,0 @@ -//! Clip launcher and arrangement editor. -use crate::*; - -/// Represents the tracks and scenes of the composition. -pub struct Arranger { - /// Name of arranger - pub name: Arc>, - /// Collection of tracks. - pub tracks: Vec>, - /// Collection of scenes. - pub scenes: Vec, - /// Currently selected element. - pub selected: ArrangerFocus, - /// Display mode of arranger - pub mode: ArrangerViewMode, - /// Slot for modal dialog displayed on top of app. - pub modal: Option>>, - /// Whether the arranger is currently focused - pub focused: bool -} -impl Arranger { - pub fn new (name: &str) -> Self { - Self { - name: Arc::new(RwLock::new(name.into())), - mode: ArrangerViewMode::VerticalCompact2, - selected: ArrangerFocus::Clip(0, 0), - scenes: vec![], - tracks: vec![], - modal: None, - focused: false - } - } - pub fn activate (&mut self) { - match self.selected { - ArrangerFocus::Scene(s) => { - for (track_index, track) in self.tracks.iter_mut().enumerate() { - track.sequence = self.scenes[s].clips[track_index]; - track.reset = true; - } - }, - ArrangerFocus::Clip(t, s) => { - self.tracks[t].sequence = self.scenes[s].clips[t]; - self.tracks[t].reset = true; - }, - _ => {} - } - } - pub fn sequencer (&self) -> Option<&Sequencer> { - self.selected.track() - .map(|track|self.tracks.get(track)) - .flatten() - } - pub fn sequencer_mut (&mut self) -> Option<&mut Sequencer> { - self.selected.track() - .map(|track|self.tracks.get_mut(track)) - .flatten() - } - pub fn show_phrase (&mut self) -> Usually<()> { - //unimplemented!() - //let phrase = self.phrase(); - //self.sequencer.show(phrase) - Ok(()) - } - pub fn is_first_row (&self) -> bool { - let selected = self.selected; - selected.is_mix() || selected.is_track() || match selected { - ArrangerFocus::Clip(_, s) => - s == 0, - _ => false - } - } - pub fn is_last_row (&self) -> bool { - let selected = self.selected; - match selected { - ArrangerFocus::Scene(s) => - s == self.scenes.len() - 1, - ArrangerFocus::Clip(_, s) => - s == self.scenes.len() - 1, - _ => false - } - } - pub fn track (&self) -> Option<&Sequencer> { - self.selected.track().map(|t|self.tracks.get(t)).flatten() - } - pub fn track_mut (&mut self) -> Option<&mut Sequencer> { - 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 Sequencer> { - self.tracks.push(name.map_or_else( - || Sequencer::new(&self.track_default_name()), - |name| Sequencer::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) - } -} -impl Arranger { - pub fn rename_selected (&mut self) { - self.modal = Some(Box::new(ArrangerRenameModal::new( - self.selected, - &match self.selected { - ArrangerFocus::Mix => self.name.clone(), - ArrangerFocus::Track(t) => self.tracks[t].name.clone(), - ArrangerFocus::Scene(s) => self.scenes[s].name.clone(), - ArrangerFocus::Clip(t, s) => self.tracks[t].phrases[s].read().unwrap().name.clone(), - } - ))); - } -} -impl Handle for Arranger { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - if let Some(modal) = self.modal.as_mut() { - let result = modal.handle(from)?; - if modal.exited() { - self.modal = None; - } - return Ok(result) - } - match from.event() { - // mode_switch: switch the display mode - key!(KeyCode::Char('`')) => { - self.mode.to_next() - }, - // cursor_up: move cursor up - key!(KeyCode::Up) => { - match self.mode { - ArrangerViewMode::Horizontal => self.track_prev(), - _ => self.scene_prev(), - }; - self.show_phrase()?; - }, - // cursor_down - key!(KeyCode::Down) => { - match self.mode { - ArrangerViewMode::Horizontal => self.track_next(), - _ => self.scene_next(), - }; - self.show_phrase()?; - }, - // cursor left - key!(KeyCode::Left) => { - match self.mode { - ArrangerViewMode::Horizontal => self.scene_prev(), - _ => self.track_prev(), - }; - self.show_phrase()?; - }, - // cursor right - key!(KeyCode::Right) => { - match self.mode { - ArrangerViewMode::Horizontal => self.scene_next(), - _ => self.track_next(), - }; - self.show_phrase()?; - }, - // increment: use next clip here - key!(KeyCode::Char('.')) => { - self.phrase_next(); - }, - // decrement: use previous next clip here - key!(KeyCode::Char(',')) => { - self.phrase_prev(); - }, - // decrement: use previous clip here - key!(KeyCode::Enter) => { - self.activate(); - }, - // scene_add: add a new scene - key!(Ctrl-KeyCode::Char('a')) => { - self.scene_add(None)?; - }, - // track_add: add a new scene - key!(Ctrl-KeyCode::Char('t')) => { - self.track_add(None)?; - }, - // rename: add a new scene - key!(KeyCode::Char('n')) => { - self.rename_selected(); - }, - // length: add a new scene - key!(KeyCode::Char('l')) => { - todo!(); - }, - // color: set color of item at cursor - key!(KeyCode::Char('c')) => { - todo!(); - }, - _ => return Ok(None) - } - Ok(Some(true)) - } -} -impl Content for Arranger { - type Engine = Tui; - fn content (&self) -> impl Widget { - Layers::new(move |add|{ - //Lozenge(Style::default().fg(Nord::BG2)) - //.draw(&mut to.alter_area(|[x, y, w, h]|[ - //x.saturating_sub(1), - //y.saturating_sub(1), - //w + 2, - //h + 2, - //])) - match self.mode { - ArrangerViewMode::Horizontal => add(&ArrangerViewHorizontal(&self)), - ArrangerViewMode::VerticalCompact1 => add(&ArrangerViewVertical( - &self, - track_clip_name_lengths(self.tracks.as_slice()).as_slice(), - (0..=self.scenes.len()).map(|i|(96, 96*i)).collect::>().as_slice(), - )), - ArrangerViewMode::VerticalCompact2 => add(&ArrangerViewVertical( - &self, - track_clip_name_lengths(self.tracks.as_slice()).as_slice(), - (0..=self.scenes.len()).map(|i|(192, 192*i)).collect::>().as_slice() - )), - ArrangerViewMode::VerticalExpanded => add(&ArrangerViewVertical( - &self, - track_clip_name_lengths(self.tracks.as_slice()).as_slice(), - scene_ppqs(self.tracks.as_slice(), self.scenes.as_slice()).as_slice(), - )), - } - }) - } -} -impl Focusable for Arranger { - fn is_focused (&self) -> bool { - self.focused - } - fn set_focused (&mut self, focused: bool) { - self.focused = focused - } -} - -#[derive(PartialEq, Clone, Copy)] -/// Represents the current user selection in the arranger -pub enum ArrangerFocus { - /** The whole mix is selected */ - Mix, - /// A track is selected. - Track(usize), - /// A scene is selected. - Scene(usize), - /// A clip (track × scene) is selected. - Clip(usize, usize), -} - -/// Focus identification methods -impl ArrangerFocus { - pub fn is_mix (&self) -> bool { - match self { Self::Mix => true, _ => false } - } - pub fn is_track (&self) -> bool { - match self { Self::Track(_) => true, _ => false } - } - pub fn is_scene (&self) -> bool { - match self { Self::Scene(_) => true, _ => false } - } - pub fn is_clip (&self) -> bool { - match self { Self::Clip(_, _) => true, _ => false } - } - pub fn track (&self) -> Option { - match self { - Self::Clip(t, _) => Some(*t), - Self::Track(t) => Some(*t), - _ => None - } - } - pub fn track_next (&mut self, last_track: usize) { - *self = match self { - Self::Mix => Self::Track(0), - Self::Track(t) => Self::Track(last_track.min(*t + 1)), - Self::Scene(s) => Self::Clip(0, *s), - Self::Clip(t, s) => Self::Clip(last_track.min(*t + 1), *s), - } - } - pub fn track_prev (&mut self) { - *self = match self { - Self::Mix => Self::Mix, - Self::Scene(s) => Self::Scene(*s), - Self::Track(t) => if *t == 0 { - Self::Mix - } else { - Self::Track(*t - 1) - }, - Self::Clip(t, s) => if *t == 0 { - Self::Scene(*s) - } else { - Self::Clip(t.saturating_sub(1), *s) - } - } - } - pub fn scene (&self) -> Option { - match self { - Self::Clip(_, s) => Some(*s), - Self::Scene(s) => Some(*s), - _ => None - } - } - pub fn scene_next (&mut self, last_scene: usize) { - *self = match self { - Self::Mix => Self::Scene(0), - Self::Track(t) => Self::Clip(*t, 0), - Self::Scene(s) => Self::Scene(last_scene.min(*s + 1)), - Self::Clip(t, s) => Self::Clip(*t, last_scene.min(*s + 1)), - } - } - pub fn scene_prev (&mut self) { - *self = match self { - Self::Mix => Self::Mix, - Self::Track(t) => Self::Track(*t), - Self::Scene(s) => if *s == 0 { - Self::Mix - } else { - Self::Scene(*s - 1) - }, - Self::Clip(t, s) => if *s == 0 { - Self::Track(*t) - } else { - Self::Clip(*t, s.saturating_sub(1)) - } - } - } -} -/// Display mode of arranger -#[derive(PartialEq)] -pub enum ArrangerViewMode { VerticalExpanded, VerticalCompact1, VerticalCompact2, Horizontal } -/// Arranger display mode can be cycled -impl ArrangerViewMode { - /// Cycle arranger display mode - pub fn to_next (&mut self) { - *self = match self { - Self::VerticalExpanded => Self::VerticalCompact1, - Self::VerticalCompact1 => Self::VerticalCompact2, - Self::VerticalCompact2 => Self::Horizontal, - Self::Horizontal => Self::VerticalExpanded, - } - } -} - -struct ArrangerViewVertical<'a, 'b, E: Engine>( - &'a Arranger, &'b [(usize, usize)], &'b [(usize, usize)] -); - -impl<'a, 'b> Content for ArrangerViewVertical<'a, 'b, Tui> { - type Engine = Tui; - fn content (&self) -> impl Widget { - let Self(state, cols, rows) = self; - let tracks: &[Sequencer] = state.tracks.as_ref(); - let scenes = state.scenes.as_ref(); - let offset = 4 + scene_name_max_len(scenes) as u16; - Layers::new(move |add|{ - add(&ColumnSeparators(offset, cols))?; - add(&RowSeparators(rows))?; - add(&CursorFocus(state.focused, state.selected, offset, cols, rows))?; - add(&Split::down(|add|{ - add(&Plus::X(offset, Split::right(move |add|{ - for (track, (w, _)) in tracks.iter().zip(*cols) { - add(&Min::XY(*w as u16, 2, Layers::new(|add|{ - add(&Background(COLOR_BG1))?; - add(&track.name.read().unwrap().as_str()) - })))?; - } - Ok(()) - })))?; - add(&Split::down(move |add| { - for (scene, (pulses, _)) in scenes.iter().zip(*rows) { - let height = 1.max((pulses / 96) as u16); - let playing = scene.is_playing(tracks); - add(&Fixed::Y(height, Split::right(move |add| { - add(&Fixed::XY(offset.saturating_sub(1), height, Split::right(|add|{ - add(&if playing { "▶ " } else { " " })?; - add(&scene.name.read().unwrap().as_str()) - })))?; - for (track, (w, _x)) in cols.iter().enumerate() { - add(&Fixed::XY(*w as u16, height, Layers::new(move |add|{ - let mut color = COLOR_BG0; - if let (Some(track), Some(Some(clip))) = ( - tracks.get(track), - scene.clips.get(track), - ) { - if let Some(phrase) = track.phrases.get(*clip) { - add(&Plus::X(1, format!( - "{clip:02} {}", - phrase.read().unwrap().name.read().unwrap() - ).as_str()))?; - color = if track.sequence == Some(*clip) { - Nord::PLAYING - } else { - COLOR_BG1 - }; - } - } - add(&Background(color)) - })))?; - } - Ok(()) - })))?; - } - Ok(()) - })) - })) - }) - } -} - -struct ColumnSeparators<'a>(u16, &'a [(usize, usize)]); - -impl<'a> Widget for ColumnSeparators<'a> { - type Engine = Tui; - fn render (&self, to: &mut TuiOutput) -> Usually<()> { - let area = to.area(); - let Self(offset, cols) = self; - let style = Some(Style::default().fg(Nord::SEPARATOR)); - for (_, x) in cols.iter() { - let x = offset + area.x() + *x as u16 - 1; - for y in area.y()..area.y2() { - to.blit(&"▎", x, y, style); - } - } - Ok(()) - } -} - -struct RowSeparators<'a>(&'a [(usize, usize)]); - -impl<'a> Widget for RowSeparators<'a> { - type Engine = Tui; - fn render (&self, to: &mut TuiOutput) -> Usually<()> { - let area = to.area(); - let Self(rows) = self; - for (_, y) in rows.iter() { - let y = area.y() + (*y / 96) as u16 + 1; - if y >= to.buffer.area.height { - break - } - for x in area.x()..area.x2().saturating_sub(2) { - if x < to.buffer.area.x && y < to.buffer.area.y { - let cell = to.buffer.get_mut(x, y); - cell.modifier = Modifier::UNDERLINED; - cell.underline_color = Nord::SEPARATOR; - } - } - } - Ok(()) - } -} - -struct CursorFocus<'a>( - bool, ArrangerFocus, u16, &'a [(usize, usize)], &'a [(usize, usize)] -); - -impl<'a> Widget for CursorFocus<'a> { - type Engine = Tui; - fn render (&self, to: &mut TuiOutput) -> Usually<()> { - let area = to.area(); - let Self(focused, selected, offset, cols, rows) = *self; - let get_track_area = |t: usize| [ - offset + area.x() + cols[t].1 as u16 - 1, - area.y(), - cols[t].0 as u16, - area.h() - ]; - let get_scene_area = |s: usize| [ - area.x(), - 2 + area.y() + (rows[s].1 / 96) as u16, - area.w(), - (rows[s].0 / 96) as u16 - ]; - let get_clip_area = |t: usize, s: usize| [ - offset + area.x() + cols[t].1 as u16 - 1, - 2 + area.y() + (rows[s].1 / 96) as u16, - cols[t].0 as u16, - (rows[s].0 / 96) as u16 - ]; - let mut track_area: Option<[u16;4]> = None; - let mut scene_area: Option<[u16;4]> = None; - let mut clip_area: Option<[u16;4]> = None; - let area = match selected { - ArrangerFocus::Mix => { - if focused { - to.fill_bg(area, COLOR_BG0); - } - area - }, - ArrangerFocus::Track(t) => { - track_area = Some(get_track_area(t)); - area - }, - ArrangerFocus::Scene(s) => { - scene_area = Some(get_scene_area(s)); - area - }, - ArrangerFocus::Clip(t, s) => { - track_area = Some(get_track_area(t)); - scene_area = Some(get_scene_area(s)); - clip_area = Some(get_clip_area(t, s)); - area - }, - }; - if let Some([x, y, width, height]) = track_area { - to.fill_fg([x, y, 1, height], COLOR_BG5); - to.fill_fg([x + width, y, 1, height], COLOR_BG5); - } - if let Some([_, y, _, height]) = scene_area { - to.fill_ul([area.x(), y - 1, area.w(), 1], COLOR_BG5); - to.fill_ul([area.x(), y + height - 1, area.w(), 1], COLOR_BG5); - } - if focused { - if let Some(clip_area) = clip_area { - to.fill_bg(clip_area, COLOR_BG0); - } else if let Some(track_area) = track_area { - to.fill_bg(track_area, COLOR_BG0); - } else if let Some(scene_area) = scene_area { - to.fill_bg(scene_area, COLOR_BG0); - } - } - //Ok(Some(area)) - Ok(()) - } -} - -struct ArrangerViewHorizontal<'a, E: Engine>( - &'a Arranger -); - -impl<'a> Content for ArrangerViewHorizontal<'a, Tui> { - type Engine = Tui; - fn content (&self) -> impl Widget { - let Arranger { tracks, focused, selected, scenes, .. } = self.0; - let tracks = tracks.as_slice(); - Layers::new(|add|{ - add(&focused.then_some(Background(COLOR_BG0)))?; - add(&Split::right(|add|{ - add(&TrackNameColumn(tracks, *selected))?; - add(&TrackMonitorColumn(tracks))?; - add(&TrackRecordColumn(tracks))?; - add(&TrackOverdubColumn(tracks))?; - add(&TrackEraseColumn(tracks))?; - add(&TrackGainColumn(tracks))?; - add(&TrackScenesColumn(tracks, scenes.as_slice(), *selected))?; - Ok(()) - })) - }) - } -} - -struct TrackNameColumn<'a>(&'a [Sequencer], ArrangerFocus); - -impl<'a> Widget for TrackNameColumn<'a> { - type Engine = Tui; - fn layout (&self, area: [u16;2]) -> Perhaps<[u16;2]> { - todo!() - } - fn render (&self, to: &mut TuiOutput) -> Usually<()> { - todo!(); - //let Self(tracks, selected) = self; - //let yellow = Some(Style::default().yellow().bold().not_dim()); - //let white = Some(Style::default().white().bold().not_dim()); - //let area = to.area(); - //let area = [area.x(), area.y(), 3 + 5.max(track_name_max_len(tracks)) as u16, area.h()]; - //let offset = 0; // track scroll offset - //for y in 0..area.h() { - //if y == 0 { - //to.blit(&"Mixer", area.x() + 1, area.y() + y, Some(DIM))?; - //} else if y % 2 == 0 { - //let index = (y as usize - 2) / 2 + offset; - //if let Some(track) = tracks.get(index) { - //let selected = selected.track() == Some(index); - //let style = if selected { yellow } else { white }; - //to.blit(&format!(" {index:>02} "), area.x(), area.y() + y, style)?; - //to.blit(&*track.name.read().unwrap(), area.x() + 4, area.y() + y, style)?; - //} - //} - //} - //Ok(Some(area)) - } -} - -struct TrackMonitorColumn<'a>(&'a [Sequencer]); - -impl<'a> Widget for TrackMonitorColumn<'a> { - type Engine = Tui; - fn layout (&self, area: [u16;2]) -> Perhaps<[u16;2]> { - todo!() - } - fn render (&self, to: &mut TuiOutput) -> Usually<()> { - todo!(); - //let Self(tracks) = self; - //let mut area = to.area(); - //let on = Some(Style::default().not_dim().green().bold()); - //let off = Some(DIM); - //area.x += 1; - //for y in 0..area.h() { - //if y == 0 { - ////" MON ".blit(to.buffer, area.x, area.y + y, style2)?; - //} else if y % 2 == 0 { - //let index = (y as usize - 2) / 2; - //if let Some(track) = tracks.get(index) { - //let style = if track.monitoring { on } else { off }; - //to.blit(&" MON ", area.x(), area.y() + y, style)?; - //} else { - //area.height = y; - //break - //} - //} - //} - //area.width = 4; - //Ok(Some(area)) - } -} - -struct TrackRecordColumn<'a>(&'a [Sequencer]); - -impl<'a> Widget for TrackRecordColumn<'a> { - type Engine = Tui; - fn layout (&self, area: [u16;2]) -> Perhaps<[u16;2]> { - todo!() - } - fn render (&self, to: &mut TuiOutput) -> Usually<()> { - todo!(); - //let Self(tracks) = self; - //let mut area = to.area(); - //let on = Some(Style::default().not_dim().red().bold()); - //let off = Some(Style::default().dim()); - //area.x += 1; - //for y in 0..area.h() { - //if y == 0 { - ////" REC ".blit(to.buffer, area.x, area.y + y, style2)?; - //} else if y % 2 == 0 { - //let index = (y as usize - 2) / 2; - //if let Some(track) = tracks.get(index) { - //let style = if track.recording { on } else { off }; - //to.blit(&" REC ", area.x(), area.y() + y, style)?; - //} else { - //area.height = y; - //break - //} - //} - //} - //area.width = 4; - //Ok(Some(area)) - } -} - -struct TrackOverdubColumn<'a>(&'a [Sequencer]); - -impl<'a> Widget for TrackOverdubColumn<'a> { - type Engine = Tui; - fn layout (&self, area: [u16;2]) -> Perhaps<[u16;2]> { - todo!() - } - fn render (&self, to: &mut TuiOutput) -> Usually<()> { - todo!(); - //let Self(tracks) = self; - //let mut area = to.area(); - //let on = Some(Style::default().not_dim().yellow().bold()); - //let off = Some(Style::default().dim()); - //area.x = area.x + 1; - //for y in 0..area.h() { - //if y == 0 { - ////" OVR ".blit(to.buffer, area.x, area.y + y, style2)?; - //} else if y % 2 == 0 { - //let index = (y as usize - 2) / 2; - //if let Some(track) = tracks.get(index) { - //to.blit(&" OVR ", area.x(), area.y() + y, if track.overdub { - //on - //} else { - //off - //})?; - //} else { - //area.height = y; - //break - //} - //} - //} - //area.width = 4; - //Ok(Some(area)) - } -} - -struct TrackEraseColumn<'a>(&'a [Sequencer]); - -impl<'a> Widget for TrackEraseColumn<'a> { - type Engine = Tui; - fn layout (&self, area: [u16;2]) -> Perhaps<[u16;2]> { - todo!() - } - fn render (&self, to: &mut TuiOutput) -> Usually<()> { - todo!(); - //let Self(tracks) = self; - //let mut area = to.area(); - //let off = Some(Style::default().dim()); - //area.x = area.x + 1; - //for y in 0..area.h() { - //if y == 0 { - ////" DEL ".blit(to.buffer, area.x, area.y + y, style2)?; - //} else if y % 2 == 0 { - //let index = (y as usize - 2) / 2; - //if let Some(_) = tracks.get(index) { - //to.blit(&" DEL ", area.x(), area.y() + y, off)?; - //} else { - //area.height = y; - //break - //} - //} - //} - //area.width = 4; - //Ok(Some(area)) - } -} - -struct TrackGainColumn<'a>(&'a [Sequencer]); - -impl<'a> Widget for TrackGainColumn<'a> { - type Engine = Tui; - fn layout (&self, area: [u16;2]) -> Perhaps<[u16;2]> { - todo!() - } - fn render (&self, to: &mut TuiOutput) -> Usually<()> { - todo!(); - //let Self(tracks) = self; - //let mut area = to.area(); - //let off = Some(Style::default().dim()); - //area.x = area.x() + 1; - //for y in 0..area.h() { - //if y == 0 { - ////" GAIN ".blit(to.buffer, area.x, area.y + y, style2)?; - //} else if y % 2 == 0 { - //let index = (y as usize - 2) / 2; - //if let Some(_) = tracks.get(index) { - //to.blit(&" +0.0 ", area.x(), area.y() + y, off)?; - //} else { - //area.height = y; - //break - //} - //} - //} - //area.width = 7; - //Ok(Some(area)) - } -} - -struct TrackScenesColumn<'a>(&'a [Sequencer], &'a [Scene], ArrangerFocus); - -impl<'a> Widget for TrackScenesColumn<'a> { - type Engine = Tui; - fn layout (&self, area: [u16;2]) -> Perhaps<[u16;2]> { - todo!() - } - fn render (&self, to: &mut TuiOutput) -> Usually<()> { - let Self(tracks, scenes, selected) = self; - let area = to.area(); - let mut x2 = 0; - let [x, y, _, height] = area; - for (scene_index, scene) in scenes.iter().enumerate() { - let active_scene = selected.scene() == Some(scene_index); - let sep = Some(if active_scene { - Style::default().yellow().not_dim() - } else { - Style::default().dim() - }); - for y in y+1..y+height { - to.blit(&"│", x + x2, y, sep); - } - let name = scene.name.read().unwrap(); - let mut x3 = name.len() as u16; - to.blit(&*name, x + x2, y, sep); - for (i, clip) in scene.clips.iter().enumerate() { - let active_track = selected.track() == Some(i); - if let Some(clip) = clip { - let y2 = y + 2 + i as u16 * 2; - let label = match tracks[i].phrases.get(*clip) { - Some(phrase) => &format!("{}", phrase.read().unwrap().name.read().unwrap()), - None => "...." - }; - to.blit(&label, x + x2, y2, Some(if active_track && active_scene { - Style::default().not_dim().yellow().bold() - } else { - Style::default().not_dim() - })); - x3 = x3.max(label.len() as u16) - } - } - x2 = x2 + x3 + 1; - } - //Ok(Some([x, y, x2, height])) - Ok(()) - } -} - -/// Appears on first run (i.e. if state dir is missing). -pub struct ArrangerRenameModal { - _engine: std::marker::PhantomData, - done: bool, - target: ArrangerFocus, - value: String, - result: Arc>, - cursor: usize -} - -impl ArrangerRenameModal { - pub fn new (target: ArrangerFocus, value: &Arc>) -> Self { - Self { - _engine: Default::default(), - done: false, - target, - value: value.read().unwrap().clone(), - cursor: value.read().unwrap().len(), - result: value.clone(), - } - } -} - -impl Widget for ArrangerRenameModal { - type Engine = Tui; - fn layout (&self, area: [u16;2]) -> Perhaps<[u16;2]> { - todo!() - } - fn render (&self, to: &mut TuiOutput) -> Usually<()> { - let area = to.area(); - let y = area.y() + area.h() / 2; - let bg_area = [1, y - 1, area.w() - 2, 3]; - to.fill_bg(bg_area, Nord::BG0); - Lozenge(Style::default().bold().white().dim()).draw(to.with_rect(bg_area)); - let label = match self.target { - ArrangerFocus::Mix => "Rename project:", - ArrangerFocus::Track(_) => "Rename track:", - ArrangerFocus::Scene(_) => "Rename scene:", - ArrangerFocus::Clip(_, _) => "Rename clip:", - }; - let style = Some(Style::default().not_bold().white().not_dim()); - to.blit(&label, area.x() + 3, y, style); - let style = Some(Style::default().bold().white().not_dim()); - to.blit(&self.value, area.x() + 3 + label.len() as u16 + 1, y, style); - let style = Some(Style::default().bold().white().not_dim().reversed()); - to.blit(&"▂", area.x() + 3 + label.len() as u16 + 1 + self.cursor as u16, y, style); - //Ok(Some(area)) - Ok(()) - } -} - -impl Handle for ArrangerRenameModal { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - match from.event() { - TuiEvent::Input(Event::Key(k)) => { - match k.code { - KeyCode::Esc => { - self.exit(); - }, - KeyCode::Enter => { - *self.result.write().unwrap() = self.value.clone(); - self.exit(); - }, - KeyCode::Left => { - self.cursor = self.cursor.saturating_sub(1); - }, - KeyCode::Right => { - self.cursor = self.value.len().min(self.cursor + 1) - }, - KeyCode::Backspace => { - let last = self.value.len().saturating_sub(1); - self.value = format!("{}{}", - &self.value[0..self.cursor.min(last)], - &self.value[self.cursor.min(last)..last] - ); - self.cursor = self.cursor.saturating_sub(1) - } - KeyCode::Char(c) => { - self.value.insert(self.cursor, c); - self.cursor = self.value.len().min(self.cursor + 1) - }, - _ => {} - } - Ok(Some(true)) - }, - _ => Ok(None), - } - } -} - -impl Exit for ArrangerRenameModal { - fn exited (&self) -> bool { - self.done - } - fn exit (&mut self) { - self.done = true - } -} - -pub fn track_name_max_len (tracks: &[Sequencer]) -> usize { - tracks.iter() - .map(|s|s.name.read().unwrap().len()) - .fold(0, usize::max) -} - -pub fn track_clip_name_lengths (tracks: &[Sequencer]) -> Vec<(usize, usize)> { - let mut total = 0; - let mut lengths: Vec<(usize, usize)> = tracks.iter().map(|track|{ - let len = 4 + track.phrases - .iter() - .fold(track.name.read().unwrap().len(), |len, phrase|{ - len.max(phrase.read().unwrap().name.read().unwrap().len()) - }); - total = total + len; - (len, total - len) - }).collect(); - lengths.push((0, total)); - lengths -} diff --git a/crates/tek_sequencer/src/lib.rs b/crates/tek_sequencer/src/lib.rs index 5c1917b9..4047b576 100644 --- a/crates/tek_sequencer/src/lib.rs +++ b/crates/tek_sequencer/src/lib.rs @@ -7,10 +7,5 @@ pub(crate) use tek_core::jack::*; pub(crate) use std::sync::{Arc, RwLock}; submod! { - arranger - midi - phrase - scene sequencer - transport } diff --git a/crates/tek_sequencer/src/arranger_main.rs b/crates/tek_sequencer/src/main_arranger.rs similarity index 100% rename from crates/tek_sequencer/src/arranger_main.rs rename to crates/tek_sequencer/src/main_arranger.rs diff --git a/crates/tek_sequencer/src/sequencer_main.rs b/crates/tek_sequencer/src/main_sequencer.rs similarity index 100% rename from crates/tek_sequencer/src/sequencer_main.rs rename to crates/tek_sequencer/src/main_sequencer.rs diff --git a/crates/tek_sequencer/src/transport_main.rs b/crates/tek_sequencer/src/main_transport.rs similarity index 100% rename from crates/tek_sequencer/src/transport_main.rs rename to crates/tek_sequencer/src/main_transport.rs diff --git a/crates/tek_sequencer/src/midi.rs b/crates/tek_sequencer/src/midi.rs deleted file mode 100644 index 23e7f1db..00000000 --- a/crates/tek_sequencer/src/midi.rs +++ /dev/null @@ -1,35 +0,0 @@ -use crate::*; - -/// MIDI message serialized to bytes -pub type MIDIMessage = Vec; - -/// Collection of serialized MIDI messages -pub type MIDIChunk = [Vec]; - -/// Add "all notes off" to the start of a buffer. -pub fn all_notes_off (output: &mut MIDIChunk) { - let mut buf = vec![]; - let msg = MidiMessage::Controller { controller: 123.into(), value: 0.into() }; - let evt = LiveEvent::Midi { channel: 0.into(), message: msg }; - evt.write(&mut buf).unwrap(); - output[0].push(buf); -} - -/// Return boxed iterator of MIDI events -pub fn parse_midi_input (input: MidiIter) -> Box + '_> { - Box::new(input.map(|RawMidi { time, bytes }|( - time as usize, - LiveEvent::parse(bytes).unwrap(), - bytes - ))) -} - -/// Write to JACK port from output buffer (containing notes from sequence and/or monitor) -pub fn write_midi_output (writer: &mut MidiWriter, output: &MIDIChunk, frames: usize) { - for time in 0..frames { - for event in output[time].iter() { - writer.write(&RawMidi { time: time as u32, bytes: &event }) - .expect(&format!("{event:?}")); - } - } -} diff --git a/crates/tek_sequencer/src/phrase.rs b/crates/tek_sequencer/src/phrase.rs deleted file mode 100644 index 91347007..00000000 --- a/crates/tek_sequencer/src/phrase.rs +++ /dev/null @@ -1,200 +0,0 @@ -use crate::*; - -#[derive(Debug)] -/// A MIDI sequence. -pub struct Phrase { - pub name: Arc>, - pub length: usize, - pub notes: PhraseData, - pub looped: Option<(usize, usize)>, - /// All notes are displayed with minimum length - pub percussive: bool -} - -pub type PhraseData = Vec>; - -impl Default for Phrase { - fn default () -> Self { - Self::new("", 0, None) - } -} - -impl Phrase { - pub fn new (name: &str, length: usize, notes: Option) -> Self { - Self { - name: Arc::new(RwLock::new(name.into())), - 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, - (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, - _ => {} - } - } - } - } - fn from_edn <'e> (ppq: usize, args: &[Edn<'e>]) -> Usually { - let mut phrase = Self::default(); - let mut name = String::new(); - let mut beats = 0usize; - let mut steps = 0usize; - edn!(edn in args { - Edn::Map(map) => { - if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) { - name = String::from(*n); - } - if let Some(Edn::Int(b)) = map.get(&Edn::Key(":beats")) { - beats = *b as usize; - phrase.length = ppq * beats; - for _ in phrase.notes.len()..phrase.length { - phrase.notes.push(Vec::with_capacity(16)) - } - } - if let Some(Edn::Int(s)) = map.get(&Edn::Key(":steps")) { - steps = *s as usize; - } - }, - Edn::List(args) => { - let time = (match args.get(0) { - Some(Edn::Key(text)) => text[1..].parse::()?, - Some(Edn::Int(i)) => *i as f64, - Some(Edn::Double(f)) => f64::from(*f), - _ => panic!("unexpected in phrase '{name}': {:?}", args.get(0)), - } * beats as f64 * ppq as f64 / steps as f64) as usize; - for edn in args[1..].iter() { - match edn { - Edn::List(args) => if let ( - Some(Edn::Int(key)), - Some(Edn::Int(vel)), - ) = ( - args.get(0), - args.get(1), - ) { - let (key, vel) = ( - u7::from((*key as u8).min(127)), - u7::from((*vel as u8).min(127)), - ); - phrase.notes[time].push(MidiMessage::NoteOn { key, vel }) - } else { - panic!("unexpected list in phrase '{name}'") - }, - _ => panic!("unexpected in phrase '{name}': {edn:?}") - } - } - }, - _ => panic!("unexpected in phrase '{name}': {edn:?}"), - }); - *phrase.name.write().unwrap() = name; - Ok(phrase) - } -} - -/// Define a MIDI phrase. -#[macro_export] macro_rules! phrase { - ($($t:expr => $msg:expr),* $(,)?) => {{ - #[allow(unused_mut)] - let mut phrase = BTreeMap::new(); - $(phrase.insert($t, vec![]);)* - $(phrase.get_mut(&$t).unwrap().push($msg);)* - phrase - }} -} - -impl Arranger { - pub fn phrase (&self) -> Option<&Arc>> { - 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(()) - }) - }); - } -} diff --git a/crates/tek_sequencer/src/scene.rs b/crates/tek_sequencer/src/scene.rs deleted file mode 100644 index e762a77a..00000000 --- a/crates/tek_sequencer/src/scene.rs +++ /dev/null @@ -1,113 +0,0 @@ -use crate::*; - -/// A collection of phrases to play on each track. -#[derive(Default)] -pub struct Scene { - pub name: Arc>, - pub clips: Vec>, -} - -impl Scene { - pub fn from_edn <'a, 'e> (args: &[Edn<'e>]) -> Usually { - let mut name = None; - let mut clips = vec![]; - edn!(edn in args { - Edn::Map(map) => { - let key = map.get(&Edn::Key(":name")); - if let Some(Edn::Str(n)) = key { - name = Some(*n); - } else { - panic!("unexpected key in scene '{name:?}': {key:?}") - } - }, - Edn::Symbol("_") => { - clips.push(None); - }, - Edn::Int(i) => { - clips.push(Some(*i as usize)); - }, - _ => panic!("unexpected in scene '{name:?}': {edn:?}") - }); - let scene = Self::new(name.unwrap_or(""), clips); - Ok(scene) - } - pub fn new (name: impl AsRef, clips: impl AsRef<[Option]>) -> Self { - let name = Arc::new(RwLock::new(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: &[Sequencer]) -> usize { - self.clips.iter().enumerate() - .filter_map(|(i, c)|c - .map(|c|tracks - .get(i) - .map(|track|track - .phrases - .get(c)))) - .filter_map(|p|p) - .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: &[Sequencer]) -> bool { - self.clips.iter().enumerate() - .all(|(track_index, phrase_index)|match phrase_index { - Some(i) => tracks - .get(track_index) - .map(|track|track.sequence == Some(*i)) - .unwrap_or(false), - None => true - }) - } -} - -pub fn scene_name_max_len (scenes: &[Scene]) -> usize { - scenes.iter() - .map(|s|s.name.read().unwrap().len()) - .fold(0, usize::max) -} - -pub fn scene_ppqs ( - tracks: &[Sequencer], - scenes: &[Scene] -) -> Vec<(usize, usize)> { - let mut total = 0; - let mut scenes: Vec<(usize, usize)> = scenes.iter().map(|scene|{ - let pulses = scene.pulses(tracks).max(96); - total = total + pulses; - (pulses, total - pulses) - }).collect(); - scenes.push((0, total)); - scenes -} - -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) - } -} diff --git a/crates/tek_sequencer/src/sequencer.rs b/crates/tek_sequencer/src/sequencer.rs index 81ab3c60..11ed70f8 100644 --- a/crates/tek_sequencer/src/sequencer.rs +++ b/crates/tek_sequencer/src/sequencer.rs @@ -1,5 +1,1566 @@ use crate::*; +const CORNERS: CornersTall = CornersTall( + NOT_DIM_GREEN +); +pub const GRAY_DIM: Style = Style { + fg: Some(Color::Gray), + bg: None, + underline_color: None, + add_modifier: Modifier::DIM, + sub_modifier: Modifier::empty(), +}; +pub const GRAY_NOT_DIM_BOLD: Style = Style { + fg: Some(Color::Gray), + bg: None, + underline_color: None, + add_modifier: Modifier::BOLD, + sub_modifier: Modifier::DIM, +}; +pub const NOT_DIM_BOLD: Style = Style { + fg: None, + bg: None, + underline_color: None, + add_modifier: Modifier::BOLD, + sub_modifier: Modifier::DIM, +}; +pub const NOT_DIM_GREEN: Style = Style { + fg: Some(Color::Rgb(96, 255, 32)), + bg: Some(COLOR_BG1), + underline_color: None, + add_modifier: Modifier::empty(), + sub_modifier: Modifier::DIM, +}; +pub const NOT_DIM: Style = Style { + fg: None, + bg: None, + underline_color: None, + add_modifier: Modifier::empty(), + sub_modifier: Modifier::DIM, +}; +pub const WHITE_NOT_DIM_BOLD: Style = Style { + fg: Some(Color::White), + bg: None, + underline_color: None, + add_modifier: Modifier::BOLD, + sub_modifier: Modifier::DIM, +}; +const STYLE_LABEL: Option