Skip to content

SoundFont2 Class

spessasus edited this page Aug 18, 2024 · 14 revisions

SoundFont2 Class

This module handles parsing and writing SoundFont2 (.sf2 and .sf3) files.

Tip

If you encounter any errors in this documentation, please open an issue!

SoundFont2 Specification

Table of Contents

Importing

// normal install
import { SoundFont2 } from "./spessasynth_lib/soundfont/soundfont.js";
// npm package
import { SoundFont2 } from "spessasynth_lib";

Tip

Using the npm package? Make sure you've read this

Initialization

const soundFont = new SoundFont2(arrayBuffer);
  • arrayBuffer - An ArrayBuffer representing the binary file data.

Methods

getPreset

Returns the matching Preset class instance.

const preset = soundFont.getPreset(bankNr, presetNr);
  • bankNr - MIDI bank number, typically set with the Bank Select controller.
  • presetNr - MIDI program number, usually set with the Program Change message.

If the matching preset is not found, the first preset will be returned. If the requested bank is 128, the first preset with bank 128 will be returned (drums).

getPresetByName

Returns the matching Preset class instance.

const preset = soundFont.getPresetByName(presetName);
  • presetName - The name of the preset as a string. If not found, the first preset will be returned.

write

Writes out an SF2 or SF3 file. The return value is an Uint8Array - the binary of the file.

const binary = soundFont.write(options);
  • options - An optional object:
    • compress - A boolean indicating if uncompressed samples should be compressed using the lossy Ogg Vorbis codec. This significantly reduces file size.
    • compressionQuality - A number from -0.1 to 1, indicating compression quality. 1 is the best, -0.1 is the worst.
    • compressionFunction - See this for a detailed explanation

Important

If the SoundFont was already compressed, it will not be decompressed to avoid losing quality.

Warning

This method is memory and CPU intensive with large SoundFonts, especially if compression is enabled.

mergeSoundfonts

Merges multiple SoundFonts, adding (not replacing) presets on top of the previous ones, and returns a new SoundFont.

SoundFont2.mergeSoundfonts(soundfont1, soundfont2, soundfont3, /* more here... */);
  • soundfonts - SoundFont2 instances, with any number of inputs. The first is used as a base, and the rest are added on top.

The return value is a new SoundFont2.

Note

This method is static.

Properties

presets

An array of all presets in the SoundFont, ordered by bank and preset number.

console.log(soundFont.presets);

soundFontInfo

Represents the SoundFont2's INFO chunk data. Stored as an object like this:

const infoData = {
    chunk: /* the read's 4-letter code, e.g. */ "INAM",
    infoText: /* the read's data as text, e.g. */ "My cool SoundFont"
}

Check out this website for more information.

Important

ifil and iver are stored as strings like this: major.minor. For example major 2 minor 1 will be 2.1

Writing a Trimmed SoundFont

trimSoundfont

Trims the SoundFont in place to only include samples used in the MIDI sequence, down to the exact key-velocity combinations.

import { trimSoundfont } from './spessasynth_lib/soundfont/write/soundfont_trimmer.js'
trimSoundfont(soundfont, midi);
  • soundfont - SoundFont2 - The SoundFont to trim.
  • midi - MIDI - The MIDI file for which to trim the SoundFont.

SoundFont Internal Structure

The following describes the internal structure of a SoundFont and includes some methods not mentioned above. Useful for editing the SoundFont.

Legend:

  • Methods: methodName (argumentName: type) -> description
  • Properties: propertyName (type) -> description

Warning

Internal values not described here, such as modulatorZoneSize, should not be tampered with.

SoundFont2

  • soundFontInfo (described above)
  • deletePreset (preset: Preset) -> Deletes a given preset.
  • deleteInstrument (instrument: Instrument) -> Deletes a given instrument. Cannot delete if it's used.
  • deleteSample (sample: Sample) -> Deletes a given sample. Cannot delete if it's used.
  • instruments (Instrument[]) -> All instruments.
  • samples (Sample[]) -> All samples.
  • presets (Preset[]) -> All presets. Note: Some methods are omitted. Click the link for the full description of the Preset class.
    • presetName (string) -> The name of the preset.
    • program (number) -> The preset's MIDI program.
    • bank (number) -> The preset's MIDI bank.
    • library (number) -> Generally unused but preserved.
    • genre (number) -> Generally unused but preserved.
    • morphology (number) -> Generally unused but preserved.
    • preload (keyMin: number, keyMax: number) -> Preloads all samples for the given range.
    • deleteZone (index: number) -> Deletes a given preset zone and instrument if not used by anything else.
    • presetZones (PresetZone[]) -> All zones of the preset.
      • keyRange ({min: number, max: number}) -> Key range of the zone.
      • velRange ({min: number, max: number}) -> Velocity range of the zone.
      • generators (Generator[]) -> Generators of the zone.
      • modulators (Modulator[]) -> Modulators of the zone.
      • isGlobal (boolean) -> If true, instrument is undefined.
      • instrument (Instrument) -> The zone's instrument. Undefined if global.
        • instrumentName (string) -> The name of the instrument.
        • safeDeleteZone (index: number) -> deleteZone but only if the instrument's use count is 0. Useful to ensure the instrument is not deleted when used by other presets.
        • deleteZone (index: number) -> Deletes a given preset zone and instrument if not used by anything else.
        • instrumentZones (InstrumentZone[]) -> All zones of the instrument.
          • keyRange ({min: number, max: number}) -> Key range of the zone.
          • velRange ({min: number, max: number}) -> Velocity range of the zone.
          • generators (Generator[]) -> Generators of the zone.
          • modulators (Modulator[]) -> Modulators of the zone.
          • isGlobal (boolean) -> If true, sample is undefined.
          • sample (Sample) -> The sample of the zone. Undefined if global.

compressionFunction

Import the function like this:

import { encodeVorbis } from './spessasynth_lib/utils/encode_vorbis.js';

Then pass it to the write method:

const file = soundFont.write({
  compress: true,
  compressionQuality: 0.5,
  compressionFunction: encodeVorbis
});

Why not import it in the code?

Importing it directly into the code would load the entire 1.1MB encoder file into the synthesizer’s thread (AudioWorkletProcessor), which would be unnecessary if the functionality is not used. This approach ensures that the file is only loaded when needed. If your program does not use this functionality, the file will not be loaded by the browser, saving the precious megabytes of data.