From b73aa8a0dc994cab11bfdc8395c26288fdb65024 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 16 Jun 2024 13:58:46 +0300 Subject: [PATCH] i dont know why that worked --- README.md | 53 ---- src/device.rs | 108 +++++--- src/device/chain.rs | 9 +- src/device/launcher.rs | 2 +- src/device/looper.rs | 5 +- src/device/mixer.rs | 33 +-- src/device/plugin.rs | 8 +- src/device/sampler.rs | 31 +-- src/device/sequencer.rs | 494 ++++++++++++++--------------------- src/device/sequencer_test.rs | 77 ++++++ src/device/transport.rs | 34 +-- src/{render.rs => draw.rs} | 0 src/layout.rs | 14 +- src/main.rs | 9 +- src/note.rs | 26 ++ src/prelude.rs | 40 ++- src/time.rs | 90 +++++++ 17 files changed, 552 insertions(+), 481 deletions(-) create mode 100644 src/device/sequencer_test.rs rename src/{render.rs => draw.rs} (100%) create mode 100644 src/note.rs create mode 100644 src/time.rs diff --git a/README.md b/README.md index 3b8b3cd0..e69de29b 100644 --- a/README.md +++ b/README.md @@ -1,53 +0,0 @@ -# That's It! - -Minimal, cross-environment user interface framework. - -## Definitions - -### User interface - -A **user interface** is a program which, repeatedly: -* **renders** information to be displayed to the user; -* **handles** input from the user; -thus interactively performing tasks until an exit condition is met. - -### Engine - -An **engine** is the underlying platform responsible for: -* Displaying your program's `render`ed output to the user -* Reading user input to be `handle`d by your program. - -For example, the `tui` engine is based on `crossterm`, -a library for rendering text user interfaces. - -### Widget - -A **widget** is any struct that implements the `Input` and `Output` -traits for a given engine `E`. This enables it to act as a component of the -user interface. Widgets may contain arbitrary state -- including other widgets. -We provide a set of basic widgets that allow you to define standard hierarchical -UI layouts. It's the implementor's responsibility to define -`render` and `handle` behaviors for custom widgets. - -### Input - -**To respond to user input**, implement the trait `Input`. -It has a single method, `handle`, which takes an input -event, and returns an engine-specific value. - -In the case of the `tui` engine, the returned value is a -`bool` corresponding to whether the input event was captured -by the current widget. Returning `true` from `render` terminates -the handling of the current event; returning `false` "bubbles" it -to the parent widget. - -### Output - -**To display data to the user**, implement the trait `Output`. -It has a single method, `render`, which takes a mutable instance -of the engine, and returns and engine-specific value. - -In the case of the `tui` engine, the returned value is `[u16;2]`, -corresponding to the size requested by the widget. This allows -the layout components to implement dynamic, responsive layouts in the -terminal. diff --git a/src/device.rs b/src/device.rs index 02074693..cb3fe14d 100644 --- a/src/device.rs +++ b/src/device.rs @@ -16,8 +16,6 @@ pub use self::mixer::Mixer; pub use self::looper::Looper; pub use self::plugin::Plugin; -use crate::prelude::*; -use std::sync::mpsc; use crossterm::event; pub fn run (device: impl Device + Send + Sync + 'static) -> Result<(), Box> { @@ -43,7 +41,7 @@ pub fn run (device: impl Device + Send + Sync + 'static) -> Result<(), Box { exited.store(true, Ordering::Relaxed); }, - _ => if device.lock().unwrap().handle(&EngineEvent::Input(event)).is_err() { + _ => if device.lock().unwrap().handle(&AppEvent::Input(event)).is_err() { break } } @@ -90,7 +88,7 @@ pub fn run (device: impl Device + Send + Sync + 'static) -> Result<(), Box Usually<()> { + fn handle (&mut self, _event: &AppEvent) -> Usually<()> { Ok(()) } fn render (&self, _buffer: &mut Buffer, _area: Rect) -> Usually { @@ -100,28 +98,32 @@ pub trait Device: Send + Sync { } pub struct DynamicDevice { - pub state: Mutex, + pub state: Arc>, pub render: MutexUsually + Send>>, - pub handle: MutexUsually<()> + Send>>, - pub process: Mutex> + pub handle: ArcUsually<()> + Send>>>, + pub process: ArcControl + Send>>>, +} + +impl Device for DynamicDevice { + fn handle (&mut self, event: &AppEvent) -> Usually<()> { + self.handle.lock().unwrap()(&mut *self.state.lock().unwrap(), event) + } + fn render (&self, buf: &mut Buffer, area: Rect) -> Usually { + self.render.lock().unwrap()(&*self.state.lock().unwrap(), buf, area) + } } impl DynamicDevice { - fn new <'a, R, H, P> ( - render: R, - handle: H, - process: P, - state: T - ) -> Self where + fn new <'a, R, H, P> (render: R, handle: H, process: P, state: T) -> Self where R: FnMut(&T, &mut Buffer, Rect)->Usually + Send + 'static, - H: FnMut(&mut T, &EngineEvent) -> Result<(), Box> + Send + 'static, - P: FnMut(&mut T) + Send + 'static + H: FnMut(&mut T, &AppEvent) -> Result<(), Box> + Send + 'static, + P: FnMut(&mut T, &Client, &ProcessScope)->Control + Send + 'static, { Self { + state: Arc::new(Mutex::new(state)), render: Mutex::new(Box::new(render)), - handle: Mutex::new(Box::new(handle)), - process: Mutex::new(Box::new(process)), - state: Mutex::new(state), + handle: Arc::new(Mutex::new(Box::new(handle))), + process: Arc::new(Mutex::new(Box::new(process))), } } fn state (&self) -> std::sync::MutexGuard<'_, T> { @@ -129,12 +131,29 @@ impl DynamicDevice { } } -impl Device for DynamicDevice { - fn handle (&mut self, event: &EngineEvent) -> Usually<()> { - self.handle.lock().unwrap()(&mut *self.state.lock().unwrap(), event) - } - fn render (&self, buf: &mut Buffer, area: Rect) -> Usually { - self.render.lock().unwrap()(&*self.state.lock().unwrap(), buf, area) +impl DynamicDevice { + fn activate (&self, client: Client) -> Usually<()> { + let notifications = { + let state = self.state.clone(); + let handle = self.handle.clone(); + move|event|{ + let mut state = state.lock().unwrap(); + let mut handle = handle.lock().unwrap(); + handle(&mut state, &event).unwrap() + } + }; + let notifications = Notifications(Box::new(notifications)); + let handler = ClosureProcessHandler::new(Box::new({ + let state = self.state.clone(); + let process = self.process.clone(); + move|client: &Client, scope: &ProcessScope|{ + let mut state = state.lock().unwrap(); + let mut process = process.lock().unwrap(); + (process)(&mut state, client, scope) + } + }) as BoxedProcessHandler); + client.activate_async(notifications, handler)?; + Ok(()) } } @@ -150,16 +169,9 @@ impl WidgetRef for dyn Device { } } -pub struct Engine { - exited: Arc, - sender: Sender, - receiver: Receiver, - pub jack_client: Jack, -} - #[derive(Debug)] -pub enum EngineEvent { - /// An input event that must be handled. +pub enum AppEvent { + /// Terminal input Input(::crossterm::event::Event), /// Update values but not the whole form. Update, @@ -169,6 +181,8 @@ pub enum EngineEvent { Focus, /// Device loses focus Blur, + /// JACK notification + Jack(JackEvent) } pub fn activate_jack_client ( @@ -191,40 +205,64 @@ fn panic_hook (info: &std::panic::PanicInfo) { .unwrap(); } -pub struct Notifications;//(mpsc::Sender); +#[derive(Debug)] +pub enum JackEvent { + ThreadInit, + Shutdown, + Freewheel, + SampleRate, + ClientRegistration, + PortRegistration, + PortRename, + PortsConnected, + GraphReorder, + XRun, +} -impl NotificationHandler for Notifications { +pub struct Notifications(T); + +impl NotificationHandler for Notifications { fn thread_init (&self, _: &Client) { + self.0(AppEvent::Jack(JackEvent::ThreadInit)); } fn shutdown (&mut self, status: ClientStatus, reason: &str) { + self.0(AppEvent::Jack(JackEvent::Shutdown)); } fn freewheel (&mut self, _: &Client, is_enabled: bool) { + self.0(AppEvent::Jack(JackEvent::Freewheel)); } fn sample_rate (&mut self, _: &Client, _: Frames) -> Control { + self.0(AppEvent::Jack(JackEvent::SampleRate)); Control::Quit } fn client_registration (&mut self, _: &Client, name: &str, is_reg: bool) { + self.0(AppEvent::Jack(JackEvent::ClientRegistration)); } fn port_registration (&mut self, _: &Client, port_id: PortId, is_reg: bool) { + self.0(AppEvent::Jack(JackEvent::PortRegistration)); } fn port_rename (&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control { + self.0(AppEvent::Jack(JackEvent::PortRename)); Control::Continue } fn ports_connected (&mut self, _: &Client, id_a: PortId, id_b: PortId, are: bool) { + self.0(AppEvent::Jack(JackEvent::PortsConnected)); } fn graph_reorder (&mut self, _: &Client) -> Control { + self.0(AppEvent::Jack(JackEvent::GraphReorder)); Control::Continue } fn xrun (&mut self, _: &Client) -> Control { + self.0(AppEvent::Jack(JackEvent::XRun)); Control::Continue } } diff --git a/src/device/chain.rs b/src/device/chain.rs index b243217d..9852bab8 100644 --- a/src/device/chain.rs +++ b/src/device/chain.rs @@ -7,17 +7,14 @@ pub struct Chain { impl Chain { pub fn new (name: &str, devices: Vec>) -> Result, Box> { - Ok(DynamicDevice::new(render, handle, |_|{}, Self { + Ok(DynamicDevice::new(render, handle, process, Self { name: name.into(), devices })) } } -pub fn process ( - client: &Client, - scope: &ProcessScope -) -> Control { +pub fn process (state: &mut Chain, client: &Client, scope: &ProcessScope) -> Control { Control::Continue } @@ -54,6 +51,6 @@ pub fn render (state: &Chain, buf: &mut Buffer, area: Rect) Ok(Rect { x: area.x, y: area.y, width: x, height: y }) } -pub fn handle (state: &mut Chain, event: &EngineEvent) -> Result<(), Box> { +pub fn handle (state: &mut Chain, event: &AppEvent) -> Result<(), Box> { Ok(()) } diff --git a/src/device/launcher.rs b/src/device/launcher.rs index 53a042c9..dcb3f321 100644 --- a/src/device/launcher.rs +++ b/src/device/launcher.rs @@ -6,7 +6,7 @@ pub struct Launcher { impl Launcher { pub fn new (name: &str) -> Result, Box> { - Ok(DynamicDevice::new(render, handle, |_|{}, Self { + Ok(DynamicDevice::new(render, handle, Self { name: name.into(), })) } diff --git a/src/device/looper.rs b/src/device/looper.rs index c7700dc8..491eae0c 100644 --- a/src/device/looper.rs +++ b/src/device/looper.rs @@ -6,13 +6,14 @@ pub struct Looper { impl Looper { pub fn new (name: &str) -> Result, Box> { - Ok(DynamicDevice::new(render, handle, |_|{}, Self { + Ok(DynamicDevice::new(render, handle, process, Self { name: name.into(), })) } } pub fn process ( + state: &mut Looper, client: &Client, scope: &ProcessScope ) -> Control { @@ -32,7 +33,7 @@ pub fn render (state: &Looper, buf: &mut Buffer, area: Rect) Ok(Rect::default()) } -pub fn handle (state: &mut Looper, event: &EngineEvent) -> Result<(), Box> { +pub fn handle (state: &mut Looper, event: &AppEvent) -> Result<(), Box> { Ok(()) } diff --git a/src/device/mixer.rs b/src/device/mixer.rs index 33d1190a..a1773524 100644 --- a/src/device/mixer.rs +++ b/src/device/mixer.rs @@ -2,7 +2,6 @@ use crate::prelude::*; pub struct Mixer { name: String, - jack: Jack, tracks: Vec, selected_track: usize, selected_column: usize, @@ -10,33 +9,27 @@ pub struct Mixer { impl Mixer { pub fn new (name: &str) -> Result, Box> { - let (client, status) = Client::new( - name, - ClientOptions::NO_START_SERVER - )?; - let jack = MixerJack.activate(client, ClosureProcessHandler::new(Box::new( - move|client: &Client, scope: &ProcessScope|process(client, scope) - ) as BoxedProcessHandler))?; - Ok(DynamicDevice::new(render, handle, |_|{}, Self { + let (client, _status) = Client::new(name, ClientOptions::NO_START_SERVER)?; + Ok(DynamicDevice::new(render, handle, process, Self { name: name.into(), selected_column: 0, selected_track: 1, tracks: vec![ - Track::new(&jack.as_client(), 1, "Mono 1")?, - Track::new(&jack.as_client(), 1, "Mono 2")?, - Track::new(&jack.as_client(), 2, "Stereo 1")?, - Track::new(&jack.as_client(), 2, "Stereo 2")?, - Track::new(&jack.as_client(), 2, "Stereo 3")?, - Track::new(&jack.as_client(), 2, "Bus 1")?, - Track::new(&jack.as_client(), 2, "Bus 2")?, - Track::new(&jack.as_client(), 2, "Mix")?, + Track::new(&client, 1, "Mono 1")?, + Track::new(&client, 1, "Mono 2")?, + Track::new(&client, 2, "Stereo 1")?, + Track::new(&client, 2, "Stereo 2")?, + Track::new(&client, 2, "Stereo 3")?, + Track::new(&client, 2, "Bus 1")?, + Track::new(&client, 2, "Bus 2")?, + Track::new(&client, 2, "Mix")?, ], - jack, })) } } pub fn process ( + mixer: &mut Mixer, client: &Client, scope: &ProcessScope ) -> Control { @@ -107,8 +100,8 @@ pub fn render (state: &Mixer, buf: &mut Buffer, mut area: Rect) Ok(area) } -pub fn handle (state: &mut Mixer, event: &EngineEvent) -> Result<(), Box> { - if let EngineEvent::Input(crossterm::event::Event::Key(event)) = event { +pub fn handle (state: &mut Mixer, event: &AppEvent) -> Result<(), Box> { + if let AppEvent::Input(crossterm::event::Event::Key(event)) = event { match event.code { //KeyCode::Char('c') => { diff --git a/src/device/plugin.rs b/src/device/plugin.rs index db27f014..9c116fbc 100644 --- a/src/device/plugin.rs +++ b/src/device/plugin.rs @@ -6,12 +6,16 @@ pub struct Plugin { impl Plugin { pub fn new (name: &str) -> Result, Box> { - Ok(DynamicDevice::new(render, handle, |_|{}, Self { + Ok(DynamicDevice::new(render, handle, process, Self { name: name.into() })) } } +pub fn process (state: &mut Plugin, client: &Client, scope: &ProcessScope) -> Control { + Control::Continue +} + pub fn render (state: &Plugin, buf: &mut Buffer, Rect { x, y, width, height }: Rect) -> Usually { @@ -27,6 +31,6 @@ pub fn render (state: &Plugin, buf: &mut Buffer, Rect { x, y, width, height }: R Ok(Rect { x, y, width: 40, height: 7 }) } -pub fn handle (state: &mut Plugin, event: &EngineEvent) -> Result<(), Box> { +pub fn handle (state: &mut Plugin, event: &AppEvent) -> Result<(), Box> { Ok(()) } diff --git a/src/device/sampler.rs b/src/device/sampler.rs index db4ac260..807fe0e2 100644 --- a/src/device/sampler.rs +++ b/src/device/sampler.rs @@ -7,7 +7,7 @@ pub const ACTIONS: [(&'static str, &'static str);2] = [ pub struct Sampler { name: String, - jack: Jack, + input: ::jack::Port<::jack::MidiIn>, samples: Arc>>, selected_sample: usize, selected_column: usize, @@ -23,36 +23,23 @@ impl Sampler { ]; let samples = Arc::new(Mutex::new(samples)); let input = client.register_port("trigger", ::jack::MidiIn::default())?; - let jack = SamplerJack.activate(client, ClosureProcessHandler::new({ - let exited = exited.clone(); - let samples = samples.clone(); - Box::new(move |client: &Client, scope: &ProcessScope|{ - process(client, scope, &exited, &samples, &input); - Control::Continue - }) as BoxedProcessHandler - }))?; - Ok(DynamicDevice::new(render, handle, |_|{}, Self { + Ok(DynamicDevice::new(render, handle, process, Self { name: name.into(), + input, selected_sample: 0, selected_column: 0, samples, - jack, })) } } pub fn process ( - client: &Client, - scope: &ProcessScope, - exited: &Arc, - samples: &Arc>>, - input: &Port + state: &mut Sampler, + client: &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 mut samples = state.samples.lock().unwrap(); + for event in state.input.iter(scope) { let len = 3.min(event.bytes.len()); let mut data = [0; 3]; data[..len].copy_from_slice(&event.bytes[..len]); @@ -183,7 +170,7 @@ pub fn render (state: &Sampler, buf: &mut Buffer, Rect { x, y, width, height }: //Ok(()) //} -pub fn handle (state: &mut Sampler, event: &EngineEvent) -> Result<(), Box> { +pub fn handle (state: &mut Sampler, event: &AppEvent) -> Result<(), Box> { //if let Event::Input(crossterm::event::Event::Key(event)) = event { //match event.code { //KeyCode::Char('c') => { diff --git a/src/device/sequencer.rs b/src/device/sequencer.rs index b29a7594..c7656f21 100644 --- a/src/device/sequencer.rs +++ b/src/device/sequencer.rs @@ -2,18 +2,16 @@ use crate::prelude::*; use ratatui::style::Stylize; pub struct Sequencer { - name: String, - jack: Jack, - playing: Arc, - recording: Arc, - overdub: Arc, - inputs_open: Arc, - outputs_open: Arc, - cursor: (u16, u16, u16), - timesig: (f32, f32), - sequence: Arc>>>>, - sequences: Arc>>, - mode: SequencerMode, + name: String, + mode: SequencerMode, + cursor: (u16, u16, u16), + rate: Hz, + tempo: Tempo, + sequences: Vec, + input_port: ::jack::Port<::jack::MidiIn>, + input_connect: Vec, + output_port: ::jack::Port<::jack::MidiOut>, + output_connect: Vec, } pub enum SequencerMode { @@ -24,120 +22,111 @@ pub enum SequencerMode { } impl Sequencer { - pub fn new (name: &str) -> Result, Box> { - let beats = 4; - let steps = 16; - let bpm = 120.0; - let rate = 44100; // Hz - let frame = 1f64 / rate as f64; // msec - let buf = 512; // frames - let t_beat = 60.0 / bpm; // msec - let t_loop = t_beat * beats as f64; // msec - let t_step = t_beat / steps as f64; // msec - let exited = Arc::new(AtomicBool::new(false)); - let playing = Arc::new(AtomicBool::new(true)); - let recording = Arc::new(AtomicBool::new(true)); - let overdub = Arc::new(AtomicBool::new(false)); - let sequence: Arc>>>> = Arc::new( - Mutex::new(vec![vec![None;64];128]) - ); - let mut step_frames = vec![]; - for step in 0..beats*steps { - let step_index = (step as f64 * t_step / frame) as usize; - step_frames.push(step_index); - } - let loop_frames = (t_loop*rate as f64) as usize; - let mut frame_steps: Vec> = vec![None;loop_frames]; - for (index, frame) in step_frames.iter().enumerate() { - frame_steps[*frame] = Some(index); - } - let (client, _status) = Client::new( - name, ClientOptions::NO_START_SERVER - )?; - let mut input = client.register_port( - "input", ::jack::MidiIn::default() - )?; - let mut output = client.register_port( - "output", ::jack::MidiOut::default() - )?; - Ok(DynamicDevice::new(render, handle, |_|{}, Self { - mode: SequencerMode::Vertical, + let (client, _status) = Client::new(name, ClientOptions::NO_START_SERVER)?; + let mut input = client.register_port("input", ::jack::MidiIn::default())?; + let mut output = client.register_port("output", ::jack::MidiOut::default())?; + let device = DynamicDevice::new(render, handle, process, Self { name: name.into(), - playing: playing.clone(), - recording: recording.clone(), - overdub: overdub.clone(), - sequence: sequence.clone(), - inputs_open: Arc::new(AtomicBool::new(false)), - outputs_open: Arc::new(AtomicBool::new(false)), - sequences: Arc::new(Mutex::new(vec![ - ])), + mode: SequencerMode::Vertical, cursor: (11, 0, 0), - timesig: (4.0, 4.0), - jack: SequencerJack.activate(client, ClosureProcessHandler::new( - Box::new(move |client: &Client, scope: &ProcessScope| process(client, scope)) as BoxedProcessHandler - ))? - })) + rate: Hz(client.sample_rate() as u32), + tempo: Tempo(120000), + sequences: vec![ + Sequence::new(&client, "Rhythm#000",)?, + Sequence::new(&client, "Rhythm#001",)?, + Sequence::new(&client, "Rhythm#002",)?, + Sequence::new(&client, "Rhythm#003",)?, + ], + input_port: client.register_port("in", ::jack::MidiIn::default())?, + input_connect: vec![], + output_port: client.register_port("out", ::jack::MidiOut::default())?, + output_connect: vec![], + }); + device.activate(client)?; + Ok(device) } - } -pub fn process ( - client: &Client, - scope: &ProcessScope -) -> Control { - //if exited.fetch_and(true, Ordering::Relaxed) { - //return Control::Quit - //} - //if client.transport().query_state().unwrap() == TransportState::Rolling { - //let chunk_start = scope.last_frame_time(); - //let chunk_size = scope.n_frames(); - //let chunk_end = chunk_start + chunk_size; - //let start_looped = chunk_start as usize % loop_frames; - //let end_looped = chunk_end as usize % loop_frames; - - //// Write MIDI notes from input to sequence - //if recording.fetch_and(true, Ordering::Relaxed) { - ////let overdub = overdub.fetch_and(true, Ordering::Relaxed); - //for event in input.iter(scope) { - //let ::jack::RawMidi { time, bytes } = event; - //println!("\n{time} {bytes:?}"); - //} - //} - - //// Write MIDI notes from sequence to output - //if playing.fetch_and(true, Ordering::Relaxed) { - //let mut writer = output.writer(scope); - //let sequence = sequence.lock().unwrap(); - //for frame in 0..chunk_size { - //let value = frame_steps[(start_looped + frame as usize) % loop_frames]; - //if let Some(step) = value { - //for track in sequence.iter() { - //for event in track[step].iter() { - //writer.write(&::jack::RawMidi { - //time: frame as u32, - //bytes: &match event { - //SequencerEvent::NoteOn(pitch, velocity) => [ - //0b10010000, - //*pitch, - //*velocity - //], - //SequencerEvent::NoteOff(pitch) => [ - //0b10000000, - //*pitch, - //0b00000000 - //], - //} - //}).unwrap() - //} - //} - //} - //} - //} - //} +pub fn process (state: &mut Sequencer, client: &Client, scope: &ProcessScope) -> Control { + for seq in state.sequences.iter() { + seq.chunk_out(scope, &mut state.output_port); + seq.chunk_in(scope, &state.input_port); + } Control::Continue } +pub struct Sequence { + is_recording: bool, + overdub: bool, + ppq: u32, + buffer: std::collections::BTreeMap>>>, + loop_points: Option<(u32, u32)>, + is_playing: bool, +} + +impl Sequence { + fn new (client: &Client, name: &str) -> Usually { + Ok(Self { + is_recording: false, + overdub: false, + ppq: 96, + buffer: std::collections::BTreeMap::new(), + loop_points: None, + is_playing: false + }) + } + fn chunk_in ( + &self, + scope: &ProcessScope, + port: &::jack::Port<::jack::MidiIn>, + ) { + if self.is_recording { + for event in port.iter(scope) { + let ::jack::RawMidi { time, bytes } = event; + println!("\n{time} {bytes:?}"); + } + } + } + fn chunk_out ( + &self, + scope: &ProcessScope, + port: &mut ::jack::Port<::jack::MidiOut>, + ) { + if self.is_playing { + let size = scope.n_frames(); + let start = scope.last_frame_time(); + let end = start + size; + let mut writer = port.writer(scope); + for time in 0..size { + // TODO + } + } + } +} + + +impl NotificationHandler for Sequencer { + fn thread_init (&self, _: &Client) {} + fn shutdown (&mut self, status: ClientStatus, reason: &str) {} + fn freewheel (&mut self, _: &Client, is_enabled: bool) {} + fn sample_rate (&mut self, _: &Client, _: Frames) -> Control { + Control::Quit + } + fn client_registration (&mut self, _: &Client, name: &str, is_reg: bool) {} + fn port_registration (&mut self, _: &Client, port_id: PortId, is_reg: bool) {} + fn port_rename (&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control { + Control::Continue + } + fn ports_connected (&mut self, _: &Client, id_a: PortId, id_b: PortId, are: bool) {} + fn graph_reorder (&mut self, _: &Client) -> Control { + Control::Continue + } + fn xrun (&mut self, _: &Client) -> Control { + Control::Continue + } +} + pub const ACTIONS: [(&'static str, &'static str);4] = [ ("+/-", "Zoom"), ("A/D", "Add/delete note"), @@ -145,12 +134,12 @@ pub const ACTIONS: [(&'static str, &'static str);4] = [ ("CapsLock", "Auto advance"), ]; -pub fn handle (state: &mut Sequencer, event: &EngineEvent) -> Result<(), Box> { +pub fn handle (state: &mut Sequencer, event: &AppEvent) -> Result<(), Box> { - if let EngineEvent::Focus = event { + if let AppEvent::Focus = event { } - if let EngineEvent::Input(Event::Key(event)) = event { + if let AppEvent::Input(Event::Key(event)) = event { match event.code { KeyCode::Down => { state.cursor.0 = if state.cursor.0 >= 23 { @@ -189,20 +178,20 @@ pub fn handle (state: &mut Sequencer, event: &EngineEvent) -> Result<(), Box { - state.inputs_open.fetch_xor(true, Ordering::Relaxed); + //state.inputs_open.fetch_xor(true, Ordering::Relaxed); }, KeyCode::Char('o') => { - state.outputs_open.fetch_xor(true, Ordering::Relaxed); + //state.outputs_open.fetch_xor(true, Ordering::Relaxed); }, KeyCode::Char('a') => { let row = state.cursor.0 as usize; let step = state.cursor.1 as usize; let duration = state.cursor.2 as usize; - let mut sequence = state.sequence.lock().unwrap(); - sequence[row][step] = Some(SequencerEvent::NoteOn(48 - row as u8, 128)); - if state.cursor.2 > 0 { - sequence[row][step + duration] = Some(SequencerEvent::NoteOff(35)); - } + //let mut sequence = state.sequence.lock().unwrap(); + //sequence[row][step] = Some(SequencerEvent::NoteOn(48 - row as u8, 128)); + //if state.cursor.2 > 0 { + //sequence[row][step + duration] = Some(SequencerEvent::NoteOff(35)); + //} }, _ => { println!("{event:?}"); @@ -518,186 +507,83 @@ fn draw_sequence_cursor ( buf.set_string(area.x + cursor.1 + 3, area.y + 1 + cursor_y, cursor_text, style); } -pub struct SequencerJack; +//pub type MIDISequenceVoice = std::collections::BTreeMap; -impl SequencerJack { - fn activate (self, client: Client, handler: ClosureProcessHandler) - -> Usually>> - { - Ok(client.activate_async(self, handler)?) - } -} +//#[derive(Clone)] +//pub enum NoteEvent { + //On(u8), + //Off(u8), +//} -impl NotificationHandler for SequencerJack { - fn thread_init (&self, _: &Client) { - } +//const VOICE_EMPTY: MIDISequenceVoice = MIDISequenceVoice::new(); - fn shutdown (&mut self, status: ClientStatus, reason: &str) { - } +//impl MIDISequence { + //fn new () -> Self { + //Self { + //channels: [ + //MIDISequenceChannel::new(1), + //MIDISequenceChannel::new(2), + //MIDISequenceChannel::new(3), + //MIDISequenceChannel::new(4), + //MIDISequenceChannel::new(5), + //MIDISequenceChannel::new(6), + //MIDISequenceChannel::new(7), + //MIDISequenceChannel::new(8), + //MIDISequenceChannel::new(9), + //MIDISequenceChannel::new(10), + //MIDISequenceChannel::new(11), + //MIDISequenceChannel::new(12), + //MIDISequenceChannel::new(13), + //MIDISequenceChannel::new(14), + //MIDISequenceChannel::new(15), + //MIDISequenceChannel::new(16), + //] + //} + //} +//} - fn freewheel (&mut self, _: &Client, is_enabled: bool) { - } +//pub struct MIDISequence { + //channels: [MIDISequenceChannel;16], +//} +//pub struct MIDISequenceChannel { + //number: u8, + //notes: [MIDISequenceVoice;128], +//} +//impl MIDISequenceChannel { + //fn new (number: u8) -> Self { + //Self { + //number, + //notes: [VOICE_EMPTY;128] + //} + //} +//} - fn sample_rate (&mut self, _: &Client, _: Frames) -> Control { - Control::Quit - } +//#[derive(Clone)] +//pub enum SequencerEvent { + //NoteOn(u8, u8), + //NoteOff(u8) +//} - fn client_registration (&mut self, _: &Client, name: &str, is_reg: bool) { - } - - fn port_registration (&mut self, _: &Client, port_id: PortId, is_reg: bool) { - } - - fn port_rename (&mut self, _: &Client, id: PortId, old: &str, new: &str) -> Control { - Control::Continue - } - - fn ports_connected (&mut self, _: &Client, id_a: PortId, id_b: PortId, are: bool) { - } - - fn graph_reorder (&mut self, _: &Client) -> Control { - Control::Continue - } - - fn xrun (&mut self, _: &Client) -> Control { - Control::Continue - } -} - -pub struct MIDISequence { - channels: [MIDISequenceChannel;16], -} - -pub struct MIDISequenceChannel { - number: u8, - notes: [MIDISequenceVoice;128], -} - -pub type MIDISequenceVoice = std::collections::BTreeMap; - -#[derive(Clone)] -pub enum NoteEvent { - On(u8), - Off(u8), -} - -const VOICE_EMPTY: MIDISequenceVoice = MIDISequenceVoice::new(); - -impl MIDISequence { - fn new () -> Self { - Self { - channels: [ - MIDISequenceChannel::new(1), - MIDISequenceChannel::new(2), - MIDISequenceChannel::new(3), - MIDISequenceChannel::new(4), - MIDISequenceChannel::new(5), - MIDISequenceChannel::new(6), - MIDISequenceChannel::new(7), - MIDISequenceChannel::new(8), - MIDISequenceChannel::new(9), - MIDISequenceChannel::new(10), - MIDISequenceChannel::new(11), - MIDISequenceChannel::new(12), - MIDISequenceChannel::new(13), - MIDISequenceChannel::new(14), - MIDISequenceChannel::new(15), - MIDISequenceChannel::new(16), - ] - } - } -} - -impl MIDISequenceChannel { - fn new (number: u8) -> Self { - Self { - number, - notes: [VOICE_EMPTY;128] - } - } -} - -#[derive(Clone)] -pub enum SequencerEvent { - NoteOn(u8, u8), - NoteOff(u8) -} - -#[cfg(test)] -mod test { - #[test] - fn test_midi_frames () { - let beats = 4; - let steps = 16; - let bpm = 120; - let rate = 44100; // Hz - let frame = 1f64 / rate as f64; // msec - let buf = 512; // frames - let t_beat = 60.0 / bpm as f64; // msec - let t_loop = t_beat * beats as f64; // msec - let t_step = t_beat / steps as f64; // msec - - let assign = |chunk: usize| { - let start = chunk * buf; // frames - let end = (chunk + 1) * buf; // frames - println!("{chunk}: {start} .. {end}"); - let mut steps: Vec<(usize, usize, f64)> = vec![]; - for frame_index in start..end { - let frame_msec = frame_index as f64 * frame; - let offset = (frame_msec * 1000.0) % (t_step * 1000.0); - if offset < 0.1 { - let time = frame_index - start; - let step_index = (frame_msec % t_loop / t_step) as usize; - println!("{chunk}: {frame_index} ({time}) -> {step_index} ({frame_msec} % {t_step} = {offset})"); - steps.push((time, step_index, offset)); - } - } - steps - }; - - for chunk in 0..10 { - let chunk = assign(chunk); - //println!("{chunk} {:#?}", assign(chunk)); - } - } - - #[test] - fn test_midi_frames_2 () { - let beats = 4; - let steps = 16; - let bpm = 120; - let rate = 44100; // Hz - let frame = 1f64 / rate as f64; // msec - let buf = 512; // frames - let t_beat = 60.0 / bpm as f64; // msec - let t_loop = t_beat * beats as f64; // msec - let t_step = t_beat / steps as f64; // msec - let mut step_frames = vec![]; - for step in 0..beats*steps { - let step_index = (step as f64 * t_step / frame) as usize; - step_frames.push(step_index); - } - let loop_frames = (t_loop*rate as f64) as usize; - let mut frame_steps: Vec> = vec![None;loop_frames]; - for (index, frame) in step_frames.iter().enumerate() { - println!("{index} {frame}"); - frame_steps[*frame] = Some(index); - } - let assign = |chunk: usize| { - let (start, end) = (chunk * buf, (chunk + 1) * buf); // frames - let (start_looped, end_looped) = (start % loop_frames, end % loop_frames); - println!("{chunk}: {start} .. {end} ({start_looped} .. {end_looped})"); - let mut steps: Vec> = vec![None;buf]; - for frame in 0..buf { - let value = frame_steps[(start_looped + frame) as usize % loop_frames]; - if value.is_some() { println!("{frame:03} = {value:?}, ") }; - steps[frame as usize] = value; - } - steps - }; - for chunk in 0..1000 { - let chunk = assign(chunk); - //println!("{chunk} {:#?}", assign(chunk)); - } - } -} + //let buffer_index = self.to_buffer_index(chunk_start + frame, scope, bpm); + //let value = frame_steps[(start_looped + frame as usize) % loop_frames]; + //if let Some(step) = value { + //for track in sequence.iter() { + //for event in track[step].iter() { + //writer.write(&::jack::RawMidi { + //time: frame as u32, + //bytes: &match event { + //SequencerEvent::NoteOn(pitch, velocity) => [ + //0b10010000, + //*pitch, + //*velocity + //], + //SequencerEvent::NoteOff(pitch) => [ + //0b10000000, + //*pitch, + //0b00000000 + //], + //} + //}).unwrap() + //} + //} + //} diff --git a/src/device/sequencer_test.rs b/src/device/sequencer_test.rs new file mode 100644 index 00000000..4a0882af --- /dev/null +++ b/src/device/sequencer_test.rs @@ -0,0 +1,77 @@ +#![cfg(test)] + +#[test] +fn test_midi_frames () { + let beats = 4; + let steps = 16; + let bpm = 120; + let rate = 44100; // Hz + let frame = 1f64 / rate as f64; // msec + let buf = 512; // frames + let t_beat = 60.0 / bpm as f64; // msec + let t_loop = t_beat * beats as f64; // msec + let t_step = t_beat / steps as f64; // msec + + let assign = |chunk: usize| { + let start = chunk * buf; // frames + let end = (chunk + 1) * buf; // frames + println!("{chunk}: {start} .. {end}"); + let mut steps: Vec<(usize, usize, f64)> = vec![]; + for frame_index in start..end { + let frame_msec = frame_index as f64 * frame; + let offset = (frame_msec * 1000.0) % (t_step * 1000.0); + if offset < 0.1 { + let time = frame_index - start; + let step_index = (frame_msec % t_loop / t_step) as usize; + println!("{chunk}: {frame_index} ({time}) -> {step_index} ({frame_msec} % {t_step} = {offset})"); + steps.push((time, step_index, offset)); + } + } + steps + }; + + for chunk in 0..10 { + let chunk = assign(chunk); + //println!("{chunk} {:#?}", assign(chunk)); + } +} + +#[test] +fn test_midi_frames_2 () { + let beats = 4; + let steps = 16; + let bpm = 120; + let rate = 44100; // Hz + let frame = 1f64 / rate as f64; // msec + let buf = 512; // frames + let t_beat = 60.0 / bpm as f64; // msec + let t_loop = t_beat * beats as f64; // msec + let t_step = t_beat / steps as f64; // msec + let mut step_frames = vec![]; + for step in 0..beats*steps { + let step_index = (step as f64 * t_step / frame) as usize; + step_frames.push(step_index); + } + let loop_frames = (t_loop*rate as f64) as usize; + let mut frame_steps: Vec> = vec![None;loop_frames]; + for (index, frame) in step_frames.iter().enumerate() { + println!("{index} {frame}"); + frame_steps[*frame] = Some(index); + } + let assign = |chunk: usize| { + let (start, end) = (chunk * buf, (chunk + 1) * buf); // frames + let (start_looped, end_looped) = (start % loop_frames, end % loop_frames); + println!("{chunk}: {start} .. {end} ({start_looped} .. {end_looped})"); + let mut steps: Vec> = vec![None;buf]; + for frame in 0..buf { + let value = frame_steps[(start_looped + frame) as usize % loop_frames]; + if value.is_some() { println!("{frame:03} = {value:?}, ") }; + steps[frame as usize] = value; + } + steps + }; + for chunk in 0..1000 { + let chunk = assign(chunk); + //println!("{chunk} {:#?}", assign(chunk)); + } +} diff --git a/src/device/transport.rs b/src/device/transport.rs index f5245229..c76f5cfe 100644 --- a/src/device/transport.rs +++ b/src/device/transport.rs @@ -2,21 +2,19 @@ use crate::prelude::*; pub struct Transport { name: String, - transport: Option<::jack::Transport>, - bpm: f64, + transport: ::jack::Transport, timesig: (f32, f32), + bpm: f64, } impl Transport { - pub fn new () - -> Result, Box> - { - //let transport = client.transport(); - Ok(DynamicDevice::new(render, handle, |_|{}, Self { - name: "Transport".into(), - bpm: 113.0, + pub fn new (name: &str) -> Result, Box> { + let (client, _) = Client::new(name, ClientOptions::NO_START_SERVER)?; + Ok(DynamicDevice::new(render, handle, process, Self { + name: name.into(), + transport: client.transport(), timesig: (4.0, 4.0), - transport: None, + bpm: 113.0, })) } @@ -24,7 +22,7 @@ impl Transport { } pub fn play_or_pause (&mut self) -> Result<(), Box> { - match self.transport.as_ref().unwrap().query_state()? { + match self.transport.query_state()? { TransportState::Stopped => self.play(), TransportState::Rolling => self.stop(), _ => Ok(()) @@ -32,14 +30,18 @@ impl Transport { } pub fn play (&mut self) -> Result<(), Box> { - Ok(self.transport.as_ref().unwrap().start()?) + Ok(self.transport.start()?) } pub fn stop (&mut self) -> Result<(), Box> { - Ok(self.transport.as_ref().unwrap().stop()?) + Ok(self.transport.stop()?) } } +pub fn process (state: &mut Transport, client: &Client, scope: &ProcessScope) -> Control { + Control::Continue +} + pub fn render (state: &Transport, buf: &mut Buffer, mut area: Rect) -> Usually { @@ -220,7 +222,7 @@ pub fn render (state: &Transport, buf: &mut Buffer, mut area: Rect) //Ok(()) //} -pub fn handle (state: &mut Transport, event: &EngineEvent) -> Result<(), Box> { +pub fn handle (state: &mut Transport, event: &AppEvent) -> Result<(), Box> { Ok(()) } @@ -231,9 +233,9 @@ pub const ACTIONS: [(&'static str, &'static str);4] = [ ("(Shift-)Space", "⯈ Play/pause"), ]; -struct Notifications; +struct TransportJack; -impl NotificationHandler for Notifications { +impl NotificationHandler for TransportJack { fn thread_init (&self, _: &Client) { } diff --git a/src/render.rs b/src/draw.rs similarity index 100% rename from src/render.rs rename to src/draw.rs diff --git a/src/layout.rs b/src/layout.rs index 997e6a33..2f40ece8 100644 --- a/src/layout.rs +++ b/src/layout.rs @@ -13,19 +13,19 @@ impl Rows { } impl Device for Rows { - fn handle (&mut self, event: &EngineEvent) -> Usually<()> { + fn handle (&mut self, event: &AppEvent) -> Usually<()> { if !self.focused { match event { - EngineEvent::Input(Event::Key(KeyEvent { code: KeyCode::Esc, .. })) => { + AppEvent::Input(Event::Key(KeyEvent { code: KeyCode::Esc, .. })) => { self.focused = true; - self.items[self.focus].handle(&EngineEvent::Blur)?; + self.items[self.focus].handle(&AppEvent::Blur)?; Ok(()) }, _ => self.items[self.focus].handle(event) } } else { match event { - EngineEvent::Input(event) => match event { + AppEvent::Input(event) => match event { Event::Key(KeyEvent { code: KeyCode::Up, .. }) => { if self.focus == 0 { self.focus = self.items.len(); @@ -40,11 +40,11 @@ impl Device for Rows { }, Event::Key(KeyEvent { code: KeyCode::Enter, .. }) => { self.focused = false; - self.items[self.focus].handle(&EngineEvent::Focus)?; + self.items[self.focus].handle(&AppEvent::Focus)?; }, Event::Key(KeyEvent { code: KeyCode::Esc, .. }) => { self.focused = true; - self.items[self.focus].handle(&EngineEvent::Blur)?; + self.items[self.focus].handle(&AppEvent::Blur)?; }, _ => { println!("{event:?}"); @@ -93,7 +93,7 @@ impl Columns { } impl Device for Columns { - fn handle (&mut self, event: &EngineEvent) -> Usually<()> { + fn handle (&mut self, event: &AppEvent) -> Usually<()> { if self.focused { self.items[self.focus].handle(event) } else { diff --git a/src/main.rs b/src/main.rs index 042fd47b..ea6a08d3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,12 +8,13 @@ use std::error::Error; pub mod cli; pub mod device; pub mod prelude; -pub mod render; +pub mod draw; pub mod config; pub mod layout; +pub mod time; -use crate::device::{Chain, Sequencer, Sampler, Plugin, Mixer, Transport}; -use crate::layout::{Rows, Columns}; +use crate::device::{Sequencer, Transport}; +use crate::layout::Rows; fn main () -> Result<(), Box> { let cli = cli::Cli::parse(); @@ -21,7 +22,7 @@ fn main () -> Result<(), Box> { crate::config::create_dirs(&xdg)?; crate::device::run(Rows::new(true, vec![ Box::new(Sequencer::new("Rhythm#000")?), - Box::new(Transport::new()?), + Box::new(Transport::new("Transport0")?), ])) //crate::device::run(Rows::new(true, vec![ //Box::new(Columns::new(false, vec![ diff --git a/src/note.rs b/src/note.rs new file mode 100644 index 00000000..581a51dd --- /dev/null +++ b/src/note.rs @@ -0,0 +1,26 @@ +pub const C_4: u8 = 0; +pub const C_3: u8 = 12; +pub const C_2: u8 = 24; +pub const C_1: u8 = 36; +pub const C0: u8 = 48; +pub const C1: u8 = 60; +pub const C2: u8 = 72; +pub const C3: u8 = 84; +pub const C4: u8 = 96; +pub const C5: u8 = 108; +pub const C6: u8 = 120; + +pub enum Pitch { + C = 0, + Cs, + D, + Ds, + E, + F, + Fs, + G, + Gs, + A, + As, + B +} diff --git a/src/prelude.rs b/src/prelude.rs index 7dec2c96..681c6a81 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,20 +1,42 @@ +pub type Usually = Result>; + +pub use crate::draw::*; + +pub use crate::device::{ + Device, + DynamicDevice, + AppEvent, +}; + +pub use crate::time::*; + +pub use crate::layout::{ + Rows, + Columns +}; + pub use std::error::Error; + pub use std::io::{ stdout, Stdout, Write }; + pub use std::thread::{ spawn, JoinHandle }; + pub use std::time::Duration; + pub use std::sync::{ Arc, Mutex, atomic::{AtomicBool, Ordering}, mpsc::{self, channel, Sender, Receiver} }; + pub use crossterm::{ ExecutableCommand, QueueableCommand, event::{Event, KeyEvent, KeyCode, KeyModifiers}, @@ -25,11 +47,16 @@ pub use crossterm::{ enable_raw_mode, disable_raw_mode }, }; + pub use ratatui::{ - prelude::*, - widgets::WidgetRef, + prelude::{ + Buffer, Rect, Style, Color, CrosstermBackend, Layout, Stylize, Direction, + Line, Constraint + }, + widgets::{Widget, WidgetRef}, //style::Stylize, }; + pub use jack::{ AsyncClient, AudioIn, @@ -51,12 +78,7 @@ pub use jack::{ TransportState, TransportStatePosition }; + pub type BoxedProcessHandler = Box Control + Send>; + pub type Jack = AsyncClient>; -pub use crate::render::*; -pub use crate::device::{ - Device, - DynamicDevice, - EngineEvent, -}; -pub type Usually = Result>; diff --git a/src/time.rs b/src/time.rs new file mode 100644 index 00000000..59498b3c --- /dev/null +++ b/src/time.rs @@ -0,0 +1,90 @@ +/// Number of data frames in a second. +pub struct Hz(pub u32); + +/// One data frame. +pub struct Frame(pub u32); + +/// One microsecond. +pub struct Usec(pub u32); + +/// Beats per minute as `120000` = 120.000BPM +pub struct Tempo(pub u32); + +/// Time signature: N/Mths per bar. +pub struct Signature(pub u32, pub u32); + +/// NoteDuration in musical terms. Has definite usec value +/// for given bpm and sample rate. +pub enum NoteDuration { + Nth(u16, u16), + Dotted(Box), + Tuplet(u16, Box), +} + +impl Frame { + #[inline] + pub fn to_usec (&self, rate: &Hz) -> Usec { + Usec((self.0 * 1000) / rate.0) + } +} + +impl Usec { + #[inline] + pub fn to_frame (&self, rate: &Hz) -> Frame { + Frame((self.0 * rate.0) / 1000) + } +} + +impl Tempo { + #[inline] + pub fn to_usec_per_beat (&self) -> Usec { + Usec((60_000_000_000 / self.0 as usize) as u32) + } + #[inline] + pub fn to_usec_per_quarter (&self) -> Usec { + Usec(self.to_usec_per_quarter().0 / 4) + } + #[inline] + pub fn to_usec_per_tick (&self, ppq: u32) -> Usec { + Usec(self.to_usec_per_quarter().0 / ppq) + } + #[inline] + pub fn quantize (&self, step: &NoteDuration, time: Usec) -> Usec { + let step = step.to_usec(self); + let t = time.0 / step.0; + Usec(step.0 * t) + } + #[inline] + pub fn quantize_into (&self, step: &NoteDuration, events: U) + -> Vec<(Usec, T)> + where U: std::iter::Iterator + Sized + { + events + .map(|(time, event)|(self.quantize(step, time), event)) + .collect() + } +} + +impl NoteDuration { + fn to_usec (&self, bpm: &Tempo) -> Usec { + Usec(match self { + Self::Nth(time, flies) => + bpm.to_usec_per_beat().0 * *time as u32 / *flies as u32, + Self::Dotted(duration) => + duration.to_usec(bpm).0 * 3 / 2, + Self::Tuplet(n, duration) => + duration.to_usec(bpm).0 * 2 / *n as u32, + }) + } + fn to_frame (&self, bpm: &Tempo, rate: &Hz) -> Frame { + self.to_usec(bpm).to_frame(rate) + } +} + +struct Loop { + repeat: bool, + pre_start: T, + start: T, + end: T, + post_end: T, +}