ACIDCAT . FILE FORMAT REFERENCE

SoundFont 2 / 3 Anatomy

sfbk RIFF.sf2 . .sf3
rev 2026.07
magic RIFF .. sfbk
endian little
samples SF2 16-bit PCM
samples SF3 Ogg Vorbis

A SoundFont is a RIFF file of form sfbk: three LIST chunks, in order. INFO holds the metadata (name, author, version). sdta holds one smpl chunk, every sample's audio back to back. pdta holds the preset/instrument/sample structure, ending in shdr, a table of 46-byte sample headers that name each sample and give its start and end index into smpl. Extracting a sample is a carve of that byte range. SF3 (MuseScore) keeps this exact layout but stores each sample as an Ogg-Vorbis stream inside smpl, marked by sample-type bit 0x10, with the shdr start/end fields repurposed as byte offsets. The record drawn below is a real shdr entry, sample "tu 13" from a library SF2. Hover any field to light its bytes and read the decode; click a field with a + to open its table. Color marks kind (see the key).

file regions

Top to bottom, what a reader walks. It is a plain RIFF tree; nothing is compressed at the container level (SF2), so the whole structure is reachable by chunk-hopping.

▸RIFF / sfbk0x00 . container

The outer wrapper: 'RIFF', a u32 little-endian size (file length minus 8), then the form type 'sfbk'. Everything else is a child chunk of this container.

magic"RIFF" at 0x00, "sfbk" at 0x08
sizeu32 LE at 0x04 = file length - 8
childrenLIST INFO, LIST sdta, LIST pdta -- in that order
the container header (12 bytes)

RIFF, so little-endian throughout -- the opposite of its IFF ancestor.

▸LIST INFOmetadata

The catalog fields, one sub-chunk each. ifil is the SoundFont spec version (2.x for SF2, 3.x for SF3). The rest are free text.

ifilu16 major . u16 minor (2.1 = SF2, 3.x = SF3)
INAMsoundfont name
isng / ISFTtarget sound engine . the editor that wrote it (a provenance tell)
IENG / ICOP / ICMTauthor . copyright . comment
ifil, the version word (12 bytes)

Two numbers that decide whether the samples are PCM or Vorbis.

a text chunk (24 bytes)

Every INFO string is NUL-terminated and padded to an even length.

▸LIST sdta / smplthe audio

One smpl chunk holding every sample concatenated. In SF2 this is 16-bit signed PCM, little-endian; a sample is the slice smpl[start*2 : end*2]. In SF3 it is a run of Ogg-Vorbis streams; a sample is smpl[start : end], a self-contained .ogg.

SF216-bit PCM; sample bytes = smpl_offset + start*2 .. end*2
sm24 (24-bit)a 24-bit SF2 keeps the top 16 bits in smpl and the low byte in a parallel sm24 chunk; a smpl-only read is a 16-bit downgrade
SF3Ogg-Vorbis streams; sample bytes = smpl_offset + start .. end (a playable .ogg)
extractioneach sample has a real byte offset, so a single sample can be pulled from the blob without decoding the bank
paddingthe spec writes ≥46 zero sample points after each sample; a carve of start..end is clean, the gap between is mandated silence
▸LIST pdta / phdr38-byte records . drawn below

The presets: what a MIDI program change actually selects. Each record is 38 bytes -- a 20-byte name, the program and bank it answers to, an index into pbag, and three fields the spec reserves and every writer leaves zero. A terminal record named EOP ends the table, and it exists to close the last real preset's zone span rather than to describe a preset.

Bank 128 is percussion. There the program number selects a drum kit rather than an instrument, so the same two bytes mean different things depending on the bank beside them.

▸LIST pdta / inst22-byte records . drawn below

The instruments, which are what presets actually play. Twenty bytes of name and one index into ibag -- the shortest record in the format, because an instrument is almost entirely defined by the zones hanging off it. A terminal EOI record closes the last one.

▸LIST pdta / igen4-byte records . drawn below

Generators are how a zone says anything at all. Each is four bytes: a 16-bit operator naming the parameter, and a 16-bit amount whose meaning depends entirely on that operator. Two of them decide what a zone plays -- 43 is the key range and 53 is the sample index -- and by the spec's rule a zone's sampleID must be its LAST generator.

The key range packs two values into one 16-bit amount: low key in the low byte, high key in the high byte. Read it as a single number and a zone covering notes 0 to 60 becomes a zone at key 15,360, which is not a key.

▸LIST pdta / shdr46-byte records . drawn below

The sample headers. Each record is exactly 46 bytes: a 20-byte name, five u32 indices into smpl (start, end, loop start, loop end, sample rate), then the original pitch, a correction, a stereo link, and a type. A terminal record named "EOS" marks the end of the table. The record below is the real first entry of a library SF2.

layoutname[20] . start/end/loop_start/loop_end/rate (5 x u32) . pitch(u8) . correction(s8) . link(u16) . type(u16)
SF3 twiststart/end become byte offsets, and type has bit 0x10 set to mark an Ogg sample
terminalseach pdta list ends in a sentinel record -- phdr "EOP", inst "EOI", shdr "EOS" -- structural, not real content
anti-garbagekeep only in-range headers (end within smpl), so a lied index cannot reach past the sample blob
what the header yields

The INFO metadata, the smpl blob size, and every shdr record -- each turned into a chunk carrying its real byte offset, so samples are carveable and hex-viewable, and convert lifts them all out. Above those, the preset and instrument tables resolve into the tree they describe: which bank and program each preset answers to, the instruments its zones reach, and the key range each instrument zone maps to a sample. Those are rows and cross-references rather than byte ranges, so they carry no offset.

▸reading a banksoundfont
readversion, INFO metadata, sample count, and each sample's rate / duration / loop
byte offseteach sample chunk carries its real range in smpl (start*2 for SF2, start for SF3)
convertan .sf2 yields PCM WAVs; an .sf3 yields the .ogg streams verbatim
SF3 noteSF3 samples are whole Ogg Vorbis streams, so turning one into PCM needs a Vorbis decoder
provenancean SF3 fingerprints as MuseScore sf3convert (Ogg samples + omitted RIFF padding) even when the ISFT string names the original editor