initial commit
This commit is contained in:
commit
a001699844
31
README.md
Normal file
31
README.md
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# hootopna
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
`./hootopna.py -t chip_type input_file`
|
||||||
|
|
||||||
|
Output is dumped straight to stdout; redirect it to a file if you want to save it.
|
||||||
|
|
||||||
|
### Currently-supported chip types
|
||||||
|
|
||||||
|
* OPM
|
||||||
|
* OPN
|
||||||
|
* OPNA (equivalent to OPN)
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
* When you see the area you need to capture, use Ctrl-C to copy it to the clipboard. This copies the entire page to the clipboard in a human-readable format, with 16 comma-separated hex bytes per line (plus a comma at the end for whatever reason, but this is ignored).
|
||||||
|
* Paste this into a file, trimming it down to get the register area of the FM sound chip. Save it as whatever.
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
## 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:
|
||||||
|
* LFO data
|
||||||
|
* Operator on/off state; all operators are considered to be in use (this *could* potentially be inferred, though)
|
||||||
|
* Panning, though VOPM doesn't seem to use this
|
153
hootvopm.py
Normal file
153
hootvopm.py
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
import sys, getopt
|
||||||
|
|
||||||
|
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:], "ot:")
|
||||||
|
|
||||||
|
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("need a file, genius")
|
||||||
|
|
||||||
|
|
||||||
|
DataFile = open(Filename,"r")
|
||||||
|
# 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 = []
|
||||||
|
OpNames = ["M1","C1","M2","C2"]
|
||||||
|
OpIdx = [0,2,1,3]
|
||||||
|
|
||||||
|
for line in DataFile.readlines():
|
||||||
|
for byte in line.strip().split(",")[:16]:
|
||||||
|
Data.append(int(byte,16))
|
||||||
|
|
||||||
|
def PrintOPM(data):
|
||||||
|
ne = (data[0xf] & 0b10000000) >> 7
|
||||||
|
slot = 120
|
||||||
|
pan = 64
|
||||||
|
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
|
||||||
|
|
||||||
|
meta = data[0x20 + i : 0x3f + i : 8]
|
||||||
|
|
||||||
|
fl = (meta[0] & 0b00111000) >> 3
|
||||||
|
con = (meta[0] & 0b00000111)
|
||||||
|
|
||||||
|
pms = (meta[3] & 0b01110000) >> 4
|
||||||
|
ams = (meta[3] & 0b00000011)
|
||||||
|
|
||||||
|
print("CH:", pan, fl, con, ams, pms, slot, ne)
|
||||||
|
|
||||||
|
for op in range(4):
|
||||||
|
get = lambda x: data[x + i + OpIdx[op]*8]
|
||||||
|
|
||||||
|
b = get(0x40)
|
||||||
|
dt1 = (b & 0b01110000) >> 4
|
||||||
|
mul = (b & 0b00001111)
|
||||||
|
|
||||||
|
tl = get(0x60)
|
||||||
|
|
||||||
|
b = get(0x80)
|
||||||
|
ks = (b & 0b11000000) >> 6
|
||||||
|
ar = (b & 0b00011111)
|
||||||
|
|
||||||
|
b = get(0xa0)
|
||||||
|
amsen = (b & 0b10000000) >> 7
|
||||||
|
d1r = (b & 0b00011111)
|
||||||
|
|
||||||
|
b = get(0xc0)
|
||||||
|
dt2 = (b & 0b11000000) >> 6
|
||||||
|
d2r = (b & 0b00011111)
|
||||||
|
|
||||||
|
b = get(0xe0)
|
||||||
|
d1l = (b & 0b11110000) >> 4
|
||||||
|
rr = (b & 0b00001111)
|
||||||
|
|
||||||
|
print(OpNames[op] + ":", ar, d1r, d2r, rr, d1l, tl, ks, mul, dt1, dt2, amsen)
|
||||||
|
|
||||||
|
print()
|
||||||
|
|
||||||
|
def PrintOPN(data):
|
||||||
|
slot = 120
|
||||||
|
pan = 64
|
||||||
|
ne = 0 # either i'm blind or opn has no noise enable bit (not that i care either way)
|
||||||
|
# SR in opn parlance is equivalent to D2R in opm, thanks yamaha
|
||||||
|
# likewise, SL -> D1L and DR -> D1R
|
||||||
|
for i in range(3):
|
||||||
|
print("@:%d Ins%d" % (i, i))
|
||||||
|
print("LFO: 0 0 0 0 0") # no way to get this data... at least for now
|
||||||
|
|
||||||
|
fbcon = data[0xb0 + i]
|
||||||
|
fl = (fbcon & 0b00111000) >> 3
|
||||||
|
con = (fbcon & 0b00000111)
|
||||||
|
|
||||||
|
sens = data[0xb4 + i]
|
||||||
|
ams = (sens & 0b00110000) >> 4
|
||||||
|
pms = (sens & 0b00000111)
|
||||||
|
|
||||||
|
print("CH:", pan, fl, con, ams, pms, slot, ne)
|
||||||
|
|
||||||
|
for op in range(4):
|
||||||
|
get = lambda x: data[x + i + OpIdx[op]*4]
|
||||||
|
|
||||||
|
b = get(0x30)
|
||||||
|
dt1 = (b & 0b01110000) >> 4
|
||||||
|
mul = (b & 0b00001111)
|
||||||
|
|
||||||
|
tl = get(0x40)
|
||||||
|
|
||||||
|
b = get(0x50)
|
||||||
|
ks = (b & 0b11000000) >> 6
|
||||||
|
ar = (b & 0b00011111)
|
||||||
|
|
||||||
|
b = get(0x60)
|
||||||
|
amsen = (b & 0b10000000) >> 7
|
||||||
|
d1r = (b & 0b00011111)
|
||||||
|
|
||||||
|
b = get(0x70)
|
||||||
|
dt2 = (b & 0b11000000) >> 6
|
||||||
|
d2r = (b & 0b00011111)
|
||||||
|
|
||||||
|
b = get(0x80)
|
||||||
|
d1l = (b & 0b11110000) >> 4
|
||||||
|
rr = (b & 0b00001111)
|
||||||
|
|
||||||
|
print(OpNames[op] + ":", ar, d1r, d2r, rr, d1l, tl, ks, mul, dt1, dt2, amsen)
|
||||||
|
|
||||||
|
TypeFuncs = {
|
||||||
|
"opn": PrintOPN,
|
||||||
|
"opna": PrintOPN, # fm portion of opn and opna are functionally equivalent
|
||||||
|
"opm": PrintOPM
|
||||||
|
}
|
||||||
|
|
||||||
|
Type = GetOption("-t").lower()
|
||||||
|
if Type==None:
|
||||||
|
Error("no chip type specified")
|
||||||
|
if Type not in TypeFuncs:
|
||||||
|
Error(Type+":","unknown chip type")
|
||||||
|
|
||||||
|
print("// Exported by hootvopm : https://gitlab.com/whutt/hootvopm")
|
||||||
|
print("// Chip type:",Type)
|
||||||
|
TypeFuncs[Type](Data)
|
Loading…
Reference in New Issue
Block a user