Skip to content

Commit dd30ddb

Browse files
committed
initial commit. linux-only for now
0 parents  commit dd30ddb

File tree

6 files changed

+213
-0
lines changed

6 files changed

+213
-0
lines changed

mem.c

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// +build cgo
2+
#include <assert.h>
3+
#include <stddef.h>
4+
#include <stdlib.h>
5+
6+
#include "mem.h"
7+
8+
void *midi_mem_alloc(long nbytes, const char *file, int line) {
9+
void *p;
10+
assert(nbytes > 0);
11+
p = malloc(nbytes);
12+
// crash if oom
13+
assert(p != NULL);
14+
return p;
15+
}
16+
17+
void *midi_mem_calloc(int count, long nbytes, const char *file, int line) {
18+
void *p;
19+
assert(count > 0);
20+
assert(nbytes > 0);
21+
p = calloc(count, nbytes);
22+
assert(p != NULL);
23+
return p;
24+
}
25+
26+
void midi_mem_free(void * ptr, const char *file, int line) {
27+
if (ptr)
28+
free(ptr);
29+
}
30+
31+
void *midi_mem_resize(void *ptr, long nbytes, const char *file, int line) {
32+
assert(ptr);
33+
assert(nbytes > 0);
34+
ptr = realloc(ptr, nbytes);
35+
assert(ptr != NULL);
36+
return ptr;
37+
}
38+

mem.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#ifndef MEM_H_INCLUDED
2+
#define MEM_H_INCLUDED
3+
4+
extern void *midi_mem_alloc(long nbytes, const char * file, int line);
5+
extern void *midi_mem_calloc(int count, long nbytes, const char * file, int line);
6+
extern void midi_mem_free(void *ptr, const char *file, int line);
7+
extern void *midi_mem_resize(void *ptr, long nbytes, const char *file, int line);
8+
9+
#define ALLOC(nbytes) midi_mem_alloc((nbytes), __FILE__, __LINE__)
10+
#define CALLOC(count, nbytes) midi_mem_calloc((count), (nbytes), __FILE__, __LINE__)
11+
12+
#define NEW(p) ((p) = ALLOC((long) sizeof *(p)))
13+
#define NEW0(p) ((p) = CALLOC(1, (long) sizeof *(p)))
14+
#define FREE(p) ((void) (midi_mem_free((p), __FILE__, __LINE__), (p) = 0))
15+
#define RESIZE(p, nbytes) ((p) = midi_mem_resize((p), (nbytes), __FILE__, __LINE__))
16+
17+
#endif // MEM_H_INCLUDED

midi.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Package midi is a self-contained (i.e. doesn't depend on a C library)
2+
// package for talking to midi devices in Go.
3+
package midi
4+
5+
// #include <stddef.h>
6+
// #include <stdlib.h>
7+
// #include "midi.h"
8+
// #cgo linux LDFLAGS: -lasound
9+
import "C"
10+
11+
// Midi provides an interface for raw MIDI devices.
12+
type Midi struct {
13+
conn C.Midi
14+
buf []byte
15+
}
16+
17+
// Open opens a MIDI device.
18+
func Open(device string) (*Midi, error) {
19+
conn := C.Midi_open(C.CString(device))
20+
return &Midi{conn: conn}, nil
21+
}
22+
23+
// Close closes the MIDI connection.
24+
func (midi *Midi) Close() error {
25+
return nil
26+
}
27+
28+
// Read reads data from a MIDI device.
29+
func (midi *Midi) Read(buf []byte) (int, error) {
30+
n, err := C.Midi_read(midi.conn, C.CString(string(buf)), C.size_t(len(buf)))
31+
return int(n), err
32+
}
33+
34+
// Write writes data to a MIDI device.
35+
func (midi *Midi) Write(buf []byte) (int, error) {
36+
n, err := C.Midi_write(midi.conn, C.CString(string(buf)), C.size_t(len(buf)))
37+
return int(n), err
38+
}

midi.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// +build cgo
2+
#ifndef MIDI_H
3+
#define MIDI_H
4+
5+
#include <stddef.h>
6+
#include <stdlib.h>
7+
8+
// Midi represents a connection to a MIDI device.
9+
typedef struct Midi *Midi;
10+
11+
// Midi_open opens a MIDI connection to the specified device.
12+
Midi Midi_open(const char *device_name);
13+
14+
// Midi_read reads bytes from the provided MIDI connection.
15+
ssize_t Midi_read(Midi midi, char *buffer, size_t buffer_size);
16+
17+
// Midi_write writes bytes to the provided MIDI connection.
18+
ssize_t Midi_write(Midi midi, const char *buffer, size_t buffer_size);
19+
20+
// Midi_close closes a MIDI connection.
21+
int Midi_close(Midi midi);
22+
23+
#endif

midi_linux.c

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// +build cgo
2+
#include <assert.h>
3+
#include <errno.h>
4+
#include <stddef.h>
5+
6+
#include <alsa/asoundlib.h>
7+
8+
#include "mem.h"
9+
#include "midi.h"
10+
11+
// Midi represents a MIDI connection that uses the ALSA RawMidi API.
12+
struct Midi {
13+
snd_rawmidi_t *in;
14+
snd_rawmidi_t *out;
15+
};
16+
17+
// Midi_open opens a MIDI connection to the specified device.
18+
// If there is an error it returns NULL.
19+
Midi Midi_open(const char *device_name) {
20+
Midi midi;
21+
int rc;
22+
NEW(midi);
23+
rc = snd_rawmidi_open(&midi->in, &midi->out, device_name, SND_RAWMIDI_SYNC);
24+
errno = rc; // Not sure if the rawmidi return codes map to errno values.
25+
if (rc != 0) return NULL;
26+
return midi;
27+
}
28+
29+
// Midi_read reads bytes from the provided MIDI connection.
30+
ssize_t Midi_read(Midi midi, char *buffer, size_t buffer_size) {
31+
assert(midi);
32+
assert(midi->in);
33+
return snd_rawmidi_read(midi->in, (void *) buffer, buffer_size);
34+
}
35+
36+
// Midi_write writes bytes to the provided MIDI connection.
37+
ssize_t Midi_write(Midi midi, const char *buffer, size_t buffer_size) {
38+
assert(midi);
39+
assert(midi->out);
40+
return snd_rawmidi_write(midi->out, (void *) buffer, buffer_size);
41+
}
42+
43+
// Midi_close closes a MIDI connection.
44+
int Midi_close(Midi midi) {
45+
assert(midi);
46+
assert(midi->in);
47+
assert(midi->out);
48+
49+
int inrc, outrc;
50+
51+
inrc = snd_rawmidi_close(midi->in);
52+
outrc = snd_rawmidi_close(midi->out);
53+
54+
if (inrc != 0) {
55+
return inrc;
56+
}
57+
return outrc;
58+
}

midi_linux_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package midi
2+
3+
import (
4+
"testing"
5+
"time"
6+
)
7+
8+
func TestLaunchpad(t *testing.T) {
9+
// This test can be run if you have a MIDI device with ID "hw:1,0,0"
10+
// It will tell you if this package is able to talk to your device.
11+
// The messages that are sent here are specific to the Novation Launchpad Mini.
12+
// The reason this package exists is because of issues that popped up when
13+
// trying to use github.com/rakyll/portmidi to talk to the launchpad on Linux.
14+
// For the launchpad MIDI reference, see https://d19ulaff0trnck.cloudfront.net/sites/default/files/novation/downloads/4080/launchpad-programmers-reference.pdf
15+
// t.SkipNow()
16+
17+
conn, err := Open("hw:1,0,0")
18+
if err != nil {
19+
t.Fatal(err)
20+
}
21+
if _, err := conn.Write([]byte{0xB0, 0x00, 0x00}); err != nil {
22+
t.Fatal(err)
23+
}
24+
if _, err := conn.Write([]byte{0xB0, 0x00, 0x7D}); err != nil {
25+
t.Fatal(err)
26+
}
27+
28+
time.Sleep(2 * time.Second)
29+
30+
if _, err := conn.Write([]byte{0xB0, 0x00, 0x00}); err != nil {
31+
t.Fatal(err)
32+
}
33+
34+
// Test hangs here until you send some MIDI data!
35+
buf := make([]byte, 3)
36+
if _, err := conn.Read(buf); err != nil {
37+
t.Fatal(err)
38+
}
39+
}

0 commit comments

Comments
 (0)