use crate::*; mod dialog; pub use self::dialog::*; mod pool; pub use self::pool::*; mod selection; pub use self::selection::*; mod track; pub use self::track::*; mod scene; pub use self::scene::*; #[derive(Default, Debug)] pub struct App { /// Must not be dropped for the duration of the process pub jack: Jack, /// Source of time pub clock: Clock, /// Theme pub color: ItemTheme, /// Contains all clips in the project pub pool: Option, /// Contains the currently edited MIDI clip pub editor: Option, /// Contains a render of the project arrangement, redrawn on update. pub arranger: Arc>, /// 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, /// Buffer for writing a midi event pub note_buf: Vec, /// Buffer for writing a chunk of midi events pub midi_buf: 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 selected: Selection, /// Display size pub size: Measure, /// Performance counter pub perf: PerfModel, /// Whether in edit mode pub editing: AtomicBool, /// Undo history pub history: Vec, /// Port handles pub ports: std::collections::BTreeMap>, // Cache of formatted strings pub view_cache: Arc>, // Dialog overlay pub dialog: Option, // View and input definition pub config: Configuration } impl App { /// 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)> { self.track_last += 1; let name: Arc = name.map_or_else( ||format!("Track{:02}", self.track_last).into(), |x|x.to_string().into() ); 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])) } /// Add and focus a track pub(crate) fn track_add_focus (&mut self) -> Usually { use Selection::*; let index = self.track_add(None, None, &[], &[])?.0; self.selected = match self.selected { Track(_) => Track(index), TrackClip { track, scene } => TrackClip { track: index, scene }, _ => self.selected }; Ok(index) } /// Delete a track pub fn track_del (&mut self, index: usize) -> Usually<()> { let exists = self.tracks().get(index).is_some(); if exists { let track = self.tracks_mut().remove(index); let Track { sequencer: Sequencer { midi_ins, midi_outs, .. }, .. } = track; for port in midi_ins.into_iter() { port.close()?; } for port in midi_outs.into_iter() { port.close()?; } for scene in self.scenes_mut().iter_mut() { scene.clips.remove(index); } } Ok(()) } /// 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])) } /// Add and focus an empty scene pub fn scene_add_focus (&mut self) -> Usually { use Selection::*; let index = self.scene_add(None, None)?.0; self.selected = match self.selected { Scene(_) => Scene(index), TrackClip { track, scene } => TrackClip { track, scene: index }, _ => self.selected }; Ok(index) } /// Enqueue clips from a scene across all tracks pub fn scene_enqueue (&mut self, scene: usize) { for track in 0..self.tracks.len() { self.tracks[track].sequencer.enqueue_next(self.scenes[scene].clips[track].as_ref()); } } pub fn toggle_editor (&mut self, value: Option) { let editing = self.is_editing(); let value = value.unwrap_or_else(||!self.is_editing()); self.editing.store(value, Relaxed); if value { self.clip_auto_create(); } else { self.clip_auto_remove(); } } // Create new clip in pool when entering empty cell pub fn clip_auto_create (&mut self) { if let Some(ref pool) = self.pool && let Selection::TrackClip { track, scene } = self.selected && let Some(scene) = self.scenes.get_mut(scene) && let Some(slot) = scene.clips.get_mut(track) && slot.is_none() { let (index, mut clip) = pool.add_new_clip(); // autocolor: new clip colors from scene and track color clip.write().unwrap().color = ItemColor::random_near( self.tracks[track].color.base.mix( scene.color.base, 0.5 ), 0.2 ).into(); if let Some(ref mut editor) = self.editor { editor.set_clip(Some(&clip)); } *slot = Some(clip); } } // Remove clip from arrangement when exiting empty clip editor pub fn clip_auto_remove (&mut self) { if let Some(ref mut pool) = self.pool && let Selection::TrackClip { track, scene } = self.selected && let Some(scene) = self.scenes.get_mut(scene) && let Some(slot) = scene.clips.get_mut(track) && let Some(clip) = slot.as_mut() { let mut swapped = None; 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()); } } } /// Put a clip in a slot pub(crate) 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(crate) 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 }) } /// Get the clip pool, if present pub(crate) fn pool (&self) -> Option<&MidiPool> { self.pool.as_ref() } /// Get the active clip pub(crate) fn clip (&self) -> Option>> { self.scene()?.clips.get(self.selected().track()?)?.clone() } /// Get the active editor pub(crate) fn editor (&self) -> Option<&MidiEditor> { self.editor.as_ref() } /// Toggle looping for the active clip pub(crate) fn toggle_loop (&mut self) { if let Some(clip) = self.clip() { clip.write().unwrap().toggle_loop() } } /// 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 }); } } /// Stop all playing clips pub(crate) fn stop_all (&mut self) { for track in 0..self.tracks.len() { self.tracks[track].sequencer.enqueue_next(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()) } }, _ => {} }; } /// Get the first sampler of the active track pub fn sampler (&self) -> Option<&Sampler> { self.track().map(|t|t.sampler(0)).flatten() } /// Get the first sampler of the active track pub fn sampler_mut (&mut self) -> Option<&mut Sampler> { self.track_mut().map(|t|t.sampler_mut(0)).flatten() } /// 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!() }) } pub(crate) fn midi_in_add (&mut self) -> Usually<()> { self.midi_ins.push(JackMidiIn::new(&self.jack, &format!("M/{}", self.midi_ins.len()), &[])?); Ok(()) } pub(crate) fn midi_out_add (&mut self) -> Usually<()> { self.midi_outs.push(JackMidiOut::new(&self.jack, &format!("{}/M", self.midi_outs.len()), &[])?); Ok(()) } pub(crate) fn device_kinds (&self) -> &'static [&'static str] { &[ "Sampler", "Plugin (LV2)", ] } pub(crate) fn device_picker_show (&mut self) { self.dialog = Some(Dialog::Device(0)); } pub(crate) fn device_pick (&mut self, index: usize) { self.dialog = Some(Dialog::Device(index)); } pub(crate) fn device_add (&mut self, index: usize) -> Usually<()> { match index { 0 => self.device_add_sampler(), 1 => self.device_add_lv2(), _ => unreachable!(), } } 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(()) } fn device_add_lv2 (&mut self) -> Usually<()> { todo!(); Ok(()) } } has_size!(|self: App|&self.size); 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 = { let size = self.size.w(); let editor = self.editor.as_ref().expect("missing editor"); let time_len = editor.time_len().get(); let time_zoom = editor.time_zoom().get().max(1); (5 + (time_len / time_zoom)).min(size.saturating_sub(20)).max(16) }; editor_h = 15; is_editing = self.editing.load(Relaxed); });