126 double inputPeak,
bool valid,
bool changed,
132 OfflineSinkTransaction<T> transaction;
141 result.report = report;
142 auto cursor = makeCursor(job);
143 using Cursor =
decltype(cursor);
144 OfflineGainSource<T, Cursor> cached(source, spec, expectedFingerprint, inputPeak, job,
145 options, std::move(cursor));
146 std::optional<OfflineGainProduct<T, Cursor>> product;
147 std::uint64_t boundaryFrames = 0;
148 for (
const auto target : settings.boundaryTargets)
150 if (target.region.begin < 0 || target.region.end < target.region.begin ||
151 target.region.end > spec.
frames || !std::isfinite(target.peakGain) ||
152 !(target.peakGain > 0) || target.peakGain > 1)
154 boundaryFrames +=
static_cast<std::uint64_t
>(target.region.end - target.region.begin);
156 if (changed && inputPeak > 0 && !cached.constant())
157 product.emplace(cached, job, boundaryFrames != 0);
158 result.report.renderInfo.bandlimited = product.has_value();
159 result.report.renderInfo.boundaryGain = cached.baseline();
160 result.report.renderInfo.leftBoundaryGain = cached.endpoint(0);
161 result.report.renderInfo.rightBoundaryGain = cached.endpoint(1);
162 result.report.renderInfo.minimumControlGain = cached.minimumGain();
163 result.report.renderInfo.maximumControlGain = cached.maximumGain();
164 const int blockSize = cached.block();
165 auto output = job.allocate<T>(
static_cast<std::uint64_t
>(blockSize) * spec.
channels);
166 result.memoryBytes = job.bytes();
167 TruePeakDetector<double, 2> detector;
168 double outputPeak = 0, normalizedTruePeak = 0;
169 const double normalizer = inputPeak > 0 ? inputPeak : 1;
170 const double maximumSample =
static_cast<double>(std::numeric_limits<T>::max());
171 const auto runPass = [&](
OfflinePhase phase,
auto &&consume,
bool boundaryOnly =
false) {
175 job.checkpoint(phase, 0, spec.
frames);
176 for (std::size_t leaf = 0; leaf < cached.leaves(); ++leaf)
178 const auto first =
static_cast<std::int64_t
>(leaf) * blockSize;
180 static_cast<int>(std::min<std::int64_t>(blockSize, spec.
frames - first));
181 if (boundaryOnly && std::none_of(settings.boundaryTargets.begin(),
182 settings.boundaryTargets.end(), [&](
const auto &target) {
183 return first < target.region.end && first + frames > target.region.begin;
186 const double *delta = product ? product->evaluate(leaf) :
nullptr;
187 const T *input = cached.audio(leaf, 0);
188 const double *gains = cached.gain(leaf);
189 consume(first, frames, input, gains, delta);
190 job.checkpoint(phase, first + frames, spec.
frames);
194 const auto maskAt = [&](std::int64_t frame) {
195 return settings.exclusions
196 ? settings.exclusions->apply(frame, 2, settings.featherFrames) - 1
199 const auto constrainedDelta = [](
double x,
double gain,
double delta,
double mask) {
202 const double native = x * (gain - 1);
203 return native + mask * (delta - native);
206 if (product && boundaryFrames)
210 const auto constraints = boundaryFrames *
static_cast<std::uint64_t
>(2 * spec.
channels);
212 const std::array<double, 2> lower{
213 settings.boundaryTargets[0].region.end > settings.boundaryTargets[0].region.begin
214 ? -cached.endpoint(0) : 0,
215 settings.boundaryTargets[1].region.end > settings.boundaryTargets[1].region.begin
216 ? -cached.endpoint(1) : 0};
217 OfflineBoundaryFeasibility feasible(job, constraints, lower);
219 const double *gains,
const double *delta) {
220 const auto *left = product->boundaryResponse(0);
221 const auto *right = product->boundaryResponse(1);
222 for (
const auto target : settings.boundaryTargets)
223 for (auto frame = std::max(first, target.region.begin);
224 frame < std::min(first + frames, target.region.end); ++frame)
226 const int f =
static_cast<int>(frame - first);
227 const double mask = maskAt(frame);
228 const double bound = offlineRepresentablePeakGain<T>(normalizer, target.peakGain);
229 for (
int c = 0; c < spec.
channels; ++c)
231 const int k = c * blockSize + f;
232 const double x =
static_cast<double>(input[k]) / normalizer;
233 const double y = x + constrainedDelta(x, gains[f], delta[k], mask);
236 const double limit = std::max(bound, std::abs(x * gains[f]));
237 const double a = mask * left[k], b = mask * right[k];
238 feasible.constrain(a, b, limit - y);
239 feasible.constrain(-a, -b, limit + y);
243 result.report.renderInfo.targetFeasible = feasible.feasible();
244 if (!feasible.feasible())
246 const auto delta = feasible.closest();
247 auto &info = result.report.renderInfo;
248 info.leftBoundaryGain = std::clamp(cached.endpoint(0) + delta[0], 0., cached.endpoint(0));
249 info.rightBoundaryGain = std::clamp(cached.endpoint(1) + delta[1], 0., cached.endpoint(1));
250 info.boundaryCalibrated = delta[0] != 0 || delta[1] != 0;
251 product->setEndpoints(info.leftBoundaryGain, info.rightBoundaryGain);
259 const double rowSum = 2 * (1 + std::log(
static_cast<double>(spec.
frames))) / pi<double>;
260 const double controlRange = std::max(std::abs(cached.minimumGain() - cached.baseline()),
261 std::abs(cached.maximumGain() - cached.baseline()));
264 const double endpointBound = .125 + 2 * (1 + std::log(
static_cast<double>(spec.
frames))) *
266 const double endpointRange =
267 std::abs(result.report.renderInfo.leftBoundaryGain - cached.baseline()) +
268 std::abs(result.report.renderInfo.rightBoundaryGain - cached.baseline());
269 const double deltaBound = std::abs(cached.baseline() - 1) +
270 (.75 + 3 * rowSum * rowSum) * controlRange +
271 endpointBound * endpointRange;
272 const bool couldOverflow = normalizer >= maximumSample / (2 * (1 + deltaBound));
273 if (product && (settings.calibratePeak || couldOverflow))
276 double hi = settings.calibratePeak && cached.minimumGain() < 1
277 ? 1 / (1 - cached.minimumGain())
280 settings.calibratePeak
281 ? std::nextafter(cached.scalarPeak(), std::numeric_limits<double>::infinity())
282 : maximumSample / normalizer;
285 const double safeBound =
286 settings.calibratePeak
288 : bound * (1 - 8 * std::numeric_limits<double>::epsilon());
289 bool feasible =
true;
291 const double *gains,
const double *delta) {
292 for (
int f = 0; f < frames; ++f)
294 const double mask = maskAt(first + f);
295 for (
int c = 0; c < spec.
channels; ++c)
297 const int k = c * blockSize + f;
298 const double x =
static_cast<double>(input[k]) / normalizer;
299 const double d = constrainedDelta(x, gains[f], delta[k], mask);
300 if (!std::isfinite(d))
303 feasible = feasible && std::abs(x) <= bound;
306 const double limit = settings.calibratePeak
308 : std::max(safeBound, std::abs(x));
309 double a = (-limit - x) / d, b = (limit - x) / d;
312 lo = std::max(lo, a);
313 hi = std::min(hi, b);
318 result.report.renderInfo.targetFeasible = feasible && lo <= hi;
319 if (!result.report.renderInfo.targetFeasible)
321 scale = std::clamp(1., lo, hi);
322 if (!settings.calibratePeak && scale < 1 && scale > 0)
323 scale = std::nextafter(scale, 0.);
324 result.report.renderInfo.deltaScale = scale;
325 result.report.renderInfo.representabilityLimited = !settings.calibratePeak && scale < 1;
327 result.report.renderInfo.minimumControlGain =
328 std::max(0., 1 + scale * (cached.minimumGain() - 1));
329 result.report.renderInfo.maximumControlGain =
330 std::max(0., 1 + scale * (cached.maximumGain() - 1));
334 transaction.sink = &sink;
337 const double *gains,
const double *delta) {
338 for (
int f = 0; f < frames; ++f)
340 const double gain = gains[f];
341 const double mask = maskAt(first + f);
342 for (
int c = 0; c < spec.
channels; ++c)
344 const int k = c * blockSize + f;
345 const T x = input[k];
347 if (delta && mask != 0 && scale != 0)
349 const double xn =
static_cast<double>(x) / normalizer;
350 const double d = constrainedDelta(xn, gain, delta[k], mask);
351 const double value = (xn + scale * d) * normalizer;
352 if (!std::isfinite(value) || std::abs(value) > maximumSample)
354 y =
static_cast<T
>(value);
356 else if (!delta && gain != 1)
358 const double value =
static_cast<double>(x) * gain;
359 if (!std::isfinite(value) || std::abs(value) > maximumSample)
361 y =
static_cast<T
>(value);
363 if (!std::isfinite(y))
365 outputPeak = std::max(outputPeak, std::abs(
static_cast<double>(y)));
366 normalizedTruePeak = std::max(
368 detector.processSample(
static_cast<double>(y) / normalizer, c));
372 for (
int offset = 0; offset < frames;)
374 const int count = std::min(options.
blockFrames, frames - offset);
375 std::array<const T *, 2> channels{};
376 for (
int c = 0; c < spec.
channels; ++c)
377 channels[c] = output.get() + c * blockSize + offset;
379 return sink.
write(first + offset, AudioBufferView<const T>(
380 channels.data(), spec.
channels, count));
385 for (
int i = 0; i < TruePeakDetector<double, 2>::getTaps() - 1; ++i)
386 for (
int c = 0; c < spec.
channels; ++c)
387 normalizedTruePeak = std::max(normalizedTruePeak, detector.processSample(0, c));
388 const double silence = -std::numeric_limits<double>::infinity();
389 result.report.outputSamplePeakDb =
gainToDecibels(outputPeak, silence);
390 result.report.outputTruePeakDb =
391 normalizedTruePeak > 0
394 result.report.peaksMeasured =
true;
397 transaction.sink =
nullptr;
398 result.memoryBytes = job.bytes();