Files
reasampler/src/peaks.cpp
T
daniel 4cb40f6e34 peaks: pure per-channel min/max envelope from interleaved PCM
New STATIC lib + peaks_tests (CTest), mirroring bank_model. Float samples,
per-channel (no fold); exact integer bin spans handle remainder, short
buffers, and degenerate inputs with no OOB. Bin-span math guarded against
size_t overflow for pathological binCount.
2026-07-22 09:23:03 -04:00

67 lines
2.7 KiB
C++

#include "peaks.h"
#include <algorithm>
#include <climits>
// peaks implementation.
//
// One linear pass per channel. The frame->bin partition is computed with integer
// arithmetic so it is exact for any frameCount / binCount pairing: bin b owns the
// half-open frame span [b*frameCount/binCount, (b+1)*frameCount/binCount). That
// span formula distributes the remainder deterministically (earlier bins get the
// extra frames) with no rounding drift and no dropped tail — the last bin's end is
// always exactly frameCount.
namespace reasampler {
Envelope computeEnvelope(const std::vector<Sample>& interleaved,
std::size_t channelCount,
std::size_t frameCount,
std::size_t binCount) {
Envelope envelope(channelCount);
if (channelCount == 0) {
return envelope; // no channels -> no envelopes
}
// Never read past what the buffer actually holds, even if the caller's
// frameCount overstates the buffer (defensive: no OOB on a short buffer).
const std::size_t availableFrames = interleaved.size() / channelCount;
const std::size_t frames = std::min(frameCount, availableFrames);
for (std::size_t ch = 0; ch < channelCount; ++ch) {
ChannelEnvelope& bins = envelope[ch];
bins.assign(binCount, MinMax{}); // empty/degenerate bins default to {0,0}
for (std::size_t b = 0; b < binCount; ++b) {
// Half-open frame span for this bin: [b*frames/binCount, (b+1)*frames/binCount).
// Guard against size_t overflow in b*frames and (b+1)*frames: binCount is
// caller-controlled and unbounded, so when b >= SIZE_MAX/frames either
// multiplication could wrap. Any such bin is unreachable in practice
// (allocating that many MinMax entries would OOM first), but we guard
// explicitly to eliminate UB.
if (frames > 0 && b >= SIZE_MAX / frames) {
continue; // b*frames or (b+1)*frames would overflow; span is empty
}
const std::size_t begin = (b * frames) / binCount;
const std::size_t end = ((b + 1) * frames) / binCount;
if (begin >= end) {
continue; // empty span (binCount > frames) -> keep {0,0}
}
const Sample first = interleaved[begin * channelCount + ch];
Sample lo = first;
Sample hi = first;
for (std::size_t f = begin + 1; f < end; ++f) {
const Sample s = interleaved[f * channelCount + ch];
lo = std::min(lo, s);
hi = std::max(hi, s);
}
bins[b] = MinMax{lo, hi};
}
}
return envelope;
}
} // namespace reasampler