package stereo import ( "github.com/jan/fm-rds-tx/internal/audio" "github.com/jan/fm-rds-tx/internal/dsp" ) // Components holds the individual MPX components produced by the stereo encoder. type Components struct { Mono float64 // L+R baseband Stereo float64 // L-R modulated onto 38 kHz DSB-SC Pilot float64 // 19 kHz pilot tone } // StereoEncoder generates stereo MPX primitives from stereo audio frames. // It internally maintains phase-coherent 19 kHz pilot and 38 kHz subcarrier // oscillators so that block-boundary phase continuity is guaranteed. type StereoEncoder struct { pilot dsp.PilotGenerator subcarrier dsp.Oscillator // 38 kHz, phase-locked to pilot (2× pilot) LevelStereo float64 } // NewStereoEncoder creates a StereoEncoder configured for the provided sample rate. func NewStereoEncoder(sampleRate float64) StereoEncoder { return StereoEncoder{ pilot: dsp.NewPilotGenerator(sampleRate, 0.1), subcarrier: dsp.Oscillator{Frequency: 38000, SampleRate: sampleRate}, LevelStereo: 1.0, } } // Encode converts a stereo frame into MPX components. // The 38 kHz subcarrier is generated from the internal oscillator, // maintaining continuous phase across calls. func (s *StereoEncoder) Encode(frame audio.Frame) Components { pilot := s.pilot.Sample() sub38 := s.subcarrier.Tick() return Components{ Mono: float64(frame.Mono()), Stereo: float64(frame.Difference()) * s.LevelStereo * sub38, Pilot: pilot, } } // Reset restarts the pilot and subcarrier generators. func (s *StereoEncoder) Reset() { s.pilot.Reset() s.subcarrier.Reset() }