refactor: compact

This commit is contained in:
🪞👃🪞 2024-06-07 16:51:30 +03:00
parent abee6cc2c8
commit 60627ac3e5
43 changed files with 923 additions and 780 deletions

View file

@ -1,209 +0,0 @@
extern crate clap;
extern crate jack;
extern crate crossterm;
pub mod engine;
pub mod sequence;
use clap::Parser;
use std::error::Error;
use std::sync::{Arc, Mutex};
use std::io::Write;
use crossterm::QueueableCommand;
use crossterm::style::Stylize;
use crate::engine::jack::Jack;
use crate::sequence::Time;
use crate::sequence::midi::{PPQ, MIDISequence, MIDIEvent};
// bloop \
// --jack-client-name blooper
// --jack-audio-out L=system/playback_1 \
// --jack-audio-out R=system/playback_2 \
// --jack-midi-in 1="nanoKEY Studio [20] (capture): nanoKEY Studio nanoKEY Studio _"
fn main () -> Result<(), Box<dyn Error>> {
let cli = Cli::parse();
let app = App::init(&cli)?;
let sleep = std::time::Duration::from_millis(16);
let mut stdout = std::io::stdout();
loop {
app.render(&mut stdout)?;
std::thread::sleep(sleep);
};
Ok(())
}
struct App {
jack: Arc<Mutex<Jack>>,
time: u64,
root: MIDISequence,
stdout: std::io::Stdout,
}
impl App {
fn init (options: &Cli) -> Result<Self, Box<dyn Error>> {
let mut root = MIDISequence::new("/", 4 * PPQ);
root.add(00 * PPQ, MIDIEvent::NoteOn(36, 100))
.add(01 * PPQ, MIDIEvent::NoteOn(40, 100))
.add(02 * PPQ - PPQ / 2, MIDIEvent::NoteOn(36, 100))
.add(02 * PPQ + PPQ / 2, MIDIEvent::NoteOn(36, 100))
.add(03 * PPQ, MIDIEvent::NoteOn(40, 100))
.add(00 * PPQ / 4, MIDIEvent::NoteOn(44, 100))
.add(01 * PPQ / 4, MIDIEvent::NoteOn(44, 100))
.add(02 * PPQ / 4, MIDIEvent::NoteOn(44, 100))
.add(03 * PPQ / 4, MIDIEvent::NoteOn(44, 100))
.add(04 * PPQ / 4, MIDIEvent::NoteOn(44, 100))
.add(06 * PPQ / 4, MIDIEvent::NoteOn(44, 100))
.add(07 * PPQ / 4, MIDIEvent::NoteOn(44, 100))
.add(09 * PPQ / 4, MIDIEvent::NoteOn(44, 100))
.add(10 * PPQ / 4, MIDIEvent::NoteOn(44, 100))
.add(11 * PPQ / 4, MIDIEvent::NoteOn(44, 100))
.add(13 * PPQ / 4, MIDIEvent::NoteOn(44, 100))
.add(14 * PPQ / 4, MIDIEvent::NoteOn(44, 100))
.add(15 * PPQ / 4, MIDIEvent::NoteOn(44, 100));
Ok(Self {std::thread::sleep(std::time::Duration::from_millis(10))
jack: Jack::init_from_cli(&options)?,
time: 0,
root,
stdout: std::io::stdout(),
})
}
fn render (&self, stdout: &mut std::io::Stdout) -> Result<(), Box<dyn Error>> {
use crossterm::*;
let zoom = 8;
let (cols, rows) = terminal::size()?;
let transport = self.jack
.lock()
.expect("Failed to lock engine")
.transport.as_ref()
.expect("Failed to get transport")
.query()
.expect("Failed to query transport");
let state = transport.state;
let frame = transport.pos.frame();
let rate = transport.pos.frame_rate();
let time_string = if let Some(rate) = rate {
let second = (frame as f64) / (rate as f64);
let seconds = second.floor();
let msec = ((second - seconds) * 1000f64) as u64;
let seconds = second as u64 % 60;
let minutes = second as u64 / 60;
format!("{minutes}:{seconds:02}.{msec:03}")
} else {
"".to_string()
};
let mut x_offset = cols / 2 - 10;
let beat_string = if let Some(rate) = rate {
let second = (frame as f64) / (rate as f64);
let minute = second / 60f64;
let bpm = 120f64;
let div = 4;
let beats = minute * bpm;
let bars = beats as u64 / div as u64;
let beat = beats as u64 % div as u64 + 1;
x_offset -= ((beats as f64 % div as f64) * 8.0) as u16;
format!("{bars} {beat}/{div}")
} else {
"".to_string()
};
stdout
.queue(terminal::Clear(terminal::ClearType::All))?
.queue(cursor::Hide)?
.queue(cursor::MoveTo(1, 0))?
.queue(style::PrintStyledContent("bloop!".yellow()))?
.queue(cursor::MoveTo(1, 1))?
.queue(style::PrintStyledContent("v0.0.1".white()))?
.queue(cursor::MoveTo(10, 0))?
.queue(style::PrintStyledContent("rate".yellow()))?
.queue(cursor::MoveTo(10, 1))?
.queue(style::PrintStyledContent(format!("{}", rate.unwrap_or(0)).white()))?
.queue(cursor::MoveTo(18, 0))?
.queue(style::PrintStyledContent("state".yellow()))?
.queue(cursor::MoveTo(18, 1))?
.queue(style::PrintStyledContent(format!("{state:?}").white()))?
.queue(cursor::MoveTo(28, 0))?
.queue(style::PrintStyledContent("time".yellow()))?
.queue(cursor::MoveTo(28, 1))?
.queue(style::PrintStyledContent(format!("{time_string}").white()))?
.queue(cursor::MoveTo(40, 0))?
.queue(style::PrintStyledContent("beats".yellow()))?
.queue(cursor::MoveTo(40, 1))?
.queue(style::PrintStyledContent(format!("{beat_string}").white()))?
.queue(cursor::MoveTo(x_offset, 3.max(rows / 2 - 2)))?;
self.root.render(stdout, zoom)?;
stdout
.queue(cursor::MoveTo(0, rows - 2))?
.queue(style::PrintStyledContent(" SPACE".green()))?
.queue(cursor::MoveTo(0, rows - 1))?
.queue(style::PrintStyledContent(" play ".yellow()))?
.queue(cursor::MoveTo(8, rows - 2))?
.queue(style::PrintStyledContent(" HOME".green()))?
.queue(cursor::MoveTo(8, rows - 1))?
.queue(style::PrintStyledContent(" sync ".yellow()))?
//.queue(style::PrintStyledContent(" ↑↓←→".green()))?
//.queue(style::PrintStyledContent(" navigate ".yellow()))?
.queue(cursor::MoveTo(14, rows - 2))?
.queue(style::PrintStyledContent(" `".green()))?
.queue(cursor::MoveTo(14, rows - 1))?
.queue(style::PrintStyledContent(" markers ".yellow()))?
.queue(cursor::MoveTo(23, rows - 2))?
.queue(style::PrintStyledContent(" ,".green()))?
.queue(cursor::MoveTo(23, rows - 1))?
.queue(style::PrintStyledContent(" routing ".yellow()))?
.queue(cursor::MoveTo(32, rows - 2))?
.queue(style::PrintStyledContent(" a".green()))?
.queue(cursor::MoveTo(32, rows - 1))?
.queue(style::PrintStyledContent(" add audio ".yellow()))?
.queue(cursor::MoveTo(43, rows - 2))?
.queue(style::PrintStyledContent(" m".green()))?
.queue(cursor::MoveTo(43, rows - 1))?
.queue(style::PrintStyledContent(" add MIDI ".yellow()))?
.queue(cursor::MoveTo(54, rows - 2))?
.queue(style::PrintStyledContent(" o".green()))?
.queue(cursor::MoveTo(54, rows - 1))?
.queue(style::PrintStyledContent(" add OSC ".yellow()))?
.flush()?;
Ok(())
}
}
#[derive(Debug, Parser)]
#[command(version, about, long_about = None)]
pub struct Cli {
#[arg(long="jack-client-name", default_value="blooper")]
jack_client_name: String,
#[arg(long="jack-audio-in")]
jack_audio_ins: Vec<String>,
#[arg(long="jack-audio-out")]
jack_audio_outs: Vec<String>,
#[arg(long="jack-midi-in")]
jack_midi_ins: Vec<String>,
#[arg(long="jack-midi-out")]
jack_midi_outs: Vec<String>,
}
enum Command {
Play,
Stop,
Rec,
Dub,
Trim,
Jump,
}

View file

@ -1,4 +0,0 @@
pub struct Sample {
samples: Vec<Vec<u64>>,
cuepoints: Vec<Vec<Vec<u64>>>,
}

View file

@ -1,114 +0,0 @@
use crate::sequence::{Frame, Time};
/// Pulses per quarter note
pub const PPQ: usize = 96;
pub struct Message {
bytes: Vec<u8>
}
pub struct MIDISequence {
name: String,
length: usize,
events: Vec<(Frame, MIDIEvent)>,
notes: Vec<u8>,
}
impl MIDISequence {
pub fn new (name: &str, length: usize) -> Self {
Self {
name: name.to_string(),
length,
events: vec![],
notes: vec![],
}
}
pub fn add (&mut self, time: usize, event: MIDIEvent) -> &mut Self {
let mut sort = false;
match &event {
MIDIEvent::NoteOn(pitch, _velocity) => {
if !self.notes.contains(pitch) {
self.notes.push(*pitch);
sort = true;
}
}
}
if sort {
self.notes.sort();
}
self.events.push((time, event));
self
}
pub fn render (&self, stdout: &mut std::io::Stdout, resolution: usize)
-> Result<(), Box<dyn std::error::Error>>
{
use crossterm::{*, style::Stylize};
let (col, row) = cursor::position()?;
let unit = PPQ / resolution;
let length = self.length / unit;
let mut header = String::from(" ");
for i in 0..length {
if i % 8 == 0 {
header.push('┍');
} else {
header.push('━');
}
}
header.push('┑');
let mut tracks: Vec<(String, String)> = vec![];
for note in self.notes.iter().rev() {
let mut row_header = format!(" {note:3} ");
let mut row = String::new();
for beat in 0..length {
let mut active = false;
for (frame, event) in self.events.iter() {
match event {
MIDIEvent::NoteOn(pitch, _) => {
if pitch == note
&& *frame >= beat * unit
&& *frame < (beat + 1) * unit
{
active = true;
continue
}
}
}
}
if active {
row.push('⯀')
} else if beat == 0 {
row.push('│');
} else if beat % 8 == 0 {
row.push('┆');
} else {
row.push('·');
}
}
tracks.push((row_header, row));
}
stdout.queue(style::PrintStyledContent(header.grey()))?;
for (row_header, row) in tracks.into_iter() {
stdout
.queue(cursor::MoveDown(1))?
.queue(cursor::MoveToColumn(col))?
.queue(style::PrintStyledContent(row_header.grey()))?
.queue(style::PrintStyledContent(row.clone().white()))?
.queue(style::PrintStyledContent(row.clone().yellow()))?
.queue(style::PrintStyledContent(row.white()))?;
}
//stdout
//.queue(cursor::MoveDown(1))?
//.queue(cursor::MoveToColumn(col))?
//.queue(style::PrintStyledContent(footer.grey()))?;
//.queue(style::PrintStyledContent("················".grey()))?
//.queue(style::PrintStyledContent("│".yellow()))?
//.queue(cursor::MoveDown(1))?
//.queue(cursor::MoveLeft(18))?
//.queue(style::PrintStyledContent("└────────────────┘".yellow()))?;
Ok(())
}
}
pub enum MIDIEvent {
NoteOn(u8, u8)
}

View file

@ -1,80 +0,0 @@
pub mod audio;
pub mod midi;
pub mod osc;
pub mod time;
pub type Frame = usize;
pub enum Time {
Fixed(Frame),
Synced(usize),
}
//enum Event {
//Trigger,
//Gate,
//MIDI(MIDIEvent),
//Audio,
//OSC,
//Text,
//Sequence
//}
//struct Clip {
//name: String,
//length: u64,
//start: u64,
//end: u64,
//clips: Sequence<Event<Clip>>,
//markers: Sequence<Event<Marker>>,
//audio: Sequence<Event<audio::Sample>>,
//midi: Sequence<Event<midi::Message>>,
//osc: Sequence<Event<osc::Command>>,
//}
//enum ClipData {
//Trigger,
//Gate,
//MIDI,
//Audio,
//OSC,
//Text,
//Clip
//}
//impl Clip {
//fn new (name: &str, length: u64) -> Self {
//Self {
//name: name.into(),
//length,
//start: 0,
//end: length,
//clips: Sequence::new(),
//markers: Sequence::new(),
//audio: Sequence::new(),
//midi: Sequence::new(),
//osc: Sequence::new(),
//}
//}
//}
//struct Sequence<T> {
//events: Vec<Event<T>>,
//next: usize,
//}
//impl<T> Sequence<T> {
//fn new () -> Self {
//Self { events: vec![], next: 0 }
//}
//}
//struct Event<T> {
//time: u64,
//data: T,
//}
//struct Marker {
//name: String,
//}

View file

@ -1,4 +0,0 @@
pub struct Command {
name: String,
args: Vec<String>,
}

View file

23
src/config.rs Normal file
View file

@ -0,0 +1,23 @@
use crate::prelude::*;
const CONFIG_FILE_NAME: &'static str = "dawdle.toml";
pub fn create_dirs (xdg: &microxdg::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(())
}

View file

@ -89,8 +89,17 @@ impl Engine {
if event::poll(poll).is_ok() {
let event = event::read().unwrap();
let mut state = state.lock().unwrap();
if state.handle(&Event::Input(event)).is_err() {
break
match event {
crossterm::event::Event::Key(crossterm::event::KeyEvent {
code: KeyCode::Char('c'),
modifiers: KeyModifiers::CONTROL,
..
}) => {
state.exit()
},
_ => if state.handle(&crate::engine::Event::Input(event)).is_err() {
break
}
}
}
})

92
src/looper.rs Normal file
View 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
}
}

View file

@ -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(())
}

View file

View file

@ -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
}
}

View file

@ -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(())
}

View file

@ -14,12 +14,15 @@ pub mod looper;
pub mod sampler;
pub mod sequencer;
pub mod render;
pub mod config;
use crate::prelude::*;
use crate::render::ActionBar;
fn main () -> Result<(), Box<dyn Error>> {
let cli = cli::Cli::parse();
let xdg = microxdg::XdgApp::new("dawdle")?;
crate::config::create_dirs(&xdg)?;
if let Some(command) = cli.command {
run_one(&command)
} else {
@ -101,8 +104,8 @@ impl WidgetRef for App {
.direction(Direction::Vertical)
.constraints([
Constraint::Length(4),
Constraint::Min(16),
Constraint::Min(16),
Constraint::Max(18),
Constraint::Max(18),
].clone())
.split(area);
self.transport.render(area, buffer);

307
src/mixer.rs Normal file
View 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)
//}
//}

View file

@ -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)
}
}

View file

@ -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(())
}

View file

@ -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
}
}

View file

@ -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
}
}

View file

@ -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(())
}

View file

@ -58,6 +58,18 @@ pub fn draw_box (buffer: &mut Buffer, area: Rect) {
format!("{}", "".repeat((area.width - 2).into())),//.repeat(area.width.saturating_sub(2).into())),
Style::default().black()
);
for y in (area.y + 1)..(area.y + area.height - 1) {
buffer.set_string(
area.x, y,
format!(""),
Style::default().black().dim()
);
buffer.set_string(
area.x + area.width - 1, y,
format!(""),
Style::default().black().dim()
);
}
buffer.set_string(
area.x, area.y + area.height - 1,
format!("{}", "".repeat((area.width - 2).into())),//.repeat(area.width.saturating_sub(2).into())),

274
src/sampler.rs Normal file
View 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
}
}

View file

@ -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(())
}

View file

@ -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
}
}

View file

@ -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)
}
}

View file

@ -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(())
}

View file

@ -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 {
fn handle (&mut self, event: &crate::engine::Event) -> Result<(), Box<dyn Error>> {
handle(self, event)
@ -198,13 +119,7 @@ impl HandleInput for Sequencer {
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 {
match event.code {
KeyCode::Char('c') => {
if event.modifiers == KeyModifiers::CONTROL {
state.exit();
}
},
KeyCode::Down => {
state.cursor.0 = if state.cursor.0 >= 23 {
0
@ -255,7 +170,6 @@ fn handle (state: &mut Sequencer, event: &crate::engine::Event) -> Result<(), Bo
println!("{event:?}");
}
}
}
Ok(())
}
@ -270,25 +184,28 @@ const KEYS: [&'static str; 6] = [
impl WidgetRef for Sequencer {
fn render_ref (&self, area: Rect, buf: &mut Buffer) {
draw_box(buf, area);
draw_leaf(buf, area, 1, 0, "Name: Melody#000");
draw_leaf(buf, area, 3, 0, "Output: None");
draw_leaf(buf, area, 5, 0, "Input: None");
draw_leaf(buf, area, 7, 0, "Channel: 01");
draw_leaf(buf, area, 9, 0, "Zoom: 1/64");
draw_leaf(buf, area, 11, 0, "Rate: 1/1");
let mut area = area.inner(&Margin { horizontal: 1, vertical: 3, });
area.x = area.x + 14;
draw_sequence_keys(area, buf, &self.jack_client.as_client().transport().query().unwrap(), &self.sequence);
draw_sequence_header(area, buf);
draw_sequence_cursor(area, buf, self.cursor);
//╭{}╮
//faul
//ring
//rea.
//╰{}╯
let cursor = self.cursor;
{
let mut area = area.clone();
area.height = 18;
draw_box(buf, area);
}
draw_leaf(buf, area, 3, 0, "Channel: 01");
draw_leaf(buf, area, 5, 0, "Zoom: 1/64");
draw_leaf(buf, area, 7, 0, "Rate: 1/1");
{
let title = "(no inputs) -> Melody#000 -> (no outputs)";
let mut area = area.clone();
area.x = (area.width - title.len() as u16) / 2;
area.width = title.len() as u16;
draw_leaf(buf, area, 1, 0, title);
}
{
let mut area = area.inner(&Margin { horizontal: 1, vertical: 3, });
area.x = area.x + 14;
draw_sequence_keys(area, buf, &self.jack_client.as_client().transport().query().unwrap(), &self.sequence);
draw_sequence_header(area, buf);
draw_sequence_cursor(area, buf, self.cursor);
}
}
}
@ -322,9 +239,7 @@ fn draw_sequence_keys (
let bars = beats as u32 / div as u32;
let beat = beats as u32 % div as u32 + 1;
let beat_sub = beats % 1.0;
let sequence = sequence.lock().unwrap();
for key in 0..12 {
buf.set_string(area.x, area.y + 1 + key, KEYS[(key % 6) as usize],
Style::default().black());
@ -424,3 +339,82 @@ impl NotificationHandler for Notifications {
Control::Continue
}
}
#[cfg(test)]
mod test {
#[test]
fn test_midi_frames () {
let beats = 4;
let steps = 16;
let bpm = 120;
let rate = 44100; // Hz
let frame = 1f64 / rate as f64; // msec
let buf = 512; // frames
let t_beat = 60.0 / bpm as f64; // msec
let t_loop = t_beat * beats as f64; // msec
let t_step = t_beat / steps as f64; // msec
let assign = |chunk: usize| {
let start = chunk * buf; // frames
let end = (chunk + 1) * buf; // frames
println!("{chunk}: {start} .. {end}");
let mut steps: Vec<(usize, usize, f64)> = vec![];
for frame_index in start..end {
let frame_msec = frame_index as f64 * frame;
let offset = (frame_msec * 1000.0) % (t_step * 1000.0);
if offset < 0.1 {
let time = frame_index - start;
let step_index = (frame_msec % t_loop / t_step) as usize;
println!("{chunk}: {frame_index} ({time}) -> {step_index} ({frame_msec} % {t_step} = {offset})");
steps.push((time, step_index, offset));
}
}
steps
};
for chunk in 0..10 {
let chunk = assign(chunk);
//println!("{chunk} {:#?}", assign(chunk));
}
}
#[test]
fn test_midi_frames_2 () {
let beats = 4;
let steps = 16;
let bpm = 120;
let rate = 44100; // Hz
let frame = 1f64 / rate as f64; // msec
let buf = 512; // frames
let t_beat = 60.0 / bpm as f64; // msec
let t_loop = t_beat * beats as f64; // msec
let t_step = t_beat / steps as f64; // msec
let mut step_frames = vec![];
for step in 0..beats*steps {
let step_index = (step as f64 * t_step / frame) as usize;
step_frames.push(step_index);
}
let loop_frames = (t_loop*rate as f64) as usize;
let mut frame_steps: Vec<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));
}
}
}