You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

528 lines
17KB

  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. #cgo windows LDFLAGS: -lcudart64_13 -lkernel32
  6. #include <windows.h>
  7. #include <stdlib.h>
  8. #include <cuda_runtime.h>
  9. typedef struct { float x; float y; } gpud_float2;
  10. typedef int (__stdcall *gpud_upload_fir_taps_fn)(const float* taps, int n);
  11. typedef int (__stdcall *gpud_launch_freq_shift_fn)(const gpud_float2* in, gpud_float2* out, int n, double phase_inc, double phase_start);
  12. typedef int (__stdcall *gpud_launch_fm_discrim_fn)(const gpud_float2* in, float* out, int n);
  13. typedef int (__stdcall *gpud_launch_fir_fn)(const gpud_float2* in, gpud_float2* out, int n, int num_taps);
  14. typedef int (__stdcall *gpud_launch_decimate_fn)(const gpud_float2* in, gpud_float2* out, int n_out, int factor);
  15. typedef int (__stdcall *gpud_launch_am_envelope_fn)(const gpud_float2* in, float* out, int n);
  16. typedef int (__stdcall *gpud_launch_ssb_product_fn)(const gpud_float2* in, float* out, int n, double phase_inc, double phase_start);
  17. static HMODULE gpud_mod = NULL;
  18. static gpud_upload_fir_taps_fn gpud_p_upload_fir_taps = NULL;
  19. static gpud_launch_freq_shift_fn gpud_p_launch_freq_shift = NULL;
  20. static gpud_launch_fm_discrim_fn gpud_p_launch_fm_discrim = NULL;
  21. static gpud_launch_fir_fn gpud_p_launch_fir = NULL;
  22. static gpud_launch_decimate_fn gpud_p_launch_decimate = NULL;
  23. static gpud_launch_am_envelope_fn gpud_p_launch_am_envelope = NULL;
  24. static gpud_launch_ssb_product_fn gpud_p_launch_ssb_product = NULL;
  25. static int gpud_cuda_malloc(void **ptr, size_t bytes) { return (int)cudaMalloc(ptr, bytes); }
  26. static int gpud_cuda_free(void *ptr) { return (int)cudaFree(ptr); }
  27. static int gpud_memcpy_h2d(void *dst, const void *src, size_t bytes) { return (int)cudaMemcpy(dst, src, bytes, cudaMemcpyHostToDevice); }
  28. static int gpud_memcpy_d2h(void *dst, const void *src, size_t bytes) { return (int)cudaMemcpy(dst, src, bytes, cudaMemcpyDeviceToHost); }
  29. static int gpud_device_sync() { return (int)cudaDeviceSynchronize(); }
  30. static int gpud_load_library(const char* path) {
  31. if (gpud_mod != NULL) return 0;
  32. gpud_mod = LoadLibraryA(path);
  33. if (gpud_mod == NULL) return -1;
  34. gpud_p_upload_fir_taps = (gpud_upload_fir_taps_fn)GetProcAddress(gpud_mod, "gpud_upload_fir_taps_cuda");
  35. gpud_p_launch_freq_shift = (gpud_launch_freq_shift_fn)GetProcAddress(gpud_mod, "gpud_launch_freq_shift_cuda");
  36. gpud_p_launch_fm_discrim = (gpud_launch_fm_discrim_fn)GetProcAddress(gpud_mod, "gpud_launch_fm_discrim_cuda");
  37. gpud_p_launch_fir = (gpud_launch_fir_fn)GetProcAddress(gpud_mod, "gpud_launch_fir_cuda");
  38. gpud_p_launch_decimate = (gpud_launch_decimate_fn)GetProcAddress(gpud_mod, "gpud_launch_decimate_cuda");
  39. gpud_p_launch_am_envelope = (gpud_launch_am_envelope_fn)GetProcAddress(gpud_mod, "gpud_launch_am_envelope_cuda");
  40. gpud_p_launch_ssb_product = (gpud_launch_ssb_product_fn)GetProcAddress(gpud_mod, "gpud_launch_ssb_product_cuda");
  41. if (!gpud_p_upload_fir_taps || !gpud_p_launch_freq_shift || !gpud_p_launch_fm_discrim || !gpud_p_launch_fir || !gpud_p_launch_decimate || !gpud_p_launch_am_envelope || !gpud_p_launch_ssb_product) {
  42. FreeLibrary(gpud_mod);
  43. gpud_mod = NULL;
  44. return -2;
  45. }
  46. return 0;
  47. }
  48. static int gpud_upload_fir_taps(const float* taps, int n) {
  49. if (!gpud_p_upload_fir_taps) return -1;
  50. return gpud_p_upload_fir_taps(taps, n);
  51. }
  52. static int gpud_launch_freq_shift(gpud_float2 *in, gpud_float2 *out, int n, double phase_inc, double phase_start) {
  53. if (!gpud_p_launch_freq_shift) return -1;
  54. return gpud_p_launch_freq_shift(in, out, n, phase_inc, phase_start);
  55. }
  56. static int gpud_launch_fm_discrim(gpud_float2 *in, float *out, int n) {
  57. if (!gpud_p_launch_fm_discrim) return -1;
  58. return gpud_p_launch_fm_discrim(in, out, n);
  59. }
  60. static int gpud_launch_fir(gpud_float2 *in, gpud_float2 *out, int n, int num_taps) {
  61. if (!gpud_p_launch_fir) return -1;
  62. return gpud_p_launch_fir(in, out, n, num_taps);
  63. }
  64. static int gpud_launch_decimate(gpud_float2 *in, gpud_float2 *out, int n_out, int factor) {
  65. if (!gpud_p_launch_decimate) return -1;
  66. return gpud_p_launch_decimate(in, out, n_out, factor);
  67. }
  68. static int gpud_launch_am_envelope(gpud_float2 *in, float *out, int n) {
  69. if (!gpud_p_launch_am_envelope) return -1;
  70. return gpud_p_launch_am_envelope(in, out, n);
  71. }
  72. static int gpud_launch_ssb_product(gpud_float2 *in, float *out, int n, double phase_inc, double phase_start) {
  73. if (!gpud_p_launch_ssb_product) return -1;
  74. return gpud_p_launch_ssb_product(in, out, n, phase_inc, phase_start);
  75. }
  76. */
  77. import "C"
  78. import (
  79. "errors"
  80. "fmt"
  81. "math"
  82. "os"
  83. "path/filepath"
  84. "sync"
  85. "unsafe"
  86. "sdr-visual-suite/internal/demod"
  87. "sdr-visual-suite/internal/dsp"
  88. )
  89. type DemodType int
  90. const (
  91. DemodNFM DemodType = iota
  92. DemodWFM
  93. DemodAM
  94. DemodUSB
  95. DemodLSB
  96. DemodCW
  97. )
  98. var loadOnce sync.Once
  99. var loadErr error
  100. func ensureDLLLoaded() error {
  101. loadOnce.Do(func() {
  102. candidates := []string{}
  103. if exe, err := os.Executable(); err == nil {
  104. dir := filepath.Dir(exe)
  105. candidates = append(candidates, filepath.Join(dir, "gpudemod_kernels.dll"))
  106. }
  107. if wd, err := os.Getwd(); err == nil {
  108. candidates = append(candidates,
  109. filepath.Join(wd, "gpudemod_kernels.dll"),
  110. filepath.Join(wd, "internal", "demod", "gpudemod", "build", "gpudemod_kernels.dll"),
  111. )
  112. }
  113. seen := map[string]bool{}
  114. for _, p := range candidates {
  115. if p == "" || seen[p] {
  116. continue
  117. }
  118. seen[p] = true
  119. if _, err := os.Stat(p); err == nil {
  120. cp := C.CString(p)
  121. res := C.gpud_load_library(cp)
  122. C.free(unsafe.Pointer(cp))
  123. if res == 0 {
  124. loadErr = nil
  125. fmt.Fprintf(os.Stderr, "gpudemod: loaded DLL %s\n", p)
  126. return
  127. }
  128. loadErr = fmt.Errorf("failed to load gpudemod DLL: %s (code %d)", p, int(res))
  129. fmt.Fprintf(os.Stderr, "gpudemod: DLL load failed for %s (code %d)\n", p, int(res))
  130. }
  131. }
  132. if loadErr == nil {
  133. loadErr = errors.New("gpudemod_kernels.dll not found")
  134. fmt.Fprintln(os.Stderr, "gpudemod: gpudemod_kernels.dll not found in search paths")
  135. }
  136. })
  137. return loadErr
  138. }
  139. type Engine struct {
  140. maxSamples int
  141. sampleRate int
  142. phase float64
  143. bfoPhase float64
  144. firTaps []float32
  145. cudaReady bool
  146. lastShiftUsedGPU bool
  147. lastFIRUsedGPU bool
  148. lastDecimUsedGPU bool
  149. lastDemodUsedGPU bool
  150. dIQIn *C.gpud_float2
  151. dShifted *C.gpud_float2
  152. dFiltered *C.gpud_float2
  153. dDecimated *C.gpud_float2
  154. dAudio *C.float
  155. iqBytes C.size_t
  156. audioBytes C.size_t
  157. }
  158. func Available() bool {
  159. if ensureDLLLoaded() != nil {
  160. return false
  161. }
  162. var count C.int
  163. if C.cudaGetDeviceCount(&count) != C.cudaSuccess {
  164. return false
  165. }
  166. return count > 0
  167. }
  168. func New(maxSamples int, sampleRate int) (*Engine, error) {
  169. if maxSamples <= 0 {
  170. return nil, errors.New("invalid maxSamples")
  171. }
  172. if sampleRate <= 0 {
  173. return nil, errors.New("invalid sampleRate")
  174. }
  175. if err := ensureDLLLoaded(); err != nil {
  176. return nil, err
  177. }
  178. if !Available() {
  179. return nil, errors.New("cuda device not available")
  180. }
  181. e := &Engine{
  182. maxSamples: maxSamples,
  183. sampleRate: sampleRate,
  184. cudaReady: true,
  185. iqBytes: C.size_t(maxSamples) * C.size_t(unsafe.Sizeof(C.gpud_float2{})),
  186. audioBytes: C.size_t(maxSamples) * C.size_t(unsafe.Sizeof(C.float(0))),
  187. }
  188. var ptr unsafe.Pointer
  189. if C.gpud_cuda_malloc(&ptr, e.iqBytes) != C.cudaSuccess {
  190. e.Close()
  191. return nil, errors.New("cudaMalloc dIQIn failed")
  192. }
  193. e.dIQIn = (*C.gpud_float2)(ptr)
  194. ptr = nil
  195. if C.gpud_cuda_malloc(&ptr, e.iqBytes) != C.cudaSuccess {
  196. e.Close()
  197. return nil, errors.New("cudaMalloc dShifted failed")
  198. }
  199. e.dShifted = (*C.gpud_float2)(ptr)
  200. ptr = nil
  201. if C.gpud_cuda_malloc(&ptr, e.iqBytes) != C.cudaSuccess {
  202. e.Close()
  203. return nil, errors.New("cudaMalloc dFiltered failed")
  204. }
  205. e.dFiltered = (*C.gpud_float2)(ptr)
  206. ptr = nil
  207. if C.gpud_cuda_malloc(&ptr, e.iqBytes) != C.cudaSuccess {
  208. e.Close()
  209. return nil, errors.New("cudaMalloc dDecimated failed")
  210. }
  211. e.dDecimated = (*C.gpud_float2)(ptr)
  212. ptr = nil
  213. if C.gpud_cuda_malloc(&ptr, e.audioBytes) != C.cudaSuccess {
  214. e.Close()
  215. return nil, errors.New("cudaMalloc dAudio failed")
  216. }
  217. e.dAudio = (*C.float)(ptr)
  218. return e, nil
  219. }
  220. func (e *Engine) SetFIR(taps []float32) {
  221. if len(taps) == 0 {
  222. e.firTaps = nil
  223. return
  224. }
  225. if len(taps) > 256 {
  226. taps = taps[:256]
  227. }
  228. e.firTaps = append(e.firTaps[:0], taps...)
  229. if e.cudaReady {
  230. _ = C.gpud_upload_fir_taps((*C.float)(unsafe.Pointer(&e.firTaps[0])), C.int(len(e.firTaps)))
  231. }
  232. }
  233. func phaseStatus() string { return "phase1c-validated-shift" }
  234. func (e *Engine) LastShiftUsedGPU() bool { return e != nil && e.lastShiftUsedGPU }
  235. func (e *Engine) LastDemodUsedGPU() bool { return e != nil && e.lastDemodUsedGPU }
  236. func (e *Engine) tryCUDAFreqShift(iq []complex64, offsetHz float64) ([]complex64, bool) {
  237. if e == nil || !e.cudaReady || len(iq) == 0 || e.dIQIn == nil || e.dShifted == nil {
  238. return nil, false
  239. }
  240. bytes := C.size_t(len(iq)) * C.size_t(unsafe.Sizeof(complex64(0)))
  241. if C.gpud_memcpy_h2d(unsafe.Pointer(e.dIQIn), unsafe.Pointer(&iq[0]), bytes) != C.cudaSuccess {
  242. return nil, false
  243. }
  244. phaseInc := -2.0 * math.Pi * offsetHz / float64(e.sampleRate)
  245. if C.gpud_launch_freq_shift(e.dIQIn, e.dShifted, C.int(len(iq)), C.double(phaseInc), C.double(e.phase)) != 0 {
  246. return nil, false
  247. }
  248. if C.gpud_device_sync() != C.cudaSuccess {
  249. return nil, false
  250. }
  251. out := make([]complex64, len(iq))
  252. if C.gpud_memcpy_d2h(unsafe.Pointer(&out[0]), unsafe.Pointer(e.dShifted), bytes) != C.cudaSuccess {
  253. return nil, false
  254. }
  255. e.phase += phaseInc * float64(len(iq))
  256. return out, true
  257. }
  258. func (e *Engine) tryCUDAFIR(iq []complex64, numTaps int) ([]complex64, bool) {
  259. if e == nil || !e.cudaReady || len(iq) == 0 || numTaps <= 0 || e.dShifted == nil || e.dFiltered == nil {
  260. return nil, false
  261. }
  262. iqBytes := C.size_t(len(iq)) * C.size_t(unsafe.Sizeof(complex64(0)))
  263. if C.gpud_memcpy_h2d(unsafe.Pointer(e.dShifted), unsafe.Pointer(&iq[0]), iqBytes) != C.cudaSuccess {
  264. return nil, false
  265. }
  266. if C.gpud_launch_fir(e.dShifted, e.dFiltered, C.int(len(iq)), C.int(numTaps)) != 0 {
  267. return nil, false
  268. }
  269. if C.gpud_device_sync() != C.cudaSuccess {
  270. return nil, false
  271. }
  272. out := make([]complex64, len(iq))
  273. if C.gpud_memcpy_d2h(unsafe.Pointer(&out[0]), unsafe.Pointer(e.dFiltered), iqBytes) != C.cudaSuccess {
  274. return nil, false
  275. }
  276. return out, true
  277. }
  278. func (e *Engine) tryCUDADecimate(filtered []complex64, factor int) ([]complex64, bool) {
  279. if e == nil || !e.cudaReady || len(filtered) == 0 || factor <= 0 || e.dFiltered == nil || e.dDecimated == nil {
  280. return nil, false
  281. }
  282. nOut := len(filtered) / factor
  283. if nOut <= 0 {
  284. return nil, false
  285. }
  286. iqBytes := C.size_t(len(filtered)) * C.size_t(unsafe.Sizeof(complex64(0)))
  287. if C.gpud_memcpy_h2d(unsafe.Pointer(e.dFiltered), unsafe.Pointer(&filtered[0]), iqBytes) != C.cudaSuccess {
  288. return nil, false
  289. }
  290. if C.gpud_launch_decimate(e.dFiltered, e.dDecimated, C.int(nOut), C.int(factor)) != 0 {
  291. return nil, false
  292. }
  293. if C.gpud_device_sync() != C.cudaSuccess {
  294. return nil, false
  295. }
  296. out := make([]complex64, nOut)
  297. outBytes := C.size_t(nOut) * C.size_t(unsafe.Sizeof(complex64(0)))
  298. if C.gpud_memcpy_d2h(unsafe.Pointer(&out[0]), unsafe.Pointer(e.dDecimated), outBytes) != C.cudaSuccess {
  299. return nil, false
  300. }
  301. return out, true
  302. }
  303. func (e *Engine) tryCUDAFMDiscrim(shifted []complex64) ([]float32, bool) {
  304. if e == nil || !e.cudaReady || len(shifted) < 2 || e.dShifted == nil || e.dAudio == nil {
  305. return nil, false
  306. }
  307. iqBytes := C.size_t(len(shifted)) * C.size_t(unsafe.Sizeof(complex64(0)))
  308. if C.gpud_memcpy_h2d(unsafe.Pointer(e.dShifted), unsafe.Pointer(&shifted[0]), iqBytes) != C.cudaSuccess {
  309. return nil, false
  310. }
  311. if C.gpud_launch_fm_discrim(e.dShifted, e.dAudio, C.int(len(shifted))) != 0 {
  312. return nil, false
  313. }
  314. if C.gpud_device_sync() != C.cudaSuccess {
  315. return nil, false
  316. }
  317. out := make([]float32, len(shifted)-1)
  318. outBytes := C.size_t(len(out)) * C.size_t(unsafe.Sizeof(float32(0)))
  319. if C.gpud_memcpy_d2h(unsafe.Pointer(&out[0]), unsafe.Pointer(e.dAudio), outBytes) != C.cudaSuccess {
  320. return nil, false
  321. }
  322. return out, true
  323. }
  324. func (e *Engine) tryCUDAAMEnvelope(shifted []complex64) ([]float32, bool) {
  325. if e == nil || !e.cudaReady || len(shifted) == 0 || e.dShifted == nil || e.dAudio == nil {
  326. return nil, false
  327. }
  328. iqBytes := C.size_t(len(shifted)) * C.size_t(unsafe.Sizeof(complex64(0)))
  329. if C.gpud_memcpy_h2d(unsafe.Pointer(e.dShifted), unsafe.Pointer(&shifted[0]), iqBytes) != C.cudaSuccess {
  330. return nil, false
  331. }
  332. if C.gpud_launch_am_envelope(e.dShifted, e.dAudio, C.int(len(shifted))) != 0 {
  333. return nil, false
  334. }
  335. if C.gpud_device_sync() != C.cudaSuccess {
  336. return nil, false
  337. }
  338. out := make([]float32, len(shifted))
  339. outBytes := C.size_t(len(out)) * C.size_t(unsafe.Sizeof(float32(0)))
  340. if C.gpud_memcpy_d2h(unsafe.Pointer(&out[0]), unsafe.Pointer(e.dAudio), outBytes) != C.cudaSuccess {
  341. return nil, false
  342. }
  343. return out, true
  344. }
  345. func (e *Engine) tryCUDASSBProduct(shifted []complex64, bfoHz float64) ([]float32, bool) {
  346. if e == nil || !e.cudaReady || len(shifted) == 0 || e.dShifted == nil || e.dAudio == nil {
  347. return nil, false
  348. }
  349. iqBytes := C.size_t(len(shifted)) * C.size_t(unsafe.Sizeof(complex64(0)))
  350. if C.gpud_memcpy_h2d(unsafe.Pointer(e.dShifted), unsafe.Pointer(&shifted[0]), iqBytes) != C.cudaSuccess {
  351. return nil, false
  352. }
  353. phaseInc := 2.0 * math.Pi * bfoHz / float64(e.sampleRate)
  354. if C.gpud_launch_ssb_product(e.dShifted, e.dAudio, C.int(len(shifted)), C.double(phaseInc), C.double(e.bfoPhase)) != 0 {
  355. return nil, false
  356. }
  357. if C.gpud_device_sync() != C.cudaSuccess {
  358. return nil, false
  359. }
  360. out := make([]float32, len(shifted))
  361. outBytes := C.size_t(len(out)) * C.size_t(unsafe.Sizeof(float32(0)))
  362. if C.gpud_memcpy_d2h(unsafe.Pointer(&out[0]), unsafe.Pointer(e.dAudio), outBytes) != C.cudaSuccess {
  363. return nil, false
  364. }
  365. e.bfoPhase += phaseInc * float64(len(shifted))
  366. return out, true
  367. }
  368. func (e *Engine) Demod(iq []complex64, offsetHz float64, bw float64, mode DemodType) ([]float32, int, error) {
  369. if e == nil {
  370. return nil, 0, errors.New("nil CUDA demod engine")
  371. }
  372. if !e.cudaReady {
  373. return nil, 0, errors.New("cuda demod engine is not initialized")
  374. }
  375. if len(iq) == 0 {
  376. return nil, 0, nil
  377. }
  378. if len(iq) > e.maxSamples {
  379. return nil, 0, errors.New("sample count exceeds engine capacity")
  380. }
  381. shifted, ok := e.tryCUDAFreqShift(iq, offsetHz)
  382. e.lastShiftUsedGPU = ok && ValidateFreqShift(iq, e.sampleRate, offsetHz, shifted, 1e-3)
  383. if !e.lastShiftUsedGPU {
  384. shifted = dsp.FreqShift(iq, e.sampleRate, offsetHz)
  385. }
  386. var outRate int
  387. switch mode {
  388. case DemodNFM, DemodAM, DemodUSB, DemodLSB, DemodCW:
  389. outRate = 48000
  390. case DemodWFM:
  391. outRate = 192000
  392. default:
  393. return nil, 0, errors.New("unsupported demod type")
  394. }
  395. cutoff := bw / 2
  396. if cutoff < 200 {
  397. cutoff = 200
  398. }
  399. taps := e.firTaps
  400. if len(taps) == 0 {
  401. base64 := dsp.LowpassFIR(cutoff, e.sampleRate, 101)
  402. taps = make([]float32, len(base64))
  403. for i, v := range base64 {
  404. taps[i] = float32(v)
  405. }
  406. e.SetFIR(taps)
  407. }
  408. filtered, ok := e.tryCUDAFIR(shifted, len(taps))
  409. e.lastFIRUsedGPU = ok && ValidateFIR(shifted, taps, filtered, 1e-3)
  410. if !e.lastFIRUsedGPU {
  411. ftaps := make([]float64, len(taps))
  412. for i, v := range taps {
  413. ftaps[i] = float64(v)
  414. }
  415. filtered = dsp.ApplyFIR(shifted, ftaps)
  416. }
  417. decim := int(math.Round(float64(e.sampleRate) / float64(outRate)))
  418. if decim < 1 {
  419. decim = 1
  420. }
  421. dec, ok := e.tryCUDADecimate(filtered, decim)
  422. e.lastDecimUsedGPU = ok && ValidateDecimate(filtered, decim, dec, 1e-3)
  423. if !e.lastDecimUsedGPU {
  424. dec = dsp.Decimate(filtered, decim)
  425. }
  426. inputRate := e.sampleRate / decim
  427. e.lastDemodUsedGPU = false
  428. switch mode {
  429. case DemodNFM:
  430. if gpuAudio, ok := e.tryCUDAFMDiscrim(dec); ok {
  431. e.lastDemodUsedGPU = true
  432. return gpuAudio, inputRate, nil
  433. }
  434. return demod.NFM{}.Demod(dec, inputRate), inputRate, nil
  435. case DemodWFM:
  436. if gpuAudio, ok := e.tryCUDAFMDiscrim(dec); ok {
  437. e.lastDemodUsedGPU = true
  438. return gpuAudio, inputRate, nil
  439. }
  440. return demod.WFM{}.Demod(dec, inputRate), inputRate, nil
  441. case DemodAM:
  442. if gpuAudio, ok := e.tryCUDAAMEnvelope(dec); ok {
  443. e.lastDemodUsedGPU = true
  444. return gpuAudio, inputRate, nil
  445. }
  446. return demod.AM{}.Demod(dec, inputRate), inputRate, nil
  447. case DemodUSB:
  448. if gpuAudio, ok := e.tryCUDASSBProduct(dec, 700.0); ok {
  449. e.lastDemodUsedGPU = true
  450. return gpuAudio, inputRate, nil
  451. }
  452. return demod.USB{}.Demod(dec, inputRate), inputRate, nil
  453. case DemodLSB:
  454. if gpuAudio, ok := e.tryCUDASSBProduct(dec, -700.0); ok {
  455. e.lastDemodUsedGPU = true
  456. return gpuAudio, inputRate, nil
  457. }
  458. return demod.LSB{}.Demod(dec, inputRate), inputRate, nil
  459. case DemodCW:
  460. if gpuAudio, ok := e.tryCUDASSBProduct(dec, 700.0); ok {
  461. e.lastDemodUsedGPU = true
  462. return gpuAudio, inputRate, nil
  463. }
  464. return demod.CW{}.Demod(dec, inputRate), inputRate, nil
  465. default:
  466. return nil, 0, errors.New("unsupported demod type")
  467. }
  468. }
  469. func (e *Engine) Close() {
  470. if e == nil {
  471. return
  472. }
  473. if e.dIQIn != nil {
  474. _ = C.gpud_cuda_free(unsafe.Pointer(e.dIQIn))
  475. e.dIQIn = nil
  476. }
  477. if e.dShifted != nil {
  478. _ = C.gpud_cuda_free(unsafe.Pointer(e.dShifted))
  479. e.dShifted = nil
  480. }
  481. if e.dFiltered != nil {
  482. _ = C.gpud_cuda_free(unsafe.Pointer(e.dFiltered))
  483. e.dFiltered = nil
  484. }
  485. if e.dDecimated != nil {
  486. _ = C.gpud_cuda_free(unsafe.Pointer(e.dDecimated))
  487. e.dDecimated = nil
  488. }
  489. if e.dAudio != nil {
  490. _ = C.gpud_cuda_free(unsafe.Pointer(e.dAudio))
  491. e.dAudio = nil
  492. }
  493. e.firTaps = nil
  494. e.cudaReady = false
  495. }