package dsp // StatefulDecimatingFIRComplex combines FIR filtering and decimation into a // single stateful stage. This avoids exposing FIR settling/transient output as // ordinary block-leading samples before decimation. type StatefulDecimatingFIRComplex struct { taps []float64 delayR []float64 delayI []float64 factor int phase int // number of input samples until next output sample (0 => emit now) } func (f *StatefulDecimatingFIRComplex) Phase() int { if f == nil { return 0 } return f.phase } func (f *StatefulDecimatingFIRComplex) TapsLen() int { if f == nil { return 0 } return len(f.taps) } func NewStatefulDecimatingFIRComplex(taps []float64, factor int) *StatefulDecimatingFIRComplex { if factor < 1 { factor = 1 } t := make([]float64, len(taps)) copy(t, taps) return &StatefulDecimatingFIRComplex{ taps: t, delayR: make([]float64, len(taps)), delayI: make([]float64, len(taps)), factor: factor, phase: 0, } } func (f *StatefulDecimatingFIRComplex) Reset() { for i := range f.delayR { f.delayR[i] = 0 f.delayI[i] = 0 } f.phase = 0 } func (f *StatefulDecimatingFIRComplex) Process(iq []complex64) []complex64 { if len(iq) == 0 || len(f.taps) == 0 { return nil } if f.factor <= 1 { out := make([]complex64, len(iq)) for i := 0; i < len(iq); i++ { copy(f.delayR[1:], f.delayR[:len(f.taps)-1]) copy(f.delayI[1:], f.delayI[:len(f.taps)-1]) f.delayR[0] = float64(real(iq[i])) f.delayI[0] = float64(imag(iq[i])) var accR, accI float64 for k := 0; k < len(f.taps); k++ { w := f.taps[k] accR += f.delayR[k] * w accI += f.delayI[k] * w } out[i] = complex(float32(accR), float32(accI)) } return out } out := make([]complex64, 0, len(iq)/f.factor+1) n := len(f.taps) for i := 0; i < len(iq); i++ { copy(f.delayR[1:], f.delayR[:n-1]) copy(f.delayI[1:], f.delayI[:n-1]) f.delayR[0] = float64(real(iq[i])) f.delayI[0] = float64(imag(iq[i])) if f.phase == 0 { var accR, accI float64 for k := 0; k < n; k++ { w := f.taps[k] accR += f.delayR[k] * w accI += f.delayI[k] * w } out = append(out, complex(float32(accR), float32(accI))) f.phase = f.factor - 1 } else { f.phase-- } } return out }