diff --git a/.old/demo.rs.old b/.old/demo.rs.old deleted file mode 100644 index 6b205580..00000000 --- a/.old/demo.rs.old +++ /dev/null @@ -1,112 +0,0 @@ -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/.old/scratch.rs b/.scratch.rs similarity index 100% rename from .old/scratch.rs rename to .scratch.rs diff --git a/Cargo.lock b/Cargo.lock index 69449423..68f62568 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -35,15 +35,15 @@ checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "ahash" -version = "0.8.12" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom 0.3.2", + "getrandom 0.2.16", "once_cell", "version_check", - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -176,9 +176,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.75" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", "cfg-if", @@ -968,7 +968,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.9.0", "libc", - "redox_syscall 0.5.12", + "redox_syscall 0.5.11", ] [[package]] @@ -1466,7 +1466,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.12", + "redox_syscall 0.5.11", "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", + "zerocopy 0.8.25", ] [[package]] @@ -1790,9 +1790,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.12" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" +checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" dependencies = [ "bitflags 2.9.0", ] @@ -2301,7 +2301,6 @@ dependencies = [ "symphonia", "tek_engine", "tengri", - "tengri_proc", "uuid", "wavers", "winit", @@ -2368,7 +2367,6 @@ dependencies = [ name = "tengri_proc" version = "0.13.0" dependencies = [ - "heck", "proc-macro2", "quote", "syn", @@ -3134,9 +3132,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.7.10" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" +checksum = "d9fb597c990f03753e08d3c29efbfcf2019a003b4bf4ba19225c158e1549f0f3" dependencies = [ "memchr", ] @@ -3207,13 +3205,33 @@ 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", + "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", ] [[package]] diff --git a/README.md b/README.md index 65fb7107..6eb7d6b9 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: - * [x] esc: options menu - * [x] f1: help/command list + * [ ] esc: options menu + * [ ] f1: help/command list * [ ] f2: rename * [ ] f6: save * [ ] f9: load @@ -68,11 +68,11 @@ paru -S tek requires docker. ``` -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 +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 ``` ## design goals diff --git a/crates/app/src/api.rs b/crates/app/src/api.rs index aa33ef34..4f7504bf 100644 --- a/crates/app/src/api.rs +++ b/crates/app/src/api.rs @@ -17,804 +17,591 @@ handle!(TuiIn: |self: Tek, input|Ok(if let Some(command) = self.config.keys.comm None })); -#[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 { +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 { index } else { 0 - } - } - fn device_kind_prev (&self) -> usize { - if let Some(Dialog::Device(index)) = self.dialog { + }) + (":device-kind-prev" if let Some(Dialog::Device(index)) = self.dialog { index.overflowing_sub(1).0.min(self.device_kinds().len().saturating_sub(1)) } else { 0 - } - } - fn device_kind_next (&self) -> usize { - if let Some(Dialog::Device(index)) = self.dialog { + }) + (":device-kind-next" 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()) } -#[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 { +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 { // autoselect: automatically load selected clip in editor - PoolCommand::Select { .. } | - // autocolor: update color in all places simultaneously - PoolCommand::Clip { command: PoolClipCommand::SetColor { .. } } => + // autocolor: update color in all places simultaneously + PoolCommand::Select(_) | PoolCommand::Clip(PoolClipCommand::SetColor(_, _)) => editor.set_clip(pool.clip().as_ref()), _ => {} - }); - undo - } else { + } + }; + 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(); + //} None }) - } - 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) - } + (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), } -#[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 - }, - _ => 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 - }, - _ => 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 - }, - _ => 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); - } - 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!(); - } - 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() { +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(); - *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) + *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, } -#[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; +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 }, + }, + Dec => match focus { + Bar => { *length = length.saturating_sub(4 * PPQ) }, + Beat => { *length = length.saturating_sub(PPQ) }, + Tick => { *length = length.saturating_sub(1) }, + }, + 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)) + }, + _ => unreachable!() } - 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 { + } else { 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) - } -} + None +}); diff --git a/crates/app/src/model/editor.rs b/crates/app/src/model/editor.rs index fec878b1..355b0e8c 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.get_time_pos(); - let note_pos = self.get_note_pos(); - let note_len = self.get_note_len(); + let note_start = self.time_pos(); + let note_pos = self.note_pos(); + let note_len = self.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,13 +93,12 @@ 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.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()); + 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()); Bsp::e( FieldH(color, "Time", format!("{length}/{time_zoom}+{time_pos} {time_lock}")), FieldH(color, "Note", format!("{note_name} {note_pos} {note_len}")), @@ -121,12 +120,15 @@ impl NoteRange for MidiEditor { } impl NotePoint for MidiEditor { - fn note_len (&self) -> &AtomicUsize { self.mode.note_len() } - fn note_pos (&self) -> &AtomicUsize { self.mode.note_pos() } + 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) } } impl TimePoint for MidiEditor { - fn time_pos (&self) -> &AtomicUsize { self.mode.time_pos() } + fn time_pos (&self) -> usize { self.mode.time_pos() } + fn set_time_pos (&self, x: usize) -> usize { self.mode.set_time_pos(x) } } impl MidiViewer for MidiEditor { diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs index 2f1bfdd9..5778861b 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -4,9 +4,13 @@ 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(); @@ -15,6 +19,8 @@ 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(); @@ -23,24 +29,38 @@ 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().get_note_pos())) + self.sampler().map(|s|s.view_sample(self.editor().unwrap().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), "")), @@ -56,14 +76,13 @@ 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)) @@ -115,6 +134,69 @@ 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)|{ @@ -1055,9 +1137,9 @@ impl PianoHorizontal { } } fn notes (&self) -> impl Content { - let time_start = self.get_time_start(); - let note_lo = self.get_note_lo(); - let note_hi = self.get_note_hi(); + let time_start = self.time_start().get(); + let note_lo = self.note_lo().get(); + let note_hi = self.note_hi(); let buffer = self.buffer.clone(); ThunkRender::new(move|to: &mut TuiOut|{ let source = buffer.read().unwrap(); @@ -1086,14 +1168,14 @@ impl PianoHorizontal { }) } fn cursor (&self) -> impl Content { - 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)); + 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(); 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) { @@ -1119,9 +1201,9 @@ impl PianoHorizontal { fn keys (&self) -> impl Content { let state = self; let color = state.color; - let note_lo = state.get_note_lo(); - let note_hi = state.get_note_hi(); - let note_pos = state.get_note_pos(); + let note_lo = state.note_lo().get(); + let note_hi = state.note_hi(); + let note_pos = state.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()); @@ -1171,12 +1253,15 @@ impl NoteRange for PianoHorizontal { } impl NotePoint for PianoHorizontal { - fn note_len (&self) -> &AtomicUsize { self.point.note_len() } - fn note_pos (&self) -> &AtomicUsize { self.point.note_pos() } + 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) } } impl TimePoint for PianoHorizontal { - fn time_pos (&self) -> &AtomicUsize { self.point.time_pos() } + fn time_pos (&self) -> usize { self.point.time_pos() } + fn set_time_pos (&self, x: usize) -> usize { self.point.set_time_pos(x) } } impl MidiViewer for PianoHorizontal { @@ -1195,8 +1280,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.get_note_len(); - let time_zoom = self.get_time_zoom(); + let note_len = self.note_len(); + let time_zoom = self.time_zoom().get(); 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 f07255a4..a84b08a9 100644 --- a/crates/device/Cargo.toml +++ b/crates/device/Cargo.toml @@ -4,16 +4,13 @@ edition = { workspace = true } version = { workspace = true } [dependencies] -tengri = { workspace = true } -tengri_proc = { workspace = true } - +tengri = { 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 e9161ec4..a2e5927f 100644 --- a/crates/device/src/clock/clock_api.rs +++ b/crates/device/src/clock/clock_api.rs @@ -1,61 +1,55 @@ use crate::*; -#[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!() - } +#[derive(Clone, Debug, PartialEq)] +pub enum ClockCommand { + Play(Option), + Pause(Option), + SeekUsec(f64), + SeekSample(f64), + SeekPulse(f64), + SetBpm(f64), + SetQuant(f64), + SetSync(f64), } +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()) // awesome + self.execute(state.clock_mut()) } } -#[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)?; +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)))), + }; 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 e0bc58f4..f80a7247 100644 --- a/crates/device/src/sampler/sampler_api.rs +++ b/crates/device/src/sampler/sampler_api.rs @@ -1,165 +1,72 @@ 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 }}; } -#[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"))))) -} +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 + } + }))); diff --git a/crates/device/src/sampler/sampler_model.rs b/crates/device/src/sampler/sampler_model.rs index 2a55e143..4d1f1b6b 100644 --- a/crates/device/src/sampler/sampler_model.rs +++ b/crates/device/src/sampler/sampler_model.rs @@ -77,10 +77,50 @@ 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 { @@ -93,19 +133,13 @@ impl NoteRange for Sampler { } impl NotePoint for Sampler { - fn note_len (&self) -> &AtomicUsize { - unreachable!(); - } - fn get_note_len (&self) -> usize { - 0 + fn note_len (&self) -> usize { + 0 /*TODO?*/ } fn set_note_len (&self, x: usize) -> usize { 0 /*TODO?*/ } - fn note_pos (&self) -> &AtomicUsize { - &self.note_pt - } - fn get_note_pos (&self) -> usize { + fn note_pos (&self) -> usize { self.note_pt.load(Relaxed) } fn set_note_pos (&self, x: usize) -> usize { @@ -155,3 +189,4 @@ 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 2b5c3fad..880d670d 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.get_note_lo(); - let note_pt = editor.get_note_pos(); - let note_hi = editor.get_note_hi(); + let note_lo = editor.note_lo().load(Relaxed); + let note_pt = editor.note_pos(); + let note_hi = editor.note_hi(); Fixed::x(12, Map::south( 1, move||(note_lo..=note_hi).rev(), @@ -80,7 +80,6 @@ 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() @@ -88,7 +87,6 @@ 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) @@ -98,7 +96,6 @@ 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 5635363e..ae6440b3 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.get_note_pos().min(127); - let note_lo = self.get_note_lo(); - let note_hi = self.get_note_hi(); + let note_pos = self.note_pos().min(127); + let note_lo = self.note_lo().get(); + let note_hi = self.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.get_time_len(); - let time_axis = self.get_time_axis(); - let time_zoom = self.get_time_zoom(); + let time_len = self.time_len().get(); + let time_axis = self.time_axis().get(); + let time_zoom = self.time_zoom().get(); loop { let time_zoom = self.time_zoom().get(); let time_area = time_axis * time_zoom; diff --git a/crates/app/examples/midi_import.rs b/crates/engine/examples/midi-import.rs similarity index 97% rename from crates/app/examples/midi_import.rs rename to crates/engine/examples/midi-import.rs index 90b848a1..f27beb2b 100644 --- a/crates/app/examples/midi_import.rs +++ b/crates/engine/examples/midi-import.rs @@ -1,4 +1,4 @@ -use tek::*; +use tek_midi::*; use tengri::input::*; use std::sync::*; struct ExampleClips(Arc>>>>); diff --git a/crates/engine/src/note/note_point.rs b/crates/engine/src/note/note_point.rs index 310cef87..8de6ca51 100644 --- a/crates/engine/src/note/note_point.rs +++ b/crates/engine/src/note/note_point.rs @@ -20,60 +20,53 @@ impl Default for MidiPointModel { } } -impl NotePoint for MidiPointModel { - fn note_len (&self) -> &AtomicUsize { - &self.note_len - } - fn note_pos (&self) -> &AtomicUsize { - &self.note_pos - } -} - -impl TimePoint for MidiPointModel { - 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) - } + fn note_len (&self) -> usize; /// 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; + fn set_note_len (&self, x: usize) -> usize; /// Get the current pitch of the note cursor. - fn get_note_pos (&self) -> usize { - self.note_pos().load(Relaxed).min(127) - } + 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 { - self.note_pos().swap(x.min(127), Relaxed) - } + fn set_note_pos (&self, x: usize) -> usize; } 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) - } + 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 { - self.time_pos().swap(x, Relaxed) - } + fn set_time_pos (&self, x: usize) -> usize; } 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() + 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 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) + } +} + +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) + } +} diff --git a/crates/engine/src/note/note_range.rs b/crates/engine/src/note/note_range.rs index 40e7acc0..308a4ae2 100644 --- a/crates/engine/src/note/note_range.rs +++ b/crates/engine/src/note/note_range.rs @@ -1,5 +1,4 @@ use crate::*; -use std::sync::atomic::Ordering; #[derive(Debug, Clone)] pub struct MidiRangeModel { @@ -29,53 +28,20 @@ from!(|data:(usize, bool)|MidiRangeModel = Self { }); pub trait TimeRange { - 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_len (&self) -> &AtomicUsize; + fn time_zoom (&self) -> &AtomicUsize; + fn time_lock (&self) -> &AtomicBool; fn time_start (&self) -> &AtomicUsize; - 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 { + 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 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 { + 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) } } diff --git a/deps/rust-jack b/deps/rust-jack index 4cbf155d..caace909 160000 --- a/deps/rust-jack +++ b/deps/rust-jack @@ -1 +1 @@ -Subproject commit 4cbf155d8ed222c140c11770474832ddfa52bcd7 +Subproject commit caace9096c9df9c288b14a7c0ea1241d8da2baa1 diff --git a/deps/tengri b/deps/tengri index 22d63eed..b543c43e 160000 --- a/deps/tengri +++ b/deps/tengri @@ -1 +1 @@ -Subproject commit 22d63eed9c9cb5bed5016d851f90773e0f60280d +Subproject commit b543c43e68154f049019da648064f36af1537434 diff --git a/target/.gitkeep b/target/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/tek.rs b/tek.rs new file mode 100644 index 00000000..e69de29b