osc-ruby 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +72 -0
- data/Rakefile +31 -0
- data/examples/classic_server.rb +18 -0
- data/examples/event_machine_server.rb +17 -0
- data/lib/osc-ruby.rb +29 -0
- data/lib/osc-ruby/address_pattern.rb +51 -0
- data/lib/osc-ruby/bundle.rb +45 -0
- data/lib/osc-ruby/client.rb +14 -0
- data/lib/osc-ruby/core_ext/numeric.rb +17 -0
- data/lib/osc-ruby/core_ext/object.rb +37 -0
- data/lib/osc-ruby/core_ext/time.rb +6 -0
- data/lib/osc-ruby/em_server.rb +68 -0
- data/lib/osc-ruby/message.rb +45 -0
- data/lib/osc-ruby/network_packet.rb +42 -0
- data/lib/osc-ruby/osc_argument.rb +18 -0
- data/lib/osc-ruby/osc_packet.rb +118 -0
- data/lib/osc-ruby/osc_types.rb +31 -0
- data/lib/osc-ruby/packet.rb +134 -0
- data/lib/osc-ruby/server.rb +91 -0
- data/spec/builders/message_builder.rb +51 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/unit/address_pattern_spec.rb +83 -0
- data/spec/unit/message_bundle_spec.rb +6 -0
- data/spec/unit/message_spec.rb +34 -0
- data/spec/unit/network_packet_spec.rb +33 -0
- data/spec/unit/osc_argument_spec.rb +7 -0
- data/spec/unit/osc_complex_packets_spec.rb +39 -0
- data/spec/unit/osc_simple_packets_spec.rb +108 -0
- data/spec/unit/osc_types_spec.rb +25 -0
- metadata +80 -0
@@ -0,0 +1,42 @@
|
|
1
|
+
module OSC
|
2
|
+
class NetworkPacket
|
3
|
+
def initialize(str)
|
4
|
+
@str, @index = str, 0
|
5
|
+
end
|
6
|
+
|
7
|
+
def to_s
|
8
|
+
@str
|
9
|
+
end
|
10
|
+
|
11
|
+
def rem()
|
12
|
+
@str.length - @index
|
13
|
+
end
|
14
|
+
|
15
|
+
def eof? ()
|
16
|
+
rem <= 0
|
17
|
+
end
|
18
|
+
|
19
|
+
def skip(n)
|
20
|
+
@index += n
|
21
|
+
end
|
22
|
+
|
23
|
+
def skip_padding()
|
24
|
+
skip((4 - (@index % 4)) % 4)
|
25
|
+
end
|
26
|
+
|
27
|
+
def getn(n)
|
28
|
+
raise EOFError if rem < n
|
29
|
+
s = @str[@index, n]
|
30
|
+
skip(n)
|
31
|
+
s
|
32
|
+
end
|
33
|
+
|
34
|
+
def getc
|
35
|
+
raise EOFError if rem < 1
|
36
|
+
c = @str[@index]
|
37
|
+
skip(1)
|
38
|
+
c
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module OSC
|
2
|
+
class OSCArgument
|
3
|
+
|
4
|
+
def initialize(val) @val = val end
|
5
|
+
|
6
|
+
attr_accessor :val
|
7
|
+
|
8
|
+
def to_i() @val.to_i end
|
9
|
+
def to_f() @val.to_f end
|
10
|
+
def to_s() @val.to_s end
|
11
|
+
|
12
|
+
|
13
|
+
private
|
14
|
+
def padding(s)
|
15
|
+
s + ("\000" * ((4 - (s.size % 4)) % 4))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require File.join( File.dirname( __FILE__ ), 'network_packet')
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
module OSC
|
5
|
+
class OSCPacket
|
6
|
+
|
7
|
+
def self.messages_from_network( string )
|
8
|
+
messages = []
|
9
|
+
osc = new( string )
|
10
|
+
|
11
|
+
if osc.bundle?
|
12
|
+
bundle = osc.get_string
|
13
|
+
time = osc.get_timestamp
|
14
|
+
|
15
|
+
osc.get_bundle_messages.each do | message |
|
16
|
+
messages << decode_simple_message( time, OSCPacket.new( message ) )
|
17
|
+
end
|
18
|
+
|
19
|
+
else
|
20
|
+
messages << decode_simple_message( time, osc )
|
21
|
+
end
|
22
|
+
|
23
|
+
return messages
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.decode_simple_message( time, osc_packet )
|
27
|
+
address = osc_packet.get_string
|
28
|
+
args = osc_packet.get_arguments
|
29
|
+
|
30
|
+
Message.new_with_time(address, time, nil, *args )
|
31
|
+
end
|
32
|
+
|
33
|
+
def initialize( string )
|
34
|
+
@packet = NetworkPacket.new( string )
|
35
|
+
|
36
|
+
@types = { "i" => lambda{ OSCInt32.new( get_int32 ) },
|
37
|
+
"f" => lambda{ OSCFloat32.new( get_float32 ) },
|
38
|
+
"s" => lambda{ OSCString.new( get_string ) },
|
39
|
+
"b" => lambda{ OSCBlob.new( get_blob )}
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_bundle_messages
|
44
|
+
bundle_messages = []
|
45
|
+
|
46
|
+
until @packet.eof?
|
47
|
+
l = @packet.getn(4).unpack('N')[0]
|
48
|
+
bundle_messages << @packet.getn(l)
|
49
|
+
end
|
50
|
+
bundle_messages
|
51
|
+
end
|
52
|
+
|
53
|
+
def get_string
|
54
|
+
result = ''
|
55
|
+
until (c = @packet.getc) == string_delemeter
|
56
|
+
result << c
|
57
|
+
end
|
58
|
+
@packet.skip_padding
|
59
|
+
result
|
60
|
+
end
|
61
|
+
|
62
|
+
def get_timestamp
|
63
|
+
t1 = @packet.getn(4).unpack('N')[0]
|
64
|
+
t2 = @packet.getn(4).unpack('N')[0]
|
65
|
+
@packet.skip_padding
|
66
|
+
|
67
|
+
if t1 == 0 && t2 == 1
|
68
|
+
time = nil
|
69
|
+
else
|
70
|
+
time = t1 + t2.to_f / (2**32)
|
71
|
+
end
|
72
|
+
|
73
|
+
time
|
74
|
+
end
|
75
|
+
|
76
|
+
def get_arguments
|
77
|
+
if @packet.getc == ?,
|
78
|
+
|
79
|
+
tags = get_string
|
80
|
+
args = []
|
81
|
+
|
82
|
+
tags.scan(/./) do | tag |
|
83
|
+
args << @types[tag].call
|
84
|
+
end
|
85
|
+
args
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def get_int32
|
90
|
+
i = @packet.getn(4).unpack('N')[0]
|
91
|
+
i -= 2**32 if i > (2**31-1)
|
92
|
+
@packet.skip_padding
|
93
|
+
i
|
94
|
+
end
|
95
|
+
|
96
|
+
def get_float32
|
97
|
+
f = @packet.getn(4).unpack('g')[0]
|
98
|
+
@packet.skip_padding
|
99
|
+
f
|
100
|
+
end
|
101
|
+
|
102
|
+
def get_blob
|
103
|
+
l = @packet.getn(4).unpack('N')[0]
|
104
|
+
b = @packet.getn(l)
|
105
|
+
@packet.skip_padding
|
106
|
+
b
|
107
|
+
end
|
108
|
+
|
109
|
+
def bundle?
|
110
|
+
!(@packet.to_s =~ /\A\#bundle/).nil?
|
111
|
+
end
|
112
|
+
|
113
|
+
def string_delemeter
|
114
|
+
# ruby 1.9 has multicharacter support
|
115
|
+
RUBY_VERSION.include?( '1.9' ) ? "\x00" : 0
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require File.join( File.dirname( __FILE__ ), "osc_argument" )
|
2
|
+
|
3
|
+
module OSC
|
4
|
+
class OSCInt32 < OSCArgument
|
5
|
+
|
6
|
+
def tag() 'i' end
|
7
|
+
def encode() [@val].pack('N') end
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
class OSCFloat32 < OSCArgument
|
12
|
+
|
13
|
+
def tag() 'f' end
|
14
|
+
def encode() [@val].pack('g') end # fake - why fake?
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
class OSCString < OSCArgument
|
19
|
+
|
20
|
+
def tag() 's' end
|
21
|
+
def encode() padding(@val.sub(/\000.*\z/, '') + "\000") end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
class OSCBlob < OSCArgument
|
26
|
+
|
27
|
+
def tag() 'b' end
|
28
|
+
def encode() padding([@val.size].pack('N') + @val) end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module OSC
|
2
|
+
class Packet
|
3
|
+
|
4
|
+
class PO
|
5
|
+
def initialize(str)
|
6
|
+
@str, @index = str, 0
|
7
|
+
end
|
8
|
+
|
9
|
+
def rem()
|
10
|
+
@str.length - @index
|
11
|
+
end
|
12
|
+
|
13
|
+
def eof? ()
|
14
|
+
rem <= 0
|
15
|
+
end
|
16
|
+
|
17
|
+
def skip(n)
|
18
|
+
@index += n
|
19
|
+
end
|
20
|
+
|
21
|
+
def skip_padding()
|
22
|
+
skip((4 - (@index % 4)) % 4)
|
23
|
+
end
|
24
|
+
|
25
|
+
def getn(n)
|
26
|
+
raise EOFError if rem < n
|
27
|
+
s = @str[@index, n]
|
28
|
+
skip(n)
|
29
|
+
s
|
30
|
+
end
|
31
|
+
|
32
|
+
def getc
|
33
|
+
raise EOFError if rem < 1
|
34
|
+
c = @str[@index]
|
35
|
+
skip(1)
|
36
|
+
c
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.decode_int32(io)
|
41
|
+
i = io.getn(4).unpack('N')[0]
|
42
|
+
i -= 2**32 if i > (2**31-1)
|
43
|
+
i
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.decode_float32(io)
|
47
|
+
f = io.getn(4).unpack('g')[0]
|
48
|
+
f
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.decode_string(io)
|
52
|
+
s = ''
|
53
|
+
until (c = io.getc) == 0
|
54
|
+
s << c
|
55
|
+
end
|
56
|
+
io.skip_padding
|
57
|
+
s
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.decode_blob(io)
|
61
|
+
l = io.getn(4).unpack('N')[0]
|
62
|
+
b = io.getn(l)
|
63
|
+
io.skip_padding
|
64
|
+
b
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.decode_timetag(io)
|
68
|
+
t1 = io.getn(4).unpack('N')[0]
|
69
|
+
t2 = io.getn(4).unpack('N')[0]
|
70
|
+
[t1, t2]
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.decode2(time, packet, list)
|
74
|
+
io = PO.new(packet)
|
75
|
+
id = decode_string(io)
|
76
|
+
if id =~ /\A\#/
|
77
|
+
if id == '#bundle'
|
78
|
+
t1, t2 = decode_timetag(io)
|
79
|
+
if t1 == 0 && t2 == 1
|
80
|
+
time = nil
|
81
|
+
else
|
82
|
+
time = t1 + t2.to_f / (2**32)
|
83
|
+
end
|
84
|
+
until io.eof?
|
85
|
+
l = io.getn(4).unpack('N')[0]
|
86
|
+
s = io.getn(l)
|
87
|
+
decode2(time, s, list)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
elsif id =~ /\//
|
91
|
+
address = id
|
92
|
+
if io.getc == ?,
|
93
|
+
tags = decode_string(io)
|
94
|
+
args = []
|
95
|
+
tags.scan(/./) do |t|
|
96
|
+
case t
|
97
|
+
when 'i'
|
98
|
+
i = decode_int32(io)
|
99
|
+
args << OSCInt32.new(i)
|
100
|
+
when 'f'
|
101
|
+
f = decode_float32(io)
|
102
|
+
args << OSCFloat32.new(f)
|
103
|
+
when 's'
|
104
|
+
s = decode_string(io)
|
105
|
+
args << OSCString.new(s)
|
106
|
+
when 'b'
|
107
|
+
b = decode_blob(io)
|
108
|
+
args << OSCBlob.new(b)
|
109
|
+
when /[htd]/; io.read(8)
|
110
|
+
when 'S'; decode_string(io)
|
111
|
+
when /[crm]/; io.read(4)
|
112
|
+
when /[TFNI\[\]]/;
|
113
|
+
end
|
114
|
+
end
|
115
|
+
list << [time, Message.new(address, nil, *args)]
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
private_class_method :decode_int32,
|
121
|
+
:decode_float32,
|
122
|
+
:decode_string,
|
123
|
+
:decode_blob,
|
124
|
+
:decode_timetag,
|
125
|
+
:decode2
|
126
|
+
|
127
|
+
def self.decode(packet)
|
128
|
+
list = []
|
129
|
+
decode2(nil, packet, list)
|
130
|
+
list
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module OSC
|
2
|
+
class Server
|
3
|
+
|
4
|
+
def initialize(port)
|
5
|
+
@socket = UDPSocket.new
|
6
|
+
@socket.bind('', port)
|
7
|
+
@cb = []
|
8
|
+
@queue = Queue.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
start_dispatcher
|
13
|
+
|
14
|
+
start_detector
|
15
|
+
end
|
16
|
+
|
17
|
+
def stop
|
18
|
+
@socket.close
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_method(address_pattern, &proc)
|
22
|
+
matcher = AddressPattern.new( address_pattern )
|
23
|
+
|
24
|
+
@cb << [matcher, proc]
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def start_detector
|
30
|
+
begin
|
31
|
+
detector
|
32
|
+
rescue
|
33
|
+
Thread.main.raise $!
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def start_dispatcher
|
38
|
+
Thread.fork do
|
39
|
+
begin
|
40
|
+
dispatcher
|
41
|
+
rescue
|
42
|
+
Thread.main.raise $!
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def sendmesg(mesg)
|
48
|
+
@cb.each do |matcher, obj|
|
49
|
+
if matcher.match?( mesg.address )
|
50
|
+
obj.call( mesg )
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def dispatcher
|
56
|
+
loop do
|
57
|
+
mesg = @queue.pop
|
58
|
+
dispatch_message( mesg )
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def detector
|
63
|
+
loop do
|
64
|
+
pa, network = @socket.recvfrom(16384)
|
65
|
+
begin
|
66
|
+
|
67
|
+
OSCPacket.messages_from_network(pa).each do |x|
|
68
|
+
@queue.push(x)
|
69
|
+
end
|
70
|
+
|
71
|
+
rescue EOFError
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def dispatch_message( message )
|
77
|
+
diff = ( message.time || 0 ) - Time.now.to_ntp
|
78
|
+
|
79
|
+
if diff <= 0
|
80
|
+
sendmesg( message)
|
81
|
+
else # spawn a thread to wait until it's time
|
82
|
+
Thread.fork do
|
83
|
+
sleep(diff)
|
84
|
+
sendmesg(mesg)
|
85
|
+
Thread.exit
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
class MessageBuilder
|
2
|
+
|
3
|
+
def initialize
|
4
|
+
@address = ""
|
5
|
+
@tags = []
|
6
|
+
@values = []
|
7
|
+
@time = nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def with_address( addr )
|
11
|
+
@address = addr
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def with_float( float )
|
16
|
+
with_arg( "f", float )
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def with_int( int )
|
21
|
+
with_arg( "i", int )
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def with_string( string )
|
26
|
+
with_arg( "s", string )
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def with_blob( blob )
|
31
|
+
with_arg( "b", blob )
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def with_time( time )
|
36
|
+
@time = time
|
37
|
+
end
|
38
|
+
|
39
|
+
def build
|
40
|
+
message = OSC::Message.new( @address , *@values)
|
41
|
+
message.time = @time
|
42
|
+
message
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def with_arg( tag, value )
|
48
|
+
@tags << tag
|
49
|
+
@values << value
|
50
|
+
end
|
51
|
+
end
|