From 25e54eba4e9ac6d886f8f40c31a2a82135002a24 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 8 Oct 2024 12:05:47 +0300 Subject: [PATCH] wip: separate PhrasePlayer vs PhraseEditor --- crates/tek_sequencer/src/arranger.rs | 261 ++++++++-------- crates/tek_sequencer/src/arranger_cli.rs | 11 +- crates/tek_sequencer/src/arranger_edn.rs | 27 ++ crates/tek_sequencer/src/arranger_tui.rs | 148 +++------ crates/tek_sequencer/src/sequencer.rs | 364 ++++++++-------------- crates/tek_sequencer/src/sequencer_cli.rs | 16 +- crates/tek_sequencer/src/sequencer_edn.rs | 0 crates/tek_sequencer/src/sequencer_tui.rs | 300 +++++++++--------- 8 files changed, 491 insertions(+), 636 deletions(-) create mode 100644 crates/tek_sequencer/src/arranger_edn.rs create mode 100644 crates/tek_sequencer/src/sequencer_edn.rs diff --git a/crates/tek_sequencer/src/arranger.rs b/crates/tek_sequencer/src/arranger.rs index ba384fee..8ef7cf45 100644 --- a/crates/tek_sequencer/src/arranger.rs +++ b/crates/tek_sequencer/src/arranger.rs @@ -3,23 +3,20 @@ use crate::*; /// Root level object for standalone `tek_arranger` pub struct Arranger { /// Controls the JACK transport. - pub transport: Option>>>, + pub transport: Option>>>, /// Contains all the sequencers. - pub arrangement: Arrangement, + pub arrangement: Arrangement, + /// Pool of all phrases in the arrangement + pub phrases: Arc>>, + /// Phrase editor view + pub editor: PhraseEditor, /// This allows the sequencer view to be moved or hidden. - pub show_sequencer: Option, + pub show_sequencer: Option, /// Index of currently focused component - pub focus: usize, - /// Focus target that passes events down to sequencer - pub sequencer_proxy: SequencerProxy, + pub focus: usize, /// Slot for modal dialog displayed on top of app. - pub modal: Option>>, - - - pub phrases: Arc>>, - pub editor: PhraseEditor, + pub modal: Option>>, } - /// Represents the tracks and scenes of the composition. pub struct Arrangement { /// Name of arranger @@ -27,23 +24,77 @@ pub struct Arrangement { /// Collection of phrases. pub phrases: Arc>>, /// Collection of tracks. - pub tracks: Vec>, + pub tracks: Vec>, /// Collection of scenes. pub scenes: Vec, /// Currently selected element. - pub selected: ArrangerFocus, + pub selected: ArrangementFocus, /// Display mode of arranger - pub mode: ArrangerViewMode, + pub mode: ArrangementViewMode, /// Whether the arranger is currently focused pub focused: bool } - +/// Represents a track in the arrangement +pub struct ArrangementTrack { + /// Name of track + pub name: Arc>, + /// Inputs + pub inputs: Vec<()>, + /// MIDI player/recorder + pub player: PhrasePlayer, + /// Outputs + pub outputs: Vec<()>, +} +#[derive(Default)] +pub struct Scene { + pub name: Arc>, + pub clips: Vec>, +} +#[derive(PartialEq, Clone, Copy)] +/// Represents the current user selection in the arranger +pub enum ArrangementFocus { + /** 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), +} +/// Display mode of arranger +#[derive(PartialEq)] +pub enum ArrangementViewMode { + Horizontal, + Vertical(usize), +} +/// A collection of phrases to play on each track. +pub struct VerticalArranger<'a, E: Engine>( + pub &'a Arrangement, pub usize +); +pub struct VerticalArrangerGrid<'a>( + pub u16, pub &'a [(usize, usize)], pub &'a [(usize, usize)] +); +pub struct VerticalArrangerCursor<'a>( + pub bool, pub ArrangementFocus, pub u16, pub &'a [(usize, usize)], pub &'a [(usize, usize)], +); +pub struct HorizontalArranger<'a, E: Engine>( + pub &'a Arrangement +); +pub struct ArrangerRenameModal { + _engine: std::marker::PhantomData, + pub done: bool, + pub target: ArrangementFocus, + pub value: String, + pub result: Arc>, + pub cursor: usize +} impl Arrangement { pub fn new (name: &str, phrases: &Arc>>) -> Self { Self { name: Arc::new(RwLock::new(name.into())), - mode: ArrangerViewMode::Vertical(2), - selected: ArrangerFocus::Clip(0, 0), + mode: ArrangementViewMode::Vertical(2), + selected: ArrangementFocus::Clip(0, 0), phrases: phrases.clone(), scenes: vec![], tracks: vec![], @@ -52,25 +103,25 @@ impl Arrangement { } pub fn activate (&mut self) { match self.selected { - ArrangerFocus::Scene(s) => { + ArrangementFocus::Scene(s) => { for (track_index, track) in self.tracks.iter_mut().enumerate() { - track.playing_phrase = self.scenes[s].clips[track_index]; - track.reset = true; + track.player.phrase = self.scenes[s].clips[track_index]; + track.player.reset = true; } }, - ArrangerFocus::Clip(t, s) => { - self.tracks[t].playing_phrase = self.scenes[s].clips[t]; - self.tracks[t].reset = true; + ArrangementFocus::Clip(t, s) => { + self.tracks[t].player.phrase = self.scenes[s].clips[t]; + self.tracks[t].player.reset = true; }, _ => {} } } - pub fn sequencer (&self) -> Option<&Sequencer> { + pub fn sequencer (&self) -> Option<&ArrangementTrack> { self.selected.track() .map(|track|self.tracks.get(track)) .flatten() } - pub fn sequencer_mut (&mut self) -> Option<&mut Sequencer> { + pub fn sequencer_mut (&mut self) -> Option<&mut ArrangementTrack> { self.selected.track() .map(|track|self.tracks.get_mut(track)) .flatten() @@ -81,7 +132,7 @@ impl Arrangement { let scene = self.scenes.get(scene_index); let track = self.tracks.get_mut(track_index); if let (Some(scene), Some(track)) = (scene, track) { - track.viewing_phrase = scene.clips[track_index] + track.player.phrase = scene.clips[track_index] } } } @@ -92,17 +143,17 @@ impl Arrangement { pub fn is_last_row (&self) -> bool { let selected = self.selected; (self.scenes.len() == 0 && (selected.is_mix() || selected.is_track())) || match selected { - ArrangerFocus::Scene(s) => + ArrangementFocus::Scene(s) => s == self.scenes.len() - 1, - ArrangerFocus::Clip(_, s) => + ArrangementFocus::Clip(_, s) => s == self.scenes.len() - 1, _ => false } } - pub fn track (&self) -> Option<&Sequencer> { + pub fn track (&self) -> Option<&PhrasePlayer> { self.selected.track().map(|t|self.tracks.get(t)).flatten() } - pub fn track_mut (&mut self) -> Option<&mut Sequencer> { + pub fn track_mut (&mut self) -> Option<&mut PhrasePlayer> { self.selected.track().map(|t|self.tracks.get_mut(t)).flatten() } pub fn track_next (&mut self) { @@ -111,10 +162,10 @@ impl Arrangement { pub fn track_prev (&mut self) { self.selected.track_prev() } - pub fn track_add (&mut self, name: Option<&str>) -> Usually<&mut Sequencer> { + pub fn track_add (&mut self, name: Option<&str>) -> Usually<&mut ArrangementTrack> { self.tracks.push(name.map_or_else( - || Sequencer::new(&self.track_default_name()), - |name| Sequencer::new(name), + || PhrasePlayer::new(&self.track_default_name()), + |name| PhrasePlayer::new(name), )); let index = self.tracks.len() - 1; Ok(&mut self.tracks[index]) @@ -214,27 +265,32 @@ impl Arrangement { }); } } - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -#[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), +impl ArrangementTrack { + pub fn longest_name (tracks: &[Self]) -> usize { + tracks.iter() + .map(|s|s.name.read().unwrap().len()) + .fold(0, usize::max) + } + pub fn clip_name_lengths (tracks: &[Self]) -> 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 + } } - /// Focus identification methods -impl ArrangerFocus { +impl ArrangementFocus { pub fn description ( &self, - tracks: &Vec>, + tracks: &Vec>, scenes: &Vec, ) -> String { format!("Selected: {}", match self { @@ -342,14 +398,8 @@ impl ArrangerFocus { } } } - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -/// Display mode of arranger -#[derive(PartialEq)] -pub enum ArrangerViewMode { Horizontal, Vertical(usize) } /// Arranger display mode can be cycled -impl ArrangerViewMode { +impl ArrangementViewMode { /// Cycle arranger display mode pub fn to_next (&mut self) { *self = match self { @@ -361,39 +411,8 @@ impl ArrangerViewMode { } } } - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -pub struct VerticalArranger<'a, E: Engine>( - pub &'a Arrangement, pub usize -); -pub struct VerticalArrangerGrid<'a>( - pub u16, pub &'a [(usize, usize)], pub &'a [(usize, usize)] -); -pub struct VerticalArrangerCursor<'a>( - pub bool, pub ArrangerFocus, pub u16, pub &'a [(usize, usize)], pub &'a [(usize, usize)], -); - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -pub struct HorizontalArranger<'a, E: Engine>( - pub &'a Arrangement -); - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -/// Appears on first run (i.e. if state dir is missing). -pub struct ArrangerRenameModal { - _engine: std::marker::PhantomData, - pub done: bool, - pub target: ArrangerFocus, - pub value: String, - pub result: Arc>, - pub cursor: usize -} - impl ArrangerRenameModal { - pub fn new (target: ArrangerFocus, value: &Arc>) -> Self { + pub fn new (target: ArrangementFocus, value: &Arc>) -> Self { Self { _engine: Default::default(), done: false, @@ -404,52 +423,19 @@ impl ArrangerRenameModal { } } } - impl Exit for ArrangerRenameModal { fn exited (&self) -> bool { self.done } fn exit (&mut self) { self.done = true } } - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -/// 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, } + Self { + name: Arc::new(RwLock::new(name.as_ref().into())), + clips: clips.as_ref().iter().map(|x|x.clone()).collect(), + } } /// Returns the pulse length of the longest phrase in the scene - pub fn pulses (&self, tracks: &[Sequencer]) -> usize { + pub fn pulses (&self, tracks: &[ArrangementTrack]) -> usize { self.clips.iter().enumerate() .filter_map(|(i, c)|c .map(|c|tracks @@ -462,14 +448,29 @@ impl Scene { .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 { + pub fn is_playing (&self, tracks: &[ArrangementTrack]) -> bool { self.clips.iter().enumerate() .all(|(track_index, phrase_index)|match phrase_index { Some(i) => tracks .get(track_index) - .map(|track|track.playing_phrase == Some(*i)) + .map(|track|track.player.phrase == Some(*i)) .unwrap_or(false), None => true }) } + pub fn ppqs (tracks: &[ArrangementTrack], scenes: &[Self]) -> Vec<(usize, usize)> { + let mut total = 0; + let mut scenes: Vec<(usize, usize)> = scenes.iter().map(|scene|{ + let pulses = scene.pulses(tracks).max(PPQ); + total = total + pulses; + (pulses, total - pulses) + }).collect(); + scenes.push((0, total)); + scenes + } + pub fn longest_name (scenes: &[Self]) -> usize { + scenes.iter() + .map(|s|s.name.read().unwrap().len()) + .fold(0, usize::max) + } } diff --git a/crates/tek_sequencer/src/arranger_cli.rs b/crates/tek_sequencer/src/arranger_cli.rs index dc0b8c7d..3921bc7f 100644 --- a/crates/tek_sequencer/src/arranger_cli.rs +++ b/crates/tek_sequencer/src/arranger_cli.rs @@ -58,12 +58,11 @@ impl ArrangerCli { )? ); Tui::run(Arc::new(RwLock::new(Arranger { - transport: self.transport.then_some(transport), - show_sequencer: Some(tek_core::Direction::Down), - focus: 0, - sequencer_proxy: SequencerProxy(Default::default(), false), - modal: None, - editor: PhraseEditor::new(), + transport: self.transport.then_some(transport), + show_sequencer: Some(tek_core::Direction::Down), + focus: 0, + modal: None, + editor: PhraseEditor::new(), arrangement, phrases, })))?; diff --git a/crates/tek_sequencer/src/arranger_edn.rs b/crates/tek_sequencer/src/arranger_edn.rs new file mode 100644 index 00000000..5d192e04 --- /dev/null +++ b/crates/tek_sequencer/src/arranger_edn.rs @@ -0,0 +1,27 @@ +use crate::*; + +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) + } +} diff --git a/crates/tek_sequencer/src/arranger_tui.rs b/crates/tek_sequencer/src/arranger_tui.rs index 6a970616..538f45ec 100644 --- a/crates/tek_sequencer/src/arranger_tui.rs +++ b/crates/tek_sequencer/src/arranger_tui.rs @@ -1,5 +1,4 @@ use crate::*; - /// The standalone arranger consists of transport, clip grid, and sequencer. impl Content for Arranger { type Engine = Tui; @@ -8,17 +7,11 @@ impl Content for Arranger { add(&Stack::down(move|add|{ add(&self.transport)?; let arrangement = &self.arrangement as &dyn Widget; - if let (Some(direction), Some(sequencer)) = ( - self.show_sequencer, - self.arrangement.sequencer(), - ) { - let sequencer = sequencer as &dyn Widget; - add(&arrangement.split( - direction, - 20, - self.phrases.clone() - .split(direction.ccw(), 20, sequencer) - .min_y(20)).fill_y()) + if let Some(direction) = self.show_sequencer { + let editor = &self.editor as &dyn Widget; + add(&arrangement.split(direction, 20, self.phrases.clone() + .split(direction.ccw(), 20, editor) + .min_y(20)).fill_y()) } else { add(&self.arrangement) } @@ -32,7 +25,6 @@ impl Content for Arranger { }) } } - /// Handle top-level events in standalone arranger. impl Handle for Arranger { fn handle (&mut self, from: &TuiInput) -> Perhaps { @@ -46,13 +38,6 @@ impl Handle for Arranger { let focus = self.focus; let is_first_row = self.arrangement.is_first_row(); let is_last_row = self.arrangement.is_last_row(); - let mut focused_handle = || { - if focus == 2 { - self.arrangement.sequencer_mut().handle(from) - } else { - self.focused_mut().handle(from) - } - }; match from.event() { key!(KeyCode::Char(' ')) => { if let Some(ref mut transport) = self.transport { @@ -73,22 +58,23 @@ impl Handle for Arranger { } else if focus == 1 && is_last_row { self.focus_next(); } else { - return focused_handle() + return + return self.focused_mut().handle(from) + } }, key!(KeyCode::Up) => { if focus == 1 && is_first_row { self.focus_prev(); } else { - return focused_handle() + return self.focused_mut().handle(from) } }, - _ => return focused_handle() + _ => return self.focused_mut().handle(from) } Ok(Some(true)) } } - /// Focusable items in standalone arranger. impl Focus<3, Tui> for Arranger { fn focus (&self) -> usize { @@ -98,27 +84,23 @@ impl Focus<3, Tui> for Arranger { &mut self.focus } fn focusable (&self) -> [&dyn Focusable;3] { - focusables!(self.transport, self.arrangement, self.sequencer_proxy) + focusables!(self.transport, self.arrangement, self.editor) } fn focusable_mut (&mut self) -> [&mut dyn Focusable;3] { - focusables_mut!(self.transport, self.arrangement, self.sequencer_proxy) + focusables_mut!(self.transport, self.arrangement, self.editor) } } - impl Arranger { pub fn rename_selected (&mut self) { - self.modal = Some(Box::new(ArrangerRenameModal::new( - self.arrangement.selected, - &match self.arrangement.selected { - ArrangerFocus::Mix => self.arrangement.name.clone(), - ArrangerFocus::Track(t) => self.arrangement.tracks[t].name.clone(), - ArrangerFocus::Scene(s) => self.arrangement.scenes[s].name.clone(), - ArrangerFocus::Clip(t, s) => self.arrangement.tracks[t].phrases[s].read().unwrap().name.clone(), - } - ))); + let Arrangement { selected, ref name, ref tracks, ref scenes, .. } = self.arrangement; + self.modal = Some(Box::new(ArrangerRenameModal::new(selected, &match selected { + ArrangementFocus::Mix => name.clone(), + ArrangementFocus::Track(t) => tracks[t].name.clone(), + ArrangementFocus::Scene(s) => scenes[s].name.clone(), + ArrangementFocus::Clip(t, s) => tracks[t].phrases[s].read().unwrap().name.clone(), + }))); } } - impl Focusable for Arrangement { fn is_focused (&self) -> bool { self.focused @@ -127,7 +109,6 @@ impl Focusable for Arrangement { self.focused = focused } } - impl Handle for Arrangement { fn handle (&mut self, from: &TuiInput) -> Perhaps { match from.event() { @@ -138,7 +119,7 @@ impl Handle for Arrangement { // cursor_up: move cursor up key!(KeyCode::Up) => { match self.mode { - ArrangerViewMode::Horizontal => self.track_prev(), + ArrangementViewMode::Horizontal => self.track_prev(), _ => self.scene_prev(), }; self.show_phrase(); @@ -146,7 +127,7 @@ impl Handle for Arrangement { // cursor_down key!(KeyCode::Down) => { match self.mode { - ArrangerViewMode::Horizontal => self.track_next(), + ArrangementViewMode::Horizontal => self.track_next(), _ => self.scene_next(), }; self.show_phrase(); @@ -154,7 +135,7 @@ impl Handle for Arrangement { // cursor left key!(KeyCode::Left) => { match self.mode { - ArrangerViewMode::Horizontal => self.scene_prev(), + ArrangementViewMode::Horizontal => self.scene_prev(), _ => self.track_prev(), }; self.show_phrase(); @@ -162,7 +143,7 @@ impl Handle for Arrangement { // cursor right key!(KeyCode::Right) => { match self.mode { - ArrangerViewMode::Horizontal => self.scene_next(), + ArrangementViewMode::Horizontal => self.scene_next(), _ => self.track_next(), }; self.show_phrase(); @@ -211,14 +192,13 @@ impl Handle for Arrangement { Ok(Some(true)) } } - impl Content for Arrangement { type Engine = Tui; fn content (&self) -> impl Widget { Layers::new(move |add|{ match self.mode { - ArrangerViewMode::Horizontal => add(&HorizontalArranger(&self)), - ArrangerViewMode::Vertical(factor) => add(&VerticalArranger(&self, factor)) + ArrangementViewMode::Horizontal => add(&HorizontalArranger(&self)), + ArrangementViewMode::Vertical(factor) => add(&VerticalArranger(&self, factor)) }?; add(&Align::SE(self.selected.description( &self.tracks, @@ -227,24 +207,21 @@ impl Content for Arrangement { }) } } - -/////////////////////////////////////////////////////////////////////////////////////////////////// - impl<'a> Content for VerticalArranger<'a, Tui> { type Engine = Tui; fn content (&self) -> impl Widget { let Self(state, factor) = self; let (cols, rows) = if *factor == 0 {( - track_clip_name_lengths(state.tracks.as_slice()), - scene_ppqs(state.tracks.as_slice(), state.scenes.as_slice()), + ArrangementTrack::clip_name_lengths(state.tracks.as_slice()), + Scene::ppqs(state.tracks.as_slice(), state.scenes.as_slice()), )} else {( - track_clip_name_lengths(state.tracks.as_slice()), + ArrangementTrack::clip_name_lengths(state.tracks.as_slice()), (0..=state.scenes.len()).map(|i|(factor*PPQ, factor*PPQ*i)).collect::>(), )}; //let height = rows.last().map(|(w,y)|(y+w)/PPQ).unwrap_or(16); - let tracks: &[Sequencer] = state.tracks.as_ref(); + let tracks: &[ArrangementTrack] = state.tracks.as_ref(); let scenes: &[Scene] = state.scenes.as_ref(); - let offset = 4 + scene_name_max_len(scenes) as u16; + let offset = 4 + Scene::longest_name(scenes) as u16; Layers::new(move |add|{ let rows: &[(usize, usize)] = rows.as_ref(); let cols: &[(usize, usize)] = cols.as_ref(); @@ -267,7 +244,7 @@ impl<'a> Content for VerticalArranger<'a, Tui> { let name = name.read().unwrap(); let name = format!("{clip:02} {}", name); add(&name.as_str().push_x(1).fixed_x(w))?; - if (track as &Sequencer<_>).playing_phrase == Some(*clip) { + if (track as &PhrasePlayer<_>).phrase == Some(*clip) { color = COLOR_PLAYING } else { color = COLOR_BG1 @@ -303,39 +280,6 @@ impl<'a> Content for VerticalArranger<'a, Tui> { .fg(Color::Rgb(70, 80, 50)))) } } - -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 -} - -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(PPQ); - total = total + pulses; - (pulses, total - pulses) - }).collect(); - scenes.push((0, total)); - scenes -} - -pub fn scene_name_max_len (scenes: &[Scene]) -> usize { - scenes.iter() - .map(|s|s.name.read().unwrap().len()) - .fold(0, usize::max) -} - impl<'a> Widget for VerticalArrangerGrid<'a> { type Engine = Tui; fn render (&self, to: &mut TuiOutput) -> Usually<()> { @@ -364,7 +308,6 @@ impl<'a> Widget for VerticalArrangerGrid<'a> { Ok(()) } } - impl<'a> Widget for VerticalArrangerCursor<'a> { type Engine = Tui; fn render (&self, to: &mut TuiOutput) -> Usually<()> { @@ -392,21 +335,21 @@ impl<'a> Widget for VerticalArrangerCursor<'a> { let mut scene_area: Option<[u16;4]> = None; let mut clip_area: Option<[u16;4]> = None; let area = match selected { - ArrangerFocus::Mix => { + ArrangementFocus::Mix => { if focused { to.fill_bg(area, Color::Rgb(40, 50, 30)); } area }, - ArrangerFocus::Track(t) => { + ArrangementFocus::Track(t) => { track_area = Some(get_track_area(t)); area }, - ArrangerFocus::Scene(s) => { + ArrangementFocus::Scene(s) => { scene_area = Some(get_scene_area(s)); area }, - ArrangerFocus::Clip(t, s) => { + ArrangementFocus::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)); @@ -436,9 +379,6 @@ impl<'a> Widget for VerticalArrangerCursor<'a> { Ok(()) } } - -/////////////////////////////////////////////////////////////////////////////////////////////////// - impl<'a> Content for HorizontalArranger<'a, Tui> { type Engine = Tui; fn content (&self) -> impl Widget { @@ -653,15 +593,6 @@ impl<'a> Content for HorizontalArranger<'a, Tui> { ) } } - -pub fn track_name_max_len (tracks: &[Sequencer]) -> usize { - tracks.iter() - .map(|s|s.name.read().unwrap().len()) - .fold(0, usize::max) -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// - impl Content for ArrangerRenameModal { type Engine = Tui; fn content (&self) -> impl Widget { @@ -673,10 +604,10 @@ impl Content for ArrangerRenameModal { //to.fill_bg(bg_area, COLOR_BG1); //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:", + //ArrangementFocus::Mix => "Rename project:", + //ArrangementFocus::Track(_) => "Rename track:", + //ArrangementFocus::Scene(_) => "Rename scene:", + //ArrangementFocus::Clip(_, _) => "Rename clip:", //}; //let style = Some(Style::default().not_bold().white().not_dim()); //to.blit(&label, area.x() + 3, y, style); @@ -688,7 +619,6 @@ impl Content for ArrangerRenameModal { //Ok(()) } } - impl Handle for ArrangerRenameModal { fn handle (&mut self, from: &TuiInput) -> Perhaps { match from.event() { diff --git a/crates/tek_sequencer/src/sequencer.rs b/crates/tek_sequencer/src/sequencer.rs index feda3e01..ee54b01b 100644 --- a/crates/tek_sequencer/src/sequencer.rs +++ b/crates/tek_sequencer/src/sequencer.rs @@ -1,53 +1,33 @@ use crate::*; - -/// A collection of MIDI messages. -pub type PhraseData = Vec>; - -/// MIDI message serialized to bytes -pub type MIDIMessage = Vec; - +/// MIDI message structural +pub type PhraseData = Vec>; +/// MIDI message serialized +pub type PhraseMessage = Vec; /// Collection of serialized MIDI messages -pub type MIDIChunk = [Vec]; - -/// Contains all phrases in the project +pub type PhraseChunk = [Vec]; +/// Root level object for standalone `tek_sequencer` +pub struct Sequencer { + /// Controls the JACK transport. + pub transport: Option>>>, + /// Pool of all phrases available to the sequencer + pub phrases: Arc>>, + /// Phrase editor view + pub editor: PhraseEditor, +} +/// Contains all phrases in a project pub struct PhrasePool { _engine: PhantomData, + /// Phrases in the pool pub phrases: Vec>>>, } - -impl PhrasePool { - pub fn new () -> Self { - Self { - _engine: Default::default(), - phrases: vec![Arc::new(RwLock::new(Some(Phrase::default())))] - } - } -} - -/// Contains state for viewing and editing a phrase -pub struct PhraseEditor { - _engine: PhantomData, - pub phrase: Arc>>, -} - -impl PhraseEditor { - pub fn new () -> Self { - Self { - _engine: Default::default(), - phrase: Arc::new(RwLock::new(None)), - } - } - pub fn show (&mut self, phrase: &Arc>>) { - self.phrase = phrase.clone(); - } -} - /// A MIDI sequence. #[derive(Debug)] pub struct Phrase { /// Name of phrase pub name: Arc>, - /// Length of phrase + /// Temporal resolution in pulses per quarter note + pub ppq: usize, + /// Length of phrase in pulses pub length: usize, /// Notes in phrase pub notes: PhraseData, @@ -60,15 +40,89 @@ pub struct Phrase { /// All notes are displayed with minimum length pub percussive: bool, } - +/// Contains state for viewing and editing a phrase +pub struct PhraseEditor { + _engine: PhantomData, + /// Phrase being played + pub phrase: Option>>, + /// The full piano keys are rendered to this buffer + pub keys: Buffer, + /// The full piano roll is rendered to this buffer + pub buffer: BigBuffer, + /// Cursor/scroll/zoom in pitch axis + pub note_axis: FixedAxis, + /// Cursor/scroll/zoom in time axis + pub time_axis: ScaledAxis, + /// Whether this widget is focused + pub focused: bool, + /// Whether note enter mode is enabled + pub entered: bool, + /// Display mode + pub mode: bool, + /// Notes currently held at input + pub notes_in: Arc>, + /// Notes currently held at output + pub notes_out: Arc>, +} +/// Phrase player. +pub struct PhrasePlayer { + _engine: PhantomData, + /// Phrase being played + pub phrase: Option>>, + /// Notes currently held at input + pub notes_in: Arc>, + /// Notes currently held at output + pub notes_out: Arc>, + /// Current point in playing phrase + pub now: usize, + /// Play input through output. + pub monitoring: bool, + /// Write input to sequence. + pub recording: bool, + /// Overdub input to sequence. + pub overdub: bool, + /// Output from current sequence. + pub midi_out: Option>, + /// MIDI output buffer + midi_out_buf: Vec>>, + /// Send all notes off + pub reset: bool, // TODO?: after Some(nframes) +} +impl PhrasePool { + pub fn new () -> Self { + Self { + _engine: Default::default(), + phrases: vec![Arc::new(RwLock::new(Some(Phrase::default())))] + } + } +} +impl PhraseEditor { + pub fn new () -> Self { + Self { + _engine: Default::default(), + phrase: None, + notes_in: Arc::new(RwLock::new([false;128])), + notes_out: Arc::new(RwLock::new([false;128])), + keys: keys_vert(), + buffer: Default::default(), + note_axis: FixedAxis { start: 12, point: Some(36) }, + time_axis: ScaledAxis { start: 0, scale: 24, point: Some(0) }, + focused: false, + entered: false, + mode: false, + } + } +} impl Default for Phrase { fn default () -> Self { Self::new("", false, 0, None) } } - impl Phrase { - pub fn new (name: &str, loop_on: bool, length: usize, notes: Option) -> Self { + pub fn new ( + name: &str, loop_on: bool, length: usize, notes: Option + ) -> Self { Self { - name: Arc::new(RwLock::new(name.into())), + name: Arc::new(RwLock::new(name.into())), + ppq: PPQ, length, notes: notes.unwrap_or(vec![Vec::with_capacity(16);length]), loop_on, @@ -106,7 +160,7 @@ impl Phrase { /// Write a chunk of MIDI events to an output port. pub fn process_out ( &self, - output: &mut MIDIChunk, + output: &mut PhraseChunk, notes_on: &mut [bool;128], timebase: &Arc, (frame0, frames, _): (usize, usize, f64), @@ -130,136 +184,21 @@ impl Phrase { } } } - pub 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) - } } - -/// Phrase player. -pub struct Sequencer { - pub name: Arc>, - pub mode: bool, - pub focused: bool, - pub entered: bool, - pub transport: Option>>>, - /// The full piano roll is rendered to this buffer - pub buffer: BigBuffer, - /// The full piano keys is rendered to this buffer - pub keys: Buffer, - /// Highlight input keys - pub keys_in: [bool; 128], - /// Highlight output keys - pub keys_out: [bool; 128], - /// Current point in playing phrase - pub now: usize, - /// Temporal resolution (default 96) - pub ppq: usize, - /// Scroll/room in pitch axis - pub note_axis: FixedAxis, - /// Scroll/room in time axis - pub time_axis: ScaledAxis, - /// Play input through output. - pub monitoring: bool, - /// Write input to sequence. - pub recording: bool, - /// Overdub input to sequence. - pub overdub: bool, - /// Map: tick -> MIDI events at tick - pub phrases: Vec>>, - /// Phrase currently being played - pub playing_phrase: Option, - /// Phrase currently being viewed - pub viewing_phrase: Option, - /// Output from current sequence. - pub midi_out: Option>, - /// MIDI output buffer - midi_out_buf: Vec>>, - /// Send all notes off - pub reset: bool, // TODO?: after Some(nframes) - /// Highlight keys on piano roll. - pub notes_in: [bool;128], - /// Highlight keys on piano roll. - pub notes_out: [bool;128], -} - -impl Sequencer { +impl PhrasePlayer { pub fn new (name: &str) -> Self { Self { - name: Arc::new(RwLock::new(name.into())), + _engine: Default::default(), + phrase: None, + notes_in: Arc::new(RwLock::new([false;128])), + notes_out: Arc::new(RwLock::new([false;128])), monitoring: false, recording: false, overdub: true, - phrases: vec![], - viewing_phrase: None, - playing_phrase: None, midi_out: None, midi_out_buf: vec![Vec::with_capacity(16);16384], reset: true, - notes_in: [false;128], - notes_out: [false;128], - buffer: Default::default(), - keys: keys_vert(), - entered: false, - focused: false, - mode: false, - keys_in: [false;128], - keys_out: [false;128], now: 0, - ppq: PPQ, - transport: None, - note_axis: FixedAxis { start: 12, point: Some(36) }, - time_axis: ScaledAxis { start: 0, scale: 24, point: Some(0) }, } } pub fn toggle_monitor (&mut self) { @@ -281,7 +220,7 @@ impl Sequencer { reset: bool, scope: &ProcessScope, (frame0, frames): (usize, usize), - (_usec0, _usecs): (usize, usize), + (_usec0, _usecs): (usize, usize), period: f64, ) { if self.midi_out.is_some() { @@ -298,15 +237,15 @@ impl Sequencer { } } if let ( - Some(TransportState::Rolling), Some((start_frame, _)), Some(phrase) - ) = ( - playing, started, self.playing_phrase.and_then(|id|self.phrases.get_mut(id)) - ) { + Some(TransportState::Rolling), + Some((start_frame, _)), + Some(ref phrase) + ) = (playing, started, &self.phrase) { phrase.read().map(|phrase|{ if self.midi_out.is_some() { phrase.process_out( &mut self.midi_out_buf, - &mut self.notes_out, + &mut self.notes_out.write().unwrap(), timebase, (frame0.saturating_sub(start_frame), frames, period) ); @@ -317,6 +256,7 @@ impl Sequencer { // Monitor and record input if input.is_some() && (self.recording || self.monitoring) { // For highlighting keys and note repeat + let mut notes_in = self.notes_in.write().unwrap(); for (frame, event, bytes) in parse_midi_input(input.unwrap()) { match event { LiveEvent::Midi { message, .. } => { @@ -337,10 +277,10 @@ impl Sequencer { } match message { MidiMessage::NoteOn { key, .. } => { - self.notes_in[key.as_int() as usize] = true; + notes_in[key.as_int() as usize] = true; } MidiMessage::NoteOff { key, .. } => { - self.notes_in[key.as_int() as usize] = false; + notes_in[key.as_int() as usize] = false; }, _ => {} } @@ -350,50 +290,45 @@ impl Sequencer { } } } else if input.is_some() && self.midi_out.is_some() && self.monitoring { + let mut notes_in = self.notes_in.write().unwrap(); for (frame, event, bytes) in parse_midi_input(input.unwrap()) { - self.process_monitor_event(frame, &event, bytes) + match event { + LiveEvent::Midi { message, .. } => { + self.midi_out_buf[frame].push(bytes.to_vec()); + match message { + MidiMessage::NoteOn { key, .. } => { + notes_in[key.as_int() as usize] = true; + } + MidiMessage::NoteOff { key, .. } => { + notes_in[key.as_int() as usize] = false; + }, + _ => {} + } + }, + _ => {} + } } } if let Some(out) = &mut self.midi_out { - write_midi_output(&mut out.writer(scope), &self.midi_out_buf, frames); - } - } - #[inline] - fn process_monitor_event (&mut self, frame: usize, event: &LiveEvent, bytes: &[u8]) { - match event { - LiveEvent::Midi { message, .. } => { - self.write_to_output_buffer(frame, bytes); - self.process_monitor_message(&message); - }, - _ => {} - } - } - #[inline] fn write_to_output_buffer (&mut self, frame: usize, bytes: &[u8]) { - self.midi_out_buf[frame].push(bytes.to_vec()); - } - #[inline] - fn process_monitor_message (&mut self, message: &MidiMessage) { - match message { - MidiMessage::NoteOn { key, .. } => { - self.notes_in[key.as_int() as usize] = true; + let writer = &mut out.writer(scope); + let output = &self.midi_out_buf; + for time in 0..frames { + for event in output[time].iter() { + writer.write(&RawMidi { time: time as u32, bytes: &event }) + .expect(&format!("{event:?}")); + } } - MidiMessage::NoteOff { key, .. } => { - self.notes_in[key.as_int() as usize] = false; - }, - _ => {} } } } - /// Add "all notes off" to the start of a buffer. -pub fn all_notes_off (output: &mut MIDIChunk) { +pub fn all_notes_off (output: &mut PhraseChunk) { 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 }|( @@ -402,30 +337,3 @@ pub fn parse_midi_input (input: MidiIter) -> Box(pub PhantomData, pub bool); - -impl Handle for SequencerProxy { - fn handle (&mut self, _: &TuiInput) -> Perhaps { unreachable!() } -} - -impl Content for SequencerProxy { - type Engine = Tui; - fn content (&self) -> impl Widget { "" } -} - -impl Focusable for SequencerProxy { - fn is_focused (&self) -> bool { self.1 } - fn set_focused (&mut self, focus: bool) { self.1 = focus } -} diff --git a/crates/tek_sequencer/src/sequencer_cli.rs b/crates/tek_sequencer/src/sequencer_cli.rs index 5fb513ae..b67c07be 100644 --- a/crates/tek_sequencer/src/sequencer_cli.rs +++ b/crates/tek_sequencer/src/sequencer_cli.rs @@ -18,22 +18,24 @@ pub struct SequencerCli { impl SequencerCli { fn run (&self) -> Usually<()> { - let mut seq = Sequencer::new(""); + let seq = Sequencer { + transport: self.transport.unwrap_or(false) + .then_some(Arc::new(RwLock::new(TransportToolbar::new(None)))), + }; if let Some(name) = self.name.as_ref() { - seq.name = Arc::new(RwLock::new(name.clone())); + // TODO + //seq.name = Arc::new(RwLock::new(name.clone())); } if let Some(ppq) = self.ppq { - seq.ppq = ppq; + // TODO + //seq.ppq = ppq; } if let Some(length) = self.length { - // TODO FIXME WTF + // TODO //if let Some(phrase) = seq.phrase.as_mut() { //phrase.write().unwrap().length = length; //} } - if self.transport == Some(true) { - seq.transport = Some(Arc::new(RwLock::new(TransportToolbar::new(None)))); - } Tui::run(Arc::new(RwLock::new(seq))).map(|_|()) } } diff --git a/crates/tek_sequencer/src/sequencer_edn.rs b/crates/tek_sequencer/src/sequencer_edn.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/tek_sequencer/src/sequencer_tui.rs b/crates/tek_sequencer/src/sequencer_tui.rs index d3df9761..0576a4b6 100644 --- a/crates/tek_sequencer/src/sequencer_tui.rs +++ b/crates/tek_sequencer/src/sequencer_tui.rs @@ -1,5 +1,4 @@ use crate::*; - impl Content for PhrasePool { type Engine = Tui; fn content (&self) -> impl Widget { @@ -14,21 +13,154 @@ impl Content for PhrasePool { .fg(Color::Rgb(70, 80, 50)))) } } - -impl Sequencer { +impl Content for PhraseEditor { + type Engine = Tui; + fn content (&self) -> impl Widget { + let field_bg = Color::Rgb(28, 35, 25); + let toolbar = Stack::down(move|add|{ + //let name = format!("{:>9}", self.name.read().unwrap().as_str()); + //add(&col!("Track:", TuiStyle::bg(name.as_str(), field_bg)))?; + if let Some(phrase) = self.phrase { + let phrase = phrase.read().unwrap(); + let length = format!("{}q{}p", phrase.length / PPQ, phrase.length % PPQ); + let length = format!("{:>9}", &length); + let loop_on = format!("{:>9}", if phrase.loop_on { "on" } else { "off" }); + let loop_start = format!("{:>9}", phrase.loop_start); + let loop_end = format!("{:>9}", phrase.loop_length); + add(&"")?; + add(&col!("Length:", TuiStyle::bg(length.as_str(), field_bg)))?; + add(&col!("Loop:", TuiStyle::bg(loop_on.as_str(), field_bg)))?; + add(&col!("L. start:", TuiStyle::bg(loop_start.as_str(), field_bg)))?; + add(&col!("L. length:", TuiStyle::bg(loop_end.as_str(), field_bg)))?; + } + Ok(()) + }).min_x(10); + let content = lay!( + // keys + CustomWidget::new(|_|Ok(Some([32,4])), |to: &mut TuiOutput|{ + if to.area().h() < 2 { return Ok(()) } + Ok(to.buffer_update(to.area().set_w(5).shrink_y(2), &|cell, x, y|{ + let y = y + self.note_axis.start as u16; + if x < self.keys.area.width && y < self.keys.area.height { + *cell = self.keys.get(x, y).clone() + } + })) + }).fill_y(), + // playhead + CustomWidget::new(|_|Ok(Some([32,2])), |to: &mut TuiOutput|{ + if let Some(phrase) = self.phrase { + let time_0 = self.time_axis.start; + let time_z = self.time_axis.scale; + let now = self.now % phrase.read().unwrap().length; + let [x, y, width, _] = to.area(); + let x2 = x as usize + Self::H_KEYS_OFFSET; + let x3 = x as usize + width as usize; + for x in x2..x3 { + let step = (time_0 + x2) * time_z; + let next_step = (time_0 + x2 + 1) * time_z; + to.blit(&"-", x as u16, y, Some(PhraseEditor::::style_timer_step( + now, step as usize, next_step as usize + ))); + } + } + Ok(()) + }).fill_x(), + // notes + CustomWidget::new(|_|Ok(Some([32,4])), |to: &mut TuiOutput|{ + let offset = Self::H_KEYS_OFFSET as u16; + if to.area().h() < 2 || to.area().w() < offset { return Ok(()) } + let area = to.area().push_x(offset).shrink_x(offset).shrink_y(2); + Ok(to.buffer_update(area, &move |cell, x, y|{ + cell.set_bg(Color::Rgb(20, 20, 20)); + let src_x = ((x as usize + self.time_axis.start) * self.time_axis.scale) as usize; + let src_y = (y as usize + self.note_axis.start) as usize; + if src_x < self.buffer.width && src_y < self.buffer.height - 1 { + let src = self.buffer.get(src_x, self.buffer.height - src_y); + src.map(|src|{ cell.set_symbol(src.symbol()); cell.set_fg(src.fg); }); + } + })) + }).fill_x(), + // note cursor + CustomWidget::new(|_|Ok(Some([1,1])), |to: &mut TuiOutput|{ + let area = to.area(); + if let (Some(time), Some(note)) = (self.time_axis.point, self.note_axis.point) { + let x = area.x() + Self::H_KEYS_OFFSET as u16 + time as u16; + let y = area.y() + 1 + note as u16 / 2; + let c = if note % 2 == 0 { "▀" } else { "▄" }; + to.blit(&c, x, y, self.style_focus()); + } + Ok(()) + }), + // zoom + CustomWidget::new(|_|Ok(Some([10,1])), |to: &mut TuiOutput|{ + let [x, y, w, h] = to.area.xywh(); + let quant = ppq_to_name(self.time_axis.scale); + Ok(to.blit( + &quant, + x + w - 1 - quant.len() as u16, + y + h - 2, + self.style_focus() + )) + }), + ); + row!(toolbar, content).fill_x() + .bg(Color::Rgb(40, 50, 30)) + .border(Lozenge(Style::default() + .bg(Color::Rgb(40, 50, 30)) + .fg(Color::Rgb(70, 80, 50)))) + } +} +impl Handle for PhraseEditor { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + match from.event() { + key!(KeyCode::Char('`')) => { + self.mode = !self.mode; + }, + key!(KeyCode::Up) => match self.entered { + true => { self.note_axis.point_dec(); }, + false => { self.note_axis.start_dec(); }, + }, + key!(KeyCode::Down) => match self.entered { + true => { self.note_axis.point_inc(); }, + false => { self.note_axis.start_inc(); }, + }, + key!(KeyCode::Left) => match self.entered { + true => { self.time_axis.point_dec(); }, + false => { self.time_axis.start_dec(); }, + }, + key!(KeyCode::Right) => match self.entered { + true => { self.time_axis.point_inc(); }, + false => { self.time_axis.start_inc(); }, + }, + _ => { + return Ok(None) + } + } + return Ok(Some(true)) + } +} +impl Focusable for PhraseEditor { + fn is_focused (&self) -> bool { + self.focused + } + fn set_focused (&mut self, focused: bool) { + self.focused = focused + } +} +impl PhraseEditor { const H_KEYS_OFFSET: usize = 5; /// Select which pattern to display. This pre-renders it to the buffer at full resolution. - pub fn show (&mut self, index: Option) { - self.viewing_phrase = index; - if let Some(phrase) = index.map(|index|self.phrases.get(index)).flatten() { + pub fn show (&mut self, phrase: Option<&Arc>>) { + if let Some(phrase) = phrase { + self.phrase = Some(phrase.clone()); let width = usize::MAX.min(phrase.read().unwrap().length); let mut buffer = BigBuffer::new(width, 64); let phrase = phrase.read().unwrap(); - Self::fill_seq_bg(&mut buffer, phrase.length, self.ppq); + Self::fill_seq_bg(&mut buffer, phrase.length, phrase.ppq); Self::fill_seq_fg(&mut buffer, &phrase); self.buffer = buffer; } else { - self.viewing_phrase = None; + self.phrase = None; self.buffer = Default::default(); } } @@ -121,130 +253,18 @@ impl Sequencer { } pub fn index_to_color (&self, index: u16, default: Color) -> Color { let index = index as usize; - if self.keys_in[index] && self.keys_out[index] { + let (notes_in, notes_out) = (self.notes_in.read().unwrap(), self.notes_out.read().unwrap()); + if notes_in[index] && notes_out[index] { Color::Yellow - } else if self.keys_in[index] { + } else if notes_in[index] { Color::Red - } else if self.keys_out[index] { + } else if notes_out[index] { Color::Green } else { default } } } - -impl Content for Sequencer { - type Engine = Tui; - fn content (&self) -> impl Widget { - let field_bg = Color::Rgb(28, 35, 25); - let toolbar = Stack::down(move|add|{ - let name = format!("{:>9}", self.name.read().unwrap().as_str()); - add(&col!("Track:", TuiStyle::bg(name.as_str(), field_bg)))?; - if let Some(phrase) = self.viewing_phrase - .map(|index|self.phrases.get(index)) - .flatten() - { - let phrase = phrase.read().unwrap(); - let length = format!("{}q{}p", phrase.length / PPQ, phrase.length % PPQ); - let length = format!("{:>9}", &length); - let loop_on = format!("{:>9}", if phrase.loop_on { "on" } else { "off" }); - let loop_start = format!("{:>9}", phrase.loop_start); - let loop_end = format!("{:>9}", phrase.loop_length); - add(&"")?; - add(&col!("Length:", TuiStyle::bg(length.as_str(), field_bg)))?; - add(&col!("Loop:", TuiStyle::bg(loop_on.as_str(), field_bg)))?; - add(&col!("L. start:", TuiStyle::bg(loop_start.as_str(), field_bg)))?; - add(&col!("L. length:", TuiStyle::bg(loop_end.as_str(), field_bg)))?; - } - Ok(()) - }).min_x(10); - let content = lay!( - // keys - CustomWidget::new(|_|Ok(Some([32,4])), |to: &mut TuiOutput|{ - if to.area().h() < 2 { return Ok(()) } - Ok(to.buffer_update(to.area().set_w(5).shrink_y(2), &|cell, x, y|{ - let y = y + self.note_axis.start as u16; - if x < self.keys.area.width && y < self.keys.area.height { - *cell = self.keys.get(x, y).clone() - } - })) - }).fill_y(), - // playhead - CustomWidget::new(|_|Ok(Some([32,2])), |to: &mut TuiOutput|{ - if let Some(phrase) = self.viewing_phrase - .map(|index|self.phrases.get(index)) - .flatten() - { - let time_0 = self.time_axis.start; - let time_z = self.time_axis.scale; - let now = self.now % phrase.read().unwrap().length; - let [x, y, width, _] = to.area(); - let x2 = x as usize + Sequencer::H_KEYS_OFFSET; - let x3 = x as usize + width as usize; - for x in x2..x3 { - let step = (time_0 + x2) * time_z; - let next_step = (time_0 + x2 + 1) * time_z; - to.blit(&"-", x as u16, y, Some(Sequencer::::style_timer_step( - now, step as usize, next_step as usize - ))); - } - } - Ok(()) - }).fill_x(), - // notes - CustomWidget::new(|_|Ok(Some([32,4])), |to: &mut TuiOutput|{ - let offset = Sequencer::H_KEYS_OFFSET as u16; - if to.area().h() < 2 || to.area().w() < offset { return Ok(()) } - let area = to.area().push_x(offset).shrink_x(offset).shrink_y(2); - Ok(to.buffer_update(area, &move |cell, x, y|{ - cell.set_bg(Color::Rgb(20, 20, 20)); - let src_x = ((x as usize + self.time_axis.start) * self.time_axis.scale) as usize; - let src_y = (y as usize + self.note_axis.start) as usize; - if src_x < self.buffer.width && src_y < self.buffer.height - 1 { - let src = self.buffer.get(src_x, self.buffer.height - src_y); - src.map(|src|{ cell.set_symbol(src.symbol()); cell.set_fg(src.fg); }); - } - })) - }).fill_x(), - // note cursor - CustomWidget::new(|_|Ok(Some([1,1])), |to: &mut TuiOutput|{ - let area = to.area(); - if let (Some(time), Some(note)) = (self.time_axis.point, self.note_axis.point) { - let x = area.x() + Sequencer::H_KEYS_OFFSET as u16 + time as u16; - let y = area.y() + 1 + note as u16 / 2; - let c = if note % 2 == 0 { "▀" } else { "▄" }; - to.blit(&c, x, y, self.style_focus()); - } - Ok(()) - }), - // zoom - CustomWidget::new(|_|Ok(Some([10,1])), |to: &mut TuiOutput|{ - let [x, y, w, h] = to.area.xywh(); - let quant = ppq_to_name(self.time_axis.scale); - Ok(to.blit( - &quant, - x + w - 1 - quant.len() as u16, - y + h - 2, - self.style_focus() - )) - }), - ); - row!(toolbar, content).fill_x() - .bg(Color::Rgb(40, 50, 30)) - .border(Lozenge(Style::default() - .bg(Color::Rgb(40, 50, 30)) - .fg(Color::Rgb(70, 80, 50)))) - } -} -impl Focusable for Sequencer { - fn is_focused (&self) -> bool { - self.focused - } - fn set_focused (&mut self, focused: bool) { - self.focused = focused - } -} - /// Colors of piano keys const KEY_COLORS: [(Color, Color);6] = [ (Color::Rgb(255, 255, 255), Color::Rgb(0, 0, 0)), @@ -254,7 +274,6 @@ const KEY_COLORS: [(Color, Color);6] = [ (Color::Rgb(0, 0, 0), Color::Rgb(255, 255, 255)), (Color::Rgb(0, 0, 0), Color::Rgb(255, 255, 255)), ]; - pub(crate) fn keys_vert () -> Buffer { let area = [0, 0, 5, 64]; let mut buffer = Buffer::empty(Rect { @@ -281,38 +300,7 @@ pub(crate) fn keys_vert () -> Buffer { }); buffer } - /// Octave number (from -1 to 9) const NTH_OCTAVE: [&'static str;11] = [ "-1", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" ]; - -impl Handle for Sequencer { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - match from.event() { - key!(KeyCode::Char('`')) => { - self.mode = !self.mode; - }, - key!(KeyCode::Up) => match self.entered { - true => { self.note_axis.point_dec(); }, - false => { self.note_axis.start_dec(); }, - }, - key!(KeyCode::Down) => match self.entered { - true => { self.note_axis.point_inc(); }, - false => { self.note_axis.start_inc(); }, - }, - key!(KeyCode::Left) => match self.entered { - true => { self.time_axis.point_dec(); }, - false => { self.time_axis.start_dec(); }, - }, - key!(KeyCode::Right) => match self.entered { - true => { self.time_axis.point_inc(); }, - false => { self.time_axis.start_inc(); }, - }, - _ => { - return Ok(None) - } - } - return Ok(Some(true)) - } -}