mrjoy-launchpad 0.4.0

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.
@@ -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