@marmooo/midy
v0.6.8
Published
A MIDI player/synthesizer written in TypeScript that supports GM-Lite/GM1 and SF2/SF3.
Readme
Midy
A MIDI player/synthesizer written in TypeScript that supports GM-Lite/GM1/GM2 and SF2/SF3.
This library provides several files depending on the implementation level.
- midy-GMLite.js: support minimal GM-Lite (ref: en, ja)
- midy-GM1.js: support minimal GM1 (ref: en, en full, ja)
- midy-GM2.js: support minimal GM2 (ref: en v1.2a, en v1.0, ja v1.0)
- midy.js: full implementation (in progress)
Demo
- General
- @marmooo/piano-visualizer - Beautiful piano visualizer
- @marmooo/midi-player - GUI library
- Free MIDI - Free MIDI search for everyone. Free to listen, copy, play, etc.
- midi2audio - Convert MIDI to audio (WAV, MP3, AAC, FLAC, Opus)
- Humidy - GM2 MIDI mixer app
- Timidy - Timidity++ style MIDI player
- Instruments
- Doremi Piano - Piano-style MPE MIDI controller
- 4x4pad - 4x4 grid MPE MIDI controller
- Celltone - Grid MPE MIDI controller with Janko-Piano layout
- Isotone - Grid MPE MIDI controller optimized for chords
- Hexatone - Hexagonal MPE MIDI controller with Wicki-Hayden layout
- Glisstone - Hexagonal MPE MIDI controller optimized for glissando
- Games
- Tip Tap Notes - Falling-note rhythm game with tap notes
- Flip Flap Notes - Falling-note rhythm game with various notes
Support Status
All implementations follow the specification.
| Message | Support | Notes | | :----------------------- | :-----: | :------- | | Note Off | ✔️ | | | Note On | ✔️ | | | Polyphonic Key Pressure | ✔️ | full GM2 | | Controller Change | ✔️ | full GM2 | | Program Change | ✔️ | full GM2 | | Channel Pressure | ✔️ | full GM2 | | Pitch Bend | ✔️ | | | System Exclusive Message | ✔️ | full GM2 | | System Common Message | ❌ | | | System Real Time Message | ✔️ | full GM2 | | MIDI Time Code | ❌ | | | MIDI Show Control | ❌ | | | MIDI Machine Control | ❌ | |
Usage
Initialization
// import { MidyGMLite as Midy } from "midy-GMLite.js";
// import { MidyGM1 as Midy } from "midy-GM1.js";
// import { MidyGM2 as Midy } from "midy-GM2.js";
import { Midy } from "midy.js";
const audioContext = new AudioContext();
if (audioContext.state === "running") await audioContext.suspend();
const midy = new Midy(audioContext);
await midy.loadMIDI("test.mid");
await midy.loadSoundFont("test.sf3");
await midy.start();Playback
midy.loop = true;
await midy.start();
await midy.stop();
await midy.pause();
await midy.resume();
midy.seekTo(second);
midy.tempoChange(tempo);Events
midy.addEventListener("looped", func);
midy.addEventListener("started", func);
midy.addEventListener("ended", func);
midy.addEventListener("stopped", func);
midy.addEventListener("paused", func);
midy.addEventListener("resumed", func);
midy.addEventListener("seeked", func);
midy.addEventListener("tempoChanged", func);Cache Mode
"none"Full real-time control (dynamic CC, LFO, pitch). Highest CPU."ads"Pre-render ADS; release fades in real time. LFO stays live."adsr"Pre-render full ADSR keyed by duration ticks + volRelease."note"Per-note offline bake (automation in-interval). MIDI-file only."segment"Per-channel tiled offline buffers; channel vol/pan stay live."chunk"All-channel tiled offline mix; vol/pan snapshotted into buffer."audio"Entire song pre-rendered to one buffer. Lowest CPU.
midy.cacheMode = "audio";Reverb Mode
- "Convolution" - Convolution Reverb
- "Schroeder" - Schroeder (1962)
- "Moorer" - Moorer (1979)
- "FDN" - FDN (1992)
- "Dattorro" - Dattorro (1997)
- "Freeverb" - Freeverb (1999)
- "VelvetNoise" - Velvet Noise Reverb (2012)
midy.setReverbEffect("FDN");MIDI Message
There are functions that handle MIDI messages as they are, as well as simplified functions.
midy.handleMessage(data, scheduleTime);
const channel = midy.channels[0];
channel.noteOff(noteNumber, velocity, endTime, force);
channel.noteOn(noteNumber, velocity, startTime);
channel.setPolyphonicKeyPressure(noteNumber, pressure);
channel.setControlChange(controlType, value);
channel.setProgramChange(programNumber);
channel.setChannelPressure(value);
channel.setPitchBend(value);Control Change
There are functions that handle control changes as they are, as well as simplified functions.
const channel = midy.channels[0];
channel.setControlChange(controller, value, scheduleTime); // [0-127] value
channel.setModulationDepth(value, scheduleTime); // [0-127] value
channel.setVolume(volume, scheduleTime); // [0-127] valueSystem Exclusive Message
There are functions that handle SysEx data as is, as well as simplified functions.
midy.handleSysEx(data, scheduleTime); // SysEx data without F0/F7
midy.handleMasterVolumeSysEx(data, scheduleTime); // [7F 7F 04 01 vv vv] data
midy.setMasterVolume(volume); // [0-1] volumeMPE (MIDI Polyphonic Expression)
midy.noteOff(channelNumber, noteNumber, velocity, endTime, force);
midy.noteOn(channelNumber, noteNumber, velocity, startTime);
midy.setControlChange(channelNumber, controlType, value);
midy.setProgramChange(channelNumber, programNumber);
midy.setChannelPressure(channelNumber, value);
midy.setPitchBend(channelNumber, value);Multiple Soundfonts
This library supports SF2 and SF3. In addition, it supports multiple soundfonts and splitted soundfonts that are optimized for playback on the web. The following example loads only the minimum presets required for playback.
const soundFontURL = "https://soundfonts.pages.dev/GeneralUser_GS_v2.0.3";
function getSoundFontPaths() {
const paths = [];
for (const instrument of midy.instruments) {
const [bank, program] = instrument.split(":");
const bankNumber = Number(bank);
const programNumber = Number(program);
const index = midy.soundFontTable[programNumber][bankNumber];
if (index !== undefined) continue;
const bankDir = String(bankNumber).padStart(3, "0");
const programFile = String(programNumber).padStart(3, "0");
paths.push(`${soundFontURL}/${bankDir}/${programFile}.sf3`);
}
return paths;
}
const paths = getSoundFontPaths();
await midy.loadSoundFont(paths);
// Optional: custom URL fallback
// (default: melodic .../BBB/PPP → .../000/PPP, drums .../128/PPP → .../128/000)
// await midy.loadSoundFont(paths, { fallback: (url) => [...] });
// await midy.loadSoundFont(paths, { fallback: null }); // disableBuild
deno task buildTest
deno test tests
deno test tests/GMLite-mock
deno test tests/GM1-mock
deno test tests/GM2-mock
deno test tests/full-mock
deno test tests/MPE-mockLicense
Apache-2.0
