From d6bf840a1f5d751c4b99d1bf7ea15bd30a01eb02 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Tue, 28 May 2024 22:13:20 +0300 Subject: [PATCH] wip: running interface in separate or combined mode also disassociating render functions from state structs --- src/{main_.rs => _main.rs} | 0 src/{sequence.rs => _sequence.rs} | 0 src/{sequence => _sequence}/audio.rs | 0 src/{sequence => _sequence}/midi.rs | 0 src/{sequence => _sequence}/osc.rs | 0 src/{sequence => _sequence}/time.rs | 0 src/cli.rs | 22 +++ src/looper.rs | 63 ++----- src/looper/handle.rs | 2 + src/looper/jack.rs | 0 src/looper/render.rs | 20 +++ src/main.rs | 157 +++++++++++++++--- src/mixer.rs | 240 ++++----------------------- src/mixer/{tui.rs => _tui.rs} | 0 src/mixer/handle.rs | 48 ++++++ src/mixer/jack.rs | 40 +++++ src/mixer/render.rs | 66 ++++++++ src/prelude.rs | 4 +- src/render.rs | 41 +++++ src/sampler.rs | 195 +--------------------- src/sampler/handle.rs | 49 ++++++ src/sampler/jack.rs | 40 +++++ src/sampler/render.rs | 80 +++++++++ src/sequencer.rs | 39 +++++ src/sequencer/handle.rs | 58 +++++++ src/sequencer/jack.rs | 40 +++++ src/sequencer/render.rs | 82 +++++++++ src/transport.rs | 70 ++------ src/transport/handle.rs | 1 + src/transport/jack.rs | 40 +++++ src/transport/render.rs | 40 +++++ 31 files changed, 905 insertions(+), 532 deletions(-) rename src/{main_.rs => _main.rs} (100%) rename src/{sequence.rs => _sequence.rs} (100%) rename src/{sequence => _sequence}/audio.rs (100%) rename src/{sequence => _sequence}/midi.rs (100%) rename src/{sequence => _sequence}/osc.rs (100%) rename src/{sequence => _sequence}/time.rs (100%) create mode 100644 src/cli.rs create mode 100644 src/looper/handle.rs create mode 100644 src/looper/jack.rs create mode 100644 src/looper/render.rs rename src/mixer/{tui.rs => _tui.rs} (100%) create mode 100644 src/mixer/handle.rs create mode 100644 src/mixer/jack.rs create mode 100644 src/mixer/render.rs create mode 100644 src/render.rs create mode 100644 src/sampler/handle.rs create mode 100644 src/sampler/jack.rs create mode 100644 src/sampler/render.rs create mode 100644 src/sequencer.rs create mode 100644 src/sequencer/handle.rs create mode 100644 src/sequencer/jack.rs create mode 100644 src/sequencer/render.rs create mode 100644 src/transport/handle.rs create mode 100644 src/transport/jack.rs create mode 100644 src/transport/render.rs diff --git a/src/main_.rs b/src/_main.rs similarity index 100% rename from src/main_.rs rename to src/_main.rs diff --git a/src/sequence.rs b/src/_sequence.rs similarity index 100% rename from src/sequence.rs rename to src/_sequence.rs diff --git a/src/sequence/audio.rs b/src/_sequence/audio.rs similarity index 100% rename from src/sequence/audio.rs rename to src/_sequence/audio.rs diff --git a/src/sequence/midi.rs b/src/_sequence/midi.rs similarity index 100% rename from src/sequence/midi.rs rename to src/_sequence/midi.rs diff --git a/src/sequence/osc.rs b/src/_sequence/osc.rs similarity index 100% rename from src/sequence/osc.rs rename to src/_sequence/osc.rs diff --git a/src/sequence/time.rs b/src/_sequence/time.rs similarity index 100% rename from src/sequence/time.rs rename to src/_sequence/time.rs diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 00000000..03719e55 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,22 @@ +use clap::{Parser, Subcommand}; + +#[derive(Debug, Parser)] +#[command(version, about, long_about = None)] +pub struct Cli { + #[command(subcommand)] + pub command: Option +} + +#[derive(Debug, Clone, Subcommand)] +pub enum Command { + /// Launch or control a master transport + Transport, + /// Launch or control a mixer + Mixer, + /// Launch or control a looper + Looper, + /// Launch or control a sampler + Sampler, + /// Launch or control a sequencer + Sequencer, +} diff --git a/src/looper.rs b/src/looper.rs index 3055fc7f..c99ffcc1 100644 --- a/src/looper.rs +++ b/src/looper.rs @@ -1,54 +1,19 @@ -use std::error::Error; -use std::io::Write; +mod handle; +mod jack; +mod render; +pub use self::handle::*; +pub use self::jack::*; +pub use self::render::*; +use crate::prelude::*; -pub struct Looper { - cols: u16, - rows: u16, -} +pub struct Looper; + +const ACTIONS: [(&'static str, &'static str);1] = [ + ("Ins/Del", "Add/remove loop"), +]; impl Looper { - pub fn run_tui () -> Result<(), Box> { - let mut app = Self { cols: 0, rows: 0 }; - let sleep = std::time::Duration::from_millis(16); - let mut stdout = std::io::stdout(); - loop { - app.render(&mut stdout)?; - std::thread::sleep(sleep); - } - } - fn render (&mut self, stdout: &mut std::io::Stdout) -> Result<(), Box> { - use crossterm::{*, style::{*, Stylize}}; - let (cols, rows) = terminal::size()?; - if cols != self.cols || rows != self.rows { - self.cols = cols; - self.rows = rows; - stdout - .queue(terminal::Clear(terminal::ClearType::All))? - .queue(cursor::Hide)? - - .queue(cursor::MoveTo(1, 0))? - .queue(PrintStyledContent("[Up/Down]".yellow().bold()))? - .queue(cursor::MoveTo(1, 1))? - .queue(PrintStyledContent("Select sequence".yellow()))? - - .queue(cursor::MoveTo(18, 0))? - .queue(PrintStyledContent("[Left/Right]".yellow().bold()))? - .queue(cursor::MoveTo(18, 1))? - .queue(PrintStyledContent("Select parameter".yellow()))? - - .queue(cursor::MoveTo(36, 0))? - .queue(PrintStyledContent("[Insert/Delete]".yellow().bold()))? - .queue(cursor::MoveTo(36, 1))? - .queue(PrintStyledContent("Add/remove sequence".yellow()))? - - .queue(cursor::MoveTo(0, 3))?.queue(Print(" Name Input Length Route"))? - .queue(cursor::MoveTo(0, 4))?.queue(PrintStyledContent(" Metronome [ ] ████ Track 1".bold()))? - .queue(cursor::MoveTo(0, 5))?.queue(PrintStyledContent(" Loop 1 [ ] ████ Track 1".bold()))? - .queue(cursor::MoveTo(0, 6))?.queue(PrintStyledContent(" Loop 2 [ ] ████████ Track 2".bold()))? - .queue(cursor::MoveTo(0, 7))?.queue(PrintStyledContent(" Loop 3 [ ] ████████ Track 3".bold()))? - .flush()?; - } - Ok(()) + pub fn new () -> Result> { + Ok(Self) } } - diff --git a/src/looper/handle.rs b/src/looper/handle.rs new file mode 100644 index 00000000..3ca1c525 --- /dev/null +++ b/src/looper/handle.rs @@ -0,0 +1,2 @@ +use crate::prelude::*; +use super::Looper; diff --git a/src/looper/jack.rs b/src/looper/jack.rs new file mode 100644 index 00000000..e69de29b diff --git a/src/looper/render.rs b/src/looper/render.rs new file mode 100644 index 00000000..4f5f3de9 --- /dev/null +++ b/src/looper/render.rs @@ -0,0 +1,20 @@ +use crate::prelude::*; +use super::{Looper, ACTIONS}; + +pub fn render ( + state: &mut Looper, + stdout: &mut std::io::Stdout, + mut offset: (u16, u16), +) -> Result<(), Box> { + let (w, h) = crate::render::render_toolbar_vertical(stdout, offset, &ACTIONS)?; + offset.0 = offset.0 + w + 2; + 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()))? + .flush()?; + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 9c85aacf..d1467dbe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,43 +5,146 @@ extern crate crossterm; use clap::{Parser, Subcommand}; use std::error::Error; -//pub mod sequence; +pub mod cli; pub mod prelude; pub mod engine; pub mod transport; pub mod mixer; pub mod looper; pub mod sampler; +pub mod sequencer; +pub mod render; + +use crate::prelude::*; fn main () -> Result<(), Box> { - let cli = Cli::parse(); - match cli.command { - Command::Transport => - crate::transport::Transport::run_tui(), - Command::Mixer => - crate::mixer::Mixer::run_tui(), - Command::Looper => - crate::looper::Looper::run_tui(), - Command::Sampler => - crate::sampler::Sampler::run_tui(), + let cli = cli::Cli::parse(); + if let Some(command) = cli.command { + match command { + cli::Command::Transport => main_loop( + &mut transport::Transport::new()?, + transport::render + ), + cli::Command::Mixer => main_loop( + &mut mixer::Mixer::new()?, + mixer::render + ), + cli::Command::Looper => main_loop( + &mut looper::Looper::new()?, + looper::render + ), + cli::Command::Sampler => main_loop( + &mut sampler::Sampler::new()?, + sampler::render + ), + cli::Command::Sequencer => main_loop( + &mut sequencer::Sequencer::new()?, + sequencer::render + ), + } + } else { + main_loop(&mut ( + transport::Transport::new()?, + mixer::Mixer::new()?, + looper::Looper::new()?, + sampler::Sampler::new()?, + sequencer::Sequencer::new()?, + ), |(transport, mixer, looper, sampler, sequencer), stdout, offset| { + transport::render(transport, stdout, (1, 1))?; + render::render_box(stdout, 28, 0, 60, 6, false)?; + mixer::render(mixer, stdout, (1, 10))?; + render::render_box(stdout, 18, 9, 62, 9, false)?; + looper::render(looper, stdout, (1, 20))?; + render::render_box(stdout, 17, 19, 50, 6, false)?; + //sampler::render(sampler, stdout, (1, 46))?; + //sequencer::render(sequencer, stdout, (1, 66))?; + Ok(()) + }) } } -#[derive(Debug, Parser)] -#[command(version, about, long_about = None)] -pub struct Cli { - #[command(subcommand)] - command: Command +pub fn main_loop ( + state: &mut T, + mut render: impl FnMut(&mut T, &mut Stdout, (u16, u16)) -> Result<(), Box> +) -> Result<(), Box> { + let sleep = std::time::Duration::from_millis(16); + let mut stdout = std::io::stdout(); + loop { + stdout.queue(Clear(ClearType::All))?.queue(Hide)?; + render(state, &mut stdout, (0, 0))?; + stdout.flush()?; + std::thread::sleep(sleep); + } } -#[derive(Debug, Clone, Subcommand)] -pub enum Command { - /// Control the master transport - Transport, - /// Control the mixer - Mixer, - /// Control the looper - Looper, - /// Control the sampler - Sampler, -} +//pub fn run_tui () -> Result<(), Box> { + //let mut stdout = stdout(); + //let mut app = Mixer::new()?; + //let sleep = std::time::Duration::from_millis(16); + //crossterm::terminal::enable_raw_mode()?; + //let (tx, input) = channel::(); + //let exited = Arc::new(AtomicBool::new(false)); + //let exit_input_thread = exited.clone(); + //spawn(move || { + //loop { + //// Exit if flag is set + //if exit_input_thread.fetch_and(true, Ordering::Relaxed) { + //break + //} + //// Listen for events and send them to the main thread + //if crossterm::event::poll(Duration::from_millis(100)).is_ok() { + //if tx.send(crossterm::event::read().unwrap()).is_err() { + //break + //} + //} + //} + //}); + //loop { + //render(&mut app, &mut stdout, (0, 0))?; + //handle(&mut app, input.recv()?)?; + //if app.exit { + //app.stdout.queue(cursor::Show)?.flush()?; + //crossterm::terminal::disable_raw_mode()?; + //break + //} + //} + //Ok(()) +//} + +//pub fn run_tui () -> Result<(), Box> { + //let mut app = Self::new()?; + //let mut stdout = std::io::stdout(); + //let sleep = std::time::Duration::from_millis(16); + //crossterm::terminal::enable_raw_mode()?; + //let (tx, input) = channel::(); + //let exited = Arc::new(AtomicBool::new(false)); + //// Spawn the input thread + //let exit_input_thread = exited.clone(); + //spawn(move || { + //loop { + //// Exit if flag is set + //if exit_input_thread.fetch_and(true, Ordering::Relaxed) { + //break + //} + //// Listen for events and send them to the main thread + //if crossterm::event::poll(Duration::from_millis(100)).is_ok() { + //if tx.send(crossterm::event::read().unwrap()).is_err() { + //break + //} + //} + //} + //}); + //loop { + //use crossterm::{*, terminal::{Clear, ClearType}, cursor::Hide}; + //stdout.queue(Clear(ClearType::All))?.queue(Hide)?; + //render(&mut app, &mut stdout, (0, 0))?; + //stdout.flush()?; + //handle(&mut app, input.recv()?)?; + //if app.exit { + //stdout.queue(cursor::Hide)?.flush()?; + //crossterm::terminal::disable_raw_mode()?; + //break + //} + //} + //Ok(()) +//} diff --git a/src/mixer.rs b/src/mixer.rs index e948be61..7d3f777b 100644 --- a/src/mixer.rs +++ b/src/mixer.rs @@ -1,3 +1,9 @@ +mod handle; +mod jack; +mod render; +pub use self::handle::*; +pub use self::jack::*; +pub use self::render::*; use crate::prelude::*; // TODO: @@ -5,17 +11,36 @@ use crate::prelude::*; // - If one stage clips, all stages after it are marked red // - If one track clips, all tracks that feed from it are marked red? +const ACTIONS: [(&'static str, &'static str);2] = [ + ("+/-", "Adjust"), + ("Ins/Del", "Add/remove track"), +]; + pub struct Mixer { jack: Jack, exit: bool, - stdout: Stdout, - cols: u16, - rows: u16, 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> { @@ -33,9 +58,6 @@ impl Mixer { )?; Ok(Self { exit: false, - stdout: std::io::stdout(), - cols: 0, - rows: 0, selected_column: 0, selected_track: 1, tracks: vec![ @@ -51,174 +73,6 @@ impl Mixer { }) } - pub fn run_tui () -> Result<(), Box> { - let mut app = Self::new()?; - let sleep = std::time::Duration::from_millis(16); - crossterm::terminal::enable_raw_mode()?; - let (tx, input) = channel::(); - let exited = Arc::new(AtomicBool::new(false)); - // Spawn the input thread - let exit_input_thread = exited.clone(); - spawn(move || { - loop { - // Exit if flag is set - if exit_input_thread.fetch_and(true, Ordering::Relaxed) { - break - } - // Listen for events and send them to the main thread - if crossterm::event::poll(Duration::from_millis(100)).is_ok() { - if tx.send(crossterm::event::read().unwrap()).is_err() { - break - } - } - } - }); - loop { - app.render()?; - app.handle(input.recv()?)?; - if app.exit { - app.stdout.queue(cursor::Hide)?.flush()?; - crossterm::terminal::disable_raw_mode()?; - break - } - } - Ok(()) - } - - fn handle (&mut self, event: crossterm::event::Event) -> Result<(), Box> { - use crossterm::event::{Event, KeyCode, KeyModifiers}; - if let Event::Key(event) = event { - match event.code { - KeyCode::Char('c') => { - if event.modifiers == KeyModifiers::CONTROL { - self.exit = true; - } - }, - KeyCode::Down => { - self.selected_track = (self.selected_track + 1) % self.tracks.len(); - println!("{}", self.selected_track); - }, - KeyCode::Up => { - if self.selected_track == 0 { - self.selected_track = self.tracks.len() - 1; - } else { - self.selected_track = self.selected_track - 1; - } - println!("{}", self.selected_track); - }, - KeyCode::Left => { - if self.selected_column == 0 { - self.selected_column = 6 - } else { - self.selected_column = self.selected_column - 1; - } - }, - KeyCode::Right => { - if self.selected_column == 6 { - self.selected_column = 0 - } else { - self.selected_column = self.selected_column + 1; - } - }, - _ => { - println!("{event:?}"); - } - } - } - Ok(()) - } - - fn render (&mut self) -> Result<(), Box> { - let (cols, rows) = terminal::size()?; - self.cols = cols; - self.rows = rows; - self.stdout.queue(terminal::Clear(terminal::ClearType::All))?; - self.stdout.queue(cursor::Hide)?; - self.render_toolbar()?; - self.render_table()?; - self.render_meters()?; - self.stdout.flush()?; - Ok(()) - } - - fn render_toolbar (&mut self) -> Result<(), Box> { - self.stdout - .queue(cursor::MoveTo(1, 0))? - .queue(PrintStyledContent("[Arrows]".yellow().bold()))? - .queue(cursor::MoveTo(1, 1))? - .queue(PrintStyledContent("Navigate".yellow()))? - - .queue(cursor::MoveTo(11, 0))? - .queue(PrintStyledContent("[+/-]".yellow().bold()))? - .queue(cursor::MoveTo(11, 1))? - .queue(PrintStyledContent("Adjust value".yellow()))? - - .queue(cursor::MoveTo(25, 0))? - .queue(PrintStyledContent("[Ins/Del]".yellow().bold()))? - .queue(cursor::MoveTo(25, 1))? - .queue(PrintStyledContent("Add/remove track".yellow()))?; - Ok(()) - } - - fn render_table (&mut self) -> Result<(), Box> { - self.stdout - .queue(cursor::MoveTo(0, 3))? - .queue(Print( - " Name Gain FX1 Pan Level FX2 Route"))?; - for (i, track) in self.tracks.iter().enumerate() { - let row = 4 + i 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() { - self.stdout.queue(cursor::MoveTo(column, row))?; - if self.selected_track == i && self.selected_column == j { - self.stdout.queue(PrintStyledContent(field.to_string().bold().reverse()))?; - } else { - self.stdout.queue(PrintStyledContent(field.to_string().bold()))?; - } - } - } - Ok(()) - } - - fn render_meters (&mut self) -> Result<(), Box> { - for (i, track) in self.tracks.iter().enumerate() { - let row = 4 + i as u16; - self.stdout - .queue(cursor::MoveTo(10, row))? - .queue(PrintStyledContent("▁".green()))? - .queue(cursor::MoveTo(20, row))? - .queue(PrintStyledContent("▁".green()))? - .queue(cursor::MoveTo(28, row))? - .queue(PrintStyledContent("▁".green()))? - .queue(cursor::MoveTo(43, row))? - .queue(PrintStyledContent("▁".green()))?; - } - Ok(()) - } -} - -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 { @@ -254,41 +108,3 @@ impl Track { }) } } - -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/tui.rs b/src/mixer/_tui.rs similarity index 100% rename from src/mixer/tui.rs rename to src/mixer/_tui.rs diff --git a/src/mixer/handle.rs b/src/mixer/handle.rs new file mode 100644 index 00000000..806ee9fd --- /dev/null +++ b/src/mixer/handle.rs @@ -0,0 +1,48 @@ +use crate::prelude::*; +use super::Mixer; + +pub fn handle ( + state: &mut Mixer, + event: crossterm::event::Event +) -> Result<(), Box> { + use crossterm::event::{Event, KeyCode, KeyModifiers}; + if let Event::Key(event) = event { + match event.code { + KeyCode::Char('c') => { + if event.modifiers == KeyModifiers::CONTROL { + state.exit = true; + } + }, + 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!("{event:?}"); + } + } + } + Ok(()) +} diff --git a/src/mixer/jack.rs b/src/mixer/jack.rs new file mode 100644 index 00000000..0f42d709 --- /dev/null +++ b/src/mixer/jack.rs @@ -0,0 +1,40 @@ +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/render.rs b/src/mixer/render.rs new file mode 100644 index 00000000..f5491086 --- /dev/null +++ b/src/mixer/render.rs @@ -0,0 +1,66 @@ +use crate::prelude::*; +use super::{Mixer, ACTIONS}; + +pub fn render ( + state: &mut Mixer, + stdout: &mut Stdout, + mut offset: (u16, u16) +) -> Result<(), Box> { + let (w, h) = crate::render::render_toolbar_vertical(stdout, offset, &ACTIONS)?; + offset.0 = offset.0 + w + 2; + render_table(state, stdout, offset)?; + render_meters(state, stdout, offset)?; + stdout.flush()?; + 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/prelude.rs b/src/prelude.rs index caddb81f..ce433571 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -9,8 +9,8 @@ pub use std::sync::{ }; pub use crossterm::{ QueueableCommand, - cursor, - terminal, + cursor::{self, MoveTo, Hide}, + terminal::{self, Clear, ClearType}, style::*, }; pub use jack::{ diff --git a/src/render.rs b/src/render.rs new file mode 100644 index 00000000..59fb80fe --- /dev/null +++ b/src/render.rs @@ -0,0 +1,41 @@ +use crate::prelude::*; + +pub fn render_toolbar_vertical ( + stdout: &mut std::io::Stdout, + offset: (u16, u16), + actions: &[(&str, &str)], +) -> Result<(u16, u16), Box> { + use crossterm::{*, style::{Stylize, PrintStyledContent}, cursor::MoveTo}; + 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, + x: u16, + y: u16, + w: u16, + h: u16, + active: bool +) -> Result<(), Box> { + let edge: String = std::iter::repeat("─").take(w.saturating_sub(2) as usize).collect(); + let back: String = std::iter::repeat(" ").take(w.saturating_sub(2) as usize).collect(); + stdout.queue(MoveTo(x, y))?.queue(Print(&format!("┌{edge}┐")))?; + for row in y+1..y+h { + stdout.queue(MoveTo(x, row))?.queue(Print("│"))?; + stdout.queue(MoveTo(x+w-1, row))?.queue(Print("│"))?; + } + stdout.queue(MoveTo(x, y+h))?.queue(Print(&format!("└{edge}┘")))?; + Ok(()) +} diff --git a/src/sampler.rs b/src/sampler.rs index 07380331..b98b6ac5 100644 --- a/src/sampler.rs +++ b/src/sampler.rs @@ -1,18 +1,20 @@ +mod handle; +mod jack; +mod render; +pub use self::handle::*; +pub use self::jack::*; +pub use self::render::*; use crate::prelude::*; pub struct Sampler { jack: Jack, exit: bool, - stdout: Stdout, - cols: u16, - rows: u16, samples: Vec, selected_sample: usize, selected_column: usize, } impl Sampler { - pub fn new () -> Result> { let (client, status) = Client::new( "bloop-sampler", @@ -28,9 +30,6 @@ impl Sampler { )?; Ok(Self { exit: false, - stdout: std::io::stdout(), - cols: 0, - rows: 0, selected_sample: 0, selected_column: 0, samples: vec![ @@ -40,150 +39,6 @@ impl Sampler { jack, }) } - - pub fn run_tui () -> Result<(), Box> { - let mut app = Self::new()?; - let sleep = std::time::Duration::from_millis(16); - crossterm::terminal::enable_raw_mode()?; - let (tx, input) = channel::(); - let exited = Arc::new(AtomicBool::new(false)); - // Spawn the input thread - let exit_input_thread = exited.clone(); - spawn(move || { - loop { - // Exit if flag is set - if exit_input_thread.fetch_and(true, Ordering::Relaxed) { - break - } - // Listen for events and send them to the main thread - if crossterm::event::poll(Duration::from_millis(100)).is_ok() { - if tx.send(crossterm::event::read().unwrap()).is_err() { - break - } - } - } - }); - loop { - app.render()?; - app.handle(input.recv()?)?; - if app.exit { - app.stdout.queue(cursor::Hide)?.flush()?; - crossterm::terminal::disable_raw_mode()?; - break - } - } - Ok(()) - } - - fn handle (&mut self, event: crossterm::event::Event) -> Result<(), Box> { - use crossterm::event::{Event, KeyCode, KeyModifiers}; - if let Event::Key(event) = event { - match event.code { - KeyCode::Char('c') => { - if event.modifiers == KeyModifiers::CONTROL { - self.exit = true; - } - }, - KeyCode::Down => { - self.selected_sample = (self.selected_sample + 1) % self.samples.len(); - println!("{}", self.selected_sample); - }, - KeyCode::Up => { - if self.selected_sample == 0 { - self.selected_sample = self.samples.len() - 1; - } else { - self.selected_sample = self.selected_sample - 1; - } - println!("{}", self.selected_sample); - }, - KeyCode::Left => { - if self.selected_column == 0 { - self.selected_column = 6 - } else { - self.selected_column = self.selected_column - 1; - } - }, - KeyCode::Right => { - if self.selected_column == 6 { - self.selected_column = 0 - } else { - self.selected_column = self.selected_column + 1; - } - }, - _ => { - println!("{event:?}"); - } - } - } - Ok(()) - } - - fn render (&mut self) -> Result<(), Box> { - let (cols, rows) = terminal::size()?; - self.cols = cols; - self.rows = rows; - self.stdout.queue(terminal::Clear(terminal::ClearType::All))?; - self.stdout.queue(cursor::Hide)?; - self.render_toolbar()?; - self.render_table()?; - self.render_meters()?; - self.stdout.flush()?; - Ok(()) - } - - fn render_toolbar (&mut self) -> Result<(), Box> { - self.stdout - .queue(cursor::MoveTo(1, 0))? - .queue(PrintStyledContent("[Arrows]".yellow().bold()))? - .queue(cursor::MoveTo(1, 1))? - .queue(PrintStyledContent("Navigate".yellow()))? - - .queue(cursor::MoveTo(12, 0))? - .queue(PrintStyledContent("[Enter]".yellow().bold()))? - .queue(cursor::MoveTo(12, 1))? - .queue(PrintStyledContent("Play sample".yellow()))? - - .queue(cursor::MoveTo(25, 0))? - .queue(PrintStyledContent("[Ins/Del]".yellow().bold()))? - .queue(cursor::MoveTo(25, 1))? - .queue(PrintStyledContent("Add/remove sample".yellow()))?; - Ok(()) - } - - fn render_table (&mut self) -> Result<(), Box> { - self.stdout - .queue(cursor::MoveTo(0, 3))? - .queue(Print( - " Name Rate Trigger Route"))?; - for (i, sample) in self.samples.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 C10 36 ")), - (33, format!(" {:.1}dB -> Output ", sample.gain)), - ].into_iter().enumerate() { - self.stdout.queue(cursor::MoveTo(column, row))?; - if self.selected_sample == i && self.selected_column == j { - self.stdout.queue(PrintStyledContent(field.to_string().bold().reverse()))?; - } else { - self.stdout.queue(PrintStyledContent(field.to_string().bold()))?; - } - } - } - Ok(()) - } - - fn render_meters (&mut self) -> Result<(), Box> { - for (i, sample) in self.samples.iter().enumerate() { - let row = 4 + i as u16; - self.stdout - .queue(cursor::MoveTo(32, row))? - .queue(PrintStyledContent("▁".green()))?; - } - Ok(()) - } - } pub struct Sample { @@ -207,41 +62,3 @@ impl Sample { } } - -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 new file mode 100644 index 00000000..3b6ffe83 --- /dev/null +++ b/src/sampler/handle.rs @@ -0,0 +1,49 @@ +use crate::prelude::*; +use super::Sampler; + +pub fn handle ( + state: &mut Sampler, + event: crossterm::event::Event +) -> Result<(), Box> { + use crossterm::event::{Event, KeyCode, KeyModifiers}; + if let Event::Key(event) = event { + match event.code { + KeyCode::Char('c') => { + if event.modifiers == KeyModifiers::CONTROL { + state.exit = true; + } + }, + KeyCode::Down => { + state.selected_sample = (state.selected_sample + 1) % state.samples.len(); + println!("{}", state.selected_sample); + }, + KeyCode::Up => { + if state.selected_sample == 0 { + state.selected_sample = state.samples.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 new file mode 100644 index 00000000..0f42d709 --- /dev/null +++ b/src/sampler/jack.rs @@ -0,0 +1,40 @@ +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/render.rs b/src/sampler/render.rs new file mode 100644 index 00000000..e5856d47 --- /dev/null +++ b/src/sampler/render.rs @@ -0,0 +1,80 @@ +use crate::prelude::*; +use super::Sampler; + +pub fn render ( + state: &mut Sampler, + stdout: &mut Stdout, + offset: (u16, u16), +) -> Result<(), Box> { + render_toolbar(state, stdout, offset)?; + render_table(state, stdout, offset)?; + render_meters(state, stdout, offset)?; + stdout.flush()?; + Ok(()) +} + +fn render_toolbar ( + state: &mut Sampler, + stdout: &mut Stdout, + offset: (u16, u16), +) -> Result<(), Box> { + stdout + .queue(cursor::MoveTo(1, 0))? + .queue(PrintStyledContent("Arrows".yellow().bold()))? + .queue(cursor::MoveTo(1, 1))? + .queue(PrintStyledContent("Navigate".yellow()))? + + .queue(cursor::MoveTo(11, 0))? + .queue(PrintStyledContent("Enter".yellow().bold()))? + .queue(cursor::MoveTo(11, 1))? + .queue(PrintStyledContent("Play sample".yellow()))? + + .queue(cursor::MoveTo(24, 0))? + .queue(PrintStyledContent("Ins/Del".yellow().bold()))? + .queue(cursor::MoveTo(24, 1))? + .queue(PrintStyledContent("Add/remove sample".yellow()))?; + Ok(()) +} + +fn render_table ( + state: &mut Sampler, + stdout: &mut Stdout, + offset: (u16, u16), +) -> Result<(), Box> { + stdout + .queue(cursor::MoveTo(0, 3))? + .queue(Print( + " Name Rate Trigger Route"))?; + for (i, sample) in state.samples.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 C10 36 ")), + (33, format!(" {:.1}dB -> Output ", sample.gain)), + ].into_iter().enumerate() { + stdout.queue(cursor::MoveTo(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> { + for (i, sample) in state.samples.iter().enumerate() { + let row = 4 + i as u16; + stdout + .queue(cursor::MoveTo(32, row))? + .queue(PrintStyledContent("▁".green()))?; + } + Ok(()) +} + diff --git a/src/sequencer.rs b/src/sequencer.rs new file mode 100644 index 00000000..b338ddb8 --- /dev/null +++ b/src/sequencer.rs @@ -0,0 +1,39 @@ +mod handle; +mod render; +mod jack; +pub use self::handle::*; +pub use self::render::*; +pub use self::jack::*; +use crate::prelude::*; + +pub struct Sequencer { + jack: Jack, + exit: bool, + stdout: Stdout, + cursor: (u16, u16), + duration: u16, +} + +impl Sequencer { + pub fn new () -> Result> { + let (client, status) = Client::new( + "bloop-sequencer", + 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 { + exit: false, + stdout: std::io::stdout(), + cursor: (0, 0), + duration: 0, + jack, + }) + } +} diff --git a/src/sequencer/handle.rs b/src/sequencer/handle.rs new file mode 100644 index 00000000..5265647c --- /dev/null +++ b/src/sequencer/handle.rs @@ -0,0 +1,58 @@ +use crate::prelude::*; +use super::Sequencer; + +fn handle ( + state: &mut Sequencer, + event: crossterm::event::Event +) -> Result<(), Box> { + use crossterm::event::{Event, KeyCode, KeyModifiers}; + if let Event::Key(event) = event { + match event.code { + KeyCode::Char('c') => { + if event.modifiers == KeyModifiers::CONTROL { + state.exit = true; + } + }, + KeyCode::Char('[') => { + if state.duration > 0 { + state.duration = state.duration - 1 + } + }, + KeyCode::Char(']') => { + state.duration = state.duration + 1 + }, + KeyCode::Down => { + state.cursor.1 = if state.cursor.1 >= 3 { + 0 + } else { + state.cursor.1 + 1 + } + }, + KeyCode::Up => { + state.cursor.1 = if state.cursor.1 == 0 { + 3 + } else { + state.cursor.1 - 1 + } + }, + KeyCode::Left => { + state.cursor.0 = if state.cursor.0 == 0 { + 63 + } else { + state.cursor.0 - 1 + } + }, + KeyCode::Right => { + state.cursor.0 = if state.cursor.0 == 63 { + 0 + } else { + state.cursor.0 + 1 + } + }, + _ => { + println!("{event:?}"); + } + } + } + Ok(()) +} diff --git a/src/sequencer/jack.rs b/src/sequencer/jack.rs new file mode 100644 index 00000000..0f42d709 --- /dev/null +++ b/src/sequencer/jack.rs @@ -0,0 +1,40 @@ +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/sequencer/render.rs b/src/sequencer/render.rs new file mode 100644 index 00000000..1ae22ca8 --- /dev/null +++ b/src/sequencer/render.rs @@ -0,0 +1,82 @@ +use crate::prelude::*; +use super::Sequencer; + +pub fn render ( + state: &mut Sequencer, + stdout: &mut Stdout, + offset: (u16, u16) +) -> Result<(), Box> { + render_toolbar(state, stdout, offset)?; + render_grid(state, stdout, offset)?; + render_events(state, stdout, offset)?; + Ok(()) +} + +fn render_toolbar ( + state: &mut Sequencer, + stdout: &mut Stdout, + offset: (u16, u16) +) -> Result<(), Box> { + stdout + .queue(MoveTo(1, 0))? + .queue(PrintStyledContent("Arrows".yellow().bold()))? + .queue(MoveTo(1, 1))? + .queue(PrintStyledContent("Navigate".yellow()))? + + .queue(MoveTo(12, 0))? + .queue(PrintStyledContent("+/-".yellow().bold()))? + .queue(MoveTo(12, 1))? + .queue(PrintStyledContent("Zoom".yellow()))? + + .queue(MoveTo(20, 0))? + .queue(PrintStyledContent("a/d".yellow().bold()))? + .queue(MoveTo(20, 1))? + .queue(PrintStyledContent("Add/delete".yellow()))? + + .queue(MoveTo(33, 0))? + .queue(PrintStyledContent("[/]".yellow().bold()))? + .queue(MoveTo(33, 1))? + .queue(PrintStyledContent("Duration".yellow()))? + + .queue(MoveTo(45, 0))? + .queue(PrintStyledContent("CapsLock".yellow().bold()))? + .queue(MoveTo(45, 1))? + .queue(PrintStyledContent("Auto advance".yellow()))?; + Ok(()) +} + +fn render_grid ( + state: &mut Sequencer, + stdout: &mut Stdout, + offset: (u16, u16) +) -> Result<(), Box> { + let bg = "┊···············┊···············┊···············┊···············"; + let cursor: String = if state.duration == 0 { + "X".into() + } else { + std::iter::repeat("·") + .take(state.duration as usize) + .collect() + }; + stdout + .queue(MoveTo(1, 3))?.queue(Print("1.1"))? + .queue(MoveTo(17, 3))?.queue(Print("1.2"))? + .queue(MoveTo(33, 3))?.queue(Print("1.3"))? + .queue(MoveTo(49, 3))?.queue(Print("1.4"))? + .queue(MoveTo(1, 4))?.queue(PrintStyledContent(bg.grey()))? + .queue(MoveTo(1, 5))?.queue(PrintStyledContent(bg.grey()))? + .queue(MoveTo(1, 6))?.queue(PrintStyledContent(bg.grey()))? + .queue(MoveTo(1, 7))?.queue(PrintStyledContent(bg.grey()))? + .queue(MoveTo(1 + state.cursor.0, 4 + state.cursor.1))? + .queue(PrintStyledContent(cursor.reverse()))?; + Ok(()) +} + +fn render_events ( + state: &mut Sequencer, + stdout: &mut Stdout, + offset: (u16, u16) +) -> Result<(), Box> { + Ok(()) +} + diff --git a/src/transport.rs b/src/transport.rs index ed5920f8..95533c4c 100644 --- a/src/transport.rs +++ b/src/transport.rs @@ -1,58 +1,22 @@ -use std::error::Error; -use std::io::Write; +mod handle; +mod jack; +mod render; +pub use self::handle::*; +pub use self::jack::*; +pub use self::render::*; +use crate::prelude::*; -pub struct Transport { - cols: u16, - rows: u16, -} +pub struct Transport; impl Transport { - pub fn run_tui () -> Result<(), Box> { - let mut app = Self { cols: 0, rows: 0 }; - let sleep = std::time::Duration::from_millis(16); - let mut stdout = std::io::stdout(); - loop { - app.render(&mut stdout)?; - std::thread::sleep(sleep); - } - } - fn render (&mut self, stdout: &mut std::io::Stdout) -> Result<(), Box> { - use crossterm::{*, style::{*, Stylize}}; - let (cols, rows) = terminal::size()?; - if cols != self.cols || rows != self.rows { - self.cols = cols; - self.rows = rows; - stdout - .queue(terminal::Clear(terminal::ClearType::All))? - .queue(cursor::Hide)? - - .queue(cursor::MoveTo(1, 0))? - .queue(PrintStyledContent("[Home]".yellow().bold()))? - .queue(cursor::MoveTo(1, 1))? - .queue(PrintStyledContent("⏹ Stop and rewind".yellow().bold()))? - - .queue(cursor::MoveTo(20, 0))? - .queue(PrintStyledContent("[Enter]".yellow().bold()))? - .queue(cursor::MoveTo(20, 1))? - .queue(PrintStyledContent("⏮ Play from start".yellow().bold()))? - - .queue(cursor::MoveTo(40, 0))? - .queue(PrintStyledContent("[Space]".yellow().bold()))? - .queue(cursor::MoveTo(40, 1))? - .queue(PrintStyledContent("⯈ Play from cursor".yellow().bold()))? - - .queue(cursor::MoveTo(1, 3))?.queue(Print("Rate: "))? - .queue(cursor::MoveTo(7, 3))?.queue(PrintStyledContent("48000Hz".white().bold()))? - .queue(cursor::MoveTo(20, 3))?.queue(Print("BPM: "))? - .queue(cursor::MoveTo(25, 3))?.queue(PrintStyledContent("120.34".white().bold()))? - .queue(cursor::MoveTo(35, 3))?.queue(Print("Signature: "))? - .queue(cursor::MoveTo(46, 3))?.queue(PrintStyledContent("4 / 4".white().bold()))? - .queue(cursor::MoveTo(1, 4))?.queue(Print("Time: "))? - .queue(cursor::MoveTo(7, 4))?.queue(PrintStyledContent("1m 23.456s".white().bold()))? - .queue(cursor::MoveTo(20, 4))?.queue(Print("Beat: "))? - .queue(cursor::MoveTo(26, 4))?.queue(PrintStyledContent("30x 3/4".white().bold()))? - .flush()?; - } - Ok(()) + pub fn new () -> Result> { + Ok(Self) } } + +const ACTIONS: [(&'static str, &'static str);3] = [ + ("Tab/Shift-Tab, Arrows", "Navigate"), + //("Home", "⏹ Stop and rewind"), + ("Space", "⏮ Play from cursor/pause"), + ("Shift-Space", "⯈ Play from start/rewind"), +]; diff --git a/src/transport/handle.rs b/src/transport/handle.rs new file mode 100644 index 00000000..3616db21 --- /dev/null +++ b/src/transport/handle.rs @@ -0,0 +1 @@ +use crate::prelude::*; diff --git a/src/transport/jack.rs b/src/transport/jack.rs new file mode 100644 index 00000000..5b149824 --- /dev/null +++ b/src/transport/jack.rs @@ -0,0 +1,40 @@ +use crate::prelude::*; + +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/transport/render.rs b/src/transport/render.rs new file mode 100644 index 00000000..8377ff04 --- /dev/null +++ b/src/transport/render.rs @@ -0,0 +1,40 @@ +use crate::prelude::*; +use super::{Transport, ACTIONS}; + +pub fn render ( + state: &mut Transport, + stdout: &mut Stdout, + mut offset: (u16, u16) +) -> Result<(), Box> { + use crossterm::{*, cursor::{Hide, MoveTo}, style::{*, Stylize}}; + let (w, h) = crate::render::render_toolbar_vertical(stdout, offset, &ACTIONS)?; + offset.0 = offset.0 + w + 2; + let move_to = |col, row| MoveTo(offset.0 + col, offset.1 + row); + stdout.queue(move_to( 1, 0))?.queue( + Print("Project: ") + )?.queue(move_to(10, 0))?.queue( + PrintStyledContent("The Quick Brown Fox - Jumping Over Lazy Dogs".white().bold()) + )?.queue(move_to( 1, 2))?.queue( + Print("Rate: ") + )?.queue(move_to( 7, 2))?.queue( + PrintStyledContent("48000Hz".white().bold()) + )?.queue(move_to(20, 2))?.queue( + Print("BPM: ") + )?.queue(move_to(25, 2))?.queue( + PrintStyledContent("120.34".white().bold()) + )?.queue(move_to(35, 2))?.queue( + Print("Signature: ") + )?.queue(move_to(46, 2))?.queue( + PrintStyledContent("4 / 4".white().bold()) + )?.queue(move_to( 1, 4))?.queue( + Print("Time: ") + )?.queue(move_to( 7, 4))?.queue( + PrintStyledContent("1m 23.456s".white().bold()) + )?.queue(move_to(20, 4))?.queue( + Print("Beat: ") + )?.queue(move_to(26, 4))?.queue( + PrintStyledContent("30x 3/4".white().bold()) + )?; + Ok(()) +} +