MidiWriterJS
♬ A JavaScript library which provides an API for programmatically generating and creating expressive multi-track MIDI files and JSON.
Install / Use
/learn @grimmdude/MidiWriterJSREADME
MidiWriterJS
A JavaScript library for generating expressive multi-track MIDI files.
- Multi-track MIDI file generation
- Written in TypeScript with full type definitions
- Works in Node.js (CJS & ESM) and the browser
- Note names (
C#4,Eb5) or raw MIDI numbers - Chords, arpeggios, grace notes, pitch bends, and more
- Experimental VexFlow integration
Install
npm install midi-writer-js
Quick Start
ESM (recommended)
import MidiWriter from 'midi-writer-js';
const track = new MidiWriter.Track();
track.addEvent(new MidiWriter.ProgramChangeEvent({instrument: 1}));
track.addEvent(new MidiWriter.NoteEvent({pitch: ['C4', 'D4', 'E4'], duration: '4', sequential: true}));
const writer = new MidiWriter.Writer(track);
console.log(writer.dataUri());
CommonJS
const MidiWriter = require('midi-writer-js');
const track = new MidiWriter.Track();
track.addEvent(new MidiWriter.NoteEvent({pitch: ['C4', 'E4', 'G4'], duration: '2'}));
const writer = new MidiWriter.Writer(track);
console.log(writer.dataUri());
TypeScript
import MidiWriter from 'midi-writer-js';
const track = new MidiWriter.Track();
track.addEvent(new MidiWriter.NoteEvent({pitch: ['C4', 'E4', 'G4'], duration: '2'}));
const writer = new MidiWriter.Writer(track);
const data: Uint8Array = writer.buildFile();
Examples
Melody (Hot Cross Buns)
import MidiWriter from 'midi-writer-js';
const track = new MidiWriter.Track();
track.addEvent([
new MidiWriter.NoteEvent({pitch: ['E4', 'D4'], duration: '4'}),
new MidiWriter.NoteEvent({pitch: ['C4'], duration: '2'}),
new MidiWriter.NoteEvent({pitch: ['E4', 'D4'], duration: '4'}),
new MidiWriter.NoteEvent({pitch: ['C4'], duration: '2'}),
new MidiWriter.NoteEvent({pitch: ['C4', 'C4', 'C4', 'C4', 'D4', 'D4', 'D4', 'D4'], duration: '8'}),
new MidiWriter.NoteEvent({pitch: ['E4', 'D4'], duration: '4'}),
new MidiWriter.NoteEvent({pitch: ['C4'], duration: '2'}),
], function(event, index) {
return {sequential: true};
}
);
const writer = new MidiWriter.Writer(track);
console.log(writer.dataUri());
Chords vs. Sequential Notes
When pitch is an array, the notes play as a chord by default. Set sequential: true to play them one after another instead.
// Chord (notes play simultaneously)
track.addEvent(new MidiWriter.NoteEvent({pitch: ['C4', 'E4', 'G4'], duration: '1'}));
// Arpeggio (notes play sequentially)
track.addEvent(new MidiWriter.NoteEvent({pitch: ['C4', 'E4', 'G4'], duration: '8', sequential: true}));
Multi-Track
Pass an array of tracks to Writer to create a multi-track MIDI file.
import MidiWriter from 'midi-writer-js';
const melody = new MidiWriter.Track();
melody.addTrackName('Melody');
melody.addEvent(new MidiWriter.ProgramChangeEvent({instrument: 1}));
melody.addEvent(new MidiWriter.NoteEvent({pitch: ['E5', 'D5', 'C5'], duration: '4', sequential: true}));
const bass = new MidiWriter.Track();
bass.addTrackName('Bass');
bass.addEvent(new MidiWriter.ProgramChangeEvent({instrument: 33}));
bass.addEvent(new MidiWriter.NoteEvent({pitch: ['C2'], duration: '1'}));
const writer = new MidiWriter.Writer([melody, bass]);
console.log(writer.dataUri());
Controller Changes & Pitch Bend
import MidiWriter from 'midi-writer-js';
const track = new MidiWriter.Track();
// Set volume via CC #7
track.addEvent(new MidiWriter.ControllerChangeEvent({controllerNumber: 7, controllerValue: 100}));
// Pitch bend ranging from -1.0 to 1.0 (0 = no bend)
track.addEvent(new MidiWriter.PitchBendEvent({bend: 0.5}));
track.addEvent(new MidiWriter.NoteEvent({pitch: ['E4'], duration: '2'}));
const writer = new MidiWriter.Writer(track);
console.log(writer.dataUri());
API Summary
Track
| Method | Description |
|---|---|
| addEvent(event, mapFunction?) | Add one or more events. Supports method chaining. |
| setTempo(bpm, tick?) | Set tempo in beats per minute. |
| setTimeSignature(numerator, denominator) | Set time signature. |
| setKeySignature(sf, mi?) | Set key signature (e.g., 'C', 'Dm', 'F#'). |
| setPitchBend(bend) | Set pitch bend (-1.0 to 1.0). |
| controllerChange(number, value, channel?, delta?) | Add a controller change event. |
| addTrackName(text) | Set the track name. |
| addText(text) | Add a text event. |
| addCopyright(text) | Add a copyright notice. |
| addInstrumentName(text) | Set the instrument name. |
| addMarker(text) | Add a marker event. |
| addCuePoint(text) | Add a cue point event. |
| addLyric(text) | Add a lyric event. |
| mergeTrack(track) | Merge another track's events into this track. |
| removeEventsByName(name) | Remove all events of a given type. |
| polyModeOn() | Enable poly mode. |
NoteEvent Options
| Name | Type | Default | Description |
|---|---|---|---|
| pitch | string or array | | Each pitch can be a string or valid MIDI note code. Format for string is C#4. You can use the output from tonal to build scales, chords, etc. |
| duration | string or array | '4' | How long the note should sound (see Duration Values). If an array of durations is passed then the sum will be used. |
| wait | string or array | 0 | Rest before sounding note. Takes same values as duration. |
| sequential | boolean | false | If true, array of pitches plays sequentially instead of as a chord. |
| velocity | number | 50 | How loud the note should sound, values 1-100. |
| repeat | number | 1 | How many times this event should repeat. |
| channel | number | 1 | MIDI channel to use (1-16). |
| grace | string or array | | Grace note(s) applied before the main note. Takes same format as pitch. |
| startTick | number | | Explicit tick position for this event. If supplied, wait is ignored. |
| tick | number | | Alias for startTick. |
Writer
| Method | Returns | Description |
|---|---|---|
| buildFile() | Uint8Array | Build the MIDI file as a byte array. |
| base64() | string | Base64-encoded string of the MIDI file. |
| dataUri() | string | Data URI string (useful for playback or download links). |
| stdout() | — | Write MIDI file to stdout (Node.js CLI usage). |
| setOption(key, value) | Writer | Set an option on the writer instance. |
Event Classes
| Class | Description |
|---|---|
| NoteEvent | High-level note event that generates NoteOn/NoteOff pairs. |
| NoteOnEvent | Raw MIDI note-on event. |
| NoteOffEvent | Raw MIDI note-off event. |
| ProgramChangeEvent | Change the instrument (patch) on a channel. |
| PitchBendEvent | Pitch bend event (-1.0 to 1.0). |
| ControllerChangeEvent | Controller change (CC) event. |
| TempoEvent | Set tempo (BPM). |
| TimeSignatureEvent | Set time signature. |
| KeySignatureEvent | Set key signature. |
| TextEvent | General text meta event. |
| CopyrightEvent | Copyright meta event. |
| TrackNameEvent | Track/sequence name meta event. |
| InstrumentNameEvent | Instrument name meta event. |
| MarkerEvent | Marker meta event. |
| CuePointEvent | Cue point meta event. |
| LyricEvent | Lyric meta event. |
| EndTrackEvent | End of track meta event (added automatically). |
See the full API documentation for details on each class.
Duration Values
| Value | Duration |
|---|---|
| 1 | Whole |
| 2 | Half |
| d2 | Dotted half |
| dd2 | Double dotted half |
| 4 | Quarter |
| 4t | Quarter triplet |
| d4 | Dotted quarter |
| dd4 | Double dotted quarter |
| 8 | Eighth |
| 8t | Eighth triplet |
| d8 | Dotted eighth |
| dd8 | Double dotted eighth |
| 16 | Sixteenth |
| 16t | Sixteenth triplet |
| 32 | Thirty-second |
| 64 | Sixty-fourth |
| Tn | Explicit number of ticks (e.g., T128 = 1 beat) |
Recipes
Rests
Use the wait property to add silence before a note. There is no dedicated rest event — wait accepts the same duration values as duration.
// Quarter rest followed by a quarter note
track.addEvent(new MidiWriter.NoteEvent({pitch: 'C4', duration: '4', wait: '4'}));
// Half rest followed by a whole note
track.addEvent(new MidiWriter.NoteEvent({pitch: 'E4', duration: '1', wait: '2'}));
Drums (Channel 10)
MIDI channel 10 is reserved for percussion. Pitch values map to drum sounds (e.g., C2 = kick, D2 = snare, F#2 = hi-hat).
const drums = new MidiWriter.Track();
drums.addTrackName('Drums');
drums.addEvent(new MidiWriter.NoteEvent({pitch: ['C2'], duration: '4', channel: 10, velocity: 80}));
drums.addEvent(new MidiWriter.NoteEvent({pitch: ['D2'], duration: '4', channel: 10, velocity: 80}));
drums.addEvent(new MidiWriter.NoteEvent({pitch: ['F#2'], duration: '8', channel: 10, repeat: 4}));
Dynamics (Velocity)
Control note loudness with velocity (1-100). Simulate a crescendo by gradually increasing velocity.
track.addEvent(new MidiWriter.NoteEvent({pitch: 'C4', duration: '4', velocity: 30})); // piano
track.addEvent(new MidiWriter.NoteEvent({pitch: 'E4', duration: '4', velocity: 60})); // mezzo-forte
track.addEvent(new MidiWriter.NoteEvent({pitch: 'G4', duration: '4', velocity: 100})); // fortissimo
Tempo Changes
Set tempo at the start of a track, or change it mid-track by speci
Related Skills
docs-writer
99.4k`docs-writer` skill instructions As an expert technical writer and editor for the Gemini CLI project, you produce accurate, clear, and consistent documentation. When asked to write, edit, or revie
model-usage
339.5kUse CodexBar CLI local cost usage to summarize per-model usage for Codex or Claude, including the current (most recent) model or a full model breakdown. Trigger when asked for model-level usage/cost data from codexbar, or when you need a scriptable per-model summary from codexbar cost JSON.
Design
Campus Second-Hand Trading Platform \- General Design Document (v5.0 \- React Architecture \- Complete Final Version)1\. System Overall Design 1.1. Project Overview This project aims t
arscontexta
2.9kClaude Code plugin that generates individualized knowledge systems from conversation. You describe how you think and work, have a conversation and get a complete second brain as markdown files you own.
