Explorar el Código

feat: report active offline source and fallback state

tags/v0.4.0-pre
Jan Svabenik hace 1 mes
padre
commit
47b46cb7cc
Se han modificado 3 ficheros con 28 adiciones y 8 borrados
  1. +2
    -0
      docs/README.md
  2. +16
    -8
      internal/offline/generator.go
  3. +10
    -0
      internal/offline/generator_test.go

+ 2
- 0
docs/README.md Ver fichero

@@ -16,6 +16,7 @@ Current no-hardware sources:
- generated stereo tones via config - generated stereo tones via config
- 16-bit PCM WAV file input via `audio.inputPath` - 16-bit PCM WAV file input via `audio.inputPath`
- basic sample-rate adaptation for WAV sources into the composite generation path - 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 ### 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. `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. 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 ## Release posture




+ 16
- 8
internal/offline/generator.go Ver fichero

@@ -20,6 +20,12 @@ type frameSource interface {
NextFrame() audio.Frame NextFrame() audio.Frame
} }


type SourceInfo struct {
Kind string
SampleRate float64
Detail string
}

type Generator struct { type Generator struct {
cfg cfgpkg.Config cfg cfgpkg.Config
} }
@@ -28,13 +34,14 @@ func NewGenerator(cfg cfgpkg.Config) *Generator {
return &Generator{cfg: cfg} 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 g.cfg.Audio.InputPath != "" {
if src, err := audio.LoadWAVSource(g.cfg.Audio.InputPath); err == nil { 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 { 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) rdsSamples := rdsEnc.Generate(samples)


source := g.sourceFor(sampleRate)
source, _ := g.sourceFor(sampleRate)


for i := 0; i < samples; i++ { for i := 0; i < samples; i++ {
t := float64(i) / sampleRate 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 { 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)
} }

+ 10
- 0
internal/offline/generator_test.go Ver fichero

@@ -47,3 +47,13 @@ func TestSummaryUsesToneFallback(t *testing.T) {
t.Fatalf("unexpected summary: %s", summary) 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)
}
}

Cargando…
Cancelar
Guardar