pub mod ruffbox_controls;
pub mod ruffbox_playhead;
#[cfg(feature = "no_denormals")]
pub(crate) mod flush_denormals;
use crossbeam::atomic::AtomicCell;
use crossbeam::channel::Receiver;
use crossbeam::channel::Sender;
use std::cmp::Ordering;
use std::sync::Arc;
use crate::building_blocks::SynthParameterAddress;
use crate::building_blocks::{
Modulator, SampleBuffer, Synth, SynthParameterLabel, SynthParameterValue, ValueOrModulator,
};
pub use crate::ruffbox::{ruffbox_controls::*, ruffbox_playhead::*};
pub enum ScheduledSource<const BUFSIZE: usize, const NCHAN: usize> {
Channel(Box<dyn Synth<BUFSIZE, NCHAN> + Send + Sync>),
Ambi(Box<dyn Synth<BUFSIZE, NCHAN> + Send + Sync>),
}
pub(crate) struct ScheduledEvent<const BUFSIZE: usize, const NCHAN: usize> {
timestamp: f64,
source: ScheduledSource<BUFSIZE, NCHAN>,
}
impl<const BUFSIZE: usize, const NCHAN: usize> Ord for ScheduledEvent<BUFSIZE, NCHAN> {
fn cmp(&self, other: &Self) -> Ordering {
self.timestamp.partial_cmp(&other.timestamp).unwrap()
}
}
impl<const BUFSIZE: usize, const NCHAN: usize> PartialOrd for ScheduledEvent<BUFSIZE, NCHAN> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl<const BUFSIZE: usize, const NCHAN: usize> PartialEq for ScheduledEvent<BUFSIZE, NCHAN> {
fn eq(&self, other: &Self) -> bool {
self.timestamp == other.timestamp
}
}
impl<const BUFSIZE: usize, const NCHAN: usize> Eq for ScheduledEvent<BUFSIZE, NCHAN> {}
impl<const BUFSIZE: usize, const NCHAN: usize> ScheduledEvent<BUFSIZE, NCHAN> {
pub fn new(ts: f64, src: ScheduledSource<BUFSIZE, NCHAN>) -> Self {
ScheduledEvent {
timestamp: ts,
source: src,
}
}
pub fn set_parameter(&mut self, par: SynthParameterAddress, value: &SynthParameterValue) {
match self.source {
ScheduledSource::Channel(ref mut src) => {
src.set_parameter(par, value);
}
ScheduledSource::Ambi(ref mut src) => {
src.set_parameter(par, value);
}
}
}
pub fn set_modulator(
&mut self,
par: SynthParameterAddress,
init: f32,
modulator: Modulator<BUFSIZE>,
) {
match self.source {
ScheduledSource::Channel(ref mut src) => {
src.set_modulator(par, init, modulator);
}
ScheduledSource::Ambi(ref mut src) => {
src.set_modulator(par, init, modulator);
}
}
}
fn set_param_or_modulator(
&mut self,
par: SynthParameterAddress,
val_or_mod: ValueOrModulator<BUFSIZE>,
) {
match val_or_mod {
ValueOrModulator::Val(val) => self.set_parameter(par, &val),
ValueOrModulator::Mod(init, modulator) => self.set_modulator(par, init, modulator),
}
}
}
pub enum ReverbMode {
FreeVerb,
Convolution(Vec<f32>, f32),
}
#[derive(Clone, Copy)]
pub enum GlobalEffect {
GlobalDelay,
GlobalReverb,
GlobalLpf,
GlobalHpf,
}
pub(crate) enum ControlMessage<const BUFSIZE: usize, const NCHAN: usize> {
LoadSample(usize, usize, SampleBuffer), SetGlobalParamOrModulator(GlobalEffect, SynthParameterLabel, ValueOrModulator<BUFSIZE>),
ScheduleEvent(ScheduledEvent<BUFSIZE, NCHAN>),
FreezeBuffer(usize, usize),
FreezeAddBuffer(usize, usize),
FreezeAfterRec(usize, usize, usize, bool),
ClearLiveBuffer(usize),
ClearFreezeBuffer(usize),
ClearAllFreezeBuffers,
ClearAllLiveBuffers,
ClearAllBuffers, }
#[allow(clippy::too_many_arguments)]
pub fn init_ruffbox<const BUFSIZE: usize, const NCHAN: usize>(
live_buffers: usize,
live_buffer_time: f64,
reverb_mode: &ReverbMode,
samplerate: f64,
max_buffers: usize,
freeze_buffers: usize,
ambisonics: bool,
ambisonics_binaural: bool,
limiter: bool,
) -> (
RuffboxControls<BUFSIZE, NCHAN>,
RuffboxPlayhead<BUFSIZE, NCHAN>,
) {
let (tx, rx): (
Sender<ControlMessage<BUFSIZE, NCHAN>>,
Receiver<ControlMessage<BUFSIZE, NCHAN>>,
) = crossbeam::channel::bounded(2000);
let now = Arc::new(AtomicCell::<f64>::new(0.0));
let controls = RuffboxControls::<BUFSIZE, NCHAN>::new(
samplerate,
live_buffers,
live_buffer_time,
max_buffers,
freeze_buffers,
&now,
tx,
);
let mut playhead = RuffboxPlayhead::<BUFSIZE, NCHAN>::new(
live_buffers,
live_buffer_time,
reverb_mode,
samplerate,
max_buffers,
freeze_buffers,
&now,
rx,
);
if ambisonics {
playhead.enable_ambisonics();
}
if ambisonics_binaural {
playhead.enable_ambisonic_binauralizer();
}
if limiter {
playhead.enable_limiter();
}
(controls, playhead)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::building_blocks::{
EnvelopeSegmentInfo, EnvelopeSegmentType, FilterType, OscillatorType, ValOp,
};
use crate::synths::{SynthDescription, SynthType};
use std::f32::consts::PI;
#[test]
fn test_stitch_stuff() {
let (_, mut ruff) = init_ruffbox::<512, 2>(
1,
2.0,
&ReverbMode::FreeVerb,
44100.0,
3000,
10,
false,
false,
false,
);
assert_approx_eq::assert_approx_eq!(ruff.fade_curve[0], 0.0, 0.00001);
assert_approx_eq::assert_approx_eq!(ruff.fade_curve[127], 1.0, 0.0002);
for _ in 0..513 {
ruff.write_sample_to_live_buffer(0, 1.0);
}
{
let SampleBuffer::Mono(buf) = &ruff.buffers[0] else {
panic!()
};
assert_approx_eq::assert_approx_eq!(buf[2], 1.0, 0.0002);
assert_approx_eq::assert_approx_eq!(buf[513], 0.0, 0.0002);
}
for _ in 0..512 {
ruff.write_sample_to_live_buffer(0, 1.0);
}
{
let SampleBuffer::Mono(buf) = &ruff.buffers[0] else {
panic!()
};
assert_approx_eq::assert_approx_eq!(buf[513], 1.0, 0.0002);
assert_approx_eq::assert_approx_eq!(buf[385], 1.0, 0.0002);
assert_approx_eq::assert_approx_eq!(buf[896], 1.0, 0.0002);
}
for _ in 0..2000 {
for _ in 0..512 {
ruff.write_sample_to_live_buffer(0, 1.0);
}
}
{
let SampleBuffer::Mono(buf) = &ruff.buffers[0] else {
panic!()
};
assert_approx_eq::assert_approx_eq!(buf[0], 0.0, 0.0002);
assert_approx_eq::assert_approx_eq!(buf[2], 1.0, 0.0002);
assert_approx_eq::assert_approx_eq!(buf[44100], 1.0, 0.0002);
assert_approx_eq::assert_approx_eq!(buf[ruff.buffer_lengths[0]], 1.0, 0.0002);
assert_approx_eq::assert_approx_eq!(buf[ruff.buffer_lengths[0] + 2], 0.0, 0.0002);
assert_approx_eq::assert_approx_eq!(buf[ruff.buffer_lengths[0] + 3], 0.0, 0.0002);
}
}
#[test]
fn test_load_mono_sample() {
let (ctrl, mut ruff) = init_ruffbox::<512, 2>(
0,
2.0,
&ReverbMode::FreeVerb,
44100.0,
3000,
10,
false,
false,
false,
);
let mut sample = vec![1.0_f32; 500];
ctrl.load_mono_sample(&mut sample, false, 44100.0);
ruff.process(0.0, true);
{
let SampleBuffer::Mono(buf) = &ruff.buffers[0] else {
panic!()
};
assert_approx_eq::assert_approx_eq!(buf[0], 0.0, 0.0002);
assert_approx_eq::assert_approx_eq!(buf[1], 0.0, 0.0002);
assert_approx_eq::assert_approx_eq!(buf[2], 1.0, 0.0002);
assert_approx_eq::assert_approx_eq!(buf[3], 1.0, 0.0002);
assert_approx_eq::assert_approx_eq!(buf[4], 1.0, 0.0002);
assert_approx_eq::assert_approx_eq!(buf[500], 1.0, 0.0002);
assert_approx_eq::assert_approx_eq!(buf[501], 1.0, 0.0002);
assert_approx_eq::assert_approx_eq!(buf[502], 0.0, 0.0002);
assert_approx_eq::assert_approx_eq!(buf[503], 0.0, 0.0002);
}
}
#[test]
fn test_sine_synth_at_block_start() {
let (ctrl, mut ruff) = init_ruffbox::<128, 2>(
0,
2.0,
&ReverbMode::FreeVerb,
44100.0,
3000,
10,
false,
false,
false,
);
if let Some(mut inst) = ctrl.prepare_instance(
SynthType::SingleOscillator(SynthDescription {
pre_filter_effects: vec![],
filters: vec![FilterType::Dummy, FilterType::Dummy],
oscillator_types: vec![OscillatorType::Sine],
ambisonic: false,
}),
0.0,
0,
) {
inst.set_instance_parameter(
SynthParameterLabel::PitchFrequency.into(),
&SynthParameterValue::ScalarF32(440.0),
);
inst.set_instance_parameter(
SynthParameterLabel::ChannelPosition.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst.set_instance_parameter(
SynthParameterLabel::Envelope.into(),
&SynthParameterValue::MultiPointEnvelope(
vec![
EnvelopeSegmentInfo {
from: 0.0,
to: 1.0,
time: 0.000025,
segment_type: EnvelopeSegmentType::Lin,
},
EnvelopeSegmentInfo {
from: 1.0,
to: 1.0,
time: 1.0 - 0.000025,
segment_type: EnvelopeSegmentType::Constant,
},
EnvelopeSegmentInfo {
from: 1.0,
to: 0.0,
time: 0.000025,
segment_type: EnvelopeSegmentType::Lin,
},
],
false,
ValOp::Replace,
),
);
ctrl.trigger(inst);
}
let out_1 = ruff.process(0.0, true);
let mut comp_1 = [0.0; 128];
for i in 0..128 {
comp_1[i] = (2.0 * PI * 440.0 * (i as f32 * (1.0 / 44100.0))).sin() * 0.5;
}
for i in 0..128 {
assert_approx_eq::assert_approx_eq!(out_1.0[0][i], comp_1[i], 0.008);
}
}
#[test]
fn test_basic_playback() {
let (ctrl, mut ruff) = init_ruffbox::<128, 2>(
1,
2.0,
&ReverbMode::FreeVerb,
44100.0,
3000,
10,
false,
false,
false,
);
let mut sample1 = vec![0.0, 0.1, 0.2, 0.3, 0.4, 0.3, 0.2, 0.1, 0.0];
let mut sample2 = vec![0.0, 0.01, 0.02, 0.03, 0.04, 0.03, 0.02, 0.01, 0.0];
let bnum1 = ctrl.load_mono_sample(&mut sample1, false, 44100.0);
let bnum2 = ctrl.load_mono_sample(&mut sample2, false, 44100.0);
ruff.process(0.0, true);
if let Some(mut inst_1) = ctrl.prepare_instance(
SynthType::Sampler(SynthDescription {
pre_filter_effects: vec![],
filters: vec![
FilterType::BiquadHpf12dB,
FilterType::Dummy,
FilterType::Dummy,
FilterType::Lpf18,
],
oscillator_types: vec![],
ambisonic: false,
}),
0.0,
bnum1,
) {
inst_1.set_instance_parameter(
SynthParameterLabel::ChannelPosition.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_1.set_instance_parameter(
SynthParameterLabel::LowpassCutoffFrequency.into(),
&SynthParameterValue::ScalarF32(22050.0),
);
inst_1.set_instance_parameter(
SynthParameterLabel::LowpassFilterDistortion.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_1.set_instance_parameter(
SynthParameterLabel::LowpassQFactor.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_1.set_instance_parameter(
SynthParameterLabel::Envelope.into(),
&SynthParameterValue::MultiPointEnvelope(
vec![
EnvelopeSegmentInfo {
from: 0.0,
to: 1.0,
time: 0.000025,
segment_type: EnvelopeSegmentType::Lin,
},
EnvelopeSegmentInfo {
from: 1.0,
to: 1.0,
time: 1.0 - 0.000025,
segment_type: EnvelopeSegmentType::Constant,
},
EnvelopeSegmentInfo {
from: 1.0,
to: 0.0,
time: 0.000025,
segment_type: EnvelopeSegmentType::Lin,
},
],
false,
ValOp::Replace,
),
);
ctrl.trigger(inst_1);
}
if let Some(mut inst_2) = ctrl.prepare_instance(
SynthType::Sampler(SynthDescription {
pre_filter_effects: vec![],
filters: vec![
FilterType::BiquadHpf12dB,
FilterType::Dummy,
FilterType::Dummy,
FilterType::Lpf18,
],
oscillator_types: vec![],
ambisonic: false,
}),
0.0,
bnum2,
) {
inst_2.set_instance_parameter(
SynthParameterLabel::ChannelPosition.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_2.set_instance_parameter(
SynthParameterLabel::LowpassCutoffFrequency.into(),
&SynthParameterValue::ScalarF32(22050.0),
);
inst_2.set_instance_parameter(
SynthParameterLabel::LowpassFilterDistortion.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_2.set_instance_parameter(
SynthParameterLabel::LowpassQFactor.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_2.set_instance_parameter(
SynthParameterLabel::Envelope.into(),
&SynthParameterValue::MultiPointEnvelope(
vec![
EnvelopeSegmentInfo {
from: 0.0,
to: 1.0,
time: 0.000025,
segment_type: EnvelopeSegmentType::Lin,
},
EnvelopeSegmentInfo {
from: 1.0,
to: 1.0,
time: 1.0 - 0.000025,
segment_type: EnvelopeSegmentType::Constant,
},
EnvelopeSegmentInfo {
from: 1.0,
to: 0.0,
time: 0.000025,
segment_type: EnvelopeSegmentType::Lin,
},
],
false,
ValOp::Replace,
),
);
ctrl.trigger(inst_2);
}
let out_buf = ruff.process(0.0, true);
for i in 0..9 {
println!("{} {} ", out_buf.0[0][i], sample1[i + 2] + sample2[i + 2]);
assert_approx_eq::assert_approx_eq!(
out_buf.0[0][i],
sample1[i + 2] + sample2[i + 2],
0.03
);
}
}
#[test]
fn reverb_smoke_test() {
let (ctrl, mut ruff) = init_ruffbox::<128, 2>(
1,
2.0,
&ReverbMode::FreeVerb,
44100.0,
3000,
10,
false,
false,
false,
);
let mut sample1 = vec![0.0, 0.1, 0.2, 0.3, 0.4, 0.3, 0.2, 0.1, 0.0];
let bnum1 = ctrl.load_mono_sample(&mut sample1, false, 44100.0);
ruff.process(0.0, true);
if let Some(mut inst_1) = ctrl.prepare_instance(
SynthType::Sampler(SynthDescription {
pre_filter_effects: vec![],
filters: vec![
FilterType::BiquadHpf12dB,
FilterType::Dummy,
FilterType::Dummy,
FilterType::Lpf18,
],
oscillator_types: vec![],
ambisonic: false,
}),
0.0,
bnum1,
) {
inst_1.set_instance_parameter(
SynthParameterLabel::ChannelPosition.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_1.set_instance_parameter(
SynthParameterLabel::ReverbMix.into(),
&SynthParameterValue::ScalarF32(1.0),
);
inst_1.set_instance_parameter(
SynthParameterLabel::LowpassCutoffFrequency.into(),
&SynthParameterValue::ScalarF32(22050.0),
);
inst_1.set_instance_parameter(
SynthParameterLabel::LowpassFilterDistortion.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_1.set_instance_parameter(
SynthParameterLabel::LowpassQFactor.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_1.set_instance_parameter(
SynthParameterLabel::Envelope.into(),
&SynthParameterValue::MultiPointEnvelope(
vec![
EnvelopeSegmentInfo {
from: 0.0,
to: 1.0,
time: 0.000025,
segment_type: EnvelopeSegmentType::Lin,
},
EnvelopeSegmentInfo {
from: 1.0,
to: 1.0,
time: 1.0 - 0.000025,
segment_type: EnvelopeSegmentType::Constant,
},
EnvelopeSegmentInfo {
from: 1.0,
to: 0.0,
time: 0.000025,
segment_type: EnvelopeSegmentType::Lin,
},
],
false,
ValOp::Replace,
),
);
ctrl.trigger(inst_1);
}
let out_buf = ruff.process(0.0, true);
for i in 0..9 {
println!("{} {} ", out_buf.0[0][i], sample1[i + 2]);
assert_approx_eq::assert_approx_eq!(out_buf.0[0][i], sample1[i + 2], 0.03);
}
}
#[test]
fn test_scheduled_playback() {
let (ctrl, mut ruff) = init_ruffbox::<128, 2>(
1,
2.0,
&ReverbMode::FreeVerb,
44100.0,
3000,
10,
false,
false,
false,
);
let block_duration = 0.00290249433;
let mut sample1 = vec![0.0, 0.1, 0.2, 0.3, 0.4, 0.3, 0.2, 0.1, 0.0];
let mut sample2 = vec![0.0, 0.01, 0.02, 0.03, 0.04, 0.03, 0.02, 0.01, 0.0];
let bnum1 = ctrl.load_mono_sample(&mut sample1, false, 44100.0);
let bnum2 = ctrl.load_mono_sample(&mut sample2, false, 44100.0);
if let Some(mut inst_1) = ctrl.prepare_instance(
SynthType::Sampler(SynthDescription {
pre_filter_effects: vec![],
filters: vec![
FilterType::BiquadHpf12dB,
FilterType::Dummy,
FilterType::Dummy,
FilterType::Lpf18,
],
oscillator_types: vec![],
ambisonic: false,
}),
0.291,
bnum1,
) {
inst_1.set_instance_parameter(
SynthParameterLabel::ChannelPosition.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_1.set_instance_parameter(
SynthParameterLabel::LowpassCutoffFrequency.into(),
&SynthParameterValue::ScalarF32(22050.0),
);
inst_1.set_instance_parameter(
SynthParameterLabel::LowpassFilterDistortion.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_1.set_instance_parameter(
SynthParameterLabel::LowpassQFactor.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_1.set_instance_parameter(
SynthParameterLabel::Envelope.into(),
&SynthParameterValue::MultiPointEnvelope(
vec![EnvelopeSegmentInfo {
from: 1.0,
to: 1.0,
time: 9.0 / 44100.0,
segment_type: EnvelopeSegmentType::Constant,
}],
false,
ValOp::Replace,
),
);
ctrl.trigger(inst_1);
}
if let Some(mut inst_2) = ctrl.prepare_instance(
SynthType::Sampler(SynthDescription {
pre_filter_effects: vec![],
filters: vec![
FilterType::BiquadHpf12dB,
FilterType::Dummy,
FilterType::Dummy,
FilterType::Lpf18,
],
oscillator_types: vec![],
ambisonic: false,
}),
0.291,
bnum2,
) {
inst_2.set_instance_parameter(
SynthParameterLabel::ChannelPosition.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_2.set_instance_parameter(
SynthParameterLabel::LowpassCutoffFrequency.into(),
&SynthParameterValue::ScalarF32(22050.0),
);
inst_2.set_instance_parameter(
SynthParameterLabel::LowpassFilterDistortion.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_2.set_instance_parameter(
SynthParameterLabel::LowpassQFactor.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_2.set_instance_parameter(
SynthParameterLabel::Envelope.into(),
&SynthParameterValue::MultiPointEnvelope(
vec![EnvelopeSegmentInfo {
from: 1.0,
to: 1.0,
time: 9.0 / 44100.0,
segment_type: EnvelopeSegmentType::Constant,
}],
false,
ValOp::Replace,
),
);
ctrl.trigger(inst_2);
}
let mut stream_time = 0.0;
for _ in 0..100 {
ruff.process(stream_time, false);
stream_time += block_duration;
}
let out_buf = ruff.process(stream_time, false);
for i in 0..9 {
assert_approx_eq::assert_approx_eq!(
out_buf.0[0][33 + i],
sample1[i + 2] + sample2[i + 2],
0.03
);
}
}
#[test]
fn test_overlap_playback() {
let (ctrl, mut ruff) = init_ruffbox::<128, 2>(
1,
2.0,
&ReverbMode::FreeVerb,
44100.0,
3000,
10,
false,
false,
false,
);
let block_duration = 0.00290249433;
let sec_per_sample = 0.00002267573;
let mut sample1 = vec![0.0, 0.1, 0.2, 0.3, 0.4, 0.3, 0.2, 0.1, 0.0];
let mut sample2 = vec![0.0, 0.01, 0.02, 0.03, 0.04, 0.03, 0.02, 0.01, 0.0];
let bnum1 = ctrl.load_mono_sample(&mut sample1, false, 44100.0);
let bnum2 = ctrl.load_mono_sample(&mut sample2, false, 44100.0);
if let Some(mut inst_1) = ctrl.prepare_instance(
SynthType::Sampler(SynthDescription {
pre_filter_effects: vec![],
filters: vec![
FilterType::BiquadHpf12dB,
FilterType::Dummy,
FilterType::Dummy,
FilterType::Lpf18,
],
oscillator_types: vec![],
ambisonic: false,
}),
0.291,
bnum1,
) {
inst_1.set_instance_parameter(
SynthParameterLabel::ChannelPosition.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_1.set_instance_parameter(
SynthParameterLabel::LowpassCutoffFrequency.into(),
&SynthParameterValue::ScalarF32(22050.0),
);
inst_1.set_instance_parameter(
SynthParameterLabel::LowpassFilterDistortion.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_1.set_instance_parameter(
SynthParameterLabel::LowpassQFactor.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_1.set_instance_parameter(
SynthParameterLabel::Envelope.into(),
&SynthParameterValue::MultiPointEnvelope(
vec![EnvelopeSegmentInfo {
from: 1.0,
to: 1.0,
time: 9.0 / 44100.0,
segment_type: EnvelopeSegmentType::Constant,
}],
false,
ValOp::Replace,
),
);
ctrl.trigger(inst_1);
}
if let Some(mut inst_2) = ctrl.prepare_instance(
SynthType::Sampler(SynthDescription {
pre_filter_effects: vec![],
filters: vec![
FilterType::BiquadHpf12dB,
FilterType::Dummy,
FilterType::Dummy,
FilterType::Lpf18,
],
oscillator_types: vec![],
ambisonic: false,
}),
0.291 + (4.0 * sec_per_sample),
bnum2,
) {
inst_2.set_instance_parameter(
SynthParameterLabel::ChannelPosition.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_2.set_instance_parameter(
SynthParameterLabel::LowpassCutoffFrequency.into(),
&SynthParameterValue::ScalarF32(22050.0),
);
inst_2.set_instance_parameter(
SynthParameterLabel::LowpassFilterDistortion.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_2.set_instance_parameter(
SynthParameterLabel::LowpassQFactor.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_2.set_instance_parameter(
SynthParameterLabel::Envelope.into(),
&SynthParameterValue::MultiPointEnvelope(
vec![EnvelopeSegmentInfo {
from: 1.0,
to: 1.0,
time: 9.0 / 44100.0,
segment_type: EnvelopeSegmentType::Constant,
}],
false,
ValOp::Replace,
),
);
ctrl.trigger(inst_2);
}
let mut stream_time = 0.0;
for _ in 0..100 {
ruff.process(stream_time, false);
stream_time += block_duration;
}
let out_buf = ruff.process(stream_time, false);
for i in 0..4 {
assert_approx_eq::assert_approx_eq!(out_buf.0[0][33 + i], sample1[i + 2], 0.03);
}
for i in 0..5 {
assert_approx_eq::assert_approx_eq!(
out_buf.0[0][37 + i],
sample1[i + 4 + 2] + sample2[i + 2],
0.03
);
}
for i in 0..4 {
assert_approx_eq::assert_approx_eq!(out_buf.0[0][42 + i], sample2[i + 5 + 1], 0.03);
}
}
#[test]
fn test_disjunct_playback() {
let (ctrl, mut ruff) = init_ruffbox::<128, 2>(
1,
2.0,
&ReverbMode::FreeVerb,
44100.0,
3000,
10,
false,
false,
false,
);
let block_duration = 0.00290249433;
let mut sample1 = vec![0.0, 0.1, 0.2, 0.3, 0.4, 0.3, 0.2, 0.1, 0.0];
let mut sample2 = vec![0.0, 0.01, 0.02, 0.03, 0.04, 0.03, 0.02, 0.01, 0.0];
let bnum1 = ctrl.load_mono_sample(&mut sample1, false, 44100.0);
let bnum2 = ctrl.load_mono_sample(&mut sample2, false, 44100.0);
let second_sample_timestamp = 0.291 + (10.0 * block_duration);
if let Some(mut inst_1) = ctrl.prepare_instance(
SynthType::Sampler(SynthDescription {
pre_filter_effects: vec![],
filters: vec![
FilterType::Dummy,
FilterType::Dummy,
FilterType::Dummy,
FilterType::Dummy,
],
oscillator_types: vec![],
ambisonic: false,
}),
0.291,
bnum1,
) {
inst_1.set_instance_parameter(
SynthParameterLabel::ChannelPosition.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_1.set_instance_parameter(
SynthParameterLabel::Envelope.into(),
&SynthParameterValue::MultiPointEnvelope(
vec![EnvelopeSegmentInfo {
from: 1.0,
to: 1.0,
time: 9.0 / 44100.0,
segment_type: EnvelopeSegmentType::Constant,
}],
false,
ValOp::Replace,
),
);
ctrl.trigger(inst_1);
}
if let Some(mut inst_2) = ctrl.prepare_instance(
SynthType::Sampler(SynthDescription {
pre_filter_effects: vec![],
filters: vec![
FilterType::Dummy,
FilterType::Dummy,
FilterType::Dummy,
FilterType::Dummy,
],
oscillator_types: vec![],
ambisonic: false,
}),
second_sample_timestamp,
bnum2,
) {
inst_2.set_instance_parameter(
SynthParameterLabel::ChannelPosition.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_2.set_instance_parameter(
SynthParameterLabel::Envelope.into(),
&SynthParameterValue::MultiPointEnvelope(
vec![EnvelopeSegmentInfo {
from: 1.0,
to: 1.0,
time: 9.0 / 44100.0,
segment_type: EnvelopeSegmentType::Constant,
}],
false,
ValOp::Replace,
),
);
ctrl.trigger(inst_2);
}
let mut stream_time = 0.0;
for _ in 0..100 {
ruff.process(stream_time, false);
stream_time += block_duration;
}
let out_buf = ruff.process(stream_time, false);
stream_time += block_duration;
println!("pre {out_buf:?}");
for i in 0..9 {
assert_approx_eq::assert_approx_eq!(out_buf.0[0][33 + i], sample1[i + 2], 0.03);
}
for _ in 0..9 {
ruff.process(stream_time, false);
stream_time += block_duration;
}
let out_buf = ruff.process(stream_time, false);
println!("{out_buf:?}");
for i in 0..9 {
assert_approx_eq::assert_approx_eq!(out_buf.0[0][33 + i], sample2[i + 2], 0.03);
}
}
#[test]
fn test_late_playback() {
let (ctrl, mut ruff) = init_ruffbox::<128, 2>(
1,
2.0,
&ReverbMode::FreeVerb,
44100.0,
3000,
10,
false,
false,
false,
);
let mut sample1 = vec![0.0, 0.1, 0.2, 0.3, 0.4, 0.3, 0.2, 0.1, 0.0];
let bnum1 = ctrl.load_mono_sample(&mut sample1, false, 44100.0);
ruff.process(0.0, false);
if let Some(mut inst_1) = ctrl.prepare_instance(
SynthType::Sampler(SynthDescription {
pre_filter_effects: vec![],
filters: vec![
FilterType::BiquadHpf12dB,
FilterType::Dummy,
FilterType::Dummy,
FilterType::Lpf18,
],
oscillator_types: vec![],
ambisonic: false,
}),
0.1,
bnum1,
) {
inst_1.set_instance_parameter(
SynthParameterLabel::ChannelPosition.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_1.set_instance_parameter(
SynthParameterLabel::LowpassCutoffFrequency.into(),
&SynthParameterValue::ScalarF32(22050.0),
);
inst_1.set_instance_parameter(
SynthParameterLabel::LowpassFilterDistortion.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_1.set_instance_parameter(
SynthParameterLabel::LowpassQFactor.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_1.set_instance_parameter(
SynthParameterLabel::Envelope.into(),
&SynthParameterValue::MultiPointEnvelope(
vec![EnvelopeSegmentInfo {
from: 1.0,
to: 1.0,
time: 9.0 / 44100.0,
segment_type: EnvelopeSegmentType::Constant,
}],
false,
ValOp::Replace,
),
);
ctrl.trigger(inst_1);
}
let out_buf = ruff.process(0.101, false);
for i in 0..9 {
assert_approx_eq::assert_approx_eq!(out_buf.0[0][i], sample1[i + 2], 0.03);
}
}
}
#[cfg(test)]
mod memory_tests {
use super::*;
use crate::building_blocks::{EnvelopeSegmentInfo, EnvelopeSegmentType, FilterType, ValOp};
use crate::synths::{SynthDescription, SynthType};
use assert_no_alloc::*;
#[cfg(debug_assertions)] #[global_allocator]
static A: AllocDisabler = AllocDisabler;
#[test]
fn test_memory_alloc() {
let (ctrl, mut ruff) = init_ruffbox::<128, 2>(
1,
2.0,
&ReverbMode::FreeVerb,
44100.0,
3000,
10,
false,
false,
false,
);
let mut sample1 = vec![0.0, 0.1, 0.2, 0.3, 0.4, 0.3, 0.2, 0.1, 0.0];
let mut sample2 = vec![0.0, 0.01, 0.02, 0.03, 0.04, 0.03, 0.02, 0.01, 0.0];
let bnum1 = ctrl.load_mono_sample(&mut sample1, false, 44100.0);
let bnum2 = ctrl.load_mono_sample(&mut sample2, false, 44100.0);
ruff.process(0.0, true);
if let Some(mut inst_1) = ctrl.prepare_instance(
SynthType::Sampler(SynthDescription {
pre_filter_effects: vec![],
filters: vec![
FilterType::BiquadHpf12dB,
FilterType::Dummy,
FilterType::Dummy,
FilterType::Lpf18,
],
oscillator_types: vec![],
ambisonic: false,
}),
0.0,
bnum1,
) {
inst_1.set_instance_parameter(
SynthParameterLabel::ChannelPosition.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_1.set_instance_parameter(
SynthParameterLabel::LowpassCutoffFrequency.into(),
&SynthParameterValue::ScalarF32(22050.0),
);
inst_1.set_instance_parameter(
SynthParameterLabel::LowpassFilterDistortion.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_1.set_instance_parameter(
SynthParameterLabel::LowpassQFactor.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_1.set_instance_parameter(
SynthParameterLabel::Envelope.into(),
&SynthParameterValue::MultiPointEnvelope(
vec![
EnvelopeSegmentInfo {
from: 0.0,
to: 1.0,
time: 0.000025,
segment_type: EnvelopeSegmentType::Lin,
},
EnvelopeSegmentInfo {
from: 1.0,
to: 1.0,
time: 1.0 - 0.000025,
segment_type: EnvelopeSegmentType::Constant,
},
EnvelopeSegmentInfo {
from: 1.0,
to: 0.0,
time: 0.000025,
segment_type: EnvelopeSegmentType::Lin,
},
],
false,
ValOp::Replace,
),
);
ctrl.trigger(inst_1);
}
if let Some(mut inst_2) = ctrl.prepare_instance(
SynthType::Sampler(SynthDescription {
pre_filter_effects: vec![],
filters: vec![
FilterType::BiquadHpf12dB,
FilterType::Dummy,
FilterType::Dummy,
FilterType::Lpf18,
],
oscillator_types: vec![],
ambisonic: false,
}),
0.0,
bnum2,
) {
inst_2.set_instance_parameter(
SynthParameterLabel::ChannelPosition.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_2.set_instance_parameter(
SynthParameterLabel::LowpassCutoffFrequency.into(),
&SynthParameterValue::ScalarF32(22050.0),
);
inst_2.set_instance_parameter(
SynthParameterLabel::LowpassFilterDistortion.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_2.set_instance_parameter(
SynthParameterLabel::LowpassQFactor.into(),
&SynthParameterValue::ScalarF32(0.0),
);
inst_2.set_instance_parameter(
SynthParameterLabel::Envelope.into(),
&SynthParameterValue::MultiPointEnvelope(
vec![
EnvelopeSegmentInfo {
from: 0.0,
to: 1.0,
time: 0.000025,
segment_type: EnvelopeSegmentType::Lin,
},
EnvelopeSegmentInfo {
from: 1.0,
to: 1.0,
time: 1.0 - 0.000025,
segment_type: EnvelopeSegmentType::Constant,
},
EnvelopeSegmentInfo {
from: 1.0,
to: 0.0,
time: 0.000025,
segment_type: EnvelopeSegmentType::Lin,
},
],
false,
ValOp::Replace,
),
);
ctrl.trigger(inst_2);
}
println!("CHECK IF MAIN PROCESS FUNCTION ALLOCATES MEMORY");
assert_no_alloc(|| {
for _ in 0..100 {
let _ = ruff.process(0.0, true);
}
});
}
}