use crate::audio::processor::SynthProcessor;
use crate::params::AudioEvent;
pub const SEQUENCER_EVENT_CAPACITY: usize = 256;
pub trait Sequencer {
fn tick(&mut self, sample_rate: f32, out: &mut Vec<AudioEvent>) -> usize;
fn is_finished(&self) -> bool {
false
}
}
impl Sequencer for () {
fn tick(&mut self, _sample_rate: f32, _out: &mut Vec<AudioEvent>) -> usize {
usize::MAX
}
}
impl<S: Sequencer + ?Sized> Sequencer for &mut S {
fn tick(&mut self, sample_rate: f32, out: &mut Vec<AudioEvent>) -> usize {
(**self).tick(sample_rate, out)
}
fn is_finished(&self) -> bool {
(**self).is_finished()
}
}
impl<S: Sequencer + ?Sized> Sequencer for Box<S> {
fn tick(&mut self, sample_rate: f32, out: &mut Vec<AudioEvent>) -> usize {
(**self).tick(sample_rate, out)
}
fn is_finished(&self) -> bool {
(**self).is_finished()
}
}
pub struct SequencedProcessor<const N: usize, S> {
processor: SynthProcessor<N>,
sequencer: S,
until_tick: usize,
pending: Vec<AudioEvent>,
}
impl<const N: usize, S> SequencedProcessor<N, S> {
#[must_use]
pub fn new(processor: SynthProcessor<N>, sequencer: S) -> Self {
Self {
processor,
sequencer,
until_tick: 0,
pending: Vec::with_capacity(SEQUENCER_EVENT_CAPACITY),
}
}
#[must_use]
pub fn processor(&self) -> &SynthProcessor<N> {
&self.processor
}
pub fn processor_mut(&mut self) -> &mut SynthProcessor<N> {
&mut self.processor
}
#[must_use]
pub fn sequencer(&self) -> &S {
&self.sequencer
}
pub fn sequencer_mut(&mut self) -> &mut S {
&mut self.sequencer
}
pub fn replace_sequencer(&mut self, sequencer: S) -> S {
self.until_tick = 0;
core::mem::replace(&mut self.sequencer, sequencer)
}
#[must_use]
pub fn frames_until_tick(&self) -> usize {
self.until_tick
}
#[must_use]
pub fn into_parts(self) -> (SynthProcessor<N>, S) {
(self.processor, self.sequencer)
}
}
impl<const N: usize, S: Sequencer> SequencedProcessor<N, S> {
pub fn process_block(&mut self, events: &[AudioEvent], buf: &mut [f32], hw_channels: usize) {
assert!(hw_channels > 0, "hw_channels must be > 0");
assert_eq!(
buf.len() % hw_channels,
0,
"buf.len() must be a multiple of hw_channels"
);
self.processor.apply_events(events);
let sample_rate = self.processor.sample_rate();
let mut rest = buf;
while !rest.is_empty() {
if self.until_tick == 0 {
self.pending.clear();
self.until_tick = self.sequencer.tick(sample_rate, &mut self.pending).max(1);
self.processor.apply_events(&self.pending);
}
let n = self.until_tick.min(rest.len() / hw_channels);
let (slice, tail) = rest.split_at_mut(n * hw_channels);
self.processor.process_block(&[], slice, hw_channels);
self.until_tick -= n;
rest = tail;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::params::{ChannelNo, DrumHit, MidiNote};
use crate::presets::sid::default_patches;
const SR: f32 = 44_100.0;
const N: usize = 4;
struct Song {
step: usize,
}
impl Sequencer for Song {
fn tick(&mut self, _sample_rate: f32, out: &mut Vec<AudioEvent>) -> usize {
const NOTES: [u8; 8] = [48, 55, 60, 64, 67, 72, 64, 60];
let step = self.step;
self.step += 1;
#[allow(clippy::cast_possible_truncation)]
let ch = ChannelNo((step % N) as u8);
let note = MidiNote(NOTES[step % NOTES.len()]);
if step.is_multiple_of(2) {
out.push(AudioEvent::NoteOnChannel(ch, note));
} else {
out.push(AudioEvent::NoteOffChannel(ch, note));
}
out.push(AudioEvent::Drum(DrumHit::ALL[step % DrumHit::ALL.len()]));
1_171
}
}
fn sequenced() -> SequencedProcessor<N, Song> {
let mut processor = SynthProcessor::<N>::new(SR);
let patches = default_patches();
#[allow(clippy::cast_possible_truncation)]
let loads: Vec<_> = (0..N)
.map(|i| {
AudioEvent::LoadPatchChannel(
ChannelNo(i as u8),
Box::new(patches[i * 3 % patches.len()].params.clone()),
)
})
.collect();
processor.process_block(&loads, &mut [], 1);
SequencedProcessor::new(processor, Song { step: 0 })
}
fn render(block_sizes: &[usize], frames: usize, hw_channels: usize) -> Vec<f32> {
let mut synth = sequenced();
let mut out = vec![0.0; frames * hw_channels];
let mut offset = 0;
for &size in block_sizes.iter().cycle() {
if offset >= frames {
break;
}
let n = size.min(frames - offset);
synth.process_block(
&[],
&mut out[offset * hw_channels..(offset + n) * hw_channels],
hw_channels,
);
offset += n;
}
out
}
fn bits(samples: &[f32]) -> Vec<u32> {
samples.iter().map(|s| s.to_bits()).collect()
}
#[test]
fn output_does_not_depend_on_the_buffer_size() {
const FRAMES: usize = 3 * 44_100;
let reference = render(&[64], FRAMES, 1);
assert!(reference.iter().any(|s| s.abs() > 0.01), "song is silent");
for sizes in [&[512][..], &[4096], &[1], &[37], &[1, 4096, 333, 64, 2]] {
assert_eq!(
bits(&render(sizes, FRAMES, 1)),
bits(&reference),
"block sizes {sizes:?}"
);
}
}
#[test]
fn interleaved_output_matches_mono() {
const FRAMES: usize = 20_000;
let mono = render(&[512], FRAMES, 1);
let stereo = render(&[300], FRAMES, 2);
for (i, frame) in stereo.chunks(2).enumerate() {
assert_eq!(frame[0].to_bits(), mono[i].to_bits(), "frame {i}");
assert_eq!(frame[1].to_bits(), mono[i].to_bits(), "frame {i}");
}
}
struct KickAt(Option<usize>);
impl Sequencer for KickAt {
fn tick(&mut self, _sample_rate: f32, out: &mut Vec<AudioEvent>) -> usize {
if let Some(frame) = self.0.take() {
frame
} else {
out.push(AudioEvent::Drum(DrumHit::Kick));
usize::MAX
}
}
}
#[test]
fn events_land_on_the_frame_the_sequencer_asked_for() {
const AT: usize = 1_000;
let mut synth = SequencedProcessor::new(SynthProcessor::<N>::new(SR), KickAt(Some(AT)));
let mut seq = vec![0.0; 4_096];
synth.process_block(&[], &mut seq, 1);
let mut processor = SynthProcessor::<N>::new(SR);
let mut manual = vec![0.0; 4_096];
processor.process_block(&[], &mut manual[..AT], 1);
processor.process_block(&[AudioEvent::Drum(DrumHit::Kick)], &mut manual[AT..], 1);
assert_eq!(bits(&seq), bits(&manual));
assert!(seq[..AT].iter().all(|&s| s == 0.0));
assert!(seq[AT..].iter().any(|&s| s != 0.0));
}
#[test]
fn external_events_apply_before_the_sequencers_events_at_the_same_frame() {
let mut synth = SequencedProcessor::new(SynthProcessor::<N>::new(SR), KickAt(Some(1)));
let mut buf = [0.0; 256];
synth.process_block(&[], &mut buf[..1], 1);
synth.process_block(&[AudioEvent::Panic], &mut buf[1..], 1);
assert!(
!synth.processor().is_idle(),
"kick was swallowed by the panic"
);
}
struct Counter {
ticks: usize,
next: usize,
}
impl Sequencer for Counter {
fn tick(&mut self, _sample_rate: f32, _out: &mut Vec<AudioEvent>) -> usize {
self.ticks += 1;
self.next
}
}
#[test]
fn a_zero_frame_request_ticks_every_frame() {
let counter = Counter { ticks: 0, next: 0 };
let mut synth = SequencedProcessor::new(SynthProcessor::<N>::new(SR), counter);
synth.process_block(&[], &mut [0.0; 100], 1);
assert_eq!(synth.sequencer().ticks, 100);
}
#[test]
fn ticks_are_carried_across_blocks_and_empty_blocks_do_not_tick() {
let counter = Counter { ticks: 0, next: 10 };
let mut synth = SequencedProcessor::new(SynthProcessor::<N>::new(SR), counter);
synth.process_block(&[], &mut [], 1);
assert_eq!(synth.sequencer().ticks, 0);
synth.process_block(&[], &mut [0.0; 15], 1); assert_eq!(synth.sequencer().ticks, 2);
assert_eq!(synth.frames_until_tick(), 5);
synth.process_block(&[], &mut [0.0; 5], 1); assert_eq!(synth.sequencer().ticks, 2);
synth.process_block(&[], &mut [0.0; 1], 1);
assert_eq!(synth.sequencer().ticks, 3);
}
#[test]
fn a_replaced_sequencer_ticks_at_once() {
let mut synth = SequencedProcessor::new(
SynthProcessor::<N>::new(SR),
Counter {
ticks: 0,
next: 1_000,
},
);
synth.process_block(&[], &mut [0.0; 10], 1);
let old = synth.replace_sequencer(Counter {
ticks: 0,
next: 1_000,
});
assert_eq!(old.ticks, 1);
synth.process_block(&[], &mut [0.0; 10], 1);
assert_eq!(synth.sequencer().ticks, 1);
}
#[test]
fn boxed_and_borrowed_sequencers_work() {
let mut counter = Counter { ticks: 0, next: 4 };
{
let mut synth = SequencedProcessor::new(SynthProcessor::<N>::new(SR), &mut counter);
synth.process_block(&[], &mut [0.0; 8], 1);
}
assert_eq!(counter.ticks, 2);
let boxed: Box<dyn Sequencer> = Box::new(Counter { ticks: 0, next: 4 });
let mut synth = SequencedProcessor::new(SynthProcessor::<N>::new(SR), boxed);
synth.process_block(&[], &mut [0.0; 8], 1);
}
}