use crate::*; /// Arranger. /// /// ``` /// let arranger = tek::Arrangement::default(); /// ``` #[derive(Default, Debug)] pub struct Arrangement { /// JACK client handle. pub jack: Jack<'static>, /// Project name. pub name: Arc, /// Base color. pub color: ItemTheme, /// FIXME a render of the project arrangement, redrawn on update. /// TODO rename to "render_cache" or smth pub arranger: Arc>, /// Display size pub size: Sizer, /// Display size of clips area pub size_inner: Sizer, /// Selected UI element pub selection: Selection, /// Source of time #[cfg(feature = "clock")] pub clock: Clock, /// Allows one MIDI clip to be edited #[cfg(feature = "editor")] pub editor: Option, /// List of global midi inputs #[cfg(feature = "port")] pub midi_ins: Vec, /// List of global midi outputs #[cfg(feature = "port")] pub midi_outs: Vec, /// List of global audio inputs #[cfg(feature = "port")] pub audio_ins: Vec, /// List of global audio outputs #[cfg(feature = "port")] pub audio_outs: Vec, /// Last track number (to avoid duplicate port names) #[cfg(feature = "track")] pub track_last: usize, /// List of tracks #[cfg(feature = "track")] pub tracks: Vec, /// Scroll offset of tracks #[cfg(feature = "track")] pub track_scroll: usize, /// List of scenes #[cfg(feature = "scene")] pub scenes: Vec, /// Scroll offset of scenes #[cfg(feature = "scene")] pub scene_scroll: usize, } /// Represents the current user selection in the arranger #[derive(PartialEq, Clone, Copy, Debug, Default)] pub enum Selection { #[default] /// Nothing is selected Nothing, /// The whole mix is selected Mix, /// A MIDI input is selected. Input(usize), /// A MIDI output is selected. Output(usize), /// A scene is selected. #[cfg(feature = "scene")] Scene(usize), /// A track is selected. #[cfg(feature = "track")] Track(usize), /// A clip (track × scene) is selected. #[cfg(feature = "track")] TrackClip { track: usize, scene: usize }, /// A track's MIDI input connection is selected. #[cfg(feature = "track")] TrackInput { track: usize, port: usize }, /// A track's MIDI output connection is selected. #[cfg(feature = "track")] TrackOutput { track: usize, port: usize }, /// A track device slot is selected. #[cfg(feature = "track")] TrackDevice { track: usize, device: usize }, } impl Arrangement { /// Create a new arrangement. pub fn new ( jack: &Jack<'static>, name: Arc, clock: Clock, tracks: impl Iterator, scenes: impl Iterator, midi_ins: impl Iterator, midi_outs: impl Iterator, audio_ins: impl Iterator, audio_outs: impl Iterator, ) -> Self { Self { jack: jack.clone(), color: ItemTheme::random(), selection: Selection::TrackClip { track: 0, scene: 0 }, tracks: tracks.collect(), scenes: scenes.collect(), midi_ins: midi_ins.collect(), midi_outs: midi_outs.collect(), audio_ins: audio_ins.collect(), audio_outs: audio_outs.collect(), clock, name, ..Default::default() } } /// Width of display pub fn w (&self) -> u16 { self.size.w() as u16 } /// Width allocated for sidebar. pub fn w_sidebar (&self, is_editing: bool) -> u16 { self.w() / if is_editing { 16 } else { 8 } as u16 } /// Width available to display tracks. pub fn w_tracks_area (&self, is_editing: bool) -> u16 { self.w().saturating_sub(self.w_sidebar(is_editing)) } /// Height of display pub fn h (&self) -> u16 { self.size.h() as u16 } /// Height taken by visible device slots. pub fn h_devices (&self) -> u16 { 2 //1 + self.devices_with_sizes().last().map(|(_, _, _, _, y)|y as u16).unwrap_or(0) } /// Get the first sampler of the active track #[cfg(feature = "sampler")] pub fn sampler (&self) -> Option<&Sampler> { self.selected_track()?.sampler(0) } /// Get the first sampler of the active track #[cfg(feature = "sampler")] pub fn sampler_mut (&mut self) -> Option<&mut Sampler> { self.selected_track_mut()?.sampler_mut(0) } } impl Selection { pub fn describe ( &self, #[cfg(feature = "track")] tracks: &[Track], #[cfg(feature = "scene")] scenes: &[Scene], ) -> Arc { use Selection::*; format!("{}", match self { Mix => "Everything".to_string(), #[cfg(feature = "scene")] Scene(s) => scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name)).unwrap_or_else(||"S??".into()), #[cfg(feature = "track")] 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{track} S{scene}: Empty"), }, _ => todo!() }).into() } #[cfg(feature = "scene")] pub fn scene (&self) -> Option { use Selection::*; match self { Scene(scene) | TrackClip { scene, .. } => Some(*scene), _ => None } } #[cfg(feature = "scene")] pub fn select_scene (&self, scene_count: usize) -> Self { use Selection::*; match self { Mix | Track(_) => Scene(0), Scene(s) => Scene((s + 1) % scene_count), TrackClip { scene, .. } => Track(*scene), _ => todo!(), } } #[cfg(feature = "scene")] pub fn select_scene_next (&self, len: usize) -> Self { use Selection::*; match self { Mix => Scene(0), Track(t) => TrackClip { track: *t, scene: 0 }, Scene(s) => if s + 1 < len { Scene(s + 1) } else { Mix }, TrackClip { track, scene } => if scene + 1 < len { TrackClip { track: *track, scene: scene + 1 } } else { Track(*track) }, _ => todo!() } } #[cfg(feature = "scene")] pub fn select_scene_prev (&self) -> Self { use Selection::*; match self { Mix | Scene(0) => Mix, Scene(s) => Scene(s - 1), Track(t) => Track(*t), TrackClip { track, scene: 0 } => Track(*track), TrackClip { track, scene } => TrackClip { track: *track, scene: scene - 1 }, _ => todo!() } } #[cfg(feature = "track")] pub fn track (&self) -> Option { use Selection::*; if let Track(track)|TrackClip{track,..}|TrackInput{track,..}|TrackOutput{track,..}|TrackDevice{track,..} = self { Some(*track) } else { None } } #[cfg(feature = "track")] pub fn select_track (&self, track_count: usize) -> Self { use Selection::*; match self { Mix => Track(0), Scene(_) => Mix, Track(t) => Track((t + 1) % track_count), TrackClip { track, .. } => Track(*track), _ => todo!(), } } #[cfg(feature = "track")] pub fn select_track_next (&self, len: usize) -> Self { use Selection::*; match self { Mix => Track(0), Scene(s) => TrackClip { track: 0, scene: *s }, Track(t) => if t + 1 < len { Track(t + 1) } else { Mix }, TrackClip {track, scene} => if track + 1 < len { TrackClip { track: track + 1, scene: *scene } } else { Scene(*scene) }, _ => todo!() } } #[cfg(feature = "track")] pub fn select_track_prev (&self) -> Self { use Selection::*; match self { Mix => Mix, Scene(s) => Scene(*s), Track(0) => Mix, Track(t) => Track(t - 1), TrackClip { track: 0, scene } => Scene(*scene), TrackClip { track: t, scene } => TrackClip { track: t - 1, scene: *scene }, _ => todo!() } } } impl +AsMut> HasSelection for T {} pub trait HasSelection: AsRef + AsMut { fn selection (&self) -> &Selection { self.as_ref() } fn selection_mut (&mut self) -> &mut Selection { self.as_mut() } /// Get the active track #[cfg(feature = "track")] fn selected_track (&self) -> Option<&Track> where Self: HasTracks { let index = self.selection().track()?; self.tracks().get(index) } /// Get a mutable reference to the active track #[cfg(feature = "track")] fn selected_track_mut (&mut self) -> Option<&mut Track> where Self: HasTracks { let index = self.selection().track()?; self.tracks_mut().get_mut(index) } /// Get the active scene #[cfg(feature = "scene")] fn selected_scene (&self) -> Option<&Scene> where Self: HasScenes { let index = self.selection().scene()?; self.scenes().get(index) } /// Get a mutable reference to the active scene #[cfg(feature = "scene")] fn selected_scene_mut (&mut self) -> Option<&mut Scene> where Self: HasScenes { let index = self.selection().scene()?; self.scenes_mut().get_mut(index) } /// Get the active clip #[cfg(feature = "clip")] fn selected_clip (&self) -> Option>> where Self: HasScenes + HasTracks { self.selected_scene()?.clips.get(self.selection().track()?)?.clone() } } impl HasJack<'static> for Arrangement { fn jack (&self) -> &Jack<'static> { &self.jack } } impl_has!(Jack<'static>: |self: Arrangement| self.jack); impl_has!(Sizer: |self: Arrangement| self.size); impl_has!(Vec: |self: Arrangement| self.midi_ins); impl_has!(Vec: |self: Arrangement| self.midi_outs); impl_has!(Clock: |self: Arrangement| self.clock); impl_has!(Selection: |self: Arrangement| self.selection); impl_as_ref_opt!(MidiEditor: |self: Arrangement| self.editor.as_ref()); impl_as_mut_opt!(MidiEditor: |self: Arrangement| self.editor.as_mut()); pub trait HasClipsSize { fn clips_size (&self) -> &Sizer; } pub trait ClipsView: TracksView + ScenesView { /// Draw clips per scene fn view_scenes_clips <'a> (&'a self) -> impl Draw + 'a { view_scenes_clips( ||self.scenes_with_sizes(), self.tracks_with_sizes(), self.selection(), self.editor(), self.clips_size(), self.is_editing(), ) } } impl HasClipsSize for App { fn clips_size (&self) -> &Sizer { &self.project.size_inner } } impl HasClipsSize for Arrangement { fn clips_size (&self) -> &Sizer { &self.size_inner } } /// TODO: Preserve the generic passthru syntax; /// remove this macro (only used twice) and potentially the trait. #[macro_export] macro_rules! impl_has_clips { (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { impl $(<$($L),*$($T $(: $U)?),*>)? HasClips for $Struct $(<$($L),*$($T),*>)? { fn clips <'a> (&'a $self) -> std::sync::RwLockReadGuard<'a, ClipPool> { $cb.read().unwrap() } fn clips_mut <'a> (&'a $self) -> std::sync::RwLockWriteGuard<'a, ClipPool> { $cb.write().unwrap() } } } } #[macro_export] macro_rules! has_clip { (|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?|$cb:expr) => { impl $(<$($L),*$($T $(: $U)?),*>)? HasMidiClip for $Struct $(<$($L),*$($T),*>)? { fn clip (&$self) -> Option>> { $cb } } } } def_command!(ClipCommand: |clip: MidiClip| { SetColor { color: Option } => { //(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!() }, SetLoop { looping: Option } => { //(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 Arrangement { /// Put a clip in a slot pub fn clip_put ( &mut self, track: usize, scene: usize, clip: Option>> ) -> Option>> { let old = self.scenes[scene].clips[track].clone(); self.scenes[scene].clips[track] = clip; old } /// Change the color of a clip, returning the previous one pub fn clip_set_color ( &self, track: usize, scene: usize, color: ItemTheme ) -> Option { self.scenes[scene].clips[track].as_ref().map(|clip|{ let mut clip = clip.write().unwrap(); let old = clip.color.clone(); clip.color = color.clone(); panic!("{color:?} {old:?}"); //old }) } /// Toggle looping for the active clip pub fn toggle_loop (&mut self) { if let Some(clip) = self.selected_clip() { clip.write().unwrap().toggle_loop() } } } /// Default scene height. pub const H_SCENE: usize = 2; /// Default editor height. pub const H_EDITOR: usize = 15; /// A scene consists of a set of clips to play together. /// /// ``` /// let scene: tek::Scene = Default::default(); /// let _ = scene.pulses(); /// let _ = scene.is_playing(&[]); /// ``` #[derive(Debug, Default)] pub struct Scene { /// Name of scene pub name: Arc, /// Identifying color of scene pub color: ItemTheme, /// Clips in scene, one per track pub clips: Vec>>>, } impl Scene { /// Returns the pulse length of the longest clip in the scene pub fn pulses (&self) -> usize { self.clips.iter().fold(0, |a, p|{ a.max(p.as_ref().map(|q|q.read().unwrap().length).unwrap_or(0)) }) } /// Returns true if all clips in the scene are /// currently playing on the given collection of tracks. pub fn is_playing (&self, tracks: &[Track]) -> bool { self.clips.iter().any(|clip|clip.is_some()) && self.clips.iter().enumerate() .all(|(track_index, clip)|match clip { Some(c) => tracks .get(track_index) .map(|track|{ if let Some((_, Some(clip))) = track.sequencer().play_clip() { *clip.read().unwrap() == *c.read().unwrap() } else { false } }) .unwrap_or(false), None => true }) } pub fn clip (&self, index: usize) -> Option<&Arc>> { match self.clips.get(index) { Some(Some(clip)) => Some(clip), _ => None } } 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!() } } pub trait ScenesView: HasEditor + HasSelection + HasSceneScroll + HasClipsSize + Send + Sync { fn h_scenes (&self) -> u16; fn w_side (&self) -> u16; fn w_mid (&self) -> u16; fn view_scenes_names (&self) -> impl Draw { let select = self.selection(); let editor = self.editor(); let editing = self.is_editing(); draw(move |to: &mut Tui|{ for (index, scene, ..) in self.scenes_with_sizes() { view_scene_name(select, editor, index, scene, editing).draw(to)?; } Ok(Some(XYWH(1, 1, 1, 1))) }) .exact_w(20) } fn scenes_with_sizes (&self) -> impl ScenesSizes<'_> { let mut y = 0; self.scenes().iter().enumerate().skip(self.scene_scroll()).map_while(move|(s, scene)|{ let height = if self.selection().scene() == Some(s) && self.editor().is_some() { 8 } else { H_SCENE }; if y + height <= self.clips_size().h() as usize { let data = (s, scene, y, y + height); y += height; Some(data) } else { None } }) } } pub trait HasSceneScroll: HasScenes { fn scene_scroll (&self) -> usize; } pub trait HasScene: AsRefOpt + AsMutOpt { fn scene_mut (&mut self) -> Option<&mut Scene> { self.as_mut_opt() } fn scene (&self) -> Option<&Scene> { self.as_ref_opt() } } pub trait HasScenes: AsRef> + AsMut> { fn scenes (&self) -> &Vec { self.as_ref() } fn scenes_mut (&mut self) -> &mut Vec { self.as_mut() } /// Generate the default name for a new scene fn scene_default_name (&self) -> Arc { format!("s{:3>}", self.scenes().len() + 1).into() } fn scene_longest_name (&self) -> usize { self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max) } /// Add multiple scenes fn scenes_add (&mut self, n: usize) -> Usually<()> where Self: HasTracks { 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)> where Self: HasTracks { 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])) } } impl HasSceneScroll for Arrangement { fn scene_scroll (&self) -> usize { self.scene_scroll } } impl HasSceneScroll for App { fn scene_scroll (&self) -> usize { self.project.scene_scroll() } } impl ScenesView for Arrangement { fn h_scenes (&self) -> u16 { (self.size.h() as u16).saturating_sub(20) } fn w_side (&self) -> u16 { (self.size.w() as u16 * 2 / 10).max(20) } fn w_mid (&self) -> u16 { (self.size.w() as u16).saturating_sub(2 * self.w_side()).max(40) } } pub type SceneWith<'a, T> = (usize, &'a Scene, usize, usize, T); def_command!(SceneCommand: |scene: Scene| { SetSize { size: usize } => { todo!() }, SetZoom { size: usize } => { todo!() }, SetName { name: Arc } => swap_value(&mut scene.name, name, |name|Self::SetName{name}), SetColor { color: ItemTheme } => swap_value(&mut scene.color, color, |color|Self::SetColor{color}), }); #[cfg(all(feature = "select"))] impl_as_ref_opt!(Scene: |self: App| self.project.as_ref_opt()); #[cfg(all(feature = "select"))] impl_as_mut_opt!(Scene: |self: App| self.project.as_mut_opt()); #[cfg(all(feature = "select"))] impl_as_ref_opt!(Scene: |self: Arrangement| self.selected_scene()); #[cfg(all(feature = "select"))] impl_as_mut_opt!(Scene: |self: Arrangement| self.selected_scene_mut()); impl_has!(Vec: |self: Arrangement| self.scenes); impl ClipsView for T {} impl>+AsMut>> HasScenes for T {} impl+AsMutOpt+Send+Sync> HasScene for T {} impl ScenesView for App { fn w_mid (&self) -> u16 { (self.size.w() as u16).saturating_sub(self.w_side()) } fn w_side (&self) -> u16 { 20 } fn h_scenes (&self) -> u16 { (self.size.h() as u16).saturating_sub(20) } } /// A track consists of a sequencer and zero or more devices chained after it. /// /// ``` /// let track: tek::Track = Default::default(); /// ``` #[derive(Debug, Default)] pub struct Track { /// Name of track pub name: Arc, /// Identifying color of track pub color: ItemTheme, /// Preferred width of track column pub width: usize, /// MIDI sequencer state pub sequencer: Sequencer, /// Device chain pub devices: Vec, } impl Track { /// Create a new track with only the default [Sequencer]. pub fn new ( name: &impl AsRef, color: Option, jack: &Jack<'static>, clock: Option<&Clock>, clip: Option<&Arc>>, midi_from: &[Connect], midi_to: &[Connect], ) -> Usually { Ok(Self { name: name.as_ref().into(), color: color.unwrap_or_default(), sequencer: Sequencer::new( format!("{}/sequencer", name.as_ref()), jack, clock, clip, midi_from, midi_to )?, ..Default::default() }) } pub fn audio_ins (&self) -> &[AudioInput] { self.devices.first().map(|x|x.audio_ins()).unwrap_or_default() } pub fn audio_outs (&self) -> &[AudioOutput] { self.devices.last().map(|x|x.audio_outs()).unwrap_or_default() } 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!() } pub fn per <'a, T: Draw + 'a, U: TracksSizes<'a>> ( tracks: impl Fn() -> U + Send + Sync + 'a, callback: impl Fn(usize, &'a Track)->T + Send + Sync + 'a ) -> impl Draw + 'a { view_track_per(tracks, callback) } } #[cfg(feature = "sampler")] impl Track { /// Create a new track connecting the [Sequencer] to a [Sampler]. pub fn new_with_sampler ( name: &impl AsRef, color: Option, jack: &Jack<'static>, clock: Option<&Clock>, clip: Option<&Arc>>, midi_from: &[Connect], midi_to: &[Connect], audio_from: &[&[Connect];2], audio_to: &[&[Connect];2], ) -> Usually { let mut track = Self::new(name, color, jack, clock, clip, midi_from, midi_to)?; let client_name = jack.with_client(|c|c.name().to_string()); let port_name = track.sequencer.midi_outs[0].port_name(); let connect = [Connect::exact(format!("{client_name}:{}", port_name))]; track.devices.push(Device::Sampler(Sampler::new( jack, &format!("{}/sampler", name.as_ref()), &connect, audio_from, audio_to )?)); Ok(track) } pub fn sampler (&self, mut nth: usize) -> Option<&Sampler> { for device in self.devices.iter() { match device { Device::Sampler(s) => if nth == 0 { return Some(s); } else { nth -= 1; }, _ => {} } } None } pub fn sampler_mut (&mut self, mut nth: usize) -> Option<&mut Sampler> { for device in self.devices.iter_mut() { match device { Device::Sampler(s) => if nth == 0 { return Some(s); } else { nth -= 1; }, _ => {} } } None } } impl HasWidth for Track { const MIN_WIDTH: usize = 9; fn width_inc (&mut self) { self.width += 1; } fn width_dec (&mut self) { if self.width > Track::MIN_WIDTH { self.width -= 1; } } } pub trait HasTracks: AsRef> + AsMut> { fn tracks (&self) -> &Vec { self.as_ref() } fn tracks_mut (&mut self) -> &mut Vec { self.as_mut() } /// Run audio callbacks for every track and every device fn process_tracks (&mut self, client: &Client, scope: &ProcessScope) -> Control { for track in self.tracks_mut().iter_mut() { if Control::Quit == Audio::process(&mut track.sequencer, client, scope) { return Control::Quit } for device in track.devices.iter_mut() { if Control::Quit == DeviceAudio(device).process(client, scope) { return Control::Quit } } } Control::Continue } fn track_longest_name (&self) -> usize { self.tracks().iter().map(|s|s.name.len()).fold(0, usize::max) } /// Stop all playing clips fn tracks_stop_all (&mut self) { for track in self.tracks_mut().iter_mut() { track.sequencer.enqueue_next(None); } } /// Stop all playing clips 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.as_ref()); } } else { for track in self.tracks_mut().iter_mut() { track.sequencer.enqueue_next(None); } } } /// Spacing between tracks. const TRACK_SPACING: usize = 0; } pub trait HasTrack: AsRefOpt + AsMutOpt { fn track (&self) -> Option<&Track> { self.as_ref_opt() } fn track_mut (&mut self) -> Option<&mut Track> { self.as_mut_opt() } #[cfg(feature = "port")] fn view_midi_ins_status <'a> (&'a self, theme: ItemTheme) -> impl Draw + 'a { view_midi_ins_status(theme, self.track()) } #[cfg(feature = "port")] fn view_midi_outs_status (&self, theme: ItemTheme) -> impl Draw + '_ { view_midi_outs_status(theme, self.track()) } #[cfg(feature = "port")] fn view_audio_ins_status (&self, theme: ItemTheme) -> impl Draw { view_audio_ins_status(theme, self.track()) } #[cfg(feature = "port")] fn view_audio_outs_status (&self, theme: ItemTheme) -> impl Draw { view_audio_outs_status(theme, self.track()) } } pub trait HasTrackScroll: HasTracks { fn track_scroll (&self) -> usize; } pub trait TracksView: ScenesView + HasMidiIns + HasMidiOuts + HasTrackScroll { /// Draw name of each track fn view_track_names (&self, theme: ItemTheme) -> impl Draw { let track_count = self.tracks().len(); let scene_count = self.scenes().len(); let selected = self.selection(); let button = south( button_3("t", "rack ", format!("{}{track_count}", selected.track() .map(|track|format!("{track}/")).unwrap_or_default()), false), button_3("s", "cene ", format!("{}{scene_count}", selected.scene() .map(|scene|format!("{scene}/")).unwrap_or_default()), false)); let button_2 = south( button_2("T", "+", false), button_2("S", "+", false)); view_track_row_section(theme, button, button_2, bg(theme.darker.term, draw(|to: &mut Tui|{ for (index, track, x1, _x2) in self.tracks_with_sizes() { let b = if selected.track() == Some(index) { track.color.light.term } else { track.color.base.term }; bg(b, south(east( format!("·t{index:02} "), fg(Rgb(255, 255, 255), bold(true, &track.name)) ).align_nw().full_w(), "")) .exact_w(track_width(index, track)) .push_x(x1 as u16) .draw(to)?; } Ok(Some(XYWH(0, 0, 0, 0))) }).exact_h(2))) } /// Draw outputs per track fn view_track_outputs <'a> (&'a self, theme: ItemTheme, _h: u16) -> impl Draw { view_track_row_section(theme, south(button_2("o", "utput", false).align_w().full_w(), draw(|to: &mut Tui|{ for port in self.midi_outs().iter() { let _ = port.port_name().align_w().full_w().draw(to)?; } Ok(Some(XYWH(0, 0, 0, 0))) })), button_2("O", "+", false), bg(theme.darker.term, draw(|to: &mut Tui|{ for (index, track, _x1, _x2) in self.tracks_with_sizes() { let f = Rgb(255, 255, 255); let b = track.color.dark.term; let iter = ||track.sequencer.midi_outs.iter(); let draw = |port: &MidiOutput, _|fg(f, bg(b, format!("·o{index:02} {}", port.port_name()).full_w().align_w()).exact_h(1)); iter_south(iter, draw).full_h().align_nw() .exact_w(track_width(index, track)) .draw(to)?; } Ok(Some(XYWH(0, 0, 0, 0))) }).align_w())) } /// Draw inputs per track fn view_track_inputs <'a> (&'a self, theme: ItemTheme) -> impl Draw { let mut height = 0u16; for track in self.tracks().iter() { height = height.max(track.sequencer.midi_ins.len() as u16); } view_track_row_section(theme, button_2("i", "nput", false), button_2("I", "+", false), bg(theme.darker.term, draw(move|to: &mut Tui|{ for (index, track, _x1, _x2) in self.tracks_with_sizes() { south( bg(track.color.base.term, east!( either(track.sequencer.monitoring, fg(Green, "●mon "), "·mon "), either(track.sequencer.recording, fg(Red, "●rec "), "·rec "), either(track.sequencer.overdub, fg(Yellow, "●dub "), "·dub "), ).align_w().full_w()), iter_south(||track.sequencer.midi_ins.iter(), |port, _|fg_bg(Rgb(255, 255, 255), track.color.dark.term, format!("·i{index:02} {}", port.port_name()).align_w().full_w())) ).align_nw().exact_wh(track_width(index, track), height + 1).draw(to)?; } Ok(Some(XYWH(0, 0, 0, 0))) }).align_w())) } /// Iterate over tracks with their corresponding sizes. fn tracks_with_sizes (&self) -> impl TracksSizes<'_> { let _editor_width = self.editor().map(|e|e.size.w()); let _active_track = self.selection().track(); let mut x = 0; let w = self.clips_size().w() as usize; self.tracks().iter().enumerate().map_while(move |(index, track)|{ let width = track.width.max(8); if x + width < w { let data = (index, track, x, x + width); x += width + Self::TRACK_SPACING; Some(data) } else { None } }) } } impl HasTrackScroll for Arrangement { fn track_scroll (&self) -> usize { self.track_scroll } } def_command!(TrackCommand: |track: Track| { Stop => { track.sequencer.enqueue_next(None); Ok(None) }, SetRec { rec: Option } => toggle_bool(&mut track.sequencer.recording, rec, |rec|Self::SetRec { rec }), SetMon { mon: Option } => toggle_bool(&mut track.sequencer.monitoring, mon, |mon|Self::SetMon { mon }), SetMute { mute: Option } => todo!(), SetSolo { solo: Option } => todo!(), SetSize { size: usize } => todo!(), SetZoom { zoom: usize } => todo!(), SetName { name: Arc } => swap_value(&mut track.name, name, |name|Self::SetName { name }), SetColor { color: ItemTheme } => swap_value(&mut track.color, color, |color|Self::SetColor { color }), }); impl>+AsMut>> HasTracks for T {} impl+AsMutOpt+Send+Sync> HasTrack for T {} impl TracksView for T {} impl_has!(Vec: |self: Arrangement| self.tracks); impl_as_ref_opt!(Track: |self: Arrangement| self.selected_track()); impl_as_mut_opt!(Track: |self: Arrangement| self.selected_track_mut()); impl_as_ref!(Vec: |self: App| self.project.as_ref()); impl_as_mut!(Vec: |self: App| self.project.as_mut()); #[cfg(feature = "select")] impl_as_ref_opt!(Track: |self: App| self.project.as_ref_opt()); #[cfg(feature = "select")] impl_as_mut_opt!(Track: |self: App| self.project.as_mut_opt()); impl Arrangement { pub fn view_inputs (&self, _theme: ItemTheme) -> impl Draw + '_ { let title_1 = button_3("i", "nput ", format!("{}", self.midi_ins().len()), false).align_w().exact_wh(20, 1); let title_2 = button_2("I", "+", false).exact_wh(4, 1); east(title_1, west(title_2, draw(move|to: &mut Tui|{ for (_index, track, x1, _x2) in self.tracks_with_sizes() { let _ = south( bg(track.color.dark.term, east!( either(track.sequencer.monitoring, fg(Green, "mon "), "mon "), either(track.sequencer.recording, fg(Red, "rec "), "rec "), either(track.sequencer.overdub, fg(Yellow, "dub "), "dub "), ).exact_w(track.width as u16)).align_w().push_x(x1 as u16), draw(move |to: &mut Tui|{ for (index, port) in self.midi_ins().as_slice().iter().enumerate() { let _ = east( east( " ● ", bold(true, fg(Rgb(255,255,255), port.port_name())) ).align_w().exact_w(20), west( ().exact_w(4), bg(track.color.darker.term, east!( either(track.sequencer.monitoring, fg(Green, " ● "), " · "), either(track.sequencer.recording, fg(Red, " ● "), " · "), either(track.sequencer.overdub, fg(Yellow, " ● "), " · "), ).exact_w(track.width as u16).align_w()) ) ).push_x(index as u16 * 10).exact_h(1).draw(to)?; } todo!() }) ).draw(to)?; } Ok(Some(to.area())) }))) } pub fn view_outputs (&self, theme: ItemTheme) -> impl Draw { let height = self.outputs_height(); let list = south( button_3( "o", "utput", format!("{}", self.midi_outs().len()), false ).align_w().full_w().exact_h(1), draw(|to: &mut Tui|{ for (_index, port) in self.midi_outs().iter().enumerate() { east( east(" ● ", fg(Rgb(255,255,255), bold(true, port.port_name()))).align_w(), format!("{}/{} ", port.port().get_connections().len(), port.connections.len()).align_e().full_w().exact_h(1)).full_w().draw(to)?; for (index, conn) in port.connections.iter().enumerate() { format!(" c{index:02}{}", conn.info()).align_w().full_w().exact_h(1).draw(to)?; } } todo!(); }).align_nw().full_wh().exact_h(height - 1) ); view_track_row_section(theme, list, button_2("O", "+", false), bg(theme.darker.term, draw(|to: &mut Tui|{ for (index, track, _x1, _x2) in self.tracks_with_sizes() { let _ = draw(|to: &mut Tui|{ east( either(true, fg(Green, "play "), "play "), either(false, fg(Yellow, "solo "), "solo "), ).align_w().exact_h(1).draw(to)?; for (_index, port) in self.midi_outs().iter().enumerate() { east( either(true, fg(Green, " ● "), " · "), either(false, fg(Yellow, " ● "), " · "), ).align_w().exact_h(1).draw(to)?; for (_index, _conn) in port.connections.iter().enumerate() { "".full_w().exact_h(1).draw(to)?; } } todo!() }).exact_w(track_width(index, track)).draw(to)?; } todo!() }).align_w().full_w())).exact_h(height) } pub fn view_track_devices (&self, theme: ItemTheme) -> impl Draw { let height = self.devices_height(); view_track_row_section(theme, button_3("d", "evice", format!("{}", self.track().map(|t|t.devices.len()).unwrap_or(0)), false), button_2("D", "+", false), iter_once(self.tracks_with_sizes(), move|(_, track, _x1, _x2), index|bg( track.color.dark.term, iter_south(move||0..height, |_, _index|fg_bg( ItemTheme::G[32].lightest.term, ItemTheme::G[32].dark.term, format!(" · {}", "--").align_nw() ).exact_wh(track.width as u16, 2) ).align_nw()).exact_wh( Some(track_width(index, track)), Some(height + 1), ))) } fn devices_height (&self) -> u16 { let mut h = 2; for track in self.tracks().iter() { h = h.max(track.devices.len() * 2); } h as u16 } fn outputs_height (&self) -> u16 { let mut h = 1; for output in self.midi_outs().iter() { h += 1 + output.connections.len(); } h as u16 } /// Add multiple tracks pub fn tracks_add ( &mut self, count: usize, width: Option, mins: &[Connect], mouts: &[Connect], ) -> Usually<()> { let track_color_1 = ItemColor::random(); let track_color_2 = ItemColor::random(); for i in 0..count { let color = track_color_1.mix(track_color_2, i as f32 / count as f32).into(); let track = self.track_add(None, Some(color), mins, mouts)?.1; if let Some(width) = width { track.width = width; } } Ok(()) } /// Add a track pub fn track_add ( &mut self, name: Option<&str>, color: Option, mins: &[Connect], mouts: &[Connect], ) -> Usually<(usize, &mut Track)> { let name: Arc = name.map_or_else( ||format!("trk{:02}", self.track_last).into(), |x|x.to_string().into() ); self.track_last += 1; let track = Track { width: (name.len() + 2).max(12), color: color.unwrap_or_else(ItemTheme::random), sequencer: Sequencer::new( &format!("{name}"), self.jack(), Some(self.clock()), None, mins, mouts )?, name, ..Default::default() }; self.tracks_mut().push(track); let len = self.tracks().len(); let index = len - 1; for scene in self.scenes_mut().iter_mut() { while scene.clips.len() < len { scene.clips.push(None); } } Ok((index, &mut self.tracks_mut()[index])) } } impl HasTrackScroll for App { fn track_scroll (&self) -> usize { self.project.track_scroll() } } pub(crate) fn track_width (_index: usize, track: &Track) -> u16 { track.width as u16 } /// Define a type alias for iterators of sized items (columns). macro_rules! def_sizes_iter { ($Type:ident => $($Item:ty),+) => { pub trait $Type<'a>: Iterator + Send + Sync + 'a {} impl<'a, T: Iterator + Send + Sync + 'a> $Type<'a> for T {} } } def_sizes_iter!(PortsSizes => Arc, [Connect]); def_sizes_iter!(ScenesSizes => Scene); def_sizes_iter!(TracksSizes => Track);