launchpad 0.1.1 → 0.2.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.
- data/README.rdoc +6 -1
- data/examples/double_buffering.rb +104 -0
- data/launchpad.gemspec +4 -2
- data/lib/launchpad/device.rb +77 -21
- data/lib/launchpad/midi_codes.rb +6 -3
- data/lib/launchpad/version.rb +1 -1
- data/test/test_device.rb +40 -8
- metadata +4 -2
data/README.rdoc
CHANGED
@@ -66,12 +66,17 @@ For more details, see the examples. examples/color_picker.rb is the most complex
|
|
66
66
|
== Near future plans
|
67
67
|
|
68
68
|
* interaction responses for presses on single grid buttons/button areas
|
69
|
-
* double buffering
|
70
69
|
* bitmap rendering
|
71
70
|
|
72
71
|
|
73
72
|
== Changelog
|
74
73
|
|
74
|
+
=== v.0.2.0
|
75
|
+
|
76
|
+
* double buffering (see Launchpad::Device#buffering_mode)
|
77
|
+
* don't update grid button 0,0 before change_all (in order to reset rapid update pointer), use MIDI message without visual effect
|
78
|
+
* (at least) doubled the speed of change_all by not sending each message individually but sending them in one go (as an array)
|
79
|
+
|
75
80
|
=== v0.1.1
|
76
81
|
|
77
82
|
* ability to close device/interaction to free portmidi resources
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'setup')
|
2
|
+
|
3
|
+
interaction = Launchpad::Interaction.new
|
4
|
+
|
5
|
+
# store and change button states, ugly but well...
|
6
|
+
@button_states = [
|
7
|
+
[false, false, false, false, false, false, false, false],
|
8
|
+
[false, false, false, false, false, false, false, false],
|
9
|
+
[[false, false], [false, false], [false, false], [false, false], [false, false], [false, false], [false, false], [false, false]],
|
10
|
+
[[false, false], [false, false], [false, false], [false, false], [false, false], [false, false], [false, false], [false, false]],
|
11
|
+
[[false, false], [false, false], [false, false], [false, false], [false, false], [false, false], [false, false], [false, false]],
|
12
|
+
[[false, false], [false, false], [false, false], [false, false], [false, false], [false, false], [false, false], [false, false]],
|
13
|
+
[[false, false], [false, false], [false, false], [false, false], [false, false], [false, false], [false, false], [false, false]],
|
14
|
+
[[false, false], [false, false], [false, false], [false, false], [false, false], [false, false], [false, false], [false, false]]
|
15
|
+
]
|
16
|
+
def change_button_state(action)
|
17
|
+
if action[:y] > 1
|
18
|
+
which = @active_buffer_button == :user2 ? 1 : 0
|
19
|
+
@button_states[action[:y]][action[:x]][which] = !@button_states[action[:y]][action[:x]][which]
|
20
|
+
else
|
21
|
+
@button_states[action[:y]][action[:x]] = !@button_states[action[:y]][action[:x]]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# setup grid buttons to:
|
26
|
+
# * set LEDs in normal mode on the first row
|
27
|
+
# * set LEDs in flashing mode on the second row
|
28
|
+
# * set LEDs in buffering mode on all other rows
|
29
|
+
interaction.response_to(:grid, :down) do |interaction, action|
|
30
|
+
color = change_button_state(action) ? @color : {}
|
31
|
+
case action[:y]
|
32
|
+
when 0
|
33
|
+
interaction.device.change(:grid, action.merge(color))
|
34
|
+
when 1
|
35
|
+
interaction.device.buffering_mode(:flashing => false, :display_buffer => 1, :update_buffer => 0)
|
36
|
+
interaction.device.change(:grid, action.merge(color).merge(:mode => :flashing))
|
37
|
+
interaction.respond_to(@active_buffer_button, :down)
|
38
|
+
else
|
39
|
+
interaction.device.change(:grid, action.merge(color).merge(:mode => :buffering))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# green feedback for buffer buttons
|
44
|
+
interaction.response_to([:session, :user1, :user2], :down) do |interaction, action|
|
45
|
+
case @active_buffer_button = action[:type]
|
46
|
+
when :session
|
47
|
+
interaction.device.buffering_mode(:flashing => true)
|
48
|
+
when :user1
|
49
|
+
interaction.device.buffering_mode(:display_buffer => 0, :update_buffer => 0)
|
50
|
+
when :user2
|
51
|
+
interaction.device.buffering_mode(:display_buffer => 1, :update_buffer => 1)
|
52
|
+
end
|
53
|
+
interaction.device.change(:session, :red => @active_buffer_button == :session ? :hi : :lo, :green => @active_buffer_button == :session ? :hi : :lo)
|
54
|
+
interaction.device.change(:user1, :red => @active_buffer_button == :user1 ? :hi : :lo, :green => @active_buffer_button == :user1 ? :hi : :lo)
|
55
|
+
interaction.device.change(:user2, :red => @active_buffer_button == :user2 ? :hi : :lo, :green => @active_buffer_button == :user2 ? :hi : :lo)
|
56
|
+
end
|
57
|
+
|
58
|
+
# setup color picker
|
59
|
+
def display_color(opts)
|
60
|
+
lambda do |interaction, action|
|
61
|
+
@red = opts[:red] if opts[:red]
|
62
|
+
@green = opts[:green] if opts[:green]
|
63
|
+
if @red == 0 && @green == 0
|
64
|
+
@red = 1 if opts[:red]
|
65
|
+
@green = 1 if opts[:green]
|
66
|
+
end
|
67
|
+
@color = {:red => @red, :green => @green}
|
68
|
+
on = {:red => 3, :green => 3}
|
69
|
+
interaction.device.change(:scene1, @red == 3 ? on : {:red => 3})
|
70
|
+
interaction.device.change(:scene2, @red == 2 ? on : {:red => 2})
|
71
|
+
interaction.device.change(:scene3, @red == 1 ? on : {:red => 1})
|
72
|
+
interaction.device.change(:scene4, @red == 0 ? on : {:red => 0})
|
73
|
+
interaction.device.change(:scene5, @green == 3 ? on : {:green => 3})
|
74
|
+
interaction.device.change(:scene6, @green == 2 ? on : {:green => 2})
|
75
|
+
interaction.device.change(:scene7, @green == 1 ? on : {:green => 1})
|
76
|
+
interaction.device.change(:scene8, @green == 0 ? on : {:green => 0})
|
77
|
+
end
|
78
|
+
end
|
79
|
+
# register color picker interactors on scene buttons
|
80
|
+
interaction.response_to(:scene1, :down, :exclusive => true, &display_color(:red => 3))
|
81
|
+
interaction.response_to(:scene2, :down, :exclusive => true, &display_color(:red => 2))
|
82
|
+
interaction.response_to(:scene3, :down, :exclusive => true, &display_color(:red => 1))
|
83
|
+
interaction.response_to(:scene4, :down, :exclusive => true, &display_color(:red => 0))
|
84
|
+
interaction.response_to(:scene5, :down, :exclusive => true, &display_color(:green => 3))
|
85
|
+
interaction.response_to(:scene6, :down, :exclusive => true, &display_color(:green => 2))
|
86
|
+
interaction.response_to(:scene7, :down, :exclusive => true, &display_color(:green => 1))
|
87
|
+
interaction.response_to(:scene8, :down, :exclusive => true, &display_color(:green => 0))
|
88
|
+
# pick green
|
89
|
+
interaction.respond_to(:scene5, :down)
|
90
|
+
|
91
|
+
# mixer button terminates interaction on button up
|
92
|
+
interaction.response_to(:mixer) do |interaction, action|
|
93
|
+
interaction.device.change(:mixer, :red => action[:state] == :down ? :hi : :off)
|
94
|
+
interaction.stop if action[:state] == :up
|
95
|
+
end
|
96
|
+
|
97
|
+
# start in auto flashing mode
|
98
|
+
interaction.respond_to(:session, :down)
|
99
|
+
|
100
|
+
# start interacting
|
101
|
+
interaction.start
|
102
|
+
|
103
|
+
# sleep so that the messages can be sent before the program terminates
|
104
|
+
sleep 0.1
|
data/launchpad.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{launchpad}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Thomas Jachmann"]
|
12
|
-
s.date = %q{2009-11-
|
12
|
+
s.date = %q{2009-11-24}
|
13
13
|
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.}
|
14
14
|
s.email = %q{tom.j@gmx.net}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -25,6 +25,7 @@ Gem::Specification.new do |s|
|
|
25
25
|
"examples/color_picker.rb",
|
26
26
|
"examples/colors.rb",
|
27
27
|
"examples/doodle.rb",
|
28
|
+
"examples/double_buffering.rb",
|
28
29
|
"examples/drawing_board.rb",
|
29
30
|
"examples/feedback.rb",
|
30
31
|
"examples/reset.rb",
|
@@ -53,6 +54,7 @@ Gem::Specification.new do |s|
|
|
53
54
|
"examples/color_picker.rb",
|
54
55
|
"examples/colors.rb",
|
55
56
|
"examples/doodle.rb",
|
57
|
+
"examples/double_buffering.rb",
|
56
58
|
"examples/drawing_board.rb",
|
57
59
|
"examples/feedback.rb",
|
58
60
|
"examples/reset.rb",
|
data/lib/launchpad/device.rb
CHANGED
@@ -119,7 +119,10 @@ module Launchpad
|
|
119
119
|
# [<tt>:y</tt>] y coordinate
|
120
120
|
# [<tt>:red</tt>] brightness of red LED
|
121
121
|
# [<tt>:green</tt>] brightness of green LED
|
122
|
-
# [<tt>:mode</tt>] button mode
|
122
|
+
# [<tt>:mode</tt>] button mode, defaults to <tt>:normal</tt>, one of:
|
123
|
+
# [<tt>:normal/tt>] updates the LED for all circumstances (the new value will be written to both buffers)
|
124
|
+
# [<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)
|
125
|
+
# [<tt>:buffering/tt>] updates the LED for the current update_buffer only
|
123
126
|
#
|
124
127
|
# Errors raised:
|
125
128
|
#
|
@@ -157,12 +160,15 @@ module Launchpad
|
|
157
160
|
# ensure that colors is at least and most 80 elements long
|
158
161
|
colors = colors.flatten[0..79]
|
159
162
|
colors += [0] * (80 - colors.size) if colors.size < 80
|
160
|
-
#
|
161
|
-
|
163
|
+
# send normal MIDI message to reset rapid LED change pointer
|
164
|
+
# in this case, set mapping mode to x-y layout (the default)
|
165
|
+
output(Status::CC, Status::NIL, GridLayout::XY)
|
162
166
|
# send colors in slices of 2
|
167
|
+
messages = []
|
163
168
|
colors.each_slice(2) do |c1, c2|
|
164
|
-
|
169
|
+
messages << message(Status::MULTI, velocity(c1), velocity(c2))
|
165
170
|
end
|
171
|
+
output_messages(messages)
|
166
172
|
end
|
167
173
|
|
168
174
|
# Switches LEDs marked as flashing on when using custom timer for flashing.
|
@@ -171,7 +177,7 @@ module Launchpad
|
|
171
177
|
#
|
172
178
|
# [Launchpad::NoOutputAllowedError] when output is not enabled
|
173
179
|
def flashing_on
|
174
|
-
|
180
|
+
buffering_mode(:display_buffer => 0)
|
175
181
|
end
|
176
182
|
|
177
183
|
# Switches LEDs marked as flashing off when using custom timer for flashing.
|
@@ -180,7 +186,7 @@ module Launchpad
|
|
180
186
|
#
|
181
187
|
# [Launchpad::NoOutputAllowedError] when output is not enabled
|
182
188
|
def flashing_off
|
183
|
-
|
189
|
+
buffering_mode(:display_buffer => 1)
|
184
190
|
end
|
185
191
|
|
186
192
|
# Starts flashing LEDs marked as flashing automatically.
|
@@ -190,21 +196,33 @@ module Launchpad
|
|
190
196
|
#
|
191
197
|
# [Launchpad::NoOutputAllowedError] when output is not enabled
|
192
198
|
def flashing_auto
|
193
|
-
|
199
|
+
buffering_mode(:flashing => true)
|
194
200
|
end
|
195
201
|
|
196
|
-
#
|
197
|
-
#
|
198
|
-
#
|
199
|
-
#
|
200
|
-
#
|
201
|
-
#
|
202
|
-
#
|
203
|
-
#
|
204
|
-
#
|
205
|
-
#
|
206
|
-
#
|
207
|
-
#
|
202
|
+
# Controls the two buffers.
|
203
|
+
#
|
204
|
+
# Optional options hash:
|
205
|
+
#
|
206
|
+
# [<tt>:display_buffer</tt>] which buffer to use for display, defaults to +0+
|
207
|
+
# [<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)
|
208
|
+
# [<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>
|
209
|
+
# [<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>
|
210
|
+
#
|
211
|
+
# Errors raised:
|
212
|
+
#
|
213
|
+
# [Launchpad::NoOutputAllowedError] when output is not enabled
|
214
|
+
def buffering_mode(opts = nil)
|
215
|
+
opts = {
|
216
|
+
:display_buffer => 0,
|
217
|
+
:update_buffer => 0,
|
218
|
+
:copy => false,
|
219
|
+
:flashing => false
|
220
|
+
}.merge(opts || {})
|
221
|
+
data = opts[:display_buffer] + 4 * opts[:update_buffer] + 32
|
222
|
+
data += 16 if opts[:copy]
|
223
|
+
data += 8 if opts[:flashing]
|
224
|
+
output(Status::CC, Status::NIL, data)
|
225
|
+
end
|
208
226
|
|
209
227
|
# Reads user actions (button presses/releases) that haven't been handled yet.
|
210
228
|
#
|
@@ -330,8 +348,22 @@ module Launchpad
|
|
330
348
|
#
|
331
349
|
# [Launchpad::NoOutputAllowedError] when output is not enabled
|
332
350
|
def output(status, data1, data2)
|
351
|
+
output_messages([message(status, data1, data2)])
|
352
|
+
end
|
353
|
+
|
354
|
+
# Writes several messages to the MIDI device.
|
355
|
+
#
|
356
|
+
# Parameters:
|
357
|
+
#
|
358
|
+
# [+messages+] an array of hashes (usually created with message) with:
|
359
|
+
# [<tt>:message</tt>] an array of
|
360
|
+
# MIDI status code,
|
361
|
+
# MIDI data 1 (note),
|
362
|
+
# MIDI data 2 (velocity)
|
363
|
+
# [<tt>:timestamp</tt>] integer indicating the time when the MIDI message was created
|
364
|
+
def output_messages(messages)
|
333
365
|
raise NoOutputAllowedError if @output.nil?
|
334
|
-
@output.write(
|
366
|
+
@output.write(messages)
|
335
367
|
nil
|
336
368
|
end
|
337
369
|
|
@@ -385,7 +417,10 @@ module Launchpad
|
|
385
417
|
#
|
386
418
|
# [<tt>:red</tt>] brightness of red LED
|
387
419
|
# [<tt>:green</tt>] brightness of green LED
|
388
|
-
# [<tt>:mode</tt>] button mode
|
420
|
+
# [<tt>:mode</tt>] button mode, defaults to <tt>:normal</tt>, one of:
|
421
|
+
# [<tt>:normal/tt>] updates the LED for all circumstances (the new value will be written to both buffers)
|
422
|
+
# [<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 )
|
423
|
+
# [<tt>:buffering/tt>] updates the LED for the current update_buffer only
|
389
424
|
#
|
390
425
|
# Returns:
|
391
426
|
#
|
@@ -430,6 +465,27 @@ module Launchpad
|
|
430
465
|
end
|
431
466
|
end
|
432
467
|
|
468
|
+
# Creates a MIDI message.
|
469
|
+
#
|
470
|
+
# Parameters:
|
471
|
+
#
|
472
|
+
# [+status+] MIDI status code
|
473
|
+
# [+data1+] MIDI data 1 (note)
|
474
|
+
# [+data2+] MIDI data 2 (velocity)
|
475
|
+
#
|
476
|
+
# Returns:
|
477
|
+
#
|
478
|
+
# an array with:
|
479
|
+
#
|
480
|
+
# [<tt>:message</tt>] an array of
|
481
|
+
# MIDI status code,
|
482
|
+
# MIDI data 1 (note),
|
483
|
+
# MIDI data 2 (velocity)
|
484
|
+
# [<tt>:timestamp</tt>] integer indicating the time when the MIDI message was created, in this case 0
|
485
|
+
def message(status, data1, data2)
|
486
|
+
{:message => [status, data1, data2], :timestamp => 0}
|
487
|
+
end
|
488
|
+
|
433
489
|
end
|
434
490
|
|
435
491
|
end
|
data/lib/launchpad/midi_codes.rb
CHANGED
@@ -38,12 +38,15 @@ module Launchpad
|
|
38
38
|
|
39
39
|
# Module defining MIDI data 2 (velocity) codes.
|
40
40
|
module Velocity
|
41
|
-
FLASHING_ON = 0x20
|
42
|
-
FLASHING_OFF = 0x21
|
43
|
-
FLASHING_AUTO = 0x28
|
44
41
|
TEST_LEDS = 0x7C
|
45
42
|
end
|
46
43
|
|
44
|
+
# Module defining MIDI data 2 codes for selecting the grid layout.
|
45
|
+
module GridLayout
|
46
|
+
XY = 0x01
|
47
|
+
DRUM_RACK = 0x02
|
48
|
+
end
|
49
|
+
|
47
50
|
end
|
48
51
|
|
49
52
|
end
|
data/lib/launchpad/version.rb
CHANGED
data/test/test_device.rb
CHANGED
@@ -34,7 +34,9 @@ class TestDevice < Test::Unit::TestCase
|
|
34
34
|
}
|
35
35
|
|
36
36
|
def expects_output(device, *args)
|
37
|
-
|
37
|
+
args = [args] unless args.first.is_a?(Array)
|
38
|
+
messages = args.collect {|data| {:message => data, :timestamp => 0}}
|
39
|
+
device.instance_variable_get('@output').expects(:write).with(messages)
|
38
40
|
end
|
39
41
|
|
40
42
|
def stub_input(device, *args)
|
@@ -414,16 +416,15 @@ class TestDevice < Test::Unit::TestCase
|
|
414
416
|
assert_nil @device.change_all([0])
|
415
417
|
end
|
416
418
|
|
417
|
-
should 'fill colors with 0, set grid
|
418
|
-
expects_output(@device,
|
419
|
-
|
420
|
-
20.times {|i| expects_output(@device, 0x92, 12, 12)}
|
419
|
+
should 'fill colors with 0, set grid layout to XY and flush colors' do
|
420
|
+
expects_output(@device, 0xB0, 0, 0x01)
|
421
|
+
expects_output(@device, *([[0x92, 17, 17]] * 20 + [[0x92, 12, 12]] * 20))
|
421
422
|
@device.change_all([5] * 40)
|
422
423
|
end
|
423
424
|
|
424
|
-
should 'cut off exceeding colors, set grid
|
425
|
-
expects_output(@device,
|
426
|
-
|
425
|
+
should 'cut off exceeding colors, set grid layout to XY and flush colors' do
|
426
|
+
expects_output(@device, 0xB0, 0, 0x01)
|
427
|
+
expects_output(@device, *([[0x92, 17, 17]] * 40))
|
427
428
|
@device.change_all([5] * 100)
|
428
429
|
end
|
429
430
|
|
@@ -431,6 +432,37 @@ class TestDevice < Test::Unit::TestCase
|
|
431
432
|
|
432
433
|
end
|
433
434
|
|
435
|
+
context 'buffering_mode' do
|
436
|
+
|
437
|
+
should 'raise NoOutputAllowedError when not initialized with output' do
|
438
|
+
assert_raise Launchpad::NoOutputAllowedError do
|
439
|
+
Launchpad::Device.new(:output => false).buffering_mode
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
{
|
444
|
+
nil => [0xB0, 0x00, 0x20],
|
445
|
+
{} => [0xB0, 0x00, 0x20],
|
446
|
+
{:display_buffer => 1} => [0xB0, 0x00, 0x21],
|
447
|
+
{:update_buffer => 1} => [0xB0, 0x00, 0x24],
|
448
|
+
{:copy => true} => [0xB0, 0x00, 0x30],
|
449
|
+
{:flashing => true} => [0xB0, 0x00, 0x28],
|
450
|
+
{
|
451
|
+
:display_buffer => 1,
|
452
|
+
:update_buffer => 1,
|
453
|
+
:copy => true,
|
454
|
+
:flashing => true
|
455
|
+
} => [0xB0, 0x00, 0x3D]
|
456
|
+
}.each do |opts, codes|
|
457
|
+
should "send #{codes.inspect} when called with #{opts.inspect}" do
|
458
|
+
d = Launchpad::Device.new
|
459
|
+
expects_output(d, *codes)
|
460
|
+
d.buffering_mode(opts)
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
end
|
465
|
+
|
434
466
|
context 'read_pending_actions' do
|
435
467
|
|
436
468
|
should 'raise NoInputAllowedError when not initialized with input' do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: launchpad
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thomas Jachmann
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-11-
|
12
|
+
date: 2009-11-24 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -60,6 +60,7 @@ files:
|
|
60
60
|
- examples/color_picker.rb
|
61
61
|
- examples/colors.rb
|
62
62
|
- examples/doodle.rb
|
63
|
+
- examples/double_buffering.rb
|
63
64
|
- examples/drawing_board.rb
|
64
65
|
- examples/feedback.rb
|
65
66
|
- examples/reset.rb
|
@@ -110,6 +111,7 @@ test_files:
|
|
110
111
|
- examples/color_picker.rb
|
111
112
|
- examples/colors.rb
|
112
113
|
- examples/doodle.rb
|
114
|
+
- examples/double_buffering.rb
|
113
115
|
- examples/drawing_board.rb
|
114
116
|
- examples/feedback.rb
|
115
117
|
- examples/reset.rb
|