package paulscode.sound; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import javax.sound.sampled.AudioFormat; /** * The Library class is the class from which all library types are extended. * It provides generic methods for interfacing with the audio libraries * supported by the SoundSystem. Specific libraries should extend this class * and override the necessary methods. For consistant naming conventions, each * sub-class should have the name prefix "Library". * * This class may also be used as the "No Sound Library" (i.e. silent mode) if * no other audio libraries are supported by the host machine, or to mute all * sound. *<br><br> *<b><i> SoundSystem 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 Library { /** * Processes status messages, warnings, and error messages. */ private SoundSystemLogger logger; /** * Position and orientation of the listener. */ protected ListenerData listener; /** * Map containing sound file data for easy lookup by filename / identifier. */ protected HashMap<String, SoundBuffer> bufferMap = null; /** * Map containing all created sources for easy look-up by name. */ protected HashMap<String, Source> sourceMap; // (name, source data) pairs /** * Interface through which MIDI files can be played. */ private MidiChannel midiChannel; /** * Array containing maximum number of non-streaming audio channels. */ protected List<Channel> streamingChannels; /** * Array containing maximum number of non-streaming audio channels. */ protected List<Channel> normalChannels; /** * Source name last played on each streaming channel. */ private String[] streamingChannelSourceNames; /** * Source name last played on each non-streaming channel. */ private String[] normalChannelSourceNames; /** * Increments through the steaming channel list as new sources are played. */ private int nextStreamingChannel = 0; /** * Increments through the non-steaming channel list as new sources are played. */ private int nextNormalChannel = 0; /** * Handles processing for all streaming sources. */ protected StreamThread streamThread; /** * Whether or not the library requires reversal of audio data byte order. */ protected boolean reverseByteOrder = false; /** * Constructor: Instantiates the source map and listener information. NOTES: * The 'super()' method should be at the top of constructors for all extended * classes. The varriable 'libraryType' should be given a new value in the * constructors for all extended classes. */ public Library() throws SoundSystemException { // grab a handle to the message logger: logger = SoundSystemConfig.getLogger(); // instantiate the buffer map: bufferMap = new HashMap<String, SoundBuffer>(); // instantiate the source map: sourceMap = new HashMap<String, Source>(); listener = new ListenerData( 0.0f, 0.0f, 0.0f, // position 0.0f, 0.0f, -1.0f, // look-at direction 0.0f, 1.0f, 0.0f, // up direction 0.0f ); // angle streamingChannels = new LinkedList<Channel>(); normalChannels = new LinkedList<Channel>(); streamingChannelSourceNames = new String[ SoundSystemConfig.getNumberStreamingChannels() ]; normalChannelSourceNames = new String[ SoundSystemConfig.getNumberNormalChannels() ]; streamThread = new StreamThread(); streamThread.start(); } /* ########################################################################## */ /* BEGIN OVERRIDE METHODS */ /* */ /* The following methods should be overrided as required */ /* ########################################################################## */ /** * Stops all sources, shuts down sound library, and removes references to all * instantiated objects. */ public void cleanup() { streamThread.kill(); streamThread.interrupt(); // wait up to 5 seconds for stream thread to end: for( int i = 0; i < 50; i++ ) { if( !streamThread.alive() ) break; try { Thread.sleep(100); } catch(Exception e) {} } if( streamThread.alive() ) { errorMessage( "Stream thread did not die!" ); message( "Ignoring errors... continuing clean-up." ); } if( midiChannel != null ) { midiChannel.cleanup(); midiChannel = null; } Channel channel = null; if( streamingChannels != null ) { while( !streamingChannels.isEmpty() ) { channel = streamingChannels.remove(0); channel.close(); channel.cleanup(); channel = null; } streamingChannels.clear(); streamingChannels = null; } if( normalChannels != null ) { while( !normalChannels.isEmpty() ) { channel = normalChannels.remove(0); channel.close(); channel.cleanup(); channel = null; } normalChannels.clear(); normalChannels = null; } Set<String> keys = sourceMap.keySet(); Iterator<String> iter = keys.iterator(); String sourcename; Source source; // loop through and cleanup all the sources: while( iter.hasNext() ) { sourcename = iter.next(); source = sourceMap.get( sourcename ); if( source != null ) source.cleanup(); } sourceMap.clear(); sourceMap = null; listener = null; streamThread = null; } /** * Initializes the sound library. */ public void init() throws SoundSystemException { Channel channel = null; // create the streaming channels: for( int x = 0; x < SoundSystemConfig.getNumberStreamingChannels(); x++ ) { channel = createChannel( SoundSystemConfig.TYPE_STREAMING ); if( channel == null ) break; streamingChannels.add( channel ); } // create the non-streaming channels: for( int x = 0; x < SoundSystemConfig.getNumberNormalChannels(); x++ ) { channel = createChannel( SoundSystemConfig.TYPE_NORMAL ); if( channel == null ) break; normalChannels.add( channel ); } } /** * Checks if the no-sound library type is compatible. * @return True or false. */ public static boolean libraryCompatible() { return true; // the no-sound library is always compatible. } /** * 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. * @return The new channel. */ protected Channel createChannel( int type ) { return new Channel( type ); } /** * Pre-loads a sound into memory. * @param filenameURL Filename/URL of the sound file to load. * @return True if the sound loaded properly. */ public boolean loadSound( FilenameURL filenameURL ) { 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. */ public boolean loadSound( SoundBuffer buffer, String identifier ) { return true; } /** * Returns the filenames of all previously loaded sounds. * @return LinkedList of String filenames. */ public LinkedList<String> getAllLoadedFilenames() { LinkedList<String> filenames = new LinkedList<String>(); Set<String> keys = bufferMap.keySet(); Iterator<String> iter = keys.iterator(); // loop through and update the volume of all sources: while( iter.hasNext() ) { filenames.add( iter.next() ); } return filenames; } /** * Returns the sourcenames of all sources. * @return LinkedList of String sourcenames. */ public LinkedList<String> getAllSourcenames() { LinkedList<String> sourcenames = new LinkedList<String>(); Set<String> keys = sourceMap.keySet(); Iterator<String> iter = keys.iterator(); if( midiChannel != null ) sourcenames.add( midiChannel.getSourcename() ); // loop through and update the volume of all sources: while( iter.hasNext() ) { sourcenames.add( iter.next() ); } return sourcenames; } /** * Removes a pre-loaded sound from memory. This is a good method to use for * freeing up memory after a large sound file is no longer needed. NOTE: the * source will remain in memory after this method has been called, for as long * as the sound is attached to an existing source. * @param filename Filename/identifier of the sound file to unload. */ public void unloadSound( String filename ) { bufferMap.remove( filename ); } /** * Opens a direct line for streaming audio data. * @param audioFormat Format that the data will be in. * @param sourcename A unique identifier for this source. Two sources may not use the same sourcename. * @param posX X position for this source. * @param posY Y position for this source. * @param posZ 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". */ public void rawDataStream( AudioFormat audioFormat, boolean priority, String sourcename, float posX, float posY, float posZ, int attModel, float distOrRoll ) { sourceMap.put( sourcename, new Source( audioFormat, priority, sourcename, posX, posY, posZ, attModel, distOrRoll ) ); } /** * Creates a new source using the specified information. * @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 posX X position for this source. * @param posY Y position for this source. * @param posZ 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". */ public void newSource( boolean priority, boolean toStream, boolean toLoop, String sourcename, FilenameURL filenameURL, float posX, float posY, float posZ, int attModel, float distOrRoll ) { sourceMap.put( sourcename, new Source( priority, toStream, toLoop, sourcename, filenameURL, null, posX, posY, posZ, attModel, distOrRoll, false ) ); } /** * Creates and immediately plays a new source that will be removed when it * finishes playing. * @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 The filename/URL of the sound file to play at this source. * @param posX X position for this source. * @param posY Y position for this source. * @param posZ 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". */ public void quickPlay( boolean priority, boolean toStream, boolean toLoop, String sourcename, FilenameURL filenameURL, float posX, float posY, float posZ, int attModel, float distOrRoll, boolean tmp ) { sourceMap.put( sourcename, new Source( priority, toStream, toLoop, sourcename, filenameURL, null, posX, posY, posZ, attModel, distOrRoll, tmp ) ); } /** * * Defines whether or not the source should be removed after it finishes * playing. * @param sourcename The source's name. * @param temporary True or False. */ public void setTemporary( String sourcename, boolean temporary ) { Source mySource = sourceMap.get( sourcename ); if( mySource != null ) mySource.setTemporary( temporary ); } /** * Changes the specified source's position. * @param sourcename The source's name. * @param x Destination X coordinate. * @param y Destination Y coordinate. * @param z Destination Z coordinate. */ public void setPosition( String sourcename, float x, float y, float z ) { Source mySource = sourceMap.get( sourcename ); if( mySource != null ) mySource.setPosition( x, y, z ); } /** * Sets the specified source's priority factor. A priority source will not be * overriden if there are too many sources playing at once. * @param sourcename The source's name. * @param pri True or False. */ public void setPriority( String sourcename, boolean pri ) { Source mySource = sourceMap.get( sourcename ); if( mySource != null ) mySource.setPriority( pri ); } /** * Sets the specified source's looping parameter. If parameter lp is false, * the source will play once and stop. * @param sourcename The source's name. * @param lp True or False. */ public void setLooping( String sourcename, boolean lp ) { Source mySource = sourceMap.get( sourcename ); if( mySource != null ) mySource.setLooping( lp ); } /** * Sets the specified source's attenuation model. * @param sourcename The source's name. * @param model Attenuation model to use. */ public void setAttenuation( String sourcename, int model ) { Source mySource = sourceMap.get( sourcename ); if( mySource != null ) mySource.setAttenuation( model ); } /** * Sets the specified source's fade distance or rolloff factor. * @param sourcename The source's name. * @param dr Fade distance or rolloff factor. */ public void setDistOrRoll( String sourcename, float dr) { Source mySource = sourceMap.get( sourcename ); if( mySource != null ) mySource.setDistOrRoll( dr ); } /** * Sets the specified source's velocity, for use in Doppler effect. * @param sourcename The source's name. * @param x Velocity along world x-axis. * @param y Velocity along world y-axis. * @param z Velocity along world z-axis. */ public void setVelocity( String sourcename, float x, float y, float z ) { Source mySource = sourceMap.get( sourcename ); if( mySource != null ) mySource.setVelocity( x, y, z ); } /** * 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. */ public void setListenerVelocity( float x, float y, float z ) { listener.setVelocity( x, y, z ); } /** * Notifies the underlying library that the Doppler parameters have changed. */ public void dopplerChanged() {} /** * Returns the number of miliseconds since the specified source began playing. * @return miliseconds, or -1 if not playing or unable to calculate */ public float millisecondsPlayed( String sourcename ) { if( sourcename == null || sourcename.equals( "" ) ) { errorMessage( "Sourcename not specified in method " + "'millisecondsPlayed'" ); return -1; } if( midiSourcename( sourcename ) ) { errorMessage( "Unable to calculate milliseconds for MIDI source." ); return -1; } else { Source source = sourceMap.get( sourcename ); if( source == null ) { errorMessage( "Source '" + sourcename + "' not found in " + "method 'millisecondsPlayed'" ); } return source.millisecondsPlayed(); } } /** * Feeds raw data through the specified source. The source must be a * streaming source and it can not be already associated with a file or URL to * stream from. * @param sourcename Name of the streaming source to play from. * @param buffer Byte buffer containing raw audio data to stream. * @return Number of prior buffers that have been processed, or -1 if unable to queue the buffer (if the source was culled, for example). */ public int feedRawAudioData( String sourcename, byte[] buffer ) { if( sourcename == null || sourcename.equals( "" ) ) { errorMessage( "Sourcename not specified in method " + "'feedRawAudioData'" ); return -1; } if( midiSourcename( sourcename ) ) { errorMessage( "Raw audio data can not be fed to the " + "MIDI channel." ); return -1; } else { Source source = sourceMap.get( sourcename ); if( source == null ) { errorMessage( "Source '" + sourcename + "' not found in " + "method 'feedRawAudioData'" ); } return feedRawAudioData( source, buffer ); } } /** * Feeds raw data through the specified source. The source must be a * streaming source and it can not be already associated with a file or URL to * stream from. * @param source Streaming source to play from. * @param buffer Byte buffer containing raw audio data to stream. * @return Number of prior buffers that have been processed, or -1 if unable to queue the buffer (if the source was culled, for example). */ public int feedRawAudioData( Source source, byte[] buffer ) { if( source == null ) { errorMessage( "Source parameter null in method " + "'feedRawAudioData'" ); return -1; } if( !source.toStream ) { errorMessage( "Only a streaming source may be specified in " + "method 'feedRawAudioData'" ); return -1; } if( !source.rawDataStream ) { errorMessage( "Streaming source already associated with a " + "file or URL in method'feedRawAudioData'" ); return -1; } if( !source.playing() || source.channel == null ) { Channel channel; if( source.channel != null && ( source.channel.attachedSource == source ) ) channel = source.channel; else channel = getNextChannel( source ); int processed = source.feedRawAudioData( channel, buffer ); channel.attachedSource = source; streamThread.watch( source ); streamThread.interrupt(); return processed; } return( source.feedRawAudioData( source.channel, buffer ) ); } /** * Looks up the specified source and plays it. * @param sourcename Name of the source to play. */ public void play( String sourcename ) { if( sourcename == null || sourcename.equals( "" ) ) { errorMessage( "Sourcename not specified in method 'play'" ); return; } if( midiSourcename( sourcename ) ) { midiChannel.play(); } else { Source source = sourceMap.get( sourcename ); if( source == null ) { errorMessage( "Source '" + sourcename + "' not found in " + "method 'play'" ); } play( source ); } } /** * Plays the specified source. * @param source The source to play. */ public void play( Source source ) { if( source == null ) return; // raw data streams will automatically play when data is sent to them, // so no need to do anything here. if( source.rawDataStream ) return; if( !source.active() ) return; if( !source.playing() ) { Channel channel = getNextChannel( source ); if( source != null && channel != null ) { if( source.channel != null && source.channel.attachedSource != source ) source.channel = null; channel.attachedSource = source; source.play( channel ); if( source.toStream ) { streamThread.watch( source ); streamThread.interrupt(); } } } } /** * Stops the specified source. * @param sourcename The source's name. */ public void stop( String sourcename ) { if( sourcename == null || sourcename.equals( "" ) ) { errorMessage( "Sourcename not specified in method 'stop'" ); return; } if( midiSourcename( sourcename ) ) { midiChannel.stop(); } else { Source mySource = sourceMap.get( sourcename ); if( mySource != null ) mySource.stop(); } } /** * Pauses the specified source. * @param sourcename The source's name. */ public void pause( String sourcename ) { if( sourcename == null || sourcename.equals( "" ) ) { errorMessage( "Sourcename not specified in method 'stop'" ); return; } if( midiSourcename( sourcename ) ) { midiChannel.pause(); } else { Source mySource = sourceMap.get( sourcename ); if( mySource != null ) mySource.pause(); } } /** * Rewinds the specified source. * @param sourcename The source's name. */ public void rewind( String sourcename ) { if( midiSourcename( sourcename ) ) { midiChannel.rewind(); } else { Source mySource = sourceMap.get( sourcename ); if( mySource != null ) mySource.rewind(); } } /** * Clears all previously queued data from a stream. * @param sourcename The source's name. */ public void flush( String sourcename ) { if( midiSourcename( sourcename ) ) errorMessage( "You can not flush the MIDI channel" ); else { Source mySource = sourceMap.get( sourcename ); if( mySource != null ) mySource.flush(); } } /** * Culls the specified source. A culled source will not play until it has been * activated again. * @param sourcename The source's name. */ public void cull( String sourcename ) { Source mySource = sourceMap.get( sourcename ); if( mySource != null ) mySource.cull(); } /** * Activates a previously culled source, so it can be played again. * @param sourcename The source's name. */ public void activate( String sourcename ) { Source mySource = sourceMap.get( sourcename ); if( mySource != null ) { mySource.activate(); if( mySource.toPlay ) play( mySource ); } } /** * Sets the overall volume to the specified value, affecting all sources. * @param value New volume, float value ( 0.0f - 1.0f ). */ public void setMasterVolume( float value ) { SoundSystemConfig.setMasterGain( value ); if( midiChannel != null ) midiChannel.resetGain(); } /** * Manually sets the specified source's volume. * @param sourcename The source's name. * @param value A float value ( 0.0f - 1.0f ). */ public void setVolume( String sourcename, float value ) { if( midiSourcename( sourcename ) ) { midiChannel.setVolume( value ); } else { Source mySource = sourceMap.get( sourcename ); if( mySource != null ) { float newVolume = value; if( newVolume < 0.0f ) newVolume = 0.0f; else if( newVolume > 1.0f ) newVolume = 1.0f; mySource.sourceVolume = newVolume; mySource.positionChanged(); } } } /** * Returns the current volume of the specified source, or zero if the specified * source was not found. * @param sourcename Source to read volume from. * @return Float value representing the source volume (0.0f - 1.0f). */ public float getVolume( String sourcename ) { if( midiSourcename( sourcename ) ) { return midiChannel.getVolume(); } else { Source mySource = sourceMap.get( sourcename ); if( mySource != null ) return mySource.sourceVolume; else return 0.0f; } } /** * Manually sets the specified source's pitch. * @param sourcename The source's name. * @param value A float value ( 0.5f - 2.0f ). */ public void setPitch( String sourcename, float value ) { if( !midiSourcename( sourcename ) ) { Source mySource = sourceMap.get( sourcename ); if( mySource != null ) { float newPitch = value; if( newPitch < 0.5f ) newPitch = 0.5f; else if( newPitch > 2.0f ) newPitch = 2.0f; mySource.setPitch( newPitch ); mySource.positionChanged(); } } } /** * Returns the pitch of the specified source. * @param sourcename The source's name. * @return Float value representing the source pitch (0.5f - 2.0f). */ public float getPitch( String sourcename ) { if( !midiSourcename( sourcename ) ) { Source mySource = sourceMap.get( sourcename ); if( mySource != null ) return mySource.getPitch(); } return 1.0f; } /** * Moves the listener relative to the current position. * @param x X offset. * @param y Y offset. * @param z Z offset. */ public void moveListener( float x, float y, float z ) { setListenerPosition( listener.position.x + x, listener.position.y + y, listener.position.z + z ); } /** * Changes the listener's position. * @param x Destination X coordinate. * @param y Destination Y coordinate. * @param z Destination Z coordinate. */ public void setListenerPosition( float x, float y, float z ) { // update listener's position listener.setPosition( x, y, z ); 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(); } } /** * Turn the listener 'angle' radians counterclockwise around the y-Axis, * relative to the current angle. * @param angle Angle in radians. */ public void turnListener( float angle ) { setListenerAngle( listener.angle + angle ); 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(); } } /** * Changes the listeners orientation to the specified 'angle' radians * counterclockwise around the y-Axis. * @param angle Angle in radians. */ public void setListenerAngle( float angle ) { listener.setAngle( angle ); 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(); } } /** * Changes the listeners orientation using the specified coordinates. * @param lookX X element of the look-at direction. * @param lookY Y element of the look-at direction. * @param lookZ Z element of the look-at direction. * @param upX X element of the up direction. * @param upY Y element of the up direction. * @param upZ Z element of the up direction. */ public void setListenerOrientation( float lookX, float lookY, float lookZ, float upX, float upY, float upZ ) { listener.setOrientation( lookX, lookY, lookZ, upX, upY, upZ ); 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(); } } /** * Changes the listeners position and orientation using the specified listener * data. * @param l Listener data to use. */ public void setListenerData( ListenerData l ) { listener.setData( l ); } /** * Creates sources based on the source map provided. * @param srcMap Sources to copy. */ public void copySources( HashMap<String, Source> srcMap ) { if( srcMap == null ) return; Set<String> keys = srcMap.keySet(); Iterator<String> iter = keys.iterator(); String sourcename; Source srcData; // remove any existing sources before starting: sourceMap.clear(); // loop through and copy all the sources: while( iter.hasNext() ) { sourcename = iter.next(); srcData = srcMap.get( sourcename ); if( srcData != null ) { loadSound( srcData.filenameURL ); sourceMap.put( sourcename, new Source( srcData, null ) ); } } } /** * Stops and deletes the specified source. * @param sourcename The source's name. */ public void removeSource( String sourcename ) { Source mySource = sourceMap.get( sourcename ); if( mySource != null ) mySource.cleanup(); // end the source, free memory sourceMap.remove( sourcename ); } /** * Searches for and removes all temporary sources that have finished playing. */ public void removeTemporarySources() { Set<String> keys = sourceMap.keySet(); Iterator<String> iter = keys.iterator(); String sourcename; Source srcData; // loop through and cleanup all the sources: while( iter.hasNext() ) { sourcename = iter.next(); srcData = sourceMap.get( sourcename ); if( (srcData != null) && (srcData.temporary) && (!srcData.playing()) ) { srcData.cleanup(); // end the source, free memory iter.remove(); } } } /* ########################################################################## */ /* END OVERRIDE METHODS */ /* ########################################################################## */ /** * Returns a handle to the next available channel. If the specified * source is a normal source, a normal channel is returned, and if it is a * streaming source, then a streaming channel is returned. If all channels of * the required type are currently playing, then the next channel playing a * non-priority source is returned. If no channels are available (i.e. they * are all playing priority sources) then getNextChannel returns null. * @param source Source to find a channel for. * @return The next available channel, or null. */ private Channel getNextChannel( Source source ) { if( source == null ) return null; String sourcename = source.sourcename; if( sourcename == null ) return null; int x; int channels; int nextChannel; List<Channel> channelList; String[] sourceNames; String name; if( source.toStream ) { nextChannel = nextStreamingChannel; channelList = streamingChannels; sourceNames = streamingChannelSourceNames; } else { nextChannel = nextNormalChannel; channelList = normalChannels; sourceNames = normalChannelSourceNames; } channels = channelList.size(); // Check if this source is already on a channel: for( x = 0; x < channels; x++ ) { if( sourcename.equals( sourceNames[x] ) ) return channelList.get( x ); } int n = nextChannel; Source src; // Play on the next new or non-playing channel: for( x = 0; x < channels; x++ ) { name = sourceNames[n]; if( name == null ) src = null; else src = sourceMap.get( name ); if( src == null || !src.playing() ) { if( source.toStream ) { nextStreamingChannel = n + 1; if( nextStreamingChannel >= channels ) nextStreamingChannel = 0; } else { nextNormalChannel = n + 1; if( nextNormalChannel >= channels ) nextNormalChannel = 0; } sourceNames[n] = sourcename; return channelList.get( n ); } n++; if( n >= channels ) n = 0; } n = nextChannel; // Play on the next non-priority channel: for( x = 0; x < channels; x++ ) { name = sourceNames[n]; if( name == null ) src = null; else src = sourceMap.get( name ); if( src == null || !src.playing() || !src.priority ) { if( source.toStream ) { nextStreamingChannel = n + 1; if( nextStreamingChannel >= channels ) nextStreamingChannel = 0; } else { nextNormalChannel = n + 1; if( nextNormalChannel >= channels ) nextNormalChannel = 0; } sourceNames[n] = sourcename; return channelList.get( n ); } n++; if( n >= channels ) n = 0; } return null; } /** * Plays all sources whose 'toPlay' varriable is true but are not currently * playing (such as sources which were culled while looping and then * reactivated). */ public void replaySources() { Set<String> keys = sourceMap.keySet(); Iterator<String> iter = keys.iterator(); String sourcename; Source source; // loop through and cleanup all the sources: while( iter.hasNext() ) { sourcename = iter.next(); source = sourceMap.get( sourcename ); if( source != null ) { if( source.toPlay && !source.playing() ) { play( sourcename ); source.toPlay = false; } } } } /** * If the specified source is a streaming source or MIDI source, this method * queues up the next sound to play when the previous playback ends. This * method has no effect on non-streaming sources. * @param sourcename Source identifier. * @param filenameURL Filename/URL of the sound file to play next. */ public void queueSound( String sourcename, FilenameURL filenameURL ) { if( midiSourcename( sourcename ) ) { midiChannel.queueSound( filenameURL ); } else { Source mySource = sourceMap.get( sourcename ); if( mySource != null ) mySource.queueSound( filenameURL ); } } /** * Removes the first occurrence of the specified filename from the specified * source's list of sounds to play when previous playback ends. This method * has no effect on non-streaming sources. * @param sourcename Source identifier. * @param filename Filename/identifier of the sound file to remove from the queue. */ public void dequeueSound( String sourcename, String filename ) { if( midiSourcename( sourcename ) ) { midiChannel.dequeueSound( filename ); } else { Source mySource = sourceMap.get( sourcename ); if( mySource != null ) mySource.dequeueSound( filename ); } } /** * Fades out the volume of whatever the specified source is currently playing, * then begins playing the specified file at the source's previously * assigned volume level. If the filenameURL parameter is null or empty, the * specified source will simply fade out and stop. The miliseconds parameter * must be non-negative or zero. This method will remove anything that is * currently in the specified source's list of queued sounds that would have * played next when the current sound finished playing. This method may only * be used for streaming and MIDI sources. * @param sourcename Name of the source to fade out. * @param filenameURL Filename/URL of the sound file to play next, or null for none. * @param milis Number of miliseconds the fadeout should take. */ public void fadeOut( String sourcename, FilenameURL filenameURL, long milis ) { if( midiSourcename( sourcename ) ) { midiChannel.fadeOut( filenameURL, milis ); } else { Source mySource = sourceMap.get( sourcename ); if( mySource != null ) mySource.fadeOut( filenameURL, milis ); } } /** * Fades out the volume of whatever the specified source is currently playing, * then fades the volume back in playing the specified file. Final volume * after fade-in completes will be equal to the source's previously assigned * volume level. The filenameURL parameter may not be null or empty. The * miliseconds parameters must be non-negative or zero. This method will * remove anything that is currently in the specified source's list of queued * sounds that would have played next when the current sound finished playing. * This method may only be used for streaming and MIDI sources. * @param sourcename Name of the source to fade out/in. * @param filenameURL Filename/URL of the sound file to play next, or null for none. * @param milisOut Number of miliseconds the fadeout should take. * @param milisIn Number of miliseconds the fadein should take. */ public void fadeOutIn( String sourcename, FilenameURL filenameURL, long milisOut, long milisIn ) { if( midiSourcename( sourcename ) ) { midiChannel.fadeOutIn( filenameURL, milisOut, milisIn ); } else { Source mySource = sourceMap.get( sourcename ); if( mySource != null ) mySource.fadeOutIn( filenameURL, milisOut, milisIn ); } } /** * Makes sure the current volume levels of streaming sources and MIDI are * correct. This method is designed to help reduce the "jerky" fading behavior * that happens when using some library and codec pluggins (such as * LibraryJavaSound and CodecJOrbis). This method has no effect on normal * "non-streaming" sources. It would normally be called somewhere in the main * "game loop". IMPORTANT: To optimize frame-rates, do not call this method * for every frame. It is better to just call this method at some acceptable * "granularity" (play around with different granularities to find what sounds * acceptable for a particular situation). */ public void checkFadeVolumes() { if( midiChannel != null ) midiChannel.resetGain(); Channel c; Source s; for( int x = 0; x < streamingChannels.size(); x++ ) { c = streamingChannels.get( x ); if( c != null ) { s = c.attachedSource; if( s != null ) s.checkFadeOut(); } } c = null; s = null; } /** * Loads the specified MIDI file, and saves the source information about it. * @param toLoop Midi file should loop or play once. * @param sourcename Source identifier. * @param filenameURL Filename/URL of the MIDI file to load. */ public void loadMidi( boolean toLoop, String sourcename, FilenameURL filenameURL ) { if( filenameURL == null ) { errorMessage( "Filename/URL not specified in method 'loadMidi'." ); return; } if( !filenameURL.getFilename().matches( SoundSystemConfig.EXTENSION_MIDI ) ) { errorMessage( "Filename/identifier doesn't end in '.mid' or" + "'.midi' in method loadMidi." ); return; } if( midiChannel == null ) { midiChannel = new MidiChannel( toLoop, sourcename, filenameURL ); } else { midiChannel.switchSource( toLoop, sourcename, filenameURL ); } } /** * Unloads the current Midi file. */ public void unloadMidi() { if( midiChannel != null ) midiChannel.cleanup(); midiChannel = null; } /** * Checks if the sourcename matches the midi source. * @param sourcename Source identifier. * @return True if sourcename and midi sourcename match. */ public boolean midiSourcename( String sourcename ) { if( midiChannel == null || sourcename == null ) return false; if( midiChannel.getSourcename() == null || sourcename.equals( "" ) ) return false; if( sourcename.equals( midiChannel.getSourcename() ) ) return true; return false; } /** * * Returns the Source object identified by the specified name. * @param sourcename The source's name. * @return The source, or null if not found. */ public Source getSource( String sourcename ) { return sourceMap.get( sourcename ); } /** * * Returns a handle to the MIDI channel, or null if one does not exist. * @return The MIDI channel. */ public MidiChannel getMidiChannel() { return midiChannel; } /** * * Specifies the MIDI channel to use. * @param c New MIDI channel. */ public void setMidiChannel( MidiChannel c ) { if( midiChannel != null && midiChannel != c ) midiChannel.cleanup(); midiChannel = c; } /** * Tells all the sources that the listener has moved. */ public void listenerMoved() { Set<String> keys = sourceMap.keySet(); Iterator<String> iter = keys.iterator(); String sourcename; Source srcData; // loop through and copy all the sources: while( iter.hasNext() ) { sourcename = iter.next(); srcData = sourceMap.get( sourcename ); if( srcData != null ) { srcData.listenerMoved(); } } } /** * Returns the sources map. * @return Map of all sources. */ public HashMap<String, Source> getSources() { return sourceMap; } /** * Returns information about the listener. * @return A ListenerData object. */ public ListenerData getListenerData() { return listener; } /** * Indicates whether or not this library requires some codecs to reverse-order * the audio data they generate. * @return True if audio data should be reverse-ordered. */ public boolean reverseByteOrder() { return reverseByteOrder; } /** * Returns the short title of this library type. * @return A short title. */ public static String getTitle() { return "No Sound"; } /** * Returns a longer description of this library type. * @return A longer description. */ public static String getDescription() { return "Silent Mode"; } /** * Returns the name of the class. * @return "Library" + library title. */ public String getClassName() { return "Library"; } /** * Prints a message. * @param message Message to print. */ protected void message( String message ) { logger.message( message, 0 ); } /** * Prints an important message. * @param message Message to print. */ protected void importantMessage( String message ) { logger.importantMessage( message, 0 ); } /** * Prints the specified message if error is true. * @param error True or False. * @param message Message to print if error is true. * @return True if error is true. */ protected boolean errorCheck( boolean error, String message ) { return logger.errorCheck( error, getClassName(), message, 0 ); } /** * Prints an error message. * @param message Message to print. */ protected void errorMessage( String message ) { logger.errorMessage( getClassName(), message, 0 ); } /** * Prints an exception's error message followed by the stack trace. * @param e Exception containing the information to print. */ protected void printStackTrace( Exception e ) { logger.printStackTrace( e, 1 ); } }