|
|
@@ -1,46 +1,18 @@ |
|
|
|
#!/usr/bin/python3 |
|
|
|
import sys, getopt |
|
|
|
import argparse, collections, sys |
|
|
|
|
|
|
|
def Error(*args): |
|
|
|
print(sys.argv[0]+":", *args, file=sys.stderr) |
|
|
|
sys.exit(1) |
|
|
|
|
|
|
|
if len(sys.argv) > 1: |
|
|
|
Filename = sys.argv[1] |
|
|
|
|
|
|
|
Options, Args = getopt.getopt(sys.argv[1:], "bot:") |
|
|
|
|
|
|
|
def OptionExist(opt): |
|
|
|
global Options |
|
|
|
f = list(filter(lambda x: x[0] == opt, Options)) |
|
|
|
return len(f) >= 1 |
|
|
|
|
|
|
|
def GetOption(opt): |
|
|
|
global Options |
|
|
|
f = list(filter(lambda x: x[0] == opt, Options)) |
|
|
|
if len(f) == 0: return None |
|
|
|
else: return f[0][1] |
|
|
|
|
|
|
|
if len(Args) >= 1: |
|
|
|
Filename = Args[0] |
|
|
|
else: |
|
|
|
Error("no input file") |
|
|
|
|
|
|
|
BinMode = OptionExist("-b") |
|
|
|
DataFile = open(Filename,"r" + ("b" if BinMode else "")) if Filename != "-" else sys.stdin |
|
|
|
# MiOPMdrv sound bank Paramer Ver2002.04.22 |
|
|
|
# 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 |
|
|
|
Data = [] if not BinMode else DataFile.read() |
|
|
|
OpNames = ["M1","C1","M2","C2"] |
|
|
|
OpIdx = [0,2,1,3] |
|
|
|
|
|
|
|
if not BinMode: |
|
|
|
for line in DataFile.readlines(): |
|
|
|
for byte in line.strip().split(",")[:16]: |
|
|
|
Data.append(int(byte,16)) |
|
|
|
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) |
|
|
@@ -48,27 +20,61 @@ def PrintChannel(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) |
|
|
|
|
|
|
|
def GetFlCon(b): |
|
|
|
return (b & 0b00111000) >> 3, (b & 0b00000111) |
|
|
|
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 GetAMSPMS(b): |
|
|
|
return (b & 0b00110000) >> 4, (b & 0b00000111) |
|
|
|
def setKSAR(self, b): |
|
|
|
self.ks, self.ar = (b & 0b11000000) >> 6, (b & 0b00011111) |
|
|
|
|
|
|
|
def GetDT1Mul(b): |
|
|
|
return (b & 0b01110000) >> 4, (b & 0b00001111) |
|
|
|
def setAmsenD1R(self, b): |
|
|
|
self.amsen, self.d1r = (b & 0b10000000) >> 7, (b & 0b00011111) |
|
|
|
|
|
|
|
def GetKSAR(b): |
|
|
|
return (b & 0b11000000) >> 6, (b & 0b00011111) |
|
|
|
def setDT2D2R(self, b): |
|
|
|
self.dt2, self.d2r = (b & 0b11000000) >> 6, (b & 0b00011111) |
|
|
|
|
|
|
|
def GetAmsenD1R(b): |
|
|
|
return (b & 0b10000000) >> 7, (b & 0b00011111) |
|
|
|
def setD1LRR(self, b): |
|
|
|
self.d1l, self.rr = (b & 0b11110000) >> 4, (b & 0b00001111) |
|
|
|
|
|
|
|
def GetDT2D2R(b): |
|
|
|
return (b & 0b11000000) >> 6, (b & 0b00011111) |
|
|
|
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) |
|
|
|
|
|
|
|
def GetD1LRR(b): |
|
|
|
return (b & 0b11110000) >> 4, (b & 0b00001111) |
|
|
|
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 |
|
|
@@ -78,96 +84,84 @@ class RecordGrabber: |
|
|
|
setattr(self, k, v) |
|
|
|
|
|
|
|
def __call__(self,data): |
|
|
|
result = [] |
|
|
|
f = self.fields |
|
|
|
slot = 120 |
|
|
|
pan = 64 |
|
|
|
ne = 0 |
|
|
|
|
|
|
|
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 |
|
|
|
print("@:%d %s" % (n, ("Ins%d" % n) if "name" not in f else |
|
|
|
f["name"](data, ofs))) |
|
|
|
print("LFO: 0 0 0 0 0") # the DRY violations will end! ...soon |
|
|
|
|
|
|
|
get = lambda s: data[ofs + f[s]] if s in f else 0 |
|
|
|
|
|
|
|
fl, con = GetFlCon(get("flcon")) |
|
|
|
ams, pms = GetAMSPMS(get("amspms")) |
|
|
|
inst.setFlCon(get("flcon")) |
|
|
|
inst.setAMSPMS(get("amspms")) |
|
|
|
|
|
|
|
PrintChannel(pan, fl, con, ams, pms, slot, ne) |
|
|
|
for o in range(4): |
|
|
|
getOp = lambda s: data[ofs + f[s] + OpIdx[o]] if s in f else 0 |
|
|
|
op = inst.ops[o] |
|
|
|
|
|
|
|
for op in range(4): |
|
|
|
getOp = lambda s: data[ofs + f[s] + OpIdx[op]] if s in f else 0 |
|
|
|
dt1, mul = GetDT1Mul(getOp("dt1mul")) |
|
|
|
tl = getOp("tl") |
|
|
|
ks, ar = GetKSAR(getOp("ksar")) |
|
|
|
amsen, d1r = GetAmsenD1R(getOp("amsend1r")) |
|
|
|
dt2, d2r = GetDT2D2R(getOp("dt2d2r")) |
|
|
|
d1l, rr = GetD1LRR(getOp("d1lrr")) |
|
|
|
op.setDT1Mul(getOp("dt1mul")) |
|
|
|
op.tl = getOp("tl") |
|
|
|
op.setKSAR(getOp("ksar")) |
|
|
|
op.setAmsenD1R(getOp("amsend1r")) |
|
|
|
op.setDT2D2R(getOp("dt2d2r")) |
|
|
|
op.setD1LRR(getOp("d1lrr")) |
|
|
|
|
|
|
|
PrintOp(op, ar, d1r, d2r, rr, d1l, tl, ks, mul, dt1, dt2, amsen) |
|
|
|
#inst.render() |
|
|
|
result.append(inst) |
|
|
|
#print(inst.__hash__()) |
|
|
|
return result |
|
|
|
|
|
|
|
|
|
|
|
def PrintOPM(data): |
|
|
|
ne = (data[0xf] & 0b10000000) >> 7 |
|
|
|
slot = 120 |
|
|
|
pan = 64 |
|
|
|
result = [] |
|
|
|
for i in range(8): |
|
|
|
print("@:%d Ins%d" % (i, i)) |
|
|
|
|
|
|
|
print("LFO: 0 0 0 0 0") # no way to get this data... at least for now |
|
|
|
inst = Instrument(i, ("Ins%d" % i)) |
|
|
|
inst.ne = (data[0xf] & 0b10000000) >> 7 |
|
|
|
|
|
|
|
meta = data[0x20 + i : 0x3f + i : 8] |
|
|
|
|
|
|
|
fl, con = GetFlCon(meta[0]) |
|
|
|
inst.setFlCon(meta[0]) |
|
|
|
inst.setAMSPMS(meta[3]) |
|
|
|
|
|
|
|
pms, ams = GetAMSPMS(meta[3]) |
|
|
|
|
|
|
|
PrintChannel(pan, fl, con, ams, pms, slot, ne) |
|
|
|
|
|
|
|
for op in range(4): |
|
|
|
get = lambda x: data[x + i + OpIdx[op]*8] |
|
|
|
for o in range(4): |
|
|
|
op = inst.ops[o] |
|
|
|
get = lambda x: data[x + i + OpIdx[o]*8] |
|
|
|
|
|
|
|
dt1, mul = GetDT1Mul(get(0x40)) |
|
|
|
tl = get(0x60) |
|
|
|
ks, ar = GetKSAR(get(0x80)) |
|
|
|
amsen, d1r = GetAmsenD1R(get(0xa0)) |
|
|
|
dt2, d2r = GetDT2D2R(get(0xc0)) |
|
|
|
d1l, rr = GetD1LRR(get(0xe0)) |
|
|
|
|
|
|
|
PrintOp(op, ar, d1r, d2r, rr, d1l, tl, ks, mul, dt1, dt2, amsen) |
|
|
|
|
|
|
|
print() |
|
|
|
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): |
|
|
|
slot = 120 |
|
|
|
pan = 64 |
|
|
|
ne = 0 # either i'm blind or opn has no noise enable bit (not that i care either way) |
|
|
|
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) |
|
|
|
print("@:%d Ins%d" % (n, n)) |
|
|
|
print("LFO: 0 0 0 0 0") # no way to get this data... at least for now |
|
|
|
|
|
|
|
fl, con = GetFlCon(data[ofs + 0xb0 + i]) |
|
|
|
|
|
|
|
ams, pms = GetAMSPMS(data[ofs + 0xb4 + i]) |
|
|
|
|
|
|
|
PrintChannel(pan, fl, con, ams, pms, slot, ne) |
|
|
|
|
|
|
|
for op in range(4): |
|
|
|
get = lambda x: data[ofs + x + i + OpIdx[op]*4] |
|
|
|
|
|
|
|
dt1, mul = GetDT1Mul(get(0x30)) |
|
|
|
tl = get(0x40) |
|
|
|
ks, ar = GetKSAR(get(0x50)) |
|
|
|
amsen, d1r = GetAmsenD1R(get(0x60)) |
|
|
|
dt2, d2r = GetDT2D2R(get(0x70)) |
|
|
|
d1l, rr = GetD1LRR(get(0x80)) |
|
|
|
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] |
|
|
|
|
|
|
|
PrintOp(op, ar, d1r, d2r, rr, d1l, tl, ks, mul, dt1, dt2, amsen) |
|
|
|
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) |
|
|
@@ -202,12 +196,39 @@ TypeFuncs = { |
|
|
|
}, 0x20) |
|
|
|
} |
|
|
|
|
|
|
|
Type = GetOption("-t") |
|
|
|
if Type==None: |
|
|
|
Error("no chip type specified") |
|
|
|
if Type not in TypeFuncs: |
|
|
|
Error(Type+":","unknown chip type") |
|
|
|
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:",Type) |
|
|
|
TypeFuncs[Type.lower()](Data) |
|
|
|
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 |