use crate::*; #[derive(Default, Debug)] pub struct Arrangement { /// Project name. pub name: Arc, /// Base color. pub color: ItemTheme, /// Jack client handle pub jack: Jack, /// Source of time pub clock: Clock, /// Allows one MIDI clip to be edited pub editor: Option, /// List of global midi inputs pub midi_ins: Vec, /// List of global midi outputs pub midi_outs: Vec, /// List of global audio inputs pub audio_ins: Vec, /// List of global audio outputs pub audio_outs: Vec, /// Last track number (to avoid duplicate port names) pub track_last: usize, /// List of tracks pub tracks: Vec, /// Scroll offset of tracks pub track_scroll: usize, /// List of scenes pub scenes: Vec, /// Scroll offset of scenes pub scene_scroll: usize, /// Selected UI element pub selection: Selection, /// Contains a render of the project arrangement, redrawn on update. /// TODO rename to "render_cache" or smth pub arranger: Arc>, /// Display size pub size: Measure, } has!(Jack: |self: Arrangement|self.jack); has!(Clock: |self: Arrangement|self.clock); has!(Selection: |self: Arrangement|self.selection); 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!(Measure: |self: Arrangement|self.size); has!(Option: |self: Arrangement|self.editor); maybe_has!(Track: |self: Arrangement| { Has::::get(self).track().map(|index|Has::>::get(self).get(index)).flatten() }; { Has::::get(self).track().map(|index|Has::>::get_mut(self).get_mut(index)).flatten() }); maybe_has!(Scene: |self: Arrangement| { Has::::get(self).track().map(|index|Has::>::get(self).get(index)).flatten() }; { Has::::get(self).track().map(|index|Has::>::get_mut(self).get_mut(index)).flatten() }); impl Arrangement { /// 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 taken by all tracks. pub fn w_tracks (&self) -> u16 { self.tracks_with_sizes(&self.selection(), None).last() .map(|(_, _, _, x)|x as u16).unwrap_or(0) } /// 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 active track pub fn get_track (&self) -> Option<&Track> { let index = self.selection().track()?; Has::>::get(self).get(index) } /// Get a mutable reference to the active track pub fn get_track_mut (&mut self) -> Option<&mut Track> { let index = self.selection().track()?; Has::>::get_mut(self).get_mut(index) } /// Get the active scene pub fn get_scene (&self) -> Option<&Scene> { let index = self.selection().scene()?; Has::>::get(self).get(index) } /// Get a mutable reference to the active scene pub fn get_scene_mut (&mut self) -> Option<&mut Scene> { let index = self.selection().scene()?; Has::>::get_mut(self).get_mut(index) } /// Get the active clip pub fn get_clip (&self) -> Option>> { self.get_scene()?.clips.get(self.selection().track()?)?.clone() } /// 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.get_clip() { clip.write().unwrap().toggle_loop() } } /// Add multiple tracks pub fn tracks_add ( &mut self, count: usize, width: Option, mins: &[PortConnect], mouts: &[PortConnect], ) -> Usually<()> { let jack = self.jack().clone(); 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 mut 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: &[PortConnect], mouts: &[PortConnect], ) -> Usually<(usize, &mut Track)> { let name: Arc = name.map_or_else( ||format!("t{:02}", self.track_last).into(), |x|x.to_string().into() ); self.track_last += 1; let mut 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])) } } #[cfg(feature = "sampler")] impl Arrangement { /// Get the first sampler of the active track pub fn sampler (&self) -> Option<&Sampler> { self.get_track()?.sampler(0) } /// Get the first sampler of the active track pub fn sampler_mut (&mut self) -> Option<&mut Sampler> { self.get_track_mut()?.sampler_mut(0) } } impl ScenesView for Arrangement { fn h_scenes (&self) -> u16 { (self.height() as u16).saturating_sub(20) } fn w_side (&self) -> u16 { (self.width() as u16 * 2 / 10).max(20) } fn w_mid (&self) -> u16 { (self.width() as u16).saturating_sub(2 * self.w_side()).max(40) } }