package paulscode.sound.libraries;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.Mixer;
import javax.sound.sampled.SourceDataLine;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Set;
import paulscode.sound.Channel;
import paulscode.sound.FilenameURL;
import paulscode.sound.ICodec;
import paulscode.sound.Library;
import paulscode.sound.Source;
import paulscode.sound.SoundBuffer;
import paulscode.sound.SoundSystem;
import paulscode.sound.SoundSystemConfig;
import paulscode.sound.SoundSystemException;
/**
* The LibraryJavaSound class interfaces the JavaSound library.
* For more information about the JavaSound API, visit
* http://java.sun.com/products/java-media/sound/
*<br><br>
*<b><i> SoundSystem LibraryJavaSound License:</b></i><br><b><br>
* You are free to use this library for any purpose, commercial or otherwise.
* You may modify this library or source code, and distribute it any way you
* like, provided the following conditions are met:
*<br>
* 1) You may not falsely claim to be the author of this library or any
* unmodified portion of it.
*<br>
* 2) You may not copyright this library or a modified version of it and then
* sue me for copyright infringement.
*<br>
* 3) If you modify the source code, you must clearly document the changes
* made before redistributing the modified source code, so other users know
* it is not the original code.
*<br>
* 4) You are not required to give me credit for this library in any derived
* work, but if you do, you must also mention my website:
* http://www.paulscode.com
*<br>
* 5) I the author will not be responsible for any damages (physical,
* financial, or otherwise) caused by the use if this library or any part
* of it.
*<br>
* 6) I the author do not guarantee, warrant, or make any representations,
* either expressed or implied, regarding the use of this library or any
* part of it.
* <br><br>
* Author: Paul Lamb
* <br>
* http://www.paulscode.com
* </b>
*/
public class LibraryJavaSound extends Library
{
/**
* Used to return a current value from one of the synchronized interface
* methods.
*/
private static final boolean GET = false;
/**
* Used to set the value in one of the synchronized interface methods.
*/
private static final boolean SET = true;
/**
* Null parameter for one the synchronized interface methods.
*/
private static final int XXX = 0;
/**
* The maximum safe size for a JavaSound clip.
*/
private final int maxClipSize = 1048576;
/**
* Mixes all the playing sources.
*/
private static Mixer myMixer = null;
/**
* Contains information on capabilities supported by the mixer.
*/
private static MixerRanking myMixerRanking = null;
/**
* Handle to the LibraryJavaSound instance.
*/
private static LibraryJavaSound instance = null;
/**
* Preferred minimum sample rate.
*/
private static int minSampleRate = 4000;
/**
* Preferred maximum sample rate.
*/
private static int maxSampleRate = 48000;
/**
* Preferred maximum number of output lines.
*/
private static int lineCount = 32;
/**
* Whether or not to use the gain control.
*/
private static boolean useGainControl = true;
/**
* Whether or not to use the pan control.
*/
private static boolean usePanControl = true;
/**
* Whether or not to use the sample rate control.
*/
private static boolean useSampleRateControl = true;
/**
* Constructor: Instantiates the source map, buffer map and listener
* information. Also sets the library type to
* SoundSystemConfig.LIBRARY_JAVASOUND
*/
public LibraryJavaSound() throws SoundSystemException
{
super();
instance = this;
}
/**
* Initializes Javasound.
*/
@Override
public void init() throws SoundSystemException
{
MixerRanking mixerRanker = null;
// Check if a mixer has already been defined:
if( myMixer == null )
{
// Nope, try the default Java Sound mixer first:
for( Mixer.Info mixerInfo : AudioSystem.getMixerInfo() )
{
if( mixerInfo.getName().equals( "Java Sound Audio Engine" ) )
{
// Found it, make sure it measures up to standards
mixerRanker = new MixerRanking();
try
{
mixerRanker.rank( mixerInfo );
}
catch( LibraryJavaSound.Exception ljse )
{
// Serious problem, don't use it!
break;
}
if( mixerRanker.rank < 14 )
break; // Minor problem, see if there is a better mixer
// It's a good mixer, let's use it:
myMixer = AudioSystem.getMixer( mixerInfo );
mixerRanking( SET, mixerRanker );
break;
}
}
// See if we have a mixer yet:
if( myMixer == null )
{
// Nope, rank all the available mixers
MixerRanking bestRankedMixer = mixerRanker;
for( Mixer.Info mixerInfo : AudioSystem.getMixerInfo() )
{
mixerRanker = new MixerRanking();
try
{
// See how good it is
mixerRanker.rank( mixerInfo );
}
catch( LibraryJavaSound.Exception ljse )
{}
// If this one is better, save it:
if( bestRankedMixer == null || mixerRanker.rank >
bestRankedMixer.rank )
bestRankedMixer = mixerRanker;
}
// Check if didn't find any useable mixers at all:
if( bestRankedMixer == null )
throw new LibraryJavaSound.Exception( "No useable mixers " +
"found!" , new MixerRanking() );
try
{
// Use the best available mixer
myMixer = AudioSystem.getMixer( bestRankedMixer.mixerInfo );
mixerRanking( SET, bestRankedMixer );
}
catch( java.lang.Exception e )
{
// Why did we arive here? Better be prepared for anything
throw new LibraryJavaSound.Exception( "No useable mixers " +
"available!" , new MixerRanking() );
}
}
}
// Start out at full volume:
setMasterVolume( 1.0f );
// Let the user know if everything is ok:
message( "JavaSound initialized." );
super.init();
}
/**
* Checks if the JavaSound library type is compatible.
* @return True or false.
*/
public static boolean libraryCompatible()
{
// No real "loading" for the JavaSound library, just grab the Mixer:
for( Mixer.Info mixerInfo : AudioSystem.getMixerInfo() )
{
if( mixerInfo.getName().equals( "Java Sound Audio Engine" ) )
return true;
}
return false;
}
/**
* Creates a new channel of the specified type (normal or streaming). Possible
* values for channel type can be found in the
* {@link paulscode.sound.SoundSystemConfig SoundSystemConfig} class.
* @param type Type of channel.
*/
@Override
protected Channel createChannel( int type )
{
return new ChannelJavaSound( type, myMixer );
}
/**
* Stops all sources, and removes references to all instantiated objects.
*/
@Override
public void cleanup()
{
super.cleanup();
instance = null;
myMixer = null;
myMixerRanking = null;
}
/**
* Pre-loads a sound into memory.
* @param filenameURL Filename/URL of a sound file to load.
* @return True if the sound loaded properly.
*/
@Override
public boolean loadSound( FilenameURL filenameURL )
{
// Make sure the buffer map exists:
if( bufferMap == null )
{
bufferMap = new HashMap<String, SoundBuffer>();
importantMessage( "Buffer Map was null in method 'loadSound'" );
}
// make sure they gave us a filename:
if( errorCheck( filenameURL == null,
"Filename/URL not specified in method 'loadSound'" ) )
return false;
// check if it is already loaded:
if( bufferMap.get( filenameURL.getFilename() ) != null )
return true;
ICodec codec = SoundSystemConfig.getCodec( filenameURL.getFilename() );
if( errorCheck( codec == null, "No codec found for file '" +
filenameURL.getFilename() +
"' in method 'loadSound'" ) )
return false;
URL url = filenameURL.getURL();
if( errorCheck( url == null, "Unable to open file '" +
filenameURL.getFilename() +
"' in method 'loadSound'" ) )
return false;
codec.initialize( url );
SoundBuffer buffer = codec.readAll();
codec.cleanup();
codec = null;
if( buffer != null )
bufferMap.put( filenameURL.getFilename(), buffer );
else
errorMessage( "Sound buffer null in method 'loadSound'" );
return true;
}
/**
* Saves the specified sample data, under the specified identifier. This
* identifier can be later used in place of 'filename' parameters to reference
* the sample data.
* @param buffer the sample data and audio format to save.
* @param identifier What to call the sample.
* @return True if there weren't any problems.
*/
@Override
public boolean loadSound( SoundBuffer buffer, String identifier )
{
// Make sure the buffer map exists:
if( bufferMap == null )
{
bufferMap = new HashMap<String, SoundBuffer>();
importantMessage( "Buffer Map was null in method 'loadSound'" );
}
// make sure they gave us an identifier:
if( errorCheck(identifier == null,
"Identifier not specified in method 'loadSound'" ) )
return false;
// check if it is already loaded:
if( bufferMap.get( identifier ) != null )
return true;
// save it for later:
if( buffer != null )
bufferMap.put( identifier, buffer );
else
errorMessage( "Sound buffer null in method 'loadSound'" );
return true;
}
/**
* Sets the overall volume to the specified value, affecting all sources.
* @param value New volume, float value ( 0.0f - 1.0f ).
*/
@Override
public void setMasterVolume( float value )
{
super.setMasterVolume( value );
Set<String> keys = sourceMap.keySet();
Iterator<String> iter = keys.iterator();
String sourcename;
Source source;
// loop through and update the volume of all sources:
while( iter.hasNext() )
{
sourcename = iter.next();
source = sourceMap.get( sourcename );
if( source != null )
source.positionChanged();
}
}
/**
* Creates a new source and places it into the source map.
* @param priority Setting this to true will prevent other sounds from overriding this one.
* @param toStream Setting this to true will load the sound in pieces rather than all at once.
* @param toLoop Should this source loop, or play only once.
* @param sourcename A unique identifier for this source. Two sources may not use the same sourcename.
* @param filenameURL Filename/URL of the sound file to play at this source.
* @param x X position for this source.
* @param y Y position for this source.
* @param z Z position for this source.
* @param attModel Attenuation model to use.
* @param distOrRoll Either the fading distance or rolloff factor, depending on the value of "attmodel".
*/
@Override
public void newSource( boolean priority, boolean toStream, boolean toLoop,
String sourcename, FilenameURL filenameURL, float x,
float y, float z, int attModel, float distOrRoll )
{
SoundBuffer buffer = null;
if( !toStream )
{
// Grab the audio data for this file:
buffer = bufferMap.get( filenameURL.getFilename() );
// if not found, try loading it:
if( buffer == null )
{
if( !loadSound( filenameURL ) )
{
errorMessage( "Source '" + sourcename + "' was not created "
+ "because an error occurred while loading "
+ filenameURL.getFilename() );
return;
}
}
// try and grab the sound buffer again:
buffer = bufferMap.get( filenameURL.getFilename() );
// see if it was there this time:
if( buffer == null )
{
errorMessage( "Source '" + sourcename + "' was not created "
+ "because audio data was not found for "
+ filenameURL.getFilename() );
return;
}
}
if( !toStream && buffer != null )
buffer.trimData( maxClipSize );
sourceMap.put( sourcename,
new SourceJavaSound( listener, priority, toStream,
toLoop, sourcename, filenameURL,
buffer, x, y, z, attModel,
distOrRoll, false ) );
}
/**
* Opens a direct line for streaming audio data.
* @param audioFormat Format that the data will be in.
* @param priority Setting this to true will prevent other sounds from overriding this one.
* @param sourcename A unique identifier for this source. Two sources may not use the same sourcename.
* @param x X position for this source.
* @param y Y position for this source.
* @param z Z position for this source.
* @param attModel Attenuation model to use.
* @param distOrRoll Either the fading distance or rolloff factor, depending on the value of "attmodel".
*/
@Override
public void rawDataStream( AudioFormat audioFormat, boolean priority,
String sourcename, float x, float y,
float z, int attModel, float distOrRoll )
{
sourceMap.put( sourcename,
new SourceJavaSound( listener, audioFormat, priority,
sourcename, x, y, z, attModel,
distOrRoll ) );
}
/**
* Creates and immediately plays a new source.
* @param priority Setting this to true will prevent other sounds from overriding this one.
* @param toStream Setting this to true will load the sound in pieces rather than all at once.
* @param toLoop Should this source loop, or play only once.
* @param sourcename A unique identifier for this source. Two sources may not use the same sourcename.
* @param filenameURL Filename/URL of the sound file to play at this source.
* @param x X position for this source.
* @param y Y position for this source.
* @param z Z position for this source.
* @param attModel Attenuation model to use.
* @param distOrRoll Either the fading distance or rolloff factor, depending on the value of "attmodel".
* @param temporary Whether or not this source should be removed after it finishes playing.
*/
@Override
public void quickPlay( boolean priority, boolean toStream, boolean toLoop,
String sourcename, FilenameURL filenameURL, float x,
float y, float z, int attModel, float distOrRoll,
boolean temporary )
{
SoundBuffer buffer = null;
if( !toStream )
{
// Grab the audio data for this file:
buffer = bufferMap.get( filenameURL.getFilename() );
// if not found, try loading it:
if( buffer == null )
{
if( !loadSound( filenameURL ) )
{
errorMessage( "Source '" + sourcename + "' was not created "
+ "because an error occurred while loading "
+ filenameURL.getFilename() );
return;
}
}
// try and grab the sound buffer again:
buffer = bufferMap.get( filenameURL.getFilename() );
// see if it was there this time:
if( buffer == null )
{
errorMessage( "Source '" + sourcename + "' was not created "
+ "because audio data was not found for "
+ filenameURL.getFilename() );
return;
}
}
if( !toStream && buffer != null)
buffer.trimData( maxClipSize );
sourceMap.put( sourcename,
new SourceJavaSound( listener, priority, toStream,
toLoop, sourcename, filenameURL,
buffer, x, y, z, attModel,
distOrRoll, temporary ) );
}
/**
* Creates sources based on the source map provided.
* @param srcMap Sources to copy.
*/
@Override
public void copySources( HashMap<String, Source> srcMap )
{
if( srcMap == null )
return;
Set<String> keys = srcMap.keySet();
Iterator<String> iter = keys.iterator();
String sourcename;
Source source;
// Make sure the buffer map exists:
if( bufferMap == null )
{
bufferMap = new HashMap<String, SoundBuffer>();
importantMessage( "Buffer Map was null in method 'copySources'" );
}
// remove any existing sources before starting:
sourceMap.clear();
SoundBuffer buffer;
// loop through and copy all the sources:
while( iter.hasNext() )
{
sourcename = iter.next();
source = srcMap.get( sourcename );
if( source != null )
{
buffer = null;
if( !source.toStream )
{
loadSound( source.filenameURL );
buffer = bufferMap.get( source.filenameURL.getFilename() );
}
if( !source.toStream && buffer != null )
{
buffer.trimData( maxClipSize );
}
if( source.toStream || buffer != null )
{
sourceMap.put( sourcename, new SourceJavaSound( listener,
source, buffer ) );
}
}
}
}
/**
* Sets the listener's velocity, for use in Doppler effect.
* @param x Velocity along world x-axis.
* @param y Velocity along world y-axis.
* @param z Velocity along world z-axis.
*/
@Override
public void setListenerVelocity( float x, float y, float z )
{
super.setListenerVelocity( x, y, z );
listenerMoved();
}
/**
* The Doppler parameters have changed.
*/
@Override
public void dopplerChanged()
{
super.dopplerChanged();
listenerMoved();
}
/**
* Returns a handle to the current JavaSound mixer, or null if no mixer has
* been set yet.
* @return Handle to the mixer or null.
*/
public static Mixer getMixer()
{
return mixer( GET, null );
}
/**
* Sets the current mixer. If this method is not called, the
* "Java Sound Audio Engine" mixer will be used by default.
* @param m New mixer.
*/
public static void setMixer( Mixer m ) throws SoundSystemException
{
mixer( SET, m );
SoundSystemException e = SoundSystem.getLastException();
SoundSystem.setException( null );
if( e != null )
throw e;
}
/**
* Either sets or returns the current mixer.
* @param action GET or SET.
* @param m New mixer or null.
* @return Handle to the mixer.
*/
private static synchronized Mixer mixer( boolean action, Mixer m )
{
if( action == SET )
{
if( m == null )
return myMixer;
MixerRanking mixerRanker = new MixerRanking();
try
{
mixerRanker.rank( m.getMixerInfo() );
}
catch( LibraryJavaSound.Exception ljse )
{
SoundSystemConfig.getLogger().printStackTrace( ljse, 1 );
SoundSystem.setException( ljse );
}
myMixer = m;
mixerRanking( SET, mixerRanker );
ChannelJavaSound c;
if( instance != null )
{
ListIterator<Channel> itr =
instance.normalChannels.listIterator();
SoundSystem.setException( null );
while( itr.hasNext() )
{
c = (ChannelJavaSound) itr.next();
c.newMixer( m );
}
itr = instance.streamingChannels.listIterator();
while( itr.hasNext() )
{
c = (ChannelJavaSound) itr.next();
c.newMixer( m );
}
}
}
return myMixer;
}
public static MixerRanking getMixerRanking()
{
return mixerRanking( GET, null );
}
private static synchronized MixerRanking mixerRanking( boolean action,
MixerRanking value )
{
if( action == SET )
myMixerRanking = value;
return myMixerRanking;
}
public static void setMinSampleRate( int value )
{
minSampleRate( SET, value );
}
private static synchronized int minSampleRate( boolean action, int value )
{
if( action == SET )
minSampleRate = value;
return minSampleRate;
}
public static void setMaxSampleRate( int value )
{
maxSampleRate( SET, value );
}
private static synchronized int maxSampleRate( boolean action, int value )
{
if( action == SET )
maxSampleRate = value;
return maxSampleRate;
}
public static void setLineCount( int value )
{
lineCount( SET, value );
}
private static synchronized int lineCount( boolean action, int value )
{
if( action == SET )
lineCount = value;
return lineCount;
}
public static void useGainControl( boolean value )
{
useGainControl( SET, value );
}
private static synchronized boolean useGainControl( boolean action,
boolean value )
{
if( action == SET )
useGainControl = value;
return useGainControl;
}
public static void usePanControl( boolean value )
{
usePanControl( SET, value );
}
private static synchronized boolean usePanControl( boolean action,
boolean value )
{
if( action == SET )
usePanControl = value;
return usePanControl;
}
public static void useSampleRateControl( boolean value )
{
useSampleRateControl( SET, value );
}
private static synchronized boolean useSampleRateControl( boolean action,
boolean value )
{
if( action == SET )
useSampleRateControl = value;
return useSampleRateControl;
}
/**
* Returns the short title of this library type.
* @return A short title.
*/
public static String getTitle()
{
return "Java Sound";
}
/**
* Returns a longer description of this library type.
* @return A longer description.
*/
public static String getDescription()
{
return "The Java Sound API. For more information, see " +
"http://java.sun.com/products/java-media/sound/";
}
/**
* Returns the name of the class.
* @return "Library" + library title.
*/
@Override
public String getClassName()
{
return "LibraryJavaSound";
}
/**
* The MixerRanking class is used to determine the capabilities of a mixer,
* and to give it a ranking based on the priority of those capabilities.
*/
public static class MixerRanking
{
/**
* A priority of HIGH means the Mixer is not usable if the capability
* is not available.
*/
public static final int HIGH = 1;
/**
* A priority of MEDIUM means the Mixer is usable without the
* capability, but functionality is greatly limited.
*/
public static final int MEDIUM = 2;
/**
* A priority of LOW means the Mixer is usable without the capability,
* but functionality may be somewhat limited.
*/
public static final int LOW = 3;
/**
* A priority of NONE means the Mixer is fully functional, and loss of
* this capability does not affect the overall ranking. Used to ignore
* capibilities not used by a particular application.
*/
public static final int NONE = 4;
/**
* Priority for the Mixer existing. Should always be HIGH.
*/
public static int MIXER_EXISTS_PRIORITY = HIGH;
/**
* Priority for the desired minimum sample-rate compatibility. By
* default, this is HIGH.
*/
public static int MIN_SAMPLE_RATE_PRIORITY = HIGH;
/**
* Priority for the desired maximum sample-rate compatibility. By
* default, this is HIGH.
*/
public static int MAX_SAMPLE_RATE_PRIORITY = HIGH;
/**
* Priority for the desired maximum line-count. By default, this is
* HIGH.
*/
public static int LINE_COUNT_PRIORITY = HIGH;
/**
* Priority for the gain control. By default, this is MEDIUM.
*/
public static int GAIN_CONTROL_PRIORITY = MEDIUM;
/**
* Priority for the pan control. By default, this is MEDIUM.
*/
public static int PAN_CONTROL_PRIORITY = MEDIUM;
/**
* Priority for the sample-rate control. By default, this is LOW.
*/
public static int SAMPLE_RATE_CONTROL_PRIORITY = LOW;
/**
* Standard information about the Mixer.
*/
public Mixer.Info mixerInfo = null;
/**
* The Mixer's overall ranking. Maximum is 14, meaning fully
* functional. Minimum is 0, meaning not usable.
*/
public int rank = 0;
/**
* Indicates whether or not the Mixer exists.
*/
public boolean mixerExists = false;
/**
* Indicates whether or not the desired minimum sample-rate is
* compatible on the Mixer.
*/
public boolean minSampleRateOK = false;
/**
* Indicates whether or not the desired maximum sample-rate is
* compatible on the Mixer.
*/
public boolean maxSampleRateOK = false;
/**
* Indicates whether or not the desired number of lines can be created
* on the Mixer.
*/
public boolean lineCountOK = false;
/**
* Indicates whether or not gain controls are possible on the Mixer.
*/
public boolean gainControlOK = false;
/**
* Indicates whether or not pan controls are possible on the Mixer.
*/
public boolean panControlOK = false;
/**
* Indicates whether or not sample-rate controls are possible on the
* Mixer.
*/
public boolean sampleRateControlOK = false;
/**
* Indicates the minimum sample rate possible for the Mixer, or -1 if
* no sample rate is possible.
*/
public int minSampleRatePossible = -1;
/**
* Indicates the maximum sample rate possible for the Mixer, or -1 if
* no sample rate is possible.
*/
public int maxSampleRatePossible = -1;
/**
* Indicates the maximum number of output lines the Mixer can handle.
*/
public int maxLinesPossible = 0;
/**
* Constructor: Instantiates a Mixer ranking with default initial
* values.
*/
public MixerRanking()
{}
/**
* Constructor: Instantiates a Mixer ranking with specified initial
* values.
* @param i Standard information about the mixer.
* @param r Overall ranking of the mixer.
* @param e Whether or not the mixer exists.
* @param mnsr Whether or not minimum sample-rate is compatible.
* @param mxsr Whether or not maximum sample-rate is compatible.
* @param lc Whether or not number of lines are compatible.
* @param gc Whether or not gain controls are compatible.
* @param pc Whether or not pan controls are compatible.
* @param src Whether or not sample-rate controls are compatible.
*/
public MixerRanking( Mixer.Info i, int r, boolean e, boolean mnsr,
boolean mxsr, boolean lc, boolean gc, boolean pc,
boolean src )
{
mixerInfo = i;
rank = r;
mixerExists = e;
minSampleRateOK = mnsr;
maxSampleRateOK = mxsr;
lineCountOK = lc;
gainControlOK = gc;
panControlOK = pc;
sampleRateControlOK = src;
}
/**
* Looks up the specified Mixer, tests its capabilities, and calculates
* its overall ranking.
* @param i Standard information about the mixer.
*/
public void rank( Mixer.Info i ) throws LibraryJavaSound.Exception
{
// STEP 1: Determine if the Mixer exists
if( i == null )
throw new LibraryJavaSound.Exception( "No Mixer info " +
"specified in method 'MixerRanking.rank'" , this );
mixerInfo = i;
Mixer m;
try
{
m = AudioSystem.getMixer( mixerInfo );
}
catch( java.lang.Exception e )
{
throw new LibraryJavaSound.Exception( "Unable to acquire the " +
"specified Mixer in method 'MixerRanking.rank'" , this );
}
if( m == null )
throw new LibraryJavaSound.Exception( "Unable to acquire the " +
"specified Mixer in method 'MixerRanking.rank'" , this );
mixerExists = true;
// STEP 2: Check if the desired sample-rate range is possible
AudioFormat format;
DataLine.Info lineInfo;
try
{
format = new AudioFormat( minSampleRate( GET, XXX ), 16, 2, true,
false );
lineInfo = new DataLine.Info( SourceDataLine.class, format );
}
catch( java.lang.Exception e )
{
throw new LibraryJavaSound.Exception( "Invalid minimum " +
"sample-rate specified in method 'MixerRanking.rank'" , this );
}
if( !AudioSystem.isLineSupported( lineInfo ) )
{
if( MIN_SAMPLE_RATE_PRIORITY == HIGH )
throw new LibraryJavaSound.Exception( "Specified minimum " +
"sample-rate not possible for Mixer '" +
mixerInfo.getName() + "'" , this );
}
else
{
minSampleRateOK = true;
}
try
{
format = new AudioFormat( maxSampleRate( GET, XXX ), 16, 2,
true, false );
lineInfo = new DataLine.Info( SourceDataLine.class, format );
}
catch( java.lang.Exception e )
{
throw new LibraryJavaSound.Exception( "Invalid maximum " +
"sample-rate specified in method 'MixerRanking.rank'" , this );
}
if( !AudioSystem.isLineSupported( lineInfo ) )
{
if( MAX_SAMPLE_RATE_PRIORITY == HIGH )
throw new LibraryJavaSound.Exception( "Specified maximum " +
"sample-rate not possible for Mixer '" +
mixerInfo.getName() + "'" , this );
}
else
{
maxSampleRateOK = true;
}
// STEP 3: If desired range is not possible, figure out what is
int lL;
int uL;
int testSampleRate;
if( minSampleRateOK )
{
minSampleRatePossible = minSampleRate( GET, XXX );
}
else
{
// Find the lower limit:
lL = minSampleRate( GET, XXX );
uL = maxSampleRate( GET, XXX );
while( uL - lL > 1 )
{
testSampleRate = lL + (uL - lL ) / 2;
format = new AudioFormat( testSampleRate, 16, 2, true, false );
lineInfo = new DataLine.Info( SourceDataLine.class, format );
if( AudioSystem.isLineSupported( lineInfo ) )
{
minSampleRatePossible = testSampleRate;
uL = testSampleRate;
}
else
{
lL = testSampleRate;
}
}
}
if( maxSampleRateOK )
{
maxSampleRatePossible = maxSampleRate( GET, XXX );
}
else if( minSampleRatePossible != -1 )
{
// Find the upper limit:
uL = maxSampleRate( GET, XXX );
lL = minSampleRatePossible;
while( uL - lL > 1 )
{
testSampleRate = lL + (uL - lL ) / 2;
format = new AudioFormat( testSampleRate, 16, 2, true, false );
lineInfo = new DataLine.Info( SourceDataLine.class, format );
if( AudioSystem.isLineSupported( lineInfo ) )
{
maxSampleRatePossible = testSampleRate;
lL = testSampleRate;
}
else
{
uL = testSampleRate;
}
}
}
// Make sure we have some range of possible sample-rates:
if( minSampleRatePossible == -1 || maxSampleRatePossible == -1 )
throw new LibraryJavaSound.Exception( "No possible " +
"sample-rate found for Mixer '" +
mixerInfo.getName() + "'" , this );
//STEP 4: Determine if the specified number of lines is possible:
format = new AudioFormat( minSampleRatePossible, 16, 2, true, false );
Clip clip = null;
try
{
DataLine.Info clipLineInfo = new DataLine.Info( Clip.class,
format );
clip = (Clip) m.getLine( clipLineInfo );
byte[] buffer = new byte[10];
clip.open( format, buffer, 0, buffer.length );
}
catch( java.lang.Exception e )
{
throw new LibraryJavaSound.Exception( "Unable to attach an " +
"actual audio buffer " +
"to an actual Clip... " +
"Mixer '" +
mixerInfo.getName() +
"' is unuseable.", this );
}
maxLinesPossible = 1;
lineInfo = new DataLine.Info( SourceDataLine.class, format );
SourceDataLine[] lines = new SourceDataLine[lineCount( GET, XXX )
- 1];
int c = 0;
int x;
for( x = 1; x < lines.length + 1; x++ )
{
try
{
lines[x-1] = (SourceDataLine) m.getLine( lineInfo );
}
catch( java.lang.Exception e )
{
if( x == 0 )
throw new LibraryJavaSound.Exception( "No output " +
"lines possible for Mixer '" +
mixerInfo.getName() + "'" , this );
else if( LINE_COUNT_PRIORITY == HIGH )
throw new LibraryJavaSound.Exception( "Specified " +
"maximum number of lines not possible for Mixer '" +
mixerInfo.getName() + "'" , this );
break;
}
maxLinesPossible = x + 1;
}
try
{
clip.close();
}
catch( java.lang.Exception e )
{}
clip = null;
if( maxLinesPossible == lineCount( GET, XXX ) )
{
lineCountOK = true;
}
//STEP 5: Check which controls are available:
if( !useGainControl( GET, false ) )
{
GAIN_CONTROL_PRIORITY = NONE;
}
else if( !lines[0].isControlSupported(
FloatControl.Type.MASTER_GAIN ) )
{
if( GAIN_CONTROL_PRIORITY == HIGH )
throw new LibraryJavaSound.Exception( "Gain control " +
"not available for Mixer '" +
mixerInfo.getName() + "'" , this );
}
else
{
gainControlOK = true;
}
if( !usePanControl( GET, false ) )
{
PAN_CONTROL_PRIORITY = NONE;
}
else if( !lines[0].isControlSupported( FloatControl.Type.PAN ) )
{
if( PAN_CONTROL_PRIORITY == HIGH )
throw new LibraryJavaSound.Exception( "Pan control " +
"not available for Mixer '" +
mixerInfo.getName() + "'" , this );
}
else
{
panControlOK = true;
}
if( !useSampleRateControl( GET, false ) )
{
SAMPLE_RATE_CONTROL_PRIORITY = NONE;
}
else if( !lines[0].isControlSupported(
FloatControl.Type.SAMPLE_RATE ) )
{
if( SAMPLE_RATE_CONTROL_PRIORITY == HIGH )
throw new LibraryJavaSound.Exception( "Sample-rate " +
"control not available for " +
"Mixer '" +
mixerInfo.getName() + "'" , this );
}
else
{
sampleRateControlOK = true;
}
//STEP 6: Calculate the Mixer's rank:
rank += getRankValue( mixerExists, MIXER_EXISTS_PRIORITY );
rank += getRankValue( minSampleRateOK, MIN_SAMPLE_RATE_PRIORITY );
rank += getRankValue( maxSampleRateOK, MAX_SAMPLE_RATE_PRIORITY );
rank += getRankValue( lineCountOK, LINE_COUNT_PRIORITY );
rank += getRankValue( gainControlOK, GAIN_CONTROL_PRIORITY );
rank += getRankValue( panControlOK, PAN_CONTROL_PRIORITY );
rank += getRankValue( sampleRateControlOK,
SAMPLE_RATE_CONTROL_PRIORITY );
//STEP 7: Clean up:
m = null;
format = null;
lineInfo = null;
lines = null;
}
/**
* Calculates the value of the specified property (or lack thereof).
* @param property Whether or not the property is available.
* @param priority The priority of the specified property.
* @return value to add to the Mixer's rank.
*/
private int getRankValue( boolean property, int priority )
{
// Maximum value if the propery is available:
if( property )
return 2;
// Property isn't available..
// Full value if the property has no priority:
if( priority == NONE )
return 2;
// Half-value if the priority is low:
if( priority == LOW )
return 1;
// Otherwise, no value:
return 0;
}
}
/**
* The LibraryJavaSound.Exception class provides library-specific error
* information.
*/
public static class Exception extends SoundSystemException
{
/**
* Global identifier for a problem with the mixer.
*/
public static final int MIXER_PROBLEM = 101;
/**
* If there is a mixer problem, this will hold additional information.
*/
public static MixerRanking mixerRanking = null;
/**
* Constructor: Generates a standard "unknown error" exception with the
* specified message.
* @param message A brief description of the problem that occurred.
*/
public Exception( String message )
{
super( message );
}
/**
* Constructor: Generates an exception of the specified type, with the
* specified message.
* @param message A brief description of the problem that occurred.
* @param type Identifier indicating they type of error.
*/
public Exception( String message, int type )
{
super( message, type );
}
/**
* Constructor: Generates a "Mixer Problem" exception with the specified
* message. Also, the mixer ranking is stored, containing additional
* information about the problem.
* @param message A brief description of the problem that occurred.
* @param rank Ranking of the mixer involved.
*/
public Exception( String message, MixerRanking rank )
{
super( message, MIXER_PROBLEM );
mixerRanking = rank;
}
}
}