add audio! macro

This commit is contained in:
🪞👃🪞 2024-12-14 23:32:07 +01:00
parent 32eb1bf085
commit 9f97c44c84
8 changed files with 139 additions and 162 deletions

View file

@ -20,8 +20,4 @@ impl From<&Arc<RwLock<Mixer>>> for MixerAudio {
} }
} }
impl Audio for MixerAudio { audio!(|self: MixerAudio, _, _|Control::Continue);
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
Control::Continue
}
}

View file

@ -62,53 +62,51 @@ impl From<&Arc<RwLock<Plugin>>> for PluginAudio {
} }
} }
impl Audio for PluginAudio { audio!(|self: PluginAudio, _, _|{
fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { let state = &mut*self.0.write().unwrap();
let state = &mut*self.0.write().unwrap(); match state.plugin.as_mut() {
match state.plugin.as_mut() { Some(PluginKind::LV2(LV2Plugin {
Some(PluginKind::LV2(LV2Plugin { features,
features, ref mut instance,
ref mut instance, ref mut input_buffer,
ref mut input_buffer, ..
.. })) => {
})) => { let urid = features.midi_urid();
let urid = features.midi_urid(); input_buffer.clear();
input_buffer.clear(); for port in state.midi_ins.iter() {
for port in state.midi_ins.iter() { let mut atom = ::livi::event::LV2AtomSequence::new(
let mut atom = ::livi::event::LV2AtomSequence::new( &features,
&features, scope.n_frames() as usize
scope.n_frames() as usize );
); for event in port.iter(scope) {
for event in port.iter(scope) { match event.bytes.len() {
match event.bytes.len() { 3 => atom.push_midi_event::<3>(
3 => atom.push_midi_event::<3>( event.time as i64,
event.time as i64, urid,
urid, &event.bytes[0..3]
&event.bytes[0..3] ).unwrap(),
).unwrap(), _ => {}
_ => {}
}
} }
input_buffer.push(atom);
} }
let mut outputs = vec![]; input_buffer.push(atom);
for _ in state.midi_outs.iter() { }
outputs.push(::livi::event::LV2AtomSequence::new( let mut outputs = vec![];
&features, for _ in state.midi_outs.iter() {
scope.n_frames() as usize outputs.push(::livi::event::LV2AtomSequence::new(
)); &features,
} scope.n_frames() as usize
let ports = ::livi::EmptyPortConnections::new() ));
.with_atom_sequence_inputs(input_buffer.iter()) }
.with_atom_sequence_outputs(outputs.iter_mut()) let ports = ::livi::EmptyPortConnections::new()
.with_audio_inputs(state.audio_ins.iter().map(|o|o.as_slice(scope))) .with_atom_sequence_inputs(input_buffer.iter())
.with_audio_outputs(state.audio_outs.iter_mut().map(|o|o.as_mut_slice(scope))); .with_atom_sequence_outputs(outputs.iter_mut())
unsafe { .with_audio_inputs(state.audio_ins.iter().map(|o|o.as_slice(scope)))
instance.run(scope.n_frames() as usize, ports).unwrap() .with_audio_outputs(state.audio_outs.iter_mut().map(|o|o.as_mut_slice(scope)));
}; unsafe {
}, instance.run(scope.n_frames() as usize, ports).unwrap()
_ => {} };
} },
Control::Continue _ => {}
} }
} Control::Continue
});

View file

@ -1,5 +1,29 @@
use crate::*; use crate::*;
/// Trait for things that have a JACK process callback.
pub trait Audio: Send + Sync {
fn process (&mut self, _: &Client, _: &ProcessScope) -> Control {
Control::Continue
}
fn callback (
state: &Arc<RwLock<Self>>, client: &Client, scope: &ProcessScope
) -> Control where Self: Sized {
if let Ok(mut state) = state.write() {
state.process(client, scope)
} else {
Control::Quit
}
}
}
#[macro_export] macro_rules! audio {
(|$self:ident:$Struct:ident$(<$($L:lifetime),*$($T:ident$(:$U:path)?),*>)?,$c:ident,$s:ident|$cb:expr) => {
impl $(<$($L),*$($T $(: $U)?),*>)? Audio for $Struct $(<$($L),*$($T),*>)? {
#[inline] fn process (&mut $self, $c: &Client, $s: &ProcessScope) -> Control { $cb }
}
}
}
pub trait HasMidiIns { pub trait HasMidiIns {
fn midi_ins (&self) -> &Vec<Port<MidiIn>>; fn midi_ins (&self) -> &Vec<Port<MidiIn>>;
fn midi_ins_mut (&mut self) -> &mut Vec<Port<MidiIn>>; fn midi_ins_mut (&mut self) -> &mut Vec<Port<MidiIn>>;
@ -54,22 +78,6 @@ impl JackActivate for JackClient {
} }
} }
/// Trait for things that have a JACK process callback.
pub trait Audio: Send + Sync {
fn process(&mut self, _: &Client, _: &ProcessScope) -> Control {
Control::Continue
}
fn callback(
state: &Arc<RwLock<Self>>, client: &Client, scope: &ProcessScope
) -> Control where Self: Sized {
if let Ok(mut state) = state.write() {
state.process(client, scope)
} else {
Control::Quit
}
}
}
/// A UI component that may be associated with a JACK client by the `Jack` factory. /// A UI component that may be associated with a JACK client by the `Jack` factory.
pub trait AudioComponent<E: Engine>: Component<E> + Audio { pub trait AudioComponent<E: Engine>: Component<E> + Audio {
/// Perform type erasure for collecting heterogeneous devices. /// Perform type erasure for collecting heterogeneous devices.

View file

@ -96,15 +96,13 @@ impl From<&Arc<RwLock<Sampler>>> for SamplerAudio {
} }
} }
impl Audio for SamplerAudio { audio!(|self: SamplerAudio, _client, scope|{
#[inline] fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { self.process_midi_in(scope);
self.process_midi_in(scope); self.clear_output_buffer();
self.clear_output_buffer(); self.process_audio_out(scope);
self.process_audio_out(scope); self.write_output_buffer(scope);
self.write_output_buffer(scope); Control::Continue
Control::Continue });
}
}
impl SamplerAudio { impl SamplerAudio {

View file

@ -323,53 +323,11 @@ impl TransportControl<ArrangerFocus> for ArrangerTui {
} }
} }
} }
has_clock!(|self:ArrangerTui|&self.clock);
impl Audio for ArrangerTui { has_clock!(|self:ArrangerTrack|self.player.clock());
#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control { has_phrases!(|self:ArrangerTui|self.phrases.phrases);
// Start profiling cycle has_editor!(|self:ArrangerTui|self.editor);
let t0 = self.perf.get_t0(); has_player!(|self:ArrangerTrack|self.player);
// Update transport clock
if ClockAudio(self).process(client, scope) == Control::Quit {
return Control::Quit
}
// Update MIDI sequencers
if TracksAudio(
&mut self.tracks,
&mut self.note_buf,
&mut self.midi_buf,
Default::default(),
).process(client, scope) == Control::Quit {
return Control::Quit
}
// FIXME: one of these per playing track
//self.now.set(0.);
//if let ArrangerSelection::Clip(t, s) = self.selected {
//let phrase = self.scenes().get(s).map(|scene|scene.clips.get(t));
//if let Some(Some(Some(phrase))) = phrase {
//if let Some(track) = self.tracks().get(t) {
//if let Some((ref started_at, Some(ref playing))) = track.player.play_phrase {
//let phrase = phrase.read().unwrap();
//if *playing.read().unwrap() == *phrase {
//let pulse = self.current().pulse.get();
//let start = started_at.pulse.get();
//let now = (pulse - start) % phrase.length as f64;
//self.now.set(now);
//}
//}
//}
//}
//}
// End profiling cycle
self.perf.update(t0, scope);
return Control::Continue
}
}
// Layout for standalone arranger app. // Layout for standalone arranger app.
render!(|self: ArrangerTui|{ render!(|self: ArrangerTui|{
let arranger_focused = self.arranger_focused(); let arranger_focused = self.arranger_focused();
@ -411,12 +369,43 @@ render!(|self: ArrangerTui|{
]) ])
]) ])
}); });
audio!(|self: ArrangerTui, client, scope|{
has_clock!(|self:ArrangerTui|&self.clock); // Start profiling cycle
has_clock!(|self:ArrangerTrack|self.player.clock()); let t0 = self.perf.get_t0();
has_phrases!(|self:ArrangerTui|self.phrases.phrases); // Update transport clock
has_editor!(|self:ArrangerTui|self.editor); if ClockAudio(self).process(client, scope) == Control::Quit {
has_player!(|self:ArrangerTrack|self.player); return Control::Quit
}
// Update MIDI sequencers
let tracks = &mut self.tracks;
let note_buf = &mut self.note_buf;
let midi_buf = &mut self.midi_buf;
if TracksAudio(tracks, note_buf, midi_buf, Default::default())
.process(client, scope) == Control::Quit {
return Control::Quit
}
// FIXME: one of these per playing track
//self.now.set(0.);
//if let ArrangerSelection::Clip(t, s) = self.selected {
//let phrase = self.scenes().get(s).map(|scene|scene.clips.get(t));
//if let Some(Some(Some(phrase))) = phrase {
//if let Some(track) = self.tracks().get(t) {
//if let Some((ref started_at, Some(ref playing))) = track.player.play_phrase {
//let phrase = phrase.read().unwrap();
//if *playing.read().unwrap() == *phrase {
//let pulse = self.current().pulse.get();
//let start = started_at.pulse.get();
//let now = (pulse - start) % phrase.length as f64;
//self.now.set(now);
//}
//}
//}
//}
//}
// End profiling cycle
self.perf.update(t0, scope);
return Control::Continue
});
impl HasPhraseList for ArrangerTui { impl HasPhraseList for ArrangerTui {
fn phrases_focused (&self) -> bool { fn phrases_focused (&self) -> bool {

View file

@ -50,6 +50,7 @@ pub enum SamplerFocus {
_TODO _TODO
} }
audio!(|self: SamplerTui, _client, _scope|Control::Continue);
render!(|self: SamplerTui|render(|to|{ render!(|self: SamplerTui|render(|to|{
let [x, y, _, height] = to.area(); let [x, y, _, height] = to.area();
let style = Style::default().gray(); let style = Style::default().gray();
@ -93,11 +94,6 @@ impl SamplerTui {
None None
} }
} }
impl Audio for SamplerTui {
#[inline] fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
Control::Continue
}
}
pub struct AddSampleModal { pub struct AddSampleModal {
exited: bool, exited: bool,

View file

@ -61,26 +61,6 @@ pub enum SequencerFocus {
PhraseEditor, PhraseEditor,
} }
impl Audio for SequencerTui {
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
// Start profiling cycle
let t0 = self.perf.get_t0();
// Update transport clock
if Control::Quit == ClockAudio(self).process(client, scope) {
return Control::Quit
}
// Update MIDI sequencer
if Control::Quit == PlayerAudio(
&mut self.player, &mut self.note_buf, &mut self.midi_buf
).process(client, scope) {
return Control::Quit
}
// End profiling cycle
self.perf.update(t0, scope);
Control::Continue
}
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum SequencerCommand { pub enum SequencerCommand {
Focus(FocusCommand<SequencerFocus>), Focus(FocusCommand<SequencerFocus>),
@ -175,6 +155,24 @@ pub fn to_sequencer_command (state: &SequencerTui, input: &TuiInput) -> Option<S
}) })
} }
audio!(|self:SequencerTui,client,scope|{
// Start profiling cycle
let t0 = self.perf.get_t0();
// Update transport clock
if Control::Quit == ClockAudio(self).process(client, scope) {
return Control::Quit
}
// Update MIDI sequencer
if Control::Quit == PlayerAudio(
&mut self.player, &mut self.note_buf, &mut self.midi_buf
).process(client, scope) {
return Control::Quit
}
// End profiling cycle
self.perf.update(t0, scope);
Control::Continue
});
render!(|self: SequencerTui|lay!([self.size, Tui::split_up(false, 1, render!(|self: SequencerTui|lay!([self.size, Tui::split_up(false, 1,
Tui::fill_xy(SequencerStatusBar::from(self)), Tui::fill_xy(SequencerStatusBar::from(self)),
Tui::split_right(false, if self.size.w() > 60 { Tui::split_right(false, if self.size.w() > 60 {

View file

@ -38,13 +38,7 @@ impl std::fmt::Debug for TransportTui {
} }
has_clock!(|self:TransportTui|&self.clock); has_clock!(|self:TransportTui|&self.clock);
audio!(|self:TransportTui,client,scope|ClockAudio(self).process(client, scope));
impl Audio for TransportTui {
fn process (&mut self, client: &Client, scope: &ProcessScope) -> Control {
ClockAudio(self).process(client, scope)
}
}
render!(|self: TransportTui|TransportView::from((self, None, true))); render!(|self: TransportTui|TransportView::from((self, None, true)));
pub struct TransportView { pub struct TransportView {