use aether_core::{
command::Command,
graph::DspGraph,
param::Param,
scheduler::Scheduler,
BUFFER_SIZE,
};
use aether_nodes::{envelope::AdsrEnvelope, oscillator::Oscillator};
use hound::{WavSpec, WavWriter};
use ringbuf::{traits::{Producer, Split}, HeapRb};
const SAMPLE_RATE: f32 = 48_000.0;
const DURATION_SECS: f32 = 6.0;
const TOTAL_SAMPLES: usize = (SAMPLE_RATE * DURATION_SECS) as usize;
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("🎵 ADSR Envelope Test");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!();
println!("Rendering: Oscillator → ADSR Envelope → Output");
println!("Duration: {} seconds", DURATION_SECS);
println!("Sample Rate: {} Hz", SAMPLE_RATE);
println!();
let (mut producer, mut consumer) = HeapRb::<Command>::new(1024).split();
let mut scheduler = Scheduler::new(SAMPLE_RATE);
let mut graph = DspGraph::new();
println!("🔧 Building audio graph...");
let osc = Box::new(Oscillator::new());
let osc_id = graph.add_node(osc).expect("Failed to add oscillator");
let envelope = Box::new(AdsrEnvelope::new());
let env_id = graph.add_node(envelope).expect("Failed to add envelope");
graph.connect(osc_id, env_id, 0);
graph.set_output_node(env_id);
producer.try_push(Command::AddNode { id: osc_id }).ok();
producer.try_push(Command::AddNode { id: env_id }).ok();
producer.try_push(Command::Connect { src: osc_id, dst: env_id, slot: 0 }).ok();
producer.try_push(Command::SetOutputNode { id: env_id }).ok();
producer.try_push(Command::UpdateParam {
node: osc_id,
param_index: 0,
new_param: Param::new(440.0), }).ok();
producer.try_push(Command::UpdateParam {
node: osc_id,
param_index: 1,
new_param: Param::new(1.0), }).ok();
producer.try_push(Command::UpdateParam {
node: osc_id,
param_index: 2,
new_param: Param::new(0.0), }).ok();
producer.try_push(Command::UpdateParam {
node: env_id,
param_index: 0,
new_param: Param::new(0.1), }).ok();
producer.try_push(Command::UpdateParam {
node: env_id,
param_index: 1,
new_param: Param::new(0.2), }).ok();
producer.try_push(Command::UpdateParam {
node: env_id,
param_index: 2,
new_param: Param::new(0.7), }).ok();
producer.try_push(Command::UpdateParam {
node: env_id,
param_index: 3,
new_param: Param::new(0.3), }).ok();
producer.try_push(Command::UpdateParam {
node: env_id,
param_index: 4,
new_param: Param::new(0.0), }).ok();
println!(" ✅ Oscillator (440 Hz) → ADSR Envelope → Output");
println!(" 📊 Envelope: A=0.1s, D=0.2s, S=0.7, R=0.3s");
println!();
let spec = WavSpec {
channels: 1,
sample_rate: SAMPLE_RATE as u32,
bits_per_sample: 16,
sample_format: hound::SampleFormat::Int,
};
let mut writer = WavWriter::create("envelope_test.wav", spec)?;
println!("🎬 Rendering audio...");
let notes = vec![
(0.5, 0.5), (2.0, 1.0), (4.0, 1.5), ];
let mut current_note = 0;
let mut gate_on_time: Option<f32> = None;
let mut samples_rendered = 0;
let mut output_buffer = vec![0.0f32; BUFFER_SIZE];
while samples_rendered < TOTAL_SAMPLES {
let current_time = samples_rendered as f32 / SAMPLE_RATE;
if current_note < notes.len() {
let (trigger_time, duration) = notes[current_note];
if gate_on_time.is_none() && current_time >= trigger_time {
producer.try_push(Command::UpdateParam {
node: env_id,
param_index: 4,
new_param: Param::new(1.0), }).ok();
gate_on_time = Some(current_time);
println!(" 🎹 Note {} ON @ {:.2}s", current_note + 1, current_time);
}
if let Some(on_time) = gate_on_time {
if current_time >= on_time + duration {
producer.try_push(Command::UpdateParam {
node: env_id,
param_index: 4,
new_param: Param::new(0.0), }).ok();
println!(" 🎹 Note {} OFF @ {:.2}s", current_note + 1, current_time);
gate_on_time = None;
current_note += 1;
}
}
}
scheduler.process_block(&mut consumer, &mut output_buffer);
for &sample in output_buffer.iter() {
let sample_i16 = (sample.clamp(-1.0, 1.0) * 32767.0) as i16;
writer.write_sample(sample_i16)?;
}
samples_rendered += BUFFER_SIZE;
}
writer.finalize()?;
println!();
println!("✅ Rendering complete!");
println!("📁 Output: envelope_test.wav");
println!();
println!("🎧 What to listen for:");
println!(" - Note 1: Quick attack, short sustain, smooth release");
println!(" - Note 2: Same envelope, longer sustain phase");
println!(" - Note 3: Same envelope, longest sustain phase");
println!(" - Each note fades in (attack), drops (decay), holds (sustain), fades out (release)");
println!();
println!("💡 Try modifying:");
println!(" - Attack time (line 107)");
println!(" - Decay time (line 112)");
println!(" - Sustain level (line 117)");
println!(" - Release time (line 122)");
println!(" - Note timings (line 153)");
Ok(())
}