diff --git a/cli/tek.rs b/cli/tek.rs index e2995eab..7c08540a 100644 --- a/cli/tek.rs +++ b/cli/tek.rs @@ -76,6 +76,8 @@ pub fn main () -> Usually<()> { let left_tos = PortConnection::collect(&cli.left_to, empty, empty); let right_froms = PortConnection::collect(&cli.right_from, empty, empty); let right_tos = PortConnection::collect(&cli.right_to, empty, empty); + let audio_froms = &[&left_froms, &right_froms]; + let audio_tos = &[&left_tos, &right_tos ]; let perf = PerfModel::default(); let size = Measure::new(); let default_clip = ||Arc::new(RwLock::new(MidiClip::new( @@ -116,25 +118,6 @@ pub fn main () -> Usually<()> { TekMode::Clock => engine.run(&jack.activate_with(|jack|Ok(TransportTui { jack: jack.clone(), clock: default_clock(jack), }))?)?, - TekMode::Sequencer => engine.run(&jack.activate_with(|jack|Ok({ - let clip = default_clip(); - let mut player = default_player(jack, Some(&clip))?; - player.clock = default_bpm(player.clock); - Sequencer { - _jack: jack.clone(), - player, - pool: (&clip).into(), - editor: (&clip).into(), - midi_buf: vec![vec![];65536], - note_buf: vec![], - status: true, - perf, - size, - compact: true, - transport: true, - selectors: true, - } - }))?)?, TekMode::Sampler => engine.run(&jack.activate_with(|jack|Ok( SamplerTui { cursor: (0, 0), @@ -147,53 +130,62 @@ pub fn main () -> Usually<()> { size, } ))?)?, + TekMode::Sequencer => engine.run(&jack.activate_with(|jack|Ok({ + let clip = default_clip(); + let mut player = default_player(jack, Some(&clip))?; + player.clock = default_bpm(player.clock); + App::sequencer( + jack, (&clip).into(), (&clip).into(), + Some(player), &midi_froms, &midi_tos, + ) + }))?)?, TekMode::Groovebox => engine.run(&jack.activate_with(|jack|Ok({ let clip = default_clip(); let mut player = default_player(jack, Some(&clip))?; player.clock = default_bpm(player.clock); let sampler = default_sampler(jack)?; jack.connect_ports(&player.midi_outs[0].port, &sampler.midi_in.port)?; - let app = Groovebox { - _jack: jack.clone(), - player, - sampler, - pool: (&clip).into(), - editor: (&clip).into(), - midi_buf: vec![vec![];65536], - note_buf: vec![], - perf, - size, - compact: true, - status: true, - }; - app + App::groovebox( + jack, (&clip).into(), (&clip).into(), + Some(player), &midi_froms, &midi_tos, + sampler, &audio_froms, &audio_tos, + ) }))?)?, - TekMode::Arranger { scenes, tracks, track_width, .. } => - engine.run(&jack.activate_with(|jack|Ok({ - let clock = default_clock(jack); - let mut app = Arranger { - jack: jack.clone(), - midi_ins: vec![JackPort::::new(jack, format!("M/{name}"), &midi_froms)?,], - midi_outs: vec![JackPort::::new(jack, format!("{name}/M"), &midi_tos)?, ], - clock, - pool: PoolModel::default(),//(&clip).into(), - editor: MidiEditor::default(),//(&clip).into(), - selected: ArrangerSelection::Clip(0, 0), - scenes: vec![], - tracks: vec![], - splits: [12, 20], - midi_buf: vec![vec![];65536], - note_buf: vec![], - compact: false, - editing: true.into(), - color, - perf, - size, - }; - app.tracks_add(tracks, track_width, &midi_froms, &midi_tos)?; - app.scenes_add(scenes)?; - app - }))?)?, + TekMode::Arranger { scenes, tracks, track_width, .. } => engine.run(&jack.activate_with(|jack|Ok({ + App::arranger( + jack, + PoolModel::default(), + MidiEditor::default(), &midi_froms, &midi_tos, + default_sampler(jack)?, &audio_froms, &audio_tos, + scenes, tracks, track_width + ) + }))?)?, + + + //let clock = default_clock(jack); + //let mut app = Arranger { + //jack: jack.clone(), + //midi_ins: vec![JackPort::::new(jack, format!("M/{name}"), &midi_froms)?,], + //midi_outs: vec![JackPort::::new(jack, format!("{name}/M"), &midi_tos)?, ], + //clock, + //pool: PoolModel::default(),//(&clip).into(), + //editor: MidiEditor::default(),//(&clip).into(), + //selected: ArrangerSelection::Clip(0, 0), + //scenes: vec![], + //tracks: vec![], + //splits: [12, 20], + //midi_buf: vec![vec![];65536], + //note_buf: vec![], + //compact: false, + //editing: true.into(), + //color, + //perf, + //size, + //}; + //app.tracks_add(tracks, track_width, &midi_froms, &midi_tos)?; + //app.scenes_add(scenes)?; + //app + //}))?)?, _ => todo!() }) diff --git a/jack/src/jack_connect.rs b/jack/src/jack_connect.rs index cd9e64e3..51fbd8b2 100644 --- a/jack/src/jack_connect.rs +++ b/jack/src/jack_connect.rs @@ -21,8 +21,10 @@ pub type DynamicAsyncClient = AsyncClient for Client { fn from (jack: JackConnection) -> Self { match jack { JackConnection::Inactive(client) => client, + JackConnection::Inert => panic!("jack client not activated"), JackConnection::Activating => panic!("jack client still activating"), JackConnection::Active(_) => panic!("jack client already activated"), } @@ -49,6 +52,7 @@ impl JackConnection { /// Return the internal [Client] handle that lets you call the JACK API. pub fn client (&self) -> &Client { match self { + Self::Inert => panic!("jack client not activated"), Self::Inactive(ref client) => client, Self::Activating => panic!("jack client has not finished activation"), Self::Active(ref client) => client.as_client(), diff --git a/output/src/edn_view.rs b/output/src/edn_view.rs index dc475452..30cc3693 100644 --- a/output/src/edn_view.rs +++ b/output/src/edn_view.rs @@ -25,7 +25,10 @@ pub trait EdnViewData { } /// Renders from EDN source and context. +#[derive(Default)] pub enum EdnView> { + #[default] + Inert, _Unused(PhantomData), Ok(T, EdnItem), //render: BoxBox + Send + Sync + 'a> + Send + Sync + 'a> diff --git a/sampler/src/sampler_tui.rs b/sampler/src/sampler_tui.rs index 9ea94988..34f76fcf 100644 --- a/sampler/src/sampler_tui.rs +++ b/sampler/src/sampler_tui.rs @@ -77,38 +77,33 @@ impl NotePoint for SamplerTui { fn note_point (&self) -> usize { self.note_pt.load(Relaxed) } fn set_note_point (&self, x: usize) { self.note_pt.store(x, Relaxed); } } -pub struct SampleList<'a> { - compact: bool, - sampler: &'a Sampler, - editor: &'a MidiEditor -} -impl<'a> SampleList<'a> { - pub fn new (compact: bool, sampler: &'a Sampler, editor: &'a MidiEditor) -> Self { - Self { compact, sampler, editor } - } -} -render!(TuiOut: (self: SampleList<'a>) => { - let Self { compact, sampler, editor } = self; - let note_lo = editor.note_lo().load(Relaxed); - let note_pt = editor.note_point(); - let note_hi = editor.note_hi(); - Outer(Style::default().fg(TuiTheme::g(96))).enclose(Map::new(move||(note_lo..=note_hi).rev(), move|note, i| { - let offset = |a|Push::y(i as u16, Align::n(Fixed::y(1, Fill::x(a)))); - let mut bg = if note == note_pt { TuiTheme::g(64) } else { Color::Reset }; - let mut fg = TuiTheme::g(160); - if sampler.mapped[note].is_some() { - fg = TuiTheme::g(224); - bg = Color::Rgb(0, if note == note_pt { 96 } else { 64 }, 0); - } - if let Some((index, _)) = sampler.recording { - if note == index { - bg = if note == note_pt { Color::Rgb(96,24,0) } else { Color::Rgb(64,16,0) }; - fg = Color::Rgb(224,64,32) +impl Sampler { + const EMPTY: &[(f64, f64)] = &[(0., 0.), (1., 1.), (2., 2.), (0., 2.), (2., 0.)]; + pub fn list <'a> (&'a self, compact: bool, editor: &MidiEditor) -> impl Content + 'a { + let note_lo = editor.note_lo().load(Relaxed); + let note_pt = editor.note_point(); + let note_hi = editor.note_hi(); + Outer(Style::default().fg(TuiTheme::g(96))).enclose(Map::new(move||(note_lo..=note_hi).rev(), move|note, i| { + let offset = |a|Push::y(i as u16, Align::n(Fixed::y(1, Fill::x(a)))); + let mut bg = if note == note_pt { TuiTheme::g(64) } else { Color::Reset }; + let mut fg = TuiTheme::g(160); + if self.mapped[note].is_some() { + fg = TuiTheme::g(224); + bg = Color::Rgb(0, if note == note_pt { 96 } else { 64 }, 0); } - } - let label = if *compact { + if let Some((index, _)) = self.recording { + if note == index { + bg = if note == note_pt { Color::Rgb(96,24,0) } else { Color::Rgb(64,16,0) }; + fg = Color::Rgb(224,64,32) + } + } + offset(Tui::fg_bg(fg, bg, format!("{note:3} {}", self.list_item(note, compact)))) + })) + } + pub fn list_item (&self, note: usize, compact: bool) -> String { + if compact { String::default() - } else if let Some(sample) = &sampler.mapped[note] { + } else if let Some(sample) = &self.mapped[note] { let sample = sample.read().unwrap(); format!("{:8} {:3} {:6}-{:6}/{:6}", sample.name, @@ -119,12 +114,8 @@ render!(TuiOut: (self: SampleList<'a>) => { ) } else { String::from("(none)") - }; - offset(Tui::fg_bg(fg, bg, format!("{note:3} {}", label))) - })) -}); -impl Sampler { - const EMPTY: &[(f64, f64)] = &[(0., 0.), (1., 1.), (2., 2.), (0., 2.), (2., 0.)]; + } + } pub fn viewer (&self, note_pt: usize) -> impl Content { let sample = if let Some((_, sample)) = &self.recording { Some(sample.clone()) diff --git a/tek/src/app.rs b/tek/src/app.rs new file mode 100644 index 00000000..1f0b5bfb --- /dev/null +++ b/tek/src/app.rs @@ -0,0 +1,157 @@ +use crate::*; +#[derive(Default)] +pub struct App { + //jack: Arc>, + //view: EdnView, + //pool: Option, + //editor: Option, + //player: Option, + //compact: AtomicBool, + //size: Measure, + //perf: PerfModel, + //note_buf: Vec, + //midi_buf: Vec>> + + pub jack: Arc>, + pub edn: String, + pub clock: Clock, + pub color: ItemPalette, + pub editing: AtomicBool, + pub pool: Option, + pub editor: Option, + pub player: Option, + pub sampler: Option, + pub midi_buf: Vec>>, + pub midi_ins: Vec>, + pub midi_outs: Vec>, + pub audio_ins: Vec>, + pub audio_outs: Vec>, + pub note_buf: Vec, + pub tracks: Vec, + pub scenes: Vec, + pub selected: ArrangerSelection, + pub splits: Vec, + pub size: Measure, + pub perf: PerfModel, +} +impl EdnViewData for &App {} +impl App { + pub fn sequencer ( + jack: &Arc>, + pool: PoolModel, + editor: MidiEditor, + player: Option, + midi_froms: &[PortConnection], + midi_tos: &[PortConnection], + ) -> Self { + Self { + edn: include_str!("sequencer.edn").to_string(), + jack: jack.clone(), + pool: Some(pool), + editor: Some(editor), + player: player, + editing: false.into(), + midi_buf: vec![vec![];65536], + color: ItemPalette::random(), + ..Default::default() + } + } + pub fn groovebox ( + jack: &Arc>, + pool: PoolModel, + editor: MidiEditor, + player: Option, + midi_froms: &[PortConnection], + midi_tos: &[PortConnection], + sampler: Sampler, + audio_froms: &[&[PortConnection]], + audio_tos: &[&[PortConnection]], + ) -> Self { + Self { + edn: include_str!("groovebox.edn").to_string(), + sampler: Some(sampler), + ..Self::sequencer( + jack, pool, editor, + player, midi_froms, midi_tos + ) + } + } + pub fn arranger ( + jack: &Arc>, + pool: PoolModel, + editor: MidiEditor, + midi_froms: &[PortConnection], + midi_tos: &[PortConnection], + sampler: Sampler, + audio_froms: &[&[PortConnection]], + audio_tos: &[&[PortConnection]], + scenes: usize, + tracks: usize, + track_width: usize, + ) -> Self { + Self { + edn: include_str!("arranger.edn").to_string(), + ..Self::groovebox( + jack, pool, editor, + None, midi_froms, midi_tos, + sampler, audio_froms, audio_tos + ) + } + } +} +render!(TuiOut: (self: App) => self.size.of(EdnView::from_source(self, self.edn.as_ref()))); + +/// Root view for standalone `tek_sequencer`. +pub struct Sequencer { + pub _jack: Arc>, + + pub pool: PoolModel, + pub editor: MidiEditor, + pub player: MidiPlayer, + + pub transport: bool, + pub selectors: bool, + pub compact: bool, + + pub size: Measure, + pub status: bool, + pub note_buf: Vec, + pub midi_buf: Vec>>, + pub perf: PerfModel, +} + +pub struct Groovebox { + pub _jack: Arc>, + pub player: MidiPlayer, + pub pool: PoolModel, + pub editor: MidiEditor, + pub sampler: Sampler, + + pub compact: bool, + pub size: Measure, + pub status: bool, + pub note_buf: Vec, + pub midi_buf: Vec>>, + pub perf: PerfModel, +} + +/// Root view for standalone `tek_arranger` +pub struct Arranger { + pub jack: Arc>, + pub midi_ins: Vec>, + pub midi_outs: Vec>, + pub clock: Clock, + pub pool: PoolModel, + pub tracks: Vec, + pub scenes: Vec, + pub splits: [u16;2], + pub selected: ArrangerSelection, + pub color: ItemPalette, + pub size: Measure, + pub note_buf: Vec, + pub midi_buf: Vec>>, + pub editor: MidiEditor, + pub editing: AtomicBool, + pub perf: PerfModel, + pub compact: bool, +} diff --git a/tek/src/arranger.rs b/tek/src/arranger.rs index a5adfe07..597ca84c 100644 --- a/tek/src/arranger.rs +++ b/tek/src/arranger.rs @@ -5,26 +5,6 @@ mod arranger_track; pub use self::arranger_track::*; mod arranger_h; use ClockCommand::{Play, Pause}; use self::ArrangerCommand as Cmd; -/// Root view for standalone `tek_arranger` -pub struct Arranger { - pub jack: Arc>, - pub midi_ins: Vec>, - pub midi_outs: Vec>, - pub clock: Clock, - pub pool: PoolModel, - pub tracks: Vec, - pub scenes: Vec, - pub splits: [u16;2], - pub selected: ArrangerSelection, - pub color: ItemPalette, - pub size: Measure, - pub note_buf: Vec, - pub midi_buf: Vec>>, - pub editor: MidiEditor, - pub editing: AtomicBool, - pub perf: PerfModel, - pub compact: bool, -} render!(TuiOut: (self: Arranger) => self.size.of(EdnView::from_source(self, Self::EDN))); impl EdnViewData for &Arranger { fn get_content <'a> (&'a self, item: EdnItem<&'a str>) -> RenderBox<'a, TuiOut> { diff --git a/tek/src/arranger/arranger_select.rs b/tek/src/arranger/arranger_select.rs index c8526295..b129ed63 100644 --- a/tek/src/arranger/arranger_select.rs +++ b/tek/src/arranger/arranger_select.rs @@ -1,9 +1,9 @@ use crate::*; -#[derive(PartialEq, Clone, Copy, Debug)] +#[derive(PartialEq, Clone, Copy, Debug, Default)] /// Represents the current user selection in the arranger pub enum ArrangerSelection { /// The whole mix is selected - Mix, + #[default] Mix, /// A track is selected. Track(usize), /// A scene is selected. diff --git a/tek/src/groovebox.rs b/tek/src/groovebox.rs index 6e09b0e0..2dbcaff9 100644 --- a/tek/src/groovebox.rs +++ b/tek/src/groovebox.rs @@ -7,28 +7,7 @@ use MidiEditCommand::*; use MidiPoolCommand::*; use KeyCode::{Char, Delete, Tab, Up, Down, Left, Right}; use std::marker::ConstParamTy; -pub struct Groovebox { - pub _jack: Arc>, - pub player: MidiPlayer, - pub pool: PoolModel, - pub editor: MidiEditor, - pub sampler: Sampler, - - pub compact: bool, - pub size: Measure, - pub status: bool, - pub note_buf: Vec, - pub midi_buf: Vec>>, - pub perf: PerfModel, -} render!(TuiOut: (self: Groovebox) => self.size.of(EdnView::from_source(self, Self::EDN))); -// this works: -//render!(TuiOut: (self: Groovebox) => self.size.of( - //Bsp::s(self.toolbar_view(), - //Bsp::n(self.selector_view(), - //Bsp::n(self.sample_view(), - //Bsp::n(self.status_view(), - //Bsp::w(self.pool_view(), Fill::xy(Bsp::e(self.sampler_view(), &self.editor))))))))); impl EdnViewData for &Groovebox { fn get_content <'a> (&'a self, item: EdnItem<&'a str>) -> RenderBox<'a, TuiOut> { use EdnItem::*; @@ -36,11 +15,11 @@ impl EdnViewData for &Groovebox { Nil => Box::new(()), Exp(items) => Box::new(EdnView::from_items(*self, items.as_slice())), Sym(":editor") => (&self.editor).boxed(), - Sym(":pool") => self.pool_view().boxed(), - Sym(":status") => self.status_view().boxed(), - Sym(":toolbar") => self.toolbar_view().boxed(), - Sym(":sampler") => self.sampler_view().boxed(), - Sym(":sample") => self.sample_view().boxed(), + Sym(":pool") => self.pool().boxed(), + Sym(":status") => self.status().boxed(), + Sym(":toolbar") => self.toolbar().boxed(), + Sym(":sampler") => self.sampler().boxed(), + Sym(":sample") => self.sample().boxed(), _ => panic!("no content for {item:?}") } } @@ -60,14 +39,14 @@ impl EdnViewData for &Groovebox { } impl Groovebox { const EDN: &'static str = include_str!("groovebox.edn"); - fn toolbar_view (&self) -> impl Content + use<'_> { + fn toolbar (&self) -> impl Content + use<'_> { Fill::x(Fixed::y(2, lay!( Fill::x(Align::w(Meter("L/", self.sampler.input_meter[0]))), Fill::x(Align::e(Meter("R/", self.sampler.input_meter[1]))), Align::x(TransportView::new(true, &self.player.clock)), ))) } - fn status_view (&self) -> impl Content + use<'_> { + fn status (&self) -> impl Content + use<'_> { row!( self.player.play_status(), self.player.next_status(), @@ -75,7 +54,7 @@ impl Groovebox { self.editor.edit_status(), ) } - fn sample_view (&self) -> impl Content + use<'_> { + fn sample (&self) -> impl Content + use<'_> { let note_pt = self.editor.note_point(); let sample_h = if self.compact { 0 } else { 5 }; Max::y(sample_h, Fill::xy( @@ -83,21 +62,19 @@ impl Groovebox { Fill::x(Align::w(Fixed::y(1, self.sampler.status(note_pt)))), self.sampler.viewer(note_pt)))) } - fn pool_view (&self) -> impl Content + use<'_> { + fn pool (&self) -> impl Content + use<'_> { let w = self.size.w(); let pool_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; Fixed::x(if self.compact { 5 } else { pool_w }, PoolView(self.compact, &self.pool)) } - fn sampler_view (&self) -> impl Content + use<'_> { + fn sampler (&self) -> impl Content + use<'_> { let note_pt = self.editor.note_point(); let sampler_w = if self.compact { 4 } else { 40 }; let sampler_y = if self.compact { 1 } else { 0 }; - Fixed::x(sampler_w, Push::y(sampler_y, Fill::y( - SampleList::new(self.compact, &self.sampler, &self.editor)))) + Fixed::x(sampler_w, Push::y(sampler_y, Fill::y(self.sampler.list(self.compact, &self.editor)))) } } - audio!(|self: Groovebox, client, scope|{ let t0 = self.perf.get_t0(); if Control::Quit == ClockAudio(&mut self.player).process(client, scope) { @@ -130,7 +107,6 @@ audio!(|self: Groovebox, client, scope|{ self.perf.update(t0, scope); Control::Continue }); - has_clock!(|self: Groovebox|self.player.clock()); pub enum GrooveboxCommand { Compact(bool), @@ -141,7 +117,6 @@ pub enum GrooveboxCommand { Enqueue(Option>>), Sampler(SamplerCommand), } - command!(|self: GrooveboxCommand, state: Groovebox|match self { Self::Enqueue(clip) => { state.player.enqueue_next(clip.as_ref()); @@ -173,10 +148,7 @@ command!(|self: GrooveboxCommand, state: Groovebox|match self { None }, }); - -handle!(TuiIn: |self: Groovebox, input| - GrooveboxCommand::execute_with_state(self, input.event())); - +handle!(TuiIn: |self: Groovebox, input|GrooveboxCommand::execute_with_state(self, input.event())); keymap!(<'a> KEYS_GROOVEBOX = |state: Groovebox, input: Event| GrooveboxCommand { // Tab: Toggle compact mode key(Tab) => Cmd::Compact(!state.compact), @@ -268,21 +240,6 @@ keymap!(<'a> KEYS_GROOVEBOX = |state: Groovebox, input: Event| GrooveboxCommand //row!(&self.cpu, &self.size) //} //} -//render!(TuiOut: (self: Groovebox) => self.size.of( - //Bsp::s(self.toolbar_view(), - //Bsp::n(self.selector_view(), - //Bsp::n(self.sample_view(), - //Bsp::n(self.status_view(), - //Bsp::w(self.pool_view(), Fill::xy(Bsp::e(self.sampler_view(), &self.editor))))))))); - -//const GROOVEBOX_EDN: &'static str = include_str!("groovebox.edn"); - -//impl Content for Groovebox { - //fn content (&self) -> impl Content { - //EdnView::parse(self.edn.as_slice()) - //} -//} - //macro_rules! edn_context { //($Struct:ident |$l:lifetime, $state:ident| { //$($key:literal = $field:ident: $Type:ty => $expr:expr,)* @@ -328,17 +285,17 @@ keymap!(<'a> KEYS_GROOVEBOX = |state: Groovebox, input: Event| GrooveboxCommand //} ////impl Groovebox { - ////fn status_view (&self) -> impl Content + use<'_> { + ////fn status (&self) -> impl Content + use<'_> { ////let note_pt = self.editor.note_point(); ////Align::w(Fixed::y(1, )) ////} - ////fn pool_view (&self) -> impl Content + use<'_> { + ////fn pool (&self) -> impl Content + use<'_> { ////let w = self.size.w(); ////let pool_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; ////Fixed::x(if self.compact { 5 } else { pool_w }, ////) ////} - ////fn sampler_view (&self) -> impl Content + use<'_> { + ////fn sampler (&self) -> impl Content + use<'_> { ////let sampler_w = if self.compact { 4 } else { 11 }; ////let sampler_y = if self.compact { 1 } else { 0 }; ////Fixed::x(sampler_w, Push::y(sampler_y, Fill::y( diff --git a/tek/src/lib.rs b/tek/src/lib.rs index 1c04d4f9..eca5375c 100644 --- a/tek/src/lib.rs +++ b/tek/src/lib.rs @@ -10,6 +10,7 @@ pub type Usually = std::result::Result>; /// Standard optional result type. pub type Perhaps = std::result::Result, Box>; +pub mod app; pub use self::app::*; pub mod arranger; pub use self::arranger::*; pub mod groovebox; pub use self::groovebox::*; pub mod mixer; pub use self::mixer::*; diff --git a/tek/src/sequencer.rs b/tek/src/sequencer.rs index 17b4ca2a..c056dddb 100644 --- a/tek/src/sequencer.rs +++ b/tek/src/sequencer.rs @@ -4,24 +4,6 @@ use KeyCode::{Tab, Char}; use SequencerCommand as Cmd; use MidiEditCommand::*; use MidiPoolCommand::*; -/// Root view for standalone `tek_sequencer`. -pub struct Sequencer { - pub _jack: Arc>, - - pub pool: PoolModel, - pub editor: MidiEditor, - pub player: MidiPlayer, - - pub transport: bool, - pub selectors: bool, - pub compact: bool, - - pub size: Measure, - pub status: bool, - pub note_buf: Vec, - pub midi_buf: Vec>>, - pub perf: PerfModel, -} render!(TuiOut: (self: Sequencer) => self.size.of(EdnView::from_source(self, Self::EDN))); impl EdnViewData for &Sequencer { fn get_content <'a> (&'a self, item: EdnItem<&'a str>) -> RenderBox<'a, TuiOut> { @@ -179,34 +161,3 @@ command!(|self: SequencerCommand, state: Sequencer|match self { None }, }); - -/// Status bar for sequencer app -#[derive(Clone)] -pub struct SequencerStatus { - pub(crate) width: usize, - pub(crate) cpu: Option>, - pub(crate) size: Arc, - pub(crate) playing: bool, -} -from!(|state:&Sequencer|SequencerStatus = { - let samples = state.clock().chunk.load(Relaxed); - let rate = state.clock().timebase.sr.get(); - let buffer = samples as f64 / rate; - let width = state.size.w(); - Self { - width, - playing: state.clock().is_rolling(), - cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%").into()), - size: format!("{}x{}│", width, state.size.h()).into(), - } -}); -render!(TuiOut: (self: SequencerStatus) => Fixed::y(2, lay!( - Sequencer::help(), - Fill::xy(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))), -))); -impl SequencerStatus { - fn stats (&self) -> impl Content + use<'_> { - row!(&self.cpu, &self.size) - } -} - diff --git a/time/src/lib.rs b/time/src/lib.rs index aaa51224..bb6b87e9 100644 --- a/time/src/lib.rs +++ b/time/src/lib.rs @@ -72,10 +72,10 @@ impl Command for ClockCommand { } } -#[derive(Clone)] +#[derive(Clone, Default)] pub struct Clock { /// JACK transport handle. - pub transport: Arc, + pub transport: Arc>, /// Global temporal resolution (shared by [Moment] fields) pub timebase: Arc, /// Current global sample and usec (monotonic from JACK clock) @@ -102,7 +102,7 @@ from!(|jack: &Arc>| Clock = { Self { quant: Arc::new(24.into()), sync: Arc::new(384.into()), - transport: Arc::new(transport), + transport: Arc::new(Some(transport)), chunk: Arc::new((chunk as usize).into()), global: Arc::new(Moment::zero(&timebase)), playhead: Arc::new(Moment::zero(&timebase)), @@ -154,17 +154,21 @@ impl Clock { } /// Start playing, optionally seeking to a given location beforehand pub fn play_from (&self, start: Option) -> Usually<()> { - if let Some(start) = start { - self.transport.locate(start)?; + if let Some(transport) = self.transport.as_ref() { + if let Some(start) = start { + transport.locate(start)?; + } + transport.start()?; } - self.transport.start()?; Ok(()) } /// Pause, optionally seeking to a given location afterwards pub fn pause_at (&self, pause: Option) -> Usually<()> { - self.transport.stop()?; - if let Some(pause) = pause { - self.transport.locate(pause)?; + if let Some(transport) = self.transport.as_ref() { + transport.stop()?; + if let Some(pause) = pause { + transport.locate(pause)?; + } } Ok(()) } @@ -189,21 +193,24 @@ impl Clock { self.global.sample.set(current_frames as f64); self.global.usec.set(current_usecs as f64); + let mut started = self.started.write().unwrap(); + // If transport has just started or just stopped, // update starting point: - let mut started = self.started.write().unwrap(); - match (self.transport.query_state()?, started.as_ref()) { - (TransportState::Rolling, None) => { - let moment = Moment::zero(&self.timebase); - moment.sample.set(current_frames as f64); - moment.usec.set(current_usecs as f64); - *started = Some(moment); - }, - (TransportState::Stopped, Some(_)) => { - *started = None; - }, - _ => {} - }; + if let Some(transport) = self.transport.as_ref() { + match (transport.query_state()?, started.as_ref()) { + (TransportState::Rolling, None) => { + let moment = Moment::zero(&self.timebase); + moment.sample.set(current_frames as f64); + moment.usec.set(current_usecs as f64); + *started = Some(moment); + }, + (TransportState::Stopped, Some(_)) => { + *started = None; + }, + _ => {} + }; + } self.playhead.update_from_sample(started.as_ref() .map(|started|current_frames as f64 - started.sample.get()) diff --git a/tui/src/tui_output.rs b/tui/src/tui_output.rs index c9b440fd..e2b67006 100644 --- a/tui/src/tui_output.rs +++ b/tui/src/tui_output.rs @@ -1,5 +1,6 @@ use crate::*; +#[derive(Default)] pub struct TuiOut { pub buffer: Buffer, pub area: [u16;4]