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.
data/PostInstall.txt ADDED
@@ -0,0 +1,7 @@
1
+
2
+ For more information on rosc, see http://rosc.rubyforge.org
3
+
4
+ NOTE: Change this information in PostInstall.txt
5
+ You can also delete it if you don't want it.
6
+
7
+
data/README ADDED
@@ -0,0 +1,55 @@
1
+ = rosc - OpenSound Control for Ruby
2
+ == Synopsis
3
+
4
+ require 'osc'
5
+
6
+ Host = 'localhost'
7
+ Port = 5000
8
+
9
+ s = OSC::UDPServer.new
10
+ s.bind Host, Port
11
+
12
+ c = OSC::UDPSocket.new
13
+ m = OSC::Message.new('/foo', 'fi', Math::PI, 42)
14
+ c.send m, 0, Host, Port
15
+
16
+ s.add_method '/f*', 'fi' do |msg|
17
+ domain, port, host, ip = msg.source
18
+ puts "#{msg.address} -> #{msg.args.inspect} from #{host}:#{port}"
19
+ end
20
+ Thread.new do
21
+ s.serve
22
+ end
23
+ sleep 5
24
+
25
+ #=> /foo -> [3.14159274101257, 42] from localhost:50843
26
+
27
+ == Requirements
28
+ - Ruby
29
+
30
+ == Installation
31
+
32
+ sudo ruby setup.rb
33
+
34
+ == Details
35
+ See the OSC home page[1], especially the "State of the Art" paper (for an
36
+ overview) and the specification. This library makes OSC easy, but you will
37
+ still need to understand OSC concepts and limitations.
38
+
39
+ The important classes are Message, Bundle, UDPSocket, and UDPServer. If you
40
+ want to make your own server on a different transport (e.g. TCP or UNIX
41
+ sockets, which are still on the TODO list), you will want to use the Server
42
+ mixin.
43
+
44
+ Please read the AUTHORS file for credits and see the TODO list for planned
45
+ enhancements.
46
+
47
+ 1. http://www.cnmat.berkeley.edu/OpenSoundControl
48
+
49
+ == Examples
50
+ Send me your interesting examples and I'll include them.
51
+
52
+ == License
53
+ Copyright (C) 2007 Hans Fugal and Tadayoshi Funaba
54
+
55
+ Distributed under Ruby's license. See the LICENSE file.
data/README.rdoc ADDED
@@ -0,0 +1,55 @@
1
+ = rosc - OpenSound Control for Ruby
2
+ == Synopsis
3
+
4
+ require 'osc'
5
+
6
+ Host = 'localhost'
7
+ Port = 5000
8
+
9
+ s = OSC::UDPServer.new
10
+ s.bind Host, Port
11
+
12
+ c = OSC::UDPSocket.new
13
+ m = OSC::Message.new('/foo', 'fi', Math::PI, 42)
14
+ c.send m, 0, Host, Port
15
+
16
+ s.add_method '/f*', 'fi' do |msg|
17
+ domain, port, host, ip = msg.source
18
+ puts "#{msg.address} -> #{msg.args.inspect} from #{host}:#{port}"
19
+ end
20
+ Thread.new do
21
+ s.serve
22
+ end
23
+ sleep 5
24
+
25
+ #=> /foo -> [3.14159274101257, 42] from localhost:50843
26
+
27
+ == Requirements
28
+ - Ruby
29
+
30
+ == Installation
31
+
32
+ sudo ruby setup.rb
33
+
34
+ == Details
35
+ See the OSC home page[1], especially the "State of the Art" paper (for an
36
+ overview) and the specification. This library makes OSC easy, but you will
37
+ still need to understand OSC concepts and limitations.
38
+
39
+ The important classes are Message, Bundle, UDPSocket, and UDPServer. If you
40
+ want to make your own server on a different transport (e.g. TCP or UNIX
41
+ sockets, which are still on the TODO list), you will want to use the Server
42
+ mixin.
43
+
44
+ Please read the AUTHORS file for credits and see the TODO list for planned
45
+ enhancements.
46
+
47
+ 1. http://www.cnmat.berkeley.edu/OpenSoundControl
48
+
49
+ == Examples
50
+ Send me your interesting examples and I'll include them.
51
+
52
+ == License
53
+ Copyright (C) 2007 Hans Fugal and Tadayoshi Funaba
54
+
55
+ Distributed under Ruby's license. See the LICENSE file.
data/Rakefile ADDED
@@ -0,0 +1,29 @@
1
+ require 'rubygems' unless ENV['NO_RUBYGEMS']
2
+ %w[rake rake/clean fileutils newgem rubigen].each { |f| require f }
3
+ require File.dirname(__FILE__) + '/lib/rosc'
4
+
5
+ # Generate all the Rake tasks
6
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
7
+ $hoe = Hoe.new('rosc', Rosc::VERSION) do |p|
8
+ p.developer('Hans Fugal', 'hans@fugal.net')
9
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
10
+ p.post_install_message = 'PostInstall.txt' # TODO remove if post-install message not required
11
+ # p.rubyforge_name = p.name # TODO this is default value
12
+ # p.extra_deps = [
13
+ # ['activesupport','>= 2.0.2'],
14
+ # ]
15
+ p.extra_dev_deps = [
16
+ ['newgem', ">= #{::Newgem::VERSION}"]
17
+ ]
18
+
19
+ p.clean_globs |= %w[**/.DS_Store tmp *.log]
20
+ # path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
21
+ # p.remote_rdoc_dir = File.join(path.gsub(/^#{p.rubyforge_name}\/?/,''), 'rdoc')
22
+ p.rsync_args = '-av --delete --ignore-errors'
23
+ end
24
+
25
+ require 'newgem/tasks' # load /tasks/*.rake
26
+ Dir['tasks/**/*.rake'].each { |t| load t }
27
+
28
+ # TODO - want other tests/tasks run by default? Add them to the list
29
+ # task :default => [:spec, :features]
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,327 @@
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 = ''
221
+ until (c = io.getc) == 0
222
+ s << c
223
+ end
224
+ io.skip_padding
225
+ s
226
+ end
227
+
228
+ def self.decode_blob(io)
229
+ l = io.read(4).unpack('N')[0]
230
+ b = io.read(l)
231
+ io.skip_padding
232
+ b
233
+ end
234
+
235
+ def self.decode_timetag(io)
236
+ t1 = io.read(4).unpack('N')[0]
237
+ t2 = io.read(4).unpack('N')[0]
238
+ TimeTag.new [t1,t2]
239
+ end
240
+
241
+ # Takes a string containing one packet
242
+ def self.decode(packet)
243
+ # XXX I think it would have been better to use a StringScanner. Maybe I
244
+ # will convert it someday...
245
+ io = StringIO.new(packet)
246
+ id = decode_string(io)
247
+ if id == '#bundle'
248
+ b = Bundle.new(decode_timetag(io))
249
+ until io.eof?
250
+ l = io.read(4).unpack('N')[0]
251
+ s = io.read(l)
252
+ b << decode(s)
253
+ end
254
+ b
255
+ elsif id =~ /^\//
256
+ m = Message.new(id)
257
+ if io.getc == ?,
258
+ tags = decode_string(io)
259
+ tags.scan(/./) do |t|
260
+ case t
261
+ when 'i'
262
+ m << decode_int32(io)
263
+ when 'f'
264
+ m << decode_float32(io)
265
+ when 's'
266
+ m << decode_string(io)
267
+ when 'b'
268
+ m << decode_blob(io)
269
+
270
+ # right now we skip over nonstandard datatypes, but we'll want to
271
+ # add these datatypes too.
272
+ when /[htd]/; io.read(8)
273
+ when 'S'; decode_string(io)
274
+ when /[crm]/; io.read(4)
275
+ when /[TFNI\[\]]/;
276
+ end
277
+ end
278
+ end
279
+ m
280
+ end
281
+ end
282
+
283
+ def self.pad(s)
284
+ s + ("\000" * ((4 - s.size)%4))
285
+ end
286
+
287
+ def self.tag(o)
288
+ case o
289
+ when Fixnum; 'i'
290
+ when TimeTag; 't'
291
+ when Float; 'f'
292
+ when Blob; 'b'
293
+ when String; 's'
294
+ else; nil
295
+ end
296
+ end
297
+
298
+ def self.encode(o)
299
+ case o
300
+ when Fixnum; [o].pack 'N'
301
+ when Float; [o].pack 'g'
302
+ when Blob; pad([o.size].pack('N') + o)
303
+ when String; pad(o.sub(/\000.*\Z/, '') + "\000")
304
+ when TimeTag; o.to_a.pack('NN')
305
+
306
+ when Message
307
+ s = encode(o.address)
308
+ s << encode(','+o.types)
309
+ s << o.args.collect{|x| encode(x)}.join
310
+
311
+ when Bundle
312
+ s = encode('#bundle')
313
+ s << encode(o.timetag)
314
+ s << o.args.collect { |x|
315
+ x2 = encode(x); [x2.size].pack('N') + x2
316
+ }.join
317
+ end
318
+ end
319
+
320
+ private_class_method :decode_int32, :decode_float32, :decode_string,
321
+ :decode_blob, :decode_timetag
322
+ end
323
+ end
324
+
325
+ require 'osc/pattern'
326
+ require 'osc/server'
327
+ require 'osc/udp'