Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
pulp::signal::QuantizeScaleT< SampleType > Class Template Reference

CV-to-scale quantizer on the 1 V/octave standard. More...

#include <scale_quantizer.hpp>

Public Member Functions

void prepare (double sample_rate)
void set_mode (QuantizeMode mode)
QuantizeMode mode () const
void set_edo (int n)
int edo () const
void set_scale_mask (std::uint16_t mask)
std::uint16_t scale_mask () const
void set_root_pc (int pc)
int root_pc () const
void set_hysteresis_cents (double cents)
double hysteresis_cents () const
void apply_reset_edge ()
 Verb 2: clears the hysteresis latch, so the next input quantizes on its own merits rather than being held by whatever the pattern left behind.
void reset ()
int latched_step () const
 The latched pre-mask step, in units of 1/N octave (edo mode) or semitones (scale_mask mode).
SampleType process (SampleType cv_volts)
 Quantizes one sample of pitch CV, in volts.

Static Public Member Functions

static constexpr int latency_samples ()

Static Public Attributes

static constexpr int kDefaultEdo = 12
 Steps per octave in edo mode.
static constexpr int kMaxEdo = 48
static constexpr std::uint16_t kMajorMask = 0b0000'1010'1011'0101u
 Pitch classes of the major scale — bits 0, 2, 4, 5, 7, 9, 11.
static constexpr double kHystCents = 20.0
 Extra travel, in cents, an input must make past a step boundary before the output follows it.
static constexpr double kMaxHystCents = 1200.0
 Ceiling on the hysteresis setting, one octave.
static constexpr double kMaxHystSteps = 0.45
 Ceiling on the hysteresis window expressed as a fraction of one step.
static constexpr double kMaxAbsSteps = 4096.0
 Widest step index the quantizer will produce, in either direction.

Detailed Description

template<typename SampleType = float>
class pulp::signal::QuantizeScaleT< SampleType >

CV-to-scale quantizer on the 1 V/octave standard.

(informative) In 12-tone equal temperament the octave is twelve equal semitones of 100 cents, so on the 1 V/oct standard a pitch plays in tune when it is a multiple of 1/12 V — "steps of exactly 1/12 V = one semitone" [illustrated by public quantizer documentation, e.g. Doepfer A-156; 12-TET itself is common practice, cited for illustration only]. The pitch-class bitmask convention is shared verbatim with the MIDI series' scale lock, so a scale authored on either side means the same thing here.

Hysteresis is the load-bearing part. A quantizer without it chatters whenever its input hovers on a step boundary — an LFO peak, a slow envelope, a noisy CV — and the chatter is audible as a trill, not as jitter. A step change therefore requires the input to cross the boundary by hyst_cents, and the latch is the block's only state.

The latch holds the chromatic (pre-mask) step rather than the snapped output. That is what makes the hysteresis window a fixed number of cents of input travel: if it latched the output, the window would silently widen and narrow with the gaps in the scale, so a semitone-wide leading tone and a minor-third gap would debounce differently.

Glide is not baked in. The output is a stepped voltage; a caller wanting smooth transitions puts a SlewLimiterT (kit) downstream, which keeps this block pure and lets the same quantizer feed a glide and a hard-stepped path.

USE. Taming the rungler or the Cartesian walk — any wild CV becomes an in-key melody. Sample-and-hold pitch — a SampleHoldT (kit) on an LFO into the quantizer into an oscillator is the classic random arpeggio. Microtonal — EDO-N mode reaches 24-tone quarter tones and 19- or 31-tone tunings without a lookup table.

RT contract: as the header. One llround-free floor and a bounded search.

Member Function Documentation

◆ apply_reset_edge()

template<typename SampleType = float>
void pulp::signal::QuantizeScaleT< SampleType >::apply_reset_edge ( )
inline

Verb 2: clears the hysteresis latch, so the next input quantizes on its own merits rather than being held by whatever the pattern left behind.

◆ edo()

template<typename SampleType = float>
int pulp::signal::QuantizeScaleT< SampleType >::edo ( ) const
inline

◆ hysteresis_cents()

template<typename SampleType = float>
double pulp::signal::QuantizeScaleT< SampleType >::hysteresis_cents ( ) const
inline

◆ latched_step()

template<typename SampleType = float>
int pulp::signal::QuantizeScaleT< SampleType >::latched_step ( ) const
inline

The latched pre-mask step, in units of 1/N octave (edo mode) or semitones (scale_mask mode).

◆ latency_samples()

template<typename SampleType = float>
constexpr int pulp::signal::QuantizeScaleT< SampleType >::latency_samples ( )
inlinestaticconstexpr

◆ mode()

template<typename SampleType = float>
QuantizeMode pulp::signal::QuantizeScaleT< SampleType >::mode ( ) const
inline

◆ prepare()

template<typename SampleType = float>
void pulp::signal::QuantizeScaleT< SampleType >::prepare ( double sample_rate)
inline

◆ process()

template<typename SampleType = float>
SampleType pulp::signal::QuantizeScaleT< SampleType >::process ( SampleType cv_volts)
inline

Quantizes one sample of pitch CV, in volts.

◆ reset()

template<typename SampleType = float>
void pulp::signal::QuantizeScaleT< SampleType >::reset ( )
inline

◆ root_pc()

template<typename SampleType = float>
int pulp::signal::QuantizeScaleT< SampleType >::root_pc ( ) const
inline

◆ scale_mask()

template<typename SampleType = float>
std::uint16_t pulp::signal::QuantizeScaleT< SampleType >::scale_mask ( ) const
inline

◆ set_edo()

template<typename SampleType = float>
void pulp::signal::QuantizeScaleT< SampleType >::set_edo ( int n)
inline

◆ set_hysteresis_cents()

template<typename SampleType = float>
void pulp::signal::QuantizeScaleT< SampleType >::set_hysteresis_cents ( double cents)
inline

◆ set_mode()

template<typename SampleType = float>
void pulp::signal::QuantizeScaleT< SampleType >::set_mode ( QuantizeMode mode)
inline

◆ set_root_pc()

template<typename SampleType = float>
void pulp::signal::QuantizeScaleT< SampleType >::set_root_pc ( int pc)
inline

◆ set_scale_mask()

template<typename SampleType = float>
void pulp::signal::QuantizeScaleT< SampleType >::set_scale_mask ( std::uint16_t mask)
inline

Member Data Documentation

◆ kDefaultEdo

template<typename SampleType = float>
int pulp::signal::QuantizeScaleT< SampleType >::kDefaultEdo = 12
staticconstexpr

Steps per octave in edo mode.

12 is 12-TET (cited); the cap is ours. [design parameter] default 12, range 1 .. 48.

◆ kHystCents

template<typename SampleType = float>
double pulp::signal::QuantizeScaleT< SampleType >::kHystCents = 20.0
staticconstexpr

Extra travel, in cents, an input must make past a step boundary before the output follows it.

[design parameter] default 20 cents, range 0 .. 50 cents.

◆ kMajorMask

template<typename SampleType = float>
std::uint16_t pulp::signal::QuantizeScaleT< SampleType >::kMajorMask = 0b0000'1010'1011'0101u
staticconstexpr

Pitch classes of the major scale — bits 0, 2, 4, 5, 7, 9, 11.

◆ kMaxAbsSteps

template<typename SampleType = float>
double pulp::signal::QuantizeScaleT< SampleType >::kMaxAbsSteps = 4096.0
staticconstexpr

Widest step index the quantizer will produce, in either direction.

This is the block that exists to tame wild CV — a rungler, a Cartesian walk, a runaway envelope — so it is the one place in the header that has to survive an input no musician would send. Casting an unbounded or non-finite double to int is undefined behaviour, not a wrong note. The bound sits far past any real pitch: the widest EDO at ten volts (EDO-48 at ±10 V) reaches ±480 steps. [design parameter] default ±4096 steps, range ±512 .. ±2^20.

◆ kMaxEdo

template<typename SampleType = float>
int pulp::signal::QuantizeScaleT< SampleType >::kMaxEdo = 48
staticconstexpr

◆ kMaxHystCents

template<typename SampleType = float>
double pulp::signal::QuantizeScaleT< SampleType >::kMaxHystCents = 1200.0
staticconstexpr

Ceiling on the hysteresis setting, one octave.

Beyond a full octave the value is meaningless anyway, since kMaxHystSteps caps the window that actually applies; this bound exists so the arithmetic stays finite. [design parameter] default 1200 cents ceiling, range 100 .. 12000 cents.

◆ kMaxHystSteps

template<typename SampleType = float>
double pulp::signal::QuantizeScaleT< SampleType >::kMaxHystSteps = 0.45
staticconstexpr

Ceiling on the hysteresis window expressed as a fraction of one step.

A window in cents and a step count are independent knobs, and above EDO-30 the shipped 20-cent default is wider than the half-step boundary it debounces: one EDO-31 step is 38.71 cents, the boundary sits 19.35 cents away, and 19.35 + 20 = 39.35 cents of required travel exceeds the 38.71 cents an adjacent step is away. The quantizer would then be unable to reach the next step at all and would lag a monotone input by one step forever — not anti-chatter, a stuck output. The window is therefore capped so an adjacent step always stays reachable with margin: required travel is 0.5 + kMaxHystSteps = 0.95 of a step, leaving 5 % of a step of headroom against float noise at the comparison. [design parameter] default 0.45 step, range 0.1 .. 0.499 step.


The documentation for this class was generated from the following file: