356 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			356 lines
		
	
	
		
			8.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package rtmidi
 | |
| 
 | |
| /*
 | |
| #cgo CXXFLAGS: -g
 | |
| #cgo LDFLAGS: -g
 | |
| 
 | |
| #cgo linux CXXFLAGS: -D__LINUX_ALSA__
 | |
| #cgo linux LDFLAGS: -lasound -pthread
 | |
| #cgo windows CXXFLAGS: -D__WINDOWS_MM__
 | |
| #cgo windows LDFLAGS: -luuid -lksuser -lwinmm -lole32
 | |
| #cgo darwin CXXFLAGS: -D__MACOSX_CORE__
 | |
| #cgo darwin LDFLAGS: -framework CoreServices -framework CoreAudio -framework CoreMIDI -framework CoreFoundation
 | |
| 
 | |
| #include <stdlib.h>
 | |
| #include <stdint.h>
 | |
| #include "rtmidi_stub.h"
 | |
| 
 | |
| extern void goMIDIInCallback(double ts, unsigned char *msg, size_t msgsz, void *arg);
 | |
| 
 | |
| static inline void midiInCallback(double ts, const unsigned char *msg, size_t msgsz, void *arg) {
 | |
| 	goMIDIInCallback(ts, (unsigned char*) msg, msgsz, arg);
 | |
| }
 | |
| 
 | |
| static inline void cgoSetCallback(RtMidiPtr in, int cb_id) {
 | |
| 	rtmidi_in_set_callback(in, midiInCallback, (void*)(uintptr_t) cb_id);
 | |
| }
 | |
| */
 | |
| import "C"
 | |
| import (
 | |
| 	"errors"
 | |
| 	"sync"
 | |
| 	"unsafe"
 | |
| )
 | |
| 
 | |
| // API is an enumeration of possible MIDI API specifiers.
 | |
| type API C.enum_RtMidiApi
 | |
| 
 | |
| const (
 | |
| 	// APIUnspecified searches for a working compiled API.
 | |
| 	APIUnspecified API = C.RTMIDI_API_UNSPECIFIED
 | |
| 	// APIMacOSXCore uses Macintosh OS-X CoreMIDI API.
 | |
| 	APIMacOSXCore = C.RTMIDI_API_MACOSX_CORE
 | |
| 	// APILinuxALSA uses the Advanced Linux Sound Architecture API.
 | |
| 	APILinuxALSA = C.RTMIDI_API_LINUX_ALSA
 | |
| 	// APIUnixJack uses the JACK Low-Latency MIDI Server API.
 | |
| 	APIUnixJack = C.RTMIDI_API_UNIX_JACK
 | |
| 	// APIWindowsMM uses the Microsoft Multimedia MIDI API.
 | |
| 	APIWindowsMM = C.RTMIDI_API_WINDOWS_MM
 | |
| 	// APIDummy is a compilable but non-functional API.
 | |
| 	APIDummy = C.RTMIDI_API_RTMIDI_DUMMY
 | |
| )
 | |
| 
 | |
| func (api API) String() string {
 | |
| 	switch api {
 | |
| 	case APIUnspecified:
 | |
| 		return "unspecified"
 | |
| 	case APILinuxALSA:
 | |
| 		return "alsa"
 | |
| 	case APIUnixJack:
 | |
| 		return "jack"
 | |
| 	case APIMacOSXCore:
 | |
| 		return "coreaudio"
 | |
| 	case APIWindowsMM:
 | |
| 		return "winmm"
 | |
| 	case APIDummy:
 | |
| 		return "dummy"
 | |
| 	}
 | |
| 	return "?"
 | |
| }
 | |
| 
 | |
| // CompiledAPI determines the available compiled MIDI APIs.
 | |
| func CompiledAPI() (apis []API) {
 | |
| 	n := C.rtmidi_get_compiled_api(nil, 0)
 | |
| 	capis := make([]C.enum_RtMidiApi, n, n)
 | |
| 	C.rtmidi_get_compiled_api(&capis[0], C.uint(n))
 | |
| 	for _, capi := range capis {
 | |
| 		apis = append(apis, API(capi))
 | |
| 	}
 | |
| 	return apis
 | |
| }
 | |
| 
 | |
| // MIDI interface provides a common, platform-independent API for realtime MIDI
 | |
| // device enumeration and handling MIDI ports.
 | |
| type MIDI interface {
 | |
| 	OpenPort(port int, name string) error
 | |
| 	OpenVirtualPort(name string) error
 | |
| 	Close() error
 | |
| 	PortCount() (int, error)
 | |
| 	PortName(port int) (string, error)
 | |
| }
 | |
| 
 | |
| // MIDIIn interface provides a common, platform-independent API for realtime
 | |
| // MIDI input. It allows access to a single MIDI input port. Incoming MIDI
 | |
| // messages are either saved to a queue for retrieval using the Message()
 | |
| // method or immediately passed to a user-specified callback function. Create
 | |
| // multiple instances of this class to connect to more than one MIDI device at
 | |
| // the same time.
 | |
| type MIDIIn interface {
 | |
| 	MIDI
 | |
| 	API() (API, error)
 | |
| 	IgnoreTypes(midiSysex bool, midiTime bool, midiSense bool) error
 | |
| 	SetCallback(func(MIDIIn, []byte, float64)) error
 | |
| 	CancelCallback() error
 | |
| 	Message() ([]byte, float64, error)
 | |
| 	Destroy()
 | |
| }
 | |
| 
 | |
| // MIDIOut interface provides a common, platform-independent API for MIDI
 | |
| // output. It allows one to probe available MIDI output ports, to connect to
 | |
| // one such port, and to send MIDI bytes immediately over the connection.
 | |
| // Create multiple instances of this class to connect to more than one MIDI
 | |
| // device at the same time.
 | |
| type MIDIOut interface {
 | |
| 	MIDI
 | |
| 	API() (API, error)
 | |
| 	SendMessage([]byte) error
 | |
| 	Destroy()
 | |
| }
 | |
| 
 | |
| type midi struct {
 | |
| 	midi C.RtMidiPtr
 | |
| }
 | |
| 
 | |
| func (m *midi) OpenPort(port int, name string) error {
 | |
| 	p := C.CString(name)
 | |
| 	defer C.free(unsafe.Pointer(p))
 | |
| 	C.rtmidi_open_port(m.midi, C.uint(port), p)
 | |
| 	if !m.midi.ok {
 | |
| 		return errors.New(C.GoString(m.midi.msg))
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (m *midi) OpenVirtualPort(name string) error {
 | |
| 	p := C.CString(name)
 | |
| 	defer C.free(unsafe.Pointer(p))
 | |
| 	C.rtmidi_open_virtual_port(m.midi, p)
 | |
| 	if !m.midi.ok {
 | |
| 		return errors.New(C.GoString(m.midi.msg))
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (m *midi) PortName(port int) (string, error) {
 | |
| 	p := C.rtmidi_get_port_name(m.midi, C.uint(port))
 | |
| 	if !m.midi.ok {
 | |
| 		return "", errors.New(C.GoString(m.midi.msg))
 | |
| 	}
 | |
| 	defer C.free(unsafe.Pointer(p))
 | |
| 	return C.GoString(p), nil
 | |
| }
 | |
| 
 | |
| func (m *midi) PortCount() (int, error) {
 | |
| 	n := C.rtmidi_get_port_count(m.midi)
 | |
| 	if !m.midi.ok {
 | |
| 		return 0, errors.New(C.GoString(m.midi.msg))
 | |
| 	}
 | |
| 	return int(n), nil
 | |
| }
 | |
| 
 | |
| func (m *midi) Close() error {
 | |
| 	C.rtmidi_close_port(C.RtMidiPtr(m.midi))
 | |
| 	if !m.midi.ok {
 | |
| 		return errors.New(C.GoString(m.midi.msg))
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| type midiIn struct {
 | |
| 	midi
 | |
| 	in C.RtMidiInPtr
 | |
| 	cb func(MIDIIn, []byte, float64)
 | |
| }
 | |
| 
 | |
| type midiOut struct {
 | |
| 	midi
 | |
| 	out C.RtMidiOutPtr
 | |
| }
 | |
| 
 | |
| // NewMIDIInDefault opens a default MIDIIn port.
 | |
| func NewMIDIInDefault() (MIDIIn, error) {
 | |
| 	in := C.rtmidi_in_create_default()
 | |
| 	if !in.ok {
 | |
| 		defer C.rtmidi_in_free(in)
 | |
| 		return nil, errors.New(C.GoString(in.msg))
 | |
| 	}
 | |
| 	return &midiIn{in: in, midi: midi{midi: C.RtMidiPtr(in)}}, nil
 | |
| }
 | |
| 
 | |
| // NewMIDIIn opens a single MIDIIn port using the given API. One can provide a
 | |
| // custom port name and a desired queue size for the incomming MIDI messages.
 | |
| func NewMIDIIn(api API, name string, queueSize int) (MIDIIn, error) {
 | |
| 	p := C.CString(name)
 | |
| 	defer C.free(unsafe.Pointer(p))
 | |
| 	in := C.rtmidi_in_create(C.enum_RtMidiApi(api), p, C.uint(queueSize))
 | |
| 	if !in.ok {
 | |
| 		defer C.rtmidi_in_free(in)
 | |
| 		return nil, errors.New(C.GoString(in.msg))
 | |
| 	}
 | |
| 	return &midiIn{in: in, midi: midi{midi: C.RtMidiPtr(in)}}, nil
 | |
| }
 | |
| 
 | |
| func (m *midiIn) API() (API, error) {
 | |
| 	api := C.rtmidi_in_get_current_api(m.in)
 | |
| 	if !m.in.ok {
 | |
| 		return APIUnspecified, errors.New(C.GoString(m.in.msg))
 | |
| 	}
 | |
| 	return API(api), nil
 | |
| }
 | |
| 
 | |
| func (m *midiIn) Close() error {
 | |
| 	unregisterMIDIIn(m)
 | |
| 	if err := m.midi.Close(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	C.rtmidi_in_free(m.in)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (m *midiIn) IgnoreTypes(midiSysex bool, midiTime bool, midiSense bool) error {
 | |
| 	C.rtmidi_in_ignore_types(m.in, C._Bool(midiSysex), C._Bool(midiTime), C._Bool(midiSense))
 | |
| 	if !m.in.ok {
 | |
| 		return errors.New(C.GoString(m.in.msg))
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| var (
 | |
| 	mu     sync.Mutex
 | |
| 	inputs = map[int]*midiIn{}
 | |
| )
 | |
| 
 | |
| func registerMIDIIn(m *midiIn) int {
 | |
| 	mu.Lock()
 | |
| 	defer mu.Unlock()
 | |
| 	for i := 0; ; i++ {
 | |
| 		if _, ok := inputs[i]; !ok {
 | |
| 			inputs[i] = m
 | |
| 			return i
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func unregisterMIDIIn(m *midiIn) {
 | |
| 	mu.Lock()
 | |
| 	defer mu.Unlock()
 | |
| 	for i := 0; i < len(inputs); i++ {
 | |
| 		if inputs[i] == m {
 | |
| 			delete(inputs, i)
 | |
| 			return
 | |
| 		}
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func findMIDIIn(k int) *midiIn {
 | |
| 	mu.Lock()
 | |
| 	defer mu.Unlock()
 | |
| 	return inputs[k]
 | |
| }
 | |
| 
 | |
| //export goMIDIInCallback
 | |
| func goMIDIInCallback(ts C.double, msg *C.uchar, msgsz C.size_t, arg unsafe.Pointer) {
 | |
| 	k := int(uintptr(arg))
 | |
| 	m := findMIDIIn(k)
 | |
| 	m.cb(m, C.GoBytes(unsafe.Pointer(msg), C.int(msgsz)), float64(ts))
 | |
| }
 | |
| 
 | |
| func (m *midiIn) SetCallback(cb func(MIDIIn, []byte, float64)) error {
 | |
| 	k := registerMIDIIn(m)
 | |
| 	m.cb = cb
 | |
| 	C.cgoSetCallback(m.in, C.int(k))
 | |
| 	if !m.in.ok {
 | |
| 		return errors.New(C.GoString(m.in.msg))
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (m *midiIn) CancelCallback() error {
 | |
| 	unregisterMIDIIn(m)
 | |
| 	C.rtmidi_in_cancel_callback(m.in)
 | |
| 	if !m.in.ok {
 | |
| 		return errors.New(C.GoString(m.in.msg))
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (m *midiIn) Message() ([]byte, float64, error) {
 | |
| 	msg := make([]C.uchar, 64*1024, 64*1024)
 | |
| 	sz := C.size_t(len(msg))
 | |
| 	r := C.rtmidi_in_get_message(m.in, &msg[0], &sz)
 | |
| 	if !m.in.ok {
 | |
| 		return nil, 0, errors.New(C.GoString(m.in.msg))
 | |
| 	}
 | |
| 	b := make([]byte, int(sz), int(sz))
 | |
| 	for i, c := range msg[:sz] {
 | |
| 		b[i] = byte(c)
 | |
| 	}
 | |
| 	return b, float64(r), nil
 | |
| }
 | |
| 
 | |
| func (m *midiIn) Destroy() {
 | |
| 	C.rtmidi_in_free(m.in)
 | |
| }
 | |
| 
 | |
| // NewMIDIOutDefault opens a default MIDIOut port.
 | |
| func NewMIDIOutDefault() (MIDIOut, error) {
 | |
| 	out := C.rtmidi_out_create_default()
 | |
| 	if !out.ok {
 | |
| 		defer C.rtmidi_out_free(out)
 | |
| 		return nil, errors.New(C.GoString(out.msg))
 | |
| 	}
 | |
| 	return &midiOut{out: out, midi: midi{midi: C.RtMidiPtr(out)}}, nil
 | |
| }
 | |
| 
 | |
| // NewMIDIOut opens a single MIDIIn port using the given API with the given port name.
 | |
| func NewMIDIOut(api API, name string) (MIDIOut, error) {
 | |
| 	p := C.CString(name)
 | |
| 	defer C.free(unsafe.Pointer(p))
 | |
| 	out := C.rtmidi_out_create(C.enum_RtMidiApi(api), p)
 | |
| 	if !out.ok {
 | |
| 		defer C.rtmidi_out_free(out)
 | |
| 		return nil, errors.New(C.GoString(out.msg))
 | |
| 	}
 | |
| 	return &midiOut{out: out, midi: midi{midi: C.RtMidiPtr(out)}}, nil
 | |
| }
 | |
| 
 | |
| func (m *midiOut) API() (API, error) {
 | |
| 	api := C.rtmidi_out_get_current_api(m.out)
 | |
| 	if !m.out.ok {
 | |
| 		return APIUnspecified, errors.New(C.GoString(m.out.msg))
 | |
| 	}
 | |
| 	return API(api), nil
 | |
| }
 | |
| 
 | |
| func (m *midiOut) Close() error {
 | |
| 	if err := m.midi.Close(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	C.rtmidi_out_free(m.out)
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (m *midiOut) SendMessage(b []byte) error {
 | |
| 	p := C.CBytes(b)
 | |
| 	defer C.free(unsafe.Pointer(p))
 | |
| 	C.rtmidi_out_send_message(m.out, (*C.uchar)(p), C.int(len(b)))
 | |
| 	if !m.out.ok {
 | |
| 		return errors.New(C.GoString(m.out.msg))
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (m *midiOut) Destroy() {
 | |
| 	C.rtmidi_out_free(m.out)
 | |
| }
 | 
