diff --git a/Justfile b/Justfile index 7d088aca..24518c7b 100644 --- a/Justfile +++ b/Justfile @@ -14,7 +14,7 @@ test: cargo test cloc: - for src in {cli,edn/src,input/src,jack/src,midi/src,output/src,tek/src,time/src,tui/src}; do echo $src; cloc $src; done + for src in {cli,edn/src,input/src,jack/src,midi/src,output/src,tek/src,time/src,tui/src}; do echo; echo $src; cloc --quiet $src; done status: cargo c diff --git a/cli/tek.rs b/cli/tek.rs index 8ef6c666..3fd11351 100644 --- a/cli/tek.rs +++ b/cli/tek.rs @@ -66,6 +66,7 @@ pub enum TekMode { pub fn main () -> Usually<()> { let cli = TekCli::parse(); let name = cli.name.as_ref().map_or("tek", |x|x.as_str()); + let color = ItemPalette::random(); let jack = JackConnection::new(name)?; let engine = Tui::new()?; let empty = &[] as &[&str]; @@ -77,109 +78,113 @@ pub fn main () -> Usually<()> { let right_tos = PortConnection::collect(&cli.right_to, empty, empty); let perf = PerfModel::default(); let size = Measure::new(); - let default_clip = ||{ - let len = 384usize; - let color = ItemColor::random().into(); - Arc::new(RwLock::new(MidiClip::new("Clip", true, len, None, Some(color)))) + let default_clip = ||Arc::new(RwLock::new(MidiClip::new( + "Clip", true, 384usize, None, Some(ItemColor::random().into())))); + let default_player = |jack: &Arc>, clip: Option<&Arc>>| + MidiPlayer::new(&jack, name, clip, &midi_froms, &midi_tos); + let default_sampler = |jack: &Arc>| + Sampler::new(jack, &"sampler", &midi_froms, + &[&left_froms, &right_froms], &[&left_tos, &right_tos]); + let default_bpm = |clock: Clock|{ + if let Some(bpm) = cli.bpm { + clock.timebase.bpm.set(bpm); + } + clock }; + let default_clock = |jack: &Arc>|{ + let clock = Clock::from(jack); + default_bpm(clock) + }; + // TODO: enable sync master/follow + //let sync_clock = |jack: &Arc>, app|{ + //if cli.sync_lead { + //jack.read().unwrap().client().register_timebase_callback(false, |mut state|{ + //app.clock().playhead.update_from_sample(state.position.frame() as f64); + //state.position.bbt = Some(app.clock().bbt()); + //state.position + //}) + //} else if cli.sync_follow { + //jack.read().unwrap().client().register_timebase_callback(false, |state|{ + //app.clock().playhead.update_from_sample(state.position.frame() as f64); + //state.position + //}) + //} else { + //Ok(()) + //} + //}; Ok(match cli.mode { - TekMode::Clock => engine.run(&jack.activate_with(|jack|Ok(TransportTui { - jack: jack.clone(), - clock: Clock::from(jack), + jack: jack.clone(), clock: default_clock(jack), }))?)?, - TekMode::Sequencer => engine.run(&jack.activate_with(|jack|Ok({ - let clip = default_clip(); - let player = MidiPlayer::new(&jack, name, Some(&clip), &midi_froms, &midi_tos)?; + let clip = default_clip(); + let mut player = default_player(jack, Some(&clip))?; + player.clock = default_bpm(player.clock); Sequencer { - _jack: jack.clone(), - clock: player.clock.clone(), - + _jack: jack.clone(), player, - editor: MidiEditor::from(&clip), - pool: PoolModel::from(&clip), - compact: true, - transport: true, - selectors: true, - midi_buf: vec![vec![];65536], - note_buf: vec![], - status: true, + 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), + cursor: (0, 0), editing: None, - mode: None, + mode: None, note_lo: 36.into(), note_pt: 36.into(), - color: ItemPalette::from(Color::Rgb(64, 128, 32)), - state: Sampler::new(jack, &"sampler", &midi_froms, - &[&left_froms, &right_froms], &[&left_tos, &right_tos])?, + state: default_sampler(jack)?, + color, size, } ))?)?, - TekMode::Groovebox => engine.run(&jack.activate_with(|jack|Ok({ - let clip = default_clip(); - let player = MidiPlayer::new(jack, &"sequencer", Some(&clip), &midi_froms, &midi_tos)?; - let sampler = Sampler::new(jack, &"sampler", &midi_froms, - &[&left_froms, &right_froms], &[&left_tos, &right_tos])?; - jack.read().unwrap().client().connect_ports(&player.midi_outs[0].port, &sampler.midi_in.port)?; + 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(), - + _jack: jack.clone(), player, sampler, - pool: PoolModel::from(&clip), - editor: MidiEditor::from(&clip), - compact: true, - status: true, + pool: (&clip).into(), + editor: (&clip).into(), midi_buf: vec![vec![];65536], note_buf: vec![], perf, size, + compact: true, + status: true, }; - if let Some(bpm) = cli.bpm { - app.clock().timebase.bpm.set(bpm); - } - if cli.sync_lead { - jack.read().unwrap().client().register_timebase_callback(false, |mut state|{ - app.clock().playhead.update_from_sample(state.position.frame() as f64); - state.position.bbt = Some(app.clock().bbt()); - state.position - })? - } else if cli.sync_follow { - jack.read().unwrap().client().register_timebase_callback(false, |state|{ - app.clock().playhead.update_from_sample(state.position.frame() as f64); - state.position - })? - } app }))?)?, - TekMode::Arranger { scenes, tracks, track_width, .. } => engine.run(&jack.activate_with(|jack|Ok({ - let clock = Clock::from(jack); let clip = default_clip(); + let clock = default_clock(jack); let mut app = Arranger { jack: jack.clone(), clock, - pool: (&clip).into(), editor: (&clip).into(), - selected: ArrangerSelection::Clip(0, 0), + selected: ArrangerSelection::Scene(4), scenes: vec![], tracks: vec![], - color: ItemPalette::random(), splits: [12, 20], midi_buf: vec![vec![];65536], note_buf: vec![], compact: true, + color, perf, size, }; diff --git a/tek/src/arranger/arranger_tui.rs b/tek/src/arranger/arranger_tui.rs index 3be532b6..84ddc882 100644 --- a/tek/src/arranger/arranger_tui.rs +++ b/tek/src/arranger/arranger_tui.rs @@ -43,26 +43,40 @@ impl Arranger { } /// A cell that is 3-row on its own, but stacks, giving (N+1)*2 rows per N cells. - fn phat_cell > (color: ItemPalette, last: ItemPalette, field: T) -> impl Content { - Bsp::s( - Self::phat_lo(color.base.rgb, last.base.rgb), - Bsp::n( - Self::phat_hi(color.base.rgb, last.base.rgb), + fn phat_cell > ( + color: ItemPalette, last: ItemPalette, field: T + ) -> impl Content { + Bsp::s(Self::phat_lo(color.base.rgb, last.base.rgb), + Bsp::n(Self::phat_hi(color.base.rgb, last.base.rgb), Fixed::y(1, Fill::x(Tui::fg_bg(color.lightest.rgb, color.base.rgb, field))), ) ) } - fn phat_cell_3 > (top: Color, middle: Color, bottom: Color, field: T) -> impl Content { - Bsp::s( - Self::phat_lo(middle, top), - Bsp::n( - Self::phat_hi(bottom, middle), + fn phat_cell_3 > ( + field: T, top: Color, middle: Color, bottom: Color + ) -> impl Content { + Bsp::s(Self::phat_lo(middle, top), + Bsp::n(Self::phat_hi(middle, bottom), Fixed::y(1, Fill::x(Tui::bg(middle, field))), ) ) } + fn phat_sel_3 > ( + selected: bool, field_1: T, field_2: T, top: Option, middle: Color, bottom: Color + ) -> impl Content { + let border = Style::default().fg(Color::Rgb(255,255,255)).bg(middle); + Either(selected, + Tui::bg(middle, Outer(border).enclose( Align::w(Bsp::s("", Bsp::s(field_1, ""))))), + Bsp::s( + Fixed::y(1, top.map(|top|Self::phat_lo(middle, top))), + Bsp::n(Self::phat_hi(middle, bottom), + Fixed::y(1, Fill::x(Tui::bg(middle, field_2))), + ) + )) + } + fn output_row_header <'a> (&'a self) -> BoxThunk<'a, TuiOut> { (||Tui::bold(true, Tui::fg_bg(TuiTheme::g(0), TuiTheme::g(200), "[ ] Out 1: NI")).boxed()).into() } @@ -118,7 +132,7 @@ impl Arranger { let iter = ||self.tracks_with_widths(); (move||Fixed::y(2, Map::new(iter, move|(_, track, x1, x2), i| { let color = track.color(); - let name = format!(" {}", &track.name); + let name = Push::x(1, &track.name); Tui::bg(color.base.rgb, map_east(x1 as u16, (x2 - x1) as u16, Tui::fg_bg(color.lightest.rgb, color.base.rgb, Self::phat_cell(color, color.darkest.rgb.into(), @@ -141,15 +155,30 @@ impl Arranger { (||{ let scenes_w = 16;//.max(SCENES_W_OFFSET + ArrangerScene::longest_name(&self.scenes) as u16); let last_color = Arc::new(RwLock::new(ItemPalette::from(Color::Rgb(0, 0, 0)))); + let selected_scene = match self.selected { + ArrangerSelection::Scene(s) => Some(s), + _ => None + }; Tui::bg(Color::Rgb(0,0,0), Fill::y(Map::new( ||self.scenes_with_heights(2), move|(_, scene, y1, y2), i| { let h = (y2 - y1) as u16; + let name = format!("🭬{}", &scene.name); let color = scene.color(); - let name = format!("🭬{}", &scene.name); - let cell = Self::phat_cell(color, *last_color.read().unwrap(), name); + let cell = Self::phat_sel_3( + selected_scene == Some(i), + Push::x(1, name.clone()), + Push::x(1, name), + if selected_scene.map(|s|s + 1) == Some(i) { + None + } else { + Some(last_color.read().unwrap().base.rgb) + }, + color.base.rgb, + Color::Rgb(0, 0, 0) + ); *last_color.write().unwrap() = color; - map_south(y1 as u16, 2, Fill::x(cell)) + map_south(y1 as u16, 3, Fill::x(cell)) } ))).boxed() }).into() @@ -160,6 +189,10 @@ impl Arranger { let cell = Bsp::s("[Rec]", "[Mon]"); let color: ItemPalette = track.color().dark.into(); let last_color = Arc::new(RwLock::new(ItemPalette::from(Color::Rgb(0, 0, 0)))); + let (selected_track, selected_scene) = match self.selected { + ArrangerSelection::Clip(t, s) => (Some(t), Some(s)), + _ => (None, None) + }; map_east(x1 as u16, w, Fixed::x(w, Tui::bg(Color::Rgb(0,0,0), Fill::y(Map::new( ||self.scenes_with_heights(2), move|(_, scene, y1, y2), i| { @@ -168,11 +201,11 @@ impl Arranger { let name = format!("🭬{}", &scene.name); //*last_color.write().unwrap() = color map_south(y1 as u16, 2, Fill::x(Self::phat_cell_3( + Tui::fg(TuiTheme::g(64), " ⏹ "), TuiTheme::g(32).into(), TuiTheme::g(32).into(), TuiTheme::g(32).into(), //Tui::fg(TuiTheme::g(64), " ⏺ ") - Tui::fg(TuiTheme::g(64), " ⏹ ") ))) } ))).boxed() diff --git a/tek/src/sequencer.rs b/tek/src/sequencer.rs index e284ec7b..2540ac42 100644 --- a/tek/src/sequencer.rs +++ b/tek/src/sequencer.rs @@ -16,7 +16,6 @@ pub struct Sequencer { pub selectors: bool, pub compact: bool, - pub clock: Clock, pub size: Measure, pub status: bool, pub note_buf: Vec, @@ -95,7 +94,7 @@ audio!(|self:Sequencer, client, scope|{ Control::Continue }); has_size!(|self:Sequencer|&self.size); -has_clock!(|self:Sequencer|&self.clock); +has_clock!(|self:Sequencer|&self.player.clock); has_phrases!(|self:Sequencer|self.pool.phrases); has_editor!(|self:Sequencer|self.editor); handle!(TuiIn: |self:Sequencer,input|SequencerCommand::execute_with_state(self, input.event())); @@ -185,13 +184,13 @@ pub struct SequencerStatus { pub(crate) playing: bool, } from!(|state:&Sequencer|SequencerStatus = { - let samples = state.clock.chunk.load(Relaxed); - let rate = state.clock.timebase.sr.get(); + 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(), + playing: state.clock().is_rolling(), cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%").into()), size: format!("{}x{}│", width, state.size.h()).into(), }