class Digiproc::Strategies::PSK

Attributes

carrier_frequency[RW]
carrier_signal_eqn[RW]
coded_signal[RW]
coding_strategy[RW]
m[RW]
modulating_signal[RW]
phase_shift_eqn[RW]
phase_signal[RW]
pulse_length[RW]
signal_to_phase[RW]

Public Class Methods

new(carrier_signal_eqn: ->(a, fo, t, theta){ a * Math.cos(2*Math::PI * fo * t + theta) } click to toggle source

Initialize Args

modulating_signal

Array; each element is a “symbol” (should be a string reprisenting bits), where a symnbol is a number of bits symbolizing a single character (ie symbol means character)

carrier_signal_eqn (optional)

Lambda (or Proc) default value is: ->(a, fo, t, theta){ a * Math.cos(2*Math::PI * fo * t + theta) }

coding_strategy (optional)

Protocol, see Digiproc::Strategies::XORDifferentialEncodingZeroAngleStrategy, default value is nil

carrier_frequency (optional)::Numeric (in Hz), defaults to 10000

pulse_length (optional)

Float (in seconds), defaults to 0.00015 determines how long to apply a phase shift

# File lib/strategies/modulation/phase_shift_keying_strategy.rb, line 12
def initialize(carrier_signal_eqn: ->(a, fo, t, theta){ a * Math.cos(2*Math::PI * fo * t + theta) }, modulating_signal: ,coding_strategy: nil, carrier_frequency: 10000, pulse_length: 0.00015)
    @carrier_signal_eqn, @modulating_signal, @coding_strategy, @carrier_frequency, @pulse_length = carrier_signal_eqn, modulating_signal, coding_strategy, carrier_frequency, pulse_length
    @m = 2 ** modulating_signal.first.length 
    if coding_strategy.nil?
        @phase_shift_eqn = ->(i){ (2.0 * Math::PI * (i)) / @m }
        @coded_signal = @modulating_signal
    else
        @phase_shift_eqn = coding_strategy.phase_shift_eqn(@m)
        @coded_signal = coding_strategy.encode modulating_signal, @m, "0"
    end
    @signal_to_phase = {}
    for i in 0...@m do
        @signal_to_phase[i.to_s] = @phase_shift_eqn[i]
    end

    @phase_signal = @coded_signal.map{ |coded_symbol| @phase_shift_eqn[coded_symbol.to_i(2)] }

end

Public Instance Methods

decode() click to toggle source

Apply inverse of the PSK strategy to decode the PSK signal

# File lib/strategies/modulation/phase_shift_keying_strategy.rb, line 47
def decode
    eqn = coding_strategy.nil? ? decode_eqn : coding_strategy.phase_to_sym(@m)
    sym = @phase_signal.map{ |phase| eqn[phase] }
    sym = coding_strategy.decode(sym) if not coding_strategy.nil?
    return sym
end
output(a: 1) click to toggle source

Input Args

a (optional)

Numeric for amplitude of the signal (default value = 1)

Return Digiproc::AnalogSignal of the Phase Shift Keyed signal

# File lib/strategies/modulation/phase_shift_keying_strategy.rb, line 35
def output(a: 1)
    eqn = Proc.new do |t|
        theta_index = (t.to_f / @pulse_length.to_f).floor
        @carrier_signal_eqn[a, @carrier_frequency, t, @phase_signal[theta_index]]
    end
    sample_rate = 0.01 / @carrier_frequency
    size = @pulse_length.to_f * @phase_signal.length / sample_rate
    Digiproc::AnalogSignal.new(eqn: eqn, sample_rate: sample_rate, size: size)
end
reciever_decode() click to toggle source

Based off of reciever Figure 4-15 INTRODUCTION TO DIGITAL COMMUNICATIONS, ZIEMER, PETERSON pg 237 NOTE: SUBOPTIMUM DETECTOR, optimum detector referenced on the above page, different reference This simulates a real-life reciever recieving the siglans and decoding them

# File lib/strategies/modulation/phase_shift_keying_strategy.rb, line 58
def reciever_decode
    recieved = output.to_ds.fft.magnitude
    ts = 1 / (@carrier_frequency / 0.01)
    max_freq = 1.0 / ts
    normalized_target = @carrier_frequency / max_freq.to_f
    bw = (0.57 / ts) / max_freq.to_f
    plt = Digiproc::QuickPlot
    factory = Digiproc::Factories::FilterFactory
    bpf = factory.filter_for(type: "bandpass", wo: normalized_target * Math::PI * 2, bw: bw * Math::PI * 2, transition_width: 0.0001, stopband_attenuation: 80)
    bpf.fft.calculate_at_size(2 ** 16)

    filtered = (bpf.fft * output.to_ds.fft(2** 16)).ifft.map(&:real)
    sample_interval = @pulse_length / (0.01 / @carrier_frequency)
    output = []
    for i in (sample_interval + 1).to_i...filtered.length do 
        output << (filtered[i] * filtered[i-sample_interval]) 
    end
    lpf = factory.filter_for(type: "lowpass", wc: 2 * Math::PI / 4, transition_width: 0.0001, stopband_attenuation: 80)
    outft = Digiproc::FFT.new(time_data: output, size: 2 ** 16)
    
    # TODO FIND WHERE SIGNAL BEGINS AND OUTPUT THE SIGNAL BASED OFF OF
    # SIGNAL SIZE. ALSO, THERE SHOULD BE A RECIEVER METHOD OR SEPERATE CLASS
    # WHICH OUPUTS THE RAW DATA OF THE RECIEVERS
    data = (outft * lpf.fft).ifft.map(&:real)
    all_out = @phase_signal.size > 200 ? data[55000,  200 * sample_interval].concat(data[0, sample_interval * (@phase_signal.size - 200)]) : data[55000,  @phase_signal.size * sample_interval]
    final_out = []
    for i in 0...all_out.length do
        final_out << all_out[i] if (i + 10) %  sample_interval == 0 
    end
    final_out
end

Private Instance Methods

decode_eqn() click to toggle source
# File lib/strategies/modulation/phase_shift_keying_strategy.rb, line 93
def decode_eqn
    ->(phase){ (phase * @m / (2.0 * Math::PI))}
end