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
data/README.rdoc
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
= An OSC client for Ruby
|
2
|
+
|
3
|
+
http://opensoundcontrol.org/
|
4
|
+
|
5
|
+
== Description
|
6
|
+
|
7
|
+
This OSC gem originally created by Tadayoshi Funaba has been updated for ruby 1.9 compatibility. I've made a point to make this code as expressive as possible and provide a test suite for confident hacking. It also should be flexible enough to support most crazy ideas.
|
8
|
+
|
9
|
+
Compatible with ruby 1.8, 1.9, and jruby
|
10
|
+
|
11
|
+
== Install
|
12
|
+
|
13
|
+
sudo gem install aberant-osc-ruby
|
14
|
+
|
15
|
+
for the EMServer, you will need EventMachine
|
16
|
+
|
17
|
+
sudo gem install eventmachine
|
18
|
+
|
19
|
+
== Event Machine example
|
20
|
+
|
21
|
+
# compatible with ruby 1.8, 1.9, and jruby
|
22
|
+
require 'rubygems'
|
23
|
+
require 'osc-ruby'
|
24
|
+
require 'osc-ruby/em_server'
|
25
|
+
|
26
|
+
@server = OSC::EMServer.new( 3333 )
|
27
|
+
@client = OSC::Client.new( 'localhost', 3333 )
|
28
|
+
|
29
|
+
@server.add_method '/greeting' do | message |
|
30
|
+
puts message.to_a
|
31
|
+
end
|
32
|
+
|
33
|
+
Thread.new do
|
34
|
+
@server.run
|
35
|
+
end
|
36
|
+
|
37
|
+
@client.send( OSC::Message.new( "/greeting" , "hullo!" ))
|
38
|
+
|
39
|
+
sleep( 3 )
|
40
|
+
|
41
|
+
== Classic example
|
42
|
+
|
43
|
+
# compatible with ruby 1.8
|
44
|
+
require 'rubygems'
|
45
|
+
require 'osc-ruby'
|
46
|
+
|
47
|
+
|
48
|
+
@server = OSC::Server.new( 3333 )
|
49
|
+
@client = OSC::Client.new( 'localhost', 3333 )
|
50
|
+
|
51
|
+
@server.add_method '/greeting' do | message |
|
52
|
+
puts message.inspect
|
53
|
+
end
|
54
|
+
|
55
|
+
Thread.new do
|
56
|
+
@server.run
|
57
|
+
end
|
58
|
+
|
59
|
+
@client.send( OSC::Message.new( "/greeting", "hullo!" ))
|
60
|
+
|
61
|
+
sleep( 3 )
|
62
|
+
|
63
|
+
|
64
|
+
== Credits
|
65
|
+
|
66
|
+
Originally created by...
|
67
|
+
|
68
|
+
Tadayoshi Funaba
|
69
|
+
|
70
|
+
http://www.funaba.org/en/
|
71
|
+
|
72
|
+
thx also to Toby Tripp and Obtiva
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec/rake/spectask'
|
2
|
+
|
3
|
+
|
4
|
+
task :default => :spec
|
5
|
+
|
6
|
+
Spec::Rake::SpecTask.new do |t|
|
7
|
+
t.warning = false
|
8
|
+
t.rcov = false
|
9
|
+
t.spec_opts = ["--colour"]
|
10
|
+
end
|
11
|
+
|
12
|
+
begin
|
13
|
+
require 'jeweler'
|
14
|
+
Jeweler::Tasks.new do |gem|
|
15
|
+
gem.name = "osc-ruby"
|
16
|
+
gem.summary = %Q{inital gem}
|
17
|
+
gem.email = "qzzzq1@gmail.com"
|
18
|
+
gem.homepage = "http://github.com/aberant/osc-ruby"
|
19
|
+
gem.authors = ["aberant"]
|
20
|
+
gem.files = FileList['Rakefile', 'examples/**/*', 'lib/**/*'].to_a
|
21
|
+
gem.test_files = FileList['spec/**/*.rb']
|
22
|
+
gem.rubyforge_project = "osc-ruby"
|
23
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
24
|
+
end
|
25
|
+
|
26
|
+
Jeweler::RubyforgeTasks.new do |rubyforge|
|
27
|
+
rubyforge.doc_task = "rdoc"
|
28
|
+
end
|
29
|
+
rescue LoadError
|
30
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
31
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# compatible with ruby 1.8
|
2
|
+
require File.join( File.dirname( __FILE__ ), '..', 'lib', 'osc-ruby' )
|
3
|
+
|
4
|
+
|
5
|
+
@server = OSC::Server.new( 3333 )
|
6
|
+
@client = OSC::Client.new( 'localhost', 3333 )
|
7
|
+
|
8
|
+
@server.add_method '/greeting' do | message |
|
9
|
+
puts message.to_a
|
10
|
+
end
|
11
|
+
|
12
|
+
Thread.new do
|
13
|
+
@server.run
|
14
|
+
end
|
15
|
+
|
16
|
+
@client.send( OSC::Message.new( "/greeting", "hullo!" ))
|
17
|
+
|
18
|
+
sleep( 3 )
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# compatible with ruby 1.8, 1.9, and jruby
|
2
|
+
require File.join( File.dirname( __FILE__ ), '..', 'lib', 'osc-ruby', 'em_server' )
|
3
|
+
|
4
|
+
@server = OSC::EMServer.new( 3333 )
|
5
|
+
@client = OSC::Client.new( 'localhost', 3333 )
|
6
|
+
|
7
|
+
@server.add_method '/greeting' do | message |
|
8
|
+
puts message.to_a
|
9
|
+
end
|
10
|
+
|
11
|
+
Thread.new do
|
12
|
+
@server.run
|
13
|
+
end
|
14
|
+
|
15
|
+
@client.send( OSC::Message.new( "/greeting" , "hullo!" ))
|
16
|
+
|
17
|
+
sleep( 3 )
|
data/lib/osc-ruby.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# osc.rb: Written by Tadayoshi Funaba 2005,2006
|
2
|
+
# $Id: osc.rb,v 1.4 2006-11-10 21:54:37+09 tadf Exp $
|
3
|
+
|
4
|
+
require 'forwardable'
|
5
|
+
require 'socket'
|
6
|
+
require 'thread'
|
7
|
+
|
8
|
+
|
9
|
+
$:.unshift( File.dirname( __FILE__ ) )
|
10
|
+
|
11
|
+
# core extensions
|
12
|
+
require 'osc-ruby/core_ext/object'
|
13
|
+
require 'osc-ruby/core_ext/numeric'
|
14
|
+
require 'osc-ruby/core_ext/time'
|
15
|
+
|
16
|
+
|
17
|
+
# jus the basics
|
18
|
+
require 'osc-ruby/osc_types'
|
19
|
+
require 'osc-ruby/packet'
|
20
|
+
require 'osc-ruby/osc_packet'
|
21
|
+
require 'osc-ruby/message'
|
22
|
+
require 'osc-ruby/bundle'
|
23
|
+
require 'osc-ruby/address_pattern'
|
24
|
+
|
25
|
+
# now we gettin fancy
|
26
|
+
require 'osc-ruby/server'
|
27
|
+
require 'osc-ruby/client'
|
28
|
+
|
29
|
+
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module OSC
|
2
|
+
class AddressPattern
|
3
|
+
def initialize( pattern )
|
4
|
+
@pattern = pattern
|
5
|
+
|
6
|
+
generate_regex_from_pattern
|
7
|
+
end
|
8
|
+
|
9
|
+
def match?( address )
|
10
|
+
!!(@re.nil? || @re.match( address ))
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
def generate_regex_from_pattern
|
15
|
+
case @pattern
|
16
|
+
when NIL; @re = @pattern
|
17
|
+
when Regexp; @re = @pattern
|
18
|
+
when String
|
19
|
+
|
20
|
+
# i'm unsure what this does
|
21
|
+
# @pattern.gsub!(/[.^(|)]/, '\\1')
|
22
|
+
|
23
|
+
# handles osc single char wildcard matching
|
24
|
+
@pattern.gsub!(/\?/, '[^/]')
|
25
|
+
|
26
|
+
# handles osc * - 0 or more matching
|
27
|
+
@pattern.gsub!(/\*/, '[^/]*')
|
28
|
+
|
29
|
+
# handles [!] matching
|
30
|
+
@pattern.gsub!(/\[!/, '[^')
|
31
|
+
|
32
|
+
# handles {} matching
|
33
|
+
@pattern.gsub!(/\{/, '(')
|
34
|
+
@pattern.gsub!(/,/, '|')
|
35
|
+
@pattern.gsub!(/\}/, ')')
|
36
|
+
|
37
|
+
|
38
|
+
# keeps from matching before the begining of the pattern
|
39
|
+
@pattern.gsub!(/\A/, '\A')
|
40
|
+
|
41
|
+
# keeps from matching beyond the end,
|
42
|
+
# eg. pattern /hi does not match /hidden
|
43
|
+
@pattern.gsub!(/\z/, '\z')
|
44
|
+
|
45
|
+
@re = Regexp.new(@pattern)
|
46
|
+
else
|
47
|
+
raise ArgumentError, 'invalid pattern'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.join( File.dirname( __FILE__ ), "packet" )
|
2
|
+
|
3
|
+
module OSC
|
4
|
+
class Bundle < Packet
|
5
|
+
attr_accessor :timetag
|
6
|
+
|
7
|
+
def initialize(timetag=nil, *args)
|
8
|
+
@timetag = timetag
|
9
|
+
@args = args
|
10
|
+
end
|
11
|
+
|
12
|
+
def encode()
|
13
|
+
s = OSCString.new('#bundle').encode
|
14
|
+
s << encode_timetag(@timetag)
|
15
|
+
s << @args.collect do |x|
|
16
|
+
x2 = x.encode; [x2.size].pack('N') + x2
|
17
|
+
end.join
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def to_a() @args.collect{|x| x.to_a} end
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def encode_timetag(t)
|
28
|
+
case t
|
29
|
+
when NIL # immediately
|
30
|
+
t1 = 0
|
31
|
+
t2 = 1
|
32
|
+
when Numeric
|
33
|
+
t1, fr = t.divmod(1)
|
34
|
+
t2 = (fr * (2**32)).to_i
|
35
|
+
when Time
|
36
|
+
t1, fr = (t.to_f + 2208988800).divmod(1)
|
37
|
+
t2 = (fr * (2**32)).to_i
|
38
|
+
else
|
39
|
+
raise ArgumentError, 'invalid time'
|
40
|
+
end
|
41
|
+
[t1, t2].pack('N2')
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Numeric
|
2
|
+
# Convert time intervals to seconds
|
3
|
+
def milliseconds; self/1000.0; end
|
4
|
+
def seconds; self; end
|
5
|
+
def minutes; self*60; end
|
6
|
+
def hours; self*60*60; end
|
7
|
+
def days; self*60*60*24; end
|
8
|
+
def weeks; self*60*60*24*7; end
|
9
|
+
|
10
|
+
# Convert seconds to other intervals
|
11
|
+
def to_milliseconds; self*1000; end
|
12
|
+
def to_seconds; self; end
|
13
|
+
def to_minutes; self/60.0; end
|
14
|
+
def to_hours; self/(60*60.0); end
|
15
|
+
def to_days; self/(60*60*24.0); end
|
16
|
+
def to_weeks; self/(60*60*24*7.0); end
|
17
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# lifted from the ruby programming language book, thx matz!
|
2
|
+
|
3
|
+
# Obtain the Mutex associated with the object o, and then evaluate
|
4
|
+
# the block under the protection of that Mutex.
|
5
|
+
# This works like the synchronized keyword of Java.
|
6
|
+
def synchronized(o)
|
7
|
+
o.mutex.synchronize { yield }
|
8
|
+
end
|
9
|
+
# Object.mutex does not actually exist. We've got to define it.
|
10
|
+
# This method returns a unique Mutex for every object, and
|
11
|
+
# always returns the same Mutex for any particular object.
|
12
|
+
# It creates Mutexes lazily, which requires synchronization for
|
13
|
+
# thread safety.
|
14
|
+
class Object
|
15
|
+
# Return the Mutex for this object, creating it if necessary.
|
16
|
+
# The tricky part is making sure that two threads don't call
|
17
|
+
# this at the same time and end up creating two different mutexes.
|
18
|
+
def mutex
|
19
|
+
# If this object already has a mutex, just return it
|
20
|
+
return @__mutex if @__mutex
|
21
|
+
|
22
|
+
# Otherwise, we've got to create a mutex for the object.
|
23
|
+
# To do this safely we've got to synchronize on our class object.
|
24
|
+
synchronized(self.class) {
|
25
|
+
# Check again: by the time we enter this synchronized block,
|
26
|
+
# some other thread might have already created the mutex.
|
27
|
+
@__mutex = @__mutex || Mutex.new
|
28
|
+
}
|
29
|
+
# The return value is @__mutex
|
30
|
+
end
|
31
|
+
end
|
32
|
+
# The Object.mutex method defined above needs to lock the class
|
33
|
+
# if the object doesn't have a Mutex yet. If the class doesn't have
|
34
|
+
# its own Mutex yet, then the class of the class (the Class object)
|
35
|
+
# will be locked. In order to prevent infinite recursion, we must
|
36
|
+
# ensure that the Class object has a mutex.
|
37
|
+
Class.instance_eval { @__mutex = Mutex.new }
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'eventmachine'
|
3
|
+
require File.join( File.dirname( __FILE__), '..', 'osc-ruby')
|
4
|
+
|
5
|
+
|
6
|
+
module OSC
|
7
|
+
Channel = EM::Channel.new
|
8
|
+
|
9
|
+
class Connection < EventMachine::Connection
|
10
|
+
|
11
|
+
def receive_data data
|
12
|
+
Channel << OSC::OSCPacket.messages_from_network( data )
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
class EMServer
|
18
|
+
|
19
|
+
def initialize( port = 3333 )
|
20
|
+
@port = port
|
21
|
+
setup_dispatcher
|
22
|
+
@tuples = []
|
23
|
+
end
|
24
|
+
|
25
|
+
def run
|
26
|
+
EM::run { EM::open_datagram_socket "localhost", @port, Connection }
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_method(address_pattern, &proc)
|
30
|
+
matcher = AddressPattern.new( address_pattern )
|
31
|
+
|
32
|
+
@tuples << [matcher, proc]
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def setup_dispatcher
|
37
|
+
Channel.subscribe do |messages|
|
38
|
+
messages.each do |message|
|
39
|
+
diff = ( message.time || 0 ) - Time.now.to_ntp
|
40
|
+
|
41
|
+
if diff <= 0
|
42
|
+
sendmesg( message )
|
43
|
+
else
|
44
|
+
EM.defer do
|
45
|
+
sleep( diff )
|
46
|
+
sendmesg( message )
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def sendmesg(mesg)
|
54
|
+
@tuples.each do |matcher, obj|
|
55
|
+
if matcher.match?( mesg.address )
|
56
|
+
obj.call( mesg )
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
|
67
|
+
|
68
|
+
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require File.join( File.dirname( __FILE__ ), "packet" )
|
2
|
+
|
3
|
+
module OSC
|
4
|
+
class Message < Packet
|
5
|
+
attr_accessor :address
|
6
|
+
attr_accessor :time
|
7
|
+
|
8
|
+
|
9
|
+
def self.new_with_time( address, time, tags=nil, *args )
|
10
|
+
message = new( address, tags, *args )
|
11
|
+
message.time = time
|
12
|
+
message
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(address, *args)
|
16
|
+
@address = address
|
17
|
+
@args = []
|
18
|
+
|
19
|
+
args.each_with_index do |arg, i|
|
20
|
+
case arg
|
21
|
+
when Integer; @args << OSCInt32.new(arg)
|
22
|
+
when Float; @args << OSCFloat32.new(arg)
|
23
|
+
when String; @args << OSCString.new(arg)
|
24
|
+
when OSCArgument; @args << arg
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
def tags() @args.collect{|x| x.tag}.join end
|
31
|
+
|
32
|
+
def encode
|
33
|
+
s = OSCString.new( @address ).encode
|
34
|
+
s << OSCString.new( ',' + tags ).encode
|
35
|
+
s << @args.collect{|x| x.encode}.join
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_a() @args.collect{|x| x.val} end
|
39
|
+
|
40
|
+
def eql?( other )
|
41
|
+
@address == other.address &&
|
42
|
+
to_a == other.to_a
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|