diff --git a/Cargo.lock b/Cargo.lock index c12f9b57..253d77e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2315,6 +2315,7 @@ dependencies = [ "jack", "midly", "tengri", + "tengri_proc", "uuid", ] diff --git a/crates/app/src/model.rs b/crates/app/src/model.rs index e7411bea..18c6d61f 100644 --- a/crates/app/src/model.rs +++ b/crates/app/src/model.rs @@ -21,12 +21,12 @@ pub struct App { pub dialog: Option, /// Browses external resources, such as directories pub browser: Option, - /// Contains the currently edited musical arrangement - pub arranger: Option, /// Contains all clips in the project pub pool: Option, /// Contains the currently edited MIDI clip pub editor: Option, + /// Contains the currently edited musical arrangement + pub arranger: Arrangement, // Cache of formatted strings pub view_cache: Arc>, @@ -34,6 +34,12 @@ pub struct App { pub color: ItemTheme, } +has!(Option: |self: App|self.arrangement.selected); +has!(Vec: |self: App|self.arrangement.midi_ins); +has!(Vec: |self: App|self.arrangement.midi_outs); +has!(Vec: |self: App|self.arrangement.scenes); +has!(Vec: |self: App|self.arrangement.tracks); + impl App { pub fn toggle_dialog (&mut self, dialog: Option) { self.dialog = if self.dialog == dialog { @@ -86,16 +92,10 @@ pub enum Message { FailedToAddDevice, } -content!(TuiOut: |self: Message| match self { - Self::FailedToAddDevice => "Failed to add device." -}); - +content!(TuiOut: |self: Message| match self { Self::FailedToAddDevice => "Failed to add device." }); has_size!(|self: App|&self.size); - -has_clock!(|self: App|self.clock); - +has!(Clock: |self: App|self.clock); has_clips!(|self: App|self.pool.as_ref().expect("no clip pool").clips); - has_editor!(|self: App|{ editor = self.editor; editor_w = { @@ -109,30 +109,6 @@ has_editor!(|self: App|{ is_editing = self.editing.load(Relaxed); }); -impl HasTracks for App { - fn midi_ins (&self) -> &Vec { - &self.arranger.midi_ins - } - fn midi_outs (&self) -> &Vec { - &self.arranger.midi_outs - } - fn tracks (&self) -> &Vec { - &self.arranger.tracks - } - fn tracks_mut (&mut self) -> &mut Vec { - &mut self.arranger.tracks - } -} - -impl HasScenes for Arrangement { - fn scenes (&self) -> &Vec { - &self.arranger.scenes - } - fn scenes_mut (&mut self) -> &mut Vec { - &mut self.arranger.scenes - } -}if - #[tengri_proc::expose] impl App { fn _todo_isize_stub (&self) -> isize { @@ -258,4 +234,27 @@ impl App { 0 } } + fn device_add_lv2 (&mut self) -> Usually<()> { + todo!(); + Ok(()) + } + fn device_add_sampler (&mut self, jack: &Jack) -> Usually { + let name = jack.with_client(|c|c.name().to_string()); + let midi = self.track().expect("no active track").sequencer.midi_outs[0].name(); + let sampler = if let Ok(sampler) = Sampler::new( + jack, + &format!("{}/Sampler", Has::::get(self).name), + &[PortConnect::exact(format!("{name}:{midi}"))], + &[&[], &[]], + &[&[], &[]] + ) { + self.dialog = None; + Device::Sampler(sampler) + } else { + self.dialog = Some(Dialog::Message(Message::FailedToAddDevice)); + return Err("failed to add device".into()) + }; + Has::::get_mut(self).expect("no active track").devices.push(sampler); + Ok(()) + } } diff --git a/crates/device/src/arranger.rs b/crates/device/src/arranger.rs index 396aebe7..2939b23e 100644 --- a/crates/device/src/arranger.rs +++ b/crates/device/src/arranger.rs @@ -10,12 +10,13 @@ macro_rules! def_sizes_iter { mod arranger_api; pub use self::arranger_api::*; mod arranger_clip; pub use self::arranger_clip::*; -mod arranger_device; pub use self::arranger_device::*; -mod arranger_port; pub use self::arranger_port::*; mod arranger_model; pub use self::arranger_model::*; +mod arranger_port; pub use self::arranger_port::*; mod arranger_scene; pub use self::arranger_scene::*; +mod arranger_scenes; pub use self::arranger_scenes::*; mod arranger_select; pub use self::arranger_select::*; mod arranger_track; pub use self::arranger_track::*; +mod arranger_tracks; pub use self::arranger_tracks::*; mod arranger_view; pub use self::arranger_view::*; def_sizes_iter!(ScenesSizes => Scene); @@ -74,13 +75,3 @@ impl HasWidth for Track { } } } - -pub trait Has: Send + Sync { - fn get (&self) -> &T; - fn get_mut (&mut self) -> &mut T; -} - -pub trait HasMany: Send + Sync { - fn get (&self, key: U) -> &T; - fn get_mut (&mut self, key: U) -> &mut T; -} diff --git a/crates/device/src/arranger/arranger_api.rs b/crates/device/src/arranger/arranger_api.rs index d13d3885..bd6d4721 100644 --- a/crates/device/src/arranger/arranger_api.rs +++ b/crates/device/src/arranger/arranger_api.rs @@ -1,7 +1,83 @@ use crate::*; +#[tengri_proc::expose] +impl Arrangement { + fn _todo_usize_stub_ (&self) -> usize { todo!() } + fn _todo_arc_str_stub_ (&self) -> Arc { todo!() } + fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() } + fn _todo_opt_item_theme_stub (&self) -> Option { todo!() } +} + #[tengri_proc::command(Arrangement)] impl ArrangementCommand { + /// Set the selection + fn select (arranger: &mut Arrangement, s: Option) -> Perhaps { + arranger.selected = s; + // autoedit: load focused clip in editor. + if let Some(ref mut editor) = arranger.editor { + editor.set_clip(match arranger.selected { + Some(Selection::TrackClip { track, scene }) + if let Some(Some(Some(clip))) = arranger + .scenes.get(scene) + .map(|s|s.clips.get(track)) => Some(clip), + _ => None + }); + } + Ok(None) + } + /// Launch a clip or scene + fn launch (arranger: &mut Arrangement) -> Perhaps { + use Selection::*; + match arranger.selected { + Some(Track(t)) => { + arranger.tracks[t].sequencer.enqueue_next(None) + }, + Some(TrackClip { track, scene }) => { + arranger.tracks[track].sequencer.enqueue_next(arranger.scenes[scene].clips[track].as_ref()) + }, + Some(Scene(s)) => { + for t in 0..arranger.tracks.len() { + arranger.tracks[t].sequencer.enqueue_next(arranger.scenes[s].clips[t].as_ref()) + } + }, + _ => {} + }; + Ok(None) + } + /// Set the color of the selected entity + fn set_color (arranger: &mut Arrangement, palette: Option) -> Option { + use Selection::*; + let palette = palette.unwrap_or_else(||ItemTheme::random()); + Some(match arranger.selected { + Some(Mix) => { + let old = arranger.color; + arranger.color = palette; + old + }, + Some(Scene(s)) => { + let old = arranger.scenes[s].color; + arranger.scenes[s].color = palette; + old + } + Some(Track(t)) => { + let old = arranger.tracks[t].color; + arranger.tracks[t].color = palette; + old + } + Some(TrackClip { track, scene }) => { + if let Some(ref clip) = arranger.scenes[scene].clips[track] { + let mut clip = clip.write().unwrap(); + let old = clip.color; + clip.color = palette; + old + } else { + return None + } + }, + _ => todo!() + }) + } + fn track (arranger: &mut Arrangement, track: TrackCommand) -> Perhaps { todo!("delegate") } @@ -9,9 +85,9 @@ impl ArrangementCommand { let index = arranger.track_add(None, None, &[], &[])?.0; arranger.selected = match arranger.selected { Some(Selection::Track(_)) => - Selection::Track(index), + Some(Selection::Track(index)), Some(Selection::TrackClip { track, scene }) => - Selection::TrackClip { track: index, scene }, + Some(Selection::TrackClip { track: index, scene }), _ => arranger.selected }; Ok(Some(Self::TrackDelete { index })) @@ -38,14 +114,14 @@ impl ArrangementCommand { Ok(None) //TODO:Ok(Some(Self::TrackAdd ( index, track: Some(deleted_track) }) } - fn midi_in (arranger: &mut Arrangement, input: InputCommand) -> Perhaps { + fn midi_in (arranger: &mut Arrangement, input: MidiInputCommand) -> Perhaps { todo!("delegate") } fn midi_in_add (arranger: &mut Arrangement) -> Perhaps { arranger.midi_in_add()?; Ok(None) } - fn midi_out (arranger: &mut Arrangement, input: OutputCommand) -> Perhaps { + fn midi_out (arranger: &mut Arrangement, input: MidiOutputCommand) -> Perhaps { todo!("delegate") } fn midi_out_add (arranger: &mut Arrangement) -> Perhaps { @@ -65,10 +141,10 @@ impl ArrangementCommand { fn scene_add (arranger: &mut Arrangement) -> Perhaps { let index = arranger.scene_add(None, None)?.0; arranger.selected = match arranger.selected { - Selection::Scene(_) => - Selection::Scene(index), - Selection::TrackClip { track, scene } => - Selection::TrackClip { track, scene: index }, + Some(Selection::Scene(_)) => + Some(Selection::Scene(index)), + Some(Selection::TrackClip { track, scene }) => + Some(Selection::TrackClip { track, scene: index }), _ => arranger.selected }; Ok(None) // TODO @@ -80,7 +156,8 @@ impl ArrangementCommand { fn scene_delete (arranger: &mut Arrangement, index: usize) -> Perhaps { let scenes = arranger.scenes_mut(); Ok(if scenes.get(index).is_some() { - Some(scenes.remove(index)) + let _scene = scenes.remove(index); + None } else { None }) @@ -90,6 +167,7 @@ impl ArrangementCommand { let clip = arranger.scenes[index].clips[track].as_ref(); arranger.tracks[track].sequencer.enqueue_next(clip); } + Ok(None) } fn clip (arranger: &mut Arrangement, scene: ClipCommand) -> Perhaps { todo!("delegate") @@ -120,52 +198,20 @@ impl ArrangementCommand { //("edit" [a: MaybeClip] Some(Self::Edit(a.unwrap()))) todo!() } - } -#[tengri_proc::command(MidiClip)] -impl ClipCommand { - fn set_color (clip: &mut MidiClip, color: Option) -> Perhaps { - //(SetColor [t: usize, s: usize, c: ItemTheme] - //clip.clip_set_color(t, s, c).map(|o|Self::SetColor(t, s, o))))); - //("color" [a: usize, b: usize] Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemTheme::random()))) - todo!() - } - fn set_loop (clip: &mut MidiClip, looping: Option) -> Perhaps { - //(SetLoop [t: usize, s: usize, l: bool] cmd_todo!("\n\rtodo: {self:?}")) - //("loop" [a: usize, b: usize, c: bool] Some(Self::SetLoop(a.unwrap(), b.unwrap(), c.unwrap()))) - todo!() +impl<'state> Context<'state, TrackCommand> for Arrangement { + fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option { + Context::get(&self, iter) } } -#[tengri_proc::command(Device)] -impl DeviceCommand { -} - -#[tengri_proc::command(JackMidiIn)] -impl InputCommand { -} - -#[tengri_proc::command(JackMidiOut)] -impl OutputCommand { -} - -#[tengri_proc::command(Scene)] -impl SceneCommand { - fn set_name (scene: &mut Scene, mut name: Arc) -> Perhaps { - std::mem::swap(&mut scene.name, &mut name); - Ok(Some(Self::SetName { name })) - } - fn set_color (scene: &mut Scene, mut color: ItemTheme) -> Perhaps { - std::mem::swap(&mut scene.color, &mut color); - Ok(Some(Self::SetColor { color })) - } - fn set_size (scene: &mut Scene, size: usize) -> Perhaps { - todo!() - } - fn set_zoom (scene: &mut Scene, zoom: usize) -> Perhaps { - todo!() - } +#[tengri_proc::expose] +impl Track { + fn _todo_opt_bool_stub_ (&self) -> Option { todo!() } + fn _todo_usize_stub_ (&self) -> usize { todo!() } + fn _todo_arc_str_stub_ (&self) -> Arc { todo!() } + fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() } } #[tengri_proc::command(Track)] @@ -174,8 +220,8 @@ impl TrackCommand { std::mem::swap(&mut name, &mut track.name); Ok(Some(Self::SetName { name })) } - fn set_color (track: &mut Track, color: ItemTheme) -> Perhaps { - std::mem::swap(color, track.color); + fn set_color (track: &mut Track, mut color: ItemTheme) -> Perhaps { + std::mem::swap(&mut color, &mut track.color); Ok(Some(Self::SetColor { color })) } fn set_mute (track: &mut Track, value: Option) -> Perhaps { @@ -205,3 +251,66 @@ impl TrackCommand { Ok(None) } } + +impl<'state> Context<'state, SceneCommand> for Arrangement { + fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option { + Context::get(&self, iter) + } +} + +impl<'state> Context<'state, ClipCommand> for Arrangement { + fn get <'source> (&'state self, iter: &mut TokenIter<'source>) -> Option { + Context::get(&self, iter) + } +} + +#[tengri_proc::expose] +impl Scene { + fn _todo_opt_bool_stub_ (&self) -> Option { todo!() } + fn _todo_usize_stub_ (&self) -> usize { todo!() } + fn _todo_arc_str_stub_ (&self) -> Arc { todo!() } + fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() } +} + +#[tengri_proc::command(Scene)] +impl SceneCommand { + fn set_name (scene: &mut Scene, mut name: Arc) -> Perhaps { + std::mem::swap(&mut scene.name, &mut name); + Ok(Some(Self::SetName { name })) + } + fn set_color (scene: &mut Scene, mut color: ItemTheme) -> Perhaps { + std::mem::swap(&mut scene.color, &mut color); + Ok(Some(Self::SetColor { color })) + } + fn set_size (scene: &mut Scene, size: usize) -> Perhaps { + todo!() + } + fn set_zoom (scene: &mut Scene, zoom: usize) -> Perhaps { + todo!() + } +} + +#[tengri_proc::expose] +impl MidiClip { + fn _todo_opt_bool_stub_ (&self) -> Option { todo!() } + fn _todo_bool_stub_ (&self) -> bool { todo!() } + fn _todo_usize_stub_ (&self) -> usize { todo!() } + fn _todo_arc_str_stub_ (&self) -> Arc { todo!() } + fn _todo_item_theme_stub (&self) -> ItemTheme { todo!() } + fn _todo_opt_item_theme_stub (&self) -> Option { todo!() } +} + +#[tengri_proc::command(MidiClip)] +impl ClipCommand { + fn set_color (clip: &mut MidiClip, color: Option) -> Perhaps { + //(SetColor [t: usize, s: usize, c: ItemTheme] + //clip.clip_set_color(t, s, c).map(|o|Self::SetColor(t, s, o))))); + //("color" [a: usize, b: usize] Some(Self::SetColor(a.unwrap(), b.unwrap(), ItemTheme::random()))) + todo!() + } + fn set_loop (clip: &mut MidiClip, looping: Option) -> Perhaps { + //(SetLoop [t: usize, s: usize, l: bool] cmd_todo!("\n\rtodo: {self:?}")) + //("loop" [a: usize, b: usize, c: bool] Some(Self::SetLoop(a.unwrap(), b.unwrap(), c.unwrap()))) + todo!() + } +} diff --git a/crates/device/src/arranger/arranger_audio.rs b/crates/device/src/arranger/arranger_audio.rs deleted file mode 100644 index 71139cf5..00000000 --- a/crates/device/src/arranger/arranger_audio.rs +++ /dev/null @@ -1,7 +0,0 @@ -use crate::*; - -pub trait RenderTracks { -} - -impl Audio for T { -} diff --git a/crates/device/src/arranger/arranger_clip.rs b/crates/device/src/arranger/arranger_clip.rs index ba5fe2bc..b5acd217 100644 --- a/crates/device/src/arranger/arranger_clip.rs +++ b/crates/device/src/arranger/arranger_clip.rs @@ -1,7 +1,7 @@ use crate::*; - impl Arrangement { + /// Put a clip in a slot pub(crate) fn clip_put ( &mut self, track: usize, scene: usize, clip: Option>> @@ -10,6 +10,7 @@ impl Arrangement { self.scenes[scene].clips[track] = clip; old } + /// Change the color of a clip, returning the previous one pub(crate) fn clip_set_color ( &self, track: usize, scene: usize, color: ItemTheme @@ -22,14 +23,78 @@ impl Arrangement { old }) } + /// Get the active clip pub(crate) fn clip (&self) -> Option>> { self.scene()?.clips.get(self.selected().track()?)?.clone() } + /// Toggle looping for the active clip pub(crate) fn toggle_loop (&mut self) { if let Some(clip) = self.clip() { clip.write().unwrap().toggle_loop() } } + +} + +#[cfg(all(feature = "pool", feature = "editor"))] +pub trait AutoCreate: + Has> + + Has> + + Has> + + Has> + + Has> + + Send + Sync +{ + // Create new clip in pool when entering empty cell + fn clip_auto_create (&mut self) -> Option>> { + if let Some(pool) = Has::>::get(self) + && let Some(Selection::TrackClip { track, scene }) = self.get() + && let Some(scene) = Has::>::get_mut(self).get_mut(scene) + && let Some(slot) = scene.clips.get_mut(track) + && slot.is_none() + && let Some(track) = Has::>::get_mut(self).get_mut(track) + { + let (index, mut clip) = pool.add_new_clip(); + // autocolor: new clip colors from scene and track color + let color = track.color.base.mix(scene.color.base, 0.5); + clip.write().unwrap().color = ItemColor::random_near(color, 0.2).into(); + if let Some(ref mut editor) = Has::>::get_mut(self) { + editor.set_clip(Some(&clip)); + } + *slot = Some(clip.clone()); + Some(clip) + } else { + None + } + } +} + +#[cfg(all(feature = "pool", feature = "editor"))] +pub trait AutoRemove: + Has> + + Has> + + Has> + + Has> + + Has> + + Send + Sync +{ + // Remove clip from arrangement when exiting empty clip editor + fn clip_auto_remove (&mut self) { + if let Some(ref mut pool) = Has::>::get(self) + && let Some(Selection::TrackClip { track, scene }) = self.get() + && let Some(scene) = Has::>::get_mut(self).get_mut(scene) + && let Some(slot) = scene.clips.get_mut(track) + && let Some(clip) = slot.as_mut() + { + let mut swapped = None; + if clip.read().unwrap().count_midi_messages() == 0 { + std::mem::swap(&mut swapped, slot); + } + if let Some(clip) = swapped { + pool.delete_clip(&clip.read().unwrap()); + } + } + } } diff --git a/crates/device/src/arranger/arranger_device.rs b/crates/device/src/arranger/arranger_device.rs deleted file mode 100644 index 60894ab5..00000000 --- a/crates/device/src/arranger/arranger_device.rs +++ /dev/null @@ -1,27 +0,0 @@ -use crate::*; - -impl Arrangement { - fn device_add_lv2 (&mut self) -> Usually<()> { - todo!(); - Ok(()) - } - fn device_add_sampler (&mut self) -> Usually<()> { - let name = self.jack.with_client(|c|c.name().to_string()); - let midi = self.track().expect("no active track").sequencer.midi_outs[0].name(); - let sampler = if let Ok(sampler) = Sampler::new( - &self.jack, - &format!("{}/Sampler", &self.track().expect("no active track").name), - &[PortConnect::exact(format!("{name}:{midi}"))], - &[&[], &[]], - &[&[], &[]] - ) { - self.dialog = None; - Device::Sampler(sampler) - } else { - self.dialog = Some(Dialog::Message(Message::FailedToAddDevice)); - return Err("failed to add device".into()) - }; - self.track_mut().expect("no active track").devices.push(sampler); - Ok(()) - } -} diff --git a/crates/device/src/arranger/arranger_edit.rs b/crates/device/src/arranger/arranger_edit.rs deleted file mode 100644 index 715623f1..00000000 --- a/crates/device/src/arranger/arranger_edit.rs +++ /dev/null @@ -1,62 +0,0 @@ -use crate::*; - -#[cfg(all(feature = "pool", feature = "editor"))] -pub trait AutoCreate: - Has> + - Has> + - Has> + - Has> + - Has> + - Send + Sync -{ - // Create new clip in pool when entering empty cell - fn clip_auto_create (&mut self) -> Option>> { - if let Some(pool) = Has::::get(self) - && let Some(Selection::TrackClip { track, scene }) = self.get() - && let Some(scene) = Has::>::get_mut(self).get_mut(scene) - && let Some(slot) = scene.clips.get_mut(track) - && slot.is_none() - && let Some(track) = Has::>::get_mut(self).get_mut(track) - { - let (index, mut clip) = pool.add_new_clip(); - // autocolor: new clip colors from scene and track color - let color = track.color.base.mix(scene.color.base, 0.5); - clip.write().unwrap().color = ItemColor::random_near(color, 0.2).into(); - if let Some(ref mut editor) = Has::>::get_mut(self) { - editor.set_clip(Some(&clip)); - } - *slot = Some(clip.clone()); - Some(clip) - } else { - None - } - } -} - -#[cfg(all(feature = "pool", feature = "editor"))] -pub trait AutoRemove: - Has> + - Has> + - Has> + - Has> + - Has> + - Send + Sync -{ - // Remove clip from arrangement when exiting empty clip editor - fn clip_auto_remove (&mut self) { - if let Some(ref mut pool) = Has::::get(self) - && let Some(Selection::TrackClip { track, scene }) = self.get() - && let Some(scene) = Has::>::get_mut(self).get_mut(scene) - && let Some(slot) = scene.clips.get_mut(track) - && let Some(clip) = slot.as_mut() - { - let mut swapped = None; - if clip.read().unwrap().count_midi_messages() == 0 { - std::mem::swap(&mut swapped, slot); - } - if let Some(clip) = swapped { - pool.delete_clip(&clip.read().unwrap()); - } - } - } -} diff --git a/crates/device/src/arranger/arranger_model.rs b/crates/device/src/arranger/arranger_model.rs index 0be3e78b..e12e73d1 100644 --- a/crates/device/src/arranger/arranger_model.rs +++ b/crates/device/src/arranger/arranger_model.rs @@ -35,6 +35,8 @@ pub struct Arrangement { pub arranger: Arc>, /// Display size pub size: Measure, + /// Jack client handle + pub jack: Jack, } has!(Option: |self: Arrangement|self.selected); @@ -42,33 +44,73 @@ has!(Vec: |self: Arrangement|self.midi_ins); has!(Vec: |self: Arrangement|self.midi_outs); has!(Vec: |self: Arrangement|self.scenes); has!(Vec: |self: Arrangement|self.tracks); +has!(Jack: |self: Arrangement|self.jack); -impl Has> for Arrangement { - fn get (&self) -> &Option { - Has::::get(self) - .and_then(|selection|selection.track()) - .and_then(|index|Has::>::get(self).get(index)) - .flatten() +impl Arrangement { + /// Width of display + pub(crate) fn w (&self) -> u16 { + self.size.w() as u16 } - fn get_mut (&mut self) -> &mut Option { - Has::::get(self) - .and_then(|selection|selection.track()) - .and_then(|index|Has::>::get_mut(self).get_mut(index)) - .flatten() - } -} - -impl Has> for Arrangement { - fn get (&self) -> &Option { - Has::::get(self) - .and_then(|selection|selection.track()) - .and_then(|index|Has::>::get(self).get(index)) - .flatten() - } - fn get_mut (&mut self) -> &mut Option { - Has::::get(self) - .and_then(|selection|selection.track()) - .and_then(|index|Has::>::get_mut(self).get_mut(index)) - .flatten() + /// Width allocated for sidebar. + pub(crate) fn w_sidebar (&self, is_editing: bool) -> u16 { + self.w() / if is_editing { 16 } else { 8 } as u16 + } + /// Width taken by all tracks. + pub(crate) fn w_tracks (&self) -> u16 { + self.tracks_with_sizes().last().map(|(_, _, _, x)|x as u16).unwrap_or(0) + } + /// Width available to display tracks. + pub(crate) fn w_tracks_area (&self, is_editing: bool) -> u16 { + self.w().saturating_sub(2 * self.w_sidebar(is_editing)) + } + /// Height of display + pub(crate) fn h (&self) -> u16 { + self.size.h() as u16 + } + /// Height available to display track headers. + pub(crate) fn h_tracks_area (&self) -> u16 { + 5 // FIXME + //self.h().saturating_sub(self.h_inputs() + self.h_outputs()) + } + /// Height available to display tracks. + pub(crate) fn h_scenes_area (&self) -> u16 { + //15 + self.h().saturating_sub( + self.h_inputs() + + self.h_outputs() + + self.h_devices() + + 13 // FIXME + ) + } + /// Height taken by all scenes. + pub(crate) fn h_scenes (&self, is_editing: bool) -> u16 { + let (selected_track, selected_scene) = match Has::>::get(self) { + Some(Selection::Track(t)) => (Some(*t), None), + Some(Selection::Scene(s)) => (None, Some(*s)), + Some(Selection::TrackClip { track, scene }) => (Some(*track), Some(*scene)), + _ => (None, None) + }; + self.scenes_with_sizes( + is_editing, + ArrangerView::H_SCENE, + ArrangerView::H_EDITOR, + selected_track, + selected_scene + ) + .last() + .map(|(_, _, _, y)|y as u16).unwrap_or(0) + } + /// Height taken by all inputs. + pub(crate) fn h_inputs (&self) -> u16 { + self.midi_ins_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) + } + /// Height taken by all outputs. + pub(crate) fn h_outputs (&self) -> u16 { + self.midi_outs_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) + } + /// Height taken by visible device slots. + pub(crate) fn h_devices (&self) -> u16 { + 2 + //1 + self.devices_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) } } diff --git a/crates/device/src/arranger/arranger_port.rs b/crates/device/src/arranger/arranger_port.rs index 9b09ba84..c98b0807 100644 --- a/crates/device/src/arranger/arranger_port.rs +++ b/crates/device/src/arranger/arranger_port.rs @@ -54,6 +54,10 @@ impl<'a> ArrangerView<'a> { |_, _|Tui::bg(Reset, Align::c(Bsp::s(OctaveVertical::default(), " ------ "))))) } +} + +impl<'a> ArrangerView<'a> { + pub(crate) fn output_nexts (&self) -> impl Content + '_ { Tryptich::top(2) .left(self.width_side, Align::ne("From clip:")) diff --git a/crates/device/src/arranger/arranger_scene.rs b/crates/device/src/arranger/arranger_scene.rs index 6fb3401c..a217d43e 100644 --- a/crates/device/src/arranger/arranger_scene.rs +++ b/crates/device/src/arranger/arranger_scene.rs @@ -1,5 +1,16 @@ use crate::*; +impl> + Send + Sync> HasScene for T {} + +pub trait HasScene: Has> + Send + Sync { + fn scene (&self) -> &Option { + Has::>::get(self) + } + fn scene_mut (&mut self) -> &mut Option { + Has::>::get_mut(self) + } +} + #[derive(Debug, Default)] pub struct Scene { /// Name of scene @@ -39,223 +50,3 @@ impl Scene { match self.clips.get(index) { Some(Some(clip)) => Some(clip), _ => None } } } - -impl> + Send + Sync> HasScenes for T {} - -pub trait HasScenes: Has> + Send + Sync { - fn scenes (&self) -> &Vec { - Has::>::get(self) - } - fn scenes_mut (&self) -> &mut Vec { - Has::>::get_mut(self) - } - fn scenes_with_sizes ( - &self, - editing: bool, - height: usize, - larger: usize, - selected_track: Option, - selected_scene: Option, - ) -> impl ScenesSizes<'_> { - let mut y = 0; - self.scenes().iter().enumerate().map(move|(s, scene)|{ - let active = editing && selected_track.is_some() && selected_scene == Some(s); - let height = if active { larger } else { height }; - let data = (s, scene, y, y + height); - y += height; - data - }) - } - /// Generate the default name for a new scene - fn scene_default_name (&self) -> Arc { - format!("Sc{:3>}", self.scenes().len() + 1).into() - } - fn scene_longest_name (&self) -> usize { - self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max) - } -} - -impl> + Send + Sync> HasScene for T {} - -pub trait HasScene: Has> + Send + Sync { - fn scene (&self) -> Option<&Scene> { - Has::>::get(self) - } - fn scene_mut (&mut self) -> Option<&mut Scene> { - Has::>::get_mut(self) - } -} - -impl<'a> ArrangerView<'a> { - /// Default scene height. - pub(crate) const H_SCENE: usize = 2; - /// Default editor height. - pub(crate) const H_EDITOR: usize = 15; - - /// Render scenes with clips - pub(crate) fn scenes (&'a self, editor: Option) -> impl Content + 'a { - - /// A scene with size and color. - type SceneWithColor<'a> = (usize, &'a Scene, usize, usize, Option); - - let Self { - arrangement, - width, width_side, width_mid, - scenes_height, scene_last, scene_selected, - track_selected, is_editing, .. - } = self; - - let selection = Has::>::get(*arrangement); - let (selected_track, selected_scene) = match selection { - Some(Selection::Track(t)) => (Some(*t), None), - Some(Selection::Scene(s)) => (None, Some(*s)), - Some(Selection::TrackClip { track, scene }) => (Some(*track), Some(*scene)), - _ => (None, None) - }; - - let scenes_with_scene_colors = ||arrangement.scenes_with_sizes( - *is_editing, - Self::H_SCENE, - Self::H_EDITOR, - selected_track, - selected_scene, - ).map_while(|(s, scene, y1, y2)|if y2 as u16 > *scenes_height { - None - } else { - Some((s, scene, y1, y2, if s == 0 { - None - } else { - Some(arrangement.scenes()[s-1].color) - })) - }); - - let scene_header = |(s, scene, y1, y2, previous): SceneWithColor, _|{ - let height = (1 + y2 - y1) as u16; - let name = Some(scene.name.clone()); - let content = Fill::x(Align::w(Tui::bold(true, Bsp::e(" ⯈ ", name)))); - let same_track = true; - let selected = same_track && *scene_selected == Some(s); - let neighbor = same_track && s > 0 && *scene_selected == Some(s - 1); - let is_last = *scene_last == s; - let theme = scene.color; - let fg = theme.lightest.rgb; - let bg = if selected { theme.light } else { theme.base }.rgb; - let hi = if let Some(previous) = previous { - if neighbor { - previous.light.rgb - } else { - previous.base.rgb - } - } else { - Reset - }; - let lo = if is_last { - Reset - } else if selected { - theme.light.rgb - } else { - theme.base.rgb - }; - Fill::x(map_south(y1 as u16, height, Fixed::y(height, Phat { - width: 0, height: 0, content, colors: [fg, bg, hi, lo] - }))) - }; - - let scenes_with_track_colors = |track: usize| arrangement.scenes_with_sizes( - self.is_editing, - Self::H_SCENE, - Self::H_EDITOR, - selected_track, - selected_scene, - ).map_while(|(s, scene, y1, y2)|if y2 as u16 > self.scenes_height { - None - } else { - Some((s, scene, y1, y2, if s == 0 { - None - } else { - Some(self.arrangement.scenes[s-1].clips[track].as_ref() - .map(|c|c.read().unwrap().color) - .unwrap_or(ItemTheme::G[32])) - })) - }); - - let scene_track_clips = |track_index, track|Map::new( - ||scenes_with_track_colors(track_index), - |(s, scene, y1, y2, previous): SceneWithColor<'a>, _|{ - let (name, theme) = if let Some(clip) = &scene.clips[track_index] { - let clip = clip.read().unwrap(); - (Some(clip.name.clone()), clip.color) - } else { - (None, ItemTheme::G[32]) - }; - let height = (1 + y2 - y1) as u16; - let content = Fill::x(Align::w(Tui::bold(true, Bsp::e(" ⏹ ", name)))); - let same_track = *track_selected == Some(track_index); - let selected = same_track && *scene_selected == Some(s); - let neighbor = same_track && s > 0 && *scene_selected == Some(s - 1); - let is_last = *scene_last == s; - let fg = theme.lightest.rgb; - let bg = if selected { theme.light } else { theme.base }.rgb; - let hi = if let Some(previous) = previous { - if neighbor { - previous.light.rgb - } else { - previous.base.rgb - } - } else { - Reset - }; - let lo = if is_last { - Reset - } else if selected { - theme.light.rgb - } else { - theme.base.rgb - }; - map_south(y1 as u16, height, Bsp::b(Fixed::y(height, Phat { - width: 0, height: 0, content, colors: [fg, bg, hi, lo] - }), When( - *is_editing && same_track && *scene_selected == Some(s), - editor - ))) - }); - - Tryptich::center(*scenes_height) - .left(*width_side, Map::new( - ||scenes_with_scene_colors(), - scene_header)) - .middle(*width_mid, per_track( - *width_mid, - ||self.tracks_with_sizes_scrolled(), - scene_track_clips)) - } - -} - -impl Arrangement { - /// Add multiple scenes - pub fn scenes_add (&mut self, n: usize) -> Usually<()> { - let scene_color_1 = ItemColor::random(); - let scene_color_2 = ItemColor::random(); - for i in 0..n { - let _ = self.scene_add(None, Some( - scene_color_1.mix(scene_color_2, i as f32 / n as f32).into() - ))?; - } - Ok(()) - } - - /// Add a scene - pub fn scene_add (&mut self, name: Option<&str>, color: Option) - -> Usually<(usize, &mut Scene)> - { - let scene = Scene { - name: name.map_or_else(||self.scene_default_name(), |x|x.to_string().into()), - clips: vec![None;self.tracks().len()], - color: color.unwrap_or_else(ItemTheme::random), - }; - self.scenes_mut().push(scene); - let index = self.scenes().len() - 1; - Ok((index, &mut self.scenes_mut()[index])) - } -} diff --git a/crates/device/src/arranger/arranger_scenes.rs b/crates/device/src/arranger/arranger_scenes.rs new file mode 100644 index 00000000..8603e603 --- /dev/null +++ b/crates/device/src/arranger/arranger_scenes.rs @@ -0,0 +1,65 @@ +use crate::*; + +impl> + Send + Sync> HasScenes for T {} + +pub trait HasScenes: Has> + Send + Sync { + fn scenes (&self) -> &Vec { + Has::>::get(self) + } + fn scenes_mut (&mut self) -> &mut Vec { + Has::>::get_mut(self) + } + fn scenes_with_sizes ( + &self, + editing: bool, + height: usize, + larger: usize, + selected_track: Option, + selected_scene: Option, + ) -> impl ScenesSizes<'_> { + let mut y = 0; + self.scenes().iter().enumerate().map(move|(s, scene)|{ + let active = editing && selected_track.is_some() && selected_scene == Some(s); + let height = if active { larger } else { height }; + let data = (s, scene, y, y + height); + y += height; + data + }) + } + /// Generate the default name for a new scene + fn scene_default_name (&self) -> Arc { + format!("Sc{:3>}", self.scenes().len() + 1).into() + } + fn scene_longest_name (&self) -> usize { + self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max) + } +} + +impl AddScene for T {} + +pub trait AddScene: HasScenes + HasTracks { + /// Add multiple scenes + fn scenes_add (&mut self, n: usize) -> Usually<()> { + let scene_color_1 = ItemColor::random(); + let scene_color_2 = ItemColor::random(); + for i in 0..n { + let _ = self.scene_add(None, Some( + scene_color_1.mix(scene_color_2, i as f32 / n as f32).into() + ))?; + } + Ok(()) + } + /// Add a scene + fn scene_add (&mut self, name: Option<&str>, color: Option) + -> Usually<(usize, &mut Scene)> + { + let scene = Scene { + name: name.map_or_else(||self.scene_default_name(), |x|x.to_string().into()), + clips: vec![None;self.tracks().len()], + color: color.unwrap_or_else(ItemTheme::random), + }; + self.scenes_mut().push(scene); + let index = self.scenes().len() - 1; + Ok((index, &mut self.scenes_mut()[index])) + } +} diff --git a/crates/device/src/arranger/arranger_select.rs b/crates/device/src/arranger/arranger_select.rs index 6105c200..2ef03033 100644 --- a/crates/device/src/arranger/arranger_select.rs +++ b/crates/device/src/arranger/arranger_select.rs @@ -1,5 +1,42 @@ use crate::*; +impl> HasSelection for T {} + +pub trait HasSelection: Has { + fn selection (&self) -> &Selection { + self.get() + } + fn selection_mut (&mut self) -> &mut Selection { + self.get_mut() + } +} + +impl Has> for Arrangement { + fn get (&self) -> &Option { + Has::>::get(self) + .and_then(|selection|selection.track()) + .and_then(|index|Has::>::get(self).get(index)) + } + fn get_mut (&mut self) -> &mut Option { + Has::>::get(self) + .and_then(|selection|selection.track()) + .and_then(|index|Has::>::get_mut(self).get_mut(index)) + } +} + +impl Has> for Arrangement { + fn get (&self) -> &Option { + Has::>::get(self) + .and_then(|selection|selection.track()) + .and_then(|index|Has::>::get(self).get(index)) + } + fn get_mut (&mut self) -> &mut Option { + Has::>::get(self) + .and_then(|selection|selection.track()) + .and_then(|index|Has::>::get_mut(self).get_mut(index)) + } +} + /// Represents the current user selection in the arranger #[derive(PartialEq, Clone, Copy, Debug, Default)] pub enum Selection { @@ -147,69 +184,4 @@ impl Selection { } impl Arrangement { - /// Set the selection - pub(crate) fn select (&mut self, s: Selection) { - self.selected = s; - // autoedit: load focused clip in editor. - if let Some(ref mut editor) = self.editor { - editor.set_clip(match self.selected { - Selection::TrackClip { track, scene } - if let Some(Some(Some(clip))) = self - .scenes.get(scene) - .map(|s|s.clips.get(track)) => Some(clip), - _ => None - }); - } - } - /// Launch a clip or scene - pub(crate) fn launch (&mut self) { - use Selection::*; - match self.selected { - Track(t) => { - self.tracks[t].sequencer.enqueue_next(None) - }, - TrackClip { track, scene } => { - self.tracks[track].sequencer.enqueue_next(self.scenes[scene].clips[track].as_ref()) - }, - Scene(s) => { - for t in 0..self.tracks.len() { - self.tracks[t].sequencer.enqueue_next(self.scenes[s].clips[t].as_ref()) - } - }, - _ => {} - }; - } - /// Set the color of the selected entity - pub fn set_color (&mut self, palette: Option) -> Option { - use Selection::*; - let palette = palette.unwrap_or_else(||ItemTheme::random()); - Some(match self.selected { - Mix => { - let old = self.color; - self.color = palette; - old - }, - Scene(s) => { - let old = self.scenes[s].color; - self.scenes[s].color = palette; - old - } - 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; - old - } else { - return None - } - }, - _ => todo!() - }) - } } diff --git a/crates/device/src/arranger/arranger_track.rs b/crates/device/src/arranger/arranger_track.rs index fc028ee5..41cb5bf1 100644 --- a/crates/device/src/arranger/arranger_track.rs +++ b/crates/device/src/arranger/arranger_track.rs @@ -13,8 +13,9 @@ pub struct Track { /// Device chain pub devices: Vec, } -has_clock!(|self: Track|self.sequencer.clock); -has_sequencer!(|self: Track|self.sequencer); + +has!(Clock: |self: Track|self.sequencer.clock); +has!(Sequencer: |self: Track|self.sequencer); impl Track { /// Create a new track with only the default [Sequencer]. @@ -98,7 +99,7 @@ impl Track { impl> + Send + Sync> HasTracks for T {} -pub trait HasTracks: Send + Sync { +pub trait HasTracks: Has> + Send + Sync { fn tracks (&self) -> &Vec { Has::>::get(self) } @@ -107,7 +108,7 @@ pub trait HasTracks: Send + Sync { } /// Run audio callbacks for every track and every device fn tracks_render (&mut self, client: &Client, scope: &ProcessScope) -> Control { - for track in self.tracks.iter_mut() { + for track in self.tracks_mut().iter_mut() { if Control::Quit == PlayerAudio( track.sequencer_mut(), &mut self.note_buf, &mut self.midi_buf ).process(client, scope) { @@ -131,7 +132,7 @@ pub trait HasTracks: Send + Sync { } } /// Stop all playing clips - fn tracks_launch (&mut self, clips: Option>>>) { + fn tracks_launch (&mut self, clips: Option>>>>) { if let Some(clips) = clips { for (clip, track) in clips.iter().zip(self.tracks_mut()) { track.sequencer.enqueue_next(clip); @@ -170,11 +171,11 @@ pub trait HasTracks: Send + Sync { impl> + Send + Sync> HasTrack for T {} pub trait HasTrack: Has> + Send + Sync { - fn scene (&self) -> Option<&Track> { - Has::>::get(self) + fn track (&self) -> Option<&Track> { + Has::>::get(self).as_ref() } - fn scene_mut (&mut self) -> Option<&mut Track> { - Has::>::get_mut(self) + fn track_mut (&mut self) -> Option<&mut Track> { + Has::>::get_mut(self).as_mut() } } @@ -216,9 +217,11 @@ pub(crate) fn per_track <'a, T: Content + 'a, U: TracksSizes<'a>> ( ) } -impl Arrangement { +impl AddTrack for T {} + +pub trait AddTrack: HasTracks + HasScenes + HasClock + HasJack { /// Add multiple tracks - pub fn tracks_add ( + fn tracks_add ( &mut self, count: usize, width: Option, @@ -239,7 +242,7 @@ impl Arrangement { } /// Add a track - pub fn track_add ( + fn track_add ( &mut self, name: Option<&str>, color: Option, diff --git a/crates/device/src/arranger/arranger_tracks.rs b/crates/device/src/arranger/arranger_tracks.rs new file mode 100644 index 00000000..e69de29b diff --git a/crates/device/src/arranger/arranger_view.rs b/crates/device/src/arranger/arranger_view.rs index f94b89aa..e28d3362 100644 --- a/crates/device/src/arranger/arranger_view.rs +++ b/crates/device/src/arranger/arranger_view.rs @@ -1,78 +1,5 @@ use crate::*; -impl Arrangement { - /// Width of display - pub(crate) fn w (&self) -> u16 { - self.size.w() as u16 - } - /// Width allocated for sidebar. - pub(crate) fn w_sidebar (&self) -> u16 { - self.w() / if self.is_editing() { 16 } else { 8 } as u16 - } - /// Width taken by all tracks. - pub(crate) fn w_tracks (&self) -> u16 { - self.tracks_with_sizes().last().map(|(_, _, _, x)|x as u16).unwrap_or(0) - } - /// Width available to display tracks. - pub(crate) fn w_tracks_area (&self) -> u16 { - self.w().saturating_sub(2 * self.w_sidebar()) - } - /// Height of display - pub(crate) fn h (&self) -> u16 { - self.size.h() as u16 - } - /// Height available to display track headers. - pub(crate) fn h_tracks_area (&self) -> u16 { - 5 // FIXME - //self.h().saturating_sub(self.h_inputs() + self.h_outputs()) - } - /// Height available to display tracks. - pub(crate) fn h_scenes_area (&self) -> u16 { - //15 - self.h().saturating_sub( - self.h_inputs() + - self.h_outputs() + - self.h_devices() + - 13 // FIXME - ) - } - /// Height taken by all scenes. - pub(crate) fn h_scenes (&self) -> u16 { - let (selected_track, selected_scene) = match Has::>::get(self) { - Some(Selection::Track(t)) => (Some(*t), None), - Some(Selection::Scene(s)) => (None, Some(*s)), - Some(Selection::TrackClip { track, scene }) => (Some(*track), Some(*scene)), - _ => (None, None) - }; - self.scenes_with_sizes( - self.is_editing, - ArrangerView::H_SCENE, - ArrangerView::H_EDITOR, - selected_track, - selected_scene - ) - .last() - .map(|(_, _, _, y)|y as u16).unwrap_or(0) - } - /// Height taken by all inputs. - pub(crate) fn h_inputs (&self) -> u16 { - self.inputs_with_sizes() - .last() - .map(|(_, _, _, _, y)|y as u16).unwrap_or(0) - } - /// Height taken by all outputs. - pub(crate) fn h_outputs (&self) -> u16 { - self.outputs_with_sizes() - .last() - .map(|(_, _, _, _, y)|y as u16).unwrap_or(0) - } - /// Height taken by visible device slots. - pub(crate) fn h_devices (&self) -> u16 { - 2 - //1 + self.devices_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) - } -} - pub(crate) struct ArrangerView<'a> { pub arrangement: &'a Arrangement, @@ -107,17 +34,18 @@ impl<'a> ArrangerView<'a> { arrangement: &'a Arrangement, editor: Option<&'a MidiEditor> ) -> Self { + let is_editing = editor.is_some(); let selected = arrangement.selected; let h_tracks_area = arrangement.h_tracks_area(); let h_scenes_area = arrangement.h_scenes_area(); - let h_scenes = arrangement.h_scenes(); + let h_scenes = arrangement.h_scenes(is_editing); Self { arrangement, - is_editing: editor.is_some(), + is_editing, width: arrangement.w(), - width_mid: arrangement.w_tracks_area(), - width_side: arrangement.w_sidebar(), + width_mid: arrangement.w_tracks_area(is_editing), + width_side: arrangement.w_sidebar(is_editing), inputs_height: arrangement.h_inputs(), inputs_count: arrangement.midi_ins.len(), @@ -214,4 +142,144 @@ impl<'a> ArrangerView<'a> { track.devices.get(0).map(|device|wrap(bg.rgb, fg, device.name())) })) } + + /// Default scene height. + pub(crate) const H_SCENE: usize = 2; + + /// Default editor height. + pub(crate) const H_EDITOR: usize = 15; + + /// Render scenes with clips + pub(crate) fn scenes (&'a self, editor: Option) -> impl Content + 'a { + /// A scene with size and color. + type SceneWithColor<'a> = (usize, &'a Scene, usize, usize, Option); + let Self { + arrangement, + width, width_side, width_mid, + scenes_height, scene_last, scene_selected, + track_selected, is_editing, .. + } = self; + + let selection = Has::>::get(self.arrangement); + let selected_track = selection.map(|s|s.track()).flatten(); + let selected_scene = selection.map(|s|s.scene()).flatten(); + + let scenes_with_scene_colors = ||HasScenes::scenes_with_sizes(self.arrangement, + *is_editing, + Self::H_SCENE, + Self::H_EDITOR, + selected_track, + selected_scene, + ).map_while(|(s, scene, y1, y2)|if y2 as u16 > *scenes_height { + None + } else { + Some((s, scene, y1, y2, if s == 0 { + None + } else { + Some(arrangement.scenes()[s-1].color) + })) + }); + + let scene_header = |(s, scene, y1, y2, previous): SceneWithColor, _|{ + let height = (1 + y2 - y1) as u16; + let name = Some(scene.name.clone()); + let content = Fill::x(Align::w(Tui::bold(true, Bsp::e(" ⯈ ", name)))); + let same_track = true; + let selected = same_track && *scene_selected == Some(s); + let neighbor = same_track && s > 0 && *scene_selected == Some(s - 1); + let is_last = *scene_last == s; + let theme = scene.color; + let fg = theme.lightest.rgb; + let bg = if selected { theme.light } else { theme.base }.rgb; + let hi = if let Some(previous) = previous { + if neighbor { + previous.light.rgb + } else { + previous.base.rgb + } + } else { + Reset + }; + let lo = if is_last { + Reset + } else if selected { + theme.light.rgb + } else { + theme.base.rgb + }; + Fill::x(map_south(y1 as u16, height, Fixed::y(height, Phat { + width: 0, height: 0, content, colors: [fg, bg, hi, lo] + }))) + }; + + let scenes_with_track_colors = |track: usize| arrangement.scenes_with_sizes( + self.is_editing, + Self::H_SCENE, + Self::H_EDITOR, + selected_track, + selected_scene, + ).map_while(|(s, scene, y1, y2)|if y2 as u16 > self.scenes_height { + None + } else { + Some((s, scene, y1, y2, if s == 0 { + None + } else { + Some(self.arrangement.scenes[s-1].clips[track].as_ref() + .map(|c|c.read().unwrap().color) + .unwrap_or(ItemTheme::G[32])) + })) + }); + + Tryptich::center(*scenes_height) + + .left(*width_side, Map::new( + ||scenes_with_scene_colors(), + scene_header)) + + .middle(*width_mid, per_track( + *width_mid, + ||self.tracks_with_sizes_scrolled(), + |track_index, track|Map::new( + ||scenes_with_track_colors(track_index), + |(s, scene, y1, y2, previous): SceneWithColor<'a>, _|{ + let (name, theme) = if let Some(clip) = &scene.clips[track_index] { + let clip = clip.read().unwrap(); + (Some(clip.name.clone()), clip.color) + } else { + (None, ItemTheme::G[32]) + }; + let height = (1 + y2 - y1) as u16; + let content = Fill::x(Align::w(Tui::bold(true, Bsp::e(" ⏹ ", name)))); + let same_track = *track_selected == Some(track_index); + let selected = same_track && *scene_selected == Some(s); + let neighbor = same_track && s > 0 && *scene_selected == Some(s - 1); + let is_last = *scene_last == s; + let fg = theme.lightest.rgb; + let bg = if selected { theme.light } else { theme.base }.rgb; + let hi = if let Some(previous) = previous { + if neighbor { + previous.light.rgb + } else { + previous.base.rgb + } + } else { + Reset + }; + let lo = if is_last { + Reset + } else if selected { + theme.light.rgb + } else { + theme.base.rgb + }; + map_south(y1 as u16, height, Bsp::b(Fixed::y(height, Phat { + width: 0, height: 0, content, colors: [fg, bg, hi, lo] + }), When( + *is_editing && same_track && *scene_selected == Some(s), + editor + ))) + }))) + + } + } diff --git a/crates/device/src/clock.rs b/crates/device/src/clock.rs index 31b19c44..f16baafe 100644 --- a/crates/device/src/clock.rs +++ b/crates/device/src/clock.rs @@ -1,3 +1,5 @@ +use crate::*; + mod clock_api; pub use self::clock_api::*; mod clock_model; pub use self::clock_model::*; mod clock_view; pub use self::clock_view::*; @@ -7,11 +9,11 @@ pub trait HasClock: Send + Sync { fn clock_mut (&mut self) -> &mut Clock; } -#[macro_export] macro_rules! has_clock { - (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { - impl $(<$($L),*$($T $(: $U)?),*>)? HasClock for $Struct $(<$($L),*$($T),*>)? { - fn clock (&$self) -> &Clock { &$cb } - fn clock_mut (&mut $self) -> &mut Clock { &mut $cb } - } +impl> HasClock for T { + fn clock (&self) -> &Clock { + self.get() + } + fn clock_mut (&mut self) -> &mut Clock { + self.get_mut() } } diff --git a/crates/device/src/device.rs b/crates/device/src/device.rs index 70da6c28..91e45fa0 100644 --- a/crates/device/src/device.rs +++ b/crates/device/src/device.rs @@ -1,5 +1,19 @@ use crate::*; +impl> + Has> HasDevices for T { + fn devices (&self) -> &Vec { + self.get() + } + fn devices_mut (&mut self) -> &mut Vec { + self.get_mut() + } +} + +pub trait HasDevices: Has { + fn devices (&self) -> &Vec; + fn devices_mut (&mut self) -> &mut Vec; +} + #[derive(Debug)] pub enum Device { #[cfg(feature = "sampler")] @@ -49,3 +63,7 @@ audio!(|self: DeviceAudio<'a>, client, scope|{ Sf2 => { todo!() }, // TODO } }); + +#[tengri_proc::command(Device)] +impl DeviceCommand { +} diff --git a/crates/device/src/pool/pool_model.rs b/crates/device/src/pool/pool_model.rs index 8347ddb7..f6034bf3 100644 --- a/crates/device/src/pool/pool_model.rs +++ b/crates/device/src/pool/pool_model.rs @@ -4,11 +4,11 @@ use crate::*; pub struct Pool { pub visible: bool, /// Collection of clips - pub clips: Arc>>>>, + pub clips: Arc>>>>, /// Selected clip - pub clip: AtomicUsize, + pub clip: AtomicUsize, /// Mode switch - pub mode: Option, + pub mode: Option, /// Embedded file browser pub browser: Option, } diff --git a/crates/device/src/sequencer/seq_model.rs b/crates/device/src/sequencer/seq_model.rs index 2282edb1..a4dd982d 100644 --- a/crates/device/src/sequencer/seq_model.rs +++ b/crates/device/src/sequencer/seq_model.rs @@ -1,20 +1,20 @@ //! MIDI sequencer use crate::*; +impl> HasSequencer for T { + fn sequencer (&self) -> &impl MidiSequencer { + self.get() + } + fn sequencer_mut (&mut self) -> &mut impl MidiSequencer { + self.get_mut() + } +} + pub trait HasSequencer { fn sequencer (&self) -> &impl MidiSequencer; fn sequencer_mut (&mut self) -> &mut impl MidiSequencer; } -#[macro_export] macro_rules! has_sequencer { - (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { - impl $(<$($L),*$($T $(: $U)?),*>)? HasSequencer for $Struct $(<$($L),*$($T),*>)? { - fn sequencer (&$self) -> &impl MidiSequencer { &$cb } - fn sequencer_mut (&mut $self) -> &mut impl MidiSequencer { &mut$cb } - } - } -} - /// Contains state for playing a clip pub struct Sequencer { /// State of clock and playhead @@ -102,7 +102,7 @@ impl std::fmt::Debug for Sequencer { } } -has_clock!(|self: Sequencer|self.clock); +has!(Clock: |self: Sequencer|self.clock); has!(Vec: |self:Sequencer| self.midi_ins); has!(Vec: |self:Sequencer| self.midi_outs); diff --git a/crates/engine/Cargo.toml b/crates/engine/Cargo.toml index d097e945..d63ae531 100644 --- a/crates/engine/Cargo.toml +++ b/crates/engine/Cargo.toml @@ -5,6 +5,7 @@ version = { workspace = true } [dependencies] tengri = { workspace = true } +tengri_proc = { workspace = true } jack = { workspace = true } midly = { workspace = true } uuid = { workspace = true } diff --git a/crates/engine/src/jack/jack_client.rs b/crates/engine/src/jack/jack_client.rs index b1200529..d8d6be6e 100644 --- a/crates/engine/src/jack/jack_client.rs +++ b/crates/engine/src/jack/jack_client.rs @@ -2,6 +2,12 @@ use crate::*; use super::*; use self::JackState::*; +impl> HasJack for T { + fn jack (&self) -> &Jack { + self.get() + } +} + /// Things that can provide a [jack::Client] reference. pub trait HasJack { /// Return the internal [jack::Client] handle diff --git a/crates/engine/src/lib.rs b/crates/engine/src/lib.rs index 67482aa5..e9bcf677 100644 --- a/crates/engine/src/lib.rs +++ b/crates/engine/src/lib.rs @@ -9,7 +9,7 @@ pub(crate) use std::sync::{Arc, atomic::{AtomicUsize, AtomicBool, Ordering::Rela pub(crate) use std::fmt::Debug; pub(crate) use std::ops::{Add, Sub, Mul, Div, Rem}; -pub(crate) use ::tengri::{from, Usually, tui::*}; +pub(crate) use ::tengri::{from, Usually, Perhaps, tui::*}; pub use ::atomic_float; pub(crate) use atomic_float::*; diff --git a/crates/engine/src/midi/midi_in.rs b/crates/engine/src/midi/midi_in.rs index 800bf2bc..27cbd76e 100644 --- a/crates/engine/src/midi/midi_in.rs +++ b/crates/engine/src/midi/midi_in.rs @@ -1,5 +1,9 @@ use crate::*; +#[tengri_proc::command(JackMidiIn)] +impl MidiInputCommand { +} + impl>> HasMidiIns for T { fn midi_ins (&self) -> &Vec { self.get() @@ -32,11 +36,20 @@ pub trait HasMidiIns { data }) } - fn midi_in_add (&mut self, jack: &Jack) -> Usually<()> { +} + +pub type CollectedMidiInput<'a> = Vec, MidiError>)>>; + +impl AddMidiIn for T { + fn midi_in_add (&mut self) -> Usually<()> { let index = self.midi_ins().len(); - self.midi_ins_mut().push(JackMidiIn::new(jack, &format!("M/{index}"), &[])?); + let port = JackMidiIn::new(self.jack(), &format!("M/{index}"), &[])?; + self.midi_ins_mut().push(port); Ok(()) } } -pub type CollectedMidiInput<'a> = Vec, MidiError>)>>; +/// May create new MIDI input ports. +pub trait AddMidiIn { + fn midi_in_add (&mut self) -> Usually<()>; +} diff --git a/crates/engine/src/midi/midi_out.rs b/crates/engine/src/midi/midi_out.rs index 1bc3a74d..89a22296 100644 --- a/crates/engine/src/midi/midi_out.rs +++ b/crates/engine/src/midi/midi_out.rs @@ -1,5 +1,9 @@ use crate::*; +#[tengri_proc::command(JackMidiOut)] +impl MidiOutputCommand { +} + impl>> HasMidiOuts for T { fn midi_outs (&self) -> &Vec { self.get() @@ -25,9 +29,19 @@ pub trait HasMidiOuts { data }) } - fn midi_out_add (&mut self, jack: &Jack) -> Usually<()> { +} + +/// Trail for thing that may gain new MIDI ports. +impl AddMidiOut for T { + fn midi_out_add (&mut self) -> Usually<()> { let index = self.midi_outs().len(); - self.midi_outs_mut().push(JackMidiOut::new(&jack, &format!("{index}/M"), &[])?); + let port = JackMidiOut::new(self.jack(), &format!("{index}/M"), &[])?; + self.midi_outs_mut().push(port); Ok(()) } } + +/// May create new MIDI output ports. +pub trait AddMidiOut { + fn midi_out_add (&mut self) -> Usually<()>; +}