// mlnl.h - mLnL annotation chunk parser (schema v2). // Walks a WAV file's RIFF chunks looking for the optional `mLnL` chunk // (UTF-8 JSON Lines), parses each line into an MlnlEvent. Every event is a // self-contained time range [t_start, t_end] (instantaneous events have // t_start == t_end). See mlnl_chunk_spec.md. #ifndef MLNL_H #define MLNL_H #include typedef enum { MLNL_KIND_UNKNOWN = 0, MLNL_KIND_CHANNEL_UP, MLNL_KIND_CHANNEL_DOWN, MLNL_KIND_CONTROL, MLNL_KIND_TX_FRAME, // v2 primary annotation: one daemon-sourced on-air frame MLNL_KIND_TX_BURST, // deprecated, energy-detected guess; superseded by tx_frame MLNL_KIND_ASSERTION_PASSED, MLNL_KIND_ASSERTION_FAILED, MLNL_KIND_IMPAIRMENT_FIRE, MLNL_KIND_GAIN_CHANGE, } MlnlKind; typedef struct { // Required. double t_start, t_end; MlnlKind kind; char kindStr[32]; // original "kind" string (for unknown kinds + display) // Frequency annotation (resolved from f_lo/f_hi OR f_center/f_bw). double f_lo_hz, f_hi_hz; bool has_freq; // Optional human/visual hints. char note[96]; bool has_note; unsigned char colorR, colorG, colorB; // parsed from "#RRGGBB" bool has_color; // Kind-specific fields. The has_* flag says whether the producer set it. unsigned int node; bool has_node; char command[64]; bool has_command; char name[128]; // assertion name bool has_name; char reason[96]; // assertion_failed reason bool has_reason; double peak, rms, papr_db; // tx_burst bool has_stats; double slack_s; // assertion_passed bool has_slack; unsigned int id; // tx_burst: monotonic per-wav identifier bool has_id; char label[32]; // tx_burst: optional frame tag (SEED/BULK/ACK/...) bool has_label; // tx_frame fields (schema v2 primary annotation). `node`/`has_node` above // carry the transmitting node_id (attribution + color). int seq, nFrames; // 0-based index + total frames in the PTT ("seq of n") bool has_seqn; // set once `n` is present char frame[32]; // mLink frame name (PRESENCE/SEED, BULK, BULK/RTS, ...) bool has_frame; char ch[16]; // daemon channel (EMERGENCY / ANNOUNCE_0|1|2 / BULK) bool has_ch; char rate[8]; // LDPC rate of a bulk OFDM frame ("1/2".."5/6"); else empty bool has_rate; // intent->air latency of this burst: air_time - modem render/intent time. // The producer already anchors t_start/t_end on the ACTUAL on-air edge, so // this is a profiling metric ("modem wants vs. actually does"), not a // plotting offset. Coarse/block-quantized. See mlnl_chunk_spec.md ยง4.2. double sched_offset_ms; bool has_sched_offset; } MlnlEvent; // Up to this many distinct MlnlKind values are tracked per-file (any new kinds // added to the enum must bump this). Indexed by the MlnlKind value. #define MLNL_KIND_MAX 16 typedef struct { MlnlEvent* events; int eventCount; bool truncated; // {"truncated":true} sentinel appeared bool loaded; // true iff the file contained a parseable mLnL chunk bool kindPresent[MLNL_KIND_MAX]; // which kinds the parser saw (drives the UI) } MlnlAnnotations; // Walk `path`'s RIFF chunks looking for `mLnL` and parse the JSONL payload. // Returns true on success (out->loaded is set the same way). On failure // (no chunk / unreadable / not WAV) returns false and zeroes *out. bool LoadMlnlFromWav(const char* path, MlnlAnnotations* out); void FreeMlnl(MlnlAnnotations* a); // Display label for an MlnlKind. const char* MlnlKindName(MlnlKind k); #endif // MLNL_H