mrjoy-launchpad 0.4.0
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 +8 -0
- data/Gemfile +6 -0
- data/LICENSE +20 -0
- data/README.rdoc +136 -0
- data/Rakefile +10 -0
- data/examples/binary_clock.rb +29 -0
- data/examples/color_picker.rb +96 -0
- data/examples/colors.rb +21 -0
- data/examples/doodle.rb +68 -0
- data/examples/double_buffering.rb +104 -0
- data/examples/drawing_board.rb +25 -0
- data/examples/feedback.rb +34 -0
- data/examples/reset.rb +6 -0
- data/launchpad.gemspec +34 -0
- data/lib/launchpad.rb +34 -0
- data/lib/launchpad/device.rb +574 -0
- data/lib/launchpad/errors.rb +37 -0
- data/lib/launchpad/interaction.rb +336 -0
- data/lib/launchpad/logging.rb +27 -0
- data/lib/launchpad/midi_codes.rb +53 -0
- data/lib/launchpad/version.rb +3 -0
- data/monitor.rb +88 -0
- data/test/helper.rb +44 -0
- data/test/test_device.rb +530 -0
- data/test/test_interaction.rb +456 -0
- data/testbed.rb +48 -0
- metadata +146 -0
@@ -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
|
data/examples/reset.rb
ADDED
data/launchpad.gemspec
ADDED
@@ -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
|
data/lib/launchpad.rb
ADDED
@@ -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
|