From aefdc8d0edad5d77d73ac40d8002363e12da00da Mon Sep 17 00:00:00 2001 From: Chris Xiong Date: Tue, 9 Aug 2022 02:56:10 -0400 Subject: fures, ostendite se! --- srx_convert.py | 280 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 280 insertions(+) create mode 100644 srx_convert.py (limited to 'srx_convert.py') 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) -- cgit v1.2.3