devalang_wasm/engine/audio/
synth.rs

1/// Audio synthesis utilities - oscillators and envelopes
2pub mod types;
3
4use std::f32::consts::PI;
5
6/// Generate a single sample from an oscillator
7pub fn oscillator_sample(waveform: &str, frequency: f32, time: f32) -> f32 {
8    let phase = 2.0 * PI * frequency * time;
9
10    match waveform {
11        "sine" => phase.sin(),
12
13        "square" => {
14            if phase.sin() >= 0.0 {
15                1.0
16            } else {
17                -1.0
18            }
19        }
20
21        "saw" => {
22            // Sawtooth: -1 to 1
23            2.0 * (frequency * time - (frequency * time + 0.5).floor())
24        }
25
26        "triangle" => {
27            // Triangle wave
28            (2.0 * (2.0 * (frequency * time).fract() - 1.0)).abs() * 2.0 - 1.0
29        }
30
31        _ => 0.0, // Unknown waveform returns silence
32    }
33}
34
35/// Calculate ADSR envelope value at sample position
36/// Returns amplitude multiplier (0.0 to 1.0)
37pub fn adsr_envelope(
38    sample_index: usize,
39    attack_samples: usize,
40    decay_samples: usize,
41    sustain_samples: usize,
42    release_samples: usize,
43    sustain_level: f32,
44) -> f32 {
45    let attack_end = attack_samples;
46    let decay_end = attack_samples + decay_samples;
47    let sustain_end = attack_samples + decay_samples + sustain_samples;
48    let release_end = attack_samples + decay_samples + sustain_samples + release_samples;
49
50    if sample_index < attack_end && attack_samples > 0 {
51        // Attack phase: 0.0 -> 1.0
52        let progress = sample_index as f32 / attack_samples.max(1) as f32;
53        progress
54    } else if sample_index < decay_end && decay_samples > 0 {
55        // Decay phase: 1.0 -> sustain_level
56        let decay_progress = (sample_index - attack_end) as f32 / decay_samples.max(1) as f32;
57        1.0 - (1.0 - sustain_level) * decay_progress
58    } else if sample_index < sustain_end {
59        // Sustain phase: constant at sustain_level
60        sustain_level
61    } else if sample_index < release_end && release_samples > 0 {
62        // Release phase: sustain_level -> 0.0
63        let release_progress = (sample_index - sustain_end) as f32 / release_samples.max(1) as f32;
64        sustain_level * (1.0 - release_progress).max(0.0)
65    } else {
66        // After release, silence
67        0.0
68    }
69}
70
71/// Convert time in seconds to samples
72pub fn time_to_samples(time_seconds: f32, sample_rate: u32) -> usize {
73    (time_seconds * sample_rate as f32) as usize
74}
75
76/// Convert MIDI note to frequency in Hz
77pub fn midi_to_frequency(midi_note: u8) -> f32 {
78    440.0 * 2.0_f32.powf((midi_note as f32 - 69.0) / 12.0)
79}
80
81#[cfg(test)]
82#[path = "test_synth.rs"]
83mod tests;