|
| void | prepare (double sample_rate) noexcept |
| void | set_profile (const DcoProfile &profile) noexcept |
| const DcoProfile & | profile () const noexcept |
| void | set_note_hz (double note_hz) noexcept |
| | Command the pitch.
|
| double | note_hz () const noexcept |
| void | set_shape (VaShape shape) noexcept |
| VaShape | shape () const noexcept |
| void | set_pulse_width (double width) noexcept |
| double | pulse_width () const noexcept |
| long long | divider_n () const noexcept |
| | Integer divider N = round(f_clk / f_note_eff).
|
| long long | tuning_word () const noexcept |
| | Fractional tuning word Δ = round(f_note_eff · 2^B / f_clk).
|
| double | realized_hz () const noexcept |
| | The realised frequency the divider actually produces: f_clk / N for integer-N, f_clk · Δ / 2^B (the accurate average) for fractional-N.
|
| double | effective_note_hz () const noexcept |
| | The effective note after footage and fine-tune — the target the divider approximates.
|
| double | detune_cents () const noexcept |
| | Pitch-quantization error in cents: 1200 · log2(realized / f_note_eff).
|
| double | quantization_bound_cents () const noexcept |
| | The worst-case quantization bound in cents for the active scheme — an EXACT upper bound on |detune_cents()|, not a linearization.
|
| int | reset_intervals (std::span< long long > out) const noexcept |
| | Fill out with the next divider reset intervals, in master clocks, from a fresh divider state — a pure, deterministic view of the reset schedule.
|
| void | reset (double phase=0.0) noexcept |
| | Reset the phase and the divider schedule to a known start.
|
| double | phase () const noexcept |
| double | next () noexcept |
| | Generate one sample and advance the divider by one output sample.
|
A divider-clocked oscillator front-end composing the shared VaOscillator.
Typical use: prepare(sample_rate), set_profile(...), set_shape(...), set_note_hz(...), then next() per sample. The derived divider (divider_n / tuning_word), the realised frequency, and the quantization error are exposed so a test can assert the quantization on the integer / rational domain (which survives cross-platform) as well as on the rendered audio.
| double pulp::signal::osc::DcoOscillator::quantization_bound_cents |
( |
| ) |
const |
|
inlinenoexcept |
The worst-case quantization bound in cents for the active scheme — an EXACT upper bound on |detune_cents()|, not a linearization.
The divider quantizes to the nearest integer, so the fractional part of the ideal divider is bounded by ½ LSB; but the resulting error in cents is ASYMMETRIC in log-frequency. Rounding the divider DOWN (realized pitch HIGHER) detunes MORE than rounding it up by the same ½ LSB, because log2(x/(x-½)) exceeds log2((x+½)/x). The bound is therefore the round-down (larger) side, 1200 · log2(x / (x − ½)), where x is the ideal divider: f_clk / f_note (integer-N) or 2^B · f_note / f_clk (fractional-N). The older linear form 865.617/x (= 1200 / (2 ln2 · x), the tangent at ½ LSB) UNDERSTATED exactly the round-down side and was violated by any note that rounds down — so it was not a true bound.
The note-dependence is unchanged: integer-N grows with the note (doubles per octave up, exact at the bottom), fractional-N is largest at LOW notes and shrinks with B.
| int pulp::signal::osc::DcoOscillator::reset_intervals |
( |
std::span< long long > | out | ) |
const |
|
inlinenoexcept |
Fill out with the next divider reset intervals, in master clocks, from a fresh divider state — a pure, deterministic view of the reset schedule.
Integer-N returns N in every slot; fractional-N alternates floor(2^B/Δ) / ceil(2^B/Δ) around the average 2^B/Δ, which is the ±1-clock period jitter in the integer domain. Returns the number written.