aboutsummaryrefslogblamecommitdiff
path: root/srx_convert.py
blob: e7a98c143527408017976742197230cae04c6567 (plain) (tree)























































































































































































































































































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