Skip to content

Writing Wave Files

spessasus edited this page Sep 17, 2024 · 2 revisions

Writing Wave files

SpessaSynth has a helper function for writing wave files.

audioBufferToWav

Converts an audio buffer into a fully valid wave file.

const file = audioBufferToWav(audioBuffer, normalizeAudio = true, channelOffset = 0, metadata = {}, loop = undefined);
  • audioBuffer - AudioBuffer - the buffer to write.
  • normalizeAudio - optional boolean - if true, the gain of the entire song will be adjusted so the max sample is always 32767 or min is always -32768 (whichever is greater). Recommended.
  • channelOffset - optional number - if the buffer has more than 2 channels, you can specify the channel offset to use. This is especially useful in one output mode
  • metadata - optional Object described below. All options are string and are optional:
    • title - the song's title
    • artist - the song's artist
    • album - the song's album
    • genre - the song's genre
  • loop - optional Object that will write loop points to the file (using the cue chunk)
    • start - start time in seconds
    • end - end time in seconds

The metadata uses the INFO chunk to write the information. It is encoded with utf-8

Example code

This example code shows how to save MIDI to a wav file with loop points.

const parsedMid = new MIDI(midiBinary, "unnamed.mid");
const sampleRate = 44100; // hz
const durationInSamples = sampleRate * parsedMid.duration;

const context = new OfflineAudioContext({
  numberOfChannels: 2,
  samplerate: sampleRate,
  length: durationInSamples
});
// remember to add the module!
await context.audioWorklet.addModule("worklet_processor.min.js");
const synth = new Synthetizer(
    context.destination, // play directly to output
    soundfontBinary,
    false,
    {
        parsedMIDI: parsedMid,
        oneOutput: false,
        snapshot: undefined,
        loopCount: 0,
    }, 
    /*
    use the default effects. 
    NOTE: it is HIGHLY recommended that you provide the impulse response here as mentioned in Synthetizer page, 
    but it's omitted for simplicity
    */
    undefined
        
);

// start rendering
const buffer = await context.startRendering();

// calculate loop points
// the file skips to the first note on event, 
// but the loop points are absolute. 
// So we need to adjust them
const startOffset = MIDIticksToSeconds(parsedMid.firstNoteOn, parsedMid);
const loopStart = MIDIticksToSeconds(parsedMid.loop.start, parsedMid) - startOffset;
const loopEnd = MIDIticksToSeconds(parsedMid.loop.end, parsedMid) - startOffset;

// create the WAV file
const wav = audioBufferToWav(
    buffer,
    true, // normalize audio
    0,    // channel offset
    { title: parsedMid.midiName }, // add some metadata
    { start: loopStart, end: loopEnd}
);

// save the file
const a = document.createElement("a");
a.href = URL.createObjectURL(wav);
a.download = parsedMid.midiName + ".wav";
a.click();

For a real use-case, see src/website/manager/export_audio.js.