147 if (!analysis.
isValid() || (options.reductionDb > 0 && !analysis.
getOptions().pulses) ||
148 (options.reductionDb > 0 && options.manualBpm == 0 && !tempo.
isValid()))
150 detail::OfflineSession job(jobOptions);
152 plan.spec_ = analysis.
energy().getSpec();
153 plan.fingerprint_ = analysis.
energy().fingerprint();
154 if (source.
getSpec() != plan.spec_ ||
158 plan.inputPeak_ = analysis.
energy().samplePeak();
159 const auto attack = std::max(std::int64_t(1),
160 static_cast<std::int64_t
>(std::llround(.002 * plan.spec_.sampleRate)));
161 const auto preroll =
static_cast<std::int64_t
>(std::llround(.004 * plan.spec_.sampleRate));
162 plan.featherFrames_ =
static_cast<double>(attack + preroll);
163 auto &report = plan.report_;
164 report.requestedReductionDb = options.reductionDb;
165 report.inputSamplePeakDb =
gainToDecibels(plan.inputPeak_, silence);
166 report.tempoBpm = options.manualBpm > 0 ? options.manualBpm : tempo.
getReport().
tempoBpm;
167 report.tempoConfidence = tempo.
isValid() ? tempo.
getReport().confidence : 0;
169 plan.exclusions_.assign(job, options.exclusions, plan.spec_.frames);
170 const auto events = analysis.
pulses();
171 const bool excluded = plan.exclusions_.covers(plan.spec_.frames);
172 const bool active = options.reductionDb > 0 && plan.inputPeak_ > 0 &&
173 !events.empty() && !excluded;
174 plan.curve_.prepare(job, active ? events.size() : 0, attack,
175 .25 * plan.spec_.sampleRate, active);
182 report.pulses = events.size();
183 plan.pulses_ = job.template allocate<Pulse>(events.size());
184 auto peaks = job.template allocate<double>(events.size());
185 detail::OfflineBlock<T> scratch(job, plan.spec_, jobOptions.
blockFrames);
186 const auto window = std::max(std::int64_t(1),
187 static_cast<std::int64_t
>(std::llround(.030 * plan.spec_.sampleRate)));
188 std::size_t first = 0, next = 0;
190 scratch, job, [&](std::int64_t frame,
double magnitude) {
191 while (next < events.size() && events[next].begin <= frame)
193 while (first < next && frame - events[first].begin >= window)
195 for (auto i = first; i < next; ++i)
196 peaks[i] = std::max(peaks[i], magnitude);
198 for (std::size_t i = 0; i < events.size(); ++i)
200 std::nth_element(peaks.get(), peaks.get() + events.size() / 2,
201 peaks.get() + events.size());
202 report.referencePeakDb =
gainToDecibels(peaks[events.size() / 2], silence);
203 for (std::size_t i = 0; i < events.size(); ++i)
207 auto &pulse = plan.pulses_[i];
208 pulse.begin = events[i].begin;
209 pulse.end = events[i].end;
210 const double excess = pulse.peakDb > report.referencePeakDb
211 ? pulse.peakDb - report.referencePeakDb : 0;
212 pulse.reductionDb = std::min(options.reductionDb, excess);
213 double bpm = options.manualBpm;
218 bpm = pulseTempo(tempo, pulse.begin);
222 pulse.releaseMs = bpm > 0 ? noteBeats(options.releaseNote) * 60000 / bpm : 250;
223 report.fallbackPulses += bpm > 0 ? 0 : 1;
224 report.reducedPulses += pulse.reductionDb > 0 ? 1 : 0;
225 report.maximumExcessDb = std::max(report.maximumExcessDb, excess);
226 report.maximumReductionDb = std::max(report.maximumReductionDb, pulse.reductionDb);
227 plan.curve_.set(i, std::max(std::int64_t(0), pulse.begin - preroll), pulse.end,
229 pulse.releaseMs * .001 * plan.spec_.sampleRate);
235 result.memoryBytes = job.bytes();
236 result.plan = std::move(plan);
293 const auto window = std::max(std::int64_t(1),
294 static_cast<std::int64_t
>(std::llround(.030 * plan.spec_.
sampleRate)));
295 const std::array<const Pulse *, 2> edge{&plan.
pulses().front(), &plan.
pulses().back()};
296 for (
int side = 0; side < 2; ++side)
298 const auto &pulse = *edge[side];
299 const bool touches = side == 0
300 ? pulse.begin <= plan.featherFrames_ : pulse.end == plan.spec_.
frames;
301 if (pulse.reductionDb > 0 && touches)
303 const auto end = pulse.begin + std::min(window, plan.spec_.
frames - pulse.begin);
304 settings.boundaryTargets[side] = {
310 return detail::offlineRenderGain<Result>(
311 source, plan.spec_, plan.fingerprint_, plan.inputPeak_, plan.
isValid(),
313 [&plan](detail::OfflineSession &session) {
314 return [&plan, cursor = detail::OfflineAttenuation::Cursor(plan.curve_, session)]
315 (std::int64_t frame) mutable {
316 return plan.exclusions_.apply(frame, cursor(frame), plan.featherFrames_);