@@ -2,109 +2,276 @@ package main
2
2
3
3
import (
4
4
"context"
5
- "flag"
6
5
"fmt"
7
6
"os"
8
7
"os/signal"
8
+ "strings"
9
9
"syscall"
10
10
11
11
"github.com/briansorahan/death"
12
12
"github.com/pkg/errors"
13
+ flag "github.com/spf13/pflag"
13
14
"github.com/xthexder/go-jack"
14
15
)
15
16
17
+ const (
18
+ clientName = "ndseq" // JACK client name.
19
+ )
20
+
21
+ // Error codes.
22
+ const (
23
+ DivideByZero = 50
24
+ )
25
+
16
26
var (
17
27
bufferSize uint32
18
- client * jack.Client
19
- in * jack.Port
20
- out * jack.Port
28
+
29
+ client * jack.Client
30
+
31
+ launchpadInput * jack.Port // JACK port for sending MIDI data to the Launchpad.
32
+ launchpadOutput * jack.Port // JACK port for receiving MIDI data from the Launchpad.
33
+
34
+ nd string // Name of the MIDI interface to use for communicating with the Nord Drum 3p.
35
+ ndInput * jack.Port // JACK port for sending MIDI data to the Nord Drum 3p.
36
+ ndOutput * jack.Port // JACK port for receiving MIDI data from the Nord Drum 3p.
37
+
38
+ beat int // 64 steps
39
+ firstNotePlayed bool // Flag telling us if we've ever played a note.
40
+ sampleCount uint32 // Current sample count. This gets reset everytime we trigger a sequencer step.
41
+ samplesPerBeat uint32 // Samples per beat. Gets updated if the sample rate or the tempo changes.
42
+ tempo uint32 // Tempo in BPM.
43
+
44
+ trigs [8 ][64 ]uint8 // Launchpad grid data.
21
45
)
22
46
23
47
func main () {
24
- config , err := NewConfig ()
25
- death .Main (errors .Wrap (err , "parsing config" ))
48
+ // Parse the command line flags.
49
+ // I use a Focusrite Scarlett 6i6 to communicate with the Nord Drum.
50
+ flag .StringVar (& nd , "nd" , "Scarlett" , "JACK port for the Nord Drum 3p." )
51
+ flag .Uint32Var (& tempo , "t" , 120 , "Tempo in BPM." )
52
+ flag .Parse ()
26
53
27
- app , err := NewApp (config )
28
- death .Main (errors .Wrap (err , "creating app" ))
29
- death .Main (errors .Wrap (app .Run (context .Background ()), "running app" ))
30
- }
54
+ var code int
31
55
32
- // App defines the behavior of the application.
33
- type App struct {
34
- Config
35
- }
56
+ // Open the JACK client.
57
+ client , code = jack .ClientOpen (clientName , jack .NoStartServer )
58
+ death .Main (wrapCode (code , "opening JACK client" ))
36
59
37
- // NewApp creates a new app .
38
- func NewApp ( config Config ) ( * App , error ) {
39
- a := & App { Config : config }
60
+ // Set the callbacks .
61
+ death . Main ( wrapCode ( client . SetSampleRateCallback ( setSamplesPerBeat ), "setting sample rate callback" ))
62
+ death . Main ( wrapCode ( client . SetProcessCallback ( Process ), "setting process callback" ))
40
63
41
- jc , code := jack .ClientOpen (a .ClientName , jack .NoStartServer )
42
- if isFailure (code ) {
43
- return nil , jack .Strerror (code )
44
- }
45
- client = jc
64
+ // Register the JACK ports.
65
+ death .Main (errors .Wrap (registerPorts (), "registering ports" ))
46
66
47
- if code := client .SetProcessCallback (Process ); isFailure (code ) {
48
- return nil , jack .Strerror (code )
49
- }
50
- in = client .PortRegister ("in" , jack .DEFAULT_MIDI_TYPE , jack .PortIsInput , 0 )
51
- out = client .PortRegister ("out" , jack .DEFAULT_MIDI_TYPE , jack .PortIsOutput , 0 )
67
+ // Activate the client.
68
+ death .Main (wrapCode (client .Activate (), "activating JACK client" ))
52
69
53
- if in == nil {
54
- return nil , errors .New ("registering input port" )
55
- }
56
- if code := client .Activate (); isFailure (code ) {
57
- return nil , errors .New ("activating client" )
58
- }
70
+ // Set the buffer size.
59
71
bufferSize = client .GetBufferSize ()
60
72
61
- return a , nil
62
- }
63
-
64
- // Run runs the application.
65
- func (a * App ) Run (ctx context.Context ) error {
66
- sc := make (chan os.Signal , 1 )
67
-
73
+ // Wait for a signal or context done.
74
+ var (
75
+ ctx = context .Background ()
76
+ sc = make (chan os.Signal , 1 )
77
+ )
68
78
signal .Notify (sc , os .Interrupt , syscall .SIGQUIT , syscall .SIGINT )
69
79
70
80
select {
71
81
case <- ctx .Done ():
72
- return ctx . Err ( )
82
+ os . Exit ( 0 )
73
83
case sig := <- sc :
74
84
fmt .Printf ("received %s, exiting\n " , sig )
75
- return nil
85
+ os . Exit ( 0 )
76
86
}
77
- return nil
78
87
}
79
88
80
- // Config defines the application's configuration.
81
- type Config struct {
82
- ClientName string `json:"clientName"`
89
+ // Process is the JACK process callback.
90
+ func Process (nframes uint32 ) int {
91
+ var (
92
+ launchpadEvents = launchpadInput .GetMidiEvents (bufferSize )
93
+ outBuffer = ndOutput .MidiClearBuffer (nframes )
94
+ )
95
+ if len (launchpadEvents ) > 0 {
96
+ for _ , event := range launchpadEvents {
97
+ if code := processMidi (nframes , event , outBuffer ); code != 0 {
98
+ return code
99
+ }
100
+ }
101
+ }
102
+ return tick (nframes , outBuffer )
83
103
}
84
104
85
- // NewConfig creates a new config.
86
- func NewConfig () ( Config , error ) {
87
- var c Config
88
-
89
- flag . StringVar ( & c . ClientName , "c" , "ndseq" , "JACK client name." )
90
- flag . Parse ()
91
-
92
- return c , nil
105
+ func advanceStepLight ( outBuffer jack. MidiBuffer ) int {
106
+ if beat == 0 && ! firstNotePlayed {
107
+ // First note ever: light step 0.
108
+ beat ++
109
+ return launchpadOutput . MidiEventWrite ( & jack. MidiData { Buffer : [] byte { 0x90 , 0x10 , 63 }}, outBuffer )
110
+ }
111
+ beat ++
112
+ return 0
93
113
}
94
114
95
- // Process is the JACK process callback.
96
- func Process (nframes uint32 ) int {
97
- events := in .GetMidiEvents (bufferSize )
115
+ func cc (nframes uint32 , in []byte , outBuffer jack.MidiBuffer ) int {
116
+ var (
117
+ event = jack.MidiData {
118
+ Buffer : []byte {0x90 , 0x36 , in [2 ]}, // Note On C3
119
+ }
120
+ )
121
+ // Set the output channel.
122
+ switch in [1 ] {
123
+ case 0x68 : // Button 1
124
+ case 0x69 : // Button 2
125
+ event .Buffer [0 ] |= 0x01
126
+ case 0x6A : // Button 3
127
+ event .Buffer [0 ] |= 0x02
128
+ case 0x6B : // Button 4
129
+ event .Buffer [0 ] |= 0x03
130
+ case 0x6C : // Button 5
131
+ event .Buffer [0 ] |= 0x04
132
+ case 0x6D : // Button 6
133
+ event .Buffer [0 ] |= 0x05
134
+ }
135
+ return ndOutput .MidiEventWrite (& event , outBuffer )
136
+ }
98
137
99
- for _ , event := range events {
100
- fmt .Printf ("midi event %X\n " , event .Buffer )
138
+ func contains (sub string ) func (string ) bool {
139
+ return func (s string ) bool {
140
+ return strings .Contains (s , sub )
101
141
}
102
- return 0
103
142
}
104
143
105
144
func isFailure (code int ) bool {
106
145
return code == jack .Failure || code == jack .InvalidOption || code == jack .NameNotUnique ||
107
146
code == jack .ServerError || code == jack .NoSuchClient || code == jack .LoadFailure ||
108
147
code == jack .InitFailure || code == jack .ShmFailure || code == jack .VersionError ||
109
- code == jack .BackendError || code == jack .ClientZombie
148
+ code == jack .BackendError || code == jack .ClientZombie || code == DivideByZero
149
+ }
150
+
151
+ func light (x , y , g , r int , outBuffer jack.MidiBuffer ) int {
152
+ var (
153
+ note = byte (x + (16 * y ))
154
+ velocity = byte ((16 * g ) + r + 8 + 4 )
155
+ )
156
+ return ndOutput .MidiEventWrite (& jack.MidiData {Buffer : []byte {0x90 , note , velocity }}, outBuffer )
157
+
158
+ }
159
+
160
+ func note (nframes uint32 , in []byte , out jack.MidiBuffer ) int {
161
+ return 0
162
+ }
163
+
164
+ type Port struct {
165
+ * jack.Port
166
+
167
+ Matches func (string ) bool
168
+ Flags uint64
169
+ BufferSize uint64
170
+ }
171
+
172
+ var Ports = struct {
173
+ Inputs map [string ]* Port
174
+ Outputs map [string ]* Port
175
+ }{
176
+ Inputs : map [string ]* Port {
177
+ "LaunchpadRecv" : {
178
+ Matches : contains ("Launchpad" ),
179
+ },
180
+ "NordDrumRecv" : {
181
+ Matches : contains ("Scarlett" ),
182
+ },
183
+ },
184
+ Outputs : map [string ]* Port {
185
+ "LaunchpadSend" : {
186
+ Matches : contains ("Launchpad" ),
187
+ },
188
+ "NordDrumSend" : {
189
+ Matches : contains ("Scarlett" ),
190
+ },
191
+ },
192
+ }
193
+
194
+ func registerPorts () error {
195
+ for name , input := range Ports .Inputs {
196
+ input .Port = client .PortRegister (name , jack .DEFAULT_MIDI_TYPE , input .Flags | jack .PortIsInput , input .BufferSize )
197
+ }
198
+ for name , output := range Ports .Outputs {
199
+ output .Port = client .PortRegister (name , jack .DEFAULT_MIDI_TYPE , output .Flags | jack .PortIsOutput , output .BufferSize )
200
+ }
201
+ for _ , in := range client .GetPorts ("" , jack .DEFAULT_MIDI_TYPE , jack .PortIsInput ) {
202
+ for name , out := range Ports .Outputs {
203
+ if out .Matches (in ) {
204
+ rc := client .ConnectPorts (out .Port , client .GetPortByName (in ))
205
+ if err := wrapCodef (rc , "connecting %s to %s" , name , in ); err != nil {
206
+ return err
207
+ }
208
+ }
209
+ }
210
+ }
211
+ return nil
212
+ }
213
+
214
+ func processMidi (nframes uint32 , event * jack.MidiData , outBuffer jack.MidiBuffer ) int {
215
+ switch event .Buffer [0 ] {
216
+ case 0xB0 : // CC
217
+ return cc (nframes , event .Buffer , outBuffer )
218
+ case 0x80 , 0x90 : // Note
219
+ return note (nframes , event .Buffer , outBuffer )
220
+ }
221
+ return 0
222
+ }
223
+
224
+ func setSamplesPerBeat (sr uint32 ) int {
225
+ if tempo == 0 {
226
+ return DivideByZero
227
+ }
228
+ samplesPerBeat = (60 * sr ) / tempo
229
+ return 0
230
+ }
231
+
232
+ func stepLightMidiData (beat int ) * jack.MidiData {
233
+ var (
234
+ note = byte ((beat / 8 ) + (16 * (beat % 8 )))
235
+ )
236
+ return & jack.MidiData {Buffer : []byte {0x90 , note , 63 }}
237
+ }
238
+
239
+ func tick (nframes uint32 , outBuffer jack.MidiBuffer ) int {
240
+ if ! firstNotePlayed {
241
+ if code := advanceStepLight (outBuffer ); isFailure (code ) {
242
+ return code
243
+ }
244
+ firstNotePlayed = true
245
+
246
+ return trigger (nframes , outBuffer )
247
+ }
248
+ if sampleCount + nframes < samplesPerBeat {
249
+ sampleCount += nframes
250
+ return 0
251
+ }
252
+ return trigger (nframes , outBuffer )
253
+ }
254
+
255
+ func trigger (nframes uint32 , outBuffer jack.MidiBuffer ) int {
256
+ for track , trackTrigs := range trigs {
257
+ for _ , trig := range trackTrigs {
258
+ // TODO: trigger the notes.
259
+ if code := triggerTrack (track , trig , outBuffer ); isFailure (code ) {
260
+ return code
261
+ }
262
+ }
263
+ }
264
+ return 0
265
+ }
266
+
267
+ func triggerTrack (track int , trig uint8 , outBuffer jack.MidiBuffer ) int {
268
+ return 0
269
+ }
270
+
271
+ func wrapCode (code int , msg string ) error {
272
+ return errors .Wrap (jack .Strerror (code ), msg )
273
+ }
274
+
275
+ func wrapCodef (code int , format string , args ... interface {}) error {
276
+ return errors .Wrapf (jack .Strerror (code ), format , args ... )
110
277
}
0 commit comments