diff --git a/docs/README.md b/docs/README.md index 807b9f5..64fdda0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -15,6 +15,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 ### Tone configuration diff --git a/internal/app/sim.go b/internal/app/sim.go index 6950fe0..0b413e5 100644 --- a/internal/app/sim.go +++ b/internal/app/sim.go @@ -52,5 +52,12 @@ func RunSimulatedTransmit(cfg cfgpkg.Config, outPath string, duration time.Durat if err := backend.Flush(context.Background()); err != nil { return "", err } - return fmt.Sprintf("simulated transmit: backend=%s output=%s duration=%s", backend.Info().Name, outPath, duration), nil + return fmt.Sprintf("simulated transmit: backend=%s output=%s duration=%s input=%s", backend.Info().Name, outPath, duration, inputLabel(cfg)), nil +} + +func inputLabel(cfg cfgpkg.Config) string { + if cfg.Audio.InputPath != "" { + return cfg.Audio.InputPath + } + return "tones" } diff --git a/internal/audio/resample.go b/internal/audio/resample.go new file mode 100644 index 0000000..7bc4b91 --- /dev/null +++ b/internal/audio/resample.go @@ -0,0 +1,28 @@ +package audio + +type ResampledSource struct { + src *WAVSource + ratio float64 + position float64 +} + +func NewResampledSource(src *WAVSource, targetSampleRate float64) *ResampledSource { + ratio := 1.0 + if src != nil && src.SampleRate > 0 && targetSampleRate > 0 { + ratio = float64(src.SampleRate) / targetSampleRate + } + return &ResampledSource{src: src, ratio: ratio} +} + +func (s *ResampledSource) NextFrame() Frame { + if s.src == nil || len(s.src.frames) == 0 { + return NewFrame(0, 0) + } + idx := int(s.position) % len(s.src.frames) + frame := s.src.frames[idx] + s.position += s.ratio + for s.position >= float64(len(s.src.frames)) { + s.position -= float64(len(s.src.frames)) + } + return frame +} diff --git a/internal/audio/resample_test.go b/internal/audio/resample_test.go new file mode 100644 index 0000000..c9798e1 --- /dev/null +++ b/internal/audio/resample_test.go @@ -0,0 +1,13 @@ +package audio + +import "testing" + +func TestResampledSource(t *testing.T) { + src := &WAVSource{frames: []Frame{NewFrame(0.1, 0.1), NewFrame(0.2, 0.2)}, SampleRate: 48000} + rs := NewResampledSource(src, 96000) + a := rs.NextFrame() + b := rs.NextFrame() + if a == (Frame{}) || b == (Frame{}) { + t.Fatal("expected frames") + } +} diff --git a/internal/offline/generator.go b/internal/offline/generator.go index 2498043..e9deecc 100644 --- a/internal/offline/generator.go +++ b/internal/offline/generator.go @@ -31,7 +31,7 @@ func NewGenerator(cfg cfgpkg.Config) *Generator { func (g *Generator) sourceFor(sampleRate float64) frameSource { if g.cfg.Audio.InputPath != "" { if src, err := audio.LoadWAVSource(g.cfg.Audio.InputPath); err == nil { - return src + return audio.NewResampledSource(src, sampleRate) } } return audio.NewConfiguredToneSource(sampleRate, g.cfg.Audio.ToneLeftHz, g.cfg.Audio.ToneRightHz, g.cfg.Audio.ToneAmplitude)