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
|
# 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
|
## 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.
|
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.
|
Pass -b to instead read straight binary data instead of comma-separated values.
|
||||||
|
|
||||||
### Currently-supported chip types
|
### Currently-supported chip types and formats
|
||||||
|
|
||||||
* OPM
|
| ID | Description |
|
||||||
* OPN
|
|------------|----------------------------------------------------------------|
|
||||||
* OPNA (equivalent to OPN)
|
| `opm` | OPM register area |
|
||||||
* Raw data in 32-byte groups (see below)
|
| `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
|
#### Raw data format
|
||||||
| Offset | Value |
|
| Offset | Value |
|
||||||
@ -42,6 +47,7 @@ Rows marked with * are groups of 4 bytes, one per operator.
|
|||||||
### Tips
|
### 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.
|
* 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.
|
* 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
|
## 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:
|
* 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
|
# MiOPMdrv sound bank Paramer Ver2002.04.22
|
||||||
# LFO: LFRQ AMD PMD WF NFRQ
|
# LFO: LFRQ AMD PMD WF NFRQ
|
||||||
# @:[Num] [Name]
|
# @:[Num] [Name]
|
||||||
# CH: PAN FL CON AMS PMS SLOT NE
|
# CH: PAN FL CON AMS PMS SLOT NE
|
||||||
# [OPname]: AR D1R D2R RR D1L TL KS MUL DT1 DT2 AMS-EN
|
# [OPname]: AR D1R D2R RR D1L TL KS MUL DT1 DT2 AMS-EN
|
||||||
Data = [] if not BinMode else DataFile.read()
|
Data = [] if not BinMode else DataFile.read()
|
||||||
OpNames = ["M1","C1","M2","C2"]
|
OpNames = ["M1","C1","M2","C2"]
|
||||||
OpIdx = [0,2,1,3]
|
OpIdx = [0,2,1,3]
|
||||||
@ -69,6 +69,45 @@ def GetDT2D2R(b):
|
|||||||
def GetD1LRR(b):
|
def GetD1LRR(b):
|
||||||
return (b & 0b11110000) >> 4, (b & 0b00001111)
|
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):
|
def PrintOPM(data):
|
||||||
ne = (data[0xf] & 0b10000000) >> 7
|
ne = (data[0xf] & 0b10000000) >> 7
|
||||||
slot = 120
|
slot = 120
|
||||||
@ -137,36 +176,30 @@ def PrintOPNA(data):
|
|||||||
PrintOPNBasic(data)
|
PrintOPNBasic(data)
|
||||||
PrintOPNBasic(data, True)
|
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 = {
|
TypeFuncs = {
|
||||||
"opn": PrintOPN,
|
"opn": PrintOPN,
|
||||||
"opna": PrintOPNA,
|
"opna": PrintOPNA,
|
||||||
"opm": PrintOPM,
|
"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")
|
Type = GetOption("-t")
|
||||||
|
Loading…
Reference in New Issue
Block a user