Skip to content

Commit 9adefed

Browse files
committed
getting there....
1 parent cee4977 commit 9adefed

File tree

1 file changed

+228
-61
lines changed

1 file changed

+228
-61
lines changed

ndseq.go

Lines changed: 228 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -2,109 +2,276 @@ package main
22

33
import (
44
"context"
5-
"flag"
65
"fmt"
76
"os"
87
"os/signal"
8+
"strings"
99
"syscall"
1010

1111
"github.com/briansorahan/death"
1212
"github.com/pkg/errors"
13+
flag "github.com/spf13/pflag"
1314
"github.com/xthexder/go-jack"
1415
)
1516

17+
const (
18+
clientName = "ndseq" // JACK client name.
19+
)
20+
21+
// Error codes.
22+
const (
23+
DivideByZero = 50
24+
)
25+
1626
var (
1727
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.
2145
)
2246

2347
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()
2653

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
3155

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"))
3659

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"))
4063

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"))
4666

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"))
5269

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.
5971
bufferSize = client.GetBufferSize()
6072

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+
)
6878
signal.Notify(sc, os.Interrupt, syscall.SIGQUIT, syscall.SIGINT)
6979

7080
select {
7181
case <-ctx.Done():
72-
return ctx.Err()
82+
os.Exit(0)
7383
case sig := <-sc:
7484
fmt.Printf("received %s, exiting\n", sig)
75-
return nil
85+
os.Exit(0)
7686
}
77-
return nil
7887
}
7988

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)
83103
}
84104

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
93113
}
94114

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+
}
98137

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)
101141
}
102-
return 0
103142
}
104143

105144
func isFailure(code int) bool {
106145
return code == jack.Failure || code == jack.InvalidOption || code == jack.NameNotUnique ||
107146
code == jack.ServerError || code == jack.NoSuchClient || code == jack.LoadFailure ||
108147
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...)
110277
}

0 commit comments

Comments
 (0)