use crate::prelude::*; mod grid; pub use self::grid::*; mod handle; pub use self::handle::*; pub struct Launcher { name: String, timebase: Arc, transport: Transport, playing: TransportState, monitoring: bool, recording: bool, overdub: bool, position: usize, cursor: (usize, usize), pub tracks: Vec, scenes: Vec, show_help: bool, view: LauncherView, } pub enum LauncherView { Tracks, Sequencer, Chains, Modal(Box), } impl LauncherView { fn is_tracks (&self) -> bool { match self { Self::Tracks => true, _ => false } } } pub struct Scene { name: String, clips: Vec>, } impl Scene { pub fn new (name: impl AsRef, clips: impl AsRef<[Option]>) -> Self { Self { name: name.as_ref().into(), clips: clips.as_ref().iter().map(|x|x.clone()).collect() } } } impl Launcher { pub fn new ( name: &str, timebase: &Arc, tracks: Option>, scenes: Option> ) -> Result, Box> { let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?; let transport = client.transport(); let ppq = timebase.ppq() as u32; DynamicDevice::new(render, handle, process, Self { name: name.into(), view: LauncherView::Tracks, playing: transport.query_state()?, monitoring: true, recording: false, overdub: true, transport, cursor: (0, 0), position: 0, scenes: scenes.unwrap_or_else(||vec![Scene::new(&"Scene 1", &[None])]), tracks: if let Some(tracks) = tracks { tracks } else { vec![ Track::new("Track 1", &timebase, None, Some(vec![ Phrase::new("MIDI Clip 1", ppq * 4, Some(BTreeMap::from([ ( ppq * 0, vec![MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }] ), ( ppq * 1, vec![MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }] ), ( ppq * 2, vec![MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }] ), ( ppq * 3, vec![MidiMessage::NoteOn { key: 36.into(), vel: 100.into() }] ), ]))) ]))?, ] }, timebase: timebase.clone(), show_help: true }).activate(client) } fn cols (&self) -> usize { (self.tracks.len() + 2) as usize } fn col (&self) -> usize { self.cursor.0 as usize } fn dec_col (&mut self) { self.cursor.0 = if self.cursor.0 > 0 { self.cursor.0 - 1 } else { (self.cols() - 1) as usize } } fn inc_col (&mut self) { self.cursor.0 = if self.cursor.0 >= self.cols() - 1 { 0 } else { self.cursor.0 + 1 } } fn rows (&self) -> usize { (self.scenes.len() + 2) as usize } fn row (&self) -> usize { self.cursor.1 as usize } fn dec_row (&mut self) { self.cursor.1 = if self.cursor.1 > 0 { self.cursor.1 - 1 } else { self.rows() - 1 } } fn inc_row (&mut self) { self.cursor.1 = if self.cursor.1 >= self.rows() - 1 { 0 } else { self.cursor.1 + 1 } } fn active_track (&self) -> Option<&Track> { if self.cursor.0 >= 1 { self.tracks.get(self.cursor.0 as usize - 1 as usize) } else { None } } fn active_sequencer <'a> (&'a self) -> Option> { self.active_track().map(|t|t.sequencer.state()) } fn active_chain <'a> (&'a self) -> Option> { self.active_track().map(|t|t.chain.state()) } } impl DynamicDevice { pub fn connect (self, midi_in: &str, audio_outs: &[&str]) -> Usually { { let state = &self.state(); let (client, _status) = Client::new( &format!("{}-init", &state.name), ClientOptions::NO_START_SERVER )?; let midi_ins = client.ports(Some(midi_in), None, PortFlags::IS_OUTPUT); let audio_outs: Vec> = audio_outs.iter() .map(|pattern|client.ports(Some(pattern), None, PortFlags::IS_INPUT)) .collect(); for (i, sequencer) in state.tracks.iter().enumerate() { for sequencer_midi_in in sequencer.midi_ins()?.iter() { for midi_in in midi_ins.iter() { client.connect_ports_by_name(&midi_in, &sequencer_midi_in)?; } } let chain: &DynamicDevice = &state.tracks[i].chain; for port in sequencer.midi_outs()?.iter() { for midi_in in chain.midi_ins()?.iter() { client.connect_ports_by_name(&port, &midi_in)?; } } for (j, port) in chain.audio_outs()?.iter().enumerate() { for audio_out in audio_outs[j % audio_outs.len()].iter() { client.connect_ports_by_name(&port, &audio_out)?; } } } } Ok(self) } } impl PortList for Launcher {} pub fn process (state: &mut Launcher, _: &Client, _: &ProcessScope) -> Control { let transport = state.transport.query().unwrap(); state.playing = transport.state; state.position = transport.pos.frame() as usize; Control::Continue } pub fn render (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually { let Rect { x, y, width, height } = area; let width = 80; crate::device::sequencer::draw_play_stop(buf, x + 1, y, &state.playing); crate::device::sequencer::draw_rec(buf, x + 12, y, state.recording); crate::device::sequencer::draw_mon(buf, x + 19, y, state.monitoring); crate::device::sequencer::draw_dub(buf, x + 26, y, state.overdub); draw_bpm(buf, x + 33, y, state.timebase.tempo()); draw_timer(buf, x + width - 1, y, &state.timebase, state.position); //let separator = format!("├{}┤", "-".repeat((width - 2).into())); //separator.blit(buf, x, y + 22, Some(Style::default().dim())); //separator.blit(buf, x, y + 41, Some(Style::default().dim())); let mut y = y + 1; y = y + LauncherGridView::new( state, buf, Rect { x, y, width, height: 8 }, state.view.is_tracks() ).draw()?.height; y = y + draw_section_sequencer(state, buf, Rect { x, y, width, height: 8 })?.height; y = y + draw_section_chains(state, buf, Rect { x, y, width, height: 8 })?.height; if state.show_help { let style = Some(Style::default().bold().white().not_dim().on_black().italic()); let hide = "[Left Right] Track [Up Down] Scene [, .] Value [F1] Toggle help "; hide.blit(buf, width - hide.len() as u16, height - 1, style); } Ok(area) } fn draw_bpm (buf: &mut Buffer, x: u16, y: u16, tempo: usize) { let style = Style::default().not_dim(); "BPM" .blit(buf, x, y, Some(style)); format!("{:03}.{:03}", tempo / 1000, tempo % 1000) .blit(buf, x + 4, y, Some(style.bold())); "SYNC" .blit(buf, x + 13, y, Some(style)); "4/4" .blit(buf, x + 18, y, Some(style.bold())); "QUANT" .blit(buf, x + 23, y, Some(style)); "1/16" .blit(buf, x + 29, y, Some(style.bold())); } fn draw_timer (buf: &mut Buffer, x: u16, y: u16, timebase: &Arc, frame: usize) { let tick = (frame as f64 / timebase.frames_per_tick()) as usize; let (beats, ticks) = (tick / timebase.ppq(), tick % timebase.ppq()); let (bars, beats) = (beats / 4, beats % 4); let timer = format!("{}.{}.{ticks:02}", bars + 1, beats + 1); timer.blit(buf, x - timer.len() as u16, y, Some(Style::default().not_dim())); } fn draw_section_sequencer (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually { let Rect { x, y, width, height } = area; let style = Some(Style::default().green().dim()); let view = &state.view; match view { LauncherView::Sequencer => { draw_box_styled(buf, area, style); }, _ => {}, }; if let Some(track) = state.tracks.get(state.col().saturating_sub(1)) { let frame = state.position; let timebase = &state.timebase; let tick = (frame as f64 / timebase.frames_per_tick()) as usize; let state = track.sequencer.state(); let zoom = state.resolution; let step = state.phrase().map(|_|tick / zoom); crate::device::sequencer::horizontal::timer(buf, x+5, y, step.unwrap_or(0) / 4, state.steps * zoom, state.time_axis.0, state.time_axis.1 ); let keys_area = Rect { x, y: y + 1, width, height }; crate::device::sequencer::horizontal::keys(buf, keys_area, state.note_axis.1 )?; if let Some(phrase) = state.phrase() { crate::device::sequencer::horizontal::lanes(buf, x, y + 1, &phrase, state.timebase.ppq() as u32, state.resolution as u32, state.time_axis.0 as u32, state.time_axis.1 as u32, state.note_axis.0 as u32, state.note_axis.1 as u32, ); } let cursor_style = match view { LauncherView::Sequencer => Style::default().green().not_dim(), _ => Style::default().green().dim(), }; crate::device::sequencer::horizontal::cursor(buf, x, y + 1, cursor_style, state.time_cursor, state.note_cursor ); } Ok(area) } fn draw_highlight (buf: &mut Buffer, highlight: &Option, style: Style) { if let Some(area) = highlight { draw_box_styled(buf, *area, Some(style)); } } fn draw_section_chains (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually { let style = Some(Style::default().green().dim()); let chain = state.active_chain(); let plugins = if let Some(chain) = &chain { let (_, plugins) = crate::device::chain::draw_as_row( &*chain, buf, area, style )?; plugins } else { vec![] }; match state.view { LauncherView::Chains => { draw_box_styled(buf, Rect { height: 18, ..area }, style); }, _ => {}, }; draw_highlight(buf, &Some(area), match state.view { LauncherView::Chains => Style::default().green().dim(), _ => Style::default().dim() }); if let Some(chain) = &chain { if let Some(plugin) = plugins.get(chain.focus) { draw_highlight(buf, &Some(*plugin), match state.view { LauncherView::Chains => Style::default().green().not_dim(), _ => Style::default().green().dim() }); } } Ok(area) }