35 std::size_t channel_count)
noexcept {
39 !(sample_rate >= 8000.0) || sample_rate > 384000.0 ||
40 !std::isfinite(sample_rate) || channel_count == 0 ||
45 sample_rate_ = sample_rate;
46 channel_count_ = channel_count;
47 std::array<bool, kSampleHeritageMaximumBusBlocks> seen{};
48 std::size_t previous_type = 0;
49 bool have_previous_type =
false;
50 for (std::size_t index = 0; index < profile_.bus_count; ++index) {
51 const auto& spec = profile_.bus[index];
56 const auto type = spec.parameters.index();
57 if (type >= seen.size() || seen[type] ||
58 (have_previous_type && type < previous_type)) {
64 have_previous_type =
true;
65 const auto valid = std::visit(
66 [&](
const auto& block)
noexcept {
67 using Block = std::decay_t<
decltype(block)>;
68 if constexpr (std::is_same_v<
70 return prepare_noise(index, block);
72 return std::isfinite(block.drive) && block.drive >= 0.0f &&
73 block.drive <= 16.0f &&
74 std::isfinite(block.ceiling) &&
75 block.ceiling >= 0.001f && block.ceiling <= 4.0f;
82 if (spec.bypass) noise_[index] = {};
97 if (!prepared_)
return;
98 for (std::size_t index = 0; index < profile_.bus_count; ++index) {
106 bool any_voice_active)
noexcept {
107 return process_impl(buffer, {}, any_voice_active);
112 std::span<const std::uint8_t> voice_activity)
noexcept {
113 if (voice_activity.size() != buffer.num_samples())
115 return process_impl(buffer, voice_activity,
false);
121 std::span<const std::uint8_t> voice_activity,
122 bool constant_voice_activity)
noexcept {
124 if (buffer.num_channels() != channel_count_)
127 for (std::size_t index = 0; index < profile_.
bus_count; ++index) {
128 const auto& spec = profile_.
bus[index];
129 if (spec.bypass)
continue;
131 [&](
const auto& block)
noexcept {
132 using Block = std::decay_t<
decltype(block)>;
133 if constexpr (std::is_same_v<
135 process_noise(index, block, voice_activity,
136 constant_voice_activity, buffer);
138 process_output(block, buffer);
150 for (std::size_t index = 0; index < profile_.bus_count; ++index) {
151 const auto& spec = profile_.bus[index];
152 if (spec.bypass)
continue;
153 const auto* block = std::get_if<SampleHeritageBusNoiseIdleBlock>(
155 if (block ==
nullptr ||
156 block->seed_policy !=
159 auto& saved =
state.rng_states[
state.rng_state_count++];
160 saved.stage_index =
static_cast<std::uint8_t
>(index);
162 saved.random_state = noise_[index].random_state;
170 if (
state.rng_state_count >
state.rng_states.size())
172 std::size_t expected = 0;
173 for (std::size_t index = 0; index < profile_.bus_count; ++index) {
174 const auto& spec = profile_.bus[index];
175 if (spec.bypass)
continue;
176 const auto* block = std::get_if<SampleHeritageBusNoiseIdleBlock>(
178 if (block ==
nullptr ||
179 block->seed_policy !=
182 if (expected >=
state.rng_state_count)
184 const auto& saved =
state.rng_states[expected++];
185 if (saved.stage_index != index ||
188 if (saved.random_state == 0)
191 if (expected !=
state.rng_state_count)
194 for (std::size_t index = 0; index <
state.rng_state_count; ++index)
195 noise_[
state.rng_states[index].stage_index].random_state =
196 state.rng_states[index].random_state;
201 double frequency)
const noexcept {
202 if (!prepared_ || bus_index >= profile_.bus_count ||
203 !(frequency > 0.0) || frequency > sample_rate_ * 0.5 ||
204 !std::isfinite(frequency))
205 return std::numeric_limits<double>::quiet_NaN();
206 const auto&
runtime = noise_[bus_index];
207 double magnitude =
runtime.reference_normalization;
208 for (std::size_t shelf = 0; shelf <
runtime.shelf_count; ++shelf)
209 magnitude *= response_magnitude(
runtime.shelves[shelf], sample_rate_,
211 return 20.0 * std::log10(magnitude);
215 static constexpr std::size_t kMaximumTiltShelves = 20;
230 struct NoiseRuntime {
231 std::uint64_t seed = 0;
232 std::uint64_t random_state = 0;
233 std::array<Biquad, kMaximumTiltShelves> shelves{};
234 std::array<std::array<BiquadState, kMaximumTiltShelves>,
236 std::size_t shelf_count = 0;
237 double reference_normalization = 1.0;
240 static std::uint64_t next_random(std::uint64_t& state)
noexcept {
241 state ^= state >> 12;
242 state ^= state << 25;
243 state ^= state >> 27;
244 return state * UINT64_C(2685821657736338717);
247 static float bipolar_random(std::uint64_t& state)
noexcept {
248 const auto bits =
static_cast<std::uint32_t
>(next_random(state) >> 40);
249 return static_cast<float>(bits) * (2.0f / 16777215.0f) - 1.0f;
252 static Biquad high_shelf(
double sample_rate,
254 double gain_db)
noexcept {
255 constexpr double pi = 3.14159265358979323846;
256 const auto a = std::pow(10.0, gain_db / 40.0);
257 const auto omega = 2.0 *
pi * frequency / sample_rate;
258 const auto cosine = std::cos(omega);
259 const auto sine = std::sin(omega);
260 const auto alpha =
sine * std::sqrt(2.0) * 0.5;
261 const auto beta = 2.0 * std::sqrt(a) *
alpha;
262 const auto a0 = (
a + 1.0) - (a - 1.0) * cosine + beta;
264 a * ((
a + 1.0) + (
a - 1.0) * cosine + beta) / a0,
265 -2.0 * a * ((a - 1.0) + (a + 1.0) * cosine) / a0,
266 a * ((
a + 1.0) + (
a - 1.0) * cosine - beta) / a0,
267 2.0 * ((a - 1.0) - (a + 1.0) * cosine) / a0,
268 ((
a + 1.0) - (
a - 1.0) * cosine - beta) / a0,
272 static double response_magnitude(
const Biquad& filter,
274 double frequency)
noexcept {
275 constexpr double pi = 3.14159265358979323846;
276 const auto z = std::polar(1.0, -2.0 * pi * frequency / sample_rate);
277 const auto numerator = filter.b0 + filter.b1 *
z + filter.b2 *
z *
z;
278 const auto denominator = 1.0 + filter.a1 *
z + filter.a2 *
z *
z;
279 return std::abs(numerator / denominator);
282 bool prepare_noise(std::size_t index,
283 const SampleHeritageBusNoiseIdleBlock& block)
noexcept {
284 if (!std::isfinite(
block.noise_amplitude) ||
285 !std::isfinite(
block.idle_amplitude) ||
286 !std::isfinite(
block.tilt_db_per_octave) ||
287 !std::isfinite(
block.tilt_floor_hz) ||
288 !std::isfinite(
block.tilt_reference_hz) ||
289 block.noise_amplitude < 0.0f ||
block.noise_amplitude > 1.0f ||
290 block.idle_amplitude < 0.0f ||
block.idle_amplitude > 1.0f ||
291 block.tilt_db_per_octave < -24.0f ||
292 block.tilt_db_per_octave > 24.0f ||
block.tilt_floor_hz < 1.0 ||
293 block.tilt_reference_hz <
block.tilt_floor_hz ||
294 block.tilt_reference_hz >= sample_rate_ * 0.5 ||
297 (
block.seed_policy !=
301 ((std::max(
block.noise_amplitude,
block.idle_amplitude) != 0.0f ||
307 auto& runtime = noise_[
index];
308 runtime.seed =
block.seed;
309 runtime.random_state =
block.seed;
310 if (
block.tilt_db_per_octave == 0.0f)
return true;
312 const auto upper = sample_rate_ * 0.5;
313 auto lower =
block.tilt_floor_hz;
314 while (lower < upper && runtime.shelf_count < runtime.shelves.size()) {
315 const auto next = std::min(lower * 2.0, upper);
316 const auto octaves = std::log2(next / lower);
317 const auto center = std::sqrt(lower * next);
318 runtime.shelves[runtime.shelf_count++] = high_shelf(
319 sample_rate_, center,
block.tilt_db_per_octave * octaves);
322 double reference_gain = 1.0;
323 for (std::size_t shelf = 0; shelf < runtime.shelf_count; ++shelf)
324 reference_gain *= response_magnitude(
325 runtime.shelves[shelf], sample_rate_,
block.tilt_reference_hz);
326 runtime.reference_normalization = reference_gain > 0.0
327 ? 1.0 / reference_gain
332 static float process_biquad(
const Biquad& filter,
334 float input)
noexcept {
335 const auto output = filter.b0 * input + state.z1;
336 state.z1 = filter.b1 * input - filter.a1 * output + state.z2;
337 state.z2 = filter.b2 * input - filter.a2 * output;
338 return static_cast<float>(output);
341 void process_noise(std::size_t index,
342 const SampleHeritageBusNoiseIdleBlock& block,
343 std::span<const std::uint8_t> voice_activity,
344 bool constant_voice_activity,
345 BufferView<float> buffer)
noexcept {
346 auto& runtime = noise_[
index];
347 for (std::size_t frame = 0; frame <
buffer.num_samples(); ++frame) {
348 const auto voice_active = voice_activity.empty()
349 ? constant_voice_activity
350 : voice_activity[frame] != 0;
351 const auto noise_open =
353 for (std::size_t channel = 0; channel < channel_count_; ++channel) {
354 float generated = 0.0f;
355 if (
block.idle_amplitude != 0.0f)
356 generated += bipolar_random(runtime.random_state) *
357 block.idle_amplitude;
358 if (noise_open &&
block.noise_amplitude != 0.0f)
359 generated += bipolar_random(runtime.random_state) *
360 block.noise_amplitude;
361 for (std::size_t shelf = 0; shelf < runtime.shelf_count; ++shelf)
362 generated = process_biquad(
363 runtime.shelves[shelf],
364 runtime.filter_state[channel][shelf], generated);
365 buffer.channel(channel)[frame] +=
static_cast<float>(
366 generated * runtime.reference_normalization);
371 static void process_output(
const SampleHeritageBusOutputDriveBlock& block,
372 BufferView<float> buffer)
noexcept {
373 for (std::size_t channel = 0; channel <
buffer.num_channels(); ++channel)
374 for (
auto& sample :
buffer.channel(channel)) {
375 const auto driven = sample *
block.drive;
376 sample = std::isinf(driven)
377 ? std::copysign(
block.ceiling, driven)
378 :
block.ceiling * driven /
383 SampleHeritagePreparedProfile profile_{};
384 std::array<NoiseRuntime, kSampleHeritageMaximumBusBlocks> noise_{};
385 double sample_rate_ = 0.0;
386 std::size_t channel_count_ = 0;
387 bool prepared_ =
false;