168 validateOptions(options);
169 if (!analysis.
isValid() || (options.reductionDb > 0 && !analysis.
getOptions().attacks))
171 detail::OfflineSession job(jobOptions);
173 plan.spec_ = analysis.
energy().getSpec();
174 if (source.
getSpec() != plan.spec_)
176 plan.fingerprint_ = analysis.
energy().fingerprint();
177 plan.inputPeak_ = analysis.
energy().samplePeak();
179 std::max(std::int64_t(1),
static_cast<std::int64_t
>(std::llround(
180 plan.spec_.sampleRate * options.lookaheadMs * .001)));
181 auto &report = plan.report_;
182 report.requestedReductionDb = options.reductionDb;
183 report.requestedLookaheadMs = options.lookaheadMs;
185 1000 *
static_cast<double>(plan.attackFrames_) / plan.spec_.sampleRate;
186 report.releaseMs = options.releaseMs;
187 report.inputSamplePeakDb =
188 gainToDecibels(plan.inputPeak_, -std::numeric_limits<double>::infinity());
190 plan.exclusions_.assign(job, options.exclusions, plan.spec_.frames);
191 const double releaseFrames = options.releaseMs * .001 * plan.spec_.sampleRate;
192 const bool allExcluded = plan.exclusions_.covers(plan.spec_.frames);
193 if (options.reductionDb == 0 || plan.inputPeak_ == 0 || allExcluded)
198 plan.curve_.prepare(job, 0, plan.attackFrames_, releaseFrames);
202 detail::OfflineBlock<T> scratch(job, plan.spec_, jobOptions.
blockFrames);
203 EnvelopeFollower<double, 1> sustain;
204 sustain.prepare({plan.spec_.sampleRate, 1, 1});
205 sustain.setAttack(60);
206 sustain.setRelease(300);
207 double loudestSustain = 0;
208 scan(source, plan, scratch, job, [&](std::int64_t,
double magnitude) {
209 loudestSustain = std::max(loudestSustain, sustain.processSample(magnitude));
211 report.availableHeadroomDb =
212 loudestSustain > 0 ? std::max(0.0, -
gainToDecibels(loudestSustain)) : 0;
213 report.effectiveReductionDb =
214 std::min(options.reductionDb, report.availableHeadroomDb);
215 const bool crestLimited = report.availableHeadroomDb < options.reductionDb;
216 const double ceiling = detail::offlineRepresentablePeakGain<T>(
219 const bool resolutionLimited =
220 report.effectiveReductionDb - representableReduction > .001;
221 report.effectiveReductionDb = std::max(0.0, representableReduction);
222 if (report.effectiveReductionDb < .001)
224 report.effectiveReductionDb = 0;
227 plan.curve_.prepare(job, 0, plan.attackFrames_, releaseFrames);
231 std::size_t count = 0;
232 const auto countHold = [&](std::int64_t, std::int64_t, double, bool) {
233 if (count == std::numeric_limits<std::size_t>::max())
237 buildHolds(source, plan, analysis.
attacks(), ceiling, scratch, job, countHold);
238 plan.curve_.prepare(job, count, plan.attackFrames_, releaseFrames);
239 std::size_t index = 0;
240 const auto addHold = [&](std::int64_t begin, std::int64_t end,
double peak,
242 const double gain = ceiling / peak;
243 plan.curve_.set(index++, begin, end, gain);
244 report.maximumReductionDb =
247 ++report.transientHolds;
249 ++report.additionalHolds;
251 buildHolds(source, plan, analysis.
attacks(), ceiling, scratch, job, addHold);
262 result.memoryBytes = job.bytes();
263 result.plan = std::move(plan);
279 validateOptions(options);
280 typename OfflineTransientAnalyzer<T>::Options features;
281 features.attacks = options.reductionDb > 0;
282 features.pulses =
false;
283 auto analyzed = OfflineTransientAnalyzer<T>().analyze(source, features, job);
284 if (!analyzed.succeeded())
286 result.status = analyzed.status;
289 result =
makePlan(source, analyzed.analysis, options,
291 result.memoryBytes = std::max(analyzed.memoryBytes,
292 result.memoryBytes + analyzed.analysis.retainedBytes());
307 auto report = plan.report_;
309 return detail::offlineRenderGain<Result>(
310 source, plan.spec_, plan.fingerprint_, plan.inputPeak_, plan.
isValid(),
313 return [&plan, cursor = detail::OfflineAttenuation::Cursor(curve, session)](
314 std::int64_t frame) mutable {
315 return plan.exclusions_.apply(frame, cursor(frame),
316 static_cast<double>(plan.attackFrames_));
319 {&plan.exclusions_,
static_cast<double>(plan.attackFrames_),
true});
321 auto result = attempt(plan.curve_, plan.attackFrames_, job);
323 auto memoryBytes = result.memoryBytes;
328 if (plan.isValid() && !result.report.renderInfo.targetFeasible)
331 static_cast<std::int64_t
>(std::llround(.010 * plan.spec_.sampleRate));
332 auto attack = plan.attackFrames_;
335 while (attack < maximum && !result.report.renderInfo.targetFeasible)
337 attack = std::min(maximum, attack * 2);
338 detail::OfflineSession session(job);
339 auto curve = plan.curve_.withAttack(session, attack);
341 memoryBytes = std::max(memoryBytes, result.memoryBytes + session.bytes());
350 result.memoryBytes = memoryBytes;
351 result.report.renderAttempts = attempts;
352 if (result.succeeded() && plan.inputPeak_ > 0)
354 result.report.achievedReductionDb =
355 std::max(0.0, result.report.inputSamplePeakDb - result.report.outputSamplePeakDb);
356 if (!plan.exclusions_.view().empty() &&
357 result.report.achievedReductionDb + .0001 < result.report.effectiveReductionDb)