Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

52 Zeilen
1.6KB

  1. package stereo
  2. import (
  3. "github.com/jan/fm-rds-tx/internal/audio"
  4. "github.com/jan/fm-rds-tx/internal/dsp"
  5. )
  6. // Components holds the individual MPX components produced by the stereo encoder.
  7. type Components struct {
  8. Mono float64 // L+R baseband
  9. Stereo float64 // L-R modulated onto 38 kHz DSB-SC
  10. Pilot float64 // 19 kHz pilot tone
  11. }
  12. // StereoEncoder generates stereo MPX primitives from stereo audio frames.
  13. // It internally maintains phase-coherent 19 kHz pilot and 38 kHz subcarrier
  14. // oscillators so that block-boundary phase continuity is guaranteed.
  15. type StereoEncoder struct {
  16. pilot dsp.PilotGenerator
  17. subcarrier dsp.Oscillator // 38 kHz, phase-locked to pilot (2× pilot)
  18. LevelStereo float64
  19. }
  20. // NewStereoEncoder creates a StereoEncoder configured for the provided sample rate.
  21. func NewStereoEncoder(sampleRate float64) StereoEncoder {
  22. return StereoEncoder{
  23. pilot: dsp.NewPilotGenerator(sampleRate, 0.1),
  24. subcarrier: dsp.Oscillator{Frequency: 38000, SampleRate: sampleRate},
  25. LevelStereo: 1.0,
  26. }
  27. }
  28. // Encode converts a stereo frame into MPX components.
  29. // The 38 kHz subcarrier is generated from the internal oscillator,
  30. // maintaining continuous phase across calls.
  31. func (s *StereoEncoder) Encode(frame audio.Frame) Components {
  32. pilot := s.pilot.Sample()
  33. sub38 := s.subcarrier.Tick()
  34. return Components{
  35. Mono: float64(frame.Mono()),
  36. Stereo: float64(frame.Difference()) * s.LevelStereo * sub38,
  37. Pilot: pilot,
  38. }
  39. }
  40. // Reset restarts the pilot and subcarrier generators.
  41. func (s *StereoEncoder) Reset() {
  42. s.pilot.Reset()
  43. s.subcarrier.Reset()
  44. }