├── LICENSE ├── Makefile ├── README.md ├── example ├── midipipe │ └── main.go └── vocoder │ ├── audio.go │ ├── main.go │ └── osc.go ├── filters.go ├── pm.yml ├── pm ├── cgo_helpers.c ├── cgo_helpers.go ├── cgo_helpers.h ├── const.go ├── doc.go ├── error.go ├── pm.go ├── portmidi.h └── types.go ├── portmidi.go └── stream.go /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright © 2016 Maxim Kupriianov 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the “Software”), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | cgogen pm.yml 3 | 4 | clean: 5 | rm -f pm/cgo_helpers.go pm/cgo_helpers.h pm/cgo_helpers.c 6 | rm -f pm/doc.go pm/types.go pm/const.go 7 | rm -f pm/pm.go 8 | 9 | test: 10 | cd pm && go build 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | portmidi [![GoDoc](https://godoc.org/github.com/xlab/portmidi?status.svg)](https://godoc.org/github.com/xlab/portmidi) 2 | ======== 3 | 4 | Package portmidi provides Go bindings for [PortMIDI](http://portmedia.sourceforge.net/portmidi/) from the PortMedia set of libraries. [PortMedia](http://portaudio.com) offers free, cross-platform, open-source I/O libraries for digital media including MIDI, video, and audio. 5 | 6 | All the binding code for the `pm` package has automatically been generated with rules defined in [pm.yml](/pm.yml). The wrapping package `portmidi` has been done by hand and leverages channels for MIDI event streaming. 7 | 8 | ## Usage 9 | 10 | ``` 11 | $ brew install portmidi 12 | (or use your package manager) 13 | 14 | $ go get github.com/xlab/portmidi 15 | ``` 16 | 17 | ## Examples 18 | 19 | ### MIDIPipe 20 | 21 | [midipipe](/example/midipipe) is a simple Go program that redirects all the events it gets from a MIDI input device 22 | to the specified MIDI output device. You can specify route by device name (see example) or by its ID. 23 | 24 | The app requires minimum two devices to operate properly, but note that a single hardware piece can act 25 | both as input and output device, so by "devices" I mean logical I/O streams. 26 | 27 | ``` 28 | $ brew install portmidi 29 | $ go get github.com/xlab/portmidi/example/midipipe 30 | 31 | $ midipipe -in "Arturia BeatStep" -out "OP-1 Midi Device" 32 | 33 | main.go:35: [INFO] total MIDI devices: 4 34 | main.go:50: [INFO] available inputs: map[Arturia BeatStep:1 OP-1 Midi Device:0] 35 | main.go:51: [INFO] available outputs: map[OP-1 Midi Device:2 Arturia BeatStep:3] 36 | 37 | main.go:56: [INFO] input device id=1 . 38 | ├── [CoreMIDI] Interface 39 | ├── [Arturia BeatStep] Name 40 | ├── [true] IsInputAvailable 41 | └── [false] IsOutputAvailable 42 | main.go:65: [INFO] output device id=2 . 43 | ├── [CoreMIDI] Interface 44 | ├── [OP-1 Midi Device] Name 45 | ├── [false] IsInputAvailable 46 | └── [true] IsOutputAvailable 47 | 48 | main.go:80: [DBG] rate 1 minute 100.14564014611834 49 | main.go:81: [DBG] rate 5 minute 51.93229225191669 50 | main.go:82: [DBG] rate mean 134.09919032344553 51 | 52 | main.go:80: [DBG] rate 1 minute 65.02414338183416 53 | main.go:81: [DBG] rate 5 minute 51.98104984601001 54 | main.go:82: [DBG] rate mean 93.71848260518783 55 | ^Cmain.go:72: bye! 56 | ``` 57 | 58 | ### Vocoder 59 | 60 | [vocoder](/example/vocoder) is an implementation of a simple vocoder in Go. It reads your voice using PortAudio, reads note-on events from your MIDI device using PortMIDI, and plays the altered voice back using PortAudio. Have fun. 61 | 62 | ``` 63 | $ brew install portaudio portmidi 64 | $ go get github.com/xlab/portmidi/example/vocoder 65 | 66 | $ vocoder -in "Arturia BeatStep" 67 | main.go:43: [INFO] available inputs: map[IAC Driver Bus 1:0 Arturia BeatStep:1] 68 | main.go:46: Using Arturia BeatStep (via CoreMIDI) 69 | main.go:62: note 63 (311.127Hz) 70 | main.go:62: note 62 (293.665Hz) 71 | main.go:62: note 65 (349.228Hz) 72 | main.go:62: note 73 (554.365Hz) 73 | main.go:62: note 66 (369.994Hz) 74 | main.go:62: note 60 (261.626Hz) 75 | ^C 76 | ``` 77 | 78 | ### Rebuilding the package 79 | 80 | You will need to get the [cgogen](https://git.io/cgogen) tool installed first. 81 | 82 | ``` 83 | $ git clone https://github.com/xlab/portmidi && cd portmidi 84 | $ make clean 85 | $ make 86 | ``` 87 | 88 | ## License 89 | 90 | All the code except when stated otherwise is licensed under the MIT license. 91 | -------------------------------------------------------------------------------- /example/midipipe/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | "time" 7 | 8 | "github.com/rcrowley/go-metrics" 9 | "github.com/xlab/closer" 10 | "github.com/xlab/portmidi" 11 | "github.com/xlab/treeprint" 12 | ) 13 | 14 | var ( 15 | inName = flag.String("in", "", "MIDI device name to use as input.") 16 | outName = flag.String("out", "", "MIDI device name to use as output.") 17 | inDevID = flag.Int("in-dev", -1, "MIDI device ID to use as input.") 18 | outDevID = flag.Int("out-dev", -1, "MIDI device ID to use as output.") 19 | ) 20 | 21 | func init() { 22 | flag.Parse() 23 | log.SetFlags(log.Lshortfile) 24 | } 25 | 26 | func main() { 27 | defer closer.Close() 28 | 29 | portmidi.Initialize() 30 | closer.Bind(func() { 31 | portmidi.Terminate() 32 | }) 33 | 34 | numDevices := portmidi.CountDevices() 35 | log.Println("[INFO] total MIDI devices:", numDevices) 36 | if numDevices < 2 { 37 | closer.Fatalln("[ERR] midipipe cannot operate with less than 2 devices") 38 | } 39 | inputs := make(map[string]portmidi.DeviceID, numDevices) 40 | outputs := make(map[string]portmidi.DeviceID, numDevices) 41 | for i := 0; i < numDevices; i++ { 42 | info := portmidi.GetDeviceInfo(portmidi.DeviceID(i)) 43 | if info.IsInputAvailable { 44 | inputs[info.Name] = portmidi.DeviceID(i) 45 | } 46 | if info.IsOutputAvailable { 47 | outputs[info.Name] = portmidi.DeviceID(i) 48 | } 49 | } 50 | log.Println("[INFO] available inputs:", inputs) 51 | log.Println("[INFO] available outputs:", outputs) 52 | inDev := findCandidate(inputs, *inDevID, *inName, true) 53 | outDev := findCandidate(outputs, *outDevID, *outName, false) 54 | 55 | inInfo := portmidi.GetDeviceInfo(inDev) 56 | log.Printf("[INFO] input device id=%d %s", inDev, treeprint.Repr(inInfo)) 57 | in, err := portmidi.NewInputStream(inDev, 1024, 0) 58 | if err != nil { 59 | closer.Fatalln("[ERR] cannot init an input stream:", err) 60 | } 61 | closer.Bind(func() { 62 | in.Close() 63 | }) 64 | outInfo := portmidi.GetDeviceInfo(outDev) 65 | log.Printf("[INFO] output device id=%d %s", outDev, treeprint.Repr(outInfo)) 66 | out, err := portmidi.NewOutputStream(outDev, 1024, 0, 0) 67 | if err != nil { 68 | closer.Fatalln("[ERR] cannot init an output stream:", err) 69 | } 70 | closer.Bind(func() { 71 | out.Close() 72 | log.Println("bye!") 73 | }) 74 | 75 | meter := metrics.NewMeter() 76 | go func() { 77 | t := time.NewTicker(time.Minute) 78 | for range t.C { 79 | snap := meter.Snapshot() 80 | log.Println("[DBG] rate 1 minute", snap.Rate1()) 81 | log.Println("[DBG] rate 5 minute", snap.Rate5()) 82 | log.Println("[DBG] rate mean", snap.RateMean()) 83 | } 84 | }() 85 | go func() { 86 | sink := out.Sink() 87 | for ev := range in.Source() { 88 | meter.Mark(int64(1)) 89 | sink <- ev 90 | } 91 | }() 92 | closer.Hold() 93 | } 94 | 95 | func findCandidate(devices map[string]portmidi.DeviceID, 96 | id int, name string, input bool) (dev portmidi.DeviceID) { 97 | 98 | if id >= 0 { 99 | dev = portmidi.DeviceID(id) 100 | return 101 | } 102 | if len(name) > 0 { 103 | nameID, ok := devices[name] 104 | if ok { 105 | dev = nameID 106 | return 107 | } 108 | closer.Fatalln("[ERR] midipipe was unable to locate required device:", name) 109 | } 110 | if input { 111 | dev, _ = portmidi.DefaultInputDeviceID() 112 | return 113 | } 114 | dev, _ = portmidi.DefaultOutputDeviceID() 115 | return 116 | } 117 | -------------------------------------------------------------------------------- /example/vocoder/audio.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "unsafe" 6 | 7 | "github.com/xlab/portaudio-go/portaudio" 8 | ) 9 | 10 | type IOControl struct { 11 | stream *portaudio.Stream 12 | inCh int 13 | outCh int 14 | in chan<- []float32 15 | out <-chan []float32 16 | } 17 | 18 | const ( 19 | paFormat = portaudio.PaFloat32 20 | paSampleRate = 44100 21 | ) 22 | 23 | func NewIOControl(inCh, outCh int, samplesPerCh int, 24 | in chan<- []float32, out <-chan []float32) (*IOControl, error) { 25 | if err := portaudio.Initialize(); paError(err) { 26 | return nil, errors.New(paErrorText(err)) 27 | } 28 | if in == nil { 29 | inCh = 0 30 | } 31 | if out == nil { 32 | outCh = 0 33 | } 34 | var stream *portaudio.Stream 35 | ctl := &IOControl{ 36 | inCh: inCh, 37 | outCh: outCh, 38 | in: in, 39 | out: out, 40 | } 41 | if err := portaudio.OpenDefaultStream(&stream, int32(inCh), int32(outCh), paFormat, paSampleRate, 42 | uint(samplesPerCh), ctl.audioCallback, nil); paError(err) { 43 | return nil, errors.New(paErrorText(err)) 44 | } 45 | ctl.stream = stream 46 | return ctl, nil 47 | } 48 | 49 | func (i *IOControl) StartStream() error { 50 | err := portaudio.StartStream(i.stream) 51 | if paError(err) { 52 | return errors.New(paErrorText(err)) 53 | } 54 | return nil 55 | } 56 | 57 | func (i *IOControl) audioCallback(input unsafe.Pointer, output unsafe.Pointer, sampleCount uint, 58 | _ *portaudio.StreamCallbackTimeInfo, _ portaudio.StreamCallbackFlags, _ unsafe.Pointer) int32 { 59 | 60 | const statusContinue = int32(portaudio.PaContinue) 61 | samples := int(sampleCount) 62 | 63 | if input != nil && i.in != nil { 64 | inFrame := (*(*[1 << 32]float32)(input))[:samples*i.inCh] 65 | i.in <- inFrame[:samples*i.inCh] // TODO(xlab): consider copying 66 | } 67 | if output != nil && i.out != nil { 68 | outFrame := (*(*[1 << 32]float32)(output))[:samples*i.outCh] 69 | select { 70 | case frame := <-i.out: 71 | copy(outFrame, frame[:samples*i.outCh]) 72 | default: 73 | return statusContinue 74 | } 75 | } 76 | return statusContinue 77 | } 78 | 79 | func (i *IOControl) Destroy() { 80 | if i.stream != nil { 81 | portaudio.StopStream(i.stream) 82 | portaudio.Terminate() 83 | i.stream = nil 84 | } 85 | } 86 | 87 | func paError(err portaudio.Error) bool { 88 | return portaudio.ErrorCode(err) != portaudio.PaNoError 89 | } 90 | 91 | func paErrorText(err portaudio.Error) string { 92 | return portaudio.GetErrorText(err) 93 | } 94 | -------------------------------------------------------------------------------- /example/vocoder/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "log" 6 | 7 | "github.com/xlab/closer" 8 | "github.com/xlab/midievent" 9 | "github.com/xlab/portmidi" 10 | ) 11 | 12 | var ( 13 | inName = flag.String("in", "", "MIDI device name to use as input.") 14 | inDevID = flag.Int("in-dev", -1, "MIDI device ID to use as input.") 15 | ) 16 | 17 | func init() { 18 | flag.Parse() 19 | log.SetFlags(log.Lshortfile) 20 | } 21 | 22 | const samplesPerChannel = 2048 23 | 24 | func main() { 25 | defer closer.Close() 26 | 27 | portmidi.Initialize() 28 | closer.Bind(func() { 29 | portmidi.Terminate() 30 | }) 31 | 32 | numDevices := portmidi.CountDevices() 33 | if numDevices < 1 { 34 | closer.Fatalln("[ERR] vocoder cannot operate with less than one MIDI device") 35 | } 36 | inputs := make(map[string]portmidi.DeviceID, numDevices) 37 | for i := 0; i < numDevices; i++ { 38 | info := portmidi.GetDeviceInfo(portmidi.DeviceID(i)) 39 | if info.IsInputAvailable { 40 | inputs[info.Name] = portmidi.DeviceID(i) 41 | } 42 | } 43 | log.Println("[INFO] available inputs:", inputs) 44 | devID := findCandidate(inputs, *inDevID, *inName, true) 45 | info := portmidi.GetDeviceInfo(devID) 46 | log.Printf("Using %s (via %s)", info.Name, info.Interface) 47 | midiIn, err := portmidi.NewInputStream(devID, 512, 0, 48 | portmidi.FilterControl|portmidi.FilterAftertouch|portmidi.FilterSystemCommon|portmidi.FilterRealtime) 49 | if err != nil { 50 | closer.Fatalln(err) 51 | } 52 | closer.Bind(func() { 53 | midiIn.Close() 54 | }) 55 | 56 | vocoder := NewVocoder() 57 | go func() { 58 | for ev := range midiIn.Source() { 59 | msg := portmidi.Message(ev.Message) 60 | if midievent.IsNoteOn(midievent.Event(msg.Status())) { 61 | n := int(msg.Data1()) 62 | log.Printf("note %d (%.3fHz)", n, noteToFreq(n)) 63 | vocoder.SwitchNote(n) 64 | } 65 | } 66 | }() 67 | 68 | in := make(chan []float32, 64) 69 | out := make(chan []float32, 64) 70 | go func() { 71 | buf := make([]float32, 2*samplesPerChannel) 72 | 73 | for frame := range in { 74 | dsp := vocoder.CurrentDSP() 75 | dsp.Process(buf) 76 | for i := range frame { 77 | buf[i] = (frame[i] * buf[i]) 78 | } 79 | out <- buf 80 | buf = make([]float32, 2*samplesPerChannel) 81 | } 82 | }() 83 | ctl, err := NewIOControl(2, 2, samplesPerChannel, in, out) 84 | if err != nil { 85 | closer.Fatalln(err) 86 | } 87 | closer.Bind(func() { 88 | ctl.Destroy() 89 | }) 90 | 91 | if err := ctl.StartStream(); err != nil { 92 | closer.Fatalln(err) 93 | } 94 | closer.Hold() 95 | } 96 | 97 | func findCandidate(devices map[string]portmidi.DeviceID, 98 | id int, name string, input bool) (dev portmidi.DeviceID) { 99 | 100 | if id >= 0 { 101 | dev = portmidi.DeviceID(id) 102 | return 103 | } 104 | if len(name) > 0 { 105 | nameID, ok := devices[name] 106 | if ok { 107 | dev = nameID 108 | return 109 | } 110 | closer.Fatalln("[ERR] vocoder was unable to locate required device:", name) 111 | } 112 | if input { 113 | dev, _ = portmidi.DefaultInputDeviceID() 114 | return 115 | } 116 | dev, _ = portmidi.DefaultOutputDeviceID() 117 | return 118 | } 119 | -------------------------------------------------------------------------------- /example/vocoder/osc.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "math" 5 | "sync" 6 | ) 7 | 8 | type DSP interface { 9 | Process(out []float32) 10 | } 11 | 12 | type sineOsc struct { 13 | step float64 14 | phase float64 15 | } 16 | 17 | func NewSineOsc(freq float64, sampleRate float64) DSP { 18 | return &sineOsc{freq / sampleRate, 0} 19 | } 20 | 21 | func (osc *sineOsc) Process(out []float32) { 22 | for i := range out { 23 | out[i] = osc.powerInNthSample(i) 24 | } 25 | } 26 | 27 | func (osc *sineOsc) powerInNthSample(n int) float32 { 28 | currentPhase := osc.phase 29 | _, osc.phase = math.Modf(osc.phase + osc.step) 30 | return float32(math.Sin(2 * math.Pi * currentPhase)) 31 | } 32 | 33 | type Vocoder struct { 34 | note int 35 | dsps map[int]DSP 36 | mux sync.Mutex 37 | } 38 | 39 | func NewVocoder() *Vocoder { 40 | v := &Vocoder{ 41 | dsps: make(map[int]DSP), 42 | } 43 | v.SwitchNote(60) // C4 44 | return v 45 | } 46 | 47 | func noteToFreq(n int) float64 { 48 | // http://newt.phys.unsw.edu.au/jw/notes.html 49 | return math.Pow(2, (float64(n)-69)/12) * 440.0 50 | } 51 | 52 | func (v *Vocoder) SwitchNote(note int) { 53 | v.mux.Lock() 54 | v.note = note 55 | _, ok := v.dsps[note] 56 | if !ok { 57 | v.dsps[note] = NewSineOsc(noteToFreq(note), 44100) 58 | } 59 | v.mux.Unlock() 60 | } 61 | 62 | func (v *Vocoder) CurrentDSP() DSP { 63 | v.mux.Lock() 64 | dsp := v.dsps[v.note] 65 | v.mux.Unlock() 66 | return dsp 67 | } 68 | -------------------------------------------------------------------------------- /filters.go: -------------------------------------------------------------------------------- 1 | package portmidi 2 | 3 | type ChannelMask int32 4 | 5 | // Channel sets a channel mask to pass to a stream. 6 | // Multiple channels should be OR'ed together Channel(1) | Channel(2). 7 | // Note that channels are numbered 0 to 15 (not 1 to 16). 8 | // All channels are allowed by default. 9 | func Channel(ch int) ChannelMask { 10 | return ChannelMask(1 << uint8(ch)) 11 | } 12 | 13 | type Filter int32 14 | 15 | func (f *Filter) Join(fs ...Filter) { 16 | for i := range fs { 17 | *f |= fs[i] 18 | } 19 | } 20 | 21 | const ( 22 | // FilterActive filters active sensing messages (0xFE). 23 | FilterActive Filter = (1 << 0x0E) 24 | // FilterSysEx filters system exclusive messages (0xF0). 25 | FilterSysEx Filter = (1 << 0x00) 26 | // FilterClock filters MIDI clock messages (0xF8). 27 | FilterClock Filter = (1 << 0x08) 28 | // FilterPlay filters play messages (start 0xFA, stop 0xFC, continue 0xFB). 29 | FilterPlay Filter = ((1 << 0x0A) | (1 << 0x0C) | (1 << 0x0B)) 30 | // FilterTick filters tick messages. 31 | FilterTick Filter = (1 << 0x09) 32 | // FilterFd filters undefined FD messages. 33 | FilterFD Filter = (1 << 0x0D) 34 | // FilterUndefined filters undefined real-time messages. 35 | FilterUndefined Filter = FilterFD 36 | // FilterReset filters reset messages. 37 | FilterReset Filter = (1 << 0x0F) 38 | // FilterRealtime filters all real-time messages. 39 | FilterRealtime Filter = (FilterActive | FilterSysEx | FilterClock | FilterPlay | FilterUndefined | FilterReset | FilterTick) 40 | // FilterNote filters note-on and note-off (0x90-0x9F and 0x80-0x8F). 41 | FilterNote Filter = ((1 << 0x19) | (1 << 0x18)) 42 | // FilterChannelAftertouch filters channel aftertouch (most midi controllers use this) (0xD0-0xDF). 43 | FilterChannelAftertouch Filter = (1 << 0x1D) 44 | // FilterPolyAftertouch filters per-note aftertouch (0xA0-0xAF). 45 | FilterPolyAftertouch Filter = (1 << 0x1A) 46 | // FilterAftertouch filters both channel and poly aftertouch. 47 | FilterAftertouch Filter = (FilterChannelAftertouch | FilterPolyAftertouch) 48 | // FilterProgram filters program changes (0xC0-0xCF). 49 | FilterProgram Filter = (1 << 0x1C) 50 | // FilterControl filters control changes (CC's) (0xB0-0xBF). 51 | FilterControl Filter = (1 << 0x1B) 52 | // FilterPitchbend filters pitch bends (0xE0-0xEF). 53 | FilterPitchbend Filter = (1 << 0x1E) 54 | // FilterMTC filters MIDI Time Code (0xF1). 55 | FilterMTC Filter = (1 << 0x01) 56 | // FilterSongPosition filters song position (0xF2). 57 | FilterSongPosition Filter = (1 << 0x02) 58 | // FilterSongSelect filters song select (0xF3). 59 | FilterSongSelect Filter = (1 << 0x03) 60 | // FilterTune filters tuning request (0xF6). 61 | FilterTune Filter = (1 << 0x06) 62 | // FilterSystemCommon filters all system common messages (mtc, song position, song select, tune request). 63 | FilterSystemCommon Filter = (FilterMTC | FilterSongPosition | FilterSongSelect | FilterTune) 64 | ) 65 | -------------------------------------------------------------------------------- /pm.yml: -------------------------------------------------------------------------------- 1 | --- 2 | GENERATOR: 3 | PackageName: pm 4 | PackageDescription: "Package pm provides Go bindings for portmidi." 5 | PackageLicense: "THE AUTOGENERATED LICENSE. ALL THE RIGHTS ARE RESERVED BY ROBOTS." 6 | Includes: ["portmidi.h"] 7 | FlagGroups: 8 | - {name: LDFLAGS, flags: [-lportmidi]} 9 | 10 | PARSER: 11 | IncludePaths: ["/usr/include"] 12 | SourcesPaths: ["pm/portmidi.h"] 13 | 14 | TRANSLATOR: 15 | ConstRules: 16 | defines: expand 17 | PtrTips: 18 | function: 19 | - {target: "_OpenOutput$", tips: [ref]} 20 | - {target: "_OpenInput$", tips: [ref]} 21 | Rules: 22 | global: 23 | - {action: accept, from: "(?i)^Pm"} 24 | - {action: replace, from: "(?i)^Pm"} 25 | - {action: accept, from: "^PortMidi"} 26 | - {transform: export} 27 | const: 28 | - {action: ignore, from: PMEXPORT} 29 | - {action: ignore, from: PmStream} 30 | - {transform: lower} 31 | type: 32 | - {action: replace, from: "_t$"} 33 | private: 34 | - {transform: unexport} 35 | post-global: 36 | - {action: replace, from: _$} 37 | - {load: snakecase} 38 | -------------------------------------------------------------------------------- /pm/cgo_helpers.c: -------------------------------------------------------------------------------- 1 | // THE AUTOGENERATED LICENSE. ALL THE RIGHTS ARE RESERVED BY ROBOTS. 2 | 3 | // WARNING: This file has automatically been generated on Tue, 05 Sep 2017 19:52:40 MSK. 4 | // By https://git.io/cgogen. DO NOT EDIT. 5 | 6 | #include "_cgo_export.h" 7 | #include "cgo_helpers.h" 8 | 9 | int PmTimeProcPtr_ae9b2a7b(void* time_info) { 10 | return timeProcPtrAE9B2A7B(time_info); 11 | } 12 | 13 | -------------------------------------------------------------------------------- /pm/cgo_helpers.go: -------------------------------------------------------------------------------- 1 | // THE AUTOGENERATED LICENSE. ALL THE RIGHTS ARE RESERVED BY ROBOTS. 2 | 3 | // WARNING: This file has automatically been generated on Tue, 05 Sep 2017 19:52:40 MSK. 4 | // By https://git.io/cgogen. DO NOT EDIT. 5 | 6 | package pm 7 | 8 | /* 9 | #cgo LDFLAGS: -lportmidi 10 | #include "portmidi.h" 11 | #include 12 | #include "cgo_helpers.h" 13 | */ 14 | import "C" 15 | import ( 16 | "runtime" 17 | "sync" 18 | "unsafe" 19 | ) 20 | 21 | // cgoAllocMap stores pointers to C allocated memory for future reference. 22 | type cgoAllocMap struct { 23 | mux sync.RWMutex 24 | m map[unsafe.Pointer]struct{} 25 | } 26 | 27 | var cgoAllocsUnknown = new(cgoAllocMap) 28 | 29 | func (a *cgoAllocMap) Add(ptr unsafe.Pointer) { 30 | a.mux.Lock() 31 | if a.m == nil { 32 | a.m = make(map[unsafe.Pointer]struct{}) 33 | } 34 | a.m[ptr] = struct{}{} 35 | a.mux.Unlock() 36 | } 37 | 38 | func (a *cgoAllocMap) IsEmpty() bool { 39 | a.mux.RLock() 40 | isEmpty := len(a.m) == 0 41 | a.mux.RUnlock() 42 | return isEmpty 43 | } 44 | 45 | func (a *cgoAllocMap) Borrow(b *cgoAllocMap) { 46 | if b == nil || b.IsEmpty() { 47 | return 48 | } 49 | b.mux.Lock() 50 | a.mux.Lock() 51 | for ptr := range b.m { 52 | if a.m == nil { 53 | a.m = make(map[unsafe.Pointer]struct{}) 54 | } 55 | a.m[ptr] = struct{}{} 56 | delete(b.m, ptr) 57 | } 58 | a.mux.Unlock() 59 | b.mux.Unlock() 60 | } 61 | 62 | func (a *cgoAllocMap) Free() { 63 | a.mux.Lock() 64 | for ptr := range a.m { 65 | C.free(ptr) 66 | delete(a.m, ptr) 67 | } 68 | a.mux.Unlock() 69 | } 70 | 71 | // allocDeviceInfoMemory allocates memory for type C.PmDeviceInfo in C. 72 | // The caller is responsible for freeing the this memory via C.free. 73 | func allocDeviceInfoMemory(n int) unsafe.Pointer { 74 | mem, err := C.calloc(C.size_t(n), (C.size_t)(sizeOfDeviceInfoValue)) 75 | if err != nil { 76 | panic("memory alloc error: " + err.Error()) 77 | } 78 | return mem 79 | } 80 | 81 | const sizeOfDeviceInfoValue = unsafe.Sizeof([1]C.PmDeviceInfo{}) 82 | 83 | // unpackPCharString represents the data from Go string as *C.char and avoids copying. 84 | func unpackPCharString(str string) (*C.char, *cgoAllocMap) { 85 | h := (*stringHeader)(unsafe.Pointer(&str)) 86 | return (*C.char)(unsafe.Pointer(h.Data)), cgoAllocsUnknown 87 | } 88 | 89 | type stringHeader struct { 90 | Data uintptr 91 | Len int 92 | } 93 | 94 | // packPCharString creates a Go string backed by *C.char and avoids copying. 95 | func packPCharString(p *C.char) (raw string) { 96 | if p != nil && *p != 0 { 97 | h := (*stringHeader)(unsafe.Pointer(&raw)) 98 | h.Data = uintptr(unsafe.Pointer(p)) 99 | for *p != 0 { 100 | p = (*C.char)(unsafe.Pointer(uintptr(unsafe.Pointer(p)) + 1)) // p++ 101 | } 102 | h.Len = int(uintptr(unsafe.Pointer(p)) - h.Data) 103 | } 104 | return 105 | } 106 | 107 | // RawString reperesents a string backed by data on the C side. 108 | type RawString string 109 | 110 | // Copy returns a Go-managed copy of raw string. 111 | func (raw RawString) Copy() string { 112 | if len(raw) == 0 { 113 | return "" 114 | } 115 | h := (*stringHeader)(unsafe.Pointer(&raw)) 116 | return C.GoStringN((*C.char)(unsafe.Pointer(h.Data)), C.int(h.Len)) 117 | } 118 | 119 | // Ref returns the underlying reference to C object or nil if struct is nil. 120 | func (x *DeviceInfo) Ref() *C.PmDeviceInfo { 121 | if x == nil { 122 | return nil 123 | } 124 | return x.refbc1771f0 125 | } 126 | 127 | // Free invokes alloc map's free mechanism that cleanups any allocated memory using C free. 128 | // Does nothing if struct is nil or has no allocation map. 129 | func (x *DeviceInfo) Free() { 130 | if x != nil && x.allocsbc1771f0 != nil { 131 | x.allocsbc1771f0.(*cgoAllocMap).Free() 132 | x.refbc1771f0 = nil 133 | } 134 | } 135 | 136 | // NewDeviceInfoRef creates a new wrapper struct with underlying reference set to the original C object. 137 | // Returns nil if the provided pointer to C object is nil too. 138 | func NewDeviceInfoRef(ref unsafe.Pointer) *DeviceInfo { 139 | if ref == nil { 140 | return nil 141 | } 142 | obj := new(DeviceInfo) 143 | obj.refbc1771f0 = (*C.PmDeviceInfo)(unsafe.Pointer(ref)) 144 | return obj 145 | } 146 | 147 | // PassRef returns the underlying C object, otherwise it will allocate one and set its values 148 | // from this wrapping struct, counting allocations into an allocation map. 149 | func (x *DeviceInfo) PassRef() (*C.PmDeviceInfo, *cgoAllocMap) { 150 | if x == nil { 151 | return nil, nil 152 | } else if x.refbc1771f0 != nil { 153 | return x.refbc1771f0, nil 154 | } 155 | membc1771f0 := allocDeviceInfoMemory(1) 156 | refbc1771f0 := (*C.PmDeviceInfo)(membc1771f0) 157 | allocsbc1771f0 := new(cgoAllocMap) 158 | var cstructVersion_allocs *cgoAllocMap 159 | refbc1771f0.structVersion, cstructVersion_allocs = (C.int)(x.StructVersion), cgoAllocsUnknown 160 | allocsbc1771f0.Borrow(cstructVersion_allocs) 161 | 162 | var cinterf_allocs *cgoAllocMap 163 | refbc1771f0.interf, cinterf_allocs = unpackPCharString(x.Interf) 164 | allocsbc1771f0.Borrow(cinterf_allocs) 165 | 166 | var cname_allocs *cgoAllocMap 167 | refbc1771f0.name, cname_allocs = unpackPCharString(x.Name) 168 | allocsbc1771f0.Borrow(cname_allocs) 169 | 170 | var cinput_allocs *cgoAllocMap 171 | refbc1771f0.input, cinput_allocs = (C.int)(x.Input), cgoAllocsUnknown 172 | allocsbc1771f0.Borrow(cinput_allocs) 173 | 174 | var coutput_allocs *cgoAllocMap 175 | refbc1771f0.output, coutput_allocs = (C.int)(x.Output), cgoAllocsUnknown 176 | allocsbc1771f0.Borrow(coutput_allocs) 177 | 178 | var copened_allocs *cgoAllocMap 179 | refbc1771f0.opened, copened_allocs = (C.int)(x.Opened), cgoAllocsUnknown 180 | allocsbc1771f0.Borrow(copened_allocs) 181 | 182 | x.refbc1771f0 = refbc1771f0 183 | x.allocsbc1771f0 = allocsbc1771f0 184 | return refbc1771f0, allocsbc1771f0 185 | 186 | } 187 | 188 | // PassValue does the same as PassRef except that it will try to dereference the returned pointer. 189 | func (x DeviceInfo) PassValue() (C.PmDeviceInfo, *cgoAllocMap) { 190 | if x.refbc1771f0 != nil { 191 | return *x.refbc1771f0, nil 192 | } 193 | ref, allocs := x.PassRef() 194 | return *ref, allocs 195 | } 196 | 197 | // Deref uses the underlying reference to C object and fills the wrapping struct with values. 198 | // Do not forget to call this method whether you get a struct for C object and want to read its values. 199 | func (x *DeviceInfo) Deref() { 200 | if x.refbc1771f0 == nil { 201 | return 202 | } 203 | x.StructVersion = (int32)(x.refbc1771f0.structVersion) 204 | x.Interf = packPCharString(x.refbc1771f0.interf) 205 | x.Name = packPCharString(x.refbc1771f0.name) 206 | x.Input = (int32)(x.refbc1771f0.input) 207 | x.Output = (int32)(x.refbc1771f0.output) 208 | x.Opened = (int32)(x.refbc1771f0.opened) 209 | } 210 | 211 | func (x TimeProcPtr) PassRef() (ref *C.PmTimeProcPtr, allocs *cgoAllocMap) { 212 | if x == nil { 213 | return nil, nil 214 | } 215 | if timeProcPtrAE9B2A7BFunc == nil { 216 | timeProcPtrAE9B2A7BFunc = x 217 | } 218 | return (*C.PmTimeProcPtr)(C.PmTimeProcPtr_ae9b2a7b), nil 219 | } 220 | 221 | func (x TimeProcPtr) PassValue() (ref C.PmTimeProcPtr, allocs *cgoAllocMap) { 222 | if x == nil { 223 | return nil, nil 224 | } 225 | if timeProcPtrAE9B2A7BFunc == nil { 226 | timeProcPtrAE9B2A7BFunc = x 227 | } 228 | return (C.PmTimeProcPtr)(C.PmTimeProcPtr_ae9b2a7b), nil 229 | } 230 | 231 | func NewTimeProcPtrRef(ref unsafe.Pointer) *TimeProcPtr { 232 | return (*TimeProcPtr)(ref) 233 | } 234 | 235 | //export timeProcPtrAE9B2A7B 236 | func timeProcPtrAE9B2A7B(ctimeInfo unsafe.Pointer) C.PmTimestamp { 237 | if timeProcPtrAE9B2A7BFunc != nil { 238 | timeInfoae9b2a7b := (unsafe.Pointer)(unsafe.Pointer(ctimeInfo)) 239 | retae9b2a7b := timeProcPtrAE9B2A7BFunc(timeInfoae9b2a7b) 240 | ret, _ := (C.PmTimestamp)(retae9b2a7b), cgoAllocsUnknown 241 | return ret 242 | } 243 | panic("callback func has not been set (race?)") 244 | } 245 | 246 | var timeProcPtrAE9B2A7BFunc TimeProcPtr 247 | 248 | // allocEventMemory allocates memory for type C.PmEvent in C. 249 | // The caller is responsible for freeing the this memory via C.free. 250 | func allocEventMemory(n int) unsafe.Pointer { 251 | mem, err := C.calloc(C.size_t(n), (C.size_t)(sizeOfEventValue)) 252 | if err != nil { 253 | panic("memory alloc error: " + err.Error()) 254 | } 255 | return mem 256 | } 257 | 258 | const sizeOfEventValue = unsafe.Sizeof([1]C.PmEvent{}) 259 | 260 | // Ref returns the underlying reference to C object or nil if struct is nil. 261 | func (x *Event) Ref() *C.PmEvent { 262 | if x == nil { 263 | return nil 264 | } 265 | return x.ref369ef8f3 266 | } 267 | 268 | // Free invokes alloc map's free mechanism that cleanups any allocated memory using C free. 269 | // Does nothing if struct is nil or has no allocation map. 270 | func (x *Event) Free() { 271 | if x != nil && x.allocs369ef8f3 != nil { 272 | x.allocs369ef8f3.(*cgoAllocMap).Free() 273 | x.ref369ef8f3 = nil 274 | } 275 | } 276 | 277 | // NewEventRef creates a new wrapper struct with underlying reference set to the original C object. 278 | // Returns nil if the provided pointer to C object is nil too. 279 | func NewEventRef(ref unsafe.Pointer) *Event { 280 | if ref == nil { 281 | return nil 282 | } 283 | obj := new(Event) 284 | obj.ref369ef8f3 = (*C.PmEvent)(unsafe.Pointer(ref)) 285 | return obj 286 | } 287 | 288 | // PassRef returns the underlying C object, otherwise it will allocate one and set its values 289 | // from this wrapping struct, counting allocations into an allocation map. 290 | func (x *Event) PassRef() (*C.PmEvent, *cgoAllocMap) { 291 | if x == nil { 292 | return nil, nil 293 | } else if x.ref369ef8f3 != nil { 294 | return x.ref369ef8f3, nil 295 | } 296 | mem369ef8f3 := allocEventMemory(1) 297 | ref369ef8f3 := (*C.PmEvent)(mem369ef8f3) 298 | allocs369ef8f3 := new(cgoAllocMap) 299 | var cmessage_allocs *cgoAllocMap 300 | ref369ef8f3.message, cmessage_allocs = (C.PmMessage)(x.Message), cgoAllocsUnknown 301 | allocs369ef8f3.Borrow(cmessage_allocs) 302 | 303 | var ctimestamp_allocs *cgoAllocMap 304 | ref369ef8f3.timestamp, ctimestamp_allocs = (C.PmTimestamp)(x.Timestamp), cgoAllocsUnknown 305 | allocs369ef8f3.Borrow(ctimestamp_allocs) 306 | 307 | x.ref369ef8f3 = ref369ef8f3 308 | x.allocs369ef8f3 = allocs369ef8f3 309 | return ref369ef8f3, allocs369ef8f3 310 | 311 | } 312 | 313 | // PassValue does the same as PassRef except that it will try to dereference the returned pointer. 314 | func (x Event) PassValue() (C.PmEvent, *cgoAllocMap) { 315 | if x.ref369ef8f3 != nil { 316 | return *x.ref369ef8f3, nil 317 | } 318 | ref, allocs := x.PassRef() 319 | return *ref, allocs 320 | } 321 | 322 | // Deref uses the underlying reference to C object and fills the wrapping struct with values. 323 | // Do not forget to call this method whether you get a struct for C object and want to read its values. 324 | func (x *Event) Deref() { 325 | if x.ref369ef8f3 == nil { 326 | return 327 | } 328 | x.Message = (Message)(x.ref369ef8f3.message) 329 | x.Timestamp = (Timestamp)(x.ref369ef8f3.timestamp) 330 | } 331 | 332 | type sliceHeader struct { 333 | Data uintptr 334 | Len int 335 | Cap int 336 | } 337 | 338 | const sizeOfPtr = unsafe.Sizeof(&struct{}{}) 339 | 340 | // unpackArgSEvent transforms a sliced Go data structure into plain C format. 341 | func unpackArgSEvent(x []Event) (unpacked *C.PmEvent, allocs *cgoAllocMap) { 342 | if x == nil { 343 | return nil, nil 344 | } 345 | allocs = new(cgoAllocMap) 346 | defer runtime.SetFinalizer(&unpacked, func(**C.PmEvent) { 347 | go allocs.Free() 348 | }) 349 | 350 | len0 := len(x) 351 | mem0 := allocEventMemory(len0) 352 | allocs.Add(mem0) 353 | h0 := &sliceHeader{ 354 | Data: uintptr(mem0), 355 | Cap: len0, 356 | Len: len0, 357 | } 358 | v0 := *(*[]C.PmEvent)(unsafe.Pointer(h0)) 359 | for i0 := range x { 360 | v0[i0], _ = x[i0].PassValue() 361 | } 362 | h := (*sliceHeader)(unsafe.Pointer(&v0)) 363 | unpacked = (*C.PmEvent)(unsafe.Pointer(h.Data)) 364 | return 365 | } 366 | 367 | // packSEvent reads sliced Go data structure out from plain C format. 368 | func packSEvent(v []Event, ptr0 *C.PmEvent) { 369 | const m = 0x7fffffff 370 | for i0 := range v { 371 | ptr1 := (*(*[m / sizeOfEventValue]C.PmEvent)(unsafe.Pointer(ptr0)))[i0] 372 | v[i0] = *NewEventRef(unsafe.Pointer(&ptr1)) 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /pm/cgo_helpers.h: -------------------------------------------------------------------------------- 1 | // THE AUTOGENERATED LICENSE. ALL THE RIGHTS ARE RESERVED BY ROBOTS. 2 | 3 | // WARNING: This file has automatically been generated on Tue, 05 Sep 2017 19:52:40 MSK. 4 | // By https://git.io/cgogen. DO NOT EDIT. 5 | 6 | #include "portmidi.h" 7 | #include 8 | #pragma once 9 | 10 | #define __CGOGEN 1 11 | 12 | // PmTimeProcPtr_ae9b2a7b is a proxy for callback PmTimeProcPtr. 13 | int PmTimeProcPtr_ae9b2a7b(void* time_info); 14 | 15 | -------------------------------------------------------------------------------- /pm/const.go: -------------------------------------------------------------------------------- 1 | // THE AUTOGENERATED LICENSE. ALL THE RIGHTS ARE RESERVED BY ROBOTS. 2 | 3 | // WARNING: This file has automatically been generated on Tue, 05 Sep 2017 19:52:40 MSK. 4 | // By https://git.io/cgogen. DO NOT EDIT. 5 | 6 | package pm 7 | 8 | /* 9 | #cgo LDFLAGS: -lportmidi 10 | #include "portmidi.h" 11 | #include 12 | #include "cgo_helpers.h" 13 | */ 14 | import "C" 15 | 16 | const ( 17 | // DefaultSysexBufferSize as defined in pm/portmidi.h:123 18 | DefaultSysexBufferSize = 1024 19 | // HostErrorMsgLen as defined in pm/portmidi.h:196 20 | HostErrorMsgLen = uint32(256) 21 | // nodevice as defined in pm/portmidi.h:206 22 | nodevice = -1 23 | // FiltActive as defined in pm/portmidi.h:390 24 | FiltActive = (1 << 0x0E) 25 | // FiltSysex as defined in pm/portmidi.h:392 26 | FiltSysex = (1 << 0x00) 27 | // FiltClock as defined in pm/portmidi.h:394 28 | FiltClock = (1 << 0x08) 29 | // FiltPlay as defined in pm/portmidi.h:396 30 | FiltPlay = ((1 << 0x0A) | (1 << 0x0C) | (1 << 0x0B)) 31 | // FiltTick as defined in pm/portmidi.h:398 32 | FiltTick = (1 << 0x09) 33 | // FiltFd as defined in pm/portmidi.h:400 34 | FiltFd = (1 << 0x0D) 35 | // FiltUndefined as defined in pm/portmidi.h:402 36 | FiltUndefined = FiltFd 37 | // FiltReset as defined in pm/portmidi.h:404 38 | FiltReset = (1 << 0x0F) 39 | // FiltRealtime as defined in pm/portmidi.h:406 40 | FiltRealtime = (FiltActive | FiltSysex | FiltClock | FiltPlay | FiltUndefined | FiltReset | FiltTick) 41 | // FiltNote as defined in pm/portmidi.h:409 42 | FiltNote = ((1 << 0x19) | (1 << 0x18)) 43 | // FiltChannelAftertouch as defined in pm/portmidi.h:411 44 | FiltChannelAftertouch = (1 << 0x1D) 45 | // FiltPolyAftertouch as defined in pm/portmidi.h:413 46 | FiltPolyAftertouch = (1 << 0x1A) 47 | // FiltAftertouch as defined in pm/portmidi.h:415 48 | FiltAftertouch = (FiltChannelAftertouch | FiltPolyAftertouch) 49 | // FiltProgram as defined in pm/portmidi.h:417 50 | FiltProgram = (1 << 0x1C) 51 | // FiltControl as defined in pm/portmidi.h:419 52 | FiltControl = (1 << 0x1B) 53 | // FiltPitchbend as defined in pm/portmidi.h:421 54 | FiltPitchbend = (1 << 0x1E) 55 | // FiltMtc as defined in pm/portmidi.h:423 56 | FiltMtc = (1 << 0x01) 57 | // FiltSongPosition as defined in pm/portmidi.h:425 58 | FiltSongPosition = (1 << 0x02) 59 | // FiltSongSelect as defined in pm/portmidi.h:427 60 | FiltSongSelect = (1 << 0x03) 61 | // FiltTune as defined in pm/portmidi.h:429 62 | FiltTune = (1 << 0x06) 63 | // FiltSystemcommon as defined in pm/portmidi.h:431 64 | FiltSystemcommon = (FiltMtc | FiltSongPosition | FiltSongSelect | FiltTune) 65 | ) 66 | 67 | // Error as declared in pm/portmidi.h:147 68 | type Error int32 69 | 70 | // Error enumeration from pm/portmidi.h:147 71 | const ( 72 | noerror Error = iota 73 | nodata Error = 0 74 | gotdata Error = 1 75 | hosterror Error = -10000 76 | invaliddeviceid Error = -9999 77 | insufficientmemory Error = -9998 78 | buffertoosmall Error = -9997 79 | bufferoverflow Error = -9996 80 | badptr Error = -9995 81 | baddata Error = -9994 82 | internalerror Error = -9993 83 | buffermaxsize Error = -9992 84 | ) 85 | -------------------------------------------------------------------------------- /pm/doc.go: -------------------------------------------------------------------------------- 1 | // THE AUTOGENERATED LICENSE. ALL THE RIGHTS ARE RESERVED BY ROBOTS. 2 | 3 | // WARNING: This file has automatically been generated on Tue, 05 Sep 2017 19:52:40 MSK. 4 | // By https://git.io/cgogen. DO NOT EDIT. 5 | 6 | /* 7 | Package pm provides Go bindings for portmidi. 8 | */ 9 | package pm 10 | -------------------------------------------------------------------------------- /pm/error.go: -------------------------------------------------------------------------------- 1 | package pm 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | var True Error = 1 9 | var False Error = 0 10 | 11 | const NoDevice = nodevice 12 | 13 | var ( 14 | ErrHostError = errors.New("portmidi: host error") 15 | // ErrInvalidDeviceID means out of range or 16 | // output device when input is requested or 17 | // input device when output is requested or 18 | // device is already opened. 19 | ErrInvalidDeviceID = errors.New("portmidi: invalid DeviceID") 20 | ErrInsufficientMemory = errors.New("portmidi: insufficient memory") 21 | ErrBufferTooSmall = errors.New("portmidi: buffer too small") 22 | ErrBufferOverflow = errors.New("portmidi: buffer overflow") 23 | // ErrBadPtr is a result of PortMidiStream parameter being nil or 24 | // stream is not opened or 25 | // stream is output when input is required or 26 | // stream is input when output is required. 27 | ErrBadPtr = errors.New("portmidi: bad pointer") 28 | // ErrBadData means illegal midi data, e.g. missing EOX. 29 | ErrBadData = errors.New("portmidi: bad data") 30 | ErrInternalError = errors.New("portmidi: internal error") 31 | // ErrBufferMaxSize means buffer is already as large as it can be. 32 | ErrBufferMaxSize = errors.New("portmidi: buffer max size") 33 | ErrUnknown = errors.New("portmidi: unknown error") 34 | ) 35 | 36 | func HasData(e Error) bool { 37 | if e == gotdata { 38 | return true 39 | } 40 | return false 41 | } 42 | 43 | func ToError(e Error) error { 44 | switch e { 45 | case noerror, gotdata: 46 | return nil 47 | case hosterror: 48 | return ErrHostError 49 | case invaliddeviceid: 50 | return ErrInvalidDeviceID 51 | case insufficientmemory: 52 | return ErrInsufficientMemory 53 | case buffertoosmall: 54 | return ErrBufferTooSmall 55 | case bufferoverflow: 56 | return ErrBufferOverflow 57 | case badptr: 58 | return ErrBadPtr 59 | case baddata: 60 | return ErrBadData 61 | case internalerror: 62 | return ErrInternalError 63 | case buffermaxsize: 64 | return ErrBufferMaxSize 65 | default: 66 | return fmt.Errorf("portmidi: %s", GetErrorText(e)) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /pm/pm.go: -------------------------------------------------------------------------------- 1 | // THE AUTOGENERATED LICENSE. ALL THE RIGHTS ARE RESERVED BY ROBOTS. 2 | 3 | // WARNING: This file has automatically been generated on Tue, 05 Sep 2017 19:52:40 MSK. 4 | // By https://git.io/cgogen. DO NOT EDIT. 5 | 6 | package pm 7 | 8 | /* 9 | #cgo LDFLAGS: -lportmidi 10 | #include "portmidi.h" 11 | #include 12 | #include "cgo_helpers.h" 13 | */ 14 | import "C" 15 | import "unsafe" 16 | 17 | // Initialize function as declared in pm/portmidi.h:153 18 | func Initialize() Error { 19 | __ret := C.Pm_Initialize() 20 | __v := (Error)(__ret) 21 | return __v 22 | } 23 | 24 | // Terminate function as declared in pm/portmidi.h:159 25 | func Terminate() Error { 26 | __ret := C.Pm_Terminate() 27 | __v := (Error)(__ret) 28 | return __v 29 | } 30 | 31 | // HasHostError function as declared in pm/portmidi.h:180 32 | func HasHostError(stream *PortMidiStream) int32 { 33 | cstream, _ := (unsafe.Pointer)(unsafe.Pointer(stream)), cgoAllocsUnknown 34 | __ret := C.Pm_HasHostError(cstream) 35 | __v := (int32)(__ret) 36 | return __v 37 | } 38 | 39 | // GetErrorText function as declared in pm/portmidi.h:187 40 | func GetErrorText(errnum Error) string { 41 | cerrnum, _ := (C.PmError)(errnum), cgoAllocsUnknown 42 | __ret := C.Pm_GetErrorText(cerrnum) 43 | __v := packPCharString(__ret) 44 | return __v 45 | } 46 | 47 | // GetHostErrorText function as declared in pm/portmidi.h:193 48 | func GetHostErrorText(msg []byte, len uint32) { 49 | cmsg, _ := (*C.char)(unsafe.Pointer((*sliceHeader)(unsafe.Pointer(&msg)).Data)), cgoAllocsUnknown 50 | clen, _ := (C.uint)(len), cgoAllocsUnknown 51 | C.Pm_GetHostErrorText(cmsg, clen) 52 | } 53 | 54 | // CountDevices function as declared in pm/portmidi.h:218 55 | func CountDevices() int32 { 56 | __ret := C.Pm_CountDevices() 57 | __v := (int32)(__ret) 58 | return __v 59 | } 60 | 61 | // GetDefaultInputDeviceID function as declared in pm/portmidi.h:261 62 | func GetDefaultInputDeviceID() DeviceID { 63 | __ret := C.Pm_GetDefaultInputDeviceID() 64 | __v := (DeviceID)(__ret) 65 | return __v 66 | } 67 | 68 | // GetDefaultOutputDeviceID function as declared in pm/portmidi.h:263 69 | func GetDefaultOutputDeviceID() DeviceID { 70 | __ret := C.Pm_GetDefaultOutputDeviceID() 71 | __v := (DeviceID)(__ret) 72 | return __v 73 | } 74 | 75 | // GetDeviceInfo function as declared in pm/portmidi.h:287 76 | func GetDeviceInfo(id DeviceID) *DeviceInfo { 77 | cid, _ := (C.PmDeviceID)(id), cgoAllocsUnknown 78 | __ret := C.Pm_GetDeviceInfo(cid) 79 | __v := NewDeviceInfoRef(unsafe.Pointer(__ret)) 80 | return __v 81 | } 82 | 83 | // OpenInput function as declared in pm/portmidi.h:353 84 | func OpenInput(stream **PortMidiStream, inputDevice DeviceID, inputDriverInfo unsafe.Pointer, bufferSize int32, timeProc TimeProcPtr, timeInfo unsafe.Pointer) Error { 85 | cstream, _ := (*unsafe.Pointer)(unsafe.Pointer(stream)), cgoAllocsUnknown 86 | cinputDevice, _ := (C.PmDeviceID)(inputDevice), cgoAllocsUnknown 87 | cinputDriverInfo, _ := (unsafe.Pointer)(unsafe.Pointer(inputDriverInfo)), cgoAllocsUnknown 88 | cbufferSize, _ := (C.int32_t)(bufferSize), cgoAllocsUnknown 89 | ctimeProc, _ := timeProc.PassValue() 90 | ctimeInfo, _ := (unsafe.Pointer)(unsafe.Pointer(timeInfo)), cgoAllocsUnknown 91 | __ret := C.Pm_OpenInput(cstream, cinputDevice, cinputDriverInfo, cbufferSize, ctimeProc, ctimeInfo) 92 | __v := (Error)(__ret) 93 | return __v 94 | } 95 | 96 | // OpenOutput function as declared in pm/portmidi.h:360 97 | func OpenOutput(stream **PortMidiStream, outputDevice DeviceID, outputDriverInfo unsafe.Pointer, bufferSize int32, timeProc TimeProcPtr, timeInfo unsafe.Pointer, latency int32) Error { 98 | cstream, _ := (*unsafe.Pointer)(unsafe.Pointer(stream)), cgoAllocsUnknown 99 | coutputDevice, _ := (C.PmDeviceID)(outputDevice), cgoAllocsUnknown 100 | coutputDriverInfo, _ := (unsafe.Pointer)(unsafe.Pointer(outputDriverInfo)), cgoAllocsUnknown 101 | cbufferSize, _ := (C.int32_t)(bufferSize), cgoAllocsUnknown 102 | ctimeProc, _ := timeProc.PassValue() 103 | ctimeInfo, _ := (unsafe.Pointer)(unsafe.Pointer(timeInfo)), cgoAllocsUnknown 104 | clatency, _ := (C.int32_t)(latency), cgoAllocsUnknown 105 | __ret := C.Pm_OpenOutput(cstream, coutputDevice, coutputDriverInfo, cbufferSize, ctimeProc, ctimeInfo, clatency) 106 | __v := (Error)(__ret) 107 | return __v 108 | } 109 | 110 | // SetFilter function as declared in pm/portmidi.h:434 111 | func SetFilter(stream *PortMidiStream, filters int32) Error { 112 | cstream, _ := (unsafe.Pointer)(unsafe.Pointer(stream)), cgoAllocsUnknown 113 | cfilters, _ := (C.int32_t)(filters), cgoAllocsUnknown 114 | __ret := C.Pm_SetFilter(cstream, cfilters) 115 | __v := (Error)(__ret) 116 | return __v 117 | } 118 | 119 | // SetChannelMask function as declared in pm/portmidi.h:452 120 | func SetChannelMask(stream *PortMidiStream, mask int32) Error { 121 | cstream, _ := (unsafe.Pointer)(unsafe.Pointer(stream)), cgoAllocsUnknown 122 | cmask, _ := (C.int)(mask), cgoAllocsUnknown 123 | __ret := C.Pm_SetChannelMask(cstream, cmask) 124 | __v := (Error)(__ret) 125 | return __v 126 | } 127 | 128 | // Abort function as declared in pm/portmidi.h:462 129 | func Abort(stream *PortMidiStream) Error { 130 | cstream, _ := (unsafe.Pointer)(unsafe.Pointer(stream)), cgoAllocsUnknown 131 | __ret := C.Pm_Abort(cstream) 132 | __v := (Error)(__ret) 133 | return __v 134 | } 135 | 136 | // Close function as declared in pm/portmidi.h:469 137 | func Close(stream *PortMidiStream) Error { 138 | cstream, _ := (unsafe.Pointer)(unsafe.Pointer(stream)), cgoAllocsUnknown 139 | __ret := C.Pm_Close(cstream) 140 | __v := (Error)(__ret) 141 | return __v 142 | } 143 | 144 | // Synchronize function as declared in pm/portmidi.h:494 145 | func Synchronize(stream *PortMidiStream) Error { 146 | cstream, _ := (unsafe.Pointer)(unsafe.Pointer(stream)), cgoAllocsUnknown 147 | __ret := C.Pm_Synchronize(cstream) 148 | __v := (Error)(__ret) 149 | return __v 150 | } 151 | 152 | // Read function as declared in pm/portmidi.h:613 153 | func Read(stream *PortMidiStream, buffer []Event, length int32) int32 { 154 | cstream, _ := (unsafe.Pointer)(unsafe.Pointer(stream)), cgoAllocsUnknown 155 | cbuffer, _ := unpackArgSEvent(buffer) 156 | clength, _ := (C.int32_t)(length), cgoAllocsUnknown 157 | __ret := C.Pm_Read(cstream, cbuffer, clength) 158 | packSEvent(buffer, cbuffer) 159 | __v := (int32)(__ret) 160 | return __v 161 | } 162 | 163 | // Poll function as declared in pm/portmidi.h:619 164 | func Poll(stream *PortMidiStream) Error { 165 | cstream, _ := (unsafe.Pointer)(unsafe.Pointer(stream)), cgoAllocsUnknown 166 | __ret := C.Pm_Poll(cstream) 167 | __v := (Error)(__ret) 168 | return __v 169 | } 170 | 171 | // Write function as declared in pm/portmidi.h:634 172 | func Write(stream *PortMidiStream, buffer []Event, length int32) Error { 173 | cstream, _ := (unsafe.Pointer)(unsafe.Pointer(stream)), cgoAllocsUnknown 174 | cbuffer, _ := unpackArgSEvent(buffer) 175 | clength, _ := (C.int32_t)(length), cgoAllocsUnknown 176 | __ret := C.Pm_Write(cstream, cbuffer, clength) 177 | packSEvent(buffer, cbuffer) 178 | __v := (Error)(__ret) 179 | return __v 180 | } 181 | 182 | // WriteShort function as declared in pm/portmidi.h:642 183 | func WriteShort(stream *PortMidiStream, when Timestamp, msg int32) Error { 184 | cstream, _ := (unsafe.Pointer)(unsafe.Pointer(stream)), cgoAllocsUnknown 185 | cwhen, _ := (C.PmTimestamp)(when), cgoAllocsUnknown 186 | cmsg, _ := (C.int32_t)(msg), cgoAllocsUnknown 187 | __ret := C.Pm_WriteShort(cstream, cwhen, cmsg) 188 | __v := (Error)(__ret) 189 | return __v 190 | } 191 | 192 | // WriteSysEx function as declared in pm/portmidi.h:647 193 | func WriteSysEx(stream *PortMidiStream, when Timestamp, msg []byte) Error { 194 | cstream, _ := (unsafe.Pointer)(unsafe.Pointer(stream)), cgoAllocsUnknown 195 | cwhen, _ := (C.PmTimestamp)(when), cgoAllocsUnknown 196 | cmsg, _ := (*C.uchar)(unsafe.Pointer((*sliceHeader)(unsafe.Pointer(&msg)).Data)), cgoAllocsUnknown 197 | __ret := C.Pm_WriteSysEx(cstream, cwhen, cmsg) 198 | __v := (Error)(__ret) 199 | return __v 200 | } 201 | -------------------------------------------------------------------------------- /pm/portmidi.h: -------------------------------------------------------------------------------- 1 | #ifndef PORT_MIDI_H 2 | #define PORT_MIDI_H 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif /* __cplusplus */ 6 | 7 | /* 8 | * PortMidi Portable Real-Time MIDI Library 9 | * PortMidi API Header File 10 | * Latest version available at: http://sourceforge.net/projects/portmedia 11 | * 12 | * Copyright (c) 1999-2000 Ross Bencina and Phil Burk 13 | * Copyright (c) 2001-2006 Roger B. Dannenberg 14 | * 15 | * Permission is hereby granted, free of charge, to any person obtaining 16 | * a copy of this software and associated documentation files 17 | * (the "Software"), to deal in the Software without restriction, 18 | * including without limitation the rights to use, copy, modify, merge, 19 | * publish, distribute, sublicense, and/or sell copies of the Software, 20 | * and to permit persons to whom the Software is furnished to do so, 21 | * subject to the following conditions: 22 | * 23 | * The above copyright notice and this permission notice shall be 24 | * included in all copies or substantial portions of the Software. 25 | * 26 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 27 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 28 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 29 | * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR 30 | * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 31 | * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 32 | * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 33 | */ 34 | 35 | /* 36 | * The text above constitutes the entire PortMidi license; however, 37 | * the PortMusic community also makes the following non-binding requests: 38 | * 39 | * Any person wishing to distribute modifications to the Software is 40 | * requested to send the modifications to the original developer so that 41 | * they can be incorporated into the canonical version. It is also 42 | * requested that these non-binding requests be included along with the 43 | * license above. 44 | */ 45 | 46 | /* CHANGELOG FOR PORTMIDI 47 | * (see ../CHANGELOG.txt) 48 | * 49 | * NOTES ON HOST ERROR REPORTING: 50 | * 51 | * PortMidi errors (of type PmError) are generic, system-independent errors. 52 | * When an error does not map to one of the more specific PmErrors, the 53 | * catch-all code pmHostError is returned. This means that PortMidi has 54 | * retained a more specific system-dependent error code. The caller can 55 | * get more information by calling Pm_HasHostError() to test if there is 56 | * a pending host error, and Pm_GetHostErrorText() to get a text string 57 | * describing the error. Host errors are reported on a per-device basis 58 | * because only after you open a device does PortMidi have a place to 59 | * record the host error code. I.e. only 60 | * those routines that receive a (PortMidiStream *) argument check and 61 | * report errors. One exception to this is that Pm_OpenInput() and 62 | * Pm_OpenOutput() can report errors even though when an error occurs, 63 | * there is no PortMidiStream* to hold the error. Fortunately, both 64 | * of these functions return any error immediately, so we do not really 65 | * need per-device error memory. Instead, any host error code is stored 66 | * in a global, pmHostError is returned, and the user can call 67 | * Pm_GetHostErrorText() to get the error message (and the invalid stream 68 | * parameter will be ignored.) The functions 69 | * pm_init and pm_term do not fail or raise 70 | * errors. The job of pm_init is to locate all available devices so that 71 | * the caller can get information via PmDeviceInfo(). If an error occurs, 72 | * the device is simply not listed as available. 73 | * 74 | * Host errors come in two flavors: 75 | * a) host error 76 | * b) host error during callback 77 | * These can occur w/midi input or output devices. (b) can only happen 78 | * asynchronously (during callback routines), whereas (a) only occurs while 79 | * synchronously running PortMidi and any resulting system dependent calls. 80 | * Both (a) and (b) are reported by the next read or write call. You can 81 | * also query for asynchronous errors (b) at any time by calling 82 | * Pm_HasHostError(). 83 | * 84 | * NOTES ON COMPILE-TIME SWITCHES 85 | * 86 | * DEBUG assumes stdio and a console. Use this if you want automatic, simple 87 | * error reporting, e.g. for prototyping. If you are using MFC or some 88 | * other graphical interface with no console, DEBUG probably should be 89 | * undefined. 90 | * PM_CHECK_ERRORS more-or-less takes over error checking for return values, 91 | * stopping your program and printing error messages when an error 92 | * occurs. This also uses stdio for console text I/O. 93 | */ 94 | 95 | #ifndef WIN32 96 | // Linux and OS X have stdint.h 97 | #include 98 | #else 99 | #ifndef INT32_DEFINED 100 | // rather than having users install a special .h file for windows, 101 | // just put the required definitions inline here. porttime.h uses 102 | // these too, so the definitions are (unfortunately) duplicated there 103 | typedef int int32_t; 104 | typedef unsigned int uint32_t; 105 | #define INT32_DEFINED 106 | #endif 107 | #endif 108 | 109 | #ifdef _WINDLL 110 | #define PMEXPORT __declspec(dllexport) 111 | #else 112 | #define PMEXPORT 113 | #endif 114 | 115 | #ifndef FALSE 116 | #define FALSE 0 117 | #endif 118 | #ifndef TRUE 119 | #define TRUE 1 120 | #endif 121 | 122 | /* default size of buffers for sysex transmission: */ 123 | #define PM_DEFAULT_SYSEX_BUFFER_SIZE 1024 124 | 125 | /** List of portmidi errors.*/ 126 | typedef enum { 127 | pmNoError = 0, 128 | pmNoData = 0, /**< A "no error" return that also indicates no data avail. */ 129 | pmGotData = 1, /**< A "no error" return that also indicates data available */ 130 | pmHostError = -10000, 131 | pmInvalidDeviceId, /** out of range or 132 | * output device when input is requested or 133 | * input device when output is requested or 134 | * device is already opened 135 | */ 136 | pmInsufficientMemory, 137 | pmBufferTooSmall, 138 | pmBufferOverflow, 139 | pmBadPtr, /* PortMidiStream parameter is NULL or 140 | * stream is not opened or 141 | * stream is output when input is required or 142 | * stream is input when output is required */ 143 | pmBadData, /** illegal midi data, e.g. missing EOX */ 144 | pmInternalError, 145 | pmBufferMaxSize /** buffer is already as large as it can be */ 146 | /* NOTE: If you add a new error type, be sure to update Pm_GetErrorText() */ 147 | } PmError; 148 | 149 | /** 150 | Pm_Initialize() is the library initialisation function - call this before 151 | using the library. 152 | */ 153 | PMEXPORT PmError Pm_Initialize( void ); 154 | 155 | /** 156 | Pm_Terminate() is the library termination function - call this after 157 | using the library. 158 | */ 159 | PMEXPORT PmError Pm_Terminate( void ); 160 | 161 | /** A single PortMidiStream is a descriptor for an open MIDI device. 162 | */ 163 | typedef void PortMidiStream; 164 | #define PmStream PortMidiStream 165 | 166 | /** 167 | Test whether stream has a pending host error. Normally, the client finds 168 | out about errors through returned error codes, but some errors can occur 169 | asynchronously where the client does not 170 | explicitly call a function, and therefore cannot receive an error code. 171 | The client can test for a pending error using Pm_HasHostError(). If true, 172 | the error can be accessed and cleared by calling Pm_GetErrorText(). 173 | Errors are also cleared by calling other functions that can return 174 | errors, e.g. Pm_OpenInput(), Pm_OpenOutput(), Pm_Read(), Pm_Write(). The 175 | client does not need to call Pm_HasHostError(). Any pending error will be 176 | reported the next time the client performs an explicit function call on 177 | the stream, e.g. an input or output operation. Until the error is cleared, 178 | no new error codes will be obtained, even for a different stream. 179 | */ 180 | PMEXPORT int Pm_HasHostError( PortMidiStream * stream ); 181 | 182 | 183 | /** Translate portmidi error number into human readable message. 184 | These strings are constants (set at compile time) so client has 185 | no need to allocate storage 186 | */ 187 | PMEXPORT const char *Pm_GetErrorText( PmError errnum ); 188 | 189 | /** Translate portmidi host error into human readable message. 190 | These strings are computed at run time, so client has to allocate storage. 191 | After this routine executes, the host error is cleared. 192 | */ 193 | PMEXPORT void Pm_GetHostErrorText(char * msg, unsigned int len); 194 | 195 | #define HDRLENGTH 50 196 | #define PM_HOST_ERROR_MSG_LEN 256u /* any host error msg will occupy less 197 | than this number of characters */ 198 | 199 | /** 200 | Device enumeration mechanism. 201 | 202 | Device ids range from 0 to Pm_CountDevices()-1. 203 | 204 | */ 205 | typedef int PmDeviceID; 206 | #define pmNoDevice -1 207 | typedef struct { 208 | int structVersion; /**< this internal structure version */ 209 | const char *interf; /**< underlying MIDI API, e.g. MMSystem or DirectX */ 210 | const char *name; /**< device name, e.g. USB MidiSport 1x1 */ 211 | int input; /**< true iff input is available */ 212 | int output; /**< true iff output is available */ 213 | int opened; /**< used by generic PortMidi code to do error checking on arguments */ 214 | 215 | } PmDeviceInfo; 216 | 217 | /** Get devices count, ids range from 0 to Pm_CountDevices()-1. */ 218 | PMEXPORT int Pm_CountDevices( void ); 219 | /** 220 | Pm_GetDefaultInputDeviceID(), Pm_GetDefaultOutputDeviceID() 221 | 222 | Return the default device ID or pmNoDevice if there are no devices. 223 | The result (but not pmNoDevice) can be passed to Pm_OpenMidi(). 224 | 225 | The default device can be specified using a small application 226 | named pmdefaults that is part of the PortMidi distribution. This 227 | program in turn uses the Java Preferences object created by 228 | java.util.prefs.Preferences.userRoot().node("/PortMidi"); the 229 | preference is set by calling 230 | prefs.put("PM_RECOMMENDED_OUTPUT_DEVICE", prefName); 231 | or prefs.put("PM_RECOMMENDED_INPUT_DEVICE", prefName); 232 | 233 | In the statements above, prefName is a string describing the 234 | MIDI device in the form "interf, name" where interf identifies 235 | the underlying software system or API used by PortMdi to access 236 | devices and name is the name of the device. These correspond to 237 | the interf and name fields of a PmDeviceInfo. (Currently supported 238 | interfaces are "MMSystem" for Win32, "ALSA" for Linux, and 239 | "CoreMIDI" for OS X, so in fact, there is no choice of interface.) 240 | In "interf, name", the strings are actually substrings of 241 | the full interface and name strings. For example, the preference 242 | "Core, Sport" will match a device with interface "CoreMIDI" 243 | and name "In USB MidiSport 1x1". It will also match "CoreMIDI" 244 | and "In USB MidiSport 2x2". The devices are enumerated in device 245 | ID order, so the lowest device ID that matches the pattern becomes 246 | the default device. Finally, if the comma-space (", ") separator 247 | between interface and name parts of the preference is not found, 248 | the entire preference string is interpreted as a name, and the 249 | interface part is the empty string, which matches anything. 250 | 251 | On the MAC, preferences are stored in 252 | /Users/$NAME/Library/Preferences/com.apple.java.util.prefs.plist 253 | which is a binary file. In addition to the pmdefaults program, 254 | there are utilities that can read and edit this preference file. 255 | 256 | On the PC, 257 | 258 | On Linux, 259 | 260 | */ 261 | PMEXPORT PmDeviceID Pm_GetDefaultInputDeviceID( void ); 262 | /** see PmDeviceID Pm_GetDefaultInputDeviceID() */ 263 | PMEXPORT PmDeviceID Pm_GetDefaultOutputDeviceID( void ); 264 | 265 | /** 266 | PmTimestamp is used to represent a millisecond clock with arbitrary 267 | start time. The type is used for all MIDI timestampes and clocks. 268 | */ 269 | typedef int32_t PmTimestamp; 270 | typedef PmTimestamp (*PmTimeProcPtr)(void *time_info); 271 | 272 | /** TRUE if t1 before t2 */ 273 | #define PmBefore(t1,t2) ((t1-t2) < 0) 274 | /** 275 | \defgroup grp_device Input/Output Devices Handling 276 | @{ 277 | */ 278 | /** 279 | Pm_GetDeviceInfo() returns a pointer to a PmDeviceInfo structure 280 | referring to the device specified by id. 281 | If id is out of range the function returns NULL. 282 | 283 | The returned structure is owned by the PortMidi implementation and must 284 | not be manipulated or freed. The pointer is guaranteed to be valid 285 | between calls to Pm_Initialize() and Pm_Terminate(). 286 | */ 287 | PMEXPORT const PmDeviceInfo* Pm_GetDeviceInfo( PmDeviceID id ); 288 | 289 | /** 290 | Pm_OpenInput() and Pm_OpenOutput() open devices. 291 | 292 | stream is the address of a PortMidiStream pointer which will receive 293 | a pointer to the newly opened stream. 294 | 295 | inputDevice is the id of the device used for input (see PmDeviceID above). 296 | 297 | inputDriverInfo is a pointer to an optional driver specific data structure 298 | containing additional information for device setup or handle processing. 299 | inputDriverInfo is never required for correct operation. If not used 300 | inputDriverInfo should be NULL. 301 | 302 | outputDevice is the id of the device used for output (see PmDeviceID above.) 303 | 304 | outputDriverInfo is a pointer to an optional driver specific data structure 305 | containing additional information for device setup or handle processing. 306 | outputDriverInfo is never required for correct operation. If not used 307 | outputDriverInfo should be NULL. 308 | 309 | For input, the buffersize specifies the number of input events to be 310 | buffered waiting to be read using Pm_Read(). For output, buffersize 311 | specifies the number of output events to be buffered waiting for output. 312 | (In some cases -- see below -- PortMidi does not buffer output at all 313 | and merely passes data to a lower-level API, in which case buffersize 314 | is ignored.) 315 | 316 | latency is the delay in milliseconds applied to timestamps to determine 317 | when the output should actually occur. (If latency is < 0, 0 is assumed.) 318 | If latency is zero, timestamps are ignored and all output is delivered 319 | immediately. If latency is greater than zero, output is delayed until the 320 | message timestamp plus the latency. (NOTE: the time is measured relative 321 | to the time source indicated by time_proc. Timestamps are absolute, 322 | not relative delays or offsets.) In some cases, PortMidi can obtain 323 | better timing than your application by passing timestamps along to the 324 | device driver or hardware. Latency may also help you to synchronize midi 325 | data to audio data by matching midi latency to the audio buffer latency. 326 | 327 | time_proc is a pointer to a procedure that returns time in milliseconds. It 328 | may be NULL, in which case a default millisecond timebase (PortTime) is 329 | used. If the application wants to use PortTime, it should start the timer 330 | (call Pt_Start) before calling Pm_OpenInput or Pm_OpenOutput. If the 331 | application tries to start the timer *after* Pm_OpenInput or Pm_OpenOutput, 332 | it may get a ptAlreadyStarted error from Pt_Start, and the application's 333 | preferred time resolution and callback function will be ignored. 334 | time_proc result values are appended to incoming MIDI data, and time_proc 335 | times are used to schedule outgoing MIDI data (when latency is non-zero). 336 | 337 | time_info is a pointer passed to time_proc. 338 | 339 | Example: If I provide a timestamp of 5000, latency is 1, and time_proc 340 | returns 4990, then the desired output time will be when time_proc returns 341 | timestamp+latency = 5001. This will be 5001-4990 = 11ms from now. 342 | 343 | return value: 344 | Upon success Pm_Open() returns PmNoError and places a pointer to a 345 | valid PortMidiStream in the stream argument. 346 | If a call to Pm_Open() fails a nonzero error code is returned (see 347 | PMError above) and the value of port is invalid. 348 | 349 | Any stream that is successfully opened should eventually be closed 350 | by calling Pm_Close(). 351 | 352 | */ 353 | PMEXPORT PmError Pm_OpenInput( PortMidiStream** stream, 354 | PmDeviceID inputDevice, 355 | void *inputDriverInfo, 356 | int32_t bufferSize, 357 | PmTimeProcPtr time_proc, 358 | void *time_info ); 359 | 360 | PMEXPORT PmError Pm_OpenOutput( PortMidiStream** stream, 361 | PmDeviceID outputDevice, 362 | void *outputDriverInfo, 363 | int32_t bufferSize, 364 | PmTimeProcPtr time_proc, 365 | void *time_info, 366 | int32_t latency ); 367 | /** @} */ 368 | 369 | /** 370 | \defgroup grp_events_filters Events and Filters Handling 371 | @{ 372 | */ 373 | 374 | /* \function PmError Pm_SetFilter( PortMidiStream* stream, int32_t filters ) 375 | Pm_SetFilter() sets filters on an open input stream to drop selected 376 | input types. By default, only active sensing messages are filtered. 377 | To prohibit, say, active sensing and sysex messages, call 378 | Pm_SetFilter(stream, PM_FILT_ACTIVE | PM_FILT_SYSEX); 379 | 380 | Filtering is useful when midi routing or midi thru functionality is being 381 | provided by the user application. 382 | For example, you may want to exclude timing messages (clock, MTC, start/stop/continue), 383 | while allowing note-related messages to pass. 384 | Or you may be using a sequencer or drum-machine for MIDI clock information but want to 385 | exclude any notes it may play. 386 | */ 387 | 388 | /* Filter bit-mask definitions */ 389 | /** filter active sensing messages (0xFE): */ 390 | #define PM_FILT_ACTIVE (1 << 0x0E) 391 | /** filter system exclusive messages (0xF0): */ 392 | #define PM_FILT_SYSEX (1 << 0x00) 393 | /** filter MIDI clock message (0xF8) */ 394 | #define PM_FILT_CLOCK (1 << 0x08) 395 | /** filter play messages (start 0xFA, stop 0xFC, continue 0xFB) */ 396 | #define PM_FILT_PLAY ((1 << 0x0A) | (1 << 0x0C) | (1 << 0x0B)) 397 | /** filter tick messages (0xF9) */ 398 | #define PM_FILT_TICK (1 << 0x09) 399 | /** filter undefined FD messages */ 400 | #define PM_FILT_FD (1 << 0x0D) 401 | /** filter undefined real-time messages */ 402 | #define PM_FILT_UNDEFINED PM_FILT_FD 403 | /** filter reset messages (0xFF) */ 404 | #define PM_FILT_RESET (1 << 0x0F) 405 | /** filter all real-time messages */ 406 | #define PM_FILT_REALTIME (PM_FILT_ACTIVE | PM_FILT_SYSEX | PM_FILT_CLOCK | \ 407 | PM_FILT_PLAY | PM_FILT_UNDEFINED | PM_FILT_RESET | PM_FILT_TICK) 408 | /** filter note-on and note-off (0x90-0x9F and 0x80-0x8F */ 409 | #define PM_FILT_NOTE ((1 << 0x19) | (1 << 0x18)) 410 | /** filter channel aftertouch (most midi controllers use this) (0xD0-0xDF)*/ 411 | #define PM_FILT_CHANNEL_AFTERTOUCH (1 << 0x1D) 412 | /** per-note aftertouch (0xA0-0xAF) */ 413 | #define PM_FILT_POLY_AFTERTOUCH (1 << 0x1A) 414 | /** filter both channel and poly aftertouch */ 415 | #define PM_FILT_AFTERTOUCH (PM_FILT_CHANNEL_AFTERTOUCH | PM_FILT_POLY_AFTERTOUCH) 416 | /** Program changes (0xC0-0xCF) */ 417 | #define PM_FILT_PROGRAM (1 << 0x1C) 418 | /** Control Changes (CC's) (0xB0-0xBF)*/ 419 | #define PM_FILT_CONTROL (1 << 0x1B) 420 | /** Pitch Bender (0xE0-0xEF*/ 421 | #define PM_FILT_PITCHBEND (1 << 0x1E) 422 | /** MIDI Time Code (0xF1)*/ 423 | #define PM_FILT_MTC (1 << 0x01) 424 | /** Song Position (0xF2) */ 425 | #define PM_FILT_SONG_POSITION (1 << 0x02) 426 | /** Song Select (0xF3)*/ 427 | #define PM_FILT_SONG_SELECT (1 << 0x03) 428 | /** Tuning request (0xF6)*/ 429 | #define PM_FILT_TUNE (1 << 0x06) 430 | /** All System Common messages (mtc, song position, song select, tune request) */ 431 | #define PM_FILT_SYSTEMCOMMON (PM_FILT_MTC | PM_FILT_SONG_POSITION | PM_FILT_SONG_SELECT | PM_FILT_TUNE) 432 | 433 | 434 | PMEXPORT PmError Pm_SetFilter( PortMidiStream* stream, int32_t filters ); 435 | 436 | #define Pm_Channel(channel) (1<<(channel)) 437 | /** 438 | Pm_SetChannelMask() filters incoming messages based on channel. 439 | The mask is a 16-bit bitfield corresponding to appropriate channels. 440 | The Pm_Channel macro can assist in calling this function. 441 | i.e. to set receive only input on channel 1, call with 442 | Pm_SetChannelMask(Pm_Channel(1)); 443 | Multiple channels should be OR'd together, like 444 | Pm_SetChannelMask(Pm_Channel(10) | Pm_Channel(11)) 445 | 446 | Note that channels are numbered 0 to 15 (not 1 to 16). Most 447 | synthesizer and interfaces number channels starting at 1, but 448 | PortMidi numbers channels starting at 0. 449 | 450 | All channels are allowed by default 451 | */ 452 | PMEXPORT PmError Pm_SetChannelMask(PortMidiStream *stream, int mask); 453 | 454 | /** 455 | Pm_Abort() terminates outgoing messages immediately 456 | The caller should immediately close the output port; 457 | this call may result in transmission of a partial midi message. 458 | There is no abort for Midi input because the user can simply 459 | ignore messages in the buffer and close an input device at 460 | any time. 461 | */ 462 | PMEXPORT PmError Pm_Abort( PortMidiStream* stream ); 463 | 464 | /** 465 | Pm_Close() closes a midi stream, flushing any pending buffers. 466 | (PortMidi attempts to close open streams when the application 467 | exits -- this is particularly difficult under Windows.) 468 | */ 469 | PMEXPORT PmError Pm_Close( PortMidiStream* stream ); 470 | 471 | /** 472 | Pm_Synchronize() instructs PortMidi to (re)synchronize to the 473 | time_proc passed when the stream was opened. Typically, this 474 | is used when the stream must be opened before the time_proc 475 | reference is actually advancing. In this case, message timing 476 | may be erratic, but since timestamps of zero mean 477 | "send immediately," initialization messages with zero timestamps 478 | can be written without a functioning time reference and without 479 | problems. Before the first MIDI message with a non-zero 480 | timestamp is written to the stream, the time reference must 481 | begin to advance (for example, if the time_proc computes time 482 | based on audio samples, time might begin to advance when an 483 | audio stream becomes active). After time_proc return values 484 | become valid, and BEFORE writing the first non-zero timestamped 485 | MIDI message, call Pm_Synchronize() so that PortMidi can observe 486 | the difference between the current time_proc value and its 487 | MIDI stream time. 488 | 489 | In the more normal case where time_proc 490 | values advance continuously, there is no need to call 491 | Pm_Synchronize. PortMidi will always synchronize at the 492 | first output message and periodically thereafter. 493 | */ 494 | PmError Pm_Synchronize( PortMidiStream* stream ); 495 | 496 | 497 | /** 498 | Pm_Message() encodes a short Midi message into a 32-bit word. If data1 499 | and/or data2 are not present, use zero. 500 | 501 | Pm_MessageStatus(), Pm_MessageData1(), and 502 | Pm_MessageData2() extract fields from a 32-bit midi message. 503 | */ 504 | #define Pm_Message(status, data1, data2) \ 505 | ((((data2) << 16) & 0xFF0000) | \ 506 | (((data1) << 8) & 0xFF00) | \ 507 | ((status) & 0xFF)) 508 | #define Pm_MessageStatus(msg) ((msg) & 0xFF) 509 | #define Pm_MessageData1(msg) (((msg) >> 8) & 0xFF) 510 | #define Pm_MessageData2(msg) (((msg) >> 16) & 0xFF) 511 | 512 | typedef int32_t PmMessage; /**< see PmEvent */ 513 | /** 514 | All midi data comes in the form of PmEvent structures. A sysex 515 | message is encoded as a sequence of PmEvent structures, with each 516 | structure carrying 4 bytes of the message, i.e. only the first 517 | PmEvent carries the status byte. 518 | 519 | Note that MIDI allows nested messages: the so-called "real-time" MIDI 520 | messages can be inserted into the MIDI byte stream at any location, 521 | including within a sysex message. MIDI real-time messages are one-byte 522 | messages used mainly for timing (see the MIDI spec). PortMidi retains 523 | the order of non-real-time MIDI messages on both input and output, but 524 | it does not specify exactly how real-time messages are processed. This 525 | is particulary problematic for MIDI input, because the input parser 526 | must either prepare to buffer an unlimited number of sysex message 527 | bytes or to buffer an unlimited number of real-time messages that 528 | arrive embedded in a long sysex message. To simplify things, the input 529 | parser is allowed to pass real-time MIDI messages embedded within a 530 | sysex message, and it is up to the client to detect, process, and 531 | remove these messages as they arrive. 532 | 533 | When receiving sysex messages, the sysex message is terminated 534 | by either an EOX status byte (anywhere in the 4 byte messages) or 535 | by a non-real-time status byte in the low order byte of the message. 536 | If you get a non-real-time status byte but there was no EOX byte, it 537 | means the sysex message was somehow truncated. This is not 538 | considered an error; e.g., a missing EOX can result from the user 539 | disconnecting a MIDI cable during sysex transmission. 540 | 541 | A real-time message can occur within a sysex message. A real-time 542 | message will always occupy a full PmEvent with the status byte in 543 | the low-order byte of the PmEvent message field. (This implies that 544 | the byte-order of sysex bytes and real-time message bytes may not 545 | be preserved -- for example, if a real-time message arrives after 546 | 3 bytes of a sysex message, the real-time message will be delivered 547 | first. The first word of the sysex message will be delivered only 548 | after the 4th byte arrives, filling the 4-byte PmEvent message field. 549 | 550 | The timestamp field is observed when the output port is opened with 551 | a non-zero latency. A timestamp of zero means "use the current time", 552 | which in turn means to deliver the message with a delay of 553 | latency (the latency parameter used when opening the output port.) 554 | Do not expect PortMidi to sort data according to timestamps -- 555 | messages should be sent in the correct order, and timestamps MUST 556 | be non-decreasing. See also "Example" for Pm_OpenOutput() above. 557 | 558 | A sysex message will generally fill many PmEvent structures. On 559 | output to a PortMidiStream with non-zero latency, the first timestamp 560 | on sysex message data will determine the time to begin sending the 561 | message. PortMidi implementations may ignore timestamps for the 562 | remainder of the sysex message. 563 | 564 | On input, the timestamp ideally denotes the arrival time of the 565 | status byte of the message. The first timestamp on sysex message 566 | data will be valid. Subsequent timestamps may denote 567 | when message bytes were actually received, or they may be simply 568 | copies of the first timestamp. 569 | 570 | Timestamps for nested messages: If a real-time message arrives in 571 | the middle of some other message, it is enqueued immediately with 572 | the timestamp corresponding to its arrival time. The interrupted 573 | non-real-time message or 4-byte packet of sysex data will be enqueued 574 | later. The timestamp of interrupted data will be equal to that of 575 | the interrupting real-time message to insure that timestamps are 576 | non-decreasing. 577 | */ 578 | typedef struct { 579 | PmMessage message; 580 | PmTimestamp timestamp; 581 | } PmEvent; 582 | 583 | /** 584 | @} 585 | */ 586 | /** \defgroup grp_io Reading and Writing Midi Messages 587 | @{ 588 | */ 589 | /** 590 | Pm_Read() retrieves midi data into a buffer, and returns the number 591 | of events read. Result is a non-negative number unless an error occurs, 592 | in which case a PmError value will be returned. 593 | 594 | Buffer Overflow 595 | 596 | The problem: if an input overflow occurs, data will be lost, ultimately 597 | because there is no flow control all the way back to the data source. 598 | When data is lost, the receiver should be notified and some sort of 599 | graceful recovery should take place, e.g. you shouldn't resume receiving 600 | in the middle of a long sysex message. 601 | 602 | With a lock-free fifo, which is pretty much what we're stuck with to 603 | enable portability to the Mac, it's tricky for the producer and consumer 604 | to synchronously reset the buffer and resume normal operation. 605 | 606 | Solution: the buffer managed by PortMidi will be flushed when an overflow 607 | occurs. The consumer (Pm_Read()) gets an error message (pmBufferOverflow) 608 | and ordinary processing resumes as soon as a new message arrives. The 609 | remainder of a partial sysex message is not considered to be a "new 610 | message" and will be flushed as well. 611 | 612 | */ 613 | PMEXPORT int Pm_Read( PortMidiStream *stream, PmEvent *buffer, int32_t length ); 614 | 615 | /** 616 | Pm_Poll() tests whether input is available, 617 | returning TRUE, FALSE, or an error value. 618 | */ 619 | PMEXPORT PmError Pm_Poll( PortMidiStream *stream); 620 | 621 | /** 622 | Pm_Write() writes midi data from a buffer. This may contain: 623 | - short messages 624 | or 625 | - sysex messages that are converted into a sequence of PmEvent 626 | structures, e.g. sending data from a file or forwarding them 627 | from midi input. 628 | 629 | Use Pm_WriteSysEx() to write a sysex message stored as a contiguous 630 | array of bytes. 631 | 632 | Sysex data may contain embedded real-time messages. 633 | */ 634 | PMEXPORT PmError Pm_Write( PortMidiStream *stream, PmEvent *buffer, int32_t length ); 635 | 636 | /** 637 | Pm_WriteShort() writes a timestamped non-system-exclusive midi message. 638 | Messages are delivered in order as received, and timestamps must be 639 | non-decreasing. (But timestamps are ignored if the stream was opened 640 | with latency = 0.) 641 | */ 642 | PMEXPORT PmError Pm_WriteShort( PortMidiStream *stream, PmTimestamp when, int32_t msg); 643 | 644 | /** 645 | Pm_WriteSysEx() writes a timestamped system-exclusive midi message. 646 | */ 647 | PMEXPORT PmError Pm_WriteSysEx( PortMidiStream *stream, PmTimestamp when, unsigned char *msg); 648 | 649 | /** @} */ 650 | 651 | #ifdef __cplusplus 652 | } 653 | #endif /* __cplusplus */ 654 | #endif /* PORT_MIDI_H */ 655 | -------------------------------------------------------------------------------- /pm/types.go: -------------------------------------------------------------------------------- 1 | // THE AUTOGENERATED LICENSE. ALL THE RIGHTS ARE RESERVED BY ROBOTS. 2 | 3 | // WARNING: This file has automatically been generated on Tue, 05 Sep 2017 19:52:40 MSK. 4 | // By https://git.io/cgogen. DO NOT EDIT. 5 | 6 | package pm 7 | 8 | /* 9 | #cgo LDFLAGS: -lportmidi 10 | #include "portmidi.h" 11 | #include 12 | #include "cgo_helpers.h" 13 | */ 14 | import "C" 15 | import "unsafe" 16 | 17 | // PortMidiStream type as declared in pm/portmidi.h:163 18 | type PortMidiStream [0]byte 19 | 20 | // DeviceID type as declared in pm/portmidi.h:205 21 | type DeviceID int32 22 | 23 | // DeviceInfo as declared in pm/portmidi.h:215 24 | type DeviceInfo struct { 25 | StructVersion int32 26 | Interf string 27 | Name string 28 | Input int32 29 | Output int32 30 | Opened int32 31 | refbc1771f0 *C.PmDeviceInfo 32 | allocsbc1771f0 interface{} 33 | } 34 | 35 | // Timestamp type as declared in pm/portmidi.h:269 36 | type Timestamp int32 37 | 38 | // TimeProcPtr type as declared in pm/portmidi.h:270 39 | type TimeProcPtr func(timeInfo unsafe.Pointer) Timestamp 40 | 41 | // Message type as declared in pm/portmidi.h:512 42 | type Message int32 43 | 44 | // Event as declared in pm/portmidi.h:581 45 | type Event struct { 46 | Message Message 47 | Timestamp Timestamp 48 | ref369ef8f3 *C.PmEvent 49 | allocs369ef8f3 interface{} 50 | } 51 | -------------------------------------------------------------------------------- /portmidi.go: -------------------------------------------------------------------------------- 1 | // Package portmidi provides Go bindings for PortMIDI from the PortMedia set of libraries. 2 | package portmidi 3 | 4 | import ( 5 | "errors" 6 | 7 | "github.com/xlab/portmidi/pm" 8 | ) 9 | 10 | // Initialize is the library initialisation function: call this before using portmidi. 11 | func Initialize() error { 12 | return pm.ToError(pm.Initialize()) 13 | } 14 | 15 | // Terminate is the library termination function: call this after using portmidi. 16 | func Terminate() error { 17 | return pm.ToError(pm.Terminate()) 18 | } 19 | 20 | // GetHostError translates portmidi host error into human readable message. 21 | func GetHostError() error { 22 | buf := make([]byte, pm.HostErrorMsgLen) 23 | pm.GetHostErrorText(buf, pm.HostErrorMsgLen) 24 | for i := range buf { 25 | if buf[i] == 0 { 26 | buf = buf[:i] 27 | break 28 | } 29 | } 30 | return errors.New(string(buf)) 31 | } 32 | 33 | // CountDevices gets devices count, ids range from 0 to CountDevices()-1. 34 | func CountDevices() int { 35 | return int(pm.CountDevices()) 36 | } 37 | 38 | type DeviceID pm.DeviceID 39 | 40 | // DefaultOutputDeviceID returns the default output device ID or ok=false if there are no devices. 41 | func DefaultOutputDeviceID() (DeviceID, bool) { 42 | dev := pm.GetDefaultOutputDeviceID() 43 | if dev == pm.NoDevice { 44 | return 0, false 45 | } 46 | return DeviceID(dev), true 47 | } 48 | 49 | // DefaultInputDeviceID returns the default input device ID or ok=false if there are no devices. 50 | func DefaultInputDeviceID() (DeviceID, bool) { 51 | dev := pm.GetDefaultInputDeviceID() 52 | if dev == pm.NoDevice { 53 | return 0, false 54 | } 55 | return DeviceID(dev), true 56 | } 57 | 58 | type DeviceInfo struct { 59 | // Interface specifies underlying MIDI API, e.g. MMSystem or DirectX. 60 | Interface string 61 | // Name is a device name, e.g. USB MidiSport 1x1 62 | Name string 63 | // IsInputAvailable true iff input is available. 64 | IsInputAvailable bool 65 | // IsOutputAvailable true iff output is available. 66 | IsOutputAvailable bool 67 | } 68 | 69 | // GetDeviceInfo returns device info for the provided device ID, or nil if ID is out of range. 70 | func GetDeviceInfo(id DeviceID) *DeviceInfo { 71 | info := pm.GetDeviceInfo(pm.DeviceID(id)) 72 | if info == nil { 73 | return nil 74 | } 75 | info.Deref() 76 | return &DeviceInfo{ 77 | Interface: info.Interf, 78 | Name: info.Name, 79 | IsInputAvailable: info.Input > 0, 80 | IsOutputAvailable: info.Output > 0, 81 | } 82 | } 83 | 84 | type Event struct { 85 | Timestamp int32 86 | Message Message 87 | SysExData []byte 88 | } 89 | 90 | // NewMessage encodes a short MIDI message into a 32-bit word. If data1 91 | // and/or data2 are not present, use zero. 92 | func NewMessage(status, data1, data2 byte) Message { 93 | return (Message(data2)<<16)&0xFF0000 | 94 | (Message(data1)<<8)&0xFF00 | Message(status)&0xFF 95 | } 96 | 97 | type Message int32 98 | 99 | func (m Message) Status() byte { 100 | return byte(m & 0xFF) 101 | } 102 | 103 | func (m Message) Data1() byte { 104 | return byte((m >> 8) & 0xFF) 105 | } 106 | 107 | func (m Message) Data2() byte { 108 | return byte((m >> 16) & 0xFF) 109 | } 110 | -------------------------------------------------------------------------------- /stream.go: -------------------------------------------------------------------------------- 1 | package portmidi 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/xlab/portmidi/pm" 7 | ) 8 | 9 | type Stream struct { 10 | stream *pm.PortMidiStream 11 | buf chan Event 12 | closeC chan struct{} 13 | doneC chan struct{} 14 | } 15 | 16 | // Close closes a midi stream, flushing any pending buffers. 17 | func (s *Stream) Close() error { 18 | close(s.closeC) 19 | <-s.doneC 20 | err := pm.ToError(pm.Close(s.stream)) 21 | s.stream = nil 22 | return err 23 | } 24 | 25 | func (s *Stream) Source() <-chan Event { 26 | return s.buf 27 | } 28 | 29 | func (s *Stream) Sink() chan<- Event { 30 | return s.buf 31 | } 32 | 33 | // NewInputStream opens device for the input. The buffersize specifies the number of input events to be 34 | // buffered waiting to be read. 35 | func NewInputStream(id DeviceID, bufferSize int, 36 | channels ChannelMask, filters ...Filter) (*Stream, error) { 37 | 38 | var stream *pm.PortMidiStream 39 | ret := pm.OpenInput(&stream, pm.DeviceID(id), nil, int32(bufferSize), nil, nil) 40 | if err := pm.ToError(ret); err != nil { 41 | return nil, err 42 | } 43 | buf := make(chan Event, bufferSize) 44 | s := &Stream{ 45 | stream: stream, 46 | closeC: make(chan struct{}), 47 | doneC: make(chan struct{}), 48 | buf: buf, 49 | } 50 | if channels > 0 { // all allowed by default 51 | pm.SetChannelMask(s.stream, int32(channels)) 52 | } 53 | if len(filters) > 0 { 54 | var fs Filter 55 | fs.Join(filters...) 56 | pm.SetFilter(s.stream, int32(fs)) 57 | } 58 | go s.processInput() 59 | return s, nil 60 | } 61 | 62 | // NewOutputStream opens device for the input. The buffersize 63 | // specifies the number of output events to be buffered waiting for output. 64 | // (In some cases -- see below -- PortMidi does not buffer output at all 65 | // and merely passes data to a lower-level API, in which case buffersize 66 | // is ignored.) 67 | // 68 | // latency is the delay in milliseconds applied to timestamps to determine 69 | // when the output should actually occur. (If latency is < 0, 0 is assumed.) 70 | // If latency is zero, timestamps are ignored and all output is delivered 71 | // immediately. If latency is greater than zero, output is delayed until the 72 | // message timestamp plus the latency. (NOTE: the time is measured relative 73 | // to the time source indicated by time_proc. Timestamps are absolute, 74 | // not relative delays or offsets.) In some cases, PortMidi can obtain 75 | // better timing than your application by passing timestamps along to the 76 | // device driver or hardware. Latency may also help you to synchronize midi 77 | // data to audio data by matching midi latency to the audio buffer latency. 78 | func NewOutputStream(id DeviceID, bufferSize, latency int, 79 | channels ChannelMask, filters ...Filter) (*Stream, error) { 80 | var stream *pm.PortMidiStream 81 | ret := pm.OpenOutput(&stream, pm.DeviceID(id), nil, int32(bufferSize), nil, nil, int32(latency)) 82 | if err := pm.ToError(ret); err != nil { 83 | return nil, err 84 | } 85 | buf := make(chan Event, bufferSize) 86 | s := &Stream{ 87 | stream: stream, 88 | closeC: make(chan struct{}), 89 | doneC: make(chan struct{}), 90 | buf: buf, 91 | } 92 | if channels > 0 { // all allowed by default 93 | pm.SetChannelMask(s.stream, int32(channels)) 94 | } 95 | go s.processOutput() 96 | return s, nil 97 | } 98 | 99 | func (s *Stream) pushEvents(buf []pm.Event) { 100 | for i := range buf { 101 | buf[i].Deref() 102 | s.buf <- Event{ 103 | Timestamp: int32(buf[i].Timestamp), 104 | Message: Message(buf[i].Message), 105 | } 106 | } 107 | } 108 | 109 | const pollDelay = 5 * time.Millisecond 110 | 111 | func (s *Stream) processInput() { 112 | var hadData bool 113 | for { 114 | select { 115 | case <-s.closeC: 116 | close(s.buf) 117 | close(s.doneC) 118 | return 119 | default: 120 | if pm.Poll(s.stream) == pm.True { 121 | hadData = true 122 | buf := make([]pm.Event, 128) 123 | size := pm.Read(s.stream, buf, 128) 124 | s.pushEvents(buf[:size]) 125 | continue 126 | } else if hadData { 127 | hadData = false 128 | continue 129 | } 130 | time.Sleep(pollDelay) 131 | } 132 | } 133 | } 134 | 135 | // An aggregating version of this function available: 136 | // https://gist.github.com/xlab/1768c3dd210bf3829b54f4cec3f748bb 137 | func (s *Stream) processOutput() { 138 | for { 139 | select { 140 | case <-s.closeC: 141 | go func() { 142 | // drain s.buf 143 | for range s.buf { 144 | } 145 | }() 146 | close(s.doneC) 147 | return 148 | case ev, ok := <-s.buf: 149 | if !ok { // s.buf closed 150 | close(s.doneC) 151 | return 152 | } 153 | if len(ev.SysExData) > 0 { // handle sysEx separately 154 | pm.WriteSysEx(s.stream, pm.Timestamp(ev.Timestamp), ev.SysExData) 155 | continue 156 | } 157 | pm.WriteShort(s.stream, pm.Timestamp(ev.Timestamp), int32(ev.Message)) 158 | } 159 | } 160 | } 161 | 162 | // HasHostError tests whether stream has a pending host error. 163 | // Normally, the client finds out about errors through returned error codes, 164 | // but some errors can occur asynchronously where the client does not 165 | // explicitly call a function, and therefore cannot receive an error code. 166 | func (s *Stream) HasHostError() bool { 167 | return pm.HasHostError(s.stream) > 0 168 | } 169 | 170 | // Synchronize instructs PortMidi to (re)synchronize to the 171 | // time_proc passed when the stream was opened. 172 | // PortMidi will always synchronize at the 173 | // first output message and periodically thereafter. 174 | // func (s *Stream) Sync() error { 175 | // return pm.ToError(pm.Synchronize(s.stream)) 176 | // } 177 | --------------------------------------------------------------------------------