{"id":19905837,"url":"https://github.com/lachenmayer/midi-messages","last_synced_at":"2025-10-24T05:15:27.590Z","repository":{"id":33660247,"uuid":"158575367","full_name":"lachenmayer/midi-messages","owner":"lachenmayer","description":"A MIDI message encoder/decoder","archived":false,"fork":false,"pushed_at":"2022-04-09T20:55:17.000Z","size":53,"stargazers_count":20,"open_issues_count":2,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-09-29T19:56:11.652Z","etag":null,"topics":["decoder","encoder","midi","music","nodejs","stream","typescript"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lachenmayer.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-11-21T16:16:49.000Z","updated_at":"2023-11-28T02:03:00.000Z","dependencies_parsed_at":"2022-07-25T19:02:19.248Z","dependency_job_id":null,"html_url":"https://github.com/lachenmayer/midi-messages","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/lachenmayer/midi-messages","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lachenmayer%2Fmidi-messages","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lachenmayer%2Fmidi-messages/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lachenmayer%2Fmidi-messages/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lachenmayer%2Fmidi-messages/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lachenmayer","download_url":"https://codeload.github.com/lachenmayer/midi-messages/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lachenmayer%2Fmidi-messages/sbom","scorecard":{"id":576609,"data":{"date":"2025-08-11","repo":{"name":"github.com/lachenmayer/midi-messages","commit":"14fd4041c254b8f2f0605367de6488769930a3b4"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2,"checks":[{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":0,"reason":"Found 0/9 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"License","score":0,"reason":"license file not detected","details":["Warn: project does not have a license file"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":5,"reason":"5 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-vh95-rmgr-6w4m","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-hj48-42vr-x3v9"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-20T17:59:06.559Z","repository_id":33660247,"created_at":"2025-08-20T17:59:06.559Z","updated_at":"2025-08-20T17:59:06.559Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":280742528,"owners_count":26382926,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-24T02:00:06.418Z","response_time":73,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["decoder","encoder","midi","music","nodejs","stream","typescript"],"created_at":"2024-11-12T20:34:42.332Z","updated_at":"2025-10-24T05:15:27.558Z","avatar_url":"https://github.com/lachenmayer.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @lachenmayer/midi-messages\n\nA 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.\n\nThis module exists because [node-midi](https://www.npmjs.com/package/midi) and [WebMIDI](https://www.w3.org/TR/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.\n\nThis 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](src/types.ts) for details on how the message objects are structured.\n\n## Non-scope / caveats\n\n- **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.\n\n- **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.\n\n- **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.\n\n- **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).\n\n## Install\n\n```\nnpm install @lachenmayer/midi-messages\n```\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cem\u003e(Why `@lachenmayer/midi-messages` and not just `midi-messages`?)\u003c/em\u003e\u003c/summary\u003e\nWhile I believe that this module could be useful to a lot of people, I wrote it for my own purposes, and I'm not interested in maintaining it for others. If you find a bug, feel free to open an issue or pull request, but don't expect me to spend time fixing it unless it affects me.\n\nI 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.\n\nFeel free to publish your own fork on NPM, but please publish it under your own scope.\n\u003c/details\u003e\n\n## Usage\n\n### Simple encoding\n\n```js\nconst { EncodeStream } = require('@lachenmayer/midi-messages')\n\nconst encode = new EncodeStream()\n\nencode.on('data', buf =\u003e {\n  console.log(buf)\n})\n\nencode.noteOn(1, 64, 100)\n// ...equivalent to:\n// encode.write({ type: 'NoteOn', channel: 1, note: 64, velocity: 100 })\n```\n\n**Output:**\n\n```\n\u003cBuffer 90 40 64\u003e\n```\n\n### Simple decoding\n\n```js\nconst { DecodeStream } = require('@lachenmayer/midi-messages')\n\nconst decode = new DecodeStream()\n\ndecode.on('data', message =\u003e {\n  console.log(message)\n})\n\ndecode.write(Buffer.from('904064', 'hex'))\ndecode.write(Buffer.from('80407f', 'hex'))\n```\n\n**Output:**\n\n```js\n{ type: 'NoteOn', channel: 1, note: 64, velocity: 100 }\n{ type: 'NoteOff', channel: 1, note: 64, velocity: 127 }\n```\n\n### Usage with [node-midi](https://npm.im/midi)\n\nThe following example creates a virtual MIDI output device and plays a random MIDI note every second.\n\n```js\nconst { EncodeStream } = require('@lachenmayer/midi-messages')\nconst midi = require('midi')\n\nconst output = new midi.output()\noutput.openVirtualPort('random note every second')\n\nconst encode = new EncodeStream()\nencode.pipe(midi.createWriteStream(output))\n\nsetInterval(() =\u003e {\n  const note = Math.floor(Math.random() * 128)\n  const velocity = Math.floor(Math.random() * 128)\n  console.log('Playing note:', note, 'velocity:', velocity)\n  encode.noteOn(1, note, velocity)\n  setTimeout(() =\u003e {\n  console.log('Stopping note:', note)\n    encode.noteOff(1, note, velocity)\n  }, 200)\n}, 1000)\n```\n\n## API\n\nThis module exposes [Node streams](https://nodejs.org/docs/latest/api/stream.html) for encoding \u0026 decoding. **If you are unfamiliar with how Node streams work, check out [stream-handbook](https://github.com/substack/stream-handbook) for a hands-on introduction.**\n\n### [`MIDIMessage`](src/types.ts)\n\nA `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.\n\nThe `MIDIMessage` types are defined in [`src/types.ts`](src/types.ts), check this file for the exact definitions. An example definition looks like this:\n\n```typescript\ntype NoteOn = {\n  type: 'NoteOn'\n  channel: Channel\n  note: U7\n  velocity: U7\n}\n```\n\nThe `Channel` \u0026 `U7` types are really just `number`s. 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:\n\n| Type | Range (inclusive) | Comment |\n|------|-------------------|-------|\n| `Channel` | `1`-`16` | **1-indexed**, ie. the first channel is `1`, not `0` |\n| `U7` | `0`-`127` | 7-bit unsigned integer |\n| `U14` | `0`-`16383` | 14-bit unsigned integer |\n\n### [`EncodeStream`](src/EncodeStream.ts)\n\nA [transform stream](https://nodejs.org/api/stream.html#stream_class_stream_transform) which turns [`MIDIMessage`](src/types.ts) 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.\n\n#### `const encode = new EncodeStream(options?)`\n\nOptions \u0026 default values:\n\n- `useRunningStatus: true` encode messages using [running status](http://midi.teragonaudio.com/tech/midispec/run.htm), ie. omit the status byte when the previous message has the same status byte.\n\n#### `encode.write(message: MIDIMessage)`\n\nUse this to manually encode a [`MIDIMessage`](src/types.ts) object. You can either call this directly, or use one of the convenience methods listed below to encode a message.\n\n#### `encode.pipe(destination: WritableStream)`\n\nUse this to automatically push data to a writable stream, for example a [`node-midi`](https://npm.im/midi) output stream, or a [file stream](https://nodejs.org/api/fs.html#fs_fs_createwritestream_path_options).\n\n#### `encode.on('data', cb: (buf: Buffer) =\u003e any)`\n\nThe `data` event is emitted (synchronously) with the message encoded in a [Buffer](https://nodejs.org/api/buffer.html) every time a message has been written to the stream.\n\n#### `encode.on('error', cb: (err: Error) =\u003e any)`\n\nThe `error` event is emitted when there is an error in your input.\n\nThe error can be one of the following:\n\n- `err.name === 'TypeError'`: Thrown when the message you wrote using `encode.write` is not a valid [`MIDIMessage`](src/types.ts) object.\n\n#### Convenience methods\n\n`EncodeStream` instances expose the following methods, which are very simple wrappers around `encode.write`.\n\n```typescript\nencode.noteOff(channel: Channel, note: U7, velocity: U7)\nencode.noteOn(channel: Channel, note: U7, velocity: U7)\nencode.polyKeyPressure(channel: Channel, note: U7, pressure: U7)\nencode.controlChange(channel: Channel, control: U7, value: U7 | U14)\nencode.programChange(channel: Channel, number: U7)\nencode.channelKeyPressure(channel: Channel, pressure: U7)\nencode.pitchBendChange(channel: Channel, value: U14)\nencode.rpnChange(channel: Channel, parameter: U14, value: U14)\nencode.nrpnChange(channel: Channel, parameter: U14, value: U14)\nencode.allSoundOff(channel: Channel)\nencode.resetAllControllers(channel: Channel)\nencode.localControl(channel: Channel, value: boolean)\nencode.allNotesOff(channel: Channel)\nencode.omniOff(channel: Channel)\nencode.omniOn(channel: Channel)\nencode.monoMode(channel: Channel)\nencode.polyMode(channel: Channel)\nencode.sysEx(deviceId: SysExDeviceID, data: U7[])\nencode.mtcQuarterFrame(data: U7)\nencode.songPositionPointer(position: U14)\nencode.songSelect(number: U7)\nencode.tuneRequest()\nencode.timingClock()\nencode.start()\nencode.continue()\nencode.stop()\nencode.activeSensing()\nencode.systemReset()\n```\n\n### [`DecodeStream`](src/DecodeStream.ts)\n\nA [transform stream](https://nodejs.org/api/stream.html#stream_class_stream_transform) 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.\n\n#### `const decode = new DecodeStream()`\n\nThis constructor has no options. Running status is handled automatically as needed.\n\n#### `decode.write(buf: Buffer)`\n\nUse this to manually decode a single [Buffer](https://nodejs.org/api/buffer.html) containing binary MIDI data.\n\n#### `decode.pipe(destination: WritableStream)`\n\nUse this to automatically push data to a writable stream.\n\nNote that the readable side of `DecodeStream` operates in [object mode](https://nodejs.org/api/stream.html#stream_object_mode), so you will not be able to pipe this directly to a stream expecting binary data.\n\n#### `decode.on('data', cb: (message: MIDIMessage) =\u003e any)`\n\nThe `data` event is emitted (synchronously) with a `MIDIMessage` object for every message found in the given buffer that was written to the stream.\n\n#### `decode.on('error', cb: (err: Error) =\u003e any)`\n\nThe `error` event is emitted when there is an error in your input.\n\nThe error can be one of the following:\n\n- `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!)\n- `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.\n- `err.name === 'InternalError'`: If you see one of these, please open an issue. Indicates an implementation error in the decoding logic.\n\n## Further reading\n\n- [MIDI Spec 1.0](https://www.midi.org/specifications-old/item/the-midi-1-0-specification)\n- [http://midi.teragonaudio.com/tech/midispec.htm](http://midi.teragonaudio.com/tech/midispec.htm)\n- [MIDI Events DTD](https://www.midi.org/dtds/MIDIEvents10.dtd.html)\n\n## License\n\nMIT © 2018 harry lachenmayer","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flachenmayer%2Fmidi-messages","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flachenmayer%2Fmidi-messages","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flachenmayer%2Fmidi-messages/lists"}