|
- // cmd/wmdump — generates a composite WAV with watermark for offline verification.
- //
- // Usage:
- //
- // wmdump --key FMRTX-XXX --config config.json --output composite.wav --duration 60s
- // wmdecode composite.wav FMRTX-XXX
- //
- // If wmdecode succeeds on the composite.wav, the watermark code is working.
- // If it fails on an air recording, the issue is in the PlutoSDR/air/receiver path.
- package main
-
- import (
- "encoding/binary"
- "flag"
- "fmt"
- "log"
- "math"
- "os"
- "time"
-
- cfgpkg "github.com/jan/fm-rds-tx/internal/config"
- "github.com/jan/fm-rds-tx/internal/license"
- offpkg "github.com/jan/fm-rds-tx/internal/offline"
- "github.com/jan/fm-rds-tx/internal/watermark"
- )
-
- func main() {
- configPath := flag.String("config", "", "path to JSON config (uses same as fmrtx)")
- key := flag.String("key", "free", "license key to embed")
- output := flag.String("output", "wmdump.wav", "output WAV path")
- duration := flag.Duration("duration", 60*time.Second, "generation duration")
- rate := flag.Int("rate", 192000, "output WAV sample rate (resampled from composite)")
- flag.Parse()
-
- cfg, err := cfgpkg.Load(*configPath)
- if err != nil {
- log.Fatalf("load config: %v", err)
- }
- // Match real TX: split-rate mode means FMModulationEnabled=false
- cfg.FM.FMModulationEnabled = false
-
- gen := offpkg.NewGenerator(cfg)
- licState := license.NewState(*key)
- gen.SetLicense(licState, *key)
-
- fmt.Printf("Generating composite with watermark...\n")
- fmt.Printf(" Key: %s (licensed=%v)\n", *key, licState.Licensed())
- fmt.Printf(" Config: %s\n", *configPath)
- fmt.Printf(" Duration: %s\n", *duration)
- fmt.Printf(" Composite: %d Hz\n", cfg.FM.CompositeRateHz)
- fmt.Printf(" Output: %s @ %d Hz\n", *output, *rate)
- fmt.Printf(" ChipRate: %d Hz (PN bandwidth 0-%d Hz)\n", watermark.ChipRate, watermark.ChipRate/2)
- fmt.Println()
-
- frame := gen.GenerateFrame(*duration)
- if frame == nil {
- log.Fatal("GenerateFrame returned nil")
- }
- fmt.Printf("Generated %d composite samples @ %.0f Hz\n", len(frame.Samples), frame.SampleRateHz)
-
- // Extract composite (I channel in non-FM mode)
- compRate := frame.SampleRateHz
- nComp := len(frame.Samples)
-
- // RMS check
- var rmsAcc float64
- for _, s := range frame.Samples {
- rmsAcc += float64(s.I) * float64(s.I)
- }
- compRMS := math.Sqrt(rmsAcc / float64(nComp))
- fmt.Printf("Composite RMS: %.1f dBFS\n", 20*math.Log10(compRMS+1e-12))
-
- // Resample composite to output rate (linear interpolation)
- outRate := float64(*rate)
- ratio := outRate / compRate
- nOut := int(float64(nComp) * ratio)
- samples := make([]float64, nOut)
- for i := range samples {
- pos := float64(i) / ratio
- idx := int(pos)
- frac := pos - float64(idx)
- if idx+1 < nComp {
- samples[i] = float64(frame.Samples[idx].I)*(1-frac) + float64(frame.Samples[idx+1].I)*frac
- } else if idx < nComp {
- samples[i] = float64(frame.Samples[idx].I)
- }
- }
-
- // RMS after resample
- var rms2 float64
- for _, s := range samples {
- rms2 += s * s
- }
- outRMS := math.Sqrt(rms2 / float64(nOut))
- fmt.Printf("Output RMS: %.1f dBFS (%d samples @ %.0f Hz)\n", 20*math.Log10(outRMS+1e-12), nOut, outRate)
-
- // Write WAV
- if err := writeWAV(*output, samples, *rate); err != nil {
- log.Fatalf("write WAV: %v", err)
- }
- fmt.Printf("\nWritten: %s\n", *output)
- fmt.Printf("\nDecode with:\n")
- fmt.Printf(" .\\wmdecode.exe %s %q\n", *output, *key)
- }
-
- func writeWAV(path string, samples []float64, rate int) error {
- f, err := os.Create(path)
- if err != nil {
- return err
- }
- defer f.Close()
- le := binary.LittleEndian
- dataSz := uint32(len(samples) * 2)
- f.Write([]byte("RIFF"))
- binary.Write(f, le, 36+dataSz)
- f.Write([]byte("WAVE"))
- f.Write([]byte("fmt "))
- binary.Write(f, le, uint32(16))
- binary.Write(f, le, uint16(1))
- binary.Write(f, le, uint16(1))
- binary.Write(f, le, uint32(rate))
- binary.Write(f, le, uint32(rate*2))
- binary.Write(f, le, uint16(2))
- binary.Write(f, le, uint16(16))
- f.Write([]byte("data"))
- binary.Write(f, le, dataSz)
- for _, s := range samples {
- v := s * 32767.0
- if v > 32767 {
- v = 32767
- }
- if v < -32768 {
- v = -32768
- }
- binary.Write(f, le, int16(v))
- }
- return nil
- }
|