|
- package recorder
-
- import (
- "math"
-
- "sdr-wideband-suite/internal/demod"
- "sdr-wideband-suite/internal/dsp"
- )
-
- func demodWFMStereoBatchAudio(iq []complex64, sampleRate int, offset float64, bw float64, deemphasisUs float64) ([]float32, int) {
- if len(iq) == 0 || sampleRate <= 0 {
- return nil, 0
- }
-
- shifted := dsp.FreqShift(iq, sampleRate, offset)
- cutoff := bw / 2
- if cutoff < 200 {
- cutoff = 200
- }
- d := demod.Get("WFM")
- if d == nil {
- return nil, 0
- }
- if cutoff < 75000 && d.OutputSampleRate() >= 192000 {
- cutoff = 75000
- }
- taps := dsp.LowpassFIR(cutoff, sampleRate, 101)
- filtered := dsp.ApplyFIR(shifted, taps)
-
- demodRate := d.OutputSampleRate()
- decim1 := int(math.Round(float64(sampleRate) / float64(demodRate)))
- if decim1 < 1 {
- decim1 = 1
- }
- dec := dsp.Decimate(filtered, decim1)
- actualDemodRate := sampleRate / decim1
- mono := d.Demod(dec, actualDemodRate)
- if len(mono) == 0 {
- return nil, 0
- }
-
- stereo, locked := wfmStereoDecodeBatch(mono, actualDemodRate)
- if !locked || len(stereo) == 0 {
- stereo = make([]float32, len(mono)*2)
- for i, s := range mono {
- stereo[i*2] = s
- stereo[i*2+1] = s
- }
- }
-
- outRate := actualDemodRate
- if actualDemodRate != streamAudioRate {
- resampler := dsp.NewStereoResampler(actualDemodRate, streamAudioRate, resamplerTaps)
- stereo = resampler.Process(stereo)
- outRate = streamAudioRate
- }
-
- if deemphasisUs > 0 && outRate > 0 {
- tau := deemphasisUs * 1e-6
- alpha := math.Exp(-1.0 / (float64(outRate) * tau))
- var yL, yR float64
- for i := 0; i+1 < len(stereo); i += 2 {
- yL = alpha*yL + (1-alpha)*float64(stereo[i])
- stereo[i] = float32(yL)
- yR = alpha*yR + (1-alpha)*float64(stereo[i+1])
- stereo[i+1] = float32(yR)
- }
- }
-
- for i := range stereo {
- stereo[i] *= 0.35
- }
-
- return stereo, outRate
- }
-
- func wfmStereoDecodeBatch(mono []float32, sampleRate int) ([]float32, bool) {
- if len(mono) == 0 || sampleRate <= 0 {
- return nil, false
- }
-
- lp := dsp.LowpassFIR(15000, sampleRate, 101)
- lpr := dsp.ApplyFIRReal(mono, lp)
-
- hi := dsp.ApplyFIRReal(mono, dsp.LowpassFIR(53000, sampleRate, 101))
- lo := dsp.ApplyFIRReal(mono, dsp.LowpassFIR(23000, sampleRate, 101))
- bpf := make([]float32, len(mono))
- for i := range bpf {
- bpf[i] = hi[i] - lo[i]
- }
-
- pilotHi := dsp.ApplyFIRReal(mono, dsp.LowpassFIR(21000, sampleRate, 101))
- pilotLo := dsp.ApplyFIRReal(mono, dsp.LowpassFIR(17000, sampleRate, 101))
- pilot := make([]float32, len(mono))
- for i := range pilot {
- pilot[i] = pilotHi[i] - pilotLo[i]
- }
-
- phase := 0.0
- freq := 2 * math.Pi * 19000 / float64(sampleRate)
- alpha, beta := pllCoefficientsBatch(50, 0.707, sampleRate)
- lpAlpha := 1 - math.Exp(-2*math.Pi*200/float64(sampleRate))
- iState := 0.0
- qState := 0.0
- minFreq := 2 * math.Pi * 17000 / float64(sampleRate)
- maxFreq := 2 * math.Pi * 21000 / float64(sampleRate)
- var pilotPower float64
- var totalPower float64
- var errSum float64
-
- lr := make([]float32, len(mono))
- for i := 0; i < len(mono); i++ {
- p := float64(pilot[i])
- sinP, cosP := math.Sincos(phase)
- iMix := p * cosP
- qMix := p * -sinP
- iState += lpAlpha * (iMix - iState)
- qState += lpAlpha * (qMix - qState)
- err := math.Atan2(qState, iState)
- freq += beta * err
- if freq < minFreq {
- freq = minFreq
- } else if freq > maxFreq {
- freq = maxFreq
- }
- phase += freq + alpha*err
- if phase > 2*math.Pi {
- phase -= 2 * math.Pi
- } else if phase < 0 {
- phase += 2 * math.Pi
- }
-
- totalPower += float64(mono[i]) * float64(mono[i])
- pilotPower += p * p
- errSum += math.Abs(err)
-
- lr[i] = bpf[i] * float32(2*math.Sin(2*phase))
- }
-
- lr = dsp.ApplyFIRReal(lr, lp)
-
- pilotRatio := 0.0
- if totalPower > 0 {
- pilotRatio = pilotPower / totalPower
- }
- freqHz := freq * float64(sampleRate) / (2 * math.Pi)
- blockErr := errSum / float64(len(mono))
- locked := pilotRatio > 0.003 && math.Abs(freqHz-19000) < 250 && blockErr < 0.35
-
- out := make([]float32, len(lpr)*2)
- for i := range lpr {
- out[i*2] = 0.5 * (lpr[i] + lr[i])
- out[i*2+1] = 0.5 * (lpr[i] - lr[i])
- }
- return out, locked
- }
-
- func pllCoefficientsBatch(loopBW, damping float64, sampleRate int) (float64, float64) {
- if sampleRate <= 0 || loopBW <= 0 {
- return 0, 0
- }
- bl := loopBW / float64(sampleRate)
- theta := bl / (damping + 0.25/damping)
- d := 1 + 2*damping*theta + theta*theta
- alpha := (4 * damping * theta) / d
- beta := (4 * theta * theta) / d
- return alpha, beta
- }
|