Wideband autonomous SDR analysis engine forked from sdr-visual-suite
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

672 lignes
19KB

  1. //go:build cufft && windows
  2. package gpudemod
  3. /*
  4. #cgo windows CFLAGS: -I"C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v13.2/include"
  5. #include <stdlib.h>
  6. #include <cuda_runtime.h>
  7. typedef struct { float x; float y; } gpud_float2;
  8. */
  9. import "C"
  10. import (
  11. "errors"
  12. "fmt"
  13. "math"
  14. "os"
  15. "path/filepath"
  16. "sync"
  17. "unsafe"
  18. "sdr-visual-suite/internal/demod"
  19. "sdr-visual-suite/internal/dsp"
  20. )
  21. type DemodType int
  22. const (
  23. DemodNFM DemodType = iota
  24. DemodWFM
  25. DemodAM
  26. DemodUSB
  27. DemodLSB
  28. DemodCW
  29. )
  30. var loadOnce sync.Once
  31. var loadErr error
  32. func ensureDLLLoaded() error {
  33. loadOnce.Do(func() {
  34. candidates := []string{}
  35. if exe, err := os.Executable(); err == nil {
  36. dir := filepath.Dir(exe)
  37. candidates = append(candidates, filepath.Join(dir, "gpudemod_kernels.dll"))
  38. }
  39. if wd, err := os.Getwd(); err == nil {
  40. candidates = append(candidates,
  41. filepath.Join(wd, "gpudemod_kernels.dll"),
  42. filepath.Join(wd, "internal", "demod", "gpudemod", "build", "gpudemod_kernels.dll"),
  43. )
  44. }
  45. if env := os.Getenv("GPUMOD_DLL"); env != "" {
  46. candidates = append([]string{env}, candidates...)
  47. }
  48. seen := map[string]bool{}
  49. for _, p := range candidates {
  50. if p == "" || seen[p] {
  51. continue
  52. }
  53. seen[p] = true
  54. if _, err := os.Stat(p); err == nil {
  55. res := bridgeLoadLibrary(p)
  56. if res == 0 {
  57. loadErr = nil
  58. fmt.Fprintf(os.Stderr, "gpudemod: loaded DLL %s\n", p)
  59. return
  60. }
  61. loadErr = fmt.Errorf("failed to load gpudemod DLL: %s (code %d)", p, res)
  62. fmt.Fprintf(os.Stderr, "gpudemod: DLL load failed for %s (code %d)\n", p, res)
  63. }
  64. }
  65. if loadErr == nil {
  66. loadErr = errors.New("gpudemod_kernels.dll not found")
  67. fmt.Fprintln(os.Stderr, "gpudemod: gpudemod_kernels.dll not found in search paths")
  68. }
  69. })
  70. return loadErr
  71. }
  72. type Engine struct {
  73. maxSamples int
  74. sampleRate int
  75. phase float64
  76. bfoPhase float64
  77. firTaps []float32
  78. cudaReady bool
  79. lastShiftUsedGPU bool
  80. lastFIRUsedGPU bool
  81. lastDecimUsedGPU bool
  82. lastDemodUsedGPU bool
  83. dIQIn *C.gpud_float2
  84. dShifted *C.gpud_float2
  85. dFiltered *C.gpud_float2
  86. dDecimated *C.gpud_float2
  87. dAudio *C.float
  88. iqBytes uintptr
  89. audioBytes uintptr
  90. }
  91. func Available() bool {
  92. if ensureDLLLoaded() != nil {
  93. return false
  94. }
  95. var count C.int
  96. if C.cudaGetDeviceCount(&count) != C.cudaSuccess {
  97. return false
  98. }
  99. return count > 0
  100. }
  101. func New(maxSamples int, sampleRate int) (*Engine, error) {
  102. if maxSamples <= 0 {
  103. return nil, errors.New("invalid maxSamples")
  104. }
  105. if sampleRate <= 0 {
  106. return nil, errors.New("invalid sampleRate")
  107. }
  108. if err := ensureDLLLoaded(); err != nil {
  109. return nil, err
  110. }
  111. if !Available() {
  112. return nil, errors.New("cuda device not available")
  113. }
  114. e := &Engine{
  115. maxSamples: maxSamples,
  116. sampleRate: sampleRate,
  117. cudaReady: true,
  118. iqBytes: uintptr(maxSamples) * unsafe.Sizeof(C.gpud_float2{}),
  119. audioBytes: uintptr(maxSamples) * unsafe.Sizeof(C.float(0)),
  120. }
  121. var ptr unsafe.Pointer
  122. if bridgeCudaMalloc(&ptr, e.iqBytes) != 0 {
  123. e.Close()
  124. return nil, errors.New("cudaMalloc dIQIn failed")
  125. }
  126. e.dIQIn = (*C.gpud_float2)(ptr)
  127. ptr = nil
  128. if bridgeCudaMalloc(&ptr, e.iqBytes) != 0 {
  129. e.Close()
  130. return nil, errors.New("cudaMalloc dShifted failed")
  131. }
  132. e.dShifted = (*C.gpud_float2)(ptr)
  133. ptr = nil
  134. if bridgeCudaMalloc(&ptr, e.iqBytes) != 0 {
  135. e.Close()
  136. return nil, errors.New("cudaMalloc dFiltered failed")
  137. }
  138. e.dFiltered = (*C.gpud_float2)(ptr)
  139. ptr = nil
  140. if bridgeCudaMalloc(&ptr, e.iqBytes) != 0 {
  141. e.Close()
  142. return nil, errors.New("cudaMalloc dDecimated failed")
  143. }
  144. e.dDecimated = (*C.gpud_float2)(ptr)
  145. ptr = nil
  146. if bridgeCudaMalloc(&ptr, e.audioBytes) != 0 {
  147. e.Close()
  148. return nil, errors.New("cudaMalloc dAudio failed")
  149. }
  150. e.dAudio = (*C.float)(ptr)
  151. return e, nil
  152. }
  153. func (e *Engine) SetFIR(taps []float32) {
  154. if len(taps) == 0 {
  155. e.firTaps = nil
  156. return
  157. }
  158. if len(taps) > 256 {
  159. taps = taps[:256]
  160. }
  161. e.firTaps = append(e.firTaps[:0], taps...)
  162. if e.cudaReady {
  163. _ = bridgeUploadFIRTaps((*C.float)(unsafe.Pointer(&e.firTaps[0])), len(e.firTaps))
  164. }
  165. }
  166. func (e *Engine) LastShiftUsedGPU() bool { return e != nil && e.lastShiftUsedGPU }
  167. func (e *Engine) LastDemodUsedGPU() bool { return e != nil && e.lastDemodUsedGPU }
  168. func (e *Engine) tryCUDAFreqShift(iq []complex64, offsetHz float64) ([]complex64, bool) {
  169. if e == nil || !e.cudaReady || len(iq) == 0 || e.dIQIn == nil || e.dShifted == nil {
  170. return nil, false
  171. }
  172. bytes := uintptr(len(iq)) * unsafe.Sizeof(complex64(0))
  173. if bridgeMemcpyH2D(unsafe.Pointer(e.dIQIn), unsafe.Pointer(&iq[0]), bytes) != 0 {
  174. return nil, false
  175. }
  176. phaseInc := -2.0 * math.Pi * offsetHz / float64(e.sampleRate)
  177. if bridgeLaunchFreqShift(e.dIQIn, e.dShifted, len(iq), phaseInc, e.phase) != 0 {
  178. return nil, false
  179. }
  180. if bridgeDeviceSync() != 0 {
  181. return nil, false
  182. }
  183. out := make([]complex64, len(iq))
  184. if bridgeMemcpyD2H(unsafe.Pointer(&out[0]), unsafe.Pointer(e.dShifted), bytes) != 0 {
  185. return nil, false
  186. }
  187. e.phase += phaseInc * float64(len(iq))
  188. return out, true
  189. }
  190. func (e *Engine) tryCUDAFIR(iq []complex64, numTaps int) ([]complex64, bool) {
  191. if e == nil || !e.cudaReady || len(iq) == 0 || numTaps <= 0 || e.dShifted == nil || e.dFiltered == nil {
  192. return nil, false
  193. }
  194. iqBytes := uintptr(len(iq)) * unsafe.Sizeof(complex64(0))
  195. if bridgeMemcpyH2D(unsafe.Pointer(e.dShifted), unsafe.Pointer(&iq[0]), iqBytes) != 0 {
  196. return nil, false
  197. }
  198. if bridgeLaunchFIR(e.dShifted, e.dFiltered, len(iq), numTaps) != 0 {
  199. return nil, false
  200. }
  201. if bridgeDeviceSync() != 0 {
  202. return nil, false
  203. }
  204. out := make([]complex64, len(iq))
  205. if bridgeMemcpyD2H(unsafe.Pointer(&out[0]), unsafe.Pointer(e.dFiltered), iqBytes) != 0 {
  206. return nil, false
  207. }
  208. return out, true
  209. }
  210. func (e *Engine) tryCUDADecimate(filtered []complex64, factor int) ([]complex64, bool) {
  211. if e == nil || !e.cudaReady || len(filtered) == 0 || factor <= 0 || e.dFiltered == nil || e.dDecimated == nil {
  212. return nil, false
  213. }
  214. nOut := len(filtered) / factor
  215. if nOut <= 0 {
  216. return nil, false
  217. }
  218. iqBytes := uintptr(len(filtered)) * unsafe.Sizeof(complex64(0))
  219. if bridgeMemcpyH2D(unsafe.Pointer(e.dFiltered), unsafe.Pointer(&filtered[0]), iqBytes) != 0 {
  220. return nil, false
  221. }
  222. if bridgeLaunchDecimate(e.dFiltered, e.dDecimated, nOut, factor) != 0 {
  223. return nil, false
  224. }
  225. if bridgeDeviceSync() != 0 {
  226. return nil, false
  227. }
  228. out := make([]complex64, nOut)
  229. outBytes := uintptr(nOut) * unsafe.Sizeof(complex64(0))
  230. if bridgeMemcpyD2H(unsafe.Pointer(&out[0]), unsafe.Pointer(e.dDecimated), outBytes) != 0 {
  231. return nil, false
  232. }
  233. return out, true
  234. }
  235. func (e *Engine) tryCUDAFMDiscrim(shifted []complex64) ([]float32, bool) {
  236. if e == nil || !e.cudaReady || len(shifted) < 2 || e.dShifted == nil || e.dAudio == nil {
  237. return nil, false
  238. }
  239. iqBytes := uintptr(len(shifted)) * unsafe.Sizeof(complex64(0))
  240. if bridgeMemcpyH2D(unsafe.Pointer(e.dShifted), unsafe.Pointer(&shifted[0]), iqBytes) != 0 {
  241. return nil, false
  242. }
  243. if bridgeLaunchFMDiscrim(e.dShifted, e.dAudio, len(shifted)) != 0 {
  244. return nil, false
  245. }
  246. if bridgeDeviceSync() != 0 {
  247. return nil, false
  248. }
  249. out := make([]float32, len(shifted)-1)
  250. outBytes := uintptr(len(out)) * unsafe.Sizeof(float32(0))
  251. if bridgeMemcpyD2H(unsafe.Pointer(&out[0]), unsafe.Pointer(e.dAudio), outBytes) != 0 {
  252. return nil, false
  253. }
  254. return out, true
  255. }
  256. func (e *Engine) tryCUDAAMEnvelope(shifted []complex64) ([]float32, bool) {
  257. if e == nil || !e.cudaReady || len(shifted) == 0 || e.dShifted == nil || e.dAudio == nil {
  258. return nil, false
  259. }
  260. iqBytes := uintptr(len(shifted)) * unsafe.Sizeof(complex64(0))
  261. if bridgeMemcpyH2D(unsafe.Pointer(e.dShifted), unsafe.Pointer(&shifted[0]), iqBytes) != 0 {
  262. return nil, false
  263. }
  264. if bridgeLaunchAMEnvelope(e.dShifted, e.dAudio, len(shifted)) != 0 {
  265. return nil, false
  266. }
  267. if bridgeDeviceSync() != 0 {
  268. return nil, false
  269. }
  270. out := make([]float32, len(shifted))
  271. outBytes := uintptr(len(out)) * unsafe.Sizeof(float32(0))
  272. if bridgeMemcpyD2H(unsafe.Pointer(&out[0]), unsafe.Pointer(e.dAudio), outBytes) != 0 {
  273. return nil, false
  274. }
  275. return out, true
  276. }
  277. func (e *Engine) tryCUDASSBProduct(shifted []complex64, bfoHz float64) ([]float32, bool) {
  278. if e == nil || !e.cudaReady || len(shifted) == 0 || e.dShifted == nil || e.dAudio == nil {
  279. return nil, false
  280. }
  281. iqBytes := uintptr(len(shifted)) * unsafe.Sizeof(complex64(0))
  282. if bridgeMemcpyH2D(unsafe.Pointer(e.dShifted), unsafe.Pointer(&shifted[0]), iqBytes) != 0 {
  283. return nil, false
  284. }
  285. phaseInc := 2.0 * math.Pi * bfoHz / float64(e.sampleRate)
  286. if bridgeLaunchSSBProduct(e.dShifted, e.dAudio, len(shifted), phaseInc, e.bfoPhase) != 0 {
  287. return nil, false
  288. }
  289. if bridgeDeviceSync() != 0 {
  290. return nil, false
  291. }
  292. out := make([]float32, len(shifted))
  293. outBytes := uintptr(len(out)) * unsafe.Sizeof(float32(0))
  294. if bridgeMemcpyD2H(unsafe.Pointer(&out[0]), unsafe.Pointer(e.dAudio), outBytes) != 0 {
  295. return nil, false
  296. }
  297. e.bfoPhase += phaseInc * float64(len(shifted))
  298. return out, true
  299. }
  300. func (e *Engine) ShiftFilterDecimate(iq []complex64, offsetHz float64, bw float64, outRate int) ([]complex64, int, error) {
  301. if e == nil {
  302. return nil, 0, errors.New("nil CUDA demod engine")
  303. }
  304. if !e.cudaReady {
  305. return nil, 0, errors.New("cuda demod engine is not initialized")
  306. }
  307. if len(iq) == 0 {
  308. return nil, 0, nil
  309. }
  310. if len(iq) > e.maxSamples {
  311. return nil, 0, errors.New("sample count exceeds engine capacity")
  312. }
  313. if outRate <= 0 {
  314. return nil, 0, errors.New("invalid output sample rate")
  315. }
  316. e.lastShiftUsedGPU = false
  317. e.lastFIRUsedGPU = false
  318. e.lastDecimUsedGPU = false
  319. e.lastDemodUsedGPU = false
  320. cutoff := bw / 2
  321. if cutoff < 200 {
  322. cutoff = 200
  323. }
  324. taps := e.firTaps
  325. if len(taps) == 0 {
  326. base64 := dsp.LowpassFIR(cutoff, e.sampleRate, 101)
  327. taps = make([]float32, len(base64))
  328. for i, v := range base64 {
  329. taps[i] = float32(v)
  330. }
  331. e.SetFIR(taps)
  332. }
  333. if len(taps) == 0 {
  334. return nil, 0, errors.New("no FIR taps configured")
  335. }
  336. decim := int(math.Round(float64(e.sampleRate) / float64(outRate)))
  337. if decim < 1 {
  338. decim = 1
  339. }
  340. n := len(iq)
  341. nOut := n / decim
  342. if nOut <= 0 {
  343. return nil, 0, errors.New("not enough output samples after decimation")
  344. }
  345. bytesIn := uintptr(n) * unsafe.Sizeof(complex64(0))
  346. if bridgeMemcpyH2D(unsafe.Pointer(e.dIQIn), unsafe.Pointer(&iq[0]), bytesIn) != 0 {
  347. return nil, 0, errors.New("cudaMemcpy H2D failed")
  348. }
  349. phaseInc := -2.0 * math.Pi * offsetHz / float64(e.sampleRate)
  350. if bridgeLaunchFreqShift(e.dIQIn, e.dShifted, n, phaseInc, e.phase) != 0 {
  351. return nil, 0, errors.New("gpu freq shift failed")
  352. }
  353. if bridgeLaunchFIR(e.dShifted, e.dFiltered, n, len(taps)) != 0 {
  354. return nil, 0, errors.New("gpu FIR failed")
  355. }
  356. if bridgeLaunchDecimate(e.dFiltered, e.dDecimated, nOut, decim) != 0 {
  357. return nil, 0, errors.New("gpu decimate failed")
  358. }
  359. if bridgeDeviceSync() != 0 {
  360. return nil, 0, errors.New("cudaDeviceSynchronize failed")
  361. }
  362. out := make([]complex64, nOut)
  363. outBytes := uintptr(nOut) * unsafe.Sizeof(complex64(0))
  364. if bridgeMemcpyD2H(unsafe.Pointer(&out[0]), unsafe.Pointer(e.dDecimated), outBytes) != 0 {
  365. return nil, 0, errors.New("cudaMemcpy D2H failed")
  366. }
  367. e.phase += phaseInc * float64(n)
  368. e.lastShiftUsedGPU = true
  369. e.lastFIRUsedGPU = true
  370. e.lastDecimUsedGPU = true
  371. return out, e.sampleRate / decim, nil
  372. }
  373. func (e *Engine) DemodFused(iq []complex64, offsetHz float64, bw float64, mode DemodType) ([]float32, int, error) {
  374. if e == nil {
  375. return nil, 0, errors.New("nil CUDA demod engine")
  376. }
  377. if !e.cudaReady {
  378. return nil, 0, errors.New("cuda demod engine is not initialized")
  379. }
  380. if len(iq) == 0 {
  381. return nil, 0, nil
  382. }
  383. e.lastShiftUsedGPU = false
  384. e.lastFIRUsedGPU = false
  385. e.lastDecimUsedGPU = false
  386. e.lastDemodUsedGPU = false
  387. if len(iq) > e.maxSamples {
  388. return nil, 0, errors.New("sample count exceeds engine capacity")
  389. }
  390. var outRate int
  391. switch mode {
  392. case DemodNFM, DemodAM, DemodUSB, DemodLSB, DemodCW:
  393. outRate = 48000
  394. case DemodWFM:
  395. outRate = 192000
  396. default:
  397. return nil, 0, errors.New("unsupported demod type")
  398. }
  399. cutoff := bw / 2
  400. if cutoff < 200 {
  401. cutoff = 200
  402. }
  403. taps := e.firTaps
  404. if len(taps) == 0 {
  405. base64 := dsp.LowpassFIR(cutoff, e.sampleRate, 101)
  406. taps = make([]float32, len(base64))
  407. for i, v := range base64 {
  408. taps[i] = float32(v)
  409. }
  410. e.SetFIR(taps)
  411. }
  412. if len(taps) == 0 {
  413. return nil, 0, errors.New("no FIR taps configured")
  414. }
  415. decim := int(math.Round(float64(e.sampleRate) / float64(outRate)))
  416. if decim < 1 {
  417. decim = 1
  418. }
  419. n := len(iq)
  420. nOut := n / decim
  421. if nOut <= 1 {
  422. return nil, 0, errors.New("not enough output samples after decimation")
  423. }
  424. bytesIn := uintptr(n) * unsafe.Sizeof(complex64(0))
  425. if bridgeMemcpyH2D(unsafe.Pointer(e.dIQIn), unsafe.Pointer(&iq[0]), bytesIn) != 0 {
  426. return nil, 0, errors.New("cudaMemcpy H2D failed")
  427. }
  428. phaseInc := -2.0 * math.Pi * offsetHz / float64(e.sampleRate)
  429. if bridgeLaunchFreqShift(e.dIQIn, e.dShifted, n, phaseInc, e.phase) != 0 {
  430. return nil, 0, errors.New("gpu freq shift failed")
  431. }
  432. if bridgeLaunchFIR(e.dShifted, e.dFiltered, n, len(taps)) != 0 {
  433. return nil, 0, errors.New("gpu FIR failed")
  434. }
  435. if bridgeLaunchDecimate(e.dFiltered, e.dDecimated, nOut, decim) != 0 {
  436. return nil, 0, errors.New("gpu decimate failed")
  437. }
  438. e.lastShiftUsedGPU = true
  439. e.lastFIRUsedGPU = true
  440. e.lastDecimUsedGPU = true
  441. e.lastDemodUsedGPU = false
  442. switch mode {
  443. case DemodNFM, DemodWFM:
  444. if bridgeLaunchFMDiscrim(e.dDecimated, e.dAudio, nOut) != 0 {
  445. return nil, 0, errors.New("gpu FM discrim failed")
  446. }
  447. out := make([]float32, nOut-1)
  448. outBytes := uintptr(len(out)) * unsafe.Sizeof(float32(0))
  449. if bridgeDeviceSync() != 0 {
  450. return nil, 0, errors.New("cudaDeviceSynchronize failed")
  451. }
  452. if bridgeMemcpyD2H(unsafe.Pointer(&out[0]), unsafe.Pointer(e.dAudio), outBytes) != 0 {
  453. return nil, 0, errors.New("cudaMemcpy D2H failed")
  454. }
  455. e.phase += phaseInc * float64(n)
  456. e.lastDemodUsedGPU = true
  457. return out, e.sampleRate / decim, nil
  458. case DemodAM:
  459. if bridgeLaunchAMEnvelope(e.dDecimated, e.dAudio, nOut) != 0 {
  460. return nil, 0, errors.New("gpu AM envelope failed")
  461. }
  462. out := make([]float32, nOut)
  463. outBytes := uintptr(len(out)) * unsafe.Sizeof(float32(0))
  464. if bridgeDeviceSync() != 0 {
  465. return nil, 0, errors.New("cudaDeviceSynchronize failed")
  466. }
  467. if bridgeMemcpyD2H(unsafe.Pointer(&out[0]), unsafe.Pointer(e.dAudio), outBytes) != 0 {
  468. return nil, 0, errors.New("cudaMemcpy D2H failed")
  469. }
  470. e.phase += phaseInc * float64(n)
  471. e.lastDemodUsedGPU = true
  472. return out, e.sampleRate / decim, nil
  473. case DemodUSB, DemodLSB, DemodCW:
  474. bfoHz := 700.0
  475. if mode == DemodLSB {
  476. bfoHz = -700.0
  477. }
  478. phaseBFO := 2.0 * math.Pi * bfoHz / float64(e.sampleRate)
  479. if bridgeLaunchSSBProduct(e.dDecimated, e.dAudio, nOut, phaseBFO, e.bfoPhase) != 0 {
  480. return nil, 0, errors.New("gpu SSB product failed")
  481. }
  482. out := make([]float32, nOut)
  483. outBytes := uintptr(len(out)) * unsafe.Sizeof(float32(0))
  484. if bridgeDeviceSync() != 0 {
  485. return nil, 0, errors.New("cudaDeviceSynchronize failed")
  486. }
  487. if bridgeMemcpyD2H(unsafe.Pointer(&out[0]), unsafe.Pointer(e.dAudio), outBytes) != 0 {
  488. return nil, 0, errors.New("cudaMemcpy D2H failed")
  489. }
  490. e.phase += phaseInc * float64(n)
  491. e.bfoPhase += phaseBFO * float64(nOut)
  492. e.lastDemodUsedGPU = true
  493. return out, e.sampleRate / decim, nil
  494. default:
  495. return nil, 0, errors.New("unsupported demod type")
  496. }
  497. }
  498. func (e *Engine) Demod(iq []complex64, offsetHz float64, bw float64, mode DemodType) ([]float32, int, error) {
  499. if e == nil {
  500. return nil, 0, errors.New("nil CUDA demod engine")
  501. }
  502. if !e.cudaReady {
  503. return nil, 0, errors.New("cuda demod engine is not initialized")
  504. }
  505. if len(iq) == 0 {
  506. return nil, 0, nil
  507. }
  508. if len(iq) > e.maxSamples {
  509. return nil, 0, errors.New("sample count exceeds engine capacity")
  510. }
  511. shifted, ok := e.tryCUDAFreqShift(iq, offsetHz)
  512. e.lastShiftUsedGPU = ok && ValidateFreqShift(iq, e.sampleRate, offsetHz, shifted, 1e-3)
  513. if !e.lastShiftUsedGPU {
  514. shifted = dsp.FreqShift(iq, e.sampleRate, offsetHz)
  515. }
  516. var outRate int
  517. switch mode {
  518. case DemodNFM, DemodAM, DemodUSB, DemodLSB, DemodCW:
  519. outRate = 48000
  520. case DemodWFM:
  521. outRate = 192000
  522. default:
  523. return nil, 0, errors.New("unsupported demod type")
  524. }
  525. cutoff := bw / 2
  526. if cutoff < 200 {
  527. cutoff = 200
  528. }
  529. taps := e.firTaps
  530. if len(taps) == 0 {
  531. base64 := dsp.LowpassFIR(cutoff, e.sampleRate, 101)
  532. taps = make([]float32, len(base64))
  533. for i, v := range base64 {
  534. taps[i] = float32(v)
  535. }
  536. e.SetFIR(taps)
  537. }
  538. filtered, ok := e.tryCUDAFIR(shifted, len(taps))
  539. if ok {
  540. if validationEnabled() {
  541. e.lastFIRUsedGPU = ValidateFIR(shifted, taps, filtered, 1e-3)
  542. if !e.lastFIRUsedGPU {
  543. ftaps := make([]float64, len(taps))
  544. for i, v := range taps {
  545. ftaps[i] = float64(v)
  546. }
  547. filtered = dsp.ApplyFIR(shifted, ftaps)
  548. }
  549. } else {
  550. e.lastFIRUsedGPU = true
  551. }
  552. }
  553. if filtered == nil {
  554. ftaps := make([]float64, len(taps))
  555. for i, v := range taps {
  556. ftaps[i] = float64(v)
  557. }
  558. filtered = dsp.ApplyFIR(shifted, ftaps)
  559. }
  560. decim := int(math.Round(float64(e.sampleRate) / float64(outRate)))
  561. if decim < 1 {
  562. decim = 1
  563. }
  564. dec, ok := e.tryCUDADecimate(filtered, decim)
  565. if ok {
  566. if validationEnabled() {
  567. e.lastDecimUsedGPU = ValidateDecimate(filtered, decim, dec, 1e-3)
  568. if !e.lastDecimUsedGPU {
  569. dec = dsp.Decimate(filtered, decim)
  570. }
  571. } else {
  572. e.lastDecimUsedGPU = true
  573. }
  574. }
  575. if dec == nil {
  576. dec = dsp.Decimate(filtered, decim)
  577. }
  578. inputRate := e.sampleRate / decim
  579. e.lastDemodUsedGPU = false
  580. switch mode {
  581. case DemodNFM:
  582. if gpuAudio, ok := e.tryCUDAFMDiscrim(dec); ok {
  583. e.lastDemodUsedGPU = true
  584. return gpuAudio, inputRate, nil
  585. }
  586. return demod.NFM{}.Demod(dec, inputRate), inputRate, nil
  587. case DemodWFM:
  588. if gpuAudio, ok := e.tryCUDAFMDiscrim(dec); ok {
  589. e.lastDemodUsedGPU = true
  590. return gpuAudio, inputRate, nil
  591. }
  592. return demod.WFM{}.Demod(dec, inputRate), inputRate, nil
  593. case DemodAM:
  594. if gpuAudio, ok := e.tryCUDAAMEnvelope(dec); ok {
  595. e.lastDemodUsedGPU = true
  596. return gpuAudio, inputRate, nil
  597. }
  598. return demod.AM{}.Demod(dec, inputRate), inputRate, nil
  599. case DemodUSB:
  600. if gpuAudio, ok := e.tryCUDASSBProduct(dec, 700.0); ok {
  601. e.lastDemodUsedGPU = true
  602. return gpuAudio, inputRate, nil
  603. }
  604. return demod.USB{}.Demod(dec, inputRate), inputRate, nil
  605. case DemodLSB:
  606. if gpuAudio, ok := e.tryCUDASSBProduct(dec, -700.0); ok {
  607. e.lastDemodUsedGPU = true
  608. return gpuAudio, inputRate, nil
  609. }
  610. return demod.LSB{}.Demod(dec, inputRate), inputRate, nil
  611. case DemodCW:
  612. if gpuAudio, ok := e.tryCUDASSBProduct(dec, 700.0); ok {
  613. e.lastDemodUsedGPU = true
  614. return gpuAudio, inputRate, nil
  615. }
  616. return demod.CW{}.Demod(dec, inputRate), inputRate, nil
  617. default:
  618. return nil, 0, errors.New("unsupported demod type")
  619. }
  620. }
  621. func (e *Engine) Close() {
  622. if e == nil {
  623. return
  624. }
  625. if e.dIQIn != nil {
  626. _ = bridgeCudaFree(unsafe.Pointer(e.dIQIn))
  627. e.dIQIn = nil
  628. }
  629. if e.dShifted != nil {
  630. _ = bridgeCudaFree(unsafe.Pointer(e.dShifted))
  631. e.dShifted = nil
  632. }
  633. if e.dFiltered != nil {
  634. _ = bridgeCudaFree(unsafe.Pointer(e.dFiltered))
  635. e.dFiltered = nil
  636. }
  637. if e.dDecimated != nil {
  638. _ = bridgeCudaFree(unsafe.Pointer(e.dDecimated))
  639. e.dDecimated = nil
  640. }
  641. if e.dAudio != nil {
  642. _ = bridgeCudaFree(unsafe.Pointer(e.dAudio))
  643. e.dAudio = nil
  644. }
  645. e.firTaps = nil
  646. e.cudaReady = false
  647. }