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

The real-unit vocabulary shared by every DSP block in the catalog. More...

#include <algorithm>
#include <cmath>
#include <cstdint>
#include <pulp/timebase/beat_division.hpp>

Go to the source code of this file.

Namespaces

namespace  pulp
namespace  pulp::signal
namespace  pulp::signal::units

Enumerations

enum class  pulp::signal::units::Division : std::uint8_t {
  pulp::signal::units::whole = 0 , pulp::signal::units::whole_dotted = 1 , pulp::signal::units::whole_triplet = 2 , pulp::signal::units::half = 3 ,
  pulp::signal::units::half_dotted = 4 , pulp::signal::units::half_triplet = 5 , pulp::signal::units::quarter = 6 , pulp::signal::units::quarter_dotted = 7 ,
  pulp::signal::units::quarter_triplet = 8 , pulp::signal::units::eighth = 9 , pulp::signal::units::eighth_dotted = 10 , pulp::signal::units::eighth_triplet = 11 ,
  pulp::signal::units::sixteenth = 12 , pulp::signal::units::sixteenth_dotted = 13 , pulp::signal::units::sixteenth_triplet = 14 , pulp::signal::units::thirty_second = 15 ,
  pulp::signal::units::thirty_second_dotted = 16 , pulp::signal::units::thirty_second_triplet = 17 , pulp::signal::units::sixty_fourth = 18 , pulp::signal::units::sixty_fourth_dotted = 19 ,
  pulp::signal::units::sixty_fourth_triplet = 20 , pulp::signal::units::count = 21
}
 Shared serialized musical-division vocabulary. More...

Functions

template<typename T>
T pulp::signal::units::db_to_linear (T db)
 Amplitude dB → linear gain. 0 dB → 1, −6.0206 dB → 0.5.
template<typename T>
T pulp::signal::units::linear_to_db (T linear)
 Linear gain → amplitude dB, floored at kDbFloorAmplitude so silence maps to a finite −200 dB rather than −inf.
template<typename T>
T pulp::signal::units::midi_to_hz (T note)
 MIDI note number → Hz (note 69 = 440 Hz).
template<typename T>
T pulp::signal::units::hz_to_midi (T hz)
 Hz → MIDI note number.
template<typename T>
T pulp::signal::units::semitones_to_ratio (T semitones)
 Semitones → frequency ratio. +12 → 2, −12 → 0.5.
template<typename T>
T pulp::signal::units::ratio_to_semitones (T ratio)
 Frequency ratio → semitones. Inverse of semitones_to_ratio.
template<typename T>
T pulp::signal::units::cents_to_ratio (T cents)
 Cents → frequency ratio. +100 → 2^(1/12), +1200 → 2.
template<typename T>
T pulp::signal::units::ratio_to_cents (T ratio)
 Frequency ratio → cents. Inverse of cents_to_ratio.
template<typename T>
T pulp::signal::units::volts_to_semitones (T volts)
 Pitch CV volts → semitones, on the 1 V/octave standard.
template<typename T>
T pulp::signal::units::semitones_to_volts (T semitones)
 Semitones → pitch CV volts, on the 1 V/octave standard.
template<typename T>
T pulp::signal::units::ms_to_samples (T ms, T sample_rate)
 Milliseconds → samples at sample_rate Hz.
template<typename T>
T pulp::signal::units::samples_to_ms (T samples, T sample_rate)
 Samples → milliseconds at sample_rate Hz.
template<typename T>
T pulp::signal::units::hz_to_period_samples (T hz, T sample_rate)
 Frequency → its period in samples. A 100 Hz tone at 48 kHz is 480 samples.
template<typename T>
T pulp::signal::units::period_samples_to_hz (T samples, T sample_rate)
 A period in samples → its frequency in Hz.
template<typename T>
T pulp::signal::units::hz_to_cycles_per_sample (T hz, T sample_rate)
 Hz → normalised frequency in cycles per sample (f / fs) — the unit a phase accumulator increments by.
template<typename T>
T pulp::signal::units::hz_to_radians_per_sample (T hz, T sample_rate)
 Hz → radians per sample (2π·f / fs) — the unit a sin/cos oscillator and most filter design formulas want.
template<typename T>
T pulp::signal::units::ms_to_onepole_coef (T ms, T sample_rate)
 A time constant in ms → the a in the one-pole smoother y += a·(x − y), i.e.
template<typename T>
T pulp::signal::units::ms_to_onepole_pole (T ms, T sample_rate)
 The complementary pole of ms_to_onepole_coef: p = 1 − a = exp(−1/(τ·fs)), the p in y = p·y + (1 − p)·x.
template<typename T>
T pulp::signal::units::t60_ms_to_tau_ms (T t60_ms)
 A 60 dB decay time in ms → the one-pole time constant τ in ms that produces it.
template<typename T>
T pulp::signal::units::taper_log (T u, T lo, T hi)
 Geometric ("log") taper: maps a normalised knob position u ∈ [0, 1] onto [lo, hi] so equal knob travel is equal RATIO — the right law for anything heard geometrically (frequency, delay time, drive).
template<typename T>
T pulp::signal::units::untaper_log (T value, T lo, T hi)
 Inverse of taper_log: a value in [lo, hi] → its knob position in [0, 1].
template<typename T>
T pulp::signal::units::taper_linear (T u, T lo, T hi)
 Linear taper — the arithmetic counterpart of taper_log, present so a parameter table can name its law instead of open-coding a lerp.
template<typename T>
T pulp::signal::units::untaper_linear (T value, T lo, T hi)
 Inverse of taper_linear.
template<typename T>
T pulp::signal::units::taper_log_inverse (T value, T lo, T hi)
 Compatibility spelling used by the shared modulation toolkit.
template<typename T>
T pulp::signal::units::t60_to_per_sample_gain (T t60_seconds, T sample_rate)
 Per-sample gain for a feedback path that decays by 60 dB in t60_seconds.
template<typename T>
T pulp::signal::units::beats_to_seconds (T beats, T bpm)
template<typename T>
T pulp::signal::units::seconds_to_beats (T seconds, T bpm)
constexpr timebase::BeatDivision pulp::signal::units::to_beat_division (Division division) noexcept
 Converts the legacy signal spelling to the canonical timebase vocabulary.
constexpr float pulp::signal::units::division_to_beats (Division division)
constexpr float pulp::signal::units::division_to_beats (int index)
float pulp::signal::units::division_to_seconds (Division division, float bpm)
double pulp::signal::units::division_to_samples (Division division, float bpm, double sample_rate)
float pulp::signal::units::division_to_hz (Division division, float bpm)

Variables

constexpr double pulp::signal::units::kDbFloorAmplitude = 1e-10
 Amplitude floor used when converting to dB.
constexpr double pulp::signal::units::kMidiA4 = 69.0
 MIDI note number of concert A. Fixed by the MIDI specification.
constexpr double pulp::signal::units::kA4Hz = 440.0
 Frequency of concert A in Hz — the tuning reference for midi_to_hz.
constexpr double pulp::signal::units::kSemitonesPerOctave = 12.0
 Semitones per octave, and cents per semitone. Fixed by 12-TET notation.
constexpr double pulp::signal::units::kCentsPerSemitone = 100.0
constexpr double pulp::signal::units::kSemitonesPerVolt = kSemitonesPerOctave
 Semitones per volt on the 1 V/octave pitch-CV standard.
constexpr int pulp::signal::units::kDivisionCount = static_cast<int>(Division::count)

Detailed Description

The real-unit vocabulary shared by every DSP block in the catalog.

Series law 3 says parameters are expressed in real units — Hz, ms, s, dB, semitones, cents — because those are what a musician reads on a knob and what a spec can state a range in. That law is only enforceable if there is exactly ONE conversion from each real unit into the internal quantity a filter or oscillator actually wants. Two hand-rolled pow(10, db/20) call sites are two chances to disagree about the dB floor; two hand-rolled one-pole coefficients are two chances to disagree about whether "20 ms" means a time constant or a settling time. Everything converts here.

Conventions this header fixes, once, for the whole library:

  • dB is amplitude dB (20·log10), never power dB. Converting a non-positive amplitude to dB floors at kDbFloorAmplitude rather than returning −inf, so a silent buffer produces a finite, plottable number.
  • MIDI note 69 is 440 Hz. Concert pitch is not a parameter here; a block that needs a different A4 scales the result.
  • A one-pole "coefficient" is the a in y += a·(x − y), derived from a TIME CONSTANT τ (the 63.2 % point), not from a settling time. The other two common readings are one multiply away and documented on ms_to_onepole_coef so nobody has to rediscover them: t60 = τ·ln(1000) ≈ 6.908·τ and t90 − t10 = τ·ln 9 ≈ 2.197·τ.
  • Pitch CV is the 1 V/octave standard — 12 semitones per volt, 100 cents per semitone — so any block's pitch output feeds any other's pitch input without unit negotiation.

Everything is a template over the sample type so a double block does not silently round its coefficients through float.

RT contract: every function here is a pure, stateless, branch-light scalar computation. Nothing allocates, locks, or performs I/O, so all of it is safe to call per sample on the audio thread. Several use std::pow / std::log2 / std::exp, which are not free — the conversions that appear in a hot path (db_to_linear, semitones_to_ratio) are cheap enough to use per sample, but a block that converts a control value that only changes per block should convert per block.