Skip to content

Commit

Permalink
better code organization, interaction, multi update
Browse files Browse the repository at this point in the history
* separation of tasks into different classes
* interaction handling with interactors
* proper handling of multi value update (updates the whole launchpad at once, should use double buffering soon)
  • Loading branch information
Thomas Jachmann committed Nov 9, 2009
1 parent 4f82564 commit 276fdf3
Show file tree
Hide file tree
Showing 10 changed files with 447 additions and 177 deletions.
17 changes: 9 additions & 8 deletions examples/colors.rb
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
require File.join(File.dirname(__FILE__), 'setup')

l = Launchpad.new(:input => false, :output => true)

sleep 1
device = Launchpad::Device.new(:input => false, :output => true)

pos_x = pos_y = 0
4.times do |red|
4.times do |green|
l.single :x => pos_x, :y => pos_y, :red => red, :green => green, :mode => :buffering
l.single :x => 7 - pos_x, :y => pos_y, :red => red, :green => green, :mode => :buffering
l.single :x => pos_x, :y => 7 - pos_y, :red => red, :green => green, :mode => :buffering
l.single :x => 7 - pos_x, :y => 7 - pos_y, :red => red, :green => green, :mode => :buffering
device.single :x => pos_x, :y => pos_y, :red => red, :green => green
device.single :x => 7 - pos_x, :y => pos_y, :red => red, :green => green
device.single :x => pos_x, :y => 7 - pos_y, :red => red, :green => green
device.single :x => 7 - pos_x, :y => 7 - pos_y, :red => red, :green => green
pos_y += 1
# sleep, otherwise the connection drops some messages - WTF?
sleep 0.01
end
pos_x += 1
pos_y = 0
end

sleep 1
# sleep so that the messages can be sent before the program terminates
sleep 0.1
29 changes: 27 additions & 2 deletions examples/feedback.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,30 @@
require File.join(File.dirname(__FILE__), 'setup')

Launchpad.start do |l, x, y, state|
l.single(:x => x, :y => y, :red => state ? 3 : 0)
interaction = Launchpad::Interaction.new

# red feedback for grid buttons
interaction.register_interactor(:grid) do |device, action|
device.single(:x => action[:x], :y => action[:y], :red => action[:state] ? :hi : :off)
end

# green feedback for top control buttons
interaction.register_interactor([:up, :down, :left, :right, :session, :user1, :user2, :mixer]) do |device, action|
device.single(:type => action[:type], :green => action[:state] ? :hi : :off)
end

# yellow feedback for scene buttons
interaction.register_interactor([:scene1, :scene2, :scene3, :scene4, :scene5, :scene6, :scene7, :scene8]) do |device, action|
brightness = action[:state] ? :hi : :off
device.single(:type => action[:type], :red => brightness, :green => brightness)
end

# mixer button terminates interaction on button up
interaction.register_interactor(:mixer, :up) do |device, action|
interaction.stop
end

# start interacting
interaction.start

# sleep so that the messages can be sent before the program terminates
sleep 0.1
6 changes: 6 additions & 0 deletions examples/reset.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
require File.join(File.dirname(__FILE__), 'setup')

Launchpad::Device.new.reset

# sleep so that the messages can be sent before the program terminates
sleep 0.1
4 changes: 3 additions & 1 deletion examples/setup.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# normally, this is done by rubygems (or whatever you use for your library management)
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))

require 'rubygems'
require File.join(File.dirname(__FILE__), '..', 'lib', 'launchpad')
require 'launchpad'
171 changes: 6 additions & 165 deletions lib/launchpad.rb
Original file line number Diff line number Diff line change
@@ -1,167 +1,8 @@
require 'portmidi'

class Launchpad

class LaunchpadError < StandardError; end
class NoInputAllowed < LaunchpadError; end
class NoOutputAllowed < LaunchpadError; end
class NoLocationError < LaunchpadError; end
class CommunicationError < LaunchpadError
attr_accessor :source
def initialize(e)
super(e.portmidi_error)
self.source = e
end
end

OFF = 0x80
ON = 0x90
CC = 0xB0

def initialize(opts = nil)
opts = {
:device_name => 'Launchpad',
:input => true,
:output => true
}.merge(opts || {})
Portmidi.start
if opts[:input]
input_device = Portmidi.input_devices.select {|device| device.name == opts[:device_name]}.first
@input = Portmidi::Input.new(input_device.device_id)
end
if opts[:output]
output_device = Portmidi.output_devices.select {|device| device.name == opts[:device_name]}.first
@output = Portmidi::Output.new(output_device.device_id)
reset
end
@buffering = false
end

def self.start(opts = nil, &block)
opts ||= {}
latency = (opts.delete(:latency) || 0.001).to_f
launchpad = Launchpad.new(opts.merge({:input => true, :output => true}))
loop do
messages = launchpad.input
if messages
messages.each do |message|
message = parse_message(message)
if message[:code] == ON
block.call(launchpad, message[:x], message[:y], message[:state])
end
end
end
sleep latency
end
rescue Portmidi::DeviceError => e
raise CommunicationError.new(e)
ensure
launchpad.reset if launchpad
end

# Reset the launchpad - all settings are reset and all LEDs are switched off
def reset
output(CC, 0x00, 0x00)
end

# Light all LEDs (for testing purposes)
# takes an optional parameter brightness (1-3, defaults to 3)
def light_all(brightness = 3)
output(CC, 0x00, 124 + min_max_color(brightness, false))
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

# Switches a single LED
# * :x => x coordinate (0 based from top left, mandatory)
# * :y => y coordinate (0 based from top left, mandatory)
# * :red => brightness of red LED (0-3, optional, defaults to 0)
# * :green => brightness of red LED (0-3, optional, defaults to 0)
# * :mode => button behaviour (:normal, :flashing, :buffering, optional, defaults to :normal)
def single(opts)
location = location(opts)
velocity = velocity(opts)
output(ON, location, velocity)
end

def multi(*velocities)
output(CC, 0x01, 0x00)
output(0x92, *velocities)
end

# Switches LEDs marked as flashing on (when using custom timer for flashing)
def custom_flashing_on
output(CC, 0x00, 0x20)
end

# Switches LEDs marked as flashing off (when using custom timer for flashing)
def custom_flashing_off
output(CC, 0x00, 0x21)
end

# Starts flashing LEDs marked as flashing automatically
def start_auto_flashing
output(CC, 0x00, 0x28)
end

# Stops flashing LEDs marked as flashing automatically (turning them on)
alias_method :stop_auto_flashing, :custom_flashing_on

def coordinates(location)
[location % 16, location / 16]
end

def input
raise NoInputAllowed if @input.nil?
@input.read(16)
end

def output(*args)
raise NoOutputAllowed if @output.nil?
@output.write([{:message => args, :timestamp => 0}])
end

private

def self.parse_message(message)
message = message[:message]
{
:code => message[0],
:x => message[1] % 16,
:y => message[1] / 16,
:state => message[2] == 127
}
end

def location(opts)
raise NoLocationError.new('you need to specify a location (x/y, 0 based from top left)') if (y = opts[:y]).nil? || (x = opts[:x]).nil?
y * 16 + x
end

def velocity(opts)
red = min_max_color(opts[:red] || 0)
green = min_max_color(opts[:green] || 0)
flags = case opts[:mode]
when :flashing then 8
when :buffering then 0
else 12
end
(16 * (green)) + red + flags
end

def min_max_color(color, with_off = true)
[[with_off ? 0 : 1, color.to_i].max, 3].min
end

end
require 'launchpad/errors'
require 'launchpad/midi_codes'
require 'launchpad/version'

require 'launchpad/device'
require 'launchpad/interaction'
Loading

0 comments on commit 276fdf3

Please sign in to comment.