surface_master 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +107 -0
- data/.travis.yml +2 -3
- data/CHANGELOG.md +16 -0
- data/Gemfile +5 -1
- data/Rakefile +73 -5
- data/debug_tools/decode.rb +1 -1
- data/examples/launchpad_testbed.rb +67 -28
- data/examples/monitor.rb +23 -11
- data/examples/orbit_testbed.rb +1 -42
- data/lib/surface_master/device.rb +40 -39
- data/lib/surface_master/interaction.rb +81 -55
- data/lib/surface_master/launchpad/device.rb +70 -58
- data/lib/surface_master/launchpad/errors.rb +8 -9
- data/lib/surface_master/launchpad/interaction.rb +24 -42
- data/lib/surface_master/orbit/device.rb +85 -85
- data/lib/surface_master/orbit/interaction.rb +1 -0
- data/lib/surface_master/orbit/midi_codes.rb +3 -1
- data/lib/surface_master/version.rb +2 -1
- data/lib/{control_center.rb → surface_master.rb} +0 -0
- data/surface_master.gemspec +7 -5
- data/test/helper.rb +16 -17
- data/test/test_device.rb +167 -355
- data/test/test_interaction.rb +177 -188
- metadata +5 -3
@@ -1,5 +1,6 @@
|
|
1
1
|
module SurfaceMaster
|
2
|
-
# Base class for event-based drivers. Sub-classes should extend the constructor, and implement
|
2
|
+
# Base class for event-based drivers. Sub-classes should extend the constructor, and implement
|
3
|
+
# `respond_to_action`, etc.
|
3
4
|
class Interaction
|
4
5
|
include Logging
|
5
6
|
|
@@ -12,10 +13,9 @@ module SurfaceMaster
|
|
12
13
|
logger.debug "Initializing #{self.class}##{object_id} with #{opts.inspect}"
|
13
14
|
|
14
15
|
@use_threads = opts[:use_threads] || true
|
15
|
-
@device = opts[:device]
|
16
|
-
|
17
|
-
|
18
|
-
logger: opts[:logger]))
|
16
|
+
@device = opts[:device] || @device_class.new(opts.merge(input: true,
|
17
|
+
output: true,
|
18
|
+
logger: opts[:logger]))
|
19
19
|
@latency = (opts[:latency] || 0.001).to_f.abs
|
20
20
|
@active = false
|
21
21
|
|
@@ -36,35 +36,10 @@ module SurfaceMaster
|
|
36
36
|
def start(opts = nil)
|
37
37
|
logger.debug "Starting #{self.class}##{object_id}"
|
38
38
|
|
39
|
-
opts
|
39
|
+
opts = { detached: false }.merge(opts || {})
|
40
|
+
@active = true
|
41
|
+
@reader_thread ||= create_reader_thread
|
40
42
|
|
41
|
-
@active = true
|
42
|
-
|
43
|
-
@reader_thread ||= Thread.new do
|
44
|
-
begin
|
45
|
-
while @active do
|
46
|
-
@device.read.each do |action|
|
47
|
-
if @use_threads
|
48
|
-
action_thread = Thread.new(action) do |act|
|
49
|
-
respond_to_action(act)
|
50
|
-
end
|
51
|
-
@action_threads.add(action_thread)
|
52
|
-
else
|
53
|
-
respond_to_action(action)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
sleep @latency# if @latency > 0.0
|
57
|
-
end
|
58
|
-
rescue Portmidi::DeviceError => e
|
59
|
-
logger.fatal "Could not read from device, stopping reader!"
|
60
|
-
raise SurfaceMaster::CommunicationError.new(e)
|
61
|
-
rescue Exception => e
|
62
|
-
logger.fatal "Unkown error, stopping reader: #{e.inspect}"
|
63
|
-
raise e
|
64
|
-
ensure
|
65
|
-
@device.reset!
|
66
|
-
end
|
67
|
-
end
|
68
43
|
@reader_thread.join unless opts[:detached]
|
69
44
|
end
|
70
45
|
|
@@ -78,28 +53,18 @@ module SurfaceMaster
|
|
78
53
|
@reader_thread = nil
|
79
54
|
end
|
80
55
|
ensure
|
81
|
-
|
82
|
-
begin
|
83
|
-
thread.kill
|
84
|
-
thread.join
|
85
|
-
rescue StandardException => e # TODO: RuntimeError, Exception, or this?
|
86
|
-
logger.error "Error when killing action thread: #{e.inspect}"
|
87
|
-
end
|
88
|
-
end
|
56
|
+
kill_action_threads!
|
89
57
|
nil
|
90
58
|
end
|
91
59
|
|
92
60
|
def response_to(types = :all, state = :both, opts = nil, &block)
|
93
|
-
logger.debug "Setting response to #{types.inspect} for state #{state.inspect} with
|
61
|
+
logger.debug "Setting response to #{types.inspect} for state #{state.inspect} with"\
|
62
|
+
" #{opts.inspect}"
|
94
63
|
types = Array(types)
|
95
64
|
opts ||= {}
|
96
65
|
no_response_to(types, state) if opts[:exclusive] == true
|
97
|
-
|
98
|
-
types
|
99
|
-
combined_types(type, opts).each do |combined_type|
|
100
|
-
responses[combined_type][st] << block
|
101
|
-
end
|
102
|
-
end
|
66
|
+
expand_states(state).each do |st|
|
67
|
+
add_response_for_state!(types, opts, st, block)
|
103
68
|
end
|
104
69
|
nil
|
105
70
|
end
|
@@ -107,12 +72,8 @@ module SurfaceMaster
|
|
107
72
|
def no_response_to(types = nil, state = :both, opts = nil)
|
108
73
|
logger.debug "Removing response to #{types.inspect} for state #{state.inspect}"
|
109
74
|
types = Array(types)
|
110
|
-
|
111
|
-
types
|
112
|
-
combined_types(type, opts).each do |combined_type|
|
113
|
-
responses[combined_type][st].clear
|
114
|
-
end
|
115
|
-
end
|
75
|
+
expand_states(state).each do |st|
|
76
|
+
clear_responses_for_state!(types, opts, st)
|
116
77
|
end
|
117
78
|
nil
|
118
79
|
end
|
@@ -123,11 +84,76 @@ module SurfaceMaster
|
|
123
84
|
|
124
85
|
protected
|
125
86
|
|
87
|
+
def create_reader_thread
|
88
|
+
Thread.new do
|
89
|
+
guard_input_and_reset_at_end! do
|
90
|
+
while @active
|
91
|
+
@device.read.each { |action| handle_action(action) }
|
92
|
+
sleep @latency if @latency && @latency > 0.0
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def guard_input_and_reset_at_end!(&block)
|
99
|
+
block.call
|
100
|
+
rescue Portmidi::DeviceError => e
|
101
|
+
logger.fatal "Could not read from device, stopping reader!"
|
102
|
+
raise SurfaceMaster::CommunicationError, e
|
103
|
+
rescue Exception => e
|
104
|
+
logger.fatal "Unkown error, stopping reader: #{e.inspect}"
|
105
|
+
raise e
|
106
|
+
ensure
|
107
|
+
@device.reset!
|
108
|
+
end
|
109
|
+
|
110
|
+
def kill_action_threads!
|
111
|
+
@action_threads.list.each do |thread|
|
112
|
+
begin
|
113
|
+
thread.kill
|
114
|
+
thread.join
|
115
|
+
rescue StandardException => e # TODO: RuntimeError, Exception, or this?
|
116
|
+
logger.error "Error when killing action thread: #{e.inspect}"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def add_response_for_state!(types, opts, state, block)
|
122
|
+
response_groups_for(types, opts, state) do |responses|
|
123
|
+
responses << block
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def clear_responses_for_state!(types, opts, state)
|
128
|
+
response_groups_for(types, opts, state, &:clear)
|
129
|
+
end
|
130
|
+
|
131
|
+
def response_groups_for(types, opts, state, &block)
|
132
|
+
types.each do |type|
|
133
|
+
combined_types(type, opts).each do |combined_type|
|
134
|
+
block.call(responses[combined_type][state])
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def expand_states(state); Array(state == :both ? %i(down up) : state); end
|
140
|
+
|
141
|
+
def handle_action(action)
|
142
|
+
if @use_threads
|
143
|
+
action_thread = Thread.new(action) do |act|
|
144
|
+
respond_to_action(act)
|
145
|
+
end
|
146
|
+
@action_threads.add(action_thread)
|
147
|
+
else
|
148
|
+
respond_to_action(action)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
126
152
|
def responses
|
127
153
|
# TODO: Generalize for arbitrary actions...
|
128
154
|
@responses ||= Hash.new { |hash, key| hash[key] = { down: [], up: [] } }
|
129
155
|
end
|
130
156
|
|
131
|
-
def respond_to_action(
|
157
|
+
def respond_to_action(_action); end
|
132
158
|
end
|
133
159
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module SurfaceMaster
|
2
2
|
module Launchpad
|
3
|
+
# Low-level interface to Novation Launchpad Mark 2 control surface.
|
3
4
|
class Device < SurfaceMaster::Device
|
4
5
|
include MIDICodes
|
5
6
|
|
@@ -54,76 +55,101 @@ module SurfaceMaster
|
|
54
55
|
# TODO: Support more of the LaunchPad Mark 2's functionality.
|
55
56
|
|
56
57
|
def change(opts = nil)
|
58
|
+
fail NoOutputAllowedError unless output_enabled?
|
59
|
+
|
57
60
|
opts ||= {}
|
58
61
|
command, payload = color_payload(opts)
|
59
62
|
sysex!(command, payload[:led], payload[:color])
|
63
|
+
nil
|
60
64
|
end
|
61
65
|
|
62
66
|
def changes(values)
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
msg_by_command.each do |command, payloads|
|
69
|
-
# The documented batch size for RGB LED updates is 80. The docs lie, at least on my current
|
70
|
-
# firmware version -- anything above 62 crashes the device hard.
|
67
|
+
fail NoOutputAllowedError unless output_enabled?
|
68
|
+
|
69
|
+
organize_commands(values).each do |command, payloads|
|
70
|
+
# The documented batch size for RGB LED updates is 80. The docs lie, at least on my
|
71
|
+
# current firmware version -- anything above 62 crashes the device hard.
|
71
72
|
while (slice = payloads.shift(62)).length > 0
|
72
|
-
|
73
|
+
messages = slice.map { |payload| [payload[:led], payload[:color]] }
|
74
|
+
sysex!(command, *messages)
|
73
75
|
end
|
74
76
|
end
|
77
|
+
nil
|
75
78
|
end
|
76
79
|
|
77
80
|
def read
|
81
|
+
fail NoInputAllowedError unless input_enabled?
|
78
82
|
super.collect do |input|
|
79
|
-
note
|
80
|
-
input[:type]
|
81
|
-
if input[:type] == :grid
|
82
|
-
|
83
|
-
input[:x] = note % 10
|
84
|
-
input[:y] = note / 10
|
85
|
-
end
|
83
|
+
note = input.delete(:note)
|
84
|
+
input[:type] = CODE_NOTE_TO_TYPE[[input.delete(:code), note]] || :grid
|
85
|
+
input[:x], input[:y] = decode_grid_coord(note) if input[:type] == :grid
|
86
|
+
input.delete(:velocity)
|
86
87
|
input
|
87
88
|
end
|
88
89
|
end
|
89
90
|
|
90
91
|
protected
|
91
92
|
|
93
|
+
def organize_commands(values)
|
94
|
+
msg_by_command = {}
|
95
|
+
values.each do |value|
|
96
|
+
command, payload = color_payload(value)
|
97
|
+
(msg_by_command[command] ||= []) << payload
|
98
|
+
end
|
99
|
+
msg_by_command
|
100
|
+
end
|
101
|
+
|
102
|
+
def decode_grid_coord(note)
|
103
|
+
note -= 11
|
104
|
+
x = note % 10
|
105
|
+
y = note / 10
|
106
|
+
[x, y]
|
107
|
+
end
|
108
|
+
|
92
109
|
def layout!(mode); sysex!(0x22, mode); end
|
93
110
|
def sysex_prefix; @sysex_prefix ||= super + [0x00, 0x20, 0x29, 0x02, 0x18]; end
|
94
111
|
|
95
112
|
def decode_led(opts)
|
96
113
|
case
|
97
|
-
when opts[:cc]
|
98
|
-
|
99
|
-
when opts[:
|
100
|
-
|
101
|
-
[:all, nil]
|
102
|
-
else
|
103
|
-
[:grid, (opts[:grid][1] * 10) + opts[:grid][0] + 11]
|
104
|
-
end
|
105
|
-
when opts[:column]
|
106
|
-
[:column, opts[:column]]
|
107
|
-
when opts[:row]
|
108
|
-
[:row, opts[:row]]
|
114
|
+
when opts[:cc] then [:cc, TYPE_TO_NOTE[opts[:cc]]]
|
115
|
+
when opts[:grid] then decode_grid_led(opts)
|
116
|
+
when opts[:column] then [:column, opts[:column]]
|
117
|
+
when opts[:row] then [:row, opts[:row]]
|
109
118
|
end
|
119
|
+
rescue
|
120
|
+
raise SurfaceMaster::Launchpad::NoValidGridCoordinatesError
|
110
121
|
end
|
111
122
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
type, led = decode_led(opts)
|
119
|
-
command = TYPE_TO_COMMAND[type]
|
120
|
-
case type
|
121
|
-
when :cc, :grid
|
122
|
-
color = [opts[:red] || 0x00, opts[:green] || 0x00, opts[:blue] || 0x00]
|
123
|
-
when :column, :row, :all
|
124
|
-
color = opts[:color] || 0x00
|
123
|
+
def decode_grid_led(opts)
|
124
|
+
if opts[:grid] == :all
|
125
|
+
[:all, nil]
|
126
|
+
else
|
127
|
+
check_xy_values!(opts[:grid])
|
128
|
+
[:grid, (opts[:grid][1] * 10) + opts[:grid][0] + 11]
|
125
129
|
end
|
126
|
-
|
130
|
+
end
|
131
|
+
|
132
|
+
def check_xy_values!(xy_pair)
|
133
|
+
x = xy_pair[0]
|
134
|
+
y = xy_pair[1]
|
135
|
+
return unless xy_pair.length != 2 ||
|
136
|
+
!coord_in_range?(x) ||
|
137
|
+
!coord_in_range?(y)
|
138
|
+
|
139
|
+
fail SurfaceMaster::Launchpad::NoValidGridCoordinatesError
|
140
|
+
end
|
141
|
+
|
142
|
+
def coord_in_range?(val); val && val >= 0 && val <= 7; end
|
143
|
+
|
144
|
+
def color_payload(opts)
|
145
|
+
# Hard-coded to single-LED RGB update right now.
|
146
|
+
# For paletted changes, commands available include:
|
147
|
+
# 0x0C -> Column
|
148
|
+
# 0x0D -> Row
|
149
|
+
# 0x0E -> All LEDs
|
150
|
+
[0x0B,
|
151
|
+
{ led: decode_led(opts),
|
152
|
+
color: [opts[:red] || 0x00, opts[:green] || 0x00, opts[:blue] || 0x00] }]
|
127
153
|
end
|
128
154
|
|
129
155
|
def output!(status, data1, data2)
|
@@ -133,27 +159,13 @@ module SurfaceMaster
|
|
133
159
|
def outputs!(*messages)
|
134
160
|
messages = Array(messages)
|
135
161
|
if @output.nil?
|
136
|
-
logger.error "trying to write to device
|
137
|
-
|
162
|
+
logger.error "trying to write to device not open for output"
|
163
|
+
fail SurfaceMaster::NoOutputAllowedError
|
138
164
|
end
|
139
165
|
logger.debug "writing messages to launchpad:\n #{messages.join("\n ")}" if logger.debug?
|
140
166
|
@output.write(messages)
|
141
167
|
nil
|
142
168
|
end
|
143
|
-
|
144
|
-
def note(type, opts)
|
145
|
-
note = TYPE_TO_NOTE[type]
|
146
|
-
if note.nil?
|
147
|
-
x = (opts[:x] || -1).to_i
|
148
|
-
y = (opts[:y] || -1).to_i
|
149
|
-
if x < 0 || x > 7 || y < 0 || y > 7
|
150
|
-
logger.error "wrong coordinates specified: x=#{x}, y=#{y}"
|
151
|
-
raise NoValidGridCoordinatesError.new("you need to specify valid coordinates (x/y, 0-7, from top left), you specified: x=#{x}, y=#{y}")
|
152
|
-
end
|
153
|
-
note = y * 10 + x
|
154
|
-
end
|
155
|
-
note
|
156
|
-
end
|
157
169
|
end
|
158
170
|
end
|
159
171
|
end
|
@@ -1,11 +1,10 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
|
1
|
+
module SurfaceMaster
|
2
|
+
module Launchpad
|
3
|
+
# Generic launchpad error.
|
4
|
+
class LaunchpadError < SurfaceMaster::GenericError; end
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
# Error raised when wrong brightness was specified.
|
10
|
-
class NoValidBrightnessError < LaunchpadError; end
|
6
|
+
# Error raised when <tt>x/y</tt> coordinates outside of the grid
|
7
|
+
# or none were specified.
|
8
|
+
class NoValidGridCoordinatesError < LaunchpadError; end
|
9
|
+
end
|
11
10
|
end
|
@@ -1,43 +1,13 @@
|
|
1
1
|
module SurfaceMaster
|
2
2
|
module Launchpad
|
3
|
+
# Higher-level interface to Novation Launchpad Mark 2, providing an input
|
4
|
+
# handling loop and event-hooks for input events.
|
3
5
|
class Interaction < SurfaceMaster::Interaction
|
4
6
|
def initialize(opts = nil)
|
5
7
|
@device_class = Device
|
6
8
|
super(opts)
|
7
9
|
end
|
8
10
|
|
9
|
-
# def response_to(types = :all, state = :both, opts = nil, &block)
|
10
|
-
# logger.debug "setting response to #{types.inspect} for state #{state.inspect} with #{opts.inspect}"
|
11
|
-
# types = Array(types)
|
12
|
-
# opts ||= {}
|
13
|
-
# no_response_to(types, state) if opts[:exclusive] == true
|
14
|
-
# Array(state == :both ? %i(down up) : state).each do |st|
|
15
|
-
# types.each do |type|
|
16
|
-
# combined_types(type, opts).each do |combined_type|
|
17
|
-
# responses[combined_type][st] << block
|
18
|
-
# end
|
19
|
-
# end
|
20
|
-
# end
|
21
|
-
# nil
|
22
|
-
# end
|
23
|
-
|
24
|
-
# def no_response_to(types = nil, state = :both, opts = nil)
|
25
|
-
# logger.debug "removing response to #{types.inspect} for state #{state.inspect}"
|
26
|
-
# types = Array(types)
|
27
|
-
# Array(state == :both ? %i(down up) : state).each do |st|
|
28
|
-
# types.each do |type|
|
29
|
-
# combined_types(type, opts).each do |combined_type|
|
30
|
-
# responses[combined_type][st].clear
|
31
|
-
# end
|
32
|
-
# end
|
33
|
-
# end
|
34
|
-
# nil
|
35
|
-
# end
|
36
|
-
|
37
|
-
# def respond_to(type, state, opts = nil)
|
38
|
-
# respond_to_action((opts || {}).merge(type: type, state: state))
|
39
|
-
# end
|
40
|
-
|
41
11
|
protected
|
42
12
|
|
43
13
|
def responses
|
@@ -56,8 +26,8 @@ module SurfaceMaster
|
|
56
26
|
x = grid_range(opts[:x])
|
57
27
|
y = grid_range(opts[:y])
|
58
28
|
return [:grid] if x.nil? && y.nil? # whole grid
|
59
|
-
x ||= [
|
60
|
-
y ||= [
|
29
|
+
x ||= ["-"] # whole row
|
30
|
+
y ||= ["-"] # whole column
|
61
31
|
x.product(y).map { |xx, yy| :"grid#{xx}#{yy}" }
|
62
32
|
else
|
63
33
|
[type.to_sym]
|
@@ -65,21 +35,33 @@ module SurfaceMaster
|
|
65
35
|
end
|
66
36
|
|
67
37
|
def respond_to_action(action)
|
38
|
+
mappings_for_action(action).each do |block|
|
39
|
+
block.call(self, action)
|
40
|
+
end
|
41
|
+
nil
|
42
|
+
rescue Exception => e # TODO: StandardException, RuntimeError, or Exception?
|
43
|
+
logger.error "Error when responding to action #{action.inspect}: #{e.inspect}"
|
44
|
+
raise e
|
45
|
+
end
|
46
|
+
|
47
|
+
def mappings_for_action(action)
|
68
48
|
type = action[:type].to_sym
|
69
49
|
state = action[:state].to_sym
|
70
50
|
actions = []
|
71
51
|
if type == :grid
|
72
|
-
actions +=
|
73
|
-
actions += responses[:"grid#{action[:x]}-"][state]
|
74
|
-
actions += responses[:"grid-#{action[:y]}"][state]
|
52
|
+
actions += mappings_for_grid_action(state, action[:x], action[:y])
|
75
53
|
end
|
76
54
|
actions += responses[type][state]
|
77
55
|
actions += responses[:all][state]
|
78
|
-
actions.compact
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
56
|
+
actions.compact
|
57
|
+
end
|
58
|
+
|
59
|
+
def mappings_for_grid_action(state, x, y)
|
60
|
+
actions = []
|
61
|
+
actions += responses[:"grid#{x}#{y}"][state]
|
62
|
+
actions += responses[:"grid#{x}-"][state]
|
63
|
+
actions += responses[:"grid-#{y}"][state]
|
64
|
+
actions
|
83
65
|
end
|
84
66
|
end
|
85
67
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module SurfaceMaster
|
2
2
|
module Orbit
|
3
|
+
# Low-level interface to Numark Orbit wireless MIDI control surface.
|
3
4
|
class Device < SurfaceMaster::Device
|
4
5
|
include MIDICodes
|
5
6
|
|
@@ -11,91 +12,8 @@ module SurfaceMaster
|
|
11
12
|
|
12
13
|
def reset!
|
13
14
|
# TODO: This... doesn't appear to work. At all.
|
14
|
-
|
15
|
-
|
16
|
-
0x00, 0x00, 0x00,
|
17
|
-
0x00, 0x04, 0x00,
|
18
|
-
0x00, 0x08, 0x00,
|
19
|
-
0x00, 0x0C, 0x00,
|
20
|
-
0x00, 0x01, 0x00,
|
21
|
-
0x00, 0x05, 0x00,
|
22
|
-
0x00, 0x09, 0x00,
|
23
|
-
0x00, 0x0D, 0x00,
|
24
|
-
0x00, 0x02, 0x00,
|
25
|
-
0x00, 0x06, 0x00,
|
26
|
-
0x00, 0x0A, 0x00,
|
27
|
-
0x00, 0x0E, 0x00,
|
28
|
-
0x00, 0x03, 0x00,
|
29
|
-
0x00, 0x07, 0x00,
|
30
|
-
0x00, 0x0B, 0x00,
|
31
|
-
0x00, 0x0F, 0x00,
|
32
|
-
0x01, 0x00, 0x00,
|
33
|
-
0x01, 0x04, 0x00,
|
34
|
-
0x01, 0x08, 0x00,
|
35
|
-
0x01, 0x0C, 0x00,
|
36
|
-
0x01, 0x01, 0x00,
|
37
|
-
0x01, 0x05, 0x00,
|
38
|
-
0x01, 0x09, 0x00,
|
39
|
-
0x01, 0x0D, 0x00,
|
40
|
-
0x01, 0x02, 0x00,
|
41
|
-
0x01, 0x06, 0x00,
|
42
|
-
0x01, 0x0A, 0x00,
|
43
|
-
0x01, 0x0E, 0x00,
|
44
|
-
0x01, 0x03, 0x00,
|
45
|
-
0x01, 0x07, 0x00,
|
46
|
-
0x01, 0x0B, 0x00,
|
47
|
-
0x01, 0x0F, 0x00,
|
48
|
-
0x02, 0x00, 0x00,
|
49
|
-
0x02, 0x04, 0x00,
|
50
|
-
0x02, 0x08, 0x00,
|
51
|
-
0x02, 0x0C, 0x00,
|
52
|
-
0x02, 0x01, 0x00,
|
53
|
-
0x02, 0x05, 0x00,
|
54
|
-
0x02, 0x09, 0x00,
|
55
|
-
0x02, 0x0D, 0x00,
|
56
|
-
0x02, 0x02, 0x00,
|
57
|
-
0x02, 0x06, 0x00,
|
58
|
-
0x02, 0x0A, 0x00,
|
59
|
-
0x02, 0x0E, 0x00,
|
60
|
-
0x02, 0x03, 0x00,
|
61
|
-
0x02, 0x07, 0x00,
|
62
|
-
0x02, 0x0B, 0x00,
|
63
|
-
0x02, 0x0F, 0x00,
|
64
|
-
0x03, 0x00, 0x00,
|
65
|
-
0x03, 0x04, 0x00,
|
66
|
-
0x03, 0x08, 0x00,
|
67
|
-
0x03, 0x0C, 0x00,
|
68
|
-
0x03, 0x01, 0x00,
|
69
|
-
0x03, 0x05, 0x00,
|
70
|
-
0x03, 0x09, 0x00,
|
71
|
-
0x03, 0x0D, 0x00,
|
72
|
-
0x03, 0x02, 0x00,
|
73
|
-
0x03, 0x06, 0x00,
|
74
|
-
0x03, 0x0A, 0x00,
|
75
|
-
0x03, 0x0E, 0x00,
|
76
|
-
0x03, 0x03, 0x00,
|
77
|
-
0x03, 0x07, 0x00,
|
78
|
-
0x03, 0x0B, 0x00,
|
79
|
-
0x03, 0x0F, 0x00,
|
80
|
-
0x00, 0x00, 0x01,
|
81
|
-
0x00, 0x02, 0x00,
|
82
|
-
0x03, 0x00, 0x00,
|
83
|
-
0x01, 0x01, 0x01,
|
84
|
-
0x02, 0x01, 0x03,
|
85
|
-
0x01, 0x00, 0x02,
|
86
|
-
0x01, 0x02, 0x02,
|
87
|
-
0x02, 0x03, 0x02,
|
88
|
-
0x00, 0x03, 0x01,
|
89
|
-
0x03, 0x02, 0x03,
|
90
|
-
0x03, 0x03, 0x0C,
|
91
|
-
0x00, 0x0D, 0x00,
|
92
|
-
0x0C, 0x00, 0x0D,
|
93
|
-
0x00, 0x0C, 0x00,
|
94
|
-
0x0D, 0x00, 0x0C,
|
95
|
-
0x00, 0x0D, 0x00]
|
96
|
-
|
97
|
-
if (result = sysex!(*mappings)) != 0
|
98
|
-
raise "Expected success (0) setting mappings, got: #{result}"
|
15
|
+
if (result = sysex!(*MAPPINGS)) != 0
|
16
|
+
fail "Expected success (0) setting mappings, got: #{result}"
|
99
17
|
end
|
100
18
|
sysex!(0x01, 0x00, 0x00)
|
101
19
|
end
|
@@ -108,6 +26,88 @@ module SurfaceMaster
|
|
108
26
|
|
109
27
|
protected
|
110
28
|
|
29
|
+
MAPPINGS = [0x03, 0x01, 0x70,
|
30
|
+
0x00, 0x00, 0x00,
|
31
|
+
0x00, 0x04, 0x00,
|
32
|
+
0x00, 0x08, 0x00,
|
33
|
+
0x00, 0x0C, 0x00,
|
34
|
+
0x00, 0x01, 0x00,
|
35
|
+
0x00, 0x05, 0x00,
|
36
|
+
0x00, 0x09, 0x00,
|
37
|
+
0x00, 0x0D, 0x00,
|
38
|
+
0x00, 0x02, 0x00,
|
39
|
+
0x00, 0x06, 0x00,
|
40
|
+
0x00, 0x0A, 0x00,
|
41
|
+
0x00, 0x0E, 0x00,
|
42
|
+
0x00, 0x03, 0x00,
|
43
|
+
0x00, 0x07, 0x00,
|
44
|
+
0x00, 0x0B, 0x00,
|
45
|
+
0x00, 0x0F, 0x00,
|
46
|
+
0x01, 0x00, 0x00,
|
47
|
+
0x01, 0x04, 0x00,
|
48
|
+
0x01, 0x08, 0x00,
|
49
|
+
0x01, 0x0C, 0x00,
|
50
|
+
0x01, 0x01, 0x00,
|
51
|
+
0x01, 0x05, 0x00,
|
52
|
+
0x01, 0x09, 0x00,
|
53
|
+
0x01, 0x0D, 0x00,
|
54
|
+
0x01, 0x02, 0x00,
|
55
|
+
0x01, 0x06, 0x00,
|
56
|
+
0x01, 0x0A, 0x00,
|
57
|
+
0x01, 0x0E, 0x00,
|
58
|
+
0x01, 0x03, 0x00,
|
59
|
+
0x01, 0x07, 0x00,
|
60
|
+
0x01, 0x0B, 0x00,
|
61
|
+
0x01, 0x0F, 0x00,
|
62
|
+
0x02, 0x00, 0x00,
|
63
|
+
0x02, 0x04, 0x00,
|
64
|
+
0x02, 0x08, 0x00,
|
65
|
+
0x02, 0x0C, 0x00,
|
66
|
+
0x02, 0x01, 0x00,
|
67
|
+
0x02, 0x05, 0x00,
|
68
|
+
0x02, 0x09, 0x00,
|
69
|
+
0x02, 0x0D, 0x00,
|
70
|
+
0x02, 0x02, 0x00,
|
71
|
+
0x02, 0x06, 0x00,
|
72
|
+
0x02, 0x0A, 0x00,
|
73
|
+
0x02, 0x0E, 0x00,
|
74
|
+
0x02, 0x03, 0x00,
|
75
|
+
0x02, 0x07, 0x00,
|
76
|
+
0x02, 0x0B, 0x00,
|
77
|
+
0x02, 0x0F, 0x00,
|
78
|
+
0x03, 0x00, 0x00,
|
79
|
+
0x03, 0x04, 0x00,
|
80
|
+
0x03, 0x08, 0x00,
|
81
|
+
0x03, 0x0C, 0x00,
|
82
|
+
0x03, 0x01, 0x00,
|
83
|
+
0x03, 0x05, 0x00,
|
84
|
+
0x03, 0x09, 0x00,
|
85
|
+
0x03, 0x0D, 0x00,
|
86
|
+
0x03, 0x02, 0x00,
|
87
|
+
0x03, 0x06, 0x00,
|
88
|
+
0x03, 0x0A, 0x00,
|
89
|
+
0x03, 0x0E, 0x00,
|
90
|
+
0x03, 0x03, 0x00,
|
91
|
+
0x03, 0x07, 0x00,
|
92
|
+
0x03, 0x0B, 0x00,
|
93
|
+
0x03, 0x0F, 0x00,
|
94
|
+
0x00, 0x00, 0x01,
|
95
|
+
0x00, 0x02, 0x00,
|
96
|
+
0x03, 0x00, 0x00,
|
97
|
+
0x01, 0x01, 0x01,
|
98
|
+
0x02, 0x01, 0x03,
|
99
|
+
0x01, 0x00, 0x02,
|
100
|
+
0x01, 0x02, 0x02,
|
101
|
+
0x02, 0x03, 0x02,
|
102
|
+
0x00, 0x03, 0x01,
|
103
|
+
0x03, 0x02, 0x03,
|
104
|
+
0x03, 0x03, 0x0C,
|
105
|
+
0x00, 0x0D, 0x00,
|
106
|
+
0x0C, 0x00, 0x0D,
|
107
|
+
0x00, 0x0C, 0x00,
|
108
|
+
0x0D, 0x00, 0x0C,
|
109
|
+
0x00, 0x0D, 0x00]
|
110
|
+
|
111
111
|
def sysex_prefix; @sysex_prefix ||= super + [0x00, 0x01, 0x3F, 0x2B]; end
|
112
112
|
|
113
113
|
def decode_shoulder(decoded, note, _velocity)
|