From 8adbdc5bc73e78e555f72f67c5303ca5702ad4b3 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 1 May 2025 16:18:00 +0300 Subject: [PATCH 1/4] add new Selection variants --- crates/app/src/api.rs | 4 +- crates/app/src/model.rs | 182 +++++++++++++++++++++++++--------------- crates/app/src/view.rs | 80 +++++++++--------- crates/cli/tek.rs | 4 +- 4 files changed, 161 insertions(+), 109 deletions(-) diff --git a/crates/app/src/api.rs b/crates/app/src/api.rs index fddb175b..c540066f 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::Clip(t, s) => self.scenes[s].clips[t].clone(), + Selection::TrackClip { track, scene } => self.scenes[scene].clips[track].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::Clip(t, s)) }))) + (t, s) => Self::Select(Selection::TrackClip { track: t, scene: s }) }))) (ClipCommand: ("edit" [a: MaybeClip] Some(Self::Edit(a.unwrap()))) diff --git a/crates/app/src/model.rs b/crates/app/src/model.rs index ce9b5bed..0ed65783 100644 --- a/crates/app/src/model.rs +++ b/crates/app/src/model.rs @@ -121,10 +121,11 @@ 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 { - Selection::Track(t) => Selection::Track(index), - Selection::Clip(t, s) => Selection::Clip(index, s), + Track(_) => Track(index), + TrackClip { track, scene } => TrackClip { track: index, scene }, _ => self.selected }; Ok(index) @@ -177,10 +178,11 @@ 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 { - Selection::Scene(s) => Selection::Scene(index), - Selection::Clip(t, s) => Selection::Clip(t, index), + Scene(_) => Scene(index), + TrackClip { track, scene } => TrackClip { track, scene: index }, _ => self.selected }; Ok(index) @@ -215,15 +217,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::Clip(t, s) = self.selected - && let Some(scene) = self.scenes.get_mut(s) - && let Some(slot) = scene.clips.get_mut(t) + && let Selection::TrackClip { track, scene } = self.selected + && let Some(scene) = self.scenes.get_mut(scene) + && let Some(slot) = scene.clips.get_mut(track) && 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[t].color.base.mix( + self.tracks[track].color.base.mix( scene.color.base, 0.5 ), @@ -239,9 +241,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::Clip(t, s) = self.selected - && let Some(scene) = self.scenes.get_mut(s) - && let Some(slot) = scene.clips.get_mut(t) + && let Selection::TrackClip { track, scene } = self.selected + && let Some(scene) = self.scenes.get_mut(scene) + && let Some(slot) = scene.clips.get_mut(track) && let Some(clip) = slot.as_mut() { let mut swapped = None; @@ -304,8 +306,10 @@ impl Tek { // autoedit: load focused clip in editor. if let Some(ref mut editor) = self.editor { editor.set_clip(match self.selected { - Selection::Clip(t, s) if let Some(Some(Some(clip))) = self - .scenes.get(s).map(|s|s.clips.get(t)) => Some(clip), + Selection::TrackClip { track, scene } + if let Some(Some(Some(clip))) = self + .scenes.get(scene) + .map(|s|s.clips.get(track)) => Some(clip), _ => None }); } @@ -320,14 +324,15 @@ impl Tek { /// Launch a clip or scene pub(crate) fn launch (&mut self) { + use Selection::*; match self.selected { - Selection::Track(t) => { + Track(t) => { self.tracks[t].player.enqueue_next(None) }, - Selection::Clip(t, s) => { - self.tracks[t].player.enqueue_next(self.scenes[s].clips[t].as_ref()) + TrackClip { track, scene } => { + self.tracks[track].player.enqueue_next(self.scenes[scene].clips[track].as_ref()) }, - Selection::Scene(s) => { + Scene(s) => { for t in 0..self.tracks.len() { self.tracks[t].player.enqueue_next(self.scenes[s].clips[t].as_ref()) } @@ -348,25 +353,26 @@ 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 { - Selection::Mix => { + Mix => { let old = self.color; self.color = palette; old }, - Selection::Track(t) => { - let old = self.tracks[t].color; - self.tracks[t].color = palette; - old - } - Selection::Scene(s) => { + 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] { + 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] { let mut clip = clip.write().unwrap(); let old = clip.color; clip.color = palette; @@ -374,7 +380,8 @@ impl Tek { } else { return None } - } + }, + _ => todo!() }) } @@ -426,12 +433,22 @@ pub enum Modal { pub enum Selection { /// The whole mix is selected #[default] Mix, - /// A track is selected. - Track(usize), + /// 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 clip (track × scene) is selected. - Clip(usize, usize), + 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 }, } /// Focus identification methods @@ -446,70 +463,101 @@ impl Selection { matches!(self, Self::Scene(_)) } pub fn is_clip (&self) -> bool { - matches!(self, Self::Clip(_, _)) + matches!(self, Self::TrackClip {..}) } pub fn track (&self) -> Option { use Selection::*; - match self { Clip(t, _) => Some(*t), Track(t) => Some(*t), _ => None } + match self { + Track(track) + | TrackClip { track, .. } + | TrackInput { track, .. } + | TrackOutput { track, .. } + | TrackDevice { track, .. } => Some(*track), + _ => None + } } pub fn track_next (&self, len: usize) -> Self { + use Selection::*; match self { - 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), + 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!() } } pub fn track_prev (&self) -> Self { + use Selection::*; match self { - 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), + 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!() } } pub fn scene (&self) -> Option { use Selection::*; - match self { Clip(_, s) => Some(*s), Scene(s) => Some(*s), _ => None } + match self { + Scene(scene) | TrackClip { scene, .. } => Some(*scene), + _ => None + } } pub fn scene_next (&self, len: usize) -> Self { + use Selection::*; match self { - 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), + 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!() } } pub fn scene_prev (&self) -> Self { + use Selection::*; match self { - 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), + 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!() } } pub fn describe (&self, tracks: &[Track], scenes: &[Scene]) -> Arc { + use Selection::*; format!("{}", match self { - Self::Mix => "Everything".to_string(), - Self::Track(t) => tracks.get(*t).map(|track|format!("T{t}: {}", &track.name)) - .unwrap_or_else(||"T??".into()), - Self::Scene(s) => scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name)) + Mix => "Everything".to_string(), + 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") + 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") }, - _ => format!("T{t} S{s}: Empty"), - } + _ => format!("T{track} S{scene}: Empty"), + }, + _ => todo!() }).into() } } diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs index f4d8f8e0..498ebbbd 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -181,11 +181,12 @@ 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() { - Selection::Track(t) if editing => Some(t), - Selection::Clip(t, _) if editing => Some(t), + Track(t) if editing => Some(t), + TrackClip { track, .. } if editing => Some(track), _ => None }; let bigger = self.editor_w(); @@ -200,10 +201,11 @@ 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() { - Selection::Track(t) => (Some(*t), None), - Selection::Scene(s) => (None, Some(*s)), - Selection::Clip(t, s) => (Some(*t), Some(*s)), + Track(t) => (Some(*t), None), + Scene(s) => (None, Some(*s)), + TrackClip { track, scene } => (Some(*track), Some(*scene)), _ => (None, None) }; let mut y = 0; @@ -304,8 +306,10 @@ 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 { @@ -313,13 +317,12 @@ 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.app.tracks_with_sizes(), - 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 { @@ -353,14 +356,12 @@ 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:"))) + Bsp::s(Align::e("Input:"), Align::e("Into clip:"))) .middle(self.width_mid, per_track_top( self.width_mid, ||self.app.tracks_with_sizes(), - |_, _|{ - Tui::bg(Reset, Align::c(Bsp::s(OctaveVertical::default(), " ------ "))) - })) + |_, _|Tui::bg(Reset, Align::c(Bsp::s(OctaveVertical::default(), " ------ "))))) } /// Render output matrix. @@ -370,20 +371,17 @@ impl<'a> ArrangerView<'a> { Bsp::s(self.output_ports(), self.output_conns()), ))) } - fn output_nexts (&'a self) -> impl Content + 'a { Tryptich::top(2) - .left(self.width_side, Align::ne("From:")) + .left(self.width_side, Align::ne("From clip:")) .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 { Tryptich::top(2) - .left(self.width_side, Align::ne("Next:")) + .left(self.width_side, Align::ne("Next clip:")) .middle(self.width_mid, per_track_top( self.width_mid, ||self.tracks_with_sizes_scrolled(), @@ -412,7 +410,11 @@ 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 "); @@ -740,24 +742,26 @@ 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 0cebdf0d..bf0cd6d6 100644 --- a/crates/cli/tek.rs +++ b/crates/cli/tek.rs @@ -185,12 +185,12 @@ impl Cli { _ => vec![] }, scenes, - selected: Selection::Clip(0, 0), + selected: Selection::TrackClip { track: 0, scene: 0 }, ..Default::default() }; if let &LaunchMode::Arranger { scenes, tracks, track_width, .. } = mode { app.arranger = Default::default(); - app.selected = Selection::Clip(1, 1); + app.selected = Selection::TrackClip { track: 1, scene: 1 }; app.scenes_add(scenes)?; app.tracks_add(tracks, Some(track_width), &[], &[])?; } From c367a0444ee72007a8734c84580b5ec1ec4877c4 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 1 May 2025 17:16:32 +0300 Subject: [PATCH 2/4] apply scroll to input headers --- crates/app/src/view.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs index 498ebbbd..678729e3 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -317,7 +317,7 @@ 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.app.tracks_with_sizes(), + per_track_top(self.width_mid, ||self.tracks_with_sizes_scrolled(), move|_, &Track { color, .. }|io_conns( color.dark.rgb, color.darker.rgb, @@ -334,7 +334,7 @@ impl<'a> ArrangerView<'a> { .middle(self.width_mid, per_track_top( self.width_mid, - ||self.app.tracks_with_sizes(), + ||self.tracks_with_sizes_scrolled(), move|t, track|{ let rec = track.player.recording; let mon = track.player.monitoring; @@ -360,7 +360,7 @@ impl<'a> ArrangerView<'a> { .middle(self.width_mid, per_track_top( self.width_mid, - ||self.app.tracks_with_sizes(), + ||self.tracks_with_sizes_scrolled(), |_, _|Tui::bg(Reset, Align::c(Bsp::s(OctaveVertical::default(), " ------ "))))) } @@ -371,6 +371,7 @@ impl<'a> ArrangerView<'a> { Bsp::s(self.output_ports(), self.output_conns()), ))) } + fn output_nexts (&'a self) -> impl Content + 'a { Tryptich::top(2) .left(self.width_side, Align::ne("From clip:")) @@ -379,6 +380,7 @@ impl<'a> ArrangerView<'a> { ||self.tracks_with_sizes_scrolled(), |_, _|Tui::bg(Reset, Align::c(Bsp::s(" ------ ", OctaveVertical::default()))))) } + fn output_froms (&'a self) -> impl Content + 'a { Tryptich::top(2) .left(self.width_side, Align::ne("Next clip:")) @@ -395,6 +397,7 @@ impl<'a> ArrangerView<'a> { Thunk::new(||Tui::bg(Reset, " ------ ")) ))) } + fn output_ports (&'a self) -> impl Content + 'a { Tryptich::top(1) .left(self.width_side, @@ -421,6 +424,7 @@ impl<'a> ArrangerView<'a> { 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, From 77703d83a511860f405de6bc09bf92b89ca42d35 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 1 May 2025 17:39:29 +0300 Subject: [PATCH 3/4] wip: refactor into fewer crates --- {crates/midi => .old}/midi.scratch.rs | 0 {crates/sampler => .old}/sampler_scratch.rs | 0 Cargo.lock | 47 ++++--------------- Cargo.toml | 18 +++---- crates/app/Cargo.toml | 9 ++-- crates/app/src/view.rs | 33 +++++++------ crates/device/Cargo.toml | 17 +++++++ crates/device/src/lib.rs | 0 .../src/lib.rs => device/src/plugin.rs} | 0 .../{plugin/src => device/src/plugin}/lv2.rs | 0 .../src => device/src/plugin}/lv2_gui.rs | 0 .../src => device/src/plugin}/lv2_tui.rs | 0 .../src => device/src/plugin}/plugin.rs | 0 .../src => device/src/plugin}/vst2_tui.rs | 0 .../src => device/src/plugin}/vst3_tui.rs | 0 .../src/lib.rs => device/src/sampler.rs} | 0 .../src => device/src/sampler}/sampler_api.rs | 0 .../src/sampler}/sampler_audio.rs | 0 .../src/sampler}/sampler_browse.rs | 0 .../src/sampler}/sampler_data.rs | 0 .../src/sampler}/sampler_midi.rs | 0 .../src/sampler}/sampler_model.rs | 0 .../src/sampler}/sampler_view.rs | 0 crates/engine/Cargo.toml | 11 +++++ .../{midi => engine}/examples/midi-import.rs | 0 crates/{time => engine}/src/clock.rs | 0 .../{time => engine}/src/clock/clock_api.rs | 0 .../{time => engine}/src/clock/clock_model.rs | 0 .../{jack/src/lib.rs => engine/src/jack.rs} | 0 .../src => engine/src/jack}/jack_client.rs | 0 .../src => engine/src/jack}/jack_device.rs | 0 .../src => engine/src/jack}/jack_event.rs | 0 .../src => engine/src/jack}/jack_port.rs | 0 .../{midi/src/lib.rs => engine/src/midi.rs} | 0 crates/{midi/src => engine/src/midi}/clip.rs | 0 .../src/midi}/clip/clip_editor.rs | 0 .../src/midi}/clip/clip_launch.rs | 0 .../src/midi}/clip/clip_model.rs | 0 .../src => engine/src/midi}/clip/clip_play.rs | 0 .../src => engine/src/midi}/clip/clip_view.rs | 0 crates/{midi/src => engine/src/midi}/mode.rs | 0 .../src/midi}/mode/mode_browse.rs | 0 .../src/midi}/mode/mode_length.rs | 0 .../src/midi}/mode/mode_rename.rs | 0 crates/{midi/src => engine/src/midi}/note.rs | 0 .../src/midi}/note/note_pitch.rs | 0 .../src/midi}/note/note_point.rs | 0 .../src/midi}/note/note_range.rs | 0 crates/{midi/src => engine/src/midi}/piano.rs | 0 .../src => engine/src/midi}/piano/piano_h.rs | 0 .../src => engine/src/midi}/piano/piano_v.rs | 0 crates/{midi/src => engine/src/midi}/pool.rs | 0 .../src => engine/src/midi}/pool/pool_api.rs | 0 .../src/midi}/pool/pool_clips.rs | 0 .../src/midi}/pool/pool_model.rs | 0 .../src => engine/src/midi}/pool/pool_view.rs | 0 crates/{midi/src => engine/src/midi}/port.rs | 0 .../src => engine/src/midi}/port/port_in.rs | 0 .../src => engine/src/midi}/port/port_out.rs | 0 .../{time/src/lib.rs => engine/src/time.rs} | 0 .../src => engine/src/time}/time_moment.rs | 0 .../src => engine/src/time}/time_note.rs | 0 .../src => engine/src/time}/time_perf.rs | 0 .../src => engine/src/time}/time_pulse.rs | 0 .../src/time}/time_sample_count.rs | 0 .../src/time}/time_sample_rate.rs | 0 .../src => engine/src/time}/time_timebase.rs | 0 .../src => engine/src/time}/time_unit.rs | 0 .../src => engine/src/time}/time_usec.rs | 0 crates/jack/Cargo.toml | 7 --- crates/midi/Cargo.toml | 13 ----- crates/plugin/Cargo.toml | 17 ------- crates/sampler/Cargo.toml | 14 ------ crates/time/Cargo.toml | 9 ---- .../vst/.github/workflows/deploy.yml | 0 .../vst/.github/workflows/docs.yml | 0 .../vst/.github/workflows/rust.yml | 0 {crates/plugin => deps}/vst/.gitignore | 0 {crates/plugin => deps}/vst/CHANGELOG.md | 0 {crates/plugin => deps}/vst/Cargo.toml | 0 {crates/plugin => deps}/vst/LICENSE | 0 {crates/plugin => deps}/vst/README.md | 0 .../vst/examples/dimension_expander.rs | 0 .../plugin => deps}/vst/examples/fwd_midi.rs | 0 .../vst/examples/gain_effect.rs | 0 .../vst/examples/ladder_filter.rs | 0 .../vst/examples/simple_host.rs | 0 .../vst/examples/sine_synth.rs | 0 .../vst/examples/transfer_and_smooth.rs | 0 .../plugin => deps}/vst/osx_vst_bundler.sh | 0 {crates/plugin => deps}/vst/rustfmt.toml | 0 {crates/plugin => deps}/vst/src/api.rs | 0 {crates/plugin => deps}/vst/src/buffer.rs | 0 {crates/plugin => deps}/vst/src/cache.rs | 0 {crates/plugin => deps}/vst/src/channels.rs | 0 {crates/plugin => deps}/vst/src/editor.rs | 0 {crates/plugin => deps}/vst/src/event.rs | 0 {crates/plugin => deps}/vst/src/host.rs | 0 {crates/plugin => deps}/vst/src/interfaces.rs | 0 {crates/plugin => deps}/vst/src/lib.rs | 0 {crates/plugin => deps}/vst/src/plugin.rs | 0 {crates/plugin => deps}/vst/src/prelude.rs | 0 .../vst/src/util/atomic_float.rs | 0 {crates/plugin => deps}/vst/src/util/mod.rs | 0 .../vst/src/util/parameter_transfer.rs | 0 105 files changed, 64 insertions(+), 131 deletions(-) rename {crates/midi => .old}/midi.scratch.rs (100%) rename {crates/sampler => .old}/sampler_scratch.rs (100%) create mode 100644 crates/device/Cargo.toml create mode 100644 crates/device/src/lib.rs rename crates/{plugin/src/lib.rs => device/src/plugin.rs} (100%) rename crates/{plugin/src => device/src/plugin}/lv2.rs (100%) rename crates/{plugin/src => device/src/plugin}/lv2_gui.rs (100%) rename crates/{plugin/src => device/src/plugin}/lv2_tui.rs (100%) rename crates/{plugin/src => device/src/plugin}/plugin.rs (100%) rename crates/{plugin/src => device/src/plugin}/vst2_tui.rs (100%) rename crates/{plugin/src => device/src/plugin}/vst3_tui.rs (100%) rename crates/{sampler/src/lib.rs => device/src/sampler.rs} (100%) rename crates/{sampler/src => device/src/sampler}/sampler_api.rs (100%) rename crates/{sampler/src => device/src/sampler}/sampler_audio.rs (100%) rename crates/{sampler/src => device/src/sampler}/sampler_browse.rs (100%) rename crates/{sampler/src => device/src/sampler}/sampler_data.rs (100%) rename crates/{sampler/src => device/src/sampler}/sampler_midi.rs (100%) rename crates/{sampler/src => device/src/sampler}/sampler_model.rs (100%) rename crates/{sampler/src => device/src/sampler}/sampler_view.rs (100%) create mode 100644 crates/engine/Cargo.toml rename crates/{midi => engine}/examples/midi-import.rs (100%) rename crates/{time => engine}/src/clock.rs (100%) rename crates/{time => engine}/src/clock/clock_api.rs (100%) rename crates/{time => engine}/src/clock/clock_model.rs (100%) rename crates/{jack/src/lib.rs => engine/src/jack.rs} (100%) rename crates/{jack/src => engine/src/jack}/jack_client.rs (100%) rename crates/{jack/src => engine/src/jack}/jack_device.rs (100%) rename crates/{jack/src => engine/src/jack}/jack_event.rs (100%) rename crates/{jack/src => engine/src/jack}/jack_port.rs (100%) rename crates/{midi/src/lib.rs => engine/src/midi.rs} (100%) rename crates/{midi/src => engine/src/midi}/clip.rs (100%) rename crates/{midi/src => engine/src/midi}/clip/clip_editor.rs (100%) rename crates/{midi/src => engine/src/midi}/clip/clip_launch.rs (100%) rename crates/{midi/src => engine/src/midi}/clip/clip_model.rs (100%) rename crates/{midi/src => engine/src/midi}/clip/clip_play.rs (100%) rename crates/{midi/src => engine/src/midi}/clip/clip_view.rs (100%) rename crates/{midi/src => engine/src/midi}/mode.rs (100%) rename crates/{midi/src => engine/src/midi}/mode/mode_browse.rs (100%) rename crates/{midi/src => engine/src/midi}/mode/mode_length.rs (100%) rename crates/{midi/src => engine/src/midi}/mode/mode_rename.rs (100%) rename crates/{midi/src => engine/src/midi}/note.rs (100%) rename crates/{midi/src => engine/src/midi}/note/note_pitch.rs (100%) rename crates/{midi/src => engine/src/midi}/note/note_point.rs (100%) rename crates/{midi/src => engine/src/midi}/note/note_range.rs (100%) rename crates/{midi/src => engine/src/midi}/piano.rs (100%) rename crates/{midi/src => engine/src/midi}/piano/piano_h.rs (100%) rename crates/{midi/src => engine/src/midi}/piano/piano_v.rs (100%) rename crates/{midi/src => engine/src/midi}/pool.rs (100%) rename crates/{midi/src => engine/src/midi}/pool/pool_api.rs (100%) rename crates/{midi/src => engine/src/midi}/pool/pool_clips.rs (100%) rename crates/{midi/src => engine/src/midi}/pool/pool_model.rs (100%) rename crates/{midi/src => engine/src/midi}/pool/pool_view.rs (100%) rename crates/{midi/src => engine/src/midi}/port.rs (100%) rename crates/{midi/src => engine/src/midi}/port/port_in.rs (100%) rename crates/{midi/src => engine/src/midi}/port/port_out.rs (100%) rename crates/{time/src/lib.rs => engine/src/time.rs} (100%) rename crates/{time/src => engine/src/time}/time_moment.rs (100%) rename crates/{time/src => engine/src/time}/time_note.rs (100%) rename crates/{time/src => engine/src/time}/time_perf.rs (100%) rename crates/{time/src => engine/src/time}/time_pulse.rs (100%) rename crates/{time/src => engine/src/time}/time_sample_count.rs (100%) rename crates/{time/src => engine/src/time}/time_sample_rate.rs (100%) rename crates/{time/src => engine/src/time}/time_timebase.rs (100%) rename crates/{time/src => engine/src/time}/time_unit.rs (100%) rename crates/{time/src => engine/src/time}/time_usec.rs (100%) delete mode 100644 crates/jack/Cargo.toml delete mode 100644 crates/midi/Cargo.toml delete mode 100644 crates/plugin/Cargo.toml delete mode 100644 crates/sampler/Cargo.toml delete mode 100644 crates/time/Cargo.toml rename {crates/plugin => deps}/vst/.github/workflows/deploy.yml (100%) rename {crates/plugin => deps}/vst/.github/workflows/docs.yml (100%) rename {crates/plugin => deps}/vst/.github/workflows/rust.yml (100%) rename {crates/plugin => deps}/vst/.gitignore (100%) rename {crates/plugin => deps}/vst/CHANGELOG.md (100%) rename {crates/plugin => deps}/vst/Cargo.toml (100%) rename {crates/plugin => deps}/vst/LICENSE (100%) rename {crates/plugin => deps}/vst/README.md (100%) rename {crates/plugin => deps}/vst/examples/dimension_expander.rs (100%) rename {crates/plugin => deps}/vst/examples/fwd_midi.rs (100%) rename {crates/plugin => deps}/vst/examples/gain_effect.rs (100%) rename {crates/plugin => deps}/vst/examples/ladder_filter.rs (100%) rename {crates/plugin => deps}/vst/examples/simple_host.rs (100%) rename {crates/plugin => deps}/vst/examples/sine_synth.rs (100%) rename {crates/plugin => deps}/vst/examples/transfer_and_smooth.rs (100%) rename {crates/plugin => deps}/vst/osx_vst_bundler.sh (100%) rename {crates/plugin => deps}/vst/rustfmt.toml (100%) rename {crates/plugin => deps}/vst/src/api.rs (100%) rename {crates/plugin => deps}/vst/src/buffer.rs (100%) rename {crates/plugin => deps}/vst/src/cache.rs (100%) rename {crates/plugin => deps}/vst/src/channels.rs (100%) rename {crates/plugin => deps}/vst/src/editor.rs (100%) rename {crates/plugin => deps}/vst/src/event.rs (100%) rename {crates/plugin => deps}/vst/src/host.rs (100%) rename {crates/plugin => deps}/vst/src/interfaces.rs (100%) rename {crates/plugin => deps}/vst/src/lib.rs (100%) rename {crates/plugin => deps}/vst/src/plugin.rs (100%) rename {crates/plugin => deps}/vst/src/prelude.rs (100%) rename {crates/plugin => deps}/vst/src/util/atomic_float.rs (100%) rename {crates/plugin => deps}/vst/src/util/mod.rs (100%) rename {crates/plugin => deps}/vst/src/util/parameter_transfer.rs (100%) diff --git a/crates/midi/midi.scratch.rs b/.old/midi.scratch.rs similarity index 100% rename from crates/midi/midi.scratch.rs rename to .old/midi.scratch.rs diff --git a/crates/sampler/sampler_scratch.rs b/.old/sampler_scratch.rs similarity index 100% rename from crates/sampler/sampler_scratch.rs rename to .old/sampler_scratch.rs diff --git a/Cargo.lock b/Cargo.lock index d8661d57..a5d8ba38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1518,11 +1518,8 @@ dependencies = [ "proptest", "proptest-derive", "rand", - "tek_jack", - "tek_midi", - "tek_plugin", - "tek_sampler", - "tek_time", + "tek_device", + "tek_engine", "tengri", "toml", ] @@ -1536,53 +1533,25 @@ dependencies = [ ] [[package]] -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" +name = "tek_device" version = "0.2.1" dependencies = [ "livi", - "tek_jack", - "tek_midi", - "tek_time", - "tengri", -] - -[[package]] -name = "tek_sampler" -version = "0.2.1" -dependencies = [ "symphonia", - "tek_jack", - "tek_midi", - "tek_time", + "tek_engine", "tengri", "wavers", ] [[package]] -name = "tek_time" +name = "tek_engine" version = "0.2.1" dependencies = [ "atomic_float", - "tek_jack", + "jack", + "midly", "tengri", + "uuid", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 215d7797..63d91683 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,13 +5,10 @@ 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" @@ -37,13 +34,10 @@ path = "./deps/rust-jack" #default-features = false [workspace.dependencies] -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" } +tek_device = { path = "./crates/device" } +tek_engine = { path = "./crates/engine" } +tek = { path = "./crates/app" } +tek_cli = { path = "./crates/cli" } atomic_float = { version = "1.0.0" } backtrace = { version = "0.3.72" } diff --git a/crates/app/Cargo.toml b/crates/app/Cargo.toml index fda6c38e..7ed1134b 100644 --- a/crates/app/Cargo.toml +++ b/crates/app/Cargo.toml @@ -6,11 +6,8 @@ version = { workspace = true } [dependencies] tengri = { workspace = true } -tek_jack = { workspace = true } -tek_time = { workspace = true } -tek_midi = { workspace = true } -tek_sampler = { workspace = true } -tek_plugin = { workspace = true, optional = true } +tek_engine = { workspace = true } +tek_device = { workspace = true } backtrace = { workspace = true } clap = { workspace = true, optional = true } @@ -25,4 +22,4 @@ proptest-derive = { workspace = true } [features] default = ["cli"] cli = ["clap"] -host = ["tek_plugin"] +host = ["tek_device/lv2"] diff --git a/crates/app/src/view.rs b/crates/app/src/view.rs index 678729e3..ec030508 100644 --- a/crates/app/src/view.rs +++ b/crates/app/src/view.rs @@ -382,20 +382,25 @@ impl<'a> ArrangerView<'a> { } fn output_froms (&'a self) -> impl Content + 'a { - Tryptich::top(2) - .left(self.width_side, Align::ne("Next clip:")) - .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, " ------ ")) - ))) + 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) + })) } fn output_ports (&'a self) -> impl Content + 'a { diff --git a/crates/device/Cargo.toml b/crates/device/Cargo.toml new file mode 100644 index 00000000..9f94bfbb --- /dev/null +++ b/crates/device/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "tek_device" +edition = { workspace = true } +version = { workspace = true } + +[dependencies] +tengri = { workspace = true } +tek_engine = { workspace = 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 = [] diff --git a/crates/device/src/lib.rs b/crates/device/src/lib.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/plugin/src/lib.rs b/crates/device/src/plugin.rs similarity index 100% rename from crates/plugin/src/lib.rs rename to crates/device/src/plugin.rs diff --git a/crates/plugin/src/lv2.rs b/crates/device/src/plugin/lv2.rs similarity index 100% rename from crates/plugin/src/lv2.rs rename to crates/device/src/plugin/lv2.rs diff --git a/crates/plugin/src/lv2_gui.rs b/crates/device/src/plugin/lv2_gui.rs similarity index 100% rename from crates/plugin/src/lv2_gui.rs rename to crates/device/src/plugin/lv2_gui.rs diff --git a/crates/plugin/src/lv2_tui.rs b/crates/device/src/plugin/lv2_tui.rs similarity index 100% rename from crates/plugin/src/lv2_tui.rs rename to crates/device/src/plugin/lv2_tui.rs diff --git a/crates/plugin/src/plugin.rs b/crates/device/src/plugin/plugin.rs similarity index 100% rename from crates/plugin/src/plugin.rs rename to crates/device/src/plugin/plugin.rs diff --git a/crates/plugin/src/vst2_tui.rs b/crates/device/src/plugin/vst2_tui.rs similarity index 100% rename from crates/plugin/src/vst2_tui.rs rename to crates/device/src/plugin/vst2_tui.rs diff --git a/crates/plugin/src/vst3_tui.rs b/crates/device/src/plugin/vst3_tui.rs similarity index 100% rename from crates/plugin/src/vst3_tui.rs rename to crates/device/src/plugin/vst3_tui.rs diff --git a/crates/sampler/src/lib.rs b/crates/device/src/sampler.rs similarity index 100% rename from crates/sampler/src/lib.rs rename to crates/device/src/sampler.rs diff --git a/crates/sampler/src/sampler_api.rs b/crates/device/src/sampler/sampler_api.rs similarity index 100% rename from crates/sampler/src/sampler_api.rs rename to crates/device/src/sampler/sampler_api.rs diff --git a/crates/sampler/src/sampler_audio.rs b/crates/device/src/sampler/sampler_audio.rs similarity index 100% rename from crates/sampler/src/sampler_audio.rs rename to crates/device/src/sampler/sampler_audio.rs diff --git a/crates/sampler/src/sampler_browse.rs b/crates/device/src/sampler/sampler_browse.rs similarity index 100% rename from crates/sampler/src/sampler_browse.rs rename to crates/device/src/sampler/sampler_browse.rs diff --git a/crates/sampler/src/sampler_data.rs b/crates/device/src/sampler/sampler_data.rs similarity index 100% rename from crates/sampler/src/sampler_data.rs rename to crates/device/src/sampler/sampler_data.rs diff --git a/crates/sampler/src/sampler_midi.rs b/crates/device/src/sampler/sampler_midi.rs similarity index 100% rename from crates/sampler/src/sampler_midi.rs rename to crates/device/src/sampler/sampler_midi.rs diff --git a/crates/sampler/src/sampler_model.rs b/crates/device/src/sampler/sampler_model.rs similarity index 100% rename from crates/sampler/src/sampler_model.rs rename to crates/device/src/sampler/sampler_model.rs diff --git a/crates/sampler/src/sampler_view.rs b/crates/device/src/sampler/sampler_view.rs similarity index 100% rename from crates/sampler/src/sampler_view.rs rename to crates/device/src/sampler/sampler_view.rs diff --git a/crates/engine/Cargo.toml b/crates/engine/Cargo.toml new file mode 100644 index 00000000..d097e945 --- /dev/null +++ b/crates/engine/Cargo.toml @@ -0,0 +1,11 @@ +[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/midi/examples/midi-import.rs b/crates/engine/examples/midi-import.rs similarity index 100% rename from crates/midi/examples/midi-import.rs rename to crates/engine/examples/midi-import.rs diff --git a/crates/time/src/clock.rs b/crates/engine/src/clock.rs similarity index 100% rename from crates/time/src/clock.rs rename to crates/engine/src/clock.rs diff --git a/crates/time/src/clock/clock_api.rs b/crates/engine/src/clock/clock_api.rs similarity index 100% rename from crates/time/src/clock/clock_api.rs rename to crates/engine/src/clock/clock_api.rs diff --git a/crates/time/src/clock/clock_model.rs b/crates/engine/src/clock/clock_model.rs similarity index 100% rename from crates/time/src/clock/clock_model.rs rename to crates/engine/src/clock/clock_model.rs diff --git a/crates/jack/src/lib.rs b/crates/engine/src/jack.rs similarity index 100% rename from crates/jack/src/lib.rs rename to crates/engine/src/jack.rs diff --git a/crates/jack/src/jack_client.rs b/crates/engine/src/jack/jack_client.rs similarity index 100% rename from crates/jack/src/jack_client.rs rename to crates/engine/src/jack/jack_client.rs diff --git a/crates/jack/src/jack_device.rs b/crates/engine/src/jack/jack_device.rs similarity index 100% rename from crates/jack/src/jack_device.rs rename to crates/engine/src/jack/jack_device.rs diff --git a/crates/jack/src/jack_event.rs b/crates/engine/src/jack/jack_event.rs similarity index 100% rename from crates/jack/src/jack_event.rs rename to crates/engine/src/jack/jack_event.rs diff --git a/crates/jack/src/jack_port.rs b/crates/engine/src/jack/jack_port.rs similarity index 100% rename from crates/jack/src/jack_port.rs rename to crates/engine/src/jack/jack_port.rs diff --git a/crates/midi/src/lib.rs b/crates/engine/src/midi.rs similarity index 100% rename from crates/midi/src/lib.rs rename to crates/engine/src/midi.rs diff --git a/crates/midi/src/clip.rs b/crates/engine/src/midi/clip.rs similarity index 100% rename from crates/midi/src/clip.rs rename to crates/engine/src/midi/clip.rs diff --git a/crates/midi/src/clip/clip_editor.rs b/crates/engine/src/midi/clip/clip_editor.rs similarity index 100% rename from crates/midi/src/clip/clip_editor.rs rename to crates/engine/src/midi/clip/clip_editor.rs diff --git a/crates/midi/src/clip/clip_launch.rs b/crates/engine/src/midi/clip/clip_launch.rs similarity index 100% rename from crates/midi/src/clip/clip_launch.rs rename to crates/engine/src/midi/clip/clip_launch.rs diff --git a/crates/midi/src/clip/clip_model.rs b/crates/engine/src/midi/clip/clip_model.rs similarity index 100% rename from crates/midi/src/clip/clip_model.rs rename to crates/engine/src/midi/clip/clip_model.rs diff --git a/crates/midi/src/clip/clip_play.rs b/crates/engine/src/midi/clip/clip_play.rs similarity index 100% rename from crates/midi/src/clip/clip_play.rs rename to crates/engine/src/midi/clip/clip_play.rs diff --git a/crates/midi/src/clip/clip_view.rs b/crates/engine/src/midi/clip/clip_view.rs similarity index 100% rename from crates/midi/src/clip/clip_view.rs rename to crates/engine/src/midi/clip/clip_view.rs diff --git a/crates/midi/src/mode.rs b/crates/engine/src/midi/mode.rs similarity index 100% rename from crates/midi/src/mode.rs rename to crates/engine/src/midi/mode.rs diff --git a/crates/midi/src/mode/mode_browse.rs b/crates/engine/src/midi/mode/mode_browse.rs similarity index 100% rename from crates/midi/src/mode/mode_browse.rs rename to crates/engine/src/midi/mode/mode_browse.rs diff --git a/crates/midi/src/mode/mode_length.rs b/crates/engine/src/midi/mode/mode_length.rs similarity index 100% rename from crates/midi/src/mode/mode_length.rs rename to crates/engine/src/midi/mode/mode_length.rs diff --git a/crates/midi/src/mode/mode_rename.rs b/crates/engine/src/midi/mode/mode_rename.rs similarity index 100% rename from crates/midi/src/mode/mode_rename.rs rename to crates/engine/src/midi/mode/mode_rename.rs diff --git a/crates/midi/src/note.rs b/crates/engine/src/midi/note.rs similarity index 100% rename from crates/midi/src/note.rs rename to crates/engine/src/midi/note.rs diff --git a/crates/midi/src/note/note_pitch.rs b/crates/engine/src/midi/note/note_pitch.rs similarity index 100% rename from crates/midi/src/note/note_pitch.rs rename to crates/engine/src/midi/note/note_pitch.rs diff --git a/crates/midi/src/note/note_point.rs b/crates/engine/src/midi/note/note_point.rs similarity index 100% rename from crates/midi/src/note/note_point.rs rename to crates/engine/src/midi/note/note_point.rs diff --git a/crates/midi/src/note/note_range.rs b/crates/engine/src/midi/note/note_range.rs similarity index 100% rename from crates/midi/src/note/note_range.rs rename to crates/engine/src/midi/note/note_range.rs diff --git a/crates/midi/src/piano.rs b/crates/engine/src/midi/piano.rs similarity index 100% rename from crates/midi/src/piano.rs rename to crates/engine/src/midi/piano.rs diff --git a/crates/midi/src/piano/piano_h.rs b/crates/engine/src/midi/piano/piano_h.rs similarity index 100% rename from crates/midi/src/piano/piano_h.rs rename to crates/engine/src/midi/piano/piano_h.rs diff --git a/crates/midi/src/piano/piano_v.rs b/crates/engine/src/midi/piano/piano_v.rs similarity index 100% rename from crates/midi/src/piano/piano_v.rs rename to crates/engine/src/midi/piano/piano_v.rs diff --git a/crates/midi/src/pool.rs b/crates/engine/src/midi/pool.rs similarity index 100% rename from crates/midi/src/pool.rs rename to crates/engine/src/midi/pool.rs diff --git a/crates/midi/src/pool/pool_api.rs b/crates/engine/src/midi/pool/pool_api.rs similarity index 100% rename from crates/midi/src/pool/pool_api.rs rename to crates/engine/src/midi/pool/pool_api.rs diff --git a/crates/midi/src/pool/pool_clips.rs b/crates/engine/src/midi/pool/pool_clips.rs similarity index 100% rename from crates/midi/src/pool/pool_clips.rs rename to crates/engine/src/midi/pool/pool_clips.rs diff --git a/crates/midi/src/pool/pool_model.rs b/crates/engine/src/midi/pool/pool_model.rs similarity index 100% rename from crates/midi/src/pool/pool_model.rs rename to crates/engine/src/midi/pool/pool_model.rs diff --git a/crates/midi/src/pool/pool_view.rs b/crates/engine/src/midi/pool/pool_view.rs similarity index 100% rename from crates/midi/src/pool/pool_view.rs rename to crates/engine/src/midi/pool/pool_view.rs diff --git a/crates/midi/src/port.rs b/crates/engine/src/midi/port.rs similarity index 100% rename from crates/midi/src/port.rs rename to crates/engine/src/midi/port.rs diff --git a/crates/midi/src/port/port_in.rs b/crates/engine/src/midi/port/port_in.rs similarity index 100% rename from crates/midi/src/port/port_in.rs rename to crates/engine/src/midi/port/port_in.rs diff --git a/crates/midi/src/port/port_out.rs b/crates/engine/src/midi/port/port_out.rs similarity index 100% rename from crates/midi/src/port/port_out.rs rename to crates/engine/src/midi/port/port_out.rs diff --git a/crates/time/src/lib.rs b/crates/engine/src/time.rs similarity index 100% rename from crates/time/src/lib.rs rename to crates/engine/src/time.rs diff --git a/crates/time/src/time_moment.rs b/crates/engine/src/time/time_moment.rs similarity index 100% rename from crates/time/src/time_moment.rs rename to crates/engine/src/time/time_moment.rs diff --git a/crates/time/src/time_note.rs b/crates/engine/src/time/time_note.rs similarity index 100% rename from crates/time/src/time_note.rs rename to crates/engine/src/time/time_note.rs diff --git a/crates/time/src/time_perf.rs b/crates/engine/src/time/time_perf.rs similarity index 100% rename from crates/time/src/time_perf.rs rename to crates/engine/src/time/time_perf.rs diff --git a/crates/time/src/time_pulse.rs b/crates/engine/src/time/time_pulse.rs similarity index 100% rename from crates/time/src/time_pulse.rs rename to crates/engine/src/time/time_pulse.rs diff --git a/crates/time/src/time_sample_count.rs b/crates/engine/src/time/time_sample_count.rs similarity index 100% rename from crates/time/src/time_sample_count.rs rename to crates/engine/src/time/time_sample_count.rs diff --git a/crates/time/src/time_sample_rate.rs b/crates/engine/src/time/time_sample_rate.rs similarity index 100% rename from crates/time/src/time_sample_rate.rs rename to crates/engine/src/time/time_sample_rate.rs diff --git a/crates/time/src/time_timebase.rs b/crates/engine/src/time/time_timebase.rs similarity index 100% rename from crates/time/src/time_timebase.rs rename to crates/engine/src/time/time_timebase.rs diff --git a/crates/time/src/time_unit.rs b/crates/engine/src/time/time_unit.rs similarity index 100% rename from crates/time/src/time_unit.rs rename to crates/engine/src/time/time_unit.rs diff --git a/crates/time/src/time_usec.rs b/crates/engine/src/time/time_usec.rs similarity index 100% rename from crates/time/src/time_usec.rs rename to crates/engine/src/time/time_usec.rs diff --git a/crates/jack/Cargo.toml b/crates/jack/Cargo.toml deleted file mode 100644 index e9b8d672..00000000 --- a/crates/jack/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "tek_jack" -edition = { workspace = true } -version = { workspace = true } - -[dependencies] -jack = { workspace = true } diff --git a/crates/midi/Cargo.toml b/crates/midi/Cargo.toml deleted file mode 100644 index a64a74a3..00000000 --- a/crates/midi/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[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/plugin/Cargo.toml b/crates/plugin/Cargo.toml deleted file mode 100644 index 3c97f743..00000000 --- a/crates/plugin/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[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/sampler/Cargo.toml b/crates/sampler/Cargo.toml deleted file mode 100644 index f2cb8b5b..00000000 --- a/crates/sampler/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[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/crates/time/Cargo.toml b/crates/time/Cargo.toml deleted file mode 100644 index 9dda5695..00000000 --- a/crates/time/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[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/plugin/vst/.github/workflows/deploy.yml b/deps/vst/.github/workflows/deploy.yml similarity index 100% rename from crates/plugin/vst/.github/workflows/deploy.yml rename to deps/vst/.github/workflows/deploy.yml diff --git a/crates/plugin/vst/.github/workflows/docs.yml b/deps/vst/.github/workflows/docs.yml similarity index 100% rename from crates/plugin/vst/.github/workflows/docs.yml rename to deps/vst/.github/workflows/docs.yml diff --git a/crates/plugin/vst/.github/workflows/rust.yml b/deps/vst/.github/workflows/rust.yml similarity index 100% rename from crates/plugin/vst/.github/workflows/rust.yml rename to deps/vst/.github/workflows/rust.yml diff --git a/crates/plugin/vst/.gitignore b/deps/vst/.gitignore similarity index 100% rename from crates/plugin/vst/.gitignore rename to deps/vst/.gitignore diff --git a/crates/plugin/vst/CHANGELOG.md b/deps/vst/CHANGELOG.md similarity index 100% rename from crates/plugin/vst/CHANGELOG.md rename to deps/vst/CHANGELOG.md diff --git a/crates/plugin/vst/Cargo.toml b/deps/vst/Cargo.toml similarity index 100% rename from crates/plugin/vst/Cargo.toml rename to deps/vst/Cargo.toml diff --git a/crates/plugin/vst/LICENSE b/deps/vst/LICENSE similarity index 100% rename from crates/plugin/vst/LICENSE rename to deps/vst/LICENSE diff --git a/crates/plugin/vst/README.md b/deps/vst/README.md similarity index 100% rename from crates/plugin/vst/README.md rename to deps/vst/README.md diff --git a/crates/plugin/vst/examples/dimension_expander.rs b/deps/vst/examples/dimension_expander.rs similarity index 100% rename from crates/plugin/vst/examples/dimension_expander.rs rename to deps/vst/examples/dimension_expander.rs diff --git a/crates/plugin/vst/examples/fwd_midi.rs b/deps/vst/examples/fwd_midi.rs similarity index 100% rename from crates/plugin/vst/examples/fwd_midi.rs rename to deps/vst/examples/fwd_midi.rs diff --git a/crates/plugin/vst/examples/gain_effect.rs b/deps/vst/examples/gain_effect.rs similarity index 100% rename from crates/plugin/vst/examples/gain_effect.rs rename to deps/vst/examples/gain_effect.rs diff --git a/crates/plugin/vst/examples/ladder_filter.rs b/deps/vst/examples/ladder_filter.rs similarity index 100% rename from crates/plugin/vst/examples/ladder_filter.rs rename to deps/vst/examples/ladder_filter.rs diff --git a/crates/plugin/vst/examples/simple_host.rs b/deps/vst/examples/simple_host.rs similarity index 100% rename from crates/plugin/vst/examples/simple_host.rs rename to deps/vst/examples/simple_host.rs diff --git a/crates/plugin/vst/examples/sine_synth.rs b/deps/vst/examples/sine_synth.rs similarity index 100% rename from crates/plugin/vst/examples/sine_synth.rs rename to deps/vst/examples/sine_synth.rs diff --git a/crates/plugin/vst/examples/transfer_and_smooth.rs b/deps/vst/examples/transfer_and_smooth.rs similarity index 100% rename from crates/plugin/vst/examples/transfer_and_smooth.rs rename to deps/vst/examples/transfer_and_smooth.rs diff --git a/crates/plugin/vst/osx_vst_bundler.sh b/deps/vst/osx_vst_bundler.sh similarity index 100% rename from crates/plugin/vst/osx_vst_bundler.sh rename to deps/vst/osx_vst_bundler.sh diff --git a/crates/plugin/vst/rustfmt.toml b/deps/vst/rustfmt.toml similarity index 100% rename from crates/plugin/vst/rustfmt.toml rename to deps/vst/rustfmt.toml diff --git a/crates/plugin/vst/src/api.rs b/deps/vst/src/api.rs similarity index 100% rename from crates/plugin/vst/src/api.rs rename to deps/vst/src/api.rs diff --git a/crates/plugin/vst/src/buffer.rs b/deps/vst/src/buffer.rs similarity index 100% rename from crates/plugin/vst/src/buffer.rs rename to deps/vst/src/buffer.rs diff --git a/crates/plugin/vst/src/cache.rs b/deps/vst/src/cache.rs similarity index 100% rename from crates/plugin/vst/src/cache.rs rename to deps/vst/src/cache.rs diff --git a/crates/plugin/vst/src/channels.rs b/deps/vst/src/channels.rs similarity index 100% rename from crates/plugin/vst/src/channels.rs rename to deps/vst/src/channels.rs diff --git a/crates/plugin/vst/src/editor.rs b/deps/vst/src/editor.rs similarity index 100% rename from crates/plugin/vst/src/editor.rs rename to deps/vst/src/editor.rs diff --git a/crates/plugin/vst/src/event.rs b/deps/vst/src/event.rs similarity index 100% rename from crates/plugin/vst/src/event.rs rename to deps/vst/src/event.rs diff --git a/crates/plugin/vst/src/host.rs b/deps/vst/src/host.rs similarity index 100% rename from crates/plugin/vst/src/host.rs rename to deps/vst/src/host.rs diff --git a/crates/plugin/vst/src/interfaces.rs b/deps/vst/src/interfaces.rs similarity index 100% rename from crates/plugin/vst/src/interfaces.rs rename to deps/vst/src/interfaces.rs diff --git a/crates/plugin/vst/src/lib.rs b/deps/vst/src/lib.rs similarity index 100% rename from crates/plugin/vst/src/lib.rs rename to deps/vst/src/lib.rs diff --git a/crates/plugin/vst/src/plugin.rs b/deps/vst/src/plugin.rs similarity index 100% rename from crates/plugin/vst/src/plugin.rs rename to deps/vst/src/plugin.rs diff --git a/crates/plugin/vst/src/prelude.rs b/deps/vst/src/prelude.rs similarity index 100% rename from crates/plugin/vst/src/prelude.rs rename to deps/vst/src/prelude.rs diff --git a/crates/plugin/vst/src/util/atomic_float.rs b/deps/vst/src/util/atomic_float.rs similarity index 100% rename from crates/plugin/vst/src/util/atomic_float.rs rename to deps/vst/src/util/atomic_float.rs diff --git a/crates/plugin/vst/src/util/mod.rs b/deps/vst/src/util/mod.rs similarity index 100% rename from crates/plugin/vst/src/util/mod.rs rename to deps/vst/src/util/mod.rs diff --git a/crates/plugin/vst/src/util/parameter_transfer.rs b/deps/vst/src/util/parameter_transfer.rs similarity index 100% rename from crates/plugin/vst/src/util/parameter_transfer.rs rename to deps/vst/src/util/parameter_transfer.rs From 4cf82af950954d56f13a5c1762b4c02534e423ab Mon Sep 17 00:00:00 2001 From: unspeaker Date: Thu, 1 May 2025 18:03:27 +0300 Subject: [PATCH 4/4] wip: refactor into fewer crates, pt.2 --- Cargo.lock | 1 + crates/{engine => app}/src/clock.rs | 0 crates/{engine => app}/src/clock/clock_api.rs | 0 .../{engine => app}/src/clock/clock_model.rs | 0 crates/app/src/editor.rs | 3 + crates/app/src/editor/editor_api.rs | 97 ++++ .../src/editor/editor_model.rs} | 94 ---- .../src/editor/editor_view.rs} | 36 ++ crates/app/src/lib.rs | 3 + crates/app/src/model.rs | 8 - crates/{engine/src/midi => app/src}/pool.rs | 0 .../src/midi => app/src}/pool/pool_api.rs | 0 .../src/midi => app/src}/pool/pool_clips.rs | 0 .../mode.rs => app/src/pool/pool_mode.rs} | 1 + .../src/pool/pool_mode}/mode_browse.rs | 0 .../src/pool/pool_mode}/mode_length.rs | 0 .../src/pool/pool_mode}/mode_rename.rs | 0 .../src/midi => app/src}/pool/pool_model.rs | 0 .../src/midi => app/src}/pool/pool_view.rs | 0 crates/device/Cargo.toml | 3 +- crates/device/src/lib.rs | 35 ++ crates/device/src/plugin.rs | 289 ++++++++++- crates/device/src/plugin/plugin.rs | 275 ----------- crates/device/src/sampler.rs | 11 +- .../midi/clip.rs => device/src/sequencer.rs} | 11 +- .../src/sequencer/seq_clip.rs} | 0 .../src/sequencer/seq_launch.rs} | 1 - crates/device/src/sequencer/seq_model.rs | 452 ++++++++++++++++++ .../src/sequencer/seq_view.rs} | 0 crates/engine/src/jack.rs | 27 +- crates/engine/src/jack/jack_client.rs | 2 +- crates/engine/src/jack/jack_event.rs | 1 + crates/engine/src/jack/jack_port.rs | 1 + crates/engine/src/lib.rs | 61 +++ crates/engine/src/midi.rs | 73 ++- crates/engine/src/midi/clip/clip_play.rs | 208 -------- crates/engine/src/midi/piano.rs | 2 - crates/engine/src/midi/piano/piano_v.rs | 34 -- crates/engine/src/midi/port.rs | 31 -- crates/engine/src/midi/port/port_in.rs | 107 ----- crates/engine/src/midi/port/port_out.rs | 164 ------- crates/engine/src/{midi => }/note.rs | 0 .../engine/src/{midi => }/note/note_pitch.rs | 0 .../engine/src/{midi => }/note/note_point.rs | 0 .../engine/src/{midi => }/note/note_range.rs | 0 crates/engine/src/time.rs | 49 -- crates/engine/src/time/time_perf.rs | 1 + 47 files changed, 1053 insertions(+), 1028 deletions(-) rename crates/{engine => app}/src/clock.rs (100%) rename crates/{engine => app}/src/clock/clock_api.rs (100%) rename crates/{engine => app}/src/clock/clock_model.rs (100%) create mode 100644 crates/app/src/editor.rs create mode 100644 crates/app/src/editor/editor_api.rs rename crates/{engine/src/midi/clip/clip_editor.rs => app/src/editor/editor_model.rs} (59%) rename crates/{engine/src/midi/piano/piano_h.rs => app/src/editor/editor_view.rs} (92%) rename crates/{engine/src/midi => app/src}/pool.rs (100%) rename crates/{engine/src/midi => app/src}/pool/pool_api.rs (100%) rename crates/{engine/src/midi => app/src}/pool/pool_clips.rs (100%) rename crates/{engine/src/midi/mode.rs => app/src/pool/pool_mode.rs} (99%) rename crates/{engine/src/midi/mode => app/src/pool/pool_mode}/mode_browse.rs (100%) rename crates/{engine/src/midi/mode => app/src/pool/pool_mode}/mode_length.rs (100%) rename crates/{engine/src/midi/mode => app/src/pool/pool_mode}/mode_rename.rs (100%) rename crates/{engine/src/midi => app/src}/pool/pool_model.rs (100%) rename crates/{engine/src/midi => app/src}/pool/pool_view.rs (100%) delete mode 100644 crates/device/src/plugin/plugin.rs rename crates/{engine/src/midi/clip.rs => device/src/sequencer.rs} (91%) rename crates/{engine/src/midi/clip/clip_model.rs => device/src/sequencer/seq_clip.rs} (100%) rename crates/{engine/src/midi/clip/clip_launch.rs => device/src/sequencer/seq_launch.rs} (99%) create mode 100644 crates/device/src/sequencer/seq_model.rs rename crates/{engine/src/midi/clip/clip_view.rs => device/src/sequencer/seq_view.rs} (100%) create mode 100644 crates/engine/src/lib.rs delete mode 100644 crates/engine/src/midi/clip/clip_play.rs delete mode 100644 crates/engine/src/midi/piano.rs delete mode 100644 crates/engine/src/midi/piano/piano_v.rs delete mode 100644 crates/engine/src/midi/port.rs delete mode 100644 crates/engine/src/midi/port/port_in.rs delete mode 100644 crates/engine/src/midi/port/port_out.rs rename crates/engine/src/{midi => }/note.rs (100%) rename crates/engine/src/{midi => }/note/note_pitch.rs (100%) rename crates/engine/src/{midi => }/note/note_point.rs (100%) rename crates/engine/src/{midi => }/note/note_range.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index a5d8ba38..a07b0cb5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1540,6 +1540,7 @@ dependencies = [ "symphonia", "tek_engine", "tengri", + "uuid", "wavers", ] diff --git a/crates/engine/src/clock.rs b/crates/app/src/clock.rs similarity index 100% rename from crates/engine/src/clock.rs rename to crates/app/src/clock.rs diff --git a/crates/engine/src/clock/clock_api.rs b/crates/app/src/clock/clock_api.rs similarity index 100% rename from crates/engine/src/clock/clock_api.rs rename to crates/app/src/clock/clock_api.rs diff --git a/crates/engine/src/clock/clock_model.rs b/crates/app/src/clock/clock_model.rs similarity index 100% rename from crates/engine/src/clock/clock_model.rs rename to crates/app/src/clock/clock_model.rs diff --git a/crates/app/src/editor.rs b/crates/app/src/editor.rs new file mode 100644 index 00000000..62c7b812 --- /dev/null +++ b/crates/app/src/editor.rs @@ -0,0 +1,3 @@ +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 new file mode 100644 index 00000000..1ac85118 --- /dev/null +++ b/crates/app/src/editor/editor_api.rs @@ -0,0 +1,97 @@ +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/engine/src/midi/clip/clip_editor.rs b/crates/app/src/editor/editor_model.rs similarity index 59% rename from crates/engine/src/midi/clip/clip_editor.rs rename to crates/app/src/editor/editor_model.rs index efdd92d6..befec6be 100644 --- a/crates/engine/src/midi/clip/clip_editor.rs +++ b/crates/app/src/editor/editor_model.rs @@ -51,39 +51,6 @@ 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 @@ -181,64 +148,3 @@ 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/engine/src/midi/piano/piano_h.rs b/crates/app/src/editor/editor_view.rs similarity index 92% rename from crates/engine/src/midi/piano/piano_h.rs rename to crates/app/src/editor/editor_view.rs index d4650cf4..67c9dc18 100644 --- a/crates/engine/src/midi/piano/piano_h.rs +++ b/crates/app/src/editor/editor_view.rs @@ -325,3 +325,39 @@ 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/app/src/lib.rs b/crates/app/src/lib.rs index 27b509c2..4ff7b835 100644 --- a/crates/app/src/lib.rs +++ b/crates/app/src/lib.rs @@ -41,6 +41,9 @@ 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 0ed65783..ab1e0fb9 100644 --- a/crates/app/src/model.rs +++ b/crates/app/src/model.rs @@ -787,11 +787,3 @@ 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/engine/src/midi/pool.rs b/crates/app/src/pool.rs similarity index 100% rename from crates/engine/src/midi/pool.rs rename to crates/app/src/pool.rs diff --git a/crates/engine/src/midi/pool/pool_api.rs b/crates/app/src/pool/pool_api.rs similarity index 100% rename from crates/engine/src/midi/pool/pool_api.rs rename to crates/app/src/pool/pool_api.rs diff --git a/crates/engine/src/midi/pool/pool_clips.rs b/crates/app/src/pool/pool_clips.rs similarity index 100% rename from crates/engine/src/midi/pool/pool_clips.rs rename to crates/app/src/pool/pool_clips.rs diff --git a/crates/engine/src/midi/mode.rs b/crates/app/src/pool/pool_mode.rs similarity index 99% rename from crates/engine/src/midi/mode.rs rename to crates/app/src/pool/pool_mode.rs index 6fd9c01c..0b3bd49f 100644 --- a/crates/engine/src/midi/mode.rs +++ b/crates/app/src/pool/pool_mode.rs @@ -16,3 +16,4 @@ pub enum PoolMode { /// Save clip to disk Export(usize, FileBrowser), } + diff --git a/crates/engine/src/midi/mode/mode_browse.rs b/crates/app/src/pool/pool_mode/mode_browse.rs similarity index 100% rename from crates/engine/src/midi/mode/mode_browse.rs rename to crates/app/src/pool/pool_mode/mode_browse.rs diff --git a/crates/engine/src/midi/mode/mode_length.rs b/crates/app/src/pool/pool_mode/mode_length.rs similarity index 100% rename from crates/engine/src/midi/mode/mode_length.rs rename to crates/app/src/pool/pool_mode/mode_length.rs diff --git a/crates/engine/src/midi/mode/mode_rename.rs b/crates/app/src/pool/pool_mode/mode_rename.rs similarity index 100% rename from crates/engine/src/midi/mode/mode_rename.rs rename to crates/app/src/pool/pool_mode/mode_rename.rs diff --git a/crates/engine/src/midi/pool/pool_model.rs b/crates/app/src/pool/pool_model.rs similarity index 100% rename from crates/engine/src/midi/pool/pool_model.rs rename to crates/app/src/pool/pool_model.rs diff --git a/crates/engine/src/midi/pool/pool_view.rs b/crates/app/src/pool/pool_view.rs similarity index 100% rename from crates/engine/src/midi/pool/pool_view.rs rename to crates/app/src/pool/pool_view.rs diff --git a/crates/device/Cargo.toml b/crates/device/Cargo.toml index 9f94bfbb..ce041180 100644 --- a/crates/device/Cargo.toml +++ b/crates/device/Cargo.toml @@ -6,6 +6,7 @@ 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 } @@ -14,4 +15,4 @@ wavers = { workspace = true, optional = true } default = [ "sequencer", "sampler" ] lv2 = [ "livi" ] sampler = [ "symphonia", "wavers" ] -sequencer = [] +sequencer = [ "uuid" ] diff --git a/crates/device/src/lib.rs b/crates/device/src/lib.rs index e69de29b..4bae13d6 100644 --- a/crates/device/src/lib.rs +++ b/crates/device/src/lib.rs @@ -0,0 +1,35 @@ +#![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/plugin.rs b/crates/device/src/plugin.rs index 40825854..f3f47d79 100644 --- a/crates/device/src/plugin.rs +++ b/crates/device/src/plugin.rs @@ -1,8 +1,281 @@ -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::*}}; +use crate::*; + +mod lv2; +mod lv2_gui; +mod lv2_tui; +mod vst2_tui; +mod vst3_tui; + +/// A plugin device. +#[derive(Debug)] +pub struct Plugin { + /// JACK client handle (needs to not be dropped for standalone mode to work). + pub jack: Jack, + pub name: Arc, + pub path: Option>, + pub plugin: Option, + pub selected: usize, + pub mapping: bool, + pub midi_ins: Vec>, + pub midi_outs: Vec>, + pub audio_ins: Vec>, + pub audio_outs: Vec>, +} + +/// Supported plugin formats. +#[derive(Default)] +pub enum PluginKind { + #[default] None, + LV2(LV2Plugin), + VST2 { instance: () /*::vst::host::PluginInstance*/ }, + VST3, +} + +impl Debug for PluginKind { + fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + write!(f, "{}", match self { + Self::None => "(none)", + Self::LV2(_) => "LV2", + Self::VST2{..} => "VST2", + Self::VST3 => "VST3", + }) + } +} +impl Plugin { + pub fn new_lv2 ( + jack: &Jack, + name: &str, + path: &str, + ) -> Usually { + Ok(Self { + jack: jack.clone(), + name: name.into(), + path: Some(String::from(path).into()), + plugin: Some(PluginKind::LV2(LV2Plugin::new(path)?)), + selected: 0, + mapping: false, + midi_ins: vec![], + midi_outs: vec![], + audio_ins: vec![], + audio_outs: vec![], + }) + } +} + +pub struct PluginAudio(Arc>); +from!(|model: &Arc>| PluginAudio = Self(model.clone())); +audio!(|self: PluginAudio, _client, scope|{ + let state = &mut*self.0.write().unwrap(); + match state.plugin.as_mut() { + Some(PluginKind::LV2(LV2Plugin { + features, + ref mut instance, + ref mut input_buffer, + .. + })) => { + let urid = features.midi_urid(); + input_buffer.clear(); + for port in state.midi_ins.iter() { + let mut atom = ::livi::event::LV2AtomSequence::new( + &features, + scope.n_frames() as usize + ); + for event in port.iter(scope) { + match event.bytes.len() { + 3 => atom.push_midi_event::<3>( + event.time as i64, + urid, + &event.bytes[0..3] + ).unwrap(), + _ => {} + } + } + input_buffer.push(atom); + } + let mut outputs = vec![]; + for _ in state.midi_outs.iter() { + outputs.push(::livi::event::LV2AtomSequence::new( + features, + scope.n_frames() as usize + )); + } + let ports = ::livi::EmptyPortConnections::new() + .with_atom_sequence_inputs(input_buffer.iter()) + .with_atom_sequence_outputs(outputs.iter_mut()) + .with_audio_inputs(state.audio_ins.iter().map(|o|o.as_slice(scope))) + .with_audio_outputs(state.audio_outs.iter_mut().map(|o|o.as_mut_slice(scope))); + unsafe { + instance.run(scope.n_frames() as usize, ports).unwrap() + }; + }, + _ => todo!("only lv2 is supported") + } + Control::Continue +}); + + //fn jack_from_lv2 (name: &str, plugin: &::livi::Plugin) -> Usually { + //let counts = plugin.port_counts(); + //let mut jack = Jack::new(name)?; + //for i in 0..counts.atom_sequence_inputs { + //jack = jack.midi_in(&format!("midi-in-{i}")) + //} + //for i in 0..counts.atom_sequence_outputs { + //jack = jack.midi_out(&format!("midi-out-{i}")); + //} + //for i in 0..counts.audio_inputs { + //jack = jack.audio_in(&format!("audio-in-{i}")); + //} + //for i in 0..counts.audio_outputs { + //jack = jack.audio_out(&format!("audio-out-{i}")); + //} + //Ok(jack) + //} + +impl Plugin { + /// Create a plugin host device. + pub fn new ( + jack: &Jack, + name: &str, + ) -> Usually { + Ok(Self { + //_engine: Default::default(), + jack: jack.clone(), + name: name.into(), + path: None, + plugin: None, + selected: 0, + mapping: false, + audio_ins: vec![], + audio_outs: vec![], + midi_ins: vec![], + midi_outs: vec![], + //ports: JackPorts::default() + }) + } +} +impl Content for Plugin { + fn render (&self, to: &mut TuiOut) { + let area = to.area(); + let [x, y, _, height] = area; + let mut width = 20u16; + match &self.plugin { + Some(PluginKind::LV2(LV2Plugin { port_list, instance, .. })) => { + let start = self.selected.saturating_sub((height as usize / 2).saturating_sub(1)); + let end = start + height as usize - 2; + //draw_box(buf, Rect { x, y, width, height }); + for i in start..end { + if let Some(port) = port_list.get(i) { + let value = if let Some(value) = instance.control_input(port.index) { + value + } else { + port.default_value + }; + //let label = &format!("C·· M·· {:25} = {value:.03}", port.name); + let label = &format!("{:25} = {value:.03}", port.name); + width = width.max(label.len() as u16 + 4); + let style = if i == self.selected { + Some(Style::default().green()) + } else { + None + } ; + to.blit(&label, x + 2, y + 1 + i as u16 - start as u16, style); + } else { + break + } + } + }, + _ => {} + }; + draw_header(self, to, x, y, width); + } +} + +fn draw_header (state: &Plugin, to: &mut TuiOut, x: u16, y: u16, w: u16) { + let style = Style::default().gray(); + let label1 = format!(" {}", state.name); + to.blit(&label1, x + 1, y, Some(style.white().bold())); + if let Some(ref path) = state.path { + let label2 = format!("{}…", &path[..((w as usize - 10).min(path.len()))]); + to.blit(&label2, x + 2 + label1.len() as u16, y, Some(style.not_dim())); + } + //Ok(Rect { x, y, width: w, height: 1 }) +} + +//handle!(TuiIn: |self:Plugin, from|{ + //match from.event() { + //kpat!(KeyCode::Up) => { + //self.selected = self.selected.saturating_sub(1); + //Ok(Some(true)) + //}, + //kpat!(KeyCode::Down) => { + //self.selected = (self.selected + 1).min(match &self.plugin { + //Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1, + //_ => unimplemented!() + //}); + //Ok(Some(true)) + //}, + //kpat!(KeyCode::PageUp) => { + //self.selected = self.selected.saturating_sub(8); + //Ok(Some(true)) + //}, + //kpat!(KeyCode::PageDown) => { + //self.selected = (self.selected + 10).min(match &self.plugin { + //Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1, + //_ => unimplemented!() + //}); + //Ok(Some(true)) + //}, + //kpat!(KeyCode::Char(',')) => { + //match self.plugin.as_mut() { + //Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => { + //let index = port_list[self.selected].index; + //if let Some(value) = instance.control_input(index) { + //instance.set_control_input(index, value - 0.01); + //} + //}, + //_ => {} + //} + //Ok(Some(true)) + //}, + //kpat!(KeyCode::Char('.')) => { + //match self.plugin.as_mut() { + //Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => { + //let index = port_list[self.selected].index; + //if let Some(value) = instance.control_input(index) { + //instance.set_control_input(index, value + 0.01); + //} + //}, + //_ => {} + //} + //Ok(Some(true)) + //}, + //kpat!(KeyCode::Char('g')) => { + //match self.plugin { + ////Some(PluginKind::LV2(ref mut plugin)) => { + ////plugin.ui_thread = Some(run_lv2_ui(LV2PluginUI::new()?)?); + ////}, + //Some(_) => unreachable!(), + //None => {} + //} + //Ok(Some(true)) + //}, + //_ => Ok(None) + //} +//}); + +//from_atom!("plugin/lv2" => |jack: &Jack, args| -> Plugin { + //let mut name = String::new(); + //let mut path = String::new(); + //atom!(atom in args { + //Atom::Map(map) => { + //if let Some(Atom::Str(n)) = map.get(&Atom::Key(":name")) { + //name = String::from(*n); + //} + //if let Some(Atom::Str(p)) = map.get(&Atom::Key(":path")) { + //path = String::from(*p); + //} + //}, + //_ => panic!("unexpected in lv2 '{name}'"), + //}); + //Plugin::new_lv2(jack, &name, &path) +//}); diff --git a/crates/device/src/plugin/plugin.rs b/crates/device/src/plugin/plugin.rs deleted file mode 100644 index ddd58e65..00000000 --- a/crates/device/src/plugin/plugin.rs +++ /dev/null @@ -1,275 +0,0 @@ -use crate::*; - -/// A plugin device. -#[derive(Debug)] -pub struct Plugin { - /// JACK client handle (needs to not be dropped for standalone mode to work). - pub jack: Jack, - pub name: Arc, - pub path: Option>, - pub plugin: Option, - pub selected: usize, - pub mapping: bool, - pub midi_ins: Vec>, - pub midi_outs: Vec>, - pub audio_ins: Vec>, - pub audio_outs: Vec>, -} - -/// Supported plugin formats. -#[derive(Default)] -pub enum PluginKind { - #[default] None, - LV2(LV2Plugin), - VST2 { instance: () /*::vst::host::PluginInstance*/ }, - VST3, -} - -impl Debug for PluginKind { - fn fmt (&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - write!(f, "{}", match self { - Self::None => "(none)", - Self::LV2(_) => "LV2", - Self::VST2{..} => "VST2", - Self::VST3 => "VST3", - }) - } -} -impl Plugin { - pub fn new_lv2 ( - jack: &Jack, - name: &str, - path: &str, - ) -> Usually { - Ok(Self { - jack: jack.clone(), - name: name.into(), - path: Some(String::from(path).into()), - plugin: Some(PluginKind::LV2(LV2Plugin::new(path)?)), - selected: 0, - mapping: false, - midi_ins: vec![], - midi_outs: vec![], - audio_ins: vec![], - audio_outs: vec![], - }) - } -} - -pub struct PluginAudio(Arc>); -from!(|model: &Arc>| PluginAudio = Self(model.clone())); -audio!(|self: PluginAudio, _client, scope|{ - let state = &mut*self.0.write().unwrap(); - match state.plugin.as_mut() { - Some(PluginKind::LV2(LV2Plugin { - features, - ref mut instance, - ref mut input_buffer, - .. - })) => { - let urid = features.midi_urid(); - input_buffer.clear(); - for port in state.midi_ins.iter() { - let mut atom = ::livi::event::LV2AtomSequence::new( - &features, - scope.n_frames() as usize - ); - for event in port.iter(scope) { - match event.bytes.len() { - 3 => atom.push_midi_event::<3>( - event.time as i64, - urid, - &event.bytes[0..3] - ).unwrap(), - _ => {} - } - } - input_buffer.push(atom); - } - let mut outputs = vec![]; - for _ in state.midi_outs.iter() { - outputs.push(::livi::event::LV2AtomSequence::new( - features, - scope.n_frames() as usize - )); - } - let ports = ::livi::EmptyPortConnections::new() - .with_atom_sequence_inputs(input_buffer.iter()) - .with_atom_sequence_outputs(outputs.iter_mut()) - .with_audio_inputs(state.audio_ins.iter().map(|o|o.as_slice(scope))) - .with_audio_outputs(state.audio_outs.iter_mut().map(|o|o.as_mut_slice(scope))); - unsafe { - instance.run(scope.n_frames() as usize, ports).unwrap() - }; - }, - _ => todo!("only lv2 is supported") - } - Control::Continue -}); - - //fn jack_from_lv2 (name: &str, plugin: &::livi::Plugin) -> Usually { - //let counts = plugin.port_counts(); - //let mut jack = Jack::new(name)?; - //for i in 0..counts.atom_sequence_inputs { - //jack = jack.midi_in(&format!("midi-in-{i}")) - //} - //for i in 0..counts.atom_sequence_outputs { - //jack = jack.midi_out(&format!("midi-out-{i}")); - //} - //for i in 0..counts.audio_inputs { - //jack = jack.audio_in(&format!("audio-in-{i}")); - //} - //for i in 0..counts.audio_outputs { - //jack = jack.audio_out(&format!("audio-out-{i}")); - //} - //Ok(jack) - //} - -impl Plugin { - /// Create a plugin host device. - pub fn new ( - jack: &Jack, - name: &str, - ) -> Usually { - Ok(Self { - //_engine: Default::default(), - jack: jack.clone(), - name: name.into(), - path: None, - plugin: None, - selected: 0, - mapping: false, - audio_ins: vec![], - audio_outs: vec![], - midi_ins: vec![], - midi_outs: vec![], - //ports: JackPorts::default() - }) - } -} -impl Content for Plugin { - fn render (&self, to: &mut TuiOut) { - let area = to.area(); - let [x, y, _, height] = area; - let mut width = 20u16; - match &self.plugin { - Some(PluginKind::LV2(LV2Plugin { port_list, instance, .. })) => { - let start = self.selected.saturating_sub((height as usize / 2).saturating_sub(1)); - let end = start + height as usize - 2; - //draw_box(buf, Rect { x, y, width, height }); - for i in start..end { - if let Some(port) = port_list.get(i) { - let value = if let Some(value) = instance.control_input(port.index) { - value - } else { - port.default_value - }; - //let label = &format!("C·· M·· {:25} = {value:.03}", port.name); - let label = &format!("{:25} = {value:.03}", port.name); - width = width.max(label.len() as u16 + 4); - let style = if i == self.selected { - Some(Style::default().green()) - } else { - None - } ; - to.blit(&label, x + 2, y + 1 + i as u16 - start as u16, style); - } else { - break - } - } - }, - _ => {} - }; - draw_header(self, to, x, y, width); - } -} - -fn draw_header (state: &Plugin, to: &mut TuiOut, x: u16, y: u16, w: u16) { - let style = Style::default().gray(); - let label1 = format!(" {}", state.name); - to.blit(&label1, x + 1, y, Some(style.white().bold())); - if let Some(ref path) = state.path { - let label2 = format!("{}…", &path[..((w as usize - 10).min(path.len()))]); - to.blit(&label2, x + 2 + label1.len() as u16, y, Some(style.not_dim())); - } - //Ok(Rect { x, y, width: w, height: 1 }) -} - -//handle!(TuiIn: |self:Plugin, from|{ - //match from.event() { - //kpat!(KeyCode::Up) => { - //self.selected = self.selected.saturating_sub(1); - //Ok(Some(true)) - //}, - //kpat!(KeyCode::Down) => { - //self.selected = (self.selected + 1).min(match &self.plugin { - //Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1, - //_ => unimplemented!() - //}); - //Ok(Some(true)) - //}, - //kpat!(KeyCode::PageUp) => { - //self.selected = self.selected.saturating_sub(8); - //Ok(Some(true)) - //}, - //kpat!(KeyCode::PageDown) => { - //self.selected = (self.selected + 10).min(match &self.plugin { - //Some(PluginKind::LV2(LV2Plugin { port_list, .. })) => port_list.len() - 1, - //_ => unimplemented!() - //}); - //Ok(Some(true)) - //}, - //kpat!(KeyCode::Char(',')) => { - //match self.plugin.as_mut() { - //Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => { - //let index = port_list[self.selected].index; - //if let Some(value) = instance.control_input(index) { - //instance.set_control_input(index, value - 0.01); - //} - //}, - //_ => {} - //} - //Ok(Some(true)) - //}, - //kpat!(KeyCode::Char('.')) => { - //match self.plugin.as_mut() { - //Some(PluginKind::LV2(LV2Plugin { port_list, ref mut instance, .. })) => { - //let index = port_list[self.selected].index; - //if let Some(value) = instance.control_input(index) { - //instance.set_control_input(index, value + 0.01); - //} - //}, - //_ => {} - //} - //Ok(Some(true)) - //}, - //kpat!(KeyCode::Char('g')) => { - //match self.plugin { - ////Some(PluginKind::LV2(ref mut plugin)) => { - ////plugin.ui_thread = Some(run_lv2_ui(LV2PluginUI::new()?)?); - ////}, - //Some(_) => unreachable!(), - //None => {} - //} - //Ok(Some(true)) - //}, - //_ => Ok(None) - //} -//}); - -//from_atom!("plugin/lv2" => |jack: &Jack, args| -> Plugin { - //let mut name = String::new(); - //let mut path = String::new(); - //atom!(atom in args { - //Atom::Map(map) => { - //if let Some(Atom::Str(n)) = map.get(&Atom::Key(":name")) { - //name = String::from(*n); - //} - //if let Some(Atom::Str(p)) = map.get(&Atom::Key(":path")) { - //path = String::from(*p); - //} - //}, - //_ => panic!("unexpected in lv2 '{name}'"), - //}); - //Plugin::new_lv2(jack, &name, &path) -//}); diff --git a/crates/device/src/sampler.rs b/crates/device/src/sampler.rs index 8ddfc4b6..6423684b 100644 --- a/crates/device/src/sampler.rs +++ b/crates/device/src/sampler.rs @@ -1,13 +1,5 @@ -#![feature(let_chains)] +use crate::*; -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, @@ -19,7 +11,6 @@ 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/engine/src/midi/clip.rs b/crates/device/src/sequencer.rs similarity index 91% rename from crates/engine/src/midi/clip.rs rename to crates/device/src/sequencer.rs index 95295621..eeec08e9 100644 --- a/crates/engine/src/midi/clip.rs +++ b/crates/device/src/sequencer.rs @@ -1,8 +1,9 @@ -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::*; +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::*; pub trait HasEditor { fn editor (&self) -> &Option; diff --git a/crates/engine/src/midi/clip/clip_model.rs b/crates/device/src/sequencer/seq_clip.rs similarity index 100% rename from crates/engine/src/midi/clip/clip_model.rs rename to crates/device/src/sequencer/seq_clip.rs diff --git a/crates/engine/src/midi/clip/clip_launch.rs b/crates/device/src/sequencer/seq_launch.rs similarity index 99% rename from crates/engine/src/midi/clip/clip_launch.rs rename to crates/device/src/sequencer/seq_launch.rs index ee2f682e..48d30f1c 100644 --- a/crates/engine/src/midi/clip/clip_launch.rs +++ b/crates/device/src/sequencer/seq_launch.rs @@ -87,4 +87,3 @@ pub trait HasPlayClip: HasClock { FieldV(color, "Next:", format!("{} {}", time, name)) } } - diff --git a/crates/device/src/sequencer/seq_model.rs b/crates/device/src/sequencer/seq_model.rs new file mode 100644 index 00000000..004db566 --- /dev/null +++ b/crates/device/src/sequencer/seq_model.rs @@ -0,0 +1,452 @@ +//! 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/src/midi/clip/clip_view.rs b/crates/device/src/sequencer/seq_view.rs similarity index 100% rename from crates/engine/src/midi/clip/clip_view.rs rename to crates/device/src/sequencer/seq_view.rs diff --git a/crates/engine/src/jack.rs b/crates/engine/src/jack.rs index 149ce0d2..390d4cb7 100644 --- a/crates/engine/src/jack.rs +++ b/crates/engine/src/jack.rs @@ -1,17 +1,18 @@ -#![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::*; +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}; -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>; + +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/jack/jack_client.rs b/crates/engine/src/jack/jack_client.rs index ad89c7ae..5adb9a3e 100644 --- a/crates/engine/src/jack/jack_client.rs +++ b/crates/engine/src/jack/jack_client.rs @@ -1,5 +1,5 @@ use crate::*; -use ::jack::contrib::*; +use super::*; use self::JackState::*; /// Things that can provide a [jack::Client] reference. diff --git a/crates/engine/src/jack/jack_event.rs b/crates/engine/src/jack/jack_event.rs index 43571f69..9ba6ad0f 100644 --- a/crates/engine/src/jack/jack_event.rs +++ b/crates/engine/src/jack/jack_event.rs @@ -1,4 +1,5 @@ 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/engine/src/jack/jack_port.rs index 5c34b748..7347269d 100644 --- a/crates/engine/src/jack/jack_port.rs +++ b/crates/engine/src/jack/jack_port.rs @@ -1,4 +1,5 @@ use crate::*; +use super::*; macro_rules! impl_port { ($Name:ident : $Spec:ident -> $Pair:ident |$jack:ident, $name:ident|$port:expr) => { diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs new file mode 100644 index 00000000..a052ac7b --- /dev/null +++ b/crates/engine/src/lib.rs @@ -0,0 +1,61 @@ +#![feature(type_alias_impl_trait)] + +mod jack; pub use self::jack::*; +mod time; pub use self::time::*; +mod note; pub use self::note::*; +mod midi; pub use self::midi::*; + +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 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 use ::atomic_float; pub(crate) use atomic_float::*; + +/// Standard result type. +pub(crate) type Usually = std::result::Result>; + +/// Standard optional result type. +pub(crate) type Perhaps = std::result::Result, Box>; + +pub trait Gettable { + /// Returns current value + fn get (&self) -> T; +} + +pub trait Mutable: Gettable { + /// Sets new value, returns old + fn set (&mut self, value: T) -> T; +} + +pub trait InteriorMutable: Gettable { + /// Sets new value, returns old + fn set (&self, value: T) -> T; +} + +impl Gettable for AtomicBool { + fn get (&self) -> bool { self.load(Relaxed) } +} + +impl InteriorMutable for AtomicBool { + fn set (&self, value: bool) -> bool { self.swap(value, Relaxed) } +} + +impl Gettable for AtomicUsize { + fn get (&self) -> usize { self.load(Relaxed) } +} + +impl InteriorMutable for AtomicUsize { + fn set (&self, value: usize) -> usize { self.swap(value, Relaxed) } +} + +#[cfg(test)] #[test] fn test_time () -> Usually<()> { + // TODO! + Ok(()) +} diff --git a/crates/engine/src/midi.rs b/crates/engine/src/midi.rs index a617b9b3..fa9fa4f2 100644 --- a/crates/engine/src/midi.rs +++ b/crates/engine/src/midi.rs @@ -1,21 +1,60 @@ -pub(crate) use std::sync::{Arc, RwLock, atomic::{AtomicUsize, AtomicBool, Ordering::Relaxed}}; -pub(crate) use std::path::PathBuf; -pub(crate) use std::fmt::Debug; +use crate::*; pub use ::midly; -pub(crate) use ::midly::{*, num::*, live::*}; +pub(crate) use ::midly::{ + MidiMessage, + 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}; +/// 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; }, + _ => {} + } +} -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::*; +/// 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/midi/clip/clip_play.rs b/crates/engine/src/midi/clip/clip_play.rs deleted file mode 100644 index b7df1fe9..00000000 --- a/crates/engine/src/midi/clip/clip_play.rs +++ /dev/null @@ -1,208 +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 - } -} diff --git a/crates/engine/src/midi/piano.rs b/crates/engine/src/midi/piano.rs deleted file mode 100644 index ea7b9152..00000000 --- a/crates/engine/src/midi/piano.rs +++ /dev/null @@ -1,2 +0,0 @@ -mod piano_h; pub use self::piano_h::*; -mod piano_v; pub use self::piano_v::*; diff --git a/crates/engine/src/midi/piano/piano_v.rs b/crates/engine/src/midi/piano/piano_v.rs deleted file mode 100644 index 27bd1d5e..00000000 --- a/crates/engine/src/midi/piano/piano_v.rs +++ /dev/null @@ -1,34 +0,0 @@ -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/engine/src/midi/port.rs b/crates/engine/src/midi/port.rs deleted file mode 100644 index 25977123..00000000 --- a/crates/engine/src/midi/port.rs +++ /dev/null @@ -1,31 +0,0 @@ -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/engine/src/midi/port/port_in.rs b/crates/engine/src/midi/port/port_in.rs deleted file mode 100644 index abbd1390..00000000 --- a/crates/engine/src/midi/port/port_in.rs +++ /dev/null @@ -1,107 +0,0 @@ -use crate::*; - -/// Trait for thing that may receive MIDI. -pub trait HasMidiIns { - fn midi_ins (&self) -> &Vec; - - fn midi_ins_mut (&mut self) -> &mut Vec; - - fn has_midi_ins (&self) -> bool { - !self.midi_ins().is_empty() - } -} - -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/engine/src/midi/port/port_out.rs b/crates/engine/src/midi/port/port_out.rs deleted file mode 100644 index 159d11c6..00000000 --- a/crates/engine/src/midi/port/port_out.rs +++ /dev/null @@ -1,164 +0,0 @@ -use crate::*; - -/// Trait for thing that may output MIDI. -pub trait HasMidiOuts { - fn midi_outs (&self) -> &Vec; - - fn midi_outs_mut (&mut self) -> &mut Vec; - - fn has_midi_outs (&self) -> bool { - !self.midi_outs().is_empty() - } - - /// Buffer for serializing a MIDI event. FIXME rename - fn midi_note (&mut self) -> &mut Vec; -} - -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/src/midi/note.rs b/crates/engine/src/note.rs similarity index 100% rename from crates/engine/src/midi/note.rs rename to crates/engine/src/note.rs diff --git a/crates/engine/src/midi/note/note_pitch.rs b/crates/engine/src/note/note_pitch.rs similarity index 100% rename from crates/engine/src/midi/note/note_pitch.rs rename to crates/engine/src/note/note_pitch.rs diff --git a/crates/engine/src/midi/note/note_point.rs b/crates/engine/src/note/note_point.rs similarity index 100% rename from crates/engine/src/midi/note/note_point.rs rename to crates/engine/src/note/note_point.rs diff --git a/crates/engine/src/midi/note/note_range.rs b/crates/engine/src/note/note_range.rs similarity index 100% rename from crates/engine/src/midi/note/note_range.rs rename to crates/engine/src/note/note_range.rs diff --git a/crates/engine/src/time.rs b/crates/engine/src/time.rs index 2ad81860..9d4e7459 100644 --- a/crates/engine/src/time.rs +++ b/crates/engine/src/time.rs @@ -1,5 +1,3 @@ -mod clock; pub use self::clock::*; - mod time_moment; pub use self::time_moment::*; mod time_note; pub use self::time_note::*; mod time_perf; pub use self::time_perf::*; @@ -9,50 +7,3 @@ 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 ::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::*, dsl::*}; -pub use ::atomic_float; pub(crate) use atomic_float::*; - -/// Standard result type. -pub(crate) type Usually = Result>; -/// Standard optional result type. -pub(crate) type Perhaps = Result, Box>; - -pub trait Gettable { - /// Returns current value - fn get (&self) -> T; -} - -pub trait Mutable: Gettable { - /// Sets new value, returns old - fn set (&mut self, value: T) -> T; -} - -pub trait InteriorMutable: Gettable { - /// Sets new value, returns old - fn set (&self, value: T) -> T; -} - -impl Gettable for AtomicBool { - fn get (&self) -> bool { self.load(Relaxed) } -} - -impl InteriorMutable for AtomicBool { - fn set (&self, value: bool) -> bool { self.swap(value, Relaxed) } -} - -impl Gettable for AtomicUsize { - fn get (&self) -> usize { self.load(Relaxed) } -} - -impl InteriorMutable for AtomicUsize { - fn set (&self, value: usize) -> usize { self.swap(value, Relaxed) } -} - -#[cfg(test)] #[test] fn test_time () -> Usually<()> { - // TODO! - Ok(()) -} diff --git a/crates/engine/src/time/time_perf.rs b/crates/engine/src/time/time_perf.rs index c435fdff..f14bcc66 100644 --- a/crates/engine/src/time/time_perf.rs +++ b/crates/engine/src/time/time_perf.rs @@ -1,5 +1,6 @@ use crate::*; use tengri::tui::PerfModel; +use ::jack::ProcessScope; pub trait JackPerfModel { fn update_from_jack_scope (&self, t0: Option, scope: &ProcessScope);