Last active
January 8, 2022 17:30
-
-
Save Pigu-A/e0d285ddbc76e442a69137d0e9a7ea24 to your computer and use it in GitHub Desktop.
Deflemask module (.dmf) to DevSound 2.4 converter. Not all features are converted correctly.
This file contains hidden or 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
import io, string, sys, zlib | |
CH3_DMF_VOL_IS_DS_VOL = False | |
def sane(a): | |
cap = True | |
t = "" | |
for i in a.decode(): | |
if i in string.ascii_letters + string.digits + "_": | |
t += i.upper() if cap else i | |
cap = False | |
else: cap = True | |
return t if t != "" else "_" | |
def note(n,c): | |
t = [] | |
while(c > 0): | |
t += [n,min(c,255)] | |
c -= 255 | |
return t | |
def db(a,h=True,s=True): | |
t = "" | |
fmt = "${:02x}" if s else "${:x}" | |
if h: | |
for i in range(0,len(a),16): | |
t += "\tdb\t"+",".join([fmt.format(j) for j in a[i:i+16]])+"\n" | |
else: | |
l = 0 | |
t += "\tdb\t" | |
for j in a: | |
if l > 64: | |
t = t[:-1]+"\n\tdb\t" | |
l = 0 | |
u = str(j)+"," | |
l += len(u) | |
t = t[:-1]+"\n" | |
return t | |
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 Ins: | |
def __init__(self,a): | |
t = u8(a) | |
self.name = sane(a.read(t)) | |
a.seek(1,1) # gameboy always has std ins | |
t = u8(a) | |
arp = [s32(a) for i in range(t)] | |
self.arploop = u8(a) if t else 255 | |
if u8(a): # fixed arp? | |
self.arp = [i+0x80 for i in arp] | |
else: self.arp = [i-12 if i-12 >= 0 else 0x80-12+i for i in arp] | |
t = u8(a) | |
self.duty = [s32(a) for i in range(t)] | |
self.dutyloop = u8(a) if t else 255 | |
t = u8(a) | |
self.wave = [s32(a) for i in range(t)] | |
self.waveloop = u8(a) if t else 255 | |
self.envol = u8(a) | |
self.endir = u8(a) | |
self.enlen = u8(a) | |
self.sndlen = u8(a) | |
class Row: | |
def __init__(self,a,b): | |
tn = s16(a) | |
to = s16(a) | |
if tn == 100: self.note = NOTE_REST | |
else: | |
tn = to*12+tn | |
if tn == 0: self.note = -1 | |
elif tn < 24 or tn > 84: self.note = NOTE_REST | |
else: self.note = tn-24 | |
self.vol = s16(a) | |
self.eff = [[s16(a),s16(a)] for i in range(b)] | |
self.ins = s16(a) | |
NOTE_REST = 0x49 | |
NOTE_NULL = 0x4a | |
CMD_INS = 0x80 | |
CMD_PORTAUP = 0x85 | |
CMD_PORTADOWN = 0x86 | |
CMD_SWEEP = 0x87 | |
CMD_PAN = 0x88 | |
CMD_SPEED = 0x89 | |
CMD_INSALT = 0x8a | |
CMD_ARP = 0x90 | |
CMD_TONEPORTA = 0x91 | |
CMD_CHANVOL = 0x92 | |
CMD_CALLBACK = 0x93 | |
CMD_RET = 0xc9 | |
# dmf import | |
finame = sys.argv[1] | |
cfi = open(finame,"rb") | |
fi = io.BytesIO(zlib.decompress(cfi.read())) | |
cfi.close() | |
fi.seek(18) # song name length | |
t = u8(fi) | |
sname = sane(fi.read(t)) | |
t = u8(fi) # artist name length | |
fi.seek(t+3,1) # song speed | |
speed = [u8(fi),u8(fi)] | |
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 | |
rpp = s32(fi) | |
ordlen = u8(fi) | |
hpn = 0 | |
ords = [] | |
for i in range(4): | |
t = [] | |
for j in range(ordlen): | |
tt = u8(fi) | |
hpn = tt if tt > hpn else hpn | |
t.append(tt) | |
ords.append(t) | |
numins = u8(fi) | |
ins = [] | |
insn = [] | |
for i in range(numins): | |
t = Ins(fi) | |
while t.name in insn: t.name += "_" | |
ins.append(t) | |
insn.append(t.name) | |
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(4): | |
t = [None]*(hpn+1) | |
effs = u8(fi) | |
for j in range(ordlen): | |
t[ords[i][j]] = [Row(fi,effs) for k in range(rpp)] | |
pats.append(t) | |
fi.close() | |
# conversion | |
# pre-format the patterns for alternating ins | |
for i in pats: | |
for j in i: | |
if j == None: continue | |
lins = [(-1,-1),None] | |
cins = -3 | |
bins = -1 | |
ains = 0 | |
for k in range(rpp): | |
ti = j[k].ins | |
if ti >= 0 or 0 <= j[k].note < NOTE_REST: | |
if ti == -1: ti = bins | |
else: bins = ti | |
j[k].ins = -1 | |
if cins >= 0: | |
if ti != lins[cins][0]: | |
cins = -2 | |
lins[0] = (ti,k) | |
j[k].ins = ti | |
else: cins = (cins+1)%2 | |
elif cins == -1: | |
if ti == lins[1][0]: | |
cins = -2 | |
lins[0] = (ti,k) | |
else: | |
j[lins[0][1]].ins = lins[0][0]*256+lins[1][0]*65536 | |
j[lins[1][1]].ins = -1 | |
if ti == lins[0][0]: cins = 1 | |
else: | |
cins = -2 | |
lins[0] = (ti,k) | |
j[k].ins = ti | |
elif cins == -2: | |
if ti != lins[0][0]: | |
cins = -1 | |
lins[1] = (ti,k) | |
j[k].ins = ti | |
else: lins[0] = (ti,k) | |
elif ti >= 0: | |
cins = -2 | |
lins[0] = (ti,k) | |
j[k].ins = ti | |
if cins == -1: | |
ains = lins[0][0]*256+lins[1][0]*65536 | |
j[lins[0][1]].ins = lins[0][0]*256+lins[1][0]*65536 | |
j[lins[1][1]].ins = -1 | |
fo = open(".".join(finame.split(".")[:-1])+".asm","w") | |
fo.write("; Generated by dmf2ds.py from {}\n\n".format(finame)) | |
fo.write("; Put this in the song pointer list\n") | |
fo.write("; dsng\t{},{},{},{}\n\n".format(sname,256-int(4096/rate+.5),speed[0],speed[1])) | |
# instruments | |
dinsout = "{}_Ins:\n\tconst_def\n".format(sname) | |
insout = "" | |
volout = "" | |
arpout = "" | |
wsout = "" | |
for i in ins: | |
arp = i.name if len(i.arp) else "_" | |
wave = i.name if len(i.duty) or len(i.wave) else "_" | |
dinsout += "\tdins\t{}\n".format(i.name) | |
insout += "ins_{0}:\tInstrument\t0,{0},{1},{2},_\n".format(i.name,arp,wave) | |
vol = (i.envol<<4)|(i.endir<<3)|i.enlen | |
volout += "vol_{}:\tdb\t$ff,${:02x}\n".format(i.name,vol) | |
arpend = [0xfe,i.arploop] if i.arploop != 255 else [0xff] | |
if len(i.arp) > 0: arpout += "arp_{}:{}".format(i.name,db(i.arp+arpend,s=False)) | |
ws = i.duty if len(i.duty) > 0 else i.wave | |
wsl = i.dutyloop if len(i.duty) > 0 else i.waveloop | |
wsend = [0xfe,wsl] if wsl != 255 else [0xff] | |
if len(ws) > 0: wsout += "waveseq_{}:{}".format(i.name,db(ws+wsend,s=False)) | |
fo.write("\n".join([dinsout,insout,volout,arpout,wsout])+"\n") | |
# waves | |
t = "; Put these in the main song data's wave table\n" | |
for i in range(0,len(wavs),8): | |
t += "\tdw\t{}\n".format(",".join(["wave_"+str(j) for j in range(i,min(len(wavs),i+8))])) | |
fo.write(t+"\n") | |
for i in range(len(wavs)): | |
fo.write("wave_{}:{}".format(i,db(wavs[i]))) | |
fo.write("\n") | |
# orderlist & patterns | |
# patterns | |
rels = [] | |
if len(sys.argv) > 2: rels = [int(i,16) for i in sys.argv[2].split(",")] | |
for i in range(4): | |
fo.write("\n{}_CH{}:\n".format(sname,i+1)) | |
for j in range(ordlen): | |
fo.write("\tdbw\tCallSection,.pattern_{:X}\n".format(ords[i][j])) | |
fo.write("\tdb\tEndChannel\n\n") | |
for j in range(hpn+1): | |
if pats[i][j] == None: continue | |
fo.write(".pattern_{:X}\n".format(j)) | |
n = NOTE_NULL | |
c = 0 | |
nn = False | |
tmpspeed = speed | |
sweepdir = 0 | |
curvol = 15 | |
ains = -1 | |
curins = -1 | |
ainsc = 0 | |
patbrk = False | |
o = [] | |
for k in range(rpp): | |
oe = [] | |
nnf = False | |
r = pats[i][j][k] | |
tvol = curvol | |
tins = curins | |
if r.note != -1: nn = True | |
if 0 <= r.note < NOTE_REST and ains > 0: | |
tins = (ains>>(8*ainsc))%256 | |
ainsc = (ainsc+1)%2 | |
if r.ins != -1: | |
if r.ins > 256: | |
ains = r.ins>>8 | |
ainsc = 1 | |
oe += [CMD_INSALT,ains%256,ains>>8] | |
tins = ains%256 | |
else: | |
tins = r.ins | |
oe += [CMD_INS,tins] | |
nnf = True | |
if tins != curins and i != 2: tvol = 15 | |
curins = tins | |
if r.vol != -1: | |
if i == 2: # ch3 | |
if CH3_DMF_VOL_IS_DS_VOL: tvol = r.vol | |
else: tvol = 2**(r.vol//4+1)-1 if r.vol > 3 else 0 | |
else: tvol = min(r.vol*15//ins[tins].envol,15) if tins >= 0 else r.vol | |
if tvol != curvol: | |
oe += [CMD_CHANVOL,tvol] | |
curvol = tvol | |
nnf = True | |
for l in r.eff: | |
if l[0] == 0: | |
oe += [CMD_ARP,l[1]] | |
nnf = True | |
elif l[0] == 1: | |
oe += [CMD_PORTAUP,l[1]] | |
nnf = True | |
elif l[0] == 2: | |
oe += [CMD_PORTADOWN,l[1]] | |
nnf = True | |
elif l[0] == 3: | |
oe += [CMD_TONEPORTA,l[1]] | |
nnf = True | |
elif l[0] == 8: | |
oe += [CMD_PAN,l[1]] | |
nnf = True | |
elif l[0] == 9: | |
tmpspeed[0] = l[1] | |
oe += [CMD_SPEED,tmpspeed[0],tmpspeed[1]] | |
nnf = True | |
elif l[0] == 13: | |
patbrk = True | |
elif l[0] == 15: | |
tmpspeed[1] = l[1] | |
oe += [CMD_SPEED,tmpspeed[0],tmpspeed[1]] | |
nnf = True | |
elif l[0] == 19: | |
oe += [CMD_SWEEP,l[1]|(sweepdir<<3)] | |
nnf = True | |
elif l[0] == 20: | |
sweepdir = 1 if l[1] else 0 | |
elif l[0] == 0xe0: | |
arpspeed = l[1] - 1 | |
elif l[0] == 0xee: | |
oe += [CMD_CALLBACK,l[1]] | |
nnf = True | |
if nn: | |
if c: o += note(n,c) | |
o += oe | |
n = r.note | |
c = 0 | |
nn = False | |
elif nnf: | |
if c: o += note(n,c) + oe | |
n = NOTE_NULL | |
c = 0 | |
c += 1 | |
if patbrk: break | |
fo.write("{}\tret\n".format(db(o+note(n,c)))) | |
fo.close() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment