From 4c640301c6f2f4d601c0a3056c6e63baba75d3b1 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 19 Sep 2024 01:08:13 +0300 Subject: [PATCH] wip: reenabling sequencer pane --- crates/tek_sequencer/src/main_arranger.rs | 62 +- crates/tek_sequencer/src/sequencer.rs | 951 +++++++++++----------- 2 files changed, 501 insertions(+), 512 deletions(-) diff --git a/crates/tek_sequencer/src/main_arranger.rs b/crates/tek_sequencer/src/main_arranger.rs index f68ff0f6..da6cc0f1 100644 --- a/crates/tek_sequencer/src/main_arranger.rs +++ b/crates/tek_sequencer/src/main_arranger.rs @@ -3,19 +3,7 @@ include!("lib.rs"); use tek_core::clap::{self, Parser}; pub fn main () -> Usually<()> { - Tui::run(Arc::new(RwLock::new(crate::ArrangerStandalone::from_args()?)))?; - Ok(()) -} - -struct ArrangerStandalone { - /// Contains all the sequencers. - arranger: Arranger, - /// Controls the JACK transport. - transport: Option>, - /// This allows the sequencer view to be moved or hidden. - show_sequencer: Option, - /// - focus: usize, + ArrangerCli::parse().run() } #[derive(Debug, Parser)] @@ -38,51 +26,69 @@ pub struct ArrangerCli { scenes: usize, } -impl ArrangerStandalone { - pub fn from_args () -> Usually { - let args = ArrangerCli::parse(); +impl ArrangerCli { + fn run (&self) -> Usually<()> { let mut arranger = Arranger::new(""); - let mut transport = match args.transport { + let mut transport = match self.transport { Some(true) => Some(TransportToolbar::new(None)), _ => None }; - if let Some(name) = args.name { + if let Some(name) = self.name.as_ref() { *arranger.name.write().unwrap() = name.clone(); } - for _ in 0..args.tracks { + for _ in 0..self.tracks { let track = arranger.track_add(None)?; - for _ in 0..args.scenes { + for _ in 0..self.scenes { track.phrases.push( Arc::new(RwLock::new(Phrase::new("", 96 * 4, None))) ); } } - for _ in 0..args.scenes { + for _ in 0..self.scenes { let _scene = arranger.scene_add(None)?; - //for i in 0..args.tracks { + //for i in 0..self.tracks { //scene.clips[i] = Some(i); //} } transport.set_focused(true); - Ok(ArrangerStandalone { + let state = Arc::new(RwLock::new(ArrangerStandalone { transport, show_sequencer: Some(tek_core::Direction::Down), arranger, focus: 0 - }) + })); + Tui::run(state)?; + Ok(()) } } +struct ArrangerStandalone { + /// Controls the JACK transport. + transport: Option>, + /// Contains all the sequencers. + arranger: Arranger, + /// This allows the sequencer view to be moved or hidden. + show_sequencer: Option, + /// + focus: usize, +} + impl Content for ArrangerStandalone { type Engine = Tui; fn content (&self) -> impl Widget { Split::down(|add|{ add(&self.transport)?; - add(&self.arranger)?; - if let Some(sequencer) = self.arranger.sequencer() { - add(sequencer)?; + if let Some(direction) = self.show_sequencer { + add(&Split::new(direction, |add|{ + add(&self.arranger)?; + add(&Min::Y(10, self.arranger.sequencer().map( + |x|x as &dyn Widget + )))?; + Ok(()) + })) + } else { + add(&self.arranger) } - Ok(()) }) //if let Some(ref modal) = self.arranger.modal { //to.fill_bg(area, Nord::bg_lo(false, false)); diff --git a/crates/tek_sequencer/src/sequencer.rs b/crates/tek_sequencer/src/sequencer.rs index d9755bff..35170944 100644 --- a/crates/tek_sequencer/src/sequencer.rs +++ b/crates/tek_sequencer/src/sequencer.rs @@ -21,450 +21,6 @@ tui_style!(STYLE_VALUE = /////////////////////////////////////////////////////////////////////////////////////////////////// -/// Stores and displays time-related state. -pub struct TransportToolbar { - /// Enable metronome? - pub metronome: bool, - /// Current sample rate, tempo, and PPQ. - pub timebase: Arc, - /// JACK client handle (needs to not be dropped for standalone mode to work). - pub jack: Option, - /// JACK transport handle. - pub transport: Option, - /// Quantization factor - /// Global frame and usec at which playback started - pub started: Option<(usize, usize)>, - - pub focused: bool, - pub focus: usize, - pub playing: TransportPlayPauseButton, - pub bpm: TransportBPM, - pub quant: TransportQuantize, - pub sync: TransportSync, - pub clock: TransportClock, -} - -impl TransportToolbar { - pub fn standalone () -> Usually>> where Self: 'static { - let mut transport = Self::new(None); - transport.focused = true; - let jack = JackClient::Inactive( - Client::new("tek_transport", ClientOptions::NO_START_SERVER)?.0 - ); - transport.transport = Some(jack.transport()); - transport.playing.transport = Some(jack.transport()); - let transport = Arc::new(RwLock::new(transport)); - transport.write().unwrap().jack = Some( - jack.activate( - &transport.clone(), - |state: &Arc>>, client, scope| { - state.write().unwrap().process(client, scope) - } - )? - ); - Ok(transport) - } - pub fn new (transport: Option) -> Self { - let timebase = Arc::new(Timebase::default()); - Self { - focused: false, - focus: 0, - - playing: TransportPlayPauseButton { - _engine: Default::default(), - transport: None, - value: Some(TransportState::Stopped), - focused: true - }, - bpm: TransportBPM { - _engine: Default::default(), - value: timebase.bpm(), - focused: false - }, - quant: TransportQuantize { - _engine: Default::default(), - value: 24, - focused: false - }, - sync: TransportSync { - _engine: Default::default(), - value: timebase.ppq() as usize * 4, - focused: false - }, - clock: TransportClock { - _engine: Default::default(), - frame: 0, - pulse: 0, - ppq: 0, - usecs: 0, - focused: false - }, - - transport, - timebase, - metronome: false, - started: None, - jack: None, - } - } - pub fn toggle_play (&mut self) -> Usually<()> { - self.playing.toggle()?; - Ok(()) - } - pub fn update (&mut self, scope: &ProcessScope) -> (bool, usize, usize, usize, usize, f64) { - let CycleTimes { - current_frames, - current_usecs, - next_usecs, - period_usecs - } = scope.cycle_times().unwrap(); - let chunk_size = scope.n_frames() as usize; - let transport = self.transport.as_ref().unwrap().query().unwrap(); - self.clock.frame = transport.pos.frame() as usize; - let mut reset = false; - if self.playing.value != Some(transport.state) { - match transport.state { - TransportState::Rolling => { - self.started = Some(( - current_frames as usize, - current_usecs as usize, - )); - }, - TransportState::Stopped => { - self.started = None; - reset = true; - }, - _ => {} - } - } - self.playing.value = Some(transport.state); - ( - reset, - current_frames as usize, - chunk_size as usize, - current_usecs as usize, - next_usecs as usize, - period_usecs as f64 - ) - } - pub fn bpm (&self) -> usize { - self.timebase.bpm() as usize - } - pub fn ppq (&self) -> usize { - self.timebase.ppq() as usize - } - pub fn pulse (&self) -> usize { - self.timebase.frame_to_pulse(self.clock.frame as f64) as usize - } - pub fn usecs (&self) -> usize { - self.timebase.frame_to_usec(self.clock.frame as f64) as usize - } - pub fn quant (&self) -> usize { - self.quant.value - } - pub fn sync (&self) -> usize { - self.sync.value - } -} - -impl Audio for TransportToolbar { - fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { - self.update(&scope); - Control::Continue - } -} - -impl Handle for TransportToolbar { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - match from.event() { - key!(KeyCode::Left) => { self.focus_prev(); }, - key!(KeyCode::Right) => { self.focus_next(); }, - _ => return self.focused_mut().handle(from) - } - Ok(Some(true)) - } -} - -impl Content for TransportToolbar { - type Engine = Tui; - fn content (&self) -> impl Widget { - Split::right(|add|{ - let focus_wrap = |focused, component|Layers::new(move |add|{ - if focused { - add(&CORNERS)?; - add(&Background(COLOR_BG1))?; - } - add(component) - }); - add(&focus_wrap(self.focused && self.playing.focused, &self.playing))?; - add(&focus_wrap(self.focused && self.bpm.focused, &self.bpm))?; - add(&focus_wrap(self.focused && self.quant.focused, &self.quant))?; - add(&focus_wrap(self.focused && self.sync.focused, &self.sync))?; - add(&focus_wrap(self.focused && self.clock.focused, &self.clock))?; - Ok(()) - }) - } -} - -impl Focus<5, Tui> for TransportToolbar { - fn focus (&self) -> usize { - self.focus - } - fn focus_mut (&mut self) -> &mut usize { - &mut self.focus - } - fn focusable (&self) -> [&dyn Focusable;5] { - [ - &self.playing as &dyn Focusable, - &self.bpm as &dyn Focusable, - &self.quant as &dyn Focusable, - &self.sync as &dyn Focusable, - &self.clock as &dyn Focusable, - ] - } - fn focusable_mut (&mut self) -> [&mut dyn Focusable;5] { - [ - &mut self.playing as &mut dyn Focusable, - &mut self.bpm as &mut dyn Focusable, - &mut self.quant as &mut dyn Focusable, - &mut self.sync as &mut dyn Focusable, - &mut self.clock as &mut dyn Focusable, - ] - } -} - -impl Focusable for TransportToolbar { - fn is_focused (&self) -> bool { - self.focused - } - fn set_focused (&mut self, focused: bool) { - self.focused = focused - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -pub struct TransportPlayPauseButton { - pub _engine: PhantomData, - pub transport: Option, - pub value: Option, - pub focused: bool -} -impl TransportPlayPauseButton { - fn toggle (&mut self) -> Usually<()> { - let transport = self.transport.as_ref().unwrap(); - self.value = match self.value.expect("1st frame has not been processed yet") { - TransportState::Stopped => { - transport.start()?; - Some(TransportState::Starting) - }, - _ => { - transport.stop()?; - transport.locate(0)?; - Some(TransportState::Stopped) - }, - }; - Ok(()) - } -} -impl Focusable for TransportPlayPauseButton { - fn is_focused (&self) -> bool { - self.focused - } - fn set_focused (&mut self, focused: bool) { - self.focused = focused - } -} -impl Handle for TransportPlayPauseButton { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - match from.event() { - key!(KeyCode::Enter) => self.toggle().map(|_|Some(true)), - _ => Ok(None) - } - } -} -impl Content for TransportPlayPauseButton { - type Engine = Tui; - fn content (&self) -> impl Widget { - Layers::new(|add|{ - add(&Plus::X(1, Min::XY(11, 2, Styled(match self.value { - Some(TransportState::Stopped) => Some(GRAY_DIM.bold()), - Some(TransportState::Starting) => Some(GRAY_NOT_DIM_BOLD), - Some(TransportState::Rolling) => Some(WHITE_NOT_DIM_BOLD), - _ => unreachable!(), - }, match self.value { - Some(TransportState::Rolling) => "▶ PLAYING", - Some(TransportState::Starting) => "READY ...", - Some(TransportState::Stopped) => "⏹ STOPPED", - _ => unreachable!(), - }))))?; - Ok(()) - }) - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -pub struct TransportBPM { - pub _engine: PhantomData, - pub value: f64, - pub focused: bool -} -impl Focusable for TransportBPM { - fn is_focused (&self) -> bool { - self.focused - } - fn set_focused (&mut self, focused: bool) { - self.focused = focused - } -} -impl Handle for TransportBPM { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - match from.event() { - key!(KeyCode::Char(',')) => { self.value -= 1.0; }, - key!(KeyCode::Char('.')) => { self.value += 1.0; }, - key!(KeyCode::Char('<')) => { self.value -= 0.001; }, - key!(KeyCode::Char('>')) => { self.value += 0.001; }, - _ => return Ok(None) - } - Ok(Some(true)) - } -} -impl Content for TransportBPM { - type Engine = Tui; - fn content (&self) -> impl Widget { - let Self { value, .. } = self; - Outset::X(1u16, Split::down(move |add|{ - add(&"BPM")?; - add(&format!("{}.{:03}", *value as usize, (value * 1000.0) % 1000.0).as_str()) - })) - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -pub struct TransportQuantize { - pub _engine: PhantomData, - pub value: usize, - pub focused: bool -} -impl Focusable for TransportQuantize { - fn is_focused (&self) -> bool { - self.focused - } - fn set_focused (&mut self, focused: bool) { - self.focused = focused - } -} -impl Handle for TransportQuantize { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - match from.event() { - key!(KeyCode::Char(',')) => { - self.value = prev_note_length(self.value); - Ok(Some(true)) - }, - key!(KeyCode::Char('.')) => { - self.value = next_note_length(self.value); - Ok(Some(true)) - }, - _ => Ok(None) - } - } -} -impl Content for TransportQuantize { - type Engine = Tui; - fn content (&self) -> impl Widget { - let Self { value, .. } = self; - Outset::X(1u16, Split::down(|add|{ - add(&"QUANT")?; - add(&ppq_to_name(*value as usize)) - })) - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -pub struct TransportSync { - pub _engine: PhantomData, - pub value: usize, - pub focused: bool -} -impl Focusable for TransportSync { - fn is_focused (&self) -> bool { - self.focused - } - fn set_focused (&mut self, focused: bool) { - self.focused = focused - } -} -impl Handle for TransportSync { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - match from.event() { - key!(KeyCode::Char(',')) => { - self.value = prev_note_length(self.value); - Ok(Some(true)) - }, - key!(KeyCode::Char('.')) => { - self.value = next_note_length(self.value); - Ok(Some(true)) - }, - _ => Ok(None) - } - } -} -impl Content for TransportSync { - type Engine = Tui; - fn content (&self) -> impl Widget { - let Self { value, .. } = self; - Outset::X(1u16, Split::down(|add|{ - add(&"SYNC")?; - add(&ppq_to_name(*value as usize)) - })) - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -pub struct TransportClock { - pub _engine: PhantomData, - pub frame: usize, - pub pulse: usize, - pub ppq: usize, - pub usecs: usize, - pub focused: bool, -} -impl Focusable for TransportClock { - fn is_focused (&self) -> bool { - self.focused - } - fn set_focused (&mut self, focused: bool) { - self.focused = focused - } -} -impl Handle for TransportClock { - fn handle (&mut self, _: &TuiInput) -> Perhaps { - Ok(None) - } -} -impl Content for TransportClock { - type Engine = Tui; - fn content (&self) -> impl Widget { - let Self { frame: _frame, pulse, ppq, usecs, .. } = self; - Layers::new(move|add|{ - let (beats, pulses) = if *ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) }; - let (bars, beats) = ((beats / 4) + 1, (beats % 4) + 1); - let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000); - let (minutes, seconds) = (seconds / 60, seconds % 60); - add(&Outset::X(1u16, Split::down(|add|{ - add(&format!("{bars}.{beats}.{pulses:02}").as_str())?; - add(&format!("{minutes}:{seconds:02}:{msecs:03}").as_str()) - }))) - }) - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// - /// Represents the tracks and scenes of the composition. pub struct Arranger { /// Name of arranger @@ -1747,7 +1303,7 @@ impl Sequencer { } } - fn index_to_color (&self, index: u16, default: Color) -> Color { + 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] { Color::Yellow @@ -1795,18 +1351,29 @@ pub type MIDIMessage = Vec; /// Collection of serialized MIDI messages pub type MIDIChunk = [Vec]; -impl Widget for Sequencer { +impl Sequencer { + const H_KEYS_OFFSET: usize = 5; +} + +impl Content for Sequencer { type Engine = Tui; - fn layout (&self, _to: [u16;2]) -> Perhaps<[u16;2]> { - todo!() - } - fn render (&self, to: &mut TuiOutput) -> Usually<()> { - self.horizontal_draw(to)?; - if self.focused && self.entered { - Corners(Style::default().green().not_dim()).draw(to)?; - } - //Ok(Some(to.area())) - Ok(()) + fn content (&self) -> impl Widget { + Split::right(move |add|{ + add(&Split::down(|add|{ + add(&SequenceName(&self))?; + add(&SequenceRange)?; + add(&SequenceLoopRange)?; + add(&SequenceNoteRange)?; + Ok(()) + }))?; + add(&Layers::new(|add|{ + add(&SequenceKeys(&self))?; + add(&self.phrase.as_ref().map(|phrase|SequenceTimer(&self, phrase.clone())))?; + add(&SequenceNotes(&self))?; + add(&SequenceCursor(&self))?; + add(&SequenceZoom(&self)) + })) + }) } } @@ -1996,34 +1563,6 @@ pub(crate) fn keys_vert () -> Buffer { buffer } -impl Sequencer { - - const H_KEYS_OFFSET: usize = 5; - - pub(crate) fn horizontal_draw <'a> (&self, to: &mut TuiOutput) -> Usually<()> { - let area = to.area(); - Split::down(|add|{ - add(&SequenceName(&self))?; - add(&SequenceRange)?; - add(&SequenceLoopRange)?; - add(&SequenceNoteRange)?; - Ok(()) - }).render(to.with_rect([area.x(), area.y(), 10, area.h()]))?; - let area = [area.x() + 10, area.y(), area.w().saturating_sub(10), area.h().min(66)]; - Lozenge(Style::default().fg(Nord::BG2)).draw(to.with_rect(area))?; - let area = [area.x() + 1, area.y(), area.w().saturating_sub(1), area.h()]; - Layers::new(|add|{ - add(&SequenceKeys(&self))?; - add(&self.phrase.as_ref().map(|phrase|SequenceTimer(&self, phrase.clone())))?; - add(&SequenceNotes(&self))?; - add(&SequenceCursor(&self))?; - add(&SequenceZoom(&self)) - }).render(to.with_rect(area))?; - Ok(()) - } - -} - /////////////////////////////////////////////////////////////////////////////////////////////////// struct SequenceName<'a>(&'a Sequencer); @@ -2438,3 +1977,447 @@ impl Phrase { Ok(phrase) } } + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +/// Stores and displays time-related state. +pub struct TransportToolbar { + /// Enable metronome? + pub metronome: bool, + /// Current sample rate, tempo, and PPQ. + pub timebase: Arc, + /// JACK client handle (needs to not be dropped for standalone mode to work). + pub jack: Option, + /// JACK transport handle. + pub transport: Option, + /// Quantization factor + /// Global frame and usec at which playback started + pub started: Option<(usize, usize)>, + + pub focused: bool, + pub focus: usize, + pub playing: TransportPlayPauseButton, + pub bpm: TransportBPM, + pub quant: TransportQuantize, + pub sync: TransportSync, + pub clock: TransportClock, +} + +impl TransportToolbar { + pub fn standalone () -> Usually>> where Self: 'static { + let mut transport = Self::new(None); + transport.focused = true; + let jack = JackClient::Inactive( + Client::new("tek_transport", ClientOptions::NO_START_SERVER)?.0 + ); + transport.transport = Some(jack.transport()); + transport.playing.transport = Some(jack.transport()); + let transport = Arc::new(RwLock::new(transport)); + transport.write().unwrap().jack = Some( + jack.activate( + &transport.clone(), + |state: &Arc>>, client, scope| { + state.write().unwrap().process(client, scope) + } + )? + ); + Ok(transport) + } + pub fn new (transport: Option) -> Self { + let timebase = Arc::new(Timebase::default()); + Self { + focused: false, + focus: 0, + + playing: TransportPlayPauseButton { + _engine: Default::default(), + transport: None, + value: Some(TransportState::Stopped), + focused: true + }, + bpm: TransportBPM { + _engine: Default::default(), + value: timebase.bpm(), + focused: false + }, + quant: TransportQuantize { + _engine: Default::default(), + value: 24, + focused: false + }, + sync: TransportSync { + _engine: Default::default(), + value: timebase.ppq() as usize * 4, + focused: false + }, + clock: TransportClock { + _engine: Default::default(), + frame: 0, + pulse: 0, + ppq: 0, + usecs: 0, + focused: false + }, + + transport, + timebase, + metronome: false, + started: None, + jack: None, + } + } + pub fn toggle_play (&mut self) -> Usually<()> { + self.playing.toggle()?; + Ok(()) + } + pub fn update (&mut self, scope: &ProcessScope) -> (bool, usize, usize, usize, usize, f64) { + let CycleTimes { + current_frames, + current_usecs, + next_usecs, + period_usecs + } = scope.cycle_times().unwrap(); + let chunk_size = scope.n_frames() as usize; + let transport = self.transport.as_ref().unwrap().query().unwrap(); + self.clock.frame = transport.pos.frame() as usize; + let mut reset = false; + if self.playing.value != Some(transport.state) { + match transport.state { + TransportState::Rolling => { + self.started = Some(( + current_frames as usize, + current_usecs as usize, + )); + }, + TransportState::Stopped => { + self.started = None; + reset = true; + }, + _ => {} + } + } + self.playing.value = Some(transport.state); + ( + reset, + current_frames as usize, + chunk_size as usize, + current_usecs as usize, + next_usecs as usize, + period_usecs as f64 + ) + } + pub fn bpm (&self) -> usize { + self.timebase.bpm() as usize + } + pub fn ppq (&self) -> usize { + self.timebase.ppq() as usize + } + pub fn pulse (&self) -> usize { + self.timebase.frame_to_pulse(self.clock.frame as f64) as usize + } + pub fn usecs (&self) -> usize { + self.timebase.frame_to_usec(self.clock.frame as f64) as usize + } + pub fn quant (&self) -> usize { + self.quant.value + } + pub fn sync (&self) -> usize { + self.sync.value + } +} + +impl Audio for TransportToolbar { + fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { + self.update(&scope); + Control::Continue + } +} + +impl Handle for TransportToolbar { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + match from.event() { + key!(KeyCode::Left) => { self.focus_prev(); }, + key!(KeyCode::Right) => { self.focus_next(); }, + _ => return self.focused_mut().handle(from) + } + Ok(Some(true)) + } +} + +impl Content for TransportToolbar { + type Engine = Tui; + fn content (&self) -> impl Widget { + Split::right(|add|{ + let focus_wrap = |focused, component|Layers::new(move |add|{ + if focused { + add(&CORNERS)?; + add(&Background(COLOR_BG1))?; + } + add(component) + }); + add(&focus_wrap(self.focused && self.playing.focused, &self.playing))?; + add(&focus_wrap(self.focused && self.bpm.focused, &self.bpm))?; + add(&focus_wrap(self.focused && self.quant.focused, &self.quant))?; + add(&focus_wrap(self.focused && self.sync.focused, &self.sync))?; + add(&focus_wrap(self.focused && self.clock.focused, &self.clock))?; + Ok(()) + }) + } +} + +impl Focus<5, Tui> for TransportToolbar { + fn focus (&self) -> usize { + self.focus + } + fn focus_mut (&mut self) -> &mut usize { + &mut self.focus + } + fn focusable (&self) -> [&dyn Focusable;5] { + [ + &self.playing as &dyn Focusable, + &self.bpm as &dyn Focusable, + &self.quant as &dyn Focusable, + &self.sync as &dyn Focusable, + &self.clock as &dyn Focusable, + ] + } + fn focusable_mut (&mut self) -> [&mut dyn Focusable;5] { + [ + &mut self.playing as &mut dyn Focusable, + &mut self.bpm as &mut dyn Focusable, + &mut self.quant as &mut dyn Focusable, + &mut self.sync as &mut dyn Focusable, + &mut self.clock as &mut dyn Focusable, + ] + } +} + +impl Focusable for TransportToolbar { + fn is_focused (&self) -> bool { + self.focused + } + fn set_focused (&mut self, focused: bool) { + self.focused = focused + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +pub struct TransportPlayPauseButton { + pub _engine: PhantomData, + pub transport: Option, + pub value: Option, + pub focused: bool +} +impl TransportPlayPauseButton { + fn toggle (&mut self) -> Usually<()> { + let transport = self.transport.as_ref().unwrap(); + self.value = match self.value.expect("1st frame has not been processed yet") { + TransportState::Stopped => { + transport.start()?; + Some(TransportState::Starting) + }, + _ => { + transport.stop()?; + transport.locate(0)?; + Some(TransportState::Stopped) + }, + }; + Ok(()) + } +} +impl Focusable for TransportPlayPauseButton { + fn is_focused (&self) -> bool { + self.focused + } + fn set_focused (&mut self, focused: bool) { + self.focused = focused + } +} +impl Handle for TransportPlayPauseButton { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + match from.event() { + key!(KeyCode::Enter) => self.toggle().map(|_|Some(true)), + _ => Ok(None) + } + } +} +impl Content for TransportPlayPauseButton { + type Engine = Tui; + fn content (&self) -> impl Widget { + Layers::new(|add|{ + add(&Plus::X(1, Min::XY(11, 2, Styled(match self.value { + Some(TransportState::Stopped) => Some(GRAY_DIM.bold()), + Some(TransportState::Starting) => Some(GRAY_NOT_DIM_BOLD), + Some(TransportState::Rolling) => Some(WHITE_NOT_DIM_BOLD), + _ => unreachable!(), + }, match self.value { + Some(TransportState::Rolling) => "▶ PLAYING", + Some(TransportState::Starting) => "READY ...", + Some(TransportState::Stopped) => "⏹ STOPPED", + _ => unreachable!(), + }))))?; + Ok(()) + }) + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +pub struct TransportBPM { + pub _engine: PhantomData, + pub value: f64, + pub focused: bool +} +impl Focusable for TransportBPM { + fn is_focused (&self) -> bool { + self.focused + } + fn set_focused (&mut self, focused: bool) { + self.focused = focused + } +} +impl Handle for TransportBPM { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + match from.event() { + key!(KeyCode::Char(',')) => { self.value -= 1.0; }, + key!(KeyCode::Char('.')) => { self.value += 1.0; }, + key!(KeyCode::Char('<')) => { self.value -= 0.001; }, + key!(KeyCode::Char('>')) => { self.value += 0.001; }, + _ => return Ok(None) + } + Ok(Some(true)) + } +} +impl Content for TransportBPM { + type Engine = Tui; + fn content (&self) -> impl Widget { + let Self { value, .. } = self; + Outset::X(1u16, Split::down(move |add|{ + add(&"BPM")?; + add(&format!("{}.{:03}", *value as usize, (value * 1000.0) % 1000.0).as_str()) + })) + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +pub struct TransportQuantize { + pub _engine: PhantomData, + pub value: usize, + pub focused: bool +} +impl Focusable for TransportQuantize { + fn is_focused (&self) -> bool { + self.focused + } + fn set_focused (&mut self, focused: bool) { + self.focused = focused + } +} +impl Handle for TransportQuantize { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + match from.event() { + key!(KeyCode::Char(',')) => { + self.value = prev_note_length(self.value); + Ok(Some(true)) + }, + key!(KeyCode::Char('.')) => { + self.value = next_note_length(self.value); + Ok(Some(true)) + }, + _ => Ok(None) + } + } +} +impl Content for TransportQuantize { + type Engine = Tui; + fn content (&self) -> impl Widget { + let Self { value, .. } = self; + Outset::X(1u16, Split::down(|add|{ + add(&"QUANT")?; + add(&ppq_to_name(*value as usize)) + })) + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +pub struct TransportSync { + pub _engine: PhantomData, + pub value: usize, + pub focused: bool +} +impl Focusable for TransportSync { + fn is_focused (&self) -> bool { + self.focused + } + fn set_focused (&mut self, focused: bool) { + self.focused = focused + } +} +impl Handle for TransportSync { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + match from.event() { + key!(KeyCode::Char(',')) => { + self.value = prev_note_length(self.value); + Ok(Some(true)) + }, + key!(KeyCode::Char('.')) => { + self.value = next_note_length(self.value); + Ok(Some(true)) + }, + _ => Ok(None) + } + } +} +impl Content for TransportSync { + type Engine = Tui; + fn content (&self) -> impl Widget { + let Self { value, .. } = self; + Outset::X(1u16, Split::down(|add|{ + add(&"SYNC")?; + add(&ppq_to_name(*value as usize)) + })) + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +pub struct TransportClock { + pub _engine: PhantomData, + pub frame: usize, + pub pulse: usize, + pub ppq: usize, + pub usecs: usize, + pub focused: bool, +} +impl Focusable for TransportClock { + fn is_focused (&self) -> bool { + self.focused + } + fn set_focused (&mut self, focused: bool) { + self.focused = focused + } +} +impl Handle for TransportClock { + fn handle (&mut self, _: &TuiInput) -> Perhaps { + Ok(None) + } +} +impl Content for TransportClock { + type Engine = Tui; + fn content (&self) -> impl Widget { + let Self { frame: _frame, pulse, ppq, usecs, .. } = self; + Layers::new(move|add|{ + let (beats, pulses) = if *ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) }; + let (bars, beats) = ((beats / 4) + 1, (beats % 4) + 1); + let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000); + let (minutes, seconds) = (seconds / 60, seconds % 60); + add(&Outset::X(1u16, Split::down(|add|{ + add(&format!("{bars}.{beats}.{pulses:02}").as_str())?; + add(&format!("{minutes}:{seconds:02}:{msecs:03}").as_str()) + }))) + }) + } +}