From 7186ec397970cbda5704e53303d4dc70e6281ef4 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 25 Dec 2024 05:19:48 +0100 Subject: [PATCH 001/815] fix arrow keys fallthrough in arranger --- crates/tek/Cargo.toml | 3 + crates/tek/src/tui/arranger_command.rs | 99 ++++++++++++++++++++++++-- 2 files changed, 95 insertions(+), 7 deletions(-) diff --git a/crates/tek/Cargo.toml b/crates/tek/Cargo.toml index 349ca090..d90fe230 100644 --- a/crates/tek/Cargo.toml +++ b/crates/tek/Cargo.toml @@ -3,6 +3,9 @@ name = "tek" edition = "2021" version = "0.2.0" +[profile.release] +lto = true + [dependencies] #no_deadlocks = "1.3.2" #vst3 = "0.1.0" diff --git a/crates/tek/src/tui/arranger_command.rs b/crates/tek/src/tui/arranger_command.rs index ddba4ce5..8c95e457 100644 --- a/crates/tek/src/tui/arranger_command.rs +++ b/crates/tek/src/tui/arranger_command.rs @@ -66,14 +66,99 @@ input_to_command!(ArrangerCommand: |state: ArrangerTui, input|match input.e // Tab: Toggle visibility of phrase pool column key_pat!(Tab) => Self::Phrases(PhrasesCommand::Show(!state.phrases.visible)), - _ => to_arrangement_command(state, input).or_else(||{ - if let Some(command) = PhraseCommand::input_to_command(&state.editor, input) { - Some(Self::Editor(command)) - } else if let Some(command) = PhrasesCommand::input_to_command(&state.phrases, input) { - Some(Self::Phrases(command)) - } else { - None + _ => { + use ArrangerCommand as Cmd; + use ArrangerSelection as Selected; + use ArrangerSceneCommand as Scene; + use ArrangerTrackCommand as Track; + use ArrangerClipCommand as Clip; + let t_len = state.tracks.len(); + let s_len = state.scenes.len(); + match state.selected() { + Selected::Clip(t, s) => match input.event() { + key_pat!(Char('g')) => Some(Cmd::Phrases(PhrasesCommand::Select(0))), + key_pat!(Char('q')) => Some(Cmd::Clip(Clip::Enqueue(t, s))), + key_pat!(Char(',')) => Some(Cmd::Clip(Clip::Put(t, s, None))), + key_pat!(Char('.')) => Some(Cmd::Clip(Clip::Put(t, s, None))), + key_pat!(Char('<')) => Some(Cmd::Clip(Clip::Put(t, s, None))), + key_pat!(Char('>')) => Some(Cmd::Clip(Clip::Put(t, s, None))), + key_pat!(Char('p')) => Some(Cmd::Clip(Clip::Put(t, s, Some(state.phrases.phrase().clone())))), + key_pat!(Char('l')) => Some(Cmd::Clip(ArrangerClipCommand::SetLoop(t, s, false))), + key_pat!(Delete) => Some(Cmd::Clip(Clip::Put(t, s, None))), + + key_pat!(Up) => Some(Cmd::Select( + if s > 0 { Selected::Clip(t, s - 1) } else { Selected::Track(t) })), + key_pat!(Down) => Some(Cmd::Select( + Selected::Clip(t, (s + 1).min(s_len.saturating_sub(1))))), + key_pat!(Left) => Some(Cmd::Select( + if t > 0 { Selected::Clip(t - 1, s) } else { Selected::Scene(s) })), + key_pat!(Right) => Some(Cmd::Select( + Selected::Clip((t + 1).min(t_len.saturating_sub(1)), s))), + + _ => None + }, + Selected::Scene(s) => match input.event() { + key_pat!(Char(',')) => Some(Cmd::Scene(Scene::Swap(s, s - 1))), + key_pat!(Char('.')) => Some(Cmd::Scene(Scene::Swap(s, s + 1))), + key_pat!(Char('<')) => Some(Cmd::Scene(Scene::Swap(s, s - 1))), + key_pat!(Char('>')) => Some(Cmd::Scene(Scene::Swap(s, s + 1))), + key_pat!(Char('q')) => Some(Cmd::Scene(Scene::Enqueue(s))), + key_pat!(Delete) => Some(Cmd::Scene(Scene::Delete(s))), + key_pat!(Char('c')) => Some(Cmd::Scene(Scene::SetColor(s, ItemPalette::random()))), + + key_pat!(Up) => Some( + Cmd::Select(if s > 0 { Selected::Scene(s - 1) } else { Selected::Mix })), + key_pat!(Down) => Some( + Cmd::Select(Selected::Scene((s + 1).min(s_len.saturating_sub(1))))), + key_pat!(Left) => + return None, + key_pat!(Right) => Some( + Cmd::Select(Selected::Clip(0, s))), + + _ => None + }, + Selected::Track(t) => match input.event() { + key_pat!(Char(',')) => Some(Cmd::Track(Track::Swap(t, t - 1))), + key_pat!(Char('.')) => Some(Cmd::Track(Track::Swap(t, t + 1))), + key_pat!(Char('<')) => Some(Cmd::Track(Track::Swap(t, t - 1))), + key_pat!(Char('>')) => Some(Cmd::Track(Track::Swap(t, t + 1))), + key_pat!(Delete) => Some(Cmd::Track(Track::Delete(t))), + key_pat!(Char('c')) => Some(Cmd::Track(Track::SetColor(t, ItemPalette::random()))), + + key_pat!(Up) => + return None, + key_pat!(Down) => Some( + Cmd::Select(Selected::Clip(t, 0))), + key_pat!(Left) => Some( + Cmd::Select(if t > 0 { Selected::Track(t - 1) } else { Selected::Mix })), + key_pat!(Right) => Some( + Cmd::Select(Selected::Track((t + 1).min(t_len.saturating_sub(1))))), + + _ => None + }, + Selected::Mix => match input.event() { + key_pat!(Delete) => Some(Cmd::Clear), + key_pat!(Char('0')) => Some(Cmd::StopAll), + key_pat!(Char('c')) => Some(Cmd::Color(ItemPalette::random())), + + key_pat!(Up) => + return None, + key_pat!(Down) => Some( + Cmd::Select(Selected::Scene(0))), + key_pat!(Left) => + return None, + key_pat!(Right) => Some( + Cmd::Select(Selected::Track(0))), + + _ => None + }, } + }.or_else(||if let Some(command) = PhraseCommand::input_to_command(&state.editor, input) { + Some(Self::Editor(command)) + } else if let Some(command) = PhrasesCommand::input_to_command(&state.phrases, input) { + Some(Self::Phrases(command)) + } else { + None })? }); fn to_arrangement_command (state: &ArrangerTui, input: &TuiInput) -> Option { From 44b94d2b1a8bc867171e1d0a4b0130cb4433ba8c Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 25 Dec 2024 05:27:50 +0100 Subject: [PATCH 002/815] update event handling for sequencer --- crates/tek/Cargo.toml | 3 --- crates/tek/src/tui/app_sequencer.rs | 29 +++++++++++++---------------- crates/tek/src/tui/tui_input.rs | 13 ++++++++----- 3 files changed, 21 insertions(+), 24 deletions(-) diff --git a/crates/tek/Cargo.toml b/crates/tek/Cargo.toml index d90fe230..349ca090 100644 --- a/crates/tek/Cargo.toml +++ b/crates/tek/Cargo.toml @@ -3,9 +3,6 @@ name = "tek" edition = "2021" version = "0.2.0" -[profile.release] -lto = true - [dependencies] #no_deadlocks = "1.3.2" #vst3 = "0.1.0" diff --git a/crates/tek/src/tui/app_sequencer.rs b/crates/tek/src/tui/app_sequencer.rs index 01e6db86..66307476 100644 --- a/crates/tek/src/tui/app_sequencer.rs +++ b/crates/tek/src/tui/app_sequencer.rs @@ -12,7 +12,6 @@ pub struct SequencerTui { pub(crate) player: PhrasePlayerModel, pub(crate) editor: PhraseEditorModel, pub(crate) size: Measure, - pub(crate) show_pool: bool, pub(crate) status: bool, pub(crate) note_buf: Vec, pub(crate) midi_buf: Vec>>, @@ -33,7 +32,6 @@ from_jack!(|jack|SequencerTui { midi_buf: vec![vec![];65536], note_buf: vec![], perf: PerfModel::default(), - show_pool: true, status: true, clock, } @@ -41,7 +39,7 @@ from_jack!(|jack|SequencerTui { render!(|self: SequencerTui|{ let w = self.size.w(); let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; - let pool_w = if self.show_pool { phrase_w } else { 0 }; + let pool_w = if self.phrases.visible { phrase_w } else { 0 }; let pool = Fill::h(Align::e(PoolView(&self.phrases))); let with_pool = move|x|Tui::split_w(false, pool_w, pool, x); let status = SequencerStatus::from(self); @@ -83,13 +81,15 @@ has_phrases!(|self:SequencerTui|self.phrases.phrases); has_editor!(|self:SequencerTui|self.editor); handle!(|self:SequencerTui,input|SequencerCommand::execute_with_state(self, input)); #[derive(Clone, Debug)] pub enum SequencerCommand { + History(isize), Clock(ClockCommand), Phrases(PhrasesCommand), Editor(PhraseCommand), Enqueue(Option>>), - ShowPool(bool), } -input_to_command!(SequencerCommand: |state:SequencerTui,input|match input.event() { +input_to_command!(SequencerCommand: |state: SequencerTui, input|match input.event() { + // TODO: k: toggle on-screen keyboard + key_pat!(Ctrl-Char('k')) => { todo!("keyboard") }, // Transport: Play/pause key_pat!(Char(' ')) => Clock( if state.clock().is_stopped() { Play(None) } else { Pause(None) } @@ -98,14 +98,12 @@ input_to_command!(SequencerCommand: |state:SequencerTui,input|match input.e key_pat!(Shift-Char(' ')) => Clock( if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) } ), - // TODO: u: undo - key_pat!(Char('u')) => { todo!("undo") }, - // TODO: Shift-U: redo - key_pat!(Char('U')) => { todo!("redo") }, - // TODO: k: toggle on-screen keyboard - key_pat!(Ctrl-Char('k')) => { todo!("keyboard") }, + // u: undo + key_pat!(Char('u')) => History(-1), + // Shift-U: redo + key_pat!(Char('U')) => History( 1), // Tab: Toggle visibility of phrase pool column - key_pat!(Tab) => ShowPool(!state.show_pool), + key_pat!(Tab) => Phrases(PhrasesCommand::Show(!state.phrases.visible)), // q: Enqueue currently edited phrase key_pat!(Char('q')) => Enqueue(Some(state.phrases.phrase().clone())), // 0: Enqueue phrase 0 (stop all) @@ -164,8 +162,7 @@ command!(|self: SequencerCommand, state: SequencerTui|match self { state.player.enqueue_next(phrase.as_ref()); None }, - Self::ShowPool(value) => { - state.show_pool = value; - None - } + Self::History(delta) => { + todo!("undo/redo") + }, }); diff --git a/crates/tek/src/tui/tui_input.rs b/crates/tek/src/tui/tui_input.rs index 7777b0e5..5cce5e33 100644 --- a/crates/tek/src/tui/tui_input.rs +++ b/crates/tek/src/tui/tui_input.rs @@ -59,7 +59,10 @@ impl Input for TuiInput { }; } -pub struct EventMap<'a, const N: usize, E, T, U>(pub [(E, &'a dyn Fn(T) -> U); N]); +pub struct EventMap<'a, const N: usize, E, T, U>( + pub [(E, &'a dyn Fn(T) -> U); N], + pub Option<&'a dyn Fn(T) -> U>, +); impl<'a, const N: usize, E: PartialEq, T, U> EventMap<'a, N, E, T, U> { pub fn handle (&self, context: T, event: &E) -> Option { @@ -74,11 +77,11 @@ impl<'a, const N: usize, E: PartialEq, T, U> EventMap<'a, N, E, T, U> { #[macro_export] macro_rules! event_map { ($events:expr) => { - EventMap($events) + EventMap($events, None) + }; + ($events:expr, $default: expr) => { + EventMap($events, $default) }; - (|$state:ident|{$([$char:expr] = [$handler:expr]),*}) => { - EventMap([$((&$char, &|$state|$handler),),*]) - } } #[macro_export] macro_rules! kexp { From 1e54d81e5d6ed0dad4c59dc3cae92a19f2a87a3c Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 25 Dec 2024 05:27:53 +0100 Subject: [PATCH 003/815] enable LTO --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 301685bc..72ee139a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,6 @@ members = [ #"crates/tek_cli", #"crates/tek_layout" ] + +[profile.release] +lto = true From 084af3ef01f0ad83f51c18588d97657c5269e6e8 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 25 Dec 2024 05:39:21 +0100 Subject: [PATCH 004/815] smartass macro --- crates/tek/src/tui/phrase_editor.rs | 16 +++------------- crates/tek/src/tui/tui_input.rs | 8 ++++++++ 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/crates/tek/src/tui/phrase_editor.rs b/crates/tek/src/tui/phrase_editor.rs index 2667236d..01492997 100644 --- a/crates/tek/src/tui/phrase_editor.rs +++ b/crates/tek/src/tui/phrase_editor.rs @@ -27,19 +27,10 @@ pub enum PhraseCommand { SetTimeZoom(usize), SetTimeLock(bool), Show(Option>>), - ToggleDirection, -} - -impl InputToCommand for PhraseCommand { - fn input_to_command (state: &PhraseEditorModel, from: &TuiInput) -> Option { - return state.to_editor_command(from) - } } +event_map_input_to_command!(Tui: PhraseEditorModel: PhraseCommand: PhraseEditorModel::KEYS); impl PhraseEditorModel { - fn phrase_length (&self) -> usize { - self.phrase().as_ref().map(|p|p.read().unwrap().length).unwrap_or(1) - } const KEYS: [(TuiEvent, &'static dyn Fn(&Self)->PhraseCommand);31] = [ (kexp!(Ctrl-Alt-Up), &|s: &Self|SetNoteScroll(s.note_point() + 3)), (kexp!(Ctrl-Alt-Down), &|s: &Self|SetNoteScroll(s.note_point().saturating_sub(3))), @@ -72,12 +63,11 @@ impl PhraseEditorModel { (kexp!(Char('.')), &|s: &Self|SetNoteLength(Note::next(s.note_len()))), (kexp!(Char('<')), &|s: &Self|SetNoteLength(Note::prev(s.note_len()))), // TODO: 3plet (kexp!(Char('>')), &|s: &Self|SetNoteLength(Note::next(s.note_len()))), - //key_pat!(Char('`')) -> ToggleDirection, //// TODO: key_pat!(Char('/')) => // toggle 3plet //// TODO: key_pat!(Char('?')) => // toggle dotted ]; - fn to_editor_command (&self, from: &TuiInput) -> Option { - event_map!(Self::KEYS).handle(self, from.event()) + fn phrase_length (&self) -> usize { + self.phrase().as_ref().map(|p|p.read().unwrap().length).unwrap_or(1) } } diff --git a/crates/tek/src/tui/tui_input.rs b/crates/tek/src/tui/tui_input.rs index 5cce5e33..f7d24a10 100644 --- a/crates/tek/src/tui/tui_input.rs +++ b/crates/tek/src/tui/tui_input.rs @@ -84,6 +84,14 @@ impl<'a, const N: usize, E: PartialEq, T, U> EventMap<'a, N, E, T, U> { }; } +#[macro_export] macro_rules! event_map_input_to_command { + ($Engine:ty: $Model:ty: $Command:ty: $EventMap:expr) => { + input_to_command!($Command: <$Engine>|state: $Model, input|{ + event_map!($EventMap).handle(state, input.event())? + }); + } +} + #[macro_export] macro_rules! kexp { (Ctrl-Alt-$code:ident) => { key_event_expr!($code, KeyModifiers::from_bits(0b0000_0110).unwrap()) }; (Ctrl-$code:ident) => { key_event_expr!($code, KeyModifiers::CONTROL) }; From 4ab9463164d69e47d241652c82235b43c83a2510 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 25 Dec 2024 05:58:45 +0100 Subject: [PATCH 005/815] PhrasesCommand -> PoolCommand --- crates/tek/src/tui/app_sequencer.rs | 12 ++++++------ crates/tek/src/tui/arranger_command.rs | 16 ++++++++-------- crates/tek/src/tui/pool.rs | 12 ++++++------ 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/crates/tek/src/tui/app_sequencer.rs b/crates/tek/src/tui/app_sequencer.rs index 66307476..fbf5dd44 100644 --- a/crates/tek/src/tui/app_sequencer.rs +++ b/crates/tek/src/tui/app_sequencer.rs @@ -83,7 +83,7 @@ handle!(|self:SequencerTui,input|SequencerCommand::execute_with_state(self, #[derive(Clone, Debug)] pub enum SequencerCommand { History(isize), Clock(ClockCommand), - Phrases(PhrasesCommand), + Phrases(PoolCommand), Editor(PhraseCommand), Enqueue(Option>>), } @@ -103,7 +103,7 @@ input_to_command!(SequencerCommand: |state: SequencerTui, input|match input // Shift-U: redo key_pat!(Char('U')) => History( 1), // Tab: Toggle visibility of phrase pool column - key_pat!(Tab) => Phrases(PhrasesCommand::Show(!state.phrases.visible)), + key_pat!(Tab) => Phrases(PoolCommand::Show(!state.phrases.visible)), // q: Enqueue currently edited phrase key_pat!(Char('q')) => Enqueue(Some(state.phrases.phrase().clone())), // 0: Enqueue phrase 0 (stop all) @@ -124,7 +124,7 @@ input_to_command!(SequencerCommand: |state: SequencerTui, input|match input // The ones defined above supersede them. _ => if let Some(command) = PhraseCommand::input_to_command(&state.editor, input) { Editor(command) - } else if let Some(command) = PhrasesCommand::input_to_command(&state.phrases, input) { + } else if let Some(command) = PoolCommand::input_to_command(&state.phrases, input) { Phrases(command) } else { return None @@ -132,18 +132,18 @@ input_to_command!(SequencerCommand: |state: SequencerTui, input|match input }); command!(|self: SequencerCommand, state: SequencerTui|match self { Self::Phrases(cmd) => { - let mut default = |cmd: PhrasesCommand|cmd + let mut default = |cmd: PoolCommand|cmd .execute(&mut state.phrases) .map(|x|x.map(Phrases)); match cmd { // autoselect: automatically load selected phrase in editor - PhrasesCommand::Select(_) => { + PoolCommand::Select(_) => { let undo = default(cmd)?; state.editor.set_phrase(Some(state.phrases.phrase())); undo }, // update color in all places simultaneously - PhrasesCommand::Phrase(SetColor(index, _)) => { + PoolCommand::Phrase(SetColor(index, _)) => { let undo = default(cmd)?; state.editor.set_phrase(Some(state.phrases.phrase())); undo diff --git a/crates/tek/src/tui/arranger_command.rs b/crates/tek/src/tui/arranger_command.rs index 8c95e457..59370065 100644 --- a/crates/tek/src/tui/arranger_command.rs +++ b/crates/tek/src/tui/arranger_command.rs @@ -11,7 +11,7 @@ use KeyCode::{Char, Delete, Tab, Up, Down, Left, Right}; Clip(ArrangerClipCommand), Select(ArrangerSelection), Zoom(usize), - Phrases(PhrasesCommand), + Phrases(PoolCommand), Editor(PhraseCommand), StopAll, Clear, @@ -65,7 +65,7 @@ input_to_command!(ArrangerCommand: |state: ArrangerTui, input|match input.e Self::Track(ArrangerTrackCommand::Add), // Tab: Toggle visibility of phrase pool column key_pat!(Tab) => - Self::Phrases(PhrasesCommand::Show(!state.phrases.visible)), + Self::Phrases(PoolCommand::Show(!state.phrases.visible)), _ => { use ArrangerCommand as Cmd; use ArrangerSelection as Selected; @@ -76,7 +76,7 @@ input_to_command!(ArrangerCommand: |state: ArrangerTui, input|match input.e let s_len = state.scenes.len(); match state.selected() { Selected::Clip(t, s) => match input.event() { - key_pat!(Char('g')) => Some(Cmd::Phrases(PhrasesCommand::Select(0))), + key_pat!(Char('g')) => Some(Cmd::Phrases(PoolCommand::Select(0))), key_pat!(Char('q')) => Some(Cmd::Clip(Clip::Enqueue(t, s))), key_pat!(Char(',')) => Some(Cmd::Clip(Clip::Put(t, s, None))), key_pat!(Char('.')) => Some(Cmd::Clip(Clip::Put(t, s, None))), @@ -155,7 +155,7 @@ input_to_command!(ArrangerCommand: |state: ArrangerTui, input|match input.e } }.or_else(||if let Some(command) = PhraseCommand::input_to_command(&state.editor, input) { Some(Self::Editor(command)) - } else if let Some(command) = PhrasesCommand::input_to_command(&state.phrases, input) { + } else if let Some(command) = PoolCommand::input_to_command(&state.phrases, input) { Some(Self::Phrases(command)) } else { None @@ -171,7 +171,7 @@ fn to_arrangement_command (state: &ArrangerTui, input: &TuiInput) -> Option match input.event() { - key_pat!(Char('g')) => Some(Cmd::Phrases(PhrasesCommand::Select(0))), + key_pat!(Char('g')) => Some(Cmd::Phrases(PoolCommand::Select(0))), key_pat!(Char('q')) => Some(Cmd::Clip(Clip::Enqueue(t, s))), key_pat!(Char(',')) => Some(Cmd::Clip(Clip::Put(t, s, None))), key_pat!(Char('.')) => Some(Cmd::Clip(Clip::Put(t, s, None))), @@ -266,18 +266,18 @@ command!(|self: ArrangerCommand, state: ArrangerTui|match self { Some(Self::Color(old)) }, Self::Phrases(cmd) => { - let mut default = |cmd: PhrasesCommand|{ + let mut default = |cmd: PoolCommand|{ cmd.execute(&mut state.phrases).map(|x|x.map(Self::Phrases)) }; match cmd { // autoselect: automatically load selected phrase in editor - PhrasesCommand::Select(_) => { + PoolCommand::Select(_) => { let undo = default(cmd)?; state.editor.set_phrase(Some(state.phrases.phrase())); undo }, // reload phrase in editor to update color - PhrasesCommand::Phrase(PhrasePoolCommand::SetColor(index, _)) => { + PoolCommand::Phrase(PhrasePoolCommand::SetColor(index, _)) => { let undo = default(cmd)?; state.editor.set_phrase(Some(state.phrases.phrase())); undo diff --git a/crates/tek/src/tui/pool.rs b/crates/tek/src/tui/pool.rs index 53901fea..61d506cc 100644 --- a/crates/tek/src/tui/pool.rs +++ b/crates/tek/src/tui/pool.rs @@ -35,7 +35,7 @@ pub enum PoolMode { } #[derive(Clone, PartialEq, Debug)] -pub enum PhrasesCommand { +pub enum PoolCommand { Show(bool), /// Update the contents of the phrase pool Phrase(Pool), @@ -51,8 +51,8 @@ pub enum PhrasesCommand { Export(Browse), } -command!(|self:PhrasesCommand, state: PoolModel|{ - use PhrasesCommand::*; +command!(|self:PoolCommand, state: PoolModel|{ + use PoolCommand::*; match self { Show(visible) => { state.visible = visible; @@ -104,7 +104,7 @@ command!(|self:PhrasesCommand, state: PoolModel|{ } }); -input_to_command!(PhrasesCommand:|state: PoolModel,input|match state.phrases_mode() { +input_to_command!(PoolCommand:|state: PoolModel,input|match state.phrases_mode() { Some(PoolMode::Rename(..)) => Self::Rename(Rename::input_to_command(state, input)?), Some(PoolMode::Length(..)) => Self::Length(Length::input_to_command(state, input)?), Some(PoolMode::Import(..)) => Self::Import(Browse::input_to_command(state, input)?), @@ -112,9 +112,9 @@ input_to_command!(PhrasesCommand:|state: PoolModel,input|match state.phrase _ => to_phrases_command(state, input)? }); -fn to_phrases_command (state: &PoolModel, input: &TuiInput) -> Option { +fn to_phrases_command (state: &PoolModel, input: &TuiInput) -> Option { use KeyCode::{Up, Down, Delete, Char}; - use PhrasesCommand as Cmd; + use PoolCommand as Cmd; let index = state.phrase_index(); let count = state.phrases().len(); Some(match input.event() { From eb5f45142312af085cbd870698ec8ca7ad93fe91 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 25 Dec 2024 06:02:26 +0100 Subject: [PATCH 006/815] move phrase length/rename modes to tui/pool/ --- crates/tek/src/tui.rs | 15 +++++++++------ crates/tek/src/tui/pool.rs | 14 ++++++++------ crates/tek/src/tui/{ => pool}/phrase_length.rs | 10 +++++++++- crates/tek/src/tui/{ => pool}/phrase_rename.rs | 2 +- 4 files changed, 27 insertions(+), 14 deletions(-) rename crates/tek/src/tui/{ => pool}/phrase_length.rs (99%) rename crates/tek/src/tui/{ => pool}/phrase_rename.rs (99%) diff --git a/crates/tek/src/tui.rs b/crates/tek/src/tui.rs index 7d108d84..b4ee76d8 100644 --- a/crates/tek/src/tui.rs +++ b/crates/tek/src/tui.rs @@ -35,13 +35,16 @@ mod arranger_mode_v; pub(crate) use arranger_mode_v::*; //////////////////////////////////////////////////////// -mod status_bar; pub(crate) use status_bar::*; -mod file_browser; pub(crate) use file_browser::*; -mod phrase_editor; pub(crate) use phrase_editor::*; -mod piano_horizontal; pub(crate) use piano_horizontal::*; -mod phrase_length; pub(crate) use phrase_length::*; -mod phrase_rename; pub(crate) use phrase_rename::*; mod pool; pub(crate) use pool::*; + +mod phrase_editor; pub(crate) use phrase_editor::*; + +mod status_bar; pub(crate) use status_bar::*; + +mod file_browser; pub(crate) use file_browser::*; + +mod piano_horizontal; pub(crate) use piano_horizontal::*; + mod port_select; //////////////////////////////////////////////////////// diff --git a/crates/tek/src/tui/pool.rs b/crates/tek/src/tui/pool.rs index 61d506cc..91a87d5f 100644 --- a/crates/tek/src/tui/pool.rs +++ b/crates/tek/src/tui/pool.rs @@ -1,10 +1,12 @@ use super::*; -use crate::{ - PhrasePoolCommand as Pool, - tui::phrase_rename::PhraseRenameCommand as Rename, - tui::phrase_length::PhraseLengthCommand as Length, - tui::file_browser::FileBrowserCommand as Browse, -}; +use crate::PhrasePoolCommand as Pool; + +mod phrase_length; pub(crate) use phrase_length::*; +mod phrase_rename; pub(crate) use phrase_rename::*; + +use PhraseRenameCommand as Rename; +use PhraseLengthCommand as Length; +use FileBrowserCommand as Browse; #[derive(Debug)] pub struct PoolModel { diff --git a/crates/tek/src/tui/phrase_length.rs b/crates/tek/src/tui/pool/phrase_length.rs similarity index 99% rename from crates/tek/src/tui/phrase_length.rs rename to crates/tek/src/tui/pool/phrase_length.rs index 08ced486..32b642f3 100644 --- a/crates/tek/src/tui/phrase_length.rs +++ b/crates/tek/src/tui/pool/phrase_length.rs @@ -1,7 +1,8 @@ use crate::*; -use super::pool::{PoolModel, PoolMode}; +use super::*; use PhraseLengthFocus::*; use PhraseLengthCommand::*; + /// Displays and edits phrase length. #[derive(Clone)] pub struct PhraseLength { @@ -14,6 +15,7 @@ pub struct PhraseLength { /// Selected subdivision pub focus: Option, } + impl PhraseLength { pub fn new (pulses: usize, focus: Option) -> Self { Self { ppq: PPQ, bpb: 4, pulses, focus } @@ -37,6 +39,7 @@ impl PhraseLength { format!("{:>02}", self.ticks()) } } + /// Focused field of `PhraseLength` #[derive(Copy, Clone, Debug)] pub enum PhraseLengthFocus { @@ -47,6 +50,7 @@ pub enum PhraseLengthFocus { /// Editing the number of ticks Tick, } + impl PhraseLengthFocus { pub fn next (&mut self) { *self = match self { @@ -63,6 +67,7 @@ impl PhraseLengthFocus { } } } + render!(|self: PhraseLength|{ let bars = ||self.bars_string(); let beats = ||self.beats_string(); @@ -78,6 +83,7 @@ render!(|self: PhraseLength|{ add(&row!([" ", bars(), ".", beats(), "[", ticks()])), }) }); + #[derive(Copy, Clone, Debug, PartialEq)] pub enum PhraseLengthCommand { Begin, @@ -88,6 +94,7 @@ pub enum PhraseLengthCommand { Inc, Dec, } + command!(|self:PhraseLengthCommand,state:PoolModel|{ match state.phrases_mode_mut().clone() { Some(PoolMode::Length(phrase, ref mut length, ref mut focus)) => match self { @@ -118,6 +125,7 @@ command!(|self:PhraseLengthCommand,state:PoolModel|{ }; None }); + input_to_command!(PhraseLengthCommand:|state:PoolModel,from|{ if let Some(PoolMode::Length(_, length, _)) = state.phrases_mode() { match from.event() { diff --git a/crates/tek/src/tui/phrase_rename.rs b/crates/tek/src/tui/pool/phrase_rename.rs similarity index 99% rename from crates/tek/src/tui/phrase_rename.rs rename to crates/tek/src/tui/pool/phrase_rename.rs index 1b792f5b..67378f00 100644 --- a/crates/tek/src/tui/phrase_rename.rs +++ b/crates/tek/src/tui/pool/phrase_rename.rs @@ -1,4 +1,5 @@ use crate::*; +use super::*; #[derive(Clone, Debug, PartialEq)] pub enum PhraseRenameCommand { @@ -57,4 +58,3 @@ impl InputToCommand for PhraseRenameCommand { } } } - From a43b7048ac57e9cc0295a204d34e31a11552bf72 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 25 Dec 2024 06:15:51 +0100 Subject: [PATCH 007/815] break down status bar, piano, arranger --- crates/tek/src/tui.rs | 55 +++---- crates/tek/src/tui/app_arranger.rs | 28 ---- crates/tek/src/tui/arranger_h.rs | 1 + crates/tek/src/tui/arranger_mode.rs | 29 ++++ crates/tek/src/tui/arranger_mode_h.rs | 0 .../tui/{arranger_mode_v.rs => arranger_v.rs} | 0 .../v_clips.rs | 0 .../v_cursor.rs | 0 .../{arranger_mode_v => arranger_v}/v_head.rs | 0 .../{arranger_mode_v => arranger_v}/v_io.rs | 0 .../{arranger_mode_v => arranger_v}/v_sep.rs | 0 .../tui/{piano_horizontal.rs => piano_h.rs} | 152 +----------------- crates/tek/src/tui/piano_h/piano_h_cursor.rs | 33 ++++ crates/tek/src/tui/piano_h/piano_h_keys.rs | 53 ++++++ crates/tek/src/tui/piano_h/piano_h_notes.rs | 46 ++++++ crates/tek/src/tui/piano_h/piano_h_time.rs | 21 +++ crates/tek/src/tui/piano_v.rs | 1 + crates/tek/src/tui/port_select.rs | 7 - crates/tek/src/tui/status.rs | 3 + crates/tek/src/tui/status/status_arranger.rs | 55 +++++++ crates/tek/src/tui/status/status_edit.rs | 31 ++++ crates/tek/src/tui/status/status_sequencer.rs | 53 ++++++ crates/tek/src/tui/status_bar.rs | 134 --------------- 23 files changed, 355 insertions(+), 347 deletions(-) create mode 100644 crates/tek/src/tui/arranger_h.rs create mode 100644 crates/tek/src/tui/arranger_mode.rs delete mode 100644 crates/tek/src/tui/arranger_mode_h.rs rename crates/tek/src/tui/{arranger_mode_v.rs => arranger_v.rs} (100%) rename crates/tek/src/tui/{arranger_mode_v => arranger_v}/v_clips.rs (100%) rename crates/tek/src/tui/{arranger_mode_v => arranger_v}/v_cursor.rs (100%) rename crates/tek/src/tui/{arranger_mode_v => arranger_v}/v_head.rs (100%) rename crates/tek/src/tui/{arranger_mode_v => arranger_v}/v_io.rs (100%) rename crates/tek/src/tui/{arranger_mode_v => arranger_v}/v_sep.rs (100%) rename crates/tek/src/tui/{piano_horizontal.rs => piano_h.rs} (55%) create mode 100644 crates/tek/src/tui/piano_h/piano_h_cursor.rs create mode 100644 crates/tek/src/tui/piano_h/piano_h_keys.rs create mode 100644 crates/tek/src/tui/piano_h/piano_h_notes.rs create mode 100644 crates/tek/src/tui/piano_h/piano_h_time.rs create mode 100644 crates/tek/src/tui/piano_v.rs delete mode 100644 crates/tek/src/tui/port_select.rs create mode 100644 crates/tek/src/tui/status.rs create mode 100644 crates/tek/src/tui/status/status_arranger.rs create mode 100644 crates/tek/src/tui/status/status_edit.rs create mode 100644 crates/tek/src/tui/status/status_sequencer.rs delete mode 100644 crates/tek/src/tui/status_bar.rs diff --git a/crates/tek/src/tui.rs b/crates/tek/src/tui.rs index b4ee76d8..e47674e6 100644 --- a/crates/tek/src/tui.rs +++ b/crates/tek/src/tui.rs @@ -1,51 +1,42 @@ use crate::*; -mod tui_input; -pub(crate) use tui_input::*; -pub use tui_input::TuiInput; +mod tui_input; pub(crate) use self::tui_input::*; +mod tui_output; pub(crate) use self::tui_output::*; -mod tui_output; -pub(crate) use tui_output::*; -pub use tui_output::TuiOutput; +pub use self::tui_input::TuiInput; +pub use self::tui_output::TuiOutput; //////////////////////////////////////////////////////// mod tui_style; -mod tui_theme; -pub(crate) use tui_theme::*; -mod tui_border; -pub(crate) use tui_border::*; +mod tui_theme; pub(crate) use self::tui_theme::*; +mod tui_border; pub(crate) use self::tui_border::*; //////////////////////////////////////////////////////// -mod app_transport; pub(crate) use app_transport::*; -mod app_sequencer; pub(crate) use app_sequencer::*; -mod app_sampler; pub(crate) use app_sampler::*; -mod app_groovebox; pub(crate) use app_groovebox::*; -mod app_arranger; pub(crate) use app_arranger::*; +mod app_transport; pub(crate) use self::app_transport::*; +mod app_sequencer; pub(crate) use self::app_sequencer::*; +mod app_sampler; pub(crate) use self::app_sampler::*; +mod app_groovebox; pub(crate) use self::app_groovebox::*; +mod app_arranger; pub(crate) use self::app_arranger::*; /////////////////////////////////////////////////////// -mod arranger_command; pub(crate) use arranger_command::*; -mod arranger_scene; pub(crate) use arranger_scene::*; -mod arranger_select; pub(crate) use arranger_select::*; -mod arranger_track; pub(crate) use arranger_track::*; -mod arranger_mode_h; -mod arranger_mode_v; pub(crate) use arranger_mode_v::*; +mod arranger_command; pub(crate) use self::arranger_command::*; +mod arranger_scene; pub(crate) use self::arranger_scene::*; +mod arranger_select; pub(crate) use self::arranger_select::*; +mod arranger_track; pub(crate) use self::arranger_track::*; +mod arranger_mode; pub(crate) use self::arranger_mode::*; +mod arranger_v; pub(crate) use self::arranger_v::*; +mod arranger_h; //////////////////////////////////////////////////////// -mod pool; pub(crate) use pool::*; - -mod phrase_editor; pub(crate) use phrase_editor::*; - -mod status_bar; pub(crate) use status_bar::*; - -mod file_browser; pub(crate) use file_browser::*; - -mod piano_horizontal; pub(crate) use piano_horizontal::*; - -mod port_select; +mod pool; pub(crate) use self::pool::*; +mod phrase_editor; pub(crate) use self::phrase_editor::*; +mod status; pub(crate) use self::status::*; +mod file_browser; pub(crate) use self::file_browser::*; +mod piano_h; pub(crate) use self::piano_h::*; //////////////////////////////////////////////////////// diff --git a/crates/tek/src/tui/app_arranger.rs b/crates/tek/src/tui/app_arranger.rs index 6a554b17..fe3f91bd 100644 --- a/crates/tek/src/tui/app_arranger.rs +++ b/crates/tek/src/tui/app_arranger.rs @@ -151,31 +151,3 @@ has_clock!(|self: ArrangerTui|&self.clock); has_phrases!(|self: ArrangerTui|self.phrases.phrases); has_editor!(|self: ArrangerTui|self.editor); handle!(|self: ArrangerTui, input|ArrangerCommand::execute_with_state(self, input)); - -/// Display mode of arranger -#[derive(Clone, PartialEq)] -pub enum ArrangerMode { - /// Tracks are columns - V(usize), - /// Tracks are rows - H, -} -render!(|self: ArrangerMode|{}); - -/// Arranger display mode can be cycled -impl ArrangerMode { - /// Cycle arranger display mode - pub fn to_next (&mut self) { - *self = match self { - Self::H => Self::V(1), - Self::V(1) => Self::V(2), - Self::V(2) => Self::V(2), - Self::V(0) => Self::H, - Self::V(_) => Self::V(0), - } - } -} - -fn any_size (_: E::Size) -> Perhaps{ - Ok(Some([0.into(),0.into()].into())) -} diff --git a/crates/tek/src/tui/arranger_h.rs b/crates/tek/src/tui/arranger_h.rs new file mode 100644 index 00000000..70b786d1 --- /dev/null +++ b/crates/tek/src/tui/arranger_h.rs @@ -0,0 +1 @@ +// TODO diff --git a/crates/tek/src/tui/arranger_mode.rs b/crates/tek/src/tui/arranger_mode.rs new file mode 100644 index 00000000..c28d8b7f --- /dev/null +++ b/crates/tek/src/tui/arranger_mode.rs @@ -0,0 +1,29 @@ +use crate::*; + +/// Display mode of arranger +#[derive(Clone, PartialEq)] +pub enum ArrangerMode { + /// Tracks are columns + V(usize), + /// Tracks are rows + H, +} +render!(|self: ArrangerMode|{}); + +/// Arranger display mode can be cycled +impl ArrangerMode { + /// Cycle arranger display mode + pub fn to_next (&mut self) { + *self = match self { + Self::H => Self::V(1), + Self::V(1) => Self::V(2), + Self::V(2) => Self::V(2), + Self::V(0) => Self::H, + Self::V(_) => Self::V(0), + } + } +} + +fn any_size (_: E::Size) -> Perhaps{ + Ok(Some([0.into(),0.into()].into())) +} diff --git a/crates/tek/src/tui/arranger_mode_h.rs b/crates/tek/src/tui/arranger_mode_h.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/crates/tek/src/tui/arranger_mode_v.rs b/crates/tek/src/tui/arranger_v.rs similarity index 100% rename from crates/tek/src/tui/arranger_mode_v.rs rename to crates/tek/src/tui/arranger_v.rs diff --git a/crates/tek/src/tui/arranger_mode_v/v_clips.rs b/crates/tek/src/tui/arranger_v/v_clips.rs similarity index 100% rename from crates/tek/src/tui/arranger_mode_v/v_clips.rs rename to crates/tek/src/tui/arranger_v/v_clips.rs diff --git a/crates/tek/src/tui/arranger_mode_v/v_cursor.rs b/crates/tek/src/tui/arranger_v/v_cursor.rs similarity index 100% rename from crates/tek/src/tui/arranger_mode_v/v_cursor.rs rename to crates/tek/src/tui/arranger_v/v_cursor.rs diff --git a/crates/tek/src/tui/arranger_mode_v/v_head.rs b/crates/tek/src/tui/arranger_v/v_head.rs similarity index 100% rename from crates/tek/src/tui/arranger_mode_v/v_head.rs rename to crates/tek/src/tui/arranger_v/v_head.rs diff --git a/crates/tek/src/tui/arranger_mode_v/v_io.rs b/crates/tek/src/tui/arranger_v/v_io.rs similarity index 100% rename from crates/tek/src/tui/arranger_mode_v/v_io.rs rename to crates/tek/src/tui/arranger_v/v_io.rs diff --git a/crates/tek/src/tui/arranger_mode_v/v_sep.rs b/crates/tek/src/tui/arranger_v/v_sep.rs similarity index 100% rename from crates/tek/src/tui/arranger_mode_v/v_sep.rs rename to crates/tek/src/tui/arranger_v/v_sep.rs diff --git a/crates/tek/src/tui/piano_horizontal.rs b/crates/tek/src/tui/piano_h.rs similarity index 55% rename from crates/tek/src/tui/piano_horizontal.rs rename to crates/tek/src/tui/piano_h.rs index 045551b3..9889fbf8 100644 --- a/crates/tek/src/tui/piano_horizontal.rs +++ b/crates/tek/src/tui/piano_h.rs @@ -1,26 +1,13 @@ use crate::*; use super::*; -fn note_y_iter (note_lo: usize, note_hi: usize, y0: u16) -> impl Iterator { - (note_lo..=note_hi).rev().enumerate().map(move|(y, n)|(y, y0 + y as u16, n)) -} +mod piano_h_cursor; use self::piano_h_cursor::*; +mod piano_h_keys; use self::piano_h_keys::*; +mod piano_h_notes; use self::piano_h_notes::*; +mod piano_h_time; use self::piano_h_time::*; -fn to_key (note: usize) -> &'static str { - match note % 12 { - 11 => "████▌", - 10 => " ", - 9 => "████▌", - 8 => " ", - 7 => "████▌", - 6 => " ", - 5 => "████▌", - 4 => "████▌", - 3 => " ", - 2 => "████▌", - 1 => " ", - 0 => "████▌", - _ => unreachable!(), - } +pub(crate) fn note_y_iter (note_lo: usize, note_hi: usize, y0: u16) -> impl Iterator { + (note_lo..=note_hi).rev().enumerate().map(move|(y, n)|(y, y0 + y as u16, n)) } /// A phrase, rendered as a horizontal piano roll. @@ -76,133 +63,6 @@ render!(|self: PianoHorizontal|{ ))) }); -pub struct PianoHorizontalTimeline<'a>(&'a PianoHorizontal); -render!(|self: PianoHorizontalTimeline<'a>|render(|to|{ - let [x, y, w, h] = to.area(); - let style = Some(Style::default().dim()); - let length = self.0.phrase.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1); - for (area_x, screen_x) in (0..w).map(|d|(d, d+x)) { - let t = area_x as usize * self.0.time_zoom().get(); - if t < length { - to.blit(&"|", screen_x, y, style); - } - } - Ok(()) -})); - //Tui::fg_bg( - //self.0.color.lightest.rgb, - //self.0.color.darkest.rgb, - //format!("{}*{}", self.0.time_start(), self.0.time_zoom()).as_str() -//)); - -pub struct PianoHorizontalKeys<'a>(&'a PianoHorizontal); -render!(|self: PianoHorizontalKeys<'a>|render(|to|Ok({ - let color = self.0.color; - let note_lo = self.0.note_lo().get(); - let note_hi = self.0.note_hi(); - let note_point = self.0.note_point(); - let [x, y0, w, h] = to.area().xywh(); - let key_style = Some(Style::default().fg(Color::Rgb(192, 192, 192)).bg(Color::Rgb(0, 0, 0))); - let off_style = Some(Style::default().fg(TuiTheme::g(160))); - let on_style = Some(Style::default().fg(TuiTheme::g(255)).bg(color.light.rgb).bold()); - for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { - to.blit(&to_key(note), x, screen_y, key_style); - if note > 127 { - continue - } - if note == note_point { - to.blit(&format!("{:<5}", to_note_name(note)), x, screen_y, on_style) - } else { - to.blit(&to_note_name(note), x, screen_y, off_style) - }; - } - //let debug = false; - //if debug { - //to.blit(&format!("XYU"), x, y0, None); - //to.blit(&format!("x={x}"), x, y0+1, None); - //to.blit(&format!("y0={y0}"), x, y0+2, None); - //to.blit(&format!("w={w}"), x, y0+3, None); - //to.blit(&format!("h={h}"), x, y0+4, None); - //to.blit(&format!("note_lo={note_lo}"), x, y0+5, None); - //to.blit(&format!("note_hi={note_hi}"), x, y0+6, None); - //} -}))); - -pub struct PianoHorizontalCursor<'a>(&'a PianoHorizontal); -render!(|self: PianoHorizontalCursor<'a>|render(|to|Ok({ - let style = Some(Style::default().fg(self.0.color.lightest.rgb)); - let note_hi = self.0.note_hi(); - let note_len = self.0.note_len(); - let note_lo = self.0.note_lo().get(); - let note_point = self.0.note_point(); - let time_point = self.0.time_point(); - let time_start = self.0.time_start().get(); - let time_zoom = self.0.time_zoom().get(); - let [x0, y0, w, _] = to.area().xywh(); - for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { - if note == note_point { - for x in 0..w { - let screen_x = x0 + x as u16; - let time_1 = time_start + x as usize * time_zoom; - let time_2 = time_1 + time_zoom; - if time_1 <= time_point && time_point < time_2 { - to.blit(&"█", screen_x, screen_y, style); - let tail = note_len as u16 / time_zoom as u16; - for x_tail in (screen_x + 1)..(screen_x + tail) { - to.blit(&"▂", x_tail, screen_y, style); - } - break - } - } - break - } - } -}))); - -pub struct PianoHorizontalNotes<'a>(&'a PianoHorizontal); -render!(|self: PianoHorizontalNotes<'a>|render(|to|Ok({ - let time_start = self.0.time_start().get(); - let note_lo = self.0.note_lo().get(); - let note_hi = self.0.note_hi(); - let note_point = self.0.note_point(); - let source = &self.0.buffer; - let [x0, y0, w, h] = to.area().xywh(); - if h as usize != self.0.note_axis().get() { - panic!("area height mismatch"); - } - for (area_x, screen_x) in (x0..x0+w).enumerate() { - for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { - //if area_x % 10 == 0 { - //to.blit(&format!("{area_y} {note}"), screen_x, screen_y, None); - //} - let source_x = time_start + area_x; - let source_y = note_hi - area_y; - ////// TODO: enable loop rollover: - //////let source_x = (time_start + area_x) % source.width.max(1); - //////let source_y = (note_hi - area_y) % source.height.max(1); - let is_in_x = source_x < source.width; - let is_in_y = source_y < source.height; - if is_in_x && is_in_y { - if let Some(source_cell) = source.get(source_x, source_y) { - *to.buffer.get_mut(screen_x, screen_y) = source_cell.clone(); - } - } - } - } - //let debug = true; - //if debug { - //let x0=20+x0; - //to.blit(&format!("KYP "), x0, y0, None); - //to.blit(&format!("x0={x0} "), x0, y0+1, None); - //to.blit(&format!("y0={y0} "), x0, y0+2, None); - //to.blit(&format!("note_lo={note_lo}, {} ", to_note_name(note_lo)), x0, y0+3, None); - //to.blit(&format!("note_hi={note_hi}, {} ", to_note_name(note_hi)), x0, y0+4, None); - //to.blit(&format!("note_point={note_point}, {} ", to_note_name(note_point)), x0, y0+5, None); - //to.blit(&format!("time_start={time_start} "), x0, y0+6, None); - //return Ok(()); - //} -}))); - impl PianoHorizontal { /// Draw the piano roll foreground using full blocks on note on and half blocks on legato: █▄ █▄ █▄ fn draw_bg (buf: &mut BigBuffer, phrase: &Phrase, zoom: usize, note_len: usize) { diff --git a/crates/tek/src/tui/piano_h/piano_h_cursor.rs b/crates/tek/src/tui/piano_h/piano_h_cursor.rs new file mode 100644 index 00000000..2c5dd6b5 --- /dev/null +++ b/crates/tek/src/tui/piano_h/piano_h_cursor.rs @@ -0,0 +1,33 @@ +use crate::*; +use super::note_y_iter; + +pub struct PianoHorizontalCursor<'a>(pub(crate) &'a PianoHorizontal); +render!(|self: PianoHorizontalCursor<'a>|render(|to|Ok({ + let style = Some(Style::default().fg(self.0.color.lightest.rgb)); + let note_hi = self.0.note_hi(); + let note_len = self.0.note_len(); + let note_lo = self.0.note_lo().get(); + let note_point = self.0.note_point(); + let time_point = self.0.time_point(); + let time_start = self.0.time_start().get(); + let time_zoom = self.0.time_zoom().get(); + let [x0, y0, w, _] = to.area().xywh(); + for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { + if note == note_point { + for x in 0..w { + let screen_x = x0 + x as u16; + let time_1 = time_start + x as usize * time_zoom; + let time_2 = time_1 + time_zoom; + if time_1 <= time_point && time_point < time_2 { + to.blit(&"█", screen_x, screen_y, style); + let tail = note_len as u16 / time_zoom as u16; + for x_tail in (screen_x + 1)..(screen_x + tail) { + to.blit(&"▂", x_tail, screen_y, style); + } + break + } + } + break + } + } +}))); diff --git a/crates/tek/src/tui/piano_h/piano_h_keys.rs b/crates/tek/src/tui/piano_h/piano_h_keys.rs new file mode 100644 index 00000000..91bc29a3 --- /dev/null +++ b/crates/tek/src/tui/piano_h/piano_h_keys.rs @@ -0,0 +1,53 @@ +use crate::*; +use super::note_y_iter; + +pub struct PianoHorizontalKeys<'a>(pub(crate) &'a PianoHorizontal); +render!(|self: PianoHorizontalKeys<'a>|render(|to|Ok({ + let color = self.0.color; + let note_lo = self.0.note_lo().get(); + let note_hi = self.0.note_hi(); + let note_point = self.0.note_point(); + let [x, y0, w, h] = to.area().xywh(); + let key_style = Some(Style::default().fg(Color::Rgb(192, 192, 192)).bg(Color::Rgb(0, 0, 0))); + let off_style = Some(Style::default().fg(TuiTheme::g(160))); + let on_style = Some(Style::default().fg(TuiTheme::g(255)).bg(color.light.rgb).bold()); + for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { + to.blit(&to_key(note), x, screen_y, key_style); + if note > 127 { + continue + } + if note == note_point { + to.blit(&format!("{:<5}", to_note_name(note)), x, screen_y, on_style) + } else { + to.blit(&to_note_name(note), x, screen_y, off_style) + }; + } + //let debug = false; + //if debug { + //to.blit(&format!("XYU"), x, y0, None); + //to.blit(&format!("x={x}"), x, y0+1, None); + //to.blit(&format!("y0={y0}"), x, y0+2, None); + //to.blit(&format!("w={w}"), x, y0+3, None); + //to.blit(&format!("h={h}"), x, y0+4, None); + //to.blit(&format!("note_lo={note_lo}"), x, y0+5, None); + //to.blit(&format!("note_hi={note_hi}"), x, y0+6, None); + //} +}))); + +fn to_key (note: usize) -> &'static str { + match note % 12 { + 11 => "████▌", + 10 => " ", + 9 => "████▌", + 8 => " ", + 7 => "████▌", + 6 => " ", + 5 => "████▌", + 4 => "████▌", + 3 => " ", + 2 => "████▌", + 1 => " ", + 0 => "████▌", + _ => unreachable!(), + } +} diff --git a/crates/tek/src/tui/piano_h/piano_h_notes.rs b/crates/tek/src/tui/piano_h/piano_h_notes.rs new file mode 100644 index 00000000..4d45a07d --- /dev/null +++ b/crates/tek/src/tui/piano_h/piano_h_notes.rs @@ -0,0 +1,46 @@ +use crate::*; +use super::note_y_iter; + +pub struct PianoHorizontalNotes<'a>(pub(crate) &'a PianoHorizontal); +render!(|self: PianoHorizontalNotes<'a>|render(|to|Ok({ + let time_start = self.0.time_start().get(); + let note_lo = self.0.note_lo().get(); + let note_hi = self.0.note_hi(); + let note_point = self.0.note_point(); + let source = &self.0.buffer; + let [x0, y0, w, h] = to.area().xywh(); + if h as usize != self.0.note_axis().get() { + panic!("area height mismatch"); + } + for (area_x, screen_x) in (x0..x0+w).enumerate() { + for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { + //if area_x % 10 == 0 { + //to.blit(&format!("{area_y} {note}"), screen_x, screen_y, None); + //} + let source_x = time_start + area_x; + let source_y = note_hi - area_y; + ////// TODO: enable loop rollover: + //////let source_x = (time_start + area_x) % source.width.max(1); + //////let source_y = (note_hi - area_y) % source.height.max(1); + let is_in_x = source_x < source.width; + let is_in_y = source_y < source.height; + if is_in_x && is_in_y { + if let Some(source_cell) = source.get(source_x, source_y) { + *to.buffer.get_mut(screen_x, screen_y) = source_cell.clone(); + } + } + } + } + //let debug = true; + //if debug { + //let x0=20+x0; + //to.blit(&format!("KYP "), x0, y0, None); + //to.blit(&format!("x0={x0} "), x0, y0+1, None); + //to.blit(&format!("y0={y0} "), x0, y0+2, None); + //to.blit(&format!("note_lo={note_lo}, {} ", to_note_name(note_lo)), x0, y0+3, None); + //to.blit(&format!("note_hi={note_hi}, {} ", to_note_name(note_hi)), x0, y0+4, None); + //to.blit(&format!("note_point={note_point}, {} ", to_note_name(note_point)), x0, y0+5, None); + //to.blit(&format!("time_start={time_start} "), x0, y0+6, None); + //return Ok(()); + //} +}))); diff --git a/crates/tek/src/tui/piano_h/piano_h_time.rs b/crates/tek/src/tui/piano_h/piano_h_time.rs new file mode 100644 index 00000000..6c2d4ec3 --- /dev/null +++ b/crates/tek/src/tui/piano_h/piano_h_time.rs @@ -0,0 +1,21 @@ +use crate::*; +use super::note_y_iter; + +pub struct PianoHorizontalTimeline<'a>(pub(crate) &'a PianoHorizontal); +render!(|self: PianoHorizontalTimeline<'a>|render(|to|{ + let [x, y, w, h] = to.area(); + let style = Some(Style::default().dim()); + let length = self.0.phrase.as_ref().map(|p|p.read().unwrap().length).unwrap_or(1); + for (area_x, screen_x) in (0..w).map(|d|(d, d+x)) { + let t = area_x as usize * self.0.time_zoom().get(); + if t < length { + to.blit(&"|", screen_x, y, style); + } + } + Ok(()) +})); + //Tui::fg_bg( + //self.0.color.lightest.rgb, + //self.0.color.darkest.rgb, + //format!("{}*{}", self.0.time_start(), self.0.time_zoom()).as_str() +//)); diff --git a/crates/tek/src/tui/piano_v.rs b/crates/tek/src/tui/piano_v.rs new file mode 100644 index 00000000..70b786d1 --- /dev/null +++ b/crates/tek/src/tui/piano_v.rs @@ -0,0 +1 @@ +// TODO diff --git a/crates/tek/src/tui/port_select.rs b/crates/tek/src/tui/port_select.rs deleted file mode 100644 index 1850a253..00000000 --- a/crates/tek/src/tui/port_select.rs +++ /dev/null @@ -1,7 +0,0 @@ -use crate::*; - -pub struct PortSelector { - pub(crate) title: &'static str, -} - -render!(|self: PortSelector|{}); diff --git a/crates/tek/src/tui/status.rs b/crates/tek/src/tui/status.rs new file mode 100644 index 00000000..8359e9da --- /dev/null +++ b/crates/tek/src/tui/status.rs @@ -0,0 +1,3 @@ +mod status_arranger; pub(crate) use self::status_arranger::*; +mod status_edit; pub(crate) use self::status_edit::*; +mod status_sequencer; pub(crate) use self::status_sequencer::*; diff --git a/crates/tek/src/tui/status/status_arranger.rs b/crates/tek/src/tui/status/status_arranger.rs new file mode 100644 index 00000000..7005d562 --- /dev/null +++ b/crates/tek/src/tui/status/status_arranger.rs @@ -0,0 +1,55 @@ +use crate::*; +use super::*; + +/// Status bar for arranger app +#[derive(Clone)] +pub struct ArrangerStatus { + pub(crate) width: usize, + pub(crate) cpu: Option, + pub(crate) size: String, + pub(crate) res: String, + pub(crate) playing: bool, +} +from!(|state:&ArrangerTui|ArrangerStatus = { + let samples = state.clock.chunk.load(Relaxed); + let rate = state.clock.timebase.sr.get() as f64; + 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}%")), + size: format!("{}x{}│", width, state.size.h()), + res: format!("│{}s│{:.1}kHz│{:.1}ms│", samples, rate / 1000., buffer * 1000.), + } +}); +render!(|self: ArrangerStatus|Fixed::h(2, lay!([ + Self::help(), + Fill::wh(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))), +]))); +impl ArrangerStatus { + fn help () -> impl Render { + let single = |binding, command|row!([" ", col!([ + Tui::fg(TuiTheme::yellow(), binding), + command + ])]); + let double = |(b1, c1), (b2, c2)|col!([ + row!([" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,]), + row!([" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,]), + ]); + Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!([ + single("SPACE", "play/pause"), + single(" Ctrl", " scroll"), + single(" ▲▼▶◀", " cell"), + double(("p", "put"), ("g", "get")), + double(("q", "enqueue"), ("e", "edit")), + single(" wsad", " note"), + double(("a", "append"), ("s", "set"),), + double((",.", "length"), ("<>", "triplet"),), + double(("[]", "phrase"), ("{}", "order"),), + ])) + } + fn stats <'a> (&'a self) -> impl Render + use<'a> { + row!([&self.cpu, &self.res, &self.size]) + } +} diff --git a/crates/tek/src/tui/status/status_edit.rs b/crates/tek/src/tui/status/status_edit.rs new file mode 100644 index 00000000..149dd2f0 --- /dev/null +++ b/crates/tek/src/tui/status/status_edit.rs @@ -0,0 +1,31 @@ +use crate::*; +use super::*; + +pub struct PhraseEditStatus<'a>(pub &'a PhraseEditorModel); +render!(|self:PhraseEditStatus<'a>|{ + let (color, name, length, looped) = if let Some(phrase) = self.0.phrase().as_ref().map(|p|p.read().unwrap()) { + (phrase.color, phrase.name.clone(), phrase.length, phrase.looped) + } else { + (ItemPalette::from(TuiTheme::g(64)), String::new(), 0, false) + }; + let bg = color.darkest.rgb; + let fg = color.lightest.rgb; + let field = move|x, y|row!([ + Tui::fg_bg(color.lighter.rgb, color.darker.rgb, Tui::bold(true, x)), + Tui::fg_bg(color.light.rgb, color.darker.rgb, Tui::bold(true, "│")), + Fill::w(Tui::fg_bg(color.lightest.rgb, color.dark.rgb, &y)), + ]); + Fill::w(Tui::fg_bg(fg, bg, row!([ + Fixed::wh(26, 3, col!(![ + field(" Edit", format!("{name}")), + field(" Length", format!("{length}")), + field(" Loop", format!("{looped}"))])), + Fixed::wh(30, 3, col!(![ + field(" Time", format!("{}/{}-{} ({}*{}) {}", + self.0.time_point(), self.0.time_start().get(), self.0.time_end(), + self.0.time_axis().get(), self.0.time_zoom().get(), + if self.0.time_lock().get() { "[lock]" } else { " " })), + field(" Note", format!("{} ({}) {} | {}-{} ({})", + self.0.note_point(), to_note_name(self.0.note_point()), self.0.note_len(), + to_note_name(self.0.note_lo().get()), to_note_name(self.0.note_hi()), + self.0.note_axis().get()))]))])))}); diff --git a/crates/tek/src/tui/status/status_sequencer.rs b/crates/tek/src/tui/status/status_sequencer.rs new file mode 100644 index 00000000..347e2cb1 --- /dev/null +++ b/crates/tek/src/tui/status/status_sequencer.rs @@ -0,0 +1,53 @@ +use crate::*; +use super::*; + +/// Status bar for sequencer app +#[derive(Clone)] +pub struct SequencerStatus { + pub(crate) width: usize, + pub(crate) cpu: Option, + pub(crate) size: String, + pub(crate) res: String, + pub(crate) playing: bool, +} +from!(|state:&SequencerTui|SequencerStatus = { + let samples = state.clock.chunk.load(Relaxed); + let rate = state.clock.timebase.sr.get() as f64; + 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}%")), + size: format!("{}x{}│", width, state.size.h()), + res: format!("│{}s│{:.1}kHz│{:.1}ms│", samples, rate / 1000., buffer * 1000.), + } +}); +render!(|self: SequencerStatus|Fixed::h(2, lay!([ + Self::help(), + Fill::wh(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))), +]))); +impl SequencerStatus { + fn help () -> impl Render { + let single = |binding, command|row!([" ", col!([ + Tui::fg(TuiTheme::yellow(), binding), + command + ])]); + let double = |(b1, c1), (b2, c2)|col!([ + row!([" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,]), + row!([" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,]), + ]); + Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!([ + single("SPACE", "play/pause"), + double(("▲▼▶◀", "cursor"), ("Ctrl", "scroll"), ), + double(("a", "append"), ("s", "set note"),), + double((",.", "length"), ("<>", "triplet"), ), + double(("[]", "phrase"), ("{}", "order"), ), + double(("q", "enqueue"), ("e", "edit"), ), + double(("c", "color"), ("", ""),), + ])) + } + fn stats <'a> (&'a self) -> impl Render + use<'a> { + row!([&self.cpu, &self.res, &self.size]) + } +} diff --git a/crates/tek/src/tui/status_bar.rs b/crates/tek/src/tui/status_bar.rs deleted file mode 100644 index a0cc3bc1..00000000 --- a/crates/tek/src/tui/status_bar.rs +++ /dev/null @@ -1,134 +0,0 @@ -use crate::*; - -pub struct PhraseEditStatus<'a>(pub &'a PhraseEditorModel); -render!(|self:PhraseEditStatus<'a>|{ - let (color, name, length, looped) = if let Some(phrase) = self.0.phrase().as_ref().map(|p|p.read().unwrap()) { - (phrase.color, phrase.name.clone(), phrase.length, phrase.looped) - } else { - (ItemPalette::from(TuiTheme::g(64)), String::new(), 0, false) - }; - let bg = color.darkest.rgb; - let fg = color.lightest.rgb; - let field = move|x, y|row!([ - Tui::fg_bg(color.lighter.rgb, color.darker.rgb, Tui::bold(true, x)), - Tui::fg_bg(color.light.rgb, color.darker.rgb, Tui::bold(true, "│")), - Fill::w(Tui::fg_bg(color.lightest.rgb, color.dark.rgb, &y)), - ]); - Fill::w(Tui::fg_bg(fg, bg, row!([ - Fixed::wh(26, 3, col!(![ - field(" Edit", format!("{name}")), - field(" Length", format!("{length}")), - field(" Loop", format!("{looped}"))])), - Fixed::wh(30, 3, col!(![ - field(" Time", format!("{}/{}-{} ({}*{}) {}", - self.0.time_point(), self.0.time_start().get(), self.0.time_end(), - self.0.time_axis().get(), self.0.time_zoom().get(), - if self.0.time_lock().get() { "[lock]" } else { " " })), - field(" Note", format!("{} ({}) {} | {}-{} ({})", - self.0.note_point(), to_note_name(self.0.note_point()), self.0.note_len(), - to_note_name(self.0.note_lo().get()), to_note_name(self.0.note_hi()), - self.0.note_axis().get()))]))])))}); - -/// Status bar for sequencer app -#[derive(Clone)] -pub struct SequencerStatus { - pub(crate) width: usize, - pub(crate) cpu: Option, - pub(crate) size: String, - pub(crate) res: String, - pub(crate) playing: bool, -} -from!(|state:&SequencerTui|SequencerStatus = { - let samples = state.clock.chunk.load(Relaxed); - let rate = state.clock.timebase.sr.get() as f64; - 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}%")), - size: format!("{}x{}│", width, state.size.h()), - res: format!("│{}s│{:.1}kHz│{:.1}ms│", samples, rate / 1000., buffer * 1000.), - } -}); -render!(|self: SequencerStatus|Fixed::h(2, lay!([ - Self::help(), - Fill::wh(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))), -]))); -impl SequencerStatus { - fn help () -> impl Render { - let single = |binding, command|row!([" ", col!([ - Tui::fg(TuiTheme::yellow(), binding), - command - ])]); - let double = |(b1, c1), (b2, c2)|col!([ - row!([" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,]), - row!([" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,]), - ]); - Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!([ - single("SPACE", "play/pause"), - double(("▲▼▶◀", "cursor"), ("Ctrl", "scroll"), ), - double(("a", "append"), ("s", "set note"),), - double((",.", "length"), ("<>", "triplet"), ), - double(("[]", "phrase"), ("{}", "order"), ), - double(("q", "enqueue"), ("e", "edit"), ), - double(("c", "color"), ("", ""),), - ])) - } - fn stats <'a> (&'a self) -> impl Render + use<'a> { - row!([&self.cpu, &self.res, &self.size]) - } -} - -/// Status bar for arranger app -#[derive(Clone)] -pub struct ArrangerStatus { - pub(crate) width: usize, - pub(crate) cpu: Option, - pub(crate) size: String, - pub(crate) res: String, - pub(crate) playing: bool, -} -from!(|state:&ArrangerTui|ArrangerStatus = { - let samples = state.clock.chunk.load(Relaxed); - let rate = state.clock.timebase.sr.get() as f64; - 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}%")), - size: format!("{}x{}│", width, state.size.h()), - res: format!("│{}s│{:.1}kHz│{:.1}ms│", samples, rate / 1000., buffer * 1000.), - } -}); -render!(|self: ArrangerStatus|Fixed::h(2, lay!([ - Self::help(), - Fill::wh(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))), -]))); -impl ArrangerStatus { - fn help () -> impl Render { - let single = |binding, command|row!([" ", col!([ - Tui::fg(TuiTheme::yellow(), binding), - command - ])]); - let double = |(b1, c1), (b2, c2)|col!([ - row!([" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,]), - row!([" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,]), - ]); - Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!([ - single("SPACE", "play/pause"), - single(" Ctrl", " scroll"), - single(" ▲▼▶◀", " cell"), - double(("p", "put"), ("g", "get")), - double(("q", "enqueue"), ("e", "edit")), - single(" wsad", " note"), - double(("a", "append"), ("s", "set"),), - double((",.", "length"), ("<>", "triplet"),), - double(("[]", "phrase"), ("{}", "order"),), - ])) - } - fn stats <'a> (&'a self) -> impl Render + use<'a> { - row!([&self.cpu, &self.res, &self.size]) - } -} From bac231b804d42de8d02a6d601784879d11d60883 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 25 Dec 2024 06:21:15 +0100 Subject: [PATCH 008/815] update inner README --- crates/tek/README.md | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/crates/tek/README.md b/crates/tek/README.md index 0d8195be..0fbfd620 100644 --- a/crates/tek/README.md +++ b/crates/tek/README.md @@ -1,18 +1,30 @@ +# `tek` +This crate implements several musical utilities. -# `tek_sequencer` +## `tek_sequencer` -This crate implements a MIDI sequencer and arranger with clip launching. +A single-track, multi-pattern MIDI sequencer with properly tempo-synced pattern switch. --- -# `tek_arranger` +## `tek_arranger` + +A multi-track, multi-pattern MIDI sequencer translating the familiar clip launching workflow +into the TUI medium. --- -# `tek_timer` +## `tek_groovebox` -This crate implements time sync and JACK transport control. +TODO: A single-track, multi-pattern MIDI sequencer, +attached to a sampler or plugin (see `tek_plugin`). + +--- + +## `tek_timer` + +TODO: This crate implements time sync and JACK transport control. * Warning: If transport is set rolling by qjackctl, this program can't pause it * Todo: bpm: shift +/- 0.001 @@ -26,24 +38,28 @@ This crate implements time sync and JACK transport control. --- -# `tek_mixer` +## `tek_mixer` -// TODO: +TODO: // - Meters: propagate clipping: // - If one stage clips, all stages after it are marked red // - If one track clips, all tracks that feed from it are marked red? -# `tek_track` +--- + +## `tek_track` + +TODO: --- -# `tek_sampler` +## `tek_sampler` -This crate implements a sampler device which plays audio files +TODO: This crate implements a sampler device which plays audio files in response to MIDI notes. --- -# `tek_plugin` - +## `tek_plugin` +TODO: From 8b498014d2001a814c36b9899bd74c22d975b88b Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 25 Dec 2024 06:23:08 +0100 Subject: [PATCH 009/815] update outer README --- README.md | 70 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index c927ac6f..4d11796e 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,8 @@ -# tek - -[![Please don't upload to GitHub](https://nogithub.codeberg.page/badge.svg)](https://nogithub.codeberg.page) +# tek [![Please don't upload to GitHub](https://nogithub.codeberg.page/badge.svg)](https://nogithub.codeberg.page) a music making program for your terminal -## project status - -for roadmap, see https://codeberg.org/unspeaker/tek/milestones - -> [!WARNING] -> -> As of 2024-10-25, I'm on track to release `tek 0.2.0` sometime in December 2024. -> I plan to tag the previous working prototype (as seen in the demos published in the -> [tek channel at basspistol's peertube](https://v.basspistol.org/c/tek/videos)) as `0.1.0`— -> once I've identified the appropriate commit! -> -> I've been dreaming of this project for a decade, and finally had the experience and peace of mind -> to start building it in late May 2024. I quickly reached the limit of how much of the UI I can -> write imperatively, so I started refactoring it in a more declarative style. The new interface -> logic is holding out pretty well, though it's not presently without its warts. -> -> Your moral support means a lot to me. Feel free to [contact me on Mastodon](https://mastodon.social/@unspeaker)! -> (Especially if you know how to host LV2 plugin UIs in `winit`; or how to relink abandoned Win32 -> VST2s into LV2 or CLAP monoliths 😁) -> -> Love, -> -> (a rogue knowledge worker in a cyberpunk dystopia) +![Screenshot](https://codeberg.org/unspeaker/tek/attachments/549efab7-f46b-438b-9508-cd499d044b41) ## what it does @@ -34,6 +10,33 @@ Tek is a [MIDI](https://en.wikipedia.org/wiki/MIDI) sequencer, sampler, and plug for the Linux terminal. It's written in [Rust](https://www.rust-lang.org/), and targets [JACK](https://jackaudio.org/) (or [Pipewire](https://www.pipewire.org/)'s JACK implementation). +> [!NOTE] +> +> I've been dreaming of this project for a decade, and finally had the experience and peace of mind +> to start building it in late May 2024. +> +> I quickly reached the limit of how much of the UI I can write imperatively, +> so I started refactoring... and refactoring... and refactoring... you know +> how it is. + +## project status + +for roadmap, see https://codeberg.org/unspeaker/tek/milestones + +> [!NOTE] +> +> As of 2024-12-25, the release window has been open since 2024-12-10. +> [6 alpha/beta versions have been published on the Releases page](https://codeberg.org/unspeaker/tek/releases). + +> [!NOTE] +> +> I still want to tag the first working prototypes (as seen in the demos published in the +> [tek channel at basspistol's peertube](https://v.basspistol.org/c/tek/videos)) as `0.1.0`— +> once I've identified the appropriate commit! + +[^0]: (Especially if you know how to host LV2 plugin UIs in `winit`; +or how to relink abandoned Win32 VST2s into LV2 or CLAP monoliths 😁) + ## design goals ### lightweight @@ -76,10 +79,7 @@ looking for an excuse to embed a ### downloads -> [!WARNING] -> -> Binaries are currently unavailable. Right now your only option is to build from source. -> In the future I plan to integrate Forgejo Actions / Codeberg CI. +See the [releases page](https://codeberg.org/unspeaker/tek/releases). ### building from source @@ -114,3 +114,13 @@ Ardour, and probably others. The main view consists of three sections: > Use `Tab` to switch focus between views. Use `Enter` to exclusively focus the highlighted view, > and `Esc` to unfocus it. When a view is focused, use the `Arrow Keys` and `Enter` to navigate. > Use `;` (semicolon) to open the command palette, which will list the remaining keybindings. + +--- + +> [!NOTE] +> Your moral support means a lot to me. +> Feel free to [contact me on Mastodon](https://mastodon.social/@unspeaker)![^0] +> +> Love, +> 🤫 +> (a rogue knowledge worker in a cyberpunk dystopia) From a581c5650059a676b51d7630941b3c198142553e Mon Sep 17 00:00:00 2001 From: Adam Perkowski Date: Wed, 25 Dec 2024 09:52:12 +0100 Subject: [PATCH 010/815] docs(README): add Arch Linux installation instructions --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 4d11796e..19b12981 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,16 @@ looking for an excuse to embed a * Samples * LV2 plugins +### installation + +#### arch linux + +[tek](https://codeberg.org/unspeaker/tek) is available as a package in the AUR. You can install it using an AUR helper (e.g. `paru`): + +```sh +paru -S tek +``` + ### downloads See the [releases page](https://codeberg.org/unspeaker/tek/releases). From 498acac9cc4ad8509d3d81c0d9cc7116ce228650 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 25 Dec 2024 16:43:27 +0100 Subject: [PATCH 011/815] fix some of the many lints and warnings --- crates/tek/Cargo.toml | 5 +++-- crates/tek/src/cli/cli_arranger.rs | 2 ++ crates/tek/src/cli/cli_sequencer.rs | 2 ++ crates/tek/src/core/color.rs | 14 +++++++------- crates/tek/src/jack.rs | 11 ++++------- crates/tek/src/lib.rs | 2 ++ crates/tek/src/midi.rs | 2 +- crates/tek/src/space/align.rs | 4 ---- crates/tek/src/space/bsp.rs | 5 ++--- crates/tek/src/space/cond.rs | 12 ++---------- crates/tek/src/time/unit.rs | 12 ++++++------ crates/tek/src/tui.rs | 10 +++++----- crates/tek/src/tui/arranger_select.rs | 4 ++-- crates/tek/src/tui/arranger_track.rs | 1 - crates/tek/src/tui/piano_h.rs | 5 ++--- crates/tek/src/tui/pool/phrase_length.rs | 1 + crates/tek/src/tui/status/status_arranger.rs | 1 - crates/tek/src/tui/status/status_edit.rs | 1 - crates/tek/src/tui/status/status_sequencer.rs | 1 - 19 files changed, 41 insertions(+), 54 deletions(-) diff --git a/crates/tek/Cargo.toml b/crates/tek/Cargo.toml index 349ca090..5c22e156 100644 --- a/crates/tek/Cargo.toml +++ b/crates/tek/Cargo.toml @@ -28,8 +28,9 @@ uuid = { version = "1.10.0", features = [ "v4" ] } wavers = "1.4.3" #winit = { version = "0.30.4", features = [ "x11" ] } -[dev-dependencies] -#tek_app = { version = "0.1.0", path = "../tek_app" } +[lib] +name = "tek_lib" +path = "src/lib.rs" [[bin]] name = "tek_arranger" diff --git a/crates/tek/src/cli/cli_arranger.rs b/crates/tek/src/cli/cli_arranger.rs index a5dd7d86..b7f3ceb9 100644 --- a/crates/tek/src/cli/cli_arranger.rs +++ b/crates/tek/src/cli/cli_arranger.rs @@ -1,3 +1,5 @@ +#![allow(clippy::unit_arg)] + include!("../lib.rs"); pub fn main () -> Usually<()> { diff --git a/crates/tek/src/cli/cli_sequencer.rs b/crates/tek/src/cli/cli_sequencer.rs index f5cbb3b3..c44cc30f 100644 --- a/crates/tek/src/cli/cli_sequencer.rs +++ b/crates/tek/src/cli/cli_sequencer.rs @@ -1,3 +1,5 @@ +#![allow(clippy::unit_arg)] + include!("../lib.rs"); pub fn main () -> Usually<()> { diff --git a/crates/tek/src/core/color.rs b/crates/tek/src/core/color.rs index 519cb20a..9fc495af 100644 --- a/crates/tek/src/core/color.rs +++ b/crates/tek/src/core/color.rs @@ -4,7 +4,7 @@ pub use ratatui::prelude::Color; pub trait HasColor { fn color (&self) -> ItemColor; - fn color_mut (&self) -> &mut ItemColor; + fn color_mut (&mut self) -> &mut ItemColor; } /// A color in OKHSL and RGB representations. @@ -50,20 +50,20 @@ impl ItemColor { } from!(|base: Color|ItemPalette = Self::from(ItemColor::from(base))); from!(|base: ItemColor|ItemPalette = { - let mut light = base.okhsl.clone(); + let mut light = base.okhsl; light.lightness = (light.lightness * 1.3).min(Okhsl::::max_lightness()); - let mut lighter = light.clone(); + let mut lighter = light; lighter.lightness = (lighter.lightness * 1.3).min(Okhsl::::max_lightness()); - let mut lightest = lighter.clone(); + let mut lightest = lighter; lightest.lightness = (lightest.lightness * 1.3).min(Okhsl::::max_lightness()); - let mut dark = base.okhsl.clone(); + let mut dark = base.okhsl; dark.lightness = (dark.lightness * 0.75).max(Okhsl::::min_lightness()); dark.saturation = (dark.saturation * 0.75).max(Okhsl::::min_saturation()); - let mut darker = dark.clone(); + let mut darker = dark; darker.lightness = (darker.lightness * 0.66).max(Okhsl::::min_lightness()); darker.saturation = (darker.saturation * 0.66).max(Okhsl::::min_saturation()); - let mut darkest = darker.clone(); + let mut darkest = darker; darkest.lightness = (darkest.lightness * 0.50).max(Okhsl::::min_lightness()); darkest.saturation = (darkest.saturation * 0.50).max(Okhsl::::min_saturation()); diff --git a/crates/tek/src/jack.rs b/crates/tek/src/jack.rs index 0dd51dc1..7dbd2c08 100644 --- a/crates/tek/src/jack.rs +++ b/crates/tek/src/jack.rs @@ -5,14 +5,11 @@ pub(crate) mod client; pub(crate) use self::client::*; pub(crate) mod jack_event; pub(crate) use self::jack_event::*; pub(crate) mod ports; pub(crate) use self::ports::*; pub(crate) use ::jack::{ - contrib::ClosureProcessHandler, + contrib::ClosureProcessHandler, NotificationHandler, Client, AsyncClient, ClientOptions, ClientStatus, - ProcessScope, Control, CycleTimes, - Port, PortId, - PortSpec, MidiIn, MidiOut, AudioIn, AudioOut, Unowned, - Transport, TransportState, MidiIter, RawMidi, - Frames, - NotificationHandler, + ProcessScope, Control, CycleTimes, Frames, + Port, PortId, PortSpec, Unowned, MidiIn, MidiOut, AudioIn, AudioOut, + Transport, TransportState, MidiIter, MidiWriter, RawMidi, }; /// Implement [TryFrom<&Arc>>]: create app state from wrapped JACK handle. diff --git a/crates/tek/src/lib.rs b/crates/tek/src/lib.rs index 34971df0..5976df1c 100644 --- a/crates/tek/src/lib.rs +++ b/crates/tek/src/lib.rs @@ -1,3 +1,5 @@ +const FOO: () = (); + pub mod core; pub use self::core::*; pub mod time; pub(crate) use self::time::*; pub mod space; pub(crate) use self::space::*; diff --git a/crates/tek/src/midi.rs b/crates/tek/src/midi.rs index 4e54d012..7b72810e 100644 --- a/crates/tek/src/midi.rs +++ b/crates/tek/src/midi.rs @@ -43,7 +43,7 @@ pub fn to_note_name (n: usize) -> &'static str { MIDI_NOTE_NAMES[n] } -pub const MIDI_NOTE_NAMES: [&'static str;128] = [ +pub const MIDI_NOTE_NAMES: [&str; 128] = [ "C0", "C#0", "D0", "D#0", "E0", "F0", "F#0", "G0", "G#0", "A0", "A#0", "B0", "C1", "C#1", "D1", "D#1", "E1", "F1", "F#1", "G1", "G#1", "A1", "A#1", "B1", "C2", "C#2", "D2", "D#2", "E2", "F2", "F#2", "G2", "G#2", "A2", "A#2", "B2", diff --git a/crates/tek/src/space/align.rs b/crates/tek/src/space/align.rs index 9cfa1ec5..d1246da1 100644 --- a/crates/tek/src/space/align.rs +++ b/crates/tek/src/space/align.rs @@ -90,11 +90,7 @@ impl> Render for Align { let inner_area = outer_area.clip(inner_size); if let Some(aligned) = align(&self, outer_area.into(), inner_area.into()) { to.render_in(aligned, self.inner())? - } else { - () } - } else { - () }) } } diff --git a/crates/tek/src/space/bsp.rs b/crates/tek/src/space/bsp.rs index 47474f1c..cf778e9f 100644 --- a/crates/tek/src/space/bsp.rs +++ b/crates/tek/src/space/bsp.rs @@ -98,7 +98,6 @@ impl, Y: Render> Render for Bsp { } } -#[cfg(test)] -mod test { - use super::*; +#[cfg(test)] #[test] fn test_bsp () { + // TODO } diff --git a/crates/tek/src/space/cond.rs b/crates/tek/src/space/cond.rs index 7966f536..08fd57da 100644 --- a/crates/tek/src/space/cond.rs +++ b/crates/tek/src/space/cond.rs @@ -57,17 +57,9 @@ pub struct Either, B: Render>( impl, B: Render> Render for Either { fn min_size (&self, to: E::Size) -> Perhaps { - if self.1 { - return self.2.min_size(to) - } else { - return self.3.min_size(to) - } + if self.1 { self.2.min_size(to) } else { self.3.min_size(to) } } fn render (&self, to: &mut E::Output) -> Usually<()> { - if self.1 { - return self.2.render(to) - } else { - return self.3.render(to) - } + if self.1 { self.2.render(to) } else { self.3.render(to) } } } diff --git a/crates/tek/src/time/unit.rs b/crates/tek/src/time/unit.rs index 846cf839..46e218fd 100644 --- a/crates/tek/src/time/unit.rs +++ b/crates/tek/src/time/unit.rs @@ -48,12 +48,12 @@ pub trait TimeUnit: InteriorMutable {} impl_op!($T, Mul, mul, |a, b|{a * b}); impl_op!($T, Div, div, |a, b|{a / b}); impl_op!($T, Rem, rem, |a, b|{a % b}); - impl From for $T { fn from (value: f64) -> Self { Self(value.into()) } } - impl From for $T { fn from (value: usize) -> Self { Self((value as f64).into()) } } - impl Into for $T { fn into (self) -> f64 { self.get() } } - impl Into for $T { fn into (self) -> usize { self.get() as usize } } - impl Into for &$T { fn into (self) -> f64 { self.get() } } - impl Into for &$T { fn into (self) -> usize { self.get() as usize } } + impl From for $T { fn from (value: f64) -> Self { Self(value.into()) } } + impl From for $T { fn from (value: usize) -> Self { Self((value as f64).into()) } } + impl From<$T> for f64 { fn from (value: $T) -> Self { value.get() } } + impl From<$T> for usize { fn from (value: $T) -> Self { value.get() as usize } } + impl From<&$T> for f64 { fn from (value: &$T) -> Self { value.get() } } + impl From<&$T> for usize { fn from (value: &$T) -> Self { value.get() as usize } } impl Clone for $T { fn clone (&self) -> Self { Self(self.get().into()) } } } } diff --git a/crates/tek/src/tui.rs b/crates/tek/src/tui.rs index e47674e6..31a0d242 100644 --- a/crates/tek/src/tui.rs +++ b/crates/tek/src/tui.rs @@ -14,11 +14,11 @@ mod tui_border; pub(crate) use self::tui_border::*; //////////////////////////////////////////////////////// -mod app_transport; pub(crate) use self::app_transport::*; -mod app_sequencer; pub(crate) use self::app_sequencer::*; -mod app_sampler; pub(crate) use self::app_sampler::*; -mod app_groovebox; pub(crate) use self::app_groovebox::*; -mod app_arranger; pub(crate) use self::app_arranger::*; +mod app_transport; #[allow(unused)] pub(crate) use self::app_transport::*; +mod app_sequencer; #[allow(unused)] pub(crate) use self::app_sequencer::*; +mod app_sampler; #[allow(unused)] pub(crate) use self::app_sampler::*; +mod app_groovebox; #[allow(unused)] pub(crate) use self::app_groovebox::*; +mod app_arranger; #[allow(unused)] pub(crate) use self::app_arranger::*; /////////////////////////////////////////////////////// diff --git a/crates/tek/src/tui/arranger_select.rs b/crates/tek/src/tui/arranger_select.rs index 0718e7e9..ca5afe6a 100644 --- a/crates/tek/src/tui/arranger_select.rs +++ b/crates/tek/src/tui/arranger_select.rs @@ -24,11 +24,11 @@ impl ArrangerSelection { Self::Mix => format!("Everything"), Self::Track(t) => match tracks.get(*t) { Some(track) => format!("T{t}: {}", &track.name.read().unwrap()), - None => format!("T??"), + None => "T??".into(), }, Self::Scene(s) => match scenes.get(*s) { Some(scene) => format!("S{s}: {}", &scene.name.read().unwrap()), - None => format!("S??"), + None => "S??".into(), }, Self::Clip(t, s) => match (tracks.get(*t), scenes.get(*s)) { (Some(_), Some(scene)) => match scene.clip(*t) { diff --git a/crates/tek/src/tui/arranger_track.rs b/crates/tek/src/tui/arranger_track.rs index 20c2f69f..bc3e6db9 100644 --- a/crates/tek/src/tui/arranger_track.rs +++ b/crates/tek/src/tui/arranger_track.rs @@ -1,5 +1,4 @@ use crate::*; -use KeyCode::{Char, Delete}; impl ArrangerTui { pub fn track_next_name (&self) -> String { diff --git a/crates/tek/src/tui/piano_h.rs b/crates/tek/src/tui/piano_h.rs index 9889fbf8..cde739df 100644 --- a/crates/tek/src/tui/piano_h.rs +++ b/crates/tek/src/tui/piano_h.rs @@ -31,8 +31,7 @@ impl PianoHorizontal { let mut range = MidiRangeModel::from((24, true)); range.time_axis = size.x.clone(); range.note_axis = size.y.clone(); - let phrase = phrase.map(|p|p.clone()); - let color = phrase.as_ref() + let color = phrase.as_ref() .map(|p|p.read().unwrap().color) .unwrap_or(ItemPalette::from(ItemColor::from(TuiTheme::g(64)))); Self { @@ -40,7 +39,7 @@ impl PianoHorizontal { point: MidiPointModel::default(), size, range, - phrase, + phrase: phrase.map(|p|p.clone()), color } } diff --git a/crates/tek/src/tui/pool/phrase_length.rs b/crates/tek/src/tui/pool/phrase_length.rs index 32b642f3..19f532d4 100644 --- a/crates/tek/src/tui/pool/phrase_length.rs +++ b/crates/tek/src/tui/pool/phrase_length.rs @@ -2,6 +2,7 @@ use crate::*; use super::*; use PhraseLengthFocus::*; use PhraseLengthCommand::*; +use KeyCode::{Up, Down, Left, Right, Enter, Esc}; /// Displays and edits phrase length. #[derive(Clone)] diff --git a/crates/tek/src/tui/status/status_arranger.rs b/crates/tek/src/tui/status/status_arranger.rs index 7005d562..a8b6b9c0 100644 --- a/crates/tek/src/tui/status/status_arranger.rs +++ b/crates/tek/src/tui/status/status_arranger.rs @@ -1,5 +1,4 @@ use crate::*; -use super::*; /// Status bar for arranger app #[derive(Clone)] diff --git a/crates/tek/src/tui/status/status_edit.rs b/crates/tek/src/tui/status/status_edit.rs index 149dd2f0..c45103d8 100644 --- a/crates/tek/src/tui/status/status_edit.rs +++ b/crates/tek/src/tui/status/status_edit.rs @@ -1,5 +1,4 @@ use crate::*; -use super::*; pub struct PhraseEditStatus<'a>(pub &'a PhraseEditorModel); render!(|self:PhraseEditStatus<'a>|{ diff --git a/crates/tek/src/tui/status/status_sequencer.rs b/crates/tek/src/tui/status/status_sequencer.rs index 347e2cb1..44f06c0b 100644 --- a/crates/tek/src/tui/status/status_sequencer.rs +++ b/crates/tek/src/tui/status/status_sequencer.rs @@ -1,5 +1,4 @@ use crate::*; -use super::*; /// Status bar for sequencer app #[derive(Clone)] From e2172f287c466d8019bf9432e98cf79e7381baf1 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 25 Dec 2024 16:43:44 +0100 Subject: [PATCH 012/815] start flattening the midi playback logic --- crates/tek/src/midi/midi_play.rs | 134 +++++++++++++++++-------------- 1 file changed, 75 insertions(+), 59 deletions(-) diff --git a/crates/tek/src/midi/midi_play.rs b/crates/tek/src/midi/midi_play.rs index e32af28b..297dc4ac 100644 --- a/crates/tek/src/midi/midi_play.rs +++ b/crates/tek/src/midi/midi_play.rs @@ -7,19 +7,19 @@ pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts { /// Clear the section of the output buffer that we will be using, /// emitting "all notes off" at start of buffer if requested. fn clear ( - &mut self, scope: &ProcessScope, out_buf: &mut Vec>>, reset: bool + &mut self, scope: &ProcessScope, out: &mut [Vec>], reset: bool ) { - for frame in &mut out_buf[0..scope.n_frames() as usize] { + for frame in &mut out[0..scope.n_frames() as usize] { frame.clear(); } if reset { - all_notes_off(out_buf); + all_notes_off(out); } } /// Output notes from phrase to MIDI output ports. fn play ( - &mut self, scope: &ProcessScope, note_buf: &mut Vec, out_buf: &mut Vec>> + &mut self, scope: &ProcessScope, note_buf: &mut Vec, out: &mut [Vec>] ) -> bool { let mut next = false; // Write MIDI events from currently playing phrase (if any) to MIDI output buffer @@ -58,28 +58,7 @@ pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts { } // If there's a currently playing phrase, output notes from it to buffer: if let Some(ref phrase) = phrase { - // Source phrase from which the MIDI events will be taken. - let phrase = phrase.read().unwrap(); - // Phrase with zero length is not processed - if phrase.length > 0 { - // Current pulse index in source phrase - let pulse = pulse % phrase.length; - // Output each MIDI event from phrase at appropriate frames of output buffer: - for message in phrase.notes[pulse].iter() { - // Clear output buffer for this MIDI event. - note_buf.clear(); - // TODO: support MIDI channels other than CH1. - let channel = 0.into(); - // Serialize MIDI event into message buffer. - LiveEvent::Midi { channel, message: *message } - .write(note_buf) - .unwrap(); - // Append serialized message to output buffer. - out_buf[sample].push(note_buf.clone()); - // Update the list of currently held notes. - update_keys(&mut*notes, &message); - } - } + Self::play_pulse(phrase, pulse, sample, note_buf, out, notes) } } } @@ -87,47 +66,84 @@ pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts { next } - /// Handle switchover from current to next playing phrase. - fn switchover ( - &mut self, scope: &ProcessScope, note_buf: &mut Vec, out_buf: &mut Vec>> + fn play_pulse ( + phrase: &RwLock, + pulse: usize, + sample: usize, + note_buf: &mut Vec, + out: &mut [Vec>], + notes: &mut [bool;128] ) { - if self.clock().is_rolling() { - let sample0 = scope.last_frame_time() as usize; - //let samples = scope.n_frames() as usize; - if let Some((start_at, phrase)) = &self.next_phrase() { - let start = start_at.sample.get() as usize; - let sample = self.clock().started.read().unwrap().as_ref().unwrap().sample.get() as usize; - // If it's time to switch to the next phrase: - if start <= sample0.saturating_sub(sample) { - // Samples elapsed since phrase was supposed to start - let skipped = sample0 - start; - // Switch over to enqueued phrase - let started = Moment::from_sample(&self.clock().timebase(), start as f64); - *self.play_phrase_mut() = Some((started, phrase.clone())); - // Unset enqueuement (TODO: where to implement looping?) - *self.next_phrase_mut() = None - } - // TODO fill in remaining ticks of chunk from next phrase. - // ?? just call self.play(scope) again, since enqueuement is off ??? - self.play(scope, note_buf, out_buf); - // ?? or must it be with modified scope ?? - // likely not because start time etc + // Source phrase from which the MIDI events will be taken. + let phrase = phrase.read().unwrap(); + // Phrase with zero length is not processed + if phrase.length > 0 { + // Current pulse index in source phrase + let pulse = pulse % phrase.length; + // Output each MIDI event from phrase at appropriate frames of output buffer: + for message in phrase.notes[pulse].iter() { + // Clear output buffer for this MIDI event. + note_buf.clear(); + // TODO: support MIDI channels other than CH1. + let channel = 0.into(); + // Serialize MIDI event into message buffer. + LiveEvent::Midi { channel, message: *message } + .write(note_buf) + .unwrap(); + // Append serialized message to output buffer. + out[sample].push(note_buf.clone()); + // Update the list of currently held notes. + update_keys(&mut*notes, &message); } } } - /// Write a chunk of MIDI notes to the output buffer. - fn write ( - &mut self, scope: &ProcessScope, out_buf: &Vec>> + /// Handle switchover from current to next playing phrase. + fn switchover ( + &mut self, scope: &ProcessScope, note_buf: &mut Vec, out: &mut [Vec>] ) { + if !self.clock().is_rolling() { + return + } + let sample0 = scope.last_frame_time() as usize; + //let samples = scope.n_frames() as usize; + if let Some((start_at, phrase)) = &self.next_phrase() { + let start = start_at.sample.get() as usize; + let sample = self.clock().started.read().unwrap() + .as_ref().unwrap().sample.get() as usize; + // If it's time to switch to the next phrase: + if start <= sample0.saturating_sub(sample) { + // Samples elapsed since phrase was supposed to start + let skipped = sample0 - start; + // Switch over to enqueued phrase + let started = Moment::from_sample(&self.clock().timebase(), start as f64); + // Launch enqueued phrase + *self.play_phrase_mut() = Some((started, phrase.clone())); + // Unset enqueuement (TODO: where to implement looping?) + *self.next_phrase_mut() = None + } + // TODO fill in remaining ticks of chunk from next phrase. + // ?? just call self.play(scope) again, since enqueuement is off ??? + self.play(scope, note_buf, out); + // ?? or must it be with modified scope ?? + // likely not because start time etc + } + } + + /// Write a chunk of MIDI data from the output buffer to all assigned output ports. + fn write (&mut self, scope: &ProcessScope, out: &[Vec>]) { let samples = scope.n_frames() as usize; for port in self.midi_outs_mut().iter_mut() { - let writer = &mut port.writer(scope); - for time in 0..samples { - for event in out_buf[time].iter() { - writer.write(&RawMidi { time: time as u32, bytes: &event }) - .expect(&format!("{event:?}")); - } + Self::write_port(&mut port.writer(scope), samples, out) + } + } + + /// Write a chunk of MIDI data from the output buffer to an output port. + fn write_port (writer: &mut MidiWriter, samples: usize, out: &[Vec>]) { + for time in 0..samples { + for event in out[time].iter() { + writer.write(&RawMidi { time: time as u32, bytes: &event }) + .expect(&format!("{event:?}")); } } } From f57589e83ae33cf0dfc934bef3a8818c037d1763 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 26 Dec 2024 00:05:42 +0100 Subject: [PATCH 013/815] flatten midi playback some more --- crates/tek/src/midi/midi_play.rs | 74 ++++++++++++++++---------------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/crates/tek/src/midi/midi_play.rs b/crates/tek/src/midi/midi_play.rs index 297dc4ac..6a703e3f 100644 --- a/crates/tek/src/midi/midi_play.rs +++ b/crates/tek/src/midi/midi_play.rs @@ -21,49 +21,51 @@ pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts { fn play ( &mut self, scope: &ProcessScope, note_buf: &mut Vec, out: &mut [Vec>] ) -> bool { - let mut next = false; - // Write MIDI events from currently playing phrase (if any) to MIDI output buffer - if self.clock().is_rolling() { - let sample0 = scope.last_frame_time() as usize; - let samples = scope.n_frames() as usize; - // If no phrase is playing, prepare for switchover immediately - next = self.play_phrase().is_none(); - let phrase = self.play_phrase(); + if !self.clock().is_rolling() { + return false + } + let playing = self.play_phrase(); + if let Some((started, phrase)) = playing { + // If a phrase is playing, write a chunk of MIDI events from it to the output buffer + let mut next = false; + let sample0 = scope.last_frame_time() as usize; + let samples = scope.n_frames() as usize; let started0 = &self.clock().started; let timebase = self.clock().timebase(); let notes_out = self.notes_out(); let next_phrase = self.next_phrase(); - if let Some((started, phrase)) = phrase { - // First sample to populate. Greater than 0 means that the first - // pulse of the phrase falls somewhere in the middle of the chunk. - let sample = started.sample.get() as usize; - let sample = sample + started0.read().unwrap().as_ref().unwrap().sample.get() as usize; - let sample = sample0.saturating_sub(sample); - // Iterator that emits sample (index into output buffer at which to write MIDI event) - // paired with pulse (index into phrase from which to take the MIDI event) for each - // sample of the output buffer that corresponds to a MIDI pulse. - let pulses = timebase.pulses_between_samples(sample, sample + samples); - // Notes active during current chunk. - let notes = &mut notes_out.write().unwrap(); - for (sample, pulse) in pulses { - // If a next phrase is enqueued, and we're past the end of the current one, - // break the loop here (FIXME count pulse correctly) - next = next_phrase.is_some() && if let Some(ref phrase) = phrase { - pulse >= phrase.read().unwrap().length - } else { - true - }; - if next { - break - } - // If there's a currently playing phrase, output notes from it to buffer: - if let Some(ref phrase) = phrase { - Self::play_pulse(phrase, pulse, sample, note_buf, out, notes) - } + // First sample to populate. Greater than 0 means that the first + // pulse of the phrase falls somewhere in the middle of the chunk. + let sample = started.sample.get() as usize; + let sample = sample + started0.read().unwrap().as_ref().unwrap().sample.get() as usize; + let sample = sample0.saturating_sub(sample); + // Iterator that emits sample (index into output buffer at which to write MIDI event) + // paired with pulse (index into phrase from which to take the MIDI event) for each + // sample of the output buffer that corresponds to a MIDI pulse. + let pulses = timebase.pulses_between_samples(sample, sample + samples); + // Notes active during current chunk. + let notes = &mut notes_out.write().unwrap(); + for (sample, pulse) in pulses { + // If a next phrase is enqueued, and we're past the end of the current one, + // break the loop here (FIXME count pulse correctly) + next = next_phrase.is_some() && if let Some(ref phrase) = phrase { + pulse >= phrase.read().unwrap().length + } else { + true + }; + if next { + break + } + // If there's a currently playing phrase, output notes from it to buffer: + if let Some(ref phrase) = phrase { + Self::play_pulse(phrase, pulse, sample, note_buf, out, notes) } } + next + } else { + // If no phrase is playing, prepare for switchover immediately + true } - next } fn play_pulse ( From 2492537c32aa30d73597de66757fd6cfb0853705 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 26 Dec 2024 00:20:04 +0100 Subject: [PATCH 014/815] flatten midi playback logic furter --- crates/tek/src/midi/midi_play.rs | 79 ++++++++++++++++---------------- 1 file changed, 39 insertions(+), 40 deletions(-) diff --git a/crates/tek/src/midi/midi_play.rs b/crates/tek/src/midi/midi_play.rs index 6a703e3f..12d414c7 100644 --- a/crates/tek/src/midi/midi_play.rs +++ b/crates/tek/src/midi/midi_play.rs @@ -24,48 +24,47 @@ pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts { if !self.clock().is_rolling() { return false } - let playing = self.play_phrase(); - if let Some((started, phrase)) = playing { - // If a phrase is playing, write a chunk of MIDI events from it to the output buffer - let mut next = false; - let sample0 = scope.last_frame_time() as usize; - let samples = scope.n_frames() as usize; - let started0 = &self.clock().started; - let timebase = self.clock().timebase(); - let notes_out = self.notes_out(); - let next_phrase = self.next_phrase(); - // First sample to populate. Greater than 0 means that the first - // pulse of the phrase falls somewhere in the middle of the chunk. - let sample = started.sample.get() as usize; - let sample = sample + started0.read().unwrap().as_ref().unwrap().sample.get() as usize; - let sample = sample0.saturating_sub(sample); - // Iterator that emits sample (index into output buffer at which to write MIDI event) - // paired with pulse (index into phrase from which to take the MIDI event) for each - // sample of the output buffer that corresponds to a MIDI pulse. - let pulses = timebase.pulses_between_samples(sample, sample + samples); - // Notes active during current chunk. - let notes = &mut notes_out.write().unwrap(); - for (sample, pulse) in pulses { - // If a next phrase is enqueued, and we're past the end of the current one, - // break the loop here (FIXME count pulse correctly) - next = next_phrase.is_some() && if let Some(ref phrase) = phrase { - pulse >= phrase.read().unwrap().length - } else { - true - }; - if next { - break - } - // If there's a currently playing phrase, output notes from it to buffer: - if let Some(ref phrase) = phrase { - Self::play_pulse(phrase, pulse, sample, note_buf, out, notes) - } + // If a phrase is playing, write a chunk of MIDI events from it to the output buffer. + // If no phrase is playing, prepare for switchover immediately. + self.play_phrase().as_ref().map_or(true, |(started, phrase)|{ + self.play_phrase_chunk(scope, note_buf, out, started, phrase) + }) + } + + fn play_phrase_chunk ( + &self, + scope: &ProcessScope, + note_buf: &mut Vec, + out: &mut [Vec>], + started: &Moment, + phrase: &Option>> + ) -> bool { + // First sample to populate. Greater than 0 means that the first + // pulse of the phrase falls somewhere in the middle of the chunk. + let sample = (scope.last_frame_time() as usize).saturating_sub( + started.sample.get() as usize + + self.clock().started.read().unwrap().as_ref().unwrap().sample.get() as usize + ); + // Iterator that emits sample (index into output buffer at which to write MIDI event) + // paired with pulse (index into phrase from which to take the MIDI event) for each + // sample of the output buffer that corresponds to a MIDI pulse. + let pulses = self.clock().timebase().pulses_between_samples(sample, sample + scope.n_frames() as usize); + // Notes active during current chunk. + let notes = &mut self.notes_out().write().unwrap(); + let length = phrase.as_ref().map_or(0, |p|p.read().unwrap().length); + for (sample, pulse) in pulses { + // If a next phrase is enqueued, and we're past the end of the current one, + // break the loop here (FIXME count pulse correctly) + let past_end = if phrase.is_some() { pulse >= length } else { true }; + if self.next_phrase().is_some() && past_end { + return true + } + // If there's a currently playing phrase, output notes from it to buffer: + if let Some(ref phrase) = phrase { + Self::play_pulse(phrase, pulse, sample, note_buf, out, notes) } - next - } else { - // If no phrase is playing, prepare for switchover immediately - true } + false } fn play_pulse ( From d7c47c2561bc9fd473468cffe971e0fb4087d99e Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 26 Dec 2024 00:36:40 +0100 Subject: [PATCH 015/815] move play call to innermost block of fn switchover --- crates/tek/src/edn.rs | 6 ++- crates/tek/src/midi/midi_play.rs | 65 +++++++++++++++----------------- 2 files changed, 35 insertions(+), 36 deletions(-) diff --git a/crates/tek/src/edn.rs b/crates/tek/src/edn.rs index f0c4d6ad..eb4992bb 100644 --- a/crates/tek/src/edn.rs +++ b/crates/tek/src/edn.rs @@ -1,3 +1,5 @@ +#![allow(unused)] + use crate::*; pub use clojure_reader::edn::Edn; //pub use clojure_reader::{edn::{read, Edn}, error::Error as EdnError}; @@ -49,9 +51,9 @@ impl crate::Sampler { unmapped: Default::default(), voices: Default::default(), buffer: Default::default(), - midi_in: midi_in, audio_outs: vec![], - output_gain: 0. + output_gain: 0., + midi_in, }) } } diff --git a/crates/tek/src/midi/midi_play.rs b/crates/tek/src/midi/midi_play.rs index 12d414c7..8331d30c 100644 --- a/crates/tek/src/midi/midi_play.rs +++ b/crates/tek/src/midi/midi_play.rs @@ -27,11 +27,40 @@ pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts { // If a phrase is playing, write a chunk of MIDI events from it to the output buffer. // If no phrase is playing, prepare for switchover immediately. self.play_phrase().as_ref().map_or(true, |(started, phrase)|{ - self.play_phrase_chunk(scope, note_buf, out, started, phrase) + self.play_chunk(scope, note_buf, out, started, phrase) }) } - fn play_phrase_chunk ( + /// Handle switchover from current to next playing phrase. + fn switchover ( + &mut self, scope: &ProcessScope, note_buf: &mut Vec, out: &mut [Vec>] + ) { + if !self.clock().is_rolling() { + return + } + let sample0 = scope.last_frame_time() as usize; + //let samples = scope.n_frames() as usize; + if let Some((start_at, phrase)) = &self.next_phrase() { + let start = start_at.sample.get() as usize; + let sample = self.clock().started.read().unwrap() + .as_ref().unwrap().sample.get() as usize; + // If it's time to switch to the next phrase: + if start <= sample0.saturating_sub(sample) { + // Samples elapsed since phrase was supposed to start + let skipped = sample0 - start; + // Switch over to enqueued phrase + let started = Moment::from_sample(&self.clock().timebase(), start as f64); + // Launch enqueued phrase + *self.play_phrase_mut() = Some((started, phrase.clone())); + // Unset enqueuement (TODO: where to implement looping?) + *self.next_phrase_mut() = None; + // Fill in remaining ticks of chunk from next phrase. + self.play(scope, note_buf, out); + } + } + } + + fn play_chunk ( &self, scope: &ProcessScope, note_buf: &mut Vec, @@ -99,38 +128,6 @@ pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts { } } - /// Handle switchover from current to next playing phrase. - fn switchover ( - &mut self, scope: &ProcessScope, note_buf: &mut Vec, out: &mut [Vec>] - ) { - if !self.clock().is_rolling() { - return - } - let sample0 = scope.last_frame_time() as usize; - //let samples = scope.n_frames() as usize; - if let Some((start_at, phrase)) = &self.next_phrase() { - let start = start_at.sample.get() as usize; - let sample = self.clock().started.read().unwrap() - .as_ref().unwrap().sample.get() as usize; - // If it's time to switch to the next phrase: - if start <= sample0.saturating_sub(sample) { - // Samples elapsed since phrase was supposed to start - let skipped = sample0 - start; - // Switch over to enqueued phrase - let started = Moment::from_sample(&self.clock().timebase(), start as f64); - // Launch enqueued phrase - *self.play_phrase_mut() = Some((started, phrase.clone())); - // Unset enqueuement (TODO: where to implement looping?) - *self.next_phrase_mut() = None - } - // TODO fill in remaining ticks of chunk from next phrase. - // ?? just call self.play(scope) again, since enqueuement is off ??? - self.play(scope, note_buf, out); - // ?? or must it be with modified scope ?? - // likely not because start time etc - } - } - /// Write a chunk of MIDI data from the output buffer to all assigned output ports. fn write (&mut self, scope: &ProcessScope, out: &[Vec>]) { let samples = scope.n_frames() as usize; From 58bb25eb40ead0011698b91b405bc1221ee29e7e Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 27 Dec 2024 08:35:29 +0100 Subject: [PATCH 016/815] PhrasePlayerModel -> MidiPlayer --- crates/tek/src/midi.rs | 24 ++++++++++++------------ crates/tek/src/tui/app_sequencer.rs | 4 ++-- crates/tek/src/tui/arranger_mode.rs | 3 --- crates/tek/src/tui/arranger_scene.rs | 3 --- crates/tek/src/tui/arranger_select.rs | 2 -- crates/tek/src/tui/arranger_track.rs | 10 ++-------- crates/tek/src/tui/arranger_v.rs | 4 ---- 7 files changed, 16 insertions(+), 34 deletions(-) diff --git a/crates/tek/src/midi.rs b/crates/tek/src/midi.rs index 7b72810e..8a4fb75c 100644 --- a/crates/tek/src/midi.rs +++ b/crates/tek/src/midi.rs @@ -59,7 +59,7 @@ pub const MIDI_NOTE_NAMES: [&str; 128] = [ pub trait MidiPlayerApi: MidiRecordApi + MidiPlaybackApi + Send + Sync {} -impl MidiPlayerApi for PhrasePlayerModel {} +impl MidiPlayerApi for MidiPlayer {} pub trait HasPlayer { fn player (&self) -> &impl MidiPlayerApi; @@ -76,7 +76,7 @@ pub trait HasPlayer { } /// Contains state for playing a phrase -pub struct PhrasePlayerModel { +pub struct MidiPlayer { /// State of clock and playhead pub(crate) clock: ClockModel, /// Start time and phrase being played @@ -102,16 +102,16 @@ pub struct PhrasePlayerModel { /// MIDI output buffer pub note_buf: Vec, } -impl std::fmt::Debug for PhrasePlayerModel { +impl std::fmt::Debug for MidiPlayer { fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - f.debug_struct("PhrasePlayerModel") + f.debug_struct("MidiPlayer") .field("clock", &self.clock) .field("play_phrase", &self.play_phrase) .field("next_phrase", &self.next_phrase) .finish() } } -from!(|clock: &ClockModel| PhrasePlayerModel = Self { +from!(|clock: &ClockModel| MidiPlayer = Self { clock: clock.clone(), midi_ins: vec![], midi_outs: vec![], @@ -125,15 +125,15 @@ from!(|clock: &ClockModel| PhrasePlayerModel = Self { notes_in: RwLock::new([false;128]).into(), notes_out: RwLock::new([false;128]).into(), }); -from!(|state: (&ClockModel, &Arc>)|PhrasePlayerModel = { +from!(|state: (&ClockModel, &Arc>)|MidiPlayer = { let (clock, phrase) = state; let mut model = Self::from(clock); model.play_phrase = Some((Moment::zero(&clock.timebase), Some(phrase.clone()))); model }); -has_clock!(|self:PhrasePlayerModel|&self.clock); +has_clock!(|self: MidiPlayer|&self.clock); -impl HasMidiIns for PhrasePlayerModel { +impl HasMidiIns for MidiPlayer { fn midi_ins (&self) -> &Vec> { &self.midi_ins } @@ -142,7 +142,7 @@ impl HasMidiIns for PhrasePlayerModel { } } -impl HasMidiOuts for PhrasePlayerModel { +impl HasMidiOuts for MidiPlayer { fn midi_outs (&self) -> &Vec> { &self.midi_outs } @@ -191,7 +191,7 @@ impl<'a, T: MidiPlayerApi> Audio for PlayerAudio<'a, T> { } } -impl MidiRecordApi for PhrasePlayerModel { +impl MidiRecordApi for MidiPlayer { fn recording (&self) -> bool { self.recording } @@ -215,13 +215,13 @@ impl MidiRecordApi for PhrasePlayerModel { } } -impl MidiPlaybackApi for PhrasePlayerModel { +impl MidiPlaybackApi for MidiPlayer { fn notes_out (&self) -> &Arc> { &self.notes_in } } -impl HasPlayPhrase for PhrasePlayerModel { +impl HasPlayPhrase for MidiPlayer { fn reset (&self) -> bool { self.reset } diff --git a/crates/tek/src/tui/app_sequencer.rs b/crates/tek/src/tui/app_sequencer.rs index fbf5dd44..97ec300f 100644 --- a/crates/tek/src/tui/app_sequencer.rs +++ b/crates/tek/src/tui/app_sequencer.rs @@ -9,7 +9,7 @@ pub struct SequencerTui { _jack: Arc>, pub(crate) clock: ClockModel, pub(crate) phrases: PoolModel, - pub(crate) player: PhrasePlayerModel, + pub(crate) player: MidiPlayer, pub(crate) editor: PhraseEditorModel, pub(crate) size: Measure, pub(crate) status: bool, @@ -27,7 +27,7 @@ from_jack!(|jack|SequencerTui { _jack: jack.clone(), phrases: PoolModel::from(&phrase), editor: PhraseEditorModel::from(&phrase), - player: PhrasePlayerModel::from((&clock, &phrase)), + player: MidiPlayer::from((&clock, &phrase)), size: Measure::new(), midi_buf: vec![vec![];65536], note_buf: vec![], diff --git a/crates/tek/src/tui/arranger_mode.rs b/crates/tek/src/tui/arranger_mode.rs index c28d8b7f..c415d12a 100644 --- a/crates/tek/src/tui/arranger_mode.rs +++ b/crates/tek/src/tui/arranger_mode.rs @@ -1,5 +1,4 @@ use crate::*; - /// Display mode of arranger #[derive(Clone, PartialEq)] pub enum ArrangerMode { @@ -9,7 +8,6 @@ pub enum ArrangerMode { H, } render!(|self: ArrangerMode|{}); - /// Arranger display mode can be cycled impl ArrangerMode { /// Cycle arranger display mode @@ -23,7 +21,6 @@ impl ArrangerMode { } } } - fn any_size (_: E::Size) -> Perhaps{ Ok(Some([0.into(),0.into()].into())) } diff --git a/crates/tek/src/tui/arranger_scene.rs b/crates/tek/src/tui/arranger_scene.rs index ddbf3114..2bdb3a48 100644 --- a/crates/tek/src/tui/arranger_scene.rs +++ b/crates/tek/src/tui/arranger_scene.rs @@ -1,5 +1,4 @@ use crate::*; - impl ArrangerTui { pub fn scene_add (&mut self, name: Option<&str>, color: Option) -> Usually<&mut ArrangerScene> @@ -27,7 +26,6 @@ impl ArrangerTui { self.selected.scene().map(|s|self.scenes.get_mut(s)).flatten() } } - #[derive(Default, Debug, Clone)] pub struct ArrangerScene { /// Name of scene pub(crate) name: Arc>, @@ -36,7 +34,6 @@ impl ArrangerTui { /// Identifying color of scene pub(crate) color: ItemPalette, } - impl ArrangerScene { pub fn name (&self) -> &Arc> { &self.name diff --git a/crates/tek/src/tui/arranger_select.rs b/crates/tek/src/tui/arranger_select.rs index ca5afe6a..e9d3ffc6 100644 --- a/crates/tek/src/tui/arranger_select.rs +++ b/crates/tek/src/tui/arranger_select.rs @@ -1,5 +1,4 @@ use crate::*; - #[derive(PartialEq, Clone, Copy, Debug)] /// Represents the current user selection in the arranger pub enum ArrangerSelection { @@ -12,7 +11,6 @@ pub enum ArrangerSelection { /// A clip (track × scene) is selected. Clip(usize, usize), } - /// Focus identification methods impl ArrangerSelection { pub fn description ( diff --git a/crates/tek/src/tui/arranger_track.rs b/crates/tek/src/tui/arranger_track.rs index bc3e6db9..390d93f5 100644 --- a/crates/tek/src/tui/arranger_track.rs +++ b/crates/tek/src/tui/arranger_track.rs @@ -1,5 +1,4 @@ use crate::*; - impl ArrangerTui { pub fn track_next_name (&self) -> String { format!("T{}", self.tracks.len() + 1) @@ -12,7 +11,7 @@ impl ArrangerTui { width: name.len() + 2, name: Arc::new(name.into()), color: color.unwrap_or_else(||ItemPalette::random()), - player: PhrasePlayerModel::from(&self.clock), + player: MidiPlayer::from(&self.clock), }; self.tracks.push(track); let index = self.tracks.len() - 1; @@ -25,7 +24,6 @@ impl ArrangerTui { } } } - #[derive(Debug)] pub struct ArrangerTrack { /// Name of track pub(crate) name: Arc>, @@ -34,12 +32,10 @@ impl ArrangerTui { /// Identifying color of track pub(crate) color: ItemPalette, /// MIDI player state - pub(crate) player: PhrasePlayerModel, + pub(crate) player: MidiPlayer, } - has_clock!(|self:ArrangerTrack|self.player.clock()); has_player!(|self:ArrangerTrack|self.player); - impl ArrangerTrack { pub fn widths (tracks: &[Self]) -> Vec<(usize, usize)> { let mut widths = vec![]; @@ -91,7 +87,6 @@ impl ArrangerTrack { } } } - /// Hosts the JACK callback for a collection of tracks pub struct TracksAudio<'a>( // Track collection @@ -101,7 +96,6 @@ pub struct TracksAudio<'a>( /// Note chunk buffer pub &'a mut Vec>>, ); - impl<'a> Audio for TracksAudio<'a> { #[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { let model = &mut self.0; diff --git a/crates/tek/src/tui/arranger_v.rs b/crates/tek/src/tui/arranger_v.rs index 9f35c3ee..ff2a774e 100644 --- a/crates/tek/src/tui/arranger_v.rs +++ b/crates/tek/src/tui/arranger_v.rs @@ -1,14 +1,11 @@ use crate::*; - mod v_clips; pub(crate) use self::v_clips::*; mod v_cursor; pub(crate) use self::v_cursor::*; mod v_head; pub(crate) use self::v_head::*; mod v_io; pub(crate) use self::v_io::*; mod v_sep; pub(crate) use self::v_sep::*; - const HEADER_H: u16 = 5; const SCENES_W_OFFSET: u16 = 3; - impl ArrangerTui { pub fn render_mode_v (state: &ArrangerTui, factor: usize) -> impl Render + use<'_> { lay!([ @@ -24,4 +21,3 @@ impl ArrangerTui { ]) } } - From 63550fabcff01ed450fa628113843c4e97435dd3 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 27 Dec 2024 08:58:00 +0100 Subject: [PATCH 017/815] midi_phrase.rs -> midi_clip.rs --- crates/tek/src/midi.rs | 2 +- crates/tek/src/midi/{midi_phrase.rs => midi_clip.rs} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename crates/tek/src/midi/{midi_phrase.rs => midi_clip.rs} (100%) diff --git a/crates/tek/src/midi.rs b/crates/tek/src/midi.rs index 8a4fb75c..e0e4672b 100644 --- a/crates/tek/src/midi.rs +++ b/crates/tek/src/midi.rs @@ -4,7 +4,7 @@ pub(crate) mod midi_in; pub(crate) use midi_in::*; pub(crate) mod midi_launch; pub(crate) use midi_launch::*; pub(crate) mod midi_note; pub(crate) use midi_note::*; pub(crate) mod midi_out; pub(crate) use midi_out::*; -pub(crate) mod midi_phrase; pub(crate) use midi_phrase::*; +pub(crate) mod midi_clip; pub(crate) use midi_clip::*; pub(crate) mod midi_play; pub(crate) use midi_play::*; pub(crate) mod midi_pool; pub(crate) use midi_pool::*; pub(crate) mod midi_rec; pub(crate) use midi_rec::*; diff --git a/crates/tek/src/midi/midi_phrase.rs b/crates/tek/src/midi/midi_clip.rs similarity index 100% rename from crates/tek/src/midi/midi_phrase.rs rename to crates/tek/src/midi/midi_clip.rs From 0530e43a2ff93dab36be674fa5b4521db07d5a3f Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 27 Dec 2024 09:05:33 +0100 Subject: [PATCH 018/815] Phrase -> MidiClip, PhraseEdit -> MidiEdit --- crates/tek/src/midi.rs | 14 +++--- crates/tek/src/midi/midi_clip.rs | 24 +++++----- crates/tek/src/midi/midi_launch.rs | 10 ++-- crates/tek/src/midi/midi_play.rs | 4 +- crates/tek/src/midi/midi_pool.rs | 12 ++--- crates/tek/src/tui/app_arranger.rs | 8 ++-- crates/tek/src/tui/app_sequencer.rs | 10 ++-- crates/tek/src/tui/arranger_command.rs | 4 +- crates/tek/src/tui/arranger_scene.rs | 6 +-- crates/tek/src/tui/arranger_v/v_clips.rs | 2 +- crates/tek/src/tui/phrase_editor.rs | 58 ++++++++++++------------ crates/tek/src/tui/piano_h.rs | 16 +++---- crates/tek/src/tui/pool.rs | 16 +++---- crates/tek/src/tui/status/status_edit.rs | 4 +- 14 files changed, 94 insertions(+), 94 deletions(-) diff --git a/crates/tek/src/midi.rs b/crates/tek/src/midi.rs index e0e4672b..4ca9200b 100644 --- a/crates/tek/src/midi.rs +++ b/crates/tek/src/midi.rs @@ -80,9 +80,9 @@ pub struct MidiPlayer { /// State of clock and playhead pub(crate) clock: ClockModel, /// Start time and phrase being played - pub(crate) play_phrase: Option<(Moment, Option>>)>, + pub(crate) play_phrase: Option<(Moment, Option>>)>, /// Start time and next phrase - pub(crate) next_phrase: Option<(Moment, Option>>)>, + pub(crate) next_phrase: Option<(Moment, Option>>)>, /// Play input through output. pub(crate) monitoring: bool, /// Write input to sequence. @@ -125,7 +125,7 @@ from!(|clock: &ClockModel| MidiPlayer = Self { notes_in: RwLock::new([false;128]).into(), notes_out: RwLock::new([false;128]).into(), }); -from!(|state: (&ClockModel, &Arc>)|MidiPlayer = { +from!(|state: (&ClockModel, &Arc>)|MidiPlayer = { let (clock, phrase) = state; let mut model = Self::from(clock); model.play_phrase = Some((Moment::zero(&clock.timebase), Some(phrase.clone()))); @@ -228,16 +228,16 @@ impl HasPlayPhrase for MidiPlayer { fn reset_mut (&mut self) -> &mut bool { &mut self.reset } - fn play_phrase (&self) -> &Option<(Moment, Option>>)> { + fn play_phrase (&self) -> &Option<(Moment, Option>>)> { &self.play_phrase } - fn play_phrase_mut (&mut self) -> &mut Option<(Moment, Option>>)> { + fn play_phrase_mut (&mut self) -> &mut Option<(Moment, Option>>)> { &mut self.play_phrase } - fn next_phrase (&self) -> &Option<(Moment, Option>>)> { + fn next_phrase (&self) -> &Option<(Moment, Option>>)> { &self.next_phrase } - fn next_phrase_mut (&mut self) -> &mut Option<(Moment, Option>>)> { + fn next_phrase_mut (&mut self) -> &mut Option<(Moment, Option>>)> { &mut self.next_phrase } } diff --git a/crates/tek/src/midi/midi_clip.rs b/crates/tek/src/midi/midi_clip.rs index 1c1f66dd..2ee1c863 100644 --- a/crates/tek/src/midi/midi_clip.rs +++ b/crates/tek/src/midi/midi_clip.rs @@ -1,20 +1,20 @@ use crate::*; -pub trait HasPhrase { - fn phrase (&self) -> &Arc>; +pub trait HasMidiClip { + fn phrase (&self) -> &Arc>; } #[macro_export] macro_rules! has_phrase { (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { - impl $(<$($L),*$($T $(: $U)?),*>)? HasPhrase for $Struct $(<$($L),*$($T),*>)? { - fn phrase (&$self) -> &Arc> { &$cb } + impl $(<$($L),*$($T $(: $U)?),*>)? HasMidiClip for $Struct $(<$($L),*$($T),*>)? { + fn phrase (&$self) -> &Arc> { &$cb } } } } /// A MIDI sequence. #[derive(Debug, Clone)] -pub struct Phrase { +pub struct MidiClip { pub uuid: uuid::Uuid, /// Name of phrase pub name: String, @@ -23,7 +23,7 @@ pub struct Phrase { /// Length of phrase in pulses pub length: usize, /// Notes in phrase - pub notes: PhraseData, + pub notes: MidiData, /// Whether to loop the phrase or play it once pub looped: bool, /// Start of loop @@ -37,14 +37,14 @@ pub struct Phrase { } /// MIDI message structural -pub type PhraseData = Vec>; +pub type MidiData = Vec>; -impl Phrase { +impl MidiClip { pub fn new ( name: impl AsRef, looped: bool, length: usize, - notes: Option, + notes: Option, color: Option, ) -> Self { Self { @@ -86,7 +86,7 @@ impl Phrase { } } -impl Default for Phrase { +impl Default for MidiClip { fn default () -> Self { Self::new( "Stop", @@ -101,10 +101,10 @@ impl Default for Phrase { } } -impl PartialEq for Phrase { +impl PartialEq for MidiClip { fn eq (&self, other: &Self) -> bool { self.uuid == other.uuid } } -impl Eq for Phrase {} +impl Eq for MidiClip {} diff --git a/crates/tek/src/midi/midi_launch.rs b/crates/tek/src/midi/midi_launch.rs index e680601a..2acabd4a 100644 --- a/crates/tek/src/midi/midi_launch.rs +++ b/crates/tek/src/midi/midi_launch.rs @@ -3,10 +3,10 @@ use crate::*; pub trait HasPlayPhrase: HasClock { fn reset (&self) -> bool; fn reset_mut (&mut self) -> &mut bool; - fn play_phrase (&self) -> &Option<(Moment, Option>>)>; - fn play_phrase_mut (&mut self) -> &mut Option<(Moment, Option>>)>; - fn next_phrase (&self) -> &Option<(Moment, Option>>)>; - fn next_phrase_mut (&mut self) -> &mut Option<(Moment, Option>>)>; + fn play_phrase (&self) -> &Option<(Moment, Option>>)>; + fn play_phrase_mut (&mut self) -> &mut Option<(Moment, Option>>)>; + fn next_phrase (&self) -> &Option<(Moment, Option>>)>; + fn next_phrase_mut (&mut self) -> &mut Option<(Moment, Option>>)>; fn pulses_since_start (&self) -> Option { if let Some((started, Some(_))) = self.play_phrase().as_ref() { let elapsed = self.clock().playhead.pulse.get() - started.pulse.get(); @@ -25,7 +25,7 @@ pub trait HasPlayPhrase: HasClock { None } } - fn enqueue_next (&mut self, phrase: Option<&Arc>>) { + fn enqueue_next (&mut self, phrase: Option<&Arc>>) { let start = self.clock().next_launch_pulse() as f64; let instant = Moment::from_pulse(&self.clock().timebase(), start); let phrase = phrase.map(|p|p.clone()); diff --git a/crates/tek/src/midi/midi_play.rs b/crates/tek/src/midi/midi_play.rs index 8331d30c..5452c7c0 100644 --- a/crates/tek/src/midi/midi_play.rs +++ b/crates/tek/src/midi/midi_play.rs @@ -66,7 +66,7 @@ pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts { note_buf: &mut Vec, out: &mut [Vec>], started: &Moment, - phrase: &Option>> + phrase: &Option>> ) -> bool { // First sample to populate. Greater than 0 means that the first // pulse of the phrase falls somewhere in the middle of the chunk. @@ -97,7 +97,7 @@ pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts { } fn play_pulse ( - phrase: &RwLock, + phrase: &RwLock, pulse: usize, sample: usize, note_buf: &mut Vec, diff --git a/crates/tek/src/midi/midi_pool.rs b/crates/tek/src/midi/midi_pool.rs index 555059be..f69d0027 100644 --- a/crates/tek/src/midi/midi_pool.rs +++ b/crates/tek/src/midi/midi_pool.rs @@ -1,22 +1,22 @@ use crate::*; pub trait HasPhrases { - fn phrases (&self) -> &Vec>>; - fn phrases_mut (&mut self) -> &mut Vec>>; + fn phrases (&self) -> &Vec>>; + fn phrases_mut (&mut self) -> &mut Vec>>; } #[macro_export] macro_rules! has_phrases { (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { impl $(<$($L),*$($T $(: $U)?),*>)? HasPhrases for $Struct $(<$($L),*$($T),*>)? { - fn phrases (&$self) -> &Vec>> { &$cb } - fn phrases_mut (&mut $self) -> &mut Vec>> { &mut$cb } + fn phrases (&$self) -> &Vec>> { &$cb } + fn phrases_mut (&mut $self) -> &mut Vec>> { &mut$cb } } } } #[derive(Clone, Debug, PartialEq)] pub enum PhrasePoolCommand { - Add(usize, Phrase), + Add(usize, MidiClip), Delete(usize), Swap(usize, usize), Import(usize, PathBuf), @@ -62,7 +62,7 @@ impl Command for PhrasePoolCommand { } } } - let mut phrase = Phrase::new("imported", true, t as usize + 1, None, None); + let mut phrase = MidiClip::new("imported", true, t as usize + 1, None, None); for event in events.iter() { phrase.notes[event.0 as usize].push(event.2); } diff --git a/crates/tek/src/tui/app_arranger.rs b/crates/tek/src/tui/app_arranger.rs index fe3f91bd..9866c45a 100644 --- a/crates/tek/src/tui/app_arranger.rs +++ b/crates/tek/src/tui/app_arranger.rs @@ -13,7 +13,7 @@ pub struct ArrangerTui { pub size: Measure, pub note_buf: Vec, pub midi_buf: Vec>>, - pub editor: PhraseEditorModel, + pub editor: MidiEditorModel, pub perf: PerfModel, } impl ArrangerTui { @@ -40,7 +40,7 @@ impl ArrangerTui { }; Ok(()) } - pub fn selected_phrase (&self) -> Option>> { + pub fn selected_phrase (&self) -> Option>> { self.selected_scene()?.clips.get(self.selected.track()?)?.clone() } pub fn toggle_loop (&mut self) { @@ -61,7 +61,7 @@ impl ArrangerTui { } from_jack!(|jack| ArrangerTui { let clock = ClockModel::from(jack); - let phrase = Arc::new(RwLock::new(Phrase::new( + let phrase = Arc::new(RwLock::new(MidiClip::new( "New", true, 4 * clock.timebase.ppq.get() as usize, None, Some(ItemColor::random().into()) ))); @@ -97,7 +97,7 @@ render!(|self: ArrangerTui|{ let pool_size = if self.phrases.visible { self.splits[1] } else { 0 }; let with_pool = |x|Split::left(false, pool_size, PoolView(&self.phrases), x); let status = ArrangerStatus::from(self); - let with_editbar = |x|Tui::split_n(false, 3, PhraseEditStatus(&self.editor), x); + let with_editbar = |x|Tui::split_n(false, 3, MidiEditStatus(&self.editor), x); let with_status = |x|Tui::split_n(false, 2, status, x); let with_size = |x|lay!([&self.size, x]); let arranger = ||lay!(|add|{ diff --git a/crates/tek/src/tui/app_sequencer.rs b/crates/tek/src/tui/app_sequencer.rs index 97ec300f..9657cfb2 100644 --- a/crates/tek/src/tui/app_sequencer.rs +++ b/crates/tek/src/tui/app_sequencer.rs @@ -10,7 +10,7 @@ pub struct SequencerTui { pub(crate) clock: ClockModel, pub(crate) phrases: PoolModel, pub(crate) player: MidiPlayer, - pub(crate) editor: PhraseEditorModel, + pub(crate) editor: MidiEditorModel, pub(crate) size: Measure, pub(crate) status: bool, pub(crate) note_buf: Vec, @@ -19,14 +19,14 @@ pub struct SequencerTui { } from_jack!(|jack|SequencerTui { let clock = ClockModel::from(jack); - let phrase = Arc::new(RwLock::new(Phrase::new( + let phrase = Arc::new(RwLock::new(MidiClip::new( "New", true, 4 * clock.timebase.ppq.get() as usize, None, Some(ItemColor::random().into()) ))); Self { _jack: jack.clone(), phrases: PoolModel::from(&phrase), - editor: PhraseEditorModel::from(&phrase), + editor: MidiEditorModel::from(&phrase), player: MidiPlayer::from((&clock, &phrase)), size: Measure::new(), midi_buf: vec![vec![];65536], @@ -44,7 +44,7 @@ render!(|self: SequencerTui|{ let with_pool = move|x|Tui::split_w(false, pool_w, pool, x); let status = SequencerStatus::from(self); let with_status = |x|Tui::split_n(false, if self.status { 2 } else { 0 }, status, x); - let with_editbar = |x|Tui::split_n(false, 3, PhraseEditStatus(&self.editor), x); + let with_editbar = |x|Tui::split_n(false, 3, MidiEditStatus(&self.editor), x); let with_size = |x|lay!([self.size, x]); let editor = with_editbar(with_pool(Fill::wh(&self.editor))); let color = self.player.play_phrase().as_ref().map(|(_,p)| @@ -85,7 +85,7 @@ handle!(|self:SequencerTui,input|SequencerCommand::execute_with_state(self, Clock(ClockCommand), Phrases(PoolCommand), Editor(PhraseCommand), - Enqueue(Option>>), + Enqueue(Option>>), } input_to_command!(SequencerCommand: |state: SequencerTui, input|match input.event() { // TODO: k: toggle on-screen keyboard diff --git a/crates/tek/src/tui/arranger_command.rs b/crates/tek/src/tui/arranger_command.rs index 59370065..731adc59 100644 --- a/crates/tek/src/tui/arranger_command.rs +++ b/crates/tek/src/tui/arranger_command.rs @@ -39,9 +39,9 @@ pub enum ArrangerSceneCommand { #[derive(Clone, Debug)] pub enum ArrangerClipCommand { Get(usize, usize), - Put(usize, usize, Option>>), + Put(usize, usize, Option>>), Enqueue(usize, usize), - Edit(Option>>), + Edit(Option>>), SetLoop(usize, usize, bool), SetColor(usize, usize, ItemPalette), } diff --git a/crates/tek/src/tui/arranger_scene.rs b/crates/tek/src/tui/arranger_scene.rs index 2bdb3a48..67eebc69 100644 --- a/crates/tek/src/tui/arranger_scene.rs +++ b/crates/tek/src/tui/arranger_scene.rs @@ -30,7 +30,7 @@ impl ArrangerTui { /// Name of scene pub(crate) name: Arc>, /// Clips in scene, one per track - pub(crate) clips: Vec>>>, + pub(crate) clips: Vec>>>, /// Identifying color of scene pub(crate) color: ItemPalette, } @@ -38,7 +38,7 @@ impl ArrangerScene { pub fn name (&self) -> &Arc> { &self.name } - pub fn clips (&self) -> &Vec>>> { + pub fn clips (&self) -> &Vec>>> { &self.clips } pub fn color (&self) -> ItemPalette { @@ -85,7 +85,7 @@ impl ArrangerScene { None => true }) } - pub fn clip (&self, index: usize) -> Option<&Arc>> { + pub fn clip (&self, index: usize) -> Option<&Arc>> { match self.clips().get(index) { Some(Some(clip)) => Some(clip), _ => None } } } diff --git a/crates/tek/src/tui/arranger_v/v_clips.rs b/crates/tek/src/tui/arranger_v/v_clips.rs index 41cd38e2..4aee54a9 100644 --- a/crates/tek/src/tui/arranger_v/v_clips.rs +++ b/crates/tek/src/tui/arranger_v/v_clips.rs @@ -41,7 +41,7 @@ impl<'a> ArrangerVClips<'a> { Fixed::wh(w, h, Layers::new(move |add|{ let mut bg = TuiTheme::border_bg(); if let Some(Some(phrase)) = scene.clips.get(index) { - let name = &(phrase as &Arc>).read().unwrap().name; + let name = &(phrase as &Arc>).read().unwrap().name; let name = format!("{}", name); let max_w = name.len().min((w as usize).saturating_sub(2)); let color = phrase.read().unwrap().color; diff --git a/crates/tek/src/tui/phrase_editor.rs b/crates/tek/src/tui/phrase_editor.rs index 01492997..5ec099a2 100644 --- a/crates/tek/src/tui/phrase_editor.rs +++ b/crates/tek/src/tui/phrase_editor.rs @@ -3,13 +3,13 @@ use KeyCode::{Char, Up, Down, Left, Right, Enter}; use PhraseCommand::*; pub trait HasEditor { - fn editor (&self) -> &PhraseEditorModel; + fn editor (&self) -> &MidiEditorModel; } #[macro_export] macro_rules! has_editor { (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { impl $(<$($L),*$($T $(: $U)?),*>)? HasEditor for $Struct $(<$($L),*$($T),*>)? { - fn editor (&$self) -> &PhraseEditorModel { &$cb } + fn editor (&$self) -> &MidiEditorModel { &$cb } } } } @@ -26,11 +26,11 @@ pub enum PhraseCommand { SetTimeScroll(usize), SetTimeZoom(usize), SetTimeLock(bool), - Show(Option>>), + Show(Option>>), } -event_map_input_to_command!(Tui: PhraseEditorModel: PhraseCommand: PhraseEditorModel::KEYS); +event_map_input_to_command!(Tui: MidiEditorModel: PhraseCommand: MidiEditorModel::KEYS); -impl PhraseEditorModel { +impl MidiEditorModel { const KEYS: [(TuiEvent, &'static dyn Fn(&Self)->PhraseCommand);31] = [ (kexp!(Ctrl-Alt-Up), &|s: &Self|SetNoteScroll(s.note_point() + 3)), (kexp!(Ctrl-Alt-Down), &|s: &Self|SetNoteScroll(s.note_point().saturating_sub(3))), @@ -71,8 +71,8 @@ impl PhraseEditorModel { } } -impl Command for PhraseCommand { - fn execute (self, state: &mut PhraseEditorModel) -> Perhaps { +impl Command for PhraseCommand { + fn execute (self, state: &mut MidiEditorModel) -> Perhaps { use PhraseCommand::*; match self { Show(phrase) => { state.set_phrase(phrase.as_ref()); }, @@ -92,13 +92,13 @@ impl Command for PhraseCommand { } /// Contains state for viewing and editing a phrase -pub struct PhraseEditorModel { +pub struct MidiEditorModel { /// Renders the phrase pub mode: Box, pub size: Measure } -impl Default for PhraseEditorModel { +impl Default for MidiEditorModel { fn default () -> Self { let mut mode = Box::new(PianoHorizontal::new(None)); mode.redraw(); @@ -106,28 +106,28 @@ impl Default for PhraseEditorModel { } } -has_size!(|self:PhraseEditorModel|&self.size); -render!(|self: PhraseEditorModel|{ +has_size!(|self:MidiEditorModel|&self.size); +render!(|self: MidiEditorModel|{ self.autoscroll(); self.autozoom(); &self.mode }); -//render!(|self: PhraseEditorModel|lay!(|add|{add(&self.size)?;add(self.mode)}));//bollocks +//render!(|self: MidiEditorModel|lay!(|add|{add(&self.size)?;add(self.mode)}));//bollocks pub trait PhraseViewMode: Render + HasSize + MidiRange + MidiPoint + Debug + Send + Sync { - fn buffer_size (&self, phrase: &Phrase) -> (usize, usize); + fn buffer_size (&self, phrase: &MidiClip) -> (usize, usize); fn redraw (&mut self); - fn phrase (&self) -> &Option>>; - fn phrase_mut (&mut self) -> &mut Option>>; - fn set_phrase (&mut self, phrase: Option<&Arc>>) { + fn phrase (&self) -> &Option>>; + fn phrase_mut (&mut self) -> &mut Option>>; + fn set_phrase (&mut self, phrase: Option<&Arc>>) { *self.phrase_mut() = phrase.map(|p|p.clone()); self.redraw(); } } -impl MidiView for PhraseEditorModel {} +impl MidiView for MidiEditorModel {} -impl MidiRange for PhraseEditorModel { +impl MidiRange for MidiEditorModel { fn time_len (&self) -> &AtomicUsize { self.mode.time_len() } fn time_zoom (&self) -> &AtomicUsize { self.mode.time_zoom() } fn time_lock (&self) -> &AtomicBool { self.mode.time_lock() } @@ -137,7 +137,7 @@ impl MidiRange for PhraseEditorModel { fn time_axis (&self) -> &AtomicUsize { self.mode.time_axis() } } -impl MidiPoint for PhraseEditorModel { +impl MidiPoint for MidiEditorModel { fn note_len (&self) -> usize { self.mode.note_len()} fn set_note_len (&self, x: usize) { self.mode.set_note_len(x) } fn note_point (&self) -> usize { self.mode.note_point() } @@ -146,25 +146,25 @@ impl MidiPoint for PhraseEditorModel { fn set_time_point (&self, x: usize) { self.mode.set_time_point(x) } } -impl PhraseViewMode for PhraseEditorModel { - fn buffer_size (&self, phrase: &Phrase) -> (usize, usize) { +impl PhraseViewMode for MidiEditorModel { + fn buffer_size (&self, phrase: &MidiClip) -> (usize, usize) { self.mode.buffer_size(phrase) } fn redraw (&mut self) { self.mode.redraw() } - fn phrase (&self) -> &Option>> { + fn phrase (&self) -> &Option>> { self.mode.phrase() } - fn phrase_mut (&mut self) -> &mut Option>> { + fn phrase_mut (&mut self) -> &mut Option>> { self.mode.phrase_mut() } - fn set_phrase (&mut self, phrase: Option<&Arc>>) { + fn set_phrase (&mut self, phrase: Option<&Arc>>) { self.mode.set_phrase(phrase) } } -impl PhraseEditorModel { +impl MidiEditorModel { /// Put note at current position pub fn put_note (&mut self, advance: bool) { let mut redraw = false; @@ -197,22 +197,22 @@ impl PhraseEditorModel { } } -from!(|phrase: &Arc>|PhraseEditorModel = { +from!(|phrase: &Arc>|MidiEditorModel = { let mut model = Self::from(Some(phrase.clone())); model.redraw(); model }); -from!(|phrase: Option>>|PhraseEditorModel = { +from!(|phrase: Option>>|MidiEditorModel = { let mut model = Self::default(); *model.phrase_mut() = phrase; model.redraw(); model }); -impl std::fmt::Debug for PhraseEditorModel { +impl std::fmt::Debug for MidiEditorModel { fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - f.debug_struct("PhraseEditorModel") + f.debug_struct("MidiEditorModel") .field("mode", &self.mode) .finish() } diff --git a/crates/tek/src/tui/piano_h.rs b/crates/tek/src/tui/piano_h.rs index cde739df..c8c3f774 100644 --- a/crates/tek/src/tui/piano_h.rs +++ b/crates/tek/src/tui/piano_h.rs @@ -12,7 +12,7 @@ pub(crate) fn note_y_iter (note_lo: usize, note_hi: usize, y0: u16) -> impl Iter /// A phrase, rendered as a horizontal piano roll. pub struct PianoHorizontal { - phrase: Option>>, + phrase: Option>>, /// Buffer where the whole phrase is rerendered on change buffer: BigBuffer, /// Size of actual notes area @@ -26,7 +26,7 @@ pub struct PianoHorizontal { } impl PianoHorizontal { - pub fn new (phrase: Option<&Arc>>) -> Self { + pub fn new (phrase: Option<&Arc>>) -> Self { let size = Measure::new(); let mut range = MidiRangeModel::from((24, true)); range.time_axis = size.x.clone(); @@ -64,7 +64,7 @@ render!(|self: PianoHorizontal|{ impl PianoHorizontal { /// Draw the piano roll foreground using full blocks on note on and half blocks on legato: █▄ █▄ █▄ - fn draw_bg (buf: &mut BigBuffer, phrase: &Phrase, zoom: usize, note_len: usize) { + fn draw_bg (buf: &mut BigBuffer, phrase: &MidiClip, zoom: usize, note_len: usize) { for (y, note) in (0..127).rev().enumerate() { for (x, time) in (0..buf.width).map(|x|(x, x*zoom)) { let cell = buf.get_mut(x, y).unwrap(); @@ -85,7 +85,7 @@ impl PianoHorizontal { } } /// Draw the piano roll background using full blocks on note on and half blocks on legato: █▄ █▄ █▄ - fn draw_fg (buf: &mut BigBuffer, phrase: &Phrase, zoom: usize) { + fn draw_fg (buf: &mut BigBuffer, phrase: &MidiClip, zoom: usize) { let style = Style::default().fg(phrase.color.base.rgb);//.bg(Color::Rgb(0, 0, 0)); let mut notes_on = [false;128]; for (x, time_start) in (0..phrase.length).step_by(zoom).enumerate() { @@ -142,14 +142,14 @@ impl MidiPoint for PianoHorizontal { fn set_time_point (&self, x: usize) { self.point.set_time_point(x) } } impl PhraseViewMode for PianoHorizontal { - fn phrase (&self) -> &Option>> { + fn phrase (&self) -> &Option>> { &self.phrase } - fn phrase_mut (&mut self) -> &mut Option>> { + fn phrase_mut (&mut self) -> &mut Option>> { &mut self.phrase } /// Determine the required space to render the phrase. - fn buffer_size (&self, phrase: &Phrase) -> (usize, usize) { + fn buffer_size (&self, phrase: &MidiClip) -> (usize, usize) { (phrase.length / self.range.time_zoom().get(), 128) } fn redraw (&mut self) { @@ -168,7 +168,7 @@ impl PhraseViewMode for PianoHorizontal { }; self.buffer = buffer } - fn set_phrase (&mut self, phrase: Option<&Arc>>) { + fn set_phrase (&mut self, phrase: Option<&Arc>>) { *self.phrase_mut() = phrase.map(|p|p.clone()); self.color = phrase.map(|p|p.read().unwrap().color.clone()) .unwrap_or(ItemPalette::from(ItemColor::from(TuiTheme::g(64)))); diff --git a/crates/tek/src/tui/pool.rs b/crates/tek/src/tui/pool.rs index 91a87d5f..57cff863 100644 --- a/crates/tek/src/tui/pool.rs +++ b/crates/tek/src/tui/pool.rs @@ -12,7 +12,7 @@ use FileBrowserCommand as Browse; pub struct PoolModel { pub(crate) visible: bool, /// Collection of phrases - pub(crate) phrases: Vec>>, + pub(crate) phrases: Vec>>, /// Selected phrase pub(crate) phrase: AtomicUsize, /// Mode switch @@ -149,10 +149,10 @@ fn to_phrases_command (state: &PoolModel, input: &TuiInput) -> Option Cmd::Phrase(Pool::Add(count, Phrase::new( + key_pat!(Char('a')) | key_pat!(Shift-Char('A')) => Cmd::Phrase(Pool::Add(count, MidiClip::new( String::from("(new)"), true, 4 * PPQ, None, Some(ItemPalette::random()) ))), - key_pat!(Char('i')) => Cmd::Phrase(Pool::Add(index + 1, Phrase::new( + key_pat!(Char('i')) => Cmd::Phrase(Pool::Add(index + 1, MidiClip::new( String::from("(new)"), true, 4 * PPQ, None, Some(ItemPalette::random()) ))), key_pat!(Char('d')) | key_pat!(Shift-Char('D')) => { @@ -167,7 +167,7 @@ impl Default for PoolModel { fn default () -> Self { Self { visible: true, - phrases: vec![RwLock::new(Phrase::default()).into()], + phrases: vec![RwLock::new(MidiClip::default()).into()], phrase: 0.into(), scroll: 0, mode: None, @@ -175,7 +175,7 @@ impl Default for PoolModel { } } } -from!(|phrase:&Arc>|PoolModel = { +from!(|phrase:&Arc>|PoolModel = { let mut model = Self::default(); model.phrases.push(phrase.clone()); model.phrase.store(1, Relaxed); @@ -214,7 +214,7 @@ render!(|self: PoolView<'a>|{ Some(PoolMode::Export(_, ref file_picker)) => add(file_picker), _ => Ok(for (i, phrase) in phrases.iter().enumerate() { add(&lay!(|add|{ - let Phrase { ref name, color, length, .. } = *phrase.read().unwrap(); + let MidiClip { ref name, color, length, .. } = *phrase.read().unwrap(); let mut length = PhraseLength::new(length, None); if let Some(PoolMode::Length(phrase, new_length, focus)) = mode { if i == *phrase { @@ -267,7 +267,7 @@ impl PhraseSelector { // beats elapsed pub fn play_phrase (state: &T) -> Self { let (name, color) = if let Some((_, Some(phrase))) = state.play_phrase() { - let Phrase { ref name, color, .. } = *phrase.read().unwrap(); + let MidiClip { ref name, color, .. } = *phrase.read().unwrap(); (name.clone(), color) } else { ("".to_string(), ItemPalette::from(TuiTheme::g(64))) @@ -282,7 +282,7 @@ impl PhraseSelector { // beats until switchover pub fn next_phrase (state: &T) -> Self { let (time, name, color) = if let Some((t, Some(phrase))) = state.next_phrase() { - let Phrase { ref name, color, .. } = *phrase.read().unwrap(); + let MidiClip { ref name, color, .. } = *phrase.read().unwrap(); let time = { let target = t.pulse.get(); let current = state.clock().playhead.pulse.get(); diff --git a/crates/tek/src/tui/status/status_edit.rs b/crates/tek/src/tui/status/status_edit.rs index c45103d8..43263565 100644 --- a/crates/tek/src/tui/status/status_edit.rs +++ b/crates/tek/src/tui/status/status_edit.rs @@ -1,7 +1,7 @@ use crate::*; -pub struct PhraseEditStatus<'a>(pub &'a PhraseEditorModel); -render!(|self:PhraseEditStatus<'a>|{ +pub struct MidiEditStatus<'a>(pub &'a MidiEditorModel); +render!(|self:MidiEditStatus<'a>|{ let (color, name, length, looped) = if let Some(phrase) = self.0.phrase().as_ref().map(|p|p.read().unwrap()) { (phrase.color, phrase.name.clone(), phrase.length, phrase.looped) } else { From 7962bdf86b16d2e5bd676284f8e094ad5ac7a785 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 27 Dec 2024 12:49:03 +0100 Subject: [PATCH 019/815] fix some more warnings --- crates/tek/src/cli/cli_arranger.rs | 2 +- crates/tek/src/cli/cli_groovebox.rs | 6 ++++-- crates/tek/src/cli/cli_sampler.rs | 3 ++- crates/tek/src/cli/cli_sequencer.rs | 3 +-- crates/tek/src/cli/cli_transport.rs | 3 ++- crates/tek/src/jack.rs | 2 +- crates/tek/src/lib.rs | 3 ++- crates/tek/src/space/push_pull.rs | 1 - crates/tek/src/space/shrink_grow.rs | 1 - crates/tek/src/tui.rs | 2 +- crates/tek/src/tui/arranger_v/v_clips.rs | 3 +-- crates/tek/src/tui/piano_h/piano_h_time.rs | 1 - 12 files changed, 15 insertions(+), 15 deletions(-) diff --git a/crates/tek/src/cli/cli_arranger.rs b/crates/tek/src/cli/cli_arranger.rs index b7f3ceb9..796f3ad9 100644 --- a/crates/tek/src/cli/cli_arranger.rs +++ b/crates/tek/src/cli/cli_arranger.rs @@ -1,5 +1,5 @@ +#![allow(unused)] #![allow(clippy::unit_arg)] - include!("../lib.rs"); pub fn main () -> Usually<()> { diff --git a/crates/tek/src/cli/cli_groovebox.rs b/crates/tek/src/cli/cli_groovebox.rs index 77bac1bc..d3339871 100644 --- a/crates/tek/src/cli/cli_groovebox.rs +++ b/crates/tek/src/cli/cli_groovebox.rs @@ -1,3 +1,5 @@ +#![allow(unused)] +#![allow(clippy::unit_arg)] include!("../lib.rs"); pub fn main () -> Usually<()> { GrooveboxCli::parse().run() @@ -10,14 +12,14 @@ pub struct GrooveboxCli; impl GrooveboxCli { fn run (&self) -> Usually<()> { Tui::run(JackClient::new("tek_groovebox")?.activate_with(|jack|{ - let midi_in_1 = jack.read().unwrap().register_port("in1", MidiIn::default())?; + let app = GrooveboxTui::try_from(jack)?; let midi_out = jack.read().unwrap().register_port("out", MidiOut::default())?; + let midi_in_1 = jack.read().unwrap().register_port("in1", MidiIn::default())?; let midi_in_2 = jack.read().unwrap().register_port("in2", MidiIn::default())?; let audio_in_1 = jack.read().unwrap().register_port("inL", AudioIn::default())?; let audio_in_2 = jack.read().unwrap().register_port("inR", AudioIn::default())?; let audio_out_1 = jack.read().unwrap().register_port("out1", AudioOut::default())?; let audio_out_2 = jack.read().unwrap().register_port("out2", AudioOut::default())?; - let mut app = GrooveboxTui::try_from(jack)?; Ok(app) })?)?; Ok(()) diff --git a/crates/tek/src/cli/cli_sampler.rs b/crates/tek/src/cli/cli_sampler.rs index 4b882fb9..aeda9da0 100644 --- a/crates/tek/src/cli/cli_sampler.rs +++ b/crates/tek/src/cli/cli_sampler.rs @@ -1,5 +1,6 @@ +#![allow(unused)] +#![allow(clippy::unit_arg)] include!("../lib.rs"); - pub fn main () -> Usually<()> { SamplerCli::parse().run() } diff --git a/crates/tek/src/cli/cli_sequencer.rs b/crates/tek/src/cli/cli_sequencer.rs index c44cc30f..16b9cfde 100644 --- a/crates/tek/src/cli/cli_sequencer.rs +++ b/crates/tek/src/cli/cli_sequencer.rs @@ -1,7 +1,6 @@ +#![allow(unused)] #![allow(clippy::unit_arg)] - include!("../lib.rs"); - pub fn main () -> Usually<()> { SequencerCli::parse().run() } diff --git a/crates/tek/src/cli/cli_transport.rs b/crates/tek/src/cli/cli_transport.rs index 68f47c94..68fb9c7c 100644 --- a/crates/tek/src/cli/cli_transport.rs +++ b/crates/tek/src/cli/cli_transport.rs @@ -1,5 +1,6 @@ +#![allow(unused)] +#![allow(clippy::unit_arg)] include!("../lib.rs"); - /// Application entrypoint. pub fn main () -> Usually<()> { Tui::run(JackClient::new("tek_transport")?.activate_with(|jack|{ diff --git a/crates/tek/src/jack.rs b/crates/tek/src/jack.rs index 7dbd2c08..ee6f698e 100644 --- a/crates/tek/src/jack.rs +++ b/crates/tek/src/jack.rs @@ -1,5 +1,5 @@ pub use ::jack as libjack; -pub(crate) mod activate; pub(crate) use self::activate::*; +pub(crate) mod activate; #[allow(unused)] pub(crate) use self::activate::*; pub(crate) mod audio; pub(crate) use self::audio::*; pub(crate) mod client; pub(crate) use self::client::*; pub(crate) mod jack_event; pub(crate) use self::jack_event::*; diff --git a/crates/tek/src/lib.rs b/crates/tek/src/lib.rs index 5976df1c..0ab08cec 100644 --- a/crates/tek/src/lib.rs +++ b/crates/tek/src/lib.rs @@ -19,7 +19,8 @@ pub use ::atomic_float; pub(crate) use atomic_float::*; pub(crate) use std::sync::{Arc, Mutex, RwLock}; -pub(crate) use std::sync::atomic::{Ordering, AtomicBool, AtomicUsize}; +#[allow(unused)] pub(crate) use std::sync::atomic::Ordering; +pub(crate) use std::sync::atomic::{AtomicBool, AtomicUsize}; pub(crate) use std::collections::BTreeMap; pub(crate) use std::marker::PhantomData; pub(crate) use std::thread::{spawn, JoinHandle}; diff --git a/crates/tek/src/space/push_pull.rs b/crates/tek/src/space/push_pull.rs index 2b83bd0e..0cd5ef67 100644 --- a/crates/tek/src/space/push_pull.rs +++ b/crates/tek/src/space/push_pull.rs @@ -68,7 +68,6 @@ impl> Render for Push { Self::X(x, _) => [area.x() + x, area.y(), size.w(), size.h()], Self::Y(y, _) => [area.x(), area.y() + y, size.w(), size.h()], Self::XY(x, y, _) => [area.x() + x, area.y() + y, size.w(), size.h()], - _ => unreachable!(), }.into(), self.inner())).transpose()?.unwrap_or(())) } } diff --git a/crates/tek/src/space/shrink_grow.rs b/crates/tek/src/space/shrink_grow.rs index 9c725ef4..9d900843 100644 --- a/crates/tek/src/space/shrink_grow.rs +++ b/crates/tek/src/space/shrink_grow.rs @@ -49,7 +49,6 @@ impl> Shrink { Self::X(_, i) => i, Self::Y(_, i) => i, Self::XY(_, _, i) => i, - _ => unreachable!(), } } } diff --git a/crates/tek/src/tui.rs b/crates/tek/src/tui.rs index 31a0d242..1f855846 100644 --- a/crates/tek/src/tui.rs +++ b/crates/tek/src/tui.rs @@ -27,7 +27,7 @@ mod arranger_scene; pub(crate) use self::arranger_scene::*; mod arranger_select; pub(crate) use self::arranger_select::*; mod arranger_track; pub(crate) use self::arranger_track::*; mod arranger_mode; pub(crate) use self::arranger_mode::*; -mod arranger_v; pub(crate) use self::arranger_v::*; +mod arranger_v; #[allow(unused)] pub(crate) use self::arranger_v::*; mod arranger_h; //////////////////////////////////////////////////////// diff --git a/crates/tek/src/tui/arranger_v/v_clips.rs b/crates/tek/src/tui/arranger_v/v_clips.rs index 4aee54a9..ad8568e2 100644 --- a/crates/tek/src/tui/arranger_v/v_clips.rs +++ b/crates/tek/src/tui/arranger_v/v_clips.rs @@ -39,8 +39,8 @@ impl<'a> ArrangerVClips<'a> { scene: &'a ArrangerScene, index: usize, track: &'a ArrangerTrack, w: u16, h: u16 ) -> impl Render + use<'a> { Fixed::wh(w, h, Layers::new(move |add|{ - let mut bg = TuiTheme::border_bg(); if let Some(Some(phrase)) = scene.clips.get(index) { + let mut bg = TuiTheme::border_bg(); let name = &(phrase as &Arc>).read().unwrap().name; let name = format!("{}", name); let max_w = name.len().min((w as usize).saturating_sub(2)); @@ -55,7 +55,6 @@ impl<'a> ArrangerVClips<'a> { Tui::push_x(1, Fixed::w(w as u16, &name.as_str()[0..max_w]))) )?; } - //add(&Background(bg)) Ok(()) })) } diff --git a/crates/tek/src/tui/piano_h/piano_h_time.rs b/crates/tek/src/tui/piano_h/piano_h_time.rs index 6c2d4ec3..e90d9e7c 100644 --- a/crates/tek/src/tui/piano_h/piano_h_time.rs +++ b/crates/tek/src/tui/piano_h/piano_h_time.rs @@ -1,5 +1,4 @@ use crate::*; -use super::note_y_iter; pub struct PianoHorizontalTimeline<'a>(pub(crate) &'a PianoHorizontal); render!(|self: PianoHorizontalTimeline<'a>|render(|to|{ From e96faeb6d3f8d5cbe29669cdbe6f1be4882fd61e Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 27 Dec 2024 13:18:00 +0100 Subject: [PATCH 020/815] fix some lints, add FromEdn trait --- crates/tek/src/audio/sampler.rs | 16 +-- crates/tek/src/cli/cli_arranger.rs | 11 +- crates/tek/src/cli/cli_sequencer.rs | 19 +-- crates/tek/src/edn.rs | 158 +++++++++++++----------- crates/tek/src/midi.rs | 4 +- crates/tek/src/midi/midi_clip.rs | 3 +- crates/tek/src/midi/midi_in.rs | 2 +- crates/tek/src/midi/midi_launch.rs | 5 +- crates/tek/src/midi/midi_out.rs | 2 +- crates/tek/src/midi/midi_play.rs | 13 +- crates/tek/src/midi/midi_rec.rs | 7 +- crates/tek/src/space/coord.rs | 2 +- crates/tek/src/space/stack.rs | 6 +- crates/tek/src/tui/arranger_v/v_head.rs | 2 +- 14 files changed, 126 insertions(+), 124 deletions(-) diff --git a/crates/tek/src/audio/sampler.rs b/crates/tek/src/audio/sampler.rs index c81e28cc..c9052520 100644 --- a/crates/tek/src/audio/sampler.rs +++ b/crates/tek/src/audio/sampler.rs @@ -50,11 +50,11 @@ impl Sampler { pub fn process_midi_in (&mut self, scope: &ProcessScope) { let Sampler { midi_in, mapped, voices, .. } = self; for RawMidi { time, bytes } in midi_in.iter(scope) { - if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() { - if let MidiMessage::NoteOn { ref key, ref vel } = message { - if let Some(sample) = mapped.get(key) { - voices.write().unwrap().push(Sample::play(sample, time as usize, vel)); - } + if let LiveEvent::Midi { + message: MidiMessage::NoteOn { ref key, ref vel }, .. + } = LiveEvent::parse(bytes).unwrap() { + if let Some(sample) = mapped.get(key) { + voices.write().unwrap().push(Sample::play(sample, time as usize, vel)); } } } @@ -85,7 +85,7 @@ impl Sampler { return false } } - return true + true }); } @@ -135,13 +135,13 @@ impl Iterator for Voice { type Item = [f32;2]; fn next (&mut self) -> Option { if self.after > 0 { - self.after = self.after - 1; + self.after -= 1; return Some([0.0, 0.0]) } let sample = self.sample.read().unwrap(); if self.position < sample.end { let position = self.position; - self.position = self.position + 1; + self.position += 1; return sample.channels[0].get(position).map(|_amplitude|[ sample.channels[0][position] * self.velocity, sample.channels[0][position] * self.velocity, diff --git a/crates/tek/src/cli/cli_arranger.rs b/crates/tek/src/cli/cli_arranger.rs index 796f3ad9..57697699 100644 --- a/crates/tek/src/cli/cli_arranger.rs +++ b/crates/tek/src/cli/cli_arranger.rs @@ -46,7 +46,7 @@ impl ArrangerCli { let mut app = ArrangerTui::try_from(jack)?; let jack = jack.read().unwrap(); app.color = ItemPalette::random(); - add_tracks(&jack, &mut app, &self)?; + add_tracks(&jack, &mut app, self)?; add_scenes(&mut app, self.scenes)?; Ok(app) })?)?; @@ -82,9 +82,8 @@ fn add_tracks (jack: &JackClient, app: &mut ArrangerTui, cli: &ArrangerCli) -> U panic!("Tried to connect track {track} or {n}. Pass -t {track} to increase number of tracks.") } if let Some(port) = split.next() { - if let Some(port) = jack.port_by_name(&port) { - jack.client().connect_ports(&port, &app.tracks[track-1].player.midi_ins[0])?; - //jack.client().connect_ports(&port, &app.tracks[track].player.midi_ins[0])?; + if let Some(port) = jack.port_by_name(port).as_ref() { + jack.client().connect_ports(port, &app.tracks[track-1].player.midi_ins[0])?; } else { panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); } @@ -106,8 +105,8 @@ fn add_tracks (jack: &JackClient, app: &mut ArrangerTui, cli: &ArrangerCli) -> U panic!("Tried to connect track {track} or {n}. Pass -t {track} to increase number of tracks.") } if let Some(port) = split.next() { - if let Some(port) = jack.port_by_name(&port) { - jack.client().connect_ports(&app.tracks[track-1].player.midi_outs[0], &port)?; + if let Some(port) = jack.port_by_name(port).as_ref() { + jack.client().connect_ports(&app.tracks[track-1].player.midi_outs[0], port)?; } else { panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); } diff --git a/crates/tek/src/cli/cli_sequencer.rs b/crates/tek/src/cli/cli_sequencer.rs index 16b9cfde..619f9381 100644 --- a/crates/tek/src/cli/cli_sequencer.rs +++ b/crates/tek/src/cli/cli_sequencer.rs @@ -24,15 +24,11 @@ pub struct SequencerCli { /// MIDI ins to connect to (multiple instances accepted) #[arg(short='o', long)] midi_to: Vec, - - /// Default phrase duration (in pulses; default: 4 * PPQ = 1 bar) - #[arg(short, long)] - length: Option, } impl SequencerCli { fn run (&self) -> Usually<()> { - let name = self.name.as_ref().map(|n|n.as_str()).unwrap_or("tek_sequencer"); + let name = self.name.as_deref().unwrap_or("tek_sequencer"); Tui::run(JackClient::new(name)?.activate_with(|jack|{ let mut app = SequencerTui::try_from(jack)?; let jack = jack.read().unwrap(); @@ -42,11 +38,6 @@ impl SequencerCli { connect_to(&jack, &midi_out, &self.midi_to)?; app.player.midi_ins.push(midi_in); app.player.midi_outs.push(midi_out); - if let Some(_) = self.length { - // TODO: if let Some(phrase) = sequencer.phrase.as_mut() { - //phrase.write().unwrap().length = length; - //} - } Ok(app) })?)?; Ok(()) @@ -55,8 +46,8 @@ impl SequencerCli { fn connect_from (jack: &JackClient, input: &Port, ports: &[String]) -> Usually<()> { for port in ports.iter() { - if let Some(port) = jack.port_by_name(&port) { - jack.client().connect_ports(&port, &input)?; + if let Some(port) = jack.port_by_name(port).as_ref() { + jack.client().connect_ports(port, input)?; } else { panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); } @@ -66,8 +57,8 @@ fn connect_from (jack: &JackClient, input: &Port, ports: &[String]) -> U fn connect_to (jack: &JackClient, output: &Port, ports: &[String]) -> Usually<()> { for port in ports.iter() { - if let Some(port) = jack.port_by_name(&port) { - jack.client().connect_ports(&output, &port)?; + if let Some(port) = jack.port_by_name(port).as_ref() { + jack.client().connect_ports(output, port)?; } else { panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); } diff --git a/crates/tek/src/edn.rs b/crates/tek/src/edn.rs index eb4992bb..a3a92fb2 100644 --- a/crates/tek/src/edn.rs +++ b/crates/tek/src/edn.rs @@ -4,6 +4,23 @@ use crate::*; pub use clojure_reader::edn::Edn; //pub use clojure_reader::{edn::{read, Edn}, error::Error as EdnError}; +pub trait FromEdn: Sized { + const ID: &'static str; + fn from_edn <'e> (context: C, expr: &[Edn<'e>]) -> Usually; +} + +/// Implements the [FromEdn] trait. +#[macro_export] macro_rules! from_edn { + (|$context:pat = $Context:ty, $id:expr, $args:ident| -> $T:ty $body:block) => { + impl FromEdn<$Context> for $T { + const ID: &'static str = $id; + fn from_edn <'e> ($context: $Context, $args: &[Edn<'e>]) -> Usually { + $body + } + } + } +} + /// EDN parsing helper. #[macro_export] macro_rules! edn { ($edn:ident { $($pat:pat => $expr:expr),* $(,)? }) => { @@ -16,82 +33,79 @@ pub use clojure_reader::edn::Edn; }; } -impl crate::Sampler { - pub fn from_edn <'e> (jack: &Arc>, args: &[Edn<'e>]) -> Usually { - let mut name = String::new(); - let mut dir = String::new(); - let mut samples = BTreeMap::new(); - 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::Str(n)) = map.get(&Edn::Key(":dir")) { - dir = String::from(*n); +from_edn!(|jack = &Arc>, "sampler", args| -> crate::Sampler { + let mut name = String::new(); + let mut dir = String::new(); + let mut samples = BTreeMap::new(); + 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::Str(n)) = map.get(&Edn::Key(":dir")) { + dir = String::from(*n); + } + }, + Edn::List(args) => match args.get(0) { + Some(Edn::Symbol("sample")) => { + let (midi, sample) = MidiSample::from_edn((jack, &dir), &args[1..])?; + if let Some(midi) = midi { + samples.insert(midi, sample); + } else { + panic!("sample without midi binding: {}", sample.read().unwrap().name); } }, - Edn::List(args) => match args.get(0) { - Some(Edn::Symbol("sample")) => { - let (midi, sample) = Sample::from_edn(jack, &dir, &args[1..])?; - if let Some(midi) = midi { - samples.insert(midi, sample); - } else { - panic!("sample without midi binding: {}", sample.read().unwrap().name); - } - }, - _ => panic!("unexpected in sampler {name}: {args:?}") - }, - _ => panic!("unexpected in sampler {name}: {edn:?}") - }); - let midi_in = jack.read().unwrap().client().register_port("in", MidiIn::default())?; - Ok(Sampler { - jack: jack.clone(), - name: name.into(), - mapped: samples, - unmapped: Default::default(), - voices: Default::default(), - buffer: Default::default(), - audio_outs: vec![], - output_gain: 0., - midi_in, - }) - } -} + _ => panic!("unexpected in sampler {name}: {args:?}") + }, + _ => panic!("unexpected in sampler {name}: {edn:?}") + }); + let midi_in = jack.read().unwrap().client().register_port("in", MidiIn::default())?; + Ok(Self { + jack: jack.clone(), + name: name.into(), + mapped: samples, + unmapped: Default::default(), + voices: Default::default(), + buffer: Default::default(), + audio_outs: vec![], + output_gain: 0., + midi_in, + }) +}); -impl crate::Sample { - pub fn from_edn <'e> (jack: &Arc>, dir: &str, args: &[Edn<'e>]) -> Usually<(Option, Arc>)> { - let mut name = String::new(); - let mut file = String::new(); - let mut midi = None; - let mut start = 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::Str(f)) = map.get(&Edn::Key(":file")) { - file = String::from(*f); - } - if let Some(Edn::Int(i)) = map.get(&Edn::Key(":start")) { - start = *i as usize; - } - if let Some(Edn::Int(m)) = map.get(&Edn::Key(":midi")) { - midi = Some(u7::from(*m as u8)); - } - }, - _ => panic!("unexpected in sample {name}"), - }); - let (end, data) = Sample::read_data(&format!("{dir}/{file}"))?; - Ok((midi, Arc::new(RwLock::new(Self { - name: name.into(), - start, - end, - channels: data, - rate: None - })))) - } -} +type MidiSample = (Option, Arc>); +from_edn!(|(jack, dir) = (&Arc>, &str), "sample", args| -> MidiSample { + let mut name = String::new(); + let mut file = String::new(); + let mut midi = None; + let mut start = 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::Str(f)) = map.get(&Edn::Key(":file")) { + file = String::from(*f); + } + if let Some(Edn::Int(i)) = map.get(&Edn::Key(":start")) { + start = *i as usize; + } + if let Some(Edn::Int(m)) = map.get(&Edn::Key(":midi")) { + midi = Some(u7::from(*m as u8)); + } + }, + _ => panic!("unexpected in sample {name}"), + }); + let (end, data) = Sample::read_data(&format!("{dir}/{file}"))?; + Ok((midi, Arc::new(RwLock::new(crate::Sample { + name, + start, + end, + channels: data, + rate: None + })))) +}); //impl ArrangerScene { diff --git a/crates/tek/src/midi.rs b/crates/tek/src/midi.rs index 4ca9200b..73379924 100644 --- a/crates/tek/src/midi.rs +++ b/crates/tek/src/midi.rs @@ -165,7 +165,7 @@ pub struct PlayerAudio<'a, T: MidiPlayerApi>( ); /// JACK process callback for a sequencer's phrase player/recorder. -impl<'a, T: MidiPlayerApi> Audio for PlayerAudio<'a, T> { +impl Audio for PlayerAudio<'_, T> { fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { let model = &mut self.0; let note_buf = &mut self.1; @@ -217,7 +217,7 @@ impl MidiRecordApi for MidiPlayer { impl MidiPlaybackApi for MidiPlayer { fn notes_out (&self) -> &Arc> { - &self.notes_in + &self.notes_out } } diff --git a/crates/tek/src/midi/midi_clip.rs b/crates/tek/src/midi/midi_clip.rs index 2ee1c863..3960dc0c 100644 --- a/crates/tek/src/midi/midi_clip.rs +++ b/crates/tek/src/midi/midi_clip.rs @@ -76,13 +76,12 @@ impl MidiClip { } /// Check if a range `start..end` contains MIDI Note On `k` pub fn contains_note_on (&self, k: u7, start: usize, end: usize) -> bool { - //panic!("{:?} {start} {end}", &self); for events in self.notes[start.max(0)..end.min(self.notes.len())].iter() { for event in events.iter() { if let MidiMessage::NoteOn {key,..} = event { if *key == k { return true } } } } - return false + false } } diff --git a/crates/tek/src/midi/midi_in.rs b/crates/tek/src/midi/midi_in.rs index 4750ca9e..29e16946 100644 --- a/crates/tek/src/midi/midi_in.rs +++ b/crates/tek/src/midi/midi_in.rs @@ -5,6 +5,6 @@ pub trait HasMidiIns { fn midi_ins (&self) -> &Vec>; fn midi_ins_mut (&mut self) -> &mut Vec>; fn has_midi_ins (&self) -> bool { - self.midi_ins().len() > 0 + !self.midi_ins().is_empty() } } diff --git a/crates/tek/src/midi/midi_launch.rs b/crates/tek/src/midi/midi_launch.rs index 2acabd4a..49ec833e 100644 --- a/crates/tek/src/midi/midi_launch.rs +++ b/crates/tek/src/midi/midi_launch.rs @@ -27,9 +27,8 @@ pub trait HasPlayPhrase: HasClock { } fn enqueue_next (&mut self, phrase: Option<&Arc>>) { let start = self.clock().next_launch_pulse() as f64; - let instant = Moment::from_pulse(&self.clock().timebase(), start); - let phrase = phrase.map(|p|p.clone()); - *self.next_phrase_mut() = Some((instant, phrase)); + let instant = Moment::from_pulse(self.clock().timebase(), start); + *self.next_phrase_mut() = Some((instant, phrase.cloned())); *self.reset_mut() = true; } } diff --git a/crates/tek/src/midi/midi_out.rs b/crates/tek/src/midi/midi_out.rs index 0010ef4d..ca9d34c8 100644 --- a/crates/tek/src/midi/midi_out.rs +++ b/crates/tek/src/midi/midi_out.rs @@ -5,7 +5,7 @@ pub trait HasMidiOuts { fn midi_outs (&self) -> &Vec>; fn midi_outs_mut (&mut self) -> &mut Vec>; fn has_midi_outs (&self) -> bool { - self.midi_outs().len() > 0 + !self.midi_outs().is_empty() } /// Buffer for serializing a MIDI event. FIXME rename fn midi_note (&mut self) -> &mut Vec; diff --git a/crates/tek/src/midi/midi_play.rs b/crates/tek/src/midi/midi_play.rs index 5452c7c0..caa71c38 100644 --- a/crates/tek/src/midi/midi_play.rs +++ b/crates/tek/src/midi/midi_play.rs @@ -49,7 +49,7 @@ pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts { // Samples elapsed since phrase was supposed to start let skipped = sample0 - start; // Switch over to enqueued phrase - let started = Moment::from_sample(&self.clock().timebase(), start as f64); + let started = Moment::from_sample(self.clock().timebase(), start as f64); // Launch enqueued phrase *self.play_phrase_mut() = Some((started, phrase.clone())); // Unset enqueuement (TODO: where to implement looping?) @@ -123,7 +123,7 @@ pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts { // Append serialized message to output buffer. out[sample].push(note_buf.clone()); // Update the list of currently held notes. - update_keys(&mut*notes, &message); + update_keys(&mut*notes, message); } } } @@ -138,10 +138,11 @@ pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts { /// Write a chunk of MIDI data from the output buffer to an output port. fn write_port (writer: &mut MidiWriter, samples: usize, out: &[Vec>]) { - for time in 0..samples { - for event in out[time].iter() { - writer.write(&RawMidi { time: time as u32, bytes: &event }) - .expect(&format!("{event:?}")); + for (time, events) in out.iter().enumerate().take(samples) { + for bytes in events.iter() { + writer.write(&RawMidi { time: time as u32, bytes }).unwrap_or_else(|_|{ + panic!("Failed to write MIDI data: {bytes:?}"); + }); } } } diff --git a/crates/tek/src/midi/midi_rec.rs b/crates/tek/src/midi/midi_rec.rs index 22a58f74..6ae97d22 100644 --- a/crates/tek/src/midi/midi_rec.rs +++ b/crates/tek/src/midi/midi_rec.rs @@ -33,12 +33,11 @@ pub trait MidiRecordApi: HasClock + HasPlayPhrase + HasMidiIns { let sample = (sample0 + sample - start) as f64; let pulse = timebase.samples_to_pulse(sample); let quantized = (pulse / quant).round() * quant; - let looped = quantized as usize % length; - looped + quantized as usize % length }, message); } } - update_keys(&mut*notes_in.write().unwrap(), &message); + update_keys(&mut notes_in.write().unwrap(), &message); } } } @@ -61,7 +60,7 @@ pub trait MidiRecordApi: HasClock + HasPlayPhrase + HasMidiIns { for (sample, event, bytes) in parse_midi_input(input.iter(scope)) { if let LiveEvent::Midi { message, .. } = event { midi_buf[sample].push(bytes.to_vec()); - update_keys(&mut*notes_in.write().unwrap(), &message); + update_keys(&mut notes_in.write().unwrap(), &message); } } } diff --git a/crates/tek/src/space/coord.rs b/crates/tek/src/space/coord.rs index 7feac7dc..67c8ddcf 100644 --- a/crates/tek/src/space/coord.rs +++ b/crates/tek/src/space/coord.rs @@ -19,7 +19,7 @@ pub trait Coordinate: Send + Sync + Copy 0.into() } } - fn ZERO () -> Self { + fn zero () -> Self { 0.into() } } diff --git a/crates/tek/src/space/stack.rs b/crates/tek/src/space/stack.rs index 914217d9..79fb643d 100644 --- a/crates/tek/src/space/stack.rs +++ b/crates/tek/src/space/stack.rs @@ -80,7 +80,7 @@ where let mut h: E::Unit = 0.into(); (self.0)(&mut |component: &dyn Render| { let max = to.h().minus(h); - if max > E::Unit::ZERO() { + if max > E::Unit::zero() { let item = E::max_y(max, E::push_y(h, component)); let size = item.min_size(to)?.map(|size|size.wh()); if let Some([width, height]) = size { @@ -98,7 +98,7 @@ where let mut h: E::Unit = 0.into(); (self.0)(&mut |component: &dyn Render| { let max = to.w().minus(w); - if max > E::Unit::ZERO() { + if max > E::Unit::zero() { let item = E::max_x(max, E::push_x(h, component)); let size = item.min_size(to)?.map(|size|size.wh()); if let Some([width, height]) = size { @@ -116,7 +116,7 @@ where let mut h: E::Unit = 0.into(); (self.0)(&mut |component: &dyn Render| { let max = to.h().minus(h); - if max > E::Unit::ZERO() { + if max > E::Unit::zero() { let item = E::max_y(to.h() - h, component); let size = item.min_size(to)?.map(|size|size.wh()); if let Some([width, height]) = size { diff --git a/crates/tek/src/tui/arranger_v/v_head.rs b/crates/tek/src/tui/arranger_v/v_head.rs index d2e7e812..fba00caf 100644 --- a/crates/tek/src/tui/arranger_v/v_head.rs +++ b/crates/tek/src/tui/arranger_v/v_head.rs @@ -37,7 +37,7 @@ render!(|self: ArrangerVHead<'a>|Tui::push_x(self.scenes_w, row!( impl<'a> ArrangerVHead<'a> { /// name and width of track - fn format_name (track: &ArrangerTrack, w: usize) -> impl Render { + fn format_name (track: &ArrangerTrack, _w: usize) -> impl Render { let name = track.name().read().unwrap().clone(); Tui::bold(true, Tui::fg(track.color.lightest.rgb, name)) } From 96f360791bcaca9325b751e4ee2895fb440e7671 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 27 Dec 2024 13:43:48 +0100 Subject: [PATCH 021/815] tons more lint fixes --- crates/tek/src/edn.rs | 6 +- crates/tek/src/midi/midi_note.rs | 6 +- crates/tek/src/midi/midi_play.rs | 2 +- crates/tek/src/midi/midi_rec.rs | 2 +- crates/tek/src/tui.rs | 2 +- crates/tek/src/tui/app_groovebox.rs | 12 +-- crates/tek/src/tui/app_sampler.rs | 102 +++++++++--------- crates/tek/src/tui/arranger_command.rs | 8 +- crates/tek/src/tui/arranger_mode.rs | 2 +- crates/tek/src/tui/arranger_scene.rs | 8 +- crates/tek/src/tui/arranger_select.rs | 24 ++--- crates/tek/src/tui/arranger_track.rs | 4 +- crates/tek/src/tui/arranger_v/v_clips.rs | 13 +-- crates/tek/src/tui/arranger_v/v_head.rs | 10 +- crates/tek/src/tui/phrase_editor.rs | 6 +- crates/tek/src/tui/piano_h.rs | 14 +-- crates/tek/src/tui/piano_h/piano_h_cursor.rs | 2 +- crates/tek/src/tui/pool.rs | 2 +- crates/tek/src/tui/pool/phrase_rename.rs | 2 +- crates/tek/src/tui/status/status_arranger.rs | 4 +- crates/tek/src/tui/status/status_edit.rs | 6 +- crates/tek/src/tui/status/status_sequencer.rs | 4 +- crates/tek/src/tui/tui_input.rs | 2 + 23 files changed, 121 insertions(+), 122 deletions(-) diff --git a/crates/tek/src/edn.rs b/crates/tek/src/edn.rs index a3a92fb2..c60ba9ca 100644 --- a/crates/tek/src/edn.rs +++ b/crates/tek/src/edn.rs @@ -6,7 +6,7 @@ pub use clojure_reader::edn::Edn; pub trait FromEdn: Sized { const ID: &'static str; - fn from_edn <'e> (context: C, expr: &[Edn<'e>]) -> Usually; + fn from_edn (context: C, expr: &[Edn<'_>]) -> Usually; } /// Implements the [FromEdn] trait. @@ -46,7 +46,7 @@ from_edn!(|jack = &Arc>, "sampler", args| -> crate::Sampler { dir = String::from(*n); } }, - Edn::List(args) => match args.get(0) { + Edn::List(args) => match args.first() { Some(Edn::Symbol("sample")) => { let (midi, sample) = MidiSample::from_edn((jack, &dir), &args[1..])?; if let Some(midi) = midi { @@ -62,7 +62,6 @@ from_edn!(|jack = &Arc>, "sampler", args| -> crate::Sampler { let midi_in = jack.read().unwrap().client().register_port("in", MidiIn::default())?; Ok(Self { jack: jack.clone(), - name: name.into(), mapped: samples, unmapped: Default::default(), voices: Default::default(), @@ -70,6 +69,7 @@ from_edn!(|jack = &Arc>, "sampler", args| -> crate::Sampler { audio_outs: vec![], output_gain: 0., midi_in, + name, }) }); diff --git a/crates/tek/src/midi/midi_note.rs b/crates/tek/src/midi/midi_note.rs index d34226c6..71b90a45 100644 --- a/crates/tek/src/midi/midi_note.rs +++ b/crates/tek/src/midi/midi_note.rs @@ -46,9 +46,9 @@ pub trait MidiView: MidiRange + MidiPoint + HasSize { } /// Make sure range is within display fn autozoom (&self) { - let time_len = self.time_len().get(); - let time_axis = self.time_axis().get(); - let mut time_zoom = self.time_zoom().get(); + let time_len = self.time_len().get(); + let time_axis = self.time_axis().get(); + let time_zoom = self.time_zoom().get(); //while time_len.div_ceil(time_zoom) > time_axis { //println!("\r{time_len} {time_zoom} {time_axis}"); //time_zoom = Note::next(time_zoom); diff --git a/crates/tek/src/midi/midi_play.rs b/crates/tek/src/midi/midi_play.rs index caa71c38..dde2c05f 100644 --- a/crates/tek/src/midi/midi_play.rs +++ b/crates/tek/src/midi/midi_play.rs @@ -47,7 +47,7 @@ pub trait MidiPlaybackApi: HasPlayPhrase + HasClock + HasMidiOuts { // If it's time to switch to the next phrase: if start <= sample0.saturating_sub(sample) { // Samples elapsed since phrase was supposed to start - let skipped = sample0 - start; + let _skipped = sample0 - start; // Switch over to enqueued phrase let started = Moment::from_sample(self.clock().timebase(), start as f64); // Launch enqueued phrase diff --git a/crates/tek/src/midi/midi_rec.rs b/crates/tek/src/midi/midi_rec.rs index 6ae97d22..94abf84d 100644 --- a/crates/tek/src/midi/midi_rec.rs +++ b/crates/tek/src/midi/midi_rec.rs @@ -42,7 +42,7 @@ pub trait MidiRecordApi: HasClock + HasPlayPhrase + HasMidiIns { } } } - if let Some((start_at, phrase)) = &self.next_phrase() { + if let Some((start_at, _clip)) = &self.next_phrase() { // TODO switch to next phrase and record into it } } diff --git a/crates/tek/src/tui.rs b/crates/tek/src/tui.rs index 1f855846..5990d455 100644 --- a/crates/tek/src/tui.rs +++ b/crates/tek/src/tui.rs @@ -41,7 +41,7 @@ mod piano_h; pub(crate) use self::piano_h::*; //////////////////////////////////////////////////////// pub fn render Usually<()>+Send+Sync> (render: F) -> impl Render { - Widget::new(|_|Ok(Some([0u16,0u16].into())), render) + Widget::new(|_|Ok(Some([0u16,0u16])), render) } //////////////////////////////////////////////////////// diff --git a/crates/tek/src/tui/app_groovebox.rs b/crates/tek/src/tui/app_groovebox.rs index 92241815..94d48858 100644 --- a/crates/tek/src/tui/app_groovebox.rs +++ b/crates/tek/src/tui/app_groovebox.rs @@ -37,13 +37,11 @@ pub enum GrooveboxCommand { Sampler(SamplerCommand), } handle!(|self:GrooveboxTui,input|GrooveboxCommand::execute_with_state(self, input)); -input_to_command!(GrooveboxCommand: |state:GrooveboxTui,input|match input.event() { - _ => match state.focus { - GrooveboxFocus::Sequencer => GrooveboxCommand::Sequencer( - SequencerCommand::input_to_command(&state.sequencer, input)?), - GrooveboxFocus::Sampler => GrooveboxCommand::Sampler( - SamplerCommand::input_to_command(&state.sampler, input)?), - } +input_to_command!(GrooveboxCommand: |state:GrooveboxTui,input|match state.focus { + GrooveboxFocus::Sequencer => GrooveboxCommand::Sequencer( + SequencerCommand::input_to_command(&state.sequencer, input)?), + GrooveboxFocus::Sampler => GrooveboxCommand::Sampler( + SamplerCommand::input_to_command(&state.sampler, input)?), }); command!(|self:GrooveboxCommand,state:GrooveboxTui|match self { GrooveboxCommand::Sequencer(command) => diff --git a/crates/tek/src/tui/app_sampler.rs b/crates/tek/src/tui/app_sampler.rs index 8753739e..75d75a8f 100644 --- a/crates/tek/src/tui/app_sampler.rs +++ b/crates/tek/src/tui/app_sampler.rs @@ -2,12 +2,17 @@ use crate::*; use super::*; use KeyCode::Char; use std::fs::File; -use symphonia::core::codecs::CODEC_TYPE_NULL; -use symphonia::core::errors::Error; -use symphonia::core::io::MediaSourceStream; -use symphonia::core::probe::Hint; -use symphonia::core::audio::SampleBuffer; -use symphonia::default::get_codecs; +use symphonia::{ + core::{ + formats::Packet, + codecs::{Decoder, CODEC_TYPE_NULL}, + errors::Error, + io::MediaSourceStream, + probe::Hint, + audio::SampleBuffer, + }, + default::get_codecs, +}; pub struct SamplerTui { pub state: Sampler, @@ -305,8 +310,8 @@ fn scan (dir: &PathBuf) -> Usually<(Vec, Vec)> { impl Sample { fn from_file (path: &PathBuf) -> Usually { - let mut sample = Self::default(); - sample.name = path.file_name().unwrap().to_string_lossy().into(); + let name = path.file_name().unwrap().to_string_lossy().into(); + let mut sample = Self { name, ..Default::default() }; // Use file extension if present let mut hint = Hint::new(); if let Some(ext) = path.extension() { @@ -322,48 +327,14 @@ impl Sample { &Default::default() )?; let mut format = probed.format; - let mut decoder = get_codecs().make( - &format.tracks().iter() - .find(|t| t.codec_params.codec != CODEC_TYPE_NULL) - .expect("no tracks found") - .codec_params, - &Default::default() - )?; + let params = &format.tracks().iter() + .find(|t| t.codec_params.codec != CODEC_TYPE_NULL) + .expect("no tracks found") + .codec_params; + let mut decoder = get_codecs().make(params, &Default::default())?; loop { match format.next_packet() { - Ok(packet) => { - // Decode a packet - let decoded = match decoder.decode(&packet) { - Ok(decoded) => decoded, - Err(err) => { return Err(err.into()); } - }; - // Determine sample rate - let spec = *decoded.spec(); - if let Some(rate) = sample.rate { - if rate != spec.rate as usize { - panic!("sample rate changed"); - } - } else { - sample.rate = Some(spec.rate as usize); - } - // Determine channel count - while sample.channels.len() < spec.channels.count() { - sample.channels.push(vec![]); - } - // Load sample - let mut samples = SampleBuffer::new( - decoded.frames() as u64, - spec - ); - if samples.capacity() > 0 { - samples.copy_interleaved_ref(decoded); - for frame in samples.samples().chunks(spec.channels.count()) { - for (chan, frame) in frame.iter().enumerate() { - sample.channels[chan].push(*frame) - } - } - } - }, + Ok(packet) => sample.decode_packet(&mut decoder, packet)?, Err(Error::IoError(_)) => break decoder.last_decoded(), Err(err) => return Err(err.into()), }; @@ -371,6 +342,41 @@ impl Sample { sample.end = sample.channels.iter().fold(0, |l, c|l + c.len()); Ok(sample) } + fn decode_packet ( + &mut self, decoder: &mut Box, packet: Packet + ) -> Usually<()> { + // Decode a packet + let decoded = decoder + .decode(&packet) + .map_err(|e|Box::::from(e))?; + // Determine sample rate + let spec = *decoded.spec(); + if let Some(rate) = self.rate { + if rate != spec.rate as usize { + panic!("sample rate changed"); + } + } else { + self.rate = Some(spec.rate as usize); + } + // Determine channel count + while self.channels.len() < spec.channels.count() { + self.channels.push(vec![]); + } + // Load sample + let mut samples = SampleBuffer::new( + decoded.frames() as u64, + spec + ); + if samples.capacity() > 0 { + samples.copy_interleaved_ref(decoded); + for frame in samples.samples().chunks(spec.channels.count()) { + for (chan, frame) in frame.iter().enumerate() { + self.channels[chan].push(*frame) + } + } + } + Ok(()) + } } fn draw_sample ( diff --git a/crates/tek/src/tui/arranger_command.rs b/crates/tek/src/tui/arranger_command.rs index 731adc59..cf51dee1 100644 --- a/crates/tek/src/tui/arranger_command.rs +++ b/crates/tek/src/tui/arranger_command.rs @@ -206,7 +206,7 @@ fn to_arrangement_command (state: &ArrangerTui, input: &TuiInput) -> Option Some( Cmd::Select(Selected::Scene((s + 1).min(s_len.saturating_sub(1))))), key_pat!(Left) => - return None, + None, key_pat!(Right) => Some( Cmd::Select(Selected::Clip(0, s))), @@ -221,7 +221,7 @@ fn to_arrangement_command (state: &ArrangerTui, input: &TuiInput) -> Option Some(Cmd::Track(Track::SetColor(t, ItemPalette::random()))), key_pat!(Up) => - return None, + None, key_pat!(Down) => Some( Cmd::Select(Selected::Clip(t, 0))), key_pat!(Left) => Some( @@ -237,11 +237,11 @@ fn to_arrangement_command (state: &ArrangerTui, input: &TuiInput) -> Option Some(Cmd::Color(ItemPalette::random())), key_pat!(Up) => - return None, + None, key_pat!(Down) => Some( Cmd::Select(Selected::Scene(0))), key_pat!(Left) => - return None, + None, key_pat!(Right) => Some( Cmd::Select(Selected::Track(0))), diff --git a/crates/tek/src/tui/arranger_mode.rs b/crates/tek/src/tui/arranger_mode.rs index c415d12a..1eacbf6b 100644 --- a/crates/tek/src/tui/arranger_mode.rs +++ b/crates/tek/src/tui/arranger_mode.rs @@ -11,7 +11,7 @@ render!(|self: ArrangerMode|{}); /// Arranger display mode can be cycled impl ArrangerMode { /// Cycle arranger display mode - pub fn to_next (&mut self) { + pub fn next (&mut self) { *self = match self { Self::H => Self::V(1), Self::V(1) => Self::V(2), diff --git a/crates/tek/src/tui/arranger_scene.rs b/crates/tek/src/tui/arranger_scene.rs index 67eebc69..0851f899 100644 --- a/crates/tek/src/tui/arranger_scene.rs +++ b/crates/tek/src/tui/arranger_scene.rs @@ -7,7 +7,7 @@ impl ArrangerTui { let scene = ArrangerScene { name: Arc::new(name.into()), clips: vec![None;self.tracks.len()], - color: color.unwrap_or_else(||ItemPalette::random()), + color: color.unwrap_or_else(ItemPalette::random), }; self.scenes.push(scene); let index = self.scenes.len() - 1; @@ -20,10 +20,10 @@ impl ArrangerTui { format!("S{:3>}", self.scenes.len() + 1) } pub fn selected_scene (&self) -> Option<&ArrangerScene> { - self.selected.scene().map(|s|self.scenes.get(s)).flatten() + self.selected.scene().and_then(|s|self.scenes.get(s)) } pub fn selected_scene_mut (&mut self) -> Option<&mut ArrangerScene> { - self.selected.scene().map(|s|self.scenes.get_mut(s)).flatten() + self.selected.scene().and_then(|s|self.scenes.get_mut(s)) } } #[derive(Default, Debug, Clone)] pub struct ArrangerScene { @@ -49,7 +49,7 @@ impl ArrangerScene { if factor == 0 { scenes.iter().map(|scene|{ let pulses = scene.pulses().max(PPQ); - total = total + pulses; + total += pulses; (pulses, total - pulses) }).collect() } else { diff --git a/crates/tek/src/tui/arranger_select.rs b/crates/tek/src/tui/arranger_select.rs index e9d3ffc6..e6bd0f22 100644 --- a/crates/tek/src/tui/arranger_select.rs +++ b/crates/tek/src/tui/arranger_select.rs @@ -13,13 +13,17 @@ pub enum ArrangerSelection { } /// Focus identification methods impl ArrangerSelection { - pub fn description ( + pub fn is_mix (&self) -> bool { matches!(self, Self::Mix) } + pub fn is_track (&self) -> bool { matches!(self, Self::Track(_)) } + pub fn is_scene (&self) -> bool { matches!(self, Self::Scene(_)) } + pub fn is_clip (&self) -> bool { matches!(self, Self::Clip(_, _)) } + pub fn description ( &self, - tracks: &Vec, - scenes: &Vec, + tracks: &[ArrangerTrack], + scenes: &[ArrangerScene], ) -> String { format!("Selected: {}", match self { - Self::Mix => format!("Everything"), + Self::Mix => "Everything".to_string(), Self::Track(t) => match tracks.get(*t) { Some(track) => format!("T{t}: {}", &track.name.read().unwrap()), None => "T??".into(), @@ -37,18 +41,6 @@ impl ArrangerSelection { } }) } - pub fn is_mix (&self) -> bool { - match self { Self::Mix => true, _ => false } - } - pub fn is_track (&self) -> bool { - match self { Self::Track(_) => true, _ => false } - } - pub fn is_scene (&self) -> bool { - match self { Self::Scene(_) => true, _ => false } - } - pub fn is_clip (&self) -> bool { - match self { Self::Clip(_, _) => true, _ => false } - } pub fn track (&self) -> Option { use ArrangerSelection::*; match self { diff --git a/crates/tek/src/tui/arranger_track.rs b/crates/tek/src/tui/arranger_track.rs index 390d93f5..6fb36583 100644 --- a/crates/tek/src/tui/arranger_track.rs +++ b/crates/tek/src/tui/arranger_track.rs @@ -10,7 +10,7 @@ impl ArrangerTui { let track = ArrangerTrack { width: name.len() + 2, name: Arc::new(name.into()), - color: color.unwrap_or_else(||ItemPalette::random()), + color: color.unwrap_or_else(ItemPalette::random), player: MidiPlayer::from(&self.clock), }; self.tracks.push(track); @@ -96,7 +96,7 @@ pub struct TracksAudio<'a>( /// Note chunk buffer pub &'a mut Vec>>, ); -impl<'a> Audio for TracksAudio<'a> { +impl Audio for TracksAudio<'_> { #[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { let model = &mut self.0; let note_buffer = &mut self.1; diff --git a/crates/tek/src/tui/arranger_v/v_clips.rs b/crates/tek/src/tui/arranger_v/v_clips.rs index ad8568e2..6d5691ed 100644 --- a/crates/tek/src/tui/arranger_v/v_clips.rs +++ b/crates/tek/src/tui/arranger_v/v_clips.rs @@ -17,7 +17,7 @@ from!(<'a>|args:(&'a ArrangerTui, usize)|ArrangerVClips<'a> = Self { render!(|self: ArrangerVClips<'a>|Fill::wh( col!((scene, pulses) in self.scenes.iter().zip(self.rows.iter().map(|row|row.0)) => { - Self::format_scene(&self.tracks, scene, pulses) + Self::format_scene(self.tracks, scene, pulses) }) )); @@ -28,8 +28,10 @@ impl<'a> ArrangerVClips<'a> { let height = 1.max((pulses / PPQ) as u16); let playing = scene.is_playing(tracks); Fixed::h(height, row!([ - Tui::bg(scene.color.base.rgb, if playing { "▶ " } else { " " }), - Tui::fg_bg(scene.color.lightest.rgb, scene.color.base.rgb, Tui::grow_x(1, Tui::bold(true, scene.name.read().unwrap().as_str()))), + Tui::bg(scene.color.base.rgb, + if playing { "▶ " } else { " " }), + Tui::fg_bg(scene.color.lightest.rgb, scene.color.base.rgb, + Tui::grow_x(1, Tui::bold(true, scene.name.read().unwrap().as_str()))), row!((index, track, x1, x2) in ArrangerTrack::with_widths(tracks) => { Self::format_clip(scene, index, track, (x2 - x1) as u16, height) })]) @@ -41,8 +43,7 @@ impl<'a> ArrangerVClips<'a> { Fixed::wh(w, h, Layers::new(move |add|{ if let Some(Some(phrase)) = scene.clips.get(index) { let mut bg = TuiTheme::border_bg(); - let name = &(phrase as &Arc>).read().unwrap().name; - let name = format!("{}", name); + let name = &(phrase as &Arc>).read().unwrap().name.to_string(); let max_w = name.len().min((w as usize).saturating_sub(2)); let color = phrase.read().unwrap().color; bg = color.dark.rgb; @@ -52,7 +53,7 @@ impl<'a> ArrangerVClips<'a> { } }; add(&Tui::bg(bg, - Tui::push_x(1, Fixed::w(w as u16, &name.as_str()[0..max_w]))) + Tui::push_x(1, Fixed::w(w, &name.as_str()[0..max_w]))) )?; } Ok(()) diff --git a/crates/tek/src/tui/arranger_v/v_head.rs b/crates/tek/src/tui/arranger_v/v_head.rs index fba00caf..4ff23a15 100644 --- a/crates/tek/src/tui/arranger_v/v_head.rs +++ b/crates/tek/src/tui/arranger_v/v_head.rs @@ -29,13 +29,13 @@ render!(|self: ArrangerVHead<'a>|Tui::push_x(self.scenes_w, row!( row(color, &Self::format_name(track, w)), row(color, &Self::format_input(track)?), row(color, &Self::format_output(track)?), - row(color, &Self::format_elapsed(track, &self.timebase)), - row(color, &Self::format_until_next(track, &self.current)), + row(color, &Self::format_elapsed(track, self.timebase)), + row(color, &Self::format_until_next(track, self.current)), ])))) } ))); -impl<'a> ArrangerVHead<'a> { +impl ArrangerVHead<'_> { /// name and width of track fn format_name (track: &ArrangerTrack, _w: usize) -> impl Render { let name = track.name().read().unwrap().clone(); @@ -43,12 +43,12 @@ impl<'a> ArrangerVHead<'a> { } /// input port fn format_input (track: &ArrangerTrack) -> Usually> { - Ok(format!(">{}", track.player.midi_ins().get(0).map(|port|port.short_name()) + Ok(format!(">{}", track.player.midi_ins().first().map(|port|port.short_name()) .transpose()?.unwrap_or("?".into()))) } /// output port fn format_output (track: &ArrangerTrack) -> Usually> { - Ok(format!("<{}", track.player.midi_outs().get(0).map(|port|port.short_name()) + Ok(format!("<{}", track.player.midi_outs().first().map(|port|port.short_name()) .transpose()?.unwrap_or("?".into()))) } /// beats elapsed diff --git a/crates/tek/src/tui/phrase_editor.rs b/crates/tek/src/tui/phrase_editor.rs index 5ec099a2..c8ed9c50 100644 --- a/crates/tek/src/tui/phrase_editor.rs +++ b/crates/tek/src/tui/phrase_editor.rs @@ -28,10 +28,10 @@ pub enum PhraseCommand { SetTimeLock(bool), Show(Option>>), } -event_map_input_to_command!(Tui: MidiEditorModel: PhraseCommand: MidiEditorModel::KEYS); +event_map_input_to_command!(Tui: MidiEditorModel: PhraseCommand: MidiEditorModel::KEYS); impl MidiEditorModel { - const KEYS: [(TuiEvent, &'static dyn Fn(&Self)->PhraseCommand);31] = [ + const KEYS: KeyMapping<31, Self> = [ (kexp!(Ctrl-Alt-Up), &|s: &Self|SetNoteScroll(s.note_point() + 3)), (kexp!(Ctrl-Alt-Down), &|s: &Self|SetNoteScroll(s.note_point().saturating_sub(3))), (kexp!(Ctrl-Alt-Left), &|s: &Self|SetTimeScroll(s.time_point().saturating_sub(s.time_zoom().get()))), @@ -120,7 +120,7 @@ pub trait PhraseViewMode: Render + HasSize + MidiRange + MidiPoint + D fn phrase (&self) -> &Option>>; fn phrase_mut (&mut self) -> &mut Option>>; fn set_phrase (&mut self, phrase: Option<&Arc>>) { - *self.phrase_mut() = phrase.map(|p|p.clone()); + *self.phrase_mut() = phrase.cloned(); self.redraw(); } } diff --git a/crates/tek/src/tui/piano_h.rs b/crates/tek/src/tui/piano_h.rs index c8c3f774..f28639fc 100644 --- a/crates/tek/src/tui/piano_h.rs +++ b/crates/tek/src/tui/piano_h.rs @@ -37,19 +37,19 @@ impl PianoHorizontal { Self { buffer: Default::default(), point: MidiPointModel::default(), + phrase: phrase.cloned(), size, range, - phrase: phrase.map(|p|p.clone()), color } } } render!(|self: PianoHorizontal|{ - let keys = move||PianoHorizontalKeys(&self); - let timeline = move||PianoHorizontalTimeline(&self); - let notes = move||PianoHorizontalNotes(&self); - let cursor = move||PianoHorizontalCursor(&self); + let keys = move||PianoHorizontalKeys(self); + let timeline = move||PianoHorizontalTimeline(self); + let notes = move||PianoHorizontalNotes(self); + let cursor = move||PianoHorizontalCursor(self); let keys_width = 5; let border = Fill::wh(Outer(Style::default().fg(self.color.dark.rgb).bg(self.color.darkest.rgb))); let with_border = |x|lay!([border, Tui::inset_xy(1, 1, &x)]); @@ -169,8 +169,8 @@ impl PhraseViewMode for PianoHorizontal { self.buffer = buffer } fn set_phrase (&mut self, phrase: Option<&Arc>>) { - *self.phrase_mut() = phrase.map(|p|p.clone()); - self.color = phrase.map(|p|p.read().unwrap().color.clone()) + *self.phrase_mut() = phrase.cloned(); + self.color = phrase.map(|p|p.read().unwrap().color) .unwrap_or(ItemPalette::from(ItemColor::from(TuiTheme::g(64)))); self.redraw(); } diff --git a/crates/tek/src/tui/piano_h/piano_h_cursor.rs b/crates/tek/src/tui/piano_h/piano_h_cursor.rs index 2c5dd6b5..c85946c8 100644 --- a/crates/tek/src/tui/piano_h/piano_h_cursor.rs +++ b/crates/tek/src/tui/piano_h/piano_h_cursor.rs @@ -15,7 +15,7 @@ render!(|self: PianoHorizontalCursor<'a>|render(|to|Ok({ for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { if note == note_point { for x in 0..w { - let screen_x = x0 + x as u16; + let screen_x = x0 + x; let time_1 = time_start + x as usize * time_zoom; let time_2 = time_1 + time_zoom; if time_1 <= time_point && time_point < time_2 { diff --git a/crates/tek/src/tui/pool.rs b/crates/tek/src/tui/pool.rs index 57cff863..c932a27c 100644 --- a/crates/tek/src/tui/pool.rs +++ b/crates/tek/src/tui/pool.rs @@ -297,7 +297,7 @@ impl PhraseSelector { } else if let Some((_, Some(phrase))) = state.play_phrase() { let phrase = phrase.read().unwrap(); if phrase.looped { - (" ".into(), phrase.name.clone(), phrase.color.clone()) + (" ".into(), phrase.name.clone(), phrase.color) } else { (" ".into(), " ".into(), TuiTheme::g(64).into()) } diff --git a/crates/tek/src/tui/pool/phrase_rename.rs b/crates/tek/src/tui/pool/phrase_rename.rs index 67378f00..55d96c11 100644 --- a/crates/tek/src/tui/pool/phrase_rename.rs +++ b/crates/tek/src/tui/pool/phrase_rename.rs @@ -15,7 +15,7 @@ impl Command for PhraseRenameCommand { match state.phrases_mode_mut().clone() { Some(PoolMode::Rename(phrase, ref mut old_name)) => match self { Set(s) => { - state.phrases()[phrase].write().unwrap().name = s.into(); + state.phrases()[phrase].write().unwrap().name = s; return Ok(Some(Self::Set(old_name.clone()))) }, Confirm => { diff --git a/crates/tek/src/tui/status/status_arranger.rs b/crates/tek/src/tui/status/status_arranger.rs index a8b6b9c0..a8f80422 100644 --- a/crates/tek/src/tui/status/status_arranger.rs +++ b/crates/tek/src/tui/status/status_arranger.rs @@ -11,7 +11,7 @@ pub struct ArrangerStatus { } from!(|state:&ArrangerTui|ArrangerStatus = { let samples = state.clock.chunk.load(Relaxed); - let rate = state.clock.timebase.sr.get() as f64; + let rate = state.clock.timebase.sr.get(); let buffer = samples as f64 / rate; let width = state.size.w(); Self { @@ -48,7 +48,7 @@ impl ArrangerStatus { double(("[]", "phrase"), ("{}", "order"),), ])) } - fn stats <'a> (&'a self) -> impl Render + use<'a> { + fn stats (&self) -> impl Render + use<'_> { row!([&self.cpu, &self.res, &self.size]) } } diff --git a/crates/tek/src/tui/status/status_edit.rs b/crates/tek/src/tui/status/status_edit.rs index 43263565..ebddb18d 100644 --- a/crates/tek/src/tui/status/status_edit.rs +++ b/crates/tek/src/tui/status/status_edit.rs @@ -16,9 +16,9 @@ render!(|self:MidiEditStatus<'a>|{ ]); Fill::w(Tui::fg_bg(fg, bg, row!([ Fixed::wh(26, 3, col!(![ - field(" Edit", format!("{name}")), - field(" Length", format!("{length}")), - field(" Loop", format!("{looped}"))])), + field(" Edit", name.to_string()), + field(" Length", length.to_string()), + field(" Loop", looped.to_string())])), Fixed::wh(30, 3, col!(![ field(" Time", format!("{}/{}-{} ({}*{}) {}", self.0.time_point(), self.0.time_start().get(), self.0.time_end(), diff --git a/crates/tek/src/tui/status/status_sequencer.rs b/crates/tek/src/tui/status/status_sequencer.rs index 44f06c0b..91257cad 100644 --- a/crates/tek/src/tui/status/status_sequencer.rs +++ b/crates/tek/src/tui/status/status_sequencer.rs @@ -11,7 +11,7 @@ pub struct SequencerStatus { } from!(|state:&SequencerTui|SequencerStatus = { let samples = state.clock.chunk.load(Relaxed); - let rate = state.clock.timebase.sr.get() as f64; + let rate = state.clock.timebase.sr.get(); let buffer = samples as f64 / rate; let width = state.size.w(); Self { @@ -46,7 +46,7 @@ impl SequencerStatus { double(("c", "color"), ("", ""),), ])) } - fn stats <'a> (&'a self) -> impl Render + use<'a> { + fn stats (&self) -> impl Render + use<'_> { row!([&self.cpu, &self.res, &self.size]) } } diff --git a/crates/tek/src/tui/tui_input.rs b/crates/tek/src/tui/tui_input.rs index f7d24a10..cabf317b 100644 --- a/crates/tek/src/tui/tui_input.rs +++ b/crates/tek/src/tui/tui_input.rs @@ -92,6 +92,8 @@ impl<'a, const N: usize, E: PartialEq, T, U> EventMap<'a, N, E, T, U> { } } +pub(crate) type KeyMapping = [(TuiEvent, &'static dyn Fn(&T)->PhraseCommand);N]; + #[macro_export] macro_rules! kexp { (Ctrl-Alt-$code:ident) => { key_event_expr!($code, KeyModifiers::from_bits(0b0000_0110).unwrap()) }; (Ctrl-$code:ident) => { key_event_expr!($code, KeyModifiers::CONTROL) }; From 911c47fc7c08755aa1c44ef090ace66769033baa Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 27 Dec 2024 13:48:18 +0100 Subject: [PATCH 022/815] remove some shorthands --- crates/tek/src/tui/pool.rs | 45 +++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/crates/tek/src/tui/pool.rs b/crates/tek/src/tui/pool.rs index c932a27c..5aa85a5d 100644 --- a/crates/tek/src/tui/pool.rs +++ b/crates/tek/src/tui/pool.rs @@ -1,13 +1,8 @@ use super::*; -use crate::PhrasePoolCommand as Pool; mod phrase_length; pub(crate) use phrase_length::*; mod phrase_rename; pub(crate) use phrase_rename::*; -use PhraseRenameCommand as Rename; -use PhraseLengthCommand as Length; -use FileBrowserCommand as Browse; - #[derive(Debug)] pub struct PoolModel { pub(crate) visible: bool, @@ -40,17 +35,17 @@ pub enum PoolMode { pub enum PoolCommand { Show(bool), /// Update the contents of the phrase pool - Phrase(Pool), + Phrase(PhrasePoolCommand), /// Select a phrase from the phrase pool Select(usize), /// Rename a phrase - Rename(Rename), + Rename(PhraseRenameCommand), /// Change the length of a phrase - Length(Length), + Length(PhraseLengthCommand), /// Import from file - Import(Browse), + Import(FileBrowserCommand), /// Export to file - Export(Browse), + Export(FileBrowserCommand), } command!(|self:PoolCommand, state: PoolModel|{ @@ -107,10 +102,10 @@ command!(|self:PoolCommand, state: PoolModel|{ }); input_to_command!(PoolCommand:|state: PoolModel,input|match state.phrases_mode() { - Some(PoolMode::Rename(..)) => Self::Rename(Rename::input_to_command(state, input)?), - Some(PoolMode::Length(..)) => Self::Length(Length::input_to_command(state, input)?), - Some(PoolMode::Import(..)) => Self::Import(Browse::input_to_command(state, input)?), - Some(PoolMode::Export(..)) => Self::Export(Browse::input_to_command(state, input)?), + Some(PoolMode::Rename(..)) => Self::Rename(PhraseRenameCommand::input_to_command(state, input)?), + Some(PoolMode::Length(..)) => Self::Length(PhraseLengthCommand::input_to_command(state, input)?), + Some(PoolMode::Import(..)) => Self::Import(FileBrowserCommand::input_to_command(state, input)?), + Some(PoolMode::Export(..)) => Self::Export(FileBrowserCommand::input_to_command(state, input)?), _ => to_phrases_command(state, input)? }); @@ -120,11 +115,11 @@ fn to_phrases_command (state: &PoolModel, input: &TuiInput) -> Option Cmd::Rename(Rename::Begin), - key_pat!(Char('t')) => Cmd::Length(Length::Begin), - key_pat!(Char('m')) => Cmd::Import(Browse::Begin), - key_pat!(Char('x')) => Cmd::Export(Browse::Begin), - key_pat!(Char('c')) => Cmd::Phrase(Pool::SetColor(index, ItemColor::random())), + key_pat!(Char('n')) => Cmd::Rename(PhraseRenameCommand::Begin), + key_pat!(Char('t')) => Cmd::Length(PhraseLengthCommand::Begin), + key_pat!(Char('m')) => Cmd::Import(FileBrowserCommand::Begin), + key_pat!(Char('x')) => Cmd::Export(FileBrowserCommand::Begin), + key_pat!(Char('c')) => Cmd::Phrase(PhrasePoolCommand::SetColor(index, ItemColor::random())), key_pat!(Char('[')) | key_pat!(Up) => Cmd::Select( index.overflowing_sub(1).0.min(state.phrases().len() - 1) ), @@ -133,32 +128,32 @@ fn to_phrases_command (state: &PoolModel, input: &TuiInput) -> Option if index > 1 { state.set_phrase_index(state.phrase_index().saturating_sub(1)); - Cmd::Phrase(Pool::Swap(index - 1, index)) + Cmd::Phrase(PhrasePoolCommand::Swap(index - 1, index)) } else { return None }, key_pat!(Char('>')) => if index < count.saturating_sub(1) { state.set_phrase_index(state.phrase_index() + 1); - Cmd::Phrase(Pool::Swap(index + 1, index)) + Cmd::Phrase(PhrasePoolCommand::Swap(index + 1, index)) } else { return None }, key_pat!(Delete) => if index > 0 { state.set_phrase_index(index.min(count.saturating_sub(1))); - Cmd::Phrase(Pool::Delete(index)) + Cmd::Phrase(PhrasePoolCommand::Delete(index)) } else { return None }, - key_pat!(Char('a')) | key_pat!(Shift-Char('A')) => Cmd::Phrase(Pool::Add(count, MidiClip::new( + key_pat!(Char('a')) | key_pat!(Shift-Char('A')) => Cmd::Phrase(PhrasePoolCommand::Add(count, MidiClip::new( String::from("(new)"), true, 4 * PPQ, None, Some(ItemPalette::random()) ))), - key_pat!(Char('i')) => Cmd::Phrase(Pool::Add(index + 1, MidiClip::new( + key_pat!(Char('i')) => Cmd::Phrase(PhrasePoolCommand::Add(index + 1, MidiClip::new( String::from("(new)"), true, 4 * PPQ, None, Some(ItemPalette::random()) ))), key_pat!(Char('d')) | key_pat!(Shift-Char('D')) => { let mut phrase = state.phrases()[index].read().unwrap().duplicate(); phrase.color = ItemPalette::random_near(phrase.color, 0.25); - Cmd::Phrase(Pool::Add(index + 1, phrase)) + Cmd::Phrase(PhrasePoolCommand::Add(index + 1, phrase)) }, _ => return None }) From e08a79b5072499dabfdebc70f830cadeb775aa22 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 27 Dec 2024 14:46:35 +0100 Subject: [PATCH 023/815] wip: multi-crate refactor --- Cargo.lock | 19 +++- Cargo.toml | 7 +- crates/cli/Cargo.toml | 40 +++++++ .../{tek/src/cli => cli/src}/cli_arranger.rs | 5 +- crates/cli/src/cli_groovebox.rs | 22 ++++ .../{tek/src/cli => cli/src}/cli_sampler.rs | 12 +-- .../{tek/src/cli => cli/src}/cli_sequencer.rs | 5 +- .../{tek/src/cli => cli/src}/cli_transport.rs | 5 +- crates/cli/src/lib.rs | 3 + .../src/cli => cli/src}/todo_cli_mixer.rs | 2 +- .../src/cli => cli/src}/todo_cli_plugin.rs | 2 +- .../src/cli => cli/src}/todo_cli_sampler.rs | 2 +- crates/edn/Cargo.toml | 8 ++ crates/{tek/src/edn.rs => edn/src/lib.rs} | 80 +++++++++++++- crates/suil/Cargo.toml | 3 +- crates/tek/Cargo.toml | 34 ------ crates/tek/src/audio.rs | 12 ++- crates/tek/src/audio/channel.rs | 56 ---------- crates/tek/src/cli/cli_groovebox.rs | 27 ----- crates/tek/src/jack.rs | 100 ++++++++++++++++-- crates/tek/src/jack/audio.rs | 78 -------------- crates/tek/src/lib.rs | 25 +++-- crates/tek/src/plugin/lv2.rs | 19 ---- crates/tek/src/tui.rs | 9 ++ crates/tek/src/tui/app_sampler.rs | 1 - 25 files changed, 311 insertions(+), 265 deletions(-) create mode 100644 crates/cli/Cargo.toml rename crates/{tek/src/cli => cli/src}/cli_arranger.rs (98%) create mode 100644 crates/cli/src/cli_groovebox.rs rename crates/{tek/src/cli => cli/src}/cli_sampler.rs (69%) rename crates/{tek/src/cli => cli/src}/cli_sequencer.rs (97%) rename crates/{tek/src/cli => cli/src}/cli_transport.rs (73%) create mode 100644 crates/cli/src/lib.rs rename crates/{tek/src/cli => cli/src}/todo_cli_mixer.rs (97%) rename crates/{tek/src/cli => cli/src}/todo_cli_plugin.rs (97%) rename crates/{tek/src/cli => cli/src}/todo_cli_sampler.rs (96%) create mode 100644 crates/edn/Cargo.toml rename crates/{tek/src/edn.rs => edn/src/lib.rs} (60%) delete mode 100644 crates/tek/src/cli/cli_groovebox.rs delete mode 100644 crates/tek/src/jack/audio.rs diff --git a/Cargo.lock b/Cargo.lock index bfd59c5e..cf73e7ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -1268,7 +1268,6 @@ dependencies = [ "atomic_float", "backtrace", "better-panic", - "clap", "clojure-reader", "crossterm", "jack", @@ -1285,6 +1284,22 @@ dependencies = [ "wavers", ] +[[package]] +name = "tek_cli" +version = "0.2.0" +dependencies = [ + "clap", + "tek", +] + +[[package]] +name = "tek_edn" +version = "0.2.0" +dependencies = [ + "clojure-reader", + "tek", +] + [[package]] name = "thiserror" version = "1.0.69" diff --git a/Cargo.toml b/Cargo.toml index 72ee139a..b502f8c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,11 +2,8 @@ resolver = "2" members = [ "crates/tek", - #"crates/tek_core", - #"crates/tek_api", - #"crates/tek_tui", - #"crates/tek_cli", - #"crates/tek_layout" + "crates/cli", + "crates/edn", ] [profile.release] diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml new file mode 100644 index 00000000..75d1321f --- /dev/null +++ b/crates/cli/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "tek_cli" +edition = "2021" +version = "0.2.0" + +[dependencies] +tek = { path = "../tek" } +clap = { version = "4.5.4", features = [ "derive" ] } + +[[bin]] +name = "tek_arranger" +path = "src/cli_arranger.rs" + +[[bin]] +name = "tek_sequencer" +path = "src/cli_sequencer.rs" + +[[bin]] +name = "tek_groovebox" +path = "src/cli_groovebox.rs" + +[[bin]] +name = "tek_transport" +path = "src/cli_transport.rs" + +[[bin]] +name = "tek_sampler" +path = "src/cli_sampler.rs" + +#[[bin]] +#name = "tek_mixer" +#path = "src/cli_mixer.rs" + +#[[bin]] +#name = "tek_track" +#path = "src/cli_track.rs" + +#[[bin]] +#name = "tek_plugin" +#path = "src/cli_plugin.rs" diff --git a/crates/tek/src/cli/cli_arranger.rs b/crates/cli/src/cli_arranger.rs similarity index 98% rename from crates/tek/src/cli/cli_arranger.rs rename to crates/cli/src/cli_arranger.rs index 57697699..6c218c8e 100644 --- a/crates/tek/src/cli/cli_arranger.rs +++ b/crates/cli/src/cli_arranger.rs @@ -1,6 +1,5 @@ -#![allow(unused)] -#![allow(clippy::unit_arg)] -include!("../lib.rs"); +include!("./lib.rs"); +use tek::tui::ArrangerTui; pub fn main () -> Usually<()> { ArrangerCli::parse().run() diff --git a/crates/cli/src/cli_groovebox.rs b/crates/cli/src/cli_groovebox.rs new file mode 100644 index 00000000..f4c10ac2 --- /dev/null +++ b/crates/cli/src/cli_groovebox.rs @@ -0,0 +1,22 @@ +include!("./lib.rs"); +pub fn main () -> Usually<()> { GrooveboxCli::parse().run() } +#[derive(Debug, Parser)] +#[command(version, about, long_about = None)] +pub struct GrooveboxCli; +impl GrooveboxCli { + fn run (&self) -> Usually<()> { + Tui::run(JackClient::new("tek_groovebox")?.activate_with(|jack|{ + let app = tek::tui::GrooveboxTui::try_from(jack)?; + let jack = jack.read().unwrap(); + let midi_out = jack.register_port("out", MidiOut::default())?; + let midi_in_1 = jack.register_port("in1", MidiIn::default())?; + let midi_in_2 = jack.register_port("in2", MidiIn::default())?; + let audio_in_1 = jack.register_port("inL", AudioIn::default())?; + let audio_in_2 = jack.register_port("inR", AudioIn::default())?; + let audio_out_1 = jack.register_port("out1", AudioOut::default())?; + let audio_out_2 = jack.register_port("out2", AudioOut::default())?; + Ok(app) + })?)?; + Ok(()) + } +} diff --git a/crates/tek/src/cli/cli_sampler.rs b/crates/cli/src/cli_sampler.rs similarity index 69% rename from crates/tek/src/cli/cli_sampler.rs rename to crates/cli/src/cli_sampler.rs index aeda9da0..55601c18 100644 --- a/crates/tek/src/cli/cli_sampler.rs +++ b/crates/cli/src/cli_sampler.rs @@ -1,21 +1,15 @@ -#![allow(unused)] -#![allow(clippy::unit_arg)] -include!("../lib.rs"); -pub fn main () -> Usually<()> { - SamplerCli::parse().run() -} - +include!("./lib.rs"); +pub fn main () -> Usually<()> { SamplerCli::parse().run() } #[derive(Debug, Parser)] #[command(version, about, long_about = None)] pub struct SamplerCli { /// Name of JACK client #[arg(short, long)] name: Option, /// Path to plugin #[arg(short, long)] path: Option, } - impl SamplerCli { fn run (&self) -> Usually<()> { Tui::run(JackClient::new("tek_sampler")?.activate_with(|x|{ - let sampler = SamplerTui::try_from(x)?; + let sampler = tek::tui::SamplerTui::try_from(x)?; Ok(sampler) })?)?; Ok(()) diff --git a/crates/tek/src/cli/cli_sequencer.rs b/crates/cli/src/cli_sequencer.rs similarity index 97% rename from crates/tek/src/cli/cli_sequencer.rs rename to crates/cli/src/cli_sequencer.rs index 619f9381..fcbb8e9f 100644 --- a/crates/tek/src/cli/cli_sequencer.rs +++ b/crates/cli/src/cli_sequencer.rs @@ -1,6 +1,5 @@ -#![allow(unused)] -#![allow(clippy::unit_arg)] -include!("../lib.rs"); +include!("./lib.rs"); + pub fn main () -> Usually<()> { SequencerCli::parse().run() } diff --git a/crates/tek/src/cli/cli_transport.rs b/crates/cli/src/cli_transport.rs similarity index 73% rename from crates/tek/src/cli/cli_transport.rs rename to crates/cli/src/cli_transport.rs index 68fb9c7c..942978fe 100644 --- a/crates/tek/src/cli/cli_transport.rs +++ b/crates/cli/src/cli_transport.rs @@ -1,6 +1,5 @@ -#![allow(unused)] -#![allow(clippy::unit_arg)] -include!("../lib.rs"); +include!("./lib.rs"); + /// Application entrypoint. pub fn main () -> Usually<()> { Tui::run(JackClient::new("tek_transport")?.activate_with(|jack|{ diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs new file mode 100644 index 00000000..5fc4389e --- /dev/null +++ b/crates/cli/src/lib.rs @@ -0,0 +1,3 @@ +use std::sync::Arc; +use clap::{self, Parser}; +use tek::{*, jack::*}; diff --git a/crates/tek/src/cli/todo_cli_mixer.rs b/crates/cli/src/todo_cli_mixer.rs similarity index 97% rename from crates/tek/src/cli/todo_cli_mixer.rs rename to crates/cli/src/todo_cli_mixer.rs index d14f9807..4130dde9 100644 --- a/crates/tek/src/cli/todo_cli_mixer.rs +++ b/crates/cli/src/todo_cli_mixer.rs @@ -1,4 +1,4 @@ -use crate::*; +include!("./lib.rs"); pub fn main () -> Usually<()> { MixerCli::parse().run() diff --git a/crates/tek/src/cli/todo_cli_plugin.rs b/crates/cli/src/todo_cli_plugin.rs similarity index 97% rename from crates/tek/src/cli/todo_cli_plugin.rs rename to crates/cli/src/todo_cli_plugin.rs index 06acc9bf..1b5a1ce5 100644 --- a/crates/tek/src/cli/todo_cli_plugin.rs +++ b/crates/cli/src/todo_cli_plugin.rs @@ -1,4 +1,4 @@ -use crate::*; +include!("./lib.rs"); pub fn main () -> Usually<()> { PluginCli::parse().run() diff --git a/crates/tek/src/cli/todo_cli_sampler.rs b/crates/cli/src/todo_cli_sampler.rs similarity index 96% rename from crates/tek/src/cli/todo_cli_sampler.rs rename to crates/cli/src/todo_cli_sampler.rs index 0fcc8a7d..bb72ec71 100644 --- a/crates/tek/src/cli/todo_cli_sampler.rs +++ b/crates/cli/src/todo_cli_sampler.rs @@ -1,4 +1,4 @@ -use crate::*; +include!("./lib.rs"); pub fn main () -> Usually<()> { SamplerCli::parse().run() diff --git a/crates/edn/Cargo.toml b/crates/edn/Cargo.toml new file mode 100644 index 00000000..01bab065 --- /dev/null +++ b/crates/edn/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "tek_edn" +edition = "2021" +version = "0.2.0" + +[dependencies] +tek = { path = "../tek" } +clojure-reader = "0.1.0" diff --git a/crates/tek/src/edn.rs b/crates/edn/src/lib.rs similarity index 60% rename from crates/tek/src/edn.rs rename to crates/edn/src/lib.rs index c60ba9ca..28b2d67b 100644 --- a/crates/tek/src/edn.rs +++ b/crates/edn/src/lib.rs @@ -1,6 +1,7 @@ -#![allow(unused)] +use tek::{*, jack::*}; +use std::sync::{Arc, RwLock}; +use std::collections::BTreeMap; -use crate::*; pub use clojure_reader::edn::Edn; //pub use clojure_reader::{edn::{read, Edn}, error::Error as EdnError}; @@ -107,6 +108,81 @@ from_edn!(|(jack, dir) = (&Arc>, &str), "sample", args| -> Mi })))) }); +impl LV2Plugin { + pub fn from_edn <'e> (jack: &Arc>, args: &[Edn<'e>]) -> Usually { + let mut name = String::new(); + let mut path = String::new(); + 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::Str(p)) = map.get(&Edn::Key(":path")) { + path = String::from(*p); + } + }, + _ => panic!("unexpected in lv2 '{name}'"), + }); + Plugin::new_lv2(jack, &name, &path) + } +} + +impl MixerTrack { + const SYM_NAME: &'static str = ":name"; + const SYM_GAIN: &'static str = ":gain"; + const SYM_SAMPLER: &'static str = "sampler"; + const SYM_LV2: &'static str = "lv2"; + pub fn from_edn <'a, 'e> (jack: &Arc>, args: &[Edn<'e>]) -> Usually { + let mut _gain = 0.0f64; + let mut track = MixerTrack { + name: String::new(), + audio_ins: vec![], + audio_outs: vec![], + devices: vec![], + }; + edn!(edn in args { + Edn::Map(map) => { + if let Some(Edn::Str(n)) = map.get(&Edn::Key(Self::SYM_NAME)) { + track.name = n.to_string(); + } + if let Some(Edn::Double(g)) = map.get(&Edn::Key(Self::SYM_GAIN)) { + _gain = f64::from(*g); + } + }, + Edn::List(args) => match args.get(0) { + // Add a sampler device to the track + Some(Edn::Symbol(Self::SYM_SAMPLER)) => { + track.devices.push( + Box::new(Sampler::from_edn(jack, &args[1..])?) as Box + ); + panic!( + "unsupported in track {}: {:?}; tek_mixer not compiled with feature \"sampler\"", + &track.name, + args.get(0).unwrap() + ) + }, + // Add a LV2 plugin to the track. + Some(Edn::Symbol(Self::SYM_LV2)) => { + track.devices.push( + Box::new(LV2Plugin::from_edn(jack, &args[1..])?) as Box + ); + panic!( + "unsupported in track {}: {:?}; tek_mixer not compiled with feature \"plugin\"", + &track.name, + args.get(0).unwrap() + ) + }, + None => + panic!("empty list track {}", &track.name), + _ => + panic!("unexpected in track {}: {:?}", &track.name, args.get(0).unwrap()) + }, + _ => {} + }); + Ok(track) + } +} + //impl ArrangerScene { ////TODO diff --git a/crates/suil/Cargo.toml b/crates/suil/Cargo.toml index db586670..7e6211bd 100644 --- a/crates/suil/Cargo.toml +++ b/crates/suil/Cargo.toml @@ -1,11 +1,12 @@ [package] -name = "suil-rs" +name = "tek_suil" version = "0.1.0" edition = "2021" [dependencies] gtk = "0.18.1" livi = "0.7.4" +#winit = { version = "0.30.4", features = [ "x11" ] } [build-dependencies] bindgen = "0.69.4" diff --git a/crates/tek/Cargo.toml b/crates/tek/Cargo.toml index 5c22e156..71d2ad10 100644 --- a/crates/tek/Cargo.toml +++ b/crates/tek/Cargo.toml @@ -9,7 +9,6 @@ version = "0.2.0" atomic_float = "1.0.0" backtrace = "0.3.72" better-panic = "0.3.0" -clap = { version = "4.5.4", features = [ "derive" ] } clojure-reader = "0.1.0" crossterm = "0.27" jack = "0.13" @@ -29,37 +28,4 @@ wavers = "1.4.3" #winit = { version = "0.30.4", features = [ "x11" ] } [lib] -name = "tek_lib" path = "src/lib.rs" - -[[bin]] -name = "tek_arranger" -path = "src/cli/cli_arranger.rs" - -[[bin]] -name = "tek_sequencer" -path = "src/cli/cli_sequencer.rs" - -[[bin]] -name = "tek_groovebox" -path = "src/cli/cli_groovebox.rs" - -[[bin]] -name = "tek_transport" -path = "src/cli/cli_transport.rs" - -[[bin]] -name = "tek_sampler" -path = "src/cli/cli_sampler.rs" - -#[[bin]] -#name = "tek_mixer" -#path = "src/cli_mixer.rs" - -#[[bin]] -#name = "tek_track" -#path = "src/cli_track.rs" - -#[[bin]] -#name = "tek_plugin" -#path = "src/cli_plugin.rs" diff --git a/crates/tek/src/audio.rs b/crates/tek/src/audio.rs index a44f6276..24900710 100644 --- a/crates/tek/src/audio.rs +++ b/crates/tek/src/audio.rs @@ -1,3 +1,9 @@ -pub(crate) mod audio_in; -pub(crate) mod audio_out; -pub(crate) mod sampler; pub(crate) use sampler::*; +use crate::*; + +mod audio_in; + +mod audio_out; + +mod sampler; +pub(crate) use sampler::*; +pub use self::sampler::{Sampler, Sample, Voice}; diff --git a/crates/tek/src/audio/channel.rs b/crates/tek/src/audio/channel.rs index 513a6934..b850147a 100644 --- a/crates/tek/src/audio/channel.rs +++ b/crates/tek/src/audio/channel.rs @@ -16,62 +16,6 @@ pub struct MixerTrack { //impl MixerTrackDevice for LV2Plugin {} -impl MixerTrack { - const SYM_NAME: &'static str = ":name"; - const SYM_GAIN: &'static str = ":gain"; - const SYM_SAMPLER: &'static str = "sampler"; - const SYM_LV2: &'static str = "lv2"; - pub fn from_edn <'a, 'e> (jack: &Arc>, args: &[Edn<'e>]) -> Usually { - let mut _gain = 0.0f64; - let mut track = MixerTrack { - name: String::new(), - audio_ins: vec![], - audio_outs: vec![], - devices: vec![], - }; - edn!(edn in args { - Edn::Map(map) => { - if let Some(Edn::Str(n)) = map.get(&Edn::Key(Self::SYM_NAME)) { - track.name = n.to_string(); - } - if let Some(Edn::Double(g)) = map.get(&Edn::Key(Self::SYM_GAIN)) { - _gain = f64::from(*g); - } - }, - Edn::List(args) => match args.get(0) { - // Add a sampler device to the track - Some(Edn::Symbol(Self::SYM_SAMPLER)) => { - track.devices.push( - Box::new(Sampler::from_edn(jack, &args[1..])?) as Box - ); - panic!( - "unsupported in track {}: {:?}; tek_mixer not compiled with feature \"sampler\"", - &track.name, - args.get(0).unwrap() - ) - }, - // Add a LV2 plugin to the track. - Some(Edn::Symbol(Self::SYM_LV2)) => { - track.devices.push( - Box::new(LV2Plugin::from_edn(jack, &args[1..])?) as Box - ); - panic!( - "unsupported in track {}: {:?}; tek_mixer not compiled with feature \"plugin\"", - &track.name, - args.get(0).unwrap() - ) - }, - None => - panic!("empty list track {}", &track.name), - _ => - panic!("unexpected in track {}: {:?}", &track.name, args.get(0).unwrap()) - }, - _ => {} - }); - Ok(track) - } -} - pub trait MixerTrackDevice: Debug + Send + Sync { fn boxed (self) -> Box where Self: Sized + 'static { Box::new(self) diff --git a/crates/tek/src/cli/cli_groovebox.rs b/crates/tek/src/cli/cli_groovebox.rs deleted file mode 100644 index d3339871..00000000 --- a/crates/tek/src/cli/cli_groovebox.rs +++ /dev/null @@ -1,27 +0,0 @@ -#![allow(unused)] -#![allow(clippy::unit_arg)] -include!("../lib.rs"); -pub fn main () -> Usually<()> { - GrooveboxCli::parse().run() -} - -#[derive(Debug, Parser)] -#[command(version, about, long_about = None)] -pub struct GrooveboxCli; - -impl GrooveboxCli { - fn run (&self) -> Usually<()> { - Tui::run(JackClient::new("tek_groovebox")?.activate_with(|jack|{ - let app = GrooveboxTui::try_from(jack)?; - let midi_out = jack.read().unwrap().register_port("out", MidiOut::default())?; - let midi_in_1 = jack.read().unwrap().register_port("in1", MidiIn::default())?; - let midi_in_2 = jack.read().unwrap().register_port("in2", MidiIn::default())?; - let audio_in_1 = jack.read().unwrap().register_port("inL", AudioIn::default())?; - let audio_in_2 = jack.read().unwrap().register_port("inR", AudioIn::default())?; - let audio_out_1 = jack.read().unwrap().register_port("out1", AudioOut::default())?; - let audio_out_2 = jack.read().unwrap().register_port("out2", AudioOut::default())?; - Ok(app) - })?)?; - Ok(()) - } -} diff --git a/crates/tek/src/jack.rs b/crates/tek/src/jack.rs index ee6f698e..0a21d92d 100644 --- a/crates/tek/src/jack.rs +++ b/crates/tek/src/jack.rs @@ -1,10 +1,7 @@ +use crate::*; + pub use ::jack as libjack; -pub(crate) mod activate; #[allow(unused)] pub(crate) use self::activate::*; -pub(crate) mod audio; pub(crate) use self::audio::*; -pub(crate) mod client; pub(crate) use self::client::*; -pub(crate) mod jack_event; pub(crate) use self::jack_event::*; -pub(crate) mod ports; pub(crate) use self::ports::*; -pub(crate) use ::jack::{ +pub use ::jack::{ contrib::ClosureProcessHandler, NotificationHandler, Client, AsyncClient, ClientOptions, ClientStatus, ProcessScope, Control, CycleTimes, Frames, @@ -12,6 +9,20 @@ pub(crate) use ::jack::{ Transport, TransportState, MidiIter, MidiWriter, RawMidi, }; +pub mod activate; +pub(crate) use self::activate::*; +pub use self::activate::JackActivate; + +pub mod client; +pub(crate) use self::client::*; +pub use self::client::JackClient; + +pub mod jack_event; +pub(crate) use self::jack_event::*; + +pub mod ports; +pub(crate) use self::ports::*; + /// Implement [TryFrom<&Arc>>]: create app state from wrapped JACK handle. #[macro_export] macro_rules! from_jack { (|$jack:ident|$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)? $cb:expr) => { @@ -24,6 +35,83 @@ pub(crate) use ::jack::{ }; } +/// Implement [Audio]: provide JACK callbacks. +#[macro_export] macro_rules! audio { + (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident|$cb:expr) => { + impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? { + #[inline] fn process (&mut $self, $c: &Client, $s: &ProcessScope) -> Control { $cb } + } + } +} + +/// Trait for thing that has a JACK process callback. +pub trait Audio: Send + Sync { + fn process (&mut self, _: &Client, _: &ProcessScope) -> Control { + Control::Continue + } + fn callback ( + state: &Arc>, client: &Client, scope: &ProcessScope + ) -> Control where Self: Sized { + if let Ok(mut state) = state.write() { + state.process(client, scope) + } else { + Control::Quit + } + } +} + + +/// Trait for things that wrap a JACK client. +pub trait AudioEngine { + + fn transport (&self) -> Transport { + self.client().transport() + } + + fn port_by_name (&self, name: &str) -> Option> { + self.client().port_by_name(name) + } + + fn register_port (&self, name: &str, spec: PS) -> Usually> { + Ok(self.client().register_port(name, spec)?) + } + + fn client (&self) -> &Client; + + fn activate ( + self, + process: impl FnMut(&Arc>, &Client, &ProcessScope) -> Control + Send + 'static + ) -> Usually>> where Self: Send + Sync + 'static; + + fn thread_init (&self, _: &Client) {} + + unsafe fn shutdown (&mut self, _status: ClientStatus, _reason: &str) {} + + fn freewheel (&mut self, _: &Client, _enabled: bool) {} + + fn client_registration (&mut self, _: &Client, _name: &str, _reg: bool) {} + + fn port_registration (&mut self, _: &Client, _id: PortId, _reg: bool) {} + + fn ports_connected (&mut self, _: &Client, _a: PortId, _b: PortId, _are: bool) {} + + fn sample_rate (&mut self, _: &Client, _frames: Frames) -> Control { + Control::Continue + } + + fn port_rename (&mut self, _: &Client, _id: PortId, _old: &str, _new: &str) -> Control { + Control::Continue + } + + fn graph_reorder (&mut self, _: &Client) -> Control { + Control::Continue + } + + fn xrun (&mut self, _: &Client) -> Control { + Control::Continue + } +} + //////////////////////////////////////////////////////////////////////////////////// ///// A [AudioComponent] bound to a JACK client and a set of ports. diff --git a/crates/tek/src/jack/audio.rs b/crates/tek/src/jack/audio.rs deleted file mode 100644 index b78c7e65..00000000 --- a/crates/tek/src/jack/audio.rs +++ /dev/null @@ -1,78 +0,0 @@ -use crate::*; - -/// Implement [Audio]: provide JACK callbacks. -#[macro_export] macro_rules! audio { - (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident|$cb:expr) => { - impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? { - #[inline] fn process (&mut $self, $c: &Client, $s: &ProcessScope) -> Control { $cb } - } - } -} - -/// Trait for thing that has a JACK process callback. -pub trait Audio: Send + Sync { - fn process (&mut self, _: &Client, _: &ProcessScope) -> Control { - Control::Continue - } - fn callback ( - state: &Arc>, client: &Client, scope: &ProcessScope - ) -> Control where Self: Sized { - if let Ok(mut state) = state.write() { - state.process(client, scope) - } else { - Control::Quit - } - } -} - - -/// Trait for things that wrap a JACK client. -pub trait AudioEngine { - - fn transport (&self) -> Transport { - self.client().transport() - } - - fn port_by_name (&self, name: &str) -> Option> { - self.client().port_by_name(name) - } - - fn register_port (&self, name: &str, spec: PS) -> Usually> { - Ok(self.client().register_port(name, spec)?) - } - - fn client (&self) -> &Client; - - fn activate ( - self, - process: impl FnMut(&Arc>, &Client, &ProcessScope) -> Control + Send + 'static - ) -> Usually>> where Self: Send + Sync + 'static; - - fn thread_init (&self, _: &Client) {} - - unsafe fn shutdown (&mut self, _status: ClientStatus, _reason: &str) {} - - fn freewheel (&mut self, _: &Client, _enabled: bool) {} - - fn client_registration (&mut self, _: &Client, _name: &str, _reg: bool) {} - - fn port_registration (&mut self, _: &Client, _id: PortId, _reg: bool) {} - - fn ports_connected (&mut self, _: &Client, _a: PortId, _b: PortId, _are: bool) {} - - fn sample_rate (&mut self, _: &Client, _frames: Frames) -> Control { - Control::Continue - } - - fn port_rename (&mut self, _: &Client, _id: PortId, _old: &str, _new: &str) -> Control { - Control::Continue - } - - fn graph_reorder (&mut self, _: &Client) -> Control { - Control::Continue - } - - fn xrun (&mut self, _: &Client) -> Control { - Control::Continue - } -} diff --git a/crates/tek/src/lib.rs b/crates/tek/src/lib.rs index 0ab08cec..d25ba66c 100644 --- a/crates/tek/src/lib.rs +++ b/crates/tek/src/lib.rs @@ -1,16 +1,22 @@ -const FOO: () = (); +#![allow(unused)] +#![allow(clippy::unit_arg)] pub mod core; pub use self::core::*; pub mod time; pub(crate) use self::time::*; pub mod space; pub(crate) use self::space::*; -pub mod tui; pub(crate) use self::tui::*; -pub mod edn; -pub mod jack; pub(crate) use self::jack::*; -pub mod midi; pub(crate) use self::midi::*; -pub mod audio; pub(crate) use self::audio::*; -//pub mod plugin; pub(crate) use self::plugin::*; -pub(crate) use clap::{self, Parser}; +pub mod tui; pub(crate) use self::tui::*; +pub use tui::{Tui, TransportTui, SequencerTui, SamplerTui, GrooveboxTui, ArrangerTui}; + +pub mod jack; pub(crate) use self::jack::*; +pub use jack::JackClient; + +pub mod midi; pub(crate) use self::midi::*; + +pub mod audio; pub(crate) use self::audio::*; +pub use audio::{Sampler, Sample, Voice}; + +//pub mod plugin; pub(crate) use self::plugin::*; pub use ::better_panic; pub(crate) use better_panic::{Settings, Verbosity}; @@ -44,13 +50,12 @@ pub(crate) use ratatui::{ backend::{Backend, CrosstermBackend, ClearType} }; -pub use ::midly; +pub use ::midly::{self, num::u7}; pub(crate) use ::midly::{ Smf, MidiMessage, TrackEventKind, live::LiveEvent, - num::u7 }; pub use ::palette; diff --git a/crates/tek/src/plugin/lv2.rs b/crates/tek/src/plugin/lv2.rs index b47c159b..206a79c3 100644 --- a/crates/tek/src/plugin/lv2.rs +++ b/crates/tek/src/plugin/lv2.rs @@ -40,22 +40,3 @@ impl LV2Plugin { }) } } - -impl LV2Plugin { - pub fn from_edn <'e> (jack: &Arc>, args: &[Edn<'e>]) -> Usually { - let mut name = String::new(); - let mut path = String::new(); - 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::Str(p)) = map.get(&Edn::Key(":path")) { - path = String::from(*p); - } - }, - _ => panic!("unexpected in lv2 '{name}'"), - }); - Plugin::new_lv2(jack, &name, &path) - } -} diff --git a/crates/tek/src/tui.rs b/crates/tek/src/tui.rs index 5990d455..35c167ad 100644 --- a/crates/tek/src/tui.rs +++ b/crates/tek/src/tui.rs @@ -15,10 +15,19 @@ mod tui_border; pub(crate) use self::tui_border::*; //////////////////////////////////////////////////////// mod app_transport; #[allow(unused)] pub(crate) use self::app_transport::*; +pub use self::app_transport::TransportTui; + mod app_sequencer; #[allow(unused)] pub(crate) use self::app_sequencer::*; +pub use self::app_sequencer::SequencerTui; + mod app_sampler; #[allow(unused)] pub(crate) use self::app_sampler::*; +pub use self::app_sampler::SamplerTui; + mod app_groovebox; #[allow(unused)] pub(crate) use self::app_groovebox::*; +pub use self::app_groovebox::GrooveboxTui; + mod app_arranger; #[allow(unused)] pub(crate) use self::app_arranger::*; +pub use self::app_arranger::ArrangerTui; /////////////////////////////////////////////////////// diff --git a/crates/tek/src/tui/app_sampler.rs b/crates/tek/src/tui/app_sampler.rs index 75d75a8f..c1fb49b6 100644 --- a/crates/tek/src/tui/app_sampler.rs +++ b/crates/tek/src/tui/app_sampler.rs @@ -13,7 +13,6 @@ use symphonia::{ }, default::get_codecs, }; - pub struct SamplerTui { pub state: Sampler, pub cursor: (usize, usize), From 8652a5e415077cfb31f845ae9ee74012095d362c Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 27 Dec 2024 15:50:06 +0100 Subject: [PATCH 024/815] wip: updates to module architecture --- crates/edn/src/lib.rs | 2 +- crates/tek/src/audio.rs | 6 +++++- crates/tek/src/audio/channel.rs | 27 -------------------------- crates/tek/src/audio/mixer.rs | 30 +++++++++++++++++++++++++---- crates/tek/src/lib.rs | 34 ++++++++++++++------------------- crates/tek/src/plugin.rs | 10 ++++++---- 6 files changed, 52 insertions(+), 57 deletions(-) delete mode 100644 crates/tek/src/audio/channel.rs diff --git a/crates/edn/src/lib.rs b/crates/edn/src/lib.rs index 28b2d67b..a764bfbc 100644 --- a/crates/edn/src/lib.rs +++ b/crates/edn/src/lib.rs @@ -1,4 +1,4 @@ -use tek::{*, jack::*}; +use tek::{*, jack::*, plugin::*, audio::*}; use std::sync::{Arc, RwLock}; use std::collections::BTreeMap; diff --git a/crates/tek/src/audio.rs b/crates/tek/src/audio.rs index 24900710..650bb769 100644 --- a/crates/tek/src/audio.rs +++ b/crates/tek/src/audio.rs @@ -5,5 +5,9 @@ mod audio_in; mod audio_out; mod sampler; -pub(crate) use sampler::*; +pub(crate) use self::sampler::*; pub use self::sampler::{Sampler, Sample, Voice}; + +mod mixer; +pub(crate) use self::mixer::*; +pub use self::mixer::{Mixer, MixerTrack, MixerTrackDevice}; diff --git a/crates/tek/src/audio/channel.rs b/crates/tek/src/audio/channel.rs deleted file mode 100644 index b850147a..00000000 --- a/crates/tek/src/audio/channel.rs +++ /dev/null @@ -1,27 +0,0 @@ -use crate::*; - -pub enum MixerTrackCommand {} - -/// A mixer track. -#[derive(Debug)] -pub struct MixerTrack { - pub name: String, - /// Inputs of 1st device - pub audio_ins: Vec>, - /// Outputs of last device - pub audio_outs: Vec>, - /// Device chain - pub devices: Vec>, -} - -//impl MixerTrackDevice for LV2Plugin {} - -pub trait MixerTrackDevice: Debug + Send + Sync { - fn boxed (self) -> Box where Self: Sized + 'static { - Box::new(self) - } -} - -impl MixerTrackDevice for Sampler {} - -impl MixerTrackDevice for Plugin {} diff --git a/crates/tek/src/audio/mixer.rs b/crates/tek/src/audio/mixer.rs index a0ac9366..0ec986c9 100644 --- a/crates/tek/src/audio/mixer.rs +++ b/crates/tek/src/audio/mixer.rs @@ -8,8 +8,30 @@ pub struct Mixer { pub selected_track: usize, pub selected_column: usize, } -pub struct MixerAudio { - model: Arc> +audio!(|self: Mixer, _client, _scope|Control::Continue); + +pub enum MixerTrackCommand {} + +/// A mixer track. +#[derive(Debug)] +pub struct MixerTrack { + pub name: String, + /// Inputs of 1st device + pub audio_ins: Vec>, + /// Outputs of last device + pub audio_outs: Vec>, + /// Device chain + pub devices: Vec>, } -from!(|mode: &Arc>| MixerAudio = Self { model: model.clone() }); -audio!(|self: MixerAudio, _, _|Control::Continue); + +//impl MixerTrackDevice for LV2Plugin {} + +pub trait MixerTrackDevice: Debug + Send + Sync { + fn boxed (self) -> Box where Self: Sized + 'static { + Box::new(self) + } +} + +impl MixerTrackDevice for Sampler {} + +impl MixerTrackDevice for Plugin {} diff --git a/crates/tek/src/lib.rs b/crates/tek/src/lib.rs index d25ba66c..34c1f2cb 100644 --- a/crates/tek/src/lib.rs +++ b/crates/tek/src/lib.rs @@ -2,30 +2,27 @@ #![allow(clippy::unit_arg)] pub mod core; pub use self::core::*; + pub mod time; pub(crate) use self::time::*; + pub mod space; pub(crate) use self::space::*; -pub mod tui; pub(crate) use self::tui::*; -pub use tui::{Tui, TransportTui, SequencerTui, SamplerTui, GrooveboxTui, ArrangerTui}; +pub mod tui; pub(crate) use self::tui::*; pub use tui::*; -pub mod jack; pub(crate) use self::jack::*; -pub use jack::JackClient; +pub mod jack; pub(crate) use self::jack::*; pub use self::jack::*; -pub mod midi; pub(crate) use self::midi::*; +pub mod midi; pub(crate) use self::midi::*; -pub mod audio; pub(crate) use self::audio::*; -pub use audio::{Sampler, Sample, Voice}; +pub mod audio; pub(crate) use self::audio::*; pub use self::audio::*; -//pub mod plugin; pub(crate) use self::plugin::*; +pub mod plugin; pub(crate) use self::plugin::*; pub use self::plugin::*; -pub use ::better_panic; -pub(crate) use better_panic::{Settings, Verbosity}; +pub use ::better_panic; pub(crate) use better_panic::{Settings, Verbosity}; -pub use ::atomic_float; -pub(crate) use atomic_float::*; +pub use ::atomic_float; pub(crate) use atomic_float::*; pub(crate) use std::sync::{Arc, Mutex, RwLock}; -#[allow(unused)] pub(crate) use std::sync::atomic::Ordering; +pub(crate) use std::sync::atomic::Ordering; pub(crate) use std::sync::atomic::{AtomicBool, AtomicUsize}; pub(crate) use std::collections::BTreeMap; pub(crate) use std::marker::PhantomData; @@ -36,30 +33,27 @@ pub(crate) use std::time::Duration; pub(crate) use std::io::{Stdout, stdout}; pub(crate) use std::ops::{Add, Sub, Mul, Div, Rem}; pub(crate) use std::cmp::{Ord, Eq, PartialEq}; -pub(crate) use std::fmt::{Debug, Display}; +pub(crate) use std::fmt::{Debug, Display, Formatter}; pub use ::crossterm; pub(crate) use crossterm::{ExecutableCommand}; pub(crate) use crossterm::terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode}; pub(crate) use crossterm::event::{KeyCode, KeyModifiers, KeyEvent, KeyEventKind, KeyEventState}; -pub use ::ratatui; -pub(crate) use ratatui::{ +pub use ::ratatui; pub(crate) use ratatui::{ prelude::{Style, Color, Buffer}, style::{Stylize, Modifier}, backend::{Backend, CrosstermBackend, ClearType} }; -pub use ::midly::{self, num::u7}; -pub(crate) use ::midly::{ +pub use ::midly::{self, num::u7}; pub(crate) use ::midly::{ Smf, MidiMessage, TrackEventKind, live::LiveEvent, }; -pub use ::palette; -pub(crate) use ::palette::{ +pub use ::palette; pub(crate) use ::palette::{ *, convert::*, okhsl::* diff --git a/crates/tek/src/plugin.rs b/crates/tek/src/plugin.rs index efbe4cd0..9f347709 100644 --- a/crates/tek/src/plugin.rs +++ b/crates/tek/src/plugin.rs @@ -1,6 +1,8 @@ -pub(crate) mod lv2; pub(crate) use lv2::*; use crate::*; +pub mod lv2; pub(crate) use lv2::*; +pub use self::lv2::LV2Plugin; + /// A plugin device. #[derive(Debug)] pub struct Plugin { @@ -22,12 +24,12 @@ pub struct Plugin { pub enum PluginKind { #[default] None, LV2(LV2Plugin), - VST2 { instance: ::vst::host::PluginInstance }, + VST2 { instance: () /*::vst::host::PluginInstance*/ }, VST3, } impl Debug for PluginKind { - fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), Error> { + fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { write!(f, "{}", match self { Self::None => "(none)", Self::LV2(_) => "LV2", @@ -77,7 +79,7 @@ impl Plugin { pub struct PluginAudio(Arc>); from!(|model: &Arc>| PluginAudio = Self(model.clone())); -audio!(|self: PluginAudio, client_, _scope|{ +audio!(|self: PluginAudio, client, scope|{ let state = &mut*self.0.write().unwrap(); match state.plugin.as_mut() { Some(PluginKind::LV2(LV2Plugin { From a64925ba8c6348d020b4ab27c79abb456c0fff0b Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 27 Dec 2024 16:00:31 +0100 Subject: [PATCH 025/815] somehow, no warnings --- crates/cli/src/cli_groovebox.rs | 14 +-- crates/cli/src/lib.rs | 6 +- crates/edn/src/lib.rs | 179 +++++++++++++-------------- crates/tek/src/core.rs | 5 +- crates/tek/src/lib.rs | 6 +- crates/tek/src/tui/app_sequencer.rs | 18 +-- crates/tek/src/tui/arranger_track.rs | 8 +- 7 files changed, 118 insertions(+), 118 deletions(-) diff --git a/crates/cli/src/cli_groovebox.rs b/crates/cli/src/cli_groovebox.rs index f4c10ac2..ae7abab1 100644 --- a/crates/cli/src/cli_groovebox.rs +++ b/crates/cli/src/cli_groovebox.rs @@ -8,13 +8,13 @@ impl GrooveboxCli { Tui::run(JackClient::new("tek_groovebox")?.activate_with(|jack|{ let app = tek::tui::GrooveboxTui::try_from(jack)?; let jack = jack.read().unwrap(); - let midi_out = jack.register_port("out", MidiOut::default())?; - let midi_in_1 = jack.register_port("in1", MidiIn::default())?; - let midi_in_2 = jack.register_port("in2", MidiIn::default())?; - let audio_in_1 = jack.register_port("inL", AudioIn::default())?; - let audio_in_2 = jack.register_port("inR", AudioIn::default())?; - let audio_out_1 = jack.register_port("out1", AudioOut::default())?; - let audio_out_2 = jack.register_port("out2", AudioOut::default())?; + let _midi_out = jack.register_port("out", MidiOut::default())?; + let _midi_in_1 = jack.register_port("in1", MidiIn::default())?; + let _midi_in_2 = jack.register_port("in2", MidiIn::default())?; + let _audio_in_1 = jack.register_port("inL", AudioIn::default())?; + let _audio_in_2 = jack.register_port("inR", AudioIn::default())?; + let _audio_out_1 = jack.register_port("out1", AudioOut::default())?; + let _audio_out_2 = jack.register_port("out2", AudioOut::default())?; Ok(app) })?)?; Ok(()) diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index 5fc4389e..4b1f648a 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -1,3 +1,3 @@ -use std::sync::Arc; -use clap::{self, Parser}; -use tek::{*, jack::*}; +#[allow(unused_imports)] use std::sync::Arc; +#[allow(unused_imports)] use clap::{self, Parser}; +#[allow(unused_imports)] use tek::{*, jack::*}; diff --git a/crates/edn/src/lib.rs b/crates/edn/src/lib.rs index a764bfbc..356c362a 100644 --- a/crates/edn/src/lib.rs +++ b/crates/edn/src/lib.rs @@ -1,27 +1,10 @@ -use tek::{*, jack::*, plugin::*, audio::*}; +#[allow(unused_imports)] use tek::{*, jack::*, plugin::*, audio::*}; use std::sync::{Arc, RwLock}; use std::collections::BTreeMap; pub use clojure_reader::edn::Edn; //pub use clojure_reader::{edn::{read, Edn}, error::Error as EdnError}; -pub trait FromEdn: Sized { - const ID: &'static str; - fn from_edn (context: C, expr: &[Edn<'_>]) -> Usually; -} - -/// Implements the [FromEdn] trait. -#[macro_export] macro_rules! from_edn { - (|$context:pat = $Context:ty, $id:expr, $args:ident| -> $T:ty $body:block) => { - impl FromEdn<$Context> for $T { - const ID: &'static str = $id; - fn from_edn <'e> ($context: $Context, $args: &[Edn<'e>]) -> Usually { - $body - } - } - } -} - /// EDN parsing helper. #[macro_export] macro_rules! edn { ($edn:ident { $($pat:pat => $expr:expr),* $(,)? }) => { @@ -34,7 +17,24 @@ pub trait FromEdn: Sized { }; } -from_edn!(|jack = &Arc>, "sampler", args| -> crate::Sampler { +pub trait FromEdn: Sized { + const ID: &'static str; + fn from_edn (context: C, expr: &[Edn<'_>]) -> Usually; +} + +/// Implements the [FromEdn] trait. +#[macro_export] macro_rules! from_edn { + ($id:expr => |$context:tt:$Context:ty, $args:ident| -> $T:ty $body:block) => { + impl FromEdn<$Context> for $T { + const ID: &'static str = $id; + fn from_edn <'e> ($context: $Context, $args: &[Edn<'e>]) -> Usually { + $body + } + } + } +} + +from_edn!("sampler" => |jack: &Arc>, args| -> crate::Sampler { let mut name = String::new(); let mut dir = String::new(); let mut samples = BTreeMap::new(); @@ -76,7 +76,7 @@ from_edn!(|jack = &Arc>, "sampler", args| -> crate::Sampler { type MidiSample = (Option, Arc>); -from_edn!(|(jack, dir) = (&Arc>, &str), "sample", args| -> MidiSample { +from_edn!("sample" => |(_jack, dir): (&Arc>, &str), args| -> MidiSample { let mut name = String::new(); let mut file = String::new(); let mut midi = None; @@ -108,80 +108,77 @@ from_edn!(|(jack, dir) = (&Arc>, &str), "sample", args| -> Mi })))) }); -impl LV2Plugin { - pub fn from_edn <'e> (jack: &Arc>, args: &[Edn<'e>]) -> Usually { - let mut name = String::new(); - let mut path = String::new(); - 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::Str(p)) = map.get(&Edn::Key(":path")) { - path = String::from(*p); - } - }, - _ => panic!("unexpected in lv2 '{name}'"), - }); - Plugin::new_lv2(jack, &name, &path) - } -} +from_edn!("plugin/lv2" => |jack: &Arc>, args| -> Plugin { + let mut name = String::new(); + let mut path = String::new(); + 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::Str(p)) = map.get(&Edn::Key(":path")) { + path = String::from(*p); + } + }, + _ => panic!("unexpected in lv2 '{name}'"), + }); + Plugin::new_lv2(jack, &name, &path) +}); -impl MixerTrack { - const SYM_NAME: &'static str = ":name"; - const SYM_GAIN: &'static str = ":gain"; - const SYM_SAMPLER: &'static str = "sampler"; - const SYM_LV2: &'static str = "lv2"; - pub fn from_edn <'a, 'e> (jack: &Arc>, args: &[Edn<'e>]) -> Usually { - let mut _gain = 0.0f64; - let mut track = MixerTrack { - name: String::new(), - audio_ins: vec![], - audio_outs: vec![], - devices: vec![], - }; - edn!(edn in args { - Edn::Map(map) => { - if let Some(Edn::Str(n)) = map.get(&Edn::Key(Self::SYM_NAME)) { - track.name = n.to_string(); - } - if let Some(Edn::Double(g)) = map.get(&Edn::Key(Self::SYM_GAIN)) { - _gain = f64::from(*g); - } +const SYM_NAME: &'static str = ":name"; +const SYM_GAIN: &'static str = ":gain"; +const SYM_SAMPLER: &'static str = "sampler"; +const SYM_LV2: &'static str = "lv2"; + +from_edn!("mixer/track" => |jack: &Arc>, args| -> MixerTrack { + let mut _gain = 0.0f64; + let mut track = MixerTrack { + name: String::new(), + audio_ins: vec![], + audio_outs: vec![], + devices: vec![], + }; + edn!(edn in args { + Edn::Map(map) => { + if let Some(Edn::Str(n)) = map.get(&Edn::Key(SYM_NAME)) { + track.name = n.to_string(); + } + if let Some(Edn::Double(g)) = map.get(&Edn::Key(SYM_GAIN)) { + _gain = f64::from(*g); + } + }, + Edn::List(args) => match args.get(0) { + // Add a sampler device to the track + Some(Edn::Symbol(SYM_SAMPLER)) => { + track.devices.push( + Box::new(Sampler::from_edn(jack, &args[1..])?) as Box + ); + panic!( + "unsupported in track {}: {:?}; tek_mixer not compiled with feature \"sampler\"", + &track.name, + args.get(0).unwrap() + ) }, - Edn::List(args) => match args.get(0) { - // Add a sampler device to the track - Some(Edn::Symbol(Self::SYM_SAMPLER)) => { - track.devices.push( - Box::new(Sampler::from_edn(jack, &args[1..])?) as Box - ); - panic!( - "unsupported in track {}: {:?}; tek_mixer not compiled with feature \"sampler\"", - &track.name, - args.get(0).unwrap() - ) - }, - // Add a LV2 plugin to the track. - Some(Edn::Symbol(Self::SYM_LV2)) => { - track.devices.push( - Box::new(LV2Plugin::from_edn(jack, &args[1..])?) as Box - ); - panic!( - "unsupported in track {}: {:?}; tek_mixer not compiled with feature \"plugin\"", - &track.name, - args.get(0).unwrap() - ) - }, - None => - panic!("empty list track {}", &track.name), - _ => - panic!("unexpected in track {}: {:?}", &track.name, args.get(0).unwrap()) + // Add a LV2 plugin to the track. + Some(Edn::Symbol(SYM_LV2)) => { + track.devices.push( + Box::new(Plugin::from_edn(jack, &args[1..])?) as Box + ); + panic!( + "unsupported in track {}: {:?}; tek_mixer not compiled with feature \"plugin\"", + &track.name, + args.get(0).unwrap() + ) }, - _ => {} - }); - Ok(track) - } -} + None => + panic!("empty list track {}", &track.name), + _ => + panic!("unexpected in track {}: {:?}", &track.name, args.get(0).unwrap()) + }, + _ => {} + }); + Ok(track) +}); //impl ArrangerScene { diff --git a/crates/tek/src/core.rs b/crates/tek/src/core.rs index 180d527c..40b42d85 100644 --- a/crates/tek/src/core.rs +++ b/crates/tek/src/core.rs @@ -1,6 +1,9 @@ pub(crate) use std::error::Error; -pub(crate) mod color; pub(crate) use color::*; +pub(crate) mod color; +pub(crate) use color::*; +pub use color::*; + pub(crate) mod command; pub(crate) use command::*; pub(crate) mod engine; pub(crate) use engine::*; pub(crate) mod focus; pub(crate) use focus::*; diff --git a/crates/tek/src/lib.rs b/crates/tek/src/lib.rs index 34c1f2cb..67cf5166 100644 --- a/crates/tek/src/lib.rs +++ b/crates/tek/src/lib.rs @@ -1,9 +1,9 @@ #![allow(unused)] #![allow(clippy::unit_arg)] -pub mod core; pub use self::core::*; +pub mod core; pub use self::core::*; -pub mod time; pub(crate) use self::time::*; +pub mod time; pub(crate) use self::time::*; pub mod space; pub(crate) use self::space::*; @@ -41,7 +41,7 @@ pub(crate) use crossterm::terminal::{EnterAlternateScreen, LeaveAlternateScreen, pub(crate) use crossterm::event::{KeyCode, KeyModifiers, KeyEvent, KeyEventKind, KeyEventState}; pub use ::ratatui; pub(crate) use ratatui::{ - prelude::{Style, Color, Buffer}, + prelude::{Style, Buffer}, style::{Stylize, Modifier}, backend::{Backend, CrosstermBackend, ClearType} }; diff --git a/crates/tek/src/tui/app_sequencer.rs b/crates/tek/src/tui/app_sequencer.rs index 9657cfb2..5bc6a43e 100644 --- a/crates/tek/src/tui/app_sequencer.rs +++ b/crates/tek/src/tui/app_sequencer.rs @@ -7,15 +7,15 @@ use PhrasePoolCommand::*; /// Root view for standalone `tek_sequencer`. pub struct SequencerTui { _jack: Arc>, - pub(crate) clock: ClockModel, - pub(crate) phrases: PoolModel, - pub(crate) player: MidiPlayer, - pub(crate) editor: MidiEditorModel, - pub(crate) size: Measure, - pub(crate) status: bool, - pub(crate) note_buf: Vec, - pub(crate) midi_buf: Vec>>, - pub(crate) perf: PerfModel, + pub clock: ClockModel, + pub phrases: PoolModel, + pub player: MidiPlayer, + pub editor: MidiEditorModel, + pub size: Measure, + pub status: bool, + pub note_buf: Vec, + pub midi_buf: Vec>>, + pub perf: PerfModel, } from_jack!(|jack|SequencerTui { let clock = ClockModel::from(jack); diff --git a/crates/tek/src/tui/arranger_track.rs b/crates/tek/src/tui/arranger_track.rs index 6fb36583..52814a32 100644 --- a/crates/tek/src/tui/arranger_track.rs +++ b/crates/tek/src/tui/arranger_track.rs @@ -26,13 +26,13 @@ impl ArrangerTui { } #[derive(Debug)] pub struct ArrangerTrack { /// Name of track - pub(crate) name: Arc>, + pub name: Arc>, /// Preferred width of track column - pub(crate) width: usize, + pub width: usize, /// Identifying color of track - pub(crate) color: ItemPalette, + pub color: ItemPalette, /// MIDI player state - pub(crate) player: MidiPlayer, + pub player: MidiPlayer, } has_clock!(|self:ArrangerTrack|self.player.clock()); has_player!(|self:ArrangerTrack|self.player); From fa9f7f8aafe70530a6baf7867e7094cca0af22a4 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 27 Dec 2024 16:12:58 +0100 Subject: [PATCH 026/815] fix some more lints --- crates/edn/src/lib.rs | 16 +-- crates/tek/src/core/command.rs | 2 +- crates/tek/src/core/focus.rs | 19 +--- crates/tek/src/core/input.rs | 2 +- crates/tek/src/core/output.rs | 2 +- crates/tek/src/plugin.rs | 40 +++---- crates/tek/src/plugin/lv2.rs | 6 +- crates/tek/src/space.rs | 169 +++++++++++++++++++++++++++++- crates/tek/src/space/area.rs | 73 ------------- crates/tek/src/space/coord.rs | 37 ------- crates/tek/src/space/direction.rs | 28 ----- crates/tek/src/space/size.rs | 26 ----- crates/tek/src/space/split.rs | 17 +-- crates/tek/src/space/stack.rs | 21 ++-- crates/tek/src/time/clock.rs | 2 +- 15 files changed, 224 insertions(+), 236 deletions(-) delete mode 100644 crates/tek/src/space/area.rs delete mode 100644 crates/tek/src/space/coord.rs delete mode 100644 crates/tek/src/space/direction.rs delete mode 100644 crates/tek/src/space/size.rs diff --git a/crates/edn/src/lib.rs b/crates/edn/src/lib.rs index 356c362a..a6b51214 100644 --- a/crates/edn/src/lib.rs +++ b/crates/edn/src/lib.rs @@ -125,10 +125,10 @@ from_edn!("plugin/lv2" => |jack: &Arc>, args| -> Plugin { Plugin::new_lv2(jack, &name, &path) }); -const SYM_NAME: &'static str = ":name"; -const SYM_GAIN: &'static str = ":gain"; -const SYM_SAMPLER: &'static str = "sampler"; -const SYM_LV2: &'static str = "lv2"; +const SYM_NAME: &str = ":name"; +const SYM_GAIN: &str = ":gain"; +const SYM_SAMPLER: &str = "sampler"; +const SYM_LV2: &str = "lv2"; from_edn!("mixer/track" => |jack: &Arc>, args| -> MixerTrack { let mut _gain = 0.0f64; @@ -147,7 +147,7 @@ from_edn!("mixer/track" => |jack: &Arc>, args| -> MixerTrack _gain = f64::from(*g); } }, - Edn::List(args) => match args.get(0) { + Edn::List(args) => match args.first() { // Add a sampler device to the track Some(Edn::Symbol(SYM_SAMPLER)) => { track.devices.push( @@ -156,7 +156,7 @@ from_edn!("mixer/track" => |jack: &Arc>, args| -> MixerTrack panic!( "unsupported in track {}: {:?}; tek_mixer not compiled with feature \"sampler\"", &track.name, - args.get(0).unwrap() + args.first().unwrap() ) }, // Add a LV2 plugin to the track. @@ -167,13 +167,13 @@ from_edn!("mixer/track" => |jack: &Arc>, args| -> MixerTrack panic!( "unsupported in track {}: {:?}; tek_mixer not compiled with feature \"plugin\"", &track.name, - args.get(0).unwrap() + args.first().unwrap() ) }, None => panic!("empty list track {}", &track.name), _ => - panic!("unexpected in track {}: {:?}", &track.name, args.get(0).unwrap()) + panic!("unexpected in track {}: {:?}", &track.name, args.first().unwrap()) }, _ => {} }); diff --git a/crates/tek/src/core/command.rs b/crates/tek/src/core/command.rs index 1f59e548..8740193b 100644 --- a/crates/tek/src/core/command.rs +++ b/crates/tek/src/core/command.rs @@ -38,7 +38,7 @@ pub fn delegate , S> ( wrap: impl Fn(C)->B, state: &mut S, ) -> Perhaps { - Ok(cmd.execute(state)?.map(|x|wrap(x))) + Ok(cmd.execute(state)?.map(wrap)) } pub trait InputToCommand: Command + Sized { diff --git a/crates/tek/src/core/focus.rs b/crates/tek/src/core/focus.rs index 0bae0cb6..00db730a 100644 --- a/crates/tek/src/core/focus.rs +++ b/crates/tek/src/core/focus.rs @@ -19,18 +19,10 @@ impl FocusState { Self::Entered(_) => Self::Entered(inner), } } - pub fn is_focused (&self) -> bool { - if let Self::Focused(_) = self { true } else { false } - } - pub fn is_entered (&self) -> bool { - if let Self::Entered(_) = self { true } else { false } - } - pub fn to_focused (&mut self) { - *self = Self::Focused(self.inner()) - } - pub fn to_entered (&mut self) { - *self = Self::Entered(self.inner()) - } + pub fn is_focused (&self) -> bool { matches!(self, Self::Focused(_)) } + pub fn is_entered (&self) -> bool { matches!(self, Self::Entered(_)) } + pub fn focus (&mut self) { *self = Self::Focused(self.inner()) } + pub fn enter (&mut self) { *self = Self::Entered(self.inner()) } } #[derive(Copy, Clone, PartialEq, Debug)] @@ -252,8 +244,7 @@ impl FocusOrder for T { } pub trait FocusWrap { - fn wrap <'a, W: Render> (self, focus: T, content: &'a W) - -> impl Render + 'a; + fn wrap > (self, focus: T, content: &'_ W) -> impl Render + '_; } pub fn to_focus_command (input: &TuiInput) -> Option> { diff --git a/crates/tek/src/core/input.rs b/crates/tek/src/core/input.rs index 19c42b3b..9686a533 100644 --- a/crates/tek/src/core/input.rs +++ b/crates/tek/src/core/input.rs @@ -45,7 +45,7 @@ impl> Handle for Option { impl Handle for Mutex where H: Handle { fn handle (&mut self, context: &E::Input) -> Perhaps { - self.lock().unwrap().handle(context) + self.get_mut().unwrap().handle(context) } } diff --git a/crates/tek/src/core/output.rs b/crates/tek/src/core/output.rs index 8798251f..5ee2a75d 100644 --- a/crates/tek/src/core/output.rs +++ b/crates/tek/src/core/output.rs @@ -75,7 +75,7 @@ impl Render for &dyn Render { //} //} -impl<'a, E: Engine> Render for Box + 'a> { +impl Render for Box + '_> { fn min_size (&self, to: E::Size) -> Perhaps { (**self).min_size(to) } diff --git a/crates/tek/src/plugin.rs b/crates/tek/src/plugin.rs index 9f347709..b728bfd1 100644 --- a/crates/tek/src/plugin.rs +++ b/crates/tek/src/plugin.rs @@ -57,24 +57,6 @@ impl Plugin { audio_outs: vec![], }) } - - //fn jack_from_lv2 (name: &str, plugin: &::livi::Plugin) -> Usually { - //let counts = plugin.port_counts(); - //let mut jack = Jack::new(name)?; - //for i in 0..counts.atom_sequence_inputs { - //jack = jack.midi_in(&format!("midi-in-{i}")) - //} - //for i in 0..counts.atom_sequence_outputs { - //jack = jack.midi_out(&format!("midi-out-{i}")); - //} - //for i in 0..counts.audio_inputs { - //jack = jack.audio_in(&format!("audio-in-{i}")); - //} - //for i in 0..counts.audio_outputs { - //jack = jack.audio_out(&format!("audio-out-{i}")); - //} - //Ok(jack) - //} } pub struct PluginAudio(Arc>); @@ -110,7 +92,7 @@ audio!(|self: PluginAudio, client, scope|{ let mut outputs = vec![]; for _ in state.midi_outs.iter() { outputs.push(::livi::event::LV2AtomSequence::new( - &features, + features, scope.n_frames() as usize )); } @@ -123,7 +105,25 @@ audio!(|self: PluginAudio, client, scope|{ instance.run(scope.n_frames() as usize, ports).unwrap() }; }, - _ => {} + _ => todo!("only lv2 is supported") } Control::Continue }); + + //fn jack_from_lv2 (name: &str, plugin: &::livi::Plugin) -> Usually { + //let counts = plugin.port_counts(); + //let mut jack = Jack::new(name)?; + //for i in 0..counts.atom_sequence_inputs { + //jack = jack.midi_in(&format!("midi-in-{i}")) + //} + //for i in 0..counts.atom_sequence_outputs { + //jack = jack.midi_out(&format!("midi-out-{i}")); + //} + //for i in 0..counts.audio_inputs { + //jack = jack.audio_in(&format!("audio-in-{i}")); + //} + //for i in 0..counts.audio_outputs { + //jack = jack.audio_out(&format!("audio-out-{i}")); + //} + //Ok(jack) + //} diff --git a/crates/tek/src/plugin/lv2.rs b/crates/tek/src/plugin/lv2.rs index 206a79c3..4beddcf0 100644 --- a/crates/tek/src/plugin/lv2.rs +++ b/crates/tek/src/plugin/lv2.rs @@ -21,10 +21,8 @@ impl LV2Plugin { min_block_length: 1, max_block_length: 65536, }); - let plugin = world - .iter_plugins() - .nth(0) - .expect(&format!("plugin not found: {uri}")); + let plugin = world.iter_plugins().nth(0) + .unwrap_or_else(||panic!("plugin not found: {uri}")); Ok(Self { instance: unsafe { plugin diff --git a/crates/tek/src/space.rs b/crates/tek/src/space.rs index 1934f727..ec0cbd7e 100644 --- a/crates/tek/src/space.rs +++ b/crates/tek/src/space.rs @@ -1,7 +1,6 @@ -pub(crate) mod coord; pub(crate) use coord::*; -pub(crate) mod size; pub(crate) use size::*; -pub(crate) mod area; pub(crate) use area::*; -pub(crate) mod direction; pub(crate) use direction::*; +use crate::*; +use std::ops::{Add, Sub, Mul, Div}; +use std::fmt::{Display, Debug}; // TODO: return impl Point and impl Size instead of [N;x] // to disambiguate between usage of 2-"tuple"s @@ -28,3 +27,165 @@ pub use self::{ bsp::*, fill::*, }; + +#[derive(Copy, Clone, PartialEq)] +pub enum Direction { North, South, West, East, } + +impl Direction { + pub fn is_north (&self) -> bool { matches!(self, Self::North) } + pub fn is_south (&self) -> bool { matches!(self, Self::South) } + pub fn is_east (&self) -> bool { matches!(self, Self::West) } + pub fn is_west (&self) -> bool { matches!(self, Self::East) } + /// Return next direction clockwise + pub fn cw (&self) -> Self { + match self { + Self::North => Self::East, + Self::South => Self::West, + Self::West => Self::North, + Self::East => Self::South, + } + } + /// Return next direction counterclockwise + pub fn ccw (&self) -> Self { + match self { + Self::North => Self::West, + Self::South => Self::East, + Self::West => Self::South, + Self::East => Self::North, + } + } +} + +/// Standard numeric type. +pub trait Coordinate: Send + Sync + Copy + + Add + + Sub + + Mul + + Div + + Ord + PartialEq + Eq + + Debug + Display + Default + + From + Into + + Into + + Into +{ + fn minus (self, other: Self) -> Self { + if self >= other { + self - other + } else { + 0.into() + } + } + fn zero () -> Self { + 0.into() + } +} + +impl Coordinate for T where T: Send + Sync + Copy + + Add + + Sub + + Mul + + Div + + Ord + PartialEq + Eq + + Debug + Display + Default + + From + Into + + Into + + Into +{} + +pub trait Size { + fn x (&self) -> N; + fn y (&self) -> N; + #[inline] fn w (&self) -> N { self.x() } + #[inline] fn h (&self) -> N { self.y() } + #[inline] fn wh (&self) -> [N;2] { [self.x(), self.y()] } + #[inline] fn clip_w (&self, w: N) -> [N;2] { [self.w().min(w), self.h()] } + #[inline] fn clip_h (&self, h: N) -> [N;2] { [self.w(), self.h().min(h)] } + #[inline] fn expect_min (&self, w: N, h: N) -> Usually<&Self> { + if self.w() < w || self.h() < h { + Err(format!("min {w}x{h}").into()) + } else { + Ok(self) + } + } +} +impl Size for (N, N) { + fn x (&self) -> N { self.0 } + fn y (&self) -> N { self.1 } +} + +impl Size for [N;2] { + fn x (&self) -> N { self[0] } + fn y (&self) -> N { self[1] } +} + +pub trait Area: Copy { + fn x (&self) -> N; + fn y (&self) -> N; + fn w (&self) -> N; + fn h (&self) -> N; + fn x2 (&self) -> N { self.x() + self.w() } + fn y2 (&self) -> N { self.y() + self.h() } + #[inline] fn wh (&self) -> [N;2] { [self.w(), self.h()] } + #[inline] fn xywh (&self) -> [N;4] { [self.x(), self.y(), self.w(), self.h()] } + #[inline] fn lrtb (&self) -> [N;4] { [self.x(), self.x2(), self.y(), self.y2()] } + #[inline] fn push_x (&self, x: N) -> [N;4] { [self.x() + x, self.y(), self.w(), self.h()] } + #[inline] fn push_y (&self, y: N) -> [N;4] { [self.x(), self.y() + y, self.w(), self.h()] } + #[inline] fn shrink_x (&self, x: N) -> [N;4] { + [self.x(), self.y(), self.w().minus(x), self.h()] + } + #[inline] fn shrink_y (&self, y: N) -> [N;4] { + [self.x(), self.y(), self.w(), self.h().minus(y)] + } + #[inline] fn set_w (&self, w: N) -> [N;4] { [self.x(), self.y(), w, self.h()] } + #[inline] fn set_h (&self, h: N) -> [N;4] { [self.x(), self.y(), self.w(), h] } + #[inline] fn clip_h (&self, h: N) -> [N;4] { + [self.x(), self.y(), self.w(), self.h().min(h)] + } + #[inline] fn clip_w (&self, w: N) -> [N;4] { + [self.x(), self.y(), self.w().min(w), self.h()] + } + #[inline] fn clip (&self, wh: impl Size) -> [N;4] { + [self.x(), self.y(), wh.w(), wh.h()] + } + #[inline] fn expect_min (&self, w: N, h: N) -> Usually<&Self> { + if self.w() < w || self.h() < h { + Err(format!("min {w}x{h}").into()) + } else { + Ok(self) + } + } + #[inline] fn split_fixed (&self, direction: Direction, a: N) -> ([N;4],[N;4]) { + match direction { + Direction::North => ( + [self.x(), (self.y()+self.h()).minus(a), self.w(), a], + [self.x(), self.y(), self.w(), self.h().minus(a)], + ), + Direction::South => ( + [self.x(), self.y(), self.w(), a], + [self.x(), self.y() + a, self.w(), self.h().minus(a)], + ), + Direction::East => ( + [self.x(), self.y(), a, self.h()], + [self.x() + a, self.y(), self.w().minus(a), self.h()], + ), + Direction::West => ( + [self.x() + self.w() - a, self.y(), a, self.h()], + [self.x(), self.y(), self.w() - a, self.h()], + ), + } + } +} + +impl Area for (N, N, N, N) { + #[inline] fn x (&self) -> N { self.0 } + #[inline] fn y (&self) -> N { self.1 } + #[inline] fn w (&self) -> N { self.2 } + #[inline] fn h (&self) -> N { self.3 } +} + +impl Area for [N;4] { + #[inline] fn x (&self) -> N { self[0] } + #[inline] fn y (&self) -> N { self[1] } + #[inline] fn w (&self) -> N { self[2] } + #[inline] fn h (&self) -> N { self[3] } +} diff --git a/crates/tek/src/space/area.rs b/crates/tek/src/space/area.rs deleted file mode 100644 index c1c8d350..00000000 --- a/crates/tek/src/space/area.rs +++ /dev/null @@ -1,73 +0,0 @@ -use crate::*; - -pub trait Area: Copy { - fn x (&self) -> N; - fn y (&self) -> N; - fn w (&self) -> N; - fn h (&self) -> N; - fn x2 (&self) -> N { self.x() + self.w() } - fn y2 (&self) -> N { self.y() + self.h() } - #[inline] fn wh (&self) -> [N;2] { [self.w(), self.h()] } - #[inline] fn xywh (&self) -> [N;4] { [self.x(), self.y(), self.w(), self.h()] } - #[inline] fn lrtb (&self) -> [N;4] { [self.x(), self.x2(), self.y(), self.y2()] } - #[inline] fn push_x (&self, x: N) -> [N;4] { [self.x() + x, self.y(), self.w(), self.h()] } - #[inline] fn push_y (&self, y: N) -> [N;4] { [self.x(), self.y() + y, self.w(), self.h()] } - #[inline] fn shrink_x (&self, x: N) -> [N;4] { - [self.x(), self.y(), self.w().minus(x), self.h()] - } - #[inline] fn shrink_y (&self, y: N) -> [N;4] { - [self.x(), self.y(), self.w(), self.h().minus(y)] - } - #[inline] fn set_w (&self, w: N) -> [N;4] { [self.x(), self.y(), w, self.h()] } - #[inline] fn set_h (&self, h: N) -> [N;4] { [self.x(), self.y(), self.w(), h] } - #[inline] fn clip_h (&self, h: N) -> [N;4] { - [self.x(), self.y(), self.w(), self.h().min(h.into())] - } - #[inline] fn clip_w (&self, w: N) -> [N;4] { - [self.x(), self.y(), self.w().min(w.into()), self.h()] - } - #[inline] fn clip (&self, wh: impl Size) -> [N;4] { - [self.x(), self.y(), wh.w(), wh.h()] - } - #[inline] fn expect_min (&self, w: N, h: N) -> Usually<&Self> { - if self.w() < w || self.h() < h { - Err(format!("min {w}x{h}").into()) - } else { - Ok(self) - } - } - #[inline] fn split_fixed (&self, direction: Direction, a: N) -> ([N;4],[N;4]) { - match direction { - Direction::Up => ( - [self.x(), (self.y()+self.h()).minus(a), self.w(), a], - [self.x(), self.y(), self.w(), self.h().minus(a)], - ), - Direction::Down => ( - [self.x(), self.y(), self.w(), a], - [self.x(), self.y() + a, self.w(), self.h().minus(a)], - ), - Direction::Right => ( - [self.x(), self.y(), a, self.h()], - [self.x() + a, self.y(), self.w().minus(a), self.h()], - ), - Direction::Left => ( - [self.x() + self.w() - a, self.y(), a, self.h()], - [self.x(), self.y(), self.w() - a, self.h()], - ), - } - } -} - -impl Area for (N, N, N, N) { - #[inline] fn x (&self) -> N { self.0 } - #[inline] fn y (&self) -> N { self.1 } - #[inline] fn w (&self) -> N { self.2 } - #[inline] fn h (&self) -> N { self.3 } -} - -impl Area for [N;4] { - #[inline] fn x (&self) -> N { self[0] } - #[inline] fn y (&self) -> N { self[1] } - #[inline] fn w (&self) -> N { self[2] } - #[inline] fn h (&self) -> N { self[3] } -} diff --git a/crates/tek/src/space/coord.rs b/crates/tek/src/space/coord.rs deleted file mode 100644 index 67c8ddcf..00000000 --- a/crates/tek/src/space/coord.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::*; - -/// Standard numeric type. -pub trait Coordinate: Send + Sync + Copy - + Add - + Sub - + Mul - + Div - + Ord + PartialEq + Eq - + Debug + Display + Default - + From + Into - + Into - + Into -{ - fn minus (self, other: Self) -> Self { - if self >= other { - self - other - } else { - 0.into() - } - } - fn zero () -> Self { - 0.into() - } -} - -impl Coordinate for T where T: Send + Sync + Copy - + Add - + Sub - + Mul - + Div - + Ord + PartialEq + Eq - + Debug + Display + Default - + From + Into - + Into - + Into -{} diff --git a/crates/tek/src/space/direction.rs b/crates/tek/src/space/direction.rs deleted file mode 100644 index 7ad3eb8d..00000000 --- a/crates/tek/src/space/direction.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::*; - -#[derive(Copy, Clone, PartialEq)] -pub enum Direction { Up, Down, Left, Right, } -impl Direction { - pub fn is_up (&self) -> bool { match self { Self::Up => true, _ => false } } - pub fn is_down (&self) -> bool { match self { Self::Down => true, _ => false } } - pub fn is_left (&self) -> bool { match self { Self::Left => true, _ => false } } - pub fn is_right (&self) -> bool { match self { Self::Right => true, _ => false } } - /// Return next direction clockwise - pub fn cw (&self) -> Self { - match self { - Self::Up => Self::Right, - Self::Down => Self::Left, - Self::Left => Self::Up, - Self::Right => Self::Down, - } - } - /// Return next direction counterclockwise - pub fn ccw (&self) -> Self { - match self { - Self::Up => Self::Left, - Self::Down => Self::Right, - Self::Left => Self::Down, - Self::Right => Self::Up, - } - } -} diff --git a/crates/tek/src/space/size.rs b/crates/tek/src/space/size.rs deleted file mode 100644 index 5fe0ed40..00000000 --- a/crates/tek/src/space/size.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::*; - -pub trait Size { - fn x (&self) -> N; - fn y (&self) -> N; - #[inline] fn w (&self) -> N { self.x() } - #[inline] fn h (&self) -> N { self.y() } - #[inline] fn wh (&self) -> [N;2] { [self.x(), self.y()] } - #[inline] fn clip_w (&self, w: N) -> [N;2] { [self.w().min(w.into()), self.h()] } - #[inline] fn clip_h (&self, h: N) -> [N;2] { [self.w(), self.h().min(h.into())] } - #[inline] fn expect_min (&self, w: N, h: N) -> Usually<&Self> { - if self.w() < w || self.h() < h { - Err(format!("min {w}x{h}").into()) - } else { - Ok(self) - } - } -} -impl Size for (N, N) { - fn x (&self) -> N { self.0 } - fn y (&self) -> N { self.1 } -} -impl Size for [N;2] { - fn x (&self) -> N { self[0] } - fn y (&self) -> N { self[1] } -} diff --git a/crates/tek/src/space/split.rs b/crates/tek/src/space/split.rs index e4c98785..26faf2c1 100644 --- a/crates/tek/src/space/split.rs +++ b/crates/tek/src/space/split.rs @@ -1,4 +1,5 @@ use crate::*; +use Direction::*; impl LayoutSplit for E {} @@ -11,22 +12,22 @@ pub trait LayoutSplit { fn split_n , B: Render> ( flip: bool, amount: E::Unit, a: A, b: B ) -> Split { - Self::split(flip, Direction::Up, amount, a, b) + Self::split(flip, North, amount, a, b) } fn split_s , B: Render> ( flip: bool, amount: E::Unit, a: A, b: B ) -> Split { - Self::split(flip, Direction::Down, amount, a, b) + Self::split(flip, South, amount, a, b) } fn split_w , B: Render> ( flip: bool, amount: E::Unit, a: A, b: B ) -> Split { - Self::split(flip, Direction::Left, amount, a, b) + Self::split(flip, West, amount, a, b) } fn split_e , B: Render> ( flip: bool, amount: E::Unit, a: A, b: B ) -> Split { - Self::split(flip, Direction::Right, amount, a, b) + Self::split(flip, East, amount, a, b) } } @@ -40,16 +41,16 @@ impl, B: Render> Split { Self(flip, direction, proportion, a, b, Default::default()) } pub fn up (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { - Self(flip, Direction::Up, proportion, a, b, Default::default()) + Self(flip, North, proportion, a, b, Default::default()) } pub fn down (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { - Self(flip, Direction::Down, proportion, a, b, Default::default()) + Self(flip, South, proportion, a, b, Default::default()) } pub fn left (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { - Self(flip, Direction::Left, proportion, a, b, Default::default()) + Self(flip, West, proportion, a, b, Default::default()) } pub fn right (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { - Self(flip, Direction::Right, proportion, a, b, Default::default()) + Self(flip, East, proportion, a, b, Default::default()) } } diff --git a/crates/tek/src/space/stack.rs b/crates/tek/src/space/stack.rs index 79fb643d..a48f5a5a 100644 --- a/crates/tek/src/space/stack.rs +++ b/crates/tek/src/space/stack.rs @@ -1,4 +1,5 @@ use crate::*; +use Direction::*; #[macro_export] macro_rules! col { ([$($expr:expr),* $(,)?]) => { @@ -58,13 +59,13 @@ impl< Self(build, direction, Default::default()) } #[inline] pub fn right (build: F) -> Self { - Self::new(Direction::Right, build) + Self::new(East, build) } #[inline] pub fn down (build: F) -> Self { - Self::new(Direction::Down, build) + Self::new(South, build) } #[inline] pub fn up (build: F) -> Self { - Self::new(Direction::Up, build) + Self::new(North, build) } } @@ -75,7 +76,7 @@ where fn min_size (&self, to: E::Size) -> Perhaps { match self.1 { - Direction::Down => { + South => { let mut w: E::Unit = 0.into(); let mut h: E::Unit = 0.into(); (self.0)(&mut |component: &dyn Render| { @@ -93,7 +94,7 @@ where Ok(Some([w, h].into())) }, - Direction::Right => { + East => { let mut w: E::Unit = 0.into(); let mut h: E::Unit = 0.into(); (self.0)(&mut |component: &dyn Render| { @@ -111,7 +112,7 @@ where Ok(Some([w, h].into())) }, - Direction::Up => { + North => { let mut w: E::Unit = 0.into(); let mut h: E::Unit = 0.into(); (self.0)(&mut |component: &dyn Render| { @@ -129,7 +130,7 @@ where Ok(Some([w, h].into())) }, - Direction::Left => { + West => { let w: E::Unit = 0.into(); let h: E::Unit = 0.into(); (self.0)(&mut |component: &dyn Render| { @@ -148,7 +149,7 @@ where let mut w = 0.into(); let mut h = 0.into(); match self.1 { - Direction::Down => { + South => { (self.0)(&mut |item| { if h < area.h() { let item = E::max_y(area.h() - h, E::push_y(h, item)); @@ -162,7 +163,7 @@ where Ok(()) })?; }, - Direction::Right => { + East => { (self.0)(&mut |item| { if w < area.w() { let item = E::max_x(area.w() - w, E::push_x(w, item)); @@ -176,7 +177,7 @@ where Ok(()) })?; }, - Direction::Up => { + North => { (self.0)(&mut |item| { if h < area.h() { let show = item.min_size([area.w(), area.h().minus(h)].into())?.map(|s|s.wh()); diff --git a/crates/tek/src/time/clock.rs b/crates/tek/src/time/clock.rs index 8798029a..022bc26f 100644 --- a/crates/tek/src/time/clock.rs +++ b/crates/tek/src/time/clock.rs @@ -185,7 +185,7 @@ impl ClockModel { /// Hosts the JACK callback for updating the temporal pointer and playback status. pub struct ClockAudio<'a, T: HasClock>(pub &'a mut T); -impl<'a, T: HasClock> Audio for ClockAudio<'a, T> { +impl Audio for ClockAudio<'_, T> { #[inline] fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { self.0.clock().update_from_scope(scope).unwrap(); Control::Continue From 71f4194cdfc05bf714628817963ad98702947dd7 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 27 Dec 2024 16:17:47 +0100 Subject: [PATCH 027/815] whatever the fuck is up with the groovebox mode --- crates/cli/src/cli_groovebox.rs | 41 +++++++++++++++++++++++++-------- crates/cli/src/cli_sequencer.rs | 27 ---------------------- crates/cli/src/lib.rs | 27 ++++++++++++++++++++++ 3 files changed, 58 insertions(+), 37 deletions(-) diff --git a/crates/cli/src/cli_groovebox.rs b/crates/cli/src/cli_groovebox.rs index ae7abab1..68689338 100644 --- a/crates/cli/src/cli_groovebox.rs +++ b/crates/cli/src/cli_groovebox.rs @@ -2,19 +2,40 @@ include!("./lib.rs"); pub fn main () -> Usually<()> { GrooveboxCli::parse().run() } #[derive(Debug, Parser)] #[command(version, about, long_about = None)] -pub struct GrooveboxCli; +pub struct GrooveboxCli { + /// Name of JACK client + #[arg(short, long)] + name: Option, + + /// Whether to include a transport toolbar (default: true) + #[arg(short, long, default_value_t = true)] + transport: bool, + + /// MIDI outs to connect to (multiple accepted) + #[arg(short='i', long)] + midi_from: Vec, + + /// MIDI ins to connect to (multiple accepted) + #[arg(short='o', long)] + midi_to: Vec, + + /// Audio ins to connect to (multiple accepted) + #[arg(short='I', long)] + audio_from: Vec, + + /// Audio outs to connect to (multiple accepted) + #[arg(short='O', long)] + audio_to: Vec, +} impl GrooveboxCli { fn run (&self) -> Usually<()> { Tui::run(JackClient::new("tek_groovebox")?.activate_with(|jack|{ - let app = tek::tui::GrooveboxTui::try_from(jack)?; - let jack = jack.read().unwrap(); - let _midi_out = jack.register_port("out", MidiOut::default())?; - let _midi_in_1 = jack.register_port("in1", MidiIn::default())?; - let _midi_in_2 = jack.register_port("in2", MidiIn::default())?; - let _audio_in_1 = jack.register_port("inL", AudioIn::default())?; - let _audio_in_2 = jack.register_port("inR", AudioIn::default())?; - let _audio_out_1 = jack.register_port("out1", AudioOut::default())?; - let _audio_out_2 = jack.register_port("out2", AudioOut::default())?; + let mut app = tek::tui::GrooveboxTui::try_from(jack)?; + let jack = jack.read().unwrap(); + let midi_in = jack.register_port("i", MidiIn::default())?; + let midi_out = jack.register_port("o", MidiOut::default())?; + app.sequencer.player.midi_ins.push(midi_in); + app.sequencer.player.midi_outs.push(midi_out); Ok(app) })?)?; Ok(()) diff --git a/crates/cli/src/cli_sequencer.rs b/crates/cli/src/cli_sequencer.rs index fcbb8e9f..40696efa 100644 --- a/crates/cli/src/cli_sequencer.rs +++ b/crates/cli/src/cli_sequencer.rs @@ -42,30 +42,3 @@ impl SequencerCli { Ok(()) } } - -fn connect_from (jack: &JackClient, input: &Port, ports: &[String]) -> Usually<()> { - for port in ports.iter() { - if let Some(port) = jack.port_by_name(port).as_ref() { - jack.client().connect_ports(port, input)?; - } else { - panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); - } - } - Ok(()) -} - -fn connect_to (jack: &JackClient, output: &Port, ports: &[String]) -> Usually<()> { - for port in ports.iter() { - if let Some(port) = jack.port_by_name(port).as_ref() { - jack.client().connect_ports(output, port)?; - } else { - panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); - } - } - Ok(()) -} - -#[test] fn verify_sequencer_cli () { - use clap::CommandFactory; - SequencerCli::command().debug_assert(); -} diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index 4b1f648a..e8d37d2b 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -1,3 +1,30 @@ #[allow(unused_imports)] use std::sync::Arc; #[allow(unused_imports)] use clap::{self, Parser}; #[allow(unused_imports)] use tek::{*, jack::*}; + +fn connect_from (jack: &JackClient, input: &Port, ports: &[String]) -> Usually<()> { + for port in ports.iter() { + if let Some(port) = jack.port_by_name(port).as_ref() { + jack.client().connect_ports(port, input)?; + } else { + panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); + } + } + Ok(()) +} + +fn connect_to (jack: &JackClient, output: &Port, ports: &[String]) -> Usually<()> { + for port in ports.iter() { + if let Some(port) = jack.port_by_name(port).as_ref() { + jack.client().connect_ports(output, port)?; + } else { + panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); + } + } + Ok(()) +} + +#[test] fn verify_sequencer_cli () { + use clap::CommandFactory; + SequencerCli::command().debug_assert(); +} From a9fb6fc17c7a4d0d3257fecd3df2c0f25a52ef95 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 27 Dec 2024 16:54:32 +0100 Subject: [PATCH 028/815] wip: samples table --- crates/cli/src/cli_groovebox.rs | 5 ++ crates/cli/src/cli_sequencer.rs | 5 ++ crates/cli/src/lib.rs | 7 +-- crates/tek/src/core/color.rs | 9 +++ crates/tek/src/core/output.rs | 13 +++- crates/tek/src/tui/app_groovebox.rs | 32 +++++++--- crates/tek/src/tui/app_sampler.rs | 69 +++++++++------------- crates/tek/src/tui/piano_h.rs | 12 +++- crates/tek/src/tui/piano_h/piano_h_keys.rs | 25 +++----- 9 files changed, 103 insertions(+), 74 deletions(-) diff --git a/crates/cli/src/cli_groovebox.rs b/crates/cli/src/cli_groovebox.rs index 68689338..d72778a6 100644 --- a/crates/cli/src/cli_groovebox.rs +++ b/crates/cli/src/cli_groovebox.rs @@ -41,3 +41,8 @@ impl GrooveboxCli { Ok(()) } } + +#[test] fn verify_groovebox_cli () { + use clap::CommandFactory; + GrooveboxCli::command().debug_assert(); +} diff --git a/crates/cli/src/cli_sequencer.rs b/crates/cli/src/cli_sequencer.rs index 40696efa..247282f7 100644 --- a/crates/cli/src/cli_sequencer.rs +++ b/crates/cli/src/cli_sequencer.rs @@ -42,3 +42,8 @@ impl SequencerCli { Ok(()) } } + +#[test] fn verify_sequencer_cli () { + use clap::CommandFactory; + SequencerCli::command().debug_assert(); +} diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index e8d37d2b..e2d6822e 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -2,6 +2,7 @@ #[allow(unused_imports)] use clap::{self, Parser}; #[allow(unused_imports)] use tek::{*, jack::*}; +#[allow(unused)] fn connect_from (jack: &JackClient, input: &Port, ports: &[String]) -> Usually<()> { for port in ports.iter() { if let Some(port) = jack.port_by_name(port).as_ref() { @@ -13,6 +14,7 @@ fn connect_from (jack: &JackClient, input: &Port, ports: &[String]) -> U Ok(()) } +#[allow(unused)] fn connect_to (jack: &JackClient, output: &Port, ports: &[String]) -> Usually<()> { for port in ports.iter() { if let Some(port) = jack.port_by_name(port).as_ref() { @@ -23,8 +25,3 @@ fn connect_to (jack: &JackClient, output: &Port, ports: &[String]) -> U } Ok(()) } - -#[test] fn verify_sequencer_cli () { - use clap::CommandFactory; - SequencerCli::command().debug_assert(); -} diff --git a/crates/tek/src/core/color.rs b/crates/tek/src/core/color.rs index 9fc495af..fd678201 100644 --- a/crates/tek/src/core/color.rs +++ b/crates/tek/src/core/color.rs @@ -7,6 +7,15 @@ pub trait HasColor { fn color_mut (&mut self) -> &mut ItemColor; } +#[macro_export] macro_rules! has_color { + (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { + impl $(<$($L),*$($T $(: $U)?),*>)? HasColor for $Struct $(<$($L),*$($T),*>)? { + fn color (&$self) -> ItemColor { &$cb } + fn color_mut (&mut $self) -> &mut ItemColor { &mut $cb } + } + } +} + /// A color in OKHSL and RGB representations. #[derive(Debug, Default, Copy, Clone, PartialEq)] pub struct ItemColor { diff --git a/crates/tek/src/core/output.rs b/crates/tek/src/core/output.rs index 5ee2a75d..ff8748a7 100644 --- a/crates/tek/src/core/output.rs +++ b/crates/tek/src/core/output.rs @@ -11,8 +11,17 @@ use crate::*; } } }; - (<$E:ty>|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { - impl $(<$($L),*$($T $(: $U)?),*>)? Render<$E> for $Struct $(<$($L),*$($T),*>)? { + (<$E:ty>|$self:ident:$Struct:ident$(< + $($($L:lifetime),+)? + $($($T:ident$(:$U:path)?),+)? + >)?|$cb:expr) => { + impl $(< + $($($L),+)? + $($($T$(:$U)?),+)? + >)? Render<$E> for $Struct $(< + $($($L),+)? + $($($T),+)? + >)? { fn min_size (&$self, to: <$E as Engine>::Size) -> Perhaps<<$E as Engine>::Size> { $cb.min_size(to) } diff --git a/crates/tek/src/tui/app_groovebox.rs b/crates/tek/src/tui/app_groovebox.rs index 94d48858..fad1c95f 100644 --- a/crates/tek/src/tui/app_groovebox.rs +++ b/crates/tek/src/tui/app_groovebox.rs @@ -1,11 +1,14 @@ use crate::*; use super::*; +use KeyCode::{Char, Delete, Tab, Up, Down, Left, Right}; + pub struct GrooveboxTui { pub sequencer: SequencerTui, pub sampler: SamplerTui, pub split: u16, pub focus: GrooveboxFocus } + from_jack!(|jack|GrooveboxTui { let mut sequencer = SequencerTui::try_from(jack)?; sequencer.status = false; @@ -20,29 +23,42 @@ from_jack!(|jack|GrooveboxTui { sequencer, sampler: SamplerTui::try_from(jack)?, split: 16, - focus: GrooveboxFocus::Sampler, + focus: GrooveboxFocus::Sequencer, } }); + pub enum GrooveboxFocus { Sequencer, Sampler } + audio!(|self:GrooveboxTui,_client,_process|Control::Continue); + render!(|self:GrooveboxTui|Bsp::n( Fixed::h(2, SequencerStatus::from(&self.sequencer)), - Fill::h(Bsp::s(&self.sequencer, &self.sampler)), + Fill::h(Bsp::s( + Tui::min_y(30, &self.sequencer), + Fill::h(&self.sampler), + )), )); + pub enum GrooveboxCommand { Sequencer(SequencerCommand), Sampler(SamplerCommand), } -handle!(|self:GrooveboxTui,input|GrooveboxCommand::execute_with_state(self, input)); -input_to_command!(GrooveboxCommand: |state:GrooveboxTui,input|match state.focus { - GrooveboxFocus::Sequencer => GrooveboxCommand::Sequencer( - SequencerCommand::input_to_command(&state.sequencer, input)?), - GrooveboxFocus::Sampler => GrooveboxCommand::Sampler( - SamplerCommand::input_to_command(&state.sampler, input)?), + +handle!(|self: GrooveboxTui, input|GrooveboxCommand::execute_with_state(self, input)); + +input_to_command!(GrooveboxCommand: |state: GrooveboxTui,input|match input.event() { + key_pat!(Up) | + key_pat!(Down) | + key_pat!(Left) | + key_pat!(Right) => + GrooveboxCommand::Sampler(SamplerCommand::input_to_command(&state.sampler, input)?), + _ => + GrooveboxCommand::Sequencer(SequencerCommand::input_to_command(&state.sequencer, input)?), }); + command!(|self:GrooveboxCommand,state:GrooveboxTui|match self { GrooveboxCommand::Sequencer(command) => command.execute(&mut state.sequencer)?.map(GrooveboxCommand::Sequencer), diff --git a/crates/tek/src/tui/app_sampler.rs b/crates/tek/src/tui/app_sampler.rs index c1fb49b6..049473fc 100644 --- a/crates/tek/src/tui/app_sampler.rs +++ b/crates/tek/src/tui/app_sampler.rs @@ -1,5 +1,6 @@ use crate::*; -use super::*; +use super::{*, piano_h::PianoHorizontalKeys}; + use KeyCode::Char; use std::fs::File; use symphonia::{ @@ -13,12 +14,36 @@ use symphonia::{ }, default::get_codecs, }; + pub struct SamplerTui { pub state: Sampler, pub cursor: (usize, usize), pub editing: Option>>, pub mode: Option, + /// Size of actual notes area + pub size: Measure, } + +render!(|self: SamplerTui|{ + let keys_width = 5; + let keys = move||SamplerKeys(self); + let fg = TuiTheme::g(200); + let bg = TuiTheme::g(50); + let border = Fill::wh(Outer(Style::default().fg(fg).bg(bg))); + let with_border = |x|lay!([border, Tui::inset_xy(1, 1, &x)]); + with_border(Fill::wh(Bsp::s( + "kyp", + Bsp::e( + Fixed::w(keys_width, keys()), + Fill::wh(lay!([&self.size, Fill::wh("nymka")])), + ), + ))) +}); + +struct SamplerKeys<'a>(&'a SamplerTui); +render!(|self: SamplerKeys<'a>|render(|to|Ok(render_keys_v(to, self)))); +has_color!(|self: SamplerKeys<'a>|ItemColor::default()); + pub enum SamplerMode { // Load sample from path Import(usize, FileBrowser), @@ -33,6 +58,7 @@ from_jack!(|jack|SamplerTui{ cursor: (0, 0), editing: None, mode: None, + size: Measure::new(), state: Sampler { jack: jack.clone(), name: "Sampler".into(), @@ -64,16 +90,10 @@ input_to_command!(SamplerCommand:|state:SamplerTui,input|match state.mode { _ => match input.event() { // load sample key_pat!(Char('l')) => Self::Import(FileBrowserCommand::Begin), + key_pat!(KeyCode::Up) => { todo!() }, + key_pat!(KeyCode::Down) => { todo!() }, _ => return None } - //key_pat!(KeyCode::Up) => state.cursor.0 = if state.cursor.0 == 0 { - //mapped.len() + unmapped.len() - 1 - //} else { - //state.cursor.0 - 1 - //}, - //key_pat!(KeyCode::Down) => { - //state.cursor.0 = (state.cursor.0 + 1) % (mapped.len() + unmapped.len()); - //}, //key_pat!(KeyCode::Char('p')) => if let Some(sample) = self.sample() { //voices.write().unwrap().push(Sample::play(sample, 0, &100.into())); //}, @@ -133,37 +153,6 @@ audio!(|self: SamplerTui, _client, scope|{ Control::Continue }); -render!(|self: SamplerTui|Tui::min_y(10, Fill::wh(lay!([ - - Fill::wh(render(|to|{ // border - let [x, y, w, h] = to.area(); - let green = Some(Style::default().fg(Color::Green)); - to.blit(&"🭚", x, y, green); - to.blit(&"🭥", x + w.saturating_sub(1), y, green); - to.blit(&"🬿", x, y + h.saturating_sub(1), green); - to.blit(&"🭊", x + w.saturating_sub(1), y + h.saturating_sub(1), green); - Ok(()) - })), - - col!(|add|{ - add(&Tui::push_x(2, row!([ - Tui::bold(true, "Sampler"), "|Voices: ", - &format!("{}", self.state.voices.read().unwrap().len()), - ])))?; - if let Some(SamplerMode::Import(_, browser)) = self.mode.as_ref() { - add(&browser) - } else { - add(&row!([ - " ", - col!(note in 35..=42 => { - &format!("{note:>3} ---------------- +0.0dB") - }), - ])) - } - }), - -])))); - pub struct AddSampleModal { exited: bool, dir: PathBuf, diff --git a/crates/tek/src/tui/piano_h.rs b/crates/tek/src/tui/piano_h.rs index f28639fc..867210b9 100644 --- a/crates/tek/src/tui/piano_h.rs +++ b/crates/tek/src/tui/piano_h.rs @@ -1,9 +1,15 @@ use crate::*; use super::*; -mod piano_h_cursor; use self::piano_h_cursor::*; -mod piano_h_keys; use self::piano_h_keys::*; +mod piano_h_cursor; +use self::piano_h_cursor::*; + +mod piano_h_keys; +pub(crate) use self::piano_h_keys::*; +pub use self::piano_h_keys::render_keys_v; + mod piano_h_notes; use self::piano_h_notes::*; + mod piano_h_time; use self::piano_h_time::*; pub(crate) fn note_y_iter (note_lo: usize, note_hi: usize, y0: u16) -> impl Iterator { @@ -46,11 +52,11 @@ impl PianoHorizontal { } render!(|self: PianoHorizontal|{ + let keys_width = 5; let keys = move||PianoHorizontalKeys(self); let timeline = move||PianoHorizontalTimeline(self); let notes = move||PianoHorizontalNotes(self); let cursor = move||PianoHorizontalCursor(self); - let keys_width = 5; let border = Fill::wh(Outer(Style::default().fg(self.color.dark.rgb).bg(self.color.darkest.rgb))); let with_border = |x|lay!([border, Tui::inset_xy(1, 1, &x)]); with_border(Fill::wh(Bsp::s( diff --git a/crates/tek/src/tui/piano_h/piano_h_keys.rs b/crates/tek/src/tui/piano_h/piano_h_keys.rs index 91bc29a3..1a80ece6 100644 --- a/crates/tek/src/tui/piano_h/piano_h_keys.rs +++ b/crates/tek/src/tui/piano_h/piano_h_keys.rs @@ -2,11 +2,14 @@ use crate::*; use super::note_y_iter; pub struct PianoHorizontalKeys<'a>(pub(crate) &'a PianoHorizontal); -render!(|self: PianoHorizontalKeys<'a>|render(|to|Ok({ - let color = self.0.color; - let note_lo = self.0.note_lo().get(); - let note_hi = self.0.note_hi(); - let note_point = self.0.note_point(); +render!(|self: PianoHorizontalKeys<'a>|render(|to|Ok(render_keys_v(to, self)))); +has_color!(|self: PianoHorizontalKeys<'a>|self.0.color); + +pub fn render_keys_v (to: &mut E::Output, state: &T) { + let color = state.color(); + let note_lo = state.note_lo().get(); + let note_hi = state.note_hi(); + let note_point = state.note_point(); let [x, y0, w, h] = to.area().xywh(); let key_style = Some(Style::default().fg(Color::Rgb(192, 192, 192)).bg(Color::Rgb(0, 0, 0))); let off_style = Some(Style::default().fg(TuiTheme::g(160))); @@ -22,17 +25,7 @@ render!(|self: PianoHorizontalKeys<'a>|render(|to|Ok({ to.blit(&to_note_name(note), x, screen_y, off_style) }; } - //let debug = false; - //if debug { - //to.blit(&format!("XYU"), x, y0, None); - //to.blit(&format!("x={x}"), x, y0+1, None); - //to.blit(&format!("y0={y0}"), x, y0+2, None); - //to.blit(&format!("w={w}"), x, y0+3, None); - //to.blit(&format!("h={h}"), x, y0+4, None); - //to.blit(&format!("note_lo={note_lo}"), x, y0+5, None); - //to.blit(&format!("note_hi={note_hi}"), x, y0+6, None); - //} -}))); +} fn to_key (note: usize) -> &'static str { match note % 12 { From 8d79537edf4b205fddb68d3fb76b80ecab7b23c8 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 27 Dec 2024 17:07:51 +0100 Subject: [PATCH 029/815] wip: split MidiRange to TimeRange/NoteRange --- crates/tek/src/core/color.rs | 2 +- crates/tek/src/midi/midi_note.rs | 22 ++++++++++++++-------- crates/tek/src/tui/app_sampler.rs | 11 +++++++++-- crates/tek/src/tui/phrase_editor.rs | 7 +++++-- crates/tek/src/tui/piano_h.rs | 6 ++++-- crates/tek/src/tui/piano_h/piano_h_keys.rs | 10 +++++++--- 6 files changed, 40 insertions(+), 18 deletions(-) diff --git a/crates/tek/src/core/color.rs b/crates/tek/src/core/color.rs index fd678201..a935c3fa 100644 --- a/crates/tek/src/core/color.rs +++ b/crates/tek/src/core/color.rs @@ -10,7 +10,7 @@ pub trait HasColor { #[macro_export] macro_rules! has_color { (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { impl $(<$($L),*$($T $(: $U)?),*>)? HasColor for $Struct $(<$($L),*$($T),*>)? { - fn color (&$self) -> ItemColor { &$cb } + fn color (&$self) -> ItemColor { $cb } fn color_mut (&mut $self) -> &mut ItemColor { &mut $cb } } } diff --git a/crates/tek/src/midi/midi_note.rs b/crates/tek/src/midi/midi_note.rs index 71b90a45..8452d1fb 100644 --- a/crates/tek/src/midi/midi_note.rs +++ b/crates/tek/src/midi/midi_note.rs @@ -81,29 +81,35 @@ from!(|data:(usize, bool)|MidiRangeModel = Self { time_zoom: Arc::new(data.0.into()), time_lock: Arc::new(data.1.into()), }); -pub trait MidiRange { +pub trait TimeRange { fn time_len (&self) -> &AtomicUsize; fn time_zoom (&self) -> &AtomicUsize; fn time_lock (&self) -> &AtomicBool; fn time_start (&self) -> &AtomicUsize; - fn note_lo (&self) -> &AtomicUsize; - fn note_axis (&self) -> &AtomicUsize; fn time_axis (&self) -> &AtomicUsize; - fn note_hi (&self) -> usize { - (self.note_lo().get() + self.note_axis().get().saturating_sub(1)).min(127) - } fn time_end (&self) -> usize { self.time_start().get() + self.time_axis().get() * self.time_zoom().get() } } -impl MidiRange for MidiRangeModel { +pub trait NoteRange { + fn note_lo (&self) -> &AtomicUsize; + fn note_axis (&self) -> &AtomicUsize; + fn note_hi (&self) -> usize { + (self.note_lo().get() + self.note_axis().get().saturating_sub(1)).min(127) + } +} +pub trait MidiRange: TimeRange + NoteRange {} +impl MidiRange for T {} +impl TimeRange for MidiRangeModel { fn time_len (&self) -> &AtomicUsize { &self.time_len } fn time_zoom (&self) -> &AtomicUsize { &self.time_zoom } fn time_lock (&self) -> &AtomicBool { &self.time_lock } fn time_start (&self) -> &AtomicUsize { &self.time_start } + fn time_axis (&self) -> &AtomicUsize { &self.time_axis } +} +impl NoteRange for MidiRangeModel { fn note_lo (&self) -> &AtomicUsize { &self.note_lo } fn note_axis (&self) -> &AtomicUsize { &self.note_axis } - fn time_axis (&self) -> &AtomicUsize { &self.time_axis } } #[derive(Debug, Clone)] diff --git a/crates/tek/src/tui/app_sampler.rs b/crates/tek/src/tui/app_sampler.rs index 049473fc..04fc9db1 100644 --- a/crates/tek/src/tui/app_sampler.rs +++ b/crates/tek/src/tui/app_sampler.rs @@ -21,7 +21,9 @@ pub struct SamplerTui { pub editing: Option>>, pub mode: Option, /// Size of actual notes area - pub size: Measure, + pub size: Measure, + /// Lowest note displayed + pub note_lo: AtomicUsize, } render!(|self: SamplerTui|{ @@ -41,8 +43,12 @@ render!(|self: SamplerTui|{ }); struct SamplerKeys<'a>(&'a SamplerTui); -render!(|self: SamplerKeys<'a>|render(|to|Ok(render_keys_v(to, self)))); has_color!(|self: SamplerKeys<'a>|ItemColor::default()); +render!(|self: SamplerKeys<'a>|render(|to|Ok(render_keys_v::(to, self)))); +impl<'a> NoteRange for SamplerKeys<'a> { + fn note_lo (&self) -> &AtomicUsize { &self.0.note_lo } + fn note_axis (&self) -> &AtomicUsize { &self.0.size.y } +} pub enum SamplerMode { // Load sample from path @@ -59,6 +65,7 @@ from_jack!(|jack|SamplerTui{ editing: None, mode: None, size: Measure::new(), + note_lo: 36.into(), state: Sampler { jack: jack.clone(), name: "Sampler".into(), diff --git a/crates/tek/src/tui/phrase_editor.rs b/crates/tek/src/tui/phrase_editor.rs index c8ed9c50..30f56684 100644 --- a/crates/tek/src/tui/phrase_editor.rs +++ b/crates/tek/src/tui/phrase_editor.rs @@ -127,14 +127,17 @@ pub trait PhraseViewMode: Render + HasSize + MidiRange + MidiPoint + D impl MidiView for MidiEditorModel {} -impl MidiRange for MidiEditorModel { +impl TimeRange for MidiEditorModel { fn time_len (&self) -> &AtomicUsize { self.mode.time_len() } fn time_zoom (&self) -> &AtomicUsize { self.mode.time_zoom() } fn time_lock (&self) -> &AtomicBool { self.mode.time_lock() } fn time_start (&self) -> &AtomicUsize { self.mode.time_start() } + fn time_axis (&self) -> &AtomicUsize { self.mode.time_axis() } +} + +impl NoteRange for MidiEditorModel { fn note_lo (&self) -> &AtomicUsize { self.mode.note_lo() } fn note_axis (&self) -> &AtomicUsize { self.mode.note_axis() } - fn time_axis (&self) -> &AtomicUsize { self.mode.time_axis() } } impl MidiPoint for MidiEditorModel { diff --git a/crates/tek/src/tui/piano_h.rs b/crates/tek/src/tui/piano_h.rs index 867210b9..58645814 100644 --- a/crates/tek/src/tui/piano_h.rs +++ b/crates/tek/src/tui/piano_h.rs @@ -130,14 +130,16 @@ impl PianoHorizontal { has_size!(|self:PianoHorizontal|&self.size); -impl MidiRange for PianoHorizontal { +impl TimeRange for PianoHorizontal { fn time_len (&self) -> &AtomicUsize { self.range.time_len() } fn time_zoom (&self) -> &AtomicUsize { self.range.time_zoom() } fn time_lock (&self) -> &AtomicBool { self.range.time_lock() } fn time_start (&self) -> &AtomicUsize { self.range.time_start() } + fn time_axis (&self) -> &AtomicUsize { self.range.time_axis() } +} +impl NoteRange for PianoHorizontal { fn note_lo (&self) -> &AtomicUsize { self.range.note_lo() } fn note_axis (&self) -> &AtomicUsize { self.range.note_axis() } - fn time_axis (&self) -> &AtomicUsize { self.range.time_axis() } } impl MidiPoint for PianoHorizontal { fn note_len (&self) -> usize { self.point.note_len()} diff --git a/crates/tek/src/tui/piano_h/piano_h_keys.rs b/crates/tek/src/tui/piano_h/piano_h_keys.rs index 1a80ece6..0fd34c09 100644 --- a/crates/tek/src/tui/piano_h/piano_h_keys.rs +++ b/crates/tek/src/tui/piano_h/piano_h_keys.rs @@ -2,10 +2,14 @@ use crate::*; use super::note_y_iter; pub struct PianoHorizontalKeys<'a>(pub(crate) &'a PianoHorizontal); -render!(|self: PianoHorizontalKeys<'a>|render(|to|Ok(render_keys_v(to, self)))); -has_color!(|self: PianoHorizontalKeys<'a>|self.0.color); +render!(|self: PianoHorizontalKeys<'a>|render(|to|Ok(render_keys_v::(to, self)))); +has_color!(|self: PianoHorizontalKeys<'a>|self.0.color.base); +impl<'a> NoteRange for PianoHorizontalKeys<'a> { + fn note_lo (&self) -> &AtomicUsize { &self.0.note_lo() } + fn note_axis (&self) -> &AtomicUsize { &self.0.note_axis() } +} -pub fn render_keys_v (to: &mut E::Output, state: &T) { +pub fn render_keys_v (to: &mut E::Output, state: &T) { let color = state.color(); let note_lo = state.note_lo().get(); let note_hi = state.note_hi(); From fc0a398702dd0d3ba4dd6b4856a188899e6a8782 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 27 Dec 2024 18:05:35 +0100 Subject: [PATCH 030/815] add GrooveboxStatus and try to autostretch sampler --- crates/tek/src/core/color.rs | 2 - crates/tek/src/midi/midi_note.rs | 21 ++++++-- crates/tek/src/tui/app_groovebox.rs | 19 ++++--- crates/tek/src/tui/app_sampler.rs | 47 ++++++++++------- crates/tek/src/tui/phrase_editor.rs | 7 ++- crates/tek/src/tui/piano_h.rs | 6 ++- crates/tek/src/tui/piano_h/piano_h_keys.rs | 12 +++-- crates/tek/src/tui/status.rs | 1 + crates/tek/src/tui/status/status_groovebox.rs | 52 +++++++++++++++++++ 9 files changed, 129 insertions(+), 38 deletions(-) create mode 100644 crates/tek/src/tui/status/status_groovebox.rs diff --git a/crates/tek/src/core/color.rs b/crates/tek/src/core/color.rs index a935c3fa..193efebe 100644 --- a/crates/tek/src/core/color.rs +++ b/crates/tek/src/core/color.rs @@ -4,14 +4,12 @@ pub use ratatui::prelude::Color; pub trait HasColor { fn color (&self) -> ItemColor; - fn color_mut (&mut self) -> &mut ItemColor; } #[macro_export] macro_rules! has_color { (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { impl $(<$($L),*$($T $(: $U)?),*>)? HasColor for $Struct $(<$($L),*$($T),*>)? { fn color (&$self) -> ItemColor { $cb } - fn color_mut (&mut $self) -> &mut ItemColor { &mut $cb } } } } diff --git a/crates/tek/src/midi/midi_note.rs b/crates/tek/src/midi/midi_note.rs index 8452d1fb..e13127cc 100644 --- a/crates/tek/src/midi/midi_note.rs +++ b/crates/tek/src/midi/midi_note.rs @@ -121,6 +121,7 @@ pub struct MidiPointModel { /// Length of note that will be inserted, in pulses pub note_len: Arc, } + impl Default for MidiPointModel { fn default () -> Self { Self { @@ -130,20 +131,32 @@ impl Default for MidiPointModel { } } } -pub trait MidiPoint { + +pub trait NotePoint { fn note_len (&self) -> usize; fn set_note_len (&self, x: usize); fn note_point (&self) -> usize; fn set_note_point (&self, x: usize); - fn time_point (&self) -> usize; - fn set_time_point (&self, x: usize); fn note_end (&self) -> usize { self.note_point() + self.note_len() } } -impl MidiPoint for MidiPointModel { + +pub trait TimePoint { + fn time_point (&self) -> usize; + fn set_time_point (&self, x: usize); +} + +pub trait MidiPoint: NotePoint + TimePoint {} + +impl MidiPoint for T {} + +impl NotePoint for MidiPointModel { fn note_len (&self) -> usize { self.note_len.load(Relaxed)} fn set_note_len (&self, x: usize) { self.note_len.store(x, Relaxed) } fn note_point (&self) -> usize { self.note_point.load(Relaxed).min(127) } fn set_note_point (&self, x: usize) { self.note_point.store(x.min(127), Relaxed) } +} + +impl TimePoint for MidiPointModel { fn time_point (&self) -> usize { self.time_point.load(Relaxed) } fn set_time_point (&self, x: usize) { self.time_point.store(x, Relaxed) } } diff --git a/crates/tek/src/tui/app_groovebox.rs b/crates/tek/src/tui/app_groovebox.rs index fad1c95f..05f1ece1 100644 --- a/crates/tek/src/tui/app_groovebox.rs +++ b/crates/tek/src/tui/app_groovebox.rs @@ -3,6 +3,7 @@ use super::*; use KeyCode::{Char, Delete, Tab, Up, Down, Left, Right}; pub struct GrooveboxTui { + pub size: Measure, pub sequencer: SequencerTui, pub sampler: SamplerTui, pub split: u16, @@ -24,6 +25,7 @@ from_jack!(|jack|GrooveboxTui { sampler: SamplerTui::try_from(jack)?, split: 16, focus: GrooveboxFocus::Sequencer, + size: Measure::new(), } }); @@ -34,13 +36,16 @@ pub enum GrooveboxFocus { audio!(|self:GrooveboxTui,_client,_process|Control::Continue); -render!(|self:GrooveboxTui|Bsp::n( - Fixed::h(2, SequencerStatus::from(&self.sequencer)), - Fill::h(Bsp::s( - Tui::min_y(30, &self.sequencer), - Fill::h(&self.sampler), - )), -)); +render!(|self:GrooveboxTui|Fill::wh(Bsp::n( + Fixed::h(2, GrooveboxStatus::from(self)), + Fill::h(lay!([ + Fill::h(&self.size), + Fill::h(Bsp::s( + Tui::min_y(20, &self.sequencer), + Tui::min_y(20, &self.sampler), + )) + ])), +))); pub enum GrooveboxCommand { Sequencer(SequencerCommand), diff --git a/crates/tek/src/tui/app_sampler.rs b/crates/tek/src/tui/app_sampler.rs index 04fc9db1..ed10f73d 100644 --- a/crates/tek/src/tui/app_sampler.rs +++ b/crates/tek/src/tui/app_sampler.rs @@ -24,6 +24,7 @@ pub struct SamplerTui { pub size: Measure, /// Lowest note displayed pub note_lo: AtomicUsize, + color: ItemColor } render!(|self: SamplerTui|{ @@ -32,28 +33,18 @@ render!(|self: SamplerTui|{ let fg = TuiTheme::g(200); let bg = TuiTheme::g(50); let border = Fill::wh(Outer(Style::default().fg(fg).bg(bg))); - let with_border = |x|lay!([border, Tui::inset_xy(1, 1, &x)]); - with_border(Fill::wh(Bsp::s( - "kyp", + let with_border = |x|lay!([ + border, + Tui::inset_xy(1, 1, Fill::wh(&x)) + ]); + Tui::bg(bg, Fill::wh(with_border(Bsp::s( + "Sampler", Bsp::e( Fixed::w(keys_width, keys()), - Fill::wh(lay!([&self.size, Fill::wh("nymka")])), + Fill::wh(lay!([&self.size, Fill::wh("Sample")])), ), - ))) + )))) }); - -struct SamplerKeys<'a>(&'a SamplerTui); -has_color!(|self: SamplerKeys<'a>|ItemColor::default()); -render!(|self: SamplerKeys<'a>|render(|to|Ok(render_keys_v::(to, self)))); -impl<'a> NoteRange for SamplerKeys<'a> { - fn note_lo (&self) -> &AtomicUsize { &self.0.note_lo } - fn note_axis (&self) -> &AtomicUsize { &self.0.size.y } -} - -pub enum SamplerMode { - // Load sample from path - Import(usize, FileBrowser), -} from_jack!(|jack|SamplerTui{ let midi_in = jack.read().unwrap().client().register_port("in", MidiIn::default())?; let audio_outs = vec![ @@ -66,6 +57,7 @@ from_jack!(|jack|SamplerTui{ mode: None, size: Measure::new(), note_lo: 36.into(), + color: ItemColor::default(), state: Sampler { jack: jack.clone(), name: "Sampler".into(), @@ -79,6 +71,25 @@ from_jack!(|jack|SamplerTui{ }, } }); + +struct SamplerKeys<'a>(&'a SamplerTui); +has_color!(|self: SamplerKeys<'a>|self.0.color); +render!(|self: SamplerKeys<'a>|render(|to|Ok(render_keys_v(to, self)))); +impl<'a> NoteRange for SamplerKeys<'a> { + fn note_lo (&self) -> &AtomicUsize { &self.0.note_lo } + fn note_axis (&self) -> &AtomicUsize { &self.0.size.y } +} +impl<'a> NotePoint for SamplerKeys<'a> { + fn note_len (&self) -> usize {0/*TODO*/} + fn set_note_len (&self, x: usize) {} + fn note_point (&self) -> usize {0/*TODO*/} + fn set_note_point (&self, x: usize) {} +} + +pub enum SamplerMode { + // Load sample from path + Import(usize, FileBrowser), +} handle!(|self:SamplerTui,input|SamplerCommand::execute_with_state(self, input)); pub enum SamplerCommand { Import(FileBrowserCommand), diff --git a/crates/tek/src/tui/phrase_editor.rs b/crates/tek/src/tui/phrase_editor.rs index 30f56684..f61779b1 100644 --- a/crates/tek/src/tui/phrase_editor.rs +++ b/crates/tek/src/tui/phrase_editor.rs @@ -140,11 +140,14 @@ impl NoteRange for MidiEditorModel { fn note_axis (&self) -> &AtomicUsize { self.mode.note_axis() } } -impl MidiPoint for MidiEditorModel { - fn note_len (&self) -> usize { self.mode.note_len()} +impl NotePoint for MidiEditorModel { + fn note_len (&self) -> usize { self.mode.note_len() } fn set_note_len (&self, x: usize) { self.mode.set_note_len(x) } fn note_point (&self) -> usize { self.mode.note_point() } fn set_note_point (&self, x: usize) { self.mode.set_note_point(x) } +} + +impl TimePoint for MidiEditorModel { fn time_point (&self) -> usize { self.mode.time_point() } fn set_time_point (&self, x: usize) { self.mode.set_time_point(x) } } diff --git a/crates/tek/src/tui/piano_h.rs b/crates/tek/src/tui/piano_h.rs index 58645814..3ed7840c 100644 --- a/crates/tek/src/tui/piano_h.rs +++ b/crates/tek/src/tui/piano_h.rs @@ -141,11 +141,13 @@ impl NoteRange for PianoHorizontal { fn note_lo (&self) -> &AtomicUsize { self.range.note_lo() } fn note_axis (&self) -> &AtomicUsize { self.range.note_axis() } } -impl MidiPoint for PianoHorizontal { - fn note_len (&self) -> usize { self.point.note_len()} +impl NotePoint for PianoHorizontal { + fn note_len (&self) -> usize { self.point.note_len() } fn set_note_len (&self, x: usize) { self.point.set_note_len(x) } fn note_point (&self) -> usize { self.point.note_point() } fn set_note_point (&self, x: usize) { self.point.set_note_point(x) } +} +impl TimePoint for PianoHorizontal { fn time_point (&self) -> usize { self.point.time_point() } fn set_time_point (&self, x: usize) { self.point.set_time_point(x) } } diff --git a/crates/tek/src/tui/piano_h/piano_h_keys.rs b/crates/tek/src/tui/piano_h/piano_h_keys.rs index 0fd34c09..2dbab758 100644 --- a/crates/tek/src/tui/piano_h/piano_h_keys.rs +++ b/crates/tek/src/tui/piano_h/piano_h_keys.rs @@ -2,14 +2,20 @@ use crate::*; use super::note_y_iter; pub struct PianoHorizontalKeys<'a>(pub(crate) &'a PianoHorizontal); -render!(|self: PianoHorizontalKeys<'a>|render(|to|Ok(render_keys_v::(to, self)))); +render!(|self: PianoHorizontalKeys<'a>|render(|to|Ok(render_keys_v(to, self)))); has_color!(|self: PianoHorizontalKeys<'a>|self.0.color.base); impl<'a> NoteRange for PianoHorizontalKeys<'a> { fn note_lo (&self) -> &AtomicUsize { &self.0.note_lo() } fn note_axis (&self) -> &AtomicUsize { &self.0.note_axis() } } +impl<'a> NotePoint for PianoHorizontalKeys<'a> { + fn note_len (&self) -> usize { self.0.note_len() } + fn set_note_len (&self, x: usize) { self.0.set_note_len(x) } + fn note_point (&self) -> usize { self.0.note_point() } + fn set_note_point (&self, x: usize) { self.0.set_note_point(x) } +} -pub fn render_keys_v (to: &mut E::Output, state: &T) { +pub fn render_keys_v (to: &mut TuiOutput, state: &T) { let color = state.color(); let note_lo = state.note_lo().get(); let note_hi = state.note_hi(); @@ -17,7 +23,7 @@ pub fn render_keys_v (to: &mut E::Output, s let [x, y0, w, h] = to.area().xywh(); let key_style = Some(Style::default().fg(Color::Rgb(192, 192, 192)).bg(Color::Rgb(0, 0, 0))); let off_style = Some(Style::default().fg(TuiTheme::g(160))); - let on_style = Some(Style::default().fg(TuiTheme::g(255)).bg(color.light.rgb).bold()); + let on_style = Some(Style::default().fg(TuiTheme::g(255)).bg(color.rgb).bold()); for (area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { to.blit(&to_key(note), x, screen_y, key_style); if note > 127 { diff --git a/crates/tek/src/tui/status.rs b/crates/tek/src/tui/status.rs index 8359e9da..bafbe953 100644 --- a/crates/tek/src/tui/status.rs +++ b/crates/tek/src/tui/status.rs @@ -1,3 +1,4 @@ mod status_arranger; pub(crate) use self::status_arranger::*; mod status_edit; pub(crate) use self::status_edit::*; mod status_sequencer; pub(crate) use self::status_sequencer::*; +mod status_groovebox; pub(crate) use self::status_groovebox::*; diff --git a/crates/tek/src/tui/status/status_groovebox.rs b/crates/tek/src/tui/status/status_groovebox.rs new file mode 100644 index 00000000..65d77a2d --- /dev/null +++ b/crates/tek/src/tui/status/status_groovebox.rs @@ -0,0 +1,52 @@ +use crate::*; + +/// Status bar for sequencer app +#[derive(Clone)] +pub struct GrooveboxStatus { + pub(crate) width: usize, + pub(crate) cpu: Option, + pub(crate) size: String, + pub(crate) res: String, + pub(crate) playing: bool, +} +from!(|state:&GrooveboxTui|GrooveboxStatus = { + let samples = state.sequencer.clock.chunk.load(Relaxed); + let rate = state.sequencer.clock.timebase.sr.get(); + let buffer = samples as f64 / rate; + let width = state.size.w(); + Self { + width, + playing: state.sequencer.clock.is_rolling(), + cpu: state.sequencer.perf.percentage().map(|cpu|format!("│{cpu:.01}%")), + size: format!("{}x{}│", width, state.size.h()), + res: format!("│{}s│{:.1}kHz│{:.1}ms│", samples, rate / 1000., buffer * 1000.), + } +}); +render!(|self: GrooveboxStatus|Fixed::h(2, lay!([ + Self::help(), + Fill::wh(Align::se(Tui::fg_bg(TuiTheme::orange(), TuiTheme::g(25), self.stats()))), +]))); +impl GrooveboxStatus { + fn help () -> impl Render { + let single = |binding, command|row!([" ", col!([ + Tui::fg(TuiTheme::yellow(), binding), + command + ])]); + let double = |(b1, c1), (b2, c2)|col!([ + row!([" ", Tui::fg(TuiTheme::yellow(), b1), " ", c1,]), + row!([" ", Tui::fg(TuiTheme::yellow(), b2), " ", c2,]), + ]); + Tui::fg_bg(TuiTheme::g(255), TuiTheme::g(50), row!([ + single("SPACE", "play/pause"), + double(("▲▼▶◀", "cursor"), ("Ctrl", "scroll"), ), + double(("a", "append"), ("s", "set note"),), + double((",.", "length"), ("<>", "triplet"), ), + double(("[]", "phrase"), ("{}", "order"), ), + double(("q", "enqueue"), ("e", "edit"), ), + double(("c", "color"), ("", ""),), + ])) + } + fn stats (&self) -> impl Render + use<'_> { + row!([&self.cpu, &self.res, &self.size]) + } +} From e69cf6d9cb3bd54df5cb127d859331710da1e7c8 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 27 Dec 2024 20:55:34 +0100 Subject: [PATCH 031/815] layer midi status; navigate sample list --- crates/tek/src/tui/app_arranger.rs | 2 +- crates/tek/src/tui/app_sampler.rs | 91 +++++++++++++++++------- crates/tek/src/tui/app_sequencer.rs | 2 +- crates/tek/src/tui/piano_h.rs | 51 ++++++++++--- crates/tek/src/tui/status/status_edit.rs | 35 ++++----- 5 files changed, 127 insertions(+), 54 deletions(-) diff --git a/crates/tek/src/tui/app_arranger.rs b/crates/tek/src/tui/app_arranger.rs index 9866c45a..3a09d730 100644 --- a/crates/tek/src/tui/app_arranger.rs +++ b/crates/tek/src/tui/app_arranger.rs @@ -97,7 +97,7 @@ render!(|self: ArrangerTui|{ let pool_size = if self.phrases.visible { self.splits[1] } else { 0 }; let with_pool = |x|Split::left(false, pool_size, PoolView(&self.phrases), x); let status = ArrangerStatus::from(self); - let with_editbar = |x|Tui::split_n(false, 3, MidiEditStatus(&self.editor), x); + let with_editbar = |x|Tui::split_n(false, 1, MidiEditStatus(&self.editor), x); let with_status = |x|Tui::split_n(false, 2, status, x); let with_size = |x|lay!([&self.size, x]); let arranger = ||lay!(|add|{ diff --git a/crates/tek/src/tui/app_sampler.rs b/crates/tek/src/tui/app_sampler.rs index ed10f73d..881ce6e7 100644 --- a/crates/tek/src/tui/app_sampler.rs +++ b/crates/tek/src/tui/app_sampler.rs @@ -24,27 +24,10 @@ pub struct SamplerTui { pub size: Measure, /// Lowest note displayed pub note_lo: AtomicUsize, - color: ItemColor + pub note_pt: AtomicUsize, + color: ItemPalette } -render!(|self: SamplerTui|{ - let keys_width = 5; - let keys = move||SamplerKeys(self); - let fg = TuiTheme::g(200); - let bg = TuiTheme::g(50); - let border = Fill::wh(Outer(Style::default().fg(fg).bg(bg))); - let with_border = |x|lay!([ - border, - Tui::inset_xy(1, 1, Fill::wh(&x)) - ]); - Tui::bg(bg, Fill::wh(with_border(Bsp::s( - "Sampler", - Bsp::e( - Fixed::w(keys_width, keys()), - Fill::wh(lay!([&self.size, Fill::wh("Sample")])), - ), - )))) -}); from_jack!(|jack|SamplerTui{ let midi_in = jack.read().unwrap().client().register_port("in", MidiIn::default())?; let audio_outs = vec![ @@ -57,7 +40,8 @@ from_jack!(|jack|SamplerTui{ mode: None, size: Measure::new(), note_lo: 36.into(), - color: ItemColor::default(), + note_pt: 36.into(), + color: ItemPalette::from(Color::Rgb(64, 128, 32)), state: Sampler { jack: jack.clone(), name: "Sampler".into(), @@ -72,8 +56,52 @@ from_jack!(|jack|SamplerTui{ } }); +render!(|self: SamplerTui|{ + let keys_width = 5; + let keys = move||SamplerKeys(self); + let fg = self.color.base.rgb; + let bg = self.color.darkest.rgb; + let border = Fill::wh(Outer(Style::default().fg(fg).bg(bg))); + let inset = 0; + let with_border = |x|lay!([border, Tui::inset_xy(inset, inset, Fill::wh(&x))]); + let with_size = |x|lay!([self.size, x]); + Tui::bg(bg, Fill::wh(with_border(Bsp::s( + Tui::push_x(1, Tui::fg(self.color.light.rgb, Tui::bold(true, "Sampler"))), + with_size(Tui::shrink_y(1, Bsp::e( + Fixed::w(keys_width, keys()), + Fill::wh(render(|to: &mut TuiOutput|Ok({ + let x = to.area.x() + 1; + let rows = self.size.h() as u16; + let bg_base = self.color.darkest.rgb; + let bg_selected = self.color.darker.rgb; + let style_empty = Style::default().fg(self.color.base.rgb); + let style_full = Style::default().fg(self.color.lighter.rgb); + let note_hi = self.note_hi(); + let note_pt = self.note_point(); + for y in 0..rows { + let note = note_hi - y as usize; + let bg = if note == note_pt { bg_selected } else { bg_base }; + let style = Some(style_empty.bg(bg)); + to.blit(&" (no sample) ", x, to.area.y() + y, style) + } + }))) + ))), + )))) +}); + +impl NoteRange for SamplerTui { + fn note_lo (&self) -> &AtomicUsize { &self.note_lo } + fn note_axis (&self) -> &AtomicUsize { &self.size.y } +} +impl NotePoint for SamplerTui { + fn note_len (&self) -> usize {0/*TODO*/} + fn set_note_len (&self, x: usize) {} + fn note_point (&self) -> usize { self.note_pt.load(Relaxed) } + fn set_note_point (&self, x: usize) { self.note_pt.store(x, Relaxed); } +} + struct SamplerKeys<'a>(&'a SamplerTui); -has_color!(|self: SamplerKeys<'a>|self.0.color); +has_color!(|self: SamplerKeys<'a>|self.0.color.base); render!(|self: SamplerKeys<'a>|render(|to|Ok(render_keys_v(to, self)))); impl<'a> NoteRange for SamplerKeys<'a> { fn note_lo (&self) -> &AtomicUsize { &self.0.note_lo } @@ -82,8 +110,8 @@ impl<'a> NoteRange for SamplerKeys<'a> { impl<'a> NotePoint for SamplerKeys<'a> { fn note_len (&self) -> usize {0/*TODO*/} fn set_note_len (&self, x: usize) {} - fn note_point (&self) -> usize {0/*TODO*/} - fn set_note_point (&self, x: usize) {} + fn note_point (&self) -> usize { self.0.note_point() } + fn set_note_point (&self, x: usize) { self.0.set_note_point(x); } } pub enum SamplerMode { @@ -101,15 +129,19 @@ pub enum SamplerCommand { NoteOn(u7, u7), NoteOff(u7) } -input_to_command!(SamplerCommand:|state:SamplerTui,input|match state.mode { +input_to_command!(SamplerCommand: |state: SamplerTui, input|match state.mode { Some(SamplerMode::Import(..)) => Self::Import( FileBrowserCommand::input_to_command(state, input)? ), _ => match input.event() { // load sample key_pat!(Char('l')) => Self::Import(FileBrowserCommand::Begin), - key_pat!(KeyCode::Up) => { todo!() }, - key_pat!(KeyCode::Down) => { todo!() }, + key_pat!(KeyCode::Up) => { + Self::SelectNote(state.note_point().overflowing_add(1).0.min(127)) + }, + key_pat!(KeyCode::Down) => { + Self::SelectNote(state.note_point().overflowing_sub(1).0.min(127)) + }, _ => return None } //key_pat!(KeyCode::Char('p')) => if let Some(sample) = self.sample() { @@ -135,12 +167,17 @@ input_to_command!(FileBrowserCommand:|state:SamplerTui,input|match input { _ => return None }); command!(|self:SamplerCommand,state:SamplerTui|match self { - SamplerCommand::Import(FileBrowserCommand::Begin) => { + Self::Import(FileBrowserCommand::Begin) => { let voices = &state.state.voices; let sample = Arc::new(RwLock::new(Sample::new("", 0, 0, vec![]))); state.mode = Some(SamplerMode::Import(0, FileBrowser::new(None)?)); None }, + Self::SelectNote(index) => { + let old = state.note_point(); + state.set_note_point(index); + Some(Self::SelectNote(old)) + }, _ => todo!() }); command!(|self:FileBrowserCommand,state:SamplerTui|match self { diff --git a/crates/tek/src/tui/app_sequencer.rs b/crates/tek/src/tui/app_sequencer.rs index 5bc6a43e..9b86fd2d 100644 --- a/crates/tek/src/tui/app_sequencer.rs +++ b/crates/tek/src/tui/app_sequencer.rs @@ -44,7 +44,7 @@ render!(|self: SequencerTui|{ let with_pool = move|x|Tui::split_w(false, pool_w, pool, x); let status = SequencerStatus::from(self); let with_status = |x|Tui::split_n(false, if self.status { 2 } else { 0 }, status, x); - let with_editbar = |x|Tui::split_n(false, 3, MidiEditStatus(&self.editor), x); + let with_editbar = |x|Tui::split_n(false, 1, MidiEditStatus(&self.editor), x); let with_size = |x|lay!([self.size, x]); let editor = with_editbar(with_pool(Fill::wh(&self.editor))); let color = self.player.play_phrase().as_ref().map(|(_,p)| diff --git a/crates/tek/src/tui/piano_h.rs b/crates/tek/src/tui/piano_h.rs index 3ed7840c..c7be69ac 100644 --- a/crates/tek/src/tui/piano_h.rs +++ b/crates/tek/src/tui/piano_h.rs @@ -52,20 +52,55 @@ impl PianoHorizontal { } render!(|self: PianoHorizontal|{ + + let (color, name, length, looped) = if let Some(phrase) = self.phrase().as_ref().map(|p|p.read().unwrap()) { + (phrase.color, phrase.name.clone(), phrase.length, phrase.looped) + } else { + (ItemPalette::from(TuiTheme::g(64)), String::new(), 0, false) + }; + + let field = move|x, y|row!([ + Tui::fg_bg(color.lighter.rgb, color.darker.rgb, Tui::bold(true, x)), + Tui::fg_bg(color.light.rgb, color.darker.rgb, Tui::bold(true, "│")), + Tui::fg_bg(color.lighter.rgb, color.dark.rgb, &y), + ]); + let keys_width = 5; let keys = move||PianoHorizontalKeys(self); + let timeline = move||PianoHorizontalTimeline(self); + let notes = move||PianoHorizontalNotes(self); + let cursor = move||PianoHorizontalCursor(self); + let border = Fill::wh(Outer(Style::default().fg(self.color.dark.rgb).bg(self.color.darkest.rgb))); - let with_border = |x|lay!([border, Tui::inset_xy(1, 1, &x)]); - with_border(Fill::wh(Bsp::s( - Fixed::h(1, Bsp::e(Fixed::w(keys_width, ""), Fill::w(timeline()),)), - Bsp::e( - Fixed::w(keys_width, keys()), - Fill::wh(lay!([&self.size, Fill::wh(lay!([Fill::wh(notes()), Fill::wh(cursor()),]))])), - ), - ))) + let with_border = |x|lay!([border, Tui::inset_xy(0, 0, &x)]); + + with_border(lay!([ + Tui::push_x(0, row!(![ + " ", + field(" Edit", name.to_string()), + field(" Length", length.to_string()), + field(" Loop", looped.to_string()) + ])), + Tui::inset_xy(0, 1, Fill::wh(Bsp::s( + Fixed::h(1, Bsp::e( + Fixed::w(keys_width, ""), + Fill::w(timeline()), + )), + Bsp::e( + Fixed::w(keys_width, keys()), + Fill::wh(lay!([ + &self.size, + Fill::wh(lay!([ + Fill::wh(notes()), + Fill::wh(cursor()), + ])) + ])), + ), + ))) + ])) }); impl PianoHorizontal { diff --git a/crates/tek/src/tui/status/status_edit.rs b/crates/tek/src/tui/status/status_edit.rs index ebddb18d..2bbde7be 100644 --- a/crates/tek/src/tui/status/status_edit.rs +++ b/crates/tek/src/tui/status/status_edit.rs @@ -2,29 +2,30 @@ use crate::*; pub struct MidiEditStatus<'a>(pub &'a MidiEditorModel); render!(|self:MidiEditStatus<'a>|{ + let (color, name, length, looped) = if let Some(phrase) = self.0.phrase().as_ref().map(|p|p.read().unwrap()) { (phrase.color, phrase.name.clone(), phrase.length, phrase.looped) } else { (ItemPalette::from(TuiTheme::g(64)), String::new(), 0, false) }; - let bg = color.darkest.rgb; - let fg = color.lightest.rgb; + let field = move|x, y|row!([ Tui::fg_bg(color.lighter.rgb, color.darker.rgb, Tui::bold(true, x)), Tui::fg_bg(color.light.rgb, color.darker.rgb, Tui::bold(true, "│")), - Fill::w(Tui::fg_bg(color.lightest.rgb, color.dark.rgb, &y)), + Tui::fg_bg(color.lightest.rgb, color.dark.rgb, &y), ]); - Fill::w(Tui::fg_bg(fg, bg, row!([ - Fixed::wh(26, 3, col!(![ - field(" Edit", name.to_string()), - field(" Length", length.to_string()), - field(" Loop", looped.to_string())])), - Fixed::wh(30, 3, col!(![ - field(" Time", format!("{}/{}-{} ({}*{}) {}", - self.0.time_point(), self.0.time_start().get(), self.0.time_end(), - self.0.time_axis().get(), self.0.time_zoom().get(), - if self.0.time_lock().get() { "[lock]" } else { " " })), - field(" Note", format!("{} ({}) {} | {}-{} ({})", - self.0.note_point(), to_note_name(self.0.note_point()), self.0.note_len(), - to_note_name(self.0.note_lo().get()), to_note_name(self.0.note_hi()), - self.0.note_axis().get()))]))])))}); + + let bg = color.darkest.rgb; + let fg = color.lightest.rgb; + Tui::bg(bg, Fill::w(Tui::fg(fg, row!([ + field(" Time", format!("{}/{}-{} ({}*{}) {}", + self.0.time_point(), self.0.time_start().get(), self.0.time_end(), + self.0.time_axis().get(), self.0.time_zoom().get(), + if self.0.time_lock().get() { "[lock]" } else { " " })), + " ", + field(" Note", format!("{} ({}) {} | {}-{} ({})", + self.0.note_point(), to_note_name(self.0.note_point()), self.0.note_len(), + to_note_name(self.0.note_lo().get()), to_note_name(self.0.note_hi()), + self.0.note_axis().get())) + ])))) +}); From cb7ba855ab3eb07fc23b04314340a8820b3428a2 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 27 Dec 2024 21:07:56 +0100 Subject: [PATCH 032/815] refactor midi_note and remove audio_in/out empty mods --- crates/tek/src/audio.rs | 4 - crates/tek/src/audio/audio_in.rs | 0 crates/tek/src/audio/audio_out.rs | 0 crates/tek/src/midi.rs | 34 ++++++-- crates/tek/src/midi/midi_in.rs | 10 --- crates/tek/src/midi/midi_note.rs | 130 +--------------------------- crates/tek/src/midi/midi_out.rs | 12 --- crates/tek/src/midi/midi_point.rs | 50 +++++++++++ crates/tek/src/midi/midi_range.rs | 64 ++++++++++++++ crates/tek/src/midi/midi_view.rs | 26 ++++++ crates/tek/src/tui/app_groovebox.rs | 10 +-- crates/tek/src/tui/app_sampler.rs | 16 ++-- 12 files changed, 182 insertions(+), 174 deletions(-) delete mode 100644 crates/tek/src/audio/audio_in.rs delete mode 100644 crates/tek/src/audio/audio_out.rs delete mode 100644 crates/tek/src/midi/midi_in.rs delete mode 100644 crates/tek/src/midi/midi_out.rs create mode 100644 crates/tek/src/midi/midi_point.rs create mode 100644 crates/tek/src/midi/midi_range.rs create mode 100644 crates/tek/src/midi/midi_view.rs diff --git a/crates/tek/src/audio.rs b/crates/tek/src/audio.rs index 650bb769..3d287f09 100644 --- a/crates/tek/src/audio.rs +++ b/crates/tek/src/audio.rs @@ -1,9 +1,5 @@ use crate::*; -mod audio_in; - -mod audio_out; - mod sampler; pub(crate) use self::sampler::*; pub use self::sampler::{Sampler, Sample, Voice}; diff --git a/crates/tek/src/audio/audio_in.rs b/crates/tek/src/audio/audio_in.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/crates/tek/src/audio/audio_out.rs b/crates/tek/src/audio/audio_out.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/crates/tek/src/midi.rs b/crates/tek/src/midi.rs index 73379924..7e97a594 100644 --- a/crates/tek/src/midi.rs +++ b/crates/tek/src/midi.rs @@ -1,14 +1,36 @@ use crate::*; -pub(crate) mod midi_in; pub(crate) use midi_in::*; -pub(crate) mod midi_launch; pub(crate) use midi_launch::*; -pub(crate) mod midi_note; pub(crate) use midi_note::*; -pub(crate) mod midi_out; pub(crate) use midi_out::*; -pub(crate) mod midi_clip; pub(crate) use midi_clip::*; -pub(crate) mod midi_play; pub(crate) use midi_play::*; pub(crate) mod midi_pool; pub(crate) use midi_pool::*; +pub(crate) mod midi_clip; pub(crate) use midi_clip::*; +pub(crate) mod midi_launch; pub(crate) use midi_launch::*; +pub(crate) mod midi_play; pub(crate) use midi_play::*; pub(crate) mod midi_rec; pub(crate) use midi_rec::*; +pub(crate) mod midi_note; pub(crate) use midi_note::*; +pub(crate) mod midi_range; pub(crate) use midi_range::*; +pub(crate) mod midi_point; pub(crate) use midi_point::*; +pub(crate) mod midi_view; pub(crate) use midi_view::*; + +/// Trait for thing that may receive MIDI. +pub trait HasMidiIns { + fn midi_ins (&self) -> &Vec>; + fn midi_ins_mut (&mut self) -> &mut Vec>; + fn has_midi_ins (&self) -> bool { + !self.midi_ins().is_empty() + } +} + +/// Trait for thing that may output MIDI. +pub trait HasMidiOuts { + fn midi_outs (&self) -> &Vec>; + fn midi_outs_mut (&mut self) -> &mut Vec>; + fn has_midi_outs (&self) -> bool { + !self.midi_outs().is_empty() + } + /// Buffer for serializing a MIDI event. FIXME rename + fn midi_note (&mut self) -> &mut Vec; +} + /// Add "all notes off" to the start of a buffer. pub fn all_notes_off (output: &mut [Vec>]) { let mut buf = vec![]; diff --git a/crates/tek/src/midi/midi_in.rs b/crates/tek/src/midi/midi_in.rs deleted file mode 100644 index 29e16946..00000000 --- a/crates/tek/src/midi/midi_in.rs +++ /dev/null @@ -1,10 +0,0 @@ -use crate::*; - -/// Trait for thing that may receive MIDI. -pub trait HasMidiIns { - fn midi_ins (&self) -> &Vec>; - fn midi_ins_mut (&mut self) -> &mut Vec>; - fn has_midi_ins (&self) -> bool { - !self.midi_ins().is_empty() - } -} diff --git a/crates/tek/src/midi/midi_note.rs b/crates/tek/src/midi/midi_note.rs index e13127cc..ca08b5ea 100644 --- a/crates/tek/src/midi/midi_note.rs +++ b/crates/tek/src/midi/midi_note.rs @@ -1,5 +1,7 @@ use crate::*; + pub struct Note; + impl Note { /// (pulses, name), assuming 96 PPQ pub const DURATIONS: [(usize, &str);26] = [ @@ -32,131 +34,3 @@ impl Note { "" } } -pub trait MidiView: MidiRange + MidiPoint + HasSize { - /// Make sure cursor is within range - fn autoscroll (&self) { - let note_point = self.note_point().min(127); - let note_lo = self.note_lo().get(); - let note_hi = self.note_hi(); - if note_point < note_lo { - self.note_lo().set(note_point); - } else if note_point > note_hi { - self.note_lo().set((note_lo + note_point).saturating_sub(note_hi)); - } - } - /// Make sure range is within display - fn autozoom (&self) { - let time_len = self.time_len().get(); - let time_axis = self.time_axis().get(); - let time_zoom = self.time_zoom().get(); - //while time_len.div_ceil(time_zoom) > time_axis { - //println!("\r{time_len} {time_zoom} {time_axis}"); - //time_zoom = Note::next(time_zoom); - //} - //self.time_zoom().set(time_zoom); - } -} -#[derive(Debug, Clone)] -pub struct MidiRangeModel { - pub time_len: Arc, - /// Length of visible time axis - pub time_axis: Arc, - /// Earliest time displayed - pub time_start: Arc, - /// Time step - pub time_zoom: Arc, - /// Auto rezoom to fit in time axis - pub time_lock: Arc, - /// Length of visible note axis - pub note_axis: Arc, - // Lowest note displayed - pub note_lo: Arc, -} -from!(|data:(usize, bool)|MidiRangeModel = Self { - time_len: Arc::new(0.into()), - note_axis: Arc::new(0.into()), - note_lo: Arc::new(0.into()), - time_axis: Arc::new(0.into()), - time_start: Arc::new(0.into()), - time_zoom: Arc::new(data.0.into()), - time_lock: Arc::new(data.1.into()), -}); -pub trait TimeRange { - fn time_len (&self) -> &AtomicUsize; - fn time_zoom (&self) -> &AtomicUsize; - fn time_lock (&self) -> &AtomicBool; - fn time_start (&self) -> &AtomicUsize; - fn time_axis (&self) -> &AtomicUsize; - fn time_end (&self) -> usize { - self.time_start().get() + self.time_axis().get() * self.time_zoom().get() - } -} -pub trait NoteRange { - fn note_lo (&self) -> &AtomicUsize; - fn note_axis (&self) -> &AtomicUsize; - fn note_hi (&self) -> usize { - (self.note_lo().get() + self.note_axis().get().saturating_sub(1)).min(127) - } -} -pub trait MidiRange: TimeRange + NoteRange {} -impl MidiRange for T {} -impl TimeRange for MidiRangeModel { - fn time_len (&self) -> &AtomicUsize { &self.time_len } - fn time_zoom (&self) -> &AtomicUsize { &self.time_zoom } - fn time_lock (&self) -> &AtomicBool { &self.time_lock } - fn time_start (&self) -> &AtomicUsize { &self.time_start } - fn time_axis (&self) -> &AtomicUsize { &self.time_axis } -} -impl NoteRange for MidiRangeModel { - fn note_lo (&self) -> &AtomicUsize { &self.note_lo } - fn note_axis (&self) -> &AtomicUsize { &self.note_axis } -} - -#[derive(Debug, Clone)] -pub struct MidiPointModel { - /// Time coordinate of cursor - pub time_point: Arc, - /// Note coordinate of cursor - pub note_point: Arc, - /// Length of note that will be inserted, in pulses - pub note_len: Arc, -} - -impl Default for MidiPointModel { - fn default () -> Self { - Self { - time_point: Arc::new(0.into()), - note_point: Arc::new(36.into()), - note_len: Arc::new(24.into()), - } - } -} - -pub trait NotePoint { - fn note_len (&self) -> usize; - fn set_note_len (&self, x: usize); - fn note_point (&self) -> usize; - fn set_note_point (&self, x: usize); - fn note_end (&self) -> usize { self.note_point() + self.note_len() } -} - -pub trait TimePoint { - fn time_point (&self) -> usize; - fn set_time_point (&self, x: usize); -} - -pub trait MidiPoint: NotePoint + TimePoint {} - -impl MidiPoint for T {} - -impl NotePoint for MidiPointModel { - fn note_len (&self) -> usize { self.note_len.load(Relaxed)} - fn set_note_len (&self, x: usize) { self.note_len.store(x, Relaxed) } - fn note_point (&self) -> usize { self.note_point.load(Relaxed).min(127) } - fn set_note_point (&self, x: usize) { self.note_point.store(x.min(127), Relaxed) } -} - -impl TimePoint for MidiPointModel { - fn time_point (&self) -> usize { self.time_point.load(Relaxed) } - fn set_time_point (&self, x: usize) { self.time_point.store(x, Relaxed) } -} diff --git a/crates/tek/src/midi/midi_out.rs b/crates/tek/src/midi/midi_out.rs deleted file mode 100644 index ca9d34c8..00000000 --- a/crates/tek/src/midi/midi_out.rs +++ /dev/null @@ -1,12 +0,0 @@ -use crate::*; - -/// Trait for thing that may output MIDI. -pub trait HasMidiOuts { - fn midi_outs (&self) -> &Vec>; - fn midi_outs_mut (&mut self) -> &mut Vec>; - fn has_midi_outs (&self) -> bool { - !self.midi_outs().is_empty() - } - /// Buffer for serializing a MIDI event. FIXME rename - fn midi_note (&mut self) -> &mut Vec; -} diff --git a/crates/tek/src/midi/midi_point.rs b/crates/tek/src/midi/midi_point.rs new file mode 100644 index 00000000..bc85de88 --- /dev/null +++ b/crates/tek/src/midi/midi_point.rs @@ -0,0 +1,50 @@ +use crate::*; + +#[derive(Debug, Clone)] +pub struct MidiPointModel { + /// Time coordinate of cursor + pub time_point: Arc, + /// Note coordinate of cursor + pub note_point: Arc, + /// Length of note that will be inserted, in pulses + pub note_len: Arc, +} + +impl Default for MidiPointModel { + fn default () -> Self { + Self { + time_point: Arc::new(0.into()), + note_point: Arc::new(36.into()), + note_len: Arc::new(24.into()), + } + } +} + +pub trait NotePoint { + fn note_len (&self) -> usize; + fn set_note_len (&self, x: usize); + fn note_point (&self) -> usize; + fn set_note_point (&self, x: usize); + fn note_end (&self) -> usize { self.note_point() + self.note_len() } +} + +pub trait TimePoint { + fn time_point (&self) -> usize; + fn set_time_point (&self, x: usize); +} + +pub trait MidiPoint: NotePoint + TimePoint {} + +impl MidiPoint for T {} + +impl NotePoint for MidiPointModel { + fn note_len (&self) -> usize { self.note_len.load(Relaxed)} + fn set_note_len (&self, x: usize) { self.note_len.store(x, Relaxed) } + fn note_point (&self) -> usize { self.note_point.load(Relaxed).min(127) } + fn set_note_point (&self, x: usize) { self.note_point.store(x.min(127), Relaxed) } +} + +impl TimePoint for MidiPointModel { + fn time_point (&self) -> usize { self.time_point.load(Relaxed) } + fn set_time_point (&self, x: usize) { self.time_point.store(x, Relaxed) } +} diff --git a/crates/tek/src/midi/midi_range.rs b/crates/tek/src/midi/midi_range.rs new file mode 100644 index 00000000..308a4ae2 --- /dev/null +++ b/crates/tek/src/midi/midi_range.rs @@ -0,0 +1,64 @@ +use crate::*; + +#[derive(Debug, Clone)] +pub struct MidiRangeModel { + pub time_len: Arc, + /// Length of visible time axis + pub time_axis: Arc, + /// Earliest time displayed + pub time_start: Arc, + /// Time step + pub time_zoom: Arc, + /// Auto rezoom to fit in time axis + pub time_lock: Arc, + /// Length of visible note axis + pub note_axis: Arc, + // Lowest note displayed + pub note_lo: Arc, +} + +from!(|data:(usize, bool)|MidiRangeModel = Self { + time_len: Arc::new(0.into()), + note_axis: Arc::new(0.into()), + note_lo: Arc::new(0.into()), + time_axis: Arc::new(0.into()), + time_start: Arc::new(0.into()), + time_zoom: Arc::new(data.0.into()), + time_lock: Arc::new(data.1.into()), +}); + +pub trait TimeRange { + fn time_len (&self) -> &AtomicUsize; + fn time_zoom (&self) -> &AtomicUsize; + fn time_lock (&self) -> &AtomicBool; + fn time_start (&self) -> &AtomicUsize; + fn time_axis (&self) -> &AtomicUsize; + fn time_end (&self) -> usize { + self.time_start().get() + self.time_axis().get() * self.time_zoom().get() + } +} + +pub trait NoteRange { + fn note_lo (&self) -> &AtomicUsize; + fn note_axis (&self) -> &AtomicUsize; + fn note_hi (&self) -> usize { + (self.note_lo().get() + self.note_axis().get().saturating_sub(1)).min(127) + } +} + +pub trait MidiRange: TimeRange + NoteRange {} + +impl MidiRange for T {} + +impl TimeRange for MidiRangeModel { + fn time_len (&self) -> &AtomicUsize { &self.time_len } + fn time_zoom (&self) -> &AtomicUsize { &self.time_zoom } + fn time_lock (&self) -> &AtomicBool { &self.time_lock } + fn time_start (&self) -> &AtomicUsize { &self.time_start } + fn time_axis (&self) -> &AtomicUsize { &self.time_axis } +} + +impl NoteRange for MidiRangeModel { + fn note_lo (&self) -> &AtomicUsize { &self.note_lo } + fn note_axis (&self) -> &AtomicUsize { &self.note_axis } +} diff --git a/crates/tek/src/midi/midi_view.rs b/crates/tek/src/midi/midi_view.rs new file mode 100644 index 00000000..0d617adb --- /dev/null +++ b/crates/tek/src/midi/midi_view.rs @@ -0,0 +1,26 @@ +use crate::*; + +pub trait MidiView: MidiRange + MidiPoint + HasSize { + /// Make sure cursor is within range + fn autoscroll (&self) { + let note_point = self.note_point().min(127); + let note_lo = self.note_lo().get(); + let note_hi = self.note_hi(); + if note_point < note_lo { + self.note_lo().set(note_point); + } else if note_point > note_hi { + self.note_lo().set((note_lo + note_point).saturating_sub(note_hi)); + } + } + /// Make sure range is within display + fn autozoom (&self) { + let time_len = self.time_len().get(); + let time_axis = self.time_axis().get(); + let time_zoom = self.time_zoom().get(); + //while time_len.div_ceil(time_zoom) > time_axis { + //println!("\r{time_len} {time_zoom} {time_axis}"); + //time_zoom = Note::next(time_zoom); + //} + //self.time_zoom().set(time_zoom); + } +} diff --git a/crates/tek/src/tui/app_groovebox.rs b/crates/tek/src/tui/app_groovebox.rs index 05f1ece1..a04774c2 100644 --- a/crates/tek/src/tui/app_groovebox.rs +++ b/crates/tek/src/tui/app_groovebox.rs @@ -55,13 +55,11 @@ pub enum GrooveboxCommand { handle!(|self: GrooveboxTui, input|GrooveboxCommand::execute_with_state(self, input)); input_to_command!(GrooveboxCommand: |state: GrooveboxTui,input|match input.event() { - key_pat!(Up) | - key_pat!(Down) | - key_pat!(Left) | - key_pat!(Right) => - GrooveboxCommand::Sampler(SamplerCommand::input_to_command(&state.sampler, input)?), + key_pat!(Up) | key_pat!(Down) | key_pat!(Left) | key_pat!(Right) | + key_pat!(Shift-Char('L')) => + SamplerCommand::input_to_command(&state.sampler, input).map(Self::Sampler)?, _ => - GrooveboxCommand::Sequencer(SequencerCommand::input_to_command(&state.sequencer, input)?), + SequencerCommand::input_to_command(&state.sequencer, input).map(Self::Sequencer)?, }); command!(|self:GrooveboxCommand,state:GrooveboxTui|match self { diff --git a/crates/tek/src/tui/app_sampler.rs b/crates/tek/src/tui/app_sampler.rs index 881ce6e7..60ca1ee9 100644 --- a/crates/tek/src/tui/app_sampler.rs +++ b/crates/tek/src/tui/app_sampler.rs @@ -62,27 +62,25 @@ render!(|self: SamplerTui|{ let fg = self.color.base.rgb; let bg = self.color.darkest.rgb; let border = Fill::wh(Outer(Style::default().fg(fg).bg(bg))); - let inset = 0; - let with_border = |x|lay!([border, Tui::inset_xy(inset, inset, Fill::wh(&x))]); + let with_border = |x|lay!([border, Fill::wh(&x)]); let with_size = |x|lay!([self.size, x]); Tui::bg(bg, Fill::wh(with_border(Bsp::s( - Tui::push_x(1, Tui::fg(self.color.light.rgb, Tui::bold(true, "Sampler"))), + Tui::fg(self.color.light.rgb, Tui::bold(true, "Sampler")), with_size(Tui::shrink_y(1, Bsp::e( Fixed::w(keys_width, keys()), Fill::wh(render(|to: &mut TuiOutput|Ok({ - let x = to.area.x() + 1; - let rows = self.size.h() as u16; + let x = to.area.x(); let bg_base = self.color.darkest.rgb; let bg_selected = self.color.darker.rgb; let style_empty = Style::default().fg(self.color.base.rgb); let style_full = Style::default().fg(self.color.lighter.rgb); let note_hi = self.note_hi(); let note_pt = self.note_point(); - for y in 0..rows { + for y in 0..self.size.h() { let note = note_hi - y as usize; let bg = if note == note_pt { bg_selected } else { bg_base }; let style = Some(style_empty.bg(bg)); - to.blit(&" (no sample) ", x, to.area.y() + y, style) + to.blit(&" (no sample) ", x, to.area.y() + y as u16, style) } }))) ))), @@ -135,7 +133,9 @@ input_to_command!(SamplerCommand: |state: SamplerTui, input|match state.mod ), _ => match input.event() { // load sample - key_pat!(Char('l')) => Self::Import(FileBrowserCommand::Begin), + key_pat!(Shift-Char('L')) => { + Self::Import(FileBrowserCommand::Begin) + }, key_pat!(KeyCode::Up) => { Self::SelectNote(state.note_point().overflowing_add(1).0.min(127)) }, From 0779560502ffe9fd38a21097b3032c78d395656a Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 27 Dec 2024 21:26:16 +0100 Subject: [PATCH 033/815] flatten modules somewhat --- crates/cli/src/cli_arranger.rs | 7 +- crates/cli/src/cli_groovebox.rs | 2 +- crates/cli/src/cli_sampler.rs | 2 +- crates/edn/src/lib.rs | 2 +- .../src/{tui/app_arranger.rs => arranger.rs} | 9 + .../src/{tui => arranger}/arranger_command.rs | 0 .../tek/src/{tui => arranger}/arranger_h.rs | 0 .../src/{tui => arranger}/arranger_mode.rs | 0 .../src/{tui => arranger}/arranger_scene.rs | 0 .../src/{tui => arranger}/arranger_select.rs | 0 .../src/{tui => arranger}/arranger_track.rs | 0 .../tek/src/{tui => arranger}/arranger_v.rs | 0 .../{tui => arranger}/arranger_v/v_clips.rs | 0 .../{tui => arranger}/arranger_v/v_cursor.rs | 0 .../{tui => arranger}/arranger_v/v_head.rs | 0 .../src/{tui => arranger}/arranger_v/v_io.rs | 0 .../src/{tui => arranger}/arranger_v/v_sep.rs | 0 crates/tek/src/audio.rs | 8 - crates/tek/src/audio/mixer.rs | 37 ---- crates/tek/src/audio/sampler.rs | 152 -------------- .../{tui/app_groovebox.rs => groovebox.rs} | 0 crates/tek/src/lib.rs | 39 +++- crates/tek/src/{tui/app_mixer.rs => mixer.rs} | 185 ++++++++++-------- crates/tek/src/plugin.rs | 137 +++++++++++++ .../src/{tui/app_sampler.rs => sampler.rs} | 154 ++++++++++++++- .../{tui/app_sequencer.rs => sequencer.rs} | 0 .../{tui/app_transport.rs => transport.rs} | 0 crates/tek/src/tui.rs | 35 +--- crates/tek/src/tui/app_plugin.rs | 147 -------------- 29 files changed, 442 insertions(+), 474 deletions(-) rename crates/tek/src/{tui/app_arranger.rs => arranger.rs} (93%) rename crates/tek/src/{tui => arranger}/arranger_command.rs (100%) rename crates/tek/src/{tui => arranger}/arranger_h.rs (100%) rename crates/tek/src/{tui => arranger}/arranger_mode.rs (100%) rename crates/tek/src/{tui => arranger}/arranger_scene.rs (100%) rename crates/tek/src/{tui => arranger}/arranger_select.rs (100%) rename crates/tek/src/{tui => arranger}/arranger_track.rs (100%) rename crates/tek/src/{tui => arranger}/arranger_v.rs (100%) rename crates/tek/src/{tui => arranger}/arranger_v/v_clips.rs (100%) rename crates/tek/src/{tui => arranger}/arranger_v/v_cursor.rs (100%) rename crates/tek/src/{tui => arranger}/arranger_v/v_head.rs (100%) rename crates/tek/src/{tui => arranger}/arranger_v/v_io.rs (100%) rename crates/tek/src/{tui => arranger}/arranger_v/v_sep.rs (100%) delete mode 100644 crates/tek/src/audio/mixer.rs delete mode 100644 crates/tek/src/audio/sampler.rs rename crates/tek/src/{tui/app_groovebox.rs => groovebox.rs} (100%) rename crates/tek/src/{tui/app_mixer.rs => mixer.rs} (63%) rename crates/tek/src/{tui/app_sampler.rs => sampler.rs} (77%) rename crates/tek/src/{tui/app_sequencer.rs => sequencer.rs} (100%) rename crates/tek/src/{tui/app_transport.rs => transport.rs} (100%) delete mode 100644 crates/tek/src/tui/app_plugin.rs diff --git a/crates/cli/src/cli_arranger.rs b/crates/cli/src/cli_arranger.rs index 6c218c8e..5cf780db 100644 --- a/crates/cli/src/cli_arranger.rs +++ b/crates/cli/src/cli_arranger.rs @@ -1,9 +1,6 @@ include!("./lib.rs"); -use tek::tui::ArrangerTui; - -pub fn main () -> Usually<()> { - ArrangerCli::parse().run() -} +use tek::ArrangerTui; +pub fn main () -> Usually<()> { ArrangerCli::parse().run() } /// Launches an interactive MIDI arranger. #[derive(Debug, Parser)] diff --git a/crates/cli/src/cli_groovebox.rs b/crates/cli/src/cli_groovebox.rs index d72778a6..002777c8 100644 --- a/crates/cli/src/cli_groovebox.rs +++ b/crates/cli/src/cli_groovebox.rs @@ -30,7 +30,7 @@ pub struct GrooveboxCli { impl GrooveboxCli { fn run (&self) -> Usually<()> { Tui::run(JackClient::new("tek_groovebox")?.activate_with(|jack|{ - let mut app = tek::tui::GrooveboxTui::try_from(jack)?; + let mut app = tek::GrooveboxTui::try_from(jack)?; let jack = jack.read().unwrap(); let midi_in = jack.register_port("i", MidiIn::default())?; let midi_out = jack.register_port("o", MidiOut::default())?; diff --git a/crates/cli/src/cli_sampler.rs b/crates/cli/src/cli_sampler.rs index 55601c18..ae7f7bd1 100644 --- a/crates/cli/src/cli_sampler.rs +++ b/crates/cli/src/cli_sampler.rs @@ -9,7 +9,7 @@ pub fn main () -> Usually<()> { SamplerCli::parse().run() } impl SamplerCli { fn run (&self) -> Usually<()> { Tui::run(JackClient::new("tek_sampler")?.activate_with(|x|{ - let sampler = tek::tui::SamplerTui::try_from(x)?; + let sampler = tek::SamplerTui::try_from(x)?; Ok(sampler) })?)?; Ok(()) diff --git a/crates/edn/src/lib.rs b/crates/edn/src/lib.rs index a6b51214..347f98a0 100644 --- a/crates/edn/src/lib.rs +++ b/crates/edn/src/lib.rs @@ -1,4 +1,4 @@ -#[allow(unused_imports)] use tek::{*, jack::*, plugin::*, audio::*}; +#[allow(unused_imports)] use tek::{*, jack::*, plugin::*}; use std::sync::{Arc, RwLock}; use std::collections::BTreeMap; diff --git a/crates/tek/src/tui/app_arranger.rs b/crates/tek/src/arranger.rs similarity index 93% rename from crates/tek/src/tui/app_arranger.rs rename to crates/tek/src/arranger.rs index 3a09d730..71478dc1 100644 --- a/crates/tek/src/tui/app_arranger.rs +++ b/crates/tek/src/arranger.rs @@ -1,4 +1,13 @@ use crate::*; + +mod arranger_command; pub(crate) use self::arranger_command::*; +mod arranger_scene; pub(crate) use self::arranger_scene::*; +mod arranger_select; pub(crate) use self::arranger_select::*; +mod arranger_track; pub(crate) use self::arranger_track::*; +mod arranger_mode; pub(crate) use self::arranger_mode::*; +mod arranger_v; #[allow(unused)] pub(crate) use self::arranger_v::*; +mod arranger_h; + /// Root view for standalone `tek_arranger` pub struct ArrangerTui { jack: Arc>, diff --git a/crates/tek/src/tui/arranger_command.rs b/crates/tek/src/arranger/arranger_command.rs similarity index 100% rename from crates/tek/src/tui/arranger_command.rs rename to crates/tek/src/arranger/arranger_command.rs diff --git a/crates/tek/src/tui/arranger_h.rs b/crates/tek/src/arranger/arranger_h.rs similarity index 100% rename from crates/tek/src/tui/arranger_h.rs rename to crates/tek/src/arranger/arranger_h.rs diff --git a/crates/tek/src/tui/arranger_mode.rs b/crates/tek/src/arranger/arranger_mode.rs similarity index 100% rename from crates/tek/src/tui/arranger_mode.rs rename to crates/tek/src/arranger/arranger_mode.rs diff --git a/crates/tek/src/tui/arranger_scene.rs b/crates/tek/src/arranger/arranger_scene.rs similarity index 100% rename from crates/tek/src/tui/arranger_scene.rs rename to crates/tek/src/arranger/arranger_scene.rs diff --git a/crates/tek/src/tui/arranger_select.rs b/crates/tek/src/arranger/arranger_select.rs similarity index 100% rename from crates/tek/src/tui/arranger_select.rs rename to crates/tek/src/arranger/arranger_select.rs diff --git a/crates/tek/src/tui/arranger_track.rs b/crates/tek/src/arranger/arranger_track.rs similarity index 100% rename from crates/tek/src/tui/arranger_track.rs rename to crates/tek/src/arranger/arranger_track.rs diff --git a/crates/tek/src/tui/arranger_v.rs b/crates/tek/src/arranger/arranger_v.rs similarity index 100% rename from crates/tek/src/tui/arranger_v.rs rename to crates/tek/src/arranger/arranger_v.rs diff --git a/crates/tek/src/tui/arranger_v/v_clips.rs b/crates/tek/src/arranger/arranger_v/v_clips.rs similarity index 100% rename from crates/tek/src/tui/arranger_v/v_clips.rs rename to crates/tek/src/arranger/arranger_v/v_clips.rs diff --git a/crates/tek/src/tui/arranger_v/v_cursor.rs b/crates/tek/src/arranger/arranger_v/v_cursor.rs similarity index 100% rename from crates/tek/src/tui/arranger_v/v_cursor.rs rename to crates/tek/src/arranger/arranger_v/v_cursor.rs diff --git a/crates/tek/src/tui/arranger_v/v_head.rs b/crates/tek/src/arranger/arranger_v/v_head.rs similarity index 100% rename from crates/tek/src/tui/arranger_v/v_head.rs rename to crates/tek/src/arranger/arranger_v/v_head.rs diff --git a/crates/tek/src/tui/arranger_v/v_io.rs b/crates/tek/src/arranger/arranger_v/v_io.rs similarity index 100% rename from crates/tek/src/tui/arranger_v/v_io.rs rename to crates/tek/src/arranger/arranger_v/v_io.rs diff --git a/crates/tek/src/tui/arranger_v/v_sep.rs b/crates/tek/src/arranger/arranger_v/v_sep.rs similarity index 100% rename from crates/tek/src/tui/arranger_v/v_sep.rs rename to crates/tek/src/arranger/arranger_v/v_sep.rs diff --git a/crates/tek/src/audio.rs b/crates/tek/src/audio.rs index 3d287f09..c7b7e813 100644 --- a/crates/tek/src/audio.rs +++ b/crates/tek/src/audio.rs @@ -1,9 +1 @@ use crate::*; - -mod sampler; -pub(crate) use self::sampler::*; -pub use self::sampler::{Sampler, Sample, Voice}; - -mod mixer; -pub(crate) use self::mixer::*; -pub use self::mixer::{Mixer, MixerTrack, MixerTrackDevice}; diff --git a/crates/tek/src/audio/mixer.rs b/crates/tek/src/audio/mixer.rs deleted file mode 100644 index 0ec986c9..00000000 --- a/crates/tek/src/audio/mixer.rs +++ /dev/null @@ -1,37 +0,0 @@ -use crate::*; -#[derive(Debug)] -pub struct Mixer { - /// JACK client handle (needs to not be dropped for standalone mode to work). - pub jack: Arc>, - pub name: String, - pub tracks: Vec, - pub selected_track: usize, - pub selected_column: usize, -} -audio!(|self: Mixer, _client, _scope|Control::Continue); - -pub enum MixerTrackCommand {} - -/// A mixer track. -#[derive(Debug)] -pub struct MixerTrack { - pub name: String, - /// Inputs of 1st device - pub audio_ins: Vec>, - /// Outputs of last device - pub audio_outs: Vec>, - /// Device chain - pub devices: Vec>, -} - -//impl MixerTrackDevice for LV2Plugin {} - -pub trait MixerTrackDevice: Debug + Send + Sync { - fn boxed (self) -> Box where Self: Sized + 'static { - Box::new(self) - } -} - -impl MixerTrackDevice for Sampler {} - -impl MixerTrackDevice for Plugin {} diff --git a/crates/tek/src/audio/sampler.rs b/crates/tek/src/audio/sampler.rs deleted file mode 100644 index c9052520..00000000 --- a/crates/tek/src/audio/sampler.rs +++ /dev/null @@ -1,152 +0,0 @@ -use crate::*; - -/// The sampler plugin plays sounds. -#[derive(Debug)] -pub struct Sampler { - pub jack: Arc>, - pub name: String, - pub mapped: BTreeMap>>, - pub unmapped: Vec>>, - pub voices: Arc>>, - pub midi_in: Port, - pub audio_outs: Vec>, - pub buffer: Vec>, - pub output_gain: f32 -} - -/// A sound sample. -#[derive(Default, Debug)] -pub struct Sample { - pub name: String, - pub start: usize, - pub end: usize, - pub channels: Vec>, - pub rate: Option, -} - -/// A currently playing instance of a sample. -#[derive(Default, Debug, Clone)] -pub struct Voice { - pub sample: Arc>, - pub after: usize, - pub position: usize, - pub velocity: f32, -} - -/// Load sample from WAV and assign to MIDI note. -#[macro_export] macro_rules! sample { - ($note:expr, $name:expr, $src:expr) => {{ - let (end, data) = read_sample_data($src)?; - ( - u7::from_int_lossy($note).into(), - Sample::new($name, 0, end, data).into() - ) - }}; -} - -impl Sampler { - - /// Create [Voice]s from [Sample]s in response to MIDI input. - pub fn process_midi_in (&mut self, scope: &ProcessScope) { - let Sampler { midi_in, mapped, voices, .. } = self; - for RawMidi { time, bytes } in midi_in.iter(scope) { - if let LiveEvent::Midi { - message: MidiMessage::NoteOn { ref key, ref vel }, .. - } = LiveEvent::parse(bytes).unwrap() { - if let Some(sample) = mapped.get(key) { - voices.write().unwrap().push(Sample::play(sample, time as usize, vel)); - } - } - } - } - - /// Zero the output buffer. - pub fn clear_output_buffer (&mut self) { - for buffer in self.buffer.iter_mut() { - buffer.fill(0.0); - } - } - - /// Mix all currently playing samples into the output. - pub fn process_audio_out (&mut self, scope: &ProcessScope) { - let Sampler { ref mut buffer, voices, output_gain, .. } = self; - let channel_count = buffer.len(); - voices.write().unwrap().retain_mut(|voice|{ - for index in 0..scope.n_frames() as usize { - if let Some(frame) = voice.next() { - for (channel, sample) in frame.iter().enumerate() { - // Averaging mixer: - //self.buffer[channel % channel_count][index] = ( - //(self.buffer[channel % channel_count][index] + sample * self.output_gain) / 2.0 - //); - buffer[channel % channel_count][index] += sample * *output_gain; - } - } else { - return false - } - } - true - }); - } - - /// Write output buffer to output ports. - pub fn write_output_buffer (&mut self, scope: &ProcessScope) { - let Sampler { ref mut audio_outs, buffer, .. } = self; - for (i, port) in audio_outs.iter_mut().enumerate() { - let buffer = &buffer[i]; - for (i, value) in port.as_mut_slice(scope).iter_mut().enumerate() { - *value = *buffer.get(i).unwrap_or(&0.0); - } - } - } - -} - -impl Sample { - pub fn new (name: &str, start: usize, end: usize, channels: Vec>) -> Self { - Self { name: name.to_string(), start, end, channels, rate: None } - } - pub fn play (sample: &Arc>, after: usize, velocity: &u7) -> Voice { - Voice { - sample: sample.clone(), - after, - position: sample.read().unwrap().start, - velocity: velocity.as_int() as f32 / 127.0, - } - } - /// Read WAV from file - pub fn read_data (src: &str) -> Usually<(usize, Vec>)> { - let mut channels: Vec> = vec![]; - for channel in wavers::Wav::from_path(src)?.channels() { - channels.push(channel); - } - let mut end = 0; - let mut data: Vec> = vec![]; - for samples in channels.iter() { - let channel = Vec::from(samples.as_ref()); - end = end.max(channel.len()); - data.push(channel); - } - Ok((end, data)) - } -} - -impl Iterator for Voice { - type Item = [f32;2]; - fn next (&mut self) -> Option { - if self.after > 0 { - self.after -= 1; - return Some([0.0, 0.0]) - } - let sample = self.sample.read().unwrap(); - if self.position < sample.end { - let position = self.position; - self.position += 1; - return sample.channels[0].get(position).map(|_amplitude|[ - sample.channels[0][position] * self.velocity, - sample.channels[0][position] * self.velocity, - ]) - } - None - } -} diff --git a/crates/tek/src/tui/app_groovebox.rs b/crates/tek/src/groovebox.rs similarity index 100% rename from crates/tek/src/tui/app_groovebox.rs rename to crates/tek/src/groovebox.rs diff --git a/crates/tek/src/lib.rs b/crates/tek/src/lib.rs index 67cf5166..2518e788 100644 --- a/crates/tek/src/lib.rs +++ b/crates/tek/src/lib.rs @@ -7,15 +7,44 @@ pub mod time; pub(crate) use self::time::*; pub mod space; pub(crate) use self::space::*; -pub mod tui; pub(crate) use self::tui::*; pub use tui::*; +pub mod tui; +pub(crate) use self::tui::*; +pub use tui::*; -pub mod jack; pub(crate) use self::jack::*; pub use self::jack::*; +pub mod jack; +pub(crate) use self::jack::*; +pub use self::jack::*; -pub mod midi; pub(crate) use self::midi::*; +pub mod midi; +pub(crate) use self::midi::*; -pub mod audio; pub(crate) use self::audio::*; pub use self::audio::*; +pub mod transport; +pub(crate) use self::transport::*; +pub use self::transport::TransportTui; -pub mod plugin; pub(crate) use self::plugin::*; pub use self::plugin::*; +pub mod sequencer; +pub(crate) use self::sequencer::*; +pub use self::sequencer::SequencerTui; + +pub mod arranger; +pub(crate) use self::arranger::*; +pub use self::arranger::ArrangerTui; + +mod sampler; +pub(crate) use self::sampler::*; +pub use self::sampler::{SamplerTui, Sampler, Sample, Voice}; + +mod mixer; +pub(crate) use self::mixer::*; +pub use self::mixer::{Mixer, MixerTrack, MixerTrackDevice}; + +pub mod plugin; +pub(crate) use self::plugin::*; +pub use self::plugin::*; + +pub mod groovebox; +pub(crate) use self::groovebox::*; +pub use self::groovebox::GrooveboxTui; pub use ::better_panic; pub(crate) use better_panic::{Settings, Verbosity}; diff --git a/crates/tek/src/tui/app_mixer.rs b/crates/tek/src/mixer.rs similarity index 63% rename from crates/tek/src/tui/app_mixer.rs rename to crates/tek/src/mixer.rs index 111d5cb2..71aba47f 100644 --- a/crates/tek/src/tui/app_mixer.rs +++ b/crates/tek/src/mixer.rs @@ -1,14 +1,30 @@ use crate::*; -pub struct Mixer { +#[derive(Debug)] +pub struct Mixer { /// JACK client handle (needs to not be dropped for standalone mode to work). pub jack: Arc>, pub name: String, - pub tracks: Vec>, + pub tracks: Vec, pub selected_track: usize, pub selected_column: usize, } -impl Mixer { + +/// A mixer track. +#[derive(Debug)] +pub struct MixerTrack { + pub name: String, + /// Inputs of 1st device + pub audio_ins: Vec>, + /// Outputs of last device + pub audio_outs: Vec>, + /// Device chain + pub devices: Vec>, +} + +audio!(|self: Mixer, _client, _scope|Control::Continue); + +impl Mixer { pub fn new (jack: &Arc>, name: &str) -> Usually { Ok(Self { jack: jack.clone(), @@ -19,57 +35,42 @@ impl Mixer { }) } pub fn track_add (&mut self, name: &str, channels: usize) -> Usually<&mut Self> { - let track = Track::new(name)?; + let track = MixerTrack::new(name)?; self.tracks.push(track); Ok(self) } - pub fn track (&self) -> Option<&Track> { + pub fn track (&self) -> Option<&MixerTrack> { self.tracks.get(self.selected_track) } } -//pub const ACTIONS: [(&'static str, &'static str);2] = [ - //("+/-", "Adjust"), - //("Ins/Del", "Add/remove track"), -//]; - - -/// A sequencer track. -#[derive(Debug)] -pub struct Track { - pub name: String, - /// Inputs and outputs of 1st and last device - pub ports: JackPorts, - /// Device chain - pub devices: Vec>, - /// Device selector - pub device: usize, -} - -impl Track { +impl MixerTrack { pub fn new (name: &str) -> Usually { Ok(Self { - name: name.to_string(), - ports: JackPorts::default(), - devices: vec![], - device: 0, + name: name.to_string(), + audio_ins: vec![], + audio_outs: vec![], + devices: vec![], + //ports: JackPorts::default(), + //devices: vec![], + //device: 0, }) } - fn get_device_mut (&self, i: usize) -> Option>>> { - self.devices.get(i).map(|d|d.state.write().unwrap()) - } - pub fn device_mut (&self) -> Option>>> { - self.get_device_mut(self.device) - } - /// Add a device to the end of the chain. - pub fn append_device (&mut self, device: JackDevice) -> Usually<&mut JackDevice> { - self.devices.push(device); - let index = self.devices.len() - 1; - Ok(&mut self.devices[index]) - } - pub fn add_device (&mut self, device: JackDevice) { - self.devices.push(device); - } + //fn get_device_mut (&self, i: usize) -> Option>>> { + //self.devices.get(i).map(|d|d.state.write().unwrap()) + //} + //pub fn device_mut (&self) -> Option>>> { + //self.get_device_mut(self.device) + //} + ///// Add a device to the end of the chain. + //pub fn append_device (&mut self, device: JackDevice) -> Usually<&mut JackDevice> { + //self.devices.push(device); + //let index = self.devices.len() - 1; + //Ok(&mut self.devices[index]) + //} + //pub fn add_device (&mut self, device: JackDevice) { + //self.devices.push(device); + //} //pub fn connect_first_device (&self) -> Usually<()> { //if let (Some(port), Some(device)) = (&self.midi_out, self.devices.get(0)) { //device.client.as_client().connect_ports(&port, &device.midi_ins()?[0])?; @@ -88,14 +89,14 @@ impl Track { //} } -pub struct TrackView<'a, E: Engine> { - pub chain: Option<&'a Track>, +pub struct TrackView<'a> { + pub chain: Option<&'a MixerTrack>, pub direction: Direction, pub focused: bool, pub entered: bool, } -impl<'a> Render for TrackView<'a, Tui> { +impl<'a> Render for TrackView<'a> { fn min_size (&self, area: [u16;2]) -> Perhaps<[u16;2]> { todo!() } @@ -129,40 +130,40 @@ impl<'a> Render for TrackView<'a, Tui> { } } -impl Content for Mixer { - fn content (&self) -> impl Render { - Stack::right(|add| { - for channel in self.tracks.iter() { - add(channel)?; - } - Ok(()) - }) - } -} +//impl Content for Mixer { + //fn content (&self) -> impl Render { + //Stack::right(|add| { + //for channel in self.tracks.iter() { + //add(channel)?; + //} + //Ok(()) + //}) + //} +//} -impl Content for Track { - fn content (&self) -> impl Render { - TrackView { - chain: Some(&self), - direction: tek_core::Direction::Right, - focused: true, - entered: true, - //pub channels: u8, - //pub input_ports: Vec>, - //pub pre_gain_meter: f64, - //pub gain: f64, - //pub insert_ports: Vec>, - //pub return_ports: Vec>, - //pub post_gain_meter: f64, - //pub post_insert_meter: f64, - //pub level: f64, - //pub pan: f64, - //pub output_ports: Vec>, - //pub post_fader_meter: f64, - //pub route: String, - } - } -} +//impl Content for Track { + //fn content (&self) -> impl Render { + //TrackView { + //chain: Some(&self), + //direction: tek_core::Direction::Right, + //focused: true, + //entered: true, + ////pub channels: u8, + ////pub input_ports: Vec>, + ////pub pre_gain_meter: f64, + ////pub gain: f64, + ////pub insert_ports: Vec>, + ////pub return_ports: Vec>, + ////pub post_gain_meter: f64, + ////pub post_insert_meter: f64, + ////pub level: f64, + ////pub pan: f64, + ////pub output_ports: Vec>, + ////pub post_fader_meter: f64, + ////pub route: String, + //} + //} +//} handle!(|self:Mixer,engine|{ if let TuiEvent::Input(crossterm::event::Event::Key(event)) = engine.event() { @@ -212,18 +213,18 @@ handle!(|self:Mixer,engine|{ Ok(None) }); -handle!(|self:Track,from|{ +handle!(|self:MixerTrack,from|{ match from.event() { //, NONE, "chain_cursor_up", "move cursor up", || { - key!(KeyCode::Up) => { + key_pat!(KeyCode::Up) => { Ok(Some(true)) }, // , NONE, "chain_cursor_down", "move cursor down", || { - key!(KeyCode::Down) => { + key_pat!(KeyCode::Down) => { Ok(Some(true)) }, // Left, NONE, "chain_cursor_left", "move cursor left", || { - key!(KeyCode::Left) => { + key_pat!(KeyCode::Left) => { //if let Some(track) = app.arranger.track_mut() { //track.device = track.device.saturating_sub(1); //return Ok(true) @@ -231,7 +232,7 @@ handle!(|self:Track,from|{ Ok(Some(true)) }, // , NONE, "chain_cursor_right", "move cursor right", || { - key!(KeyCode::Right) => { + key_pat!(KeyCode::Right) => { //if let Some(track) = app.arranger.track_mut() { //track.device = (track.device + 1).min(track.devices.len().saturating_sub(1)); //return Ok(true) @@ -239,10 +240,24 @@ handle!(|self:Track,from|{ Ok(Some(true)) }, // , NONE, "chain_mode_switch", "switch the display mode", || { - key!(KeyCode::Char('`')) => { + key_pat!(KeyCode::Char('`')) => { //app.chain_mode = !app.chain_mode; Ok(Some(true)) }, _ => Ok(None) } }); + +pub enum MixerTrackCommand {} + +//impl MixerTrackDevice for LV2Plugin {} + +pub trait MixerTrackDevice: Debug + Send + Sync { + fn boxed (self) -> Box where Self: Sized + 'static { + Box::new(self) + } +} + +impl MixerTrackDevice for Sampler {} + +impl MixerTrackDevice for Plugin {} diff --git a/crates/tek/src/plugin.rs b/crates/tek/src/plugin.rs index b728bfd1..63595c17 100644 --- a/crates/tek/src/plugin.rs +++ b/crates/tek/src/plugin.rs @@ -127,3 +127,140 @@ audio!(|self: PluginAudio, client, scope|{ //} //Ok(jack) //} + +impl Plugin { + /// Create a plugin host device. + pub fn new ( + jack: &Arc>, + name: &str, + ) -> Usually { + Ok(Self { + //_engine: Default::default(), + jack: jack.clone(), + name: name.into(), + path: None, + plugin: None, + selected: 0, + mapping: false, + audio_ins: vec![], + audio_outs: vec![], + midi_ins: vec![], + midi_outs: vec![], + //ports: JackPorts::default() + }) + } +} +impl Render for Plugin { + fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> { + Ok(Some(to)) + } + fn render (&self, to: &mut TuiOutput) -> Usually<()> { + let area = to.area(); + let [x, y, _, height] = area; + let mut width = 20u16; + match &self.plugin { + Some(PluginKind::LV2(LV2Plugin { port_list, instance, .. })) => { + let start = self.selected.saturating_sub((height as usize / 2).saturating_sub(1)); + let end = start + height as usize - 2; + //draw_box(buf, Rect { x, y, width, height }); + for i in start..end { + if let Some(port) = port_list.get(i) { + let value = if let Some(value) = instance.control_input(port.index) { + value + } else { + port.default_value + }; + //let label = &format!("C·· M·· {:25} = {value:.03}", port.name); + let label = &format!("{:25} = {value:.03}", port.name); + width = width.max(label.len() as u16 + 4); + let style = if i == self.selected { + Some(Style::default().green()) + } else { + None + } ; + to.blit(&label, x + 2, y + 1 + i as u16 - start as u16, style); + } else { + break + } + } + }, + _ => {} + }; + draw_header(self, to, x, y, width)?; + Ok(()) + } +} + +fn draw_header (state: &Plugin, to: &mut TuiOutput, x: u16, y: u16, w: u16) -> Usually<()> { + let style = Style::default().gray(); + let label1 = format!(" {}", state.name); + to.blit(&label1, x + 1, y, Some(style.white().bold())); + if let Some(ref path) = state.path { + let label2 = format!("{}…", &path[..((w as usize - 10).min(path.len()))]); + to.blit(&label2, x + 2 + label1.len() as u16, y, Some(style.not_dim())); + } + Ok(()) + //Ok(Rect { x, y, width: w, height: 1 }) +} + +handle!(|self:Plugin, from|{ + match from.event() { + key_pat!(KeyCode::Up) => { + self.selected = self.selected.saturating_sub(1); + Ok(Some(true)) + }, + key_pat!(KeyCode::Down) => { + self.selected = (self.selected + 1).min(match &self.plugin { + Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1, + _ => unimplemented!() + }); + Ok(Some(true)) + }, + key_pat!(KeyCode::PageUp) => { + self.selected = self.selected.saturating_sub(8); + Ok(Some(true)) + }, + key_pat!(KeyCode::PageDown) => { + self.selected = (self.selected + 10).min(match &self.plugin { + Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1, + _ => unimplemented!() + }); + Ok(Some(true)) + }, + key_pat!(KeyCode::Char(',')) => { + match self.plugin.as_mut() { + Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => { + let index = port_list[self.selected].index; + if let Some(value) = instance.control_input(index) { + instance.set_control_input(index, value - 0.01); + } + }, + _ => {} + } + Ok(Some(true)) + }, + key_pat!(KeyCode::Char('.')) => { + match self.plugin.as_mut() { + Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => { + let index = port_list[self.selected].index; + if let Some(value) = instance.control_input(index) { + instance.set_control_input(index, value + 0.01); + } + }, + _ => {} + } + Ok(Some(true)) + }, + key_pat!(KeyCode::Char('g')) => { + match self.plugin { + //Some(PluginKind::LV2(ref mut plugin)) => { + //plugin.ui_thread = Some(run_lv2_ui(LV2PluginUI::new()?)?); + //}, + Some(_) => unreachable!(), + None => {} + } + Ok(Some(true)) + }, + _ => Ok(None) + } +}); diff --git a/crates/tek/src/tui/app_sampler.rs b/crates/tek/src/sampler.rs similarity index 77% rename from crates/tek/src/tui/app_sampler.rs rename to crates/tek/src/sampler.rs index 60ca1ee9..ba68e5db 100644 --- a/crates/tek/src/tui/app_sampler.rs +++ b/crates/tek/src/sampler.rs @@ -1,5 +1,155 @@ -use crate::*; -use super::{*, piano_h::PianoHorizontalKeys}; +use crate::{*, tui::piano_h::PianoHorizontalKeys}; + +/// The sampler plugin plays sounds. +#[derive(Debug)] +pub struct Sampler { + pub jack: Arc>, + pub name: String, + pub mapped: BTreeMap>>, + pub unmapped: Vec>>, + pub voices: Arc>>, + pub midi_in: Port, + pub audio_outs: Vec>, + pub buffer: Vec>, + pub output_gain: f32 +} + +/// A sound sample. +#[derive(Default, Debug)] +pub struct Sample { + pub name: String, + pub start: usize, + pub end: usize, + pub channels: Vec>, + pub rate: Option, +} + +/// A currently playing instance of a sample. +#[derive(Default, Debug, Clone)] +pub struct Voice { + pub sample: Arc>, + pub after: usize, + pub position: usize, + pub velocity: f32, +} + +/// Load sample from WAV and assign to MIDI note. +#[macro_export] macro_rules! sample { + ($note:expr, $name:expr, $src:expr) => {{ + let (end, data) = read_sample_data($src)?; + ( + u7::from_int_lossy($note).into(), + Sample::new($name, 0, end, data).into() + ) + }}; +} + +impl Sampler { + + /// Create [Voice]s from [Sample]s in response to MIDI input. + pub fn process_midi_in (&mut self, scope: &ProcessScope) { + let Sampler { midi_in, mapped, voices, .. } = self; + for RawMidi { time, bytes } in midi_in.iter(scope) { + if let LiveEvent::Midi { + message: MidiMessage::NoteOn { ref key, ref vel }, .. + } = LiveEvent::parse(bytes).unwrap() { + if let Some(sample) = mapped.get(key) { + voices.write().unwrap().push(Sample::play(sample, time as usize, vel)); + } + } + } + } + + /// Zero the output buffer. + pub fn clear_output_buffer (&mut self) { + for buffer in self.buffer.iter_mut() { + buffer.fill(0.0); + } + } + + /// Mix all currently playing samples into the output. + pub fn process_audio_out (&mut self, scope: &ProcessScope) { + let Sampler { ref mut buffer, voices, output_gain, .. } = self; + let channel_count = buffer.len(); + voices.write().unwrap().retain_mut(|voice|{ + for index in 0..scope.n_frames() as usize { + if let Some(frame) = voice.next() { + for (channel, sample) in frame.iter().enumerate() { + // Averaging mixer: + //self.buffer[channel % channel_count][index] = ( + //(self.buffer[channel % channel_count][index] + sample * self.output_gain) / 2.0 + //); + buffer[channel % channel_count][index] += sample * *output_gain; + } + } else { + return false + } + } + true + }); + } + + /// Write output buffer to output ports. + pub fn write_output_buffer (&mut self, scope: &ProcessScope) { + let Sampler { ref mut audio_outs, buffer, .. } = self; + for (i, port) in audio_outs.iter_mut().enumerate() { + let buffer = &buffer[i]; + for (i, value) in port.as_mut_slice(scope).iter_mut().enumerate() { + *value = *buffer.get(i).unwrap_or(&0.0); + } + } + } + +} + +impl Sample { + pub fn new (name: &str, start: usize, end: usize, channels: Vec>) -> Self { + Self { name: name.to_string(), start, end, channels, rate: None } + } + pub fn play (sample: &Arc>, after: usize, velocity: &u7) -> Voice { + Voice { + sample: sample.clone(), + after, + position: sample.read().unwrap().start, + velocity: velocity.as_int() as f32 / 127.0, + } + } + /// Read WAV from file + pub fn read_data (src: &str) -> Usually<(usize, Vec>)> { + let mut channels: Vec> = vec![]; + for channel in wavers::Wav::from_path(src)?.channels() { + channels.push(channel); + } + let mut end = 0; + let mut data: Vec> = vec![]; + for samples in channels.iter() { + let channel = Vec::from(samples.as_ref()); + end = end.max(channel.len()); + data.push(channel); + } + Ok((end, data)) + } +} + +impl Iterator for Voice { + type Item = [f32;2]; + fn next (&mut self) -> Option { + if self.after > 0 { + self.after -= 1; + return Some([0.0, 0.0]) + } + let sample = self.sample.read().unwrap(); + if self.position < sample.end { + let position = self.position; + self.position += 1; + return sample.channels[0].get(position).map(|_amplitude|[ + sample.channels[0][position] * self.velocity, + sample.channels[0][position] * self.velocity, + ]) + } + None + } +} use KeyCode::Char; use std::fs::File; diff --git a/crates/tek/src/tui/app_sequencer.rs b/crates/tek/src/sequencer.rs similarity index 100% rename from crates/tek/src/tui/app_sequencer.rs rename to crates/tek/src/sequencer.rs diff --git a/crates/tek/src/tui/app_transport.rs b/crates/tek/src/transport.rs similarity index 100% rename from crates/tek/src/tui/app_transport.rs rename to crates/tek/src/transport.rs diff --git a/crates/tek/src/tui.rs b/crates/tek/src/tui.rs index 35c167ad..4b703916 100644 --- a/crates/tek/src/tui.rs +++ b/crates/tek/src/tui.rs @@ -12,40 +12,15 @@ mod tui_style; mod tui_theme; pub(crate) use self::tui_theme::*; mod tui_border; pub(crate) use self::tui_border::*; -//////////////////////////////////////////////////////// - -mod app_transport; #[allow(unused)] pub(crate) use self::app_transport::*; -pub use self::app_transport::TransportTui; - -mod app_sequencer; #[allow(unused)] pub(crate) use self::app_sequencer::*; -pub use self::app_sequencer::SequencerTui; - -mod app_sampler; #[allow(unused)] pub(crate) use self::app_sampler::*; -pub use self::app_sampler::SamplerTui; - -mod app_groovebox; #[allow(unused)] pub(crate) use self::app_groovebox::*; -pub use self::app_groovebox::GrooveboxTui; - -mod app_arranger; #[allow(unused)] pub(crate) use self::app_arranger::*; -pub use self::app_arranger::ArrangerTui; - /////////////////////////////////////////////////////// -mod arranger_command; pub(crate) use self::arranger_command::*; -mod arranger_scene; pub(crate) use self::arranger_scene::*; -mod arranger_select; pub(crate) use self::arranger_select::*; -mod arranger_track; pub(crate) use self::arranger_track::*; -mod arranger_mode; pub(crate) use self::arranger_mode::*; -mod arranger_v; #[allow(unused)] pub(crate) use self::arranger_v::*; -mod arranger_h; - //////////////////////////////////////////////////////// -mod pool; pub(crate) use self::pool::*; -mod phrase_editor; pub(crate) use self::phrase_editor::*; -mod status; pub(crate) use self::status::*; -mod file_browser; pub(crate) use self::file_browser::*; -mod piano_h; pub(crate) use self::piano_h::*; +pub mod pool; pub(crate) use self::pool::*; +pub mod phrase_editor; pub(crate) use self::phrase_editor::*; +pub mod status; pub(crate) use self::status::*; +pub mod file_browser; pub(crate) use self::file_browser::*; +pub mod piano_h; pub(crate) use self::piano_h::*; //////////////////////////////////////////////////////// diff --git a/crates/tek/src/tui/app_plugin.rs b/crates/tek/src/tui/app_plugin.rs deleted file mode 100644 index 8f4eddfd..00000000 --- a/crates/tek/src/tui/app_plugin.rs +++ /dev/null @@ -1,147 +0,0 @@ -use crate::*; - -/// A plugin device. -pub struct Plugin { - _engine: PhantomData, - /// JACK client handle (needs to not be dropped for standalone mode to work). - pub jack: Arc>, - pub name: String, - pub path: Option, - pub plugin: Option, - pub selected: usize, - pub mapping: bool, - pub ports: JackPorts, -} - -impl Plugin { - /// Create a plugin host device. - pub fn new ( - jack: &Arc>, - name: &str, - ) -> Usually { - Ok(Self { - _engine: Default::default(), - jack: jack.clone(), - name: name.into(), - path: None, - plugin: None, - selected: 0, - mapping: false, - ports: JackPorts::default() - }) - } -} -impl Render for Plugin { - fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> { - Ok(Some(to)) - } - fn render (&self, to: &mut TuiOutput) -> Usually<()> { - let area = to.area(); - let [x, y, _, height] = area; - let mut width = 20u16; - match &self.plugin { - Some(PluginKind::LV2(LV2Plugin { port_list, instance, .. })) => { - let start = self.selected.saturating_sub((height as usize / 2).saturating_sub(1)); - let end = start + height as usize - 2; - //draw_box(buf, Rect { x, y, width, height }); - for i in start..end { - if let Some(port) = port_list.get(i) { - let value = if let Some(value) = instance.control_input(port.index) { - value - } else { - port.default_value - }; - //let label = &format!("C·· M·· {:25} = {value:.03}", port.name); - let label = &format!("{:25} = {value:.03}", port.name); - width = width.max(label.len() as u16 + 4); - let style = if i == self.selected { - Some(Style::default().green()) - } else { - None - } ; - to.blit(&label, x + 2, y + 1 + i as u16 - start as u16, style); - } else { - break - } - } - }, - _ => {} - }; - draw_header(self, to, x, y, width)?; - Ok(()) - } -} - -fn draw_header (state: &Plugin, to: &mut TuiOutput, x: u16, y: u16, w: u16) -> Usually { - let style = Style::default().gray(); - let label1 = format!(" {}", state.name); - to.blit(&label1, x + 1, y, Some(style.white().bold())); - if let Some(ref path) = state.path { - let label2 = format!("{}…", &path[..((w as usize - 10).min(path.len()))]); - to.blit(&label2, x + 2 + label1.len() as u16, y, Some(style.not_dim())); - } - Ok(Rect { x, y, width: w, height: 1 }) -} - -handle!(|self:Plugin,from|{ - match from.event() { - key!(KeyCode::Up) => { - self.selected = self.selected.saturating_sub(1); - Ok(Some(true)) - }, - key!(KeyCode::Down) => { - self.selected = (self.selected + 1).min(match &self.plugin { - Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1, - _ => unimplemented!() - }); - Ok(Some(true)) - }, - key!(KeyCode::PageUp) => { - self.selected = self.selected.saturating_sub(8); - Ok(Some(true)) - }, - key!(KeyCode::PageDown) => { - self.selected = (self.selected + 10).min(match &self.plugin { - Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1, - _ => unimplemented!() - }); - Ok(Some(true)) - }, - key!(KeyCode::Char(',')) => { - match self.plugin.as_mut() { - Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => { - let index = port_list[self.selected].index; - if let Some(value) = instance.control_input(index) { - instance.set_control_input(index, value - 0.01); - } - }, - _ => {} - } - Ok(Some(true)) - }, - key!(KeyCode::Char('.')) => { - match self.plugin.as_mut() { - Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => { - let index = port_list[self.selected].index; - if let Some(value) = instance.control_input(index) { - instance.set_control_input(index, value + 0.01); - } - }, - _ => {} - } - Ok(Some(true)) - }, - key!(KeyCode::Char('g')) => { - match self.plugin { - Some(PluginKind::LV2(ref mut plugin)) => { - plugin.ui_thread = Some(run_lv2_ui(LV2PluginUI::new()?)?); - }, - Some(_) => unreachable!(), - None => {} - } - Ok(Some(true)) - }, - _ => Ok(None) - } -}); -} From ba56c1909d5fc9e44ded659b05903d462f15e23c Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 27 Dec 2024 21:29:28 +0100 Subject: [PATCH 034/815] flatten modules a little more --- crates/tek/src/lib.rs | 35 ++++++++----------- crates/tek/src/midi.rs | 2 ++ .../phrase_editor.rs => midi/midi_editor.rs} | 0 crates/tek/src/{tui => }/pool.rs | 0 .../tek/src/{tui => }/pool/phrase_length.rs | 0 .../tek/src/{tui => }/pool/phrase_rename.rs | 0 crates/tek/src/{tui => }/status.rs | 0 .../src/{tui => }/status/status_arranger.rs | 0 .../tek/src/{tui => }/status/status_edit.rs | 0 .../src/{tui => }/status/status_groovebox.rs | 0 .../src/{tui => }/status/status_sequencer.rs | 0 crates/tek/src/tui.rs | 9 ++--- 12 files changed, 19 insertions(+), 27 deletions(-) rename crates/tek/src/{tui/phrase_editor.rs => midi/midi_editor.rs} (100%) rename crates/tek/src/{tui => }/pool.rs (100%) rename crates/tek/src/{tui => }/pool/phrase_length.rs (100%) rename crates/tek/src/{tui => }/pool/phrase_rename.rs (100%) rename crates/tek/src/{tui => }/status.rs (100%) rename crates/tek/src/{tui => }/status/status_arranger.rs (100%) rename crates/tek/src/{tui => }/status/status_edit.rs (100%) rename crates/tek/src/{tui => }/status/status_groovebox.rs (100%) rename crates/tek/src/{tui => }/status/status_sequencer.rs (100%) diff --git a/crates/tek/src/lib.rs b/crates/tek/src/lib.rs index 2518e788..be8099fb 100644 --- a/crates/tek/src/lib.rs +++ b/crates/tek/src/lib.rs @@ -7,45 +7,40 @@ pub mod time; pub(crate) use self::time::*; pub mod space; pub(crate) use self::space::*; -pub mod tui; -pub(crate) use self::tui::*; +pub mod tui; pub(crate) use self::tui::*; pub use tui::*; -pub mod jack; -pub(crate) use self::jack::*; +pub mod jack; pub(crate) use self::jack::*; pub use self::jack::*; -pub mod midi; -pub(crate) use self::midi::*; +pub mod midi; pub(crate) use self::midi::*; -pub mod transport; -pub(crate) use self::transport::*; +pub mod transport; pub(crate) use self::transport::*; pub use self::transport::TransportTui; -pub mod sequencer; -pub(crate) use self::sequencer::*; +pub mod sequencer; pub(crate) use self::sequencer::*; pub use self::sequencer::SequencerTui; -pub mod arranger; -pub(crate) use self::arranger::*; +pub mod arranger; pub(crate) use self::arranger::*; pub use self::arranger::ArrangerTui; -mod sampler; -pub(crate) use self::sampler::*; +pub mod sampler; pub(crate) use self::sampler::*; pub use self::sampler::{SamplerTui, Sampler, Sample, Voice}; -mod mixer; -pub(crate) use self::mixer::*; +pub mod mixer; pub(crate) use self::mixer::*; pub use self::mixer::{Mixer, MixerTrack, MixerTrackDevice}; -pub mod plugin; -pub(crate) use self::plugin::*; +pub mod plugin; pub(crate) use self::plugin::*; pub use self::plugin::*; -pub mod groovebox; -pub(crate) use self::groovebox::*; +pub mod groovebox; pub(crate) use self::groovebox::*; pub use self::groovebox::GrooveboxTui; +pub mod pool; pub(crate) use self::pool::*; +pub use self::pool::PoolModel; + +pub mod status; pub(crate) use self::status::*; + pub use ::better_panic; pub(crate) use better_panic::{Settings, Verbosity}; pub use ::atomic_float; pub(crate) use atomic_float::*; diff --git a/crates/tek/src/midi.rs b/crates/tek/src/midi.rs index 7e97a594..c242ed61 100644 --- a/crates/tek/src/midi.rs +++ b/crates/tek/src/midi.rs @@ -11,6 +11,8 @@ pub(crate) mod midi_range; pub(crate) use midi_range::*; pub(crate) mod midi_point; pub(crate) use midi_point::*; pub(crate) mod midi_view; pub(crate) use midi_view::*; +pub(crate) mod midi_editor; pub(crate) use midi_editor::*; + /// Trait for thing that may receive MIDI. pub trait HasMidiIns { fn midi_ins (&self) -> &Vec>; diff --git a/crates/tek/src/tui/phrase_editor.rs b/crates/tek/src/midi/midi_editor.rs similarity index 100% rename from crates/tek/src/tui/phrase_editor.rs rename to crates/tek/src/midi/midi_editor.rs diff --git a/crates/tek/src/tui/pool.rs b/crates/tek/src/pool.rs similarity index 100% rename from crates/tek/src/tui/pool.rs rename to crates/tek/src/pool.rs diff --git a/crates/tek/src/tui/pool/phrase_length.rs b/crates/tek/src/pool/phrase_length.rs similarity index 100% rename from crates/tek/src/tui/pool/phrase_length.rs rename to crates/tek/src/pool/phrase_length.rs diff --git a/crates/tek/src/tui/pool/phrase_rename.rs b/crates/tek/src/pool/phrase_rename.rs similarity index 100% rename from crates/tek/src/tui/pool/phrase_rename.rs rename to crates/tek/src/pool/phrase_rename.rs diff --git a/crates/tek/src/tui/status.rs b/crates/tek/src/status.rs similarity index 100% rename from crates/tek/src/tui/status.rs rename to crates/tek/src/status.rs diff --git a/crates/tek/src/tui/status/status_arranger.rs b/crates/tek/src/status/status_arranger.rs similarity index 100% rename from crates/tek/src/tui/status/status_arranger.rs rename to crates/tek/src/status/status_arranger.rs diff --git a/crates/tek/src/tui/status/status_edit.rs b/crates/tek/src/status/status_edit.rs similarity index 100% rename from crates/tek/src/tui/status/status_edit.rs rename to crates/tek/src/status/status_edit.rs diff --git a/crates/tek/src/tui/status/status_groovebox.rs b/crates/tek/src/status/status_groovebox.rs similarity index 100% rename from crates/tek/src/tui/status/status_groovebox.rs rename to crates/tek/src/status/status_groovebox.rs diff --git a/crates/tek/src/tui/status/status_sequencer.rs b/crates/tek/src/status/status_sequencer.rs similarity index 100% rename from crates/tek/src/tui/status/status_sequencer.rs rename to crates/tek/src/status/status_sequencer.rs diff --git a/crates/tek/src/tui.rs b/crates/tek/src/tui.rs index 4b703916..726f6ce7 100644 --- a/crates/tek/src/tui.rs +++ b/crates/tek/src/tui.rs @@ -14,13 +14,8 @@ mod tui_border; pub(crate) use self::tui_border::*; /////////////////////////////////////////////////////// -//////////////////////////////////////////////////////// - -pub mod pool; pub(crate) use self::pool::*; -pub mod phrase_editor; pub(crate) use self::phrase_editor::*; -pub mod status; pub(crate) use self::status::*; -pub mod file_browser; pub(crate) use self::file_browser::*; -pub mod piano_h; pub(crate) use self::piano_h::*; +pub mod file_browser; pub(crate) use self::file_browser::*; +pub mod piano_h; pub(crate) use self::piano_h::*; //////////////////////////////////////////////////////// From 7e02a46beb9ac62ed3b9dfeae445d68db773f640 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 27 Dec 2024 22:10:21 +0100 Subject: [PATCH 035/815] more stats in transport --- crates/tek/src/arranger.rs | 28 ++++++++++++++-------------- crates/tek/src/time/perf.rs | 4 ++++ crates/tek/src/transport.rs | 36 ++++++++++++++++++++++++------------ 3 files changed, 42 insertions(+), 26 deletions(-) diff --git a/crates/tek/src/arranger.rs b/crates/tek/src/arranger.rs index 71478dc1..9aea0de3 100644 --- a/crates/tek/src/arranger.rs +++ b/crates/tek/src/arranger.rs @@ -10,20 +10,20 @@ mod arranger_h; /// Root view for standalone `tek_arranger` pub struct ArrangerTui { - jack: Arc>, - pub clock: ClockModel, - pub phrases: PoolModel, - pub tracks: Vec, - pub scenes: Vec, - pub splits: [u16;2], - pub selected: ArrangerSelection, - pub mode: ArrangerMode, - pub color: ItemPalette, - pub size: Measure, - pub note_buf: Vec, - pub midi_buf: Vec>>, - pub editor: MidiEditorModel, - pub perf: PerfModel, + jack: Arc>, + pub clock: ClockModel, + pub phrases: PoolModel, + pub tracks: Vec, + pub scenes: Vec, + pub splits: [u16;2], + pub selected: ArrangerSelection, + pub mode: ArrangerMode, + pub color: ItemPalette, + pub size: Measure, + pub note_buf: Vec, + pub midi_buf: Vec>>, + pub editor: MidiEditorModel, + pub perf: PerfModel, } impl ArrangerTui { pub fn selected (&self) -> ArrangerSelection { diff --git a/crates/tek/src/time/perf.rs b/crates/tek/src/time/perf.rs index abe1e593..7d659867 100644 --- a/crates/tek/src/time/perf.rs +++ b/crates/tek/src/time/perf.rs @@ -10,6 +10,10 @@ pub struct PerfModel { period: AtomicF64, } +pub trait HasPerf { + fn perf (&self) -> &PerfModel; +} + impl Default for PerfModel { fn default () -> Self { Self { diff --git a/crates/tek/src/transport.rs b/crates/tek/src/transport.rs index 92e8eb80..980d091a 100644 --- a/crates/tek/src/transport.rs +++ b/crates/tek/src/transport.rs @@ -35,6 +35,8 @@ pub struct TransportView { color: ItemPalette, focused: bool, sr: String, + chunk: String, + latency: String, bpm: String, ppq: String, beat: String, @@ -46,17 +48,22 @@ pub struct TransportView { } impl From<(&T, Option, bool)> for TransportView { fn from ((state, color, focused): (&T, Option, bool)) -> Self { - let clock = state.clock(); - let sr = format!("{:.1}k", clock.timebase.sr.get() / 1000.0); - let bpm = format!("{:.3}", clock.timebase.bpm.get()); - let ppq = format!("{:.0}", clock.timebase.ppq.get()); - let color = color.unwrap_or(ItemPalette::from(TuiTheme::g(32))); + let clock = state.clock(); + let rate = clock.timebase.sr.get(); + let chunk = clock.chunk.load(Relaxed); + let latency = chunk as f64 / rate * 1000.; + let sr = format!("{:.1}k", rate / 1000.0); + let bpm = format!("{:.3}", clock.timebase.bpm.get()); + let ppq = format!("{:.0}", clock.timebase.ppq.get()); + let chunk = format!("{chunk}"); + let latency = format!("{latency}"); + let color = color.unwrap_or(ItemPalette::from(TuiTheme::g(32))); if let Some(started) = clock.started.read().unwrap().as_ref() { let current_sample = (clock.global.sample.get() - started.sample.get())/1000.; let current_usec = clock.global.usec.get() - started.usec.get(); let current_second = current_usec/1000000.; Self { - color, focused, sr, bpm, ppq, + color, focused, sr, bpm, ppq, chunk, latency, started: true, global_sample: format!("{:.0}k", started.sample.get()/1000.), global_second: format!("{:.1}s", started.usec.get()/1000.), @@ -68,7 +75,7 @@ impl From<(&T, Option, bool)> for TransportView { } } else { Self { - color, focused, sr, bpm, ppq, + color, focused, sr, bpm, ppq, chunk, latency, started: false, global_sample: format!("{:.0}k", clock.global.sample.get()/1000.), global_second: format!("{:.1}s", clock.global.usec.get()/1000000.), @@ -91,13 +98,18 @@ render!(|self: TransportView|{ Tui::bg(color.base.rgb, Fill::w(row!([ //PlayPause(self.started), " ", col!([ - Field(" Beat", self.beat.as_str(), &color), - Field(" BPM", self.bpm.as_str(), &color), + Field(" Beat", self.beat.as_str(), &color), + Field(" BPM", self.bpm.as_str(), &color), ]), - " ", col!([ - Field("Time", format!("{:.1}s", self.current_second).as_str(), &color), - Field("Smpl", format!("{:.1}k", self.current_sample).as_str(), &color), + Field(" Time", format!("{:.1}s", self.current_second).as_str(), &color), + //Field(" Smpl", format!("{:.1}k", self.current_sample).as_str(), &color), + Field(" Rate", format!("{}", self.sr).as_str(), &color), + //Field(" CPU%", format!("{:.1}ms", self.perf).as_str(), &color), + ]), + col!([ + Field(" Chunk", format!("{}", self.chunk).as_str(), &color), + Field(" Lag", format!("{:.3}ms", self.latency).as_str(), &color), ]), ]))) }); From 774af02e5ee9634ff0389511533b95de91e38358 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 27 Dec 2024 22:22:08 +0100 Subject: [PATCH 036/815] updating phrase selector layout --- crates/tek/src/pool.rs | 68 +++----------------------- crates/tek/src/pool/phrase_selector.rs | 65 ++++++++++++++++++++++++ crates/tek/src/sequencer.rs | 7 +-- crates/tek/src/transport.rs | 4 +- crates/tek/src/tui/piano_h.rs | 9 ++-- 5 files changed, 83 insertions(+), 70 deletions(-) create mode 100644 crates/tek/src/pool/phrase_selector.rs diff --git a/crates/tek/src/pool.rs b/crates/tek/src/pool.rs index 5aa85a5d..0ab0e060 100644 --- a/crates/tek/src/pool.rs +++ b/crates/tek/src/pool.rs @@ -1,7 +1,13 @@ use super::*; -mod phrase_length; pub(crate) use phrase_length::*; -mod phrase_rename; pub(crate) use phrase_rename::*; +pub mod phrase_length; +pub(crate) use phrase_length::*; + +pub mod phrase_rename; +pub(crate) use phrase_rename::*; + +pub mod phrase_selector; +pub(crate) use phrase_selector::*; #[derive(Debug)] pub struct PoolModel { @@ -244,64 +250,6 @@ render!(|self: PoolView<'a>|{ add(&self.0.size) })) }); -pub struct PhraseSelector { - pub(crate) title: &'static str, - pub(crate) name: String, - pub(crate) color: ItemPalette, - pub(crate) time: String, -} -// TODO: Display phrases always in order of appearance -render!(|self: PhraseSelector|Fixed::wh(24, 1, row!([ - Tui::fg(self.color.lightest.rgb, Tui::bold(true, &self.title)), - Tui::fg_bg(self.color.lighter.rgb, self.color.base.rgb, row!([ - format!("{:8}", &self.name[0..8.min(self.name.len())]), - Tui::bg(self.color.dark.rgb, &self.time), - ])), -]))); -impl PhraseSelector { - // beats elapsed - pub fn play_phrase (state: &T) -> Self { - let (name, color) = if let Some((_, Some(phrase))) = state.play_phrase() { - let MidiClip { ref name, color, .. } = *phrase.read().unwrap(); - (name.clone(), color) - } else { - ("".to_string(), ItemPalette::from(TuiTheme::g(64))) - }; - let time = if let Some(elapsed) = state.pulses_since_start_looped() { - format!("+{:>}", state.clock().timebase.format_beats_0(elapsed)) - } else { - String::from(" ") - }; - Self { title: " Now|", time, name, color, } - } - // beats until switchover - pub fn next_phrase (state: &T) -> Self { - let (time, name, color) = if let Some((t, Some(phrase))) = state.next_phrase() { - let MidiClip { ref name, color, .. } = *phrase.read().unwrap(); - let time = { - let target = t.pulse.get(); - let current = state.clock().playhead.pulse.get(); - if target > current { - let remaining = target - current; - format!("-{:>}", state.clock().timebase.format_beats_0(remaining)) - } else { - String::new() - } - }; - (time, name.clone(), color) - } else if let Some((_, Some(phrase))) = state.play_phrase() { - let phrase = phrase.read().unwrap(); - if phrase.looped { - (" ".into(), phrase.name.clone(), phrase.color) - } else { - (" ".into(), " ".into(), TuiTheme::g(64).into()) - } - } else { - (" ".into(), " ".into(), TuiTheme::g(64).into()) - }; - Self { title: " Next|", time, name, color, } - } -} command!(|self: FileBrowserCommand, state: PoolModel|{ use PoolMode::*; use FileBrowserCommand::*; diff --git a/crates/tek/src/pool/phrase_selector.rs b/crates/tek/src/pool/phrase_selector.rs new file mode 100644 index 00000000..855a870b --- /dev/null +++ b/crates/tek/src/pool/phrase_selector.rs @@ -0,0 +1,65 @@ +use crate::*; + +pub struct PhraseSelector { + pub(crate) title: &'static str, + pub(crate) name: String, + pub(crate) color: ItemPalette, + pub(crate) time: String, +} + +// TODO: Display phrases always in order of appearance +render!(|self: PhraseSelector|Fixed::wh(24, 1, row!([ + Tui::fg(self.color.lightest.rgb, Tui::bold(true, &self.title)), + Tui::fg_bg(self.color.lighter.rgb, self.color.base.rgb, row!([ + format!("{:8}", &self.name[0..8.min(self.name.len())]), + Tui::bg(self.color.dark.rgb, &self.time), + ])), +]))); + +impl PhraseSelector { + + // beats elapsed + pub fn play_phrase (state: &T) -> Self { + let (name, color) = if let Some((_, Some(phrase))) = state.play_phrase() { + let MidiClip { ref name, color, .. } = *phrase.read().unwrap(); + (name.clone(), color) + } else { + ("".to_string(), ItemPalette::from(TuiTheme::g(64))) + }; + let time = if let Some(elapsed) = state.pulses_since_start_looped() { + format!("+{:>}", state.clock().timebase.format_beats_0(elapsed)) + } else { + String::from(" ") + }; + Self { title: "Now:|", time, name, color, } + } + + // beats until switchover + pub fn next_phrase (state: &T) -> Self { + let (time, name, color) = if let Some((t, Some(phrase))) = state.next_phrase() { + let MidiClip { ref name, color, .. } = *phrase.read().unwrap(); + let time = { + let target = t.pulse.get(); + let current = state.clock().playhead.pulse.get(); + if target > current { + let remaining = target - current; + format!("-{:>}", state.clock().timebase.format_beats_0(remaining)) + } else { + String::new() + } + }; + (time, name.clone(), color) + } else if let Some((_, Some(phrase))) = state.play_phrase() { + let phrase = phrase.read().unwrap(); + if phrase.looped { + (" ".into(), phrase.name.clone(), phrase.color) + } else { + (" ".into(), " ".into(), TuiTheme::g(64).into()) + } + } else { + (" ".into(), " ".into(), TuiTheme::g(64).into()) + }; + Self { title: " Next|", time, name, color, } + } + +} diff --git a/crates/tek/src/sequencer.rs b/crates/tek/src/sequencer.rs index 9b86fd2d..2613ffdc 100644 --- a/crates/tek/src/sequencer.rs +++ b/crates/tek/src/sequencer.rs @@ -52,11 +52,12 @@ render!(|self: SequencerTui|{ ).flatten().clone(); let play = Fixed::wh(5, 2, PlayPause(self.clock.is_rolling())); let transport = Fixed::h(2, TransportView::from((self, color, true))); - let toolbar = row!([play, col!([ + let toolbar = row!([play, transport]); + let play_queue = row!([ PhraseSelector::play_phrase(&self.player), PhraseSelector::next_phrase(&self.player), - ]), transport]); - Tui::min_y(15, with_size(with_status(col!([ toolbar, editor, ])))) + ]); + Tui::min_y(15, with_size(with_status(col!([ toolbar, play_queue, editor, ])))) }); audio!(|self:SequencerTui, client, scope|{ // Start profiling cycle diff --git a/crates/tek/src/transport.rs b/crates/tek/src/transport.rs index 980d091a..32d64d66 100644 --- a/crates/tek/src/transport.rs +++ b/crates/tek/src/transport.rs @@ -99,10 +99,10 @@ render!(|self: TransportView|{ //PlayPause(self.started), " ", col!([ Field(" Beat", self.beat.as_str(), &color), - Field(" BPM", self.bpm.as_str(), &color), + Field(" Time", format!("{:.1}s", self.current_second).as_str(), &color), ]), col!([ - Field(" Time", format!("{:.1}s", self.current_second).as_str(), &color), + Field(" BPM", self.bpm.as_str(), &color), //Field(" Smpl", format!("{:.1}k", self.current_sample).as_str(), &color), Field(" Rate", format!("{}", self.sr).as_str(), &color), //Field(" CPU%", format!("{:.1}ms", self.perf).as_str(), &color), diff --git a/crates/tek/src/tui/piano_h.rs b/crates/tek/src/tui/piano_h.rs index c7be69ac..78ff8645 100644 --- a/crates/tek/src/tui/piano_h.rs +++ b/crates/tek/src/tui/piano_h.rs @@ -61,7 +61,6 @@ render!(|self: PianoHorizontal|{ let field = move|x, y|row!([ Tui::fg_bg(color.lighter.rgb, color.darker.rgb, Tui::bold(true, x)), - Tui::fg_bg(color.light.rgb, color.darker.rgb, Tui::bold(true, "│")), Tui::fg_bg(color.lighter.rgb, color.dark.rgb, &y), ]); @@ -79,10 +78,10 @@ render!(|self: PianoHorizontal|{ with_border(lay!([ Tui::push_x(0, row!(![ - " ", - field(" Edit", name.to_string()), - field(" Length", length.to_string()), - field(" Loop", looped.to_string()) + //" ", + field("Edit:", name.to_string()), " ", + field("Length:", length.to_string()), " ", + field("Loop:", looped.to_string()) ])), Tui::inset_xy(0, 1, Fill::wh(Bsp::s( Fixed::h(1, Bsp::e( From a4835e2c8142850294511992ab138a68a9eaee5e Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 27 Dec 2024 23:06:19 +0100 Subject: [PATCH 037/815] break down sampler into modules and align with sequencer --- crates/tek/src/groovebox.rs | 40 +- crates/tek/src/sampler.rs | 615 ++-------------------- crates/tek/src/sampler/sample.rs | 120 +++++ crates/tek/src/sampler/sample_import.rs | 246 +++++++++ crates/tek/src/sampler/sampler_audio.rs | 67 +++ crates/tek/src/sampler/sampler_control.rs | 66 +++ crates/tek/src/sampler/sampler_keys.rs | 15 + crates/tek/src/sampler/voice.rs | 30 ++ crates/tek/src/sequencer.rs | 33 +- crates/tek/src/space.rs | 2 +- crates/tek/src/status/status_arranger.rs | 4 +- crates/tek/src/status/status_groovebox.rs | 4 +- crates/tek/src/status/status_sequencer.rs | 4 +- 13 files changed, 644 insertions(+), 602 deletions(-) create mode 100644 crates/tek/src/sampler/sample.rs create mode 100644 crates/tek/src/sampler/sample_import.rs create mode 100644 crates/tek/src/sampler/sampler_audio.rs create mode 100644 crates/tek/src/sampler/sampler_control.rs create mode 100644 crates/tek/src/sampler/sampler_keys.rs create mode 100644 crates/tek/src/sampler/voice.rs diff --git a/crates/tek/src/groovebox.rs b/crates/tek/src/groovebox.rs index a04774c2..3a1a24f4 100644 --- a/crates/tek/src/groovebox.rs +++ b/crates/tek/src/groovebox.rs @@ -12,7 +12,9 @@ pub struct GrooveboxTui { from_jack!(|jack|GrooveboxTui { let mut sequencer = SequencerTui::try_from(jack)?; - sequencer.status = false; + sequencer.status = false; + sequencer.transport = false; + sequencer.selectors = false; let midi_in_1 = jack.read().unwrap().register_port("in1", MidiIn::default())?; let midi_out = jack.read().unwrap().register_port("out", MidiOut::default())?; let midi_in_2 = jack.read().unwrap().register_port("in2", MidiIn::default())?; @@ -35,17 +37,31 @@ pub enum GrooveboxFocus { } audio!(|self:GrooveboxTui,_client,_process|Control::Continue); - -render!(|self:GrooveboxTui|Fill::wh(Bsp::n( - Fixed::h(2, GrooveboxStatus::from(self)), - Fill::h(lay!([ - Fill::h(&self.size), - Fill::h(Bsp::s( - Tui::min_y(20, &self.sequencer), - Tui::min_y(20, &self.sampler), - )) - ])), -))); +has_clock!(|self:GrooveboxTui|&self.sequencer.clock); +render!(|self:GrooveboxTui|Fill::wh(lay!([ + &self.size, + Fill::wh(Align::s(Fixed::h(2, GrooveboxStatus::from(self)))), + Tui::shrink_y(2, col!([ + Fixed::h(2, row!([ + Fixed::wh(5, 2, PlayPause(self.clock().is_rolling())), + Fixed::h(2, TransportView::from((self, self.sequencer.player.play_phrase().as_ref().map(|(_,p)| + p.as_ref().map(|p|p.read().unwrap().color) + ).flatten().clone(), true))), + ])), + Tui::push_x(20, Fixed::h(1, row!([ + PhraseSelector::play_phrase(&self.sequencer.player), + PhraseSelector::next_phrase(&self.sequencer.player), + ]))), + row!([ + Tui::pull_y(1, Tui::shrink_y(0, Fill::h(Fixed::w(20, &self.sampler)))), + Fill::wh(&self.sequencer), + ]), + ])) +]))); + //Bsp::n( + //Fill::wh(lay!([ + //])), +//))); pub enum GrooveboxCommand { Sequencer(SequencerCommand), diff --git a/crates/tek/src/sampler.rs b/crates/tek/src/sampler.rs index ba68e5db..0caa6a45 100644 --- a/crates/tek/src/sampler.rs +++ b/crates/tek/src/sampler.rs @@ -1,156 +1,5 @@ use crate::{*, tui::piano_h::PianoHorizontalKeys}; -/// The sampler plugin plays sounds. -#[derive(Debug)] -pub struct Sampler { - pub jack: Arc>, - pub name: String, - pub mapped: BTreeMap>>, - pub unmapped: Vec>>, - pub voices: Arc>>, - pub midi_in: Port, - pub audio_outs: Vec>, - pub buffer: Vec>, - pub output_gain: f32 -} - -/// A sound sample. -#[derive(Default, Debug)] -pub struct Sample { - pub name: String, - pub start: usize, - pub end: usize, - pub channels: Vec>, - pub rate: Option, -} - -/// A currently playing instance of a sample. -#[derive(Default, Debug, Clone)] -pub struct Voice { - pub sample: Arc>, - pub after: usize, - pub position: usize, - pub velocity: f32, -} - -/// Load sample from WAV and assign to MIDI note. -#[macro_export] macro_rules! sample { - ($note:expr, $name:expr, $src:expr) => {{ - let (end, data) = read_sample_data($src)?; - ( - u7::from_int_lossy($note).into(), - Sample::new($name, 0, end, data).into() - ) - }}; -} - -impl Sampler { - - /// Create [Voice]s from [Sample]s in response to MIDI input. - pub fn process_midi_in (&mut self, scope: &ProcessScope) { - let Sampler { midi_in, mapped, voices, .. } = self; - for RawMidi { time, bytes } in midi_in.iter(scope) { - if let LiveEvent::Midi { - message: MidiMessage::NoteOn { ref key, ref vel }, .. - } = LiveEvent::parse(bytes).unwrap() { - if let Some(sample) = mapped.get(key) { - voices.write().unwrap().push(Sample::play(sample, time as usize, vel)); - } - } - } - } - - /// Zero the output buffer. - pub fn clear_output_buffer (&mut self) { - for buffer in self.buffer.iter_mut() { - buffer.fill(0.0); - } - } - - /// Mix all currently playing samples into the output. - pub fn process_audio_out (&mut self, scope: &ProcessScope) { - let Sampler { ref mut buffer, voices, output_gain, .. } = self; - let channel_count = buffer.len(); - voices.write().unwrap().retain_mut(|voice|{ - for index in 0..scope.n_frames() as usize { - if let Some(frame) = voice.next() { - for (channel, sample) in frame.iter().enumerate() { - // Averaging mixer: - //self.buffer[channel % channel_count][index] = ( - //(self.buffer[channel % channel_count][index] + sample * self.output_gain) / 2.0 - //); - buffer[channel % channel_count][index] += sample * *output_gain; - } - } else { - return false - } - } - true - }); - } - - /// Write output buffer to output ports. - pub fn write_output_buffer (&mut self, scope: &ProcessScope) { - let Sampler { ref mut audio_outs, buffer, .. } = self; - for (i, port) in audio_outs.iter_mut().enumerate() { - let buffer = &buffer[i]; - for (i, value) in port.as_mut_slice(scope).iter_mut().enumerate() { - *value = *buffer.get(i).unwrap_or(&0.0); - } - } - } - -} - -impl Sample { - pub fn new (name: &str, start: usize, end: usize, channels: Vec>) -> Self { - Self { name: name.to_string(), start, end, channels, rate: None } - } - pub fn play (sample: &Arc>, after: usize, velocity: &u7) -> Voice { - Voice { - sample: sample.clone(), - after, - position: sample.read().unwrap().start, - velocity: velocity.as_int() as f32 / 127.0, - } - } - /// Read WAV from file - pub fn read_data (src: &str) -> Usually<(usize, Vec>)> { - let mut channels: Vec> = vec![]; - for channel in wavers::Wav::from_path(src)?.channels() { - channels.push(channel); - } - let mut end = 0; - let mut data: Vec> = vec![]; - for samples in channels.iter() { - let channel = Vec::from(samples.as_ref()); - end = end.max(channel.len()); - data.push(channel); - } - Ok((end, data)) - } -} - -impl Iterator for Voice { - type Item = [f32;2]; - fn next (&mut self) -> Option { - if self.after > 0 { - self.after -= 1; - return Some([0.0, 0.0]) - } - let sample = self.sample.read().unwrap(); - if self.position < sample.end { - let position = self.position; - self.position += 1; - return sample.channels[0].get(position).map(|_amplitude|[ - sample.channels[0][position] * self.velocity, - sample.channels[0][position] * self.velocity, - ]) - } - None - } -} - use KeyCode::Char; use std::fs::File; use symphonia::{ @@ -165,6 +14,39 @@ use symphonia::{ default::get_codecs, }; +pub mod sample; +pub(crate) use self::sample::*; +pub use self::sample::Sample; + +pub mod voice; +pub(crate) use self::voice::*; +pub use self::voice::Voice; + +pub mod sampler_control; +pub(crate) use self::sampler_control::*; + +pub mod sampler_audio; +pub(crate) use self::sampler_audio::*; + +pub mod sampler_keys; +pub(crate) use self::sampler_keys::*; + +pub mod sample_import; +pub(crate) use self::sample_import::*; + +/// The sampler plugin plays sounds. +#[derive(Debug)] +pub struct Sampler { + pub jack: Arc>, + pub name: String, + pub mapped: BTreeMap>>, + pub unmapped: Vec>>, + pub voices: Arc>>, + pub midi_in: Port, + pub audio_outs: Vec>, + pub buffer: Vec>, + pub output_gain: f32 +} pub struct SamplerTui { pub state: Sampler, pub cursor: (usize, usize), @@ -177,7 +59,22 @@ pub struct SamplerTui { pub note_pt: AtomicUsize, color: ItemPalette } - +impl SamplerTui { + /// Immutable reference to sample at cursor. + pub fn sample (&self) -> Option<&Arc>> { + for (i, sample) in self.state.mapped.values().enumerate() { + if i == self.cursor.0 { + return Some(sample) + } + } + for (i, sample) in self.state.unmapped.iter().enumerate() { + if i + self.state.mapped.len() == self.cursor.0 { + return Some(sample) + } + } + None + } +} from_jack!(|jack|SamplerTui{ let midi_in = jack.read().unwrap().client().register_port("in", MidiIn::default())?; let audio_outs = vec![ @@ -205,7 +102,6 @@ from_jack!(|jack|SamplerTui{ }, } }); - render!(|self: SamplerTui|{ let keys_width = 5; let keys = move||SamplerKeys(self); @@ -236,7 +132,6 @@ render!(|self: SamplerTui|{ ))), )))) }); - impl NoteRange for SamplerTui { fn note_lo (&self) -> &AtomicUsize { &self.note_lo } fn note_axis (&self) -> &AtomicUsize { &self.size.y } @@ -248,419 +143,7 @@ impl NotePoint for SamplerTui { fn set_note_point (&self, x: usize) { self.note_pt.store(x, Relaxed); } } -struct SamplerKeys<'a>(&'a SamplerTui); -has_color!(|self: SamplerKeys<'a>|self.0.color.base); -render!(|self: SamplerKeys<'a>|render(|to|Ok(render_keys_v(to, self)))); -impl<'a> NoteRange for SamplerKeys<'a> { - fn note_lo (&self) -> &AtomicUsize { &self.0.note_lo } - fn note_axis (&self) -> &AtomicUsize { &self.0.size.y } -} -impl<'a> NotePoint for SamplerKeys<'a> { - fn note_len (&self) -> usize {0/*TODO*/} - fn set_note_len (&self, x: usize) {} - fn note_point (&self) -> usize { self.0.note_point() } - fn set_note_point (&self, x: usize) { self.0.set_note_point(x); } -} - pub enum SamplerMode { // Load sample from path Import(usize, FileBrowser), } -handle!(|self:SamplerTui,input|SamplerCommand::execute_with_state(self, input)); -pub enum SamplerCommand { - Import(FileBrowserCommand), - SelectNote(usize), - SelectField(usize), - SetName(String), - SetNote(u7, Arc>), - SetGain(f32), - NoteOn(u7, u7), - NoteOff(u7) -} -input_to_command!(SamplerCommand: |state: SamplerTui, input|match state.mode { - Some(SamplerMode::Import(..)) => Self::Import( - FileBrowserCommand::input_to_command(state, input)? - ), - _ => match input.event() { - // load sample - key_pat!(Shift-Char('L')) => { - Self::Import(FileBrowserCommand::Begin) - }, - key_pat!(KeyCode::Up) => { - Self::SelectNote(state.note_point().overflowing_add(1).0.min(127)) - }, - key_pat!(KeyCode::Down) => { - Self::SelectNote(state.note_point().overflowing_sub(1).0.min(127)) - }, - _ => return None - } - //key_pat!(KeyCode::Char('p')) => if let Some(sample) = self.sample() { - //voices.write().unwrap().push(Sample::play(sample, 0, &100.into())); - //}, - //key_pat!(KeyCode::Char('a')) => { - //let sample = Arc::new(RwLock::new(Sample::new("", 0, 0, vec![]))); - //self.mode = None;//Some(Exit::boxed(AddSampleModal::new(&sample, &voices)?)); - //unmapped.push(sample); - //}, - //key_pat!(KeyCode::Char('r')) => if let Some(sample) = self.sample() { - //self.mode = None;//Some(Exit::boxed(AddSampleModal::new(&sample, &voices)?)); - //}, - //key_pat!(KeyCode::Enter) => if let Some(sample) = self.sample() { - //self.editing = Some(sample.clone()); - //}, - //_ => { - //return Ok(None) - //} - //} -}); -input_to_command!(FileBrowserCommand:|state:SamplerTui,input|match input { - _ => return None -}); -command!(|self:SamplerCommand,state:SamplerTui|match self { - Self::Import(FileBrowserCommand::Begin) => { - let voices = &state.state.voices; - let sample = Arc::new(RwLock::new(Sample::new("", 0, 0, vec![]))); - state.mode = Some(SamplerMode::Import(0, FileBrowser::new(None)?)); - None - }, - Self::SelectNote(index) => { - let old = state.note_point(); - state.set_note_point(index); - Some(Self::SelectNote(old)) - }, - _ => todo!() -}); -command!(|self:FileBrowserCommand,state:SamplerTui|match self { - _ => todo!() -}); -impl SamplerTui { - /// Immutable reference to sample at cursor. - pub fn sample (&self) -> Option<&Arc>> { - for (i, sample) in self.state.mapped.values().enumerate() { - if i == self.cursor.0 { - return Some(sample) - } - } - for (i, sample) in self.state.unmapped.iter().enumerate() { - if i + self.state.mapped.len() == self.cursor.0 { - return Some(sample) - } - } - None - } -} - -audio!(|self: SamplerTui, _client, scope|{ - self.state.process_midi_in(scope); - self.state.clear_output_buffer(); - self.state.process_audio_out(scope); - self.state.write_output_buffer(scope); - Control::Continue -}); - -pub struct AddSampleModal { - exited: bool, - dir: PathBuf, - subdirs: Vec, - files: Vec, - cursor: usize, - offset: usize, - sample: Arc>, - voices: Arc>>, - _search: Option, -} - -impl Exit for AddSampleModal { - fn exited (&self) -> bool { - self.exited - } - fn exit (&mut self) { - self.exited = true - } -} - -impl AddSampleModal { - pub fn new ( - sample: &Arc>, - voices: &Arc>> - ) -> Usually { - let dir = std::env::current_dir()?; - let (subdirs, files) = scan(&dir)?; - Ok(Self { - exited: false, - dir, - subdirs, - files, - cursor: 0, - offset: 0, - sample: sample.clone(), - voices: voices.clone(), - _search: None - }) - } - fn rescan (&mut self) -> Usually<()> { - scan(&self.dir).map(|(subdirs, files)|{ - self.subdirs = subdirs; - self.files = files; - }) - } - fn prev (&mut self) { - self.cursor = self.cursor.saturating_sub(1); - } - fn next (&mut self) { - self.cursor = self.cursor + 1; - } - fn try_preview (&mut self) -> Usually<()> { - if let Some(path) = self.cursor_file() { - if let Ok(sample) = Sample::from_file(&path) { - *self.sample.write().unwrap() = sample; - self.voices.write().unwrap().push( - Sample::play(&self.sample, 0, &u7::from(100u8)) - ); - } - //load_sample(&path)?; - //let src = std::fs::File::open(&path)?; - //let mss = MediaSourceStream::new(Box::new(src), Default::default()); - //let mut hint = Hint::new(); - //if let Some(ext) = path.extension() { - //hint.with_extension(&ext.to_string_lossy()); - //} - //let meta_opts: MetadataOptions = Default::default(); - //let fmt_opts: FormatOptions = Default::default(); - //if let Ok(mut probed) = symphonia::default::get_probe() - //.format(&hint, mss, &fmt_opts, &meta_opts) - //{ - //panic!("{:?}", probed.format.metadata()); - //}; - } - Ok(()) - } - fn cursor_dir (&self) -> Option { - if self.cursor < self.subdirs.len() { - Some(self.dir.join(&self.subdirs[self.cursor])) - } else { - None - } - } - fn cursor_file (&self) -> Option { - if self.cursor < self.subdirs.len() { - return None - } - let index = self.cursor.saturating_sub(self.subdirs.len()); - if index < self.files.len() { - Some(self.dir.join(&self.files[index])) - } else { - None - } - } - fn pick (&mut self) -> Usually { - if self.cursor == 0 { - if let Some(parent) = self.dir.parent() { - self.dir = parent.into(); - self.rescan()?; - self.cursor = 0; - return Ok(false) - } - } - if let Some(dir) = self.cursor_dir() { - self.dir = dir; - self.rescan()?; - self.cursor = 0; - return Ok(false) - } - if let Some(path) = self.cursor_file() { - let (end, channels) = read_sample_data(&path.to_string_lossy())?; - let mut sample = self.sample.write().unwrap(); - sample.name = path.file_name().unwrap().to_string_lossy().into(); - sample.end = end; - sample.channels = channels; - return Ok(true) - } - return Ok(false) - } -} - -fn read_sample_data (_: &str) -> Usually<(usize, Vec>)> { - todo!(); -} - -fn scan (dir: &PathBuf) -> Usually<(Vec, Vec)> { - let (mut subdirs, mut files) = std::fs::read_dir(dir)? - .fold((vec!["..".into()], vec![]), |(mut subdirs, mut files), entry|{ - let entry = entry.expect("failed to read drectory entry"); - let meta = entry.metadata().expect("failed to read entry metadata"); - if meta.is_file() { - files.push(entry.file_name()); - } else if meta.is_dir() { - subdirs.push(entry.file_name()); - } - (subdirs, files) - }); - subdirs.sort(); - files.sort(); - Ok((subdirs, files)) -} - -impl Sample { - fn from_file (path: &PathBuf) -> Usually { - let name = path.file_name().unwrap().to_string_lossy().into(); - let mut sample = Self { name, ..Default::default() }; - // Use file extension if present - let mut hint = Hint::new(); - if let Some(ext) = path.extension() { - hint.with_extension(&ext.to_string_lossy()); - } - let probed = symphonia::default::get_probe().format( - &hint, - MediaSourceStream::new( - Box::new(File::open(path)?), - Default::default(), - ), - &Default::default(), - &Default::default() - )?; - let mut format = probed.format; - let params = &format.tracks().iter() - .find(|t| t.codec_params.codec != CODEC_TYPE_NULL) - .expect("no tracks found") - .codec_params; - let mut decoder = get_codecs().make(params, &Default::default())?; - loop { - match format.next_packet() { - Ok(packet) => sample.decode_packet(&mut decoder, packet)?, - Err(Error::IoError(_)) => break decoder.last_decoded(), - Err(err) => return Err(err.into()), - }; - }; - sample.end = sample.channels.iter().fold(0, |l, c|l + c.len()); - Ok(sample) - } - fn decode_packet ( - &mut self, decoder: &mut Box, packet: Packet - ) -> Usually<()> { - // Decode a packet - let decoded = decoder - .decode(&packet) - .map_err(|e|Box::::from(e))?; - // Determine sample rate - let spec = *decoded.spec(); - if let Some(rate) = self.rate { - if rate != spec.rate as usize { - panic!("sample rate changed"); - } - } else { - self.rate = Some(spec.rate as usize); - } - // Determine channel count - while self.channels.len() < spec.channels.count() { - self.channels.push(vec![]); - } - // Load sample - let mut samples = SampleBuffer::new( - decoded.frames() as u64, - spec - ); - if samples.capacity() > 0 { - samples.copy_interleaved_ref(decoded); - for frame in samples.samples().chunks(spec.channels.count()) { - for (chan, frame) in frame.iter().enumerate() { - self.channels[chan].push(*frame) - } - } - } - Ok(()) - } -} - -fn draw_sample ( - to: &mut TuiOutput, x: u16, y: u16, note: Option<&u7>, sample: &Sample, focus: bool -) -> Usually { - let style = if focus { Style::default().green() } else { Style::default() }; - if focus { - to.blit(&"🬴", x+1, y, Some(style.bold())); - } - let label1 = format!("{:3} {:12}", - note.map(|n|n.to_string()).unwrap_or(String::default()), - sample.name); - let label2 = format!("{:>6} {:>6} +0.0", - sample.start, - sample.end); - to.blit(&label1, x+2, y, Some(style.bold())); - to.blit(&label2, x+3+label1.len()as u16, y, Some(style)); - Ok(label1.len() + label2.len() + 4) -} - -impl Render for AddSampleModal { - fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> { - todo!() - //Align::Center(()).layout(to) - } - fn render (&self, to: &mut TuiOutput) -> Usually<()> { - todo!() - //let area = to.area(); - //to.make_dim(); - //let area = center_box( - //area, - //64.max(area.w().saturating_sub(8)), - //20.max(area.w().saturating_sub(8)), - //); - //to.fill_fg(area, Color::Reset); - //to.fill_bg(area, Nord::bg_lo(true, true)); - //to.fill_char(area, ' '); - //to.blit(&format!("{}", &self.dir.to_string_lossy()), area.x()+2, area.y()+1, Some(Style::default().bold()))?; - //to.blit(&"Select sample:", area.x()+2, area.y()+2, Some(Style::default().bold()))?; - //for (i, (is_dir, name)) in self.subdirs.iter() - //.map(|path|(true, path)) - //.chain(self.files.iter().map(|path|(false, path))) - //.enumerate() - //.skip(self.offset) - //{ - //if i >= area.h() as usize - 4 { - //break - //} - //let t = if is_dir { "" } else { "" }; - //let line = format!("{t} {}", name.to_string_lossy()); - //let line = &line[..line.len().min(area.w() as usize - 4)]; - //to.blit(&line, area.x() + 2, area.y() + 3 + i as u16, Some(if i == self.cursor { - //Style::default().green() - //} else { - //Style::default().white() - //}))?; - //} - //Lozenge(Style::default()).draw(to) - } -} - -//impl Handle for AddSampleModal { - //fn handle (&mut self, from: &TuiInput) -> Perhaps { - //if from.handle_keymap(self, KEYMAP_ADD_SAMPLE)? { - //return Ok(Some(true)) - //} - //Ok(Some(true)) - //} -//} - -//pub const KEYMAP_ADD_SAMPLE: &'static [KeyBinding] = keymap!(AddSampleModal { - //[Esc, NONE, "sampler/add/close", "close help dialog", |modal: &mut AddSampleModal|{ - //modal.exit(); - //Ok(true) - //}], - //[Up, NONE, "sampler/add/prev", "select previous entry", |modal: &mut AddSampleModal|{ - //modal.prev(); - //Ok(true) - //}], - //[Down, NONE, "sampler/add/next", "select next entry", |modal: &mut AddSampleModal|{ - //modal.next(); - //Ok(true) - //}], - //[Enter, NONE, "sampler/add/enter", "activate selected entry", |modal: &mut AddSampleModal|{ - //if modal.pick()? { - //modal.exit(); - //} - //Ok(true) - //}], - //[Char('p'), NONE, "sampler/add/preview", "preview selected entry", |modal: &mut AddSampleModal|{ - //modal.try_preview()?; - //Ok(true) - //}] -//}); diff --git a/crates/tek/src/sampler/sample.rs b/crates/tek/src/sampler/sample.rs new file mode 100644 index 00000000..adfc9fa0 --- /dev/null +++ b/crates/tek/src/sampler/sample.rs @@ -0,0 +1,120 @@ +use crate::*; +use super::*; + +/// A sound sample. +#[derive(Default, Debug)] +pub struct Sample { + pub name: String, + pub start: usize, + pub end: usize, + pub channels: Vec>, + pub rate: Option, +} + +/// Load sample from WAV and assign to MIDI note. +#[macro_export] macro_rules! sample { + ($note:expr, $name:expr, $src:expr) => {{ + let (end, data) = read_sample_data($src)?; + ( + u7::from_int_lossy($note).into(), + Sample::new($name, 0, end, data).into() + ) + }}; +} + +impl Sample { + pub fn new (name: &str, start: usize, end: usize, channels: Vec>) -> Self { + Self { name: name.to_string(), start, end, channels, rate: None } + } + pub fn play (sample: &Arc>, after: usize, velocity: &u7) -> Voice { + Voice { + sample: sample.clone(), + after, + position: sample.read().unwrap().start, + velocity: velocity.as_int() as f32 / 127.0, + } + } + /// Read WAV from file + pub fn read_data (src: &str) -> Usually<(usize, Vec>)> { + let mut channels: Vec> = vec![]; + for channel in wavers::Wav::from_path(src)?.channels() { + channels.push(channel); + } + let mut end = 0; + let mut data: Vec> = vec![]; + for samples in channels.iter() { + let channel = Vec::from(samples.as_ref()); + end = end.max(channel.len()); + data.push(channel); + } + Ok((end, data)) + } + pub fn from_file (path: &PathBuf) -> Usually { + let name = path.file_name().unwrap().to_string_lossy().into(); + let mut sample = Self { name, ..Default::default() }; + // Use file extension if present + let mut hint = Hint::new(); + if let Some(ext) = path.extension() { + hint.with_extension(&ext.to_string_lossy()); + } + let probed = symphonia::default::get_probe().format( + &hint, + MediaSourceStream::new( + Box::new(File::open(path)?), + Default::default(), + ), + &Default::default(), + &Default::default() + )?; + let mut format = probed.format; + let params = &format.tracks().iter() + .find(|t| t.codec_params.codec != CODEC_TYPE_NULL) + .expect("no tracks found") + .codec_params; + let mut decoder = get_codecs().make(params, &Default::default())?; + loop { + match format.next_packet() { + Ok(packet) => sample.decode_packet(&mut decoder, packet)?, + Err(symphonia::core::errors::Error::IoError(_)) => break decoder.last_decoded(), + Err(err) => return Err(err.into()), + }; + }; + sample.end = sample.channels.iter().fold(0, |l, c|l + c.len()); + Ok(sample) + } + fn decode_packet ( + &mut self, decoder: &mut Box, packet: Packet + ) -> Usually<()> { + // Decode a packet + let decoded = decoder + .decode(&packet) + .map_err(|e|Box::::from(e))?; + // Determine sample rate + let spec = *decoded.spec(); + if let Some(rate) = self.rate { + if rate != spec.rate as usize { + panic!("sample rate changed"); + } + } else { + self.rate = Some(spec.rate as usize); + } + // Determine channel count + while self.channels.len() < spec.channels.count() { + self.channels.push(vec![]); + } + // Load sample + let mut samples = SampleBuffer::new( + decoded.frames() as u64, + spec + ); + if samples.capacity() > 0 { + samples.copy_interleaved_ref(decoded); + for frame in samples.samples().chunks(spec.channels.count()) { + for (chan, frame) in frame.iter().enumerate() { + self.channels[chan].push(*frame) + } + } + } + Ok(()) + } +} diff --git a/crates/tek/src/sampler/sample_import.rs b/crates/tek/src/sampler/sample_import.rs new file mode 100644 index 00000000..e14609ff --- /dev/null +++ b/crates/tek/src/sampler/sample_import.rs @@ -0,0 +1,246 @@ +use crate::*; +use super::*; + +input_to_command!(FileBrowserCommand:|state:SamplerTui,input|match input { + _ => return None +}); + +command!(|self:FileBrowserCommand,state:SamplerTui|match self { + _ => todo!() +}); + +pub struct AddSampleModal { + exited: bool, + dir: PathBuf, + subdirs: Vec, + files: Vec, + cursor: usize, + offset: usize, + sample: Arc>, + voices: Arc>>, + _search: Option, +} + +impl Exit for AddSampleModal { + fn exited (&self) -> bool { + self.exited + } + fn exit (&mut self) { + self.exited = true + } +} + +impl AddSampleModal { + pub fn new ( + sample: &Arc>, + voices: &Arc>> + ) -> Usually { + let dir = std::env::current_dir()?; + let (subdirs, files) = scan(&dir)?; + Ok(Self { + exited: false, + dir, + subdirs, + files, + cursor: 0, + offset: 0, + sample: sample.clone(), + voices: voices.clone(), + _search: None + }) + } + fn rescan (&mut self) -> Usually<()> { + scan(&self.dir).map(|(subdirs, files)|{ + self.subdirs = subdirs; + self.files = files; + }) + } + fn prev (&mut self) { + self.cursor = self.cursor.saturating_sub(1); + } + fn next (&mut self) { + self.cursor = self.cursor + 1; + } + fn try_preview (&mut self) -> Usually<()> { + if let Some(path) = self.cursor_file() { + if let Ok(sample) = Sample::from_file(&path) { + *self.sample.write().unwrap() = sample; + self.voices.write().unwrap().push( + Sample::play(&self.sample, 0, &u7::from(100u8)) + ); + } + //load_sample(&path)?; + //let src = std::fs::File::open(&path)?; + //let mss = MediaSourceStream::new(Box::new(src), Default::default()); + //let mut hint = Hint::new(); + //if let Some(ext) = path.extension() { + //hint.with_extension(&ext.to_string_lossy()); + //} + //let meta_opts: MetadataOptions = Default::default(); + //let fmt_opts: FormatOptions = Default::default(); + //if let Ok(mut probed) = symphonia::default::get_probe() + //.format(&hint, mss, &fmt_opts, &meta_opts) + //{ + //panic!("{:?}", probed.format.metadata()); + //}; + } + Ok(()) + } + fn cursor_dir (&self) -> Option { + if self.cursor < self.subdirs.len() { + Some(self.dir.join(&self.subdirs[self.cursor])) + } else { + None + } + } + fn cursor_file (&self) -> Option { + if self.cursor < self.subdirs.len() { + return None + } + let index = self.cursor.saturating_sub(self.subdirs.len()); + if index < self.files.len() { + Some(self.dir.join(&self.files[index])) + } else { + None + } + } + fn pick (&mut self) -> Usually { + if self.cursor == 0 { + if let Some(parent) = self.dir.parent() { + self.dir = parent.into(); + self.rescan()?; + self.cursor = 0; + return Ok(false) + } + } + if let Some(dir) = self.cursor_dir() { + self.dir = dir; + self.rescan()?; + self.cursor = 0; + return Ok(false) + } + if let Some(path) = self.cursor_file() { + let (end, channels) = read_sample_data(&path.to_string_lossy())?; + let mut sample = self.sample.write().unwrap(); + sample.name = path.file_name().unwrap().to_string_lossy().into(); + sample.end = end; + sample.channels = channels; + return Ok(true) + } + return Ok(false) + } +} + +fn read_sample_data (_: &str) -> Usually<(usize, Vec>)> { + todo!(); +} + +fn scan (dir: &PathBuf) -> Usually<(Vec, Vec)> { + let (mut subdirs, mut files) = std::fs::read_dir(dir)? + .fold((vec!["..".into()], vec![]), |(mut subdirs, mut files), entry|{ + let entry = entry.expect("failed to read drectory entry"); + let meta = entry.metadata().expect("failed to read entry metadata"); + if meta.is_file() { + files.push(entry.file_name()); + } else if meta.is_dir() { + subdirs.push(entry.file_name()); + } + (subdirs, files) + }); + subdirs.sort(); + files.sort(); + Ok((subdirs, files)) +} + +fn draw_sample ( + to: &mut TuiOutput, x: u16, y: u16, note: Option<&u7>, sample: &Sample, focus: bool +) -> Usually { + let style = if focus { Style::default().green() } else { Style::default() }; + if focus { + to.blit(&"🬴", x+1, y, Some(style.bold())); + } + let label1 = format!("{:3} {:12}", + note.map(|n|n.to_string()).unwrap_or(String::default()), + sample.name); + let label2 = format!("{:>6} {:>6} +0.0", + sample.start, + sample.end); + to.blit(&label1, x+2, y, Some(style.bold())); + to.blit(&label2, x+3+label1.len()as u16, y, Some(style)); + Ok(label1.len() + label2.len() + 4) +} + +impl Render for AddSampleModal { + fn min_size (&self, to: [u16;2]) -> Perhaps<[u16;2]> { + todo!() + //Align::Center(()).layout(to) + } + fn render (&self, to: &mut TuiOutput) -> Usually<()> { + todo!() + //let area = to.area(); + //to.make_dim(); + //let area = center_box( + //area, + //64.max(area.w().saturating_sub(8)), + //20.max(area.w().saturating_sub(8)), + //); + //to.fill_fg(area, Color::Reset); + //to.fill_bg(area, Nord::bg_lo(true, true)); + //to.fill_char(area, ' '); + //to.blit(&format!("{}", &self.dir.to_string_lossy()), area.x()+2, area.y()+1, Some(Style::default().bold()))?; + //to.blit(&"Select sample:", area.x()+2, area.y()+2, Some(Style::default().bold()))?; + //for (i, (is_dir, name)) in self.subdirs.iter() + //.map(|path|(true, path)) + //.chain(self.files.iter().map(|path|(false, path))) + //.enumerate() + //.skip(self.offset) + //{ + //if i >= area.h() as usize - 4 { + //break + //} + //let t = if is_dir { "" } else { "" }; + //let line = format!("{t} {}", name.to_string_lossy()); + //let line = &line[..line.len().min(area.w() as usize - 4)]; + //to.blit(&line, area.x() + 2, area.y() + 3 + i as u16, Some(if i == self.cursor { + //Style::default().green() + //} else { + //Style::default().white() + //}))?; + //} + //Lozenge(Style::default()).draw(to) + } +} + +//impl Handle for AddSampleModal { + //fn handle (&mut self, from: &TuiInput) -> Perhaps { + //if from.handle_keymap(self, KEYMAP_ADD_SAMPLE)? { + //return Ok(Some(true)) + //} + //Ok(Some(true)) + //} +//} + +//pub const KEYMAP_ADD_SAMPLE: &'static [KeyBinding] = keymap!(AddSampleModal { + //[Esc, NONE, "sampler/add/close", "close help dialog", |modal: &mut AddSampleModal|{ + //modal.exit(); + //Ok(true) + //}], + //[Up, NONE, "sampler/add/prev", "select previous entry", |modal: &mut AddSampleModal|{ + //modal.prev(); + //Ok(true) + //}], + //[Down, NONE, "sampler/add/next", "select next entry", |modal: &mut AddSampleModal|{ + //modal.next(); + //Ok(true) + //}], + //[Enter, NONE, "sampler/add/enter", "activate selected entry", |modal: &mut AddSampleModal|{ + //if modal.pick()? { + //modal.exit(); + //} + //Ok(true) + //}], + //[Char('p'), NONE, "sampler/add/preview", "preview selected entry", |modal: &mut AddSampleModal|{ + //modal.try_preview()?; + //Ok(true) + //}] +//}); diff --git a/crates/tek/src/sampler/sampler_audio.rs b/crates/tek/src/sampler/sampler_audio.rs new file mode 100644 index 00000000..60efa41f --- /dev/null +++ b/crates/tek/src/sampler/sampler_audio.rs @@ -0,0 +1,67 @@ +use crate::*; + +audio!(|self: SamplerTui, _client, scope|{ + self.state.process_midi_in(scope); + self.state.clear_output_buffer(); + self.state.process_audio_out(scope); + self.state.write_output_buffer(scope); + Control::Continue +}); + +impl Sampler { + + /// Create [Voice]s from [Sample]s in response to MIDI input. + pub fn process_midi_in (&mut self, scope: &ProcessScope) { + let Sampler { midi_in, mapped, voices, .. } = self; + for RawMidi { time, bytes } in midi_in.iter(scope) { + if let LiveEvent::Midi { + message: MidiMessage::NoteOn { ref key, ref vel }, .. + } = LiveEvent::parse(bytes).unwrap() { + if let Some(sample) = mapped.get(key) { + voices.write().unwrap().push(Sample::play(sample, time as usize, vel)); + } + } + } + } + + /// Zero the output buffer. + pub fn clear_output_buffer (&mut self) { + for buffer in self.buffer.iter_mut() { + buffer.fill(0.0); + } + } + + /// Mix all currently playing samples into the output. + pub fn process_audio_out (&mut self, scope: &ProcessScope) { + let Sampler { ref mut buffer, voices, output_gain, .. } = self; + let channel_count = buffer.len(); + voices.write().unwrap().retain_mut(|voice|{ + for index in 0..scope.n_frames() as usize { + if let Some(frame) = voice.next() { + for (channel, sample) in frame.iter().enumerate() { + // Averaging mixer: + //self.buffer[channel % channel_count][index] = ( + //(self.buffer[channel % channel_count][index] + sample * self.output_gain) / 2.0 + //); + buffer[channel % channel_count][index] += sample * *output_gain; + } + } else { + return false + } + } + true + }); + } + + /// Write output buffer to output ports. + pub fn write_output_buffer (&mut self, scope: &ProcessScope) { + let Sampler { ref mut audio_outs, buffer, .. } = self; + for (i, port) in audio_outs.iter_mut().enumerate() { + let buffer = &buffer[i]; + for (i, value) in port.as_mut_slice(scope).iter_mut().enumerate() { + *value = *buffer.get(i).unwrap_or(&0.0); + } + } + } + +} diff --git a/crates/tek/src/sampler/sampler_control.rs b/crates/tek/src/sampler/sampler_control.rs new file mode 100644 index 00000000..0a8d9c63 --- /dev/null +++ b/crates/tek/src/sampler/sampler_control.rs @@ -0,0 +1,66 @@ +use crate::*; +use KeyCode::Char; + +handle!(|self:SamplerTui,input|SamplerCommand::execute_with_state(self, input)); + +pub enum SamplerCommand { + Import(FileBrowserCommand), + SelectNote(usize), + SelectField(usize), + SetName(String), + SetNote(u7, Arc>), + SetGain(f32), + NoteOn(u7, u7), + NoteOff(u7) +} + +input_to_command!(SamplerCommand: |state: SamplerTui, input|match state.mode { + Some(SamplerMode::Import(..)) => Self::Import( + FileBrowserCommand::input_to_command(state, input)? + ), + _ => match input.event() { + // load sample + key_pat!(Shift-Char('L')) => { + Self::Import(FileBrowserCommand::Begin) + }, + key_pat!(KeyCode::Up) => { + Self::SelectNote(state.note_point().overflowing_add(1).0.min(127)) + }, + key_pat!(KeyCode::Down) => { + Self::SelectNote(state.note_point().overflowing_sub(1).0.min(127)) + }, + _ => return None + } + //key_pat!(KeyCode::Char('p')) => if let Some(sample) = self.sample() { + //voices.write().unwrap().push(Sample::play(sample, 0, &100.into())); + //}, + //key_pat!(KeyCode::Char('a')) => { + //let sample = Arc::new(RwLock::new(Sample::new("", 0, 0, vec![]))); + //self.mode = None;//Some(Exit::boxed(AddSampleModal::new(&sample, &voices)?)); + //unmapped.push(sample); + //}, + //key_pat!(KeyCode::Char('r')) => if let Some(sample) = self.sample() { + //self.mode = None;//Some(Exit::boxed(AddSampleModal::new(&sample, &voices)?)); + //}, + //key_pat!(KeyCode::Enter) => if let Some(sample) = self.sample() { + //self.editing = Some(sample.clone()); + //}, + //_ => { + //return Ok(None) + //} + //} +}); +command!(|self:SamplerCommand,state:SamplerTui|match self { + Self::Import(FileBrowserCommand::Begin) => { + let voices = &state.state.voices; + let sample = Arc::new(RwLock::new(Sample::new("", 0, 0, vec![]))); + state.mode = Some(SamplerMode::Import(0, FileBrowser::new(None)?)); + None + }, + Self::SelectNote(index) => { + let old = state.note_point(); + state.set_note_point(index); + Some(Self::SelectNote(old)) + }, + _ => todo!() +}); diff --git a/crates/tek/src/sampler/sampler_keys.rs b/crates/tek/src/sampler/sampler_keys.rs new file mode 100644 index 00000000..92fad051 --- /dev/null +++ b/crates/tek/src/sampler/sampler_keys.rs @@ -0,0 +1,15 @@ +use crate::*; + +pub struct SamplerKeys<'a>(pub &'a SamplerTui); +has_color!(|self: SamplerKeys<'a>|self.0.color.base); +render!(|self: SamplerKeys<'a>|render(|to|Ok(render_keys_v(to, self)))); +impl<'a> NoteRange for SamplerKeys<'a> { + fn note_lo (&self) -> &AtomicUsize { &self.0.note_lo } + fn note_axis (&self) -> &AtomicUsize { &self.0.size.y } +} +impl<'a> NotePoint for SamplerKeys<'a> { + fn note_len (&self) -> usize {0/*TODO*/} + fn set_note_len (&self, x: usize) {} + fn note_point (&self) -> usize { self.0.note_point() } + fn set_note_point (&self, x: usize) { self.0.set_note_point(x); } +} diff --git a/crates/tek/src/sampler/voice.rs b/crates/tek/src/sampler/voice.rs new file mode 100644 index 00000000..738f51aa --- /dev/null +++ b/crates/tek/src/sampler/voice.rs @@ -0,0 +1,30 @@ +use crate::*; + +/// A currently playing instance of a sample. +#[derive(Default, Debug, Clone)] +pub struct Voice { + pub sample: Arc>, + pub after: usize, + pub position: usize, + pub velocity: f32, +} + +impl Iterator for Voice { + type Item = [f32;2]; + fn next (&mut self) -> Option { + if self.after > 0 { + self.after -= 1; + return Some([0.0, 0.0]) + } + let sample = self.sample.read().unwrap(); + if self.position < sample.end { + let position = self.position; + self.position += 1; + return sample.channels[0].get(position).map(|_amplitude|[ + sample.channels[0][position] * self.velocity, + sample.channels[0][position] * self.velocity, + ]) + } + None + } +} diff --git a/crates/tek/src/sequencer.rs b/crates/tek/src/sequencer.rs index 2613ffdc..7b24dc5d 100644 --- a/crates/tek/src/sequencer.rs +++ b/crates/tek/src/sequencer.rs @@ -7,15 +7,17 @@ use PhrasePoolCommand::*; /// Root view for standalone `tek_sequencer`. pub struct SequencerTui { _jack: Arc>, - pub clock: ClockModel, - pub phrases: PoolModel, - pub player: MidiPlayer, - pub editor: MidiEditorModel, - pub size: Measure, - pub status: bool, - pub note_buf: Vec, - pub midi_buf: Vec>>, - pub perf: PerfModel, + pub transport: bool, + pub selectors: bool, + pub clock: ClockModel, + pub phrases: PoolModel, + pub player: MidiPlayer, + pub editor: MidiEditorModel, + pub size: Measure, + pub status: bool, + pub note_buf: Vec, + pub midi_buf: Vec>>, + pub perf: PerfModel, } from_jack!(|jack|SequencerTui { let clock = ClockModel::from(jack); @@ -25,6 +27,8 @@ from_jack!(|jack|SequencerTui { ))); Self { _jack: jack.clone(), + transport: true, + selectors: true, phrases: PoolModel::from(&phrase), editor: MidiEditorModel::from(&phrase), player: MidiPlayer::from((&clock, &phrase)), @@ -40,7 +44,7 @@ render!(|self: SequencerTui|{ let w = self.size.w(); let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; let pool_w = if self.phrases.visible { phrase_w } else { 0 }; - let pool = Fill::h(Align::e(PoolView(&self.phrases))); + let pool = Tui::pull_y(1, Fill::h(Align::e(PoolView(&self.phrases)))); let with_pool = move|x|Tui::split_w(false, pool_w, pool, x); let status = SequencerStatus::from(self); let with_status = |x|Tui::split_n(false, if self.status { 2 } else { 0 }, status, x); @@ -50,13 +54,14 @@ render!(|self: SequencerTui|{ let color = self.player.play_phrase().as_ref().map(|(_,p)| p.as_ref().map(|p|p.read().unwrap().color) ).flatten().clone(); - let play = Fixed::wh(5, 2, PlayPause(self.clock.is_rolling())); - let transport = Fixed::h(2, TransportView::from((self, color, true))); - let toolbar = row!([play, transport]); + let toolbar = row!([ + Fixed::wh(5, 2, PlayPause(self.clock.is_rolling())), + Fixed::h(2, TransportView::from((self, color, true))), + ]).when(self.transport); let play_queue = row!([ PhraseSelector::play_phrase(&self.player), PhraseSelector::next_phrase(&self.player), - ]); + ]).when(self.selectors);; Tui::min_y(15, with_size(with_status(col!([ toolbar, play_queue, editor, ])))) }); audio!(|self:SequencerTui, client, scope|{ diff --git a/crates/tek/src/space.rs b/crates/tek/src/space.rs index ec0cbd7e..7b6a624b 100644 --- a/crates/tek/src/space.rs +++ b/crates/tek/src/space.rs @@ -9,7 +9,7 @@ use std::fmt::{Display, Debug}; pub(crate) mod align; pub(crate) mod bsp; -pub(crate) mod cond; +pub(crate) mod cond; pub(crate) use cond::*; pub(crate) mod fill; pub(crate) mod fixed; pub(crate) use fixed::*; pub(crate) mod inset_outset; pub(crate) use inset_outset::*; diff --git a/crates/tek/src/status/status_arranger.rs b/crates/tek/src/status/status_arranger.rs index a8f80422..751fb030 100644 --- a/crates/tek/src/status/status_arranger.rs +++ b/crates/tek/src/status/status_arranger.rs @@ -6,7 +6,6 @@ pub struct ArrangerStatus { pub(crate) width: usize, pub(crate) cpu: Option, pub(crate) size: String, - pub(crate) res: String, pub(crate) playing: bool, } from!(|state:&ArrangerTui|ArrangerStatus = { @@ -19,7 +18,6 @@ from!(|state:&ArrangerTui|ArrangerStatus = { playing: state.clock.is_rolling(), cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%")), size: format!("{}x{}│", width, state.size.h()), - res: format!("│{}s│{:.1}kHz│{:.1}ms│", samples, rate / 1000., buffer * 1000.), } }); render!(|self: ArrangerStatus|Fixed::h(2, lay!([ @@ -49,6 +47,6 @@ impl ArrangerStatus { ])) } fn stats (&self) -> impl Render + use<'_> { - row!([&self.cpu, &self.res, &self.size]) + row!([&self.cpu, &self.size]) } } diff --git a/crates/tek/src/status/status_groovebox.rs b/crates/tek/src/status/status_groovebox.rs index 65d77a2d..e92e99cf 100644 --- a/crates/tek/src/status/status_groovebox.rs +++ b/crates/tek/src/status/status_groovebox.rs @@ -6,7 +6,6 @@ pub struct GrooveboxStatus { pub(crate) width: usize, pub(crate) cpu: Option, pub(crate) size: String, - pub(crate) res: String, pub(crate) playing: bool, } from!(|state:&GrooveboxTui|GrooveboxStatus = { @@ -19,7 +18,6 @@ from!(|state:&GrooveboxTui|GrooveboxStatus = { playing: state.sequencer.clock.is_rolling(), cpu: state.sequencer.perf.percentage().map(|cpu|format!("│{cpu:.01}%")), size: format!("{}x{}│", width, state.size.h()), - res: format!("│{}s│{:.1}kHz│{:.1}ms│", samples, rate / 1000., buffer * 1000.), } }); render!(|self: GrooveboxStatus|Fixed::h(2, lay!([ @@ -47,6 +45,6 @@ impl GrooveboxStatus { ])) } fn stats (&self) -> impl Render + use<'_> { - row!([&self.cpu, &self.res, &self.size]) + row!([&self.cpu, &self.size]) } } diff --git a/crates/tek/src/status/status_sequencer.rs b/crates/tek/src/status/status_sequencer.rs index 91257cad..9bf9c020 100644 --- a/crates/tek/src/status/status_sequencer.rs +++ b/crates/tek/src/status/status_sequencer.rs @@ -6,7 +6,6 @@ pub struct SequencerStatus { pub(crate) width: usize, pub(crate) cpu: Option, pub(crate) size: String, - pub(crate) res: String, pub(crate) playing: bool, } from!(|state:&SequencerTui|SequencerStatus = { @@ -19,7 +18,6 @@ from!(|state:&SequencerTui|SequencerStatus = { playing: state.clock.is_rolling(), cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%")), size: format!("{}x{}│", width, state.size.h()), - res: format!("│{}s│{:.1}kHz│{:.1}ms│", samples, rate / 1000., buffer * 1000.), } }); render!(|self: SequencerStatus|Fixed::h(2, lay!([ @@ -47,6 +45,6 @@ impl SequencerStatus { ])) } fn stats (&self) -> impl Render + use<'_> { - row!([&self.cpu, &self.res, &self.size]) + row!([&self.cpu, &self.size]) } } From 12f6b679c7cf830b196ebc27ed3d2a6abad9fd44 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 28 Dec 2024 13:33:05 +0100 Subject: [PATCH 038/815] add release build to ci --- .forgejo/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/build.yaml b/.forgejo/workflows/build.yaml index 45ccc8a1..0c716744 100644 --- a/.forgejo/workflows/build.yaml +++ b/.forgejo/workflows/build.yaml @@ -7,4 +7,4 @@ jobs: - run: nix-channel --list && nix-channel --update - run: nix-shell -p git --command 'git clone $GITHUB_SERVER_URL/$GITHUB_REPOSITORY .' - run: whoami && pwd && ls -al - - run: nix-shell --command 'cargo version -vv && cargo test && cloc crates/tek/src' .forgejo/workflows/build.nix + - run: nix-shell --command 'cargo version -vv && cargo test && cargo build --release && cloc crates/tek/src' .forgejo/workflows/build.nix From 51971e4c25d651653a4b55d850299f0d2b68cc45 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 28 Dec 2024 13:45:22 +0100 Subject: [PATCH 039/815] move piano_h to top of crate --- crates/tek/src/groovebox.rs | 54 +++++++++++-------- crates/tek/src/lib.rs | 2 + crates/tek/src/{tui => }/piano_h.rs | 0 .../src/{tui => }/piano_h/piano_h_cursor.rs | 0 .../tek/src/{tui => }/piano_h/piano_h_keys.rs | 0 .../src/{tui => }/piano_h/piano_h_notes.rs | 0 .../tek/src/{tui => }/piano_h/piano_h_time.rs | 0 crates/tek/src/{tui => }/piano_v.rs | 0 crates/tek/src/sampler.rs | 3 +- crates/tek/src/sequencer.rs | 6 ++- crates/tek/src/tui.rs | 1 - 11 files changed, 39 insertions(+), 27 deletions(-) rename crates/tek/src/{tui => }/piano_h.rs (100%) rename crates/tek/src/{tui => }/piano_h/piano_h_cursor.rs (100%) rename crates/tek/src/{tui => }/piano_h/piano_h_keys.rs (100%) rename crates/tek/src/{tui => }/piano_h/piano_h_notes.rs (100%) rename crates/tek/src/{tui => }/piano_h/piano_h_time.rs (100%) rename crates/tek/src/{tui => }/piano_v.rs (100%) diff --git a/crates/tek/src/groovebox.rs b/crates/tek/src/groovebox.rs index 3a1a24f4..ad2316a5 100644 --- a/crates/tek/src/groovebox.rs +++ b/crates/tek/src/groovebox.rs @@ -38,30 +38,38 @@ pub enum GrooveboxFocus { audio!(|self:GrooveboxTui,_client,_process|Control::Continue); has_clock!(|self:GrooveboxTui|&self.sequencer.clock); -render!(|self:GrooveboxTui|Fill::wh(lay!([ - &self.size, - Fill::wh(Align::s(Fixed::h(2, GrooveboxStatus::from(self)))), - Tui::shrink_y(2, col!([ - Fixed::h(2, row!([ - Fixed::wh(5, 2, PlayPause(self.clock().is_rolling())), - Fixed::h(2, TransportView::from((self, self.sequencer.player.play_phrase().as_ref().map(|(_,p)| - p.as_ref().map(|p|p.read().unwrap().color) - ).flatten().clone(), true))), - ])), - Tui::push_x(20, Fixed::h(1, row!([ - PhraseSelector::play_phrase(&self.sequencer.player), - PhraseSelector::next_phrase(&self.sequencer.player), - ]))), - row!([ - Tui::pull_y(1, Tui::shrink_y(0, Fill::h(Fixed::w(20, &self.sampler)))), - Fill::wh(&self.sequencer), - ]), +render!(|self:GrooveboxTui|{ + let w = self.size.w(); + let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; + let pool_w = if self.sequencer.phrases.visible { phrase_w } else { 0 }; + let sampler_w = 24; + Fill::wh(lay!([ + &self.size, + Fill::wh(Align::s(Fixed::h(2, GrooveboxStatus::from(self)))), + Tui::shrink_y(2, col!([ + Fixed::h(2, row!([ + Fixed::wh(5, 2, PlayPause(self.clock().is_rolling())), + Fixed::h(2, TransportView::from((self, self.sequencer.player.play_phrase().as_ref().map(|(_,p)| + p.as_ref().map(|p|p.read().unwrap().color) + ).flatten().clone(), true))), + ])), + Tui::push_x(sampler_w, Fixed::h(1, row!([ + PhraseSelector::play_phrase(&self.sequencer.player), + PhraseSelector::next_phrase(&self.sequencer.player), + ]))), + row!([ + Tui::pull_y(1, Tui::shrink_y(0, Fill::h(Fixed::w(sampler_w, &self.sampler)))), + Tui::split_n(false, 1, + MidiEditStatus(&self.sequencer.editor), + Tui::split_w(false, pool_w, + Tui::pull_y(1, Fill::h(Align::e(PoolView(&self.sequencer.phrases)))), + Fill::wh(&self.sequencer.editor) + ) + ), + ]), + ])) ])) -]))); - //Bsp::n( - //Fill::wh(lay!([ - //])), -//))); +}); pub enum GrooveboxCommand { Sequencer(SequencerCommand), diff --git a/crates/tek/src/lib.rs b/crates/tek/src/lib.rs index be8099fb..934eb858 100644 --- a/crates/tek/src/lib.rs +++ b/crates/tek/src/lib.rs @@ -15,6 +15,8 @@ pub use self::jack::*; pub mod midi; pub(crate) use self::midi::*; +pub mod piano_h; pub(crate) use self::piano_h::*; + pub mod transport; pub(crate) use self::transport::*; pub use self::transport::TransportTui; diff --git a/crates/tek/src/tui/piano_h.rs b/crates/tek/src/piano_h.rs similarity index 100% rename from crates/tek/src/tui/piano_h.rs rename to crates/tek/src/piano_h.rs diff --git a/crates/tek/src/tui/piano_h/piano_h_cursor.rs b/crates/tek/src/piano_h/piano_h_cursor.rs similarity index 100% rename from crates/tek/src/tui/piano_h/piano_h_cursor.rs rename to crates/tek/src/piano_h/piano_h_cursor.rs diff --git a/crates/tek/src/tui/piano_h/piano_h_keys.rs b/crates/tek/src/piano_h/piano_h_keys.rs similarity index 100% rename from crates/tek/src/tui/piano_h/piano_h_keys.rs rename to crates/tek/src/piano_h/piano_h_keys.rs diff --git a/crates/tek/src/tui/piano_h/piano_h_notes.rs b/crates/tek/src/piano_h/piano_h_notes.rs similarity index 100% rename from crates/tek/src/tui/piano_h/piano_h_notes.rs rename to crates/tek/src/piano_h/piano_h_notes.rs diff --git a/crates/tek/src/tui/piano_h/piano_h_time.rs b/crates/tek/src/piano_h/piano_h_time.rs similarity index 100% rename from crates/tek/src/tui/piano_h/piano_h_time.rs rename to crates/tek/src/piano_h/piano_h_time.rs diff --git a/crates/tek/src/tui/piano_v.rs b/crates/tek/src/piano_v.rs similarity index 100% rename from crates/tek/src/tui/piano_v.rs rename to crates/tek/src/piano_v.rs diff --git a/crates/tek/src/sampler.rs b/crates/tek/src/sampler.rs index 0caa6a45..c394b28e 100644 --- a/crates/tek/src/sampler.rs +++ b/crates/tek/src/sampler.rs @@ -1,5 +1,4 @@ -use crate::{*, tui::piano_h::PianoHorizontalKeys}; - +use crate::*; use KeyCode::Char; use std::fs::File; use symphonia::{ diff --git a/crates/tek/src/sequencer.rs b/crates/tek/src/sequencer.rs index 7b24dc5d..7e6bfc60 100644 --- a/crates/tek/src/sequencer.rs +++ b/crates/tek/src/sequencer.rs @@ -62,7 +62,11 @@ render!(|self: SequencerTui|{ PhraseSelector::play_phrase(&self.player), PhraseSelector::next_phrase(&self.player), ]).when(self.selectors);; - Tui::min_y(15, with_size(with_status(col!([ toolbar, play_queue, editor, ])))) + Tui::min_y(15, with_size(with_status(col!([ + toolbar, + play_queue, + editor, + ])))) }); audio!(|self:SequencerTui, client, scope|{ // Start profiling cycle diff --git a/crates/tek/src/tui.rs b/crates/tek/src/tui.rs index 726f6ce7..737ef479 100644 --- a/crates/tek/src/tui.rs +++ b/crates/tek/src/tui.rs @@ -15,7 +15,6 @@ mod tui_border; pub(crate) use self::tui_border::*; /////////////////////////////////////////////////////// pub mod file_browser; pub(crate) use self::file_browser::*; -pub mod piano_h; pub(crate) use self::piano_h::*; //////////////////////////////////////////////////////// From 9f739fe04039765e985d7a91cfcbe09bd2af8105 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 28 Dec 2024 14:03:12 +0100 Subject: [PATCH 040/815] add groovebox app its own copy of sequencer innards --- crates/cli/src/cli_groovebox.rs | 4 +- crates/tek/src/groovebox.rs | 161 +++++++++++++++++----- crates/tek/src/piano_h.rs | 9 +- crates/tek/src/sequencer.rs | 10 +- crates/tek/src/status/status_groovebox.rs | 8 +- crates/tek/src/transport.rs | 99 +++++++++++++ crates/tek/src/tui.rs | 99 ------------- 7 files changed, 240 insertions(+), 150 deletions(-) diff --git a/crates/cli/src/cli_groovebox.rs b/crates/cli/src/cli_groovebox.rs index 002777c8..49631ec1 100644 --- a/crates/cli/src/cli_groovebox.rs +++ b/crates/cli/src/cli_groovebox.rs @@ -34,8 +34,8 @@ impl GrooveboxCli { let jack = jack.read().unwrap(); let midi_in = jack.register_port("i", MidiIn::default())?; let midi_out = jack.register_port("o", MidiOut::default())?; - app.sequencer.player.midi_ins.push(midi_in); - app.sequencer.player.midi_outs.push(midi_out); + app.player.midi_ins.push(midi_in); + app.player.midi_outs.push(midi_out); Ok(app) })?)?; Ok(()) diff --git a/crates/tek/src/groovebox.rs b/crates/tek/src/groovebox.rs index ad2316a5..64a444fe 100644 --- a/crates/tek/src/groovebox.rs +++ b/crates/tek/src/groovebox.rs @@ -1,20 +1,31 @@ use crate::*; use super::*; use KeyCode::{Char, Delete, Tab, Up, Down, Left, Right}; +use ClockCommand::{Play, Pause}; +use GrooveboxCommand::*; +use PhraseCommand::*; +use PhrasePoolCommand::*; pub struct GrooveboxTui { + _jack: Arc>, + pub clock: ClockModel, + pub phrases: PoolModel, + pub player: MidiPlayer, + pub editor: MidiEditorModel, pub size: Measure, - pub sequencer: SequencerTui, + pub status: bool, + pub note_buf: Vec, + pub midi_buf: Vec>>, + pub perf: PerfModel, pub sampler: SamplerTui, - pub split: u16, - pub focus: GrooveboxFocus } from_jack!(|jack|GrooveboxTui { - let mut sequencer = SequencerTui::try_from(jack)?; - sequencer.status = false; - sequencer.transport = false; - sequencer.selectors = false; + let clock = ClockModel::from(jack); + let phrase = Arc::new(RwLock::new(MidiClip::new( + "New", true, 4 * clock.timebase.ppq.get() as usize, + None, Some(ItemColor::random().into()) + ))); let midi_in_1 = jack.read().unwrap().register_port("in1", MidiIn::default())?; let midi_out = jack.read().unwrap().register_port("out", MidiOut::default())?; let midi_in_2 = jack.read().unwrap().register_port("in2", MidiIn::default())?; @@ -23,25 +34,25 @@ from_jack!(|jack|GrooveboxTui { let audio_out_1 = jack.read().unwrap().register_port("out1", AudioOut::default())?; let audio_out_2 = jack.read().unwrap().register_port("out2", AudioOut::default())?; Self { - sequencer, - sampler: SamplerTui::try_from(jack)?, - split: 16, - focus: GrooveboxFocus::Sequencer, - size: Measure::new(), + _jack: jack.clone(), + phrases: PoolModel::from(&phrase), + editor: MidiEditorModel::from(&phrase), + player: MidiPlayer::from((&clock, &phrase)), + sampler: SamplerTui::try_from(jack)?, + size: Measure::new(), + midi_buf: vec![vec![];65536], + note_buf: vec![], + perf: PerfModel::default(), + status: true, + clock, } }); - -pub enum GrooveboxFocus { - Sequencer, - Sampler -} - audio!(|self:GrooveboxTui,_client,_process|Control::Continue); -has_clock!(|self:GrooveboxTui|&self.sequencer.clock); +has_clock!(|self:GrooveboxTui|&self.clock); render!(|self:GrooveboxTui|{ let w = self.size.w(); let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; - let pool_w = if self.sequencer.phrases.visible { phrase_w } else { 0 }; + let pool_w = if self.phrases.visible { phrase_w } else { 0 }; let sampler_w = 24; Fill::wh(lay!([ &self.size, @@ -49,21 +60,21 @@ render!(|self:GrooveboxTui|{ Tui::shrink_y(2, col!([ Fixed::h(2, row!([ Fixed::wh(5, 2, PlayPause(self.clock().is_rolling())), - Fixed::h(2, TransportView::from((self, self.sequencer.player.play_phrase().as_ref().map(|(_,p)| + Fixed::h(2, TransportView::from((self, self.player.play_phrase().as_ref().map(|(_,p)| p.as_ref().map(|p|p.read().unwrap().color) ).flatten().clone(), true))), ])), Tui::push_x(sampler_w, Fixed::h(1, row!([ - PhraseSelector::play_phrase(&self.sequencer.player), - PhraseSelector::next_phrase(&self.sequencer.player), + PhraseSelector::play_phrase(&self.player), + PhraseSelector::next_phrase(&self.player), ]))), row!([ Tui::pull_y(1, Tui::shrink_y(0, Fill::h(Fixed::w(sampler_w, &self.sampler)))), Tui::split_n(false, 1, - MidiEditStatus(&self.sequencer.editor), + MidiEditStatus(&self.editor), Tui::split_w(false, pool_w, - Tui::pull_y(1, Fill::h(Align::e(PoolView(&self.sequencer.phrases)))), - Fill::wh(&self.sequencer.editor) + Tui::pull_y(1, Fill::h(Align::e(PoolView(&self.phrases)))), + Fill::wh(&self.editor) ) ), ]), @@ -72,23 +83,99 @@ render!(|self:GrooveboxTui|{ }); pub enum GrooveboxCommand { - Sequencer(SequencerCommand), + History(isize), + Clock(ClockCommand), + Pool(PoolCommand), + Editor(PhraseCommand), + Enqueue(Option>>), Sampler(SamplerCommand), } handle!(|self: GrooveboxTui, input|GrooveboxCommand::execute_with_state(self, input)); -input_to_command!(GrooveboxCommand: |state: GrooveboxTui,input|match input.event() { - key_pat!(Up) | key_pat!(Down) | key_pat!(Left) | key_pat!(Right) | - key_pat!(Shift-Char('L')) => - SamplerCommand::input_to_command(&state.sampler, input).map(Self::Sampler)?, - _ => - SequencerCommand::input_to_command(&state.sequencer, input).map(Self::Sequencer)?, +input_to_command!(GrooveboxCommand: |state: GrooveboxTui, input|match input.event() { + // TODO: k: toggle on-screen keyboard + key_pat!(Ctrl-Char('k')) => { + todo!("keyboard") + }, + + // Transport: Play/pause + key_pat!(Char(' ')) => Clock( + if state.clock().is_stopped() { Play(None) } else { Pause(None) } + ), + + // Transport: Play from start or rewind to start + key_pat!(Shift-Char(' ')) => Clock( + if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) } + ), + + // Tab: Toggle visibility of phrase pool column + key_pat!(Tab) => Pool(PoolCommand::Show(!state.phrases.visible)), + // q: Enqueue currently edited phrase + key_pat!(Char('q')) => Enqueue(Some(state.phrases.phrase().clone())), + // 0: Enqueue phrase 0 (stop all) + key_pat!(Char('0')) => Enqueue(Some(state.phrases.phrases()[0].clone())), + + // e: Toggle between editing currently playing or other phrase + key_pat!(Char('e')) => if let Some((_, Some(playing))) = state.player.play_phrase() { + let editing = state.editor.phrase().as_ref().map(|p|p.read().unwrap().clone()); + let selected = state.phrases.phrase().clone(); + Editor(Show(Some(if Some(selected.read().unwrap().clone()) != editing { + selected + } else { + playing.clone() + }))) + } else { + return None + }, + + // For the rest, use the default keybindings of the components. + // The ones defined above supersede them. + _ => if let Some(command) = PhraseCommand::input_to_command(&state.editor, input) { + Editor(command) + } else if let Some(command) = PoolCommand::input_to_command(&state.phrases, input) { + Pool(command) + } else { + return None + } }); command!(|self:GrooveboxCommand,state:GrooveboxTui|match self { - GrooveboxCommand::Sequencer(command) => - command.execute(&mut state.sequencer)?.map(GrooveboxCommand::Sequencer), - GrooveboxCommand::Sampler(command) => - command.execute(&mut state.sampler)?.map(GrooveboxCommand::Sampler), + Self::Pool(cmd) => { + let mut default = |cmd: PoolCommand|cmd + .execute(&mut state.phrases) + .map(|x|x.map(Pool)); + match cmd { + // autoselect: automatically load selected phrase in editor + PoolCommand::Select(_) => { + let undo = default(cmd)?; + state.editor.set_phrase(Some(state.phrases.phrase())); + undo + }, + // update color in all places simultaneously + PoolCommand::Phrase(SetColor(index, _)) => { + let undo = default(cmd)?; + state.editor.set_phrase(Some(state.phrases.phrase())); + undo + }, + _ => default(cmd)? + } + }, + Self::Editor(cmd) => { + let default = ||cmd.execute(&mut state.editor).map(|x|x.map(Editor)); + match cmd { + _ => default()? + } + }, + Self::Clock(cmd) => cmd.execute(state)?.map(Clock), + Self::Enqueue(phrase) => { + state.player.enqueue_next(phrase.as_ref()); + None + }, + Self::History(delta) => { + todo!("undo/redo") + }, + Self::Sampler(command) => { + todo!("sampler") + } }); diff --git a/crates/tek/src/piano_h.rs b/crates/tek/src/piano_h.rs index 78ff8645..be9cb76e 100644 --- a/crates/tek/src/piano_h.rs +++ b/crates/tek/src/piano_h.rs @@ -29,6 +29,8 @@ pub struct PianoHorizontal { point: MidiPointModel, /// The highlight color palette color: ItemPalette, + /// Width of the keyboard + keys_width: u16, } impl PianoHorizontal { @@ -46,7 +48,8 @@ impl PianoHorizontal { phrase: phrase.cloned(), size, range, - color + color, + keys_width: 5 } } } @@ -85,11 +88,11 @@ render!(|self: PianoHorizontal|{ ])), Tui::inset_xy(0, 1, Fill::wh(Bsp::s( Fixed::h(1, Bsp::e( - Fixed::w(keys_width, ""), + Fixed::w(self.keys_width, ""), Fill::w(timeline()), )), Bsp::e( - Fixed::w(keys_width, keys()), + Fixed::w(self.keys_width, keys()), Fill::wh(lay!([ &self.size, Fill::wh(lay!([ diff --git a/crates/tek/src/sequencer.rs b/crates/tek/src/sequencer.rs index 7e6bfc60..791279c9 100644 --- a/crates/tek/src/sequencer.rs +++ b/crates/tek/src/sequencer.rs @@ -93,7 +93,7 @@ handle!(|self:SequencerTui,input|SequencerCommand::execute_with_state(self, #[derive(Clone, Debug)] pub enum SequencerCommand { History(isize), Clock(ClockCommand), - Phrases(PoolCommand), + Pool(PoolCommand), Editor(PhraseCommand), Enqueue(Option>>), } @@ -113,7 +113,7 @@ input_to_command!(SequencerCommand: |state: SequencerTui, input|match input // Shift-U: redo key_pat!(Char('U')) => History( 1), // Tab: Toggle visibility of phrase pool column - key_pat!(Tab) => Phrases(PoolCommand::Show(!state.phrases.visible)), + key_pat!(Tab) => Pool(PoolCommand::Show(!state.phrases.visible)), // q: Enqueue currently edited phrase key_pat!(Char('q')) => Enqueue(Some(state.phrases.phrase().clone())), // 0: Enqueue phrase 0 (stop all) @@ -135,16 +135,16 @@ input_to_command!(SequencerCommand: |state: SequencerTui, input|match input _ => if let Some(command) = PhraseCommand::input_to_command(&state.editor, input) { Editor(command) } else if let Some(command) = PoolCommand::input_to_command(&state.phrases, input) { - Phrases(command) + Pool(command) } else { return None } }); command!(|self: SequencerCommand, state: SequencerTui|match self { - Self::Phrases(cmd) => { + Self::Pool(cmd) => { let mut default = |cmd: PoolCommand|cmd .execute(&mut state.phrases) - .map(|x|x.map(Phrases)); + .map(|x|x.map(Pool)); match cmd { // autoselect: automatically load selected phrase in editor PoolCommand::Select(_) => { diff --git a/crates/tek/src/status/status_groovebox.rs b/crates/tek/src/status/status_groovebox.rs index e92e99cf..a00b30cd 100644 --- a/crates/tek/src/status/status_groovebox.rs +++ b/crates/tek/src/status/status_groovebox.rs @@ -9,14 +9,14 @@ pub struct GrooveboxStatus { pub(crate) playing: bool, } from!(|state:&GrooveboxTui|GrooveboxStatus = { - let samples = state.sequencer.clock.chunk.load(Relaxed); - let rate = state.sequencer.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.sequencer.clock.is_rolling(), - cpu: state.sequencer.perf.percentage().map(|cpu|format!("│{cpu:.01}%")), + playing: state.clock.is_rolling(), + cpu: state.perf.percentage().map(|cpu|format!("│{cpu:.01}%")), size: format!("{}x{}│", width, state.size.h()), } }); diff --git a/crates/tek/src/transport.rs b/crates/tek/src/transport.rs index 32d64d66..629217d1 100644 --- a/crates/tek/src/transport.rs +++ b/crates/tek/src/transport.rs @@ -273,3 +273,102 @@ fn to_seek_command (input: &TuiInput) -> Option { _ => return None, }) } + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +//struct Field(&'static str, String); + +//render!(|self: Field|{ + //Tui::to_east("│", Tui::to_east( + //Tui::bold(true, self.0), + //Tui::bg(Color::Rgb(0, 0, 0), self.1.as_str()), + //)) +//}); + +//pub struct TransportView { + //pub(crate) state: Option, + //pub(crate) selected: Option, + //pub(crate) focused: bool, + //pub(crate) bpm: f64, + //pub(crate) sync: f64, + //pub(crate) quant: f64, + //pub(crate) beat: String, + //pub(crate) msu: String, +//} + ////)?; + ////match *state { + ////Some(TransportState::Rolling) => { + ////add(&row!( + ////"│", + ////TuiStyle::fg("▶ PLAYING", Color::Rgb(0, 255, 0)), + ////format!("│0 (0)"), + ////format!("│00m00s000u"), + ////format!("│00B 0b 00/00") + ////))?; + ////add(&row!("│Now ", row!( + ////format!("│0 (0)"), //sample(chunk) + ////format!("│00m00s000u"), //msu + ////format!("│00B 0b 00/00"), //bbt + ////)))?; + ////}, + ////_ => { + ////add(&row!("│", TuiStyle::fg("⏹ STOPPED", Color::Rgb(255, 128, 0))))?; + ////add(&"")?; + ////} + ////} + ////Ok(()) + ////}).fill_x().bg(Color::Rgb(40, 50, 30)) +////}); + +//impl<'a, T: HasClock> From<&'a T> for TransportView where Option: From<&'a T> { + //fn from (state: &'a T) -> Self { + //let selected = state.into(); + //Self { + //selected, + //focused: selected.is_some(), + //state: Some(state.clock().transport.query_state().unwrap()), + //bpm: state.clock().bpm().get(), + //sync: state.clock().sync.get(), + //quant: state.clock().quant.get(), + //beat: state.clock().playhead.format_beat(), + //msu: state.clock().playhead.usec.format_msu(), + //} + //} +//} + + //row!( + ////selected.wrap(TransportFocus::PlayPause, &play_pause.fixed_xy(10, 3)), + //row!( + //col!( + //Field("SR ", format!("192000")), + //Field("BUF ", format!("1024")), + //Field("LEN ", format!("21300")), + //Field("CPU ", format!("00.0%")) + //), + //col!( + //Field("PUL ", format!("000000000")), + //Field("PPQ ", format!("96")), + //Field("BBT ", format!("00B0b00p")) + //), + //col!( + //Field("SEC ", format!("000000.000")), + //Field("BPM ", format!("000.000")), + //Field("MSU ", format!("00m00s00u")) + //), + //), + //selected.wrap(TransportFocus::Bpm, &Outset::X(1u16, { + //row! { + //"BPM ", + //format!("{}.{:03}", *bpm as usize, (bpm * 1000.0) % 1000.0) + //} + //})), + //selected.wrap(TransportFocus::Sync, &Outset::X(1u16, row! { + //"SYNC ", pulses_to_name(*sync as usize) + //})), + //selected.wrap(TransportFocus::Quant, &Outset::X(1u16, row! { + //"QUANT ", pulses_to_name(*quant as usize) + //})), + //selected.wrap(TransportFocus::Clock, &{ + //row!("B" , beat.as_str(), " T", msu.as_str()).outset_x(1) + //}).align_e().fill_x(), + //).fill_x().bg(Color::Rgb(40, 50, 30)) diff --git a/crates/tek/src/tui.rs b/crates/tek/src/tui.rs index 737ef479..8cd25285 100644 --- a/crates/tek/src/tui.rs +++ b/crates/tek/src/tui.rs @@ -151,102 +151,3 @@ impl Tui { buffer } } - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -//struct Field(&'static str, String); - -//render!(|self: Field|{ - //Tui::to_east("│", Tui::to_east( - //Tui::bold(true, self.0), - //Tui::bg(Color::Rgb(0, 0, 0), self.1.as_str()), - //)) -//}); - -//pub struct TransportView { - //pub(crate) state: Option, - //pub(crate) selected: Option, - //pub(crate) focused: bool, - //pub(crate) bpm: f64, - //pub(crate) sync: f64, - //pub(crate) quant: f64, - //pub(crate) beat: String, - //pub(crate) msu: String, -//} - ////)?; - ////match *state { - ////Some(TransportState::Rolling) => { - ////add(&row!( - ////"│", - ////TuiStyle::fg("▶ PLAYING", Color::Rgb(0, 255, 0)), - ////format!("│0 (0)"), - ////format!("│00m00s000u"), - ////format!("│00B 0b 00/00") - ////))?; - ////add(&row!("│Now ", row!( - ////format!("│0 (0)"), //sample(chunk) - ////format!("│00m00s000u"), //msu - ////format!("│00B 0b 00/00"), //bbt - ////)))?; - ////}, - ////_ => { - ////add(&row!("│", TuiStyle::fg("⏹ STOPPED", Color::Rgb(255, 128, 0))))?; - ////add(&"")?; - ////} - ////} - ////Ok(()) - ////}).fill_x().bg(Color::Rgb(40, 50, 30)) -////}); - -//impl<'a, T: HasClock> From<&'a T> for TransportView where Option: From<&'a T> { - //fn from (state: &'a T) -> Self { - //let selected = state.into(); - //Self { - //selected, - //focused: selected.is_some(), - //state: Some(state.clock().transport.query_state().unwrap()), - //bpm: state.clock().bpm().get(), - //sync: state.clock().sync.get(), - //quant: state.clock().quant.get(), - //beat: state.clock().playhead.format_beat(), - //msu: state.clock().playhead.usec.format_msu(), - //} - //} -//} - - //row!( - ////selected.wrap(TransportFocus::PlayPause, &play_pause.fixed_xy(10, 3)), - //row!( - //col!( - //Field("SR ", format!("192000")), - //Field("BUF ", format!("1024")), - //Field("LEN ", format!("21300")), - //Field("CPU ", format!("00.0%")) - //), - //col!( - //Field("PUL ", format!("000000000")), - //Field("PPQ ", format!("96")), - //Field("BBT ", format!("00B0b00p")) - //), - //col!( - //Field("SEC ", format!("000000.000")), - //Field("BPM ", format!("000.000")), - //Field("MSU ", format!("00m00s00u")) - //), - //), - //selected.wrap(TransportFocus::Bpm, &Outset::X(1u16, { - //row! { - //"BPM ", - //format!("{}.{:03}", *bpm as usize, (bpm * 1000.0) % 1000.0) - //} - //})), - //selected.wrap(TransportFocus::Sync, &Outset::X(1u16, row! { - //"SYNC ", pulses_to_name(*sync as usize) - //})), - //selected.wrap(TransportFocus::Quant, &Outset::X(1u16, row! { - //"QUANT ", pulses_to_name(*quant as usize) - //})), - //selected.wrap(TransportFocus::Clock, &{ - //row!("B" , beat.as_str(), " T", msu.as_str()).outset_x(1) - //}).align_e().fill_x(), - //).fill_x().bg(Color::Rgb(40, 50, 30)) From 120a67ba21525e8ce6102f6b54c707bf0653e9c0 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 28 Dec 2024 14:16:27 +0100 Subject: [PATCH 041/815] autoregister sampler ports --- crates/edn/src/lib.rs | 13 +----------- crates/tek/src/jack/ports.rs | 22 ++++++++++++++++++++ crates/tek/src/sampler.rs | 40 +++++++++++++++++++++--------------- 3 files changed, 47 insertions(+), 28 deletions(-) diff --git a/crates/edn/src/lib.rs b/crates/edn/src/lib.rs index 347f98a0..9b814a17 100644 --- a/crates/edn/src/lib.rs +++ b/crates/edn/src/lib.rs @@ -60,18 +60,7 @@ from_edn!("sampler" => |jack: &Arc>, args| -> crate::Sampler }, _ => panic!("unexpected in sampler {name}: {edn:?}") }); - let midi_in = jack.read().unwrap().client().register_port("in", MidiIn::default())?; - Ok(Self { - jack: jack.clone(), - mapped: samples, - unmapped: Default::default(), - voices: Default::default(), - buffer: Default::default(), - audio_outs: vec![], - output_gain: 0., - midi_in, - name, - }) + Self::new(jack, &name) }); type MidiSample = (Option, Arc>); diff --git a/crates/tek/src/jack/ports.rs b/crates/tek/src/jack/ports.rs index 299f123e..c64e050a 100644 --- a/crates/tek/src/jack/ports.rs +++ b/crates/tek/src/jack/ports.rs @@ -1,5 +1,27 @@ use crate::*; +pub trait RegisterPort { + fn midi_in (&self, name: &str) -> Usually>; + fn midi_out (&self, name: &str) -> Usually>; + fn audio_in (&self, name: &str) -> Usually>; + fn audio_out (&self, name: &str) -> Usually>; +} + +impl RegisterPort for &Arc> { + fn midi_in (&self, name: &str) -> Usually> { + Ok(self.read().unwrap().client().register_port(name, MidiIn::default())?) + } + fn midi_out (&self, name: &str) -> Usually> { + Ok(self.read().unwrap().client().register_port(name, MidiOut::default())?) + } + fn audio_out (&self, name: &str) -> Usually> { + Ok(self.read().unwrap().client().register_port(name, AudioOut::default())?) + } + fn audio_in (&self, name: &str) -> Usually> { + Ok(self.read().unwrap().client().register_port(name, AudioIn::default())?) + } +} + /// Trait for things that may expose JACK ports. pub trait Ports { fn audio_ins(&self) -> Usually>> { diff --git a/crates/tek/src/sampler.rs b/crates/tek/src/sampler.rs index c394b28e..0ffff68a 100644 --- a/crates/tek/src/sampler.rs +++ b/crates/tek/src/sampler.rs @@ -42,10 +42,33 @@ pub struct Sampler { pub unmapped: Vec>>, pub voices: Arc>>, pub midi_in: Port, + pub audio_ins: Vec>, pub audio_outs: Vec>, pub buffer: Vec>, pub output_gain: f32 } +impl Sampler { + pub fn new (jack: &Arc>, name: &str) -> Usually { + Ok(Self { + midi_in: jack.midi_in(&format!("M->{name}"))?, + audio_ins: vec![ + jack.audio_in(&format!("L/{name}"))?, + jack.audio_in(&format!("R/{name}"))? + ], + audio_outs: vec![ + jack.audio_out(&format!("{name}/L"))?, + jack.audio_out(&format!("{name}/R"))?, + ], + jack: jack.clone(), + name: name.into(), + mapped: BTreeMap::new(), + unmapped: vec![], + voices: Arc::new(RwLock::new(vec![])), + buffer: vec![vec![0.0;16384];2], + output_gain: 0.5, + }) + } +} pub struct SamplerTui { pub state: Sampler, pub cursor: (usize, usize), @@ -75,11 +98,6 @@ impl SamplerTui { } } from_jack!(|jack|SamplerTui{ - let midi_in = jack.read().unwrap().client().register_port("in", MidiIn::default())?; - let audio_outs = vec![ - jack.read().unwrap().client().register_port("outL", AudioOut::default())?, - jack.read().unwrap().client().register_port("outR", AudioOut::default())?, - ]; Self { cursor: (0, 0), editing: None, @@ -88,17 +106,7 @@ from_jack!(|jack|SamplerTui{ note_lo: 36.into(), note_pt: 36.into(), color: ItemPalette::from(Color::Rgb(64, 128, 32)), - state: Sampler { - jack: jack.clone(), - name: "Sampler".into(), - mapped: BTreeMap::new(), - unmapped: vec![], - voices: Arc::new(RwLock::new(vec![])), - buffer: vec![vec![0.0;16384];2], - output_gain: 0.5, - midi_in, - audio_outs, - }, + state: Sampler::new(jack, "sampler")?, } }); render!(|self: SamplerTui|{ From 88ed2c160c10bf7c394368b276e58ed67d454f6a Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 28 Dec 2024 15:03:53 +0100 Subject: [PATCH 042/815] suddenly, audio meter --- crates/cli/src/cli_groovebox.rs | 11 +-- crates/tek/src/groovebox.rs | 84 +++++++++++++---------- crates/tek/src/midi.rs | 25 +++++++ crates/tek/src/sampler.rs | 4 +- crates/tek/src/sampler/sampler_audio.rs | 30 ++++++-- crates/tek/src/status/status_groovebox.rs | 6 +- 6 files changed, 105 insertions(+), 55 deletions(-) diff --git a/crates/cli/src/cli_groovebox.rs b/crates/cli/src/cli_groovebox.rs index 49631ec1..04ef82f1 100644 --- a/crates/cli/src/cli_groovebox.rs +++ b/crates/cli/src/cli_groovebox.rs @@ -29,15 +29,8 @@ pub struct GrooveboxCli { } impl GrooveboxCli { fn run (&self) -> Usually<()> { - Tui::run(JackClient::new("tek_groovebox")?.activate_with(|jack|{ - let mut app = tek::GrooveboxTui::try_from(jack)?; - let jack = jack.read().unwrap(); - let midi_in = jack.register_port("i", MidiIn::default())?; - let midi_out = jack.register_port("o", MidiOut::default())?; - app.player.midi_ins.push(midi_in); - app.player.midi_outs.push(midi_out); - Ok(app) - })?)?; + Tui::run(JackClient::new("tek_groovebox")? + .activate_with(|jack|tek::GrooveboxTui::try_from(jack))?)?; Ok(()) } } diff --git a/crates/tek/src/groovebox.rs b/crates/tek/src/groovebox.rs index 64a444fe..2070a41a 100644 --- a/crates/tek/src/groovebox.rs +++ b/crates/tek/src/groovebox.rs @@ -8,51 +8,58 @@ use PhrasePoolCommand::*; pub struct GrooveboxTui { _jack: Arc>, - pub clock: ClockModel, - pub phrases: PoolModel, + pub player: MidiPlayer, + pub pool: PoolModel, pub editor: MidiEditorModel, + pub sampler: crate::sampler::Sampler, + pub size: Measure, pub status: bool, pub note_buf: Vec, pub midi_buf: Vec>>, pub perf: PerfModel, - pub sampler: SamplerTui, } from_jack!(|jack|GrooveboxTui { - let clock = ClockModel::from(jack); + let mut player = MidiPlayer::new(jack, "sequencer")?; let phrase = Arc::new(RwLock::new(MidiClip::new( - "New", true, 4 * clock.timebase.ppq.get() as usize, + "New", true, 4 * player.clock.timebase.ppq.get() as usize, None, Some(ItemColor::random().into()) ))); - let midi_in_1 = jack.read().unwrap().register_port("in1", MidiIn::default())?; - let midi_out = jack.read().unwrap().register_port("out", MidiOut::default())?; - let midi_in_2 = jack.read().unwrap().register_port("in2", MidiIn::default())?; - let audio_in_1 = jack.read().unwrap().register_port("inL", AudioIn::default())?; - let audio_in_2 = jack.read().unwrap().register_port("inR", AudioIn::default())?; - let audio_out_1 = jack.read().unwrap().register_port("out1", AudioOut::default())?; - let audio_out_2 = jack.read().unwrap().register_port("out2", AudioOut::default())?; + player.play_phrase = Some((Moment::zero(&player.clock.timebase), Some(phrase.clone()))); + let pool = crate::pool::PoolModel::from(&phrase); + let editor = crate::midi::MidiEditorModel::from(&phrase); + let sampler = crate::sampler::Sampler::new(jack, "sampler")?; Self { - _jack: jack.clone(), - phrases: PoolModel::from(&phrase), - editor: MidiEditorModel::from(&phrase), - player: MidiPlayer::from((&clock, &phrase)), - sampler: SamplerTui::try_from(jack)?, - size: Measure::new(), - midi_buf: vec![vec![];65536], - note_buf: vec![], - perf: PerfModel::default(), - status: true, - clock, + _jack: jack.clone(), + player, + pool, + editor, + sampler, + size: Measure::new(), + midi_buf: vec![vec![];65536], + note_buf: vec![], + perf: PerfModel::default(), + status: true, } }); -audio!(|self:GrooveboxTui,_client,_process|Control::Continue); -has_clock!(|self:GrooveboxTui|&self.clock); +audio!(|self: GrooveboxTui, client, scope|{ + let t0 = self.perf.get_t0(); + if Control::Quit == ClockAudio(&mut self.player).process(client, scope) { + return Control::Quit + } + if Control::Quit == SamplerAudio(&mut self.sampler).process(client, scope) { + return Control::Quit + } + self.perf.update(t0, scope); + Control::Continue +}); +has_clock!(|self:GrooveboxTui|&self.player.clock); render!(|self:GrooveboxTui|{ let w = self.size.w(); let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; - let pool_w = if self.phrases.visible { phrase_w } else { 0 }; + let pool_w = if self.pool.visible { phrase_w } else { 0 }; let sampler_w = 24; Fill::wh(lay!([ &self.size, @@ -69,12 +76,15 @@ render!(|self:GrooveboxTui|{ PhraseSelector::next_phrase(&self.player), ]))), row!([ - Tui::pull_y(1, Tui::shrink_y(0, Fill::h(Fixed::w(sampler_w, &self.sampler)))), Tui::split_n(false, 1, MidiEditStatus(&self.editor), Tui::split_w(false, pool_w, - Tui::pull_y(1, Fill::h(Align::e(PoolView(&self.phrases)))), - Fill::wh(&self.editor) + Tui::pull_y(1, Fill::h(Align::e(PoolView(&self.pool)))), + col!([ + &format!("L/{:>+10.3}", self.sampler.input_meter[0]), + &format!("R/{:>+10.3}", self.sampler.input_meter[1]), + Fill::wh(&self.editor) + ]) ) ), ]), @@ -110,16 +120,16 @@ input_to_command!(GrooveboxCommand: |state: GrooveboxTui, input|match input ), // Tab: Toggle visibility of phrase pool column - key_pat!(Tab) => Pool(PoolCommand::Show(!state.phrases.visible)), + key_pat!(Tab) => Pool(PoolCommand::Show(!state.pool.visible)), // q: Enqueue currently edited phrase - key_pat!(Char('q')) => Enqueue(Some(state.phrases.phrase().clone())), + key_pat!(Char('q')) => Enqueue(Some(state.pool.phrase().clone())), // 0: Enqueue phrase 0 (stop all) - key_pat!(Char('0')) => Enqueue(Some(state.phrases.phrases()[0].clone())), + key_pat!(Char('0')) => Enqueue(Some(state.pool.phrases()[0].clone())), // e: Toggle between editing currently playing or other phrase key_pat!(Char('e')) => if let Some((_, Some(playing))) = state.player.play_phrase() { let editing = state.editor.phrase().as_ref().map(|p|p.read().unwrap().clone()); - let selected = state.phrases.phrase().clone(); + let selected = state.pool.phrase().clone(); Editor(Show(Some(if Some(selected.read().unwrap().clone()) != editing { selected } else { @@ -133,7 +143,7 @@ input_to_command!(GrooveboxCommand: |state: GrooveboxTui, input|match input // The ones defined above supersede them. _ => if let Some(command) = PhraseCommand::input_to_command(&state.editor, input) { Editor(command) - } else if let Some(command) = PoolCommand::input_to_command(&state.phrases, input) { + } else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) { Pool(command) } else { return None @@ -143,19 +153,19 @@ input_to_command!(GrooveboxCommand: |state: GrooveboxTui, input|match input command!(|self:GrooveboxCommand,state:GrooveboxTui|match self { Self::Pool(cmd) => { let mut default = |cmd: PoolCommand|cmd - .execute(&mut state.phrases) + .execute(&mut state.pool) .map(|x|x.map(Pool)); match cmd { // autoselect: automatically load selected phrase in editor PoolCommand::Select(_) => { let undo = default(cmd)?; - state.editor.set_phrase(Some(state.phrases.phrase())); + state.editor.set_phrase(Some(state.pool.phrase())); undo }, // update color in all places simultaneously PoolCommand::Phrase(SetColor(index, _)) => { let undo = default(cmd)?; - state.editor.set_phrase(Some(state.phrases.phrase())); + state.editor.set_phrase(Some(state.pool.phrase())); undo }, _ => default(cmd)? diff --git a/crates/tek/src/midi.rs b/crates/tek/src/midi.rs index c242ed61..da2f3018 100644 --- a/crates/tek/src/midi.rs +++ b/crates/tek/src/midi.rs @@ -126,6 +126,31 @@ pub struct MidiPlayer { /// MIDI output buffer pub note_buf: Vec, } +impl MidiPlayer { + pub fn new (jack: &Arc>, name: &str) -> Usually { + Ok(Self { + clock: ClockModel::from(jack), + play_phrase: None, + next_phrase: None, + recording: false, + monitoring: false, + overdub: false, + + notes_in: RwLock::new([false;128]).into(), + midi_ins: vec![ + jack.midi_in(&format!("M/{name}"))?, + ], + + midi_outs: vec![ + jack.midi_out(&format!("{name}/M"))?, + ], + notes_out: RwLock::new([false;128]).into(), + reset: true, + + note_buf: vec![0;8], + }) + } +} impl std::fmt::Debug for MidiPlayer { fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { f.debug_struct("MidiPlayer") diff --git a/crates/tek/src/sampler.rs b/crates/tek/src/sampler.rs index 0ffff68a..195b3109 100644 --- a/crates/tek/src/sampler.rs +++ b/crates/tek/src/sampler.rs @@ -43,6 +43,7 @@ pub struct Sampler { pub voices: Arc>>, pub midi_in: Port, pub audio_ins: Vec>, + pub input_meter: Vec, pub audio_outs: Vec>, pub buffer: Vec>, pub output_gain: f32 @@ -50,11 +51,12 @@ pub struct Sampler { impl Sampler { pub fn new (jack: &Arc>, name: &str) -> Usually { Ok(Self { - midi_in: jack.midi_in(&format!("M->{name}"))?, + midi_in: jack.midi_in(&format!("M/{name}"))?, audio_ins: vec![ jack.audio_in(&format!("L/{name}"))?, jack.audio_in(&format!("R/{name}"))? ], + input_meter: vec![0.0;2], audio_outs: vec![ jack.audio_out(&format!("{name}/L"))?, jack.audio_out(&format!("{name}/R"))?, diff --git a/crates/tek/src/sampler/sampler_audio.rs b/crates/tek/src/sampler/sampler_audio.rs index 60efa41f..fac91ba6 100644 --- a/crates/tek/src/sampler/sampler_audio.rs +++ b/crates/tek/src/sampler/sampler_audio.rs @@ -1,15 +1,35 @@ use crate::*; -audio!(|self: SamplerTui, _client, scope|{ - self.state.process_midi_in(scope); - self.state.clear_output_buffer(); - self.state.process_audio_out(scope); - self.state.write_output_buffer(scope); +audio!(|self: SamplerTui, client, scope|{ + SamplerAudio(&mut self.state).process(client, scope) +}); + +pub struct SamplerAudio<'a>(pub &'a mut Sampler); + +audio!(|self: SamplerAudio<'a>, _client, scope|{ + self.0.process_midi_in(scope); + self.0.clear_output_buffer(); + self.0.process_audio_out(scope); + self.0.write_output_buffer(scope); + self.0.update_input_meter(scope); Control::Continue }); impl Sampler { + pub fn update_input_meter (&mut self, scope: &ProcessScope) { + let Sampler { audio_ins, input_meter, .. } = self; + if audio_ins.len() != input_meter.len() { + *input_meter = vec![0.0;audio_ins.len()]; + } + for (input, meter) in audio_ins.iter().zip(input_meter) { + let slice = input.as_slice(scope); + let total: f32 = slice.iter().map(|x|x.abs()).sum(); + let count = slice.len() as f32; + *meter = 10. * (total / count).log10(); + } + } + /// Create [Voice]s from [Sample]s in response to MIDI input. pub fn process_midi_in (&mut self, scope: &ProcessScope) { let Sampler { midi_in, mapped, voices, .. } = self; diff --git a/crates/tek/src/status/status_groovebox.rs b/crates/tek/src/status/status_groovebox.rs index a00b30cd..a10b9b92 100644 --- a/crates/tek/src/status/status_groovebox.rs +++ b/crates/tek/src/status/status_groovebox.rs @@ -9,13 +9,13 @@ pub struct GrooveboxStatus { pub(crate) playing: bool, } from!(|state:&GrooveboxTui|GrooveboxStatus = { - 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}%")), size: format!("{}x{}│", width, state.size.h()), } From bcdb5f51f5897d99ca7f32825af01422679b8873 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 28 Dec 2024 15:32:53 +0100 Subject: [PATCH 043/815] update Justfile --- Justfile | 17 +++++++++++-- crates/cli/src/cli_groovebox.rs | 45 ++++++++++++++++++++++----------- crates/cli/src/cli_sequencer.rs | 5 +--- crates/cli/src/lib.rs | 24 ++++++++++++++++++ 4 files changed, 70 insertions(+), 21 deletions(-) diff --git a/Justfile b/Justfile index a6ff44fe..4223d64b 100644 --- a/Justfile +++ b/Justfile @@ -63,19 +63,32 @@ groovebox: groovebox-release: reset cargo run --release --bin tek_groovebox +groovebox-release-ext: + reset + cargo run --release --bin tek_groovebox -- -n tek \ + -i "Midi-Bridge:nanoKEY Studio 1:(capture_0) nanoKEY Studio nanoKEY Studio _" \ + -o "Midi-Bridge:Komplete Audio 6 0:(playback_0) Komplete Audio 6 MIDI 1" \ + -l "Komplete Audio 6 Pro:capture_AUX1" \ + -r "Komplete Audio 6 Pro:capture_AUX1" \ + -L "Komplete Audio 6 Pro:playback_AUX1" \ + -R "Komplete Audio 6 Pro:playback_AUX1" sequencer: reset cargo run --bin tek_sequencer sequencer-ext: reset - cargo run --bin tek_sequencer -- -i "Midi-Bridge:nanoKEY Studio 2:(capture_0) nanoKEY Studio nanoKEY Studio _" -o "Midi-Bridge:Komplete Audio 6 1:(playback_0) Komplete Audio 6 MIDI 1" + cargo run --bin tek_sequencer -- \ + -i "Midi-Bridge:nanoKEY Studio 1:(capture_0) nanoKEY Studio nanoKEY Studio _" \ + -o "Midi-Bridge:Komplete Audio 6 0:(playback_0) Komplete Audio 6 MIDI 1" sequencer-release: reset cargo run --release --bin tek_sequencer sequencer-release-ext: reset - cargo run --release --bin tek_sequencer -- -i "Midi-Bridge:nanoKEY Studio 2:(capture_0) nanoKEY Studio nanoKEY Studio _" -o "Midi-Bridge:Komplete Audio 6 1:(playback_0) Komplete Audio 6 MIDI 1" + cargo run --release --bin tek_sequencer -- \ + -i "Midi-Bridge:nanoKEY Studio 1:(capture_0) nanoKEY Studio nanoKEY Studio _" \ + -o "Midi-Bridge:Komplete Audio 6 0:(playback_0) Komplete Audio 6 MIDI 1" mixer: reset diff --git a/crates/cli/src/cli_groovebox.rs b/crates/cli/src/cli_groovebox.rs index 04ef82f1..e175d59e 100644 --- a/crates/cli/src/cli_groovebox.rs +++ b/crates/cli/src/cli_groovebox.rs @@ -6,31 +6,46 @@ pub struct GrooveboxCli { /// Name of JACK client #[arg(short, long)] name: Option, - /// Whether to include a transport toolbar (default: true) #[arg(short, long, default_value_t = true)] transport: bool, - - /// MIDI outs to connect to (multiple accepted) + /// MIDI outs to connect to MIDI input #[arg(short='i', long)] midi_from: Vec, - - /// MIDI ins to connect to (multiple accepted) + /// MIDI ins to connect from MIDI output #[arg(short='o', long)] midi_to: Vec, - - /// Audio ins to connect to (multiple accepted) - #[arg(short='I', long)] - audio_from: Vec, - - /// Audio outs to connect to (multiple accepted) - #[arg(short='O', long)] - audio_to: Vec, + /// Audio outs to connect to left input + #[arg(short='l', long)] + l_from: Vec, + /// Audio outs to connect to right input + #[arg(short='r', long)] + r_from: Vec, + /// Audio ins to connect from left output + #[arg(short='L', long)] + l_to: Vec, + /// Audio ins to connect from right output + #[arg(short='R', long)] + r_to: Vec, } impl GrooveboxCli { fn run (&self) -> Usually<()> { - Tui::run(JackClient::new("tek_groovebox")? - .activate_with(|jack|tek::GrooveboxTui::try_from(jack))?)?; + Tui::run(JackClient::new("tek_groovebox")?.activate_with(|jack|{ + let app = tek::GrooveboxTui::try_from(jack)?; + let jack = jack.read().unwrap(); + + jack.client().connect_ports(&app.player.midi_outs[0], &app.sampler.midi_in)?; + + connect_from(&jack, &app.player.midi_ins[0], &self.midi_from)?; + connect_to(&jack, &app.player.midi_outs[0], &self.midi_to)?; + + connect_audio_from(&jack, &app.sampler.audio_ins[0], &self.l_from)?; + connect_audio_from(&jack, &app.sampler.audio_ins[1], &self.r_from)?; + connect_audio_to(&jack, &app.sampler.audio_outs[0], &self.l_to)?; + connect_audio_to(&jack, &app.sampler.audio_outs[1], &self.r_to)?; + + Ok(app) + })?)?; Ok(()) } } diff --git a/crates/cli/src/cli_sequencer.rs b/crates/cli/src/cli_sequencer.rs index 247282f7..e8474507 100644 --- a/crates/cli/src/cli_sequencer.rs +++ b/crates/cli/src/cli_sequencer.rs @@ -11,15 +11,12 @@ pub struct SequencerCli { /// Name of JACK client #[arg(short, long)] name: Option, - /// Whether to include a transport toolbar (default: true) #[arg(short, long, default_value_t = true)] transport: bool, - /// MIDI outs to connect to (multiple instances accepted) #[arg(short='i', long)] midi_from: Vec, - /// MIDI ins to connect to (multiple instances accepted) #[arg(short='o', long)] midi_to: Vec, @@ -33,7 +30,7 @@ impl SequencerCli { let jack = jack.read().unwrap(); let midi_in = jack.register_port("i", MidiIn::default())?; let midi_out = jack.register_port("o", MidiOut::default())?; - connect_from(&jack, &midi_in, &self.midi_from)?; + connect_from(&jack, &midi_in, &self.midi_from)?; connect_to(&jack, &midi_out, &self.midi_to)?; app.player.midi_ins.push(midi_in); app.player.midi_outs.push(midi_out); diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs index e2d6822e..538ecfe7 100644 --- a/crates/cli/src/lib.rs +++ b/crates/cli/src/lib.rs @@ -25,3 +25,27 @@ fn connect_to (jack: &JackClient, output: &Port, ports: &[String]) -> U } Ok(()) } + +#[allow(unused)] +fn connect_audio_from (jack: &JackClient, input: &Port, ports: &[String]) -> Usually<()> { + for port in ports.iter() { + if let Some(port) = jack.port_by_name(port).as_ref() { + jack.client().connect_ports(port, input)?; + } else { + panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); + } + } + Ok(()) +} + +#[allow(unused)] +fn connect_audio_to (jack: &JackClient, output: &Port, ports: &[String]) -> Usually<()> { + for port in ports.iter() { + if let Some(port) = jack.port_by_name(port).as_ref() { + jack.client().connect_ports(output, port)?; + } else { + panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); + } + } + Ok(()) +} From 48f341ba2c9c79a86f8f9e20da9325fcacfe49b0 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 28 Dec 2024 16:50:35 +0100 Subject: [PATCH 044/815] oh no, begin to implement sampling --- Justfile | 9 +++++ crates/tek/src/groovebox.rs | 22 ++++++++--- crates/tek/src/sampler/sampler_control.rs | 10 ++++- crates/tek/src/space.rs | 45 +++++++++++++++++++++++ crates/tek/src/space/stack.rs | 45 ----------------------- 5 files changed, 78 insertions(+), 53 deletions(-) diff --git a/Justfile b/Justfile index 4223d64b..c657edd1 100644 --- a/Justfile +++ b/Justfile @@ -60,6 +60,15 @@ arranger-release-ext: groovebox: reset cargo run --bin tek_groovebox +groovebox-ext: + reset + cargo run --bin tek_groovebox -- -n tek \ + -i "Midi-Bridge:nanoKEY Studio 1:(capture_0) nanoKEY Studio nanoKEY Studio _" \ + -o "Midi-Bridge:Komplete Audio 6 0:(playback_0) Komplete Audio 6 MIDI 1" \ + -l "Komplete Audio 6 Pro:capture_AUX1" \ + -r "Komplete Audio 6 Pro:capture_AUX1" \ + -L "Komplete Audio 6 Pro:playback_AUX1" \ + -R "Komplete Audio 6 Pro:playback_AUX1" groovebox-release: reset cargo run --release --bin tek_groovebox diff --git a/crates/tek/src/groovebox.rs b/crates/tek/src/groovebox.rs index 2070a41a..a9bec7c6 100644 --- a/crates/tek/src/groovebox.rs +++ b/crates/tek/src/groovebox.rs @@ -60,7 +60,7 @@ render!(|self:GrooveboxTui|{ let w = self.size.w(); let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; let pool_w = if self.pool.visible { phrase_w } else { 0 }; - let sampler_w = 24; + let sampler_w = 11; Fill::wh(lay!([ &self.size, Fill::wh(Align::s(Fixed::h(2, GrooveboxStatus::from(self)))), @@ -80,11 +80,17 @@ render!(|self:GrooveboxTui|{ MidiEditStatus(&self.editor), Tui::split_w(false, pool_w, Tui::pull_y(1, Fill::h(Align::e(PoolView(&self.pool)))), - col!([ - &format!("L/{:>+10.3}", self.sampler.input_meter[0]), - &format!("R/{:>+10.3}", self.sampler.input_meter[1]), - Fill::wh(&self.editor) - ]) + Tui::split_e(false, sampler_w, Fill::wh(col!([ + &format!("L/{:>+9.3}", self.sampler.input_meter[0]), + &format!("R/{:>+9.3}", self.sampler.input_meter[1]), + Fill::wh(col!(note in (self.editor.note_lo().load(Relaxed)..=self.editor.note_hi()).rev() => { + if self.sampler.mapped.contains_key(&u7::from(note as u8)) { + todo!() + } else { + Tui::fg(TuiTheme::g(96), format!("{note:3} (none)")) + } + })), + ])), Fill::h(&self.editor)) ) ), ]), @@ -126,6 +132,10 @@ input_to_command!(GrooveboxCommand: |state: GrooveboxTui, input|match input // 0: Enqueue phrase 0 (stop all) key_pat!(Char('0')) => Enqueue(Some(state.pool.phrases()[0].clone())), + key_pat!(Shift-Char('R')) => Sampler( + SamplerCommand::Record(u7::from(state.editor.note_point() as u8)) + ), + // e: Toggle between editing currently playing or other phrase key_pat!(Char('e')) => if let Some((_, Some(playing))) = state.player.play_phrase() { let editing = state.editor.phrase().as_ref().map(|p|p.read().unwrap().clone()); diff --git a/crates/tek/src/sampler/sampler_control.rs b/crates/tek/src/sampler/sampler_control.rs index 0a8d9c63..63107481 100644 --- a/crates/tek/src/sampler/sampler_control.rs +++ b/crates/tek/src/sampler/sampler_control.rs @@ -8,10 +8,11 @@ pub enum SamplerCommand { SelectNote(usize), SelectField(usize), SetName(String), - SetNote(u7, Arc>), + SetNote(u7, Option>>), SetGain(f32), NoteOn(u7, u7), - NoteOff(u7) + NoteOff(u7), + Record(u7), } input_to_command!(SamplerCommand: |state: SamplerTui, input|match state.mode { @@ -62,5 +63,10 @@ command!(|self:SamplerCommand,state:SamplerTui|match self { state.set_note_point(index); Some(Self::SelectNote(old)) }, + Self::Record(index) => { + let old = state.state.mapped.get(&index).cloned(); + + Some(Self::SetNote(index, old)) + }, _ => todo!() }); diff --git a/crates/tek/src/space.rs b/crates/tek/src/space.rs index 7b6a624b..5629081b 100644 --- a/crates/tek/src/space.rs +++ b/crates/tek/src/space.rs @@ -189,3 +189,48 @@ impl Area for [N;4] { #[inline] fn w (&self) -> N { self[2] } #[inline] fn h (&self) -> N { self[3] } } + +#[macro_export] macro_rules! col { + ([$($expr:expr),* $(,)?]) => { + Stack::down(move|add|{ $(add(&$expr)?;)* Ok(()) }) + }; + (![$($expr:expr),* $(,)?]) => { + Stack::down(|add|{ $(add(&$expr)?;)* Ok(()) }) + }; + ($expr:expr) => { + Stack::down($expr) + }; + ($pat:pat in $collection:expr => $item:expr) => { + Stack::down(move|add|{ for $pat in $collection { add(&$item)?; } Ok(()) }) + }; +} + +#[macro_export] macro_rules! col_up { + ([$($expr:expr),* $(,)?]) => { + Stack::up(move|add|{ $(add(&$expr)?;)* Ok(()) }) + }; + (![$($expr:expr),* $(,)?]) => { + Stack::up(|add|{ $(add(&$expr)?;)* Ok(()) }) + }; + ($expr:expr) => { + Stack::up(expr) + }; + ($pat:pat in $collection:expr => $item:expr) => { + Stack::up(move |add|{ for $pat in $collection { add(&$item)?; } Ok(()) }) + }; +} + +#[macro_export] macro_rules! row { + ([$($expr:expr),* $(,)?]) => { + Stack::right(move|add|{ $(add(&$expr)?;)* Ok(()) }) + }; + (![$($expr:expr),* $(,)?]) => { + Stack::right(|add|{ $(add(&$expr)?;)* Ok(()) }) + }; + ($expr:expr) => { + Stack::right($expr) + }; + ($pat:pat in $collection:expr => $item:expr) => { + Stack::right(move|add|{ for $pat in $collection { add(&$item)?; } Ok(()) }) + }; +} diff --git a/crates/tek/src/space/stack.rs b/crates/tek/src/space/stack.rs index a48f5a5a..40a5142c 100644 --- a/crates/tek/src/space/stack.rs +++ b/crates/tek/src/space/stack.rs @@ -1,51 +1,6 @@ use crate::*; use Direction::*; -#[macro_export] macro_rules! col { - ([$($expr:expr),* $(,)?]) => { - Stack::down(move|add|{ $(add(&$expr)?;)* Ok(()) }) - }; - (![$($expr:expr),* $(,)?]) => { - Stack::down(|add|{ $(add(&$expr)?;)* Ok(()) }) - }; - ($expr:expr) => { - Stack::down($expr) - }; - ($pat:pat in $collection:expr => $item:expr) => { - Stack::down(move|add|{ for $pat in $collection { add(&$item)?; } Ok(()) }) - }; -} - -#[macro_export] macro_rules! col_up { - ([$($expr:expr),* $(,)?]) => { - Stack::up(move|add|{ $(add(&$expr)?;)* Ok(()) }) - }; - (![$($expr:expr),* $(,)?]) => { - Stack::up(|add|{ $(add(&$expr)?;)* Ok(()) }) - }; - ($expr:expr) => { - Stack::up(expr) - }; - ($pat:pat in $collection:expr => $item:expr) => { - Stack::up(move |add|{ for $pat in $collection { add(&$item)?; } Ok(()) }) - }; -} - -#[macro_export] macro_rules! row { - ([$($expr:expr),* $(,)?]) => { - Stack::right(move|add|{ $(add(&$expr)?;)* Ok(()) }) - }; - (![$($expr:expr),* $(,)?]) => { - Stack::right(|add|{ $(add(&$expr)?;)* Ok(()) }) - }; - ($expr:expr) => { - Stack::right($expr) - }; - ($pat:pat in $collection:expr => $item:expr) => { - Stack::right(move|add|{ for $pat in $collection { add(&$item)?; } Ok(()) }) - }; -} - pub struct Stack< E: Engine, F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> From 2feb21bd1fedaf621c5f4d9ba3627866f8502879 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 28 Dec 2024 17:06:19 +0100 Subject: [PATCH 045/815] simplify sample mapping --- crates/tek/src/groovebox.rs | 18 ++++++------ crates/tek/src/sampler.rs | 8 +++--- crates/tek/src/sampler/sampler_audio.rs | 2 +- crates/tek/src/sampler/sampler_control.rs | 35 ++++++++++++++++------- 4 files changed, 40 insertions(+), 23 deletions(-) diff --git a/crates/tek/src/groovebox.rs b/crates/tek/src/groovebox.rs index a9bec7c6..86649d19 100644 --- a/crates/tek/src/groovebox.rs +++ b/crates/tek/src/groovebox.rs @@ -61,6 +61,7 @@ render!(|self:GrooveboxTui|{ let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; let pool_w = if self.pool.visible { phrase_w } else { 0 }; let sampler_w = 11; + let note_pt = self.editor.note_point(); Fill::wh(lay!([ &self.size, Fill::wh(Align::s(Fixed::h(2, GrooveboxStatus::from(self)))), @@ -84,11 +85,14 @@ render!(|self:GrooveboxTui|{ &format!("L/{:>+9.3}", self.sampler.input_meter[0]), &format!("R/{:>+9.3}", self.sampler.input_meter[1]), Fill::wh(col!(note in (self.editor.note_lo().load(Relaxed)..=self.editor.note_hi()).rev() => { - if self.sampler.mapped.contains_key(&u7::from(note as u8)) { - todo!() - } else { - Tui::fg(TuiTheme::g(96), format!("{note:3} (none)")) - } + Tui::bg( + if note == note_pt { TuiTheme::g(64) } else { Color::Reset }, + if let Some(sample) = &self.sampler.mapped[note] { + todo!() + } else { + Tui::fg(TuiTheme::g(160), format!("{note:3} (none)")) + } + ) })), ])), Fill::h(&self.editor)) ) @@ -188,6 +192,7 @@ command!(|self:GrooveboxCommand,state:GrooveboxTui|match self { } }, Self::Clock(cmd) => cmd.execute(state)?.map(Clock), + Self::Sampler(cmd) => cmd.execute(&mut state.sampler)?.map(Sampler), Self::Enqueue(phrase) => { state.player.enqueue_next(phrase.as_ref()); None @@ -195,7 +200,4 @@ command!(|self:GrooveboxCommand,state:GrooveboxTui|match self { Self::History(delta) => { todo!("undo/redo") }, - Self::Sampler(command) => { - todo!("sampler") - } }); diff --git a/crates/tek/src/sampler.rs b/crates/tek/src/sampler.rs index 195b3109..d692b955 100644 --- a/crates/tek/src/sampler.rs +++ b/crates/tek/src/sampler.rs @@ -38,7 +38,7 @@ pub(crate) use self::sample_import::*; pub struct Sampler { pub jack: Arc>, pub name: String, - pub mapped: BTreeMap>>, + pub mapped: [Option>>;128], pub unmapped: Vec>>, pub voices: Arc>>, pub midi_in: Port, @@ -63,7 +63,7 @@ impl Sampler { ], jack: jack.clone(), name: name.into(), - mapped: BTreeMap::new(), + mapped: [const { None };128], unmapped: vec![], voices: Arc::new(RwLock::new(vec![])), buffer: vec![vec![0.0;16384];2], @@ -86,9 +86,9 @@ pub struct SamplerTui { impl SamplerTui { /// Immutable reference to sample at cursor. pub fn sample (&self) -> Option<&Arc>> { - for (i, sample) in self.state.mapped.values().enumerate() { + for (i, sample) in self.state.mapped.iter().enumerate() { if i == self.cursor.0 { - return Some(sample) + return sample.as_ref() } } for (i, sample) in self.state.unmapped.iter().enumerate() { diff --git a/crates/tek/src/sampler/sampler_audio.rs b/crates/tek/src/sampler/sampler_audio.rs index fac91ba6..8b9ca5e4 100644 --- a/crates/tek/src/sampler/sampler_audio.rs +++ b/crates/tek/src/sampler/sampler_audio.rs @@ -37,7 +37,7 @@ impl Sampler { if let LiveEvent::Midi { message: MidiMessage::NoteOn { ref key, ref vel }, .. } = LiveEvent::parse(bytes).unwrap() { - if let Some(sample) = mapped.get(key) { + if let Some(ref sample) = mapped[key.as_int() as usize] { voices.write().unwrap().push(Sample::play(sample, time as usize, vel)); } } diff --git a/crates/tek/src/sampler/sampler_control.rs b/crates/tek/src/sampler/sampler_control.rs index 63107481..7963fcad 100644 --- a/crates/tek/src/sampler/sampler_control.rs +++ b/crates/tek/src/sampler/sampler_control.rs @@ -1,21 +1,24 @@ use crate::*; use KeyCode::Char; -handle!(|self:SamplerTui,input|SamplerCommand::execute_with_state(self, input)); +handle!(|self: SamplerTui, input|SamplerTuiCommand::execute_with_state(self, input)); -pub enum SamplerCommand { +pub enum SamplerTuiCommand { Import(FileBrowserCommand), SelectNote(usize), SelectField(usize), - SetName(String), - SetNote(u7, Option>>), + Sample(SamplerCommand), +} + +pub enum SamplerCommand { + Record(u7), + SetSample(u7, Option>>), SetGain(f32), NoteOn(u7, u7), NoteOff(u7), - Record(u7), } -input_to_command!(SamplerCommand: |state: SamplerTui, input|match state.mode { +input_to_command!(SamplerTuiCommand: |state: SamplerTui, input|match state.mode { Some(SamplerMode::Import(..)) => Self::Import( FileBrowserCommand::input_to_command(state, input)? ), @@ -51,7 +54,8 @@ input_to_command!(SamplerCommand: |state: SamplerTui, input|match state.mod //} //} }); -command!(|self:SamplerCommand,state:SamplerTui|match self { + +command!(|self: SamplerTuiCommand, state: SamplerTui|match self { Self::Import(FileBrowserCommand::Begin) => { let voices = &state.state.voices; let sample = Arc::new(RwLock::new(Sample::new("", 0, 0, vec![]))); @@ -63,10 +67,21 @@ command!(|self:SamplerCommand,state:SamplerTui|match self { state.set_note_point(index); Some(Self::SelectNote(old)) }, + Self::Sample(cmd) => cmd.execute(&mut state.state)?.map(Self::Sample), + _ => todo!() +}); + +command!(|self: SamplerCommand, state: Sampler|match self { + Self::SetSample(index, sample) => { + let i = index.as_int() as usize; + let old = state.mapped[i].clone(); + state.mapped[i] = sample; + Some(Self::SetSample(index, old)) + }, Self::Record(index) => { - let old = state.state.mapped.get(&index).cloned(); - - Some(Self::SetNote(index, old)) + let i = index.as_int() as usize; + let old = state.mapped[i].clone(); + Some(Self::SetSample(index, old)) }, _ => todo!() }); From ae3099847a564d5d0fd8964bfccbaa6bf6cfd7da Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 28 Dec 2024 17:57:34 +0100 Subject: [PATCH 046/815] highlight recorded sample --- crates/tek/src/groovebox.rs | 45 ++++++++++++++++------- crates/tek/src/sampler.rs | 31 ++++++++++++++++ crates/tek/src/sampler/sampler_control.rs | 19 +++++++--- 3 files changed, 76 insertions(+), 19 deletions(-) diff --git a/crates/tek/src/groovebox.rs b/crates/tek/src/groovebox.rs index 86649d19..3266a170 100644 --- a/crates/tek/src/groovebox.rs +++ b/crates/tek/src/groovebox.rs @@ -61,7 +61,6 @@ render!(|self:GrooveboxTui|{ let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; let pool_w = if self.pool.visible { phrase_w } else { 0 }; let sampler_w = 11; - let note_pt = self.editor.note_point(); Fill::wh(lay!([ &self.size, Fill::wh(Align::s(Fixed::h(2, GrooveboxStatus::from(self)))), @@ -82,18 +81,8 @@ render!(|self:GrooveboxTui|{ Tui::split_w(false, pool_w, Tui::pull_y(1, Fill::h(Align::e(PoolView(&self.pool)))), Tui::split_e(false, sampler_w, Fill::wh(col!([ - &format!("L/{:>+9.3}", self.sampler.input_meter[0]), - &format!("R/{:>+9.3}", self.sampler.input_meter[1]), - Fill::wh(col!(note in (self.editor.note_lo().load(Relaxed)..=self.editor.note_hi()).rev() => { - Tui::bg( - if note == note_pt { TuiTheme::g(64) } else { Color::Reset }, - if let Some(sample) = &self.sampler.mapped[note] { - todo!() - } else { - Tui::fg(TuiTheme::g(160), format!("{note:3} (none)")) - } - ) - })), + Meters(self.sampler.input_meter.as_ref()), + GrooveboxSamples(self), ])), Fill::h(&self.editor)) ) ), @@ -102,6 +91,34 @@ render!(|self:GrooveboxTui|{ ])) }); +struct Meters<'a>(&'a[f32]); +render!(|self: Meters<'a>|col!([ + &format!("L/{:>+9.3}", self.0[0]), + &format!("R/{:>+9.3}", self.0[1]), +])); + +struct GrooveboxSamples<'a>(&'a GrooveboxTui); +render!(|self: GrooveboxSamples<'a>|{ + let note_lo = self.0.editor.note_lo().load(Relaxed); + let note_pt = self.0.editor.note_point(); + let note_hi = self.0.editor.note_hi(); + Fill::wh(col!(note in (note_lo..=note_hi).rev() => { + let mut bg = if note == note_pt { TuiTheme::g(64) } else { Color::Reset }; + let mut fg = TuiTheme::g(160); + if let Some((index, _)) = self.0.sampler.recording { + if note == index { + bg = Color::Rgb(64,16,0); + fg = Color::Rgb(224,64,32) + } + } + Tui::bg(bg, if let Some(sample) = &self.0.sampler.mapped[note] { + todo!() + } else { + Tui::fg(fg, format!("{note:3} (none) ")) + }) + })) +}); + pub enum GrooveboxCommand { History(isize), Clock(ClockCommand), @@ -137,7 +154,7 @@ input_to_command!(GrooveboxCommand: |state: GrooveboxTui, input|match input key_pat!(Char('0')) => Enqueue(Some(state.pool.phrases()[0].clone())), key_pat!(Shift-Char('R')) => Sampler( - SamplerCommand::Record(u7::from(state.editor.note_point() as u8)) + SamplerCommand::RecordBegin(u7::from(state.editor.note_point() as u8)) ), // e: Toggle between editing currently playing or other phrase diff --git a/crates/tek/src/sampler.rs b/crates/tek/src/sampler.rs index d692b955..85434e82 100644 --- a/crates/tek/src/sampler.rs +++ b/crates/tek/src/sampler.rs @@ -39,6 +39,7 @@ pub struct Sampler { pub jack: Arc>, pub name: String, pub mapped: [Option>>;128], + pub recording: Option<(usize, Sample)>, pub unmapped: Vec>>, pub voices: Arc>>, pub midi_in: Port, @@ -68,8 +69,38 @@ impl Sampler { voices: Arc::new(RwLock::new(vec![])), buffer: vec![vec![0.0;16384];2], output_gain: 0.5, + recording: None, }) } + pub fn cancel_recording (&mut self) { + self.recording = None; + } + pub fn begin_recording (&mut self, index: usize) { + self.recording = Some( + (index, Sample::new("(new)", 0, 0, vec![vec![];self.audio_ins.len()])) + ); + } + pub fn finish_recording (&mut self) -> Option>> { + let recording = self.recording.take(); + if let Some((index, sample)) = recording { + let old = self.mapped[index].clone(); + self.mapped[index] = Some(Arc::new(RwLock::new(sample))); + old + } else { + None + } + } + pub fn record_chunk (&mut self, chunks: &[&[f32]]) { + if let Some((_, sample)) = &mut self.recording { + if chunks.len() != sample.channels.len() { + panic!() + } + for (chunk, channel) in chunks.iter().zip(sample.channels.iter_mut()) { + channel.extend_from_slice(chunk) + } + sample.end += chunks[0].len(); + } + } } pub struct SamplerTui { pub state: Sampler, diff --git a/crates/tek/src/sampler/sampler_control.rs b/crates/tek/src/sampler/sampler_control.rs index 7963fcad..6e490fef 100644 --- a/crates/tek/src/sampler/sampler_control.rs +++ b/crates/tek/src/sampler/sampler_control.rs @@ -11,7 +11,9 @@ pub enum SamplerTuiCommand { } pub enum SamplerCommand { - Record(u7), + RecordBegin(u7), + RecordCancel, + RecordFinish, SetSample(u7, Option>>), SetGain(f32), NoteOn(u7, u7), @@ -78,10 +80,17 @@ command!(|self: SamplerCommand, state: Sampler|match self { state.mapped[i] = sample; Some(Self::SetSample(index, old)) }, - Self::Record(index) => { - let i = index.as_int() as usize; - let old = state.mapped[i].clone(); - Some(Self::SetSample(index, old)) + Self::RecordBegin(index) => { + state.begin_recording(index.as_int() as usize); + None + }, + Self::RecordCancel => { + state.cancel_recording(); + None + }, + Self::RecordFinish => { + state.finish_recording(); + None }, _ => todo!() }); From 97920d706305a1f3edbad607211014ee071fbed6 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 28 Dec 2024 18:06:17 +0100 Subject: [PATCH 047/815] record sample (y no playback?) --- Justfile | 2 +- crates/tek/src/groovebox.rs | 8 +++--- crates/tek/src/sampler/sampler_audio.rs | 33 +++++++++++++++++++------ 3 files changed, 31 insertions(+), 12 deletions(-) diff --git a/Justfile b/Justfile index c657edd1..83b7e30f 100644 --- a/Justfile +++ b/Justfile @@ -80,7 +80,7 @@ groovebox-release-ext: -l "Komplete Audio 6 Pro:capture_AUX1" \ -r "Komplete Audio 6 Pro:capture_AUX1" \ -L "Komplete Audio 6 Pro:playback_AUX1" \ - -R "Komplete Audio 6 Pro:playback_AUX1" + -R "Komplete Audio 6 Pro:playback_AUX2" sequencer: reset diff --git a/crates/tek/src/groovebox.rs b/crates/tek/src/groovebox.rs index 3266a170..97e7e3c6 100644 --- a/crates/tek/src/groovebox.rs +++ b/crates/tek/src/groovebox.rs @@ -112,7 +112,7 @@ render!(|self: GrooveboxSamples<'a>|{ } } Tui::bg(bg, if let Some(sample) = &self.0.sampler.mapped[note] { - todo!() + Tui::fg(fg, format!("{note:3} ????? ")) } else { Tui::fg(fg, format!("{note:3} (none) ")) }) @@ -153,9 +153,11 @@ input_to_command!(GrooveboxCommand: |state: GrooveboxTui, input|match input // 0: Enqueue phrase 0 (stop all) key_pat!(Char('0')) => Enqueue(Some(state.pool.phrases()[0].clone())), - key_pat!(Shift-Char('R')) => Sampler( + key_pat!(Shift-Char('R')) => Sampler(if state.sampler.recording.is_some() { + SamplerCommand::RecordFinish + } else { SamplerCommand::RecordBegin(u7::from(state.editor.note_point() as u8)) - ), + }), // e: Toggle between editing currently playing or other phrase key_pat!(Char('e')) => if let Some((_, Some(playing))) = state.player.play_phrase() { diff --git a/crates/tek/src/sampler/sampler_audio.rs b/crates/tek/src/sampler/sampler_audio.rs index 8b9ca5e4..c7558455 100644 --- a/crates/tek/src/sampler/sampler_audio.rs +++ b/crates/tek/src/sampler/sampler_audio.rs @@ -11,22 +11,39 @@ audio!(|self: SamplerAudio<'a>, _client, scope|{ self.0.clear_output_buffer(); self.0.process_audio_out(scope); self.0.write_output_buffer(scope); - self.0.update_input_meter(scope); + self.0.process_audio_in(scope); Control::Continue }); impl Sampler { - pub fn update_input_meter (&mut self, scope: &ProcessScope) { - let Sampler { audio_ins, input_meter, .. } = self; + pub fn process_audio_in (&mut self, scope: &ProcessScope) { + let Sampler { audio_ins, input_meter, recording, .. } = self; if audio_ins.len() != input_meter.len() { *input_meter = vec![0.0;audio_ins.len()]; } - for (input, meter) in audio_ins.iter().zip(input_meter) { - let slice = input.as_slice(scope); - let total: f32 = slice.iter().map(|x|x.abs()).sum(); - let count = slice.len() as f32; - *meter = 10. * (total / count).log10(); + if let Some((_, sample)) = recording { + if sample.channels.len() != audio_ins.len() { + panic!("channel count mismatch"); + } + let iterator = audio_ins.iter().zip(input_meter).zip(sample.channels.iter_mut()); + let mut length = 0; + for ((input, meter), channel) in iterator { + let slice = input.as_slice(scope); + length = length.max(slice.len()); + let total: f32 = slice.iter().map(|x|x.abs()).sum(); + let count = slice.len() as f32; + *meter = 10. * (total / count).log10(); + channel.extend_from_slice(slice); + } + sample.end += length; + } else { + for (input, meter) in audio_ins.iter().zip(input_meter) { + let slice = input.as_slice(scope); + let total: f32 = slice.iter().map(|x|x.abs()).sum(); + let count = slice.len() as f32; + *meter = 10. * (total / count).log10(); + } } } From df00fedfd6d774235d6086c10651254a73e1d3cf Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 28 Dec 2024 18:12:43 +0100 Subject: [PATCH 048/815] refactor sampler module --- crates/tek/src/groovebox.rs | 4 +- crates/tek/src/sampler.rs | 225 +++++++++++++--------- crates/tek/src/sampler/sampler_audio.rs | 104 ---------- crates/tek/src/sampler/sampler_control.rs | 96 --------- crates/tek/src/sampler/sampler_keys.rs | 15 -- crates/tek/src/sampler/sampler_tui.rs | 155 +++++++++++++++ 6 files changed, 293 insertions(+), 306 deletions(-) delete mode 100644 crates/tek/src/sampler/sampler_audio.rs delete mode 100644 crates/tek/src/sampler/sampler_control.rs delete mode 100644 crates/tek/src/sampler/sampler_keys.rs create mode 100644 crates/tek/src/sampler/sampler_tui.rs diff --git a/crates/tek/src/groovebox.rs b/crates/tek/src/groovebox.rs index 97e7e3c6..5152b397 100644 --- a/crates/tek/src/groovebox.rs +++ b/crates/tek/src/groovebox.rs @@ -110,9 +110,11 @@ render!(|self: GrooveboxSamples<'a>|{ bg = Color::Rgb(64,16,0); fg = Color::Rgb(224,64,32) } + } else if self.0.sampler.mapped[note].is_some() { + fg = TuiTheme::g(224); } Tui::bg(bg, if let Some(sample) = &self.0.sampler.mapped[note] { - Tui::fg(fg, format!("{note:3} ????? ")) + Tui::fg(fg, format!("{note:3} ?????? ")) } else { Tui::fg(fg, format!("{note:3} (none) ")) }) diff --git a/crates/tek/src/sampler.rs b/crates/tek/src/sampler.rs index 85434e82..b3d69db6 100644 --- a/crates/tek/src/sampler.rs +++ b/crates/tek/src/sampler.rs @@ -21,14 +21,9 @@ pub mod voice; pub(crate) use self::voice::*; pub use self::voice::Voice; -pub mod sampler_control; -pub(crate) use self::sampler_control::*; - -pub mod sampler_audio; -pub(crate) use self::sampler_audio::*; - -pub mod sampler_keys; -pub(crate) use self::sampler_keys::*; +pub mod sampler_tui; +pub(crate) use self::sampler_tui::*; +pub use self::sampler_tui::SamplerTui; pub mod sample_import; pub(crate) use self::sample_import::*; @@ -102,88 +97,138 @@ impl Sampler { } } } -pub struct SamplerTui { - pub state: Sampler, - pub cursor: (usize, usize), - pub editing: Option>>, - pub mode: Option, - /// Size of actual notes area - pub size: Measure, - /// Lowest note displayed - pub note_lo: AtomicUsize, - pub note_pt: AtomicUsize, - color: ItemPalette -} -impl SamplerTui { - /// Immutable reference to sample at cursor. - pub fn sample (&self) -> Option<&Arc>> { - for (i, sample) in self.state.mapped.iter().enumerate() { - if i == self.cursor.0 { - return sample.as_ref() - } - } - for (i, sample) in self.state.unmapped.iter().enumerate() { - if i + self.state.mapped.len() == self.cursor.0 { - return Some(sample) - } - } - None - } -} -from_jack!(|jack|SamplerTui{ - Self { - cursor: (0, 0), - editing: None, - mode: None, - size: Measure::new(), - note_lo: 36.into(), - note_pt: 36.into(), - color: ItemPalette::from(Color::Rgb(64, 128, 32)), - state: Sampler::new(jack, "sampler")?, - } -}); -render!(|self: SamplerTui|{ - let keys_width = 5; - let keys = move||SamplerKeys(self); - let fg = self.color.base.rgb; - let bg = self.color.darkest.rgb; - let border = Fill::wh(Outer(Style::default().fg(fg).bg(bg))); - let with_border = |x|lay!([border, Fill::wh(&x)]); - let with_size = |x|lay!([self.size, x]); - Tui::bg(bg, Fill::wh(with_border(Bsp::s( - Tui::fg(self.color.light.rgb, Tui::bold(true, "Sampler")), - with_size(Tui::shrink_y(1, Bsp::e( - Fixed::w(keys_width, keys()), - Fill::wh(render(|to: &mut TuiOutput|Ok({ - let x = to.area.x(); - let bg_base = self.color.darkest.rgb; - let bg_selected = self.color.darker.rgb; - let style_empty = Style::default().fg(self.color.base.rgb); - let style_full = Style::default().fg(self.color.lighter.rgb); - let note_hi = self.note_hi(); - let note_pt = self.note_point(); - for y in 0..self.size.h() { - let note = note_hi - y as usize; - let bg = if note == note_pt { bg_selected } else { bg_base }; - let style = Some(style_empty.bg(bg)); - to.blit(&" (no sample) ", x, to.area.y() + y as u16, style) - } - }))) - ))), - )))) -}); -impl NoteRange for SamplerTui { - fn note_lo (&self) -> &AtomicUsize { &self.note_lo } - fn note_axis (&self) -> &AtomicUsize { &self.size.y } -} -impl NotePoint for SamplerTui { - fn note_len (&self) -> usize {0/*TODO*/} - fn set_note_len (&self, x: usize) {} - fn note_point (&self) -> usize { self.note_pt.load(Relaxed) } - fn set_note_point (&self, x: usize) { self.note_pt.store(x, Relaxed); } + +pub enum SamplerCommand { + RecordBegin(u7), + RecordCancel, + RecordFinish, + SetSample(u7, Option>>), + SetGain(f32), + NoteOn(u7, u7), + NoteOff(u7), } -pub enum SamplerMode { - // Load sample from path - Import(usize, FileBrowser), +command!(|self: SamplerCommand, state: Sampler|match self { + Self::SetSample(index, sample) => { + let i = index.as_int() as usize; + let old = state.mapped[i].clone(); + state.mapped[i] = sample; + Some(Self::SetSample(index, old)) + }, + Self::RecordBegin(index) => { + state.begin_recording(index.as_int() as usize); + None + }, + Self::RecordCancel => { + state.cancel_recording(); + None + }, + Self::RecordFinish => { + state.finish_recording(); + None + }, + _ => todo!() +}); + +audio!(|self: SamplerTui, client, scope|{ + SamplerAudio(&mut self.state).process(client, scope) +}); + +pub struct SamplerAudio<'a>(pub &'a mut Sampler); + +audio!(|self: SamplerAudio<'a>, _client, scope|{ + self.0.process_midi_in(scope); + self.0.clear_output_buffer(); + self.0.process_audio_out(scope); + self.0.write_output_buffer(scope); + self.0.process_audio_in(scope); + Control::Continue +}); + +impl Sampler { + + pub fn process_audio_in (&mut self, scope: &ProcessScope) { + let Sampler { audio_ins, input_meter, recording, .. } = self; + if audio_ins.len() != input_meter.len() { + *input_meter = vec![0.0;audio_ins.len()]; + } + if let Some((_, sample)) = recording { + if sample.channels.len() != audio_ins.len() { + panic!("channel count mismatch"); + } + let iterator = audio_ins.iter().zip(input_meter).zip(sample.channels.iter_mut()); + let mut length = 0; + for ((input, meter), channel) in iterator { + let slice = input.as_slice(scope); + length = length.max(slice.len()); + let total: f32 = slice.iter().map(|x|x.abs()).sum(); + let count = slice.len() as f32; + *meter = 10. * (total / count).log10(); + channel.extend_from_slice(slice); + } + sample.end += length; + } else { + for (input, meter) in audio_ins.iter().zip(input_meter) { + let slice = input.as_slice(scope); + let total: f32 = slice.iter().map(|x|x.abs()).sum(); + let count = slice.len() as f32; + *meter = 10. * (total / count).log10(); + } + } + } + + /// Create [Voice]s from [Sample]s in response to MIDI input. + pub fn process_midi_in (&mut self, scope: &ProcessScope) { + let Sampler { midi_in, mapped, voices, .. } = self; + for RawMidi { time, bytes } in midi_in.iter(scope) { + if let LiveEvent::Midi { + message: MidiMessage::NoteOn { ref key, ref vel }, .. + } = LiveEvent::parse(bytes).unwrap() { + if let Some(ref sample) = mapped[key.as_int() as usize] { + voices.write().unwrap().push(Sample::play(sample, time as usize, vel)); + } + } + } + } + + /// Zero the output buffer. + pub fn clear_output_buffer (&mut self) { + for buffer in self.buffer.iter_mut() { + buffer.fill(0.0); + } + } + + /// Mix all currently playing samples into the output. + pub fn process_audio_out (&mut self, scope: &ProcessScope) { + let Sampler { ref mut buffer, voices, output_gain, .. } = self; + let channel_count = buffer.len(); + voices.write().unwrap().retain_mut(|voice|{ + for index in 0..scope.n_frames() as usize { + if let Some(frame) = voice.next() { + for (channel, sample) in frame.iter().enumerate() { + // Averaging mixer: + //self.buffer[channel % channel_count][index] = ( + //(self.buffer[channel % channel_count][index] + sample * self.output_gain) / 2.0 + //); + buffer[channel % channel_count][index] += sample * *output_gain; + } + } else { + return false + } + } + true + }); + } + + /// Write output buffer to output ports. + pub fn write_output_buffer (&mut self, scope: &ProcessScope) { + let Sampler { ref mut audio_outs, buffer, .. } = self; + for (i, port) in audio_outs.iter_mut().enumerate() { + let buffer = &buffer[i]; + for (i, value) in port.as_mut_slice(scope).iter_mut().enumerate() { + *value = *buffer.get(i).unwrap_or(&0.0); + } + } + } + } diff --git a/crates/tek/src/sampler/sampler_audio.rs b/crates/tek/src/sampler/sampler_audio.rs deleted file mode 100644 index c7558455..00000000 --- a/crates/tek/src/sampler/sampler_audio.rs +++ /dev/null @@ -1,104 +0,0 @@ -use crate::*; - -audio!(|self: SamplerTui, client, scope|{ - SamplerAudio(&mut self.state).process(client, scope) -}); - -pub struct SamplerAudio<'a>(pub &'a mut Sampler); - -audio!(|self: SamplerAudio<'a>, _client, scope|{ - self.0.process_midi_in(scope); - self.0.clear_output_buffer(); - self.0.process_audio_out(scope); - self.0.write_output_buffer(scope); - self.0.process_audio_in(scope); - Control::Continue -}); - -impl Sampler { - - pub fn process_audio_in (&mut self, scope: &ProcessScope) { - let Sampler { audio_ins, input_meter, recording, .. } = self; - if audio_ins.len() != input_meter.len() { - *input_meter = vec![0.0;audio_ins.len()]; - } - if let Some((_, sample)) = recording { - if sample.channels.len() != audio_ins.len() { - panic!("channel count mismatch"); - } - let iterator = audio_ins.iter().zip(input_meter).zip(sample.channels.iter_mut()); - let mut length = 0; - for ((input, meter), channel) in iterator { - let slice = input.as_slice(scope); - length = length.max(slice.len()); - let total: f32 = slice.iter().map(|x|x.abs()).sum(); - let count = slice.len() as f32; - *meter = 10. * (total / count).log10(); - channel.extend_from_slice(slice); - } - sample.end += length; - } else { - for (input, meter) in audio_ins.iter().zip(input_meter) { - let slice = input.as_slice(scope); - let total: f32 = slice.iter().map(|x|x.abs()).sum(); - let count = slice.len() as f32; - *meter = 10. * (total / count).log10(); - } - } - } - - /// Create [Voice]s from [Sample]s in response to MIDI input. - pub fn process_midi_in (&mut self, scope: &ProcessScope) { - let Sampler { midi_in, mapped, voices, .. } = self; - for RawMidi { time, bytes } in midi_in.iter(scope) { - if let LiveEvent::Midi { - message: MidiMessage::NoteOn { ref key, ref vel }, .. - } = LiveEvent::parse(bytes).unwrap() { - if let Some(ref sample) = mapped[key.as_int() as usize] { - voices.write().unwrap().push(Sample::play(sample, time as usize, vel)); - } - } - } - } - - /// Zero the output buffer. - pub fn clear_output_buffer (&mut self) { - for buffer in self.buffer.iter_mut() { - buffer.fill(0.0); - } - } - - /// Mix all currently playing samples into the output. - pub fn process_audio_out (&mut self, scope: &ProcessScope) { - let Sampler { ref mut buffer, voices, output_gain, .. } = self; - let channel_count = buffer.len(); - voices.write().unwrap().retain_mut(|voice|{ - for index in 0..scope.n_frames() as usize { - if let Some(frame) = voice.next() { - for (channel, sample) in frame.iter().enumerate() { - // Averaging mixer: - //self.buffer[channel % channel_count][index] = ( - //(self.buffer[channel % channel_count][index] + sample * self.output_gain) / 2.0 - //); - buffer[channel % channel_count][index] += sample * *output_gain; - } - } else { - return false - } - } - true - }); - } - - /// Write output buffer to output ports. - pub fn write_output_buffer (&mut self, scope: &ProcessScope) { - let Sampler { ref mut audio_outs, buffer, .. } = self; - for (i, port) in audio_outs.iter_mut().enumerate() { - let buffer = &buffer[i]; - for (i, value) in port.as_mut_slice(scope).iter_mut().enumerate() { - *value = *buffer.get(i).unwrap_or(&0.0); - } - } - } - -} diff --git a/crates/tek/src/sampler/sampler_control.rs b/crates/tek/src/sampler/sampler_control.rs deleted file mode 100644 index 6e490fef..00000000 --- a/crates/tek/src/sampler/sampler_control.rs +++ /dev/null @@ -1,96 +0,0 @@ -use crate::*; -use KeyCode::Char; - -handle!(|self: SamplerTui, input|SamplerTuiCommand::execute_with_state(self, input)); - -pub enum SamplerTuiCommand { - Import(FileBrowserCommand), - SelectNote(usize), - SelectField(usize), - Sample(SamplerCommand), -} - -pub enum SamplerCommand { - RecordBegin(u7), - RecordCancel, - RecordFinish, - SetSample(u7, Option>>), - SetGain(f32), - NoteOn(u7, u7), - NoteOff(u7), -} - -input_to_command!(SamplerTuiCommand: |state: SamplerTui, input|match state.mode { - Some(SamplerMode::Import(..)) => Self::Import( - FileBrowserCommand::input_to_command(state, input)? - ), - _ => match input.event() { - // load sample - key_pat!(Shift-Char('L')) => { - Self::Import(FileBrowserCommand::Begin) - }, - key_pat!(KeyCode::Up) => { - Self::SelectNote(state.note_point().overflowing_add(1).0.min(127)) - }, - key_pat!(KeyCode::Down) => { - Self::SelectNote(state.note_point().overflowing_sub(1).0.min(127)) - }, - _ => return None - } - //key_pat!(KeyCode::Char('p')) => if let Some(sample) = self.sample() { - //voices.write().unwrap().push(Sample::play(sample, 0, &100.into())); - //}, - //key_pat!(KeyCode::Char('a')) => { - //let sample = Arc::new(RwLock::new(Sample::new("", 0, 0, vec![]))); - //self.mode = None;//Some(Exit::boxed(AddSampleModal::new(&sample, &voices)?)); - //unmapped.push(sample); - //}, - //key_pat!(KeyCode::Char('r')) => if let Some(sample) = self.sample() { - //self.mode = None;//Some(Exit::boxed(AddSampleModal::new(&sample, &voices)?)); - //}, - //key_pat!(KeyCode::Enter) => if let Some(sample) = self.sample() { - //self.editing = Some(sample.clone()); - //}, - //_ => { - //return Ok(None) - //} - //} -}); - -command!(|self: SamplerTuiCommand, state: SamplerTui|match self { - Self::Import(FileBrowserCommand::Begin) => { - let voices = &state.state.voices; - let sample = Arc::new(RwLock::new(Sample::new("", 0, 0, vec![]))); - state.mode = Some(SamplerMode::Import(0, FileBrowser::new(None)?)); - None - }, - Self::SelectNote(index) => { - let old = state.note_point(); - state.set_note_point(index); - Some(Self::SelectNote(old)) - }, - Self::Sample(cmd) => cmd.execute(&mut state.state)?.map(Self::Sample), - _ => todo!() -}); - -command!(|self: SamplerCommand, state: Sampler|match self { - Self::SetSample(index, sample) => { - let i = index.as_int() as usize; - let old = state.mapped[i].clone(); - state.mapped[i] = sample; - Some(Self::SetSample(index, old)) - }, - Self::RecordBegin(index) => { - state.begin_recording(index.as_int() as usize); - None - }, - Self::RecordCancel => { - state.cancel_recording(); - None - }, - Self::RecordFinish => { - state.finish_recording(); - None - }, - _ => todo!() -}); diff --git a/crates/tek/src/sampler/sampler_keys.rs b/crates/tek/src/sampler/sampler_keys.rs deleted file mode 100644 index 92fad051..00000000 --- a/crates/tek/src/sampler/sampler_keys.rs +++ /dev/null @@ -1,15 +0,0 @@ -use crate::*; - -pub struct SamplerKeys<'a>(pub &'a SamplerTui); -has_color!(|self: SamplerKeys<'a>|self.0.color.base); -render!(|self: SamplerKeys<'a>|render(|to|Ok(render_keys_v(to, self)))); -impl<'a> NoteRange for SamplerKeys<'a> { - fn note_lo (&self) -> &AtomicUsize { &self.0.note_lo } - fn note_axis (&self) -> &AtomicUsize { &self.0.size.y } -} -impl<'a> NotePoint for SamplerKeys<'a> { - fn note_len (&self) -> usize {0/*TODO*/} - fn set_note_len (&self, x: usize) {} - fn note_point (&self) -> usize { self.0.note_point() } - fn set_note_point (&self, x: usize) { self.0.set_note_point(x); } -} diff --git a/crates/tek/src/sampler/sampler_tui.rs b/crates/tek/src/sampler/sampler_tui.rs new file mode 100644 index 00000000..1bd13a77 --- /dev/null +++ b/crates/tek/src/sampler/sampler_tui.rs @@ -0,0 +1,155 @@ +use crate::*; +use KeyCode::Char; + +pub struct SamplerTui { + pub state: Sampler, + pub cursor: (usize, usize), + pub editing: Option>>, + pub mode: Option, + /// Size of actual notes area + pub size: Measure, + /// Lowest note displayed + pub note_lo: AtomicUsize, + pub note_pt: AtomicUsize, + color: ItemPalette +} + +impl SamplerTui { + /// Immutable reference to sample at cursor. + pub fn sample (&self) -> Option<&Arc>> { + for (i, sample) in self.state.mapped.iter().enumerate() { + if i == self.cursor.0 { + return sample.as_ref() + } + } + for (i, sample) in self.state.unmapped.iter().enumerate() { + if i + self.state.mapped.len() == self.cursor.0 { + return Some(sample) + } + } + None + } +} + +from_jack!(|jack|SamplerTui{ + Self { + cursor: (0, 0), + editing: None, + mode: None, + size: Measure::new(), + note_lo: 36.into(), + note_pt: 36.into(), + color: ItemPalette::from(Color::Rgb(64, 128, 32)), + state: Sampler::new(jack, "sampler")?, + } +}); + +render!(|self: SamplerTui|{ + let keys_width = 5; + let keys = move||"";//SamplerKeys(self); + let fg = self.color.base.rgb; + let bg = self.color.darkest.rgb; + let border = Fill::wh(Outer(Style::default().fg(fg).bg(bg))); + let with_border = |x|lay!([border, Fill::wh(&x)]); + let with_size = |x|lay!([self.size, x]); + Tui::bg(bg, Fill::wh(with_border(Bsp::s( + Tui::fg(self.color.light.rgb, Tui::bold(true, "Sampler")), + with_size(Tui::shrink_y(1, Bsp::e( + Fixed::w(keys_width, keys()), + Fill::wh(render(|to: &mut TuiOutput|Ok({ + let x = to.area.x(); + let bg_base = self.color.darkest.rgb; + let bg_selected = self.color.darker.rgb; + let style_empty = Style::default().fg(self.color.base.rgb); + let style_full = Style::default().fg(self.color.lighter.rgb); + let note_hi = self.note_hi(); + let note_pt = self.note_point(); + for y in 0..self.size.h() { + let note = note_hi - y as usize; + let bg = if note == note_pt { bg_selected } else { bg_base }; + let style = Some(style_empty.bg(bg)); + to.blit(&" (no sample) ", x, to.area.y() + y as u16, style) + } + }))) + ))), + )))) +}); + +impl NoteRange for SamplerTui { + fn note_lo (&self) -> &AtomicUsize { &self.note_lo } + fn note_axis (&self) -> &AtomicUsize { &self.size.y } +} + +impl NotePoint for SamplerTui { + fn note_len (&self) -> usize {0/*TODO*/} + fn set_note_len (&self, x: usize) {} + fn note_point (&self) -> usize { self.note_pt.load(Relaxed) } + fn set_note_point (&self, x: usize) { self.note_pt.store(x, Relaxed); } +} + +pub enum SamplerMode { + // Load sample from path + Import(usize, FileBrowser), +} + +handle!(|self: SamplerTui, input|SamplerTuiCommand::execute_with_state(self, input)); + +pub enum SamplerTuiCommand { + Import(FileBrowserCommand), + SelectNote(usize), + SelectField(usize), + Sample(SamplerCommand), +} + +input_to_command!(SamplerTuiCommand: |state: SamplerTui, input|match state.mode { + Some(SamplerMode::Import(..)) => Self::Import( + FileBrowserCommand::input_to_command(state, input)? + ), + _ => match input.event() { + // load sample + key_pat!(Shift-Char('L')) => { + Self::Import(FileBrowserCommand::Begin) + }, + key_pat!(KeyCode::Up) => { + Self::SelectNote(state.note_point().overflowing_add(1).0.min(127)) + }, + key_pat!(KeyCode::Down) => { + Self::SelectNote(state.note_point().overflowing_sub(1).0.min(127)) + }, + _ => return None + } + //key_pat!(KeyCode::Char('p')) => if let Some(sample) = self.sample() { + //voices.write().unwrap().push(Sample::play(sample, 0, &100.into())); + //}, + //key_pat!(KeyCode::Char('a')) => { + //let sample = Arc::new(RwLock::new(Sample::new("", 0, 0, vec![]))); + //self.mode = None;//Some(Exit::boxed(AddSampleModal::new(&sample, &voices)?)); + //unmapped.push(sample); + //}, + //key_pat!(KeyCode::Char('r')) => if let Some(sample) = self.sample() { + //self.mode = None;//Some(Exit::boxed(AddSampleModal::new(&sample, &voices)?)); + //}, + //key_pat!(KeyCode::Enter) => if let Some(sample) = self.sample() { + //self.editing = Some(sample.clone()); + //}, + //_ => { + //return Ok(None) + //} + //} +}); + +command!(|self: SamplerTuiCommand, state: SamplerTui|match self { + Self::Import(FileBrowserCommand::Begin) => { + let voices = &state.state.voices; + let sample = Arc::new(RwLock::new(Sample::new("", 0, 0, vec![]))); + state.mode = Some(SamplerMode::Import(0, FileBrowser::new(None)?)); + None + }, + Self::SelectNote(index) => { + let old = state.note_point(); + state.set_note_point(index); + Some(Self::SelectNote(old)) + }, + Self::Sample(cmd) => cmd.execute(&mut state.state)?.map(Self::Sample), + _ => todo!() +}); From b63a5e31baf036e8ecbf3af60a2340a6973ecd1c Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 28 Dec 2024 18:45:30 +0100 Subject: [PATCH 049/815] add sample viewer area --- crates/tek/src/groovebox.rs | 23 +++++++++++++++-------- crates/tek/src/lib.rs | 2 ++ crates/tek/src/meter.rs | 8 ++++++++ crates/tek/src/sampler.rs | 4 ++++ crates/tek/src/sampler/sample_viewer.rs | 21 +++++++++++++++++++++ 5 files changed, 50 insertions(+), 8 deletions(-) create mode 100644 crates/tek/src/meter.rs create mode 100644 crates/tek/src/sampler/sample_viewer.rs diff --git a/crates/tek/src/groovebox.rs b/crates/tek/src/groovebox.rs index 5152b397..69c3ebb8 100644 --- a/crates/tek/src/groovebox.rs +++ b/crates/tek/src/groovebox.rs @@ -61,6 +61,7 @@ render!(|self:GrooveboxTui|{ let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; let pool_w = if self.pool.visible { phrase_w } else { 0 }; let sampler_w = 11; + let note_pt = self.editor.note_point(); Fill::wh(lay!([ &self.size, Fill::wh(Align::s(Fixed::h(2, GrooveboxStatus::from(self)))), @@ -76,8 +77,20 @@ render!(|self:GrooveboxTui|{ PhraseSelector::next_phrase(&self.player), ]))), row!([ - Tui::split_n(false, 1, - MidiEditStatus(&self.editor), + Tui::split_n(false, 5, + col!([ + row!(|add|{ + if let Some(sample) = &self.sampler.mapped[note_pt] { + add(&format!("Sample {}", sample.read().unwrap().end))?; + } + add(&MidiEditStatus(&self.editor))?; + Ok(()) + }), + lay!([ + Outer(Style::default().fg(TuiTheme::g(128))), + Fill::w(Fixed::h(4, SampleViewer(None))), + ]), + ]), Tui::split_w(false, pool_w, Tui::pull_y(1, Fill::h(Align::e(PoolView(&self.pool)))), Tui::split_e(false, sampler_w, Fill::wh(col!([ @@ -91,12 +104,6 @@ render!(|self:GrooveboxTui|{ ])) }); -struct Meters<'a>(&'a[f32]); -render!(|self: Meters<'a>|col!([ - &format!("L/{:>+9.3}", self.0[0]), - &format!("R/{:>+9.3}", self.0[1]), -])); - struct GrooveboxSamples<'a>(&'a GrooveboxTui); render!(|self: GrooveboxSamples<'a>|{ let note_lo = self.0.editor.note_lo().load(Relaxed); diff --git a/crates/tek/src/lib.rs b/crates/tek/src/lib.rs index 934eb858..091e2fef 100644 --- a/crates/tek/src/lib.rs +++ b/crates/tek/src/lib.rs @@ -15,6 +15,8 @@ pub use self::jack::*; pub mod midi; pub(crate) use self::midi::*; +pub mod meter; pub(crate) use self::meter::*; + pub mod piano_h; pub(crate) use self::piano_h::*; pub mod transport; pub(crate) use self::transport::*; diff --git a/crates/tek/src/meter.rs b/crates/tek/src/meter.rs new file mode 100644 index 00000000..b52b8429 --- /dev/null +++ b/crates/tek/src/meter.rs @@ -0,0 +1,8 @@ +use crate::*; + +pub struct Meters<'a>(pub &'a[f32]); + +render!(|self: Meters<'a>|col!([ + &format!("L/{:>+9.3}", self.0[0]), + &format!("R/{:>+9.3}", self.0[1]), +])); diff --git a/crates/tek/src/sampler.rs b/crates/tek/src/sampler.rs index b3d69db6..bb74c2bb 100644 --- a/crates/tek/src/sampler.rs +++ b/crates/tek/src/sampler.rs @@ -28,6 +28,10 @@ pub use self::sampler_tui::SamplerTui; pub mod sample_import; pub(crate) use self::sample_import::*; +pub mod sample_viewer; +pub(crate) use self::sample_viewer::*; +pub use self::sample_viewer::SampleViewer; + /// The sampler plugin plays sounds. #[derive(Debug)] pub struct Sampler { diff --git a/crates/tek/src/sampler/sample_viewer.rs b/crates/tek/src/sampler/sample_viewer.rs new file mode 100644 index 00000000..3dcedb51 --- /dev/null +++ b/crates/tek/src/sampler/sample_viewer.rs @@ -0,0 +1,21 @@ +use crate::*; +use std::ops::Deref; +use ratatui::{prelude::Rect, widgets::{Widget, canvas::{Canvas, Points}}}; + +pub struct SampleViewer<'a>(pub Option<&'a Arc>>); +render!(|self: SampleViewer<'a>|render(|to|{ + let [x, y, width, height] = to.area(); + let area = Rect { x, y, width, height }; + Canvas::default() + .x_bounds([0.0, f64::from(width)]) + .y_bounds([0.0, f64::from(height)]) + .paint(|ctx|{ + ctx.draw(&Points { + coords: &[(0., 0.), (1., 2.), (3., 4.)], + color: Color::Rgb(255,0,0) + }) + // TODO + }) + .render(area, &mut to.buffer); + Ok(()) +})); From f09a6072f8faef782e9bcda57c7eb7d4956a4743 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 28 Dec 2024 18:55:58 +0100 Subject: [PATCH 050/815] draw x for no sample --- crates/tek/src/sampler/sample_viewer.rs | 29 +++++++++++++++---------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/crates/tek/src/sampler/sample_viewer.rs b/crates/tek/src/sampler/sample_viewer.rs index 3dcedb51..00d3e35e 100644 --- a/crates/tek/src/sampler/sample_viewer.rs +++ b/crates/tek/src/sampler/sample_viewer.rs @@ -6,16 +6,23 @@ pub struct SampleViewer<'a>(pub Option<&'a Arc>>); render!(|self: SampleViewer<'a>|render(|to|{ let [x, y, width, height] = to.area(); let area = Rect { x, y, width, height }; - Canvas::default() - .x_bounds([0.0, f64::from(width)]) - .y_bounds([0.0, f64::from(height)]) - .paint(|ctx|{ - ctx.draw(&Points { - coords: &[(0., 0.), (1., 2.), (3., 4.)], - color: Color::Rgb(255,0,0) - }) - // TODO - }) - .render(area, &mut to.buffer); + let (x_bounds, y_bounds, coords): ([f64;2], [f64;2], &[(f64,f64)]) = + if let Some(sample) = self.0 { + let sample = sample.read().unwrap(); + ( + [sample.start as f64, sample.end as f64], + [0 as f64, f32::MAX as f64], + &[] + ) + } else { + ( + [0.0, f64::from(width)], + [0.0, f64::from(height)], + &[(0., 0.), (1., 1.), (2., 2.), (0., 2.), (2., 0.)] + ) + }; + Canvas::default().x_bounds(x_bounds).y_bounds(y_bounds).paint(|ctx|{ + ctx.draw(&Points { coords, color: Color::Rgb(255,0,0) }) + }).render(area, &mut to.buffer); Ok(()) })); From 240c498a504f6e19b053a074ddd04ec54d3757b5 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 28 Dec 2024 19:07:28 +0100 Subject: [PATCH 051/815] show sample during recording --- crates/tek/src/groovebox.rs | 8 +++++++- crates/tek/src/sampler.rs | 23 +++++++---------------- crates/tek/src/sampler/sample_viewer.rs | 6 +++--- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/crates/tek/src/groovebox.rs b/crates/tek/src/groovebox.rs index 69c3ebb8..8dd04d17 100644 --- a/crates/tek/src/groovebox.rs +++ b/crates/tek/src/groovebox.rs @@ -88,7 +88,13 @@ render!(|self:GrooveboxTui|{ }), lay!([ Outer(Style::default().fg(TuiTheme::g(128))), - Fill::w(Fixed::h(4, SampleViewer(None))), + Fill::w(Fixed::h(4, if let Some((_, sample)) = &self.sampler.recording { + SampleViewer(Some(sample.clone())) + } else if let Some(sample) = &self.sampler.mapped[note_pt] { + SampleViewer(Some(sample.clone())) + } else { + SampleViewer(None) + })), ]), ]), Tui::split_w(false, pool_w, diff --git a/crates/tek/src/sampler.rs b/crates/tek/src/sampler.rs index bb74c2bb..0c106cab 100644 --- a/crates/tek/src/sampler.rs +++ b/crates/tek/src/sampler.rs @@ -38,7 +38,7 @@ pub struct Sampler { pub jack: Arc>, pub name: String, pub mapped: [Option>>;128], - pub recording: Option<(usize, Sample)>, + pub recording: Option<(usize, Arc>)>, pub unmapped: Vec>>, pub voices: Arc>>, pub midi_in: Port, @@ -75,31 +75,21 @@ impl Sampler { self.recording = None; } pub fn begin_recording (&mut self, index: usize) { - self.recording = Some( - (index, Sample::new("(new)", 0, 0, vec![vec![];self.audio_ins.len()])) - ); + self.recording = Some(( + index, + Arc::new(RwLock::new(Sample::new("(new)", 0, 0, vec![vec![];self.audio_ins.len()]))) + )); } pub fn finish_recording (&mut self) -> Option>> { let recording = self.recording.take(); if let Some((index, sample)) = recording { let old = self.mapped[index].clone(); - self.mapped[index] = Some(Arc::new(RwLock::new(sample))); + self.mapped[index] = Some(sample); old } else { None } } - pub fn record_chunk (&mut self, chunks: &[&[f32]]) { - if let Some((_, sample)) = &mut self.recording { - if chunks.len() != sample.channels.len() { - panic!() - } - for (chunk, channel) in chunks.iter().zip(sample.channels.iter_mut()) { - channel.extend_from_slice(chunk) - } - sample.end += chunks[0].len(); - } - } } pub enum SamplerCommand { @@ -157,6 +147,7 @@ impl Sampler { *input_meter = vec![0.0;audio_ins.len()]; } if let Some((_, sample)) = recording { + let mut sample = sample.write().unwrap(); if sample.channels.len() != audio_ins.len() { panic!("channel count mismatch"); } diff --git a/crates/tek/src/sampler/sample_viewer.rs b/crates/tek/src/sampler/sample_viewer.rs index 00d3e35e..cb54d0db 100644 --- a/crates/tek/src/sampler/sample_viewer.rs +++ b/crates/tek/src/sampler/sample_viewer.rs @@ -2,12 +2,12 @@ use crate::*; use std::ops::Deref; use ratatui::{prelude::Rect, widgets::{Widget, canvas::{Canvas, Points}}}; -pub struct SampleViewer<'a>(pub Option<&'a Arc>>); -render!(|self: SampleViewer<'a>|render(|to|{ +pub struct SampleViewer(pub Option>>); +render!(|self: SampleViewer|render(|to|{ let [x, y, width, height] = to.area(); let area = Rect { x, y, width, height }; let (x_bounds, y_bounds, coords): ([f64;2], [f64;2], &[(f64,f64)]) = - if let Some(sample) = self.0 { + if let Some(sample) = &self.0 { let sample = sample.read().unwrap(); ( [sample.start as f64, sample.end as f64], From 080c4131b70423b4c082bdd7291e80bca37510a9 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 28 Dec 2024 19:16:27 +0100 Subject: [PATCH 052/815] render computed points --- crates/tek/src/sampler/sample_viewer.rs | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/crates/tek/src/sampler/sample_viewer.rs b/crates/tek/src/sampler/sample_viewer.rs index cb54d0db..3ad94263 100644 --- a/crates/tek/src/sampler/sample_viewer.rs +++ b/crates/tek/src/sampler/sample_viewer.rs @@ -2,23 +2,41 @@ use crate::*; use std::ops::Deref; use ratatui::{prelude::Rect, widgets::{Widget, canvas::{Canvas, Points}}}; +const EMPTY: &[(f64, f64)] = &[(0., 0.), (1., 1.), (2., 2.), (0., 2.), (2., 0.)]; + pub struct SampleViewer(pub Option>>); render!(|self: SampleViewer|render(|to|{ let [x, y, width, height] = to.area(); let area = Rect { x, y, width, height }; + let mut points = vec![]; let (x_bounds, y_bounds, coords): ([f64;2], [f64;2], &[(f64,f64)]) = if let Some(sample) = &self.0 { let sample = sample.read().unwrap(); + let start = sample.start as f64; + let end = sample.end as f64; + let length = end - start; + let step = length / width as f64; + let mut t = start; + while t < end { + points.push((t as f64, 0.)); + points.push((t as f64, 1.)); + points.push((t as f64, 0.5)); + points.push((t as f64, 0.25)); + points.push((t as f64, 0.125)); + points.push((t as f64, 0.0625)); + points.push((t as f64, 0.03125)); + t += step; + } ( [sample.start as f64, sample.end as f64], - [0 as f64, f32::MAX as f64], - &[] + [0., 1.], + points.as_slice() ) } else { ( [0.0, f64::from(width)], [0.0, f64::from(height)], - &[(0., 0.), (1., 1.), (2., 2.), (0., 2.), (2., 0.)] + EMPTY ) }; Canvas::default().x_bounds(x_bounds).y_bounds(y_bounds).paint(|ctx|{ From 1859f378eacb0b962b349569434fd1f9c7726a5a Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 28 Dec 2024 19:23:19 +0100 Subject: [PATCH 053/815] watch it do: display sample waveform during recording --- crates/tek/src/groovebox.rs | 4 ++-- crates/tek/src/sampler/sample_viewer.rs | 14 ++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/crates/tek/src/groovebox.rs b/crates/tek/src/groovebox.rs index 8dd04d17..439c57d2 100644 --- a/crates/tek/src/groovebox.rs +++ b/crates/tek/src/groovebox.rs @@ -77,7 +77,7 @@ render!(|self:GrooveboxTui|{ PhraseSelector::next_phrase(&self.player), ]))), row!([ - Tui::split_n(false, 5, + Tui::split_n(false, 9, col!([ row!(|add|{ if let Some(sample) = &self.sampler.mapped[note_pt] { @@ -88,7 +88,7 @@ render!(|self:GrooveboxTui|{ }), lay!([ Outer(Style::default().fg(TuiTheme::g(128))), - Fill::w(Fixed::h(4, if let Some((_, sample)) = &self.sampler.recording { + Fill::w(Fixed::h(8, if let Some((_, sample)) = &self.sampler.recording { SampleViewer(Some(sample.clone())) } else if let Some(sample) = &self.sampler.mapped[note_pt] { SampleViewer(Some(sample.clone())) diff --git a/crates/tek/src/sampler/sample_viewer.rs b/crates/tek/src/sampler/sample_viewer.rs index 3ad94263..6f860746 100644 --- a/crates/tek/src/sampler/sample_viewer.rs +++ b/crates/tek/src/sampler/sample_viewer.rs @@ -18,18 +18,16 @@ render!(|self: SampleViewer|render(|to|{ let step = length / width as f64; let mut t = start; while t < end { - points.push((t as f64, 0.)); - points.push((t as f64, 1.)); - points.push((t as f64, 0.5)); - points.push((t as f64, 0.25)); - points.push((t as f64, 0.125)); - points.push((t as f64, 0.0625)); - points.push((t as f64, 0.03125)); + let chunk = &sample.channels[0][t as usize..((t + step) as usize).min(sample.end)]; + let total: f32 = chunk.iter().map(|x|x.abs()).sum(); + let count = chunk.len() as f32; + let meter = 10. * (total / count).log10(); + points.push((t as f64, meter as f64)); t += step; } ( [sample.start as f64, sample.end as f64], - [0., 1.], + [-40., 0.], points.as_slice() ) } else { From b992843e1c044d022d44bac11ca7abdab8c66e6d Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 28 Dec 2024 20:00:58 +0100 Subject: [PATCH 054/815] control sample start/end with cc20/21 --- crates/cli/src/cli_groovebox.rs | 17 +++++------ crates/tek/src/groovebox.rs | 24 ++++++++++++++++ crates/tek/src/jack.rs | 1 + crates/tek/src/jack/ports.rs | 50 ++++++++++++++++++++++++++++++++- crates/tek/src/sampler.rs | 16 +++++++---- 5 files changed, 93 insertions(+), 15 deletions(-) diff --git a/crates/cli/src/cli_groovebox.rs b/crates/cli/src/cli_groovebox.rs index e175d59e..a90827f1 100644 --- a/crates/cli/src/cli_groovebox.rs +++ b/crates/cli/src/cli_groovebox.rs @@ -32,17 +32,18 @@ impl GrooveboxCli { fn run (&self) -> Usually<()> { Tui::run(JackClient::new("tek_groovebox")?.activate_with(|jack|{ let app = tek::GrooveboxTui::try_from(jack)?; - let jack = jack.read().unwrap(); + jack.read().unwrap().client().connect_ports(&app.player.midi_outs[0], &app.sampler.midi_in)?; - jack.client().connect_ports(&app.player.midi_outs[0], &app.sampler.midi_in)?; + jack.connect_midi_from(&app.player.midi_ins[0], &self.midi_from)?; + jack.connect_midi_from(&app.sampler.midi_in, &self.midi_from)?; - connect_from(&jack, &app.player.midi_ins[0], &self.midi_from)?; - connect_to(&jack, &app.player.midi_outs[0], &self.midi_to)?; + jack.connect_midi_to(&app.player.midi_outs[0], &self.midi_to)?; - connect_audio_from(&jack, &app.sampler.audio_ins[0], &self.l_from)?; - connect_audio_from(&jack, &app.sampler.audio_ins[1], &self.r_from)?; - connect_audio_to(&jack, &app.sampler.audio_outs[0], &self.l_to)?; - connect_audio_to(&jack, &app.sampler.audio_outs[1], &self.r_to)?; + jack.connect_audio_from(&app.sampler.audio_ins[0], &self.l_from)?; + jack.connect_audio_from(&app.sampler.audio_ins[1], &self.r_from)?; + + jack.connect_audio_to(&app.sampler.audio_outs[0], &self.l_to)?; + jack.connect_audio_to(&app.sampler.audio_outs[1], &self.r_to)?; Ok(app) })?)?; diff --git a/crates/tek/src/groovebox.rs b/crates/tek/src/groovebox.rs index 439c57d2..71eea6ef 100644 --- a/crates/tek/src/groovebox.rs +++ b/crates/tek/src/groovebox.rs @@ -52,6 +52,30 @@ audio!(|self: GrooveboxTui, client, scope|{ if Control::Quit == SamplerAudio(&mut self.sampler).process(client, scope) { return Control::Quit } + for RawMidi { time, bytes } in self.sampler.midi_in.iter(scope) { + if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() { + match message { + MidiMessage::Controller { controller, value } => { + if controller == u7::from(20) { + if let Some(sample) = &self.sampler.mapped[self.editor.note_point()] { + let mut sample = sample.write().unwrap(); + let percentage = value.as_int() as f64 / 127.; + sample.start = (percentage * sample.end as f64) as usize; + } + } else if controller == u7::from(21) { + if let Some(sample) = &self.sampler.mapped[self.editor.note_point()] { + let mut sample = sample.write().unwrap(); + let percentage = value.as_int() as f64 / 127.; + let length = sample.channels[0].len(); + sample.end = sample.start + (percentage * (length as f64 - sample.start as f64)) as usize; + sample.end = sample.end.min(length); + } + } + } + _ => {} + } + } + } self.perf.update(t0, scope); Control::Continue }); diff --git a/crates/tek/src/jack.rs b/crates/tek/src/jack.rs index 0a21d92d..fae62340 100644 --- a/crates/tek/src/jack.rs +++ b/crates/tek/src/jack.rs @@ -22,6 +22,7 @@ pub(crate) use self::jack_event::*; pub mod ports; pub(crate) use self::ports::*; +pub use self::ports::RegisterPort; /// Implement [TryFrom<&Arc>>]: create app state from wrapped JACK handle. #[macro_export] macro_rules! from_jack { diff --git a/crates/tek/src/jack/ports.rs b/crates/tek/src/jack/ports.rs index c64e050a..d65ac790 100644 --- a/crates/tek/src/jack/ports.rs +++ b/crates/tek/src/jack/ports.rs @@ -5,9 +5,13 @@ pub trait RegisterPort { fn midi_out (&self, name: &str) -> Usually>; fn audio_in (&self, name: &str) -> Usually>; fn audio_out (&self, name: &str) -> Usually>; + fn connect_midi_from (&self, my_input: &Port, ports: &[String]) -> Usually<()>; + fn connect_midi_to (&self, my_output: &Port, ports: &[String]) -> Usually<()>; + fn connect_audio_from (&self, my_input: &Port, ports: &[String]) -> Usually<()>; + fn connect_audio_to (&self, my_output: &Port, ports: &[String]) -> Usually<()>; } -impl RegisterPort for &Arc> { +impl RegisterPort for Arc> { fn midi_in (&self, name: &str) -> Usually> { Ok(self.read().unwrap().client().register_port(name, MidiIn::default())?) } @@ -20,6 +24,50 @@ impl RegisterPort for &Arc> { fn audio_in (&self, name: &str) -> Usually> { Ok(self.read().unwrap().client().register_port(name, AudioIn::default())?) } + fn connect_midi_from (&self, input: &Port, ports: &[String]) -> Usually<()> { + let jack = self.read().unwrap(); + for port in ports.iter() { + if let Some(port) = jack.port_by_name(port).as_ref() { + jack.client().connect_ports(port, input)?; + } else { + panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); + } + } + Ok(()) + } + fn connect_midi_to (&self, output: &Port, ports: &[String]) -> Usually<()> { + let jack = self.read().unwrap(); + for port in ports.iter() { + if let Some(port) = jack.port_by_name(port).as_ref() { + jack.client().connect_ports(output, port)?; + } else { + panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); + } + } + Ok(()) + } + fn connect_audio_from (&self, input: &Port, ports: &[String]) -> Usually<()> { + let jack = self.read().unwrap(); + for port in ports.iter() { + if let Some(port) = jack.port_by_name(port).as_ref() { + jack.client().connect_ports(port, input)?; + } else { + panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); + } + } + Ok(()) + } + fn connect_audio_to (&self, output: &Port, ports: &[String]) -> Usually<()> { + let jack = self.read().unwrap(); + for port in ports.iter() { + if let Some(port) = jack.port_by_name(port).as_ref() { + jack.client().connect_ports(output, port)?; + } else { + panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); + } + } + Ok(()) + } } /// Trait for things that may expose JACK ports. diff --git a/crates/tek/src/sampler.rs b/crates/tek/src/sampler.rs index 0c106cab..54926e09 100644 --- a/crates/tek/src/sampler.rs +++ b/crates/tek/src/sampler.rs @@ -67,7 +67,7 @@ impl Sampler { unmapped: vec![], voices: Arc::new(RwLock::new(vec![])), buffer: vec![vec![0.0;16384];2], - output_gain: 0.5, + output_gain: 1., recording: None, }) } @@ -97,6 +97,7 @@ pub enum SamplerCommand { RecordCancel, RecordFinish, SetSample(u7, Option>>), + SetStart(u7, usize), SetGain(f32), NoteOn(u7, u7), NoteOff(u7), @@ -176,11 +177,14 @@ impl Sampler { pub fn process_midi_in (&mut self, scope: &ProcessScope) { let Sampler { midi_in, mapped, voices, .. } = self; for RawMidi { time, bytes } in midi_in.iter(scope) { - if let LiveEvent::Midi { - message: MidiMessage::NoteOn { ref key, ref vel }, .. - } = LiveEvent::parse(bytes).unwrap() { - if let Some(ref sample) = mapped[key.as_int() as usize] { - voices.write().unwrap().push(Sample::play(sample, time as usize, vel)); + if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() { + match message { + MidiMessage::NoteOn { ref key, ref vel } => { + if let Some(ref sample) = mapped[key.as_int() as usize] { + voices.write().unwrap().push(Sample::play(sample, time as usize, vel)); + } + }, + _ => {} } } } From b1ca35e5d96ec89b282af84b04d37efcfff59fd4 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 28 Dec 2024 20:12:30 +0100 Subject: [PATCH 055/815] nicer sample display --- crates/cli/src/cli_groovebox.rs | 5 ----- crates/tek/src/sampler/sample_viewer.rs | 28 ++++++++++++++++--------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/crates/cli/src/cli_groovebox.rs b/crates/cli/src/cli_groovebox.rs index a90827f1..b001b633 100644 --- a/crates/cli/src/cli_groovebox.rs +++ b/crates/cli/src/cli_groovebox.rs @@ -33,18 +33,13 @@ impl GrooveboxCli { Tui::run(JackClient::new("tek_groovebox")?.activate_with(|jack|{ let app = tek::GrooveboxTui::try_from(jack)?; jack.read().unwrap().client().connect_ports(&app.player.midi_outs[0], &app.sampler.midi_in)?; - jack.connect_midi_from(&app.player.midi_ins[0], &self.midi_from)?; jack.connect_midi_from(&app.sampler.midi_in, &self.midi_from)?; - jack.connect_midi_to(&app.player.midi_outs[0], &self.midi_to)?; - jack.connect_audio_from(&app.sampler.audio_ins[0], &self.l_from)?; jack.connect_audio_from(&app.sampler.audio_ins[1], &self.r_from)?; - jack.connect_audio_to(&app.sampler.audio_outs[0], &self.l_to)?; jack.connect_audio_to(&app.sampler.audio_outs[1], &self.r_to)?; - Ok(app) })?)?; Ok(()) diff --git a/crates/tek/src/sampler/sample_viewer.rs b/crates/tek/src/sampler/sample_viewer.rs index 6f860746..0abd7ea9 100644 --- a/crates/tek/src/sampler/sample_viewer.rs +++ b/crates/tek/src/sampler/sample_viewer.rs @@ -1,6 +1,6 @@ use crate::*; use std::ops::Deref; -use ratatui::{prelude::Rect, widgets::{Widget, canvas::{Canvas, Points}}}; +use ratatui::{prelude::Rect, widgets::{Widget, canvas::{Canvas, Points, Line}}}; const EMPTY: &[(f64, f64)] = &[(0., 0.), (1., 1.), (2., 2.), (0., 2.), (2., 0.)]; @@ -8,8 +8,8 @@ pub struct SampleViewer(pub Option>>); render!(|self: SampleViewer|render(|to|{ let [x, y, width, height] = to.area(); let area = Rect { x, y, width, height }; - let mut points = vec![]; - let (x_bounds, y_bounds, coords): ([f64;2], [f64;2], &[(f64,f64)]) = + let min_db = -40.0; + let (x_bounds, y_bounds, lines): ([f64;2], [f64;2], Vec) = if let Some(sample) = &self.0 { let sample = sample.read().unwrap(); let start = sample.start as f64; @@ -17,28 +17,36 @@ render!(|self: SampleViewer|render(|to|{ let length = end - start; let step = length / width as f64; let mut t = start; + let mut lines = vec![]; while t < end { let chunk = &sample.channels[0][t as usize..((t + step) as usize).min(sample.end)]; let total: f32 = chunk.iter().map(|x|x.abs()).sum(); let count = chunk.len() as f32; let meter = 10. * (total / count).log10(); - points.push((t as f64, meter as f64)); + let x = t as f64; + let y = meter as f64; + lines.push(Line::new(x, min_db, x, y, Color::Green)); t += step; } ( [sample.start as f64, sample.end as f64], - [-40., 0.], - points.as_slice() + [min_db, 0.], + lines ) } else { ( - [0.0, f64::from(width)], - [0.0, f64::from(height)], - EMPTY + [0.0, width as f64], + [0.0, height as f64], + vec![ + Line::new(0.0, 0.0, width as f64, height as f64, Color::Red), + Line::new(width as f64, 0.0, 0.0, height as f64, Color::Red), + ] ) }; Canvas::default().x_bounds(x_bounds).y_bounds(y_bounds).paint(|ctx|{ - ctx.draw(&Points { coords, color: Color::Rgb(255,0,0) }) + for line in lines.iter() { + ctx.draw(line) + } }).render(area, &mut to.buffer); Ok(()) })); From 198a730e330943d8e9802834de57f5ae4b27158c Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 28 Dec 2024 20:34:08 +0100 Subject: [PATCH 056/815] fix canvas density; play sampler from sequencer; jump to pressed key --- crates/tek/src/groovebox.rs | 10 +++++++++- crates/tek/src/sampler/sample_viewer.rs | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/tek/src/groovebox.rs b/crates/tek/src/groovebox.rs index 71eea6ef..ac138f5c 100644 --- a/crates/tek/src/groovebox.rs +++ b/crates/tek/src/groovebox.rs @@ -49,12 +49,20 @@ audio!(|self: GrooveboxTui, client, scope|{ if Control::Quit == ClockAudio(&mut self.player).process(client, scope) { return Control::Quit } + if Control::Quit == PlayerAudio( + &mut self.player, &mut self.note_buf, &mut self.midi_buf + ).process(client, scope) { + return Control::Quit + } if Control::Quit == SamplerAudio(&mut self.sampler).process(client, scope) { return Control::Quit } - for RawMidi { time, bytes } in self.sampler.midi_in.iter(scope) { + for RawMidi { time, bytes } in self.player.midi_ins[0].iter(scope) { if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() { match message { + MidiMessage::NoteOn { ref key, .. } => { + self.editor.set_note_point(key.as_int() as usize); + }, MidiMessage::Controller { controller, value } => { if controller == u7::from(20) { if let Some(sample) = &self.sampler.mapped[self.editor.note_point()] { diff --git a/crates/tek/src/sampler/sample_viewer.rs b/crates/tek/src/sampler/sample_viewer.rs index 0abd7ea9..b7c337da 100644 --- a/crates/tek/src/sampler/sample_viewer.rs +++ b/crates/tek/src/sampler/sample_viewer.rs @@ -26,7 +26,7 @@ render!(|self: SampleViewer|render(|to|{ let x = t as f64; let y = meter as f64; lines.push(Line::new(x, min_db, x, y, Color::Green)); - t += step; + t += step / 2.; } ( [sample.start as f64, sample.end as f64], From 1d7d81689946c7ed3bd58d8337a127733c98bbbb Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 28 Dec 2024 21:40:18 +0100 Subject: [PATCH 057/815] flatten midi recording code --- crates/edn/src/lib.rs | 3 +- crates/tek/src/groovebox.rs | 29 ++++++----- crates/tek/src/midi/midi_rec.rs | 87 +++++++++++++++++--------------- crates/tek/src/sampler/sample.rs | 3 +- crates/tek/src/sampler/voice.rs | 4 +- 5 files changed, 68 insertions(+), 58 deletions(-) diff --git a/crates/edn/src/lib.rs b/crates/edn/src/lib.rs index 9b814a17..1465b951 100644 --- a/crates/edn/src/lib.rs +++ b/crates/edn/src/lib.rs @@ -93,7 +93,8 @@ from_edn!("sample" => |(_jack, dir): (&Arc>, &str), args| -> start, end, channels: data, - rate: None + rate: None, + gain: 1.0 })))) }); diff --git a/crates/tek/src/groovebox.rs b/crates/tek/src/groovebox.rs index ac138f5c..fbad7ed9 100644 --- a/crates/tek/src/groovebox.rs +++ b/crates/tek/src/groovebox.rs @@ -64,19 +64,22 @@ audio!(|self: GrooveboxTui, client, scope|{ self.editor.set_note_point(key.as_int() as usize); }, MidiMessage::Controller { controller, value } => { - if controller == u7::from(20) { - if let Some(sample) = &self.sampler.mapped[self.editor.note_point()] { - let mut sample = sample.write().unwrap(); - let percentage = value.as_int() as f64 / 127.; - sample.start = (percentage * sample.end as f64) as usize; - } - } else if controller == u7::from(21) { - if let Some(sample) = &self.sampler.mapped[self.editor.note_point()] { - let mut sample = sample.write().unwrap(); - let percentage = value.as_int() as f64 / 127.; - let length = sample.channels[0].len(); - sample.end = sample.start + (percentage * (length as f64 - sample.start as f64)) as usize; - sample.end = sample.end.min(length); + if let Some(sample) = &self.sampler.mapped[self.editor.note_point()] { + let mut sample = sample.write().unwrap(); + let percentage = value.as_int() as f64 / 127.; + match controller.as_int() { + 20 => { + sample.start = (percentage * sample.end as f64) as usize; + }, + 21 => { + let length = sample.channels[0].len(); + sample.end = sample.start + (percentage * (length as f64 - sample.start as f64)) as usize; + sample.end = sample.end.min(length); + }, + 24 => { + sample.gain = percentage as f32 * 2.0; + }, + _ => {} } } } diff --git a/crates/tek/src/midi/midi_rec.rs b/crates/tek/src/midi/midi_rec.rs index 94abf84d..6c2bb768 100644 --- a/crates/tek/src/midi/midi_rec.rs +++ b/crates/tek/src/midi/midi_rec.rs @@ -8,46 +8,6 @@ pub trait MidiRecordApi: HasClock + HasPlayPhrase + HasMidiIns { fn toggle_record (&mut self) { *self.recording_mut() = !self.recording(); } - fn record (&mut self, scope: &ProcessScope, midi_buf: &mut Vec>>) { - let sample0 = scope.last_frame_time() as usize; - // For highlighting keys and note repeat - let notes_in = self.notes_in().clone(); - if self.clock().is_rolling() { - if let Some((started, ref phrase)) = self.play_phrase().clone() { - let start = started.sample.get() as usize; - let quant = self.clock().quant.get(); - let timebase = self.clock().timebase().clone(); - let monitoring = self.monitoring(); - let recording = self.recording(); - for input in self.midi_ins_mut().iter() { - for (sample, event, bytes) in parse_midi_input(input.iter(scope)) { - if let LiveEvent::Midi { message, .. } = event { - if monitoring { - midi_buf[sample].push(bytes.to_vec()) - } - if recording { - if let Some(phrase) = phrase { - let mut phrase = phrase.write().unwrap(); - let length = phrase.length; - phrase.record_event({ - let sample = (sample0 + sample - start) as f64; - let pulse = timebase.samples_to_pulse(sample); - let quantized = (pulse / quant).round() * quant; - quantized as usize % length - }, message); - } - } - update_keys(&mut notes_in.write().unwrap(), &message); - } - } - } - } - if let Some((start_at, _clip)) = &self.next_phrase() { - // TODO switch to next phrase and record into it - } - } - } - fn monitoring (&self) -> bool; fn monitoring_mut (&mut self) -> &mut bool; fn toggle_monitor (&mut self) { @@ -65,7 +25,52 @@ pub trait MidiRecordApi: HasClock + HasPlayPhrase + HasMidiIns { } } } - + fn record (&mut self, scope: &ProcessScope, midi_buf: &mut Vec>>) { + if self.monitoring() { + self.monitor(scope, midi_buf); + } + if !self.clock().is_rolling() { + return + } + if let Some((started, ref clip)) = self.play_phrase().clone() { + self.record_clip(scope, started, clip, midi_buf); + } + if let Some((start_at, phrase)) = &self.next_phrase() { + self.record_next(); + } + } + fn record_clip ( + &mut self, + scope: &ProcessScope, + started: Moment, + phrase: &Option>>, + midi_buf: &mut Vec>> + ) { + let sample0 = scope.last_frame_time() as usize; + let start = started.sample.get() as usize; + let recording = self.recording(); + let timebase = self.clock().timebase().clone(); + let quant = self.clock().quant.get(); + if let Some(phrase) = phrase { + let mut phrase = phrase.write().unwrap(); + let length = phrase.length; + for input in self.midi_ins_mut().iter() { + for (sample, event, bytes) in parse_midi_input(input.iter(scope)) { + if let LiveEvent::Midi { message, .. } = event { + phrase.record_event({ + let sample = (sample0 + sample - start) as f64; + let pulse = timebase.samples_to_pulse(sample); + let quantized = (pulse / quant).round() * quant; + quantized as usize % length + }, message); + } + } + } + } + } + fn record_next (&mut self) { + // TODO switch to next clip and record into it + } fn overdub (&self) -> bool; fn overdub_mut (&mut self) -> &mut bool; fn toggle_overdub (&mut self) { diff --git a/crates/tek/src/sampler/sample.rs b/crates/tek/src/sampler/sample.rs index adfc9fa0..39f6d9b6 100644 --- a/crates/tek/src/sampler/sample.rs +++ b/crates/tek/src/sampler/sample.rs @@ -9,6 +9,7 @@ pub struct Sample { pub end: usize, pub channels: Vec>, pub rate: Option, + pub gain: f32, } /// Load sample from WAV and assign to MIDI note. @@ -24,7 +25,7 @@ pub struct Sample { impl Sample { pub fn new (name: &str, start: usize, end: usize, channels: Vec>) -> Self { - Self { name: name.to_string(), start, end, channels, rate: None } + Self { name: name.to_string(), start, end, channels, rate: None, gain: 1.0 } } pub fn play (sample: &Arc>, after: usize, velocity: &u7) -> Voice { Voice { diff --git a/crates/tek/src/sampler/voice.rs b/crates/tek/src/sampler/voice.rs index 738f51aa..be4d8918 100644 --- a/crates/tek/src/sampler/voice.rs +++ b/crates/tek/src/sampler/voice.rs @@ -21,8 +21,8 @@ impl Iterator for Voice { let position = self.position; self.position += 1; return sample.channels[0].get(position).map(|_amplitude|[ - sample.channels[0][position] * self.velocity, - sample.channels[0][position] * self.velocity, + sample.channels[0][position] * self.velocity * sample.gain, + sample.channels[0][position] * self.velocity * sample.gain, ]) } None From fe316a64d3920ea9d57f1a84e214f904393cb325 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 28 Dec 2024 21:55:30 +0100 Subject: [PATCH 058/815] don't autoconnect groovebox to midi out --- Justfile | 2 -- 1 file changed, 2 deletions(-) diff --git a/Justfile b/Justfile index 83b7e30f..3654e2fb 100644 --- a/Justfile +++ b/Justfile @@ -64,7 +64,6 @@ groovebox-ext: reset cargo run --bin tek_groovebox -- -n tek \ -i "Midi-Bridge:nanoKEY Studio 1:(capture_0) nanoKEY Studio nanoKEY Studio _" \ - -o "Midi-Bridge:Komplete Audio 6 0:(playback_0) Komplete Audio 6 MIDI 1" \ -l "Komplete Audio 6 Pro:capture_AUX1" \ -r "Komplete Audio 6 Pro:capture_AUX1" \ -L "Komplete Audio 6 Pro:playback_AUX1" \ @@ -76,7 +75,6 @@ groovebox-release-ext: reset cargo run --release --bin tek_groovebox -- -n tek \ -i "Midi-Bridge:nanoKEY Studio 1:(capture_0) nanoKEY Studio nanoKEY Studio _" \ - -o "Midi-Bridge:Komplete Audio 6 0:(playback_0) Komplete Audio 6 MIDI 1" \ -l "Komplete Audio 6 Pro:capture_AUX1" \ -r "Komplete Audio 6 Pro:capture_AUX1" \ -L "Komplete Audio 6 Pro:playback_AUX1" \ From ee2406c1aef67d56a892e9506fd82c62c1e3549b Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 28 Dec 2024 23:45:01 +0100 Subject: [PATCH 059/815] add rust-jack submodule --- .gitmodules | 4 ++++ rust-jack | 1 + 2 files changed, 5 insertions(+) create mode 160000 rust-jack diff --git a/.gitmodules b/.gitmodules index e69de29b..c32def4c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "rust-jack"] + path = rust-jack + url = git@codeberg.org:unspeaker/rust-jack.git + branch = timebase diff --git a/rust-jack b/rust-jack new file mode 160000 index 00000000..29e8b0be --- /dev/null +++ b/rust-jack @@ -0,0 +1 @@ +Subproject commit 29e8b0be4b34e4f0f669d34cecf9a4a638d27739 From 4812012f394cda135934d4d2ce9bb7a5fa3d0fbf Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sat, 28 Dec 2024 23:45:08 +0100 Subject: [PATCH 060/815] flatten monitoring --- crates/tek/src/midi/midi_rec.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/crates/tek/src/midi/midi_rec.rs b/crates/tek/src/midi/midi_rec.rs index 6c2bb768..02e75213 100644 --- a/crates/tek/src/midi/midi_rec.rs +++ b/crates/tek/src/midi/midi_rec.rs @@ -19,7 +19,9 @@ pub trait MidiRecordApi: HasClock + HasPlayPhrase + HasMidiIns { for input in self.midi_ins_mut().iter() { for (sample, event, bytes) in parse_midi_input(input.iter(scope)) { if let LiveEvent::Midi { message, .. } = event { - midi_buf[sample].push(bytes.to_vec()); + if self.monitoring() { + midi_buf[sample].push(bytes.to_vec()); + } update_keys(&mut notes_in.write().unwrap(), &message); } } @@ -46,14 +48,14 @@ pub trait MidiRecordApi: HasClock + HasPlayPhrase + HasMidiIns { phrase: &Option>>, midi_buf: &mut Vec>> ) { - let sample0 = scope.last_frame_time() as usize; - let start = started.sample.get() as usize; - let recording = self.recording(); - let timebase = self.clock().timebase().clone(); - let quant = self.clock().quant.get(); if let Some(phrase) = phrase { + let sample0 = scope.last_frame_time() as usize; + let start = started.sample.get() as usize; + let recording = self.recording(); + let timebase = self.clock().timebase().clone(); + let quant = self.clock().quant.get(); let mut phrase = phrase.write().unwrap(); - let length = phrase.length; + let length = phrase.length; for input in self.midi_ins_mut().iter() { for (sample, event, bytes) in parse_midi_input(input.iter(scope)) { if let LiveEvent::Midi { message, .. } = event { From c36802bad9de0b6c008bc58987c2606ca564b4bc Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 29 Dec 2024 00:00:42 +0100 Subject: [PATCH 061/815] use rust-jack from submodule --- Cargo.lock | 34 +++++++++++++++++++++++++-------- crates/tek/Cargo.toml | 2 +- crates/tek/src/midi/midi_rec.rs | 3 ++- rust-jack | 2 +- 4 files changed, 30 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cf73e7ff..9b015ea2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -261,6 +261,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.5" @@ -311,6 +320,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "ctor" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "either" version = "1.13.0" @@ -442,10 +461,11 @@ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "jack" version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78a4ae24f4ee29676aef8330fed1104e72f314cab16643dbeb61bfd99b4a8273" dependencies = [ + "approx", "bitflags 2.6.0", + "crossbeam-channel", + "ctor", "jack-sys", "lazy_static", "libc", @@ -455,10 +475,8 @@ dependencies = [ [[package]] name = "jack-sys" version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6013b7619b95a22b576dfb43296faa4ecbe40abbdb97dfd22ead520775fc86ab" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", "lazy_static", "libc", "libloading", @@ -490,12 +508,12 @@ checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "libloading" -version = "0.7.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "winapi", + "windows-targets 0.52.6", ] [[package]] diff --git a/crates/tek/Cargo.toml b/crates/tek/Cargo.toml index 71d2ad10..00c324a6 100644 --- a/crates/tek/Cargo.toml +++ b/crates/tek/Cargo.toml @@ -11,7 +11,7 @@ backtrace = "0.3.72" better-panic = "0.3.0" clojure-reader = "0.1.0" crossterm = "0.27" -jack = "0.13" +jack = { path = "../../rust-jack" } livi = "0.7.4" midly = "0.5" once_cell = "1.19.0" diff --git a/crates/tek/src/midi/midi_rec.rs b/crates/tek/src/midi/midi_rec.rs index 02e75213..6d65e427 100644 --- a/crates/tek/src/midi/midi_rec.rs +++ b/crates/tek/src/midi/midi_rec.rs @@ -16,10 +16,11 @@ pub trait MidiRecordApi: HasClock + HasPlayPhrase + HasMidiIns { fn monitor (&mut self, scope: &ProcessScope, midi_buf: &mut Vec>>) { // For highlighting keys and note repeat let notes_in = self.notes_in().clone(); + let monitoring = self.monitoring(); for input in self.midi_ins_mut().iter() { for (sample, event, bytes) in parse_midi_input(input.iter(scope)) { if let LiveEvent::Midi { message, .. } = event { - if self.monitoring() { + if monitoring { midi_buf[sample].push(bytes.to_vec()); } update_keys(&mut notes_in.write().unwrap(), &message); diff --git a/rust-jack b/rust-jack index 29e8b0be..5256af1d 160000 --- a/rust-jack +++ b/rust-jack @@ -1 +1 @@ -Subproject commit 29e8b0be4b34e4f0f669d34cecf9a4a638d27739 +Subproject commit 5256af1ddea221dd78f22e48c0a72a3842cad26d From 7c4e1e216609c358720f72077266515e35c7c391 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 29 Dec 2024 00:04:26 +0100 Subject: [PATCH 062/815] seek to start --- crates/tek/src/groovebox.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/crates/tek/src/groovebox.rs b/crates/tek/src/groovebox.rs index fbad7ed9..713dd4bb 100644 --- a/crates/tek/src/groovebox.rs +++ b/crates/tek/src/groovebox.rs @@ -186,22 +186,18 @@ input_to_command!(GrooveboxCommand: |state: GrooveboxTui, input|match input todo!("keyboard") }, - // Transport: Play/pause - key_pat!(Char(' ')) => Clock( - if state.clock().is_stopped() { Play(None) } else { Pause(None) } - ), - // Transport: Play from start or rewind to start - key_pat!(Shift-Char(' ')) => Clock( + key_pat!(Char(' ')) => Clock( if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) } ), // Tab: Toggle visibility of phrase pool column - key_pat!(Tab) => Pool(PoolCommand::Show(!state.pool.visible)), + key_pat!(Tab) => Pool(PoolCommand::Show(!state.pool.visible)), + // q: Enqueue currently edited phrase - key_pat!(Char('q')) => Enqueue(Some(state.pool.phrase().clone())), + key_pat!(Char('q')) => Enqueue(Some(state.pool.phrase().clone())), // 0: Enqueue phrase 0 (stop all) - key_pat!(Char('0')) => Enqueue(Some(state.pool.phrases()[0].clone())), + key_pat!(Char('0')) => Enqueue(Some(state.pool.phrases()[0].clone())), key_pat!(Shift-Char('R')) => Sampler(if state.sampler.recording.is_some() { SamplerCommand::RecordFinish From d926422c67539e217bf007366cc4d0a9b16714d4 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 29 Dec 2024 00:10:30 +0100 Subject: [PATCH 063/815] flatten workspace into 1 crate --- {crates/tek.old => .old}/Cargo.toml | 0 {crates/tek.old => .old}/README.md | 0 {crates/tek.old => .old}/example.edn | 0 {crates/tek.old => .old}/src/app.rs | 0 {crates/tek.old => .old}/src/app_focus.rs | 0 {crates/tek.old => .old}/src/app_paths.rs | 0 {crates/tek.old => .old}/src/cli.rs | 0 {crates/tek.old => .old}/src/control.rs | 0 {crates/tek.old => .old}/src/edn.rs | 0 {crates/tek.old => .old}/src/help.rs | 0 {crates/tek.old => .old}/src/main.rs | 0 {crates/tek.old => .old}/src/setup.rs | 0 Cargo.lock | 39 +--------- Cargo.toml | 71 +++++++++++++++++-- {crates/cli/src => bin}/cli_arranger.rs | 0 {crates/cli/src => bin}/cli_groovebox.rs | 0 {crates/cli/src => bin}/cli_sampler.rs | 0 {crates/cli/src => bin}/cli_sequencer.rs | 0 {crates/cli/src => bin}/cli_transport.rs | 0 {crates/cli/src => bin}/lib.rs | 0 {crates/cli/src => bin}/todo_cli_mixer.rs | 0 {crates/cli/src => bin}/todo_cli_plugin.rs | 0 {crates/cli/src => bin}/todo_cli_sampler.rs | 0 crates/cli/Cargo.toml | 40 ----------- crates/edn/Cargo.toml | 8 --- crates/tek/Cargo.toml | 31 -------- .../tek/examples => examples}/demo.rs.fixme | 0 .../examples => examples}/demo_bsp.rs.fixme | 0 .../midi_import.rs.fixme | 0 {crates/tek/examples => examples}/mixer.edn | 0 .../tek/examples => examples}/sequencer.edn | 0 {crates/tek => src}/README.md | 0 {crates/tek/src => src}/api.rs | 0 {crates/tek => src}/architecture.svg | 0 {crates/tek/src => src}/arranger.rs | 0 .../src => src}/arranger/arranger_command.rs | 0 .../tek/src => src}/arranger/arranger_h.rs | 0 .../tek/src => src}/arranger/arranger_mode.rs | 0 .../src => src}/arranger/arranger_scene.rs | 0 .../src => src}/arranger/arranger_select.rs | 0 .../src => src}/arranger/arranger_track.rs | 0 .../tek/src => src}/arranger/arranger_v.rs | 0 .../arranger/arranger_v/v_clips.rs | 0 .../arranger/arranger_v/v_cursor.rs | 0 .../src => src}/arranger/arranger_v/v_head.rs | 0 .../src => src}/arranger/arranger_v/v_io.rs | 0 .../src => src}/arranger/arranger_v/v_sep.rs | 0 {crates/tek/src => src}/audio.rs | 0 {crates/tek/src => src}/core.rs | 0 {crates/tek/src => src}/core/color.rs | 0 {crates/tek/src => src}/core/command.rs | 0 {crates/tek/src => src}/core/engine.rs | 0 {crates/tek/src => src}/core/focus.rs | 0 {crates/tek/src => src}/core/input.rs | 0 {crates/tek/src => src}/core/output.rs | 0 {crates/tek/src => src}/core/test.rs | 0 crates/edn/src/lib.rs => src/edn.rs | 3 +- {crates/tek/src => src}/groovebox.rs | 0 {crates/tek/src => src}/jack.rs | 0 {crates/tek/src => src}/jack/activate.rs | 0 {crates/tek/src => src}/jack/client.rs | 0 {crates/tek/src => src}/jack/jack_event.rs | 0 {crates/tek/src => src}/jack/ports.rs | 0 {crates/tek/src => src}/lib.rs | 0 {crates/tek/src => src}/meter.rs | 0 {crates/tek/src => src}/midi.rs | 0 {crates/tek/src => src}/midi/midi_clip.rs | 0 {crates/tek/src => src}/midi/midi_editor.rs | 0 {crates/tek/src => src}/midi/midi_launch.rs | 0 {crates/tek/src => src}/midi/midi_note.rs | 0 {crates/tek/src => src}/midi/midi_play.rs | 0 {crates/tek/src => src}/midi/midi_point.rs | 0 {crates/tek/src => src}/midi/midi_pool.rs | 0 {crates/tek/src => src}/midi/midi_range.rs | 0 {crates/tek/src => src}/midi/midi_rec.rs | 0 {crates/tek/src => src}/midi/midi_view.rs | 0 {crates/tek/src => src}/mixer.rs | 0 {crates/tek/src => src}/piano_h.rs | 0 .../tek/src => src}/piano_h/piano_h_cursor.rs | 0 .../tek/src => src}/piano_h/piano_h_keys.rs | 0 .../tek/src => src}/piano_h/piano_h_notes.rs | 0 .../tek/src => src}/piano_h/piano_h_time.rs | 0 {crates/tek/src => src}/piano_v.rs | 0 {crates/tek/src => src}/plugin.rs | 0 {crates/tek/src => src}/plugin/lv2.rs | 0 {crates/tek/src => src}/plugin/lv2_gui.rs | 0 {crates/tek/src => src}/plugin/lv2_tui.rs | 0 {crates/tek/src => src}/plugin/vst2_tui.rs | 0 {crates/tek/src => src}/plugin/vst3_tui.rs | 0 {crates/tek/src => src}/pool.rs | 0 {crates/tek/src => src}/pool/phrase_length.rs | 0 {crates/tek/src => src}/pool/phrase_rename.rs | 0 .../tek/src => src}/pool/phrase_selector.rs | 0 {crates/tek/src => src}/sampler.rs | 0 {crates/tek/src => src}/sampler/sample.rs | 0 .../tek/src => src}/sampler/sample_import.rs | 0 .../tek/src => src}/sampler/sample_viewer.rs | 0 .../tek/src => src}/sampler/sampler_tui.rs | 0 {crates/tek/src => src}/sampler/voice.rs | 0 {crates/tek/src => src}/sequencer.rs | 0 {crates/tek/src => src}/space.rs | 0 {crates/tek/src => src}/space/align.rs | 0 {crates/tek/src => src}/space/bsp.rs | 0 {crates/tek/src => src}/space/collect.rs | 0 {crates/tek/src => src}/space/cond.rs | 0 {crates/tek/src => src}/space/fill.rs | 0 {crates/tek/src => src}/space/fixed.rs | 0 {crates/tek/src => src}/space/inset_outset.rs | 0 {crates/tek/src => src}/space/layers.rs | 0 {crates/tek/src => src}/space/map_reduce.rs | 0 {crates/tek/src => src}/space/measure.rs | 0 {crates/tek/src => src}/space/min_max.rs | 0 {crates/tek/src => src}/space/push_pull.rs | 0 {crates/tek/src => src}/space/scroll.rs | 0 {crates/tek/src => src}/space/shrink_grow.rs | 0 {crates/tek/src => src}/space/split.rs | 0 {crates/tek/src => src}/space/stack.rs | 0 {crates/tek/src => src}/status.rs | 0 .../tek/src => src}/status/status_arranger.rs | 0 {crates/tek/src => src}/status/status_edit.rs | 0 .../src => src}/status/status_groovebox.rs | 0 .../src => src}/status/status_sequencer.rs | 0 {crates/tek/src => src}/test.rs | 0 {crates/tek/src => src}/time.rs | 0 {crates/tek/src => src}/time/clock.rs | 0 {crates/tek/src => src}/time/moment.rs | 0 {crates/tek/src => src}/time/perf.rs | 0 {crates/tek/src => src}/time/pulse.rs | 0 {crates/tek/src => src}/time/sr.rs | 0 {crates/tek/src => src}/time/unit.rs | 0 {crates/tek/src => src}/transport.rs | 0 {crates/tek/src => src}/tui.rs | 0 {crates/tek/src => src}/tui/file_browser.rs | 0 {crates/tek/src => src}/tui/tui_border.rs | 0 {crates/tek/src => src}/tui/tui_input.rs | 0 {crates/tek/src => src}/tui/tui_output.rs | 0 {crates/tek/src => src}/tui/tui_style.rs | 0 {crates/tek/src => src}/tui/tui_theme.rs | 0 {crates/suil => suil}/Cargo.toml | 0 {crates/suil => suil}/build.rs | 0 {crates/suil => suil}/src/bound.rs | 0 {crates/suil => suil}/src/gtk.rs | 0 {crates/suil => suil}/src/lib.rs | 0 {crates/suil => suil}/src/test.rs | 0 {crates/suil => suil}/stdbool.h | 0 {crates/suil => suil}/stdint.h | 0 {crates/suil => suil}/wrapper.h | 0 147 files changed, 66 insertions(+), 126 deletions(-) rename {crates/tek.old => .old}/Cargo.toml (100%) rename {crates/tek.old => .old}/README.md (100%) rename {crates/tek.old => .old}/example.edn (100%) rename {crates/tek.old => .old}/src/app.rs (100%) rename {crates/tek.old => .old}/src/app_focus.rs (100%) rename {crates/tek.old => .old}/src/app_paths.rs (100%) rename {crates/tek.old => .old}/src/cli.rs (100%) rename {crates/tek.old => .old}/src/control.rs (100%) rename {crates/tek.old => .old}/src/edn.rs (100%) rename {crates/tek.old => .old}/src/help.rs (100%) rename {crates/tek.old => .old}/src/main.rs (100%) rename {crates/tek.old => .old}/src/setup.rs (100%) rename {crates/cli/src => bin}/cli_arranger.rs (100%) rename {crates/cli/src => bin}/cli_groovebox.rs (100%) rename {crates/cli/src => bin}/cli_sampler.rs (100%) rename {crates/cli/src => bin}/cli_sequencer.rs (100%) rename {crates/cli/src => bin}/cli_transport.rs (100%) rename {crates/cli/src => bin}/lib.rs (100%) rename {crates/cli/src => bin}/todo_cli_mixer.rs (100%) rename {crates/cli/src => bin}/todo_cli_plugin.rs (100%) rename {crates/cli/src => bin}/todo_cli_sampler.rs (100%) delete mode 100644 crates/cli/Cargo.toml delete mode 100644 crates/edn/Cargo.toml delete mode 100644 crates/tek/Cargo.toml rename {crates/tek/examples => examples}/demo.rs.fixme (100%) rename {crates/tek/examples => examples}/demo_bsp.rs.fixme (100%) rename {crates/tek/examples => examples}/midi_import.rs.fixme (100%) rename {crates/tek/examples => examples}/mixer.edn (100%) rename {crates/tek/examples => examples}/sequencer.edn (100%) rename {crates/tek => src}/README.md (100%) rename {crates/tek/src => src}/api.rs (100%) rename {crates/tek => src}/architecture.svg (100%) rename {crates/tek/src => src}/arranger.rs (100%) rename {crates/tek/src => src}/arranger/arranger_command.rs (100%) rename {crates/tek/src => src}/arranger/arranger_h.rs (100%) rename {crates/tek/src => src}/arranger/arranger_mode.rs (100%) rename {crates/tek/src => src}/arranger/arranger_scene.rs (100%) rename {crates/tek/src => src}/arranger/arranger_select.rs (100%) rename {crates/tek/src => src}/arranger/arranger_track.rs (100%) rename {crates/tek/src => src}/arranger/arranger_v.rs (100%) rename {crates/tek/src => src}/arranger/arranger_v/v_clips.rs (100%) rename {crates/tek/src => src}/arranger/arranger_v/v_cursor.rs (100%) rename {crates/tek/src => src}/arranger/arranger_v/v_head.rs (100%) rename {crates/tek/src => src}/arranger/arranger_v/v_io.rs (100%) rename {crates/tek/src => src}/arranger/arranger_v/v_sep.rs (100%) rename {crates/tek/src => src}/audio.rs (100%) rename {crates/tek/src => src}/core.rs (100%) rename {crates/tek/src => src}/core/color.rs (100%) rename {crates/tek/src => src}/core/command.rs (100%) rename {crates/tek/src => src}/core/engine.rs (100%) rename {crates/tek/src => src}/core/focus.rs (100%) rename {crates/tek/src => src}/core/input.rs (100%) rename {crates/tek/src => src}/core/output.rs (100%) rename {crates/tek/src => src}/core/test.rs (100%) rename crates/edn/src/lib.rs => src/edn.rs (99%) rename {crates/tek/src => src}/groovebox.rs (100%) rename {crates/tek/src => src}/jack.rs (100%) rename {crates/tek/src => src}/jack/activate.rs (100%) rename {crates/tek/src => src}/jack/client.rs (100%) rename {crates/tek/src => src}/jack/jack_event.rs (100%) rename {crates/tek/src => src}/jack/ports.rs (100%) rename {crates/tek/src => src}/lib.rs (100%) rename {crates/tek/src => src}/meter.rs (100%) rename {crates/tek/src => src}/midi.rs (100%) rename {crates/tek/src => src}/midi/midi_clip.rs (100%) rename {crates/tek/src => src}/midi/midi_editor.rs (100%) rename {crates/tek/src => src}/midi/midi_launch.rs (100%) rename {crates/tek/src => src}/midi/midi_note.rs (100%) rename {crates/tek/src => src}/midi/midi_play.rs (100%) rename {crates/tek/src => src}/midi/midi_point.rs (100%) rename {crates/tek/src => src}/midi/midi_pool.rs (100%) rename {crates/tek/src => src}/midi/midi_range.rs (100%) rename {crates/tek/src => src}/midi/midi_rec.rs (100%) rename {crates/tek/src => src}/midi/midi_view.rs (100%) rename {crates/tek/src => src}/mixer.rs (100%) rename {crates/tek/src => src}/piano_h.rs (100%) rename {crates/tek/src => src}/piano_h/piano_h_cursor.rs (100%) rename {crates/tek/src => src}/piano_h/piano_h_keys.rs (100%) rename {crates/tek/src => src}/piano_h/piano_h_notes.rs (100%) rename {crates/tek/src => src}/piano_h/piano_h_time.rs (100%) rename {crates/tek/src => src}/piano_v.rs (100%) rename {crates/tek/src => src}/plugin.rs (100%) rename {crates/tek/src => src}/plugin/lv2.rs (100%) rename {crates/tek/src => src}/plugin/lv2_gui.rs (100%) rename {crates/tek/src => src}/plugin/lv2_tui.rs (100%) rename {crates/tek/src => src}/plugin/vst2_tui.rs (100%) rename {crates/tek/src => src}/plugin/vst3_tui.rs (100%) rename {crates/tek/src => src}/pool.rs (100%) rename {crates/tek/src => src}/pool/phrase_length.rs (100%) rename {crates/tek/src => src}/pool/phrase_rename.rs (100%) rename {crates/tek/src => src}/pool/phrase_selector.rs (100%) rename {crates/tek/src => src}/sampler.rs (100%) rename {crates/tek/src => src}/sampler/sample.rs (100%) rename {crates/tek/src => src}/sampler/sample_import.rs (100%) rename {crates/tek/src => src}/sampler/sample_viewer.rs (100%) rename {crates/tek/src => src}/sampler/sampler_tui.rs (100%) rename {crates/tek/src => src}/sampler/voice.rs (100%) rename {crates/tek/src => src}/sequencer.rs (100%) rename {crates/tek/src => src}/space.rs (100%) rename {crates/tek/src => src}/space/align.rs (100%) rename {crates/tek/src => src}/space/bsp.rs (100%) rename {crates/tek/src => src}/space/collect.rs (100%) rename {crates/tek/src => src}/space/cond.rs (100%) rename {crates/tek/src => src}/space/fill.rs (100%) rename {crates/tek/src => src}/space/fixed.rs (100%) rename {crates/tek/src => src}/space/inset_outset.rs (100%) rename {crates/tek/src => src}/space/layers.rs (100%) rename {crates/tek/src => src}/space/map_reduce.rs (100%) rename {crates/tek/src => src}/space/measure.rs (100%) rename {crates/tek/src => src}/space/min_max.rs (100%) rename {crates/tek/src => src}/space/push_pull.rs (100%) rename {crates/tek/src => src}/space/scroll.rs (100%) rename {crates/tek/src => src}/space/shrink_grow.rs (100%) rename {crates/tek/src => src}/space/split.rs (100%) rename {crates/tek/src => src}/space/stack.rs (100%) rename {crates/tek/src => src}/status.rs (100%) rename {crates/tek/src => src}/status/status_arranger.rs (100%) rename {crates/tek/src => src}/status/status_edit.rs (100%) rename {crates/tek/src => src}/status/status_groovebox.rs (100%) rename {crates/tek/src => src}/status/status_sequencer.rs (100%) rename {crates/tek/src => src}/test.rs (100%) rename {crates/tek/src => src}/time.rs (100%) rename {crates/tek/src => src}/time/clock.rs (100%) rename {crates/tek/src => src}/time/moment.rs (100%) rename {crates/tek/src => src}/time/perf.rs (100%) rename {crates/tek/src => src}/time/pulse.rs (100%) rename {crates/tek/src => src}/time/sr.rs (100%) rename {crates/tek/src => src}/time/unit.rs (100%) rename {crates/tek/src => src}/transport.rs (100%) rename {crates/tek/src => src}/tui.rs (100%) rename {crates/tek/src => src}/tui/file_browser.rs (100%) rename {crates/tek/src => src}/tui/tui_border.rs (100%) rename {crates/tek/src => src}/tui/tui_input.rs (100%) rename {crates/tek/src => src}/tui/tui_output.rs (100%) rename {crates/tek/src => src}/tui/tui_style.rs (100%) rename {crates/tek/src => src}/tui/tui_theme.rs (100%) rename {crates/suil => suil}/Cargo.toml (100%) rename {crates/suil => suil}/build.rs (100%) rename {crates/suil => suil}/src/bound.rs (100%) rename {crates/suil => suil}/src/gtk.rs (100%) rename {crates/suil => suil}/src/lib.rs (100%) rename {crates/suil => suil}/src/test.rs (100%) rename {crates/suil => suil}/stdbool.h (100%) rename {crates/suil => suil}/stdint.h (100%) rename {crates/suil => suil}/wrapper.h (100%) diff --git a/crates/tek.old/Cargo.toml b/.old/Cargo.toml similarity index 100% rename from crates/tek.old/Cargo.toml rename to .old/Cargo.toml diff --git a/crates/tek.old/README.md b/.old/README.md similarity index 100% rename from crates/tek.old/README.md rename to .old/README.md diff --git a/crates/tek.old/example.edn b/.old/example.edn similarity index 100% rename from crates/tek.old/example.edn rename to .old/example.edn diff --git a/crates/tek.old/src/app.rs b/.old/src/app.rs similarity index 100% rename from crates/tek.old/src/app.rs rename to .old/src/app.rs diff --git a/crates/tek.old/src/app_focus.rs b/.old/src/app_focus.rs similarity index 100% rename from crates/tek.old/src/app_focus.rs rename to .old/src/app_focus.rs diff --git a/crates/tek.old/src/app_paths.rs b/.old/src/app_paths.rs similarity index 100% rename from crates/tek.old/src/app_paths.rs rename to .old/src/app_paths.rs diff --git a/crates/tek.old/src/cli.rs b/.old/src/cli.rs similarity index 100% rename from crates/tek.old/src/cli.rs rename to .old/src/cli.rs diff --git a/crates/tek.old/src/control.rs b/.old/src/control.rs similarity index 100% rename from crates/tek.old/src/control.rs rename to .old/src/control.rs diff --git a/crates/tek.old/src/edn.rs b/.old/src/edn.rs similarity index 100% rename from crates/tek.old/src/edn.rs rename to .old/src/edn.rs diff --git a/crates/tek.old/src/help.rs b/.old/src/help.rs similarity index 100% rename from crates/tek.old/src/help.rs rename to .old/src/help.rs diff --git a/crates/tek.old/src/main.rs b/.old/src/main.rs similarity index 100% rename from crates/tek.old/src/main.rs rename to .old/src/main.rs diff --git a/crates/tek.old/src/setup.rs b/.old/src/setup.rs similarity index 100% rename from crates/tek.old/src/setup.rs rename to .old/src/setup.rs diff --git a/Cargo.lock b/Cargo.lock index 9b015ea2..012efc81 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -261,15 +261,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "crossbeam-channel" -version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ba6d68e24814cb8de6bb986db8222d3a027d15872cabc0d18817bc3c0e4471" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "crossbeam-deque" version = "0.8.5" @@ -320,16 +311,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "ctor" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" -dependencies = [ - "quote", - "syn", -] - [[package]] name = "either" version = "1.13.0" @@ -462,10 +443,7 @@ checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" name = "jack" version = "0.13.0" dependencies = [ - "approx", "bitflags 2.6.0", - "crossbeam-channel", - "ctor", "jack-sys", "lazy_static", "libc", @@ -1286,6 +1264,7 @@ dependencies = [ "atomic_float", "backtrace", "better-panic", + "clap", "clojure-reader", "crossterm", "jack", @@ -1302,22 +1281,6 @@ dependencies = [ "wavers", ] -[[package]] -name = "tek_cli" -version = "0.2.0" -dependencies = [ - "clap", - "tek", -] - -[[package]] -name = "tek_edn" -version = "0.2.0" -dependencies = [ - "clojure-reader", - "tek", -] - [[package]] name = "thiserror" version = "1.0.69" diff --git a/Cargo.toml b/Cargo.toml index b502f8c8..b97ec445 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,67 @@ -[workspace] -resolver = "2" -members = [ - "crates/tek", - "crates/cli", - "crates/edn", -] +[package] +name = "tek" +edition = "2021" +version = "0.2.0" + +[dependencies] +#no_deadlocks = "1.3.2" +#vst3 = "0.1.0" +atomic_float = "1.0.0" +backtrace = "0.3.72" +better-panic = "0.3.0" +clojure-reader = "0.1.0" +crossterm = "0.27" +jack = { path = "./rust-jack" } +livi = "0.7.4" +midly = "0.5" +once_cell = "1.19.0" +palette = { version = "0.7.6", features = [ "random" ] } +quanta = "0.12.3" +rand = "0.8.5" +ratatui = { version = "0.26.3", features = [ "unstable-widget-ref", "underline-color" ] } +#suil-rs = { path = "../suil" } +symphonia = { version = "0.5.4", features = [ "all" ] } +toml = "0.8.12" +uuid = { version = "1.10.0", features = [ "v4" ] } +#vst = "0.4.0" +wavers = "1.4.3" +#winit = { version = "0.30.4", features = [ "x11" ] } +clap = { version = "4.5.4", features = [ "derive" ] } + +[[bin]] +name = "tek_arranger" +path = "bin/cli_arranger.rs" + +[[bin]] +name = "tek_sequencer" +path = "bin/cli_sequencer.rs" + +[[bin]] +name = "tek_groovebox" +path = "bin/cli_groovebox.rs" + +[[bin]] +name = "tek_transport" +path = "bin/cli_transport.rs" + +[[bin]] +name = "tek_sampler" +path = "bin/cli_sampler.rs" + +#[[bin]] +#name = "tek_mixer" +#path = "src/cli_mixer.rs" + +#[[bin]] +#name = "tek_track" +#path = "src/cli_track.rs" + +#[[bin]] +#name = "tek_plugin" +#path = "src/cli_plugin.rs" + +[lib] +path = "src/lib.rs" [profile.release] lto = true diff --git a/crates/cli/src/cli_arranger.rs b/bin/cli_arranger.rs similarity index 100% rename from crates/cli/src/cli_arranger.rs rename to bin/cli_arranger.rs diff --git a/crates/cli/src/cli_groovebox.rs b/bin/cli_groovebox.rs similarity index 100% rename from crates/cli/src/cli_groovebox.rs rename to bin/cli_groovebox.rs diff --git a/crates/cli/src/cli_sampler.rs b/bin/cli_sampler.rs similarity index 100% rename from crates/cli/src/cli_sampler.rs rename to bin/cli_sampler.rs diff --git a/crates/cli/src/cli_sequencer.rs b/bin/cli_sequencer.rs similarity index 100% rename from crates/cli/src/cli_sequencer.rs rename to bin/cli_sequencer.rs diff --git a/crates/cli/src/cli_transport.rs b/bin/cli_transport.rs similarity index 100% rename from crates/cli/src/cli_transport.rs rename to bin/cli_transport.rs diff --git a/crates/cli/src/lib.rs b/bin/lib.rs similarity index 100% rename from crates/cli/src/lib.rs rename to bin/lib.rs diff --git a/crates/cli/src/todo_cli_mixer.rs b/bin/todo_cli_mixer.rs similarity index 100% rename from crates/cli/src/todo_cli_mixer.rs rename to bin/todo_cli_mixer.rs diff --git a/crates/cli/src/todo_cli_plugin.rs b/bin/todo_cli_plugin.rs similarity index 100% rename from crates/cli/src/todo_cli_plugin.rs rename to bin/todo_cli_plugin.rs diff --git a/crates/cli/src/todo_cli_sampler.rs b/bin/todo_cli_sampler.rs similarity index 100% rename from crates/cli/src/todo_cli_sampler.rs rename to bin/todo_cli_sampler.rs diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml deleted file mode 100644 index 75d1321f..00000000 --- a/crates/cli/Cargo.toml +++ /dev/null @@ -1,40 +0,0 @@ -[package] -name = "tek_cli" -edition = "2021" -version = "0.2.0" - -[dependencies] -tek = { path = "../tek" } -clap = { version = "4.5.4", features = [ "derive" ] } - -[[bin]] -name = "tek_arranger" -path = "src/cli_arranger.rs" - -[[bin]] -name = "tek_sequencer" -path = "src/cli_sequencer.rs" - -[[bin]] -name = "tek_groovebox" -path = "src/cli_groovebox.rs" - -[[bin]] -name = "tek_transport" -path = "src/cli_transport.rs" - -[[bin]] -name = "tek_sampler" -path = "src/cli_sampler.rs" - -#[[bin]] -#name = "tek_mixer" -#path = "src/cli_mixer.rs" - -#[[bin]] -#name = "tek_track" -#path = "src/cli_track.rs" - -#[[bin]] -#name = "tek_plugin" -#path = "src/cli_plugin.rs" diff --git a/crates/edn/Cargo.toml b/crates/edn/Cargo.toml deleted file mode 100644 index 01bab065..00000000 --- a/crates/edn/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "tek_edn" -edition = "2021" -version = "0.2.0" - -[dependencies] -tek = { path = "../tek" } -clojure-reader = "0.1.0" diff --git a/crates/tek/Cargo.toml b/crates/tek/Cargo.toml deleted file mode 100644 index 00c324a6..00000000 --- a/crates/tek/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -[package] -name = "tek" -edition = "2021" -version = "0.2.0" - -[dependencies] -#no_deadlocks = "1.3.2" -#vst3 = "0.1.0" -atomic_float = "1.0.0" -backtrace = "0.3.72" -better-panic = "0.3.0" -clojure-reader = "0.1.0" -crossterm = "0.27" -jack = { path = "../../rust-jack" } -livi = "0.7.4" -midly = "0.5" -once_cell = "1.19.0" -palette = { version = "0.7.6", features = [ "random" ] } -quanta = "0.12.3" -rand = "0.8.5" -ratatui = { version = "0.26.3", features = [ "unstable-widget-ref", "underline-color" ] } -#suil-rs = { path = "../suil" } -symphonia = { version = "0.5.4", features = [ "all" ] } -toml = "0.8.12" -uuid = { version = "1.10.0", features = [ "v4" ] } -#vst = "0.4.0" -wavers = "1.4.3" -#winit = { version = "0.30.4", features = [ "x11" ] } - -[lib] -path = "src/lib.rs" diff --git a/crates/tek/examples/demo.rs.fixme b/examples/demo.rs.fixme similarity index 100% rename from crates/tek/examples/demo.rs.fixme rename to examples/demo.rs.fixme diff --git a/crates/tek/examples/demo_bsp.rs.fixme b/examples/demo_bsp.rs.fixme similarity index 100% rename from crates/tek/examples/demo_bsp.rs.fixme rename to examples/demo_bsp.rs.fixme diff --git a/crates/tek/examples/midi_import.rs.fixme b/examples/midi_import.rs.fixme similarity index 100% rename from crates/tek/examples/midi_import.rs.fixme rename to examples/midi_import.rs.fixme diff --git a/crates/tek/examples/mixer.edn b/examples/mixer.edn similarity index 100% rename from crates/tek/examples/mixer.edn rename to examples/mixer.edn diff --git a/crates/tek/examples/sequencer.edn b/examples/sequencer.edn similarity index 100% rename from crates/tek/examples/sequencer.edn rename to examples/sequencer.edn diff --git a/crates/tek/README.md b/src/README.md similarity index 100% rename from crates/tek/README.md rename to src/README.md diff --git a/crates/tek/src/api.rs b/src/api.rs similarity index 100% rename from crates/tek/src/api.rs rename to src/api.rs diff --git a/crates/tek/architecture.svg b/src/architecture.svg similarity index 100% rename from crates/tek/architecture.svg rename to src/architecture.svg diff --git a/crates/tek/src/arranger.rs b/src/arranger.rs similarity index 100% rename from crates/tek/src/arranger.rs rename to src/arranger.rs diff --git a/crates/tek/src/arranger/arranger_command.rs b/src/arranger/arranger_command.rs similarity index 100% rename from crates/tek/src/arranger/arranger_command.rs rename to src/arranger/arranger_command.rs diff --git a/crates/tek/src/arranger/arranger_h.rs b/src/arranger/arranger_h.rs similarity index 100% rename from crates/tek/src/arranger/arranger_h.rs rename to src/arranger/arranger_h.rs diff --git a/crates/tek/src/arranger/arranger_mode.rs b/src/arranger/arranger_mode.rs similarity index 100% rename from crates/tek/src/arranger/arranger_mode.rs rename to src/arranger/arranger_mode.rs diff --git a/crates/tek/src/arranger/arranger_scene.rs b/src/arranger/arranger_scene.rs similarity index 100% rename from crates/tek/src/arranger/arranger_scene.rs rename to src/arranger/arranger_scene.rs diff --git a/crates/tek/src/arranger/arranger_select.rs b/src/arranger/arranger_select.rs similarity index 100% rename from crates/tek/src/arranger/arranger_select.rs rename to src/arranger/arranger_select.rs diff --git a/crates/tek/src/arranger/arranger_track.rs b/src/arranger/arranger_track.rs similarity index 100% rename from crates/tek/src/arranger/arranger_track.rs rename to src/arranger/arranger_track.rs diff --git a/crates/tek/src/arranger/arranger_v.rs b/src/arranger/arranger_v.rs similarity index 100% rename from crates/tek/src/arranger/arranger_v.rs rename to src/arranger/arranger_v.rs diff --git a/crates/tek/src/arranger/arranger_v/v_clips.rs b/src/arranger/arranger_v/v_clips.rs similarity index 100% rename from crates/tek/src/arranger/arranger_v/v_clips.rs rename to src/arranger/arranger_v/v_clips.rs diff --git a/crates/tek/src/arranger/arranger_v/v_cursor.rs b/src/arranger/arranger_v/v_cursor.rs similarity index 100% rename from crates/tek/src/arranger/arranger_v/v_cursor.rs rename to src/arranger/arranger_v/v_cursor.rs diff --git a/crates/tek/src/arranger/arranger_v/v_head.rs b/src/arranger/arranger_v/v_head.rs similarity index 100% rename from crates/tek/src/arranger/arranger_v/v_head.rs rename to src/arranger/arranger_v/v_head.rs diff --git a/crates/tek/src/arranger/arranger_v/v_io.rs b/src/arranger/arranger_v/v_io.rs similarity index 100% rename from crates/tek/src/arranger/arranger_v/v_io.rs rename to src/arranger/arranger_v/v_io.rs diff --git a/crates/tek/src/arranger/arranger_v/v_sep.rs b/src/arranger/arranger_v/v_sep.rs similarity index 100% rename from crates/tek/src/arranger/arranger_v/v_sep.rs rename to src/arranger/arranger_v/v_sep.rs diff --git a/crates/tek/src/audio.rs b/src/audio.rs similarity index 100% rename from crates/tek/src/audio.rs rename to src/audio.rs diff --git a/crates/tek/src/core.rs b/src/core.rs similarity index 100% rename from crates/tek/src/core.rs rename to src/core.rs diff --git a/crates/tek/src/core/color.rs b/src/core/color.rs similarity index 100% rename from crates/tek/src/core/color.rs rename to src/core/color.rs diff --git a/crates/tek/src/core/command.rs b/src/core/command.rs similarity index 100% rename from crates/tek/src/core/command.rs rename to src/core/command.rs diff --git a/crates/tek/src/core/engine.rs b/src/core/engine.rs similarity index 100% rename from crates/tek/src/core/engine.rs rename to src/core/engine.rs diff --git a/crates/tek/src/core/focus.rs b/src/core/focus.rs similarity index 100% rename from crates/tek/src/core/focus.rs rename to src/core/focus.rs diff --git a/crates/tek/src/core/input.rs b/src/core/input.rs similarity index 100% rename from crates/tek/src/core/input.rs rename to src/core/input.rs diff --git a/crates/tek/src/core/output.rs b/src/core/output.rs similarity index 100% rename from crates/tek/src/core/output.rs rename to src/core/output.rs diff --git a/crates/tek/src/core/test.rs b/src/core/test.rs similarity index 100% rename from crates/tek/src/core/test.rs rename to src/core/test.rs diff --git a/crates/edn/src/lib.rs b/src/edn.rs similarity index 99% rename from crates/edn/src/lib.rs rename to src/edn.rs index 1465b951..8067b562 100644 --- a/crates/edn/src/lib.rs +++ b/src/edn.rs @@ -1,7 +1,6 @@ -#[allow(unused_imports)] use tek::{*, jack::*, plugin::*}; +use crate::*; use std::sync::{Arc, RwLock}; use std::collections::BTreeMap; - pub use clojure_reader::edn::Edn; //pub use clojure_reader::{edn::{read, Edn}, error::Error as EdnError}; diff --git a/crates/tek/src/groovebox.rs b/src/groovebox.rs similarity index 100% rename from crates/tek/src/groovebox.rs rename to src/groovebox.rs diff --git a/crates/tek/src/jack.rs b/src/jack.rs similarity index 100% rename from crates/tek/src/jack.rs rename to src/jack.rs diff --git a/crates/tek/src/jack/activate.rs b/src/jack/activate.rs similarity index 100% rename from crates/tek/src/jack/activate.rs rename to src/jack/activate.rs diff --git a/crates/tek/src/jack/client.rs b/src/jack/client.rs similarity index 100% rename from crates/tek/src/jack/client.rs rename to src/jack/client.rs diff --git a/crates/tek/src/jack/jack_event.rs b/src/jack/jack_event.rs similarity index 100% rename from crates/tek/src/jack/jack_event.rs rename to src/jack/jack_event.rs diff --git a/crates/tek/src/jack/ports.rs b/src/jack/ports.rs similarity index 100% rename from crates/tek/src/jack/ports.rs rename to src/jack/ports.rs diff --git a/crates/tek/src/lib.rs b/src/lib.rs similarity index 100% rename from crates/tek/src/lib.rs rename to src/lib.rs diff --git a/crates/tek/src/meter.rs b/src/meter.rs similarity index 100% rename from crates/tek/src/meter.rs rename to src/meter.rs diff --git a/crates/tek/src/midi.rs b/src/midi.rs similarity index 100% rename from crates/tek/src/midi.rs rename to src/midi.rs diff --git a/crates/tek/src/midi/midi_clip.rs b/src/midi/midi_clip.rs similarity index 100% rename from crates/tek/src/midi/midi_clip.rs rename to src/midi/midi_clip.rs diff --git a/crates/tek/src/midi/midi_editor.rs b/src/midi/midi_editor.rs similarity index 100% rename from crates/tek/src/midi/midi_editor.rs rename to src/midi/midi_editor.rs diff --git a/crates/tek/src/midi/midi_launch.rs b/src/midi/midi_launch.rs similarity index 100% rename from crates/tek/src/midi/midi_launch.rs rename to src/midi/midi_launch.rs diff --git a/crates/tek/src/midi/midi_note.rs b/src/midi/midi_note.rs similarity index 100% rename from crates/tek/src/midi/midi_note.rs rename to src/midi/midi_note.rs diff --git a/crates/tek/src/midi/midi_play.rs b/src/midi/midi_play.rs similarity index 100% rename from crates/tek/src/midi/midi_play.rs rename to src/midi/midi_play.rs diff --git a/crates/tek/src/midi/midi_point.rs b/src/midi/midi_point.rs similarity index 100% rename from crates/tek/src/midi/midi_point.rs rename to src/midi/midi_point.rs diff --git a/crates/tek/src/midi/midi_pool.rs b/src/midi/midi_pool.rs similarity index 100% rename from crates/tek/src/midi/midi_pool.rs rename to src/midi/midi_pool.rs diff --git a/crates/tek/src/midi/midi_range.rs b/src/midi/midi_range.rs similarity index 100% rename from crates/tek/src/midi/midi_range.rs rename to src/midi/midi_range.rs diff --git a/crates/tek/src/midi/midi_rec.rs b/src/midi/midi_rec.rs similarity index 100% rename from crates/tek/src/midi/midi_rec.rs rename to src/midi/midi_rec.rs diff --git a/crates/tek/src/midi/midi_view.rs b/src/midi/midi_view.rs similarity index 100% rename from crates/tek/src/midi/midi_view.rs rename to src/midi/midi_view.rs diff --git a/crates/tek/src/mixer.rs b/src/mixer.rs similarity index 100% rename from crates/tek/src/mixer.rs rename to src/mixer.rs diff --git a/crates/tek/src/piano_h.rs b/src/piano_h.rs similarity index 100% rename from crates/tek/src/piano_h.rs rename to src/piano_h.rs diff --git a/crates/tek/src/piano_h/piano_h_cursor.rs b/src/piano_h/piano_h_cursor.rs similarity index 100% rename from crates/tek/src/piano_h/piano_h_cursor.rs rename to src/piano_h/piano_h_cursor.rs diff --git a/crates/tek/src/piano_h/piano_h_keys.rs b/src/piano_h/piano_h_keys.rs similarity index 100% rename from crates/tek/src/piano_h/piano_h_keys.rs rename to src/piano_h/piano_h_keys.rs diff --git a/crates/tek/src/piano_h/piano_h_notes.rs b/src/piano_h/piano_h_notes.rs similarity index 100% rename from crates/tek/src/piano_h/piano_h_notes.rs rename to src/piano_h/piano_h_notes.rs diff --git a/crates/tek/src/piano_h/piano_h_time.rs b/src/piano_h/piano_h_time.rs similarity index 100% rename from crates/tek/src/piano_h/piano_h_time.rs rename to src/piano_h/piano_h_time.rs diff --git a/crates/tek/src/piano_v.rs b/src/piano_v.rs similarity index 100% rename from crates/tek/src/piano_v.rs rename to src/piano_v.rs diff --git a/crates/tek/src/plugin.rs b/src/plugin.rs similarity index 100% rename from crates/tek/src/plugin.rs rename to src/plugin.rs diff --git a/crates/tek/src/plugin/lv2.rs b/src/plugin/lv2.rs similarity index 100% rename from crates/tek/src/plugin/lv2.rs rename to src/plugin/lv2.rs diff --git a/crates/tek/src/plugin/lv2_gui.rs b/src/plugin/lv2_gui.rs similarity index 100% rename from crates/tek/src/plugin/lv2_gui.rs rename to src/plugin/lv2_gui.rs diff --git a/crates/tek/src/plugin/lv2_tui.rs b/src/plugin/lv2_tui.rs similarity index 100% rename from crates/tek/src/plugin/lv2_tui.rs rename to src/plugin/lv2_tui.rs diff --git a/crates/tek/src/plugin/vst2_tui.rs b/src/plugin/vst2_tui.rs similarity index 100% rename from crates/tek/src/plugin/vst2_tui.rs rename to src/plugin/vst2_tui.rs diff --git a/crates/tek/src/plugin/vst3_tui.rs b/src/plugin/vst3_tui.rs similarity index 100% rename from crates/tek/src/plugin/vst3_tui.rs rename to src/plugin/vst3_tui.rs diff --git a/crates/tek/src/pool.rs b/src/pool.rs similarity index 100% rename from crates/tek/src/pool.rs rename to src/pool.rs diff --git a/crates/tek/src/pool/phrase_length.rs b/src/pool/phrase_length.rs similarity index 100% rename from crates/tek/src/pool/phrase_length.rs rename to src/pool/phrase_length.rs diff --git a/crates/tek/src/pool/phrase_rename.rs b/src/pool/phrase_rename.rs similarity index 100% rename from crates/tek/src/pool/phrase_rename.rs rename to src/pool/phrase_rename.rs diff --git a/crates/tek/src/pool/phrase_selector.rs b/src/pool/phrase_selector.rs similarity index 100% rename from crates/tek/src/pool/phrase_selector.rs rename to src/pool/phrase_selector.rs diff --git a/crates/tek/src/sampler.rs b/src/sampler.rs similarity index 100% rename from crates/tek/src/sampler.rs rename to src/sampler.rs diff --git a/crates/tek/src/sampler/sample.rs b/src/sampler/sample.rs similarity index 100% rename from crates/tek/src/sampler/sample.rs rename to src/sampler/sample.rs diff --git a/crates/tek/src/sampler/sample_import.rs b/src/sampler/sample_import.rs similarity index 100% rename from crates/tek/src/sampler/sample_import.rs rename to src/sampler/sample_import.rs diff --git a/crates/tek/src/sampler/sample_viewer.rs b/src/sampler/sample_viewer.rs similarity index 100% rename from crates/tek/src/sampler/sample_viewer.rs rename to src/sampler/sample_viewer.rs diff --git a/crates/tek/src/sampler/sampler_tui.rs b/src/sampler/sampler_tui.rs similarity index 100% rename from crates/tek/src/sampler/sampler_tui.rs rename to src/sampler/sampler_tui.rs diff --git a/crates/tek/src/sampler/voice.rs b/src/sampler/voice.rs similarity index 100% rename from crates/tek/src/sampler/voice.rs rename to src/sampler/voice.rs diff --git a/crates/tek/src/sequencer.rs b/src/sequencer.rs similarity index 100% rename from crates/tek/src/sequencer.rs rename to src/sequencer.rs diff --git a/crates/tek/src/space.rs b/src/space.rs similarity index 100% rename from crates/tek/src/space.rs rename to src/space.rs diff --git a/crates/tek/src/space/align.rs b/src/space/align.rs similarity index 100% rename from crates/tek/src/space/align.rs rename to src/space/align.rs diff --git a/crates/tek/src/space/bsp.rs b/src/space/bsp.rs similarity index 100% rename from crates/tek/src/space/bsp.rs rename to src/space/bsp.rs diff --git a/crates/tek/src/space/collect.rs b/src/space/collect.rs similarity index 100% rename from crates/tek/src/space/collect.rs rename to src/space/collect.rs diff --git a/crates/tek/src/space/cond.rs b/src/space/cond.rs similarity index 100% rename from crates/tek/src/space/cond.rs rename to src/space/cond.rs diff --git a/crates/tek/src/space/fill.rs b/src/space/fill.rs similarity index 100% rename from crates/tek/src/space/fill.rs rename to src/space/fill.rs diff --git a/crates/tek/src/space/fixed.rs b/src/space/fixed.rs similarity index 100% rename from crates/tek/src/space/fixed.rs rename to src/space/fixed.rs diff --git a/crates/tek/src/space/inset_outset.rs b/src/space/inset_outset.rs similarity index 100% rename from crates/tek/src/space/inset_outset.rs rename to src/space/inset_outset.rs diff --git a/crates/tek/src/space/layers.rs b/src/space/layers.rs similarity index 100% rename from crates/tek/src/space/layers.rs rename to src/space/layers.rs diff --git a/crates/tek/src/space/map_reduce.rs b/src/space/map_reduce.rs similarity index 100% rename from crates/tek/src/space/map_reduce.rs rename to src/space/map_reduce.rs diff --git a/crates/tek/src/space/measure.rs b/src/space/measure.rs similarity index 100% rename from crates/tek/src/space/measure.rs rename to src/space/measure.rs diff --git a/crates/tek/src/space/min_max.rs b/src/space/min_max.rs similarity index 100% rename from crates/tek/src/space/min_max.rs rename to src/space/min_max.rs diff --git a/crates/tek/src/space/push_pull.rs b/src/space/push_pull.rs similarity index 100% rename from crates/tek/src/space/push_pull.rs rename to src/space/push_pull.rs diff --git a/crates/tek/src/space/scroll.rs b/src/space/scroll.rs similarity index 100% rename from crates/tek/src/space/scroll.rs rename to src/space/scroll.rs diff --git a/crates/tek/src/space/shrink_grow.rs b/src/space/shrink_grow.rs similarity index 100% rename from crates/tek/src/space/shrink_grow.rs rename to src/space/shrink_grow.rs diff --git a/crates/tek/src/space/split.rs b/src/space/split.rs similarity index 100% rename from crates/tek/src/space/split.rs rename to src/space/split.rs diff --git a/crates/tek/src/space/stack.rs b/src/space/stack.rs similarity index 100% rename from crates/tek/src/space/stack.rs rename to src/space/stack.rs diff --git a/crates/tek/src/status.rs b/src/status.rs similarity index 100% rename from crates/tek/src/status.rs rename to src/status.rs diff --git a/crates/tek/src/status/status_arranger.rs b/src/status/status_arranger.rs similarity index 100% rename from crates/tek/src/status/status_arranger.rs rename to src/status/status_arranger.rs diff --git a/crates/tek/src/status/status_edit.rs b/src/status/status_edit.rs similarity index 100% rename from crates/tek/src/status/status_edit.rs rename to src/status/status_edit.rs diff --git a/crates/tek/src/status/status_groovebox.rs b/src/status/status_groovebox.rs similarity index 100% rename from crates/tek/src/status/status_groovebox.rs rename to src/status/status_groovebox.rs diff --git a/crates/tek/src/status/status_sequencer.rs b/src/status/status_sequencer.rs similarity index 100% rename from crates/tek/src/status/status_sequencer.rs rename to src/status/status_sequencer.rs diff --git a/crates/tek/src/test.rs b/src/test.rs similarity index 100% rename from crates/tek/src/test.rs rename to src/test.rs diff --git a/crates/tek/src/time.rs b/src/time.rs similarity index 100% rename from crates/tek/src/time.rs rename to src/time.rs diff --git a/crates/tek/src/time/clock.rs b/src/time/clock.rs similarity index 100% rename from crates/tek/src/time/clock.rs rename to src/time/clock.rs diff --git a/crates/tek/src/time/moment.rs b/src/time/moment.rs similarity index 100% rename from crates/tek/src/time/moment.rs rename to src/time/moment.rs diff --git a/crates/tek/src/time/perf.rs b/src/time/perf.rs similarity index 100% rename from crates/tek/src/time/perf.rs rename to src/time/perf.rs diff --git a/crates/tek/src/time/pulse.rs b/src/time/pulse.rs similarity index 100% rename from crates/tek/src/time/pulse.rs rename to src/time/pulse.rs diff --git a/crates/tek/src/time/sr.rs b/src/time/sr.rs similarity index 100% rename from crates/tek/src/time/sr.rs rename to src/time/sr.rs diff --git a/crates/tek/src/time/unit.rs b/src/time/unit.rs similarity index 100% rename from crates/tek/src/time/unit.rs rename to src/time/unit.rs diff --git a/crates/tek/src/transport.rs b/src/transport.rs similarity index 100% rename from crates/tek/src/transport.rs rename to src/transport.rs diff --git a/crates/tek/src/tui.rs b/src/tui.rs similarity index 100% rename from crates/tek/src/tui.rs rename to src/tui.rs diff --git a/crates/tek/src/tui/file_browser.rs b/src/tui/file_browser.rs similarity index 100% rename from crates/tek/src/tui/file_browser.rs rename to src/tui/file_browser.rs diff --git a/crates/tek/src/tui/tui_border.rs b/src/tui/tui_border.rs similarity index 100% rename from crates/tek/src/tui/tui_border.rs rename to src/tui/tui_border.rs diff --git a/crates/tek/src/tui/tui_input.rs b/src/tui/tui_input.rs similarity index 100% rename from crates/tek/src/tui/tui_input.rs rename to src/tui/tui_input.rs diff --git a/crates/tek/src/tui/tui_output.rs b/src/tui/tui_output.rs similarity index 100% rename from crates/tek/src/tui/tui_output.rs rename to src/tui/tui_output.rs diff --git a/crates/tek/src/tui/tui_style.rs b/src/tui/tui_style.rs similarity index 100% rename from crates/tek/src/tui/tui_style.rs rename to src/tui/tui_style.rs diff --git a/crates/tek/src/tui/tui_theme.rs b/src/tui/tui_theme.rs similarity index 100% rename from crates/tek/src/tui/tui_theme.rs rename to src/tui/tui_theme.rs diff --git a/crates/suil/Cargo.toml b/suil/Cargo.toml similarity index 100% rename from crates/suil/Cargo.toml rename to suil/Cargo.toml diff --git a/crates/suil/build.rs b/suil/build.rs similarity index 100% rename from crates/suil/build.rs rename to suil/build.rs diff --git a/crates/suil/src/bound.rs b/suil/src/bound.rs similarity index 100% rename from crates/suil/src/bound.rs rename to suil/src/bound.rs diff --git a/crates/suil/src/gtk.rs b/suil/src/gtk.rs similarity index 100% rename from crates/suil/src/gtk.rs rename to suil/src/gtk.rs diff --git a/crates/suil/src/lib.rs b/suil/src/lib.rs similarity index 100% rename from crates/suil/src/lib.rs rename to suil/src/lib.rs diff --git a/crates/suil/src/test.rs b/suil/src/test.rs similarity index 100% rename from crates/suil/src/test.rs rename to suil/src/test.rs diff --git a/crates/suil/stdbool.h b/suil/stdbool.h similarity index 100% rename from crates/suil/stdbool.h rename to suil/stdbool.h diff --git a/crates/suil/stdint.h b/suil/stdint.h similarity index 100% rename from crates/suil/stdint.h rename to suil/stdint.h diff --git a/crates/suil/wrapper.h b/suil/wrapper.h similarity index 100% rename from crates/suil/wrapper.h rename to suil/wrapper.h From ae69e87dc97d5f40d7b2948a20e221270825fcd4 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 29 Dec 2024 00:16:43 +0100 Subject: [PATCH 064/815] wip: successfully registers transport callback --- Cargo.toml | 10 +++++----- bin/cli_groovebox.rs | 11 ++++++++++- rust-jack | 2 +- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b97ec445..c1285f0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,11 +4,10 @@ edition = "2021" version = "0.2.0" [dependencies] -#no_deadlocks = "1.3.2" -#vst3 = "0.1.0" atomic_float = "1.0.0" backtrace = "0.3.72" better-panic = "0.3.0" +clap = { version = "4.5.4", features = [ "derive" ] } clojure-reader = "0.1.0" crossterm = "0.27" jack = { path = "./rust-jack" } @@ -19,14 +18,15 @@ palette = { version = "0.7.6", features = [ "random" ] } quanta = "0.12.3" rand = "0.8.5" ratatui = { version = "0.26.3", features = [ "unstable-widget-ref", "underline-color" ] } -#suil-rs = { path = "../suil" } symphonia = { version = "0.5.4", features = [ "all" ] } toml = "0.8.12" uuid = { version = "1.10.0", features = [ "v4" ] } -#vst = "0.4.0" wavers = "1.4.3" +#no_deadlocks = "1.3.2" +#suil-rs = { path = "../suil" } +#vst = "0.4.0" +#vst3 = "0.1.0" #winit = { version = "0.30.4", features = [ "x11" ] } -clap = { version = "4.5.4", features = [ "derive" ] } [[bin]] name = "tek_arranger" diff --git a/bin/cli_groovebox.rs b/bin/cli_groovebox.rs index b001b633..bb46e003 100644 --- a/bin/cli_groovebox.rs +++ b/bin/cli_groovebox.rs @@ -9,6 +9,9 @@ pub struct GrooveboxCli { /// Whether to include a transport toolbar (default: true) #[arg(short, long, default_value_t = true)] transport: bool, + /// Whether to attempt to become transport master + #[arg(short, long, default_value_t = true)] + sync: bool, /// MIDI outs to connect to MIDI input #[arg(short='i', long)] midi_from: Vec, @@ -31,7 +34,7 @@ pub struct GrooveboxCli { impl GrooveboxCli { fn run (&self) -> Usually<()> { Tui::run(JackClient::new("tek_groovebox")?.activate_with(|jack|{ - let app = tek::GrooveboxTui::try_from(jack)?; + let app = tek::GrooveboxTui::try_from(jack)?; jack.read().unwrap().client().connect_ports(&app.player.midi_outs[0], &app.sampler.midi_in)?; jack.connect_midi_from(&app.player.midi_ins[0], &self.midi_from)?; jack.connect_midi_from(&app.sampler.midi_in, &self.midi_from)?; @@ -40,6 +43,12 @@ impl GrooveboxCli { jack.connect_audio_from(&app.sampler.audio_ins[1], &self.r_from)?; jack.connect_audio_to(&app.sampler.audio_outs[0], &self.l_to)?; jack.connect_audio_to(&app.sampler.audio_outs[1], &self.r_to)?; + if self.sync { + jack.read().unwrap().client().register_timebase_callback(false, |bbt, state, nframes, new_pos|{ + println!("\r{state:?} {nframes} {new_pos}"); + // TODO + })? + } Ok(app) })?)?; Ok(()) diff --git a/rust-jack b/rust-jack index 5256af1d..5a913d4c 160000 --- a/rust-jack +++ b/rust-jack @@ -1 +1 @@ -Subproject commit 5256af1ddea221dd78f22e48c0a72a3842cad26d +Subproject commit 5a913d4c97b18d51bd8175d1bcb55c1b0fd52cb1 From 003329aa1bd3173ae265d9f77179756ad806eb40 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 29 Dec 2024 00:52:20 +0100 Subject: [PATCH 065/815] invoke timebase callback, persists state but doesn't seem to do anything --- bin/cli_groovebox.rs | 16 ++- src/groovebox.rs | 2 +- src/lib.rs | 1 + src/time.rs | 208 ++++++++++++++++++++++++++++- src/time/clock.rs | 193 -------------------------- src/time/microsecond.rs | 15 +++ src/time/moment.rs | 111 --------------- src/time/sample_count.rs | 5 + src/time/{sr.rs => sample_rate.rs} | 16 --- src/time/timebase.rs | 112 ++++++++++++++++ 10 files changed, 350 insertions(+), 329 deletions(-) delete mode 100644 src/time/clock.rs create mode 100644 src/time/microsecond.rs create mode 100644 src/time/sample_count.rs rename src/time/{sr.rs => sample_rate.rs} (59%) create mode 100644 src/time/timebase.rs diff --git a/bin/cli_groovebox.rs b/bin/cli_groovebox.rs index bb46e003..6686d674 100644 --- a/bin/cli_groovebox.rs +++ b/bin/cli_groovebox.rs @@ -45,8 +45,20 @@ impl GrooveboxCli { jack.connect_audio_to(&app.sampler.audio_outs[1], &self.r_to)?; if self.sync { jack.read().unwrap().client().register_timebase_callback(false, |bbt, state, nframes, new_pos|{ - println!("\r{state:?} {nframes} {new_pos}"); - // TODO + if new_pos { + let ppq = bbt.ticks_per_beat; + let pulse = bbt.bar as f64 * 4. * ppq + bbt.beat as f64 * ppq + bbt.tick as f64; + app.clock().playhead.update_from_pulse(pulse) + } else { + let pulse = app.clock().playhead.pulse.get(); + let ppq = app.clock().timebase.ppq.get(); + let bpm = app.clock().timebase.bpm.get(); + bbt.bar = (pulse / ppq) as usize / 4; + bbt.beat = (pulse / ppq) as usize % 4; + bbt.tick = (pulse % ppq) as usize; + bbt.ticks_per_beat = ppq; + bbt.bpm = bpm; + } })? } Ok(app) diff --git a/src/groovebox.rs b/src/groovebox.rs index 713dd4bb..932fcd5a 100644 --- a/src/groovebox.rs +++ b/src/groovebox.rs @@ -44,6 +44,7 @@ from_jack!(|jack|GrooveboxTui { status: true, } }); +has_clock!(|self: GrooveboxTui|self.player.clock()); audio!(|self: GrooveboxTui, client, scope|{ let t0 = self.perf.get_t0(); if Control::Quit == ClockAudio(&mut self.player).process(client, scope) { @@ -90,7 +91,6 @@ audio!(|self: GrooveboxTui, client, scope|{ self.perf.update(t0, scope); Control::Continue }); -has_clock!(|self:GrooveboxTui|&self.player.clock); render!(|self:GrooveboxTui|{ let w = self.size.w(); let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; diff --git a/src/lib.rs b/src/lib.rs index 091e2fef..5f05cfaf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ pub mod core; pub use self::core::*; pub mod time; pub(crate) use self::time::*; +pub use self::time::HasClock; pub mod space; pub(crate) use self::space::*; diff --git a/src/time.rs b/src/time.rs index 4cc89706..5b3b8c35 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,9 +1,205 @@ -pub(crate) mod clock; pub(crate) use clock::*; -pub(crate) mod moment; pub(crate) use moment::*; -pub(crate) mod perf; pub(crate) use perf::*; -pub(crate) mod pulse; pub(crate) use pulse::*; -pub(crate) mod sr; pub(crate) use sr::*; -pub(crate) mod unit; pub(crate) use unit::*; +use crate::*; + +pub mod microsecond; pub(crate) use self::microsecond::*; +pub mod moment; pub(crate) use self::moment::*; +pub mod perf; pub(crate) use self::perf::*; +pub mod pulse; pub(crate) use self::pulse::*; +pub mod sample_count; pub(crate) use self::sample_count::*; +pub mod sample_rate; pub(crate) use self::sample_rate::*; +pub mod timebase; pub(crate) use self::timebase::*; +pub mod unit; pub(crate) use self::unit::*; + +pub trait HasClock: Send + Sync { + fn clock (&self) -> &ClockModel; +} + +#[macro_export] macro_rules! has_clock { + (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { + impl $(<$($L),*$($T $(: $U)?),*>)? HasClock for $Struct $(<$($L),*$($T),*>)? { + fn clock (&$self) -> &ClockModel { $cb } + } + } +} + +/// Hosts the JACK callback for updating the temporal pointer and playback status. +pub struct ClockAudio<'a, T: HasClock>(pub &'a mut T); + +impl Audio for ClockAudio<'_, T> { + #[inline] fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { + self.0.clock().update_from_scope(scope).unwrap(); + Control::Continue + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum ClockCommand { + Play(Option), + Pause(Option), + SeekUsec(f64), + SeekSample(f64), + SeekPulse(f64), + SetBpm(f64), + SetQuant(f64), + SetSync(f64), +} + +impl Command for ClockCommand { + fn execute (self, state: &mut T) -> Perhaps { + use ClockCommand::*; + match self { + Play(start) => state.clock().play_from(start)?, + Pause(pause) => state.clock().pause_at(pause)?, + SeekUsec(usec) => state.clock().playhead.update_from_usec(usec), + SeekSample(sample) => state.clock().playhead.update_from_sample(sample), + SeekPulse(pulse) => state.clock().playhead.update_from_pulse(pulse), + SetBpm(bpm) => return Ok(Some(SetBpm(state.clock().timebase().bpm.set(bpm)))), + SetQuant(quant) => return Ok(Some(SetQuant(state.clock().quant.set(quant)))), + SetSync(sync) => return Ok(Some(SetSync(state.clock().sync.set(sync)))), + }; + Ok(None) + } +} + +#[derive(Clone)] +pub struct ClockModel { + /// JACK transport handle. + pub transport: Arc, + /// Global temporal resolution (shared by [Moment] fields) + pub timebase: Arc, + /// Current global sample and usec (monotonic from JACK clock) + pub global: Arc, + /// Global sample and usec at which playback started + pub started: Arc>>, + /// Playback offset (when playing not from start) + pub offset: Arc, + /// Current playhead position + pub playhead: Arc, + /// Note quantization factor + pub quant: Arc, + /// Launch quantization factor + pub sync: Arc, + /// Size of buffer in samples + pub chunk: Arc, +} + +from!(|jack: &Arc>| ClockModel = { + let jack = jack.read().unwrap(); + let chunk = jack.client().buffer_size(); + let transport = jack.client().transport(); + let timebase = Arc::new(Timebase::default()); + Self { + quant: Arc::new(24.into()), + sync: Arc::new(384.into()), + transport: Arc::new(transport), + chunk: Arc::new((chunk as usize).into()), + global: Arc::new(Moment::zero(&timebase)), + playhead: Arc::new(Moment::zero(&timebase)), + offset: Arc::new(Moment::zero(&timebase)), + started: RwLock::new(None).into(), + timebase, + } +}); + +impl std::fmt::Debug for ClockModel { + fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + f.debug_struct("ClockModel") + .field("timebase", &self.timebase) + .field("chunk", &self.chunk) + .field("quant", &self.quant) + .field("sync", &self.sync) + .field("global", &self.global) + .field("playhead", &self.playhead) + .field("started", &self.started) + .finish() + } +} + +impl ClockModel { + pub fn timebase (&self) -> &Arc { + &self.timebase + } + /// Current sample rate + pub fn sr (&self) -> &SampleRate { + &self.timebase.sr + } + /// Current tempo + pub fn bpm (&self) -> &BeatsPerMinute { + &self.timebase.bpm + } + /// Current MIDI resolution + pub fn ppq (&self) -> &PulsesPerQuaver { + &self.timebase.ppq + } + /// Next pulse that matches launch sync (for phrase switchover) + pub fn next_launch_pulse (&self) -> usize { + let sync = self.sync.get() as usize; + let pulse = self.playhead.pulse.get() as usize; + if pulse % sync == 0 { + pulse + } else { + (pulse / sync + 1) * sync + } + } + /// 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)?; + } + 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)?; + } + Ok(()) + } + /// Is currently paused? + pub fn is_stopped (&self) -> bool { + self.started.read().unwrap().is_none() + } + /// Is currently playing? + pub fn is_rolling (&self) -> bool { + self.started.read().unwrap().is_some() + } + /// Update chunk size + pub fn set_chunk (&self, n_frames: usize) { + self.chunk.store(n_frames, Relaxed); + } + pub fn update_from_scope (&self, scope: &ProcessScope) -> Usually<()> { + // Store buffer length + self.set_chunk(scope.n_frames() as usize); + + // Store reported global frame and usec + let CycleTimes { current_frames, current_usecs, .. } = scope.cycle_times()?; + self.global.sample.set(current_frames as f64); + self.global.usec.set(current_usecs as f64); + + // 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; + }, + _ => {} + }; + + self.playhead.update_from_sample(started.as_ref() + .map(|started|current_frames as f64 - started.sample.get()) + .unwrap_or(0.)); + + Ok(()) + } +} //#[cfg(test)] //mod test { diff --git a/src/time/clock.rs b/src/time/clock.rs deleted file mode 100644 index 022bc26f..00000000 --- a/src/time/clock.rs +++ /dev/null @@ -1,193 +0,0 @@ -use crate::*; - -pub trait HasClock: Send + Sync { - fn clock (&self) -> &ClockModel; -} - -#[macro_export] macro_rules! has_clock { - (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { - impl $(<$($L),*$($T $(: $U)?),*>)? HasClock for $Struct $(<$($L),*$($T),*>)? { - fn clock (&$self) -> &ClockModel { $cb } - } - } -} - -#[derive(Clone, Debug, PartialEq)] -pub enum ClockCommand { - Play(Option), - Pause(Option), - SeekUsec(f64), - SeekSample(f64), - SeekPulse(f64), - SetBpm(f64), - SetQuant(f64), - SetSync(f64), -} - -impl Command for ClockCommand { - fn execute (self, state: &mut T) -> Perhaps { - use ClockCommand::*; - match self { - Play(start) => state.clock().play_from(start)?, - Pause(pause) => state.clock().pause_at(pause)?, - SeekUsec(usec) => state.clock().playhead.update_from_usec(usec), - SeekSample(sample) => state.clock().playhead.update_from_sample(sample), - SeekPulse(pulse) => state.clock().playhead.update_from_pulse(pulse), - SetBpm(bpm) => return Ok(Some(SetBpm(state.clock().timebase().bpm.set(bpm)))), - SetQuant(quant) => return Ok(Some(SetQuant(state.clock().quant.set(quant)))), - SetSync(sync) => return Ok(Some(SetSync(state.clock().sync.set(sync)))), - }; - Ok(None) - } -} - -#[derive(Clone)] -pub struct ClockModel { - /// JACK transport handle. - pub transport: Arc, - /// Global temporal resolution (shared by [Moment] fields) - pub timebase: Arc, - /// Current global sample and usec (monotonic from JACK clock) - pub global: Arc, - /// Global sample and usec at which playback started - pub started: Arc>>, - /// Playback offset (when playing not from start) - pub offset: Arc, - /// Current playhead position - pub playhead: Arc, - /// Note quantization factor - pub quant: Arc, - /// Launch quantization factor - pub sync: Arc, - /// Size of buffer in samples - pub chunk: Arc, -} - -from!(|jack: &Arc>| ClockModel = { - let jack = jack.read().unwrap(); - let chunk = jack.client().buffer_size(); - let transport = jack.client().transport(); - let timebase = Arc::new(Timebase::default()); - Self { - quant: Arc::new(24.into()), - sync: Arc::new(384.into()), - transport: Arc::new(transport), - chunk: Arc::new((chunk as usize).into()), - global: Arc::new(Moment::zero(&timebase)), - playhead: Arc::new(Moment::zero(&timebase)), - offset: Arc::new(Moment::zero(&timebase)), - started: RwLock::new(None).into(), - timebase, - } -}); - -impl std::fmt::Debug for ClockModel { - fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - f.debug_struct("ClockModel") - .field("timebase", &self.timebase) - .field("chunk", &self.chunk) - .field("quant", &self.quant) - .field("sync", &self.sync) - .field("global", &self.global) - .field("playhead", &self.playhead) - .field("started", &self.started) - .finish() - } -} - -impl ClockModel { - pub fn timebase (&self) -> &Arc { - &self.timebase - } - /// Current sample rate - pub fn sr (&self) -> &SampleRate { - &self.timebase.sr - } - /// Current tempo - pub fn bpm (&self) -> &BeatsPerMinute { - &self.timebase.bpm - } - /// Current MIDI resolution - pub fn ppq (&self) -> &PulsesPerQuaver { - &self.timebase.ppq - } - /// Next pulse that matches launch sync (for phrase switchover) - pub fn next_launch_pulse (&self) -> usize { - let sync = self.sync.get() as usize; - let pulse = self.playhead.pulse.get() as usize; - if pulse % sync == 0 { - pulse - } else { - (pulse / sync + 1) * sync - } - } - /// 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)?; - } - 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)?; - } - Ok(()) - } - /// Is currently paused? - pub fn is_stopped (&self) -> bool { - self.started.read().unwrap().is_none() - } - /// Is currently playing? - pub fn is_rolling (&self) -> bool { - self.started.read().unwrap().is_some() - } - /// Update chunk size - pub fn set_chunk (&self, n_frames: usize) { - self.chunk.store(n_frames, Relaxed); - } - pub fn update_from_scope (&self, scope: &ProcessScope) -> Usually<()> { - // Store buffer length - self.set_chunk(scope.n_frames() as usize); - - // Store reported global frame and usec - let CycleTimes { current_frames, current_usecs, .. } = scope.cycle_times()?; - self.global.sample.set(current_frames as f64); - self.global.usec.set(current_usecs as f64); - - // 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; - }, - _ => {} - }; - - self.playhead.update_from_sample(started.as_ref() - .map(|started|current_frames as f64 - started.sample.get()) - .unwrap_or(0.)); - - Ok(()) - } -} - -/// Hosts the JACK callback for updating the temporal pointer and playback status. -pub struct ClockAudio<'a, T: HasClock>(pub &'a mut T); - -impl Audio for ClockAudio<'_, T> { - #[inline] fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { - self.0.clock().update_from_scope(scope).unwrap(); - Control::Continue - } -} diff --git a/src/time/microsecond.rs b/src/time/microsecond.rs new file mode 100644 index 00000000..8f69548b --- /dev/null +++ b/src/time/microsecond.rs @@ -0,0 +1,15 @@ +use crate::*; + +/// Timestamp in microseconds +#[derive(Debug, Default)] pub struct Microsecond(AtomicF64); + +impl_time_unit!(Microsecond); + +impl Microsecond { + #[inline] pub fn format_msu (&self) -> String { + let usecs = self.get() as usize; + let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000); + let (minutes, seconds) = (seconds / 60, seconds % 60); + format!("{minutes}:{seconds:02}:{msecs:03}") + } +} diff --git a/src/time/moment.rs b/src/time/moment.rs index 79e0a68a..34f53997 100644 --- a/src/time/moment.rs +++ b/src/time/moment.rs @@ -68,114 +68,3 @@ impl Moment { self.timebase.format_beats_1(self.pulse.get()) } } - -/// Temporal resolutions: sample rate, tempo, MIDI pulses per quaver (beat) -#[derive(Debug, Clone)] -pub struct Timebase { - /// Audio samples per second - pub sr: SampleRate, - /// MIDI beats per minute - pub bpm: BeatsPerMinute, - /// MIDI ticks per beat - pub ppq: PulsesPerQuaver, -} - -impl Timebase { - /// Specify sample rate, BPM and PPQ - pub fn new ( - s: impl Into, - b: impl Into, - p: impl Into - ) -> Self { - Self { sr: s.into(), bpm: b.into(), ppq: p.into() } - } - /// Iterate over ticks between start and end. - #[inline] pub fn pulses_between_samples (&self, start: usize, end: usize) -> TicksIterator { - TicksIterator { spp: self.samples_per_pulse(), sample: start, start, end } - } - /// Return the duration fo a beat in microseconds - #[inline] pub fn usec_per_beat (&self) -> f64 { 60_000_000f64 / self.bpm.get() } - /// Return the number of beats in a second - #[inline] pub fn beat_per_second (&self) -> f64 { self.bpm.get() / 60f64 } - /// Return the number of microseconds corresponding to a note of the given duration - #[inline] pub fn note_to_usec (&self, (num, den): (f64, f64)) -> f64 { - 4.0 * self.usec_per_beat() * num / den - } - /// Return duration of a pulse in microseconds (BPM-dependent) - #[inline] pub fn pulse_per_usec (&self) -> f64 { self.ppq.get() / self.usec_per_beat() } - /// Return duration of a pulse in microseconds (BPM-dependent) - #[inline] pub fn usec_per_pulse (&self) -> f64 { self.usec_per_beat() / self.ppq.get() } - /// Return number of pulses to which a number of microseconds corresponds (BPM-dependent) - #[inline] pub fn usecs_to_pulse (&self, usec: f64) -> f64 { usec * self.pulse_per_usec() } - /// Convert a number of pulses to a sample number (SR- and BPM-dependent) - #[inline] pub fn pulses_to_usec (&self, pulse: f64) -> f64 { pulse / self.usec_per_pulse() } - /// Return number of pulses in a second (BPM-dependent) - #[inline] pub fn pulses_per_second (&self) -> f64 { self.beat_per_second() * self.ppq.get() } - /// Return fraction of a pulse to which a sample corresponds (SR- and BPM-dependent) - #[inline] pub fn pulses_per_sample (&self) -> f64 { - self.usec_per_pulse() / self.sr.usec_per_sample() - } - /// Return number of samples in a pulse (SR- and BPM-dependent) - #[inline] pub fn samples_per_pulse (&self) -> f64 { - self.sr.get() / self.pulses_per_second() - } - /// Convert a number of pulses to a sample number (SR- and BPM-dependent) - #[inline] pub fn pulses_to_sample (&self, p: f64) -> f64 { - self.pulses_per_sample() * p - } - /// Convert a number of samples to a pulse number (SR- and BPM-dependent) - #[inline] pub fn samples_to_pulse (&self, s: f64) -> f64 { - s / self.pulses_per_sample() - } - /// Return the number of samples corresponding to a note of the given duration - #[inline] pub fn note_to_samples (&self, note: (f64, f64)) -> f64 { - self.usec_to_sample(self.note_to_usec(note)) - } - /// Return the number of samples corresponding to the given number of microseconds - #[inline] pub fn usec_to_sample (&self, usec: f64) -> f64 { - usec * self.sr.get() / 1000f64 - } - /// Return the quantized position of a moment in time given a step - #[inline] pub fn quantize (&self, step: (f64, f64), time: f64) -> (f64, f64) { - let step = self.note_to_usec(step); - (time / step, time % step) - } - /// Quantize a collection of events - #[inline] pub fn quantize_into + Sized, T> ( - &self, step: (f64, f64), events: E - ) -> Vec<(f64, f64)> { - events.map(|(time, event)|(self.quantize(step, time).0, event)).collect() - } - /// Format a number of pulses into Beat.Bar.Pulse starting from 0 - #[inline] pub fn format_beats_0 (&self, pulse: f64) -> String { - let pulse = pulse as usize; - let ppq = self.ppq.get() as usize; - let (beats, pulses) = if ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) }; - format!("{}.{}.{pulses:02}", beats / 4, beats % 4) - } - /// Format a number of pulses into Beat.Bar starting from 0 - #[inline] pub fn format_beats_0_short (&self, pulse: f64) -> String { - let pulse = pulse as usize; - let ppq = self.ppq.get() as usize; - let beats = if ppq > 0 { pulse / ppq } else { 0 }; - format!("{}.{}", beats / 4, beats % 4) - } - /// Format a number of pulses into Beat.Bar.Pulse starting from 1 - #[inline] pub fn format_beats_1 (&self, pulse: f64) -> String { - let pulse = pulse as usize; - let ppq = self.ppq.get() as usize; - let (beats, pulses) = if ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) }; - format!("{}.{}.{pulses:02}", beats / 4 + 1, beats % 4 + 1) - } - /// Format a number of pulses into Beat.Bar.Pulse starting from 1 - #[inline] pub fn format_beats_1_short (&self, pulse: f64) -> String { - let pulse = pulse as usize; - let ppq = self.ppq.get() as usize; - let beats = if ppq > 0 { pulse / ppq } else { 0 }; - format!("{}.{}", beats / 4 + 1, beats % 4 + 1) - } -} - -impl Default for Timebase { - fn default () -> Self { Self::new(48000f64, 150f64, DEFAULT_PPQ) } -} diff --git a/src/time/sample_count.rs b/src/time/sample_count.rs new file mode 100644 index 00000000..f91ebeba --- /dev/null +++ b/src/time/sample_count.rs @@ -0,0 +1,5 @@ +use crate::*; + +/// Timestamp in audio samples +#[derive(Debug, Default)] pub struct SampleCount(AtomicF64); +impl_time_unit!(SampleCount); diff --git a/src/time/sr.rs b/src/time/sample_rate.rs similarity index 59% rename from src/time/sr.rs rename to src/time/sample_rate.rs index bdaf5190..40617728 100644 --- a/src/time/sr.rs +++ b/src/time/sample_rate.rs @@ -1,17 +1,5 @@ use crate::*; -/// Timestamp in microseconds -#[derive(Debug, Default)] pub struct Microsecond(AtomicF64); -impl_time_unit!(Microsecond); -impl Microsecond { - #[inline] pub fn format_msu (&self) -> String { - let usecs = self.get() as usize; - let (seconds, msecs) = (usecs / 1000000, usecs / 1000 % 1000); - let (minutes, seconds) = (seconds / 60, seconds % 60); - format!("{minutes}:{seconds:02}:{msecs:03}") - } -} - /// Audio sample rate in Hz (samples per second) #[derive(Debug, Default)] pub struct SampleRate(AtomicF64); impl_time_unit!(SampleRate); @@ -33,7 +21,3 @@ impl SampleRate { self.sample_per_usec() * usecs } } - -/// Timestamp in audio samples -#[derive(Debug, Default)] pub struct SampleCount(AtomicF64); -impl_time_unit!(SampleCount); diff --git a/src/time/timebase.rs b/src/time/timebase.rs new file mode 100644 index 00000000..cc898b92 --- /dev/null +++ b/src/time/timebase.rs @@ -0,0 +1,112 @@ +use crate::*; + +/// Temporal resolutions: sample rate, tempo, MIDI pulses per quaver (beat) +#[derive(Debug, Clone)] +pub struct Timebase { + /// Audio samples per second + pub sr: SampleRate, + /// MIDI beats per minute + pub bpm: BeatsPerMinute, + /// MIDI ticks per beat + pub ppq: PulsesPerQuaver, +} + +impl Timebase { + /// Specify sample rate, BPM and PPQ + pub fn new ( + s: impl Into, + b: impl Into, + p: impl Into + ) -> Self { + Self { sr: s.into(), bpm: b.into(), ppq: p.into() } + } + /// Iterate over ticks between start and end. + #[inline] pub fn pulses_between_samples (&self, start: usize, end: usize) -> TicksIterator { + TicksIterator { spp: self.samples_per_pulse(), sample: start, start, end } + } + /// Return the duration fo a beat in microseconds + #[inline] pub fn usec_per_beat (&self) -> f64 { 60_000_000f64 / self.bpm.get() } + /// Return the number of beats in a second + #[inline] pub fn beat_per_second (&self) -> f64 { self.bpm.get() / 60f64 } + /// Return the number of microseconds corresponding to a note of the given duration + #[inline] pub fn note_to_usec (&self, (num, den): (f64, f64)) -> f64 { + 4.0 * self.usec_per_beat() * num / den + } + /// Return duration of a pulse in microseconds (BPM-dependent) + #[inline] pub fn pulse_per_usec (&self) -> f64 { self.ppq.get() / self.usec_per_beat() } + /// Return duration of a pulse in microseconds (BPM-dependent) + #[inline] pub fn usec_per_pulse (&self) -> f64 { self.usec_per_beat() / self.ppq.get() } + /// Return number of pulses to which a number of microseconds corresponds (BPM-dependent) + #[inline] pub fn usecs_to_pulse (&self, usec: f64) -> f64 { usec * self.pulse_per_usec() } + /// Convert a number of pulses to a sample number (SR- and BPM-dependent) + #[inline] pub fn pulses_to_usec (&self, pulse: f64) -> f64 { pulse / self.usec_per_pulse() } + /// Return number of pulses in a second (BPM-dependent) + #[inline] pub fn pulses_per_second (&self) -> f64 { self.beat_per_second() * self.ppq.get() } + /// Return fraction of a pulse to which a sample corresponds (SR- and BPM-dependent) + #[inline] pub fn pulses_per_sample (&self) -> f64 { + self.usec_per_pulse() / self.sr.usec_per_sample() + } + /// Return number of samples in a pulse (SR- and BPM-dependent) + #[inline] pub fn samples_per_pulse (&self) -> f64 { + self.sr.get() / self.pulses_per_second() + } + /// Convert a number of pulses to a sample number (SR- and BPM-dependent) + #[inline] pub fn pulses_to_sample (&self, p: f64) -> f64 { + self.pulses_per_sample() * p + } + /// Convert a number of samples to a pulse number (SR- and BPM-dependent) + #[inline] pub fn samples_to_pulse (&self, s: f64) -> f64 { + s / self.pulses_per_sample() + } + /// Return the number of samples corresponding to a note of the given duration + #[inline] pub fn note_to_samples (&self, note: (f64, f64)) -> f64 { + self.usec_to_sample(self.note_to_usec(note)) + } + /// Return the number of samples corresponding to the given number of microseconds + #[inline] pub fn usec_to_sample (&self, usec: f64) -> f64 { + usec * self.sr.get() / 1000f64 + } + /// Return the quantized position of a moment in time given a step + #[inline] pub fn quantize (&self, step: (f64, f64), time: f64) -> (f64, f64) { + let step = self.note_to_usec(step); + (time / step, time % step) + } + /// Quantize a collection of events + #[inline] pub fn quantize_into + Sized, T> ( + &self, step: (f64, f64), events: E + ) -> Vec<(f64, f64)> { + events.map(|(time, event)|(self.quantize(step, time).0, event)).collect() + } + /// Format a number of pulses into Beat.Bar.Pulse starting from 0 + #[inline] pub fn format_beats_0 (&self, pulse: f64) -> String { + let pulse = pulse as usize; + let ppq = self.ppq.get() as usize; + let (beats, pulses) = if ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) }; + format!("{}.{}.{pulses:02}", beats / 4, beats % 4) + } + /// Format a number of pulses into Beat.Bar starting from 0 + #[inline] pub fn format_beats_0_short (&self, pulse: f64) -> String { + let pulse = pulse as usize; + let ppq = self.ppq.get() as usize; + let beats = if ppq > 0 { pulse / ppq } else { 0 }; + format!("{}.{}", beats / 4, beats % 4) + } + /// Format a number of pulses into Beat.Bar.Pulse starting from 1 + #[inline] pub fn format_beats_1 (&self, pulse: f64) -> String { + let pulse = pulse as usize; + let ppq = self.ppq.get() as usize; + let (beats, pulses) = if ppq > 0 { (pulse / ppq, pulse % ppq) } else { (0, 0) }; + format!("{}.{}.{pulses:02}", beats / 4 + 1, beats % 4 + 1) + } + /// Format a number of pulses into Beat.Bar.Pulse starting from 1 + #[inline] pub fn format_beats_1_short (&self, pulse: f64) -> String { + let pulse = pulse as usize; + let ppq = self.ppq.get() as usize; + let beats = if ppq > 0 { pulse / ppq } else { 0 }; + format!("{}.{}", beats / 4 + 1, beats % 4 + 1) + } +} + +impl Default for Timebase { + fn default () -> Self { Self::new(48000f64, 150f64, DEFAULT_PPQ) } +} From e5ec4ded31490488e1ea231d1698db8e2e9e196b Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 29 Dec 2024 11:36:43 +0100 Subject: [PATCH 066/815] set validity flag in timebase callback --- rust-jack | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-jack b/rust-jack index 5a913d4c..9a94493e 160000 --- a/rust-jack +++ b/rust-jack @@ -1 +1 @@ -Subproject commit 5a913d4c97b18d51bd8175d1bcb55c1b0fd52cb1 +Subproject commit 9a94493e6efffd2137a5c7f54baa85e42d12a13f From cfbb9722afbb9a30f7bc93d46d7d6811d7030eb3 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 29 Dec 2024 13:54:56 +0100 Subject: [PATCH 067/815] wip: now following transport position --- bin/cli_groovebox.rs | 29 ++++++++++++++++++----------- rust-jack | 2 +- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/bin/cli_groovebox.rs b/bin/cli_groovebox.rs index 6686d674..15a9912b 100644 --- a/bin/cli_groovebox.rs +++ b/bin/cli_groovebox.rs @@ -44,21 +44,28 @@ impl GrooveboxCli { jack.connect_audio_to(&app.sampler.audio_outs[0], &self.l_to)?; jack.connect_audio_to(&app.sampler.audio_outs[1], &self.r_to)?; if self.sync { - jack.read().unwrap().client().register_timebase_callback(false, |bbt, state, nframes, new_pos|{ + jack.read().unwrap().client().register_timebase_callback(false, |state|{ + let ::jack::contrib::TimebaseInfo { state, new_pos, nframes, mut position } = state; if new_pos { - let ppq = bbt.ticks_per_beat; - let pulse = bbt.bar as f64 * 4. * ppq + bbt.beat as f64 * ppq + bbt.tick as f64; - app.clock().playhead.update_from_pulse(pulse) + app.clock().playhead.update_from_sample(position.frame() as f64) } else { - let pulse = app.clock().playhead.pulse.get(); - let ppq = app.clock().timebase.ppq.get(); + println!("\n\r{state:?} {new_pos} {nframes} {position:?}"); + let pulse = app.clock().playhead.pulse.get() as i32; + let ppq = app.clock().timebase.ppq.get() as i32; let bpm = app.clock().timebase.bpm.get(); - bbt.bar = (pulse / ppq) as usize / 4; - bbt.beat = (pulse / ppq) as usize % 4; - bbt.tick = (pulse % ppq) as usize; - bbt.ticks_per_beat = ppq; - bbt.bpm = bpm; + position.bbt = Some(::jack::contrib::PositionBBT { + bar: 1 + (pulse / ppq) / 4, + beat: 1 + (pulse / ppq) % 4, + tick: (pulse % ppq), + bar_start_tick: 0., + beat_type: 4., + beats_per_bar: 4., + beats_per_minute: bpm, + ticks_per_beat: ppq as f64 + }); + println!("\n\r{:?}", position.bbt); } + position })? } Ok(app) diff --git a/rust-jack b/rust-jack index 9a94493e..de9a6b4e 160000 --- a/rust-jack +++ b/rust-jack @@ -1 +1 @@ -Subproject commit 9a94493e6efffd2137a5c7f54baa85e42d12a13f +Subproject commit de9a6b4e46c403b08703b272b5b2ee353aa6cb53 From 29db79f8069aa5759fc73a0551cb44aae9a4108c Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 29 Dec 2024 15:11:06 +0100 Subject: [PATCH 068/815] wip: still trying to retain correct position --- bin/cli_groovebox.rs | 19 +++---------------- rust-jack | 2 +- src/time.rs | 17 +++++++++++++++++ 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/bin/cli_groovebox.rs b/bin/cli_groovebox.rs index 15a9912b..e68776c9 100644 --- a/bin/cli_groovebox.rs +++ b/bin/cli_groovebox.rs @@ -46,24 +46,11 @@ impl GrooveboxCli { if self.sync { jack.read().unwrap().client().register_timebase_callback(false, |state|{ let ::jack::contrib::TimebaseInfo { state, new_pos, nframes, mut position } = state; + println!("\n\r{state:?} {new_pos} {nframes} {position:?}"); + position.bbt = Some(app.clock().bbt()); + println!("\r{:?}", position.bbt); if new_pos { app.clock().playhead.update_from_sample(position.frame() as f64) - } else { - println!("\n\r{state:?} {new_pos} {nframes} {position:?}"); - let pulse = app.clock().playhead.pulse.get() as i32; - let ppq = app.clock().timebase.ppq.get() as i32; - let bpm = app.clock().timebase.bpm.get(); - position.bbt = Some(::jack::contrib::PositionBBT { - bar: 1 + (pulse / ppq) / 4, - beat: 1 + (pulse / ppq) % 4, - tick: (pulse % ppq), - bar_start_tick: 0., - beat_type: 4., - beats_per_bar: 4., - beats_per_minute: bpm, - ticks_per_beat: ppq as f64 - }); - println!("\n\r{:?}", position.bbt); } position })? diff --git a/rust-jack b/rust-jack index de9a6b4e..cfd2a62e 160000 --- a/rust-jack +++ b/rust-jack @@ -1 +1 @@ -Subproject commit de9a6b4e46c403b08703b272b5b2ee353aa6cb53 +Subproject commit cfd2a62e959f2a0f5569a129790cfdb316570a7f diff --git a/src/time.rs b/src/time.rs index 5b3b8c35..0f1241c8 100644 --- a/src/time.rs +++ b/src/time.rs @@ -199,6 +199,23 @@ impl ClockModel { Ok(()) } + + pub fn bbt (&self) -> ::jack::contrib::PositionBBT { + let pulse = self.playhead.pulse.get() as i32; + let ppq = self.timebase.ppq.get() as i32; + let bpm = self.timebase.bpm.get(); + let bar = (pulse / ppq) / 4; + ::jack::contrib::PositionBBT { + bar: 1 + bar, + beat: 1 + (pulse / ppq) % 4, + tick: (pulse % ppq), + bar_start_tick: (bar * 4 * ppq) as f64, + beat_type: 4., + beats_per_bar: 4., + beats_per_minute: bpm, + ticks_per_beat: ppq as f64 + } + } } //#[cfg(test)] From c3f9aa75490be5c472503697ccd01cdf4d21323e Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 29 Dec 2024 15:22:14 +0100 Subject: [PATCH 069/815] now syncing correctly, though not in all cases --- bin/cli_groovebox.rs | 6 +----- rust-jack | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/bin/cli_groovebox.rs b/bin/cli_groovebox.rs index e68776c9..dcc85a17 100644 --- a/bin/cli_groovebox.rs +++ b/bin/cli_groovebox.rs @@ -46,12 +46,8 @@ impl GrooveboxCli { if self.sync { jack.read().unwrap().client().register_timebase_callback(false, |state|{ let ::jack::contrib::TimebaseInfo { state, new_pos, nframes, mut position } = state; - println!("\n\r{state:?} {new_pos} {nframes} {position:?}"); + app.clock().playhead.update_from_sample(position.frame() as f64); position.bbt = Some(app.clock().bbt()); - println!("\r{:?}", position.bbt); - if new_pos { - app.clock().playhead.update_from_sample(position.frame() as f64) - } position })? } diff --git a/rust-jack b/rust-jack index cfd2a62e..76f4bef0 160000 --- a/rust-jack +++ b/rust-jack @@ -1 +1 @@ -Subproject commit cfd2a62e959f2a0f5569a129790cfdb316570a7f +Subproject commit 76f4bef07e3f9115e8efba0bc053a9bad7e25a19 From 411d4bc91d94456812508ab1bbbdbd88d22eb627 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 29 Dec 2024 15:32:39 +0100 Subject: [PATCH 070/815] JackClient -> JackConnection --- bin/cli_arranger.rs | 4 ++-- bin/cli_groovebox.rs | 11 +++++------ bin/cli_sampler.rs | 2 +- bin/cli_sequencer.rs | 2 +- bin/cli_transport.rs | 2 +- bin/lib.rs | 8 ++++---- bin/todo_cli_mixer.rs | 2 +- bin/todo_cli_plugin.rs | 2 +- bin/todo_cli_sampler.rs | 2 +- src/arranger.rs | 2 +- src/edn.rs | 8 ++++---- src/groovebox.rs | 2 +- src/jack.rs | 8 ++++---- src/jack/activate.rs | 6 +++--- src/jack/client.rs | 19 ++++++++++++------- src/jack/ports.rs | 2 +- src/midi.rs | 4 ++-- src/mixer.rs | 4 ++-- src/plugin.rs | 6 +++--- src/sampler.rs | 4 ++-- src/sequencer.rs | 2 +- src/time.rs | 2 +- src/transport.rs | 2 +- 23 files changed, 55 insertions(+), 51 deletions(-) diff --git a/bin/cli_arranger.rs b/bin/cli_arranger.rs index 5cf780db..e275ff09 100644 --- a/bin/cli_arranger.rs +++ b/bin/cli_arranger.rs @@ -38,7 +38,7 @@ impl ArrangerCli { if let Some(name) = self.name.as_ref() { client_name = name.clone(); } - Tui::run(JackClient::new(client_name.as_str())?.activate_with(|jack|{ + Tui::run(JackConnection::new(client_name.as_str())?.activate_with(|jack|{ let mut app = ArrangerTui::try_from(jack)?; let jack = jack.read().unwrap(); app.color = ItemPalette::random(); @@ -50,7 +50,7 @@ impl ArrangerCli { } } -fn add_tracks (jack: &JackClient, app: &mut ArrangerTui, cli: &ArrangerCli) -> Usually<()> { +fn add_tracks (jack: &JackConnection, app: &mut ArrangerTui, cli: &ArrangerCli) -> Usually<()> { let n = cli.tracks; let track_color_1 = ItemColor::random(); let track_color_2 = ItemColor::random(); diff --git a/bin/cli_groovebox.rs b/bin/cli_groovebox.rs index dcc85a17..59e3c663 100644 --- a/bin/cli_groovebox.rs +++ b/bin/cli_groovebox.rs @@ -33,7 +33,7 @@ pub struct GrooveboxCli { } impl GrooveboxCli { fn run (&self) -> Usually<()> { - Tui::run(JackClient::new("tek_groovebox")?.activate_with(|jack|{ + Tui::run(JackConnection::new("tek_groovebox")?.activate_with(|jack|{ let app = tek::GrooveboxTui::try_from(jack)?; jack.read().unwrap().client().connect_ports(&app.player.midi_outs[0], &app.sampler.midi_in)?; jack.connect_midi_from(&app.player.midi_ins[0], &self.midi_from)?; @@ -44,11 +44,10 @@ impl GrooveboxCli { jack.connect_audio_to(&app.sampler.audio_outs[0], &self.l_to)?; jack.connect_audio_to(&app.sampler.audio_outs[1], &self.r_to)?; if self.sync { - jack.read().unwrap().client().register_timebase_callback(false, |state|{ - let ::jack::contrib::TimebaseInfo { state, new_pos, nframes, mut position } = state; - app.clock().playhead.update_from_sample(position.frame() as f64); - position.bbt = Some(app.clock().bbt()); - position + 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 })? } Ok(app) diff --git a/bin/cli_sampler.rs b/bin/cli_sampler.rs index ae7f7bd1..b2305de6 100644 --- a/bin/cli_sampler.rs +++ b/bin/cli_sampler.rs @@ -8,7 +8,7 @@ pub fn main () -> Usually<()> { SamplerCli::parse().run() } } impl SamplerCli { fn run (&self) -> Usually<()> { - Tui::run(JackClient::new("tek_sampler")?.activate_with(|x|{ + Tui::run(JackConnection::new("tek_sampler")?.activate_with(|x|{ let sampler = tek::SamplerTui::try_from(x)?; Ok(sampler) })?)?; diff --git a/bin/cli_sequencer.rs b/bin/cli_sequencer.rs index e8474507..a5ef676b 100644 --- a/bin/cli_sequencer.rs +++ b/bin/cli_sequencer.rs @@ -25,7 +25,7 @@ pub struct SequencerCli { impl SequencerCli { fn run (&self) -> Usually<()> { let name = self.name.as_deref().unwrap_or("tek_sequencer"); - Tui::run(JackClient::new(name)?.activate_with(|jack|{ + Tui::run(JackConnection::new(name)?.activate_with(|jack|{ let mut app = SequencerTui::try_from(jack)?; let jack = jack.read().unwrap(); let midi_in = jack.register_port("i", MidiIn::default())?; diff --git a/bin/cli_transport.rs b/bin/cli_transport.rs index 942978fe..bb2e56b6 100644 --- a/bin/cli_transport.rs +++ b/bin/cli_transport.rs @@ -2,7 +2,7 @@ include!("./lib.rs"); /// Application entrypoint. pub fn main () -> Usually<()> { - Tui::run(JackClient::new("tek_transport")?.activate_with(|jack|{ + Tui::run(JackConnection::new("tek_transport")?.activate_with(|jack|{ TransportTui::try_from(jack) })?)?; Ok(()) diff --git a/bin/lib.rs b/bin/lib.rs index 538ecfe7..87eec13e 100644 --- a/bin/lib.rs +++ b/bin/lib.rs @@ -3,7 +3,7 @@ #[allow(unused_imports)] use tek::{*, jack::*}; #[allow(unused)] -fn connect_from (jack: &JackClient, input: &Port, ports: &[String]) -> Usually<()> { +fn connect_from (jack: &JackConnection, input: &Port, ports: &[String]) -> Usually<()> { for port in ports.iter() { if let Some(port) = jack.port_by_name(port).as_ref() { jack.client().connect_ports(port, input)?; @@ -15,7 +15,7 @@ fn connect_from (jack: &JackClient, input: &Port, ports: &[String]) -> U } #[allow(unused)] -fn connect_to (jack: &JackClient, output: &Port, ports: &[String]) -> Usually<()> { +fn connect_to (jack: &JackConnection, output: &Port, ports: &[String]) -> Usually<()> { for port in ports.iter() { if let Some(port) = jack.port_by_name(port).as_ref() { jack.client().connect_ports(output, port)?; @@ -27,7 +27,7 @@ fn connect_to (jack: &JackClient, output: &Port, ports: &[String]) -> U } #[allow(unused)] -fn connect_audio_from (jack: &JackClient, input: &Port, ports: &[String]) -> Usually<()> { +fn connect_audio_from (jack: &JackConnection, input: &Port, ports: &[String]) -> Usually<()> { for port in ports.iter() { if let Some(port) = jack.port_by_name(port).as_ref() { jack.client().connect_ports(port, input)?; @@ -39,7 +39,7 @@ fn connect_audio_from (jack: &JackClient, input: &Port, ports: &[String } #[allow(unused)] -fn connect_audio_to (jack: &JackClient, output: &Port, ports: &[String]) -> Usually<()> { +fn connect_audio_to (jack: &JackConnection, output: &Port, ports: &[String]) -> Usually<()> { for port in ports.iter() { if let Some(port) = jack.port_by_name(port).as_ref() { jack.client().connect_ports(output, port)?; diff --git a/bin/todo_cli_mixer.rs b/bin/todo_cli_mixer.rs index 4130dde9..e419ffed 100644 --- a/bin/todo_cli_mixer.rs +++ b/bin/todo_cli_mixer.rs @@ -13,7 +13,7 @@ pub fn main () -> Usually<()> { impl MixerCli { fn run (&self) -> Usually<()> { - Tui::run(JackClient::new("tek_mixer")?.activate_with(|jack|{ + Tui::run(JackConnection::new("tek_mixer")?.activate_with(|jack|{ let mut mixer = Mixer::new(jack, self.name.as_ref().map(|x|x.as_str()).unwrap_or("mixer"))?; for channel in 0..self.channels.unwrap_or(8) { mixer.track_add(&format!("Track {}", channel + 1), 1)?; diff --git a/bin/todo_cli_plugin.rs b/bin/todo_cli_plugin.rs index 1b5a1ce5..cfb81fe2 100644 --- a/bin/todo_cli_plugin.rs +++ b/bin/todo_cli_plugin.rs @@ -13,7 +13,7 @@ pub fn main () -> Usually<()> { impl PluginCli { fn run (&self) -> Usually<()> { - Tui::run(JackClient::new("tek_plugin")?.activate_with(|jack|{ + Tui::run(JackConnection::new("tek_plugin")?.activate_with(|jack|{ let mut plugin = Plugin::new_lv2( jack, self.name.as_ref().map(|x|x.as_str()).unwrap_or("mixer"), diff --git a/bin/todo_cli_sampler.rs b/bin/todo_cli_sampler.rs index bb72ec71..75e4c62a 100644 --- a/bin/todo_cli_sampler.rs +++ b/bin/todo_cli_sampler.rs @@ -13,7 +13,7 @@ pub fn main () -> Usually<()> { impl SamplerCli { fn run (&self) -> Usually<()> { - Tui::run(JackClient::new("tek_sampler")?.activate_with(|jack|{ + Tui::run(JackConnection::new("tek_sampler")?.activate_with(|jack|{ let mut plugin = Sampler::new( jack, self.name.as_ref().map(|x|x.as_str()).unwrap_or("mixer"), diff --git a/src/arranger.rs b/src/arranger.rs index 9aea0de3..6b4ab255 100644 --- a/src/arranger.rs +++ b/src/arranger.rs @@ -10,7 +10,7 @@ mod arranger_h; /// Root view for standalone `tek_arranger` pub struct ArrangerTui { - jack: Arc>, + jack: Arc>, pub clock: ClockModel, pub phrases: PoolModel, pub tracks: Vec, diff --git a/src/edn.rs b/src/edn.rs index 8067b562..289d3e81 100644 --- a/src/edn.rs +++ b/src/edn.rs @@ -33,7 +33,7 @@ pub trait FromEdn: Sized { } } -from_edn!("sampler" => |jack: &Arc>, args| -> crate::Sampler { +from_edn!("sampler" => |jack: &Arc>, args| -> crate::Sampler { let mut name = String::new(); let mut dir = String::new(); let mut samples = BTreeMap::new(); @@ -64,7 +64,7 @@ from_edn!("sampler" => |jack: &Arc>, args| -> crate::Sampler type MidiSample = (Option, Arc>); -from_edn!("sample" => |(_jack, dir): (&Arc>, &str), args| -> MidiSample { +from_edn!("sample" => |(_jack, dir): (&Arc>, &str), args| -> MidiSample { let mut name = String::new(); let mut file = String::new(); let mut midi = None; @@ -97,7 +97,7 @@ from_edn!("sample" => |(_jack, dir): (&Arc>, &str), args| -> })))) }); -from_edn!("plugin/lv2" => |jack: &Arc>, args| -> Plugin { +from_edn!("plugin/lv2" => |jack: &Arc>, args| -> Plugin { let mut name = String::new(); let mut path = String::new(); edn!(edn in args { @@ -119,7 +119,7 @@ const SYM_GAIN: &str = ":gain"; const SYM_SAMPLER: &str = "sampler"; const SYM_LV2: &str = "lv2"; -from_edn!("mixer/track" => |jack: &Arc>, args| -> MixerTrack { +from_edn!("mixer/track" => |jack: &Arc>, args| -> MixerTrack { let mut _gain = 0.0f64; let mut track = MixerTrack { name: String::new(), diff --git a/src/groovebox.rs b/src/groovebox.rs index 932fcd5a..d3659008 100644 --- a/src/groovebox.rs +++ b/src/groovebox.rs @@ -7,7 +7,7 @@ use PhraseCommand::*; use PhrasePoolCommand::*; pub struct GrooveboxTui { - _jack: Arc>, + _jack: Arc>, pub player: MidiPlayer, pub pool: PoolModel, diff --git a/src/jack.rs b/src/jack.rs index fae62340..bf729a57 100644 --- a/src/jack.rs +++ b/src/jack.rs @@ -15,7 +15,7 @@ pub use self::activate::JackActivate; pub mod client; pub(crate) use self::client::*; -pub use self::client::JackClient; +pub use self::client::JackConnection; pub mod jack_event; pub(crate) use self::jack_event::*; @@ -24,12 +24,12 @@ pub mod ports; pub(crate) use self::ports::*; pub use self::ports::RegisterPort; -/// Implement [TryFrom<&Arc>>]: create app state from wrapped JACK handle. +/// Implement [TryFrom<&Arc>>]: create app state from wrapped JACK handle. #[macro_export] macro_rules! from_jack { (|$jack:ident|$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)? $cb:expr) => { - impl $(<$($L),*$($T $(: $U)?),*>)? TryFrom<&Arc>> for $Struct $(<$($L),*$($T),*>)? { + impl $(<$($L),*$($T $(: $U)?),*>)? TryFrom<&Arc>> for $Struct $(<$($L),*$($T),*>)? { type Error = Box; - fn try_from ($jack: &Arc>) -> Usually { + fn try_from ($jack: &Arc>) -> Usually { Ok($cb) } } diff --git a/src/jack/activate.rs b/src/jack/activate.rs index 37829f3c..5b7e4923 100644 --- a/src/jack/activate.rs +++ b/src/jack/activate.rs @@ -3,15 +3,15 @@ use crate::*; pub trait JackActivate: Sized { fn activate_with ( self, - init: impl FnOnce(&Arc>)->Usually + init: impl FnOnce(&Arc>)->Usually ) -> Usually>>; } -impl JackActivate for JackClient { +impl JackActivate for JackConnection { fn activate_with ( self, - init: impl FnOnce(&Arc>)->Usually + init: impl FnOnce(&Arc>)->Usually ) -> Usually>> { diff --git a/src/jack/client.rs b/src/jack/client.rs index be18cb0f..674737fd 100644 --- a/src/jack/client.rs +++ b/src/jack/client.rs @@ -4,7 +4,7 @@ pub type DynamicAudioHandler = ClosureProcessHandler<(), BoxedAudioHandler>; pub type BoxedAudioHandler = Box Control + Send>; /// Wraps [Client] or [DynamicAsyncClient] in place. #[derive(Debug)] -pub enum JackClient { +pub enum JackConnection { /// Before activation. Inactive(Client), /// During activation. @@ -12,18 +12,19 @@ pub enum JackClient { /// After activation. Must not be dropped for JACK thread to persist. Active(DynamicAsyncClient), } -from!(|jack: JackClient|Client = match jack { - JackClient::Inactive(client) => client, - JackClient::Activating => panic!("jack client still activating"), - JackClient::Active(_) => panic!("jack client already activated"), +from!(|jack: JackConnection|Client = match jack { + JackConnection::Inactive(client) => client, + JackConnection::Activating => panic!("jack client still activating"), + JackConnection::Active(_) => panic!("jack client already activated"), }); -impl JackClient { +impl JackConnection { pub fn new (name: &str) -> Usually { let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?; Ok(Self::Inactive(client)) } } -impl AudioEngine for JackClient { +impl AudioEngine for JackConnection { + /// Return the internal [Client] handle that lets you call the JACK API. fn client(&self) -> &Client { match self { Self::Inactive(ref client) => client, @@ -31,6 +32,10 @@ impl AudioEngine for JackClient { Self::Active(ref client) => client.as_client(), } } + /// Bind a process callback to a `JackConnection::Inactive`, + /// turning it into a `JackConnection::Active`. Needs work. + /// Strange ownership situation between the callback and + /// the host object. fn activate( self, mut cb: impl FnMut(&Arc>, &Client, &ProcessScope) -> Control + Send + 'static, diff --git a/src/jack/ports.rs b/src/jack/ports.rs index d65ac790..584207cf 100644 --- a/src/jack/ports.rs +++ b/src/jack/ports.rs @@ -11,7 +11,7 @@ pub trait RegisterPort { fn connect_audio_to (&self, my_output: &Port, ports: &[String]) -> Usually<()>; } -impl RegisterPort for Arc> { +impl RegisterPort for Arc> { fn midi_in (&self, name: &str) -> Usually> { Ok(self.read().unwrap().client().register_port(name, MidiIn::default())?) } diff --git a/src/midi.rs b/src/midi.rs index da2f3018..f9c483aa 100644 --- a/src/midi.rs +++ b/src/midi.rs @@ -127,7 +127,7 @@ pub struct MidiPlayer { pub note_buf: Vec, } impl MidiPlayer { - pub fn new (jack: &Arc>, name: &str) -> Usually { + pub fn new (jack: &Arc>, name: &str) -> Usually { Ok(Self { clock: ClockModel::from(jack), play_phrase: None, @@ -324,7 +324,7 @@ impl HasPlayPhrase for MidiPlayer { ///// Methods used primarily by the process callback //impl MIDIPlayer { //pub fn new ( - //jack: &Arc>, + //jack: &Arc>, //clock: &Arc, //name: &str //) -> Usually { diff --git a/src/mixer.rs b/src/mixer.rs index 71aba47f..54c28716 100644 --- a/src/mixer.rs +++ b/src/mixer.rs @@ -3,7 +3,7 @@ use crate::*; #[derive(Debug)] pub struct Mixer { /// JACK client handle (needs to not be dropped for standalone mode to work). - pub jack: Arc>, + pub jack: Arc>, pub name: String, pub tracks: Vec, pub selected_track: usize, @@ -25,7 +25,7 @@ pub struct MixerTrack { audio!(|self: Mixer, _client, _scope|Control::Continue); impl Mixer { - pub fn new (jack: &Arc>, name: &str) -> Usually { + pub fn new (jack: &Arc>, name: &str) -> Usually { Ok(Self { jack: jack.clone(), name: name.into(), diff --git a/src/plugin.rs b/src/plugin.rs index 63595c17..c6f8e493 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -7,7 +7,7 @@ pub use self::lv2::LV2Plugin; #[derive(Debug)] pub struct Plugin { /// JACK client handle (needs to not be dropped for standalone mode to work). - pub jack: Arc>, + pub jack: Arc>, pub name: String, pub path: Option, pub plugin: Option, @@ -40,7 +40,7 @@ impl Debug for PluginKind { } impl Plugin { pub fn new_lv2 ( - jack: &Arc>, + jack: &Arc>, name: &str, path: &str, ) -> Usually { @@ -131,7 +131,7 @@ audio!(|self: PluginAudio, client, scope|{ impl Plugin { /// Create a plugin host device. pub fn new ( - jack: &Arc>, + jack: &Arc>, name: &str, ) -> Usually { Ok(Self { diff --git a/src/sampler.rs b/src/sampler.rs index 54926e09..363bba41 100644 --- a/src/sampler.rs +++ b/src/sampler.rs @@ -35,7 +35,7 @@ pub use self::sample_viewer::SampleViewer; /// The sampler plugin plays sounds. #[derive(Debug)] pub struct Sampler { - pub jack: Arc>, + pub jack: Arc>, pub name: String, pub mapped: [Option>>;128], pub recording: Option<(usize, Arc>)>, @@ -49,7 +49,7 @@ pub struct Sampler { pub output_gain: f32 } impl Sampler { - pub fn new (jack: &Arc>, name: &str) -> Usually { + pub fn new (jack: &Arc>, name: &str) -> Usually { Ok(Self { midi_in: jack.midi_in(&format!("M/{name}"))?, audio_ins: vec![ diff --git a/src/sequencer.rs b/src/sequencer.rs index 791279c9..d24f5511 100644 --- a/src/sequencer.rs +++ b/src/sequencer.rs @@ -6,7 +6,7 @@ use PhraseCommand::*; use PhrasePoolCommand::*; /// Root view for standalone `tek_sequencer`. pub struct SequencerTui { - _jack: Arc>, + _jack: Arc>, pub transport: bool, pub selectors: bool, pub clock: ClockModel, diff --git a/src/time.rs b/src/time.rs index 0f1241c8..a8fd4112 100644 --- a/src/time.rs +++ b/src/time.rs @@ -82,7 +82,7 @@ pub struct ClockModel { pub chunk: Arc, } -from!(|jack: &Arc>| ClockModel = { +from!(|jack: &Arc>| ClockModel = { let jack = jack.read().unwrap(); let chunk = jack.client().buffer_size(); let transport = jack.client().transport(); diff --git a/src/transport.rs b/src/transport.rs index 629217d1..5a1cb9e2 100644 --- a/src/transport.rs +++ b/src/transport.rs @@ -5,7 +5,7 @@ use FocusCommand::{Next, Prev}; use KeyCode::{Enter, Left, Right, Char}; /// Transport clock app. pub struct TransportTui { - pub jack: Arc>, + pub jack: Arc>, pub clock: ClockModel, pub size: Measure, pub cursor: (usize, usize), From 02878dd9541134cb5d4dab26fee62bfecec44626 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 29 Dec 2024 15:35:29 +0100 Subject: [PATCH 071/815] merge jack::client into jack need to remove AudioEngine trait and register callbacks manually --- src/jack.rs | 117 ++++++++++++++++++++++++++----------------- src/jack/activate.rs | 14 ------ src/jack/client.rs | 55 -------------------- 3 files changed, 70 insertions(+), 116 deletions(-) delete mode 100644 src/jack/client.rs diff --git a/src/jack.rs b/src/jack.rs index bf729a57..5173607f 100644 --- a/src/jack.rs +++ b/src/jack.rs @@ -13,10 +13,6 @@ pub mod activate; pub(crate) use self::activate::*; pub use self::activate::JackActivate; -pub mod client; -pub(crate) use self::client::*; -pub use self::client::JackConnection; - pub mod jack_event; pub(crate) use self::jack_event::*; @@ -61,56 +57,69 @@ pub trait Audio: Send + Sync { } } +pub type DynamicAsyncClient = AsyncClient; -/// Trait for things that wrap a JACK client. -pub trait AudioEngine { +pub type DynamicAudioHandler = ClosureProcessHandler<(), BoxedAudioHandler>; - fn transport (&self) -> Transport { - self.client().transport() +pub type BoxedAudioHandler = Box Control + Send>; + +/// Wraps [Client] or [DynamicAsyncClient] in place. +#[derive(Debug)] +pub enum JackConnection { + /// Before activation. + Inactive(Client), + /// During activation. + Activating, + /// After activation. Must not be dropped for JACK thread to persist. + Active(DynamicAsyncClient), +} + +from!(|jack: JackConnection|Client = match jack { + JackConnection::Inactive(client) => client, + JackConnection::Activating => panic!("jack client still activating"), + JackConnection::Active(_) => panic!("jack client already activated"), +}); + +impl JackConnection { + pub fn new (name: &str) -> Usually { + let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?; + Ok(Self::Inactive(client)) } - - fn port_by_name (&self, name: &str) -> Option> { + /// Return the internal [Client] handle that lets you call the JACK API. + pub fn client (&self) -> &Client { + match self { + Self::Inactive(ref client) => client, + Self::Activating => panic!("jack client has not finished activation"), + Self::Active(ref client) => client.as_client(), + } + } + /// Bind a process callback to a `JackConnection::Inactive`, + /// consuming it and returning a `JackConnection::Active`. + /// + /// Needs work. Strange ownership situation between the callback + /// and the host object. + pub fn activate ( + self, + mut cb: impl FnMut(&Arc>, &Client, &ProcessScope) -> Control + Send + 'static, + ) -> Usually>> + where + Self: Send + Sync + 'static + { + let client = Client::from(self); + let state = Arc::new(RwLock::new(Self::Activating)); + let event = Box::new(move|_|{/*TODO*/}) as Box; + let events = Notifications(event); + let frame = Box::new({let state = state.clone(); move|c: &_, s: &_|cb(&state, c, s)}); + let frames = ClosureProcessHandler::new(frame as BoxedAudioHandler); + *state.write().unwrap() = Self::Active(client.activate_async(events, frames)?); + Ok(state) + } + pub fn port_by_name (&self, name: &str) -> Option> { self.client().port_by_name(name) } - - fn register_port (&self, name: &str, spec: PS) -> Usually> { + pub fn register_port (&self, name: &str, spec: PS) -> Usually> { Ok(self.client().register_port(name, spec)?) } - - fn client (&self) -> &Client; - - fn activate ( - self, - process: impl FnMut(&Arc>, &Client, &ProcessScope) -> Control + Send + 'static - ) -> Usually>> where Self: Send + Sync + 'static; - - fn thread_init (&self, _: &Client) {} - - unsafe fn shutdown (&mut self, _status: ClientStatus, _reason: &str) {} - - fn freewheel (&mut self, _: &Client, _enabled: bool) {} - - fn client_registration (&mut self, _: &Client, _name: &str, _reg: bool) {} - - fn port_registration (&mut self, _: &Client, _id: PortId, _reg: bool) {} - - fn ports_connected (&mut self, _: &Client, _a: PortId, _b: PortId, _are: bool) {} - - fn sample_rate (&mut self, _: &Client, _frames: Frames) -> Control { - Control::Continue - } - - fn port_rename (&mut self, _: &Client, _id: PortId, _old: &str, _new: &str) -> Control { - Control::Continue - } - - fn graph_reorder (&mut self, _: &Client) -> Control { - Control::Continue - } - - fn xrun (&mut self, _: &Client) -> Control { - Control::Continue - } } //////////////////////////////////////////////////////////////////////////////////// @@ -375,3 +384,17 @@ pub trait AudioEngine { //self //} //} + +///// A UI component that may be associated with a JACK client by the `Jack` factory. +//pub trait AudioComponent: Component + Audio { + ///// Perform type erasure for collecting heterogeneous devices. + //fn boxed(self) -> Box> + //where + //Self: Sized + 'static, + //{ + //Box::new(self) + //} +//} + +///// All things that implement the required traits can be treated as `AudioComponent`. +//impl + Audio> AudioComponent for W {} diff --git a/src/jack/activate.rs b/src/jack/activate.rs index 5b7e4923..03a7f2ef 100644 --- a/src/jack/activate.rs +++ b/src/jack/activate.rs @@ -34,17 +34,3 @@ impl JackActivate for JackConnection { Ok(target) } } - -/// A UI component that may be associated with a JACK client by the `Jack` factory. -pub trait AudioComponent: Component + Audio { - /// Perform type erasure for collecting heterogeneous devices. - fn boxed(self) -> Box> - where - Self: Sized + 'static, - { - Box::new(self) - } -} - -/// All things that implement the required traits can be treated as `AudioComponent`. -impl + Audio> AudioComponent for W {} diff --git a/src/jack/client.rs b/src/jack/client.rs deleted file mode 100644 index 674737fd..00000000 --- a/src/jack/client.rs +++ /dev/null @@ -1,55 +0,0 @@ -use crate::*; -pub type DynamicAsyncClient = AsyncClient; -pub type DynamicAudioHandler = ClosureProcessHandler<(), BoxedAudioHandler>; -pub type BoxedAudioHandler = Box Control + Send>; -/// Wraps [Client] or [DynamicAsyncClient] in place. -#[derive(Debug)] -pub enum JackConnection { - /// Before activation. - Inactive(Client), - /// During activation. - Activating, - /// After activation. Must not be dropped for JACK thread to persist. - Active(DynamicAsyncClient), -} -from!(|jack: JackConnection|Client = match jack { - JackConnection::Inactive(client) => client, - JackConnection::Activating => panic!("jack client still activating"), - JackConnection::Active(_) => panic!("jack client already activated"), -}); -impl JackConnection { - pub fn new (name: &str) -> Usually { - let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?; - Ok(Self::Inactive(client)) - } -} -impl AudioEngine for JackConnection { - /// Return the internal [Client] handle that lets you call the JACK API. - fn client(&self) -> &Client { - match self { - Self::Inactive(ref client) => client, - Self::Activating => panic!("jack client has not finished activation"), - Self::Active(ref client) => client.as_client(), - } - } - /// Bind a process callback to a `JackConnection::Inactive`, - /// turning it into a `JackConnection::Active`. Needs work. - /// Strange ownership situation between the callback and - /// the host object. - fn activate( - self, - mut cb: impl FnMut(&Arc>, &Client, &ProcessScope) -> Control + Send + 'static, - ) -> Usually>> - where - Self: Send + Sync + 'static - { - let client = Client::from(self); - let state = Arc::new(RwLock::new(Self::Activating)); - let event = Box::new(move|_|{/*TODO*/}) as Box; - let events = Notifications(event); - let frame = Box::new({let state = state.clone(); move|c: &_, s: &_|cb(&state, c, s)}); - let frames = ClosureProcessHandler::new(frame as BoxedAudioHandler); - *state.write().unwrap() = Self::Active(client.activate_async(events, frames)?); - Ok(state) - } -} From b96fa347029e3c278af68494ee858bab46b5eb2d Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 29 Dec 2024 18:39:05 +0100 Subject: [PATCH 072/815] remove trait JackActivate --- src/jack.rs | 36 +++++++++++++++++++++++++++++++----- src/jack/activate.rs | 36 ------------------------------------ 2 files changed, 31 insertions(+), 41 deletions(-) delete mode 100644 src/jack/activate.rs diff --git a/src/jack.rs b/src/jack.rs index 5173607f..5b3a6978 100644 --- a/src/jack.rs +++ b/src/jack.rs @@ -9,10 +9,6 @@ pub use ::jack::{ Transport, TransportState, MidiIter, MidiWriter, RawMidi, }; -pub mod activate; -pub(crate) use self::activate::*; -pub use self::activate::JackActivate; - pub mod jack_event; pub(crate) use self::jack_event::*; @@ -98,7 +94,7 @@ impl JackConnection { /// /// Needs work. Strange ownership situation between the callback /// and the host object. - pub fn activate ( + fn activate ( self, mut cb: impl FnMut(&Arc>, &Client, &ProcessScope) -> Control + Send + 'static, ) -> Usually>> @@ -114,6 +110,36 @@ impl JackConnection { *state.write().unwrap() = Self::Active(client.activate_async(events, frames)?); Ok(state) } + /// Consume a `JackConnection::Inactive`, activate a client, + /// initialize an app around it with the `init` callback, + /// then return the result of it all. + pub fn activate_with ( + self, + init: impl FnOnce(&Arc>)->Usually + ) + -> Usually>> + { + let client = Arc::new(RwLock::new(self)); + let target = Arc::new(RwLock::new(init(&client)?)); + let event = Box::new(move|_|{/*TODO*/}) as Box; + let events = Notifications(event); + let frame = Box::new({ + let target = target.clone(); + move|c: &_, s: &_|if let Ok(mut target) = target.write() { + target.process(c, s) + } else { + Control::Quit + } + }); + let frames = ClosureProcessHandler::new(frame as BoxedAudioHandler); + let mut buffer = Self::Activating; + std::mem::swap(&mut*client.write().unwrap(), &mut buffer); + *client.write().unwrap() = Self::Active(Client::from(buffer).activate_async( + events, + frames, + )?); + Ok(target) + } pub fn port_by_name (&self, name: &str) -> Option> { self.client().port_by_name(name) } diff --git a/src/jack/activate.rs b/src/jack/activate.rs deleted file mode 100644 index 03a7f2ef..00000000 --- a/src/jack/activate.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::*; - -pub trait JackActivate: Sized { - fn activate_with ( - self, - init: impl FnOnce(&Arc>)->Usually - ) - -> Usually>>; -} - -impl JackActivate for JackConnection { - fn activate_with ( - self, - init: impl FnOnce(&Arc>)->Usually - ) - -> Usually>> - { - let client = Arc::new(RwLock::new(self)); - let target = Arc::new(RwLock::new(init(&client)?)); - let event = Box::new(move|_|{/*TODO*/}) as Box; - let events = Notifications(event); - let frame = Box::new({ - let target = target.clone(); - move|c: &_, s: &_|if let Ok(mut target) = target.write() { - target.process(c, s) - } else { - Control::Quit - } - }); - let frames = ClosureProcessHandler::new(frame as BoxedAudioHandler); - let mut buffer = Self::Activating; - std::mem::swap(&mut*client.write().unwrap(), &mut buffer); - *client.write().unwrap() = Self::Active(Client::from(buffer).activate_async(events, frames)?); - Ok(target) - } -} From b78b55faa2443f799a430fbdbb98178d4af71032 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 29 Dec 2024 18:39:20 +0100 Subject: [PATCH 073/815] implement sync_lead and sync_follow flags for groovebox --- bin/cli_groovebox.rs | 30 +++++++++++++++++++----------- rust-jack | 2 +- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/bin/cli_groovebox.rs b/bin/cli_groovebox.rs index 59e3c663..ab516e84 100644 --- a/bin/cli_groovebox.rs +++ b/bin/cli_groovebox.rs @@ -5,31 +5,34 @@ pub fn main () -> Usually<()> { GrooveboxCli::parse().run() } pub struct GrooveboxCli { /// Name of JACK client #[arg(short, long)] - name: Option, + name: Option, /// Whether to include a transport toolbar (default: true) #[arg(short, long, default_value_t = true)] - transport: bool, + transport: bool, /// Whether to attempt to become transport master - #[arg(short, long, default_value_t = true)] - sync: bool, + #[arg(short='S', long, default_value_t = false)] + sync_lead: bool, + /// Whether to attempt to become transport master + #[arg(short='s', long, default_value_t = true)] + sync_follow: bool, /// MIDI outs to connect to MIDI input #[arg(short='i', long)] - midi_from: Vec, + midi_from: Vec, /// MIDI ins to connect from MIDI output #[arg(short='o', long)] - midi_to: Vec, + midi_to: Vec, /// Audio outs to connect to left input #[arg(short='l', long)] - l_from: Vec, + l_from: Vec, /// Audio outs to connect to right input #[arg(short='r', long)] - r_from: Vec, + r_from: Vec, /// Audio ins to connect from left output #[arg(short='L', long)] - l_to: Vec, + l_to: Vec, /// Audio ins to connect from right output #[arg(short='R', long)] - r_to: Vec, + r_to: Vec, } impl GrooveboxCli { fn run (&self) -> Usually<()> { @@ -43,12 +46,17 @@ impl GrooveboxCli { jack.connect_audio_from(&app.sampler.audio_ins[1], &self.r_from)?; jack.connect_audio_to(&app.sampler.audio_outs[0], &self.l_to)?; jack.connect_audio_to(&app.sampler.audio_outs[1], &self.r_to)?; - if self.sync { + if self.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 self.sync_follow { + jack.read().unwrap().client().register_timebase_callback(false, |state|{ + app.clock().playhead.update_from_sample(state.position.frame() as f64); + state.position + })? } Ok(app) })?)?; diff --git a/rust-jack b/rust-jack index 76f4bef0..d0978895 160000 --- a/rust-jack +++ b/rust-jack @@ -1 +1 @@ -Subproject commit 76f4bef07e3f9115e8efba0bc053a9bad7e25a19 +Subproject commit d09788959fe1cc937b27e8bfb2b695f84b406885 From e8b97bed370c1bcc2f553ff8ca8bce13e808e451 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 29 Dec 2024 18:58:06 +0100 Subject: [PATCH 074/815] flatten jack module --- src/jack.rs | 190 +++++++++++++++++++++++++++++++++++++++-- src/jack/jack_event.rs | 69 --------------- src/jack/ports.rs | 109 ----------------------- 3 files changed, 182 insertions(+), 186 deletions(-) delete mode 100644 src/jack/jack_event.rs delete mode 100644 src/jack/ports.rs diff --git a/src/jack.rs b/src/jack.rs index 5b3a6978..23850b1d 100644 --- a/src/jack.rs +++ b/src/jack.rs @@ -1,5 +1,4 @@ use crate::*; - pub use ::jack as libjack; pub use ::jack::{ contrib::ClosureProcessHandler, NotificationHandler, @@ -9,13 +8,6 @@ pub use ::jack::{ Transport, TransportState, MidiIter, MidiWriter, RawMidi, }; -pub mod jack_event; -pub(crate) use self::jack_event::*; - -pub mod ports; -pub(crate) use self::ports::*; -pub use self::ports::RegisterPort; - /// Implement [TryFrom<&Arc>>]: create app state from wrapped JACK handle. #[macro_export] macro_rules! from_jack { (|$jack:ident|$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)? $cb:expr) => { @@ -148,6 +140,144 @@ impl JackConnection { } } +pub trait RegisterPort { + fn midi_in (&self, name: &str) -> Usually>; + fn midi_out (&self, name: &str) -> Usually>; + fn audio_in (&self, name: &str) -> Usually>; + fn audio_out (&self, name: &str) -> Usually>; + fn connect_midi_from (&self, my_input: &Port, ports: &[String]) -> Usually<()>; + fn connect_midi_to (&self, my_output: &Port, ports: &[String]) -> Usually<()>; + fn connect_audio_from (&self, my_input: &Port, ports: &[String]) -> Usually<()>; + fn connect_audio_to (&self, my_output: &Port, ports: &[String]) -> Usually<()>; +} + +impl RegisterPort for Arc> { + fn midi_in (&self, name: &str) -> Usually> { + Ok(self.read().unwrap().client().register_port(name, MidiIn::default())?) + } + fn midi_out (&self, name: &str) -> Usually> { + Ok(self.read().unwrap().client().register_port(name, MidiOut::default())?) + } + fn audio_out (&self, name: &str) -> Usually> { + Ok(self.read().unwrap().client().register_port(name, AudioOut::default())?) + } + fn audio_in (&self, name: &str) -> Usually> { + Ok(self.read().unwrap().client().register_port(name, AudioIn::default())?) + } + fn connect_midi_from (&self, input: &Port, ports: &[String]) -> Usually<()> { + let jack = self.read().unwrap(); + for port in ports.iter() { + if let Some(port) = jack.port_by_name(port).as_ref() { + jack.client().connect_ports(port, input)?; + } else { + panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); + } + } + Ok(()) + } + fn connect_midi_to (&self, output: &Port, ports: &[String]) -> Usually<()> { + let jack = self.read().unwrap(); + for port in ports.iter() { + if let Some(port) = jack.port_by_name(port).as_ref() { + jack.client().connect_ports(output, port)?; + } else { + panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); + } + } + Ok(()) + } + fn connect_audio_from (&self, input: &Port, ports: &[String]) -> Usually<()> { + let jack = self.read().unwrap(); + for port in ports.iter() { + if let Some(port) = jack.port_by_name(port).as_ref() { + jack.client().connect_ports(port, input)?; + } else { + panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); + } + } + Ok(()) + } + fn connect_audio_to (&self, output: &Port, ports: &[String]) -> Usually<()> { + let jack = self.read().unwrap(); + for port in ports.iter() { + if let Some(port) = jack.port_by_name(port).as_ref() { + jack.client().connect_ports(output, port)?; + } else { + panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); + } + } + Ok(()) + } +} + +#[derive(Debug, Clone, PartialEq)] +/// Event enum for JACK events. +pub enum JackEvent { + ThreadInit, + Shutdown(ClientStatus, String), + Freewheel(bool), + SampleRate(Frames), + ClientRegistration(String, bool), + PortRegistration(PortId, bool), + PortRename(PortId, String, String), + PortsConnected(PortId, PortId, bool), + GraphReorder, + XRun, +} + +/// Notification handler used by the [Jack] factory +/// when constructing [JackDevice]s. +pub type DynamicNotifications = Notifications>; + +/// Generic notification handler that emits [JackEvent] +pub struct Notifications(pub T); + +impl NotificationHandler for Notifications { + fn thread_init(&self, _: &Client) { + self.0(JackEvent::ThreadInit); + } + + unsafe fn shutdown(&mut self, status: ClientStatus, reason: &str) { + self.0(JackEvent::Shutdown(status, reason.into())); + } + + fn freewheel(&mut self, _: &Client, enabled: bool) { + self.0(JackEvent::Freewheel(enabled)); + } + + fn sample_rate(&mut self, _: &Client, frames: Frames) -> Control { + self.0(JackEvent::SampleRate(frames)); + Control::Quit + } + + fn client_registration(&mut self, _: &Client, name: &str, reg: bool) { + self.0(JackEvent::ClientRegistration(name.into(), reg)); + } + + fn port_registration(&mut self, _: &Client, id: PortId, reg: bool) { + self.0(JackEvent::PortRegistration(id, reg)); + } + + fn port_rename(&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control { + self.0(JackEvent::PortRename(id, old.into(), new.into())); + Control::Continue + } + + fn ports_connected(&mut self, _: &Client, a: PortId, b: PortId, are: bool) { + self.0(JackEvent::PortsConnected(a, b, are)); + } + + fn graph_reorder(&mut self, _: &Client) -> Control { + self.0(JackEvent::GraphReorder); + Control::Continue + } + + fn xrun(&mut self, _: &Client) -> Control { + self.0(JackEvent::XRun); + Control::Continue + } +} + //////////////////////////////////////////////////////////////////////////////////// ///// A [AudioComponent] bound to a JACK client and a set of ports. @@ -424,3 +554,47 @@ impl JackConnection { ///// All things that implement the required traits can be treated as `AudioComponent`. //impl + Audio> AudioComponent for W {} + +///////// + +/* + +/// Trait for things that may expose JACK ports. +pub trait Ports { + fn audio_ins(&self) -> Usually>> { + Ok(vec![]) + } + fn audio_outs(&self) -> Usually>> { + Ok(vec![]) + } + fn midi_ins(&self) -> Usually>> { + Ok(vec![]) + } + fn midi_outs(&self) -> Usually>> { + Ok(vec![]) + } +} + +fn register_ports( + client: &Client, + names: Vec, + spec: T, +) -> Usually>> { + names + .into_iter() + .try_fold(BTreeMap::new(), |mut ports, name| { + let port = client.register_port(&name, spec)?; + ports.insert(name, port); + Ok(ports) + }) +} + +fn query_ports(client: &Client, names: Vec) -> BTreeMap> { + names.into_iter().fold(BTreeMap::new(), |mut ports, name| { + let port = client.port_by_name(&name).unwrap(); + ports.insert(name, port); + ports + }) +} + +*/ diff --git a/src/jack/jack_event.rs b/src/jack/jack_event.rs deleted file mode 100644 index e62945d7..00000000 --- a/src/jack/jack_event.rs +++ /dev/null @@ -1,69 +0,0 @@ -use crate::*; - -#[derive(Debug, Clone, PartialEq)] -/// Event enum for JACK events. -pub enum JackEvent { - ThreadInit, - Shutdown(ClientStatus, String), - Freewheel(bool), - SampleRate(Frames), - ClientRegistration(String, bool), - PortRegistration(PortId, bool), - PortRename(PortId, String, String), - PortsConnected(PortId, PortId, bool), - GraphReorder, - XRun, -} - -/// Notification handler used by the [Jack] factory -/// when constructing [JackDevice]s. -pub type DynamicNotifications = Notifications>; - -/// Generic notification handler that emits [JackEvent] -pub struct Notifications(pub T); - -impl NotificationHandler for Notifications { - fn thread_init(&self, _: &Client) { - self.0(JackEvent::ThreadInit); - } - - unsafe fn shutdown(&mut self, status: ClientStatus, reason: &str) { - self.0(JackEvent::Shutdown(status, reason.into())); - } - - fn freewheel(&mut self, _: &Client, enabled: bool) { - self.0(JackEvent::Freewheel(enabled)); - } - - fn sample_rate(&mut self, _: &Client, frames: Frames) -> Control { - self.0(JackEvent::SampleRate(frames)); - Control::Quit - } - - fn client_registration(&mut self, _: &Client, name: &str, reg: bool) { - self.0(JackEvent::ClientRegistration(name.into(), reg)); - } - - fn port_registration(&mut self, _: &Client, id: PortId, reg: bool) { - self.0(JackEvent::PortRegistration(id, reg)); - } - - fn port_rename(&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control { - self.0(JackEvent::PortRename(id, old.into(), new.into())); - Control::Continue - } - - fn ports_connected(&mut self, _: &Client, a: PortId, b: PortId, are: bool) { - self.0(JackEvent::PortsConnected(a, b, are)); - } - - fn graph_reorder(&mut self, _: &Client) -> Control { - self.0(JackEvent::GraphReorder); - Control::Continue - } - - fn xrun(&mut self, _: &Client) -> Control { - self.0(JackEvent::XRun); - Control::Continue - } -} diff --git a/src/jack/ports.rs b/src/jack/ports.rs deleted file mode 100644 index 584207cf..00000000 --- a/src/jack/ports.rs +++ /dev/null @@ -1,109 +0,0 @@ -use crate::*; - -pub trait RegisterPort { - fn midi_in (&self, name: &str) -> Usually>; - fn midi_out (&self, name: &str) -> Usually>; - fn audio_in (&self, name: &str) -> Usually>; - fn audio_out (&self, name: &str) -> Usually>; - fn connect_midi_from (&self, my_input: &Port, ports: &[String]) -> Usually<()>; - fn connect_midi_to (&self, my_output: &Port, ports: &[String]) -> Usually<()>; - fn connect_audio_from (&self, my_input: &Port, ports: &[String]) -> Usually<()>; - fn connect_audio_to (&self, my_output: &Port, ports: &[String]) -> Usually<()>; -} - -impl RegisterPort for Arc> { - fn midi_in (&self, name: &str) -> Usually> { - Ok(self.read().unwrap().client().register_port(name, MidiIn::default())?) - } - fn midi_out (&self, name: &str) -> Usually> { - Ok(self.read().unwrap().client().register_port(name, MidiOut::default())?) - } - fn audio_out (&self, name: &str) -> Usually> { - Ok(self.read().unwrap().client().register_port(name, AudioOut::default())?) - } - fn audio_in (&self, name: &str) -> Usually> { - Ok(self.read().unwrap().client().register_port(name, AudioIn::default())?) - } - fn connect_midi_from (&self, input: &Port, ports: &[String]) -> Usually<()> { - let jack = self.read().unwrap(); - for port in ports.iter() { - if let Some(port) = jack.port_by_name(port).as_ref() { - jack.client().connect_ports(port, input)?; - } else { - panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); - } - } - Ok(()) - } - fn connect_midi_to (&self, output: &Port, ports: &[String]) -> Usually<()> { - let jack = self.read().unwrap(); - for port in ports.iter() { - if let Some(port) = jack.port_by_name(port).as_ref() { - jack.client().connect_ports(output, port)?; - } else { - panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); - } - } - Ok(()) - } - fn connect_audio_from (&self, input: &Port, ports: &[String]) -> Usually<()> { - let jack = self.read().unwrap(); - for port in ports.iter() { - if let Some(port) = jack.port_by_name(port).as_ref() { - jack.client().connect_ports(port, input)?; - } else { - panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); - } - } - Ok(()) - } - fn connect_audio_to (&self, output: &Port, ports: &[String]) -> Usually<()> { - let jack = self.read().unwrap(); - for port in ports.iter() { - if let Some(port) = jack.port_by_name(port).as_ref() { - jack.client().connect_ports(output, port)?; - } else { - panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); - } - } - Ok(()) - } -} - -/// Trait for things that may expose JACK ports. -pub trait Ports { - fn audio_ins(&self) -> Usually>> { - Ok(vec![]) - } - fn audio_outs(&self) -> Usually>> { - Ok(vec![]) - } - fn midi_ins(&self) -> Usually>> { - Ok(vec![]) - } - fn midi_outs(&self) -> Usually>> { - Ok(vec![]) - } -} - -fn register_ports( - client: &Client, - names: Vec, - spec: T, -) -> Usually>> { - names - .into_iter() - .try_fold(BTreeMap::new(), |mut ports, name| { - let port = client.register_port(&name, spec)?; - ports.insert(name, port); - Ok(ports) - }) -} - -fn query_ports(client: &Client, names: Vec) -> BTreeMap> { - names.into_iter().fold(BTreeMap::new(), |mut ports, name| { - let port = client.port_by_name(&name).unwrap(); - ports.insert(name, port); - ports - }) -} From 6607491f1658a3b675181d971295f76d5aab4e9f Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 29 Dec 2024 20:15:12 +0100 Subject: [PATCH 075/815] move all port connections to constructors (port: impl AsRef) --- bin/cli_groovebox.rs | 16 ++-- bin/cli_sampler.rs | 35 +++++++- src/groovebox.rs | 64 +++++++++----- src/jack.rs | 166 +++++++++++++++++++++---------------- src/lib.rs | 1 + src/midi.rs | 12 ++- src/sampler.rs | 23 +++-- src/sampler/sampler_tui.rs | 26 +++--- src/space.rs | 1 + 9 files changed, 215 insertions(+), 129 deletions(-) diff --git a/bin/cli_groovebox.rs b/bin/cli_groovebox.rs index ab516e84..facd66c9 100644 --- a/bin/cli_groovebox.rs +++ b/bin/cli_groovebox.rs @@ -37,15 +37,13 @@ pub struct GrooveboxCli { impl GrooveboxCli { fn run (&self) -> Usually<()> { Tui::run(JackConnection::new("tek_groovebox")?.activate_with(|jack|{ - let app = tek::GrooveboxTui::try_from(jack)?; - jack.read().unwrap().client().connect_ports(&app.player.midi_outs[0], &app.sampler.midi_in)?; - jack.connect_midi_from(&app.player.midi_ins[0], &self.midi_from)?; - jack.connect_midi_from(&app.sampler.midi_in, &self.midi_from)?; - jack.connect_midi_to(&app.player.midi_outs[0], &self.midi_to)?; - jack.connect_audio_from(&app.sampler.audio_ins[0], &self.l_from)?; - jack.connect_audio_from(&app.sampler.audio_ins[1], &self.r_from)?; - jack.connect_audio_to(&app.sampler.audio_outs[0], &self.l_to)?; - jack.connect_audio_to(&app.sampler.audio_outs[1], &self.r_to)?; + let app = tek::GrooveboxTui::new( + jack, + &self.midi_from.as_slice(), + &self.midi_to.as_slice(), + &[&self.l_from.as_slice(), &self.r_from.as_slice()], + &[&self.l_to.as_slice(), &self.r_to.as_slice()], + )?; if self.sync_lead { jack.read().unwrap().client().register_timebase_callback(false, |mut state|{ app.clock().playhead.update_from_sample(state.position.frame() as f64); diff --git a/bin/cli_sampler.rs b/bin/cli_sampler.rs index b2305de6..262e40c5 100644 --- a/bin/cli_sampler.rs +++ b/bin/cli_sampler.rs @@ -5,12 +5,41 @@ pub fn main () -> Usually<()> { SamplerCli::parse().run() } #[arg(short, long)] name: Option, /// Path to plugin #[arg(short, long)] path: Option, + /// MIDI outs to connect to MIDI input + #[arg(short='i', long)] + midi_from: Vec, + /// Audio outs to connect to left input + #[arg(short='l', long)] + l_from: Vec, + /// Audio outs to connect to right input + #[arg(short='r', long)] + r_from: Vec, + /// Audio ins to connect from left output + #[arg(short='L', long)] + l_to: Vec, + /// Audio ins to connect from right output + #[arg(short='R', long)] + r_to: Vec, } impl SamplerCli { fn run (&self) -> Usually<()> { - Tui::run(JackConnection::new("tek_sampler")?.activate_with(|x|{ - let sampler = tek::SamplerTui::try_from(x)?; - Ok(sampler) + Tui::run(JackConnection::new("tek_sampler")?.activate_with(|jack|{ + Ok(tek::SamplerTui { + cursor: (0, 0), + editing: None, + mode: None, + size: Measure::new(), + note_lo: 36.into(), + note_pt: 36.into(), + color: ItemPalette::from(Color::Rgb(64, 128, 32)), + state: Sampler::new( + jack, + &"sampler", + &self.midi_from.as_slice(), + &[&self.l_from.as_slice(), &self.r_from.as_slice()], + &[&self.l_to.as_slice(), &self.r_to.as_slice()], + )?, + }) })?)?; Ok(()) } diff --git a/src/groovebox.rs b/src/groovebox.rs index d3659008..ce802986 100644 --- a/src/groovebox.rs +++ b/src/groovebox.rs @@ -20,30 +20,50 @@ pub struct GrooveboxTui { pub midi_buf: Vec>>, pub perf: PerfModel, } +impl GrooveboxTui { + pub fn new ( + jack: &Arc>, + midi_from: &[impl AsRef], + midi_to: &[impl AsRef], + audio_from: &[&[impl AsRef];2], + audio_to: &[&[impl AsRef];2], + ) -> Usually { + let sampler = crate::sampler::Sampler::new( + jack, &"sampler", midi_from, audio_from, audio_to + )?; + let mut player = crate::midi::MidiPlayer::new( + jack, &"sequencer", &midi_from, &midi_to + )?; + jack.read().unwrap().client().connect_ports(&player.midi_outs[0], &sampler.midi_in)?; + //jack.connect_midi_from(&player.midi_ins[0], &midi_from)?; + //jack.connect_midi_from(&sampler.midi_in, &midi_from)?; + //jack.connect_midi_to(&player.midi_outs[0], &midi_to)?; + //jack.connect_audio_from(&sampler.audio_ins[0], &audio_from[0])?; + //jack.connect_audio_from(&sampler.audio_ins[1], &audio_from[1])?; + //jack.connect_audio_to(&sampler.audio_outs[0], &audio_to[0])?; + //jack.connect_audio_to(&sampler.audio_outs[1], &audio_to[1])?; -from_jack!(|jack|GrooveboxTui { - let mut player = MidiPlayer::new(jack, "sequencer")?; - let phrase = Arc::new(RwLock::new(MidiClip::new( - "New", true, 4 * player.clock.timebase.ppq.get() as usize, - None, Some(ItemColor::random().into()) - ))); - player.play_phrase = Some((Moment::zero(&player.clock.timebase), Some(phrase.clone()))); - let pool = crate::pool::PoolModel::from(&phrase); - let editor = crate::midi::MidiEditorModel::from(&phrase); - let sampler = crate::sampler::Sampler::new(jack, "sampler")?; - Self { - _jack: jack.clone(), - player, - pool, - editor, - sampler, - size: Measure::new(), - midi_buf: vec![vec![];65536], - note_buf: vec![], - perf: PerfModel::default(), - status: true, + let phrase = Arc::new(RwLock::new(MidiClip::new( + "New", true, 4 * player.clock.timebase.ppq.get() as usize, + None, Some(ItemColor::random().into()) + ))); + player.play_phrase = Some((Moment::zero(&player.clock.timebase), Some(phrase.clone()))); + let pool = crate::pool::PoolModel::from(&phrase); + let editor = crate::midi::MidiEditorModel::from(&phrase); + Ok(Self { + _jack: jack.clone(), + player, + pool, + editor, + sampler, + size: Measure::new(), + midi_buf: vec![vec![];65536], + note_buf: vec![], + perf: PerfModel::default(), + status: true, + }) } -}); +} has_clock!(|self: GrooveboxTui|self.player.clock()); audio!(|self: GrooveboxTui, client, scope|{ let t0 = self.perf.get_t0(); diff --git a/src/jack.rs b/src/jack.rs index 23850b1d..b0862784 100644 --- a/src/jack.rs +++ b/src/jack.rs @@ -45,13 +45,27 @@ pub trait Audio: Send + Sync { } } -pub type DynamicAsyncClient = AsyncClient; +/// This is a boxed realtime callback. +pub type BoxedAudioHandler = Box Control + Send>; -pub type DynamicAudioHandler = ClosureProcessHandler<(), BoxedAudioHandler>; +/// This is the notification handler wrapper for a boxed realtime callback. +pub type DynamicAudioHandler = ClosureProcessHandler<(), BoxedAudioHandler>; -pub type BoxedAudioHandler = Box Control + Send>; +/// This is a boxed [JackEvent] callback. +pub type BoxedJackEventHandler = Box; -/// Wraps [Client] or [DynamicAsyncClient] in place. +/// This is the notification handler wrapper for a boxed [JackEvent] callback. +pub type DynamicNotifications = Notifications; + +/// This is a running JACK [AsyncClient] with maximum type erasure. +/// It has one [Box] containing a function that handles [JackEvent]s, +/// and another [Box] containing a function that handles realtime IO, +/// and that's all it knows about them. +pub type DynamicAsyncClient = AsyncClient; + +/// This is a connection which may be `Inactive`, `Activating`, or `Active`. +/// In the `Active` and `Inactive` states, its `client` method returns a +/// [Client] which you can use to talk to the JACK API. #[derive(Debug)] pub enum JackConnection { /// Before activation. @@ -81,8 +95,11 @@ impl JackConnection { Self::Active(ref client) => client.as_client(), } } - /// Bind a process callback to a `JackConnection::Inactive`, - /// consuming it and returning a `JackConnection::Active`. + /// Activate a connection with an application. + /// + /// Consume a `JackConnection::Inactive`, + /// binding a process callback and + /// returning a `JackConnection::Active`. /// /// Needs work. Strange ownership situation between the callback /// and the host object. @@ -102,33 +119,48 @@ impl JackConnection { *state.write().unwrap() = Self::Active(client.activate_async(events, frames)?); Ok(state) } - /// Consume a `JackConnection::Inactive`, activate a client, - /// initialize an app around it with the `init` callback, - /// then return the result of it all. + /// Activate a connection with an application. + /// + /// * Wrap a [JackConnection::Inactive] into [Arc>]. + /// * Pass it to the `init` callback + /// * This allows user code to connect to JACK + /// * While user code retains clone of the + /// [Arc>] that is + /// passed to `init`, the audio engine is running. pub fn activate_with ( self, init: impl FnOnce(&Arc>)->Usually ) -> Usually>> { - let client = Arc::new(RwLock::new(self)); - let target = Arc::new(RwLock::new(init(&client)?)); - let event = Box::new(move|_|{/*TODO*/}) as Box; - let events = Notifications(event); - let frame = Box::new({ - let target = target.clone(); - move|c: &_, s: &_|if let Ok(mut target) = target.write() { - target.process(c, s) - } else { - Control::Quit - } - }); - let frames = ClosureProcessHandler::new(frame as BoxedAudioHandler); - let mut buffer = Self::Activating; - std::mem::swap(&mut*client.write().unwrap(), &mut buffer); - *client.write().unwrap() = Self::Active(Client::from(buffer).activate_async( - events, - frames, + // Wrap self for multiple ownership. + let connection = Arc::new(RwLock::new(self)); + // Run init callback. Return value is target. Target must retain clone of `connection`. + let target = Arc::new(RwLock::new(init(&connection)?)); + // Swap the `client` from the `JackConnection::Inactive` + // for a `JackConnection::Activating`. + let mut client = Self::Activating; + std::mem::swap(&mut*connection.write().unwrap(), &mut client); + // Replace the `JackConnection::Activating` with a + // `JackConnection::Active` wrapping the [AsyncClient] + // returned by the activation. + *connection.write().unwrap() = Self::Active(Client::from(client).activate_async( + // This is the misc notifications handler. It's a struct that wraps a [Box] + // which performs type erasure on a callback that takes [JackEvent], which is + // one of the available misc notifications. + Notifications(Box::new(move|_|{/*TODO*/}) as BoxedJackEventHandler), + // This is the main processing handler. It's a struct that wraps a [Box] + // which performs type erasure on a callback that takes [Client] and [ProcessScope] + // and passes them down to the `target`'s `process` callback, which in turn + // implements audio and MIDI input and output on a realtime basis. + ClosureProcessHandler::new(Box::new({ + let target = target.clone(); + move|c: &_, s: &_|if let Ok(mut target) = target.write() { + target.process(c, s) + } else { + Control::Quit + } + }) as BoxedAudioHandler), )?); Ok(target) } @@ -140,73 +172,69 @@ impl JackConnection { } } +/// This is a utility trait for things that may register or connect [Port]s. +/// It contains shorthand methods to this purpose. It's implemented for +/// `Arc>` for terse port registration in the +/// `init` callback of [JackClient::activate_with]. pub trait RegisterPort { - fn midi_in (&self, name: &str) -> Usually>; - fn midi_out (&self, name: &str) -> Usually>; - fn audio_in (&self, name: &str) -> Usually>; - fn audio_out (&self, name: &str) -> Usually>; - fn connect_midi_from (&self, my_input: &Port, ports: &[String]) -> Usually<()>; - fn connect_midi_to (&self, my_output: &Port, ports: &[String]) -> Usually<()>; - fn connect_audio_from (&self, my_input: &Port, ports: &[String]) -> Usually<()>; - fn connect_audio_to (&self, my_output: &Port, ports: &[String]) -> Usually<()>; + fn midi_in (&self, name: impl AsRef, connect: &[impl AsRef]) -> Usually>; + fn midi_out (&self, name: impl AsRef, connect: &[impl AsRef]) -> Usually>; + fn audio_in (&self, name: impl AsRef, connect: &[impl AsRef]) -> Usually>; + fn audio_out (&self, name: impl AsRef, connect: &[impl AsRef]) -> Usually>; } impl RegisterPort for Arc> { - fn midi_in (&self, name: &str) -> Usually> { - Ok(self.read().unwrap().client().register_port(name, MidiIn::default())?) - } - fn midi_out (&self, name: &str) -> Usually> { - Ok(self.read().unwrap().client().register_port(name, MidiOut::default())?) - } - fn audio_out (&self, name: &str) -> Usually> { - Ok(self.read().unwrap().client().register_port(name, AudioOut::default())?) - } - fn audio_in (&self, name: &str) -> Usually> { - Ok(self.read().unwrap().client().register_port(name, AudioIn::default())?) - } - fn connect_midi_from (&self, input: &Port, ports: &[String]) -> Usually<()> { + fn midi_in (&self, name: impl AsRef, connect: &[impl AsRef]) -> Usually> { let jack = self.read().unwrap(); - for port in ports.iter() { - if let Some(port) = jack.port_by_name(port).as_ref() { - jack.client().connect_ports(port, input)?; + let input = jack.client().register_port(name.as_ref(), MidiIn::default())?; + for port in connect.iter() { + let port = port.as_ref(); + if let Some(output) = jack.port_by_name(port).as_ref() { + jack.client().connect_ports(output, &input)?; } else { panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); } } - Ok(()) + Ok(input) } - fn connect_midi_to (&self, output: &Port, ports: &[String]) -> Usually<()> { + fn midi_out (&self, name: impl AsRef, connect: &[impl AsRef]) -> Usually> { let jack = self.read().unwrap(); - for port in ports.iter() { - if let Some(port) = jack.port_by_name(port).as_ref() { - jack.client().connect_ports(output, port)?; + let output = jack.client().register_port(name.as_ref(), MidiOut::default())?; + for port in connect.iter() { + let port = port.as_ref(); + if let Some(input) = jack.port_by_name(port).as_ref() { + jack.client().connect_ports(&output, input)?; } else { panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); } } - Ok(()) + Ok(output) } - fn connect_audio_from (&self, input: &Port, ports: &[String]) -> Usually<()> { + fn audio_in (&self, name: impl AsRef, connect: &[impl AsRef]) -> Usually> { let jack = self.read().unwrap(); - for port in ports.iter() { - if let Some(port) = jack.port_by_name(port).as_ref() { - jack.client().connect_ports(port, input)?; + let input = jack.client().register_port(name.as_ref(), AudioIn::default())?; + for port in connect.iter() { + let port = port.as_ref(); + if let Some(output) = jack.port_by_name(port).as_ref() { + jack.client().connect_ports(output, &input)?; } else { panic!("Missing MIDI output: {port}. Use jack_lsp to list all port names."); } } - Ok(()) + Ok(input) } - fn connect_audio_to (&self, output: &Port, ports: &[String]) -> Usually<()> { + fn audio_out (&self, name: impl AsRef, connect: &[impl AsRef]) -> Usually> { let jack = self.read().unwrap(); - for port in ports.iter() { - if let Some(port) = jack.port_by_name(port).as_ref() { - jack.client().connect_ports(output, port)?; + let output = jack.client().register_port(name.as_ref(), AudioOut::default())?; + for port in connect.iter() { + let port = port.as_ref(); + if let Some(input) = jack.port_by_name(port).as_ref() { + jack.client().connect_ports(&output, input)?; } else { panic!("Missing MIDI input: {port}. Use jack_lsp to list all port names."); } } - Ok(()) + Ok(output) } } @@ -225,10 +253,6 @@ pub enum JackEvent { XRun, } -/// Notification handler used by the [Jack] factory -/// when constructing [JackDevice]s. -pub type DynamicNotifications = Notifications>; - /// Generic notification handler that emits [JackEvent] pub struct Notifications(pub T); diff --git a/src/lib.rs b/src/lib.rs index 5f05cfaf..31bf316b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ pub mod time; pub(crate) use self::time::*; pub use self::time::HasClock; pub mod space; pub(crate) use self::space::*; +pub use self::space::Measure; pub mod tui; pub(crate) use self::tui::*; pub use tui::*; diff --git a/src/midi.rs b/src/midi.rs index f9c483aa..624235ce 100644 --- a/src/midi.rs +++ b/src/midi.rs @@ -127,7 +127,13 @@ pub struct MidiPlayer { pub note_buf: Vec, } impl MidiPlayer { - pub fn new (jack: &Arc>, name: &str) -> Usually { + pub fn new ( + jack: &Arc>, + name: impl AsRef, + midi_from: &[impl AsRef], + midi_to: &[impl AsRef], + ) -> Usually { + let name = name.as_ref(); Ok(Self { clock: ClockModel::from(jack), play_phrase: None, @@ -138,11 +144,11 @@ impl MidiPlayer { notes_in: RwLock::new([false;128]).into(), midi_ins: vec![ - jack.midi_in(&format!("M/{name}"))?, + jack.midi_in(&format!("M/{name}"), midi_from)?, ], midi_outs: vec![ - jack.midi_out(&format!("{name}/M"))?, + jack.midi_out(&format!("{name}/M"), midi_to)?, ], notes_out: RwLock::new([false;128]).into(), reset: true, diff --git a/src/sampler.rs b/src/sampler.rs index 363bba41..d11aa888 100644 --- a/src/sampler.rs +++ b/src/sampler.rs @@ -49,17 +49,24 @@ pub struct Sampler { pub output_gain: f32 } impl Sampler { - pub fn new (jack: &Arc>, name: &str) -> Usually { + pub fn new ( + jack: &Arc>, + name: impl AsRef, + midi_from: &[impl AsRef], + audio_from: &[&[impl AsRef];2], + audio_to: &[&[impl AsRef];2], + ) -> Usually { + let name = name.as_ref(); Ok(Self { - midi_in: jack.midi_in(&format!("M/{name}"))?, - audio_ins: vec![ - jack.audio_in(&format!("L/{name}"))?, - jack.audio_in(&format!("R/{name}"))? + midi_in: jack.midi_in(&format!("M/{name}"), midi_from)?, + audio_ins: vec![ + jack.audio_in(&format!("L/{name}"), audio_from[0])?, + jack.audio_in(&format!("R/{name}"), audio_from[1])? ], input_meter: vec![0.0;2], - audio_outs: vec![ - jack.audio_out(&format!("{name}/L"))?, - jack.audio_out(&format!("{name}/R"))?, + audio_outs: vec![ + jack.audio_out(&format!("{name}/L"), audio_to[0])?, + jack.audio_out(&format!("{name}/R"), audio_to[1])?, ], jack: jack.clone(), name: name.into(), diff --git a/src/sampler/sampler_tui.rs b/src/sampler/sampler_tui.rs index 1bd13a77..386910c6 100644 --- a/src/sampler/sampler_tui.rs +++ b/src/sampler/sampler_tui.rs @@ -11,7 +11,7 @@ pub struct SamplerTui { /// Lowest note displayed pub note_lo: AtomicUsize, pub note_pt: AtomicUsize, - color: ItemPalette + pub color: ItemPalette } impl SamplerTui { @@ -31,18 +31,18 @@ impl SamplerTui { } } -from_jack!(|jack|SamplerTui{ - Self { - cursor: (0, 0), - editing: None, - mode: None, - size: Measure::new(), - note_lo: 36.into(), - note_pt: 36.into(), - color: ItemPalette::from(Color::Rgb(64, 128, 32)), - state: Sampler::new(jack, "sampler")?, - } -}); +//from_jack!(|jack|SamplerTui{ + //Self { + //cursor: (0, 0), + //editing: None, + //mode: None, + //size: Measure::new(), + //note_lo: 36.into(), + //note_pt: 36.into(), + //color: ItemPalette::from(Color::Rgb(64, 128, 32)), + //state: Sampler::new(jack, &"sampler", &[], &[&[], &[]], &[&[], &[]])?, + //} +//}); render!(|self: SamplerTui|{ let keys_width = 5; diff --git a/src/space.rs b/src/space.rs index 5629081b..cfc5a0d2 100644 --- a/src/space.rs +++ b/src/space.rs @@ -15,6 +15,7 @@ pub(crate) mod fixed; pub(crate) use fixed::*; pub(crate) mod inset_outset; pub(crate) use inset_outset::*; pub(crate) mod layers; pub(crate) use layers::*; pub(crate) mod measure; pub(crate) use measure::*; +pub use self::measure::Measure; pub(crate) mod min_max; pub(crate) use min_max::*; pub(crate) mod push_pull; pub(crate) use push_pull::*; pub(crate) mod scroll; From 0c9c386a798535a7f270340681b2ac3a7f7ebf67 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 29 Dec 2024 20:32:00 +0100 Subject: [PATCH 076/815] unify init naming; GrooveboxTui -> Groovebox --- bin/cli_arranger.rs | 7 ++----- bin/cli_groovebox.rs | 5 +++-- bin/cli_sampler.rs | 3 ++- bin/cli_transport.rs | 3 ++- src/groovebox.rs | 18 +++++++++--------- src/lib.rs | 2 +- src/status/status_groovebox.rs | 2 +- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/bin/cli_arranger.rs b/bin/cli_arranger.rs index e275ff09..a29651e0 100644 --- a/bin/cli_arranger.rs +++ b/bin/cli_arranger.rs @@ -34,11 +34,8 @@ pub struct ArrangerCli { impl ArrangerCli { /// Run the arranger TUI from CLI arguments. fn run (&self) -> Usually<()> { - let mut client_name = String::from("tek_arranger"); - if let Some(name) = self.name.as_ref() { - client_name = name.clone(); - } - Tui::run(JackConnection::new(client_name.as_str())?.activate_with(|jack|{ + let name = self.name.as_deref().unwrap_or("tek_arranger"); + Tui::run(JackConnection::new(name)?.activate_with(|jack|{ let mut app = ArrangerTui::try_from(jack)?; let jack = jack.read().unwrap(); app.color = ItemPalette::random(); diff --git a/bin/cli_groovebox.rs b/bin/cli_groovebox.rs index facd66c9..a946e376 100644 --- a/bin/cli_groovebox.rs +++ b/bin/cli_groovebox.rs @@ -36,8 +36,9 @@ pub struct GrooveboxCli { } impl GrooveboxCli { fn run (&self) -> Usually<()> { - Tui::run(JackConnection::new("tek_groovebox")?.activate_with(|jack|{ - let app = tek::GrooveboxTui::new( + let name = self.name.as_deref().unwrap_or("tek_groovebox"); + Tui::run(JackConnection::new(name)?.activate_with(|jack|{ + let app = tek::Groovebox::new( jack, &self.midi_from.as_slice(), &self.midi_to.as_slice(), diff --git a/bin/cli_sampler.rs b/bin/cli_sampler.rs index 262e40c5..278ee8b0 100644 --- a/bin/cli_sampler.rs +++ b/bin/cli_sampler.rs @@ -23,7 +23,8 @@ pub fn main () -> Usually<()> { SamplerCli::parse().run() } } impl SamplerCli { fn run (&self) -> Usually<()> { - Tui::run(JackConnection::new("tek_sampler")?.activate_with(|jack|{ + let name = self.name.as_deref().unwrap_or("tek_sampler"); + Tui::run(JackConnection::new(name)?.activate_with(|jack|{ Ok(tek::SamplerTui { cursor: (0, 0), editing: None, diff --git a/bin/cli_transport.rs b/bin/cli_transport.rs index bb2e56b6..fead5a05 100644 --- a/bin/cli_transport.rs +++ b/bin/cli_transport.rs @@ -2,7 +2,8 @@ include!("./lib.rs"); /// Application entrypoint. pub fn main () -> Usually<()> { - Tui::run(JackConnection::new("tek_transport")?.activate_with(|jack|{ + let name = self.name.as_deref().unwrap_or("tek_transport"); + Tui::run(JackConnection::new(name)?.activate_with(|jack|{ TransportTui::try_from(jack) })?)?; Ok(()) diff --git a/src/groovebox.rs b/src/groovebox.rs index ce802986..29498f7a 100644 --- a/src/groovebox.rs +++ b/src/groovebox.rs @@ -6,7 +6,7 @@ use GrooveboxCommand::*; use PhraseCommand::*; use PhrasePoolCommand::*; -pub struct GrooveboxTui { +pub struct Groovebox { _jack: Arc>, pub player: MidiPlayer, @@ -20,7 +20,7 @@ pub struct GrooveboxTui { pub midi_buf: Vec>>, pub perf: PerfModel, } -impl GrooveboxTui { +impl Groovebox { pub fn new ( jack: &Arc>, midi_from: &[impl AsRef], @@ -64,8 +64,8 @@ impl GrooveboxTui { }) } } -has_clock!(|self: GrooveboxTui|self.player.clock()); -audio!(|self: GrooveboxTui, client, scope|{ +has_clock!(|self: Groovebox|self.player.clock()); +audio!(|self: Groovebox, client, scope|{ let t0 = self.perf.get_t0(); if Control::Quit == ClockAudio(&mut self.player).process(client, scope) { return Control::Quit @@ -111,7 +111,7 @@ audio!(|self: GrooveboxTui, client, scope|{ self.perf.update(t0, scope); Control::Continue }); -render!(|self:GrooveboxTui|{ +render!(|self:Groovebox|{ let w = self.size.w(); let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; let pool_w = if self.pool.visible { phrase_w } else { 0 }; @@ -165,7 +165,7 @@ render!(|self:GrooveboxTui|{ ])) }); -struct GrooveboxSamples<'a>(&'a GrooveboxTui); +struct GrooveboxSamples<'a>(&'a Groovebox); render!(|self: GrooveboxSamples<'a>|{ let note_lo = self.0.editor.note_lo().load(Relaxed); let note_pt = self.0.editor.note_point(); @@ -198,9 +198,9 @@ pub enum GrooveboxCommand { Sampler(SamplerCommand), } -handle!(|self: GrooveboxTui, input|GrooveboxCommand::execute_with_state(self, input)); +handle!(|self: Groovebox, input|GrooveboxCommand::execute_with_state(self, input)); -input_to_command!(GrooveboxCommand: |state: GrooveboxTui, input|match input.event() { +input_to_command!(GrooveboxCommand: |state: Groovebox, input|match input.event() { // TODO: k: toggle on-screen keyboard key_pat!(Ctrl-Char('k')) => { todo!("keyboard") @@ -249,7 +249,7 @@ input_to_command!(GrooveboxCommand: |state: GrooveboxTui, input|match input } }); -command!(|self:GrooveboxCommand,state:GrooveboxTui|match self { +command!(|self: GrooveboxCommand, state: Groovebox|match self { Self::Pool(cmd) => { let mut default = |cmd: PoolCommand|cmd .execute(&mut state.pool) diff --git a/src/lib.rs b/src/lib.rs index 31bf316b..f274eed6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,7 +40,7 @@ pub mod plugin; pub(crate) use self::plugin::*; pub use self::plugin::*; pub mod groovebox; pub(crate) use self::groovebox::*; -pub use self::groovebox::GrooveboxTui; +pub use self::groovebox::Groovebox; pub mod pool; pub(crate) use self::pool::*; pub use self::pool::PoolModel; diff --git a/src/status/status_groovebox.rs b/src/status/status_groovebox.rs index a10b9b92..5fe29457 100644 --- a/src/status/status_groovebox.rs +++ b/src/status/status_groovebox.rs @@ -8,7 +8,7 @@ pub struct GrooveboxStatus { pub(crate) size: String, pub(crate) playing: bool, } -from!(|state:&GrooveboxTui|GrooveboxStatus = { +from!(|state: &Groovebox|GrooveboxStatus = { let samples = state.clock().chunk.load(Relaxed); let rate = state.clock().timebase.sr.get(); let buffer = samples as f64 / rate; From 35a88cb70f157d02a21c87127aa47d35cb560938 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 30 Dec 2024 12:54:19 +0100 Subject: [PATCH 077/815] remove LayoutSplit; merge split and bsp modules --- bin/cli_arranger.rs | 5 -- bin/cli_transport.rs | 2 +- src/arranger.rs | 6 +- src/groovebox.rs | 6 +- src/sequencer.rs | 6 +- src/space.rs | 3 +- src/space/bsp.rs | 103 ------------------------------- src/space/split.rs | 140 ++++++++++++++++++++++++++++++++----------- 8 files changed, 117 insertions(+), 154 deletions(-) delete mode 100644 src/space/bsp.rs diff --git a/bin/cli_arranger.rs b/bin/cli_arranger.rs index a29651e0..23ae086c 100644 --- a/bin/cli_arranger.rs +++ b/bin/cli_arranger.rs @@ -9,23 +9,18 @@ pub struct ArrangerCli { /// Name of JACK client #[arg(short, long)] name: Option, - /// Whether to include a transport toolbar (default: true) #[arg(short, long, default_value_t = true)] transport: bool, - /// Number of tracks #[arg(short = 'x', long, default_value_t = 4)] tracks: usize, - /// Number of scenes #[arg(short, long, default_value_t = 8)] scenes: usize, - /// MIDI outs to connect each track to. #[arg(short='i', long)] midi_from: Vec, - /// MIDI ins to connect each track to. #[arg(short='o', long)] midi_to: Vec, diff --git a/bin/cli_transport.rs b/bin/cli_transport.rs index fead5a05..5d90cad8 100644 --- a/bin/cli_transport.rs +++ b/bin/cli_transport.rs @@ -2,7 +2,7 @@ include!("./lib.rs"); /// Application entrypoint. pub fn main () -> Usually<()> { - let name = self.name.as_deref().unwrap_or("tek_transport"); + let name = "tek_transport"; Tui::run(JackConnection::new(name)?.activate_with(|jack|{ TransportTui::try_from(jack) })?)?; diff --git a/src/arranger.rs b/src/arranger.rs index 6b4ab255..502d6293 100644 --- a/src/arranger.rs +++ b/src/arranger.rs @@ -104,10 +104,10 @@ render!(|self: ArrangerTui|{ let transport = TransportView::from((self, Some(ItemPalette::from(TuiTheme::g(96))), true)); let with_transport = |x|col!([row!(![&play, &transport]), &x]); let pool_size = if self.phrases.visible { self.splits[1] } else { 0 }; - let with_pool = |x|Split::left(false, pool_size, PoolView(&self.phrases), x); + let with_pool = |x|Split::w(false, pool_size, PoolView(&self.phrases), x); let status = ArrangerStatus::from(self); - let with_editbar = |x|Tui::split_n(false, 1, MidiEditStatus(&self.editor), x); - let with_status = |x|Tui::split_n(false, 2, status, x); + let with_editbar = |x|Split::n(false, 1, MidiEditStatus(&self.editor), x); + let with_status = |x|Split::n(false, 2, status, x); let with_size = |x|lay!([&self.size, x]); let arranger = ||lay!(|add|{ let color = self.color; diff --git a/src/groovebox.rs b/src/groovebox.rs index 29498f7a..875564ec 100644 --- a/src/groovebox.rs +++ b/src/groovebox.rs @@ -132,7 +132,7 @@ render!(|self:Groovebox|{ PhraseSelector::next_phrase(&self.player), ]))), row!([ - Tui::split_n(false, 9, + Split::n(false, 9, col!([ row!(|add|{ if let Some(sample) = &self.sampler.mapped[note_pt] { @@ -152,9 +152,9 @@ render!(|self:Groovebox|{ })), ]), ]), - Tui::split_w(false, pool_w, + Split::w(false, pool_w, Tui::pull_y(1, Fill::h(Align::e(PoolView(&self.pool)))), - Tui::split_e(false, sampler_w, Fill::wh(col!([ + Split::e(false, sampler_w, Fill::wh(col!([ Meters(self.sampler.input_meter.as_ref()), GrooveboxSamples(self), ])), Fill::h(&self.editor)) diff --git a/src/sequencer.rs b/src/sequencer.rs index d24f5511..f41cb6a6 100644 --- a/src/sequencer.rs +++ b/src/sequencer.rs @@ -45,10 +45,10 @@ render!(|self: SequencerTui|{ let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; let pool_w = if self.phrases.visible { phrase_w } else { 0 }; let pool = Tui::pull_y(1, Fill::h(Align::e(PoolView(&self.phrases)))); - let with_pool = move|x|Tui::split_w(false, pool_w, pool, x); + let with_pool = move|x|Split::w(false, pool_w, pool, x); let status = SequencerStatus::from(self); - let with_status = |x|Tui::split_n(false, if self.status { 2 } else { 0 }, status, x); - let with_editbar = |x|Tui::split_n(false, 1, MidiEditStatus(&self.editor), x); + let with_status = |x|Split::n(false, if self.status { 2 } else { 0 }, status, x); + let with_editbar = |x|Split::n(false, 1, MidiEditStatus(&self.editor), x); let with_size = |x|lay!([self.size, x]); let editor = with_editbar(with_pool(Fill::wh(&self.editor))); let color = self.player.play_phrase().as_ref().map(|(_,p)| diff --git a/src/space.rs b/src/space.rs index cfc5a0d2..0eed3bfd 100644 --- a/src/space.rs +++ b/src/space.rs @@ -8,7 +8,6 @@ use std::fmt::{Display, Debug}; ////////////////////////////////////////////////////// pub(crate) mod align; -pub(crate) mod bsp; pub(crate) mod cond; pub(crate) use cond::*; pub(crate) mod fill; pub(crate) mod fixed; pub(crate) use fixed::*; @@ -25,8 +24,8 @@ pub(crate) mod stack; pub(crate) use stack::*; pub use self::{ align::*, - bsp::*, fill::*, + split::*, }; #[derive(Copy, Clone, PartialEq)] diff --git a/src/space/bsp.rs b/src/space/bsp.rs deleted file mode 100644 index cf778e9f..00000000 --- a/src/space/bsp.rs +++ /dev/null @@ -1,103 +0,0 @@ -use crate::*; - -pub enum Bsp, Y: Render> { - /// X is north of Y - N(Option, Option), - /// X is south of Y - S(Option, Option), - /// X is east of Y - E(Option, Option), - /// X is west of Y - W(Option, Option), - /// X is above Y - A(Option, Option), - /// X is below Y - B(Option, Option), - /// Should be avoided. - Null(PhantomData), -} - -impl, Y: Render> Bsp { - pub fn new (x: X) -> Self { Self::A(Some(x), None) } - pub fn n (x: X, y: Y) -> Self { Self::N(Some(x), Some(y)) } - pub fn s (x: X, y: Y) -> Self { Self::S(Some(x), Some(y)) } - pub fn e (x: X, y: Y) -> Self { Self::E(Some(x), Some(y)) } - pub fn w (x: X, y: Y) -> Self { Self::W(Some(x), Some(y)) } - pub fn a (x: X, y: Y) -> Self { Self::A(Some(x), Some(y)) } - pub fn b (x: X, y: Y) -> Self { Self::B(Some(x), Some(y)) } -} - -impl, Y: Render> Default for Bsp { - fn default () -> Self { - Self::Null(Default::default()) - } -} - -impl, Y: Render> Render for Bsp { - fn min_size (&self, to: E::Size) -> Perhaps { - Ok(Some(match self { - Self::Null(_) => [0.into(), 0.into()].into(), - Self::S(a, b) => { - let a = a.min_size(to)?.unwrap_or([0.into(), 0.into()].into()); - let b = b.min_size(to)?.unwrap_or([0.into(), 0.into()].into()); - [a.w().max(b.w()), a.h() + b.h()].into() - }, - Self::E(a, b) => { - let a = a.min_size(to)?.unwrap_or([0.into(), 0.into()].into()); - let b = b.min_size(to)?.unwrap_or([0.into(), 0.into()].into()); - [a.w() + b.w(), a.h().max(b.h())].into() - }, - Self::W(a, b) => { - let a = a.min_size(to)?.unwrap_or([0.into(), 0.into()].into()); - let b = b.min_size(to)?.unwrap_or([0.into(), 0.into()].into()); - [a.w() + b.w(), a.h().max(b.h())].into() - }, - Self::N(a, b) => { - let a = a.min_size(to)?.unwrap_or([0.into(), 0.into()].into()); - let b = b.min_size(to)?.unwrap_or([0.into(), 0.into()].into()); - [a.w().max(b.w()), a.h() + b.h()].into() - }, - _ => todo!() - })) - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - let n = [0.into(), 0.into()].into(); - let s = to.area().wh().into(); - Ok(match self { - Self::Null(_) => {}, - Self::S(a, b) => { - let s_a = a.min_size(s)?.unwrap_or(n); - let _ = b.min_size(s)?.unwrap_or(n); - let h = s_a.h().into(); - to.render_in(to.area().clip_h(h).into(), a)?; - to.render_in(to.area().shrink_y(h).push_y(h).into(), b)?; - }, - Self::E(a, b) => { - let s_a = a.min_size(s)?.unwrap_or(n); - let _ = b.min_size(s)?.unwrap_or(n); - let w = s_a.w().into(); - to.render_in(to.area().clip_w(w).into(), a)?; - to.render_in(to.area().push_x(w).shrink_x(w).into(), b)?; - }, - Self::W(a, b) => { - let s_a = a.min_size(s)?.unwrap_or(n); - let _ = b.min_size(s)?.unwrap_or(n); - let w = (to.area().w() - s_a.w()).into(); - to.render_in(to.area().push_x(w).into(), a)?; - to.render_in(to.area().shrink_x(w).into(), b)?; - }, - Self::N(a, b) => { - let s_a = a.min_size(s)?.unwrap_or(n); - let _ = b.min_size(s)?.unwrap_or(n); - let h = to.area().h() - s_a.h(); - to.render_in(to.area().push_y(h).into(), a)?; - to.render_in(to.area().shrink_y(h).into(), b)?; - }, - _ => todo!() - }) - } -} - -#[cfg(test)] #[test] fn test_bsp () { - // TODO -} diff --git a/src/space/split.rs b/src/space/split.rs index 26faf2c1..73f9261d 100644 --- a/src/space/split.rs +++ b/src/space/split.rs @@ -1,36 +1,6 @@ use crate::*; use Direction::*; -impl LayoutSplit for E {} - -pub trait LayoutSplit { - fn split , B: Render> ( - flip: bool, direction: Direction, amount: E::Unit, a: A, b: B - ) -> Split { - Split::new(flip, direction, amount, a, b) - } - fn split_n , B: Render> ( - flip: bool, amount: E::Unit, a: A, b: B - ) -> Split { - Self::split(flip, North, amount, a, b) - } - fn split_s , B: Render> ( - flip: bool, amount: E::Unit, a: A, b: B - ) -> Split { - Self::split(flip, South, amount, a, b) - } - fn split_w , B: Render> ( - flip: bool, amount: E::Unit, a: A, b: B - ) -> Split { - Self::split(flip, West, amount, a, b) - } - fn split_e , B: Render> ( - flip: bool, amount: E::Unit, a: A, b: B - ) -> Split { - Self::split(flip, East, amount, a, b) - } -} - /// A binary split with fixed proportion pub struct Split, B: Render>( pub bool, pub Direction, pub E::Unit, A, B, PhantomData @@ -40,16 +10,16 @@ impl, B: Render> Split { pub fn new (flip: bool, direction: Direction, proportion: E::Unit, a: A, b: B) -> Self { Self(flip, direction, proportion, a, b, Default::default()) } - pub fn up (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { + pub fn n (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { Self(flip, North, proportion, a, b, Default::default()) } - pub fn down (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { + pub fn s (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { Self(flip, South, proportion, a, b, Default::default()) } - pub fn left (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { + pub fn e (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { Self(flip, West, proportion, a, b, Default::default()) } - pub fn right (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { + pub fn w (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { Self(flip, East, proportion, a, b, Default::default()) } } @@ -69,3 +39,105 @@ impl, B: Render> Render for Split { }) } } + +pub enum Bsp, Y: Render> { + /// X is north of Y + N(Option, Option), + /// X is south of Y + S(Option, Option), + /// X is east of Y + E(Option, Option), + /// X is west of Y + W(Option, Option), + /// X is above Y + A(Option, Option), + /// X is below Y + B(Option, Option), + /// Should be avoided. + Null(PhantomData), +} + +impl, Y: Render> Bsp { + pub fn new (x: X) -> Self { Self::A(Some(x), None) } + pub fn n (x: X, y: Y) -> Self { Self::N(Some(x), Some(y)) } + pub fn s (x: X, y: Y) -> Self { Self::S(Some(x), Some(y)) } + pub fn e (x: X, y: Y) -> Self { Self::E(Some(x), Some(y)) } + pub fn w (x: X, y: Y) -> Self { Self::W(Some(x), Some(y)) } + pub fn a (x: X, y: Y) -> Self { Self::A(Some(x), Some(y)) } + pub fn b (x: X, y: Y) -> Self { Self::B(Some(x), Some(y)) } +} + +impl, Y: Render> Default for Bsp { + fn default () -> Self { + Self::Null(Default::default()) + } +} + +impl, Y: Render> Render for Bsp { + fn min_size (&self, to: E::Size) -> Perhaps { + Ok(Some(match self { + Self::Null(_) => [0.into(), 0.into()].into(), + Self::S(a, b) => { + let a = a.min_size(to)?.unwrap_or([0.into(), 0.into()].into()); + let b = b.min_size(to)?.unwrap_or([0.into(), 0.into()].into()); + [a.w().max(b.w()), a.h() + b.h()].into() + }, + Self::E(a, b) => { + let a = a.min_size(to)?.unwrap_or([0.into(), 0.into()].into()); + let b = b.min_size(to)?.unwrap_or([0.into(), 0.into()].into()); + [a.w() + b.w(), a.h().max(b.h())].into() + }, + Self::W(a, b) => { + let a = a.min_size(to)?.unwrap_or([0.into(), 0.into()].into()); + let b = b.min_size(to)?.unwrap_or([0.into(), 0.into()].into()); + [a.w() + b.w(), a.h().max(b.h())].into() + }, + Self::N(a, b) => { + let a = a.min_size(to)?.unwrap_or([0.into(), 0.into()].into()); + let b = b.min_size(to)?.unwrap_or([0.into(), 0.into()].into()); + [a.w().max(b.w()), a.h() + b.h()].into() + }, + _ => todo!() + })) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + let n = [0.into(), 0.into()].into(); + let s = to.area().wh().into(); + Ok(match self { + Self::Null(_) => {}, + Self::S(a, b) => { + let s_a = a.min_size(s)?.unwrap_or(n); + let _ = b.min_size(s)?.unwrap_or(n); + let h = s_a.h().into(); + to.render_in(to.area().clip_h(h).into(), a)?; + to.render_in(to.area().shrink_y(h).push_y(h).into(), b)?; + }, + Self::E(a, b) => { + let s_a = a.min_size(s)?.unwrap_or(n); + let _ = b.min_size(s)?.unwrap_or(n); + let w = s_a.w().into(); + to.render_in(to.area().clip_w(w).into(), a)?; + to.render_in(to.area().push_x(w).shrink_x(w).into(), b)?; + }, + Self::W(a, b) => { + let s_a = a.min_size(s)?.unwrap_or(n); + let _ = b.min_size(s)?.unwrap_or(n); + let w = (to.area().w() - s_a.w()).into(); + to.render_in(to.area().push_x(w).into(), a)?; + to.render_in(to.area().shrink_x(w).into(), b)?; + }, + Self::N(a, b) => { + let s_a = a.min_size(s)?.unwrap_or(n); + let _ = b.min_size(s)?.unwrap_or(n); + let h = to.area().h() - s_a.h(); + to.render_in(to.area().push_y(h).into(), a)?; + to.render_in(to.area().shrink_y(h).into(), b)?; + }, + _ => todo!() + }) + } +} + +#[cfg(test)] #[test] fn test_bsp () { + // TODO +} From 61b447403becfdff3cfbb11616b14d5befb62d73 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 30 Dec 2024 13:13:29 +0100 Subject: [PATCH 078/815] reduce number of space modules --- src/lib.rs | 9 +- src/sequencer.rs | 18 +- src/space.rs | 35 +-- src/space/cond.rs | 66 ++---- src/space/fill.rs | 47 ---- src/space/fixed.rs | 54 ----- src/space/inset_outset.rs | 92 -------- src/space/layers.rs | 12 - src/space/min_max.rs | 95 -------- src/space/{push_pull.rs => position.rs} | 91 ++++++++ src/space/shrink_grow.rs | 103 --------- src/space/size.rs | 296 ++++++++++++++++++++++++ src/space/split.rs | 151 ++++++++++++ src/space/stack.rs | 153 ------------ 14 files changed, 599 insertions(+), 623 deletions(-) delete mode 100644 src/space/fill.rs delete mode 100644 src/space/fixed.rs delete mode 100644 src/space/inset_outset.rs delete mode 100644 src/space/min_max.rs rename src/space/{push_pull.rs => position.rs} (59%) delete mode 100644 src/space/shrink_grow.rs create mode 100644 src/space/size.rs delete mode 100644 src/space/stack.rs diff --git a/src/lib.rs b/src/lib.rs index f274eed6..01258fc5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,20 +70,23 @@ pub(crate) use crossterm::{ExecutableCommand}; pub(crate) use crossterm::terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode}; pub(crate) use crossterm::event::{KeyCode, KeyModifiers, KeyEvent, KeyEventKind, KeyEventState}; -pub use ::ratatui; pub(crate) use ratatui::{ +pub use ::ratatui; +pub(crate) use ratatui::{ prelude::{Style, Buffer}, style::{Stylize, Modifier}, backend::{Backend, CrosstermBackend, ClearType} }; -pub use ::midly::{self, num::u7}; pub(crate) use ::midly::{ +pub use ::midly::{self, num::u7}; +pub(crate) use ::midly::{ Smf, MidiMessage, TrackEventKind, live::LiveEvent, }; -pub use ::palette; pub(crate) use ::palette::{ +pub use ::palette; +pub(crate) use ::palette::{ *, convert::*, okhsl::* diff --git a/src/sequencer.rs b/src/sequencer.rs index f41cb6a6..47ac79f6 100644 --- a/src/sequencer.rs +++ b/src/sequencer.rs @@ -51,17 +51,21 @@ render!(|self: SequencerTui|{ let with_editbar = |x|Split::n(false, 1, MidiEditStatus(&self.editor), x); let with_size = |x|lay!([self.size, x]); let editor = with_editbar(with_pool(Fill::wh(&self.editor))); - let color = self.player.play_phrase().as_ref().map(|(_,p)| - p.as_ref().map(|p|p.read().unwrap().color) - ).flatten().clone(); - let toolbar = row!([ + + let color = self.player.play_phrase().as_ref().map(|(_,p)| + p.as_ref().map(|p|p.read().unwrap().color) + ).flatten().clone(); + + let toolbar = Cond::when(self.transport, row!([ Fixed::wh(5, 2, PlayPause(self.clock.is_rolling())), Fixed::h(2, TransportView::from((self, color, true))), - ]).when(self.transport); - let play_queue = row!([ + ])); + + let play_queue = Cond::when(self.selectors, row!([ PhraseSelector::play_phrase(&self.player), PhraseSelector::next_phrase(&self.player), - ]).when(self.selectors);; + ])); + Tui::min_y(15, with_size(with_status(col!([ toolbar, play_queue, diff --git a/src/space.rs b/src/space.rs index 0eed3bfd..83c6b7d8 100644 --- a/src/space.rs +++ b/src/space.rs @@ -8,23 +8,20 @@ use std::fmt::{Display, Debug}; ////////////////////////////////////////////////////// pub(crate) mod align; -pub(crate) mod cond; pub(crate) use cond::*; -pub(crate) mod fill; -pub(crate) mod fixed; pub(crate) use fixed::*; -pub(crate) mod inset_outset; pub(crate) use inset_outset::*; -pub(crate) mod layers; pub(crate) use layers::*; -pub(crate) mod measure; pub(crate) use measure::*; -pub use self::measure::Measure; -pub(crate) mod min_max; pub(crate) use min_max::*; -pub(crate) mod push_pull; pub(crate) use push_pull::*; +pub(crate) mod cond; pub(crate) use cond::*; +pub(crate) mod layers; pub(crate) use layers::*; +pub(crate) mod measure; pub(crate) use measure::*; +pub(crate) mod position; pub(crate) use position::*; pub(crate) mod scroll; -pub(crate) mod shrink_grow; pub(crate) use shrink_grow::*; -pub(crate) mod split; pub(crate) use split::*; -pub(crate) mod stack; pub(crate) use stack::*; +pub(crate) mod size; pub(crate) use size::*; +pub(crate) mod split; pub(crate) use split::*; pub use self::{ align::*, - fill::*, + cond::*, + measure::*, + position::*, + size::*, split::*, }; @@ -234,3 +231,15 @@ impl Area for [N;4] { Stack::right(move|add|{ for $pat in $collection { add(&$item)?; } Ok(()) }) }; } + +#[macro_export] macro_rules! lay { + ([$($expr:expr),* $(,)?]) => { + Layers::new(move|add|{ $(add(&$expr)?;)* Ok(()) }) + }; + (![$($expr:expr),* $(,)?]) => { + Layers::new(|add|{ $(add(&$expr)?;)* Ok(()) }) + }; + ($expr:expr) => { + Layers::new($expr) + }; +} diff --git a/src/space/cond.rs b/src/space/cond.rs index 08fd57da..2f8a6006 100644 --- a/src/space/cond.rs +++ b/src/space/cond.rs @@ -1,65 +1,43 @@ use crate::*; -pub enum Cond, B: Render> { - _Unused(E), - When(bool, A), - Either(bool, A, B) -} +/// Conditional rendering, in unary and binary forms. +pub struct Cond; -impl> LayoutCond for R {} - -pub trait LayoutCond: Render + Sized { - fn when (self, cond: bool) -> If { - If(Default::default(), cond, self) +impl Cond { + /// Render `item` when `cond` is true. + pub fn when > (cond: bool, item: A) -> When { + When(cond, item, Default::default()) } - fn or > (self, cond: bool, other: B) -> Either { - Either(Default::default(), cond, self, other) + /// Render `item` if `cond` is true, otherwise render `other`. + pub fn either , B: Render> (cond: bool, item: A, other: B) -> Either { + Either(cond, item, other, Default::default()) } } -impl LayoutCondStatic for E {} +/// Renders `self.1` when `self.0` is true. +pub struct When>(bool, A, PhantomData); -pub trait LayoutCondStatic { - fn either , B: Render> ( - condition: bool, - a: A, - b: B, - ) -> Either { - Either(Default::default(), condition, a, b) - } -} +/// Renders `self.1` when `self.0` is true, otherwise renders `self.2` +pub struct Either, B: Render>(bool, A, B, PhantomData); -/// Render widget if predicate is true -pub struct If>(PhantomData, bool, A); - -impl> Render for If { +impl> Render for When { fn min_size (&self, to: E::Size) -> Perhaps { - if self.1 { - return self.2.min_size(to) - } - Ok(None) + let Self(cond, item, ..) = self; + if *cond { item.min_size(to) } else { Ok(Some([0.into(), 0.into()].into())) } } fn render (&self, to: &mut E::Output) -> Usually<()> { - if self.1 { - return self.2.render(to) - } - Ok(()) + let Self(cond, item, ..) = self; + if *cond { item.render(to) } else { Ok(()) } } } -/// Render widget A if predicate is true, otherwise widget B -pub struct Either, B: Render>( - PhantomData, - bool, - A, - B, -); - impl, B: Render> Render for Either { fn min_size (&self, to: E::Size) -> Perhaps { - if self.1 { self.2.min_size(to) } else { self.3.min_size(to) } + let Self(cond, item, other, ..) = self; + if *cond { item.min_size(to) } else { other.min_size(to) } } fn render (&self, to: &mut E::Output) -> Usually<()> { - if self.1 { self.2.render(to) } else { self.3.render(to) } + let Self(cond, item, other, ..) = self; + if *cond { item.render(to) } else { other.render(to) } } } diff --git a/src/space/fill.rs b/src/space/fill.rs deleted file mode 100644 index 044d751a..00000000 --- a/src/space/fill.rs +++ /dev/null @@ -1,47 +0,0 @@ -use crate::*; - -pub enum Fill> { - X(W), - Y(W), - XY(W), - _Unused(PhantomData) -} - -impl> Fill { - fn inner (&self) -> &W { - match self { - Self::X(inner) => &inner, - Self::Y(inner) => &inner, - Self::XY(inner) => &inner, - _ => unreachable!(), - } - } - pub fn w (fill: W) -> Self { - Self::X(fill) - } - pub fn h (fill: W) -> Self { - Self::Y(fill) - } - pub fn wh (fill: W) -> Self { - Self::XY(fill) - } -} - -impl> Render for Fill { - fn min_size (&self, to: E::Size) -> Perhaps { - let area = self.inner().min_size(to.into())?; - if let Some(area) = area { - Ok(Some(match self { - Self::X(_) => [to.w().into(), area.h()], - Self::Y(_) => [area.w(), to.h().into()], - Self::XY(_) => [to.w().into(), to.h().into()], - _ => unreachable!(), - }.into())) - } else { - Ok(None) - } - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - self.inner().render(to) - } -} diff --git a/src/space/fixed.rs b/src/space/fixed.rs deleted file mode 100644 index 63db372b..00000000 --- a/src/space/fixed.rs +++ /dev/null @@ -1,54 +0,0 @@ -use crate::*; - -/// Enforce fixed size of drawing area -pub enum Fixed> { - _Unused(PhantomData), - /// Enforce fixed width - X(E::Unit, T), - /// Enforce fixed height - Y(E::Unit, T), - /// Enforce fixed width and height - XY(E::Unit, E::Unit, T), -} - -impl> Fixed { - pub fn inner (&self) -> &T { - match self { - Self::X(_, i) => i, - Self::Y(_, i) => i, - Self::XY(_, _, i) => i, - _ => unreachable!(), - } - } - pub fn w (x: E::Unit, w: T) -> Self { - Self::X(x, w) - } - pub fn h (y: E::Unit, w: T) -> Self { - Self::Y(y, w) - } - pub fn wh (x: E::Unit, y: E::Unit, w: T) -> Self { - Self::XY(x, y, w) - } -} - -impl> Render for Fixed { - fn min_size (&self, to: E::Size) -> Perhaps { - Ok(match self { - Self::X(w, _) => - if to.w() >= *w { Some([*w, to.h()].into()) } else { None }, - Self::Y(h, _) => - if to.h() >= *h { Some([to.w(), *h].into()) } else { None }, - Self::XY(w, h, _) - => if to.w() >= *w && to.h() >= *h { Some([*w, *h].into()) } else { None }, - _ => unreachable!(), - }) - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - // 🡘 🡙 ←🡙→ - if let Some(size) = self.min_size(to.area().wh().into())? { - to.render_in(to.area().clip(size).into(), self.inner()) - } else { - Ok(()) - } - } -} diff --git a/src/space/inset_outset.rs b/src/space/inset_outset.rs deleted file mode 100644 index 2985aecf..00000000 --- a/src/space/inset_outset.rs +++ /dev/null @@ -1,92 +0,0 @@ -use crate::*; - -impl + LayoutShrinkGrow> LayoutInsetOutset for E {} - -pub trait LayoutInsetOutset: LayoutPushPull + LayoutShrinkGrow { - fn inset_x > (x: E::Unit, w: W) -> Inset { - Inset::X(x, w) - } - fn inset_y > (y: E::Unit, w: W) -> Inset { - Inset::Y(y, w) - } - fn inset_xy > (x: E::Unit, y: E::Unit, w: W) -> Inset { - Inset::XY(x, y, w) - } - fn outset_x > (x: E::Unit, w: W) -> Outset { - Outset::X(x, w) - } - fn outset_y > (y: E::Unit, w: W) -> Outset { - Outset::Y(y, w) - } - fn outset_xy > (x: E::Unit, y: E::Unit, w: W) -> Outset { - Outset::XY(x, y, w) - } -} - -/// Shrink from each side -pub enum Inset { - /// Decrease width - X(E::Unit, T), - /// Decrease height - Y(E::Unit, T), - /// Decrease width and height - XY(E::Unit, E::Unit, T), -} - -impl> Inset { - pub fn inner (&self) -> &T { - match self { - Self::X(_, i) => i, - Self::Y(_, i) => i, - Self::XY(_, _, i) => i, - } - } -} - -impl> Render for Inset { - fn render (&self, to: &mut E::Output) -> Usually<()> { - match self { - Self::X(x, inner) => E::push_x(*x, E::shrink_x(*x, inner)), - Self::Y(y, inner) => E::push_y(*y, E::shrink_y(*y, inner)), - Self::XY(x, y, inner) => E::push_xy(*x, *y, E::shrink_xy(*x, *y, inner)), - }.render(to) - } -} - -/// Grow on each side -pub enum Outset> { - /// Increase width - X(E::Unit, T), - /// Increase height - Y(E::Unit, T), - /// Increase width and height - XY(E::Unit, E::Unit, T), -} - - -impl> Outset { - pub fn inner (&self) -> &T { - match self { - Self::X(_, i) => i, - Self::Y(_, i) => i, - Self::XY(_, _, i) => i, - } - } -} - -impl> Render for Outset { - fn min_size (&self, to: E::Size) -> Perhaps { - match *self { - Self::X(x, ref inner) => E::grow_x(x + x, inner), - Self::Y(y, ref inner) => E::grow_y(y + y, inner), - Self::XY(x, y, ref inner) => E::grow_xy(x + x, y + y, inner), - }.min_size(to) - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - match *self { - Self::X(x, ref inner) => E::push_x(x, inner), - Self::Y(y, ref inner) => E::push_y(y, inner), - Self::XY(x, y, ref inner) => E::push_xy(x, y, inner), - }.render(to) - } -} diff --git a/src/space/layers.rs b/src/space/layers.rs index 353198ab..9fe07ecd 100644 --- a/src/space/layers.rs +++ b/src/space/layers.rs @@ -1,17 +1,5 @@ use crate::*; -#[macro_export] macro_rules! lay { - ([$($expr:expr),* $(,)?]) => { - Layers::new(move|add|{ $(add(&$expr)?;)* Ok(()) }) - }; - (![$($expr:expr),* $(,)?]) => { - Layers::new(|add|{ $(add(&$expr)?;)* Ok(()) }) - }; - ($expr:expr) => { - Layers::new($expr) - }; -} - pub struct Layers< E: Engine, F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> diff --git a/src/space/min_max.rs b/src/space/min_max.rs deleted file mode 100644 index f0b5f26f..00000000 --- a/src/space/min_max.rs +++ /dev/null @@ -1,95 +0,0 @@ -use crate::*; - -impl LayoutMinMax for E {} - -pub trait LayoutMinMax { - fn min_x > (x: E::Unit, w: W) -> Min { - Min::X(x, w) - } - fn min_y > (y: E::Unit, w: W) -> Min { - Min::Y(y, w) - } - fn min_xy > (x: E::Unit, y: E::Unit, w: W) -> Min { - Min::XY(x, y, w) - } - fn max_x > (x: E::Unit, w: W) -> Max { - Max::X(x, w) - } - fn max_y > (y: E::Unit, w: W) -> Max { - Max::Y(y, w) - } - fn max_xy > (x: E::Unit, y: E::Unit, w: W) -> Max { - Max::XY(x, y, w) - } -} - -/// Enforce minimum size of drawing area -pub enum Min> { - /// Enforce minimum width - X(E::Unit, T), - /// Enforce minimum height - Y(E::Unit, T), - /// Enforce minimum width and height - XY(E::Unit, E::Unit, T), -} - -/// Enforce maximum size of drawing area -pub enum Max> { - /// Enforce maximum width - X(E::Unit, T), - /// Enforce maximum height - Y(E::Unit, T), - /// Enforce maximum width and height - XY(E::Unit, E::Unit, T), -} - -impl> Min { - pub fn inner (&self) -> &T { - match self { - Self::X(_, i) => i, - Self::Y(_, i) => i, - Self::XY(_, _, i) => i, - } - } -} - -impl> Render for Min { - fn min_size (&self, to: E::Size) -> Perhaps { - Ok(self.inner().min_size(to)?.map(|to|match *self { - Self::X(w, _) => [to.w().max(w), to.h()], - Self::Y(h, _) => [to.w(), to.h().max(h)], - Self::XY(w, h, _) => [to.w().max(w), to.h().max(h)], - }.into())) - } - // TODO: 🡘 🡙 ←🡙→ - fn render (&self, to: &mut E::Output) -> Usually<()> { - Ok(self.min_size(to.area().wh().into())? - .map(|size|to.render_in(to.area().clip(size).into(), self.inner())) - .transpose()?.unwrap_or(())) - } -} - -impl> Max { - fn inner (&self) -> &T { - match self { - Self::X(_, i) => i, - Self::Y(_, i) => i, - Self::XY(_, _, i) => i, - } - } -} - -impl> Render for Max { - fn min_size (&self, to: E::Size) -> Perhaps { - Ok(self.inner().min_size(to)?.map(|to|match *self { - Self::X(w, _) => [to.w().min(w), to.h()], - Self::Y(h, _) => [to.w(), to.h().min(h)], - Self::XY(w, h, _) => [to.w().min(w), to.h().min(h)], - }.into())) - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - Ok(self.min_size(to.area().wh().into())? - .map(|size|to.render_in(to.area().clip(size).into(), self.inner())) - .transpose()?.unwrap_or(())) - } -} diff --git a/src/space/push_pull.rs b/src/space/position.rs similarity index 59% rename from src/space/push_pull.rs rename to src/space/position.rs index 0cd5ef67..459329ee 100644 --- a/src/space/push_pull.rs +++ b/src/space/position.rs @@ -126,3 +126,94 @@ impl> Render for Pull { } } + +impl + LayoutShrinkGrow> LayoutInsetOutset for E {} + +pub trait LayoutInsetOutset: LayoutPushPull + LayoutShrinkGrow { + fn inset_x > (x: E::Unit, w: W) -> Inset { + Inset::X(x, w) + } + fn inset_y > (y: E::Unit, w: W) -> Inset { + Inset::Y(y, w) + } + fn inset_xy > (x: E::Unit, y: E::Unit, w: W) -> Inset { + Inset::XY(x, y, w) + } + fn outset_x > (x: E::Unit, w: W) -> Outset { + Outset::X(x, w) + } + fn outset_y > (y: E::Unit, w: W) -> Outset { + Outset::Y(y, w) + } + fn outset_xy > (x: E::Unit, y: E::Unit, w: W) -> Outset { + Outset::XY(x, y, w) + } +} + +/// Shrink from each side +pub enum Inset { + /// Decrease width + X(E::Unit, T), + /// Decrease height + Y(E::Unit, T), + /// Decrease width and height + XY(E::Unit, E::Unit, T), +} + +impl> Inset { + pub fn inner (&self) -> &T { + match self { + Self::X(_, i) => i, + Self::Y(_, i) => i, + Self::XY(_, _, i) => i, + } + } +} + +impl> Render for Inset { + fn render (&self, to: &mut E::Output) -> Usually<()> { + match self { + Self::X(x, inner) => E::push_x(*x, E::shrink_x(*x, inner)), + Self::Y(y, inner) => E::push_y(*y, E::shrink_y(*y, inner)), + Self::XY(x, y, inner) => E::push_xy(*x, *y, E::shrink_xy(*x, *y, inner)), + }.render(to) + } +} + +/// Grow on each side +pub enum Outset> { + /// Increase width + X(E::Unit, T), + /// Increase height + Y(E::Unit, T), + /// Increase width and height + XY(E::Unit, E::Unit, T), +} + + +impl> Outset { + pub fn inner (&self) -> &T { + match self { + Self::X(_, i) => i, + Self::Y(_, i) => i, + Self::XY(_, _, i) => i, + } + } +} + +impl> Render for Outset { + fn min_size (&self, to: E::Size) -> Perhaps { + match *self { + Self::X(x, ref inner) => E::grow_x(x + x, inner), + Self::Y(y, ref inner) => E::grow_y(y + y, inner), + Self::XY(x, y, ref inner) => E::grow_xy(x + x, y + y, inner), + }.min_size(to) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + match *self { + Self::X(x, ref inner) => E::push_x(x, inner), + Self::Y(y, ref inner) => E::push_y(y, inner), + Self::XY(x, y, ref inner) => E::push_xy(x, y, inner), + }.render(to) + } +} diff --git a/src/space/shrink_grow.rs b/src/space/shrink_grow.rs deleted file mode 100644 index 9d900843..00000000 --- a/src/space/shrink_grow.rs +++ /dev/null @@ -1,103 +0,0 @@ -use crate::*; - -impl LayoutShrinkGrow for E {} - -pub trait LayoutShrinkGrow { - fn shrink_x > (x: E::Unit, w: W) -> Shrink { - Shrink::X(x, w) - } - fn shrink_y > (y: E::Unit, w: W) -> Shrink { - Shrink::Y(y, w) - } - fn shrink_xy > (x: E::Unit, y: E::Unit, w: W) -> Shrink { - Shrink::XY(x, y, w) - } - fn grow_x > (x: E::Unit, w: W) -> Grow { - Grow::X(x, w) - } - fn grow_y > (y: E::Unit, w: W) -> Grow { - Grow::Y(y, w) - } - fn grow_xy > (x: E::Unit, y: E::Unit, w: W) -> Grow { - Grow::XY(x, y, w) - } -} - -/// Shrink drawing area -pub enum Shrink> { - /// Decrease width - X(E::Unit, T), - /// Decrease height - Y(E::Unit, T), - /// Decrease width and height - XY(E::Unit, E::Unit, T), -} - -/// Expand drawing area -pub enum Grow> { - /// Increase width - X(E::Unit, T), - /// Increase height - Y(E::Unit, T), - /// Increase width and height - XY(E::Unit, E::Unit, T) -} - -impl> Shrink { - fn inner (&self) -> &T { - match self { - Self::X(_, i) => i, - Self::Y(_, i) => i, - Self::XY(_, _, i) => i, - } - } -} - -impl> Grow { - fn inner (&self) -> &T { - match self { - Self::X(_, i) => i, - Self::Y(_, i) => i, - Self::XY(_, _, i) => i, - } - } -} - -impl> Render for Shrink { - fn min_size (&self, to: E::Size) -> Perhaps { - Ok(self.inner().min_size(to)?.map(|to|match *self { - Self::X(w, _) => [ - if to.w() > w { to.w() - w } else { 0.into() }, - to.h() - ], - Self::Y(h, _) => [ - to.w(), - if to.h() > h { to.h() - h } else { 0.into() } - ], - Self::XY(w, h, _) => [ - if to.w() > w { to.w() - w } else { 0.into() }, - if to.h() > h { to.h() - h } else { 0.into() } - ], - }.into())) - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - Ok(self.min_size(to.area().wh().into())? - .map(|size|to.render_in(to.area().clip(size).into(), self.inner())) - .transpose()?.unwrap_or(())) - } -} - -impl> Render for Grow { - fn min_size (&self, to: E::Size) -> Perhaps { - Ok(self.inner().min_size(to)?.map(|to|match *self { - Self::X(w, _) => [to.w() + w, to.h()], - Self::Y(h, _) => [to.w(), to.h() + h], - Self::XY(w, h, _) => [to.w() + w, to.h() + h], - }.into())) - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - Ok(self.min_size(to.area().wh().into())? - .map(|size|to.render_in(to.area().clip(size).into(), self.inner())) - .transpose()?.unwrap_or(())) - } -} diff --git a/src/space/size.rs b/src/space/size.rs new file mode 100644 index 00000000..4200bdf4 --- /dev/null +++ b/src/space/size.rs @@ -0,0 +1,296 @@ +use crate::*; + +impl LayoutShrinkGrow for E {} + +pub trait LayoutShrinkGrow { + fn shrink_x > (x: E::Unit, w: W) -> Shrink { + Shrink::X(x, w) + } + fn shrink_y > (y: E::Unit, w: W) -> Shrink { + Shrink::Y(y, w) + } + fn shrink_xy > (x: E::Unit, y: E::Unit, w: W) -> Shrink { + Shrink::XY(x, y, w) + } + fn grow_x > (x: E::Unit, w: W) -> Grow { + Grow::X(x, w) + } + fn grow_y > (y: E::Unit, w: W) -> Grow { + Grow::Y(y, w) + } + fn grow_xy > (x: E::Unit, y: E::Unit, w: W) -> Grow { + Grow::XY(x, y, w) + } +} + +/// Shrink drawing area +pub enum Shrink> { + /// Decrease width + X(E::Unit, T), + /// Decrease height + Y(E::Unit, T), + /// Decrease width and height + XY(E::Unit, E::Unit, T), +} + +/// Expand drawing area +pub enum Grow> { + /// Increase width + X(E::Unit, T), + /// Increase height + Y(E::Unit, T), + /// Increase width and height + XY(E::Unit, E::Unit, T) +} + +impl> Shrink { + fn inner (&self) -> &T { + match self { + Self::X(_, i) => i, + Self::Y(_, i) => i, + Self::XY(_, _, i) => i, + } + } +} + +impl> Grow { + fn inner (&self) -> &T { + match self { + Self::X(_, i) => i, + Self::Y(_, i) => i, + Self::XY(_, _, i) => i, + } + } +} + +impl> Render for Shrink { + fn min_size (&self, to: E::Size) -> Perhaps { + Ok(self.inner().min_size(to)?.map(|to|match *self { + Self::X(w, _) => [ + if to.w() > w { to.w() - w } else { 0.into() }, + to.h() + ], + Self::Y(h, _) => [ + to.w(), + if to.h() > h { to.h() - h } else { 0.into() } + ], + Self::XY(w, h, _) => [ + if to.w() > w { to.w() - w } else { 0.into() }, + if to.h() > h { to.h() - h } else { 0.into() } + ], + }.into())) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + Ok(self.min_size(to.area().wh().into())? + .map(|size|to.render_in(to.area().clip(size).into(), self.inner())) + .transpose()?.unwrap_or(())) + } +} + +impl> Render for Grow { + fn min_size (&self, to: E::Size) -> Perhaps { + Ok(self.inner().min_size(to)?.map(|to|match *self { + Self::X(w, _) => [to.w() + w, to.h()], + Self::Y(h, _) => [to.w(), to.h() + h], + Self::XY(w, h, _) => [to.w() + w, to.h() + h], + }.into())) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + Ok(self.min_size(to.area().wh().into())? + .map(|size|to.render_in(to.area().clip(size).into(), self.inner())) + .transpose()?.unwrap_or(())) + } +} + +pub enum Fill> { + _Unused(PhantomData), + X(W), + Y(W), + XY(W), +} + +impl> Fill { + fn inner (&self) -> &W { + match self { + Self::X(inner) => &inner, + Self::Y(inner) => &inner, + Self::XY(inner) => &inner, + _ => unreachable!(), + } + } + pub fn w (fill: W) -> Self { + Self::X(fill) + } + pub fn h (fill: W) -> Self { + Self::Y(fill) + } + pub fn wh (fill: W) -> Self { + Self::XY(fill) + } +} + +impl> Render for Fill { + fn min_size (&self, to: E::Size) -> Perhaps { + let area = self.inner().min_size(to.into())?; + if let Some(area) = area { + Ok(Some(match self { + Self::X(_) => [to.w().into(), area.h()], + Self::Y(_) => [area.w(), to.h().into()], + Self::XY(_) => [to.w().into(), to.h().into()], + _ => unreachable!(), + }.into())) + } else { + Ok(None) + } + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + self.inner().render(to) + } +} + +/// Enforce fixed size of drawing area +pub enum Fixed> { + _Unused(PhantomData), + /// Enforce fixed width + X(E::Unit, T), + /// Enforce fixed height + Y(E::Unit, T), + /// Enforce fixed width and height + XY(E::Unit, E::Unit, T), +} + +impl> Fixed { + pub fn inner (&self) -> &T { + match self { + Self::X(_, i) => i, + Self::Y(_, i) => i, + Self::XY(_, _, i) => i, + _ => unreachable!(), + } + } + pub fn w (x: E::Unit, w: T) -> Self { + Self::X(x, w) + } + pub fn h (y: E::Unit, w: T) -> Self { + Self::Y(y, w) + } + pub fn wh (x: E::Unit, y: E::Unit, w: T) -> Self { + Self::XY(x, y, w) + } +} + +impl> Render for Fixed { + fn min_size (&self, to: E::Size) -> Perhaps { + Ok(match self { + Self::X(w, _) => + if to.w() >= *w { Some([*w, to.h()].into()) } else { None }, + Self::Y(h, _) => + if to.h() >= *h { Some([to.w(), *h].into()) } else { None }, + Self::XY(w, h, _) + => if to.w() >= *w && to.h() >= *h { Some([*w, *h].into()) } else { None }, + _ => unreachable!(), + }) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + // 🡘 🡙 ←🡙→ + if let Some(size) = self.min_size(to.area().wh().into())? { + to.render_in(to.area().clip(size).into(), self.inner()) + } else { + Ok(()) + } + } +} + +impl LayoutMinMax for E {} + +pub trait LayoutMinMax { + fn min_x > (x: E::Unit, w: W) -> Min { + Min::X(x, w) + } + fn min_y > (y: E::Unit, w: W) -> Min { + Min::Y(y, w) + } + fn min_xy > (x: E::Unit, y: E::Unit, w: W) -> Min { + Min::XY(x, y, w) + } + fn max_x > (x: E::Unit, w: W) -> Max { + Max::X(x, w) + } + fn max_y > (y: E::Unit, w: W) -> Max { + Max::Y(y, w) + } + fn max_xy > (x: E::Unit, y: E::Unit, w: W) -> Max { + Max::XY(x, y, w) + } +} + +/// Enforce minimum size of drawing area +pub enum Min> { + /// Enforce minimum width + X(E::Unit, T), + /// Enforce minimum height + Y(E::Unit, T), + /// Enforce minimum width and height + XY(E::Unit, E::Unit, T), +} + +/// Enforce maximum size of drawing area +pub enum Max> { + /// Enforce maximum width + X(E::Unit, T), + /// Enforce maximum height + Y(E::Unit, T), + /// Enforce maximum width and height + XY(E::Unit, E::Unit, T), +} + +impl> Min { + pub fn inner (&self) -> &T { + match self { + Self::X(_, i) => i, + Self::Y(_, i) => i, + Self::XY(_, _, i) => i, + } + } +} + +impl> Render for Min { + fn min_size (&self, to: E::Size) -> Perhaps { + Ok(self.inner().min_size(to)?.map(|to|match *self { + Self::X(w, _) => [to.w().max(w), to.h()], + Self::Y(h, _) => [to.w(), to.h().max(h)], + Self::XY(w, h, _) => [to.w().max(w), to.h().max(h)], + }.into())) + } + // TODO: 🡘 🡙 ←🡙→ + fn render (&self, to: &mut E::Output) -> Usually<()> { + Ok(self.min_size(to.area().wh().into())? + .map(|size|to.render_in(to.area().clip(size).into(), self.inner())) + .transpose()?.unwrap_or(())) + } +} + +impl> Max { + fn inner (&self) -> &T { + match self { + Self::X(_, i) => i, + Self::Y(_, i) => i, + Self::XY(_, _, i) => i, + } + } +} + +impl> Render for Max { + fn min_size (&self, to: E::Size) -> Perhaps { + Ok(self.inner().min_size(to)?.map(|to|match *self { + Self::X(w, _) => [to.w().min(w), to.h()], + Self::Y(h, _) => [to.w(), to.h().min(h)], + Self::XY(w, h, _) => [to.w().min(w), to.h().min(h)], + }.into())) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + Ok(self.min_size(to.area().wh().into())? + .map(|size|to.render_in(to.area().clip(size).into(), self.inner())) + .transpose()?.unwrap_or(())) + } +} diff --git a/src/space/split.rs b/src/space/split.rs index 73f9261d..264b49f2 100644 --- a/src/space/split.rs +++ b/src/space/split.rs @@ -138,6 +138,157 @@ impl, Y: Render> Render for Bsp { } } +pub struct Stack< + E: Engine, + F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> +>(pub F, pub Direction, PhantomData); + +impl< + E: Engine, + F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> +> Stack { + #[inline] pub fn new (direction: Direction, build: F) -> Self { + Self(build, direction, Default::default()) + } + #[inline] pub fn right (build: F) -> Self { + Self::new(East, build) + } + #[inline] pub fn down (build: F) -> Self { + Self::new(South, build) + } + #[inline] pub fn up (build: F) -> Self { + Self::new(North, build) + } +} + +impl Render for Stack +where + F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> +{ + fn min_size (&self, to: E::Size) -> Perhaps { + match self.1 { + + South => { + let mut w: E::Unit = 0.into(); + let mut h: E::Unit = 0.into(); + (self.0)(&mut |component: &dyn Render| { + let max = to.h().minus(h); + if max > E::Unit::zero() { + let item = E::max_y(max, E::push_y(h, component)); + let size = item.min_size(to)?.map(|size|size.wh()); + if let Some([width, height]) = size { + h = h + height.into(); + w = w.max(width); + } + } + Ok(()) + })?; + Ok(Some([w, h].into())) + }, + + East => { + let mut w: E::Unit = 0.into(); + let mut h: E::Unit = 0.into(); + (self.0)(&mut |component: &dyn Render| { + let max = to.w().minus(w); + if max > E::Unit::zero() { + let item = E::max_x(max, E::push_x(h, component)); + let size = item.min_size(to)?.map(|size|size.wh()); + if let Some([width, height]) = size { + w = w + width.into(); + h = h.max(height); + } + } + Ok(()) + })?; + Ok(Some([w, h].into())) + }, + + North => { + let mut w: E::Unit = 0.into(); + let mut h: E::Unit = 0.into(); + (self.0)(&mut |component: &dyn Render| { + let max = to.h().minus(h); + if max > E::Unit::zero() { + let item = E::max_y(to.h() - h, component); + let size = item.min_size(to)?.map(|size|size.wh()); + if let Some([width, height]) = size { + h = h + height.into(); + w = w.max(width); + } + } + Ok(()) + })?; + Ok(Some([w, h].into())) + }, + + West => { + let w: E::Unit = 0.into(); + let h: E::Unit = 0.into(); + (self.0)(&mut |component: &dyn Render| { + if w < to.w() { + todo!(); + } + Ok(()) + })?; + Ok(Some([w, h].into())) + }, + } + } + + fn render (&self, to: &mut E::Output) -> Usually<()> { + let area = to.area(); + let mut w = 0.into(); + let mut h = 0.into(); + match self.1 { + South => { + (self.0)(&mut |item| { + if h < area.h() { + let item = E::max_y(area.h() - h, E::push_y(h, item)); + let show = item.min_size(area.wh().into())?.map(|s|s.wh()); + if let Some([width, height]) = show { + item.render(to)?; + h = h + height; + if width > w { w = width } + }; + } + Ok(()) + })?; + }, + East => { + (self.0)(&mut |item| { + if w < area.w() { + let item = E::max_x(area.w() - w, E::push_x(w, item)); + let show = item.min_size(area.wh().into())?.map(|s|s.wh()); + if let Some([width, height]) = show { + item.render(to)?; + w = width + w; + if height > h { h = height } + }; + } + Ok(()) + })?; + }, + North => { + (self.0)(&mut |item| { + if h < area.h() { + let show = item.min_size([area.w(), area.h().minus(h)].into())?.map(|s|s.wh()); + if let Some([width, height]) = show { + E::shrink_y(height, E::push_y(area.h() - height, item)) + .render(to)?; + h = h + height; + if width > w { w = width } + }; + } + Ok(()) + })?; + }, + _ => todo!() + }; + Ok(()) + } +} + #[cfg(test)] #[test] fn test_bsp () { // TODO } diff --git a/src/space/stack.rs b/src/space/stack.rs deleted file mode 100644 index 40a5142c..00000000 --- a/src/space/stack.rs +++ /dev/null @@ -1,153 +0,0 @@ -use crate::*; -use Direction::*; - -pub struct Stack< - E: Engine, - F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> ->(pub F, pub Direction, PhantomData); - -impl< - E: Engine, - F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> -> Stack { - #[inline] pub fn new (direction: Direction, build: F) -> Self { - Self(build, direction, Default::default()) - } - #[inline] pub fn right (build: F) -> Self { - Self::new(East, build) - } - #[inline] pub fn down (build: F) -> Self { - Self::new(South, build) - } - #[inline] pub fn up (build: F) -> Self { - Self::new(North, build) - } -} - -impl Render for Stack -where - F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> -{ - fn min_size (&self, to: E::Size) -> Perhaps { - match self.1 { - - South => { - let mut w: E::Unit = 0.into(); - let mut h: E::Unit = 0.into(); - (self.0)(&mut |component: &dyn Render| { - let max = to.h().minus(h); - if max > E::Unit::zero() { - let item = E::max_y(max, E::push_y(h, component)); - let size = item.min_size(to)?.map(|size|size.wh()); - if let Some([width, height]) = size { - h = h + height.into(); - w = w.max(width); - } - } - Ok(()) - })?; - Ok(Some([w, h].into())) - }, - - East => { - let mut w: E::Unit = 0.into(); - let mut h: E::Unit = 0.into(); - (self.0)(&mut |component: &dyn Render| { - let max = to.w().minus(w); - if max > E::Unit::zero() { - let item = E::max_x(max, E::push_x(h, component)); - let size = item.min_size(to)?.map(|size|size.wh()); - if let Some([width, height]) = size { - w = w + width.into(); - h = h.max(height); - } - } - Ok(()) - })?; - Ok(Some([w, h].into())) - }, - - North => { - let mut w: E::Unit = 0.into(); - let mut h: E::Unit = 0.into(); - (self.0)(&mut |component: &dyn Render| { - let max = to.h().minus(h); - if max > E::Unit::zero() { - let item = E::max_y(to.h() - h, component); - let size = item.min_size(to)?.map(|size|size.wh()); - if let Some([width, height]) = size { - h = h + height.into(); - w = w.max(width); - } - } - Ok(()) - })?; - Ok(Some([w, h].into())) - }, - - West => { - let w: E::Unit = 0.into(); - let h: E::Unit = 0.into(); - (self.0)(&mut |component: &dyn Render| { - if w < to.w() { - todo!(); - } - Ok(()) - })?; - Ok(Some([w, h].into())) - }, - } - } - - fn render (&self, to: &mut E::Output) -> Usually<()> { - let area = to.area(); - let mut w = 0.into(); - let mut h = 0.into(); - match self.1 { - South => { - (self.0)(&mut |item| { - if h < area.h() { - let item = E::max_y(area.h() - h, E::push_y(h, item)); - let show = item.min_size(area.wh().into())?.map(|s|s.wh()); - if let Some([width, height]) = show { - item.render(to)?; - h = h + height; - if width > w { w = width } - }; - } - Ok(()) - })?; - }, - East => { - (self.0)(&mut |item| { - if w < area.w() { - let item = E::max_x(area.w() - w, E::push_x(w, item)); - let show = item.min_size(area.wh().into())?.map(|s|s.wh()); - if let Some([width, height]) = show { - item.render(to)?; - w = width + w; - if height > h { h = height } - }; - } - Ok(()) - })?; - }, - North => { - (self.0)(&mut |item| { - if h < area.h() { - let show = item.min_size([area.w(), area.h().minus(h)].into())?.map(|s|s.wh()); - if let Some([width, height]) = show { - E::shrink_y(height, E::push_y(area.h() - height, item)) - .render(to)?; - h = h + height; - if width > w { w = width } - }; - } - Ok(()) - })?; - }, - _ => todo!() - }; - Ok(()) - } -} From e0e680eb7ca327002c9e590459c1664258ed5247 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 30 Dec 2024 13:48:51 +0100 Subject: [PATCH 079/815] detach all layout constructors from engine --- src/arranger/arranger_v/v_clips.rs | 4 +- src/arranger/arranger_v/v_head.rs | 4 +- src/groovebox.rs | 6 +- src/piano_h.rs | 6 +- src/pool.rs | 8 +- src/sampler/sampler_tui.rs | 2 +- src/sequencer.rs | 4 +- src/space.rs | 412 ++++++++++++++++++++++------- src/space/align.rs | 96 ------- src/space/collect.rs | 1 + src/space/cond.rs | 21 +- src/space/coord.rs | 14 + src/space/layers.rs | 6 +- src/space/measure.rs | 30 +-- src/space/position.rs | 204 ++++++-------- src/space/scroll.rs | 7 - src/space/size.rs | 117 ++------ src/space/split.rs | 63 +++-- src/tui/tui_border.rs | 2 +- 19 files changed, 487 insertions(+), 520 deletions(-) delete mode 100644 src/space/align.rs create mode 100644 src/space/coord.rs diff --git a/src/arranger/arranger_v/v_clips.rs b/src/arranger/arranger_v/v_clips.rs index 6d5691ed..19784fca 100644 --- a/src/arranger/arranger_v/v_clips.rs +++ b/src/arranger/arranger_v/v_clips.rs @@ -31,7 +31,7 @@ impl<'a> ArrangerVClips<'a> { Tui::bg(scene.color.base.rgb, if playing { "▶ " } else { " " }), Tui::fg_bg(scene.color.lightest.rgb, scene.color.base.rgb, - Tui::grow_x(1, Tui::bold(true, scene.name.read().unwrap().as_str()))), + Grow::x(1, Tui::bold(true, scene.name.read().unwrap().as_str()))), row!((index, track, x1, x2) in ArrangerTrack::with_widths(tracks) => { Self::format_clip(scene, index, track, (x2 - x1) as u16, height) })]) @@ -53,7 +53,7 @@ impl<'a> ArrangerVClips<'a> { } }; add(&Tui::bg(bg, - Tui::push_x(1, Fixed::w(w, &name.as_str()[0..max_w]))) + Push::x(1, Fixed::w(w, &name.as_str()[0..max_w]))) )?; } Ok(()) diff --git a/src/arranger/arranger_v/v_head.rs b/src/arranger/arranger_v/v_head.rs index 4ff23a15..f64c84d5 100644 --- a/src/arranger/arranger_v/v_head.rs +++ b/src/arranger/arranger_v/v_head.rs @@ -15,7 +15,7 @@ from!(<'a>|state: &'a ArrangerTui|ArrangerVHead<'a> = Self { // A scenes_w: SCENES_W_OFFSET + ArrangerScene::longest_name(&state.scenes) as u16, }); -render!(|self: ArrangerVHead<'a>|Tui::push_x(self.scenes_w, row!( +render!(|self: ArrangerVHead<'a>|Push::x(self.scenes_w, row!( (_, track, x1, x2) in ArrangerTrack::with_widths(self.tracks) => { let (w, h) = (ArrangerTrack::MIN_WIDTH.max(x2 - x1), HEADER_H); let color = track.color(); @@ -25,7 +25,7 @@ render!(|self: ArrangerVHead<'a>|Tui::push_x(self.scenes_w, row!( Tui::fg(color.lightest.rgb, field) ]) } - Tui::bg(color.base.rgb, Tui::min_xy(w as u16, h, Fixed::wh(w as u16, 5, col!([ + Tui::bg(color.base.rgb, Min::xy(w as u16, h, Fixed::wh(w as u16, 5, col!([ row(color, &Self::format_name(track, w)), row(color, &Self::format_input(track)?), row(color, &Self::format_output(track)?), diff --git a/src/groovebox.rs b/src/groovebox.rs index 875564ec..00457ed4 100644 --- a/src/groovebox.rs +++ b/src/groovebox.rs @@ -120,14 +120,14 @@ render!(|self:Groovebox|{ Fill::wh(lay!([ &self.size, Fill::wh(Align::s(Fixed::h(2, GrooveboxStatus::from(self)))), - Tui::shrink_y(2, col!([ + Shrink::y(2, col!([ Fixed::h(2, row!([ Fixed::wh(5, 2, PlayPause(self.clock().is_rolling())), Fixed::h(2, TransportView::from((self, self.player.play_phrase().as_ref().map(|(_,p)| p.as_ref().map(|p|p.read().unwrap().color) ).flatten().clone(), true))), ])), - Tui::push_x(sampler_w, Fixed::h(1, row!([ + Push::x(sampler_w, Fixed::h(1, row!([ PhraseSelector::play_phrase(&self.player), PhraseSelector::next_phrase(&self.player), ]))), @@ -153,7 +153,7 @@ render!(|self:Groovebox|{ ]), ]), Split::w(false, pool_w, - Tui::pull_y(1, Fill::h(Align::e(PoolView(&self.pool)))), + Pull::y(1, Fill::h(Align::e(PoolView(&self.pool)))), Split::e(false, sampler_w, Fill::wh(col!([ Meters(self.sampler.input_meter.as_ref()), GrooveboxSamples(self), diff --git a/src/piano_h.rs b/src/piano_h.rs index be9cb76e..d7e46bfb 100644 --- a/src/piano_h.rs +++ b/src/piano_h.rs @@ -77,16 +77,16 @@ render!(|self: PianoHorizontal|{ let cursor = move||PianoHorizontalCursor(self); let border = Fill::wh(Outer(Style::default().fg(self.color.dark.rgb).bg(self.color.darkest.rgb))); - let with_border = |x|lay!([border, Tui::inset_xy(0, 0, &x)]); + let with_border = |x|lay!([border, Inset::xy(0, 0, &x)]); with_border(lay!([ - Tui::push_x(0, row!(![ + Push::x(0, row!(![ //" ", field("Edit:", name.to_string()), " ", field("Length:", length.to_string()), " ", field("Loop:", looped.to_string()) ])), - Tui::inset_xy(0, 1, Fill::wh(Bsp::s( + Inset::xy(0, 1, Fill::wh(Bsp::s( Fixed::h(1, Bsp::e( Fixed::w(self.keys_width, ""), Fill::w(timeline()), diff --git a/src/pool.rs b/src/pool.rs index 0ab0e060..a7ad1a42 100644 --- a/src/pool.rs +++ b/src/pool.rs @@ -210,7 +210,7 @@ render!(|self: PoolView<'a>|{ Tui::bg(bg, lay!(move|add|{ add(&Fill::wh(Outer(Style::default().fg(color.base.rgb).bg(bg))))?; //add(&Lozenge(Style::default().bg(border_bg).fg(border_color)))?; - add(&Tui::inset_xy(0, 1, Fill::wh(col!(move|add|match mode { + add(&Inset::xy(0, 1, Fill::wh(col!(move|add|match mode { Some(PoolMode::Import(_, ref file_picker)) => add(file_picker), Some(PoolMode::Export(_, ref file_picker)) => add(file_picker), _ => Ok(for (i, phrase) in phrases.iter().enumerate() { @@ -226,7 +226,7 @@ render!(|self: PoolView<'a>|{ add(&Tui::bg(color.base.rgb, Fill::w(col!([ Fill::w(lay!(|add|{ add(&Fill::w(Align::w(format!(" {i}"))))?; - add(&Fill::w(Align::e(Tui::pull_x(1, length.clone())))) + add(&Fill::w(Align::e(Pull::x(1, length.clone())))) })), Tui::bold(true, { let mut row2 = format!(" {name}"); @@ -245,8 +245,8 @@ render!(|self: PoolView<'a>|{ }))?; }) }))))?; - add(&Fill::w(Align::nw(Tui::push_x(1, Tui::fg(title_color, upper_left.to_string())))))?; - add(&Fill::w(Align::ne(Tui::pull_x(1, Tui::fg(title_color, upper_right.to_string())))))?; + add(&Fill::w(Align::nw(Push::x(1, Tui::fg(title_color, upper_left.to_string())))))?; + add(&Fill::w(Align::ne(Pull::x(1, Tui::fg(title_color, upper_right.to_string())))))?; add(&self.0.size) })) }); diff --git a/src/sampler/sampler_tui.rs b/src/sampler/sampler_tui.rs index 386910c6..4aad6ce4 100644 --- a/src/sampler/sampler_tui.rs +++ b/src/sampler/sampler_tui.rs @@ -54,7 +54,7 @@ render!(|self: SamplerTui|{ let with_size = |x|lay!([self.size, x]); Tui::bg(bg, Fill::wh(with_border(Bsp::s( Tui::fg(self.color.light.rgb, Tui::bold(true, "Sampler")), - with_size(Tui::shrink_y(1, Bsp::e( + with_size(Shrink::y(1, Bsp::e( Fixed::w(keys_width, keys()), Fill::wh(render(|to: &mut TuiOutput|Ok({ let x = to.area.x(); diff --git a/src/sequencer.rs b/src/sequencer.rs index 47ac79f6..02577226 100644 --- a/src/sequencer.rs +++ b/src/sequencer.rs @@ -44,7 +44,7 @@ render!(|self: SequencerTui|{ let w = self.size.w(); let phrase_w = if w > 60 { 20 } else if w > 40 { 15 } else { 10 }; let pool_w = if self.phrases.visible { phrase_w } else { 0 }; - let pool = Tui::pull_y(1, Fill::h(Align::e(PoolView(&self.phrases)))); + let pool = Pull::y(1, Fill::h(Align::e(PoolView(&self.phrases)))); let with_pool = move|x|Split::w(false, pool_w, pool, x); let status = SequencerStatus::from(self); let with_status = |x|Split::n(false, if self.status { 2 } else { 0 }, status, x); @@ -66,7 +66,7 @@ render!(|self: SequencerTui|{ PhraseSelector::next_phrase(&self.player), ])); - Tui::min_y(15, with_size(with_status(col!([ + Min::y(15, with_size(with_status(col!([ toolbar, play_queue, editor, diff --git a/src/space.rs b/src/space.rs index 83c6b7d8..2543ee07 100644 --- a/src/space.rs +++ b/src/space.rs @@ -2,58 +2,58 @@ use crate::*; use std::ops::{Add, Sub, Mul, Div}; use std::fmt::{Display, Debug}; -// TODO: return impl Point and impl Size instead of [N;x] -// to disambiguate between usage of 2-"tuple"s +mod cond; pub use self::cond::*; +mod coord; pub use self::coord::*; +mod layers; pub use self::layers::*; +mod measure; pub use self::measure::*; +mod position; pub use self::position::*; +mod scroll; pub use self::scroll::*; +mod size; pub use self::size::*; +mod split; pub use self::split::*; -////////////////////////////////////////////////////// +/// Has static methods for conditional rendering, +/// in unary and binary forms. +pub struct Cond; -pub(crate) mod align; -pub(crate) mod cond; pub(crate) use cond::*; -pub(crate) mod layers; pub(crate) use layers::*; -pub(crate) mod measure; pub(crate) use measure::*; -pub(crate) mod position; pub(crate) use position::*; -pub(crate) mod scroll; -pub(crate) mod size; pub(crate) use size::*; -pub(crate) mod split; pub(crate) use split::*; - -pub use self::{ - align::*, - cond::*, - measure::*, - position::*, - size::*, - split::*, -}; - -#[derive(Copy, Clone, PartialEq)] -pub enum Direction { North, South, West, East, } - -impl Direction { - pub fn is_north (&self) -> bool { matches!(self, Self::North) } - pub fn is_south (&self) -> bool { matches!(self, Self::South) } - pub fn is_east (&self) -> bool { matches!(self, Self::West) } - pub fn is_west (&self) -> bool { matches!(self, Self::East) } - /// Return next direction clockwise - pub fn cw (&self) -> Self { - match self { - Self::North => Self::East, - Self::South => Self::West, - Self::West => Self::North, - Self::East => Self::South, - } +impl Cond { + /// Render `item` when `cond` is true. + pub fn when > (cond: bool, item: A) -> When { + When(cond, item, Default::default()) } - /// Return next direction counterclockwise - pub fn ccw (&self) -> Self { - match self { - Self::North => Self::West, - Self::South => Self::East, - Self::West => Self::South, - Self::East => Self::North, - } + /// Render `item` if `cond` is true, otherwise render `other`. + pub fn either , B: Render> (cond: bool, item: A, other: B) -> Either { + Either(cond, item, other, Default::default()) } } -/// Standard numeric type. +/// Renders `self.1` when `self.0` is true. +pub struct When>(bool, A, PhantomData); + +/// Renders `self.1` when `self.0` is true, otherwise renders `self.2` +pub struct Either, B: Render>(bool, A, B, PhantomData); + +/// Renders multiple things on top of each other, +/// in the order they are provided by the callback. +/// Total size is largest width x largest height. +pub struct Layers< + E: Engine, + F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> +>(pub F, PhantomData); + +/// Shorthand for defining an instance of [Layers]. +#[macro_export] macro_rules! lay { + ([$($expr:expr),* $(,)?]) => { + Layers::new(move|add|{ $(add(&$expr)?;)* Ok(()) }) + }; + (![$($expr:expr),* $(,)?]) => { + Layers::new(|add|{ $(add(&$expr)?;)* Ok(()) }) + }; + ($expr:expr) => { + Layers::new($expr) + }; +} + +/// A linear coordinate. pub trait Coordinate: Send + Sync + Copy + Add + Sub @@ -77,18 +77,6 @@ pub trait Coordinate: Send + Sync + Copy } } -impl Coordinate for T where T: Send + Sync + Copy - + Add - + Sub - + Mul - + Div - + Ord + PartialEq + Eq - + Debug + Display + Default - + From + Into - + Into - + Into -{} - pub trait Size { fn x (&self) -> N; fn y (&self) -> N; @@ -105,43 +93,52 @@ pub trait Size { } } } -impl Size for (N, N) { - fn x (&self) -> N { self.0 } - fn y (&self) -> N { self.1 } -} - -impl Size for [N;2] { - fn x (&self) -> N { self[0] } - fn y (&self) -> N { self[1] } -} pub trait Area: Copy { fn x (&self) -> N; fn y (&self) -> N; fn w (&self) -> N; fn h (&self) -> N; - fn x2 (&self) -> N { self.x() + self.w() } - fn y2 (&self) -> N { self.y() + self.h() } - #[inline] fn wh (&self) -> [N;2] { [self.w(), self.h()] } - #[inline] fn xywh (&self) -> [N;4] { [self.x(), self.y(), self.w(), self.h()] } - #[inline] fn lrtb (&self) -> [N;4] { [self.x(), self.x2(), self.y(), self.y2()] } - #[inline] fn push_x (&self, x: N) -> [N;4] { [self.x() + x, self.y(), self.w(), self.h()] } - #[inline] fn push_y (&self, y: N) -> [N;4] { [self.x(), self.y() + y, self.w(), self.h()] } + fn x2 (&self) -> N { + self.x() + self.w() + } + fn y2 (&self) -> N { + self.y() + self.h() + } + #[inline] fn wh (&self) -> [N;2] { + [self.w(), self.h()] + } + #[inline] fn xywh (&self) -> [N;4] { + [self.x(), self.y(), self.w(), self.h()] + } + #[inline] fn lrtb (&self) -> [N;4] { + [self.x(), self.x2(), self.y(), self.y2()] + } + #[inline] fn push_x (&self, x: N) -> [N;4] { + [self.x() + x, self.y(), self.w(), self.h()] + } + #[inline] fn push_y (&self, y: N) -> [N;4] { + [self.x(), self.y() + y, self.w(), self.h()] + } #[inline] fn shrink_x (&self, x: N) -> [N;4] { [self.x(), self.y(), self.w().minus(x), self.h()] } #[inline] fn shrink_y (&self, y: N) -> [N;4] { [self.x(), self.y(), self.w(), self.h().minus(y)] } - #[inline] fn set_w (&self, w: N) -> [N;4] { [self.x(), self.y(), w, self.h()] } - #[inline] fn set_h (&self, h: N) -> [N;4] { [self.x(), self.y(), self.w(), h] } - #[inline] fn clip_h (&self, h: N) -> [N;4] { + #[inline] fn set_w (&self, w: N) -> [N;4] { + [self.x(), self.y(), w, self.h()] + } + #[inline] fn set_h (&self, h: N) -> [N;4] { + [self.x(), self.y(), self.w(), h] + } + #[inline] fn clip_h (&self, h: N) -> [N;4] { [self.x(), self.y(), self.w(), self.h().min(h)] } - #[inline] fn clip_w (&self, w: N) -> [N;4] { + #[inline] fn clip_w (&self, w: N) -> [N;4] { [self.x(), self.y(), self.w().min(w), self.h()] } - #[inline] fn clip (&self, wh: impl Size) -> [N;4] { + #[inline] fn clip (&self, wh: impl Size) -> [N;4] { [self.x(), self.y(), wh.w(), wh.h()] } #[inline] fn expect_min (&self, w: N, h: N) -> Usually<&Self> { @@ -173,20 +170,186 @@ pub trait Area: Copy { } } -impl Area for (N, N, N, N) { - #[inline] fn x (&self) -> N { self.0 } - #[inline] fn y (&self) -> N { self.1 } - #[inline] fn w (&self) -> N { self.2 } - #[inline] fn h (&self) -> N { self.3 } +macro_rules! by_axis { + (!$Enum:ident) => { + impl> $Enum { + pub fn x (item: T) -> Self { + Self::X(item) + } + pub fn y (item: T) -> Self { + Self::Y(item) + } + pub fn xy (item: T) -> Self { + Self::XY(item) + } + } + }; + (+$Enum:ident) => { + impl> $Enum { + pub fn x (x: E::Unit, item: T) -> Self { + Self::X(x, item) + } + pub fn y (y: E::Unit, item: T) -> Self { + Self::Y(y, item) + } + pub fn xy (x: E::Unit, y: E::Unit, item: T) -> Self { + Self::XY(x, y, item) + } + } + }; } -impl Area for [N;4] { - #[inline] fn x (&self) -> N { self[0] } - #[inline] fn y (&self) -> N { self[1] } - #[inline] fn w (&self) -> N { self[2] } - #[inline] fn h (&self) -> N { self[3] } +/// Shrink drawing area +pub enum Shrink> { + /// Decrease width + X(E::Unit, T), + /// Decrease height + Y(E::Unit, T), + /// Decrease width and height + XY(E::Unit, E::Unit, T), } +by_axis!(+Shrink); + +/// Expand drawing area +pub enum Grow> { + /// Increase width + X(E::Unit, T), + /// Increase height + Y(E::Unit, T), + /// Increase width and height + XY(E::Unit, E::Unit, T) +} + +by_axis!(+Grow); + +pub enum Fill> { + _Unused(PhantomData), + /// Maximize width + X(W), + /// Maximize height + Y(W), + /// Maximize width and height + XY(W), +} + +by_axis!(!Fill); + +/// Enforce fixed size of drawing area +pub enum Fixed> { + /// Set width + X(E::Unit, T), + /// Set height + Y(E::Unit, T), + /// Set width and height + XY(E::Unit, E::Unit, T), +} + +by_axis!(+Fixed); + +/// Enforce minimum size of drawing area +pub enum Min> { + /// Enforce minimum width + X(E::Unit, T), + /// Enforce minimum height + Y(E::Unit, T), + /// Enforce minimum width and height + XY(E::Unit, E::Unit, T), +} + +by_axis!(+Min); + +/// Enforce maximum size of drawing area +pub enum Max> { + /// Enforce maximum width + X(E::Unit, T), + /// Enforce maximum height + Y(E::Unit, T), + /// Enforce maximum width and height + XY(E::Unit, E::Unit, T), +} + +by_axis!(+Max); + +/// Increment origin point of drawing area +pub enum Push> { + /// Move origin to the right + X(E::Unit, T), + /// Move origin downwards + Y(E::Unit, T), + /// Move origin to the right and downwards + XY(E::Unit, E::Unit, T), +} + +by_axis!(+Push); + +/// Decrement origin point of drawing area +pub enum Pull> { + /// Move origin to the right + X(E::Unit, T), + /// Move origin downwards + Y(E::Unit, T), + /// Move origin to the right and downwards + XY(E::Unit, E::Unit, T), +} + +by_axis!(+Pull); + +/// Shrink from each side +pub enum Inset { + /// Decrease width + X(E::Unit, T), + /// Decrease height + Y(E::Unit, T), + /// Decrease width and height + XY(E::Unit, E::Unit, T), +} + +by_axis!(+Inset); + +/// Grow on each side +pub enum Outset> { + /// Increase width + X(E::Unit, T), + /// Increase height + Y(E::Unit, T), + /// Increase width and height + XY(E::Unit, E::Unit, T), +} + +by_axis!(+Outset); + +/// A cardinal direction. +#[derive(Copy, Clone, PartialEq)] +pub enum Direction { North, South, West, East, } + +/// A binary split with fixed proportion +pub struct Split, B: Render>( + pub bool, pub Direction, pub E::Unit, A, B, PhantomData +); + +pub enum Bsp, Y: Render> { + /// X is north of Y + N(Option, Option), + /// X is south of Y + S(Option, Option), + /// X is east of Y + E(Option, Option), + /// X is west of Y + W(Option, Option), + /// X is above Y + A(Option, Option), + /// X is below Y + B(Option, Option), + /// Should be avoided. + Null(PhantomData), +} + +pub struct Stack< + E: Engine, + F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> +>(pub F, pub Direction, PhantomData); + #[macro_export] macro_rules! col { ([$($expr:expr),* $(,)?]) => { Stack::down(move|add|{ $(add(&$expr)?;)* Ok(()) }) @@ -232,14 +395,65 @@ impl Area for [N;4] { }; } -#[macro_export] macro_rules! lay { - ([$($expr:expr),* $(,)?]) => { - Layers::new(move|add|{ $(add(&$expr)?;)* Ok(()) }) - }; - (![$($expr:expr),* $(,)?]) => { - Layers::new(|add|{ $(add(&$expr)?;)* Ok(()) }) - }; - ($expr:expr) => { - Layers::new($expr) - }; +pub trait HasSize { + fn size (&self) -> &Measure; +} + +#[macro_export] macro_rules! has_size { + (<$E:ty>|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { + impl $(<$($L),*$($T $(: $U)?),*>)? HasSize<$E> for $Struct $(<$($L),*$($T),*>)? { + fn size (&$self) -> &Measure<$E> { $cb } + } + } +} + +pub trait LayoutDebug { + fn debug > (other: W) -> DebugOverlay { + DebugOverlay(Default::default(), other) + } +} + +pub struct DebugOverlay>(PhantomData, pub W); + +/// A widget that tracks its render width and height +#[derive(Default)] +pub struct Measure { + _engine: PhantomData, + pub x: Arc, + pub y: Arc, +} + +pub struct ShowMeasure<'a>(&'a Measure); + +/// A scrollable area. +pub struct Scroll< + E: Engine, + F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> +>(pub F, pub Direction, pub u64, PhantomData); + +/// Override X and Y coordinates, aligning to corner, side, or center of area +pub enum Align> { + _Unused(PhantomData), + /// Draw at center of container + Center(T), + /// Draw at center of X axis + X(T), + /// Draw at center of Y axis + Y(T), + /// Draw at upper left corner of contaier + NW(T), + /// Draw at center of upper edge of container + N(T), + /// Draw at right left corner of contaier + NE(T), + /// Draw at center of left edge of container + W(T), + /// Draw at center of right edge of container + E(T), + /// Draw at lower left corner of container + SW(T), + /// Draw at center of lower edge of container + S(T), + /// Draw at lower right edge of container + SE(T) } diff --git a/src/space/align.rs b/src/space/align.rs deleted file mode 100644 index d1246da1..00000000 --- a/src/space/align.rs +++ /dev/null @@ -1,96 +0,0 @@ -use crate::*; - -/// Override X and Y coordinates, aligning to corner, side, or center of area -pub enum Align> { - _Unused(PhantomData), - /// Draw at center of container - Center(T), - /// Draw at center of X axis - X(T), - /// Draw at center of Y axis - Y(T), - /// Draw at upper left corner of contaier - NW(T), - /// Draw at center of upper edge of container - N(T), - /// Draw at right left corner of contaier - NE(T), - /// Draw at center of left edge of container - W(T), - /// Draw at center of right edge of container - E(T), - /// Draw at lower left corner of container - SW(T), - /// Draw at center of lower edge of container - S(T), - /// Draw at lower right edge of container - SE(T) -} - -impl> Align { - pub fn inner (&self) -> &T { - match self { - Self::Center(inner) => inner, - Self::X(inner) => inner, - Self::Y(inner) => inner, - Self::NW(inner) => inner, - Self::N(inner) => inner, - Self::NE(inner) => inner, - Self::W(inner) => inner, - Self::E(inner) => inner, - Self::SW(inner) => inner, - Self::S(inner) => inner, - Self::SE(inner) => inner, - _ => unreachable!(), - } - } - pub fn c (w: T) -> Self { Self::Center(w) } - pub fn x (w: T) -> Self { Self::X(w) } - pub fn y (w: T) -> Self { Self::Y(w) } - pub fn n (w: T) -> Self { Self::N(w) } - pub fn s (w: T) -> Self { Self::S(w) } - pub fn e (w: T) -> Self { Self::E(w) } - pub fn w (w: T) -> Self { Self::W(w) } - pub fn nw (w: T) -> Self { Self::NW(w) } - pub fn sw (w: T) -> Self { Self::SW(w) } - pub fn ne (w: T) -> Self { Self::NE(w) } - pub fn se (w: T) -> Self { Self::SE(w) } -} - -fn align, N: Coordinate, R: Area + From<[N;4]>> (align: &Align, outer: R, inner: R) -> Option { - if outer.w() < inner.w() || outer.h() < inner.h() { - None - } else { - let [ox, oy, ow, oh] = outer.xywh(); - let [ix, iy, iw, ih] = inner.xywh(); - Some(match align { - Align::Center(_) => [ox + (ow - iw) / 2.into(), oy + (oh - ih) / 2.into(), iw, ih,].into(), - Align::X(_) => [ox + (ow - iw) / 2.into(), iy, iw, ih,].into(), - Align::Y(_) => [ix, oy + (oh - ih) / 2.into(), iw, ih,].into(), - Align::NW(_) => [ox, oy, iw, ih,].into(), - Align::N(_) => [ox + (ow - iw) / 2.into(), oy, iw, ih,].into(), - Align::NE(_) => [ox + ow - iw, oy, iw, ih,].into(), - Align::W(_) => [ox, oy + (oh - ih) / 2.into(), iw, ih,].into(), - Align::E(_) => [ox + ow - iw, oy + (oh - ih) / 2.into(), iw, ih,].into(), - Align::SW(_) => [ox, oy + oh - ih, iw, ih,].into(), - Align::S(_) => [ox + (ow - iw) / 2.into(), oy + oh - ih, iw, ih,].into(), - Align::SE(_) => [ox + ow - iw, oy + oh - ih, iw, ih,].into(), - _ => unreachable!() - }) - } -} - -impl> Render for Align { - fn min_size (&self, outer_area: E::Size) -> Perhaps { - self.inner().min_size(outer_area) - } - fn render (&self, to: &mut E::Output) -> Usually<()> { - let outer_area = to.area(); - Ok(if let Some(inner_size) = self.min_size(outer_area.wh().into())? { - let inner_area = outer_area.clip(inner_size); - if let Some(aligned) = align(&self, outer_area.into(), inner_area.into()) { - to.render_in(aligned, self.inner())? - } - }) - } -} diff --git a/src/space/collect.rs b/src/space/collect.rs index 141992f1..99167436 100644 --- a/src/space/collect.rs +++ b/src/space/collect.rs @@ -1,4 +1,5 @@ use crate::*; +use super::*; pub enum Collect<'a, E: Engine, const N: usize> { Callback(CallbackCollection<'a, E>), diff --git a/src/space/cond.rs b/src/space/cond.rs index 2f8a6006..33dc6e52 100644 --- a/src/space/cond.rs +++ b/src/space/cond.rs @@ -1,24 +1,5 @@ use crate::*; - -/// Conditional rendering, in unary and binary forms. -pub struct Cond; - -impl Cond { - /// Render `item` when `cond` is true. - pub fn when > (cond: bool, item: A) -> When { - When(cond, item, Default::default()) - } - /// Render `item` if `cond` is true, otherwise render `other`. - pub fn either , B: Render> (cond: bool, item: A, other: B) -> Either { - Either(cond, item, other, Default::default()) - } -} - -/// Renders `self.1` when `self.0` is true. -pub struct When>(bool, A, PhantomData); - -/// Renders `self.1` when `self.0` is true, otherwise renders `self.2` -pub struct Either, B: Render>(bool, A, B, PhantomData); +use super::*; impl> Render for When { fn min_size (&self, to: E::Size) -> Perhaps { diff --git a/src/space/coord.rs b/src/space/coord.rs new file mode 100644 index 00000000..59a29742 --- /dev/null +++ b/src/space/coord.rs @@ -0,0 +1,14 @@ +use crate::*; +use super::*; + +impl Coordinate for T where T: Send + Sync + Copy + + Add + + Sub + + Mul + + Div + + Ord + PartialEq + Eq + + Debug + Display + Default + + From + Into + + Into + + Into +{} diff --git a/src/space/layers.rs b/src/space/layers.rs index 9fe07ecd..b0736f24 100644 --- a/src/space/layers.rs +++ b/src/space/layers.rs @@ -1,9 +1,5 @@ use crate::*; - -pub struct Layers< - E: Engine, - F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> ->(pub F, PhantomData); +use super::*; impl< E: Engine, diff --git a/src/space/measure.rs b/src/space/measure.rs index c92264cb..569c6845 100644 --- a/src/space/measure.rs +++ b/src/space/measure.rs @@ -1,35 +1,8 @@ use crate::*; - -pub trait HasSize { - fn size (&self) -> &Measure; -} - -#[macro_export] macro_rules! has_size { - (<$E:ty>|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { - impl $(<$($L),*$($T $(: $U)?),*>)? HasSize<$E> for $Struct $(<$($L),*$($T),*>)? { - fn size (&$self) -> &Measure<$E> { $cb } - } - } -} +use super::*; impl LayoutDebug for E {} -pub trait LayoutDebug { - fn debug > (other: W) -> DebugOverlay { - DebugOverlay(Default::default(), other) - } -} - -pub struct DebugOverlay>(PhantomData, pub W); - -/// A widget that tracks its render width and height -#[derive(Default)] -pub struct Measure { - _engine: PhantomData, - pub x: Arc, - pub y: Arc, -} - impl Clone for Measure { fn clone (&self) -> Self { Self { @@ -83,7 +56,6 @@ impl Measure { } } -pub struct ShowMeasure<'a>(&'a Measure); render!(|self: ShowMeasure<'a>|render(|to|Ok({ let w = self.0.w(); let h = self.0.h(); diff --git a/src/space/position.rs b/src/space/position.rs index 459329ee..8b0804b8 100644 --- a/src/space/position.rs +++ b/src/space/position.rs @@ -1,59 +1,18 @@ use crate::*; - -impl LayoutPushPull for E {} - -pub trait LayoutPushPull { - fn push_x > (x: E::Unit, w: W) -> Push { - Push::X(x, w) - } - fn push_y > (y: E::Unit, w: W) -> Push { - Push::Y(y, w) - } - fn push_xy > (x: E::Unit, y: E::Unit, w: W) -> Push { - Push::XY(x, y, w) - } - fn pull_x > (x: E::Unit, w: W) -> Pull { - Pull::X(x, w) - } - fn pull_y > (y: E::Unit, w: W) -> Pull { - Pull::Y(y, w) - } - fn pull_xy > (x: E::Unit, y: E::Unit, w: W) -> Pull { - Pull::XY(x, y, w) - } -} - -/// Increment origin point of drawing area -pub enum Push> { - /// Move origin to the right - X(E::Unit, T), - /// Move origin downwards - Y(E::Unit, T), - /// Move origin to the right and downwards - XY(E::Unit, E::Unit, T), -} +use super::*; impl> Push { pub fn inner (&self) -> &T { - match self { - Self::X(_, i) => i, - Self::Y(_, i) => i, - Self::XY(_, _, i) => i, - } + use Push::*; + match self { X(_, i) => i, Y(_, i) => i, XY(_, _, i) => i, } } - pub fn x (&self) -> E::Unit { - match self { - Self::X(x, _) => *x, - Self::Y(_, _) => E::Unit::default(), - Self::XY(x, _, _) => *x, - } + pub fn dx (&self) -> E::Unit { + use Push::*; + match self { X(x, _) => *x, Y(_, _) => E::Unit::default(), XY(x, _, _) => *x, } } - pub fn y (&self) -> E::Unit { - match self { - Self::X(_, _) => E::Unit::default(), - Self::Y(y, _) => *y, - Self::XY(_, y, _) => *y, - } + pub fn dy (&self) -> E::Unit { + use Push::*; + match self { X(_, _) => E::Unit::default(), Y(y, _) => *y, XY(_, y, _) => *y, } } } @@ -72,17 +31,6 @@ impl> Render for Push { } } -/// Decrement origin point of drawing area -pub enum Pull> { - _Unused(PhantomData), - /// Move origin to the right - X(E::Unit, T), - /// Move origin downwards - Y(E::Unit, T), - /// Move origin to the right and downwards - XY(E::Unit, E::Unit, T), -} - impl> Pull { pub fn inner (&self) -> &T { match self { @@ -92,7 +40,7 @@ impl> Pull { _ => unreachable!(), } } - pub fn x (&self) -> E::Unit { + pub fn dx (&self) -> E::Unit { match self { Self::X(x, _) => *x, Self::Y(_, _) => E::Unit::default(), @@ -100,7 +48,7 @@ impl> Pull { _ => unreachable!(), } } - pub fn y (&self) -> E::Unit { + pub fn dy (&self) -> E::Unit { match self { Self::X(_, _) => E::Unit::default(), Self::Y(y, _) => *y, @@ -126,40 +74,6 @@ impl> Render for Pull { } } - -impl + LayoutShrinkGrow> LayoutInsetOutset for E {} - -pub trait LayoutInsetOutset: LayoutPushPull + LayoutShrinkGrow { - fn inset_x > (x: E::Unit, w: W) -> Inset { - Inset::X(x, w) - } - fn inset_y > (y: E::Unit, w: W) -> Inset { - Inset::Y(y, w) - } - fn inset_xy > (x: E::Unit, y: E::Unit, w: W) -> Inset { - Inset::XY(x, y, w) - } - fn outset_x > (x: E::Unit, w: W) -> Outset { - Outset::X(x, w) - } - fn outset_y > (y: E::Unit, w: W) -> Outset { - Outset::Y(y, w) - } - fn outset_xy > (x: E::Unit, y: E::Unit, w: W) -> Outset { - Outset::XY(x, y, w) - } -} - -/// Shrink from each side -pub enum Inset { - /// Decrease width - X(E::Unit, T), - /// Decrease height - Y(E::Unit, T), - /// Decrease width and height - XY(E::Unit, E::Unit, T), -} - impl> Inset { pub fn inner (&self) -> &T { match self { @@ -173,23 +87,13 @@ impl> Inset { impl> Render for Inset { fn render (&self, to: &mut E::Output) -> Usually<()> { match self { - Self::X(x, inner) => E::push_x(*x, E::shrink_x(*x, inner)), - Self::Y(y, inner) => E::push_y(*y, E::shrink_y(*y, inner)), - Self::XY(x, y, inner) => E::push_xy(*x, *y, E::shrink_xy(*x, *y, inner)), + Self::X(x, inner) => Push::x(*x, Shrink::x(*x, inner)), + Self::Y(y, inner) => Push::y(*y, Shrink::y(*y, inner)), + Self::XY(x, y, inner) => Push::xy(*x, *y, Shrink::xy(*x, *y, inner)), }.render(to) } } -/// Grow on each side -pub enum Outset> { - /// Increase width - X(E::Unit, T), - /// Increase height - Y(E::Unit, T), - /// Increase width and height - XY(E::Unit, E::Unit, T), -} - impl> Outset { pub fn inner (&self) -> &T { @@ -204,16 +108,84 @@ impl> Outset { impl> Render for Outset { fn min_size (&self, to: E::Size) -> Perhaps { match *self { - Self::X(x, ref inner) => E::grow_x(x + x, inner), - Self::Y(y, ref inner) => E::grow_y(y + y, inner), - Self::XY(x, y, ref inner) => E::grow_xy(x + x, y + y, inner), + Self::X(x, ref inner) => Grow::x(x + x, inner), + Self::Y(y, ref inner) => Grow::y(y + y, inner), + Self::XY(x, y, ref inner) => Grow::xy(x + x, y + y, inner), }.min_size(to) } fn render (&self, to: &mut E::Output) -> Usually<()> { match *self { - Self::X(x, ref inner) => E::push_x(x, inner), - Self::Y(y, ref inner) => E::push_y(y, inner), - Self::XY(x, y, ref inner) => E::push_xy(x, y, inner), + Self::X(x, ref inner) => Push::x(x, inner), + Self::Y(y, ref inner) => Push::y(y, inner), + Self::XY(x, y, ref inner) => Push::xy(x, y, inner), }.render(to) } } + +impl> Align { + pub fn c (w: T) -> Self { Self::Center(w) } + pub fn x (w: T) -> Self { Self::X(w) } + pub fn y (w: T) -> Self { Self::Y(w) } + pub fn n (w: T) -> Self { Self::N(w) } + pub fn s (w: T) -> Self { Self::S(w) } + pub fn e (w: T) -> Self { Self::E(w) } + pub fn w (w: T) -> Self { Self::W(w) } + pub fn nw (w: T) -> Self { Self::NW(w) } + pub fn sw (w: T) -> Self { Self::SW(w) } + pub fn ne (w: T) -> Self { Self::NE(w) } + pub fn se (w: T) -> Self { Self::SE(w) } + pub fn inner (&self) -> &T { + match self { + Self::Center(inner) => inner, + Self::X(inner) => inner, + Self::Y(inner) => inner, + Self::NW(inner) => inner, + Self::N(inner) => inner, + Self::NE(inner) => inner, + Self::W(inner) => inner, + Self::E(inner) => inner, + Self::SW(inner) => inner, + Self::S(inner) => inner, + Self::SE(inner) => inner, + _ => unreachable!(), + } + } +} + +fn align, N: Coordinate, R: Area + From<[N;4]>> (align: &Align, outer: R, inner: R) -> Option { + if outer.w() < inner.w() || outer.h() < inner.h() { + None + } else { + let [ox, oy, ow, oh] = outer.xywh(); + let [ix, iy, iw, ih] = inner.xywh(); + Some(match align { + Align::Center(_) => [ox + (ow - iw) / 2.into(), oy + (oh - ih) / 2.into(), iw, ih,].into(), + Align::X(_) => [ox + (ow - iw) / 2.into(), iy, iw, ih,].into(), + Align::Y(_) => [ix, oy + (oh - ih) / 2.into(), iw, ih,].into(), + Align::NW(_) => [ox, oy, iw, ih,].into(), + Align::N(_) => [ox + (ow - iw) / 2.into(), oy, iw, ih,].into(), + Align::NE(_) => [ox + ow - iw, oy, iw, ih,].into(), + Align::W(_) => [ox, oy + (oh - ih) / 2.into(), iw, ih,].into(), + Align::E(_) => [ox + ow - iw, oy + (oh - ih) / 2.into(), iw, ih,].into(), + Align::SW(_) => [ox, oy + oh - ih, iw, ih,].into(), + Align::S(_) => [ox + (ow - iw) / 2.into(), oy + oh - ih, iw, ih,].into(), + Align::SE(_) => [ox + ow - iw, oy + oh - ih, iw, ih,].into(), + _ => unreachable!() + }) + } +} + +impl> Render for Align { + fn min_size (&self, outer_area: E::Size) -> Perhaps { + self.inner().min_size(outer_area) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + let outer_area = to.area(); + Ok(if let Some(inner_size) = self.min_size(outer_area.wh().into())? { + let inner_area = outer_area.clip(inner_size); + if let Some(aligned) = align(&self, outer_area.into(), inner_area.into()) { + to.render_in(aligned, self.inner())? + } + }) + } +} diff --git a/src/space/scroll.rs b/src/space/scroll.rs index 326f6ab6..c7b7e813 100644 --- a/src/space/scroll.rs +++ b/src/space/scroll.rs @@ -1,8 +1 @@ use crate::*; - -/// A scrollable area. -pub struct Scroll< - E: Engine, - F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> ->(pub F, pub Direction, pub u64, PhantomData); - diff --git a/src/space/size.rs b/src/space/size.rs index 4200bdf4..bcfcbdf8 100644 --- a/src/space/size.rs +++ b/src/space/size.rs @@ -1,46 +1,28 @@ use crate::*; +use super::*; -impl LayoutShrinkGrow for E {} - -pub trait LayoutShrinkGrow { - fn shrink_x > (x: E::Unit, w: W) -> Shrink { - Shrink::X(x, w) - } - fn shrink_y > (y: E::Unit, w: W) -> Shrink { - Shrink::Y(y, w) - } - fn shrink_xy > (x: E::Unit, y: E::Unit, w: W) -> Shrink { - Shrink::XY(x, y, w) - } - fn grow_x > (x: E::Unit, w: W) -> Grow { - Grow::X(x, w) - } - fn grow_y > (y: E::Unit, w: W) -> Grow { - Grow::Y(y, w) - } - fn grow_xy > (x: E::Unit, y: E::Unit, w: W) -> Grow { - Grow::XY(x, y, w) - } +impl Size for (N, N) { + fn x (&self) -> N { self.0 } + fn y (&self) -> N { self.1 } } -/// Shrink drawing area -pub enum Shrink> { - /// Decrease width - X(E::Unit, T), - /// Decrease height - Y(E::Unit, T), - /// Decrease width and height - XY(E::Unit, E::Unit, T), +impl Size for [N;2] { + fn x (&self) -> N { self[0] } + fn y (&self) -> N { self[1] } } -/// Expand drawing area -pub enum Grow> { - /// Increase width - X(E::Unit, T), - /// Increase height - Y(E::Unit, T), - /// Increase width and height - XY(E::Unit, E::Unit, T) +impl Area for (N, N, N, N) { + #[inline] fn x (&self) -> N { self.0 } + #[inline] fn y (&self) -> N { self.1 } + #[inline] fn w (&self) -> N { self.2 } + #[inline] fn h (&self) -> N { self.3 } +} + +impl Area for [N;4] { + #[inline] fn x (&self) -> N { self[0] } + #[inline] fn y (&self) -> N { self[1] } + #[inline] fn w (&self) -> N { self[2] } + #[inline] fn h (&self) -> N { self[3] } } impl> Shrink { @@ -102,13 +84,6 @@ impl> Render for Grow { } } -pub enum Fill> { - _Unused(PhantomData), - X(W), - Y(W), - XY(W), -} - impl> Fill { fn inner (&self) -> &W { match self { @@ -148,17 +123,6 @@ impl> Render for Fill { } } -/// Enforce fixed size of drawing area -pub enum Fixed> { - _Unused(PhantomData), - /// Enforce fixed width - X(E::Unit, T), - /// Enforce fixed height - Y(E::Unit, T), - /// Enforce fixed width and height - XY(E::Unit, E::Unit, T), -} - impl> Fixed { pub fn inner (&self) -> &T { match self { @@ -201,49 +165,6 @@ impl> Render for Fixed { } } -impl LayoutMinMax for E {} - -pub trait LayoutMinMax { - fn min_x > (x: E::Unit, w: W) -> Min { - Min::X(x, w) - } - fn min_y > (y: E::Unit, w: W) -> Min { - Min::Y(y, w) - } - fn min_xy > (x: E::Unit, y: E::Unit, w: W) -> Min { - Min::XY(x, y, w) - } - fn max_x > (x: E::Unit, w: W) -> Max { - Max::X(x, w) - } - fn max_y > (y: E::Unit, w: W) -> Max { - Max::Y(y, w) - } - fn max_xy > (x: E::Unit, y: E::Unit, w: W) -> Max { - Max::XY(x, y, w) - } -} - -/// Enforce minimum size of drawing area -pub enum Min> { - /// Enforce minimum width - X(E::Unit, T), - /// Enforce minimum height - Y(E::Unit, T), - /// Enforce minimum width and height - XY(E::Unit, E::Unit, T), -} - -/// Enforce maximum size of drawing area -pub enum Max> { - /// Enforce maximum width - X(E::Unit, T), - /// Enforce maximum height - Y(E::Unit, T), - /// Enforce maximum width and height - XY(E::Unit, E::Unit, T), -} - impl> Min { pub fn inner (&self) -> &T { match self { diff --git a/src/space/split.rs b/src/space/split.rs index 264b49f2..e5f6217c 100644 --- a/src/space/split.rs +++ b/src/space/split.rs @@ -1,10 +1,31 @@ use crate::*; +use super::*; use Direction::*; -/// A binary split with fixed proportion -pub struct Split, B: Render>( - pub bool, pub Direction, pub E::Unit, A, B, PhantomData -); +impl Direction { + pub fn is_north (&self) -> bool { matches!(self, Self::North) } + pub fn is_south (&self) -> bool { matches!(self, Self::South) } + pub fn is_east (&self) -> bool { matches!(self, Self::West) } + pub fn is_west (&self) -> bool { matches!(self, Self::East) } + /// Return next direction clockwise + pub fn cw (&self) -> Self { + match self { + Self::North => Self::East, + Self::South => Self::West, + Self::West => Self::North, + Self::East => Self::South, + } + } + /// Return next direction counterclockwise + pub fn ccw (&self) -> Self { + match self { + Self::North => Self::West, + Self::South => Self::East, + Self::West => Self::South, + Self::East => Self::North, + } + } +} impl, B: Render> Split { pub fn new (flip: bool, direction: Direction, proportion: E::Unit, a: A, b: B) -> Self { @@ -40,23 +61,6 @@ impl, B: Render> Render for Split { } } -pub enum Bsp, Y: Render> { - /// X is north of Y - N(Option, Option), - /// X is south of Y - S(Option, Option), - /// X is east of Y - E(Option, Option), - /// X is west of Y - W(Option, Option), - /// X is above Y - A(Option, Option), - /// X is below Y - B(Option, Option), - /// Should be avoided. - Null(PhantomData), -} - impl, Y: Render> Bsp { pub fn new (x: X) -> Self { Self::A(Some(x), None) } pub fn n (x: X, y: Y) -> Self { Self::N(Some(x), Some(y)) } @@ -138,11 +142,6 @@ impl, Y: Render> Render for Bsp { } } -pub struct Stack< - E: Engine, - F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> ->(pub F, pub Direction, PhantomData); - impl< E: Engine, F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> @@ -174,7 +173,7 @@ where (self.0)(&mut |component: &dyn Render| { let max = to.h().minus(h); if max > E::Unit::zero() { - let item = E::max_y(max, E::push_y(h, component)); + let item = Max::y(max, Push::y(h, component)); let size = item.min_size(to)?.map(|size|size.wh()); if let Some([width, height]) = size { h = h + height.into(); @@ -192,7 +191,7 @@ where (self.0)(&mut |component: &dyn Render| { let max = to.w().minus(w); if max > E::Unit::zero() { - let item = E::max_x(max, E::push_x(h, component)); + let item = Max::x(max, Push::x(h, component)); let size = item.min_size(to)?.map(|size|size.wh()); if let Some([width, height]) = size { w = w + width.into(); @@ -210,7 +209,7 @@ where (self.0)(&mut |component: &dyn Render| { let max = to.h().minus(h); if max > E::Unit::zero() { - let item = E::max_y(to.h() - h, component); + let item = Max::y(to.h() - h, component); let size = item.min_size(to)?.map(|size|size.wh()); if let Some([width, height]) = size { h = h + height.into(); @@ -244,7 +243,7 @@ where South => { (self.0)(&mut |item| { if h < area.h() { - let item = E::max_y(area.h() - h, E::push_y(h, item)); + let item = Max::y(area.h() - h, Push::y(h, item)); let show = item.min_size(area.wh().into())?.map(|s|s.wh()); if let Some([width, height]) = show { item.render(to)?; @@ -258,7 +257,7 @@ where East => { (self.0)(&mut |item| { if w < area.w() { - let item = E::max_x(area.w() - w, E::push_x(w, item)); + let item = Max::x(area.w() - w, Push::x(w, item)); let show = item.min_size(area.wh().into())?.map(|s|s.wh()); if let Some([width, height]) = show { item.render(to)?; @@ -274,7 +273,7 @@ where if h < area.h() { let show = item.min_size([area.w(), area.h().minus(h)].into())?.map(|s|s.wh()); if let Some([width, height]) = show { - E::shrink_y(height, E::push_y(area.h() - height, item)) + Shrink::y(height, Push::y(area.h() - height, item)) .render(to)?; h = h + height; if width > w { w = width } diff --git a/src/tui/tui_border.rs b/src/tui/tui_border.rs index 8408ed44..ba19074f 100644 --- a/src/tui/tui_border.rs +++ b/src/tui/tui_border.rs @@ -3,7 +3,7 @@ use crate::*; pub struct Bordered>(pub S, pub W); render!(|self: Bordered>|{ - Fill::wh(lay!([Border(self.0), Tui::inset_xy(1, 1, widget(&self.1))])) + Fill::wh(lay!([Border(self.0), Inset::xy(1, 1, widget(&self.1))])) }); pub struct Border(pub S); From 9fa858f226e97f069fd35179e4a2f8ba3be59bd0 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 30 Dec 2024 13:50:49 +0100 Subject: [PATCH 080/815] turn Inset and Outset into Padding and Margin --- examples/demo.rs.fixme | 20 +++++++------- src/piano_h.rs | 4 +-- src/pool.rs | 2 +- src/space.rs | 8 +++--- src/space/position.rs | 8 +++--- src/test.rs | 60 +++++++++++++++++++++--------------------- src/transport.rs | 8 +++--- src/tui/tui_border.rs | 2 +- src/tui/tui_style.rs | 2 +- 9 files changed, 57 insertions(+), 57 deletions(-) diff --git a/examples/demo.rs.fixme b/examples/demo.rs.fixme index f3e9e3f2..d235f005 100644 --- a/examples/demo.rs.fixme +++ b/examples/demo.rs.fixme @@ -40,26 +40,26 @@ impl Content for Demo { add(&Background(Color::Rgb(0,128,128)))?; - add(&Outset::XY(1, 1, Stack::down(|add|{ + add(&Margin::XY(1, 1, Stack::down(|add|{ add(&Layers::new(|add|{ add(&Background(Color::Rgb(128,96,0)))?; add(&Border(Square(border_style)))?; - add(&Outset::XY(2, 1, "..."))?; + add(&Margin::XY(2, 1, "..."))?; Ok(()) }).debug())?; add(&Layers::new(|add|{ add(&Background(Color::Rgb(128,64,0)))?; add(&Border(Lozenge(border_style)))?; - add(&Outset::XY(4, 2, "---"))?; + add(&Margin::XY(4, 2, "---"))?; Ok(()) }).debug())?; add(&Layers::new(|add|{ add(&Background(Color::Rgb(96,64,0)))?; add(&Border(SquareBold(border_style)))?; - add(&Outset::XY(6, 3, "~~~"))?; + add(&Margin::XY(6, 3, "~~~"))?; Ok(()) }).debug())?; @@ -69,15 +69,15 @@ impl Content for Demo { Ok(()) })) - //Align::Center(Outset::X(1, Layers::new(|add|{ + //Align::Center(Margin::X(1, Layers::new(|add|{ //add(&Background(Color::Rgb(128,0,0)))?; //add(&Stack::down(|add|{ - //add(&Outset::Y(1, Layers::new(|add|{ + //add(&Margin::Y(1, Layers::new(|add|{ //add(&Background(Color::Rgb(0,128,0)))?; //add(&Align::Center("12345"))?; //add(&Align::Center("FOO")) //})))?; - //add(&Outset::XY(1, 1, Layers::new(|add|{ + //add(&Margin::XY(1, 1, Layers::new(|add|{ //add(&Align::Center("1234567"))?; //add(&Align::Center("BAR"))?; //add(&Background(Color::Rgb(0,0,128))) @@ -87,13 +87,13 @@ impl Content for Demo { //Align::Y(Layers::new(|add|{ //add(&Background(Color::Rgb(128,0,0)))?; - //add(&Outset::X(1, Align::Center(Stack::down(|add|{ - //add(&Align::X(Outset::Y(1, Layers::new(|add|{ + //add(&Margin::X(1, Align::Center(Stack::down(|add|{ + //add(&Align::X(Margin::Y(1, Layers::new(|add|{ //add(&Background(Color::Rgb(0,128,0)))?; //add(&Align::Center("12345"))?; //add(&Align::Center("FOO")) //})))?; - //add(&Outset::XY(1, 1, Layers::new(|add|{ + //add(&Margin::XY(1, 1, Layers::new(|add|{ //add(&Align::Center("1234567"))?; //add(&Align::Center("BAR"))?; //add(&Background(Color::Rgb(0,0,128))) diff --git a/src/piano_h.rs b/src/piano_h.rs index d7e46bfb..b74114b4 100644 --- a/src/piano_h.rs +++ b/src/piano_h.rs @@ -77,7 +77,7 @@ render!(|self: PianoHorizontal|{ let cursor = move||PianoHorizontalCursor(self); let border = Fill::wh(Outer(Style::default().fg(self.color.dark.rgb).bg(self.color.darkest.rgb))); - let with_border = |x|lay!([border, Inset::xy(0, 0, &x)]); + let with_border = |x|lay!([border, Padding::xy(0, 0, &x)]); with_border(lay!([ Push::x(0, row!(![ @@ -86,7 +86,7 @@ render!(|self: PianoHorizontal|{ field("Length:", length.to_string()), " ", field("Loop:", looped.to_string()) ])), - Inset::xy(0, 1, Fill::wh(Bsp::s( + Padding::xy(0, 1, Fill::wh(Bsp::s( Fixed::h(1, Bsp::e( Fixed::w(self.keys_width, ""), Fill::w(timeline()), diff --git a/src/pool.rs b/src/pool.rs index a7ad1a42..945ac07b 100644 --- a/src/pool.rs +++ b/src/pool.rs @@ -210,7 +210,7 @@ render!(|self: PoolView<'a>|{ Tui::bg(bg, lay!(move|add|{ add(&Fill::wh(Outer(Style::default().fg(color.base.rgb).bg(bg))))?; //add(&Lozenge(Style::default().bg(border_bg).fg(border_color)))?; - add(&Inset::xy(0, 1, Fill::wh(col!(move|add|match mode { + add(&Padding::xy(0, 1, Fill::wh(col!(move|add|match mode { Some(PoolMode::Import(_, ref file_picker)) => add(file_picker), Some(PoolMode::Export(_, ref file_picker)) => add(file_picker), _ => Ok(for (i, phrase) in phrases.iter().enumerate() { diff --git a/src/space.rs b/src/space.rs index 2543ee07..e8019866 100644 --- a/src/space.rs +++ b/src/space.rs @@ -296,7 +296,7 @@ pub enum Pull> { by_axis!(+Pull); /// Shrink from each side -pub enum Inset { +pub enum Padding { /// Decrease width X(E::Unit, T), /// Decrease height @@ -305,10 +305,10 @@ pub enum Inset { XY(E::Unit, E::Unit, T), } -by_axis!(+Inset); +by_axis!(+Padding); /// Grow on each side -pub enum Outset> { +pub enum Margin> { /// Increase width X(E::Unit, T), /// Increase height @@ -317,7 +317,7 @@ pub enum Outset> { XY(E::Unit, E::Unit, T), } -by_axis!(+Outset); +by_axis!(+Margin); /// A cardinal direction. #[derive(Copy, Clone, PartialEq)] diff --git a/src/space/position.rs b/src/space/position.rs index 8b0804b8..2039dfc0 100644 --- a/src/space/position.rs +++ b/src/space/position.rs @@ -74,7 +74,7 @@ impl> Render for Pull { } } -impl> Inset { +impl> Padding { pub fn inner (&self) -> &T { match self { Self::X(_, i) => i, @@ -84,7 +84,7 @@ impl> Inset { } } -impl> Render for Inset { +impl> Render for Padding { fn render (&self, to: &mut E::Output) -> Usually<()> { match self { Self::X(x, inner) => Push::x(*x, Shrink::x(*x, inner)), @@ -95,7 +95,7 @@ impl> Render for Inset { } -impl> Outset { +impl> Margin { pub fn inner (&self) -> &T { match self { Self::X(_, i) => i, @@ -105,7 +105,7 @@ impl> Outset { } } -impl> Render for Outset { +impl> Render for Margin { fn min_size (&self, to: E::Size) -> Perhaps { match *self { Self::X(x, ref inner) => Grow::x(x + x, inner), diff --git a/src/test.rs b/src/test.rs index fcb75eeb..5f7dcb0e 100644 --- a/src/test.rs +++ b/src/test.rs @@ -50,10 +50,10 @@ //let engine = TestEngine(area, vec![vec![' ';10];10]); //let test = TestArea(4, 4); //assert_eq!(test.layout(area)?, Some([0, 0, 4, 4])); - //assert_eq!(Outset::X(1, test).layout(area)?, Some([0, 0, 6, 4])); + //assert_eq!(Margin::X(1, test).layout(area)?, Some([0, 0, 6, 4])); //assert_eq!(Align::X(test).layout(area)?, Some([3, 0, 4, 4])); - //assert_eq!(Align::X(Outset::X(1, test)).layout(area)?, Some([2, 0, 6, 4])); - //assert_eq!(Outset::X(1, Align::X(test)).layout(area)?, Some([2, 0, 6, 4])); + //assert_eq!(Align::X(Margin::X(1, test)).layout(area)?, Some([2, 0, 6, 4])); + //assert_eq!(Margin::X(1, Align::X(test)).layout(area)?, Some([2, 0, 6, 4])); //Ok(()) //} @@ -71,24 +71,24 @@ ////})).layout(area)?, ////Some([3, 1, 4, 8])); ////assert_eq!(Align::Center(Stack::down(|add|{ - ////add(&Outset::XY(2, 2, test))?; + ////add(&Margin::XY(2, 2, test))?; ////add(&test) ////})).layout(area)?, ////Some([2, 0, 6, 10])); ////assert_eq!(Align::Center(Stack::down(|add|{ - ////add(&Outset::XY(2, 2, test))?; - ////add(&Inset::XY(2, 2, test)) + ////add(&Margin::XY(2, 2, test))?; + ////add(&Padding::XY(2, 2, test)) ////})).layout(area)?, ////Some([2, 1, 6, 8])); ////assert_eq!(Stack::down(|add|{ - ////add(&Outset::XY(2, 2, test))?; - ////add(&Inset::XY(2, 2, test)) + ////add(&Margin::XY(2, 2, test))?; + ////add(&Padding::XY(2, 2, test)) ////}).layout(area)?, ////Some([0, 0, 6, 8])); ////assert_eq!(Stack::right(|add|{ ////add(&Stack::down(|add|{ - ////add(&Outset::XY(2, 2, test))?; - ////add(&Inset::XY(2, 2, test)) + ////add(&Margin::XY(2, 2, test))?; + ////add(&Padding::XY(2, 2, test)) ////}))?; ////add(&Align::Center(TestArea(2 ,2))) ////}).layout(area)?, @@ -110,19 +110,19 @@ ////fn test_outset () -> Usually<()> { ////let area: [u16;4] = [50, 50, 100, 100]; ////let test = TestArea(3, 3); - ////assert_eq!(Outset::X(1, test).layout(area)?, Some([49, 50, 5, 3])); - ////assert_eq!(Outset::Y(1, test).layout(area)?, Some([50, 49, 3, 5])); - ////assert_eq!(Outset::XY(1, 1, test).layout(area)?, Some([49, 49, 5, 5])); + ////assert_eq!(Margin::X(1, test).layout(area)?, Some([49, 50, 5, 3])); + ////assert_eq!(Margin::Y(1, test).layout(area)?, Some([50, 49, 3, 5])); + ////assert_eq!(Margin::XY(1, 1, test).layout(area)?, Some([49, 49, 5, 5])); ////Ok(()) ////} ////#[test] -////fn test_inset () -> Usually<()> { +////fn test_padding () -> Usually<()> { ////let area: [u16;4] = [50, 50, 100, 100]; ////let test = TestArea(3, 3); - ////assert_eq!(Inset::X(1, test).layout(area)?, Some([51, 50, 1, 3])); - ////assert_eq!(Inset::Y(1, test).layout(area)?, Some([50, 51, 3, 1])); - ////assert_eq!(Inset::XY(1, 1, test).layout(area)?, Some([51, 51, 1, 1])); + ////assert_eq!(Padding::X(1, test).layout(area)?, Some([51, 50, 1, 3])); + ////assert_eq!(Padding::Y(1, test).layout(area)?, Some([50, 51, 3, 1])); + ////assert_eq!(Padding::XY(1, 1, test).layout(area)?, Some([51, 51, 1, 1])); ////Ok(()) ////} @@ -145,35 +145,35 @@ ////}).layout(area)?, ////Some([0, 0, 5, 2])); ////let area: [u16;4] = [1, 1, 100, 100]; - ////assert_eq!(Outset::X(1, Stack::right(|add|{add(&"1")?;add(&"333")})).layout(area)?, + ////assert_eq!(Margin::X(1, Stack::right(|add|{add(&"1")?;add(&"333")})).layout(area)?, ////Some([0, 1, 6, 1])); - ////assert_eq!(Outset::Y(1, Stack::right(|add|{add(&"1")?;add(&"333")})).layout(area)?, + ////assert_eq!(Margin::Y(1, Stack::right(|add|{add(&"1")?;add(&"333")})).layout(area)?, ////Some([1, 0, 4, 3])); - ////assert_eq!(Outset::XY(1, 1, Stack::right(|add|{add(&"1")?;add(&"333")})).layout(area)?, + ////assert_eq!(Margin::XY(1, 1, Stack::right(|add|{add(&"1")?;add(&"333")})).layout(area)?, ////Some([0, 0, 6, 3])); ////assert_eq!(Stack::down(|add|{ - ////add(&Outset::XY(1, 1, "1"))?; - ////add(&Outset::XY(1, 1, "333")) + ////add(&Margin::XY(1, 1, "1"))?; + ////add(&Margin::XY(1, 1, "333")) ////}).layout(area)?, ////Some([1, 1, 5, 6])); ////let area: [u16;4] = [1, 1, 95, 100]; ////assert_eq!(Align::Center(Stack::down(|add|{ - ////add(&Outset::XY(1, 1, "1"))?; - ////add(&Outset::XY(1, 1, "333")) + ////add(&Margin::XY(1, 1, "1"))?; + ////add(&Margin::XY(1, 1, "333")) ////})).layout(area)?, ////Some([46, 48, 5, 6])); ////assert_eq!(Align::Center(Stack::down(|add|{ ////add(&Layers::new(|add|{ - //////add(&Outset::XY(1, 1, Background(Color::Rgb(0,128,0))))?; - ////add(&Outset::XY(1, 1, "1"))?; - ////add(&Outset::XY(1, 1, "333"))?; + //////add(&Margin::XY(1, 1, Background(Color::Rgb(0,128,0))))?; + ////add(&Margin::XY(1, 1, "1"))?; + ////add(&Margin::XY(1, 1, "333"))?; //////add(&Background(Color::Rgb(0,128,0)))?; ////Ok(()) ////}))?; ////add(&Layers::new(|add|{ - //////add(&Outset::XY(1, 1, Background(Color::Rgb(0,0,128))))?; - ////add(&Outset::XY(1, 1, "555"))?; - ////add(&Outset::XY(1, 1, "777777"))?; + //////add(&Margin::XY(1, 1, Background(Color::Rgb(0,0,128))))?; + ////add(&Margin::XY(1, 1, "555"))?; + ////add(&Margin::XY(1, 1, "777777"))?; //////add(&Background(Color::Rgb(0,0,128)))?; ////Ok(()) ////})) diff --git a/src/transport.rs b/src/transport.rs index 5a1cb9e2..746e8cd7 100644 --- a/src/transport.rs +++ b/src/transport.rs @@ -356,19 +356,19 @@ fn to_seek_command (input: &TuiInput) -> Option { //Field("MSU ", format!("00m00s00u")) //), //), - //selected.wrap(TransportFocus::Bpm, &Outset::X(1u16, { + //selected.wrap(TransportFocus::Bpm, &Margin::X(1u16, { //row! { //"BPM ", //format!("{}.{:03}", *bpm as usize, (bpm * 1000.0) % 1000.0) //} //})), - //selected.wrap(TransportFocus::Sync, &Outset::X(1u16, row! { + //selected.wrap(TransportFocus::Sync, &Margin::X(1u16, row! { //"SYNC ", pulses_to_name(*sync as usize) //})), - //selected.wrap(TransportFocus::Quant, &Outset::X(1u16, row! { + //selected.wrap(TransportFocus::Quant, &Margin::X(1u16, row! { //"QUANT ", pulses_to_name(*quant as usize) //})), //selected.wrap(TransportFocus::Clock, &{ - //row!("B" , beat.as_str(), " T", msu.as_str()).outset_x(1) + //row!("B" , beat.as_str(), " T", msu.as_str()).margin_x(1) //}).align_e().fill_x(), //).fill_x().bg(Color::Rgb(40, 50, 30)) diff --git a/src/tui/tui_border.rs b/src/tui/tui_border.rs index ba19074f..039cfb80 100644 --- a/src/tui/tui_border.rs +++ b/src/tui/tui_border.rs @@ -3,7 +3,7 @@ use crate::*; pub struct Bordered>(pub S, pub W); render!(|self: Bordered>|{ - Fill::wh(lay!([Border(self.0), Inset::xy(1, 1, widget(&self.1))])) + Fill::wh(lay!([Border(self.0), Padding::xy(1, 1, widget(&self.1))])) }); pub struct Border(pub S); diff --git a/src/tui/tui_style.rs b/src/tui/tui_style.rs index 4a01d0e1..fb6b4fd9 100644 --- a/src/tui/tui_style.rs +++ b/src/tui/tui_style.rs @@ -91,6 +91,6 @@ impl Render for Styled<&str> { //impl> Content for Bordered { //fn content (&self) -> impl Render { //let content: &dyn Render = &self.1; - //lay! { content.inset_xy(1, 1), Border(self.0) }.fill_xy() + //lay! { content.padding_xy(1, 1), Border(self.0) }.fill_xy() //} //} From 304ce35cbbba7c1a4c83aa2f74e345f3524874f0 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 30 Dec 2024 14:31:00 +0100 Subject: [PATCH 081/815] more updates to space and transport --- src/arranger.rs | 4 +- src/groovebox.rs | 11 ++-- src/sequencer.rs | 4 +- src/space.rs | 126 +++++++++++++++++++++++++++++++-------------- src/space/split.rs | 46 ----------------- src/transport.rs | 88 ++++++++++++++++--------------- 6 files changed, 139 insertions(+), 140 deletions(-) diff --git a/src/arranger.rs b/src/arranger.rs index 502d6293..efb6d39f 100644 --- a/src/arranger.rs +++ b/src/arranger.rs @@ -100,8 +100,8 @@ impl ArrangerTui { } } render!(|self: ArrangerTui|{ - let play = Fixed::wh(5, 2, PlayPause(self.clock.is_rolling())); - let transport = TransportView::from((self, Some(ItemPalette::from(TuiTheme::g(96))), true)); + let play = PlayPause(self.clock.is_rolling()); + let transport = TransportView::new(self, Some(ItemPalette::from(TuiTheme::g(96))), true); let with_transport = |x|col!([row!(![&play, &transport]), &x]); let pool_size = if self.phrases.visible { self.splits[1] } else { 0 }; let with_pool = |x|Split::w(false, pool_size, PoolView(&self.phrases), x); diff --git a/src/groovebox.rs b/src/groovebox.rs index 00457ed4..a1f24c71 100644 --- a/src/groovebox.rs +++ b/src/groovebox.rs @@ -117,15 +117,16 @@ render!(|self:Groovebox|{ let pool_w = if self.pool.visible { phrase_w } else { 0 }; let sampler_w = 11; let note_pt = self.editor.note_point(); + let color = self.player.play_phrase().as_ref() + .and_then(|(_,p)|p.as_ref().map(|p|p.read().unwrap().color)) + .clone(); Fill::wh(lay!([ &self.size, Fill::wh(Align::s(Fixed::h(2, GrooveboxStatus::from(self)))), Shrink::y(2, col!([ - Fixed::h(2, row!([ - Fixed::wh(5, 2, PlayPause(self.clock().is_rolling())), - Fixed::h(2, TransportView::from((self, self.player.play_phrase().as_ref().map(|(_,p)| - p.as_ref().map(|p|p.read().unwrap().color) - ).flatten().clone(), true))), + Fixed::h(3, row!([ + PlayPause(self.clock().is_rolling()), + TransportView::new(self, color, true), ])), Push::x(sampler_w, Fixed::h(1, row!([ PhraseSelector::play_phrase(&self.player), diff --git a/src/sequencer.rs b/src/sequencer.rs index 02577226..b9dc4287 100644 --- a/src/sequencer.rs +++ b/src/sequencer.rs @@ -57,8 +57,8 @@ render!(|self: SequencerTui|{ ).flatten().clone(); let toolbar = Cond::when(self.transport, row!([ - Fixed::wh(5, 2, PlayPause(self.clock.is_rolling())), - Fixed::h(2, TransportView::from((self, color, true))), + PlayPause(self.clock.is_rolling()), + TransportView::new(self, color, true), ])); let play_queue = Cond::when(self.selectors, row!([ diff --git a/src/space.rs b/src/space.rs index e8019866..72bd9d3c 100644 --- a/src/space.rs +++ b/src/space.rs @@ -11,6 +11,11 @@ mod scroll; pub use self::scroll::*; mod size; pub use self::size::*; mod split; pub use self::split::*; +/// A cardinal direction. +#[derive(Copy, Clone, PartialEq)] +pub enum Direction { North, South, West, East, } +use self::Direction::*; + /// Has static methods for conditional rendering, /// in unary and binary forms. pub struct Cond; @@ -94,6 +99,36 @@ pub trait Size { } } +pub trait HasSize { + fn size (&self) -> &Measure; +} + +#[macro_export] macro_rules! has_size { + (<$E:ty>|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { + impl $(<$($L),*$($T $(: $U)?),*>)? HasSize<$E> for $Struct $(<$($L),*$($T),*>)? { + fn size (&$self) -> &Measure<$E> { $cb } + } + } +} + +/// A widget that tracks its render width and height +#[derive(Default)] +pub struct Measure { + _engine: PhantomData, + pub x: Arc, + pub y: Arc, +} + +pub struct ShowMeasure<'a>(&'a Measure); + +pub trait LayoutDebug { + fn debug > (other: W) -> DebugOverlay { + DebugOverlay(Default::default(), other) + } +} + +pub struct DebugOverlay>(PhantomData, pub W); + pub trait Area: Copy { fn x (&self) -> N; fn y (&self) -> N; @@ -319,14 +354,27 @@ pub enum Margin> { by_axis!(+Margin); -/// A cardinal direction. -#[derive(Copy, Clone, PartialEq)] -pub enum Direction { North, South, West, East, } - /// A binary split with fixed proportion -pub struct Split, B: Render>( - pub bool, pub Direction, pub E::Unit, A, B, PhantomData -); +pub struct Split(pub bool, pub Direction, pub E::Unit, A, B, PhantomData) +where E: Engine, A: Render, B: Render; + +impl, B: Render> Split { + #[inline] pub fn new (flip: bool, direction: Direction, proportion: E::Unit, a: A, b: B) -> Self { + Self(flip, direction, proportion, a, b, Default::default()) + } + #[inline] pub fn n (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { + Self::new(flip, North, proportion, a, b) + } + #[inline] pub fn s (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { + Self::new(flip, South, proportion, a, b) + } + #[inline] pub fn e (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { + Self::new(flip, West, proportion, a, b) + } + #[inline] pub fn w (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { + Self::new(flip, East, proportion, a, b) + } +} pub enum Bsp, Y: Render> { /// X is north of Y @@ -345,11 +393,39 @@ pub enum Bsp, Y: Render> { Null(PhantomData), } +impl, Y: Render> Bsp { + pub fn new (x: X) -> Self { Self::A(Some(x), None) } + pub fn n (x: X, y: Y) -> Self { Self::N(Some(x), Some(y)) } + pub fn s (x: X, y: Y) -> Self { Self::S(Some(x), Some(y)) } + pub fn e (x: X, y: Y) -> Self { Self::E(Some(x), Some(y)) } + pub fn w (x: X, y: Y) -> Self { Self::W(Some(x), Some(y)) } + pub fn a (x: X, y: Y) -> Self { Self::A(Some(x), Some(y)) } + pub fn b (x: X, y: Y) -> Self { Self::B(Some(x), Some(y)) } +} + pub struct Stack< E: Engine, F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> >(pub F, pub Direction, PhantomData); +impl< + E: Engine, + F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> +> Stack { + #[inline] pub fn new (direction: Direction, build: F) -> Self { + Self(build, direction, Default::default()) + } + #[inline] pub fn right (build: F) -> Self { + Self::new(East, build) + } + #[inline] pub fn down (build: F) -> Self { + Self::new(South, build) + } + #[inline] pub fn up (build: F) -> Self { + Self::new(North, build) + } +} + #[macro_export] macro_rules! col { ([$($expr:expr),* $(,)?]) => { Stack::down(move|add|{ $(add(&$expr)?;)* Ok(()) }) @@ -395,41 +471,11 @@ pub struct Stack< }; } -pub trait HasSize { - fn size (&self) -> &Measure; -} - -#[macro_export] macro_rules! has_size { - (<$E:ty>|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { - impl $(<$($L),*$($T $(: $U)?),*>)? HasSize<$E> for $Struct $(<$($L),*$($T),*>)? { - fn size (&$self) -> &Measure<$E> { $cb } - } - } -} - -pub trait LayoutDebug { - fn debug > (other: W) -> DebugOverlay { - DebugOverlay(Default::default(), other) - } -} - -pub struct DebugOverlay>(PhantomData, pub W); - -/// A widget that tracks its render width and height -#[derive(Default)] -pub struct Measure { - _engine: PhantomData, - pub x: Arc, - pub y: Arc, -} - -pub struct ShowMeasure<'a>(&'a Measure); - /// A scrollable area. -pub struct Scroll< +pub struct Scroll(pub F, pub Direction, pub u64, PhantomData) +where E: Engine, - F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> ->(pub F, pub Direction, pub u64, PhantomData); + F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()>; /// Override X and Y coordinates, aligning to corner, side, or center of area pub enum Align> { diff --git a/src/space/split.rs b/src/space/split.rs index e5f6217c..6bc8ef2d 100644 --- a/src/space/split.rs +++ b/src/space/split.rs @@ -27,24 +27,6 @@ impl Direction { } } -impl, B: Render> Split { - pub fn new (flip: bool, direction: Direction, proportion: E::Unit, a: A, b: B) -> Self { - Self(flip, direction, proportion, a, b, Default::default()) - } - pub fn n (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { - Self(flip, North, proportion, a, b, Default::default()) - } - pub fn s (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { - Self(flip, South, proportion, a, b, Default::default()) - } - pub fn e (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { - Self(flip, West, proportion, a, b, Default::default()) - } - pub fn w (flip: bool, proportion: E::Unit, a: A, b: B) -> Self { - Self(flip, East, proportion, a, b, Default::default()) - } -} - impl, B: Render> Render for Split { fn min_size (&self, to: E::Size) -> Perhaps { Ok(Some(to)) @@ -61,16 +43,6 @@ impl, B: Render> Render for Split { } } -impl, Y: Render> Bsp { - pub fn new (x: X) -> Self { Self::A(Some(x), None) } - pub fn n (x: X, y: Y) -> Self { Self::N(Some(x), Some(y)) } - pub fn s (x: X, y: Y) -> Self { Self::S(Some(x), Some(y)) } - pub fn e (x: X, y: Y) -> Self { Self::E(Some(x), Some(y)) } - pub fn w (x: X, y: Y) -> Self { Self::W(Some(x), Some(y)) } - pub fn a (x: X, y: Y) -> Self { Self::A(Some(x), Some(y)) } - pub fn b (x: X, y: Y) -> Self { Self::B(Some(x), Some(y)) } -} - impl, Y: Render> Default for Bsp { fn default () -> Self { Self::Null(Default::default()) @@ -142,24 +114,6 @@ impl, Y: Render> Render for Bsp { } } -impl< - E: Engine, - F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> -> Stack { - #[inline] pub fn new (direction: Direction, build: F) -> Self { - Self(build, direction, Default::default()) - } - #[inline] pub fn right (build: F) -> Self { - Self::new(East, build) - } - #[inline] pub fn down (build: F) -> Self { - Self::new(South, build) - } - #[inline] pub fn up (build: F) -> Self { - Self::new(North, build) - } -} - impl Render for Stack where F: Send + Sync + Fn(&mut dyn FnMut(&dyn Render)->Usually<()>)->Usually<()> diff --git a/src/transport.rs b/src/transport.rs index 746e8cd7..f9d90e93 100644 --- a/src/transport.rs +++ b/src/transport.rs @@ -18,10 +18,13 @@ from_jack!(|jack|TransportTui Self { cursor: (0, 0), focus: TransportFocus::PlayPause }); -has_clock!(|self:TransportTui|&self.clock); -audio!(|self:TransportTui,client,scope|ClockAudio(self).process(client, scope)); -handle!(|self:TransportTui,from|TransportCommand::execute_with_state(self, from)); -render!(|self: TransportTui|TransportView::from((self, None, true))); +has_clock!(|self: TransportTui|&self.clock); +audio!(|self: TransportTui, client, scope|ClockAudio(self).process(client, scope)); +handle!(|self: TransportTui, from|TransportCommand::execute_with_state(self, from)); +render!(|self: TransportTui|row!([ + Fixed::wh(5, 3, PlayPause(false)), + Fixed::h(3, TransportView::new(self, None, true)) +])); impl std::fmt::Debug for TransportTui { fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { f.debug_struct("TransportTui") @@ -46,8 +49,8 @@ pub struct TransportView { current_sample: f64, current_second: f64, } -impl From<(&T, Option, bool)> for TransportView { - fn from ((state, color, focused): (&T, Option, bool)) -> Self { +impl TransportView { + pub fn new (state: &impl HasClock, color: Option, focused: bool) -> Self { let clock = state.clock(); let rate = clock.timebase.sr.get(); let chunk = clock.chunk.load(Relaxed); @@ -58,61 +61,56 @@ impl From<(&T, Option, bool)> for TransportView { let chunk = format!("{chunk}"); let latency = format!("{latency}"); let color = color.unwrap_or(ItemPalette::from(TuiTheme::g(32))); - if let Some(started) = clock.started.read().unwrap().as_ref() { + let ( + global_sample, global_second, current_sample, current_second, beat + ) = if let Some(started) = clock.started.read().unwrap().as_ref() { let current_sample = (clock.global.sample.get() - started.sample.get())/1000.; let current_usec = clock.global.usec.get() - started.usec.get(); let current_second = current_usec/1000000.; - Self { - color, focused, sr, bpm, ppq, chunk, latency, - started: true, - global_sample: format!("{:.0}k", started.sample.get()/1000.), - global_second: format!("{:.1}s", started.usec.get()/1000.), - current_sample, - current_second, - beat: clock.timebase.format_beats_0( - clock.timebase.usecs_to_pulse(current_usec) - ), - } + let global_sample = format!("{:.0}k", started.sample.get()/1000.); + let global_second = format!("{:.1}s", started.usec.get()/1000.); + let beat = clock.timebase.format_beats_1(clock.timebase.usecs_to_pulse(current_usec)); + (global_sample, global_second, current_sample, current_second, beat) } else { - Self { - color, focused, sr, bpm, ppq, chunk, latency, - started: false, - global_sample: format!("{:.0}k", clock.global.sample.get()/1000.), - global_second: format!("{:.1}s", clock.global.usec.get()/1000000.), - current_sample: 0.0, - current_second: 0.0, - beat: format!("000.0.00") - } + let global_sample = format!("{:.0}k", clock.global.sample.get()/1000.); + let global_second = format!("{:.1}s", clock.global.usec.get()/1000000.); + let current_sample = 0.0; + let current_second = 0.0; + let beat = format!("000.0.00"); + (global_sample, global_second, current_sample, current_second, beat) + }; + Self { + color, focused, sr, bpm, ppq, chunk, latency, started: false, + global_sample, global_second, current_sample, current_second, beat } } } render!(|self: TransportView|{ let color = self.color; - struct Field<'a>(&'a str, &'a str, &'a ItemPalette); - render!(|self: Field<'a>|row!([ - Tui::fg_bg(self.2.lightest.rgb, self.2.base.rgb, Tui::bold(true, self.0)), - Tui::fg_bg(self.2.base.rgb, self.2.darkest.rgb, "▌"), - Tui::fg_bg(self.2.lightest.rgb, self.2.darkest.rgb, format!("{:>10}", self.1)), - Tui::fg_bg(self.2.darkest.rgb, self.2.base.rgb, "▌"), - ])); - Tui::bg(color.base.rgb, Fill::w(row!([ + Fixed::h(3, Tui::bg(color.base.rgb, Fill::w(row!([ //PlayPause(self.started), " ", col!([ - Field(" Beat", self.beat.as_str(), &color), - Field(" Time", format!("{:.1}s", self.current_second).as_str(), &color), + TransportField(" Beat", self.beat.as_str(), &color), + TransportField(" Time", format!("{:.1}s", self.current_second).as_str(), &color), + TransportField(" BPM", self.bpm.as_str(), &color), + ]), + col!([ + TransportField(" Rate", format!("{}", self.sr).as_str(), &color), + TransportField(" Chunk", format!("{}", self.chunk).as_str(), &color), + TransportField(" Lag", format!("{:.3}ms", self.latency).as_str(), &color), ]), col!([ - Field(" BPM", self.bpm.as_str(), &color), - //Field(" Smpl", format!("{:.1}k", self.current_sample).as_str(), &color), - Field(" Rate", format!("{}", self.sr).as_str(), &color), //Field(" CPU%", format!("{:.1}ms", self.perf).as_str(), &color), ]), - col!([ - Field(" Chunk", format!("{}", self.chunk).as_str(), &color), - Field(" Lag", format!("{:.3}ms", self.latency).as_str(), &color), - ]), - ]))) + ])))) }); +struct TransportField<'a>(&'a str, &'a str, &'a ItemPalette); +render!(|self: TransportField<'a>|row!([ + Tui::fg_bg(self.2.lightest.rgb, self.2.base.rgb, Tui::bold(true, self.0)), + Tui::fg_bg(self.2.base.rgb, self.2.darkest.rgb, "▌"), + Tui::fg_bg(self.2.lightest.rgb, self.2.darkest.rgb, format!("{:>10}", self.1)), + Tui::fg_bg(self.2.darkest.rgb, self.2.base.rgb, "▌"), +])); pub struct PlayPause(pub bool); render!(|self: PlayPause|Tui::bg( if self.0{Color::Rgb(0,128,0)}else{Color::Rgb(128,64,0)}, From 8cbe621b077fab349946a8e9a5006ad611e58953 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 30 Dec 2024 15:28:46 +0100 Subject: [PATCH 082/815] wip: refactoring groovebox render --- src/groovebox.rs | 78 +++++++++++++++++++++++------------------------- 1 file changed, 37 insertions(+), 41 deletions(-) diff --git a/src/groovebox.rs b/src/groovebox.rs index a1f24c71..f8a6753a 100644 --- a/src/groovebox.rs +++ b/src/groovebox.rs @@ -12,7 +12,7 @@ pub struct Groovebox { pub player: MidiPlayer, pub pool: PoolModel, pub editor: MidiEditorModel, - pub sampler: crate::sampler::Sampler, + pub sampler: Sampler, pub size: Measure, pub status: bool, @@ -120,52 +120,48 @@ render!(|self:Groovebox|{ let color = self.player.play_phrase().as_ref() .and_then(|(_,p)|p.as_ref().map(|p|p.read().unwrap().color)) .clone(); + let transport = Fixed::h(3, row!([ + PlayPause(self.clock().is_rolling()), + TransportView::new(self, color, true), + ])); + let selector = Push::x(sampler_w, Fixed::h(1, row!(![ + PhraseSelector::play_phrase(&self.player), + PhraseSelector::next_phrase(&self.player), + ]))); + let pool = move|x|Split::w(false, pool_w, Pull::y(1, Fill::h(Align::e(PoolView(&self.pool)))), x); + let sampler = move|x|Split::e(false, sampler_w, Fill::wh(col!([ + Meters(self.sampler.input_meter.as_ref()), + GrooveboxSamples(self) + ])), x); + let status = EditStatus(&self.sampler, &self.editor, note_pt, pool(sampler(&self.editor))); Fill::wh(lay!([ &self.size, Fill::wh(Align::s(Fixed::h(2, GrooveboxStatus::from(self)))), - Shrink::y(2, col!([ - Fixed::h(3, row!([ - PlayPause(self.clock().is_rolling()), - TransportView::new(self, color, true), - ])), - Push::x(sampler_w, Fixed::h(1, row!([ - PhraseSelector::play_phrase(&self.player), - PhraseSelector::next_phrase(&self.player), - ]))), - row!([ - Split::n(false, 9, - col!([ - row!(|add|{ - if let Some(sample) = &self.sampler.mapped[note_pt] { - add(&format!("Sample {}", sample.read().unwrap().end))?; - } - add(&MidiEditStatus(&self.editor))?; - Ok(()) - }), - lay!([ - Outer(Style::default().fg(TuiTheme::g(128))), - Fill::w(Fixed::h(8, if let Some((_, sample)) = &self.sampler.recording { - SampleViewer(Some(sample.clone())) - } else if let Some(sample) = &self.sampler.mapped[note_pt] { - SampleViewer(Some(sample.clone())) - } else { - SampleViewer(None) - })), - ]), - ]), - Split::w(false, pool_w, - Pull::y(1, Fill::h(Align::e(PoolView(&self.pool)))), - Split::e(false, sampler_w, Fill::wh(col!([ - Meters(self.sampler.input_meter.as_ref()), - GrooveboxSamples(self), - ])), Fill::h(&self.editor)) - ) - ), - ]), - ])) + Shrink::y(2, col!(![transport, selector, status])) ])) }); +struct EditStatus<'a, T: Render>(&'a Sampler, &'a MidiEditor, usize, T); +render!(|self: EditStatus<'a, T: Render>|Split::n(false, 9, col!(![ + row!(|add|{ + if let Some(sample) = &self.0.mapped[self.2] { + add(&format!("Sample {}", sample.read().unwrap().end))?; + } + add(&MidiEditStatus(&self.1))?; + Ok(()) + }), + lay!([ + Outer(Style::default().fg(TuiTheme::g(128))), + Fill::w(Fixed::h(8, if let Some((_, sample)) = &self.0.recording { + SampleViewer(Some(sample.clone())) + } else if let Some(sample) = &self.0.mapped[self.2] { + SampleViewer(Some(sample.clone())) + } else { + SampleViewer(None) + })), + ]), +]), self.3)); + struct GrooveboxSamples<'a>(&'a Groovebox); render!(|self: GrooveboxSamples<'a>|{ let note_lo = self.0.editor.note_lo().load(Relaxed); From 4a3de618d0ce0db4d3f270bf222337c0a42a802c Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 30 Dec 2024 15:56:56 +0100 Subject: [PATCH 083/815] wip: big flat --- src/arranger.rs | 46 ++++---- src/{core => }/color.rs | 0 src/{core => }/command.rs | 0 src/core.rs | 69 ------------ src/core/test.rs | 49 --------- src/{core => }/engine.rs | 0 src/event.rs | 34 ++++++ src/{tui/file_browser.rs => file.rs} | 0 src/{core => }/focus.rs | 0 src/groovebox.rs | 72 +++++++------ src/{core => }/input.rs | 0 src/lib.rs | 155 ++++++++++++++++----------- src/midi/midi_editor.rs | 44 ++++---- src/{core => }/output.rs | 53 +++++---- src/sequencer.rs | 4 +- src/status/status_edit.rs | 2 +- src/test.rs | 49 +++++++++ src/tui.rs | 22 ++-- src/tui/tui_input.rs | 33 ------ src/tui/tui_output.rs | 9 -- 20 files changed, 305 insertions(+), 336 deletions(-) rename src/{core => }/color.rs (100%) rename src/{core => }/command.rs (100%) delete mode 100644 src/core/test.rs rename src/{core => }/engine.rs (100%) create mode 100644 src/event.rs rename src/{tui/file_browser.rs => file.rs} (100%) rename src/{core => }/focus.rs (100%) rename src/{core => }/input.rs (100%) rename src/{core => }/output.rs (79%) diff --git a/src/arranger.rs b/src/arranger.rs index efb6d39f..33e74002 100644 --- a/src/arranger.rs +++ b/src/arranger.rs @@ -22,7 +22,7 @@ pub struct ArrangerTui { pub size: Measure, pub note_buf: Vec, pub midi_buf: Vec>>, - pub editor: MidiEditorModel, + pub editor: MidiEditor, pub perf: PerfModel, } impl ArrangerTui { @@ -99,27 +99,29 @@ impl ArrangerTui { } } } -render!(|self: ArrangerTui|{ - let play = PlayPause(self.clock.is_rolling()); - let transport = TransportView::new(self, Some(ItemPalette::from(TuiTheme::g(96))), true); - let with_transport = |x|col!([row!(![&play, &transport]), &x]); - let pool_size = if self.phrases.visible { self.splits[1] } else { 0 }; - let with_pool = |x|Split::w(false, pool_size, PoolView(&self.phrases), x); - let status = ArrangerStatus::from(self); - let with_editbar = |x|Split::n(false, 1, MidiEditStatus(&self.editor), x); - let with_status = |x|Split::n(false, 2, status, x); - let with_size = |x|lay!([&self.size, x]); - let arranger = ||lay!(|add|{ - let color = self.color; - add(&Fill::wh(Tui::bg(color.darkest.rgb, ())))?; - add(&Fill::wh(Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb))))?; - add(&Self::render_mode(self)) - }); - with_size(with_status(with_editbar(with_pool(with_transport(col!([ - Fill::w(Fixed::h(20, arranger())), - Fill::wh(&self.editor), - ])))))) -}); +impl Render for ArrangerTui { + fn content (&self) -> Option> { + let play = PlayPause(self.clock.is_rolling()); + let transport = TransportView::new(self, Some(ItemPalette::from(TuiTheme::g(96))), true); + let with_transport = |x|col!([row!(![&play, &transport]), &x]); + let pool_size = if self.phrases.visible { self.splits[1] } else { 0 }; + let with_pool = |x|Split::w(false, pool_size, PoolView(&self.phrases), x); + let status = ArrangerStatus::from(self); + let with_editbar = |x|Split::n(false, 1, MidiEditStatus(&self.editor), x); + let with_status = |x|Split::n(false, 2, status, x); + let with_size = |x|lay!([&self.size, x]); + let arranger = ||lay!(|add|{ + let color = self.color; + add(&Fill::wh(Tui::bg(color.darkest.rgb, ())))?; + add(&Fill::wh(Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb))))?; + add(&Self::render_mode(self)) + }); + Some(with_size(with_status(with_editbar(with_pool(with_transport(col!([ + Fill::w(Fixed::h(20, arranger())), + Fill::wh(&self.editor), + ]))))))) + } +} audio!(|self: ArrangerTui, client, scope|{ // Start profiling cycle let t0 = self.perf.get_t0(); diff --git a/src/core/color.rs b/src/color.rs similarity index 100% rename from src/core/color.rs rename to src/color.rs diff --git a/src/core/command.rs b/src/command.rs similarity index 100% rename from src/core/command.rs rename to src/command.rs diff --git a/src/core.rs b/src/core.rs index 40b42d85..e69de29b 100644 --- a/src/core.rs +++ b/src/core.rs @@ -1,69 +0,0 @@ -pub(crate) use std::error::Error; - -pub(crate) mod color; -pub(crate) use color::*; -pub use color::*; - -pub(crate) mod command; pub(crate) use command::*; -pub(crate) mod engine; pub(crate) use engine::*; -pub(crate) mod focus; pub(crate) use focus::*; -pub(crate) mod input; pub(crate) use input::*; -pub(crate) mod output; pub(crate) use output::*; - -pub(crate) use std::sync::atomic::{Ordering, AtomicBool, AtomicUsize}; -pub(crate) use Ordering::Relaxed; - -pub use self::{ - engine::Engine, - input::Handle, - output::Render -}; - -/// Standard result type. -pub type Usually = Result>; - -/// Standard optional result type. -pub type Perhaps = Result, Box>; - -/// Define test modules. -#[macro_export] macro_rules! testmod { - ($($name:ident)*) => { $(#[cfg(test)] mod $name;)* }; -} - -/// Prototypal case of implementor macro. -/// Saves 4loc per data pats. -#[macro_export] macro_rules! from { - ($(<$($lt:lifetime),+>)?|$state:ident:$Source:ty|$Target:ty=$cb:expr) => { - impl $(<$($lt),+>)? From<$Source> for $Target { - fn from ($state:$Source) -> Self { $cb } - } - }; -} - -pub trait Gettable { - /// Returns current value - fn get (&self) -> T; -} - -pub trait Mutable: Gettable { - /// Sets new value, returns old - fn set (&mut self, value: T) -> T; -} - -pub trait InteriorMutable: Gettable { - /// Sets new value, returns old - fn set (&self, value: T) -> T; -} - -impl Gettable for AtomicBool { - fn get (&self) -> bool { self.load(Relaxed) } -} -impl InteriorMutable for AtomicBool { - fn set (&self, value: bool) -> bool { self.swap(value, Relaxed) } -} -impl Gettable for AtomicUsize { - fn get (&self) -> usize { self.load(Relaxed) } -} -impl InteriorMutable for AtomicUsize { - fn set (&self, value: usize) -> usize { self.swap(value, Relaxed) } -} diff --git a/src/core/test.rs b/src/core/test.rs deleted file mode 100644 index 88699914..00000000 --- a/src/core/test.rs +++ /dev/null @@ -1,49 +0,0 @@ -#[cfg(test)] mod test_focus { - use super::focus::*; - #[test] fn test_focus () { - - struct FocusTest { - focused: char, - cursor: (usize, usize) - } - - impl HasFocus for FocusTest { - type Item = char; - fn focused (&self) -> Self::Item { - self.focused - } - fn set_focused (&mut self, to: Self::Item) { - self.focused = to - } - } - - impl FocusGrid for FocusTest { - fn focus_cursor (&self) -> (usize, usize) { - self.cursor - } - fn focus_cursor_mut (&mut self) -> &mut (usize, usize) { - &mut self.cursor - } - fn focus_layout (&self) -> &[&[Self::Item]] { - &[ - &['a', 'a', 'a', 'b', 'b', 'd'], - &['a', 'a', 'a', 'b', 'b', 'd'], - &['a', 'a', 'a', 'c', 'c', 'd'], - &['a', 'a', 'a', 'c', 'c', 'd'], - &['e', 'e', 'e', 'e', 'e', 'e'], - ] - } - } - - let mut tester = FocusTest { focused: 'a', cursor: (0, 0) }; - - tester.focus_right(); - assert_eq!(tester.cursor.0, 3); - assert_eq!(tester.focused, 'b'); - - tester.focus_down(); - assert_eq!(tester.cursor.1, 2); - assert_eq!(tester.focused, 'c'); - - } -} diff --git a/src/core/engine.rs b/src/engine.rs similarity index 100% rename from src/core/engine.rs rename to src/engine.rs diff --git a/src/event.rs b/src/event.rs new file mode 100644 index 00000000..ea13af41 --- /dev/null +++ b/src/event.rs @@ -0,0 +1,34 @@ +use crate::*; + +pub struct EventMap<'a, const N: usize, E, T, U>( + pub [(E, &'a dyn Fn(T) -> U); N], + pub Option<&'a dyn Fn(T) -> U>, +); + +impl<'a, const N: usize, E: PartialEq, T, U> EventMap<'a, N, E, T, U> { + pub fn handle (&self, context: T, event: &E) -> Option { + for (binding, handler) in self.0.iter() { + if event == binding { + return Some(handler(context)) + } + } + return None + } +} + +#[macro_export] macro_rules! event_map { + ($events:expr) => { + EventMap($events, None) + }; + ($events:expr, $default: expr) => { + EventMap($events, $default) + }; +} + +#[macro_export] macro_rules! event_map_input_to_command { + ($Engine:ty: $Model:ty: $Command:ty: $EventMap:expr) => { + input_to_command!($Command: <$Engine>|state: $Model, input|{ + event_map!($EventMap).handle(state, input.event())? + }); + } +} diff --git a/src/tui/file_browser.rs b/src/file.rs similarity index 100% rename from src/tui/file_browser.rs rename to src/file.rs diff --git a/src/core/focus.rs b/src/focus.rs similarity index 100% rename from src/core/focus.rs rename to src/focus.rs diff --git a/src/groovebox.rs b/src/groovebox.rs index f8a6753a..94f63393 100644 --- a/src/groovebox.rs +++ b/src/groovebox.rs @@ -2,7 +2,7 @@ use crate::*; use super::*; use KeyCode::{Char, Delete, Tab, Up, Down, Left, Right}; use ClockCommand::{Play, Pause}; -use GrooveboxCommand::*; +use GrooveboxCommand as Cmd; use PhraseCommand::*; use PhrasePoolCommand::*; @@ -11,7 +11,7 @@ pub struct Groovebox { pub player: MidiPlayer, pub pool: PoolModel, - pub editor: MidiEditorModel, + pub editor: MidiEditor, pub sampler: Sampler, pub size: Measure, @@ -49,7 +49,7 @@ impl Groovebox { ))); player.play_phrase = Some((Moment::zero(&player.clock.timebase), Some(phrase.clone()))); let pool = crate::pool::PoolModel::from(&phrase); - let editor = crate::midi::MidiEditorModel::from(&phrase); + let editor = crate::midi::MidiEditor::from(&phrase); Ok(Self { _jack: jack.clone(), player, @@ -142,25 +142,29 @@ render!(|self:Groovebox|{ }); struct EditStatus<'a, T: Render>(&'a Sampler, &'a MidiEditor, usize, T); -render!(|self: EditStatus<'a, T: Render>|Split::n(false, 9, col!(![ - row!(|add|{ - if let Some(sample) = &self.0.mapped[self.2] { - add(&format!("Sample {}", sample.read().unwrap().end))?; - } - add(&MidiEditStatus(&self.1))?; - Ok(()) - }), - lay!([ - Outer(Style::default().fg(TuiTheme::g(128))), - Fill::w(Fixed::h(8, if let Some((_, sample)) = &self.0.recording { - SampleViewer(Some(sample.clone())) - } else if let Some(sample) = &self.0.mapped[self.2] { - SampleViewer(Some(sample.clone())) - } else { - SampleViewer(None) - })), - ]), -]), self.3)); +impl<'a, T: Render> Render for EditStatus<'a, T> { + fn content (&self) -> impl Render { + Split::n(false, 9, col!([ + row!(|add|{ + if let Some(sample) = &self.0.mapped[self.2] { + add(&format!("Sample {}", sample.read().unwrap().end))?; + } + add(&MidiEditStatus(&self.1))?; + Ok(()) + }), + lay!([ + Outer(Style::default().fg(TuiTheme::g(128))), + Fill::w(Fixed::h(8, if let Some((_, sample)) = &self.0.recording { + SampleViewer(Some(sample.clone())) + } else if let Some(sample) = &self.0.mapped[self.2] { + SampleViewer(Some(sample.clone())) + } else { + SampleViewer(None) + })), + ]), + ]), &self.3) + } +} struct GrooveboxSamples<'a>(&'a Groovebox); render!(|self: GrooveboxSamples<'a>|{ @@ -204,19 +208,19 @@ input_to_command!(GrooveboxCommand: |state: Groovebox, input|match input.ev }, // Transport: Play from start or rewind to start - key_pat!(Char(' ')) => Clock( + key_pat!(Char(' ')) => Cmd::Clock( if state.clock().is_stopped() { Play(Some(0)) } else { Pause(Some(0)) } ), // Tab: Toggle visibility of phrase pool column - key_pat!(Tab) => Pool(PoolCommand::Show(!state.pool.visible)), + key_pat!(Tab) => Cmd::Pool(PoolCommand::Show(!state.pool.visible)), // q: Enqueue currently edited phrase - key_pat!(Char('q')) => Enqueue(Some(state.pool.phrase().clone())), + key_pat!(Char('q')) => Cmd::Enqueue(Some(state.pool.phrase().clone())), // 0: Enqueue phrase 0 (stop all) - key_pat!(Char('0')) => Enqueue(Some(state.pool.phrases()[0].clone())), + key_pat!(Char('0')) => Cmd::Enqueue(Some(state.pool.phrases()[0].clone())), - key_pat!(Shift-Char('R')) => Sampler(if state.sampler.recording.is_some() { + key_pat!(Shift-Char('R')) => Cmd::Sampler(if state.sampler.recording.is_some() { SamplerCommand::RecordFinish } else { SamplerCommand::RecordBegin(u7::from(state.editor.note_point() as u8)) @@ -226,7 +230,7 @@ input_to_command!(GrooveboxCommand: |state: Groovebox, input|match input.ev key_pat!(Char('e')) => if let Some((_, Some(playing))) = state.player.play_phrase() { let editing = state.editor.phrase().as_ref().map(|p|p.read().unwrap().clone()); let selected = state.pool.phrase().clone(); - Editor(Show(Some(if Some(selected.read().unwrap().clone()) != editing { + Cmd::Editor(Show(Some(if Some(selected.read().unwrap().clone()) != editing { selected } else { playing.clone() @@ -238,9 +242,9 @@ input_to_command!(GrooveboxCommand: |state: Groovebox, input|match input.ev // For the rest, use the default keybindings of the components. // The ones defined above supersede them. _ => if let Some(command) = PhraseCommand::input_to_command(&state.editor, input) { - Editor(command) + Cmd::Editor(command) } else if let Some(command) = PoolCommand::input_to_command(&state.pool, input) { - Pool(command) + Cmd::Pool(command) } else { return None } @@ -250,7 +254,7 @@ command!(|self: GrooveboxCommand, state: Groovebox|match self { Self::Pool(cmd) => { let mut default = |cmd: PoolCommand|cmd .execute(&mut state.pool) - .map(|x|x.map(Pool)); + .map(|x|x.map(Self::Pool)); match cmd { // autoselect: automatically load selected phrase in editor PoolCommand::Select(_) => { @@ -268,13 +272,13 @@ command!(|self: GrooveboxCommand, state: Groovebox|match self { } }, Self::Editor(cmd) => { - let default = ||cmd.execute(&mut state.editor).map(|x|x.map(Editor)); + let default = ||cmd.execute(&mut state.editor).map(|x|x.map(Self::Editor)); match cmd { _ => default()? } }, - Self::Clock(cmd) => cmd.execute(state)?.map(Clock), - Self::Sampler(cmd) => cmd.execute(&mut state.sampler)?.map(Sampler), + Self::Clock(cmd) => cmd.execute(state)?.map(Self::Clock), + Self::Sampler(cmd) => cmd.execute(&mut state.sampler)?.map(Self::Sampler), Self::Enqueue(phrase) => { state.player.enqueue_next(phrase.as_ref()); None diff --git a/src/core/input.rs b/src/input.rs similarity index 100% rename from src/core/input.rs rename to src/input.rs diff --git a/src/lib.rs b/src/lib.rs index 01258fc5..60204b0e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,69 +1,50 @@ #![allow(unused)] #![allow(clippy::unit_arg)] -pub mod core; pub use self::core::*; - -pub mod time; pub(crate) use self::time::*; -pub use self::time::HasClock; - -pub mod space; pub(crate) use self::space::*; -pub use self::space::Measure; - -pub mod tui; pub(crate) use self::tui::*; -pub use tui::*; - -pub mod jack; pub(crate) use self::jack::*; -pub use self::jack::*; - -pub mod midi; pub(crate) use self::midi::*; - -pub mod meter; pub(crate) use self::meter::*; - -pub mod piano_h; pub(crate) use self::piano_h::*; - -pub mod transport; pub(crate) use self::transport::*; -pub use self::transport::TransportTui; - -pub mod sequencer; pub(crate) use self::sequencer::*; -pub use self::sequencer::SequencerTui; - -pub mod arranger; pub(crate) use self::arranger::*; -pub use self::arranger::ArrangerTui; - -pub mod sampler; pub(crate) use self::sampler::*; -pub use self::sampler::{SamplerTui, Sampler, Sample, Voice}; - -pub mod mixer; pub(crate) use self::mixer::*; -pub use self::mixer::{Mixer, MixerTrack, MixerTrackDevice}; - -pub mod plugin; pub(crate) use self::plugin::*; -pub use self::plugin::*; - -pub mod groovebox; pub(crate) use self::groovebox::*; -pub use self::groovebox::Groovebox; - -pub mod pool; pub(crate) use self::pool::*; -pub use self::pool::PoolModel; - -pub mod status; pub(crate) use self::status::*; - -pub use ::better_panic; pub(crate) use better_panic::{Settings, Verbosity}; - -pub use ::atomic_float; pub(crate) use atomic_float::*; - -pub(crate) use std::sync::{Arc, Mutex, RwLock}; -pub(crate) use std::sync::atomic::Ordering; -pub(crate) use std::sync::atomic::{AtomicBool, AtomicUsize}; -pub(crate) use std::collections::BTreeMap; -pub(crate) use std::marker::PhantomData; -pub(crate) use std::thread::{spawn, JoinHandle}; -pub(crate) use std::path::PathBuf; -pub(crate) use std::ffi::OsString; -pub(crate) use std::time::Duration; -pub(crate) use std::io::{Stdout, stdout}; -pub(crate) use std::ops::{Add, Sub, Mul, Div, Rem}; pub(crate) use std::cmp::{Ord, Eq, PartialEq}; +pub(crate) use std::collections::BTreeMap; +pub(crate) use std::error::Error; +pub(crate) use std::ffi::OsString; pub(crate) use std::fmt::{Debug, Display, Formatter}; +pub(crate) use std::io::{Stdout, stdout}; +pub(crate) use std::marker::PhantomData; +pub(crate) use std::ops::{Add, Sub, Mul, Div, Rem}; +pub(crate) use std::path::PathBuf; +pub(crate) use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::{self, *}}; +pub(crate) use std::sync::{Arc, Mutex, RwLock}; +pub(crate) use std::thread::{spawn, JoinHandle}; +pub(crate) use std::time::Duration; + +pub mod arranger; pub use self::arranger::*; +pub mod color; pub use self::color::*; +pub mod command; pub use self::command::*; +pub mod engine; pub use self::engine::*; +pub mod event; pub use self::event::*; +pub mod file; pub use self::file::*; +pub mod focus; pub use self::focus::*; +pub mod groovebox; pub use self::groovebox::*; +pub mod input; pub use self::input::*; +pub mod jack; pub use self::jack::*; +pub mod meter; pub use self::meter::*; +pub mod midi; pub use self::midi::*; +pub mod mixer; pub use self::mixer::*; +pub mod output; pub use self::output::*; +pub mod piano_h; pub use self::piano_h::*; +pub mod plugin; pub use self::plugin::*; +pub mod pool; pub use self::pool::*; +pub mod sampler; pub use self::sampler::*; +pub mod sequencer; pub use self::sequencer::*; +pub mod space; pub use self::space::*; +pub mod status; pub use self::status::*; +pub mod time; pub use self::time::*; +pub mod transport; pub use self::transport::*; +pub mod tui; pub use self::tui::*; + +pub use ::better_panic; +pub(crate) use better_panic::{Settings, Verbosity}; + +pub use ::atomic_float; +pub(crate) use atomic_float::*; pub use ::crossterm; pub(crate) use crossterm::{ExecutableCommand}; @@ -93,3 +74,55 @@ pub(crate) use ::palette::{ }; testmod! { test } + +/// Standard result type. +pub type Usually = Result>; + +/// Standard optional result type. +pub type Perhaps = Result, Box>; + +/// Define test modules. +#[macro_export] macro_rules! testmod { + ($($name:ident)*) => { $(#[cfg(test)] mod $name;)* }; +} + +/// Prototypal case of implementor macro. +/// Saves 4loc per data pats. +#[macro_export] macro_rules! from { + ($(<$($lt:lifetime),+>)?|$state:ident:$Source:ty|$Target:ty=$cb:expr) => { + impl $(<$($lt),+>)? From<$Source> for $Target { + fn from ($state:$Source) -> Self { $cb } + } + }; +} + +pub trait Gettable { + /// Returns current value + fn get (&self) -> T; +} + +pub trait Mutable: Gettable { + /// Sets new value, returns old + fn set (&mut self, value: T) -> T; +} + +pub trait InteriorMutable: Gettable { + /// Sets new value, returns old + fn set (&self, value: T) -> T; +} + +impl Gettable for AtomicBool { + fn get (&self) -> bool { self.load(Relaxed) } +} + +impl InteriorMutable for AtomicBool { + fn set (&self, value: bool) -> bool { self.swap(value, Relaxed) } +} + +impl Gettable for AtomicUsize { + fn get (&self) -> usize { self.load(Relaxed) } +} + +impl InteriorMutable for AtomicUsize { + fn set (&self, value: usize) -> usize { self.swap(value, Relaxed) } +} diff --git a/src/midi/midi_editor.rs b/src/midi/midi_editor.rs index f61779b1..72e79c59 100644 --- a/src/midi/midi_editor.rs +++ b/src/midi/midi_editor.rs @@ -3,13 +3,13 @@ use KeyCode::{Char, Up, Down, Left, Right, Enter}; use PhraseCommand::*; pub trait HasEditor { - fn editor (&self) -> &MidiEditorModel; + fn editor (&self) -> &MidiEditor; } #[macro_export] macro_rules! has_editor { (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { impl $(<$($L),*$($T $(: $U)?),*>)? HasEditor for $Struct $(<$($L),*$($T),*>)? { - fn editor (&$self) -> &MidiEditorModel { &$cb } + fn editor (&$self) -> &MidiEditor { &$cb } } } } @@ -29,8 +29,8 @@ pub enum PhraseCommand { Show(Option>>), } -event_map_input_to_command!(Tui: MidiEditorModel: PhraseCommand: MidiEditorModel::KEYS); -impl MidiEditorModel { +event_map_input_to_command!(Tui: MidiEditor: PhraseCommand: MidiEditor::KEYS); +impl MidiEditor { const KEYS: KeyMapping<31, Self> = [ (kexp!(Ctrl-Alt-Up), &|s: &Self|SetNoteScroll(s.note_point() + 3)), (kexp!(Ctrl-Alt-Down), &|s: &Self|SetNoteScroll(s.note_point().saturating_sub(3))), @@ -71,8 +71,8 @@ impl MidiEditorModel { } } -impl Command for PhraseCommand { - fn execute (self, state: &mut MidiEditorModel) -> Perhaps { +impl Command for PhraseCommand { + fn execute (self, state: &mut MidiEditor) -> Perhaps { use PhraseCommand::*; match self { Show(phrase) => { state.set_phrase(phrase.as_ref()); }, @@ -92,13 +92,13 @@ impl Command for PhraseCommand { } /// Contains state for viewing and editing a phrase -pub struct MidiEditorModel { +pub struct MidiEditor { /// Renders the phrase pub mode: Box, pub size: Measure } -impl Default for MidiEditorModel { +impl Default for MidiEditor { fn default () -> Self { let mut mode = Box::new(PianoHorizontal::new(None)); mode.redraw(); @@ -106,13 +106,13 @@ impl Default for MidiEditorModel { } } -has_size!(|self:MidiEditorModel|&self.size); -render!(|self: MidiEditorModel|{ +has_size!(|self: MidiEditor|&self.size); +render!(|self: MidiEditor|{ self.autoscroll(); self.autozoom(); &self.mode }); -//render!(|self: MidiEditorModel|lay!(|add|{add(&self.size)?;add(self.mode)}));//bollocks +//render!(|self: MidiEditor|lay!(|add|{add(&self.size)?;add(self.mode)}));//bollocks pub trait PhraseViewMode: Render + HasSize + MidiRange + MidiPoint + Debug + Send + Sync { fn buffer_size (&self, phrase: &MidiClip) -> (usize, usize); @@ -125,9 +125,9 @@ pub trait PhraseViewMode: Render + HasSize + MidiRange + MidiPoint + D } } -impl MidiView for MidiEditorModel {} +impl MidiView for MidiEditor {} -impl TimeRange for MidiEditorModel { +impl TimeRange for MidiEditor { fn time_len (&self) -> &AtomicUsize { self.mode.time_len() } fn time_zoom (&self) -> &AtomicUsize { self.mode.time_zoom() } fn time_lock (&self) -> &AtomicBool { self.mode.time_lock() } @@ -135,24 +135,24 @@ impl TimeRange for MidiEditorModel { fn time_axis (&self) -> &AtomicUsize { self.mode.time_axis() } } -impl NoteRange for MidiEditorModel { +impl NoteRange for MidiEditor { fn note_lo (&self) -> &AtomicUsize { self.mode.note_lo() } fn note_axis (&self) -> &AtomicUsize { self.mode.note_axis() } } -impl NotePoint for MidiEditorModel { +impl NotePoint for MidiEditor { fn note_len (&self) -> usize { self.mode.note_len() } fn set_note_len (&self, x: usize) { self.mode.set_note_len(x) } fn note_point (&self) -> usize { self.mode.note_point() } fn set_note_point (&self, x: usize) { self.mode.set_note_point(x) } } -impl TimePoint for MidiEditorModel { +impl TimePoint for MidiEditor { fn time_point (&self) -> usize { self.mode.time_point() } fn set_time_point (&self, x: usize) { self.mode.set_time_point(x) } } -impl PhraseViewMode for MidiEditorModel { +impl PhraseViewMode for MidiEditor { fn buffer_size (&self, phrase: &MidiClip) -> (usize, usize) { self.mode.buffer_size(phrase) } @@ -170,7 +170,7 @@ impl PhraseViewMode for MidiEditorModel { } } -impl MidiEditorModel { +impl MidiEditor { /// Put note at current position pub fn put_note (&mut self, advance: bool) { let mut redraw = false; @@ -203,22 +203,22 @@ impl MidiEditorModel { } } -from!(|phrase: &Arc>|MidiEditorModel = { +from!(|phrase: &Arc>|MidiEditor = { let mut model = Self::from(Some(phrase.clone())); model.redraw(); model }); -from!(|phrase: Option>>|MidiEditorModel = { +from!(|phrase: Option>>|MidiEditor = { let mut model = Self::default(); *model.phrase_mut() = phrase; model.redraw(); model }); -impl std::fmt::Debug for MidiEditorModel { +impl std::fmt::Debug for MidiEditor { fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - f.debug_struct("MidiEditorModel") + f.debug_struct("MidiEditor") .field("mode", &self.mode) .finish() } diff --git a/src/core/output.rs b/src/output.rs similarity index 79% rename from src/core/output.rs rename to src/output.rs index ff8748a7..5a45e89e 100644 --- a/src/core/output.rs +++ b/src/output.rs @@ -1,13 +1,24 @@ use crate::*; +/// Ad-hoc widget with custom rendering. +pub fn render (render: F) -> impl Render + where E: Engine, F: Fn(&mut E::Output)->Usually<()>+Send+Sync +{ + Widget::new(|_|Ok(Some([0.into(),0.into()].into())), render) +} + +/// Cast to dynamic pointer +pub fn widget (w: &T) -> &dyn Render + where E: Engine, T: Render +{ + w as &dyn Render +} + #[macro_export] macro_rules! render { (|$self:ident:$Struct:ident$(<$($L:lifetime),*E$(,$T:ident$(:$U:path)?)*$(,)?>)?|$cb:expr) => { impl Render for $Struct $(<$($L,)* E, $($T),*>)? { - fn min_size (&$self, to: ::Size) -> Perhaps<::Size> { - $cb.min_size(to) - } - fn render (&$self, to: &mut ::Output) -> Usually<()> { - $cb.render(to) + fn content (&$self) -> Option> { + Some($cb) } } }; @@ -22,11 +33,8 @@ use crate::*; $($($L),+)? $($($T),+)? >)? { - fn min_size (&$self, to: <$E as Engine>::Size) -> Perhaps<<$E as Engine>::Size> { - $cb.min_size(to) - } - fn render (&$self, to: &mut <$E as Engine>::Output) -> Usually<()> { - $cb.render(to) + fn content (&$self) -> Option> { + Some($cb) } } } @@ -42,19 +50,28 @@ pub trait Output { fn render_in (&mut self, area: E::Area, widget: &dyn Render) -> Usually<()>; } -/// Cast to dynamic pointer -pub fn widget > (w: &T) -> &dyn Render { - w as &dyn Render -} - /// A renderable component -pub trait Render: Send + Sync { +pub trait Render: Send + Sync where (): Render { + fn content (&self) -> Option> where Self: Sized { + None::<()> + } /// Minimum size to use fn min_size (&self, to: E::Size) -> Perhaps { - Ok(Some(to)) + self.content().map(|content|content.min_size(to)).unwrap_or(Ok(None)) } /// Draw to output render target - fn render (&self, to: &mut E::Output) -> Usually<()>; + fn render (&self, to: &mut E::Output) -> Usually<()> { + self.content().map(|content|content.render(to)).unwrap_or(Ok(())) + } +} + +impl Render for () { + fn min_size (&self, to: E::Size) -> Perhaps { + Ok(None) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + Ok(()) + } } impl> Render for &R { diff --git a/src/sequencer.rs b/src/sequencer.rs index b9dc4287..fdfa6cc5 100644 --- a/src/sequencer.rs +++ b/src/sequencer.rs @@ -12,7 +12,7 @@ pub struct SequencerTui { pub clock: ClockModel, pub phrases: PoolModel, pub player: MidiPlayer, - pub editor: MidiEditorModel, + pub editor: MidiEditor, pub size: Measure, pub status: bool, pub note_buf: Vec, @@ -30,7 +30,7 @@ from_jack!(|jack|SequencerTui { transport: true, selectors: true, phrases: PoolModel::from(&phrase), - editor: MidiEditorModel::from(&phrase), + editor: MidiEditor::from(&phrase), player: MidiPlayer::from((&clock, &phrase)), size: Measure::new(), midi_buf: vec![vec![];65536], diff --git a/src/status/status_edit.rs b/src/status/status_edit.rs index 2bbde7be..5ea8de03 100644 --- a/src/status/status_edit.rs +++ b/src/status/status_edit.rs @@ -1,6 +1,6 @@ use crate::*; -pub struct MidiEditStatus<'a>(pub &'a MidiEditorModel); +pub struct MidiEditStatus<'a>(pub &'a MidiEditor); render!(|self:MidiEditStatus<'a>|{ let (color, name, length, looped) = if let Some(phrase) = self.0.phrase().as_ref().map(|p|p.read().unwrap()) { diff --git a/src/test.rs b/src/test.rs index 5f7dcb0e..34c6e4aa 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,3 +1,52 @@ +//#[cfg(test)] mod test_focus { + //use super::focus::*; + //#[test] fn test_focus () { + + //struct FocusTest { + //focused: char, + //cursor: (usize, usize) + //} + + //impl HasFocus for FocusTest { + //type Item = char; + //fn focused (&self) -> Self::Item { + //self.focused + //} + //fn set_focused (&mut self, to: Self::Item) { + //self.focused = to + //} + //} + + //impl FocusGrid for FocusTest { + //fn focus_cursor (&self) -> (usize, usize) { + //self.cursor + //} + //fn focus_cursor_mut (&mut self) -> &mut (usize, usize) { + //&mut self.cursor + //} + //fn focus_layout (&self) -> &[&[Self::Item]] { + //&[ + //&['a', 'a', 'a', 'b', 'b', 'd'], + //&['a', 'a', 'a', 'b', 'b', 'd'], + //&['a', 'a', 'a', 'c', 'c', 'd'], + //&['a', 'a', 'a', 'c', 'c', 'd'], + //&['e', 'e', 'e', 'e', 'e', 'e'], + //] + //} + //} + + //let mut tester = FocusTest { focused: 'a', cursor: (0, 0) }; + + //tester.focus_right(); + //assert_eq!(tester.cursor.0, 3); + //assert_eq!(tester.focused, 'b'); + + //tester.focus_down(); + //assert_eq!(tester.cursor.1, 2); + //assert_eq!(tester.focused, 'c'); + + //} +//} //use crate::*; //struct TestEngine([u16;4], Vec>); diff --git a/src/tui.rs b/src/tui.rs index 8cd25285..361c45b4 100644 --- a/src/tui.rs +++ b/src/tui.rs @@ -1,27 +1,17 @@ use crate::*; -mod tui_input; pub(crate) use self::tui_input::*; -mod tui_output; pub(crate) use self::tui_output::*; - +mod tui_input; pub(crate) use self::tui_input::*; pub use self::tui_input::TuiInput; + +mod tui_output; pub(crate) use self::tui_output::*; pub use self::tui_output::TuiOutput; -//////////////////////////////////////////////////////// - mod tui_style; + mod tui_theme; pub(crate) use self::tui_theme::*; + mod tui_border; pub(crate) use self::tui_border::*; -/////////////////////////////////////////////////////// - -pub mod file_browser; pub(crate) use self::file_browser::*; - -//////////////////////////////////////////////////////// - -pub fn render Usually<()>+Send+Sync> (render: F) -> impl Render { - Widget::new(|_|Ok(Some([0u16,0u16])), render) -} - //////////////////////////////////////////////////////// pub struct Tui { @@ -31,7 +21,7 @@ pub struct Tui { pub area: [u16;4], // FIXME auto resize } -impl crate::core::Engine for Tui { +impl Engine for Tui { type Unit = u16; type Size = [Self::Unit;2]; type Area = [Self::Unit;4]; diff --git a/src/tui/tui_input.rs b/src/tui/tui_input.rs index cabf317b..d8b20059 100644 --- a/src/tui/tui_input.rs +++ b/src/tui/tui_input.rs @@ -59,39 +59,6 @@ impl Input for TuiInput { }; } -pub struct EventMap<'a, const N: usize, E, T, U>( - pub [(E, &'a dyn Fn(T) -> U); N], - pub Option<&'a dyn Fn(T) -> U>, -); - -impl<'a, const N: usize, E: PartialEq, T, U> EventMap<'a, N, E, T, U> { - pub fn handle (&self, context: T, event: &E) -> Option { - for (binding, handler) in self.0.iter() { - if event == binding { - return Some(handler(context)) - } - } - return None - } -} - -#[macro_export] macro_rules! event_map { - ($events:expr) => { - EventMap($events, None) - }; - ($events:expr, $default: expr) => { - EventMap($events, $default) - }; -} - -#[macro_export] macro_rules! event_map_input_to_command { - ($Engine:ty: $Model:ty: $Command:ty: $EventMap:expr) => { - input_to_command!($Command: <$Engine>|state: $Model, input|{ - event_map!($EventMap).handle(state, input.event())? - }); - } -} - pub(crate) type KeyMapping = [(TuiEvent, &'static dyn Fn(&T)->PhraseCommand);N]; #[macro_export] macro_rules! kexp { diff --git a/src/tui/tui_output.rs b/src/tui/tui_output.rs index b346b535..b139c490 100644 --- a/src/tui/tui_output.rs +++ b/src/tui/tui_output.rs @@ -121,15 +121,6 @@ pub fn half_block (lower: bool, upper: bool) -> Option { } } -impl Render for () { - fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> { - Ok(None) - } - fn render (&self, to: &mut TuiOutput) -> Usually<()> { - Ok(()) - } -} - impl Render for &str { fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> { // TODO: line breaks From a5628fb6633229a826b0dfd643dac35ec813fd96 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Mon, 30 Dec 2024 17:54:30 +0100 Subject: [PATCH 084/815] wip: big flat pt.2: extract engine crate --- Cargo.lock | 5 + Cargo.toml | 4 +- engine/Cargo.lock | 666 +++++++++++++++++++++++++++ engine/Cargo.toml | 9 + engine/src/component.rs | 27 ++ engine/src/engine.rs | 80 ++++ {src => engine/src}/input.rs | 11 +- engine/src/lib.rs | 62 +++ engine/src/output.rs | 272 +++++++++++ engine/src/tui.rs | 440 ++++++++++++++++++ src/arranger.rs | 44 +- src/arranger/arranger_mode.rs | 2 +- src/arranger/arranger_v/v_io.rs | 4 +- src/{tui/tui_border.rs => border.rs} | 2 +- src/engine.rs | 50 +- src/engine/render.rs | 0 src/groovebox.rs | 6 +- src/lib.rs | 66 ++- src/midi/midi_editor.rs | 8 + src/output.rs | 177 ------- src/piano_h/piano_h_cursor.rs | 2 +- src/piano_h/piano_h_notes.rs | 2 +- src/piano_h/piano_h_time.rs | 2 +- src/sampler/sample_viewer.rs | 2 +- src/space.rs | 223 ++++----- src/space/measure.rs | 2 +- src/{tui/tui_style.rs => style.rs} | 0 src/{tui/tui_theme.rs => theme.rs} | 0 src/tui.rs | 140 ------ src/tui/tui_input.rs | 160 ------- src/tui/tui_output.rs | 158 ------- 31 files changed, 1738 insertions(+), 888 deletions(-) create mode 100644 engine/Cargo.lock create mode 100644 engine/Cargo.toml create mode 100644 engine/src/component.rs create mode 100644 engine/src/engine.rs rename {src => engine/src}/input.rs (98%) create mode 100644 engine/src/lib.rs create mode 100644 engine/src/output.rs create mode 100644 engine/src/tui.rs rename src/{tui/tui_border.rs => border.rs} (99%) create mode 100644 src/engine/render.rs delete mode 100644 src/output.rs rename src/{tui/tui_style.rs => style.rs} (100%) rename src/{tui/tui_theme.rs => theme.rs} (100%) delete mode 100644 src/tui/tui_input.rs delete mode 100644 src/tui/tui_output.rs diff --git a/Cargo.lock b/Cargo.lock index 012efc81..45e88ed8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1276,11 +1276,16 @@ dependencies = [ "rand", "ratatui", "symphonia", + "tek_engine", "toml", "uuid", "wavers", ] +[[package]] +name = "tek_engine" +version = "0.2.0" + [[package]] name = "thiserror" version = "1.0.69" diff --git a/Cargo.toml b/Cargo.toml index c1285f0d..ae5af5fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,12 +4,11 @@ edition = "2021" version = "0.2.0" [dependencies] +tek_engine = { path = "./engine" } atomic_float = "1.0.0" backtrace = "0.3.72" -better-panic = "0.3.0" clap = { version = "4.5.4", features = [ "derive" ] } clojure-reader = "0.1.0" -crossterm = "0.27" jack = { path = "./rust-jack" } livi = "0.7.4" midly = "0.5" @@ -17,7 +16,6 @@ once_cell = "1.19.0" palette = { version = "0.7.6", features = [ "random" ] } quanta = "0.12.3" rand = "0.8.5" -ratatui = { version = "0.26.3", features = [ "unstable-widget-ref", "underline-color" ] } symphonia = { version = "0.5.4", features = [ "all" ] } toml = "0.8.12" uuid = { version = "1.10.0", features = [ "v4" ] } diff --git a/engine/Cargo.lock b/engine/Cargo.lock new file mode 100644 index 00000000..4c3144ab --- /dev/null +++ b/engine/Cargo.lock @@ -0,0 +1,666 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "backtrace" +version = "0.3.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "better-panic" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fa9e1d11a268684cbd90ed36370d7577afb6c62d912ddff5c15fc34343e5036" +dependencies = [ + "backtrace", + "console", +] + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "castaway" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +dependencies = [ + "rustversion", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "compact_str" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "ryu", + "static_assertions", +] + +[[package]] +name = "console" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "windows-sys 0.59.0", +] + +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "object" +version = "0.36.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ratatui" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef" +dependencies = [ + "bitflags", + "cassowary", + "compact_str", + "crossterm", + "itertools 0.12.1", + "lru", + "paste", + "stability", + "strum", + "unicode-segmentation", + "unicode-truncate", + "unicode-width", +] + +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "stability" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d904e7009df136af5297832a3ace3370cd14ff1546a232f4f185036c2736fcac" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c786062daee0d6db1132800e623df74274a0a87322d8e183338e01b3d98d058" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tek_engine" +version = "0.2.0" +dependencies = [ + "better-panic", + "crossterm", + "ratatui", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-truncate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +dependencies = [ + "itertools 0.13.0", + "unicode-segmentation", + "unicode-width", +] + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/engine/Cargo.toml b/engine/Cargo.toml new file mode 100644 index 00000000..ab351dc5 --- /dev/null +++ b/engine/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "tek_engine" +edition = "2021" +version = "0.2.0" + +[dependencies] +crossterm = "0.27" +ratatui = { version = "0.26.3", features = [ "unstable-widget-ref", "underline-color" ] } +better-panic = "0.3.0" diff --git a/engine/src/component.rs b/engine/src/component.rs new file mode 100644 index 00000000..3a9cba2b --- /dev/null +++ b/engine/src/component.rs @@ -0,0 +1,27 @@ +use crate::*; + +/// A UI component that can render itself as a [Render], and [Handle] input. +pub trait Component: Render + Handle {} + +/// Everything that implements [Render] and [Handle] is a [Component]. +impl + Handle> Component for C {} + +/// A component that can exit. +pub trait Exit: Send { + //fn exited (&self) -> bool; + //fn exit (&mut self); + //fn boxed (self) -> Box where Self: Sized + 'static { + //Box::new(self) + //} +} + +/// Marker trait for [Component]s that can [Exit]. +pub trait ExitableComponent: Exit + Component where E: Engine { + ///// Perform type erasure for collecting heterogeneous components. + //fn boxed (self) -> Box> where Self: Sized + 'static { + //Box::new(self) + //} +} + +/// All [Components]s that implement [Exit] implement [ExitableComponent]. +impl + Exit> ExitableComponent for C {} diff --git a/engine/src/engine.rs b/engine/src/engine.rs new file mode 100644 index 00000000..59f332a9 --- /dev/null +++ b/engine/src/engine.rs @@ -0,0 +1,80 @@ +use crate::*; +use std::fmt::{Debug, Display}; +use std::ops::{Add, Sub, Mul, Div}; + +/// Platform backend. +pub trait Engine: Send + Sync + Sized { + /// Input event type + type Input: Input; + /// Result of handling input + type Handled; + /// Render target + type Output: Output; + /// Unit of length + type Unit: Coordinate; + /// Rectangle without offset + type Size: Size + From<[Self::Unit;2]> + Debug + Copy; + /// Rectangle with offset + type Area: Area + From<[Self::Unit;4]> + Debug + Copy; + /// Prepare before run + fn setup (&mut self) -> Usually<()> { Ok(()) } + /// True if done + fn exited (&self) -> bool; + /// Clean up after run + fn teardown (&mut self) -> Usually<()> { Ok(()) } +} + +/// A linear coordinate. +pub trait Coordinate: Send + Sync + Copy + + Add + + Sub + + Mul + + Div + + Ord + PartialEq + Eq + + Debug + Display + Default + + From + Into + + Into + + Into +{ + fn minus (self, other: Self) -> Self { + if self >= other { + self - other + } else { + 0.into() + } + } + fn zero () -> Self { + 0.into() + } +} + +pub trait Size { + fn x (&self) -> N; + fn y (&self) -> N; + #[inline] fn w (&self) -> N { self.x() } + #[inline] fn h (&self) -> N { self.y() } + #[inline] fn wh (&self) -> [N;2] { [self.x(), self.y()] } + #[inline] fn clip_w (&self, w: N) -> [N;2] { [self.w().min(w), self.h()] } + #[inline] fn clip_h (&self, h: N) -> [N;2] { [self.w(), self.h().min(h)] } + #[inline] fn expect_min (&self, w: N, h: N) -> Usually<&Self> { + if self.w() < w || self.h() < h { + Err(format!("min {w}x{h}").into()) + } else { + Ok(self) + } + } +} + +pub trait Area: Copy { + fn x (&self) -> N; + fn y (&self) -> N; + fn w (&self) -> N; + fn h (&self) -> N; + #[inline] fn expect_min (&self, w: N, h: N) -> Usually<&Self> { + if self.w() < w || self.h() < h { + Err(format!("min {w}x{h}").into()) + } else { + Ok(self) + } + } +} diff --git a/src/input.rs b/engine/src/input.rs similarity index 98% rename from src/input.rs rename to engine/src/input.rs index 9686a533..b1a3ed34 100644 --- a/src/input.rs +++ b/engine/src/input.rs @@ -1,4 +1,5 @@ use crate::*; +use std::sync::{Arc, Mutex, RwLock}; /// Current input state pub trait Input { @@ -12,11 +13,6 @@ pub trait Input { fn done (&self); } -/// Handle input -pub trait Handle: Send + Sync { - fn handle (&mut self, context: &E::Input) -> Perhaps; -} - #[macro_export] macro_rules! handle { (<$E:ty>|$self:ident:$Struct:ty,$input:ident|$handler:expr) => { impl Handle<$E> for $Struct { @@ -27,6 +23,11 @@ pub trait Handle: Send + Sync { } } +/// Handle input +pub trait Handle: Send + Sync { + fn handle (&mut self, context: &E::Input) -> Perhaps; +} + impl> Handle for &mut H { fn handle (&mut self, context: &E::Input) -> Perhaps { (*self).handle(context) diff --git a/engine/src/lib.rs b/engine/src/lib.rs new file mode 100644 index 00000000..c4078b54 --- /dev/null +++ b/engine/src/lib.rs @@ -0,0 +1,62 @@ +mod component; pub use self::component::*; +mod engine; pub use self::engine::*; +mod input; pub use self::input::*; +mod output; pub use self::output::*; +mod tui; pub use self::tui::*; + +pub use std::error::Error; + +/// Standard result type. +pub type Usually = Result>; + +/// Standard optional result type. +pub type Perhaps = Result, Box>; + +#[cfg(test)] #[test] fn test_stub_engine () -> Usually<()> { + struct TestEngine(bool); + struct TestInput(bool); + struct TestOutput([u16;4]); + enum TestEvent { Test1 } + impl Engine for TestEngine { + type Input = TestInput; + type Handled = (); + type Output = TestOutput; + type Unit = u16; + type Size = [u16;2]; + type Area = [u16;4]; + fn exited (&self) -> bool { + self.0 + } + } + impl Input for TestInput { + type Event = TestEvent; + fn event (&self) -> &Self::Event { + &TestEvent::Test1 + } + fn is_done (&self) -> bool { + self.0 + } + fn done (&self) {} + } + impl Output for TestOutput { + fn area (&self) -> [u16;4] { + self.0 + } + fn area_mut (&mut self) -> &mut [u16;4] { + &mut self.0 + } + fn render_in (&mut self, _: [u16;4], _: &dyn Render) -> Usually<()> { + Ok(()) + } + } + impl Render for String { + fn min_size (&self, _: [u16;2]) -> Perhaps<[u16;2]> { + Ok(Some([self.len() as u16, 1])) + } + } + Ok(()) +} + +#[cfg(test)] #[test] fn test_tui_engine () -> Usually<()> { + Ok(()) +} diff --git a/engine/src/output.rs b/engine/src/output.rs new file mode 100644 index 00000000..c50769bb --- /dev/null +++ b/engine/src/output.rs @@ -0,0 +1,272 @@ +use crate::*; +use std::sync::{Arc, Mutex, RwLock}; + +/// Rendering target +pub trait Output { + /// Current output area + fn area (&self) -> E::Area; + /// Mutable pointer to area + fn area_mut (&mut self) -> &mut E::Area; + /// Render widget in area + fn render_in (&mut self, area: E::Area, widget: &dyn Render) -> Usually<()>; +} + +/// Write content to output buffer. +pub trait Render: Send + Sync { + /// Minimum size to use + fn min_size (&self, _: E::Size) -> Perhaps { + Ok(None) + } + /// Draw to output render target + fn render (&self, _: &mut E::Output) -> Usually<()> { + Ok(()) + } +} + +impl Render for &dyn Render {} +impl Render for &mut dyn Render {} +impl Render for Box> {} +impl> Render for &R {} +impl> Render for &mut R {} +impl> Render for Option {} +impl> Render for Arc {} +impl> Render for Mutex {} +impl> Render for RwLock {} + +/// Something that can be represented by a renderable component. +pub trait Content: Render + Send + Sync { + fn content (&self) -> Option> where (): Render { + None::<()> + } +} + +impl Content for &dyn Render {} +impl Content for &mut dyn Render {} +impl Content for Box> {} +impl> Content for &C {} +impl> Content for &mut C {} +impl> Content for Option {} +impl> Content for Arc {} +impl> Content for Mutex {} +impl> Content for RwLock {} + +/**** + + +impl + Send + Sync> Content for R {} + +//impl> Content for R { + //fn content (&self) -> Option> { + //Some(self) + //} +//} + +/// All implementors of [Content] can be [Render]ed. +impl> Render for C { + /// Minimum size to use + fn min_size (&self, to: E::Size) -> Perhaps { + self.content().map(|content|content.min_size(to)) + .unwrap_or(Ok(None)) + } + /// Draw to output render target + fn render (&self, to: &mut E::Output) -> Usually<()> { + self.content().map(|content|content.render(to)) + .unwrap_or(Ok(())) + } +} + +//impl> Content for &C { + //fn content (&self) -> Option> { + //Some(self) + //} +//} + +//impl> Content for Option { + //fn content (&self) -> Option> { + //Some(self) + //} +//} + + +/// Define custom content for a struct. +#[macro_export] macro_rules! render { + + // Implement for all engines + (|$self:ident:$Struct:ident$(<$($L:lifetime),*E$(,$T:ident$(:$U:path)?)*$(,)?>)?|$cb:expr) => { + impl Content for $Struct $(<$($L,)* E, $($T),*>)? { + fn content (&$self) -> Option> { + Some($cb) + } + } + }; + + // Implement for a specific engine + (<$E:ty>|$self:ident:$Struct:ident$(< + $($($L:lifetime),+)? + $($($T:ident$(:$U:path)?),+)? + >)?|$cb:expr) => { + impl $(< + $($($L),+)? + $($($T$(:$U)?),+)? + >)? Content<$E> for $Struct $(< + $($($L),+)? + $($($T),+)? + >)? { + fn content (&$self) -> Option> { + Some($cb) + } + } + } + +} + +impl> Render for &R { + fn min_size (&self, to: E::Size) -> Perhaps { + (*self).min_size(to) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + (*self).render(to) + } +} + +impl> Render for Option { + fn min_size (&self, to: E::Size) -> Perhaps { + self.map(|content|content.min_size(to)) + .unwrap_or(Ok(None)) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + self.map(|content|content.render(to)) + .unwrap_or(Ok(())) + } +} + +//impl Render for &dyn Render { + //fn min_size (&self, to: E::Size) -> Perhaps { + //(*self).min_size(to) + //} + //fn render (&self, to: &mut E::Output) -> Usually<()> { + //(*self).render(to) + //} +//} + +//impl Render for &mut dyn Render { + //fn min_size (&self, to: E::Size) -> Perhaps { + //(*self).min_size(to) + //} + //fn render (&self, to: &mut E::Output) -> Usually<()> { + //(*self).render(to) + //} +//} + +//impl Render for Box + '_> { + //fn min_size (&self, to: E::Size) -> Perhaps { + //(**self).min_size(to) + //} + //fn render (&self, to: &mut E::Output) -> Usually<()> { + //(**self).render(to) + //} +//} + +//impl> Render for Arc { + //fn min_size (&self, to: E::Size) -> Perhaps { + //self.as_ref().min_size(to) + //} + //fn render (&self, to: &mut E::Output) -> Usually<()> { + //self.as_ref().render(to) + //} +//} + +//impl> Render for Mutex { + //fn min_size (&self, to: E::Size) -> Perhaps { + //self.lock().unwrap().min_size(to) + //} + //fn render (&self, to: &mut E::Output) -> Usually<()> { + //self.lock().unwrap().render(to) + //} +//} + +//impl> Render for RwLock { + //fn min_size (&self, to: E::Size) -> Perhaps { + //self.read().unwrap().min_size(to) + //} + //fn render (&self, to: &mut E::Output) -> Usually<()> { + //self.read().unwrap().render(to) + //} +//} + +//impl> Render for Option { + //fn min_size (&self, to: E::Size) -> Perhaps { + //Ok(self.as_ref().map(|widget|widget.min_size(to)).transpose()?.flatten()) + //} + //fn render (&self, to: &mut E::Output) -> Usually<()> { + //self.as_ref().map(|widget|widget.render(to)).unwrap_or(Ok(())) + //} +//} + +/// Cast to dynamic pointer +pub fn widget (w: &T) -> &dyn Render + where E: Engine, T: Render +{ + w as &dyn Render +} + +/// Ad-hoc widget with custom rendering. +pub fn render (render: F) -> impl Render + where E: Engine, F: Fn(&mut E::Output)->Usually<()>+Send+Sync +{ + Widget::new(|_|Ok(Some([0.into(),0.into()].into())), render) +} + +/// A custom [Render] defined by passing layout and render closures in place. +pub struct Widget< + E: Engine, + L: Send + Sync + Fn(E::Size)->Perhaps, + R: Send + Sync + Fn(&mut E::Output)->Usually<()> +>(L, R, PhantomData); + +impl< + E: Engine, + L: Send + Sync + Fn(E::Size)->Perhaps, + R: Send + Sync + Fn(&mut E::Output)->Usually<()> +> Widget { + pub fn new (layout: L, render: R) -> Self { + Self(layout, render, Default::default()) + } +} + +impl< + E: Engine, + L: Send + Sync + Fn(E::Size)->Perhaps, + R: Send + Sync + Fn(&mut E::Output)->Usually<()> +> Render for Widget { + fn min_size (&self, to: E::Size) -> Perhaps { + self.0(to) + } + fn render (&self, to: &mut E::Output) -> Usually<()> { + self.1(to) + } +} + +/// Has static methods for conditional rendering, +/// in unary and binary forms. +pub struct Cond; + +impl Cond { + /// Render `item` when `cond` is true. + pub fn when > (cond: bool, item: A) -> When { + When(cond, item, Default::default()) + } + /// Render `item` if `cond` is true, otherwise render `other`. + pub fn either , B: Render> (cond: bool, item: A, other: B) -> Either { + Either(cond, item, other, Default::default()) + } +} + +/// Renders `self.1` when `self.0` is true. +pub struct When>(bool, A, PhantomData); + +/// Renders `self.1` when `self.0` is true, otherwise renders `self.2` +pub struct Either, B: Render>(bool, A, B, PhantomData); + + +**/ diff --git a/engine/src/tui.rs b/engine/src/tui.rs new file mode 100644 index 00000000..7d6aa090 --- /dev/null +++ b/engine/src/tui.rs @@ -0,0 +1,440 @@ +use crate::*; +use std::sync::{Arc, RwLock, atomic::{AtomicBool, Ordering::*}}; +use std::io::{stdout, Stdout}; +use std::time::Duration; +use std::thread::{spawn, JoinHandle}; + +pub use ::better_panic; +pub(crate) use better_panic::{Settings, Verbosity}; + +pub use ::crossterm; +pub(crate) use crossterm::{ + ExecutableCommand, + event::*, + terminal::{EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode}, + event::{KeyCode, KeyModifiers, KeyEvent, KeyEventKind, KeyEventState}, +}; + +pub use ::ratatui; +pub(crate) use ratatui::{ + prelude::{Color, Style, Buffer}, + style::Modifier, + backend::{Backend, CrosstermBackend, ClearType}, + buffer::Cell +}; + +impl Coordinate for u16 {} + +impl Size for [u16;2] { + fn x (&self) -> u16 { self[0] } + fn y (&self) -> u16 { self[1] } +} + +impl Area for [u16;4] { + fn x (&self) -> u16 { self[0] } + fn y (&self) -> u16 { self[1] } + fn w (&self) -> u16 { self[2] } + fn h (&self) -> u16 { self[3] } +} + +pub struct Tui { + pub exited: Arc, + pub buffer: Buffer, + pub backend: CrosstermBackend, + pub area: [u16;4], // FIXME auto resize +} + +impl Engine for Tui { + type Unit = u16; + type Size = [Self::Unit;2]; + type Area = [Self::Unit;4]; + type Input = TuiInput; + type Handled = bool; + type Output = TuiOutput; + fn exited (&self) -> bool { + self.exited.fetch_and(true, Relaxed) + } + fn setup (&mut self) -> Usually<()> { + let better_panic_handler = Settings::auto().verbosity(Verbosity::Full).create_panic_handler(); + std::panic::set_hook(Box::new(move |info: &std::panic::PanicHookInfo|{ + stdout().execute(LeaveAlternateScreen).unwrap(); + CrosstermBackend::new(stdout()).show_cursor().unwrap(); + disable_raw_mode().unwrap(); + better_panic_handler(info); + })); + stdout().execute(EnterAlternateScreen)?; + self.backend.hide_cursor()?; + enable_raw_mode().map_err(Into::into) + } + fn teardown (&mut self) -> Usually<()> { + stdout().execute(LeaveAlternateScreen)?; + self.backend.show_cursor()?; + disable_raw_mode().map_err(Into::into) + } +} + +impl Tui { + /// Run the main loop. + pub fn run + Sized + 'static> ( + state: Arc> + ) -> Usually>> { + let backend = CrosstermBackend::new(stdout()); + let area = backend.size()?; + let engine = Self { + exited: Arc::new(AtomicBool::new(false)), + buffer: Buffer::empty(area), + area: [area.x, area.y, area.width, area.height], + backend, + }; + let engine = Arc::new(RwLock::new(engine)); + let _input_thread = Self::spawn_input_thread(&engine, &state, Duration::from_millis(100)); + engine.write().unwrap().setup()?; + let render_thread = Self::spawn_render_thread(&engine, &state, Duration::from_millis(10)); + render_thread.join().expect("main thread failed"); + engine.write().unwrap().teardown()?; + Ok(state) + } + fn spawn_input_thread + Sized + 'static> ( + engine: &Arc>, state: &Arc>, poll: Duration + ) -> JoinHandle<()> { + let exited = engine.read().unwrap().exited.clone(); + let state = state.clone(); + spawn(move || loop { + if exited.fetch_and(true, Relaxed) { + break + } + if ::crossterm::event::poll(poll).is_ok() { + let event = TuiEvent::Input(::crossterm::event::read().unwrap()); + match event { + key_pat!(Ctrl-KeyCode::Char('c')) => { + exited.store(true, Relaxed); + }, + _ => { + let exited = exited.clone(); + if let Err(e) = state.write().unwrap().handle(&TuiInput { event, exited }) { + panic!("{e}") + } + } + } + } + }) + } + fn spawn_render_thread + Sized + 'static> ( + engine: &Arc>, state: &Arc>, sleep: Duration + ) -> JoinHandle<()> { + let exited = engine.read().unwrap().exited.clone(); + let engine = engine.clone(); + let state = state.clone(); + let size = engine.read().unwrap().backend.size().expect("get size failed"); + let mut buffer = Buffer::empty(size); + spawn(move || loop { + if exited.fetch_and(true, Relaxed) { + break + } + let size = engine.read().unwrap().backend.size() + .expect("get size failed"); + if let Ok(state) = state.try_read() { + if buffer.area != size { + engine.write().unwrap().backend.clear_region(ClearType::All) + .expect("clear failed"); + buffer.resize(size); + buffer.reset(); + } + let mut output = TuiOutput { + buffer, + area: [size.x, size.y, size.width, size.height] + }; + state.render(&mut output).expect("render failed"); + buffer = engine.write().unwrap().flip(output.buffer, size); + } + std::thread::sleep(sleep); + }) + } + fn flip (&mut self, mut buffer: Buffer, size: ratatui::prelude::Rect) -> Buffer { + if self.buffer.area != size { + self.backend.clear_region(ClearType::All).unwrap(); + self.buffer.resize(size); + self.buffer.reset(); + } + let updates = self.buffer.diff(&buffer); + self.backend.draw(updates.into_iter()).expect("failed to render"); + self.backend.flush().expect("failed to flush output buffer"); + std::mem::swap(&mut self.buffer, &mut buffer); + buffer.reset(); + buffer + } +} + +pub struct TuiInput { + pub(crate) exited: Arc, + pub(crate) event: TuiEvent, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum TuiEvent { + /// Terminal input + Input(Event), +} + +impl Input for TuiInput { + type Event = TuiEvent; + fn event (&self) -> &TuiEvent { + &self.event + } + fn is_done (&self) -> bool { + self.exited.fetch_and(true, Relaxed) + } + fn done (&self) { + self.exited.store(true, Relaxed); + } +} + +#[macro_export] macro_rules! key_pat { + (Ctrl-Alt-$code:pat) => { key_event_pat!($code, KeyModifiers::CONTROL | KeyModifiers::ALT) }; + (Ctrl-$code:pat) => { key_event_pat!($code, KeyModifiers::CONTROL) }; + (Alt-$code:pat) => { key_event_pat!($code, KeyModifiers::ALT) }; + (Shift-$code:pat) => { key_event_pat!($code, KeyModifiers::SHIFT) }; + ($code:pat) => { TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { + code: $code, + modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE + })) }; +} + +#[macro_export] macro_rules! key_event_pat { + ($code:pat) => { + TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { + code: $code, + modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE + })) + }; + ($code:pat, $modifiers: pat) => { + TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { + code: $code, + modifiers: $modifiers, + kind: KeyEventKind::Press, + state: KeyEventState::NONE + })) + }; +} + +#[macro_export] macro_rules! kexp { + (Ctrl-Alt-$code:ident) => { key_event_expr!($code, KeyModifiers::from_bits(0b0000_0110).unwrap()) }; + (Ctrl-$code:ident) => { key_event_expr!($code, KeyModifiers::CONTROL) }; + (Alt-$code:ident) => { key_event_expr!($code, KeyModifiers::ALT) }; + (Shift-$code:ident) => { key_event_expr!($code, KeyModifiers::SHIFT) }; + ($code:ident) => { key_event_expr!($code) }; + ($code:expr) => { key_event_expr!($code) }; +} + +#[macro_export] macro_rules! key_expr { + (Ctrl-Alt-$code:ident) => { key_event_expr!($code, KeyModifiers::CONTROL | KeyModifiers::ALT) }; + (Ctrl-$code:ident) => { key_event_expr!($code, KeyModifiers::CONTROL) }; + (Alt-$code:ident) => { key_event_expr!($code, KeyModifiers::ALT) }; + (Shift-$code:ident) => { key_event_expr!($code, KeyModifiers::SHIFT) }; + ($code:ident) => { key_event_expr!($code) }; +} + +#[macro_export] macro_rules! key_event_expr { + ($code:expr, $modifiers: expr) => { + TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { + code: $code, + modifiers: $modifiers, + kind: KeyEventKind::Press, + state: KeyEventState::NONE + })) + }; + ($code:expr) => { + TuiEvent::Input(crossterm::event::Event::Key(KeyEvent { + code: $code, + modifiers: KeyModifiers::NONE, + kind: KeyEventKind::Press, + state: KeyEventState::NONE + })) + }; +} + +/* +/// Define a key +pub const fn key (code: KeyCode) -> KeyEvent { + let modifiers = KeyModifiers::NONE; + let kind = KeyEventKind::Press; + let state = KeyEventState::NONE; + KeyEvent { code, modifiers, kind, state } +} + +/// Add Ctrl modifier to key +pub const fn ctrl (key: KeyEvent) -> KeyEvent { + KeyEvent { modifiers: key.modifiers.union(KeyModifiers::CONTROL), ..key } +} + +/// Add Alt modifier to key +pub const fn alt (key: KeyEvent) -> KeyEvent { + KeyEvent { modifiers: key.modifiers.union(KeyModifiers::ALT), ..key } +} + +/// Add Shift modifier to key +pub const fn shift (key: KeyEvent) -> KeyEvent { + KeyEvent { modifiers: key.modifiers.union(KeyModifiers::SHIFT), ..key } +} + +/// Define a keymap +#[macro_export] macro_rules! keymap { + ($T:ty { $([$k:ident $(($char:literal))?, $m:ident, $n: literal, $d: literal, $f: expr]),* $(,)? }) => { + &[ + $((KeyCode::$k $(($char))?, KeyModifiers::$m, $n, $d, &$f as KeyHandler<$T>)),* + ] as &'static [KeyBinding<$T>] + } +} + +*/ + +/* + +impl TuiInput { + // TODO remove + pub fn handle_keymap (&self, state: &mut T, keymap: &KeyMap) -> Usually { + match self.event() { + TuiEvent::Input(Key(event)) => { + for (code, modifiers, _, _, command) in keymap.iter() { + if *code == event.code && modifiers.bits() == event.modifiers.bits() { + return command(state) + } + } + }, + _ => {} + }; + Ok(false) + } +} + +pub type KeyHandler = &'static dyn Fn(&mut T)->Usually; + +pub type KeyBinding = (KeyCode, KeyModifiers, &'static str, &'static str, KeyHandler); + +pub type KeyMap = [KeyBinding]; + +*/ + +pub struct TuiOutput { + pub buffer: Buffer, + pub area: [u16;4] +} + +impl Output for TuiOutput { + #[inline] fn area (&self) -> [u16;4] { self.area } + #[inline] fn area_mut (&mut self) -> &mut [u16;4] { &mut self.area } + #[inline] fn render_in (&mut self, area: [u16;4], widget: &dyn Render) -> Usually<()> { + let last = self.area(); + *self.area_mut() = area; + widget.render(self)?; + *self.area_mut() = last; + Ok(()) + } +} + +impl TuiOutput { + pub fn buffer_update (&mut self, area: [u16;4], callback: &impl Fn(&mut Cell, u16, u16)) { + buffer_update(&mut self.buffer, area, callback); + } + pub fn fill_bold (&mut self, area: [u16;4], on: bool) { + if on { + self.buffer_update(area, &|cell,_,_|cell.modifier.insert(Modifier::BOLD)) + } else { + self.buffer_update(area, &|cell,_,_|cell.modifier.remove(Modifier::BOLD)) + } + } + pub fn fill_bg (&mut self, area: [u16;4], color: Color) { + self.buffer_update(area, &|cell,_,_|{cell.set_bg(color);}) + } + pub fn fill_fg (&mut self, area: [u16;4], color: Color) { + self.buffer_update(area, &|cell,_,_|{cell.set_fg(color);}) + } + pub fn fill_ul (&mut self, area: [u16;4], color: Color) { + self.buffer_update(area, &|cell,_,_|{ + cell.modifier = ratatui::prelude::Modifier::UNDERLINED; + cell.underline_color = color; + }) + } + pub fn fill_char (&mut self, area: [u16;4], c: char) { + self.buffer_update(area, &|cell,_,_|{cell.set_char(c);}) + } + pub fn make_dim (&mut self) { + for cell in self.buffer.content.iter_mut() { + cell.bg = ratatui::style::Color::Rgb(30,30,30); + cell.fg = ratatui::style::Color::Rgb(100,100,100); + cell.modifier = ratatui::style::Modifier::DIM; + } + } + pub fn blit ( + &mut self, text: &impl AsRef, x: u16, y: u16, style: Option