Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lDEVinux
GitHub Repository: lDEVinux/eaglercraft
Path: blob/main/src/lwjgl/java/paulscode/sound/Library.java
8644 views
1
package paulscode.sound;
2
3
import java.util.HashMap;
4
import java.util.Iterator;
5
import java.util.LinkedList;
6
import java.util.List;
7
import java.util.Set;
8
import javax.sound.sampled.AudioFormat;
9
10
/**
11
* The Library class is the class from which all library types are extended.
12
* It provides generic methods for interfacing with the audio libraries
13
* supported by the SoundSystem. Specific libraries should extend this class
14
* and override the necessary methods. For consistant naming conventions, each
15
* sub-class should have the name prefix "Library".
16
*
17
* This class may also be used as the "No Sound Library" (i.e. silent mode) if
18
* no other audio libraries are supported by the host machine, or to mute all
19
* sound.
20
*<br><br>
21
*<b><i> SoundSystem License:</b></i><br><b><br>
22
* You are free to use this library for any purpose, commercial or otherwise.
23
* You may modify this library or source code, and distribute it any way you
24
* like, provided the following conditions are met:
25
*<br>
26
* 1) You may not falsely claim to be the author of this library or any
27
* unmodified portion of it.
28
*<br>
29
* 2) You may not copyright this library or a modified version of it and then
30
* sue me for copyright infringement.
31
*<br>
32
* 3) If you modify the source code, you must clearly document the changes
33
* made before redistributing the modified source code, so other users know
34
* it is not the original code.
35
*<br>
36
* 4) You are not required to give me credit for this library in any derived
37
* work, but if you do, you must also mention my website:
38
* http://www.paulscode.com
39
*<br>
40
* 5) I the author will not be responsible for any damages (physical,
41
* financial, or otherwise) caused by the use if this library or any part
42
* of it.
43
*<br>
44
* 6) I the author do not guarantee, warrant, or make any representations,
45
* either expressed or implied, regarding the use of this library or any
46
* part of it.
47
* <br><br>
48
* Author: Paul Lamb
49
* <br>
50
* http://www.paulscode.com
51
* </b>
52
*/
53
public class Library
54
{
55
/**
56
* Processes status messages, warnings, and error messages.
57
*/
58
private SoundSystemLogger logger;
59
60
/**
61
* Position and orientation of the listener.
62
*/
63
protected ListenerData listener;
64
65
/**
66
* Map containing sound file data for easy lookup by filename / identifier.
67
*/
68
protected HashMap<String, SoundBuffer> bufferMap = null;
69
70
/**
71
* Map containing all created sources for easy look-up by name.
72
*/
73
protected HashMap<String, Source> sourceMap; // (name, source data) pairs
74
75
/**
76
* Interface through which MIDI files can be played.
77
*/
78
private MidiChannel midiChannel;
79
80
/**
81
* Array containing maximum number of non-streaming audio channels.
82
*/
83
protected List<Channel> streamingChannels;
84
85
/**
86
* Array containing maximum number of non-streaming audio channels.
87
*/
88
protected List<Channel> normalChannels;
89
90
/**
91
* Source name last played on each streaming channel.
92
*/
93
private String[] streamingChannelSourceNames;
94
95
/**
96
* Source name last played on each non-streaming channel.
97
*/
98
private String[] normalChannelSourceNames;
99
100
/**
101
* Increments through the steaming channel list as new sources are played.
102
*/
103
private int nextStreamingChannel = 0;
104
105
/**
106
* Increments through the non-steaming channel list as new sources are played.
107
*/
108
private int nextNormalChannel = 0;
109
110
/**
111
* Handles processing for all streaming sources.
112
*/
113
protected StreamThread streamThread;
114
115
/**
116
* Whether or not the library requires reversal of audio data byte order.
117
*/
118
protected boolean reverseByteOrder = false;
119
120
/**
121
* Constructor: Instantiates the source map and listener information. NOTES:
122
* The 'super()' method should be at the top of constructors for all extended
123
* classes. The varriable 'libraryType' should be given a new value in the
124
* constructors for all extended classes.
125
*/
126
public Library() throws SoundSystemException
127
{
128
// grab a handle to the message logger:
129
logger = SoundSystemConfig.getLogger();
130
131
// instantiate the buffer map:
132
bufferMap = new HashMap<String, SoundBuffer>();
133
134
// instantiate the source map:
135
sourceMap = new HashMap<String, Source>();
136
137
listener = new ListenerData( 0.0f, 0.0f, 0.0f, // position
138
0.0f, 0.0f, -1.0f, // look-at direction
139
0.0f, 1.0f, 0.0f, // up direction
140
0.0f ); // angle
141
142
streamingChannels = new LinkedList<Channel>();
143
normalChannels = new LinkedList<Channel>();
144
streamingChannelSourceNames = new String[
145
SoundSystemConfig.getNumberStreamingChannels() ];
146
normalChannelSourceNames = new String[
147
SoundSystemConfig.getNumberNormalChannels() ];
148
149
streamThread = new StreamThread();
150
streamThread.start();
151
}
152
153
154
/* ########################################################################## */
155
/* BEGIN OVERRIDE METHODS */
156
/* */
157
/* The following methods should be overrided as required */
158
/* ########################################################################## */
159
160
/**
161
* Stops all sources, shuts down sound library, and removes references to all
162
* instantiated objects.
163
*/
164
public void cleanup()
165
{
166
streamThread.kill();
167
streamThread.interrupt();
168
169
// wait up to 5 seconds for stream thread to end:
170
for( int i = 0; i < 50; i++ )
171
{
172
if( !streamThread.alive() )
173
break;
174
try
175
{
176
Thread.sleep(100);
177
}
178
catch(Exception e)
179
{}
180
}
181
182
if( streamThread.alive() )
183
{
184
errorMessage( "Stream thread did not die!" );
185
message( "Ignoring errors... continuing clean-up." );
186
}
187
188
if( midiChannel != null )
189
{
190
midiChannel.cleanup();
191
midiChannel = null;
192
}
193
194
Channel channel = null;
195
if( streamingChannels != null )
196
{
197
while( !streamingChannels.isEmpty() )
198
{
199
channel = streamingChannels.remove(0);
200
channel.close();
201
channel.cleanup();
202
channel = null;
203
}
204
streamingChannels.clear();
205
streamingChannels = null;
206
}
207
if( normalChannels != null )
208
{
209
while( !normalChannels.isEmpty() )
210
{
211
channel = normalChannels.remove(0);
212
channel.close();
213
channel.cleanup();
214
channel = null;
215
}
216
normalChannels.clear();
217
normalChannels = null;
218
}
219
220
Set<String> keys = sourceMap.keySet();
221
Iterator<String> iter = keys.iterator();
222
String sourcename;
223
Source source;
224
225
// loop through and cleanup all the sources:
226
while( iter.hasNext() )
227
{
228
sourcename = iter.next();
229
source = sourceMap.get( sourcename );
230
if( source != null )
231
source.cleanup();
232
}
233
sourceMap.clear();
234
sourceMap = null;
235
236
listener = null;
237
streamThread = null;
238
}
239
240
/**
241
* Initializes the sound library.
242
*/
243
public void init() throws SoundSystemException
244
{
245
Channel channel = null;
246
247
// create the streaming channels:
248
for( int x = 0; x < SoundSystemConfig.getNumberStreamingChannels(); x++ )
249
{
250
channel = createChannel( SoundSystemConfig.TYPE_STREAMING );
251
if( channel == null )
252
break;
253
streamingChannels.add( channel );
254
}
255
// create the non-streaming channels:
256
for( int x = 0; x < SoundSystemConfig.getNumberNormalChannels(); x++ )
257
{
258
channel = createChannel( SoundSystemConfig.TYPE_NORMAL );
259
if( channel == null )
260
break;
261
normalChannels.add( channel );
262
}
263
}
264
265
/**
266
* Checks if the no-sound library type is compatible.
267
* @return True or false.
268
*/
269
public static boolean libraryCompatible()
270
{
271
return true; // the no-sound library is always compatible.
272
}
273
274
/**
275
* Creates a new channel of the specified type (normal or streaming). Possible
276
* values for channel type can be found in the
277
* {@link paulscode.sound.SoundSystemConfig SoundSystemConfig} class.
278
* @param type Type of channel.
279
* @return The new channel.
280
*/
281
protected Channel createChannel( int type )
282
{
283
return new Channel( type );
284
}
285
286
/**
287
* Pre-loads a sound into memory.
288
* @param filenameURL Filename/URL of the sound file to load.
289
* @return True if the sound loaded properly.
290
*/
291
public boolean loadSound( FilenameURL filenameURL )
292
{
293
return true;
294
}
295
296
/**
297
* Saves the specified sample data, under the specified identifier. This
298
* identifier can be later used in place of 'filename' parameters to reference
299
* the sample data.
300
* @param buffer the sample data and audio format to save.
301
* @param identifier What to call the sample.
302
* @return True if there weren't any problems.
303
*/
304
public boolean loadSound( SoundBuffer buffer, String identifier )
305
{
306
return true;
307
}
308
309
/**
310
* Returns the filenames of all previously loaded sounds.
311
* @return LinkedList of String filenames.
312
*/
313
public LinkedList<String> getAllLoadedFilenames()
314
{
315
LinkedList<String> filenames = new LinkedList<String>();
316
Set<String> keys = bufferMap.keySet();
317
Iterator<String> iter = keys.iterator();
318
319
// loop through and update the volume of all sources:
320
while( iter.hasNext() )
321
{
322
filenames.add( iter.next() );
323
}
324
325
return filenames;
326
}
327
328
/**
329
* Returns the sourcenames of all sources.
330
* @return LinkedList of String sourcenames.
331
*/
332
public LinkedList<String> getAllSourcenames()
333
{
334
LinkedList<String> sourcenames = new LinkedList<String>();
335
Set<String> keys = sourceMap.keySet();
336
Iterator<String> iter = keys.iterator();
337
338
if( midiChannel != null )
339
sourcenames.add( midiChannel.getSourcename() );
340
341
// loop through and update the volume of all sources:
342
while( iter.hasNext() )
343
{
344
sourcenames.add( iter.next() );
345
}
346
347
return sourcenames;
348
}
349
350
/**
351
* Removes a pre-loaded sound from memory. This is a good method to use for
352
* freeing up memory after a large sound file is no longer needed. NOTE: the
353
* source will remain in memory after this method has been called, for as long
354
* as the sound is attached to an existing source.
355
* @param filename Filename/identifier of the sound file to unload.
356
*/
357
public void unloadSound( String filename )
358
{
359
bufferMap.remove( filename );
360
}
361
362
/**
363
* Opens a direct line for streaming audio data.
364
* @param audioFormat Format that the data will be in.
365
* @param sourcename A unique identifier for this source. Two sources may not use the same sourcename.
366
* @param posX X position for this source.
367
* @param posY Y position for this source.
368
* @param posZ Z position for this source.
369
* @param attModel Attenuation model to use.
370
* @param distOrRoll Either the fading distance or rolloff factor, depending on the value of "attmodel".
371
*/
372
public void rawDataStream( AudioFormat audioFormat, boolean priority,
373
String sourcename, float posX, float posY,
374
float posZ, int attModel, float distOrRoll )
375
{
376
sourceMap.put( sourcename,
377
new Source( audioFormat, priority, sourcename, posX,
378
posY, posZ, attModel, distOrRoll ) );
379
}
380
381
/**
382
* Creates a new source using the specified information.
383
* @param priority Setting this to true will prevent other sounds from overriding this one.
384
* @param toStream Setting this to true will load the sound in pieces rather than all at once.
385
* @param toLoop Should this source loop, or play only once.
386
* @param sourcename A unique identifier for this source. Two sources may not use the same sourcename.
387
* @param filenameURL Filename/URL of the sound file to play at this source.
388
* @param posX X position for this source.
389
* @param posY Y position for this source.
390
* @param posZ Z position for this source.
391
* @param attModel Attenuation model to use.
392
* @param distOrRoll Either the fading distance or rolloff factor, depending on the value of "attmodel".
393
*/
394
public void newSource( boolean priority, boolean toStream, boolean toLoop,
395
String sourcename, FilenameURL filenameURL,
396
float posX, float posY, float posZ, int attModel,
397
float distOrRoll )
398
{
399
sourceMap.put( sourcename,
400
new Source( priority, toStream, toLoop, sourcename,
401
filenameURL, null, posX, posY, posZ,
402
attModel, distOrRoll, false ) );
403
}
404
405
/**
406
* Creates and immediately plays a new source that will be removed when it
407
* finishes playing.
408
* @param priority Setting this to true will prevent other sounds from overriding this one.
409
* @param toStream Setting this to true will load the sound in pieces rather than all at once.
410
* @param toLoop Should this source loop, or play only once.
411
* @param sourcename A unique identifier for this source. Two sources may not use the same sourcename.
412
* @param filenameURL The filename/URL of the sound file to play at this source.
413
* @param posX X position for this source.
414
* @param posY Y position for this source.
415
* @param posZ Z position for this source.
416
* @param attModel Attenuation model to use.
417
* @param distOrRoll Either the fading distance or rolloff factor, depending on the value of "attmodel".
418
*/
419
public void quickPlay( boolean priority, boolean toStream, boolean toLoop,
420
String sourcename, FilenameURL filenameURL,
421
float posX, float posY, float posZ, int attModel,
422
float distOrRoll, boolean tmp )
423
{
424
sourceMap.put( sourcename,
425
new Source( priority, toStream, toLoop, sourcename,
426
filenameURL, null, posX, posY, posZ,
427
attModel, distOrRoll, tmp ) );
428
}
429
430
/**
431
*
432
* Defines whether or not the source should be removed after it finishes
433
* playing.
434
* @param sourcename The source's name.
435
* @param temporary True or False.
436
*/
437
public void setTemporary( String sourcename, boolean temporary )
438
{
439
Source mySource = sourceMap.get( sourcename );
440
if( mySource != null )
441
mySource.setTemporary( temporary );
442
}
443
444
/**
445
* Changes the specified source's position.
446
* @param sourcename The source's name.
447
* @param x Destination X coordinate.
448
* @param y Destination Y coordinate.
449
* @param z Destination Z coordinate.
450
*/
451
public void setPosition( String sourcename, float x, float y, float z )
452
{
453
Source mySource = sourceMap.get( sourcename );
454
if( mySource != null )
455
mySource.setPosition( x, y, z );
456
}
457
458
/**
459
* Sets the specified source's priority factor. A priority source will not be
460
* overriden if there are too many sources playing at once.
461
* @param sourcename The source's name.
462
* @param pri True or False.
463
*/
464
public void setPriority( String sourcename, boolean pri )
465
{
466
Source mySource = sourceMap.get( sourcename );
467
if( mySource != null )
468
mySource.setPriority( pri );
469
}
470
471
/**
472
* Sets the specified source's looping parameter. If parameter lp is false,
473
* the source will play once and stop.
474
* @param sourcename The source's name.
475
* @param lp True or False.
476
*/
477
public void setLooping( String sourcename, boolean lp )
478
{
479
Source mySource = sourceMap.get( sourcename );
480
if( mySource != null )
481
mySource.setLooping( lp );
482
}
483
484
/**
485
* Sets the specified source's attenuation model.
486
* @param sourcename The source's name.
487
* @param model Attenuation model to use.
488
*/
489
public void setAttenuation( String sourcename, int model )
490
{
491
Source mySource = sourceMap.get( sourcename );
492
if( mySource != null )
493
mySource.setAttenuation( model );
494
}
495
496
/**
497
* Sets the specified source's fade distance or rolloff factor.
498
* @param sourcename The source's name.
499
* @param dr Fade distance or rolloff factor.
500
*/
501
public void setDistOrRoll( String sourcename, float dr)
502
{
503
Source mySource = sourceMap.get( sourcename );
504
if( mySource != null )
505
mySource.setDistOrRoll( dr );
506
}
507
508
/**
509
* Sets the specified source's velocity, for use in Doppler effect.
510
* @param sourcename The source's name.
511
* @param x Velocity along world x-axis.
512
* @param y Velocity along world y-axis.
513
* @param z Velocity along world z-axis.
514
*/
515
public void setVelocity( String sourcename, float x, float y, float z )
516
{
517
Source mySource = sourceMap.get( sourcename );
518
if( mySource != null )
519
mySource.setVelocity( x, y, z );
520
}
521
522
/**
523
* Sets the listener's velocity, for use in Doppler effect.
524
* @param x Velocity along world x-axis.
525
* @param y Velocity along world y-axis.
526
* @param z Velocity along world z-axis.
527
*/
528
public void setListenerVelocity( float x, float y, float z )
529
{
530
listener.setVelocity( x, y, z );
531
}
532
533
/**
534
* Notifies the underlying library that the Doppler parameters have changed.
535
*/
536
public void dopplerChanged()
537
{}
538
539
/**
540
* Returns the number of miliseconds since the specified source began playing.
541
* @return miliseconds, or -1 if not playing or unable to calculate
542
*/
543
public float millisecondsPlayed( String sourcename )
544
{
545
if( sourcename == null || sourcename.equals( "" ) )
546
{
547
errorMessage( "Sourcename not specified in method " +
548
"'millisecondsPlayed'" );
549
return -1;
550
}
551
552
if( midiSourcename( sourcename ) )
553
{
554
errorMessage( "Unable to calculate milliseconds for MIDI source." );
555
return -1;
556
}
557
else
558
{
559
Source source = sourceMap.get( sourcename );
560
if( source == null )
561
{
562
errorMessage( "Source '" + sourcename + "' not found in " +
563
"method 'millisecondsPlayed'" );
564
}
565
return source.millisecondsPlayed();
566
}
567
}
568
/**
569
* Feeds raw data through the specified source. The source must be a
570
* streaming source and it can not be already associated with a file or URL to
571
* stream from.
572
* @param sourcename Name of the streaming source to play from.
573
* @param buffer Byte buffer containing raw audio data to stream.
574
* @return Number of prior buffers that have been processed, or -1 if unable to queue the buffer (if the source was culled, for example).
575
*/
576
public int feedRawAudioData( String sourcename, byte[] buffer )
577
{
578
if( sourcename == null || sourcename.equals( "" ) )
579
{
580
errorMessage( "Sourcename not specified in method " +
581
"'feedRawAudioData'" );
582
return -1;
583
}
584
585
if( midiSourcename( sourcename ) )
586
{
587
errorMessage( "Raw audio data can not be fed to the " +
588
"MIDI channel." );
589
return -1;
590
}
591
else
592
{
593
Source source = sourceMap.get( sourcename );
594
if( source == null )
595
{
596
errorMessage( "Source '" + sourcename + "' not found in " +
597
"method 'feedRawAudioData'" );
598
}
599
return feedRawAudioData( source, buffer );
600
}
601
}
602
603
/**
604
* Feeds raw data through the specified source. The source must be a
605
* streaming source and it can not be already associated with a file or URL to
606
* stream from.
607
* @param source Streaming source to play from.
608
* @param buffer Byte buffer containing raw audio data to stream.
609
* @return Number of prior buffers that have been processed, or -1 if unable to queue the buffer (if the source was culled, for example).
610
*/
611
public int feedRawAudioData( Source source, byte[] buffer )
612
{
613
if( source == null )
614
{
615
errorMessage( "Source parameter null in method " +
616
"'feedRawAudioData'" );
617
return -1;
618
}
619
if( !source.toStream )
620
{
621
errorMessage( "Only a streaming source may be specified in " +
622
"method 'feedRawAudioData'" );
623
return -1;
624
}
625
if( !source.rawDataStream )
626
{
627
errorMessage( "Streaming source already associated with a " +
628
"file or URL in method'feedRawAudioData'" );
629
return -1;
630
}
631
632
if( !source.playing() || source.channel == null )
633
{
634
Channel channel;
635
if( source.channel != null && ( source.channel.attachedSource ==
636
source ) )
637
channel = source.channel;
638
else
639
channel = getNextChannel( source );
640
641
int processed = source.feedRawAudioData( channel, buffer );
642
channel.attachedSource = source;
643
streamThread.watch( source );
644
streamThread.interrupt();
645
return processed;
646
}
647
648
return( source.feedRawAudioData( source.channel, buffer ) );
649
}
650
651
/**
652
* Looks up the specified source and plays it.
653
* @param sourcename Name of the source to play.
654
*/
655
public void play( String sourcename )
656
{
657
if( sourcename == null || sourcename.equals( "" ) )
658
{
659
errorMessage( "Sourcename not specified in method 'play'" );
660
return;
661
}
662
663
if( midiSourcename( sourcename ) )
664
{
665
midiChannel.play();
666
}
667
else
668
{
669
Source source = sourceMap.get( sourcename );
670
if( source == null )
671
{
672
errorMessage( "Source '" + sourcename + "' not found in " +
673
"method 'play'" );
674
}
675
play( source );
676
}
677
}
678
679
/**
680
* Plays the specified source.
681
* @param source The source to play.
682
*/
683
public void play( Source source )
684
{
685
if( source == null )
686
return;
687
688
// raw data streams will automatically play when data is sent to them,
689
// so no need to do anything here.
690
if( source.rawDataStream )
691
return;
692
693
if( !source.active() )
694
return;
695
696
if( !source.playing() )
697
{
698
Channel channel = getNextChannel( source );
699
700
if( source != null && channel != null )
701
{
702
if( source.channel != null &&
703
source.channel.attachedSource != source )
704
source.channel = null;
705
channel.attachedSource = source;
706
source.play( channel );
707
if( source.toStream )
708
{
709
streamThread.watch( source );
710
streamThread.interrupt();
711
}
712
}
713
}
714
}
715
716
/**
717
* Stops the specified source.
718
* @param sourcename The source's name.
719
*/
720
public void stop( String sourcename )
721
{
722
if( sourcename == null || sourcename.equals( "" ) )
723
{
724
errorMessage( "Sourcename not specified in method 'stop'" );
725
return;
726
}
727
if( midiSourcename( sourcename ) )
728
{
729
midiChannel.stop();
730
}
731
else
732
{
733
Source mySource = sourceMap.get( sourcename );
734
if( mySource != null )
735
mySource.stop();
736
}
737
}
738
739
/**
740
* Pauses the specified source.
741
* @param sourcename The source's name.
742
*/
743
public void pause( String sourcename )
744
{
745
if( sourcename == null || sourcename.equals( "" ) )
746
{
747
errorMessage( "Sourcename not specified in method 'stop'" );
748
return;
749
}
750
if( midiSourcename( sourcename ) )
751
{
752
midiChannel.pause();
753
}
754
else
755
{
756
Source mySource = sourceMap.get( sourcename );
757
if( mySource != null )
758
mySource.pause();
759
}
760
}
761
762
/**
763
* Rewinds the specified source.
764
* @param sourcename The source's name.
765
*/
766
public void rewind( String sourcename )
767
{
768
if( midiSourcename( sourcename ) )
769
{
770
midiChannel.rewind();
771
}
772
else
773
{
774
Source mySource = sourceMap.get( sourcename );
775
if( mySource != null )
776
mySource.rewind();
777
}
778
}
779
780
/**
781
* Clears all previously queued data from a stream.
782
* @param sourcename The source's name.
783
*/
784
public void flush( String sourcename )
785
{
786
if( midiSourcename( sourcename ) )
787
errorMessage( "You can not flush the MIDI channel" );
788
else
789
{
790
Source mySource = sourceMap.get( sourcename );
791
if( mySource != null )
792
mySource.flush();
793
}
794
}
795
796
/**
797
* Culls the specified source. A culled source will not play until it has been
798
* activated again.
799
* @param sourcename The source's name.
800
*/
801
public void cull( String sourcename )
802
{
803
Source mySource = sourceMap.get( sourcename );
804
if( mySource != null )
805
mySource.cull();
806
}
807
808
/**
809
* Activates a previously culled source, so it can be played again.
810
* @param sourcename The source's name.
811
*/
812
public void activate( String sourcename )
813
{
814
Source mySource = sourceMap.get( sourcename );
815
if( mySource != null )
816
{
817
mySource.activate();
818
if( mySource.toPlay )
819
play( mySource );
820
}
821
}
822
823
/**
824
* Sets the overall volume to the specified value, affecting all sources.
825
* @param value New volume, float value ( 0.0f - 1.0f ).
826
*/
827
public void setMasterVolume( float value )
828
{
829
SoundSystemConfig.setMasterGain( value );
830
if( midiChannel != null )
831
midiChannel.resetGain();
832
}
833
834
/**
835
* Manually sets the specified source's volume.
836
* @param sourcename The source's name.
837
* @param value A float value ( 0.0f - 1.0f ).
838
*/
839
public void setVolume( String sourcename, float value )
840
{
841
if( midiSourcename( sourcename ) )
842
{
843
midiChannel.setVolume( value );
844
}
845
else
846
{
847
Source mySource = sourceMap.get( sourcename );
848
if( mySource != null )
849
{
850
float newVolume = value;
851
if( newVolume < 0.0f )
852
newVolume = 0.0f;
853
else if( newVolume > 1.0f )
854
newVolume = 1.0f;
855
856
mySource.sourceVolume = newVolume;
857
mySource.positionChanged();
858
}
859
}
860
}
861
862
/**
863
* Returns the current volume of the specified source, or zero if the specified
864
* source was not found.
865
* @param sourcename Source to read volume from.
866
* @return Float value representing the source volume (0.0f - 1.0f).
867
*/
868
public float getVolume( String sourcename )
869
{
870
if( midiSourcename( sourcename ) )
871
{
872
return midiChannel.getVolume();
873
}
874
else
875
{
876
Source mySource = sourceMap.get( sourcename );
877
if( mySource != null )
878
return mySource.sourceVolume;
879
else
880
return 0.0f;
881
}
882
}
883
884
/**
885
* Manually sets the specified source's pitch.
886
* @param sourcename The source's name.
887
* @param value A float value ( 0.5f - 2.0f ).
888
*/
889
public void setPitch( String sourcename, float value )
890
{
891
if( !midiSourcename( sourcename ) )
892
{
893
Source mySource = sourceMap.get( sourcename );
894
if( mySource != null )
895
{
896
float newPitch = value;
897
if( newPitch < 0.5f )
898
newPitch = 0.5f;
899
else if( newPitch > 2.0f )
900
newPitch = 2.0f;
901
902
mySource.setPitch( newPitch );
903
mySource.positionChanged();
904
}
905
}
906
}
907
908
/**
909
* Returns the pitch of the specified source.
910
* @param sourcename The source's name.
911
* @return Float value representing the source pitch (0.5f - 2.0f).
912
*/
913
public float getPitch( String sourcename )
914
{
915
if( !midiSourcename( sourcename ) )
916
{
917
Source mySource = sourceMap.get( sourcename );
918
if( mySource != null )
919
return mySource.getPitch();
920
}
921
return 1.0f;
922
}
923
924
/**
925
* Moves the listener relative to the current position.
926
* @param x X offset.
927
* @param y Y offset.
928
* @param z Z offset.
929
*/
930
public void moveListener( float x, float y, float z )
931
{
932
setListenerPosition( listener.position.x + x, listener.position.y + y,
933
listener.position.z + z );
934
}
935
936
/**
937
* Changes the listener's position.
938
* @param x Destination X coordinate.
939
* @param y Destination Y coordinate.
940
* @param z Destination Z coordinate.
941
*/
942
public void setListenerPosition( float x, float y, float z )
943
{
944
// update listener's position
945
listener.setPosition( x, y, z );
946
947
Set<String> keys = sourceMap.keySet();
948
Iterator<String> iter = keys.iterator();
949
String sourcename;
950
Source source;
951
952
// loop through and update the volume of all sources:
953
while( iter.hasNext() )
954
{
955
sourcename = iter.next();
956
source = sourceMap.get( sourcename );
957
if( source != null )
958
source.positionChanged();
959
}
960
}
961
962
/**
963
* Turn the listener 'angle' radians counterclockwise around the y-Axis,
964
* relative to the current angle.
965
* @param angle Angle in radians.
966
*/
967
public void turnListener( float angle )
968
{
969
setListenerAngle( listener.angle + angle );
970
971
Set<String> keys = sourceMap.keySet();
972
Iterator<String> iter = keys.iterator();
973
String sourcename;
974
Source source;
975
976
// loop through and update the volume of all sources:
977
while( iter.hasNext() )
978
{
979
sourcename = iter.next();
980
source = sourceMap.get( sourcename );
981
if( source != null )
982
source.positionChanged();
983
}
984
}
985
986
/**
987
* Changes the listeners orientation to the specified 'angle' radians
988
* counterclockwise around the y-Axis.
989
* @param angle Angle in radians.
990
*/
991
public void setListenerAngle( float angle )
992
{
993
listener.setAngle( angle );
994
995
Set<String> keys = sourceMap.keySet();
996
Iterator<String> iter = keys.iterator();
997
String sourcename;
998
Source source;
999
1000
// loop through and update the volume of all sources:
1001
while( iter.hasNext() )
1002
{
1003
sourcename = iter.next();
1004
source = sourceMap.get( sourcename );
1005
if( source != null )
1006
source.positionChanged();
1007
}
1008
}
1009
1010
/**
1011
* Changes the listeners orientation using the specified coordinates.
1012
* @param lookX X element of the look-at direction.
1013
* @param lookY Y element of the look-at direction.
1014
* @param lookZ Z element of the look-at direction.
1015
* @param upX X element of the up direction.
1016
* @param upY Y element of the up direction.
1017
* @param upZ Z element of the up direction.
1018
*/
1019
public void setListenerOrientation( float lookX, float lookY, float lookZ,
1020
float upX, float upY, float upZ )
1021
{
1022
listener.setOrientation( lookX, lookY, lookZ, upX, upY, upZ );
1023
1024
Set<String> keys = sourceMap.keySet();
1025
Iterator<String> iter = keys.iterator();
1026
String sourcename;
1027
Source source;
1028
1029
// loop through and update the volume of all sources:
1030
while( iter.hasNext() )
1031
{
1032
sourcename = iter.next();
1033
source = sourceMap.get( sourcename );
1034
if( source != null )
1035
source.positionChanged();
1036
}
1037
}
1038
1039
/**
1040
* Changes the listeners position and orientation using the specified listener
1041
* data.
1042
* @param l Listener data to use.
1043
*/
1044
public void setListenerData( ListenerData l )
1045
{
1046
listener.setData( l );
1047
}
1048
1049
/**
1050
* Creates sources based on the source map provided.
1051
* @param srcMap Sources to copy.
1052
*/
1053
public void copySources( HashMap<String, Source> srcMap )
1054
{
1055
if( srcMap == null )
1056
return;
1057
Set<String> keys = srcMap.keySet();
1058
Iterator<String> iter = keys.iterator();
1059
String sourcename;
1060
Source srcData;
1061
1062
// remove any existing sources before starting:
1063
sourceMap.clear();
1064
1065
// loop through and copy all the sources:
1066
while( iter.hasNext() )
1067
{
1068
sourcename = iter.next();
1069
srcData = srcMap.get( sourcename );
1070
if( srcData != null )
1071
{
1072
loadSound( srcData.filenameURL );
1073
sourceMap.put( sourcename, new Source( srcData, null ) );
1074
}
1075
}
1076
}
1077
1078
/**
1079
* Stops and deletes the specified source.
1080
* @param sourcename The source's name.
1081
*/
1082
public void removeSource( String sourcename )
1083
{
1084
Source mySource = sourceMap.get( sourcename );
1085
if( mySource != null ) {
1086
// if this is a streaming source just mark it removed - https://github.com/MinecraftForge/MinecraftForge/pull/4765
1087
if ( mySource.toStream )
1088
mySource.removed = true;
1089
else
1090
mySource.cleanup(); // end the source, free memory
1091
}
1092
sourceMap.remove( sourcename );
1093
}
1094
1095
/**
1096
* Searches for and removes all temporary sources that have finished playing.
1097
*/
1098
public void removeTemporarySources()
1099
{
1100
Set<String> keys = sourceMap.keySet();
1101
Iterator<String> iter = keys.iterator();
1102
String sourcename;
1103
Source srcData;
1104
1105
// loop through and cleanup all the sources:
1106
while( iter.hasNext() )
1107
{
1108
sourcename = iter.next();
1109
srcData = sourceMap.get( sourcename );
1110
if( (srcData != null) && (srcData.temporary)
1111
&& (!srcData.playing()) )
1112
{
1113
srcData.cleanup(); // end the source, free memory
1114
iter.remove();
1115
}
1116
}
1117
}
1118
1119
/* ########################################################################## */
1120
/* END OVERRIDE METHODS */
1121
/* ########################################################################## */
1122
1123
/**
1124
* Returns a handle to the next available channel. If the specified
1125
* source is a normal source, a normal channel is returned, and if it is a
1126
* streaming source, then a streaming channel is returned. If all channels of
1127
* the required type are currently playing, then the next channel playing a
1128
* non-priority source is returned. If no channels are available (i.e. they
1129
* are all playing priority sources) then getNextChannel returns null.
1130
* @param source Source to find a channel for.
1131
* @return The next available channel, or null.
1132
*/
1133
private Channel getNextChannel( Source source )
1134
{
1135
if( source == null )
1136
return null;
1137
1138
String sourcename = source.sourcename;
1139
if( sourcename == null )
1140
return null;
1141
1142
int x;
1143
int channels;
1144
int nextChannel;
1145
List<Channel> channelList;
1146
String[] sourceNames;
1147
String name;
1148
1149
if( source.toStream )
1150
{
1151
nextChannel = nextStreamingChannel;
1152
channelList = streamingChannels;
1153
sourceNames = streamingChannelSourceNames;
1154
}
1155
else
1156
{
1157
nextChannel = nextNormalChannel;
1158
channelList = normalChannels;
1159
sourceNames = normalChannelSourceNames;
1160
}
1161
1162
channels = channelList.size();
1163
1164
// Check if this source is already on a channel:
1165
for( x = 0; x < channels; x++ )
1166
{
1167
if( sourcename.equals( sourceNames[x] ) )
1168
return channelList.get( x );
1169
}
1170
1171
int n = nextChannel;
1172
Source src;
1173
// Play on the next new or non-playing channel:
1174
for( x = 0; x < channels; x++ )
1175
{
1176
name = sourceNames[n];
1177
if( name == null )
1178
src = null;
1179
else
1180
src = sourceMap.get( name );
1181
1182
if( src == null || !src.playing() )
1183
{
1184
if( source.toStream )
1185
{
1186
nextStreamingChannel = n + 1;
1187
if( nextStreamingChannel >= channels )
1188
nextStreamingChannel = 0;
1189
}
1190
else
1191
{
1192
nextNormalChannel = n + 1;
1193
if( nextNormalChannel >= channels )
1194
nextNormalChannel = 0;
1195
}
1196
sourceNames[n] = sourcename;
1197
return channelList.get( n );
1198
}
1199
n++;
1200
if( n >= channels )
1201
n = 0;
1202
}
1203
1204
n = nextChannel;
1205
// Play on the next non-priority channel:
1206
for( x = 0; x < channels; x++ )
1207
{
1208
name = sourceNames[n];
1209
if( name == null )
1210
src = null;
1211
else
1212
src = sourceMap.get( name );
1213
1214
if( src == null || !src.playing() || !src.priority )
1215
{
1216
if( source.toStream )
1217
{
1218
nextStreamingChannel = n + 1;
1219
if( nextStreamingChannel >= channels )
1220
nextStreamingChannel = 0;
1221
}
1222
else
1223
{
1224
nextNormalChannel = n + 1;
1225
if( nextNormalChannel >= channels )
1226
nextNormalChannel = 0;
1227
}
1228
sourceNames[n] = sourcename;
1229
return channelList.get( n );
1230
}
1231
n++;
1232
if( n >= channels )
1233
n = 0;
1234
}
1235
1236
return null;
1237
}
1238
1239
/**
1240
* Plays all sources whose 'toPlay' varriable is true but are not currently
1241
* playing (such as sources which were culled while looping and then
1242
* reactivated).
1243
*/
1244
public void replaySources()
1245
{
1246
Set<String> keys = sourceMap.keySet();
1247
Iterator<String> iter = keys.iterator();
1248
String sourcename;
1249
Source source;
1250
1251
// loop through and cleanup all the sources:
1252
while( iter.hasNext() )
1253
{
1254
sourcename = iter.next();
1255
source = sourceMap.get( sourcename );
1256
if( source != null )
1257
{
1258
if( source.toPlay && !source.playing() )
1259
{
1260
play( sourcename );
1261
source.toPlay = false;
1262
}
1263
}
1264
}
1265
}
1266
1267
/**
1268
* If the specified source is a streaming source or MIDI source, this method
1269
* queues up the next sound to play when the previous playback ends. This
1270
* method has no effect on non-streaming sources.
1271
* @param sourcename Source identifier.
1272
* @param filenameURL Filename/URL of the sound file to play next.
1273
*/
1274
public void queueSound( String sourcename, FilenameURL filenameURL )
1275
{
1276
if( midiSourcename( sourcename ) )
1277
{
1278
midiChannel.queueSound( filenameURL );
1279
}
1280
else
1281
{
1282
Source mySource = sourceMap.get( sourcename );
1283
if( mySource != null )
1284
mySource.queueSound( filenameURL );
1285
}
1286
}
1287
1288
/**
1289
* Removes the first occurrence of the specified filename from the specified
1290
* source's list of sounds to play when previous playback ends. This method
1291
* has no effect on non-streaming sources.
1292
* @param sourcename Source identifier.
1293
* @param filename Filename/identifier of the sound file to remove from the queue.
1294
*/
1295
public void dequeueSound( String sourcename, String filename )
1296
{
1297
if( midiSourcename( sourcename ) )
1298
{
1299
midiChannel.dequeueSound( filename );
1300
}
1301
else
1302
{
1303
Source mySource = sourceMap.get( sourcename );
1304
if( mySource != null )
1305
mySource.dequeueSound( filename );
1306
}
1307
}
1308
1309
/**
1310
* Fades out the volume of whatever the specified source is currently playing,
1311
* then begins playing the specified file at the source's previously
1312
* assigned volume level. If the filenameURL parameter is null or empty, the
1313
* specified source will simply fade out and stop. The miliseconds parameter
1314
* must be non-negative or zero. This method will remove anything that is
1315
* currently in the specified source's list of queued sounds that would have
1316
* played next when the current sound finished playing. This method may only
1317
* be used for streaming and MIDI sources.
1318
* @param sourcename Name of the source to fade out.
1319
* @param filenameURL Filename/URL of the sound file to play next, or null for none.
1320
* @param milis Number of miliseconds the fadeout should take.
1321
*/
1322
public void fadeOut( String sourcename, FilenameURL filenameURL,
1323
long milis )
1324
{
1325
if( midiSourcename( sourcename ) )
1326
{
1327
midiChannel.fadeOut( filenameURL, milis );
1328
}
1329
else
1330
{
1331
Source mySource = sourceMap.get( sourcename );
1332
if( mySource != null )
1333
mySource.fadeOut( filenameURL, milis );
1334
}
1335
}
1336
1337
/**
1338
* Fades out the volume of whatever the specified source is currently playing,
1339
* then fades the volume back in playing the specified file. Final volume
1340
* after fade-in completes will be equal to the source's previously assigned
1341
* volume level. The filenameURL parameter may not be null or empty. The
1342
* miliseconds parameters must be non-negative or zero. This method will
1343
* remove anything that is currently in the specified source's list of queued
1344
* sounds that would have played next when the current sound finished playing.
1345
* This method may only be used for streaming and MIDI sources.
1346
* @param sourcename Name of the source to fade out/in.
1347
* @param filenameURL Filename/URL of the sound file to play next, or null for none.
1348
* @param milisOut Number of miliseconds the fadeout should take.
1349
* @param milisIn Number of miliseconds the fadein should take.
1350
*/
1351
public void fadeOutIn( String sourcename, FilenameURL filenameURL,
1352
long milisOut, long milisIn )
1353
{
1354
if( midiSourcename( sourcename ) )
1355
{
1356
midiChannel.fadeOutIn( filenameURL, milisOut, milisIn );
1357
}
1358
else
1359
{
1360
Source mySource = sourceMap.get( sourcename );
1361
if( mySource != null )
1362
mySource.fadeOutIn( filenameURL, milisOut, milisIn );
1363
}
1364
}
1365
1366
/**
1367
* Makes sure the current volume levels of streaming sources and MIDI are
1368
* correct. This method is designed to help reduce the "jerky" fading behavior
1369
* that happens when using some library and codec pluggins (such as
1370
* LibraryJavaSound and CodecJOrbis). This method has no effect on normal
1371
* "non-streaming" sources. It would normally be called somewhere in the main
1372
* "game loop". IMPORTANT: To optimize frame-rates, do not call this method
1373
* for every frame. It is better to just call this method at some acceptable
1374
* "granularity" (play around with different granularities to find what sounds
1375
* acceptable for a particular situation).
1376
*/
1377
public void checkFadeVolumes()
1378
{
1379
if( midiChannel != null )
1380
midiChannel.resetGain();
1381
Channel c;
1382
Source s;
1383
for( int x = 0; x < streamingChannels.size(); x++ )
1384
{
1385
c = streamingChannels.get( x );
1386
if( c != null )
1387
{
1388
s = c.attachedSource;
1389
if( s != null )
1390
s.checkFadeOut();
1391
}
1392
}
1393
c = null;
1394
s = null;
1395
}
1396
1397
/**
1398
* Loads the specified MIDI file, and saves the source information about it.
1399
* @param toLoop Midi file should loop or play once.
1400
* @param sourcename Source identifier.
1401
* @param filenameURL Filename/URL of the MIDI file to load.
1402
*/
1403
public void loadMidi( boolean toLoop, String sourcename,
1404
FilenameURL filenameURL )
1405
{
1406
if( filenameURL == null )
1407
{
1408
errorMessage( "Filename/URL not specified in method 'loadMidi'." );
1409
return;
1410
}
1411
1412
if( !filenameURL.getFilename().matches(
1413
SoundSystemConfig.EXTENSION_MIDI ) )
1414
{
1415
errorMessage( "Filename/identifier doesn't end in '.mid' or" +
1416
"'.midi' in method loadMidi." );
1417
return;
1418
}
1419
1420
if( midiChannel == null )
1421
{
1422
midiChannel = new MidiChannel( toLoop, sourcename, filenameURL );
1423
}
1424
else
1425
{
1426
midiChannel.switchSource( toLoop, sourcename, filenameURL );
1427
}
1428
}
1429
1430
/**
1431
* Unloads the current Midi file.
1432
*/
1433
public void unloadMidi()
1434
{
1435
if( midiChannel != null )
1436
midiChannel.cleanup();
1437
midiChannel = null;
1438
}
1439
1440
/**
1441
* Checks if the sourcename matches the midi source.
1442
* @param sourcename Source identifier.
1443
* @return True if sourcename and midi sourcename match.
1444
*/
1445
public boolean midiSourcename( String sourcename )
1446
{
1447
if( midiChannel == null || sourcename == null )
1448
return false;
1449
1450
if( midiChannel.getSourcename() == null || sourcename.equals( "" ) )
1451
return false;
1452
1453
if( sourcename.equals( midiChannel.getSourcename() ) )
1454
return true;
1455
1456
return false;
1457
}
1458
1459
/**
1460
*
1461
* Returns the Source object identified by the specified name.
1462
* @param sourcename The source's name.
1463
* @return The source, or null if not found.
1464
*/
1465
public Source getSource( String sourcename )
1466
{
1467
return sourceMap.get( sourcename );
1468
}
1469
1470
/**
1471
*
1472
* Returns a handle to the MIDI channel, or null if one does not exist.
1473
* @return The MIDI channel.
1474
*/
1475
public MidiChannel getMidiChannel()
1476
{
1477
return midiChannel;
1478
}
1479
1480
/**
1481
*
1482
* Specifies the MIDI channel to use.
1483
* @param c New MIDI channel.
1484
*/
1485
public void setMidiChannel( MidiChannel c )
1486
{
1487
if( midiChannel != null && midiChannel != c )
1488
midiChannel.cleanup();
1489
1490
midiChannel = c;
1491
}
1492
1493
/**
1494
* Tells all the sources that the listener has moved.
1495
*/
1496
public void listenerMoved()
1497
{
1498
Set<String> keys = sourceMap.keySet();
1499
Iterator<String> iter = keys.iterator();
1500
String sourcename;
1501
Source srcData;
1502
1503
// loop through and copy all the sources:
1504
while( iter.hasNext() )
1505
{
1506
sourcename = iter.next();
1507
srcData = sourceMap.get( sourcename );
1508
if( srcData != null )
1509
{
1510
srcData.listenerMoved();
1511
}
1512
}
1513
}
1514
1515
/**
1516
* Returns the sources map.
1517
* @return Map of all sources.
1518
*/
1519
public HashMap<String, Source> getSources()
1520
{
1521
return sourceMap;
1522
}
1523
1524
/**
1525
* Returns information about the listener.
1526
* @return A ListenerData object.
1527
*/
1528
public ListenerData getListenerData()
1529
{
1530
return listener;
1531
}
1532
1533
/**
1534
* Indicates whether or not this library requires some codecs to reverse-order
1535
* the audio data they generate.
1536
* @return True if audio data should be reverse-ordered.
1537
*/
1538
public boolean reverseByteOrder()
1539
{
1540
return reverseByteOrder;
1541
}
1542
/**
1543
* Returns the short title of this library type.
1544
* @return A short title.
1545
*/
1546
public static String getTitle()
1547
{
1548
return "No Sound";
1549
}
1550
1551
/**
1552
* Returns a longer description of this library type.
1553
* @return A longer description.
1554
*/
1555
public static String getDescription()
1556
{
1557
return "Silent Mode";
1558
}
1559
1560
/**
1561
* Returns the name of the class.
1562
* @return "Library" + library title.
1563
*/
1564
public String getClassName()
1565
{
1566
return "Library";
1567
}
1568
1569
/**
1570
* Prints a message.
1571
* @param message Message to print.
1572
*/
1573
protected void message( String message )
1574
{
1575
logger.message( message, 0 );
1576
}
1577
1578
/**
1579
* Prints an important message.
1580
* @param message Message to print.
1581
*/
1582
protected void importantMessage( String message )
1583
{
1584
logger.importantMessage( message, 0 );
1585
}
1586
1587
/**
1588
* Prints the specified message if error is true.
1589
* @param error True or False.
1590
* @param message Message to print if error is true.
1591
* @return True if error is true.
1592
*/
1593
protected boolean errorCheck( boolean error, String message )
1594
{
1595
return logger.errorCheck( error, getClassName(), message, 0 );
1596
}
1597
1598
/**
1599
* Prints an error message.
1600
* @param message Message to print.
1601
*/
1602
protected void errorMessage( String message )
1603
{
1604
logger.errorMessage( getClassName(), message, 0 );
1605
}
1606
1607
/**
1608
* Prints an exception's error message followed by the stack trace.
1609
* @param e Exception containing the information to print.
1610
*/
1611
protected void printStackTrace( Exception e )
1612
{
1613
logger.printStackTrace( e, 1 );
1614
}
1615
}
1616
1617