Created
January 17, 2018 13:33
-
-
Save Pigu-A/a51a4f3b83a238579e225e06098ba4e7 to your computer and use it in GitHub Desktop.
Deflemask module (.dmf) to PMD MML converter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# By Pigu | |
# Usage: dmf2pmd.py <input.dmf> <output.txt> | |
import io, math, string, sys, zlib | |
poke = lambda c,o,a,d: "y{},{}".format(a+(c%3)+o*4,d) | |
timerB = lambda x: math.floor(256.5-(10400/3/x)) | |
u8 = lambda x: int.from_bytes(x.read(1),byteorder="little") | |
s16 = lambda x: int.from_bytes(x.read(2),byteorder="little",signed=True) | |
s32 = lambda x: int.from_bytes(x.read(4),byteorder="little",signed=True) | |
class NoteConv: | |
notenames = ["c","c+","d","d+","e","f","f+","g","g+","a","a+","b"] | |
def __init__(self): | |
self.lastlen = -1 | |
self.buflen = -1 | |
self.bufoct = -1 | |
self.bufnote = "" | |
self.bufeff = "" | |
self.spacecnt = 0 | |
def lentext(self,a,dot): | |
if a >= 3 and 96%a == 0: return str(96//a) | |
elif dot: | |
if a >= 9 and 144%a == 0: return "{}.".format(144//a) | |
elif a >= 14 and 168%a == 0: return "{}..".format(168//a) | |
return "%{}".format(a) | |
def lentextrel(self,a,l): | |
if a == -1: return "" | |
if a > 96: | |
t = "" | |
while a > 96: | |
t += "1&" | |
a -= 96 | |
return t + self.lentext(a,True) | |
else: | |
if a == l: return "" | |
elif a*2 == l*3: return "." | |
elif a*4 == l*7: return ".." | |
return self.lentext(a,True) | |
def freebuf(self,o): | |
o[-1] = self.bufnote + self.lentextrel(self.buflen,self.lastlen) | |
if self.spacecnt >= 192: o[-1] += " " | |
o += [self.bufeff,""] | |
return t | |
def note(self,o,n,c,h,e): | |
if self.lastlen == -1: self.lastlen = c | |
if c == self.buflen != self.lastlen and c < 96: | |
self.lastlen = c | |
o[-2] += "l{}".format(self.lentext(c,False)) | |
self.freebuf(o) | |
t = "" | |
if n == -1: t = "r" # rest | |
else: | |
coct = n//12 | |
if self.bufoct == -1 or abs(coct-self.bufoct) > 2: | |
t += "o{}".format(coct+1) | |
else: | |
dir = "<" if coct < self.bufoct else ">" | |
t += dir*abs(coct-self.bufoct) | |
self.bufoct = coct | |
if h: t += "&" | |
t += self.notenames[n%12] | |
if self.spacecnt >= 192: self.spacecnt %= 192 | |
self.bufeff = e | |
self.bufnote = t if c > 0 else "" | |
self.buflen = c | |
self.spacecnt += c | |
class Ins: | |
def __init__(self,a): | |
if a == None: | |
self.name = "" | |
self.ssgenv = None | |
self.pms = 0 | |
self.ams = 0 | |
self.fmvars = [0,0]+[0,0,0,0,15,127,0,0,0,0]*4 | |
else: | |
t = u8(a) | |
n = a.read(t) | |
self.name = n | |
if u8(a): # fm? | |
self.ssgenv = None # doubles as instrument type | |
v = [u8(a),u8(a)] # alg fb | |
self.pms = u8(a) | |
self.ams = u8(a) | |
for i in range(4): | |
ams, ar, dr, ml, rr, sl, tl, dt2, ks, dt, sr, ssgeg = (u8(a) for i in range(12)) | |
v += [ar, dr, sr, rr, sl, tl, ks, ml, dt+1, ams] | |
self.fmvars = v | |
else: # std | |
# try parsing the name if it's SSG envelope definition | |
try: | |
n = n.replace(b" ",b"") | |
if len(n) == 0: raise ValueError() | |
if n[0] == ord("@"): | |
t = int(n[1:]) | |
if t < 0 or t > 9: raise ValueError() | |
elif n[0] == ord("E"): | |
t = n[1:].split(b",") | |
if len(t) < 4 or len(t) > 6: raise ValueError() | |
else: raise ValueError() | |
except ValueError: | |
n = b"@0" # defaults to @0 if invalid | |
self.ssgenv = n | |
# seek through std tables | |
t = u8(a) | |
a.seek(t*4,1) # volume table | |
if t > 0: a.seek(1,1) # volume loop | |
t = u8(a) | |
a.seek(t*4+1,1) # arp table + arp mode | |
if t > 0: a.seek(1,1) # arp loop | |
t = u8(a) | |
a.seek(t*4,1) # duty table | |
if t > 0: a.seek(1,1) # duty loop | |
t = u8(a) | |
a.seek(t*4,1) # wave table | |
if t > 0: a.seek(1,1) # wave loop | |
dummins = Ins(None) | |
def getins(a,i,fm=False): | |
if i < 0: return dummins | |
elif fm and a[i].ssgenv != None: return dummins | |
return a[i] | |
class Row: | |
def __init__(self,a,b,t): | |
tn = s16(a) | |
to = s16(a) | |
if tn == 100: self.note = -1 # rest | |
else: | |
self.note = tn+to*12 | |
if self.note > 0: self.note += t | |
if self.note < 0 or self.note > 95: self.note = -1 | |
self.vol = s16(a) | |
self.eff = [[s16(a),s16(a)] for i in range(b)] | |
self.ins = s16(a) | |
# dmf import | |
finame = sys.argv[1] | |
cfi = open(finame,"rb") | |
fi = io.BytesIO(zlib.decompress(cfi.read())) | |
cfi.close() | |
fi.seek(17) # system | |
t = u8(fi) | |
chs = 0 | |
if t == 0x02: chs = 10 | |
elif t == 0x12: chs = 13 # ch3 ext | |
else: raise Exception("System is not Genesis") | |
t = u8(fi) # song name length | |
sname = fi.read(t) | |
t = u8(fi) # author name length | |
aname = fi.read(t) | |
fi.seek(2,1) # speed | |
smul = u8(fi)+1 | |
speed = {0:(u8(fi)*smul,u8(fi)*smul)} | |
rate = u8(fi) | |
iscustomrate = u8(fi) | |
customrate = fi.read(3) | |
if iscustomrate: rate = int(customrate.strip(b"\x00")) | |
else: rate = 60 if rate else 50 | |
rate = timerB(rate) | |
rpp = s32(fi) | |
ordlen = u8(fi) | |
ords = [[u8(fi) for j in range(ordlen)] for i in range(chs)] | |
numins = u8(fi) | |
ins = [Ins(fi) for i in range(numins)] | |
wavs = [[(s32(fi)<<4)+s32(fi) for j in range(0,s32(fi),2)] for i in range(u8(fi))] | |
pats = [] | |
for i in range(chs): | |
t = 0 if i < chs-4 else 12 | |
effs = u8(fi) | |
t = [[Row(fi,effs,t) for j in range(rpp)] for i in range(ordlen)] | |
pats.append(t) | |
fi.close() | |
# conversion | |
fo = open(sys.argv[2],"w") | |
fo.write("#Title {}\n#Composer {}\n#Option /v\n".format(sname.decode(),aname.decode())) | |
if chs == 13: fo.write("#FM3Extend XYZ".format(sname,aname)) | |
fo.write("#Filename .M\n#Timer {}\n#Bendrange 1\n#Memo Generated by dmf2pmdmml.py from {}\n\n" | |
.format(rate,finame)) | |
# instruments | |
for i in range(len(ins)): | |
if ins[i].ssgenv == None: | |
t = ins[i].fmvars | |
fo.write("@{:02} {} {} ; {}\n".format(i,t[0],t[1],ins[i].name.decode())) | |
for i in [2,22,12,32]: | |
fo.write("{:>2} {:>2} {:>2} {:>2} {:>2} {:>3} {:1} {:>2} {:1} {:1}\n".format(*t[i:i+10])) | |
fo.write("\n") | |
outs = [] | |
outp = [] | |
if chs == 13: | |
fo.write("ABCXYZDEF |C s1 |X s2 |Y s4 |Z s8 | V127\nGHI v15\n") # setup for extended channels | |
outs = [[] for i in range(12)] | |
outp = [[0]*(ordlen+1) for i in range(12)] | |
else: | |
fo.write("ABCDEF V127\nGHI v15\n") | |
outs = [[] for i in range(9)] | |
outp = [[0]*(ordlen+1) for i in range(9)] | |
# orderlist & patterns | |
patrange = [(0,rpp) for i in range(ordlen+1)] | |
s = speed[0] | |
lfo = {0:1} | |
for i in range(ordlen): | |
# scan for global effects first | |
for j in range(rpp): | |
for k in range(chs): | |
p = i*rpp+j | |
r = pats[k][i][j] | |
for l in r.eff: | |
if l[0] == 9: | |
s = (l[1],speed.get(p,s)[1]) | |
speed[p] = s*smul | |
elif l[0] == 13: | |
if l[1] < rpp and j+1 < patrange[i][1]: | |
patrange[i][1] = j+1 | |
patrange[i+1][0] = l[1] | |
elif l[0] == 15: | |
s = (speed.get(p,s)[0],l[1]) | |
speed[p] = s*smul | |
elif l[0] == 16 and l[1]&15 < 8: | |
if l[1] & 16: lfo[p] = l[1]&15 | |
else: lfo[p] = -1 | |
nnf = True | |
for i in range(chs-1): | |
n = -1 | |
c = 0 | |
pc = 2 | |
nn = False | |
tie = False | |
arpspeed = 1 | |
sweepdir = 0 | |
curvol = 127 if i < chs-4 else 15 | |
curins = -1 | |
curspeed = speed[0] | |
curlfo = lfo[0] | |
curpms = 0 | |
curams = 0 | |
o = ["",""] | |
conv = NoteConv() | |
for j in range(ordlen): | |
cf = True | |
for k in range(patrange[j][0],patrange[j][1]): | |
oe = "" | |
nnf = False | |
r = pats[i][j][k] | |
curspeed = speed.get(j*rpp+k,curspeed) | |
curlfo = lfo.get(j*rpp+k,curlfo) | |
s = curspeed[(j*rpp+k)%2] | |
if r.note != 0: nn = True | |
if r.ins != -1 and r.ins != curins: | |
curins = r.ins | |
tins = getins(ins,curins) | |
if i < chs-4: | |
oe += "@{:02}".format(r.ins) | |
tpms = tins.pms | |
tams = tins.ams | |
if tpms != curpms or tams != curams: | |
curpms = tpms | |
curams = tams | |
oe += "H{},{}".format(tpms,tams) | |
if tpms == tams == 0 or curlfo == -1: | |
oe += "#0" | |
else: oe += "#1,{}".format(curlfo) | |
else: | |
oe += tins.ssgenv.decode() if tins.ssgenv != None else "" | |
nnf = True | |
if r.vol != -1 and r.vol != curvol: | |
curvol = r.vol | |
oe += "V{}".format(curvol) if i < chs-4 else "v{}".format(curvol) | |
nnf = True | |
for l in r.eff: | |
if l[0] == 3 or l[0] == 5 and nn: tie = True | |
if l[0] == 8 and i < chs-4: | |
oe += "p{}".format(((l[1]&16)>>3)+(l[1]&1)) | |
nnf = True | |
elif l[0] == 16 and i < chs-4 and l[1]&15 < 8: | |
if l[1] & 16: oe += "#1,{}".format(l[1]&15) | |
else: oe += "#0" | |
nnf = True | |
elif l[0] == 17 and i < chs-4 and l[1] < 8: | |
oe += "FB{}".format(l[1]) | |
nnf = True | |
elif l[0] == 18 and i < chs-4 and l[1] < 128: | |
oe += "O1,{}".format(l[1]) | |
nnf = True | |
elif l[0] == 19 and i < chs-4 and l[1] < 128: | |
oe += "O2,{}".format(l[1]) | |
nnf = True | |
elif l[0] == 20 and i < chs-4 and l[1] < 128: | |
oe += "O4,{}".format(l[1]) | |
nnf = True | |
elif l[0] == 21 and i < chs-4 and l[1] < 128: | |
oe += "O8,{}".format(l[1]) | |
nnf = True | |
elif l[0] == 22 and i < chs-4: | |
t = ((l[1]&0xf0)>>4,l[1]&15) | |
if 0 < t[0] < 5 and t[1] < 16: | |
t2 = getins(ins,curins,fm=True).fmvars[t[0]*10]*16 # get dt | |
oe += poke(i,t[0]-1,0x30,t[1]+t2) | |
nnf = True | |
elif 25 <= l[0] <= 29 and i < chs-4 and l[1] < 32: | |
t = getins(ins,curins,fm=True).fmvars | |
if l[0] == 25: | |
oe += poke(i,0,0x50,l[1]+t[8]*64) # get ks | |
oe += poke(i,1,0x50,l[1]+t[18]*64) | |
oe += poke(i,2,0x50,l[1]+t[28]*64) | |
oe += poke(i,3,0x50,l[1]+t[38]*64) | |
elif l[0] == 26: oe += poke(i,0,0x50,l[1]+t[8]*64) | |
elif l[0] == 27: oe += poke(i,1,0x50,l[1]+t[18]*64) | |
elif l[0] == 28: oe += poke(i,2,0x50,l[1]+t[28]*64) | |
elif l[0] == 29: oe += poke(i,3,0x50,l[1]+t[38]*64) | |
nnf = True | |
elif l[0] == 0xe5: | |
oe += "I{}".format(l[1]*64-8192) | |
nnf = True | |
if cf: | |
outp[i][j] = pc | |
cf = False | |
if n == -1: nnf = True | |
if nn: | |
conv.note(o,n,c,tie,oe) | |
n = r.note | |
c = 0 | |
pc += 2 | |
nn = False | |
tie = False | |
elif nnf: | |
conv.note(o,n,c,tie,oe) | |
c = 0 | |
pc += 2 | |
# don't tie if ssg doesn't have an envelope | |
if i < chs-4 or getins(ins,curins).ssgenv != b"@0": tie = True | |
c += s | |
conv.note(o,n,c,False,"") | |
conv.freebuf(o) | |
outs[i] = o | |
outp[i][-1] = pc | |
# write | |
chname = "ABCXYZDEFGHI" if chs == 13 else "ABCDEFGHI" | |
cc = [outp[i][0] for i in range(chs-1)] | |
for i in range(ordlen): | |
fo.write("\n; Ord {:X}\n\n".format(i)) | |
for j in range(chs-1): | |
t = "" | |
c = cc[j] | |
while c <= outp[j][i+1]: | |
t += outs[j][c] + outs[j][c+1] | |
c += 2 | |
cc[j] = c | |
t = t.strip() | |
if t != "": fo.write("{} {}\n".format(chname[j],t)) | |
fo.close() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment