launchpad_mk2 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|