rosc 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ require 'rake/testtask'
2
+ require 'rake/rdoctask'
3
+
4
+ require 'echoe'
5
+ Echoe.new('rosc')
6
+
7
+ task :default => :rdoc
8
+ Rake::RDocTask.new do |rd|
9
+ rd.rdoc_files.add ['README','AUTHORS','TODO','lib/**/*.rb']
10
+ #rd.template = ENV['HOME']+'/src/allison/allison.rb'
11
+ rd.rdoc_dir = 'doc'
12
+ rd.options = ['-x_darcs','-xtest']
13
+ rd.title = 'rosc'
14
+ rd.options += ['--line-numbers','--inline-source']
15
+ end
16
+
17
+ Rake::TestTask.new do |t|
18
+ #t.verbose = true
19
+ end
20
+
21
+ # vim: filetype=ruby
data/TODO ADDED
@@ -0,0 +1,3 @@
1
+ - nonstandard types
2
+ - TCP and UNIX sockets
3
+ - OSC urls
@@ -0,0 +1,19 @@
1
+ require 'osc'
2
+ Host = 'localhost'
3
+ Port = 5000
4
+
5
+ s = OSC::UDPServer.new
6
+ s.bind Host, Port
7
+
8
+ c = OSC::UDPSocket.new
9
+ m = OSC::Message.new('/foo', 'fi', Math::PI, 42)
10
+ c.send m, 0, Host, Port
11
+
12
+ s.add_method '/f*', 'fi' do |msg|
13
+ domain, port, host, ip = msg.source
14
+ puts "#{msg.address} -> #{msg.args.inspect} from #{host}:#{port}"
15
+ end
16
+ Thread.new do
17
+ s.serve
18
+ end
19
+ sleep 5
data/lib/osc.rb ADDED
@@ -0,0 +1,324 @@
1
+ require 'time'
2
+ require 'forwardable'
3
+ require 'stringio'
4
+ require 'yaml'
5
+
6
+ # Test for broken pack/unpack
7
+ if [1].pack('n') == "\001\000"
8
+ class String
9
+ alias_method :broken_unpack, :unpack
10
+ def unpack(spec)
11
+ broken_unpack(spec.tr("nNvV","vVnN"))
12
+ end
13
+ end
14
+ class Array
15
+ alias_method :broken_pack, :pack
16
+ def pack(spec)
17
+ broken_pack(spec.tr("nNvV","vVnN"))
18
+ end
19
+ end
20
+ end
21
+
22
+
23
+ class StringIO
24
+ def skip(n)
25
+ self.seek(n, IO::SEEK_CUR)
26
+ end
27
+ def skip_padding
28
+ self.skip((4-pos)%4)
29
+ end
30
+ end
31
+
32
+ # Of particular interest are OSC::Client, OSC::Server, OSC::Message and
33
+ # OSC::Bundle.
34
+ module OSC
35
+ # 64-bit big-endian fixed-point time tag
36
+ class TimeTag
37
+ JAN_1970 = 0x83aa7e80
38
+ # nil:: immediately
39
+ # Numeric:: seconds since January 1, 1900 00:00
40
+ # Numeric,Numeric:: int,frac parts of a TimeTag.
41
+ # Time:: convert from Time object
42
+ def initialize(*args)
43
+ t = args
44
+ t = t.first if t and t.size == 1
45
+ case t
46
+ when NIL # immediately
47
+ @int = 0
48
+ @frac = 1
49
+ when Numeric
50
+ @int, fr = t.divmod(1)
51
+ @frac = (fr * (2**32)).to_i
52
+ when Array
53
+ @int,@frac = t
54
+ when Time
55
+ @int, fr = (t.to_f+JAN_1970).divmod(1)
56
+ @frac = (fr * (2**32)).to_i
57
+ else
58
+ raise ArgumentError
59
+ end
60
+ end
61
+ attr_accessor :int, :frac
62
+ def to_i; to_f.to_i; end
63
+ # Ruby's Float can handle the 64 bits so we have the luxury of dealing with
64
+ # Float directly
65
+ def to_f; @int.to_f + @frac.to_f/(2**32); end
66
+ # [int,frac]
67
+ def to_a; [@int,@frac]; end
68
+ # Human-readable, like the output of Time#to_s
69
+ def to_s; to_time.to_s; end
70
+ # Ruby Time object
71
+ def to_time; Time.at(to_f-JAN_1970); end
72
+ alias :time :to_time
73
+ def self.now; TimeTag.new(Time.now); end
74
+ def method_missing(sym, *args)
75
+ time.__send__(sym, *args)
76
+ end
77
+ def to_yaml
78
+ to_a.to_yaml
79
+ end
80
+ end
81
+
82
+ class Blob < String
83
+ end
84
+
85
+ class Message
86
+ attr_accessor :address, :args
87
+ # The source of this message, usually something like ["AF_INET", 50475,
88
+ # 'localhost','127.0.0.1']
89
+ attr_accessor :source
90
+
91
+ # address:: The OSC address (a String)
92
+ # types:: The OSC type tags string
93
+ # args:: arguments. must match type tags in arity
94
+ #
95
+ # Example:
96
+ # Message.new('/foo','ff', Math::PI, Math::E)
97
+ #
98
+ # Arguments will be coerced as indicated by the type tags. If types is nil,
99
+ # type tags will be inferred from arguments.
100
+ def initialize(address, types=nil, *args)
101
+ if types and types.size != args.size
102
+ raise ArgumentError, 'type/args arity mismatch'
103
+ end
104
+
105
+ @address = address
106
+ @args = []
107
+
108
+ if types
109
+ args.each_with_index do |arg, i|
110
+ case types[i]
111
+ when ?i; @args << arg.to_i
112
+ when ?f; @args << arg.to_f
113
+ when ?s; @args << arg.to_s
114
+ when ?b; @args << Blob.new(arg)
115
+ else
116
+ raise ArgumentError, "unknown type tag '#{@types[i].inspect}'"
117
+ end
118
+ end
119
+ else
120
+ args.each do |arg|
121
+ case arg
122
+ when Fixnum,Float,String,TimeTag,Blob
123
+ @args << arg
124
+ else
125
+ raise ArgumentError, "Object has unknown OSC type: '#{arg}'"
126
+ end
127
+ end
128
+ end
129
+ end
130
+
131
+ def types
132
+ @args.collect {|a| Packet.tag a}.join
133
+ end
134
+ alias :typetag :types
135
+
136
+ # Encode this message for transport
137
+ def encode
138
+ Packet.encode(self)
139
+ end
140
+ # string representation. *not* the raw representation, for that use
141
+ # encode.
142
+ def to_s
143
+ "#{address},#{types},#{args.collect{|a| a.to_s}.join(',')}"
144
+ end
145
+ def to_yaml
146
+ {'address'=>address, 'types'=>types, 'args'=>args}.to_yaml
147
+ end
148
+
149
+ extend Forwardable
150
+ include Enumerable
151
+
152
+ de = (Array.instance_methods - self.instance_methods)
153
+ de -= %w(assoc flatten flatten! pack rassoc transpose)
154
+ de += %w(include? sort)
155
+
156
+ def_delegators(:@args, *de)
157
+
158
+ undef_method :zip
159
+ end
160
+
161
+ # bundle of messages and/or bundles
162
+ class Bundle
163
+ attr_accessor :timetag
164
+ attr_accessor :args
165
+ attr_accessor :source
166
+ alias :timestamp :timetag
167
+ alias :messages :args
168
+ alias :contents :args
169
+ alias :to_a :args
170
+
171
+ # New bundle with time and messages
172
+ def initialize(t=nil, *args)
173
+ @timetag =
174
+ case t
175
+ when TimeTag
176
+ t
177
+ else
178
+ TimeTag.new(t)
179
+ end
180
+ @args = args
181
+ end
182
+
183
+ def to_yaml
184
+ {'timestamp'=>timetag, 'contents'=>contents}.to_yaml
185
+ end
186
+
187
+ extend Forwardable
188
+ include Enumerable
189
+
190
+ de = (Array.instance_methods - self.instance_methods)
191
+ de -= %w(assoc flatten flatten! pack rassoc transpose)
192
+ de += %w(include? sort)
193
+
194
+ def_delegators(:@args, *de)
195
+
196
+ undef_method :zip
197
+
198
+ def encode
199
+ Packet.encode(self)
200
+ end
201
+
202
+ end
203
+
204
+ # Unit of transmission. Really needs revamping
205
+ module Packet
206
+ # XXX I might fold this and its siblings back into the decode case
207
+ # statement
208
+ def self.decode_int32(io)
209
+ i = io.read(4).unpack('N')[0]
210
+ i = 2**32 - i if i > (2**31-1) # two's complement
211
+ i
212
+ end
213
+
214
+ def self.decode_float32(io)
215
+ f = io.read(4).unpack('g')[0]
216
+ f
217
+ end
218
+
219
+ def self.decode_string(io)
220
+ s = io.gets("\0").chomp("\0")
221
+ io.skip_padding
222
+ s
223
+ end
224
+
225
+ def self.decode_blob(io)
226
+ l = io.read(4).unpack('N')[0]
227
+ b = io.read(l)
228
+ io.skip_padding
229
+ b
230
+ end
231
+
232
+ def self.decode_timetag(io)
233
+ t1 = io.read(4).unpack('N')[0]
234
+ t2 = io.read(4).unpack('N')[0]
235
+ TimeTag.new [t1,t2]
236
+ end
237
+
238
+ # Takes a string containing one packet
239
+ def self.decode(packet)
240
+ # XXX I think it would have been better to use a StringScanner. Maybe I
241
+ # will convert it someday...
242
+ io = StringIO.new(packet)
243
+ id = decode_string(io)
244
+ if id == '#bundle'
245
+ b = Bundle.new(decode_timetag(io))
246
+ until io.eof?
247
+ l = io.read(4).unpack('N')[0]
248
+ s = io.read(l)
249
+ b << decode(s)
250
+ end
251
+ b
252
+ elsif id =~ /^\//
253
+ m = Message.new(id)
254
+ if io.getc == ?,
255
+ tags = decode_string(io)
256
+ tags.scan(/./) do |t|
257
+ case t
258
+ when 'i'
259
+ m << decode_int32(io)
260
+ when 'f'
261
+ m << decode_float32(io)
262
+ when 's'
263
+ m << decode_string(io)
264
+ when 'b'
265
+ m << decode_blob(io)
266
+
267
+ # right now we skip over nonstandard datatypes, but we'll want to
268
+ # add these datatypes too.
269
+ when /[htd]/; io.read(8)
270
+ when 'S'; decode_string(io)
271
+ when /[crm]/; io.read(4)
272
+ when /[TFNI\[\]]/;
273
+ end
274
+ end
275
+ end
276
+ m
277
+ end
278
+ end
279
+
280
+ def self.pad(s)
281
+ s + ("\000" * ((4 - s.size)%4))
282
+ end
283
+
284
+ def self.tag(o)
285
+ case o
286
+ when Fixnum; 'i'
287
+ when TimeTag; 't'
288
+ when Float; 'f'
289
+ when Blob; 'b'
290
+ when String; 's'
291
+ else; nil
292
+ end
293
+ end
294
+
295
+ def self.encode(o)
296
+ case o
297
+ when Fixnum; [o].pack 'N'
298
+ when Float; [o].pack 'g'
299
+ when Blob; pad([o.size].pack('N') + o)
300
+ when String; pad(o.sub(/\000.*\Z/, '') + "\000")
301
+ when TimeTag; o.to_a.pack('NN')
302
+
303
+ when Message
304
+ s = encode(o.address)
305
+ s << encode(','+o.types)
306
+ s << o.args.collect{|x| encode(x)}.join
307
+
308
+ when Bundle
309
+ s = encode('#bundle')
310
+ s << encode(o.timetag)
311
+ s << o.args.collect { |x|
312
+ x2 = encode(x); [x2.size].pack('N') + x2
313
+ }.join
314
+ end
315
+ end
316
+
317
+ private_class_method :decode_int32, :decode_float32, :decode_string,
318
+ :decode_blob, :decode_timetag
319
+ end
320
+ end
321
+
322
+ require 'osc/pattern'
323
+ require 'osc/server'
324
+ require 'osc/udp'
@@ -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