launchpad 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|