63 [[nodiscard]]
bool isValid() const noexcept
65 return bins_ !=
nullptr;
69 [[nodiscard]] std::span<const Bin>
bins() const noexcept
71 return isValid() ? std::span<const Bin>(bins_.get(), count_) : std::span<const Bin>();
81 [[nodiscard]] std::int64_t
binFrames() const noexcept
98 std::int64_t hop_ = 0;
99 std::size_t count_ = 0;
101 std::unique_ptr<Bin[]> bins_;
141 return bins_ !=
nullptr;
144 [[nodiscard]] std::span<const MidSideBin>
bins() const noexcept
146 return isValid() ? std::span<const MidSideBin>(bins_.get(), count_)
147 : std::span<const MidSideBin>();
161 return sideFingerprint_;
171 [[nodiscard]]
double midRms() const noexcept
192 std::int64_t hop_ = 0;
193 std::size_t count_ = 0;
194 double peak_ = 0, midRms_ = 0, sideRms_ = 0, sidePeak_ = 0;
195 std::unique_ptr<MidSideBin[]> bins_;
215 return measure<false>(source, options);
228 return measure<true>(source, options);
232 template <
bool Stereo>
233 [[nodiscard]]
static auto measure(OfflineAudioSource<T> &source,
234 const OfflineJobOptions &options)
236 using Output = std::conditional_t<Stereo, MidSideResult, Result>;
237 using Map = std::conditional_t<Stereo, MidSideAnalysis, Analysis>;
238 using Cell = std::conditional_t<Stereo, MidSideBin, Bin>;
242 detail::OfflineSession job(options);
244 candidate.spec_ = source.getSpec();
246 const auto spec = candidate.spec_;
247 if constexpr (Stereo)
248 if (spec.channels != 2)
251 candidate.hop_ = std::max<std::int64_t>(
252 1,
static_cast<std::int64_t
>(spec.sampleRate / (Stereo ? 100 : 10)));
253 const auto count =
static_cast<std::uint64_t
>((spec.frames - 1) / candidate.hop_ + 1);
254 candidate.bins_ = job.allocate<Cell>(count);
255 candidate.count_ =
static_cast<std::size_t
>(count);
256 detail::OfflineBlock<T> scratch(job, spec, options.blockFrames);
260 detail::ScaledSumSquares energy, sideEnergy, totalMid, totalSide;
261 std::int64_t inBin = 0;
263 for (std::int64_t first = 0; first < spec.frames;)
266 static_cast<int>(std::min<std::int64_t>(scratch.frames(), spec.frames - first));
267 auto block = scratch.view(frames);
269 for (
int f = 0; f < frames; ++f)
271 for (
int c = 0; c < spec.channels; ++c)
273 const T value = block.getChannel(c)[f];
274 if (!std::isfinite(value))
277 if constexpr (!Stereo)
278 energy.add(
static_cast<double>(value));
281 std::max(candidate.peak_, std::abs(
static_cast<double>(value)));
283 if constexpr (Stereo)
285 const double left =
static_cast<double>(block.getChannel(0)[f]);
286 const double right =
static_cast<double>(block.getChannel(1)[f]);
287 const double mid = std::midpoint(left, right);
288 const double side = std::midpoint(left, -right);
290 sideEnergy.add(side);
296 if (inBin == candidate.hop_ || first + f == spec.frames - 1)
298 if constexpr (Stereo)
300 candidate.bins_[bin++] = {energy.rms(
static_cast<double>(inBin)),
301 sideEnergy.rms(
static_cast<double>(inBin)),
302 energy.peak(), sideEnergy.peak(), inBin};
308 energy.rms(
static_cast<double>(inBin) * spec.channels);
309 candidate.bins_[bin++] = {rms, energy.peak(), inBin};
310 candidate.peak_ = std::max(candidate.peak_, energy.peak());
319 if (source.getSpec() != spec)
321 if constexpr (Stereo)
323 candidate.midRms_ = totalMid.rms(
static_cast<double>(spec.frames));
324 candidate.sideRms_ = totalSide.rms(
static_cast<double>(spec.frames));
325 candidate.sidePeak_ = totalSide.peak();
327 result.memoryBytes = job.bytes();
328 result.analysis = std::move(candidate);