view overlays/openjdk/jdk/src/share/classes/com/sun/media/sound/DLSSoundbank.java @ 843:bcba163568ac

Integrate Gervill. 2008-04-30 Mark Wielaard <mark@klomp.org> * Makefile.am (ICEDTEA_PATCHES): Add patches/icedtea-gervill.patch. * Makefile.in: Regenerated. * patches/icedtea-gervill.patch: New patch. * overlays/openjdk/jdk/src/share/classes/com/sun/media/sound/*: New Gervill files.
author Mark Wielaard <mark@klomp.org>
date Wed, 30 Apr 2008 22:09:08 +0200
parents
children 4706d355d973
line wrap: on
line source

/*
  * Copyright 2007 Sun Microsystems, Inc.  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.  Sun designates this
  * particular file as subject to the "Classpath" exception as provided
  * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
  * CA 95054 USA or visit www.sun.com if you need additional information or
  * have any questions.
  */ 

/**
 * This class reads DLS (Downloadable Sounds) from files/url/streams. 
 * It can both read Level 1 and Level 2 DLS files.
 *
 * @version %I%, %E%
 * @author Karl Helgason
 */ 
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.
 *
 * @version %I%, %E%
 * @author Karl Helgason
 */ 
public 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()
		{			
		}
		public 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.readByte();
			d.x2 = riff.readByte();
			d.x3 = riff.readByte();
			d.x4 = riff.readByte();
			d.x5 = riff.readByte();
			d.x6 = riff.readByte();
			d.x7 = riff.readByte();
			d.x8 = riff.readByte();
			return d;
		}
		
		public boolean equals(Object obj) {
			if(!(obj instanceof DLSID)) return false;
			DLSID t = (DLSID)obj;
			if(i1 != t.i1) return false;
			if(s1 != t.s1) return false;
			if(s2 != t.s2) return false;
			if(x1 != t.x1) return false;
			if(x2 != t.x2) return false;
			if(x3 != t.x3) return false;
			if(x4 != t.x4) return false;
			if(x5 != t.x5) return false;
			if(x6 != t.x6) return false;
			if(x7 != t.x7) return false;
			if(x8 != t.x8) return false;
			return true;
		}
		
		
	}
	
	private static final int DLS_CDL_AND			= 0x0001;	/* X = X & Y */
	private static final int DLS_CDL_OR			    = 0x0002;	/* X = X | Y */
	private static final int DLS_CDL_XOR			= 0x0003;	/* X = X ^ Y */
	private static final int DLS_CDL_ADD			= 0x0004;	/* X = X + Y */
	private static final int DLS_CDL_SUBTRACT		= 0x0005;	/* X = X - Y */
	private static final int DLS_CDL_MULTIPLY		= 0x0006;	/* X = X * Y */
	private static final int DLS_CDL_DIVIDE		    = 0x0007;	/* X = X / Y */
	private static final int DLS_CDL_LOGICAL_AND	= 0x0008;	/* X = X && Y */
	private static final int DLS_CDL_LOGICAL_OR		= 0x0009;	/* X = X || Y */
	private static final int DLS_CDL_LT			    = 0x000A;	/* X = (X < Y) */
	private static final int DLS_CDL_LE			    = 0x000B;	/* X = (X <= Y) */
	private static final int DLS_CDL_GT			    = 0x000C;	/* X = (X > Y) */
	private static final int DLS_CDL_GE			    = 0x000D;	/* X = (X >= Y) */
	private static final int DLS_CDL_EQ			    = 0x000E;	/* X = (X == Y) */
	private static final int DLS_CDL_NOT			= 0x000F;	/* X = !X */
	private static final int DLS_CDL_CONST		    = 0x0010;	/* 32-bit constant */
	private static final int DLS_CDL_QUERY		    = 0x0011;	/* 32-bit value returned from query */
	private static final int DLS_CDL_QUERYSUPPORTED	= 0x0012;	/* 32-bit value returned from query */
	
	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 DLSInfo info = new DLSInfo();
	
	private List<DLSInstrument> instruments = new ArrayList<DLSInstrument>();
	private 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)
	{
		if(uuid.equals(DLSID_GMInHardware)) return true;
		if(uuid.equals(DLSID_GSInHardware)) return true;
		if(uuid.equals(DLSID_XGInHardware)) return true;
		if(uuid.equals(DLSID_SupportsDLS1)) return true;
		if(uuid.equals(DLSID_SupportsDLS2)) return true;
		if(uuid.equals(DLSID_SampleMemorySize)) return true;
		if(uuid.equals(DLSID_ManufacturersID)) return true;
		if(uuid.equals(DLSID_ProductID)) return true;
		if(uuid.equals(DLSID_SamplePlaybackRate)) return true;
		return false;
	}
	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(new Long( ((x!=0) & (y!=0)) ?1:0));
				break;
			case DLS_CDL_OR:
				x = stack.pop();
				y = stack.pop();
				stack.push(new Long( ((x!=0) | (y!=0)) ?1:0));
				break;
			case DLS_CDL_XOR:
				x = stack.pop();
				y = stack.pop();
				stack.push(new Long( ((x!=0) ^ (y!=0)) ?1:0));
				break;
			case DLS_CDL_ADD:
				x = stack.pop();
				y = stack.pop();
				stack.push(new Long( x+y ));
				break;
			case DLS_CDL_SUBTRACT:
				x = stack.pop();
				y = stack.pop();
				stack.push(new Long( x-y ));
				break;
			case DLS_CDL_MULTIPLY:
				x = stack.pop();
				y = stack.pop();
				stack.push(new Long( x*y ));
				break;
			case DLS_CDL_DIVIDE:
				x = stack.pop();
				y = stack.pop();
				stack.push(new Long( x/y ));
				break;
			case DLS_CDL_LOGICAL_AND:
				x = stack.pop();
				y = stack.pop();
				stack.push(new Long( ((x!=0) & (y!=0)) ?1:0));
				break;
			case DLS_CDL_LOGICAL_OR:
				x = stack.pop();
				y = stack.pop();
				stack.push(new Long( ((x!=0) | (y!=0)) ?1:0));
				break;
			case DLS_CDL_LT:
				x = stack.pop();
				y = stack.pop();
				stack.push(new Long( (x < y) ?1:0));
				break;				
			case DLS_CDL_LE:
				x = stack.pop();
				y = stack.pop();
				stack.push(new Long( (x <= y) ?1:0));
				break;				
			case DLS_CDL_GT:
				x = stack.pop();
				y = stack.pop();
				stack.push(new Long( (x > y) ?1:0));
				break;				
			case DLS_CDL_GE:
				x = stack.pop();
				y = stack.pop();
				stack.push(new Long( (x >= y) ?1:0));
				break;				
			case DLS_CDL_EQ:
				x = stack.pop();
				y = stack.pop();
				stack.push(new Long( (x == y) ?1:0));
				break;		
			case DLS_CDL_NOT:
				x = stack.pop();
				y = stack.pop();
				stack.push(new Long((x==0)?1:0));
				break;
			case DLS_CDL_CONST:
				stack.push(new Long(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(new Long(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.read(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.read(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();
					/* long framerate = */ chunk.readUnsignedInt(); // bytes per sec
					int framesize = chunk.readUnsignedShort(); // block align, framesize
					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
							(AudioFloatConverter.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.read(buffer, read, 65536);
								read += 65536;
							}
							else
							{
								chunk.read(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(new Long(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;
		if(audioformat.getEncoding().equals(Encoding.PCM_SIGNED)) sampleformat = 1;
		if(audioformat.getEncoding().equals(AudioFloatConverter.PCM_FLOAT)) sampleformat = 3;
		fmt_chunk.writeUnsignedShort(sampleformat);		
		fmt_chunk.writeUnsignedShort(audioformat.getChannels());
		fmt_chunk.writeUnsignedInt((long)audioformat.getSampleRate());
		long srate = (long)audioformat.getSampleRate();
		srate *= audioformat.getChannels();
		srate *= audioformat.getSampleSizeInBits() / 8;
		//fmt_chunk.writeUnsignedInt((long)audioformat.getFrameRate());
		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;
	}		
	
	
}