/*
 *	AudioSystem.java
 *
 *	This file is part of Tritonus: http://www.tritonus.org/
 */

/*
 *  Copyright (c) 1999 - 2002 by Matthias Pfisterer
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU Library General Public License as published
 *   by the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program 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 Library General Public License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
|<---            this code is formatted to fit into 80 columns             --->|
*/

package javax.sound.sampled;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.util.Arrays;
import java.util.Collection;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.Iterator;

import javax.sound.sampled.spi.AudioFileReader;
import javax.sound.sampled.spi.AudioFileWriter;
import javax.sound.sampled.spi.FormatConversionProvider;
import javax.sound.sampled.spi.MixerProvider;

import org.tritonus.core.TAudioConfig;
import org.tritonus.share.TDebug;
import org.tritonus.share.ArraySet;


public class AudioSystem
{
	public static final int		NOT_SPECIFIED = -1;

	private static final AudioFormat[]	EMPTY_FORMAT_ARRAY = new AudioFormat[0];
	private static final AudioFormat.Encoding[]	EMPTY_ENCODING_ARRAY = new AudioFormat.Encoding[0];
	private static final AudioFileFormat.Type[]	EMPTY_TYPE_ARRAY = new AudioFileFormat.Type[0];
	private static final Mixer.Info[]	EMPTY_MIXER_INFO_ARRAY = new Mixer.Info[0];
	private static final Line.Info[]	EMPTY_LINE_INFO_ARRAY = new Line.Info[0];



	/**	MixerProviderAction for getMixerInfo().
	 */
	private static class GetMixerInfoMixerProviderAction
	implements MixerProviderAction
	{
		private Collection<Mixer.Info>	m_mixerInfos = new ArrayList<Mixer.Info>();



		public GetMixerInfoMixerProviderAction()
		{
		}



		public boolean handleMixerProvider(
			MixerProvider mixerProvider)
		{
			Mixer.Info[]	aMixerInfos = mixerProvider.getMixerInfo();
			if (aMixerInfos != null)
			{
				// TODO: is this if needed?
				if (aMixerInfos.length > 0)
				{
					if (TDebug.TraceAudioSystem)
					{
						TDebug.out("AudioSystem.getMixerInfo(): MixerProvider returns array:");
						for (int i = 0; i < aMixerInfos.length; i++)
						{
							TDebug.out("" + aMixerInfos[i]);
						}
					}
				}
				else if (TDebug.TraceAudioSystem)
				{
					TDebug.out("AudioSystem.getMixerInfo(): MixerProvider returns empty array.");
				}
				m_mixerInfos.addAll(Arrays.asList(aMixerInfos));
			}
			else if (TDebug.TraceAudioSystem)
			{
				TDebug.out("AudioSystem.getMixerInfo(): MixerProvider returns null.");
			}
			// always continue
			return false;
		}



		public Mixer.Info[] getMixerInfos()
		{
			return m_mixerInfos.toArray(EMPTY_MIXER_INFO_ARRAY);
		}
	}



	public static Mixer.Info[] getMixerInfo()
	{
		GetMixerInfoMixerProviderAction	action = new GetMixerInfoMixerProviderAction();
		doMixerProviderIteration(action);
		Mixer.Info[] infos = action.getMixerInfos();
		// TDebug.out("MI length: " + infos.length);
		return action.getMixerInfos();
	}



	/**	MixerProviderAction for getMixer().
	 */
	private static class GetMixerMixerProviderAction
	implements MixerProviderAction
	{
		private Mixer.Info	m_info = null;
		private Mixer		m_mixer = null;



		public GetMixerMixerProviderAction(Mixer.Info info)
		{
			m_info = info;
		}



		public boolean handleMixerProvider(
			MixerProvider mixerProvider)
		{
			try
			{
				Mixer	mixer = mixerProvider.getMixer(m_info);
				if (m_mixer == null)
				{
					m_mixer = mixer;
					// now interrupt the iteration
					return true;
				}
			}
			catch (IllegalArgumentException e)
			{
				if (TDebug.TraceAudioSystem || TDebug.TraceAllExceptions) { TDebug.out(e); }
			}
			// continue the iteration
			return false;
		}



		public Mixer getMixer()
		{
			return m_mixer;
		}
	}



	public static Mixer getMixer(Mixer.Info info)
	{
		if (info == null)
		{
			// TODO: this currently always yields null!!!
			// but default mixers are handled by some magic in
			// TMixerProvider
			info = TAudioConfig.getDefaultMixerInfo();
		}
		GetMixerMixerProviderAction	action = new GetMixerMixerProviderAction(info);
		doMixerProviderIteration(action);
		Mixer	mixer = action.getMixer();
		if (mixer != null)
		{
			return mixer;
		}
		else
		{
			throw new IllegalArgumentException("no mixer found for " + info);
		}
	}



	/**	MixerAction for getSourceLineInfo().
	 */
	private static class GetSourceLineInfoMixerAction
	implements MixerAction
	{
		private Line.Info	m_info = null;
		private Set<Line.Info>		m_infos;



		public GetSourceLineInfoMixerAction(Line.Info info)
		{
			m_info = info;
			m_infos = new ArraySet<Line.Info>();
		}



		public boolean handleMixer(
			Mixer mixer)
		{
			Line.Info[]	infos = mixer.getSourceLineInfo(m_info);
			m_infos.addAll(Arrays.asList(infos));
			// always continue the iteration
			return false;
		}



		public Line.Info[] getInfos()
		{
			return m_infos.toArray(EMPTY_LINE_INFO_ARRAY);
		}
	}



	public static Line.Info[] getSourceLineInfo(Line.Info info)
	{
		GetSourceLineInfoMixerAction	action = new GetSourceLineInfoMixerAction(info);
		doMixerIteration(action);
		return action.getInfos();
	}



	/**	MixerAction for getTargetLineInfo().
	 */
	private static class GetTargetLineInfoMixerAction
	implements MixerAction
	{
		private Line.Info	m_info = null;
		private Set<Line.Info>		m_infos;



		public GetTargetLineInfoMixerAction(Line.Info info)
		{
			m_info = info;
			m_infos = new ArraySet<Line.Info>();
		}



		public boolean handleMixer(
			Mixer mixer)
		{
			Line.Info[]	infos = mixer.getTargetLineInfo(m_info);
			m_infos.addAll(Arrays.asList(infos));
			// always continue the iteration
			return false;
		}



		public Line.Info[] getInfos()
		{
			return m_infos.toArray(EMPTY_LINE_INFO_ARRAY);
		}
	}



	public static Line.Info[] getTargetLineInfo(Line.Info info)
	{
		GetTargetLineInfoMixerAction	action = new GetTargetLineInfoMixerAction(info);
		doMixerIteration(action);
		return action.getInfos();
	}



	/**	MixerAction for isLineSupported().
	 */
	private static class IsLineSupportedMixerAction
	implements MixerAction
	{
		private Line.Info	m_info = null;
		private boolean		m_bSupported = false;



		public IsLineSupportedMixerAction(Line.Info info)
		{
			m_info = info;
		}



		public boolean handleMixer(
			Mixer mixer)
		{
			boolean bSupported = mixer.isLineSupported(m_info);
			m_bSupported |= bSupported;
			// interrupt the iteration depending on the result
			return bSupported;
		}



		public boolean isSupported()
		{
			return m_bSupported;
		}
	}



	public static boolean isLineSupported(Line.Info info)
	{
		IsLineSupportedMixerAction	action = new IsLineSupportedMixerAction(info);
		doMixerIteration(action);
		return action.isSupported();
	}



	/**	MixerAction for getLine().
	 */
	private static class GetLineMixerAction
	implements MixerAction
	{
		private Line.Info	m_info = null;
		private Line		m_line = null;
		private boolean		m_bLineTypeSupported = false;



		public GetLineMixerAction(Line.Info info)
		{
			m_info = info;
		}



		public boolean handleMixer(
			Mixer mixer)
		{
			try
			{
				Line	line = mixer.getLine(m_info);
				if (m_line == null)
				{
					m_line = line;
					// interrupt the iteration
					return true;
				}
			}
			catch (LineUnavailableException e)
			{
				m_bLineTypeSupported = true;
				if (TDebug.TraceAudioSystem || TDebug.TraceAllExceptions) { TDebug.out(e); }
			}
			catch (IllegalArgumentException e)
			{
				if (TDebug.TraceAudioSystem || TDebug.TraceAllExceptions) { TDebug.out(e); }
			}
			// continue the iteration
			return false;
		}



		public Line getLine()
		{
			return m_line;
		}



		public boolean isLineTypeSupported()
		{
			return m_bLineTypeSupported;
		}
	}



	public static Line getLine(Line.Info info)
		throws LineUnavailableException
	{
		GetLineMixerAction	action = new GetLineMixerAction(info);
		doMixerIteration(action);
		Line	line = action.getLine();
		if (line != null)
		{
			return line;
		}
		else if (action.isLineTypeSupported())
		{
			throw new LineUnavailableException("currently no line available due to resource restrictions");
		}
		else
		{
			throw new IllegalArgumentException("no mixer supporting this type of line: " + info);
		}
	}



	/**	FormatConversionProviderAction for getTargetEncodings().
	 */
	private static class GetTargetEncodingsFormatConversionProviderAction
	implements FormatConversionProviderAction
	{
		private Object		m_sourceDescription;
		private Collection<AudioFormat.Encoding>	m_targetEncodings;


		//$$fb 2000-08-15: added for workaround below
		public GetTargetEncodingsFormatConversionProviderAction() {
			this(null);
		}

		public GetTargetEncodingsFormatConversionProviderAction(
			Object sourceDescription)
		{
			m_sourceDescription = sourceDescription;
			m_targetEncodings = new ArraySet<AudioFormat.Encoding>();
		}


		public boolean handleFormatConversionProvider(
			FormatConversionProvider formatConversionProvider)
		{
			// $$fb 2000-03-30 default to empty array
			AudioFormat.Encoding[]	encodings = EMPTY_ENCODING_ARRAY;
			if (m_sourceDescription instanceof AudioFormat.Encoding)
			{
				// TODO: not directely implementable. Contact Sun.
				//$$fb 2000-08-15: see workaround below
				/*
				  encodings = formatConversionProvider.getTargetEncodings(
				  (AudioFormat.Encoding) m_sourceDescription);
				*/
			}
			else if (m_sourceDescription instanceof AudioFormat)
			{
				encodings = formatConversionProvider.getTargetEncodings(
					(AudioFormat) m_sourceDescription);
			}
			else
			{
				// TODO: debug message
			}
			m_targetEncodings.addAll(Arrays.asList(encodings));
			// continue the iteration
			return false;
		}


		public AudioFormat.Encoding[] getEncodings()
		{
			return m_targetEncodings.toArray(EMPTY_ENCODING_ARRAY);
		}

		//$$fb 2000-08-15: added for workaround below
		public void setSourceDescription(Object sourceDescription) {
			m_sourceDescription = sourceDescription;
		}
			
	}



	//$$fb 2000-08-15: added for workaround below
	private static void doEncodingActionWorkaround(boolean bigEndian, AudioFormat.Encoding encoding, 
						       GetTargetEncodingsFormatConversionProviderAction action) {
		AudioFormat format=new AudioFormat(
			encoding, 
			NOT_SPECIFIED, // sample rate
			NOT_SPECIFIED, // sample size in bits
			NOT_SPECIFIED, // channels
			NOT_SPECIFIED, // frame size
			NOT_SPECIFIED, // frame rate,
			bigEndian);
		action.setSourceDescription(format);
		doFormatConversionProviderIteration(action);
	}

	public static AudioFormat.Encoding[] getTargetEncodings(
		AudioFormat.Encoding sourceEncoding)
	{
		//$$fb 2000-08-15: workaround
		//return getTargetEncodings((Object) sourceEncoding);
		GetTargetEncodingsFormatConversionProviderAction action = 
			new GetTargetEncodingsFormatConversionProviderAction();
		doEncodingActionWorkaround(false, sourceEncoding, action);
		doEncodingActionWorkaround(true, sourceEncoding, action);
		return action.getEncodings();
	}



	public static AudioFormat.Encoding[] getTargetEncodings(
		AudioFormat sourceFormat)
	{
		return getTargetEncodings((Object) sourceFormat);
	}



	private static AudioFormat.Encoding[] getTargetEncodings(
		Object sourceDescription)
	{
		GetTargetEncodingsFormatConversionProviderAction	action = new GetTargetEncodingsFormatConversionProviderAction(sourceDescription);
		doFormatConversionProviderIteration(action);
		return action.getEncodings();
	}



	/**	FormatConversionProviderAction for isConversionSupported().
	 */
	private static class IsConversionSupportedFormatConversionProviderAction
	implements FormatConversionProviderAction
	{
		private AudioFormat		m_sourceFormat;
		/*
		 *	May be AudioFormat or AudioFormat.Encoding.
		 */
		private Object			m_targetDescription;
		private boolean			m_bSupported;



		public IsConversionSupportedFormatConversionProviderAction(
			AudioFormat sourceFormat,
			Object targetDescription)
		{
			m_sourceFormat = sourceFormat;
			m_targetDescription = targetDescription;
			m_bSupported = false;
		}



		public boolean handleFormatConversionProvider(
			FormatConversionProvider formatConversionProvider)
		{
			boolean bSupported = false;
			if (m_targetDescription instanceof AudioFormat.Encoding)
			{
				bSupported = formatConversionProvider.isConversionSupported(
					(AudioFormat.Encoding) m_targetDescription,
					m_sourceFormat);
			}
			else if (m_targetDescription instanceof AudioFormat)
			{
				bSupported = formatConversionProvider.isConversionSupported(
					(AudioFormat) m_targetDescription,
					m_sourceFormat);
			}
			else
			{
				// TODO: debug message
			}
			m_bSupported |= bSupported;
			// interrupt the iteration depending on the result
			return bSupported;
		}



		public boolean isSupported()
		{
			return m_bSupported;
		}
	}



	public static boolean isConversionSupported(
		AudioFormat.Encoding targetEncoding,
		AudioFormat sourceFormat)
	{
		IsConversionSupportedFormatConversionProviderAction	action = new IsConversionSupportedFormatConversionProviderAction(sourceFormat, targetEncoding);
		doFormatConversionProviderIteration(action);
		return action.isSupported();
	}



	/**	FormatConversionProviderAction for getAudioInputStream().
	 */
	private static class GetAudioInputStreamFormatConversionProviderAction
	implements FormatConversionProviderAction
	{
		private AudioInputStream	m_sourceAudioInputStream;
		private Object			m_targetDescription;
		private AudioInputStream	m_targetAudioInputStream;



		public GetAudioInputStreamFormatConversionProviderAction(
			AudioInputStream audioInputStream,
			Object targetDescription)
		{
			m_sourceAudioInputStream = audioInputStream;
			m_targetDescription = targetDescription;
			m_targetAudioInputStream = null;
		}



		public boolean handleFormatConversionProvider(
			FormatConversionProvider formatConversionProvider)
		{
			AudioInputStream	audioInputStream = null;
			try
			{
				if (m_targetDescription instanceof AudioFormat.Encoding)
				{
					audioInputStream = formatConversionProvider.getAudioInputStream(
						(AudioFormat.Encoding) m_targetDescription,
						m_sourceAudioInputStream);
				}
				else if (m_targetDescription instanceof AudioFormat)
				{
					audioInputStream = formatConversionProvider.getAudioInputStream(
						(AudioFormat) m_targetDescription,
						m_sourceAudioInputStream);
				}
				else
				{
					// TODO: debug message
				}
				m_targetAudioInputStream = audioInputStream;
				// interrupt the iteration
				return true;
			}
			catch (IllegalArgumentException e)
			{
				if (TDebug.TraceAudioSystem || TDebug.TraceAllExceptions) { TDebug.out(e); }
			}
			// continue the iteration
			return false;
		}



		public AudioInputStream getAudioInputStream()
		{
			return m_targetAudioInputStream;
		}
	}



	public static AudioInputStream getAudioInputStream(
		AudioFormat.Encoding targetEncoding,
		AudioInputStream sourceStream)
	{
		return getAudioInputStreamImpl((Object) targetEncoding,
					       sourceStream);
	}



	private static AudioInputStream getAudioInputStreamImpl(
		Object targetDescription,
		AudioInputStream sourceStream)
	{
		GetAudioInputStreamFormatConversionProviderAction	action = new GetAudioInputStreamFormatConversionProviderAction(sourceStream, targetDescription);
		doFormatConversionProviderIteration(action);
		AudioInputStream	audioInputStream = action.getAudioInputStream();
		if (audioInputStream != null)
		{
			return audioInputStream;
		}
		else
		{
			throw new IllegalArgumentException("conversion not supported");
		}
	}



	public static AudioFormat[] getTargetFormats(
		AudioFormat.Encoding targetEncoding,
		AudioFormat sourceFormat)
	{
		// TODO:
		return null;
	}


	public static boolean isConversionSupported(
		AudioFormat targetFormat,
		AudioFormat sourceFormat)
	{
		IsConversionSupportedFormatConversionProviderAction	action = new IsConversionSupportedFormatConversionProviderAction(sourceFormat, targetFormat);
		doFormatConversionProviderIteration(action);
		return action.isSupported();
	}



	public static AudioInputStream getAudioInputStream(
		AudioFormat targetFormat,
		AudioInputStream sourceStream)
	{
		return getAudioInputStreamImpl((Object) targetFormat,
					       sourceStream);
	}




	// $$fb 19 Dec 99: what about cascaded conversions ?
	// we'll never have an alround-converter doing all possible conversions.
	// we could implement an algorithm that, if no conversion is available,
	// in a second step a conversion to PCM is tried, followed by a conversion from
	// PCM to the requested target format.
	// we might even introduce a third level for inter-PCM-conversion.
	// e.g. we want to convert from aLaw 8000Hz to uLaw 44100Hz.
	// 1. the first round we won't find a direct converter
	// 2. try to find a converter from aLaw 8000 to PCM 44100
	//    we won't find it.
	// 3. third level: try to find a converter from alaw 8000 to PCM 8000
	//    we'll find one.
	//    then find a converter from PCM 8000 to PCM 44100
	//    let's say we have that already.
	//    then find a converter from PCM 44100 to ulaw 44100.
	// I think that sounds more impossible than it is, when
	// we have a powerful PCM-PCM converter.
/*
  public static AudioInputStream getAudioInputStream(AudioFormat audioFormat, AudioInputStream audioInputStream)
  {
  Iterator	formatConversionProviders = TAudioConfig.getFormatConversionProviders();
  while (formatConversionProviders.hasNext())
  {
  FormatConversionProvider	formatConversionProvider = (FormatConversionProvider) formatConversionProviders.next();
  if (TDebug.TraceAudioSystem)
  {
  System.out.print("AudioSystem.getAudioInputStream(AudioFormat, AudioInputStream): asking FormatConversionProvider: " + formatConversionProvider + "...");
  }
  try
  {
  AudioInputStream	outputStream = formatConversionProvider.getConvertedStream(audioFormat, audioInputStream);
  if (TDebug.TraceAudioSystem)
  {
  TDebug.out("ok");
  }
  return outputStream;
  }
  catch (IllegalArgumentException e)
  {
  if (TDebug.TraceAudioSystem || TDebug.TraceAllExceptions)
  {
  TDebug.out("failed");
  TDebug.out(e);
  }
  }
  }
  if (TDebug.TraceAudioSystem)
  {
  TDebug.out("AudioSystem.getAudioInputStream(AudioFormat, AudioInputStream): no suitable FormatConversionProvider found. Returning null.");
  }
  return null;
  }
*/
/*
  public static AudioInputStream getAudioInputStream(Type encoding, AudioInputStream audioInputStream)
  {
  AudioFormat	inputFormat = audioInputStream.getFormat();
  // $$fb 19 Dec 99: Here I think, sample size in bits and framesize should
  // be set to NOT_SPECIFIED, as we cannot assume that it stays
  // the same with the new encoding.
  */
	/* $$mp19991220: Yes, possible optimisation:
	   find all formats with the given encoding that can be
	   generated by conversion from the stream. Then pick the
	   one nearest to the original format.
	*/
/*
  AudioFormat	outputFormat = new AudioFormat(encoding,
  inputFormat.getSampleRate(),
  inputFormat.getSampleSizeInBits(),
  inputFormat.getChannels(),
  inputFormat.getFrameSize(),
  inputFormat.getFrameRate(),
  inputFormat.isBigEndian());
  return getAudioInputStream(outputFormat, audioInputStream);
  }
*/


	/**	AudioFileReaderAction for getAudioFileFormat().
	 */
	private static class GetAudioFileFormatAudioFileReaderAction
	implements AudioFileReaderAction
	{
		private Object			m_source = null;
		private AudioFileFormat		m_audioFileFormat = null;



		public GetAudioFileFormatAudioFileReaderAction(Object source)
		{
			m_source = source;
		}



		public boolean handleAudioFileReader(
			AudioFileReader audioFileReader)
			throws IOException
		{
			AudioFileFormat	audioFileFormat = null;
			try
			{
				if (m_source instanceof InputStream)
				{
					audioFileFormat = audioFileReader.getAudioFileFormat((InputStream) m_source);
				}
				else if (m_source instanceof File)
				{
					audioFileFormat = audioFileReader.getAudioFileFormat((File) m_source);
				}
				else if (m_source instanceof URL)
				{
					audioFileFormat = audioFileReader.getAudioFileFormat((URL) m_source);
				}
				else
				{
					// TODO: debug message
				}
				if (m_audioFileFormat == null)
				{
					m_audioFileFormat = audioFileFormat;
					// interrupt the iteration
					return true;
				}
			}
			catch (UnsupportedAudioFileException e)
			{
				if (TDebug.TraceAudioSystem || TDebug.TraceAllExceptions) { TDebug.out(e); }
			}
			// continue the iteration
			return false;
		}



		public AudioFileFormat getAudioFileFormat()
		{
			return m_audioFileFormat;
		}
	}



	public static AudioFileFormat getAudioFileFormat(
		InputStream inputStream)
		throws UnsupportedAudioFileException, IOException
	{
		return getAudioFileFormatImpl(inputStream);
	}



	public static AudioFileFormat getAudioFileFormat(URL url)
		throws UnsupportedAudioFileException, IOException
	{
		return getAudioFileFormatImpl(url);
	}



	public static AudioFileFormat getAudioFileFormat(File file)
		throws UnsupportedAudioFileException, IOException
	{
		return getAudioFileFormatImpl(file);
	}



	private static AudioFileFormat getAudioFileFormatImpl(
		Object source)
		throws UnsupportedAudioFileException, IOException
	{
		GetAudioFileFormatAudioFileReaderAction	action = new GetAudioFileFormatAudioFileReaderAction(source);
		doAudioFileReaderIteration(action);
		AudioFileFormat	audioFileFormat = action.getAudioFileFormat();
		if (audioFileFormat != null)
		{
			return audioFileFormat;
		}
		else
		{
			throw new UnsupportedAudioFileException("format not supported");
		}
	}



	/**	AudioFileReaderAction for getAudioInputStream().
	 */
	private static class GetAudioInputStreamAudioFileReaderAction
	implements AudioFileReaderAction
	{
		private Object			m_source = null;
		private AudioInputStream	m_audioInputStream = null;



		public GetAudioInputStreamAudioFileReaderAction(Object source)
		{
			m_source = source;
		}



		public boolean handleAudioFileReader(
			AudioFileReader audioFileReader)
			throws IOException
		{
			AudioInputStream	audioInputStream = null;
			try
			{
				if (m_source instanceof InputStream)
				{
					audioInputStream = audioFileReader.getAudioInputStream((InputStream) m_source);
				}
				else if (m_source instanceof File)
				{
					audioInputStream = audioFileReader.getAudioInputStream((File) m_source);
				}
				else if (m_source instanceof URL)
				{
					audioInputStream = audioFileReader.getAudioInputStream((URL) m_source);
				}
				else
				{
					// TODO: debug message
				}
				if (m_audioInputStream == null)
				{
					m_audioInputStream = audioInputStream;
					// interrupt the iteration
					return true;
				}
			}
			catch (UnsupportedAudioFileException e)
			{
				if (TDebug.TraceAudioSystem || TDebug.TraceAllExceptions) { TDebug.out(e); }
			}
			// continue the iteration
			return false;
		}



		public AudioInputStream getAudioInputStream()
		{
			return m_audioInputStream;
		}
	}



	public static AudioInputStream getAudioInputStream(InputStream inputStream)
		throws UnsupportedAudioFileException, IOException
	{
		return getAudioInputStreamImpl(inputStream);
	}



	public static AudioInputStream getAudioInputStream(URL url)
		throws UnsupportedAudioFileException, IOException
	{
		return getAudioInputStreamImpl(url);
	}



	public static AudioInputStream getAudioInputStream(File file)
		throws UnsupportedAudioFileException, IOException
	{
		return getAudioInputStreamImpl(file);
	}



	private static AudioInputStream getAudioInputStreamImpl(
		Object source)
		throws UnsupportedAudioFileException, IOException
	{
		GetAudioInputStreamAudioFileReaderAction	action = new GetAudioInputStreamAudioFileReaderAction(source);
		doAudioFileReaderIteration(action);
		AudioInputStream	audioInputStream = action.getAudioInputStream();
		if (audioInputStream != null)
		{
			return audioInputStream;
		}
		else
		{
			throw new UnsupportedAudioFileException("format not supported");
		}
	}



	public static AudioFileFormat.Type[] getAudioFileTypes()
	{
		Set<AudioFileFormat.Type>		supportedTypes = new HashSet<AudioFileFormat.Type>();
		Iterator	audioFileWriters = TAudioConfig.getAudioFileWriters();
		if (TDebug.TraceAudioSystem) { TDebug.out(">AudioSystem.getAudioFileTypes()"); }
		while (audioFileWriters.hasNext())
		{
			AudioFileWriter	audioFileProvider = (AudioFileWriter) audioFileWriters.next();
			if (TDebug.TraceAudioSystem) { TDebug.out("trying AudioFileWriter: " + audioFileProvider); }
			AudioFileFormat.Type[]	aSupportedTypes = audioFileProvider.getAudioFileTypes();
			if (TDebug.TraceAudioSystem) { TDebug.out("this AudioFileWriter supports the following Types:"); }
			for (int i = 0; i < aSupportedTypes.length; i++)
			{
				if (TDebug.TraceAudioSystem) { TDebug.out(aSupportedTypes[i].toString()); }
				supportedTypes.add(aSupportedTypes[i]);
			}
		}
		if (TDebug.TraceAudioSystem) { TDebug.out("< returning " + supportedTypes.size() + " types."); }
		return supportedTypes.toArray(EMPTY_TYPE_ARRAY);
	}



	/**	AudioFileWriterAction for isFileTypeSupported().
	 */
	private static class IsFileTypeSupportedAudioFileWriterAction
	implements AudioFileWriterAction
	{
		private AudioFileFormat.Type	m_fileType;
		private AudioInputStream	m_audioInputStream;
		private boolean			m_bSupported;



		public IsFileTypeSupportedAudioFileWriterAction(
			AudioFileFormat.Type fileType,
			AudioInputStream audioInputStream)
		{
			m_fileType = fileType;
			m_audioInputStream = audioInputStream;
			m_bSupported = false;
		}



		public boolean handleAudioFileWriter(
			AudioFileWriter mixer)
		{
			boolean bSupported = false;
			if (m_audioInputStream == null)
			{
				bSupported = mixer.isFileTypeSupported(
					m_fileType);
			}
			else
			{
				bSupported = mixer.isFileTypeSupported(
					m_fileType,
					m_audioInputStream);
			}
			m_bSupported |= bSupported;
			// interrupt the iteration depending on the result
			return bSupported;
		}



		public boolean isSupported()
		{
			return m_bSupported;
		}
	}



	public static boolean isFileTypeSupported(
		AudioFileFormat.Type fileType)
	{
		return isFileTypeSupported(fileType, null);
	}



	public static AudioFileFormat.Type[] getAudioFileTypes(
		AudioInputStream audioInputStream)
	{
		Set<AudioFileFormat.Type>		supportedTypes = new HashSet<AudioFileFormat.Type>();
		Iterator	audioFileWriters = TAudioConfig.getAudioFileWriters();
		if (TDebug.TraceAudioSystem) { TDebug.out(">AudioSystem.getAudioFileTypes()"); }
		while (audioFileWriters.hasNext())
		{
			AudioFileWriter	audioFileProvider = (AudioFileWriter) audioFileWriters.next();
			if (TDebug.TraceAudioSystem) { TDebug.out("trying AudioFileWriter: " + audioFileProvider); }
			AudioFileFormat.Type[]	aSupportedTypes = audioFileProvider.getAudioFileTypes(audioInputStream);
			if (TDebug.TraceAudioSystem) { TDebug.out("this AudioFileWriter supports the following Types:"); }
			for (int i = 0; i < aSupportedTypes.length; i++)
			{
				if (TDebug.TraceAudioSystem)
				{
					TDebug.out(aSupportedTypes[i].toString());
				}
				supportedTypes.add(aSupportedTypes[i]);
			}
		}
		if (TDebug.TraceAudioSystem) { TDebug.out("< returning " + supportedTypes.size() + " types."); }
		return supportedTypes.toArray(EMPTY_TYPE_ARRAY);
	}



	public static boolean isFileTypeSupported(
		AudioFileFormat.Type fileType,
		AudioInputStream audioInputStream)
	{
		IsFileTypeSupportedAudioFileWriterAction	action = new IsFileTypeSupportedAudioFileWriterAction(
			fileType,
			audioInputStream);
		try
		{
			doAudioFileWriterIteration(action);
		}
		catch (IOException e)
		{
			if (TDebug.TraceAllExceptions) { TDebug.out(e); }
		}
		return action.isSupported();
	}



	/**	AudioFileWriterAction for write().
	 */
	private static class WriteAudioFileWriterAction
	implements AudioFileWriterAction
	{
		private AudioInputStream	m_audioInputStream;
		private AudioFileFormat.Type	m_fileType;
		private Object			m_destination;
		private int			m_nWritten;



		public WriteAudioFileWriterAction(
			AudioInputStream audioInputStream,
			AudioFileFormat.Type fileType,
			Object destination)
		{
			m_audioInputStream = audioInputStream;
			m_fileType = fileType;
			m_destination = destination;
			m_nWritten = -1;
		}



		// $$fb 2000-04-02: variable should be called "audioFileWriter" (too much copy&paste :)
		public boolean handleAudioFileWriter(
			AudioFileWriter audioFileWriter)
			throws IOException
		{
			int	nWritten = -1;
			// $$fb 2000-04-02: need to check whether this audioFileWriter is actually
			//                  capable of handling this file type !
			if (TDebug.TraceAudioSystem) { TDebug.out(">AudioSystem.handleAudioFileWriter("+audioFileWriter.getClass().getName()+") checking for type "+m_fileType); }
			if (!audioFileWriter.isFileTypeSupported(m_fileType)) 
			{
				if (TDebug.TraceAudioSystem) { TDebug.out("< is not capable of handling this file type"); }
				return false;
			}
			try
			{
				if (m_destination instanceof OutputStream)
				{
					// $$fb 2000-04-02: s.a.
					nWritten = audioFileWriter.write(
						m_audioInputStream,
						m_fileType,
						(OutputStream) m_destination);
				}
				else if (m_destination instanceof File)
				{
					// $$fb 2000-04-02: s.a.
					nWritten = audioFileWriter.write(
						m_audioInputStream,
						m_fileType,
						(File) m_destination);
				}
				else
				{
					// TODO: debug message
				}
				m_nWritten = nWritten;
				if (TDebug.TraceAudioSystem) { TDebug.out("< wrote "+nWritten+" bytes"); }
				// interrupt the iteration
				return true;
			}
			catch (IllegalArgumentException e)
			{
				if (TDebug.TraceAudioSystem || TDebug.TraceAllExceptions) { TDebug.out(e); }
			}
			if (TDebug.TraceAudioSystem) { TDebug.out("< does not support this file type."); }
			// continue the iteration
			return false;
		}



		public int getWritten()
		{
			return m_nWritten;
		}
	}



	public static int write(AudioInputStream inputStream,
				AudioFileFormat.Type fileType,
				OutputStream outputStream)
		throws IOException
	{
		return writeImpl(inputStream,
				 fileType,
				 outputStream);
	}



	public static int write(AudioInputStream inputStream,
				AudioFileFormat.Type fileType,
				File file)
		throws IOException
	{
		return writeImpl(inputStream,
				 fileType,
				 file);
	}



	private static int writeImpl(AudioInputStream audioInputStream,
				     AudioFileFormat.Type fileType,
				     Object destination)
		throws IOException
	{
		WriteAudioFileWriterAction	action = new WriteAudioFileWriterAction(
			audioInputStream,
			fileType,
			destination);
		doAudioFileWriterIteration(action);
		int	nWritten = action.getWritten();
		if (nWritten == -1)
		{
			throw new IllegalArgumentException("format not  supported");
		}
		else
		{
			return nWritten;
		}
	}




	//////////////////////////////////////////////////////////////
	//
	//	auxiliary methods and interfaces
	//
	//////////////////////////////////////////////////////////////



	private static interface MixerProviderAction
	{
		public boolean handleMixerProvider(MixerProvider mixerProvider);
	}



	/**	Iterates over the available MixerProviders.
	 *	For each MixerProvider, the mathod handleMixerProvider() of
	 *	the passed MixerProviderAction is called with the MixerProvider
	 *	in question as the only argument.
	 */
	private static void doMixerProviderIteration(
		MixerProviderAction action)
	{
		Iterator	mixerProviders = TAudioConfig.getMixerProviders();
		boolean	bCompleted = false;
		if (TDebug.TraceAudioSystem) { TDebug.out(">AudioSystem.doMixerProviderIteration()"); }
		while (mixerProviders.hasNext() && ! bCompleted)
		{
			MixerProvider	mixerProvider = (MixerProvider) mixerProviders.next();
			if (TDebug.TraceAudioSystem) { TDebug.out("handling MixerProvider: " + mixerProvider); }
			bCompleted = action.handleMixerProvider(mixerProvider);
		}
		if (TDebug.TraceAudioSystem) { TDebug.out("< completed="+bCompleted); }
	}



	private static interface MixerAction
	{
		public boolean handleMixer(Mixer mixer);
	}



	/**	Iterates over the available MixerProviders.
	 *	For each MixerProvider, the mathod handleMixerProvider() of
	 *	the passed MixerProviderAction is called with the MixerProvider
	 *	in question as the only argument.
	 */
	private static void doMixerIteration(
		MixerAction action)
	{
		Mixer.Info[]	mixerInfos = getMixerInfo();
		boolean	bCompleted = false;
		if (TDebug.TraceAudioSystem) { TDebug.out(">AudioSystem.doMixerIteration()"); }
		for (int nMixer = 0; nMixer < mixerInfos.length && ! bCompleted; nMixer++)
		{
			Mixer	mixer = getMixer(mixerInfos[nMixer]);
			if (TDebug.TraceAudioSystem) { TDebug.out("handling Mixer: " + mixer); }
			bCompleted = action.handleMixer(mixer);
		}
		if (TDebug.TraceAudioSystem) { TDebug.out("< completed="+bCompleted); }
	}



	private static interface FormatConversionProviderAction
	{
		public boolean handleFormatConversionProvider(FormatConversionProvider formatConversionProvider);
		// throws IOException;
	}



	/**	Iterates over the available FormatConversionProviders.
	 *	For each FormatConversionProvider, the mathod handleFormatConversionProvider() of
	 *	the passed FormatConversionProviderAction is called with the FormatConversionProvider
	 *	in question as the only argument.
	 */
	private static void doFormatConversionProviderIteration(
		FormatConversionProviderAction action)
		// throws IOException
	{
		Iterator	formatConversionProviders = TAudioConfig.getFormatConversionProviders();
		boolean bCompleted = false;
		if (TDebug.TraceAudioSystem) { TDebug.out(">AudioSystem.doFormatConversionProviderIteration()"); }
		while (formatConversionProviders.hasNext() && ! bCompleted)
		{
			FormatConversionProvider	formatConversionProvider = (FormatConversionProvider) formatConversionProviders.next();
			if (TDebug.TraceAudioSystem) { TDebug.out("handling FormatConversionProvider: " + formatConversionProvider); }
			bCompleted = action.handleFormatConversionProvider(formatConversionProvider);
		}
		if (TDebug.TraceAudioSystem) { TDebug.out("< completed="+bCompleted); }
	}



	private static interface AudioFileReaderAction
	{
		public boolean handleAudioFileReader(AudioFileReader audioFileReader)
			throws IOException;
	}



	/**	Iterates over the available AudioFileReaders.
	 *	For each AudioFileReader, the mathod handleAudioFileReader() of
	 *	the passed AudioFileReaderAction is called with the AudioFileReader
	 *	in question as the only argument.
	 */
	private static void doAudioFileReaderIteration(
		AudioFileReaderAction action)
		throws IOException
	{
		Iterator	audioFileReaders = TAudioConfig.getAudioFileReaders();
		boolean bCompleted = false;
		if (TDebug.TraceAudioSystem) { TDebug.out(">AudioSystem.doAudioFileReaderIteration()"); }
		while (audioFileReaders.hasNext() && ! bCompleted)
		{
			AudioFileReader	audioFileReader = (AudioFileReader) audioFileReaders.next();
			if (TDebug.TraceAudioSystem) { TDebug.out("handling AudioFileReader: " + audioFileReader); }
			bCompleted = action.handleAudioFileReader(audioFileReader);
		}
		if (TDebug.TraceAudioSystem) { TDebug.out("< completed="+bCompleted); }
	}



	private static interface AudioFileWriterAction
	{
		public boolean handleAudioFileWriter(AudioFileWriter audioFileWriter)
			throws IOException;
	}



	/**	Iterates over the available AudioFileWriters.
	 *	For each AudioFileWriter, the mathod handleAudioFileWriter() of
	 *	the passed AudioFileWriterAction is called with the AudioFileWriter
	 *	in question as the only argument.
	 */
	private static void doAudioFileWriterIteration(
		AudioFileWriterAction action)
		throws IOException
	{
		Iterator	audioFileWriters = TAudioConfig.getAudioFileWriters();
		boolean bCompleted = false;
		if (TDebug.TraceAudioSystem) { TDebug.out(">AudioSystem.doAudioFileWriterIteration()"); }
		while (audioFileWriters.hasNext() && ! bCompleted)
		{
			AudioFileWriter	audioFileWriter = (AudioFileWriter) audioFileWriters.next();
			if (TDebug.TraceAudioSystem) { TDebug.out("handling AudioFileWriter: " + audioFileWriter); }
			bCompleted = action.handleAudioFileWriter(audioFileWriter);
		}
		if (TDebug.TraceAudioSystem) { TDebug.out("< completed="+bCompleted); }
	}
}



/*** AudioSystem.java ***/
