rtlsdr 0.1.11 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/README.md +193 -1
- data/lib/rtlsdr/demod.rb +386 -0
- data/lib/rtlsdr/dsp/filter.rb +311 -0
- data/lib/rtlsdr/dsp.rb +302 -0
- data/lib/rtlsdr/fftw.rb +174 -0
- data/lib/rtlsdr/version.rb +1 -1
- data/lib/rtlsdr.rb +3 -0
- metadata +5 -2
data/lib/rtlsdr/fftw.rb
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RTLSDR
|
|
4
|
+
# FFI bindings for FFTW3 (Fastest Fourier Transform in the West)
|
|
5
|
+
#
|
|
6
|
+
# This module provides low-level FFI bindings to the FFTW3 library for
|
|
7
|
+
# performing fast Fourier transforms. FFTW3 must be installed on the system.
|
|
8
|
+
#
|
|
9
|
+
# @note This is an internal module. Use RTLSDR::DSP.fft and related methods instead.
|
|
10
|
+
#
|
|
11
|
+
# @example System installation
|
|
12
|
+
# # macOS: brew install fftw
|
|
13
|
+
# # Ubuntu/Debian: apt-get install libfftw3-dev
|
|
14
|
+
# # Fedora: dnf install fftw-devel
|
|
15
|
+
module FFTW
|
|
16
|
+
extend ::FFI::Library
|
|
17
|
+
|
|
18
|
+
# Try to load FFTW3 library with common paths
|
|
19
|
+
LIBRARY_NAMES = %w[
|
|
20
|
+
fftw3
|
|
21
|
+
libfftw3.so.3
|
|
22
|
+
libfftw3.dylib
|
|
23
|
+
libfftw3-3.dll
|
|
24
|
+
].freeze
|
|
25
|
+
|
|
26
|
+
begin
|
|
27
|
+
ffi_lib LIBRARY_NAMES
|
|
28
|
+
@available = true
|
|
29
|
+
rescue LoadError => e
|
|
30
|
+
@available = false
|
|
31
|
+
@load_error = e.message
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class << self
|
|
35
|
+
# Check if FFTW3 library is available
|
|
36
|
+
#
|
|
37
|
+
# @return [Boolean] true if FFTW3 is loaded and available
|
|
38
|
+
def available?
|
|
39
|
+
@available
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Get the error message if FFTW3 failed to load
|
|
43
|
+
#
|
|
44
|
+
# @return [String, nil] Error message or nil if loaded successfully
|
|
45
|
+
attr_reader :load_error
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# FFTW planning flags
|
|
49
|
+
FFTW_MEASURE = 0
|
|
50
|
+
FFTW_DESTROY_INPUT = 1
|
|
51
|
+
FFTW_UNALIGNED = 2
|
|
52
|
+
FFTW_CONSERVE_MEMORY = 4
|
|
53
|
+
FFTW_EXHAUSTIVE = 8
|
|
54
|
+
FFTW_PRESERVE_INPUT = 16
|
|
55
|
+
FFTW_PATIENT = 32
|
|
56
|
+
FFTW_ESTIMATE = 64
|
|
57
|
+
FFTW_WISDOM_ONLY = 2_097_152
|
|
58
|
+
|
|
59
|
+
# FFT direction
|
|
60
|
+
FFTW_FORWARD = -1
|
|
61
|
+
FFTW_BACKWARD = 1
|
|
62
|
+
|
|
63
|
+
if available?
|
|
64
|
+
# Memory allocation
|
|
65
|
+
attach_function :fftw_malloc, [:size_t], :pointer
|
|
66
|
+
attach_function :fftw_free, [:pointer], :void
|
|
67
|
+
|
|
68
|
+
# Plan creation and execution
|
|
69
|
+
attach_function :fftw_plan_dft_1d, %i[int pointer pointer int uint], :pointer
|
|
70
|
+
attach_function :fftw_execute, [:pointer], :void
|
|
71
|
+
attach_function :fftw_destroy_plan, [:pointer], :void
|
|
72
|
+
|
|
73
|
+
# Wisdom (plan caching)
|
|
74
|
+
attach_function :fftw_export_wisdom_to_string, [], :pointer
|
|
75
|
+
attach_function :fftw_import_wisdom_from_string, [:string], :int
|
|
76
|
+
attach_function :fftw_forget_wisdom, [], :void
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Size of a complex number in FFTW (two doubles)
|
|
80
|
+
COMPLEX_SIZE = 16
|
|
81
|
+
|
|
82
|
+
# Perform forward FFT on complex samples
|
|
83
|
+
#
|
|
84
|
+
# @param [Array<Complex>] samples Input complex samples
|
|
85
|
+
# @return [Array<Complex>] FFT result (complex frequency bins)
|
|
86
|
+
# @raise [RuntimeError] if FFTW3 is not available
|
|
87
|
+
def self.forward(samples)
|
|
88
|
+
raise "FFTW3 library not available: #{load_error}" unless available?
|
|
89
|
+
|
|
90
|
+
n = samples.length
|
|
91
|
+
return [] if n.zero?
|
|
92
|
+
|
|
93
|
+
# Allocate input and output arrays
|
|
94
|
+
input = fftw_malloc(n * COMPLEX_SIZE)
|
|
95
|
+
output = fftw_malloc(n * COMPLEX_SIZE)
|
|
96
|
+
|
|
97
|
+
begin
|
|
98
|
+
# Copy samples to input array (interleaved real/imag doubles)
|
|
99
|
+
samples.each_with_index do |sample, i|
|
|
100
|
+
input.put_float64(i * COMPLEX_SIZE, sample.real)
|
|
101
|
+
input.put_float64((i * COMPLEX_SIZE) + 8, sample.imag)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Create and execute plan
|
|
105
|
+
plan = fftw_plan_dft_1d(n, input, output, FFTW_FORWARD, FFTW_ESTIMATE)
|
|
106
|
+
raise "Failed to create FFTW plan" if plan.null?
|
|
107
|
+
|
|
108
|
+
begin
|
|
109
|
+
fftw_execute(plan)
|
|
110
|
+
|
|
111
|
+
# Read output into Ruby Complex array
|
|
112
|
+
result = Array.new(n) do |i|
|
|
113
|
+
real = output.get_float64(i * COMPLEX_SIZE)
|
|
114
|
+
imag = output.get_float64((i * COMPLEX_SIZE) + 8)
|
|
115
|
+
Complex(real, imag)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
result
|
|
119
|
+
ensure
|
|
120
|
+
fftw_destroy_plan(plan)
|
|
121
|
+
end
|
|
122
|
+
ensure
|
|
123
|
+
fftw_free(input)
|
|
124
|
+
fftw_free(output)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Perform inverse FFT on complex spectrum
|
|
129
|
+
#
|
|
130
|
+
# @param [Array<Complex>] spectrum Input complex spectrum
|
|
131
|
+
# @return [Array<Complex>] IFFT result (time domain samples)
|
|
132
|
+
# @raise [RuntimeError] if FFTW3 is not available
|
|
133
|
+
def self.backward(spectrum)
|
|
134
|
+
raise "FFTW3 library not available: #{load_error}" unless available?
|
|
135
|
+
|
|
136
|
+
n = spectrum.length
|
|
137
|
+
return [] if n.zero?
|
|
138
|
+
|
|
139
|
+
# Allocate input and output arrays
|
|
140
|
+
input = fftw_malloc(n * COMPLEX_SIZE)
|
|
141
|
+
output = fftw_malloc(n * COMPLEX_SIZE)
|
|
142
|
+
|
|
143
|
+
begin
|
|
144
|
+
# Copy spectrum to input array
|
|
145
|
+
spectrum.each_with_index do |sample, i|
|
|
146
|
+
input.put_float64(i * COMPLEX_SIZE, sample.real)
|
|
147
|
+
input.put_float64((i * COMPLEX_SIZE) + 8, sample.imag)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Create and execute plan
|
|
151
|
+
plan = fftw_plan_dft_1d(n, input, output, FFTW_BACKWARD, FFTW_ESTIMATE)
|
|
152
|
+
raise "Failed to create FFTW plan" if plan.null?
|
|
153
|
+
|
|
154
|
+
begin
|
|
155
|
+
fftw_execute(plan)
|
|
156
|
+
|
|
157
|
+
# Read output and normalize (FFTW doesn't normalize IFFT)
|
|
158
|
+
result = Array.new(n) do |i|
|
|
159
|
+
real = output.get_float64(i * COMPLEX_SIZE) / n
|
|
160
|
+
imag = output.get_float64((i * COMPLEX_SIZE) + 8) / n
|
|
161
|
+
Complex(real, imag)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
result
|
|
165
|
+
ensure
|
|
166
|
+
fftw_destroy_plan(plan)
|
|
167
|
+
end
|
|
168
|
+
ensure
|
|
169
|
+
fftw_free(input)
|
|
170
|
+
fftw_free(output)
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
data/lib/rtlsdr/version.rb
CHANGED
data/lib/rtlsdr.rb
CHANGED
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "rtlsdr/version"
|
|
4
4
|
require_relative "rtlsdr/ffi"
|
|
5
|
+
require_relative "rtlsdr/fftw"
|
|
5
6
|
require_relative "rtlsdr/device"
|
|
6
7
|
require_relative "rtlsdr/errors"
|
|
7
8
|
require_relative "rtlsdr/dsp"
|
|
9
|
+
require_relative "rtlsdr/dsp/filter"
|
|
10
|
+
require_relative "rtlsdr/demod"
|
|
8
11
|
require_relative "rtlsdr/scanner"
|
|
9
12
|
|
|
10
13
|
# Ruby bindings for RTL-SDR (Software Defined Radio) devices
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rtlsdr
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.2.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- joshfng
|
|
@@ -49,10 +49,13 @@ files:
|
|
|
49
49
|
- examples/spectrum_analyzer.rb
|
|
50
50
|
- exe/rtlsdr
|
|
51
51
|
- lib/rtlsdr.rb
|
|
52
|
+
- lib/rtlsdr/demod.rb
|
|
52
53
|
- lib/rtlsdr/device.rb
|
|
53
54
|
- lib/rtlsdr/dsp.rb
|
|
55
|
+
- lib/rtlsdr/dsp/filter.rb
|
|
54
56
|
- lib/rtlsdr/errors.rb
|
|
55
57
|
- lib/rtlsdr/ffi.rb
|
|
58
|
+
- lib/rtlsdr/fftw.rb
|
|
56
59
|
- lib/rtlsdr/scanner.rb
|
|
57
60
|
- lib/rtlsdr/version.rb
|
|
58
61
|
homepage: https://github.com/joshfng/rtlsdr
|
|
@@ -84,7 +87,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
84
87
|
- !ruby/object:Gem::Version
|
|
85
88
|
version: '0'
|
|
86
89
|
requirements: []
|
|
87
|
-
rubygems_version: 3.
|
|
90
|
+
rubygems_version: 3.6.9
|
|
88
91
|
specification_version: 4
|
|
89
92
|
summary: Ruby bindings for librtlsdr
|
|
90
93
|
test_files: []
|