osc-access 0.0.15
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/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
|