use crate::core::*; use crate::layout::*; use crate::device::*; mod grid; pub use self::grid::*; mod handle; pub use self::handle::*; pub struct Launcher { name: String, timebase: Arc, transport: ::jack::Transport, playing: TransportState, monitoring: bool, recording: bool, overdub: bool, current_frame: 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 usize; 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), current_frame: 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 } } pub 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)) } } } pub 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)) } } } pub fn sequencer <'a> (&'a self) -> Option> { Some(self.track()?.1.sequencer.state()) } pub fn chain <'a> (&'a self) -> Option> { Some(self.track()?.1.chain.state()) } pub 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.current_frame = 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; { use crate::device::transport::*; draw_play_stop(buf, x + 1, y, &state.playing); draw_rec(buf, x + 12, y, state.sequencer().map(|s|s.recording).unwrap_or(false)); draw_mon(buf, x + 19, y, state.sequencer().map(|s|s.monitoring).unwrap_or(false)); draw_dub(buf, x + 26, y, state.sequencer().map(|s|s.overdub).unwrap_or(false)); draw_bpm(buf, x + 33, y, state.timebase.bpm() as usize); draw_timer(buf, x + width - 1, y, &state.timebase, state.current_frame); } let mut y = y + 1; y = y + LauncherGrid::new( state, buf, Rect { x, y, width, height: height/3 }, state.view.is_tracks() ).draw()?.height; y = y + draw_section_chains(state, buf, Rect { x, y, width, height: height/3 })?.height; y = y + draw_section_sequencer(state, buf, Rect { x, y, width, height: height - y })?.height; area.height = y; if state.show_help { let style = Some(Style::default().bold().white().not_dim().on_black().italic()); let hide = "[Tab] Mode [Arrows] Move [.,] Value [F1] Toggle help "; hide.blit(buf, x + (width - hide.len() as u16) / 2, height - 1, style); } Ok(area) } 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); }, _ => {}, }; let track = state.track(); if track.is_none() { return Ok(area); } let track = track.unwrap().1; let sequencer = track.sequencer.state(); crate::device::sequencer::horizontal::draw( buf, area, match state.phrase_id().map(|id|sequencer.phrases.get(id)) { Some(Some(phrase)) => Some(phrase), _ => None }, state.timebase.ppq() as usize, sequencer.time_cursor, sequencer.time_start, sequencer.time_zoom, sequencer.note_cursor, sequencer.note_start, Some(match view { LauncherView::Sequencer => Style::default().green().not_dim(), _ => Style::default().green().dim(), }) ) } 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) }