1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 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 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
|
#--
# This file is part of Sonic Pi: http://sonic-pi.net
# Full project source: https://github.com/samaaron/sonic-pi
# License: https://github.com/samaaron/sonic-pi/blob/master/LICENSE.md
#
# Copyright 2013, 2014, 2015, 2016 by Sam Aaron (http://sam.aaron.name).
# All rights reserved.
#
# Permission is granted for use, copying, modification, and
# distribution of modified versions of this work as long as this
# notice is included.
#++
module SonicPi
module OSC
class OscEncode
# 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 http://opensoundcontrol.org for spec.
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_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
@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
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
tags << @literal_low_s
args_encoded << get_from_or_add_to_string_cache(arg)
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
"#{address}#{tags_encoded}#{args_encoded}"
end
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
private
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
def time_encoded(time)
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
end
class StreamOscEncode < OscEncode
def encode_single_message(address, args=[])
message = super
([message.length].pack(@literal_cap_n) << message).force_encoding(@literal_binary_str)
end
def encode_single_bundle(ts, address, args=[])
message = super
message.count.pack(@literal_cap_n) << message
end
end
end
end
|