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::Chains, playing: transport.query_state()?, transport, timebase: timebase.clone(), monitoring: true, recording: false, overdub: true, cursor: (2, 2), 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() }] ), ]))) ]))?, ] }, 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 track <'a> (&'a self) -> Option<(usize, &'a Track)> { match self.col() { 0 => None, _ => { let id = self.col() as usize - 1; self.tracks.get(id).map(|t|(id, t)) } } } fn scene <'a> (&'a self) -> Option<(usize, &'a Scene)> { match self.row() { 0 => None, _ => { let id = self.row() as usize - 1; self.scenes.get(id).map(|t|(id, t)) } } } fn sequencer <'a> (&'a self) -> Option> { self.track().map(|t|t.1.sequencer.state()) } fn chain <'a> (&'a self) -> Option> { self.track().map(|t|t.1.chain.state()) } fn phrase_id (&self) -> Option { let (track_id, _) = self.track()?; let (_, scene) = self.scene()?; *scene.clips.get(track_id)? } } 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, mut area: Rect) -> Usually { //area.width = 80; // DOS mode //area.height = 25; let Rect { x, y, width, height } = area; 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 mut y = y + 1; y = y + LauncherGridView::new( state, buf, Rect { x, y, width, height: height / 3 }, state.view.is_tracks() ).draw()?.height; y = y + draw_section_sequencer(state, buf, Rect { x, y, width, height: height / 3 })?.height; y = y + draw_section_chains(state, buf, Rect { x, y, width, height: height /3 })?.height; area.height = y; 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 => { lozenge_left(buf, x, y, height, style); lozenge_right(buf, x + width - 1, y, height, style); }, _ => {}, }; if let Some((_, track)) = state.track() { let frame = state.position; let timebase = &state.timebase; let tick = (frame as f64 / timebase.frames_per_tick()) as usize; let sequencer = track.sequencer.state(); let zoom = sequencer.resolution; let steps = if let Some(_phrase) = sequencer.phrase() { 0 } else { 0 } / 4; // TODO crate::device::sequencer::horizontal::timer(buf, x+5, y, steps, sequencer.steps * zoom, sequencer.time_axis.0, sequencer.time_axis.1 ); crate::device::sequencer::horizontal::keys(buf, Rect { x, y: y + 1, width, height }, sequencer.note_axis.1 )?; if let Some(id) = state.phrase_id() { if let Some(phrase) = sequencer.phrases.get(id) { crate::device::sequencer::horizontal::lanes(buf, x, y + 1, &phrase, sequencer.timebase.ppq() as u32, sequencer.resolution as u32, sequencer.time_axis.0 as u32, sequencer.time_axis.1 as u32, sequencer.note_axis.0 as u32, sequencer.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, sequencer.time_cursor, sequencer.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()); match state.view { LauncherView::Chains => { let Rect { x, y, width, height} = area; lozenge_left(buf, x, y, height, style); lozenge_right(buf, x + width - 1, y, height, style); }, _ => {}, }; let chain = state.chain(); let _ = 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) }