use crate::*; mod arranger_command; pub(crate) use self::arranger_command::*; mod arranger_scene; pub(crate) use self::arranger_scene::*; mod arranger_select; pub(crate) use self::arranger_select::*; mod arranger_track; pub(crate) use self::arranger_track::*; mod arranger_mode; pub(crate) use self::arranger_mode::*; mod arranger_v; #[allow(unused)] pub(crate) use self::arranger_v::*; mod arranger_h; mod arranger_v_clips; pub(crate) use self::arranger_v_clips::*; mod arranger_v_cursor; pub(crate) use self::arranger_v_cursor::*; mod arranger_v_head; pub(crate) use self::arranger_v_head::*; mod arranger_v_io; pub(crate) use self::arranger_v_io::*; mod arranger_v_sep; pub(crate) use self::arranger_v_sep::*; /// Root view for standalone `tek_arranger` pub struct ArrangerTui { jack: Arc>, pub clock: Clock, pub phrases: PoolModel, pub tracks: Vec, pub scenes: Vec, pub splits: [u16;2], pub selected: ArrangerSelection, pub mode: ArrangerMode, pub color: ItemPalette, pub size: Measure, pub note_buf: Vec, pub midi_buf: Vec>>, pub editor: MidiEditor, pub perf: PerfModel, } impl ArrangerTui { pub fn selected (&self) -> ArrangerSelection { self.selected } pub fn selected_mut (&mut self) -> &mut ArrangerSelection { &mut self.selected } pub fn activate (&mut self) -> Usually<()> { if let ArrangerSelection::Scene(s) = self.selected { for (t, track) in self.tracks.iter_mut().enumerate() { let phrase = self.scenes[s].clips[t].clone(); if track.player.play_phrase.is_some() || phrase.is_some() { track.player.enqueue_next(phrase.as_ref()); } } if self.clock().is_stopped() { self.clock().play_from(Some(0))?; } } else if let ArrangerSelection::Clip(t, s) = self.selected { let phrase = self.scenes[s].clips[t].clone(); self.tracks[t].player.enqueue_next(phrase.as_ref()); }; Ok(()) } pub fn selected_phrase (&self) -> Option>> { self.selected_scene()?.clips.get(self.selected.track()?)?.clone() } pub fn toggle_loop (&mut self) { if let Some(phrase) = self.selected_phrase() { phrase.write().unwrap().toggle_loop() } } pub fn randomize_color (&mut self) { match self.selected { ArrangerSelection::Mix => { self.color = ItemPalette::random() }, ArrangerSelection::Track(t) => { self.tracks[t].color = ItemPalette::random() }, ArrangerSelection::Scene(s) => { self.scenes[s].color = ItemPalette::random() }, ArrangerSelection::Clip(t, s) => if let Some(phrase) = &self.scenes[s].clips[t] { phrase.write().unwrap().color = ItemPalette::random(); } } } } from_jack!(|jack| ArrangerTui { let clock = Clock::from(jack); let phrase = Arc::new(RwLock::new(MidiClip::new( "New", true, 4 * clock.timebase.ppq.get() as usize, None, Some(ItemColor::random().into()) ))); Self { clock, phrases: (&phrase).into(), editor: (&phrase).into(), selected: ArrangerSelection::Clip(0, 0), scenes: vec![], tracks: vec![], color: TuiTheme::bg().into(), mode: ArrangerMode::V(1), size: Measure::new(), splits: [12, 20], midi_buf: vec![vec![];65536], note_buf: vec![], perf: PerfModel::default(), jack: jack.clone(), } }); impl ArrangerTui { fn render_mode (state: &Self) -> impl Content + use<'_> { match state.mode { ArrangerMode::H => todo!("horizontal arranger"), ArrangerMode::V(factor) => Self::render_mode_v(state, factor), } } } render!(Tui: (self: ArrangerTui) => { let play = PlayPause(self.clock.is_rolling()); let pool_size = if self.phrases.visible { self.splits[1] } else { 0 }; let with_pool = |x|Bsp::w(Fixed::x(pool_size, PoolView(&self.phrases)), x); let status = ArrangerStatus::from(self); let with_editbar = |x|Bsp::n(Fixed::y(1, MidiEditStatus(&self.editor)), x); let with_status = |x|Bsp::n(Fixed::y(2, status), x); let with_size = |x|lay!(&self.size, x); let arranger = ||{ let color = self.color; lay!( Fill::xy(Tui::bg(color.darkest.rgb, "")), Fill::xy(Outer(Style::default().fg(color.dark.rgb).bg(color.darkest.rgb))), Self::render_mode(self), ) }; with_size(with_status(with_editbar(with_pool(col!( TransportView(&self.clock), Fill::x(Fixed::y(20, arranger())), Fill::xy(&self.editor), ))))) }); audio!(|self: ArrangerTui, client, scope|{ // Start profiling cycle let t0 = self.perf.get_t0(); // Update transport clock if Control::Quit == ClockAudio(self).process(client, scope) { return Control::Quit } // Update MIDI sequencers let tracks = &mut self.tracks; let note_buf = &mut self.note_buf; let midi_buf = &mut self.midi_buf; if Control::Quit == TracksAudio(tracks, note_buf, midi_buf).process(client, scope) { return Control::Quit } // FIXME: one of these per playing track //self.now.set(0.); //if let ArrangerSelection::Clip(t, s) = self.selected { //let phrase = self.scenes.get(s).map(|scene|scene.clips.get(t)); //if let Some(Some(Some(phrase))) = phrase { //if let Some(track) = self.tracks().get(t) { //if let Some((ref started_at, Some(ref playing))) = track.player.play_phrase { //let phrase = phrase.read().unwrap(); //if *playing.read().unwrap() == *phrase { //let pulse = self.current().pulse.get(); //let start = started_at.pulse.get(); //let now = (pulse - start) % phrase.length as f64; //self.now.set(now); //} //} //} //} //} // End profiling cycle self.perf.update(t0, scope); return Control::Continue }); has_clock!(|self: ArrangerTui|&self.clock); has_phrases!(|self: ArrangerTui|self.phrases.phrases); has_editor!(|self: ArrangerTui|self.editor); handle!(|self: ArrangerTui, input|ArrangerCommand::execute_with_state(self, input));