launchpad_mk2 0.0.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.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/LICENSE +7 -0
- data/README.rdoc +75 -0
- data/Rakefile +10 -0
- data/examples/binary_clock.rb +41 -0
- data/examples/color_picker.rb +96 -0
- data/examples/colors.rb +27 -0
- data/examples/corners.rb +11 -0
- data/examples/doodle.rb +65 -0
- data/examples/drawing_board.rb +20 -0
- data/examples/feedback.rb +30 -0
- data/examples/pong.rb +193 -0
- data/examples/sysex.rb +34 -0
- data/launchpad-gem-overview.gif +0 -0
- data/launchpad_mk2.gemspec +30 -0
- data/lib/launchpad_mk2.rb +23 -0
- data/lib/launchpad_mk2/device.rb +466 -0
- data/lib/launchpad_mk2/errors.rb +37 -0
- data/lib/launchpad_mk2/interaction.rb +333 -0
- data/lib/launchpad_mk2/logging.rb +27 -0
- data/lib/launchpad_mk2/midi_codes.rb +40 -0
- data/lib/launchpad_mk2/version.rb +3 -0
- data/test/helper.rb +45 -0
- data/test/test_device.rb +446 -0
- data/test/test_interaction.rb +460 -0
- metadata +154 -0
data/examples/sysex.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'launchpad_mk2'
|
2
|
+
|
3
|
+
device = Launchpad::Device.new(:input => false, :output => true)
|
4
|
+
|
5
|
+
device.reset_all()
|
6
|
+
# sleep 0.5
|
7
|
+
device.lightn_column([0, 3,6], 45)
|
8
|
+
# sleep 0.5
|
9
|
+
device.lightn_row([7,6, 2], 22)
|
10
|
+
# sleep 0.5
|
11
|
+
# device.light_all(63)
|
12
|
+
# sleep 0.5
|
13
|
+
# device.reset()
|
14
|
+
# sleep 0.5
|
15
|
+
# device.light_all(79)
|
16
|
+
# sleep 0.5
|
17
|
+
# device.reset()
|
18
|
+
# sleep 0.5
|
19
|
+
# device.flash1(4, 4, 63)
|
20
|
+
# sleep 2
|
21
|
+
device.flashn([[1, 1],[2, 2],[3, 3],[4, 4]], 63)
|
22
|
+
device.pulsen([[4, 1],[4, 2],[4, 3],[4, 4]], 63)
|
23
|
+
sleep 2
|
24
|
+
# device.pulse(3, 3, 45)
|
25
|
+
# sleep 2
|
26
|
+
device.reset_all()
|
27
|
+
# device.pulse(3, 3, 63)
|
28
|
+
# sleep 2
|
29
|
+
# device.scroll_once(45, "Oscar is a snot monster")
|
30
|
+
# sleep 10
|
31
|
+
# device.scroll_stop()
|
32
|
+
|
33
|
+
# sleep so that the messages can be sent before the program terminates
|
34
|
+
sleep 0.1
|
Binary file
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "launchpad_mk2/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "launchpad_mk2"
|
7
|
+
s.version = LaunchpadMk2::VERSION
|
8
|
+
s.authors = ["Andy Marks"]
|
9
|
+
s.email = ["vampwillow@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/andeemarks/launchpad"
|
11
|
+
s.summary = %q{A Ruby gem for programmatically controlling the Novation Launchpad MK2.}
|
12
|
+
s.description = %q{This gem provides programmatic access to the Novation Launchpad MK2. LEDs can be lighted and button presses can be evaluated using launchpad's MIDI input/output.}
|
13
|
+
s.license = 'MIT'
|
14
|
+
s.rubyforge_project = "launchpad_mk2"
|
15
|
+
|
16
|
+
s.add_dependency "portmidi", "= 0.0.6"
|
17
|
+
s.add_dependency "ffi", "= 1.9.18"
|
18
|
+
s.add_development_dependency "rake", "~> 12"
|
19
|
+
if RUBY_VERSION < "1.9"
|
20
|
+
s.add_development_dependency "minitest"
|
21
|
+
else
|
22
|
+
s.add_development_dependency "minitest-reporters", "~> 1.1", ">= 1.1.14"
|
23
|
+
end
|
24
|
+
s.add_development_dependency "mocha", "~> 0.14", ">= 0.14.0"
|
25
|
+
|
26
|
+
s.files = `git ls-files`.split("\n")
|
27
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
28
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
29
|
+
s.require_paths = ["lib"]
|
30
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'launchpad_mk2/interaction'
|
2
|
+
|
3
|
+
# All the fun of launchpad in one module!
|
4
|
+
#
|
5
|
+
# See Launchpad::Device for basic access to launchpad input/ouput
|
6
|
+
# and Launchpad::Interaction for advanced interaction features.
|
7
|
+
#
|
8
|
+
# The following parameters will be used throughout the library, so here are the ranges:
|
9
|
+
#
|
10
|
+
# [+type+] type of the button, one of
|
11
|
+
# <tt>
|
12
|
+
# :grid,
|
13
|
+
# :up, :down, :left, :right, :session, :user1, :user2, :mixer,
|
14
|
+
# :scene1 - :scene8
|
15
|
+
# </tt>
|
16
|
+
# [<tt>x/y</tt>] x/y coordinate (used when type is set to :grid),
|
17
|
+
# <tt>0-7</tt> (from left to right/bottom to top),
|
18
|
+
# mandatory when +type+ is set to <tt>:grid</tt>
|
19
|
+
# [<tt>color</tt>] color of the LED (value between 0 and 127 inclusive)
|
20
|
+
# optional, defaults to <tt>:off</tt>
|
21
|
+
# [+state+] whether the button is pressed or released, <tt>:down/:up</tt>
|
22
|
+
module LaunchpadMk2
|
23
|
+
end
|
@@ -0,0 +1,466 @@
|
|
1
|
+
require 'portmidi'
|
2
|
+
|
3
|
+
require 'launchpad_mk2/errors'
|
4
|
+
require 'launchpad_mk2/logging'
|
5
|
+
require 'launchpad_mk2/midi_codes'
|
6
|
+
require 'launchpad_mk2/version'
|
7
|
+
|
8
|
+
module LaunchpadMk2
|
9
|
+
|
10
|
+
# This class is used to exchange data with the launchpad.
|
11
|
+
# It provides methods to light LEDs and to get information about button presses/releases.
|
12
|
+
#
|
13
|
+
# Example:
|
14
|
+
#
|
15
|
+
# require 'launchpad_mk2/device'
|
16
|
+
#
|
17
|
+
# device = Launchpad::Device.new
|
18
|
+
# device.test_leds
|
19
|
+
# sleep 1
|
20
|
+
# device.reset
|
21
|
+
# sleep 1
|
22
|
+
# device.change :grid, :x => 4, :y => 4, :red => :high, :green => :low
|
23
|
+
class Device
|
24
|
+
|
25
|
+
include Logging
|
26
|
+
include MidiCodes
|
27
|
+
|
28
|
+
MK2_DEVICE_NAME = 'Launchpad MK2 MIDI 1'
|
29
|
+
|
30
|
+
CODE_NOTE_TO_DATA_TYPE = {
|
31
|
+
[Status::ON, SceneButton::SCENE1] => :scene1,
|
32
|
+
[Status::ON, SceneButton::SCENE2] => :scene2,
|
33
|
+
[Status::ON, SceneButton::SCENE3] => :scene3,
|
34
|
+
[Status::ON, SceneButton::SCENE4] => :scene4,
|
35
|
+
[Status::ON, SceneButton::SCENE5] => :scene5,
|
36
|
+
[Status::ON, SceneButton::SCENE6] => :scene6,
|
37
|
+
[Status::ON, SceneButton::SCENE7] => :scene7,
|
38
|
+
[Status::ON, SceneButton::SCENE8] => :scene8,
|
39
|
+
[Status::CC, ControlButton::UP] => :up,
|
40
|
+
[Status::CC, ControlButton::DOWN] => :down,
|
41
|
+
[Status::CC, ControlButton::LEFT] => :left,
|
42
|
+
[Status::CC, ControlButton::RIGHT] => :right,
|
43
|
+
[Status::CC, ControlButton::SESSION] => :session,
|
44
|
+
[Status::CC, ControlButton::USER1] => :user1,
|
45
|
+
[Status::CC, ControlButton::USER2] => :user2,
|
46
|
+
[Status::CC, ControlButton::MIXER] => :mixer
|
47
|
+
}.freeze
|
48
|
+
|
49
|
+
TYPE_TO_NOTE = {
|
50
|
+
:up => ControlButton::UP,
|
51
|
+
:down => ControlButton::DOWN,
|
52
|
+
:left => ControlButton::LEFT,
|
53
|
+
:right => ControlButton::RIGHT,
|
54
|
+
:session => ControlButton::SESSION,
|
55
|
+
:user1 => ControlButton::USER1,
|
56
|
+
:user2 => ControlButton::USER2,
|
57
|
+
:mixer => ControlButton::MIXER,
|
58
|
+
:scene1 => SceneButton::SCENE1,
|
59
|
+
:scene2 => SceneButton::SCENE2,
|
60
|
+
:scene3 => SceneButton::SCENE3,
|
61
|
+
:scene4 => SceneButton::SCENE4,
|
62
|
+
:scene5 => SceneButton::SCENE5,
|
63
|
+
:scene6 => SceneButton::SCENE6,
|
64
|
+
:scene7 => SceneButton::SCENE7,
|
65
|
+
:scene8 => SceneButton::SCENE8
|
66
|
+
}.freeze
|
67
|
+
|
68
|
+
# Initializes the launchpad device. When output capabilities are requested,
|
69
|
+
# the launchpad will be reset.
|
70
|
+
#
|
71
|
+
# Optional options hash:
|
72
|
+
#
|
73
|
+
# [<tt>:input</tt>] whether to use MIDI input for user interaction,
|
74
|
+
# <tt>true/false</tt>, optional, defaults to +true+
|
75
|
+
# [<tt>:output</tt>] whether to use MIDI output for data display,
|
76
|
+
# <tt>true/false</tt>, optional, defaults to +true+
|
77
|
+
# [<tt>:input_device_id</tt>] ID of the MIDI input device to use,
|
78
|
+
# optional, <tt>:device_name</tt> will be used if omitted
|
79
|
+
# [<tt>:output_device_id</tt>] ID of the MIDI output device to use,
|
80
|
+
# optional, <tt>:device_name</tt> will be used if omitted
|
81
|
+
# [<tt>:device_name</tt>] Name of the MIDI device to use,
|
82
|
+
# optional, defaults to "Launchpad"
|
83
|
+
# [<tt>:logger</tt>] [Logger] to be used by this device instance, can be changed afterwards
|
84
|
+
#
|
85
|
+
# Errors raised:
|
86
|
+
#
|
87
|
+
# [Launchpad::NoSuchDeviceError] when device with ID or name specified does not exist
|
88
|
+
# [Launchpad::DeviceBusyError] when device with ID or name specified is busy
|
89
|
+
def initialize(opts = nil)
|
90
|
+
@input = nil
|
91
|
+
@output = nil
|
92
|
+
opts = {
|
93
|
+
:input => true,
|
94
|
+
:output => true
|
95
|
+
}.merge(opts || {})
|
96
|
+
|
97
|
+
self.logger = opts[:logger]
|
98
|
+
logger.debug "initializing Launchpad::Device##{object_id} with #{opts.inspect}"
|
99
|
+
|
100
|
+
Portmidi.start
|
101
|
+
|
102
|
+
@input = create_device!(Portmidi.input_devices, Portmidi::Input,
|
103
|
+
:id => opts[:input_device_id],
|
104
|
+
:name => opts[:device_name]
|
105
|
+
) if opts[:input]
|
106
|
+
@output = create_device!(Portmidi.output_devices, Portmidi::Output,
|
107
|
+
:id => opts[:output_device_id],
|
108
|
+
:name => opts[:device_name]
|
109
|
+
) if opts[:output]
|
110
|
+
end
|
111
|
+
|
112
|
+
# Closes the device - nothing can be done with the device afterwards.
|
113
|
+
def close
|
114
|
+
logger.debug "closing Launchpad::Device##{object_id}"
|
115
|
+
@input.close unless @input.nil?
|
116
|
+
@input = nil
|
117
|
+
@output.close unless @output.nil?
|
118
|
+
@output = nil
|
119
|
+
end
|
120
|
+
|
121
|
+
# Determines whether this device has been closed.
|
122
|
+
def closed?
|
123
|
+
!(input_enabled? || output_enabled?)
|
124
|
+
end
|
125
|
+
|
126
|
+
# Determines whether this device can be used to read input.
|
127
|
+
def input_enabled?
|
128
|
+
!@input.nil?
|
129
|
+
end
|
130
|
+
|
131
|
+
# Determines whether this device can be used to output data.
|
132
|
+
def output_enabled?
|
133
|
+
!@output.nil?
|
134
|
+
end
|
135
|
+
|
136
|
+
# Changes a single LED.
|
137
|
+
#
|
138
|
+
# Parameters (see Launchpad for values):
|
139
|
+
#
|
140
|
+
# [+type+] type of the button to change
|
141
|
+
#
|
142
|
+
# Optional options hash (see Launchpad for values):
|
143
|
+
#
|
144
|
+
# [<tt>:x</tt>] x coordinate
|
145
|
+
# [<tt>:y</tt>] y coordinate
|
146
|
+
# [<tt>color</tt>] color of the LED (value between 0 and 127 inclusive)
|
147
|
+
# optional, defaults to <tt>:off</tt>
|
148
|
+
#
|
149
|
+
# Errors raised:
|
150
|
+
#
|
151
|
+
# [Launchpad::NoValidGridCoordinatesError] when coordinates aren't within the valid range
|
152
|
+
# [Launchpad::NoValidColorError] when color value isn't within the valid range
|
153
|
+
# [Launchpad::NoOutputAllowedError] when output is not enabled
|
154
|
+
def change(type, opts = nil)
|
155
|
+
opts ||= {}
|
156
|
+
status = %w(up down left right session user1 user2 mixer).include?(type.to_s) ? Status::CC : Status::ON
|
157
|
+
output(status, note(type, opts), velocity(opts))
|
158
|
+
end
|
159
|
+
|
160
|
+
SYSEX_HEADER = [240, 0, 32, 41, 2, 24]
|
161
|
+
SYSEX_FOOTER = [247]
|
162
|
+
|
163
|
+
def pulse1(x, y, color_key)
|
164
|
+
note = note(:grid, {:x => x, :y => y})
|
165
|
+
output_sysex(SYSEX_HEADER + [40, 0, note, color_key] + SYSEX_FOOTER)
|
166
|
+
end
|
167
|
+
|
168
|
+
def pulsen(notes, color_key)
|
169
|
+
notes.each { |coord|
|
170
|
+
note = note(:grid, {:x => coord[0], :y => coord[1]})
|
171
|
+
output_sysex(SYSEX_HEADER + [40, 0, note, color_key] + SYSEX_FOOTER)
|
172
|
+
}
|
173
|
+
end
|
174
|
+
|
175
|
+
def flash1(x, y, color_key)
|
176
|
+
note = note(:grid, {:x => x, :y => y})
|
177
|
+
output_sysex(SYSEX_HEADER + [35, 0, note, color_key] + SYSEX_FOOTER)
|
178
|
+
end
|
179
|
+
|
180
|
+
def flashn(notes, color_key)
|
181
|
+
notes.each { |coord|
|
182
|
+
note = note(:grid, {:x => coord[0], :y => coord[1]})
|
183
|
+
output_sysex(SYSEX_HEADER + [35, 0, note, color_key, 0] + SYSEX_FOOTER)
|
184
|
+
}
|
185
|
+
end
|
186
|
+
|
187
|
+
def scroll(color_key, text, mode)
|
188
|
+
output_sysex(SYSEX_HEADER + [20, color_key, mode] + text.chars.map(&:ord) + SYSEX_FOOTER)
|
189
|
+
end
|
190
|
+
|
191
|
+
def scroll_forever(color_key, text)
|
192
|
+
scroll(color_key, text, 1)
|
193
|
+
end
|
194
|
+
|
195
|
+
def scroll_once(color_key, text)
|
196
|
+
scroll(color_key, text, 0)
|
197
|
+
end
|
198
|
+
|
199
|
+
def scroll_stop()
|
200
|
+
output_sysex(SYSEX_HEADER + [20] + SYSEX_FOOTER)
|
201
|
+
end
|
202
|
+
|
203
|
+
def light_all(color_key)
|
204
|
+
output_sysex(SYSEX_HEADER + [14, color_key] + SYSEX_FOOTER)
|
205
|
+
end
|
206
|
+
|
207
|
+
def reset_all()
|
208
|
+
light_all(0)
|
209
|
+
end
|
210
|
+
|
211
|
+
def lightn_column(column_keys, color_key)
|
212
|
+
column_keys.each { |column_key|
|
213
|
+
output_sysex(SYSEX_HEADER + [12, column_key, color_key] + SYSEX_FOOTER)
|
214
|
+
}
|
215
|
+
end
|
216
|
+
|
217
|
+
def light1_column(column_key, color_key)
|
218
|
+
output_sysex(SYSEX_HEADER + [12, column_key, color_key] + SYSEX_FOOTER)
|
219
|
+
end
|
220
|
+
|
221
|
+
def lightn_row(rows_keys, color_key)
|
222
|
+
rows_keys.each { |row_key|
|
223
|
+
output_sysex(SYSEX_HEADER + [13, row_key, color_key] + SYSEX_FOOTER)
|
224
|
+
}
|
225
|
+
end
|
226
|
+
|
227
|
+
def light1_row(row_key, color_key)
|
228
|
+
output_sysex(SYSEX_HEADER + [13, row_key, color_key] + SYSEX_FOOTER)
|
229
|
+
end
|
230
|
+
|
231
|
+
# Reads user actions (button presses/releases) that haven't been handled yet.
|
232
|
+
# This is non-blocking, so when nothing happend yet you'll get an empty array.
|
233
|
+
#
|
234
|
+
# Returns:
|
235
|
+
#
|
236
|
+
# an array of hashes with (see Launchpad for values):
|
237
|
+
#
|
238
|
+
# [<tt>:timestamp</tt>] integer indicating the time when the action occured
|
239
|
+
# [<tt>:state</tt>] state of the button after action
|
240
|
+
# [<tt>:type</tt>] type of the button
|
241
|
+
# [<tt>:x</tt>] x coordinate
|
242
|
+
# [<tt>:y</tt>] y coordinate
|
243
|
+
#
|
244
|
+
# Errors raised:
|
245
|
+
#
|
246
|
+
# [Launchpad::NoInputAllowedError] when input is not enabled
|
247
|
+
def read_pending_actions
|
248
|
+
Array(input).collect do |midi_message|
|
249
|
+
(code, note, velocity) = midi_message[:message]
|
250
|
+
data = {
|
251
|
+
:timestamp => midi_message[:timestamp],
|
252
|
+
:state => (velocity == 127 ? :down : :up)
|
253
|
+
}
|
254
|
+
data[:type] = CODE_NOTE_TO_DATA_TYPE[[code, note]] || :grid
|
255
|
+
if data[:type] == :grid
|
256
|
+
data[:x] = (note % 10) - 1
|
257
|
+
data[:y] = (note / 10) - 1
|
258
|
+
end
|
259
|
+
data
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
private
|
264
|
+
|
265
|
+
# Creates input/output devices.
|
266
|
+
#
|
267
|
+
# Parameters:
|
268
|
+
#
|
269
|
+
# [+devices+] array of portmidi devices
|
270
|
+
# [+device_type] class to instantiate (<tt>Portmidi::Input/Portmidi::Output</tt>)
|
271
|
+
#
|
272
|
+
# Options hash:
|
273
|
+
#
|
274
|
+
# [<tt>:id</tt>] id of the MIDI device to use
|
275
|
+
# [<tt>:name</tt>] name of the MIDI device to use,
|
276
|
+
# only used when <tt>:id</tt> is not specified,
|
277
|
+
# defaults to "Launchpad"
|
278
|
+
#
|
279
|
+
# Returns:
|
280
|
+
#
|
281
|
+
# newly created device
|
282
|
+
#
|
283
|
+
# Errors raised:
|
284
|
+
#
|
285
|
+
# [Launchpad::NoSuchDeviceError] when device with ID or name specified does not exist
|
286
|
+
# [Launchpad::DeviceBusyError] when device with ID or name specified is busy
|
287
|
+
def create_device!(devices, device_type, opts)
|
288
|
+
logger.debug "creating #{device_type} with #{opts.inspect}, choosing from portmidi devices #{devices.inspect}"
|
289
|
+
id = opts[:id]
|
290
|
+
if id.nil?
|
291
|
+
name = opts[:name] || MK2_DEVICE_NAME
|
292
|
+
device = devices.select {|current_device| current_device.name == name}.first
|
293
|
+
id = device.device_id unless device.nil?
|
294
|
+
end
|
295
|
+
if id.nil?
|
296
|
+
message = "MIDI device #{opts[:id] || opts[:name]} doesn't exist"
|
297
|
+
logger.fatal message
|
298
|
+
raise NoSuchDeviceError.new(message)
|
299
|
+
end
|
300
|
+
device_type.new(id)
|
301
|
+
rescue RuntimeError => e
|
302
|
+
logger.fatal "error creating #{device_type}: #{e.inspect}"
|
303
|
+
raise DeviceBusyError.new(e)
|
304
|
+
end
|
305
|
+
|
306
|
+
# Reads input from the MIDI device.
|
307
|
+
#
|
308
|
+
# Returns:
|
309
|
+
#
|
310
|
+
# an array of hashes with:
|
311
|
+
#
|
312
|
+
# [<tt>:message</tt>] an array of
|
313
|
+
# MIDI status code,
|
314
|
+
# MIDI data 1 (note),
|
315
|
+
# MIDI data 2 (velocity)
|
316
|
+
# and a fourth value
|
317
|
+
# [<tt>:timestamp</tt>] integer indicating the time when the MIDI message was created
|
318
|
+
#
|
319
|
+
# Errors raised:
|
320
|
+
#
|
321
|
+
# [Launchpad::NoInputAllowedError] when output is not enabled
|
322
|
+
def input
|
323
|
+
if @input.nil?
|
324
|
+
logger.error "trying to read from device that's not been initialized for input"
|
325
|
+
raise NoInputAllowedError
|
326
|
+
end
|
327
|
+
@input.read(16)
|
328
|
+
end
|
329
|
+
|
330
|
+
# Writes data to the MIDI device.
|
331
|
+
#
|
332
|
+
# Parameters:
|
333
|
+
#
|
334
|
+
# [+status+] MIDI status code
|
335
|
+
# [+data1+] MIDI data 1 (note)
|
336
|
+
# [+data2+] MIDI data 2 (velocity)
|
337
|
+
#
|
338
|
+
# Errors raised:
|
339
|
+
#
|
340
|
+
# [Launchpad::NoOutputAllowedError] when output is not enabled
|
341
|
+
def output(status, data1, data2)
|
342
|
+
output_messages([message(status, data1, data2)])
|
343
|
+
end
|
344
|
+
|
345
|
+
# Writes several messages to the MIDI device.
|
346
|
+
#
|
347
|
+
# Parameters:
|
348
|
+
#
|
349
|
+
# [+messages+] an array of hashes (usually created with message) with:
|
350
|
+
# [<tt>:message</tt>] an array of
|
351
|
+
# MIDI status code,
|
352
|
+
# MIDI data 1 (note),
|
353
|
+
# MIDI data 2 (velocity)
|
354
|
+
# [<tt>:timestamp</tt>] integer indicating the time when the MIDI message was created
|
355
|
+
def output_messages(messages)
|
356
|
+
if @output.nil?
|
357
|
+
logger.error "trying to write to device that's not been initialized for output"
|
358
|
+
raise NoOutputAllowedError
|
359
|
+
end
|
360
|
+
logger.debug "writing messages to launchpad:\n #{messages.join("\n ")}" if logger.debug?
|
361
|
+
@output.write(messages)
|
362
|
+
nil
|
363
|
+
end
|
364
|
+
|
365
|
+
def output_sysex(messages)
|
366
|
+
if @output.nil?
|
367
|
+
logger.error "trying to write to device that's not been initialized for output"
|
368
|
+
raise NoOutputAllowedError
|
369
|
+
end
|
370
|
+
logger.debug "writing sysex to launchpad:\n #{messages.join("\n ")}" if logger.debug?
|
371
|
+
@output.write_sysex(messages)
|
372
|
+
nil
|
373
|
+
end
|
374
|
+
|
375
|
+
# Calculates the MIDI data 1 value (note) for a button.
|
376
|
+
#
|
377
|
+
# Parameters (see Launchpad for values):
|
378
|
+
#
|
379
|
+
# [+type+] type of the button
|
380
|
+
#
|
381
|
+
# Options hash:
|
382
|
+
#
|
383
|
+
# [<tt>:x</tt>] x coordinate
|
384
|
+
# [<tt>:y</tt>] y coordinate
|
385
|
+
#
|
386
|
+
# Returns:
|
387
|
+
#
|
388
|
+
# integer to be used for MIDI data 1
|
389
|
+
#
|
390
|
+
# Errors raised:
|
391
|
+
#
|
392
|
+
# [Launchpad::NoValidGridCoordinatesError] when coordinates aren't within the valid range
|
393
|
+
def note(type, opts)
|
394
|
+
note = TYPE_TO_NOTE[type]
|
395
|
+
if note.nil?
|
396
|
+
x = (opts[:x] || -1).to_i
|
397
|
+
y = (opts[:y] || -1).to_i
|
398
|
+
if x < 0 || x > 7 || y < 0 || y > 7
|
399
|
+
logger.error "wrong coordinates specified: x=#{x}, y=#{y}"
|
400
|
+
raise NoValidGridCoordinatesError.new("you need to specify valid coordinates (x/y, 0-7, from top left), you specified: x=#{x}, y=#{y}")
|
401
|
+
end
|
402
|
+
note = (y + 1) * 10 + (x + 1)
|
403
|
+
end
|
404
|
+
note
|
405
|
+
end
|
406
|
+
|
407
|
+
# Calculates the MIDI data 2 value (velocity) for given brightness and mode values.
|
408
|
+
#
|
409
|
+
# Options hash:
|
410
|
+
#
|
411
|
+
# [<tt>color</tt>] color of the LED (value between 0 and 127 inclusive)
|
412
|
+
# optional, defaults to <tt>:off</tt>
|
413
|
+
#
|
414
|
+
# Returns:
|
415
|
+
#
|
416
|
+
# integer to be used for MIDI data 2
|
417
|
+
def velocity(opts)
|
418
|
+
if opts.is_a?(Hash)
|
419
|
+
color = color(opts[:color]) || 0
|
420
|
+
else
|
421
|
+
opts.to_i + 12
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
def color(color_key)
|
426
|
+
if color_key.nil?
|
427
|
+
0
|
428
|
+
else
|
429
|
+
if (not (color_key.is_a? Integer))
|
430
|
+
logger.error "wrong color specified: color_key=#{color_key}"
|
431
|
+
raise NoValidColorError.new("you need to specify a valid color (0-127), you specified: color_key=#{color_key}")
|
432
|
+
end
|
433
|
+
|
434
|
+
if color_key < 0 || color_key > 127
|
435
|
+
logger.error "wrong color specified: color_key=#{color_key}"
|
436
|
+
raise NoValidColorError.new("you need to specify a valid color (0-127), you specified: color_key=#{color_key}")
|
437
|
+
end
|
438
|
+
|
439
|
+
color_key
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
# Creates a MIDI message.
|
444
|
+
#
|
445
|
+
# Parameters:
|
446
|
+
#
|
447
|
+
# [+status+] MIDI status code
|
448
|
+
# [+data1+] MIDI data 1 (note)
|
449
|
+
# [+data2+] MIDI data 2 (velocity)
|
450
|
+
#
|
451
|
+
# Returns:
|
452
|
+
#
|
453
|
+
# an array with:
|
454
|
+
#
|
455
|
+
# [<tt>:message</tt>] an array of
|
456
|
+
# MIDI status code,
|
457
|
+
# MIDI data 1 (note),
|
458
|
+
# MIDI data 2 (velocity)
|
459
|
+
# [<tt>:timestamp</tt>] integer indicating the time when the MIDI message was created, in this case 0
|
460
|
+
def message(status, data1, data2)
|
461
|
+
{:message => [status, data1, data2], :timestamp => 0}
|
462
|
+
end
|
463
|
+
|
464
|
+
end
|
465
|
+
|
466
|
+
end
|