extern crate clap; extern crate jack; extern crate crossterm; use clap::{Parser, Subcommand}; use std::error::Error; pub mod cli; pub mod prelude; pub mod engine; pub mod transport; pub mod mixer; pub mod looper; pub mod sampler; pub mod sequencer; pub mod render; use crate::prelude::*; fn main () -> Result<(), Box> { let cli = cli::Cli::parse(); if let Some(command) = cli.command { run_one(&command) } else { run_all() } } fn run_one (command: &cli::Command) -> Result<(), Box> { match command { cli::Command::Transport => main_loop( &mut transport::Transport::new()?, |state, stdout, mut offset| { let (w, h) = render::render_toolbar_vertical(stdout, offset, &transport::ACTIONS)?; offset.0 = offset.0 + w + 2; transport::render(state, stdout, offset) }, |_, _|Ok(()) ), cli::Command::Mixer => main_loop( &mut mixer::Mixer::new()?, |state, stdout, mut offset| { let (w, h) = render::render_toolbar_vertical(stdout, offset, &mixer::ACTIONS)?; offset.0 = offset.0 + w + 2; mixer::render(state, stdout, offset) }, |_, _|Ok(()) ), cli::Command::Looper => main_loop( &mut looper::Looper::new()?, |state, stdout, mut offset| { let (w, h) = render::render_toolbar_vertical(stdout, offset, &looper::ACTIONS)?; offset.0 = offset.0 + w + 2; looper::render(state, stdout, offset) }, |_, _|Ok(()) ), cli::Command::Sampler => main_loop( &mut sampler::Sampler::new()?, |state, stdout, mut offset| { let (w, h) = render::render_toolbar_vertical(stdout, offset, &sampler::ACTIONS)?; offset.0 = offset.0 + w + 2; sampler::render(state, stdout, offset) }, |_, _|Ok(()) ), cli::Command::Sequencer => main_loop( &mut sequencer::Sequencer::new()?, |state, stdout, mut offset| { let (w, h) = render::render_toolbar_vertical(stdout, offset, &sequencer::ACTIONS)?; offset.0 = offset.0 + w + 2; sequencer::render(state, stdout, offset) }, |_, _|Ok(()) ), } } fn run_all () -> Result<(), Box> { let mut actions = vec![]; main_loop( &mut App { exited: false, mode: Mode::Sequencer, transport: transport::Transport::new()?, mixer: mixer::Mixer::new()?, looper: looper::Looper::new()?, sampler: sampler::Sampler::new()?, sequencer: sequencer::Sequencer::new()?, }, |state, stdout, mut offset| { actions.clear(); actions.extend_from_slice(&transport::ACTIONS); match state.mode { Mode::Transport => {}, Mode::Mixer => actions.extend_from_slice(&mixer::ACTIONS), Mode::Looper => actions.extend_from_slice(&looper::ACTIONS), Mode::Sampler => actions.extend_from_slice(&sampler::ACTIONS), Mode::Sequencer => actions.extend_from_slice(&sequencer::ACTIONS), } let (w, h) = render::render_toolbar_vertical(stdout, (offset.0, offset.1 + 1), &actions)?; offset.0 = offset.0 + 20; transport::render(&mut state.transport, stdout, (offset.0 + 1, 1))?; render::render_box(stdout, Some("Transport"), offset.0, 0, 70, 4, state.mode == Mode::Transport)?; sequencer::render(&mut state.sequencer, stdout, (offset.0 + 1, 3))?; render::render_box(stdout, Some("Sequencer"), offset.0, 5, 70, 6, state.mode == Mode::Sequencer)?; sampler::render(&mut state.sampler, stdout, (offset.0 + 1, 10))?; render::render_box(stdout, Some("Sampler"), offset.0, 12, 70, 4, state.mode == Mode::Sampler)?; mixer::render(&mut state.mixer, stdout, (offset.0 + 1, 18))?; render::render_box(stdout, Some("Mixer"), offset.0, 17, 70, 9, state.mode == Mode::Mixer)?; looper::render(&mut state.looper, stdout, (offset.0 + 1, 28))?; render::render_box(stdout, Some("Looper"), offset.0, 27, 70, 6, state.mode == Mode::Looper)?; Ok(()) }, |state, event| { if let Event::Key(key) = event { match key.code { KeyCode::Char('c') => { if key.modifiers == KeyModifiers::CONTROL { state.exit(); } }, KeyCode::Char(' ') => { if key.modifiers == KeyModifiers::SHIFT { state.transport.play_from_start_or_stop_and_rewind(); } else { state.transport.play_or_pause(); } }, KeyCode::Tab => match state.mode { Mode::Transport => state.mode = Mode::Sequencer, Mode::Sequencer => state.mode = Mode::Sampler, Mode::Sampler => state.mode = Mode::Mixer, Mode::Mixer => state.mode = Mode::Looper, Mode::Looper => state.mode = Mode::Transport, _ => {} }, KeyCode::BackTab => match state.mode { Mode::Transport => state.mode = Mode::Looper, Mode::Sequencer => state.mode = Mode::Transport, Mode::Sampler => state.mode = Mode::Sequencer, Mode::Mixer => state.mode = Mode::Sampler, Mode::Looper => state.mode = Mode::Mixer, _ => {} }, _ => match state.mode { Mode::Transport => transport::handle( &mut state.transport, event )?, Mode::Mixer => mixer::handle( &mut state.mixer, event )?, Mode::Looper => looper::handle( &mut state.looper, event )?, Mode::Sampler => sampler::handle( &mut state.sampler, event )?, Mode::Sequencer => sequencer::handle( &mut state.sequencer, event )?, } } } Ok(()) } ) } struct App { exited: bool, mode: Mode, transport: crate::transport::Transport, mixer: crate::mixer::Mixer, looper: crate::looper::Looper, sampler: crate::sampler::Sampler, sequencer: crate::sequencer::Sequencer, } #[derive(PartialEq)] enum Mode { Transport, Mixer, Looper, Sampler, Sequencer } impl Exitable for App { fn exit (&mut self) { self.exited = true } fn exited (&self) -> bool { self.exited } } pub fn main_loop ( state: &mut T, mut render: impl FnMut(&mut T, &mut Stdout, (u16, u16)) -> Result<(), Box>, mut handle: impl FnMut(&mut T, &Event) -> Result<(), Box>, ) -> Result<(), Box> { let mut stdout = stdout(); let sleep = std::time::Duration::from_millis(16); crossterm::terminal::enable_raw_mode()?; let (tx, input) = channel::(); let exited = Arc::new(AtomicBool::new(false)); let exit_input_thread = exited.clone(); spawn(move || { loop { // Exit if flag is set if exit_input_thread.fetch_and(true, Ordering::Relaxed) { break } // Listen for events and send them to the main thread if crossterm::event::poll(Duration::from_millis(100)).is_ok() { if tx.send(crossterm::event::read().unwrap()).is_err() { break } } } }); stdout.queue(Clear(ClearType::All))?.queue(Hide)?.flush()?; loop { stdout.queue(Clear(ClearType::All))?; render(state, &mut stdout, (0, 0))?; stdout.flush()?; handle(state, &input.recv()?)?; if state.exited() { crossterm::terminal::disable_raw_mode()?; stdout.queue(Clear(ClearType::All))?.queue(Show)?.flush()?; break } } Ok(()) }