Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
waveshaper.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <cmath>
4#include <algorithm>
5
6namespace pulp::signal {
7
8// Waveshaping distortion with multiple curve types.
9// RT contract: setters and process paths are scalar-only and allocate no
10// memory.
11template <typename SampleType = float>
13public:
15
16 void set_curve(Curve c) { curve_ = c; }
17 void set_drive(SampleType drive) { drive_ = drive; }
18
19 SampleType process(SampleType input) const {
20 const SampleType x = input * drive_;
21 switch (curve_) {
22 case Curve::soft_clip:
23 return shape<Curve::soft_clip>(x);
24 case Curve::hard_clip:
25 return shape<Curve::hard_clip>(x);
26 case Curve::tanh_clip:
27 return shape<Curve::tanh_clip>(x);
28 case Curve::fold:
29 return shape<Curve::fold>(x);
30 case Curve::sine_fold:
31 return shape<Curve::sine_fold>(x);
32 }
33 return x;
34 }
35
36 // Same result as process(sample) on each sample, bit for bit, with two
37 // exceptions at unity drive, where the buffer path skips the multiply: a
38 // signaling NaN input is not quieted first (the output is still a NaN),
39 // and with denormals-are-zero enabled a subnormal input is not flushed
40 // to zero first, so hard_clip and fold, which do no arithmetic on an
41 // in-range sample, pass it through where the sample path returns 0. The
42 // curve
43 // is selected once per buffer and the drive held in a register: read
44 // through the object inside the loop they would be reloaded every sample,
45 // since the compiler cannot prove that an opaque libm call leaves them
46 // unchanged, and the curve switch would stay in the loop.
47 void process(SampleType* buffer, int num_samples) const {
48 switch (curve_) {
49 case Curve::soft_clip:
50 return run<Curve::soft_clip>(buffer, num_samples, drive_);
51 case Curve::hard_clip:
52 return run<Curve::hard_clip>(buffer, num_samples, drive_);
53 case Curve::tanh_clip:
54 return run<Curve::tanh_clip>(buffer, num_samples, drive_);
55 case Curve::fold:
56 return run<Curve::fold>(buffer, num_samples, drive_);
57 case Curve::sine_fold:
58 return run<Curve::sine_fold>(buffer, num_samples, drive_);
59 }
60 for (int i = 0; i < num_samples; ++i)
61 buffer[i] *= drive_;
62 }
63
64 private:
65 // The curve applied to an already-driven sample.
66 template <Curve C> static SampleType shape(SampleType x) {
67 if constexpr (C == Curve::soft_clip) {
68 return x / (SampleType{1.0f} + std::abs(x));
69 } else if constexpr (C == Curve::hard_clip) {
70 return std::clamp(x, SampleType{-1.0f}, SampleType{1.0f});
71 } else if constexpr (C == Curve::tanh_clip) {
72 return std::tanh(x);
73 } else if constexpr (C == Curve::fold) {
74 // Wave folding: fold back at +-1
75 while (x > SampleType{1.0f} || x < SampleType{-1.0f}) {
76 if (x > SampleType{1.0f})
77 x = SampleType{2.0f} - x;
78 if (x < SampleType{-1.0f})
79 x = SampleType{-2.0f} - x;
80 }
81 return x;
82 } else {
83 return std::sin(x * SampleType{1.5707963f}); // pi/2
84 }
85 }
86
87 template <Curve C> static void run(SampleType* buffer, int num_samples, SampleType drive) {
88 // Multiplying by exactly 1 changes no value other than quieting a
89 // signaling NaN (and, under denormals-are-zero, flushing a subnormal),
90 // so unity drive skips it: the multiply otherwise sits on the path
91 // into each curve evaluation.
92 if (drive == SampleType{1.0f}) {
93 for (int i = 0; i < num_samples; ++i)
94 buffer[i] = shape<C>(buffer[i]);
95 return;
96 }
97 for (int i = 0; i < num_samples; ++i)
98 buffer[i] = shape<C>(buffer[i] * drive);
99 }
100
101 Curve curve_ = Curve::tanh_clip;
102 SampleType drive_ = SampleType{1.0f};
103};
104
107
108} // namespace pulp::signal
Definition waveshaper.hpp:12
void process(SampleType *buffer, int num_samples) const
Definition waveshaper.hpp:47
void set_drive(SampleType drive)
Definition waveshaper.hpp:17
SampleType process(SampleType input) const
Definition waveshaper.hpp:19
Curve
Definition waveshaper.hpp:14
@ fold
Definition waveshaper.hpp:14
@ hard_clip
Definition waveshaper.hpp:14
@ sine_fold
Definition waveshaper.hpp:14
@ soft_clip
Definition waveshaper.hpp:14
@ tanh_clip
Definition waveshaper.hpp:14
void set_curve(Curve c)
Definition waveshaper.hpp:16
Definition additive_bank.hpp:218
@ buffer
A borrowed static buffer with a traversable playhead.
Definition granular.hpp:110
WaveShaperT< float > WaveShaper
Definition waveshaper.hpp:105
@ c
Definition spectrum_trace.hpp:35
@ i
Definition formant_filter_bank.hpp:20
WaveShaperT< double > WaveShaper64
Definition waveshaper.hpp:106
@ x
Definition input_events.hpp:41