class SonicPi::OSC::OscDecode

Public Class Methods

new(use_cache = false, cache_size=1000) click to toggle source
# File lib/fast_osc/pure_ruby_fallback_decode.rb, line 18
def initialize(use_cache = false, cache_size=1000)
  @float_cache = {}
  @integer_cache = {}
  @cache_size = cache_size

  @num_cached_integers = 0
  @num_cached_floats = 0
  @string_terminator = "\x00".freeze
  @bundle_header = "#bundle\x00".freeze
  @args = []
  @i_tag = "i".freeze
  @f_tag = "f".freeze
  @s_tag = "s".freeze
  @d_tag = "d".freeze
  @h_tag = "h".freeze
  @b_tag = "b".freeze

  @cap_n = 'N'.freeze
  @cap_g = 'G'.freeze
  @low_g = 'g'.freeze
  @q_lt = 'q>'.freeze
  @binary_encoding = "BINARY".freeze

  @literal_magic_time_offset = 2208988800
  @literal_cap_n = 'N'.freeze
end

Public Instance Methods

decode(m, output=[]) click to toggle source
# File lib/fast_osc/pure_ruby_fallback_decode.rb, line 128
def decode(m, output=[])
  tt = parse_timetag(m)
  raw_msgs, raw_bundles = parse_bundles(m)

  output << [tt, raw_msgs.map {|m| self.decode_single_message(m) }]

  if raw_bundles.any?
    output << raw_bundles[1..-1]
    self.decode(raw_bundles.first, output)
  else
    # for compatibility with the C Extension API
    return output.reject {|msg| msg.empty? }.reverse
  end
end
decode_single_message(m) click to toggle source
# File lib/fast_osc/pure_ruby_fallback_decode.rb, line 45
def decode_single_message(m)
  ## Note everything is inlined here for effienciency to remove the
  ## cost of method dispatch. Apologies if this makes it harder to
  ## read & understand. See http://opensoundcontrol.org for spec.

  m.force_encoding(@binary_encoding)

  args, idx = [], 0

  # Get OSC address e.g. /foo
  orig_idx = idx
  idx = m.index(@string_terminator, orig_idx)
  address, idx =  m[orig_idx...idx], idx + 1 + ((4 - ((idx + 1) % 4)) % 4)

  sep, idx = m[idx], idx + 1

  # Let's see if we have some args..
  if sep == ?,

    # Get type tags
    orig_idx = idx
    idx = m.index(@string_terminator, orig_idx)
    tags, idx = m[orig_idx...idx], idx + 1 + ((4 - ((idx + 1) % 4)) % 4)

    tags.each_char do |t|
      case t
      when @i_tag
        # int32
        raw = m[idx, 4]
        arg, idx = @integer_cache[raw], idx + 4

        unless arg
          arg = raw.unpack(@cap_n)[0]
          # Values placed inline for efficiency:
          # 2**32 == 4294967296
          # 2**31 - 1 == 2147483647
          arg -= 4294967296 if arg > 2147483647
          if @num_cached_integers < @cache_size
            @integer_cache[raw] = arg
            @num_cached_integers += 1
          end
        end
      when @f_tag
        # float32
        raw = m[idx, 4]
        arg, idx = @float_cache[raw], idx + 4
        unless arg
          arg = raw.unpack(@low_g)[0]
          if @num_cached_floats < @cache_size
            @float_cache[raw] = arg
            @num_cached_floats += 1
          end
        end
      when @s_tag
        # string
        orig_idx = idx
        idx = m.index(@string_terminator, orig_idx)
        arg, idx =  m[orig_idx...idx], idx + 1 + ((4 - ((idx + 1) % 4)) % 4)
        arg.force_encoding("UTF-8")
      when @d_tag
        # double64
        arg, idx = m[idx, 8].unpack(@cap_g)[0], idx + 8
      when @h_tag
        # int64
        arg, idx = m[idx, 8].unpack(@q_lt)[0], idx + 8
      when @b_tag
        # binary blob
        l = m[idx, 4].unpack(@cap_n)[0]
        idx += 4
        arg = m[idx, l]
        idx += l
        #Skip Padding
        idx += ((4 - (idx % 4)) % 4)
      else
        raise "Unknown OSC type #{t}"
      end

      args << arg
    end
  end
  return address, args
end

Private Instance Methods

is_bundle?(m) click to toggle source
# File lib/fast_osc/pure_ruby_fallback_decode.rb, line 181
def is_bundle?(m)
  m[0..7] == @bundle_header
end
parse_bundles(m) click to toggle source
# File lib/fast_osc/pure_ruby_fallback_decode.rb, line 155
def parse_bundles(m)
  raw_msgs = []
  raw_bundles = []

  if is_bundle?(m)
    rest_of_message = m[16..-1]

    while rest_of_message && rest_of_message.bytesize > 0
      first_element_length = rest_of_message[0..3].unpack(@literal_cap_n).first
      first_element = rest_of_message[4..(4 + first_element_length - 1)]

      if is_bundle?(first_element)
        raw_bundles << first_element
      else
        raw_msgs << first_element
      end

      rest_of_message = rest_of_message[(4 + first_element_length)..-1]
    end
  else
    raw_msgs << m
  end

  [raw_msgs, raw_bundles]
end
parse_timetag(m) click to toggle source
# File lib/fast_osc/pure_ruby_fallback_decode.rb, line 145
def parse_timetag(m)
  return nil unless is_bundle?(m)

  t1 = m[8..11].unpack(@literal_cap_n).first
  t1 = (t1 - @literal_magic_time_offset)

  t2 = m[12..15].unpack(@literal_cap_n).first
  Time.at(t1 + t2)
end