-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
better code organization, interaction, multi update
* 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
Showing
10 changed files
with
447 additions
and
177 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' |
Oops, something went wrong.