use approx::assert_relative_eq;
use rawdio::{prelude::*, Gain};
struct Fixture {
channel_count: usize,
sample_rate: usize,
context: Box<dyn Context>,
audio_process: Box<dyn AudioProcess>,
gain: Gain,
}
impl Fixture {
fn process_seconds(&mut self, seconds: f64) -> OwnedAudioBuffer {
let frame_count = (seconds * self.sample_rate as f64).ceil() as usize;
let frequency = 1_000.0;
let amplitude = 1.0;
let input_buffer = OwnedAudioBuffer::sine(
frame_count,
self.channel_count,
self.sample_rate,
frequency,
amplitude,
);
let mut output_buffer =
OwnedAudioBuffer::new(frame_count, self.channel_count, self.sample_rate);
self.audio_process
.process(&input_buffer, &mut output_buffer);
output_buffer
}
fn new(channel_count: usize) -> Self {
let sample_rate = 44100;
let (mut context, process) =
create_engine_with_options(EngineOptions::default().with_sample_rate(sample_rate));
let gain = Gain::new(context.as_ref(), channel_count);
connect_nodes!("input" => gain => "output");
context.start();
Self {
channel_count,
sample_rate,
context,
audio_process: process,
gain,
}
}
}
impl Default for Fixture {
fn default() -> Self {
let channel_count = 1;
Self::new(channel_count)
}
}
impl Drop for Fixture {
fn drop(&mut self) {
self.context.stop();
}
}
fn create_envelope(
buffer: &[f32],
sample_rate: usize,
attack_time: f64,
release_time: f64,
) -> Vec<f32> {
let mut envelope = Vec::with_capacity(buffer.len());
let attack_gain = (-1.0 / (sample_rate as f64 * attack_time)).exp() as f32;
let release_gain = (-1.0 / (sample_rate as f64 * release_time)).exp() as f32;
let mut envelope_output = 0.0;
for sample in buffer {
let envelope_input = sample.abs();
envelope_output = if envelope_output < envelope_input {
envelope_input + attack_gain * (envelope_output - envelope_input)
} else {
envelope_input + release_gain * (envelope_output - envelope_input)
};
envelope.push(envelope_output);
}
envelope
}
fn create_normalised_envelope(output_buffer: &[f32], sample_rate: usize) -> Vec<f32> {
let attack_time = 0.01;
let release_time = 0.01;
let envelope = create_envelope(output_buffer, sample_rate, attack_time, release_time);
let max_value = envelope
.iter()
.fold(0.0_f32, |current_max, value| current_max.max(*value));
assert!(max_value > 0.0);
envelope.iter().map(|value| *value / max_value).collect()
}
#[test]
fn test_gain_envelope() {
let mut fixture = Fixture::default();
fixture
.gain
.gain()
.set_value_at_time(0.0, Timestamp::zero());
for (gain, start_time, end_time) in [
(1.0, Timestamp::zero(), Timestamp::from_seconds(1.0)),
(
0.0,
Timestamp::from_seconds(1.0),
Timestamp::from_seconds(2.0),
),
] {
fixture
.gain
.gain()
.linear_ramp_to_value(gain, start_time, end_time);
}
let output_buffer = fixture.process_seconds(2.0);
let envelope = create_normalised_envelope(
output_buffer.get_channel_data(SampleLocation::origin()),
fixture.sample_rate,
);
for (frame, value) in envelope.iter().enumerate() {
let time = Timestamp::from_samples(frame as f64, fixture.sample_rate).as_seconds();
let expected_value = if time < 1.0 { time } else { 2.0 - time };
assert_relative_eq!(expected_value as f32, value, epsilon = 0.05);
}
}
#[test]
fn test_peak() {
let mut fixture = Fixture::default();
let gain = 0.5;
fixture
.gain
.gain()
.set_value_at_time(gain, Timestamp::zero());
let output_buffer = fixture.process_seconds(2.0);
let output_samples = output_buffer.get_channel_data(SampleLocation::origin());
let max_value = output_samples.iter().fold(0.0_f32, |a, b| a.max(*b));
let max_relative = Level::from_db(0.1).as_linear() as f32;
assert_relative_eq!(max_value, gain as f32, max_relative = max_relative);
}
#[test]
fn test_multichannel_gain() {
let channel_count = 2;
let mut fixture = Fixture::new(channel_count);
let output_buffer = fixture.process_seconds(1.0);
for channel in 0..channel_count {
assert!(!output_buffer.channel_is_silent(channel));
}
}