use crate::*; #[derive(Default, Debug)] pub struct Tek { /// 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>>, /// 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>, /// View definition pub view: SourceIter<'static>, // Cache of formatted strings pub view_cache: Arc>, // Modal overlay pub modal: Option, // Input keymap pub keys: InputMap<'static, Self, TekCommand, TuiIn, TokenIter<'static>> } impl Tek { /// 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 = name.map_or_else(||self.track_next_name(), |x|x.to_string().into()); let mut track = Track { width: (name.len() + 2).max(12), color: color.unwrap_or_else(ItemTheme::random), player: MidiPlayer::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 { let index = self.track_add(None, None, &[], &[])?.0; self.selected = match self.selected { Selection::Track(t) => Selection::Track(index), Selection::Clip(t, s) => Selection::Clip(index, s), _ => self.selected }; Ok(index) } /// Delete a track pub fn track_del (&mut self, index: usize) { self.tracks_mut().remove(index); for scene in self.scenes_mut().iter_mut() { scene.clips.remove(index); } } /// 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 { let index = self.scene_add(None, None)?.0; self.selected = match self.selected { Selection::Scene(s) => Selection::Scene(index), Selection::Clip(t, s) => Selection::Clip(t, 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].player.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(); } } pub fn toggle_modal (&mut self, modal: Option) { self.modal = if self.modal == modal { None } else { modal } } // 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::Clip(t, s) = self.selected && let Some(scene) = self.scenes.get_mut(s) && let Some(slot) = scene.clips.get_mut(t) && 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[t].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::Clip(t, s) = self.selected && let Some(scene) = self.scenes.get_mut(s) && let Some(slot) = scene.clips.get_mut(t) && 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::Clip(t, s) if let Some(Some(Some(clip))) = self .scenes.get(s).map(|s|s.clips.get(t)) => Some(clip), _ => None }); } } /// Stop all playing clips pub(crate) fn stop_all (&mut self) { for track in 0..self.tracks.len() { self.tracks[track].player.enqueue_next(None); } } /// Launch a clip or scene pub(crate) fn launch (&mut self) { match self.selected { Selection::Track(t) => { self.tracks[t].player.enqueue_next(None) }, Selection::Clip(t, s) => { self.tracks[t].player.enqueue_next(self.scenes[s].clips[t].as_ref()) }, Selection::Scene(s) => { for t in 0..self.tracks.len() { self.tracks[t].player.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 { let palette = palette.unwrap_or_else(||ItemTheme::random()); Some(match self.selected { Selection::Mix => { let old = self.color; self.color = palette; old }, Selection::Track(t) => { let old = self.tracks[t].color; self.tracks[t].color = palette; old } Selection::Scene(s) => { let old = self.scenes[s].color; self.scenes[s].color = palette; old } Selection::Clip(t, s) => { if let Some(ref clip) = self.scenes[s].clips[t] { let mut clip = clip.write().unwrap(); let old = clip.color; clip.color = palette; old } else { return None } } }) } pub(crate) fn add_midi_in (&mut self) -> Usually<()> { self.midi_ins.push(JackMidiIn::new(&self.jack, &format!("M/{}", self.midi_ins.len()), &[])?); Ok(()) } pub(crate) fn add_midi_out (&mut self) -> Usually<()> { self.midi_outs.push(JackMidiOut::new(&self.jack, &format!("{}/M", self.midi_outs.len()), &[])?); Ok(()) } } has_size!(|self: Tek|&self.size); has_clock!(|self: Tek|self.clock); has_clips!(|self: Tek|self.pool.as_ref().expect("no clip pool").clips); has_editor!(|self: Tek|{ 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); }); pub trait HasSelection { fn selected (&self) -> &Selection; fn selected_mut (&mut self) -> &mut Selection; } /// Various possible modal overlays #[derive(PartialEq, Clone, Copy, Debug)] pub enum Modal { Help, Menu, } /// Represents the current user selection in the arranger #[derive(PartialEq, Clone, Copy, Debug, Default)] pub enum Selection { /// The whole mix is selected #[default] Mix, /// A track is selected. Track(usize), /// A scene is selected. Scene(usize), /// A clip (track × scene) is selected. Clip(usize, usize), } /// Focus identification methods impl Selection { pub fn is_mix (&self) -> bool { matches!(self, Self::Mix) } pub fn is_track (&self) -> bool { matches!(self, Self::Track(_)) } pub fn is_scene (&self) -> bool { matches!(self, Self::Scene(_)) } pub fn is_clip (&self) -> bool { matches!(self, Self::Clip(_, _)) } pub fn track (&self) -> Option { use Selection::*; match self { Clip(t, _) => Some(*t), Track(t) => Some(*t), _ => None } } pub fn track_next (&self, len: usize) -> Self { match self { Selection::Mix => Selection::Track(0), Selection::Track(t) if t + 1 < len => Selection::Track(t + 1), Selection::Track(t) => Selection::Mix, Selection::Scene(s) => Selection::Clip(0, *s), Selection::Clip(t, s) if t + 1 < len => Selection::Clip(t + 1, *s), Selection::Clip(t, s) => Selection::Scene(*s), } } pub fn track_prev (&self) -> Self { match self { Selection::Mix => Selection::Mix, Selection::Scene(s) => Selection::Scene(*s), Selection::Track(0) => Selection::Mix, Selection::Track(t) => Selection::Track(t - 1), Selection::Clip(0, s) => Selection::Scene(*s), Selection::Clip(t, s) => Selection::Clip(t - 1, *s), } } pub fn scene (&self) -> Option { use Selection::*; match self { Clip(_, s) => Some(*s), Scene(s) => Some(*s), _ => None } } pub fn scene_next (&self, len: usize) -> Self { match self { Selection::Mix => Selection::Scene(0), Selection::Track(t) => Selection::Clip(*t, 0), Selection::Scene(s) if s + 1 < len => Selection::Scene(s + 1), Selection::Scene(s) => Selection::Mix, Selection::Clip(t, s) if s + 1 < len => Selection::Clip(*t, s + 1), Selection::Clip(t, s) => Selection::Track(*t), } } pub fn scene_prev (&self) -> Self { match self { Selection::Mix => Selection::Mix, Selection::Track(t) => Selection::Track(*t), Selection::Scene(0) => Selection::Mix, Selection::Scene(s) => Selection::Scene(s - 1), Selection::Clip(t, 0) => Selection::Track(*t), Selection::Clip(t, s) => Selection::Clip(*t, s - 1), } } pub fn describe (&self, tracks: &[Track], scenes: &[Scene]) -> Arc { format!("{}", match self { Self::Mix => "Everything".to_string(), Self::Track(t) => tracks.get(*t).map(|track|format!("T{t}: {}", &track.name)) .unwrap_or_else(||"T??".into()), Self::Scene(s) => scenes.get(*s).map(|scene|format!("S{s}: {}", &scene.name)) .unwrap_or_else(||"S??".into()), Self::Clip(t, s) => match (tracks.get(*t), scenes.get(*s)) { (Some(_), Some(scene)) => match scene.clip(*t) { Some(clip) => format!("T{t} S{s} C{}", &clip.read().unwrap().name), None => format!("T{t} S{s}: Empty") }, _ => format!("T{t} S{s}: Empty"), } }).into() } } impl HasSelection for Tek { fn selected (&self) -> &Selection { &self.selected } fn selected_mut (&mut self) -> &mut Selection { &mut self.selected } } pub trait HasScenes: HasSelection + HasEditor + Send + Sync { fn scenes (&self) -> &Vec; fn scenes_mut (&mut self) -> &mut Vec; fn scene_longest (&self) -> usize { self.scenes().iter().map(|s|s.name.len()).fold(0, usize::max) } fn scene (&self) -> Option<&Scene> { self.selected().scene().and_then(|s|self.scenes().get(s)) } fn scene_mut (&mut self) -> Option<&mut Scene> { self.selected().scene().and_then(|s|self.scenes_mut().get_mut(s)) } fn scene_del (&mut self, index: usize) { self.selected().scene().and_then(|s|Some(self.scenes_mut().remove(index))); } /// Set the color of a scene, returning the previous one. fn scene_set_color (&mut self, index: usize, color: ItemTheme) -> ItemTheme { let scenes = self.scenes_mut(); let old = scenes[index].color; scenes[index].color = color; old } /// Generate the default name for a new scene fn scene_default_name (&self) -> Arc { format!("Sc{:3>}", self.scenes().len() + 1).into() } } #[derive(Debug, Default)] pub struct Scene { /// Name of scene pub name: Arc, /// Clips in scene, one per track pub clips: Vec>>>, /// Identifying color of scene pub color: ItemTheme, } 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.player().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 } } } impl HasScenes for Tek { fn scenes (&self) -> &Vec { &self.scenes } fn scenes_mut (&mut self) -> &mut Vec { &mut self.scenes } } pub trait HasTracks: HasSelection + HasClock + HasJack + HasEditor + Send + Sync { fn midi_ins (&self) -> &Vec; fn midi_outs (&self) -> &Vec; fn tracks (&self) -> &Vec; fn tracks_mut (&mut self) -> &mut Vec; fn track_longest (&self) -> usize { self.tracks().iter().map(|s|s.name.len()).fold(0, usize::max) } const WIDTH_OFFSET: usize = 1; fn track_next_name (&self) -> Arc { format!("Track{:02}", self.tracks().len() + 1).into() } fn track (&self) -> Option<&Track> { self.selected().track().and_then(|s|self.tracks().get(s)) } fn track_mut (&mut self) -> Option<&mut Track> { self.selected().track().and_then(|s|self.tracks_mut().get_mut(s)) } /// Set the color of a track fn track_set_color (&mut self, index: usize, color: ItemTheme) -> ItemTheme { let tracks = self.tracks_mut(); let old = tracks[index].color; tracks[index].color = color; old } /// Toggle track recording fn track_toggle_record (&mut self) { if let Some(t) = self.selected().track() { let tracks = self.tracks_mut(); tracks[t-1].player.recording = !tracks[t-1].player.recording; } } /// Toggle track monitoring fn track_toggle_monitor (&mut self) { if let Some(t) = self.selected().track() { let tracks = self.tracks_mut(); tracks[t-1].player.monitoring = !tracks[t-1].player.monitoring; } } } #[derive(Debug, Default)] pub struct Track { /// Name of track pub name: Arc, /// Preferred width of track column pub width: usize, /// Identifying color of track pub color: ItemTheme, /// MIDI player state pub player: MidiPlayer, /// Device chain pub devices: Vec, /// Inputs of 1st device pub audio_ins: Vec, /// Outputs of last device pub audio_outs: Vec, } has_clock!(|self: Track|self.player.clock); has_player!(|self: Track|self.player); impl Track { pub const MIN_WIDTH: usize = 9; /// Create a new track containing a sequencer. pub fn new_sequencer () -> Self { let mut track = Self::default(); track.devices.push(Device::Sequencer(MidiPlayer::default())); track } /// Create a new track containing a sequencer and sampler. pub fn new_groovebox ( jack: &Jack, midi_from: &[PortConnect], audio_from: &[&[PortConnect];2], audio_to: &[&[PortConnect];2], ) -> Usually { let mut track = Self::new_sequencer(); track.devices.push(Device::Sampler( Sampler::new(jack, &"sampler", midi_from, audio_from, audio_to)? )); Ok(track) } /// Create a new track containing a sampler. pub fn new_sampler ( jack: &Jack, midi_from: &[PortConnect], audio_from: &[&[PortConnect];2], audio_to: &[&[PortConnect];2], ) -> Usually { let mut track = Self::default(); track.devices.push(Device::Sampler( Sampler::new(jack, &"sampler", midi_from, audio_from, audio_to)? )); Ok(track) } pub fn width_inc (&mut self) { self.width += 1; } pub fn width_dec (&mut self) { if self.width > Track::MIN_WIDTH { self.width -= 1; } } pub fn sequencer (&self, mut nth: usize) -> Option<&MidiPlayer> { for device in self.devices.iter() { match device { Device::Sequencer(s) => if nth == 0 { return Some(s); } else { nth -= 1; }, _ => {} } } None } 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 HasTracks for Tek { fn midi_ins (&self) -> &Vec { &self.midi_ins } fn midi_outs (&self) -> &Vec { &self.midi_outs } fn tracks (&self) -> &Vec { &self.tracks } fn tracks_mut (&mut self) -> &mut Vec { &mut self.tracks } } #[derive(Debug)] pub enum Device { Sequencer(MidiPlayer), Sampler(Sampler), #[cfg(feature="host")] Plugin(Plugin), }