Преглед на файлове

feat: make no-hardware tone source configurable

tags/v0.3.0-pre
Jan Svabenik преди 1 месец
родител
ревизия
b8884310c4
променени са 6 файла, в които са добавени 37 реда и са изтрити 10 реда
  1. +7
    -0
      docs/README.md
  2. +4
    -1
      docs/config.sample.json
  3. +8
    -0
      internal/audio/source.go
  4. +13
    -4
      internal/config/config.go
  5. +3
    -3
      internal/config/config_test.go
  6. +2
    -2
      internal/offline/generator.go

+ 7
- 0
docs/README.md Целия файл

@@ -10,6 +10,13 @@
- `go run ./cmd/fmrtx --simulate-tx --simulate-output build/sim/simulated-soapy.iqf32 --simulate-duration 250ms`
- `go run ./cmd/offline -duration 500ms -output build/offline/composite.iqf32`

### Tone configuration

The current no-hardware source can be parameterized via config:
- `audio.toneLeftHz`
- `audio.toneRightHz`
- `audio.toneAmplitude`

### Internal DSP module
- `cd internal`
- `go test ./...`


+ 4
- 1
docs/config.sample.json Целия файл

@@ -2,7 +2,10 @@
"audio": {
"inputPath": "",
"sampleRate": 48000,
"gain": 1.0
"gain": 1.0,
"toneLeftHz": 1000,
"toneRightHz": 1600,
"toneAmplitude": 0.4
},
"rds": {
"enabled": true,


+ 8
- 0
internal/audio/source.go Целия файл

@@ -22,6 +22,14 @@ func NewToneSource(sampleRate float64) *ToneSource {
}
}

func NewConfiguredToneSource(sampleRate, leftHz, rightHz, amplitude float64) *ToneSource {
src := NewToneSource(sampleRate)
src.LeftFreq = leftHz
src.RightFreq = rightHz
src.Amplitude = amplitude
return src
}

func (s *ToneSource) NextFrame() Frame {
left := s.Amplitude * math.Sin(2*math.Pi*s.LeftPhase)
right := s.Amplitude * math.Sin(2*math.Pi*s.RightPhase)


+ 13
- 4
internal/config/config.go Целия файл

@@ -15,9 +15,12 @@ type Config struct {
}

type AudioConfig struct {
InputPath string `json:"inputPath"`
SampleRate int `json:"sampleRate"`
Gain float64 `json:"gain"`
InputPath string `json:"inputPath"`
SampleRate int `json:"sampleRate"`
Gain float64 `json:"gain"`
ToneLeftHz float64 `json:"toneLeftHz"`
ToneRightHz float64 `json:"toneRightHz"`
ToneAmplitude float64 `json:"toneAmplitude"`
}

type RDSConfig struct {
@@ -50,7 +53,7 @@ type ControlConfig struct {

func Default() Config {
return Config{
Audio: AudioConfig{SampleRate: 48000, Gain: 1.0},
Audio: AudioConfig{SampleRate: 48000, Gain: 1.0, ToneLeftHz: 1000, ToneRightHz: 1600, ToneAmplitude: 0.4},
RDS: RDSConfig{Enabled: true, PI: "1234", PS: "FMRTX", RadioText: "fm-rds-tx", PTY: 0},
FM: FMConfig{FrequencyMHz: 100.0, StereoEnabled: true, PilotLevel: 0.1, RDSInjection: 0.03, OutputDrive: 0.5, CompositeRateHz: 228000},
Backend: BackendConfig{Kind: "file", OutputPath: "build/out/composite.f32"},
@@ -80,6 +83,12 @@ func (c Config) Validate() error {
if c.Audio.Gain < 0 || c.Audio.Gain > 4 {
return fmt.Errorf("audio.gain out of range")
}
if c.Audio.ToneLeftHz <= 0 || c.Audio.ToneRightHz <= 0 {
return fmt.Errorf("audio tone frequencies must be positive")
}
if c.Audio.ToneAmplitude < 0 || c.Audio.ToneAmplitude > 1 {
return fmt.Errorf("audio.toneAmplitude out of range")
}
if c.FM.FrequencyMHz < 65 || c.FM.FrequencyMHz > 110 {
return fmt.Errorf("fm.frequencyMHz out of range")
}


+ 3
- 3
internal/config/config_test.go Целия файл

@@ -16,15 +16,15 @@ func TestDefaultValidate(t *testing.T) {
func TestLoadAndValidate(t *testing.T) {
dir := t.TempDir()
path := filepath.Join(dir, "config.json")
if err := os.WriteFile(path, []byte(`{"fm":{"frequencyMHz":99.9},"backend":{"kind":"file","outputPath":"out.f32"},"control":{"listenAddress":"127.0.0.1:8088"}}`), 0o644); err != nil {
if err := os.WriteFile(path, []byte(`{"audio":{"toneLeftHz":900,"toneRightHz":1700,"toneAmplitude":0.3},"fm":{"frequencyMHz":99.9},"backend":{"kind":"file","outputPath":"out.f32"},"control":{"listenAddress":"127.0.0.1:8088"}}`), 0o644); err != nil {
t.Fatalf("write config: %v", err)
}
cfg, err := Load(path)
if err != nil {
t.Fatalf("load config: %v", err)
}
if cfg.FM.FrequencyMHz != 99.9 {
t.Fatalf("unexpected frequency: %v", cfg.FM.FrequencyMHz)
if cfg.Audio.ToneLeftHz != 900 {
t.Fatalf("unexpected left tone: %v", cfg.Audio.ToneLeftHz)
}
}



+ 2
- 2
internal/offline/generator.go Целия файл

@@ -55,7 +55,7 @@ func (g *Generator) GenerateFrame(duration time.Duration) *output.CompositeFrame
})
rdsSamples := rdsEnc.Generate(samples)

source := audio.NewToneSource(sampleRate)
source := audio.NewConfiguredToneSource(sampleRate, g.cfg.Audio.ToneLeftHz, g.cfg.Audio.ToneRightHz, g.cfg.Audio.ToneAmplitude)

for i := 0; i < samples; i++ {
t := float64(i) / sampleRate
@@ -108,5 +108,5 @@ func (g *Generator) WriteFile(path string, duration time.Duration) error {
}

func (g *Generator) Summary(duration time.Duration) string {
return fmt.Sprintf("offline frame: freq=%.1fMHz sampleRate=%d duration=%s outputDrive=%.2f stereo=%t rds=%t", g.cfg.FM.FrequencyMHz, g.cfg.FM.CompositeRateHz, duration.String(), g.cfg.FM.OutputDrive, g.cfg.FM.StereoEnabled, g.cfg.RDS.Enabled)
return fmt.Sprintf("offline frame: freq=%.1fMHz sampleRate=%d duration=%s outputDrive=%.2f stereo=%t rds=%t toneL=%.1f toneR=%.1f", g.cfg.FM.FrequencyMHz, g.cfg.FM.CompositeRateHz, duration.String(), g.cfg.FM.OutputDrive, g.cfg.FM.StereoEnabled, g.cfg.RDS.Enabled, g.cfg.Audio.ToneLeftHz, g.cfg.Audio.ToneRightHz)
}

Loading…
Отказ
Запис