184 validateOptions(options);
187 detail::OfflineSession job(jobOptions);
189 plan.spec_ = analysis.
getSpec();
194 const auto bins = analysis.
bins();
195 const auto n = bins.size();
196 const auto total =
static_cast<std::int64_t
>(n + 1) * 3;
199 plan.gains_ = job.allocate<
double>(n + 1);
200 std::fill_n(plan.gains_.get(), n + 1, 1.0);
201 plan.exclusions_.assign(job, options.exclusions, plan.spec_.frames);
202 plan.report_.controlPoints = n + 1;
203 auto powers = job.allocate<
double>(n);
205 for (std::size_t i = 0; i < n; ++i)
207 powers[i] = bins[i].rms;
208 scale = std::max(scale, bins[i].rms);
210 const auto quantile = (n - 1) / 10 * 9 + (n - 1) % 10 * 9 / 10;
212 const double floorDb = std::max(-65.0,
gainToDecibels(powers[quantile], -6400.0) - 35);
214 for (
const auto bin : bins)
217 const bool allExcluded = plan.exclusions_.covers(plan.spec_.frames);
218 if (!active || options.amount == 0 || options.maximumBoostDb == 0 || allExcluded)
226 result.memoryBytes = job.bytes();
227 result.plan = std::move(plan);
230 for (std::size_t i = 0; i < n; ++i)
232 const double ratio = bins[i].rms / scale;
233 powers[i] = ratio * ratio;
235 const double binsPerSecond = plan.spec_.sampleRate /
static_cast<double>(plan.hop_);
236 const auto macro = std::min(
238 std::max<std::size_t>(1,
static_cast<std::size_t
>(std::round(3 * binsPerSecond))));
240 std::max<std::size_t>(2,
static_cast<std::size_t
>(std::round(binsPerSecond)));
243 const auto meanPower = [&](std::size_t from, std::size_t to) {
244 double energy = 0, frames = 0;
245 for (
auto j = from; j < to; ++j)
247 energy +=
powers[j] *
static_cast<double>(bins[j].frames);
248 frames +=
static_cast<double>(bins[j].frames);
250 return energy / frames;
252 const auto meanDb = [&](std::size_t from, std::size_t to) {
253 double localScale = 0, energy = 0, frames = 0;
254 for (
auto j = from; j < to; ++j)
255 localScale = std::max(localScale, bins[j].rms);
258 for (
auto j = from; j < to; ++j)
260 const double r = bins[j].rms / localScale;
261 energy += r * r *
static_cast<double>(bins[j].frames);
262 frames +=
static_cast<double>(bins[j].frames);
264 return gainToDecibels(localScale * std::sqrt(std::min(1.0, energy / frames)),
267 double targetPower = 0;
268 for (std::size_t i = 0; i <= n - macro; ++i)
269 targetPower = std::max(targetPower, meanPower(i, i + macro));
270 const double targetDb =
271 gainToDecibels(scale * std::sqrt(std::min(1.0, targetPower)), -6400.0);
272 plan.report_.referenceRmsDb = targetDb;
273 auto novelty = job.allocate<
double>(n + 1);
274 auto edges = job.allocate<std::size_t>(n + 2);
275 std::size_t edgeCount = 1;
276 if (n >= 2 * context)
277 for (
auto i = context; i <= n - context; ++i)
279 const auto half = context / 2;
280 const double left = meanDb(i - context, i), right = meanDb(i, i + context);
281 if (left > floorDb + 6 && right > floorDb + 6 && std::abs(right - left) >= 3 &&
282 std::abs(meanDb(i - context, i - half) - meanDb(i - half, i)) < 1.5 &&
283 std::abs(meanDb(i, i + half) - meanDb(i + half, i + context)) < 1.5)
284 novelty[i] = std::abs(right - left);
286 if (n >= 2 * context)
287 for (
auto i = context; i <= n - context; ++i)
292 for (
auto j = std::max(context, i - context);
293 j <= std::min(n - context, i + context); ++j)
294 if (novelty[j] > novelty[i] || (novelty[j] == novelty[i] && j < i))
299 if (maximum && i - edges[edgeCount - 1] >= context * 2)
300 edges[edgeCount++] = i;
302 edges[edgeCount++] = n;
303 plan.report_.boundaries = edgeCount - 2;
304 const auto radius = std::max<std::size_t>(
305 1,
static_cast<std::size_t
>(
306 std::round(3 * std::pow(1.0 / 3, options.speed) * binsPerSecond)));
307 const auto smoothing = std::max<std::size_t>(
308 1,
static_cast<std::size_t
>(
309 std::round(0.6 * std::pow(1.0 / 6, options.speed) * binsPerSecond)));
310 plan.featherFrames_ =
311 std::max(plan.spec_.sampleRate * 0.1,
312 static_cast<double>(smoothing) *
static_cast<double>(plan.hop_));
313 auto desired = job.allocate<
double>(n + 1);
314 std::size_t region = 0;
315 for (std::size_t i = 0; i <= n; ++i)
319 while (region + 2 < edgeCount && i >= edges[region + 1])
321 auto from = std::max(edges[region], i > radius ? i - radius : 0);
322 auto to = std::min(edges[region + 1], i + radius);
325 from = std::min(i, n - 1);
328 const double correction = std::max(0.0, targetDb - meanDb(from, to));
329 double activity = std::clamp(
330 (
gainToDecibels(bins[std::min(i, n - 1)].rms, -6400.0) - floorDb) / 6, 0.0,
332 activity = activity * activity * (3 - 2 * activity);
333 if (correction > options.maximumBoostDb && activity > 0.99)
334 ++plan.report_.limitedPoints;
336 options.amount * std::min(options.maximumBoostDb, correction) * activity;
338 const auto smooth = [n, smoothing](
const double *values, std::size_t i) {
339 double sum = 0, weights = 0;
340 const auto radiusSigned =
static_cast<std::int64_t
>(smoothing);
341 for (
auto j = -radiusSigned; j <= radiusSigned; ++j)
344 static_cast<double>(smoothing + 1) -
static_cast<double>(std::abs(j));
345 const auto index =
static_cast<std::size_t
>(
346 std::clamp(
static_cast<std::int64_t
>(i) + j, std::int64_t(0),
347 static_cast<std::int64_t
>(n)));
348 sum += w * values[index];
351 return sum / weights;
353 for (std::size_t i = 0; i <= n; ++i)
354 plan.gains_[i] =
decibelsToGain(std::min(desired[i], smooth(desired.get(), i)));
355 const double maxSample =
static_cast<double>(std::numeric_limits<T>::max());
357 for (std::size_t i = 0; i < n; ++i)
358 if (bins[i].peak > maxSample / maxGain)
361 std::max(1.0, std::nextafter(maxSample / bins[i].peak, 0.0));
362 if (std::max(plan.gains_[i], plan.gains_[i + 1]) > bound)
363 ++plan.report_.representabilityLimitedBins;
364 plan.gains_[i] = std::min(plan.gains_[i], bound);
365 plan.gains_[i + 1] = std::min(plan.gains_[i + 1], bound);
370 for (std::size_t start = 0; start <= n - macro; ++start)
372 if ((start & 1023) == 0)
375 const auto end = start + macro;
376 double a = 0, b = 0, c = 0, frames = 0;
377 for (
auto j = start; j < end; ++j)
379 const double energy =
powers[j] *
static_cast<double>(bins[j].frames);
380 const double extra = std::max(plan.gains_[j], plan.gains_[j + 1]) - 1;
381 a += energy * extra * extra;
382 b += 2 * energy * extra;
384 frames +=
static_cast<double>(bins[j].frames);
386 const double budget = std::max(0.0, targetPower * frames - c);
387 if (a + b > budget && a + b > 0)
389 const double factor = std::clamp(
390 a == 0 ? budget / b : 2 * budget / (b + std::sqrt(b * b + 4 * a * budget)),
392 for (
auto j = start; j <= end; ++j)
393 plan.gains_[j] = 1 + (plan.gains_[j] - 1) * factor;
396 auto lower = job.allocate<
double>(n + 1);
397 for (std::size_t i = 0; i <= n; ++i)
399 {plan.gains_[i], plan.gains_[i ? i - 1 : 0], plan.gains_[std::min(n, i + 1)]});
400 double minimum = std::numeric_limits<double>::infinity(), maximum = 1;
401 for (std::size_t i = 0; i <= n; ++i)
406 plan.gains_[i] = std::max(1.0, std::min(plan.gains_[i], smooth(lower.get(), i)));
407 minimum = std::min(minimum, plan.gains_[i]);
408 maximum = std::max(maximum, plan.gains_[i]);
410 if (!plan.exclusions_.view().empty())
414 plan.report_.reason = plan.report_.representabilityLimitedBins
421 result.memoryBytes = job.bytes();
422 result.plan = std::move(plan);