launchpad 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +55 -8
- data/Rakefile +2 -1
- data/examples/color_picker.rb +99 -0
- data/examples/colors.rb +9 -8
- data/examples/doodle.rb +68 -0
- data/examples/drawing_board.rb +25 -0
- data/examples/feedback.rb +31 -2
- data/examples/reset.rb +6 -0
- data/examples/setup.rb +3 -1
- data/launchpad.gemspec +24 -4
- data/lib/launchpad.rb +2 -167
- data/lib/launchpad/device.rb +244 -0
- data/lib/launchpad/errors.rb +37 -0
- data/lib/launchpad/interaction.rb +89 -0
- data/lib/launchpad/midi_codes.rb +44 -0
- data/lib/launchpad/version.rb +2 -2
- data/test/helper.rb +37 -1
- data/test/test_device.rb +429 -0
- data/test/test_interaction.rb +183 -0
- metadata +38 -4
- data/test/test_launchpad-gem.rb +0 -7
data/README.rdoc
CHANGED
@@ -1,23 +1,70 @@
|
|
1
1
|
= launchpad
|
2
2
|
|
3
|
-
This gem provides an interface to access novation's launchpad programmatically. LEDs can be lighted and button presses can be
|
3
|
+
This gem provides an interface to access novation's launchpad programmatically. LEDs can be lighted and button presses can be responded to. Internally, launchpad's MIDI input/output is used to accomplish this.
|
4
4
|
|
5
|
-
|
5
|
+
The interfaces should be rather stable now, so experiment with them and comment on their usability. This still is work in progress. If you need anything or think the interfaces could be improved in any way, please contact me.
|
6
6
|
|
7
|
-
Sometimes, the launchpad won't react to anything. Don't despair, just dis- and reconnect the thing. It seems that some (unexpected) MIDI signals make it hickup.
|
7
|
+
Sometimes, the launchpad won't react to anything or react to/light up the wrong LEDs. Don't despair, just dis- and reconnect the thing. It seems that some (unexpected) MIDI signals make it hickup.
|
8
8
|
|
9
9
|
|
10
10
|
== Requirements
|
11
11
|
|
12
|
-
*
|
13
|
-
*
|
12
|
+
* Roger B. Dannenberg's {portmidi library}[http://sourceforge.net/projects/portmedia/]
|
13
|
+
* Jan Krutisch's {portmidi gem}[http://github.com/halfbyte/portmidi]
|
14
|
+
|
15
|
+
|
16
|
+
== Installation
|
17
|
+
|
18
|
+
The gem is hosted on Gemcutter[http://gemcutter.org/], so in order to use it, you're gonna install the gemcutter gem (for details, see their site):
|
19
|
+
|
20
|
+
gem install gemcutter
|
21
|
+
gem tumble
|
22
|
+
|
23
|
+
After that, it's a simple gem install:
|
24
|
+
|
25
|
+
gem install launchpad
|
26
|
+
|
27
|
+
|
28
|
+
== Usage
|
29
|
+
|
30
|
+
There are two main entry points:
|
31
|
+
|
32
|
+
* require 'launchpad/device', providing Launchpad::Device, which handles all the basic input/output stuff
|
33
|
+
* require 'launchpad/interaction' or just 'launchpad', additionally providing Launchpad::Interaction, which lets you respond to actions (button presses/releases)
|
34
|
+
|
35
|
+
This is a simple example (only requiring the device for output) that switches on all LEDs (for testing), resets the launchpad again and then lights the grid button at position 4/4 (from top left).
|
36
|
+
|
37
|
+
require 'rubygems'
|
38
|
+
require 'launchpad/device'
|
39
|
+
|
40
|
+
device = Launchpad::Device.new
|
41
|
+
device.test_leds
|
42
|
+
sleep 1
|
43
|
+
device.reset
|
44
|
+
sleep 1
|
45
|
+
device.change :grid, :x => 4, :y => 4, :red => :high, :green => :low
|
46
|
+
|
47
|
+
This is an interaction example lighting all grid buttons in red when pressed and keeping them lit.
|
48
|
+
|
49
|
+
require 'rubygems'
|
50
|
+
require 'launchpad'
|
51
|
+
|
52
|
+
interaction = Launchpad::Interaction.new
|
53
|
+
interaction.response_to(:grid, :down) do |interaction, action|
|
54
|
+
interaction.device.change(:grid, action.merge(:red => :high))
|
55
|
+
end
|
56
|
+
|
57
|
+
interaction.start
|
58
|
+
|
59
|
+
|
60
|
+
For more details, see the examples. examples/color_picker.rb is the most complex example with interaction.
|
14
61
|
|
15
62
|
|
16
63
|
== Near future plans
|
17
64
|
|
18
|
-
*
|
19
|
-
*
|
20
|
-
* double buffering
|
65
|
+
* close devices properly as soon as halfbyte's pulled my changes to portmidi
|
66
|
+
* interaction responses for presses on single grid buttons/button areas
|
67
|
+
* double buffering
|
21
68
|
* bitmap rendering
|
22
69
|
|
23
70
|
|
data/Rakefile
CHANGED
@@ -14,7 +14,8 @@ begin
|
|
14
14
|
gem.version = Launchpad::VERSION
|
15
15
|
gem.authors = ['Thomas Jachmann']
|
16
16
|
gem.add_dependency('portmidi')
|
17
|
-
|
17
|
+
gem.add_development_dependency('thoughtbot-shoulda', '>= 0')
|
18
|
+
gem.add_development_dependency('mocha')
|
18
19
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
19
20
|
end
|
20
21
|
Jeweler::GemcutterTasks.new
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'setup')
|
2
|
+
|
3
|
+
# need to declare as instance variables here for being able to access
|
4
|
+
# @interaction within interactors created by create_interactor_block
|
5
|
+
device = Launchpad::Device.new
|
6
|
+
interaction = Launchpad::Interaction.new(:device => device)
|
7
|
+
|
8
|
+
# build color arrays for color display views
|
9
|
+
colors_single = [
|
10
|
+
[ 0, 1, 2, 3, 0, 0, 0, 0],
|
11
|
+
[16, 17, 18, 19, 0, 0, 0, 0],
|
12
|
+
[32, 33, 34, 35, 0, 0, 0, 0],
|
13
|
+
[48, 49, 50, 51, 0, 0, 0, 0],
|
14
|
+
[0] * 8,
|
15
|
+
[0] * 8,
|
16
|
+
[0] * 8,
|
17
|
+
[0] * 8,
|
18
|
+
[0] * 8
|
19
|
+
]
|
20
|
+
colors_double = [
|
21
|
+
[ 0, 0, 1, 1, 2, 2, 3, 3],
|
22
|
+
[ 0, 0, 1, 1, 2, 2, 3, 3],
|
23
|
+
[16, 16, 17, 17, 18, 18, 19, 19],
|
24
|
+
[16, 16, 17, 17, 18, 18, 19, 19],
|
25
|
+
[32, 32, 33, 33, 34, 34, 35, 35],
|
26
|
+
[32, 32, 33, 33, 34, 34, 35, 35],
|
27
|
+
[48, 48, 49, 49, 50, 50, 51, 51],
|
28
|
+
[48, 48, 49, 49, 50, 50, 51, 51],
|
29
|
+
[0] * 8
|
30
|
+
]
|
31
|
+
colors_mirrored = [
|
32
|
+
[ 0, 1, 2, 3, 3, 2, 1, 0],
|
33
|
+
[16, 17, 18, 19, 19, 18, 17, 16],
|
34
|
+
[32, 33, 34, 35, 35, 34, 33, 32],
|
35
|
+
[48, 49, 50, 51, 51, 50, 49, 48],
|
36
|
+
[48, 49, 50, 51, 51, 50, 49, 48],
|
37
|
+
[32, 33, 34, 35, 35, 34, 33, 32],
|
38
|
+
[16, 17, 18, 19, 19, 18, 17, 16],
|
39
|
+
[ 0, 1, 2, 3, 3, 2, 1, 0],
|
40
|
+
[0] * 8
|
41
|
+
]
|
42
|
+
|
43
|
+
# setup color display views
|
44
|
+
def display_color_view(colors)
|
45
|
+
lambda do |interaction, action|
|
46
|
+
# set color
|
47
|
+
interaction.device.change_all(colors)
|
48
|
+
# register mute interactor on scene buttons
|
49
|
+
interaction.response_to(%w(scene1 scene2 scene3 scene4 scene5 scene6 scene7 scene8), :down, :exclusive => true, &@mute)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
interaction.response_to(:up, :down, &display_color_view(colors_single + [48, 16, 16, 16]))
|
53
|
+
interaction.response_to(:down, :down, &display_color_view(colors_double + [16, 48, 16, 16]))
|
54
|
+
interaction.response_to(:left, :down, &display_color_view(colors_mirrored + [16, 16, 48, 16]))
|
55
|
+
|
56
|
+
# setup color picker view
|
57
|
+
def display_color(opts)
|
58
|
+
lambda do |interaction, action|
|
59
|
+
@red = opts[:red] if opts[:red]
|
60
|
+
@green = opts[:green] if opts[:green]
|
61
|
+
colors = [(@green * 16 + @red)] * 64
|
62
|
+
scenes = [@red == 3 ? 51 : 3, @red == 2 ? 51 : 2, @red == 1 ? 51 : 1, @red == 0 ? 51 : 0, @green == 3 ? 51 : 48, @green == 2 ? 51 : 32, @green == 1 ? 51 : 16, @green == 0 ? 51 : 0]
|
63
|
+
interaction.device.change_all(colors + scenes + [16, 16, 16, 48])
|
64
|
+
end
|
65
|
+
end
|
66
|
+
interaction.response_to(:right, :down) do |interaction, action|
|
67
|
+
@red = 0
|
68
|
+
@green = 0
|
69
|
+
# register color picker interactors on scene buttons
|
70
|
+
interaction.response_to(:scene1, :down, :exclusive => true, &display_color(:red => 3))
|
71
|
+
interaction.response_to(:scene2, :down, :exclusive => true, &display_color(:red => 2))
|
72
|
+
interaction.response_to(:scene3, :down, :exclusive => true, &display_color(:red => 1))
|
73
|
+
interaction.response_to(:scene4, :down, :exclusive => true, &display_color(:red => 0))
|
74
|
+
interaction.response_to(:scene5, :down, :exclusive => true, &display_color(:green => 3))
|
75
|
+
interaction.response_to(:scene6, :down, :exclusive => true, &display_color(:green => 2))
|
76
|
+
interaction.response_to(:scene7, :down, :exclusive => true, &display_color(:green => 1))
|
77
|
+
interaction.response_to(:scene8, :down, :exclusive => true, &display_color(:green => 0))
|
78
|
+
# display color
|
79
|
+
interaction.respond_to(:scene8, :down)
|
80
|
+
end
|
81
|
+
|
82
|
+
# mixer button terminates interaction on button up
|
83
|
+
interaction.response_to(:mixer) do |interaction, action|
|
84
|
+
interaction.device.change(:mixer, :red => action[:state] == :down ? :hi : :off)
|
85
|
+
interaction.stop if action[:state] == :up
|
86
|
+
end
|
87
|
+
|
88
|
+
# setup mute display interactors on all unused buttons
|
89
|
+
@mute = display_color_view([0] * 72 + [16, 16, 16, 16])
|
90
|
+
interaction.response_to(%w(session user1 user2 grid), :down, &@mute)
|
91
|
+
|
92
|
+
# display mute view
|
93
|
+
interaction.respond_to(:session, :down)
|
94
|
+
|
95
|
+
# start interacting
|
96
|
+
interaction.start
|
97
|
+
|
98
|
+
# sleep so that the messages can be sent before the program terminates
|
99
|
+
sleep 0.1
|
data/examples/colors.rb
CHANGED
@@ -1,20 +1,21 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), 'setup')
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
sleep 1
|
3
|
+
device = Launchpad::Device.new(:input => false, :output => true)
|
6
4
|
|
7
5
|
pos_x = pos_y = 0
|
8
6
|
4.times do |red|
|
9
7
|
4.times do |green|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
8
|
+
device.change :grid, :x => pos_x, :y => pos_y, :red => red, :green => green
|
9
|
+
device.change :grid, :x => 7 - pos_x, :y => pos_y, :red => red, :green => green
|
10
|
+
device.change :grid, :x => pos_x, :y => 7 - pos_y, :red => red, :green => green
|
11
|
+
device.change :grid, :x => 7 - pos_x, :y => 7 - pos_y, :red => red, :green => green
|
14
12
|
pos_y += 1
|
13
|
+
# sleep, otherwise the connection drops some messages - WTF?
|
14
|
+
sleep 0.01
|
15
15
|
end
|
16
16
|
pos_x += 1
|
17
17
|
pos_y = 0
|
18
18
|
end
|
19
19
|
|
20
|
-
sleep
|
20
|
+
# sleep so that the messages can be sent before the program terminates
|
21
|
+
sleep 0.1
|
data/examples/doodle.rb
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'setup')
|
2
|
+
|
3
|
+
interaction = Launchpad::Interaction.new
|
4
|
+
|
5
|
+
current_color = {
|
6
|
+
:red => :hi,
|
7
|
+
:green => :hi,
|
8
|
+
:mode => :normal
|
9
|
+
}
|
10
|
+
|
11
|
+
def update_scene_buttons(d, color)
|
12
|
+
on = {:red => :hi, :green => :hi}
|
13
|
+
d.change(:scene1, color[:red] == :hi ? on : {:red => :hi})
|
14
|
+
d.change(:scene2, color[:red] == :med ? on : {:red => :med})
|
15
|
+
d.change(:scene3, color[:red] == :lo ? on : {:red => :lo})
|
16
|
+
d.change(:scene4, color[:red] == :off ? on : {:red => :off})
|
17
|
+
d.change(:scene5, color[:green] == :hi ? on : {:green => :hi})
|
18
|
+
d.change(:scene6, color[:green] == :med ? on : {:green => :med})
|
19
|
+
d.change(:scene7, color[:green] == :lo ? on : {:green => :lo})
|
20
|
+
d.change(:scene8, color[:green] == :off ? on : {:green => :off})
|
21
|
+
d.change(:user1, :green => color[:mode] == :normal ? :lo : :hi, :mode => :flashing)
|
22
|
+
d.change(:user2, :green => color[:mode] == :normal ? :hi : :lo)
|
23
|
+
end
|
24
|
+
|
25
|
+
def choose_color(color, opts)
|
26
|
+
lambda do |interaction, action|
|
27
|
+
color.update(opts)
|
28
|
+
update_scene_buttons(interaction.device, color)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# register color picker interactors on scene buttons
|
33
|
+
interaction.response_to(:scene1, :down, &choose_color(current_color, :red => :hi))
|
34
|
+
interaction.response_to(:scene2, :down, &choose_color(current_color, :red => :med))
|
35
|
+
interaction.response_to(:scene3, :down, &choose_color(current_color, :red => :lo))
|
36
|
+
interaction.response_to(:scene4, :down, &choose_color(current_color, :red => :off))
|
37
|
+
interaction.response_to(:scene5, :down, &choose_color(current_color, :green => :hi))
|
38
|
+
interaction.response_to(:scene6, :down, &choose_color(current_color, :green => :med))
|
39
|
+
interaction.response_to(:scene7, :down, &choose_color(current_color, :green => :lo))
|
40
|
+
interaction.response_to(:scene8, :down, &choose_color(current_color, :green => :off))
|
41
|
+
|
42
|
+
# register mode picker interactors on user buttons
|
43
|
+
interaction.response_to(:user1, :down, &choose_color(current_color, :mode => :flashing))
|
44
|
+
interaction.response_to(:user2, :down, &choose_color(current_color, :mode => :normal))
|
45
|
+
|
46
|
+
# update scene buttons and start flashing
|
47
|
+
update_scene_buttons(interaction.device, current_color)
|
48
|
+
interaction.device.flashing_auto
|
49
|
+
|
50
|
+
# feedback for grid buttons
|
51
|
+
interaction.response_to(:grid, :down) do |interaction, action|
|
52
|
+
#coord = 16 * action[:y] + action[:x]
|
53
|
+
#brightness = flags[coord] ? :off : :hi
|
54
|
+
#flags[coord] = !flags[coord]
|
55
|
+
interaction.device.change(:grid, action.merge(current_color))
|
56
|
+
end
|
57
|
+
|
58
|
+
# mixer button terminates interaction on button up
|
59
|
+
interaction.response_to(:mixer) do |interaction, action|
|
60
|
+
interaction.device.change(:mixer, :red => action[:state] == :down ? :hi : :off)
|
61
|
+
interaction.stop if action[:state] == :up
|
62
|
+
end
|
63
|
+
|
64
|
+
# start interacting
|
65
|
+
interaction.start
|
66
|
+
|
67
|
+
# sleep so that the messages can be sent before the program terminates
|
68
|
+
sleep 0.1
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'setup')
|
2
|
+
|
3
|
+
interaction = Launchpad::Interaction.new
|
4
|
+
|
5
|
+
flags = Hash.new(false)
|
6
|
+
|
7
|
+
# yellow feedback for grid buttons
|
8
|
+
interaction.response_to(:grid, :down) do |interaction, action|
|
9
|
+
coord = 16 * action[:y] + action[:x]
|
10
|
+
brightness = flags[coord] ? :off : :hi
|
11
|
+
flags[coord] = !flags[coord]
|
12
|
+
interaction.device.change(:grid, action.merge(:red => brightness, :green => brightness))
|
13
|
+
end
|
14
|
+
|
15
|
+
# mixer button terminates interaction on button up
|
16
|
+
interaction.response_to(:mixer) do |interaction, action|
|
17
|
+
interaction.device.change(:mixer, :red => action[:state] == :down ? :hi : :off)
|
18
|
+
interaction.stop if action[:state] == :up
|
19
|
+
end
|
20
|
+
|
21
|
+
# start interacting
|
22
|
+
interaction.start
|
23
|
+
|
24
|
+
# sleep so that the messages can be sent before the program terminates
|
25
|
+
sleep 0.1
|
data/examples/feedback.rb
CHANGED
@@ -1,5 +1,34 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), 'setup')
|
2
2
|
|
3
|
-
Launchpad.
|
4
|
-
|
3
|
+
interaction = Launchpad::Interaction.new
|
4
|
+
|
5
|
+
def brightness(action)
|
6
|
+
action[:state] == :down ? :hi : :off
|
7
|
+
end
|
8
|
+
|
9
|
+
# yellow feedback for grid buttons
|
10
|
+
interaction.response_to(:grid) do |interaction, action|
|
11
|
+
b = brightness(action)
|
12
|
+
interaction.device.change(:grid, action.merge(:red => b, :green => b))
|
5
13
|
end
|
14
|
+
|
15
|
+
# red feedback for top control buttons
|
16
|
+
interaction.response_to([:up, :down, :left, :right, :session, :user1, :user2, :mixer]) do |interaction, action|
|
17
|
+
interaction.device.change(action[:type], :red => brightness(action))
|
18
|
+
end
|
19
|
+
|
20
|
+
# green feedback for scene buttons
|
21
|
+
interaction.response_to([:scene1, :scene2, :scene3, :scene4, :scene5, :scene6, :scene7, :scene8]) do |interaction, action|
|
22
|
+
interaction.device.change(action[:type], :green => brightness(action))
|
23
|
+
end
|
24
|
+
|
25
|
+
# mixer button terminates interaction on button up
|
26
|
+
interaction.response_to(:mixer, :up) do |interaction, action|
|
27
|
+
interaction.stop
|
28
|
+
end
|
29
|
+
|
30
|
+
# start interacting
|
31
|
+
interaction.start
|
32
|
+
|
33
|
+
# sleep so that the messages can be sent before the program terminates
|
34
|
+
sleep 0.1
|
data/examples/reset.rb
ADDED
data/examples/setup.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
1
|
# normally, this is done by rubygems (or whatever you use for your library management)
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
3
|
+
|
2
4
|
require 'rubygems'
|
3
|
-
require
|
5
|
+
require 'launchpad'
|
data/launchpad.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{launchpad}
|
8
|
-
s.version = "0.0
|
8
|
+
s.version = "0.1.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-
|
12
|
+
s.date = %q{2009-11-13}
|
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 = [
|
@@ -22,15 +22,24 @@ Gem::Specification.new do |s|
|
|
22
22
|
"LICENSE",
|
23
23
|
"README.rdoc",
|
24
24
|
"Rakefile",
|
25
|
+
"examples/color_picker.rb",
|
25
26
|
"examples/colors.rb",
|
27
|
+
"examples/doodle.rb",
|
28
|
+
"examples/drawing_board.rb",
|
26
29
|
"examples/feedback.rb",
|
30
|
+
"examples/reset.rb",
|
27
31
|
"examples/setup.rb",
|
28
32
|
"experiments/wandering_dot.rb",
|
29
33
|
"launchpad.gemspec",
|
30
34
|
"lib/launchpad.rb",
|
35
|
+
"lib/launchpad/device.rb",
|
36
|
+
"lib/launchpad/errors.rb",
|
37
|
+
"lib/launchpad/interaction.rb",
|
38
|
+
"lib/launchpad/midi_codes.rb",
|
31
39
|
"lib/launchpad/version.rb",
|
32
40
|
"test/helper.rb",
|
33
|
-
"test/
|
41
|
+
"test/test_device.rb",
|
42
|
+
"test/test_interaction.rb"
|
34
43
|
]
|
35
44
|
s.homepage = %q{http://github.com/thomasjachmann/launchpad}
|
36
45
|
s.rdoc_options = ["--charset=UTF-8"]
|
@@ -39,9 +48,14 @@ Gem::Specification.new do |s|
|
|
39
48
|
s.summary = %q{A gem for accessing novation's launchpad programmatically and easily.}
|
40
49
|
s.test_files = [
|
41
50
|
"test/helper.rb",
|
42
|
-
"test/
|
51
|
+
"test/test_device.rb",
|
52
|
+
"test/test_interaction.rb",
|
53
|
+
"examples/color_picker.rb",
|
43
54
|
"examples/colors.rb",
|
55
|
+
"examples/doodle.rb",
|
56
|
+
"examples/drawing_board.rb",
|
44
57
|
"examples/feedback.rb",
|
58
|
+
"examples/reset.rb",
|
45
59
|
"examples/setup.rb"
|
46
60
|
]
|
47
61
|
|
@@ -51,11 +65,17 @@ Gem::Specification.new do |s|
|
|
51
65
|
|
52
66
|
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
53
67
|
s.add_runtime_dependency(%q<portmidi>, [">= 0"])
|
68
|
+
s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
69
|
+
s.add_development_dependency(%q<mocha>, [">= 0"])
|
54
70
|
else
|
55
71
|
s.add_dependency(%q<portmidi>, [">= 0"])
|
72
|
+
s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
73
|
+
s.add_dependency(%q<mocha>, [">= 0"])
|
56
74
|
end
|
57
75
|
else
|
58
76
|
s.add_dependency(%q<portmidi>, [">= 0"])
|
77
|
+
s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
78
|
+
s.add_dependency(%q<mocha>, [">= 0"])
|
59
79
|
end
|
60
80
|
end
|
61
81
|
|
data/lib/launchpad.rb
CHANGED
@@ -1,167 +1,2 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
class Launchpad
|
4
|
-
|
5
|
-
class LaunchpadError < StandardError; end
|
6
|
-
class NoInputAllowed < LaunchpadError; end
|
7
|
-
class NoOutputAllowed < LaunchpadError; end
|
8
|
-
class NoLocationError < LaunchpadError; end
|
9
|
-
class CommunicationError < LaunchpadError
|
10
|
-
attr_accessor :source
|
11
|
-
def initialize(e)
|
12
|
-
super(e.portmidi_error)
|
13
|
-
self.source = e
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
OFF = 0x80
|
18
|
-
ON = 0x90
|
19
|
-
CC = 0xB0
|
20
|
-
|
21
|
-
def initialize(opts = nil)
|
22
|
-
opts = {
|
23
|
-
:device_name => 'Launchpad',
|
24
|
-
:input => true,
|
25
|
-
:output => true
|
26
|
-
}.merge(opts || {})
|
27
|
-
Portmidi.start
|
28
|
-
if opts[:input]
|
29
|
-
input_device = Portmidi.input_devices.select {|device| device.name == opts[:device_name]}.first
|
30
|
-
@input = Portmidi::Input.new(input_device.device_id)
|
31
|
-
end
|
32
|
-
if opts[:output]
|
33
|
-
output_device = Portmidi.output_devices.select {|device| device.name == opts[:device_name]}.first
|
34
|
-
@output = Portmidi::Output.new(output_device.device_id)
|
35
|
-
reset
|
36
|
-
end
|
37
|
-
@buffering = false
|
38
|
-
end
|
39
|
-
|
40
|
-
def self.start(opts = nil, &block)
|
41
|
-
opts ||= {}
|
42
|
-
latency = (opts.delete(:latency) || 0.001).to_f
|
43
|
-
launchpad = Launchpad.new(opts.merge({:input => true, :output => true}))
|
44
|
-
loop do
|
45
|
-
messages = launchpad.input
|
46
|
-
if messages
|
47
|
-
messages.each do |message|
|
48
|
-
message = parse_message(message)
|
49
|
-
if message[:code] == ON
|
50
|
-
block.call(launchpad, message[:x], message[:y], message[:state])
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
sleep latency
|
55
|
-
end
|
56
|
-
rescue Portmidi::DeviceError => e
|
57
|
-
raise CommunicationError.new(e)
|
58
|
-
ensure
|
59
|
-
launchpad.reset if launchpad
|
60
|
-
end
|
61
|
-
|
62
|
-
# Reset the launchpad - all settings are reset and all LEDs are switched off
|
63
|
-
def reset
|
64
|
-
output(CC, 0x00, 0x00)
|
65
|
-
end
|
66
|
-
|
67
|
-
# Light all LEDs (for testing purposes)
|
68
|
-
# takes an optional parameter brightness (1-3, defaults to 3)
|
69
|
-
def light_all(brightness = 3)
|
70
|
-
output(CC, 0x00, 124 + min_max_color(brightness, false))
|
71
|
-
end
|
72
|
-
|
73
|
-
def start_buffering
|
74
|
-
output(CC, 0x00, 0x31)
|
75
|
-
@buffering = true
|
76
|
-
end
|
77
|
-
|
78
|
-
def flush_buffer(end_buffering = true)
|
79
|
-
output(CC, 0x00, 0x34)
|
80
|
-
if end_buffering
|
81
|
-
output(CC, 0x00, 0x30)
|
82
|
-
@buffering = false
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
# Switches a single LED
|
87
|
-
# * :x => x coordinate (0 based from top left, mandatory)
|
88
|
-
# * :y => y coordinate (0 based from top left, mandatory)
|
89
|
-
# * :red => brightness of red LED (0-3, optional, defaults to 0)
|
90
|
-
# * :green => brightness of red LED (0-3, optional, defaults to 0)
|
91
|
-
# * :mode => button behaviour (:normal, :flashing, :buffering, optional, defaults to :normal)
|
92
|
-
def single(opts)
|
93
|
-
location = location(opts)
|
94
|
-
velocity = velocity(opts)
|
95
|
-
output(ON, location, velocity)
|
96
|
-
end
|
97
|
-
|
98
|
-
def multi(*velocities)
|
99
|
-
output(CC, 0x01, 0x00)
|
100
|
-
output(0x92, *velocities)
|
101
|
-
end
|
102
|
-
|
103
|
-
# Switches LEDs marked as flashing on (when using custom timer for flashing)
|
104
|
-
def custom_flashing_on
|
105
|
-
output(CC, 0x00, 0x20)
|
106
|
-
end
|
107
|
-
|
108
|
-
# Switches LEDs marked as flashing off (when using custom timer for flashing)
|
109
|
-
def custom_flashing_off
|
110
|
-
output(CC, 0x00, 0x21)
|
111
|
-
end
|
112
|
-
|
113
|
-
# Starts flashing LEDs marked as flashing automatically
|
114
|
-
def start_auto_flashing
|
115
|
-
output(CC, 0x00, 0x28)
|
116
|
-
end
|
117
|
-
|
118
|
-
# Stops flashing LEDs marked as flashing automatically (turning them on)
|
119
|
-
alias_method :stop_auto_flashing, :custom_flashing_on
|
120
|
-
|
121
|
-
def coordinates(location)
|
122
|
-
[location % 16, location / 16]
|
123
|
-
end
|
124
|
-
|
125
|
-
def input
|
126
|
-
raise NoInputAllowed if @input.nil?
|
127
|
-
@input.read(16)
|
128
|
-
end
|
129
|
-
|
130
|
-
def output(*args)
|
131
|
-
raise NoOutputAllowed if @output.nil?
|
132
|
-
@output.write([{:message => args, :timestamp => 0}])
|
133
|
-
end
|
134
|
-
|
135
|
-
private
|
136
|
-
|
137
|
-
def self.parse_message(message)
|
138
|
-
message = message[:message]
|
139
|
-
{
|
140
|
-
:code => message[0],
|
141
|
-
:x => message[1] % 16,
|
142
|
-
:y => message[1] / 16,
|
143
|
-
:state => message[2] == 127
|
144
|
-
}
|
145
|
-
end
|
146
|
-
|
147
|
-
def location(opts)
|
148
|
-
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?
|
149
|
-
y * 16 + x
|
150
|
-
end
|
151
|
-
|
152
|
-
def velocity(opts)
|
153
|
-
red = min_max_color(opts[:red] || 0)
|
154
|
-
green = min_max_color(opts[:green] || 0)
|
155
|
-
flags = case opts[:mode]
|
156
|
-
when :flashing then 8
|
157
|
-
when :buffering then 0
|
158
|
-
else 12
|
159
|
-
end
|
160
|
-
(16 * (green)) + red + flags
|
161
|
-
end
|
162
|
-
|
163
|
-
def min_max_color(color, with_off = true)
|
164
|
-
[[with_off ? 0 : 1, color.to_i].max, 3].min
|
165
|
-
end
|
166
|
-
|
167
|
-
end
|
1
|
+
require 'launchpad/device'
|
2
|
+
require 'launchpad/interaction'
|