Skip to content

Commit

Permalink
add go bindings
Browse files Browse the repository at this point in the history
  • Loading branch information
zserge committed Oct 3, 2017
1 parent 431354f commit b2cac96
Show file tree
Hide file tree
Showing 4 changed files with 406 additions and 0 deletions.
355 changes: 355 additions & 0 deletions contrib/go/rtmidi/rtmidi.go
@@ -0,0 +1,355 @@
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.RT_MIDI_API_UNSPECIFIED
// APIMacOSXCore uses Macintosh OS-X Core Midi API.
APIMacOSXCore = C.RT_MIDI_API_MACOSX_CORE
// APILinuxALSA uses the Advanced Linux Sound Architecture API.
APILinuxALSA = C.RT_MIDI_API_LINUX_ALSA
// APIUnixJack uses the JACK Low-Latency MIDI Server API.
APIUnixJack = C.RT_MIDI_API_UNIX_JACK
// APIWindowsMM uses the Microsoft Multimedia MIDI API.
APIWindowsMM = C.RT_MIDI_API_WINDOWS_MM
// APIDummy is a compilable but non-functional API.
APIDummy = C.RT_MIDI_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)
capis := make([]C.enum_RtMidiApi, n, n)
C.rtmidi_get_compiled_api(&capis[0])
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)
}
4 changes: 4 additions & 0 deletions contrib/go/rtmidi/rtmidi_stub.cpp
@@ -0,0 +1,4 @@
#include "../../../RtMidi.h"

#include "../../../RtMidi.cpp"
#include "../../../rtmidi_c.cpp"
1 change: 1 addition & 0 deletions contrib/go/rtmidi/rtmidi_stub.h
@@ -0,0 +1 @@
#include "../../../rtmidi_c.h"

0 comments on commit b2cac96

Please sign in to comment.