Last active
November 1, 2025 16:28
-
-
Save holly-hacker/a427029875a1fd5eadad49f5da9f1344 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #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