osc-access 0.0.15

Sign up to get free protection for your applications and to get access to all the features.
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.
@@ -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
@@ -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,7 @@
1
+ #!/usr/bin/env ruby
2
+ module OSCAccess
3
+
4
+ DefaultPattern = /.*/
5
+ DefaultRemoteRange = 0..1
6
+
7
+ 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,10 @@
1
+ #!/usr/bin/env ruby
2
+ module OSC
3
+
4
+ class Message
5
+
6
+ alias_method :args, :to_a
7
+
8
+ end
9
+
10
+ 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