use crate::*; /// Arranger. /// /// ``` /// let arranger = tek::Project::default(); /// ``` #[namespace(ClipPool ClipPool::eval)] #[derive(Default, Debug)] pub struct Project { /// 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, /// Contains all recently created clips. #[cfg(feature = "sequencer")] pub clips: Pool, /// Contains all recently created clips. #[cfg(feature = "sampler")] pub samples: Pool, /// 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, } impl Project { /// 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(), size_inner: Sizer(Arc::new(40.into()), Arc::new(25.into())), 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) } } impl_has!(Jack<'static>: |self: Project| self.jack); impl_has!(Sizer: |self: Project| self.size); impl_has!(Clock: |self: Project| self.clock); impl_has!(Selection: |self: Project| self.selection); impl_has!(Vec: |self: Project| self.scenes); impl_as_ref_opt!(Scene: |self: Project| self.selected_scene()); impl_as_mut_opt!(Scene: |self: Project| self.selected_scene_mut()); impl_as_ref!(Option: |self: Project| &self.editor); impl_as_mut!(Option: |self: Project| &mut self.editor); impl HasJack<'static> for Project { fn jack (&self) -> &Jack<'static> { &self.jack } } impl HasClip for Project { fn clip_slot (&self) -> Option> { match self.selection { Selection::Clip(index) => Some(self.clips.slot(index)), Selection::TrackClip { track, scene } if let Some(scene) = self.scenes.get(scene) => Some(scene.clips.slot(track)), _ => None } } } impl HasClipsSize for Project { fn clips_size (&self) -> &Sizer { &self.size_inner } } impl ScenesView for Project { 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 fn swap_value ( target: &mut T, value: &T, returned: impl Fn(T)->U ) -> Perhaps { if *target == *value { Ok(None) } else { let mut value = value.clone(); std::mem::swap(target, &mut value); Ok(Some(returned(value))) } } pub fn toggle_bool ( target: &mut bool, value: &Option, returned: impl Fn(Option)->U ) -> Perhaps { let mut value = value.unwrap_or(!*target); if value == *target { Ok(None) } else { std::mem::swap(target, &mut value); Ok(Some(returned(Some(value)))) } } /// Modes for clip pool #[derive(Debug, Clone)] pub enum PoolMode { /// Renaming a pattern Rename(usize, Arc), /// Editing the length of a pattern Length(usize, usize, ClipLengthFocus), /// Load clip from disk Import(usize, Browse), /// Save clip to disk Export(usize, Browse), } /// Focused field of `ClipLength` #[derive(Copy, Clone, Debug)] pub enum ClipLengthFocus { /// Editing the number of bars Bar, /// Editing the number of beats Beat, /// Editing the number of ticks Tick, } impl ClipLengthFocus { pub fn next (&mut self) { use ClipLengthFocus::*; *self = match self { Bar => Beat, Beat => Tick, Tick => Bar, } } pub fn prev (&mut self) { use ClipLengthFocus::*; *self = match self { Bar => Tick, Beat => Bar, Tick => Beat, } } } /// Displays and edits clip length. #[derive(Clone, Debug, Default)] pub struct ClipLength { /// Pulses per beat (quaver) pub ppq: usize, /// Beats per bar pub bpb: usize, /// Length of clip in pulses pub pulses: usize, /// Selected subdivision pub focus: Option, } impl ClipLength { pub fn _new (pulses: usize, focus: Option) -> Self { Self { ppq: PPQ, bpb: 4, pulses, focus } } pub fn bars (&self) -> usize { self.pulses / (self.bpb * self.ppq) } pub fn beats (&self) -> usize { (self.pulses % (self.bpb * self.ppq)) / self.ppq } pub fn ticks (&self) -> usize { self.pulses % self.ppq } } impl HasTrackScroll for &mut Project { fn track_scroll (&self) -> usize { self.track_scroll } fn tracks_last (&self) -> usize { self.track_last } fn tracks_last_mut (&mut self) -> &mut usize { &mut self.track_last } } impl HasTrackScroll for Project { fn track_scroll (&self) -> usize { self.track_scroll } fn tracks_last (&self) -> usize { self.track_last } fn tracks_last_mut (&mut self) -> &mut usize { &mut self.track_last } } impl HasSceneScroll for Project { fn scene_scroll (&self) -> usize { self.scene_scroll } } def_sizes_iter!(PortsSizes => Arc, [Connect]); impl_has!(Vec: |self: Project| self.midi_ins); impl_has!(Vec: |self: Project| self.midi_outs); impl_has!(Vec: |self: App|self.project.midi_ins); impl_has!(Vec: |self: App|self.project.midi_outs); pub struct Junction(T); impl Draw for Junction { fn draw (&self, to: &mut Tui) -> Drawn { T::KIND.draw(to) } } impl_as_ref_opt!(Track: |self: Project| self.selected_track()); impl_as_mut_opt!(Track: |self: Project| self.selected_track_mut()); impl_as_ref_opt!(Track: |self: App| self.project.as_ref_opt()); impl_as_mut_opt!(Track: |self: App| self.project.as_mut_opt()); /// 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 clip is selected. Clip(usize), /// A sample is selected. Sample(usize), /// A scene is selected. #[cfg(feature = "scene")] Scene(usize), /// A track is selected. #[cfg(feature = "track")] Track(usize), /// A clip slot (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 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.clips.get(*track) { None => format!("T{track} S{scene}: Empty"), Some(clip) => format!("T{track} S{scene} C{}", clip.as_ref().map(|clip|clip.read().name.clone()).unwrap_or_default()), }, _ => format!("T{track} S{scene}: Empty"), }, _ => todo!() }).into() } } #[cfg(feature = "scene")] impl Selection { pub fn scene (&self) -> Option { use Selection::*; match self { Scene(scene) | TrackClip { scene, .. } => Some(*scene), _ => None } } 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!(), } } 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!() } } 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")] impl Selection { pub fn track (&self) -> Option { use Selection::*; if let Track(track)|TrackClip{track,..}|TrackInput{track,..}|TrackOutput{track,..}|TrackDevice{track,..} = self { Some(*track) } else { None } } 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!(), } } 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!() } } 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 SelectionModel for T {} pub trait SelectionModel: HasSelection { /// 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.read().get(self.selection().track()?).cloned() } } //impl_from!(Pool: |clip:&Arc>|{ //let model = Self::default(); //model.clips.try_write().unwrap().push(clip.clone()); //model.clip.store(1, Relaxed); //model //}); //impl Pool { //pub fn clip_index (&self) -> usize { //self.clip.load(Relaxed) //} //pub fn set_clip_index (&self, value: usize) { //self.clip.store(value, Relaxed); //} //pub fn mode (&self) -> &Option { //&self.mode //} //pub fn mode_mut (&mut self) -> &mut Option { //&mut self.mode //} //pub fn begin_import (&mut self) -> Usually<()> { //*self.mode_mut() = Some(PoolMode::Import( //self.clip_index(), //Browse::new(None)? //)); //Ok(()) //} //pub fn begin_export (&mut self) -> Usually<()> { //*self.mode_mut() = Some(PoolMode::Export( //self.clip_index(), //Browse::new(None)? //)); //Ok(()) //} //pub fn new_clip (&self) -> MidiClip { //MidiClip::new("Clip", true, 4 * PPQ, None, Some(ItemTheme::random())) //} //pub fn cloned_clip (&self) -> MidiClip { //let index = self.clip_index(); //let mut clip = self.clips.read()[index].read().duplicate(); //clip.color = ItemTheme::random_near(clip.color, 0.25); //clip //} //pub fn add_new_clip (&self) -> (usize, Arc>) { //let clip = Arc::new(RwLock::new(self.new_clip())); //let index = { //let mut clips = self.clips.try_write().unwrap(); //clips.push(clip.clone()); //clips.len().saturating_sub(1) //}; //self.clip.store(index, Relaxed); //(index, clip) //} //pub fn delete_clip (&mut self, clip: &MidiClip) -> bool { //let index = self.clips.try_read().unwrap().iter().position(|x|*x.try_read().unwrap()==*clip); //if let Some(index) = index { //self.clips.try_write().unwrap().remove(index); //return true //} //false //} //fn _clip_new (&self) -> MidiClip { self.new_clip() } //fn _clip_cloned (&self) -> MidiClip { self.cloned_clip() } //fn _clip_index_current (&self) -> usize { 0 } //fn _clip_index_after (&self) -> usize { 0 } //fn _clip_index_previous (&self) -> usize { 0 } //fn _clip_index_next (&self) -> usize { 0 } //fn _color_random (&self) -> ItemColor { ItemColor::random() } //}