ruby-osc 0.31.0 → 1.0.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.
- 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
|