mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-07 12:16:42 +01:00
refactor: compact
This commit is contained in:
parent
abee6cc2c8
commit
60627ac3e5
43 changed files with 923 additions and 780 deletions
96
Cargo.lock
generated
96
Cargo.lock
generated
|
|
@ -125,7 +125,9 @@ dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"crossterm",
|
"crossterm",
|
||||||
"jack",
|
"jack",
|
||||||
|
"microxdg",
|
||||||
"ratatui",
|
"ratatui",
|
||||||
|
"toml",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -245,6 +247,12 @@ version = "1.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
|
checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equivalent"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gimli"
|
name = "gimli"
|
||||||
version = "0.29.0"
|
version = "0.29.0"
|
||||||
|
|
@ -267,6 +275,16 @@ version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
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]]
|
[[package]]
|
||||||
name = "is_terminal_polyfill"
|
name = "is_terminal_polyfill"
|
||||||
version = "1.70.0"
|
version = "1.70.0"
|
||||||
|
|
@ -367,6 +385,12 @@ version = "2.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
|
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "microxdg"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fdcd3dc4ca0d1a9b1b80576639e93911a3e1db25a31ab6e6ba33027429adde5e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
|
|
@ -509,6 +533,35 @@ version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
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]]
|
[[package]]
|
||||||
name = "signal-hook"
|
name = "signal-hook"
|
||||||
version = "0.3.17"
|
version = "0.3.17"
|
||||||
|
|
@ -600,6 +653,40 @@ dependencies = [
|
||||||
"unicode-ident",
|
"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]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.12"
|
version = "1.0.12"
|
||||||
|
|
@ -807,6 +894,15 @@ version = "0.52.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winnow"
|
||||||
|
version = "0.6.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.7.34"
|
version = "0.7.34"
|
||||||
|
|
|
||||||
|
|
@ -8,3 +8,5 @@ clap = { version = "4.5.4", features = [ "derive" ] }
|
||||||
crossterm = "0.27"
|
crossterm = "0.27"
|
||||||
ratatui = { version = "0.26.3", features = [ "unstable-widget-ref" ] }
|
ratatui = { version = "0.26.3", features = [ "unstable-widget-ref" ] }
|
||||||
backtrace = "0.3.72"
|
backtrace = "0.3.72"
|
||||||
|
microxdg = "0.1.2"
|
||||||
|
toml = "0.8.12"
|
||||||
|
|
|
||||||
23
src/config.rs
Normal file
23
src/config.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
use crate::prelude::*;
|
||||||
|
|
||||||
|
const CONFIG_FILE_NAME: &'static str = "dawdle.toml";
|
||||||
|
|
||||||
|
pub fn create_dirs (xdg: µxdg::XdgApp) -> Result<(), Box<dyn Error>> {
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
|
@ -89,8 +89,17 @@ impl Engine {
|
||||||
if event::poll(poll).is_ok() {
|
if event::poll(poll).is_ok() {
|
||||||
let event = event::read().unwrap();
|
let event = event::read().unwrap();
|
||||||
let mut state = state.lock().unwrap();
|
let mut state = state.lock().unwrap();
|
||||||
if state.handle(&Event::Input(event)).is_err() {
|
match event {
|
||||||
break
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
||||||
92
src/looper.rs
Normal file
92
src/looper.rs
Normal file
|
|
@ -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<Self, Box<dyn Error>> {
|
||||||
|
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<dyn Error>> {
|
||||||
|
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<dyn Error>> {
|
||||||
|
handle(self, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle (state: &mut Looper, event: &Event) -> Result<(), Box<dyn Error>> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
use crate::prelude::*;
|
|
||||||
use super::Looper;
|
|
||||||
|
|
||||||
impl HandleInput for Looper {
|
|
||||||
fn handle (&mut self, event: &Event) -> Result<(), Box<dyn Error>> {
|
|
||||||
handle(self, event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle (state: &mut Looper, event: &Event) -> Result<(), Box<dyn Error>> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
@ -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<Self, Box<dyn Error>> {
|
|
||||||
Ok(Self { exited: false })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Exitable for Looper {
|
|
||||||
fn exit (&mut self) {
|
|
||||||
self.exited = true
|
|
||||||
}
|
|
||||||
fn exited (&self) -> bool {
|
|
||||||
self.exited
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<dyn Error>> {
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
|
|
@ -14,12 +14,15 @@ pub mod looper;
|
||||||
pub mod sampler;
|
pub mod sampler;
|
||||||
pub mod sequencer;
|
pub mod sequencer;
|
||||||
pub mod render;
|
pub mod render;
|
||||||
|
pub mod config;
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::render::ActionBar;
|
use crate::render::ActionBar;
|
||||||
|
|
||||||
fn main () -> Result<(), Box<dyn Error>> {
|
fn main () -> Result<(), Box<dyn Error>> {
|
||||||
let cli = cli::Cli::parse();
|
let cli = cli::Cli::parse();
|
||||||
|
let xdg = microxdg::XdgApp::new("dawdle")?;
|
||||||
|
crate::config::create_dirs(&xdg)?;
|
||||||
if let Some(command) = cli.command {
|
if let Some(command) = cli.command {
|
||||||
run_one(&command)
|
run_one(&command)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -101,8 +104,8 @@ impl WidgetRef for App {
|
||||||
.direction(Direction::Vertical)
|
.direction(Direction::Vertical)
|
||||||
.constraints([
|
.constraints([
|
||||||
Constraint::Length(4),
|
Constraint::Length(4),
|
||||||
Constraint::Min(16),
|
Constraint::Max(18),
|
||||||
Constraint::Min(16),
|
Constraint::Max(18),
|
||||||
].clone())
|
].clone())
|
||||||
.split(area);
|
.split(area);
|
||||||
self.transport.render(area, buffer);
|
self.transport.render(area, buffer);
|
||||||
|
|
|
||||||
307
src/mixer.rs
Normal file
307
src/mixer.rs
Normal file
|
|
@ -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<Notifications>,
|
||||||
|
tracks: Vec<Track>,
|
||||||
|
selected_track: usize,
|
||||||
|
selected_column: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Track {
|
||||||
|
name: String,
|
||||||
|
channels: u8,
|
||||||
|
input_ports: Vec<Port<AudioIn>>,
|
||||||
|
pre_gain_meter: f64,
|
||||||
|
gain: f64,
|
||||||
|
insert_ports: Vec<Port<AudioOut>>,
|
||||||
|
return_ports: Vec<Port<AudioIn>>,
|
||||||
|
post_gain_meter: f64,
|
||||||
|
post_insert_meter: f64,
|
||||||
|
level: f64,
|
||||||
|
pan: f64,
|
||||||
|
output_ports: Vec<Port<AudioOut>>,
|
||||||
|
post_fader_meter: f64,
|
||||||
|
route: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mixer {
|
||||||
|
|
||||||
|
pub fn new () -> Result<Self, Box<dyn Error>> {
|
||||||
|
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 Box<dyn FnMut(&Client, &ProcessScope)->Control + 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<Self, Box<dyn Error>> {
|
||||||
|
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<dyn Error>> {
|
||||||
|
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<dyn Error>> {
|
||||||
|
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<dyn Error>> {
|
||||||
|
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<dyn Error>> {
|
||||||
|
handle(self, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle (state: &mut Mixer, event: &Event) -> Result<(), Box<dyn Error>> {
|
||||||
|
|
||||||
|
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<W: Write> Input<TUI<W>, bool> for Mixer {
|
||||||
|
//fn handle (&mut self, engine: &mut TUI<W>) -> Result<Option<bool>> {
|
||||||
|
//Ok(None)
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
|
||||||
|
//impl<W: Write> Output<TUI<W>, [u16;2]> for Mixer {
|
||||||
|
//fn render (&self, envine: &mut TUI<W>) -> Result<Option<[u16;2]>> {
|
||||||
|
|
||||||
|
//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)
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
use super::Mixer;
|
|
||||||
|
|
||||||
impl<W: Write> Input<TUI<W>, bool> for Mixer {
|
|
||||||
fn handle (&mut self, engine: &mut TUI<W>) -> Result<Option<bool>> {
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<W: Write> Output<TUI<W>, [u16;2]> for Mixer {
|
|
||||||
fn render (&self, envine: &mut TUI<W>) -> Result<Option<[u16;2]>> {
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
use crate::prelude::*;
|
|
||||||
use super::Mixer;
|
|
||||||
|
|
||||||
impl HandleInput for Mixer {
|
|
||||||
fn handle (&mut self, event: &Event) -> Result<(), Box<dyn Error>> {
|
|
||||||
handle(self, event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle (state: &mut Mixer, event: &Event) -> Result<(), Box<dyn Error>> {
|
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
119
src/mixer/mod.rs
119
src/mixer/mod.rs
|
|
@ -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<Notifications>,
|
|
||||||
tracks: Vec<Track>,
|
|
||||||
selected_track: usize,
|
|
||||||
selected_column: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Track {
|
|
||||||
name: String,
|
|
||||||
channels: u8,
|
|
||||||
input_ports: Vec<Port<AudioIn>>,
|
|
||||||
pre_gain_meter: f64,
|
|
||||||
gain: f64,
|
|
||||||
insert_ports: Vec<Port<AudioOut>>,
|
|
||||||
return_ports: Vec<Port<AudioIn>>,
|
|
||||||
post_gain_meter: f64,
|
|
||||||
post_insert_meter: f64,
|
|
||||||
level: f64,
|
|
||||||
pan: f64,
|
|
||||||
output_ports: Vec<Port<AudioOut>>,
|
|
||||||
post_fader_meter: f64,
|
|
||||||
route: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Mixer {
|
|
||||||
|
|
||||||
pub fn new () -> Result<Self, Box<dyn Error>> {
|
|
||||||
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 Box<dyn FnMut(&Client, &ProcessScope)->Control + 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<Self, Box<dyn Error>> {
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<dyn Error>> {
|
|
||||||
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<dyn Error>> {
|
|
||||||
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<dyn Error>> {
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -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())),
|
format!("╭{}╮", "─".repeat((area.width - 2).into())),//.repeat(area.width.saturating_sub(2).into())),
|
||||||
Style::default().black()
|
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(
|
buffer.set_string(
|
||||||
area.x, area.y + area.height - 1,
|
area.x, area.y + area.height - 1,
|
||||||
format!("╰{}╯", "─".repeat((area.width - 2).into())),//.repeat(area.width.saturating_sub(2).into())),
|
format!("╰{}╯", "─".repeat((area.width - 2).into())),//.repeat(area.width.saturating_sub(2).into())),
|
||||||
|
|
|
||||||
274
src/sampler.rs
Normal file
274
src/sampler.rs
Normal file
|
|
@ -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<AtomicBool>,
|
||||||
|
jack: Jack<Notifications>,
|
||||||
|
samples: Arc<Mutex<Vec<Sample>>>,
|
||||||
|
selected_sample: usize,
|
||||||
|
selected_column: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sampler {
|
||||||
|
pub fn new () -> Result<Self, Box<dyn Error>> {
|
||||||
|
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<AudioOut>,
|
||||||
|
name: String,
|
||||||
|
rate: u32,
|
||||||
|
gain: f64,
|
||||||
|
channels: u8,
|
||||||
|
data: Vec<Vec<f32>>,
|
||||||
|
trigger: (u8, u8),
|
||||||
|
playing: Option<usize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sample {
|
||||||
|
|
||||||
|
pub fn new (name: &str, client: &Client, channel: u8, note: u8) -> Result<Self, Box<dyn Error>> {
|
||||||
|
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<dyn Error>> {
|
||||||
|
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<dyn Error>> {
|
||||||
|
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<dyn Error>> {
|
||||||
|
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<dyn Error>> {
|
||||||
|
handle(self, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle (
|
||||||
|
state: &mut Sampler,
|
||||||
|
event: &Event
|
||||||
|
) -> Result<(), Box<dyn Error>> {
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
use crate::prelude::*;
|
|
||||||
use super::Sampler;
|
|
||||||
|
|
||||||
impl HandleInput for Sampler {
|
|
||||||
fn handle (&mut self, event: &Event) -> Result<(), Box<dyn Error>> {
|
|
||||||
handle(self, event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle (
|
|
||||||
state: &mut Sampler,
|
|
||||||
event: &Event
|
|
||||||
) -> Result<(), Box<dyn Error>> {
|
|
||||||
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -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<AtomicBool>,
|
|
||||||
jack: Jack<Notifications>,
|
|
||||||
samples: Arc<Mutex<Vec<Sample>>>,
|
|
||||||
selected_sample: usize,
|
|
||||||
selected_column: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sampler {
|
|
||||||
pub fn new () -> Result<Self, Box<dyn Error>> {
|
|
||||||
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<AudioOut>,
|
|
||||||
name: String,
|
|
||||||
rate: u32,
|
|
||||||
gain: f64,
|
|
||||||
channels: u8,
|
|
||||||
data: Vec<Vec<f32>>,
|
|
||||||
trigger: (u8, u8),
|
|
||||||
playing: Option<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Sample {
|
|
||||||
|
|
||||||
pub fn new (name: &str, client: &Client, channel: u8, note: u8) -> Result<Self, Box<dyn Error>> {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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<dyn Error>> {
|
|
||||||
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<dyn Error>> {
|
|
||||||
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<dyn Error>> {
|
|
||||||
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(())
|
|
||||||
}
|
|
||||||
|
|
||||||
208
src/sequencer.rs
208
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<Option<usize>> = 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<Option<usize>> = 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 {
|
impl HandleInput for Sequencer {
|
||||||
fn handle (&mut self, event: &crate::engine::Event) -> Result<(), Box<dyn Error>> {
|
fn handle (&mut self, event: &crate::engine::Event) -> Result<(), Box<dyn Error>> {
|
||||||
handle(self, event)
|
handle(self, event)
|
||||||
|
|
@ -198,13 +119,7 @@ impl HandleInput for Sequencer {
|
||||||
fn handle (state: &mut Sequencer, event: &crate::engine::Event) -> Result<(), Box<dyn Error>> {
|
fn handle (state: &mut Sequencer, event: &crate::engine::Event) -> Result<(), Box<dyn Error>> {
|
||||||
|
|
||||||
if let crate::engine::Event::Input(crossterm::event::Event::Key(event)) = event {
|
if let crate::engine::Event::Input(crossterm::event::Event::Key(event)) = event {
|
||||||
|
|
||||||
match event.code {
|
match event.code {
|
||||||
KeyCode::Char('c') => {
|
|
||||||
if event.modifiers == KeyModifiers::CONTROL {
|
|
||||||
state.exit();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
KeyCode::Down => {
|
KeyCode::Down => {
|
||||||
state.cursor.0 = if state.cursor.0 >= 23 {
|
state.cursor.0 = if state.cursor.0 >= 23 {
|
||||||
0
|
0
|
||||||
|
|
@ -255,7 +170,6 @@ fn handle (state: &mut Sequencer, event: &crate::engine::Event) -> Result<(), Bo
|
||||||
println!("{event:?}");
|
println!("{event:?}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -270,25 +184,28 @@ const KEYS: [&'static str; 6] = [
|
||||||
|
|
||||||
impl WidgetRef for Sequencer {
|
impl WidgetRef for Sequencer {
|
||||||
fn render_ref (&self, area: Rect, buf: &mut Buffer) {
|
fn render_ref (&self, area: Rect, buf: &mut Buffer) {
|
||||||
draw_box(buf, area);
|
{
|
||||||
draw_leaf(buf, area, 1, 0, "Name: Melody#000");
|
let mut area = area.clone();
|
||||||
draw_leaf(buf, area, 3, 0, "Output: None");
|
area.height = 18;
|
||||||
draw_leaf(buf, area, 5, 0, "Input: None");
|
draw_box(buf, area);
|
||||||
draw_leaf(buf, area, 7, 0, "Channel: 01");
|
}
|
||||||
draw_leaf(buf, area, 9, 0, "Zoom: 1/64");
|
draw_leaf(buf, area, 3, 0, "Channel: 01");
|
||||||
draw_leaf(buf, area, 11, 0, "Rate: 1/1");
|
draw_leaf(buf, area, 5, 0, "Zoom: 1/64");
|
||||||
let mut area = area.inner(&Margin { horizontal: 1, vertical: 3, });
|
draw_leaf(buf, area, 7, 0, "Rate: 1/1");
|
||||||
area.x = area.x + 14;
|
{
|
||||||
draw_sequence_keys(area, buf, &self.jack_client.as_client().transport().query().unwrap(), &self.sequence);
|
let title = "(no inputs) -> Melody#000 -> (no outputs)";
|
||||||
draw_sequence_header(area, buf);
|
let mut area = area.clone();
|
||||||
draw_sequence_cursor(area, buf, self.cursor);
|
area.x = (area.width - title.len() as u16) / 2;
|
||||||
//╭{}╮
|
area.width = title.len() as u16;
|
||||||
//faul
|
draw_leaf(buf, area, 1, 0, title);
|
||||||
|
}
|
||||||
//ring
|
{
|
||||||
//rea.
|
let mut area = area.inner(&Margin { horizontal: 1, vertical: 3, });
|
||||||
//╰{}╯
|
area.x = area.x + 14;
|
||||||
let cursor = self.cursor;
|
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 bars = beats as u32 / div as u32;
|
||||||
let beat = beats as u32 % div as u32 + 1;
|
let beat = beats as u32 % div as u32 + 1;
|
||||||
let beat_sub = beats % 1.0;
|
let beat_sub = beats % 1.0;
|
||||||
|
|
||||||
let sequence = sequence.lock().unwrap();
|
let sequence = sequence.lock().unwrap();
|
||||||
|
|
||||||
for key in 0..12 {
|
for key in 0..12 {
|
||||||
buf.set_string(area.x, area.y + 1 + key, KEYS[(key % 6) as usize],
|
buf.set_string(area.x, area.y + 1 + key, KEYS[(key % 6) as usize],
|
||||||
Style::default().black());
|
Style::default().black());
|
||||||
|
|
@ -424,3 +339,82 @@ impl NotificationHandler for Notifications {
|
||||||
Control::Continue
|
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<Option<usize>> = 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<Option<usize>> = 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue