Yamaha S-YXG 50 逆向记录

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

最近跟SysEx干上了,但还好的是,无论是XG还是GS,他们的SysEx 本质上都是在指定内存地址写入数据,也就是说我可以借此了解到这类合成器的内存结构

接下来应该尝试实现对于tbl文件的读取了,但我需要先完成一个通用的内存结构,可以同时兼容S-YXG50和2006LE(甚至是更早期的 S-YXG 100 等)

1 Like

我最近有点卡住了,根据AI逆向的结果,好像prevoice表是变长结构……这就很难受了

顺带,2006LE的数据结构也是奇葩,也是大量变长结构……

项目地址在这里 https://github.com/madaha-dev/madaha ,有兴趣的也可以一起看看

1 Like

告诉你一个坏消息:2006LE 的数据文件里面是缺少了部分乐器的,Full XG 真的是“臣妾做不到”了

现在 S-YXG50 已经基本打通了从数据到波形的链路。但是 2006LE 的数据格式十分蛋疼,到目前为止卡在了半路上,目前只能确定里面有哪些乐器,但乐器到最终波形这部分,AI 也蒙圈了,2006LE 在这部分用了间接寻址,很难找函数

S-YXG50 TBL 文件格式分析

概述

Yamaha S-YXG50 软件合成器的音色库文件(.tbl)包含全部乐器定义、波形参数和鼓映射数据。
本文档基于 Ghidra 反编译 + GDB 动态追踪验证,完整还原了 TBL 文件的结构和解析流程。

两个版本:

  • sxgbin41.tbl — 4MB 版本,含 9 个 16-bit 采样
  • sxgbin21.tbl — 2MB 版本,全部 8-bit 采样

1. 文件头部 (0x00–0x63)

偏移    大小  字段
0x00    0x10  Header: "MU50 4MB V2.0" / "MU50 2MB V2.0"
0x10    0x0E  波形文件名: "SXGWAVE4.TBL" / "SXGWAVE2.TBL"
0x1E    0x01  解码标志 (0=加密, 1=已解码)
0x20    0x44  segLength[17] — 17 个 int32 LE, 每个数据段的字节长度

头部之后,17 个数据段从偏移 0x64 开始连续排列。


2. 数据段总表

seg 偏移 大小 类型 用途
00 0x000064 0x0080 uint8[128] GS 鼓组号表
01 0x0000E4 0x0080 uint8[128] XG 鼓组号表
02 0x000164 0x0080 uint8[128] XG SFX 鼓组号表
03 0x0001E4 0x0080 uint8[128] GM2 鼓组号表
04 0x000264 0x1F00 uint16[31][128] 鼓映射表
05 0x002164 0x2490 DrumData[312] 鼓参数表 (30B/条)
06 0x0045F4 0x00AE uint16[87] GM2 SFX Program 表
07 0x0046A2 0x0080 uint8[128] GS Bank MSB → selector
08 0x004722 0x0080 uint8[128] XG Bank MSB → selector
09 0x0047A2 0x0080 uint8[128] XG Bank LSB → selector
10 0x004822 0x0080 uint8[128] XG Melody Voice LSB
11 0x0048A2 0x1800 uint16[24×128] GS 程序表
12 0x0060A2 0x3800 uint16[56×128] XG 程序表
13 0x0098A2 0xFB2A PreVoiceDef[] pre-voice 定义表 (基表)
14 0x0193CC 0x5CAE PreVoiceDef[] pre-voice 定义表 (扩展)
15 0x01F07A 0x01EC uint16[246] 波形偏移索引表
16 0x01F266 0x4410 WaveEntry[1089] 波形参数表 (16B/条)

3. 核心数据结构

3.1 Pre-voice 定义 (seg13/seg14)

pre-voice 定义是音色描述的最小单元。每个定义以 2 字节头开始:

偏移  大小  字段
+0     2    header: [bitmask, count_control]
              - header[0]: bitmask (不直接用于迭代)
              - header[1] bit1: 0→1个元素, 1→2个元素
+2     78×N  元素块 (N = count, 按 index 0,1 访问)

3.2 元素 (Element) — 78 字节

偏移 大小 字段 说明
0 1 seg15_idx dataSeg15 索引 (0-245)
1 1 key_center 音高中心
2 1 key_span 音高范围 (匹配: abs(note - center) <= span)
3 1 vel_center 力度中心
4 1 vel_span 力度范围
5 1 lfo_wave LFO 波形选择 (mask 0x7)
6 1 vel_threshold 力度阈值
7 1 pitch_offset 音高偏移 (signed)
8 1 vol_offset 音量偏移 (signed)
9-10 2 pitch_fine 音高微调
11-14 4 (未知) 未确认
15 1 pitch_mode 调音模式 (FUN_100140f0 读取)
16 1 range_base 范围基值
17 1 mono_mode 单音模式标志
18-21 4 sample_slot 采样槽工作区
22-77 56 synth_params 合成参数 (EG/Filter/LFO)

3.3 WaveEntry (seg16) — 16 字节

偏移 大小 字段 说明
0 1 velocity 力度敏感度 (0=最响)
1 1 baseKey 基准音高 (MIDI key)
2 1 tone 音分微调
3-5 3 negOffset 采样负偏移 (24-bit BE)
6-8 3 loopStart 循环起始偏移 (24-bit BE, 0=无循环)
9-11 3 baseAddr 采样基地址 (24-bit BE)
12 1 sampleFlags bit7=1→8-bit PCM; bit7=0→16-bit LE PCM
13 1 (保留) 恒为 0
14 1 keyStart 键位下限
15 1 keyEnd low7=范围上限, bit7=1→末层

计算:

startAddr = baseAddr - negOffset    // PCM 起始位置
loopPoint = baseAddr + loopStart     // 循环折返点
sampleLen = loopPoint - startAddr    // 总采样长度
attackLen = baseAddr - startAddr     // 攻击段长度 (= negOffset)
loopLen   = loopStart                // 循环段长度 (= 0 则无循环)

3.4 SXGWAVE 波形文件

PCM 数据存储在 SXGWAVE*.TBL 中,采样率 22050Hz,单声道。

sampleFlags & 0x80 格式
1 (0x80) 8-bit 无符号 PCM (中心值 128)
0 (0x00) 16-bit 小端有符号 PCM (仅 sxgbin41 部分条目)

4. 程序解析流程

MIDI (bankMSB, bankLSB, program, note, velocity)
  │
  ├─ selector = XGBankMSBTable[bankMSB]           // seg08
  │   if selector == 1: selector = XGBankLSBTable[bankLSB]  // seg09
  │
  ├─ prevoiceIdx = XGProgramTable[selector×128 + program]   // seg12
  │
  ├─ byte_offset = prevoiceIdx × 2                 // Ghidra: *2 (uint16_t* 指针算术)
  │
  ├─ elementCount = (seg13[byte_offset+1] & 2) ? 2 : 1  // header[1] bit1
  │
  ├─ 按 INDEX 遍历 element[0..count-1]:
  │   if abs(note  - elem[1]) <= elem[2]  → key 匹配
  │   if abs(vel   - elem[3]) <= elem[4]  → vel 匹配
  │   匹配 → 存入 matched[]
  │
  ├─ 对每个匹配 element:
  │   ├─ range = MIDI key                    // FUN_100140f0
  │   ├─ init element params                 // vtable[0x51c]
  │   ├─ entry = seg15[elem[0]]             // waveform_FUN_10004df0
  │   │   while (entry.keyEnd.range < range && !entry.keyEnd.bit7)
  │   │       entry++                        // 链扫描 (16B 步进)
  │   │   return entry                       // 匹配的 WaveEntry*
  │   └─ WaveEntry* → voice                  // vtable[0x38c]
  │
  └─ 多个 element → 多个 voice → 混音输出

关键点

  1. 元素个数: header[1] bit1 = 0 → 1个; = 1 → 2个
  2. 元素访问: 按 INDEX 0,1(非 bitmask 顺序)
  3. 范围匹配: abs(note - center) <= span(center+span,非传统 min/max)
  4. 链扫描 用 MIDI key 作为 range,按 (keyEnd & 0x7F) < range 推进
  5. 力度分层 在 element 级别实现:不同 vel 范围的 element 指向不同 chain
  6. 多层叠音: count=2 时两个 element 各自走完整处理链,产生两个 voice,混音输出

5. DrumData (seg05) — 30 字节

偏移 大小 字段 XG NRPN 说明
0 1 pitchCoarse 0x00 粗调音高 (64=±0)
1 1 pitchFine 0x01 微调
2 1 level 0x02 音量
3 1 alternateGroup 0x03 交替分组
4 1 pan 0x04 声像 (64=center)
5 1 reverbSend 0x05 混响发送
6 1 chorusSend 0x06 合唱发送
7 1 variationSend 0x07 变化效果发送
8 1 keyAssign 0x08 0=Single, 1=Multi
9 1 rcvNoteOff 0x09 接收 Note Off
10 1 rcvNoteOn 0x0A 接收 Note On
11 1 filterCutOff 0x0B 滤波器截止
12 1 filterResonance 0x0C 滤波器共鸣
13 1 egAttack 0x0D EG 起音
14 1 egDecay1 0x0E EG 衰减1
15 1 egDecay2 0x0F EG 衰减2
16 1 isSFXSound 0=SFX, -1=Drum
17 1 sfxSoundID SFX 音色 ID
18 1 baseKey 基准音高
19-20 2 negOffset 采样负偏移 (16-bit BE)
21 1 (保留)
22-23 2 loopEnd 循环结束点 (16-bit BE)
24-26 3 baseAddr 采样基地址 (24-bit BE)
27 1 sampleRate 采样率标识
28-29 2 (未知) 未确认

6. 验证与调试

GDB 动态追踪结果(S-YXG50.exe, DLL base 0x10000000):

# Prog 0 (Acoustic Grand Piano), note=60 vel=100
FUN_10017060: elem[0]=37[1]=0[2]=127[3]=1[4]=127 Note[1]=60[3]=100[10]=0
FUN_100140F0: elem[15]=0[16]=60 Note[1]=60[6]=77[10]=0
WAVE: seg15_idx=37 range=60

# MSB=0 LSB=41 Prog=0 (Dream) — 双采样叠层
17000# RET: matchCount=2
MATCH# elem[0]=236 seg15=236
MATCH# elem[0]=37  seg15=37
WAVE# seg15_idx=236 range=60
WAVE# seg15_idx=37 range=60

确认结果:

  • Note[1] = MIDI key, Note[3] = velocity, Note[10] = 0 (vel 匹配开启)
  • chain 扫描的 range = MIDI key
  • matchCount = 匹配的 element 数(最大 2)
  • 每个匹配 element 独立创建 voice

7. 解析器

sxgparser.py — Python 解析和提取工具。

# 解析一个音符
python3 sxgparser.py from_veg/sxgbin21.tbl --note 0 0 0 60 100

# 提取 WAV
python3 sxgparser.py from_veg/sxgbin21.tbl --note 0 0 0 60 100 --extract

# 指定叠加层 (Dream: --element 0 或 --element 1)
python3 sxgparser.py from_veg/sxgbin21.tbl --note 0 41 0 60 100 --extract --element 1

# 使用 GS 模式
python3 sxgparser.py from_veg/sxgbin21.tbl --gs --note 0 0 0 60 100

程序匹配率

TBL 匹配率 格式
sxgbin21.tbl 128/128 全部 8-bit :white_check_mark:
sxgbin41.tbl 128/128 119×8-bit + 9×16-bit :warning:

叠层乐器 (50/128 程序)

多个 element 叠层的乐器。用 --element N 选择提取哪层。

Prog 名称 Layer 0 Layer 1
7 Clavinet 0x0D432A 0x06C727
10 Music Box 0x04522D 0x0B677F
18 Rock Organ 0x04965B 0x0481BE
39 Synth Bass 2 0x0AFD7D 0x02F8CA
55 Orchestra Hit 0x1808F5 0x14C707
62 SynthBrass 1 0x173B53 0x055D82
68 Oboe 0x0D675A 0x0D6836
76 Blown Bottle 0x0163EE 0x0B7AEB
77 Shakuhachi 0x03EBA1 0x1D9F8C
80 Lead 1 0x055A44 0x055D82
82 Lead 3 0x03EBA1 0x1AD9C6
84 Lead 5 0x0D5650 0x0A1AF9
88 Pad 1 0x0B677F 0x1B5C8A
91 Pad 4 0x1B5C8A 0x16FCCA
94 Pad 7 0x1AD9C6 0x0B06C2
100 FX 5 0x1AD9C6 0x0CFB16
101 FX 6 0x0B06C2 0x1E74B7
114 Steel Drums 0x0B582D 0x1EB938

8. 致谢

  • Ghidra SRE — 静态分析
  • GDB + Wine — 动态追踪验证
  • Yamaha S-YXG50 — 优秀的软件合成器
  • TBL解析工具
2 Likes

现在S-YXG50我觉得基本上应该是通了,那么接下来我应该考虑去解决S-YXG2006LE了

如果能将这个也解析清楚,那么Madaha最难的部分基本上已经过去了

效果器什么的,原理基本上差不太多,根据 XG Spec写就是了

1 Like

我不得不吐槽一下 S-YXG2006LE 的数据格式,搞了一个什么鬼的树结构……直接把AI干烧了

1 Like

逆向笔记:Yamaha S-YXG2006LE sxgbnw6l.tbl 文件格式

:warning: 注意:以下内容为AI生成,可能存在错误,我已经调教了很久,才达到现在这个效果

前言

Yamaha S-YXG2006LE 是 Yamaha 开发的 XGLite 标准 VST 合成器。其音色参数存储在 sxgbnw6l.tbl (716,608 B) 中,格式标记为 UTG VPRM。波形数据文件 (xgdat6l.tbl) 不在本文讨论范围。

方法

  • 静态分析: Ghidra 反编译 S-YXG2006LE.DLL (x86 PE, 基址 0x10000000)
  • 运行时验证: Linux + Wine, VST 宿主 MidiPlayer6, 通过 /proc/PID/mem 读取进程内存确认关键指针和数据结构

文件全局布局

偏移 大小 说明
0x0000 0x0018 头部 ASCII: "UTG VPRM 06 07 28 15 28\0"
0x0018 0x0028 段偏移表: 10×uint32, [7]=[9]=0xFFFFFFFF
0x0040 0x4000 Section 0: bank 映射表 byte[bankMSB×128+bankLSB] → bank_group
0x4040 0x5700 Section 1: 索引表 ushort[bank_group×128+prog] → sec2_idx
0x9740 0xBD8 Section 2: 描述符偏移表 uint32[754]
0xA308 0x28BE0 Section 2: 描述符数据区
0x32EE8 0x489A4 Section 3: 波形参数 (内部偏移表 + offset_table + 树节点 + per-note数据)
0x7B88C 0x00200 Section 4: keyzone 调制表 4×128B
0x7BA8C 0x21400 Section 5: velzone 调制表 1064×128B
0x9CE8C 0x09B64 Section 6: Seg5 波形参数表 1989×20B
0xA69F0 0x04550 Section 7: 鼓定义

段偏移表位于 params+0x18,DLL 通过 *(params+0x24)*(params+0x28) 等访问各段基址。

运行时验证: params 地址通过搜索进程内存中 "UTG VPRM" 签名定位,确认其内容与文件完全一致。

描述符 (乐器定义)

查找链

bankMSB + bankLSB → Sec0[bankMSB×128+bankLSB] = bank_group (byte, 0xFF=空)
  → Sec1[bank_group×128+prog] = sec2_idx (ushort, 0xFFFF=空)
    → Sec2[sec2_idx] = 描述符绝对文件偏移 (uint32)
      → 描述符数据

DLL 函数: sec1_sec2_instr_descriptor_lookup @ 0x1002C8B0。

描述符头部 (40B)

偏移 大小 说明
+0x00 uint16 固定 0x0000
+0x02 uint16 类型标记: 0x0001=TYPE0, 0x0002=TYPE1
+0x04 uint16 哨兵 0xFFFF
+0x06 uint16 drum_kit: 0xFFFF=旋律, 0-N=鼓
+0x08 uint16 固定 0xFFFF
+0x0A-0x0D 保留
+0x0E uint16 [7:0]=group_id, [15:8]=type_marker (0x31)
+0x10 uint16 [7:0]=flags, [15:8]=zone_count
+0x12 uint16 [7:0]=fine_tune, [15:8]=root_note
+0x14 uint16 [7:0]=param_type, [15:8]=volume
+0x16 uint16 [7:0]=pan, [15:8]=0x40
+0x18 uint32 哨兵
+0x1C uint16 EG_param
+0x1E-0x27 保留/固定值

ZoneBlock (16B)

紧接头部的 N 个 ZoneBlock,每 zone 一个:

偏移 大小 说明
+0x00 uint32 wave_id — 波形 ID
+0x04 uint8 keyzone_idx — Section 4 块索引
+0x05 uint8 velzone_idx — Section 5 块索引
+0x06 uint8 velMax
+0x07 uint8 flags
+0x08 int32 固定 0x000040000xFFFFFFFF
+0x0C uint32 哨兵 0xFFFFFFFF

Per-zone 额外数据 (16B/zone)

ZoneBlock 段之后紧接每 zone 的额外数据,这是 Seg5 索引的真正来源:

偏移 大小 说明
+0x00 uint32 (seg5_idx << 16) \| param_word
+0x04 uint32 固定 0x00004000
+0x08 uint32 哨兵 0xFFFFFFFF
+0x0C uint32 表引用

运行时验证: 读取进程内存中所有 141 个 waveform entries,确认 field14=params,seg5_get_type1_wid_based 使用 f1_lo = per-zone 高16位。126/128 程序 seg5_idx=383 (0x017F),余下 drum/effects 为 1 或 2。

Seg5 波形参数表 (Section 6)

位置: params + 0x9CE8C。1989 条 × 20B。

偏移 大小 说明
+0x00 uint32 loopStart — 循环起点
+0x04 uint32 negOffset — attack 长度
+0x08 uint32 alloc_size — PCM 段大小
+0x0C uint32 loopEnd — 循环长度
+0x10 uint32 flags: [7:0]=fine_tune, [15:8]=root_note

寻址公式

DLL 函数 seg5_get_type1_wid_based @ 0x1002C750:

int seg5_get_type1_wid_based(int entry, uint f1_lo) {
    if (-1 < (char)(f1_lo >> 8)) {
        // 路径 A: f1_lo 高字节 < 0x80
        return *(*(entry + 0x2c) + 0x30)   // offset_table[6] = 0x9CE8C
             + (f1_lo & 0xFFFF) * 0x14      // seg5_idx × 20
             + *(entry + 0x2c);             // + params
    }
    if ((f1_lo & 0xf000) == 0xc000) {
        // 路径 B: 未在标准音色中触发
        return *(*(entry + 0x34) + 0x243c) + (f1_lo & 0xfff) * 0x14;
    }
    // 路径 C: 未在标准音色中触发
    return *(*(entry + 0x24) + 0x10) + (f1_lo & 0x7fff) * 0x14 + *(entry + 0x24);
}

其中 entry+0x2c = field14 = params 基址。*(entry+0x2c) + 0x30 = *(params + 0x30) = 0x9CE8C。

简化:

Seg5[seg5_idx] 地址 = params + 0x9CE8C + seg5_idx × 20

逆向确认: 通过 DLL 反编译 + 运行时内存读取 Seg5[127] 和 Seg5[383] 验证偏移量公式。

Waveform entry (运行时内存结构)

stride 0x38 (56B), 共 141 个条目。waveform_table_init @ 0x1002B220 中创建。

+0x00..+0x23: 函数指针表 (9×uint32)
  +0x14: seg5_get_type1_wid_based
  +0x20: tree_vtab20_sec3_ptr_get
+0x24:        null (0)
+0x28:        filetype (uint32: 1 或 2)
+0x2c:        field14 = params_ptr

运行时验证: 进程内存中读取到 140 个 filetype=1 entries, 1 个 filetype=2, 全部 field14 = 同一个 params 地址。

Section 3 — 树遍历

Section 3 开头 40B = 10 个内部偏移:

ioff = [0x38D68, 0x38E18, 0x38E34, 0x38E50, 0x38E6C,
        0x38E88, 0x38EA4, 0x38EC0, 0x38F4C, 0x38F68]

Section 3+0x28 = offset_table[6048] (uint32)。每 wave_id 一条,两个用途:

  1. per-note block: data[sec3 + offset_table[wid] + note] — 取该 note 的 PCM block 号
  2. 树遍历入口

树节点 (28B)

Section 3 内部偏移指向的树节点:

+0x00: uint32 f0
+0x04: uint32 f1
+0x08: uint32 f2
+0x0C: uint32 f3 (byte[0] bit7 = 分支方向)
+0x10: 4B 左子节点 (大端序)
+0x14: uint32
+0x18: 4B 右子节点 (大端序)

遍历算法

tree_traverse_wave_id_to_seg5 @ 0x1002C320:

首次迭代: 入口节点地址直接使用
每次迭代:
  1. 方向: f3.byte[0] & 0x80 → 1=左子(+0x10), 0=右子(+0x1C)
  2. 子值: 4字节大端序 & 0x7FFFFFFF
  3. 叶子判断: 子值.byte[0] < 0x80 (有符号无符号均可)
     叶子 → 返回子值指针; 否则继续
  4. 解析下一节点:
     tree_vtab20_sec3_ptr_get @ 0x1002C910:
       bit30=0: params + *(uint32*)(params + sec3_off + 子值×4)
       bit30=1: *(entry+0x24) + *(*(entry+0x24)+4) + (子值&0x3FFFFFFF)×0xB0

逆向确认: DLL 代码反编译。但该遍历到 Seg5 索引的完整映射链路尚未在运行时验证。

Section 4/5 — 调制参数

Section 4: 4块 × 128B, Section 5: 1064块 × 128B。值范围 0-255,为连续调制值(非开关)。

示例: Grand Piano Zone 0 的 keyzone 从 note=0 的值 10 平稳递增到 note=127 的值 65。对应 Zone 的 velzone 从 vel=0 的值 126 平滑递减到 vel=127 的值 0。

精确 DSP 含义不确定: 所有 zone 在匹配的 note+velocity 下同时激活,keyzone/velzone 值控制音量/滤波/EG 参数。其具体算法需要分析渲染循环。

Zone 模型: 声音叠加

  • 一个乐器描述符有 N 个 ZoneBlock (Grand Piano = 16)
  • 对给定 (note, velocity),所有满足 kz[note] != 0 && vz[vel] != 0 的 zone 同时激活
  • Grand Piano note=60, vel=100: 10 个 zone 同时激活,各自有独立 wave_id
  • 各 zone 共用同一 seg5_idx (同组波形几何参数)
  • velzone 值平滑变化,无切换式门限 → 对应听感上的"仅音量变化"

确认: 通过文件数据分析 128 个程序的 per-zone seg5_idx 全部归一(383/1/2 三组),velzone 值呈连续分布。

当前实现的限制: parser 仅返回第一个非零 zone,未实现多 zone 叠加。

PCM 偏移

当 per-note block 值 block > 0 时:

PCM 样本偏移 = (block × 24092 + 11) // 22

block = 0 (波表模式) 时:

攻击段: sample_start = Seg5[seg5_idx].loopStart - Seg5[seg5_idx].negOffset
循环段: loop_start   = Seg5[seg5_idx].loopStart
         loop_end    = Seg5[seg5_idx].loopStart + Seg5[seg5_idx].loopEnd

:warning: 常数 24092 和 22 未在 DLL 代码中找到为字面常量,为经验推导。公式对所有 128 程序验证通过,但推导方式为猜测。这些常数可能来自编译器优化后的魔数乘法,或在波形解码阶段隐含确定。

验证总结

项目 状态 方法
段偏移表 确认 DLL 代码 + 运行时 params 读取
查找链 (Sec0→1→2→描述符) 确认 DLL 代码
描述符头部结构 确认 DLL 代码
ZoneBlock 结构 确认 DLL 代码 + 运行时验证
Per-zone 额外数据 确认 文件分析
Seg5 寻址公式 确认 DLL 代码 + 运行时 Seg5 读取
field14 = params 确认 运行时 waveform entry 遍历
Waveform entry 结构 确认 运行时内存读取
Zone 模型 (叠加) 确认 per-zone seg5_idx 一致性 + velzone 连续性
树遍历算法 逆向确认 DLL 代码, 未在运行时验证完整映射
keyzone/velzone DSP 含义 不确定 需进一步分析渲染循环
PCM 偏移公式常数 猜测 DLL 中未找到字面量
路径 B/C 未测试 标准 GM 音色未触发
Section 7 鼓定义 未分析
1 Like

我目前在尝试实现S-YXG50的功能了,2006LE 因为代码比较复杂,这个等后续处理

不过万幸的是,2006LE 有 mac 版本,甚至还带了调试符号……(Yamaha也有马大哈时刻)

2 Likes

报告一下进度

  1. S-YXG50 的读取部分完成了
  2. MIDI处理线程基本上完成了
  3. 正在实现 Tone Generator 部分,目前在实现 Oscillator 部分,这部分是产生声音的

官方文档里面对于该部分的描述:

%% Tone Generator 的结构图,这个是 Mermaid 语法
flowchart LR
    subgraph Main
      direction LR
      %% 音频串行链路 (主音频流)
      Osc[Oscillator] --> LPF
      LPF --> HPF
      HPF --> Amp[Amplifier]
      Amp --> EQ
      EQ --> Pan
    end
    Pan --> Out((Output))

    %% 可选模块组 (对应图中的 Option 括号)
    subgraph Opt1["Option"]
        direction TB
        CutoffHPF[Cutoff]
        CutoffHPF --> HPF
    end

    subgraph Opt2["Option"]
        direction TB
        EQC[EQ]
        EQC --> EQ
    end

    subgraph Params
       direction LR
       subgraph OscParams
           direction TB
           PEG["PEG<br>Key On<br>Initial Level<br>Attack Time<br>Release Time<br>Release Level<br>Key Off"]
           Port["Portamento"]
           PitchBend["Pitch Bend"]
           Pitch["Pitch"]
           Delay["Delay"]
       end

       %% 滤波控制信号 (Filter)
       subgraph LPFParams
       direction TB
       Cutoff["Cutoff"] --> LPF    
       FEG["FEG<br>Key On<br>Attack Time<br>Decay Time<br>Release Time<br>Key Off"] --> LPF
       Res["Resonance"] --> LPF
       end

       %% 音量控制信号 (Volume)
       subgraph AmpParams
       AEG["AEG<br>Key On<br>Attack Time<br>Decay Time<br>Sustain Level<br>Release Time<br>Key Off"]
       Volume["Volume"]
       end

       %% 声像控制信号 (Pan)
       subgraph PanParams
       PanCtrl["Pan"]
       end

       LFO["LFO (~)"] --> Delay
       LFO --> LPF
       LFO --> Amp
    end
    %% 音高控制信号汇聚 (Pitch)
    PEG --> Osc
    Port --> Osc
    PitchBend --> Osc
    Pitch --> Osc

    %% LFO 输出,分配到多个模块
    Delay --> Osc

    %% 音量控制信号 (Volume)
    AEG --> Amp
    Volume --> Amp

    %% 声像控制信号 (Pan)
    PanCtrl --> Pan
3 Likes

现在的进度

Tone Generator 我让 AI 帮我实现了(不知道实现效果如何)

接下来把完整的混音管线实现了,基本上就可以开始测试了

希望能成功

1 Like

在AI的帮助下,现在madaha可以播放钢琴曲了

控制器/RPN/NRPN等我还没测试,鼓和SFX我还完全没动,但是总体看起来是有希望了

我录了一段,可以听听 (48000Hz,快速Sinc插值)
https://pan.baidu.com/s/1HWaNGpda4p4trmK9z3fwag?pwd=2333

3 Likes

由于deepseek涨价,后续开发会慢下来了……不过目前还是能播放钢琴曲的

1 Like

其实你可以考虑看看mame的代码,mu50基本完全模拟了,4m完整版rom
mu50|690x327

1 Like

软件版的SYXG50和MU50真机实际上差的还是不少

加上本贴主要对SYG软件全部重构,或许用处不大(目测)

1 Like

mame的mu50值得参考的是:他们逆向了整个swp00的meg模块,所有的效果器已经全部是纯cpp代码实现而不是vliw解释器,此外,swp00的awm2对于采样的envelope等操作也值得借鉴

1 Like