|
#-- |
|
# 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 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. |
|
#++ |
|
require 'strscan' |
|
|
|
module SonicPi |
|
class OscDecode |
|
|
|
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 = Regexp.new("\x00") |
|
@tag_separator = ',' |
|
@args = [] |
|
@i_tag = "i" |
|
@f_tag = "f" |
|
@s_tag = "s" |
|
@d_tag = "d" |
|
@h_tag = "h" |
|
@b_tag = "b" |
|
|
|
@cap_n = 'N' |
|
@cap_g = 'G' |
|
@low_g = 'g' |
|
@low_l = 'l' |
|
@q_lt = 'q>' |
|
@binary_encoding = "BINARY" |
|
end |
|
|
|
def decode_single_message(msg) |
|
## 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 = StringScanner.new(msg) |
|
|
|
pack_string = "" |
|
|
|
# Get OSC address e.g. /foo |
|
m.skip_until(@string_terminator) |
|
m.pos = 4 * ((m.pos + 0.01)/4).ceil |
|
pack_string << "Z#{m.pos}" |
|
|
|
# Let's see if we have some args.. |
|
# check the next char to see if it's a comma |
|
if m.peek(1) == @tag_separator |
|
# Get type tags |
|
orig_idx = m.pos |
|
tags = m.scan_until(@string_terminator) |
|
m.pos = 4 * ((m.pos + 0.01)/4).ceil |
|
if tags.length > 1 |
|
pack_string << "Z#{m.pos - orig_idx}" |
|
end |
|
|
|
# skip the comma |
|
tags.each_char do |t| |
|
case t |
|
when @tag_separator |
|
# skip the comma |
|
nil |
|
when @string_terminator |
|
# skip the null bytes |
|
nil |
|
when @i_tag |
|
# int32 |
|
m.pos = m.pos+4 |
|
pack_string << @cap_n |
|
when @f_tag |
|
# float32 |
|
m.pos = m.pos+4 |
|
pack_string << @low_g |
|
when @s_tag |
|
# string |
|
orig_idx = m.pos |
|
m.skip_until(@string_terminator) |
|
m.pos = 4 * ((m.pos + 0.01)/4).ceil |
|
pack_string << "Z#{m.pos - orig_idx}" |
|
when @d_tag |
|
# double64 |
|
m.pos = m.pos+8 |
|
pack_string << @cap_g |
|
when @h_tag |
|
# int64 |
|
m.pos = m.pos+8 |
|
pack_string << @q_lt |
|
when @b_tag |
|
# binary blob |
|
orig_idx = m.pos |
|
pack_string << @cap_n |
|
blob_length = m.getch.unpack(@cap_n)[0] |
|
m.skip(Regexp.new(".#{blob_length}")) |
|
pack_string << "Z#{m.pos - orig_idx}" |
|
|
|
m.pos = 4 * ((m.pos + 0.01)/4).ceil |
|
else |
|
raise "Unknown OSC type #{t}" |
|
end |
|
end |
|
end |
|
|
|
address, tags, *args = msg.unpack(pack_string) |
|
[address, args] |
|
end |
|
|
|
end |
|
end |
http://stackoverflow.com/questions/4886994/reading-a-binary-16-bit-signed-big-endian-integer-in-ruby
http://stackoverflow.com/questions/5236059/unpack-signed-little-endian-in-ruby
https://github.com/aberant/osc-ruby/blob/master/lib/osc-ruby/osc_packet.rb#L107
https://github.com/supercollider/supercollider/blob/25a296ff544f04850be2ce0f93282131d3c7d6d9/external_libraries/oscpack_1_1_0/osc/OscOutboundPacketStream.cpp#L60