55 static_assert(std::is_floating_point_v<SampleType>);
58 std::array<SampleType, MaxBands>
peak_db{};
64template <
typename SampleType =
float, std::
size_t MaxInputBins = 4097, std::
size_t MaxBands = 256>
66 static_assert(std::is_floating_point_v<SampleType>);
67 static_assert(MaxInputBins >= 2);
68 static_assert(MaxInputBins <= std::numeric_limits<std::uint32_t>::max() / 2u + 1u);
69 static_assert(MaxBands > 0);
77 static_cast<std::uint32_t
>(std::min<std::size_t>(1024, 2 * (MaxInputBins - 1)));
78 config.band_count = std::min<std::size_t>(128, MaxBands);
85 std::array<std::size_t, MaxBands> first_bin{};
86 std::array<std::size_t, MaxBands> end_bin{};
87 std::array<SampleType, MaxBands> center_hz{};
88 std::array<SampleType, MaxInputBins> weighting_db{};
97 if (!std::isfinite(
config.sample_rate) ||
config.sample_rate < 1.0 ||
99 config.fft_size / 2u + 1u > MaxInputBins ||
config.band_count == 0 ||
100 config.band_count > MaxBands || !std::isfinite(
config.minimum_hz) ||
101 !std::isfinite(
config.maximum_hz) ||
config.minimum_hz < 0.0 ||
105 !std::isfinite(
config.floor_db) || !std::isfinite(
config.ceiling_db) ||
109 !std::isfinite(
config.peak_decay_db_per_frame) ||
110 config.peak_decay_db_per_frame < 0.0 || !scale_valid || !aggregation_valid ||
111 !weighting_valid || !representable_(
config.floor_db) ||
112 !representable_(
config.ceiling_db) || !representable_(
config.peak_decay_db_per_frame)) {
116 const auto input_bins =
static_cast<std::size_t
>(
config.fft_size / 2u + 1u);
117 const double bin_hz =
config.sample_rate /
static_cast<double>(
config.fft_size);
118 for (std::size_t band = 0; band <
config.band_count; ++band) {
119 const double t0 =
static_cast<double>(band) /
static_cast<double>(
config.band_count);
121 static_cast<double>(band + 1) /
static_cast<double>(
config.band_count);
122 const double low = band_edge_(
config, t0);
123 const double high = band_edge_(
config, t1);
124 const auto first =
static_cast<std::size_t
>(std::ceil(low / bin_hz));
125 const auto end = band + 1 ==
config.band_count
126 ?
static_cast<std::size_t
>(std::floor(high / bin_hz)) + 1
127 :
static_cast<std::size_t
>(std::ceil(high / bin_hz));
128 first_bin[band] = std::min(
first, input_bins);
129 end_bin[band] = std::min(end, input_bins);
131 ? std::sqrt(low * high)
132 : (low + high) * 0.5;
133 center_hz[band] =
static_cast<SampleType
>(
center);
135 for (std::size_t bin = 0; bin < input_bins; ++bin) {
136 weighting_db[bin] =
static_cast<SampleType
>(
137 calculate_weighting_db_(
static_cast<double>(bin) * bin_hz,
config.weighting));
141 input_bin_count_ = input_bins;
142 first_bin_ = first_bin;
144 weighting_db_ = weighting_db;
145 centers_hz_ = center_hz;
155 for (std::size_t band = 0; band < config_.band_count; ++band) {
156 SampleType target =
static_cast<SampleType
>(config_.floor_db);
157 const bool band_has_bins = first_bin_[band] < end_bin_[band];
159 for (std::size_t bin = first_bin_[band]; bin < end_bin_[band]; ++bin)
160 target = std::max(target, sanitize_bin_(
magnitude_db[bin], bin));
161 }
else if (band_has_bins) {
162 long double power = 0.0L;
163 for (std::size_t bin = first_bin_[band]; bin < end_bin_[band]; ++bin) {
165 power += std::pow(10.0L,
static_cast<long double>(db) / 10.0L);
167 power /=
static_cast<long double>(end_bin_[band] - first_bin_[band]);
168 const auto aggregated_db = 10.0L * std::log10(power);
169 target =
static_cast<SampleType
>(
170 std::clamp(aggregated_db,
static_cast<long double>(config_.floor_db),
171 static_cast<long double>(config_.ceiling_db)));
173 target = std::clamp(target,
static_cast<SampleType
>(config_.floor_db),
174 static_cast<SampleType
>(config_.ceiling_db));
177 frame_.magnitude_db[band] = target;
178 frame_.peak_db[band] = target;
179 peak_hold_remaining_[band] = config_.peak_hold_frames;
183 auto& smoothed = frame_.magnitude_db[band];
184 const auto coefficient =
185 static_cast<SampleType
>(target > smoothed ? config_.attack : config_.release);
186 smoothed = std::lerp(smoothed, target, coefficient);
188 auto&
peak = frame_.peak_db[band];
189 if (smoothed >=
peak) {
191 peak_hold_remaining_[band] = config_.peak_hold_frames;
192 }
else if (peak_hold_remaining_[band] > 0) {
193 --peak_hold_remaining_[band];
195 const auto decay =
static_cast<SampleType
>(config_.peak_decay_db_per_frame);
196 const auto lowest = std::numeric_limits<SampleType>::lowest();
199 peak = std::max(smoothed, decayed);
204 frame_.band_count = config_.band_count;
205 frame_.sequence = ++sequence_;
210 initialized_ =
false;
213 frame_.band_count = config_.band_count;
214 for (std::size_t band = 0; band < config_.band_count; ++band) {
215 frame_.magnitude_db[band] =
static_cast<SampleType
>(config_.floor_db);
216 frame_.peak_db[band] =
static_cast<SampleType
>(config_.floor_db);
217 frame_.center_hz[band] = centers_hz_[band];
219 peak_hold_remaining_.fill(0);
230 return input_bin_count_;
237 static bool representable_(
double value)
noexcept {
238 return value >=
static_cast<double>(std::numeric_limits<SampleType>::lowest()) &&
239 value <=
static_cast<double>(std::numeric_limits<SampleType>::max());
242 static double band_edge_(
const SpectrumTraceConfig&
config,
double t)
noexcept {
248 static double calculate_weighting_db_(
double frequency_hz,
252 if (!(frequency_hz > 0.0) || !std::isfinite(frequency_hz))
254 const double f2 = frequency_hz * frequency_hz;
255 constexpr double f1 = 20.6;
256 constexpr double f2a = 107.7;
257 constexpr double f3 = 737.9;
258 constexpr double f4 = 12200.0;
260 const double numerator = f4 * f4 * f2 * f2;
261 const double denominator =
262 (f2 + f1 * f1) * std::sqrt((f2 + f2a * f2a) * (f2 + f3 * f3)) * (f2 + f4 * f4);
263 return 20.0 * std::log10(numerator / denominator) + 2.0;
265 const double numerator =
f4 *
f4 *
f2;
266 const double denominator = (
f2 +
f1 *
f1) * (f2 + f4 * f4);
267 return 20.0 * std::log10(numerator / denominator) + 0.06;
270 SampleType sanitize_bin_(SampleType value, std::size_t bin)
const noexcept {
271 const auto floor =
static_cast<SampleType
>(config_.floor_db);
272 const auto ceiling =
static_cast<SampleType
>(config_.ceiling_db);
273 if (!std::isfinite(value))
277 const auto correction = weighting_db_[bin];
278 const auto maximum = std::numeric_limits<SampleType>::max();
279 const auto lowest = std::numeric_limits<SampleType>::lowest();
280 if (correction > SampleType{0} &&
value > SampleType{0} && correction >
maximum -
value)
282 if (correction < SampleType{0} &&
value < SampleType{0} && correction < lowest -
value)
284 const auto weighted =
static_cast<SampleType
>(
value + correction);
285 return std::clamp(weighted, floor, ceiling);
288 SpectrumTraceConfig config_{};
289 std::size_t input_bin_count_ = 0;
290 std::array<std::size_t, MaxBands> first_bin_{};
291 std::array<std::size_t, MaxBands> end_bin_{};
292 std::array<SampleType, MaxInputBins> weighting_db_{};
293 std::array<SampleType, MaxBands> centers_hz_{};
294 std::array<std::uint32_t, MaxBands> peak_hold_remaining_{};
296 std::uint64_t sequence_ = 0;
297 bool initialized_ =
false;
Definition spectrum_trace.hpp:65
const Frame & current() const noexcept
Same-thread view. This reference is not a concurrent publication API.
Definition spectrum_trace.hpp:223
static constexpr std::uint32_t algorithmic_latency_samples() noexcept
Definition spectrum_trace.hpp:232
bool configure(const SpectrumTraceConfig &config) noexcept
Control-thread operation.
Definition spectrum_trace.hpp:84
bool process_frame(std::span< const SampleType > magnitude_db) noexcept
Rejected frames leave the current trace and sequence unchanged.
Definition spectrum_trace.hpp:151
std::size_t input_bin_count() const noexcept
Definition spectrum_trace.hpp:229
const SpectrumTraceConfig & config() const noexcept
Definition spectrum_trace.hpp:226
SpectrumTraceFrameT< SampleType, MaxBands > Frame
Definition spectrum_trace.hpp:72
SpectrumTraceT() noexcept
Definition spectrum_trace.hpp:74
void reset() noexcept
Definition spectrum_trace.hpp:209
Definition additive_bank.hpp:218
@ first
Definition dither.hpp:23
@ center
Definition multi_channel_meter.hpp:68
@ peak
Sample peak. Literal, fastest-responding.
Definition feedforward_compressor.hpp:119
float magnitude_db(const BiquadT< SampleType > &filter, double freq_hz, double sample_rate)
Ask a live filter what it looks like: the magnitude, in dB, that this BiquadT is applying at freq_hz ...
Definition frequency_response.hpp:130
SpectrumFrequencyWeighting
Definition spectrum_trace.hpp:35
@ a
Definition spectrum_trace.hpp:35
@ none
Definition spectrum_trace.hpp:35
@ c
Definition spectrum_trace.hpp:35
SpectrumTraceT< float > SpectrumTrace
Definition spectrum_trace.hpp:300
@ decay
Definition envelope.hpp:60
SpectrumTraceT< double > SpectrumTrace64
Definition spectrum_trace.hpp:301
SpectrumBandScale
Definition spectrum_trace.hpp:33
@ logarithmic
Definition spectrum_trace.hpp:33
@ linear
Definition spectrum_trace.hpp:33
@ logarithmic
Definition modulation_curve.hpp:29
SpectrumBandAggregation
Definition spectrum_trace.hpp:34
@ maximum
Definition spectrum_trace.hpp:34
@ mean_power
Definition spectrum_trace.hpp:34
@ a
Definition formant_filter_bank.hpp:20
@ none
Definition dither.hpp:17
@ linear
2-point. Cheapest; audible imaging when pitching up.
Definition granular.hpp:133
@ value
the widget's primary value (Knob, Fader, RangeSlider, Toggle, ProgressBar)
Definition binding_diagnostics.hpp:19
@ f2
Definition input_events.hpp:61
@ f4
Definition input_events.hpp:61
@ f1
Definition input_events.hpp:61
Definition spectrum_trace.hpp:37
double maximum_hz
Definition spectrum_trace.hpp:42
double release
Definition spectrum_trace.hpp:49
SpectrumBandAggregation aggregation
Definition spectrum_trace.hpp:44
double attack
Definition spectrum_trace.hpp:48
SpectrumBandScale band_scale
Definition spectrum_trace.hpp:43
double sample_rate
Definition spectrum_trace.hpp:38
SpectrumFrequencyWeighting weighting
Definition spectrum_trace.hpp:45
double floor_db
Definition spectrum_trace.hpp:46
double minimum_hz
Definition spectrum_trace.hpp:41
double ceiling_db
Definition spectrum_trace.hpp:47
std::uint32_t fft_size
Definition spectrum_trace.hpp:39
std::size_t band_count
Definition spectrum_trace.hpp:40
double peak_decay_db_per_frame
Definition spectrum_trace.hpp:51
std::uint32_t peak_hold_frames
Definition spectrum_trace.hpp:50
Definition spectrum_trace.hpp:54
std::size_t band_count
Definition spectrum_trace.hpp:60
std::array< SampleType, MaxBands > peak_db
Definition spectrum_trace.hpp:58
std::uint64_t sequence
Definition spectrum_trace.hpp:61
std::array< SampleType, MaxBands > center_hz
Definition spectrum_trace.hpp:59
std::array< SampleType, MaxBands > magnitude_db
Definition spectrum_trace.hpp:57