|
|
|
@@ -0,0 +1,631 @@ |
|
|
|
{ |
|
|
|
"concept": "fm-rds-tx composite/mpx live metering", |
|
|
|
"version": 3.1, |
|
|
|
"status": "draft", |
|
|
|
"summary": "Introduce true runtime broadcast-style metering for the FM stereo/RDS multiplex chain by measuring multiple semantically distinct DSP stages and exposing them through a dedicated measurements endpoint for Overview, Flow, and diagnostics UI surfaces.", |
|
|
|
"problemStatement": { |
|
|
|
"currentState": [ |
|
|
|
"The current UI mostly exposes configured MPX-related values such as pilotLevel, rdsInjection, mpxGain, BS.412 state, and composite clipper state.", |
|
|
|
"These values describe intended configuration but not the actual measured runtime behavior of the multiplex chain.", |
|
|
|
"The current Flow Composite / MPX node is therefore closer to a config summary than to a true live broadcast metering surface.", |
|
|
|
"Runtime telemetry currently focuses on transport and engine health such as queue health, underruns, runtime states, and fault transitions, but not on actual composite/MPX signal composition." |
|
|
|
], |
|
|
|
"operationalGap": [ |
|
|
|
"Operators cannot currently see how audio processing, stereo encoding, composite clipping, BS.412, pilot injection, and RDS injection interact on the live signal.", |
|
|
|
"Without live stage-aware metering, it is difficult to identify whether signal behavior is caused by audio processing, compliance shaping, or final composite assembly.", |
|
|
|
"A single config snapshot is not enough to answer whether the transmitted multiplex is nominal, compliance-limited, overdriven, missing pilot, or missing RDS." |
|
|
|
] |
|
|
|
}, |
|
|
|
"objective": { |
|
|
|
"primary": "Turn the Composite / MPX area of fm-rds-tx into a true runtime metering surface that reflects measured DSP behavior rather than only saved configuration values.", |
|
|
|
"secondary": [ |
|
|
|
"Give operators a trustworthy view of what the DSP chain is actually producing at critical stages.", |
|
|
|
"Separate audio-path behavior, BS.412 compliance behavior, and final composite/on-air behavior into distinct measured views.", |
|
|
|
"Support a richer Overview and Flow UI without increasing full-runtime polling cost.", |
|
|
|
"Create a stable foundation for future alerts, engineering diagnostics, and signal-quality workflows." |
|
|
|
] |
|
|
|
}, |
|
|
|
"whatWeAreTryingToAchieve": [ |
|
|
|
{ |
|
|
|
"goal": "Show real measured multiplex behavior", |
|
|
|
"description": "Measure actual runtime signal values at key DSP stages so the UI reflects what is really happening in the chain, not only what was configured." |
|
|
|
}, |
|
|
|
{ |
|
|
|
"goal": "Explain stage interactions", |
|
|
|
"description": "Make visible how processing, stereo encoding, composite clipping, BS.412, pilot injection, and RDS injection each affect the final multiplex." |
|
|
|
}, |
|
|
|
{ |
|
|
|
"goal": "Separate compliance and on-air views", |
|
|
|
"description": "Provide distinct views before BS.412, after BS.412, and at the final composite point directly before FM modulation." |
|
|
|
}, |
|
|
|
{ |
|
|
|
"goal": "Support operator decisions", |
|
|
|
"description": "Allow operators to quickly assess whether the system is nominal, overdriven, compliance-limited, missing pilot/RDS, or behaving unexpectedly." |
|
|
|
}, |
|
|
|
{ |
|
|
|
"goal": "Stay aligned with the actual DSP architecture", |
|
|
|
"description": "Add metering in a way that matches the current GenerateFrame() signal flow and preserves semantic clarity between stages." |
|
|
|
} |
|
|
|
], |
|
|
|
"expectedOutcome": { |
|
|
|
"overviewUi": [ |
|
|
|
"Overview can show measured L/R and final MPX/composite values instead of only static configuration values.", |
|
|
|
"Pilot and RDS indicators become measurement-backed rather than purely declarative." |
|
|
|
], |
|
|
|
"flowUi": [ |
|
|
|
"The Composite / MPX node becomes a true live-status node.", |
|
|
|
"The Flow popover can compare pre-BS.412, post-BS.412, and final pre-IQ behavior." |
|
|
|
], |
|
|
|
"diagnostics": [ |
|
|
|
"Engineers can determine whether a problem is introduced before BS.412, by BS.412, or only in the final composite stage." |
|
|
|
], |
|
|
|
"futureWork": [ |
|
|
|
"The same model can support alerts, trends, compliance heuristics, and richer engineering telemetry later." |
|
|
|
] |
|
|
|
}, |
|
|
|
"designPrinciples": [ |
|
|
|
"Meter measured runtime signal states, not only configured target values.", |
|
|
|
"Keep measurement taps aligned with actual DSP semantics already present in generator.GenerateFrame().", |
|
|
|
"Separate audio-path compliance measurements from final composite/on-air measurements.", |
|
|
|
"Expose a dedicated faster measurements endpoint instead of raising full UI polling frequency.", |
|
|
|
"Provide a stable and explicit measurement model that can serve UI, diagnostics, and future alerting.", |
|
|
|
"Use chunk-level aggregation, not per-sample external reporting.", |
|
|
|
"Keep measurement logic low-overhead and safe for the hot path.", |
|
|
|
"Prefer semantically conservative field names over catchy but ambiguous meter labels." |
|
|
|
], |
|
|
|
"whyMultipleTaps": [ |
|
|
|
"A single final composite reading cannot explain whether a change came from L/R processing, composite clipping, BS.412 gain reduction, or fixed pilot/RDS injection.", |
|
|
|
"BS.412 acts on the audio MPX budget, not on pilot/RDS directly, so pre-BS.412 and post-BS.412 views are semantically different.", |
|
|
|
"Pilot and RDS become meaningful only in the final composite stage, so they require final pre-IQ measurement.", |
|
|
|
"Operator-facing UI needs both an audio/compliance view and a final on-air-adjacent composite view." |
|
|
|
], |
|
|
|
"semanticAdjustmentsFromReview": [ |
|
|
|
"The L/R tap must account for optional watermark insertion between first-pass audio processing and stereo encoding.", |
|
|
|
"Pilot and RDS percent-style fields must not conflate RMS measurements with injection-equivalent display values.", |
|
|
|
"Composite threshold counters must avoid overmodulation-suggestive names unless their semantics are explicitly regulatory, which they are not.", |
|
|
|
"Composite clipper-related metrics should avoid claiming hard clip event semantics where the implementation is actually shaping/protection-oriented.", |
|
|
|
"BS.412 gain must be described as a chunk-level running control state, not a sample-exact causal response.", |
|
|
|
"Final-composite interpretation should expose feature flags such as watermark, RDS2, and license injection activity.", |
|
|
|
"stereoMode should be exposed alongside stereoEnabled so non-DSB operation remains interpretable in diagnostics and UI.", |
|
|
|
"licenseInjectionActive must mean chunk-local actual activity, not merely that the feature exists.", |
|
|
|
"rdsInjectionEquivalentPercent must be mathematically defined as a derived operator-facing quantity before implementation begins." |
|
|
|
], |
|
|
|
"measurementTaps": [ |
|
|
|
{ |
|
|
|
"id": "lr_pre_encode_post_watermark", |
|
|
|
"label": "L/R Pre-Encode Post-Watermark", |
|
|
|
"stage": "after pre-emphasis, audio LPF, pilot notch, output drive, stereo limiter, cleanup LPF, final L/R hard clipping, and optional watermark injection; immediately before stereo encoding", |
|
|
|
"locationInCode": { |
|
|
|
"file": "internal/offline/generator.go", |
|
|
|
"function": "GenerateFrame", |
|
|
|
"region": "after optional watermark has been added back into lBuf/rBuf and before stereo encoding pass" |
|
|
|
}, |
|
|
|
"semanticPurpose": [ |
|
|
|
"Represents the effective left/right program audio that actually enters the stereo encoder.", |
|
|
|
"Avoids ambiguity when watermarking is enabled." |
|
|
|
], |
|
|
|
"primaryUseCases": [ |
|
|
|
"Overview L/R meters", |
|
|
|
"Channel imbalance visibility", |
|
|
|
"Audio processing inspection" |
|
|
|
], |
|
|
|
"metrics": [ |
|
|
|
"l_rms", |
|
|
|
"r_rms", |
|
|
|
"l_peak_abs", |
|
|
|
"r_peak_abs", |
|
|
|
"lr_balance_db", |
|
|
|
"l_clip_events", |
|
|
|
"r_clip_events" |
|
|
|
], |
|
|
|
"notes": [ |
|
|
|
"If a future pre-watermark tap is desired for engineering comparison, it should be added as a separate optional diagnostic tap rather than overloading this one." |
|
|
|
], |
|
|
|
"required": true |
|
|
|
}, |
|
|
|
{ |
|
|
|
"id": "audio_mpx_pre_bs412", |
|
|
|
"label": "Audio MPX Pre-BS.412", |
|
|
|
"stage": "after stereo encode and composite clipper/protection, before BS.412 gain is applied", |
|
|
|
"locationInCode": { |
|
|
|
"file": "internal/offline/generator.go", |
|
|
|
"function": "GenerateFrame", |
|
|
|
"region": "after audioMPX is finalized from mono/stereo and after composite clipper or legacy clip/notch path, before bs412Gain multiplication" |
|
|
|
}, |
|
|
|
"semanticPurpose": [ |
|
|
|
"Represents the raw audio-only multiplex signal before BS.412 compliance shaping.", |
|
|
|
"Shows what the multiplex audio path is structurally producing." |
|
|
|
], |
|
|
|
"primaryUseCases": [ |
|
|
|
"Raw MPX level view", |
|
|
|
"Comparison against post-BS.412 attenuation", |
|
|
|
"Explaining why final loudness changed" |
|
|
|
], |
|
|
|
"metrics": [ |
|
|
|
"audio_mpx_rms", |
|
|
|
"audio_mpx_peak_abs", |
|
|
|
"mono_component_rms", |
|
|
|
"stereo_component_rms", |
|
|
|
"crest_factor", |
|
|
|
"clipper_lookahead_gain", |
|
|
|
"clipper_envelope", |
|
|
|
"clipper_or_protection_active" |
|
|
|
], |
|
|
|
"notes": [ |
|
|
|
"Avoid promising exact hard clip event semantics here when the active implementation may be a true composite clipper/protection stage rather than simple hard clipping.", |
|
|
|
"If clipper_or_protection_active is exposed, it must be derived from a documented condition. Prefer exposing raw clipper diagnostics such as lookahead gain and envelope so UI can derive simpler booleans later." |
|
|
|
], |
|
|
|
"required": true |
|
|
|
}, |
|
|
|
{ |
|
|
|
"id": "audio_mpx_post_bs412", |
|
|
|
"label": "Audio MPX Post-BS.412", |
|
|
|
"stage": "after BS.412 gain reduction on audio MPX, before pilot and RDS injection", |
|
|
|
"locationInCode": { |
|
|
|
"file": "internal/offline/generator.go", |
|
|
|
"function": "GenerateFrame", |
|
|
|
"region": "immediately after bs412Gain is applied to audioMPX and before composite starts from audioMPX" |
|
|
|
}, |
|
|
|
"semanticPurpose": [ |
|
|
|
"Represents the compliance-shaped audio multiplex signal.", |
|
|
|
"Shows what survives BS.412 before fixed protected components are added." |
|
|
|
], |
|
|
|
"primaryUseCases": [ |
|
|
|
"BS.412 activity visibility", |
|
|
|
"Compliance-focused diagnostics", |
|
|
|
"Gain reduction explanation" |
|
|
|
], |
|
|
|
"metrics": [ |
|
|
|
"audio_mpx_rms", |
|
|
|
"audio_mpx_peak_abs", |
|
|
|
"bs412_gain_applied", |
|
|
|
"bs412_attenuation_db", |
|
|
|
"estimated_audio_power" |
|
|
|
], |
|
|
|
"notes": [ |
|
|
|
"bs412_gain_applied is a chunk-level running control value and must not be presented as a sample-instantaneous causal reaction." |
|
|
|
], |
|
|
|
"required": true |
|
|
|
}, |
|
|
|
{ |
|
|
|
"id": "composite_final_pre_iq", |
|
|
|
"label": "Composite Final Pre-IQ", |
|
|
|
"stage": "after audio MPX plus pilot plus RDS plus optional RDS2 and optional license injection, immediately before fmMod.Modulate(composite) or direct composite output", |
|
|
|
"locationInCode": { |
|
|
|
"file": "internal/offline/generator.go", |
|
|
|
"function": "GenerateFrame", |
|
|
|
"region": "final composite variable just before FM modulation or direct composite write" |
|
|
|
}, |
|
|
|
"semanticPurpose": [ |
|
|
|
"Represents the true final composite signal that is about to enter FM modulation or composite output.", |
|
|
|
"This is the most operator-relevant on-air-adjacent metering point." |
|
|
|
], |
|
|
|
"primaryUseCases": [ |
|
|
|
"Final MPX meter", |
|
|
|
"Pilot and RDS measurement", |
|
|
|
"Peak/deviation proxy", |
|
|
|
"On-air confidence" |
|
|
|
], |
|
|
|
"metrics": [ |
|
|
|
"composite_rms", |
|
|
|
"composite_peak_abs", |
|
|
|
"pilot_rms", |
|
|
|
"pilot_peak_abs", |
|
|
|
"pilot_injection_equivalent_percent", |
|
|
|
"rds_rms", |
|
|
|
"rds_peak_abs", |
|
|
|
"rds_injection_equivalent_percent", |
|
|
|
"composite_over_nominal_events", |
|
|
|
"composite_over_headroom_events" |
|
|
|
], |
|
|
|
"notes": [ |
|
|
|
"Percent-style fields here are operator-facing derived values and must be clearly separated from raw RMS values.", |
|
|
|
"Composite threshold counters are internal normalized envelope indicators, not regulatory overmodulation judgements.", |
|
|
|
"rdsInjectionEquivalentPercent must be implemented only after its mathematical derivation has been explicitly documented." |
|
|
|
], |
|
|
|
"required": true |
|
|
|
} |
|
|
|
], |
|
|
|
"preferredMetricSemantics": { |
|
|
|
"rms": "Chunk-local RMS of the measured signal component.", |
|
|
|
"peak_abs": "Maximum absolute sample magnitude within the chunk.", |
|
|
|
"injection_equivalent_percent": "A derived operator-facing injection/deviation-style display value, not identical to RMS. This should be documented separately from raw measured signal energy.", |
|
|
|
"clip_events": "Count of samples that hit or exceed a defined clipping threshold. Use only where the tapped stage really has hard-threshold clip semantics.", |
|
|
|
"attenuation_db": "Derived gain-reduction view, typically 20*log10(post/pre) when meaningful.", |
|
|
|
"estimated_audio_power": "Chunk-local power estimate suitable for BS.412-related operator display, but not legal certification.", |
|
|
|
"over_nominal_events": "Count of samples above an internal nominal composite reference threshold in normalized composite units.", |
|
|
|
"over_headroom_events": "Count of samples above a higher internal headroom reference threshold in normalized composite units.", |
|
|
|
"crest_factor": "Derived peak-versus-RMS characteristic useful where clipper/protection stages do not map cleanly to simple clip event counters." |
|
|
|
}, |
|
|
|
"recommendedMeasurementFields": { |
|
|
|
"topLevel": [ |
|
|
|
"timestamp", |
|
|
|
"sampleRateHz", |
|
|
|
"chunkSamples", |
|
|
|
"chunkDurationMs", |
|
|
|
"sequence", |
|
|
|
"stale", |
|
|
|
"noData", |
|
|
|
"flags" |
|
|
|
], |
|
|
|
"flags": [ |
|
|
|
"stereoEnabled", |
|
|
|
"stereoMode", |
|
|
|
"rdsEnabled", |
|
|
|
"rds2Enabled", |
|
|
|
"bs412Enabled", |
|
|
|
"compositeClipperEnabled", |
|
|
|
"watermarkEnabled", |
|
|
|
"licenseInjectionActive" |
|
|
|
], |
|
|
|
"groups": { |
|
|
|
"lrPreEncodePostWatermark": [ |
|
|
|
"lRms", |
|
|
|
"rRms", |
|
|
|
"lPeakAbs", |
|
|
|
"rPeakAbs", |
|
|
|
"lrBalanceDb", |
|
|
|
"lClipEvents", |
|
|
|
"rClipEvents" |
|
|
|
], |
|
|
|
"audioMpxPreBs412": [ |
|
|
|
"rms", |
|
|
|
"peakAbs", |
|
|
|
"monoRms", |
|
|
|
"stereoRms", |
|
|
|
"crestFactor", |
|
|
|
"clipperLookaheadGain", |
|
|
|
"clipperEnvelope", |
|
|
|
"clipperOrProtectionActive" |
|
|
|
], |
|
|
|
"audioMpxPostBs412": [ |
|
|
|
"rms", |
|
|
|
"peakAbs", |
|
|
|
"bs412GainApplied", |
|
|
|
"bs412AttenuationDb", |
|
|
|
"estimatedAudioPower" |
|
|
|
], |
|
|
|
"compositeFinalPreIq": [ |
|
|
|
"rms", |
|
|
|
"peakAbs", |
|
|
|
"pilotRms", |
|
|
|
"pilotPeakAbs", |
|
|
|
"pilotInjectionEquivalentPercent", |
|
|
|
"rdsRms", |
|
|
|
"rdsPeakAbs", |
|
|
|
"rdsInjectionEquivalentPercent", |
|
|
|
"overNominalEvents", |
|
|
|
"overHeadroomEvents" |
|
|
|
] |
|
|
|
} |
|
|
|
}, |
|
|
|
"dataModelProposal": { |
|
|
|
"goTypeName": "MeasurementSnapshot", |
|
|
|
"shape": { |
|
|
|
"timestamp": "time.Time", |
|
|
|
"sampleRateHz": "float64", |
|
|
|
"chunkSamples": "int", |
|
|
|
"chunkDurationMs": "float64", |
|
|
|
"sequence": "uint64", |
|
|
|
"stale": "bool", |
|
|
|
"noData": "bool", |
|
|
|
"flags": { |
|
|
|
"stereoEnabled": "bool", |
|
|
|
"stereoMode": "string", |
|
|
|
"rdsEnabled": "bool", |
|
|
|
"rds2Enabled": "bool", |
|
|
|
"bs412Enabled": "bool", |
|
|
|
"compositeClipperEnabled": "bool", |
|
|
|
"watermarkEnabled": "bool", |
|
|
|
"licenseInjectionActive": "bool" |
|
|
|
}, |
|
|
|
"lrPreEncodePostWatermark": { |
|
|
|
"lRms": "float64", |
|
|
|
"rRms": "float64", |
|
|
|
"lPeakAbs": "float64", |
|
|
|
"rPeakAbs": "float64", |
|
|
|
"lrBalanceDb": "float64", |
|
|
|
"lClipEvents": "uint32", |
|
|
|
"rClipEvents": "uint32" |
|
|
|
}, |
|
|
|
"audioMpxPreBs412": { |
|
|
|
"rms": "float64", |
|
|
|
"peakAbs": "float64", |
|
|
|
"monoRms": "float64", |
|
|
|
"stereoRms": "float64", |
|
|
|
"crestFactor": "float64", |
|
|
|
"clipperLookaheadGain": "float64", |
|
|
|
"clipperEnvelope": "float64", |
|
|
|
"clipperOrProtectionActive": "bool" |
|
|
|
}, |
|
|
|
"audioMpxPostBs412": { |
|
|
|
"rms": "float64", |
|
|
|
"peakAbs": "float64", |
|
|
|
"bs412GainApplied": "float64", |
|
|
|
"bs412AttenuationDb": "float64", |
|
|
|
"estimatedAudioPower": "float64" |
|
|
|
}, |
|
|
|
"compositeFinalPreIq": { |
|
|
|
"rms": "float64", |
|
|
|
"peakAbs": "float64", |
|
|
|
"pilotRms": "float64", |
|
|
|
"pilotPeakAbs": "float64", |
|
|
|
"pilotInjectionEquivalentPercent": "float64", |
|
|
|
"rdsRms": "float64", |
|
|
|
"rdsPeakAbs": "float64", |
|
|
|
"rdsInjectionEquivalentPercent": "float64 // TODO: only expose after mathematical derivation is fixed; otherwise omit or null in MVP", |
|
|
|
"overNominalEvents": "uint32", |
|
|
|
"overHeadroomEvents": "uint32" |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
"aggregationApproach": { |
|
|
|
"method": "chunk-local accumulation", |
|
|
|
"details": [ |
|
|
|
"Accumulate sums, absolute peaks, and counters during one GenerateFrame() call.", |
|
|
|
"Finalize RMS and derived values once per chunk into one immutable snapshot.", |
|
|
|
"Publish only the latest completed snapshot.", |
|
|
|
"Do not emit per-sample external telemetry or unbounded histories from the hot path." |
|
|
|
], |
|
|
|
"why": [ |
|
|
|
"Matches current engine/generator architecture.", |
|
|
|
"Provides enough temporal resolution for 4-10 Hz UI polling.", |
|
|
|
"Keeps CPU and allocation overhead predictable." |
|
|
|
] |
|
|
|
}, |
|
|
|
"placementRecommendation": { |
|
|
|
"primaryOwner": "generator", |
|
|
|
"reasoning": [ |
|
|
|
"All semantically relevant intermediate signals already exist in GenerateFrame().", |
|
|
|
"The generator has direct access to mono, stereo, pilot, RDS, audioMPX, and final composite values before they are flattened into output samples.", |
|
|
|
"Later measurement would lose stage separation and component identity." |
|
|
|
], |
|
|
|
"preferredPublicationModel": "Generator computes and stores the latest MeasurementSnapshot through an atomic pointer or equivalent low-contention accessor." |
|
|
|
}, |
|
|
|
"apiProposal": { |
|
|
|
"newEndpoint": "/measurements", |
|
|
|
"method": "GET", |
|
|
|
"pollingHz": { |
|
|
|
"min": 4, |
|
|
|
"recommended": 5, |
|
|
|
"max": 10 |
|
|
|
}, |
|
|
|
"whySeparateEndpoint": [ |
|
|
|
"/runtime should remain focused on operational/runtime telemetry.", |
|
|
|
"UI should not need to poll the full runtime payload at high frequency for metering.", |
|
|
|
"Measurements have different freshness and payload-shape requirements than runtime state and config." |
|
|
|
], |
|
|
|
"responseShapeExample": { |
|
|
|
"timestamp": "2026-04-13T05:30:00Z", |
|
|
|
"sampleRateHz": 228000, |
|
|
|
"chunkSamples": 11400, |
|
|
|
"chunkDurationMs": 50, |
|
|
|
"sequence": 12345, |
|
|
|
"stale": false, |
|
|
|
"noData": false, |
|
|
|
"flags": { |
|
|
|
"stereoEnabled": true, |
|
|
|
"stereoMode": "DSB", |
|
|
|
"rdsEnabled": true, |
|
|
|
"rds2Enabled": false, |
|
|
|
"bs412Enabled": true, |
|
|
|
"compositeClipperEnabled": true, |
|
|
|
"watermarkEnabled": false, |
|
|
|
"licenseInjectionActive": false |
|
|
|
}, |
|
|
|
"lrPreEncodePostWatermark": { |
|
|
|
"lRms": 0.41, |
|
|
|
"rRms": 0.39, |
|
|
|
"lPeakAbs": 0.98, |
|
|
|
"rPeakAbs": 0.96, |
|
|
|
"lrBalanceDb": 0.42, |
|
|
|
"lClipEvents": 12, |
|
|
|
"rClipEvents": 8 |
|
|
|
}, |
|
|
|
"audioMpxPreBs412": { |
|
|
|
"rms": 0.52, |
|
|
|
"peakAbs": 1.0, |
|
|
|
"monoRms": 0.34, |
|
|
|
"stereoRms": 0.18, |
|
|
|
"crestFactor": 1.92, |
|
|
|
"clipperLookaheadGain": 0.94, |
|
|
|
"clipperEnvelope": 1.03, |
|
|
|
"clipperOrProtectionActive": true |
|
|
|
}, |
|
|
|
"audioMpxPostBs412": { |
|
|
|
"rms": 0.46, |
|
|
|
"peakAbs": 0.91, |
|
|
|
"bs412GainApplied": 0.88, |
|
|
|
"bs412AttenuationDb": -1.11, |
|
|
|
"estimatedAudioPower": 0.21 |
|
|
|
}, |
|
|
|
"compositeFinalPreIq": { |
|
|
|
"rms": 0.49, |
|
|
|
"peakAbs": 1.08, |
|
|
|
"pilotRms": 0.064, |
|
|
|
"pilotPeakAbs": 0.09, |
|
|
|
"pilotInjectionEquivalentPercent": 9.0, |
|
|
|
"rdsRms": 0.028, |
|
|
|
"rdsPeakAbs": 0.04, |
|
|
|
"rdsInjectionEquivalentPercent": 4.0, |
|
|
|
"overNominalEvents": 91, |
|
|
|
"overHeadroomEvents": 0 |
|
|
|
} |
|
|
|
}, |
|
|
|
"emptyStateBehavior": { |
|
|
|
"whenTxInactive": "return either noData=true or the last snapshot with stale=true", |
|
|
|
"preferred": "explicit noData=true when no current generator output exists" |
|
|
|
} |
|
|
|
}, |
|
|
|
"uiIntegrationPlan": { |
|
|
|
"overview": { |
|
|
|
"changes": [ |
|
|
|
"Add or extend a metering section for measured L/R and final composite/MPX values.", |
|
|
|
"Show measured pilot and RDS values with explicit separation between raw measurements and operator-facing injection-equivalent display values.", |
|
|
|
"Optionally add compact L/R bar meters from lrPreEncodePostWatermark." |
|
|
|
], |
|
|
|
"avoid": [ |
|
|
|
"Do not replace queue/runtime health indicators with signal metering.", |
|
|
|
"Do not raise /runtime polling frequency just to animate meters.", |
|
|
|
"Do not label derived percent-style values as RMS values." |
|
|
|
] |
|
|
|
}, |
|
|
|
"flowTab": { |
|
|
|
"compositeMpxNode": [ |
|
|
|
"Keep config-derived metadata as fallback.", |
|
|
|
"Use measured values as the primary state/detail source where available.", |
|
|
|
"Drive state and detail from final composite measurements plus compliance indicators.", |
|
|
|
"Include flags-based context when watermark, RDS2, or license injection changes the final composite interpretation.", |
|
|
|
"Include stereoMode context when non-DSB stereo operation changes signal expectations." |
|
|
|
], |
|
|
|
"popover": [ |
|
|
|
"Add pre-BS.412 vs post-BS.412 comparison rows.", |
|
|
|
"Show final composite peak and measured pilot/RDS values.", |
|
|
|
"If desired, show both raw measured values and operator-friendly injection-equivalent display values side by side." |
|
|
|
] |
|
|
|
}, |
|
|
|
"diagnostics": { |
|
|
|
"future": [ |
|
|
|
"Add a dedicated engineering metering panel.", |
|
|
|
"Optionally add short client-side sparklines for composite RMS or BS.412 attenuation." |
|
|
|
] |
|
|
|
} |
|
|
|
}, |
|
|
|
"statusLogicProposal": { |
|
|
|
"compositeMpxNode": { |
|
|
|
"green": [ |
|
|
|
"Measurements available", |
|
|
|
"Final composite is within expected internal normalized envelope", |
|
|
|
"Pilot and RDS are near expected target windows", |
|
|
|
"No severe compliance attention" |
|
|
|
], |
|
|
|
"amber": [ |
|
|
|
"Measurement is stale", |
|
|
|
"Pilot or RDS materially deviates from expected target", |
|
|
|
"High composite peak or visible compliance shaping", |
|
|
|
"Restart-pending settings affect MPX semantics" |
|
|
|
], |
|
|
|
"red": [ |
|
|
|
"Measurement unavailable while TX is running", |
|
|
|
"Final composite exceeds internal critical envelope threshold repeatedly", |
|
|
|
"Pilot missing when stereo is enabled", |
|
|
|
"RDS missing when RDS is enabled and expected" |
|
|
|
], |
|
|
|
"gray": [ |
|
|
|
"TX idle or no active signal path" |
|
|
|
] |
|
|
|
} |
|
|
|
}, |
|
|
|
"performanceConstraints": { |
|
|
|
"must": [ |
|
|
|
"No contended locks in the per-sample inner loop.", |
|
|
|
"No heap allocation per sample.", |
|
|
|
"At most one snapshot publication per chunk.", |
|
|
|
"Endpoint read must be cheap and non-blocking." |
|
|
|
], |
|
|
|
"acceptableCosts": [ |
|
|
|
"A few additional per-sample accumulations such as abs, squares, and counters.", |
|
|
|
"A small per-chunk finalize step with sqrt and log calculations.", |
|
|
|
"Sampling lightweight clipper diagnostics when available." |
|
|
|
] |
|
|
|
}, |
|
|
|
"implementationPhases": [ |
|
|
|
{ |
|
|
|
"phase": 1, |
|
|
|
"name": "measurement model and generator instrumentation", |
|
|
|
"deliverables": [ |
|
|
|
"Define MeasurementSnapshot and stage structs.", |
|
|
|
"Add accumulator logic in GenerateFrame().", |
|
|
|
"Publish latest completed snapshot from generator." |
|
|
|
] |
|
|
|
}, |
|
|
|
{ |
|
|
|
"phase": 2, |
|
|
|
"name": "control-plane endpoint", |
|
|
|
"deliverables": [ |
|
|
|
"Add GET /measurements handler.", |
|
|
|
"Wire generator/engine access into control server.", |
|
|
|
"Document endpoint in docs/API.md.", |
|
|
|
"Add tests for active, stale, and noData cases." |
|
|
|
] |
|
|
|
}, |
|
|
|
{ |
|
|
|
"phase": 3, |
|
|
|
"name": "UI integration MVP", |
|
|
|
"deliverables": [ |
|
|
|
"Poll /measurements separately at 4-10 Hz.", |
|
|
|
"Add measured MPX display in Overview.", |
|
|
|
"Drive Flow Composite / MPX node from measurement data." |
|
|
|
] |
|
|
|
}, |
|
|
|
{ |
|
|
|
"phase": 4, |
|
|
|
"name": "diagnostics refinement", |
|
|
|
"deliverables": [ |
|
|
|
"Add engineering-oriented meter details.", |
|
|
|
"Add client-side trend memory for sparkline rendering.", |
|
|
|
"Refine thresholds and warning logic." |
|
|
|
] |
|
|
|
} |
|
|
|
], |
|
|
|
"nonGoals": [ |
|
|
|
"This is not a legal or certification-grade compliance measurement system.", |
|
|
|
"This does not replace external tools such as MpxTool or RF instrumentation.", |
|
|
|
"This does not implement full spectral analysis in the browser polling path.", |
|
|
|
"This does not yet define Prometheus/exporter metrics." |
|
|
|
], |
|
|
|
"openQuestions": [ |
|
|
|
{ |
|
|
|
"id": "Q1", |
|
|
|
"question": "Should pilot and RDS percentages be derived from configured injection targets, measured component values, or both?", |
|
|
|
"currentPreference": "Expose measured component values and derived operator-facing injection-equivalent display fields separately, with the RDS derivation documented mathematically before implementation." |
|
|
|
}, |
|
|
|
{ |
|
|
|
"id": "Q2", |
|
|
|
"question": "Should internal thresholds be stored as normalized signal values or broadcast-style deviation percentages?", |
|
|
|
"currentPreference": "Store normalized internal values and format into broadcast-friendly display values in UI." |
|
|
|
}, |
|
|
|
{ |
|
|
|
"id": "Q3", |
|
|
|
"question": "Should /measurements include short rolling history arrays?", |
|
|
|
"currentPreference": "No for MVP; keep endpoint single-snapshot and let UI build short client-side history." |
|
|
|
}, |
|
|
|
{ |
|
|
|
"id": "Q4", |
|
|
|
"question": "Should long-term ownership remain in generator or later move into engine stats?", |
|
|
|
"currentPreference": "Generator ownership first, engine exposure second." |
|
|
|
}, |
|
|
|
{ |
|
|
|
"id": "Q5", |
|
|
|
"question": "Should clipperOrProtectionActive exist as a first-class field or be derived from raw clipper diagnostics?", |
|
|
|
"currentPreference": "Expose raw clipper diagnostics such as lookahead gain and envelope; only expose the boolean if its derivation rule is explicitly documented." |
|
|
|
}, |
|
|
|
{ |
|
|
|
"id": "Q6", |
|
|
|
"question": "Should a second optional pre-watermark engineering tap be added later?", |
|
|
|
"currentPreference": "Not for MVP; only add it if there is a real diagnostic need." |
|
|
|
} |
|
|
|
], |
|
|
|
"reviewerFollowup": { |
|
|
|
"resolvedContradictions": [ |
|
|
|
"audioMpxPreBs412.shape now includes clipperLookaheadGain and clipperEnvelope so the data-model shape matches the documented preference for raw clipper diagnostics first.", |
|
|
|
"rdsInjectionEquivalentPercent is now explicitly marked as a mathematically pending field that must be omitted, null, or TODO-gated in MVP until its derivation is fixed." |
|
|
|
], |
|
|
|
"implementationGuardrails": [ |
|
|
|
"Do not let developers treat dataModelProposal.shape as permission to drop raw clipper diagnostics in favor of only a boolean.", |
|
|
|
"Do not implement rdsInjectionEquivalentPercent with an ad-hoc plausible-looking formula; either define it rigorously first or keep it unexposed in MVP." |
|
|
|
] |
|
|
|
}, |
|
|
|
"recommendedNextStep": { |
|
|
|
"action": "implement phase 1", |
|
|
|
"firstConcreteWorkItems": [ |
|
|
|
"Define MeasurementSnapshot and nested structs including flags and stereoMode.", |
|
|
|
"Add latest-measurement storage/accessor in generator.", |
|
|
|
"Instrument GenerateFrame() at lr_pre_encode_post_watermark, audio_mpx_pre_bs412, audio_mpx_post_bs412, and composite_final_pre_iq.", |
|
|
|
"Keep derived injection-equivalent fields and raw RMS fields explicitly separate from day one.", |
|
|
|
"Define rdsInjectionEquivalentPercent mathematically before exposing it.", |
|
|
|
"Treat licenseInjectionActive as chunk-local actual activity, not feature presence.", |
|
|
|
"Prefer raw clipper diagnostics over an underdefined active boolean unless the boolean rule is documented." |
|
|
|
] |
|
|
|
} |
|
|
|
} |