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 #include #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) }