T2: realtime capture tail trimming
Auto/Manual/Off tail handling for realtime track-tap capture. Auto scans recorded PCM backward for last frame above -72 dB and truncates the wav header-aware (8 s cap); Manual pads a fixed tail; Off is byte-identical. wav_trim rejects WAVE_FORMAT_EXTENSIBLE with non-float SubFormat GUID.
This commit is contained in:
@@ -278,6 +278,76 @@ static void testLargeBinCountOverflowGuard() {
|
||||
CHECK(env[0][7].min == 0.0f && env[0][7].max == 0.0f);
|
||||
}
|
||||
|
||||
// --- lastFrameAboveThreshold: the realtime tail's decay-scan boundary primitive --
|
||||
|
||||
// A mono decaying ramp: frame i has amplitude that falls linearly to zero. With a
|
||||
// threshold set between two frames' levels, the last frame above it is deterministic.
|
||||
static void testLastFrameDecayingRamp() {
|
||||
// 10 mono frames, amplitude 1.0 - i*0.1: frame0=1.0 ... frame9=0.1.
|
||||
std::vector<AudioSample> buf(10);
|
||||
for (std::size_t i = 0; i < 10; ++i) buf[i] = 1.0f - 0.1f * (float)i;
|
||||
|
||||
// Threshold 0.35: frames 0..6 (levels 1.0..0.4) exceed it; frame 6 is the last
|
||||
// (level 0.4 > 0.35), frame 7 (0.3) does not. Strict > semantics.
|
||||
CHECK(lastFrameAboveThreshold(buf, 1, 10, 0.35f) == 6);
|
||||
|
||||
// Threshold just under frame 9's level (0.1): the very last frame stays.
|
||||
CHECK(lastFrameAboveThreshold(buf, 1, 10, 0.05f) == 9);
|
||||
|
||||
// Threshold above the loudest frame: nothing survives.
|
||||
CHECK(lastFrameAboveThreshold(buf, 1, 10, 1.5f) == kNoFrameAboveThreshold);
|
||||
}
|
||||
|
||||
// Pure silence at or below the threshold -> sentinel (the "trim back to end" case:
|
||||
// no frame in the tail window exceeds -72 dB).
|
||||
static void testLastFrameSilence() {
|
||||
std::vector<AudioSample> zeros(20, 0.0f);
|
||||
CHECK(lastFrameAboveThreshold(zeros, 2, 10, 0.001f) == kNoFrameAboveThreshold);
|
||||
|
||||
// A DC level exactly AT the threshold does not count (strict >).
|
||||
std::vector<AudioSample> atThresh(8, 0.25f);
|
||||
CHECK(lastFrameAboveThreshold(atThresh, 1, 8, 0.25f) == kNoFrameAboveThreshold);
|
||||
}
|
||||
|
||||
// Every frame above the threshold (a non-decaying source): the last frame is the
|
||||
// boundary — the caller keeps the whole window (the 8 s cap did its job).
|
||||
static void testLastFrameAllAbove() {
|
||||
std::vector<AudioSample> loud(12, 0.8f); // 6 stereo frames
|
||||
CHECK(lastFrameAboveThreshold(loud, 2, 6, 0.1f) == 5);
|
||||
}
|
||||
|
||||
// Per-frame peak is the MAX abs across channels (no fold): a frame with one loud
|
||||
// channel and one silent channel is "above" on the strength of the loud one, and a
|
||||
// negative sample is compared by magnitude.
|
||||
static void testLastFramePerChannelMaxAbs() {
|
||||
// 3 stereo frames. Frame0: (0.9, 0.0) loud L. Frame1: (0.0, -0.9) loud R (negative
|
||||
// -> abs). Frame2: (0.05, -0.05) both quiet.
|
||||
std::vector<AudioSample> buf = {0.9f, 0.0f, 0.0f, -0.9f, 0.05f, -0.05f};
|
||||
// Threshold 0.5: frame2 is below (peak 0.05), frame1 is above (|-0.9|=0.9).
|
||||
CHECK(lastFrameAboveThreshold(buf, 2, 3, 0.5f) == 1);
|
||||
// If both channels of the last frame mattered independently, a fold-average
|
||||
// (0.9+0.0)/2 = 0.45 on frame0 would fall below 0.5 — but frame0's L alone (0.9)
|
||||
// is above, proving max-abs, not average. Lower the threshold to isolate frame0.
|
||||
std::vector<AudioSample> f0 = {0.9f, 0.0f};
|
||||
CHECK(lastFrameAboveThreshold(f0, 2, 1, 0.5f) == 0);
|
||||
}
|
||||
|
||||
// Degenerate: zero channels, zero frames, and a frameCount that overstates the
|
||||
// buffer (must clamp to available frames, no OOB read).
|
||||
static void testLastFrameDegenerate() {
|
||||
std::vector<AudioSample> buf = {0.5f, 0.5f, 0.5f, 0.5f}; // 2 stereo frames
|
||||
|
||||
CHECK(lastFrameAboveThreshold(buf, 0, 2, 0.1f) == kNoFrameAboveThreshold);
|
||||
CHECK(lastFrameAboveThreshold(buf, 2, 0, 0.1f) == kNoFrameAboveThreshold);
|
||||
|
||||
std::vector<AudioSample> empty;
|
||||
CHECK(lastFrameAboveThreshold(empty, 2, 10, 0.1f) == kNoFrameAboveThreshold);
|
||||
|
||||
// frameCount=100 but only 2 real stereo frames: clamps to frame 1 (the last real
|
||||
// frame), which is above -> index 1, no read past the buffer.
|
||||
CHECK(lastFrameAboveThreshold(buf, 2, 100, 0.1f) == 1);
|
||||
}
|
||||
|
||||
int main() {
|
||||
testSineEnvelope();
|
||||
testRampMonotonic();
|
||||
@@ -289,6 +359,11 @@ int main() {
|
||||
testSingleBinWholeBuffer();
|
||||
testDegenerateInputs();
|
||||
testLargeBinCountOverflowGuard();
|
||||
testLastFrameDecayingRamp();
|
||||
testLastFrameSilence();
|
||||
testLastFrameAllAbove();
|
||||
testLastFramePerChannelMaxAbs();
|
||||
testLastFrameDegenerate();
|
||||
|
||||
if (g_fail == 0) std::printf("All tests passed.\n");
|
||||
return g_fail ? 1 : 0;
|
||||
|
||||
Reference in New Issue
Block a user