diff --git a/Cargo.lock b/Cargo.lock index a07b0cb5..d8661d57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1518,8 +1518,11 @@ dependencies = [ "proptest", "proptest-derive", "rand", - "tek_device", - "tek_engine", + "tek_jack", + "tek_midi", + "tek_plugin", + "tek_sampler", + "tek_time", "tengri", "toml", ] @@ -1533,26 +1536,53 @@ dependencies = [ ] [[package]] -name = "tek_device" +name = "tek_jack" +version = "0.2.1" +dependencies = [ + "jack", +] + +[[package]] +name = "tek_midi" +version = "0.2.1" +dependencies = [ + "midly", + "tek_jack", + "tek_time", + "tengri", + "uuid", +] + +[[package]] +name = "tek_plugin" version = "0.2.1" dependencies = [ "livi", - "symphonia", - "tek_engine", + "tek_jack", + "tek_midi", + "tek_time", + "tengri", +] + +[[package]] +name = "tek_sampler" +version = "0.2.1" +dependencies = [ + "symphonia", + "tek_jack", + "tek_midi", + "tek_time", "tengri", - "uuid", "wavers", ] [[package]] -name = "tek_engine" +name = "tek_time" version = "0.2.1" dependencies = [ "atomic_float", - "jack", - "midly", + "tek_jack", "tengri", - "uuid", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 63d91683..215d7797 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,10 +5,13 @@ version = "0.2.1" [workspace] resolver = "2" members = [ - "./crates/engine", - "./crates/device", "./crates/app", "./crates/cli", + "./crates/jack", + "./crates/midi", + "./crates/plugin", + "./crates/sampler", + "./crates/time" ] exclude = [ "./deps/tengri" @@ -34,10 +37,13 @@ path = "./deps/rust-jack" #default-features = false [workspace.dependencies] -tek_device = { path = "./crates/device" } -tek_engine = { path = "./crates/engine" } -tek = { path = "./crates/app" } -tek_cli = { path = "./crates/cli" } +tek = { path = "./crates/app" } +tek_cli = { path = "./crates/cli" } +tek_jack = { path = "./crates/jack" } +tek_midi = { path = "./crates/midi" } +tek_plugin = { path = "./crates/plugin", default-features = false } +tek_sampler = { path = "./crates/sampler" } +tek_time = { path = "./crates/time" } atomic_float = { version = "1.0.0" } backtrace = { version = "0.3.72" } diff --git a/crates/app/Cargo.toml b/crates/app/Cargo.toml index 7ed1134b..fda6c38e 100644 --- a/crates/app/Cargo.toml +++ b/crates/app/Cargo.toml @@ -6,8 +6,11 @@ version = { workspace = true } [dependencies] tengri = { workspace = true } -tek_engine = { workspace = true } -tek_device = { workspace = true } +tek_jack = { workspace = true } +tek_time = { workspace = true } +tek_midi = { workspace = true } +tek_sampler = { workspace = true } +tek_plugin = { workspace = true, optional = true } backtrace = { workspace = true } clap = { workspace = true, optional = true } @@ -22,4 +25,4 @@ proptest-derive = { workspace = true } [features] default = ["cli"] cli = ["clap"] -host = ["tek_device/lv2"] +host = ["tek_plugin"] diff --git a/crates/app/src/api.rs b/crates/app/src/api.rs index c540066f..fddb175b 100644 --- a/crates/app/src/api.rs +++ b/crates/app/src/api.rs @@ -23,7 +23,7 @@ expose!([self: Tek] (":track" self.selected.track())) ([MaybeClip] (":clip" match self.selected { - Selection::TrackClip { track, scene } => self.scenes[scene].clips[track].clone(), + Selection::Clip(t, s) => self.scenes[s].clips[t].clone(), _ => None })) ([Selection] @@ -70,7 +70,7 @@ impose!([app: Tek] (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 }) }))) + (t, s) => Self::Select(Selection::Clip(t, s)) }))) (ClipCommand: ("edit" [a: MaybeClip] Some(Self::Edit(a.unwrap()))) diff --git a/crates/app/src/editor.rs b/crates/app/src/editor.rs deleted file mode 100644 index 62c7b812..00000000 --- a/crates/app/src/editor.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod editor_api; -mod editor_model; -mod editor_view; diff --git a/crates/app/src/editor/editor_api.rs b/crates/app/src/editor/editor_api.rs deleted file mode 100644 index 1ac85118..00000000 --- a/crates/app/src/editor/editor_api.rs +++ /dev/null @@ -1,97 +0,0 @@ -use crate::*; - -provide!(bool: |self: MidiEditor| { - ":true" => true, - ":false" => false, - ":time-lock" => self.time_lock().get(), - ":time-lock-toggle" => !self.time_lock().get(), -}); - -provide!(usize: |self: MidiEditor| { - ":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), -}); - -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()))) -}); - -#[derive(Clone, Debug)] pub enum MidiEditCommand { - // TODO: 1-9 seek markers that by default start every 8th of the clip - AppendNote, - PutNote, - DelNote, - SetNoteCursor(usize), - SetNoteLength(usize), - SetNoteScroll(usize), - SetTimeCursor(usize), - SetTimeScroll(usize), - SetTimeZoom(usize), - SetTimeLock(bool), - Show(Option>>), -} - -handle!(TuiIn: |self: MidiEditor, input|Ok(if let Some(command) = self.keys.command(self, input) { - command.execute(self)?; - Some(true) -} else { - None -})); - -impl Command for MidiEditCommand { - fn execute (self, state: &mut MidiEditor) -> Perhaps { - use MidiEditCommand::*; - match self { - Show(clip) => { state.set_clip(clip.as_ref()); }, - DelNote => {}, - PutNote => { state.put_note(false); }, - AppendNote => { state.put_note(true); }, - SetTimeZoom(x) => { state.time_zoom().set(x); state.redraw(); }, - SetTimeLock(x) => { state.time_lock().set(x); }, - SetTimeScroll(x) => { state.time_start().set(x); }, - SetNoteScroll(x) => { state.note_lo().set(x.min(127)); }, - SetNoteLength(x) => { - 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(); - //} - }, - SetTimeCursor(x) => { state.set_time_pos(x); }, - SetNoteCursor(note) => { state.set_note_pos(note.min(127)); }, - //_ => todo!("{:?}", self) - } - Ok(None) - } -} - diff --git a/crates/app/src/lib.rs b/crates/app/src/lib.rs index 4ff7b835..27b509c2 100644 --- a/crates/app/src/lib.rs +++ b/crates/app/src/lib.rs @@ -41,9 +41,6 @@ mod audio; pub use self::audio::*; mod model; pub use self::model::*; mod view; pub use self::view::*; -mod pool; -mod editor; - #[cfg(test)] #[test] fn test_model () { let mut tek = Tek::default(); let _ = tek.clip(); diff --git a/crates/app/src/model.rs b/crates/app/src/model.rs index ab1e0fb9..ce9b5bed 100644 --- a/crates/app/src/model.rs +++ b/crates/app/src/model.rs @@ -121,11 +121,10 @@ impl Tek { /// Add and focus a track pub(crate) fn track_add_focus (&mut self) -> Usually { - use Selection::*; let index = self.track_add(None, None, &[], &[])?.0; self.selected = match self.selected { - Track(_) => Track(index), - TrackClip { track, scene } => TrackClip { track: index, scene }, + Selection::Track(t) => Selection::Track(index), + Selection::Clip(t, s) => Selection::Clip(index, s), _ => self.selected }; Ok(index) @@ -178,11 +177,10 @@ impl Tek { /// Add and focus an empty scene pub fn scene_add_focus (&mut self) -> Usually { - use Selection::*; let index = self.scene_add(None, None)?.0; self.selected = match self.selected { - Scene(_) => Scene(index), - TrackClip { track, scene } => TrackClip { track, scene: index }, + Selection::Scene(s) => Selection::Scene(index), + Selection::Clip(t, s) => Selection::Clip(t, index), _ => self.selected }; Ok(index) @@ -217,15 +215,15 @@ impl Tek { // Create new clip in pool when entering empty cell pub fn clip_auto_create (&mut self) { if let Some(ref pool) = self.pool - && let Selection::TrackClip { track, scene } = self.selected - && let Some(scene) = self.scenes.get_mut(scene) - && let Some(slot) = scene.clips.get_mut(track) + && let Selection::Clip(t, s) = self.selected + && let Some(scene) = self.scenes.get_mut(s) + && let Some(slot) = scene.clips.get_mut(t) && slot.is_none() { let (index, mut clip) = pool.add_new_clip(); // autocolor: new clip colors from scene and track color clip.write().unwrap().color = ItemColor::random_near( - self.tracks[track].color.base.mix( + self.tracks[t].color.base.mix( scene.color.base, 0.5 ), @@ -241,9 +239,9 @@ impl Tek { // Remove clip from arrangement when exiting empty clip editor pub fn clip_auto_remove (&mut self) { if let Some(ref mut pool) = self.pool - && let Selection::TrackClip { track, scene } = self.selected - && let Some(scene) = self.scenes.get_mut(scene) - && let Some(slot) = scene.clips.get_mut(track) + && let Selection::Clip(t, s) = self.selected + && let Some(scene) = self.scenes.get_mut(s) + && let Some(slot) = scene.clips.get_mut(t) && let Some(clip) = slot.as_mut() { let mut swapped = None; @@ -306,10 +304,8 @@ impl Tek { // autoedit: load focused clip in editor. if let Some(ref mut editor) = self.editor { editor.set_clip(match self.selected { - Selection::TrackClip { track, scene } - if let Some(Some(Some(clip))) = self - .scenes.get(scene) - .map(|s|s.clips.get(track)) => Some(clip), + Selection::Clip(t, s) if let Some(Some(Some(clip))) = self + .scenes.get(s).map(|s|s.clips.get(t)) => Some(clip), _ => None }); } @@ -324,15 +320,14 @@ impl Tek { /// Launch a clip or scene pub(crate) fn launch (&mut self) { - use Selection::*; match self.selected { - Track(t) => { + Selection::Track(t) => { self.tracks[t].player.enqueue_next(None) }, - TrackClip { track, scene } => { - self.tracks[track].player.enqueue_next(self.scenes[scene].clips[track].as_ref()) + Selection::Clip(t, s) => { + self.tracks[t].player.enqueue_next(self.scenes[s].clips[t].as_ref()) }, - Scene(s) => { + Selection::Scene(s) => { for t in 0..self.tracks.len() { self.tracks[t].player.enqueue_next(self.scenes[s].clips[t].as_ref()) } @@ -353,26 +348,25 @@ impl Tek { /// Set the color of the selected entity pub fn set_color (&mut self, palette: Option) -> Option { - use Selection::*; let palette = palette.unwrap_or_else(||ItemTheme::random()); Some(match self.selected { - Mix => { + Selection::Mix => { let old = self.color; self.color = palette; old }, - Scene(s) => { - let old = self.scenes[s].color; - self.scenes[s].color = palette; - old - } - Track(t) => { + Selection::Track(t) => { let old = self.tracks[t].color; self.tracks[t].color = palette; old } - TrackClip { track, scene } => { - if let Some(ref clip) = self.scenes[scene].clips[track] { + Selection::Scene(s) => { + let old = self.scenes[s].color; + self.scenes[s].color = palette; + old + } + Selection::Clip(t, s) => { + if let Some(ref clip) = self.scenes[s].clips[t] { let mut clip = clip.write().unwrap(); let old = clip.color; clip.color = palette; @@ -380,8 +374,7 @@ impl Tek { } else { return None } - }, - _ => todo!() + } }) } @@ -433,22 +426,12 @@ pub enum Modal { pub enum Selection { /// The whole mix is selected #[default] Mix, - /// A MIDI input is selected. - Input(usize), - /// A MIDI output is selected. - Output(usize), - /// A scene is selected. - Scene(usize), /// A track is selected. Track(usize), + /// A scene is selected. + Scene(usize), /// A clip (track × scene) is selected. - TrackClip { track: usize, scene: usize }, - /// A track's MIDI input connection is selected. - TrackInput { track: usize, port: usize }, - /// A track's MIDI output connection is selected. - TrackOutput { track: usize, port: usize }, - /// A track device slot is selected. - TrackDevice { track: usize, device: usize }, + Clip(usize, usize), } /// Focus identification methods @@ -463,101 +446,70 @@ impl Selection { matches!(self, Self::Scene(_)) } pub fn is_clip (&self) -> bool { - matches!(self, Self::TrackClip {..}) + matches!(self, Self::Clip(_, _)) } pub fn track (&self) -> Option { use Selection::*; - match self { - Track(track) - | TrackClip { track, .. } - | TrackInput { track, .. } - | TrackOutput { track, .. } - | TrackDevice { track, .. } => Some(*track), - _ => None - } + match self { Clip(t, _) => Some(*t), Track(t) => Some(*t), _ => None } } pub fn track_next (&self, len: usize) -> Self { - use Selection::*; match self { - Mix => Track(0), - Scene(s) => TrackClip { track: 0, scene: *s }, - Track(t) => if t + 1 < len { - Track(t + 1) - } else { - Mix - }, - TrackClip {track, scene} => if track + 1 < len { - TrackClip { track: track + 1, scene: *scene } - } else { - Scene(*scene) - }, - _ => todo!() + Selection::Mix => Selection::Track(0), + Selection::Track(t) if t + 1 < len => Selection::Track(t + 1), + Selection::Track(t) => Selection::Mix, + Selection::Scene(s) => Selection::Clip(0, *s), + Selection::Clip(t, s) if t + 1 < len => Selection::Clip(t + 1, *s), + Selection::Clip(t, s) => Selection::Scene(*s), } } pub fn track_prev (&self) -> Self { - use Selection::*; match self { - Mix => Mix, - Scene(s) => Scene(*s), - Track(0) => Mix, - Track(t) => Track(t - 1), - TrackClip { track: 0, scene } => Scene(*scene), - TrackClip { track: t, scene } => TrackClip { track: t - 1, scene: *scene }, - _ => todo!() + Selection::Mix => Selection::Mix, + Selection::Scene(s) => Selection::Scene(*s), + Selection::Track(0) => Selection::Mix, + Selection::Track(t) => Selection::Track(t - 1), + Selection::Clip(0, s) => Selection::Scene(*s), + Selection::Clip(t, s) => Selection::Clip(t - 1, *s), } } pub fn scene (&self) -> Option { use Selection::*; - match self { - Scene(scene) | TrackClip { scene, .. } => Some(*scene), - _ => None - } + match self { Clip(_, s) => Some(*s), Scene(s) => Some(*s), _ => None } } pub fn scene_next (&self, len: usize) -> Self { - use Selection::*; match self { - Mix => Scene(0), - Track(t) => TrackClip { track: *t, scene: 0 }, - Scene(s) => if s + 1 < len { - Scene(s + 1) - } else { - Mix - }, - TrackClip { track, scene } => if scene + 1 < len { - TrackClip { track: *track, scene: scene + 1 } - } else { - Track(*track) - }, - _ => todo!() + Selection::Mix => Selection::Scene(0), + Selection::Track(t) => Selection::Clip(*t, 0), + Selection::Scene(s) if s + 1 < len => Selection::Scene(s + 1), + Selection::Scene(s) => Selection::Mix, + Selection::Clip(t, s) if s + 1 < len => Selection::Clip(*t, s + 1), + Selection::Clip(t, s) => Selection::Track(*t), } } pub fn scene_prev (&self) -> Self { - use Selection::*; match self { - Mix | Scene(0) => Mix, - Scene(s) => Scene(s - 1), - Track(t) => Track(*t), - TrackClip { track, scene: 0 } => Track(*track), - TrackClip { track, scene } => TrackClip { track: *track, scene: scene - 1 }, - _ => todo!() + Selection::Mix => Selection::Mix, + Selection::Track(t) => Selection::Track(*t), + Selection::Scene(0) => Selection::Mix, + Selection::Scene(s) => Selection::Scene(s - 1), + Selection::Clip(t, 0) => Selection::Track(*t), + Selection::Clip(t, s) => Selection::Clip(*t, s - 1), } } pub fn describe (&self, tracks: &[Track], scenes: &[Scene]) -> Arc { - use Selection::*; format!("{}", match self { - Mix => "Everything".to_string(), - Scene(s) => scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name)) - .unwrap_or_else(||"S??".into()), - Track(t) => tracks.get(*t).map(|track|format!("T{t}: {}", &track.name)) + Self::Mix => "Everything".to_string(), + Self::Track(t) => tracks.get(*t).map(|track|format!("T{t}: {}", &track.name)) .unwrap_or_else(||"T??".into()), - TrackClip { track, scene } => match (tracks.get(*track), scenes.get(*scene)) { - (Some(_), Some(s)) => match s.clip(*track) { - Some(clip) => format!("T{track} S{scene} C{}", &clip.read().unwrap().name), - None => format!("T{track} S{scene}: Empty") + Self::Scene(s) => scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name)) + .unwrap_or_else(||"S??".into()), + Self::Clip(t, s) => match (tracks.get(*t), scenes.get(*s)) { + (Some(_), Some(scene)) => match scene.clip(*t) { + Some(clip) => format!("T{t} S{s} C{}", &clip.read().unwrap().name), + None => format!("T{t} S{s}: Empty") }, - _ => format!("T{track} S{scene}: Empty"), - }, - _ => todo!() + _ => format!("T{t} S{s}: Empty"), + } }).into() } } @@ -787,3 +739,11 @@ impl HasTracks for Tek { fn tracks (&self) -> &Vec { &self.tracks } fn tracks_mut (&mut self) -> &mut Vec { &mut self.tracks } } + +#[derive(Debug)] +pub enum Device { + Sequencer(MidiPlayer), + Sampler(Sampler), + #[cfg(feature="host")] + Plugin(Plugin), +} diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs index ec030508..f4d8f8e0 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -181,12 +181,11 @@ impl Tek { } pub(crate) fn tracks_with_sizes (&self) -> impl TracksSizes<'_> { - use Selection::*; let mut x = 0; let editing = self.is_editing(); let active = match self.selected() { - Track(t) if editing => Some(t), - TrackClip { track, .. } if editing => Some(track), + Selection::Track(t) if editing => Some(t), + Selection::Clip(t, _) if editing => Some(t), _ => None }; let bigger = self.editor_w(); @@ -201,11 +200,10 @@ impl Tek { pub(crate) fn scenes_with_sizes (&self, editing: bool, height: usize, larger: usize) -> impl ScenesSizes<'_> { - use Selection::*; let (selected_track, selected_scene) = match self.selected() { - Track(t) => (Some(*t), None), - Scene(s) => (None, Some(*s)), - TrackClip { track, scene } => (Some(*track), Some(*scene)), + Selection::Track(t) => (Some(*t), None), + Selection::Scene(s) => (None, Some(*s)), + Selection::Clip(t, s) => (Some(*t), Some(*s)), _ => (None, None) }; let mut y = 0; @@ -306,10 +304,8 @@ impl<'a> ArrangerView<'a> { /// Render input matrix. pub(crate) fn inputs (&'a self) -> impl Content + 'a { - Tui::bg(Color::Reset, Bsp::s( - Bsp::s(self.input_routes(), self.input_ports()), - self.input_intos() - )) + Tui::bg(Color::Reset, + Bsp::s(Bsp::s(self.input_routes(), self.input_ports()), self.input_intos())) } fn input_routes (&'a self) -> impl Content + 'a { @@ -317,12 +313,13 @@ impl<'a> ArrangerView<'a> { .left(self.width_side, io_ports(Tui::g(224), Tui::g(32), ||self.app.inputs_with_sizes())) .middle(self.width_mid, - per_track_top(self.width_mid, ||self.tracks_with_sizes_scrolled(), - move|_, &Track { color, .. }|io_conns( - color.dark.rgb, - color.darker.rgb, - ||self.app.inputs_with_sizes() - ))) + per_track_top( + self.width_mid, + ||self.app.tracks_with_sizes(), + move|_, &Track { color, .. }|{ + io_conns(color.dark.rgb, color.darker.rgb, ||self.app.inputs_with_sizes()) + } + )) } fn input_ports (&'a self) -> impl Content + 'a { @@ -334,7 +331,7 @@ impl<'a> ArrangerView<'a> { .middle(self.width_mid, per_track_top( self.width_mid, - ||self.tracks_with_sizes_scrolled(), + ||self.app.tracks_with_sizes(), move|t, track|{ let rec = track.player.recording; let mon = track.player.monitoring; @@ -356,12 +353,14 @@ impl<'a> ArrangerView<'a> { fn input_intos (&'a self) -> impl Content + 'a { Tryptich::top(2) .left(self.width_side, - Bsp::s(Align::e("Input:"), Align::e("Into clip:"))) + Bsp::s(Align::e("Input:"), Align::e("Into:"))) .middle(self.width_mid, per_track_top( self.width_mid, - ||self.tracks_with_sizes_scrolled(), - |_, _|Tui::bg(Reset, Align::c(Bsp::s(OctaveVertical::default(), " ------ "))))) + ||self.app.tracks_with_sizes(), + |_, _|{ + Tui::bg(Reset, Align::c(Bsp::s(OctaveVertical::default(), " ------ "))) + })) } /// Render output matrix. @@ -374,35 +373,30 @@ impl<'a> ArrangerView<'a> { fn output_nexts (&'a self) -> impl Content + 'a { Tryptich::top(2) - .left(self.width_side, Align::ne("From clip:")) + .left(self.width_side, Align::ne("From:")) .middle(self.width_mid, per_track_top( self.width_mid, ||self.tracks_with_sizes_scrolled(), - |_, _|Tui::bg(Reset, Align::c(Bsp::s(" ------ ", OctaveVertical::default()))))) + |_, _|{ + Tui::bg(Reset, Align::c(Bsp::s(" ------ ", OctaveVertical::default()))) + })) } - fn output_froms (&'a self) -> impl Content + 'a { - let label = Align::ne("Next clip:"); - Tryptich::top(2).left(self.width_side, label).middle(self.width_mid, per_track_top( - self.width_mid, ||self.tracks_with_sizes_scrolled(), |t, track|{ - let queued = track.player.next_clip.is_some(); - let queued_blank = Thunk::new(||Tui::bg(Reset, " ------ ")); - let queued_clip = Thunk::new(||{ - let title = if let Some((_, clip)) = track.player.next_clip.as_ref() { - if let Some(clip) = clip { - clip.read().unwrap().name.as_ref().clone() - } else { - "Stop" - } - } else { - "" - }; - Tui::bg(Reset, title) - }); - Either(queued, queued_clip, queued_blank) - })) + Tryptich::top(2) + .left(self.width_side, Align::ne("Next:")) + .middle(self.width_mid, per_track_top( + self.width_mid, + ||self.tracks_with_sizes_scrolled(), + |t, track|Either( + track.player.next_clip.is_some(), + Thunk::new(||Tui::bg(Reset, format!("{:?}", + track.player.next_clip.as_ref() + .map(|(moment, clip)|clip.as_ref() + .map(|clip|clip.read().unwrap().name.clone())) + .flatten().as_ref()))), + Thunk::new(||Tui::bg(Reset, " ------ ")) + ))) } - fn output_ports (&'a self) -> impl Content + 'a { Tryptich::top(1) .left(self.width_side, @@ -418,18 +412,13 @@ impl<'a> ArrangerView<'a> { let solo = false; let mute = if mute { White } else { t.color.darkest.rgb }; let solo = if solo { White } else { t.color.darkest.rgb }; - let bg_1 = if self.track_selected == Some(i) { - t.color.light.rgb - } else { - t.color.base.rgb - }; + let bg_1 = if self.track_selected == Some(i) { t.color.light.rgb } else { t.color.base.rgb }; let bg_2 = if i > 0 { t.color.base.rgb } else { Reset }; let mute = Tui::fg_bg(mute, bg_1, "Play "); let solo = Tui::fg_bg(solo, bg_1, "Solo "); wrap(bg_1, Tui::g(224), Tui::bold(true, Fill::x(Bsp::e(mute, solo)))) })) } - fn output_conns (&'a self) -> impl Content + 'a { Tryptich::top(self.outputs_height) .left(self.width_side, @@ -751,26 +740,24 @@ pub(crate) fn heading <'a> ( pub(crate) fn io_ports <'a, T: PortsSizes<'a>> ( fg: Color, bg: Color, iter: impl Fn()->T + Send + Sync + 'a ) -> impl Content + 'a { - Map::new(iter, move|( - index, name, connections, y, y2 - ): (usize, &'a Arc, &'a [PortConnect], usize, usize), _| - map_south(y as u16, (y2-y) as u16, Bsp::s( - Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(Bsp::e(" 󰣲 ", name))))), - Map::new(||connections.iter(), move|connect: &'a PortConnect, index|map_south(index as u16, 1, - Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg, - &connect.info))))))))) + Map::new(iter, + move|(index, name, connections, y, y2): (usize, &'a Arc, &'a [PortConnect], usize, usize), _| + map_south(y as u16, (y2-y) as u16, Bsp::s( + Fill::x(Tui::bold(true, Tui::fg_bg(fg, bg, Align::w(Bsp::e(" 󰣲 ", name))))), + Map::new(||connections.iter(), move|connect: &'a PortConnect, index|map_south(index as u16, 1, + Fill::x(Align::w(Tui::bold(false, Tui::fg_bg(fg, bg, + &connect.info))))))))) } pub(crate) fn io_conns <'a, T: PortsSizes<'a>> ( fg: Color, bg: Color, iter: impl Fn()->T + Send + Sync + 'a ) -> impl Content + 'a { - Map::new(iter, move|( - index, name, connections, y, y2 - ): (usize, &'a Arc, &'a [PortConnect], usize, usize), _| - map_south(y as u16, (y2-y) as u16, Bsp::s( - Fill::x(Tui::bold(true, wrap(bg, fg, Fill::x(Align::w("▞▞▞▞ ▞▞▞▞"))))), - Map::new(||connections.iter(), move|connect, index|map_south(index as u16, 1, - Fill::x(Align::w(Tui::bold(false, wrap(bg, fg, Fill::x("")))))))))) + Map::new(iter, + move|(index, name, connections, y, y2): (usize, &'a Arc, &'a [PortConnect], usize, usize), _| + map_south(y as u16, (y2-y) as u16, Bsp::s( + Fill::x(Tui::bold(true, wrap(bg, fg, Fill::x(Align::w("▞▞▞▞ ▞▞▞▞"))))), + Map::new(||connections.iter(), move|connect, index|map_south(index as u16, 1, + Fill::x(Align::w(Tui::bold(false, wrap(bg, fg, Fill::x("")))))))))) } pub(crate) fn per_track_top <'a, T: Content + 'a, U: TracksSizes<'a>> ( diff --git a/crates/cli/tek.rs b/crates/cli/tek.rs index bf0cd6d6..0cebdf0d 100644 --- a/crates/cli/tek.rs +++ b/crates/cli/tek.rs @@ -185,12 +185,12 @@ impl Cli { _ => vec![] }, scenes, - selected: Selection::TrackClip { track: 0, scene: 0 }, + selected: Selection::Clip(0, 0), ..Default::default() }; if let &LaunchMode::Arranger { scenes, tracks, track_width, .. } = mode { app.arranger = Default::default(); - app.selected = Selection::TrackClip { track: 1, scene: 1 }; + app.selected = Selection::Clip(1, 1); app.scenes_add(scenes)?; app.tracks_add(tracks, Some(track_width), &[], &[])?; } diff --git a/crates/device/Cargo.toml b/crates/device/Cargo.toml deleted file mode 100644 index ce041180..00000000 --- a/crates/device/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "tek_device" -edition = { workspace = true } -version = { workspace = true } - -[dependencies] -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 } - -[features] -default = [ "sequencer", "sampler" ] -lv2 = [ "livi" ] -sampler = [ "symphonia", "wavers" ] -sequencer = [ "uuid" ] diff --git a/crates/device/src/lib.rs b/crates/device/src/lib.rs deleted file mode 100644 index 4bae13d6..00000000 --- a/crates/device/src/lib.rs +++ /dev/null @@ -1,35 +0,0 @@ -#![feature(let_chains)] - -pub(crate) use std::cmp::Ord; -pub(crate) use std::fmt::{Debug, Formatter}; -pub(crate) use std::thread::JoinHandle; -pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicUsize, Ordering::Relaxed}}; -pub(crate) use std::fs::File; -pub(crate) use std::path::PathBuf; -pub(crate) use std::error::Error; -pub(crate) use std::ffi::OsString; - -pub(crate) use ::tengri::{dsl::*, input::*, output::*, tui::{*, ratatui::prelude::*}}; -pub(crate) use ::tek_engine::*; -pub(crate) use ::tek_engine::midi::{u7, LiveEvent, MidiMessage}; -pub(crate) use ::tek_engine::jack::{Control, ProcessScope, MidiWriter, RawMidi}; -pub(crate) use ratatui::{prelude::Rect, widgets::{Widget, canvas::{Canvas, Line}}}; - -#[cfg(feature = "sequencer")] mod sequencer; -#[cfg(feature = "sequencer")] pub use self::sequencer::*; - -#[cfg(feature = "sampler")] mod sampler; -#[cfg(feature = "sampler")] pub use self::sampler::*; - -#[cfg(feature = "plugin")] mod plugin; -#[cfg(feature = "plugin")] pub use self::plugin::*; - -#[derive(Debug)] -pub enum Device { - #[cfg(feature = "sequencer")] - Sequencer(MidiPlayer), - #[cfg(feature = "sampler")] - Sampler(Sampler), - #[cfg(feature = "plugin")] - Plugin(Plugin), -} diff --git a/crates/device/src/sequencer/seq_model.rs b/crates/device/src/sequencer/seq_model.rs deleted file mode 100644 index 004db566..00000000 --- a/crates/device/src/sequencer/seq_model.rs +++ /dev/null @@ -1,452 +0,0 @@ -//! MIDI player -use crate::*; - -pub trait HasPlayer { - fn player (&self) -> &impl MidiPlayerApi; - fn player_mut (&mut self) -> &mut impl MidiPlayerApi; -} - -#[macro_export] macro_rules! has_player { - (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { - impl $(<$($L),*$($T $(: $U)?),*>)? HasPlayer for $Struct $(<$($L),*$($T),*>)? { - fn player (&$self) -> &impl MidiPlayerApi { &$cb } - fn player_mut (&mut $self) -> &mut impl MidiPlayerApi { &mut$cb } - } - } -} - -pub trait MidiPlayerApi: MidiRecordApi + MidiPlaybackApi + Send + Sync {} - -impl MidiPlayerApi for MidiPlayer {} - -/// Contains state for playing a clip -pub struct MidiPlayer { - /// State of clock and playhead - pub clock: Clock, - /// Start time and clip being played - pub play_clip: Option<(Moment, Option>>)>, - /// Start time and next clip - pub next_clip: Option<(Moment, Option>>)>, - /// Play input through output. - pub monitoring: bool, - /// Write input to sequence. - pub recording: bool, - /// Overdub input to sequence. - pub overdub: bool, - /// Send all notes off - pub reset: bool, // TODO?: after Some(nframes) - /// Record from MIDI ports to current sequence. - pub midi_ins: Vec, - /// Play from current sequence to MIDI ports - pub midi_outs: Vec, - /// Notes currently held at input - pub notes_in: Arc>, - /// Notes currently held at output - pub notes_out: Arc>, - /// MIDI output buffer - pub note_buf: Vec, -} - -impl Default for MidiPlayer { - fn default () -> Self { - Self { - play_clip: None, - next_clip: None, - recording: false, - monitoring: false, - overdub: false, - - notes_in: RwLock::new([false;128]).into(), - notes_out: RwLock::new([false;128]).into(), - note_buf: vec![0;8], - reset: true, - - midi_ins: vec![], - midi_outs: vec![], - clock: Clock::default(), - } - } -} - -impl MidiPlayer { - pub fn new ( - name: impl AsRef, - jack: &Jack, - clock: Option<&Clock>, - clip: Option<&Arc>>, - midi_from: &[PortConnect], - midi_to: &[PortConnect], - ) -> Usually { - let _name = name.as_ref(); - let clock = clock.cloned().unwrap_or_default(); - Ok(Self { - midi_ins: vec![JackMidiIn::new(jack, format!("M/{}", name.as_ref()), midi_from)?,], - midi_outs: vec![JackMidiOut::new(jack, format!("{}/M", name.as_ref()), midi_to)?, ], - play_clip: clip.map(|clip|(Moment::zero(&clock.timebase), Some(clip.clone()))), - clock, - note_buf: vec![0;8], - reset: true, - recording: false, - monitoring: false, - overdub: false, - next_clip: None, - notes_in: RwLock::new([false;128]).into(), - notes_out: RwLock::new([false;128]).into(), - }) - } -} - -impl std::fmt::Debug for MidiPlayer { - fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - f.debug_struct("MidiPlayer") - .field("clock", &self.clock) - .field("play_clip", &self.play_clip) - .field("next_clip", &self.next_clip) - .finish() - } -} - -has_clock!(|self: MidiPlayer|self.clock); - -impl HasMidiIns for MidiPlayer { - fn midi_ins (&self) -> &Vec { &self.midi_ins } - fn midi_ins_mut (&mut self) -> &mut Vec { &mut self.midi_ins } -} - -impl HasMidiOuts for MidiPlayer { - fn midi_outs (&self) -> &Vec { &self.midi_outs } - fn midi_outs_mut (&mut self) -> &mut Vec { &mut self.midi_outs } - fn midi_note (&mut self) -> &mut Vec { &mut self.note_buf } -} - -/// Hosts the JACK callback for a single MIDI player -pub struct PlayerAudio<'a, T: MidiPlayerApi>( - /// Player - pub &'a mut T, - /// Note buffer - pub &'a mut Vec, - /// Note chunk buffer - pub &'a mut Vec>>, -); - -/// JACK process callback for a sequencer's clip player/recorder. -impl Audio for PlayerAudio<'_, T> { - fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { - let model = &mut self.0; - let note_buf = &mut self.1; - let midi_buf = &mut self.2; - // Clear output buffer(s) - model.clear(scope, midi_buf, false); - // Write chunk of clip to output, handle switchover - if model.play(scope, note_buf, midi_buf) { - model.switchover(scope, note_buf, midi_buf); - } - if model.has_midi_ins() { - if model.recording() || model.monitoring() { - // Record and/or monitor input - model.record(scope, midi_buf) - } else if model.has_midi_outs() && model.monitoring() { - // Monitor input to output - model.monitor(scope, midi_buf) - } - } - // Write to output port(s) - model.write(scope, midi_buf); - Control::Continue - } -} - -impl MidiRecordApi for MidiPlayer { - fn recording (&self) -> bool { - self.recording - } - fn recording_mut (&mut self) -> &mut bool { - &mut self.recording - } - fn monitoring (&self) -> bool { - self.monitoring - } - fn monitoring_mut (&mut self) -> &mut bool { - &mut self.monitoring - } - fn overdub (&self) -> bool { - self.overdub - } - fn overdub_mut (&mut self) -> &mut bool { - &mut self.overdub - } - fn notes_in (&self) -> &Arc> { - &self.notes_in - } -} - -impl MidiPlaybackApi for MidiPlayer { - fn notes_out (&self) -> &Arc> { - &self.notes_out - } -} - -impl HasPlayClip for MidiPlayer { - fn reset (&self) -> bool { - self.reset - } - fn reset_mut (&mut self) -> &mut bool { - &mut self.reset - } - fn play_clip (&self) -> &Option<(Moment, Option>>)> { - &self.play_clip - } - fn play_clip_mut (&mut self) -> &mut Option<(Moment, Option>>)> { - &mut self.play_clip - } - fn next_clip (&self) -> &Option<(Moment, Option>>)> { - &self.next_clip - } - fn next_clip_mut (&mut self) -> &mut Option<(Moment, Option>>)> { - &mut self.next_clip - } -} - -pub trait MidiRecordApi: HasClock + HasPlayClip + HasMidiIns { - fn notes_in (&self) -> &Arc>; - - fn recording (&self) -> bool; - - fn recording_mut (&mut self) -> &mut bool; - - fn toggle_record (&mut self) { - *self.recording_mut() = !self.recording(); - } - - fn monitoring (&self) -> bool; - - fn monitoring_mut (&mut self) -> &mut bool; - - fn toggle_monitor (&mut self) { - *self.monitoring_mut() = !self.monitoring(); - } - - fn overdub (&self) -> bool; - - fn overdub_mut (&mut self) -> &mut bool; - - fn toggle_overdub (&mut self) { - *self.overdub_mut() = !self.overdub(); - } - - fn monitor (&mut self, scope: &ProcessScope, midi_buf: &mut Vec>>) { - // For highlighting keys and note repeat - let notes_in = self.notes_in().clone(); - let monitoring = self.monitoring(); - for input in self.midi_ins_mut().iter() { - for (sample, event, bytes) in parse_midi_input(input.port().iter(scope)) { - if let LiveEvent::Midi { message, .. } = event { - if monitoring { - midi_buf[sample].push(bytes.to_vec()); - } - // FIXME: don't lock on every event! - update_keys(&mut notes_in.write().unwrap(), &message); - } - } - } - } - - fn record (&mut self, scope: &ProcessScope, midi_buf: &mut Vec>>) { - if self.monitoring() { - self.monitor(scope, midi_buf); - } - if !self.clock().is_rolling() { - return - } - if let Some((started, ref clip)) = self.play_clip().clone() { - self.record_clip(scope, started, clip, midi_buf); - } - if let Some((_start_at, _clip)) = &self.next_clip() { - self.record_next(); - } - } - - fn record_clip ( - &mut self, - scope: &ProcessScope, - started: Moment, - clip: &Option>>, - _midi_buf: &mut Vec>> - ) { - if let Some(clip) = clip { - let sample0 = scope.last_frame_time() as usize; - let start = started.sample.get() as usize; - let _recording = self.recording(); - let timebase = self.clock().timebase().clone(); - let quant = self.clock().quant.get(); - let mut clip = clip.write().unwrap(); - let length = clip.length; - for input in self.midi_ins_mut().iter() { - for (sample, event, _bytes) in parse_midi_input(input.port().iter(scope)) { - if let LiveEvent::Midi { message, .. } = event { - clip.record_event({ - let sample = (sample0 + sample - start) as f64; - let pulse = timebase.samples_to_pulse(sample); - let quantized = (pulse / quant).round() * quant; - quantized as usize % length - }, message); - } - } - } - } - } - - fn record_next (&mut self) { - // TODO switch to next clip and record into it - } - -} - -pub trait MidiPlaybackApi: HasPlayClip + HasClock + HasMidiOuts { - - fn notes_out (&self) -> &Arc>; - - /// Clear the section of the output buffer that we will be using, - /// emitting "all notes off" at start of buffer if requested. - fn clear ( - &mut self, scope: &ProcessScope, out: &mut [Vec>], reset: bool - ) { - let n_frames = (scope.n_frames() as usize).min(out.len()); - for frame in &mut out[0..n_frames] { - frame.clear(); - } - if reset { - all_notes_off(out); - } - } - - /// Output notes from clip to MIDI output ports. - fn play ( - &mut self, scope: &ProcessScope, note_buf: &mut Vec, out: &mut [Vec>] - ) -> bool { - if !self.clock().is_rolling() { - return false - } - // If a clip is playing, write a chunk of MIDI events from it to the output buffer. - // If no clip is playing, prepare for switchover immediately. - self.play_clip().as_ref().map_or(true, |(started, clip)|{ - self.play_chunk(scope, note_buf, out, started, clip) - }) - } - - /// Handle switchover from current to next playing clip. - fn switchover ( - &mut self, scope: &ProcessScope, note_buf: &mut Vec, out: &mut [Vec>] - ) { - if !self.clock().is_rolling() { - return - } - let sample0 = scope.last_frame_time() as usize; - //let samples = scope.n_frames() as usize; - if let Some((start_at, clip)) = &self.next_clip() { - let start = start_at.sample.get() as usize; - let sample = self.clock().started.read().unwrap() - .as_ref().unwrap().sample.get() as usize; - // If it's time to switch to the next clip: - if start <= sample0.saturating_sub(sample) { - // Samples elapsed since clip was supposed to start - let _skipped = sample0 - start; - // Switch over to enqueued clip - let started = Moment::from_sample(self.clock().timebase(), start as f64); - // Launch enqueued clip - *self.play_clip_mut() = Some((started, clip.clone())); - // Unset enqueuement (TODO: where to implement looping?) - *self.next_clip_mut() = None; - // Fill in remaining ticks of chunk from next clip. - self.play(scope, note_buf, out); - } - } - } - - fn play_chunk ( - &self, - scope: &ProcessScope, - note_buf: &mut Vec, - out: &mut [Vec>], - started: &Moment, - clip: &Option>> - ) -> bool { - // First sample to populate. Greater than 0 means that the first - // pulse of the clip falls somewhere in the middle of the chunk. - let sample = (scope.last_frame_time() as usize).saturating_sub( - started.sample.get() as usize + - self.clock().started.read().unwrap().as_ref().unwrap().sample.get() as usize - ); - // Iterator that emits sample (index into output buffer at which to write MIDI event) - // paired with pulse (index into clip from which to take the MIDI event) for each - // sample of the output buffer that corresponds to a MIDI pulse. - let pulses = self.clock().timebase().pulses_between_samples(sample, sample + scope.n_frames() as usize); - // Notes active during current chunk. - let notes = &mut self.notes_out().write().unwrap(); - let length = clip.as_ref().map_or(0, |p|p.read().unwrap().length); - for (sample, pulse) in pulses { - // If a next clip is enqueued, and we're past the end of the current one, - // break the loop here (FIXME count pulse correctly) - let past_end = if clip.is_some() { pulse >= length } else { true }; - if self.next_clip().is_some() && past_end { - return true - } - // If there's a currently playing clip, output notes from it to buffer: - if let Some(ref clip) = clip { - Self::play_pulse(clip, pulse, sample, note_buf, out, notes) - } - } - false - } - - fn play_pulse ( - clip: &RwLock, - pulse: usize, - sample: usize, - note_buf: &mut Vec, - out: &mut [Vec>], - notes: &mut [bool;128] - ) { - // Source clip from which the MIDI events will be taken. - let clip = clip.read().unwrap(); - // Clip with zero length is not processed - if clip.length > 0 { - // Current pulse index in source clip - let pulse = pulse % clip.length; - // Output each MIDI event from clip at appropriate frames of output buffer: - for message in clip.notes[pulse].iter() { - // Clear output buffer for this MIDI event. - note_buf.clear(); - // TODO: support MIDI channels other than CH1. - let channel = 0.into(); - // Serialize MIDI event into message buffer. - LiveEvent::Midi { channel, message: *message } - .write(note_buf) - .unwrap(); - // Append serialized message to output buffer. - out[sample].push(note_buf.clone()); - // Update the list of currently held notes. - update_keys(&mut*notes, message); - } - } - } - - /// Write a chunk of MIDI data from the output buffer to all assigned output ports. - fn write (&mut self, scope: &ProcessScope, out: &[Vec>]) { - let samples = scope.n_frames() as usize; - for port in self.midi_outs_mut().iter_mut() { - Self::write_port(&mut port.port_mut().writer(scope), samples, out) - } - } - - /// Write a chunk of MIDI data from the output buffer to an output port. - fn write_port (writer: &mut MidiWriter, samples: usize, out: &[Vec>]) { - for (time, events) in out.iter().enumerate().take(samples) { - for bytes in events.iter() { - writer.write(&RawMidi { time: time as u32, bytes }).unwrap_or_else(|_|{ - panic!("Failed to write MIDI data: {bytes:?}"); - }); - } - } - } -} diff --git a/crates/engine/Cargo.toml b/crates/engine/Cargo.toml deleted file mode 100644 index d097e945..00000000 --- a/crates/engine/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "tek_engine" -edition = { workspace = true } -version = { workspace = true } - -[dependencies] -tengri = { workspace = true } -jack = { workspace = true } -midly = { workspace = true } -uuid = { workspace = true } -atomic_float = { workspace = true } diff --git a/crates/engine/src/jack.rs b/crates/engine/src/jack.rs deleted file mode 100644 index 390d4cb7..00000000 --- a/crates/engine/src/jack.rs +++ /dev/null @@ -1,18 +0,0 @@ -use ::jack::{*, contrib::{*, ClosureProcessHandler}}; - //contrib::ClosureProcessHandler, - //NotificationHandler, - //Client, AsyncClient, ClientOptions, ClientStatus, - //ProcessScope, Control, Frames, - //Port, PortId, PortSpec, PortFlags, - //Unowned, MidiIn, MidiOut, AudioIn, AudioOut, -//}; - -pub(crate) use PortConnectName::*; -pub(crate) use PortConnectScope::*; -pub(crate) use PortConnectStatus::*; -pub(crate) use std::sync::{Arc, RwLock}; - -mod jack_client; pub use self::jack_client::*; -mod jack_event; pub use self::jack_event::*; -mod jack_port; pub use self::jack_port::*; - diff --git a/crates/engine/src/midi.rs b/crates/engine/src/midi.rs deleted file mode 100644 index fa9fa4f2..00000000 --- a/crates/engine/src/midi.rs +++ /dev/null @@ -1,60 +0,0 @@ -use crate::*; - -pub use ::midly; -pub(crate) use ::midly::{ - MidiMessage, - num::*, - live::*, -}; - -/// Update notes_in array -pub fn update_keys (keys: &mut[bool;128], message: &MidiMessage) { - match message { - MidiMessage::NoteOn { key, .. } => { keys[key.as_int() as usize] = true; } - MidiMessage::NoteOff { key, .. } => { keys[key.as_int() as usize] = false; }, - _ => {} - } -} - -/// Return boxed iterator of MIDI events -pub fn parse_midi_input <'a> (input: ::jack::MidiIter<'a>) -> Box, &'a [u8])> + 'a> { - Box::new(input.map(|::jack::RawMidi { time, bytes }|( - time as usize, - LiveEvent::parse(bytes).unwrap(), - bytes - ))) -} - -/// Add "all notes off" to the start of a buffer. -pub fn all_notes_off (output: &mut [Vec>]) { - let mut buf = vec![]; - let msg = MidiMessage::Controller { controller: 123.into(), value: 0.into() }; - let evt = LiveEvent::Midi { channel: 0.into(), message: msg }; - evt.write(&mut buf).unwrap(); - output[0].push(buf); -} - -/// Trait for thing that may receive MIDI. -pub trait HasMidiIns { - fn midi_ins (&self) -> &Vec; - - fn midi_ins_mut (&mut self) -> &mut Vec; - - fn has_midi_ins (&self) -> bool { - !self.midi_ins().is_empty() - } -} - -/// Trait for thing that may output MIDI. -pub trait HasMidiOuts { - fn midi_outs (&self) -> &Vec; - - fn midi_outs_mut (&mut self) -> &mut Vec; - - fn has_midi_outs (&self) -> bool { - !self.midi_outs().is_empty() - } - - /// Buffer for serializing a MIDI event. FIXME rename - fn midi_note (&mut self) -> &mut Vec; -} diff --git a/crates/engine/src/time.rs b/crates/engine/src/time.rs deleted file mode 100644 index 9d4e7459..00000000 --- a/crates/engine/src/time.rs +++ /dev/null @@ -1,9 +0,0 @@ -mod time_moment; pub use self::time_moment::*; -mod time_note; pub use self::time_note::*; -mod time_perf; pub use self::time_perf::*; -mod time_pulse; pub use self::time_pulse::*; -mod time_sample_count; pub use self::time_sample_count::*; -mod time_sample_rate; pub use self::time_sample_rate::*; -mod time_timebase; pub use self::time_timebase::*; -mod time_unit; pub use self::time_unit::*; -mod time_usec; pub use self::time_usec::*; diff --git a/crates/jack/Cargo.toml b/crates/jack/Cargo.toml new file mode 100644 index 00000000..e9b8d672 --- /dev/null +++ b/crates/jack/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "tek_jack" +edition = { workspace = true } +version = { workspace = true } + +[dependencies] +jack = { workspace = true } diff --git a/crates/engine/src/jack/jack_client.rs b/crates/jack/src/jack_client.rs similarity index 99% rename from crates/engine/src/jack/jack_client.rs rename to crates/jack/src/jack_client.rs index 5adb9a3e..ad89c7ae 100644 --- a/crates/engine/src/jack/jack_client.rs +++ b/crates/jack/src/jack_client.rs @@ -1,5 +1,5 @@ use crate::*; -use super::*; +use ::jack::contrib::*; use self::JackState::*; /// Things that can provide a [jack::Client] reference. diff --git a/crates/engine/src/jack/jack_device.rs b/crates/jack/src/jack_device.rs similarity index 100% rename from crates/engine/src/jack/jack_device.rs rename to crates/jack/src/jack_device.rs diff --git a/crates/engine/src/jack/jack_event.rs b/crates/jack/src/jack_event.rs similarity index 99% rename from crates/engine/src/jack/jack_event.rs rename to crates/jack/src/jack_event.rs index 9ba6ad0f..43571f69 100644 --- a/crates/engine/src/jack/jack_event.rs +++ b/crates/jack/src/jack_event.rs @@ -1,5 +1,4 @@ use crate::*; -use super::*; /// Event enum for JACK events. #[derive(Debug, Clone, PartialEq)] pub enum JackEvent { diff --git a/crates/engine/src/jack/jack_port.rs b/crates/jack/src/jack_port.rs similarity index 99% rename from crates/engine/src/jack/jack_port.rs rename to crates/jack/src/jack_port.rs index 7347269d..5c34b748 100644 --- a/crates/engine/src/jack/jack_port.rs +++ b/crates/jack/src/jack_port.rs @@ -1,5 +1,4 @@ use crate::*; -use super::*; macro_rules! impl_port { ($Name:ident : $Spec:ident -> $Pair:ident |$jack:ident, $name:ident|$port:expr) => { diff --git a/crates/jack/src/lib.rs b/crates/jack/src/lib.rs new file mode 100644 index 00000000..149ce0d2 --- /dev/null +++ b/crates/jack/src/lib.rs @@ -0,0 +1,17 @@ +#![feature(type_alias_impl_trait)] +mod jack_client; pub use self::jack_client::*; +mod jack_event; pub use self::jack_event::*; +mod jack_port; pub use self::jack_port::*; +pub(crate) use PortConnectName::*; +pub(crate) use PortConnectScope::*; +pub(crate) use PortConnectStatus::*; +pub(crate) use std::sync::{Arc, RwLock}; +pub use ::jack; pub(crate) use ::jack::{ + //contrib::ClosureProcessHandler, + NotificationHandler, + Client, AsyncClient, ClientOptions, ClientStatus, + ProcessScope, Control, Frames, + Port, PortId, PortSpec, PortFlags, + Unowned, MidiIn, MidiOut, AudioIn, AudioOut, +}; +pub(crate) type Usually = Result>; diff --git a/crates/midi/Cargo.toml b/crates/midi/Cargo.toml new file mode 100644 index 00000000..a64a74a3 --- /dev/null +++ b/crates/midi/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "tek_midi" +edition = { workspace = true } +version = { workspace = true } + +[dependencies] +tengri = { workspace = true } + +tek_jack = { workspace = true } +tek_time = { workspace = true } + +midly = { workspace = true } +uuid = { workspace = true } diff --git a/crates/engine/examples/midi-import.rs b/crates/midi/examples/midi-import.rs similarity index 100% rename from crates/engine/examples/midi-import.rs rename to crates/midi/examples/midi-import.rs diff --git a/.old/midi.scratch.rs b/crates/midi/midi.scratch.rs similarity index 100% rename from .old/midi.scratch.rs rename to crates/midi/midi.scratch.rs diff --git a/crates/device/src/sequencer.rs b/crates/midi/src/clip.rs similarity index 91% rename from crates/device/src/sequencer.rs rename to crates/midi/src/clip.rs index eeec08e9..95295621 100644 --- a/crates/device/src/sequencer.rs +++ b/crates/midi/src/clip.rs @@ -1,9 +1,8 @@ -use crate::*; - -mod seq_clip; pub use self::seq_clip::*; -mod seq_launch; pub use self::seq_launch::*; -mod seq_model; pub use self::seq_model::*; -mod seq_view; pub use self::seq_view::*; +mod clip_editor; pub use self::clip_editor::*; +mod clip_launch; pub use self::clip_launch::*; +mod clip_model; pub use self::clip_model::*; +mod clip_play; pub use self::clip_play::*; +mod clip_view; pub use self::clip_view::*; pub trait HasEditor { fn editor (&self) -> &Option; diff --git a/crates/app/src/editor/editor_model.rs b/crates/midi/src/clip/clip_editor.rs similarity index 59% rename from crates/app/src/editor/editor_model.rs rename to crates/midi/src/clip/clip_editor.rs index befec6be..efdd92d6 100644 --- a/crates/app/src/editor/editor_model.rs +++ b/crates/midi/src/clip/clip_editor.rs @@ -51,6 +51,39 @@ from!(|clip: Option>>|MidiEditor = { model }); +provide!(bool: |self: MidiEditor| { + ":true" => true, + ":false" => false, + ":time-lock" => self.time_lock().get(), + ":time-lock-toggle" => !self.time_lock().get(), +}); + +provide!(usize: |self: MidiEditor| { + ":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), +}); + impl MidiEditor { /// Put note at current position @@ -148,3 +181,64 @@ impl MidiViewer for MidiEditor { fn set_clip (&mut self, p: Option<&Arc>>) { self.mode.set_clip(p) } } +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()))) +}); + +#[derive(Clone, Debug)] pub enum MidiEditCommand { + // TODO: 1-9 seek markers that by default start every 8th of the clip + AppendNote, + PutNote, + DelNote, + SetNoteCursor(usize), + SetNoteLength(usize), + SetNoteScroll(usize), + SetTimeCursor(usize), + SetTimeScroll(usize), + SetTimeZoom(usize), + SetTimeLock(bool), + Show(Option>>), +} + +handle!(TuiIn: |self: MidiEditor, input|Ok(if let Some(command) = self.keys.command(self, input) { + command.execute(self)?; + Some(true) +} else { + None +})); + +impl Command for MidiEditCommand { + fn execute (self, state: &mut MidiEditor) -> Perhaps { + use MidiEditCommand::*; + match self { + Show(clip) => { state.set_clip(clip.as_ref()); }, + DelNote => {}, + PutNote => { state.put_note(false); }, + AppendNote => { state.put_note(true); }, + SetTimeZoom(x) => { state.time_zoom().set(x); state.redraw(); }, + SetTimeLock(x) => { state.time_lock().set(x); }, + SetTimeScroll(x) => { state.time_start().set(x); }, + SetNoteScroll(x) => { state.note_lo().set(x.min(127)); }, + SetNoteLength(x) => { + 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(); + //} + }, + SetTimeCursor(x) => { state.set_time_pos(x); }, + SetNoteCursor(note) => { state.set_note_pos(note.min(127)); }, + //_ => todo!("{:?}", self) + } + Ok(None) + } +} diff --git a/crates/device/src/sequencer/seq_launch.rs b/crates/midi/src/clip/clip_launch.rs similarity index 99% rename from crates/device/src/sequencer/seq_launch.rs rename to crates/midi/src/clip/clip_launch.rs index 48d30f1c..ee2f682e 100644 --- a/crates/device/src/sequencer/seq_launch.rs +++ b/crates/midi/src/clip/clip_launch.rs @@ -87,3 +87,4 @@ pub trait HasPlayClip: HasClock { FieldV(color, "Next:", format!("{} {}", time, name)) } } + diff --git a/crates/device/src/sequencer/seq_clip.rs b/crates/midi/src/clip/clip_model.rs similarity index 100% rename from crates/device/src/sequencer/seq_clip.rs rename to crates/midi/src/clip/clip_model.rs diff --git a/crates/midi/src/clip/clip_play.rs b/crates/midi/src/clip/clip_play.rs new file mode 100644 index 00000000..b7df1fe9 --- /dev/null +++ b/crates/midi/src/clip/clip_play.rs @@ -0,0 +1,208 @@ +//! MIDI player +use crate::*; + +pub trait HasPlayer { + fn player (&self) -> &impl MidiPlayerApi; + fn player_mut (&mut self) -> &mut impl MidiPlayerApi; +} + +#[macro_export] macro_rules! has_player { + (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { + impl $(<$($L),*$($T $(: $U)?),*>)? HasPlayer for $Struct $(<$($L),*$($T),*>)? { + fn player (&$self) -> &impl MidiPlayerApi { &$cb } + fn player_mut (&mut $self) -> &mut impl MidiPlayerApi { &mut$cb } + } + } +} + +pub trait MidiPlayerApi: MidiRecordApi + MidiPlaybackApi + Send + Sync {} + +impl MidiPlayerApi for MidiPlayer {} + +/// Contains state for playing a clip +pub struct MidiPlayer { + /// State of clock and playhead + pub clock: Clock, + /// Start time and clip being played + pub play_clip: Option<(Moment, Option>>)>, + /// Start time and next clip + pub next_clip: Option<(Moment, Option>>)>, + /// Play input through output. + pub monitoring: bool, + /// Write input to sequence. + pub recording: bool, + /// Overdub input to sequence. + pub overdub: bool, + /// Send all notes off + pub reset: bool, // TODO?: after Some(nframes) + /// Record from MIDI ports to current sequence. + pub midi_ins: Vec, + /// Play from current sequence to MIDI ports + pub midi_outs: Vec, + /// Notes currently held at input + pub notes_in: Arc>, + /// Notes currently held at output + pub notes_out: Arc>, + /// MIDI output buffer + pub note_buf: Vec, +} + +impl Default for MidiPlayer { + fn default () -> Self { + Self { + play_clip: None, + next_clip: None, + recording: false, + monitoring: false, + overdub: false, + + notes_in: RwLock::new([false;128]).into(), + notes_out: RwLock::new([false;128]).into(), + note_buf: vec![0;8], + reset: true, + + midi_ins: vec![], + midi_outs: vec![], + clock: Clock::default(), + } + } +} + +impl MidiPlayer { + pub fn new ( + name: impl AsRef, + jack: &Jack, + clock: Option<&Clock>, + clip: Option<&Arc>>, + midi_from: &[PortConnect], + midi_to: &[PortConnect], + ) -> Usually { + let _name = name.as_ref(); + let clock = clock.cloned().unwrap_or_default(); + Ok(Self { + midi_ins: vec![JackMidiIn::new(jack, format!("M/{}", name.as_ref()), midi_from)?,], + midi_outs: vec![JackMidiOut::new(jack, format!("{}/M", name.as_ref()), midi_to)?, ], + play_clip: clip.map(|clip|(Moment::zero(&clock.timebase), Some(clip.clone()))), + clock, + note_buf: vec![0;8], + reset: true, + recording: false, + monitoring: false, + overdub: false, + next_clip: None, + notes_in: RwLock::new([false;128]).into(), + notes_out: RwLock::new([false;128]).into(), + }) + } +} + +impl std::fmt::Debug for MidiPlayer { + fn fmt (&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + f.debug_struct("MidiPlayer") + .field("clock", &self.clock) + .field("play_clip", &self.play_clip) + .field("next_clip", &self.next_clip) + .finish() + } +} + +has_clock!(|self: MidiPlayer|self.clock); + +impl HasMidiIns for MidiPlayer { + fn midi_ins (&self) -> &Vec { &self.midi_ins } + fn midi_ins_mut (&mut self) -> &mut Vec { &mut self.midi_ins } +} + +impl HasMidiOuts for MidiPlayer { + fn midi_outs (&self) -> &Vec { &self.midi_outs } + fn midi_outs_mut (&mut self) -> &mut Vec { &mut self.midi_outs } + fn midi_note (&mut self) -> &mut Vec { &mut self.note_buf } +} + +/// Hosts the JACK callback for a single MIDI player +pub struct PlayerAudio<'a, T: MidiPlayerApi>( + /// Player + pub &'a mut T, + /// Note buffer + pub &'a mut Vec, + /// Note chunk buffer + pub &'a mut Vec>>, +); + +/// JACK process callback for a sequencer's clip player/recorder. +impl Audio for PlayerAudio<'_, T> { + fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { + let model = &mut self.0; + let note_buf = &mut self.1; + let midi_buf = &mut self.2; + // Clear output buffer(s) + model.clear(scope, midi_buf, false); + // Write chunk of clip to output, handle switchover + if model.play(scope, note_buf, midi_buf) { + model.switchover(scope, note_buf, midi_buf); + } + if model.has_midi_ins() { + if model.recording() || model.monitoring() { + // Record and/or monitor input + model.record(scope, midi_buf) + } else if model.has_midi_outs() && model.monitoring() { + // Monitor input to output + model.monitor(scope, midi_buf) + } + } + // Write to output port(s) + model.write(scope, midi_buf); + Control::Continue + } +} + +impl MidiRecordApi for MidiPlayer { + fn recording (&self) -> bool { + self.recording + } + fn recording_mut (&mut self) -> &mut bool { + &mut self.recording + } + fn monitoring (&self) -> bool { + self.monitoring + } + fn monitoring_mut (&mut self) -> &mut bool { + &mut self.monitoring + } + fn overdub (&self) -> bool { + self.overdub + } + fn overdub_mut (&mut self) -> &mut bool { + &mut self.overdub + } + fn notes_in (&self) -> &Arc> { + &self.notes_in + } +} + +impl MidiPlaybackApi for MidiPlayer { + fn notes_out (&self) -> &Arc> { + &self.notes_out + } +} + +impl HasPlayClip for MidiPlayer { + fn reset (&self) -> bool { + self.reset + } + fn reset_mut (&mut self) -> &mut bool { + &mut self.reset + } + fn play_clip (&self) -> &Option<(Moment, Option>>)> { + &self.play_clip + } + fn play_clip_mut (&mut self) -> &mut Option<(Moment, Option>>)> { + &mut self.play_clip + } + fn next_clip (&self) -> &Option<(Moment, Option>>)> { + &self.next_clip + } + fn next_clip_mut (&mut self) -> &mut Option<(Moment, Option>>)> { + &mut self.next_clip + } +} diff --git a/crates/device/src/sequencer/seq_view.rs b/crates/midi/src/clip/clip_view.rs similarity index 100% rename from crates/device/src/sequencer/seq_view.rs rename to crates/midi/src/clip/clip_view.rs diff --git a/crates/midi/src/lib.rs b/crates/midi/src/lib.rs new file mode 100644 index 00000000..a617b9b3 --- /dev/null +++ b/crates/midi/src/lib.rs @@ -0,0 +1,21 @@ +pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicUsize, AtomicBool, Ordering::Relaxed}}; +pub(crate) use std::path::PathBuf; +pub(crate) use std::fmt::Debug; + +pub use ::midly; +pub(crate) use ::midly::{*, num::*, live::*}; + +pub(crate) use ::tek_time::*; +pub(crate) use ::tek_jack::{*, jack::*}; +pub(crate) use ::tengri::input::*; +pub(crate) use ::tengri::output::*; +pub(crate) use ::tengri::dsl::*; +pub(crate) use ::tengri::tui::*; +pub(crate) use ::tengri::tui::ratatui::style::{Style, Stylize, Color}; + +mod clip; pub use self::clip::*; +mod mode; pub use self::mode::*; +mod note; pub use self::note::*; +mod piano; pub use self::piano::*; +mod pool; pub use self::pool::*; +mod port; pub use self::port::*; diff --git a/crates/app/src/pool/pool_mode.rs b/crates/midi/src/mode.rs similarity index 99% rename from crates/app/src/pool/pool_mode.rs rename to crates/midi/src/mode.rs index 0b3bd49f..6fd9c01c 100644 --- a/crates/app/src/pool/pool_mode.rs +++ b/crates/midi/src/mode.rs @@ -16,4 +16,3 @@ pub enum PoolMode { /// Save clip to disk Export(usize, FileBrowser), } - diff --git a/crates/app/src/pool/pool_mode/mode_browse.rs b/crates/midi/src/mode/mode_browse.rs similarity index 100% rename from crates/app/src/pool/pool_mode/mode_browse.rs rename to crates/midi/src/mode/mode_browse.rs diff --git a/crates/app/src/pool/pool_mode/mode_length.rs b/crates/midi/src/mode/mode_length.rs similarity index 100% rename from crates/app/src/pool/pool_mode/mode_length.rs rename to crates/midi/src/mode/mode_length.rs diff --git a/crates/app/src/pool/pool_mode/mode_rename.rs b/crates/midi/src/mode/mode_rename.rs similarity index 100% rename from crates/app/src/pool/pool_mode/mode_rename.rs rename to crates/midi/src/mode/mode_rename.rs diff --git a/crates/engine/src/note.rs b/crates/midi/src/note.rs similarity index 100% rename from crates/engine/src/note.rs rename to crates/midi/src/note.rs diff --git a/crates/engine/src/note/note_pitch.rs b/crates/midi/src/note/note_pitch.rs similarity index 100% rename from crates/engine/src/note/note_pitch.rs rename to crates/midi/src/note/note_pitch.rs diff --git a/crates/engine/src/note/note_point.rs b/crates/midi/src/note/note_point.rs similarity index 100% rename from crates/engine/src/note/note_point.rs rename to crates/midi/src/note/note_point.rs diff --git a/crates/engine/src/note/note_range.rs b/crates/midi/src/note/note_range.rs similarity index 100% rename from crates/engine/src/note/note_range.rs rename to crates/midi/src/note/note_range.rs diff --git a/crates/midi/src/piano.rs b/crates/midi/src/piano.rs new file mode 100644 index 00000000..ea7b9152 --- /dev/null +++ b/crates/midi/src/piano.rs @@ -0,0 +1,2 @@ +mod piano_h; pub use self::piano_h::*; +mod piano_v; pub use self::piano_v::*; diff --git a/crates/app/src/editor/editor_view.rs b/crates/midi/src/piano/piano_h.rs similarity index 92% rename from crates/app/src/editor/editor_view.rs rename to crates/midi/src/piano/piano_h.rs index 67c9dc18..d4650cf4 100644 --- a/crates/app/src/editor/editor_view.rs +++ b/crates/midi/src/piano/piano_h.rs @@ -325,39 +325,3 @@ fn to_key (note: usize) -> &'static str { _ => unreachable!(), } } - -pub struct OctaveVertical { - on: [bool; 12], - colors: [Color; 3] -} - -impl Default for OctaveVertical { - fn default () -> Self { - Self { - on: [false; 12], - colors: [Rgb(255,255,255), Rgb(0,0,0), Rgb(255,0,0)] - } - } -} - -impl OctaveVertical { - fn color (&self, pitch: usize) -> Color { - let pitch = pitch % 12; - self.colors[if self.on[pitch] { 2 } else { - match pitch { 0 | 2 | 4 | 5 | 6 | 8 | 10 => 0, _ => 1 } - }] - } -} - -impl Content for OctaveVertical { - fn content (&self) -> impl Render { - row!( - Tui::fg_bg(self.color(0), self.color(1), "▙"), - Tui::fg_bg(self.color(2), self.color(3), "▙"), - Tui::fg_bg(self.color(4), self.color(5), "▌"), - Tui::fg_bg(self.color(6), self.color(7), "▟"), - Tui::fg_bg(self.color(8), self.color(9), "▟"), - Tui::fg_bg(self.color(10), self.color(11), "▟"), - ) - } -} diff --git a/crates/midi/src/piano/piano_v.rs b/crates/midi/src/piano/piano_v.rs new file mode 100644 index 00000000..27bd1d5e --- /dev/null +++ b/crates/midi/src/piano/piano_v.rs @@ -0,0 +1,34 @@ +use crate::*; +use Color::*; +pub struct OctaveVertical { + on: [bool; 12], + colors: [Color; 3] +} +impl Default for OctaveVertical { + fn default () -> Self { + Self { + on: [false; 12], + colors: [Rgb(255,255,255), Rgb(0,0,0), Rgb(255,0,0)] + } + } +} +impl OctaveVertical { + fn color (&self, pitch: usize) -> Color { + let pitch = pitch % 12; + self.colors[if self.on[pitch] { 2 } else { + match pitch { 0 | 2 | 4 | 5 | 6 | 8 | 10 => 0, _ => 1 } + }] + } +} +impl Content for OctaveVertical { + fn content (&self) -> impl Render { + row!( + Tui::fg_bg(self.color(0), self.color(1), "▙"), + Tui::fg_bg(self.color(2), self.color(3), "▙"), + Tui::fg_bg(self.color(4), self.color(5), "▌"), + Tui::fg_bg(self.color(6), self.color(7), "▟"), + Tui::fg_bg(self.color(8), self.color(9), "▟"), + Tui::fg_bg(self.color(10), self.color(11), "▟"), + ) + } +} diff --git a/crates/app/src/pool.rs b/crates/midi/src/pool.rs similarity index 100% rename from crates/app/src/pool.rs rename to crates/midi/src/pool.rs diff --git a/crates/app/src/pool/pool_api.rs b/crates/midi/src/pool/pool_api.rs similarity index 100% rename from crates/app/src/pool/pool_api.rs rename to crates/midi/src/pool/pool_api.rs diff --git a/crates/app/src/pool/pool_clips.rs b/crates/midi/src/pool/pool_clips.rs similarity index 100% rename from crates/app/src/pool/pool_clips.rs rename to crates/midi/src/pool/pool_clips.rs diff --git a/crates/app/src/pool/pool_model.rs b/crates/midi/src/pool/pool_model.rs similarity index 100% rename from crates/app/src/pool/pool_model.rs rename to crates/midi/src/pool/pool_model.rs diff --git a/crates/app/src/pool/pool_view.rs b/crates/midi/src/pool/pool_view.rs similarity index 100% rename from crates/app/src/pool/pool_view.rs rename to crates/midi/src/pool/pool_view.rs diff --git a/crates/midi/src/port.rs b/crates/midi/src/port.rs new file mode 100644 index 00000000..25977123 --- /dev/null +++ b/crates/midi/src/port.rs @@ -0,0 +1,31 @@ +use crate::*; + +mod port_in; pub use self::port_in::*; +mod port_out; pub use self::port_out::*; + +/// Update notes_in array +pub fn update_keys (keys: &mut[bool;128], message: &MidiMessage) { + match message { + MidiMessage::NoteOn { key, .. } => { keys[key.as_int() as usize] = true; } + MidiMessage::NoteOff { key, .. } => { keys[key.as_int() as usize] = false; }, + _ => {} + } +} + +/// Return boxed iterator of MIDI events +pub fn parse_midi_input <'a> (input: MidiIter<'a>) -> Box, &'a [u8])> + 'a> { + Box::new(input.map(|RawMidi { time, bytes }|( + time as usize, + LiveEvent::parse(bytes).unwrap(), + bytes + ))) +} + +/// Add "all notes off" to the start of a buffer. +pub fn all_notes_off (output: &mut [Vec>]) { + let mut buf = vec![]; + let msg = MidiMessage::Controller { controller: 123.into(), value: 0.into() }; + let evt = LiveEvent::Midi { channel: 0.into(), message: msg }; + evt.write(&mut buf).unwrap(); + output[0].push(buf); +} diff --git a/crates/midi/src/port/port_in.rs b/crates/midi/src/port/port_in.rs new file mode 100644 index 00000000..abbd1390 --- /dev/null +++ b/crates/midi/src/port/port_in.rs @@ -0,0 +1,107 @@ +use crate::*; + +/// Trait for thing that may receive MIDI. +pub trait HasMidiIns { + fn midi_ins (&self) -> &Vec; + + fn midi_ins_mut (&mut self) -> &mut Vec; + + fn has_midi_ins (&self) -> bool { + !self.midi_ins().is_empty() + } +} + +pub trait MidiRecordApi: HasClock + HasPlayClip + HasMidiIns { + fn notes_in (&self) -> &Arc>; + + fn recording (&self) -> bool; + + fn recording_mut (&mut self) -> &mut bool; + + fn toggle_record (&mut self) { + *self.recording_mut() = !self.recording(); + } + + fn monitoring (&self) -> bool; + + fn monitoring_mut (&mut self) -> &mut bool; + + fn toggle_monitor (&mut self) { + *self.monitoring_mut() = !self.monitoring(); + } + + fn overdub (&self) -> bool; + + fn overdub_mut (&mut self) -> &mut bool; + + fn toggle_overdub (&mut self) { + *self.overdub_mut() = !self.overdub(); + } + + fn monitor (&mut self, scope: &ProcessScope, midi_buf: &mut Vec>>) { + // For highlighting keys and note repeat + let notes_in = self.notes_in().clone(); + let monitoring = self.monitoring(); + for input in self.midi_ins_mut().iter() { + for (sample, event, bytes) in parse_midi_input(input.port().iter(scope)) { + if let LiveEvent::Midi { message, .. } = event { + if monitoring { + midi_buf[sample].push(bytes.to_vec()); + } + // FIXME: don't lock on every event! + update_keys(&mut notes_in.write().unwrap(), &message); + } + } + } + } + + fn record (&mut self, scope: &ProcessScope, midi_buf: &mut Vec>>) { + if self.monitoring() { + self.monitor(scope, midi_buf); + } + if !self.clock().is_rolling() { + return + } + if let Some((started, ref clip)) = self.play_clip().clone() { + self.record_clip(scope, started, clip, midi_buf); + } + if let Some((_start_at, _clip)) = &self.next_clip() { + self.record_next(); + } + } + + fn record_clip ( + &mut self, + scope: &ProcessScope, + started: Moment, + clip: &Option>>, + _midi_buf: &mut Vec>> + ) { + if let Some(clip) = clip { + let sample0 = scope.last_frame_time() as usize; + let start = started.sample.get() as usize; + let _recording = self.recording(); + let timebase = self.clock().timebase().clone(); + let quant = self.clock().quant.get(); + let mut clip = clip.write().unwrap(); + let length = clip.length; + for input in self.midi_ins_mut().iter() { + for (sample, event, _bytes) in parse_midi_input(input.port().iter(scope)) { + if let LiveEvent::Midi { message, .. } = event { + clip.record_event({ + let sample = (sample0 + sample - start) as f64; + let pulse = timebase.samples_to_pulse(sample); + let quantized = (pulse / quant).round() * quant; + quantized as usize % length + }, message); + } + } + } + } + } + + fn record_next (&mut self) { + // TODO switch to next clip and record into it + } + +} diff --git a/crates/midi/src/port/port_out.rs b/crates/midi/src/port/port_out.rs new file mode 100644 index 00000000..159d11c6 --- /dev/null +++ b/crates/midi/src/port/port_out.rs @@ -0,0 +1,164 @@ +use crate::*; + +/// Trait for thing that may output MIDI. +pub trait HasMidiOuts { + fn midi_outs (&self) -> &Vec; + + fn midi_outs_mut (&mut self) -> &mut Vec; + + fn has_midi_outs (&self) -> bool { + !self.midi_outs().is_empty() + } + + /// Buffer for serializing a MIDI event. FIXME rename + fn midi_note (&mut self) -> &mut Vec; +} + +pub trait MidiPlaybackApi: HasPlayClip + HasClock + HasMidiOuts { + + fn notes_out (&self) -> &Arc>; + + /// Clear the section of the output buffer that we will be using, + /// emitting "all notes off" at start of buffer if requested. + fn clear ( + &mut self, scope: &ProcessScope, out: &mut [Vec>], reset: bool + ) { + let n_frames = (scope.n_frames() as usize).min(out.len()); + for frame in &mut out[0..n_frames] { + frame.clear(); + } + if reset { + all_notes_off(out); + } + } + + /// Output notes from clip to MIDI output ports. + fn play ( + &mut self, scope: &ProcessScope, note_buf: &mut Vec, out: &mut [Vec>] + ) -> bool { + if !self.clock().is_rolling() { + return false + } + // If a clip is playing, write a chunk of MIDI events from it to the output buffer. + // If no clip is playing, prepare for switchover immediately. + self.play_clip().as_ref().map_or(true, |(started, clip)|{ + self.play_chunk(scope, note_buf, out, started, clip) + }) + } + + /// Handle switchover from current to next playing clip. + fn switchover ( + &mut self, scope: &ProcessScope, note_buf: &mut Vec, out: &mut [Vec>] + ) { + if !self.clock().is_rolling() { + return + } + let sample0 = scope.last_frame_time() as usize; + //let samples = scope.n_frames() as usize; + if let Some((start_at, clip)) = &self.next_clip() { + let start = start_at.sample.get() as usize; + let sample = self.clock().started.read().unwrap() + .as_ref().unwrap().sample.get() as usize; + // If it's time to switch to the next clip: + if start <= sample0.saturating_sub(sample) { + // Samples elapsed since clip was supposed to start + let _skipped = sample0 - start; + // Switch over to enqueued clip + let started = Moment::from_sample(self.clock().timebase(), start as f64); + // Launch enqueued clip + *self.play_clip_mut() = Some((started, clip.clone())); + // Unset enqueuement (TODO: where to implement looping?) + *self.next_clip_mut() = None; + // Fill in remaining ticks of chunk from next clip. + self.play(scope, note_buf, out); + } + } + } + + fn play_chunk ( + &self, + scope: &ProcessScope, + note_buf: &mut Vec, + out: &mut [Vec>], + started: &Moment, + clip: &Option>> + ) -> bool { + // First sample to populate. Greater than 0 means that the first + // pulse of the clip falls somewhere in the middle of the chunk. + let sample = (scope.last_frame_time() as usize).saturating_sub( + started.sample.get() as usize + + self.clock().started.read().unwrap().as_ref().unwrap().sample.get() as usize + ); + // Iterator that emits sample (index into output buffer at which to write MIDI event) + // paired with pulse (index into clip from which to take the MIDI event) for each + // sample of the output buffer that corresponds to a MIDI pulse. + let pulses = self.clock().timebase().pulses_between_samples(sample, sample + scope.n_frames() as usize); + // Notes active during current chunk. + let notes = &mut self.notes_out().write().unwrap(); + let length = clip.as_ref().map_or(0, |p|p.read().unwrap().length); + for (sample, pulse) in pulses { + // If a next clip is enqueued, and we're past the end of the current one, + // break the loop here (FIXME count pulse correctly) + let past_end = if clip.is_some() { pulse >= length } else { true }; + if self.next_clip().is_some() && past_end { + return true + } + // If there's a currently playing clip, output notes from it to buffer: + if let Some(ref clip) = clip { + Self::play_pulse(clip, pulse, sample, note_buf, out, notes) + } + } + false + } + + fn play_pulse ( + clip: &RwLock, + pulse: usize, + sample: usize, + note_buf: &mut Vec, + out: &mut [Vec>], + notes: &mut [bool;128] + ) { + // Source clip from which the MIDI events will be taken. + let clip = clip.read().unwrap(); + // Clip with zero length is not processed + if clip.length > 0 { + // Current pulse index in source clip + let pulse = pulse % clip.length; + // Output each MIDI event from clip at appropriate frames of output buffer: + for message in clip.notes[pulse].iter() { + // Clear output buffer for this MIDI event. + note_buf.clear(); + // TODO: support MIDI channels other than CH1. + let channel = 0.into(); + // Serialize MIDI event into message buffer. + LiveEvent::Midi { channel, message: *message } + .write(note_buf) + .unwrap(); + // Append serialized message to output buffer. + out[sample].push(note_buf.clone()); + // Update the list of currently held notes. + update_keys(&mut*notes, message); + } + } + } + + /// Write a chunk of MIDI data from the output buffer to all assigned output ports. + fn write (&mut self, scope: &ProcessScope, out: &[Vec>]) { + let samples = scope.n_frames() as usize; + for port in self.midi_outs_mut().iter_mut() { + Self::write_port(&mut port.port_mut().writer(scope), samples, out) + } + } + + /// Write a chunk of MIDI data from the output buffer to an output port. + fn write_port (writer: &mut MidiWriter, samples: usize, out: &[Vec>]) { + for (time, events) in out.iter().enumerate().take(samples) { + for bytes in events.iter() { + writer.write(&RawMidi { time: time as u32, bytes }).unwrap_or_else(|_|{ + panic!("Failed to write MIDI data: {bytes:?}"); + }); + } + } + } +} diff --git a/crates/plugin/Cargo.toml b/crates/plugin/Cargo.toml new file mode 100644 index 00000000..3c97f743 --- /dev/null +++ b/crates/plugin/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "tek_plugin" +edition = { workspace = true } +version = { workspace = true } + +[dependencies] +tengri = { workspace = true } + +tek_jack = { workspace = true } +tek_time = { workspace = true } +tek_midi = { workspace = true } + +livi = { workspace = true, optional = true } + +[features] +default = [ "lv2" ] +lv2 = [ "livi" ] diff --git a/crates/plugin/src/lib.rs b/crates/plugin/src/lib.rs new file mode 100644 index 00000000..40825854 --- /dev/null +++ b/crates/plugin/src/lib.rs @@ -0,0 +1,8 @@ +mod plugin; pub use self::plugin::*; +mod lv2; pub use self::lv2::*; +pub(crate) use std::cmp::Ord; +pub(crate) use std::fmt::{Debug, Formatter}; +pub(crate) use std::sync::{Arc, RwLock}; +pub(crate) use std::thread::JoinHandle; +pub(crate) use ::tek_jack::{*, jack::*}; +pub(crate) use ::tengri::{output::*, tui::{*, ratatui::prelude::*}}; diff --git a/crates/device/src/plugin/lv2.rs b/crates/plugin/src/lv2.rs similarity index 100% rename from crates/device/src/plugin/lv2.rs rename to crates/plugin/src/lv2.rs diff --git a/crates/device/src/plugin/lv2_gui.rs b/crates/plugin/src/lv2_gui.rs similarity index 100% rename from crates/device/src/plugin/lv2_gui.rs rename to crates/plugin/src/lv2_gui.rs diff --git a/crates/device/src/plugin/lv2_tui.rs b/crates/plugin/src/lv2_tui.rs similarity index 100% rename from crates/device/src/plugin/lv2_tui.rs rename to crates/plugin/src/lv2_tui.rs diff --git a/crates/device/src/plugin.rs b/crates/plugin/src/plugin.rs similarity index 99% rename from crates/device/src/plugin.rs rename to crates/plugin/src/plugin.rs index f3f47d79..ddd58e65 100644 --- a/crates/device/src/plugin.rs +++ b/crates/plugin/src/plugin.rs @@ -1,11 +1,5 @@ use crate::*; -mod lv2; -mod lv2_gui; -mod lv2_tui; -mod vst2_tui; -mod vst3_tui; - /// A plugin device. #[derive(Debug)] pub struct Plugin { diff --git a/crates/device/src/plugin/vst2_tui.rs b/crates/plugin/src/vst2_tui.rs similarity index 100% rename from crates/device/src/plugin/vst2_tui.rs rename to crates/plugin/src/vst2_tui.rs diff --git a/crates/device/src/plugin/vst3_tui.rs b/crates/plugin/src/vst3_tui.rs similarity index 100% rename from crates/device/src/plugin/vst3_tui.rs rename to crates/plugin/src/vst3_tui.rs diff --git a/deps/vst/.github/workflows/deploy.yml b/crates/plugin/vst/.github/workflows/deploy.yml similarity index 100% rename from deps/vst/.github/workflows/deploy.yml rename to crates/plugin/vst/.github/workflows/deploy.yml diff --git a/deps/vst/.github/workflows/docs.yml b/crates/plugin/vst/.github/workflows/docs.yml similarity index 100% rename from deps/vst/.github/workflows/docs.yml rename to crates/plugin/vst/.github/workflows/docs.yml diff --git a/deps/vst/.github/workflows/rust.yml b/crates/plugin/vst/.github/workflows/rust.yml similarity index 100% rename from deps/vst/.github/workflows/rust.yml rename to crates/plugin/vst/.github/workflows/rust.yml diff --git a/deps/vst/.gitignore b/crates/plugin/vst/.gitignore similarity index 100% rename from deps/vst/.gitignore rename to crates/plugin/vst/.gitignore diff --git a/deps/vst/CHANGELOG.md b/crates/plugin/vst/CHANGELOG.md similarity index 100% rename from deps/vst/CHANGELOG.md rename to crates/plugin/vst/CHANGELOG.md diff --git a/deps/vst/Cargo.toml b/crates/plugin/vst/Cargo.toml similarity index 100% rename from deps/vst/Cargo.toml rename to crates/plugin/vst/Cargo.toml diff --git a/deps/vst/LICENSE b/crates/plugin/vst/LICENSE similarity index 100% rename from deps/vst/LICENSE rename to crates/plugin/vst/LICENSE diff --git a/deps/vst/README.md b/crates/plugin/vst/README.md similarity index 100% rename from deps/vst/README.md rename to crates/plugin/vst/README.md diff --git a/deps/vst/examples/dimension_expander.rs b/crates/plugin/vst/examples/dimension_expander.rs similarity index 100% rename from deps/vst/examples/dimension_expander.rs rename to crates/plugin/vst/examples/dimension_expander.rs diff --git a/deps/vst/examples/fwd_midi.rs b/crates/plugin/vst/examples/fwd_midi.rs similarity index 100% rename from deps/vst/examples/fwd_midi.rs rename to crates/plugin/vst/examples/fwd_midi.rs diff --git a/deps/vst/examples/gain_effect.rs b/crates/plugin/vst/examples/gain_effect.rs similarity index 100% rename from deps/vst/examples/gain_effect.rs rename to crates/plugin/vst/examples/gain_effect.rs diff --git a/deps/vst/examples/ladder_filter.rs b/crates/plugin/vst/examples/ladder_filter.rs similarity index 100% rename from deps/vst/examples/ladder_filter.rs rename to crates/plugin/vst/examples/ladder_filter.rs diff --git a/deps/vst/examples/simple_host.rs b/crates/plugin/vst/examples/simple_host.rs similarity index 100% rename from deps/vst/examples/simple_host.rs rename to crates/plugin/vst/examples/simple_host.rs diff --git a/deps/vst/examples/sine_synth.rs b/crates/plugin/vst/examples/sine_synth.rs similarity index 100% rename from deps/vst/examples/sine_synth.rs rename to crates/plugin/vst/examples/sine_synth.rs diff --git a/deps/vst/examples/transfer_and_smooth.rs b/crates/plugin/vst/examples/transfer_and_smooth.rs similarity index 100% rename from deps/vst/examples/transfer_and_smooth.rs rename to crates/plugin/vst/examples/transfer_and_smooth.rs diff --git a/deps/vst/osx_vst_bundler.sh b/crates/plugin/vst/osx_vst_bundler.sh similarity index 100% rename from deps/vst/osx_vst_bundler.sh rename to crates/plugin/vst/osx_vst_bundler.sh diff --git a/deps/vst/rustfmt.toml b/crates/plugin/vst/rustfmt.toml similarity index 100% rename from deps/vst/rustfmt.toml rename to crates/plugin/vst/rustfmt.toml diff --git a/deps/vst/src/api.rs b/crates/plugin/vst/src/api.rs similarity index 100% rename from deps/vst/src/api.rs rename to crates/plugin/vst/src/api.rs diff --git a/deps/vst/src/buffer.rs b/crates/plugin/vst/src/buffer.rs similarity index 100% rename from deps/vst/src/buffer.rs rename to crates/plugin/vst/src/buffer.rs diff --git a/deps/vst/src/cache.rs b/crates/plugin/vst/src/cache.rs similarity index 100% rename from deps/vst/src/cache.rs rename to crates/plugin/vst/src/cache.rs diff --git a/deps/vst/src/channels.rs b/crates/plugin/vst/src/channels.rs similarity index 100% rename from deps/vst/src/channels.rs rename to crates/plugin/vst/src/channels.rs diff --git a/deps/vst/src/editor.rs b/crates/plugin/vst/src/editor.rs similarity index 100% rename from deps/vst/src/editor.rs rename to crates/plugin/vst/src/editor.rs diff --git a/deps/vst/src/event.rs b/crates/plugin/vst/src/event.rs similarity index 100% rename from deps/vst/src/event.rs rename to crates/plugin/vst/src/event.rs diff --git a/deps/vst/src/host.rs b/crates/plugin/vst/src/host.rs similarity index 100% rename from deps/vst/src/host.rs rename to crates/plugin/vst/src/host.rs diff --git a/deps/vst/src/interfaces.rs b/crates/plugin/vst/src/interfaces.rs similarity index 100% rename from deps/vst/src/interfaces.rs rename to crates/plugin/vst/src/interfaces.rs diff --git a/deps/vst/src/lib.rs b/crates/plugin/vst/src/lib.rs similarity index 100% rename from deps/vst/src/lib.rs rename to crates/plugin/vst/src/lib.rs diff --git a/deps/vst/src/plugin.rs b/crates/plugin/vst/src/plugin.rs similarity index 100% rename from deps/vst/src/plugin.rs rename to crates/plugin/vst/src/plugin.rs diff --git a/deps/vst/src/prelude.rs b/crates/plugin/vst/src/prelude.rs similarity index 100% rename from deps/vst/src/prelude.rs rename to crates/plugin/vst/src/prelude.rs diff --git a/deps/vst/src/util/atomic_float.rs b/crates/plugin/vst/src/util/atomic_float.rs similarity index 100% rename from deps/vst/src/util/atomic_float.rs rename to crates/plugin/vst/src/util/atomic_float.rs diff --git a/deps/vst/src/util/mod.rs b/crates/plugin/vst/src/util/mod.rs similarity index 100% rename from deps/vst/src/util/mod.rs rename to crates/plugin/vst/src/util/mod.rs diff --git a/deps/vst/src/util/parameter_transfer.rs b/crates/plugin/vst/src/util/parameter_transfer.rs similarity index 100% rename from deps/vst/src/util/parameter_transfer.rs rename to crates/plugin/vst/src/util/parameter_transfer.rs diff --git a/crates/sampler/Cargo.toml b/crates/sampler/Cargo.toml new file mode 100644 index 00000000..f2cb8b5b --- /dev/null +++ b/crates/sampler/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "tek_sampler" +edition = { workspace = true } +version = { workspace = true } + +[dependencies] +tengri = { workspace = true } + +tek_jack = { workspace = true } +tek_time = { workspace = true } +tek_midi = { workspace = true } + +symphonia = { workspace = true } +wavers = { workspace = true } diff --git a/.old/sampler_scratch.rs b/crates/sampler/sampler_scratch.rs similarity index 100% rename from .old/sampler_scratch.rs rename to crates/sampler/sampler_scratch.rs diff --git a/crates/device/src/sampler.rs b/crates/sampler/src/lib.rs similarity index 56% rename from crates/device/src/sampler.rs rename to crates/sampler/src/lib.rs index 6423684b..8ddfc4b6 100644 --- a/crates/device/src/sampler.rs +++ b/crates/sampler/src/lib.rs @@ -1,5 +1,13 @@ -use crate::*; +#![feature(let_chains)] +pub(crate) use ::tek_jack::{*, jack::*}; +pub(crate) use ::tek_midi::{*, midly::{*, live::*, num::*}}; +pub(crate) use ::tengri::{dsl::*, input::*, output::*, tui::{*, ratatui::prelude::*}}; +pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicUsize, Ordering::Relaxed}}; +pub(crate) use std::fs::File; +pub(crate) use std::path::PathBuf; +pub(crate) use std::error::Error; +pub(crate) use std::ffi::OsString; pub(crate) use symphonia::{ core::{ formats::Packet, @@ -11,6 +19,7 @@ pub(crate) use symphonia::{ }, default::get_codecs, }; +pub(crate) use ratatui::{prelude::Rect, widgets::{Widget, canvas::{Canvas, Line}}}; mod sampler_api; pub use self::sampler_api::*; mod sampler_audio; pub use self::sampler_audio::*; diff --git a/crates/device/src/sampler/sampler_api.rs b/crates/sampler/src/sampler_api.rs similarity index 100% rename from crates/device/src/sampler/sampler_api.rs rename to crates/sampler/src/sampler_api.rs diff --git a/crates/device/src/sampler/sampler_audio.rs b/crates/sampler/src/sampler_audio.rs similarity index 100% rename from crates/device/src/sampler/sampler_audio.rs rename to crates/sampler/src/sampler_audio.rs diff --git a/crates/device/src/sampler/sampler_browse.rs b/crates/sampler/src/sampler_browse.rs similarity index 100% rename from crates/device/src/sampler/sampler_browse.rs rename to crates/sampler/src/sampler_browse.rs diff --git a/crates/device/src/sampler/sampler_data.rs b/crates/sampler/src/sampler_data.rs similarity index 100% rename from crates/device/src/sampler/sampler_data.rs rename to crates/sampler/src/sampler_data.rs diff --git a/crates/device/src/sampler/sampler_midi.rs b/crates/sampler/src/sampler_midi.rs similarity index 100% rename from crates/device/src/sampler/sampler_midi.rs rename to crates/sampler/src/sampler_midi.rs diff --git a/crates/device/src/sampler/sampler_model.rs b/crates/sampler/src/sampler_model.rs similarity index 100% rename from crates/device/src/sampler/sampler_model.rs rename to crates/sampler/src/sampler_model.rs diff --git a/crates/device/src/sampler/sampler_view.rs b/crates/sampler/src/sampler_view.rs similarity index 100% rename from crates/device/src/sampler/sampler_view.rs rename to crates/sampler/src/sampler_view.rs diff --git a/crates/time/Cargo.toml b/crates/time/Cargo.toml new file mode 100644 index 00000000..9dda5695 --- /dev/null +++ b/crates/time/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "tek_time" +edition = { workspace = true } +version = { workspace = true } + +[dependencies] +tengri = { workspace = true } +tek_jack = { workspace = true } +atomic_float = { workspace = true } diff --git a/crates/app/src/clock.rs b/crates/time/src/clock.rs similarity index 100% rename from crates/app/src/clock.rs rename to crates/time/src/clock.rs diff --git a/crates/app/src/clock/clock_api.rs b/crates/time/src/clock/clock_api.rs similarity index 100% rename from crates/app/src/clock/clock_api.rs rename to crates/time/src/clock/clock_api.rs diff --git a/crates/app/src/clock/clock_model.rs b/crates/time/src/clock/clock_model.rs similarity index 100% rename from crates/app/src/clock/clock_model.rs rename to crates/time/src/clock/clock_model.rs diff --git a/crates/engine/src/lib.rs b/crates/time/src/lib.rs similarity index 53% rename from crates/engine/src/lib.rs rename to crates/time/src/lib.rs index a052ac7b..2ad81860 100644 --- a/crates/engine/src/lib.rs +++ b/crates/time/src/lib.rs @@ -1,28 +1,25 @@ -#![feature(type_alias_impl_trait)] +mod clock; pub use self::clock::*; -mod jack; pub use self::jack::*; -mod time; pub use self::time::*; -mod note; pub use self::note::*; -mod midi; pub use self::midi::*; +mod time_moment; pub use self::time_moment::*; +mod time_note; pub use self::time_note::*; +mod time_perf; pub use self::time_perf::*; +mod time_pulse; pub use self::time_pulse::*; +mod time_sample_count; pub use self::time_sample_count::*; +mod time_sample_rate; pub use self::time_sample_rate::*; +mod time_timebase; pub use self::time_timebase::*; +mod time_unit; pub use self::time_unit::*; +mod time_usec; pub use self::time_usec::*; -pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicUsize, AtomicBool, Ordering::Relaxed}}; -pub(crate) use std::path::PathBuf; -pub(crate) use std::fmt::Debug; +pub(crate) use ::tek_jack::{*, jack::{*, contrib::*}}; +pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicBool, AtomicUsize, Ordering::*}}; pub(crate) use std::ops::{Add, Sub, Mul, Div, Rem}; - -pub(crate) use ::tengri::input::*; -pub(crate) use ::tengri::output::*; -pub(crate) use ::tengri::dsl::*; -pub(crate) use ::tengri::tui::*; -pub(crate) use ::tengri::tui::ratatui::style::{Style, Stylize, Color}; - +pub(crate) use ::tengri::{input::*, dsl::*}; pub use ::atomic_float; pub(crate) use atomic_float::*; /// Standard result type. -pub(crate) type Usually = std::result::Result>; - +pub(crate) type Usually = Result>; /// Standard optional result type. -pub(crate) type Perhaps = std::result::Result, Box>; +pub(crate) type Perhaps = Result, Box>; pub trait Gettable { /// Returns current value diff --git a/crates/engine/src/time/time_moment.rs b/crates/time/src/time_moment.rs similarity index 100% rename from crates/engine/src/time/time_moment.rs rename to crates/time/src/time_moment.rs diff --git a/crates/engine/src/time/time_note.rs b/crates/time/src/time_note.rs similarity index 100% rename from crates/engine/src/time/time_note.rs rename to crates/time/src/time_note.rs diff --git a/crates/engine/src/time/time_perf.rs b/crates/time/src/time_perf.rs similarity index 95% rename from crates/engine/src/time/time_perf.rs rename to crates/time/src/time_perf.rs index f14bcc66..c435fdff 100644 --- a/crates/engine/src/time/time_perf.rs +++ b/crates/time/src/time_perf.rs @@ -1,6 +1,5 @@ use crate::*; use tengri::tui::PerfModel; -use ::jack::ProcessScope; pub trait JackPerfModel { fn update_from_jack_scope (&self, t0: Option, scope: &ProcessScope); diff --git a/crates/engine/src/time/time_pulse.rs b/crates/time/src/time_pulse.rs similarity index 100% rename from crates/engine/src/time/time_pulse.rs rename to crates/time/src/time_pulse.rs diff --git a/crates/engine/src/time/time_sample_count.rs b/crates/time/src/time_sample_count.rs similarity index 100% rename from crates/engine/src/time/time_sample_count.rs rename to crates/time/src/time_sample_count.rs diff --git a/crates/engine/src/time/time_sample_rate.rs b/crates/time/src/time_sample_rate.rs similarity index 100% rename from crates/engine/src/time/time_sample_rate.rs rename to crates/time/src/time_sample_rate.rs diff --git a/crates/engine/src/time/time_timebase.rs b/crates/time/src/time_timebase.rs similarity index 100% rename from crates/engine/src/time/time_timebase.rs rename to crates/time/src/time_timebase.rs diff --git a/crates/engine/src/time/time_unit.rs b/crates/time/src/time_unit.rs similarity index 100% rename from crates/engine/src/time/time_unit.rs rename to crates/time/src/time_unit.rs diff --git a/crates/engine/src/time/time_usec.rs b/crates/time/src/time_usec.rs similarity index 100% rename from crates/engine/src/time/time_usec.rs rename to crates/time/src/time_usec.rs