From 4fd208d53f78657c37d7b9afe201c008e2430181 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 31 May 2024 23:43:12 +0300 Subject: [PATCH] transport is rolling --- Cargo.lock | 6 +- Cargo.toml | 2 +- src/{_sequence.rs => _sequence/mod.rs} | 1 + src/engine.rs | 320 +++++++++++++++++++++++++ src/engine/jack.rs | 178 -------------- src/engine/tui.rs | 54 ----- src/{looper.rs => looper/mod.rs} | 0 src/main.rs | 301 +++++++++++------------ src/mixer/handle.rs | 2 +- src/{mixer.rs => mixer/mod.rs} | 0 src/prelude.rs | 24 +- src/sampler/handle.rs | 7 +- src/{sampler.rs => sampler/mod.rs} | 0 src/sequencer/handle.rs | 2 +- src/{sequencer.rs => sequencer/mod.rs} | 0 src/transport.rs | 83 ------- src/transport/mod.rs | 57 +++++ src/transport/render.rs | 1 - 18 files changed, 540 insertions(+), 498 deletions(-) rename src/{_sequence.rs => _sequence/mod.rs} (99%) delete mode 100644 src/engine/jack.rs delete mode 100644 src/engine/tui.rs rename src/{looper.rs => looper/mod.rs} (100%) rename src/{mixer.rs => mixer/mod.rs} (100%) rename src/{sampler.rs => sampler/mod.rs} (100%) rename src/{sequencer.rs => sequencer/mod.rs} (100%) delete mode 100644 src/transport.rs create mode 100644 src/transport/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 5a0deed6..7916e2ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -132,11 +132,11 @@ checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "crossterm" -version = "0.25.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "crossterm_winapi", "libc", "mio", diff --git a/Cargo.toml b/Cargo.toml index db9dfecc..148a4ac8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,4 +5,4 @@ edition = "2021" [dependencies] jack = "0.10" clap = { version = "4.5.4", features = [ "derive" ] } -crossterm = "0.25" +crossterm = "0.27" diff --git a/src/_sequence.rs b/src/_sequence/mod.rs similarity index 99% rename from src/_sequence.rs rename to src/_sequence/mod.rs index 17e0c371..058bedac 100644 --- a/src/_sequence.rs +++ b/src/_sequence/mod.rs @@ -77,3 +77,4 @@ pub enum Time { //struct Marker { //name: String, //} + diff --git a/src/engine.rs b/src/engine.rs index 9e4daf29..b75991e5 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,2 +1,322 @@ //pub mod jack; //pub mod tui; + +use crate::prelude::*; +use std::sync::mpsc; +use crossterm::event; + +pub trait Exitable { + fn exit (&mut self); + fn exited (&self) -> bool; +} + +#[derive(Debug)] +pub enum Event { + /// An input event that must be handled. + Input(::crossterm::event::Event), + /// Update values but not the whole form. + Update, + /// Update the whole form. + Redraw, +} + +pub struct Engine { + stdout: Stdout, + exited: Arc, + sender: Sender, + receiver: Receiver, + input_thread: JoinHandle<()>, + pub jack_client: Jack, +} + +impl Engine { + + pub fn new (name: Option<&str>) -> Result> { + let (sender, receiver) = mpsc::channel::(); + let exited = Arc::new(AtomicBool::new(false)); + + let jack_client = { + let sender = sender.clone(); + let exited = exited.clone(); + let (client, _status) = Client::new( + name.unwrap_or("blinkenlive"), + ClientOptions::NO_START_SERVER + )?; + let handler = ClosureProcessHandler::new( + Box::new(move |_client: &Client, _ps: &ProcessScope| -> Control { + if exited.fetch_and(true, Ordering::Relaxed) { + Control::Quit + } else { + sender.send(Event::Update); + Control::Continue + } + }) as BoxedProcessHandler + ); + + client.activate_async(Notifications, handler)? + }; + + let input_thread = { + let sender = sender.clone(); + let exited = exited.clone(); + let poll = std::time::Duration::from_millis(100); + spawn(move || { + loop { + // Exit if flag is set + if exited.fetch_and(true, Ordering::Relaxed) { + break + } + // Listen for events and send them to the main thread + if event::poll(poll).is_ok() { + let event = event::read().unwrap(); + if sender.send(Event::Input(event)).is_err() { + break + } + } + } + }) + }; + + Ok(Self { + stdout: stdout(), + exited, + sender, + receiver, + jack_client, + input_thread, + }) + } + + pub fn run ( + &mut self, + 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> { + // JACK thread: + // Input thread: + // Render (main) thread: + crossterm::terminal::enable_raw_mode()?; + let stdout = &mut self.stdout; + stdout + .queue(crossterm::terminal::EnterAlternateScreen)? + .flush()?; + let sleep = std::time::Duration::from_millis(16); + loop { + stdout + .queue(crossterm::terminal::BeginSynchronizedUpdate)? + .queue(Clear(ClearType::All))?; + render(state, stdout, (0, 0))?; + stdout + .queue(crossterm::terminal::EndSynchronizedUpdate)? + .flush()?; + // Handle event if present (`None` redraws) + if let event = self.receiver.recv()? { + handle(state, &event)?; + } + if state.exited() { + self.exited.store(true, Ordering::Relaxed); + stdout + .queue(crossterm::terminal::LeaveAlternateScreen)? + .flush()?; + crossterm::terminal::disable_raw_mode()?; + break + } + std::thread::sleep(sleep); + } + Ok(()) + } + +} + +pub struct Notifications; + +impl NotificationHandler for Notifications { + fn thread_init (&self, _: &Client) { + } + + fn shutdown (&mut self, status: ClientStatus, reason: &str) { + } + + fn freewheel (&mut self, _: &Client, is_enabled: bool) { + } + + fn sample_rate (&mut self, _: &Client, _: Frames) -> Control { + Control::Quit + } + + fn client_registration (&mut self, _: &Client, name: &str, is_reg: bool) { + } + + fn port_registration (&mut self, _: &Client, port_id: PortId, is_reg: bool) { + } + + fn port_rename (&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control { + Control::Continue + } + + fn ports_connected (&mut self, _: &Client, id_a: PortId, id_b: PortId, are: bool) { + } + + fn graph_reorder (&mut self, _: &Client) -> Control { + Control::Continue + } + + fn xrun (&mut self, _: &Client) -> Control { + Control::Continue + } +} + +//pub struct Jack { + //client: OptionControl + Send>> + //>>, + //pub transport: Option, + //audio_ins: BTreeMap>, + //audio_outs: BTreeMap>, + //midi_ins: BTreeMap>, + //midi_outs: BTreeMap>, +//} + +//impl Jack { + //pub fn init_from_cli (options: &Cli) + //-> Result>, Box> + //{ + //let jack = Self::init(&options.jack_client_name)?; + //{ + //let jack = jack.clone(); + //let mut jack = jack.lock().unwrap(); + //for port in options.jack_audio_ins.iter() { + //jack.add_audio_in(port)?; + //} + //for port in options.jack_audio_outs.iter() { + //jack.add_audio_out(port)?; + //} + //for port in options.jack_midi_ins.iter() { + //jack.add_midi_in(port)?; + //} + //for port in options.jack_midi_outs.iter() { + //jack.add_midi_out(port)?; + //} + //} + //Ok(jack.clone()) + //} + //fn init (name: &str) + //-> Result>, Box> + //{ + //let jack = Arc::new(Mutex::new(Self { + //client: None, + //transport: None, + //audio_ins: BTreeMap::new(), + //audio_outs: BTreeMap::new(), + //midi_ins: BTreeMap::new(), + //midi_outs: BTreeMap::new(), + //})); + //let (client, status) = Client::new(name, ClientOptions::NO_START_SERVER)?; + //println!("Client status: {status:?}"); + //let jack1 = jack.clone(); + //let mut jack1 = jack1.lock().unwrap(); + //let jack2 = jack.clone(); + //jack1.transport = Some(client.transport()); + //jack1.client = Some(client.activate_async( + //Notifications, ClosureProcessHandler::new(Box::new( + //move |_client: &Client, _ps: &ProcessScope| -> Control { + //let jack = jack2.lock().expect("Failed to lock jack mutex"); + //jack.read_inputs(); + //jack.write_outputs(); + //Control::Continue + //} + //) as BoxControl + Send>) + //)?); + //Ok(jack) + //} + //fn start (&self) { + //} + //fn process (&self, _: &Client, ps: &ProcessScope) -> Control { + //Control::Continue + //} + //fn add_audio_in (&mut self, name: &str) -> Result<&mut Self, Box> { + //let port = self.client + //.as_ref() + //.expect("Not initialized.") + //.as_client() + //.register_port(name, AudioIn::default())?; + //self.audio_ins.insert(name.into(), port); + //Ok(self) + //} + //fn add_audio_out (&mut self, name: &str) -> Result<&mut Self, Box> { + //let port = self.client + //.as_ref() + //.expect("Not initialized.") + //.as_client() + //.register_port(name, AudioOut::default())?; + //self.audio_outs.insert(name.into(), port); + //Ok(self) + //} + //fn add_midi_in (&mut self, name: &str) -> Result<&mut Self, Box> { + //let port = self.client + //.as_ref() + //.expect("Not initialized.") + //.as_client() + //.register_port(name, MidiIn::default())?; + //self.midi_ins.insert(name.into(), port); + //Ok(self) + //} + //fn add_midi_out (&mut self, name: &str) -> Result<&mut Self, Box> { + //let port = self.client + //.as_ref() + //.expect("Not initialized.") + //.as_client() + //.register_port(name, MidiOut::default())?; + //self.midi_outs.insert(name.into(), port); + //Ok(self) + //} + //fn read_inputs (&self) { + //// read input buffers + ////println!("read"); + //} + //fn write_outputs (&self) { + //// clear output buffers + //// write output buffers + ////println!("write"); + //} +//} + +//struct Notifications; + +//impl NotificationHandler for Notifications { + //fn thread_init (&self, _: &Client) { + //} + + //fn shutdown (&mut self, status: ClientStatus, reason: &str) { + //} + + //fn freewheel (&mut self, _: &Client, is_enabled: bool) { + //} + + //fn sample_rate (&mut self, _: &Client, _: Frames) -> Control { + //Control::Quit + //} + + //fn client_registration (&mut self, _: &Client, name: &str, is_reg: bool) { + //} + + //fn port_registration (&mut self, _: &Client, port_id: PortId, is_reg: bool) { + //} + + //fn port_rename (&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control { + //Control::Continue + //} + + //fn ports_connected (&mut self, _: &Client, id_a: PortId, id_b: PortId, are: bool) { + //} + + //fn graph_reorder (&mut self, _: &Client) -> Control { + //Control::Continue + //} + + //fn xrun (&mut self, _: &Client) -> Control { + //Control::Continue + //} +//} diff --git a/src/engine/jack.rs b/src/engine/jack.rs deleted file mode 100644 index 028d5815..00000000 --- a/src/engine/jack.rs +++ /dev/null @@ -1,178 +0,0 @@ -use std::error::Error; -use std::thread; -use std::collections::BTreeMap; -use std::sync::{Arc, Mutex}; -use crate::Cli; -use jack::{ - Control, - AsyncClient, - Client, - ClientStatus, - ClientOptions, - ClosureProcessHandler, - NotificationHandler, - ProcessScope, - ProcessHandler, - Frames, - Port, - PortId, - AudioIn, - AudioOut, - MidiIn, - MidiOut, - Transport -}; - -//pub struct Jack { - //client: OptionControl + Send>> - //>>, - //pub transport: Option, - //audio_ins: BTreeMap>, - //audio_outs: BTreeMap>, - //midi_ins: BTreeMap>, - //midi_outs: BTreeMap>, -//} - -//impl Jack { - //pub fn init_from_cli (options: &Cli) - //-> Result>, Box> - //{ - //let jack = Self::init(&options.jack_client_name)?; - //{ - //let jack = jack.clone(); - //let mut jack = jack.lock().unwrap(); - //for port in options.jack_audio_ins.iter() { - //jack.add_audio_in(port)?; - //} - //for port in options.jack_audio_outs.iter() { - //jack.add_audio_out(port)?; - //} - //for port in options.jack_midi_ins.iter() { - //jack.add_midi_in(port)?; - //} - //for port in options.jack_midi_outs.iter() { - //jack.add_midi_out(port)?; - //} - //} - //Ok(jack.clone()) - //} - //fn init (name: &str) - //-> Result>, Box> - //{ - //let jack = Arc::new(Mutex::new(Self { - //client: None, - //transport: None, - //audio_ins: BTreeMap::new(), - //audio_outs: BTreeMap::new(), - //midi_ins: BTreeMap::new(), - //midi_outs: BTreeMap::new(), - //})); - //let (client, status) = Client::new(name, ClientOptions::NO_START_SERVER)?; - //println!("Client status: {status:?}"); - //let jack1 = jack.clone(); - //let mut jack1 = jack1.lock().unwrap(); - //let jack2 = jack.clone(); - //jack1.transport = Some(client.transport()); - //jack1.client = Some(client.activate_async( - //Notifications, ClosureProcessHandler::new(Box::new( - //move |_client: &Client, _ps: &ProcessScope| -> Control { - //let jack = jack2.lock().expect("Failed to lock jack mutex"); - //jack.read_inputs(); - //jack.write_outputs(); - //Control::Continue - //} - //) as BoxControl + Send>) - //)?); - //Ok(jack) - //} - //fn start (&self) { - //} - //fn process (&self, _: &Client, ps: &ProcessScope) -> Control { - //Control::Continue - //} - //fn add_audio_in (&mut self, name: &str) -> Result<&mut Self, Box> { - //let port = self.client - //.as_ref() - //.expect("Not initialized.") - //.as_client() - //.register_port(name, AudioIn::default())?; - //self.audio_ins.insert(name.into(), port); - //Ok(self) - //} - //fn add_audio_out (&mut self, name: &str) -> Result<&mut Self, Box> { - //let port = self.client - //.as_ref() - //.expect("Not initialized.") - //.as_client() - //.register_port(name, AudioOut::default())?; - //self.audio_outs.insert(name.into(), port); - //Ok(self) - //} - //fn add_midi_in (&mut self, name: &str) -> Result<&mut Self, Box> { - //let port = self.client - //.as_ref() - //.expect("Not initialized.") - //.as_client() - //.register_port(name, MidiIn::default())?; - //self.midi_ins.insert(name.into(), port); - //Ok(self) - //} - //fn add_midi_out (&mut self, name: &str) -> Result<&mut Self, Box> { - //let port = self.client - //.as_ref() - //.expect("Not initialized.") - //.as_client() - //.register_port(name, MidiOut::default())?; - //self.midi_outs.insert(name.into(), port); - //Ok(self) - //} - //fn read_inputs (&self) { - //// read input buffers - ////println!("read"); - //} - //fn write_outputs (&self) { - //// clear output buffers - //// write output buffers - ////println!("write"); - //} -//} - -//struct Notifications; - -//impl NotificationHandler for Notifications { - //fn thread_init (&self, _: &Client) { - //} - - //fn shutdown (&mut self, status: ClientStatus, reason: &str) { - //} - - //fn freewheel (&mut self, _: &Client, is_enabled: bool) { - //} - - //fn sample_rate (&mut self, _: &Client, _: Frames) -> Control { - //Control::Quit - //} - - //fn client_registration (&mut self, _: &Client, name: &str, is_reg: bool) { - //} - - //fn port_registration (&mut self, _: &Client, port_id: PortId, is_reg: bool) { - //} - - //fn port_rename (&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control { - //Control::Continue - //} - - //fn ports_connected (&mut self, _: &Client, id_a: PortId, id_b: PortId, are: bool) { - //} - - //fn graph_reorder (&mut self, _: &Client) -> Control { - //Control::Continue - //} - - //fn xrun (&mut self, _: &Client) -> Control { - //Control::Continue - //} -//} diff --git a/src/engine/tui.rs b/src/engine/tui.rs deleted file mode 100644 index 434987fd..00000000 --- a/src/engine/tui.rs +++ /dev/null @@ -1,54 +0,0 @@ -use std::error::Error; -use std::io::{stdout, Write}; -use std::thread::spawn; -use std::time::Duration; -use std::sync::{ - Arc, - atomic::{AtomicBool, Ordering}, - mpsc::{channel, Sender, Receiver} -}; - -pub struct Tui { - /// Exit flag. Setting this to true terminates the main loop. - exited: Arc, - /// Receives input events from input thread. - input: Receiver, - /// Currently handled input event - pub event: Option, - /// Output. Terminal commands are written to this. - pub output: Box, - /// Currently available screen area. - pub area: [u16; 4] -} - -impl Tui { - pub fn new () -> Result> { - let output = stdout(); - crossterm::terminal::enable_raw_mode()?; - let (tx, input) = channel::(); - let exited = Arc::new(AtomicBool::new(false)); - // Spawn the input thread - 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 - } - } - } - }); - Ok(Self { - exited, - input, - event: None, - output: Box::new(output), - area: [0, 0, 0, 0] - }) - } -} diff --git a/src/looper.rs b/src/looper/mod.rs similarity index 100% rename from src/looper.rs rename to src/looper/mod.rs diff --git a/src/main.rs b/src/main.rs index 10a12e76..bf96a173 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,9 +27,12 @@ fn main () -> Result<(), Box> { } fn run_one (command: &cli::Command) -> Result<(), Box> { + let mut engine = crate::engine::Engine::new(None)?; + match command { - cli::Command::Transport => main_loop( - &mut transport::Transport::new()?, + + cli::Command::Transport => engine.run( + &mut transport::Transport::new(engine.jack_client.as_client())?, |state, stdout, mut offset| { let (w, h) = render::render_toolbar_vertical(stdout, offset, &transport::ACTIONS)?; offset.0 = offset.0 + w + 2; @@ -37,7 +40,8 @@ fn run_one (command: &cli::Command) -> Result<(), Box> { }, |_, _|Ok(()) ), - cli::Command::Mixer => main_loop( + + cli::Command::Mixer => engine.run( &mut mixer::Mixer::new()?, |state, stdout, mut offset| { let (w, h) = render::render_toolbar_vertical(stdout, offset, &mixer::ACTIONS)?; @@ -46,7 +50,8 @@ fn run_one (command: &cli::Command) -> Result<(), Box> { }, |_, _|Ok(()) ), - cli::Command::Looper => main_loop( + + cli::Command::Looper => engine.run( &mut looper::Looper::new()?, |state, stdout, mut offset| { let (w, h) = render::render_toolbar_vertical(stdout, offset, &looper::ACTIONS)?; @@ -55,7 +60,8 @@ fn run_one (command: &cli::Command) -> Result<(), Box> { }, |_, _|Ok(()) ), - cli::Command::Sampler => main_loop( + + cli::Command::Sampler => engine.run( &mut sampler::Sampler::new()?, |state, stdout, mut offset| { let (w, h) = render::render_toolbar_vertical(stdout, offset, &sampler::ACTIONS)?; @@ -64,7 +70,8 @@ fn run_one (command: &cli::Command) -> Result<(), Box> { }, |_, _|Ok(()) ), - cli::Command::Sequencer => main_loop( + + cli::Command::Sequencer => engine.run( &mut sequencer::Sequencer::new()?, |state, stdout, mut offset| { let (w, h) = render::render_toolbar_vertical(stdout, offset, &sequencer::ACTIONS)?; @@ -73,177 +80,145 @@ fn run_one (command: &cli::Command) -> Result<(), Box> { }, |_, _|Ok(()) ), + } } fn run_all () -> Result<(), Box> { + + let mut engine = crate::engine::Engine::new(None)?; + + 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 + } + } + 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; + let mut state = App { + exited: false, + mode: Mode::Sequencer, + transport: transport::Transport::new(engine.jack_client.as_client())?, + mixer: mixer::Mixer::new()?, + looper: looper::Looper::new()?, + sampler: sampler::Sampler::new()?, + sequencer: sequencer::Sequencer::new()?, + }; - 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)?; + let render = |state: &mut App, stdout: &mut Stdout, mut offset: (u16, u16)| { + 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), + } - 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)?; + let (w, h) = render::render_toolbar_vertical(stdout, (offset.0, offset.1 + 1), &actions)?; + offset.0 = offset.0 + 20; - 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)?; + 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)?; - 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)?; + 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)?; - 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)?; + 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)?; - 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 - )?, + 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(()) + }; + + let handle = |state: &mut App, event: &Event| { + //println!("{event:?}"); + if let Event::Input(crossterm::event::Event::Key(key)) = event { + match key.code { + KeyCode::Char('c') => { + if key.modifiers == KeyModifiers::CONTROL { + state.exit(); } - } - } - 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); - let poll = std::time::Duration::from_millis(100); - crossterm::terminal::enable_raw_mode()?; - let (tx, input) = std::sync::mpsc::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(poll).is_ok() { - if tx.send(crossterm::event::read().unwrap()).is_err() { - break + }, + 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 + )?, } } } - }); - 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(()) + Ok(()) + }; + + engine.run(&mut state, render, handle) + } diff --git a/src/mixer/handle.rs b/src/mixer/handle.rs index a7027d43..57337e0a 100644 --- a/src/mixer/handle.rs +++ b/src/mixer/handle.rs @@ -3,7 +3,7 @@ use super::Mixer; pub fn handle (state: &mut Mixer, event: &Event) -> Result<(), Box> { - if let Event::Key(event) = event { + if let Event::Input(crossterm::event::Event::Key(event)) = event { match event.code { KeyCode::Char('c') => { diff --git a/src/mixer.rs b/src/mixer/mod.rs similarity index 100% rename from src/mixer.rs rename to src/mixer/mod.rs diff --git a/src/prelude.rs b/src/prelude.rs index 3ae2258d..c59d884d 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,6 +1,13 @@ pub use std::error::Error; -pub use std::io::{stdout, Stdout, Write}; -pub use std::thread::spawn; +pub use std::io::{ + stdout, + Stdout, + Write +}; +pub use std::thread::{ + spawn, + JoinHandle +}; pub use std::time::Duration; pub use std::sync::{ Arc, @@ -10,7 +17,7 @@ pub use std::sync::{ }; pub use crossterm::{ QueueableCommand, - event::{Event, KeyCode, KeyModifiers}, + event::{self, KeyCode, KeyModifiers}, cursor::{self, MoveTo, Show, Hide}, terminal::{self, Clear, ClearType}, style::*, @@ -36,11 +43,6 @@ pub use jack::{ TransportState, TransportStatePosition }; -pub type Jack = AsyncClient< - N, - ClosureProcessHandler Control + Send>> ->; -pub trait Exitable { - fn exit (&mut self); - fn exited (&self) -> bool; -} +pub type BoxedProcessHandler = Box Control + Send>; +pub type Jack = AsyncClient>; +pub use crate::engine::{Exitable, Event}; diff --git a/src/sampler/handle.rs b/src/sampler/handle.rs index 8190156b..d613ebb2 100644 --- a/src/sampler/handle.rs +++ b/src/sampler/handle.rs @@ -5,8 +5,9 @@ pub fn handle ( state: &mut Sampler, event: &Event ) -> Result<(), Box> { - use crossterm::event::{Event, KeyCode, KeyModifiers}; - if let Event::Key(event) = event { + + if let Event::Input(crossterm::event::Event::Key(event)) = event { + match event.code { KeyCode::Char('c') => { if event.modifiers == KeyModifiers::CONTROL { @@ -43,7 +44,9 @@ pub fn handle ( println!("{event:?}"); } } + } + Ok(()) } diff --git a/src/sampler.rs b/src/sampler/mod.rs similarity index 100% rename from src/sampler.rs rename to src/sampler/mod.rs diff --git a/src/sequencer/handle.rs b/src/sequencer/handle.rs index cebac790..39f4bc8b 100644 --- a/src/sequencer/handle.rs +++ b/src/sequencer/handle.rs @@ -3,7 +3,7 @@ use super::Sequencer; pub fn handle (state: &mut Sequencer, event: &Event) -> Result<(), Box> { - if let Event::Key(event) = event { + if let Event::Input(crossterm::event::Event::Key(event)) = event { match event.code { KeyCode::Char('c') => { diff --git a/src/sequencer.rs b/src/sequencer/mod.rs similarity index 100% rename from src/sequencer.rs rename to src/sequencer/mod.rs diff --git a/src/transport.rs b/src/transport.rs deleted file mode 100644 index c088c3c1..00000000 --- a/src/transport.rs +++ /dev/null @@ -1,83 +0,0 @@ -mod handle; -mod jack; -mod render; -pub use self::handle::*; -pub use self::jack::*; -pub use self::render::*; -use crate::prelude::*; - -pub const ACTIONS: [(&'static str, &'static str);3] = [ - ("(Shift-)Tab", "Switch pane"), - ("Arrows", "Navigate"), - ("(Shift-)Space", "⯈ Play/pause"), -]; - -pub struct Transport { - exited: bool, - title: String, - client: ::jack::Client, - transport: ::jack::Transport, - //position: Arc>>, - //poll_thread: std::thread::JoinHandle<()>, -} - -impl Transport { - pub fn new () -> Result> { - let (client, status) = Client::new( - "bloop_transport_client", - ClientOptions::NO_START_SERVER - )?; - // Poll transport state every 10ms - //let poll = std::time::Duration::from_millis(16); - //let poll_thread = { - //let transport = transport.clone(); - //let position = position.clone(); - //std::thread::spawn(move || loop { - //std::thread::sleep(poll); - //match transport.query() { - //Ok(state) => *position.lock().unwrap() = Some(state), - //Err(error) => { - //*position.lock().unwrap() = None; - ////println!("{error:?}"); - //} - //} - //}) - //}; - Ok(Self { - exited: false, - title: String::from("Untitled project"), - transport: client.transport(), - client, - //position, - //poll_thread - }) - } - - pub fn play_from_start_or_stop_and_rewind (&mut self) { - } - - pub fn play_or_pause (&mut self) -> Result<(), Box> { - match self.transport.query_state()? { - TransportState::Stopped => self.play(), - TransportState::Rolling => self.stop(), - _ => Ok(()) - } - } - - pub fn play (&mut self) -> Result<(), Box> { - Ok(self.transport.start()?) - } - - pub fn stop (&mut self) -> Result<(), Box> { - Ok(self.transport.stop()?) - } -} - -impl Exitable for Transport { - fn exit (&mut self) { - self.exited = true - } - fn exited (&self) -> bool { - self.exited - } -} diff --git a/src/transport/mod.rs b/src/transport/mod.rs new file mode 100644 index 00000000..fb91d2e0 --- /dev/null +++ b/src/transport/mod.rs @@ -0,0 +1,57 @@ +mod handle; +mod jack; +mod render; +pub use self::handle::*; +pub use self::jack::*; +pub use self::render::*; +use crate::prelude::*; + +pub const ACTIONS: [(&'static str, &'static str);3] = [ + ("(Shift-)Tab", "Switch pane"), + ("Arrows", "Navigate"), + ("(Shift-)Space", "⯈ Play/pause"), +]; + +pub struct Transport { + exited: bool, + title: String, + transport: ::jack::Transport, +} + +impl Transport { + pub fn new (client: &Client) -> Result> { + Ok(Self { + exited: false, + title: String::from("Untitled project"), + transport: client.transport(), + }) + } + + pub fn play_from_start_or_stop_and_rewind (&mut self) { + } + + pub fn play_or_pause (&mut self) -> Result<(), Box> { + match self.transport.query_state()? { + TransportState::Stopped => self.play(), + TransportState::Rolling => self.stop(), + _ => Ok(()) + } + } + + pub fn play (&mut self) -> Result<(), Box> { + Ok(self.transport.start()?) + } + + pub fn stop (&mut self) -> Result<(), Box> { + Ok(self.transport.stop()?) + } +} + +impl Exitable for Transport { + fn exit (&mut self) { + self.exited = true + } + fn exited (&self) -> bool { + self.exited + } +} diff --git a/src/transport/render.rs b/src/transport/render.rs index 038a2fdd..16c6da08 100644 --- a/src/transport/render.rs +++ b/src/transport/render.rs @@ -7,7 +7,6 @@ pub fn render ( mut offset: (u16, u16) ) -> Result<(), Box> { let move_to = |col, row| MoveTo(offset.0 + col, offset.1 + row); - let position = state.transport.query(); stdout.queue(move_to( 1, 0))?.queue( Print("Project: ") )?.queue(move_to(10, 0))?.queue(