# 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)