osc-ruby 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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 )
@@ -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,14 @@
1
+ module OSC
2
+ class Client
3
+
4
+ def initialize(host, port)
5
+ @so = UDPSocket.new
6
+ @so.connect(host, port)
7
+ end
8
+
9
+ def send(mesg)
10
+ @so.send(mesg.encode, 0)
11
+ end
12
+
13
+ end
14
+ 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,6 @@
1
+ class Time
2
+
3
+ def to_ntp
4
+ self.to_f + 2208988800
5
+ end
6
+ end
@@ -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