Skip to content

Audio

Audio synthesis and processing for procedural sound generation.

Prior Art

Pure Data (Pd) / Max/MSP

  • Dataflow: objects connected by patch cords
  • Two rates: audio rate (~44100 Hz) vs control rate (~64 samples)
  • Hot/cold inlets: leftmost inlet triggers computation
  • Abstractions: patches as reusable objects

SuperCollider

  • SynthDef: define synth as UGen graph, instantiate as Synth
  • UGens: unit generators (oscillators, filters, etc.)
  • Demand rate: generates values on demand (sequencers)
  • Buffers: sample data, wavetables

FAUST

  • Functional DSP: audio as pure functions on streams
  • Block diagram algebra: sequential :, parallel ,, split <:, merge :>
  • Automatic differentiation: for physical modeling

VCV Rack / Modular Synths

  • Modules: self-contained units with inputs/outputs/knobs
  • Polyphony: multiple voices per cable
  • CV/Gate: control voltage for parameters, gates for triggers

Module Structure

ModulePurpose
oscOscillators (sine, saw, square, triangle, noise)
filterFilters (lowpass, highpass, bandpass, biquad)
envelopeEnvelopes (ADSR, AR, LFO)
effectsEffects (reverb, delay, chorus, phaser, flanger, distortion)
granularGranular synthesis
percussionPhysical modeling percussion
physicalKarplus-Strong strings
roomRoom acoustics and convolution reverb
spectralFFT, STFT, spectral processing
vocoderVocoder and filterbank analysis
graphAudio graph / signal chain
patchSynthesizer patch system
wavWAV file import/export
midiMIDI parsing and utilities

Basic Oscillators

rust
use rhizome_resin_audio::{SineOsc, SawOsc, SquareOsc, TriangleOsc, NoiseOsc};

// Create oscillators at sample rate
let sample_rate = 44100.0;

let mut sine = SineOsc::new(440.0, sample_rate);
let mut saw = SawOsc::new(440.0, sample_rate);
let mut square = SquareOsc::new(440.0, sample_rate);
let mut triangle = TriangleOsc::new(440.0, sample_rate);
let mut noise = NoiseOsc::white();

// Generate samples
let sample = sine.tick();
sine.set_frequency(880.0);  // Change frequency

// Generate buffer
let buffer: Vec<f32> = (0..44100).map(|_| sine.tick()).collect();

Filters

rust
use rhizome_resin_audio::{LowPassFilter, HighPassFilter, BiquadFilter, BiquadType};

// Simple one-pole filters
let mut lpf = LowPassFilter::new(1000.0, sample_rate);
let mut hpf = HighPassFilter::new(100.0, sample_rate);

let output = lpf.process(input);

// Biquad filters (more flexible)
let mut biquad = BiquadFilter::new(BiquadType::LowPass, 1000.0, 0.707, sample_rate);
biquad.set_frequency(2000.0);
biquad.set_q(2.0);  // Resonance

let output = biquad.process(input);

Biquad Types

TypeParametersUse
LowPasscutoff, QRemove highs
HighPasscutoff, QRemove lows
BandPasscenter, QIsolate band
Notchcenter, QRemove band
Peakcenter, Q, gainEQ boost/cut
LowShelfcutoff, gainBass boost/cut
HighShelfcutoff, gainTreble boost/cut

Envelopes

ADSR

rust
use rhizome_resin_audio::{Adsr, AdsrConfig};

let config = AdsrConfig {
    attack: 0.01,   // 10ms attack
    decay: 0.1,     // 100ms decay
    sustain: 0.7,   // 70% sustain level
    release: 0.3,   // 300ms release
};

let mut env = Adsr::new(config, sample_rate);

// Trigger envelope
env.gate(true);   // Note on
// ... process samples ...
env.gate(false);  // Note off

// Get envelope value
let amplitude = env.tick();
let output = oscillator.tick() * amplitude;

LFO

rust
use rhizome_resin_audio::{Lfo, LfoShape};

let mut lfo = Lfo::new(5.0, sample_rate);  // 5 Hz
lfo.set_shape(LfoShape::Sine);
lfo.set_shape(LfoShape::Triangle);
lfo.set_shape(LfoShape::Square);
lfo.set_shape(LfoShape::SawUp);
lfo.set_shape(LfoShape::SawDown);
lfo.set_shape(LfoShape::Random);

// Use for modulation
let mod_value = lfo.tick();  // -1.0 to 1.0
filter.set_frequency(base_freq + mod_value * mod_depth);

Effects

Delay

rust
use rhizome_resin_audio::{Delay, FeedbackDelay};

// Simple delay
let mut delay = Delay::new(0.5, sample_rate);  // 500ms delay
let output = delay.process(input);

// Feedback delay
let mut fb_delay = FeedbackDelay::new(0.25, 0.6, sample_rate);  // 250ms, 60% feedback
let output = fb_delay.process(input);

Reverb

rust
use rhizome_resin_audio::{Reverb, ReverbConfig};

let config = ReverbConfig {
    room_size: 0.8,
    damping: 0.5,
    wet: 0.3,
    dry: 0.7,
    width: 1.0,
};

let mut reverb = Reverb::new(config, sample_rate);
let (left, right) = reverb.process_stereo(input_left, input_right);

Modulation Effects

rust
use rhizome_resin_audio::{Chorus, Phaser, Flanger};

// Chorus
let mut chorus = Chorus::new(sample_rate);
chorus.set_rate(1.5);   // LFO rate
chorus.set_depth(0.5);  // Modulation depth
chorus.set_mix(0.5);

// Phaser
let mut phaser = Phaser::new(sample_rate);
phaser.set_rate(0.5);
phaser.set_depth(0.8);
phaser.set_stages(4);  // Number of allpass stages

// Flanger
let mut flanger = Flanger::new(sample_rate);
flanger.set_rate(0.3);
flanger.set_depth(0.7);
flanger.set_feedback(0.5);

Distortion

rust
use rhizome_resin_audio::{Distortion, DistortionType};

let mut dist = Distortion::new(DistortionType::SoftClip);
dist.set_drive(2.0);

// Types: SoftClip, HardClip, Fold, Bitcrush, Tube
let output = dist.process(input);

Compressor

rust
use rhizome_resin_audio::{Compressor, CompressorConfig};

let config = CompressorConfig {
    threshold: -20.0,  // dB
    ratio: 4.0,        // 4:1 compression
    attack: 0.01,      // 10ms
    release: 0.1,      // 100ms
    makeup_gain: 6.0,  // dB
};

let mut comp = Compressor::new(config, sample_rate);
let output = comp.process(input);

FM Synthesis

rust
use rhizome_resin_audio::{FmOsc, FmSynth, FmAlgorithm};

// Simple 2-operator FM
let mut fm = FmOsc::new(sample_rate);
fm.set_carrier_freq(440.0);
fm.set_mod_freq(880.0);      // 2:1 ratio
fm.set_mod_index(2.0);       // Modulation depth

let sample = fm.tick();

// Multi-operator FM synth
let mut synth = FmSynth::new(sample_rate);
synth.set_algorithm(FmAlgorithm::Stack4);  // 4 operators in series
synth.set_ratios(&[1.0, 2.0, 3.0, 4.0]);
synth.set_levels(&[1.0, 0.5, 0.3, 0.2]);

// Presets
let electric_piano = FmSynth::electric_piano(sample_rate);
let bass = FmSynth::bass(sample_rate);
let brass = FmSynth::brass(sample_rate);

Wavetable Synthesis

rust
use rhizome_resin_audio::{Wavetable, WavetableOsc, WavetableBank};

// Create wavetable from function
let sine_table = Wavetable::from_fn(2048, |phase| (phase * 2.0 * PI).sin());

// Or use generators
let saw_table = Wavetable::saw(2048);
let square_table = Wavetable::square(2048);
let supersaw_table = Wavetable::supersaw(2048, 7, 0.1);  // 7 saws, 10% detune

// Oscillator
let mut osc = WavetableOsc::new(&sine_table, sample_rate);
osc.set_frequency(440.0);
let sample = osc.tick();

// Wavetable bank (morphing between tables)
let mut bank = WavetableBank::new(sample_rate);
bank.add_table(sine_table);
bank.add_table(saw_table);
bank.add_table(square_table);
bank.set_position(0.5);  // Morph between tables

let sample = bank.tick();

Granular Synthesis

rust
use rhizome_resin_audio::{GrainCloud, GrainConfig, GrainScheduler};

// Load or create source buffer
let buffer: Vec<f32> = load_sample("voice.wav");

let mut cloud = GrainCloud::new(buffer, sample_rate);

// Configure grains
cloud.set_grain_size(50.0);      // 50ms grains
cloud.set_density(20.0);         // 20 grains/second
cloud.set_position(0.5);         // Middle of buffer
cloud.set_position_spread(0.1);  // Random variation
cloud.set_pitch(1.0);            // Original pitch
cloud.set_pitch_spread(0.1);     // Random pitch variation

// Generate audio
let sample = cloud.tick();

// Scheduled grains
let mut scheduler = GrainScheduler::new(sample_rate);
scheduler.schedule_grain(start_time, config);

Physical Modeling

Karplus-Strong Strings

rust
use rhizome_resin_audio::{KarplusStrong, PluckConfig, PolyStrings};

// Single string
let config = PluckConfig {
    frequency: 440.0,
    decay: 0.996,
    brightness: 0.5,
    pluck_position: 0.25,
};

let mut string = KarplusStrong::new(config, sample_rate);
string.pluck(0.8);  // Pluck with velocity 0.8

let sample = string.tick();

// Polyphonic strings
let mut strings = PolyStrings::new(8, sample_rate);  // 8 voices
strings.note_on(60, 0.8);  // MIDI note, velocity
strings.note_off(60);

let sample = strings.tick();

Percussion (Modal Synthesis)

rust
use rhizome_resin_audio::percussion::{Membrane, Bar, Plate, MembraneConfig};

// Drum membrane
let config = MembraneConfig::snare();  // Preset
let mut drum = Membrane::new(config, sample_rate);
drum.strike(0.8);

// Bar (xylophone, marimba)
let mut bar = Bar::xylophone(440.0, sample_rate);
bar.strike(0.7);

// Plate (cymbal, bell)
let mut cymbal = Plate::cymbal(sample_rate);
let mut bell = Plate::bell(440.0, sample_rate);
cymbal.strike(0.9);

// Generate samples
let sample = drum.process() + cymbal.process();

Room Acoustics

Convolution Reverb

rust
use rhizome_resin_audio::{ConvolutionReverb, generate_room_ir};

// Load impulse response
let ir = load_wav("hall.wav");
let mut reverb = ConvolutionReverb::new(&ir, sample_rate);

// Or generate synthetic IR
let ir = generate_room_ir(room_size, rt60, sample_rate);

// Process
let output = reverb.process(input);

Room Simulation

rust
use rhizome_resin_audio::{RoomAcoustics, RoomConfig};

let config = RoomConfig {
    dimensions: Vec3::new(10.0, 3.0, 8.0),  // Room size in meters
    absorption: 0.3,                         // Wall absorption
    source: Vec3::new(2.0, 1.5, 4.0),       // Sound source
    listener: Vec3::new(8.0, 1.5, 4.0),     // Listener
};

let room = RoomAcoustics::new(config);

// Calculate early reflections
let reflections = room.calculate_early_reflections(order: 3);

// Generate impulse response
let ir = room.generate_ir(sample_rate, length_seconds: 2.0);

Spectral Processing

FFT

rust
use rhizome_resin_audio::{fft, ifft, Complex, window};

// Apply window
let windowed: Vec<f32> = window::hann(&samples);

// Forward FFT
let spectrum: Vec<Complex> = fft(&windowed);

// Manipulate spectrum...
// spectrum[i].re, spectrum[i].im

// Inverse FFT
let reconstructed: Vec<f32> = ifft(&spectrum);

STFT (Short-Time Fourier Transform)

rust
use rhizome_resin_audio::{stft, istft, StftConfig};

let config = StftConfig {
    fft_size: 2048,
    hop_size: 512,
    window: WindowType::Hann,
};

// Analyze
let result = stft(&samples, &config);

// result.frames[frame_index][bin_index] is Complex

// Resynthesize
let output = istft(&result);

Vocoder

rust
use rhizome_resin_audio::{Vocoder, FilterbankVocoder};

// FFT-based vocoder
let mut vocoder = Vocoder::new(2048, sample_rate);

// carrier = synth sound, modulator = voice
let output = vocoder.process(carrier, modulator);

// Filterbank vocoder (classic analog style)
let mut fb_vocoder = FilterbankVocoder::new(16, sample_rate);  // 16 bands
let output = fb_vocoder.process(carrier, modulator);

Audio Graph

rust
use rhizome_resin_audio::{Chain, Mixer, AudioGraph};

// Simple chain
let mut chain = Chain::new(sample_rate);
chain.add(SineOsc::new(440.0, sample_rate));
chain.add(LowPassFilter::new(2000.0, sample_rate));
chain.add(Reverb::default());

let output = chain.tick();

// Mixer
let mut mixer = Mixer::new(sample_rate);
mixer.add_channel(osc1, 0.5);  // 50% volume
mixer.add_channel(osc2, 0.3);  // 30% volume

let output = mixer.tick();

Synthesizer Patches

rust
use rhizome_resin_audio::{SynthPatch, PatchBank, ModRouting};

// Create patch
let mut patch = SynthPatch::new("Lead");
patch.set_osc1(OscType::Saw);
patch.set_osc2(OscType::Square);
patch.set_filter_cutoff(2000.0);
patch.set_filter_resonance(0.5);
patch.set_envelope(AdsrConfig { ... });

// Modulation routing
patch.add_mod_routing(ModRouting {
    source: ModSource::Lfo1,
    destination: ModDest::FilterCutoff,
    amount: 0.3,
});

// Save/load patches
let bank = PatchBank::new();
bank.add(patch);
bank.save("my_patches.json");
let bank = PatchBank::load("my_patches.json");

MIDI

rust
use rhizome_resin_audio::midi::{MidiMessage, note_to_freq, freq_to_note};

// Parse MIDI message
let msg = MidiMessage::parse(&[0x90, 60, 100]);
match msg {
    MidiMessage::NoteOn { channel, note, velocity } => {
        let freq = note_to_freq(note);
        synth.note_on(freq, velocity as f32 / 127.0);
    }
    MidiMessage::NoteOff { channel, note, .. } => {
        synth.note_off(note);
    }
    MidiMessage::ControlChange { channel, controller, value } => {
        // Handle CC
    }
    _ => {}
}

// Utilities
let freq = note_to_freq(69);  // A4 = 440 Hz
let note = freq_to_note(440.0);  // 69

WAV Files

rust
use rhizome_resin_audio::{WavFile, WavFormat};

// Load
let wav = WavFile::load("sample.wav")?;
let samples = wav.samples();
let sample_rate = wav.sample_rate();

// Save
let wav = WavFile::new(samples, sample_rate, WavFormat::PCM16);
wav.save("output.wav")?;

// Supported formats
WavFormat::PCM8
WavFormat::PCM16
WavFormat::PCM24
WavFormat::PCM32
WavFormat::Float32

Example: Complete Synth

rust
use rhizome_resin_audio::*;

// Build a simple subtractive synth
let sample_rate = 44100.0;

let mut osc1 = SawOsc::new(440.0, sample_rate);
let mut osc2 = SawOsc::new(440.0 * 1.01, sample_rate);  // Slight detune
let mut filter = BiquadFilter::new(BiquadType::LowPass, 2000.0, 2.0, sample_rate);
let mut env = Adsr::new(AdsrConfig::default(), sample_rate);
let mut lfo = Lfo::new(5.0, sample_rate);

// Process
env.gate(true);
let mut output = Vec::new();

for _ in 0..44100 {
    // Oscillators
    let osc_out = osc1.tick() + osc2.tick() * 0.5;

    // LFO modulates filter
    let lfo_val = lfo.tick();
    filter.set_frequency(2000.0 + lfo_val * 500.0);

    // Filter
    let filtered = filter.process(osc_out);

    // Envelope
    let amp = env.tick();

    output.push(filtered * amp);
}