surface_master 0.2.0 → 0.2.1

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.
@@ -1,5 +1,6 @@
1
1
  module SurfaceMaster
2
- # Base class for event-based drivers. Sub-classes should extend the constructor, and implement `respond_to_action`
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
- @device ||= @device_class.new(opts.merge(input: true,
17
- output: true,
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 = { detached: false }.merge(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
- @action_threads.list.each do |thread|
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 #{opts.inspect}"
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
- Array(state == :both ? %i(down up) : state).each do |st|
98
- types.each do |type|
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
- Array(state == :both ? %i(down up) : state).each do |st|
111
- types.each do |type|
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(action); end
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
- msg_by_command = {}
64
- values.each do |value|
65
- command, payload = color_payload(value)
66
- (msg_by_command[command] ||= []) << payload
67
- end
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
- sysex!(command, *slice.map { |payload| [payload[:led], payload[:color]] })
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 = input[:note]
80
- input[:type] = CODE_NOTE_TO_TYPE[[input[:code], note]] || :grid
81
- if input[:type] == :grid
82
- note = note - 11
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
- [:cc, TYPE_TO_NOTE[opts[:cc]]]
99
- when opts[:grid]
100
- if opts[:grid] == :all
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
- TYPE_TO_COMMAND = { cc: 0x0B,
113
- grid: 0x0B,
114
- column: 0x0C,
115
- row: 0x0D,
116
- all: 0x0E }.freeze
117
- def color_payload(opts)
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
- [command, { led: led, color: color }]
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 that's not been initialized for output"
137
- raise SurfaceMaster::NoOutputAllowedError
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 Launchpad
2
- # Generic launchpad error.
3
- class LaunchpadError < SurfaceMaster::GenericError; end
1
+ module SurfaceMaster
2
+ module Launchpad
3
+ # Generic launchpad error.
4
+ class LaunchpadError < SurfaceMaster::GenericError; end
4
5
 
5
- # Error raised when <tt>x/y</tt> coordinates outside of the grid
6
- # or none were specified.
7
- class NoValidGridCoordinatesError < LaunchpadError; end
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 ||= ['-'] # whole row
60
- y ||= ['-'] # whole column
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 += responses[:"grid#{action[:x]}#{action[:y]}"][state]
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.each {|block| block.call(self, action)}
79
- nil
80
- rescue Exception => e # TODO: StandardException, RuntimeError, or Exception?
81
- logger.error "Error when responding to action #{action.inspect}: #{e.inspect}"
82
- raise e
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
- mappings = [0x03, 0x01, 0x70,
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)
@@ -1,5 +1,6 @@
1
1
  module SurfaceMaster
2
2
  module Orbit
3
+ # Higher-level interface for Numark Orbit wireless MIDI control surface.
3
4
  class Interaction < SurfaceMaster::Interaction
4
5
  def initialize(opts = nil)
5
6
  @device_class = Device