diff --git a/.old/demo.rs.old b/.old/demo.rs.old new file mode 100644 index 00000000..6b205580 --- /dev/null +++ b/.old/demo.rs.old @@ -0,0 +1,112 @@ +use tek::*; + +fn main () -> Usually<()> { + Tui::run(Arc::new(RwLock::new(Demo::new())))?; + Ok(()) +} + +pub struct Demo { + index: usize, + items: Vec>> +} + +impl Demo { + fn new () -> Self { + Self { + index: 0, + items: vec![] + } + } +} + +impl Content for Demo { + type Engine = Tui; + fn content (&self) -> dyn Render { + let border_style = Style::default().fg(Color::Rgb(0,0,0)); + Align::Center(Layers::new(move|add|{ + + add(&Background(Color::Rgb(0,128,128)))?; + + 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(&Margin::XY(2, 1, "..."))?; + Ok(()) + }).debug())?; + + add(&Layers::new(|add|{ + add(&Background(Color::Rgb(128,64,0)))?; + add(&Border(Lozenge(border_style)))?; + add(&Margin::XY(4, 2, "---"))?; + Ok(()) + }).debug())?; + + add(&Layers::new(|add|{ + add(&Background(Color::Rgb(96,64,0)))?; + add(&Border(SquareBold(border_style)))?; + add(&Margin::XY(6, 3, "~~~"))?; + Ok(()) + }).debug())?; + + Ok(()) + })).debug())?; + + Ok(()) + + })) + //Align::Center(Margin::X(1, Layers::new(|add|{ + //add(&Background(Color::Rgb(128,0,0)))?; + //add(&Stack::down(|add|{ + //add(&Margin::Y(1, Layers::new(|add|{ + //add(&Background(Color::Rgb(0,128,0)))?; + //add(&Align::Center("12345"))?; + //add(&Align::Center("FOO")) + //})))?; + //add(&Margin::XY(1, 1, Layers::new(|add|{ + //add(&Align::Center("1234567"))?; + //add(&Align::Center("BAR"))?; + //add(&Background(Color::Rgb(0,0,128))) + //}))) + //})) + //}))) + + //Align::Y(Layers::new(|add|{ + //add(&Background(Color::Rgb(128,0,0)))?; + //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(&Margin::XY(1, 1, Layers::new(|add|{ + //add(&Align::Center("1234567"))?; + //add(&Align::Center("BAR"))?; + //add(&Background(Color::Rgb(0,0,128))) + //})))?; + //Ok(()) + //}))))) + //})) + } +} + +impl Handle for Demo { + fn handle (&mut self, from: &TuiIn) -> Perhaps { + use KeyCode::{PageUp, PageDown}; + match from.event() { + kexp!(PageUp) => { + self.index = (self.index + 1) % self.items.len(); + }, + kexp!(PageDown) => { + self.index = if self.index > 1 { + self.index - 1 + } else { + self.items.len() - 1 + }; + }, + _ => return Ok(None) + } + Ok(Some(true)) + } +} diff --git a/.scratch.rs b/.old/scratch.rs similarity index 100% rename from .scratch.rs rename to .old/scratch.rs diff --git a/Cargo.lock b/Cargo.lock index 68f62568..69449423 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -35,15 +35,15 @@ checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.2.16", + "getrandom 0.3.2", "once_cell", "version_check", - "zerocopy 0.7.35", + "zerocopy", ] [[package]] @@ -176,9 +176,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.74" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", "cfg-if", @@ -968,7 +968,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.9.0", "libc", - "redox_syscall 0.5.11", + "redox_syscall 0.5.12", ] [[package]] @@ -1466,7 +1466,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.11", + "redox_syscall 0.5.12", "smallvec", "windows-targets 0.52.6", ] @@ -1578,7 +1578,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ - "zerocopy 0.8.25", + "zerocopy", ] [[package]] @@ -1790,9 +1790,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" dependencies = [ "bitflags 2.9.0", ] @@ -2301,6 +2301,7 @@ dependencies = [ "symphonia", "tek_engine", "tengri", + "tengri_proc", "uuid", "wavers", "winit", @@ -2367,6 +2368,7 @@ dependencies = [ name = "tengri_proc" version = "0.13.0" dependencies = [ + "heck", "proc-macro2", "quote", "syn", @@ -3132,9 +3134,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9fb597c990f03753e08d3c29efbfcf2019a003b4bf4ba19225c158e1549f0f3" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" dependencies = [ "memchr", ] @@ -3205,33 +3207,13 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "zerocopy-derive 0.7.35", -] - [[package]] name = "zerocopy" version = "0.8.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" dependencies = [ - "zerocopy-derive 0.8.25", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "zerocopy-derive", ] [[package]] diff --git a/README.md b/README.md index 6eb7d6b9..65fb7107 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,8 @@ or [**matrix** `@unspeaker:matrix.org`](https://matrix.to/#/@unspeaker:matrix.or * [ ] `z`: zoom lock/unlock * [ ] `del`: delete * Global: - * [ ] esc: options menu - * [ ] f1: help/command list + * [x] esc: options menu + * [x] f1: help/command list * [ ] f2: rename * [ ] f6: save * [ ] f9: load @@ -68,11 +68,11 @@ paru -S tek requires docker. ``` -git clone https://codeberg.org/unspeaker/tek # obtain source -cd tek # enter directory -cat bin/release-glibc.sh # preview build script -sudo bin/release-glibc.sh # run build script -sudo cp bin/tek /usr/local/bin/tek # install +git clone --recursive -b 0.2 https://codeberg.org/unspeaker/tek +cd tek # enter directory +cat bin/release-glibc.sh # preview build script +sudo bin/release-glibc.sh # run build script +sudo cp bin/tek /usr/local/bin/tek # install ``` ## design goals diff --git a/crates/engine/examples/midi-import.rs b/crates/app/examples/midi_import.rs similarity index 97% rename from crates/engine/examples/midi-import.rs rename to crates/app/examples/midi_import.rs index f27beb2b..90b848a1 100644 --- a/crates/engine/examples/midi-import.rs +++ b/crates/app/examples/midi_import.rs @@ -1,4 +1,4 @@ -use tek_midi::*; +use tek::*; use tengri::input::*; use std::sync::*; struct ExampleClips(Arc>>>>); diff --git a/crates/app/src/api.rs b/crates/app/src/api.rs index 4f7504bf..aa33ef34 100644 --- a/crates/app/src/api.rs +++ b/crates/app/src/api.rs @@ -17,591 +17,804 @@ handle!(TuiIn: |self: Tek, input|Ok(if let Some(command) = self.config.keys.comm None })); -expose!([self: Tek] - ([bool] - (":mode-editor" self.is_editing()) - (":mode-message" matches!(self.dialog, Some(Dialog::Message(..)))) - (":mode-device-add" matches!(self.dialog, Some(Dialog::Device(..)))) - (":mode-clip" !self.is_editing() && self.selected.is_clip()) - (":mode-track" !self.is_editing() && self.selected.is_track()) - (":mode-scene" !self.is_editing() && self.selected.is_scene()) - (":mode-mix" !self.is_editing() && self.selected.is_mix()) - (":mode-pool-import" matches!( - self.pool.as_ref().map(|p|p.mode.as_ref()).flatten(), - Some(PoolMode::Import(..)))) - (":mode-pool-export" matches!( - self.pool.as_ref().map(|p|p.mode.as_ref()).flatten(), - Some(PoolMode::Export(..)))) - (":mode-pool-rename" matches!( - self.pool.as_ref().map(|p|p.mode.as_ref()).flatten(), - Some(PoolMode::Rename(..)))) - (":mode-pool-length" matches!( - self.pool.as_ref().map(|p|p.mode.as_ref()).flatten(), - Some(PoolMode::Length(..))))) - ([isize]) - ([Color]) - ([Arc>]) - ([u7] - (":pitch" (self.editor().map(|e|e.note_pos()).unwrap() as u8).into())) - ([u16] - (":w-sidebar" self.w_sidebar())) - ([usize] - (":scene-last" self.scenes.len()) - (":track-last" self.tracks.len()) - (":device-kind" if let Some(Dialog::Device(index)) = self.dialog { +#[tengri_proc::expose] impl Tek { + fn focus_editor (&self) -> bool { + self.is_editing() + } + fn focus_message (&self) -> bool { + matches!(self.dialog, Some(Dialog::Message(..))) + } + fn focus_device_add (&self) -> bool { + matches!(self.dialog, Some(Dialog::Device(..))) + } + fn focus_clip (&self) -> bool { + !self.is_editing() && self.selected.is_clip() + } + fn focus_track (&self) -> bool { + !self.is_editing() && self.selected.is_track() + } + fn focus_scene (&self) -> bool { + !self.is_editing() && self.selected.is_scene() + } + fn focus_mix (&self) -> bool { + !self.is_editing() && self.selected.is_mix() + } + fn focus_pool_import (&self) -> bool { + matches!(self.pool.as_ref().map(|p|p.mode.as_ref()).flatten(), Some(PoolMode::Import(..))) + } + fn focus_pool_export (&self) -> bool { + matches!(self.pool.as_ref().map(|p|p.mode.as_ref()).flatten(), Some(PoolMode::Export(..))) + } + fn focus_pool_rename (&self) -> bool { + matches!(self.pool.as_ref().map(|p|p.mode.as_ref()).flatten(), Some(PoolMode::Rename(..))) + } + fn focus_pool_length (&self) -> bool { + matches!(self.pool.as_ref().map(|p|p.mode.as_ref()).flatten(), Some(PoolMode::Length(..))) + } + fn editor_pitch (&self) -> Option { + Some((self.editor().map(|e|e.note_pos()).unwrap() as u8).into()) + } + /// Width of display + pub(crate) fn w (&self) -> u16 { + self.size.w() as u16 + } + /// Width allocated for sidebar. + pub(crate) fn w_sidebar (&self) -> u16 { + self.w() / if self.is_editing() { 16 } else { 8 } as u16 + } + /// Width taken by all tracks. + pub(crate) fn w_tracks (&self) -> u16 { + self.tracks_with_sizes().last().map(|(_, _, _, x)|x as u16).unwrap_or(0) + } + /// Width available to display tracks. + pub(crate) fn w_tracks_area (&self) -> u16 { + self.w().saturating_sub(2 * self.w_sidebar()) + } + /// Height of display + pub(crate) fn h (&self) -> u16 { + self.size.h() as u16 + } + /// Height available to display track headers. + pub(crate) fn h_tracks_area (&self) -> u16 { + 5 // FIXME + //self.h().saturating_sub(self.h_inputs() + self.h_outputs()) + } + /// Height available to display tracks. + pub(crate) fn h_scenes_area (&self) -> u16 { + //15 + self.h().saturating_sub( + self.h_inputs() + + self.h_outputs() + + self.h_devices() + + 13 // FIXME + ) + } + /// Height taken by all scenes. + pub(crate) fn h_scenes (&self) -> u16 { + self.scenes_with_sizes(self.is_editing(), Self::H_SCENE, Self::H_EDITOR).last() + .map(|(_, _, _, y)|y as u16).unwrap_or(0) + } + /// Height taken by all inputs. + pub(crate) fn h_inputs (&self) -> u16 { + self.inputs_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) + } + /// Height taken by all outputs. + pub(crate) fn h_outputs (&self) -> u16 { + self.outputs_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) + } + /// Height taken by visible device slots. + pub(crate) fn h_devices (&self) -> u16 { + 2 + //1 + self.devices_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) + } + fn scene_count (&self) -> usize { + self.scenes.len() + } + fn scene_selected (&self) -> Option { + self.selected.scene() + } + fn scene_select_next (&self) -> Selection { + self.selected.scene_next(self.scenes.len()) + } + fn scene_select_prev (&self) -> Selection { + self.selected.scene_prev() + } + fn track_count (&self) -> usize { + self.tracks.len() + } + fn track_selected (&self) -> Option { + self.selected.track() + } + fn track_select_next (&self) -> Selection { + self.selected.track_next(self.tracks.len()) + } + fn track_select_prev (&self) -> Selection { + self.selected.track_prev() + } + fn clip_selected (&self) -> Option>> { + match self.selected { + Selection::TrackClip { track, scene } => self.scenes[scene].clips[track].clone(), + _ => None + } + } + fn device_kind (&self) -> usize { + if let Some(Dialog::Device(index)) = self.dialog { index } else { 0 - }) - (":device-kind-prev" if let Some(Dialog::Device(index)) = self.dialog { + } + } + fn device_kind_prev (&self) -> usize { + if let Some(Dialog::Device(index)) = self.dialog { index.overflowing_sub(1).0.min(self.device_kinds().len().saturating_sub(1)) } else { 0 - }) - (":device-kind-next" if let Some(Dialog::Device(index)) = self.dialog { + } + } + fn device_kind_next (&self) -> usize { + if let Some(Dialog::Device(index)) = self.dialog { (index + 1) % self.device_kinds().len() } else { 0 - })) - ([Option] - (":scene" self.selected.scene()) - (":track" self.selected.track())) - ([MaybeClip] - (":clip" match self.selected { - Selection::TrackClip { track, scene } => self.scenes[scene].clips[track].clone(), - _ => None - })) - ([Selection] - (":scene-next" self.selected.scene_next(self.scenes.len())) - (":scene-prev" self.selected.scene_prev()) - (":track-next" self.selected.track_next(self.tracks.len())) - (":track-prev" self.selected.track_prev()))); -expose!([self: MidiPool] - ([bool]) - ([PathBuf]) - ([Arc]) - ([MidiClip] - (":new-clip" self.new_clip()) - (":cloned-clip" self.cloned_clip())) - ([usize] - (":current" 0) - (":after" 0) - (":previous" 0) - (":next" 0)) - ([ItemColor] - (":random-color" ItemColor::random()))); -expose!([self: MidiEditor] - ([bool] - (":true" true) - (":false" false) - (":time-lock" self.time_lock().get()) - (":time-lock-toggle" !self.time_lock().get())) - ([usize] - (":note-length" self.note_len()) - (":note-pos" self.note_pos()) - (":note-pos-next" self.note_pos() + 1) - (":note-pos-prev" self.note_pos().saturating_sub(1)) - (":note-pos-next-octave" self.note_pos() + 12) - (":note-pos-prev-octave" self.note_pos().saturating_sub(12)) - (":note-len" self.note_len()) - (":note-len-next" self.note_len() + 1) - (":note-len-prev" self.note_len().saturating_sub(1)) - (":note-range" self.note_axis().get()) - (":note-range-prev" self.note_axis().get() + 1) - (":note-range-next" self.note_axis().get().saturating_sub(1)) - (":time-pos" self.time_pos()) - (":time-pos-next" self.time_pos() + self.time_zoom().get()) - (":time-pos-prev" self.time_pos().saturating_sub(self.time_zoom().get())) - (":time-zoom" self.time_zoom().get()) - (":time-zoom-next" self.time_zoom().get() + 1) - (":time-zoom-prev" self.time_zoom().get().saturating_sub(1).max(1)))); - -impose!([app: Tek] - (TekCommand: - ("menu" [] Some(Self::ToggleMenu)) - ("help" [] Some(Self::ToggleHelp)) - ("stop" [] Some(Self::StopAll)) - ("undo" [d: usize] Some(Self::History(-(d.unwrap_or(0) as isize)))) - ("redo" [d: usize] Some(Self::History(d.unwrap_or(0) as isize))) - ("zoom" [z: usize] Some(Self::Zoom(z))) - ("edit" [] Some(Self::Edit(None))) - ("edit" [c: bool] Some(Self::Edit(c))) - ("color" [] Some(Self::Color(ItemTheme::random()))) - ("color" [c: Color] Some(Self::Color(c.map(ItemTheme::from).expect("no color")))) - ("enqueue" [c: Arc>] Some(Self::Enqueue(c))) - ("launch" [] Some(Self::Launch)) - ("select" [t: Selection] Some(t.map(Self::Select).expect("no selection"))) - ("clock" [,..a] ns!(ClockCommand, app.clock(), a, Self::Clock)) - ("scene" [,..a] ns!(SceneCommand, app, a, Self::Scene)) - ("track" [,..a] ns!(TrackCommand, app, a, Self::Track)) - ("input" [,..a] ns!(InputCommand, app, a, Self::Input)) - ("output" [,..a] ns!(OutputCommand, app, a, Self::Output)) - ("clip" [,..a] ns!(ClipCommand, app, a, Self::Clip)) - ("device" [,..a] ns!(DeviceCommand, app, a, Self::Device)) - ("message" [,..a] ns!(MessageCommand, app, a, Self::Message)) - ("pool" [,..a] app.pool.as_ref().map(|p|ns!(PoolCommand, p, a, Self::Pool)).flatten()) - ("editor" [,..a] app.editor().map(|e|ns!(MidiEditCommand, e, a, Self::Editor)).flatten()) - ("sampler" [,..a] app.sampler().map(|s|ns!(SamplerCommand, s, a, Self::Sampler)).flatten()) - ("select" [t: usize, s: usize] Some(match (t.expect("no track"), s.expect("no scene")) { - (0, 0) => Self::Select(Selection::Mix), - (t, 0) => Self::Select(Selection::Track(t)), - (0, s) => Self::Select(Selection::Scene(s)), - (t, s) => Self::Select(Selection::TrackClip { track: t, scene: s }) }))) - - (ClipCommand: - ("edit" [a: MaybeClip] Some(Self::Edit(a.unwrap()))) - ("color" [a: usize, b: usize] Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemTheme::random()))) - ("enqueue" [a: usize, b: usize] Some(Self::Enqueue(a.unwrap(), b.unwrap()))) - ("get" [a: usize, b: usize] Some(Self::Get(a.unwrap(), b.unwrap()))) - ("loop" [a: usize, b: usize, c: bool] Some(Self::SetLoop(a.unwrap(), b.unwrap(), c.unwrap()))) - ("put" [a: usize, b: usize, c: MaybeClip] Some(Self::Put(a.unwrap(), b.unwrap(), c.unwrap()))) - ("delete" [a: usize, b: usize] Some(Self::Put(a.unwrap(), b.unwrap(), None)))) - - (InputCommand: - ("add" [] Some(Self::Add))) - - (OutputCommand: - ("add" [] Some(Self::Add))) - - (DeviceCommand: - ("picker" [] Some(Self::Picker)) - ("pick" [index: usize] Some(Self::Pick(index.unwrap()))) - ("add" [index: usize] Some(Self::Add(index.unwrap())))) - - (MessageCommand: - ("dismiss" [] Some(Self::Dismiss))) - - (SceneCommand: - ("add" [] Some(Self::Add)) - ("delete" [a: Option] Some(Self::Del(a.flatten().unwrap()))) - ("zoom" [a: usize] Some(Self::SetZoom(a.unwrap()))) - ("color" [a: usize] Some(Self::SetColor(a.unwrap(), ItemTheme::G[128]))) - ("enqueue" [a: usize] Some(Self::Enqueue(a.unwrap()))) - ("swap" [a: usize, b: usize] Some(Self::Swap(a.unwrap(), b.unwrap())))) - - (TrackCommand: - ("add" [] Some(Self::Add)) - ("size" [a: usize] Some(Self::SetSize(a.unwrap()))) - ("zoom" [a: usize] Some(Self::SetZoom(a.unwrap()))) - ("color" [a: usize] Some(Self::SetColor(a.unwrap(), ItemTheme::random()))) - ("delete" [a: Option] Some(Self::Del(a.flatten().unwrap()))) - ("stop" [a: usize] Some(Self::Stop(a.unwrap()))) - ("swap" [a: usize, b: usize] Some(Self::Swap(a.unwrap(), b.unwrap()))) - ("play" [] Some(Self::TogglePlay)) - ("solo" [] Some(Self::ToggleSolo)) - ("rec" [] Some(Self::ToggleRec)) - ("mon" [] Some(Self::ToggleMon)))); - -//#[tengri_proc::input(TuiIn)] -//impl Tek { - //#[tengri::command("sampler", TekCommand::Sampler)] - //fn cmd_sampler (&mut self, cmd: SamplerCommand) -> Perhaps { - //self.sampler_mut().map(|s|cmd.delegate(s, Self::Sampler)).transpose()?.flatten()) - //} - //#[tengri::command("scene", TekCommand::Scene)] - //fn cmd_scene (&mut self, cmd: SceneCommand) -> Perhaps { - //cmd.delegate(self, scene) - //} -//} - -defcom!([self, app: Tek] - - (TekCommand - (Sampler [cmd: SamplerCommand] app.sampler_mut().map(|s|cmd.delegate(s, Self::Sampler)).transpose()?.flatten()) - (Scene [cmd: SceneCommand] cmd.delegate(app, Self::Scene)?) - (Track [cmd: TrackCommand] cmd.delegate(app, Self::Track)?) - (Output [cmd: OutputCommand] cmd.delegate(app, Self::Output)?) - (Input [cmd: InputCommand] cmd.delegate(app, Self::Input)?) - (Clip [cmd: ClipCommand] cmd.delegate(app, Self::Clip)?) - (Clock [cmd: ClockCommand] cmd.delegate(app, Self::Clock)?) - (Device [cmd: DeviceCommand] cmd.delegate(app, Self::Device)?) - (Message [cmd: MessageCommand] cmd.delegate(app, Self::Message)?) - (Editor [cmd: MidiEditCommand] delegate_to_editor(app, cmd)?) - (Pool [cmd: PoolCommand] delegate_to_pool(app, cmd)?) - (ToggleHelp [] cmd!(app.toggle_dialog(Some(Dialog::Help)))) - (ToggleMenu [] cmd!(app.toggle_dialog(Some(Dialog::Menu)))) - (Color [p: ItemTheme] app.set_color(Some(p)).map(Self::Color)) - (Enqueue [c: MaybeClip] cmd_todo!("\n\rtodo: enqueue {c:?}")) - (History [d: isize] cmd_todo!("\n\rtodo: history {d:?}")) - (Zoom [z: Option] cmd_todo!("\n\rtodo: zoom {z:?}")) - (Edit [value: Option] cmd!(app.toggle_editor(value))) - (Launch [] cmd!(app.launch())) - (Select [s: Selection] cmd!(app.select(s))) - (StopAll [] cmd!(app.stop_all()))) - - (InputCommand - (Add [] cmd!(app.midi_in_add()?))) - - (OutputCommand - (Add [] cmd!(app.midi_out_add()?))) - - (DeviceCommand - (Picker [] cmd!(app.device_picker_show())) - (Pick [i: usize] cmd!(app.device_pick(i))) - (Add [i: usize] cmd!(app.device_add(i)))) - - (MessageCommand - (Dismiss [] cmd!(app.message_dismiss()))) - - (TrackCommand - (TogglePlay [] Some(Self::TogglePlay)) - (ToggleSolo [] Some(Self::ToggleSolo)) - (SetSize [t: usize] cmd_todo!("\n\rtodo: {self:?}")) - (SetZoom [z: usize] cmd_todo!("\n\rtodo: {self:?}")) - (Swap [a: usize, b: usize] cmd_todo!("\n\rtodo: {self:?}")) - (Del [index: usize] cmd!(app.track_del(index))) - (Stop [index: usize] cmd!(app.tracks[index].player.enqueue_next(None))) - (Add [] Some(Self::Del(app.track_add_focus()?))) - (SetColor [i: usize, c: ItemTheme] Some(Self::SetColor(i, app.track_set_color(i, c)))) - (ToggleRec [] { app.track_toggle_record(); Some(Self::ToggleRec) }) - (ToggleMon [] { app.track_toggle_monitor(); Some(Self::ToggleMon) })) - - (SceneCommand - (Swap [a: usize, b: usize] cmd_todo!("\n\rtodo: {self:?}")) - (SetSize [index: usize] cmd_todo!("\n\rtodo: {self:?}")) - (SetZoom [zoom: usize] cmd_todo!("\n\rtodo: {self:?}")) - (Enqueue [scene: usize] cmd!(app.scene_enqueue(scene))) - (Del [index: usize] cmd!(app.scene_del(index))) - (Add [] Some(Self::Del(app.scene_add_focus()?))) - (SetColor [i: usize, c: ItemTheme] Some(Self::SetColor(i, app.scene_set_color(i, c))))) - - (ClipCommand - (Get [a: usize, b: usize] cmd_todo!("\n\rtodo: clip: get: {a} {b}")) - (Edit [clip: MaybeClip] cmd_todo!("\n\rtodo: clip: edit: {clip:?}")) - (SetLoop [t: usize, s: usize, l: bool] cmd_todo!("\n\rtodo: {self:?}")) - (Put [t: usize, s: usize, c: MaybeClip] - Some(Self::Put(t, s, app.clip_put(t, s, c)))) - (Enqueue [t: usize, s: usize] - cmd!(app.tracks[t].player.enqueue_next(app.scenes[s].clips[t].as_ref()))) - (SetColor [t: usize, s: usize, c: ItemTheme] - app.clip_set_color(t, s, c).map(|o|Self::SetColor(t, s, o))))); - -fn delegate_to_editor (app: &mut Tek, cmd: MidiEditCommand) -> Perhaps { - Ok(app.editor.as_mut().map(|editor|cmd.delegate(editor, TekCommand::Editor)) - .transpose()? - .flatten()) + } + } } -fn delegate_to_pool (app: &mut Tek, cmd: PoolCommand) -> Perhaps { - Ok(if let Some(pool) = app.pool.as_mut() { - let undo = cmd.clone().delegate(pool, TekCommand::Pool)?; - if let Some(editor) = app.editor.as_mut() { - match cmd { +#[tengri_proc::expose] impl MidiPool { + fn clip_new (&self) -> MidiClip { + self.new_clip() + } + fn clip_cloned (&self) -> MidiClip { + self.cloned_clip() + } + fn clip_index_current (&self) -> usize { + 0 + } + fn clip_index_after (&self) -> usize { + 0 + } + fn clip_index_previous (&self) -> usize { + 0 + } + fn clip_index_next (&self) -> usize { + 0 + } + fn color_random (&self) -> ItemColor { + ItemColor::random() + } +} + +#[tengri_proc::expose] impl MidiEditor { + fn time_lock (&self) -> bool { + self.get_time_lock() + } + fn time_lock_toggled (&self) -> bool { + !self.get_time_lock() + } + + fn note_length (&self) -> usize { + self.get_note_len() + } + + fn note_pos (&self) -> usize { + self.get_note_pos() + } + fn note_pos_next (&self) -> usize { + self.get_note_pos() + 1 + } + fn note_pos_next_octave (&self) -> usize { + self.get_note_pos() + 12 + } + fn note_pos_prev (&self) -> usize { + self.get_note_pos().saturating_sub(1) + } + fn note_pos_prev_octave (&self) -> usize { + self.get_note_pos().saturating_sub(12) + } + + fn note_len (&self) -> usize { + self.get_note_len() + } + fn note_len_next (&self) -> usize { + self.get_note_len() + 1 + } + fn note_len_prev (&self) -> usize { + self.get_note_len().saturating_sub(1) + } + + fn note_range (&self) -> usize { + self.get_note_axis() + } + fn note_range_next (&self) -> usize { + self.get_note_axis() + 1 + } + fn note_range_prev (&self) -> usize { + self.get_note_axis().saturating_sub(1) + } + + fn time_pos (&self) -> usize { + self.get_time_pos() + } + fn time_pos_next (&self) -> usize { + self.get_time_pos() + self.time_zoom() + } + fn time_pos_prev (&self) -> usize { + self.get_time_pos().saturating_sub(self.time_zoom()) + } + + fn time_zoom (&self) -> usize { + self.get_time_zoom() + } + fn time_zoom_next (&self) -> usize { + self.get_time_zoom() + 1 + } + fn time_zoom_prev (&self) -> usize { + self.get_time_zoom().saturating_sub(1).max(1) + } +} + +#[tengri_proc::command(Tek)] impl TekCommand { + fn toggle_help (tek: &mut Tek, value: bool) -> Perhaps { + tek.toggle_dialog(Some(Dialog::Help)); + Ok(None) + } + fn toggle_menu (tek: &mut Tek, value: bool) -> Perhaps { + tek.toggle_dialog(Some(Dialog::Menu)); + Ok(None) + } + fn toggle_edit (tek: &mut Tek, value: bool) -> Perhaps { + tek.toggle_editor(Some(value)); + Ok(None) + } + fn editor (tek: &mut Tek, command: MidiEditCommand) -> Perhaps { + Ok(tek.editor.as_mut().map(|editor|command.execute(editor)) + .transpose()? + .flatten() + .map(|undo|Self::Editor { command: undo })) + } + fn pool (tek: &mut Tek, command: PoolCommand) -> Perhaps { + Ok(if let Some(pool) = tek.pool.as_mut() { + let undo = command.clone().delegate(pool, |command|TekCommand::Pool{command})?; + // update linked editor after pool action + tek.editor.as_mut().map(|editor|match command { // autoselect: automatically load selected clip in editor - // autocolor: update color in all places simultaneously - PoolCommand::Select(_) | PoolCommand::Clip(PoolClipCommand::SetColor(_, _)) => + PoolCommand::Select { .. } | + // autocolor: update color in all places simultaneously + PoolCommand::Clip { command: PoolClipCommand::SetColor { .. } } => editor.set_clip(pool.clip().as_ref()), _ => {} - } - }; - undo - } else { - None - }) -} - -#[derive(Clone, PartialEq, Debug)] pub enum PoolCommand { - /// Toggle visibility of pool - Show(bool), - /// Select a clip from the clip pool - Select(usize), - /// Rename a clip - Rename(ClipRenameCommand), - /// Change the length of a clip - Length(ClipLengthCommand), - /// Import from file - Import(FileBrowserCommand), - /// Export to file - Export(FileBrowserCommand), - /// Update the contents of the clip pool - Clip(PoolClipCommand), -} - -atom_command!(PoolCommand: |state: MidiPool| { - ("show" [a: bool] Some(Self::Show(a.expect("no flag")))) - ("select" [i: usize] Some(Self::Select(i.expect("no index")))) - ("rename" [,..a] ClipRenameCommand::try_from_expr(state, a).map(Self::Rename)) - ("length" [,..a] ClipLengthCommand::try_from_expr(state, a).map(Self::Length)) - ("import" [,..a] FileBrowserCommand::try_from_expr(state, a).map(Self::Import)) - ("export" [,..a] FileBrowserCommand::try_from_expr(state, a).map(Self::Export)) - ("clip" [,..a] PoolClipCommand::try_from_expr(state, a).map(Self::Clip)) -}); - -command!(|self: PoolCommand, state: MidiPool|{ - use PoolCommand::*; - match self { - Rename(ClipRenameCommand::Begin) => { state.begin_clip_rename(); None } - Rename(command) => command.delegate(state, Rename)?, - Length(ClipLengthCommand::Begin) => { state.begin_clip_length(); None }, - Length(command) => command.delegate(state, Length)?, - Import(FileBrowserCommand::Begin) => { state.begin_import()?; None }, - Import(command) => command.delegate(state, Import)?, - Export(FileBrowserCommand::Begin) => { state.begin_export()?; None }, - Export(command) => command.delegate(state, Export)?, - Clip(command) => command.execute(state)?.map(Clip), - Show(visible) => { state.visible = visible; Some(Self::Show(!visible)) }, - Select(clip) => { state.set_clip_index(clip); None }, - } -}); - -#[derive(Clone, Debug, PartialEq)] pub enum PoolClipCommand { - Add(usize, MidiClip), - Delete(usize), - Swap(usize, usize), - Import(usize, PathBuf), - Export(usize, PathBuf), - SetName(usize, Arc), - SetLength(usize, usize), - SetColor(usize, ItemColor), -} - -impl Command for PoolClipCommand { - fn execute (self, model: &mut T) -> Perhaps { - use PoolClipCommand::*; - Ok(match self { - Add(mut index, clip) => { - let clip = Arc::new(RwLock::new(clip)); - let mut clips = model.clips_mut(); - if index >= clips.len() { - index = clips.len(); - clips.push(clip) - } else { - clips.insert(index, clip); - } - Some(Self::Delete(index)) - }, - Delete(index) => { - let clip = model.clips_mut().remove(index).read().unwrap().clone(); - Some(Self::Add(index, clip)) - }, - Swap(index, other) => { - model.clips_mut().swap(index, other); - Some(Self::Swap(index, other)) - }, - Import(index, path) => { - let bytes = std::fs::read(&path)?; - let smf = Smf::parse(bytes.as_slice())?; - let mut t = 0u32; - let mut events = vec![]; - for track in smf.tracks.iter() { - for event in track.iter() { - t += event.delta.as_int(); - if let TrackEventKind::Midi { channel, message } = event.kind { - events.push((t, channel.as_int(), message)); - } - } - } - let mut clip = MidiClip::new("imported", true, t as usize + 1, None, None); - for event in events.iter() { - clip.notes[event.0 as usize].push(event.2); - } - Self::Add(index, clip).execute(model)? - }, - Export(_index, _path) => { - todo!("export clip to midi file"); - }, - SetName(index, name) => { - let clip = &mut model.clips_mut()[index]; - let old_name = clip.read().unwrap().name.clone(); - clip.write().unwrap().name = name; - Some(Self::SetName(index, old_name)) - }, - SetLength(index, length) => { - let clip = &mut model.clips_mut()[index]; - let old_len = clip.read().unwrap().length; - clip.write().unwrap().length = length; - Some(Self::SetLength(index, old_len)) - }, - SetColor(index, color) => { - let mut color = ItemTheme::from(color); - std::mem::swap(&mut color, &mut model.clips()[index].write().unwrap().color); - Some(Self::SetColor(index, color.base)) - }, - }) - } -} - -atom_command!(ClipRenameCommand: |state: MidiPool| { - ("begin" [] Some(Self::Begin)) - ("cancel" [] Some(Self::Cancel)) - ("confirm" [] Some(Self::Confirm)) - ("set" [n: Arc] Some(Self::Set(n.expect("no name")))) -}); -atom_command!(ClipLengthCommand: |state: MidiPool| { - ("begin" [] Some(Self::Begin)) - ("cancel" [] Some(Self::Cancel)) - ("next" [] Some(Self::Next)) - ("prev" [] Some(Self::Prev)) - ("inc" [] Some(Self::Inc)) - ("dec" [] Some(Self::Dec)) - ("set" [l: usize] Some(Self::Set(l.expect("no length")))) -}); -atom_command!(FileBrowserCommand: |state: MidiPool| { - ("begin" [] Some(Self::Begin)) - ("cancel" [] Some(Self::Cancel)) - ("confirm" [] Some(Self::Confirm)) - ("select" [i: usize] Some(Self::Select(i.expect("no index")))) - ("chdir" [p: PathBuf] Some(Self::Chdir(p.expect("no path")))) - ("filter" [f: Arc] Some(Self::Filter(f.expect("no filter")))) -}); -atom_command!(MidiEditCommand: |state: MidiEditor| { - ("note/append" [] Some(Self::AppendNote)) - ("note/put" [] Some(Self::PutNote)) - ("note/del" [] Some(Self::DelNote)) - ("note/pos" [a: usize] Some(Self::SetNoteCursor(a.expect("no note cursor")))) - ("note/len" [a: usize] Some(Self::SetNoteLength(a.expect("no note length")))) - ("time/pos" [a: usize] Some(Self::SetTimeCursor(a.expect("no time cursor")))) - ("time/zoom" [a: usize] Some(Self::SetTimeZoom(a.expect("no time zoom")))) - ("time/lock" [a: bool] Some(Self::SetTimeLock(a.expect("no time lock")))) - ("time/lock" [] Some(Self::SetTimeLock(!state.time_lock().get()))) -}); -atom_command!(PoolClipCommand: |state: MidiPool| { - ("add" [i: usize, c: MidiClip] - Some(Self::Add(i.expect("no index"), c.expect("no clip")))) - ("delete" [i: usize] - Some(Self::Delete(i.expect("no index")))) - ("swap" [a: usize, b: usize] - Some(Self::Swap(a.expect("no index"), b.expect("no index")))) - ("import" [i: usize, p: PathBuf] - Some(Self::Import(i.expect("no index"), p.expect("no path")))) - ("export" [i: usize, p: PathBuf] - Some(Self::Export(i.expect("no index"), p.expect("no path")))) - ("set-name" [i: usize, n: Arc] - Some(Self::SetName(i.expect("no index"), n.expect("no name")))) - ("set-length" [i: usize, l: usize] - Some(Self::SetLength(i.expect("no index"), l.expect("no length")))) - ("set-color" [i: usize, c: ItemColor] - Some(Self::SetColor(i.expect("no index"), c.expect("no color")))) -}); -// TODO: 1-9 seek markers that by default start every 8th of the clip -defcom!([self, state: MidiEditor] - (MidiEditCommand - (AppendNote [] { state.put_note(true); None }) - (PutNote [] { state.put_note(false); None }) - (DelNote [] { None }) - (SetNoteCursor [x: usize] { state.set_note_pos(x.min(127)); None }) - (SetNoteLength [x: usize] { - let note_len = state.note_len(); - let time_zoom = state.time_zoom().get(); - state.set_note_len(x); - //if note_len / time_zoom != x / time_zoom { - state.redraw(); - //} + }); + undo + } else { None }) - (SetNoteScroll [x: usize] { state.note_lo().set(x.min(127)); None }) - (SetTimeCursor [x: usize] { state.set_time_pos(x); None }) - (SetTimeScroll [x: usize] { state.time_start().set(x); None }) - (SetTimeZoom [x: usize] { state.time_zoom().set(x); state.redraw(); None }) - (SetTimeLock [x: bool] { state.time_lock().set(x); None }) - (Show [x: MaybeClip] { state.set_clip(x.as_ref()); None }))); - -#[derive(Clone, Debug, PartialEq)] pub enum ClipRenameCommand { - Begin, - Cancel, - Confirm, - Set(Arc), -} - - -command!(|self: ClipRenameCommand, state: MidiPool|if let Some( - PoolMode::Rename(clip, ref mut old_name) -) = state.mode_mut().clone() { - match self { - Self::Set(s) => { - state.clips()[clip].write().unwrap().name = s; - return Ok(Some(Self::Set(old_name.clone().into()))) - }, - Self::Confirm => { - let old_name = old_name.clone(); - *state.mode_mut() = None; - return Ok(Some(Self::Set(old_name))) - }, - Self::Cancel => { - state.clips()[clip].write().unwrap().name = old_name.clone().into(); - return Ok(None) - }, - _ => unreachable!() } -} else { - unreachable!() -}); - -command!(|self: FileBrowserCommand, state: MidiPool|{ - use PoolMode::*; - use FileBrowserCommand::*; - let mode = &mut state.mode; - match mode { - Some(Import(index, ref mut browser)) => match self { - Cancel => { *mode = None; }, - Chdir(cwd) => { *mode = Some(Import(*index, FileBrowser::new(Some(cwd))?)); }, - Select(index) => { browser.index = index; }, - Confirm => if browser.is_file() { - let index = *index; - let path = browser.path(); - *mode = None; - PoolClipCommand::Import(index, path).execute(state)?; - } else if browser.is_dir() { - *mode = Some(Import(*index, browser.chdir()?)); - }, - _ => todo!(), - }, - Some(Export(index, ref mut browser)) => match self { - Cancel => { *mode = None; }, - Chdir(cwd) => { *mode = Some(Export(*index, FileBrowser::new(Some(cwd))?)); }, - Select(index) => { browser.index = index; }, - _ => unreachable!() - }, - _ => unreachable!(), - }; - None -}); - -#[derive(Copy, Clone, Debug, PartialEq)] -pub enum ClipLengthCommand { - Begin, - Cancel, - Set(usize), - Next, - Prev, - Inc, - Dec, + fn sampler (tek: &mut Tek, command: SamplerCommand) -> Perhaps { + Ok(tek.sampler_mut() + .map(|s|command.delegate(s, |command|Self::Sampler{command})) + .transpose()? + .flatten()) + } + fn scene (tek: &mut Tek, command: SceneCommand) -> Perhaps { + Ok(command.delegate(tek, |command|Self::Scene{command})?) + } + fn track (tek: &mut Tek, command: TrackCommand) -> Perhaps { + Ok(command.delegate(tek, |command|Self::Track{command})?) + } + fn input (tek: &mut Tek, command: InputCommand) -> Perhaps { + Ok(command.delegate(tek, |command|Self::Input{command})?) + } + fn output (tek: &mut Tek, command: OutputCommand) -> Perhaps { + Ok(command.delegate(tek, |command|Self::Output{command})?) + } + fn clip (tek: &mut Tek, command: ClipCommand) -> Perhaps { + Ok(command.delegate(tek, |command|Self::Clip{command})?) + } + fn clock (tek: &mut Tek, command: ClockCommand) -> Perhaps { + Ok(command.execute(&mut tek.clock)?.map(|command|Self::Clock{command})) + } + fn device (tek: &mut Tek, command: DeviceCommand) -> Perhaps { + Ok(command.delegate(tek, |command|Self::Device{command})?) + } + fn message (tek: &mut Tek, command: MessageCommand) -> Perhaps { + Ok(command.delegate(tek, |command|Self::Message{command})?) + } + fn color (tek: &mut Tek, theme: ItemTheme) -> Perhaps { + Ok(tek.set_color(Some(theme)).map(|theme|Self::Color{theme})) + } + fn enqueue (tek: &mut Tek, clip: Option>>) -> Perhaps { + todo!() + } + fn history (tek: &mut Tek, delta: isize) -> Perhaps { + todo!() + } + fn zoom (tek: &mut Tek, zoom: usize) -> Perhaps { + todo!() + } + fn launch (tek: &mut Tek) -> Perhaps { + tek.launch(); + Ok(None) + } + fn select (tek: &mut Tek, selection: Selection) -> Perhaps { + tek.select(selection); + Ok(None) + //("select" [t: usize, s: usize] Some(match (t.expect("no track"), s.expect("no scene")) { + //(0, 0) => Self::Select(Selection::Mix), + //(t, 0) => Self::Select(Selection::Track(t)), + //(0, s) => Self::Select(Selection::Scene(s)), + //(t, s) => Self::Select(Selection::TrackClip { track: t, scene: s }) }))) + } + fn stop_all (tek: &mut Tek) -> Perhaps { + tek.stop_all(); + Ok(None) + } } -command!(|self: ClipLengthCommand, state: MidiPool|{ - use ClipLengthCommand::*; - use ClipLengthFocus::*; - if let Some( - PoolMode::Length(clip, ref mut length, ref mut focus) - ) = state.mode_mut().clone() { - match self { - Cancel => { *state.mode_mut() = None; }, - Prev => { focus.prev() }, - Next => { focus.next() }, - Inc => match focus { - Bar => { *length += 4 * PPQ }, - Beat => { *length += PPQ }, - Tick => { *length += 1 }, +#[tengri_proc::command(Tek)] impl InputCommand { + fn add (tek: &mut Tek) -> Perhaps { + tek.midi_in_add()?; + Ok(None) + } +} + +#[tengri_proc::command(Tek)] impl OutputCommand { + fn add (tek: &mut Tek) -> Perhaps { + tek.midi_out_add()?; + Ok(None) + } +} + +#[tengri_proc::command(Tek)] impl DeviceCommand { + fn picker (tek: &mut Tek) -> Perhaps { + tek.device_picker_show(); + Ok(None) + } + fn pick (tek: &mut Tek, i: usize) -> Perhaps { + tek.device_pick(i); + Ok(None) + } + fn add (tek: &mut Tek, i: usize) -> Perhaps { + tek.device_add(i); + Ok(None) + } +} + +#[tengri_proc::command(Tek)] impl MessageCommand { + fn dismiss (tek: &mut Tek) -> Perhaps { + tek.message_dismiss(); + Ok(None) + } +} + +#[tengri_proc::command(Tek)] impl TrackCommand { + fn toggle_play (tek: &mut Tek) -> Perhaps { + todo!() + } + fn toggle_solo (tek: &mut Tek) -> Perhaps { + todo!() + } + fn toggle_rec (tek: &mut Tek) -> Perhaps { + tek.track_toggle_record(); + Ok(Some(Self::ToggleRec)) + } + fn toggle_mon (tek: &mut Tek) -> Perhaps { + tek.track_toggle_monitor(); + Ok(Some(Self::ToggleMon)) + } + fn set_size (tek: &mut Tek, size: usize) -> Perhaps { + todo!() + } + fn set_zoom (tek: &mut Tek, zoom: usize) -> Perhaps { + todo!() + } + fn swap (tek: &mut Tek, index: usize, other: usize) -> Perhaps { + todo!(); + Ok(Some(Self::Swap { index, other })) + } + fn del (tek: &mut Tek, index: usize) -> Perhaps { + tek.track_del(index); + Ok(None) + } + fn stop (tek: &mut Tek, index: usize) -> Perhaps { + tek.tracks[index].player.enqueue_next(None); + Ok(None) + } + fn add (tek: &mut Tek) -> Perhaps { + Ok(Some(Self::Del { index: tek.track_add_focus()? })) + } + fn set_color (tek: &mut Tek, index: usize, color: ItemTheme) -> Perhaps { + Ok(Some(Self::SetColor { index, color: tek.track_set_color(index, color) })) + } +} + +#[tengri_proc::command(Tek)] impl SceneCommand { + fn add (tek: &mut Tek) -> Perhaps { + todo!() + } + fn del (tek: &mut Tek, index: usize) -> Perhaps { + tek.scene_del(index); + Ok(None) + } + fn enqueue (tek: &mut Tek, index: usize) -> Perhaps { + tek.scene_enqueue(index); + Ok(None) + } + fn set_color (tek: &mut Tek, index: usize, color: ItemTheme) -> Perhaps { + Ok(Some(Self::SetColor { index, color: tek.scene_set_color(index, color) })) + } + fn set_size (tek: &mut Tek, index: usize, size: usize) -> Perhaps { + todo!() + } + fn set_zoom (tek: &mut Tek, index: usize, zoom: usize) -> Perhaps { + todo!() + } + fn swap (tek: &mut Tek, index: usize, other: usize) -> Perhaps { + todo!(); + Ok(Some(Self::Swap { index, other })) + } +} + +#[tengri_proc::command(Tek)] impl ClipCommand { + fn get (tek: &mut Tek, a: usize, b: usize) -> Perhaps { + //(Get [a: usize, b: usize] cmd_todo!("\n\rtodo: clip: get: {a} {b}")) + //("get" [a: usize, b: usize] Some(Self::Get(a.unwrap(), b.unwrap()))) + todo!() + } + fn edit (tek: &mut Tek, a: usize, b: usize) -> Perhaps { + //(Edit [clip: MaybeClip] cmd_todo!("\n\rtodo: clip: edit: {clip:?}")) + //("edit" [a: MaybeClip] Some(Self::Edit(a.unwrap()))) + todo!() + } + fn set_loop (tek: &mut Tek, a: usize, b: usize) -> Perhaps { + //(SetLoop [t: usize, s: usize, l: bool] cmd_todo!("\n\rtodo: {self:?}")) + //("loop" [a: usize, b: usize, c: bool] Some(Self::SetLoop(a.unwrap(), b.unwrap(), c.unwrap()))) + todo!() + } + fn put (tek: &mut Tek, a: usize, b: usize) -> Perhaps { + //(Put [t: usize, s: usize, c: MaybeClip] + //Some(Self::Put(t, s, app.clip_put(t, s, c)))) + //("put" [a: usize, b: usize, c: MaybeClip] Some(Self::Put(a.unwrap(), b.unwrap(), c.unwrap()))) + todo!() + } + fn del (tek: &mut Tek, a: usize, b: usize) -> Perhaps { + //("delete" [a: usize, b: usize] Some(Self::Put(a.unwrap(), b.unwrap(), None)))) + todo!() + } + fn enqueue (tek: &mut Tek, a: usize, b: usize) -> Perhaps { + //(Enqueue [t: usize, s: usize] + //cmd!(app.tracks[t].player.enqueue_next(app.scenes[s].clips[t].as_ref()))) + //("enqueue" [a: usize, b: usize] Some(Self::Enqueue(a.unwrap(), b.unwrap()))) + todo!() + } + fn set_color (tek: &mut Tek, a: usize, b: usize) -> Perhaps { + //(SetColor [t: usize, s: usize, c: ItemTheme] + //app.clip_set_color(t, s, c).map(|o|Self::SetColor(t, s, o))))); + //("color" [a: usize, b: usize] Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemTheme::random()))) + todo!() + } +} + +#[tengri_proc::command(MidiPool)] impl PoolCommand { + /// Toggle visibility of pool + fn show (pool: &mut MidiPool, visible: bool) -> Perhaps { + pool.visible = visible; + Ok(Some(Self::Show { visible: !visible })) + } + /// Select a clip from the clip pool + fn select (pool: &mut MidiPool, index: usize) -> Perhaps { + pool.set_clip_index(index); + Ok(None) + } + /// Rename a clip + fn rename (pool: &mut MidiPool, command: ClipRenameCommand) -> Perhaps { + Ok(match command { + ClipRenameCommand::Begin => { + pool.begin_clip_rename(); + None }, - Dec => match focus { - Bar => { *length = length.saturating_sub(4 * PPQ) }, - Beat => { *length = length.saturating_sub(PPQ) }, - Tick => { *length = length.saturating_sub(1) }, + _ => command.delegate(pool, |command|Self::Rename{command})? + }) + } + /// Change the length of a clip + fn length (pool: &mut MidiPool, command: ClipLengthCommand) -> Perhaps { + Ok(match command { + ClipLengthCommand::Begin => { + pool.begin_clip_length(); + None }, - Set(length) => { - let old_length; - { - let clip = state.clips()[clip].clone();//.write().unwrap(); - old_length = Some(clip.read().unwrap().length); - clip.write().unwrap().length = length; - } - *state.mode_mut() = None; - return Ok(old_length.map(Self::Set)) + _ => command.delegate(pool, |command|Self::Length{command})? + }) + } + /// Import from file + fn import (pool: &mut MidiPool, command: FileBrowserCommand) -> Perhaps { + Ok(match command { + FileBrowserCommand::Begin => { + pool.begin_import(); + None }, - _ => unreachable!() + _ => command.delegate(pool, |command|Self::Import{command})? + }) + } + /// Export to file + fn export (pool: &mut MidiPool, command: FileBrowserCommand) -> Perhaps { + Ok(match command { + FileBrowserCommand::Begin => { + pool.begin_export(); + None + }, + _ => command.delegate(pool, |command|Self::Export{command})? + }) + } + /// Update the contents of the clip pool + fn clip (pool: &mut MidiPool, command: PoolClipCommand) -> Perhaps { + Ok(command.execute(pool)?.map(|command|Self::Clip{command})) + } +} + +#[tengri_proc::command(MidiPool)] impl PoolClipCommand { + fn add (pool: &mut MidiPool, index: usize, clip: MidiClip) -> Perhaps { + let mut index = index; + let clip = Arc::new(RwLock::new(clip)); + let mut clips = pool.clips_mut(); + if index >= clips.len() { + index = clips.len(); + clips.push(clip) + } else { + clips.insert(index, clip); } - } else { + Ok(Some(Self::Delete { index })) + } + fn delete (pool: &mut MidiPool, index: usize) -> Perhaps { + let clip = pool.clips_mut().remove(index).read().unwrap().clone(); + Ok(Some(Self::Add { index, clip })) + } + fn swap (pool: &mut MidiPool, index: usize, other: usize) -> Perhaps { + pool.clips_mut().swap(index, other); + Ok(Some(Self::Swap { index, other })) + } + fn import (pool: &mut MidiPool, index: usize, path: PathBuf) -> Perhaps { + let bytes = std::fs::read(&path)?; + let smf = Smf::parse(bytes.as_slice())?; + let mut t = 0u32; + let mut events = vec![]; + for track in smf.tracks.iter() { + for event in track.iter() { + t += event.delta.as_int(); + if let TrackEventKind::Midi { channel, message } = event.kind { + events.push((t, channel.as_int(), message)); + } + } + } + let mut clip = MidiClip::new("imported", true, t as usize + 1, None, None); + for event in events.iter() { + clip.notes[event.0 as usize].push(event.2); + } + Ok(Self::Add { index, clip }.execute(pool)?) + } + fn export (pool: &mut MidiPool, index: usize, path: PathBuf) -> Perhaps { + todo!("export clip to midi file"); + } + fn set_name (pool: &mut MidiPool, index: usize, name: Arc) -> Perhaps { + let clip = &mut pool.clips_mut()[index]; + let old_name = clip.read().unwrap().name.clone(); + clip.write().unwrap().name = name; + Ok(Some(Self::SetName { index, name: old_name })) + } + fn set_length (pool: &mut MidiPool, index: usize, length: usize) -> Perhaps { + let clip = &mut pool.clips_mut()[index]; + let old_len = clip.read().unwrap().length; + clip.write().unwrap().length = length; + Ok(Some(Self::SetLength { index, length: old_len })) + } + fn set_color (pool: &mut MidiPool, index: usize, color: ItemColor) -> Perhaps { + let mut color = ItemTheme::from(color); + std::mem::swap(&mut color, &mut pool.clips()[index].write().unwrap().color); + Ok(Some(Self::SetColor { index, color: color.base })) + } +} + +#[tengri_proc::command(MidiPool)] impl ClipRenameCommand { + fn begin (pool: &mut MidiPool) -> Perhaps { unreachable!(); } - None -}); + fn cancel (pool: &mut MidiPool) -> Perhaps { + if let Some(PoolMode::Rename(clip, ref mut old_name)) = pool.mode_mut().clone() { + pool.clips()[clip].write().unwrap().name = old_name.clone().into(); + } + return Ok(None) + } + fn confirm (pool: &mut MidiPool) -> Perhaps { + if let Some(PoolMode::Rename(clip, ref mut old_name)) = pool.mode_mut().clone() { + let old_name = old_name.clone(); + *pool.mode_mut() = None; + return Ok(Some(Self::Set { value: old_name })) + } + return Ok(None) + } + fn set (pool: &mut MidiPool, value: Arc) -> Perhaps { + if let Some(PoolMode::Rename(clip, ref mut old_name)) = pool.mode_mut().clone() { + pool.clips()[clip].write().unwrap().name = value; + } + return Ok(None) + } +} + +#[tengri_proc::command(MidiPool)] impl ClipLengthCommand { + fn begin (pool: &mut MidiPool) -> Perhaps { + unreachable!() + } + fn cancel (pool: &mut MidiPool) -> Perhaps { + if let Some(PoolMode::Length(..)) = pool.mode_mut().clone() { + *pool.mode_mut() = None; + } + Ok(None) + } + fn set (pool: &mut MidiPool, length: usize) -> Perhaps { + if let Some(PoolMode::Length(clip, ref mut length, ref mut focus)) + = pool.mode_mut().clone() + { + let old_length; + { + let clip = pool.clips()[clip].clone();//.write().unwrap(); + old_length = Some(clip.read().unwrap().length); + clip.write().unwrap().length = *length; + } + *pool.mode_mut() = None; + return Ok(old_length.map(|length|Self::Set { length })) + } + Ok(None) + } + fn next (pool: &mut MidiPool) -> Perhaps { + if let Some(PoolMode::Length(clip, ref mut length, ref mut focus)) + = pool.mode_mut().clone() + { + focus.next() + } + Ok(None) + } + fn prev (pool: &mut MidiPool) -> Perhaps { + if let Some(PoolMode::Length(clip, ref mut length, ref mut focus)) + = pool.mode_mut().clone() + { + focus.prev() + } + Ok(None) + } + fn inc (pool: &mut MidiPool) -> Perhaps { + if let Some(PoolMode::Length(clip, ref mut length, ref mut focus)) + = pool.mode_mut().clone() + { + match focus { + ClipLengthFocus::Bar => { *length += 4 * PPQ }, + ClipLengthFocus::Beat => { *length += PPQ }, + ClipLengthFocus::Tick => { *length += 1 }, + } + } + Ok(None) + } + fn dec (pool: &mut MidiPool) -> Perhaps { + if let Some(PoolMode::Length(clip, ref mut length, ref mut focus)) + = pool.mode_mut().clone() + { + match focus { + ClipLengthFocus::Bar => { *length = length.saturating_sub(4 * PPQ) }, + ClipLengthFocus::Beat => { *length = length.saturating_sub(PPQ) }, + ClipLengthFocus::Tick => { *length = length.saturating_sub(1) }, + } + } + Ok(None) + } +} + +#[tengri_proc::command(MidiPool)] impl FileBrowserCommand { + fn begin (pool: &mut MidiPool) -> Perhaps { + unreachable!(); + } + fn cancel (pool: &mut MidiPool) -> Perhaps { + pool.mode = None; + Ok(None) + } + fn confirm (pool: &mut MidiPool) -> Perhaps { + Ok(match pool.mode { + Some(PoolMode::Import(index, ref mut browser)) => { + if browser.is_file() { + let path = browser.path(); + pool.mode = None; + let _undo = PoolClipCommand::import(pool, index, path)?; + None + } else if browser.is_dir() { + pool.mode = Some(PoolMode::Import(index, browser.chdir()?)); + None + } else { + None + } + }, + Some(PoolMode::Export(index, ref mut browser)) => { + todo!() + }, + _ => unreachable!(), + }) + } + fn select (pool: &mut MidiPool, index: usize) -> Perhaps { + Ok(match pool.mode { + Some(PoolMode::Import(index, ref mut browser)) => { + browser.index = index; + None + }, + Some(PoolMode::Export(index, ref mut browser)) => { + browser.index = index; + None + }, + _ => unreachable!(), + }) + } + fn chdir (pool: &mut MidiPool, dir: PathBuf) -> Perhaps { + Ok(match pool.mode { + Some(PoolMode::Import(index, ref mut browser)) => { + pool.mode = Some(PoolMode::Import(index, FileBrowser::new(Some(dir))?)); + None + }, + Some(PoolMode::Export(index, ref mut browser)) => { + pool.mode = Some(PoolMode::Export(index, FileBrowser::new(Some(dir))?)); + None + }, + _ => unreachable!(), + }) + } + fn filter (pool: &mut MidiPool, filter: Arc) -> Perhaps { + todo!() + } +} + +#[tengri_proc::command(MidiEditor)] impl MidiEditCommand { + // TODO: 1-9 seek markers that by default start every 8th of the clip + fn note_append (editor: &mut MidiEditor) -> Perhaps { + editor.put_note(true); + Ok(None) + } + fn note_put (editor: &mut MidiEditor) -> Perhaps { + editor.put_note(false); + Ok(None) + } + fn note_del (editor: &mut MidiEditor) -> Perhaps { + todo!() + } + fn note_pos (editor: &mut MidiEditor, pos: usize) -> Perhaps { + editor.set_note_pos(pos.min(127)); + Ok(None) + } + fn note_len (editor: &mut MidiEditor, value: usize) -> Perhaps { + //let note_len = editor.get_note_len(); + //let time_zoom = editor.get_time_zoom(); + editor.set_note_len(value); + //if note_len / time_zoom != x / time_zoom { + editor.redraw(); + //} + Ok(None) + } + fn note_scroll (editor: &mut MidiEditor, value: usize) -> Perhaps { + editor.set_note_lo(value.min(127)); + Ok(None) + } + fn time_pos (editor: &mut MidiEditor, value: usize) -> Perhaps { + editor.set_time_pos(value); + Ok(None) + } + fn time_scroll (editor: &mut MidiEditor, value: usize) -> Perhaps { + editor.set_time_start(value); + Ok(None) + } + fn time_zoom (editor: &mut MidiEditor, value: usize) -> Perhaps { + editor.set_time_zoom(value); + editor.redraw(); + Ok(None) + } + fn time_lock (editor: &mut MidiEditor, value: bool) -> Perhaps { + editor.set_time_lock(value); + Ok(None) + } + fn show (editor: &mut MidiEditor, clip: Option>>) -> Perhaps { + editor.set_clip(clip.as_ref()); + Ok(None) + } +} diff --git a/crates/app/src/model/editor.rs b/crates/app/src/model/editor.rs index 355b0e8c..fec878b1 100644 --- a/crates/app/src/model/editor.rs +++ b/crates/app/src/model/editor.rs @@ -53,9 +53,9 @@ impl MidiEditor { let mut redraw = false; if let Some(clip) = self.clip() { let mut clip = clip.write().unwrap(); - let note_start = self.time_pos(); - let note_pos = self.note_pos(); - let note_len = self.note_len(); + let note_start = self.get_time_pos(); + let note_pos = self.get_note_pos(); + let note_len = self.get_note_len(); let note_end = note_start + (note_len.saturating_sub(1)); let key: u7 = u7::from(note_pos as u8); let vel: u7 = 100.into(); @@ -93,12 +93,13 @@ impl MidiEditor { let (color, length) = if let Some(clip) = self.clip().as_ref().map(|p|p.read().unwrap()) { (clip.color, clip.length) } else { (ItemTheme::G[64], 0) }; - let time_pos = self.time_pos(); - let time_zoom = self.time_zoom().get(); - let time_lock = if self.time_lock().get() { "[lock]" } else { " " }; - let note_pos = format!("{:>3}", self.note_pos()); - let note_name = format!("{:4}", Note::pitch_to_name(self.note_pos())); - let note_len = format!("{:>4}", self.note_len()); + let time_pos = self.get_time_pos(); + let time_zoom = self.get_time_zoom(); + let time_lock = if self.get_time_lock() { "[lock]" } else { " " }; + let note_pos = self.get_note_pos(); + let note_name = format!("{:4}", Note::pitch_to_name(note_pos)); + let note_pos = format!("{:>3}", note_pos); + let note_len = format!("{:>4}", self.get_note_len()); Bsp::e( FieldH(color, "Time", format!("{length}/{time_zoom}+{time_pos} {time_lock}")), FieldH(color, "Note", format!("{note_name} {note_pos} {note_len}")), @@ -120,15 +121,12 @@ impl NoteRange for MidiEditor { } impl NotePoint for MidiEditor { - fn note_len (&self) -> usize { self.mode.note_len() } - fn set_note_len (&self, x: usize) -> usize { self.mode.set_note_len(x) } - fn note_pos (&self) -> usize { self.mode.note_pos() } - fn set_note_pos (&self, x: usize) -> usize { self.mode.set_note_pos(x) } + fn note_len (&self) -> &AtomicUsize { self.mode.note_len() } + fn note_pos (&self) -> &AtomicUsize { self.mode.note_pos() } } impl TimePoint for MidiEditor { - fn time_pos (&self) -> usize { self.mode.time_pos() } - fn set_time_pos (&self, x: usize) -> usize { self.mode.set_time_pos(x) } + fn time_pos (&self) -> &AtomicUsize { self.mode.time_pos() } } impl MidiViewer for MidiEditor { diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs index 5778861b..2f1bfdd9 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -4,13 +4,9 @@ pub(crate) use ::tengri::tui::ratatui::prelude::Position; #[tengri_proc::view(TuiOut)] impl Tek { - - #[tengri::view(":nil")] fn view_nil (&self) -> impl Content + use<'_> { "nil" } - - #[tengri::view(":status")] fn view_status (&self) -> impl Content + use<'_> { self.update_clock(); let cache = self.view_cache.read().unwrap(); @@ -19,8 +15,6 @@ impl Tek { cache.sr.view.clone(), cache.buf.view.clone(), cache.lat.view.clone(), ) } - - #[tengri::view(":transport")] fn view_transport (&self) -> impl Content + use<'_> { self.update_clock(); let cache = self.view_cache.read().unwrap(); @@ -29,38 +23,24 @@ impl Tek { cache.bpm.view.clone(), cache.beat.view.clone(), cache.time.view.clone(), ) } - - #[tengri::view(":arranger")] fn view_arranger (&self) -> impl Content + use<'_> { ArrangerView::new(self) } - - #[tengri::view(":pool")] fn view_pool (&self) -> impl Content + use<'_> { self.pool().map(|p|Fixed::x(self.w_sidebar(), PoolView(self.is_editing(), p))) } - - #[tengri::view(":editor")] fn view_editor (&self) -> impl Content + use<'_> { self.editor().map(|e|Bsp::n(Bsp::e(e.clip_status(), e.edit_status()), e)) } - - #[tengri::view(":samples-keys")] fn view_samples_keys (&self) -> impl Content + use<'_> { self.sampler().map(|s|s.view_list(false, self.editor().unwrap())) } - - #[tengri::view(":samples-grid")] fn view_samples_grid (&self) -> impl Content + use<'_> { self.sampler().map(|s|s.view_grid()) } - - #[tengri::view(":sample-viewer")] fn view_sample_viewer (&self) -> impl Content + use<'_> { - self.sampler().map(|s|s.view_sample(self.editor().unwrap().note_pos())) + self.sampler().map(|s|s.view_sample(self.editor().unwrap().get_note_pos())) } - - #[tengri::view(":dialog")] fn view_dialog (&self) -> impl Content + use<'_> { When::new(self.dialog.is_some(), Bsp::b( Fill::xy(Tui::fg_bg(Rgb(64,64,64), Rgb(32,32,32), "")), @@ -76,13 +56,14 @@ impl Tek { ))) )) } +} +impl Tek { fn view_dialog_menu (&self) -> impl Content { let options = ||["Projects", "Settings", "Help", "Quit"].iter(); let option = |a,i|Tui::fg(Rgb(255,255,255), format!("{}", a)); Bsp::s(Tui::bold(true, "tek!"), Bsp::s("", Map::south(1, options, option))) } - fn view_dialog_help (&self) -> impl Content + use<'_> { let bindings = ||self.config.keys.layers.iter() .filter_map(|a|(a.0)(self).then_some(a.1)) @@ -134,69 +115,6 @@ impl Tek { /// Default editor height. pub(crate) const H_EDITOR: usize = 15; - /// Width of display - pub(crate) fn w (&self) -> u16 { - self.size.w() as u16 - } - - pub(crate) fn w_sidebar (&self) -> u16 { - self.w() / if self.is_editing() { 16 } else { 8 } as u16 - } - - /// Width taken by all tracks. - pub(crate) fn w_tracks (&self) -> u16 { - self.tracks_with_sizes().last().map(|(_, _, _, x)|x as u16).unwrap_or(0) - } - - /// Width available to display tracks. - pub(crate) fn w_tracks_area (&self) -> u16 { - self.w().saturating_sub(2 * self.w_sidebar()) - } - - /// Height of display - pub(crate) fn h (&self) -> u16 { - self.size.h() as u16 - } - - /// Height available to display track headers. - pub(crate) fn h_tracks_area (&self) -> u16 { - 5 // FIXME - //self.h().saturating_sub(self.h_inputs() + self.h_outputs()) - } - - /// Height available to display tracks. - pub(crate) fn h_scenes_area (&self) -> u16 { - //15 - self.h().saturating_sub( - self.h_inputs() + - self.h_outputs() + - self.h_devices() + - 13 // FIXME - ) - } - - /// Height taken by all inputs. - pub(crate) fn h_inputs (&self) -> u16 { - self.inputs_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) - } - - /// Height taken by all outputs. - pub(crate) fn h_outputs (&self) -> u16 { - self.outputs_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) - } - - /// Height taken by visible device slots. - pub(crate) fn h_devices (&self) -> u16 { - 2 - //1 + self.devices_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) - } - - /// Height taken by all scenes. - pub(crate) fn h_scenes (&self) -> u16 { - self.scenes_with_sizes(self.is_editing(), Self::H_SCENE, Self::H_EDITOR).last() - .map(|(_, _, _, y)|y as u16).unwrap_or(0) - } - pub(crate) fn inputs_with_sizes (&self) -> impl PortsSizes<'_> { let mut y = 0; self.midi_ins.iter().enumerate().map(move|(i, input)|{ @@ -1137,9 +1055,9 @@ impl PianoHorizontal { } } fn notes (&self) -> impl Content { - let time_start = self.time_start().get(); - let note_lo = self.note_lo().get(); - let note_hi = self.note_hi(); + let time_start = self.get_time_start(); + let note_lo = self.get_note_lo(); + let note_hi = self.get_note_hi(); let buffer = self.buffer.clone(); ThunkRender::new(move|to: &mut TuiOut|{ let source = buffer.read().unwrap(); @@ -1168,14 +1086,14 @@ impl PianoHorizontal { }) } fn cursor (&self) -> impl Content { - let style = Some(Style::default().fg(self.color.lightest.rgb)); - let note_hi = self.note_hi(); - let note_lo = self.note_lo().get(); - let note_pos = self.note_pos(); - let note_len = self.note_len(); - let time_pos = self.time_pos(); - let time_start = self.time_start().get(); - let time_zoom = self.time_zoom().get(); + let note_hi = self.get_note_hi(); + let note_lo = self.get_note_lo(); + let note_pos = self.get_note_pos(); + let note_len = self.get_note_len(); + let time_pos = self.get_time_pos(); + let time_start = self.get_time_start(); + let time_zoom = self.get_time_zoom(); + let style = Some(Style::default().fg(self.color.lightest.rgb)); ThunkRender::new(move|to: &mut TuiOut|{ let [x0, y0, w, _] = to.area().xywh(); for (_area_y, screen_y, note) in note_y_iter(note_lo, note_hi, y0) { @@ -1201,9 +1119,9 @@ impl PianoHorizontal { fn keys (&self) -> impl Content { let state = self; let color = state.color; - let note_lo = state.note_lo().get(); - let note_hi = state.note_hi(); - let note_pos = state.note_pos(); + let note_lo = state.get_note_lo(); + let note_hi = state.get_note_hi(); + let note_pos = state.get_note_pos(); let key_style = Some(Style::default().fg(Rgb(192, 192, 192)).bg(Rgb(0, 0, 0))); let off_style = Some(Style::default().fg(Tui::g(255))); let on_style = Some(Style::default().fg(Rgb(255,0,0)).bg(color.base.rgb).bold()); @@ -1253,15 +1171,12 @@ impl NoteRange for PianoHorizontal { } impl NotePoint for PianoHorizontal { - fn note_len (&self) -> usize { self.point.note_len() } - fn set_note_len (&self, x: usize) -> usize { self.point.set_note_len(x) } - fn note_pos (&self) -> usize { self.point.note_pos() } - fn set_note_pos (&self, x: usize) -> usize { self.point.set_note_pos(x) } + fn note_len (&self) -> &AtomicUsize { self.point.note_len() } + fn note_pos (&self) -> &AtomicUsize { self.point.note_pos() } } impl TimePoint for PianoHorizontal { - fn time_pos (&self) -> usize { self.point.time_pos() } - fn set_time_pos (&self, x: usize) -> usize { self.point.set_time_pos(x) } + fn time_pos (&self) -> &AtomicUsize { self.point.time_pos() } } impl MidiViewer for PianoHorizontal { @@ -1280,8 +1195,8 @@ impl MidiViewer for PianoHorizontal { let clip = clip.read().unwrap(); let buf_size = self.buffer_size(&clip); let mut buffer = BigBuffer::from(buf_size); - let note_len = self.note_len(); - let time_zoom = self.time_zoom().get(); + let note_len = self.get_note_len(); + let time_zoom = self.get_time_zoom(); self.time_len().set(clip.length); PianoHorizontal::draw_bg(&mut buffer, &clip, time_zoom, note_len); PianoHorizontal::draw_fg(&mut buffer, &clip, time_zoom); diff --git a/crates/device/Cargo.toml b/crates/device/Cargo.toml index a84b08a9..f07255a4 100644 --- a/crates/device/Cargo.toml +++ b/crates/device/Cargo.toml @@ -4,13 +4,16 @@ edition = { workspace = true } version = { workspace = true } [dependencies] -tengri = { workspace = true } +tengri = { workspace = true } +tengri_proc = { workspace = true } + tek_engine = { workspace = true } -uuid = { workspace = true, optional = true } -livi = { workspace = true, optional = true } -symphonia = { workspace = true, optional = true } -wavers = { workspace = true, optional = true } -winit = { workspace = true, optional = true } + +uuid = { workspace = true, optional = true } +livi = { workspace = true, optional = true } +symphonia = { workspace = true, optional = true } +wavers = { workspace = true, optional = true } +winit = { workspace = true, optional = true } [features] default = [ "clock", "sequencer", "sampler", "lv2" ] diff --git a/crates/device/src/clock/clock_api.rs b/crates/device/src/clock/clock_api.rs index a2e5927f..e9161ec4 100644 --- a/crates/device/src/clock/clock_api.rs +++ b/crates/device/src/clock/clock_api.rs @@ -1,55 +1,61 @@ use crate::*; -#[derive(Clone, Debug, PartialEq)] -pub enum ClockCommand { - Play(Option), - Pause(Option), - SeekUsec(f64), - SeekSample(f64), - SeekPulse(f64), - SetBpm(f64), - SetQuant(f64), - SetSync(f64), +#[tengri_proc::expose] +impl Clock { + fn _todo_provide_u32 (&self) -> u32 { + todo!() + } + fn _todo_provide_opt_u32 (&self) -> Option { + todo!() + } + fn _todo_provide_f64 (&self) -> f64 { + todo!() + } } -provide_num!(u32: |self: Clock| {}); - -provide!(f64: |self: Clock| {}); - -atom_command!(ClockCommand: |state: Clock| { - ("play" [] Some(Self::Play(None))) - ("play" [t: u32] Some(Self::Play(t))) - ("pause" [] Some(Self::Pause(None))) - ("pause" [t: u32] Some(Self::Pause(t))) - ("toggle" [] Some(if state.is_rolling() { Self::Pause(None) } else { Self::Play(None) })) - ("toggle" [t: u32] Some(if state.is_rolling() { Self::Pause(t) } else { Self::Play(t) })) - ("seek/usec" [t: f64] Some(Self::SeekUsec(t.expect("no usec")))) - ("seek/pulse" [t: f64] Some(Self::SeekPulse(t.expect("no pulse")))) - ("seek/sample" [t: f64] Some(Self::SeekSample(t.expect("no sample")))) - ("set/bpm" [t: f64] Some(Self::SetBpm(t.expect("no bpm")))) - ("set/sync" [t: f64] Some(Self::SetSync(t.expect("no sync")))) - ("set/quant" [t: f64] Some(Self::SetQuant(t.expect("no quant")))) -}); - impl Command for ClockCommand { fn execute (self, state: &mut T) -> Perhaps { - self.execute(state.clock_mut()) + self.execute(state.clock_mut()) // awesome } } -impl Command for ClockCommand { - fn execute (self, state: &mut Clock) -> Perhaps { - use ClockCommand::*; - match self { - Play(start) => state.play_from(start)?, - Pause(pause) => state.pause_at(pause)?, - SeekUsec(usec) => state.playhead.update_from_usec(usec), - SeekSample(sample) => state.playhead.update_from_sample(sample), - SeekPulse(pulse) => state.playhead.update_from_pulse(pulse), - SetBpm(bpm) => return Ok(Some(SetBpm(state.timebase().bpm.set(bpm)))), - SetQuant(quant) => return Ok(Some(SetQuant(state.quant.set(quant)))), - SetSync(sync) => return Ok(Some(SetSync(state.sync.set(sync)))), - }; +#[tengri_proc::command(Clock)] +impl ClockCommand { + fn play (state: &mut Clock, position: Option) -> Perhaps { + state.play_from(position)?; + Ok(None) // TODO Some(Pause(previousPosition)) + } + fn pause (state: &mut Clock, position: Option) -> Perhaps { + state.pause_at(position)?; Ok(None) } + fn toggle_playback (state: &mut Clock, position: Option) -> Perhaps { + if state.is_rolling() { + state.pause_at(position)?; + } else { + state.play_from(position)?; + } + Ok(None) + } + fn seek_usec (state: &mut Clock, usec: f64) -> Perhaps { + state.playhead.update_from_usec(usec); + Ok(None) + } + fn seek_sample (state: &mut Clock, sample: f64) -> Perhaps { + state.playhead.update_from_sample(sample); + Ok(None) + } + fn seek_pulse (state: &mut Clock, pulse: f64) -> Perhaps { + state.playhead.update_from_pulse(pulse); + Ok(None) + } + fn set_bpm (state: &mut Clock, bpm: f64) -> Perhaps { + Ok(Some(Self::SetBpm { bpm: state.timebase().bpm.set(bpm) })) + } + fn set_quant (state: &mut Clock, quant: f64) -> Perhaps { + Ok(Some(Self::SetQuant { quant: state.quant.set(quant) })) + } + fn set_sync (state: &mut Clock, sync: f64) -> Perhaps { + Ok(Some(Self::SetSync { sync: state.sync.set(sync) })) + } } diff --git a/crates/device/src/sampler/sampler_api.rs b/crates/device/src/sampler/sampler_api.rs index f80a7247..e0bc58f4 100644 --- a/crates/device/src/sampler/sampler_api.rs +++ b/crates/device/src/sampler/sampler_api.rs @@ -1,72 +1,165 @@ use crate::*; -expose!([self: Sampler] - ([Arc]) - ([MaybeSample]) - ([PathBuf]) - ([f32]) - ([u7] - (":pitch" (self.note_pos() as u8).into()) // TODO - (":sample" (self.note_pos() as u8).into())) - ([usize] - (":sample-up" self.note_pos().min(119) + 8) - (":sample-down" self.note_pos().max(8) - 8) - (":sample-left" self.note_pos().min(126) + 1) - (":sample-right" self.note_pos().max(1) - 1))); - -impose!([state: Sampler] - (FileBrowserCommand: - ("begin" [] Some(Self::Begin)) - ("cancel" [] Some(Self::Cancel)) - ("confirm" [] Some(Self::Confirm)) - ("select" [i: usize] Some(Self::Select(i.expect("no index")))) - ("chdir" [p: PathBuf] Some(Self::Chdir(p.expect("no path")))) - ("filter" [f: Arc] Some(Self::Filter(f.expect("no filter"))))) - (SamplerCommand: - ("import" [,..a] - FileBrowserCommand::try_from_expr(state, a).map(Self::Import)) - ("select" [i: usize] - Some(Self::Select(i.expect("no index")))) - ("record/begin" [i: u7] - Some(Self::RecordBegin(i.expect("no index")))) - ("record/cancel" [] - Some(Self::RecordCancel)) - ("record/finish" [] - Some(Self::RecordFinish)) - ("set/sample" [i: u7, s: MaybeSample] - Some(Self::SetSample(i.expect("no index"), s.expect("no sampler")))) - ("set/start" [i: u7, s: usize] - Some(Self::SetStart(i.expect("no index"), s.expect("no start")))) - ("set/gain" [i: u7, g: f32] - Some(Self::SetGain(i.expect("no index"), g.expect("no gain")))) - ("note/on" [p: u7, v: u7] - Some(Self::NoteOn(p.expect("no pitch"), v.expect("no velocity")))) - ("note/off" [p: u7] - Some(Self::NoteOff(p.expect("no pitch")))))); - macro_rules! cmd { ($cmd:expr) => {{ $cmd; None }}; } macro_rules! cmd_todo { ($msg:literal) => {{ println!($msg); None }}; } -defcom!([self, state: Sampler] - (SamplerCommand - (Select [i: usize] Some(Self::Select(state.set_note_pos(i)))) - (RecordBegin [p: u7] cmd!(state.begin_recording(p.as_int() as usize))) - (RecordCancel [] cmd!(state.cancel_recording())) - (RecordFinish [] cmd!(state.finish_recording())) - (SetStart [p: u7, frame: usize] cmd_todo!("\n\rtodo: {self:?}")) - (SetGain [p: u7, gain: f32] cmd_todo!("\n\rtodo: {self:?}")) - (NoteOn [p: u7, velocity: u7] cmd_todo!("\n\rtodo: {self:?}")) - (NoteOff [p: u7] cmd_todo!("\n\rtodo: {self:?}")) - (SetSample [p: u7, s: MaybeSample] Some(Self::SetSample(p, state.set_sample(p, s)))) - (Import [c: FileBrowserCommand] match c { - 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 - }, - _ => { - println!("\n\rtodo: import: filebrowser: {c:?}"); - None - } - }))); +#[tengri_proc::expose] +impl Sampler { + //fn file_browser_filter (&self) -> Arc { + //todo!() + //} + //fn file_browser_path (&self) -> PathBuf { + //todo!(); + //} + ///// Immutable reference to sample at cursor. + //fn sample_selected (&self) -> MaybeSample { + //for (i, sample) in self.mapped.iter().enumerate() { + //if i == self.cursor().0 { + //return sample.as_ref() + //} + //} + //for (i, sample) in self.unmapped.iter().enumerate() { + //if i + self.mapped.len() == self.cursor().0 { + //return Some(sample) + //} + //} + //None + //} + //fn sample_gain (&self) -> f32 { + //todo!() + //} + //fn sample_above () -> usize { + //self.note_pos().min(119) + 8 + //} + //fn sample_below () -> usize { + //self.note_pos().max(8) - 8 + //} + //fn sample_to_left () -> usize { + //self.note_pos().min(126) + 1 + //} + //fn sample_to_right () -> usize { + //self.note_pos().max(1) - 1 + //} + //fn selected_pitch () -> u7 { + //(self.note_pos() as u8).into() // TODO + //} + //fn selected_sample () -> u7 { // TODO + //(self.note_pos() as u8).into() + //} +} + +#[tengri_proc::command(Sampler)] +impl SamplerCommand { + //fn select (&self, state: &mut Sampler, i: usize) -> Option { + //Self::Select(state.set_note_pos(i)) + //} + ///// Assign sample to pitch + //fn set (&self, pitch: u7, sample: MaybeSample) -> Option { + //let i = pitch.as_int() as usize; + //let old = self.mapped[i].clone(); + //self.mapped[i] = sample; + //Some(Self::Set(old)) + //} + //fn record_begin (&self, state: &mut Sampler, pitch: u7) -> Option { + //self.recording = Some(( + //pitch.as_int() as usize, + //Arc::new(RwLock::new(Sample::new("Sample", 0, 0, vec![vec![];self.audio_ins.len()]))) + //)); + //None + //} + //fn record_cancel (&self, state: &mut Sampler) -> Option { + //self.recording = None; + //None + //} + //fn record_finish (&self, state: &mut Sampler) -> Option { + //let recording = self.recording.take(); + //let _sample = if let Some((index, sample)) = recording { + //let old = self.mapped[index].clone(); + //self.mapped[index] = Some(sample); + //old + //} else { + //None + //}; + //None + //} + //fn set_start (&self, state: &mut Sampler, pitch: u7, frame: usize) -> Option { + //todo!() + //} + //fn set_gain (&self, state: &mut Sampler, pitch: u7, g: f32) -> Option { + //todo!() + //} + //fn note_on (&self, state: &mut Sampler, pitch: u7, v: u7) -> Option { + //todo!() + //} + //fn note_off (&self, state: &mut Sampler, pitch: u7) -> Option { + //todo!() + //} + //fn set_sample (&self, state: &mut Sampler, pitch: u7, s: MaybeSample) -> Option { + //Some(Self::SetSample(p, state.set_sample(p, s))) + //} + //fn import (&self, state: &mut Sampler, c: FileBrowserCommand) -> Option { + //match c { + //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 + //}, + //_ => { + //println!("\n\rtodo: import: filebrowser: {c:?}"); + //None + //} + //} + //} + ////(Select [i: usize] Some(Self::Select(state.set_note_pos(i)))) + ////(RecordBegin [p: u7] cmd!(state.begin_recording(p.as_int() as usize))) + ////(RecordCancel [] cmd!(state.cancel_recording())) + ////(RecordFinish [] cmd!(state.finish_recording())) + ////(SetStart [p: u7, frame: usize] cmd_todo!("\n\rtodo: {self:?}")) + ////(SetGain [p: u7, gain: f32] cmd_todo!("\n\rtodo: {self:?}")) + ////(NoteOn [p: u7, velocity: u7] cmd_todo!("\n\rtodo: {self:?}")) + ////(NoteOff [p: u7] cmd_todo!("\n\rtodo: {self:?}")) + ////(SetSample [p: u7, s: MaybeSample] Some(Self::SetSample(p, state.set_sample(p, s)))) + ////(Import [c: FileBrowserCommand] match c { + ////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 + ////}, + ////_ => { + ////println!("\n\rtodo: import: filebrowser: {c:?}"); + ////None + ////} + ////}))); + ////("import" [,..a] + ////FileBrowserCommand::try_from_expr(state, a).map(Self::Import)) + ////("select" [i: usize] + ////Some(Self::Select(i.expect("no index")))) + ////("record/begin" [i: u7] + ////Some(Self::RecordBegin(i.expect("no index")))) + ////("record/cancel" [] + ////Some(Self::RecordCancel)) + ////("record/finish" [] + ////Some(Self::RecordFinish)) + ////("set/sample" [i: u7, s: MaybeSample] + ////Some(Self::SetSample(i.expect("no index"), s.expect("no sampler")))) + ////("set/start" [i: u7, s: usize] + ////Some(Self::SetStart(i.expect("no index"), s.expect("no start")))) + ////("set/gain" [i: u7, g: f32] + ////Some(Self::SetGain(i.expect("no index"), g.expect("no gain")))) + ////("note/on" [p: u7, v: u7] + ////Some(Self::NoteOn(p.expect("no pitch"), v.expect("no velocity")))) + ////("note/off" [p: u7] + ////Some(Self::NoteOff(p.expect("no pitch")))))); +} + +#[tengri_proc::command(Sampler)] +impl FileBrowserCommand { + //("begin" [] Some(Self::Begin)) + //("cancel" [] Some(Self::Cancel)) + //("confirm" [] Some(Self::Confirm)) + //("select" [i: usize] Some(Self::Select(i.expect("no index")))) + //("chdir" [p: PathBuf] Some(Self::Chdir(p.expect("no path")))) + //("filter" [f: Arc] Some(Self::Filter(f.expect("no filter"))))) +} diff --git a/crates/device/src/sampler/sampler_model.rs b/crates/device/src/sampler/sampler_model.rs index 4d1f1b6b..2a55e143 100644 --- a/crates/device/src/sampler/sampler_model.rs +++ b/crates/device/src/sampler/sampler_model.rs @@ -77,50 +77,10 @@ impl Sampler { ..Default::default() }) } - pub fn cancel_recording (&mut self) { - self.recording = None; - } - pub fn begin_recording (&mut self, index: usize) { - self.recording = Some(( - index, - Arc::new(RwLock::new(Sample::new("Sample", 0, 0, vec![vec![];self.audio_ins.len()]))) - )); - } - pub fn finish_recording (&mut self) -> MaybeSample { - let recording = self.recording.take(); - if let Some((index, sample)) = recording { - let old = self.mapped[index].clone(); - self.mapped[index] = Some(sample); - old - } else { - None - } - } - /// Immutable reference to sample at cursor. - pub fn sample (&self) -> Option<&Arc>> { - for (i, sample) in self.mapped.iter().enumerate() { - if i == self.cursor().0 { - return sample.as_ref() - } - } - for (i, sample) in self.unmapped.iter().enumerate() { - if i + self.mapped.len() == self.cursor().0 { - return Some(sample) - } - } - None - } /// Value of cursor pub fn cursor (&self) -> (usize, usize) { (self.cursor.0.load(Relaxed), self.cursor.1.load(Relaxed)) } - /// Assign sample to pitch - pub fn set_sample (&mut self, pitch: u7, sample: MaybeSample) -> MaybeSample { - let i = pitch.as_int() as usize; - let old = self.mapped[i].clone(); - self.mapped[i] = sample; - old - } } impl NoteRange for Sampler { @@ -133,13 +93,19 @@ impl NoteRange for Sampler { } impl NotePoint for Sampler { - fn note_len (&self) -> usize { - 0 /*TODO?*/ + fn note_len (&self) -> &AtomicUsize { + unreachable!(); + } + fn get_note_len (&self) -> usize { + 0 } fn set_note_len (&self, x: usize) -> usize { 0 /*TODO?*/ } - fn note_pos (&self) -> usize { + fn note_pos (&self) -> &AtomicUsize { + &self.note_pt + } + fn get_note_pos (&self) -> usize { self.note_pt.load(Relaxed) } fn set_note_pos (&self, x: usize) -> usize { @@ -189,4 +155,3 @@ pub enum SamplerMode { // Load sample from path Import(usize, FileBrowser), } - diff --git a/crates/device/src/sampler/sampler_view.rs b/crates/device/src/sampler/sampler_view.rs index 880d670d..2b5c3fad 100644 --- a/crates/device/src/sampler/sampler_view.rs +++ b/crates/device/src/sampler/sampler_view.rs @@ -56,9 +56,9 @@ impl Sampler { pub fn view_list <'a, T: NotePoint + NoteRange> ( &'a self, compact: bool, editor: &T ) -> impl Content + 'a { - let note_lo = editor.note_lo().load(Relaxed); - let note_pt = editor.note_pos(); - let note_hi = editor.note_hi(); + let note_lo = editor.get_note_lo(); + let note_pt = editor.get_note_pos(); + let note_hi = editor.get_note_hi(); Fixed::x(12, Map::south( 1, move||(note_lo..=note_hi).rev(), @@ -80,6 +80,7 @@ impl Sampler { Tui::fg_bg(fg, bg, format!("{note:3} {}", self.view_list_item(note, compact))) })) } + pub fn view_list_item (&self, note: usize, compact: bool) -> String { if compact { String::default() @@ -87,6 +88,7 @@ impl Sampler { draw_list_item(&self.mapped[note]) } } + pub fn view_sample (&self, note_pt: usize) -> impl Content + use<'_> { Outer(true, Style::default().fg(Tui::g(96))).enclose(draw_viewer(if let Some((_, sample)) = &self.recording { Some(sample) @@ -96,6 +98,7 @@ impl Sampler { None })) } + pub fn status (&self, index: usize) -> impl Content { draw_status(self.mapped[index].as_ref()) } diff --git a/crates/device/src/sequencer/seq_view.rs b/crates/device/src/sequencer/seq_view.rs index ae6440b3..5635363e 100644 --- a/crates/device/src/sequencer/seq_view.rs +++ b/crates/device/src/sequencer/seq_view.rs @@ -11,9 +11,9 @@ pub trait MidiViewer: HasSize + MidiRange + MidiPoint + Debug + Send + S } /// Make sure cursor is within note range fn autoscroll (&self) { - let note_pos = self.note_pos().min(127); - let note_lo = self.note_lo().get(); - let note_hi = self.note_hi(); + let note_pos = self.get_note_pos().min(127); + let note_lo = self.get_note_lo(); + let note_hi = self.get_note_hi(); if note_pos < note_lo { self.note_lo().set(note_pos); } else if note_pos > note_hi { @@ -23,9 +23,9 @@ pub trait MidiViewer: HasSize + MidiRange + MidiPoint + Debug + Send + S /// Make sure time range is within display fn autozoom (&self) { if self.time_lock().get() { - let time_len = self.time_len().get(); - let time_axis = self.time_axis().get(); - let time_zoom = self.time_zoom().get(); + let time_len = self.get_time_len(); + let time_axis = self.get_time_axis(); + let time_zoom = self.get_time_zoom(); loop { let time_zoom = self.time_zoom().get(); let time_area = time_axis * time_zoom; diff --git a/crates/engine/src/note/note_point.rs b/crates/engine/src/note/note_point.rs index 8de6ca51..310cef87 100644 --- a/crates/engine/src/note/note_point.rs +++ b/crates/engine/src/note/note_point.rs @@ -20,53 +20,60 @@ impl Default for MidiPointModel { } } -pub trait NotePoint { - /// Get the current length of the note cursor. - fn note_len (&self) -> usize; - /// Set the length of the note cursor, returning the previous value. - fn set_note_len (&self, x: usize) -> usize; - /// Get the current pitch of the note cursor. - fn note_pos (&self) -> usize; - /// Set the current pitch fo the note cursor, returning the previous value. - fn set_note_pos (&self, x: usize) -> usize; -} - -pub trait TimePoint { - /// Get the current time position of the note cursor. - fn time_pos (&self) -> usize; - /// Set the current time position of the note cursor, returning the previous value. - fn set_time_pos (&self, x: usize) -> usize; -} - -pub trait MidiPoint: NotePoint + TimePoint { - /// Get the current end of the note cursor. - fn note_end (&self) -> usize { - self.time_pos() + self.note_len() - } -} - -impl MidiPoint for T {} - impl NotePoint for MidiPointModel { - fn note_len (&self) -> usize { - self.note_len.load(Relaxed) + fn note_len (&self) -> &AtomicUsize { + &self.note_len } - fn set_note_len (&self, x: usize) -> usize { - self.note_len.swap(x, Relaxed) - } - fn note_pos (&self) -> usize { - self.note_pos.load(Relaxed).min(127) - } - fn set_note_pos (&self, x: usize) -> usize { - self.note_pos.swap(x.min(127), Relaxed) + fn note_pos (&self) -> &AtomicUsize { + &self.note_pos } } impl TimePoint for MidiPointModel { - fn time_pos (&self) -> usize { - self.time_pos.load(Relaxed) - } - fn set_time_pos (&self, x: usize) -> usize { - self.time_pos.swap(x, Relaxed) + fn time_pos (&self) -> &AtomicUsize { + self.time_pos.as_ref() } } + +pub trait NotePoint { + fn note_len (&self) -> &AtomicUsize; + /// Get the current length of the note cursor. + fn get_note_len (&self) -> usize { + self.note_len().load(Relaxed) + } + /// Set the length of the note cursor, returning the previous value. + fn set_note_len (&self, x: usize) -> usize { + self.note_len().swap(x, Relaxed) + } + + fn note_pos (&self) -> &AtomicUsize; + /// Get the current pitch of the note cursor. + fn get_note_pos (&self) -> usize { + self.note_pos().load(Relaxed).min(127) + } + /// Set the current pitch fo the note cursor, returning the previous value. + fn set_note_pos (&self, x: usize) -> usize { + self.note_pos().swap(x.min(127), Relaxed) + } +} + +pub trait TimePoint { + fn time_pos (&self) -> &AtomicUsize; + /// Get the current time position of the note cursor. + fn get_time_pos (&self) -> usize { + self.time_pos().load(Relaxed) + } + /// Set the current time position of the note cursor, returning the previous value. + fn set_time_pos (&self, x: usize) -> usize { + self.time_pos().swap(x, Relaxed) + } +} + +pub trait MidiPoint: NotePoint + TimePoint { + /// Get the current end of the note cursor. + fn get_note_end (&self) -> usize { + self.get_time_pos() + self.get_note_len() + } +} + +impl MidiPoint for T {} diff --git a/crates/engine/src/note/note_range.rs b/crates/engine/src/note/note_range.rs index 308a4ae2..40e7acc0 100644 --- a/crates/engine/src/note/note_range.rs +++ b/crates/engine/src/note/note_range.rs @@ -1,4 +1,5 @@ use crate::*; +use std::sync::atomic::Ordering; #[derive(Debug, Clone)] pub struct MidiRangeModel { @@ -28,20 +29,53 @@ from!(|data:(usize, bool)|MidiRangeModel = Self { }); pub trait TimeRange { - fn time_len (&self) -> &AtomicUsize; - fn time_zoom (&self) -> &AtomicUsize; - fn time_lock (&self) -> &AtomicBool; + fn time_len (&self) -> &AtomicUsize; + fn get_time_len (&self) -> usize { + self.time_len().load(Ordering::Relaxed) + } + fn time_zoom (&self) -> &AtomicUsize; + fn get_time_zoom (&self) -> usize { + self.time_zoom().load(Ordering::Relaxed) + } + fn set_time_zoom (&self, value: usize) -> usize { + self.time_zoom().swap(value, Ordering::Relaxed) + } + fn time_lock (&self) -> &AtomicBool; + fn get_time_lock (&self) -> bool { + self.time_lock().load(Ordering::Relaxed) + } + fn set_time_lock (&self, value: bool) -> bool { + self.time_lock().swap(value, Ordering::Relaxed) + } fn time_start (&self) -> &AtomicUsize; - fn time_axis (&self) -> &AtomicUsize; - fn time_end (&self) -> usize { + fn get_time_start (&self) -> usize { + self.time_start().load(Ordering::Relaxed) + } + fn set_time_start (&self, value: usize) -> usize { + self.time_start().swap(value, Ordering::Relaxed) + } + fn time_axis (&self) -> &AtomicUsize; + fn get_time_axis (&self) -> usize { + self.time_axis().load(Ordering::Relaxed) + } + fn get_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 { + fn note_lo (&self) -> &AtomicUsize; + fn get_note_lo (&self) -> usize { + self.note_lo().load(Ordering::Relaxed) + } + fn set_note_lo (&self, x: usize) -> usize { + self.note_lo().swap(x, Ordering::Relaxed) + } + fn note_axis (&self) -> &AtomicUsize; + fn get_note_axis (&self) -> usize { + self.note_axis().load(Ordering::Relaxed) + } + fn get_note_hi (&self) -> usize { (self.note_lo().get() + self.note_axis().get().saturating_sub(1)).min(127) } } diff --git a/deps/rust-jack b/deps/rust-jack index caace909..4cbf155d 160000 --- a/deps/rust-jack +++ b/deps/rust-jack @@ -1 +1 @@ -Subproject commit caace9096c9df9c288b14a7c0ea1241d8da2baa1 +Subproject commit 4cbf155d8ed222c140c11770474832ddfa52bcd7 diff --git a/deps/tengri b/deps/tengri index b543c43e..22d63eed 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit b543c43e68154f049019da648064f36af1537434 +Subproject commit 22d63eed9c9cb5bed5016d851f90773e0f60280d diff --git a/target/.gitkeep b/target/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/tek.rs b/tek.rs deleted file mode 100644 index e69de29b..00000000