#!/usr/bin/env python3
# First-order audio filters (one-pole high-pass and low-pass) through the firmware demodulator and framer
# (innerpulse DM-1701 review audits). Checks claims such as "a 30 Hz first-order high-pass kills decoding" and
# "de-emphasis is a low-pass and by itself benign" against standard C4FM.
# Run: DM1701_REPO=/path/to/DM-1701 C4FM_SIM_WORK=/path/to/work python3 firstorder_check.py
import json
import math
import sys
from pathlib import Path

sys.dont_write_bytecode = True

sys.path.insert(0, str(Path(__file__).resolve().parent))
import c4fm_sim as S  # noqa: E402


def onepole_hp(fc):
    K = math.tan(math.pi * fc / S.FS)
    b0 = 1.0 / (K + 1.0)
    return (b0, -b0, 0.0), (1.0, (K - 1.0) / (K + 1.0), 0.0)


CASES = [('none', 'no filtering', []),
         ('hp10', 'first-order high-pass 10 Hz', [onepole_hp(10.0)]),
         ('hp30', 'first-order high-pass 30 Hz', [onepole_hp(30.0)]),
         ('hp100', 'first-order high-pass 100 Hz', [onepole_hp(100.0)]),
         ('hp300', 'first-order high-pass 300 Hz', [onepole_hp(300.0)]),
         ('lp2000', 'first-order low-pass 2 kHz', [S.onepole_lp(2000.0)]),
         ('lp300', 'first-order low-pass 300 Hz (de-emphasis)', [S.onepole_lp(300.0)])]


def main():
    S.build_libs()
    lib = S.load('rrc')
    wire, wanted = S.build_stream(20, 1701)
    base = S.tx_c4fm(wire)
    out = []
    for key, label, stages in CASES:
        row = {'case': key, 'label': label}
        for tag, snr in (('clean', None), ('snr20', 20)):
            y = S.add_noise(base, snr, 777)
            y = S.apply_chain(y, stages) if stages else y
            ref = S.apply_chain(base, stages) if stages else base
            y = y * (6000.0 / max(1.0, float(abs(ref).max())))
            frames, nids, dibits, _ = S.decode(lib, y, len(wanted))
            errors, symbols = S.raw_ser(dibits, wire)
            row[tag] = {'frames': frames, 'nids': nids, 'ser': errors / max(symbols, 1)}
        print(f"{label:44s} noise-free {row['clean']['frames']:3d}/180 frames · 20 dB {row['snr20']['frames']:3d}/180", flush=True)
        out.append(row)
    S.WORK.mkdir(parents=True, exist_ok=True)
    (S.WORK / 'firstorder_check.json').write_text(json.dumps(out, indent=1))


if __name__ == '__main__':
    main()
