midi

package module
v1.23.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 26, 2021 License: MIT Imports: 2 Imported by: 116

README

midi

Modular library for reading and writing of MIDI messages and 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.14
  • OS/architectures: everywhere Go runs (tested on Linux and Windows).

Installation

go get gitlab.com/gomidi/midi@latest

Features

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)
  • reading and optional writing with "running status"
  • seamless 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
  • no dependencies outside the standard library
  • small modular core packages
  • typed Messages

Drivers

For "cable" communication you need a Driverto connect with the MIDI system of your OS. Currently the following drivers available (all multi-platform):

  • package gitlab.com/gomidi/rtmididrv based on rtmidi (requires CGO)
  • package gitlab.com/gomidi/portmididrv based on portmidi (requires CGO)
  • package gitlab.com/gomidi/webmididrv based on the Web MIDI standard (produces webassembly)
  • package gitlab.com/gomidi/midicatdrv based on the midicat binaries via piping (stdin / stdout) (no CGO needed)
  • package gitlab.com/gomidi/midi/testdrv for testing (no CGO needed)

Projects using this library

Porcelain package

For easy access, the following packages are recommended:

  • reading: gitlab.com/gomidi/midi/reader Go Reference
  • writing: gitlab.com/gomidi/midi/writer Go Reference

The other packages are more low level and allow you to write your own implementations of the midi.Reader, midi.Writerand midi.Driver interfaces to wrap the given SMF and live readers/writers/drivers for your own application.

Example with MIDI cables
package main

import (
	"fmt"
	"time"

	"gitlab.com/gomidi/midi"
	"gitlab.com/gomidi/midi/reader"

	// replace with e.g. "gitlab.com/gomidi/rtmididrv" for real midi connections
	driver "gitlab.com/gomidi/midi/testdrv"
	"gitlab.com/gomidi/midi/writer"
)

// This example reads from the first input and and writes to the first output port
func main() {
	// you would take a real driver here e.g. rtmididrv.New()
	drv := driver.New("fake cables: messages written to output port 0 are received on input port 0")

	// make sure to close all open ports at the end
	defer drv.Close()

	ins, err := drv.Ins()
	must(err)

	outs, err := drv.Outs()
	must(err)

	in, out := ins[0], outs[0]

	must(in.Open())
	must(out.Open())

	defer in.Close()
	defer out.Close()

	// the writer we are writing to
	wr := writer.New(out)

	// to disable logging, pass mid.NoLogger() as option
	rd := reader.New(
		reader.NoLogger(),
		// write every message to the out port
		reader.Each(func(pos *reader.Position, msg midi.Message) {
			fmt.Printf("got %s\n", msg)
		}),
	)

	// listen for MIDI
	err = rd.ListenTo(in)
	must(err)

	err = writer.NoteOn(wr, 60, 100)
	must(err)

	time.Sleep(1)
	err = writer.NoteOff(wr, 60)

	must(err)
	// Output: got channel.NoteOn channel 0 key 60 velocity 100
	// got channel.NoteOff channel 0 key 60
}

func must(err error) {
	if err != nil {
		panic(err.Error())
	}
}
Example with MIDI file (SMF)
package main

import (
	"fmt"
	"os"
	"path/filepath"

	"gitlab.com/gomidi/midi/reader"
	"gitlab.com/gomidi/midi/writer"
)

type printer struct{}

func (pr printer) noteOn(p *reader.Position, channel, key, vel uint8) {
	fmt.Printf("Track: %v Pos: %v NoteOn (ch %v: key %v vel: %v)\n", p.Track, p.AbsoluteTicks, channel, key, vel)
}

func (pr printer) noteOff(p *reader.Position, channel, key, vel uint8) {
	fmt.Printf("Track: %v Pos: %v NoteOff (ch %v: key %v)\n", p.Track, p.AbsoluteTicks, channel, key)
}

func main() {
	dir := os.TempDir()
	f := filepath.Join(dir, "smf-test.mid")

	defer os.Remove(f)

	var p printer

	err := writer.WriteSMF(f, 2, func(wr *writer.SMF) error {
		
		wr.SetChannel(11) // sets the channel for the next messages
		writer.NoteOn(wr, 120, 50)
		wr.SetDelta(120)
		writer.NoteOff(wr, 120)
		
		wr.SetDelta(240)
		writer.NoteOn(wr, 125, 50)
		wr.SetDelta(20)
		writer.NoteOff(wr, 125)
		writer.EndOfTrack(wr)
		
		wr.SetChannel(2)
		writer.NoteOn(wr, 120, 50)
		wr.SetDelta(60)
		writer.NoteOff(wr, 120)
		writer.EndOfTrack(wr)
		return nil
	})

	if err != nil {
		fmt.Printf("could not write SMF file %v\n", f)
		return
	}

	// to disable logging, pass mid.NoLogger() as option
	rd := reader.New(reader.NoLogger(),
		// set the functions for the messages you are interested in
		reader.NoteOn(p.noteOn),
		reader.NoteOff(p.noteOff),
	)

	err = reader.ReadSMFFile(rd, f)

	if err != nil {
		fmt.Printf("could not read SMF file %v\n", f)
	}

	// Output: Track: 0 Pos: 0 NoteOn (ch 11: key 120 vel: 50)
	// Track: 0 Pos: 120 NoteOff (ch 11: key 120)
	// Track: 0 Pos: 360 NoteOn (ch 11: key 125 vel: 50)
	// Track: 0 Pos: 380 NoteOff (ch 11: key 125)
	// Track: 1 Pos: 0 NoteOn (ch 2: key 120 vel: 50)
	// Track: 1 Pos: 60 NoteOff (ch 2: key 120)
}

Low level packages

Go Reference

Example with low level packages
package main

import (
    "bytes"
    "fmt"
    . "gitlab.com/gomidi/midi/midimessage/channel"
    "gitlab.com/gomidi/midi/midimessage/realtime"
    "gitlab.com/gomidi/midi/midireader"
    "gitlab.com/gomidi/midi/midiwriter"
    "io"
    "gitlab.com/gomidi/midi"
)

func main() {
    var bf bytes.Buffer

    wr := midiwriter.New(&bf)
    wr.Write(Channel2.Pitchbend(5000))
    wr.Write(Channel2.NoteOn(65, 90))
    wr.Write(realtime.Reset)
    wr.Write(Channel2.NoteOff(65))

    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 {
            break
        }

        // inspect
        fmt.Println(m)

        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
}

Modularity

Apart from the porcelain packages there are 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 cable 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, use the midiio subpackage.

Stability / API / Semantic Versioning

This excellent blog post by Peter Bourgon describes perfectly the problem, I have with Go modules' take on semantic versioning.

First of all, the API of this library is large (if you use all of it, which is unlikely) and generally stable. There may be small incompatibilities from time to time that will only affect a small amount of users and that will "blow up into your face" when compiling. These are easy to fix with the help of the compiler and the documentation.

The diamond dependency problem should be unlikely with this library, since that would mean, you are using a library that is abstracting over MIDI and uses this library in order to do so. Well, then you should reconsider the use of that library, since there really is no point in further abstractions. For other uses however (e.g. integration), the interfaces should be used and they won't change.

As a last resort, you could fork that other library, add a replace statement to your go.mod and fix the issue, followed by a pull request. If that libary requiring the old code is not maintained anymore, you would need to fork anyway in the future. If it is maintained, your pull request should get accepted.

We are not going to bump the major version with every tiny incompatible change of this code base. The costs are too high and the benefits too low. Instead, any minor version upgrade reflects incompatible changes and the patchversions contain compatible changes (i.e. also compatible additions). A large rewrite will however end up in a major version bump.

License

MIT (see LICENSE file)

Credits

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.

Alternatives

Matt Aimonetti is also working on MIDI inside https://github.com/mattetti/audio but I didn't try it.

Documentation

Overview

Package midi provides interfaces for reading and writing of MIDI messages.

Since they are handled slightly different, this packages introduces the terminology of "live"/cable MIDI reading/writing for dealing with MIDI messages as "over the wire" (in realtime) as opposed to smf MIDI reading/writing to Standard MIDI Files (SMF).

However both variants can be used with io.Writer and io.Reader and can thus be "streamed".

This package provides a Reader and Writer interface that is common to live and SMF MIDI handling. This should allow to easily develop transformations (e.g. quantization, filtering) that may be used in both cases.

If you want a comfortable common package providing everything at a high level, use the porcelain packages

gitlab.com/gomidi/midi/reader
gitlab.com/gomidi/midi/writer

The underlying core implementations can be found here:

gitlab.com/gomidi/midi/midireader      (live reading)
gitlab.com/gomidi/midi/midiwriter      (live writing)
gitlab.com/gomidi/midi/smf/smfreader   (SMF reading)
gitlab.com/gomidi/midi/smf/smfwriter   (SMF writing)
gitlab.com/gomidi/midi/smf/smftrack    (SMF modification)

The core of the MIDI messages that can be written or analyzed can be found here:

gitlab.com/gomidi/midi/midimessage/channel    (Channel Messages)
gitlab.com/gomidi/midi/midimessage/meta       (Meta Messages)
gitlab.com/gomidi/midi/midimessage/realtime   (System Realtime Messages)
gitlab.com/gomidi/midi/midimessage/syscommon  (System Common messages)
gitlab.com/gomidi/midi/midimessage/sysex      (System Exclusive messages)

Please keep in mind that that by the MIDI standard not all kinds of MIDI messages can be used in both scenarios.

System Realtime and System Common Messages are restricted to "over the wire", while Meta Messages are restricted to SMF files. However System Realtime and System Common Messages can be saved inside a SMF file which the help of SysEx escaping (F7).

Index

Constants

This section is empty.

Variables

View Source
var ErrPortClosed = fmt.Errorf("ERROR: port is closed")

ErrPortClosed should be returned from a driver when trying to write to a closed port.

View Source
var ErrUnexpectedEOF = fmt.Errorf("Unexpected End of File found.")

ErrUnexpectedEOF is returned, when an unexspected end of file is reached.

Functions

func RegisterDriver added in v1.21.0

func RegisterDriver(d Driver)

Types

type Driver added in v1.15.0

type Driver interface {

	// Ins returns the available MIDI input ports.
	Ins() ([]In, error)

	// Outs returns the available MIDI output ports.
	Outs() ([]Out, error)

	// String returns the name of the driver.
	String() string

	// Close closes the driver. Must be called for cleanup at the end of a session.
	Close() error
}

Driver is a driver for MIDI connections.

type In added in v1.15.0

type In interface {
	Port

	// SetListener sets the callback function that is called when data arrives.
	SetListener(func(data []byte, deltaMicroseconds int64)) error

	// StopListening stops the listening.
	// When closing a MIDI input port, StopListening must be called before (from the driver).
	StopListening() error
}

In is an interface for a MIDI input port

func OpenIn added in v1.15.0

func OpenIn(d Driver, number int, name string) (in In, err error)

OpenIn opens a MIDI input port with the help of the given driver. To find the port by port number, pass a number >= 0. To find the port by port name, pass a number < 0 and a non empty string.

type Message

type Message interface {

	// String inspects the MIDI message in an informative way.
	String() string

	// Raw returns the raw bytes of the MIDI message.
	Raw() []byte
}

Message is a MIDI message.

type Out added in v1.15.0

type Out interface {
	Port

	// Write writes the given MIDI bytes over the wire and returns the bytes send
	// If the port is closed, ErrPortClosed must be returned.
	Write(b []byte) (int, error)
}

Out is an interface for a MIDI output port.

func OpenOut added in v1.15.0

func OpenOut(d Driver, number int, name string) (out Out, err error)

OpenOut opens a MIDI output port with the help of the given driver. To find the port by port number, pass a number >= 0. To find the port by port name, pass a number < 0 and a non empty string.

type Port added in v1.15.0

type Port interface {

	// Open opens the MIDI port. An implementation should save the open state to make it
	// save to call open when the port is already open without getting an error.
	Open() error

	// Close closes the MIDI port. An implementation should save the open state to make it
	// save to call close when the port is already closed without getting an error.
	Close() error

	// IsOpen returns wether the MIDI port is open.
	IsOpen() bool

	// Number returns the number of the MIDI port. It is only guaranteed that the numbers are unique within
	// MIDI port groups i.e. within MIDI input ports and MIDI output ports. So there may be the same number
	// for a given MIDI input port and some MIDI output port. Or not - that depends on the underlying driver.
	Number() int

	// String represents the MIDI port by a string, aka name.
	String() string

	// Underlying returns the underlying driver to allow further adjustments.
	// When using the underlying driver, the user must take care of proper opening/closing etc.
	Underlying() interface{}
}

Port is an interface for a MIDI port.

type ReadCloser

type ReadCloser interface {
	Reader
	Close() error
}

ReadCloser is a Reader that must be closed at the end of reading.

type Reader

type Reader interface {

	// Read reads a MIDI message.
	Read() (Message, error)
}

Reader reads MIDI messages.

type WriteCloser

type WriteCloser interface {
	Writer
	Close() error
}

WriteCloser is a Writer that must be closed at the end of writing.

type Writer

type Writer interface {

	// Write writes the given MIDI message and returns any error.
	Write(Message) error
}

Writer writes MIDI messages.

Directories

Path Synopsis
Package cc provides shortcuts for MIDI Control Change Messages
Package cc provides shortcuts for MIDI Control Change Messages
examples module
smf
logger Module
looper Module
smfplayer Module
smfrecorder Module
sysex Module
webmidi Module
Package gm provides constants for instruments, drumkits and percussion keys based on the General MIDI standard.
Package gm provides constants for instruments, drumkits and percussion keys based on the General MIDI standard.
internal
vlq
Package midiio provides helpers for connecting io.Readers and io.Writers to midi.Readers and midi.Writers.
Package midiio provides helpers for connecting io.Readers and io.Writers to midi.Readers and midi.Writers.
Package midimessage provides helper functions for MIDI messages.
Package midimessage provides helper functions for MIDI messages.
channel
Package channel provides MIDI Channel Messages
Package channel provides MIDI Channel Messages
meta
Package meta provides MIDI Meta Messages
Package meta provides MIDI Meta Messages
meta/key
Package key provides helper functions for key signature meta messages.
Package key provides helper functions for key signature meta messages.
meta/meter
Package meter provides helper functions for time signature meta messages.
Package meter provides helper functions for time signature meta messages.
realtime
Package realtime provides MIDI System Realtime Messages
Package realtime provides MIDI System Realtime Messages
syscommon
Package syscommon provides MIDI System Common Messages
Package syscommon provides MIDI System Common Messages
sysex
Package sysex provides MIDI System Exclusive Messages
Package sysex provides MIDI System Exclusive Messages
Package midireader provides a reader for live/streaming/"over the wire" MIDI data.
Package midireader provides a reader for live/streaming/"over the wire" MIDI data.
Package midiwriter provides a writer for live/streaming/"over the wire" MIDI data.
Package midiwriter provides a writer for live/streaming/"over the wire" MIDI data.
Package player provides an easy way to play a SMF (Standard MIDI File).
Package player provides an easy way to play a SMF (Standard MIDI File).
Package reader provides an easy abstraction for reading of cable MIDI and SMF (Standard MIDI File) data.
Package reader provides an easy abstraction for reading of cable MIDI and SMF (Standard MIDI File) data.
smf
Package smf provides constants and interfaces for reading and writing of Standard MIDI Files (SMF).
Package smf provides constants and interfaces for reading and writing of Standard MIDI Files (SMF).
smfreader
Package smfreader provides a reader of Standard MIDI Files (SMF).
Package smfreader provides a reader of Standard MIDI Files (SMF).
smfwriter
Package smfwriter provides a writer of Standard MIDI Files (SMF).
Package smfwriter provides a writer of Standard MIDI Files (SMF).
Package testdrv provides a gomidi/midi.Driver for testing.
Package testdrv provides a gomidi/midi.Driver for testing.
tools module
hyperarp Module
midicat Module
midispy Module
smflyrics Module
smfsysex Module
v2
Package writer provides an easy abstraction for writing of cable MIDI and SMF (Standard MIDI File) data.
Package writer provides an easy abstraction for writing of cable MIDI and SMF (Standard MIDI File) data.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL