osc-access 0.0.15
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +13 -0
- data/README.rdoc +85 -0
- data/lib/osc-access.rb +33 -0
- data/lib/osc-access/accessible.rb +144 -0
- data/lib/osc-access/class.rb +34 -0
- data/lib/osc-access/class_scheme.rb +20 -0
- data/lib/osc-access/default.rb +7 -0
- data/lib/osc-access/emittable_property.rb +31 -0
- data/lib/osc-access/emitter.rb +30 -0
- data/lib/osc-access/message.rb +10 -0
- data/lib/osc-access/receiver.rb +75 -0
- data/lib/osc-access/translate.rb +127 -0
- data/lib/osc-access/zeroconf.rb +47 -0
- data/test/helper.rb +25 -0
- data/test/test_accessible.rb +334 -0
- data/test/test_class.rb +43 -0
- data/test/test_class_scheme.rb +32 -0
- data/test/test_emittable_property.rb +18 -0
- data/test/test_emitter.rb +71 -0
- data/test/test_message.rb +15 -0
- data/test/test_receiver.rb +58 -0
- data/test/test_translate.rb +86 -0
- metadata +99 -0
data/LICENSE
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2011-2012 Ari Russo
|
2
|
+
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
you may not use this file except in compliance with the License.
|
5
|
+
You may obtain a copy of the License at
|
6
|
+
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
See the License for the specific language governing permissions and
|
13
|
+
limitations under the License.
|
data/README.rdoc
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
= OSC Access
|
2
|
+
|
3
|
+
A simple way to bind {OSC}[http://en.wikipedia.org/wiki/Open_Sound_Control] directly to Ruby classes and objects.
|
4
|
+
|
5
|
+
== Features
|
6
|
+
|
7
|
+
* Works with MRI 1.9 and JRuby (in 1.9 mode)
|
8
|
+
* Binding OSC events can be done by class or by instance
|
9
|
+
* Network resources are invisible to the user even when shared or multiplexed
|
10
|
+
* Shortcuts for common tasks associated with OSC such as translating numeric values from one range to another
|
11
|
+
* Make OSC-accessible objects discoverable on a network using {Zeroconf}[http://en.wikipedia.org/wiki/Zero_configuration_networking]
|
12
|
+
|
13
|
+
== Requirements
|
14
|
+
|
15
|
+
Requires {eventmachine}[http://github.com/eventmachine/eventmachine] and {osc-ruby}[http://github.com/aberant/osc-ruby]. These should install automatically with the gem
|
16
|
+
|
17
|
+
For Zeroconf support, {dnssd}[http://github.com/tenderlove/dnssd] is required.
|
18
|
+
|
19
|
+
== Installation
|
20
|
+
|
21
|
+
gem install osc-access
|
22
|
+
|
23
|
+
== Usage
|
24
|
+
|
25
|
+
require "osc-access"
|
26
|
+
|
27
|
+
In this example when OSC messages for <em>/1/fader1</em> are received, <em>velocity=</em> is called and another message is outputted confirming that the first message was received
|
28
|
+
|
29
|
+
class Instrument
|
30
|
+
|
31
|
+
include OSCAccessible
|
32
|
+
|
33
|
+
osc_receive("/1/fader1", :translate => { :remote => 0..1, :local => 0..127 }) do |instance, val|
|
34
|
+
instance.velocity = val
|
35
|
+
instance.osc_send("/receipt", "val set to #{val}")
|
36
|
+
end
|
37
|
+
|
38
|
+
def velocity=(val)
|
39
|
+
puts "setting velocity to #{val}"
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
i = Instrument.new
|
45
|
+
i.osc_start(:input_port => 8000, :output => { :host => "192.168.1.8", :port => 9000}).join
|
46
|
+
|
47
|
+
The <em>:translate</em> option means that the first OSC argument will be translated from a number between 0 to 1 to the analogous value between 0 and 127 before being passed to the code block.
|
48
|
+
|
49
|
+
Or, here is an example that produces the same object, but it's created by passing a Hash map to the instance:
|
50
|
+
|
51
|
+
map = {
|
52
|
+
"/1/fader1" => {
|
53
|
+
:translate => { :remote => 0..1, :local => 0..127 }
|
54
|
+
:action => Proc.new do |instance, val|
|
55
|
+
instance.pitch = val
|
56
|
+
instance.osc_send("/receipt", "val set to #{val}")
|
57
|
+
end
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
class Instrument
|
62
|
+
|
63
|
+
include OSCAccessible
|
64
|
+
|
65
|
+
def pitch=(val)
|
66
|
+
p "setting pitch to #{val}"
|
67
|
+
...
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
i = Instrument.new
|
73
|
+
i.osc_start(:map => map, :input_port => 8000, :output => { :host => "192.168.1.8", :port => 9000}).join
|
74
|
+
|
75
|
+
{Here's a blog post}[http://tx81z.blogspot.com/2011/10/osc-access-build-osc-into-ruby-objects.html] that explains these examples in further depth and much more.
|
76
|
+
|
77
|
+
== Other Documentation
|
78
|
+
|
79
|
+
* {rdoc}[http://rubydoc.info/github/arirusso/osc-access]
|
80
|
+
|
81
|
+
== License
|
82
|
+
|
83
|
+
Licensed under Apache 2.0, See the file LICENSE
|
84
|
+
|
85
|
+
Copyright �2011-2012 Ari Russo
|
data/lib/osc-access.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# OSCAccess
|
4
|
+
# Control Ruby objects with OSC
|
5
|
+
# (c)2011-2012 Ari Russo and licensed under the Apache 2.0 License
|
6
|
+
#
|
7
|
+
|
8
|
+
# libs
|
9
|
+
require "osc-ruby"
|
10
|
+
require "osc-ruby/em_server"
|
11
|
+
|
12
|
+
# modules
|
13
|
+
require "osc-access/accessible"
|
14
|
+
require "osc-access/class"
|
15
|
+
|
16
|
+
# classes
|
17
|
+
require "osc-access/class_scheme"
|
18
|
+
require "osc-access/emittable_property"
|
19
|
+
require "osc-access/emitter"
|
20
|
+
require "osc-access/message"
|
21
|
+
require "osc-access/receiver"
|
22
|
+
require "osc-access/translate"
|
23
|
+
require "osc-access/zeroconf"
|
24
|
+
|
25
|
+
# other
|
26
|
+
require "osc-access/default"
|
27
|
+
|
28
|
+
module OSCAccess
|
29
|
+
|
30
|
+
VERSION = "0.0.15"
|
31
|
+
|
32
|
+
end
|
33
|
+
OSCAccessible = OSCAccess::Accessible
|
@@ -0,0 +1,144 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
module OSCAccess
|
3
|
+
|
4
|
+
module Accessible
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.extend(Class)
|
8
|
+
end
|
9
|
+
|
10
|
+
def osc_receive(pattern, options = {}, &block)
|
11
|
+
osc_initialize
|
12
|
+
@osc_receiver.add_receiver(self, pattern, options, &block)
|
13
|
+
if !options[:accessor].nil?
|
14
|
+
@osc_properties << EmittableProperty.new(options[:accessor], pattern, options)
|
15
|
+
elsif !options[:initialize].nil?
|
16
|
+
@osc_properties << EmittableProperty.new(options[:initialize], pattern, options)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def osc_send_property(prop)
|
21
|
+
prop = @osc_properties.find { |ep| ep.subject == prop } if prop.kind_of?(Symbol)
|
22
|
+
val = prop.translated(self)
|
23
|
+
msg = OSC::Message.new(prop.pattern, *val)
|
24
|
+
@osc_emitter.transmit(msg)
|
25
|
+
local_msg = OSC::Message.new(prop.pattern, *prop.value(self))
|
26
|
+
local_val = osc_process_arg_option(local_msg, :arg => prop.arg)
|
27
|
+
prop.action.call(self, local_val, local_msg) unless prop.action.nil?
|
28
|
+
end
|
29
|
+
|
30
|
+
def osc_send(*a)
|
31
|
+
osc_initialize
|
32
|
+
a.first.kind_of?(Symbol) ? osc_send_property(a.first) : @osc_emitter.transmit(*a)
|
33
|
+
end
|
34
|
+
|
35
|
+
def osc_join(options = {})
|
36
|
+
osc_initialize
|
37
|
+
@osc_receiver.join(options)
|
38
|
+
end
|
39
|
+
|
40
|
+
# osc_output takes arguments as such:
|
41
|
+
#
|
42
|
+
# osc_output(:host => "localhost", :port => 9000)
|
43
|
+
# osc_output(:host => "localhost", :port => [9000, 9002, 9005])
|
44
|
+
# osc_output(:host => "localhost", :port => 9000..9010)
|
45
|
+
#
|
46
|
+
def osc_output(pair)
|
47
|
+
osc_initialize
|
48
|
+
ports = osc_process_ports_args(pair[:port])
|
49
|
+
ports.each { |port| @osc_emitter.add_client(pair[:host], port) }
|
50
|
+
end
|
51
|
+
|
52
|
+
# osc_input takes arguments as such:
|
53
|
+
#
|
54
|
+
# osc_input(8000)
|
55
|
+
# osc_input(:port => 8000)
|
56
|
+
# osc_input(8000, 8005, 8010)
|
57
|
+
# osc_input(8000..8010)
|
58
|
+
#
|
59
|
+
def osc_input(*args)
|
60
|
+
osc_initialize
|
61
|
+
ports = osc_process_ports_args(args)
|
62
|
+
ports.each { |port| @osc_receiver.add_server(port) }
|
63
|
+
end
|
64
|
+
|
65
|
+
def osc_start(options = {})
|
66
|
+
osc_initialize(options)
|
67
|
+
osc_initialize_from_class_def
|
68
|
+
osc_load_map(options[:map]) unless options[:map].nil?
|
69
|
+
|
70
|
+
osc_input(options[:input_port]) unless options[:input_port].nil?
|
71
|
+
osc_output(options[:output]) unless options[:output].nil?
|
72
|
+
|
73
|
+
Thread.new do
|
74
|
+
sleep(1)
|
75
|
+
osc_send_all_properties
|
76
|
+
end
|
77
|
+
|
78
|
+
@osc_receiver.threads.first || Thread.new { Thread.abort_on_exception = true; loop {} }
|
79
|
+
end
|
80
|
+
|
81
|
+
def osc_send_all_properties
|
82
|
+
osc_initialize
|
83
|
+
@osc_properties.each { |prop| osc_send_property(prop) }
|
84
|
+
end
|
85
|
+
|
86
|
+
def osc_load_map(map)
|
87
|
+
osc_initialize
|
88
|
+
map.each { |attr, mapping| osc_add_map_row(attr, mapping) }
|
89
|
+
end
|
90
|
+
|
91
|
+
def osc_translate(value, range, options = {})
|
92
|
+
Translate.using(value, range, options)
|
93
|
+
end
|
94
|
+
|
95
|
+
protected
|
96
|
+
|
97
|
+
def osc_on_receive(msg, options = {}, &block)
|
98
|
+
val = osc_process_arg_option(msg, options)
|
99
|
+
val = osc_translate(val, options[:translate]) unless options[:translate].nil?
|
100
|
+
osc_send(msg) if options[:thru]
|
101
|
+
accessor = options[:accessor]
|
102
|
+
self.send("#{accessor.to_s}=", val) unless accessor.nil?
|
103
|
+
yield(self, val, msg) unless block.nil?
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def osc_initialize(options = {})
|
109
|
+
@osc_emitter ||= Emitter.new
|
110
|
+
@osc_properties ||= []
|
111
|
+
@osc_receiver ||= Receiver.new
|
112
|
+
end
|
113
|
+
|
114
|
+
def osc_initialize_from_class_def
|
115
|
+
scheme = self.class.osc_class_scheme
|
116
|
+
scheme.inputs.each { |port| @osc_receiver.add_server(port) }
|
117
|
+
scheme.outputs.each { |hash| @osc_emitter.add_client(hash) }
|
118
|
+
scheme.receivers.each { |hash| osc_receive(hash[:pattern], hash[:options], &hash[:action]) }
|
119
|
+
end
|
120
|
+
|
121
|
+
def osc_add_map_row(pattern, mapping)
|
122
|
+
options = mapping.kind_of?(Hash) ? mapping : {}
|
123
|
+
action = mapping.kind_of?(Hash) ? mapping[:action] : mapping
|
124
|
+
osc_receive(pattern, options, &action)
|
125
|
+
end
|
126
|
+
|
127
|
+
def osc_process_arg_option(msg, options = {})
|
128
|
+
arg = options[:arg] || 0
|
129
|
+
array = (!arg.nil? && arg == :all)
|
130
|
+
array ? msg.args : msg.args[arg]
|
131
|
+
end
|
132
|
+
|
133
|
+
def osc_process_ports_args(args)
|
134
|
+
case args
|
135
|
+
when Array then args.map { |a| osc_process_ports_args(a) }.flatten
|
136
|
+
when Hash then osc_process_ports_args(args[:port])
|
137
|
+
when Range then args.to_a
|
138
|
+
when Numeric then [args]
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
module OSCAccess
|
3
|
+
|
4
|
+
module Class
|
5
|
+
|
6
|
+
def osc_class_scheme
|
7
|
+
osc_ensure_initialized
|
8
|
+
@osc_class_scheme
|
9
|
+
end
|
10
|
+
|
11
|
+
def osc_receive(pattern, options = {}, &block)
|
12
|
+
osc_ensure_initialized
|
13
|
+
@osc_class_scheme.add_receiver(pattern, options, &block)
|
14
|
+
end
|
15
|
+
|
16
|
+
def osc_output(args)
|
17
|
+
osc_ensure_initialized
|
18
|
+
@osc_class_scheme.outputs << args
|
19
|
+
end
|
20
|
+
|
21
|
+
def osc_input(val)
|
22
|
+
osc_ensure_initialized
|
23
|
+
@osc_class_scheme.inputs << val
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def osc_ensure_initialized
|
29
|
+
@osc_class_scheme ||= ClassScheme.new
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
module OSCAccess
|
3
|
+
|
4
|
+
class ClassScheme
|
5
|
+
|
6
|
+
attr_reader :inputs,
|
7
|
+
:outputs,
|
8
|
+
:receivers
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@inputs, @outputs, @receivers = [], [], []
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_receiver(pattern, options = {}, &block)
|
15
|
+
@receivers << { :pattern => pattern, :options => options, :action => block }
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
module OSCAccess
|
3
|
+
|
4
|
+
class EmittableProperty
|
5
|
+
|
6
|
+
attr_reader :action, :arg, :pattern, :subject
|
7
|
+
|
8
|
+
def initialize(subject, pattern, options = {})
|
9
|
+
@subject = subject
|
10
|
+
@pattern = pattern
|
11
|
+
@arg = options[:arg]
|
12
|
+
@translate = options[:translate]
|
13
|
+
@action = options[:action]
|
14
|
+
end
|
15
|
+
|
16
|
+
def value(target_obj)
|
17
|
+
val = case @subject
|
18
|
+
when Proc then @subject.call(target_obj)
|
19
|
+
when Symbol then target_obj.send(@subject)
|
20
|
+
end
|
21
|
+
[val].flatten
|
22
|
+
end
|
23
|
+
|
24
|
+
def translated(target_obj)
|
25
|
+
raw_val = value(target_obj)
|
26
|
+
@translate.nil? ? raw_val : Translate.using(raw_val, @translate, :to_local => false)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
module OSCAccess
|
3
|
+
|
4
|
+
class Emitter
|
5
|
+
|
6
|
+
attr_reader :clients
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@clients = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def add_client(host, port)
|
13
|
+
@clients << self.class.client(host, port)
|
14
|
+
end
|
15
|
+
|
16
|
+
def transmit(*a)
|
17
|
+
msg = a.first.kind_of?(OSC::Message) ? a.first : OSC::Message.new(*a)
|
18
|
+
@clients.each { |c| c.send(msg) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.client(host, port)
|
22
|
+
@clients ||= {}
|
23
|
+
@clients[host] ||= {}
|
24
|
+
@clients[host][port] ||= OSC::Client.new(host, port)
|
25
|
+
@clients[host][port]
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
module OSCAccess
|
3
|
+
|
4
|
+
class Receiver
|
5
|
+
|
6
|
+
attr_reader :servers
|
7
|
+
|
8
|
+
def initialize(options = {})
|
9
|
+
@receivers = []
|
10
|
+
@servers = {}
|
11
|
+
@cached_message = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def threads
|
15
|
+
@servers.values.map { |server| server[:thread] }
|
16
|
+
end
|
17
|
+
|
18
|
+
def join(options = {})
|
19
|
+
port = options[:port]
|
20
|
+
thread = port.nil? ? threads.last : @servers[port][:thread]
|
21
|
+
thread.join
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_server(port)
|
25
|
+
server = self.class.server(port)
|
26
|
+
@servers[port] = server
|
27
|
+
@receivers.each { |receiver| add_method(server[:server], receiver) }
|
28
|
+
@servers[port][:thread]
|
29
|
+
end
|
30
|
+
|
31
|
+
def add_receiver(target_obj, pattern, options = {}, &block)
|
32
|
+
receiver = {
|
33
|
+
:target_obj => target_obj,
|
34
|
+
:pattern => pattern,
|
35
|
+
:options => options,
|
36
|
+
:action => block
|
37
|
+
}
|
38
|
+
@receivers << receiver
|
39
|
+
@servers.values.each { |server| add_method(server[:server], receiver) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.server(port)
|
43
|
+
@servers ||= {}
|
44
|
+
@servers[port] ||= { :server => OSC::EMServer.new(port) }
|
45
|
+
@servers[port][:thread] ||= thread_for(port)
|
46
|
+
@servers[port]
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.thread_for(port)
|
50
|
+
@servers[port][:thread] ||= Thread.new do
|
51
|
+
@servers[port][:service] ||= Zeroconf::Service.new("Ruby", port).start
|
52
|
+
Thread.abort_on_exception = true
|
53
|
+
@servers[port][:server].run
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def add_method(server, receiver)
|
60
|
+
options = receiver[:options]
|
61
|
+
obj = receiver[:target_obj]
|
62
|
+
pattern = receiver[:pattern].dup
|
63
|
+
# this prevents the same action being called multiple times when
|
64
|
+
# an receiver object has multiple servers
|
65
|
+
server.add_method(pattern) do |message|
|
66
|
+
unless @cached_message === message
|
67
|
+
obj.send(:osc_on_receive, message, options, &receiver[:action])
|
68
|
+
@cached_message = message
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|