Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
pulp::signal::osc::DcoOscillator Class Reference

A divider-clocked oscillator front-end composing the shared VaOscillator. More...

#include <dco.hpp>

Public Member Functions

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.

Detailed Description

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.

Member Function Documentation

◆ detune_cents()

double pulp::signal::osc::DcoOscillator::detune_cents ( ) const
inlinenoexcept

Pitch-quantization error in cents: 1200 · log2(realized / f_note_eff).

For BOTH schemes this obeys |detune| <= quantization_bound_cents() whenever the divider is not clamped — integer-N: f_note_eff < 2·f_clk (the N ≥ 1 lower clamp); fractional-N: additionally below the Δ ≤ 2^B−1 upper clamp, i.e. roughly f_note_eff < f_clk. Both hold for any audio note well below the master clock; quantization_bound_cents() returns 0 (no bound) in the clamp regimes rather than a false one.

◆ divider_n()

long long pulp::signal::osc::DcoOscillator::divider_n ( ) const
inlinenoexcept

Integer divider N = round(f_clk / f_note_eff).

0 for the fractional scheme (use tuning_word() there).

◆ effective_note_hz()

double pulp::signal::osc::DcoOscillator::effective_note_hz ( ) const
inlinenoexcept

The effective note after footage and fine-tune — the target the divider approximates.

◆ next()

double pulp::signal::osc::DcoOscillator::next ( )
inlinenoexcept

Generate one sample and advance the divider by one output sample.

◆ note_hz()

double pulp::signal::osc::DcoOscillator::note_hz ( ) const
inlinenoexcept

◆ phase()

double pulp::signal::osc::DcoOscillator::phase ( ) const
inlinenoexcept

◆ prepare()

void pulp::signal::osc::DcoOscillator::prepare ( double sample_rate)
inlinenoexcept

◆ profile()

const DcoProfile & pulp::signal::osc::DcoOscillator::profile ( ) const
inlinenoexcept

◆ pulse_width()

double pulp::signal::osc::DcoOscillator::pulse_width ( ) const
inlinenoexcept

◆ quantization_bound_cents()

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.

◆ realized_hz()

double pulp::signal::osc::DcoOscillator::realized_hz ( ) const
inlinenoexcept

The realised frequency the divider actually produces: f_clk / N for integer-N, f_clk · Δ / 2^B (the accurate average) for fractional-N.

◆ reset()

void pulp::signal::osc::DcoOscillator::reset ( double phase = 0.0)
inlinenoexcept

Reset the phase and the divider schedule to a known start.

A DCO reset is fully deterministic — no seeded state, because there is no noise.

◆ reset_intervals()

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.

◆ set_note_hz()

void pulp::signal::osc::DcoOscillator::set_note_hz ( double note_hz)
inlinenoexcept

Command the pitch.

The divider (N or Δ), the realised frequency, and the reset schedule are derived here from the note and the profile.

◆ set_profile()

void pulp::signal::osc::DcoOscillator::set_profile ( const DcoProfile & profile)
inlinenoexcept

◆ set_pulse_width()

void pulp::signal::osc::DcoOscillator::set_pulse_width ( double width)
inlinenoexcept

◆ set_shape()

void pulp::signal::osc::DcoOscillator::set_shape ( VaShape shape)
inlinenoexcept

◆ shape()

VaShape pulp::signal::osc::DcoOscillator::shape ( ) const
inlinenoexcept

◆ tuning_word()

long long pulp::signal::osc::DcoOscillator::tuning_word ( ) const
inlinenoexcept

Fractional tuning word Δ = round(f_note_eff · 2^B / f_clk).

0 for the integer scheme.


The documentation for this class was generated from the following file:
  • /home/runner/work/pulp/pulp/core/signal/include/pulp/signal/osc/dco.hpp