OSC-VCO: the deterministic signal path of a circuit-flavored analog voltage- controlled oscillator, built by composing the bandlimited core (VaOscillator — itself PhaseAccumulator + the BLEP/BLAMP kernels) with the memoryless and LTI stages that give an analog VCO its character. More...
#include "blep.hpp"#include "phase.hpp"#include "va.hpp"#include <pulp/signal/dc_blocker.hpp>#include <algorithm>#include <array>#include <cmath>#include <cstddef>#include <cstdint>#include <limits>#include <numbers>Go to the source code of this file.
Classes | |
| struct | pulp::signal::osc::VcoTuning |
| The analog exponential pitch converter, as a pure control-voltage → frequency map. More... | |
| struct | pulp::signal::osc::WaveshaperParams |
| A per-shape lumped memoryless nonlinearity — the "analog character" the output stage imprints. More... | |
| class | pulp::signal::osc::VcoOscillator |
| Virtual-analog VCO over the static shapes, with a circuit-flavored deterministic path. More... | |
Namespaces | |
| namespace | pulp |
| namespace | pulp::signal |
| namespace | pulp::signal::osc |
OSC-VCO: the deterministic signal path of a circuit-flavored analog voltage- controlled oscillator, built by composing the bandlimited core (VaOscillator — itself PhaseAccumulator + the BLEP/BLAMP kernels) with the memoryless and LTI stages that give an analog VCO its character.
── What this is, and what it is NOT ──────────────────────────────────────
This is the deterministic front-to-back path only: a pitch-control front end, the bandlimited core shape, an integrator-leak "bow" on the ramp, a per-shape lumped waveshaper, a level-vs-pitch curve, and an output DC-blocking (AC-coupling) stage. Every one of those is a hand-set parameter with a physically-motivated meaning, and every one DEFAULTS TO ITS NEUTRAL VALUE, so a default-constructed OSC-VCO is bit-for-bit a VaOscillator (see the null in test_osc_vco.cpp).
It also carries the VCO's two stochastic pitch sources — drift and jitter — as a seeded frequency modulation of the per-sample increment before it reaches the bandlimited core. They are two distinct processes:
Both are converted from cents to a frequency multiplier by 2^(cents/1200) and injected at ONE site, pitch_noise_factor(), which multiplies the increment. When both depths are 0 that factor is exactly 1.0 (an early-out that advances no noise state), so a default-constructed OSC-VCO stays bit-for- bit a VaOscillator (see the master null in test_osc_vco.cpp). The source is deterministic and seed-reproducible (set_seed; no random_device, no clock) — the same seed and inputs yield bit-identical output, which the suite gates. The offline fitting tool and data-driven profiles remain out of scope: this is the ENGINE that has the parameters, hand-set, not the estimator that recovers them.
── Circuit-accuracy of the correction, honestly ─────────────────────────
The bow and the waveshaper are applied as memoryless maps of the core's already-bandlimited output. That is the standard analog-modeling composition, and it is honest about one thing: a memoryless nonlinearity on a bandlimited signal reintroduces some aliasing (the analog stages alias a little too), and the bow's reshape breaks the ramp's slope at the wrap without a fresh BLAMP. The core's own discontinuity correction is intact and measured with the character neutral; the bow is therefore gated for level/DC correctness, not for alias rejection. A circuit-accurate wrap correction for a bowed ramp would need the core's correction loop to accept a caller-supplied shape (value + slope + internal thresholds) rather than its four fixed shapes — an extraction worth doing if a later increment needs it, and called out rather than done by copying the loop here.
RT contract: no allocation, no locks, no I/O on the per-sample path. double throughout, matching the core; a float caller narrows once on store. The bow's exp and the waveshaper's tanh are libcalls, like the core's sin — none of them allocates, locks, or blocks.