|
- package gpudemod
-
- import (
- "fmt"
- "math"
- )
-
- type CPUOracleState struct {
- SignalID int64
- ConfigHash uint64
- NCOPhase float64
- Decim int
- PhaseCount int
- NumTaps int
- ShiftedHistory []complex64
- BaseTaps []float32
- PolyphaseTaps []float32
- }
-
- func ResetCPUOracleStateIfConfigChanged(state *CPUOracleState, newHash uint64) {
- if state == nil {
- return
- }
- if state.ConfigHash != newHash {
- state.ConfigHash = newHash
- state.NCOPhase = 0
- state.PhaseCount = 0
- state.ShiftedHistory = state.ShiftedHistory[:0]
- }
- }
-
- func CPUOracleExtract(iqNew []complex64, state *CPUOracleState, phaseInc float64) []complex64 {
- if state == nil || state.NumTaps <= 0 || state.Decim <= 0 || len(state.BaseTaps) < state.NumTaps {
- return nil
- }
- out := make([]complex64, 0, len(iqNew)/maxInt(1, state.Decim)+2)
- phase := state.NCOPhase
- hist := append([]complex64(nil), state.ShiftedHistory...)
-
- for _, x := range iqNew {
- rot := complex64(complex(math.Cos(phase), math.Sin(phase)))
- s := x * rot
- hist = append(hist, s)
- state.PhaseCount++
-
- if state.PhaseCount == state.Decim {
- var y complex64
- for k := 0; k < state.NumTaps; k++ {
- idx := len(hist) - 1 - k
- var sample complex64
- if idx >= 0 {
- sample = hist[idx]
- }
- y += complex(state.BaseTaps[k], 0) * sample
- }
- out = append(out, y)
- state.PhaseCount = 0
- }
-
- if len(hist) > state.NumTaps-1 {
- hist = hist[len(hist)-(state.NumTaps-1):]
- }
-
- phase += phaseInc
- if phase >= math.Pi {
- phase -= 2 * math.Pi
- } else if phase < -math.Pi {
- phase += 2 * math.Pi
- }
- }
-
- state.NCOPhase = phase
- state.ShiftedHistory = append(state.ShiftedHistory[:0], hist...)
- return out
- }
-
- // CPUOracleExtractPolyphase keeps the same streaming state semantics as CPUOracleExtract,
- // but computes outputs using the explicit phase-major polyphase tap layout.
- func CPUOracleExtractPolyphase(iqNew []complex64, state *CPUOracleState, phaseInc float64) []complex64 {
- if state == nil || state.NumTaps <= 0 || state.Decim <= 0 || len(state.BaseTaps) < state.NumTaps {
- return nil
- }
- if len(state.PolyphaseTaps) == 0 {
- state.PolyphaseTaps = BuildPolyphaseTapsPhaseMajor(state.BaseTaps, state.Decim)
- }
- phaseLen := PolyphasePhaseLen(len(state.BaseTaps), state.Decim)
- out := make([]complex64, 0, len(iqNew)/maxInt(1, state.Decim)+2)
- phase := state.NCOPhase
- hist := append([]complex64(nil), state.ShiftedHistory...)
-
- for _, x := range iqNew {
- rot := complex64(complex(math.Cos(phase), math.Sin(phase)))
- s := x * rot
- hist = append(hist, s)
- state.PhaseCount++
-
- if state.PhaseCount == state.Decim {
- var y complex64
- for p := 0; p < state.Decim; p++ {
- for k := 0; k < phaseLen; k++ {
- tap := state.PolyphaseTaps[p*phaseLen+k]
- if tap == 0 {
- continue
- }
- srcBack := p + k*state.Decim
- idx := len(hist) - 1 - srcBack
- if idx < 0 {
- continue
- }
- y += complex(tap, 0) * hist[idx]
- }
- }
- out = append(out, y)
- state.PhaseCount = 0
- }
-
- if len(hist) > state.NumTaps-1 {
- hist = hist[len(hist)-(state.NumTaps-1):]
- }
-
- phase += phaseInc
- if phase >= math.Pi {
- phase -= 2 * math.Pi
- } else if phase < -math.Pi {
- phase += 2 * math.Pi
- }
- }
-
- state.NCOPhase = phase
- state.ShiftedHistory = append(state.ShiftedHistory[:0], hist...)
- return out
- }
-
- func RunChunkedCPUOracle(all []complex64, chunkSizes []int, mkState func() *CPUOracleState, phaseInc float64) []complex64 {
- state := mkState()
- out := make([]complex64, 0)
- pos := 0
- for _, n := range chunkSizes {
- if pos >= len(all) {
- break
- }
- end := pos + n
- if end > len(all) {
- end = len(all)
- }
- out = append(out, CPUOracleExtract(all[pos:end], state, phaseInc)...)
- pos = end
- }
- if pos < len(all) {
- out = append(out, CPUOracleExtract(all[pos:], state, phaseInc)...)
- }
- return out
- }
-
- func ExactIntegerDecimation(sampleRate int, outRate int) (int, error) {
- if sampleRate <= 0 || outRate <= 0 {
- return 0, fmt.Errorf("invalid sampleRate/outRate: %d/%d", sampleRate, outRate)
- }
- if sampleRate%outRate != 0 {
- return 0, fmt.Errorf("streaming polyphase extractor requires integer decimation: sampleRate=%d outRate=%d", sampleRate, outRate)
- }
- return sampleRate / outRate, nil
- }
-
- func maxInt(a int, b int) int {
- if a > b {
- return a
- }
- return b
- }
|