33 : frames_(frames), block_(block),
34 values_(job.allocate<double>(12 * static_cast<std::uint64_t>(block)))
37 center_ = psi(
static_cast<double>(frames) * .5 + .5)[0];
40 [[nodiscard]]
const double *
get(std::size_t leaf,
int side,
bool diagonal =
false)
42 const auto slot = leaf % 3;
43 if (cached_[slot] !=
static_cast<std::int64_t
>(leaf))
45 auto *data = values_.get() + slot * 4 * block_;
46 std::fill_n(data, 4 * block_, 0.0);
47 const auto first =
static_cast<std::int64_t
>(leaf) * block_;
48 const int count =
static_cast<int>(std::min<std::int64_t>(block_, frames_ - first));
49 std::array<double, 2> left{}, right{};
50 std::int64_t previousLeft = 0, previousRight = 0;
51 for (
int i = 0; i < count; ++i)
53 const auto frame = first + i;
54 const auto dl = frame + (frame % 2 ? 2 : 1);
55 auto dr = frames_ - frame;
59 left = psi(
static_cast<double>(dl) * .5);
60 right = psi(
static_cast<double>(dr) * .5);
64 if (dl != previousLeft)
66 const double reciprocal = 2 /
static_cast<double>(previousLeft);
67 left[0] += reciprocal;
68 left[1] -= reciprocal * reciprocal;
70 if (dr != previousRight)
72 const double reciprocal = 2 /
static_cast<double>(dr);
73 right[0] -= reciprocal;
74 right[1] += reciprocal * reciprocal;
79 data[i] = .5 * (left[0] - center_);
80 data[block_ + i] = -.5 * (right[0] - center_);
81 constexpr double divisor = 4 * std::numbers::pi * std::numbers::pi;
82 data[2 * block_ + i] = left[1] / divisor;
83 data[3 * block_ + i] = right[1] / divisor;
85 cached_[slot] =
static_cast<std::int64_t
>(leaf);
87 return values_.get() + (slot * 4 + side + (diagonal ? 2 : 0)) * block_;
91 [[nodiscard]]
static std::array<double, 2> psi(
double z)
noexcept
96 const double u = 1 / z;
101 const double u = 1 / z, v = u * u;
104 p += std::log(z) - u / 2 - v * (1. / 12 - v * (1. / 120 - v *
105 (1. / 252 - v * (1. / 240 - v / 132))));
106 q += u + v / 2 + u * v * (1. / 6 - v * (1. / 30 - v *
107 (1. / 42 - v * (1. / 30 - v * 5. / 66))));
110 std::int64_t frames_;
113 std::array<std::int64_t, 3> cached_{};
114 std::unique_ptr<double[]> values_;
122 static constexpr std::size_t cacheLeaves = 5;
127 : source_(source), spec_(spec), expected_(fingerprint), peak_(peak), job_(job),
128 options_(options), cursor_(std::move(cursor))
131 while (block_ < spec.
frames && block_ < 4096)
133 leaves_ =
static_cast<std::uint64_t
>(spec.
frames / block_ + (spec.
frames % block_ != 0));
135 audio_ = job.
allocate<T>(cacheLeaves *
static_cast<std::uint64_t
>(block_) * spec.
channels);
136 gain_ = job.
allocate<
double>(cacheLeaves *
static_cast<std::uint64_t
>(block_));
141 [[nodiscard]]
int block() const noexcept
145 [[nodiscard]] std::int64_t
frames() const noexcept
153 [[nodiscard]] std::size_t
leaves() const noexcept
155 return static_cast<std::size_t
>(leaves_);
159 return peak_ > 0 ? peak_ : 1;
165 [[nodiscard]]
double endpoint(
int side)
const noexcept
167 return endpoints_[side];
183 return minimum_ == maximum_;
191 [[nodiscard]]
const T *
audio(std::size_t leaf,
int channel)
194 return audio_.get() + (leaf % cacheLeaves * spec_.
channels + channel) * block_;
196 [[nodiscard]]
const double *
gain(std::size_t leaf)
199 return gain_.get() + leaf % cacheLeaves * block_;
202 void readAudio(
int channel, std::int64_t first,
int count,
double *output)
204 read(first, count, output, [&](std::size_t leaf,
int offset,
int length,
double *out) {
205 const auto *input =
audio(leaf, channel) + offset;
206 for (
int i = 0; i < length; ++i)
207 out[i] =
static_cast<double>(input[i]) /
normalizer();
212 read(first, count, output, [&](std::size_t leaf,
int offset,
int length,
double *out) {
213 const auto *input =
gain(leaf) + offset;
214 for (
int i = 0; i < length; ++i)
215 out[i] = input[i] - baseline_;
220 if (source_.getSpec() != spec_)
225 template <
class Sample>
void read(std::int64_t first,
int count,
double *output, Sample sample)
227 if (first < 0 || count < 0 || count > spec_.
frames || first > spec_.
frames - count)
231 const int offset =
static_cast<int>(first % block_);
232 const int length = std::min(block_ - offset, count);
233 sample(
static_cast<std::size_t
>(first / block_), offset, length, output);
239 void load(std::size_t leaf)
243 const auto slot = leaf % cacheLeaves;
244 if (cached_[slot] ==
static_cast<std::int64_t
>(leaf))
246 const auto first =
static_cast<std::int64_t
>(leaf) * block_;
247 const int count =
static_cast<int>(std::min<std::int64_t>(block_, spec_.
frames - first));
248 auto *data = audio_.get() + slot * spec_.
channels * block_;
249 auto *control = gain_.get() + slot * block_;
250 std::fill_n(data, spec_.
channels * block_, T(0));
251 for (
int offset = 0; offset < count;)
253 const int length = std::min(options_.
blockFrames, count - offset);
255 for (
int c = 0; c < spec_.
channels; ++c)
256 channels[c] = data + c * block_ + offset;
261 OfflineFingerprint fingerprint;
262 for (
int i = 0; i < count; ++i)
264 const double gain = cursor_(first + i);
265 if (!(
gain > 0) || !std::isfinite(
gain))
268 for (
int c = 0; c < spec_.
channels; ++c)
270 const T x = data[c * block_ + i];
271 if (!std::isfinite(x))
273 if (std::abs(
static_cast<double>(x)) > peak_)
278 if (verified_ && fingerprint != fingerprints_[leaf])
281 fingerprints_[leaf] = fingerprint;
282 cached_[slot] =
static_cast<std::int64_t
>(leaf);
287 OfflineFingerprint fingerprint;
288 double firstGain = 1, lastGain = 1;
289 for (std::size_t leaf = 0; leaf < leaves_; ++leaf)
291 const auto first =
static_cast<std::int64_t
>(leaf) * block_;
293 static_cast<int>(std::min<std::int64_t>(block_, spec_.
frames - first));
294 const auto *control =
gain(leaf);
295 const auto *data =
audio(leaf, 0);
297 firstGain = control[0];
298 lastGain = control[count - 1];
299 for (
int i = 0; i < count; ++i)
301 minimum_ = std::min(minimum_, control[i]);
302 maximum_ = std::max(maximum_, control[i]);
303 for (
int c = 0; c < spec_.
channels; ++c)
305 const T x = data[c * block_ + i];
308 std::max(scalarPeak_,
309 std::abs(
static_cast<double>(x) /
normalizer() * control[i]));
315 if (fingerprint != expected_)
319 endpoints_ = {firstGain, lastGain};
320 baseline_ = (firstGain + lastGain) * .5;
325 OfflineAudioSource<T> &source_;
326 OfflineAudioSpec spec_;
327 OfflineFingerprint expected_;
329 OfflineSession &job_;
330 const OfflineJobOptions &options_;
333 std::uint64_t leaves_ = 0;
334 bool verified_ =
false;
335 double minimum_ = std::numeric_limits<double>::infinity(), maximum_ = 0;
336 double baseline_ = 1, scalarPeak_ = 0;
337 std::array<double, 2> endpoints_{1, 1};
338 std::array<std::int64_t, cacheLeaves> cached_{};
339 std::unique_ptr<OfflineFingerprint[]> fingerprints_;
340 std::unique_ptr<T[]> audio_;
341 std::unique_ptr<double[]> gain_;
351 void operator()(std::int64_t first,
int count,
double *output)
353 source->
readAudio(channel, first, count, output);
359 void operator()(std::int64_t first,
int count,
double *output)
364 struct WeightedReader
366 AudioReader *audio =
nullptr;
368 int block = 0, side = 0;
369 std::array<double, 2> coefficients{};
370 void operator()(std::int64_t first,
int count,
double *output)
372 (*audio)(first, count, output);
373 for (
int offset = 0; offset < count;)
375 const auto frame = first + offset;
376 const int begin =
static_cast<int>(frame % block);
377 const int length = std::min(block - begin, count - offset);
378 const auto leaf =
static_cast<std::size_t
>(frame / block);
379 const auto *weight = weights->
get(leaf, side == 2 ? 0 : side);
380 const auto *right = side == 2 ? weights->
get(leaf, 1) :
nullptr;
381 for (
int i = 0; i < length; ++i)
382 output[offset + i] *= right
383 ? coefficients[0] * weight[begin + i] + coefficients[1] * right[begin + i]
393 : source_(source), work_(job, source.frames(), source.block()), control_{&source}
396 gainMap_.emplace(work_, control_);
397 for (
int c = 0; c < source.
channels(); ++c)
400 readers_[c] = {&source, c};
401 audioMaps_[c].emplace(work_, readers_[c]);
402 combinations_[c].emplace(work_, readers_[c], control_, *audioMaps_[c], *gainMap_);
404 combinedMaps_[c].emplace(work_, *combinations_[c]);
408 boundaryCount_ = calibrateBoundary ? 2 : 1;
410 weights_.emplace(job, source.
frames(), source.
block());
411 boundaryWork_ = job.
allocate<
double>(2 *
static_cast<std::uint64_t
>(source.
block()));
412 if (calibrateBoundary)
413 boundaryResponse_ = job.
allocate<
double>(
414 2 *
static_cast<std::uint64_t
>(source.
block()) * source.
channels());
415 for (
int c = 0; c < source.
channels(); ++c)
416 for (
int side = 0; side < boundaryCount_; ++side)
420 weighted_[c][side] = {&readers_[c], &*weights_, source.
block(),
421 calibrateBoundary ? side : 2,
424 boundaryMaps_[c][side].emplace(work_, weighted_[c][side]);
434 endpoints_ = {left, right};
438 return boundaryResponse_.get() + side * source_.
channels() * source_.
block();
443 for (
int c = 0; c < source_.
channels(); ++c)
444 combinations_[c]->
reset();
449 [[nodiscard]]
const double *
evaluate(std::size_t leaf)
451 const int block = source_.
block();
452 for (
int c = 0; c < source_.
channels(); ++c)
454 auto *output = output_.get() + c * block;
455 combinedMaps_[c]->evaluate(work_, leaf, *combinations_[c], output,
true);
456 const auto *data = combinations_[c]->get(leaf);
457 for (
int i = 0; i < block; ++i)
458 output[i] = (source_.
baseline() - 1) * data[i] + .75 * data[i] * data[block + i] +
459 data[2 * block + i] * data[3 * block + i] - output[i];
462 auto *a = boundaryWork_.get(), *weighted = a + block;
463 audioMaps_[c]->evaluateCauchy(work_, leaf, readers_[c], a,
true);
464 for (
int side = 0; side < boundaryCount_; ++side)
466 boundaryMaps_[c][side]->evaluateCauchy(work_, leaf, weighted_[c][side],
468 const auto *f = weights_->get(leaf, side);
469 const auto *d = weights_->get(leaf, side,
true);
470 const auto *rightF = boundaryCount_ == 1 ? weights_->get(leaf, 1) :
nullptr;
471 const auto *rightD = boundaryCount_ == 1 ? weights_->get(leaf, 1,
true) :
nullptr;
472 auto *response = boundaryResponse_
473 ? boundaryResponse_.get() + (side * source_.
channels() + c) * block : weighted;
474 const double left = endpoints_[0] - source_.
baseline();
475 const double right = endpoints_[1] - source_.
baseline();
476 const double coefficient = boundaryCount_ == 1 ? 1 : side == 0 ? left : right;
477 for (
int i = 0; i < block; ++i)
479 const double sign = i % 2 ? -1. : 1.;
480 const double fValue = rightF ? left * f[i] + right * rightF[i] : f[i];
481 const double dValue = rightD ? left * d[i] + right * rightD[i] : d[i];
482 response[i] = dValue * data[i] +
483 sign / std::numbers::pi * (fValue * a[i] - weighted[i]);
484 output[i] += coefficient * response[i];
489 return output_.get();
495 std::array<AudioReader, 2> readers_{};
496 ControlReader control_;
497 std::optional<OfflineHilbertMap> gainMap_;
498 std::array<std::optional<OfflineHilbertMap>, 2> audioMaps_, combinedMaps_;
499 std::array<std::optional<Combination>, 2> combinations_;
500 std::optional<OfflineEndpointWeights> weights_;
501 std::array<std::array<WeightedReader, 2>, 2> weighted_{};
502 std::array<std::array<std::optional<OfflineHilbertMap>, 2>, 2> boundaryMaps_;
503 std::unique_ptr<double[]> boundaryWork_;
504 std::unique_ptr<double[]> boundaryResponse_;
505 std::array<double, 2> endpoints_{1, 1};
506 int boundaryCount_ = 0;
507 std::unique_ptr<double[]> output_;
OfflineGainSource(OfflineAudioSource< T > &source, const OfflineAudioSpec &spec, const OfflineFingerprint &fingerprint, double peak, OfflineSession &job, const OfflineJobOptions &options, Cursor cursor)