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