Yamaha S-YXG 50 逆向记录

感谢 @gaozhe3321 带来新的逆向进展。

这种文件有反编译过的样本么,或者说具体结构可以形容下

闲话本人就折叠起来了,想看就展开吧

提到这个其实联想到的是Motif的乐器预设或波表(如wXa,wXv;只是举个例子实际上有更多变种)

https://archive.org/details/motif-es-sound-library
以上这张CD是购买Motif附赠的音色库,如果跑题的话可能就没什么研究价值了

雅马哈的其他私有格式Fmj_Awave说是整理吧(但它软件能读数据,所以基本是转了)

就看看得了


但也并非一无是处,至少得知了雅马哈习惯性的把乐器预设(voice)和样本(waveformat)分开放这个事实(但似乎也早说过了?)

https://www.fmjsoft.com/awavestudio.html#formats
(看一眼这几个格式的介绍也无妨?)

2605082330

你太牛了!

事实上 2006LE 也采取了跟 YXG-50 相似的索引+波表的方式

我事实上还发现,其实很多音色是共用了同一个采样,但是在调制方式上有区别。也就是说我们目前只是知道了这些数据是如何排布的,但是更复杂的调制算法以及效果器/SysEx这些功能是如何实现的,还有待进一步分析。

整个程序的结构也需要进一步去分析,但这可能需要动态调试了,我能力和时间都有限,只能暂时搁置了

1 Like

我找了一个帮手,用AI来尝试还原代码,(烧token的日子开始了……)

1 Like

大佬你也用 OpenCode? :crazy_face:

也许可以试试本地部署大模型(如果可能的话,虽然不太可能)

也许可以试试本地部署大模型(如果可能的话,虽然不太可能)

我没有很好的显卡……也没有¥

烧了一晚上,大概¥15,进度如上图

1 Like

今天的逆向进度

1 Like

花钱充了小米的 mimo token plan
现在烧不完,我打算把 2006le 也逆向掉

另外,如果有想使用mimo的,我这里有邀请码

1 Like

S-YXG2006LE 数据文件结构报告

该文档由AI生成,可能存在不准确的部分

1. 概述

S-YXG2006LE 合成器使用两个数据文件:

文件 大小 加密 用途
sxgbnw6l.tbl 716,608 字节 否 (明文) 参数表:音色定义、波形偏移、力度曲线
sxgdat6l.tbl 11,723,868 字节 是 (Stage 1) 波形数据:16-bit PCM 采样

2. sxgbnw6l.tbl 参数表结构

2.1 文件头部 (0x00-0x3F, 64字节)

偏移    大小    值              说明
0x00    28B     "UTG VPRM 06 07 28 15 28\0"   魔数 + 版本号
0x1C    uint32  0x00004040      Section A 偏移 (Voice 索引表)
0x20    uint32  0x00009740      Section B 偏移 (波形偏移表)
0x24    uint32  0x00032EE8      Section C 偏移 (音色参数数据)
0x28    uint32  0x0007B88C      Section D 偏移 (力度/音量曲线)
0x2C    uint32  0x0007BA8C      Section E 偏移 (鼓组/音符参数)
0x30    uint32  0x0009CE8C      Section F 偏移 (每音符 Voice 映射)
0x34    uint32  0xFFFFFFFF      保留
0x38    uint32  0x000A69F0      Section G 偏移 (鼓键参数)
0x3C    uint32  0xFFFFFFFF      保留

2.2 段布局总览

偏移 大小 用途
Header 0x000000-0x00003F 64 B 文件头
Lookup Table 0x000040-0x00013F 256 B MIDI Program → Voice 索引映射
Sparse Region 0x000140-0x00403F 16 KB 填充 (0xFF)
Section A 0x004040-0x00973F 22 KB Voice 索引表
Section B 0x009740-0x032EE7 170 KB 波形偏移表 (42,474 个 uint32)
Section C 0x032EE8-0x07B88B 297 KB 音色参数数据 (6,048 条记录)
Section D 0x07B88C-0x07BA8B 512 B 力度曲线 (4×128 字节)
Section E 0x07BA8C-0x09CE8B 136 KB 鼓组/音符参数
Section F 0x09CE8C-0x0A69EF 40 KB 每音符 Voice 映射
Section G 0x0A69F0-0x0AEF3F 34 KB 鼓键参数 (1,422×24 字节)

3. 各段详细结构

3.1 Lookup Table (0x40-0x13F)

128 字节,每个字节对应一个 MIDI Program (0-127),值为内部 Voice 索引。

  • 0xFF = 未映射
  • 0x00-0x3D = 有效 Voice 索引

已映射的 MIDI Programs (59个):

0, 1, 3, 6, 8, 12, 14, 16-20, 24-25, 27-28, 32-38, 40-43, 45,
64-72, 96-101, 112-127

映射示例:

MIDI Program Voice Index 说明
0 0 Acoustic Grand Piano
1 1 Bright Acoustic Piano
24 12 Acoustic Guitar (nylon)
40 23 Violin

3.2 Section B: 波形偏移表 (0x9740)

42,474 个 uint32 值,指向 sxgdat6l.tbl 中的波形数据。

结构:

偏移 = Section B 起始 + 索引 * 4
波形大小 = 偏移[索引+1] - 偏移[索引]

统计:

  • 有效偏移 (< 文件大小): 21,057 个
  • 无效偏移 (0xFFFFFFFF): 9,512 个
  • 波形大小范围: 44-440 字节 (22-220 采样)

波形示例:

索引 偏移 大小 采样数
0 0x00A308 264 132
1 0x00A410 152 76
2 0x00A4A8 408 204

3.3 Section C: 音色参数 (0x32EE8)

Section C 开头是偏移表,每个条目指向一个音色定义记录。

音色定义记录大小:

大小 元素数 说明
28 字节 1 单元素音色
60 字节 2 双元素音色
108 字节 4 四元素音色
140 字节 5 五元素音色
176 字节 6 六元素音色

VoiceElement 结构 (28字节):

偏移    大小    说明
0x00    4B      标志 (通常为 0)
0x04    1B      音高范围低 (通常 0)
0x05    1B      音高范围高 (通常 127)
0x06    1B      波形索引 (指向 Section B)
0x07    1B      元素标志
0x08    1B      力度范围低 (通常 0)
0x09    1B      力度范围高 (通常 127)
0x0A    1B      粗调 (有符号)
0x0B    1B      微调 (有符号)
0x0C    1B      电平 (128 = 最大)
0x0D    3B      保留
0x10    1B      声像 (128 = 中央)
0x11    3B      保留
0x14    1B      滤波器截止频率 (128 = 最大)
0x15    3B      保留
0x18    1B      滤波器共振 (128 = 最大)
0x19    3B      保留

音色参数示例 (Program 0, Piano):

  • Voice Index: 0
  • 记录大小: 176 字节 (6 元素)
  • 元素波形索引: [132, 127, 1, 0, 255, 37]

3.4 Section D: 力度曲线 (0x7B88C)

4 条曲线,每条 128 字节,将 MIDI 力度 (0-127) 映射到输出值 (0-127)。

曲线 vel=0 vel=32 vel=64 vel=96 vel=127 特点
0 0 32 72 114 126 标准
1 0 26 60 98 126 柔和
2 0 26 64 104 126 中等
3 0 32 73 115 127 线性

3.5 Section G: 鼓键参数 (0xA69F0)

1,422 个记录,每个 24 字节。

默认记录:

40 40 7F 00 40 00 00 7F 00 01 01 40 40 40 40 40 40 40 0C 36 00 40 40 40

字段解释:

偏移 大小 默认值 说明
0x00 1B 0x40 (64) 电平
0x01 1B 0x40 (64) 声像
0x02 1B 0x7F (127) 编组
0x03 1B 0x00 标志
0x04 1B 0x40 (64) 音调
0x07 1B 0x7F (127) 衰减
0x08 1B 0x00 混响发送
0x09 1B 0x01 合唱发送
0x0B 1B 0x40 (64) 滤波器截止

4. sxgdat6l.tbl 波形数据

4.1 加密方式

波形数据使用 Stage 1 (nibble-swap XOR) 加密。

解密算法 (地址 0x1004c900):

void decrypt(uint8_t* data, uint32_t size) {
    uint8_t key = 0x5B;      // 初始密钥
    uint8_t counter = 0;     // 位置计数器

    for (uint32_t i = 0; i < size; i++) {
        // 1. XOR with key and counter
        uint8_t val = data[i] ^ key ^ counter;

        // 2. Nibble swap (高4位 ↔ 低4位)
        val = ((val >> 4) & 0x0F) | ((val << 4) & 0xF0);

        data[i] = val;

        // 3. 更新密钥和计数器
        counter++;
        key = ~key;  // 0x5B ↔ 0xA4
    }
}

密钥演化:

  • 字节 0: key=0x5B, counter=0
  • 字节 1: key=0xA4, counter=1
  • 字节 2: key=0x5B, counter=2
  • 字节 3: key=0xA4, counter=3

4.2 DLL 中的解密流程

LoadDataFiles (0x10002dd0)
    │
    ├── OpenAndReadFile("sxgbnw6l.tbl")  → 不解密 (明文)
    │
    ├── OpenAndReadFile("sxgdat6l.tbl")
    │
    └── DecryptData(sxgdat6l_data, size)  (0x10002ef0)
            │
            └── DecryptNibbleSwap(data, size, &key)  (0x1004c900)

注意: sxgbnw6l.tbl 是明文,不需要解密。

4.3 解密后的数据格式

解密后的数据是 16-bit 小端 PCM 音频

验证结果 (前 10 个波形):

索引 偏移 大小 前 5 个采样
0 0x00A308 264 [5905, 17423, 8720, 3605, 287]
1 0x00A410 152 [7863, 16049, -4179, -10323, 30896]
2 0x00A4A8 408 [-18141, -2260, -1233, -29651, -28629]
3 0x00A640 296 [-2279, -31209, 24345, -13536, 26156]
4 0x00A768 184 [-22035, -23566, -22280, 20991, 13063]

采样特征:

  • 范围: ±32000 (16-bit)
  • 采样率: 44100 Hz (推测)
  • 格式: 单声道

5. 数据对应关系

5.1 音色加载流程

MIDI Program (0-127)
       │
       ▼
Lookup Table (0x40-0x13F)
       │
       ▼
Voice Index (0-6047)
       │
       ▼
Section C 偏移表
       │
       ▼
音色定义记录 (28-176 字节)
       │
       ├── 元素 0: VoiceElement (28 字节)
       │           └── 波形索引 (byte 6)
       │                    │
       │                    ▼
       │           Section B 偏移表
       │                    │
       │                    ▼
       │           sxgdat6l.tbl 波形数据
       │
       ├── 元素 1: VoiceElement (28 字节)
       │           └── ...
       └── ...

5.2 MIDI 事件处理流程

Note On (channel, note, velocity)
       │
       ├── 通道 9? → 鼓组模式 (Section G)
       │
       └── 其他通道 → 查找音色参数
              │
              ├── 应用力度曲线 (Section D)
              │
              ├── 查找 VoiceElement
              │     ├── 检查音高范围
              │     └── 检查力度范围
              │
              ├── 获取波形数据 (Section B → sxgdat6l.tbl)
              │
              └── 设置合成器参数
                    ├── 频率 (note → Hz)
                    ├── 振幅 (velocity → 0.0-1.0)
                    ├── 滤波器 (cutoff, resonance)
                    └── 包络 (ADSR)

5.3 全局变量地址

地址 名称 说明
0x1009e6cc g_refCount 数据加载引用计数
0x1009e6d0 g_bnwData sxgbnw6l.tbl 数据指针
0x1009e6d4 g_datData sxgdat6l.tbl 数据指针
0x1009b2ec str_sxgbnw6l 字符串 “sxgbnw6l.tbl”
0x1009b2fc str_sxgdat6l 字符串 “sxgdat6l.tbl”

6. 关键函数地址

地址 函数 说明
0x10002dd0 LoadDataFiles 加载两个 .tbl 文件
0x10002ef0 DecryptData 两阶段解密 (实际只需 Stage 1)
0x1004c900 DecryptNibbleSwap Stage 1: nibble-swap XOR
0x10002d60 CopySxgbnw6lPath 复制 “sxgbnw6l.tbl” 字符串
0x10002d80 CopySxgdat6lPath 复制 “sxgdat6l.tbl” 字符串
0x10002db0 ValidateData 验证文件路径
0x1004c830 OpenAndReadFile 打开并读取文件
0x100030a0 SynthInit 合成器初始化
0x10003ec0 VoiceDispatcher 音色参数分发器
0x10005b38 RenderVoice 读取波形索引 [esi+6]

7. 未解密问题

7.1 Stage 2 解密

DLL 中的 DecryptData 函数包含两个阶段:

  1. Stage 1: Nibble-swap XOR (已验证有效)
  2. Stage 2: Byte-pair swap + XOR 0x5A

经验证,sxgdat6l.tbl 只需 Stage 1 即可得到有效 PCM 数据。Stage 2 可能用于其他文件或已被废弃。

7.2 sxgbnw6l.tbl 加密

DLL 代码显示 DecryptData 被调用两次 (对两个文件都解密),但 sxgbnw6l.tbl 是明文。可能的原因:

  1. sxgbnw6l.tbl 的数据部分 (偏移 0x40 之后) 需要解密
  2. 解密函数对明文数据是幂等的
  3. 代码被修改或版本不一致

8. 附录

8.1 完整 Lookup Table 映射

Program 0  → Voice 0    (Piano)
Program 1  → Voice 1    (E.Piano)
Program 3  → Voice 2    (E.Piano 2)
Program 6  → Voice 3    (Harpsichord)
Program 8  → Voice 4    (Clavinet)
Program 12 → Voice 5    (Celesta)
Program 14 → Voice 6    (Vibraphone)
Program 16 → Voice 7    (Organ)
Program 17 → Voice 8    (Organ)
Program 18 → Voice 9    (Organ)
Program 19 → Voice 10   (Church Organ)
Program 20 → Voice 11   (Reed Organ)
Program 24 → Voice 12   (Guitar)
Program 25 → Voice 13   (Guitar)
Program 27 → Voice 14   (Guitar)
Program 28 → Voice 15   (Guitar)
Program 32 → Voice 16   (Bass)
Program 33 → Voice 17   (Bass)
Program 34 → Voice 18   (Bass)
Program 35 → Voice 19   (Bass)
Program 36 → Voice 20   (Bass)
Program 37 → Voice 21   (Bass)
Program 38 → Voice 22   (Bass)
Program 40 → Voice 23   (Strings)
Program 41 → Voice 24   (Strings)
Program 42 → Voice 25   (Strings)
Program 43 → Voice 26   (Strings)
Program 45 → Voice 27   (Strings)
Program 64 → Voice 28   (Brass)
Program 65 → Voice 29   (Brass)
Program 66 → Voice 30   (Brass)
Program 67 → Voice 31   (Brass)
Program 68 → Voice 32   (Brass)
Program 69 → Voice 33   (Brass)
Program 70 → Voice 34   (Brass)
Program 71 → Voice 35   (Brass)
Program 72 → Voice 36   (Brass)
Program 96 → Voice 37   (Synth)
Program 97 → Voice 38   (Synth)
Program 98 → Voice 39   (Synth)
Program 99 → Voice 40   (Synth)
Program 100 → Voice 41  (Synth)
Program 101 → Voice 42  (Synth)
Program 112 → Voice 43  (Percussion)
Program 113 → Voice 44  (Percussion)
Program 114 → Voice 45  (Percussion)
Program 115 → Voice 46  (Percussion)
Program 116 → Voice 47  (Percussion)
Program 117 → Voice 48  (Percussion)
Program 118 → Voice 49  (Percussion)
Program 119 → Voice 50  (Percussion)
Program 120 → Voice 51  (Percussion)
Program 121 → Voice 52  (Percussion)
Program 122 → Voice 53  (Percussion)
Program 123 → Voice 54  (Percussion)
Program 124 → Voice 55  (Percussion)
Program 125 → Voice 56  (Percussion)
Program 126 → Voice 57  (Percussion)
Program 127 → Voice 58  (Percussion)
2 Likes

我这边跑出来的结果,好像是不需要做第二阶段的 字节交换+XOR 0x5A

Update:如果不做第二阶段,能听出乐器的声音,但杂音很重,所以还是要考虑第二阶段

1 Like

我觉得大概率还是 PCM,你可以把解密后的数据文件用 音频编辑器打开看一下,如果你用 Linux,也可以用 aplay 命令播一下听听

1 Like

用AI做了几天逆向,我的感觉是,这玩意没有预想中那么智能
AI很会偷懒,有的时候跟智障一样,总之需要大量调教才可以

2 Likes

Yamaha S-YXG50 逆向工程架构文档

以下是我烧掉 150 亿 token 的结果
因为是 AI 分析的,可能有不准确的地方

目录

  1. 概述
  2. 程序架构
  3. MIDI 到音频的完整数据流
  4. 效果器链运作机制
  5. 核心效果器算法
  6. 关键数据结构
  7. 音色表系统
  8. 附录

概述

Yamaha S-YXG50 是 Yamaha 公司于 2003 年发布的一款软件波表合成器,以 DLL 形式存在,主要用作 VST 插件。它的核心功能是接收 MIDI 音乐信号,实时合成出高质量的音频输出。

支持的 MIDI 标准:

  • GM(通用 MIDI):最基础的 MIDI 标准,定义了 128 种乐器和一套鼓组
  • GS(Roland 标准):GM 的扩展,增加了更多音色和鼓组
  • XG(Yamaha 标准):Yamaha 自己的扩展标准,支持更丰富的音色编辑和效果器控制

基本能力:

  • 同时处理 16 个 MIDI 通道,每个通道可以演奏不同的乐器
  • 最大 128 复音,即同时可以有 128 个音符在发声
  • 内置三组效果器:混响、合唱、变奏
  • 44.1kHz 采样率、立体声浮点音频输出
  • 基于波表的音色系统,音色数据存储在外部文件中

程序架构

整体结构

S-YXG50 采用分层架构设计,从上到下分为四个主要层次:

第一层:VST 插件外壳(CVstEffect)

这是与宿主程序(如 Cubase、FL Studio)对接的最外层。它负责接收宿主发来的各种消息,比如"打开插件"、“关闭插件”、“处理 MIDI 事件”、"设置采样率"等。这一层本身不做任何音频处理,只是一个翻译官,把宿主的消息翻译成内部引擎能理解的指令。

CVstEffect 内部持有一个 CSynthEngine 实例,所有实际工作都委托给它完成。

第二层:合成器核心引擎(CSynthEngine)

这是整个合成器的大脑。它协调各个子系统的工作:

  • 持有 MIDI 处理器,负责解析 MIDI 消息
  • 持有声音管理器,负责分配和回收声音资源
  • 持有效果处理器,负责给音频加效果
  • 持有音色表,存储所有乐器的音色数据

每当宿主请求一帧音频时,CSynthEngine 就会指挥各个子系统协同工作,最终输出立体声采样。

第三层:功能子系统

这一层包含四个独立的子系统,各司其职:

MIDI 处理器(CMidiHandler):接收原始 MIDI 字节流,解析出有意义的音乐事件。比如它能识别出"第 5 通道按下中央 C,力度 100"这样的指令,并通知声音管理器去触发对应的声音。

声音管理器(CVoiceManager):管理一个包含 128 个声音槽的池子。当收到 Note On 指令时,它会从池子里找一个空闲的声音槽,初始化它的参数,开始播放。当收到 Note Off 指令时,它会让对应的声音进入释放状态。每个声音槽都是一个独立的 DSP 处理单元,有自己的振荡器、包络、滤波器等。

效果处理器(CEffectsProcessor):对所有声音混合后的音频进行效果处理。它包含混响、合唱、变奏三组效果器,每组都可以独立设置类型和参数。

音色表管理器(CVoiceTable):负责加载和管理音色数据文件。它读取 sxgbin41.tbl 中的音色参数(比如用什么波形、包络怎么设置、滤波器怎么调)和 sxgwave4.tbl 中的实际波形采样数据。

第四层:DSP 处理模块

这是最底层的信号处理库,提供各种基础的音频处理功能:

  • 振荡器:生成或读取波形数据
  • 滤波器:改变音频的频率特性
  • 包络发生器:控制声音的音量随时间变化
  • 混响效果器:模拟空间反射,让声音有空间感
  • 合唱效果器:通过延迟调制让声音更丰满
  • 混音器:将多个声音混合成最终的立体声输出

源代码组织

源代码按照功能模块组织在不同的目录中:

include/ 目录存放所有头文件,定义了核心数据结构和接口。最重要的包括 types.h(基础类型)、engine.h(合成器引擎)、voice.h(声音对象)、effects.h(效果器接口)、voice_table.h(音色表)。

midi/ 目录存放 MIDI 消息处理相关的代码。核心文件 midi_processing.cpp 负责解析各种 MIDI 消息,包括 Note On/Off、控制变化、程序变化、系统专属消息等。

voice/ 目录存放声音引擎相关的代码,是整个项目中文件最多、代码量最大的部分。voice_engine.cpp 是声音引擎的核心,voice_rendering.cpp 处理声音的渲染,voice_param.cpp 管理声音参数,voice_state.cpp 管理声音的状态转换(激活、释放、静音等)。

effects/ 目录存放效果器相关的代码。effects_algorithms.cpp 实现了各种效果器算法,effects_params.cpp 处理效果器参数的设置和读取。

dsp/ 目录存放底层 DSP 处理代码。oscillator.cpp 实现波形生成,filters.cpp 实现各种滤波器,envelope.cpp 实现包络发生器,reverb.cpp 实现混响,chorus.cpp 实现合唱,mixer.cpp 实现混音。

channel/ 目录存放通道管理相关的代码。每个 MIDI 通道都有自己的参数状态(音量、声像、弯音等),这些代码负责管理它们。

audio/ 目录存放音频参数管理和命令分发的代码。audio_params.cpp 是最大的单个文件,包含了 DSP 处理器的大部分实现。

xg/ 目录存放 XG/GS 兼容层的代码。这些代码确保 S-YXG50 能正确响应各种 XG 和 GS 专属的 MIDI 消息和系统专属指令。

mfc/ 目录存放 MFC(Microsoft Foundation Classes)相关的工具函数。原始 DLL 使用了 MFC 框架,这些函数处理内存管理、字符串操作等基础功能。

core/ 目录存放 DLL 入口点、运行时工具等核心功能。


MIDI 到音频的完整数据流

总体流程

从 MIDI 信号进入 DLL 到最终输出音频,需要经过以下主要阶段:

  1. MIDI 事件接收
  2. MIDI 消息解析
  3. 通道参数更新
  4. 声音触发与分配
  5. 声音初始化
  6. 波形渲染
  7. 效果器处理
  8. 混音输出

阶段一:MIDI 事件接收

宿主程序通过 VST 接口将 MIDI 事件传递给插件。这些事件被打包在一个事件数组中,每个事件包含事件类型、时间戳和 MIDI 数据字节。

S-YXG50 在音频处理循环的开始处检查是否有待处理的 MIDI 事件。如果有,就逐个取出处理。

阶段二:MIDI 消息解析

对于每个 MIDI 事件,系统首先解析它的状态字节(第一个字节的高四位),确定这是什么类型的消息:

Note On(音符开启):表示某个键被按下。数据包含通道号、音符号(0-127,对应钢琴的 88 个键)和力度(按键的力度,0-127)。力度为 0 的 Note On 等同于 Note Off。

Note Off(音符关闭):表示某个键被释放。数据包含通道号和音符号。

Control Change(控制变化):表示某个控制器的值发生了变化。常见的控制器包括音量(CC#7)、声像(CC#10)、调制轮(CC#1)、延音踏板(CC#64)等。每个 CC 消息包含控制器编号和新的值。

Program Change(程序变化):表示切换乐器音色。数据包含新的音色编号(0-127)。

Pitch Bend(弯音):表示弯音轮的位置变化。弯音值是一个 14 位的数(0-16383),中间值 8192 表示没有弯音。

System Exclusive(系统专属):用于厂商特定的扩展功能。S-YXG50 通过系统专属消息接收 XG、GS、GM 的各种高级设置。

阶段三:通道参数更新

每个 MIDI 通道都有自己的参数状态,包括音量、声像、弯音、调制深度、滤波器截止频率、包络参数等。当收到控制变化消息时,系统会更新对应通道的对应参数。

这些参数存储在全局参数块中,供后续的声音渲染使用。比如音量参数会影响声音的响度,声像参数会影响声音在左右声道的分布,弯音参数会影响声音的音高。

阶段四:声音触发与分配

当收到 Note On 消息时,声音管理器需要为这个音符分配一个声音槽。声音池共有 128 个声音槽,每个 MIDI 通道最多可以同时使用 32 个声音。

分配过程如下:

  • 首先在对应通道的声音槽范围内寻找空闲的槽位
  • 如果找到空闲槽位,直接使用
  • 如果没有空闲槽位,需要"偷取"一个正在发声的槽位(通常选择最早开始发声的)

分配成功后,声音槽被标记为"激活"状态。

阶段五:声音初始化

声音槽分配好后,需要初始化它的各种参数:

音色参数加载:从音色表中查找对应的音色数据。查找时需要考虑当前通道选择的乐器编号、Bank Select(音色库选择)的 MSB 和 LSB 值。如果是鼓组通道(第 10 通道),则从鼓组数据中查找。

振荡器初始化:设置波形数据的起始地址、循环点位置、采样率等。如果波形采样率与输出采样率不同,还需要设置采样率转换参数。

包络初始化:根据音色数据中的包络参数,计算 Attack、Decay、Sustain、Release 各阶段的速率和目标值。

滤波器初始化:根据音色数据中的滤波器参数,计算滤波器的截止频率和共振值。

增益初始化:根据力度、音量、声像等参数,计算初始的左右声道增益值。

阶段六:波形渲染

声音初始化完成后,进入波形渲染阶段。系统以 128 采样为一个处理块,逐块进行渲染。

波形读取:振荡器从波形数据中读取采样值。读取速率由音高决定——音高越高,读取速率越快,波形被"压缩",频率升高。由于读取速率通常不是整数,需要使用线性插值来计算中间值,保证声音平滑。

循环处理:大多数乐器音色都有循环区域。当读取位置到达循环结束点时,会跳回循环起始点继续读取,这样声音可以持续任意长时间。有些音色是单次播放(如钢琴),到达终点后就停止。

包络应用:包络发生器根据当前所处的阶段(Attack、Decay、Sustain、Release)计算一个音量因子,乘到波形数据上。Attack 阶段音量从 0 上升到最大值;Decay 阶段从最大值下降到 Sustain 电平;Sustain 阶段保持在 Sustain 电平;Release 阶段从当前电平下降到 0。

S-YXG50 为每个声音维护三组包络:主包络控制整体音量,合唱发送包络控制送往合唱效果器的信号量,混响发送包络控制送往混响效果器的信号量。

滤波器应用:双二阶滤波器(Biquad Filter)改变音频的频率特性。最常用的是低通滤波器,它让低频通过而衰减高频,使声音变得更暗、更柔和。滤波器的截止频率可以由 MIDI CC#74(Brightness)控制。

增益与声像:根据力度、通道音量、表情、声像等参数,计算最终的左右声道增益。声像控制声音在左右声道的分布——声像值为中间时左右声道音量相等,偏左时左声道更响,偏右时右声道更响。

输出到缓冲区:渲染好的采样数据被写入多个缓冲区——主输出缓冲区(左右声道)、合唱发送缓冲区、混响发送缓冲区。这些缓冲区将在后续的效果器处理阶段使用。

阶段七:效果器处理

当所有活跃的声音都渲染完成后,系统将它们的输出混合在一起,然后送入效果器链处理。效果器处理的详细机制在下一章描述。

阶段八:混音输出

效果器处理完成后,进入最终的混音输出阶段:

效果返回混合:将效果器处理后的信号(混响返回、合唱返回、变奏返回)与干信号混合在一起。

主音量应用:将混合后的信号乘以主音量系数。

限幅处理:防止信号超出有效范围。如果采样值超过最大值(131071)或低于最小值(-131072),就将其限制在范围内,避免削波失真。

输出到宿主:将最终的立体声音频数据写入宿主提供的输出缓冲区。


效果器链运作机制

效果器架构概述

S-YXG50 的效果器系统由三组独立的效果器组成:混响(Reverb)、合唱(Chorus)、变奏(Variation)。每组效果器可以独立设置类型和参数。

效果器的信号流如下:

每个声音渲染完成后,它的输出被分成多个部分:

  • 主输出:直接送到最终混音器
  • 混响发送:送到混响效果器
  • 合唱发送:送到合唱效果器
  • 变奏发送:送到变奏效果器

每组效果器处理完后,输出信号被送回最终混音器,与干信号混合。发送量由 MIDI CC 控制(CC#91 控制混响发送,CC#93 控制合唱发送,CC#94 控制变奏发送)。

混响效果器

混响模拟声音在空间中的反射效果,让声音听起来像是在某个房间里演奏。

支持的混响类型:

  • Hall(大厅):模拟音乐厅的大空间,混响时间长,声音温暖丰满
  • Room(房间):模拟普通房间,混响时间较短,声音紧凑
  • Stage(舞台):模拟舞台环境,声音明亮清晰
  • Plate(板式):模拟金属板混响器,声音明亮,衰减均匀

可调参数:

  • 混响时间:声音衰减到听不见所需的时间
  • 阻尼:高频衰减的速度,阻尼越大高频衰减越快,声音越暗
  • 扩散:反射的密度,扩散越大声音越平滑
  • 预延迟:直达声与第一次反射之间的时间差

合唱效果器

合唱效果通过将信号延迟并调制,产生多个略有差异的声音叠加的效果,让单个乐器听起来像是多个乐器在同时演奏。

支持的合唱类型:

  • Chorus(合唱):标准合唱效果,延迟时间较长(10-30ms),没有或很少反馈
  • Flanger(镶边):延迟时间很短(1-10ms),有较高的反馈,产生"喷气机"效果
  • Symphonic(交响):多个延迟线叠加,产生更丰满的合唱效果

工作原理:

  • 输入信号被写入一个延迟缓冲区
  • 一个低频振荡器(LFO)不断调制读取位置,使得延迟时间在一定范围内周期性变化
  • 读取出来的信号与原始信号混合,产生音高微调的效果
  • 左右声道使用不同相位的 LFO,产生立体声扩展效果

变奏效果器

变奏效果器是一个通用的效果器插槽,可以加载各种不同类型的效果。支持的效果类型非常丰富:

调制类效果:

  • Chorus/Flanger/Symphonic:与合唱效果器类似
  • Phaser(相位器):使用全通滤波器链产生梳状滤波效果
  • Tremolo(颤音):周期性调制音量
  • Auto Pan(自动声像):周期性调制声像位置

空间类效果:

  • Delay/Echo(延迟/回声):将信号延迟后与原始信号混合
  • Early Reflections(早期反射):模拟房间的前几次反射
  • Gate Reverb(门混响):混响突然截止的效果

失真类效果:

  • Distortion(失真):硬削波,产生强烈的谐波失真
  • Overdrive(过载):软削波,产生温暖的失真效果
  • Amp Simulator(音箱模拟):模拟吉他音箱的音色

动态类效果:

  • Compressor(压缩器):减小动态范围,让响的和轻的部分差距变小
  • Limiter(限幅器):防止信号超过阈值

频率类效果:

  • 3-Band EQ / 2-Band EQ:多段均衡器,可以调整不同频段的音量
  • Auto Wah(自动哇音):周期性调制滤波器截止频率

其他效果:

  • Rotary Speaker(旋转扬声器):模拟 Leslie 音箱的旋转效果
  • Pitch Change(变调):改变音高
  • Karaoke(卡拉 OK):消除人声

效果参数管理

效果器的参数通过一个专门的状态结构来管理。这个结构存储了所有效果器的当前设置,包括每种效果器的类型编号和各个参数值。

参数值通常是 0-127 范围的整数,对应 MIDI 控制器的值范围。有些参数使用 16 位编码,可以表示更精细的控制。


核心效果器算法

混响算法

S-YXG50 的混响采用经典的 Schroeder 混响结构,这是一种在数字音频领域广泛使用的混响算法。

信号处理流程:

  1. 预延迟:输入信号首先经过一个短延迟(5-20ms)。这模拟了从声源到墙壁的首次反射的时间差,增加了"空间感"。预延迟越长,听起来空间越大。

  2. 早期反射:接下来模拟房间的前几次反射。这些反射是离散的回声,每个回声有不同的延迟时间和音量。早期反射帮助听觉系统判断空间的大小和形状。

  3. 梳状滤波器组:这是混响的核心部分。8 个并联的梳状滤波器,每个都有不同的延迟长度(大约 30-45ms)。梳状滤波器的工作原理是将信号延迟后与原始信号混合,产生一系列共振峰。多个不同延迟长度的梳状滤波器并联,产生的共振峰相互补充,形成密集的混响尾音。每个梳状滤波器都有反馈(控制混响时间)和阻尼(控制高频衰减)。

  4. 全通滤波器组:4 个串联的全通滤波器。全通滤波器的特点是让所有频率都通过,但会改变相位。这一步的目的是增加混响的"扩散感",让声音更平滑,减少梳状滤波器带来的"金属感"。

  5. 阻尼处理:根据阻尼参数调整高频的衰减速度。阻尼越大,高频衰减越快,混响听起来越"温暖"、越"暗"。这模拟了真实房间中声波被墙壁材料吸收的特性。

不同混响类型的差异:

大厅混响有较长的混响时间(约 0.7 秒),较高的扩散和密度,预延迟约 20ms,适合营造宏大的空间感。

房间混响混响时间较短(约 0.3-0.5 秒),扩散和密度中等,预延迟约 5-10ms,适合营造紧凑的房间感。

板式混响扩散很高,声音明亮均匀,没有明显的早期反射,适合人声和乐器的混响处理。

合唱算法

信号处理流程:

  1. 延迟线:输入信号被写入一个循环缓冲区。缓冲区长度由最大延迟时间决定(10-30ms)。

  2. LFO 调制读取位置:一个低频振荡器(通常是正弦波或三角波)不断改变读取位置。这使得读出的信号相对于写入的信号有周期性的时间偏移,从而产生音高微调效果。

  3. 反馈(可选):部分输出信号反馈回输入,增加效果的"厚度"。合唱模式通常没有或很少反馈,镶边模式有较高的反馈(0.7-0.9)。

  4. 立体声扩展:左右声道使用相位差 90 度的 LFO,使得左右声道的调制不同步,产生宽广的立体声效果。

  5. 混合:将调制后的信号与原始信号按一定比例混合。

合唱与镶边的区别:

合唱的延迟时间较长(10-30ms),LFO 速率较慢(0.1-5Hz),没有或很少反馈,产生的效果是声音变得更丰满、更宽广。

镶边的延迟时间很短(1-10ms),LFO 速率更慢(0.1-2Hz),有较高的反馈,产生的效果是那种典型的"喷气机"声音,带有明显的共振峰。

延迟算法

信号处理流程:

  1. 输入信号被写入延迟线
  2. 延迟线的输出按反馈系数送回输入
  3. 干信号和延迟信号按混合比例混合后输出

延迟时间可以从 1 毫秒到 1 秒以上。反馈系数控制重复次数——0% 时只有一个回声,接近 100% 时回声会重复很多次逐渐衰减。

相位器算法

信号处理流程:

  1. 输入信号通过 4-12 个串联的全通滤波器
  2. LFO 调制全通滤波器的中心频率
  3. 部分输出信号反馈回输入
  4. 调制后的信号与原始信号混合

全通滤波器会改变信号的相位但不改变幅度。当多个全通滤波器串联时,某些频率的相位变化会相互抵消,而另一些频率会增强或减弱,形成梳状滤波效果。LFO 不断改变这个梳状滤波的频率位置,产生动态的"嗖嗖"声。

颤音与自动声像算法

颤音:LFO 直接调制信号的音量。当 LFO 值大时音量大,LFO 值小时音量小,产生周期性的音量波动。

自动声像:LFO 调制信号在左右声道的分配。当 LFO 值大时声音偏右,LFO 值小时声音偏左,产生声音在左右之间移动的效果。

失真与过载算法

信号处理流程:

  1. 输入信号先经过增益放大
  2. 放大后的信号通过非线性函数进行削波
  3. 削波后的信号通过低通滤波器去除高频谐波
  4. 处理后的信号与原始信号按混合比例混合

过载使用软削波函数(如双曲正切函数),信号在接近阈值时逐渐被压缩,产生的失真比较温暖、平滑。

失真使用硬削波,信号超过阈值后直接被截断,产生的失真比较强烈、粗糙。

削波会产生大量谐波,听起来很刺耳,所以后面通常会接一个低通滤波器来柔化音色。

均衡器算法

S-YXG50 的均衡器采用三段设计:

低频搁架:可以提升或衰减某个频率以下的所有频率。典型频率范围是 20-500Hz。就像一个可以整体调高或调低的低音旋钮。

中频峰值:可以提升或衰减某个中心频率附近的一个频带。中心频率和带宽(Q 值)都可以调整。这是最灵活的 EQ 段,可以针对特定频率进行精确调整。

高频搁架:可以提升或衰减某个频率以上的所有频率。典型频率范围是 2kHz-20kHz。就像一个可以整体调高或调低的高音旋钮。

压缩器算法

信号处理流程:

  1. 检测输入信号的电平(可以是 RMS 平均值或峰值)
  2. 将检测到的电平与阈值比较
  3. 如果电平低于阈值,增益为 1(不压缩)
  4. 如果电平高于阈值,按照压缩比降低增益
  5. 用攻击时间和释放时间平滑增益变化
  6. 将计算出的增益应用到信号上

压缩比决定了压缩的强度。比如 4:1 的压缩比意味着输入信号每增加 4dB,输出只增加 1dB。无限大:1 的压缩比就是限幅器,输出永远不会超过阈值。

攻击时间决定了增益下降的速度——攻击时间短,压缩反应快,能抓住瞬态峰值;攻击时间长,压缩反应慢,保留更多瞬态。

释放时间决定了增益恢复的速度——释放时间短,压缩恢复快,可能产生"抽吸"效果;释放时间长,压缩恢复慢,动作更平滑。

旋转扬声器算法

这个效果模拟 Leslie 音箱的旋转扬声器,常见于风琴音色。

工作原理:

Leslie 音箱有两组扬声器——高频号角和低频低音箱——它们会旋转。旋转产生两个效果:

多普勒效应:当扬声器朝向听者运动时,接收到的频率会升高;当远离时频率会降低。这产生了音高调制效果。

幅度调制:旋转时,扬声器有时朝向听者(音量大),有时背向听者(音量小)。这产生了音量调制效果。

S-YXG50 分别模拟高频和低频的旋转效果,然后将它们混合。左右声道分别模拟不同的旋转位置,产生环绕感。LFO 速率可以切换快速和慢速。

早期反射算法

早期反射模拟房间中直达声之后的前几次墙壁反射。与混响不同,早期反射是离散的回声,而不是密集的混响尾音。

工作原理:

使用多抽头延迟线,每个抽头代表一次反射。每个抽头有不同的延迟时间和增益,模拟不同距离和角度的反射。

小房间的反射间隔短、衰减快;大房间的反射间隔长、衰减慢。走廊会产生强侧向反射。


关键数据结构

声音 DSP 对象(VoiceDSPObject)

这是每个活跃声音的核心数据结构,大小为 780 字节(0x30C)。每个正在发声的音符都有一个这样的对象实例。

主要字段说明:

状态标志

  • active 字段表示这个声音槽是否在使用中
  • flags 字段用位标志表示声音的各种状态——是否正在发声、是否正在释放、是否静音等

音频参数

  • sampleRate 存储当前的采样率
  • rateScale 存储速率缩放因子,由力度决定
  • coeffTable 指向系数表,系数表存储了各种 DSP 处理需要的参数

振荡器状态

  • 包含波形数据指针、循环点位置、当前采样位置等
  • samplePos 和 sampleFrac 记录精确的读取位置(整数部分和小数部分)

滤波器状态

  • 22 个浮点数存储滤波器的内部状态
  • 双二阶滤波器需要存储前两个输入和输出的值

噪声发生器

  • 4 组 23 位线性反馈移位寄存器(LFSR)
  • 用于生成白噪声,模拟某些乐器的噪声成分

包络电平

  • 四个浮点数分别存储主包络、合唱包络、混响包络和备用主包络的当前电平

增益值

  • 四组当前增益和目标增益(分别对应主输出 L/R 和发送 L/R)
  • 增益平滑算法逐步将当前增益移向目标增益,避免突变产生咔嗒声

DSP 函数指针表

  • 29 个函数指针,构成一个处理链
  • 每个函数负责一个特定的 DSP 任务
  • 系统按顺序调用这些函数,完成整个声音的渲染

引擎布局结构(EngineLayout)

这个结构描述了 DLL 内部引擎对象的内存布局。它是连接各个子系统的桥梁。

关键字段:

  • voiceManager 指针:指向声音管理器
  • midiHandler 指针:指向 MIDI 处理器
  • effectsProcessor 指针:指向效果处理器
  • voiceTable 指针:指向音色表管理器
  • midiResetMode:MIDI 重置模式(0=GM,1=XG,2=GS)
  • isXGLite:XG Lite 模式标志

效果器布局结构(EffectsLayout)

这个结构描述了效果处理器内部的参数存储布局。所有效果器参数都以字节为单位存储在连续的内存区域中。

主要参数组:

  • 混响参数:时间、阻尼、扩散、预延迟
  • 合唱参数:速率、深度、反馈、延迟
  • 延迟参数:时间、反馈、电平
  • 变奏参数:类型和最多 4 个通用参数
  • 均衡器参数:低频增益、中频增益、高频增益、中频频率
  • 插入效果参数:20 个通用参数
  • 主效果参数:4 个通用参数

MIDI 参数块(MidiParamBlock)

存储当前活跃通道的 MIDI CC 值。这是一个全局数据块,所有通道共享,当切换通道时需要更新。

存储的参数包括:

  • 声像(CC#10)
  • 音量(CC#7)
  • 弯音值(0-16383)
  • 滤波器截止频率(CC#74)
  • 调制轮(CC#1)
  • 滤波器共振(CC#71)
  • 包络起音(CC#73)
  • 包络衰减(CC#75)
  • 表情(CC#11)
  • 包络释放(CC#72)
  • 合唱发送(CC#93)
  • 混响发送(CC#91)
  • 变奏发送(CC#94)
  • 弯音灵敏度(RPN 0)
  • 调音(RPN 1/2)
  • 当前力度

音色表系统

音色表文件结构

S-YXG50 使用两个音色数据文件:

sxgbin41.tbl:音色参数表,约 4MB。包含各种乐器的参数定义、鼓组配置、音色索引等。文件内部被分成 17 个数据段,每个段存储不同类型的数据。

sxgwave4.tbl:波形数据表,约 4MB。包含所有乐器的实际音频采样数据。这些采样是单声道的,以 22050Hz 或 44100Hz 采样率录制。文件经过简单的编码保护,加载时需要解码。

音色参数表的数据段

鼓组表(dataSeg00-03):四张鼓组表,分别对应 GS、XG、XG SFX、GM2 标准。每张表 128 个字节,每个字节对应一个音符,值是鼓组编号(用于索引鼓组数据)。

鼓组映射(dataSeg04):XG 鼓组的映射表,31 个鼓组 × 128 个音符。每个条目是 16 位,指向鼓组参数数据的位置。

鼓组默认参数(dataSeg05):每个鼓键的默认参数,包括音高偏移、电平、声像、效果发送量、滤波器设置、包络设置等。每个鼓键 30 字节。

SFX 索引表(dataSeg06):特殊音效的索引表,用于快速查找 SFX 音色。

Bank Select 表(dataSeg07-10):存储 GS 和 XG 标准的 Bank Select MSB/LSB 映射关系。当收到 Bank Select 消息时,系统通过这些表确定实际要加载的音色。

音色索引表(dataSeg11-12):GS/GM 索引表和 XG 索引表。这些表将程序编号(和可能的 Bank Select 值)映射到具体的音色参数位置。

基础音色参数表(dataSeg13):存储所有基础音色的参数,每个音色的参数大小可变。这些参数包括波形选择、音高设置、包络参数、滤波器参数等。

扩展音色参数表(dataSeg14):存储扩展音色的参数,结构与基础参数表类似。

采样参数索引(dataSeg15):将音色映射到具体的采样描述符。

采样描述符(dataSeg16):每个描述符 16 字节,描述一个采样片段的属性。

采样描述符详解

每个采样描述符包含以下信息:

力度分层:同一个音符可能有多个采样,按力度分层。比如轻按和重按会触发不同的采样,以模拟真实乐器的力度变化。

基准音高:这个采样原本录制时的音高。播放时需要根据请求的音高进行音高转换。

采样起始地址:波形数据在文件中的起始位置。使用 3 字节大端序编码。

循环结束点:循环播放的结束位置。到达这个位置后会跳回循环起始点继续播放。

采样率指示:标明这个采样是以 22050Hz 还是 44100Hz 录制的。如果采样率与输出采样率不同,播放时需要进行采样率转换。

音域范围:这个采样适用的音高范围。超出这个范围的音符会使用相邻的采样。

波形数据解码

sxgwave4.tbl 文件经过简单的编码保护。解码过程如下:

对文件中的每个字节,依次进行以下操作:

  1. 与当前密钥和字节索引进行异或运算
  2. 交换高四位和低四位
  3. 与固定值 0x5C 异或
  4. 密钥在 0x5D 和 0xA2 之间交替切换

这个解码算法在 DLL 中实现,加载波形文件时自动执行。

音色查找流程

当需要为某个音符查找音色时,系统执行以下步骤:

  1. 确定当前通道是旋律通道还是鼓组通道
  2. 如果是旋律通道,根据 Bank Select MSB、LSB 和程序编号,在索引表中查找对应的音色参数
  3. 如果是鼓组通道,根据鼓组编号和音符号,在鼓组映射表中查找对应的鼓键参数
  4. 从音色参数中获取波形索引
  5. 根据波形索引找到采样描述符
  6. 从采样描述符中获取采样起始地址、循环点等信息
  7. 使用这些信息初始化声音的振荡器

附录

MIDI CC 编号与功能对照

CC#0 Bank Select MSB:选择音色库的高 7 位
CC#1 Modulation:调制轮,通常用于控制颤音深度
CC#7 Volume:通道音量
CC#10 Pan:声像位置,控制声音在左右声道的分布
CC#11 Expression:表情,通常与音量配合使用
CC#64 Sustain:延音踏板,按下后音符不会在释放键时停止
CC#71 Resonance:滤波器共振,增强截止频率附近的频率
CC#72 Release:包络释放时间
CC#73 Attack:包络起音时间
CC#74 Cutoff:滤波器截止频率
CC#75 Decay:包络衰减时间
CC#91 Reverb Send:送往混响效果器的信号量
CC#93 Chorus Send:送往合唱效果器的信号量
CC#94 Variation Send:送往变奏效果器的信号量
CC#120 All Sound Off:立即停止所有声音
CC#121 Reset All:重置所有控制器到默认值
CC#123 All Notes Off:让所有正在发声的音符进入释放状态

XG 效果类型代码

混响类型(0x0100-0x0500):

  • Hall 1/2:大厅混响
  • Room 1/2/3:房间混响
  • Stage 1/2:舞台混响
  • Plate 1/2:板式混响
  • GM Reverb:通用 MIDI 默认混响

合唱类型(0x4100-0x4300):

  • Chorus 1/2/3/4:不同风格的合唱
  • Flanger 1/2:镶边效果
  • GM Chorus:通用 MIDI 默认合唱

变奏类型(0x4100-0x5D02):

  • 合唱/镶边/交响:调制类效果
  • 相位器:梳状滤波效果
  • 旋转扬声器:Leslie 音箱模拟
  • 颤音/自动声像:周期性调制
  • 失真/过载:谐波失真
  • 均衡器:频率调整
  • 延迟/回声:时间延迟效果
  • 早期反射/门混响:空间效果
  • 卡拉 OK:人声消除

采样率转换原理

当波形采样率(如 22050Hz)与输出采样率(44100Hz)不同时,需要进行采样率转换。

转换使用线性插值方法:假设输入采样率是输出采样率的一半,那么每产生一个输出采样,读取位置只前进半个输入采样。当读取位置落在两个输入采样之间时,根据距离两个采样的比例计算出一个中间值。

比如读取位置在第 100 个和第 101 个输入采样之间的 0.3 位置,那么输出值 = 第100个采样 × 0.7 + 第101个采样 × 0.3。


文档版本:1.0
生成日期:2026-06-07
基于 S-YXG50.dll 逆向工程分析

1 Like

事实上逆向下来,我发现AI并没有很好执行我的意图,RVA的标注都对不上……
而且我在想,就XG给的那种小容量音色库,实际音质能做到什么程度呢?
我想如果真的要做这种midi音源,我还是应该考虑一下能不能用rust去重写timidity

1 Like

TiMidity似乎很久没更新了,但是效率比FluidSynth快多了。
还有个WildMIDI,但是它不支持SoundFonts,只支持GUS音色。

我有一个新的想法,就是只逆向yamaha数据文件读取和调用相关的程序,然后剩余部分基于timidity的思路重新实现

2 Likes

重大进展,不需要 XOR 0x5A,第一阶段解密后,只做字节交换即可

我感觉 yamaha 有点鸡贼,做这个异或运算可能是想防内存 dump,所以在音频输出阶段很可能会再做一次异或运算,等我后续逆向结果

opencode+鸡爪(Ghidra)真好用~

2 Likes

我的猜测应该是正确的,在 0x10034DFB0x10034E5E 这两个函数也有 XOR 0x5A 的操作

@gaozhe3321 你按照我的方法应该能拿到 PCM 格式的采样文件

1 Like

我已经在着手写合成器了,目前是把 MIDI 处理部分的架构搭好了,接下来计划参考 QXGEdit 的代码进一步核实 tbl 文件内置数据结构
希望能最终实现对于 S-YXG50 的完整替代,可以跨平台使用,然后实现对于 S-YXG2006LE 的数据文件兼容,尽可能让这个只支持 XGLite 的软件实现尽可能对 FullXG 的支持(没有的乐器就退回基础乐器了)
后续实现 ALSA/Pipewire/JACK/CoreAudio 的多平台支持

1 Like