From 788dc1ccdefcfdb8248026d797e9e4820c3c6c00 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Wed, 12 Jun 2024 16:55:57 +0300 Subject: [PATCH] refactor: device abstraction, layout components --- .misc/.1.rs | 141 +++++++++++ Cargo.lock | 29 +++ Cargo.toml | 1 + src/device.rs | 360 ++++++++++++++++++++++++--- src/device/README.md | 21 ++ src/device/chain.rs | 21 ++ src/device/launcher.rs | 91 ++++--- src/device/looper.rs | 69 ++--- src/device/mixer.rs | 270 +++++++++----------- src/device/{lv2_host.rs => patch.rs} | 0 src/device/plugin.rs | 19 ++ src/device/port_list.rs | 0 src/device/sampler.rs | 221 +++++++--------- src/device/sequencer.rs | 166 ++++++------ src/device/sequencer/midi.rs | 52 ---- src/device/transport.rs | 162 ++++++------ src/engine.rs | 341 ------------------------- src/layout.rs | 37 +++ src/main.rs | 177 ++----------- src/prelude.rs | 18 +- src/render.rs | 114 ++++----- 21 files changed, 1144 insertions(+), 1166 deletions(-) create mode 100644 .misc/.1.rs create mode 100644 src/device/README.md create mode 100644 src/device/chain.rs rename src/device/{lv2_host.rs => patch.rs} (100%) create mode 100644 src/device/plugin.rs delete mode 100644 src/device/port_list.rs delete mode 100644 src/device/sequencer/midi.rs delete mode 100644 src/engine.rs create mode 100644 src/layout.rs diff --git a/.misc/.1.rs b/.misc/.1.rs new file mode 100644 index 00000000..7b022e70 --- /dev/null +++ b/.misc/.1.rs @@ -0,0 +1,141 @@ + + + + //match cli.command { + //Some(cli::Command::Transport) => engine.run( + //crate::device::transport::Transport::new(engine.jack_client.as_client())?, + //), + //Some(cli::Command::Mixer) => engine.run( + //crate::device::mixer::Mixer::new()?, + //), + //Some(cli::Command::Looper) => engine.run( + //crate::device::looper::Looper::new()?, + //), + //Some(cli::Command::Sampler) => engine.run( + //crate::device::sampler::Sampler::new()?, + //), + //Some(cli::Command::Sequencer { inputs, outputs }) => { + //engine.run(crate::device::sequencer::Sequencer::new( + //Some("Sequencer"), + //Some(&inputs.into_iter().map(|x|x.unwrap()).collect::>()), + //Some(&outputs.into_iter().map(|x|x.unwrap()).collect::>()), + //)?) + //}, + //None => engine.run(App { + //exited: false, + //mode: Mode::Sequencer, + //transport: crate::device::transport::Transport::new( + //engine.jack_client.as_client() + //)?, + //focus: 0, + //devices: vec![ + //crate::device::Device::Sequencer( + //crate::device::sequencer::Sequencer::new( + //Some("Melody#000"), + //None, + //None + //)? + //), + ////crate::device::Device::Sequencer( + ////crate::device::sequencer::Sequencer::new( + ////Some("Rhythm#000"), + ////None, + ////None, + ////)? + ////), + ////crate::device::Device::Mixer( + ////crate::device::mixer::Mixer::new()? + ////), + ////crate::device::Device::Sampler( + ////crate::device::sampler::Sampler::new()? + ////), + ////crate::device::Device::Looper( + ////crate::device::looper::Looper::new()? + ////), + //] + //}) + //} +} + +//struct App { + //exited: bool, + //transport: crate::device::Transport, +//} + +//impl Device for App { + //fn name (&self) -> &str { + //"dawdle" + //} + //fn render (&self, area: Rect, buffer: &mut Buffer) { + //use ratatui::style::Stylize; + //let mut constraints = vec![ + //Constraint::Max(40), + //Constraint::Max(40), + //]; + //let areas = Layout::default() + //.direction(Direction::Horizontal) + //.constraints(&constraints) + //.split(Rect { + //x: area.x, + //y: area.y, + //width: area.width, + //height: area.height - 4 + //}); + + //self.transport.render(Rect { + //x: area.width.saturating_sub(80u16) / 2, + //y: area.y + area.height - 4, + //width: area.width, + //height: 4 + //}, buffer); + + //for (index, device) in self.devices.iter().enumerate() { + //device.render(areas[index], buffer); + //if index == self.focus { + //draw_focus_corners(buffer, areas[index]); + //} + //} + //} + //fn handle (&mut self, event: &Event) -> Result<(), Box> { + //println!("{event:?}"); + //if let Event::Input(crossterm::event::Event::Key(key)) = event { + //match key.code { + //KeyCode::Char('c') => { + //if key.modifiers == KeyModifiers::CONTROL { + //self.exit(); + //} + //}, + //KeyCode::Char(' ') => { + //if key.modifiers == KeyModifiers::SHIFT { + //self.transport.play_from_start_or_stop_and_rewind(); + //} else { + //self.transport.play_or_pause().unwrap(); + //} + //}, + //KeyCode::Tab => { + //self.focus = self.focus + 1; + //if self.focus >= self.devices.len() { + //self.focus = 0; + //} + //}, + //KeyCode::BackTab => { + //if self.focus == 0 { + //self.focus = self.devices.len() - 1; + //} else { + //self.focus = self.focus - 1; + //} + //}, + //_ => { + //self.devices[self.focus].handle(&event)? + //} + //} + //} + //Ok(()) + //} + //fn exit (&mut self) { + //self.exited = true + //} + //fn exited (&self) -> bool { + //self.exited + //} +//} diff --git a/Cargo.lock b/Cargo.lock index 4e1edbb9..b0442fa8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -105,6 +105,16 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "better-panic" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fa9e1d11a268684cbd90ed36370d7577afb6c62d912ddff5c15fc34343e5036" +dependencies = [ + "backtrace", + "console", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -122,6 +132,7 @@ name = "bloop" version = "0.0.0" dependencies = [ "backtrace", + "better-panic", "clap", "crossterm", "jack", @@ -216,6 +227,18 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "crossterm" version = "0.27.0" @@ -247,6 +270,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "equivalent" version = "1.0.1" diff --git a/Cargo.toml b/Cargo.toml index 99ea3033..1ee5e462 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,4 @@ ratatui = { version = "0.26.3", features = [ "unstable-widget-ref" ] } backtrace = "0.3.72" microxdg = "0.1.2" toml = "0.8.12" +better-panic = "0.3.0" diff --git a/src/device.rs b/src/device.rs index 39598de1..6e445eaf 100644 --- a/src/device.rs +++ b/src/device.rs @@ -1,44 +1,338 @@ -pub mod transport; -pub mod sequencer; -pub mod sampler; -pub mod mixer; -pub mod looper; +use crate::prelude::*; + +mod transport; +mod chain; +mod sequencer; +mod sampler; +mod mixer; +mod looper; +mod plugin; + +pub use self::transport::Transport; +pub use self::chain::Chain; +pub use self::sequencer::Sequencer; +pub use self::sampler::Sampler; +pub use self::mixer::Mixer; +pub use self::looper::Looper; +pub use self::plugin::Plugin; use crate::prelude::*; -use self::transport::*; -use self::sequencer::*; -use self::sampler::*; -use self::mixer::*; -use self::looper::*; +use std::sync::mpsc; +use crossterm::event; -pub enum Device { - Transport(self::transport::Transport), - Sequencer(self::sequencer::Sequencer), - Sampler(self::sampler::Sampler), - Mixer(self::mixer::Mixer), - Looper(self::looper::Looper), +pub fn run (device: impl Device + Send + Sync + 'static) -> Result<(), Box> { + let device = Arc::new(Mutex::new(device)); + let exited = Arc::new(AtomicBool::new(false)); + let input_thread = { + let poll = std::time::Duration::from_millis(100); + let exited = exited.clone(); + let device = device.clone(); + 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(); + match event { + crossterm::event::Event::Key(KeyEvent { + code: KeyCode::Char('c'), + modifiers: KeyModifiers::CONTROL, + .. + }) => { + exited.store(true, Ordering::Relaxed); + }, + _ => if device.lock().unwrap().handle(&EngineEvent::Input(event)).is_err() { + break + } + } + } + }) + }; + stdout().execute(EnterAlternateScreen)?; + enable_raw_mode()?; + let mut terminal = ratatui::Terminal::new(CrosstermBackend::new(stdout()))?; + let sleep = std::time::Duration::from_millis(16); + //better_panic::install(); + let better_panic_handler = better_panic::Settings::auto() + .verbosity(better_panic::Verbosity::Full) + .create_panic_handler(); + std::panic::set_hook(Box::new(move |info: &std::panic::PanicInfo|{ + stdout() + .execute(crossterm::terminal::LeaveAlternateScreen) + .unwrap(); + crossterm::terminal::disable_raw_mode() + .unwrap(); + better_panic_handler(info); + //writeln!(std::io::stderr(), "{}", info) + //.unwrap(); + //writeln!(std::io::stderr(), "{:?}", ::backtrace::Backtrace::new()) + //.unwrap(); + })); + loop { + terminal.draw(|frame|{ + let area = frame.size(); + let buffer = frame.buffer_mut(); + device.lock().unwrap().render(buffer, area) + }).expect("Failed to render frame"); + if exited.fetch_and(true, Ordering::Relaxed) { + break + } + std::thread::sleep(sleep); + }; + //render_thread.join().expect("Failed to join render thread"); + stdout() + .queue(crossterm::terminal::LeaveAlternateScreen)? + .flush()?; + crossterm::terminal::disable_raw_mode()?; + Ok(()) } -impl WidgetRef for Device { +pub trait Device: Send + Sync { + fn handle (&mut self, _event: &EngineEvent) -> Result<(), Box> { Ok(()) } + fn render (&self, _buffer: &mut Buffer, _area: Rect) {} + fn process (&mut self, _client: Client, _scope: ProcessScope) {} +} + +pub struct DynamicDevice { + pub state: Mutex, + pub render: Mutex>, + pub handle: MutexResult<(), Box> + Send>>, + pub process: Mutex> +} + +impl DynamicDevice { + fn new <'a, R, H, P> ( + render: R, + handle: H, + process: P, + state: T + ) -> Self where + R: FnMut(&T, &mut Buffer, Rect) + Send + 'static, + H: FnMut(&mut T, &EngineEvent) -> Result<(), Box> + Send + 'static, + P: FnMut(&mut T) + Send + 'static + { + Self { + render: Mutex::new(Box::new(render)), + handle: Mutex::new(Box::new(handle)), + process: Mutex::new(Box::new(process)), + state: Mutex::new(state), + } + } + fn state (&self) -> std::sync::MutexGuard<'_, T> { + self.state.lock().unwrap() + } +} + +impl Device for DynamicDevice { + fn handle (&mut self, event: &EngineEvent) -> Result<(), Box> { + self.handle.lock().unwrap()(&mut *self.state.lock().unwrap(), event) + } + fn render (&self, buf: &mut Buffer, area: Rect) { + self.render.lock().unwrap()(&*self.state.lock().unwrap(), buf, area) + } +} + +impl WidgetRef for &dyn Device { fn render_ref (&self, area: Rect, buf: &mut Buffer) { - match self { - Self::Transport(device) => device.render(area, buf), - Self::Sequencer(device) => device.render(area, buf), - Self::Sampler(device) => device.render(area, buf), - Self::Mixer(device) => device.render(area, buf), - Self::Looper(device) => device.render(area, buf), - } + Device::render(*self, buf, area) } } -impl HandleInput for Device { - fn handle (&mut self, event: &crate::engine::Event) -> Result<(), Box> { - match self { - Self::Transport(device) => device.handle(event), - Self::Sequencer(device) => device.handle(event), - Self::Sampler(device) => device.handle(event), - Self::Mixer(device) => device.handle(event), - Self::Looper(device) => device.handle(event), - } +impl WidgetRef for dyn Device { + fn render_ref (&self, area: Rect, buf: &mut Buffer) { + Device::render(self, buf, area) } } + +pub struct Engine { + exited: Arc, + sender: Sender, + receiver: Receiver, + pub jack_client: Jack, +} + +#[derive(Debug)] +pub enum EngineEvent { + /// 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 fn activate_jack_client ( + client: Client, + notifications: N, + handler: BoxedProcessHandler +) -> Result, Box> { + Ok(client.activate_async(notifications, ClosureProcessHandler::new(handler))?) +} + +fn panic_hook (info: &std::panic::PanicInfo) { + stdout() + .execute(crossterm::terminal::LeaveAlternateScreen) + .unwrap(); + crossterm::terminal::disable_raw_mode() + .unwrap(); + writeln!(std::io::stderr(), "{}", info) + .unwrap(); + writeln!(std::io::stderr(), "{:?}", ::backtrace::Backtrace::new()) + .unwrap(); +} + +pub struct Notifications;//(mpsc::Sender); + +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"); + //} +//} diff --git a/src/device/README.md b/src/device/README.md new file mode 100644 index 00000000..fd8a2878 --- /dev/null +++ b/src/device/README.md @@ -0,0 +1,21 @@ +Device.inputs is Port[] +Device.outputs is Port[] + +Transport is Device +Transport.session is Device + +Mixer is Device +Mixer.tracks is Device[] + +Chain is Device +Chain.devices is Device[] + +Sequencer is Device + +Sampler is Device + +Plugin is Device + +Mixer -> [Track1, Track2... TrackN] + +Track -> [Device1, Device2... N] diff --git a/src/device/chain.rs b/src/device/chain.rs new file mode 100644 index 00000000..3279e60b --- /dev/null +++ b/src/device/chain.rs @@ -0,0 +1,21 @@ +use crate::prelude::*; + +pub struct Chain { + name: String, + devices: Vec> +} + +impl Chain { + pub fn new (name: &str, devices: Vec>) -> Result, Box> { + Ok(DynamicDevice::new(render, handle, |_|{}, Self { + name: name.into(), + devices + })) + } +} + +pub fn render (state: &Chain, buf: &mut Buffer, area: Rect) {} + +pub fn handle (state: &mut Chain, event: &EngineEvent) -> Result<(), Box> { + Ok(()) +} diff --git a/src/device/launcher.rs b/src/device/launcher.rs index f0fd78b4..7c1c3adc 100644 --- a/src/device/launcher.rs +++ b/src/device/launcher.rs @@ -1,37 +1,56 @@ - let mut x = areas[1].x; - for (index, track) in [ - "Track 1", - "Track 2", - "Track 3", - "Track 4", - "Track 5", - "Bus 1", - "Bus 2", - "Mix", - ].iter().enumerate() { - buffer.set_string( - x + 10 * (index + 1) as u16, areas[1].y, - "┬", Style::default().not_bold().dim() - ); - buffer.set_string( - x + 10 * (index + 1) as u16, areas[1].y + areas[1].height - 1, - "┴", Style::default().not_bold().dim() - ); - for y in areas[1].y+1..areas[1].y+areas[1].height - 1 { - buffer.set_string( - x + 10 * (index + 1) as u16, y, - "│", Style::default().not_bold().gray().dim() - ); - } - for y in areas[1].y+2..areas[1].y+areas[1].height - 1 { - buffer.set_string( - x + 10 * index as u16 + 1, y, - "--------", Style::default().not_bold().gray().dim() - ); - } - buffer.set_string( - x + 10 * index as u16 + 1, areas[1].y + 1, - track, Style::default().bold().not_dim() - ); - } +use crate::prelude::*; + +pub struct Launcher { + name: String +} + +impl Launcher { + pub fn new (name: &str) -> Result, Box> { + Ok(DynamicDevice::new(render, handle, |_|{}, Self { + name: name.into(), + })) + } +} + +pub fn render (state: &Launcher, buf: &mut Buffer, area: Rect) {} + +pub fn handle (state: &mut Launcher, event: &Event) -> Result<(), Box> { + Ok(()) +} + //let mut x = areas[1].x; + //for (index, track) in [ + //"Track 1", + //"Track 2", + //"Track 3", + //"Track 4", + //"Track 5", + //"Bus 1", + //"Bus 2", + //"Mix", + //].iter().enumerate() { + //buffer.set_string( + //x + 10 * (index + 1) as u16, areas[1].y, + //"┬", Style::default().not_bold().dim() + //); + //buffer.set_string( + //x + 10 * (index + 1) as u16, areas[1].y + areas[1].height - 1, + //"┴", Style::default().not_bold().dim() + //); + //for y in areas[1].y+1..areas[1].y+areas[1].height - 1 { + //buffer.set_string( + //x + 10 * (index + 1) as u16, y, + //"│", Style::default().not_bold().gray().dim() + //); + //} + //for y in areas[1].y+2..areas[1].y+areas[1].height - 1 { + //buffer.set_string( + //x + 10 * index as u16 + 1, y, + //"--------", Style::default().not_bold().gray().dim() + //); + //} + //buffer.set_string( + //x + 10 * index as u16 + 1, areas[1].y + 1, + //track, Style::default().bold().not_dim() + //); + //} diff --git a/src/device/looper.rs b/src/device/looper.rs index a7e0d462..0fb56538 100644 --- a/src/device/looper.rs +++ b/src/device/looper.rs @@ -1,58 +1,35 @@ use crate::prelude::*; pub struct Looper { - exited: bool + name: String +} + +impl Looper { + pub fn new (name: &str) -> Result, Box> { + Ok(DynamicDevice::new(render, handle, |_|{}, Self { + name: name.into(), + })) + } +} + +pub fn render (state: &Looper, buf: &mut Buffer, area: Rect) { + //let move_to = |col, row| MoveTo(offset.0 + col, offset.1 + row); + //stdout + //.queue(move_to(0, 0))?.queue(Print(" Name Input Length Route"))? + //.queue(move_to(0, 1))?.queue(PrintStyledContent(" Metronome [ ] ████ Track 1".bold()))? + //.queue(move_to(0, 2))?.queue(PrintStyledContent(" Loop 1 [ ] ████ Track 1".bold()))? + //.queue(move_to(0, 3))?.queue(PrintStyledContent(" Loop 2 [ ] ████████ Track 2".bold()))? + //.queue(move_to(0, 4))?.queue(PrintStyledContent(" Loop 3 [ ] ████████ Track 3".bold()))?; +} + +pub fn handle (state: &mut Looper, event: &EngineEvent) -> Result<(), Box> { + Ok(()) } pub const ACTIONS: [(&'static str, &'static str);1] = [ ("Ins/Del", "Add/remove loop"), ]; -impl Looper { - pub fn new () -> Result> { - Ok(Self { exited: false }) - } -} - -impl Exitable for Looper { - fn exit (&mut self) { - self.exited = true - } - fn exited (&self) -> bool { - self.exited - } -} - -impl WidgetRef for Looper { - fn render_ref (&self, area: Rect, buf: &mut Buffer) { - } -} - -pub fn render ( - state: &mut Looper, - stdout: &mut std::io::Stdout, - mut offset: (u16, u16), -) -> Result<(), Box> { - let move_to = |col, row| MoveTo(offset.0 + col, offset.1 + row); - stdout - .queue(move_to(0, 0))?.queue(Print(" Name Input Length Route"))? - .queue(move_to(0, 1))?.queue(PrintStyledContent(" Metronome [ ] ████ Track 1".bold()))? - .queue(move_to(0, 2))?.queue(PrintStyledContent(" Loop 1 [ ] ████ Track 1".bold()))? - .queue(move_to(0, 3))?.queue(PrintStyledContent(" Loop 2 [ ] ████████ Track 2".bold()))? - .queue(move_to(0, 4))?.queue(PrintStyledContent(" Loop 3 [ ] ████████ Track 3".bold()))?; - Ok(()) -} - -impl HandleInput for Looper { - fn handle (&mut self, event: &Event) -> Result<(), Box> { - handle(self, event) - } -} - -pub fn handle (state: &mut Looper, event: &Event) -> Result<(), Box> { - Ok(()) -} - pub struct Notifications; impl NotificationHandler for Notifications { diff --git a/src/device/mixer.rs b/src/device/mixer.rs index 4b2f4f56..2f5b26e8 100644 --- a/src/device/mixer.rs +++ b/src/device/mixer.rs @@ -1,45 +1,17 @@ use crate::prelude::*; -// TODO: -// - Meters: propagate clipping: -// - If one stage clips, all stages after it are marked red -// - If one track clips, all tracks that feed from it are marked red? - -pub const ACTIONS: [(&'static str, &'static str);2] = [ - ("+/-", "Adjust"), - ("Ins/Del", "Add/remove track"), -]; - pub struct Mixer { - exited: bool, + name: String, jack: Jack, tracks: Vec, selected_track: usize, selected_column: usize, } -pub struct Track { - name: String, - channels: u8, - input_ports: Vec>, - pre_gain_meter: f64, - gain: f64, - insert_ports: Vec>, - return_ports: Vec>, - post_gain_meter: f64, - post_insert_meter: f64, - level: f64, - pan: f64, - output_ports: Vec>, - post_fader_meter: f64, - route: String, -} - impl Mixer { - - pub fn new () -> Result> { + pub fn new (name: &str) -> Result, Box> { let (client, status) = Client::new( - "bloop-mixer", + name, ClientOptions::NO_START_SERVER )?; let jack = client.activate_async( @@ -50,8 +22,8 @@ impl Mixer { }) as BoxControl + Send> ) )?; - Ok(Self { - exited: false, + Ok(DynamicDevice::new(render, handle, |_|{}, Self { + name: name.into(), selected_column: 0, selected_track: 1, tracks: vec![ @@ -65,129 +37,74 @@ impl Mixer { Track::new(&jack.as_client(), 2, "Mix")?, ], jack, - }) - } - -} - -impl Track { - pub fn new (jack: &Client, channels: u8, name: &str) -> Result> { - let mut input_ports = vec![]; - let mut insert_ports = vec![]; - let mut return_ports = vec![]; - let mut output_ports = vec![]; - for channel in 1..=channels { - input_ports.push(jack.register_port(&format!("{name} [input {channel}]"), AudioIn::default())?); - output_ports.push(jack.register_port(&format!("{name} [out {channel}]"), AudioOut::default())?); - let insert_port = jack.register_port(&format!("{name} [pre {channel}]"), AudioOut::default())?; - let return_port = jack.register_port(&format!("{name} [insert {channel}]"), AudioIn::default())?; - jack.connect_ports(&insert_port, &return_port)?; - insert_ports.push(insert_port); - return_ports.push(return_port); - } - Ok(Self { - name: name.into(), - channels, - input_ports, - pre_gain_meter: 0.0, - gain: 0.0, - post_gain_meter: 0.0, - insert_ports, - return_ports, - post_insert_meter: 0.0, - level: 0.0, - pan: 0.0, - post_fader_meter: 0.0, - route: "---".into(), - output_ports, - }) + })) } } -impl Exitable for Mixer { - fn exit (&mut self) { - self.exited = true - } - fn exited (&self) -> bool { - self.exited - } -} - -impl WidgetRef for Mixer { - fn render_ref (&self, area: Rect, buf: &mut Buffer) { - use ratatui::style::Stylize; - draw_box(buf, area); - let x = area.x + 1; - let y = area.y + 1; - let h = area.height - 2; - for (i, track) in self.tracks.iter().enumerate() { - //buf.set_string( - //x, y + index as u16, - //&track.name, Style::default().bold().not_dim() - //); - for (j, (column, field)) in [ - (0, format!(" {:7} ", track.name)), - (12, format!(" {:.1}dB ", track.gain)), - (22, format!(" [ ] ")), - (30, format!(" C ")), - (35, format!(" {:.1}dB ", track.level)), - (45, format!(" [ ] ")), - (51, format!(" {:7} ", track.route)), - ].into_iter().enumerate() { - buf.set_string( - x + column as u16, - y + i as u16, - field, - if self.selected_track == i && self.selected_column == j { - Style::default().white().bold().not_dim() - } else { - Style::default().not_dim() - } - ); - //stdout.queue(move_to(column, row))?; - //if state.selected_track == i && state.selected_column == j { - //stdout.queue(PrintStyledContent(field.to_string().bold().reverse()))?; - //} else { - //stdout.queue(PrintStyledContent(field.to_string().bold()))?; +pub fn render (state: &Mixer, buf: &mut Buffer, area: Rect) { + draw_box(buf, area); + let x = area.x + 1; + let y = area.y + 1; + let h = area.height - 2; + for (i, track) in state.tracks.iter().enumerate() { + //buf.set_string( + //x, y + index as u16, + //&track.name, Style::default().bold().not_dim() + //); + for (j, (column, field)) in [ + (0, format!(" {:7} ", track.name)), + (12, format!(" {:.1}dB ", track.gain)), + (22, format!(" [ ] ")), + (30, format!(" C ")), + (35, format!(" {:.1}dB ", track.level)), + (45, format!(" [ ] ")), + (51, format!(" {:7} ", track.route)), + ].into_iter().enumerate() { + buf.set_string( + x + column as u16, + y + i as u16, + field, + if state.selected_track == i && state.selected_column == j { + Style::default().white().bold().not_dim() + } else { + Style::default().not_dim() + } + ); + //stdout.queue(move_to(column, row))?; + //if state.selected_track == i && state.selected_column == j { + //stdout.queue(PrintStyledContent(field.to_string().bold().reverse()))?; + //} else { + //stdout.queue(PrintStyledContent(field.to_string().bold()))?; + //} + //fn render_meters ( + //state: &mut Mixer, + //stdout: &mut Stdout, + //offset: (u16, u16) + //) -> Result<(), Box> { + //let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row); + //for (i, track) in state.tracks.iter().enumerate() { + //let row = (i + 1) as u16; + //stdout + //.queue(move_to(10, row))?.queue(PrintStyledContent("▁".green()))? + //.queue(move_to(20, row))?.queue(PrintStyledContent("▁".green()))? + //.queue(move_to(28, row))?.queue(PrintStyledContent("▁".green()))? + //.queue(move_to(43, row))?.queue(PrintStyledContent("▁".green()))?; //} - //fn render_meters ( - //state: &mut Mixer, - //stdout: &mut Stdout, - //offset: (u16, u16) - //) -> Result<(), Box> { - //let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row); - //for (i, track) in state.tracks.iter().enumerate() { - //let row = (i + 1) as u16; - //stdout - //.queue(move_to(10, row))?.queue(PrintStyledContent("▁".green()))? - //.queue(move_to(20, row))?.queue(PrintStyledContent("▁".green()))? - //.queue(move_to(28, row))?.queue(PrintStyledContent("▁".green()))? - //.queue(move_to(43, row))?.queue(PrintStyledContent("▁".green()))?; - //} - //Ok(()) - //} - } + //Ok(()) + //} } } } - -impl HandleInput for Mixer { - fn handle (&mut self, event: &Event) -> Result<(), Box> { - handle(self, event) - } -} - -pub fn handle (state: &mut Mixer, event: &Event) -> Result<(), Box> { - - if let Event::Input(crossterm::event::Event::Key(event)) = event { +pub fn handle (state: &mut Mixer, event: &EngineEvent) -> Result<(), Box> { + if let EngineEvent::Input(crossterm::event::Event::Key(event)) = event { match event.code { - KeyCode::Char('c') => { - if event.modifiers == KeyModifiers::CONTROL { - state.exit(); - } - }, + //KeyCode::Char('c') => { + //if event.modifiers == KeyModifiers::CONTROL { + //state.exit(); + //} + //}, KeyCode::Down => { state.selected_track = (state.selected_track + 1) % state.tracks.len(); println!("{}", state.selected_track); @@ -224,6 +141,67 @@ pub fn handle (state: &mut Mixer, event: &Event) -> Result<(), Box> { Ok(()) } +// TODO: +// - Meters: propagate clipping: +// - If one stage clips, all stages after it are marked red +// - If one track clips, all tracks that feed from it are marked red? + +pub const ACTIONS: [(&'static str, &'static str);2] = [ + ("+/-", "Adjust"), + ("Ins/Del", "Add/remove track"), +]; + +pub struct Track { + name: String, + channels: u8, + input_ports: Vec>, + pre_gain_meter: f64, + gain: f64, + insert_ports: Vec>, + return_ports: Vec>, + post_gain_meter: f64, + post_insert_meter: f64, + level: f64, + pan: f64, + output_ports: Vec>, + post_fader_meter: f64, + route: String, +} + +impl Track { + pub fn new (jack: &Client, channels: u8, name: &str) -> Result> { + let mut input_ports = vec![]; + let mut insert_ports = vec![]; + let mut return_ports = vec![]; + let mut output_ports = vec![]; + for channel in 1..=channels { + input_ports.push(jack.register_port(&format!("{name} [input {channel}]"), AudioIn::default())?); + output_ports.push(jack.register_port(&format!("{name} [out {channel}]"), AudioOut::default())?); + let insert_port = jack.register_port(&format!("{name} [pre {channel}]"), AudioOut::default())?; + let return_port = jack.register_port(&format!("{name} [insert {channel}]"), AudioIn::default())?; + jack.connect_ports(&insert_port, &return_port)?; + insert_ports.push(insert_port); + return_ports.push(return_port); + } + Ok(Self { + name: name.into(), + channels, + input_ports, + pre_gain_meter: 0.0, + gain: 0.0, + post_gain_meter: 0.0, + insert_ports, + return_ports, + post_insert_meter: 0.0, + level: 0.0, + pan: 0.0, + post_fader_meter: 0.0, + route: "---".into(), + output_ports, + }) + } +} + pub struct Notifications; impl NotificationHandler for Notifications { diff --git a/src/device/lv2_host.rs b/src/device/patch.rs similarity index 100% rename from src/device/lv2_host.rs rename to src/device/patch.rs diff --git a/src/device/plugin.rs b/src/device/plugin.rs new file mode 100644 index 00000000..454980f8 --- /dev/null +++ b/src/device/plugin.rs @@ -0,0 +1,19 @@ +use crate::prelude::*; + +pub struct Plugin { + name: String +} + +impl Plugin { + pub fn new (name: &str) -> Result, Box> { + Ok(DynamicDevice::new(render, handle, |_|{}, Self { + name: name.into() + })) + } +} + +pub fn render (state: &Plugin, buf: &mut Buffer, area: Rect) {} + +pub fn handle (state: &mut Plugin, event: &EngineEvent) -> Result<(), Box> { + Ok(()) +} diff --git a/src/device/port_list.rs b/src/device/port_list.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/src/device/sampler.rs b/src/device/sampler.rs index 37b02c22..b02c7c32 100644 --- a/src/device/sampler.rs +++ b/src/device/sampler.rs @@ -6,20 +6,17 @@ pub const ACTIONS: [(&'static str, &'static str);2] = [ ]; pub struct Sampler { - exited: Arc, - jack: Jack, + name: String, + jack: Jack, samples: Arc>>, selected_sample: usize, selected_column: usize, } impl Sampler { - pub fn new () -> Result> { + pub fn new (name: &str) -> Result, Box> { let exited = Arc::new(AtomicBool::new(false)); - let (client, status) = Client::new( - "Sampler#000", - ClientOptions::NO_START_SERVER - )?; + let (client, status) = Client::new(name, ClientOptions::NO_START_SERVER)?; let samples = vec![ Sample::new("Kick", &client, 1, 35)?, Sample::new("Snare", &client, 1, 38)?, @@ -64,8 +61,8 @@ impl Sampler { Control::Continue }) }); - Ok(Self { - exited, + Ok(DynamicDevice::new(render, handle, |_|{}, Self { + name: name.into(), selected_sample: 0, selected_column: 0, samples, @@ -73,7 +70,7 @@ impl Sampler { self::Notifications, ClosureProcessHandler::new(handler) )?, - }) + })) } } @@ -109,131 +106,97 @@ impl Sample { } -impl Exitable for Sampler { - fn exit (&mut self) { - self.exited.store(true, Ordering::Relaxed) - } - fn exited (&self) -> bool { - self.exited.fetch_and(true, Ordering::Relaxed) - } +pub fn render (state: &Sampler, buf: &mut Buffer, area: Rect) { + //render_table(state, stdout, offset)?; + //render_meters(state, stdout, offset)?; } -impl WidgetRef for Sampler { - fn render_ref (&self, area: Rect, buf: &mut Buffer) { - } -} +//fn render_table ( + //state: &mut Sampler, + //stdout: &mut Stdout, + //offset: (u16, u16), +//) -> Result<(), Box> { + //let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row); + //stdout.queue(move_to(0, 3))?.queue( + //Print(" Name Rate Trigger Route") + //)?; + //for (i, sample) in state.samples.lock().unwrap().iter().enumerate() { + //let row = 4 + i as u16; + //for (j, (column, field)) in [ + //(0, format!(" {:7} ", sample.name)), + //(9, format!(" {:.1}Hz ", sample.rate)), + //(18, format!(" MIDI C{} {} ", sample.trigger.0, sample.trigger.1)), + //(33, format!(" {:.1}dB -> Output ", sample.gain)), + //(50, format!(" {} ", sample.playing.unwrap_or(0))), + //].into_iter().enumerate() { + //stdout.queue(move_to(column, row))?; + //if state.selected_sample == i && state.selected_column == j { + //stdout.queue(PrintStyledContent(field.to_string().bold().reverse()))?; + //} else { + //stdout.queue(PrintStyledContent(field.to_string().bold()))?; + //} + //} + //} + //Ok(()) +//} -pub fn render ( - state: &mut Sampler, - stdout: &mut Stdout, - offset: (u16, u16), -) -> Result<(), Box> { - render_table(state, stdout, offset)?; - render_meters(state, stdout, offset)?; +//fn render_meters ( + //state: &mut Sampler, + //stdout: &mut Stdout, + //offset: (u16, u16), +//) -> Result<(), Box> { + //let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row); + //for (i, sample) in state.samples.lock().iter().enumerate() { + //let row = 4 + i as u16; + //stdout.queue(move_to(32, row))?.queue( + //PrintStyledContent("▁".green()) + //)?; + //} + //Ok(()) +//} + +pub fn handle (state: &mut Sampler, event: &EngineEvent) -> Result<(), Box> { + //if let Event::Input(crossterm::event::Event::Key(event)) = event { + //match event.code { + //KeyCode::Char('c') => { + //if event.modifiers == KeyModifiers::CONTROL { + //state.exit(); + //} + //}, + //KeyCode::Down => { + //state.selected_sample = (state.selected_sample + 1) % state.samples.lock().unwrap().len(); + //println!("{}", state.selected_sample); + //}, + //KeyCode::Up => { + //if state.selected_sample == 0 { + //state.selected_sample = state.samples.lock().unwrap().len() - 1; + //} else { + //state.selected_sample = state.selected_sample - 1; + //} + //println!("{}", state.selected_sample); + //}, + //KeyCode::Left => { + //if state.selected_column == 0 { + //state.selected_column = 6 + //} else { + //state.selected_column = state.selected_column - 1; + //} + //}, + //KeyCode::Right => { + //if state.selected_column == 6 { + //state.selected_column = 0 + //} else { + //state.selected_column = state.selected_column + 1; + //} + //}, + //_ => { + //println!("{event:?}"); + //} + //} + //} Ok(()) } -fn render_table ( - state: &mut Sampler, - stdout: &mut Stdout, - offset: (u16, u16), -) -> Result<(), Box> { - let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row); - stdout.queue(move_to(0, 3))?.queue( - Print(" Name Rate Trigger Route") - )?; - for (i, sample) in state.samples.lock().unwrap().iter().enumerate() { - let row = 4 + i as u16; - for (j, (column, field)) in [ - (0, format!(" {:7} ", sample.name)), - (9, format!(" {:.1}Hz ", sample.rate)), - (18, format!(" MIDI C{} {} ", sample.trigger.0, sample.trigger.1)), - (33, format!(" {:.1}dB -> Output ", sample.gain)), - (50, format!(" {} ", sample.playing.unwrap_or(0))), - ].into_iter().enumerate() { - stdout.queue(move_to(column, row))?; - if state.selected_sample == i && state.selected_column == j { - stdout.queue(PrintStyledContent(field.to_string().bold().reverse()))?; - } else { - stdout.queue(PrintStyledContent(field.to_string().bold()))?; - } - } - } - Ok(()) -} - -fn render_meters ( - state: &mut Sampler, - stdout: &mut Stdout, - offset: (u16, u16), -) -> Result<(), Box> { - let move_to = |col, row| crossterm::cursor::MoveTo(offset.0 + col, offset.1 + row); - for (i, sample) in state.samples.lock().iter().enumerate() { - let row = 4 + i as u16; - stdout.queue(move_to(32, row))?.queue( - PrintStyledContent("▁".green()) - )?; - } - Ok(()) -} - - -impl HandleInput for Sampler { - fn handle (&mut self, event: &Event) -> Result<(), Box> { - handle(self, event) - } -} - -pub fn handle ( - state: &mut Sampler, - event: &Event -) -> Result<(), Box> { - - if let Event::Input(crossterm::event::Event::Key(event)) = event { - - match event.code { - KeyCode::Char('c') => { - if event.modifiers == KeyModifiers::CONTROL { - state.exit(); - } - }, - KeyCode::Down => { - state.selected_sample = (state.selected_sample + 1) % state.samples.lock().unwrap().len(); - println!("{}", state.selected_sample); - }, - KeyCode::Up => { - if state.selected_sample == 0 { - state.selected_sample = state.samples.lock().unwrap().len() - 1; - } else { - state.selected_sample = state.selected_sample - 1; - } - println!("{}", state.selected_sample); - }, - KeyCode::Left => { - if state.selected_column == 0 { - state.selected_column = 6 - } else { - state.selected_column = state.selected_column - 1; - } - }, - KeyCode::Right => { - if state.selected_column == 6 { - state.selected_column = 0 - } else { - state.selected_column = state.selected_column + 1; - } - }, - _ => { - println!("{event:?}"); - } - } - - } - - Ok(()) -} - - pub struct Notifications; impl NotificationHandler for Notifications { diff --git a/src/device/sequencer.rs b/src/device/sequencer.rs index 8f77f6eb..488c011b 100644 --- a/src/device/sequencer.rs +++ b/src/device/sequencer.rs @@ -1,44 +1,23 @@ use crate::prelude::*; use ratatui::style::Stylize; -mod midi; -pub use midi::*; - -pub const ACTIONS: [(&'static str, &'static str);4] = [ - ("+/-", "Zoom"), - ("A/D", "Add/delete note"), - ("]/[", "Duration"), - ("CapsLock", "Auto advance"), -]; - pub struct Sequencer { - name: Arc, - exited: Arc, - playing: Arc, - recording: Arc, - overdub: Arc, - inputs_open: Arc, - outputs_open: Arc, - cursor: (u16, u16, u16), - timesig: (f32, f32), - pub jack_client: Jack, - sequence: Arc>>>>, - sequences: Arc>>, -} - -#[derive(Clone)] -pub enum Event { - NoteOn(u8, u8), - NoteOff(u8) + name: String, + playing: Arc, + recording: Arc, + overdub: Arc, + inputs_open: Arc, + outputs_open: Arc, + cursor: (u16, u16, u16), + timesig: (f32, f32), + pub jack_client: Jack, + sequence: Arc>>>>, + sequences: Arc>>, } impl Sequencer { - pub fn new ( - name: Option<&str>, - connect_inputs: Option<&[String]>, - connect_outputs: Option<&[String]>, - ) -> Result> { + pub fn new (name: &str) -> Result, Box> { let beats = 4; let steps = 16; let bpm = 120.0; @@ -52,7 +31,6 @@ impl Sequencer { let playing = Arc::new(AtomicBool::new(true)); let recording = Arc::new(AtomicBool::new(true)); let overdub = Arc::new(AtomicBool::new(false)); - let name = name.unwrap_or("sequencer"); let (client, _status) = Client::new( name, ClientOptions::NO_START_SERVER )?; @@ -62,7 +40,7 @@ impl Sequencer { let mut output = client.register_port( "output", ::jack::MidiOut::default() )?; - let sequence: Arc>>>> = Arc::new( + let sequence: Arc>>>> = Arc::new( Mutex::new(vec![vec![None;64];128]) ); let mut step_frames = vec![]; @@ -75,9 +53,8 @@ impl Sequencer { for (index, frame) in step_frames.iter().enumerate() { frame_steps[*frame] = Some(index); } - Ok(Self { + Ok(DynamicDevice::new(render, handle, |_|{}, Self { name: name.into(), - exited: exited.clone(), playing: playing.clone(), recording: recording.clone(), overdub: overdub.clone(), @@ -88,7 +65,7 @@ impl Sequencer { ])), cursor: (11, 0, 0), timesig: (4.0, 4.0), - jack_client: crate::engine::activate_jack_client( + jack_client: crate::device::activate_jack_client( client, Notifications, Box::new(move |client: &Client, scope: &ProcessScope| -> Control { @@ -123,12 +100,12 @@ impl Sequencer { writer.write(&::jack::RawMidi { time: frame as u32, bytes: &match event { - Event::NoteOn(pitch, velocity) => [ + SequencerEvent::NoteOn(pitch, velocity) => [ 0b10010000, *pitch, *velocity ], - Event::NoteOff(pitch) => [ + SequencerEvent::NoteOff(pitch) => [ 0b10000000, *pitch, 0b00000000 @@ -144,29 +121,21 @@ impl Sequencer { Control::Continue }) )? - }) + })) } } -impl Exitable for Sequencer { - fn exit (&mut self) { - self.exited.store(true, Ordering::Relaxed) - } - fn exited (&self) -> bool { - self.exited.fetch_and(true, Ordering::Relaxed) - } -} +pub const ACTIONS: [(&'static str, &'static str);4] = [ + ("+/-", "Zoom"), + ("A/D", "Add/delete note"), + ("]/[", "Duration"), + ("CapsLock", "Auto advance"), +]; -impl HandleInput for Sequencer { - fn handle (&mut self, event: &crate::engine::Event) -> Result<(), Box> { - handle(self, event) - } -} +pub fn handle (state: &mut Sequencer, event: &EngineEvent) -> Result<(), Box> { -fn handle (state: &mut Sequencer, event: &crate::engine::Event) -> Result<(), Box> { - - if let crate::engine::Event::Input(crossterm::event::Event::Key(event)) = event { + if let EngineEvent::Input(Event::Key(event)) = event { match event.code { KeyCode::Down => { state.cursor.0 = if state.cursor.0 >= 23 { @@ -215,9 +184,9 @@ fn handle (state: &mut Sequencer, event: &crate::engine::Event) -> Result<(), Bo let step = state.cursor.1 as usize; let duration = state.cursor.2 as usize; let mut sequence = state.sequence.lock().unwrap(); - sequence[row][step] = Some(self::Event::NoteOn(48 - row as u8, 128)); + sequence[row][step] = Some(SequencerEvent::NoteOn(48 - row as u8, 128)); if state.cursor.2 > 0 { - sequence[row][step + duration] = Some(self::Event::NoteOff(35)); + sequence[row][step + duration] = Some(SequencerEvent::NoteOff(35)); } }, _ => { @@ -236,13 +205,7 @@ const KEYS_VERTICAL: [&'static str; 6] = [ "▀", "▀", "▀", "█", "▄", "▄", ]; -impl WidgetRef for Sequencer { - fn render_ref (&self, mut area: Rect, buf: &mut Buffer) { - draw_sequencer(self, buf, area) - } -} - -fn draw_sequencer (sequencer: &Sequencer, buf: &mut Buffer, mut area: Rect) { +fn render (sequencer: &Sequencer, buf: &mut Buffer, mut area: Rect) { area.height = 18; //draw_box(buf, area); let Rect { x, y, width, height } = area; @@ -289,12 +252,12 @@ fn draw_sequencer (sequencer: &Sequencer, buf: &mut Buffer, mut area: Rect) { //buf.set_string(x + 1, y + 5, //&format!(" {} │ IN │ ⏺ REC │ ⏺ DUB │ ▶ PLAY │ ⏹ STOP │ OUT │ 00:00.00 / 00:00.00", &sequencer.name), //Style::default().gray()); - if sequencer.inputs_open.fetch_and(true, Ordering::Relaxed) { - buf.set_string(x + 15, y + 1, "IN", Style::default().green().bold()); - } - if sequencer.outputs_open.fetch_and(true, Ordering::Relaxed) { - buf.set_string(x + 54, y + 1, "OUT", Style::default().green().bold()); - } + //if sequencer.inputs_open.fetch_and(true, Ordering::Relaxed) { + //buf.set_string(x + 15, y + 1, "IN", Style::default().green().bold()); + //} + //if sequencer.outputs_open.fetch_and(true, Ordering::Relaxed) { + //buf.set_string(x + 54, y + 1, "OUT", Style::default().green().bold()); + //} let mut command = |y2: u16, c: &str, ommand: &str, value: &str| { buf.set_string(x + 1, y + y2, c, Style::default().bold()); @@ -542,6 +505,65 @@ impl NotificationHandler for Notifications { } } +pub struct MIDISequence { + channels: [MIDISequenceChannel;16], +} + +pub struct MIDISequenceChannel { + number: u8, + notes: [MIDISequenceVoice;128], +} + +pub type MIDISequenceVoice = std::collections::BTreeMap; + +#[derive(Clone)] +pub enum NoteEvent { + On(u8), + Off(u8), +} + +const VOICE_EMPTY: MIDISequenceVoice = MIDISequenceVoice::new(); + +impl MIDISequence { + fn new () -> Self { + Self { + channels: [ + MIDISequenceChannel::new(1), + MIDISequenceChannel::new(2), + MIDISequenceChannel::new(3), + MIDISequenceChannel::new(4), + MIDISequenceChannel::new(5), + MIDISequenceChannel::new(6), + MIDISequenceChannel::new(7), + MIDISequenceChannel::new(8), + MIDISequenceChannel::new(9), + MIDISequenceChannel::new(10), + MIDISequenceChannel::new(11), + MIDISequenceChannel::new(12), + MIDISequenceChannel::new(13), + MIDISequenceChannel::new(14), + MIDISequenceChannel::new(15), + MIDISequenceChannel::new(16), + ] + } + } +} + +impl MIDISequenceChannel { + fn new (number: u8) -> Self { + Self { + number, + notes: [VOICE_EMPTY;128] + } + } +} + +#[derive(Clone)] +pub enum SequencerEvent { + NoteOn(u8, u8), + NoteOff(u8) +} + #[cfg(test)] mod test { #[test] diff --git a/src/device/sequencer/midi.rs b/src/device/sequencer/midi.rs deleted file mode 100644 index 58b44b7a..00000000 --- a/src/device/sequencer/midi.rs +++ /dev/null @@ -1,52 +0,0 @@ -pub struct MIDISequence { - channels: [MIDISequenceChannel;16], -} - -pub struct MIDISequenceChannel { - number: u8, - notes: [MIDISequenceVoice;128], -} - -pub type MIDISequenceVoice = std::collections::BTreeMap; - -#[derive(Clone)] -pub enum NoteEvent { - On(u8), - Off(u8), -} - -const VOICE_EMPTY: MIDISequenceVoice = MIDISequenceVoice::new(); - -impl MIDISequence { - fn new () -> Self { - Self { - channels: [ - MIDISequenceChannel::new(1), - MIDISequenceChannel::new(2), - MIDISequenceChannel::new(3), - MIDISequenceChannel::new(4), - MIDISequenceChannel::new(5), - MIDISequenceChannel::new(6), - MIDISequenceChannel::new(7), - MIDISequenceChannel::new(8), - MIDISequenceChannel::new(9), - MIDISequenceChannel::new(10), - MIDISequenceChannel::new(11), - MIDISequenceChannel::new(12), - MIDISequenceChannel::new(13), - MIDISequenceChannel::new(14), - MIDISequenceChannel::new(15), - MIDISequenceChannel::new(16), - ] - } - } -} - -impl MIDISequenceChannel { - fn new (number: u8) -> Self { - Self { - number, - notes: [VOICE_EMPTY;128] - } - } -} diff --git a/src/device/transport.rs b/src/device/transport.rs index 82016d77..9f43affb 100644 --- a/src/device/transport.rs +++ b/src/device/transport.rs @@ -1,37 +1,32 @@ use crate::prelude::*; -pub const ACTIONS: [(&'static str, &'static str);4] = [ - ("?", "Toggle help"), - ("(Shift-)Tab", "Switch pane"), - ("Arrows", "Navigate"), - ("(Shift-)Space", "⯈ Play/pause"), -]; - pub struct Transport { - exited: bool, - title: String, - transport: ::jack::Transport, + name: String, + transport: Option<::jack::Transport>, bpm: f64, timesig: (f32, f32), + devices: Vec>, } impl Transport { - pub fn new (client: &Client) -> Result> { - let transport = client.transport(); - Ok(Self { - exited: false, - title: String::from("Untitled project"), + pub fn new (devices: Vec>) + -> Result, Box> + { + //let transport = client.transport(); + Ok(DynamicDevice::new(render, handle, |_|{}, Self { + name: "Transport".into(), bpm: 113.0, timesig: (4.0, 4.0), - transport, - }) + transport: None, + devices + })) } 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()? { + match self.transport.as_ref().unwrap().query_state()? { TransportState::Stopped => self.play(), TransportState::Rolling => self.stop(), _ => Ok(()) @@ -39,41 +34,27 @@ impl Transport { } pub fn play (&mut self) -> Result<(), Box> { - Ok(self.transport.start()?) + Ok(self.transport.as_ref().unwrap().start()?) } pub fn stop (&mut self) -> Result<(), Box> { - Ok(self.transport.stop()?) + Ok(self.transport.as_ref().unwrap().stop()?) } } -impl Exitable for Transport { - fn exit (&mut self) { - self.exited = true - } - fn exited (&self) -> bool { - self.exited - } -} - -impl WidgetRef for Transport { - fn render_ref (&self, area: Rect, buf: &mut Buffer) { - use ratatui::{layout::*, widgets::*, style::Stylize}; - draw_leaf(buf, area, 0, 0, "REC"); - draw_leaf(buf, area, 0, 5, "DUB"); - draw_leaf(buf, area, 0, 10, "STOP"); - draw_leaf(buf, area, 0, 16, "PLAY/PAUSE"); - draw_leaf(buf, area, 0, 28, "START"); - draw_leaf(buf, area, 0, 35, "Project: Witty Gerbil - Sha Na Na "); - let position = self.transport.query().expect("failed to query transport"); - draw_leaf(buf, area, 2, 0, &format!("BPM {:03}.{:03}", - self.bpm as u64, - ((self.bpm % 1.0) * 1000.0) as u64 - )); - //let bbt = position.pos.bbt().map(|mut bbt|*bbt - //.with_bpm(self.bpm) - //.with_timesig(self.timesig.0, self.timesig.1)); - //.unwrap(); +pub fn render (state: &Transport, buf: &mut Buffer, area: Rect) { + draw_leaf(buf, area, 0, 0, "REC"); + draw_leaf(buf, area, 0, 5, "DUB"); + draw_leaf(buf, area, 0, 10, "STOP"); + draw_leaf(buf, area, 0, 16, "PLAY/PAUSE"); + draw_leaf(buf, area, 0, 28, "START"); + draw_leaf(buf, area, 0, 35, "Project: Witty Gerbil - Sha Na Na "); + draw_leaf(buf, area, 2, 0, &format!("BPM {:03}.{:03}", + state.bpm as u64, + ((state.bpm % 1.0) * 1000.0) as u64 + )); + let position = state.transport.as_ref().map(|t|t.query()); + if let Some(Ok(position)) = position { let rate = position.pos.frame_rate().unwrap(); let frame = position.pos.frame(); let second = (frame as f64) / (rate as f64); @@ -105,42 +86,46 @@ impl WidgetRef for Transport { )); draw_leaf(buf, area, 2, 48, &format!("Rate {:>6}Hz", rate)); draw_leaf(buf, area, 2, 63, &format!("Frame {:>10}", frame)); - //Line::from("Project:").render(area, buf); - //if let Ok(position) = self.transport.query() { - //let frame = position.pos.frame(); - //let rate = position.pos.frame_rate(); - //let bbt = position.pos.bbt().map(|mut bbt|*bbt - //.with_bpm(self.bpm) - //.with_timesig(self.timesig.0, self.timesig.1)); - //Line::from("Frame:").render(area.clone().offset(Offset { x: 0, y: 1 }), buf); - //Line::from(format!("{frame}")).render(area.clone().offset(Offset { x: 0, y: 2 }), buf); - //Line::from("Rate:").render(area.clone().offset(Offset { x: 10, y: 1 }), buf); - //Line::from(match rate { - //Some(rate) => format!("{rate}Hz"), - //None => String::from("(none)"), - //}).render(area.clone().offset(Offset { x: 10, y: 2 }), buf); - //Line::from("Time:").render(area.clone().offset(Offset { x: 20, y: 1 }), buf); - //Line::from(match rate { - //Some(rate) => format!("{:.03}", frame as f64 / rate as f64), - //None => String::from("(none)") - //}).render(area.clone().offset(Offset { x: 20, y: 2 }), buf); - //Line::from("BPM:").render(area.clone().offset(Offset { x: 30, y: 1 }), buf); - //Line::from(match bbt { - //Some(bbt) => format!("{:.01}", bbt.bpm), - //None => String::from("(none)") - //}).render(area.clone().offset(Offset { x: 30, y: 2 }), buf); - //Line::from("TimeSig:").render(area.clone().offset(Offset { x: 40, y: 1 }), buf); - //Line::from(match bbt { - //Some(bbt) => format!("{}/{}", bbt.sig_num, bbt.sig_denom), - //None => String::from("(none)") - //}).render(area.clone().offset(Offset { x: 40, y: 2 }), buf); - //Line::from("Beat:").render(area.clone().offset(Offset { x: 50, y: 1 }), buf); - //Line::from(match bbt { - //Some(bbt) => format!("{}.{}.{}", bbt.bar, bbt.beat, bbt.tick), - //None => String::from("(none)") - //}).render(area.clone().offset(Offset { x: 50, y: 2 }), buf); - //} } + //let bbt = position.pos.bbt().map(|mut bbt|*bbt + //.with_bpm(state.bpm) + //.with_timesig(state.timesig.0, state.timesig.1)); + //.unwrap(); + //Line::from("Project:").render(area, buf); + //if let Ok(position) = state.transport.query() { + //let frame = position.pos.frame(); + //let rate = position.pos.frame_rate(); + //let bbt = position.pos.bbt().map(|mut bbt|*bbt + //.with_bpm(state.bpm) + //.with_timesig(state.timesig.0, state.timesig.1)); + //Line::from("Frame:").render(area.clone().offset(Offset { x: 0, y: 1 }), buf); + //Line::from(format!("{frame}")).render(area.clone().offset(Offset { x: 0, y: 2 }), buf); + //Line::from("Rate:").render(area.clone().offset(Offset { x: 10, y: 1 }), buf); + //Line::from(match rate { + //Some(rate) => format!("{rate}Hz"), + //None => String::from("(none)"), + //}).render(area.clone().offset(Offset { x: 10, y: 2 }), buf); + //Line::from("Time:").render(area.clone().offset(Offset { x: 20, y: 1 }), buf); + //Line::from(match rate { + //Some(rate) => format!("{:.03}", frame as f64 / rate as f64), + //None => String::from("(none)") + //}).render(area.clone().offset(Offset { x: 20, y: 2 }), buf); + //Line::from("BPM:").render(area.clone().offset(Offset { x: 30, y: 1 }), buf); + //Line::from(match bbt { + //Some(bbt) => format!("{:.01}", bbt.bpm), + //None => String::from("(none)") + //}).render(area.clone().offset(Offset { x: 30, y: 2 }), buf); + //Line::from("TimeSig:").render(area.clone().offset(Offset { x: 40, y: 1 }), buf); + //Line::from(match bbt { + //Some(bbt) => format!("{}/{}", bbt.sig_num, bbt.sig_denom), + //None => String::from("(none)") + //}).render(area.clone().offset(Offset { x: 40, y: 2 }), buf); + //Line::from("Beat:").render(area.clone().offset(Offset { x: 50, y: 1 }), buf); + //Line::from(match bbt { + //Some(bbt) => format!("{}.{}.{}", bbt.bar, bbt.beat, bbt.tick), + //None => String::from("(none)") + //}).render(area.clone().offset(Offset { x: 50, y: 2 }), buf); + //} } //pub fn render ( @@ -206,12 +191,17 @@ impl WidgetRef for Transport { //Ok(()) //} -impl HandleInput for self::Transport { - fn handle (&mut self, event: &Event) -> Result<(), Box> { - Ok(()) - } +pub fn handle (state: &mut Transport, event: &EngineEvent) -> Result<(), Box> { + Ok(()) } +pub const ACTIONS: [(&'static str, &'static str);4] = [ + ("?", "Toggle help"), + ("(Shift-)Tab", "Switch pane"), + ("Arrows", "Navigate"), + ("(Shift-)Space", "⯈ Play/pause"), +]; + struct Notifications; impl NotificationHandler for Notifications { diff --git a/src/engine.rs b/src/engine.rs deleted file mode 100644 index 3f05a476..00000000 --- a/src/engine.rs +++ /dev/null @@ -1,341 +0,0 @@ -//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; -} - -pub trait HandleInput { - fn handle (&mut self, event: &Event) -> Result<(), Box>; -} - -#[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 { - exited: Arc, - sender: Sender, - receiver: Receiver, - pub jack_client: Jack, -} - -pub fn activate_jack_client ( - client: Client, - notifications: N, - handler: BoxedProcessHandler -) -> Result, Box> { - Ok(client.activate_async(notifications, ClosureProcessHandler::new(handler))?) -} - -pub trait Component: Exitable + WidgetRef + HandleInput + Send + 'static {} - -impl Component for T {} - -impl Engine { - - pub fn new (name: Option<&str>) -> Result> { - let (sender, receiver) = mpsc::channel::(); - let exited = Arc::new(AtomicBool::new(false)); - let (client, _status) = Client::new(name.unwrap_or("engine"), ClientOptions::NO_START_SERVER)?; - Ok(Self { - receiver, - sender: sender.clone(), - exited: exited.clone(), - jack_client: activate_jack_client( - client, - Notifications(sender.clone()), - Box::new(move |_client: &Client, _ps: &ProcessScope| -> Control { - if exited.fetch_and(true, Ordering::Relaxed) { - Control::Quit - } else { - sender.send(Event::Update).unwrap(); - Control::Continue - } - }) - )? - }) - } - - pub fn run ( - &mut self, - mut state: impl Component, - ) -> Result<(), Box> { - - let state = Arc::new(Mutex::new(state)); - - let input_thread = { - let state = state.clone(); - let sender = self.sender.clone(); - let exited = self.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(); - let mut state = state.lock().unwrap(); - match event { - crossterm::event::Event::Key(crossterm::event::KeyEvent { - code: KeyCode::Char('c'), - modifiers: KeyModifiers::CONTROL, - .. - }) => { - state.exit() - }, - _ => if state.handle(&crate::engine::Event::Input(event)).is_err() { - break - } - } - } - }) - }; - - stdout().execute(EnterAlternateScreen)?; - enable_raw_mode()?; - let mut terminal = ratatui::Terminal::new(CrosstermBackend::new(stdout()))?; - let sleep = std::time::Duration::from_millis(16); - let exited = self.exited.clone(); - std::panic::set_hook(Box::new(panic_hook)); - loop { - terminal.draw(|frame|{ - let area = frame.size(); - frame.render_widget( - &*state.lock().unwrap(), - area - ); - }).expect("Failed to render frame"); - if state.lock().unwrap().exited() { - exited.store(true, Ordering::Relaxed); - break - } - std::thread::sleep(sleep); - }; - - //render_thread.join().expect("Failed to join render thread"); - - stdout() - .queue(crossterm::terminal::LeaveAlternateScreen)? - .flush()?; - crossterm::terminal::disable_raw_mode()?; - - Ok(()) - } - -} - -fn panic_hook (info: &std::panic::PanicInfo) { - stdout() - .execute(crossterm::terminal::LeaveAlternateScreen) - .unwrap(); - crossterm::terminal::disable_raw_mode() - .unwrap(); - writeln!(std::io::stderr(), "{}", info); - writeln!(std::io::stderr(), "{:?}", ::backtrace::Backtrace::new()); -} - -pub struct Notifications(mpsc::Sender); - -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/layout.rs b/src/layout.rs new file mode 100644 index 00000000..6dabaf7b --- /dev/null +++ b/src/layout.rs @@ -0,0 +1,37 @@ +use crate::prelude::*; + +pub struct Rows(pub Vec>); + +pub struct Columns(pub Vec>); + +impl Device for Rows { + fn handle (&mut self, event: &EngineEvent) -> Result<(), Box> { + Ok(()) + } + fn render (&self, buf: &mut Buffer, area: Rect) { + for i in 0..3 { + self.0[i].render(buf, Rect { + x: area.x, + y: area.height / 3 * i as u16, + width: area.width, + height: area.height / 3 + }) + } + } +} + +impl Device for Columns { + fn handle (&mut self, event: &EngineEvent) -> Result<(), Box> { + Ok(()) + } + fn render (&self, buf: &mut Buffer, area: Rect) { + for i in 0..3 { + self.0[i].render(buf, Rect { + x: area.width / 3 * i as u16, + y: area.y, + width: area.width / 3, + height: area.height + }) + } + } +} diff --git a/src/main.rs b/src/main.rs index 1ec717ee..27699a02 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,174 +2,37 @@ extern crate clap; extern crate jack; extern crate crossterm; -use clap::{Parser, Subcommand}; +use clap::{Parser}; use std::error::Error; pub mod cli; pub mod device; pub mod prelude; -pub mod engine; pub mod render; pub mod config; +pub mod layout; -use crate::prelude::*; -use crate::render::ActionBar; +use crate::device::{Chain, Sequencer, Sampler, Plugin, Mixer, Transport}; +use crate::layout::{Rows, Columns}; fn main () -> Result<(), Box> { let cli = cli::Cli::parse(); let xdg = microxdg::XdgApp::new("dawdle")?; crate::config::create_dirs(&xdg)?; - let mut engine = crate::engine::Engine::new(None)?; - match cli.command { - Some(cli::Command::Transport) => engine.run( - crate::device::transport::Transport::new(engine.jack_client.as_client())?, - ), - Some(cli::Command::Mixer) => engine.run( - crate::device::mixer::Mixer::new()?, - ), - Some(cli::Command::Looper) => engine.run( - crate::device::looper::Looper::new()?, - ), - Some(cli::Command::Sampler) => engine.run( - crate::device::sampler::Sampler::new()?, - ), - Some(cli::Command::Sequencer { inputs, outputs }) => { - engine.run(crate::device::sequencer::Sequencer::new( - Some("Sequencer"), - Some(&inputs.into_iter().map(|x|x.unwrap()).collect::>()), - Some(&outputs.into_iter().map(|x|x.unwrap()).collect::>()), - )?) - }, - None => engine.run(App { - exited: false, - mode: Mode::Sequencer, - transport: crate::device::transport::Transport::new( - engine.jack_client.as_client() - )?, - focus: 0, - devices: vec![ - crate::device::Device::Sequencer( - crate::device::sequencer::Sequencer::new( - Some("Melody#000"), - None, - None - )? - ), - //crate::device::Device::Sequencer( - //crate::device::sequencer::Sequencer::new( - //Some("Rhythm#000"), - //None, - //None, - //)? - //), - //crate::device::Device::Mixer( - //crate::device::mixer::Mixer::new()? - //), - //crate::device::Device::Sampler( - //crate::device::sampler::Sampler::new()? - //), - //crate::device::Device::Looper( - //crate::device::looper::Looper::new()? - //), - ] - }) - } -} - -struct App { - exited: bool, - mode: Mode, - transport: crate::device::transport::Transport, - focus: usize, - devices: Vec, -} - -#[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 - } -} - -impl WidgetRef for App { - fn render_ref (&self, area: Rect, buffer: &mut Buffer) { - use ratatui::style::Stylize; - let mut constraints = vec![ - Constraint::Max(40), - Constraint::Max(40), - ]; - let areas = Layout::default() - .direction(Direction::Horizontal) - .constraints(&constraints) - .split(Rect { - x: area.x, - y: area.y, - width: area.width, - height: area.height - 4 - }); - - self.transport.render(Rect { - x: area.width.saturating_sub(80u16) / 2, - y: area.y + area.height - 4, - width: area.width, - height: 4 - }, buffer); - - for (index, device) in self.devices.iter().enumerate() { - device.render(areas[index], buffer); - if index == self.focus { - draw_focus_corners(buffer, areas[index]); - } - } - } -} - -impl HandleInput for App { - fn handle (&mut self, event: &Event) -> Result<(), Box> { - //println!("{event:?}"); - if let Event::Input(crossterm::event::Event::Key(key)) = event { - match key.code { - KeyCode::Char('c') => { - if key.modifiers == KeyModifiers::CONTROL { - self.exit(); - } - }, - KeyCode::Char(' ') => { - if key.modifiers == KeyModifiers::SHIFT { - self.transport.play_from_start_or_stop_and_rewind(); - } else { - self.transport.play_or_pause().unwrap(); - } - }, - KeyCode::Tab => { - self.focus = self.focus + 1; - if self.focus >= self.devices.len() { - self.focus = 0; - } - }, - KeyCode::BackTab => { - if self.focus == 0 { - self.focus = self.devices.len() - 1; - } else { - self.focus = self.focus - 1; - } - }, - _ => { - self.devices[self.focus].handle(&event)? - } - } - } - Ok(()) - } + crate::device::run(Rows(vec![ + Box::new(Columns(vec![ + Box::new(Chain::new("Chain#00", vec![ + Box::new(Sequencer::new("Rhythm#000")?), + Box::new(Sampler::new("Sampler#00")?), + ])?), + Box::new(Chain::new("Chain#01", vec![ + Box::new(Sequencer::new("Melody#000")?), + Box::new(Plugin::new("Plugin#000")?), + ])?), + Box::new(Chain::new("Chain#02", vec![])?), + Box::new(Chain::new("Chain#03", vec![])?), + ])), + Box::new(Mixer::new("Mixer#00")?), + Box::new(Transport::new(vec![])?), + ])) } diff --git a/src/prelude.rs b/src/prelude.rs index 6936ed03..416c4547 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -16,17 +16,14 @@ pub use std::sync::{ mpsc::{self, channel, Sender, Receiver} }; pub use crossterm::{ - QueueableCommand, - ExecutableCommand, - event::{self, KeyCode, KeyModifiers}, - cursor::{self, MoveTo, Show, Hide}, + ExecutableCommand, QueueableCommand, + event::{Event, KeyEvent, KeyCode, KeyModifiers}, terminal::{ self, Clear, ClearType, EnterAlternateScreen, LeaveAlternateScreen, enable_raw_mode, disable_raw_mode }, - style::*, }; pub use ratatui::{ prelude::*, @@ -56,14 +53,13 @@ pub use jack::{ }; pub type BoxedProcessHandler = Box Control + Send>; pub type Jack = AsyncClient>; -pub use crate::engine::{ - Component, - Exitable, - HandleInput, - Event -}; pub use crate::render::{ draw_box, draw_leaf, draw_focus_corners }; +pub use crate::device::{ + Device, + DynamicDevice, + EngineEvent +}; diff --git a/src/render.rs b/src/render.rs index c4c36af4..6bcbe88f 100644 --- a/src/render.rs +++ b/src/render.rs @@ -61,61 +61,61 @@ pub fn draw_focus_corners (buffer: &mut Buffer, area: Rect) { buffer.set_string(area.x + area.width - 1, area.y + area.height - 1, "╯", focus); } -pub fn render_toolbar_vertical ( - stdout: &mut std::io::Stdout, - offset: (u16, u16), - actions: &[(&str, &str)], -) -> Result<(u16, u16), Box> { - let move_to = |col, row| MoveTo(offset.0 + col, offset.1 + row); - let mut x: u16 = 1; - let mut y: u16 = 0; - for (name, description) in actions.iter() { - stdout.queue(move_to(1, y))?.queue( - PrintStyledContent(name.yellow().bold()) - )?.queue(move_to(1, y + 1))?.queue( - PrintStyledContent(description.yellow()) - )?; - y = y + 3; - x = u16::max(x, usize::max(name.len(), description.len()) as u16); - } - Ok((x, y)) -} +//pub fn render_toolbar_vertical ( + //stdout: &mut std::io::Stdout, + //offset: (u16, u16), + //actions: &[(&str, &str)], +//) -> Result<(u16, u16), Box> { + //let move_to = |col, row| MoveTo(offset.0 + col, offset.1 + row); + //let mut x: u16 = 1; + //let mut y: u16 = 0; + //for (name, description) in actions.iter() { + //stdout.queue(move_to(1, y))?.queue( + //PrintStyledContent(name.yellow().bold()) + //)?.queue(move_to(1, y + 1))?.queue( + //PrintStyledContent(description.yellow()) + //)?; + //y = y + 3; + //x = u16::max(x, usize::max(name.len(), description.len()) as u16); + //} + //Ok((x, y)) +//} -pub fn render_box ( - stdout: &mut std::io::Stdout, - title: Option<&str>, - x: u16, - y: u16, - mut w: u16, - h: u16, - active: bool -) -> Result<(), Box> { - if let Some(title) = title { - w = u16::max(w, title.len() as u16 + 4); - } - let back: String = std::iter::repeat(" ").take(w.saturating_sub(2) as usize).collect(); - if active { - let edge: String = std::iter::repeat("━").take(w.saturating_sub(2) as usize).collect(); - stdout.queue(MoveTo(x, y))?.queue(PrintStyledContent(format!("┏{edge}┓").bold().yellow()))?; - if let Some(title) = title { - stdout.queue(MoveTo(x+1, y))?.queue(PrintStyledContent(format!(" {title} ").yellow()))?; - } - for row in y+1..y+h { - stdout.queue(MoveTo(x, row))?.queue(PrintStyledContent("┃".bold().yellow()))?; - stdout.queue(MoveTo(x+w-1, row))?.queue(PrintStyledContent("┃".bold().yellow()))?; - } - stdout.queue(MoveTo(x, y+h))?.queue(PrintStyledContent(format!("┗{edge}┛").bold().yellow()))?; - } else { - let edge: String = std::iter::repeat("─").take(w.saturating_sub(2) as usize).collect(); - stdout.queue(MoveTo(x, y))?.queue(PrintStyledContent(format!("┌{edge}┐").grey().dim()))?; - if let Some(title) = title { - stdout.queue(MoveTo(x+1, y))?.queue(Print(format!(" {title} ")))?; - } - for row in y+1..y+h { - stdout.queue(MoveTo(x, row))?.queue(PrintStyledContent("│".grey().dim()))?; - stdout.queue(MoveTo(x+w-1, row))?.queue(PrintStyledContent("│".grey().dim()))?; - } - stdout.queue(MoveTo(x, y+h))?.queue(PrintStyledContent(format!("└{edge}┘").grey().dim()))?; - } - Ok(()) -} +//pub fn render_box ( + //stdout: &mut std::io::Stdout, + //title: Option<&str>, + //x: u16, + //y: u16, + //mut w: u16, + //h: u16, + //active: bool +//) -> Result<(), Box> { + //if let Some(title) = title { + //w = u16::max(w, title.len() as u16 + 4); + //} + //let back: String = std::iter::repeat(" ").take(w.saturating_sub(2) as usize).collect(); + //if active { + //let edge: String = std::iter::repeat("━").take(w.saturating_sub(2) as usize).collect(); + //stdout.queue(MoveTo(x, y))?.queue(PrintStyledContent(format!("┏{edge}┓").bold().yellow()))?; + //if let Some(title) = title { + //stdout.queue(MoveTo(x+1, y))?.queue(PrintStyledContent(format!(" {title} ").yellow()))?; + //} + //for row in y+1..y+h { + //stdout.queue(MoveTo(x, row))?.queue(PrintStyledContent("┃".bold().yellow()))?; + //stdout.queue(MoveTo(x+w-1, row))?.queue(PrintStyledContent("┃".bold().yellow()))?; + //} + //stdout.queue(MoveTo(x, y+h))?.queue(PrintStyledContent(format!("┗{edge}┛").bold().yellow()))?; + //} else { + //let edge: String = std::iter::repeat("─").take(w.saturating_sub(2) as usize).collect(); + //stdout.queue(MoveTo(x, y))?.queue(PrintStyledContent(format!("┌{edge}┐").grey().dim()))?; + //if let Some(title) = title { + //stdout.queue(MoveTo(x+1, y))?.queue(Print(format!(" {title} ")))?; + //} + //for row in y+1..y+h { + //stdout.queue(MoveTo(x, row))?.queue(PrintStyledContent("│".grey().dim()))?; + //stdout.queue(MoveTo(x+w-1, row))?.queue(PrintStyledContent("│".grey().dim()))?; + //} + //stdout.queue(MoveTo(x, y+h))?.queue(PrintStyledContent(format!("└{edge}┘").grey().dim()))?; + //} + //Ok(()) +//}