#!/usr/bin/python3 import argparse, collections, sys # MiOPMdrv sound bank Paramer Ver2002.04.22 # LFO: LFRQ AMD PMD WF NFRQ # @:[Num] [Name] # CH: PAN FL CON AMS PMS SLOT NE # [OPname]: AR D1R D2R RR D1L TL KS MUL DT1 DT2 AMS-EN OpNames = ["M1","C1","M2","C2"] OpIdx = [0,2,1,3] def Error(*args): print(sys.argv[0]+":", *args, file=sys.stderr) sys.exit(1) def PrintChannel(pan, fl, con, ams, pms, slot, ne): print("CH:", pan, fl, con, ams, pms, slot, ne) def PrintOp(op, ar, d1r, d2r, rr, d1l, tl, ks, mul, dt1, dt2, amsen): print(OpNames[op] + ":", ar, d1r, d2r, rr, d1l, tl, ks, mul, dt1, dt2, amsen) class Operator: def __init__(self, num): self.num = num for n in "ar d1r d2r rr d1l tl ks mul dt1 dt2 amsen".split(): setattr(self, n, 0) def setDT1Mul(self, b): self.dt1, self.mul = (b & 0b01110000) >> 4, (b & 0b00001111) def setKSAR(self, b): self.ks, self.ar = (b & 0b11000000) >> 6, (b & 0b00011111) def setAmsenD1R(self, b): self.amsen, self.d1r = (b & 0b10000000) >> 7, (b & 0b00011111) def setDT2D2R(self, b): self.dt2, self.d2r = (b & 0b11000000) >> 6, (b & 0b00011111) def setD1LRR(self, b): self.d1l, self.rr = (b & 0b11110000) >> 4, (b & 0b00001111) class Instrument: def __init__(self, num, name): self.num = num self.name = name self.slot = 120 self.pan = 64 self.ne = 0 for n in "fl con ams pms".split(): setattr(self, n, 0) self.ops = [Operator(i) for i in range(4)] def render(self): print("@:%d %s" % (self.num, self.name)) print("LFO: 0 0 0 0 0") PrintChannel(self.pan, self.fl, self.con, self.ams, self.pms, self.slot, self.ne) for o in self.ops: PrintOp(o.num, o.ar, o.d1r, o.d2r, o.rr, o.d1l, o.tl, o.ks, o.mul, o.dt1, o.dt2, o.amsen) def setFlCon(self, b): self.fl, self.con = (b & 0b00111000) >> 3, (b & 0b00000111) def setAMSPMS(self, b): self.ams, self.pms = (b & 0b00110000) >> 4, (b & 0b00000111) # """ def __hash__(self): get = lambda n, d = 1: [getattr(self.ops[x], n) // d for x in range(4)] data = bytes([self.con] + get("tl", 4)) #print(data, data.__hash__()) return data.__hash__() def __eq__(self, other): return self.__hash__() == other.__hash__() # """ class RecordGrabber: def __init__(self, fields, recordSize, **options): self.fields = fields self.recordSize = recordSize self.instStep = 1 for k, v in options: setattr(self, k, v) def __call__(self,data): result = [] f = self.fields for n in range(0, len(data) // self.recordSize): inst = Instrument(n, ("Ins%d" % n) if "name" not in f else f["name"](data, ofs)) ofs = n * self.recordSize get = lambda s: data[ofs + f[s]] if s in f else 0 inst.setFlCon(get("flcon")) inst.setAMSPMS(get("amspms")) for o in range(4): getOp = lambda s: data[ofs + f[s] + OpIdx[o]] if s in f else 0 op = inst.ops[o] op.setDT1Mul(getOp("dt1mul")) op.tl = getOp("tl") op.setKSAR(getOp("ksar")) op.setAmsenD1R(getOp("amsend1r")) op.setDT2D2R(getOp("dt2d2r")) op.setD1LRR(getOp("d1lrr")) #inst.render() result.append(inst) #print(inst.__hash__()) return result def PrintOPM(data): result = [] for i in range(8): inst = Instrument(i, ("Ins%d" % i)) inst.ne = (data[0xf] & 0b10000000) >> 7 meta = data[0x20 + i : 0x3f + i : 8] inst.setFlCon(meta[0]) inst.setAMSPMS(meta[3]) for o in range(4): op = inst.ops[o] get = lambda x: data[x + i + OpIdx[o]*8] op.setDT1Mul(get(0x40)) op.tl = get(0x60) op.setKSAR(get(0x80)) op.setAmsenD1R(get(0xa0)) op.setDT2D2R(get(0xc0)) op.setD1LRR(get(0xe0)) return result def PrintOPNBasic(data, opna = False): result = [] # SR in opn parlance is equivalent to D2R in opm, thanks yamaha # likewise, SL -> D1L and DR -> D1R ofs = 0x100 if opna else 0 for i in range(3): n = i + (3 if opna else 0) inst = Instrument(n, ("Ins%d" % n)) inst.setFlCon(data[ofs + 0xb0 + i]) inst.setAMSPMS(data[ofs + 0xb4 + i]) for o in range(4): op = inst.ops[o] get = lambda x: data[ofs + x + i + OpIdx[o]*4] op.getDT1Mul(get(0x30)) op.tl = get(0x40) op.getKSAR(get(0x50)) op.getAmsenD1R(get(0x60)) op.getDT2D2R(get(0x70)) op.getD1LRR(get(0x80)) result.append(inst) return result def PrintOPN(data): PrintOPNBasic(data) def PrintOPNA(data): PrintOPNBasic(data) PrintOPNBasic(data, True) TypeFuncs = { "opn": PrintOPN, "opna": PrintOPNA, "opm": PrintOPM, "raw": RecordGrabber({ "flcon": 0x18, "amspms": 0x19, "dt1mul": 0x0, "tl": 0x04, "ksar": 0x08, "amsend1r": 0x0c, "dt2d2r": 0x10, "d1lrr": 0x14 }, 0x20), "solfeace": RecordGrabber({ "flcon": 0x00, "dt1mul": 0x02, "tl": 0x06, "ksar": 0x0a, "amsend1r": 0x0e, "dt2d2r": 0x12, "d1lrr": 0x16, "name": lambda data, ofs: data[ofs + 0x1a : ofs + 0x20].decode("shift-jis") }, 0x20) } Parser = argparse.ArgumentParser( prog="hootvopm", description="Convert Yamaha OPM/OPN data to VOPM instruments") Parser.add_argument("file", help="file to read (- for stdin)") Parser.add_argument("-b", "--binary", dest="binary", action="store_true", help="read binary output instead of comma-separated hex") Parser.add_argument("-t", "--type", dest="type", action="store", default="raw", choices=TypeFuncs.keys(), help="chip/format type (default: raw)") Args = Parser.parse_args() Filename = Args.file DataFile = open(Filename,"r" + ("b" if Args.binary else "")) if Filename != "-" else sys.stdin Data = [] if not Args.binary else DataFile.read() if not Args.binary: for line in DataFile.readlines(): for byte in line.strip().split(",")[:16]: Data.append(int(byte,16)) print("// Exported by hootvopm : https://git.lain.church/whut/hootvopm") print("// Chip type:", Args.type) Instruments = set(TypeFuncs[Args.type.lower()](Data)) #print(len(Instruments)) Instruments = sorted(list(Instruments), key = lambda x: x.num) count = 0 for i in Instruments: i.num = count i.render() count += 1