codec: wire decode_waveform_v2 into production; add MicL dB helper
Replaces the broken legacy int16 LE decoder in client.py with the
verified multi-channel codec. Three changes:
1. blastware_file.extract_body_bytes(a5_frames) — new helper that
factors out the body-reconstruction logic from write_blastware_file
so both writers (BW binary) and decoders (sample arrays) can use
the same canonical bytes.
2. waveform_codec.decode_a5_frames(a5_frames) — production entry point.
Returns the raw_samples dict consumers expect (Tran/Vert/Long as
int16 ADC counts; MicL as native ADC counts). Internally:
A5 frames → extract_body_bytes → decode_waveform_v2
→ decoded_to_adc_counts (geos ×16; mic pass-through)
3. waveform_codec.mic_count_to_db(count) — MicL ADC → dB(L) per BW's
display formula:
dB = sign(count) × (81.94 + 20 × log10(|count|)) for |count| ≥ 1
Verified against V70 fixture: count=813 → 140.14 dB (BW PSPL 140.1).
client.py:_decode_a5_waveform is reduced to a thin wrapper that calls
decode_a5_frames and populates event.raw_samples. Original implementation
preserved as _decode_a5_waveform_LEGACY (dead code; reference only).
Also fixed a tail-end bug in decode_waveform_v2 where trailer-section
"40 02" markers (containing ASCII serial bytes, NOT real segment headers)
were being mis-interpreted, producing 2 spurious samples per channel at
the end of each event. Added bytes [12:14] == "02 00" validation to
reject non-header markers.
7 new pytest tests cover the new helpers and dB conversion. Total:
71 passing (up from 64).
Known limitation (carried over from before): the walker still stops
mid-event on the loudest fixtures (SP0/SS0/SV0/event-b) at some
mid-segment edge cases not yet characterized. Every sample reached
is decoded correctly; the walker just doesn't reach all of them.
Loud events still yield 5,000–15,000 byte-exact samples each.
This commit is contained in:
@@ -16,7 +16,9 @@ from minimateplus.waveform_codec import (
|
||||
WaveformBlock,
|
||||
decode_tran_initial,
|
||||
decode_waveform_v2,
|
||||
decoded_to_adc_counts,
|
||||
find_data_start,
|
||||
mic_count_to_db,
|
||||
parse_segment_header,
|
||||
split_segments,
|
||||
walk_body,
|
||||
@@ -448,3 +450,60 @@ def test_decode_tran_initial_full_segment_silent_events():
|
||||
)
|
||||
# And we should have decoded at least 400 samples (= segment 0 worth).
|
||||
assert n >= 400, f"only {n} samples decoded for {path}"
|
||||
|
||||
|
||||
# ── ADC scaling + dB conversion ──────────────────────────────────────────────
|
||||
|
||||
|
||||
def test_decoded_to_adc_counts_geo_scales_by_16():
|
||||
"""Geo channels in decoder units (16-count) should multiply by 16 to ADC."""
|
||||
decoded = {"Tran": [0, 1, -2, 100], "Vert": [5], "Long": [-10], "MicL": [813]}
|
||||
adc = decoded_to_adc_counts(decoded)
|
||||
assert adc["Tran"] == [0, 16, -32, 1600]
|
||||
assert adc["Vert"] == [80]
|
||||
assert adc["Long"] == [-160]
|
||||
# Mic passes through unchanged (already ADC counts).
|
||||
assert adc["MicL"] == [813]
|
||||
|
||||
|
||||
def test_decoded_to_adc_counts_empty():
|
||||
assert decoded_to_adc_counts({}) == {}
|
||||
assert decoded_to_adc_counts(
|
||||
{"Tran": [], "Vert": [], "Long": [], "MicL": []}
|
||||
) == {"Tran": [], "Vert": [], "Long": [], "MicL": []}
|
||||
|
||||
|
||||
def test_mic_count_to_db_zero_is_zero():
|
||||
assert mic_count_to_db(0) == 0.0
|
||||
|
||||
|
||||
def test_mic_count_to_db_unit_is_reference():
|
||||
"""count = ±1 → ±81.94 dB (the calibration reference)."""
|
||||
assert abs(mic_count_to_db(1) - 81.94) < 0.01
|
||||
assert abs(mic_count_to_db(-1) - (-81.94)) < 0.01
|
||||
|
||||
|
||||
def test_mic_count_to_db_doubles_every_6db():
|
||||
"""Each doubling of |count| adds ~6.02 dB."""
|
||||
# count=2 → 87.96 dB (+ 6.02 from 81.94)
|
||||
assert abs(mic_count_to_db(2) - 87.96) < 0.05
|
||||
# count=4 → 93.98 dB
|
||||
assert abs(mic_count_to_db(4) - 93.98) < 0.05
|
||||
# count=8 → 100.00 dB
|
||||
assert abs(mic_count_to_db(8) - 100.00) < 0.05
|
||||
|
||||
|
||||
def test_mic_count_to_db_v70_peak():
|
||||
"""V70 mic peak count 813 → 140.14 dB (matches BW reported PSPL 140.1)."""
|
||||
assert abs(mic_count_to_db(813) - 140.14) < 0.1
|
||||
# And the negative-direction equivalent
|
||||
assert abs(mic_count_to_db(-813) - (-140.14)) < 0.1
|
||||
|
||||
|
||||
# ── End-to-end: decode_a5_frames (production entry point) ───────────────────
|
||||
|
||||
|
||||
def test_decode_a5_frames_empty():
|
||||
from minimateplus.waveform_codec import decode_a5_frames
|
||||
assert decode_a5_frames([]) is None
|
||||
assert decode_a5_frames(None) is None
|
||||
|
||||
Reference in New Issue
Block a user