Skip to content

🎵 05_07_02_oscillator - Oscillator Atoms

Overview

Oscillator atoms are fundamental signal generators for audio synthesis, testing, and modulation. This subsystem provides high-quality oscillators covering all essential waveforms and noise types.

Status: ✅ Complete (4/4 core oscillators) Completion: SineOscillator ✅ | WavetableOscillator ✅ | PolyBLEPOscillator ✅ | NoiseGenerator ✅


Implemented Oscillators

✅ SineOscillator - Pure Sine Wave Generation

File: include/sine_oscillator.h

High-quality sine wave generator with three algorithms for quality/performance tradeoff.

Generation Methods: - DIRECT - std::sin() calculation (highest quality, moderate CPU) - QUADRATURE - Coupled oscillator equations (good quality, low CPU) - WAVETABLE - Table lookup with interpolation (lowest CPU)

Key Features: - Phase-accurate frequency control - Phase modulation support (FM synthesis) - LFO wrapper class included - Multi-channel independent phases - Template-based (float/double)

Performance: ~3-15 cycles per sample (method dependent)

Usage Example:

#include "sine_oscillator.h"

using namespace audiolab;

// High-quality test tone
SineOscillator<float> osc;
osc.set_sample_rate(44100.0f);
osc.set_method(SineMethod::DIRECT);
osc.set_frequency(1000.0f);  // 1kHz
osc.set_amplitude(0.5f);     // -6dB

AudioBuffer<float> buffer(2, 512);
osc.process(buffer);

// Low-CPU LFO
LFO<float> lfo;
lfo.set_sample_rate(44100.0f);
lfo.set_frequency(2.0f);     // 2Hz vibrato
float mod = lfo.generate();

// FM synthesis
SineOscillator<float> carrier, modulator;
carrier.set_frequency(440.0f);
modulator.set_frequency(220.0f);

for (size_t i = 0; i < buffer_size; ++i) {
    float mod = modulator.generate() * 100.0f;  // 100Hz deviation
    output[i] = carrier.generate_pm(mod);       // Phase modulation
}

✅ WavetableOscillator - Arbitrary Waveform Synthesis

File: include/wavetable_oscillator.h

Flexible wavetable oscillator supporting custom waveforms and morphing.

Interpolation Methods: - NONE - Nearest neighbor (fastest, lowest quality) - LINEAR - Linear interpolation (fast, good quality) - CUBIC - Cubic interpolation (moderate CPU, better quality) - HERMITE - Hermite interpolation (more CPU, best quality)

Key Features: - Load arbitrary waveforms - Multi-table morphing (crossfade between wavetables) - Built-in standard waveforms (saw, square, triangle, sine, pulse) - Variable pulse width - High-quality interpolation options

Performance: ~10-20 cycles per sample (interpolation dependent)

Usage Example:

#include "wavetable_oscillator.h"

using namespace audiolab;

// Custom wavetable
WavetableOscillator<float> osc;
osc.set_sample_rate(44100.0f);
osc.set_interpolation(InterpolationMode::HERMITE);

// Load custom waveform
std::vector<float> custom_wave = create_my_waveform();
osc.set_wavetable(custom_wave);

// Or use built-in waveform
osc.load_sawtooth(2048);  // 2048 sample table

osc.set_frequency(220.0f);
osc.set_amplitude(0.8f);

AudioBuffer<float> buffer(2, 512);
osc.process(buffer);

// Wavetable morphing (Serum/Vital style)
std::vector<std::vector<float>> wavetable_bank = {
    create_sine_table(),
    create_saw_table(),
    create_square_table()
};

osc.load_wavetable_bank(wavetable_bank);
osc.set_morph_position(0.5f);  // Morph between sine and saw

Built-in Waveforms: - Sawtooth - Square - Triangle - Sine - Pulse (variable width)


✅ PolyBLEPOscillator - Band-Limited Waveforms

File: include/polyblep_oscillator.h

Anti-aliased classic waveforms using PolyBLEP (Polynomial Band-Limited Step) algorithm.

Waveforms: - SAWTOOTH - All harmonics, no aliasing - SQUARE - Odd harmonics, 50% duty cycle - TRIANGLE - Odd harmonics, -12dB/octave - PULSE - Variable width (PWM synthesis)

Key Features: - PolyBLEP anti-aliasing (eliminates harmonic aliasing) - Variable pulse width for PWM - Hard sync support (SyncOscillator wrapper) - Clean at all frequencies (up to Nyquist/2) - Vintage analog synth sound

Performance: ~15-25 cycles per sample

Usage Example:

#include "polyblep_oscillator.h"

using namespace audiolab;

// Classic sawtooth (no aliasing!)
PolyBLEPOscillator<float> saw;
saw.set_sample_rate(44100.0f);
saw.set_waveform(PolyBLEPWaveform::SAWTOOTH);
saw.set_frequency(110.0f);  // A2
saw.set_amplitude(0.8f);

AudioBuffer<float> buffer(2, 512);
saw.process(buffer);

// Pulse width modulation (PWM)
PolyBLEPOscillator<float> pwm;
pwm.set_waveform(PolyBLEPWaveform::PULSE);
pwm.set_frequency(220.0f);

LFO<float> lfo;
lfo.set_frequency(0.5f);  // 0.5Hz LFO

for (size_t i = 0; i < buffer_size; ++i) {
    float width = 0.5f + 0.4f * lfo.generate();  // 10-90% width
    pwm.set_pulse_width(width);
    output[i] = pwm.generate();
}

// Hard sync synthesis
SyncOscillator<float> sync;
sync.set_sample_rate(44100.0f);
sync.set_master_frequency(110.0f);   // A2 master
sync.set_slave_frequency(440.0f);     // A4 slave (4x)
sync.set_slave_waveform(PolyBLEPWaveform::SAWTOOTH);

// Animate slave frequency for sync sweep
for (size_t i = 0; i < buffer_size; ++i) {
    float freq = 200.0f + 1000.0f * lfo.generate();
    sync.set_slave_frequency(freq);
    output[i] = sync.generate();
}

PolyBLEP Algorithm: - Detects discontinuities in waveform - Applies polynomial smoothing at transitions - Result: band-limited output without aliasing - Much lower CPU than traditional BLEP/BLAMP methods


✅ NoiseGenerator - Multi-Color Noise

File: include/noise_generator.h

Versatile noise generator with multiple spectral characteristics.

Noise Types: - WHITE - Flat spectrum (all frequencies equal) - PINK - 1/f spectrum, -3dB/octave (natural, balanced) - BROWN - 1/f² spectrum, -6dB/octave (warm, rumble) - BLUE - f spectrum, +3dB/octave (bright) - VIOLET - f² spectrum, +6dB/octave (very bright)

Key Features: - High-quality random number generation (Mersenne Twister) - Deterministic mode (seedable for reproducibility) - BurstNoise wrapper for gated noise - Multi-channel independent streams - Voss-McCartney algorithm for pink noise

Performance: ~5-20 cycles per sample (type dependent)

Usage Example:

#include "noise_generator.h"

using namespace audiolab;

// White noise for sound effects
NoiseGenerator<float> white;
white.set_sample_rate(44100.0f);
white.set_type(NoiseType::WHITE);
white.set_amplitude(0.3f);

AudioBuffer<float> buffer(2, 512);
white.process(buffer);

// Pink noise for natural ambience
NoiseGenerator<float> pink;
pink.set_type(NoiseType::PINK);
pink.set_amplitude(0.5f);

// Deterministic noise (reproducible)
white.seed(12345);  // Same sequence every time

// Burst noise (gated)
BurstNoise<float> burst;
burst.set_sample_rate(44100.0f);
burst.set_type(NoiseType::WHITE);
burst.set_burst_length(0.05f);    // 50ms bursts
burst.set_burst_interval(0.2f);   // Every 200ms

for (size_t i = 0; i < buffer_size; ++i) {
    output[i] = burst.generate();
}

Noise Color Characteristics:

Type Spectrum Perception Use Cases
White Flat Bright, harsh Testing, digital effects
Pink 1/f Natural, balanced Ambience, realistic sounds
Brown 1/f² Warm, rumble Bass effects, ocean sounds
Blue f Bright, airy High-frequency effects
Violet Very bright Special effects, hiss

Build Instructions

Prerequisites

# Install Catch2 for testing
C:/vcpkg/vcpkg.exe install catch2:x64-windows

# CMake 3.15+
# MSVC 2019+, GCC 9+, or Clang 10+

Compile and Test

# From 05_07_02_oscillator directory
mkdir build
cd build

# Configure
cmake ..

# Build
cmake --build . --config Release

# Run tests
ctest -C Release --verbose

# Or run specific test
./Release/test_sine_oscillator.exe

Integration

Header-only library:

// In your CMakeLists.txt
include_directories(
    "path/to/05_07_02_oscillator/include"
    "path/to/05_07_00_atom_interface/include"
)

// In your code
#include "sine_oscillator.h"
#include "wavetable_oscillator.h"
#include "polyblep_oscillator.h"
#include "noise_generator.h"

using namespace audiolab;

What You Can Build

✅ Synthesizers

Subtractive Synthesis:

// Classic analog synth voice
PolyBLEPOscillator<float> osc1, osc2;
osc1.set_waveform(PolyBLEPWaveform::SAWTOOTH);
osc2.set_waveform(PolyBLEPWaveform::PULSE);

// Mix oscillators
float mixed = osc1.generate() * 0.5f + osc2.generate() * 0.5f;

// Filter through ladder
LadderFilter<float> filter;
float output = filter.process_sample(mixed);

FM Synthesis:

// Classic FM (DX7 style)
SineOscillator<float> carrier, modulator;
carrier.set_frequency(440.0f);
modulator.set_frequency(880.0f);  // 2x carrier

float mod_depth = 300.0f;
float mod = modulator.generate() * mod_depth;
float output = carrier.generate_pm(mod);

Wavetable Synthesis:

// Modern wavetable synth (Serum/Vital style)
WavetableOscillator<float> wt_osc;
wt_osc.load_wavetable_bank(custom_tables);

// Animate wavetable position
float morph = 0.5f + 0.5f * lfo.generate();
wt_osc.set_morph_position(morph);

✅ Sound Design

White Noise Generator:

NoiseGenerator<float> noise;
noise.set_type(NoiseType::WHITE);

// Shape with filter
BiquadFilter<float> filter;
filter.set_type(FilterType::BANDPASS);
filter.set_cutoff(2000.0f);

float shaped_noise = filter.process_sample(noise.generate());

Wind/Ocean Sounds:

NoiseGenerator<float> brown_noise;
brown_noise.set_type(NoiseType::BROWN);

// Modulate amplitude for gusts
LFO<float> wind_lfo;
wind_lfo.set_frequency(0.2f);

float wind = brown_noise.generate() * (0.3f + 0.7f * wind_lfo.generate());

✅ Test Signals

Sweep Generator:

SineOscillator<float> sweep;
sweep.set_sample_rate(44100.0f);

for (size_t i = 0; i < buffer_size; ++i) {
    float t = float(i) / float(buffer_size);
    float freq = 20.0f + 20000.0f * t;  // 20Hz to 20kHz
    sweep.set_frequency(freq);
    output[i] = sweep.generate();
}

Impulse Response Testing:

NoiseGenerator<float> noise;
noise.set_type(NoiseType::WHITE);

// Generate impulse
std::vector<float> ir(sample_rate * 2);  // 2 seconds
noise.process(buffer);

// Send through system under test
// Analyze frequency response


Performance Comparison

Oscillator CPU/sample Memory/ch Quality Best For
Sine (QUADRATURE) ~3 cycles 8 bytes Good LFOs, modulation
Sine (DIRECT) ~15 cycles 4 bytes Excellent Test tones, FM carrier
Sine (WAVETABLE) ~8 cycles 4 bytes Very Good General use
Wavetable (LINEAR) ~10 cycles 4 bytes Good Fast playback
Wavetable (HERMITE) ~20 cycles 4 bytes Excellent High quality
PolyBLEP ~20 cycles 12 bytes Excellent Virtual analog
Noise (WHITE) ~5 cycles 0 bytes N/A Effects, testing
Noise (PINK) ~15 cycles 64 bytes N/A Natural sounds

Design Philosophy

Signal Purity

Each oscillator prioritizes: - Frequency accuracy - Phase-perfect tracking - Spectral cleanliness - Minimal/no aliasing (PolyBLEP) - Low distortion - High-quality algorithms - Stable output - No DC drift, numerical stability

Flexibility

  • Template-based - Float/double support
  • Multi-algorithm - Choose quality vs performance
  • Modulation-ready - All parameters animatable
  • Composable - Easy to combine (FM, sync, etc.)

Performance

  • Header-only - Inline optimization
  • Minimal state - Cache-friendly
  • SIMD-ready - Vectorization potential
  • Real-time safe - No allocations in generate()

Advanced Techniques

FM Synthesis (2-Operator)

SineOscillator<float> carrier, modulator;
carrier.set_frequency(220.0f);    // A3
modulator.set_frequency(440.0f);   // A4 (harmonic)

ADSR<float> mod_env;
float mod_index = 5.0f;  // Modulation depth

for (sample in buffer) {
    float env = mod_env.generate();
    float mod = modulator.generate() * carrier.get_frequency() * mod_index * env;
    sample = carrier.generate_pm(mod);
}

Wavetable Morphing with Envelope

WavetableOscillator<float> osc;
osc.load_wavetable_bank({sine, triangle, saw, square});

ADSR<float> morph_env;

for (sample in buffer) {
    float env = morph_env.generate();
    osc.set_morph_position(env);  // Morph through tables
    sample = osc.generate();
}

PWM with LFO

PolyBLEPOscillator<float> pwm;
pwm.set_waveform(PolyBLEPWaveform::PULSE);
pwm.set_frequency(110.0f);

LFO<float> lfo;
lfo.set_frequency(0.3f);  // Slow modulation

for (sample in buffer) {
    float width = 0.5f + 0.4f * lfo.generate();  // 10-90%
    pwm.set_pulse_width(width);
    sample = pwm.generate();
}

Filtered Noise (Subtractive)

NoiseGenerator<float> noise;
noise.set_type(NoiseType::PINK);

SVFFilter<float> filter;
filter.set_mode(SVFMode::LOWPASS);
filter.set_cutoff(500.0f);
filter.set_resonance(5.0f);

for (sample in buffer) {
    AudioBuffer<float> buf(1, 1);
    buf.get_channel_data(0)[0] = noise.generate();
    filter.process(buf);
    sample = buf.get_channel_data(0)[0];
}

References

Books

  • "Designing Sound" - Andy Farnell
  • "The Computer Music Tutorial" - Curtis Roads
  • "Designing Software Synthesizer Plug-Ins in C++" - Will Pirkle

Papers

  • "Alias-Free Digital Synthesis of Classic Analog Waveforms" - Välimäki, Huovilainen (2007)
  • "PolyBLEP" - Tale (KVR forum, 2010)
  • "The Voss algorithm" - Voss & Clarke (1978)

Online Resources


Troubleshooting

Q: Oscillator sounds aliased/harsh - A: Use PolyBLEPOscillator for classic waveforms - For wavetable, use HERMITE interpolation - Ensure frequency < Nyquist/2

Q: Frequency drift over time - A: For SineOscillator QUADRATURE, use DIRECT method for critical applications - Wavetable/PolyBLEP are phase-accurate

Q: FM sounds wrong - A: Use generate_pm() not set_frequency() - Modulation depth = mod_amplitude × carrier_frequency - Ensure modulator frequency is stable

Q: Noise sounds repetitive - A: Use different seeds for multiple generators - Pink/Brown noise has longer period than White

Q: Performance issues - A: Choose fastest algorithm for use case: - LFO: Sine QUADRATURE - Audio: Sine WAVETABLE or PolyBLEP - Wavetable: LINEAR interpolation - Noise: WHITE type


Status: ✅ 100% COMPLETE - All 4 core oscillators implemented Maintainer: AudioLab Core Team Last Updated: 2025-10-10