use embedded_audio::envelope::{Adsr, AdsrSpec};
use embedded_audio::hal::{DmaDoubleBuffer, DutyBuffer, PwmDutySink, fill_buffer_into, tick_into};
use embedded_audio::prelude::*;
use embedded_audio::synth::{
PULSE_25_TABLE, SAW_TABLE, SINE_TABLE, SQUARE_TABLE, TRIANGLE_TABLE, ToneParams, ToneVoice,
Waveform, WavetableVoice, generate_wavetable_fixed,
};
#[test]
fn test_adsr_envelope_lifecycle() {
let spec = AdsrSpec {
attack_ms: 10,
decay_ms: 10,
sustain_q8: 128, release_ms: 10,
};
let mut env = Adsr::new(spec, 1000);
env.trigger();
assert!(env.is_active());
for _ in 0..10 {
env.tick();
}
assert_eq!(env.level_q8(), 255);
for _ in 0..10 {
env.tick();
}
assert!((env.level_q8() as i16 - 128).abs() <= 5);
env.release();
for _ in 0..10 {
env.tick();
}
assert_eq!(env.level_q8(), 0);
assert!(!env.is_active());
}
#[test]
fn test_all_synth_waveforms_produce_samples() {
let waveforms = [Waveform::Sine, Waveform::Triangle, Waveform::Square];
for wf in waveforms {
let mut voice = ToneVoice::new();
voice.start(
ToneParams {
freq_hz: 440,
duration_ms: 50,
waveform: wf,
adsr: AdsrSpec::click(),
},
16_000,
);
let mut samples = [0i8; 64];
let mut active_count = 0;
for s in samples.iter_mut() {
if let Some(sample) = voice.next_sample() {
*s = sample;
active_count += 1;
}
}
assert_eq!(active_count, 64, "Waveform {:?} should yield samples", wf);
assert!(
samples.iter().any(|&s| s != 0),
"Waveform {:?} should produce non-zero samples",
wf
);
}
}
#[test]
fn test_wavetable_voice_synthesis() {
let mut voice = WavetableVoice::new(&SINE_TABLE);
voice.start(440, 16_000);
let mut samples_collected = 0;
while let Some(_sample) = voice.next_sample() {
samples_collected += 1;
if samples_collected >= 100 {
break;
}
}
assert_eq!(samples_collected, 100);
}
struct MockPwmHardware {
written_duties: heapless::Vec<u16, 64>,
}
impl PwmDutySink for MockPwmHardware {
fn set_duty(&mut self, duty: u16) {
let _ = self.written_duties.push(duty);
}
}
#[test]
fn test_hal_tick_into_dma_sink() {
let mut engine = AudioEngine::from_sample_rate(16000, 1000, DutyMode::Linear);
engine.play_tone(440, 100, Waveform::Sine);
let mut hw = MockPwmHardware {
written_duties: heapless::Vec::new(),
};
tick_into(&mut engine, &mut hw);
assert_eq!(hw.written_duties.len(), 1);
let mut out_buf = [0u16; 32];
let mut target = [0u16; 32];
let mut duty_buf = DutyBuffer::new(&mut target);
let written = fill_buffer_into(&mut engine, &mut out_buf, &mut duty_buf);
assert_eq!(written, 32);
assert_eq!(duty_buf.cursor, 1);
}
#[test]
fn test_audio_engine_stop_and_reset() {
let mut engine = AudioEngine::from_sample_rate(16000, 255, DutyMode::Linear);
engine.play_tone(440, 500, Waveform::Sine);
assert!(engine.is_playing());
engine.stop_all();
for _ in 0..200 {
engine.tick();
}
assert!(!engine.is_playing());
assert_eq!(engine.tick_pcm(), 0);
}
#[test]
fn test_wavetable_presets_and_play_wavetable() {
let mut engine = AudioEngine::from_sample_rate(16000, 1000, DutyMode::Linear);
let voice_idx = engine
.play_wavetable(&SAW_TABLE, 440, AdsrSpec::click())
.expect("play wavetable saw");
assert_eq!(voice_idx, 0);
assert!(engine.is_playing());
let mut pcm_buf = [0i8; 32];
engine.fill_pcm_i8_buffer(&mut pcm_buf);
assert!(pcm_buf.iter().any(|&s| s != 0));
let custom_table = generate_wavetable_fixed(|phase_idx| {
if phase_idx < 128 {
(phase_idx as i16 - 64) as i8
} else {
(191 - phase_idx as i16) as i8
}
});
assert_eq!(custom_table.len(), 256);
engine.stop_all();
engine
.play_wavetable(&SQUARE_TABLE, 220, AdsrSpec::click())
.unwrap();
assert!(engine.is_playing());
engine.stop_all();
engine
.play_wavetable(&PULSE_25_TABLE, 220, AdsrSpec::click())
.unwrap();
assert!(engine.is_playing());
engine.stop_all();
engine
.play_wavetable(&custom_table, 880, AdsrSpec::click())
.unwrap();
assert!(engine.is_playing());
}
#[test]
fn test_peripheral_dma_buffer_filling() {
let mut engine = AudioEngine::from_sample_rate(16000, 1000, DutyMode::Linear);
engine
.play_wavetable(&TRIANGLE_TABLE, 440, AdsrSpec::click())
.unwrap();
let mut buf_i16 = [0i16; 16];
engine.fill_pcm_i16_buffer(&mut buf_i16);
assert!(buf_i16.iter().any(|&s| s != 0));
let mut buf_i32 = [0i32; 16];
engine.fill_pcm_i32_buffer(&mut buf_i32);
assert!(buf_i32.iter().any(|&s| s != 0));
let mut buf_u8 = [0u8; 16];
engine.fill_dac_u8_buffer(&mut buf_u8);
assert!(buf_u8.iter().any(|&s| s != 128));
let mut buf_u12 = [0u16; 16];
engine.fill_dac_u12_buffer(&mut buf_u12);
assert!(buf_u12.iter().all(|&s| s <= 4095));
assert!(buf_u12.iter().any(|&s| s != 2048));
let mut buf_u16 = [0u16; 16];
engine.fill_dac_u16_buffer(&mut buf_u16);
assert!(buf_u16.iter().any(|&s| s != 32768));
let mut buf_stereo_i16 = [0i16; 32];
engine.fill_stereo_i16_buffer(&mut buf_stereo_i16);
assert_eq!(buf_stereo_i16[0], buf_stereo_i16[1]);
let mut buf_stereo_i32 = [0i32; 32];
engine.fill_stereo_i32_buffer(&mut buf_stereo_i32);
assert_eq!(buf_stereo_i32[0], buf_stereo_i32[1]);
}
#[test]
fn test_dma_double_buffer() {
let mut dbuf = DmaDoubleBuffer::<u16, 64>::new();
assert_eq!(DmaDoubleBuffer::<u16, 64>::HALF_SIZE, 64);
assert_eq!(DmaDoubleBuffer::<u16, 64>::TOTAL_SIZE, 128);
let mut engine = AudioEngine::from_sample_rate(16000, 1000, DutyMode::Linear);
engine.play_tone(440, 100, Waveform::Sine);
engine.fill_dac_u12_buffer(dbuf.current_half_mut());
let active_sample = dbuf.current_half()[0];
assert!(active_sample <= 4095);
let next_half = dbuf.swap_and_get_next();
engine.fill_dac_u12_buffer(next_half);
dbuf.fill_and_swap(|slice| {
engine.fill_dac_u12_buffer(slice);
});
}