Module: RTLSDR::DSP
- Defined in:
- lib/rtlsdr/dsp.rb
Overview
Digital Signal Processing utilities for RTL-SDR
The DSP module provides essential signal processing functions for working with RTL-SDR sample data. It includes utilities for converting raw IQ data to complex samples, calculating power spectra, performing filtering operations, and extracting signal characteristics.
All methods are designed to work with Ruby's Complex number type and standard Array collections, making them easy to integrate into Ruby applications and pipelines.
Features:
- IQ data conversion to complex samples
- Power spectrum analysis with windowing
- Peak detection and frequency estimation
- DC removal and filtering
- Magnitude and phase extraction
- Average power calculation
Class Method Summary collapse
-
.average_power(samples) ⇒ Float
Calculate average power of complex samples.
-
.estimate_frequency(samples, sample_rate) ⇒ Float
Estimate frequency using zero-crossing detection.
-
.find_peak(power_spectrum) ⇒ Array<Integer, Float>
Find peak power and frequency bin in spectrum.
-
.iq_to_complex(data) ⇒ Array<Complex>
Convert raw IQ data to complex samples.
-
.magnitude(samples) ⇒ Array<Float>
Extract magnitude from complex samples.
-
.phase(samples) ⇒ Array<Float>
Extract phase from complex samples.
-
.power_spectrum(samples, window_size = 1024) ⇒ Array<Float>
Calculate power spectral density.
-
.remove_dc(samples, alpha = 0.995) ⇒ Array<Complex>
Remove DC component using high-pass filter.
Class Method Details
.average_power(samples) ⇒ Float
Calculate average power of complex samples
Computes the mean power (magnitude squared) across all samples. This is useful for signal strength measurements and AGC calculations.
95 96 97 98 99 100 |
# File 'lib/rtlsdr/dsp.rb', line 95 def self.average_power(samples) return 0.0 if samples.empty? total_power = samples.reduce(0.0) { |sum, sample| sum + sample.abs2 } total_power / samples.length end |
.estimate_frequency(samples, sample_rate) ⇒ Float
Estimate frequency using zero-crossing detection
Provides a rough frequency estimate by counting zero crossings in the magnitude signal. This is a simple method that works reasonably well for single-tone signals but may be inaccurate for complex signals.
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 |
# File 'lib/rtlsdr/dsp.rb', line 182 def self.estimate_frequency(samples, sample_rate) return 0.0 if samples.length < 2 magnitudes = magnitude(samples) zero_crossings = 0 (1...magnitudes.length).each do |i| if (magnitudes[i - 1] >= 0 && magnitudes[i].negative?) || (magnitudes[i - 1].negative? && magnitudes[i] >= 0) zero_crossings += 1 end end # Frequency = (zero crossings / 2) / time_duration time_duration = samples.length.to_f / sample_rate (zero_crossings / 2.0) / time_duration end |
.find_peak(power_spectrum) ⇒ Array<Integer, Float>
Find peak power and frequency bin in spectrum
Locates the frequency bin with maximum power in a power spectrum. Returns both the bin index and the power value at that bin.
113 114 115 116 117 118 119 |
# File 'lib/rtlsdr/dsp.rb', line 113 def self.find_peak(power_spectrum) return [0, 0.0] if power_spectrum.empty? max_power = power_spectrum.max max_index = power_spectrum.index(max_power) [max_index, max_power] end |
.iq_to_complex(data) ⇒ Array<Complex>
Convert raw IQ data to complex samples
Converts raw 8-bit IQ data from RTL-SDR devices to Ruby Complex numbers. The RTL-SDR outputs unsigned 8-bit integers centered at 128, which are converted to floating point values in the range [-1.0, 1.0].
47 48 49 50 51 52 53 54 55 |
# File 'lib/rtlsdr/dsp.rb', line 47 def self.iq_to_complex(data) samples = [] (0...data.length).step(2) do |i| i_sample = (data[i] - 128) / 128.0 q_sample = (data[i + 1] - 128) / 128.0 samples << Complex(i_sample, q_sample) end samples end |
.magnitude(samples) ⇒ Array<Float>
Extract magnitude from complex samples
Calculates the magnitude (absolute value) of each complex sample. This converts I+jQ samples to their envelope/amplitude values.
152 153 154 |
# File 'lib/rtlsdr/dsp.rb', line 152 def self.magnitude(samples) samples.map(&:abs) end |
.phase(samples) ⇒ Array<Float>
Extract phase from complex samples
Calculates the phase angle (argument) of each complex sample in radians. The phase represents the angle between the I and Q components.
166 167 168 |
# File 'lib/rtlsdr/dsp.rb', line 166 def self.phase(samples) samples.map { |s| Math.atan2(s.imag, s.real) } end |
.power_spectrum(samples, window_size = 1024) ⇒ Array<Float>
Calculate power spectral density
Computes a basic power spectrum from complex samples using windowing. This applies a Hanning window to reduce spectral leakage and then calculates the power (magnitude squared) for each sample. A proper FFT implementation would require an external library.
70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/rtlsdr/dsp.rb', line 70 def self.power_spectrum(samples, window_size = 1024) return [] if samples.length < window_size windowed_samples = samples.take(window_size) # Apply Hanning window windowed_samples = windowed_samples.each_with_index.map do |sample, i| window_factor = 0.5 * (1 - Math.cos(2 * Math::PI * i / (window_size - 1))) sample * window_factor end # Simple magnitude calculation (real FFT would require external library) windowed_samples.map { |s| ((s.real**2) + (s.imag**2)) } end |
.remove_dc(samples, alpha = 0.995) ⇒ Array<Complex>
Remove DC component using high-pass filter
Applies a simple first-order high-pass filter to remove DC bias from the signal. This is useful for RTL-SDR devices which often have a DC offset in their I/Q samples.
132 133 134 135 136 137 138 139 140 |
# File 'lib/rtlsdr/dsp.rb', line 132 def self.remove_dc(samples, alpha = 0.995) return samples if samples.empty? filtered = [samples.first] (1...samples.length).each do |i| filtered[i] = samples[i] - samples[i - 1] + (alpha * filtered[i - 1]) end filtered end |