/*
 * Copyright (c) 2007, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package com.sun.media.sound;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import javax.sound.midi.Instrument;
import javax.sound.midi.Patch;
import javax.sound.midi.Soundbank;
import javax.sound.midi.SoundbankResource;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.AudioFormat.Encoding;

A DLS Level 1 and Level 2 soundbank reader (from files/url/streams).
Author:Karl Helgason
/** * A DLS Level 1 and Level 2 soundbank reader (from files/url/streams). * * @author Karl Helgason */
public final class DLSSoundbank implements Soundbank { static private class DLSID { long i1; int s1; int s2; int x1; int x2; int x3; int x4; int x5; int x6; int x7; int x8; private DLSID() { } DLSID(long i1, int s1, int s2, int x1, int x2, int x3, int x4, int x5, int x6, int x7, int x8) { this.i1 = i1; this.s1 = s1; this.s2 = s2; this.x1 = x1; this.x2 = x2; this.x3 = x3; this.x4 = x4; this.x5 = x5; this.x6 = x6; this.x7 = x7; this.x8 = x8; } public static DLSID read(RIFFReader riff) throws IOException { DLSID d = new DLSID(); d.i1 = riff.readUnsignedInt(); d.s1 = riff.readUnsignedShort(); d.s2 = riff.readUnsignedShort(); d.x1 = riff.readUnsignedByte(); d.x2 = riff.readUnsignedByte(); d.x3 = riff.readUnsignedByte(); d.x4 = riff.readUnsignedByte(); d.x5 = riff.readUnsignedByte(); d.x6 = riff.readUnsignedByte(); d.x7 = riff.readUnsignedByte(); d.x8 = riff.readUnsignedByte(); return d; } public int hashCode() { return (int)i1; } public boolean equals(Object obj) { if (!(obj instanceof DLSID)) { return false; } DLSID t = (DLSID) obj; return i1 == t.i1 && s1 == t.s1 && s2 == t.s2 && x1 == t.x1 && x2 == t.x2 && x3 == t.x3 && x4 == t.x4 && x5 == t.x5 && x6 == t.x6 && x7 == t.x7 && x8 == t.x8; } }
X = X & Y
/** X = X & Y */
private static final int DLS_CDL_AND = 0x0001;
X = X | Y
/** X = X | Y */
private static final int DLS_CDL_OR = 0x0002;
X = X ^ Y
/** X = X ^ Y */
private static final int DLS_CDL_XOR = 0x0003;
X = X + Y
/** X = X + Y */
private static final int DLS_CDL_ADD = 0x0004;
X = X - Y
/** X = X - Y */
private static final int DLS_CDL_SUBTRACT = 0x0005;
X = X * Y
/** X = X * Y */
private static final int DLS_CDL_MULTIPLY = 0x0006;
X = X / Y
/** X = X / Y */
private static final int DLS_CDL_DIVIDE = 0x0007;
X = X && Y
/** X = X && Y */
private static final int DLS_CDL_LOGICAL_AND = 0x0008;
X = X || Y
/** X = X || Y */
private static final int DLS_CDL_LOGICAL_OR = 0x0009;
X = (X < Y)
/** X = (X < Y) */
private static final int DLS_CDL_LT = 0x000A;
X = (X <= Y)
/** X = (X <= Y) */
private static final int DLS_CDL_LE = 0x000B;
X = (X > Y)
/** X = (X > Y) */
private static final int DLS_CDL_GT = 0x000C;
X = (X >= Y)
/** X = (X >= Y) */
private static final int DLS_CDL_GE = 0x000D;
X = (X == Y)
/** X = (X == Y) */
private static final int DLS_CDL_EQ = 0x000E;
X = !X
/** X = !X */
private static final int DLS_CDL_NOT = 0x000F;
32-bit constant
/** 32-bit constant */
private static final int DLS_CDL_CONST = 0x0010;
32-bit value returned from query
/** 32-bit value returned from query */
private static final int DLS_CDL_QUERY = 0x0011;
32-bit value returned from query
/** 32-bit value returned from query */
private static final int DLS_CDL_QUERYSUPPORTED = 0x0012; private static final DLSID DLSID_GMInHardware = new DLSID(0x178f2f24, 0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12); private static final DLSID DLSID_GSInHardware = new DLSID(0x178f2f25, 0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12); private static final DLSID DLSID_XGInHardware = new DLSID(0x178f2f26, 0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12); private static final DLSID DLSID_SupportsDLS1 = new DLSID(0x178f2f27, 0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12); private static final DLSID DLSID_SupportsDLS2 = new DLSID(0xf14599e5, 0x4689, 0x11d2, 0xaf, 0xa6, 0x0, 0xaa, 0x0, 0x24, 0xd8, 0xb6); private static final DLSID DLSID_SampleMemorySize = new DLSID(0x178f2f28, 0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12); private static final DLSID DLSID_ManufacturersID = new DLSID(0xb03e1181, 0x8095, 0x11d2, 0xa1, 0xef, 0x0, 0x60, 0x8, 0x33, 0xdb, 0xd8); private static final DLSID DLSID_ProductID = new DLSID(0xb03e1182, 0x8095, 0x11d2, 0xa1, 0xef, 0x0, 0x60, 0x8, 0x33, 0xdb, 0xd8); private static final DLSID DLSID_SamplePlaybackRate = new DLSID(0x2a91f713, 0xa4bf, 0x11d2, 0xbb, 0xdf, 0x0, 0x60, 0x8, 0x33, 0xdb, 0xd8); private long major = -1; private long minor = -1; private final DLSInfo info = new DLSInfo(); private final List<DLSInstrument> instruments = new ArrayList<DLSInstrument>(); private final List<DLSSample> samples = new ArrayList<DLSSample>(); private boolean largeFormat = false; private File sampleFile; public DLSSoundbank() { } public DLSSoundbank(URL url) throws IOException { InputStream is = url.openStream(); try { readSoundbank(is); } finally { is.close(); } } public DLSSoundbank(File file) throws IOException { largeFormat = true; sampleFile = file; InputStream is = new FileInputStream(file); try { readSoundbank(is); } finally { is.close(); } } public DLSSoundbank(InputStream inputstream) throws IOException { readSoundbank(inputstream); } private void readSoundbank(InputStream inputstream) throws IOException { RIFFReader riff = new RIFFReader(inputstream); if (!riff.getFormat().equals("RIFF")) { throw new RIFFInvalidFormatException( "Input stream is not a valid RIFF stream!"); } if (!riff.getType().equals("DLS ")) { throw new RIFFInvalidFormatException( "Input stream is not a valid DLS soundbank!"); } while (riff.hasNextChunk()) { RIFFReader chunk = riff.nextChunk(); if (chunk.getFormat().equals("LIST")) { if (chunk.getType().equals("INFO")) readInfoChunk(chunk); if (chunk.getType().equals("lins")) readLinsChunk(chunk); if (chunk.getType().equals("wvpl")) readWvplChunk(chunk); } else { if (chunk.getFormat().equals("cdl ")) { if (!readCdlChunk(chunk)) { throw new RIFFInvalidFormatException( "DLS file isn't supported!"); } } if (chunk.getFormat().equals("colh")) { // skipped because we will load the entire bank into memory // long instrumentcount = chunk.readUnsignedInt(); // System.out.println("instrumentcount = "+ instrumentcount); } if (chunk.getFormat().equals("ptbl")) { // Pool Table Chunk // skipped because we will load the entire bank into memory } if (chunk.getFormat().equals("vers")) { major = chunk.readUnsignedInt(); minor = chunk.readUnsignedInt(); } } } for (Map.Entry<DLSRegion, Long> entry : temp_rgnassign.entrySet()) { entry.getKey().sample = samples.get((int)entry.getValue().longValue()); } temp_rgnassign = null; } private boolean cdlIsQuerySupported(DLSID uuid) { return uuid.equals(DLSID_GMInHardware) || uuid.equals(DLSID_GSInHardware) || uuid.equals(DLSID_XGInHardware) || uuid.equals(DLSID_SupportsDLS1) || uuid.equals(DLSID_SupportsDLS2) || uuid.equals(DLSID_SampleMemorySize) || uuid.equals(DLSID_ManufacturersID) || uuid.equals(DLSID_ProductID) || uuid.equals(DLSID_SamplePlaybackRate); } private long cdlQuery(DLSID uuid) { if (uuid.equals(DLSID_GMInHardware)) return 1; if (uuid.equals(DLSID_GSInHardware)) return 0; if (uuid.equals(DLSID_XGInHardware)) return 0; if (uuid.equals(DLSID_SupportsDLS1)) return 1; if (uuid.equals(DLSID_SupportsDLS2)) return 1; if (uuid.equals(DLSID_SampleMemorySize)) return Runtime.getRuntime().totalMemory(); if (uuid.equals(DLSID_ManufacturersID)) return 0; if (uuid.equals(DLSID_ProductID)) return 0; if (uuid.equals(DLSID_SamplePlaybackRate)) return 44100; return 0; } // Reading cdl-ck Chunk // "cdl " chunk can only appear inside : DLS,lart,lar2,rgn,rgn2 private boolean readCdlChunk(RIFFReader riff) throws IOException { DLSID uuid; long x; long y; Stack<Long> stack = new Stack<Long>(); while (riff.available() != 0) { int opcode = riff.readUnsignedShort(); switch (opcode) { case DLS_CDL_AND: x = stack.pop(); y = stack.pop(); stack.push(Long.valueOf(((x != 0) && (y != 0)) ? 1 : 0)); break; case DLS_CDL_OR: x = stack.pop(); y = stack.pop(); stack.push(Long.valueOf(((x != 0) || (y != 0)) ? 1 : 0)); break; case DLS_CDL_XOR: x = stack.pop(); y = stack.pop(); stack.push(Long.valueOf(((x != 0) ^ (y != 0)) ? 1 : 0)); break; case DLS_CDL_ADD: x = stack.pop(); y = stack.pop(); stack.push(Long.valueOf(x + y)); break; case DLS_CDL_SUBTRACT: x = stack.pop(); y = stack.pop(); stack.push(Long.valueOf(x - y)); break; case DLS_CDL_MULTIPLY: x = stack.pop(); y = stack.pop(); stack.push(Long.valueOf(x * y)); break; case DLS_CDL_DIVIDE: x = stack.pop(); y = stack.pop(); stack.push(Long.valueOf(x / y)); break; case DLS_CDL_LOGICAL_AND: x = stack.pop(); y = stack.pop(); stack.push(Long.valueOf(((x != 0) && (y != 0)) ? 1 : 0)); break; case DLS_CDL_LOGICAL_OR: x = stack.pop(); y = stack.pop(); stack.push(Long.valueOf(((x != 0) || (y != 0)) ? 1 : 0)); break; case DLS_CDL_LT: x = stack.pop(); y = stack.pop(); stack.push(Long.valueOf((x < y) ? 1 : 0)); break; case DLS_CDL_LE: x = stack.pop(); y = stack.pop(); stack.push(Long.valueOf((x <= y) ? 1 : 0)); break; case DLS_CDL_GT: x = stack.pop(); y = stack.pop(); stack.push(Long.valueOf((x > y) ? 1 : 0)); break; case DLS_CDL_GE: x = stack.pop(); y = stack.pop(); stack.push(Long.valueOf((x >= y) ? 1 : 0)); break; case DLS_CDL_EQ: x = stack.pop(); y = stack.pop(); stack.push(Long.valueOf((x == y) ? 1 : 0)); break; case DLS_CDL_NOT: x = stack.pop(); y = stack.pop(); stack.push(Long.valueOf((x == 0) ? 1 : 0)); break; case DLS_CDL_CONST: stack.push(Long.valueOf(riff.readUnsignedInt())); break; case DLS_CDL_QUERY: uuid = DLSID.read(riff); stack.push(cdlQuery(uuid)); break; case DLS_CDL_QUERYSUPPORTED: uuid = DLSID.read(riff); stack.push(Long.valueOf(cdlIsQuerySupported(uuid) ? 1 : 0)); break; default: break; } } if (stack.isEmpty()) return false; return stack.pop() == 1; } private void readInfoChunk(RIFFReader riff) throws IOException { info.name = null; while (riff.hasNextChunk()) { RIFFReader chunk = riff.nextChunk(); String format = chunk.getFormat(); if (format.equals("INAM")) info.name = chunk.readString(chunk.available()); else if (format.equals("ICRD")) info.creationDate = chunk.readString(chunk.available()); else if (format.equals("IENG")) info.engineers = chunk.readString(chunk.available()); else if (format.equals("IPRD")) info.product = chunk.readString(chunk.available()); else if (format.equals("ICOP")) info.copyright = chunk.readString(chunk.available()); else if (format.equals("ICMT")) info.comments = chunk.readString(chunk.available()); else if (format.equals("ISFT")) info.tools = chunk.readString(chunk.available()); else if (format.equals("IARL")) info.archival_location = chunk.readString(chunk.available()); else if (format.equals("IART")) info.artist = chunk.readString(chunk.available()); else if (format.equals("ICMS")) info.commissioned = chunk.readString(chunk.available()); else if (format.equals("IGNR")) info.genre = chunk.readString(chunk.available()); else if (format.equals("IKEY")) info.keywords = chunk.readString(chunk.available()); else if (format.equals("IMED")) info.medium = chunk.readString(chunk.available()); else if (format.equals("ISBJ")) info.subject = chunk.readString(chunk.available()); else if (format.equals("ISRC")) info.source = chunk.readString(chunk.available()); else if (format.equals("ISRF")) info.source_form = chunk.readString(chunk.available()); else if (format.equals("ITCH")) info.technician = chunk.readString(chunk.available()); } } private void readLinsChunk(RIFFReader riff) throws IOException { while (riff.hasNextChunk()) { RIFFReader chunk = riff.nextChunk(); if (chunk.getFormat().equals("LIST")) { if (chunk.getType().equals("ins ")) readInsChunk(chunk); } } } private void readInsChunk(RIFFReader riff) throws IOException { DLSInstrument instrument = new DLSInstrument(this); while (riff.hasNextChunk()) { RIFFReader chunk = riff.nextChunk(); String format = chunk.getFormat(); if (format.equals("LIST")) { if (chunk.getType().equals("INFO")) { readInsInfoChunk(instrument, chunk); } if (chunk.getType().equals("lrgn")) { while (chunk.hasNextChunk()) { RIFFReader subchunk = chunk.nextChunk(); if (subchunk.getFormat().equals("LIST")) { if (subchunk.getType().equals("rgn ")) { DLSRegion split = new DLSRegion(); if (readRgnChunk(split, subchunk)) instrument.getRegions().add(split); } if (subchunk.getType().equals("rgn2")) { // support for DLS level 2 regions DLSRegion split = new DLSRegion(); if (readRgnChunk(split, subchunk)) instrument.getRegions().add(split); } } } } if (chunk.getType().equals("lart")) { List<DLSModulator> modlist = new ArrayList<DLSModulator>(); while (chunk.hasNextChunk()) { RIFFReader subchunk = chunk.nextChunk(); if (chunk.getFormat().equals("cdl ")) { if (!readCdlChunk(chunk)) { modlist.clear(); break; } } if (subchunk.getFormat().equals("art1")) readArt1Chunk(modlist, subchunk); } instrument.getModulators().addAll(modlist); } if (chunk.getType().equals("lar2")) { // support for DLS level 2 ART List<DLSModulator> modlist = new ArrayList<DLSModulator>(); while (chunk.hasNextChunk()) { RIFFReader subchunk = chunk.nextChunk(); if (chunk.getFormat().equals("cdl ")) { if (!readCdlChunk(chunk)) { modlist.clear(); break; } } if (subchunk.getFormat().equals("art2")) readArt2Chunk(modlist, subchunk); } instrument.getModulators().addAll(modlist); } } else { if (format.equals("dlid")) { instrument.guid = new byte[16]; chunk.readFully(instrument.guid); } if (format.equals("insh")) { chunk.readUnsignedInt(); // Read Region Count - ignored int bank = chunk.read(); // LSB bank += (chunk.read() & 127) << 7; // MSB chunk.read(); // Read Reserved byte int drumins = chunk.read(); // Drum Instrument int id = chunk.read() & 127; // Read only first 7 bits chunk.read(); // Read Reserved byte chunk.read(); // Read Reserved byte chunk.read(); // Read Reserved byte instrument.bank = bank; instrument.preset = (int) id; instrument.druminstrument = (drumins & 128) > 0; //System.out.println("bank="+bank+" drumkit="+drumkit // +" id="+id); } } } instruments.add(instrument); } private void readArt1Chunk(List<DLSModulator> modulators, RIFFReader riff) throws IOException { long size = riff.readUnsignedInt(); long count = riff.readUnsignedInt(); if (size - 8 != 0) riff.skip(size - 8); for (int i = 0; i < count; i++) { DLSModulator modulator = new DLSModulator(); modulator.version = 1; modulator.source = riff.readUnsignedShort(); modulator.control = riff.readUnsignedShort(); modulator.destination = riff.readUnsignedShort(); modulator.transform = riff.readUnsignedShort(); modulator.scale = riff.readInt(); modulators.add(modulator); } } private void readArt2Chunk(List<DLSModulator> modulators, RIFFReader riff) throws IOException { long size = riff.readUnsignedInt(); long count = riff.readUnsignedInt(); if (size - 8 != 0) riff.skip(size - 8); for (int i = 0; i < count; i++) { DLSModulator modulator = new DLSModulator(); modulator.version = 2; modulator.source = riff.readUnsignedShort(); modulator.control = riff.readUnsignedShort(); modulator.destination = riff.readUnsignedShort(); modulator.transform = riff.readUnsignedShort(); modulator.scale = riff.readInt(); modulators.add(modulator); } } private Map<DLSRegion, Long> temp_rgnassign = new HashMap<DLSRegion, Long>(); private boolean readRgnChunk(DLSRegion split, RIFFReader riff) throws IOException { while (riff.hasNextChunk()) { RIFFReader chunk = riff.nextChunk(); String format = chunk.getFormat(); if (format.equals("LIST")) { if (chunk.getType().equals("lart")) { List<DLSModulator> modlist = new ArrayList<DLSModulator>(); while (chunk.hasNextChunk()) { RIFFReader subchunk = chunk.nextChunk(); if (chunk.getFormat().equals("cdl ")) { if (!readCdlChunk(chunk)) { modlist.clear(); break; } } if (subchunk.getFormat().equals("art1")) readArt1Chunk(modlist, subchunk); } split.getModulators().addAll(modlist); } if (chunk.getType().equals("lar2")) { // support for DLS level 2 ART List<DLSModulator> modlist = new ArrayList<DLSModulator>(); while (chunk.hasNextChunk()) { RIFFReader subchunk = chunk.nextChunk(); if (chunk.getFormat().equals("cdl ")) { if (!readCdlChunk(chunk)) { modlist.clear(); break; } } if (subchunk.getFormat().equals("art2")) readArt2Chunk(modlist, subchunk); } split.getModulators().addAll(modlist); } } else { if (format.equals("cdl ")) { if (!readCdlChunk(chunk)) return false; } if (format.equals("rgnh")) { split.keyfrom = chunk.readUnsignedShort(); split.keyto = chunk.readUnsignedShort(); split.velfrom = chunk.readUnsignedShort(); split.velto = chunk.readUnsignedShort(); split.options = chunk.readUnsignedShort(); split.exclusiveClass = chunk.readUnsignedShort(); } if (format.equals("wlnk")) { split.fusoptions = chunk.readUnsignedShort(); split.phasegroup = chunk.readUnsignedShort(); split.channel = chunk.readUnsignedInt(); long sampleid = chunk.readUnsignedInt(); temp_rgnassign.put(split, sampleid); } if (format.equals("wsmp")) { split.sampleoptions = new DLSSampleOptions(); readWsmpChunk(split.sampleoptions, chunk); } } } return true; } private void readWsmpChunk(DLSSampleOptions sampleOptions, RIFFReader riff) throws IOException { long size = riff.readUnsignedInt(); sampleOptions.unitynote = riff.readUnsignedShort(); sampleOptions.finetune = riff.readShort(); sampleOptions.attenuation = riff.readInt(); sampleOptions.options = riff.readUnsignedInt(); long loops = riff.readInt(); if (size > 20) riff.skip(size - 20); for (int i = 0; i < loops; i++) { DLSSampleLoop loop = new DLSSampleLoop(); long size2 = riff.readUnsignedInt(); loop.type = riff.readUnsignedInt(); loop.start = riff.readUnsignedInt(); loop.length = riff.readUnsignedInt(); sampleOptions.loops.add(loop); if (size2 > 16) riff.skip(size2 - 16); } } private void readInsInfoChunk(DLSInstrument dlsinstrument, RIFFReader riff) throws IOException { dlsinstrument.info.name = null; while (riff.hasNextChunk()) { RIFFReader chunk = riff.nextChunk(); String format = chunk.getFormat(); if (format.equals("INAM")) { dlsinstrument.info.name = chunk.readString(chunk.available()); } else if (format.equals("ICRD")) { dlsinstrument.info.creationDate = chunk.readString(chunk.available()); } else if (format.equals("IENG")) { dlsinstrument.info.engineers = chunk.readString(chunk.available()); } else if (format.equals("IPRD")) { dlsinstrument.info.product = chunk.readString(chunk.available()); } else if (format.equals("ICOP")) { dlsinstrument.info.copyright = chunk.readString(chunk.available()); } else if (format.equals("ICMT")) { dlsinstrument.info.comments = chunk.readString(chunk.available()); } else if (format.equals("ISFT")) { dlsinstrument.info.tools = chunk.readString(chunk.available()); } else if (format.equals("IARL")) { dlsinstrument.info.archival_location = chunk.readString(chunk.available()); } else if (format.equals("IART")) { dlsinstrument.info.artist = chunk.readString(chunk.available()); } else if (format.equals("ICMS")) { dlsinstrument.info.commissioned = chunk.readString(chunk.available()); } else if (format.equals("IGNR")) { dlsinstrument.info.genre = chunk.readString(chunk.available()); } else if (format.equals("IKEY")) { dlsinstrument.info.keywords = chunk.readString(chunk.available()); } else if (format.equals("IMED")) { dlsinstrument.info.medium = chunk.readString(chunk.available()); } else if (format.equals("ISBJ")) { dlsinstrument.info.subject = chunk.readString(chunk.available()); } else if (format.equals("ISRC")) { dlsinstrument.info.source = chunk.readString(chunk.available()); } else if (format.equals("ISRF")) { dlsinstrument.info.source_form = chunk.readString(chunk.available()); } else if (format.equals("ITCH")) { dlsinstrument.info.technician = chunk.readString(chunk.available()); } } } private void readWvplChunk(RIFFReader riff) throws IOException { while (riff.hasNextChunk()) { RIFFReader chunk = riff.nextChunk(); if (chunk.getFormat().equals("LIST")) { if (chunk.getType().equals("wave")) readWaveChunk(chunk); } } } private void readWaveChunk(RIFFReader riff) throws IOException { DLSSample sample = new DLSSample(this); while (riff.hasNextChunk()) { RIFFReader chunk = riff.nextChunk(); String format = chunk.getFormat(); if (format.equals("LIST")) { if (chunk.getType().equals("INFO")) { readWaveInfoChunk(sample, chunk); } } else { if (format.equals("dlid")) { sample.guid = new byte[16]; chunk.readFully(sample.guid); } if (format.equals("fmt ")) { int sampleformat = chunk.readUnsignedShort(); if (sampleformat != 1 && sampleformat != 3) { throw new RIFFInvalidDataException( "Only PCM samples are supported!"); } int channels = chunk.readUnsignedShort(); long samplerate = chunk.readUnsignedInt(); // bytes per sec /* long framerate = */ chunk.readUnsignedInt(); // block align, framesize int framesize = chunk.readUnsignedShort(); int bits = chunk.readUnsignedShort(); AudioFormat audioformat = null; if (sampleformat == 1) { if (bits == 8) { audioformat = new AudioFormat( Encoding.PCM_UNSIGNED, samplerate, bits, channels, framesize, samplerate, false); } else { audioformat = new AudioFormat( Encoding.PCM_SIGNED, samplerate, bits, channels, framesize, samplerate, false); } } if (sampleformat == 3) { audioformat = new AudioFormat( Encoding.PCM_FLOAT, samplerate, bits, channels, framesize, samplerate, false); } sample.format = audioformat; } if (format.equals("data")) { if (largeFormat) { sample.setData(new ModelByteBuffer(sampleFile, chunk.getFilePointer(), chunk.available())); } else { byte[] buffer = new byte[chunk.available()]; // chunk.read(buffer); sample.setData(buffer); int read = 0; int avail = chunk.available(); while (read != avail) { if (avail - read > 65536) { chunk.readFully(buffer, read, 65536); read += 65536; } else { chunk.readFully(buffer, read, avail - read); read = avail; } } } } if (format.equals("wsmp")) { sample.sampleoptions = new DLSSampleOptions(); readWsmpChunk(sample.sampleoptions, chunk); } } } samples.add(sample); } private void readWaveInfoChunk(DLSSample dlssample, RIFFReader riff) throws IOException { dlssample.info.name = null; while (riff.hasNextChunk()) { RIFFReader chunk = riff.nextChunk(); String format = chunk.getFormat(); if (format.equals("INAM")) { dlssample.info.name = chunk.readString(chunk.available()); } else if (format.equals("ICRD")) { dlssample.info.creationDate = chunk.readString(chunk.available()); } else if (format.equals("IENG")) { dlssample.info.engineers = chunk.readString(chunk.available()); } else if (format.equals("IPRD")) { dlssample.info.product = chunk.readString(chunk.available()); } else if (format.equals("ICOP")) { dlssample.info.copyright = chunk.readString(chunk.available()); } else if (format.equals("ICMT")) { dlssample.info.comments = chunk.readString(chunk.available()); } else if (format.equals("ISFT")) { dlssample.info.tools = chunk.readString(chunk.available()); } else if (format.equals("IARL")) { dlssample.info.archival_location = chunk.readString(chunk.available()); } else if (format.equals("IART")) { dlssample.info.artist = chunk.readString(chunk.available()); } else if (format.equals("ICMS")) { dlssample.info.commissioned = chunk.readString(chunk.available()); } else if (format.equals("IGNR")) { dlssample.info.genre = chunk.readString(chunk.available()); } else if (format.equals("IKEY")) { dlssample.info.keywords = chunk.readString(chunk.available()); } else if (format.equals("IMED")) { dlssample.info.medium = chunk.readString(chunk.available()); } else if (format.equals("ISBJ")) { dlssample.info.subject = chunk.readString(chunk.available()); } else if (format.equals("ISRC")) { dlssample.info.source = chunk.readString(chunk.available()); } else if (format.equals("ISRF")) { dlssample.info.source_form = chunk.readString(chunk.available()); } else if (format.equals("ITCH")) { dlssample.info.technician = chunk.readString(chunk.available()); } } } public void save(String name) throws IOException { writeSoundbank(new RIFFWriter(name, "DLS ")); } public void save(File file) throws IOException { writeSoundbank(new RIFFWriter(file, "DLS ")); } public void save(OutputStream out) throws IOException { writeSoundbank(new RIFFWriter(out, "DLS ")); } private void writeSoundbank(RIFFWriter writer) throws IOException { RIFFWriter colh_chunk = writer.writeChunk("colh"); colh_chunk.writeUnsignedInt(instruments.size()); if (major != -1 && minor != -1) { RIFFWriter vers_chunk = writer.writeChunk("vers"); vers_chunk.writeUnsignedInt(major); vers_chunk.writeUnsignedInt(minor); } writeInstruments(writer.writeList("lins")); RIFFWriter ptbl = writer.writeChunk("ptbl"); ptbl.writeUnsignedInt(8); ptbl.writeUnsignedInt(samples.size()); long ptbl_offset = writer.getFilePointer(); for (int i = 0; i < samples.size(); i++) ptbl.writeUnsignedInt(0); RIFFWriter wvpl = writer.writeList("wvpl"); long off = wvpl.getFilePointer(); List<Long> offsettable = new ArrayList<Long>(); for (DLSSample sample : samples) { offsettable.add(Long.valueOf(wvpl.getFilePointer() - off)); writeSample(wvpl.writeList("wave"), sample); } // small cheat, we are going to rewrite data back in wvpl long bak = writer.getFilePointer(); writer.seek(ptbl_offset); writer.setWriteOverride(true); for (Long offset : offsettable) writer.writeUnsignedInt(offset.longValue()); writer.setWriteOverride(false); writer.seek(bak); writeInfo(writer.writeList("INFO"), info); writer.close(); } private void writeSample(RIFFWriter writer, DLSSample sample) throws IOException { AudioFormat audioformat = sample.getFormat(); Encoding encoding = audioformat.getEncoding(); float sampleRate = audioformat.getSampleRate(); int sampleSizeInBits = audioformat.getSampleSizeInBits(); int channels = audioformat.getChannels(); int frameSize = audioformat.getFrameSize(); float frameRate = audioformat.getFrameRate(); boolean bigEndian = audioformat.isBigEndian(); boolean convert_needed = false; if (audioformat.getSampleSizeInBits() == 8) { if (!encoding.equals(Encoding.PCM_UNSIGNED)) { encoding = Encoding.PCM_UNSIGNED; convert_needed = true; } } else { if (!encoding.equals(Encoding.PCM_SIGNED)) { encoding = Encoding.PCM_SIGNED; convert_needed = true; } if (bigEndian) { bigEndian = false; convert_needed = true; } } if (convert_needed) { audioformat = new AudioFormat(encoding, sampleRate, sampleSizeInBits, channels, frameSize, frameRate, bigEndian); } // fmt RIFFWriter fmt_chunk = writer.writeChunk("fmt "); int sampleformat = 0; if (audioformat.getEncoding().equals(Encoding.PCM_UNSIGNED)) sampleformat = 1; else if (audioformat.getEncoding().equals(Encoding.PCM_SIGNED)) sampleformat = 1; else if (audioformat.getEncoding().equals(Encoding.PCM_FLOAT)) sampleformat = 3; fmt_chunk.writeUnsignedShort(sampleformat); fmt_chunk.writeUnsignedShort(audioformat.getChannels()); fmt_chunk.writeUnsignedInt((long) audioformat.getSampleRate()); long srate = ((long)audioformat.getFrameRate())*audioformat.getFrameSize(); fmt_chunk.writeUnsignedInt(srate); fmt_chunk.writeUnsignedShort(audioformat.getFrameSize()); fmt_chunk.writeUnsignedShort(audioformat.getSampleSizeInBits()); fmt_chunk.write(0); fmt_chunk.write(0); writeSampleOptions(writer.writeChunk("wsmp"), sample.sampleoptions); if (convert_needed) { RIFFWriter data_chunk = writer.writeChunk("data"); AudioInputStream stream = AudioSystem.getAudioInputStream( audioformat, (AudioInputStream)sample.getData()); byte[] buff = new byte[1024]; int ret; while ((ret = stream.read(buff)) != -1) { data_chunk.write(buff, 0, ret); } } else { RIFFWriter data_chunk = writer.writeChunk("data"); ModelByteBuffer databuff = sample.getDataBuffer(); databuff.writeTo(data_chunk); /* data_chunk.write(databuff.array(), databuff.arrayOffset(), databuff.capacity()); */ } writeInfo(writer.writeList("INFO"), sample.info); } private void writeInstruments(RIFFWriter writer) throws IOException { for (DLSInstrument instrument : instruments) { writeInstrument(writer.writeList("ins "), instrument); } } private void writeInstrument(RIFFWriter writer, DLSInstrument instrument) throws IOException { int art1_count = 0; int art2_count = 0; for (DLSModulator modulator : instrument.getModulators()) { if (modulator.version == 1) art1_count++; if (modulator.version == 2) art2_count++; } for (DLSRegion region : instrument.regions) { for (DLSModulator modulator : region.getModulators()) { if (modulator.version == 1) art1_count++; if (modulator.version == 2) art2_count++; } } int version = 1; if (art2_count > 0) version = 2; RIFFWriter insh_chunk = writer.writeChunk("insh"); insh_chunk.writeUnsignedInt(instrument.getRegions().size()); insh_chunk.writeUnsignedInt(instrument.bank + (instrument.druminstrument ? 2147483648L : 0)); insh_chunk.writeUnsignedInt(instrument.preset); RIFFWriter lrgn = writer.writeList("lrgn"); for (DLSRegion region: instrument.regions) writeRegion(lrgn, region, version); writeArticulators(writer, instrument.getModulators()); writeInfo(writer.writeList("INFO"), instrument.info); } private void writeArticulators(RIFFWriter writer, List<DLSModulator> modulators) throws IOException { int art1_count = 0; int art2_count = 0; for (DLSModulator modulator : modulators) { if (modulator.version == 1) art1_count++; if (modulator.version == 2) art2_count++; } if (art1_count > 0) { RIFFWriter lar1 = writer.writeList("lart"); RIFFWriter art1 = lar1.writeChunk("art1"); art1.writeUnsignedInt(8); art1.writeUnsignedInt(art1_count); for (DLSModulator modulator : modulators) { if (modulator.version == 1) { art1.writeUnsignedShort(modulator.source); art1.writeUnsignedShort(modulator.control); art1.writeUnsignedShort(modulator.destination); art1.writeUnsignedShort(modulator.transform); art1.writeInt(modulator.scale); } } } if (art2_count > 0) { RIFFWriter lar2 = writer.writeList("lar2"); RIFFWriter art2 = lar2.writeChunk("art2"); art2.writeUnsignedInt(8); art2.writeUnsignedInt(art2_count); for (DLSModulator modulator : modulators) { if (modulator.version == 2) { art2.writeUnsignedShort(modulator.source); art2.writeUnsignedShort(modulator.control); art2.writeUnsignedShort(modulator.destination); art2.writeUnsignedShort(modulator.transform); art2.writeInt(modulator.scale); } } } } private void writeRegion(RIFFWriter writer, DLSRegion region, int version) throws IOException { RIFFWriter rgns = null; if (version == 1) rgns = writer.writeList("rgn "); if (version == 2) rgns = writer.writeList("rgn2"); if (rgns == null) return; RIFFWriter rgnh = rgns.writeChunk("rgnh"); rgnh.writeUnsignedShort(region.keyfrom); rgnh.writeUnsignedShort(region.keyto); rgnh.writeUnsignedShort(region.velfrom); rgnh.writeUnsignedShort(region.velto); rgnh.writeUnsignedShort(region.options); rgnh.writeUnsignedShort(region.exclusiveClass); if (region.sampleoptions != null) writeSampleOptions(rgns.writeChunk("wsmp"), region.sampleoptions); if (region.sample != null) { if (samples.indexOf(region.sample) != -1) { RIFFWriter wlnk = rgns.writeChunk("wlnk"); wlnk.writeUnsignedShort(region.fusoptions); wlnk.writeUnsignedShort(region.phasegroup); wlnk.writeUnsignedInt(region.channel); wlnk.writeUnsignedInt(samples.indexOf(region.sample)); } } writeArticulators(rgns, region.getModulators()); rgns.close(); } private void writeSampleOptions(RIFFWriter wsmp, DLSSampleOptions sampleoptions) throws IOException { wsmp.writeUnsignedInt(20); wsmp.writeUnsignedShort(sampleoptions.unitynote); wsmp.writeShort(sampleoptions.finetune); wsmp.writeInt(sampleoptions.attenuation); wsmp.writeUnsignedInt(sampleoptions.options); wsmp.writeInt(sampleoptions.loops.size()); for (DLSSampleLoop loop : sampleoptions.loops) { wsmp.writeUnsignedInt(16); wsmp.writeUnsignedInt(loop.type); wsmp.writeUnsignedInt(loop.start); wsmp.writeUnsignedInt(loop.length); } } private void writeInfoStringChunk(RIFFWriter writer, String name, String value) throws IOException { if (value == null) return; RIFFWriter chunk = writer.writeChunk(name); chunk.writeString(value); int len = value.getBytes("ascii").length; chunk.write(0); len++; if (len % 2 != 0) chunk.write(0); } private void writeInfo(RIFFWriter writer, DLSInfo info) throws IOException { writeInfoStringChunk(writer, "INAM", info.name); writeInfoStringChunk(writer, "ICRD", info.creationDate); writeInfoStringChunk(writer, "IENG", info.engineers); writeInfoStringChunk(writer, "IPRD", info.product); writeInfoStringChunk(writer, "ICOP", info.copyright); writeInfoStringChunk(writer, "ICMT", info.comments); writeInfoStringChunk(writer, "ISFT", info.tools); writeInfoStringChunk(writer, "IARL", info.archival_location); writeInfoStringChunk(writer, "IART", info.artist); writeInfoStringChunk(writer, "ICMS", info.commissioned); writeInfoStringChunk(writer, "IGNR", info.genre); writeInfoStringChunk(writer, "IKEY", info.keywords); writeInfoStringChunk(writer, "IMED", info.medium); writeInfoStringChunk(writer, "ISBJ", info.subject); writeInfoStringChunk(writer, "ISRC", info.source); writeInfoStringChunk(writer, "ISRF", info.source_form); writeInfoStringChunk(writer, "ITCH", info.technician); } public DLSInfo getInfo() { return info; } public String getName() { return info.name; } public String getVersion() { return major + "." + minor; } public String getVendor() { return info.engineers; } public String getDescription() { return info.comments; } public void setName(String s) { info.name = s; } public void setVendor(String s) { info.engineers = s; } public void setDescription(String s) { info.comments = s; } public SoundbankResource[] getResources() { SoundbankResource[] resources = new SoundbankResource[samples.size()]; int j = 0; for (int i = 0; i < samples.size(); i++) resources[j++] = samples.get(i); return resources; } public DLSInstrument[] getInstruments() { DLSInstrument[] inslist_array = instruments.toArray(new DLSInstrument[instruments.size()]); Arrays.sort(inslist_array, new ModelInstrumentComparator()); return inslist_array; } public DLSSample[] getSamples() { return samples.toArray(new DLSSample[samples.size()]); } public Instrument getInstrument(Patch patch) { int program = patch.getProgram(); int bank = patch.getBank(); boolean percussion = false; if (patch instanceof ModelPatch) percussion = ((ModelPatch) patch).isPercussion(); for (Instrument instrument : instruments) { Patch patch2 = instrument.getPatch(); int program2 = patch2.getProgram(); int bank2 = patch2.getBank(); if (program == program2 && bank == bank2) { boolean percussion2 = false; if (patch2 instanceof ModelPatch) percussion2 = ((ModelPatch) patch2).isPercussion(); if (percussion == percussion2) return instrument; } } return null; } public void addResource(SoundbankResource resource) { if (resource instanceof DLSInstrument) instruments.add((DLSInstrument) resource); if (resource instanceof DLSSample) samples.add((DLSSample) resource); } public void removeResource(SoundbankResource resource) { if (resource instanceof DLSInstrument) instruments.remove((DLSInstrument) resource); if (resource instanceof DLSSample) samples.remove((DLSSample) resource); } public void addInstrument(DLSInstrument resource) { instruments.add(resource); } public void removeInstrument(DLSInstrument resource) { instruments.remove(resource); } public long getMajor() { return major; } public void setMajor(long major) { this.major = major; } public long getMinor() { return minor; } public void setMinor(long minor) { this.minor = minor; } }