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
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
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)
acidcateach sample gets its real byte offset, so carve --offset extracts it and the hex pane shows it
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 / 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-garbageacidcat keeps only in-range headers (end within smpl), so a lied index cannot carve past the blob
what acidcat reads

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.

inspect + convertsoundfont
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)
convertacidcat convert font.sf2 -> a folder of WAVs; font.sf3 -> a folder of .ogg streams
SF3 noteVorbis-to-PCM decode needs a codec acidcat does not bundle, so SF3 extracts the Ogg verbatim
provenancean SF3 fingerprints as MuseScore sf3convert (Ogg samples + omitted RIFF padding) even when the ISFT string names the original editor