You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言解析SF2:如何正确映射预置头与关联乐器及样本?

SF2文件预置与样本关联的核心逻辑及C语言实现示例

核心关联链梳理

SF2的预置到样本映射是多层索引链式关联,核心路径如下:
Preset (phdr) → Preset Bag (pbag) → Preset Generator (pgen) → Instrument (inst) → Instrument Bag (ibag) → Instrument Generator (igen) → Sample Header (shdr)

每个环节的索引都是起始索引,需要连续遍历直到下一个条目起始索引或数组末尾,不能仅取单个索引。

分步索引追踪实现

1. 从PHDR到PBAG

每个PHDR结构体的wPresetBagNdx字段,代表该预置对应的第一个PBAG索引。遍历PBAG数组时,当前预置的所有PBAG范围是:从wPresetBagNdx开始,到下一个PHDR的wPresetBagNdx(或PBAG数组末尾)结束。

2. PBAG到PGEN,定位Instrument索引

每个PBAG的wGenNdx是该Bag对应的第一个PGEN起始索引。遍历PGEN时,找到类型为GEN_INSTRUMENT(值为0x0008)的生成器,其wAmount字段就是对应的INST索引。

3. INST到IBAG

逻辑与PHDR到PBAG一致:INST的wInstBagNdx是第一个IBAG的起始索引,遍历IBAG直到下一个INST的wInstBagNdx(或IBAG数组末尾)。

4. IBAG到IGEN,定位Sample索引

每个IBAG的wGenNdx是第一个IGEN的起始索引。遍历IGEN时,找到类型为GEN_SAMPLE_ID(值为0x0019)的生成器,其wAmount字段就是对应的SHDR索引,由此即可关联到样本数据。

C语言代码示例

首先定义符合SF2规范的结构体(注意SF2为小端字节序,读取时需做字节序转换):

#include <stdint.h>
#include <stdio.h>

// SF2核心结构体简化定义
typedef struct {
    char achPresetName[20];
    uint16_t wPreset;
    uint16_t wBank;
    uint16_t wPresetBagNdx;
    uint32_t dwLibrary;
    uint32_t dwGenre;
    uint32_t dwMorphology;
} SF2_PHDR;

typedef struct {
    uint16_t wGenNdx;
    uint16_t wModNdx;
} SF2_PBAG;

typedef struct {
    uint16_t wOper;
    uint16_t wAmount;
} SF2_PGEN;

typedef struct {
    char achInstName[20];
    uint16_t wInstBagNdx;
} SF2_INST;

typedef struct {
    uint16_t wGenNdx;
    uint16_t wModNdx;
} SF2_IBAG;

typedef struct {
    uint16_t wOper;
    uint16_t wAmount;
} SF2_IGEN;

typedef struct {
    char achSampleName[20];
    uint32_t dwStart;
    uint32_t dwEnd;
    uint32_t dwStartLoop;
    uint32_t dwEndLoop;
    uint32_t dwSampleRate;
    uint8_t byOriginalPitch;
    int8_t chPitchCorrection;
    uint16_t wSampleLink;
    uint16_t sfSampleType;
} SF2_SHDR;

// 小端转主机字节序
uint16_t le16toh(uint16_t val) {
#ifdef __BYTE_ORDER__
#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
    return ((val >> 8) & 0xFF) | ((val << 8) & 0xFF00);
#else
    return val;
#endif
#else
    return val;
#endif
}

然后实现预置到样本的关联函数:

// 输入:预置索引、各区块数据;输出:关联的样本信息
void map_preset_to_samples(int preset_idx, SF2_PHDR* phdrs, int phdr_count,
                           SF2_PBAG* pbags, int pbag_count,
                           SF2_PGEN* pgens, int pgen_count,
                           SF2_INST* insts, int inst_count,
                           SF2_IBAG* ibags, int ibag_count,
                           SF2_IGEN* igens, int igen_count,
                           SF2_SHDR* shdrs, int shdr_count) {
    SF2_PHDR* preset = &phdrs[preset_idx];
    int start_pbag = le16toh(preset->wPresetBagNdx);
    int end_pbag = (preset_idx + 1 < phdr_count) ? le16toh(phdrs[preset_idx+1].wPresetBagNdx) : pbag_count;

    printf("预置: %s\n", preset->achPresetName);

    // 遍历当前预置的所有PBAG
    for (int i = start_pbag; i < end_pbag; i++) {
        SF2_PBAG* pbag = &pbags[i];
        int start_pgen = le16toh(pbag->wGenNdx);
        int end_pgen = (i + 1 < pbag_count) ? le16toh(pbags[i+1].wGenNdx) : pgen_count;

        // 查找Instrument生成器
        int inst_idx = -1;
        for (int j = start_pgen; j < end_pgen; j++) {
            SF2_PGEN* pgen = &pgens[j];
            uint16_t oper = le16toh(pgen->wOper);
            if (oper == 0x0008) { // GEN_INSTRUMENT
                inst_idx = le16toh(pgen->wAmount);
                break;
            }
        }

        if (inst_idx == -1 || inst_idx >= inst_count) continue;
        SF2_INST* inst = &insts[inst_idx];
        printf("  关联乐器: %s\n", inst->achInstName);

        // 遍历乐器的IBAG
        int start_ibag = le16toh(inst->wInstBagNdx);
        int end_ibag = (inst_idx + 1 < inst_count) ? le16toh(insts[inst_idx+1].wInstBagNdx) : ibag_count;

        for (int k = start_ibag; k < end_ibag; k++) {
            SF2_IBAG* ibag = &ibags[k];
            int start_igen = le16toh(ibag->wGenNdx);
            int end_igen = (k + 1 < ibag_count) ? le16toh(ibags[k+1].wGenNdx) : igen_count;

            // 查找Sample ID生成器
            int shdr_idx = -1;
            for (int l = start_igen; l < end_igen; l++) {
                SF2_IGEN* igen = &igens[l];
                uint16_t oper = le16toh(igen->wOper);
                if (oper == 0x0019) { // GEN_SAMPLE_ID
                    shdr_idx = le16toh(igen->wAmount);
                    break;
                }
            }

            if (shdr_idx == -1 || shdr_idx >= shdr_count) continue;
            SF2_SHDR* shdr = &shdrs[shdr_idx];
            printf("    关联样本: %s\n", shdr->achSampleName);
        }
    }
}

常见问题排查

  1. 字节序错误:SF2所有多字节字段都是小端,若主机为大端(如部分嵌入式设备)必须转换字节序,否则索引会完全混乱。
  2. 索引范围处理错误:每个Bag对应的Generator是连续区间,不能仅取第一个Generator,必须遍历到下一个Bag的Generator起始索引。
  3. 生成器类型值错误:确认GEN_INSTRUMENT(0x0008)和GEN_SAMPLE_ID(0x0019)的数值,主流SF2文件均遵循该规范。
  4. 忽略多Bag情况:一个预置/乐器可能对应多个Bag(如不同音区对应不同样本),必须遍历所有关联Bag,不能仅处理第一个。

内容的提问来源于stack exchange,提问作者Elric Lescop

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 22:17:04