diff --git a/crates/tek_api/src/arrange_cmd.rs b/crates/tek_api/src/arrange_cmd.rs index 6380838a..fec6e74d 100644 --- a/crates/tek_api/src/arrange_cmd.rs +++ b/crates/tek_api/src/arrange_cmd.rs @@ -17,7 +17,7 @@ pub enum ArrangementSceneCommand { Delete(usize), RandomColor, Play(usize), - Swap(usize), + Swap(usize, usize), SetSize(usize), SetZoom(usize), } @@ -28,7 +28,7 @@ pub enum ArrangementTrackCommand { Delete(usize), RandomColor, Stop, - Swap(usize), + Swap(usize, usize), SetSize(usize), SetZoom(usize), } @@ -46,33 +46,39 @@ pub enum ArrangementClipCommand { impl Command for ArrangementCommand { fn execute (self, state: &mut Arrangement) -> Perhaps { match self { - Self::Clear => todo!(), - Self::Export => todo!(), - Self::Import => todo!(), - Self::StopAll => todo!(), Self::Scene(command) => { return Ok(command.execute(state)?.map(Self::Scene)) }, Self::Track(command) => { return Ok(command.execute(state)?.map(Self::Track)) }, Self::Clip(command) => { return Ok(command.execute(state)?.map(Self::Clip)) }, + _ => todo!() } - return Ok(None) + Ok(None) } } impl Command for ArrangementSceneCommand { fn execute (self, state: &mut Arrangement) -> Perhaps { - todo!() + match self { + _ => todo!() + } + Ok(None) } } impl Command for ArrangementTrackCommand { fn execute (self, state: &mut Arrangement) -> Perhaps { - todo!() + match self { + _ => todo!() + } + Ok(None) } } impl Command for ArrangementClipCommand { fn execute (self, state: &mut Arrangement) -> Perhaps { - todo!() + match self { + _ => todo!() + } + Ok(None) } } @@ -92,3 +98,49 @@ impl Command for ArrangementClipCommand { //AddScene => { state.state.scene_add(None, None)?; }, //AddTrack => { state.state.track_add(None, None)?; }, //ToggleLoop => { state.state.toggle_loop() }, + //pub fn zoom_in (&mut self) { + //if let ArrangementEditorMode::Vertical(factor) = self.mode { + //self.mode = ArrangementEditorMode::Vertical(factor + 1) + //} + //} + //pub fn zoom_out (&mut self) { + //if let ArrangementEditorMode::Vertical(factor) = self.mode { + //self.mode = ArrangementEditorMode::Vertical(factor.saturating_sub(1)) + //} + //} + //pub fn move_back (&mut self) { + //match self.selected { + //ArrangementEditorFocus::Scene(s) => { + //if s > 0 { + //self.scenes.swap(s, s - 1); + //self.selected = ArrangementEditorFocus::Scene(s - 1); + //} + //}, + //ArrangementEditorFocus::Track(t) => { + //if t > 0 { + //self.tracks.swap(t, t - 1); + //self.selected = ArrangementEditorFocus::Track(t - 1); + //// FIXME: also swap clip order in scenes + //} + //}, + //_ => todo!("arrangement: move forward") + //} + //} + //pub fn move_forward (&mut self) { + //match self.selected { + //ArrangementEditorFocus::Scene(s) => { + //if s < self.scenes.len().saturating_sub(1) { + //self.scenes.swap(s, s + 1); + //self.selected = ArrangementEditorFocus::Scene(s + 1); + //} + //}, + //ArrangementEditorFocus::Track(t) => { + //if t < self.tracks.len().saturating_sub(1) { + //self.tracks.swap(t, t + 1); + //self.selected = ArrangementEditorFocus::Track(t + 1); + //// FIXME: also swap clip order in scenes + //} + //}, + //_ => todo!("arrangement: move forward") + //} + //} diff --git a/crates/tek_snd/src/snd_plugin.rs b/crates/tek_snd/src/snd_plugin.rs index cd02b402..26565a94 100644 --- a/crates/tek_snd/src/snd_plugin.rs +++ b/crates/tek_snd/src/snd_plugin.rs @@ -48,12 +48,8 @@ impl Audio for PluginAudio { let ports = ::livi::EmptyPortConnections::new() .with_atom_sequence_inputs(input_buffer.iter()) .with_atom_sequence_outputs(outputs.iter_mut()) - .with_audio_inputs( - self.audio_ins.iter().map(|o|o.as_slice(scope)) - ) - .with_audio_outputs( - self.audio_outs.iter_mut().map(|o|o.as_mut_slice(scope)) - ); + .with_audio_inputs(self.audio_ins.iter().map(|o|o.as_slice(scope))) + .with_audio_outputs(self.audio_outs.iter_mut().map(|o|o.as_mut_slice(scope))); unsafe { instance.run(scope.n_frames() as usize, ports).unwrap() }; diff --git a/crates/tek_tui/src/lib.rs b/crates/tek_tui/src/lib.rs index 1047a4be..87da6826 100644 --- a/crates/tek_tui/src/lib.rs +++ b/crates/tek_tui/src/lib.rs @@ -13,10 +13,6 @@ submod! { tui_app tui_app_foc - tui_arrangement - tui_arrangement_cmd - tui_arrangement_foc - tui_arranger tui_arranger_bar tui_arranger_cmd diff --git a/crates/tek_tui/src/tui_arrangement.rs b/crates/tek_tui/src/tui_arrangement.rs deleted file mode 100644 index 5e81843c..00000000 --- a/crates/tek_tui/src/tui_arrangement.rs +++ /dev/null @@ -1,156 +0,0 @@ -use crate::*; - -pub struct ArrangementEditor { - pub model: Arrangement, - /// Currently selected element. - pub selected: ArrangementEditorFocus, - /// Display mode of arranger - pub mode: ArrangementEditorMode, - /// Background color of arrangement - pub color: ItemColor, - /// Width and height of arrangement area at last render - pub size: Measure, - /// Whether the arranger is currently focused - pub focused: bool, - /// Whether this is currently in edit mode - pub entered: bool, -} - -/// Display mode of arranger -#[derive(Clone, PartialEq)] -pub enum ArrangementEditorMode { - /// Tracks are rows - Horizontal, - /// Tracks are columns - Vertical(usize), -} - -/// Arranger display mode can be cycled -impl ArrangementEditorMode { - /// 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 Content for ArrangementEditor { - type Engine = Tui; - fn content (&self) -> impl Widget { - Layers::new(move |add|{ - match self.mode { - ArrangementEditorMode::Horizontal => - add(&arranger_content_horizontal(self))?, - ArrangementEditorMode::Vertical(factor) => - add(&arranger_content_vertical(self, factor))? - }; - add(&self.size) - }) - } -} - -impl ArrangementEditor { - pub fn new (model: Arrangement) -> Self { - Self { - model, - selected: ArrangementEditorFocus::Clip(0, 0), - mode: ArrangementEditorMode::Vertical(2), - color: Color::Rgb(28, 35, 25).into(), - size: Measure::new(), - focused: false, - entered: false, - } - } -} - -impl ArrangementEditor { - pub fn track (&self) -> Option<&ArrangementTrack> { - self.selected.track().map(|t|self.model.tracks.get(t)).flatten() - } - pub fn track_mut (&mut self) -> Option<&mut ArrangementTrack> { - self.selected.track().map(|t|self.model.tracks.get_mut(t)).flatten() - } - pub fn scene (&self) -> Option<&ArrangementScene> { - self.selected.scene().map(|s|self.model.scenes.get(s)).flatten() - } - pub fn scene_mut (&mut self) -> Option<&mut ArrangementScene> { - self.selected.scene().map(|s|self.model.scenes.get_mut(s)).flatten() - } - pub fn phrase (&self) -> Option>> { - self.scene()?.clips.get(self.selected.track()?)?.clone() - } - pub fn track_del (&mut self) { - if let Some(index) = self.selected.track() { self.model.track_del(index); } - } - pub fn scene_del (&mut self) { - if let Some(index) = self.selected.scene() { self.model.scene_del(index); } - } - pub fn track_widths (&self) -> Vec<(usize, usize)> { - let mut widths = vec![]; - let mut total = 0; - for track in self.model.tracks.iter() { - let width = track.width; - widths.push((width, total)); - total += width; - } - widths.push((0, total)); - widths - } - pub fn phrase_del (&mut self) { - let track_index = self.selected.track(); - let scene_index = self.selected.scene(); - track_index - .and_then(|index|self.model.tracks.get_mut(index).map(|track|(index, track))) - .map(|(track_index, _)|scene_index - .and_then(|index|self.model.scenes.get_mut(index)) - .map(|scene|scene.clips[track_index] = None)); - } - pub fn phrase_put (&mut self) { - if let ArrangementEditorFocus::Clip(track, scene) = self.selected { - self.model.scenes[scene].clips[track] = Some( - self.model.phrases.read().unwrap().phrase().clone() - ); - } - } - pub fn phrase_get (&mut self) { - if let ArrangementEditorFocus::Clip(track, scene) = self.selected { - if let Some(phrase) = &self.model.scenes[scene].clips[track] { - let mut phrases = self.model.phrases.write().unwrap(); - if let Some(index) = phrases.index_of(&*phrase.read().unwrap()) { - phrases.phrase = index; - } - } - } - } - pub fn phrase_next (&mut self) { - if let ArrangementEditorFocus::Clip(track, scene) = self.selected { - if let Some(ref mut phrase) = self.model.scenes[scene].clips[track] { - let phrases = self.model.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 ArrangementEditorFocus::Clip(track, scene) = self.selected { - if let Some(ref mut phrase) = self.model.scenes[scene].clips[track] { - let phrases = self.model.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(); - } - } - } - } - } -} diff --git a/crates/tek_tui/src/tui_arrangement_cmd.rs b/crates/tek_tui/src/tui_arrangement_cmd.rs deleted file mode 100644 index b6acd34c..00000000 --- a/crates/tek_tui/src/tui_arrangement_cmd.rs +++ /dev/null @@ -1,146 +0,0 @@ -use crate::*; - -#[derive(Clone)] -pub enum ArrangementEditorCommand { - Edit(ArrangementCommand), - Select(ArrangementEditorFocus), - Zoom(usize), -} - -/// Handle events for arrangement. -impl Handle for ArrangementEditor { - fn handle (&mut self, from: &TuiInput) -> Perhaps { - ArrangementEditorCommand::execute_with_state(self, from) - } -} - -impl InputToCommand> for ArrangementEditorCommand { - fn input_to_command (state: &ArrangementEditor, input: &TuiInput) -> Option { - use ArrangementEditorCommand as Cmd; - use ArrangementCommand as Edit; - use ArrangementEditorFocus as Focus; - use ArrangementTrackCommand as Track; - use ArrangementClipCommand as Clip; - use ArrangementSceneCommand as Scene; - Some(match input.event() { - // FIXME: boundary conditions - - key!(KeyCode::Up) => match state.selected { - ArrangementEditorFocus::Mix => return None, - ArrangementEditorFocus::Track(t) => return None, - ArrangementEditorFocus::Scene(s) => Cmd::Select(Focus::Scene(s - 1)), - ArrangementEditorFocus::Clip(t, s) => Cmd::Select(Focus::Clip(t, s - 1)), - }, - - key!(KeyCode::Down) => match state.selected { - ArrangementEditorFocus::Mix => Cmd::Select(Focus::Scene(0)), - ArrangementEditorFocus::Track(t) => Cmd::Select(Focus::Clip(t, 0)), - ArrangementEditorFocus::Scene(s) => Cmd::Select(Focus::Scene(s + 1)), - ArrangementEditorFocus::Clip(t, s) => Cmd::Select(Focus::Clip(t, s + 1)), - }, - - key!(KeyCode::Left) => match state.selected { - ArrangementEditorFocus::Mix => return None, - ArrangementEditorFocus::Track(t) => Cmd::Select(Focus::Track(t - 1)), - ArrangementEditorFocus::Scene(s) => return None, - ArrangementEditorFocus::Clip(t, s) => Cmd::Select(Focus::Clip(t - 1, s)), - }, - - key!(KeyCode::Right) => match state.selected { - ArrangementEditorFocus::Mix => return None, - ArrangementEditorFocus::Track(t) => Cmd::Select(Focus::Track(t + 1)), - ArrangementEditorFocus::Scene(s) => Cmd::Select(Focus::Clip(0, s)), - ArrangementEditorFocus::Clip(t, s) => Cmd::Select(Focus::Clip(t, s - 1)), - }, - - key!(KeyCode::Char('+')) => Cmd::Zoom(0), - - key!(KeyCode::Char('=')) => Cmd::Zoom(0), - - key!(KeyCode::Char('_')) => Cmd::Zoom(0), - - key!(KeyCode::Char('-')) => Cmd::Zoom(0), - - key!(KeyCode::Char('`')) => { todo!("toggle view mode") }, - - key!(KeyCode::Char(',')) => match state.selected { - ArrangementEditorFocus::Mix => Cmd::Zoom(0), - ArrangementEditorFocus::Track(t) => Cmd::Edit(Edit::Track(Track::Swap(0))), - ArrangementEditorFocus::Scene(s) => Cmd::Edit(Edit::Scene(Scene::Swap(0))), - ArrangementEditorFocus::Clip(t, s) => Cmd::Edit(Edit::Clip(Clip::Set(t, s, None))), - }, - - key!(KeyCode::Char('.')) => match state.selected { - ArrangementEditorFocus::Mix => Cmd::Zoom(0), - ArrangementEditorFocus::Track(t) => Cmd::Edit(Edit::Track(Track::Swap(0))), - ArrangementEditorFocus::Scene(s) => Cmd::Edit(Edit::Scene(Scene::Swap(0))), - ArrangementEditorFocus::Clip(t, s) => Cmd::Edit(Edit::Clip(Clip::Set(t, s, None))), - }, - - key!(KeyCode::Char('<')) => match state.selected { - ArrangementEditorFocus::Mix => Cmd::Zoom(0), - ArrangementEditorFocus::Track(t) => Cmd::Edit(Edit::Track(Track::Swap(0))), - ArrangementEditorFocus::Scene(s) => Cmd::Edit(Edit::Scene(Scene::Swap(0))), - ArrangementEditorFocus::Clip(t, s) => Cmd::Edit(Edit::Clip(Clip::Set(t, s, None))), - }, - - key!(KeyCode::Char('>')) => match state.selected { - ArrangementEditorFocus::Mix => Cmd::Zoom(0), - ArrangementEditorFocus::Track(t) => Cmd::Edit(Edit::Track(Track::Swap(0))), - ArrangementEditorFocus::Scene(s) => Cmd::Edit(Edit::Scene(Scene::Swap(0))), - ArrangementEditorFocus::Clip(t, s) => Cmd::Edit(Edit::Clip(Clip::Set(t, s, None))), - }, - - key!(KeyCode::Enter) => match state.selected { - ArrangementEditorFocus::Mix => return None, - ArrangementEditorFocus::Track(t) => return None, - ArrangementEditorFocus::Scene(s) => Cmd::Edit(Edit::Scene(Scene::Play(s))), - ArrangementEditorFocus::Clip(t, s) => return None, - }, - - key!(KeyCode::Delete) => match state.selected { - ArrangementEditorFocus::Mix => Cmd::Edit(Edit::Clear), - ArrangementEditorFocus::Track(t) => Cmd::Edit(Edit::Track(Track::Delete(t))), - ArrangementEditorFocus::Scene(s) => Cmd::Edit(Edit::Scene(Scene::Delete(s))), - ArrangementEditorFocus::Clip(t, s) => Cmd::Edit(Edit::Clip(Clip::Set(t, s, None))), - }, - - key!(KeyCode::Char('c')) => Cmd::Edit(Edit::Clip(Clip::RandomColor)), - - key!(KeyCode::Char('s')) => match state.selected { - ArrangementEditorFocus::Clip(t, s) => Cmd::Edit(Edit::Clip(Clip::Set(t, s, None))), - _ => return None, - }, - - key!(KeyCode::Char('g')) => match state.selected { - ArrangementEditorFocus::Clip(t, s) => Cmd::Edit(Edit::Clip(Clip::Get(t, s))), - _ => return None, - }, - - key!(Ctrl-KeyCode::Char('a')) => Cmd::Edit(Edit::Scene(Scene::Add)), - - key!(Ctrl-KeyCode::Char('t')) => Cmd::Edit(Edit::Track(Track::Add)), - - key!(KeyCode::Char('l')) => Cmd::Edit(Edit::Clip(Clip::SetLoop(false))), - - _ => return None - }) - } -} - -impl Command> for ArrangementEditorCommand { - fn execute (self, view: &mut ArrangementEditor) -> Perhaps { - match self { - Self::Zoom(zoom) => { - todo!(); - }, - Self::Select(selected) => { - view.selected = selected; - }, - Self::Edit(command) => { - return Ok(command.execute(&mut view.model)?.map(Self::Edit)) - }, - } - Ok(None) - } -} diff --git a/crates/tek_tui/src/tui_arrangement_foc.rs b/crates/tek_tui/src/tui_arrangement_foc.rs deleted file mode 100644 index b7b85124..00000000 --- a/crates/tek_tui/src/tui_arrangement_foc.rs +++ /dev/null @@ -1,100 +0,0 @@ -use crate::*; - -#[derive(PartialEq, Clone, Copy)] -/// Represents the current user selection in the arranger -pub enum ArrangementEditorFocus { - /// 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), -} - -/// Focus identification methods -impl ArrangementEditorFocus { - pub fn description ( - &self, - tracks: &Vec, - scenes: &Vec, - ) -> String { - format!("Selected: {}", match self { - Self::Mix => format!("Everything"), - Self::Track(t) => match tracks.get(*t) { - Some(track) => format!("T{t}: {}", &track.name.read().unwrap()), - None => format!("T??"), - }, - Self::Scene(s) => match scenes.get(*s) { - Some(scene) => format!("S{s}: {}", &scene.name.read().unwrap()), - None => format!("S??"), - }, - 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"), - } - }) - } - 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)) } - } - } -} diff --git a/crates/tek_tui/src/tui_arranger.rs b/crates/tek_tui/src/tui_arranger.rs index 545fbcb3..de6dca3b 100644 --- a/crates/tek_tui/src/tui_arranger.rs +++ b/crates/tek_tui/src/tui_arranger.rs @@ -12,22 +12,42 @@ pub struct ArrangerView { pub size: Measure, } +pub struct ArrangementEditor { + pub model: Arrangement, + /// Currently selected element. + pub selected: ArrangementEditorFocus, + /// Display mode of arranger + pub mode: ArrangementEditorMode, + /// Background color of arrangement + pub color: ItemColor, + /// Width and height of arrangement area at last render + pub size: Measure, + /// Whether the arranger is currently focused + pub focused: bool, + /// Whether this is currently in edit mode + pub entered: bool, +} + +/// Display mode of arranger +#[derive(Clone, PartialEq)] +pub enum ArrangementEditorMode { + /// Tracks are rows + Horizontal, + /// Tracks are columns + Vertical(usize), +} + impl Audio for ArrangerView { - #[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { - if self.sequencer.transport.process(client, scope) == Control::Quit { - return Control::Quit - } - if self.arrangement.process(client, scope) == Control::Quit { - return Control::Quit - } + #[inline] fn process (&mut self, _: &Client, _: &ProcessScope) -> Control { + // FIXME: one of these per playing track if let ArrangementEditorFocus::Clip(t, s) = self.arrangement.selected { - let phrase = self.arrangement.state.scenes.get(s).map(|scene|scene.clips.get(t)); + let phrase = self.arrangement.model.scenes.get(s).map(|scene|scene.clips.get(t)); if let Some(Some(Some(phrase))) = phrase { - if let Some(track) = self.arrangement.state.tracks.get(t) { + if let Some(track) = self.arrangement.model.tracks.get(t) { if let Some((ref started_at, Some(ref playing))) = track.player.phrase { let phrase = phrase.read().unwrap(); if *playing.read().unwrap() == *phrase { - let pulse = self.sequencer.transport.state.clock.current.pulse.get(); + let pulse = self.sequencer.transport.model.clock.current.pulse.get(); let start = started_at.pulse.get(); let now = (pulse - start) % phrase.length as f64; self.sequencer.editor.now.set(now); @@ -82,7 +102,7 @@ impl ArrangerView { sequencer: SequencerView, arrangement: ArrangementEditor, ) -> Self { - let mut app = Self { sequencer, arrangement, split: 15, size: Default::default() }; + let mut app = Self { sequencer, arrangement, split: 15, size: Measure::new() }; app.update_focus(); app } @@ -93,8 +113,8 @@ impl ArrangerView { } pub fn next_color (&self) -> ItemColor { if let ArrangementEditorFocus::Clip(track, scene) = self.arrangement.selected { - let track_color = self.arrangement.tracks[track].color; - let scene_color = self.arrangement.scenes[scene].color; + let track_color = self.arrangement.model.tracks[track].color; + let scene_color = self.arrangement.model.scenes[scene].color; track_color.mix(scene_color, 0.5).mix(ItemColor::random(), 0.25) } else { panic!("could not compute next color") @@ -102,31 +122,32 @@ impl ArrangerView { } /// Focus the editor with the current phrase pub fn show_phrase (&mut self) { - self.editor.show(self.arrangement.state.phrase().as_ref()); + self.sequencer.editor.show(self.arrangement.phrase().as_ref()); } /// Focus the editor with the current phrase pub fn edit_phrase (&mut self) { - if self.arrangement.selected.is_clip() && self.arrangement.state.phrase().is_none() { - self.phrases.write().unwrap().append_new(None, Some(self.next_color().into())); + if self.arrangement.selected.is_clip() && self.arrangement.phrase().is_none() { + self.sequencer.phrases.append_new(None, Some(self.next_color().into())); self.arrangement.phrase_put(); } self.show_phrase(); self.focus(ArrangerViewFocus::PhraseEditor); - self.editor.entered = true; + self.sequencer.editor.entered = true; } /// Rename the selected track, scene, or clip pub fn rename_selected (&mut self) { - let Arrangement { selected, ref scenes, .. } = self.arrangement; - match selected { + match self.arrangement.selected { ArrangementEditorFocus::Mix => {}, ArrangementEditorFocus::Track(_) => { todo!("rename track"); }, ArrangementEditorFocus::Scene(_) => { todo!("rename scene"); }, - ArrangementEditorFocus::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(ArrangerViewFocus::PhrasePool); - self.phrases.write().unwrap().phrase = index; - self.phrases.write().unwrap().begin_rename(); + ArrangementEditorFocus::Clip(t, s) => { + if let Some(ref phrase) = self.arrangement.model.scenes[s].clips[t] { + let index = self.sequencer.phrases.index_of(&*phrase.read().unwrap()); + if let Some(index) = index { + self.focus(ArrangerViewFocus::PhrasePool); + self.sequencer.phrases.phrase = index; + self.sequencer.phrases.begin_rename(); + } } }, } @@ -149,11 +170,11 @@ impl ArrangerView { } } pub fn activate (&mut self) { - match self.selected { + match self.arrangement.selected { ArrangementEditorFocus::Scene(s) => { - for (t, track) in self.tracks.iter_mut().enumerate() { + for (t, track) in self.arrangement.model.tracks.iter_mut().enumerate() { let player = &mut track.player; - let clip = self.scenes[s].clips[t].as_ref(); + let clip = self.arrangement.model.scenes[s].clips[t].as_ref(); if player.phrase.is_some() || clip.is_some() { player.enqueue_next(clip); } @@ -165,7 +186,8 @@ impl ArrangerView { //} }, ArrangementEditorFocus::Clip(t, s) => { - self.tracks[t].player.enqueue_next(self.scenes[s].clips[t].as_ref()); + let clip = self.arrangement.model.scenes[s].clips[t].as_ref(); + self.arrangement.model.tracks[t].player.enqueue_next(clip); }, _ => {} } @@ -178,32 +200,6 @@ impl ArrangerView { _ => {} } } - pub fn increment (&mut self) { - match self.arrangement.selected { - ArrangementEditorFocus::Track(_) => self.track_mut().map(|t|t.width_inc()), - ArrangementEditorFocus::Scene(_) => self.scene_next(), - ArrangementEditorFocus::Clip(_, _) => self.phrase_next(), - ArrangementEditorFocus::Mix => self.zoom_in(), - } - } - pub fn decrement (&mut self) { - match self.arrangement.selected { - ArrangementEditorFocus::Track(_) => self.track_mut().map(|t|t.width_dec()), - ArrangementEditorFocus::Scene(_) => self.scene_prev(), - ArrangementEditorFocus::Clip(_, _) => self.phrase_prev(), - ArrangementEditorFocus::Mix => self.zoom_out(), - } - } - pub fn zoom_in (&mut self) { - if let ArrangementEditorMode::Vertical(factor) = self.mode { - self.mode = ArrangementEditorMode::Vertical(factor + 1) - } - } - pub fn zoom_out (&mut self) { - if let ArrangementEditorMode::Vertical(factor) = self.mode { - self.mode = ArrangementEditorMode::Vertical(factor.saturating_sub(1)) - } - } pub fn is_first_row (&self) -> bool { let selected = self.selected; selected.is_mix() || selected.is_track() @@ -211,8 +207,8 @@ impl ArrangerView { pub fn is_last_row (&self) -> bool { let selected = self.selected; (self.scenes.len() == 0 && (selected.is_mix() || selected.is_track())) || match selected { - ArrangementEditorFocus::Scene(s) => s == self.scenes.len() - 1, - ArrangementEditorFocus::Clip(_, s) => s == self.scenes.len() - 1, + ArrangementEditorFocus::Scene(s) => s == self.arrangement.model.scenes.len() - 1, + ArrangementEditorFocus::Clip(_, s) => s == self.arrangement.model.scenes.len() - 1, _ => false } } @@ -221,49 +217,151 @@ impl ArrangerView { phrase.write().unwrap().toggle_loop() } } - pub fn move_back (&mut self) { - match self.selected { - ArrangementEditorFocus::Scene(s) => { - if s > 0 { - self.scenes.swap(s, s - 1); - self.selected = ArrangementEditorFocus::Scene(s - 1); - } - }, - ArrangementEditorFocus::Track(t) => { - if t > 0 { - self.tracks.swap(t, t - 1); - self.selected = ArrangementEditorFocus::Track(t - 1); - // FIXME: also swap clip order in scenes - } - }, - _ => todo!("arrangement: move forward") - } - } - pub fn move_forward (&mut self) { - match self.selected { - ArrangementEditorFocus::Scene(s) => { - if s < self.scenes.len().saturating_sub(1) { - self.scenes.swap(s, s + 1); - self.selected = ArrangementEditorFocus::Scene(s + 1); - } - }, - ArrangementEditorFocus::Track(t) => { - if t < self.tracks.len().saturating_sub(1) { - self.tracks.swap(t, t + 1); - self.selected = ArrangementEditorFocus::Track(t + 1); - // FIXME: also swap clip order in scenes - } - }, - _ => todo!("arrangement: move forward") - } - } pub fn randomize_color (&mut self) { - match self.selected { - ArrangementEditorFocus::Mix => { self.color = ItemColor::random_dark() }, - ArrangementEditorFocus::Track(t) => { self.tracks[t].color = ItemColor::random() }, - ArrangementEditorFocus::Scene(s) => { self.scenes[s].color = ItemColor::random() }, - ArrangementEditorFocus::Clip(t, s) => if let Some(phrase) = &self.scenes[s].clips[t] { - phrase.write().unwrap().color = ItemColorTriplet::random(); + match self.arrangement.selected { + ArrangementEditorFocus::Mix => { + self.arrangement.color = ItemColor::random_dark() + }, + ArrangementEditorFocus::Track(t) => { + self.arrangement.model.tracks[t].color = ItemColor::random() + }, + ArrangementEditorFocus::Scene(s) => { + self.arrangement.model.scenes[s].color = ItemColor::random() + }, + ArrangementEditorFocus::Clip(t, s) => { + if let Some(phrase) = &self.arrangement.model.scenes[s].clips[t] { + phrase.write().unwrap().color = ItemColorTriplet::random(); + } + } + } + } +} + +/// Arranger display mode can be cycled +impl ArrangementEditorMode { + /// 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 Content for ArrangementEditor { + type Engine = Tui; + fn content (&self) -> impl Widget { + Layers::new(move |add|{ + match self.mode { + ArrangementEditorMode::Horizontal => + add(&arranger_content_horizontal(self))?, + ArrangementEditorMode::Vertical(factor) => + add(&arranger_content_vertical(self, factor))? + }; + add(&self.size) + }) + } +} + +impl ArrangementEditor { + pub fn new (model: Arrangement) -> Self { + Self { + model, + selected: ArrangementEditorFocus::Clip(0, 0), + mode: ArrangementEditorMode::Vertical(2), + color: Color::Rgb(28, 35, 25).into(), + size: Measure::new(), + focused: false, + entered: false, + } + } +} + +impl ArrangementEditor { + pub fn track (&self) -> Option<&ArrangementTrack> { + self.selected.track().map(|t|self.model.tracks.get(t)).flatten() + } + pub fn track_mut (&mut self) -> Option<&mut ArrangementTrack> { + self.selected.track().map(|t|self.model.tracks.get_mut(t)).flatten() + } + pub fn scene (&self) -> Option<&ArrangementScene> { + self.selected.scene().map(|s|self.model.scenes.get(s)).flatten() + } + pub fn scene_mut (&mut self) -> Option<&mut ArrangementScene> { + self.selected.scene().map(|s|self.model.scenes.get_mut(s)).flatten() + } + pub fn phrase (&self) -> Option>> { + self.scene()?.clips.get(self.selected.track()?)?.clone() + } + pub fn track_del (&mut self) { + if let Some(index) = self.selected.track() { self.model.track_del(index); } + } + pub fn scene_del (&mut self) { + if let Some(index) = self.selected.scene() { self.model.scene_del(index); } + } + pub fn track_widths (&self) -> Vec<(usize, usize)> { + let mut widths = vec![]; + let mut total = 0; + for track in self.model.tracks.iter() { + let width = track.width; + widths.push((width, total)); + total += width; + } + widths.push((0, total)); + widths + } + pub fn phrase_del (&mut self) { + let track_index = self.selected.track(); + let scene_index = self.selected.scene(); + track_index + .and_then(|index|self.model.tracks.get_mut(index).map(|track|(index, track))) + .map(|(track_index, _)|scene_index + .and_then(|index|self.model.scenes.get_mut(index)) + .map(|scene|scene.clips[track_index] = None)); + } + pub fn phrase_put (&mut self) { + if let ArrangementEditorFocus::Clip(track, scene) = self.selected { + self.model.scenes[scene].clips[track] = Some( + self.model.phrases.phrase().clone() + ); + } + } + pub fn phrase_get (&mut self) { + if let ArrangementEditorFocus::Clip(track, scene) = self.selected { + if let Some(phrase) = &self.model.scenes[scene].clips[track] { + let mut phrases = self.model.phrases.write().unwrap(); + if let Some(index) = phrases.index_of(&*phrase.read().unwrap()) { + phrases.phrase = index; + } + } + } + } + pub fn phrase_next (&mut self) { + if let ArrangementEditorFocus::Clip(track, scene) = self.selected { + if let Some(ref mut phrase) = self.model.scenes[scene].clips[track] { + let phrases = self.model.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 ArrangementEditorFocus::Clip(track, scene) = self.selected { + if let Some(ref mut phrase) = self.model.scenes[scene].clips[track] { + let phrases = self.model.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(); + } + } } } } diff --git a/crates/tek_tui/src/tui_arranger_cmd.rs b/crates/tek_tui/src/tui_arranger_cmd.rs index 236adaf8..1879e070 100644 --- a/crates/tek_tui/src/tui_arranger_cmd.rs +++ b/crates/tek_tui/src/tui_arranger_cmd.rs @@ -88,3 +88,148 @@ impl Command> for ArrangerViewCommand { return Ok(undo); } } + +#[derive(Clone)] +pub enum ArrangementEditorCommand { + Edit(ArrangementCommand), + Select(ArrangementEditorFocus), + Zoom(usize), +} + +/// Handle events for arrangement. +impl Handle for ArrangementEditor { + fn handle (&mut self, from: &TuiInput) -> Perhaps { + ArrangementEditorCommand::execute_with_state(self, from) + } +} + +impl InputToCommand> for ArrangementEditorCommand { + fn input_to_command (state: &ArrangementEditor, input: &TuiInput) -> Option { + use ArrangementEditorCommand as Cmd; + use ArrangementCommand as Edit; + use ArrangementEditorFocus as Focus; + use ArrangementTrackCommand as Track; + use ArrangementClipCommand as Clip; + use ArrangementSceneCommand as Scene; + Some(match input.event() { + // FIXME: boundary conditions + + key!(KeyCode::Up) => match state.selected { + ArrangementEditorFocus::Mix => return None, + ArrangementEditorFocus::Track(t) => return None, + ArrangementEditorFocus::Scene(s) => Cmd::Select(Focus::Scene(s - 1)), + ArrangementEditorFocus::Clip(t, s) => Cmd::Select(Focus::Clip(t, s - 1)), + }, + + key!(KeyCode::Down) => match state.selected { + ArrangementEditorFocus::Mix => Cmd::Select(Focus::Scene(0)), + ArrangementEditorFocus::Track(t) => Cmd::Select(Focus::Clip(t, 0)), + ArrangementEditorFocus::Scene(s) => Cmd::Select(Focus::Scene(s + 1)), + ArrangementEditorFocus::Clip(t, s) => Cmd::Select(Focus::Clip(t, s + 1)), + }, + + key!(KeyCode::Left) => match state.selected { + ArrangementEditorFocus::Mix => return None, + ArrangementEditorFocus::Track(t) => Cmd::Select(Focus::Track(t - 1)), + ArrangementEditorFocus::Scene(s) => return None, + ArrangementEditorFocus::Clip(t, s) => Cmd::Select(Focus::Clip(t - 1, s)), + }, + + key!(KeyCode::Right) => match state.selected { + ArrangementEditorFocus::Mix => return None, + ArrangementEditorFocus::Track(t) => Cmd::Select(Focus::Track(t + 1)), + ArrangementEditorFocus::Scene(s) => Cmd::Select(Focus::Clip(0, s)), + ArrangementEditorFocus::Clip(t, s) => Cmd::Select(Focus::Clip(t, s - 1)), + }, + + key!(KeyCode::Char('+')) => Cmd::Zoom(0), + + key!(KeyCode::Char('=')) => Cmd::Zoom(0), + + key!(KeyCode::Char('_')) => Cmd::Zoom(0), + + key!(KeyCode::Char('-')) => Cmd::Zoom(0), + + key!(KeyCode::Char('`')) => { todo!("toggle view mode") }, + + key!(KeyCode::Char(',')) => match state.selected { + ArrangementEditorFocus::Mix => Cmd::Zoom(0), + ArrangementEditorFocus::Track(t) => Cmd::Edit(Edit::Track(Track::Swap(0))), + ArrangementEditorFocus::Scene(s) => Cmd::Edit(Edit::Scene(Scene::Swap(0))), + ArrangementEditorFocus::Clip(t, s) => Cmd::Edit(Edit::Clip(Clip::Set(t, s, None))), + }, + + key!(KeyCode::Char('.')) => match state.selected { + ArrangementEditorFocus::Mix => Cmd::Zoom(0), + ArrangementEditorFocus::Track(t) => Cmd::Edit(Edit::Track(Track::Swap(0))), + ArrangementEditorFocus::Scene(s) => Cmd::Edit(Edit::Scene(Scene::Swap(0))), + ArrangementEditorFocus::Clip(t, s) => Cmd::Edit(Edit::Clip(Clip::Set(t, s, None))), + }, + + key!(KeyCode::Char('<')) => match state.selected { + ArrangementEditorFocus::Mix => Cmd::Zoom(0), + ArrangementEditorFocus::Track(t) => Cmd::Edit(Edit::Track(Track::Swap(0))), + ArrangementEditorFocus::Scene(s) => Cmd::Edit(Edit::Scene(Scene::Swap(0))), + ArrangementEditorFocus::Clip(t, s) => Cmd::Edit(Edit::Clip(Clip::Set(t, s, None))), + }, + + key!(KeyCode::Char('>')) => match state.selected { + ArrangementEditorFocus::Mix => Cmd::Zoom(0), + ArrangementEditorFocus::Track(t) => Cmd::Edit(Edit::Track(Track::Swap(0))), + ArrangementEditorFocus::Scene(s) => Cmd::Edit(Edit::Scene(Scene::Swap(0))), + ArrangementEditorFocus::Clip(t, s) => Cmd::Edit(Edit::Clip(Clip::Set(t, s, None))), + }, + + key!(KeyCode::Enter) => match state.selected { + ArrangementEditorFocus::Mix => return None, + ArrangementEditorFocus::Track(t) => return None, + ArrangementEditorFocus::Scene(s) => Cmd::Edit(Edit::Scene(Scene::Play(s))), + ArrangementEditorFocus::Clip(t, s) => return None, + }, + + key!(KeyCode::Delete) => match state.selected { + ArrangementEditorFocus::Mix => Cmd::Edit(Edit::Clear), + ArrangementEditorFocus::Track(t) => Cmd::Edit(Edit::Track(Track::Delete(t))), + ArrangementEditorFocus::Scene(s) => Cmd::Edit(Edit::Scene(Scene::Delete(s))), + ArrangementEditorFocus::Clip(t, s) => Cmd::Edit(Edit::Clip(Clip::Set(t, s, None))), + }, + + key!(KeyCode::Char('c')) => Cmd::Edit(Edit::Clip(Clip::RandomColor)), + + key!(KeyCode::Char('s')) => match state.selected { + ArrangementEditorFocus::Clip(t, s) => Cmd::Edit(Edit::Clip(Clip::Set(t, s, None))), + _ => return None, + }, + + key!(KeyCode::Char('g')) => match state.selected { + ArrangementEditorFocus::Clip(t, s) => Cmd::Edit(Edit::Clip(Clip::Get(t, s))), + _ => return None, + }, + + key!(Ctrl-KeyCode::Char('a')) => Cmd::Edit(Edit::Scene(Scene::Add)), + + key!(Ctrl-KeyCode::Char('t')) => Cmd::Edit(Edit::Track(Track::Add)), + + key!(KeyCode::Char('l')) => Cmd::Edit(Edit::Clip(Clip::SetLoop(false))), + + _ => return None + }) + } +} + +impl Command> for ArrangementEditorCommand { + fn execute (self, view: &mut ArrangementEditor) -> Perhaps { + match self { + Self::Zoom(zoom) => { + todo!(); + }, + Self::Select(selected) => { + view.selected = selected; + }, + Self::Edit(command) => { + return Ok(command.execute(&mut view.model)?.map(Self::Edit)) + }, + } + Ok(None) + } +} diff --git a/crates/tek_tui/src/tui_arranger_foc.rs b/crates/tek_tui/src/tui_arranger_foc.rs index a89a7488..871d8240 100644 --- a/crates/tek_tui/src/tui_arranger_foc.rs +++ b/crates/tek_tui/src/tui_arranger_foc.rs @@ -66,3 +66,102 @@ impl FocusGrid for ArrangerView { self.update_status(); } } + +#[derive(PartialEq, Clone, Copy)] +/// Represents the current user selection in the arranger +pub enum ArrangementEditorFocus { + /// 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), +} + +/// Focus identification methods +impl ArrangementEditorFocus { + pub fn description ( + &self, + tracks: &Vec, + scenes: &Vec, + ) -> String { + format!("Selected: {}", match self { + Self::Mix => format!("Everything"), + Self::Track(t) => match tracks.get(*t) { + Some(track) => format!("T{t}: {}", &track.name.read().unwrap()), + None => format!("T??"), + }, + Self::Scene(s) => match scenes.get(*s) { + Some(scene) => format!("S{s}: {}", &scene.name.read().unwrap()), + None => format!("S??"), + }, + 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"), + } + }) + } + 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)) } + } + } +}