Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

106 lines
3.1KB

  1. package config
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "os"
  6. )
  7. type Config struct {
  8. Audio AudioConfig `json:"audio"`
  9. RDS RDSConfig `json:"rds"`
  10. FM FMConfig `json:"fm"`
  11. Backend BackendConfig `json:"backend"`
  12. Control ControlConfig `json:"control"`
  13. }
  14. type AudioConfig struct {
  15. InputPath string `json:"inputPath"`
  16. SampleRate int `json:"sampleRate"`
  17. Gain float64 `json:"gain"`
  18. }
  19. type RDSConfig struct {
  20. Enabled bool `json:"enabled"`
  21. PI string `json:"pi"`
  22. PS string `json:"ps"`
  23. RadioText string `json:"radioText"`
  24. PTY int `json:"pty"`
  25. }
  26. type FMConfig struct {
  27. FrequencyMHz float64 `json:"frequencyMHz"`
  28. StereoEnabled bool `json:"stereoEnabled"`
  29. PilotLevel float64 `json:"pilotLevel"`
  30. RDSInjection float64 `json:"rdsInjection"`
  31. PreEmphasisUS bool `json:"preEmphasisUS"`
  32. OutputDrive float64 `json:"outputDrive"`
  33. CompositeRateHz int `json:"compositeRateHz"`
  34. }
  35. type BackendConfig struct {
  36. Kind string `json:"kind"`
  37. Device string `json:"device"`
  38. OutputPath string `json:"outputPath"`
  39. }
  40. type ControlConfig struct {
  41. ListenAddress string `json:"listenAddress"`
  42. }
  43. func Default() Config {
  44. return Config{
  45. Audio: AudioConfig{SampleRate: 48000, Gain: 1.0},
  46. RDS: RDSConfig{Enabled: true, PI: "1234", PS: "FMRTX", RadioText: "fm-rds-tx", PTY: 0},
  47. FM: FMConfig{FrequencyMHz: 100.0, StereoEnabled: true, PilotLevel: 0.1, RDSInjection: 0.03, OutputDrive: 0.5, CompositeRateHz: 228000},
  48. Backend: BackendConfig{Kind: "file", OutputPath: "build/out/composite.f32"},
  49. Control: ControlConfig{ListenAddress: "127.0.0.1:8088"},
  50. }
  51. }
  52. func Load(path string) (Config, error) {
  53. cfg := Default()
  54. if path == "" {
  55. return cfg, cfg.Validate()
  56. }
  57. data, err := os.ReadFile(path)
  58. if err != nil {
  59. return Config{}, err
  60. }
  61. if err := json.Unmarshal(data, &cfg); err != nil {
  62. return Config{}, err
  63. }
  64. return cfg, cfg.Validate()
  65. }
  66. func (c Config) Validate() error {
  67. if c.Audio.SampleRate < 8000 || c.Audio.SampleRate > 384000 {
  68. return fmt.Errorf("audio.sampleRate out of range")
  69. }
  70. if c.Audio.Gain < 0 || c.Audio.Gain > 4 {
  71. return fmt.Errorf("audio.gain out of range")
  72. }
  73. if c.FM.FrequencyMHz < 65 || c.FM.FrequencyMHz > 110 {
  74. return fmt.Errorf("fm.frequencyMHz out of range")
  75. }
  76. if c.FM.PilotLevel < 0 || c.FM.PilotLevel > 0.2 {
  77. return fmt.Errorf("fm.pilotLevel out of range")
  78. }
  79. if c.FM.RDSInjection < 0 || c.FM.RDSInjection > 0.1 {
  80. return fmt.Errorf("fm.rdsInjection out of range")
  81. }
  82. if c.FM.OutputDrive < 0 || c.FM.OutputDrive > 1 {
  83. return fmt.Errorf("fm.outputDrive out of range")
  84. }
  85. if c.FM.CompositeRateHz < 96000 || c.FM.CompositeRateHz > 1520000 {
  86. return fmt.Errorf("fm.compositeRateHz out of range")
  87. }
  88. if c.Backend.Kind == "" {
  89. return fmt.Errorf("backend.kind is required")
  90. }
  91. if c.Control.ListenAddress == "" {
  92. return fmt.Errorf("control.listenAddress is required")
  93. }
  94. return nil
  95. }