Path: blob/main/src/lwjgl/java/paulscode/sound/Library.java
8644 views
package paulscode.sound;12import java.util.HashMap;3import java.util.Iterator;4import java.util.LinkedList;5import java.util.List;6import java.util.Set;7import javax.sound.sampled.AudioFormat;89/**10* The Library class is the class from which all library types are extended.11* It provides generic methods for interfacing with the audio libraries12* supported by the SoundSystem. Specific libraries should extend this class13* and override the necessary methods. For consistant naming conventions, each14* sub-class should have the name prefix "Library".15*16* This class may also be used as the "No Sound Library" (i.e. silent mode) if17* no other audio libraries are supported by the host machine, or to mute all18* sound.19*<br><br>20*<b><i> SoundSystem License:</b></i><br><b><br>21* You are free to use this library for any purpose, commercial or otherwise.22* You may modify this library or source code, and distribute it any way you23* like, provided the following conditions are met:24*<br>25* 1) You may not falsely claim to be the author of this library or any26* unmodified portion of it.27*<br>28* 2) You may not copyright this library or a modified version of it and then29* sue me for copyright infringement.30*<br>31* 3) If you modify the source code, you must clearly document the changes32* made before redistributing the modified source code, so other users know33* it is not the original code.34*<br>35* 4) You are not required to give me credit for this library in any derived36* work, but if you do, you must also mention my website:37* http://www.paulscode.com38*<br>39* 5) I the author will not be responsible for any damages (physical,40* financial, or otherwise) caused by the use if this library or any part41* of it.42*<br>43* 6) I the author do not guarantee, warrant, or make any representations,44* either expressed or implied, regarding the use of this library or any45* part of it.46* <br><br>47* Author: Paul Lamb48* <br>49* http://www.paulscode.com50* </b>51*/52public class Library53{54/**55* Processes status messages, warnings, and error messages.56*/57private SoundSystemLogger logger;5859/**60* Position and orientation of the listener.61*/62protected ListenerData listener;6364/**65* Map containing sound file data for easy lookup by filename / identifier.66*/67protected HashMap<String, SoundBuffer> bufferMap = null;6869/**70* Map containing all created sources for easy look-up by name.71*/72protected HashMap<String, Source> sourceMap; // (name, source data) pairs7374/**75* Interface through which MIDI files can be played.76*/77private MidiChannel midiChannel;7879/**80* Array containing maximum number of non-streaming audio channels.81*/82protected List<Channel> streamingChannels;8384/**85* Array containing maximum number of non-streaming audio channels.86*/87protected List<Channel> normalChannels;8889/**90* Source name last played on each streaming channel.91*/92private String[] streamingChannelSourceNames;9394/**95* Source name last played on each non-streaming channel.96*/97private String[] normalChannelSourceNames;9899/**100* Increments through the steaming channel list as new sources are played.101*/102private int nextStreamingChannel = 0;103104/**105* Increments through the non-steaming channel list as new sources are played.106*/107private int nextNormalChannel = 0;108109/**110* Handles processing for all streaming sources.111*/112protected StreamThread streamThread;113114/**115* Whether or not the library requires reversal of audio data byte order.116*/117protected boolean reverseByteOrder = false;118119/**120* Constructor: Instantiates the source map and listener information. NOTES:121* The 'super()' method should be at the top of constructors for all extended122* classes. The varriable 'libraryType' should be given a new value in the123* constructors for all extended classes.124*/125public Library() throws SoundSystemException126{127// grab a handle to the message logger:128logger = SoundSystemConfig.getLogger();129130// instantiate the buffer map:131bufferMap = new HashMap<String, SoundBuffer>();132133// instantiate the source map:134sourceMap = new HashMap<String, Source>();135136listener = new ListenerData( 0.0f, 0.0f, 0.0f, // position1370.0f, 0.0f, -1.0f, // look-at direction1380.0f, 1.0f, 0.0f, // up direction1390.0f ); // angle140141streamingChannels = new LinkedList<Channel>();142normalChannels = new LinkedList<Channel>();143streamingChannelSourceNames = new String[144SoundSystemConfig.getNumberStreamingChannels() ];145normalChannelSourceNames = new String[146SoundSystemConfig.getNumberNormalChannels() ];147148streamThread = new StreamThread();149streamThread.start();150}151152153/* ########################################################################## */154/* BEGIN OVERRIDE METHODS */155/* */156/* The following methods should be overrided as required */157/* ########################################################################## */158159/**160* Stops all sources, shuts down sound library, and removes references to all161* instantiated objects.162*/163public void cleanup()164{165streamThread.kill();166streamThread.interrupt();167168// wait up to 5 seconds for stream thread to end:169for( int i = 0; i < 50; i++ )170{171if( !streamThread.alive() )172break;173try174{175Thread.sleep(100);176}177catch(Exception e)178{}179}180181if( streamThread.alive() )182{183errorMessage( "Stream thread did not die!" );184message( "Ignoring errors... continuing clean-up." );185}186187if( midiChannel != null )188{189midiChannel.cleanup();190midiChannel = null;191}192193Channel channel = null;194if( streamingChannels != null )195{196while( !streamingChannels.isEmpty() )197{198channel = streamingChannels.remove(0);199channel.close();200channel.cleanup();201channel = null;202}203streamingChannels.clear();204streamingChannels = null;205}206if( normalChannels != null )207{208while( !normalChannels.isEmpty() )209{210channel = normalChannels.remove(0);211channel.close();212channel.cleanup();213channel = null;214}215normalChannels.clear();216normalChannels = null;217}218219Set<String> keys = sourceMap.keySet();220Iterator<String> iter = keys.iterator();221String sourcename;222Source source;223224// loop through and cleanup all the sources:225while( iter.hasNext() )226{227sourcename = iter.next();228source = sourceMap.get( sourcename );229if( source != null )230source.cleanup();231}232sourceMap.clear();233sourceMap = null;234235listener = null;236streamThread = null;237}238239/**240* Initializes the sound library.241*/242public void init() throws SoundSystemException243{244Channel channel = null;245246// create the streaming channels:247for( int x = 0; x < SoundSystemConfig.getNumberStreamingChannels(); x++ )248{249channel = createChannel( SoundSystemConfig.TYPE_STREAMING );250if( channel == null )251break;252streamingChannels.add( channel );253}254// create the non-streaming channels:255for( int x = 0; x < SoundSystemConfig.getNumberNormalChannels(); x++ )256{257channel = createChannel( SoundSystemConfig.TYPE_NORMAL );258if( channel == null )259break;260normalChannels.add( channel );261}262}263264/**265* Checks if the no-sound library type is compatible.266* @return True or false.267*/268public static boolean libraryCompatible()269{270return true; // the no-sound library is always compatible.271}272273/**274* Creates a new channel of the specified type (normal or streaming). Possible275* values for channel type can be found in the276* {@link paulscode.sound.SoundSystemConfig SoundSystemConfig} class.277* @param type Type of channel.278* @return The new channel.279*/280protected Channel createChannel( int type )281{282return new Channel( type );283}284285/**286* Pre-loads a sound into memory.287* @param filenameURL Filename/URL of the sound file to load.288* @return True if the sound loaded properly.289*/290public boolean loadSound( FilenameURL filenameURL )291{292return true;293}294295/**296* Saves the specified sample data, under the specified identifier. This297* identifier can be later used in place of 'filename' parameters to reference298* the sample data.299* @param buffer the sample data and audio format to save.300* @param identifier What to call the sample.301* @return True if there weren't any problems.302*/303public boolean loadSound( SoundBuffer buffer, String identifier )304{305return true;306}307308/**309* Returns the filenames of all previously loaded sounds.310* @return LinkedList of String filenames.311*/312public LinkedList<String> getAllLoadedFilenames()313{314LinkedList<String> filenames = new LinkedList<String>();315Set<String> keys = bufferMap.keySet();316Iterator<String> iter = keys.iterator();317318// loop through and update the volume of all sources:319while( iter.hasNext() )320{321filenames.add( iter.next() );322}323324return filenames;325}326327/**328* Returns the sourcenames of all sources.329* @return LinkedList of String sourcenames.330*/331public LinkedList<String> getAllSourcenames()332{333LinkedList<String> sourcenames = new LinkedList<String>();334Set<String> keys = sourceMap.keySet();335Iterator<String> iter = keys.iterator();336337if( midiChannel != null )338sourcenames.add( midiChannel.getSourcename() );339340// loop through and update the volume of all sources:341while( iter.hasNext() )342{343sourcenames.add( iter.next() );344}345346return sourcenames;347}348349/**350* Removes a pre-loaded sound from memory. This is a good method to use for351* freeing up memory after a large sound file is no longer needed. NOTE: the352* source will remain in memory after this method has been called, for as long353* as the sound is attached to an existing source.354* @param filename Filename/identifier of the sound file to unload.355*/356public void unloadSound( String filename )357{358bufferMap.remove( filename );359}360361/**362* Opens a direct line for streaming audio data.363* @param audioFormat Format that the data will be in.364* @param sourcename A unique identifier for this source. Two sources may not use the same sourcename.365* @param posX X position for this source.366* @param posY Y position for this source.367* @param posZ Z position for this source.368* @param attModel Attenuation model to use.369* @param distOrRoll Either the fading distance or rolloff factor, depending on the value of "attmodel".370*/371public void rawDataStream( AudioFormat audioFormat, boolean priority,372String sourcename, float posX, float posY,373float posZ, int attModel, float distOrRoll )374{375sourceMap.put( sourcename,376new Source( audioFormat, priority, sourcename, posX,377posY, posZ, attModel, distOrRoll ) );378}379380/**381* Creates a new source using the specified information.382* @param priority Setting this to true will prevent other sounds from overriding this one.383* @param toStream Setting this to true will load the sound in pieces rather than all at once.384* @param toLoop Should this source loop, or play only once.385* @param sourcename A unique identifier for this source. Two sources may not use the same sourcename.386* @param filenameURL Filename/URL of the sound file to play at this source.387* @param posX X position for this source.388* @param posY Y position for this source.389* @param posZ Z position for this source.390* @param attModel Attenuation model to use.391* @param distOrRoll Either the fading distance or rolloff factor, depending on the value of "attmodel".392*/393public void newSource( boolean priority, boolean toStream, boolean toLoop,394String sourcename, FilenameURL filenameURL,395float posX, float posY, float posZ, int attModel,396float distOrRoll )397{398sourceMap.put( sourcename,399new Source( priority, toStream, toLoop, sourcename,400filenameURL, null, posX, posY, posZ,401attModel, distOrRoll, false ) );402}403404/**405* Creates and immediately plays a new source that will be removed when it406* finishes playing.407* @param priority Setting this to true will prevent other sounds from overriding this one.408* @param toStream Setting this to true will load the sound in pieces rather than all at once.409* @param toLoop Should this source loop, or play only once.410* @param sourcename A unique identifier for this source. Two sources may not use the same sourcename.411* @param filenameURL The filename/URL of the sound file to play at this source.412* @param posX X position for this source.413* @param posY Y position for this source.414* @param posZ Z position for this source.415* @param attModel Attenuation model to use.416* @param distOrRoll Either the fading distance or rolloff factor, depending on the value of "attmodel".417*/418public void quickPlay( boolean priority, boolean toStream, boolean toLoop,419String sourcename, FilenameURL filenameURL,420float posX, float posY, float posZ, int attModel,421float distOrRoll, boolean tmp )422{423sourceMap.put( sourcename,424new Source( priority, toStream, toLoop, sourcename,425filenameURL, null, posX, posY, posZ,426attModel, distOrRoll, tmp ) );427}428429/**430*431* Defines whether or not the source should be removed after it finishes432* playing.433* @param sourcename The source's name.434* @param temporary True or False.435*/436public void setTemporary( String sourcename, boolean temporary )437{438Source mySource = sourceMap.get( sourcename );439if( mySource != null )440mySource.setTemporary( temporary );441}442443/**444* Changes the specified source's position.445* @param sourcename The source's name.446* @param x Destination X coordinate.447* @param y Destination Y coordinate.448* @param z Destination Z coordinate.449*/450public void setPosition( String sourcename, float x, float y, float z )451{452Source mySource = sourceMap.get( sourcename );453if( mySource != null )454mySource.setPosition( x, y, z );455}456457/**458* Sets the specified source's priority factor. A priority source will not be459* overriden if there are too many sources playing at once.460* @param sourcename The source's name.461* @param pri True or False.462*/463public void setPriority( String sourcename, boolean pri )464{465Source mySource = sourceMap.get( sourcename );466if( mySource != null )467mySource.setPriority( pri );468}469470/**471* Sets the specified source's looping parameter. If parameter lp is false,472* the source will play once and stop.473* @param sourcename The source's name.474* @param lp True or False.475*/476public void setLooping( String sourcename, boolean lp )477{478Source mySource = sourceMap.get( sourcename );479if( mySource != null )480mySource.setLooping( lp );481}482483/**484* Sets the specified source's attenuation model.485* @param sourcename The source's name.486* @param model Attenuation model to use.487*/488public void setAttenuation( String sourcename, int model )489{490Source mySource = sourceMap.get( sourcename );491if( mySource != null )492mySource.setAttenuation( model );493}494495/**496* Sets the specified source's fade distance or rolloff factor.497* @param sourcename The source's name.498* @param dr Fade distance or rolloff factor.499*/500public void setDistOrRoll( String sourcename, float dr)501{502Source mySource = sourceMap.get( sourcename );503if( mySource != null )504mySource.setDistOrRoll( dr );505}506507/**508* Sets the specified source's velocity, for use in Doppler effect.509* @param sourcename The source's name.510* @param x Velocity along world x-axis.511* @param y Velocity along world y-axis.512* @param z Velocity along world z-axis.513*/514public void setVelocity( String sourcename, float x, float y, float z )515{516Source mySource = sourceMap.get( sourcename );517if( mySource != null )518mySource.setVelocity( x, y, z );519}520521/**522* Sets the listener's velocity, for use in Doppler effect.523* @param x Velocity along world x-axis.524* @param y Velocity along world y-axis.525* @param z Velocity along world z-axis.526*/527public void setListenerVelocity( float x, float y, float z )528{529listener.setVelocity( x, y, z );530}531532/**533* Notifies the underlying library that the Doppler parameters have changed.534*/535public void dopplerChanged()536{}537538/**539* Returns the number of miliseconds since the specified source began playing.540* @return miliseconds, or -1 if not playing or unable to calculate541*/542public float millisecondsPlayed( String sourcename )543{544if( sourcename == null || sourcename.equals( "" ) )545{546errorMessage( "Sourcename not specified in method " +547"'millisecondsPlayed'" );548return -1;549}550551if( midiSourcename( sourcename ) )552{553errorMessage( "Unable to calculate milliseconds for MIDI source." );554return -1;555}556else557{558Source source = sourceMap.get( sourcename );559if( source == null )560{561errorMessage( "Source '" + sourcename + "' not found in " +562"method 'millisecondsPlayed'" );563}564return source.millisecondsPlayed();565}566}567/**568* Feeds raw data through the specified source. The source must be a569* streaming source and it can not be already associated with a file or URL to570* stream from.571* @param sourcename Name of the streaming source to play from.572* @param buffer Byte buffer containing raw audio data to stream.573* @return Number of prior buffers that have been processed, or -1 if unable to queue the buffer (if the source was culled, for example).574*/575public int feedRawAudioData( String sourcename, byte[] buffer )576{577if( sourcename == null || sourcename.equals( "" ) )578{579errorMessage( "Sourcename not specified in method " +580"'feedRawAudioData'" );581return -1;582}583584if( midiSourcename( sourcename ) )585{586errorMessage( "Raw audio data can not be fed to the " +587"MIDI channel." );588return -1;589}590else591{592Source source = sourceMap.get( sourcename );593if( source == null )594{595errorMessage( "Source '" + sourcename + "' not found in " +596"method 'feedRawAudioData'" );597}598return feedRawAudioData( source, buffer );599}600}601602/**603* Feeds raw data through the specified source. The source must be a604* streaming source and it can not be already associated with a file or URL to605* stream from.606* @param source Streaming source to play from.607* @param buffer Byte buffer containing raw audio data to stream.608* @return Number of prior buffers that have been processed, or -1 if unable to queue the buffer (if the source was culled, for example).609*/610public int feedRawAudioData( Source source, byte[] buffer )611{612if( source == null )613{614errorMessage( "Source parameter null in method " +615"'feedRawAudioData'" );616return -1;617}618if( !source.toStream )619{620errorMessage( "Only a streaming source may be specified in " +621"method 'feedRawAudioData'" );622return -1;623}624if( !source.rawDataStream )625{626errorMessage( "Streaming source already associated with a " +627"file or URL in method'feedRawAudioData'" );628return -1;629}630631if( !source.playing() || source.channel == null )632{633Channel channel;634if( source.channel != null && ( source.channel.attachedSource ==635source ) )636channel = source.channel;637else638channel = getNextChannel( source );639640int processed = source.feedRawAudioData( channel, buffer );641channel.attachedSource = source;642streamThread.watch( source );643streamThread.interrupt();644return processed;645}646647return( source.feedRawAudioData( source.channel, buffer ) );648}649650/**651* Looks up the specified source and plays it.652* @param sourcename Name of the source to play.653*/654public void play( String sourcename )655{656if( sourcename == null || sourcename.equals( "" ) )657{658errorMessage( "Sourcename not specified in method 'play'" );659return;660}661662if( midiSourcename( sourcename ) )663{664midiChannel.play();665}666else667{668Source source = sourceMap.get( sourcename );669if( source == null )670{671errorMessage( "Source '" + sourcename + "' not found in " +672"method 'play'" );673}674play( source );675}676}677678/**679* Plays the specified source.680* @param source The source to play.681*/682public void play( Source source )683{684if( source == null )685return;686687// raw data streams will automatically play when data is sent to them,688// so no need to do anything here.689if( source.rawDataStream )690return;691692if( !source.active() )693return;694695if( !source.playing() )696{697Channel channel = getNextChannel( source );698699if( source != null && channel != null )700{701if( source.channel != null &&702source.channel.attachedSource != source )703source.channel = null;704channel.attachedSource = source;705source.play( channel );706if( source.toStream )707{708streamThread.watch( source );709streamThread.interrupt();710}711}712}713}714715/**716* Stops the specified source.717* @param sourcename The source's name.718*/719public void stop( String sourcename )720{721if( sourcename == null || sourcename.equals( "" ) )722{723errorMessage( "Sourcename not specified in method 'stop'" );724return;725}726if( midiSourcename( sourcename ) )727{728midiChannel.stop();729}730else731{732Source mySource = sourceMap.get( sourcename );733if( mySource != null )734mySource.stop();735}736}737738/**739* Pauses the specified source.740* @param sourcename The source's name.741*/742public void pause( String sourcename )743{744if( sourcename == null || sourcename.equals( "" ) )745{746errorMessage( "Sourcename not specified in method 'stop'" );747return;748}749if( midiSourcename( sourcename ) )750{751midiChannel.pause();752}753else754{755Source mySource = sourceMap.get( sourcename );756if( mySource != null )757mySource.pause();758}759}760761/**762* Rewinds the specified source.763* @param sourcename The source's name.764*/765public void rewind( String sourcename )766{767if( midiSourcename( sourcename ) )768{769midiChannel.rewind();770}771else772{773Source mySource = sourceMap.get( sourcename );774if( mySource != null )775mySource.rewind();776}777}778779/**780* Clears all previously queued data from a stream.781* @param sourcename The source's name.782*/783public void flush( String sourcename )784{785if( midiSourcename( sourcename ) )786errorMessage( "You can not flush the MIDI channel" );787else788{789Source mySource = sourceMap.get( sourcename );790if( mySource != null )791mySource.flush();792}793}794795/**796* Culls the specified source. A culled source will not play until it has been797* activated again.798* @param sourcename The source's name.799*/800public void cull( String sourcename )801{802Source mySource = sourceMap.get( sourcename );803if( mySource != null )804mySource.cull();805}806807/**808* Activates a previously culled source, so it can be played again.809* @param sourcename The source's name.810*/811public void activate( String sourcename )812{813Source mySource = sourceMap.get( sourcename );814if( mySource != null )815{816mySource.activate();817if( mySource.toPlay )818play( mySource );819}820}821822/**823* Sets the overall volume to the specified value, affecting all sources.824* @param value New volume, float value ( 0.0f - 1.0f ).825*/826public void setMasterVolume( float value )827{828SoundSystemConfig.setMasterGain( value );829if( midiChannel != null )830midiChannel.resetGain();831}832833/**834* Manually sets the specified source's volume.835* @param sourcename The source's name.836* @param value A float value ( 0.0f - 1.0f ).837*/838public void setVolume( String sourcename, float value )839{840if( midiSourcename( sourcename ) )841{842midiChannel.setVolume( value );843}844else845{846Source mySource = sourceMap.get( sourcename );847if( mySource != null )848{849float newVolume = value;850if( newVolume < 0.0f )851newVolume = 0.0f;852else if( newVolume > 1.0f )853newVolume = 1.0f;854855mySource.sourceVolume = newVolume;856mySource.positionChanged();857}858}859}860861/**862* Returns the current volume of the specified source, or zero if the specified863* source was not found.864* @param sourcename Source to read volume from.865* @return Float value representing the source volume (0.0f - 1.0f).866*/867public float getVolume( String sourcename )868{869if( midiSourcename( sourcename ) )870{871return midiChannel.getVolume();872}873else874{875Source mySource = sourceMap.get( sourcename );876if( mySource != null )877return mySource.sourceVolume;878else879return 0.0f;880}881}882883/**884* Manually sets the specified source's pitch.885* @param sourcename The source's name.886* @param value A float value ( 0.5f - 2.0f ).887*/888public void setPitch( String sourcename, float value )889{890if( !midiSourcename( sourcename ) )891{892Source mySource = sourceMap.get( sourcename );893if( mySource != null )894{895float newPitch = value;896if( newPitch < 0.5f )897newPitch = 0.5f;898else if( newPitch > 2.0f )899newPitch = 2.0f;900901mySource.setPitch( newPitch );902mySource.positionChanged();903}904}905}906907/**908* Returns the pitch of the specified source.909* @param sourcename The source's name.910* @return Float value representing the source pitch (0.5f - 2.0f).911*/912public float getPitch( String sourcename )913{914if( !midiSourcename( sourcename ) )915{916Source mySource = sourceMap.get( sourcename );917if( mySource != null )918return mySource.getPitch();919}920return 1.0f;921}922923/**924* Moves the listener relative to the current position.925* @param x X offset.926* @param y Y offset.927* @param z Z offset.928*/929public void moveListener( float x, float y, float z )930{931setListenerPosition( listener.position.x + x, listener.position.y + y,932listener.position.z + z );933}934935/**936* Changes the listener's position.937* @param x Destination X coordinate.938* @param y Destination Y coordinate.939* @param z Destination Z coordinate.940*/941public void setListenerPosition( float x, float y, float z )942{943// update listener's position944listener.setPosition( x, y, z );945946Set<String> keys = sourceMap.keySet();947Iterator<String> iter = keys.iterator();948String sourcename;949Source source;950951// loop through and update the volume of all sources:952while( iter.hasNext() )953{954sourcename = iter.next();955source = sourceMap.get( sourcename );956if( source != null )957source.positionChanged();958}959}960961/**962* Turn the listener 'angle' radians counterclockwise around the y-Axis,963* relative to the current angle.964* @param angle Angle in radians.965*/966public void turnListener( float angle )967{968setListenerAngle( listener.angle + angle );969970Set<String> keys = sourceMap.keySet();971Iterator<String> iter = keys.iterator();972String sourcename;973Source source;974975// loop through and update the volume of all sources:976while( iter.hasNext() )977{978sourcename = iter.next();979source = sourceMap.get( sourcename );980if( source != null )981source.positionChanged();982}983}984985/**986* Changes the listeners orientation to the specified 'angle' radians987* counterclockwise around the y-Axis.988* @param angle Angle in radians.989*/990public void setListenerAngle( float angle )991{992listener.setAngle( angle );993994Set<String> keys = sourceMap.keySet();995Iterator<String> iter = keys.iterator();996String sourcename;997Source source;998999// loop through and update the volume of all sources:1000while( iter.hasNext() )1001{1002sourcename = iter.next();1003source = sourceMap.get( sourcename );1004if( source != null )1005source.positionChanged();1006}1007}10081009/**1010* Changes the listeners orientation using the specified coordinates.1011* @param lookX X element of the look-at direction.1012* @param lookY Y element of the look-at direction.1013* @param lookZ Z element of the look-at direction.1014* @param upX X element of the up direction.1015* @param upY Y element of the up direction.1016* @param upZ Z element of the up direction.1017*/1018public void setListenerOrientation( float lookX, float lookY, float lookZ,1019float upX, float upY, float upZ )1020{1021listener.setOrientation( lookX, lookY, lookZ, upX, upY, upZ );10221023Set<String> keys = sourceMap.keySet();1024Iterator<String> iter = keys.iterator();1025String sourcename;1026Source source;10271028// loop through and update the volume of all sources:1029while( iter.hasNext() )1030{1031sourcename = iter.next();1032source = sourceMap.get( sourcename );1033if( source != null )1034source.positionChanged();1035}1036}10371038/**1039* Changes the listeners position and orientation using the specified listener1040* data.1041* @param l Listener data to use.1042*/1043public void setListenerData( ListenerData l )1044{1045listener.setData( l );1046}10471048/**1049* Creates sources based on the source map provided.1050* @param srcMap Sources to copy.1051*/1052public void copySources( HashMap<String, Source> srcMap )1053{1054if( srcMap == null )1055return;1056Set<String> keys = srcMap.keySet();1057Iterator<String> iter = keys.iterator();1058String sourcename;1059Source srcData;10601061// remove any existing sources before starting:1062sourceMap.clear();10631064// loop through and copy all the sources:1065while( iter.hasNext() )1066{1067sourcename = iter.next();1068srcData = srcMap.get( sourcename );1069if( srcData != null )1070{1071loadSound( srcData.filenameURL );1072sourceMap.put( sourcename, new Source( srcData, null ) );1073}1074}1075}10761077/**1078* Stops and deletes the specified source.1079* @param sourcename The source's name.1080*/1081public void removeSource( String sourcename )1082{1083Source mySource = sourceMap.get( sourcename );1084if( mySource != null ) {1085// if this is a streaming source just mark it removed - https://github.com/MinecraftForge/MinecraftForge/pull/47651086if ( mySource.toStream )1087mySource.removed = true;1088else1089mySource.cleanup(); // end the source, free memory1090}1091sourceMap.remove( sourcename );1092}10931094/**1095* Searches for and removes all temporary sources that have finished playing.1096*/1097public void removeTemporarySources()1098{1099Set<String> keys = sourceMap.keySet();1100Iterator<String> iter = keys.iterator();1101String sourcename;1102Source srcData;11031104// loop through and cleanup all the sources:1105while( iter.hasNext() )1106{1107sourcename = iter.next();1108srcData = sourceMap.get( sourcename );1109if( (srcData != null) && (srcData.temporary)1110&& (!srcData.playing()) )1111{1112srcData.cleanup(); // end the source, free memory1113iter.remove();1114}1115}1116}11171118/* ########################################################################## */1119/* END OVERRIDE METHODS */1120/* ########################################################################## */11211122/**1123* Returns a handle to the next available channel. If the specified1124* source is a normal source, a normal channel is returned, and if it is a1125* streaming source, then a streaming channel is returned. If all channels of1126* the required type are currently playing, then the next channel playing a1127* non-priority source is returned. If no channels are available (i.e. they1128* are all playing priority sources) then getNextChannel returns null.1129* @param source Source to find a channel for.1130* @return The next available channel, or null.1131*/1132private Channel getNextChannel( Source source )1133{1134if( source == null )1135return null;11361137String sourcename = source.sourcename;1138if( sourcename == null )1139return null;11401141int x;1142int channels;1143int nextChannel;1144List<Channel> channelList;1145String[] sourceNames;1146String name;11471148if( source.toStream )1149{1150nextChannel = nextStreamingChannel;1151channelList = streamingChannels;1152sourceNames = streamingChannelSourceNames;1153}1154else1155{1156nextChannel = nextNormalChannel;1157channelList = normalChannels;1158sourceNames = normalChannelSourceNames;1159}11601161channels = channelList.size();11621163// Check if this source is already on a channel:1164for( x = 0; x < channels; x++ )1165{1166if( sourcename.equals( sourceNames[x] ) )1167return channelList.get( x );1168}11691170int n = nextChannel;1171Source src;1172// Play on the next new or non-playing channel:1173for( x = 0; x < channels; x++ )1174{1175name = sourceNames[n];1176if( name == null )1177src = null;1178else1179src = sourceMap.get( name );11801181if( src == null || !src.playing() )1182{1183if( source.toStream )1184{1185nextStreamingChannel = n + 1;1186if( nextStreamingChannel >= channels )1187nextStreamingChannel = 0;1188}1189else1190{1191nextNormalChannel = n + 1;1192if( nextNormalChannel >= channels )1193nextNormalChannel = 0;1194}1195sourceNames[n] = sourcename;1196return channelList.get( n );1197}1198n++;1199if( n >= channels )1200n = 0;1201}12021203n = nextChannel;1204// Play on the next non-priority channel:1205for( x = 0; x < channels; x++ )1206{1207name = sourceNames[n];1208if( name == null )1209src = null;1210else1211src = sourceMap.get( name );12121213if( src == null || !src.playing() || !src.priority )1214{1215if( source.toStream )1216{1217nextStreamingChannel = n + 1;1218if( nextStreamingChannel >= channels )1219nextStreamingChannel = 0;1220}1221else1222{1223nextNormalChannel = n + 1;1224if( nextNormalChannel >= channels )1225nextNormalChannel = 0;1226}1227sourceNames[n] = sourcename;1228return channelList.get( n );1229}1230n++;1231if( n >= channels )1232n = 0;1233}12341235return null;1236}12371238/**1239* Plays all sources whose 'toPlay' varriable is true but are not currently1240* playing (such as sources which were culled while looping and then1241* reactivated).1242*/1243public void replaySources()1244{1245Set<String> keys = sourceMap.keySet();1246Iterator<String> iter = keys.iterator();1247String sourcename;1248Source source;12491250// loop through and cleanup all the sources:1251while( iter.hasNext() )1252{1253sourcename = iter.next();1254source = sourceMap.get( sourcename );1255if( source != null )1256{1257if( source.toPlay && !source.playing() )1258{1259play( sourcename );1260source.toPlay = false;1261}1262}1263}1264}12651266/**1267* If the specified source is a streaming source or MIDI source, this method1268* queues up the next sound to play when the previous playback ends. This1269* method has no effect on non-streaming sources.1270* @param sourcename Source identifier.1271* @param filenameURL Filename/URL of the sound file to play next.1272*/1273public void queueSound( String sourcename, FilenameURL filenameURL )1274{1275if( midiSourcename( sourcename ) )1276{1277midiChannel.queueSound( filenameURL );1278}1279else1280{1281Source mySource = sourceMap.get( sourcename );1282if( mySource != null )1283mySource.queueSound( filenameURL );1284}1285}12861287/**1288* Removes the first occurrence of the specified filename from the specified1289* source's list of sounds to play when previous playback ends. This method1290* has no effect on non-streaming sources.1291* @param sourcename Source identifier.1292* @param filename Filename/identifier of the sound file to remove from the queue.1293*/1294public void dequeueSound( String sourcename, String filename )1295{1296if( midiSourcename( sourcename ) )1297{1298midiChannel.dequeueSound( filename );1299}1300else1301{1302Source mySource = sourceMap.get( sourcename );1303if( mySource != null )1304mySource.dequeueSound( filename );1305}1306}13071308/**1309* Fades out the volume of whatever the specified source is currently playing,1310* then begins playing the specified file at the source's previously1311* assigned volume level. If the filenameURL parameter is null or empty, the1312* specified source will simply fade out and stop. The miliseconds parameter1313* must be non-negative or zero. This method will remove anything that is1314* currently in the specified source's list of queued sounds that would have1315* played next when the current sound finished playing. This method may only1316* be used for streaming and MIDI sources.1317* @param sourcename Name of the source to fade out.1318* @param filenameURL Filename/URL of the sound file to play next, or null for none.1319* @param milis Number of miliseconds the fadeout should take.1320*/1321public void fadeOut( String sourcename, FilenameURL filenameURL,1322long milis )1323{1324if( midiSourcename( sourcename ) )1325{1326midiChannel.fadeOut( filenameURL, milis );1327}1328else1329{1330Source mySource = sourceMap.get( sourcename );1331if( mySource != null )1332mySource.fadeOut( filenameURL, milis );1333}1334}13351336/**1337* Fades out the volume of whatever the specified source is currently playing,1338* then fades the volume back in playing the specified file. Final volume1339* after fade-in completes will be equal to the source's previously assigned1340* volume level. The filenameURL parameter may not be null or empty. The1341* miliseconds parameters must be non-negative or zero. This method will1342* remove anything that is currently in the specified source's list of queued1343* sounds that would have played next when the current sound finished playing.1344* This method may only be used for streaming and MIDI sources.1345* @param sourcename Name of the source to fade out/in.1346* @param filenameURL Filename/URL of the sound file to play next, or null for none.1347* @param milisOut Number of miliseconds the fadeout should take.1348* @param milisIn Number of miliseconds the fadein should take.1349*/1350public void fadeOutIn( String sourcename, FilenameURL filenameURL,1351long milisOut, long milisIn )1352{1353if( midiSourcename( sourcename ) )1354{1355midiChannel.fadeOutIn( filenameURL, milisOut, milisIn );1356}1357else1358{1359Source mySource = sourceMap.get( sourcename );1360if( mySource != null )1361mySource.fadeOutIn( filenameURL, milisOut, milisIn );1362}1363}13641365/**1366* Makes sure the current volume levels of streaming sources and MIDI are1367* correct. This method is designed to help reduce the "jerky" fading behavior1368* that happens when using some library and codec pluggins (such as1369* LibraryJavaSound and CodecJOrbis). This method has no effect on normal1370* "non-streaming" sources. It would normally be called somewhere in the main1371* "game loop". IMPORTANT: To optimize frame-rates, do not call this method1372* for every frame. It is better to just call this method at some acceptable1373* "granularity" (play around with different granularities to find what sounds1374* acceptable for a particular situation).1375*/1376public void checkFadeVolumes()1377{1378if( midiChannel != null )1379midiChannel.resetGain();1380Channel c;1381Source s;1382for( int x = 0; x < streamingChannels.size(); x++ )1383{1384c = streamingChannels.get( x );1385if( c != null )1386{1387s = c.attachedSource;1388if( s != null )1389s.checkFadeOut();1390}1391}1392c = null;1393s = null;1394}13951396/**1397* Loads the specified MIDI file, and saves the source information about it.1398* @param toLoop Midi file should loop or play once.1399* @param sourcename Source identifier.1400* @param filenameURL Filename/URL of the MIDI file to load.1401*/1402public void loadMidi( boolean toLoop, String sourcename,1403FilenameURL filenameURL )1404{1405if( filenameURL == null )1406{1407errorMessage( "Filename/URL not specified in method 'loadMidi'." );1408return;1409}14101411if( !filenameURL.getFilename().matches(1412SoundSystemConfig.EXTENSION_MIDI ) )1413{1414errorMessage( "Filename/identifier doesn't end in '.mid' or" +1415"'.midi' in method loadMidi." );1416return;1417}14181419if( midiChannel == null )1420{1421midiChannel = new MidiChannel( toLoop, sourcename, filenameURL );1422}1423else1424{1425midiChannel.switchSource( toLoop, sourcename, filenameURL );1426}1427}14281429/**1430* Unloads the current Midi file.1431*/1432public void unloadMidi()1433{1434if( midiChannel != null )1435midiChannel.cleanup();1436midiChannel = null;1437}14381439/**1440* Checks if the sourcename matches the midi source.1441* @param sourcename Source identifier.1442* @return True if sourcename and midi sourcename match.1443*/1444public boolean midiSourcename( String sourcename )1445{1446if( midiChannel == null || sourcename == null )1447return false;14481449if( midiChannel.getSourcename() == null || sourcename.equals( "" ) )1450return false;14511452if( sourcename.equals( midiChannel.getSourcename() ) )1453return true;14541455return false;1456}14571458/**1459*1460* Returns the Source object identified by the specified name.1461* @param sourcename The source's name.1462* @return The source, or null if not found.1463*/1464public Source getSource( String sourcename )1465{1466return sourceMap.get( sourcename );1467}14681469/**1470*1471* Returns a handle to the MIDI channel, or null if one does not exist.1472* @return The MIDI channel.1473*/1474public MidiChannel getMidiChannel()1475{1476return midiChannel;1477}14781479/**1480*1481* Specifies the MIDI channel to use.1482* @param c New MIDI channel.1483*/1484public void setMidiChannel( MidiChannel c )1485{1486if( midiChannel != null && midiChannel != c )1487midiChannel.cleanup();14881489midiChannel = c;1490}14911492/**1493* Tells all the sources that the listener has moved.1494*/1495public void listenerMoved()1496{1497Set<String> keys = sourceMap.keySet();1498Iterator<String> iter = keys.iterator();1499String sourcename;1500Source srcData;15011502// loop through and copy all the sources:1503while( iter.hasNext() )1504{1505sourcename = iter.next();1506srcData = sourceMap.get( sourcename );1507if( srcData != null )1508{1509srcData.listenerMoved();1510}1511}1512}15131514/**1515* Returns the sources map.1516* @return Map of all sources.1517*/1518public HashMap<String, Source> getSources()1519{1520return sourceMap;1521}15221523/**1524* Returns information about the listener.1525* @return A ListenerData object.1526*/1527public ListenerData getListenerData()1528{1529return listener;1530}15311532/**1533* Indicates whether or not this library requires some codecs to reverse-order1534* the audio data they generate.1535* @return True if audio data should be reverse-ordered.1536*/1537public boolean reverseByteOrder()1538{1539return reverseByteOrder;1540}1541/**1542* Returns the short title of this library type.1543* @return A short title.1544*/1545public static String getTitle()1546{1547return "No Sound";1548}15491550/**1551* Returns a longer description of this library type.1552* @return A longer description.1553*/1554public static String getDescription()1555{1556return "Silent Mode";1557}15581559/**1560* Returns the name of the class.1561* @return "Library" + library title.1562*/1563public String getClassName()1564{1565return "Library";1566}15671568/**1569* Prints a message.1570* @param message Message to print.1571*/1572protected void message( String message )1573{1574logger.message( message, 0 );1575}15761577/**1578* Prints an important message.1579* @param message Message to print.1580*/1581protected void importantMessage( String message )1582{1583logger.importantMessage( message, 0 );1584}15851586/**1587* Prints the specified message if error is true.1588* @param error True or False.1589* @param message Message to print if error is true.1590* @return True if error is true.1591*/1592protected boolean errorCheck( boolean error, String message )1593{1594return logger.errorCheck( error, getClassName(), message, 0 );1595}15961597/**1598* Prints an error message.1599* @param message Message to print.1600*/1601protected void errorMessage( String message )1602{1603logger.errorMessage( getClassName(), message, 0 );1604}16051606/**1607* Prints an exception's error message followed by the stack trace.1608* @param e Exception containing the information to print.1609*/1610protected void printStackTrace( Exception e )1611{1612logger.printStackTrace( e, 1 );1613}1614}161516161617