From 47b46cb7ccc7e7a15deeda1a73d50f78d99e04cf Mon Sep 17 00:00:00 2001 From: Jan Svabenik Date: Thu, 2 Apr 2026 23:07:37 +0200 Subject: [PATCH] feat: report active offline source and fallback state --- docs/README.md | 2 ++ internal/offline/generator.go | 24 ++++++++++++++++-------- internal/offline/generator_test.go | 10 ++++++++++ 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/docs/README.md b/docs/README.md index 64fdda0..e49d12e 100644 --- a/docs/README.md +++ b/docs/README.md @@ -16,6 +16,7 @@ Current no-hardware sources: - generated stereo tones via config - 16-bit PCM WAV file input via `audio.inputPath` - basic sample-rate adaptation for WAV sources into the composite generation path +- transparent tone fallback if the configured WAV source cannot be loaded ### Tone configuration @@ -67,6 +68,7 @@ This is the current closest no-hardware stand-in for the future transmit pipelin `cmd/offline` generates a deterministic no-hardware IQ/composite-style file using the repository's output backend path. This is still an MVP path, but it is a more realistic offline artifact than the JSON-only dry-run. +The generator summary now reports whether the active source is tones, wav, or tone-fallback. ## Release posture diff --git a/internal/offline/generator.go b/internal/offline/generator.go index e9deecc..79d0734 100644 --- a/internal/offline/generator.go +++ b/internal/offline/generator.go @@ -20,6 +20,12 @@ type frameSource interface { NextFrame() audio.Frame } +type SourceInfo struct { + Kind string + SampleRate float64 + Detail string +} + type Generator struct { cfg cfgpkg.Config } @@ -28,13 +34,14 @@ func NewGenerator(cfg cfgpkg.Config) *Generator { return &Generator{cfg: cfg} } -func (g *Generator) sourceFor(sampleRate float64) frameSource { +func (g *Generator) sourceFor(sampleRate float64) (frameSource, SourceInfo) { if g.cfg.Audio.InputPath != "" { if src, err := audio.LoadWAVSource(g.cfg.Audio.InputPath); err == nil { - return audio.NewResampledSource(src, sampleRate) + return audio.NewResampledSource(src, sampleRate), SourceInfo{Kind: "wav", SampleRate: float64(src.SampleRate), Detail: g.cfg.Audio.InputPath} } + return audio.NewConfiguredToneSource(sampleRate, g.cfg.Audio.ToneLeftHz, g.cfg.Audio.ToneRightHz, g.cfg.Audio.ToneAmplitude), SourceInfo{Kind: "tone-fallback", SampleRate: sampleRate, Detail: g.cfg.Audio.InputPath} } - return audio.NewConfiguredToneSource(sampleRate, g.cfg.Audio.ToneLeftHz, g.cfg.Audio.ToneRightHz, g.cfg.Audio.ToneAmplitude) + return audio.NewConfiguredToneSource(sampleRate, g.cfg.Audio.ToneLeftHz, g.cfg.Audio.ToneRightHz, g.cfg.Audio.ToneAmplitude), SourceInfo{Kind: "tones", SampleRate: sampleRate, Detail: "generated"} } func (g *Generator) GenerateFrame(duration time.Duration) *output.CompositeFrame { @@ -68,7 +75,7 @@ func (g *Generator) GenerateFrame(duration time.Duration) *output.CompositeFrame }) rdsSamples := rdsEnc.Generate(samples) - source := g.sourceFor(sampleRate) + source, _ := g.sourceFor(sampleRate) for i := 0; i < samples; i++ { t := float64(i) / sampleRate @@ -121,9 +128,10 @@ func (g *Generator) WriteFile(path string, duration time.Duration) error { } func (g *Generator) Summary(duration time.Duration) string { - source := "tones" - if g.cfg.Audio.InputPath != "" { - source = g.cfg.Audio.InputPath + sampleRate := float64(g.cfg.FM.CompositeRateHz) + if sampleRate <= 0 { + sampleRate = 228000 } - return fmt.Sprintf("offline frame: freq=%.1fMHz sampleRate=%d duration=%s outputDrive=%.2f stereo=%t rds=%t source=%s", g.cfg.FM.FrequencyMHz, g.cfg.FM.CompositeRateHz, duration.String(), g.cfg.FM.OutputDrive, g.cfg.FM.StereoEnabled, g.cfg.RDS.Enabled, source) + _, info := g.sourceFor(sampleRate) + return fmt.Sprintf("offline frame: freq=%.1fMHz sampleRate=%d duration=%s outputDrive=%.2f stereo=%t rds=%t source=%s detail=%s", g.cfg.FM.FrequencyMHz, g.cfg.FM.CompositeRateHz, duration.String(), g.cfg.FM.OutputDrive, g.cfg.FM.StereoEnabled, g.cfg.RDS.Enabled, info.Kind, info.Detail) } diff --git a/internal/offline/generator_test.go b/internal/offline/generator_test.go index 5d37b7a..5309cae 100644 --- a/internal/offline/generator_test.go +++ b/internal/offline/generator_test.go @@ -47,3 +47,13 @@ func TestSummaryUsesToneFallback(t *testing.T) { t.Fatalf("unexpected summary: %s", summary) } } + +func TestSummaryUsesFallbackLabelOnBadWAV(t *testing.T) { + cfg := cfgpkg.Default() + cfg.Audio.InputPath = "missing.wav" + g := NewGenerator(cfg) + summary := g.Summary(10 * time.Millisecond) + if !strings.Contains(summary, "source=tone-fallback") { + t.Fatalf("unexpected summary: %s", summary) + } +}