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 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