Sound Byte Libs 29c5ff3
C++ firmware library for audio applications on 32-bit ARM Cortex-M processors
Loading...
Searching...
No Matches
warp.hpp
Go to the documentation of this file.
1// sbl/dsp/warp.hpp — Curve warp engines for envelopes and parameter scaling
2//
3// Each engine warps a linear phase [0, 65535] into a shaped phase using a
4// curve parameter: 0 = full concave (log), 32768 = linear, 65535 = full convex (exp).
5//
6// Engines are stateless structs with a single static method, selected at
7// compile time as a template parameter to SegmentGenerator/Envelope.
8//
9// See FDP-016 for design rationale.
10
11#pragma once
12
13#include <cstdint>
14
15#include <sbl/dsp/fixed.hpp>
16#include <sbl/dsp/lut.hpp>
18
19namespace sbl::dsp {
20
21// ─── Curve shape presets ─────────────────────────────────────────────
22//
23// Named constants for the curve parameter used by warp engines and
24// scaled_curved(). Arbitrary uint16_t values work too — these are the
25// three common presets.
26//
27// Log: logarithmic shape (concave, above diagonal)
28// Output rises quickly then flattens. More resolution at high values.
29//
30// Linear: identity (no shaping). Same as scaled().
31//
32// Exp: exponential shape (convex, below diagonal)
33// Output is flat then rises sharply. More resolution at low values.
34// Matches audio-taper pot behavior — use for frequency, pitch, cutoff.
35
36namespace curve {
37
38static constexpr uint16_t Log = 0; // Full concave: fast start, slow approach
39static constexpr uint16_t Linear = U16_MID; // No shaping (identity)
40static constexpr uint16_t Exp = U16_MAX; // Full convex: slow start, fast snap
41
42} // namespace curve
43
44// ─── lut_curve_warp ──────────────────────────────────────────────────
45//
46// Generic LUT-based curve warp. Works with any monotonically increasing
47// uint16_t table of the given Size. Uses the complement trick + crossfade
48// to produce concave, linear, and convex shapes from a single table.
49//
50// This is the core algorithm extracted from ExpCurveWarp — users can call
51// it directly with custom LUTE-generated tables.
52
53template<uint16_t Size>
54inline uint16_t lut_curve_warp(const uint16_t* table, uint16_t phase, uint16_t curve) {
55 if (curve == U16_MID) return phase;
56
57 uint32_t phase32 = static_cast<uint32_t>(phase) << 16;
58 uint16_t shaped;
59 uint32_t amount;
60
61 if (curve < U16_MID) {
62 // Concave (logarithmic feel): fast start, slow approach to target
63 shaped = lut::lookup_linear<Size>(table, phase32);
64 uint32_t dc = U16_MID - curve;
65 amount = (dc >= U16_MID) ? U16_MAX : dc * 2;
66 } else {
67 // Convex (exponential feel): slow start, fast snap to target
68 // Complement trick: max - f(max - t)
69 uint32_t inv_phase32 = static_cast<uint32_t>(U16_MAX - phase) << 16;
70 shaped = U16_MAX - lut::lookup_linear<Size>(table, inv_phase32);
71 uint32_t dc = curve - U16_MID;
72 amount = (dc >= U16_MID) ? U16_MAX : dc * 2;
73 }
74
75 // Crossfade between linear (phase) and shaped value
76 // amount=0 → pure linear, amount=65534 → pure shaped
77 return static_cast<uint16_t>(
78 phase + ((static_cast<int32_t>(shaped) - phase) * amount >> 16));
79}
80
81// ─── ExpCurveWarp ────────────────────────────────────────────────────
82//
83// Single exponential LUT with complement trick. 514 bytes flash, zero RAM.
84// True RC charge/discharge character — the Maths approach.
85//
86// Cost: ~10 cycles on M0+, ~5 cycles on M7.
87
89 /// @note ISR-safe — bounded computation, no I/O.
90 static uint16_t warp(uint16_t phase, uint16_t curve) {
91 return lut_curve_warp<256>(lut::exp_curve_256, phase, curve);
92 }
93};
94
95// ─── RationalWarp ────────────────────────────────────────────────────
96//
97// Stages-inspired rational function: f(t) = (1+a)*t / (1+a*t).
98// Zero flash, zero RAM. Computed per-sample.
99//
100// The coefficient a = STAGES_K * (dc/65536)^2 gives a range of [0, ~32],
101// matching MI Stages' WarpPhase function.
102//
103// Cost: ~60 cycles on M0+ (software division), ~15 cycles on M7.
104//
105// Reference: MI Stages segment_generator.cc (MIT), WarpPhase()
106
108 /// @note ISR-safe — bounded computation, no I/O.
109
110 // Coefficient scaling factor from MI Stages WarpPhase.
111 // a_float = STAGES_K * curve_centered^2, giving a ∈ [0, ~32].
112 static constexpr uint32_t STAGES_K = 128;
113
114 static uint16_t warp(uint16_t phase, uint16_t curve) {
115 if (curve == U16_MID) return phase;
116
117 // Raw rational is concave (above diagonal). Flip for convex side.
118 bool flip = curve > U16_MID;
119 uint32_t t = flip ? (U16_MAX - phase) : phase;
120
121 // Map curve distance from center to coefficient 'a'
122 // a_q16 = a * 65536 for Q16 fixed-point arithmetic
123 uint32_t dc = flip ? (curve - U16_MID) : (U16_MID - curve);
124 uint32_t a_q16 = static_cast<uint32_t>((static_cast<uint64_t>(STAGES_K) * dc * dc) >> 16);
125
126 // Rational function in Q16 fixed-point:
127 // result = (1 + a) * t / (1 + a * t)
128 // where (1+a) is (Q16_ONE + a_q16) in Q16, and t is [0, U16_MAX]
129 uint64_t num = static_cast<uint64_t>(Q16_ONE + a_q16) * t;
130 uint32_t den = Q16_ONE + static_cast<uint32_t>(
131 static_cast<uint64_t>(a_q16) * t >> 16);
132
133 uint32_t result = static_cast<uint32_t>(num / den);
134 if (result > U16_MAX) result = U16_MAX;
135
136 return static_cast<uint16_t>(flip ? (U16_MAX - result) : result);
137 }
138};
139
140// ─── DefaultWarp ─────────────────────────────────────────────────────
141//
142// The default uint16 engine for I/O stack (Pot::scaled_curved, CvInput::scaled_curved).
143// ExpCurveWarp: best balance of quality, performance, and flash cost.
144
146
147// ─── Float warp engines ─────────────────────────────────────────────
148//
149// Float-domain warp engines for the Audio stack (SegmentGenerator, Envelope).
150// Phase and curve are both float; no LUT or fixed-point math required.
151//
152// Phase: [0.0, 1.0] — linear ramp through the segment
153// Curve: [-1.0, 1.0] where:
154// -1.0 = full concave (fast start, slow approach to target)
155// 0.0 = linear (no shaping)
156// 1.0 = full convex (slow start, fast snap to target)
157
158/// Rational function warp (float). Zero flash, zero RAM.
159/// f(t) = (1+a)*t / (1+a*t), with a = K * curve^2.
160/// Inspired by MI Stages WarpPhase (MIT).
162 static constexpr float K = 128.0f;
163
164 /// @note ISR-safe — bounded computation, no I/O.
165 static float warp(float phase, float curve) {
166 if (curve > -0.001f && curve < 0.001f) return phase;
167
168 bool flip = curve > 0.0f;
169 float t = flip ? (1.0f - phase) : phase;
170 float dc = flip ? curve : -curve;
171
172 float a = K * dc * dc;
173 float num = (1.0f + a) * t;
174 float den = 1.0f + a * t;
175 float result = num / den;
176
177 if (result > 1.0f) result = 1.0f;
178 return flip ? (1.0f - result) : result;
179 }
180};
181
182/// Default float warp engine for SegmentGenerator and Envelope.
183/// FloatRationalWarp: zero flash, analytical, good character.
185
186} // namespace sbl::dsp
static constexpr uint16_t Exp
Definition warp.hpp:40
static constexpr uint16_t Log
Definition warp.hpp:38
static constexpr uint16_t Linear
Definition warp.hpp:39
uint16_t lookup_linear(const uint16_t *table, uint32_t phase)
Definition lut.hpp:39
constexpr uint16_t exp_curve_256[257]
DSP atoms for audio signal processing.
Definition allpass.hpp:22
constexpr uint16_t U16_MID
Definition fixed.hpp:21
uint16_t lut_curve_warp(const uint16_t *table, uint16_t phase, uint16_t curve)
Definition warp.hpp:54
constexpr uint16_t U16_MAX
Definition fixed.hpp:18
constexpr uint32_t Q16_ONE
Definition fixed.hpp:15
static uint16_t warp(uint16_t phase, uint16_t curve)
Definition warp.hpp:90
static float warp(float phase, float curve)
Definition warp.hpp:165
static constexpr float K
Definition warp.hpp:162
static constexpr uint32_t STAGES_K
Definition warp.hpp:112
static uint16_t warp(uint16_t phase, uint16_t curve)
Definition warp.hpp:114