package dsp import "math" // FMModulator converts a composite baseband signal into FM-modulated IQ samples. // The modulated output represents the instantaneous frequency deviation around // a zero-IF carrier (baseband IQ). For a real SDR transmitter the SDR hardware // then upconverts this to the desired center frequency. type FMModulator struct { // MaxDeviation is the peak frequency deviation in Hz (±75 kHz for FM broadcast). MaxDeviation float64 SampleRate float64 phase float64 // accumulated carrier phase in radians } // NewFMModulator creates a modulator with broadcast FM defaults. func NewFMModulator(sampleRate float64) *FMModulator { return &FMModulator{ MaxDeviation: 75000, // ±75 kHz SampleRate: sampleRate, } } // Modulate converts a single composite sample (normalized to [-1,+1] representing // full deviation) into an IQ pair. func (m *FMModulator) Modulate(composite float64) (i, q float64) { // Instantaneous frequency offset = composite * maxDeviation // Phase increment per sample = 2π * freq_offset / sampleRate freqOffset := composite * m.MaxDeviation m.phase += 2 * math.Pi * freqOffset / m.SampleRate // Keep phase bounded to avoid float64 precision loss over long runs if m.phase > math.Pi || m.phase < -math.Pi { m.phase -= 2 * math.Pi * math.Floor((m.phase+math.Pi)/(2*math.Pi)) } i = math.Cos(m.phase) q = math.Sin(m.phase) return i, q } // Reset clears the modulator phase. func (m *FMModulator) Reset() { m.phase = 0 }