Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
wt_lofi.hpp File Reference

OSC-WT (lo-fi tier): a dedicated variable-clock zero-order-hold wavetable oscillator. More...

#include <pulp/signal/windowed_sinc_design.hpp>
#include <algorithm>
#include <cmath>
#include <cstddef>
#include <span>
#include <utility>
#include <vector>

Go to the source code of this file.

Classes

class  pulp::signal::osc::LofiWtOscillator
 Dedicated variable-clock ZOH wavetable oscillator (the lo-fi tier). More...

Namespaces

namespace  pulp
namespace  pulp::signal
namespace  pulp::signal::osc

Detailed Description

OSC-WT (lo-fi tier): a dedicated variable-clock zero-order-hold wavetable oscillator.

It plays a raw short single-cycle table with NEAREST/ZOH lookup at a playback clock that tracks pitch (fs_play = f0 · L), so its spectral-image ladder rides at n · L · f0 and moves with the note. That pitch-tracking image ladder IS the lo-fi sound — and it is exactly what a fixed-rate, band-limited, linearly-interpolated wavetable engine cannot produce — which is why this is a separate engine, not a mode of WtOscillator / WavetableT.

── Why a dedicated engine, decided by measurement ────────────────────────

The wavetable-architecture question was settled by rendering both candidates and comparing each spectrum to the analytic variable-clock-ZOH image ladder:

  • A variable-clock ZOH reader over a raw table reproduces the image lines at (nL ± m)·f0 to ~0.001 dB of the analytic sinc model, and the ladder tracks pitch and table length L.
  • WavetableT does NOT: its sample_band() always LINEAR-interpolates — which alone suppresses the first image by ~42 dB — and its factory tables are band-limited at build time, which removes the ladder entirely. There is no way to parameterize WavetableT into a lo-fi reader without dropping the very things that make it WavetableT.

So the modern tier extends WavetableT (wt.hpp) and the lo-fi tier is this separate class. The two tiers deliberately diverge on every axis the modern tier cleans up: ZOH vs interpolation, raw table vs band-limited, and a HARD (stepped) scan vs the modern tier's slewed, zipper-free scan.

── The five lo-fi mechanisms, each a measurable property ─────────────────

  1. Variable-clock ZOH images. The table is clocked at fs_play = f0·L and held (zero-order, no interpolation), so the baseband line m·f0 is replicated at (nL ± m)·f0, each weighted by the ZOH sinc(π f/fs_play) envelope. Because fs_play scales with pitch, the images track the note — the signature that separates this from a fixed-rate table.
  2. 8-bit table quantization → grit. The stored table (not the output stream) is optionally quantized to bit_depth bits, undithered, about zero. For a symmetric table this preserves half-wave symmetry, so the grit is ODD-harmonic (h3, h5, …; even harmonics absent) with an aggregate SNR near the ideal 6.02·N + 1.76 (≈ 49.9 dB at 8 bits) spread across those odd lines — not a single spur, and not white.
  3. A real reconstruction stage. Naive point-sampling of the staircase at the host rate folds an authentic-but-supra-Nyquist image back IN-BAND (audible), which is a modeling artifact, not the instrument's sound. So the reader oversamples the staircase, applies a reconstruction lowpass, and decimates — preserving the sub-Nyquist images the original passed while not folding in the ultrasonic ones it removed. This stage is load-bearing (it moves that in-band fold from ~−54 dBc to below −77 dBc), not an afterthought; set_reconstruction(false) exposes the raw naive path for A/B and for callers who want the rawer grit.
  4. Hard (stepped) wave-scan. set_scan selects the nearest table in the set with NO interpolation and NO slew, so crossing a table boundary steps the output instantaneously — the classic wavetable "zipper." That step is the faithful lo-fi behavior (contrast WtOscillator, which slews it smooth); a mode-aware click gate must null it against the intended stepped reference rather than flag it.
  5. Short-table harmonic ceiling. A length-L table represents at most L/2 harmonics, so low notes are intrinsically darker and high notes fold — a property of L, reproduced by playing the raw table rather than a band-limited one.

── No factory content ────────────────────────────────────────────────────

This engine ships NO wavetable data. The caller supplies the raw table(s); the model reproduces the playback engine's character, never its wave content.

── RT contract ────────────────────────────────────────────────────────────

set_tables, set_oversample_factor, set_reconstruction, and prepare allocate / rebuild the reconstruction filter and run off the audio thread. set_scan, reset, and next allocate nothing, lock nothing, and perform no I/O. double throughout the reader; the table can be lower precision but is stored as double after quantization.