diff --git a/src/_main.rs b/.misc/_main.rs similarity index 100% rename from src/_main.rs rename to .misc/_main.rs diff --git a/src/_sequence/audio.rs b/.misc/_sequence/audio.rs similarity index 100% rename from src/_sequence/audio.rs rename to .misc/_sequence/audio.rs diff --git a/src/_sequence/midi.rs b/.misc/_sequence/midi.rs similarity index 100% rename from src/_sequence/midi.rs rename to .misc/_sequence/midi.rs diff --git a/src/_sequence/mod.rs b/.misc/_sequence/mod.rs similarity index 100% rename from src/_sequence/mod.rs rename to .misc/_sequence/mod.rs diff --git a/src/_sequence/osc.rs b/.misc/_sequence/osc.rs similarity index 100% rename from src/_sequence/osc.rs rename to .misc/_sequence/osc.rs diff --git a/src/_sequence/time.rs b/.misc/_sequence/time.rs similarity index 100% rename from src/_sequence/time.rs rename to .misc/_sequence/time.rs diff --git a/crates/engine/Cargo.toml b/.misc/crates/engine/Cargo.toml similarity index 100% rename from crates/engine/Cargo.toml rename to .misc/crates/engine/Cargo.toml diff --git a/crates/engine/src/lib.rs b/.misc/crates/engine/src/lib.rs similarity index 100% rename from crates/engine/src/lib.rs rename to .misc/crates/engine/src/lib.rs diff --git a/crates/midikbd/Cargo.toml b/.misc/crates/midikbd/Cargo.toml similarity index 100% rename from crates/midikbd/Cargo.toml rename to .misc/crates/midikbd/Cargo.toml diff --git a/crates/midikbd/src/main.rs b/.misc/crates/midikbd/src/main.rs similarity index 100% rename from crates/midikbd/src/main.rs rename to .misc/crates/midikbd/src/main.rs diff --git a/crates/mixer/Cargo.toml b/.misc/crates/mixer/Cargo.toml similarity index 100% rename from crates/mixer/Cargo.toml rename to .misc/crates/mixer/Cargo.toml diff --git a/crates/mixer/src/main.rs b/.misc/crates/mixer/src/main.rs similarity index 100% rename from crates/mixer/src/main.rs rename to .misc/crates/mixer/src/main.rs diff --git a/crates/sampler/Cargo.toml b/.misc/crates/sampler/Cargo.toml similarity index 100% rename from crates/sampler/Cargo.toml rename to .misc/crates/sampler/Cargo.toml diff --git a/crates/sampler/src/main.rs b/.misc/crates/sampler/src/main.rs similarity index 100% rename from crates/sampler/src/main.rs rename to .misc/crates/sampler/src/main.rs diff --git a/crates/sequencer/Cargo.toml b/.misc/crates/sequencer/Cargo.toml similarity index 100% rename from crates/sequencer/Cargo.toml rename to .misc/crates/sequencer/Cargo.toml diff --git a/crates/sequencer/src/main.rs b/.misc/crates/sequencer/src/main.rs similarity index 100% rename from crates/sequencer/src/main.rs rename to .misc/crates/sequencer/src/main.rs diff --git a/crates/timebase/Cargo.toml b/.misc/crates/timebase/Cargo.toml similarity index 100% rename from crates/timebase/Cargo.toml rename to .misc/crates/timebase/Cargo.toml diff --git a/crates/timebase/src/lib.rs b/.misc/crates/timebase/src/lib.rs similarity index 100% rename from crates/timebase/src/lib.rs rename to .misc/crates/timebase/src/lib.rs diff --git a/crates/transport/Cargo.toml b/.misc/crates/transport/Cargo.toml similarity index 100% rename from crates/transport/Cargo.toml rename to .misc/crates/transport/Cargo.toml diff --git a/crates/transport/src/main.rs b/.misc/crates/transport/src/main.rs similarity index 100% rename from crates/transport/src/main.rs rename to .misc/crates/transport/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 92f7c1ba..4e1edbb9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,7 +125,9 @@ dependencies = [ "clap", "crossterm", "jack", + "microxdg", "ratatui", + "toml", ] [[package]] @@ -245,6 +247,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "gimli" version = "0.29.0" @@ -267,6 +275,16 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.0" @@ -367,6 +385,12 @@ version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +[[package]] +name = "microxdg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdcd3dc4ca0d1a9b1b80576639e93911a3e1db25a31ab6e6ba33027429adde5e" + [[package]] name = "miniz_oxide" version = "0.7.3" @@ -509,6 +533,35 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "serde" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_spanned" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +dependencies = [ + "serde", +] + [[package]] name = "signal-hook" version = "0.3.17" @@ -600,6 +653,40 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "toml" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "unicode-ident" version = "1.0.12" @@ -807,6 +894,15 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +[[package]] +name = "winnow" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" +dependencies = [ + "memchr", +] + [[package]] name = "zerocopy" version = "0.7.34" diff --git a/Cargo.toml b/Cargo.toml index 3b093dce..99ea3033 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,5 @@ clap = { version = "4.5.4", features = [ "derive" ] } crossterm = "0.27" ratatui = { version = "0.26.3", features = [ "unstable-widget-ref" ] } backtrace = "0.3.72" +microxdg = "0.1.2" +toml = "0.8.12" diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 00000000..0082f807 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,23 @@ +use crate::prelude::*; + +const CONFIG_FILE_NAME: &'static str = "dawdle.toml"; + +pub fn create_dirs (xdg: µxdg::XdgApp) -> Result<(), Box> { + use std::{path::Path,fs::{File,create_dir_all}}; + let config_dir = xdg.app_config()?; + if !Path::new(&config_dir).exists() { + println!("Creating {config_dir:?}"); + create_dir_all(&config_dir)?; + } + let config_path = config_dir.join(CONFIG_FILE_NAME); + if !Path::new(&config_path).exists() { + println!("Creating {config_path:?}"); + File::create_new(&config_path)?; + } + let data_dir = xdg.app_data()?; + if !Path::new(&data_dir).exists() { + println!("Creating {data_dir:?}"); + create_dir_all(&data_dir)?; + } + Ok(()) +} diff --git a/src/engine.rs b/src/engine.rs index 038ce345..3f05a476 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -89,8 +89,17 @@ impl Engine { if event::poll(poll).is_ok() { let event = event::read().unwrap(); let mut state = state.lock().unwrap(); - if state.handle(&Event::Input(event)).is_err() { - break + 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 + } } } }) diff --git a/src/looper.rs b/src/looper.rs new file mode 100644 index 00000000..a7e0d462 --- /dev/null +++ b/src/looper.rs @@ -0,0 +1,92 @@ +use crate::prelude::*; + +pub struct Looper { + exited: bool +} + +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 { + 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/looper/handle.rs b/src/looper/handle.rs deleted file mode 100644 index 4d323c1c..00000000 --- a/src/looper/handle.rs +++ /dev/null @@ -1,12 +0,0 @@ -use crate::prelude::*; -use super::Looper; - -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(()) -} diff --git a/src/looper/jack.rs b/src/looper/jack.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/src/looper/mod.rs b/src/looper/mod.rs deleted file mode 100644 index f6c807c3..00000000 --- a/src/looper/mod.rs +++ /dev/null @@ -1,30 +0,0 @@ -mod handle; -mod jack; -mod render; -pub use self::handle::*; -pub use self::jack::*; -pub use self::render::*; -use crate::prelude::*; - -pub struct Looper { - exited: bool -} - -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 - } -} diff --git a/src/looper/render.rs b/src/looper/render.rs deleted file mode 100644 index 541d54c3..00000000 --- a/src/looper/render.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::prelude::*; -use super::{Looper, ACTIONS}; - -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(()) -} diff --git a/src/main.rs b/src/main.rs index 88f4cb68..cbea56d9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,12 +14,15 @@ pub mod looper; pub mod sampler; pub mod sequencer; pub mod render; +pub mod config; use crate::prelude::*; use crate::render::ActionBar; fn main () -> Result<(), Box> { let cli = cli::Cli::parse(); + let xdg = microxdg::XdgApp::new("dawdle")?; + crate::config::create_dirs(&xdg)?; if let Some(command) = cli.command { run_one(&command) } else { @@ -101,8 +104,8 @@ impl WidgetRef for App { .direction(Direction::Vertical) .constraints([ Constraint::Length(4), - Constraint::Min(16), - Constraint::Min(16), + Constraint::Max(18), + Constraint::Max(18), ].clone()) .split(area); self.transport.render(area, buffer); diff --git a/src/mixer.rs b/src/mixer.rs new file mode 100644 index 00000000..f0d074e5 --- /dev/null +++ b/src/mixer.rs @@ -0,0 +1,307 @@ +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, + 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> { + let (client, status) = Client::new( + "bloop-mixer", + ClientOptions::NO_START_SERVER + )?; + let jack = client.activate_async( + Notifications, + ClosureProcessHandler::new(Box::new( + move |_: &Client, _: &ProcessScope| -> Control { + Control::Continue + }) as BoxControl + Send> + ) + )?; + Ok(Self { + exited: false, + selected_column: 0, + selected_track: 1, + tracks: vec![ + Track::new(&jack.as_client(), 1, "Kick")?, + Track::new(&jack.as_client(), 1, "Snare")?, + Track::new(&jack.as_client(), 2, "Hihats")?, + Track::new(&jack.as_client(), 2, "Sample")?, + Track::new(&jack.as_client(), 2, "Bus 1")?, + Track::new(&jack.as_client(), 2, "Bus 2")?, + 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) { + } +} + +pub fn render ( + state: &mut Mixer, + stdout: &mut Stdout, + mut offset: (u16, u16) +) -> Result<(), Box> { + render_table(state, stdout, offset)?; + render_meters(state, stdout, offset)?; + Ok(()) +} + +fn render_table ( + 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); + stdout.queue( + move_to(0, 0) + )?.queue( + Print(" Name Gain FX1 Pan Level FX2 Route") + )?; + for (i, track) in state.tracks.iter().enumerate() { + let row = (i + 1) as u16; + 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() { + 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()))?; + } + } + } + Ok(()) +} + +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(()) +} + +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 { + + match event.code { + 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); + }, + KeyCode::Up => { + if state.selected_track == 0 { + state.selected_track = state.tracks.len() - 1; + } else { + state.selected_track = state.selected_track - 1; + } + println!("{}", state.selected_track); + }, + 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!("\n{event:?}"); + } + } + + } + + 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 + } +} + + +//impl Input, bool> for Mixer { + //fn handle (&mut self, engine: &mut TUI) -> Result> { + //Ok(None) + //} +//} + +//impl Output, [u16;2]> for Mixer { + //fn render (&self, envine: &mut TUI) -> Result> { + + //let tracks_table = Columns::new() + //.add(titles) + //.add(input_meters) + //.add(gains) + //.add(gain_meters) + //.add(pres) + //.add(pre_meters) + //.add(levels) + //.add(pans) + //.add(pan_meters) + //.add(posts) + //.add(routes) + + //Rows::new() + //.add(Columns::new() + //.add(Rows::new() + //.add("[Arrows]".bold()) + //.add("Navigate")) + //.add(Rows::new() + //.add("[+/-]".bold()) + //.add("Adjust")) + //.add(Rows::new() + //.add("[Ins/Del]".bold()) + //.add("Add/remove track"))) + //.add(tracks_table) + //.render(engine) + //} +//} diff --git a/src/mixer/_tui.rs b/src/mixer/_tui.rs deleted file mode 100644 index 25ce10bd..00000000 --- a/src/mixer/_tui.rs +++ /dev/null @@ -1,39 +0,0 @@ -use super::Mixer; - -impl Input, bool> for Mixer { - fn handle (&mut self, engine: &mut TUI) -> Result> { - Ok(None) - } -} - -impl Output, [u16;2]> for Mixer { - fn render (&self, envine: &mut TUI) -> Result> { - - let tracks_table = Columns::new() - .add(titles) - .add(input_meters) - .add(gains) - .add(gain_meters) - .add(pres) - .add(pre_meters) - .add(levels) - .add(pans) - .add(pan_meters) - .add(posts) - .add(routes) - - Rows::new() - .add(Columns::new() - .add(Rows::new() - .add("[Arrows]".bold()) - .add("Navigate")) - .add(Rows::new() - .add("[+/-]".bold()) - .add("Adjust")) - .add(Rows::new() - .add("[Ins/Del]".bold()) - .add("Add/remove track"))) - .add(tracks_table) - .render(engine) - } -} diff --git a/src/mixer/handle.rs b/src/mixer/handle.rs deleted file mode 100644 index 31d1a742..00000000 --- a/src/mixer/handle.rs +++ /dev/null @@ -1,54 +0,0 @@ -use crate::prelude::*; -use super::Mixer; - -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 { - - match event.code { - 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); - }, - KeyCode::Up => { - if state.selected_track == 0 { - state.selected_track = state.tracks.len() - 1; - } else { - state.selected_track = state.selected_track - 1; - } - println!("{}", state.selected_track); - }, - 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!("\n{event:?}"); - } - } - - } - - Ok(()) -} diff --git a/src/mixer/jack.rs b/src/mixer/jack.rs deleted file mode 100644 index 0f42d709..00000000 --- a/src/mixer/jack.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::prelude::*; - -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 - } -} - diff --git a/src/mixer/mod.rs b/src/mixer/mod.rs deleted file mode 100644 index 9cc22985..00000000 --- a/src/mixer/mod.rs +++ /dev/null @@ -1,119 +0,0 @@ -mod handle; -mod jack; -mod render; -pub use self::handle::*; -pub use self::jack::*; -pub use self::render::*; -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, - 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> { - let (client, status) = Client::new( - "bloop-mixer", - ClientOptions::NO_START_SERVER - )?; - let jack = client.activate_async( - Notifications, - ClosureProcessHandler::new(Box::new( - move |_: &Client, _: &ProcessScope| -> Control { - Control::Continue - }) as BoxControl + Send> - ) - )?; - Ok(Self { - exited: false, - selected_column: 0, - selected_track: 1, - tracks: vec![ - Track::new(&jack.as_client(), 1, "Kick")?, - Track::new(&jack.as_client(), 1, "Snare")?, - Track::new(&jack.as_client(), 2, "Hihats")?, - Track::new(&jack.as_client(), 2, "Sample")?, - Track::new(&jack.as_client(), 2, "Bus 1")?, - Track::new(&jack.as_client(), 2, "Bus 2")?, - 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 - } -} diff --git a/src/mixer/render.rs b/src/mixer/render.rs deleted file mode 100644 index 798b441d..00000000 --- a/src/mixer/render.rs +++ /dev/null @@ -1,68 +0,0 @@ -use crate::prelude::*; -use super::{Mixer, ACTIONS}; - -impl WidgetRef for Mixer { - fn render_ref (&self, area: Rect, buf: &mut Buffer) { - } -} - -pub fn render ( - state: &mut Mixer, - stdout: &mut Stdout, - mut offset: (u16, u16) -) -> Result<(), Box> { - render_table(state, stdout, offset)?; - render_meters(state, stdout, offset)?; - Ok(()) -} - -fn render_table ( - 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); - stdout.queue( - move_to(0, 0) - )?.queue( - Print(" Name Gain FX1 Pan Level FX2 Route") - )?; - for (i, track) in state.tracks.iter().enumerate() { - let row = (i + 1) as u16; - 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() { - 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()))?; - } - } - } - Ok(()) -} - -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(()) -} - diff --git a/src/render.rs b/src/render.rs index e000f677..724363d4 100644 --- a/src/render.rs +++ b/src/render.rs @@ -58,6 +58,18 @@ pub fn draw_box (buffer: &mut Buffer, area: Rect) { format!("╭{}╮", "─".repeat((area.width - 2).into())),//.repeat(area.width.saturating_sub(2).into())), Style::default().black() ); + for y in (area.y + 1)..(area.y + area.height - 1) { + buffer.set_string( + area.x, y, + format!("│"), + Style::default().black().dim() + ); + buffer.set_string( + area.x + area.width - 1, y, + format!("│"), + Style::default().black().dim() + ); + } buffer.set_string( area.x, area.y + area.height - 1, format!("╰{}╯", "─".repeat((area.width - 2).into())),//.repeat(area.width.saturating_sub(2).into())), diff --git a/src/sampler.rs b/src/sampler.rs new file mode 100644 index 00000000..e6be2cad --- /dev/null +++ b/src/sampler.rs @@ -0,0 +1,274 @@ +use crate::prelude::*; + +pub const ACTIONS: [(&'static str, &'static str);2] = [ + ("Enter", "Play sample"), + ("Ins/Del", "Add/remove sample"), +]; + +pub struct Sampler { + exited: Arc, + jack: Jack, + samples: Arc>>, + selected_sample: usize, + selected_column: usize, +} + +impl Sampler { + pub fn new () -> Result> { + let exited = Arc::new(AtomicBool::new(false)); + let (client, status) = Client::new( + "blinkenlive-sampler", + ClientOptions::NO_START_SERVER + )?; + let samples = vec![ + Sample::new("Kick", &client, 1, 35)?, + Sample::new("Snare", &client, 1, 38)?, + ]; + let samples = Arc::new(Mutex::new(samples)); + let input = client.register_port("trigger", ::jack::MidiIn::default())?; + let handler: BoxedProcessHandler = Box::new({ + let exited = exited.clone(); + let samples = samples.clone(); + Box::new(move |_: &Client, scope: &ProcessScope| -> Control { + if exited.fetch_and(true, Ordering::Relaxed) { + return Control::Quit + } + let mut samples = samples.lock().unwrap(); + for event in input.iter(scope) { + let len = 3.min(event.bytes.len()); + let mut data = [0; 3]; + data[..len].copy_from_slice(&event.bytes[..len]); + if (data[0] >> 4) == 0b1001 { // note on + let channel = data[0] & 0b00001111; + let note = data[1]; + let velocity = data[2]; + for sample in samples.iter_mut() { + if /*sample.trigger.0 == channel &&*/ sample.trigger.1 == note { + sample.play(velocity); + } + } + } + for sample in samples.iter_mut() { + if let Some(playing) = sample.playing { + for (index, value) in sample.port.as_mut_slice(scope).iter_mut().enumerate() { + *value = *sample.data[0].get(playing + index).unwrap_or(&0f32); + } + if playing + scope.n_frames() as usize > sample.data[0].len() { + sample.playing = None + } else { + sample.playing = Some(playing + scope.n_frames() as usize) + } + } + } + } + Control::Continue + }) + }); + Ok(Self { + exited, + selected_sample: 0, + selected_column: 0, + samples, + jack: client.activate_async( + self::Notifications, + ClosureProcessHandler::new(handler) + )?, + }) + } +} + +pub struct Sample { + port: Port, + name: String, + rate: u32, + gain: f64, + channels: u8, + data: Vec>, + trigger: (u8, u8), + playing: Option, +} + +impl Sample { + + pub fn new (name: &str, client: &Client, channel: u8, note: u8) -> Result> { + Ok(Self { + port: client.register_port(name, ::jack::AudioOut::default())?, + name: name.into(), + rate: 44100, + channels: 1, + gain: 0.0, + data: vec![vec![1.0, 0.0, 0.0, 0.0]], + trigger: (channel, note), + playing: None + }) + } + + fn play (&mut self, velocity: u8) { + self.playing = Some(0) + } + +} + +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) + } +} + +impl WidgetRef for Sampler { + fn render_ref (&self, area: Rect, buf: &mut Buffer) { + } +} + +pub fn render ( + state: &mut Sampler, + stdout: &mut Stdout, + offset: (u16, u16), +) -> Result<(), Box> { + render_table(state, stdout, offset)?; + render_meters(state, stdout, offset)?; + 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 { + 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/sampler/handle.rs b/src/sampler/handle.rs deleted file mode 100644 index 7a130ad0..00000000 --- a/src/sampler/handle.rs +++ /dev/null @@ -1,58 +0,0 @@ -use crate::prelude::*; -use super::Sampler; - -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(()) -} - diff --git a/src/sampler/jack.rs b/src/sampler/jack.rs deleted file mode 100644 index 0f42d709..00000000 --- a/src/sampler/jack.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::prelude::*; - -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 - } -} - diff --git a/src/sampler/mod.rs b/src/sampler/mod.rs deleted file mode 100644 index a5767b0c..00000000 --- a/src/sampler/mod.rs +++ /dev/null @@ -1,125 +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);2] = [ - ("Enter", "Play sample"), - ("Ins/Del", "Add/remove sample"), -]; - -pub struct Sampler { - exited: Arc, - jack: Jack, - samples: Arc>>, - selected_sample: usize, - selected_column: usize, -} - -impl Sampler { - pub fn new () -> Result> { - let exited = Arc::new(AtomicBool::new(false)); - let (client, status) = Client::new( - "blinkenlive-sampler", - ClientOptions::NO_START_SERVER - )?; - let samples = vec![ - Sample::new("Kick", &client, 1, 35)?, - Sample::new("Snare", &client, 1, 38)?, - ]; - let samples = Arc::new(Mutex::new(samples)); - let input = client.register_port("trigger", ::jack::MidiIn::default())?; - let handler: BoxedProcessHandler = Box::new({ - let exited = exited.clone(); - let samples = samples.clone(); - Box::new(move |_: &Client, scope: &ProcessScope| -> Control { - if exited.fetch_and(true, Ordering::Relaxed) { - return Control::Quit - } - let mut samples = samples.lock().unwrap(); - for event in input.iter(scope) { - let len = 3.min(event.bytes.len()); - let mut data = [0; 3]; - data[..len].copy_from_slice(&event.bytes[..len]); - if (data[0] >> 4) == 0b1001 { // note on - let channel = data[0] & 0b00001111; - let note = data[1]; - let velocity = data[2]; - for sample in samples.iter_mut() { - if /*sample.trigger.0 == channel &&*/ sample.trigger.1 == note { - sample.play(velocity); - } - } - } - for sample in samples.iter_mut() { - if let Some(playing) = sample.playing { - for (index, value) in sample.port.as_mut_slice(scope).iter_mut().enumerate() { - *value = *sample.data[0].get(playing + index).unwrap_or(&0f32); - } - if playing + scope.n_frames() as usize > sample.data[0].len() { - sample.playing = None - } else { - sample.playing = Some(playing + scope.n_frames() as usize) - } - } - } - } - Control::Continue - }) - }); - Ok(Self { - exited, - selected_sample: 0, - selected_column: 0, - samples, - jack: client.activate_async( - self::jack::Notifications, - ClosureProcessHandler::new(handler) - )?, - }) - } -} - -pub struct Sample { - port: Port, - name: String, - rate: u32, - gain: f64, - channels: u8, - data: Vec>, - trigger: (u8, u8), - playing: Option, -} - -impl Sample { - - pub fn new (name: &str, client: &Client, channel: u8, note: u8) -> Result> { - Ok(Self { - port: client.register_port(name, ::jack::AudioOut::default())?, - name: name.into(), - rate: 44100, - channels: 1, - gain: 0.0, - data: vec![vec![1.0, 0.0, 0.0, 0.0]], - trigger: (channel, note), - playing: None - }) - } - - fn play (&mut self, velocity: u8) { - self.playing = Some(0) - } - -} - -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) - } -} diff --git a/src/sampler/render.rs b/src/sampler/render.rs deleted file mode 100644 index fbe8d81f..00000000 --- a/src/sampler/render.rs +++ /dev/null @@ -1,62 +0,0 @@ -use crate::prelude::*; -use super::Sampler; - -impl WidgetRef for Sampler { - fn render_ref (&self, area: Rect, buf: &mut Buffer) { - } -} - -pub fn render ( - state: &mut Sampler, - stdout: &mut Stdout, - offset: (u16, u16), -) -> Result<(), Box> { - render_table(state, stdout, offset)?; - render_meters(state, stdout, offset)?; - 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(()) -} - diff --git a/src/sequencer.rs b/src/sequencer.rs index 24ae1c06..ea583467 100644 --- a/src/sequencer.rs +++ b/src/sequencer.rs @@ -110,85 +110,6 @@ impl Exitable for Sequencer { } } -#[cfg(test)] -mod test { - #[test] - fn test_midi_frames () { - let beats = 4; - let steps = 16; - let bpm = 120; - let rate = 44100; // Hz - let frame = 1f64 / rate as f64; // msec - let buf = 512; // frames - let t_beat = 60.0 / bpm as f64; // msec - let t_loop = t_beat * beats as f64; // msec - let t_step = t_beat / steps as f64; // msec - - let assign = |chunk: usize| { - let start = chunk * buf; // frames - let end = (chunk + 1) * buf; // frames - println!("{chunk}: {start} .. {end}"); - let mut steps: Vec<(usize, usize, f64)> = vec![]; - for frame_index in start..end { - let frame_msec = frame_index as f64 * frame; - let offset = (frame_msec * 1000.0) % (t_step * 1000.0); - if offset < 0.1 { - let time = frame_index - start; - let step_index = (frame_msec % t_loop / t_step) as usize; - println!("{chunk}: {frame_index} ({time}) -> {step_index} ({frame_msec} % {t_step} = {offset})"); - steps.push((time, step_index, offset)); - } - } - steps - }; - - for chunk in 0..10 { - let chunk = assign(chunk); - //println!("{chunk} {:#?}", assign(chunk)); - } - } - - #[test] - fn test_midi_frames_2 () { - let beats = 4; - let steps = 16; - let bpm = 120; - let rate = 44100; // Hz - let frame = 1f64 / rate as f64; // msec - let buf = 512; // frames - let t_beat = 60.0 / bpm as f64; // msec - let t_loop = t_beat * beats as f64; // msec - let t_step = t_beat / steps as f64; // msec - let mut step_frames = vec![]; - for step in 0..beats*steps { - let step_index = (step as f64 * t_step / frame) as usize; - step_frames.push(step_index); - } - let loop_frames = (t_loop*rate as f64) as usize; - let mut frame_steps: Vec> = vec![None;loop_frames]; - for (index, frame) in step_frames.iter().enumerate() { - println!("{index} {frame}"); - frame_steps[*frame] = Some(index); - } - let assign = |chunk: usize| { - let (start, end) = (chunk * buf, (chunk + 1) * buf); // frames - let (start_looped, end_looped) = (start % loop_frames, end % loop_frames); - println!("{chunk}: {start} .. {end} ({start_looped} .. {end_looped})"); - let mut steps: Vec> = vec![None;buf]; - for frame in 0..buf { - let value = frame_steps[(start_looped + frame) as usize % loop_frames]; - if value.is_some() { println!("{frame:03} = {value:?}, ") }; - steps[frame as usize] = value; - } - steps - }; - for chunk in 0..1000 { - let chunk = assign(chunk); - //println!("{chunk} {:#?}", assign(chunk)); - } - } -} - impl HandleInput for Sequencer { fn handle (&mut self, event: &crate::engine::Event) -> Result<(), Box> { handle(self, event) @@ -198,13 +119,7 @@ impl HandleInput for Sequencer { fn handle (state: &mut Sequencer, event: &crate::engine::Event) -> Result<(), Box> { if let crate::engine::Event::Input(crossterm::event::Event::Key(event)) = event { - match event.code { - KeyCode::Char('c') => { - if event.modifiers == KeyModifiers::CONTROL { - state.exit(); - } - }, KeyCode::Down => { state.cursor.0 = if state.cursor.0 >= 23 { 0 @@ -255,7 +170,6 @@ fn handle (state: &mut Sequencer, event: &crate::engine::Event) -> Result<(), Bo println!("{event:?}"); } } - } Ok(()) } @@ -270,25 +184,28 @@ const KEYS: [&'static str; 6] = [ impl WidgetRef for Sequencer { fn render_ref (&self, area: Rect, buf: &mut Buffer) { - draw_box(buf, area); - draw_leaf(buf, area, 1, 0, "Name: Melody#000"); - draw_leaf(buf, area, 3, 0, "Output: None"); - draw_leaf(buf, area, 5, 0, "Input: None"); - draw_leaf(buf, area, 7, 0, "Channel: 01"); - draw_leaf(buf, area, 9, 0, "Zoom: 1/64"); - draw_leaf(buf, area, 11, 0, "Rate: 1/1"); - let mut area = area.inner(&Margin { horizontal: 1, vertical: 3, }); - area.x = area.x + 14; - draw_sequence_keys(area, buf, &self.jack_client.as_client().transport().query().unwrap(), &self.sequence); - draw_sequence_header(area, buf); - draw_sequence_cursor(area, buf, self.cursor); -//╭{}╮ -//faul - -//ring -//rea. -//╰{}╯ - let cursor = self.cursor; + { + let mut area = area.clone(); + area.height = 18; + draw_box(buf, area); + } + draw_leaf(buf, area, 3, 0, "Channel: 01"); + draw_leaf(buf, area, 5, 0, "Zoom: 1/64"); + draw_leaf(buf, area, 7, 0, "Rate: 1/1"); + { + let title = "(no inputs) -> Melody#000 -> (no outputs)"; + let mut area = area.clone(); + area.x = (area.width - title.len() as u16) / 2; + area.width = title.len() as u16; + draw_leaf(buf, area, 1, 0, title); + } + { + let mut area = area.inner(&Margin { horizontal: 1, vertical: 3, }); + area.x = area.x + 14; + draw_sequence_keys(area, buf, &self.jack_client.as_client().transport().query().unwrap(), &self.sequence); + draw_sequence_header(area, buf); + draw_sequence_cursor(area, buf, self.cursor); + } } } @@ -322,9 +239,7 @@ fn draw_sequence_keys ( let bars = beats as u32 / div as u32; let beat = beats as u32 % div as u32 + 1; let beat_sub = beats % 1.0; - let sequence = sequence.lock().unwrap(); - for key in 0..12 { buf.set_string(area.x, area.y + 1 + key, KEYS[(key % 6) as usize], Style::default().black()); @@ -424,3 +339,82 @@ impl NotificationHandler for Notifications { Control::Continue } } + +#[cfg(test)] +mod test { + #[test] + fn test_midi_frames () { + let beats = 4; + let steps = 16; + let bpm = 120; + let rate = 44100; // Hz + let frame = 1f64 / rate as f64; // msec + let buf = 512; // frames + let t_beat = 60.0 / bpm as f64; // msec + let t_loop = t_beat * beats as f64; // msec + let t_step = t_beat / steps as f64; // msec + + let assign = |chunk: usize| { + let start = chunk * buf; // frames + let end = (chunk + 1) * buf; // frames + println!("{chunk}: {start} .. {end}"); + let mut steps: Vec<(usize, usize, f64)> = vec![]; + for frame_index in start..end { + let frame_msec = frame_index as f64 * frame; + let offset = (frame_msec * 1000.0) % (t_step * 1000.0); + if offset < 0.1 { + let time = frame_index - start; + let step_index = (frame_msec % t_loop / t_step) as usize; + println!("{chunk}: {frame_index} ({time}) -> {step_index} ({frame_msec} % {t_step} = {offset})"); + steps.push((time, step_index, offset)); + } + } + steps + }; + + for chunk in 0..10 { + let chunk = assign(chunk); + //println!("{chunk} {:#?}", assign(chunk)); + } + } + + #[test] + fn test_midi_frames_2 () { + let beats = 4; + let steps = 16; + let bpm = 120; + let rate = 44100; // Hz + let frame = 1f64 / rate as f64; // msec + let buf = 512; // frames + let t_beat = 60.0 / bpm as f64; // msec + let t_loop = t_beat * beats as f64; // msec + let t_step = t_beat / steps as f64; // msec + let mut step_frames = vec![]; + for step in 0..beats*steps { + let step_index = (step as f64 * t_step / frame) as usize; + step_frames.push(step_index); + } + let loop_frames = (t_loop*rate as f64) as usize; + let mut frame_steps: Vec> = vec![None;loop_frames]; + for (index, frame) in step_frames.iter().enumerate() { + println!("{index} {frame}"); + frame_steps[*frame] = Some(index); + } + let assign = |chunk: usize| { + let (start, end) = (chunk * buf, (chunk + 1) * buf); // frames + let (start_looped, end_looped) = (start % loop_frames, end % loop_frames); + println!("{chunk}: {start} .. {end} ({start_looped} .. {end_looped})"); + let mut steps: Vec> = vec![None;buf]; + for frame in 0..buf { + let value = frame_steps[(start_looped + frame) as usize % loop_frames]; + if value.is_some() { println!("{frame:03} = {value:?}, ") }; + steps[frame as usize] = value; + } + steps + }; + for chunk in 0..1000 { + let chunk = assign(chunk); + //println!("{chunk} {:#?}", assign(chunk)); + } + } +}