Peak detector — algorithm overview¶
The peak detector (scripts/median-filter/csrc/peak_detector.c) isolates short
impulse-like peaks in noisy recordings. For the function-level C API, see the
auto-generated C API reference.
Motivation and purpose¶
The detector isolates short impulse-like peaks in noisy recordings without requiring heap allocations. It keeps a sliding window of multiple taps (blocks) and uses per-offset medians to estimate background noise. The design trades a small amount of memory for deterministic runtime and predictable latency.
Terminology¶
- tap — a contiguous block of
tap_sizesamples processed together. - offset — sample position inside a tap (
0 .. tap_size-1). - middle tap — the chronological middle block inside the sliding window. A peak must appear here to be eligible, because the surrounding taps provide context.
Architecture¶
- A ring buffer holds
num_tapsblocks, each of lengthtap_size. - For every offset there are two heaps (max/min) that store the samples for that offset across all taps. Lazy invalidation via generation counters avoids deleting stale nodes when the write cursor wraps.
- A running RMS accumulator sums the squares of all samples in the window to deliver a dynamic noise estimate.
Step-by-step algorithm¶
- Fill the window. Push blocks into the ring until
num_tapstaps are available. Each sample updates its per-offset heaps and the RMS sum. - Noise estimate via median. For every offset, take the max-heap top as the median of that offset across taps. This forms a noise profile for the entire middle tap.
- Find maximum deviation. In the middle tap, find the offset whose sample deviates the most (positive) from its noise median. This is the peak candidate.
- RMS gate. Convert the RMS accumulator into
rms_noiseand require thatpeak_value > det_levelandpeak_value > det_rms * rms_noise. - Before/after energy test. Compute medians inside the middle tap before and after
the candidate offset. The "before" energy must exceed the "after" energy scaled by
det_energy. This rejects symmetric spikes or pure noise. - Absolute position. Translate the offset in the middle tap to an absolute position
by combining
block_start_offsetwith tap indices. The newest tap is always known, so the middle tap index is derived relative to it.
Differences vs. the paper, and rationale¶
- Lazy invalidation. Instead of removing old samples, generation counters mark stale nodes. This keeps updates O(log N) and avoids heap compaction on embedded targets.
- Fixed-size buffers. All memory is supplied by the caller; no dynamic allocation occurs. This makes the detector predictable and safe for real-time contexts.
- Truncated median slices. The small before/after median uses a fixed stack buffer
(
MEDIAN_SLICE_MAX_LEN) to avoid heap use. In practicetap_sizeis well within that bound.
API integration¶
Key entry points (full signatures in the C API reference):
detector_state_size— compute the required buffer size for a configuration.detector_init/detector_reset— prepare or clear the detector state without dynamic allocation.detector_feed_block— online processing that returns peak hits as soon as the window is full.detect_recording_i16— convenience offline routine for entire recordings.