ruby-osc 0.31.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rubocop.yml +1153 -0
- data/README.rdoc +2 -0
- data/Rakefile +7 -7
- data/examples/localtest.rb +8 -7
- data/lib/ruby-osc.rb +35 -31
- data/lib/ruby-osc/bundle.rb +29 -22
- data/lib/ruby-osc/client.rb +14 -10
- data/lib/ruby-osc/message.rb +48 -31
- data/lib/ruby-osc/server.rb +13 -12
- data/lib/ruby-osc/version.rb +2 -1
- data/ruby-osc.gemspec +7 -9
- data/spec/bundle_spec.rb +48 -47
- data/spec/message_spec.rb +129 -86
- data/spec/server_spec.rb +2 -1
- data/spec/spec_helper.rb +4 -4
- data/streamscanner_benchmark.rb +40 -0
- metadata +24 -31
data/README.rdoc
CHANGED
data/Rakefile
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
require
|
2
|
-
require "rspec/core/rake_task"
|
3
|
-
|
4
|
-
desc "Run specs"
|
5
|
-
RSpec::Core::RakeTask.new(:spec)
|
6
|
-
|
7
|
-
task :
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require "rspec/core/rake_task"
|
3
|
+
|
4
|
+
desc "Run specs"
|
5
|
+
RSpec::Core::RakeTask.new(:spec)
|
6
|
+
|
7
|
+
task default: :spec
|
data/examples/localtest.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
|
2
|
-
require
|
1
|
+
require "rubygems"
|
2
|
+
require "ruby-osc"
|
3
3
|
|
4
4
|
include OSC
|
5
5
|
|
@@ -7,7 +7,7 @@ OSC.run do
|
|
7
7
|
server = Server.new 9090
|
8
8
|
client = Client.new 9090
|
9
9
|
|
10
|
-
server.add_pattern
|
10
|
+
server.add_pattern(/.*/) do |*args| # this will match any address
|
11
11
|
p "/.*/: #{ args.join(', ') }"
|
12
12
|
end
|
13
13
|
|
@@ -19,11 +19,12 @@ OSC.run do
|
|
19
19
|
p "'/foo/bar': #{ args.join(', ') }"
|
20
20
|
end
|
21
21
|
|
22
|
-
server.add_pattern "/exit" do |*
|
22
|
+
server.add_pattern "/exit" do |*_args| # this will just match /exit address
|
23
23
|
exit
|
24
24
|
end
|
25
25
|
|
26
|
-
|
27
|
-
client.send Message.new(
|
28
|
-
client.send
|
26
|
+
|
27
|
+
client.send Message.new("/foo/bar", 1, 1.2, "a string")
|
28
|
+
client.send Message.new("/foo/bar/zar", 1, 1.2, "a string")
|
29
|
+
client.send Bundle.new(Time.now + 2, Message.new("/exit"))
|
29
30
|
end
|
data/lib/ruby-osc.rb
CHANGED
@@ -1,61 +1,65 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
require
|
11
|
-
require
|
1
|
+
require "rubygems"
|
2
|
+
require "eventmachine"
|
3
|
+
require "socket" # Strange side effects with eventmachine udp client and SuperCollider
|
4
|
+
require "strscan"
|
5
|
+
require "thread"
|
6
|
+
|
7
|
+
$LOAD_PATH.unshift( File.join( File.dirname( __FILE__), "..", "lib" ) )
|
8
|
+
|
9
|
+
# encoding: UTF-8
|
10
|
+
require "ruby-osc/message"
|
11
|
+
require "ruby-osc/bundle"
|
12
|
+
require "ruby-osc/server"
|
13
|
+
require "ruby-osc/client"
|
12
14
|
require "ruby-osc/version"
|
13
15
|
|
14
16
|
module OSC
|
15
17
|
class DecodeError < StandardError; end
|
16
|
-
|
18
|
+
|
17
19
|
class Blob < String; end
|
18
|
-
|
20
|
+
|
19
21
|
module OSCArgument
|
20
22
|
def to_osc_type
|
21
23
|
raise NotImplementedError, "#to_osc_type method should be implemented for #{ self.class }"
|
22
24
|
end
|
23
25
|
end
|
24
|
-
|
25
|
-
def self.coerce_argument
|
26
|
+
|
27
|
+
def self.coerce_argument(arg)
|
26
28
|
case arg
|
27
29
|
when OSCArgument then arg.to_osc_type
|
28
30
|
when Symbol then arg.to_s
|
29
|
-
when String, Float,
|
30
|
-
else
|
31
|
+
when String, Float, Integer, Blob, String then arg # Osc 1.0 spec
|
32
|
+
else
|
33
|
+
raise(TypeError, "#{ arg.inspect } is not a valid Message argument")
|
34
|
+
end
|
31
35
|
end
|
32
|
-
|
33
|
-
def self.decode
|
36
|
+
|
37
|
+
def self.decode(str) #:nodoc:
|
34
38
|
str.match(/^#bundle/) ? Bundle.decode(str) : Message.decode(str)
|
35
39
|
end
|
36
|
-
|
37
|
-
def self.padding_size
|
38
|
-
(4 - (size) % 4) % 4
|
40
|
+
|
41
|
+
def self.padding_size(size)
|
42
|
+
(4 - (size) % 4) % 4
|
39
43
|
end
|
40
44
|
|
41
45
|
def self.run
|
42
46
|
EM.run do
|
43
47
|
EM.error_handler { |e| puts e }
|
44
|
-
EM.set_quantum 5
|
48
|
+
EM.set_quantum 5
|
45
49
|
yield
|
46
50
|
end
|
47
51
|
end
|
48
|
-
|
49
|
-
def self.encoding_directive
|
52
|
+
|
53
|
+
def self.encoding_directive(obj) #:nodoc:
|
50
54
|
case obj
|
51
|
-
when Float
|
52
|
-
when
|
53
|
-
when Blob
|
54
|
-
when String then [obj,
|
55
|
+
when Float then [obj, "f", "g"]
|
56
|
+
when Integer then [obj, "i", "N"]
|
57
|
+
when Blob then [[obj.bytesize, obj], "b", "Na*x#{ padding_size obj.bytesize + 4 }"]
|
58
|
+
when String then [obj, "s", "Z*x#{ padding_size obj.bytesize + 1 }"]
|
55
59
|
when Time
|
56
|
-
t1, fr = (obj.to_f +
|
60
|
+
t1, fr = (obj.to_f + 2_208_988_800).divmod(1)
|
57
61
|
t2 = (fr * (2**32)).to_i
|
58
|
-
[[t1, t2],
|
62
|
+
[[t1, t2], "t", "N2"]
|
59
63
|
end
|
60
64
|
end
|
61
65
|
end
|
data/lib/ruby-osc/bundle.rb
CHANGED
@@ -1,52 +1,59 @@
|
|
1
|
+
# encoding: UTF-8
|
1
2
|
module OSC
|
2
3
|
class Bundle < Array
|
3
4
|
attr_accessor :timetag
|
4
5
|
|
5
|
-
def initialize
|
6
|
+
def initialize(timetag = nil, *args)
|
6
7
|
args.each{ |arg| raise TypeError, "#{ arg.inspect } is required to be a Bundle or Message" unless Bundle === arg or Message === arg }
|
7
|
-
raise TypeError, "#{ timetag.inspect } is required to be Time or nil" unless timetag
|
8
|
+
raise TypeError, "#{ timetag.inspect } is required to be Time or nil" unless timetag.nil? or Time === timetag
|
8
9
|
super args
|
9
10
|
@timetag = timetag
|
10
11
|
end
|
11
12
|
|
12
13
|
def encode
|
13
14
|
timetag =
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
if @timetag
|
16
|
+
time, _tag, dir = OSC.encoding_directive(@timetag)
|
17
|
+
time.pack dir
|
18
|
+
else
|
19
|
+
"\000\000\000\000\000\000\000\001"
|
20
|
+
end
|
21
|
+
|
19
22
|
"#bundle\000#{ timetag }" + collect do |x|
|
20
23
|
x = x.encode
|
21
|
-
[x.size].pack(
|
24
|
+
[x.size].pack("N") + x
|
22
25
|
end.join
|
23
26
|
end
|
24
27
|
|
25
|
-
def self.decode
|
26
|
-
string.sub!
|
27
|
-
t1, t2, content_str = string.unpack(
|
28
|
-
|
28
|
+
def self.decode(string)
|
29
|
+
string.sub!(/^#bundle\000/, "")
|
30
|
+
t1, t2, content_str = string.unpack("N2a*")
|
31
|
+
|
29
32
|
timetag = t1 == 0 && t2 == 1 ? nil : Time.at(t1 + t2 / (2**32.0) - 2_208_988_800)
|
30
33
|
scanner = StringScanner.new content_str
|
31
34
|
args = []
|
32
|
-
|
35
|
+
|
33
36
|
until scanner.eos?
|
34
|
-
size = scanner.scan(/.{4}/).unpack(
|
35
|
-
arg_str =
|
36
|
-
|
37
|
+
size = scanner.scan(/.{4}/).unpack("N").first
|
38
|
+
arg_str = begin
|
39
|
+
scanner.scan(/.{#{ size }}/nm)
|
40
|
+
rescue
|
41
|
+
raise(DecodeError, "An error occured while trying to decode bad formatted osc bundle")
|
42
|
+
end
|
43
|
+
args << OSC.decode(arg_str)
|
37
44
|
end
|
38
|
-
|
45
|
+
|
39
46
|
new timetag, *args
|
40
47
|
end
|
41
|
-
|
42
|
-
def ==
|
43
|
-
self.class == other.class and
|
48
|
+
|
49
|
+
def ==(other)
|
50
|
+
self.class == other.class and timetag == other.timetag and to_a == other.to_a
|
44
51
|
end
|
45
52
|
|
46
53
|
def to_a; Array.new self; end
|
47
|
-
|
54
|
+
|
48
55
|
def to_s
|
49
|
-
"OSC::Bundle(#{
|
56
|
+
"OSC::Bundle(#{ join(', ') })"
|
50
57
|
end
|
51
58
|
end
|
52
59
|
end
|
data/lib/ruby-osc/client.rb
CHANGED
@@ -1,13 +1,17 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require "socket" # Strange side effects with eventmachine udp client and SuperCollider
|
3
|
+
|
1
4
|
# From the Funaba osc gem:
|
2
5
|
module OSC
|
3
6
|
class Client
|
4
|
-
|
5
|
-
def initialize port, host = '127.0.0.1'
|
7
|
+
def initialize(port, host = "127.0.0.1")
|
6
8
|
@socket = UDPSocket.new
|
9
|
+
@socket = UDPSocket.open
|
10
|
+
@socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true)
|
7
11
|
@socket.connect host, port
|
8
12
|
end
|
9
|
-
|
10
|
-
def send
|
13
|
+
|
14
|
+
def send(mesg, *_args)
|
11
15
|
@socket.send mesg.encode, 0
|
12
16
|
end
|
13
17
|
end
|
@@ -15,24 +19,24 @@ end
|
|
15
19
|
|
16
20
|
# module OSC
|
17
21
|
# class Client
|
18
|
-
#
|
22
|
+
#
|
19
23
|
# def initialize port, address = 'localhost'
|
20
24
|
# @address, @port = address, port
|
21
25
|
# run
|
22
26
|
# end
|
23
|
-
#
|
27
|
+
#
|
24
28
|
# def run
|
25
29
|
# @connection = EventMachine.open_datagram_socket 'localhost', 0, Connection
|
26
30
|
# end
|
27
|
-
#
|
31
|
+
#
|
28
32
|
# def stop
|
29
33
|
# @connection.close_connection if @connection
|
30
34
|
# end
|
31
|
-
#
|
35
|
+
#
|
32
36
|
# def send item
|
33
|
-
# @connection.send_datagram item.encode, @address, @port
|
37
|
+
# @connection.send_datagram item.encode, @address, @port
|
34
38
|
# end
|
35
|
-
#
|
39
|
+
#
|
36
40
|
# class Connection < EventMachine::Connection #:nodoc:
|
37
41
|
# end
|
38
42
|
# end
|
data/lib/ruby-osc/message.rb
CHANGED
@@ -2,7 +2,7 @@ module OSC
|
|
2
2
|
class Message
|
3
3
|
attr_accessor :address, :time, :args
|
4
4
|
|
5
|
-
def initialize
|
5
|
+
def initialize(address = "", *args)
|
6
6
|
args.collect! { |arg| OSC.coerce_argument arg }
|
7
7
|
args.flatten! # won't harm we're not accepting arrays anyway, in case an custom coerced arg coerces to Array eg. Hash
|
8
8
|
raise(TypeError, "Expected address to be a string") unless String === address
|
@@ -14,7 +14,7 @@ module OSC
|
|
14
14
|
dirs ||= [] and objs ||= []
|
15
15
|
|
16
16
|
[",#{ tags and tags.join }", @address].each do |str|
|
17
|
-
obj,
|
17
|
+
obj, _tag, dir = OSC.encoding_directive(str)
|
18
18
|
objs.unshift obj
|
19
19
|
dirs.unshift dir
|
20
20
|
end
|
@@ -22,45 +22,62 @@ module OSC
|
|
22
22
|
objs.flatten.compact.pack dirs.join
|
23
23
|
end
|
24
24
|
|
25
|
-
def ==
|
25
|
+
def ==(other)
|
26
26
|
self.class == other.class and to_a == other.to_a
|
27
27
|
end
|
28
28
|
|
29
29
|
def to_a; @args.dup.unshift(@address) end
|
30
30
|
def to_s; "OSC::Message(#{ args.join(', ') })" end
|
31
31
|
|
32
|
-
def self.decode
|
32
|
+
def self.decode(string)
|
33
33
|
scanner = StringScanner.new string
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
end
|
34
|
+
msg = decode_message scanner
|
35
|
+
raise DecodeError unless scanner.eos?
|
36
|
+
msg
|
37
|
+
end
|
39
38
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
when 's'
|
49
|
-
str = scanner.scan(/[^\000]+\000/)
|
50
|
-
scanner.pos += OSC.padding_size(str.size)
|
51
|
-
args.push str.chomp("\000")
|
52
|
-
when 'b'
|
53
|
-
size = scanner.scan(/.{4}/).unpack('N').first
|
54
|
-
str = scanner.scan(/.{#{ size }}/nm)
|
55
|
-
scanner.pos += OSC.padding_size(size + 4)
|
56
|
-
args.push Blob.new(str)
|
57
|
-
else
|
58
|
-
raise DecodeError, "#{ t } is not a known tag"
|
39
|
+
def self.decode_message(scanner)
|
40
|
+
pos = scanner.pos
|
41
|
+
begin
|
42
|
+
address, tags = (1..2).map do
|
43
|
+
string = scanner.scan(/[^\000]+\000/)
|
44
|
+
raise DecodeError, "no address or tags" if string.nil?
|
45
|
+
scanner.pos += OSC.padding_size(string.bytesize)
|
46
|
+
string.chomp("\000")
|
59
47
|
end
|
60
|
-
end
|
61
48
|
|
62
|
-
|
49
|
+
args = []
|
50
|
+
tags.scan(/\w/) do |tag|
|
51
|
+
case tag
|
52
|
+
when "i"
|
53
|
+
int = scanner.scan(/.{4}/nm).unpack("N").first
|
54
|
+
args.push( int > (2**31 - 1) ? int - 2**32 : int )
|
55
|
+
when "f"
|
56
|
+
args.push scanner.scan(/.{4}/nm).unpack("g").first
|
57
|
+
when "d"
|
58
|
+
args.push scanner.scan(/.{8}/nm).unpack("G").first
|
59
|
+
when "s"
|
60
|
+
str = scanner.scan(/[^\000]+\000/)
|
61
|
+
scanner.pos += OSC.padding_size(str.size)
|
62
|
+
str = str.respond_to?(:force_encoding) ? str.force_encoding("UTF-8") : str
|
63
|
+
args.push str.chomp("\000")
|
64
|
+
when "b"
|
65
|
+
size = scanner.scan(/.{4}/).unpack("N").first
|
66
|
+
str = scanner.scan(/.{#{ size }}/nm)
|
67
|
+
scanner.pos += OSC.padding_size(size + 4)
|
68
|
+
args.push Blob.new(str)
|
69
|
+
else
|
70
|
+
raise DecodeError, "#{ tag } is not a known tag"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
new address, *args
|
74
|
+
end
|
75
|
+
rescue DecodeError => e
|
76
|
+
scanner.pos = pos
|
77
|
+
raise e
|
78
|
+
rescue => e
|
79
|
+
scanner.pos = pos
|
80
|
+
raise DecodeError, e
|
63
81
|
end
|
64
|
-
|
65
82
|
end
|
66
83
|
end
|
data/lib/ruby-osc/server.rb
CHANGED
@@ -2,7 +2,7 @@ module OSC
|
|
2
2
|
class Server
|
3
3
|
attr_accessor :port, :address
|
4
4
|
|
5
|
-
def initialize
|
5
|
+
def initialize(port, address = "127.0.0.1")
|
6
6
|
@port, @address = port, address
|
7
7
|
@queue, @patterns = [], []
|
8
8
|
@mutex = Mutex.new
|
@@ -20,27 +20,28 @@ module OSC
|
|
20
20
|
@timer.cancel
|
21
21
|
end
|
22
22
|
|
23
|
-
def add_pattern
|
24
|
-
raise ArgumentError
|
23
|
+
def add_pattern(pattern, &block)
|
24
|
+
raise ArgumentError, "A block must be given" unless block
|
25
25
|
@patterns << [pattern, block]
|
26
26
|
end
|
27
27
|
|
28
|
-
def delete_pattern
|
28
|
+
def delete_pattern(pattern)
|
29
29
|
@patterns.delete pattern
|
30
30
|
end
|
31
31
|
|
32
|
-
def receive
|
32
|
+
def receive(data)
|
33
33
|
case decoded = OSC.decode(data)
|
34
34
|
when Bundle
|
35
35
|
decoded.timetag.nil? ? decoded.each{ |m| dispatch m } : @mutex.synchronize{@queue.push(decoded)}
|
36
36
|
when Message
|
37
37
|
dispatch decoded
|
38
38
|
end
|
39
|
-
|
40
|
-
|
39
|
+
rescue => e
|
40
|
+
warn "Bad data received: #{ e }"
|
41
41
|
end
|
42
42
|
|
43
43
|
private
|
44
|
+
|
44
45
|
def check_queue
|
45
46
|
@timer = EventMachine::PeriodicTimer.new 0.002 do
|
46
47
|
now = Time.now
|
@@ -53,19 +54,19 @@ module OSC
|
|
53
54
|
end
|
54
55
|
end
|
55
56
|
|
56
|
-
def dispatch
|
57
|
-
@patterns.each do |pat, block|
|
57
|
+
def dispatch(message)
|
58
|
+
@patterns.each do |pat, block|
|
58
59
|
block.call(*message.to_a) if pat === message.address
|
59
60
|
end
|
60
61
|
end
|
61
62
|
|
62
63
|
class Connection < EventMachine::Connection #:nodoc:
|
63
|
-
def initialize
|
64
|
+
def initialize(server)
|
64
65
|
@server = server
|
65
66
|
end
|
66
67
|
|
67
|
-
def receive_data
|
68
|
-
@server.receive(data)
|
68
|
+
def receive_data(data)
|
69
|
+
@server.receive(data)
|
69
70
|
end
|
70
71
|
end
|
71
72
|
end
|