launchpad 0.1.1 → 0.2.0

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