Sound Byte Libs 29c5ff3
C++ firmware library for audio applications on 32-bit ARM Cortex-M processors
Loading...
Searching...
No Matches
exp_mod.hpp
Go to the documentation of this file.
1// sbl/signal/exp_mod.hpp — Exponential modulation converter (Signal layer)
2//
3// The "exponential converter IC" of the digital world. In analog synths,
4// a dedicated circuit converts linear control voltage to exponential
5// frequency scaling — this is what makes 1V/oct work. Without it,
6// modulation is perceptually broken: ±200 Hz is 2.3 octaves at 300 Hz
7// but 0.03 octaves at 10 kHz.
8//
9// exp_mod() bridges normalized modulation signals [-1.0, 1.0] and
10// engineering-unit parameters (Hz). This is the application point where
11// the Signal principle holds: "Signals follow [-1.0, 1.0]. Configuration
12// uses engineering units."
13//
14// Part of the Audio Stack: Atoms → Signal → Widgets
15// See FDP-031 for architecture rationale, RPT-014 for the triggering bug.
16//
17// Usage:
18// // Scalar — inside a per-sample loop
19// float freq = sbl::signal::exp_mod(cutoff_hz, lfo_value, 2.0f);
20//
21// // Block — apply to an entire modulation buffer
22// float freq_buf[48];
23// sbl::signal::exp_mod_block(lfo_buf, freq_buf, n, cutoff_hz, 2.0f);
24
25#pragma once
26
27#include <cstdint>
28
29#include <sbl/dsp/fast_math.hpp>
30
31namespace sbl::signal {
32
33/// Apply exponential modulation to a base frequency (scalar).
34///
35/// Converts a normalized modulation signal to a frequency via:
36/// freq = base * 2^(mod * depth)
37///
38/// At depth = 2.0 and mod = 1.0, output is base * 4 (2 octaves up).
39/// At depth = 2.0 and mod = -1.0, output is base / 4 (2 octaves down).
40/// The relationship is perceptually uniform — equal mod deltas produce
41/// equal musical intervals regardless of the base frequency.
42///
43/// @param base Center frequency in Hz (e.g., filter cutoff)
44/// @param mod Normalized modulation value, typically [-1.0, 1.0]
45/// @param depth Modulation depth in octaves (e.g., 2.0 = ±2 octaves)
46/// @param lo Minimum output frequency in Hz (default 20)
47/// @param hi Maximum output frequency in Hz (default 20000)
48/// @return Modulated frequency in Hz, clamped to [lo, hi]
49inline float exp_mod(float base, float mod, float depth,
50 float lo = 20.0f, float hi = 20000.0f) {
51 float freq = base * dsp::fast_exp2f(mod * depth);
52 if (freq < lo) freq = lo;
53 if (freq > hi) freq = hi;
54 return freq;
55}
56
57/// Apply exponential modulation to an entire block (buffer version).
58///
59/// Writes modulated frequencies to freq_out[] for each sample. Use this
60/// when you need the frequency buffer for direct coefficient computation
61/// (e.g., feeding into a filter's set_cutoff per-sample).
62///
63/// @param mod Normalized modulation signal buffer [-1.0, 1.0]
64/// @param freq_out Output frequency buffer (Hz)
65/// @param n Frame count
66/// @param base Center frequency in Hz
67/// @param depth Modulation depth in octaves
68/// @param lo Minimum output frequency in Hz (default 20)
69/// @param hi Maximum output frequency in Hz (default 20000)
70inline void exp_mod_block(const float* mod, float* freq_out, uint16_t n,
71 float base, float depth,
72 float lo = 20.0f, float hi = 20000.0f) {
73 for (uint16_t i = 0; i < n; ++i) {
74 freq_out[i] = exp_mod(base, mod[i], depth, lo, hi);
75 }
76}
77
78} // namespace sbl::signal
float fast_exp2f(float x)
Definition fast_math.hpp:96
void exp_mod_block(const float *mod, float *freq_out, uint16_t n, float base, float depth, float lo=20.0f, float hi=20000.0f)
Definition exp_mod.hpp:70
float exp_mod(float base, float mod, float depth, float lo=20.0f, float hi=20000.0f)
Definition exp_mod.hpp:49