use crate::*; /// Root level object for standalone `tek_arranger` pub struct Arranger { /// Which view is focused pub focus_cursor: (usize, usize), /// Controls the JACK transport. pub transport: Option>>>, /// Global timebase pub clock: Arc, /// Contains all the sequencers. pub arrangement: Arrangement, /// Pool of all phrases in the arrangement pub phrases: Arc>>, /// Phrase editor view pub editor: PhraseEditor, /// Status bar pub status: ArrangerStatusBar, /// Height of arrangement pub arrangement_split: u16, /// Width of phrase pool pub phrases_split: u16, } /// Sections in the arranger app that may be focused #[derive(Copy, Clone, PartialEq, Eq)] pub enum ArrangerFocus { /// The transport (toolbar) is focused Transport, /// The arrangement (grid) is focused Arrangement, /// The phrase list (pool) is focused PhrasePool, /// The phrase editor (sequencer) is focused PhraseEditor, } /// Status bar for arranger ap pub enum ArrangerStatusBar { Transport, ArrangementMix, ArrangementTrack, ArrangementScene, ArrangementClip, PhrasePool, PhraseView, PhraseEdit, } /// Represents the tracks and scenes of the composition. pub struct Arrangement { /// Global timebase pub clock: Arc, /// Name of arranger pub name: Arc>, /// Collection of phrases. pub phrases: Arc>>, /// Collection of tracks. pub tracks: Vec>, /// Collection of scenes. pub scenes: Vec, /// Currently selected element. pub selected: ArrangementFocus, /// Display mode of arranger pub mode: ArrangementViewMode, /// Whether the arranger is currently focused pub focused: bool, /// Background color of arrangement pub color: Color, } /// Represents a track in the arrangement pub struct ArrangementTrack { /// Name of track pub name: Arc>, /// Inputs pub inputs: Vec<()>, /// MIDI player/recorder pub player: PhrasePlayer, /// Outputs pub outputs: Vec<()>, /// Preferred width of track column pub width: usize, /// Identifying color of track pub color: Color, } #[derive(Default, Debug)] pub struct Scene { /// Name of scene pub name: Arc>, /// Clips in scene, one per track pub clips: Vec>>>, /// Identifying color of scene pub color: Color, } #[derive(PartialEq, Clone, Copy)] /// Represents the current user selection in the arranger pub enum ArrangementFocus { /// The whole mix is selected Mix, /// A track is selected. Track(usize), /// A scene is selected. Scene(usize), /// A clip (track × scene) is selected. Clip(usize, usize), } /// Display mode of arranger #[derive(PartialEq)] pub enum ArrangementViewMode { /// Tracks are rows Horizontal, /// Tracks are columns Vertical(usize), } /// Arrangement, rendered vertically (session/grid mode). pub struct VerticalArranger<'a, E: Engine>(pub &'a Arrangement, pub usize); /// Arrangement, rendered horizontally (arrangement/track mode). pub struct HorizontalArranger<'a, E: Engine>(pub &'a Arrangement); /// General methods for arranger impl Arranger { pub fn new ( transport: Option>>>, arrangement: Arrangement, phrases: Arc>>, ) -> Self { let mut app = Self { focus_cursor: (0, 1), phrases_split: 20, arrangement_split: 21, editor: PhraseEditor::new(), status: ArrangerStatusBar::ArrangementClip, clock: if let Some(ref transport) = transport { transport.read().unwrap().clock.clone() } else { Arc::new(TransportTime::default()) }, transport, arrangement, phrases, }; app.update_focus(); app } /// Toggle global play/pause pub fn toggle_play (&mut self) -> Perhaps { match self.transport { Some(ref mut transport) => { transport.write().unwrap().toggle_play()?; }, None => { return Ok(None) } } Ok(Some(true)) } /// Focus the editor with the current phrase pub fn show_phrase (&mut self) { self.editor.show(self.arrangement.phrase().as_ref()); } /// Focus the editor with the current phrase pub fn edit_phrase (&mut self) { if self.arrangement.phrase().is_none() { self.phrases.write().unwrap().append_new(None, None); self.arrangement.phrase_put(); } self.show_phrase(); self.focus(ArrangerFocus::PhraseEditor); } /// Rename the selected track, scene, or clip pub fn rename_selected (&mut self) { let Arrangement { selected, ref scenes, .. } = self.arrangement; match selected { ArrangementFocus::Mix => {}, ArrangementFocus::Track(_) => { todo!("rename track"); }, ArrangementFocus::Scene(_) => { todo!("rename scene"); }, ArrangementFocus::Clip(t, s) => if let Some(ref phrase) = scenes[s].clips[t] { let index = self.phrases.read().unwrap().index_of(&*phrase.read().unwrap()); if let Some(index) = index { self.focus(ArrangerFocus::PhrasePool); self.phrases.write().unwrap().phrase = index; self.phrases.write().unwrap().begin_rename(); } }, } } /// Update status bar pub fn update_status (&mut self) { self.status = match self.focused() { ArrangerFocus::Transport => ArrangerStatusBar::Transport, ArrangerFocus::Arrangement => match self.arrangement.selected { ArrangementFocus::Mix => ArrangerStatusBar::ArrangementMix, ArrangementFocus::Track(_) => ArrangerStatusBar::ArrangementTrack, ArrangementFocus::Scene(_) => ArrangerStatusBar::ArrangementScene, ArrangementFocus::Clip(_, _) => ArrangerStatusBar::ArrangementClip, }, ArrangerFocus::PhrasePool => ArrangerStatusBar::PhrasePool, ArrangerFocus::PhraseEditor => match self.editor.entered { true => ArrangerStatusBar::PhraseEdit, false => ArrangerStatusBar::PhraseView, }, } } } /// Focus layout of arranger app impl FocusGrid for Arranger { fn cursor (&self) -> (usize, usize) { self.focus_cursor } fn cursor_mut (&mut self) -> &mut (usize, usize) { &mut self.focus_cursor } fn layout (&self) -> &[&[ArrangerFocus]] { &[ &[ArrangerFocus::Transport], &[ArrangerFocus::Arrangement, ArrangerFocus::Arrangement], &[ArrangerFocus::PhrasePool, ArrangerFocus::PhraseEditor], ] } fn update_focus (&mut self) { let focused = *self.focused(); if let Some(transport) = self.transport.as_ref() { transport.write().unwrap().focused = focused == ArrangerFocus::Transport } self.arrangement.focused = focused == ArrangerFocus::Arrangement; self.phrases.write().unwrap().focused = focused == ArrangerFocus::PhrasePool; self.editor.focused = focused == ArrangerFocus::PhraseEditor; self.update_status(); } } /// General methods for arrangement impl Arrangement { pub fn new (clock: &Arc, name: &str, phrases: &Arc>>) -> Self { Self { clock: clock.clone(), name: Arc::new(RwLock::new(name.into())), mode: ArrangementViewMode::Vertical(2), selected: ArrangementFocus::Clip(0, 0), phrases: phrases.clone(), scenes: vec![], tracks: vec![], focused: false, color: Color::Rgb(28, 35, 25), } } pub fn activate (&mut self) { match self.selected { ArrangementFocus::Scene(s) => { for (t, track) in self.tracks.iter_mut().enumerate() { track.player.enqueue_next(self.scenes[s].clips[t].as_ref()); } }, ArrangementFocus::Clip(t, s) => { self.tracks[t].player.enqueue_next(self.scenes[s].clips[t].as_ref()); }, _ => {} } } pub fn delete (&mut self) { match self.selected { ArrangementFocus::Track(_) => self.track_del(), ArrangementFocus::Scene(_) => self.scene_del(), ArrangementFocus::Clip(_, _) => self.phrase_del(), _ => {} } } pub fn increment (&mut self) { match self.selected { ArrangementFocus::Track(_) => self.track_width_inc(), ArrangementFocus::Scene(_) => self.scene_next(), ArrangementFocus::Clip(_, _) => self.phrase_next(), ArrangementFocus::Mix => self.zoom_in(), } } pub fn decrement (&mut self) { match self.selected { ArrangementFocus::Track(_) => self.track_width_dec(), ArrangementFocus::Scene(_) => self.scene_prev(), ArrangementFocus::Clip(_, _) => self.phrase_prev(), ArrangementFocus::Mix => self.zoom_out(), } } pub fn zoom_in (&mut self) { if let ArrangementViewMode::Vertical(factor) = self.mode { self.mode = ArrangementViewMode::Vertical(factor + 1) } } pub fn zoom_out (&mut self) { if let ArrangementViewMode::Vertical(factor) = self.mode { self.mode = ArrangementViewMode::Vertical(factor.saturating_sub(1)) } } pub fn is_first_row (&self) -> bool { let selected = self.selected; selected.is_mix() || selected.is_track() } pub fn is_last_row (&self) -> bool { let selected = self.selected; (self.scenes.len() == 0 && (selected.is_mix() || selected.is_track())) || match selected { ArrangementFocus::Scene(s) => s == self.scenes.len() - 1, ArrangementFocus::Clip(_, s) => s == self.scenes.len() - 1, _ => false } } pub fn toggle_loop (&mut self) { if let Some(phrase) = self.phrase() { phrase.write().unwrap().toggle_loop() } } pub fn go_up (&mut self) { match self.mode { ArrangementViewMode::Horizontal => self.track_prev(), _ => self.scene_prev(), }; } pub fn go_down (&mut self) { match self.mode { ArrangementViewMode::Horizontal => self.track_next(), _ => self.scene_next(), }; } pub fn go_left (&mut self) { match self.mode { ArrangementViewMode::Horizontal => self.scene_prev(), _ => self.track_prev(), }; } pub fn go_right (&mut self) { match self.mode { ArrangementViewMode::Horizontal => self.scene_next(), _ => self.track_next(), }; } pub fn move_back (&mut self) { match self.selected { ArrangementFocus::Scene(s) => { if s > 0 { self.scenes.swap(s, s - 1); self.selected = ArrangementFocus::Scene(s - 1); } }, ArrangementFocus::Track(t) => { if t > 0 { self.tracks.swap(t, t - 1); self.selected = ArrangementFocus::Track(t - 1); // FIXME: also swap clip order in scenes } }, _ => todo!("arrangement: move forward") } } pub fn move_forward (&mut self) { match self.selected { ArrangementFocus::Scene(s) => { if s < self.scenes.len().saturating_sub(1) { self.scenes.swap(s, s + 1); self.selected = ArrangementFocus::Scene(s + 1); } }, ArrangementFocus::Track(t) => { if t < self.tracks.len().saturating_sub(1) { self.tracks.swap(t, t + 1); self.selected = ArrangementFocus::Track(t + 1); // FIXME: also swap clip order in scenes } }, _ => todo!("arrangement: move forward") } } pub fn randomize_color (&mut self) { match self.selected { ArrangementFocus::Mix => { self.color = random_color_dark() }, ArrangementFocus::Track(t) => { self.tracks[t].color = random_color() }, ArrangementFocus::Scene(s) => { self.scenes[s].color = random_color() }, ArrangementFocus::Clip(t, s) => if let Some(phrase) = &self.scenes[s].clips[t] { phrase.write().unwrap().color = random_color(); } } } } /// Methods for tracks in arrangement impl Arrangement { pub fn track (&self) -> Option<&ArrangementTrack> { self.selected.track().map(|t|self.tracks.get(t)).flatten() } pub fn track_mut (&mut self) -> Option<&mut ArrangementTrack> { self.selected.track().map(|t|self.tracks.get_mut(t)).flatten() } pub fn track_width_inc (&mut self) { self.track_mut().map(|t|t.width_inc()); } pub fn track_width_dec (&mut self) { self.track_mut().map(|t|t.width_dec()); } pub fn track_next (&mut self) { self.selected.track_next(self.tracks.len() - 1) } pub fn track_prev (&mut self) { self.selected.track_prev() } pub fn track_add ( &mut self, name: Option<&str>, color: Option ) -> Usually<&mut ArrangementTrack> { self.tracks.push(name.map_or_else( || ArrangementTrack::new(&self.clock, &self.track_default_name(), color), |name| ArrangementTrack::new(&self.clock, name, color), )); let index = self.tracks.len() - 1; Ok(&mut self.tracks[index]) } pub fn track_del (&mut self) { if let Some(index) = self.selected.track() { self.tracks.remove(index); for scene in self.scenes.iter_mut() { scene.clips.remove(index); } } } pub fn track_default_name (&self) -> String { format!("Track {}", self.tracks.len() + 1) } pub fn track_widths (&self) -> Vec<(usize, usize)> { let mut widths = vec![]; let mut total = 0; for track in self.tracks.iter() { let width = track.width; widths.push((width, total)); total += width; } widths.push((0, total)); widths } } /// Methods for scenes in arrangement impl Arrangement { pub fn scene (&self) -> Option<&Scene> { self.selected.scene().map(|s|self.scenes.get(s)).flatten() } pub fn scene_mut (&mut self) -> Option<&mut Scene> { self.selected.scene().map(|s|self.scenes.get_mut(s)).flatten() } pub fn scene_next (&mut self) { self.selected.scene_next(self.scenes.len() - 1) } pub fn scene_prev (&mut self) { self.selected.scene_prev() } pub fn scene_add (&mut self, name: Option<&str>, color: Option) -> Usually<&mut Scene> { let clips = vec![None;self.tracks.len()]; let name = name.map(|x|x.to_string()).unwrap_or_else(||self.scene_default_name()); self.scenes.push(Scene::new(name, clips, color)); let index = self.scenes.len() - 1; Ok(&mut self.scenes[index]) } pub fn scene_del (&mut self) { if let Some(index) = self.selected.scene() { self.scenes.remove(index); } } pub fn scene_default_name (&self) -> String { format!("Scene {}", self.scenes.len() + 1) } } /// Methods for phrases in arrangement impl Arrangement { pub fn sequencer (&self) -> Option<&ArrangementTrack> { self.selected.track() .map(|track|self.tracks.get(track)) .flatten() } pub fn sequencer_mut (&mut self) -> Option<&mut ArrangementTrack> { self.selected.track() .map(|track|self.tracks.get_mut(track)) .flatten() } pub fn phrase (&self) -> Option>> { self.scene()?.clips.get(self.selected.track()?)?.clone() } pub fn phrase_del (&mut self) { let track_index = self.selected.track(); let scene_index = self.selected.scene(); track_index .and_then(|index|self.tracks.get_mut(index).map(|track|(index, track))) .map(|(track_index, _)|{ scene_index .and_then(|index|self.scenes.get_mut(index)) .map(|scene|scene.clips[track_index] = None); }); } pub fn phrase_put (&mut self) { if let ArrangementFocus::Clip(track, scene) = self.selected { self.scenes[scene].clips[track] = Some(self.phrases.read().unwrap().phrase().clone()); } } pub fn phrase_get (&mut self) { if let ArrangementFocus::Clip(track, scene) = self.selected { if let Some(phrase) = &self.scenes[scene].clips[track] { let mut phrases = self.phrases.write().unwrap(); if let Some(index) = phrases.index_of(&*phrase.read().unwrap()) { phrases.phrase = index; } } } } pub fn phrase_next (&mut self) { if let ArrangementFocus::Clip(track, scene) = self.selected { if let Some(ref mut phrase) = self.scenes[scene].clips[track] { let phrases = self.phrases.read().unwrap(); let index = phrases.index_of(&*phrase.read().unwrap()); if let Some(index) = index { if index < phrases.phrases.len().saturating_sub(1) { *phrase = phrases.phrases[index + 1].clone(); } } } } } pub fn phrase_prev (&mut self) { if let ArrangementFocus::Clip(track, scene) = self.selected { if let Some(ref mut phrase) = self.scenes[scene].clips[track] { let phrases = self.phrases.read().unwrap(); let index = phrases.index_of(&*phrase.read().unwrap()); if let Some(index) = index { if index > 0 { *phrase = phrases.phrases[index - 1].clone(); } } } } } } impl ArrangementTrack { pub fn new (clock: &Arc, name: &str, color: Option) -> Self { Self { name: Arc::new(RwLock::new(name.into())), inputs: vec![], player: PhrasePlayer::new(clock), outputs: vec![], width: name.len() + 2, color: color.unwrap_or_else(random_color) } } pub fn longest_name (tracks: &[Self]) -> usize { tracks.iter() .map(|s|s.name.read().unwrap().len()) .fold(0, usize::max) } pub fn width_inc (&mut self) { self.width += 1; } pub fn width_dec (&mut self) { if self.width > 3 { self.width -= 1; } } } /// Focus identification methods impl ArrangementFocus { pub fn description ( &self, tracks: &Vec>, scenes: &Vec, ) -> String { format!("Selected: {}", match self { Self::Mix => format!("Everything"), Self::Track(t) => if let Some(track) = tracks.get(*t) { format!("T{t}: {}", &track.name.read().unwrap()) } else { format!("T??") }, Self::Scene(s) => if let Some(scene) = scenes.get(*s) { format!("S{s}: {}", &scene.name.read().unwrap()) } else { format!("S??") }, Self::Clip(t, s) => if let (Some(_), Some(scene)) = ( tracks.get(*t), scenes.get(*s), ) { if let Some(clip) = scene.clip(*t) { format!("T{t} S{s} C{}", &clip.read().unwrap().name) } else { format!("T{t} S{s}: Empty") } } else { format!("T{t} S{s}: Empty") } }) } pub fn is_mix (&self) -> bool { match self { Self::Mix => true, _ => false } } pub fn is_track (&self) -> bool { match self { Self::Track(_) => true, _ => false } } pub fn is_scene (&self) -> bool { match self { Self::Scene(_) => true, _ => false } } pub fn is_clip (&self) -> bool { match self { Self::Clip(_, _) => true, _ => false } } pub fn track (&self) -> Option { match self { Self::Clip(t, _) => Some(*t), Self::Track(t) => Some(*t), _ => None } } pub fn track_next (&mut self, last_track: usize) { *self = match self { Self::Mix => Self::Track(0), Self::Track(t) => Self::Track(last_track.min(*t + 1)), Self::Scene(s) => Self::Clip(0, *s), Self::Clip(t, s) => Self::Clip(last_track.min(*t + 1), *s), } } pub fn track_prev (&mut self) { *self = match self { Self::Mix => Self::Mix, Self::Scene(s) => Self::Scene(*s), Self::Track(t) => if *t == 0 { Self::Mix } else { Self::Track(*t - 1) }, Self::Clip(t, s) => if *t == 0 { Self::Scene(*s) } else { Self::Clip(t.saturating_sub(1), *s) } } } pub fn scene (&self) -> Option { match self { Self::Clip(_, s) => Some(*s), Self::Scene(s) => Some(*s), _ => None } } pub fn scene_next (&mut self, last_scene: usize) { *self = match self { Self::Mix => Self::Scene(0), Self::Track(t) => Self::Clip(*t, 0), Self::Scene(s) => Self::Scene(last_scene.min(*s + 1)), Self::Clip(t, s) => Self::Clip(*t, last_scene.min(*s + 1)), } } pub fn scene_prev (&mut self) { *self = match self { Self::Mix => Self::Mix, Self::Track(t) => Self::Track(*t), Self::Scene(s) => if *s == 0 { Self::Mix } else { Self::Scene(*s - 1) }, Self::Clip(t, s) => if *s == 0 { Self::Track(*t) } else { Self::Clip(*t, s.saturating_sub(1)) } } } } /// Arranger display mode can be cycled impl ArrangementViewMode { /// Cycle arranger display mode pub fn to_next (&mut self) { *self = match self { Self::Horizontal => Self::Vertical(1), Self::Vertical(1) => Self::Vertical(2), Self::Vertical(2) => Self::Vertical(2), Self::Vertical(0) => Self::Horizontal, Self::Vertical(_) => Self::Vertical(0), } } } impl Scene { pub fn new ( name: impl AsRef, clips: impl AsRef<[Option>>]>, color: Option, ) -> Self { Self { name: Arc::new(RwLock::new(name.as_ref().into())), clips: clips.as_ref().iter().map(|x|x.clone()).collect(), color: color.unwrap_or_else(random_color), } } /// Returns the pulse length of the longest phrase 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 phrases in the scene are currently playing pub fn is_playing (&self, tracks: &[ArrangementTrack]) -> bool { self.clips.iter().any(|clip|clip.is_some()) && self.clips.iter().enumerate() .all(|(track_index, clip)|match clip { Some(clip) => tracks .get(track_index) .map(|track|if let Some((_, Some(phrase))) = &track.player.phrase { *phrase.read().unwrap() == *clip.read().unwrap() } else { false }) .unwrap_or(false), None => true }) } pub fn ppqs (scenes: &[Self], factor: usize) -> Vec<(usize, usize)> { let mut total = 0; if factor == 0 { scenes.iter().map(|scene|{ let pulses = scene.pulses().max(PPQ); total = total + pulses; (pulses, total - pulses) }).collect() } else { (0..=scenes.len()).map(|i|{ (factor*PPQ, factor*PPQ*i) }).collect() } } pub fn longest_name (scenes: &[Self]) -> usize { scenes.iter() .map(|s|s.name.read().unwrap().len()) .fold(0, usize::max) } pub fn clip (&self, index: usize) -> Option<&Arc>> { if let Some(Some(clip)) = self.clips.get(index) { Some(clip) } else { None } } }