🎵 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 | f² | 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