Skip to content

Commit 4dfd805

Browse files
authored
Merge pull request #3 from scgolang/enumerate-devices
Enumerate devices on linux
2 parents 2c1442e + 1cf1cc2 commit 4dfd805

File tree

4 files changed

+194
-8
lines changed

4 files changed

+194
-8
lines changed

launchpad_test.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,7 @@ 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{
19-
Name: "hw:0,0,0",
20-
QueueSize: 0,
21-
}
18+
device := &Device{ID: "hw:0"}
2219
if err := device.Open(); err != nil {
2320
t.Fatal(err)
2421
}

list_test.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package midi
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
)
7+
8+
func TestDevices(t *testing.T) {
9+
devices, err := Devices()
10+
if err != nil {
11+
t.Fatal(err)
12+
}
13+
for i, d := range devices {
14+
if d == nil {
15+
continue
16+
}
17+
fmt.Printf("device %d: %#v\n", i, *d)
18+
}
19+
}

midi_linux.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// +build cgo
22
#include <assert.h>
3-
#include <errno.h>
43
#include <stddef.h>
54

65
#include <alsa/asoundlib.h>

midi_linux.go

Lines changed: 174 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// package for talking to midi devices in Go.
33
package midi
44

5+
// #include <alsa/asoundlib.h>
56
// #include <stddef.h>
67
// #include <stdlib.h>
78
// #include "midi_linux.h"
@@ -11,25 +12,41 @@ import "C"
1112
import (
1213
"fmt"
1314
"os"
15+
"unsafe"
1416

1517
"github.com/pkg/errors"
1618
)
1719

1820
// Packet is a MIDI packet.
1921
type Packet [3]byte
2022

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+
2132
// Device provides an interface for MIDI devices.
2233
type Device struct {
34+
ID string
2335
Name string
2436
QueueSize int
37+
Type DeviceType
2538

2639
conn C.Midi
27-
buf []byte
2840
}
2941

3042
// Open opens a MIDI device.
3143
func (d *Device) Open() error {
32-
result := C.Midi_open(C.CString(d.Name))
44+
var (
45+
id = C.CString(d.ID)
46+
result = C.Midi_open(id)
47+
)
48+
defer C.free(unsafe.Pointer(id))
49+
3350
if result.error != 0 {
3451
return errors.Errorf("error opening device %d", result.error)
3552
}
@@ -75,6 +92,160 @@ func (d *Device) Read(buf []byte) (int, error) {
7592

7693
// Write writes data to a MIDI device.
7794
func (d *Device) Write(buf []byte) (int, error) {
78-
n, err := C.Midi_write(d.conn, C.CString(string(buf)), C.size_t(len(buf)))
95+
cs := C.CString(string(buf))
96+
n, err := C.Midi_write(d.conn, cs, C.size_t(len(buf)))
97+
C.free(unsafe.Pointer(cs))
7998
return int(n), err
8099
}
100+
101+
type Stream struct {
102+
Name string
103+
}
104+
105+
func Devices() ([]*Device, error) {
106+
var card C.int = -1
107+
108+
if rc := C.snd_card_next(&card); rc != 0 {
109+
return nil, alsaMidiError(rc)
110+
}
111+
if card < 0 {
112+
return nil, errors.New("no sound card found")
113+
}
114+
devices := []*Device{}
115+
116+
for {
117+
cardDevices, err := getCardDevices(card)
118+
if err != nil {
119+
return nil, err
120+
}
121+
if rc := C.snd_card_next(&card); rc != 0 {
122+
return nil, alsaMidiError(rc)
123+
}
124+
if card < 0 {
125+
break
126+
}
127+
devices = append(devices, cardDevices...)
128+
}
129+
return devices, nil
130+
}
131+
132+
func getCardDevices(card C.int) ([]*Device, error) {
133+
var (
134+
ctl *C.snd_ctl_t
135+
name = C.CString(fmt.Sprintf("hw:%d", card))
136+
)
137+
defer C.free(unsafe.Pointer(name))
138+
139+
if rc := C.snd_ctl_open(&ctl, name, 0); rc != 0 {
140+
return nil, alsaMidiError(rc)
141+
}
142+
var (
143+
cardDevices = []*Device{}
144+
device C.int = -1
145+
)
146+
for {
147+
if rc := C.snd_ctl_rawmidi_next_device(ctl, &device); rc != 0 {
148+
return nil, alsaMidiError(rc)
149+
}
150+
if device < 0 {
151+
break
152+
}
153+
deviceDevices, err := getDeviceDevices(ctl, card, C.uint(device))
154+
if err != nil {
155+
return nil, err
156+
}
157+
cardDevices = append(cardDevices, deviceDevices...)
158+
}
159+
if rc := C.snd_ctl_close(ctl); rc != 0 {
160+
return nil, alsaMidiError(rc)
161+
}
162+
return cardDevices, nil
163+
}
164+
165+
func getDeviceDevices(ctl *C.snd_ctl_t, card C.int, device C.uint) ([]*Device, error) {
166+
var info *C.snd_rawmidi_info_t
167+
C.snd_rawmidi_info_malloc(&info)
168+
C.snd_rawmidi_info_set_device(info, device)
169+
170+
defer C.snd_rawmidi_info_free(info)
171+
172+
// Get inputs.
173+
var subsIn C.uint
174+
C.snd_rawmidi_info_set_stream(info, C.SND_RAWMIDI_STREAM_INPUT)
175+
if rc := C.snd_ctl_rawmidi_info(ctl, info); rc != 0 {
176+
return nil, alsaMidiError(rc)
177+
}
178+
subsIn = C.snd_rawmidi_info_get_subdevices_count(info)
179+
180+
// Get outputs.
181+
var subsOut C.uint
182+
C.snd_rawmidi_info_set_stream(info, C.SND_RAWMIDI_STREAM_OUTPUT)
183+
if rc := C.snd_ctl_rawmidi_info(ctl, info); rc != 0 {
184+
return nil, alsaMidiError(rc)
185+
}
186+
subsOut = C.snd_rawmidi_info_get_subdevices_count(info)
187+
188+
// List subdevices.
189+
var subs C.uint
190+
if subsIn > subsOut {
191+
subs = subsIn
192+
} else {
193+
subs = subsOut
194+
}
195+
if subs == C.uint(0) {
196+
return nil, errors.New("no streams")
197+
}
198+
devices := []*Device{}
199+
200+
for sub := C.uint(0); sub < subs; sub++ {
201+
subDevice, err := getSubdevice(ctl, info, card, device, sub, subsIn, subsOut)
202+
if err != nil {
203+
return nil, err
204+
}
205+
devices = append(devices, subDevice)
206+
}
207+
return devices, nil
208+
}
209+
210+
func getSubdevice(ctl *C.snd_ctl_t, info *C.snd_rawmidi_info_t, card C.int, device, sub, subsIn, subsOut C.uint) (*Device, error) {
211+
if sub < subsIn {
212+
C.snd_rawmidi_info_set_stream(info, C.SND_RAWMIDI_STREAM_INPUT)
213+
} else {
214+
C.snd_rawmidi_info_set_stream(info, C.SND_RAWMIDI_STREAM_OUTPUT)
215+
}
216+
C.snd_rawmidi_info_set_subdevice(info, sub)
217+
if rc := C.snd_ctl_rawmidi_info(ctl, info); rc != 0 {
218+
return nil, alsaMidiError(rc)
219+
}
220+
var (
221+
name = C.GoString(C.snd_rawmidi_info_get_name(info))
222+
subName = C.GoString(C.snd_rawmidi_info_get_subdevice_name(info))
223+
)
224+
var dt DeviceType
225+
if sub < subsIn && sub >= subsOut {
226+
dt = DeviceInput
227+
} else if sub >= subsIn && sub < subsOut {
228+
dt = DeviceOutput
229+
} else {
230+
dt = DeviceDuplex
231+
}
232+
if sub == 0 && len(subName) > 0 && subName[0] == 0 {
233+
return &Device{
234+
ID: fmt.Sprintf("hw:%d,%d", card, device),
235+
Name: name,
236+
Type: dt,
237+
}, nil
238+
}
239+
return &Device{
240+
ID: fmt.Sprintf("hw:%d,%d,%d", card, device, sub),
241+
Name: subName,
242+
Type: dt,
243+
}, nil
244+
}
245+
246+
func alsaMidiError(code C.int) error {
247+
if code == C.int(0) {
248+
return nil
249+
}
250+
return errors.New(C.GoString(C.snd_strerror(code)))
251+
}

0 commit comments

Comments
 (0)