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
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
|