maca-rosc 0.0.1

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,131 @@
1
+ require 'set'
2
+ module OSC
3
+ class Pattern < String
4
+ # Create an OSC pattern from a string or (experimental) from a Regex.
5
+ def initialize(s)
6
+ case s
7
+ when Regexp # This is experimental
8
+ s = Regexp.source s
9
+ s.gsub! /(\\\\)*\[^\/\]\*/, "\1*"
10
+ s.gsub! /(\\\\)*\[^\/\]/, "\1?"
11
+ s.gsub! /(\\\\)*\[^/, "\1[!"
12
+ s.gsub! /(\\\\)*\(/, "\1{"
13
+ s.gsub! /(\\\\)*\|/, "\1,"
14
+ s.gsub! /(\\\\)*\)/, "\1}"
15
+ s.gsub! /\\\\/, "\\"
16
+ end
17
+ super s
18
+ end
19
+
20
+ # Return a Regex representing this pattern
21
+ def regexp
22
+ s = Regexp.escape self
23
+ s.gsub! /\\\?/, '[^/]'
24
+ s.gsub! /\\\*/, '[^/]*'
25
+ s.gsub! /\\\[!/, '[^'
26
+ s.gsub! /\\\]/, ']'
27
+ s.gsub! /\\\{/, '('
28
+ s.gsub! /,/, '|'
29
+ s.gsub! /\\\}/, ')'
30
+ Regexp.new s
31
+ end
32
+
33
+ # Do these two patterns intersect?
34
+ #--
35
+ # This might be improved by following the (much simpler, but related)
36
+ # algorithm here:
37
+ #
38
+ # http://groups.google.com/group/comp.theory/browse_frm/thread/f33e033269bd5ab0/c87e19081f45454c?lnk=st&q=regular+expression+intersection&rnum=1&hl=en#c87e19081f45454c
39
+ #
40
+ # That is, convert each regexp into an NFA, then generate the set of valid
41
+ # state pairs, then check if the pair of final states is included.
42
+ # That's basically what I'm doing here, but I'm not generating all the
43
+ # state pairs, I'm just doing a search. My way may be faster and/or
44
+ # smaller, or it may not. My initial feeling is that it is faster since
45
+ # we're basically doing a depth-first search and OSC patterns are going to
46
+ # tend to be fairly simple. Still it might be a fun experiment for the
47
+ # masochistic.
48
+ def self.intersect?(s1,s2)
49
+ r = /\*|\?|\[[^\]]*\]|\{[^\}]*\}|./
50
+ a = s1.to_s.scan r
51
+ b = s2.to_s.scan r
52
+ q = [[a,b]]
53
+ until q.empty?
54
+ q.uniq!
55
+ a,b = q.pop
56
+ a = a.dup
57
+ b = b.dup
58
+
59
+ return true if a.empty? and b.empty?
60
+ next if a.empty? or b.empty?
61
+
62
+ x,y = a.shift, b.shift
63
+
64
+ # branch {}
65
+ if x =~ /^\{/
66
+ x.scan /[^\{\},]+/ do |x|
67
+ q.push [x.scan(/./)+a,[y]+b]
68
+ end
69
+ next
70
+ end
71
+ if y =~ /^\{/
72
+ y.scan /[^\{\},]+/ do |y|
73
+ q.push [[x]+a,y.scan(/./)+b]
74
+ end
75
+ next
76
+ end
77
+
78
+ # sort
79
+ if y =~ /^\[/
80
+ x,y = y,x
81
+ a,b = b,a
82
+ end
83
+ if y =~ /^(\*|\?)/
84
+ x,y = y,x
85
+ a,b = b,a
86
+ end
87
+
88
+ # match
89
+ case x
90
+ when '*'
91
+ unless y == '/'
92
+ q.push [a,b]
93
+ q.push [[x]+a,b]
94
+ end
95
+ if y == '*'
96
+ q.push [a,[y]+b]
97
+ q.push [[x]+a,b]
98
+ end
99
+ when '?'
100
+ q.push [a,b] unless y == '/'
101
+ q.push [a,[y]+b] if y == '*'
102
+ when /^\[/
103
+ xinv = (x[1] == ?!)
104
+ yinv = (y =~ /^\[!/)
105
+ x = x[(xinv ? 2 : 1)..-2].scan(/./).to_set
106
+ if y =~ /^\[/
107
+ y = y[(yinv ? 2 : 1)..-2].scan(/./).to_set
108
+ else
109
+ y = [y].to_set
110
+ end
111
+
112
+ # simplifying assumption: nobody in their right mind is going to do
113
+ # [^everyprintablecharacter]
114
+ if xinv and yinv
115
+ q.push [a,b]
116
+ elsif xinv and not yinv
117
+ q.push [a,b] unless (y-x).empty?
118
+ elsif not xinv and yinv
119
+ q.push [a,b] unless (x-y).empty?
120
+ else
121
+ q.push [a,b] unless (x&y).empty?
122
+ end
123
+ else
124
+ q.push [a,b] if x == y
125
+ end
126
+ end
127
+
128
+ false # no intersection
129
+ end
130
+ end
131
+ end
data/lib/osc/server.rb ADDED
@@ -0,0 +1,94 @@
1
+ module OSC
2
+ # Mixin for making servers.
3
+ # Your job is to read a packet and call dispatch(Packet.decode(raw)), ad
4
+ # infinitum (e.g. in a method named serve).
5
+ module Server
6
+ # prock.respond_to?(:call) #=> true
7
+ # Pass an OSC pattern, a typespec, and either prock or a block.
8
+ # The block/prock will be called if the pattern and typspec match. Numeric
9
+ # types will be coerced, so e.g. 'fi' would match 'ii' and the float would
10
+ # be coerced to an int.
11
+ def add_method(pat, typespec, prock=nil, &block)
12
+ pat = Pattern.new(pat) unless Pattern === pat
13
+ if block_given? and prock
14
+ raise ArgumentError, 'Specify either a block or a Proc, not both.'
15
+ end
16
+ prock = block if block_given?
17
+ unless prock.respond_to?(:call)
18
+ raise ArgumentError, "Prock doesn't respond to :call"
19
+ end
20
+ unless typespec.nil? or typespec =~ /[ifsb]*/
21
+ raise ArgumentError, "Bad typespec '#{typespec}'"
22
+ end
23
+ @cb ||= []
24
+ @cb << [pat, typespec, prock]
25
+ end
26
+
27
+ # dispatch the provided message. It can be raw or already decoded with
28
+ # Packet.decode
29
+ def dispatch(mesg)
30
+ case mesg
31
+ when Bundle, Message
32
+ else
33
+ mesg = Packet.decode(mesg)
34
+ end
35
+
36
+ case mesg
37
+ when Bundle; dispatch_bundle(mesg)
38
+ when Message
39
+ unless @cb.nil?
40
+ @cb.each do |pat, typespec, obj|
41
+ if pat.nil? or Pattern.intersect?(pat, mesg.address)
42
+ if typespec
43
+ if typespec.size == mesg.args.size
44
+ match = true
45
+ typespec.size.times do |i|
46
+ c = typespec[i]
47
+ case c
48
+ when ?i, ?f
49
+ match &&= (Numeric === mesg.args[i])
50
+ when ?s, ?b
51
+ match &&= (String === mesg.args[i])
52
+ end
53
+ end
54
+ if match
55
+ typespec.size.times do |i|
56
+ case typespec[i]
57
+ when ?i
58
+ mesg.args[i] = mesg.args[i].to_i
59
+ when ?f
60
+ mesg.args[i] = mesg.args[i].to_f
61
+ when ?s,?b
62
+ mesg.args[i] = mesg.args[i].to_s
63
+ mesg.args[i] = mesg.args[i].to_s
64
+ end
65
+ end
66
+
67
+ obj.call(mesg)
68
+ end
69
+ end
70
+ else # no typespec
71
+ obj.call(mesg)
72
+ end
73
+ end # pattern match
74
+ end # @cb.each
75
+ end # unless @cb.nil?
76
+ else
77
+ raise "bad mesg"
78
+ end
79
+ end
80
+
81
+ # May create a new thread to wait to dispatch according to p.timetag.
82
+ def dispatch_bundle(p)
83
+ diff = p.timetag.to_f - TimeTag.now.to_f
84
+ if diff <= 0
85
+ p.each {|m| m.source = p.source; dispatch m}
86
+ else
87
+ Thread.new do
88
+ sleep diff
89
+ p.each {|m| m.source = p.source; dispatch m}
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,42 @@
1
+ module OSC
2
+ # Mixin for OSC transports. You implement (or in many cases just alias)
3
+ # send_raw, recvfrom_raw, and recv_raw, which have the semantics of send,
4
+ # recvfrom, and recv in e.g. UDPSocket
5
+ module Transport
6
+ # Send a Message, Bundle, or even raw data
7
+ def send(msg, *args)
8
+ case msg
9
+ when Message,Bundle
10
+ send_raw(msg.encode, *args)
11
+ else
12
+ send_raw(msg, *args)
13
+ end
14
+ end
15
+
16
+ # Receive a Message, Bundle, or raw data and the sender. The source
17
+ # attribute of the Message or Bundle is also set to sender. e.g.
18
+ # packet, sender = udp_osc_client.recvfrom(32768)
19
+ def recvfrom(*args)
20
+ data, sender = recvfrom_raw(*args)
21
+ m = Packet.decode(data)
22
+ m.source = sender
23
+ [m, sender]
24
+ rescue
25
+ [data, sender]
26
+ end
27
+
28
+ # Receive a Message, Bundle, or raw data.
29
+ def recv(*args)
30
+ data = recv_raw(*args)
31
+ Packet.decode(data)
32
+ rescue
33
+ end
34
+
35
+ # Send a Message/Bundle with a timestamp (a Time or TimeTag object).
36
+ def send_timestamped(msg, ts, *args)
37
+ m = Bundle.new(ts, msg)
38
+ send(m, *args)
39
+ end
40
+ alias :send_ts :send_timestamped
41
+ end
42
+ end
data/lib/osc/udp.rb ADDED
@@ -0,0 +1,30 @@
1
+ require 'osc/transport'
2
+ require 'socket'
3
+
4
+ module OSC
5
+ # A ::UDPSocket with a send method that accepts a Message or Bundle or
6
+ # a raw String.
7
+ class UDPSocket < ::UDPSocket
8
+ alias :send_raw :send
9
+ alias :recvfrom_raw :recvfrom
10
+ alias :recv_raw :recv
11
+ include Transport
12
+ end
13
+
14
+ class UDPServer < OSC::UDPSocket
15
+ MAX_MSG_SIZE=32768
16
+ include Server
17
+ def serve
18
+ loop do
19
+ p, sender = recvfrom(MAX_MSG_SIZE)
20
+ dispatch p
21
+ end
22
+ end
23
+
24
+ # send msg2 as a reply to msg1
25
+ def reply(msg1, msg2)
26
+ domain, port, host, ip = msg2.source
27
+ send(msg2, 0, host, port)
28
+ end
29
+ end
30
+ end
data/lib/rosc.rb ADDED
@@ -0,0 +1,8 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'osc'
5
+
6
+ module Rosc
7
+ VERSION = '0.0.1'
8
+ end
data/script/console ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ # File: script/console
3
+ irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
4
+
5
+ libs = " -r irb/completion"
6
+ # Perhaps use a console_lib to store any extra methods I may want available in the cosole
7
+ # libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
8
+ libs << " -r #{File.dirname(__FILE__) + '/../lib/rosc.rb'}"
9
+ puts "Loading rosc gem"
10
+ exec "#{irb} #{libs} --simple-prompt"
data/script/destroy ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/destroy'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Destroy.new.run(ARGV)
data/script/generate ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
3
+
4
+ begin
5
+ require 'rubigen'
6
+ rescue LoadError
7
+ require 'rubygems'
8
+ require 'rubigen'
9
+ end
10
+ require 'rubigen/scripts/generate'
11
+
12
+ ARGV.shift if ['--help', '-h'].include?(ARGV[0])
13
+ RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
14
+ RubiGen::Scripts::Generate.new.run(ARGV)
data/test/test_osc.rb ADDED
@@ -0,0 +1,160 @@
1
+ $:.unshift(File.dirname(__FILE__) + "/../lib/")
2
+ require 'osc'
3
+ require 'time'
4
+ require 'test/unit'
5
+
6
+ class TC_OSC < Test::Unit::TestCase
7
+ include OSC
8
+ # def setup
9
+ # end
10
+
11
+ # def teardown
12
+ # end
13
+
14
+ def test_datatype
15
+ s = 'foo'
16
+ i = 42
17
+ f = 3.14
18
+
19
+ assert_equal 'i', Packet.tag(i)
20
+ assert_equal 'f', Packet.tag(f)
21
+ assert_equal 's', Packet.tag(s)
22
+ assert_equal s+"\000", Packet.encode(s)
23
+ b = Blob.new("foobardoobar\0\0x200")
24
+ assert_equal 'b', Packet.tag(b)
25
+ assert_equal b.size+4 + (b.size+4)%4, Packet.encode(b).size
26
+ end
27
+
28
+ def test_timetag
29
+ t1 = TimeTag::JAN_1970
30
+ t2 = Time.now
31
+ t3 = t2.to_f+t1
32
+
33
+ tt = TimeTag.new t2
34
+ assert_equal t3, tt.to_f
35
+ assert_equal t3.floor, tt.to_i
36
+ assert_equal t3.floor - t3, tt.to_i - tt.to_f
37
+ assert_equal [0,1].pack('NN'), Packet.encode(TimeTag.new(nil))
38
+ assert_equal t2.to_i,tt.to_time.to_i # to_f has roundoff error at the lsb
39
+ end
40
+
41
+ def test_message
42
+ a = 'foo'
43
+ b = 'quux'
44
+ m = Message.new '/foobar', 'ssi', a, b, 1
45
+ assert_equal "/foobar\000"+",ssi\000\000\000\000"+
46
+ "foo\000"+"quux\000\000\000\000"+"\000\000\000\001", Packet.encode(m)
47
+ end
48
+
49
+ def test_bundle
50
+ m1 = Message.new '/foo','s','foo'
51
+ m2 = Message.new '/bar','s','bar'
52
+ t = Time.now
53
+ b = Bundle.new(TimeTag.new(Time.at(t + 10)), m1, m2)
54
+ b2 = Bundle.new(nil, b, m1)
55
+
56
+ assert_equal 10, b.timetag.to_time.to_i - t.to_i
57
+ e = Packet.encode(b2)
58
+ assert_equal '#bundle', e[0,7]
59
+ assert_equal "\000\000\000\000\000\000\000\001", e[8,8]
60
+ assert_equal '#bundle', e[16+4,7]
61
+ assert_equal '/foo', e[16+4+Packet.encode(b).size+4,4]
62
+ assert_equal 0, e.size % 4
63
+
64
+ assert_instance_of Array, b2.to_a
65
+ assert_instance_of Bundle, b2.to_a[0]
66
+ assert_instance_of Message, b2.to_a[1]
67
+
68
+ bundle = Packet.decode(e)
69
+ assert_instance_of Bundle, bundle
70
+
71
+
72
+ end
73
+
74
+
75
+
76
+ def test_packet
77
+ m = Message.new '/foo','s','foo'
78
+ b = Bundle.new nil,m
79
+
80
+ m2 = Packet.decode("/foo\000\000\000\000,s\000\000foo\000")
81
+ assert_equal m.address,m2.address
82
+ m2 = Packet.decode(Packet.encode(m))
83
+ assert_equal m.address,m2.address
84
+ assert_equal m.typetag,m2.typetag
85
+ assert_equal m.args.size,m2.args.size
86
+ b2 = Packet.decode(Packet.encode(b))
87
+ assert_equal b.args.size,b2.args.size
88
+ end
89
+
90
+ class TestServer
91
+ include OSC::Transport
92
+ include OSC::Server
93
+ def test_request(p)
94
+ send p
95
+ end
96
+
97
+ def send_raw(msg, *args)
98
+ dispatch msg
99
+ end
100
+
101
+ end
102
+
103
+ def test_server
104
+ s = TestServer.new
105
+ s.add_method('/foo/bar',nil) { |msg|
106
+ assert_equal 'si',msg.typetag
107
+ assert_equal 'Hello, World!',msg[0]
108
+ assert_equal 42,msg[1]
109
+ }
110
+ s.test_request Message.new("/foo/bar",'si','Hello, World!',42)
111
+ end
112
+
113
+ def test_server_with_bundle
114
+ s = TestServer.new
115
+ s.add_method('/foo/bar',nil) { |msg|
116
+ assert_equal 'si',msg.typetag
117
+ assert_equal 'Hello, World!',msg[0]
118
+ assert_equal 42,msg[1]
119
+ }
120
+ s.test_request Bundle.new(nil, Message.new("/foo/bar",'si','Hello, World!',42), Message.new("/foo/bar",'si','Hello, World!',42), Message.new("/foo/bar",'si','Hello, World!',42))
121
+ end
122
+
123
+ def test_pattern
124
+ # test *
125
+ assert Pattern.intersect?('/*/bar/baz','/foo/*/baz')
126
+ assert Pattern.intersect?('/f*','/*o')
127
+ assert ! Pattern.intersect?('/f*','/foo/bar')
128
+ assert ! Pattern.intersect?('/f*','/bar')
129
+ # test ?
130
+ assert Pattern.intersect?('/fo?/bar','/foo/?ar')
131
+ assert ! Pattern.intersect?('/foo?','/foo')
132
+ # test []
133
+ assert Pattern.intersect?('/foo/ba[rz]','/foo/bar')
134
+ assert Pattern.intersect?('/[!abcde]/a','/[!abcde]/a')
135
+ assert Pattern.intersect?('/[!abcde]/a','/f/a')
136
+ assert Pattern.intersect?('/[!abcde]/a','/[abf]/a')
137
+ assert ! Pattern.intersect?('/[ab]/a','/[!abc]/a')
138
+ assert ! Pattern.intersect?('/[abcde]','/[!abcde]')
139
+ assert ! Pattern.intersect?('/[abcde]','/f')
140
+ assert ! Pattern.intersect?('/[!abcde]','/a')
141
+ # test {}
142
+ assert Pattern.intersect?('/{foo,bar,baz}','/foo')
143
+ assert Pattern.intersect?('/{foo,bar,baz}','/bar')
144
+ assert Pattern.intersect?('/{foo,bar,baz}','/baz')
145
+ assert ! Pattern.intersect?('/{foo,bar,baz}','/quux')
146
+ assert ! Pattern.intersect?('/{foo,bar,baz}','/fo')
147
+ # * with *,?,[]
148
+ assert Pattern.intersect?('/*/bar','/*/ba?')
149
+ assert Pattern.intersect?('/*/bar','/*x/ba?')
150
+ assert Pattern.intersect?('/*/bar','/?/ba?')
151
+ assert Pattern.intersect?('/*/bar','/?x/ba?')
152
+ assert Pattern.intersect?('/*/bar','/[abcde]/ba?')
153
+ assert Pattern.intersect?('/*/bar','/[abcde]x/ba?')
154
+ assert Pattern.intersect?('/*/bar','/[!abcde]/ba?')
155
+ assert Pattern.intersect?('/*/bar','/[!abcde]x/ba?')
156
+ # ? with []
157
+ assert Pattern.intersect?('/?','/[abcde]')
158
+ assert Pattern.intersect?('/?','/[!abcde]')
159
+ end
160
+ end