npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@lachenmayer/midi-messages

v1.0.1

Published

A MIDI message encoder/decoder

Downloads

6

Readme

@lachenmayer/midi-messages

A MIDI message encoder/decoder written in TypeScript. Because all other ones I've found have weird APIs, weird dependencies, or were just generally weird. Which is weird, because MIDI is such a simple and mostly sensible format.

This module exists because node-midi and WebMIDI do not parse MIDI messages, and instead return cryptic arrays of numbers. That's not very useful, especially if you actually want to do something with the MIDI messages.

This module parses raw MIDI messages into JavaScript objects. These objects are designed to match the official MIDI spec as closely as possible, in naming and structure. See the type definitions for details on how the message objects are structured.

Non-scope / caveats

  • This module does not parse or play MIDI files. I wrote this to interface with MIDI controllers in real time, not to read MIDI files.

  • This module silently overflows. All MIDI data bytes are 7-bit values, ie. 0 to 127. You might expect this module to throw errors if values are out of range, but it doesn't. Values wrap around instead.

  • This module currently does not parse Sample Dump messages. While this would be a nice addition to this module, it is currently not implemented because I have no use for it.

  • This module currently does not parse the contents of timecode (MTC) messages. The contents of MTC quarter frame messages are currently only represented as an opaque number. MTC sysex messages are not parsed at all, and are just emitted as unparsed sysex messages. Partial support for this will probably be added at some point (probably only for MTC quarter frame messages).

Install

npm install @lachenmayer/midi-messages

I believe that most people should author packages under their own scope, (a) to avoid name-squatting and namespace pollution, and (b) so that it is clear who is maintaining the module.

Feel free to publish your own fork on NPM, but please publish it under your own scope.

Usage

Simple encoding

const { EncodeStream } = require('@lachenmayer/midi-messages')

const encode = new EncodeStream()

encode.on('data', buf => {
  console.log(buf)
})

encode.noteOn(1, 64, 100)
// ...equivalent to:
encode.write({ type: 'NoteOn', channel: 1, note: 64, velocity: 100 })

Output:

<Buffer 90 40 64>
<Buffer 90 40 64>

Simple decoding

const { DecodeStream } = require('@lachenmayer/midi-messages')

const decode = new DecodeStream()

decode.on('data', message => {
  console.log(message)
})

decode.write(Buffer.from('904064', 'hex'))
decode.write(Buffer.from('80407f', 'hex'))

Output:

{ type: 'NoteOn', channel: 1, note: 64, velocity: 100 }
{ type: 'NoteOff', channel: 1, note: 64, velocity: 127 }

Usage with node-midi

The following example creates a virtual MIDI output device and plays a random MIDI note every second.

const { EncodeStream } = require('@lachenmayer/midi-messages')
const midi = require('midi')

const output = new midi.output()
output.openVirtualPort('random note every second')

const encode = new EncodeStream()
encode.pipe(midi.createWriteStream(output))

setInterval(() => {
  const note = Math.floor(Math.random() * 128)
  const velocity = Math.floor(Math.random() * 128)
  console.log('Playing note:', note, 'velocity:', velocity)
  encode.noteOn(1, note, velocity)
  setTimeout(() => {
  console.log('Stopping note:', note)
    encode.noteOff(1, note, velocity)
  }, 200)
}, 1000)

API

This module exposes Node streams for encoding & decoding. If you are unfamiliar with how Node streams work, check out stream-handbook for a hands-on introduction.

MIDIMessage

A MIDIMessage object represents a single MIDI message. Every MIDIMessage object has a type field which corresponds to the message type (status byte) as defined in the MIDI specification. Most other messages contain data fields, eg. a lot of messages contain a channel field.

The MIDIMessage types are defined in src/types.ts, check this file for the exact definitions. An example definition looks like this:

type NoteOn = {
  type: 'NoteOn'
  channel: Channel
  note: U7
  velocity: U7
}

The Channel & U7 types are really just numbers. The type names are used as a documentation hint to remind you of the range of values that can be encoded in a message. The ranges are not enforced at runtime - you are responsible for checking that the values you write are within range, otherwise they will silently overflow. You should be aware of these types:

| Type | Range (inclusive) | Comment | |------|-------------------|-------| | Channel | 1-16 | 1-indexed, ie. the first channel is 1, not 0 | | U7 | 0-127 | 7-bit unsigned integer | | U14 | 0-16383 | 14-bit unsigned integer |

EncodeStream

A transform stream which turns MIDIMessage objects into buffers containing binary MIDI data. Use this if you want to generate new MIDI messages in your application and send them "down the wire", eg. to a MIDI device.

const encode = new EncodeStream(options?)

Options & default values:

  • useRunningStatus: true encode messages using running status, ie. omit the status byte when the previous message has the same status byte.

encode.write(message: MIDIMessage)

Use this to manually encode a MIDIMessage object. You can either call this directly, or use one of the convenience methods listed below to encode a message.

encode.pipe(destination: WritableStream)

Use this to automatically push data to a writable stream, for example a node-midi output stream, or a file stream.

encode.on('data', cb: (buf: Buffer) => any)

The data event is emitted (synchronously) with the message encoded in a Buffer every time a message has been written to the stream.

encode.on('error', cb: (err: Error) => any)

The error event is emitted when there is an error in your input.

The error can be one of the following:

  • err.name === 'TypeError': Thrown when the message you wrote using encode.write is not a valid MIDIMessage object.

Convenience methods

EncodeStream instances expose the following methods, which are very simple wrappers around encode.write.

encode.noteOff(channel: Channel, note: U7, velocity: U7)
encode.noteOn(channel: Channel, note: U7, velocity: U7)
encode.polyKeyPressure(channel: Channel, note: U7, pressure: U7)
encode.controlChange(channel: Channel, control: U7, value: U7 | U14)
encode.programChange(channel: Channel, number: U7)
encode.channelKeyPressure(channel: Channel, pressure: U7)
encode.pitchBendChange(channel: Channel, value: U14)
encode.rpnChange(channel: Channel, parameter: U14, value: U14)
encode.nrpnChange(channel: Channel, parameter: U14, value: U14)
encode.allSoundOff(channel: Channel)
encode.resetAllControllers(channel: Channel)
encode.localControl(channel: Channel, value: boolean)
encode.allNotesOff(channel: Channel)
encode.omniOff(channel: Channel)
encode.omniOn(channel: Channel)
encode.monoMode(channel: Channel)
encode.polyMode(channel: Channel)
encode.sysEx(deviceId: SysExDeviceID, data: U7[])
encode.mtcQuarterFrame(data: U7)
encode.songPositionPointer(position: U14)
encode.songSelect(number: U7)
encode.tuneRequest()
encode.timingClock()
encode.start()
encode.continue()
encode.stop()
encode.activeSensing()
encode.systemReset()

DecodeStream

A transform stream which parses binary MIDI data into MIDIMessage objects. Use this if you want to interpret MIDI data coming "from the wire", eg. from a MIDI device.

const decode = new DecodeStream()

This constructor has no options. Running status is handled automatically as needed.

decode.write(buf: Buffer)

Use this to manually decode a single Buffer containing binary MIDI data.

decode.pipe(destination: WritableStream)

Use this to automatically push data to a writable stream.

Note that the readable side of DecodeStream operates in object mode, so you will not be able to pipe this directly to a stream expecting binary data.

decode.on('data', cb: (message: MIDIMessage) => any)

The data event is emitted (synchronously) with a MIDIMessage object for every message found in the given buffer that was written to the stream.

decode.on('error', cb: (err: Error) => any)

The error event is emitted when there is an error in your input.

The error can be one of the following:

  • err.name === 'UnexpectedDataError': Thrown when the protocol expects a status byte (0x80-0xFF), but a data byte (0x00-0x7F) was found. Indicates that the contents of the buffer are not valid MIDI data (or that there is a bug in the decoding implementation!)
  • err.name === 'UnexpectedEOFError': Thrown when the protocol expects further data bytes, but end of the buffer has been reached. The input buffer must contain full MIDI messages.
  • err.name === 'InternalError': If you see one of these, please open an issue. Indicates an implementation error in the decoding logic.

Further reading

License

MIT © 2018 harry lachenmayer