From 4e2702f69eea48b0ce0c50f886c9d5980c8cecc3 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 11 May 2025 02:06:08 +0300 Subject: [PATCH 1/4] pass root clock - and groovebox works! --- crates/cli/tek.rs | 7 ++++--- crates/device/src/sequencer/seq_model.rs | 24 ++++++++++++------------ 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/crates/cli/tek.rs b/crates/cli/tek.rs index b65a8abf..1e303f23 100644 --- a/crates/cli/tek.rs +++ b/crates/cli/tek.rs @@ -111,10 +111,10 @@ impl Cli { _ => todo!("{mode:?}"), }; let config = Configuration::new(&config_path, false)?; + let clock = Clock::new(jack, self.bpm)?; let mut app = App { jack: jack.clone(), color: ItemTheme::random(), - clock: Clock::new(jack, self.bpm)?, pool: match mode { LaunchMode::Sequencer | LaunchMode::Groovebox => clip.as_ref().map(Into::into), LaunchMode::Arranger { .. } => Some(Default::default()), @@ -141,7 +141,7 @@ impl Cli { &name, None, jack, - None, + Some(&clock), clip.as_ref(), midi_froms.as_slice(), midi_tos.as_slice() @@ -152,7 +152,7 @@ impl Cli { &name, None, jack, - None, + Some(&clock), clip.as_ref(), midi_froms.as_slice(), midi_froms.as_slice(), @@ -165,6 +165,7 @@ impl Cli { scenes, selected: Selection::TrackClip { track: 0, scene: 0 }, config, + clock, ..Default::default() }; if let &LaunchMode::Arranger { scenes, tracks, track_width, .. } = mode { diff --git a/crates/device/src/sequencer/seq_model.rs b/crates/device/src/sequencer/seq_model.rs index 60a57b26..298364ac 100644 --- a/crates/device/src/sequencer/seq_model.rs +++ b/crates/device/src/sequencer/seq_model.rs @@ -18,29 +18,29 @@ pub trait HasSequencer { /// Contains state for playing a clip pub struct Sequencer { /// State of clock and playhead - pub clock: Clock, + pub clock: Clock, /// Start time and clip being played - pub play_clip: Option<(Moment, Option>>)>, + pub play_clip: Option<(Moment, Option>>)>, /// Start time and next clip - pub next_clip: Option<(Moment, Option>>)>, + pub next_clip: Option<(Moment, Option>>)>, /// Play input through output. - pub monitoring: bool, + pub monitoring: bool, /// Write input to sequence. - pub recording: bool, + pub recording: bool, /// Overdub input to sequence. - pub overdub: bool, + pub overdub: bool, /// Send all notes off - pub reset: bool, // TODO?: after Some(nframes) + pub reset: bool, // TODO?: after Some(nframes) /// Record from MIDI ports to current sequence. - pub midi_ins: Vec, + pub midi_ins: Vec, /// Play from current sequence to MIDI ports - pub midi_outs: Vec, + pub midi_outs: Vec, /// Notes currently held at input - pub notes_in: Arc>, + pub notes_in: Arc>, /// Notes currently held at output - pub notes_out: Arc>, + pub notes_out: Arc>, /// MIDI output buffer - pub note_buf: Vec, + pub note_buf: Vec, } impl Default for Sequencer { From e00d870d701132d0136281c41c78f0d839c5151a Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 11 May 2025 03:32:24 +0300 Subject: [PATCH 2/4] groovebox: draw sample info --- config/config_groovebox.edn | 19 ++++---- crates/app/src/view.rs | 15 +++--- crates/device/src/sampler/sampler_model.rs | 4 +- crates/device/src/sampler/sampler_view.rs | 56 ++++++++++++++++------ 4 files changed, 63 insertions(+), 31 deletions(-) diff --git a/config/config_groovebox.edn b/config/config_groovebox.edn index 24154b74..3f63ce1d 100644 --- a/config/config_groovebox.edn +++ b/config/config_groovebox.edn @@ -3,15 +3,16 @@ (info "A sequencer with built-in sampler.") (view - (bsp/a :view-dialog - (bsp/s (fixed/y 1 :view-transport) - (bsp/n (fixed/y 1 :view-status) - (bsp/w :view-meters-output - (bsp/e :view-meters-input - (bsp/n (fixed/y 5 :view-sample-viewer) - (bsp/w (fixed/x :w-sidebar :view-pool) - (bsp/e :view-samples-keys - (fill/y :view-editor)))))))))) + (bsp/a :view-dialog + (bsp/s (fixed/y 1 :view-transport) + (bsp/n (fixed/y 1 :view-status) + (bsp/w :view-meters-output + (bsp/e :view-meters-input + (bsp/n :view-sample-info + (bsp/n (fixed/y 5 :view-sample-viewer) + (bsp/w (fixed/x :w-sidebar :view-pool) + (bsp/e :view-samples-keys + (fill/y :view-editor))))))))))) (keys (layer-if :focus-pool-import "./keys_pool_file.edn") diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs index 4a2c7859..21743eb7 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -41,6 +41,15 @@ impl App { pub fn view_sample_viewer (&self) -> impl Content + use<'_> { self.sampler().map(|s|s.view_sample(self.editor().unwrap().get_note_pos())) } + pub fn view_sample_info (&self) -> impl Content + use<'_> { + self.sampler().map(|s|s.view_sample_info(self.editor().unwrap().get_note_pos())) + } + pub fn view_meters_input (&self) -> impl Content + use<'_> { + self.sampler().map(|s|s.view_meters_input()) + } + pub fn view_meters_output (&self) -> impl Content + use<'_> { + self.sampler().map(|s|s.view_meters_output()) + } pub fn view_dialog (&self) -> impl Content + use<'_> { When::new(self.dialog.is_some(), Bsp::b( Fill::xy(Tui::fg_bg(Rgb(64,64,64), Rgb(32,32,32), "")), @@ -56,12 +65,6 @@ impl App { ))) )) } - pub fn view_meters_input (&self) -> impl Content + use<'_> { - self.sampler().map(|s|s.view_meters_input()) - } - pub fn view_meters_output (&self) -> impl Content + use<'_> { - self.sampler().map(|s|s.view_meters_output()) - } } impl App { diff --git a/crates/device/src/sampler/sampler_model.rs b/crates/device/src/sampler/sampler_model.rs index ad429a9d..1d9ec18e 100644 --- a/crates/device/src/sampler/sampler_model.rs +++ b/crates/device/src/sampler/sampler_model.rs @@ -4,7 +4,7 @@ use crate::*; #[derive(Debug)] pub struct Sampler { /// Name of sampler. - pub name: String, + pub name: Arc, /// Device color. pub color: ItemTheme, /// Audio input ports. Samples get recorded here. @@ -55,7 +55,7 @@ impl Default for Sampler { input_meters: vec![0.0;2], output_meters: vec![0.0;2], audio_outs: vec![], - name: "tek_sampler".to_string(), + name: "tek_sampler".into(), mapped: [const { None };128], unmapped: vec![], voices: Arc::new(RwLock::new(vec![])), diff --git a/crates/device/src/sampler/sampler_view.rs b/crates/device/src/sampler/sampler_view.rs index b8fb9b20..ee67807b 100644 --- a/crates/device/src/sampler/sampler_view.rs +++ b/crates/device/src/sampler/sampler_view.rs @@ -100,33 +100,46 @@ impl Sampler { }))) } - pub fn status (&self, index: usize) -> impl Content { + pub fn view_sample_info (&self, note_pt: usize) -> impl Content + use<'_> { + Fill::x(Fixed::y(1, draw_info(if let Some((_, sample)) = &self.recording { + Some(sample) + } else if let Some(sample) = &self.mapped[note_pt] { + Some(sample) + } else { + None + }))) + } + + pub fn view_status (&self, index: usize) -> impl Content { draw_status(self.mapped[index].as_ref()) } pub fn view_meters_input (&self) -> impl Content + use<'_> { - Tui::bg(Black, Fixed::x(2, Map::east(1, ||self.input_meters.iter(), |value, _index|{ - Fill::y(RmsMeter(*value)) - }))) + draw_meters(&self.input_meters) } pub fn view_meters_output (&self) -> impl Content + use<'_> { - Tui::bg(Black, Fixed::x(2, Map::east(1, ||self.output_meters.iter(), |value, _index|{ - Fill::y(RmsMeter(*value)) - }))) + draw_meters(&self.output_meters) } } +fn draw_meters (meters: &[f32]) -> impl Content + use<'_> { + Tui::bg(Black, Fixed::x(2, Map::east(1, ||meters.iter(), |value, _index|{ + Fill::y(RmsMeter(*value)) + }))) +} + fn draw_list_item (sample: &Option>>) -> String { if let Some(sample) = sample { let sample = sample.read().unwrap(); - format!("{:8} {:3} {:6}-{:6}/{:6}", - sample.name, - sample.gain, - sample.start, - sample.end, - sample.channels[0].len() - ) + format!("{:8}", sample.name) + //format!("{:8} {:3} {:6}-{:6}/{:6}", + //sample.name, + //sample.gain, + //sample.start, + //sample.end, + //sample.channels[0].len() + //) } else { String::from("........") } @@ -187,6 +200,21 @@ fn draw_viewer (sample: Option<&Arc>>) -> impl Content + }) } +fn draw_info (sample: Option<&Arc>>) -> impl Content + use<'_> { + When(sample.is_some(), Thunk::new(move||{ + let sample = sample.unwrap().read().unwrap(); + let theme = ItemTheme::G[96]; + row!( + FieldH(theme, "Name", format!("{:<10}", sample.name.clone())), + FieldH(theme, "Length", format!("{:<8}", sample.channels[0].len())), + FieldH(theme, "Start", format!("{:<8}", sample.start)), + FieldH(theme, "End", format!("{:<8}", sample.end)), + FieldH(theme, "Transpose", " 0 "), + FieldH(theme, "Gain", format!("{}", sample.gain)), + ) + })) +} + fn draw_status (sample: Option<&Arc>>) -> impl Content { Tui::bold(true, Tui::fg(Tui::g(224), sample .map(|sample|{ From 85a144798b0a60decc76315207533d79138c3815 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 11 May 2025 04:01:23 +0300 Subject: [PATCH 3/4] editor: add note and advance; preparations --- config/keys_editor.edn | 39 +++++++++---------- crates/app/src/api.rs | 2 +- crates/device/src/editor/editor_api.rs | 49 +++++++++++++----------- crates/device/src/editor/editor_model.rs | 2 +- 4 files changed, 47 insertions(+), 45 deletions(-) diff --git a/config/keys_editor.edn b/config/keys_editor.edn index 09c225f6..64523ac7 100644 --- a/config/keys_editor.edn +++ b/config/keys_editor.edn @@ -1,25 +1,24 @@ -(@up editor note-pos :note-pos-next) -(@down editor note-pos :note-pos-prev) -(@pgup editor note-pos :note-pos-next-octave) -(@pgdn editor note-pos :note-pos-prev-octave) +(@left editor set-time-pos :time-pos-prev) +(@right editor set-time-pos :time-pos-next) -(@comma editor note-len :note-len-prev) -(@period editor note-len :note-len-next) -(@lt editor note-len :note-len-prev) -(@gt editor note-len :note-len-next) +(@equal editor set-time-zoom :time-zoom-prev) +(@minus editor set-time-zoom :time-zoom-next) +(@plus editor set-time-zoom :time-zoom-next-fine) +(@underscore editor set-time-zoom :time-zoom-prev-fine) -(@plus editor note-range :note-range-next) -(@underscore editor note-range :note-range-prev) +(@z editor set-time-lock) -(@left editor time-pos :time-pos-prev) -(@right editor time-pos :time-pos-next) +(@up editor set-note-pos :note-pos-next) +(@down editor set-note-pos :note-pos-prev) +(@pgup editor set-note-pos :note-pos-next-octave) +(@pgdn editor set-note-pos :note-pos-prev-octave) -(@equal editor time-zoom :time-zoom-prev) -(@minus editor time-zoom :time-zoom-next) +(@comma editor set-note-len :note-len-prev) +(@period editor set-note-len :note-len-next) +(@lt editor set-note-len :note-len-prev) +(@gt editor set-note-len :note-len-next) -(@z editor time-lock) - -(@enter editor note-put) -(@shift-enter editor note-append) -(@del editor note-del) -(@shift-del editor note-del) +(@a editor append-note :true) +(@enter editor append-note :false) +(@del editor delete-note) +(@shift-del editor delete-note) diff --git a/crates/app/src/api.rs b/crates/app/src/api.rs index ce88fe99..f601ad70 100644 --- a/crates/app/src/api.rs +++ b/crates/app/src/api.rs @@ -280,7 +280,7 @@ handle!(TuiIn: |self: App, input|Ok(if let Some(command) = self.config.keys.comm // update linked sampler after editor action app.sampler_mut().map(|sampler|match command { // autoselect: automatically select sample in sampler - MidiEditCommand::NotePos { pos } => { sampler.set_note_pos(pos); }, + MidiEditCommand::SetNotePos { pos } => { sampler.set_note_pos(pos); }, _ => {} }); undo diff --git a/crates/device/src/editor/editor_api.rs b/crates/device/src/editor/editor_api.rs index a1c6a6e5..85748cae 100644 --- a/crates/device/src/editor/editor_api.rs +++ b/crates/device/src/editor/editor_api.rs @@ -4,12 +4,6 @@ use crate::*; fn _todo_opt_clip_stub (&self) -> Option>> { todo!() } - fn time_lock (&self) -> bool { - self.get_time_lock() - } - fn time_lock_toggled (&self) -> bool { - !self.get_time_lock() - } fn note_length (&self) -> usize { self.get_note_len() @@ -55,10 +49,10 @@ use crate::*; self.get_time_pos() } fn time_pos_next (&self) -> usize { - self.get_time_pos() + self.time_zoom() + self.get_time_pos() + self.get_note_len() } fn time_pos_prev (&self) -> usize { - self.get_time_pos().saturating_sub(self.time_zoom()) + self.get_time_pos().saturating_sub(self.get_note_len()) } fn time_zoom (&self) -> usize { @@ -67,29 +61,37 @@ use crate::*; fn time_zoom_next (&self) -> usize { self.get_time_zoom() + 1 } + fn time_zoom_next_fine (&self) -> usize { + self.get_time_zoom() + 1 + } fn time_zoom_prev (&self) -> usize { self.get_time_zoom().saturating_sub(1).max(1) } + fn time_zoom_prev_fine (&self) -> usize { + self.get_time_zoom().saturating_sub(1).max(1) + } + + fn time_lock (&self) -> bool { + self.get_time_lock() + } + fn time_lock_toggled (&self) -> bool { + !self.get_time_lock() + } } #[tengri_proc::command(MidiEditor)] impl MidiEditCommand { - // TODO: 1-9 seek markers that by default start every 8th of the clip - fn note_append (editor: &mut MidiEditor) -> Perhaps { - editor.put_note(true); + fn append_note (editor: &mut MidiEditor, advance: bool) -> Perhaps { + editor.put_note(advance); Ok(None) } - fn note_put (editor: &mut MidiEditor) -> Perhaps { - editor.put_note(false); - Ok(None) - } - fn note_del (_editor: &mut MidiEditor) -> Perhaps { + fn delete_note (_editor: &mut MidiEditor) -> Perhaps { todo!() } - fn note_pos (editor: &mut MidiEditor, pos: usize) -> Perhaps { + fn set_note_pos (editor: &mut MidiEditor, pos: usize) -> Perhaps { editor.set_note_pos(pos.min(127)); Ok(None) } - fn note_len (editor: &mut MidiEditor, value: usize) -> Perhaps { + fn set_note_len (editor: &mut MidiEditor, value: usize) -> Perhaps { //let note_len = editor.get_note_len(); //let time_zoom = editor.get_time_zoom(); editor.set_note_len(value); @@ -98,24 +100,24 @@ use crate::*; //} Ok(None) } - fn note_scroll (editor: &mut MidiEditor, value: usize) -> Perhaps { + fn set_note_scroll (editor: &mut MidiEditor, value: usize) -> Perhaps { editor.set_note_lo(value.min(127)); Ok(None) } - fn time_pos (editor: &mut MidiEditor, value: usize) -> Perhaps { + fn set_time_pos (editor: &mut MidiEditor, value: usize) -> Perhaps { editor.set_time_pos(value); Ok(None) } - fn time_scroll (editor: &mut MidiEditor, value: usize) -> Perhaps { + fn set_time_scroll (editor: &mut MidiEditor, value: usize) -> Perhaps { editor.set_time_start(value); Ok(None) } - fn time_zoom (editor: &mut MidiEditor, value: usize) -> Perhaps { + fn set_time_zoom (editor: &mut MidiEditor, value: usize) -> Perhaps { editor.set_time_zoom(value); editor.redraw(); Ok(None) } - fn time_lock (editor: &mut MidiEditor, value: bool) -> Perhaps { + fn set_time_lock (editor: &mut MidiEditor, value: bool) -> Perhaps { editor.set_time_lock(value); Ok(None) } @@ -123,4 +125,5 @@ use crate::*; editor.set_clip(clip.as_ref()); Ok(None) } + // TODO: 1-9 seek markers that by default start every 8th of the clip } diff --git a/crates/device/src/editor/editor_model.rs b/crates/device/src/editor/editor_model.rs index a92e96ba..90d6d2ee 100644 --- a/crates/device/src/editor/editor_model.rs +++ b/crates/device/src/editor/editor_model.rs @@ -61,7 +61,7 @@ impl MidiEditor { clip.notes[note_end].push(note_off); } if advance { - self.set_time_pos(note_end); + self.set_time_pos(note_end + 1); } redraw = true; } From cdeb355972a8f31f0b7d60182141c005038888e4 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 11 May 2025 04:02:55 +0300 Subject: [PATCH 4/4] fix menu and help bindings --- config/keys_global.edn | 4 ++-- crates/app/src/api.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/keys_global.edn b/config/keys_global.edn index cd9fc7e6..bcb16252 100644 --- a/config/keys_global.edn +++ b/config/keys_global.edn @@ -1,5 +1,5 @@ -(@esc menu) -(@f1 help) +(@esc toggle-menu) +(@f1 toggle-help) (@u undo 1) (@shift-u redo 1) diff --git a/crates/app/src/api.rs b/crates/app/src/api.rs index f601ad70..3b2acbb6 100644 --- a/crates/app/src/api.rs +++ b/crates/app/src/api.rs @@ -203,11 +203,11 @@ handle!(TuiIn: |self: App, input|Ok(if let Some(command) = self.config.keys.comm } #[tengri_proc::command(App)] impl AppCommand { - fn toggle_help (app: &mut App, value: bool) -> Perhaps { + fn toggle_help (app: &mut App) -> Perhaps { app.toggle_dialog(Some(Dialog::Help)); Ok(None) } - fn toggle_menu (app: &mut App, value: bool) -> Perhaps { + fn toggle_menu (app: &mut App) -> Perhaps { app.toggle_dialog(Some(Dialog::Menu)); Ok(None) }