Skip to content

Instantly share code, notes, and snippets.

@holly-hacker
Last active November 1, 2025 16:28
Show Gist options
  • Save holly-hacker/a427029875a1fd5eadad49f5da9f1344 to your computer and use it in GitHub Desktop.
Save holly-hacker/a427029875a1fd5eadad49f5da9f1344 to your computer and use it in GitHub Desktop.
#pragma author Variant9
#pragma description Parses SoundFont2 (v2.04) files
#pragma magic [52 49 46 46 ?? ?? ?? ?? 73 66 62 6B]
#pragma endian little
/*!
Describes the SF2 soundfont format.
For more info on SF2:
- Specification: https://www.synthfont.com/sfspec24.pdf
- alt: https://www.polyphone.io/doc/files/sf_specifications_v2.04.pdf
- RIFF structure: https://mrtenz.github.io/soundfont2/getting-started/soundfont2-structure.html
- Overview/comparison with other formats: https://www.polyphone.io/en/documentation/manual/annexes/the-different-soundfont-formats
*/
import std.io;
import std.mem;
import std.string;
import std.sys;
struct RiffHeader {
char magic[4];
u32 size [[hidden]];
char format[4];
};
struct RiffChunkBase {
char type[4] [[hidden]];
u32 size [[hidden]];
};
fn format_riff_chunk_base(ref RiffChunkBase item) {
return item.type;
};
/// A version struct used by the ifil and iver subchunks.
struct VersionTag {
s16 major;
s16 minor;
} [[format("format_version_tag")]];
fn format_version_tag(ref VersionTag version) {
return std::format("{}.{}", version.major, version.minor);
};
/// The INFO chunk
struct InfoChunk : RiffChunkBase {
if (type == "ifil") {
VersionTag version [[comment("The SoundFont specification version.")]];
padding[4-size] [[hidden]];
} else if (type == "isng") {
std::string::NullString soundEngine [[comment("The sound engine for which the SoundFont was optimized.")]];
padding[size - sizeof(soundEngine)];
} else if (type == "INAM") {
std::string::NullString name [[comment("The name of the SoundFont.")]];
padding[size - sizeof(name)];
} else if (type == "irom") {
std::string::NullString rom [[comment("A sound data ROM to which any ROM samples refer.")]];
padding[size - sizeof(rom)];
} else if (type == "iver") {
VersionTag romVersion [[comment("A sound data ROM revision to which any ROM samples refer.")]];
padding[size-4] [[hidden]];
} else if (type == "ICRD") {
std::string::NullString creationDate [[comment("The creation date of the SoundFont, conventionally in the 'Month Day, Year' format.")]];
padding[size - sizeof(creationDate)];
} else if (type == "IENG") {
std::string::NullString author [[comment("The author or authors of the SoundFont.")]];
padding[size - sizeof(author)];
} else if (type == "IPDR") {
std::string::NullString product [[comment("The product for which the SoundFont is intended.")]];
padding[size - sizeof(product)];
} else if (type == "ICOP") {
std::string::NullString copyright [[comment("Copyright assertion string associated with the SoundFont.")]];
padding[size - sizeof(copyright)];
} else if (type == "ICMT") {
std::string::NullString comments [[comment("Any comments associated with the SoundFont.")]];
padding[size - sizeof(comments)];
} else if (type == "ISFT") {
std::string::NullString createdBy [[comment("The tool used to create the SoundFont.")]];
padding[size - sizeof(createdBy)];
} else {
u8 unknown_subchunk[size];
}
} [[format("format_riff_chunk_base")]];
/// The sdta chunk
struct SampleDataChunk : RiffChunkBase {
if (type == "smpl") {
u16 sampleData[size/2] [[comment("The 16-bit WAV sample data.")]];
if (size % 2 == 1) {
padding[1]; // untested, shouldn't be needed?
}
} else if (type == "sm24") {
u8 sampleData8[size] [[comment("The 8-bit WAV sample data to combine with the 16-bit data to get 24-bit sample data.")]];
} else {
u8 unknown_subchunk[size];
}
} [[format("format_riff_chunk_base")]];
struct RangesType {
u8 lo;
u8 hi;
} [[format("format_ranges_type")]];
fn format_ranges_type(ref RangesType ranges_type) {
return std::format("{} - {}", ranges_type.lo, ranges_type.hi);
};
union GenAmountType {
RangesType ranges;
s16 amountSigned; // shAmount
u16 amount; // wAmount
};
enum SampleLink : u16
{
monoSample = 1,
rightSample = 2,
leftSample = 4,
linkedSample = 8,
RomMonoSample = 0x8001,
RomRightSample = 0x8002,
RomLeftSample = 0x8004,
RomLinkedSample = 0x8008
};
bitfield Modulator {
type: 6;
polarity: 1;
direction: 1;
continuousController: 1;
index: 7;
};
enum Generator : u16 {
startAddrsOffset = 0,
endAddrsOffset = 1,
startloopAddrsOffset = 2,
endloopAddrsOffset = 3,
startAddrsCoarseOffset = 4,
modLfoToPitch = 5,
vibLfoToPitch = 6,
modEnvToPitch = 7,
initialFilterFc = 8,
initialFilterQ = 9,
modLfoToFilterFc = 10,
modEnvToFilterFc = 11,
endAddrsCoarseOffset = 12,
modLfoToVolume = 13,
unused1 = 14,
chorusEffectsSend = 15,
reverbEffectsSend = 16,
pan = 17,
unused2 = 18,
unused3 = 19,
unused4 = 20,
delayModLFO = 21,
freqModLFO = 22,
delayVibLFO = 23,
freqVibLFO = 24,
delayModEnv = 25,
attackModEnv = 26,
holdModEnv = 27,
decayModEnv = 28,
sustainModEnv = 29,
releaseModEnv = 30,
keynumToModEnvHold = 31,
keynumToModEnvDecay = 32,
delayVolEnv = 33,
attackVolEnv = 34,
holdVolEnv = 35,
decayVolEnv = 36,
sustainVolEnv = 37,
releaseVolEnv = 38,
keynumToVolEnvHold = 39,
keynumToVolEnvDecay = 40,
instrument = 41,
reserved1 = 42,
keyRange = 43,
velRange = 44,
startloopAddrsCoarseOffset = 45,
keynum = 46,
velocity = 47,
initialAttenuation = 48,
reserved2 = 49,
endloopAddrsCoarseOffset = 50,
coarseTune = 51,
fineTune = 52,
sampleID = 53,
sampleModes = 54,
reserved3 = 55,
scaleTuning = 56,
exclusiveClass = 57,
overridingRootKey = 58,
unused5 = 59,
endOper = 60,
};
enum Transform : u16 {
Linear = 0,
AbsoluteValue = 2,
};
/// The phdr subchunk
struct PresetHeader {
std::string::NullString presetName;
padding[20 - sizeof(presetName)];
u16 preset;
u16 bank;
u16 presetBagNdx;
u32 library;
u32 genre;
u32 morphology;
} [[format("format_preset_header")]];
fn format_preset_header(ref PresetHeader header) {
return header.presetName;
};
/// The pbag subchunk
struct PresetBag {
u16 genIndex;
u16 modIndex;
} [[format("format_preset_bag")]];
fn format_preset_bag(ref PresetBag header) {
return std::format("{}, {}", header.genIndex, header.modIndex);
};
/// The pmod subchunk. Documented to contain 10 zero-valued bytes in SoundFont 2.00
struct PresetModList {
Modulator modSrcOper;
Generator modDstOper;
s16 modAmount;
Modulator modAmtSrcOper;
Transform modTransOper;
};
/// The pgen subchunk
struct PresetGenList {
Generator genOper;
GenAmountType genAmount;
};
/// The inst subchunk
struct Instrument {
std::string::NullString instrumentName;
padding[20 - sizeof(instrumentName)];
u16 instBagIndex;
} [[format("format_instrument")]];
fn format_instrument(ref Instrument header) {
return header.instrumentName;
};
/// The ibag subchunk
struct InstrumentBag {
u16 instGenIndex;
u16 instModIndex;
} [[format("format_instrument_bag")]];
fn format_instrument_bag(ref InstrumentBag header) {
return std::format("{}, {}", header.instGenIndex, header.instModIndex);
};
/// The imod subchunk
struct InstrumentModList {
Modulator modSrcOper;
Generator modDstOper;
s16 modAmount;
Modulator modAmtSrcOper;
Transform modTransOper;
};
/// The igen subchunk
struct InstrumentGenList {
Generator genOper;
GenAmountType genAmount;
};
/// The shdr subchunk
struct SampleHeader {
std::string::NullString sampleName;
padding[20 - sizeof(sampleName)];
u32 start;
u32 end;
u32 startloop;
u32 endloop;
u32 sampleRate;
u8 originalKey;
s8 correction;
u16 sampleLink;
SampleLink sampleType;
} [[format("format_sample_header")]];
fn format_sample_header(ref SampleHeader header) {
return header.sampleName;
};
/// The pdta chunk
struct PresetDataChunk : RiffChunkBase {
if (type == "phdr") {
PresetHeader presetHeaders[size/sizeof(PresetHeader)] [[comment("The preset headers.")]];
} else if (type == "pbag") {
PresetBag presetZones[size/sizeof(PresetBag)] [[comment("The preset zone indices.")]];
} else if (type == "pmod") {
PresetModList presetModulators[size/sizeof(PresetModList)] [[comment("The preset modulators.")]];
} else if (type == "pgen") {
PresetGenList presetGenerators[size/sizeof(PresetGenList)] [[comment("The preset generators.")]];
} else if (type == "inst") {
Instrument instrumentHeaders[size/sizeof(Instrument)] [[comment("The instrument headers.")]];
} else if (type == "ibag") {
InstrumentBag instrumentZones[size/sizeof(InstrumentBag)] [[comment("The instrument zone indices.")]];
} else if (type == "imod") {
InstrumentModList instrumentModulators[size/sizeof(InstrumentModList)] [[comment("The instrument modulators.")]];
} else if (type == "igen") {
InstrumentGenList instrumentGenerators[size/sizeof(InstrumentGenList)] [[comment("The instrument generators.")]];
} else if (type == "shdr") {
SampleHeader sampleHeaders[size/sizeof(SampleHeader)] [[comment("The sample headers.")]];
} else {
u8 unknown_subchunk[size];
}
} [[format("format_riff_chunk_base")]];
u64 listEnd;
/// The LIST chunk
struct ListChunk {
char type[4] [[hidden]];
if (type == "INFO") {
InfoChunk infoChunk[while ($ < listEnd)] [[inline]];
} else if (type == "sdta") {
SampleDataChunk sampleDataChunk[while ($ < listEnd)] [[inline]];
} else if (type == "pdta") {
PresetDataChunk presetDataChunk[while ($ < listEnd)] [[inline]];
} else {
char unknownData[while ($ < listEnd)];
}
} [[format("format_list_chunk")]];
fn format_list_chunk(ref ListChunk chunk) {
return std::format("LIST chunk - {}", chunk.type);
};
/// A generic, top-level RIFF chunk
struct RiffChunk {
char type[4] [[hidden]];
u32 size [[hidden]];
if (type == "LIST") {
listEnd = $ + size;
ListChunk listChunk [[inline]];
} else {
u8 data[size];
}
} [[format("format_chunk")]];
fn format_chunk(ref RiffChunk chunk) {
if (chunk.type == "LIST") {
return format_list_chunk(chunk.listChunk);
} else {
return "Unknown chunk type";
}
};
RiffHeader header @ 0x00;
RiffChunk chunks[while (!std::mem::eof())] @ $;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment