mrjoy-launchpad 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,25 @@
1
+ require 'launchpad'
2
+
3
+ interaction = Launchpad::Interaction.new
4
+
5
+ flags = Hash.new(false)
6
+
7
+ # yellow feedback for grid buttons
8
+ interaction.response_to(:grid, :down) do |interaction, action|
9
+ coord = 16 * action[:y] + action[:x]
10
+ brightness = flags[coord] ? :off : :hi
11
+ flags[coord] = !flags[coord]
12
+ interaction.device.change(:grid, action.merge(:red => brightness, :green => brightness))
13
+ end
14
+
15
+ # mixer button terminates interaction on button up
16
+ interaction.response_to(:mixer) do |interaction, action|
17
+ interaction.device.change(:mixer, :red => action[:state] == :down ? :hi : :off)
18
+ interaction.stop if action[:state] == :up
19
+ end
20
+
21
+ # start interacting
22
+ interaction.start
23
+
24
+ # sleep so that the messages can be sent before the program terminates
25
+ sleep 0.1
@@ -0,0 +1,34 @@
1
+ require 'launchpad'
2
+
3
+ interaction = Launchpad::Interaction.new
4
+
5
+ def brightness(action)
6
+ action[:state] == :down ? :hi : :off
7
+ end
8
+
9
+ # yellow feedback for grid buttons
10
+ interaction.response_to(:grid) do |interaction, action|
11
+ b = brightness(action)
12
+ interaction.device.change(:grid, action.merge(:red => b, :green => b))
13
+ end
14
+
15
+ # red feedback for top control buttons
16
+ interaction.response_to([:up, :down, :left, :right, :session, :user1, :user2, :mixer]) do |interaction, action|
17
+ interaction.device.change(action[:type], :red => brightness(action))
18
+ end
19
+
20
+ # green feedback for scene buttons
21
+ interaction.response_to([:scene1, :scene2, :scene3, :scene4, :scene5, :scene6, :scene7, :scene8]) do |interaction, action|
22
+ interaction.device.change(action[:type], :green => brightness(action))
23
+ end
24
+
25
+ # mixer button terminates interaction on button up
26
+ interaction.response_to(:mixer, :up) do |interaction, action|
27
+ interaction.stop
28
+ end
29
+
30
+ # start interacting
31
+ interaction.start
32
+
33
+ # sleep so that the messages can be sent before the program terminates
34
+ sleep 0.1
@@ -0,0 +1,6 @@
1
+ require 'launchpad'
2
+
3
+ Launchpad::Device.new.reset
4
+
5
+ # sleep so that the messages can be sent before the program terminates
6
+ sleep 0.1
@@ -0,0 +1,34 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "launchpad/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "mrjoy-launchpad"
7
+ s.version = Launchpad::VERSION
8
+ s.authors = ["Jon Frisby", "Thomas Jachmann"]
9
+ s.email = ["jfrisby@mrjoy.com", "self@thomasjachmann.com"]
10
+ s.homepage = "https://github.com/MrJoy/launchpad"
11
+ s.summary = %q{A gem for accessing Novation's LaunchPad programmatically and easily.}
12
+ s.description = %q{This gem provides an interface to access Novation's LaunchPad programmatically. LEDs can be lighted and button presses can be evaluated using launchpad's MIDI input/output.}
13
+
14
+ s.rubyforge_project = "launchpad"
15
+
16
+ s.add_dependency "portmidi", ">= 0.0.6"
17
+ s.add_dependency "ffi"
18
+ s.add_development_dependency "rake"
19
+ if RUBY_VERSION < "1.9"
20
+ s.add_development_dependency "minitest"
21
+ # s.add_development_dependency "ruby-debug"
22
+ else
23
+ s.add_development_dependency "minitest-reporters"
24
+ # s.add_development_dependency "debugger"
25
+ end
26
+ s.add_development_dependency "mocha"
27
+
28
+ # s.has_rdoc = true
29
+
30
+ s.files = `git ls-files`.split("\n")
31
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
32
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
33
+ s.require_paths = ["lib"]
34
+ end
@@ -0,0 +1,34 @@
1
+ require 'launchpad/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/top to bottom),
18
+ # mandatory when +type+ is set to <tt>:grid</tt>
19
+ # [<tt>red/green</tt>] brightness of the red/green LED,
20
+ # can be set to one of four levels:
21
+ # * off (<tt>:off, 0</tt>)
22
+ # * low brightness (<tt>:low, :lo, 1</tt>)
23
+ # * medium brightness (<tt>:medium, :med, 2</tt>)
24
+ # * full brightness (<tt>:high, :hi, 3</tt>)
25
+ # optional, defaults to <tt>:off</tt>
26
+ # [+mode+] button mode,
27
+ # one of
28
+ # * <tt>:normal</tt>
29
+ # * <tt>:flashing</tt> (LED is marked as flashing, see Launchpad::Device.flashing_on, Launchpad::Device.flashing_off and Launchpad::Device.flashing_auto)
30
+ # * <tt>:buffering</tt> (LED is written to buffer, see Launchpad::Device.start_buffering, Launchpad::Device.flush_buffer)
31
+ # optional, defaults to <tt>:normal</tt>
32
+ # [+state+] whether the button is pressed or released, <tt>:down/:up</tt>
33
+ module Launchpad
34
+ end
@@ -0,0 +1,574 @@
1
+ require 'portmidi'
2
+
3
+ require 'launchpad/errors'
4
+ require 'launchpad/logging'
5
+ require 'launchpad/midi_codes'
6
+ require 'launchpad/version'
7
+
8
+ module Launchpad
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/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
+ CODE_NOTE_TO_DATA_TYPE = {
29
+ [Status::ON, SceneButton::SCENE1] => :scene1,
30
+ [Status::ON, SceneButton::SCENE2] => :scene2,
31
+ [Status::ON, SceneButton::SCENE3] => :scene3,
32
+ [Status::ON, SceneButton::SCENE4] => :scene4,
33
+ [Status::ON, SceneButton::SCENE5] => :scene5,
34
+ [Status::ON, SceneButton::SCENE6] => :scene6,
35
+ [Status::ON, SceneButton::SCENE7] => :scene7,
36
+ [Status::ON, SceneButton::SCENE8] => :scene8,
37
+ [Status::CC, ControlButton::UP] => :up,
38
+ [Status::CC, ControlButton::DOWN] => :down,
39
+ [Status::CC, ControlButton::LEFT] => :left,
40
+ [Status::CC, ControlButton::RIGHT] => :right,
41
+ [Status::CC, ControlButton::SESSION] => :session,
42
+ [Status::CC, ControlButton::USER1] => :user1,
43
+ [Status::CC, ControlButton::USER2] => :user2,
44
+ [Status::CC, ControlButton::MIXER] => :mixer
45
+ }.freeze
46
+
47
+ # TODO: Rename scenes to match Mk2
48
+ TYPE_TO_NOTE = {
49
+ :up => ControlButton::UP,
50
+ :down => ControlButton::DOWN,
51
+ :left => ControlButton::LEFT,
52
+ :right => ControlButton::RIGHT,
53
+ :session => ControlButton::SESSION,
54
+ :user1 => ControlButton::USER1,
55
+ :user2 => ControlButton::USER2,
56
+ :mixer => ControlButton::MIXER,
57
+ :scene1 => SceneButton::SCENE1,
58
+ :scene2 => SceneButton::SCENE2,
59
+ :scene3 => SceneButton::SCENE3,
60
+ :scene4 => SceneButton::SCENE4,
61
+ :scene5 => SceneButton::SCENE5,
62
+ :scene6 => SceneButton::SCENE6,
63
+ :scene7 => SceneButton::SCENE7,
64
+ :scene8 => SceneButton::SCENE8
65
+ }.freeze
66
+
67
+ # Initializes the launchpad device. When output capabilities are requested,
68
+ # the launchpad will be reset.
69
+ #
70
+ # Optional options hash:
71
+ #
72
+ # [<tt>:input</tt>] whether to use MIDI input for user interaction,
73
+ # <tt>true/false</tt>, optional, defaults to +true+
74
+ # [<tt>:output</tt>] whether to use MIDI output for data display,
75
+ # <tt>true/false</tt>, optional, defaults to +true+
76
+ # [<tt>:input_device_id</tt>] ID of the MIDI input device to use,
77
+ # optional, <tt>:device_name</tt> will be used if omitted
78
+ # [<tt>:output_device_id</tt>] ID of the MIDI output device to use,
79
+ # optional, <tt>:device_name</tt> will be used if omitted
80
+ # [<tt>:device_name</tt>] Name of the MIDI device to use,
81
+ # optional, defaults to "Launchpad"
82
+ # [<tt>:logger</tt>] [Logger] to be used by this device instance, can be changed afterwards
83
+ #
84
+ # Errors raised:
85
+ #
86
+ # [Launchpad::NoSuchDeviceError] when device with ID or name specified does not exist
87
+ # [Launchpad::DeviceBusyError] when device with ID or name specified is busy
88
+ def initialize(opts = nil)
89
+ opts = {
90
+ :input => true,
91
+ :output => true
92
+ }.merge(opts || {})
93
+
94
+ self.logger = opts[:logger]
95
+ logger.debug "initializing Launchpad::Device##{object_id} with #{opts.inspect}"
96
+
97
+ Portmidi.start
98
+
99
+ @input = create_device!(Portmidi.input_devices, Portmidi::Input,
100
+ :id => opts[:input_device_id],
101
+ :name => opts[:device_name]
102
+ ) if opts[:input]
103
+ @output = create_device!(Portmidi.output_devices, Portmidi::Output,
104
+ :id => opts[:output_device_id],
105
+ :name => opts[:device_name]
106
+ ) if opts[:output]
107
+
108
+ reset if output_enabled?
109
+ end
110
+
111
+ # Closes the device - nothing can be done with the device afterwards.
112
+ def close
113
+ logger.debug "closing Launchpad::Device##{object_id}"
114
+ @input.close unless @input.nil?
115
+ @input = nil
116
+ @output.close unless @output.nil?
117
+ @output = nil
118
+ end
119
+
120
+ # Determines whether this device has been closed.
121
+ def closed?
122
+ !(input_enabled? || output_enabled?)
123
+ end
124
+
125
+ # Determines whether this device can be used to read input.
126
+ def input_enabled?
127
+ !@input.nil?
128
+ end
129
+
130
+ # Determines whether this device can be used to output data.
131
+ def output_enabled?
132
+ !@output.nil?
133
+ end
134
+
135
+ # Resets the launchpad - all settings are reset and all LEDs are switched off.
136
+ #
137
+ # Errors raised:
138
+ #
139
+ # [Launchpad::NoOutputAllowedError] when output is not enabled
140
+ def reset
141
+ output(Status::CC, Status::NIL, Status::NIL)
142
+ end
143
+
144
+ # Lights all LEDs (for testing purposes).
145
+ #
146
+ # Parameters (see Launchpad for values):
147
+ #
148
+ # [+brightness+] brightness of both LEDs for all buttons
149
+ #
150
+ # Errors raised:
151
+ #
152
+ # [Launchpad::NoOutputAllowedError] when output is not enabled
153
+ # def test_leds(brightness = :high)
154
+ # brightness = brightness(brightness)
155
+ # if brightness == 0
156
+ # reset
157
+ # else
158
+ # output(Status::CC, Status::NIL, Velocity::TEST_LEDS + brightness)
159
+ # end
160
+ # end
161
+
162
+ # Changes a single LED.
163
+ #
164
+ # Parameters (see Launchpad for values):
165
+ #
166
+ # [+type+] type of the button to change
167
+ #
168
+ # Optional options hash (see Launchpad for values):
169
+ #
170
+ # [<tt>:x</tt>] x coordinate
171
+ # [<tt>:y</tt>] y coordinate
172
+ # [<tt>:red</tt>] brightness of red LED
173
+ # [<tt>:green</tt>] brightness of green LED
174
+ # [<tt>:mode</tt>] button mode, defaults to <tt>:normal</tt>, one of:
175
+ # [<tt>:normal/tt>] updates the LED for all circumstances (the new value will be written to both buffers)
176
+ # [<tt>:flashing/tt>] updates the LED for flashing (the new value will be written to buffer 0 while the LED will be off in buffer 1, see buffering_mode)
177
+ # [<tt>:buffering/tt>] updates the LED for the current update_buffer only
178
+ #
179
+ # Errors raised:
180
+ #
181
+ # [Launchpad::NoValidGridCoordinatesError] when coordinates aren't within the valid range
182
+ # [Launchpad::NoValidBrightnessError] when brightness values aren't within the valid range
183
+ # [Launchpad::NoOutputAllowedError] when output is not enabled
184
+ # def change(type, opts = nil)
185
+ # opts ||= {}
186
+ # status = %w(up down left right session user1 user2 mixer).include?(type.to_s) ? Status::CC : Status::ON
187
+ # output(status, note(type, opts), velocity(opts))
188
+ # end
189
+ def change_grid(x, y, r, g, b)
190
+ led = (y * 10) + x + 11
191
+ @output.write_sysex([
192
+ # SysEx Begin:
193
+ 0xF0,
194
+ # Manufacturer/Device:
195
+ 0x00,
196
+ 0x20,
197
+ 0x29,
198
+ 0x02,
199
+ 0x18,
200
+ # Command:
201
+ 0x0B,
202
+ # LED:
203
+ led,
204
+ # Red, Green, Blue:
205
+ r,
206
+ g,
207
+ b,
208
+ # SysEx End:
209
+ 0xF7,
210
+ ])
211
+ end
212
+
213
+ def change_command(position, r, g, b)
214
+ led = TYPE_TO_NOTE[position]
215
+ @output.write_sysex([
216
+ # SysEx Begin:
217
+ 0xF0,
218
+ # Manufacturer/Device:
219
+ 0x00,
220
+ 0x20,
221
+ 0x29,
222
+ 0x02,
223
+ 0x18,
224
+ # Command:
225
+ 0x0B,
226
+ # LED:
227
+ led,
228
+ # Red, Green, Blue:
229
+ r,
230
+ g,
231
+ b,
232
+ # SysEx End:
233
+ 0xF7,
234
+ ])
235
+ end
236
+
237
+ # Changes all LEDs in batch mode.
238
+ #
239
+ # Parameters (see Launchpad for values):
240
+ #
241
+ # [+colors] an array of colors, each either being an integer or a Hash
242
+ # * integer: calculated using the formula
243
+ # <tt>color = 16 * green + red</tt>
244
+ # * Hash:
245
+ # [<tt>:red</tt>] brightness of red LED
246
+ # [<tt>:green</tt>] brightness of green LED
247
+ # [<tt>:mode</tt>] button mode, defaults to <tt>:normal</tt>, one of:
248
+ # [<tt>:normal/tt>] updates the LEDs for all circumstances (the new value will be written to both buffers)
249
+ # [<tt>:flashing/tt>] updates the LEDs for flashing (the new values will be written to buffer 0 while the LEDs will be off in buffer 1, see buffering_mode)
250
+ # [<tt>:buffering/tt>] updates the LEDs for the current update_buffer only
251
+ # the array consists of 64 colors for the grid buttons,
252
+ # 8 colors for the scene buttons (top to bottom)
253
+ # and 8 colors for the top control buttons (left to right),
254
+ # maximum 80 values - excessive values will be ignored,
255
+ # missing values will be filled with 0
256
+ #
257
+ # Errors raised:
258
+ #
259
+ # [Launchpad::NoValidBrightnessError] when brightness values aren't within the valid range
260
+ # [Launchpad::NoOutputAllowedError] when output is not enabled
261
+ # def change_all(*colors)
262
+ # # ensure that colors is at least and most 80 elements long
263
+ # colors = colors.flatten[0..79]
264
+ # colors += [0] * (80 - colors.size) if colors.size < 80
265
+ # # send normal MIDI message to reset rapid LED change pointer
266
+ # # in this case, set mapping mode to x-y layout (the default)
267
+ # output(Status::CC, Status::NIL, GridLayout::XY)
268
+ # # send colors in slices of 2
269
+ # messages = []
270
+ # colors.each_slice(2) do |c1, c2|
271
+ # messages << message(Status::MULTI, velocity(c1), velocity(c2))
272
+ # end
273
+ # output_messages(messages)
274
+ # end
275
+
276
+ # Switches LEDs marked as flashing on when using custom timer for flashing.
277
+ #
278
+ # Errors raised:
279
+ #
280
+ # [Launchpad::NoOutputAllowedError] when output is not enabled
281
+ # def flashing_on
282
+ # buffering_mode(:display_buffer => 0)
283
+ # end
284
+
285
+ # Switches LEDs marked as flashing off when using custom timer for flashing.
286
+ #
287
+ # Errors raised:
288
+ #
289
+ # [Launchpad::NoOutputAllowedError] when output is not enabled
290
+ # def flashing_off
291
+ # buffering_mode(:display_buffer => 1)
292
+ # end
293
+
294
+ # Starts flashing LEDs marked as flashing automatically.
295
+ # Stop flashing by calling flashing_on or flashing_off.
296
+ #
297
+ # Errors raised:
298
+ #
299
+ # [Launchpad::NoOutputAllowedError] when output is not enabled
300
+ # def flashing_auto
301
+ # buffering_mode(:flashing => true)
302
+ # end
303
+
304
+ # Controls the two buffers.
305
+ #
306
+ # Optional options hash:
307
+ #
308
+ # [<tt>:display_buffer</tt>] which buffer to use for display, defaults to +0+
309
+ # [<tt>:update_buffer</tt>] which buffer to use for updates when <tt>:mode</tt> is set to <tt>:buffering</tt>, defaults to +0+ (see change)
310
+ # [<tt>:copy</tt>] whether to copy the LEDs states from the new display_buffer over to the new update_buffer, <tt>true/false</tt>, defaults to <tt>false</tt>
311
+ # [<tt>:flashing</tt>] whether to start flashing by automatically switching between the two buffers for display, <tt>true/false</tt>, defaults to <tt>false</tt>
312
+ #
313
+ # Errors raised:
314
+ #
315
+ # [Launchpad::NoOutputAllowedError] when output is not enabled
316
+ # def buffering_mode(opts = nil)
317
+ # opts = {
318
+ # :display_buffer => 0,
319
+ # :update_buffer => 0,
320
+ # :copy => false,
321
+ # :flashing => false
322
+ # }.merge(opts || {})
323
+ # data = opts[:display_buffer] + 4 * opts[:update_buffer] + 32
324
+ # data += 16 if opts[:copy]
325
+ # data += 8 if opts[:flashing]
326
+ # output(Status::CC, Status::NIL, data)
327
+ # end
328
+
329
+ # Reads user actions (button presses/releases) that haven't been handled yet.
330
+ # This is non-blocking, so when nothing happend yet you'll get an empty array.
331
+ #
332
+ # Returns:
333
+ #
334
+ # an array of hashes with (see Launchpad for values):
335
+ #
336
+ # [<tt>:timestamp</tt>] integer indicating the time when the action occured
337
+ # [<tt>:state</tt>] state of the button after action
338
+ # [<tt>:type</tt>] type of the button
339
+ # [<tt>:x</tt>] x coordinate
340
+ # [<tt>:y</tt>] y coordinate
341
+ #
342
+ # Errors raised:
343
+ #
344
+ # [Launchpad::NoInputAllowedError] when input is not enabled
345
+ def read_pending_actions
346
+ Array(input).collect do |midi_message|
347
+ (code, note, velocity) = midi_message[:message]
348
+ data = {
349
+ :timestamp => midi_message[:timestamp],
350
+ :state => (velocity == 127 ? :down : :up)
351
+ }
352
+ data[:type] = CODE_NOTE_TO_DATA_TYPE[[code, note]] || :grid
353
+ if data[:type] == :grid
354
+ note = note - 11
355
+ data[:x] = note % 10
356
+ data[:y] = note / 10
357
+ end
358
+ data
359
+ end
360
+ end
361
+
362
+ private
363
+
364
+ # Creates input/output devices.
365
+ #
366
+ # Parameters:
367
+ #
368
+ # [+devices+] array of portmidi devices
369
+ # [+device_type] class to instantiate (<tt>Portmidi::Input/Portmidi::Output</tt>)
370
+ #
371
+ # Options hash:
372
+ #
373
+ # [<tt>:id</tt>] id of the MIDI device to use
374
+ # [<tt>:name</tt>] name of the MIDI device to use,
375
+ # only used when <tt>:id</tt> is not specified,
376
+ # defaults to "Launchpad"
377
+ #
378
+ # Returns:
379
+ #
380
+ # newly created device
381
+ #
382
+ # Errors raised:
383
+ #
384
+ # [Launchpad::NoSuchDeviceError] when device with ID or name specified does not exist
385
+ # [Launchpad::DeviceBusyError] when device with ID or name specified is busy
386
+ def create_device!(devices, device_type, opts)
387
+ logger.debug "creating #{device_type} with #{opts.inspect}, choosing from portmidi devices #{devices.inspect}"
388
+ id = opts[:id]
389
+ if id.nil?
390
+ name = opts[:name] || "Launchpad MK2"
391
+ device = devices.select {|dev| dev.name == name}.first
392
+ id = device.device_id unless device.nil?
393
+ end
394
+ if id.nil?
395
+ message = "MIDI device #{opts[:id] || opts[:name]} doesn't exist"
396
+ logger.fatal message
397
+ raise NoSuchDeviceError.new(message)
398
+ end
399
+ device_type.new(id)
400
+ rescue RuntimeError => e
401
+ logger.fatal "error creating #{device_type}: #{e.inspect}"
402
+ raise DeviceBusyError.new(e)
403
+ end
404
+
405
+ # Reads input from the MIDI device.
406
+ #
407
+ # Returns:
408
+ #
409
+ # an array of hashes with:
410
+ #
411
+ # [<tt>:message</tt>] an array of
412
+ # MIDI status code,
413
+ # MIDI data 1 (note),
414
+ # MIDI data 2 (velocity)
415
+ # and a fourth value
416
+ # [<tt>:timestamp</tt>] integer indicating the time when the MIDI message was created
417
+ #
418
+ # Errors raised:
419
+ #
420
+ # [Launchpad::NoInputAllowedError] when output is not enabled
421
+ def input
422
+ if @input.nil?
423
+ logger.error "trying to read from device that's not been initialized for input"
424
+ raise NoInputAllowedError
425
+ end
426
+ @input.read(16)
427
+ end
428
+
429
+ # Writes data to the MIDI device.
430
+ #
431
+ # Parameters:
432
+ #
433
+ # [+status+] MIDI status code
434
+ # [+data1+] MIDI data 1 (note)
435
+ # [+data2+] MIDI data 2 (velocity)
436
+ #
437
+ # Errors raised:
438
+ #
439
+ # [Launchpad::NoOutputAllowedError] when output is not enabled
440
+ def output(status, data1, data2)
441
+ output_messages([message(status, data1, data2)])
442
+ end
443
+
444
+ # Writes several messages to the MIDI device.
445
+ #
446
+ # Parameters:
447
+ #
448
+ # [+messages+] an array of hashes (usually created with message) with:
449
+ # [<tt>:message</tt>] an array of
450
+ # MIDI status code,
451
+ # MIDI data 1 (note),
452
+ # MIDI data 2 (velocity)
453
+ # [<tt>:timestamp</tt>] integer indicating the time when the MIDI message was created
454
+ def output_messages(messages)
455
+ if @output.nil?
456
+ logger.error "trying to write to device that's not been initialized for output"
457
+ raise NoOutputAllowedError
458
+ end
459
+ logger.debug "writing messages to launchpad:\n #{messages.join("\n ")}" if logger.debug?
460
+ @output.write(messages)
461
+ nil
462
+ end
463
+
464
+ # Calculates the MIDI data 1 value (note) for a button.
465
+ #
466
+ # Parameters (see Launchpad for values):
467
+ #
468
+ # [+type+] type of the button
469
+ #
470
+ # Options hash:
471
+ #
472
+ # [<tt>:x</tt>] x coordinate
473
+ # [<tt>:y</tt>] y coordinate
474
+ #
475
+ # Returns:
476
+ #
477
+ # integer to be used for MIDI data 1
478
+ #
479
+ # Errors raised:
480
+ #
481
+ # [Launchpad::NoValidGridCoordinatesError] when coordinates aren't within the valid range
482
+ def note(type, opts)
483
+ note = TYPE_TO_NOTE[type]
484
+ if note.nil?
485
+ x = (opts[:x] || -1).to_i
486
+ y = (opts[:y] || -1).to_i
487
+ if x < 0 || x > 7 || y < 0 || y > 7
488
+ logger.error "wrong coordinates specified: x=#{x}, y=#{y}"
489
+ raise NoValidGridCoordinatesError.new("you need to specify valid coordinates (x/y, 0-7, from top left), you specified: x=#{x}, y=#{y}")
490
+ end
491
+ note = y * 10 + x
492
+ end
493
+ note
494
+ end
495
+
496
+ # Calculates the MIDI data 2 value (velocity) for given brightness and mode values.
497
+ #
498
+ # Options hash:
499
+ #
500
+ # [<tt>:red</tt>] brightness of red LED
501
+ # [<tt>:green</tt>] brightness of green LED
502
+ # [<tt>:mode</tt>] button mode, defaults to <tt>:normal</tt>, one of:
503
+ # [<tt>:normal/tt>] updates the LED for all circumstances (the new value will be written to both buffers)
504
+ # [<tt>:flashing/tt>] updates the LED for flashing (the new value will be written to buffer 0 while in buffer 1, the value will be :off, see )
505
+ # [<tt>:buffering/tt>] updates the LED for the current update_buffer only
506
+ #
507
+ # Returns:
508
+ #
509
+ # integer to be used for MIDI data 2
510
+ #
511
+ # Errors raised:
512
+ #
513
+ # [Launchpad::NoValidBrightnessError] when brightness values aren't within the valid range
514
+ # def velocity(opts)
515
+ # if opts.is_a?(Hash)
516
+ # red = brightness(opts[:red] || 0)
517
+ # green = brightness(opts[:green] || 0)
518
+ # color = 16 * green + red
519
+ # flags = case opts[:mode]
520
+ # when :flashing then 8
521
+ # when :buffering then 0
522
+ # else 12
523
+ # end
524
+ # color + flags
525
+ # else
526
+ # opts.to_i + 12
527
+ # end
528
+ # end
529
+
530
+ # Calculates the integer brightness for given brightness values.
531
+ #
532
+ # Parameters (see Launchpad for values):
533
+ #
534
+ # [+brightness+] brightness
535
+ #
536
+ # Errors raised:
537
+ #
538
+ # [Launchpad::NoValidBrightnessError] when brightness values aren't within the valid range
539
+ # def brightness(brightness)
540
+ # case brightness
541
+ # when 0, :off then 0
542
+ # when 1, :low, :lo then 1
543
+ # when 2, :medium, :med then 2
544
+ # when 3, :high, :hi then 3
545
+ # else
546
+ # logger.error "wrong brightness specified: #{brightness}"
547
+ # raise NoValidBrightnessError.new("you need to specify the brightness as 0/1/2/3, :off/:low/:medium/:high or :off/:lo/:hi, you specified: #{brightness}")
548
+ # end
549
+ # end
550
+
551
+ # Creates a MIDI message.
552
+ #
553
+ # Parameters:
554
+ #
555
+ # [+status+] MIDI status code
556
+ # [+data1+] MIDI data 1 (note)
557
+ # [+data2+] MIDI data 2 (velocity)
558
+ #
559
+ # Returns:
560
+ #
561
+ # an array with:
562
+ #
563
+ # [<tt>:message</tt>] an array of
564
+ # MIDI status code,
565
+ # MIDI data 1 (note),
566
+ # MIDI data 2 (velocity)
567
+ # [<tt>:timestamp</tt>] integer indicating the time when the MIDI message was created, in this case 0
568
+ def message(status, data1, data2)
569
+ {:message => [status, data1, data2], :timestamp => 0}
570
+ end
571
+
572
+ end
573
+
574
+ end