Permalink
Browse files

double buffering

  • Loading branch information...
1 parent 7b7defe commit 7dcb87785ffd594df6a8c9e83b8e91071d57672f Thomas Jachmann committed Nov 23, 2009
Showing with 171 additions and 21 deletions.
  1. +1 −1 README.rdoc
  2. +104 −0 examples/double_buffering.rb
  3. +35 −17 lib/launchpad/device.rb
  4. +0 −3 lib/launchpad/midi_codes.rb
  5. +31 −0 test/test_device.rb
View
2 README.rdoc
@@ -66,12 +66,12 @@ For more details, see the examples. examples/color_picker.rb is the most complex
== Near future plans
* interaction responses for presses on single grid buttons/button areas
-* double buffering
* bitmap rendering
== Changelog
+* double buffering (see Launchpad::Device#buffering_mode)
* don't update grid button 0,0 before change_all (in order to reset rapid update pointer), use MIDI message without visual effect
* (at least) doubled the speed of change_all by not sending each message individually but sending them in one go (as an array)
View
104 examples/double_buffering.rb
@@ -0,0 +1,104 @@
+require File.join(File.dirname(__FILE__), 'setup')
+
+interaction = Launchpad::Interaction.new
+
+# store and change button states, ugly but well...
+@button_states = [
+ [false, false, false, false, false, false, false, false],
+ [false, false, false, false, false, false, false, false],
+ [[false, false], [false, false], [false, false], [false, false], [false, false], [false, false], [false, false], [false, false]],
+ [[false, false], [false, false], [false, false], [false, false], [false, false], [false, false], [false, false], [false, false]],
+ [[false, false], [false, false], [false, false], [false, false], [false, false], [false, false], [false, false], [false, false]],
+ [[false, false], [false, false], [false, false], [false, false], [false, false], [false, false], [false, false], [false, false]],
+ [[false, false], [false, false], [false, false], [false, false], [false, false], [false, false], [false, false], [false, false]],
+ [[false, false], [false, false], [false, false], [false, false], [false, false], [false, false], [false, false], [false, false]]
+]
+def change_button_state(action)
+ if action[:y] > 1
+ which = @active_buffer_button == :user2 ? 1 : 0
+ @button_states[action[:y]][action[:x]][which] = !@button_states[action[:y]][action[:x]][which]
+ else
+ @button_states[action[:y]][action[:x]] = !@button_states[action[:y]][action[:x]]
+ end
+end
+
+# setup grid buttons to:
+# * set LEDs in normal mode on the first row
+# * set LEDs in flashing mode on the second row
+# * set LEDs in buffering mode on all other rows
+interaction.response_to(:grid, :down) do |interaction, action|
+ color = change_button_state(action) ? @color : {}
+ case action[:y]
+ when 0
+ interaction.device.change(:grid, action.merge(color))
+ when 1
+ interaction.device.buffering_mode(:flashing => false, :display_buffer => 1, :update_buffer => 0)
+ interaction.device.change(:grid, action.merge(color).merge(:mode => :flashing))
+ interaction.respond_to(@active_buffer_button, :down)
+ else
+ interaction.device.change(:grid, action.merge(color).merge(:mode => :buffering))
+ end
+end
+
+# green feedback for buffer buttons
+interaction.response_to([:session, :user1, :user2], :down) do |interaction, action|
+ case @active_buffer_button = action[:type]
+ when :session
+ interaction.device.buffering_mode(:flashing => true)
+ when :user1
+ interaction.device.buffering_mode(:display_buffer => 0, :update_buffer => 0)
+ when :user2
+ interaction.device.buffering_mode(:display_buffer => 1, :update_buffer => 1)
+ end
+ interaction.device.change(:session, :red => @active_buffer_button == :session ? :hi : :lo, :green => @active_buffer_button == :session ? :hi : :lo)
+ interaction.device.change(:user1, :red => @active_buffer_button == :user1 ? :hi : :lo, :green => @active_buffer_button == :user1 ? :hi : :lo)
+ interaction.device.change(:user2, :red => @active_buffer_button == :user2 ? :hi : :lo, :green => @active_buffer_button == :user2 ? :hi : :lo)
+end
+
+# setup color picker
+def display_color(opts)
+ lambda do |interaction, action|
+ @red = opts[:red] if opts[:red]
+ @green = opts[:green] if opts[:green]
+ if @red == 0 && @green == 0
+ @red = 1 if opts[:red]
+ @green = 1 if opts[:green]
+ end
+ @color = {:red => @red, :green => @green}
+ on = {:red => 3, :green => 3}
+ interaction.device.change(:scene1, @red == 3 ? on : {:red => 3})
+ interaction.device.change(:scene2, @red == 2 ? on : {:red => 2})
+ interaction.device.change(:scene3, @red == 1 ? on : {:red => 1})
+ interaction.device.change(:scene4, @red == 0 ? on : {:red => 0})
+ interaction.device.change(:scene5, @green == 3 ? on : {:green => 3})
+ interaction.device.change(:scene6, @green == 2 ? on : {:green => 2})
+ interaction.device.change(:scene7, @green == 1 ? on : {:green => 1})
+ interaction.device.change(:scene8, @green == 0 ? on : {:green => 0})
+ end
+end
+# register color picker interactors on scene buttons
+interaction.response_to(:scene1, :down, :exclusive => true, &display_color(:red => 3))
+interaction.response_to(:scene2, :down, :exclusive => true, &display_color(:red => 2))
+interaction.response_to(:scene3, :down, :exclusive => true, &display_color(:red => 1))
+interaction.response_to(:scene4, :down, :exclusive => true, &display_color(:red => 0))
+interaction.response_to(:scene5, :down, :exclusive => true, &display_color(:green => 3))
+interaction.response_to(:scene6, :down, :exclusive => true, &display_color(:green => 2))
+interaction.response_to(:scene7, :down, :exclusive => true, &display_color(:green => 1))
+interaction.response_to(:scene8, :down, :exclusive => true, &display_color(:green => 0))
+# pick green
+interaction.respond_to(:scene5, :down)
+
+# mixer button terminates interaction on button up
+interaction.response_to(:mixer) do |interaction, action|
+ interaction.device.change(:mixer, :red => action[:state] == :down ? :hi : :off)
+ interaction.stop if action[:state] == :up
+end
+
+# start in auto flashing mode
+interaction.respond_to(:session, :down)
+
+# start interacting
+interaction.start
+
+# sleep so that the messages can be sent before the program terminates
+sleep 0.1
View
52 lib/launchpad/device.rb
@@ -119,7 +119,10 @@ def test_leds(brightness = :high)
# [<tt>:y</tt>] y coordinate
# [<tt>:red</tt>] brightness of red LED
# [<tt>:green</tt>] brightness of green LED
- # [<tt>:mode</tt>] button mode
+ # [<tt>:mode</tt>] button mode, defaults to <tt>:normal</tt>, one of:
+ # [<tt>:normal/tt>] updates the LED for all circumstances (the new value will be written to both buffers)
+ # [<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)
+ # [<tt>:buffering/tt>] updates the LED for the current update_buffer only
#
# Errors raised:
#
@@ -174,7 +177,7 @@ def change_all(*colors)
#
# [Launchpad::NoOutputAllowedError] when output is not enabled
def flashing_on
- output(Status::CC, Status::NIL, Velocity::FLASHING_ON)
+ buffering_mode(:display_buffer => 0)
end
# Switches LEDs marked as flashing off when using custom timer for flashing.
@@ -183,7 +186,7 @@ def flashing_on
#
# [Launchpad::NoOutputAllowedError] when output is not enabled
def flashing_off
- output(Status::CC, Status::NIL, Velocity::FLASHING_OFF)
+ buffering_mode(:display_buffer => 1)
end
# Starts flashing LEDs marked as flashing automatically.
@@ -193,21 +196,33 @@ def flashing_off
#
# [Launchpad::NoOutputAllowedError] when output is not enabled
def flashing_auto
- output(Status::CC, Status::NIL, Velocity::FLASHING_AUTO)
+ buffering_mode(:flashing => true)
end
- # def start_buffering
- # output(CC, 0x00, 0x31)
- # @buffering = true
- # end
- #
- # def flush_buffer(end_buffering = true)
- # output(CC, 0x00, 0x34)
- # if end_buffering
- # output(CC, 0x00, 0x30)
- # @buffering = false
- # end
- # end
+ # Controls the two buffers.
+ #
+ # Optional options hash:
+ #
+ # [<tt>:display_buffer</tt>] which buffer to use for display, defaults to +0+
+ # [<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)
+ # [<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>
+ # [<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>
+ #
+ # Errors raised:
+ #
+ # [Launchpad::NoOutputAllowedError] when output is not enabled
+ def buffering_mode(opts = nil)
+ opts = {
+ :display_buffer => 0,
+ :update_buffer => 0,
+ :copy => false,
+ :flashing => false
+ }.merge(opts || {})
+ data = opts[:display_buffer] + 4 * opts[:update_buffer] + 32
+ data += 16 if opts[:copy]
+ data += 8 if opts[:flashing]
+ output(Status::CC, Status::NIL, data)
+ end
# Reads user actions (button presses/releases) that haven't been handled yet.
#
@@ -402,7 +417,10 @@ def note(type, opts)
#
# [<tt>:red</tt>] brightness of red LED
# [<tt>:green</tt>] brightness of green LED
- # [<tt>:mode</tt>] button mode
+ # [<tt>:mode</tt>] button mode, defaults to <tt>:normal</tt>, one of:
+ # [<tt>:normal/tt>] updates the LED for all circumstances (the new value will be written to both buffers)
+ # [<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 )
+ # [<tt>:buffering/tt>] updates the LED for the current update_buffer only
#
# Returns:
#
View
3 lib/launchpad/midi_codes.rb
@@ -38,9 +38,6 @@ module SceneButton
# Module defining MIDI data 2 (velocity) codes.
module Velocity
- FLASHING_ON = 0x20
- FLASHING_OFF = 0x21
- FLASHING_AUTO = 0x28
TEST_LEDS = 0x7C
end
View
31 test/test_device.rb
@@ -432,6 +432,37 @@ def stub_input(device, *args)
end
+ context 'buffering_mode' do
+
+ should 'raise NoOutputAllowedError when not initialized with output' do
+ assert_raise Launchpad::NoOutputAllowedError do
+ Launchpad::Device.new(:output => false).buffering_mode
+ end
+ end
+
+ {
+ nil => [0xB0, 0x00, 0x20],
+ {} => [0xB0, 0x00, 0x20],
+ {:display_buffer => 1} => [0xB0, 0x00, 0x21],
+ {:update_buffer => 1} => [0xB0, 0x00, 0x24],
+ {:copy => true} => [0xB0, 0x00, 0x30],
+ {:flashing => true} => [0xB0, 0x00, 0x28],
+ {
+ :display_buffer => 1,
+ :update_buffer => 1,
+ :copy => true,
+ :flashing => true
+ } => [0xB0, 0x00, 0x3D]
+ }.each do |opts, codes|
+ should "send #{codes.inspect} when called with #{opts.inspect}" do
+ d = Launchpad::Device.new
+ expects_output(d, *codes)
+ d.buffering_mode(opts)
+ end
+ end
+
+ end
+
context 'read_pending_actions' do
should 'raise NoInputAllowedError when not initialized with input' do

0 comments on commit 7dcb877

Please sign in to comment.