patch 0.4.13
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.
- checksums.yaml +7 -0
- data/LICENSE +13 -0
- data/README.md +176 -0
- data/bin/patchrb +40 -0
- data/lib/patch/config.rb +124 -0
- data/lib/patch/em_patch.rb +47 -0
- data/lib/patch/hub.rb +68 -0
- data/lib/patch/io/midi/action.rb +42 -0
- data/lib/patch/io/midi/input.rb +110 -0
- data/lib/patch/io/midi/message.rb +112 -0
- data/lib/patch/io/midi/output.rb +58 -0
- data/lib/patch/io/midi.rb +45 -0
- data/lib/patch/io/module.rb +35 -0
- data/lib/patch/io/osc/action.rb +43 -0
- data/lib/patch/io/osc/client.rb +60 -0
- data/lib/patch/io/osc/message.rb +109 -0
- data/lib/patch/io/osc/server.rb +159 -0
- data/lib/patch/io/osc.rb +43 -0
- data/lib/patch/io/websocket/node.rb +103 -0
- data/lib/patch/io/websocket/socket.rb +103 -0
- data/lib/patch/io/websocket.rb +27 -0
- data/lib/patch/io.rb +15 -0
- data/lib/patch/log.rb +97 -0
- data/lib/patch/message.rb +67 -0
- data/lib/patch/node/container.rb +69 -0
- data/lib/patch/node/map.rb +71 -0
- data/lib/patch/node.rb +10 -0
- data/lib/patch/patch.rb +59 -0
- data/lib/patch/report.rb +132 -0
- data/lib/patch/thread.rb +19 -0
- data/lib/patch.rb +42 -0
- data/test/config/nodes.yml +16 -0
- data/test/config/patches.yml +41 -0
- data/test/config_test.rb +216 -0
- data/test/helper.rb +20 -0
- data/test/hub_test.rb +49 -0
- data/test/io/midi/action_test.rb +82 -0
- data/test/io/midi/input_test.rb +130 -0
- data/test/io/midi/message_test.rb +54 -0
- data/test/io/midi/output_test.rb +44 -0
- data/test/io/midi_test.rb +94 -0
- data/test/io/module_test.rb +21 -0
- data/test/io/osc/action_test.rb +76 -0
- data/test/io/osc/client_test.rb +49 -0
- data/test/io/osc/message_test.rb +53 -0
- data/test/io/osc/server_test.rb +116 -0
- data/test/io/osc_test.rb +111 -0
- data/test/io/websocket/node_test.rb +96 -0
- data/test/io/websocket_test.rb +37 -0
- data/test/js/logger.js +67 -0
- data/test/js/message.js +62 -0
- data/test/js/qunit-1.18.0.js +3828 -0
- data/test/js/qunit.css +291 -0
- data/test/js/test.html +15 -0
- data/test/js/websocket.js +12 -0
- data/test/log_test.rb +96 -0
- data/test/message_test.rb +109 -0
- data/test/node/container_test.rb +104 -0
- data/test/node/map_test.rb +50 -0
- data/test/node_test.rb +14 -0
- data/test/patch_test.rb +57 -0
- data/test/report_test.rb +37 -0
- metadata +320 -0
@@ -0,0 +1,112 @@
|
|
1
|
+
module Patch
|
2
|
+
|
3
|
+
module IO
|
4
|
+
|
5
|
+
module MIDI
|
6
|
+
|
7
|
+
# Convert between MIDI message objects and Patch::Message objects
|
8
|
+
module Message
|
9
|
+
|
10
|
+
extend self
|
11
|
+
|
12
|
+
# @param [::Patch::Patch] patch
|
13
|
+
# @param [Array<::Patch::Message>, ::Patch::Message] messages
|
14
|
+
# @return [Array<::MIDIMessage>]
|
15
|
+
def to_midi_messages(patch, patch_messages)
|
16
|
+
patch_messages = [patch_messages].flatten
|
17
|
+
midi_messages = patch_messages.map do |patch_message|
|
18
|
+
unless (action = patch.actions.at(patch_message.index)).nil?
|
19
|
+
to_midi_message(action, patch_message) unless action[:midi].nil?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
midi_messages.compact
|
23
|
+
end
|
24
|
+
|
25
|
+
# Convert the given MIDI message to Patch::Message objects using the context of the given patch
|
26
|
+
# @param [::Patch::Patch] patch
|
27
|
+
# @param [Array<MIDIMessage>, MIDIMessage] midi_messages
|
28
|
+
# @return [Array<::Patch::Message>]
|
29
|
+
def to_patch_messages(patch, midi_messages)
|
30
|
+
midi_messages = [midi_messages].flatten
|
31
|
+
patch_messages = midi_messages.map do |midi_message|
|
32
|
+
unless (action = Action.find_by_index(patch.actions, midi_message.index)).nil?
|
33
|
+
index = patch.actions.index(action)
|
34
|
+
to_patch_message(action, index, patch.name, midi_message)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
patch_messages.compact
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
# @param [Hash] action An action to contextualize the conversion
|
43
|
+
# @param [::Patch::Message] patch_message
|
44
|
+
def get_midi_value_from_action(action, patch_message)
|
45
|
+
to = action[:midi][:scale]
|
46
|
+
to ||= 0..127
|
47
|
+
from = action[:default][:scale] unless action[:default].nil?
|
48
|
+
from ||= to
|
49
|
+
get_value(patch_message.value, from, to)
|
50
|
+
end
|
51
|
+
|
52
|
+
# @param [Hash] action An action to contextualize the conversion
|
53
|
+
# @param [MIDIMessage] midi_message
|
54
|
+
def get_patch_values_from_action(action, midi_message)
|
55
|
+
from = action[:midi][:scale]
|
56
|
+
from ||= 0..127
|
57
|
+
to = action[:default][:scale] unless action[:default].nil?
|
58
|
+
to ||= from
|
59
|
+
get_value(midi_message.value, from, to)
|
60
|
+
end
|
61
|
+
|
62
|
+
# Convert a patch message to a MIDI message
|
63
|
+
# @param [Hash] action An action to contextualize the conversion
|
64
|
+
# @param [::Patch::Message] patch_message
|
65
|
+
# @return [::MIDIMessage::ControlChange, nil]
|
66
|
+
def to_midi_message(action, patch_message)
|
67
|
+
if !action[:midi].nil?
|
68
|
+
index = action[:midi][:index] || patch_message.index
|
69
|
+
channel = action[:midi][:channel] || 0
|
70
|
+
value = get_midi_value_from_action(action, patch_message)
|
71
|
+
MIDIMessage::ControlChange.new(channel, index, value)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Convert a MIDI message to a patch message
|
76
|
+
# @param [Hash] action An action to contextualize the conversion
|
77
|
+
# @param [Fixnum] index The index of the message
|
78
|
+
# @param [Symbol] patch_name A patch name
|
79
|
+
# @param [::MIDIMessage::ControlChange] midi_message
|
80
|
+
# @return [::Patch::Message, nil]
|
81
|
+
def to_patch_message(action, index, patch_name, midi_message)
|
82
|
+
if action[:midi][:channel].nil? || action[:midi][:channel] == midi_message.channel
|
83
|
+
value = get_patch_values_from_action(action, midi_message)
|
84
|
+
properties = {
|
85
|
+
:index => index,
|
86
|
+
:patch_name => patch_name,
|
87
|
+
:value => value
|
88
|
+
}
|
89
|
+
::Patch::Message.new(properties)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Translate a value
|
94
|
+
# @param [Fixnum] value
|
95
|
+
# @param [Range] from
|
96
|
+
# @param [Range] to
|
97
|
+
# @return [Fixnum]
|
98
|
+
def get_value(value, from, to)
|
99
|
+
if from == to
|
100
|
+
value
|
101
|
+
else
|
102
|
+
Scale.transform(value).from(from).to(to)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Patch
|
2
|
+
|
3
|
+
module IO
|
4
|
+
|
5
|
+
module MIDI
|
6
|
+
|
7
|
+
# MIDI Output functions
|
8
|
+
class Output
|
9
|
+
|
10
|
+
attr_reader :id, :device
|
11
|
+
|
12
|
+
# @param [Fixnum] id
|
13
|
+
# @param [String, UniMIDI::Output] device
|
14
|
+
# @param [Hash] options
|
15
|
+
# @option options [Debug] :log
|
16
|
+
def initialize(id, device, options = {})
|
17
|
+
@log = options[:log]
|
18
|
+
@id = id
|
19
|
+
@device = get_output(device)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Convert Patch::Message objects to MIDI and send
|
23
|
+
# @param [Patch::Patch] patch Context
|
24
|
+
# @param [Array<Patch::Message>, Patch::Message] messages Message(s) to send via MIDI
|
25
|
+
# @return [Array<MIDIMessage>]
|
26
|
+
def puts(patch, patch_messages)
|
27
|
+
patch_messages = [patch_messages].flatten
|
28
|
+
messages = ::Patch::IO::MIDI::Message.to_midi_messages(patch, patch_messages)
|
29
|
+
@device.puts(messages) unless messages.empty?
|
30
|
+
messages
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# Initialize the output device given a name or device object. If the name of the device is the string "choose",
|
36
|
+
# the user is prompted to select an availble MIDI output.
|
37
|
+
# @param [String, UniMIDI::Output, nil] device
|
38
|
+
# @return [UniMIDI::Output]
|
39
|
+
def get_output(device)
|
40
|
+
if device.kind_of?(String)
|
41
|
+
if device == "choose"
|
42
|
+
UniMIDI::Output.gets
|
43
|
+
else
|
44
|
+
UniMIDI::Output.find_by_name(device)
|
45
|
+
end
|
46
|
+
elsif device.respond_to?(:puts)
|
47
|
+
device.open if device.kind_of?(UniMIDI::Output)
|
48
|
+
device
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# Modules
|
2
|
+
require "patch/io/midi/action"
|
3
|
+
require "patch/io/midi/message"
|
4
|
+
# Classes
|
5
|
+
require "patch/io/midi/input"
|
6
|
+
require "patch/io/midi/output"
|
7
|
+
|
8
|
+
module Patch
|
9
|
+
|
10
|
+
module IO
|
11
|
+
|
12
|
+
# MIDI IO
|
13
|
+
module MIDI
|
14
|
+
|
15
|
+
# Key that will be used by Patch to identify the module
|
16
|
+
KEY = :midi
|
17
|
+
extend self
|
18
|
+
::Patch::IO::Module.add(self)
|
19
|
+
|
20
|
+
# Instantiate a MIDI device based on the given config
|
21
|
+
# @param [Hash] config
|
22
|
+
# @param [Hash] options
|
23
|
+
# @option options [Log] :log
|
24
|
+
# @return [MIDI::Input, MIDI::Output]
|
25
|
+
def new_from_config(config, options = {})
|
26
|
+
klass = get_direction_class(config[:direction])
|
27
|
+
klass.new(config[:id], config[:name], :log => options[:log])
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
# Get the direction class for the given key
|
33
|
+
# @param [Symbol] key
|
34
|
+
# @return [Class]
|
35
|
+
def get_direction_class(key)
|
36
|
+
case key.to_sym
|
37
|
+
when :input then Input
|
38
|
+
when :output then Output
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Patch
|
2
|
+
|
3
|
+
module IO
|
4
|
+
|
5
|
+
# Manage node modules
|
6
|
+
class Module
|
7
|
+
|
8
|
+
class << self
|
9
|
+
|
10
|
+
# Find an IO module by its key
|
11
|
+
# @param [Symbol] key
|
12
|
+
# @return [Module]
|
13
|
+
def find_by_key(key)
|
14
|
+
all.find { |mod| mod::KEY === key }
|
15
|
+
end
|
16
|
+
|
17
|
+
# Add an IO module to the list of modules available to Patch
|
18
|
+
# @param [Module] mod
|
19
|
+
# @return [Array<Module>]
|
20
|
+
def add(mod)
|
21
|
+
@modules ||= []
|
22
|
+
@modules << mod
|
23
|
+
end
|
24
|
+
|
25
|
+
# Mapping of node modules and names
|
26
|
+
# @return [Array<Module>]
|
27
|
+
def all
|
28
|
+
@modules ||= []
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Patch
|
2
|
+
|
3
|
+
module IO
|
4
|
+
|
5
|
+
module OSC
|
6
|
+
|
7
|
+
# Find and identify OSC Actions
|
8
|
+
module Action
|
9
|
+
|
10
|
+
extend self
|
11
|
+
|
12
|
+
# Is the given action OSC?
|
13
|
+
# @param [Hash] action
|
14
|
+
# @return [Boolean]
|
15
|
+
def osc?(action)
|
16
|
+
!action[:osc].nil?
|
17
|
+
end
|
18
|
+
|
19
|
+
# Filter the given actions only to return OSC actions
|
20
|
+
# @param [Array<Hash>] actions
|
21
|
+
# @return [Array<Hash>]
|
22
|
+
def osc_actions(actions)
|
23
|
+
actions.select { |action| osc?(action) }
|
24
|
+
end
|
25
|
+
|
26
|
+
# Find an action by its OSC address
|
27
|
+
# @param [Array<Hash>] actions
|
28
|
+
# @param [String] address
|
29
|
+
# @return [Hash]
|
30
|
+
def find_by_address(actions, address)
|
31
|
+
osc_actions(actions).find do |action|
|
32
|
+
regex = Regexp.new(action[:osc][:address])
|
33
|
+
address.match(regex)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Patch
|
2
|
+
|
3
|
+
module IO
|
4
|
+
|
5
|
+
module OSC
|
6
|
+
|
7
|
+
# OSC Client
|
8
|
+
class Client
|
9
|
+
|
10
|
+
attr_reader :id
|
11
|
+
|
12
|
+
# @param [String] host
|
13
|
+
# @param [Fixnum] port
|
14
|
+
# @param [Hash] options
|
15
|
+
# @option options [Fixnum] :id
|
16
|
+
# @option options [Log] :log
|
17
|
+
def initialize(host, port, options = {})
|
18
|
+
@id = options[:id]
|
19
|
+
@log = options[:log]
|
20
|
+
@client = ::OSC::Client.new(host, port)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Convert message objects to OSC and send
|
24
|
+
# @param [Patch::Patch] patch Context
|
25
|
+
# @param [Array<Patch::Message, ::OSC::Message>, ::OSC::Message, Patch::Message] messages Message(s) to send
|
26
|
+
# @return [Array<::OSC::Message>]]
|
27
|
+
def puts(patch, messages)
|
28
|
+
osc_messages = get_osc_messages(patch, messages)
|
29
|
+
osc_messages.each { |osc_message| @client.send(osc_message) }
|
30
|
+
osc_messages
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
# @param [::Patch::Patch] patch
|
36
|
+
# @param [Array<Patch::Message, ::OSC::Message>, ::OSC::Message, Patch::Message] messages Message(s) to send
|
37
|
+
# @return [Array<::OSC::Message>]]
|
38
|
+
def get_osc_messages(patch, messages)
|
39
|
+
messages = [messages].flatten
|
40
|
+
messages.map { |message| ensure_osc_message(patch, message) }
|
41
|
+
end
|
42
|
+
|
43
|
+
# @param [::Patch::Patch] patch
|
44
|
+
# @param [::OSC::Message, Patch::Message] message
|
45
|
+
# @return [::OSC::Message]
|
46
|
+
def ensure_osc_message(patch, message)
|
47
|
+
unless message.kind_of?(::OSC::Message)
|
48
|
+
osc_message = ::Patch::IO::OSC::Message.to_osc_messages(patch, message)
|
49
|
+
end
|
50
|
+
osc_message ||= message
|
51
|
+
osc_message
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module Patch
|
2
|
+
|
3
|
+
module IO
|
4
|
+
|
5
|
+
module OSC
|
6
|
+
|
7
|
+
# Convert between OSC message and Patch::Message objects
|
8
|
+
module Message
|
9
|
+
|
10
|
+
extend self
|
11
|
+
|
12
|
+
# Convert a message object to an OSC message given the context of the given patch
|
13
|
+
# @param [::Patch::Patch] patch
|
14
|
+
# @param [::Patch::Message] message
|
15
|
+
# @return [Array<::OSC::Message>]
|
16
|
+
def to_osc_messages(patch, patch_message)
|
17
|
+
messages = []
|
18
|
+
unless (action = get_osc_action(patch.actions, patch_message)).nil?
|
19
|
+
messages << get_osc_message(action, patch_message)
|
20
|
+
end
|
21
|
+
messages
|
22
|
+
end
|
23
|
+
|
24
|
+
# Convert the given OSC message to Patch::Message objects using the context of the given patch
|
25
|
+
# @param [::Patch::Patch] patch
|
26
|
+
# @param [Object] raw_osc
|
27
|
+
# @return [Array<::Patch::Message>]
|
28
|
+
def to_patch_messages(patch, raw_osc)
|
29
|
+
messages = []
|
30
|
+
unless (action = Action.find_by_address(patch.actions, raw_osc.address)).nil?
|
31
|
+
messages << get_patch_message(patch, action, raw_osc)
|
32
|
+
end
|
33
|
+
messages
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# @param [::Patch::Patch] patch
|
39
|
+
# @param [Hash] action
|
40
|
+
# @param [Object] raw_osc
|
41
|
+
# @return [::Patch::Message]
|
42
|
+
def get_patch_message(patch, action, raw_osc)
|
43
|
+
index = patch.actions.index(action)
|
44
|
+
values = get_patch_values_from_action(raw_osc, action)
|
45
|
+
properties = {
|
46
|
+
:index => index,
|
47
|
+
:patch_name => patch.name,
|
48
|
+
:value => values[0]
|
49
|
+
}
|
50
|
+
::Patch::Message.new(properties)
|
51
|
+
end
|
52
|
+
|
53
|
+
# @param [Hash] action
|
54
|
+
# @param [::Patch::Message] patch_message
|
55
|
+
# @return [::OSC::Message]
|
56
|
+
def get_osc_message(action, patch_message)
|
57
|
+
address = action[:osc][:address]
|
58
|
+
value = get_osc_value_from_action(patch_message.value, action)
|
59
|
+
::OSC::Message.new(address, value)
|
60
|
+
end
|
61
|
+
|
62
|
+
# @param [Object] raw_osc
|
63
|
+
# @param [Hash] action
|
64
|
+
# @return [Array<Object>]
|
65
|
+
def get_patch_values_from_action(raw_osc, action)
|
66
|
+
from = action[:osc][:scale]
|
67
|
+
to = action[:default][:scale] unless action[:default].nil?
|
68
|
+
to ||= from
|
69
|
+
raw_osc.to_a.map { |value| get_value(value.to_f, from, to) }
|
70
|
+
end
|
71
|
+
|
72
|
+
# @param [Object] value
|
73
|
+
# @param [Hash] action
|
74
|
+
# @return [Object]
|
75
|
+
def get_osc_value_from_action(value, action)
|
76
|
+
to = action[:osc][:scale]
|
77
|
+
from = action[:default][:scale] unless action[:default].nil?
|
78
|
+
from ||= to
|
79
|
+
get_value(value, from, to)
|
80
|
+
end
|
81
|
+
|
82
|
+
# @param [Array<Hash>] actions
|
83
|
+
# @param [::Patch::Message] patch_message
|
84
|
+
# @return [Hash]
|
85
|
+
def get_osc_action(actions, patch_message)
|
86
|
+
action = actions.at(patch_message.index)
|
87
|
+
action unless action.nil? || action[:osc].nil?
|
88
|
+
end
|
89
|
+
|
90
|
+
# Translate a value
|
91
|
+
# @param [Fixnum] value
|
92
|
+
# @param [Range] from
|
93
|
+
# @param [Range] to
|
94
|
+
# @return [Fixnum]
|
95
|
+
def get_value(value, from, to)
|
96
|
+
if from == to
|
97
|
+
value
|
98
|
+
else
|
99
|
+
Scale.transform(value).from(from).to(to)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
module Patch
|
2
|
+
|
3
|
+
module IO
|
4
|
+
|
5
|
+
module OSC
|
6
|
+
|
7
|
+
# OSC server
|
8
|
+
class Server
|
9
|
+
|
10
|
+
attr_reader :id
|
11
|
+
|
12
|
+
# @param [Fixnum] id
|
13
|
+
# @param [Fixnum] port
|
14
|
+
# @param [Hash] options
|
15
|
+
# @option options [Hash] :echo
|
16
|
+
# @option options [Log] :log
|
17
|
+
def initialize(id, port, options = {})
|
18
|
+
@log = options[:log]
|
19
|
+
@server = nil
|
20
|
+
@active = false
|
21
|
+
@id = id
|
22
|
+
@is_failsafe = true
|
23
|
+
|
24
|
+
configure(port, options)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Is the server active?
|
28
|
+
# @return [Boolean]
|
29
|
+
def active?
|
30
|
+
@active
|
31
|
+
end
|
32
|
+
|
33
|
+
# Start the server
|
34
|
+
# @return [Boolean] Whether the server was started
|
35
|
+
def start
|
36
|
+
@active = true
|
37
|
+
@connection = @server.run
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
# Stop the server
|
42
|
+
# @return [Boolean]
|
43
|
+
def stop
|
44
|
+
@active = false
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
# Disable the message handlers
|
49
|
+
# @return [Boolean]
|
50
|
+
def disable(patch)
|
51
|
+
addresses = get_addresses(patch)
|
52
|
+
addresses.select { |address| @server.remove_method(address) }.any?
|
53
|
+
end
|
54
|
+
|
55
|
+
# Listen for messages
|
56
|
+
# @param [::Patch::Patch] patch The patch to use for context
|
57
|
+
# @param [Proc] callback A callback to fire when messages are received
|
58
|
+
# @return [Boolean] Whether any actions were configured
|
59
|
+
def listen(patch, &callback)
|
60
|
+
addresses = get_addresses(patch)
|
61
|
+
addresses.select { |address| listen_for(address, patch, &callback) }.any?
|
62
|
+
end
|
63
|
+
|
64
|
+
protected
|
65
|
+
|
66
|
+
# @param [::Patch::Patch] patch The patch to use for context
|
67
|
+
# @return [Array<String, Regexp>]
|
68
|
+
def get_addresses(patch)
|
69
|
+
actions = ::Patch::IO::OSC::Action.osc_actions(patch.actions)
|
70
|
+
actions.map { |action| action[:osc][:address] }.compact.uniq
|
71
|
+
end
|
72
|
+
|
73
|
+
# Handle a new message
|
74
|
+
# @param [::Patch::Patch] patch A patch for context
|
75
|
+
# @param [OSC::Message] message The OSC message object
|
76
|
+
# @param [Proc] callback A callback to fire when a message or messages is received
|
77
|
+
# @return [Array<Patch::Message>]
|
78
|
+
def handle_message_received(patch, raw_osc, &callback)
|
79
|
+
messages = ::Patch::IO::OSC::Message.to_patch_messages(patch, raw_osc)
|
80
|
+
echo(patch, raw_osc) if echo?
|
81
|
+
# yield to custom behavior
|
82
|
+
yield(messages) if block_given?
|
83
|
+
messages
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
# @param [Fixnum] port
|
89
|
+
# @param [Hash] options
|
90
|
+
# @option options [Hash] :echo
|
91
|
+
# @return [Boolean]
|
92
|
+
def configure(port, options = {})
|
93
|
+
configure_server(port)
|
94
|
+
unless options[:echo].nil?
|
95
|
+
configure_echo(options[:echo][:host], options[:echo][:port])
|
96
|
+
end
|
97
|
+
true
|
98
|
+
end
|
99
|
+
|
100
|
+
# Listen for messages on the given address
|
101
|
+
# @param [::Patch::Patch] patch The patch to use for context
|
102
|
+
# @param [Proc] callback A callback to fire when messages are received
|
103
|
+
# @return [Boolean] Whether an action was configured
|
104
|
+
def listen_for(address, patch, &callback)
|
105
|
+
@server.add_method(address) do |message|
|
106
|
+
handle_message_received(patch, message, &callback)
|
107
|
+
end
|
108
|
+
true
|
109
|
+
end
|
110
|
+
|
111
|
+
# Configure the underlying server
|
112
|
+
# @param [Fixnum] port
|
113
|
+
# @return [::OSC::Server]
|
114
|
+
def configure_server(port)
|
115
|
+
@server = ::OSC::EMServer.new(port)
|
116
|
+
if @log
|
117
|
+
@server.add_method(/.*/) { |message| @log.puts("Received: #{message.address}") }
|
118
|
+
end
|
119
|
+
@server
|
120
|
+
end
|
121
|
+
|
122
|
+
# Will received messages be echoed back to the client?
|
123
|
+
# @return [Boolean]
|
124
|
+
def echo?
|
125
|
+
!@client.nil?
|
126
|
+
end
|
127
|
+
|
128
|
+
# Echo a message back to the client to update the UI or whatever
|
129
|
+
# @param [::Patch::Patch] patch
|
130
|
+
# @param [OSC::Message] osc_message
|
131
|
+
# @return [Boolean] Whether the echo occurred
|
132
|
+
def echo(patch, osc_message)
|
133
|
+
begin
|
134
|
+
@client.puts(patch, osc_message)
|
135
|
+
true
|
136
|
+
rescue Exception => exception # failsafe
|
137
|
+
@log.exception(exception) if @log
|
138
|
+
::Thread.main.raise(exception) unless @is_failsafe
|
139
|
+
false
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Configure the echo client
|
144
|
+
# @param [String] host
|
145
|
+
# @param [Fixnum] echo
|
146
|
+
# @param [Hash] options
|
147
|
+
# @param [Log] :log
|
148
|
+
# @return [::Patch::IO::OSC::Client]
|
149
|
+
def configure_echo(host, port, options = {})
|
150
|
+
@client = Client.new(host, port, :log => options.fetch(:log, @log))
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
data/lib/patch/io/osc.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# Modules
|
2
|
+
require "patch/io/osc/action"
|
3
|
+
require "patch/io/osc/message"
|
4
|
+
# Classes
|
5
|
+
require "patch/io/osc/client"
|
6
|
+
require "patch/io/osc/server"
|
7
|
+
|
8
|
+
module Patch
|
9
|
+
|
10
|
+
module IO
|
11
|
+
|
12
|
+
# Receive OSC messages and do something with them
|
13
|
+
module OSC
|
14
|
+
|
15
|
+
# Key that will be used by Patch to identify the module
|
16
|
+
KEY = :osc
|
17
|
+
extend self
|
18
|
+
::Patch::IO::Module.add(self)
|
19
|
+
|
20
|
+
# Instantiate an OSC server and/or client using the given config
|
21
|
+
# @param [Hash] config
|
22
|
+
# @param [Hash] options
|
23
|
+
# @option options [Action::Container] :actions
|
24
|
+
# @option options [Log] :log
|
25
|
+
# @return [::Patch::IO::OSC::Server]
|
26
|
+
def new_from_config(config, options = {})
|
27
|
+
instance_options = {
|
28
|
+
:log => options[:log]
|
29
|
+
}
|
30
|
+
if config[:server].nil?
|
31
|
+
unless config[:client].nil?
|
32
|
+
instance_options[:id] = config[:id]
|
33
|
+
Client.new(config[:client][:host], config[:client][:port], instance_options)
|
34
|
+
end
|
35
|
+
else
|
36
|
+
instance_options[:echo] = config[:client]
|
37
|
+
Server.new(config[:id], config[:server][:port], instance_options)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|