# =====================================================================================================
# Preliminary MLT--->DSF converter (ver 0.03 2008-10-14) by kingshriek
# This script only supports the Manatee sound driver and not necessarily all the different versions of
# it. For sound data not in .MLT files, convert to .MLT first before using.
# Requires bin2psf & psfpoint (http://www.neillcorlett.com/psf/utilities.html)
# Update history located near end of file
# =====================================================================================================
# =====================================================================================================
# This script produces .dsf/.minidsf files from a Manatee sound driver image and a multi-unit sound
# data file (.MLT extension) and has similar functionality to the ssfmake script (for .ssf/.minissf).
# Not all games pack their sound data into .MLT files, and for those that don't, it will require
# combining the various sound files into an .MLT file before using this script. The individual sound
# formats should have extentions similar to their header strings, only without the leading "S" (e.g. a
# sequence data file will have extention .msb and header string SMSB). See comments below inside
# dsfmake() for information on the various sound formats and header strings.
#
# This script in its current form may not work with some versions of the manatee driver. This is the
# case since the driver needs to be patched in a few locations so that it doesn't wipe out ripper-provided
# sound commands upon initialization and these patch locations may vary with different driver versions.
# Also, some of these patches disable loading of various default sound data (the driver contains itself
# contains a default DSP program and mixer for instance) so that it uses data in the MLT file instead.
# If a track needs any of this default data, you will need to comment out the corresponding patches in
# dsfmake() for a proper rip.
#
# The bulk of the script is defined as a function, which is called at the very bottom. Feel free to customize
# this bottom section as you wish. Once good use for it is that you can write some code to loop over a bunch
# of files and perform the dsf creation in batch mode.
# =====================================================================================================
# =====================================================================================================
# PARAMETERS
# -----------------------------------------------------------------------------------------------------
bank = 0x00 # sequence bank number
track = None # sequence track number (if None, create minidsfs for all tracks)
volume = 0x77 # sequence volume (0xFF is max, but it should probably be <= 0x7F in most cases)
mixer = 0x00 # effect-out mixer select (usually 0x00)
effect = 0x00 # DSP program select (usually 0x00)
# Not sure about any other paramaters yet, especially for sound command 0x87. For others, try editing
# the sound commands specified in dsfmake() directly for now.
# -----------------------------------------------------------------------------------------------------
use_osb = 0 # 0: use MIDI sequence data, 1: use one-shot data (sound effects)
naomi = 0 # 0: assume 2 MB sound RAM, 1: assume 8 MB sound RAM (Naomi/Hikaru boards)
# -----------------------------------------------------------------------------------------------------
# filenames
ndrv = 'MANATEE.DRV' # sound driver (usually MANATEE.DRV)
nmlt = 'FILE.MLT' # sound data archive (.MLT file)
# -----------------------------------------------------------------------------------------------------
nout = nmlt.upper().replace('.MLT','.DSF') # output file name
# =====================================================================================================
# =====================================================================================================
from struct import * # pack, unpack
from array import *
import os # system
# =====================================================================================================
# =====================================================================================================
# Converts list of bytes into a sound command array (zero-padded out to 16 bytes)
def sndcmd(x):
if len(x) > 0x10:
x = x[:0x10]
return array('B',x + [0x00]*(0x10-len(x)))
# =====================================================================================================
# =====================================================================================================
# Get base file name with and without extension
def fnoext(fname):
fnameb = os.path.basename(fname)
idot = -fname[::-1].find('.')-1
if idot:
fnamex = fnameb[:idot]
else:
fnamex = fnameb
return (fnameb,fnamex)
# =====================================================================================================
# =====================================================================================================
# Get sound RAM offset and byte allocation (useful for custom dsflib/minidsf creation)
def get_offset(mlt_filename, hdr_str, bank):
mlt_hdr_top = array('B', open(mlt_filename, 'rb').read(0x20))
nhdr = unpack('<I', mlt_hdr_top[0x8:0xC])[0] # Number of MLT header entries
hdr_offset = 0x20 # Skip the MLT top header
mlt_hdr = array('B', open(mlt_filename, 'rb').read(0x20*(nhdr+1))) # Get the full MLT header
for k in range(nhdr): # Search for header string and bank
mlt_hdr_str = mlt_hdr[hdr_offset:hdr_offset+4].tostring() # Get header string from MLT header entry
mlt_bank = unpack('<I', mlt_hdr[hdr_offset+4:hdr_offset+8])[0] # Get bank number from MLT header entry
if mlt_hdr_str == hdr_str and mlt_bank == bank: # Compare to inputs
offset = unpack('<I', mlt_hdr[hdr_offset+0x8:hdr_offset+0xC])[0] # Get sound RAM offset from MLT header entry
alloc = unpack('<I', mlt_hdr[hdr_offset+0xC:hdr_offset+0x10])[0] # Get sound RAM allocation from MLT header entry
return (offset, alloc)
hdr_offset += 0x20
return (None, None)
# =====================================================================================================
# =====================================================================================================
# Creates ssf file from user-specified parameters.
# Inputs are defined in paramter section above.
def dsfmake(nout,ndrv,nmlt,bank,track,volume,mixer,effect):
# Initialization
szdrv = 0
szmlt = 0
datadrv = array('B',[])
datamlt = array('B',[])
ntracks = 0
# -----------------------------------------------------------------------------------------------------
if ndrv != '':
fdrv = open(ndrv,'rb') # sound driver
szdrv = os.path.getsize(ndrv)
datadrv = array('B',fdrv.read(szdrv))
fdrv.close()
if nmlt != '':
fmlt = open(nmlt,'rb') # sound data
szmlt = os.path.getsize(nmlt)
datamlt = array('B',fmlt.read(szmlt))
fmlt.close()
# -----------------------------------------------------------------------------------------------------
if naomi:
dsfbin = array('B','\x00'*0x800000)
else:
dsfbin = array('B','\x00'*0x200000)
# -----------------------------------------------------------------------------------------------------
# Set driver
# Identified by header string 'SDRV'
if datadrv[:4].tostring() == 'SDRV': # header string for sound driver
sfbin[:szdrv-0x20] = datadrv[0x20:szdrv]
vdrv = datadrv[0x04] # driver version
# -----------------------------------------------------------------------------------------------------
# Set sound data
# MANATEE driver sound data formats identified by header strings:
# SMLT - multi-unit archive
# SMSB - MIDI sequence bank
# SMPB - MIDI program bank
# SMDB - MIDI drum bank
# SOSB - one-shot bank (sound effects)
# SFPB - DSP program bank
# SFPW - DSP work RAM
# SFOB - DSP output bank
# SPSR - PCM stream ring-buffer
sbank = ('SMSB', 'SOSB') # DSF can either target MIDI sequences or one-shots
areamap = {'SMSB':0x14000,'SMPB':0x14080,'SOSB':0x14100,'SFPB':0x14200,'SFOB':0x14280,'SFPW':0x14288,'SMDB':0x14290,'SPSR':0x14180} # canonical area map offsets
#fcon = open('mlt_contents.txt','w') # DEBUG
if datamlt[:4].tostring() == 'SMLT':
nfiles = unpack('<I',datamlt[0x8:0xC].tostring())[0]
offset = 0x20
for ifile in range(0,nfiles):
fhdr = datamlt[offset+0x0:offset+0x4].tostring()
foff = unpack('<i',datamlt[offset+0x10:offset+0x14].tostring())[0]
fsz = unpack('<i',datamlt[offset+0x14:offset+0x18].tostring())[0]
mbank = unpack('<i',datamlt[offset+0x4:offset+0x8].tostring())[0]
moff = unpack('<i',datamlt[offset+0x8:offset+0xC].tostring())[0]
msz = unpack('<i',datamlt[offset+0xC:offset+0x10].tostring())[0]
#if fhdr in areamap and (fhdr == 'SFPW' or foff >=0): # write area map data if offset is known
dsfbin[areamap[fhdr]+8*mbank+0x0:areamap[fhdr]+8*mbank+0x4] = datamlt[offset+0x8:offset+0xC]
dsfbin[areamap[fhdr]+8*mbank+0x4:areamap[fhdr]+8*mbank+0x8] = datamlt[offset+0xC:offset+0x10]
#if foff >= 0 or fhdr == 'SFPW' or fhdr == 'SPSR': # DEBUG
#fcon.write('%s: bank=0x%02X offset=0x%06X size=0x%06X\n' % (fhdr,mbank,moff,msz)) # DEBUG
if fhdr != 'SFPW' and fhdr != 'SPSR' and foff >= 0: # if DSP work ram or invalid offset, don't write out anything
if fhdr == 'SMPB' and use_osb:
offset += 0x20
continue
if fhdr == 'SOSB' and not use_osb:
offset += 0x20
continue
dsfbin[moff:moff+fsz] = datamlt[foff:foff+fsz] # write sound data
if fhdr == sbank[use_osb] and mbank == bank: # check specifically for sequence data and get number of tracks
ntracks = dsfbin[moff+0xC]
print ('%s data contains %d track(s).' % (sbank[use_osb], ntracks))
offset += 0x20
#fcon.close() # DEBUG
# -----------------------------------------------------------------------------------------------------
# Set sound commands
if track != None:
ctrack = track
else:
ctrack = 0
scbase = {0x01: 0x13200, 0x02: 0x12200}[vdrv]
ssbase = scbase + 0x1F0 # SEQUENCE_START track number offset (for minidsf)
dsfbin[scbase+0x00:scbase+0x10] = sndcmd([0x87,0x00,0x00,0x00,0x00,0x00]) # not sure, but 3rd and 5th bytes have various effects
dsfbin[scbase+0x10:scbase+0x20] = sndcmd([0x82,0x00,mixer]) # MIXER_CHANGE
dsfbin[scbase+0x20:scbase+0x30] = sndcmd([0x84,0x00,effect]) # EFFECT_CHANGE
dsfbin[scbase+0x30:scbase+0x40] = sndcmd([0x81,0x00,0xFF]) # TOTAL_VOLUME
dsfbin[scbase+0x40:scbase+0x50] = sndcmd([0x05+0x10*use_osb,0x00,0x00,volume]) # SEQUENCE_VOLUME (or OSB_VOLUME)
dsfbin[ssbase+0x00:ssbase+0x10] = sndcmd([0x01+0x10*use_osb,0x00,0x00,bank,ctrack]) # SEQUENCE_START (or OSB_START)
# -----------------------------------------------------------------------------------------------------
# Necessary for sound command processing
dsfbin[scbase+0x200] = 0x01
# -----------------------------------------------------------------------------------------------------
# Driver patch for MANATEE.DRV - should cover most versions of the driver
# Comment any of these out as needed
NOPtable = {
0x01: [0x448,0x470,0x488,0x4A4,0x4AC,0x4C4,0x4DC],
0x02: [0x434,0x4CC,0x4E4,0x500,0x508,0x520,0x538],
}[vdrv]
NOP = array('B',[0x00,0x00,0xA0,0xE3])
dsfbin[NOPtable[0]:NOPtable[0]+4] = NOP # don't clear command area
dsfbin[NOPtable[1]:NOPtable[1]+4] = NOP # don't clear MSB banks
dsfbin[NOPtable[2]:NOPtable[2]+4] = NOP # don't clear MPB banks
dsfbin[NOPtable[3]:NOPtable[3]+8] = NOP*2 # don't use default MDB bank 0
dsfbin[NOPtable[4]:NOPtable[4]+8] = NOP*2 # don't use default MDB bank 1
dsfbin[NOPtable[5]:NOPtable[5]+8] = NOP*2 # don't use default FPB
dsfbin[NOPtable[6]:NOPtable[6]+8] = NOP*2 # don't use default FOB
# -----------------------------------------------------------------------------------------------------
# Write output file
(bout,xout) = fnoext(nout)
if use_osb:
xout += '_OSB'
fo = open(xout+'.bin','wb')
fo.write('\x00'*4) # load address
fo.write(dsfbin.tostring())
fo.close()
# -----------------------------------------------------------------------------------------------------
# If track number is explicity given, generate a single dsf file
if track != None:
ext = bout.replace(xout,'')[1:]
os.system('bin2psf %s 18 %s.bin' % (ext,xout))
# Otherwise, automatically create minidsfs based on number of tracks found in bank
else:
# If only 1 track, create dsf file
if ntracks == 1:
os.system('bin2psf dsf 18 %s.bin' % xout)
# Otherwise, create the dsflib file
elif ntracks > 1:
os.system('bin2psf dsflib 18 %s.bin' % xout)
# -----------------------------------------------------------------------------------------------------
# Create minidsfs
for itrack in range(0,ntracks):
fo = open('temp.bin','wb')
fo.write(pack('<I',ssbase+3))
fo.write(pack('B',bank))
fo.write(pack('B',itrack))
fo.close()
os.system('bin2psf minidsf 18 ' + 'temp.bin')
os.system('psfpoint "-_lib='+xout+'.dsflib" '+'temp.minidsf')
strfmt = ('_%02d','_%03d')[use_osb]
fname = xout + '_%02d' % bank + strfmt % itrack + '.minidsf'
if os.access(fname,os.F_OK):
os.remove(fname)
os.rename('temp.minidsf',fname)
# -----------------------------------------------------------------------------------------------------
# Cleanup
if os.access(xout+'.bin',os.F_OK):
os.remove(xout+'.bin')
if os.access('temp.bin',os.F_OK):
os.remove('temp.bin')
# =====================================================================================================
# =====================================================================================================
# Generate dsf file(s). Feel free to customize this section as you see fit (looping through a bunch of files, etc.)
dsfmake(nout,ndrv,nmlt,bank,track,volume,mixer,effect)
# =====================================================================================================
# =====================================================================================================
# Update history:
# 08-10-14 (0.03) - Changed the naming behavior of the output file when automatic dsflib/minidsf creation
# is turned off so that it doesn't force a .dsf extention (useful for custom dsflibs).
# Added a simple utility function that gets the sound RAM offset and allocation for
# a given sound data type and bank (useful for custom dsflib/minidsf creation).
# 08-10-12 (0.02) - Very minor update. Made it possible to turn off automatic dsflib/minidsf creation.
# 08-02-16 (0.01) - Filled in much of the missing information from the initial version - sound data types,
# area map locations, sound commands, driver patches, etc. Added ability to rip one-shot
# data in addition to sequence data.
# 08-01-12 (0.00) - Initial version.
# =====================================================================================================