From c1895506e036c2c855f0cc167e29fc58fb21ad33 Mon Sep 17 00:00:00 2001 From: QIU Date: Fri, 14 Aug 2026 13:28:55 +0000 Subject: [PATCH] fix(cw): flush archive buffer inside loop to stop dropping audio CwDeepDecoder appended evicted samples with a bare bounds check: for (v in overflow) { if (archiveSize < archiveBuffer.size) archiveBuffer[archiveSize++] = v } if (archiveSize >= ARCHIVE_THRESHOLD) { flush() } Once archiveBuffer (64000 samples / 20 s) filled up mid-batch the remaining samples were silently discarded, because the flush only ran after the loop. Worst measured case: 47999 samples already accumulated (just under the 48000 flush threshold, so no flush) plus a 64000-sample overflow batch means 111999 samples pushed into a 64000 buffer -> 47999 dropped, i.e. 15 s of audio missing from the permanently archived CW history. Now the buffer is flushed as soon as it is full and before appending, so every sample reaches archiveDecode. Simulation over five batch patterns: dropped count goes 47999 -> 0 for the worst case and all 111999 samples are archived. Bounds: single append() can evict at most capacity samples, so drainOverflow() returns at most 64000 - archiveBuffer never needs to grow. --- .../look4sat/core/data/cw/CwDeepDecoder.kt | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/core/data/src/main/java/com/rtbishop/look4sat/core/data/cw/CwDeepDecoder.kt b/core/data/src/main/java/com/rtbishop/look4sat/core/data/cw/CwDeepDecoder.kt index e614b6d0..2788f133 100644 --- a/core/data/src/main/java/com/rtbishop/look4sat/core/data/cw/CwDeepDecoder.kt +++ b/core/data/src/main/java/com/rtbishop/look4sat/core/data/cw/CwDeepDecoder.kt @@ -181,8 +181,22 @@ class CwDeepDecoder(context: Context) : ICwDecoder { val overflow = buffer.drainOverflow() if (overflow.isNotEmpty()) { for (v in overflow) { - if (archiveSize < archiveBuffer.size) archiveBuffer[archiveSize++] = v + // Flush before appending when the buffer is full, so large batches + // (e.g. 47999 samples already accumulated + 64000 new overflow) + // do not silently drop audio that scrolled out of the live window. + if (archiveSize >= archiveBuffer.size) { + val audio = archiveBuffer.copyOf(archiveSize) + archiveSize = 0 + try { + archiveDecode(audio) + } catch (t: Throwable) { + if (t is CancellationException) throw t + Log.e(TAG, "archive decode failed", t) + } + } + archiveBuffer[archiveSize++] = v } + // Final flush when threshold is reached (e.g. exactly 48000 accumulated). if (archiveSize >= ARCHIVE_THRESHOLD) { val audio = archiveBuffer.copyOf(archiveSize) archiveSize = 0