lib/cw/read.rb

Summary

Maintainability
C
1 day
Test Coverage
# encoding: utf-8

#require 'wavefile'
require 'coreaudio'

module CW

  SAMPLE_RATE = 44100
  MAGNITUDE_CUTOFF = 50000 #  200000
  N = 441
  TONE = 882
  START = 0
  VALUE = 1
  BLANKING = 2
  FILTERED = 3
  PREV = 4
  PERIOD = 1
  AVG = 2
  class Read

    def n ; N ; end
    def k ; (0.5 + ((n * @tone / @sample_rate))).to_i ; end
    def w ; ((2 * Math::PI) / n) * k ; end
    def cosine ; Math.cos(w) ; end
    def coeff ; 2 * cosine ; end
    def n_delay_ms ;  n * 1000/SAMPLE_RATE; end
    def print ; @print ; end

    def initialize(filename)
      @tone = TONE
      @mag_max = 0
      @mag_min = 999999999
      @sample_rate = SAMPLE_RATE
      @n_val = n
      @coeff = coeff
      @n_delay_ms = n_delay_ms
      @filename = filename
      @code = []
      @q1 = 0
      @q2 = 0
      @magnitude_set_point = 10000
      @magnitude_set_point_low = @magnitude_set_point
      @wpm = 40
      @noise_blanking_ms = 6
      @last_start_time = 0
      @state = Array.new(5)
      @high = Array.new(3)
      @low = Array.new(2)
      @queue = Queue.new
      @cw_encoding = Encoding.new
      @print = Print.new
      @state[START] = 0
      @state[VALUE] = :low
      @state[BLANKING] = false
      @state[FILTERED] = false
      @state[PREV] = false
      @high[START] = 0
      @high[PERIOD] = 0
      @high[AVG] = 120
      @low[START] = 0
      @low[PERIOD] = 0
      @low[AVG] = 0
      @millisecs = 0
      @last = 0
      @need_space = false
      puts "@n_val  #{@n_val}"
      puts "@coeff  #{@coeff}"
      puts "@n_delay_ms #{@n_delay_ms}"
      input
    end

    def open_sound_device
      soundflower = nil
      CoreAudio.devices.each do |device|
        if device.name == 'Soundflower (2ch)'
          soundflower = device
        end
      end
      @buf_in = soundflower.input_buffer(44100)
      @buf_in.start
    end

    def input
      dly = @n_delay_ms
      open_sound_device
      nval = @n_val
      buf = @buf_in
      bufs = nil
      block_size = nval * 28
      thr = Thread.fork do
        loop do
          @queue.push buf.read(block_size)
        end
      end
      loop do
        loop do
          bufs = @queue.pop
          break if @queue.empty?
          sleep 0.001
        end
        count = 0
        28.times do
          nval.times do
            temp = bufs[count] + bufs[count + 1]
#            puts temp
            calc_coeff temp
            count += 2
          end
          @millisecs += dly
#          @millisecs += dly
          per_block_processing
          calc_real_state
          calc_filtered_state
          decode_signal
        end
      end
      @buf_in.stop
      $stdout.puts "done."
    end

    def dbg_print message
      if @millisecs > @last
        @last = @millisecs + 1000
        puts
        puts "  " + message
        @mag_min = 999999999
        @mag_max = 0
      end
    end

    def per_block_processing
      @magnitude = (@q1 * @q1) + (@q2 * @q2) - @q1 * @q2 * @coeff
      @magnitude = @magnitude.to_i / 1000000
      @magnitude = 0 if @magnitude < MAGNITUDE_CUTOFF
      #      if @magnitude >= 1000000000000.0
      #        @magnitude = Math.sqrt(magnitude_squared).to_i
      #      else
      #        @magnitude = 0
      #      end
      @q1, @q2 = 0, 0;
      #      p @magnitude
    end

    def magnitude_filter
      if(@magnitude > @magnitude_set_point_low)
        @magnitude_set_point = (@magnitude_set_point + ((@magnitude - @magnitude_set_point) / 6))  # moving average filter
      else
        @magnitude_set_point = @magnitude_set_point_low
      end
      #      dbg_print "@magnitude: #{@magnitude.to_s}\n  @magnitude_set_point: #{@magnitude_set_point.to_s}\n  @magnitude_set_point_low = #{@magnitude_set_point_low}"

      @mag_max = @magnitude if @magnitude > @mag_max
      @mag_min = @magnitude if @magnitude < @mag_min

      #      dbg_print "@magnitude: #{@magnitude.to_s}\n" +
      #                "  @mag_max: #{@mag_max.to_s}\n" +
      #                "  @mag_min: #{@mag_min.to_s}\n"
    end

    def calc_real_state
      @state[VALUE] =
        (@magnitude > (@magnitude_set_point * 0.6)) ?
          :high : :low
    end

    def calc_filtered_state
      if real_state_change?
        reset_noise_blanker
      end

      store_real_state

      if @state[BLANKING]
        if noise_blanked
          @state[FILTERED] = true
          if(@state[VALUE] == :high)
            @high[START] = @millisecs
            @high_mag = @magnitude
            #          dbg_print "high: #{@high_mag}\n  low: #{@low_mag}\n  set point: #{@magnitude_set_point}"
            @low[PERIOD] = (@millisecs - @low[START])
          else
            @low[START] = @millisecs;
            @low_mag = @magnitude
            @high[PERIOD] = @millisecs -  @high[START]
            if @high[PERIOD] < (2 * @high[AVG])
              @high[AVG] = ((@high[PERIOD] + @high[AVG] + @high[AVG]) / 3)  # now we know avg dit time ( rolling 3 avg)
            elsif(@high[PERIOD] > (5 * @high[AVG]))
              @high[AVG] = @high[PERIOD] + @high[AVG];     # if speed decrease fast ..
            end
          end
        end
      end
    end

    def decode_signal
      if(@state[FILTERED])
        @state[FILTERED] = false
        @need_space = true
        #dbg_print "wpm #{@wpm.to_s}\nhigh period: #{@high[PERIOD]}\nhigh avg: #{@high[AVG]}"
        if(@state[VALUE] == :low) #  we did end a HIGH
          if high_avg_compare?(@high[PERIOD], 0.6, 2.0)
            #  0.6 filter out false dits
            @code << :dot
#            $stdout.print '.'
          end
          if high_avg_compare?(@high[PERIOD], 2.0, 6.0)
            @code << :dash
#            $stdout.print '_'
            if @millisecs % 10 == 0
              @wpm = (@wpm + (1200 / ((@high[PERIOD]) / 3))) / 2;  # the most precise we can do ;o)
              # dbg_print "high #{@high[PERIOD]}"
            end
          end
        else # we did end a LOW
          @need_space = false
          if high_avg_compare?(@low[PERIOD], 2.0, 4.8) # letter space
            print_char
          elsif(high_avg_compare?(@low[PERIOD], 4.8, 6.0)) # word space
            print_char
            $stdout.print ' '
          end
        end
      end
#      dbg_print "millisecs #{@millisecs}"
      if @need_space
        if high_avg_compare?(@millisecs - @low[START], 6.0, 10)
          @need_space = false
          if @state[BLANKING] = false
          end
          print_char
          $stdout.print ' '
        end
      end
    end

    def high_avg_compare?(period, avg_x_low, avg_x_high)
      (period <= (@high[AVG] * avg_x_high).to_i) &&
        (period >= (@high[AVG] * avg_x_low).to_i)
    end

    def noise_blanked
      if((@millisecs - @state[START]) > @noise_blanking_ms)
        @state[BLANKING] = false
        return true
      end
    end

    def print_space
      @code = [:space]
      print_char
    end

    def calc_coeff(data)
      q0 = @coeff * @q1 - @q2 + data
      @q2, @q1 = @q1, q0
    end

    def reset_noise_blanker
      @state[BLANKING] = true
      @state[START] = @millisecs
    end

    def real_state_change?
      #      if @real_state != @real_state_prev
      #        $stdout.print 'hi' if @real_state == true
      #        $stdout.print 'low' if @real_state == false
      #      end
      @state[VALUE] != @state[PREV]
    end

    def store_real_state
      @state[PREV] = @state[VALUE]
    end

#    def matched_char
#      @cw_encoding.fetch_char @code
#    end

    def print_char
      char = @cw_encoding.fetch_char @code
      @code = []
      print.optimum char
      #      if char == ' '
      #        puts "\n  high: #{@high_mag}\n"
      #      end

      if false # char == '*'
        #        @wpm -= 5
        @state[VALUE] = false
        @state[FILTERED] = false
        @state[PREV] = false
        #        @awaiting_space = false
        @low[PERIOD] = 0

        @high[PERIOD] = 0
        @high[PREV] = 0
        @high[AVG] = 0
        @low[START] = 0
        @millisecs = 0
        @last = 0
      end
    end

#    def print_char
      #      index = 0
      #      dash_jump = 64
      #      if @code.length < 6
      #        @code.each do |ele|
      ##          puts "ele: #{ele}"
      #          dash_jump = dash_jump / 2
      #          index = index + ((ele == :dot) ? 1 : dash_jump)
      #        end
      #        char = @char_lookup[index].to_s
      ##        char = @cw_encoding.fetch_char(@code) if char == '*'
      #      #      if char == ' '
      #      #        puts "\n  high: #{@high_mag}\n"
      #      #      end
      #      else
#      char = @cw_encoding.fetch_char(@code)
#    end

#      if false # char == '*'
#        #        @wpm -= 5
#        @state[VALUE] = false
#        @state[FILTERED] = false
#        @state[PREV] = false
#        #        @awaiting_space = false
#        @low[PERIOD] = 0
#
#        @high[PERIOD] = 0
#        @high[PREV] = 0
#        @high[AVG] = 0
#        @low[START] = 0
#        @millisecs = 0
#        @last = 0
#      end
#      print.optimum char
#      @code = []
#    end
  end
end