diff --git a/internal/offline/generator.go b/internal/offline/generator.go index 4af764b..ff38a4c 100644 --- a/internal/offline/generator.go +++ b/internal/offline/generator.go @@ -5,6 +5,8 @@ import ( "encoding/binary" "fmt" "path/filepath" + "strconv" + "strings" "time" "github.com/jan/fm-rds-tx/internal/audio" @@ -44,6 +46,20 @@ func (g *Generator) sourceFor(sampleRate float64) (frameSource, SourceInfo) { return audio.NewConfiguredToneSource(sampleRate, g.cfg.Audio.ToneLeftHz, g.cfg.Audio.ToneRightHz, g.cfg.Audio.ToneAmplitude), SourceInfo{Kind: "tones", SampleRate: sampleRate, Detail: "generated"} } +func parsePI(pi string) uint16 { + trimmed := strings.TrimSpace(pi) + if trimmed == "" { + return 0x1234 + } + trimmed = strings.TrimPrefix(trimmed, "0x") + trimmed = strings.TrimPrefix(trimmed, "0X") + v, err := strconv.ParseUint(trimmed, 16, 16) + if err != nil { + return 0x1234 + } + return uint16(v) +} + func (g *Generator) GenerateFrame(duration time.Duration) *output.CompositeFrame { sampleRate := float64(g.cfg.FM.CompositeRateHz) if sampleRate <= 0 { @@ -80,7 +96,7 @@ func (g *Generator) GenerateFrame(duration time.Duration) *output.CompositeFrame // RDS encoder (standards-grade group framing + CRC + diff encoding) rdsEnc, _ := rds.NewEncoder(rds.RDSConfig{ - PI: 0x1234, + PI: parsePI(g.cfg.RDS.PI), PS: g.cfg.RDS.PS, RT: g.cfg.RDS.RadioText, PTY: uint8(g.cfg.RDS.PTY), diff --git a/internal/offline/generator_test.go b/internal/offline/generator_test.go index 46bbcc3..4967736 100644 --- a/internal/offline/generator_test.go +++ b/internal/offline/generator_test.go @@ -126,3 +126,31 @@ func TestLimiterPreventsClipping(t *testing.T) { } } } + +func TestParsePI(t *testing.T) { + tests := []struct { + name string + in string + want uint16 + }{ + {name: "plain hex", in: "1234", want: 0x1234}, + {name: "0x prefix", in: "0xBEEF", want: 0xBEEF}, + {name: "uppercase prefix", in: "0XCAFE", want: 0xCAFE}, + {name: "whitespace", in: " 0x2345 ", want: 0x2345}, + {name: "empty fallback", in: "", want: 0x1234}, + {name: "invalid fallback", in: "nope", want: 0x1234}, + } + for _, tt := range tests { + if got := parsePI(tt.in); got != tt.want { + t.Fatalf("%s: got 0x%04X want 0x%04X", tt.name, got, tt.want) + } + } +} + +func TestGeneratorUsesConfiguredPI(t *testing.T) { + cfg := cfgpkg.Default() + cfg.RDS.PI = "BEEF" + if got := parsePI(cfg.RDS.PI); got != 0xBEEF { + t.Fatalf("configured PI was not parsed as expected: got 0x%04X", got) + } +} diff --git a/internal/rds/encoder_test.go b/internal/rds/encoder_test.go index c1fcfb7..478c94a 100644 --- a/internal/rds/encoder_test.go +++ b/internal/rds/encoder_test.go @@ -53,6 +53,17 @@ func TestBuildGroup2ABlockCount(t *testing.T) { } } +func TestBuildGroupUsesConfiguredPI(t *testing.T) { + g0 := buildGroup0A(0xBEEF, 0, false, false, 0, "TESTFM") + if g0[0] != 0xBEEF { + t.Fatalf("group0A block A not configured PI: %x", g0[0]) + } + g2 := buildGroup2A(0xCAFE, 0, false, false, 0, "Hello World") + if g2[0] != 0xCAFE { + t.Fatalf("group2A block A not configured PI: %x", g2[0]) + } +} + func TestEncoderGenerate(t *testing.T) { cfg := DefaultConfig() cfg.SampleRate = 228000