Browse Source

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

master
whut 1 year ago
parent
commit
cb2cfddc68
2 changed files with 74 additions and 35 deletions
  1. +13
    -7
      README.md
  2. +61
    -28
      hootvopm.py

+ 13
- 7
README.md 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:


+ 61
- 28
hootvopm.py 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")


Loading…
Cancel
Save