organize instruments into objects instead of printing them immediately; finally move to argparse
This commit is contained in:
parent
cb2cfddc68
commit
9b0c060bcc
16
README.md
16
README.md
@ -16,13 +16,13 @@ Pass -b to instead read straight binary data instead of comma-separated values.
|
||||
|
||||
### Currently-supported chip types and formats
|
||||
|
||||
| ID | Description |
|
||||
|------------|----------------------------------------------------------------|
|
||||
| `opm` | OPM register area |
|
||||
| `opn` | OPN register area |
|
||||
| `opna` | OPNA register area (similar to OPN but has 3 more instruments) |
|
||||
| `raw` | Raw data in 32-byte groups; see table below |
|
||||
| `solfeace` | Format used in Sol-Feace for X68000 ("SOL.VCE") |
|
||||
| ID | Description |
|
||||
|-----------------|----------------------------------------------------------------|
|
||||
| `raw` (Default) | Raw data in 32-byte groups; see table below |
|
||||
| `opm` | OPM register area |
|
||||
| `opn` | OPN register area |
|
||||
| `opna` | OPNA register area (similar to OPN but has 3 more instruments) |
|
||||
| `solfeace` | Format used in Sol-Feace for X68000 ("SOL.VCE") |
|
||||
|
||||
#### Raw data format
|
||||
| Offset | Value |
|
||||
@ -38,6 +38,8 @@ Pass -b to instead read straight binary data instead of comma-separated values.
|
||||
|
||||
Rows marked with * are groups of 4 bytes, one per operator.
|
||||
|
||||
If you're curious, this is the format found in the PC-88VA version of Sorcerian, plus some padding bytes.
|
||||
|
||||
## Capturing memory in hoot
|
||||
|
||||
* Left- and right-clicking the "driver work" area flips between available memory pages. Scrolling the wheel in this area scrolls through the current page; you can also use Ctrl-(Up/Down/PgUp/PgDn) for this purpose.
|
||||
|
251
hootvopm.py
251
hootvopm.py
@ -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
|
||||
# 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 GetAMSPMS(b):
|
||||
return (b & 0b00110000) >> 4, (b & 0b00000111)
|
||||
def setDT1Mul(self, b):
|
||||
self.dt1, self.mul = (b & 0b01110000) >> 4, (b & 0b00001111)
|
||||
|
||||
def GetDT1Mul(b):
|
||||
return (b & 0b01110000) >> 4, (b & 0b00001111)
|
||||
def setKSAR(self, b):
|
||||
self.ks, self.ar = (b & 0b11000000) >> 6, (b & 0b00011111)
|
||||
|
||||
def GetKSAR(b):
|
||||
return (b & 0b11000000) >> 6, (b & 0b00011111)
|
||||
def setAmsenD1R(self, b):
|
||||
self.amsen, self.d1r = (b & 0b10000000) >> 7, (b & 0b00011111)
|
||||
|
||||
def GetAmsenD1R(b):
|
||||
return (b & 0b10000000) >> 7, (b & 0b00011111)
|
||||
def setDT2D2R(self, b):
|
||||
self.dt2, self.d2r = (b & 0b11000000) >> 6, (b & 0b00011111)
|
||||
|
||||
def GetDT2D2R(b):
|
||||
return (b & 0b11000000) >> 6, (b & 0b00011111)
|
||||
def setD1LRR(self, b):
|
||||
self.d1l, self.rr = (b & 0b11110000) >> 4, (b & 0b00001111)
|
||||
|
||||
def GetD1LRR(b):
|
||||
return (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
|
||||
@ -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])
|
||||
for o in range(4):
|
||||
op = inst.ops[o]
|
||||
get = lambda x: data[x + i + OpIdx[o]*8]
|
||||
|
||||
PrintChannel(pan, fl, con, ams, pms, slot, ne)
|
||||
|
||||
for op in range(4):
|
||||
get = lambda x: data[x + i + OpIdx[op]*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
|
||||
inst = Instrument(n, ("Ins%d" % n))
|
||||
|
||||
fl, con = GetFlCon(data[ofs + 0xb0 + i])
|
||||
inst.setFlCon(data[ofs + 0xb0 + i])
|
||||
inst.setAMSPMS(data[ofs + 0xb4 + i])
|
||||
|
||||
ams, pms = GetAMSPMS(data[ofs + 0xb4 + i])
|
||||
for o in range(4):
|
||||
op = inst.ops[o]
|
||||
get = lambda x: data[ofs + x + i + OpIdx[o]*4]
|
||||
|
||||
PrintChannel(pan, fl, con, ams, pms, slot, ne)
|
||||
op.getDT1Mul(get(0x30))
|
||||
op.tl = get(0x40)
|
||||
op.getKSAR(get(0x50))
|
||||
op.getAmsenD1R(get(0x60))
|
||||
op.getDT2D2R(get(0x70))
|
||||
op.getD1LRR(get(0x80))
|
||||
|
||||
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))
|
||||
|
||||
PrintOp(op, ar, d1r, d2r, rr, d1l, tl, ks, mul, dt1, dt2, amsen)
|
||||
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
|
||||
|
Loading…
Reference in New Issue
Block a user