Skip to content

Commit 0b2109a

Browse files
committed
add support for Launchpad MK2
1 parent 72b1c9c commit 0b2109a

File tree

14 files changed

+551
-163
lines changed

14 files changed

+551
-163
lines changed

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# launchpad
2-
A package allows you to talk to your Novation Launchpad S in Go. Light buttons or read your touches.
2+
A package allows you to talk to your Novation Launchpad S or Launchpad MK2 in Go. Light buttons or read your touches.
33

4-
This library is currently only working with Launchpad S (Green-Red Launchpads)
4+
This library is currently only working with Launchpad S (Green-Red Launchpads) and Launchpad MK2 (RGB)
55

66
## Usage
77
Initialize a new Launchpad. If there are no currently connected Launchpad
@@ -53,6 +53,7 @@ The coordinate system is illustrated below.
5353
+--------- arrow keys -----------+ +--- mode keys ---+
5454
{0, 8} {1, 8} {2, 8} {3, 8} {4, 8} {5, 8} {6, 8} {7, 8} | ableton
5555
----------------------------------------------------------------
56+
----------------------------------------------------------------
5657
{0, 0} {1, 0} {2, 0} {3, 0} {4, 0} {5, 0} {6, 0} {7, 0} | {8, 0} vol
5758
----------------------------------------------------------------
5859
{0, 1} {1, 1} {2, 1} {3, 1} {4, 1} {5, 1} {6, 1} {7, 1} | {8, 1} pan
@@ -100,7 +101,7 @@ func main() {
100101
for {
101102
select {
102103
case hit := <-hits:
103-
pad.Light(hit.X, hit.Y, 3, 3)
104+
pad.Light(hit.X, hit.Y, launchpad.ColorS{3, 3})
104105
}
105106
}
106107
}
@@ -128,7 +129,7 @@ func main() {
128129
pad.Clear()
129130

130131
// Send Text-Loop
131-
pad.Text(3, 0).Add(7, "Hello World!").Perform()
132+
pad.Text(launchpad.ColorS{3, 0}).Add(7, "Hello World!").Perform()
132133

133134
marker, err := pad.ListenToScrollTextEndMarker()
134135
if err != nil {

cmd/helloworld/launchpad_mk2/main.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package main
2+
3+
import (
4+
// replace with e.g. "gitlab.com/gomidi/rtmididrv" for real midi connections
5+
driver "gitlab.com/gomidi/midi/testdrv"
6+
"time"
7+
8+
"github.com/rainu/launchpad"
9+
"log"
10+
)
11+
12+
func main() {
13+
pad, err := launchpad.NewLaunchpad(driver.New("fake"))
14+
if err != nil {
15+
log.Fatalf("error while openning connection to launchpad: %v", err)
16+
}
17+
defer pad.Close()
18+
19+
pad.Clear()
20+
21+
// Set <0,8> to yellow (via RGB mode).
22+
pad.Light(0, 8, launchpad.ColorMK2RGB{16, 16, 0})
23+
24+
// Set <1,8> to yellow (via RGB mode).
25+
pad.Light(1, 8, launchpad.ColorMK2{15})
26+
27+
// Show the whole color palette
28+
c := 0
29+
for x := 0; x < 8; x++ {
30+
for y := 0; y < 8; y++ {
31+
pad.Light(x, y, launchpad.ColorMK2{c})
32+
c++
33+
}
34+
}
35+
time.Sleep(5 * time.Second)
36+
for x := 0; x < 8; x++ {
37+
for y := 0; y < 8; y++ {
38+
pad.Light(x, y, launchpad.ColorMK2{c})
39+
c++
40+
}
41+
}
42+
43+
pad.Text(launchpad.ColorMK2{9}).
44+
Add(7, "Hello ").
45+
Add(1, "World!").
46+
Perform()
47+
48+
hits, err := pad.ListenToHits()
49+
if err != nil {
50+
panic(err)
51+
}
52+
53+
marker, err := pad.ListenToScrollTextEndMarker()
54+
if err != nil {
55+
panic(err)
56+
}
57+
58+
for {
59+
select {
60+
case hit := <-hits:
61+
log.Printf("Button pressed at <x=%d, y=%d>", hit.X, hit.Y)
62+
// Turn to green.
63+
if hit.Down {
64+
pad.Light(hit.X, hit.Y, launchpad.ColorMK2{13})
65+
} else {
66+
pad.Light(hit.X, hit.Y, launchpad.ColorMK2RGB{0, 16, 16})
67+
}
68+
case <-marker:
69+
log.Print("Text ends")
70+
}
71+
72+
}
73+
}

cmd/helloworld/main.go renamed to cmd/helloworld/launchpad_s/main.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ func main() {
1818
pad.Clear()
1919

2020
// Set <0,0> to yellow.
21-
pad.Light(0, 0, 2, 2)
21+
pad.Light(0, 0, launchpad.ColorS{2, 2})
2222

23-
pad.Text(3, 0).
23+
pad.Text(launchpad.ColorS{3, 0}).
2424
Add(7, "Hello ").
2525
Add(1, "World!").
2626
Perform()
@@ -41,9 +41,9 @@ func main() {
4141
log.Printf("Button pressed at <x=%d, y=%d>", hit.X, hit.Y)
4242
// Turn to green.
4343
if hit.Down {
44-
pad.Light(hit.X, hit.Y, 0, 3)
44+
pad.Light(hit.X, hit.Y, launchpad.ColorS{0, 3})
4545
} else {
46-
pad.Light(hit.X, hit.Y, 3, 0)
46+
pad.Light(hit.X, hit.Y, launchpad.ColorS{3, 0})
4747
}
4848
case <-marker:
4949
log.Print("Text ends")

event_mk2.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package launchpad
2+
3+
import (
4+
"gitlab.com/gomidi/midi"
5+
"gitlab.com/gomidi/midi/midimessage/channel"
6+
"gitlab.com/gomidi/midi/reader"
7+
)
8+
9+
func (l *LaunchpadMK2) listen() error {
10+
rd := reader.New(
11+
reader.NoLogger(),
12+
// write every message to the out port
13+
reader.Each(l.handleMidiMessage),
14+
)
15+
16+
if !l.inputStream.IsOpen() {
17+
if err := l.inputStream.Open(); err != nil {
18+
return err
19+
}
20+
}
21+
22+
if err := rd.ListenTo(l.inputStream); err != nil {
23+
return err
24+
}
25+
26+
l.isListening = true
27+
return nil
28+
}
29+
30+
func (l *LaunchpadMK2) handleMidiMessage(pos *reader.Position, msg midi.Message) {
31+
l.listenerMutex.RLock()
32+
defer l.listenerMutex.RUnlock()
33+
34+
for i := range l.listener {
35+
//delegate to listener
36+
l.listener[i](pos, msg)
37+
}
38+
}
39+
40+
func (l *LaunchpadMK2) addMidiMessageListener(listener func(pos *reader.Position, msg midi.Message)) {
41+
l.listenerMutex.Lock()
42+
defer l.listenerMutex.Unlock()
43+
44+
l.listener = append(l.listener, listener)
45+
}
46+
47+
func (l *LaunchpadMK2) ListenToHits() (<-chan Hit, error) {
48+
if !l.isListening {
49+
if err := l.listen(); err != nil {
50+
return nil, err
51+
}
52+
}
53+
54+
hitChan := make(chan Hit) //unbuffered/blocking channel
55+
56+
l.addMidiMessageListener(func(pos *reader.Position, msg midi.Message) {
57+
isHit := false
58+
hit := Hit{
59+
X: 0,
60+
Y: 0,
61+
Down: false,
62+
}
63+
64+
if controlChange, ok := msg.(channel.ControlChange); ok {
65+
if controlChange.Controller() >= 104 && controlChange.Controller() <= 112 {
66+
isHit = true
67+
68+
hit.X = int(controlChange.Controller()) - 104
69+
hit.Y = 8
70+
hit.Down = controlChange.Value() == 127
71+
}
72+
} else if noteOn, ok := msg.(channel.NoteOn); ok {
73+
isHit = true
74+
hit.Down = true
75+
76+
hit.X = int(noteOn.Key())%10 - 1
77+
hit.Y = 8 - (int(noteOn.Key())-hit.X)/10
78+
} else if noteOn, ok := msg.(channel.NoteOff); ok {
79+
isHit = true
80+
hit.Down = false
81+
82+
hit.X = int(noteOn.Key())%10 - 1
83+
hit.Y = 8 - (int(noteOn.Key())-hit.X)/10
84+
}
85+
86+
if isHit {
87+
hitChan <- hit
88+
}
89+
})
90+
91+
return hitChan, nil
92+
}
93+
94+
func (l *LaunchpadMK2) ListenToScrollTextEndMarker() (<-chan interface{}, error) {
95+
if !l.isListening {
96+
if err := l.listen(); err != nil {
97+
return nil, err
98+
}
99+
}
100+
101+
markerChan := make(chan interface{}) //unbuffered/blocking channel
102+
103+
l.addMidiMessageListener(func(pos *reader.Position, msg midi.Message) {
104+
if controlChange, ok := msg.(channel.ControlChange); ok && controlChange.Value() == 3 {
105+
markerChan <- true
106+
}
107+
})
108+
109+
return markerChan, nil
110+
}

event.go renamed to event_s.go

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,7 @@ import (
66
"gitlab.com/gomidi/midi/reader"
77
)
88

9-
type Hit struct {
10-
X int
11-
Y int
12-
Down bool
13-
}
14-
15-
func (l *launchpad) listen() error {
9+
func (l *LaunchpadS) listen() error {
1610
rd := reader.New(
1711
reader.NoLogger(),
1812
// write every message to the out port
@@ -33,7 +27,7 @@ func (l *launchpad) listen() error {
3327
return nil
3428
}
3529

36-
func (l *launchpad) handleMidiMessage(pos *reader.Position, msg midi.Message) {
30+
func (l *LaunchpadS) handleMidiMessage(pos *reader.Position, msg midi.Message) {
3731
l.listenerMutex.RLock()
3832
defer l.listenerMutex.RUnlock()
3933

@@ -43,14 +37,14 @@ func (l *launchpad) handleMidiMessage(pos *reader.Position, msg midi.Message) {
4337
}
4438
}
4539

46-
func (l *launchpad) addMidiMessageListener(listener func(pos *reader.Position, msg midi.Message)) {
40+
func (l *LaunchpadS) addMidiMessageListener(listener func(pos *reader.Position, msg midi.Message)) {
4741
l.listenerMutex.Lock()
4842
defer l.listenerMutex.Unlock()
4943

5044
l.listener = append(l.listener, listener)
5145
}
5246

53-
func (l *launchpad) ListenToHits() (<-chan Hit, error) {
47+
func (l *LaunchpadS) ListenToHits() (<-chan Hit, error) {
5448
if !l.isListening {
5549
if err := l.listen(); err != nil {
5650
return nil, err
@@ -97,7 +91,7 @@ func (l *launchpad) ListenToHits() (<-chan Hit, error) {
9791
return hitChan, nil
9892
}
9993

100-
func (l *launchpad) ListenToScrollTextEndMarker() (<-chan interface{}, error) {
94+
func (l *LaunchpadS) ListenToScrollTextEndMarker() (<-chan interface{}, error) {
10195
if !l.isListening {
10296
if err := l.listen(); err != nil {
10397
return nil, err

0 commit comments

Comments
 (0)