@rgglez/midi2tonejs
v0.2.2
Published
Convert MIDI files to the JSON format used by Tone.js (@tonejs/midi)
Readme
midi2tonejs
midi2tonejs converts Standard MIDI Files (.mid) into the JSON format that
@tonejs/midi produces with Midi.toJSON().
The JSON can go straight into new Midi().fromJSON(json) or be read directly by
code built on Tone.js.
The tool works as a native command-line program and as a WebAssembly module for the browser.
Output format
{
"header": {
"keySignatures": [{ "key": "Eb", "scale": "minor", "ticks": 0 }],
"meta": [{ "text": "Verse", "ticks": 0, "type": "marker" }],
"name": "Song title",
"ppq": 480,
"tempos": [{ "bpm": 120, "ticks": 0 }],
"timeSignatures": [{ "ticks": 0, "timeSignature": [4, 4], "measures": 0 }]
},
"tracks": [
{
"channel": 0,
"controlChanges": { "7": [{ "number": 7, "ticks": 0, "time": 0, "value": 0.78 }] },
"pitchBends": [{ "ticks": 200, "time": 0.2, "value": -0.5 }],
"instrument": { "family": "piano", "number": 0, "name": "acoustic grand piano" },
"name": "Piano",
"notes": [
{ "duration": 0.5, "durationTicks": 480, "midi": 60, "name": "C4",
"ticks": 0, "time": 0, "velocity": 0.787 }
],
"endOfTrackTicks": 1920
}
]
}The conversion copies the behaviour of @tonejs/midi in detail:
- Each source track is split into one track per (program, channel) pair.
- A format‑1 conductor track with no notes is dropped.
- Note‑ons are matched to note‑offs in the same order Tone.js uses.
- Velocities and controller values are scaled to
0..1, and pitch bend to-1..1. - Seconds are computed with the same floating‑point operations as Tone.js.
Its output was compared with @tonejs/midi and parses to the same object.
The only textual difference is that whole numbers are written as 120.0 instead of 120.
Files that use SMPTE timecode instead of PPQ timing are not supported.
Requirements
- Rust 1.74 or newer (
rustup,cargo) - For the web build: the
wasm32-unknown-unknowntarget andwasm-pack
Command line
cargo build --release
./target/release/midi2tonejs --input song.mid --output song.json| Flag | Description |
|------|-------------|
| -i, --input <FILE> | Source MIDI file |
| -o, --output <FILE> | Destination JSON file |
| -p, --pretty | Pretty‑print the JSON |
| -h, --help | Show help |
| -V, --version | Show version |
To install it in ~/.cargo/bin, run cargo install --path ..
WebAssembly
The crate has two Cargo features:
cli(enabled by default) addsclapand the binary.wasmadds awasm-bindgenexport.
Build the web version without the CLI:
rustup target add wasm32-unknown-unknown
cargo install wasm-pack
wasm-pack build --target web --release -- --no-default-features --features wasmThis writes an ES module to pkg/. It exports one function:
midiToJson(bytes: Uint8Array, pretty: boolean): string // throws on invalid MIDIExample
<input type="file" id="file" accept=".mid,.midi">
<script type="module">
import init, { midiToJson } from "./pkg/midi2tonejs.js";
await init();
document.getElementById("file").addEventListener("change", async (e) => {
const bytes = new Uint8Array(await e.target.files[0].arrayBuffer());
const midi = JSON.parse(midiToJson(bytes, false));
console.log(midi.header.tempos, midi.tracks.length);
});
</script>To use the result with Tone.js:
import { Midi } from "@tonejs/midi";
import * as Tone from "tone";
const midi = new Midi();
midi.fromJSON(JSON.parse(midiToJson(bytes, false)));
const synth = new Tone.PolySynth().toDestination();
midi.tracks.forEach((track) =>
track.notes.forEach((n) =>
synth.triggerAttackRelease(n.name, n.duration, Tone.now() + n.time, n.velocity)));Use --target bundler for webpack or Vite, or --target nodejs for Node.
As a Rust library
[dependencies]
midi2tonejs = { git = "https://github.com/rgglez/music-midi2tonejs", default-features = false }let bytes = std::fs::read("song.mid")?;
let json: String = midi2tonejs::convert(&bytes, true)?; // JSON text
let midi: midi2tonejs::Midi = midi2tonejs::parse(&bytes)?; // typed structsDevelopment
A Makefile wraps the common tasks:
| Rule | Does |
|------|------|
| make / make all | cli + wasm |
| make cli | Release binary in target/release/ |
| make wasm | WebAssembly package in pkg/ (WASM_TARGET=bundler\|nodejs to change) |
| make wasm-deps | Install the wasm32 target and wasm-pack |
| make test | Run tests |
| make lint | rustfmt check + clippy |
| make install | Install the CLI into ~/.cargo/bin |
| make clean | Remove target/ and pkg/ |
Releasing
The version is set only in Cargo.toml. wasm-pack copies it into pkg/package.json, so the
npm package and the crate always have the same SemVer version.
npm login && cargo login # once
make bump VERSION=0.2.0 # updates Cargo.toml + Cargo.lock, commits and tags v0.2.0
make publish-dry # optional: build and check both packages without uploading
make publish # publishes to npm and crates.io
git push --follow-tagsmake publish runs preflight first. That step stops the release if any of these is true:
- Tests or lint fail.
- The working tree has uncommitted changes.
- The
vX.Y.Ztag is missing. - The version already exists on npm or crates.io.
Prerelease versions such as 0.2.0-beta.1 get the npm dist-tag next, so npm install does not
pick them up by default. The npm package is published as @rgglez/midi2tonejs, and the crate
as midi2tonejs.
Dependencies
License
Copyright © 2026 Rodolfo González González.
Released under the MIT License, the same license as @tonejs/midi.
