add support for sol-feace/"wolfteam" format, generalize things a bit
This commit is contained in:
parent
7f337c0bec
commit
cb2cfddc68
20
README.md
20
README.md
@ -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
89
hootvopm.py
Normal file → Executable 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")
|
||||
|
Loading…
Reference in New Issue
Block a user