From 595ff3ba847e293dec9b4b4ac2094ebceedc0921 Mon Sep 17 00:00:00 2001 From: Jan Svabenik Date: Sun, 22 Mar 2026 17:11:31 +0100 Subject: [PATCH] fix: use stable signals for live audio streaming --- ROADMAP.md | 10 ++++++++++ cmd/sdrd/dsp_loop.go | 24 ++++++++++++++++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index 712d996..48cfcf8 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -128,6 +128,16 @@ Phase 5 should still avoid unnecessary premature hardware-specific complexity, b --- +## Future Architecture Note - Observation/Track Reconciliation + +A likely later improvement is an explicit reconciliation layer between: +- raw surveillance observations / candidates +- stable tracked signals / identities + +This is intentionally NOT the first fix for live-audio regressions. +For now, stable-ID-carrying signal sources should be used for stream/session-sensitive paths. +If needed later, a dedicated observation-to-track reconciliation layer can be introduced as its own architecture block. + ## Phase 6 - Adaptive QoS / Scheduler Intelligence ### Core idea diff --git a/cmd/sdrd/dsp_loop.go b/cmd/sdrd/dsp_loop.go index 52dc8f9..db8d05f 100644 --- a/cmd/sdrd/dsp_loop.go +++ b/cmd/sdrd/dsp_loop.go @@ -65,11 +65,26 @@ func runDSP(ctx context.Context, srcMgr *sourceManager, cfg config.Config, det * var displaySignals []detector.Signal if len(art.detailIQ) > 0 { displaySignals = state.refinement.Result.Signals - if rec != nil && len(displaySignals) > 0 && len(art.allIQ) > 0 { + stableSignals := rt.det.StableSignals() + streamSignals := displaySignals + if len(stableSignals) > 0 { + streamSignals = stableSignals + } + if rec != nil && len(art.allIQ) > 0 { + log.Printf("LIVEAUDIO DSP: detailIQ=%d displaySignals=%d streamSignals=%d stableSignals=%d allIQ=%d", len(art.detailIQ), len(displaySignals), len(streamSignals), len(stableSignals), len(art.allIQ)) aqCfg := extractionConfig{firTaps: rt.cfg.Recorder.ExtractionTaps, bwMult: rt.cfg.Recorder.ExtractionBwMult} - streamSnips, streamRates := extractForStreaming(extractMgr, art.allIQ, rt.cfg.SampleRate, rt.cfg.CenterHz, displaySignals, rt.streamPhaseState, rt.streamOverlap, aqCfg) - items := make([]recorder.StreamFeedItem, 0, len(displaySignals)) - for j, ds := range displaySignals { + streamSnips, streamRates := extractForStreaming(extractMgr, art.allIQ, rt.cfg.SampleRate, rt.cfg.CenterHz, streamSignals, rt.streamPhaseState, rt.streamOverlap, aqCfg) + items := make([]recorder.StreamFeedItem, 0, len(streamSignals)) + for j, ds := range streamSignals { + className := "" + if ds.Class != nil { + className = string(ds.Class.ModType) + } + snipLen := 0 + if j < len(streamSnips) { + snipLen = len(streamSnips[j]) + } + log.Printf("LIVEAUDIO DSP: streamSignal idx=%d id=%d center=%.3fMHz bw=%.0f class=%s snip=%d", j, ds.ID, ds.CenterHz/1e6, ds.BWHz, className, snipLen) if ds.ID == 0 || ds.Class == nil { continue } @@ -82,6 +97,7 @@ func runDSP(ctx context.Context, srcMgr *sourceManager, cfg config.Config, det * } items = append(items, recorder.StreamFeedItem{Signal: ds, Snippet: streamSnips[j], SnipRate: snipRate}) } + log.Printf("LIVEAUDIO DSP: feedItems=%d", len(items)) if len(items) > 0 { rec.FeedSnippets(items) }