launchpad_mk2 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/LICENSE +7 -0
- data/README.rdoc +75 -0
- data/Rakefile +10 -0
- data/examples/binary_clock.rb +41 -0
- data/examples/color_picker.rb +96 -0
- data/examples/colors.rb +27 -0
- data/examples/corners.rb +11 -0
- data/examples/doodle.rb +65 -0
- data/examples/drawing_board.rb +20 -0
- data/examples/feedback.rb +30 -0
- data/examples/pong.rb +193 -0
- data/examples/sysex.rb +34 -0
- data/launchpad-gem-overview.gif +0 -0
- data/launchpad_mk2.gemspec +30 -0
- data/lib/launchpad_mk2.rb +23 -0
- data/lib/launchpad_mk2/device.rb +466 -0
- data/lib/launchpad_mk2/errors.rb +37 -0
- data/lib/launchpad_mk2/interaction.rb +333 -0
- data/lib/launchpad_mk2/logging.rb +27 -0
- data/lib/launchpad_mk2/midi_codes.rb +40 -0
- data/lib/launchpad_mk2/version.rb +3 -0
- data/test/helper.rb +45 -0
- data/test/test_device.rb +446 -0
- data/test/test_interaction.rb +460 -0
- metadata +154 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 66358055a34d22fc58af54828d732c74d489e88e
|
4
|
+
data.tar.gz: 6f4a31bcb5eca94c4959d00d4e962817dd022880
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cc52a3b22776aa47684998163e02c5661da310fc2d397fe475f17e40b376fb072930a7c8f1691f0a752280d9487a4232867519ac3b048b7edd5853500ec1e859
|
7
|
+
data.tar.gz: 0018de9688efec8d10ccdf5a9b96a2c32c4d7ea33e1faf34f3e4646d83dda698b4eaf903a69f8ef573d856ce3f5495bb94468fd1a294149d685754674afb05b6
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Copyright 2017 Andy Marks
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
|
+
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6
|
+
|
7
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
= launchpad
|
2
|
+
|
3
|
+
{<img src="https://travis-ci.org/andeemarks/launchpad.png?branch=master" alt="Build Status" />}[https://travis-ci.org/andeemarks/launchpad]
|
4
|
+
|
5
|
+
[https://github.com/andeemarks/launchpad/blob/master/launchpad-gem-overview.gif]
|
6
|
+
|
7
|
+
This gem provides a Ruby interface to access the {Novation Launchpad MK2}[https://global.novationmusic.com/launch/launchpad#] programmatically. The code started life as a clone of {Thomas Jachman's gem}[https://github.com/thomasjachmann/launchpad] and was subsequently updated to handle the MK2 version of the Launchpad. The mapping of buttons and specification of colours completely changed when the MK2 was released, so lots of the original code in these areas has been re-written, but the interaction code is still largely intact.
|
8
|
+
|
9
|
+
== More Info
|
10
|
+
|
11
|
+
* Novation's Launchpad MK2 MIDI programmer's reference {here}[https://global.novationmusic.com/sites/default/files/novation/downloads/10529/launchpad-mk2-programmers-reference-guide_0.pdf] was the sole source for helping me understand how to interact with the Launchpad.
|
12
|
+
|
13
|
+
* Due to limitations in the Portmidi gem, it's not possible to specify which channel is used to send MIDI messages. As such, all interaction with the Launchpad are done over the default channel 1. This means some workarounds have been needed to access functionality like flash and pulse, which usually require channel 2 messages. All of these workarounds have been implemented via sending {System Exclusive messages}[http://electronicmusic.wikia.com/wiki/System_exclusive] as per the above reference guide.
|
14
|
+
|
15
|
+
== Requirements
|
16
|
+
|
17
|
+
* Roger B. Dannenberg's {portmidi library}[http://sourceforge.net/projects/portmedia/]
|
18
|
+
* Jan Krutisch's {portmidi gem}[http://github.com/halfbyte/portmidi]
|
19
|
+
|
20
|
+
== Compatibility
|
21
|
+
|
22
|
+
The gem is known to be compatible with the following ruby versions:
|
23
|
+
|
24
|
+
* MRI 2.3.1
|
25
|
+
|
26
|
+
== Installation
|
27
|
+
|
28
|
+
The gem is hosted on RubyGems[https://rubygems.org/], so in order to use it, you're gonna gem install it:
|
29
|
+
|
30
|
+
gem install launchpad
|
31
|
+
|
32
|
+
|
33
|
+
== Usage
|
34
|
+
|
35
|
+
There are two main entry points:
|
36
|
+
|
37
|
+
* <code>require 'launchpad/device'</code>, providing <code>Launchpad::Device</code>, which handles all the basic input/output stuff
|
38
|
+
* <code>require 'launchpad/interaction'</code> or just 'launchpad', additionally providing <code>Launchpad::Interaction</code>, which lets you respond to actions (button presses/releases)
|
39
|
+
|
40
|
+
This is a simple example (only requiring the device for output) that resets the launchpad and then lights the grid button at position 4/4 (from bottom left of 0/0).
|
41
|
+
|
42
|
+
require 'launchpad/device'
|
43
|
+
|
44
|
+
device = Launchpad::Device.new
|
45
|
+
sleep 1
|
46
|
+
device.reset_all
|
47
|
+
sleep 1
|
48
|
+
device.change :grid, :x => 4, :y => 4, :color => 72
|
49
|
+
|
50
|
+
This is an interaction example lighting all grid buttons in red when pressed and keeping them lit.
|
51
|
+
|
52
|
+
require 'launchpad'
|
53
|
+
|
54
|
+
interaction = Launchpad::Interaction.new
|
55
|
+
interaction.response_to(:grid, :down) do |interaction, action|
|
56
|
+
interaction.device.change(:grid, action.merge(:color => 72))
|
57
|
+
end
|
58
|
+
interaction.response_to(:mixer, :down) do |interaction, action|
|
59
|
+
interaction.stop
|
60
|
+
end
|
61
|
+
|
62
|
+
interaction.start
|
63
|
+
|
64
|
+
|
65
|
+
For more details, see the examples. examples/color_picker.rb is the most complex example with interaction.
|
66
|
+
|
67
|
+
== To Do
|
68
|
+
|
69
|
+
* Rename gem to avoid name clashes with original version
|
70
|
+
* Ensure all examples are working
|
71
|
+
* Update examples in README
|
72
|
+
|
73
|
+
== Copyright
|
74
|
+
|
75
|
+
Copyright (c) 2017 Andy Marks. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'launchpad_mk2'
|
2
|
+
|
3
|
+
device = Launchpad::Device.new
|
4
|
+
|
5
|
+
on = { :color => 16 }
|
6
|
+
off = { :color => 72 }
|
7
|
+
|
8
|
+
digit_map = [
|
9
|
+
[off, off, off, off],
|
10
|
+
[on , off, off, off],
|
11
|
+
[off, on , off, off],
|
12
|
+
[on , on , off, off],
|
13
|
+
[off, off, on , off],
|
14
|
+
[on , off, on , off],
|
15
|
+
[off, on , on , off],
|
16
|
+
[on , on , on , off],
|
17
|
+
[off, off, off, on ],
|
18
|
+
[on , off, off, on ]
|
19
|
+
]
|
20
|
+
|
21
|
+
def offset_x(x)
|
22
|
+
if (x == 0 or x == 1)
|
23
|
+
return x
|
24
|
+
end
|
25
|
+
|
26
|
+
if (x == 2 or x == 3)
|
27
|
+
return (x + 1)
|
28
|
+
end
|
29
|
+
|
30
|
+
return (x + 2)
|
31
|
+
end
|
32
|
+
|
33
|
+
while true do
|
34
|
+
Time.now.strftime('%H%M%S').split('').each_with_index do |digit, x|
|
35
|
+
digit_map[digit.to_i].each_with_index do |color, y|
|
36
|
+
device.change :grid, color.merge(:x => offset_x(x), :y => (y + 2))
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
sleep 0.25
|
41
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'launchpad_mk2'
|
2
|
+
|
3
|
+
interaction = Launchpad::Interaction.new
|
4
|
+
|
5
|
+
# build color arrays for color display views
|
6
|
+
colors_single = [
|
7
|
+
[ 0, 1, 2, 3, 0, 0, 0, 0],
|
8
|
+
[16, 17, 18, 19, 0, 0, 0, 0],
|
9
|
+
[32, 33, 34, 35, 0, 0, 0, 0],
|
10
|
+
[48, 49, 50, 51, 0, 0, 0, 0],
|
11
|
+
[0] * 8,
|
12
|
+
[0] * 8,
|
13
|
+
[0] * 8,
|
14
|
+
[0] * 8,
|
15
|
+
[0] * 8
|
16
|
+
]
|
17
|
+
colors_double = [
|
18
|
+
[ 0, 0, 1, 1, 2, 2, 3, 3],
|
19
|
+
[ 0, 0, 1, 1, 2, 2, 3, 3],
|
20
|
+
[16, 16, 17, 17, 18, 18, 19, 19],
|
21
|
+
[16, 16, 17, 17, 18, 18, 19, 19],
|
22
|
+
[32, 32, 33, 33, 34, 34, 35, 35],
|
23
|
+
[32, 32, 33, 33, 34, 34, 35, 35],
|
24
|
+
[48, 48, 49, 49, 50, 50, 51, 51],
|
25
|
+
[48, 48, 49, 49, 50, 50, 51, 51],
|
26
|
+
[0] * 8
|
27
|
+
]
|
28
|
+
colors_mirrored = [
|
29
|
+
[ 0, 1, 2, 3, 3, 2, 1, 0],
|
30
|
+
[16, 17, 18, 19, 19, 18, 17, 16],
|
31
|
+
[32, 33, 34, 35, 35, 34, 33, 32],
|
32
|
+
[48, 49, 50, 51, 51, 50, 49, 48],
|
33
|
+
[48, 49, 50, 51, 51, 50, 49, 48],
|
34
|
+
[32, 33, 34, 35, 35, 34, 33, 32],
|
35
|
+
[16, 17, 18, 19, 19, 18, 17, 16],
|
36
|
+
[ 0, 1, 2, 3, 3, 2, 1, 0],
|
37
|
+
[0] * 8
|
38
|
+
]
|
39
|
+
|
40
|
+
# setup color display views
|
41
|
+
def display_color_view(colors)
|
42
|
+
lambda do |interaction, action|
|
43
|
+
# set color
|
44
|
+
interaction.device.change_all(colors)
|
45
|
+
# register mute interactor on scene buttons
|
46
|
+
interaction.response_to(%w(scene1 scene2 scene3 scene4 scene5 scene6 scene7 scene8), :down, :exclusive => true, &@mute)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
interaction.response_to(:up, :down, &display_color_view(colors_single + [48, 16, 16, 16]))
|
50
|
+
interaction.response_to(:down, :down, &display_color_view(colors_double + [16, 48, 16, 16]))
|
51
|
+
interaction.response_to(:left, :down, &display_color_view(colors_mirrored + [16, 16, 48, 16]))
|
52
|
+
|
53
|
+
# setup color picker view
|
54
|
+
def display_color(opts)
|
55
|
+
lambda do |interaction, action|
|
56
|
+
@red = opts[:red] if opts[:red]
|
57
|
+
@green = opts[:green] if opts[:green]
|
58
|
+
colors = [(@green * 16 + @red)] * 64
|
59
|
+
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]
|
60
|
+
interaction.device.change_all(colors + scenes + [16, 16, 16, 48])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
interaction.response_to(:right, :down) do |interaction, action|
|
64
|
+
@red = 0
|
65
|
+
@green = 0
|
66
|
+
# register color picker interactors on scene buttons
|
67
|
+
interaction.response_to(:scene1, :down, :exclusive => true, &display_color(:red => 3))
|
68
|
+
interaction.response_to(:scene2, :down, :exclusive => true, &display_color(:red => 2))
|
69
|
+
interaction.response_to(:scene3, :down, :exclusive => true, &display_color(:red => 1))
|
70
|
+
interaction.response_to(:scene4, :down, :exclusive => true, &display_color(:red => 0))
|
71
|
+
interaction.response_to(:scene5, :down, :exclusive => true, &display_color(:green => 3))
|
72
|
+
interaction.response_to(:scene6, :down, :exclusive => true, &display_color(:green => 2))
|
73
|
+
interaction.response_to(:scene7, :down, :exclusive => true, &display_color(:green => 1))
|
74
|
+
interaction.response_to(:scene8, :down, :exclusive => true, &display_color(:green => 0))
|
75
|
+
# display color
|
76
|
+
interaction.respond_to(:scene8, :down)
|
77
|
+
end
|
78
|
+
|
79
|
+
# mixer button terminates interaction on button up
|
80
|
+
interaction.response_to(:mixer) do |interaction, action|
|
81
|
+
interaction.device.change(:mixer, :red => action[:state] == :down ? :hi : :off)
|
82
|
+
interaction.stop if action[:state] == :up
|
83
|
+
end
|
84
|
+
|
85
|
+
# setup mute display interactors on all unused buttons
|
86
|
+
@mute = display_color_view([0] * 72 + [16, 16, 16, 16])
|
87
|
+
interaction.response_to(%w(session user1 user2 grid), :down, &@mute)
|
88
|
+
|
89
|
+
# display mute view
|
90
|
+
interaction.respond_to(:session, :down)
|
91
|
+
|
92
|
+
# start interacting
|
93
|
+
interaction.start
|
94
|
+
|
95
|
+
# sleep so that the messages can be sent before the program terminates
|
96
|
+
sleep 0.1
|
data/examples/colors.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'launchpad_mk2'
|
2
|
+
|
3
|
+
device = Launchpad::Device.new(:input => false, :output => true)
|
4
|
+
|
5
|
+
color = 0
|
6
|
+
# first page of colors
|
7
|
+
(0..7).each do |row|
|
8
|
+
(0..7).each do |column|
|
9
|
+
device.change :grid, :x => column, :y => row, :color => color
|
10
|
+
color = color + 1
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
sleep 2
|
15
|
+
|
16
|
+
# second page of colors
|
17
|
+
(0..7).each do |row|
|
18
|
+
(0..7).each do |column|
|
19
|
+
device.change :grid, :x => column, :y => row, :color => color
|
20
|
+
color = color + 1
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# sleep so that the messages can be sent before the program terminates
|
25
|
+
sleep 2
|
26
|
+
|
27
|
+
device.reset_all()
|
data/examples/corners.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'launchpad_mk2'
|
2
|
+
|
3
|
+
device = Launchpad::Device.new(:input => false, :output => true)
|
4
|
+
|
5
|
+
device.change :grid, :x => 0, :y => 0, :color => 56 # fuschia
|
6
|
+
device.change :grid, :x => 7, :y => 7, :color => 63 # olive
|
7
|
+
device.change :grid, :x => 0, :y => 7, :color => 72 # red
|
8
|
+
device.change :grid, :x => 7, :y => 0, :color => 8 # orange
|
9
|
+
|
10
|
+
# sleep so that the messages can be sent before the program terminates
|
11
|
+
sleep 0.1
|
data/examples/doodle.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'launchpad_mk2'
|
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
|
+
|
49
|
+
# feedback for grid buttons
|
50
|
+
interaction.response_to(:grid, :down) do |interaction, action|
|
51
|
+
interaction.device.change(:grid, :x => action[:x], :y => action[:y], :color => current_color)
|
52
|
+
end
|
53
|
+
|
54
|
+
# mixer button terminates interaction on button up
|
55
|
+
interaction.response_to(:mixer) do |interaction, action|
|
56
|
+
interaction.device.change(:mixer, :color => action[:state] == :down ? 5 : 61)
|
57
|
+
interaction.stop if action[:state] == :up
|
58
|
+
interaction.device.reset_all()
|
59
|
+
end
|
60
|
+
|
61
|
+
# start interacting
|
62
|
+
interaction.start
|
63
|
+
|
64
|
+
# sleep so that the messages can be sent before the program terminates
|
65
|
+
sleep 0.1
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'launchpad_mk2'
|
2
|
+
|
3
|
+
interaction = Launchpad::Interaction.new
|
4
|
+
|
5
|
+
# yellow feedback for grid buttons
|
6
|
+
interaction.response_to(:grid, :down) do |interaction, action|
|
7
|
+
interaction.device.change(:grid, :x => action[:x], :y => action[:y], :color => 13)
|
8
|
+
end
|
9
|
+
|
10
|
+
# mixer button terminates interaction on button up
|
11
|
+
interaction.response_to(:mixer) do |interaction, action|
|
12
|
+
interaction.device.change(:mixer, :color => action[:state] == :down ? 5 : 61)
|
13
|
+
interaction.stop if action[:state] == :up
|
14
|
+
end
|
15
|
+
|
16
|
+
# start interacting
|
17
|
+
interaction.start
|
18
|
+
|
19
|
+
# sleep so that the messages can be sent before the program terminates
|
20
|
+
sleep 0.1
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'launchpad_mk2'
|
2
|
+
|
3
|
+
interaction = Launchpad::Interaction.new
|
4
|
+
|
5
|
+
# yellow feedback for grid buttons
|
6
|
+
interaction.response_to(:grid) do |interaction, action|
|
7
|
+
interaction.device.change(:grid, :x => action[:x], :y => action[:y], :color => 72)
|
8
|
+
end
|
9
|
+
|
10
|
+
# red feedback for top control buttons
|
11
|
+
interaction.response_to([:up, :down, :left, :right, :session, :user1, :user2, :mixer]) do |interaction, action|
|
12
|
+
interaction.device.change(action[:type], :color => 13)
|
13
|
+
end
|
14
|
+
|
15
|
+
# green feedback for scene buttons
|
16
|
+
interaction.response_to([:scene1, :scene2, :scene3, :scene4, :scene5, :scene6, :scene7, :scene8]) do |interaction, action|
|
17
|
+
interaction.device.change(action[:type], :color => 16)
|
18
|
+
end
|
19
|
+
|
20
|
+
# mixer button terminates interaction on button up
|
21
|
+
interaction.response_to(:mixer, :up) do |interaction, action|
|
22
|
+
interaction.device.reset_all()
|
23
|
+
interaction.stop
|
24
|
+
end
|
25
|
+
|
26
|
+
# start interacting
|
27
|
+
interaction.start
|
28
|
+
|
29
|
+
# sleep so that the messages can be sent before the program terminates
|
30
|
+
sleep 0.1
|
data/examples/pong.rb
ADDED
@@ -0,0 +1,193 @@
|
|
1
|
+
require 'launchpad_mk2'
|
2
|
+
|
3
|
+
interaction = Launchpad::Interaction.new
|
4
|
+
|
5
|
+
UP = -1
|
6
|
+
DOWN = 1
|
7
|
+
|
8
|
+
@left_bottom_edge = @right_bottom_edge = 3
|
9
|
+
@left_score = @right_score = 0
|
10
|
+
|
11
|
+
def move_flipper(flipper, d, delta, column)
|
12
|
+
flipper = flipper + delta
|
13
|
+
if (flipper < 0 || flipper > 5)
|
14
|
+
return flipper - delta
|
15
|
+
end
|
16
|
+
show_flipper(d, column, flipper)
|
17
|
+
|
18
|
+
return flipper
|
19
|
+
end
|
20
|
+
|
21
|
+
def move_left_flipper(d, delta)
|
22
|
+
@left_bottom_edge = move_flipper(@left_bottom_edge, d, delta, 0)
|
23
|
+
end
|
24
|
+
|
25
|
+
def move_right_flipper(d, delta)
|
26
|
+
@right_bottom_edge = move_flipper(@right_bottom_edge, d, delta, 7)
|
27
|
+
end
|
28
|
+
|
29
|
+
def show_flipper(d, x, bottom_edge)
|
30
|
+
d.light1_column(x, 0)
|
31
|
+
d.change(:grid, :x => x, :y => bottom_edge, :color => 16)
|
32
|
+
d.change(:grid, :x => x, :y => bottom_edge + 1, :color => 16)
|
33
|
+
d.change(:grid, :x => x, :y => bottom_edge + 2, :color => 16)
|
34
|
+
end
|
35
|
+
|
36
|
+
# yellow feedback for grid buttons
|
37
|
+
interaction.response_to(:grid, :down) do |interaction, action|
|
38
|
+
x = action[:x]
|
39
|
+
y = action[:y]
|
40
|
+
if (x == 0 && y == 0)
|
41
|
+
move_left_flipper(interaction.device, UP)
|
42
|
+
elsif (x == 0 && y == 7)
|
43
|
+
move_left_flipper(interaction.device, DOWN)
|
44
|
+
elsif (x == 7 && y == 0)
|
45
|
+
move_right_flipper(interaction.device, UP)
|
46
|
+
elsif (x == 7 && y == 7)
|
47
|
+
move_right_flipper(interaction.device, DOWN)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# red feedback for top control buttons
|
52
|
+
interaction.response_to([:up, :down, :left, :right, :session, :user1, :user2, :mixer]) do |interaction, action|
|
53
|
+
interaction.device.change(action[:type], :color => 13)
|
54
|
+
end
|
55
|
+
|
56
|
+
# green feedback for scene buttons
|
57
|
+
interaction.response_to([:scene1, :scene2, :scene3, :scene4, :scene5, :scene6, :scene7, :scene8]) do |interaction, action|
|
58
|
+
interaction.device.change(action[:type], :color => 16)
|
59
|
+
end
|
60
|
+
|
61
|
+
# mixer button terminates interaction on button up
|
62
|
+
interaction.response_to(:mixer, :up) do |interaction, action|
|
63
|
+
interaction.device.reset_all()
|
64
|
+
interaction.stop
|
65
|
+
end
|
66
|
+
|
67
|
+
def hits_left_flipper?(new_ball_y)
|
68
|
+
new_ball_y >= @left_bottom_edge && new_ball_y <= @left_bottom_edge + 2
|
69
|
+
end
|
70
|
+
|
71
|
+
def hits_right_flipper?(new_ball_y)
|
72
|
+
new_ball_y >= @right_bottom_edge && new_ball_y <= @right_bottom_edge + 2
|
73
|
+
end
|
74
|
+
|
75
|
+
def update_scores(d)
|
76
|
+
puts "Left score: " + @left_score.to_s
|
77
|
+
puts "Right score: " + @right_score.to_s
|
78
|
+
if (@left_score > 0)
|
79
|
+
scene_button = (":scene" + @left_score.to_s).to_sym
|
80
|
+
puts scene_button
|
81
|
+
d.change(scene_button, :color => 72)
|
82
|
+
end
|
83
|
+
if (@right_score > 0)
|
84
|
+
scene_button = (":scene" + (9 - @right_score).to_s).to_sym
|
85
|
+
puts scene_button
|
86
|
+
d.change(scene_button, :color => 72)
|
87
|
+
end
|
88
|
+
if (@left_score == 4 || @right_score == 4)
|
89
|
+
@player_wins = true
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def approaching_left_gutter?(new_ball_x)
|
94
|
+
new_ball_x < 1
|
95
|
+
end
|
96
|
+
|
97
|
+
def approaching_right_gutter?(new_ball_x)
|
98
|
+
new_ball_x > 6
|
99
|
+
end
|
100
|
+
|
101
|
+
def record_score(d)
|
102
|
+
update_scores(d)
|
103
|
+
hide_ball(d, @ball_x, @ball_y)
|
104
|
+
end
|
105
|
+
|
106
|
+
def record_right_score(d)
|
107
|
+
puts "Right score with ball at " + @ball_x.to_s + ", " + @ball_y.to_s + ", and left flipper at " + @left_bottom_edge.to_s
|
108
|
+
@right_score = @right_score + 1
|
109
|
+
record_score(d)
|
110
|
+
end
|
111
|
+
|
112
|
+
def record_left_score(d)
|
113
|
+
puts "Left score with ball at " + @ball_x.to_s + ", " + @ball_y.to_s + ", and right flipper at " + @right_bottom_edge.to_s
|
114
|
+
@left_score = @left_score + 1
|
115
|
+
record_score(d)
|
116
|
+
end
|
117
|
+
|
118
|
+
def bounce?(new_ball_y)
|
119
|
+
new_ball_y < 0 || new_ball_y > 7
|
120
|
+
end
|
121
|
+
|
122
|
+
def run_ball_movement(d)
|
123
|
+
Thread.new {
|
124
|
+
@player_wins = false
|
125
|
+
until @player_wins do
|
126
|
+
new_score = false
|
127
|
+
start_ball(d)
|
128
|
+
until new_score do
|
129
|
+
sleep 0.5
|
130
|
+
if (approaching_left_gutter?(@ball_x + @lat_delta))
|
131
|
+
if (hits_left_flipper?(@ball_y + @lon_delta))
|
132
|
+
@lat_delta = -@lat_delta
|
133
|
+
move_ball(d, @ball_x + @lat_delta, @ball_y + @lon_delta)
|
134
|
+
else
|
135
|
+
record_right_score(d)
|
136
|
+
new_score = true
|
137
|
+
end
|
138
|
+
elsif (approaching_right_gutter?(@ball_x + @lat_delta))
|
139
|
+
if (hits_right_flipper?(@ball_y + @lon_delta))
|
140
|
+
@lat_delta = -@lat_delta
|
141
|
+
move_ball(d, @ball_x + @lat_delta, @ball_y + @lon_delta)
|
142
|
+
else
|
143
|
+
record_left_score(d)
|
144
|
+
new_score = true
|
145
|
+
end
|
146
|
+
end
|
147
|
+
if (!new_score)
|
148
|
+
if (bounce?(@ball_y + @lon_delta))
|
149
|
+
@lon_delta = -@lon_delta
|
150
|
+
end
|
151
|
+
move_ball(d, @ball_x + @lat_delta, @ball_y + @lon_delta)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
}
|
156
|
+
end
|
157
|
+
|
158
|
+
def move_ball(d, new_ball_x, new_ball_y)
|
159
|
+
hide_ball(d, @ball_x, @ball_y)
|
160
|
+
@ball_x = new_ball_x
|
161
|
+
@ball_y = new_ball_y
|
162
|
+
show_ball(d, @ball_x, @ball_y)
|
163
|
+
end
|
164
|
+
|
165
|
+
def show_ball(d, x, y)
|
166
|
+
d.change(:grid, :x => x, :y => y, :color => 72)
|
167
|
+
end
|
168
|
+
|
169
|
+
def hilite_collision(d, x, y)
|
170
|
+
d.change(:grid, :x => x, :y => y, :color => 34)
|
171
|
+
end
|
172
|
+
|
173
|
+
def hide_ball(d, x, y)
|
174
|
+
d.change(:grid, :x => x, :y => y, :color => 0)
|
175
|
+
end
|
176
|
+
|
177
|
+
def start_ball(d)
|
178
|
+
@lat_delta = rand(2) == 0 ? -1 : 1
|
179
|
+
@lon_delta = rand(2) == 0 ? -1 : 1
|
180
|
+
@ball_x = @lat_delta == -1 ? 4 : 3
|
181
|
+
@ball_y = @lon_delta == -1 ? 4 : 3
|
182
|
+
puts "Ball moving using " + @lat_delta.to_s + ", " + @lon_delta.to_s + " from " + @ball_x.to_s + ", " + @ball_y.to_s
|
183
|
+
show_ball(d, @ball_x, @ball_y)
|
184
|
+
end
|
185
|
+
|
186
|
+
show_flipper(interaction.device, 0, @left_bottom_edge)
|
187
|
+
show_flipper(interaction.device, 7, @right_bottom_edge)
|
188
|
+
run_ball_movement(interaction.device)
|
189
|
+
# start interacting
|
190
|
+
interaction.start
|
191
|
+
|
192
|
+
# sleep so that the messages can be sent before the program terminates
|
193
|
+
sleep 0.1
|