Writing MIDI Files
Below is a basic guide to writing .mid and .rmi files.
Writing a MIDI file
Important
Also see Creating MIDI Files From Scratch
writeMIDI
Writes the sequence as a Standard MIDI File.
The returned value is an ArrayBuffer
- a binary representation of the Standard MIDI File.
modify
Allows easily modifying the sequence’s programs and controllers.
midi.modify(desiredProgramChanges, desiredControllerChanges, desiredChannelsToClear, desiredChannelsToTranspose);
- desiredProgramChanges - an array of objects, defined as follows:
/**
* @typedef desiredProgramChange {Object}
* @property {number} channel - the channel to modify. Note that this allows going over 16 if the MIDI is a multi port file
* @property {number} program - the MIDI program to use.
* @property {number} bank - the bank to use.
* @property {boolean} isDrum - if the channel is a drum channel. Will add GS Use Drums System exclusive and GS on if needed
*/
- desiredControllerChanges - an array of objects, defined as follows:
/**
* @typedef desiredControllerChange {Object}
* @property {number} channel - same as above.
* @property {number} controllerNumber - the MIDI CC number to use.
* @property {number} controllerValue - the desired value of the controller.
*
*/
- desiredChannelsToClear - an array of numbers, indicating the channel number to effectively mute.
- desiredChannelsToTranspose - an array of objects, defined as follows:
/**
* @typedef desiredTranspose {Object}
* @property {number} channel - same as above.
* @property {number} keyShift - the amount to shift the notes on this channel by. Can be negative. The decimal part will be tuned via the RPN fine tune command if provided.
*/
Warning
Clearing the channel removes the messages rather than setting volume to 0! This operation is irreversible if the original midi file is lost.
applySnapshot
Applies a SynthesizerSnapshot to the sequence in place. This means changing the programs and controllers if they are locked.
- snapshot - the
SynthesizerSnapshot
to use.
For example, if channel 1 has locked preset on Drawbar Organ
,
this will remove all program changes for channel 1 and add one at the start to change the program to Drawbar organ
.
Example
Below is a basic example of writing a modified MIDI file
// create your midi and synthesizer
const midi = BasicMIDI.fromArrayBuffer(yourBufferGoesHere);
const synth = new SpessaSynthProcessor(44100);
// ...
// get the snapshot and apply it
const snapshot = SynthesizerSnapshot.create(synth);
midi.applySnapshot(snapshot);
// write midi
const midiBinary = midi.writeMIDI();
// save the file
const blob = new Blob([midiBinary.buffer], {type: "audio/midi"});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = midi.name + ".mid";
a.click();
Writing an .rmi file
writeRMIDI
This function writes out an RMIDI file (MIDI + SF2). See more info about this format
Parameters
soundBankBinary
The binary sound bank to embed into the file.
midi
BasicMIDI
to embed.
soundfont
SoundFont2
- The soundfont that soundfontBinary
contains. Used for correcting bank and program changes.
bankOffset
number
, optional - The bank offset to apply to the file. A value of 0 is
recommended. See this for more info
encoding
string
, optional - The encoding to add to the INFO chunk of an RMID file. Make sure to pick a value that is acceptable
by the TextDecoder
metadata
Object
, optional - The metadata of the file. If left undefined, some basic metadata (like song’s title) will be copied
from the MIDI.
Important
All the properties below are optional.
- name -
string
- the name of the song. - engineer -
string
- the engineer of the soundfont. - artist -
string
- the artist of the song. - album -
string
- the album’s name. - genre -
string
- the song’s genre. - comment -
string
- a comment about the song. - creationDate -
string
- the creation date of the file. If not provided, current day is used. - copyright -
string
- the copyright string. If not provided,midi.copyright
is copied. - picture -
ArrayBuffer
- the album cover of the song. Binary data of the image. - midiEncoding -
string
- The encoding of the inner MIDI file. Make sure to pick a value that is acceptable by theTextDecoder
Warning
Providing any of the metadata fields overrides the encoding with utf-8
.
This behavior is forced due to lack of support for other encodings by the TextEncoder
class.
correctBankOffset
boolean
, optional - if the function should correct all the
program-selects, and the bank-selects in the MIDI file to reflect the embedded soundfont
(i.e., Make it Self-contained).
Recommended unless a specific use-case is required.
Defaults to true
.
Tip
use trimSoundBank to drastically reduce the file size. consider also using compression (like shown in example) to save even more space. (using these both methods, I managed to cram a 1GB soundfont into a 5MB RMIDI!)
Example
Below is a simple example for exporting an RMIDI file
<label for='soundfont_upload'>Upload soundfont</label>
<input type='file' id='soundfont_upload'>
<label for='midi_upload'>Upload MIDI</label>
<input type='file' id='midi_upload'>
<button id='export'>Export</button>
Note
This example uses soundfont3 compression. Make sure you’ve read this
const sfInput = document.getElementById("soundfont_upload");
const midiInput = document.getElementById("midi_upload");
document.getElementById("export").onchange = async () => {
// get the files
const soundBank = SoundBankLoader.fromArrayBuffer(await sfInput.files[0].arrayBuffer());
const midi = BasicMIDI.fromArrayBuffer(await midiInput.files[0].arrayBuffer());
// trim the soundfont
soundBank.trimSoundBank(soundBank);
// write out with compression to save space (0.5 is medium quality)
const soundfontBinary = await soundBank.writeSF2({
compress: true,
compressionFunction: SampleEncodingFunction // Remember to get your compression function
});
// get the rmidi
const rmidiBinary = midi.writeRMIDI(soundBankBinary, {
soundBank,
metadata: {
name: "A cool song",
artist: "John",
creationDate: new Date(),
album: "John's songs",
genre: "Rock",
comment: "My favorite!"
}
});
// save the file
const blob = new Blob([rmidiBinary.buffer], {type: "audio/rmid"});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = midi.name + ".rmi";
a.click();
}