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.
@@ -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
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{launchpad}
8
- s.version = "0.1.1"
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-18}
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",
@@ -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
- # HACK switch off first grid LED to reset rapid LED change pointer
161
- output(Status::ON, 0, 0)
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
- output(Status::MULTI, velocity(c1), velocity(c2))
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
- output(Status::CC, Status::NIL, Velocity::FLASHING_ON)
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
- output(Status::CC, Status::NIL, Velocity::FLASHING_OFF)
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
- output(Status::CC, Status::NIL, Velocity::FLASHING_AUTO)
199
+ buffering_mode(:flashing => true)
194
200
  end
195
201
 
196
- # def start_buffering
197
- # output(CC, 0x00, 0x31)
198
- # @buffering = true
199
- # end
200
- #
201
- # def flush_buffer(end_buffering = true)
202
- # output(CC, 0x00, 0x34)
203
- # if end_buffering
204
- # output(CC, 0x00, 0x30)
205
- # @buffering = false
206
- # end
207
- # end
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([{:message => [status, data1, data2], :timestamp => 0}])
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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Launchpad
2
- VERSION = '0.1.1'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -34,7 +34,9 @@ class TestDevice < Test::Unit::TestCase
34
34
  }
35
35
 
36
36
  def expects_output(device, *args)
37
- device.instance_variable_get('@output').expects(:write).with([{:message => args, :timestamp => 0}])
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 0,0 to 0 and flush colors' do
418
- expects_output(@device, 0x90, 0, 0)
419
- 20.times {|i| expects_output(@device, 0x92, 17, 17)}
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 0,0 to 0 and flush colors' do
425
- expects_output(@device, 0x90, 0, 0)
426
- 40.times {|i| expects_output(@device, 0x92, 17, 17)}
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.1.1
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-18 00:00:00 +01:00
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