Class: PWN::SDR::Decoder::Tempest::Demod
- Inherits:
-
Object
- Object
- PWN::SDR::Decoder::Tempest::Demod
- Defined in:
- lib/pwn/sdr/decoder/tempest.rb
Overview
Streaming I/Q demodulator for Base.run_iq.
Instance Method Summary collapse
- #feed_iq(iq_samples, rate:, &emit) ⇒ Object
- #finish ⇒ Object
-
#initialize(opts = {}) ⇒ Demod
constructor
A new instance of Demod.
Constructor Details
#initialize(opts = {}) ⇒ Demod
Returns a new instance of Demod.
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/pwn/sdr/decoder/tempest.rb', line 39 def initialize(opts = {}) timing = PWN::SDR::Decoder::Tempest.resolve_timing(opts) @h_active = timing[:h_active] @h_total = timing[:h_total] @v_active = timing[:v_active] @v_total = timing[:v_total] @refresh = timing[:refresh] unless [@h_active, @h_total, @v_active, @v_total].all?(&:positive?) && @h_active <= @h_total && @v_active <= @v_total && @refresh.finite? && @refresh.positive? raise ArgumentError, 'invalid raster timing: active dimensions must fit positive totals and refresh' end @pixel_hz = (@h_total * @v_total * @refresh).to_f raise ArgumentError, 'pixel rate must be finite' unless @pixel_hz.finite? stamp = Time.now.strftime('%Y%m%d_%H%M%S') @pgm_path = opts[:out_path] || "/tmp/tempest_#{stamp}.pgm" @frames_want = opts[:continuous] ? nil : [opts[:frames].to_i, 1].max @mag_buf = [] @src_rate = nil @phase = 0.0 @pending_i = nil @written = 0 end |
Instance Method Details
#feed_iq(iq_samples, rate:, &emit) ⇒ Object
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/pwn/sdr/decoder/tempest.rb', line 64 def feed_iq(iq_samples, rate:, &emit) raise ArgumentError, 'sample rate must be finite and positive' unless rate.to_f.finite? && rate.to_f.positive? raise ArgumentError, 'sample rate changed during raster' if @src_rate && (@src_rate - rate.to_f).abs > 1e-9 @src_rate ||= rate.to_f iq_samples.each do |value| break if @frames_want && @written >= @frames_want if @pending_i.nil? @pending_i = value next end magnitude = Math.hypot(@pending_i, value) @pending_i = nil # Stateful sample-and-hold: transport chunk boundaries never reset # pixel phase. Retain at most one raster, even during upsampling. @phase += @pixel_hz while @phase >= @src_rate @phase -= @src_rate @mag_buf << magnitude drain(emit: emit) if @mag_buf.length == @h_total * @v_total break if @frames_want && @written >= @frames_want end end end |
#finish ⇒ Object
90 91 92 93 |
# File 'lib/pwn/sdr/decoder/tempest.rb', line 90 def finish drain(force: true) { path: @pgm_path, width: @h_active, height: @v_active, frames: @written } end |