Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
dither.hpp
Go to the documentation of this file.
1#pragma once
2
5
7#include <pulp/signal/rng.hpp>
8
9#include <algorithm>
10#include <cmath>
11#include <cstdint>
12#include <limits>
13
14namespace pulp::signal {
15
16enum class DitherMode : std::uint8_t {
17 none = 0,
18 tpdf = 1,
19};
20
21enum class NoiseShapingOrder : std::uint8_t {
22 none = 0,
23 first = 1,
24 second = 2,
25};
26
31template <typename SampleType>
32constexpr SampleType tpdf_difference(SampleType first, SampleType second) noexcept {
33 return first - second;
34}
35
36template <typename SampleType>
37constexpr SampleType tpdf_centered_sum(SampleType first, SampleType second) noexcept {
38 return first + second - SampleType{1};
39}
40
41template <typename SampleType> struct NoiseShaperStateT {
42 SampleType error_1{};
43 SampleType error_2{};
44
45 constexpr void reset() noexcept {
46 error_1 = SampleType{};
47 error_2 = SampleType{};
48 }
49};
50
54template <typename SampleType> struct DitheredQuantizationResultT {
55 SampleType output{};
56 SampleType shaped_input{};
57 SampleType dither{};
58 SampleType quantization_error{};
59 bool recovered = false;
60};
61
63template <typename SampleType> inline SampleType quantization_step(SampleType bits) noexcept {
64 if (!std::isfinite(bits))
65 return SampleType{1};
66 return std::exp2(-(std::max(bits, SampleType{1}) - SampleType{1}));
67}
68
75template <typename SampleType>
76inline DitheredQuantizationResultT<SampleType>
77quantize_with_error_feedback(SampleType input, SampleType step, DitherMode dither_mode,
79 SampleType first_uniform = SampleType{},
80 SampleType second_uniform = SampleType{}) noexcept {
81 DitheredQuantizationResultT<SampleType> result{};
82 if (!(std::isfinite(input) && std::isfinite(step) && step > SampleType{})) {
83 state.reset();
84 result.recovered = true;
85 return result;
86 }
87
88 SampleType feedback{};
89 if (shaping == NoiseShapingOrder::first) {
90 feedback = -state.error_1;
91 } else if (shaping == NoiseShapingOrder::second) {
92 feedback = SampleType{-2} * state.error_1 + state.error_2;
93 }
94
95 result.dither = dither_mode == DitherMode::tpdf
96 ? tpdf_difference(first_uniform, second_uniform) * step
97 : SampleType{};
98 result.shaped_input = input + feedback + result.dither;
99 const SampleType scaled = result.shaped_input / step;
100 if (!std::isfinite(result.shaped_input) || !std::isfinite(scaled) ||
101 std::abs(scaled) > static_cast<SampleType>(std::numeric_limits<std::int64_t>::max() / 2)) {
102 state.reset();
103 result.recovered = true;
104 return result;
105 }
106
107 result.output = std::round(scaled) * step;
108 if (!std::isfinite(result.output)) {
109 state.reset();
110 result = {};
111 result.recovered = true;
112 return result;
113 }
114
115 // Retain the current innovation: rounding residual plus non-subtractive
116 // dither. Feeding this value back produces the FIR noise-transfer
117 // functions documented above while shaping both sources of output noise.
118 result.quantization_error = result.output - input - feedback;
119 const SampleType error_limit = SampleType{4} * step;
120 const SampleType bounded = std::clamp(result.quantization_error, -error_limit, error_limit);
121 state.error_2 = snap_to_zero(state.error_1);
123 return result;
124}
125
131template <typename SampleType = float> class DitherQuantizerT {
132 public:
133 static constexpr std::uint32_t default_seed = 0x2C1B3A5Du;
134
135 void set_bits(SampleType bits) noexcept {
136 if (!std::isfinite(bits)) {
137 bits_ = SampleType{24};
138 state_.reset();
139 return;
140 }
141 const SampleType sanitized = std::clamp(bits, SampleType{1}, SampleType{24});
142 if (sanitized != bits_) {
143 // A continuous bit-depth control changes the quantizer step every
144 // sample. Preserve the error history in signal units so shaping
145 // remains active through that glide, while applying the same
146 // four-step safety bound used by the quantizer transition. This
147 // also makes a coarse-to-fine jump safe without turning every
148 // ordinary automation sample into a state reset.
149 const SampleType error_limit = SampleType{4} * quantization_step(sanitized);
150 state_.error_1 = snap_to_zero(
151 std::clamp(state_.error_1, -error_limit, error_limit));
152 state_.error_2 = snap_to_zero(
153 std::clamp(state_.error_2, -error_limit, error_limit));
154 }
155 bits_ = sanitized;
156 }
157 void set_dither_mode(DitherMode mode) noexcept {
158 if (mode != dither_mode_) state_.reset();
159 dither_mode_ = mode;
160 }
162 if (order != shaping_) state_.reset();
163 shaping_ = order;
164 }
165 void set_seed(std::uint32_t seed) noexcept {
166 seed_ = seed == 0u ? default_seed : seed;
167 rng_.seed(seed_);
168 }
169
172 void sync_configuration_from(const DitherQuantizerT& other) noexcept {
173 bits_ = other.bits_;
174 dither_mode_ = other.dither_mode_;
175 shaping_ = other.shaping_;
176 seed_ = other.seed_;
177 }
178
179 void reset() noexcept {
180 state_.reset();
181 rng_.seed(seed_);
182 }
183
184 SampleType process(SampleType input) noexcept {
185 if (bits_ >= SampleType{24})
186 return input;
187 SampleType first{};
188 SampleType second{};
189 if (dither_mode_ == DitherMode::tpdf) {
190 first = rng_.template next_unit<SampleType>();
191 second = rng_.template next_unit<SampleType>();
192 }
193 return quantize_with_error_feedback(input, quantization_step(bits_), dither_mode_, shaping_,
194 state_, first, second)
195 .output;
196 }
197
198 void process(SampleType* samples, int count) noexcept {
199 if (samples == nullptr || count <= 0)
200 return;
201 for (int i = 0; i < count; ++i)
202 samples[i] = process(samples[i]);
203 }
204
205 SampleType bits() const noexcept {
206 return bits_;
207 }
208 DitherMode dither_mode() const noexcept {
209 return dither_mode_;
210 }
212 return shaping_;
213 }
214 const NoiseShaperStateT<SampleType>& state() const noexcept {
215 return state_;
216 }
217 std::uint32_t rng_state() const noexcept {
218 return rng_.state();
219 }
220 std::uint32_t seed() const noexcept {
221 return seed_;
222 }
223 static constexpr int latency_samples() noexcept {
224 return 0;
225 }
226
227 private:
228 SampleType bits_{24};
229 DitherMode dither_mode_ = DitherMode::none;
231 NoiseShaperStateT<SampleType> state_{};
232 std::uint32_t seed_ = default_seed;
233 Xorshift32 rng_{default_seed};
234};
235
238
239} // namespace pulp::signal
Stateful deterministic wrapper for per-sample and block processing.
Definition dither.hpp:131
float bits() const noexcept
Definition dither.hpp:205
void set_noise_shaping(NoiseShapingOrder order) noexcept
Definition dither.hpp:161
std::uint32_t seed() const noexcept
Definition dither.hpp:220
SampleType process(SampleType input) noexcept
Definition dither.hpp:184
void reset() noexcept
Definition dither.hpp:179
DitherMode dither_mode() const noexcept
Definition dither.hpp:208
void set_seed(std::uint32_t seed) noexcept
Definition dither.hpp:165
static constexpr int latency_samples() noexcept
Definition dither.hpp:223
void sync_configuration_from(const DitherQuantizerT &other) noexcept
Copy policy and reset seed without copying another instance's live error memory or random-stream posi...
Definition dither.hpp:172
static constexpr std::uint32_t default_seed
Definition dither.hpp:133
void set_bits(SampleType bits) noexcept
Definition dither.hpp:135
void process(SampleType *samples, int count) noexcept
Definition dither.hpp:198
void set_dither_mode(DitherMode mode) noexcept
Definition dither.hpp:157
std::uint32_t rng_state() const noexcept
Definition dither.hpp:217
NoiseShapingOrder noise_shaping() const noexcept
Definition dither.hpp:211
const NoiseShaperStateT< SampleType > & state() const noexcept
Definition dither.hpp:214
Definition additive_bank.hpp:218
@ feedback
Definition comb_filter.hpp:18
NoiseShapingOrder
Definition dither.hpp:21
@ none
Definition dither.hpp:22
@ first
Definition dither.hpp:23
@ second
Definition dither.hpp:24
DitheredQuantizationResultT< SampleType > quantize_with_error_feedback(SampleType input, SampleType step, DitherMode dither_mode, NoiseShapingOrder shaping, NoiseShaperStateT< SampleType > &state, SampleType first_uniform=SampleType{}, SampleType second_uniform=SampleType{}) noexcept
One allocation-free error-feedback transition.
Definition dither.hpp:77
DitherQuantizerT< float > DitherQuantizer
Definition dither.hpp:236
SampleType quantization_step(SampleType bits) noexcept
Mid-tread quantization step for a continuous bit-depth control.
Definition dither.hpp:63
@ bounded
Definition fm_operator_engine.hpp:35
DitherQuantizerT< double > DitherQuantizer64
Definition dither.hpp:237
T snap_to_zero(T x)
Returns 0 if |x| < snap_threshold, else x.
Definition denormal.hpp:45
@ u
Definition formant_filter_bank.hpp:20
@ i
Definition formant_filter_bank.hpp:20
DitherMode
Definition dither.hpp:16
@ tpdf
Definition dither.hpp:18
@ none
Definition dither.hpp:17
constexpr SampleType tpdf_difference(SampleType first, SampleType second) noexcept
Converts two independent uniform draws in [0, 1) to unit-peak TPDF noise.
Definition dither.hpp:32
constexpr SampleType tpdf_centered_sum(SampleType first, SampleType second) noexcept
Definition dither.hpp:37
SequencerStateChannel is the audio-safe transport for a plugin's structured NON-parameter state: the ...
Definition audio_device_manager.hpp:48
Deterministic randomness for modulation: a counter-free PRNG, a Gaussian draw, a stateless purpose-ke...
Public intermediate returned by the pure quantizer transition.
Definition dither.hpp:54
SampleType shaped_input
Definition dither.hpp:56
bool recovered
Definition dither.hpp:59
SampleType quantization_error
Definition dither.hpp:58
SampleType dither
Definition dither.hpp:57
SampleType output
Definition dither.hpp:55
Definition dither.hpp:41
SampleType error_2
Definition dither.hpp:43
constexpr void reset() noexcept
Definition dither.hpp:45
SampleType error_1
Definition dither.hpp:42