aboutsummaryrefslogtreecommitdiff
path: root/srx_convert.py
diff options
context:
space:
mode:
Diffstat (limited to 'srx_convert.py')
-rw-r--r--srx_convert.py280
1 files changed, 280 insertions, 0 deletions
diff --git a/srx_convert.py b/srx_convert.py
new file mode 100644
index 0000000..e7a98c1
--- /dev/null
+++ b/srx_convert.py
@@ -0,0 +1,280 @@
+# Part of the Fifteen-Thieves Project
+# Chris Xiong 2022
+# License: Expat (MIT)
+
+import time
+import math
+import sys
+from glob import glob
+import xv.amap
+import xv.util
+import os
+
+inst_sets = [(96,"classical"),(97,"contemporary"),(98,"solo"),(99,"enhanced"),(80,"special_1"),(81,"special_2")]
+rhyt_sets = [(104,"classical"),(105,"contemporary"),(106,"solo"),(107,"enhanced")]
+mfx_map = [ 0, 1, 35, 36, -1, # 0
+ -1, 7, 8, 21, -1, # 5
+ -1, 26, 27, 28, 23, # 10
+ 24, 25, 43, 46, 47, # 15
+ 48, 53, 62, 61, 64, # 20
+ 65, 66, 67, 68, 69, # 25
+ 70, 71, 72, 73, 74, # 30
+ 75, 76, 77, -1, -1, # 35
+ -1, 11, -1, -1, 15, # 40
+ 49, 50, 51, 52, -1, # 45
+ 57, 56, 10, 37, 38, # 50
+ 40, 41, 42, 20, 3, # 55
+ 29, 30, 17, 18, -1, # 60
+ -1, 9, -1, -1, -1, # 65
+ -1, 22, -1, -1, -1, # 70
+ -1, -1, -1, 39, -1, # 75
+ -1, -1, -1, -1, -1, # 80
+ -1, -1, -1, 2, -1, # 85
+ -1] # 90
+mfx_offsets = [
+ 0x0000, 0x48c3, 0x48f3, 0x491f, 0x494b, # 0
+ 0x4967, 0x499f, 0x4a07, 0x4a1f, 0x4a57, # 5
+ 0x4a97, 0x4aaf, 0x4ae7, 0x4b2b, 0x4b5f, # 10
+ 0x4b83, 0x4ba3, 0x4c07, 0x4c2b, 0x4c4f, # 15
+ 0x4caf, 0x4d17, 0x4d47, 0x4d8b, 0x4dbf, # 20
+ 0x4df7, 0x4e3b, 0x4e67, 0x4e9b, 0x4ec7, # 25
+ 0x4eff, 0x4f3b, 0x4f83, 0x4fc3, 0x500b, # 30
+ 0x506b, 0x5087, 0x50a3, 0x50c7, 0x50eb, # 35
+ 0x5133, 0x514f, 0x516f, 0x518f, 0x51cf, # 40
+ 0x51ff, 0x523f, 0x528b, 0x52d7, 0x5333, # 45
+ 0x539f, 0x540f, 0x544f, 0x549f, 0x54cb, # 50
+ 0x54fb, 0x5537, 0x557b, 0x559f, 0x55c7, # 55
+ 0x55db, 0x5613, 0x563f, 0x5693, 0x570f, # 60
+ 0x5733, 0x5753, 0x577b, 0x57a7, 0x57cf, # 65
+ 0x57f7, 0x5823, 0x584b, 0x5873, 0x589f, # 70
+ 0x58c7, 0x58ff, 0x593b, 0x5977, 0x59c3 # 75
+]
+# off chorus delay gm2 end
+chorus_offsets = [0x0000, 0x59c3, 0x59e7, 0x5a1f, 0x5a3b]
+# off reverb s.room s.hall s.plat gm2 end
+reverb_offsets = [0x0000, 0x5a3b, 0x5a4b, 0x5a73, 0x5a9b, 0x5ac3, 0x5ad7]
+
+#srx-09 is NEX04, srx-07 is NEX02
+
+headers = [
+ b"",
+ b"",
+ b"KoaBankFile00003PG-NEX09", # Piano 1 / Concert Piano
+ b"KoaBankFile00003PG-NEX05", # Studio / Studio SRX
+ b"KoaBankFile00003PG-NEX07", # Strings / Symphonique Strings
+ b"KoaBankFile00003PG-NEX03", # Dance Trax / Supreme Dance + Platinum Trax
+ b"KoaBankFile00003PG-NEX01", # Orchestra / Complete Orchestra
+ b"KoaBankFile00003PG-NEX02", # Keyboards / Ultimate Keys
+ b"KoaBankFile00003PG-NEX03", # Dance Trax / Supreme Dance + Platinum Trax
+ b"KoaBankFile00003PG-NEX04", # World / World Collection
+ b"KoaBankFile00003PG-NEX08", # Brass / Big Brass Ensemble
+ b"KoaBankFile00003PG-NEX10", # Piano 2 / Complete Piano
+ b"KoaBankFile00003PG-NEX06", # Electric Piano / Classic EPs
+ ]
+
+header = b"KoaBankFile00003PG-NEX02"
+template = b""
+amap = xv.amap.AddrMapTemplate("amaps/sd-80.amap")
+tree = xv.amap.create_addr_map_tree(amap)
+tree.data[:] = [0xff for i in range(0, len(tree.data))]
+
+def load_memregion(path):
+ mr = xv.util.load_memoryregion(path)
+ for addr, data in mr:
+ ra = xv.util.b7belist2int(addr)
+ tree.data[ra:ra + len(data)] = data
+
+def clip_or_pad(b, l):
+ if len(b) < l:
+ return b + b'\00' * (l - len(b))
+ else: return b[0:l]
+
+def convert_patch(iset, bk, pc):
+ ret = bytearray(template)
+ name = tree.value("sd_root.part[0].patch.pc_common.name")
+ ret[0:12] = name
+
+ common = tree.value("sd_root.part[0].patch.pc_common")
+ ret[0x54 : 0x54 + len(common)] = common
+
+ patch_common = tree.find_node("sd_root.part[0].patch.pc_common")
+ do_mod_lfo = patch_common.value("vibrato_pitch") != 64 or \
+ patch_common.value("vibrato_cutoff") != 64 or \
+ patch_common.value("vibrato_amp") != 64
+
+ tmt = tree.value("sd_root.part[0].patch.tmt")
+ ret[0x1dc : 0x1dc + len(tmt)] = tmt
+
+ tone_offsets = [0x205, 0x29f, 0x339, 0x3d3]
+ for t in range(0, 4):
+ tree.set_value(f"sd_root.part[0].patch.tone[{t}].tn_wave.wave_group_type", 1)
+ if do_mod_lfo:
+ lfo = []
+ lfo.append(tree.find_node(f"sd_root.part[0].patch.tone[{t}].tn_lfo[0]"))
+ lfo.append(tree.find_node(f"sd_root.part[0].patch.tone[{t}].tn_lfo[1]"))
+ dst_lfo = -1
+ for i in range(0, 2):
+ if lfo[i].value("pitch_depth") == 64 and \
+ lfo[i].value("filter_depth") == 64 and \
+ lfo[i].value("amp_depth") == 64 and \
+ lfo[i].value("pan_depth") == 64:
+ dst_lfo = i
+ break
+ if dst_lfo == -1:
+ print(f"WARNING ({iset}, {bk}, {pc}): cannot relocate modulation LFO for tone {t}")
+ else:
+ lfo[dst_lfo].set_value("waveform", patch_common.value("vibrato_lfo_waveform"))
+ lfo[dst_lfo].set_value("rate", patch_common.value("vibrato_rate"))
+ lfo[dst_lfo].set_value("offset", 2)
+ lfo[dst_lfo].set_value("rate_detune", 0)
+ lfo[dst_lfo].set_value("delay", patch_common.value("vibrato_delay"))
+ lfo[dst_lfo].set_value("delay_keyfollow", 64)
+ lfo[dst_lfo].set_value("fade_mode", 0)
+ lfo[dst_lfo].set_value("fade_time", patch_common.value("vibrato_attack"))
+ lfo[dst_lfo].set_value("key_trigger", 0)
+ lfo[dst_lfo].set_value("pitch_depth", patch_common.value("vibrato_pitch"))
+ lfo[dst_lfo].set_value("filter_depth", patch_common.value("vibrato_cutoff"))
+ lfo[dst_lfo].set_value("amp_depth", patch_common.value("vibrato_amp"))
+ lfo[dst_lfo].set_value("pan_depth", 64)
+ tone = tree.value(f"sd_root.part[0].patch.tone[{t}]")
+ ret[tone_offsets[t] : tone_offsets[t] + len(tone)] = tone
+
+ mfx = tree.value("sd_root.part[0].patch.mfx")
+ mfx_type = tree.value("sd_root.part[0].patch.mfx.type")
+ ret[0xa4 : 0xa4 + 13] = mfx[0:13]
+ mapped_mfx = mfx_map[tree.value("sd_root.part[0].patch.mfx.type")]
+ if mapped_mfx == -1:
+ print(f"WARNING ({iset}, {bk}, {pc}): no valid MFX mapped (XV MFX #{mfx_type})")
+ else:
+ ret[0xa4] = mapped_mfx
+ if mapped_mfx != 0:
+ ret[mfx_offsets[mapped_mfx] : mfx_offsets[mapped_mfx + 1]] = mfx[13 : 13 + mfx_offsets[mapped_mfx + 1] - mfx_offsets[mapped_mfx]]
+
+ chorus = tree.value("sd_root.part[0].patch.chorus")
+ chorus_type = tree.value("sd_root.part[0].patch.chorus.type")
+ ret[0x135 : 0x135 + 4] = chorus[0:4]
+ if chorus_type != 0:
+ ret[chorus_offsets[chorus_type] : chorus_offsets[chorus_type + 1]] = clip_or_pad(chorus[4:], chorus_offsets[chorus_type + 1] - chorus_offsets[chorus_type])
+
+ reverb = tree.value("sd_root.part[0].patch.reverb")
+ reverb_type = tree.value("sd_root.part[0].patch.reverb.type")
+ ret[0x189 : 0x189 + 3] = reverb[0:3]
+ if reverb_type != 0:
+ ret[reverb_offsets[reverb_type] : reverb_offsets[reverb_type + 1]] = reverb[3 : 3 + reverb_offsets[reverb_type + 1] - reverb_offsets[reverb_type]]
+
+ if len(ret) != 0x5ad7:
+ print(f"ERROR: ({iset}, {bk}, {pc}) messed up: {len(ret):x}!!! {chorus_type}")
+ return ret
+
+def convert_rhythm(rset, pc):
+ ret = bytearray(template)
+ name = tree.value("sd_root.part[0].rhythm.ry_common.name")
+ ret[0:12] = name
+ ret[0x0c] = 1
+
+ common = tree.value("sd_root.part[0].rhythm.ry_common")
+ ret[0x46d : 0x46d + len(common)] = common
+
+ for key in range(0, 88):
+ for tone in range(0, 4):
+ tree.set_value(f"sd_root.part[0].rhythm.rtone[{key}].rt_wmt[{tone}].wave_group_type", 1)
+ rtone = tree.value(f"sd_root.part[0].rhythm.rtone[{key}]")
+ ret[0x5b7 + 0xc3 * key : 0x5b7 + 0xc3 * key + len(rtone)] = rtone
+
+ mfx = tree.value("sd_root.part[0].rhythm.mfx")
+ mfx_type = tree.value("sd_root.part[0].rhythm.mfx.type")
+ ret[0x47f : 0x47f + 13] = mfx[0:13]
+ mapped_mfx = mfx_map[tree.value("sd_root.part[0].rhythm.mfx.type")]
+ if mapped_mfx == -1:
+ print(f"WARNING ({rset}, {pc}): no valid MFX mapped (XV MFX #{mfx_type})")
+ else:
+ ret[0xa4] = mapped_mfx
+ if mapped_mfx != 0:
+ ret[mfx_offsets[mapped_mfx] : mfx_offsets[mapped_mfx + 1]] = mfx[13 : 13 + mfx_offsets[mapped_mfx + 1] - mfx_offsets[mapped_mfx]]
+
+ chorus = tree.value("sd_root.part[0].rhythm.chorus")
+ chorus_type = tree.value("sd_root.part[0].rhythm.chorus.type")
+ ret[0x510 : 0x510 + 4] = chorus[0:4]
+ if chorus_type != 0:
+ ret[chorus_offsets[chorus_type] : chorus_offsets[chorus_type + 1]] = clip_or_pad(chorus[4:], chorus_offsets[chorus_type + 1] - chorus_offsets[chorus_type])
+
+ reverb = tree.value("sd_root.part[0].rhythm.reverb")
+ reverb_type = tree.value("sd_root.part[0].rhythm.reverb.type")
+ ret[0x564 : 0x564 + 3] = reverb[0:3]
+ if reverb_type != 0:
+ ret[reverb_offsets[reverb_type] : reverb_offsets[reverb_type + 1]] = reverb[3 : 3 + reverb_offsets[reverb_type + 1] - reverb_offsets[reverb_type]]
+
+ if len(ret) != 0x5ad7:
+ print(f"ERROR: ({iset}, {bk}, {pc}) messed up: {len(ret):x}!!!")
+ return ret
+
+def write_bank(fn, b):
+ if len(b) != 0x2d6b80:
+ print(f"ERROR: Invalid bank file: {len(b):x}")
+ return
+ with open(fn, "wb") as f:
+ f.write(header)
+ f.write(b)
+
+if __name__ == "__main__":
+ try:
+ os.mkdir("data/srxsdpresets")
+ except Exception: pass
+ try:
+ os.mkdir("data/srxsdpresets/by_order")
+ except Exception: pass
+ try:
+ os.mkdir("data/srxsdpresets/by_set")
+ except Exception: pass
+
+ if len(sys.argv) > 1:
+ if int(sys.argv[1]) >= 2 and int(sys.argv[1]) <= 12:
+ header = headers[int(sys.argv[1])]
+
+ gm2pcb = []
+ gm2rhy = []
+ with open("data/gm2pcbanks", "r") as f:
+ gm2pcb = [int(x.strip(), 16) for x in f.readline().split(',')]
+ gm2rhy = [int(x.strip()) for x in f.readline().split(',')]
+ with open("data/srx_vsti_preset_template", "rb") as f:
+ template = f.read()
+
+ cur_prs = [bytearray() for i in range(0, 10)]
+ for iset in inst_sets:
+ patchdata = [bytearray(), bytearray()]
+ c = 0
+ for pc in range(0, 128):
+ for bk in range(0, 1 if iset[0] < 96 else gm2pcb[pc]):
+ fn = glob(f"data/patchparam/{iset[1]}/{bk:02d}-{pc:03d}-*.memoryregion")[0]
+ load_memregion(fn)
+ p = convert_patch(iset[0], bk, pc)
+ patchdata[int(bk > 0)] += p
+ if iset[0] < 96:
+ cur_prs[iset[0] - 80 + 8] += p
+ else:
+ cur_prs[c // 32] += p
+ c += 1
+ for bank in range(0, 1 + int(iset[0] >= 96)):
+ typ = "variation" if bank else "capital"
+ fn = f"data/srxsdpresets/by_set/{iset[1]}_{typ}.bin"
+ if iset[0] < 96:
+ fn = f"data/srxsdpresets/by_set/{iset[1]}.bin"
+ write_bank(fn, patchdata[bank])
+ for i, b in enumerate(cur_prs):
+ write_bank(f"data/srxsdpresets/by_order/PR-{chr(ord('A') + i)}.bin", b)
+
+ rhy_prs = bytearray()
+ for rset in rhyt_sets:
+ rhydata = bytearray()
+ for pc in range(0, 128):
+ if pc in gm2rhy:
+ fn = glob(f"data/rhythmparam/{rset[1]}/{pc:02d}-*.memoryregion")[0]
+ load_memregion(fn)
+ p = convert_rhythm(rset[0], pc)
+ rhydata += p
+ rhy_prs += p
+ else: rhydata += template
+ rhy_prs += 23 * template
+ fn = f"data/srxsdpresets/by_set/{rset[1]}_rhythm.bin"
+ write_bank(fn, rhydata)
+ write_bank(f"data/srxsdpresets/by_order/PR-K.bin", rhy_prs)