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/AUTHORS +11 -0
- data/ChangeLog +6 -0
- data/GPL.txt +340 -0
- data/History.txt +4 -0
- data/LICENSE +57 -0
- data/Manifest.txt +22 -0
- data/PostInstall.txt +7 -0
- data/README +55 -0
- data/README.rdoc +55 -0
- data/Rakefile +29 -0
- data/TODO +3 -0
- data/examples/readme.rb +19 -0
- data/lib/osc.rb +327 -0
- data/lib/osc/pattern.rb +131 -0
- data/lib/osc/server.rb +94 -0
- data/lib/osc/transport.rb +42 -0
- data/lib/osc/udp.rb +30 -0
- data/lib/rosc.rb +8 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/test/test_osc.rb +160 -0
- metadata +99 -0
data/PostInstall.txt
ADDED
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/examples/readme.rb
ADDED
@@ -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'
|