osc-ruby 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|