Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
lfo_sampling.hpp
Go to the documentation of this file.
1#pragma once
44
45#include <cmath>
46#include <cstdint>
47
48namespace pulp::signal {
49
62
65 double pulse_width = 0.5;
66 double triangle_peak = 0.5;
67};
68
71[[nodiscard]] constexpr bool lfo_wave_is_periodic(LfoWave wave) noexcept {
72 return wave != LfoWave::sample_hold && wave != LfoWave::smooth_random;
73}
74
75namespace detail {
76
77constexpr double kTwoPi = 6.283185307179586477;
78
79[[nodiscard]] inline double wrap01(double x) noexcept {
80 return x - std::floor(x);
81}
82
83[[nodiscard]] inline double clamp_open(double x) noexcept {
84 return x < 1.0e-6 ? 1.0e-6 : (x > 1.0 - 1.0e-6 ? 1.0 - 1.0e-6 : x);
85}
86
88[[nodiscard]] inline double wave_integral_in_cycle(LfoWave wave, double x,
89 const LfoShapeParams& params) noexcept {
90 switch (wave) {
91 case LfoWave::sine:
92 return (1.0 - std::cos(kTwoPi * x)) / kTwoPi;
93 case LfoWave::triangle: {
94 const double a = clamp_open(params.triangle_peak);
95 if (x < a)
96 return x * x / a - x;
97 const double r = x - a;
98 return r - r * r / (1.0 - a);
99 }
100 case LfoWave::saw_up:
101 return x * x - x;
103 return x - x * x;
104 case LfoWave::square: {
105 const double pw = clamp_open(params.pulse_width);
106 return x < pw ? x : 2.0 * pw - x;
107 }
110 default:
111 return 0.0;
112 }
113}
114
116[[nodiscard]] inline double wave_integral_per_cycle(LfoWave wave,
117 const LfoShapeParams& params) noexcept {
118 return wave == LfoWave::square ? 2.0 * clamp_open(params.pulse_width) - 1.0 : 0.0;
119}
120
121} // namespace detail
122
126[[nodiscard]] inline double lfo_wave(LfoWave wave, double phase,
127 const LfoShapeParams& params = {}) noexcept {
128 const double p = detail::wrap01(phase);
129 switch (wave) {
130 case LfoWave::sine:
131 return std::sin(detail::kTwoPi * p);
132 case LfoWave::triangle: {
133 const double a = detail::clamp_open(params.triangle_peak);
134 return p < a ? -1.0 + 2.0 * p / a : 1.0 - 2.0 * (p - a) / (1.0 - a);
135 }
136 case LfoWave::saw_up:
137 return 2.0 * p - 1.0;
139 return 1.0 - 2.0 * p;
140 case LfoWave::square:
141 return p < detail::clamp_open(params.pulse_width) ? 1.0 : -1.0;
144 default:
145 return 0.0;
146 }
147}
148
152[[nodiscard]] inline double lfo_wave_integral(LfoWave wave, double phase,
153 const LfoShapeParams& params = {}) noexcept {
154 const double whole = std::floor(phase);
155 return whole * detail::wave_integral_per_cycle(wave, params) +
156 detail::wave_integral_in_cycle(wave, phase - whole, params);
157}
158
165[[nodiscard]] inline double lfo_wave_mean(LfoWave wave, double from, double to,
166 const LfoShapeParams& params = {}) noexcept {
167 if (!(std::isfinite(from) && std::isfinite(to)))
168 return 0.0;
169 if (to < from) {
170 const double t = from;
171 from = to;
172 to = t;
173 }
174 const double span = to - from;
175 // Below this the difference of two integrals loses more digits than the
176 // point value is wrong by.
177 if (span < 1.0e-9)
178 return lfo_wave(wave, from, params);
179 return (lfo_wave_integral(wave, to, params) - lfo_wave_integral(wave, from, params)) / span;
180}
181
196[[nodiscard]] inline double lfo_wave_bandlimited(LfoWave wave, double phase, double increment,
197 const LfoShapeParams& params = {}) noexcept {
198 const double p = detail::wrap01(phase);
199 double value = lfo_wave(wave, p, params);
200 if (!(increment > 0.0) || !std::isfinite(increment) || increment >= 1.0)
201 return value;
202 const double dt = increment;
203
204 // The correction a corner at phase `at` contributes to the sample at `p`:
205 // `after` when the corner lies within one step before p, `before` when it
206 // lies within one step after p. `height` is a value jump (BLEP) or a
207 // slope change per sample (BLAMP).
208 const auto corner = [&](double at, double height, bool slope) {
209 // Distance from the corner forward to this sample, in cycles, wrapped
210 // so a corner at 0 is seen from both ends of the cycle.
211 double behind = p - at;
212 behind -= std::floor(behind);
213 if (behind < dt) {
214 // The corner is `behind` cycles before this sample: this is the
215 // sample after it, and d (the corner's position past the previous
216 // sample) is 1 - behind / dt.
217 const double d = 1.0 - behind / dt;
218 value += slope ? osc::poly_blamp(d, height).after : osc::poly_blep(d, height).after;
219 }
220 double ahead = at - p;
221 ahead -= std::floor(ahead);
222 if (ahead > 0.0 && ahead <= dt) {
223 // The corner is `ahead` cycles after this sample: this is the
224 // sample before it, with the corner `ahead / dt` of a step past it.
225 const double d = ahead / dt;
226 value += slope ? osc::poly_blamp(d, height).before : osc::poly_blep(d, height).before;
227 }
228 };
229
230 switch (wave) {
231 case LfoWave::saw_up:
232 corner(0.0, -2.0, false);
233 break; // +1 wraps to -1
235 corner(0.0, +2.0, false);
236 break; // -1 wraps to +1
237 case LfoWave::square: {
238 const double pw = detail::clamp_open(params.pulse_width);
239 corner(0.0, +2.0, false);
240 corner(pw, -2.0, false);
241 break;
242 }
243 case LfoWave::triangle: {
244 // Slopes per cycle: +2/a up to the apex, -2/(1-a) after it.
245 const double a = detail::clamp_open(params.triangle_peak);
246 const double turn = (2.0 / a + 2.0 / (1.0 - a)) * dt; // per sample
247 corner(a, -turn, true);
248 corner(0.0, +turn, true);
249 break;
250 }
251 case LfoWave::sine:
254 default:
255 break;
256 }
257 return value;
258}
259
260} // namespace pulp::signal
Bandlimited-step (BLEP) and bandlimited-ramp (BLAMP) correction kernels.
double wave_integral_in_cycle(LfoWave wave, double x, const LfoShapeParams &params) noexcept
The integral of one wave over [0, x] for x in [0, 1].
Definition lfo_sampling.hpp:88
double clamp_open(double x) noexcept
Definition lfo_sampling.hpp:83
constexpr double kTwoPi
Definition lfo_sampling.hpp:77
double wrap01(double x) noexcept
Definition lfo_sampling.hpp:79
double wave_integral_per_cycle(LfoWave wave, const LfoShapeParams &params) noexcept
The integral over one whole cycle: zero for every wave but the square.
Definition lfo_sampling.hpp:116
Correction poly_blep(double d, double height) noexcept
polyBLEP correction for a step in the VALUE.
Definition blep.hpp:177
Correction poly_blamp(double d, double slope_change) noexcept
polyBLAMP correction for a break in the SLOPE.
Definition blep.hpp:206
Definition additive_bank.hpp:218
double lfo_wave_integral(LfoWave wave, double phase, const LfoShapeParams &params={}) noexcept
The cumulative integral of wave from phase 0 to phase, in value x cycles.
Definition lfo_sampling.hpp:152
double lfo_wave_bandlimited(LfoWave wave, double phase, double increment, const LfoShapeParams &params={}) noexcept
The band-limited value of wave for a consumer that reads it EVERY sample, at phase with a per-sample ...
Definition lfo_sampling.hpp:196
constexpr bool lfo_wave_is_periodic(LfoWave wave) noexcept
True for the waves that are periodic functions of phase (everything but the two random waves).
Definition lfo_sampling.hpp:71
@ a
Definition formant_filter_bank.hpp:20
LfoWave
The effect-wave vocabulary, shared by these functions and by EffectLfoT (lfo.hpp),...
Definition lfo_sampling.hpp:53
@ sample_hold
Definition lfo_sampling.hpp:59
@ square
Definition lfo_sampling.hpp:58
@ saw_down
Definition lfo_sampling.hpp:57
@ sine
Definition lfo_sampling.hpp:54
@ saw_up
Definition lfo_sampling.hpp:56
@ smooth_random
Definition lfo_sampling.hpp:60
@ triangle
Definition lfo_sampling.hpp:55
double lfo_wave(LfoWave wave, double phase, const LfoShapeParams &params={}) noexcept
The trivial value of wave at phase (any real number of cycles), in [-1, 1].
Definition lfo_sampling.hpp:126
double lfo_wave_mean(LfoWave wave, double from, double to, const LfoShapeParams &params={}) noexcept
The exact mean of wave over the phase interval [from, to], in any units of whole cycles and of any le...
Definition lfo_sampling.hpp:165
@ value
the widget's primary value (Knob, Fader, RangeSlider, Toggle, ProgressBar)
Definition binding_diagnostics.hpp:19
The shape parameters the deterministic waves take.
Definition lfo_sampling.hpp:64
double pulse_width
Square: fraction of the cycle spent at +1, in (0, 1).
Definition lfo_sampling.hpp:65
double triangle_peak
Triangle: phase of the +1 apex, in (0, 1).
Definition lfo_sampling.hpp:66
double after
Add to the sample following it.
Definition blep.hpp:93
double before
Add to the sample preceding the discontinuity.
Definition blep.hpp:91