library for reading and writing of MIDI messages and SMF/MIDI files with Go.
Note: If you are reading this on Github, please note that the repo has moved to Gitlab (gitlab.com/gomidi/midi) and this is only a mirror.
- Go version: >= 1.24.2
- OS/architectures: everywhere Go runs (tested on Linux and Windows).
go get gitlab.com/gomidi/midi/v2@latest
This package provides a unified way to read and write "over the wire" MIDI data and MIDI files (SMF).
- implementation of complete MIDI standard ("cable" and SMF MIDI)
- unified Driver interface (see below)
- reading and optional writing with "running status"
- seamless integration with io.Reader and io.Writer
- no dependencies outside the standard library
- low overhead
- shortcuts for General MIDI, Sysex messages etc.
- CLI tools that use the library
- SMF files can be converted back and forth into a human readable JSON format
For "cable" communication you need a Driver to connect with the MIDI system of your OS.
Currently the following drivers available in the drivers subdirectory (all multi-platform):
- rtmididrv based on rtmidi (requires CGO)
- webmididrv based on the Web MIDI standard (produces webassembly)
- midicatdrv based on the midicat binaries via piping (stdin / stdout) (no CGO needed)
- testdrv for testing (no CGO needed)
(there used to be a driver, based on portmidi, but this is not supported anymore)
package main
import (
"fmt""time""gitlab.com/gomidi/midi/v2"
_ "gitlab.com/gomidi/midi/v2/drivers/rtmididrv"// autoregisters driver
)
funcmain() {
defermidi.CloseDriver()
in, err:=midi.FindInPort("VMPK")
iferr!=nil {
fmt.Println("can't find VMPK")
return
}
stop, err:=midi.ListenTo(in, func(msg midi.Message, timestampmsint32) {
varbt []bytevarch, key, veluint8switch {
casemsg.GetSysEx(&bt):
fmt.Printf("got sysex: % X\n", bt)
casemsg.GetNoteStart(&ch, &key, &vel):
fmt.Printf("starting note %s on channel %v with velocity %v\n", midi.Note(key), ch, vel)
casemsg.GetNoteEnd(&ch, &key):
fmt.Printf("ending note %s on channel %v\n", midi.Note(key), ch)
default:
// ignore
}
}, midi.UseSysEx())
iferr!=nil {
fmt.Printf("ERROR: %s\n", err)
return
}
time.Sleep(time.Second*5)
stop()
}package main
import (
"bytes""fmt""gitlab.com/gomidi/midi/v2""gitlab.com/gomidi/midi/v2/gm""gitlab.com/gomidi/midi/v2/smf"
_ "gitlab.com/gomidi/midi/v2/drivers/rtmididrv"// autoregisters driver
)
funcmain() {
defermidi.CloseDriver()
fmt.Printf("outports:\n"+midi.GetOutPorts() +"\n")
out, err:=midi.FindOutPort("qsynth")
iferr!=nil {
fmt.Printf("can't find qsynth")
return
}
// create a SMFrd:=bytes.NewReader(mkSMF())
// read and play itsmf.ReadTracksFrom(rd).Do(func(ev smf.TrackEvent) {
fmt.Printf("track %v @%vms %s\n", ev.TrackNo, ev.AbsMicroSeconds/1000, ev.Message)
}).Play(out)
}
// makes a SMF and returns the bytesfuncmkSMF() []byte {
var (
bf bytes.Bufferclock=smf.MetricTicks(96) // resolution: 96 ticks per quarternote 960 is also commontr smf.Track
)
// first track must have tempo and meter informationstr.Add(0, smf.MetaMeter(3, 4))
tr.Add(0, smf.MetaTempo(140))
tr.Add(0, smf.MetaInstrument("Brass"))
tr.Add(0, midi.ProgramChange(0, gm.Instr_BrassSection.Value()))
tr.Add(0, midi.Ab(3).NoteOn(0, 120)) // or midi.NoteOn(0, midi.Ab(3).Value(), 120)tr.Add(clock.Ticks8th(), midi.C(4).NoteOn(0, 120))
// duration: a quarter note (96 ticks in our case)tr.Add(clock.Ticks4th()*2, midi.Ab(3).NoteOff(0)) // or midi.NoteOff(0, midi.Ab(3).Value())tr.Add(0, midi.C(4).NoteOff(0))
tr.Close(0)
// create the SMF and add the trackss:=smf.New()
s.TimeFormat=clocks.Add(tr)
s.WriteTo(&bf)
returnbf.Bytes()
}see https://pkg.go.dev/gitlab.com/gomidi/midi/v2
MIT (see LICENSE file)
Inspiration and low level code for MIDI reading (see internal midilib package) came from the http://github.com/afandian/go-midi package of Joe Wass which also helped as a starting point for the reading of SMF files.
Matt Aimonetti is also working on MIDI inside https://github.com/mattetti/audio but I didn't try it.