Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Analog Modeling

Quiver includes tools to model the imperfections and character of analog hardware. These subtle variations are what make vintage synthesizers sound “alive.”

Why Model Analog?

Digital audio is mathematically perfect. Analog audio has:

  • Component tolerance: Resistors/capacitors vary ±1-5%
  • Thermal drift: Parameters change with temperature
  • Nonlinearities: Saturation, clipping, distortion
  • Noise: Thermal noise, power supply hum

These “imperfections” create the warmth and character we love.

Saturation Functions

Quiver provides several saturation models:

Hyperbolic Tangent

Smooth, tube-like warmth:

$$y = \tanh(x \cdot \text{drive})$$

use quiver::analog::saturation;

let output = saturation::tanh_sat(input, drive);
graph LR
    subgraph "tanh Saturation"
        A["Linear<br/>region"] --> B["Soft<br/>compression"]
        B --> C["Limiting"]
    end

Soft Clipping

Adjustable knee:

$$y = \begin{cases} x & |x| < k \ \text{sign}(x) \cdot (k + (1-k) \cdot \tanh(\frac{|x|-k}{1-k})) & |x| \geq k \end{cases}$$

let output = saturation::soft_clip(input, knee);

Asymmetric Saturation

Even harmonics from asymmetry (like tubes):

$$y = \tanh(a \cdot x^+) - \tanh(b \cdot x^-)$$

let output = saturation::asym_sat(input, pos_drive, neg_drive);

Diode Clipping

Hard edges like guitar pedals:

$$y = \begin{cases} \text{threshold} & x > \text{threshold} \ x & |x| \leq \text{threshold} \ -\text{threshold} & x < -\text{threshold} \end{cases}$$

Wave Folding

Complex harmonics through folding:

graph TB
    subgraph "Wavefolding"
        IN[Input Signal] --> FOLD[Fold Function]
        FOLD --> OUT[Rich Harmonics]
    end

$$y = \sin(\text{folds} \cdot \pi \cdot x)$$

Component Modeling

ComponentModel

Simulates real component variation:

use quiver::analog::ComponentModel;

// 1% tolerance like precision resistors
let model = ComponentModel::resistor_1_percent();

// 5% tolerance like standard capacitors
let model = ComponentModel::capacitor_5_percent();

// Apply to a value
let actual_value = model.apply(nominal_value);

Each instance gets a random offset within tolerance, creating unique “units.”

Example: Filter Cutoff Variation

// Two filters with component variation
let filter1 = DiodeLadderFilter::new(44100.0)
    .with_component_model(ComponentModel::capacitor_5_percent());
let filter2 = DiodeLadderFilter::new(44100.0)
    .with_component_model(ComponentModel::capacitor_5_percent());

// filter1 and filter2 will have slightly different cutoffs
// even with identical CV input

Thermal Modeling

ThermalModel

Temperature affects component values:

use quiver::analog::ThermalModel;

let thermal = ThermalModel::new()
    .with_temp_coefficient(0.002)  // 0.2% per °C
    .with_time_constant(30.0);     // 30 second thermal lag

// In processing loop
let temp_factor = thermal.update(ambient_temp, dt);
let adjusted_value = base_value * temp_factor;

This creates slow drift that adds organic movement.

V/Oct Tracking Errors

Real oscillators don’t track pitch perfectly:

use quiver::analog::VoctTrackingModel;

let tracking = VoctTrackingModel::new()
    .with_tracking_error(0.01)      // 1% scale error
    .with_offset_error(0.005);      // 5mV offset

let actual_voct = tracking.apply(intended_voct);

This is why analog synths need tuning!

High Frequency Rolloff

Real circuits have bandwidth limits:

use quiver::analog::HighFrequencyRolloff;

let rolloff = HighFrequencyRolloff::new(44100.0)
    .with_cutoff(15000.0)  // -3dB at 15kHz
    .with_order(2);        // 12dB/octave

let filtered = rolloff.process(sample);

The AnalogVco Module

Combines all effects:

use quiver::analog::AnalogVco;

let vco = AnalogVco::new(44100.0)
    .with_tracking(VoctTrackingModel::default())
    .with_rolloff(HighFrequencyRolloff::default())
    .with_components(ComponentModel::resistor_1_percent())
    .with_saturation(|x| saturation::tanh_sat(x, 1.5));
flowchart LR
    VOCT[V/Oct In] --> TRACK[Tracking<br/>Errors]
    TRACK --> OSC[Oscillator<br/>Core]
    OSC --> SAT[Saturation]
    SAT --> ROLL[HF Rolloff]
    ROLL --> OUT[Output]

    COMP[Component<br/>Model] -.-> OSC
    TEMP[Thermal<br/>Model] -.-> OSC

Crosstalk

Signals bleeding between channels:

use quiver::modules::Crosstalk;

let crosstalk = Crosstalk::new()
    .with_amount(0.01);  // 1% bleed

// Left and right influence each other slightly

Ground Loop Hum

Power supply noise:

use quiver::modules::GroundLoop;

let hum = GroundLoop::new(44100.0)
    .with_frequency(60.0)   // 60Hz (US) or 50Hz (EU)
    .with_amplitude(0.01)   // Very subtle
    .with_harmonics(3);     // Include some harmonics

When to Use Analog Modeling

EffectUse Case
SaturationWarmth, harmonics, preventing clipping
Component toleranceUnique character per voice
Thermal driftSlow organic movement
V/Oct errorsVintage oscillator feel
HF rolloffSoften digital harshness
CrosstalkSubtle stereo interaction
Ground loopVintage authenticity

Performance Considerations

  • Saturation: Cheap (just math)
  • Component models: Cheap (multiply)
  • Thermal: Very cheap (slow update)
  • Rolloff: Medium (filter)
  • Full AnalogVco: Sum of above

Use sparingly for character; most processing should be “clean” digital.