use crate::prelude::*; pub struct Launcher { name: String, timebase: Arc, transport: Transport, playing: TransportState, monitoring: bool, recording: bool, overdub: bool, cursor: (usize, usize), tracks: Vec>, chains: Vec>, scenes: Vec, show_help: bool, view: LauncherView, } pub enum LauncherView { Tracks, Sequencer, Chains } 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,) -> Result, Box> { let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?; let transport = client.transport(); let timebase = Arc::new(Timebase { rate: AtomicUsize::new(client.sample_rate()), tempo: AtomicUsize::new(113000), ppq: AtomicUsize::new(96), }); DynamicDevice::new(render, handle, process, Self { name: name.into(), view: LauncherView::Tracks, playing: transport.query_state()?, monitoring: true, recording: false, overdub: true, transport, cursor: (1, 2), scenes: vec![ Scene::new(&"Scene#01", &[Some(0), None, None, None]), Scene::new(&"Scene#02", &[None, None, None, None]), Scene::new(&"Scene#03", &[None, None, None, None]), Scene::new(&"Scene#04", &[None, None, None, None]), Scene::new(&"Scene#05", &[None, None, None, None]), Scene::new(&"Scene#06", &[None, None, None, None]), Scene::new(&"Scene#07", &[None, None, None, None]), Scene::new(&"Scene#08", &[None, None, None, None]), ], tracks: vec![ Sequencer::new("Drum", &timebase)?, Sequencer::new("Bass", &timebase)?, Sequencer::new("Pads", &timebase)?, Sequencer::new("Lead", &timebase)?, ], chains: vec![ Chain::new("Chain#0000", vec![ Box::new(Plugin::new("Plugin#000")?), ])?, Chain::new("Chain#0000", vec![ Box::new(Plugin::new("Plugin#001")?), ])?, Chain::new("Chain#0000", vec![ Box::new(Plugin::new("Plugin#002")?), ])?, Chain::new("Chain#0000", vec![ Box::new(Plugin::new("Plugin#003")?), ])?, ], timebase, show_help: true }).activate(client) } fn cols (&self) -> usize { (self.tracks.len() + 1) 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 } } } impl DevicePorts for Launcher {} pub fn process (state: &mut Launcher, _: &Client, _: &ProcessScope) -> Control { state.playing = state.transport.query_state().unwrap(); Control::Continue } pub fn render (state: &Launcher, buf: &mut Buffer, area: Rect) -> Usually { let Rect { x, y, width, height } = area; crate::device::sequencer::draw_timer(buf, x + width - 1, y, 0, 0, 0, 0); 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); let track_area = Rect { x: x, y: y+1, width, height: 22 }; let seq_area = Rect { x: x, y: y+22, width, height: 20 }; let chain_area = Rect { x: x, y: y+41, width, height: 21 }; let separator = format!("├{}┤", "-".repeat((width - 2).into())); let scenes = draw_scenes(state, buf, x, y + 1); separator.blit(buf, x, y + 3, Some(Style::default().dim())); separator.blit(buf, x, y + 5, Some(Style::default().dim())); separator.blit(buf, x, y + 22, Some(Style::default().dim())); separator.blit(buf, x, y + 41, Some(Style::default().dim())); let (w, mut track_highlight) = draw_tracks(state, buf, track_area.x, track_area.y); if state.col() == 0 { track_highlight = Some(scenes); } draw_crossings(state, buf, x + w - 2, y + 1); draw_box(buf, Rect { x, y: y + 1, width, height: height - 1 }); let style = Some(Style::default().green().dim()); let chain = &*state.chains[0].state(); let (_, plugins) = crate::device::chain::draw_as_row(chain, buf, chain_area, style)?; match state.view { LauncherView::Tracks => draw_box_styled(buf, track_area, style), LauncherView::Sequencer => draw_box_styled(buf, seq_area, style), LauncherView::Chains => draw_box_styled(buf, Rect { height: 18, ..chain_area }, style), }; draw_highlight(state, buf, &track_highlight, match state.view { LauncherView::Tracks => Style::default().green().not_dim(), _ => Style::default().green().dim() }); draw_highlight(state, buf, &Some(plugins[chain.focus]), match state.view { LauncherView::Chains => Style::default().green().not_dim(), _ => Style::default().green().dim() }); draw_sequencer(state, buf, seq_area.x, seq_area.y + 1, seq_area.width, seq_area.height - 2)?; 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_scenes ( state: &Launcher, buf: &mut Buffer, x: u16, y: u16, ) -> Rect { let style = Style::default().not_dim().bold(); let row = state.row() as u16; let col = state.col() as u16; let green = |r: u16, c: u16| if row == r && col == c { Style::default().green() } else { Style::default() }; let mut width = 8u16; let mut height = 6u16; format!("{} | ", state.name).blit(buf, x+2, y+1, Some(green(0, 0))); format!("Sync 1/1").blit(buf, x+2, y+3, Some(green(1, 0))); for (scene_index, scene) in state.scenes.iter().enumerate() { let y = y + 5 + scene_index as u16 * 2; let label = format!("▶ {}", &scene.name); width = width.max(label.len() as u16 + 2); label.blit(buf, x + 2, y, Some(green(scene_index as u16 + 2, 0))); height = height + 2; } Rect { x, y, width, height } } fn draw_tracks ( state: &Launcher, buf: &mut Buffer, x: u16, y: u16 ) -> (u16, Option) { let mut w = 15; let mut highlight = None; for (i, track) in state.tracks.iter().enumerate() { let track = track.state(); draw_crossings(state, buf, x + w - 2, y); let width = draw_track(state, buf, x + w, y, i as u16, &track); if i + 1 == state.col() { highlight = Some(Rect { x: x + w - 2, y, width: width + 1, height: 22 }); } w = w + width; } (w, highlight) } fn draw_track ( state: &Launcher, buf: &mut Buffer, x: u16, y: u16, i: u16, track: &Sequencer ) -> u16 { let mut width = 11.max(track.name.len() as u16 + 3); let row = state.row() as u16; let col = state.col() as u16; track.name.blit(buf, x, y + 1, Some( if row == 0 && col == i + 1 { Style::default().green() } else { Style::default() } )); "(global)".blit(buf, x, y + 3, Some( if row == 1 && col == i + 1 { Style::default().green() } else { Style::default().dim() } )); "┊".blit(buf, x - 2, y + 3, Some( Style::default().dim() )); let green = |r: u16, c: u16| if row == r && col == c { Style::default().green() } else { Style::default() }; for (scene_index, scene) in state.scenes.iter().enumerate() { if let Some(Some(sequence_index)) = scene.clips.get(i as usize) { if let Some(sequence) = track.sequences.get(*sequence_index) { width = width.max(sequence.name.len() as u16 + 5); } } } for (scene_index, scene) in state.scenes.iter().enumerate() { let y = y + 5 + scene_index as u16 * 2; let style = if scene_index + 2 == row as usize && i + 1 == col { Style::default().green().bold() } else { Style::default().dim() }; if let Some(Some(sequence_index)) = scene.clips.get(i as usize) { if let Some(sequence) = track.sequences.get(*sequence_index) { let label = format!("▶ {}", &sequence.name); label.blit(buf, x, y, Some(style)); } else { format!("┊ {}", &"?".repeat(track.name.len())) .blit(buf, x - 2, y as u16, Some(Style::default().dim())); } } else { "····".blit(buf, x, y, Some(style.dim())); } let style = Some(style.dim()); "┊".blit(buf, x - 2, y + 0, style); "┊".blit(buf, x - 2, y + 1, style); "┊".blit(buf, x + width - 2, y + 0, style); "┊".blit(buf, x + width - 2, y + 1, style); } width } fn draw_crossings (state: &Launcher, buf: &mut Buffer, x: u16, y: u16) { "╭".blit(buf, x, y + 0, None); "┊".blit(buf, x, y + 1, Some(Style::default().dim())); "┼".blit(buf, x, y + 2, Some(Style::default().dim())); "┊".blit(buf, x, y + 3, Some(Style::default().dim())); "┼".blit(buf, x, y + 4, Some(Style::default().dim())); } fn draw_sequencer ( state: &Launcher, buf: &mut Buffer, x: u16, y: u16, width: u16, height: u16 ) -> Usually<()> { if let Some(sequencer) = state.tracks.get(state.col().saturating_sub(1)) { //crate::device::sequencer::horizontal::footer( //&sequencer.state(), buf, 0, y, width, 0 //); crate::device::sequencer::horizontal::keys( &sequencer.state(), buf, Rect { x, y: y + 1, width, height } )?; crate::device::sequencer::horizontal::lanes( &sequencer.state(), buf, x, y + 1, width, ); crate::device::sequencer::horizontal::cursor( &sequencer.state(), buf, x, y + 1, match state.view { LauncherView::Sequencer => Style::default().green().not_dim(), _ => Style::default().green().dim(), } ); } Ok(()) } fn draw_highlight ( state: &Launcher, buf: &mut Buffer, highlight: &Option, style: Style ) { if let Some(area) = highlight { draw_box_styled(buf, *area, Some(style)); } } pub fn handle (state: &mut Launcher, event: &AppEvent) -> Usually { Ok(handle_keymap(state, event, KEYMAP)? || match state.view { LauncherView::Tracks => { handle_keymap(state, event, KEYMAP_TRACKS)? }, LauncherView::Sequencer => { let i = state.col().saturating_sub(1); if let Some(sequencer) = state.tracks.get_mut(i) { crate::device::sequencer::handle(&mut *sequencer.state(), event)? } else { true } }, LauncherView::Chains => { true } }) } pub const KEYMAP: &'static [KeyBinding] = keymap!(Launcher { [Char('n'), NONE, "rename", "rename current element", rename], [F(1), NONE, "toggle_help", "toggle help", toggle_help], [Tab, SHIFT, "focus_prev", "focus previous area", focus_prev], [Tab, NONE, "focus_next", "focus next area", focus_next], [Char(' '), NONE, "play_toggle", "play or pause", play_toggle], [Char('r'), NONE, "record_toggle", "toggle recording", record_toggle], [Char('d'), NONE, "overdub_toggle", "toggle overdub", overdub_toggle], [Char('m'), NONE, "monitor_toggle", "toggle input monitoring", monitor_toggle], //[Char(' '), SHIFT, "play_start", "play from start", play_start], }); pub const KEYMAP_TRACKS: &'static [KeyBinding] = keymap!(Launcher { [Up, NONE, "cursor_up", "move cursor up", cursor_up], [Down, NONE, "cursor_down", "move cursor down", cursor_down], [Left, NONE, "cursor_left", "move cursor left", cursor_left], [Right, NONE, "cursor_right", "move cursor right", cursor_right], [Char('.'), NONE, "clip_next", "set clip to next phrase", clip_next], [Char(','), NONE, "clip_next", "set clip to last phrase", clip_prev], }); fn rename (_: &mut Launcher) -> Usually { Ok(true) } fn cursor_up (state: &mut Launcher) -> Usually { state.dec_row(); Ok(true) } fn cursor_down (state: &mut Launcher) -> Usually { state.inc_row(); Ok(true) } fn cursor_left (state: &mut Launcher) -> Usually { state.dec_col(); Ok(true) } fn cursor_right (state: &mut Launcher) -> Usually { state.inc_col(); Ok(true) } fn toggle_help (state: &mut Launcher) -> Usually { state.show_help = !state.show_help; Ok(true) } fn focus_next (state: &mut Launcher) -> Usually { state.view = match state.view { LauncherView::Tracks => LauncherView::Sequencer, LauncherView::Sequencer => LauncherView::Chains, LauncherView::Chains => LauncherView::Tracks, }; Ok(true) } fn focus_prev (state: &mut Launcher) -> Usually { state.view = match state.view { LauncherView::Tracks => LauncherView::Chains, LauncherView::Chains => LauncherView::Sequencer, LauncherView::Sequencer => LauncherView::Tracks, }; Ok(true) } fn clip_next (state: &mut Launcher) -> Usually { if state.cursor.0 >= 1 && state.cursor.1 >= 2 { let scene_id = state.cursor.1 - 2; let clip_id = state.cursor.0 - 1; let scene = &mut state.scenes[scene_id]; scene.clips[clip_id] = match scene.clips[clip_id] { None => Some(0), Some(i) => if i >= state.tracks[clip_id].state().sequences.len().saturating_sub(1) { None } else { Some(i + 1) } }; } Ok(true) } fn clip_prev (state: &mut Launcher) -> Usually { if state.cursor.0 >= 1 && state.cursor.1 >= 2 { let scene_id = state.cursor.1 - 2; let clip_id = state.cursor.0 - 1; let scene = &mut state.scenes[scene_id]; scene.clips[clip_id] = match scene.clips[clip_id] { None => Some(state.tracks[clip_id].state().sequences.len().saturating_sub(1)), Some(i) => if i == 0 { None } else { Some(i - 1) } }; } Ok(true) } fn play_toggle (s: &mut Launcher) -> Usually { s.playing = match s.playing { TransportState::Stopped => { s.transport.start()?; TransportState::Starting }, _ => { s.transport.stop()?; s.transport.locate(0)?; TransportState::Stopped }, }; Ok(true) } fn play_start (s: &mut Launcher) -> Usually { unimplemented!() } fn record_toggle (s: &mut Launcher) -> Usually { s.recording = !s.recording; Ok(true) } fn overdub_toggle (s: &mut Launcher) -> Usually { s.overdub = !s.overdub; Ok(true) } fn monitor_toggle (s: &mut Launcher) -> Usually { s.monitoring = !s.monitoring; Ok(true) }