use crate::*; /// Root level object for standalone `tek_arranger` pub struct ArrangerView { /// Sequencer component pub sequencer: SequencerView, /// Contains all the sequencers. pub arrangement: ArrangementEditor, /// Height of arrangement pub split: u16, /// Width and height of app at last render pub size: Measure, } 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 } if let ArrangementEditorFocus::Clip(t, s) = self.arrangement.selected { let phrase = self.arrangement.state.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((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 start = started_at.pulse.get(); let now = (pulse - start) % phrase.length as f64; self.sequencer.editor.now.set(now); return Control::Continue } } } } } self.sequencer.editor.now.set(0.); return Control::Continue } } /// Layout for standalone arranger app. impl Content for ArrangerView { type Engine = Tui; fn content (&self) -> impl Widget { Split::up( 1, widget(&self.sequencer.transport), Split::down( self.split, lay!( widget(&self.arrangement) .grow_y(1) .border(Lozenge(Style::default() .bg(TuiTheme::border_bg()) .fg(TuiTheme::border_fg(self.arrangement.focused)))), widget(&self.arrangement.size), widget(&format!("[{}] Arrangement", if self.arrangement.entered { "■" } else { " " })) .fg(TuiTheme::title_fg(self.arrangement.focused)) .push_x(1), ), Split::right( self.sequencer.split, widget(&self.sequencer.phrases), widget(&self.sequencer.editor), ) ) ) } } /// General methods for arranger impl ArrangerView { pub fn new ( sequencer: SequencerView, arrangement: ArrangementEditor, ) -> Self { let mut app = Self { sequencer, arrangement, split: 15, size: Default::default() }; 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)) } 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; track_color.mix(scene_color, 0.5).mix(ItemColor::random(), 0.25) } else { panic!("could not compute next color") } } /// Focus the editor with the current phrase pub fn show_phrase (&mut self) { self.editor.show(self.arrangement.state.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())); self.arrangement.phrase_put(); } self.show_phrase(); self.focus(ArrangerViewFocus::PhraseEditor); self.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 { 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(); } }, } } /// Update status bar pub fn update_status (&mut self) { self.status = match self.focused() { ArrangerViewFocus::Transport => ArrangerStatusBar::Transport, ArrangerViewFocus::Arrangement => match self.arrangement.selected { ArrangementEditorFocus::Mix => ArrangerStatusBar::ArrangementMix, ArrangementEditorFocus::Track(_) => ArrangerStatusBar::ArrangementTrack, ArrangementEditorFocus::Scene(_) => ArrangerStatusBar::ArrangementScene, ArrangementEditorFocus::Clip(_, _) => ArrangerStatusBar::ArrangementClip, }, ArrangerViewFocus::PhrasePool => ArrangerStatusBar::PhrasePool, ArrangerViewFocus::PhraseEditor => match self.editor.entered { true => ArrangerStatusBar::PhraseEdit, false => ArrangerStatusBar::PhraseView, }, } } pub fn activate (&mut self) { match self.selected { ArrangementEditorFocus::Scene(s) => { for (t, track) in self.tracks.iter_mut().enumerate() { let player = &mut track.player; let clip = self.scenes[s].clips[t].as_ref(); if player.phrase.is_some() || clip.is_some() { player.enqueue_next(clip); } } // TODO make transport available here, so that // activating a scene when stopped starts playback //if self.is_stopped() { //self.transport.toggle_play() //} }, ArrangementEditorFocus::Clip(t, s) => { self.tracks[t].player.enqueue_next(self.scenes[s].clips[t].as_ref()); }, _ => {} } } pub fn delete (&mut self) { match self.arrangement.selected { ArrangementEditorFocus::Track(_) => self.track_del(), ArrangementEditorFocus::Scene(_) => self.scene_del(), ArrangementEditorFocus::Clip(_, _) => self.phrase_del(), _ => {} } } 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() } 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, _ => 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 { ArrangementEditorMode::Horizontal => self.track_prev(), _ => self.scene_prev(), }; } pub fn go_down (&mut self) { match self.mode { ArrangementEditorMode::Horizontal => self.track_next(), _ => self.scene_next(), }; } pub fn go_left (&mut self) { match self.mode { ArrangementEditorMode::Horizontal => self.scene_prev(), _ => self.track_prev(), }; } pub fn go_right (&mut self) { match self.mode { ArrangementEditorMode::Horizontal => self.scene_next(), _ => self.track_next(), }; } 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(); } } } }