Module: PWN::FFI::FFTW

Extended by:
Library
Defined in:
lib/pwn/ffi/fftw.rb

Overview

Thin single-precision FFTW3 binding (libfftw3f).

Used by PWN::SDR::* spectrum work (GQRX FFT snapshots, wideband energy detectors) when MHz-rate FFTs outgrow pure-Ruby DFT. Missing library degrades cleanly via .available? — callers keep pure-Ruby fallbacks.

No compile step at gem install; no shells. Plans are built with FFTW_ESTIMATE so first-call latency stays acceptable for REPL use.

Constant Summary collapse

FFTW_ESTIMATE =
(1 << 6)
FFTW_MEASURE =
0
FFTW_FORWARD =
-1
FFTW_BACKWARD =
1

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.load_errorObject (readonly)

Returns the value of attribute load_error.



33
34
35
# File 'lib/pwn/ffi/fftw.rb', line 33

def load_error
  @load_error
end

Class Method Details

.authorsObject

Author(s)

0day Inc. support@0dayinc.com



173
174
175
# File 'lib/pwn/ffi/fftw.rb', line 173

public_class_method def self.authors
  "AUTHOR(S):\n  0day Inc. <support@0dayinc.com>\n"
end

.available?Boolean

Supported Method Parameters

PWN::FFI::FFTW.available?

Returns:

  • (Boolean)


63
64
65
66
67
# File 'lib/pwn/ffi/fftw.rb', line 63

public_class_method def self.available?
  !@load_error && respond_to?(:fftwf_malloc, true)
rescue StandardError
  false
end

.cfft(opts = {}) ⇒ Object

Supported Method Parameters

spectrum = PWN::FFI::FFTW.cfft( iq: 'required - interleaved Array I/Q (even length)', n: 'optional - number of complex samples (default iq.length/2)', sign: 'optional - :forward (default) or :backward' ) Returns Array of [re, im] pairs length n.



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/pwn/ffi/fftw.rb', line 141

public_class_method def self.cfft(opts = {})
  raise 'ERROR: libfftw3f not available' unless available?

  iq = opts[:iq]
  n = (opts[:n] || (iq.length / 2)).to_i
  raise 'ERROR: n must be >= 1' if n < 1

  sign = opts[:sign] == :backward ? FFTW_BACKWARD : FFTW_FORWARD
  bytes = n * 2 * 4
  in_ptr = fftwf_malloc(bytes)
  out_ptr = fftwf_malloc(bytes)
  raise 'ERROR: fftwf_malloc failed' if in_ptr.null? || out_ptr.null?

  src = iq.first([2 * n, iq.length].min).map(&:to_f)
  padded = src + Array.new((2 * n) - src.length, 0.0)
  in_ptr.write_array_of_float(padded)

  plan = fftwf_plan_dft_1d(n, in_ptr, out_ptr, sign, FFTW_ESTIMATE)
  raise 'ERROR: fftwf_plan_dft_1d failed' if plan.null?

  fftwf_execute(plan)
  flat = out_ptr.read_array_of_float(n * 2)
  result = Array.new(n) { |i| [flat[2 * i], flat[(2 * i) + 1]] }

  fftwf_destroy_plan(plan)
  fftwf_free(in_ptr)
  fftwf_free(out_ptr)
  result
end

.helpObject

Display Usage for this Module



179
180
181
182
183
184
185
186
187
188
189
# File 'lib/pwn/ffi/fftw.rb', line 179

public_class_method def self.help
  puts "USAGE:
    #{self}.available?                         # => true/false
    #{self}.rfft(samples:, n: nil)             # real→complex, [[re,im],…]
    #{self}.rfft_magnitude(samples:, n: nil)   # |X[k]|
    #{self}.rfft_power_db(samples:, n:, floor: -120.0)
    #{self}.cfft(iq:, n: nil, sign: :forward)  # complex FFT

    #{self}.authors
  "
end

.rfft(opts = {}) ⇒ Object

Supported Method Parameters

spectrum = PWN::FFI::FFTW.rfft( samples: 'required - Array real input (length = n)', n: 'optional - FFT size (default samples.length; zero-pads/truncates)' ) Returns Array of [re, im] pairs, length n/2+1 (DC .. Nyquist).



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/pwn/ffi/fftw.rb', line 76

public_class_method def self.rfft(opts = {})
  raise 'ERROR: libfftw3f not available' unless available?

  samples = opts[:samples]
  n = (opts[:n] || samples.length).to_i
  raise 'ERROR: n must be >= 1' if n < 1

  in_ptr = fftwf_malloc(n * 4)
  out_bins = (n / 2) + 1
  out_ptr = fftwf_malloc(out_bins * 2 * 4)
  raise 'ERROR: fftwf_malloc failed' if in_ptr.null? || out_ptr.null?

  # zero + copy
  in_ptr.write_array_of_float(Array.new(n, 0.0))
  src = samples.first([n, samples.length].min).map(&:to_f)
  in_ptr.write_array_of_float(src + Array.new(n - src.length, 0.0)) if src.length < n
  in_ptr.write_array_of_float(src) if src.length == n

  plan = fftwf_plan_dft_r2c_1d(n, in_ptr, out_ptr, FFTW_ESTIMATE)
  raise 'ERROR: fftwf_plan_dft_r2c_1d failed' if plan.null?

  fftwf_execute(plan)
  flat = out_ptr.read_array_of_float(out_bins * 2)
  result = Array.new(out_bins) { |i| [flat[2 * i], flat[(2 * i) + 1]] }

  fftwf_destroy_plan(plan)
  fftwf_free(in_ptr)
  fftwf_free(out_ptr)
  result
end

.rfft_magnitude(opts = {}) ⇒ Object

Supported Method Parameters

mag = PWN::FFI::FFTW.rfft_magnitude( samples: 'required - Array', n: 'optional - FFT size' ) Returns Array of |X| for k=0..n/2.



114
115
116
# File 'lib/pwn/ffi/fftw.rb', line 114

public_class_method def self.rfft_magnitude(opts = {})
  rfft(opts).map { |re, im| Math.sqrt((re * re) + (im * im)) }
end

.rfft_power_db(opts = {}) ⇒ Object

Supported Method Parameters

power_db = PWN::FFI::FFTW.rfft_power_db( samples: 'required - Array', n: 'optional - FFT size', floor: 'optional - dB floor for zeros (default -120.0)' ) Returns Array of 20*log10(|X|) with a noise floor.



126
127
128
129
130
131
# File 'lib/pwn/ffi/fftw.rb', line 126

public_class_method def self.rfft_power_db(opts = {})
  floor = (opts[:floor] || -120.0).to_f
  rfft_magnitude(opts).map do |m|
    m.positive? ? (20.0 * Math.log10(m)) : floor
  end
end