use aether_core::{
command::Command,
graph::DspGraph,
param::Param,
scheduler::Scheduler,
BUFFER_SIZE,
};
use aether_nodes::{filter::StateVariableFilter, oscillator::Oscillator};
use hound::{WavSpec, WavWriter};
use ringbuf::{traits::{Producer, Split}, HeapRb};
const SAMPLE_RATE: f32 = 48_000.0;
const DURATION_SECS: f32 = 5.0;
const TOTAL_SAMPLES: usize = (SAMPLE_RATE * DURATION_SECS) as usize;
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("🎵 Filter Sweep Example");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!();
println!("Rendering: Sawtooth wave (220 Hz) → Low-pass filter sweep");
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 filter = Box::new(StateVariableFilter::new());
let filter_id = graph.add_node(filter).expect("Failed to add filter");
graph.connect(osc_id, filter_id, 0);
graph.set_output_node(filter_id);
producer.try_push(Command::AddNode { id: osc_id }).ok();
producer.try_push(Command::AddNode { id: filter_id }).ok();
producer.try_push(Command::Connect { src: osc_id, dst: filter_id, slot: 0 }).ok();
producer.try_push(Command::SetOutputNode { id: filter_id }).ok();
producer.try_push(Command::UpdateParam {
node: osc_id,
param_index: 0,
new_param: Param::new(220.0), }).ok();
producer.try_push(Command::UpdateParam {
node: osc_id,
param_index: 1,
new_param: Param::new(0.5), }).ok();
producer.try_push(Command::UpdateParam {
node: osc_id,
param_index: 2,
new_param: Param::new(1.0), }).ok();
producer.try_push(Command::UpdateParam {
node: filter_id,
param_index: 0,
new_param: Param::new(200.0), }).ok();
producer.try_push(Command::UpdateParam {
node: filter_id,
param_index: 1,
new_param: Param::new(1.0), }).ok();
producer.try_push(Command::UpdateParam {
node: filter_id,
param_index: 2,
new_param: Param::new(0.0), }).ok();
println!(" ✅ Oscillator (220 Hz sawtooth) → Filter (LP) → Output");
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("filter_sweep.wav", spec)?;
println!("🎬 Rendering audio...");
let start_cutoff: f32 = 200.0;
let end_cutoff: f32 = 8000.0;
let sweep_samples = TOTAL_SAMPLES;
let mut samples_rendered = 0;
let mut output_buffer = vec![0.0f32; BUFFER_SIZE];
while samples_rendered < TOTAL_SAMPLES {
let progress = samples_rendered as f32 / sweep_samples as f32;
let log_start = start_cutoff.ln();
let log_end = end_cutoff.ln();
let current_cutoff = (log_start + progress * (log_end - log_start)).exp();
let new_cutoff = Param::new(current_cutoff);
producer.try_push(Command::UpdateParam {
node: filter_id,
param_index: 0,
new_param: new_cutoff,
}).ok();
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;
if samples_rendered % (SAMPLE_RATE as usize) == 0 {
let seconds = samples_rendered / SAMPLE_RATE as usize;
println!(" ⏱️ {} / {} seconds (cutoff: {:.0} Hz)",
seconds, DURATION_SECS as usize, current_cutoff);
}
}
writer.finalize()?;
println!();
println!("🎧 What to listen for:");
println!(" - Sound starts muffled (low cutoff)");
println!(" - Gradually becomes brighter");
println!(" - More harmonics become audible");
println!(" - Ends with full brightness (high cutoff)");
println!();
println!("💡 Try modifying:");
println!(" - Oscillator frequency (line 73)");
println!(" - Sweep range (lines 127-128)");
println!(" - Filter Q/resonance (line 87)");
println!(" - Filter mode: 0=LP, 1=HP, 2=BP (line 92)");
Ok(())
}