Skip to content

Instantly share code, notes, and snippets.

@Pigu-A
Last active January 8, 2022 17:30
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Pigu-A/e0d285ddbc76e442a69137d0e9a7ea24 to your computer and use it in GitHub Desktop.
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.
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