package gpudemod import ( "fmt" "sdr-wideband-suite/internal/dsp" ) type CPUOracleRunner struct { SampleRate int States map[int64]*CPUOracleState } func (r *CPUOracleRunner) ResetAllStates() { if r == nil { return } r.States = make(map[int64]*CPUOracleState) } func NewCPUOracleRunner(sampleRate int) *CPUOracleRunner { return &CPUOracleRunner{ SampleRate: sampleRate, States: make(map[int64]*CPUOracleState), } } func (r *CPUOracleRunner) ResetSignalState(signalID int64) { if r == nil || r.States == nil { return } delete(r.States, signalID) } func (r *CPUOracleRunner) getOrInitState(job StreamingExtractJob) (*CPUOracleState, error) { if r == nil { return nil, fmt.Errorf("nil CPUOracleRunner") } if r.States == nil { r.States = make(map[int64]*CPUOracleState) } decim, err := ExactIntegerDecimation(r.SampleRate, job.OutRate) if err != nil { return nil, err } state := r.States[job.SignalID] if state == nil { state = &CPUOracleState{SignalID: job.SignalID} r.States[job.SignalID] = state } ResetCPUOracleStateIfConfigChanged(state, job.ConfigHash) state.Decim = decim state.NumTaps = job.NumTaps if state.NumTaps <= 0 { state.NumTaps = 101 } cutoff := job.Bandwidth / 2 if cutoff < 200 { cutoff = 200 } base := dsp.LowpassFIR(cutoff, r.SampleRate, state.NumTaps) state.BaseTaps = make([]float32, len(base)) for i, v := range base { state.BaseTaps[i] = float32(v) } state.PolyphaseTaps = BuildPolyphaseTapsPhaseMajor(state.BaseTaps, state.Decim) if state.ShiftedHistory == nil { state.ShiftedHistory = make([]complex64, 0, maxInt(0, state.NumTaps-1)) } return state, nil } func (r *CPUOracleRunner) StreamingExtract(iqNew []complex64, jobs []StreamingExtractJob) ([]StreamingExtractResult, error) { results := make([]StreamingExtractResult, len(jobs)) active := make(map[int64]struct{}, len(jobs)) for i, job := range jobs { active[job.SignalID] = struct{}{} state, err := r.getOrInitState(job) if err != nil { return nil, err } out, phase, phaseCount, hist := runStreamingPolyphaseHostCore( iqNew, r.SampleRate, job.OffsetHz, state.NCOPhase, state.PhaseCount, state.NumTaps, state.Decim, state.ShiftedHistory, state.PolyphaseTaps, ) state.NCOPhase = phase state.PhaseCount = phaseCount state.ShiftedHistory = append(state.ShiftedHistory[:0], hist...) results[i] = StreamingExtractResult{ SignalID: job.SignalID, IQ: out, Rate: job.OutRate, NOut: len(out), PhaseCount: state.PhaseCount, HistoryLen: len(state.ShiftedHistory), } } for signalID := range r.States { if _, ok := active[signalID]; !ok { delete(r.States, signalID) } } return results, nil }