Skip to content
Modular core library for MIDI and SMF
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Type Name Latest commit message Commit time
Failed to load latest commit information.
midiio improve wording for purpose of midiio package Aug 17, 2018
smf add FractionalDuration and FractionalTicks to metric ticks Sep 12, 2018
AUTHORS add AUTHORS and CONTRIBUTING files Jul 11, 2017
LICENSE fixing coverage badge Sep 3, 2018
appveyor.yml add appveyor.yml Jul 19, 2017
doc.go fix misspellings Aug 17, 2018
go.mod add go module Aug 15, 2018
midi.go fix some comments Aug 17, 2018


Modular library for reading and writing of MIDI messages with Go.

Build Status Travis/Linux Build Status AppVeyor/Windows Coverage Status Go Report Documentation

This package is meant for users that have some knowledge of the MIDI standards. Beginners, and people on the run might want to look at the porcelain package



  • Go version: >= 1.10
  • OS/architectures: everywhere Go runs (tested on Linux and Windows).


go get




  • implementation of complete MIDI standard (live and SMF data)
  • reading and optional writing with "running status"
  • seemless integration with io.Reader and io.Writer
  • allows the reuse of same libraries for live writing and writing to SMF files
  • provide building blocks for other MIDI libraries and applications
  • stable API
  • no dependencies outside the standard library
  • small modular core packages
  • typed Messages
  • pure Go library (no C, no assembler)


  • constructing of MIDI time code messages
  • Multidimensional Polyphonic Expression (MPE)
  • dealing with the inner structure of sysex messages
  • connection to MIDI devices (for this combine it with
  • CLI tools

Usage / Example

package main

import (
    . ""

func main() {
    var bf bytes.Buffer

    wr := midiwriter.New(&bf)
    wr.Write(Channel2.NoteOn(65, 90))

    rthandler := func(m realtime.Message) {
        fmt.Printf("Realtime: %s\n", m)

    rd := midireader.New(bytes.NewReader(bf.Bytes()), rthandler)

    var m midi.Message
    var err error

    for {
        m, err = rd.Read()

        // breaking at least with io.EOF
        if err != nil {

        // inspect

        switch v := m.(type) {
        case NoteOn:
            fmt.Printf("NoteOn at channel %v: key: %v velocity: %v\n", v.Channel(), v.Key(), v.Velocity())
        case NoteOff:
            fmt.Printf("NoteOff at channel %v: key: %v\n", v.Channel(), v.Key())


    if err != io.EOF {
        panic("error: " + err.Error())

    // Output:
    // channel.Pitchbend channel 2 value 5000 absValue 13192
    // channel.NoteOn channel 2 key 65 velocity 90
    // NoteOn at channel 2: key: 65 velocity: 90
    // Realtime: Reset
    // channel.NoteOff channel 2 key 65
    // NoteOff at channel 2: key: 65


This package is divided into small subpackages, so that you only need to import what you really need. This keeps packages and dependencies small, better testable and should result in a smaller memory footprint which should help smaller devices.

For reading and writing of live and SMF MIDI data io.Readers are accepted as input and io.Writers as output. Furthermore there are common interfaces for live and SMF MIDI data handling: midi.Reader and midi.Writer. The typed MIDI messages used in each case are the same.

To connect with MIDI libraries expecting and returning plain bytes (e.g. over the wire), use midiio subpackage.


On my laptop, writing noteon and noteoff ("live")

BenchmarkSameChannel            123 ns/op  12 B/op  3 allocs/op
BenchmarkAlternatingChannel     123 ns/op  12 B/op  3 allocs/op
BenchmarkRunningStatusDisabled  110 ns/op  12 B/op  3 allocs/op

On my laptop, reading noteon and noteoff ("live") ("Samechannel" makes use of running status byte).

BenchmarkSameChannel            351 ns/op  12 B/op  7 allocs/op
BenchmarkAlternatingChannel     425 ns/op  14 B/op  9 allocs/op


MIT (see LICENSE file)


Inspiration and low level code for MIDI reading (see internal midilib package) came from the 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 but I didn't try it.

You can’t perform that action at this time.