Skip to content

Instantly share code, notes, and snippets.

@Pigu-A
Created January 17, 2018 13:33
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Pigu-A/a51a4f3b83a238579e225e06098ba4e7 to your computer and use it in GitHub Desktop.
Save Pigu-A/a51a4f3b83a238579e225e06098ba4e7 to your computer and use it in GitHub Desktop.
Deflemask module (.dmf) to PMD MML converter
# 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