class SonicPi::OSC::OscEncode

Public Class Methods

new(use_cache = false, cache_size=1000) click to toggle source

Apologies for the density of this code - I've inlined a lot of the code to reduce method dispatch overhead and to increase efficiency. See opensoundcontrol.org for spec.

# File lib/fast_osc/pure_ruby_fallback_encode.rb, line 21
def initialize(use_cache = false, cache_size=1000)
  @literal_binary_str = "BINARY".freeze
  @literal_cap_n = 'N'.freeze
  @literal_cap_n2 = 'N2'.freeze
  @literal_low_f = 'f'.freeze
  @literal_low_i = 'i'.freeze
  @literal_low_g = 'g'.freeze
  @literal_low_s = 's'.freeze
  @literal_low_b = 'b'.freeze
  @literal_empty_str = ''.freeze
  @literal_str_encode_regexp = /\000.*\z/
  @literal_str_pad = "\000".freeze
  @literal_two_to_pow_2 = 2 ** 32
  @literal_magic_time_offset = 2208988800
  @ascii_encoding = Encoding.find("ASCII-8BIT")

  @use_cache = use_cache
  @integer_cache = {}
  @string_cache = {}
  @float_cache = {}
  @cache_size = cache_size

  @num_cached_integers = 0
  @num_cached_floats = 0
  @num_cached_strings = 0

  @bundle_header = get_from_or_add_to_string_cache("#bundle")
end

Public Instance Methods

encode_single_bundle(ts, address, args=[]) click to toggle source
# File lib/fast_osc/pure_ruby_fallback_encode.rb, line 115
def encode_single_bundle(ts, address, args=[])
  message = encode_single_message(address, args)
  message_encoded = [message.size].pack(@literal_cap_n) << message
  "#{@bundle_header}#{time_encoded(ts)}#{message_encoded}"
end
encode_single_message(address, args=[]) click to toggle source
# File lib/fast_osc/pure_ruby_fallback_encode.rb, line 50
def encode_single_message(address, args=[])
  args_encoded, tags = String.new(""), String.new(",")

  # inlining this method was not faster surprisingly
  address = get_from_or_add_to_string_cache(address)

  args.each do |arg|
    case arg
    when Integer
      tags << @literal_low_i

      if @use_cache
        if cached = @integer_cache[arg]
          args_encoded << cached
        else
          res = [arg].pack(@literal_cap_n)
          if @num_cached_integers < @cache_size
            @integer_cache[arg] = res
            @num_cached_integers += 1
            # log "caching integer #{arg}"
          end
          args_encoded << res
        end
      else
        args_encoded << [arg].pack(@literal_cap_n)
      end
    when Float, Rational
      arg = arg.to_f
      tags << @literal_low_f

      if @use_cache
        if cached = @float_cache[arg]
          args_encoded << cached
        else
          res = [arg].pack(@literal_low_g)
          if @num_cached_floats < @cache_size
            @float_cache[arg] = res
            @num_cached_floats += 1
            # log "caching float #{arg}"
          end
          args_encoded << res
        end
      else
        args_encoded << [arg].pack(@literal_low_g)
      end
    when String, Symbol
      arg = arg.to_s
      if(arg.encoding == @ascii_encoding)
        tags <<  @literal_low_b
        args_encoded << [arg.bytesize].pack(@literal_cap_n)
        args_encoded << [arg].pack("Z#{arg.bytesize + 4 - (arg.bytesize % 4)}")
      else
        tags << @literal_low_s
        args_encoded << get_from_or_add_to_string_cache(arg)
      end
    else
      raise "Unknown arg type to encode: #{arg.inspect}"
    end
  end

  tags_encoded = get_from_or_add_to_string_cache(tags)
  # Address here needs to be a new string, not sure why
  String.new("#{address}#{tags_encoded}#{args_encoded}").force_encoding("ASCII-8BIT")
end

Private Instance Methods

get_from_or_add_to_string_cache(s) click to toggle source
# File lib/fast_osc/pure_ruby_fallback_encode.rb, line 122
def get_from_or_add_to_string_cache(s)
  if cached = @string_cache[s]
    return cached
  else
    # This makes a null padded string rounded up to the nearest
    # multiple of four
    size = s.bytesize
    res = [s].pack("Z#{size + 4 - (size % 4)}")
    if @num_cached_strings < @cache_size
      # only cache the first @cache_size strings to avoid a memory
      # memory leak.
      @string_cache[s] = res
      @num_cached_strings += 1
      # log "caching string #{s}"
    end
    return  res
  end
end
time_encoded(time) click to toggle source
# File lib/fast_osc/pure_ruby_fallback_encode.rb, line 141
def time_encoded(time)
  return "\x00\x00\x00\x00\x00\x00\x00\x01" if time.nil?

  t1, fr = (time.to_f + @literal_magic_time_offset).divmod(1)

  t2 = (fr * @literal_two_to_pow_2).to_i
  [t1, t2].pack(@literal_cap_n2)
end