Skip to content

Commit dfadd42

Browse files
authored
Enumerate devices darwin (#4)
* first pass at Devices with CoreMIDI * clean up a few linting errors * darwin: free endpoints and midi struct pointer in Midi_close * darwin: defer CFRelease of the device name in Devices * add Err to Packet
1 parent bd7e751 commit dfadd42

File tree

6 files changed

+133
-90
lines changed

6 files changed

+133
-90
lines changed

launchpad_test.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,19 @@ func TestLaunchpad(t *testing.T) {
1515
// For the launchpad MIDI reference, see https://d19ulaff0trnck.cloudfront.net/sites/default/files/novation/downloads/4080/launchpad-programmers-reference.pdf
1616
t.SkipNow()
1717

18-
device := &Device{ID: "hw:0"}
18+
devices, err := Devices()
19+
if err != nil {
20+
t.Fatal(err)
21+
}
22+
var device *Device
23+
for _, d := range devices {
24+
if d.Name == "Launchpad Mini" {
25+
device = d
26+
}
27+
}
28+
if device == nil {
29+
t.Fatal("getting device Launchpad Mini")
30+
}
1931
if err := device.Open(); err != nil {
2032
t.Fatal(err)
2133
}
@@ -44,6 +56,9 @@ func TestLaunchpad(t *testing.T) {
4456

4557
fmt.Println("waiting for packet")
4658
packet := <-packets
59+
if packet.Err != nil {
60+
t.Fatal(err)
61+
}
4762
fmt.Printf("packet %#v\n", packet)
4863

4964
if err := device.Close(); err != nil {

midi.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Package midi is a package for talking to midi devices in Go.
2+
package midi
3+
4+
// Packet is a MIDI packet.
5+
type Packet struct {
6+
Data [3]byte
7+
Err error
8+
}
9+
10+
// DeviceType is a flag that says if a device is an input, an output, or duplex.
11+
type DeviceType int
12+
13+
// Device types.
14+
const (
15+
DeviceInput DeviceType = iota
16+
DeviceOutput
17+
DeviceDuplex
18+
)

midi_darwin.c

Lines changed: 19 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@
1212

1313
extern void SendPacket(Midi midi, unsigned char c1, unsigned char c2, unsigned char c3);
1414

15-
Midi_device_endpoints find_device_endpoints(const char *device);
16-
char *CFStringToUTF8(CFStringRef aString);
17-
1815
// Midi represents a MIDI connection that uses the ALSA RawMidi API.
1916
struct Midi {
2017
MIDIClientRef client;
@@ -26,19 +23,14 @@ struct Midi {
2623

2724
// Midi_open opens a MIDI connection to the specified device.
2825
// If there is an error it returns NULL.
29-
Midi_open_result Midi_open(const char *name) {
30-
Midi midi;
31-
OSStatus rc;
26+
Midi_open_result Midi_open(MIDIEndpointRef input, MIDIEndpointRef output) {
27+
Midi midi;
28+
OSStatus rc;
3229

3330
NEW(midi);
3431

35-
// Read input and output endpoints.
36-
Midi_device_endpoints device_endpoints = find_device_endpoints(name);
37-
if (device_endpoints.error != 0) {
38-
return (Midi_open_result) { .midi = NULL, .error = device_endpoints.error };
39-
}
40-
midi->input = device_endpoints.input;
41-
midi->output = device_endpoints.output;
32+
midi->input = input;
33+
midi->output = output;
4234

4335
rc = MIDIClientCreate(CFSTR("scgolang"), NULL, NULL, &midi->client);
4436
if (rc != 0) {
@@ -52,7 +44,7 @@ Midi_open_result Midi_open(const char *name) {
5244
if (rc != 0) {
5345
return (Midi_open_result) { .midi = NULL, .error = rc };
5446
}
55-
rc = MIDIPortConnectSource(midi->inputPort, midi->input, midi);
47+
rc = MIDIPortConnectSource(midi->inputPort, input, midi);
5648
if (rc != 0) {
5749
return (Midi_open_result) { .midi = NULL, .error = rc };
5850
}
@@ -102,63 +94,34 @@ Midi_write_result Midi_write(Midi midi, const char *buffer, size_t buffer_size)
10294
int Midi_close(Midi midi) {
10395
assert(midi);
10496

105-
OSStatus rc1, rc2, rc3;
97+
OSStatus rc1, rc2, rc3, rc4, rc5;
10698

10799
rc1 = MIDIPortDispose(midi->inputPort);
108100
rc2 = MIDIPortDispose(midi->outputPort);
109101
rc3 = MIDIClientDispose(midi->client);
102+
rc4 = MIDIEndpointDispose(midi->input);
103+
rc5 = MIDIEndpointDispose(midi->output);
104+
105+
FREE(midi);
110106

111107
if (rc1 != 0) return rc1;
112108
else if (rc2 != 0) return rc2;
113109
else if (rc3 != 0) return rc3;
110+
else if (rc4 != 0) return rc4;
111+
else if (rc5 != 0) return rc5;
114112
else return 0;
115113
}
116114

117-
Midi_device_endpoints find_device_endpoints(const char *name) {
118-
ItemCount numDevices = MIDIGetNumberOfDevices();
119-
OSStatus rc;
120-
121-
for (int i = 0; i < numDevices; i++) {
122-
CFStringRef deviceName;
123-
MIDIDeviceRef deviceRef = MIDIGetDevice(i);
124-
125-
rc = MIDIObjectGetStringProperty(deviceRef, kMIDIPropertyName, &deviceName);
126-
if (rc != 0) {
127-
return (Midi_device_endpoints) { .device = 0, .input = 0, .output = 0, .error = rc };
128-
}
129-
if (strcmp(CFStringToUTF8(deviceName), name) != 0) {
130-
continue;
131-
}
132-
ItemCount numEntities = MIDIDeviceGetNumberOfEntities(deviceRef);
133-
134-
for (int i = 0; i < numEntities; i++) {
135-
MIDIEntityRef entityRef = MIDIDeviceGetEntity(deviceRef, i);
136-
ItemCount numDestinations = MIDIGetNumberOfDestinations(entityRef);
137-
ItemCount numSources = MIDIGetNumberOfSources(entityRef);
138-
139-
if (numDestinations < 1 || numSources < 1) {
140-
continue;
141-
}
142-
MIDIEndpointRef input = MIDIGetSource(0);
143-
MIDIEndpointRef output = MIDIGetDestination(0);
144-
145-
return (Midi_device_endpoints) { .device = deviceRef, .input = input, .output = output, .error = 0 };
146-
}
147-
}
148-
return (Midi_device_endpoints) { .device = 0, .input = 0, .output = 0, .error = -10901 };
149-
}
150-
115+
// CFStringToUTF8 converts a CoreFoundation string to a UTF-encoded C string.
116+
// Callers are responsible for free'ing the returned string.
151117
char *CFStringToUTF8(CFStringRef aString) {
152118
if (aString == NULL) {
153119
return NULL;
154120
}
155-
156-
CFIndex length = CFStringGetLength(aString);
157-
CFIndex maxSize =
158-
CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
159-
char *buffer = (char *)malloc(maxSize);
160-
if (CFStringGetCString(aString, buffer, maxSize,
161-
kCFStringEncodingUTF8)) {
121+
CFIndex length = CFStringGetLength(aString);
122+
CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8) + 1;
123+
char *buffer = (char *)malloc(maxSize);
124+
if (CFStringGetCString(aString, buffer, maxSize, kCFStringEncodingUTF8)) {
162125
return buffer;
163126
}
164127
free(buffer); // If we failed

midi_darwin.go

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
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.
31
package midi
42

53
// #include <stddef.h>
@@ -11,37 +9,39 @@ import "C"
119

1210
import (
1311
"sync"
12+
"unsafe"
1413

1514
"github.com/pkg/errors"
1615
)
1716

1817
// Common errors.
1918
var (
20-
ErrDeviceNotFound = errors.New("device not found, did you open the device?")
19+
ErrNotOpen = errors.New("Did you remember to open the device?")
2120
)
2221

23-
// Packet is a MIDI packet.
24-
type Packet [3]byte
25-
2622
var (
2723
packetChans = map[*Device]chan Packet{}
2824
packetChansMutex sync.RWMutex
2925
)
3026

3127
// Device provides an interface for MIDI devices.
3228
type Device struct {
29+
ID string
3330
Name string
31+
Type DeviceType
3432

3533
// QueueSize controls the buffer size of the read channel. Use 0 for blocking reads.
3634
QueueSize int
3735

38-
conn C.Midi
36+
conn C.Midi
37+
input C.MIDIEndpointRef
38+
output C.MIDIEndpointRef
3939
}
4040

4141
// Open opens a MIDI device.
4242
// queueSize is the number of packets to buffer in the channel associated with the device.
4343
func (d *Device) Open() error {
44-
result := C.Midi_open(C.CString(d.Name))
44+
result := C.Midi_open(d.input, d.output)
4545
if result.error != 0 {
4646
return coreMidiError(result.error)
4747
}
@@ -58,6 +58,7 @@ func (d *Device) Close() error {
5858
}
5959

6060
// Packets emits MIDI packets.
61+
// If the device has not been opened it will return ErrNotOpen.
6162
func (d *Device) Packets() (<-chan Packet, error) {
6263
packetChansMutex.RLock()
6364
for device, packetChan := range packetChans {
@@ -67,7 +68,7 @@ func (d *Device) Packets() (<-chan Packet, error) {
6768
}
6869
}
6970
packetChansMutex.RUnlock()
70-
return nil, ErrDeviceNotFound
71+
return nil, ErrNotOpen
7172
}
7273

7374
// Write writes data to a MIDI device.
@@ -84,7 +85,9 @@ func SendPacket(conn C.Midi, c1 C.uchar, c2 C.uchar, c3 C.uchar) {
8485
packetChansMutex.RLock()
8586
for device, packetChan := range packetChans {
8687
if device.conn == conn {
87-
packetChan <- Packet{byte(c1), byte(c2), byte(c3)}
88+
packetChan <- Packet{
89+
Data: [3]byte{byte(c1), byte(c2), byte(c3)},
90+
}
8891
}
8992
}
9093
packetChansMutex.RUnlock()
@@ -141,3 +144,61 @@ func coreMidiError(code C.OSStatus) error {
141144
return errors.Errorf("unknown CoreMIDI error: %d", code)
142145
}
143146
}
147+
148+
// Devices returns a list of devices.
149+
func Devices() ([]*Device, error) {
150+
var (
151+
maxEndpoints C.ItemCount
152+
devices = []*Device{}
153+
numDestinations C.ItemCount = C.MIDIGetNumberOfDestinations()
154+
numSources C.ItemCount = C.MIDIGetNumberOfSources()
155+
)
156+
if numDestinations > numSources {
157+
maxEndpoints = numDestinations
158+
} else {
159+
maxEndpoints = numSources
160+
}
161+
for i := C.ItemCount(0); i < maxEndpoints; i++ {
162+
var (
163+
d *Device
164+
obj C.MIDIObjectRef
165+
)
166+
if i < numDestinations && i < numSources {
167+
d = &Device{
168+
Type: DeviceDuplex,
169+
input: C.MIDIGetSource(i),
170+
output: C.MIDIGetDestination(i),
171+
}
172+
obj = C.MIDIObjectRef(d.output)
173+
} else if i < numDestinations {
174+
d = &Device{
175+
Type: DeviceOutput,
176+
output: C.MIDIGetDestination(i),
177+
}
178+
obj = C.MIDIObjectRef(d.output)
179+
} else {
180+
d = &Device{
181+
Type: DeviceInput,
182+
input: C.MIDIGetSource(i),
183+
}
184+
obj = C.MIDIObjectRef(d.input)
185+
}
186+
var name C.CFStringRef
187+
if rc := C.MIDIObjectGetStringProperty(obj, C.kMIDIPropertyName, &name); rc != 0 {
188+
return nil, coreMidiError(rc)
189+
}
190+
d.Name = fromCFString(name)
191+
C.CFRelease(C.CFTypeRef(name))
192+
devices = append(devices, d)
193+
}
194+
return devices, nil
195+
}
196+
197+
func fromCFString(cfs C.CFStringRef) string {
198+
var (
199+
cs = C.CFStringToUTF8(cfs)
200+
gs = C.GoString(cs)
201+
)
202+
C.free(unsafe.Pointer(cs))
203+
return gs
204+
}

midi_darwin.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ typedef struct Midi_device_endpoints {
3030
} Midi_device_endpoints;
3131

3232
// Midi_open opens a MIDI connection to the specified device.
33-
Midi_open_result Midi_open(const char *name);
33+
Midi_open_result Midi_open(MIDIEndpointRef input, MIDIEndpointRef output);
3434

3535
// Midi_read_proc is the callback that gets invoked when MIDI data comes in.
3636
void Midi_read_proc(const MIDIPacketList *pkts, void *readProcRefCon, void *srcConnRefCon);
@@ -47,4 +47,7 @@ Midi_write_result Midi_write(Midi midi, const char *buffer, size_t buffer_size);
4747
// Midi_close closes a MIDI connection.
4848
int Midi_close(Midi midi);
4949

50+
// CFStringToUTF8 converts a CFStringRef to a UTF8-encoded C string.
51+
char *CFStringToUTF8(CFStringRef aString);
52+
5053
#endif // MIDI_DARWIN_H defined

midi_linux.go

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
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.
31
package midi
42

53
// #include <alsa/asoundlib.h>
@@ -11,24 +9,11 @@ import "C"
119

1210
import (
1311
"fmt"
14-
"os"
1512
"unsafe"
1613

1714
"github.com/pkg/errors"
1815
)
1916

20-
// Packet is a MIDI packet.
21-
type Packet [3]byte
22-
23-
// DeviceType is a flag that says if a device is an input, an output, or duplex.
24-
type DeviceType int
25-
26-
const (
27-
DeviceInput DeviceType = iota
28-
DeviceOutput
29-
DeviceDuplex
30-
)
31-
3217
// Device provides an interface for MIDI devices.
3318
type Device struct {
3419
ID string
@@ -69,11 +54,12 @@ func (d *Device) Packets() (<-chan Packet, error) {
6954
go func() {
7055
for {
7156
if _, err := d.Read(buf); err != nil {
72-
fmt.Fprintf(os.Stderr, "could not read from device: %s", err)
73-
close(ch)
57+
ch <- Packet{Err: err}
7458
return
7559
}
76-
ch <- Packet{buf[0], buf[1], buf[2]}
60+
ch <- Packet{
61+
Data: [3]byte{buf[0], buf[1], buf[2]},
62+
}
7763
}
7864
}()
7965
return ch, nil
@@ -98,10 +84,7 @@ func (d *Device) Write(buf []byte) (int, error) {
9884
return int(n), err
9985
}
10086

101-
type Stream struct {
102-
Name string
103-
}
104-
87+
// Devices returns a list of devices.
10588
func Devices() ([]*Device, error) {
10689
var card C.int = -1
10790

0 commit comments

Comments
 (0)