use iterator in sampler

This commit is contained in:
🪞👃🪞 2024-07-06 17:39:16 +03:00
parent 81717f17b8
commit c5369328f4
9 changed files with 175 additions and 120 deletions

1
.gitignore vendored
View file

@ -1 +1,2 @@
target target
perf.data*

View file

@ -111,7 +111,7 @@ pub fn main_thread (
let exited = exited.clone(); let exited = exited.clone();
let device = device.clone(); let device = device.clone();
let mut terminal = ratatui::Terminal::new(CrosstermBackend::new(stdout()))?; let mut terminal = ratatui::Terminal::new(CrosstermBackend::new(stdout()))?;
let sleep = Duration::from_millis(16); let sleep = Duration::from_millis(20);
Ok(spawn(move || loop { Ok(spawn(move || loop {
if let Ok(device) = device.try_read() { if let Ok(device) = device.try_read() {

View file

@ -1,18 +1,6 @@
use crate::core::*; use crate::core::*;
submod!( device event factory ports process ); submod!( device event factory ports );
pub type DynamicAsyncClient =
AsyncClient<DynamicNotifications, DynamicProcessHandler>;
pub type DynamicNotifications =
Notifications<Box<dyn Fn(AppEvent) + Send + Sync>>;
pub type DynamicProcessHandler =
ClosureProcessHandler<BoxedProcessHandler>;
pub type BoxedProcessHandler =
Box<dyn FnMut(&Client, &ProcessScope)-> Control + Send>;
pub use ::_jack::{ pub use ::_jack::{
AsyncClient, AsyncClient,
@ -42,6 +30,43 @@ pub use ::_jack::{
Unowned Unowned
}; };
pub type DynamicAsyncClient =
AsyncClient<DynamicNotifications, DynamicProcessHandler>;
type DynamicProcessHandler =
ClosureProcessHandler<BoxedProcessHandler>;
pub type BoxedProcessHandler =
Box<dyn FnMut(&Client, &ProcessScope)-> Control + Send>;
/// Trait for things that have a JACK process callback.
pub trait Process {
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
Control::Continue
}
}
/// Define the JACK process callback associated with a struct.
#[macro_export] macro_rules! process {
($T:ty) => {
impl Process for $T {}
};
($T:ty |$self:ident, $c:ident, $s:ident|$block:tt) => {
impl Process for $T {
fn process (&mut $self, $c: &Client, $s: &ProcessScope) -> Control {
$block
}
}
};
($T:ty = $process:path) => {
impl Process for $T {
fn process (&mut self, c: &Client, s: &ProcessScope) -> Control {
$process(self, c, s)
}
}
}
}
/// Just run thing with JACK. Returns the activated client. /// Just run thing with JACK. Returns the activated client.
pub fn jack_run <T: Sync> (name: &str, app: &Arc<RwLock<T>>) -> Usually<DynamicAsyncClient> pub fn jack_run <T: Sync> (name: &str, app: &Arc<RwLock<T>>) -> Usually<DynamicAsyncClient>
where T: Handle + Process + Send + 'static where T: Handle + Process + Send + 'static
@ -65,3 +90,41 @@ pub fn jack_run <T: Sync> (name: &str, app: &Arc<RwLock<T>>) -> Usually<DynamicA
}) as BoxedProcessHandler) }) as BoxedProcessHandler)
)?) )?)
} }
pub trait ProcessSplit {
fn process_in (&mut self, _: &Client, _: &ProcessScope) -> Usually<()>;
fn process_out (&self, _: &Client, _: &ProcessScope) -> Usually<()>;
}
impl<T: ProcessSplit> Process for T {
fn process (&mut self, c: &Client, s: &ProcessScope) -> Control {
self.process_in(c, s).unwrap();
self.process_out(c, s).unwrap();
Control::Continue
}
}
/// Just run thing with JACK. Returns the activated client.
pub fn jack_run_split <T: Sync> (name: &str, app: &Arc<RwLock<T>>) -> Usually<DynamicAsyncClient>
where T: Handle + ProcessSplit + Send + 'static
{
let options = ClientOptions::NO_START_SERVER;
let (client, _status) = Client::new(name, options)?;
Ok(client.activate_async(
Notifications(Box::new({
let _app = app.clone();
move|_event|{
// FIXME: this deadlocks
//app.lock().unwrap().handle(&event).unwrap();
}
}) as Box<dyn Fn(AppEvent) + Send + Sync>),
ClosureProcessHandler::new(Box::new({
let app = app.clone();
move|c: &Client, s: &ProcessScope|{
app.write().unwrap().process_in(c, s).unwrap();
app.read().unwrap().process_out(c, s).unwrap();
Control::Continue
}
}) as BoxedProcessHandler)
)?)
}

View file

@ -1,5 +1,8 @@
use super::*; use super::*;
pub type DynamicNotifications =
Notifications<Box<dyn Fn(AppEvent) + Send + Sync>>;
#[derive(Debug)] #[derive(Debug)]
pub enum JackEvent { pub enum JackEvent {
ThreadInit, ThreadInit,

View file

@ -1,30 +0,0 @@
use super::*;
/// Trait for things that have a JACK process callback.
pub trait Process {
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
Control::Continue
}
}
/// Define the JACK process callback associated with a struct.
#[macro_export] macro_rules! process {
($T:ty) => {
impl Process for $T {}
};
($T:ty |$self:ident, $c:ident, $s:ident|$block:tt) => {
impl Process for $T {
fn process (&mut $self, $c: &Client, $s: &ProcessScope) -> Control {
$block
}
}
};
($T:ty = $process:path) => {
impl Process for $T {
fn process (&mut self, c: &Client, s: &ProcessScope) -> Control {
$process(self, c, s)
}
}
}
}

View file

@ -73,6 +73,27 @@ pub struct App {
pub quant: usize, pub quant: usize,
} }
process!(App |self, _client, scope| { process!(App |self, _client, scope| {
let (
reset, current_frames, current_usecs, next_usecs, period_usecs
) = self.process_update_time(&scope);
for track in self.tracks.iter_mut() {
track.process(
self.midi_in.as_ref().unwrap().iter(scope),
&self.timebase,
self.playing,
self.play_started,
self.quant,
reset,
&scope,
(current_frames as usize, self.chunk_size),
(current_usecs as usize, next_usecs.saturating_sub(current_usecs) as usize),
period_usecs as f64
);
}
Control::Continue
});
impl App {
pub fn process_update_time (&mut self, scope: &ProcessScope) -> (bool, usize, usize, usize, f64) {
self.chunk_size = scope.n_frames() as usize; self.chunk_size = scope.n_frames() as usize;
let CycleTimes { let CycleTimes {
current_frames, current_frames,
@ -99,23 +120,10 @@ process!(App |self, _client, scope| {
} }
} }
self.playing = Some(transport.state); self.playing = Some(transport.state);
for track in self.tracks.iter_mut() { (
track.process( reset, current_frames as usize, current_usecs as usize, next_usecs as usize, period_usecs as f64
self.midi_in.as_ref().unwrap().iter(scope), )
&self.timebase,
self.playing,
self.play_started,
self.quant,
reset,
&scope,
(current_frames as usize, self.chunk_size),
(current_usecs as usize, next_usecs.saturating_sub(current_usecs) as usize),
period_usecs as f64
);
} }
Control::Continue
});
impl App {
pub fn client (&self) -> &Client { pub fn client (&self) -> &Client {
self.jack.as_ref().unwrap().as_client() self.jack.as_ref().unwrap().as_client()
} }

View file

@ -55,22 +55,23 @@ impl Phrase {
timebase: &Arc<Timebase>, timebase: &Arc<Timebase>,
(frame0, frames, _): (usize, usize, f64), (frame0, frames, _): (usize, usize, f64),
) { ) {
let mut buf = Vec::with_capacity(8);
for (time, tick) in Ticks(timebase.pulse_per_frame()).between_frames( for (time, tick) in Ticks(timebase.pulse_per_frame()).between_frames(
frame0, frame0 + frames frame0, frame0 + frames
) { ) {
let tick = tick % self.length; let tick = tick % self.length;
if let Some(events) = self.notes.get(&(tick as usize)) { if let Some(events) = self.notes.get(&(tick as usize)) {
for message in events.iter() { for message in events.iter() {
let mut buf = vec![]; buf.clear();
let channel = 0.into(); let channel = 0.into();
let message = *message; let message = *message;
LiveEvent::Midi { channel, message }.write(&mut buf).unwrap();
output[time as usize].push(buf.clone());
match message { match message {
MidiMessage::NoteOn { key, .. } => notes_on[key.as_int() as usize] = true, MidiMessage::NoteOn { key, .. } => notes_on[key.as_int() as usize] = true,
MidiMessage::NoteOff { key, .. } => notes_on[key.as_int() as usize] = false, MidiMessage::NoteOff { key, .. } => notes_on[key.as_int() as usize] = false,
_ => {} _ => {}
} }
LiveEvent::Midi { channel, message }.write(&mut buf).unwrap();
output[time as usize].push(buf);
} }
} }
} }

View file

@ -6,6 +6,8 @@ pub struct Sampler {
pub samples: BTreeMap<u7, Arc<Sample>>, pub samples: BTreeMap<u7, Arc<Sample>>,
pub voices: Vec<Voice>, pub voices: Vec<Voice>,
pub ports: JackPorts, pub ports: JackPorts,
pub buffer: Vec<Vec<f32>>,
pub output_gain: f32
} }
render!(Sampler = crate::view::sampler::render); render!(Sampler = crate::view::sampler::render);
@ -48,15 +50,21 @@ impl Sampler {
cursor: (0, 0), cursor: (0, 0),
samples: samples.unwrap_or(BTreeMap::new()), samples: samples.unwrap_or(BTreeMap::new()),
voices: vec![], voices: vec![],
ports ports,
buffer: vec![vec![0.0;16384];2],
output_gain: 0.5,
})) }))
} }
pub fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { pub fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control {
// Output buffer: this will be copied to the audio outs. self.process_midi_in(scope);
let channel_count = self.ports.audio_outs.len(); self.clear_output_buffer();
let mut mixed = vec![vec![0.0;scope.n_frames() as usize];channel_count]; self.process_audio_out(scope);
// Process MIDI input to add new voices. self.write_output_buffer(scope);
Control::Continue
}
fn process_midi_in (&mut self, scope: &ProcessScope) {
for RawMidi { time, bytes } in self.ports.midi_ins.get("midi").unwrap().iter(scope) { for RawMidi { time, bytes } in self.ports.midi_ins.get("midi").unwrap().iter(scope) {
if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() { if let LiveEvent::Midi { message, .. } = LiveEvent::parse(bytes).unwrap() {
if let MidiMessage::NoteOn { ref key, .. } = message { if let MidiMessage::NoteOn { ref key, .. } = message {
@ -66,33 +74,39 @@ impl Sampler {
} }
} }
} }
// Emit next chunk of each currently playing voice, }
// dropping voices that have reached their ends.
let gain = 0.5; fn clear_output_buffer (&mut self) {
for buffer in self.buffer.iter_mut() {
buffer.fill(0.0);
}
}
fn process_audio_out (&mut self, scope: &ProcessScope) {
let channel_count = self.buffer.len();
self.voices.retain_mut(|voice|{ self.voices.retain_mut(|voice|{
if let Some(chunk) = voice.chunk(scope.n_frames() as usize) { for index in 0..scope.n_frames() as usize {
for (i, channel) in chunk.iter().enumerate() { if let Some(frame) = voice.next() {
let buffer = &mut mixed[i % channel_count]; for (channel, sample) in frame.iter().enumerate() {
for (i, sample) in channel.iter().enumerate() { self.buffer[channel % channel_count][index] += sample * self.output_gain;
buffer[i] += sample * gain;
} }
}
true
} else { } else {
false return false
} }
}
return true
}); });
// Write output buffer to output ports. }
/// Write output buffer to output ports.
fn write_output_buffer (&mut self, scope: &ProcessScope) {
for (i, port) in self.ports.audio_outs.values_mut().enumerate() { for (i, port) in self.ports.audio_outs.values_mut().enumerate() {
let buffer = &mixed[i]; let buffer = &self.buffer[i];
for (i, value) in port.as_mut_slice(scope).iter_mut().enumerate() { for (i, value) in port.as_mut_slice(scope).iter_mut().enumerate() {
*value = *buffer.get(i).unwrap_or(&0.0); *value = *buffer.get(i).unwrap_or(&0.0);
} }
} }
Control::Continue
} }
fn load_sample (&mut self, _path: &str) {}
} }
#[macro_export] macro_rules! sample { #[macro_export] macro_rules! sample {
@ -126,7 +140,11 @@ impl Sample {
Arc::new(Self { name: name.to_string(), start, end, channels }) Arc::new(Self { name: name.to_string(), start, end, channels })
} }
pub fn play (self: &Arc<Self>, after: usize) -> Voice { pub fn play (self: &Arc<Self>, after: usize) -> Voice {
Voice { sample: self.clone(), after, position: self.start } Voice {
sample: self.clone(),
after,
position: self.start
}
} }
} }
@ -136,32 +154,23 @@ pub struct Voice {
pub position: usize, pub position: usize,
} }
impl Voice { const BUFFER: [f32;64] = [0.0f32;64];
pub fn chunk (&mut self, mut frames: usize) -> Option<Vec<Vec<f32>>> {
// Create output buffer for each channel impl Iterator for Voice {
let mut chunk = vec![vec![];self.sample.channels.len()]; type Item = [f32;2];
// If it's not time to play yet, count down fn next (&mut self) -> Option<Self::Item> {
if self.after >= frames { if self.after > 0 {
self.after = self.after - frames; self.after = self.after - 1;
return Some(chunk) return Some([0.0, 0.0])
}
// If the voice will start playing within the current buffer,
// subtract the remaining number of wait frames.
if self.after > 0 && self.after < frames {
chunk = vec![vec![0.0;self.after];self.sample.channels.len()];
frames = frames - self.after;
self.after = 0;
} }
if self.position < self.sample.end { if self.position < self.sample.end {
let start = self.position.min(self.sample.end); let position = self.position;
let end = (self.position + frames).min(self.sample.end); self.position = self.position + 1;
for (i, channel) in self.sample.channels.iter().enumerate() { return Some([
chunk[i].extend_from_slice(&channel[start..end]); self.sample.channels[0][position],
}; self.sample.channels[0][position],
self.position = self.position + frames; ])
Some(chunk) }
} else {
None None
} }
} }
}

View file

@ -45,7 +45,7 @@ impl Track {
Ok(Self { Ok(Self {
name: name.to_string(), name: name.to_string(),
midi_out: jack.register_port(name, MidiOut)?, midi_out: jack.register_port(name, MidiOut)?,
midi_out_buf: vec![vec![];1024], midi_out_buf: vec![vec![];16384],
notes_on: vec![false;128], notes_on: vec![false;128],
monitoring: false, monitoring: false,
recording: false, recording: false,