add support for sol-feace/"wolfteam" format, generalize things a bit

This commit is contained in:
whut 2023-01-29 23:17:31 -06:00
parent 7f337c0bec
commit cb2cfddc68
2 changed files with 74 additions and 35 deletions

View File

@ -1,10 +1,12 @@
# hootvopm
This script converts a [Hoot](http://snesmusic.org/hoot/v2/) memory dump into an .opm instrument pack, suitable for the VOPM VST plugin. It's an extremely rudimentary way of extracting FM instruments from games, but it works.
This script was initially made to convert a [Hoot](http://snesmusic.org/hoot/v2/) memory dump into an .opm instrument pack, suitable for the VOPM VST plugin. It's an extremely rudimentary way of extracting FM instruments from games, but it works.
Over time, though, I've found myself slowly turning this into a general Yamaha 4op rip-o-tron.
## Usage
`./hootvopm.py [-b] -t chip_type input_file`
`./hootvopm.py [-b] -t format input_file`
Input file can also be `-` to read from stdin.
@ -12,12 +14,15 @@ Output is dumped straight to stdout; redirect it to a file if you want to save i
Pass -b to instead read straight binary data instead of comma-separated values.
### Currently-supported chip types
### Currently-supported chip types and formats
* OPM
* OPN
* OPNA (equivalent to OPN)
* Raw data in 32-byte groups (see below)
| 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") |
#### Raw data format
| Offset | Value |
@ -42,6 +47,7 @@ Rows marked with * are groups of 4 bytes, one per operator.
### Tips
* Most chips (read: all of the ones currently supported by this script) have a 256-byte register area, meaning you'll need to trim it down to 16 lines.
* From what I've seen, the chip's register area almost always resides on the first page, starting either at 0x0000 or 0x0100.
* This should be obvious, but for the formats that don't involve capturing the register area, you should pass `-b`.
## Known issues
* Since the script looks at a simple static memory dump, it has no way of capturing certain data, instead replacing it with placeholders. These include:

89
hootvopm.py Normal file → Executable file
View File

@ -31,8 +31,8 @@ DataFile = open(Filename,"r" + ("b" if BinMode else "")) if Filename != "-" else
# 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
# 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]
@ -69,6 +69,45 @@ def GetDT2D2R(b):
def GetD1LRR(b):
return (b & 0b11110000) >> 4, (b & 0b00001111)
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):
f = self.fields
slot = 120
pan = 64
ne = 0
for n in range(0, len(data) // self.recordSize):
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"))
PrintChannel(pan, fl, con, ams, pms, slot, ne)
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"))
PrintOp(op, ar, d1r, d2r, rr, d1l, tl, ks, mul, dt1, dt2, amsen)
def PrintOPM(data):
ne = (data[0xf] & 0b10000000) >> 7
slot = 120
@ -137,36 +176,30 @@ def PrintOPNA(data):
PrintOPNBasic(data)
PrintOPNBasic(data, True)
def PrintRaw(data):
slot = 120
pan = 64
ne = 0
for n in range(0, len(data) // 32):
ofs = n * 32
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+0x18])
ams, pms = GetAMSPMS(data[ofs+0x19])
PrintChannel(pan, fl, con, ams, pms, slot, ne)
for op in range(4):
get = lambda x: data[ofs + x + OpIdx[op]]
dt1, mul = GetDT1Mul(get(0x00))
tl = get(0x04)
ks, ar = GetKSAR(get(0x08))
amsen, d1r = GetAmsenD1R(get(0x0C))
dt2, d2r = GetDT2D2R(get(0x10))
d1l, rr = GetD1LRR(get(0x14))
PrintOp(op, ar, d1r, d2r, rr, d1l, tl, ks, mul, dt1, dt2, amsen)
TypeFuncs = {
"opn": PrintOPN,
"opna": PrintOPNA,
"opm": PrintOPM,
"raw": PrintRaw
"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)
}
Type = GetOption("-t")