Browse Source

organize instruments into objects instead of printing them immediately; finally move to argparse

master
whut 1 year ago
parent
commit
9b0c060bcc
2 changed files with 149 additions and 126 deletions
  1. +9
    -7
      README.md
  2. +140
    -119
      hootvopm.py

+ 9
- 7
README.md View File

@@ -16,13 +16,13 @@ Pass -b to instead read straight binary data instead of comma-separated values.

### Currently-supported chip types and formats

| 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") |
| ID | Description |
|-----------------|----------------------------------------------------------------|
| `raw` (Default) | Raw data in 32-byte groups; see table below |
| `opm` | OPM register area |
| `opn` | OPN register area |
| `opna` | OPNA register area (similar to OPN but has 3 more instruments) |
| `solfeace` | Format used in Sol-Feace for X68000 ("SOL.VCE") |

#### Raw data format
| Offset | Value |
@@ -38,6 +38,8 @@ Pass -b to instead read straight binary data instead of comma-separated values.

Rows marked with * are groups of 4 bytes, one per operator.

If you're curious, this is the format found in the PC-88VA version of Sorcerian, plus some padding bytes.

## 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.


+ 140
- 119
hootvopm.py View File

@@ -1,46 +1,18 @@
#!/usr/bin/python3
import sys, getopt
import argparse, collections, sys

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:], "bot:")

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("no input file")

BinMode = OptionExist("-b")
DataFile = open(Filename,"r" + ("b" if BinMode else "")) if Filename != "-" else sys.stdin
# MiOPMdrv sound bank Paramer Ver2002.04.22
# 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 = [] if not BinMode else DataFile.read()
OpNames = ["M1","C1","M2","C2"]
OpIdx = [0,2,1,3]

if not BinMode:
for line in DataFile.readlines():
for byte in line.strip().split(",")[:16]:
Data.append(int(byte,16))
def Error(*args):
print(sys.argv[0]+":", *args, file=sys.stderr)
sys.exit(1)


def PrintChannel(pan, fl, con, ams, pms, slot, ne):
print("CH:", pan, fl, con, ams, pms, slot, ne)
@@ -48,27 +20,61 @@ def PrintChannel(pan, fl, con, ams, pms, slot, ne):
def PrintOp(op, ar, d1r, d2r, rr, d1l, tl, ks, mul, dt1, dt2, amsen):
print(OpNames[op] + ":", ar, d1r, d2r, rr, d1l, tl, ks, mul, dt1, dt2, amsen)

def GetFlCon(b):
return (b & 0b00111000) >> 3, (b & 0b00000111)
class Operator:
def __init__(self, num):
self.num = num
for n in "ar d1r d2r rr d1l tl ks mul dt1 dt2 amsen".split():
setattr(self, n, 0)

def setDT1Mul(self, b):
self.dt1, self.mul = (b & 0b01110000) >> 4, (b & 0b00001111)

def GetAMSPMS(b):
return (b & 0b00110000) >> 4, (b & 0b00000111)
def setKSAR(self, b):
self.ks, self.ar = (b & 0b11000000) >> 6, (b & 0b00011111)

def GetDT1Mul(b):
return (b & 0b01110000) >> 4, (b & 0b00001111)
def setAmsenD1R(self, b):
self.amsen, self.d1r = (b & 0b10000000) >> 7, (b & 0b00011111)

def GetKSAR(b):
return (b & 0b11000000) >> 6, (b & 0b00011111)
def setDT2D2R(self, b):
self.dt2, self.d2r = (b & 0b11000000) >> 6, (b & 0b00011111)

def GetAmsenD1R(b):
return (b & 0b10000000) >> 7, (b & 0b00011111)
def setD1LRR(self, b):
self.d1l, self.rr = (b & 0b11110000) >> 4, (b & 0b00001111)

def GetDT2D2R(b):
return (b & 0b11000000) >> 6, (b & 0b00011111)
class Instrument:
def __init__(self, num, name):
self.num = num
self.name = name
self.slot = 120
self.pan = 64
self.ne = 0
for n in "fl con ams pms".split():
setattr(self, n, 0)

def GetD1LRR(b):
return (b & 0b11110000) >> 4, (b & 0b00001111)
self.ops = [Operator(i) for i in range(4)]

def render(self):
print("@:%d %s" % (self.num, self.name))
print("LFO: 0 0 0 0 0")
PrintChannel(self.pan, self.fl, self.con, self.ams, self.pms, self.slot, self.ne)
for o in self.ops:
PrintOp(o.num, o.ar, o.d1r, o.d2r, o.rr, o.d1l, o.tl, o.ks, o.mul, o.dt1, o.dt2, o.amsen)

def setFlCon(self, b):
self.fl, self.con = (b & 0b00111000) >> 3, (b & 0b00000111)
def setAMSPMS(self, b):
self.ams, self.pms = (b & 0b00110000) >> 4, (b & 0b00000111)
# """
def __hash__(self):
get = lambda n, d = 1: [getattr(self.ops[x], n) // d for x in range(4)]
data = bytes([self.con] + get("tl", 4))
#print(data, data.__hash__())
return data.__hash__()

def __eq__(self, other):
return self.__hash__() == other.__hash__()
# """
class RecordGrabber:
def __init__(self, fields, recordSize, **options):
self.fields = fields
@@ -78,96 +84,84 @@ class RecordGrabber:
setattr(self, k, v)

def __call__(self,data):
result = []
f = self.fields
slot = 120
pan = 64
ne = 0

for n in range(0, len(data) // self.recordSize):
inst = Instrument(n, ("Ins%d" % n) if "name" not in f else
f["name"](data, ofs))

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"))
inst.setFlCon(get("flcon"))
inst.setAMSPMS(get("amspms"))

PrintChannel(pan, fl, con, ams, pms, slot, ne)
for o in range(4):
getOp = lambda s: data[ofs + f[s] + OpIdx[o]] if s in f else 0
op = inst.ops[o]

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"))
op.setDT1Mul(getOp("dt1mul"))
op.tl = getOp("tl")
op.setKSAR(getOp("ksar"))
op.setAmsenD1R(getOp("amsend1r"))
op.setDT2D2R(getOp("dt2d2r"))
op.setD1LRR(getOp("d1lrr"))

PrintOp(op, ar, d1r, d2r, rr, d1l, tl, ks, mul, dt1, dt2, amsen)
#inst.render()
result.append(inst)
#print(inst.__hash__())
return result


def PrintOPM(data):
ne = (data[0xf] & 0b10000000) >> 7
slot = 120
pan = 64
result = []
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
inst = Instrument(i, ("Ins%d" % i))
inst.ne = (data[0xf] & 0b10000000) >> 7
meta = data[0x20 + i : 0x3f + i : 8]
fl, con = GetFlCon(meta[0])
inst.setFlCon(meta[0])
inst.setAMSPMS(meta[3])
pms, ams = GetAMSPMS(meta[3])
PrintChannel(pan, fl, con, ams, pms, slot, ne)
for op in range(4):
get = lambda x: data[x + i + OpIdx[op]*8]
for o in range(4):
op = inst.ops[o]
get = lambda x: data[x + i + OpIdx[o]*8]
dt1, mul = GetDT1Mul(get(0x40))
tl = get(0x60)
ks, ar = GetKSAR(get(0x80))
amsen, d1r = GetAmsenD1R(get(0xa0))
dt2, d2r = GetDT2D2R(get(0xc0))
d1l, rr = GetD1LRR(get(0xe0))

PrintOp(op, ar, d1r, d2r, rr, d1l, tl, ks, mul, dt1, dt2, amsen)
print()
op.setDT1Mul(get(0x40))
op.tl = get(0x60)
op.setKSAR(get(0x80))
op.setAmsenD1R(get(0xa0))
op.setDT2D2R(get(0xc0))
op.setD1LRR(get(0xe0))
return result

def PrintOPNBasic(data, opna = False):
slot = 120
pan = 64
ne = 0 # either i'm blind or opn has no noise enable bit (not that i care either way)
result = []
# SR in opn parlance is equivalent to D2R in opm, thanks yamaha
# likewise, SL -> D1L and DR -> D1R
ofs = 0x100 if opna else 0
for i in range(3):
n = i + (3 if opna else 0)
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 + 0xb0 + i])
ams, pms = GetAMSPMS(data[ofs + 0xb4 + i])
PrintChannel(pan, fl, con, ams, pms, slot, ne)
for op in range(4):
get = lambda x: data[ofs + x + i + OpIdx[op]*4]
dt1, mul = GetDT1Mul(get(0x30))
tl = get(0x40)
ks, ar = GetKSAR(get(0x50))
amsen, d1r = GetAmsenD1R(get(0x60))
dt2, d2r = GetDT2D2R(get(0x70))
d1l, rr = GetD1LRR(get(0x80))
inst = Instrument(n, ("Ins%d" % n))

inst.setFlCon(data[ofs + 0xb0 + i])
inst.setAMSPMS(data[ofs + 0xb4 + i])

for o in range(4):
op = inst.ops[o]
get = lambda x: data[ofs + x + i + OpIdx[o]*4]
PrintOp(op, ar, d1r, d2r, rr, d1l, tl, ks, mul, dt1, dt2, amsen)
op.getDT1Mul(get(0x30))
op.tl = get(0x40)
op.getKSAR(get(0x50))
op.getAmsenD1R(get(0x60))
op.getDT2D2R(get(0x70))
op.getD1LRR(get(0x80))

result.append(inst)
return result

def PrintOPN(data):
PrintOPNBasic(data)
@@ -202,12 +196,39 @@ TypeFuncs = {
}, 0x20)
}

Type = GetOption("-t")
if Type==None:
Error("no chip type specified")
if Type not in TypeFuncs:
Error(Type+":","unknown chip type")
Parser = argparse.ArgumentParser(
prog="hootvopm",
description="Convert Yamaha OPM/OPN data to VOPM instruments")

Parser.add_argument("file", help="file to read (- for stdin)")
Parser.add_argument("-b", "--binary", dest="binary", action="store_true",
help="read binary output instead of comma-separated hex")
Parser.add_argument("-t", "--type", dest="type", action="store",
default="raw", choices=TypeFuncs.keys(),
help="chip/format type (default: raw)")

Args = Parser.parse_args()

Filename = Args.file

DataFile = open(Filename,"r" + ("b" if Args.binary else "")) if Filename != "-" else sys.stdin
Data = [] if not Args.binary else DataFile.read()

if not Args.binary:
for line in DataFile.readlines():
for byte in line.strip().split(",")[:16]:
Data.append(int(byte,16))


print("// Exported by hootvopm : https://git.lain.church/whut/hootvopm")
print("// Chip type:",Type)
TypeFuncs[Type.lower()](Data)
print("// Chip type:", Args.type)

Instruments = set(TypeFuncs[Args.type.lower()](Data))
#print(len(Instruments))
Instruments = sorted(list(Instruments), key = lambda x: x.num)

count = 0
for i in Instruments:
i.num = count
i.render()
count += 1

Loading…
Cancel
Save