/** * Import DLS (Sound Bank) * * @author pquiring * * Created : Feb 22, 2014 * * See : DlsBank.cpp from OpenMPT for more info. * */ import java.io.*; import java.util.*; import java.lang.reflect.*; import javaforce.*; import javaforce.media.*; public class DLS { public String errmsg; public static boolean debug = false; //lots of info private final static int IFFID_RIFF = 0x46464952; private final static int IFFID_WAVE = 0x45564157; private final static int IFFID_wave = 0x65766177; private final static int IFFID_XDLS = 0x534c4458; private final static int IFFID_DLS = 0x20534C44; private final static int IFFID_MLS = 0x20534C4D; private final static int IFFID_RMID = 0x44494D52; private final static int IFFID_colh = 0x686C6F63; private final static int IFFID_vers = 0x73726576; private final static int IFFID_msyn = 0x6E79736D; private final static int IFFID_lins = 0x736E696C; private final static int IFFID_ins = 0x20736E69; private final static int IFFID_insh = 0x68736E69; private final static int IFFID_ptbl = 0x6C627470; private final static int IFFID_wvpl = 0x6C707677; private final static int IFFID_rgn = 0x206E6772; private final static int IFFID_rgn2 = 0x326E6772; private final static int IFFID_rgnh = 0x686E6772; private final static int IFFID_wlnk = 0x6B6E6C77; private final static int IFFID_art1 = 0x31747261; private final static int IFFID_LIST = 0x5453494C; private final static int IFFID_INFO = 0x4F464E49; private final static int IFFID_wsmp = 0x706D7377; private final static int IFFID_INAM = 0x4D414E49; private final static int IFFID___X1 = 0x6e67726c; private final static int IFFID___X2 = 0x7472616c; private final static int IFFID_ICOP = 0x504f4349; public static class Region { int loopStart = -1; int loopLength = -1; int waveLink; int percEnv; int volume; // 0..256 int fuOptions; // flags + key group int fineTune; // 1..100 int keyMin; int keyMax; int unityNote; int attenuation; } public static class Instrument { public String name; public int id, len, bank, instrument; public Region regions[]; public short samples[]; public int loopStart = -1; public int loopLength = -1; } private int waveFormsOffsets[]; private byte waveFormPool[]; public Instrument instruments[]; public String version; private String getName(int id) { Field fields[] = this.getClass().getDeclaredFields(); try { for(int a=0;a<fields.length;a++) { String name = fields[a].getName(); if (!name.startsWith("IFFID_")) continue; int fid = fields[a].getInt(null); if (fid == id) return name; } } catch (Exception e) { } return null; } public boolean load(InputStream is) { JFLog.log("Import DLS..."); int nInstruments = -1; int nWaveForms = -1; int nRegions = 0; //total regions of all instruments (debug) errmsg = ""; int cInstrument = -1; int cRegion = -1; int cbSize; try { byte data[] = new byte[32]; //read RIFF header (12 bytes) is.read(data, 0, 12); if (LE.getuint32(data, 0) != IFFID_RIFF) throw new Exception("not a valid RIFF file)"); int riffLength = LE.getuint32(data, 4); if (LE.getuint32(data, 8) != IFFID_DLS) throw new Exception("not a DLS file"); int pos = 4; while (pos < riffLength) { //read chunk ID/length if (debug) JFLog.log("pos=" + pos); is.read(data, 0, 8); int id = LE.getuint32(data, 0); int chunkLength = LE.getuint32(data, 4); int chunkRead = 0; if (debug) JFLog.log("id=0x" + Integer.toString(id, 16) + ":" + getName(id) + ":" + chunkLength); pos += 8; switch (id) { case IFFID_colh: is.read(data, 0, 4); chunkRead = 4; nInstruments = LE.getuint32(data, 0); if (debug) JFLog.log("Found " + nInstruments + " instruments"); instruments = new Instrument[nInstruments]; for(int a=0;a<nInstruments;a++) { instruments[a] = new Instrument(); } break; case IFFID_vers: is.read(data, 0, 4); chunkRead = 4; version = LE.getString(data, 0, 4); break; case IFFID_ptbl: is.read(data, 0, 8); cbSize = LE.getuint32(data, 0); nWaveForms = LE.getuint32(data, 4); if (debug) JFLog.log("waveForms=" + nWaveForms + ",total regions=" + nRegions); if (cbSize > 8) is.skip(cbSize - 8); waveFormsOffsets = new int[nWaveForms]; byte offsets[] = new byte[nWaveForms * 4]; is.read(offsets); for(int a=0;a<nWaveForms;a++) { int offset = LE.getuint32(offsets, a * 4); if (debug) JFLog.log("offset=" + offset); waveFormsOffsets[a] = offset; } chunkRead = cbSize + (nWaveForms * 4); break; case IFFID_LIST: //read list id (which is included in chunkLength) is.read(data, 0, 4); chunkRead = 4; int listid = LE.getuint32(data, 0); if (debug) JFLog.log("listid=0x" + Integer.toString(listid, 16) + ":" + getName(listid)); switch(listid) { case IFFID___X1: //regions list ??? case IFFID___X2: //art1 list ??? case IFFID_rgn: //region list case IFFID_lins: //list of instruments case IFFID_INFO: //more info chunkLength = chunkRead = 4; //process sub-chunks break; case IFFID_ins: //instrument chunkLength = chunkRead = 4; //process sub-chunks cInstrument++; cRegion = -1; if (debug) JFLog.log("next instrument"); break; case IFFID_wvpl: // case IFFID_sdta: //SF2 //waveform pool waveFormPool = new byte[chunkLength - 4]; //-4 for listid (which is part of chunk) is.read(waveFormPool); if (debug) { FileOutputStream fos = new FileOutputStream("wavepool.dat"); fos.write(waveFormPool); fos.close(); } chunkRead = chunkLength; break; } break; case IFFID_insh: //instrument header is.read(data, 0, 4 * 3); chunkRead += 4 * 3; instruments[cInstrument].regions = new Region[LE.getuint32(data, 0)]; if (debug) JFLog.log("Found " + instruments[cInstrument].regions.length + " regions"); nRegions += instruments[cInstrument].regions.length; for(int a=0;a<instruments[cInstrument].regions.length;a++) { instruments[cInstrument].regions[a] = new Region(); } instruments[cInstrument].bank = LE.getuint32(data, 4); instruments[cInstrument].instrument = LE.getuint32(data, 12); break; case IFFID_rgnh: //region header is.read(data, 0, 6 * 2); chunkRead += 6 * 2; int rangeKeyLow = LE.getuint16(data, 0); int rangeKeyHigh = LE.getuint16(data, 2); int rangeVelocityLow = LE.getuint16(data, 4); int rangeVelocityHigh = LE.getuint16(data, 6); int fusOptions = LE.getuint16(data, 8); int usKeyGroup = LE.getuint16(data, 10); cRegion++; if (debug) JFLog.log("next region, range=" + rangeKeyLow + "," + rangeKeyHigh); instruments[cInstrument].regions[cRegion].keyMin = rangeKeyLow; instruments[cInstrument].regions[cRegion].keyMax = rangeKeyHigh; break; case IFFID_wlnk: //wave link is.read(data, 0, 12); chunkRead += 12; int _fusOptions = LE.getuint16(data, 0); int phaseGroup = LE.getuint16(data, 2); int channel = LE.getuint32(data, 4); int tableIndex = LE.getuint32(data, 8); if (debug) JFLog.log("link:" + phaseGroup + "," + channel + "," + tableIndex); instruments[cInstrument].regions[cRegion].waveLink = tableIndex; break; case IFFID_wsmp: //wave samples header is.read(data, 0, 20); chunkRead += 20; cbSize = LE.getuint32(data, 0); int unityNote = LE.getuint16(data, 4); int fineTune = LE.getuint16(data, 6); int attenuation = LE.getuint32(data, 8); int fuOptions = LE.getuint32(data, 12); int cSampleLoops = LE.getuint32(data, 16); if (cbSize > 20) { cbSize -= 20; //already read 20 bytes is.skip(cbSize); chunkRead += cbSize; } instruments[cInstrument].regions[cRegion].unityNote = unityNote; instruments[cInstrument].regions[cRegion].fineTune = fineTune; instruments[cInstrument].regions[cRegion].attenuation = attenuation; instruments[cInstrument].regions[cRegion].fuOptions = fuOptions; if (debug) JFLog.log("cSampleLoops = " + cSampleLoops); if (cSampleLoops != 0) { //samples is.read(data, 0, 16); chunkRead += 16; cbSize = LE.getuint32(data, 0); int loopType = LE.getuint32(data, 4); int loopStart = LE.getuint32(data, 8); int loopLength = LE.getuint32(data, 12); if (debug) JFLog.log("Region Loop:" + loopStart + "," + loopLength + "," + loopType); instruments[cInstrument].regions[cRegion].loopStart = loopStart; instruments[cInstrument].regions[cRegion].loopLength = loopLength; } break; case IFFID_INAM: byte str[] = new byte[chunkLength]; is.read(str); chunkRead += chunkLength; instruments[cInstrument].name = LE.getString(str, 0, chunkLength).trim(); break; } if (chunkRead != chunkLength) is.skip(chunkLength - chunkRead); pos += chunkLength; //each chunk MUST start on a WORD boundry if ((chunkLength & 1) == 1) { is.skip(1); pos++; } } //ensure names are unique (not really needed unless you get by names) for(int a=0;a<instruments.length;a++) { Instrument i = instruments[a]; //change duplicate names to something unique boolean dup; char ch = 'a'; String name = i.name; do { dup = false; for(int b=0;b<instruments.length;b++) { if (b == a) continue; if (instruments[b].name.equals(i.name)) { i.name = name + ch; ch++; dup = true; break; } } } while (dup); if (debug) JFLog.log("Instrument:" + i.name); } JFLog.log("Import complete"); return true; } catch (Exception e) { JFLog.log(e); return false; } } public Instrument buildSamples(Instrument i, int region) { //each region is a wav file try { Region r = i.regions[region]; i.loopStart = r.loopStart; i.loopLength = r.loopLength; int offset = waveFormsOffsets[r.waveLink]; if (debug) JFLog.log("offset=" + offset); ByteArrayInputStream bais = new ByteArrayInputStream(waveFormPool); bais.skip(offset); byte chunk[] = new byte[4 * 3]; bais.read(chunk); int id = LE.getuint32(chunk, 0); int chunkLength = LE.getuint32(chunk, 4); int subid = LE.getuint32(chunk, 8); if (debug) { JFLog.log("id=0x" + Integer.toString(id, 16) + ":" + getName(id) + ":" + chunkLength); JFLog.log("listid=0x" + Integer.toString(subid, 16) + ":" + getName(id)); } if (id == IFFID_LIST && subid == IFFID_wave) { LE.setuint32(waveFormPool, offset, IFFID_RIFF); id = IFFID_RIFF; LE.setuint32(waveFormPool, offset + 8, IFFID_WAVE); subid = IFFID_WAVE; } if (id != IFFID_RIFF || subid != IFFID_WAVE) { throw new Exception("WAV not found"); } bais.reset(); bais.skip(offset); Wav wav = new Wav(); if (!wav.load(bais)) { throw new Exception("WAV load failed"); } wav.readAllSamples(); i.samples = wav.samples16; return i; } catch (Exception e) { JFLog.log(e); return null; } } public int getRegionsCount(String name) { for(int a=0;a<instruments.length;a++) { Instrument i = instruments[a]; if (i.name.equals(name)) return i.regions.length; } JFLog.log("Instrument not found:" + name); return 0; } /** Returns the key range that a range should be used within. * * @return int[] : [0]=min Key [1]=max Key */ public Region getRegion(String name, int region) { for(int a=0;a<instruments.length;a++) { Instrument i = instruments[a]; if (i.name.equals(name)) { Region r = i.regions[region]; return r; } } JFLog.log("Instrument not found:" + name); return null; } //C-2=0x00 C-1=0x0c C0=0x18 C1=0x24 C2=0x30 ... C8=0x78 ... C9=0x7f private static String notes[] = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"}; public static String getKeyName(int key) { int octave = key / 12; int note = key % 12; return notes[note] + octave; } public Instrument getInstrument(String name, int region) { for(int a=0;a<instruments.length;a++) { Instrument i = instruments[a]; if (i.name.equals(name)) return buildSamples(i, region); } JFLog.log("Instrument not found:" + name); return null; } public String[] getInstrumentNames() { ArrayList<String> names = new ArrayList<String>(); for(int a=0;a<instruments.length;a++) { Instrument i = instruments[a]; names.add(i.name); } return names.toArray(new String[0]); } }