62 static_assert(std::is_same_v<T, float> || std::is_same_v<T, double>);
63 static_assert(std::atomic<float>::is_always_lock_free && std::atomic<int>::is_always_lock_free);
95 if (!validConfiguration(spec, options))
98 const int source = State::sourceFactor(spec.sampleRate);
99 const int colorLatency = Color::latencyFor(options.oversampling, source);
100 const auto hp = highPassLayout(spec.sampleRate, options.lowCutHz);
101 const int totalLatency = colorLatency + hp.order / 2 + hp.block;
102 const auto buffer = [](std::size_t channels, std::size_t frames, std::size_t bytes) {
103 return channels * ((frames * bytes + 31) / 32 * 32) + 64;
105 auto bytes =
sizeof(State) + 64 + 2 * Color::allocationBound(options.oversampling, source) +
106 buffer(2, 256,
sizeof(
double)) + buffer(1, 256,
sizeof(
double)) +
107 buffer(1, 256,
sizeof(
double)) + buffer(1, colorLatency,
sizeof(
double)) +
108 buffer(2, 256,
sizeof(T)) + buffer(2, totalLatency,
sizeof(T));
110 bytes += 3 * ((
static_cast<std::size_t
>(hp.order) + 1) *
sizeof(double) + 64) +
121 return prepare(spec, options_);
127 if (!validConfiguration(spec, options))
129 auto next = std::make_unique<State>(spec, options,
getWidth());
130 state_ = std::move(next);
132 latency_.store(state_->latency, std::memory_order_relaxed);
140 if (!std::isfinite(width) || width < 0 || width > 1)
142 width_.store(width, std::memory_order_relaxed);
148 return width_.load(std::memory_order_relaxed);
158 return latency_.load(std::memory_order_relaxed);
163 return static_cast<Status>(status_.load(std::memory_order_relaxed));
168 return state_ ? state_->frame : 0;
212 const int count = buffer.getNumSamples();
213 if (buffer.getNumChannels() != 2 || count > state_->spec.maxBlockSize ||
214 (!generatedDelta.empty() && generatedDelta.size() !=
static_cast<std::size_t
>(count)))
216 if (
static_cast<std::uint64_t
>(count) >
217 std::numeric_limits<std::uint64_t>::max() - state_->frame)
221 auto *left = buffer.getChannel(0);
222 auto *right = buffer.getChannel(1);
225 if (!generatedDelta.empty() &&
226 (!generatedDelta.data() ||
227 overlaps(generatedDelta.data(),
228 static_cast<std::uint64_t
>(generatedDelta.size()) *
sizeof(
double), left,
229 static_cast<std::uint64_t
>(count) *
sizeof(T)) ||
230 overlaps(generatedDelta.data(),
231 static_cast<std::uint64_t
>(generatedDelta.size()) *
sizeof(
double), right,
232 static_cast<std::uint64_t
>(count) *
sizeof(T))))
234 const auto a =
reinterpret_cast<std::uintptr_t
>(left);
235 const auto b =
reinterpret_cast<std::uintptr_t
>(right);
236 if (
static_cast<std::uint64_t
>(a > b ? a - b : b - a) <
237 static_cast<std::uint64_t
>(count) *
sizeof(T))
239 for (
int i = 0; i < count; ++i)
240 if (!std::isfinite(left[i]) || !std::isfinite(right[i]))
242 state_->width.setTargetValue(
getWidth());
243 for (
int offset = 0; offset < count;)
245 const int size = std::min(256, count - offset);
246 state_->process(buffer.getSubView(offset, size),
247 generatedDelta.empty()
248 ? std::span<double>{}
249 : generatedDelta.subspan(
static_cast<std::size_t
>(offset),
250 static_cast<std::size_t
>(size)));
257 [[nodiscard]] std::vector<std::uint8_t>
getState()
const
263 return writer.
blob();
270 bool setState(
const std::uint8_t *data, std::size_t size)
276 std::array<bool, 3> seen{};
277 for (
const auto &entry : reader.
entries())
279 const int index = entry.key ==
"width" ? 0
280 : entry.key ==
"oversampling" ? 1
281 : entry.key ==
"lowCutHz" ? 2
285 if (seen[index] || entry.type != (index == 1 ? 1 : 0))
292 if (!std::isfinite(width) || width < 0 || width > 1 || !validOptions(options))
295 width_.store(width, std::memory_order_relaxed);
300 struct HighPassLayout
302 int order = 0, block = 0;
303 bool allReject =
false;
305 static HighPassLayout highPassLayout(
double rate,
double lowCut)
noexcept
307 HighPassLayout result;
310 const double transition =
311 std::min(std::max(50., lowCut * .25), std::min(lowCut * 1.5, rate - 2 * lowCut));
312 if (lowCut >= rate * .5 || transition < 30)
313 result.allReject =
true;
317 static_cast<int>(std::ceil(94 / (2.285 * twoPi<double> * transition / rate)));
318 if (result.order & 1)
321 while (result.block * 2 <= std::max(64,
static_cast<int>(std::round(rate / 93.75))))
326 static bool overlaps(
const void *left, std::uint64_t leftBytes,
const void *right,
327 std::uint64_t rightBytes)
noexcept
329 const auto a =
reinterpret_cast<std::uintptr_t
>(left);
330 const auto b =
reinterpret_cast<std::uintptr_t
>(right);
331 return a <= b ? b - a < leftBytes : a - b < rightBytes;
335 detail::TptSvfState<double> state{};
337 double rate, base, corridor, g = 0, gain = 0, dg = 0, dGain = 0;
340 void prepare(
int i,
double fs)
noexcept
344 const double first = i < 16 ? 210 : 45;
345 const double last = i < 16 ? std::min(14000., fs * .35) : 165;
346 corridor = std::log(last / first) / (i < 16 ? 15 : 3);
347 base = first * std::exp((i < 16 ? i : i - 16) * corridor);
349 double coefficient(
double t)
const noexcept
353 std::exp(.22 * corridor *
354 std::sin(twoPi<double> * (.029 + index * .0017) * t + index * 2.399963));
355 return std::tan(pi<double> * f / rate);
357 double amplitude(
double t)
const noexcept
359 const double db = (((index / 2 + index % 2) & 1) == 0 ? 1 : -1) *
360 (2.6 + .4 * std::sin(twoPi<double> * (.037 + index * .0013) * t +
362 return std::expm1(db * std::log(10.) / 20);
364 void position(std::uint64_t frame)
noexcept
366 const auto offset =
static_cast<int>(frame % 64);
367 const double t =
static_cast<double>(frame - offset) / rate;
368 const double future = t + 64. / rate;
371 dg = (coefficient(future) - g) / 64;
372 dGain = (amplitude(future) - gain) / 64;
373 for (
int i = 0; i < offset; ++i)
378 untilUpdate = 64 - offset;
380 double process(
double input, std::uint64_t frame)
noexcept
382 if (untilUpdate == 0)
384 const double a1 = 1 / (1 + g * (g + .5));
385 const double a2 = g * a1;
386 const double a3 = g * a2;
398 std::array<Band, 20> bands{};
399 AudioBuffer<double>
copy, delta;
400 DryWetMixer<T, 2> dry;
401 DryWetMixer<double, 1> linear;
402 Convolver<double> highPass;
403 detail::StereoColor<detail::ClipperCurve::AsymmetricKnee> tube;
404 detail::StereoColor<detail::ClipperCurve::SymmetricKnee> tape;
405 SmoothedValue<double> width;
406 std::uint64_t frame = 0;
408 bool useHighPass =
false, allReject =
false, fault =
false;
410 State(AudioSpec environment, Options options,
float initialWidth)
411 : spec(environment), tube(options.oversampling, sourceFactor(spec.sampleRate)),
412 tape(options.oversampling, sourceFactor(spec.sampleRate)),
latency(tube.
latency())
414 for (
int i = 0; i < 20; ++i)
415 bands[i].
prepare(i, spec.sampleRate);
417 delta.resize(1, 256);
418 linear.prepare({spec.sampleRate, 256, 1});
419 linear.setLatencyCompensation(tube.latency());
420 const auto hp = highPassLayout(spec.sampleRate, options.lowCutHz);
421 allReject = hp.allReject;
426 highPass.prepare(hp.block, taps.data(),
static_cast<int>(taps.size()));
427 latency += hp.order / 2 + hp.block;
430 dry.prepare({spec.sampleRate, 256, 2});
431 dry.setLatencyCompensation(latency);
432 width.prepare(spec.sampleRate, 5);
433 width.setSmoothingType(SmoothedValue<double>::SmoothingType::Linear);
434 reset(0, initialWidth);
436 static int sourceFactor(
double rate)
noexcept
438 return rate <= 48000 ? 8 : rate <= 96000 ? 4 : rate <= 192000 ? 2 : 1;
440 void reset(std::uint64_t position,
float targetWidth)
noexcept
443 for (
auto &band : bands)
446 band.position(position);
453 width.reset(targetWidth);
456 void process(AudioBufferView<T> input, std::span<double> generatedDelta)
noexcept
458 const int count = input.getNumSamples();
460 double *l =
copy.getChannel(0), *r =
copy.getChannel(1), *d = delta.getChannel(0);
463 for (
int i = 0; i < count; ++i)
466 for (
int j = 0; j < 20; ++j)
467 (j & 1 ? r[i] : l[i]) +=
468 bands[j].process(input.getChannel(j & 1)[i], frame + i);
469 d[i] = .5 * (l[i] - r[i]);
470 if (!std::isfinite(l[i]) || !std::isfinite(r[i]) || !std::isfinite(d[i]))
475 linear.pushDry(delta.toView().getSubView(0, count));
476 double *lc[]{l}, *rc[]{r};
477 tube.process({lc, 1, count});
478 tape.process({rc, 1, count});
479 const auto *aligned = linear.getDryChannel(0);
480 for (
int i = 0; i < count; ++i)
481 d[i] = aligned[i] + .1 * (.5 * (l[i] - r[i]) - aligned[i]);
483 highPass.processInPlace(d, count);
484 for (
int i = 0; i < count; ++i)
485 if (!std::isfinite(d[i]))
488 std::fill_n(d, count, 0.);
491 const auto *dl = dry.getDryChannel(0), *dr = dry.getDryChannel(1);
492 std::array<double, 256> changes{};
493 for (
int i = 0; i < count; ++i)
495 const double w = width.getNextValue();
496 const double change = fault || w == 0 ? 0 : 8 * w * d[i];
497 const double outL =
static_cast<double>(dl[i]) + change;
498 const double outR =
static_cast<double>(dr[i]) - change;
499 if (!std::isfinite(outL) || !std::isfinite(outR) ||
500 std::abs(outL) > std::numeric_limits<T>::max() ||
501 std::abs(outR) > std::numeric_limits<T>::max())
505 for (
int i = 0; i < count; ++i)
507 if (!generatedDelta.empty())
508 generatedDelta[i] = fault ? 0 : changes[i];
510 input.getChannel(0)[i] =
511 fault || changes[i] == 0 ? dl[i] :
static_cast<T
>(dl[i] + changes[i]);
512 input.getChannel(1)[i] =
513 fault || changes[i] == 0 ? dr[i] :
static_cast<T
>(dr[i] - changes[i]);
515 frame +=
static_cast<std::uint64_t
>(count);
519 static bool validConfiguration(
const AudioSpec &spec, Options options)
noexcept
521 return spec.isValid() && spec.numChannels == 2 && (spec.sampleRate >= 8000) &&
522 (spec.sampleRate <= 384000) && validOptions(options);
524 static bool validOptions(Options options)
noexcept
526 return options.oversampling >= 1 && options.oversampling <= 16 &&
527 (options.oversampling & (options.oversampling - 1)) == 0 &&
528 std::isfinite(options.lowCutHz) &&
529 (options.lowCutHz == 0 || (options.lowCutHz >= 20 && options.lowCutHz <= 5000));
531 bool publish(
Status status)
noexcept
533 status_.store(
static_cast<int>(status), std::memory_order_relaxed);
536 std::unique_ptr<State> state_;
538 std::atomic<float> width_{0};