mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
use iterator in sampler
This commit is contained in:
parent
81717f17b8
commit
c5369328f4
9 changed files with 175 additions and 120 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1 +1,2 @@
|
||||||
target
|
target
|
||||||
|
perf.data*
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
|
|
||||||
89
src/jack.rs
89
src/jack.rs
|
|
@ -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)
|
||||||
|
)?)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
60
src/model.rs
60
src/model.rs
|
|
@ -73,32 +73,9 @@ pub struct App {
|
||||||
pub quant: usize,
|
pub quant: usize,
|
||||||
}
|
}
|
||||||
process!(App |self, _client, scope| {
|
process!(App |self, _client, scope| {
|
||||||
self.chunk_size = scope.n_frames() as usize;
|
let (
|
||||||
let CycleTimes {
|
reset, current_frames, current_usecs, next_usecs, period_usecs
|
||||||
current_frames,
|
) = self.process_update_time(&scope);
|
||||||
current_usecs,
|
|
||||||
next_usecs,
|
|
||||||
period_usecs
|
|
||||||
} = scope.cycle_times().unwrap();
|
|
||||||
let transport = self.transport.as_ref().unwrap().query().unwrap();
|
|
||||||
self.playhead = transport.pos.frame() as usize;
|
|
||||||
let mut reset = false;
|
|
||||||
if self.playing != Some(transport.state) {
|
|
||||||
match transport.state {
|
|
||||||
TransportState::Rolling => {
|
|
||||||
self.play_started = Some((
|
|
||||||
current_frames as usize,
|
|
||||||
current_usecs as usize,
|
|
||||||
));
|
|
||||||
},
|
|
||||||
TransportState::Stopped => {
|
|
||||||
self.play_started = None;
|
|
||||||
reset = true;
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.playing = Some(transport.state);
|
|
||||||
for track in self.tracks.iter_mut() {
|
for track in self.tracks.iter_mut() {
|
||||||
track.process(
|
track.process(
|
||||||
self.midi_in.as_ref().unwrap().iter(scope),
|
self.midi_in.as_ref().unwrap().iter(scope),
|
||||||
|
|
@ -116,6 +93,37 @@ process!(App |self, _client, scope| {
|
||||||
Control::Continue
|
Control::Continue
|
||||||
});
|
});
|
||||||
impl App {
|
impl App {
|
||||||
|
pub fn process_update_time (&mut self, scope: &ProcessScope) -> (bool, usize, usize, usize, f64) {
|
||||||
|
self.chunk_size = scope.n_frames() as usize;
|
||||||
|
let CycleTimes {
|
||||||
|
current_frames,
|
||||||
|
current_usecs,
|
||||||
|
next_usecs,
|
||||||
|
period_usecs
|
||||||
|
} = scope.cycle_times().unwrap();
|
||||||
|
let transport = self.transport.as_ref().unwrap().query().unwrap();
|
||||||
|
self.playhead = transport.pos.frame() as usize;
|
||||||
|
let mut reset = false;
|
||||||
|
if self.playing != Some(transport.state) {
|
||||||
|
match transport.state {
|
||||||
|
TransportState::Rolling => {
|
||||||
|
self.play_started = Some((
|
||||||
|
current_frames as usize,
|
||||||
|
current_usecs as usize,
|
||||||
|
));
|
||||||
|
},
|
||||||
|
TransportState::Stopped => {
|
||||||
|
self.play_started = None;
|
||||||
|
reset = true;
|
||||||
|
},
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.playing = Some(transport.state);
|
||||||
|
(
|
||||||
|
reset, current_frames as usize, current_usecs as usize, next_usecs as usize, period_usecs as f64
|
||||||
|
)
|
||||||
|
}
|
||||||
pub fn client (&self) -> &Client {
|
pub fn client (&self) -> &Client {
|
||||||
self.jack.as_ref().unwrap().as_client()
|
self.jack.as_ref().unwrap().as_client()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
true
|
|
||||||
} else {
|
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue