replace source for third part library, TinySound, with build rule to download jar
parent
22223077f1
commit
850781d954
22
build.xml
22
build.xml
|
@ -44,6 +44,10 @@
|
|||
<get src="http://repo1.maven.org/maven2/org/json/json/20140107/json-20140107.jar"
|
||||
dest="${lib}/json-20140107.jar"
|
||||
skipexisting="true" />
|
||||
<echo message="downloading TinySound library to ${lib}"/>
|
||||
<get src="https://github.com/yadarts/maven/raw/master/kuusisto/tinysound/1.1.1/tinysound-1.1.1.jar"
|
||||
dest="${lib}/tinysound-1.1.1.jar"
|
||||
skipexisting="true" />
|
||||
</target>
|
||||
|
||||
<!-- init - Create temporary directory to build the program -->
|
||||
|
@ -77,7 +81,14 @@
|
|||
<manifest>
|
||||
<attribute name="Main-Class" value="magic.MagicMain" />
|
||||
<attribute name="SplashScreen-Image" value="magic/data/textures/splash.png" />
|
||||
<attribute name="Class-Path" value="lib/groovy-all-${groovy-version}.jar lib/json-20140107.jar lib/miglayout-core-4.2.jar lib/miglayout-swing-4.2.jar lib/commons-io-2.4.jar lib/trident-1.3.jar" />
|
||||
<attribute name="Class-Path"
|
||||
value="lib/groovy-all-${groovy-version}.jar
|
||||
lib/miglayout-core-4.2.jar
|
||||
lib/miglayout-swing-4.2.jar
|
||||
lib/commons-io-2.4.jar
|
||||
lib/trident-1.3.jar
|
||||
lib/json-20140107.jar
|
||||
lib/tinysound-1.1.1.jar" />
|
||||
</manifest>
|
||||
</jar>
|
||||
</target>
|
||||
|
@ -85,7 +96,14 @@
|
|||
<target depends="build" name="core">
|
||||
<jar destfile="release/Magarena-core.jar" basedir="build" includes="**/*.*" excludes="magic/*,magic/ui/**">
|
||||
<manifest>
|
||||
<attribute name="Class-Path" value="lib/groovy-all-${groovy-version}.jar lib/json-20140107.jar lib/commons-io-2.4.jar lib/trident-1.3.jar" />
|
||||
<attribute name="Class-Path"
|
||||
value="lib/groovy-all-${groovy-version}.jar
|
||||
lib/miglayout-core-4.2.jar
|
||||
lib/miglayout-swing-4.2.jar
|
||||
lib/commons-io-2.4.jar
|
||||
lib/trident-1.3.jar
|
||||
lib/json-20140107.jar
|
||||
lib/tinysound-1.1.1.jar" />
|
||||
</manifest>
|
||||
</jar>
|
||||
</target>
|
||||
|
|
|
@ -1,164 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012, Finn Kuusisto
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package kuusisto.tinysound;
|
||||
|
||||
/**
|
||||
* The Music interface is an abstraction for music. Music objects should only
|
||||
* be loaded via the TinySound <code>loadMusic()</code> functions. Music can be
|
||||
* played, paused, resumed, stopped and looped from specified positions.
|
||||
*
|
||||
* @author Finn Kuusisto
|
||||
*/
|
||||
public interface Music {
|
||||
|
||||
/**
|
||||
* Play this Music and loop if specified.
|
||||
* @param loop if this Music should loop
|
||||
*/
|
||||
public void play(boolean loop);
|
||||
|
||||
/**
|
||||
* Play this Music at the specified volume and loop if specified.
|
||||
* @param loop if this Music should loop
|
||||
* @param volume the volume to play the this Music
|
||||
*/
|
||||
public void play(boolean loop, double volume);
|
||||
|
||||
/**
|
||||
* Play this Music at the specified volume and pan, and loop if specified.
|
||||
* @param loop if this Music should loop
|
||||
* @param volume the volume to play the this Music
|
||||
* @param pan the pan at which to play this Music [-1.0,1.0], values outside
|
||||
* the valid range will be ignored
|
||||
*/
|
||||
public void play(boolean loop, double volume, double pan);
|
||||
|
||||
/**
|
||||
* Stop playing this Music and set its position to the beginning.
|
||||
*/
|
||||
public void stop();
|
||||
|
||||
/**
|
||||
* Stop playing this Music and keep its current position.
|
||||
*/
|
||||
public void pause();
|
||||
|
||||
/**
|
||||
* Play this Music from its current position.
|
||||
*/
|
||||
public void resume();
|
||||
|
||||
/**
|
||||
* Set this Music's position to the beginning.
|
||||
*/
|
||||
public void rewind();
|
||||
|
||||
/**
|
||||
* Set this Music's position to the loop position.
|
||||
*/
|
||||
public void rewindToLoopPosition();
|
||||
|
||||
/**
|
||||
* Determine if this Music is playing.
|
||||
* @return true if this Music is playing
|
||||
*/
|
||||
public boolean playing();
|
||||
|
||||
/**
|
||||
* Determine if this Music has reached its end and is done playing.
|
||||
* @return true if this Music is at the end and is done playing
|
||||
*/
|
||||
public boolean done();
|
||||
|
||||
/**
|
||||
* Determine if this Music will loop.
|
||||
* @return true if this Music will loop
|
||||
*/
|
||||
public boolean loop();
|
||||
|
||||
/**
|
||||
* Set whether this Music will loop.
|
||||
* @param loop whether this Music will loop
|
||||
*/
|
||||
public void setLoop(boolean loop);
|
||||
|
||||
/**
|
||||
* Get the loop position of this Music by sample frame.
|
||||
* @return loop position by sample frame
|
||||
*/
|
||||
public int getLoopPositionByFrame();
|
||||
|
||||
/**
|
||||
* Get the loop position of this Music by seconds.
|
||||
* @return loop position by seconds
|
||||
*/
|
||||
public double getLoopPositionBySeconds();
|
||||
|
||||
/**
|
||||
* Set the loop position of this Music by sample frame.
|
||||
* @param frameIndex sample frame loop position to set
|
||||
*/
|
||||
public void setLoopPositionByFrame(int frameIndex);
|
||||
|
||||
/**
|
||||
* Set the loop position of this Music by seconds.
|
||||
* @param seconds loop position to set by seconds
|
||||
*/
|
||||
public void setLoopPositionBySeconds(double seconds);
|
||||
|
||||
/**
|
||||
* Get the volume of this Music.
|
||||
* @return volume of this Music
|
||||
*/
|
||||
public double getVolume();
|
||||
|
||||
/**
|
||||
* Set the volume of this Music.
|
||||
* @param volume the desired volume of this Music
|
||||
*/
|
||||
public void setVolume(double volume);
|
||||
|
||||
/**
|
||||
* Get the pan of this Music.
|
||||
* @return pan of this Music
|
||||
*/
|
||||
public double getPan();
|
||||
|
||||
/**
|
||||
* Set the pan of this Music. Must be between -1.0 (full pan left) and 1.0
|
||||
* (full pan right). Values outside the valid range will be ignored.
|
||||
* @param pan the desired pan of this Music
|
||||
*/
|
||||
public void setPan(double pan);
|
||||
|
||||
/**
|
||||
* Unload this Music from the system. Attempts to use this Music after
|
||||
* unloading will result in error.
|
||||
*/
|
||||
public void unload();
|
||||
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012, Finn Kuusisto
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package kuusisto.tinysound;
|
||||
|
||||
/**
|
||||
* The Sound interface is an abstraction for sound effects. Sound objects
|
||||
* should only be loaded via the TinySound <code>loadSound()</code> functions.
|
||||
* Sounds can be played repeatedly in an overlapping fashion.
|
||||
*
|
||||
* @author Finn Kuusisto
|
||||
*/
|
||||
public interface Sound {
|
||||
|
||||
/**
|
||||
* Plays this Sound.
|
||||
*/
|
||||
public void play();
|
||||
|
||||
/**
|
||||
* Plays this Sound with a specified volume.
|
||||
* @param volume the volume at which to play this Sound
|
||||
*/
|
||||
public void play(double volume);
|
||||
|
||||
/**
|
||||
* Plays this Sound with a specified volume and pan.
|
||||
* @param volume the volume at which to play this Sound
|
||||
* @param pan the pan value to play this Sound [-1.0,1.0], values outside
|
||||
* the valid range will assume no panning (0.0)
|
||||
*/
|
||||
public void play(double volume, double pan);
|
||||
|
||||
/**
|
||||
* Stops this Sound from playing. Note that if this Sound was played
|
||||
* repeatedly in an overlapping fashion, all instances of this Sound still
|
||||
* playing will be stopped.
|
||||
*/
|
||||
public void stop();
|
||||
|
||||
/**
|
||||
* Unloads this Sound from the system. Attempts to use this Sound after
|
||||
* unloading will result in error.
|
||||
*/
|
||||
public void unload();
|
||||
|
||||
}
|
|
@ -1,900 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012, Finn Kuusisto
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package kuusisto.tinysound;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
import javax.sound.sampled.AudioFormat;
|
||||
import javax.sound.sampled.AudioInputStream;
|
||||
import javax.sound.sampled.AudioSystem;
|
||||
import javax.sound.sampled.DataLine;
|
||||
import javax.sound.sampled.LineUnavailableException;
|
||||
import javax.sound.sampled.SourceDataLine;
|
||||
import javax.sound.sampled.UnsupportedAudioFileException;
|
||||
|
||||
import kuusisto.tinysound.internal.ByteList;
|
||||
import kuusisto.tinysound.internal.MemMusic;
|
||||
import kuusisto.tinysound.internal.MemSound;
|
||||
import kuusisto.tinysound.internal.Mixer;
|
||||
import kuusisto.tinysound.internal.StreamInfo;
|
||||
import kuusisto.tinysound.internal.StreamMusic;
|
||||
import kuusisto.tinysound.internal.StreamSound;
|
||||
import kuusisto.tinysound.internal.UpdateRunner;
|
||||
|
||||
/**
|
||||
* TinySound is the main class of the TinySound system. In order to use the
|
||||
* TinySound system, it must be initialized. After that, Music and Sound
|
||||
* objects can be loaded and used. When finished with the TinySound system, it
|
||||
* must be shutdown.
|
||||
*
|
||||
* @author Finn Kuusisto
|
||||
*/
|
||||
public class TinySound {
|
||||
|
||||
public static final String VERSION = "1.1.1";
|
||||
|
||||
/**
|
||||
* The internal format used by TinySound.
|
||||
*/
|
||||
public static final AudioFormat FORMAT = new AudioFormat(
|
||||
AudioFormat.Encoding.PCM_SIGNED, //linear signed PCM
|
||||
44100, //44.1kHz sampling rate
|
||||
16, //16-bit
|
||||
2, //2 channels fool
|
||||
4, //frame size 4 bytes (16-bit, 2 channel)
|
||||
44100, //same as sampling rate
|
||||
false //little-endian
|
||||
);
|
||||
|
||||
//the system has only one mixer for both music and sounds
|
||||
private static Mixer mixer;
|
||||
//need a line to the speakers
|
||||
private static SourceDataLine outLine;
|
||||
//see if the system has been initialized
|
||||
private static boolean inited = false;
|
||||
//auto-updater for the system
|
||||
private static UpdateRunner autoUpdater;
|
||||
//counter for unique sound IDs
|
||||
private static int soundCount = 0;
|
||||
|
||||
/**
|
||||
* Initialize Tinysound. This must be called before loading audio.
|
||||
*/
|
||||
public static void init() {
|
||||
if (TinySound.inited) {
|
||||
return;
|
||||
}
|
||||
//try to open a line to the speakers
|
||||
DataLine.Info info = new DataLine.Info(SourceDataLine.class,
|
||||
TinySound.FORMAT);
|
||||
if (!AudioSystem.isLineSupported(info)) {
|
||||
System.err.println("Unsupported output format!");
|
||||
return;
|
||||
}
|
||||
TinySound.outLine = TinySound.tryGetLine();
|
||||
if (TinySound.outLine == null) {
|
||||
System.err.println("Output line unavailable!");
|
||||
return;
|
||||
}
|
||||
//start the line and finish initialization
|
||||
TinySound.outLine.start();
|
||||
TinySound.finishInit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternative function to initialize TinySound which should only be used by
|
||||
* those very familiar with the Java Sound API. This function allows the
|
||||
* line that is used for audio playback to be opened on a specific Mixer.
|
||||
* @param info the Mixer.Info representing the desired Mixer
|
||||
* @throws LineUnavailableException if a Line is not available from the
|
||||
* specified Mixer
|
||||
* @throws SecurityException if the specified Mixer or Line are unavailable
|
||||
* due to security restrictions
|
||||
* @throws IllegalArgumentException if the specified Mixer is not installed
|
||||
* on the system
|
||||
*/
|
||||
public static void init(javax.sound.sampled.Mixer.Info info)
|
||||
throws LineUnavailableException, SecurityException,
|
||||
IllegalArgumentException {
|
||||
if (TinySound.inited) {
|
||||
return;
|
||||
}
|
||||
//try to open a line to the speakers
|
||||
javax.sound.sampled.Mixer mixer = AudioSystem.getMixer(info);
|
||||
DataLine.Info lineInfo = new DataLine.Info(SourceDataLine.class,
|
||||
TinySound.FORMAT);
|
||||
TinySound.outLine = (SourceDataLine)mixer.getLine(lineInfo);
|
||||
TinySound.outLine.open(TinySound.FORMAT);
|
||||
//start the line and finish initialization
|
||||
TinySound.outLine.start();
|
||||
TinySound.finishInit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the mixer and updater, and marks TinySound as initialized.
|
||||
*/
|
||||
private static void finishInit() {
|
||||
//now initialize the mixer
|
||||
TinySound.mixer = new Mixer();
|
||||
//initialize and start the updater
|
||||
TinySound.autoUpdater = new UpdateRunner(TinySound.mixer,
|
||||
TinySound.outLine);
|
||||
Thread updateThread = new Thread(TinySound.autoUpdater);
|
||||
try {
|
||||
updateThread.setDaemon(true);
|
||||
updateThread.setPriority(Thread.MAX_PRIORITY);
|
||||
} catch (Exception e) {}
|
||||
TinySound.inited = true;
|
||||
updateThread.start();
|
||||
//yield to potentially give the updater a chance
|
||||
Thread.yield();
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown TinySound.
|
||||
*/
|
||||
public static void shutdown() {
|
||||
if (!TinySound.inited) {
|
||||
return;
|
||||
}
|
||||
TinySound.inited = false;
|
||||
//stop the auto-updater if running
|
||||
TinySound.autoUpdater.stop();
|
||||
TinySound.autoUpdater = null;
|
||||
TinySound.outLine.stop();
|
||||
TinySound.outLine.flush();
|
||||
TinySound.mixer.clearMusic();
|
||||
TinySound.mixer.clearSounds();
|
||||
TinySound.mixer = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if TinySound is initialized and ready for use.
|
||||
* @return true if TinySound is initialized, false if TinySound has not been
|
||||
* initialized or has subsequently been shutdown
|
||||
*/
|
||||
public static boolean isInitialized() {
|
||||
return TinySound.inited;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the global volume for all audio.
|
||||
* @return the global volume for all audio, -1.0 if TinySound has not been
|
||||
* initialized or has subsequently been shutdown
|
||||
*/
|
||||
public static double getGlobalVolume() {
|
||||
if (!TinySound.inited) {
|
||||
return -1.0;
|
||||
}
|
||||
return TinySound.mixer.getVolume();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the global volume. This is an extra multiplier, not a replacement,
|
||||
* for all Music and Sound volume settings. It starts at 1.0.
|
||||
* @param volume the global volume to set
|
||||
*/
|
||||
public static void setGlobalVolume(double volume) {
|
||||
if (!TinySound.inited) {
|
||||
return;
|
||||
}
|
||||
TinySound.mixer.setVolume(volume);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Music by a resource name. The resource must be on the classpath
|
||||
* for this to work. This will store audio data in memory.
|
||||
* @param name name of the Music resource
|
||||
* @return Music resource as specified, null if not found/loaded
|
||||
*/
|
||||
public static Music loadMusic(String name) {
|
||||
return TinySound.loadMusic(name, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Music by a resource name. The resource must be on the classpath
|
||||
* for this to work.
|
||||
* @param name name of the Music resource
|
||||
* @param streamFromFile true if this Music should be streamed from a
|
||||
* temporary file to reduce memory overhead
|
||||
* @return Music resource as specified, null if not found/loaded
|
||||
*/
|
||||
public static Music loadMusic(String name, boolean streamFromFile) {
|
||||
//check if the system is initialized
|
||||
if (!TinySound.inited) {
|
||||
System.err.println("TinySound not initialized!");
|
||||
return null;
|
||||
}
|
||||
//check for failure
|
||||
if (name == null) {
|
||||
return null;
|
||||
}
|
||||
//check for correct naming
|
||||
if (!name.startsWith("/")) {
|
||||
name = "/" + name;
|
||||
}
|
||||
URL url = TinySound.class.getResource(name);
|
||||
//check for failure to find resource
|
||||
if (url == null) {
|
||||
System.err.println("Unable to find resource " + name + "!");
|
||||
return null;
|
||||
}
|
||||
return TinySound.loadMusic(url, streamFromFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Music by a File. This will store audio data in memory.
|
||||
* @param file the Music file to load
|
||||
* @return Music from file as specified, null if not found/loaded
|
||||
*/
|
||||
public static Music loadMusic(File file) {
|
||||
return TinySound.loadMusic(file, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Music by a File.
|
||||
* @param file the Music file to load
|
||||
* @param streamFromFile true if this Music should be streamed from a
|
||||
* temporary file to reduce memory overhead
|
||||
* @return Music from file as specified, null if not found/loaded
|
||||
*/
|
||||
public static Music loadMusic(File file, boolean streamFromFile) {
|
||||
//check if the system is initialized
|
||||
if (!TinySound.inited) {
|
||||
System.err.println("TinySound not initialized!");
|
||||
return null;
|
||||
}
|
||||
//check for failure
|
||||
if (file == null) {
|
||||
return null;
|
||||
}
|
||||
URL url = null;
|
||||
try {
|
||||
url = file.toURI().toURL();
|
||||
} catch (MalformedURLException e) {
|
||||
System.err.println("Unable to find file " + file + "!");
|
||||
return null;
|
||||
}
|
||||
return TinySound.loadMusic(url, streamFromFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Music by a URL. This will store audio data in memory.
|
||||
* @param url the URL of the Music
|
||||
* @return Music from URL as specified, null if not found/loaded
|
||||
*/
|
||||
public static Music loadMusic(URL url) {
|
||||
return TinySound.loadMusic(url, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Music by a URL.
|
||||
* @param url the URL of the Music
|
||||
* @param streamFromFile true if this Music should be streamed from a
|
||||
* temporary file to reduce memory overhead
|
||||
* @return Music from URL as specified, null if not found/loaded
|
||||
*/
|
||||
public static Music loadMusic(URL url, boolean streamFromFile) {
|
||||
//check if the system is initialized
|
||||
if (!TinySound.inited) {
|
||||
System.err.println("TinySound not initialized!");
|
||||
return null;
|
||||
}
|
||||
//check for failure
|
||||
if (url == null) {
|
||||
return null;
|
||||
}
|
||||
//get a valid stream of audio data
|
||||
AudioInputStream audioStream = TinySound.getValidAudioStream(url);
|
||||
//check for failure
|
||||
if (audioStream == null) {
|
||||
return null;
|
||||
}
|
||||
//try to read all the bytes
|
||||
byte[][] data = TinySound.readAllBytes(audioStream);
|
||||
//check for failure
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
//handle differently if streaming from a file
|
||||
if (streamFromFile) {
|
||||
StreamInfo info = TinySound.createFileStream(data);
|
||||
//check for failure
|
||||
if (info == null) {
|
||||
return null;
|
||||
}
|
||||
//try to create it
|
||||
StreamMusic sm = null;
|
||||
try {
|
||||
sm = new StreamMusic(info.URL, info.NUM_BYTES_PER_CHANNEL,
|
||||
TinySound.mixer);
|
||||
} catch (IOException e) {
|
||||
System.err.println("Failed to create StreamMusic!");
|
||||
}
|
||||
return sm;
|
||||
}
|
||||
//construct the Music object and register it with the mixer
|
||||
return new MemMusic(data[0], data[1], TinySound.mixer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Sound by a resource name. The resource must be on the classpath
|
||||
* for this to work. This will store audio data in memory.
|
||||
* @param name name of the Sound resource
|
||||
* @return Sound resource as specified, null if not found/loaded
|
||||
*/
|
||||
public static Sound loadSound(String name) {
|
||||
return TinySound.loadSound(name, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Sound by a resource name. The resource must be on the classpath
|
||||
* for this to work.
|
||||
* @param name name of the Sound resource
|
||||
* @param streamFromFile true if this Music should be streamed from a
|
||||
* temporary file to reduce memory overhead
|
||||
* @return Sound resource as specified, null if not found/loaded
|
||||
*/
|
||||
public static Sound loadSound(String name, boolean streamFromFile) {
|
||||
//check if the system is initialized
|
||||
if (!TinySound.inited) {
|
||||
System.err.println("TinySound not initialized!");
|
||||
return null;
|
||||
}
|
||||
//check for failure
|
||||
if (name == null) {
|
||||
return null;
|
||||
}
|
||||
//check for correct naming
|
||||
if (!name.startsWith("/")) {
|
||||
name = "/" + name;
|
||||
}
|
||||
URL url = TinySound.class.getResource(name);
|
||||
//check for failure to find resource
|
||||
if (url == null) {
|
||||
System.err.println("Unable to find resource " + name + "!");
|
||||
return null;
|
||||
}
|
||||
return TinySound.loadSound(url, streamFromFile);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Sound by a File. This will store audio data in memory.
|
||||
* @param file the Sound file to load
|
||||
* @return Sound from file as specified, null if not found/loaded
|
||||
*/
|
||||
public static Sound loadSound(File file) {
|
||||
return TinySound.loadSound(file, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Sound by a File.
|
||||
* @param file the Sound file to load
|
||||
* @param streamFromFile true if this Music should be streamed from a
|
||||
* temporary file to reduce memory overhead
|
||||
* @return Sound from file as specified, null if not found/loaded
|
||||
*/
|
||||
public static Sound loadSound(File file, boolean streamFromFile) {
|
||||
//check if the system is initialized
|
||||
if (!TinySound.inited) {
|
||||
System.err.println("TinySound not initialized!");
|
||||
return null;
|
||||
}
|
||||
//check for failure
|
||||
if (file == null) {
|
||||
return null;
|
||||
}
|
||||
URL url = null;
|
||||
try {
|
||||
url = file.toURI().toURL();
|
||||
} catch (MalformedURLException e) {
|
||||
System.err.println("Unable to find file " + file + "!");
|
||||
return null;
|
||||
}
|
||||
return TinySound.loadSound(url, streamFromFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Sound by a URL. This will store audio data in memory.
|
||||
* @param url the URL of the Sound
|
||||
* @return Sound from URL as specified, null if not found/loaded
|
||||
*/
|
||||
public static Sound loadSound(URL url) {
|
||||
return TinySound.loadSound(url, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a Sound by a URL. This will store audio data in memory.
|
||||
* @param url the URL of the Sound
|
||||
* @param streamFromFile true if this Music should be streamed from a
|
||||
* temporary file to reduce memory overhead
|
||||
* @return Sound from URL as specified, null if not found/loaded
|
||||
*/
|
||||
public static Sound loadSound(URL url, boolean streamFromFile) {
|
||||
//check if the system is initialized
|
||||
if (!TinySound.inited) {
|
||||
System.err.println("TinySound not initialized!");
|
||||
return null;
|
||||
}
|
||||
//check for failure
|
||||
if (url == null) {
|
||||
return null;
|
||||
}
|
||||
//get a valid stream of audio data
|
||||
AudioInputStream audioStream = TinySound.getValidAudioStream(url);
|
||||
//check for failure
|
||||
if (audioStream == null) {
|
||||
return null;
|
||||
}
|
||||
//try to read all the bytes
|
||||
byte[][] data = TinySound.readAllBytes(audioStream);
|
||||
//check for failure
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
//handle differently if streaming from file
|
||||
if (streamFromFile) {
|
||||
StreamInfo info = TinySound.createFileStream(data);
|
||||
//check for failure
|
||||
if (info == null) {
|
||||
return null;
|
||||
}
|
||||
//try to create it
|
||||
StreamSound ss = null;
|
||||
try {
|
||||
ss = new StreamSound(info.URL, info.NUM_BYTES_PER_CHANNEL,
|
||||
TinySound.mixer, TinySound.soundCount);
|
||||
TinySound.soundCount++;
|
||||
} catch (IOException e) {
|
||||
System.err.println("Failed to create StreamSound!");
|
||||
}
|
||||
return ss;
|
||||
}
|
||||
//construct the Sound object
|
||||
TinySound.soundCount++;
|
||||
return new MemSound(data[0], data[1], TinySound.mixer,
|
||||
TinySound.soundCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all of the bytes from an AudioInputStream.
|
||||
* @param stream the stream to read
|
||||
* @return all bytes from the stream, null if error
|
||||
*/
|
||||
private static byte[][] readAllBytes(AudioInputStream stream) {
|
||||
//left and right channels
|
||||
byte[][] data = null;
|
||||
int numChannels = stream.getFormat().getChannels();
|
||||
//handle 1-channel
|
||||
if (numChannels == 1) {
|
||||
byte[] left = TinySound.readAllBytesOneChannel(stream);
|
||||
//check failure
|
||||
if (left == null) {
|
||||
return null;
|
||||
}
|
||||
data = new byte[2][];
|
||||
data[0] = left;
|
||||
data[1] = left; //don't copy for the right channel
|
||||
} //handle 2-channel
|
||||
else if (numChannels == 2) {
|
||||
data = TinySound.readAllBytesTwoChannel(stream);
|
||||
}
|
||||
else { //wtf?
|
||||
System.err.println("Unable to read " + numChannels + " channels!");
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all of the bytes from a 1-channel AudioInputStream.
|
||||
* @param stream the stream to read
|
||||
* @return all bytes from the stream, null if error
|
||||
*/
|
||||
private static byte[] readAllBytesOneChannel(AudioInputStream stream) {
|
||||
//read all the bytes (assuming 1-channel)
|
||||
byte[] data = null;
|
||||
try {
|
||||
data = TinySound.getBytes(stream);
|
||||
}
|
||||
catch (IOException e) {
|
||||
System.err.println("Error reading all bytes from stream!");
|
||||
return null;
|
||||
}
|
||||
finally {
|
||||
try { stream.close(); } catch (IOException e) {}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all of the bytes from a 2-channel AudioInputStream.
|
||||
* @param stream the stream to read
|
||||
* @return all bytes from the stream, null if error
|
||||
*/
|
||||
private static byte[][] readAllBytesTwoChannel(AudioInputStream stream) {
|
||||
//read all the bytes (assuming 16-bit, 2-channel)
|
||||
byte[][] data = null;
|
||||
try {
|
||||
byte[] allBytes = TinySound.getBytes(stream);
|
||||
byte[] left = new byte[allBytes.length / 2];
|
||||
byte[] right = new byte[allBytes.length / 2];
|
||||
for (int i = 0, j = 0; i < allBytes.length; i += 4, j += 2) {
|
||||
//interleaved left then right
|
||||
left[j] = allBytes[i];
|
||||
left[j + 1] = allBytes[i + 1];
|
||||
right[j] = allBytes[i + 2];
|
||||
right[j + 1] = allBytes[i + 3];
|
||||
}
|
||||
data = new byte[2][];
|
||||
data[0] = left;
|
||||
data[1] = right;
|
||||
}
|
||||
catch (IOException e) {
|
||||
System.err.println("Error reading all bytes from stream!");
|
||||
return null;
|
||||
}
|
||||
finally {
|
||||
try { stream.close(); } catch (IOException e) {}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and AudioInputStream in the TinySound system format.
|
||||
* @param url URL of the resource
|
||||
* @return the specified stream as an AudioInputStream stream, null if
|
||||
* failure
|
||||
*/
|
||||
private static AudioInputStream getValidAudioStream(URL url) {
|
||||
AudioInputStream audioStream = null;
|
||||
try {
|
||||
audioStream = AudioSystem.getAudioInputStream(url);
|
||||
AudioFormat streamFormat = audioStream.getFormat();
|
||||
//1-channel can also be treated as stereo
|
||||
AudioFormat mono16 = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
|
||||
44100, 16, 1, 2, 44100, false);
|
||||
//1 or 2 channel 8-bit may be easy to convert
|
||||
AudioFormat mono8 = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
|
||||
44100, 8, 1, 1, 44100, false);
|
||||
AudioFormat stereo8 =
|
||||
new AudioFormat(AudioFormat.Encoding.PCM_SIGNED, 44100, 8, 2, 2,
|
||||
44100, false);
|
||||
//now check formats (attempt conversion as needed)
|
||||
if (streamFormat.matches(TinySound.FORMAT) ||
|
||||
streamFormat.matches(mono16)) {
|
||||
return audioStream;
|
||||
} //check conversion to TinySound format
|
||||
else if (AudioSystem.isConversionSupported(TinySound.FORMAT,
|
||||
streamFormat)) {
|
||||
audioStream = AudioSystem.getAudioInputStream(TinySound.FORMAT,
|
||||
audioStream);
|
||||
} //check conversion to mono alternate
|
||||
else if (AudioSystem.isConversionSupported(mono16, streamFormat)) {
|
||||
audioStream = AudioSystem.getAudioInputStream(mono16,
|
||||
audioStream);
|
||||
} //try convert from 8-bit, 2-channel
|
||||
else if (streamFormat.matches(stereo8) ||
|
||||
AudioSystem.isConversionSupported(stereo8, streamFormat)) {
|
||||
//convert to 8-bit stereo first?
|
||||
if (!streamFormat.matches(stereo8)) {
|
||||
audioStream = AudioSystem.getAudioInputStream(stereo8,
|
||||
audioStream);
|
||||
}
|
||||
audioStream = TinySound.convertStereo8Bit(audioStream);
|
||||
} //try convert from 8-bit, 1-channel
|
||||
else if (streamFormat.matches(mono8) ||
|
||||
AudioSystem.isConversionSupported(mono8, streamFormat)) {
|
||||
//convert to 8-bit mono first?
|
||||
if (!streamFormat.matches(mono8)) {
|
||||
audioStream = AudioSystem.getAudioInputStream(mono8,
|
||||
audioStream);
|
||||
}
|
||||
audioStream = TinySound.convertMono8Bit(audioStream);
|
||||
} //it's time to give up
|
||||
else {
|
||||
System.err.println("Unable to convert audio resource!");
|
||||
System.err.println(url);
|
||||
System.err.println(streamFormat);
|
||||
audioStream.close();
|
||||
return null;
|
||||
}
|
||||
//check the frame length
|
||||
long frameLength = audioStream.getFrameLength();
|
||||
//too long
|
||||
if (frameLength > Integer.MAX_VALUE) {
|
||||
System.err.println("Audio resource too long!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
catch (UnsupportedAudioFileException e) {
|
||||
System.err.println("Unsupported audio resource!\n" +
|
||||
e.getMessage());
|
||||
return null;
|
||||
}
|
||||
catch (IOException e) {
|
||||
System.err.println("Error getting resource stream!\n" +
|
||||
e.getMessage());
|
||||
return null;
|
||||
}
|
||||
return audioStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an 8-bit, signed, 1-channel AudioInputStream to 16-bit, signed,
|
||||
* 1-channel.
|
||||
* @param stream stream to convert
|
||||
* @return converted stream
|
||||
*/
|
||||
private static AudioInputStream convertMono8Bit(AudioInputStream stream) {
|
||||
//assuming 8-bit, 1-channel to 16-bit, 1-channel
|
||||
byte[] newData = null;
|
||||
try {
|
||||
byte[] data = TinySound.getBytes(stream);
|
||||
int newNumBytes = data.length * 2;
|
||||
//check if size overflowed
|
||||
if (newNumBytes < 0) {
|
||||
System.err.println("Audio resource too long!");
|
||||
return null;
|
||||
}
|
||||
newData = new byte[newNumBytes];
|
||||
//convert bytes one-by-one to int, and then to 16-bit
|
||||
for (int i = 0, j = 0; i < data.length; i++, j += 2) {
|
||||
//convert it to a double
|
||||
double floatVal = (double)data[i];
|
||||
floatVal /= (floatVal < 0) ? 128 : 127;
|
||||
if (floatVal < -1.0) { //just in case
|
||||
floatVal = -1.0;
|
||||
}
|
||||
else if (floatVal > 1.0) {
|
||||
floatVal = 1.0;
|
||||
}
|
||||
//convert it to an int and then to 2 bytes
|
||||
int val = (int)(floatVal * Short.MAX_VALUE);
|
||||
newData[j + 1] = (byte)((val >> 8) & 0xFF); //MSB
|
||||
newData[j] = (byte)(val & 0xFF); //LSB
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
System.err.println("Error reading all bytes from stream!");
|
||||
return null;
|
||||
}
|
||||
finally {
|
||||
try { stream.close(); } catch (IOException e) {}
|
||||
}
|
||||
AudioFormat mono16 = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
|
||||
44100, 16, 1, 2, 44100, false);
|
||||
return new AudioInputStream(new ByteArrayInputStream(newData), mono16,
|
||||
newData.length / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an 8-bit, signed, 2-channel AudioInputStream to 16-bit, signed,
|
||||
* 2-channel.
|
||||
* @param stream stream to convert
|
||||
* @return converted stream
|
||||
*/
|
||||
private static AudioInputStream convertStereo8Bit(AudioInputStream stream) {
|
||||
//assuming 8-bit, 2-channel to 16-bit, 2-channel
|
||||
byte[] newData = null;
|
||||
try {
|
||||
byte[] data = TinySound.getBytes(stream);
|
||||
int newNumBytes = data.length * 2 * 2;
|
||||
//check if size overflowed
|
||||
if (newNumBytes < 0) {
|
||||
System.err.println("Audio resource too long!");
|
||||
return null;
|
||||
}
|
||||
newData = new byte[newNumBytes];
|
||||
for (int i = 0, j = 0; i < data.length; i += 2, j += 4) {
|
||||
//convert them to doubles
|
||||
double leftFloatVal = (double)data[i];
|
||||
double rightFloatVal = (double)data[i + 1];
|
||||
leftFloatVal /= (leftFloatVal < 0) ? 128 : 127;
|
||||
rightFloatVal /= (rightFloatVal < 0) ? 128 : 127;
|
||||
if (leftFloatVal < -1.0) { //just in case
|
||||
leftFloatVal = -1.0;
|
||||
}
|
||||
else if (leftFloatVal > 1.0) {
|
||||
leftFloatVal = 1.0;
|
||||
}
|
||||
if (rightFloatVal < -1.0) { //just in case
|
||||
rightFloatVal = -1.0;
|
||||
}
|
||||
else if (rightFloatVal > 1.0) {
|
||||
rightFloatVal = 1.0;
|
||||
}
|
||||
//convert them to ints and then to 2 bytes each
|
||||
int leftVal = (int)(leftFloatVal * Short.MAX_VALUE);
|
||||
int rightVal = (int)(rightFloatVal * Short.MAX_VALUE);
|
||||
//left channel bytes
|
||||
newData[j + 1] = (byte)((leftVal >> 8) & 0xFF); //MSB
|
||||
newData[j] = (byte)(leftVal & 0xFF); //LSB
|
||||
//then right channel bytes
|
||||
newData[j + 3] = (byte)((rightVal >> 8) & 0xFF); //MSB
|
||||
newData[j + 2] = (byte)(rightVal & 0xFF); //LSB
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
System.err.println("Error reading all bytes from stream!");
|
||||
return null;
|
||||
}
|
||||
finally {
|
||||
try { stream.close(); } catch (IOException e) {}
|
||||
}
|
||||
AudioFormat stereo16 = new AudioFormat(AudioFormat.Encoding.PCM_SIGNED,
|
||||
44100, 16, 2, 4, 44100, false);
|
||||
return new AudioInputStream(new ByteArrayInputStream(newData), stereo16,
|
||||
newData.length / 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read all of the bytes from an AudioInputStream.
|
||||
* @param stream the stream from which to read bytes
|
||||
* @return all bytes read from the AudioInputStream
|
||||
* @throws IOException
|
||||
*/
|
||||
private static byte[] getBytes(AudioInputStream stream)
|
||||
throws IOException {
|
||||
//buffer 1-sec at a time
|
||||
int bufSize = (int)TinySound.FORMAT.getSampleRate() *
|
||||
TinySound.FORMAT.getChannels() * TinySound.FORMAT.getFrameSize();
|
||||
byte[] buf = new byte[bufSize];
|
||||
ByteList list = new ByteList(bufSize);
|
||||
int numRead = 0;
|
||||
while ((numRead = stream.read(buf)) > -1) {
|
||||
for (int i = 0; i < numRead; i++) {
|
||||
list.add(buf[i]);
|
||||
}
|
||||
}
|
||||
return list.asArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps audio data to a temporary file for streaming and returns a
|
||||
* StreamInfo for the stream.
|
||||
* @param data the audio data to write to the temporary file
|
||||
* @return a StreamInfo for the stream
|
||||
*/
|
||||
private static StreamInfo createFileStream(byte[][] data) {
|
||||
//first try to create a file for the data to live in
|
||||
File temp = null;
|
||||
try {
|
||||
temp = File.createTempFile("tiny", "sound");
|
||||
//make sure this file will be deleted on exit
|
||||
temp.deleteOnExit();
|
||||
} catch (IOException e) {
|
||||
System.err.println("Failed to create file for streaming!");
|
||||
return null;
|
||||
}
|
||||
//see if we can get the URL for this file
|
||||
URL url = null;
|
||||
try {
|
||||
url = temp.toURI().toURL();
|
||||
} catch (MalformedURLException e1) {
|
||||
System.err.println("Failed to get URL for stream file!");
|
||||
return null;
|
||||
}
|
||||
//we have the file, now we want to be able to write to it
|
||||
OutputStream out = null;
|
||||
try {
|
||||
out = new BufferedOutputStream(new FileOutputStream(temp),
|
||||
(512 * 1024)); //buffer 512kb
|
||||
} catch (FileNotFoundException e) {
|
||||
System.err.println("Failed to open stream file for writing!");
|
||||
return null;
|
||||
}
|
||||
//write the bytes to the file
|
||||
try {
|
||||
//write two at a time from each channel
|
||||
for (int i = 0; i < data[0].length; i += 2) {
|
||||
try {
|
||||
//first left
|
||||
out.write(data[0], i, 2);
|
||||
//then right
|
||||
out.write(data[1], i, 2);
|
||||
}
|
||||
catch (IOException e) {
|
||||
//hmm
|
||||
System.err.println("Failed writing bytes to stream file!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
out.close();
|
||||
} catch (IOException e) {
|
||||
//what?
|
||||
System.err.println("Failed closing stream file after writing!");
|
||||
}
|
||||
}
|
||||
return new StreamInfo(url, data[0].length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates through available JavaSound Mixers looking for one that can
|
||||
* provide a line to the speakers.
|
||||
* @return an opened SourceDataLine to the speakers
|
||||
*/
|
||||
private static SourceDataLine tryGetLine() {
|
||||
//first build our line info and get all available mixers
|
||||
DataLine.Info lineInfo = new DataLine.Info(SourceDataLine.class,
|
||||
TinySound.FORMAT);
|
||||
javax.sound.sampled.Mixer.Info[] mixerInfos =
|
||||
AudioSystem.getMixerInfo();
|
||||
//iterate through the mixers trying to find a line
|
||||
for (int i = 0; i < mixerInfos.length; i++) {
|
||||
javax.sound.sampled.Mixer mixer = null;
|
||||
try {
|
||||
//first try to actually get the mixer
|
||||
mixer = AudioSystem.getMixer(mixerInfos[i]);
|
||||
}
|
||||
catch (SecurityException e) {
|
||||
//not much we can do here
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
//this should never happen since we were told the mixer exists
|
||||
}
|
||||
//check if we got a mixer and our line is supported
|
||||
if (mixer == null || !mixer.isLineSupported(lineInfo)) {
|
||||
continue;
|
||||
}
|
||||
//see if we can actually get a line
|
||||
SourceDataLine line = null;
|
||||
try {
|
||||
line = (SourceDataLine)mixer.getLine(lineInfo);
|
||||
//don't try to open if already open
|
||||
if (!line.isOpen()) {
|
||||
line.open(TinySound.FORMAT);
|
||||
}
|
||||
}
|
||||
catch (LineUnavailableException e) {
|
||||
//we either failed to get or open
|
||||
//should we do anything here?
|
||||
}
|
||||
catch (SecurityException e) {
|
||||
//not much we can do here
|
||||
}
|
||||
//check if we succeeded
|
||||
if (line != null && line.isOpen()) {
|
||||
return line;
|
||||
}
|
||||
}
|
||||
//no good
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,119 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012, Finn Kuusisto
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package kuusisto.tinysound.internal;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* The ByteList class is a dynamically sized array of primitive bytes. This
|
||||
* allows the use of a dynamically sized array without the extra overhead that
|
||||
* comes with List\<Byte\>. ByteList is an internal class of the TinySound
|
||||
* system and should be of no real concern to the average user of TinySound.
|
||||
*
|
||||
* @author Finn Kuusisto
|
||||
*/
|
||||
public class ByteList {
|
||||
|
||||
private int numBytes;
|
||||
private byte[] data;
|
||||
|
||||
/**
|
||||
* Create a new ByteList of default starting size.
|
||||
*/
|
||||
public ByteList() {
|
||||
this(10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new ByteList of a specified starting size. If the size is not
|
||||
* valid, the default starting size is used.
|
||||
* @param startSize the desired start size for the backing array
|
||||
*/
|
||||
public ByteList(int startSize) {
|
||||
startSize = startSize >= 0 ? startSize : 10;
|
||||
this.data = new byte[startSize];
|
||||
this.numBytes = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a byte to the end of this ByteList.
|
||||
* @param b the byte to add
|
||||
*/
|
||||
public void add(byte b) {
|
||||
if (this.numBytes >= this.data.length
|
||||
&& this.numBytes >= Integer.MAX_VALUE) {
|
||||
throw new RuntimeException("Array reached maximum size");
|
||||
}
|
||||
else if (this.numBytes >= this.data.length) {
|
||||
//grow the backing array
|
||||
long tmp = this.data.length * 2;
|
||||
int newSize = tmp > Integer.MAX_VALUE ?
|
||||
Integer.MAX_VALUE : (int)tmp;
|
||||
this.data = Arrays.copyOf(this.data, newSize);
|
||||
}
|
||||
this.data[this.numBytes] = b;
|
||||
this.numBytes++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a byte at a specified index in this ByteList.
|
||||
* @param i the index of the byte to get
|
||||
* @return the byte at index i
|
||||
*/
|
||||
public byte get(int i) {
|
||||
if (i < 0 || i > this.numBytes) {
|
||||
throw new ArrayIndexOutOfBoundsException(i);
|
||||
}
|
||||
return this.data[i];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of bytes that have been added to this ByteList.
|
||||
* @return the number of bytes added to this ByteList
|
||||
*/
|
||||
public int size() {
|
||||
return this.numBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of all the bytes added to this ByteList. This does not
|
||||
* affect the backing array.
|
||||
* @return an array of the bytes added to this ByteList
|
||||
*/
|
||||
public byte[] asArray() {
|
||||
return Arrays.copyOf(this.data, this.numBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear this ByteList of all added bytes.
|
||||
*/
|
||||
public void clear() {
|
||||
this.data = new byte[10];
|
||||
this.numBytes = 0;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,536 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012, Finn Kuusisto
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package kuusisto.tinysound.internal;
|
||||
|
||||
import kuusisto.tinysound.Music;
|
||||
import kuusisto.tinysound.TinySound;
|
||||
|
||||
/**
|
||||
* The MemMusic class is an implementation of the Music interface that stores
|
||||
* all audio data in memory for low latency.
|
||||
*
|
||||
* @author Finn Kuusisto
|
||||
*/
|
||||
public class MemMusic implements Music {
|
||||
|
||||
private byte[] left;
|
||||
private byte[] right;
|
||||
private Mixer mixer;
|
||||
private MusicReference reference;
|
||||
|
||||
/**
|
||||
* Construct a new MemMusic with the given music data and the Mixer with
|
||||
* which to register this MemMusic.
|
||||
* @param left left channel of music data
|
||||
* @param right right channel of music data
|
||||
* @param mixer Mixer with which this Music is registered
|
||||
*/
|
||||
public MemMusic(byte[] left, byte[] right, Mixer mixer) {
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
this.mixer = mixer;
|
||||
this.reference = new MemMusicReference(this.left, this.right, false,
|
||||
false, 0, 0, 1.0, 0.0);
|
||||
this.mixer.registerMusicReference(this.reference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Play this MemMusic and loop if specified.
|
||||
* @param loop if this MemMusic should loop
|
||||
*/
|
||||
@Override
|
||||
public void play(boolean loop) {
|
||||
this.reference.setLoop(loop);
|
||||
this.reference.setPlaying(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Play this MemMusic at the specified volume and loop if specified.
|
||||
* @param loop if this MemMusic should loop
|
||||
* @param volume the volume to play the this Music
|
||||
*/
|
||||
@Override
|
||||
public void play(boolean loop, double volume) {
|
||||
this.setLoop(loop);
|
||||
this.setVolume(volume);
|
||||
this.reference.setPlaying(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Play this MemMusic at the specified volume and pan, and loop if specified
|
||||
* .
|
||||
* @param loop if this MemMusic should loop
|
||||
* @param volume the volume to play the this MemMusic
|
||||
* @param pan the pan at which to play this MemMusic [-1.0,1.0], values
|
||||
* outside the valid range will be ignored
|
||||
*/
|
||||
@Override
|
||||
public void play(boolean loop, double volume, double pan) {
|
||||
this.setLoop(loop);
|
||||
this.setVolume(volume);
|
||||
this.setPan(pan);
|
||||
this.reference.setPlaying(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop playing this MemMusic and set its position to the beginning.
|
||||
*/
|
||||
@Override
|
||||
public void stop() {
|
||||
this.reference.setPlaying(false);
|
||||
this.rewind();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop playing this MemMusic and keep its current position.
|
||||
*/
|
||||
@Override
|
||||
public void pause() {
|
||||
this.reference.setPlaying(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Play this MemMusic from its current position.
|
||||
*/
|
||||
@Override
|
||||
public void resume() {
|
||||
this.reference.setPlaying(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this MemMusic's position to the beginning.
|
||||
*/
|
||||
@Override
|
||||
public void rewind() {
|
||||
this.reference.setPosition(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this MemMusic's position to the loop position.
|
||||
*/
|
||||
@Override
|
||||
public void rewindToLoopPosition() {
|
||||
long byteIndex = this.reference.getLoopPosition();
|
||||
this.reference.setPosition(byteIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this MemMusic is playing.
|
||||
* @return true if this MemMusic is playing
|
||||
*/
|
||||
@Override
|
||||
public boolean playing() {
|
||||
return this.reference.getPlaying();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this MemMusic has reached its end and is done playing.
|
||||
* @return true if this MemMusic has reached the end and is done playing
|
||||
*/
|
||||
@Override
|
||||
public boolean done() {
|
||||
return this.reference.done();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this MemMusic will loop.
|
||||
* @return true if this MemMusic will loop
|
||||
*/
|
||||
@Override
|
||||
public boolean loop() {
|
||||
return this.reference.getLoop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this MemMusic will loop.
|
||||
* @param loop whether this MemMusic will loop
|
||||
*/
|
||||
@Override
|
||||
public void setLoop(boolean loop) {
|
||||
this.reference.setLoop(loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the loop position of this MemMusic by sample frame.
|
||||
* @return loop position by sample frame
|
||||
*/
|
||||
@Override
|
||||
public int getLoopPositionByFrame() {
|
||||
int bytesPerChannelForFrame = TinySound.FORMAT.getFrameSize() /
|
||||
TinySound.FORMAT.getChannels();
|
||||
long byteIndex = this.reference.getLoopPosition();
|
||||
return (int)(byteIndex / bytesPerChannelForFrame);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the loop position of this MemMusic by seconds.
|
||||
* @return loop position by seconds
|
||||
*/
|
||||
@Override
|
||||
public double getLoopPositionBySeconds() {
|
||||
int bytesPerChannelForFrame = TinySound.FORMAT.getFrameSize() /
|
||||
TinySound.FORMAT.getChannels();
|
||||
long byteIndex = this.reference.getLoopPosition();
|
||||
return (byteIndex / (TinySound.FORMAT.getFrameRate() *
|
||||
bytesPerChannelForFrame));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the loop position of this MemMusic by sample frame.
|
||||
* @param frameIndex sample frame loop position to set
|
||||
*/
|
||||
@Override
|
||||
public void setLoopPositionByFrame(int frameIndex) {
|
||||
//get the byte index for a channel
|
||||
int bytesPerChannelForFrame = TinySound.FORMAT.getFrameSize() /
|
||||
TinySound.FORMAT.getChannels();
|
||||
long byteIndex = (long)(frameIndex * bytesPerChannelForFrame);
|
||||
this.reference.setLoopPosition(byteIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the loop position of this MemMusic by seconds.
|
||||
* @param seconds loop position to set by seconds
|
||||
*/
|
||||
@Override
|
||||
public void setLoopPositionBySeconds(double seconds) {
|
||||
//get the byte index for a channel
|
||||
int bytesPerChannelForFrame = TinySound.FORMAT.getFrameSize() /
|
||||
TinySound.FORMAT.getChannels();
|
||||
long byteIndex = (long)(seconds * TinySound.FORMAT.getFrameRate()) *
|
||||
bytesPerChannelForFrame;
|
||||
this.reference.setLoopPosition(byteIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the volume of this MemMusic.
|
||||
* @return volume of this MemMusic
|
||||
*/
|
||||
@Override
|
||||
public double getVolume() {
|
||||
return this.reference.getVolume();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the volume of this MemMusic.
|
||||
* @param volume the desired volume of this MemMusic
|
||||
*/
|
||||
@Override
|
||||
public void setVolume(double volume) {
|
||||
if (volume >= 0.0) {
|
||||
this.reference.setVolume(volume);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pan of this MemMusic.
|
||||
* @return pan of this MemMusic
|
||||
*/
|
||||
@Override
|
||||
public double getPan() {
|
||||
return this.reference.getPan();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the pan of this MemMusic. Must be between -1.0 (full pan left) and
|
||||
* 1.0 (full pan right). Values outside the valid range will be ignored.
|
||||
* @param pan the desired pan of this MemMusic
|
||||
*/
|
||||
@Override
|
||||
public void setPan(double pan) {
|
||||
if (pan >= -1.0 && pan <= 1.0) {
|
||||
this.reference.setPan(pan);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unload this MemMusic from the system. Attempts to use this MemMusic
|
||||
* after unloading will result in error.
|
||||
*/
|
||||
@Override
|
||||
public void unload() {
|
||||
//unregister the reference
|
||||
this.mixer.unRegisterMusicReference(this.reference);
|
||||
this.reference.dispose();
|
||||
this.mixer = null;
|
||||
this.left = null;
|
||||
this.right = null;
|
||||
this.reference = null;
|
||||
}
|
||||
|
||||
/////////////
|
||||
//Reference//
|
||||
/////////////
|
||||
|
||||
/**
|
||||
* The MemMusicReference is an implementation of the MusicReference
|
||||
* interface.
|
||||
*
|
||||
* @author Finn Kuusisto
|
||||
*/
|
||||
private static class MemMusicReference implements MusicReference {
|
||||
|
||||
private byte[] left;
|
||||
private byte[] right;
|
||||
private boolean playing;
|
||||
private boolean loop;
|
||||
private int loopPosition;
|
||||
private int position;
|
||||
private double volume;
|
||||
private double pan;
|
||||
|
||||
/**
|
||||
* Construct a new MemMusicReference with the given audio data and
|
||||
* settings.
|
||||
* @param left left channel of music data
|
||||
* @param right right channel of music data
|
||||
* @param playing true if the music should be playing
|
||||
* @param loop true if the music should loop
|
||||
* @param loopPosition byte index of the loop position in music data
|
||||
* @param position byte index position in music data
|
||||
* @param volume volume to play the music
|
||||
* @param pan pan to play the music
|
||||
*/
|
||||
public MemMusicReference(byte[] left, byte[] right, boolean playing,
|
||||
boolean loop, int loopPosition, int position, double volume,
|
||||
double pan) {
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
this.playing = playing;
|
||||
this.loop = loop;
|
||||
this.loopPosition = loopPosition;
|
||||
this.position = position;
|
||||
this.volume = volume;
|
||||
this.pan = pan;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the playing setting of this MemMusicReference.
|
||||
* @return true if this MemMusicReference is set to play
|
||||
*/
|
||||
@Override
|
||||
public synchronized boolean getPlaying() {
|
||||
return this.playing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the loop setting of this MemMusicReference.
|
||||
* @return true if this MemMusicReference is set to loop
|
||||
*/
|
||||
@Override
|
||||
public synchronized boolean getLoop() {
|
||||
return this.loop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the byte index of this MemMusicReference.
|
||||
* @return byte index of this MemMusicReference
|
||||
*/
|
||||
@Override
|
||||
public synchronized long getPosition() {
|
||||
return this.position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the loop-position byte index of this MemMusicReference.
|
||||
* @return loop-position byte index of this MemMusicReference
|
||||
*/
|
||||
@Override
|
||||
public synchronized long getLoopPosition() {
|
||||
return this.loopPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the volume of this MemMusicReference.
|
||||
* @return volume of this MemMusicReference
|
||||
*/
|
||||
@Override
|
||||
public synchronized double getVolume() {
|
||||
return this.volume;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pan of this MemMusicReference.
|
||||
* @return pan of this MemMusicReference
|
||||
*/
|
||||
@Override
|
||||
public synchronized double getPan() {
|
||||
return this.pan;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this MemMusicReference is playing.
|
||||
* @param playing whether this MemMusicReference is playing
|
||||
*/
|
||||
@Override
|
||||
public synchronized void setPlaying(boolean playing) {
|
||||
this.playing = playing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this MemMusicReference will loop.
|
||||
* @param loop whether this MemMusicReference will loop
|
||||
*/
|
||||
@Override
|
||||
public synchronized void setLoop(boolean loop) {
|
||||
this.loop = loop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the byte index of this MemMusicReference.
|
||||
* @param position the byte index to set
|
||||
*/
|
||||
@Override
|
||||
public synchronized void setPosition(long position) {
|
||||
if (position >= 0 && position < this.left.length) {
|
||||
this.position = (int)position;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the loop-position byte index of this MemMusicReference.
|
||||
* @param loopPosition the loop-position byte index to set
|
||||
*/
|
||||
@Override
|
||||
public synchronized void setLoopPosition(long loopPosition) {
|
||||
if (loopPosition >= 0 && loopPosition < this.left.length) {
|
||||
this.loopPosition = (int)loopPosition;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the volume of this MemMusicReference.
|
||||
* @param volume the desired volume of this MemMusicReference
|
||||
*/
|
||||
@Override
|
||||
public synchronized void setVolume(double volume) {
|
||||
this.volume = volume;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the pan of this MemMusicReference. Must be between -1.0 (full
|
||||
* pan left) and 1.0 (full pan right).
|
||||
* @param pan the desired pan of this MemMusicReference
|
||||
*/
|
||||
@Override
|
||||
public synchronized void setPan(double pan) {
|
||||
this.pan = pan;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of bytes remaining for each channel until the end of
|
||||
* this MemMusicReference.
|
||||
* @return number of bytes remaining for each channel
|
||||
*/
|
||||
@Override
|
||||
public synchronized long bytesAvailable() {
|
||||
return this.left.length - this.position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if there are no bytes remaining and play has stopped.
|
||||
* @return true if there are no bytes remaining and the reference is no
|
||||
* longer playing
|
||||
*/
|
||||
@Override
|
||||
public synchronized boolean done() {
|
||||
long available = this.left.length - this.position;
|
||||
return available <= 0 && !this.playing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip a specified number of bytes of the audio data.
|
||||
* @param num number of bytes to skip
|
||||
*/
|
||||
@Override
|
||||
public synchronized void skipBytes(long num) {
|
||||
for (int i = 0; i < num; i++) {
|
||||
this.position++;
|
||||
//wrap if looping, stop otherwise
|
||||
if (this.position >= this.left.length) {
|
||||
if (this.loop) {
|
||||
this.position = this.loopPosition;
|
||||
}
|
||||
else {
|
||||
this.playing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next two bytes from the music data in the specified
|
||||
* endianness.
|
||||
* @param data length-2 array to write in next two bytes from each
|
||||
* channel
|
||||
* @param bigEndian true if the bytes should be read big-endian
|
||||
*/
|
||||
@Override
|
||||
public synchronized void nextTwoBytes(int[] data, boolean bigEndian) {
|
||||
if (bigEndian) {
|
||||
//left
|
||||
data[0] = ((this.left[this.position] << 8) |
|
||||
(this.left[this.position + 1] & 0xFF));
|
||||
//right
|
||||
data[1] = ((this.right[this.position] << 8) |
|
||||
(this.right[this.position + 1] & 0xFF));
|
||||
}
|
||||
else {
|
||||
//left
|
||||
data[0] = ((this.left[this.position + 1] << 8) |
|
||||
(this.left[this.position] & 0xFF));
|
||||
//right
|
||||
data[1] = ((this.right[this.position + 1] << 8) |
|
||||
(this.right[this.position] & 0xFF));
|
||||
}
|
||||
this.position += 2;
|
||||
//wrap if looping, stop otherwise
|
||||
if (this.position >= this.left.length) {
|
||||
if (this.loop) {
|
||||
this.position = this.loopPosition;
|
||||
}
|
||||
else {
|
||||
this.playing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does any cleanup necessary to dispose of resources in use by this
|
||||
* MemMusicReference.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void dispose() {
|
||||
this.playing = false;
|
||||
this.position = this.left.length + 1;
|
||||
this.left = null;
|
||||
this.right = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,236 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012, Finn Kuusisto
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package kuusisto.tinysound.internal;
|
||||
|
||||
import kuusisto.tinysound.Sound;
|
||||
|
||||
/**
|
||||
* The MemSound class is an implementation of the Sound interface that stores
|
||||
* all audio data in memory for low latency.
|
||||
*
|
||||
* @author Finn Kuusisto
|
||||
*/
|
||||
public class MemSound implements Sound {
|
||||
|
||||
private byte[] left;
|
||||
private byte[] right;
|
||||
private Mixer mixer;
|
||||
private final int ID; //unique ID to match references
|
||||
|
||||
/**
|
||||
* Construct a new MemSound with the given data and Mixer which will handle
|
||||
* this MemSound.
|
||||
* @param left left channel of sound data
|
||||
* @param right right channel of sound data
|
||||
* @param mixer Mixer that will handle this MemSound
|
||||
* @param id unique ID of this MemSound
|
||||
*/
|
||||
public MemSound(byte[] left, byte[] right, Mixer mixer, int id) {
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
this.mixer = mixer;
|
||||
this.ID = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays this MemSound.
|
||||
*/
|
||||
@Override
|
||||
public void play() {
|
||||
this.play(1.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays this MemSound with a specified volume.
|
||||
* @param volume the volume at which to play this MemSound
|
||||
*/
|
||||
@Override
|
||||
public void play(double volume) {
|
||||
this.play(volume, 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays this MemSound with a specified volume and pan.
|
||||
* @param volume the volume at which to play this MemSound
|
||||
* @param pan the pan value to play this MemSound [-1.0,1.0], values outside
|
||||
* the valid range will assume no panning (0.0)
|
||||
*/
|
||||
@Override
|
||||
public void play(double volume, double pan) {
|
||||
//dispatch a sound refence to the mixer
|
||||
SoundReference ref = new MemSoundReference(this.left, this.right,
|
||||
volume, pan, this.ID);
|
||||
this.mixer.registerSoundReference(ref);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops this MemSound from playing. Note that if this MemSound was played
|
||||
* repeatedly in an overlapping fashion, all instances of this MemSound
|
||||
* still playing will be stopped.
|
||||
*/
|
||||
@Override
|
||||
public void stop() {
|
||||
this.mixer.unRegisterSoundReference(this.ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unloads this MemSound from the system. Attempts to use this MemSound
|
||||
* after unloading will result in error.
|
||||
*/
|
||||
@Override
|
||||
public void unload() {
|
||||
this.mixer.unRegisterSoundReference(this.ID);
|
||||
this.mixer = null;
|
||||
this.left = null;
|
||||
this.right = null;
|
||||
}
|
||||
|
||||
/////////////
|
||||
//Reference//
|
||||
/////////////
|
||||
|
||||
/**
|
||||
* The MemSoundReference is an implementation of the SoundReference
|
||||
* interface.
|
||||
*
|
||||
* @author Finn Kuusisto
|
||||
*/
|
||||
private static class MemSoundReference implements SoundReference {
|
||||
|
||||
public final int SOUND_ID; //parent MemSound
|
||||
|
||||
private byte[] left;
|
||||
private byte[] right;
|
||||
private int position;
|
||||
private double volume;
|
||||
private double pan;
|
||||
|
||||
/**
|
||||
* Construct a new MemSoundReference with the given reference data.
|
||||
* @param left left channel of sound data
|
||||
* @param right right channel of sound data
|
||||
* @param volume volume at which to play the sound
|
||||
* @param pan pan at which to play the sound
|
||||
* @param soundID ID of the MemSound for which this is a reference
|
||||
*/
|
||||
public MemSoundReference(byte[] left, byte[] right, double volume,
|
||||
double pan, int soundID) {
|
||||
this.left = left;
|
||||
this.right = right;
|
||||
this.volume = (volume >= 0.0) ? volume : 1.0;
|
||||
this.pan = (pan >= -1.0 && pan <= 1.0) ? pan : 0.0;
|
||||
this.position = 0;
|
||||
this.SOUND_ID = soundID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID of the MemSound that produced this MemSoundReference.
|
||||
* @return the ID of this MemSoundReference's parent MemSound
|
||||
*/
|
||||
@Override
|
||||
public int getSoundID() {
|
||||
return this.SOUND_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the volume of this MemSoundReference.
|
||||
* @return volume of this MemSoundReference
|
||||
*/
|
||||
@Override
|
||||
public double getVolume() {
|
||||
return this.volume;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the pan of this MemSoundReference.
|
||||
* @return pan of this MemSoundReference
|
||||
*/
|
||||
@Override
|
||||
public double getPan() {
|
||||
return this.pan;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of bytes remaining for each channel.
|
||||
* @return number of bytes remaining for each channel
|
||||
*/
|
||||
@Override
|
||||
public long bytesAvailable() {
|
||||
return this.left.length - this.position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip a specified number of bytes of the audio data.
|
||||
* @param num number of bytes to skip
|
||||
*/
|
||||
@Override
|
||||
public synchronized void skipBytes(long num) {
|
||||
this.position += num;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next two bytes from the sound data in the specified
|
||||
* endianness.
|
||||
* @param data length-2 array to write in next two bytes from each
|
||||
* channel
|
||||
* @param bigEndian true if the bytes should be read big-endian
|
||||
*/
|
||||
@Override
|
||||
public void nextTwoBytes(int[] data, boolean bigEndian) {
|
||||
if (bigEndian) {
|
||||
//left
|
||||
data[0] = ((this.left[this.position] << 8) |
|
||||
(this.left[this.position + 1] & 0xFF));
|
||||
//right
|
||||
data[1] = ((this.right[this.position] << 8) |
|
||||
(this.right[this.position + 1] & 0xFF));
|
||||
}
|
||||
else {
|
||||
//left
|
||||
data[0] = ((this.left[this.position + 1] << 8) |
|
||||
(this.left[this.position] & 0xFF));
|
||||
//right
|
||||
data[1] = ((this.right[this.position + 1] << 8) |
|
||||
(this.right[this.position] & 0xFF));
|
||||
}
|
||||
this.position += 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does any cleanup necessary to dispose of resources in use by this
|
||||
* MemSoundReference.
|
||||
*/
|
||||
@Override
|
||||
public void dispose() {
|
||||
this.position = this.left.length + 1;
|
||||
this.left = null;
|
||||
this.right = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,274 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012, Finn Kuusisto
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package kuusisto.tinysound.internal;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The Mixer class is what does the audio data mixing for the TinySound system.
|
||||
* Mixer is an internal class of the TinySound system and should be of no real
|
||||
* concern to the average user of TinySound.
|
||||
*
|
||||
* @author Finn Kuusisto
|
||||
*/
|
||||
public class Mixer {
|
||||
|
||||
private List<MusicReference> musics;
|
||||
private List<SoundReference> sounds;
|
||||
private double globalVolume;
|
||||
private int[] dataBuf; //buffer for reading sound data
|
||||
|
||||
/**
|
||||
* Construct a new Mixer for TinySound system.
|
||||
*/
|
||||
public Mixer() {
|
||||
this.musics = new ArrayList<MusicReference>();
|
||||
this.sounds = new ArrayList<SoundReference>();
|
||||
this.globalVolume = 1.0;
|
||||
this.dataBuf = new int[2]; //2-channel
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the global volume for this Mixer.
|
||||
* @return the global volume
|
||||
*/
|
||||
public synchronized double getVolume() {
|
||||
return this.globalVolume;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the global volume for this Mixer.
|
||||
* @param volume the global volume to set
|
||||
*/
|
||||
public synchronized void setVolume(double volume) {
|
||||
if (volume >= 0.0) {
|
||||
this.globalVolume = volume;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a MusicReference with this Mixer.
|
||||
* @param music MusicReference to be registered
|
||||
*/
|
||||
public synchronized void registerMusicReference(MusicReference music) {
|
||||
this.musics.add(music);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a SoundReference with this Mixer.
|
||||
* @param sound SoundReference to be registered
|
||||
*/
|
||||
public synchronized void registerSoundReference(SoundReference sound) {
|
||||
this.sounds.add(sound);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a MusicReference with this Mixer.
|
||||
* @param music MusicReference to be unregistered
|
||||
*/
|
||||
public synchronized void unRegisterMusicReference(MusicReference music) {
|
||||
this.musics.remove(music);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters all SoundReferences with a given soundID.
|
||||
* @param soundID ID of SoundReferences to be unregistered
|
||||
*/
|
||||
public synchronized void unRegisterSoundReference(int soundID) {
|
||||
//removal working backward is easier
|
||||
for (int i = this.sounds.size() - 1; i >= 0; i--) {
|
||||
if (this.sounds.get(i).getSoundID() == soundID) {
|
||||
this.sounds.remove(i).dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister all Music registered with this Mixer.
|
||||
*/
|
||||
public synchronized void clearMusic() {
|
||||
this.musics.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister all Sounds registered with this Mixer.
|
||||
*/
|
||||
public synchronized void clearSounds() {
|
||||
for (SoundReference s : this.sounds) {
|
||||
s.dispose();
|
||||
}
|
||||
this.sounds.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read bytes from this Mixer.
|
||||
* @param data the buffer to read the bytes into
|
||||
* @param offset the start index to read bytes into
|
||||
* @param length the maximum number of bytes that should be read
|
||||
* @return number of bytes read into buffer
|
||||
*/
|
||||
public synchronized int read(byte[] data, int offset, int length) {
|
||||
//************************************************//
|
||||
//assume little-endian, stereo, 16-bit, signed PCM//
|
||||
//************************************************//
|
||||
int numRead = 0;
|
||||
boolean bytesRead = true; //terminate early if out of bytes
|
||||
for (int i = offset; i < (length + offset) && bytesRead; i += 4) {
|
||||
//first assume we are done
|
||||
bytesRead = false;
|
||||
//need to track value across audio sources
|
||||
double leftValue = 0.0;
|
||||
double rightValue = 0.0;
|
||||
//go through all the music first
|
||||
for (int m = 0; m < this.musics.size(); m++) {
|
||||
MusicReference music = this.musics.get(m);
|
||||
//is the music playing and are there bytes available
|
||||
if (music.getPlaying() && music.bytesAvailable() > 0) {
|
||||
//add this music to the mix by volume (and global volume)
|
||||
music.nextTwoBytes(this.dataBuf, false);
|
||||
double volume = music.getVolume() * this.globalVolume;
|
||||
double leftCurr = (this.dataBuf[0] * volume);
|
||||
double rightCurr = (this.dataBuf[1] * volume);
|
||||
//do panning
|
||||
double pan = music.getPan();
|
||||
if (pan != 0.0) {
|
||||
double ll = (pan <= 0.0) ? 1.0 : (1.0 - pan);
|
||||
double lr = (pan <= 0.0) ? Math.abs(pan) : 0.0;
|
||||
double rl = (pan >= 0.0) ? pan : 0.0;
|
||||
double rr = (pan >= 0.0) ? 1.0 : (1.0 - Math.abs(pan));
|
||||
double tmpL = (ll * leftCurr) + (lr * rightCurr);
|
||||
double tmpR = (rl * leftCurr) + (rr * rightCurr);
|
||||
leftCurr = tmpL;
|
||||
rightCurr = tmpR;
|
||||
}
|
||||
//update the final left and right channels
|
||||
leftValue += leftCurr;
|
||||
rightValue += rightCurr;
|
||||
//we know we aren't done yet now
|
||||
bytesRead = true;
|
||||
}
|
||||
}
|
||||
//then go through all the sounds (backwards to remove completed)
|
||||
for (int s = this.sounds.size() - 1; s >= 0; s--) {
|
||||
SoundReference sound = this.sounds.get(s);
|
||||
//are there bytes available
|
||||
if (sound.bytesAvailable() > 0) {
|
||||
//add this sound to the mix by volume (and global volume)
|
||||
sound.nextTwoBytes(this.dataBuf, false);
|
||||
double volume = sound.getVolume() * this.globalVolume;
|
||||
double leftCurr = (this.dataBuf[0] * volume);
|
||||
double rightCurr = (this.dataBuf[1] * volume);
|
||||
//do panning
|
||||
//do panning
|
||||
double pan = sound.getPan();
|
||||
if (pan != 0.0) {
|
||||
double ll = (pan <= 0.0) ? 1.0 : (1.0 - pan);
|
||||
double lr = (pan <= 0.0) ? Math.abs(pan) : 0.0;
|
||||
double rl = (pan >= 0.0) ? pan : 0.0;
|
||||
double rr = (pan >= 0.0) ? 1.0 : (1.0 - Math.abs(pan));
|
||||
double tmpL = (ll * leftCurr) + (lr * rightCurr);
|
||||
double tmpR = (rl * leftCurr) + (rr * rightCurr);
|
||||
leftCurr = tmpL;
|
||||
rightCurr = tmpR;
|
||||
}
|
||||
//update the final left and right channels
|
||||
leftValue += leftCurr;
|
||||
rightValue += rightCurr;
|
||||
//we know we aren't done yet now
|
||||
bytesRead = true;
|
||||
//remove the reference if done
|
||||
if (sound.bytesAvailable() <= 0) {
|
||||
this.sounds.remove(s).dispose();
|
||||
}
|
||||
}
|
||||
else { //otherwise remove this reference
|
||||
this.sounds.remove(s).dispose();
|
||||
}
|
||||
}
|
||||
//if we actually read bytes, store in the buffer
|
||||
if (bytesRead) {
|
||||
int finalLeftValue = (int)leftValue;
|
||||
int finalRightValue = (int)rightValue;
|
||||
//clipping
|
||||
if (finalLeftValue > Short.MAX_VALUE) {
|
||||
finalLeftValue = Short.MAX_VALUE;
|
||||
}
|
||||
else if (finalLeftValue < Short.MIN_VALUE) {
|
||||
finalLeftValue = Short.MIN_VALUE;
|
||||
}
|
||||
if (finalRightValue > Short.MAX_VALUE) {
|
||||
finalRightValue = Short.MAX_VALUE;
|
||||
}
|
||||
else if (finalRightValue < Short.MIN_VALUE) {
|
||||
finalRightValue = Short.MIN_VALUE;
|
||||
}
|
||||
//left channel bytes
|
||||
data[i + 1] = (byte)((finalLeftValue >> 8) & 0xFF); //MSB
|
||||
data[i] = (byte)(finalLeftValue & 0xFF); //LSB
|
||||
//then right channel bytes
|
||||
data[i + 3] = (byte)((finalRightValue >> 8) & 0xFF); //MSB
|
||||
data[i + 2] = (byte)(finalRightValue & 0xFF); //LSB
|
||||
numRead += 4;
|
||||
}
|
||||
}
|
||||
return numRead;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip specified number of bytes of all audio in this Mixer.
|
||||
* @param numBytes the number of bytes to skip
|
||||
*/
|
||||
public synchronized void skip(int numBytes) {
|
||||
//go through all the music first
|
||||
for (int m = 0; m < this.musics.size(); m++) {
|
||||
MusicReference music = this.musics.get(m);
|
||||
//is the music playing and are there bytes available
|
||||
if (music.getPlaying() && music.bytesAvailable() > 0) {
|
||||
//skip the bytes
|
||||
music.skipBytes(numBytes);
|
||||
}
|
||||
}
|
||||
//then go through all the sounds (backwards to remove completed)
|
||||
for (int s = this.sounds.size() - 1; s >= 0; s--) {
|
||||
SoundReference sound = this.sounds.get(s);
|
||||
//are there bytes available
|
||||
if (sound.bytesAvailable() > 0) {
|
||||
//skip the bytes
|
||||
sound.skipBytes(numBytes);
|
||||
//remove the reference if done
|
||||
if (sound.bytesAvailable() <= 0) {
|
||||
this.sounds.remove(s).dispose();
|
||||
}
|
||||
}
|
||||
else { //otherwise remove this reference
|
||||
this.sounds.remove(s).dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,144 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012, Finn Kuusisto
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package kuusisto.tinysound.internal;
|
||||
|
||||
/**
|
||||
* The MusicReference interface is the Mixer's interface to the audio data of a
|
||||
* Music object. MusicReference is an internal interface of the TinySound
|
||||
* system and should be of no real concern to the average user of TinySound.
|
||||
*
|
||||
* @author Finn Kuusisto
|
||||
*/
|
||||
public interface MusicReference {
|
||||
|
||||
/**
|
||||
* Get the playing setting of this MusicReference.
|
||||
* @return true if this MusicReference is set to play
|
||||
*/
|
||||
public boolean getPlaying();
|
||||
|
||||
/**
|
||||
* Get the loop setting of this MusicReference.
|
||||
* @return true if this MusicReference is set to loop
|
||||
*/
|
||||
public boolean getLoop();
|
||||
|
||||
/**
|
||||
* Get the byte index of this MusicReference.
|
||||
* @return byte index of this MusicReference
|
||||
*/
|
||||
public long getPosition();
|
||||
|
||||
/**
|
||||
* Get the loop-position byte index of this MusicReference.
|
||||
* @return loop-position byte index of this MusicReference
|
||||
*/
|
||||
public long getLoopPosition();
|
||||
|
||||
/**
|
||||
* Get the volume of this MusicReference.
|
||||
* @return volume of this MusicReference
|
||||
*/
|
||||
public double getVolume();
|
||||
|
||||
/**
|
||||
* Get the pan of this MusicReference.
|
||||
* @return pan of this MusicReference
|
||||
*/
|
||||
public double getPan();
|
||||
|
||||
/**
|
||||
* Set whether this MusicReference is playing.
|
||||
* @param playing whether this MusicReference is playing
|
||||
*/
|
||||
public void setPlaying(boolean playing);
|
||||
|
||||
/**
|
||||
* Set whether this MusicReference will loop.
|
||||
* @param loop whether this MusicReference will loop
|
||||
*/
|
||||
public void setLoop(boolean loop);
|
||||
|
||||
/**
|
||||
* Set the byte index of this MusicReference.
|
||||
* @param position the byte index to set
|
||||
*/
|
||||
public void setPosition(long position);
|
||||
|
||||
/**
|
||||
* Set the loop-position byte index of this MusicReference.
|
||||
* @param loopPosition the loop-position byte index to set
|
||||
*/
|
||||
public void setLoopPosition(long loopPosition);
|
||||
|
||||
/**
|
||||
* Set the volume of this MusicReference.
|
||||
* @param volume the desired volume of this MusicReference
|
||||
*/
|
||||
public void setVolume(double volume);
|
||||
|
||||
/**
|
||||
* Set the pan of this MusicReference. Must be between -1.0 (full pan left)
|
||||
* and 1.0 (full pan right).
|
||||
* @param pan the desired pan of this MusicReference
|
||||
*/
|
||||
public void setPan(double pan);
|
||||
|
||||
/**
|
||||
* Get the number of bytes remaining for each channel until the end of this
|
||||
* Music.
|
||||
* @return number of bytes remaining for each channel
|
||||
*/
|
||||
public long bytesAvailable();
|
||||
|
||||
/**
|
||||
* Determine if there are no bytes remaining and play has stopped.
|
||||
* @return true if there are no bytes remaining and the reference is no
|
||||
* longer playing
|
||||
*/
|
||||
public boolean done();
|
||||
|
||||
/**
|
||||
* Skip a specified number of bytes of the audio data.
|
||||
* @param num number of bytes to skip
|
||||
*/
|
||||
public void skipBytes(long num);
|
||||
|
||||
/**
|
||||
* Get the next two bytes from the music data in the specified endianness.
|
||||
* @param data length-2 array to write in next two bytes from each channel
|
||||
* @param bigEndian true if the bytes should be read big-endian
|
||||
*/
|
||||
public void nextTwoBytes(int[] data, boolean bigEndian);
|
||||
|
||||
/**
|
||||
* Does any cleanup necessary to dispose of resources in use by this
|
||||
* MusicReference.
|
||||
*/
|
||||
public void dispose();
|
||||
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012, Finn Kuusisto
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package kuusisto.tinysound.internal;
|
||||
|
||||
/**
|
||||
* The SoundReference interface is the Mixer's interface to the audio data of a
|
||||
* Sound object. SoundReference is an internal interface of the TinySound
|
||||
* system and should be of no real concern to the average user of TinySound.
|
||||
*
|
||||
* @author Finn Kuusisto
|
||||
*/
|
||||
public interface SoundReference {
|
||||
|
||||
/**
|
||||
* Get the ID of the Sound that produced this SoundReference.
|
||||
* @return the ID of this SoundReference's parent Sound
|
||||
*/
|
||||
public int getSoundID();
|
||||
|
||||
/**
|
||||
* Gets the volume of this SoundReference.
|
||||
* @return volume of this SoundReference
|
||||
*/
|
||||
public double getVolume();
|
||||
|
||||
/**
|
||||
* Gets the pan of this SoundReference.
|
||||
* @return pan of this SoundReference
|
||||
*/
|
||||
public double getPan();
|
||||
|
||||
/**
|
||||
* Get the number of bytes remaining for each channel.
|
||||
* @return number of bytes remaining for each channel
|
||||
*/
|
||||
public long bytesAvailable();
|
||||
|
||||
/**
|
||||
* Skip a specified number of bytes of the audio data.
|
||||
* @param num number of bytes to skip
|
||||
*/
|
||||
public void skipBytes(long num);
|
||||
|
||||
/**
|
||||
* Get the next two bytes from the sound data in the specified endianness.
|
||||
* @param data length-2 array to write in next two bytes from each channel
|
||||
* @param bigEndian true if the bytes should be read big-endian
|
||||
*/
|
||||
public void nextTwoBytes(int[] data, boolean bigEndian);
|
||||
|
||||
/**
|
||||
* Does any cleanup necessary to dispose of resources in use by this
|
||||
* SoundReference.
|
||||
*/
|
||||
public void dispose();
|
||||
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012, Finn Kuusisto
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package kuusisto.tinysound.internal;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
public class StreamInfo {
|
||||
|
||||
public final URL URL;
|
||||
public final long NUM_BYTES_PER_CHANNEL;
|
||||
|
||||
public StreamInfo(URL url, long numBytesPerChannel) {
|
||||
this.URL = url;
|
||||
this.NUM_BYTES_PER_CHANNEL = numBytesPerChannel;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,632 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012, Finn Kuusisto
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package kuusisto.tinysound.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
|
||||
import kuusisto.tinysound.Music;
|
||||
import kuusisto.tinysound.TinySound;
|
||||
|
||||
/**
|
||||
* The StreamMusic class is an implementation of the Music interface that
|
||||
* streams audio data from a temporary file to reduce memory overhead.
|
||||
*
|
||||
* @author Finn Kuusisto
|
||||
*/
|
||||
public class StreamMusic implements Music {
|
||||
|
||||
private URL dataURL;
|
||||
private Mixer mixer;
|
||||
private MusicReference reference;
|
||||
|
||||
/**
|
||||
* Construct a new StreamMusic with the given data and the Mixer with which
|
||||
* to register this StreamMusic.
|
||||
* @param dataURL URL of the temporary file containing audio data
|
||||
* @param numBytesPerChannel the total number of bytes for each channel in
|
||||
* the file
|
||||
* @param mixer Mixer that will handle this StreamSound
|
||||
* @throws IOException if a stream cannot be opened from the URL
|
||||
*/
|
||||
public StreamMusic(URL dataURL, long numBytesPerChannel, Mixer mixer)
|
||||
throws IOException {
|
||||
this.dataURL = dataURL;
|
||||
this.mixer = mixer;
|
||||
this.reference = new StreamMusicReference(this.dataURL, false, false, 0,
|
||||
0, numBytesPerChannel, 1.0, 0.0);
|
||||
this.mixer.registerMusicReference(this.reference);
|
||||
}
|
||||
|
||||
/**
|
||||
* Play this StreamMusic and loop if specified.
|
||||
* @param loop if this StreamMusic should loop
|
||||
*/
|
||||
@Override
|
||||
public void play(boolean loop) {
|
||||
this.reference.setLoop(loop);
|
||||
this.reference.setPlaying(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Play this StreamMusic at the specified volume and loop if specified.
|
||||
* @param loop if this StreamMusic should loop
|
||||
* @param volume the volume to play the this StreamMusic
|
||||
*/
|
||||
@Override
|
||||
public void play(boolean loop, double volume) {
|
||||
this.setLoop(loop);
|
||||
this.setVolume(volume);
|
||||
this.reference.setPlaying(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Play this StreamMusic at the specified volume and pan, and loop if
|
||||
* specified.
|
||||
* @param loop if this StreamMusic should loop
|
||||
* @param volume the volume to play the this StreamMusic
|
||||
* @param pan the pan at which to play this StreamMusic [-1.0,1.0], values
|
||||
* outside the valid range will be ignored
|
||||
*/
|
||||
@Override
|
||||
public void play(boolean loop, double volume, double pan) {
|
||||
this.setLoop(loop);
|
||||
this.setVolume(volume);
|
||||
this.setPan(pan);
|
||||
this.reference.setPlaying(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop playing this StreamMusic and set its position to the beginning.
|
||||
*/
|
||||
@Override
|
||||
public void stop() {
|
||||
this.reference.setPlaying(false);
|
||||
this.rewind();
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop playing this StreamMusic and keep its current position.
|
||||
*/
|
||||
@Override
|
||||
public void pause() {
|
||||
this.reference.setPlaying(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Play this StreamMusic from its current position.
|
||||
*/
|
||||
@Override
|
||||
public void resume() {
|
||||
this.reference.setPlaying(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this StreamMusic's position to the beginning.
|
||||
*/
|
||||
@Override
|
||||
public void rewind() {
|
||||
this.reference.setPosition(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set this StreamMusic's position to the loop position.
|
||||
*/
|
||||
@Override
|
||||
public void rewindToLoopPosition() {
|
||||
long byteIndex = this.reference.getLoopPosition();
|
||||
this.reference.setPosition(byteIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this StreamMusic is playing.
|
||||
* @return true if this StreamMusic is playing
|
||||
*/
|
||||
@Override
|
||||
public boolean playing() {
|
||||
return this.reference.getPlaying();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this StreamMusic has reached its end and is done playing.
|
||||
* @return true if this StreamMusic has reached the end and is done playing
|
||||
*/
|
||||
@Override
|
||||
public boolean done() {
|
||||
return this.reference.done();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if this StreamMusic will loop.
|
||||
* @return true if this StreamMusic will loop
|
||||
*/
|
||||
@Override
|
||||
public boolean loop() {
|
||||
return this.reference.getLoop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this StreamMusic will loop.
|
||||
* @param loop whether this StreamMusic will loop
|
||||
*/
|
||||
@Override
|
||||
public void setLoop(boolean loop) {
|
||||
this.reference.setLoop(loop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the loop position of this StreamMusic by sample frame.
|
||||
* @return loop position by sample frame
|
||||
*/
|
||||
@Override
|
||||
public int getLoopPositionByFrame() {
|
||||
int bytesPerChannelForFrame = TinySound.FORMAT.getFrameSize() /
|
||||
TinySound.FORMAT.getChannels();
|
||||
long byteIndex = this.reference.getLoopPosition();
|
||||
return (int)(byteIndex / bytesPerChannelForFrame);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the loop position of this StreamMusic by seconds.
|
||||
* @return loop position by seconds
|
||||
*/
|
||||
@Override
|
||||
public double getLoopPositionBySeconds() {
|
||||
int bytesPerChannelForFrame = TinySound.FORMAT.getFrameSize() /
|
||||
TinySound.FORMAT.getChannels();
|
||||
long byteIndex = this.reference.getLoopPosition();
|
||||
return (byteIndex / (TinySound.FORMAT.getFrameRate() *
|
||||
bytesPerChannelForFrame));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the loop position of this StreamMusic by sample frame.
|
||||
* @param frameIndex sample frame loop position to set
|
||||
*/
|
||||
@Override
|
||||
public void setLoopPositionByFrame(int frameIndex) {
|
||||
//get the byte index for a channel
|
||||
int bytesPerChannelForFrame = TinySound.FORMAT.getFrameSize() /
|
||||
TinySound.FORMAT.getChannels();
|
||||
long byteIndex = (long)(frameIndex * bytesPerChannelForFrame);
|
||||
this.reference.setLoopPosition(byteIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the loop position of this StreamMusic by seconds.
|
||||
* @param seconds loop position to set by seconds
|
||||
*/
|
||||
@Override
|
||||
public void setLoopPositionBySeconds(double seconds) {
|
||||
//get the byte index for a channel
|
||||
int bytesPerChannelForFrame = TinySound.FORMAT.getFrameSize() /
|
||||
TinySound.FORMAT.getChannels();
|
||||
long byteIndex = (long)(seconds * TinySound.FORMAT.getFrameRate()) *
|
||||
bytesPerChannelForFrame;
|
||||
this.reference.setLoopPosition(byteIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the volume of this StreamMusic.
|
||||
* @return volume of this StreamMusic
|
||||
*/
|
||||
@Override
|
||||
public double getVolume() {
|
||||
return this.reference.getVolume();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the volume of this StreamMusic.
|
||||
* @param volume the desired volume of this StreamMusic
|
||||
*/
|
||||
@Override
|
||||
public void setVolume(double volume) {
|
||||
if (volume >= 0.0) {
|
||||
this.reference.setVolume(volume);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pan of this StreamMusic.
|
||||
* @return pan of this StreamMusic
|
||||
*/
|
||||
@Override
|
||||
public double getPan() {
|
||||
return this.reference.getPan();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the pan of this StreamMusic. Must be between -1.0 (full pan left)
|
||||
* and 1.0 (full pan right). Values outside the valid range will be ignored
|
||||
* .
|
||||
* @param pan the desired pan of this StreamMusic
|
||||
*/
|
||||
@Override
|
||||
public void setPan(double pan) {
|
||||
if (pan >= -1.0 && pan <= 1.0) {
|
||||
this.reference.setPan(pan);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unload this MemMusic from the system. Attempts to use this MemMusic
|
||||
* after unloading will result in error.
|
||||
*/
|
||||
@Override
|
||||
public void unload() {
|
||||
//unregister the reference
|
||||
this.mixer.unRegisterMusicReference(this.reference);
|
||||
this.reference.dispose();
|
||||
this.mixer = null;
|
||||
this.dataURL = null;
|
||||
this.reference = null;
|
||||
}
|
||||
|
||||
/////////////
|
||||
//Reference//
|
||||
/////////////
|
||||
|
||||
/**
|
||||
* The StreamMusicReference is an implementation of the MusicReference
|
||||
* interface.
|
||||
*/
|
||||
private static class StreamMusicReference implements MusicReference {
|
||||
|
||||
private URL url;
|
||||
private InputStream data;
|
||||
private long numBytesPerChannel; //not per frame, but the whole sound
|
||||
private byte[] buf;
|
||||
private byte[] skipBuf;
|
||||
private boolean playing;
|
||||
private boolean loop;
|
||||
private long loopPosition;
|
||||
private long position;
|
||||
private double volume;
|
||||
private double pan;
|
||||
|
||||
/**
|
||||
* Constructs a new StreamMusicReference with the given audio data and
|
||||
* settings.
|
||||
* @param dataURL URL of the temporary file containing audio data
|
||||
* @param playing true if the music should be playing
|
||||
* @param loop true if the music should loop
|
||||
* @param loopPosition byte index of the loop position in music data
|
||||
* @param position byte index position in music data
|
||||
* @param numBytesPerChannel the total number of bytes for each channel
|
||||
* in the file
|
||||
* @param volume volume to play the music
|
||||
* @param pan pan to play the music
|
||||
* @throws IOException if a stream cannot be opened from the URL
|
||||
*/
|
||||
public StreamMusicReference(URL dataURL, boolean playing, boolean loop,
|
||||
long loopPosition, long position, long numBytesPerChannel,
|
||||
double volume, double pan) throws IOException {
|
||||
this.url = dataURL;
|
||||
this.playing = playing;
|
||||
this.loop = loop;
|
||||
this.loopPosition = loopPosition;
|
||||
this.position = position;
|
||||
this.numBytesPerChannel = numBytesPerChannel;
|
||||
this.volume = volume;
|
||||
this.pan = pan;
|
||||
this.buf = new byte[4];
|
||||
this.skipBuf = new byte[50];
|
||||
//now get the data stream
|
||||
this.data = this.url.openStream();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the playing setting of this StreamMusicReference.
|
||||
* @return true if this StreamMusicReference is set to play
|
||||
*/
|
||||
@Override
|
||||
public synchronized boolean getPlaying() {
|
||||
return this.playing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the loop setting of this StreamMusicReference.
|
||||
* @return true if this StreamMusicReference is set to loop
|
||||
*/
|
||||
@Override
|
||||
public synchronized boolean getLoop() {
|
||||
return this.loop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the byte index of this StreamMusicReference.
|
||||
* @return byte index of this StreamMusicReference
|
||||
*/
|
||||
@Override
|
||||
public synchronized long getPosition() {
|
||||
return this.position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the loop-position byte index of this StreamMusicReference.
|
||||
* @return loop-position byte index of this StreamMusicReference
|
||||
*/
|
||||
@Override
|
||||
public synchronized long getLoopPosition() {
|
||||
return this.loopPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the volume of this StreamMusicReference.
|
||||
* @return volume of this StreamMusicReference
|
||||
*/
|
||||
@Override
|
||||
public synchronized double getVolume() {
|
||||
return this.volume;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pan of this StreamMusicReference.
|
||||
* @return pan of this StreamMusicReference
|
||||
*/
|
||||
@Override
|
||||
public synchronized double getPan() {
|
||||
return this.pan;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this StreamMusicReference is playing.
|
||||
* @param playing whether this StreamMusicReference is playing
|
||||
*/
|
||||
@Override
|
||||
public synchronized void setPlaying(boolean playing) {
|
||||
this.playing = playing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether this StreamMusicReference will loop.
|
||||
* @param loop whether this StreamMusicReference will loop
|
||||
*/
|
||||
@Override
|
||||
public synchronized void setLoop(boolean loop) {
|
||||
this.loop = loop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the byte index of this StreamMusicReference.
|
||||
* @param position the byte index to set
|
||||
*/
|
||||
@Override
|
||||
public synchronized void setPosition(long position) {
|
||||
if (position >= 0 && position < this.numBytesPerChannel) {
|
||||
//if it's later, skip
|
||||
if (position >= this.position) {
|
||||
this.skipBytes(position - this.position);
|
||||
}
|
||||
else { //otherwise skip from the beginning
|
||||
//first close our current stream
|
||||
try {
|
||||
this.data.close();
|
||||
} catch (IOException e) {
|
||||
//whatever...
|
||||
}
|
||||
//open a new stream
|
||||
try {
|
||||
this.data = this.url.openStream();
|
||||
this.position = 0;
|
||||
this.skipBytes(position);
|
||||
} catch (IOException e) {
|
||||
System.err.println("Failed to open stream for StreamMusic");
|
||||
this.playing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the loop-position byte index of this StreamMusicReference.
|
||||
* @param loopPosition the loop-position byte index to set
|
||||
*/
|
||||
@Override
|
||||
public synchronized void setLoopPosition(long loopPosition) {
|
||||
if (loopPosition >= 0 && loopPosition < this.numBytesPerChannel) {
|
||||
this.loopPosition = loopPosition;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the volume of this StreamMusicReference.
|
||||
* @param volume the desired volume of this StreamMusicReference
|
||||
*/
|
||||
@Override
|
||||
public synchronized void setVolume(double volume) {
|
||||
this.volume = volume;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the pan of this StreamMusicReference. Must be between -1.0 (full
|
||||
* pan left) and 1.0 (full pan right).
|
||||
* @param pan the desired pan of this StreamMusicReference
|
||||
*/
|
||||
@Override
|
||||
public synchronized void setPan(double pan) {
|
||||
this.pan = pan;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of bytes remaining for each channel until the end of
|
||||
* this StreamMusicReference.
|
||||
* @return number of bytes remaining for each channel
|
||||
*/
|
||||
@Override
|
||||
public synchronized long bytesAvailable() {
|
||||
return this.numBytesPerChannel - this.position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if there are no bytes remaining and play has stopped.
|
||||
* @return true if there are no bytes remaining and the reference is no
|
||||
* longer playing
|
||||
*/
|
||||
@Override
|
||||
public synchronized boolean done() {
|
||||
long available = this.numBytesPerChannel - this.position;
|
||||
return available <= 0 && !this.playing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip a specified number of bytes of the audio data.
|
||||
* @param num number of bytes to skip
|
||||
*/
|
||||
@Override
|
||||
public synchronized void skipBytes(long num) {
|
||||
//couple of shortcuts if we are going to complete the stream
|
||||
if ((this.position + num) >= this.numBytesPerChannel) {
|
||||
//if we're not looping, nothing special needs to happen
|
||||
if (!this.loop) {
|
||||
this.position += num;
|
||||
//now stop since we're out
|
||||
this.playing = false;
|
||||
return;
|
||||
}
|
||||
else {
|
||||
//compute the next position
|
||||
long loopLength = this.numBytesPerChannel -
|
||||
this.loopPosition;
|
||||
long bytesOver = (this.position + num) -
|
||||
this.numBytesPerChannel;
|
||||
long nextPosition = this.loopPosition +
|
||||
(bytesOver % loopLength);
|
||||
//and set us there
|
||||
this.setPosition(nextPosition);
|
||||
return;
|
||||
}
|
||||
}
|
||||
//this is the number of bytes to skip per channel, so double it
|
||||
long numSkip = num * 2;
|
||||
//spin read since skip is not always supported apparently and won't
|
||||
//guarantee a correct skip amount
|
||||
int tmpRead = 0;
|
||||
int numRead = 0;
|
||||
try {
|
||||
while (numRead < numSkip && tmpRead != -1) {
|
||||
//determine safe length to read
|
||||
long remaining = numSkip - numRead;
|
||||
int len = remaining > this.skipBuf.length ?
|
||||
this.skipBuf.length : (int)remaining;
|
||||
//and read
|
||||
tmpRead = this.data.read(this.skipBuf, 0, len);
|
||||
numRead += tmpRead;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
//hmm... I guess invalidate this reference
|
||||
this.position = this.numBytesPerChannel;
|
||||
this.playing = false;
|
||||
}
|
||||
//increment the position appropriately
|
||||
if (tmpRead == -1) { //reached end of file in the middle of reading
|
||||
this.position = this.numBytesPerChannel;
|
||||
this.playing = false;
|
||||
}
|
||||
else {
|
||||
this.position += num;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next two bytes from the music data in the specified
|
||||
* endianness.
|
||||
* @param data length-2 array to write in next two bytes from each
|
||||
* channel
|
||||
* @param bigEndian true if the bytes should be read big-endian
|
||||
*/
|
||||
@Override
|
||||
public synchronized void nextTwoBytes(int[] data, boolean bigEndian) {
|
||||
//try to read audio data
|
||||
int tmpRead = 0;
|
||||
int numRead = 0;
|
||||
try {
|
||||
while (numRead < this.buf.length && tmpRead != -1) {
|
||||
tmpRead = this.data.read(this.buf, numRead,
|
||||
this.buf.length - numRead);
|
||||
numRead += tmpRead;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
//this shouldn't happen if the bytes were written correctly to
|
||||
//the temp file, but this sound should now be invalid at least
|
||||
this.position = this.numBytesPerChannel;
|
||||
System.err.println("Failed reading bytes for stream sound");
|
||||
}
|
||||
//copy the values into the caller buffer
|
||||
if (bigEndian) {
|
||||
//left
|
||||
data[0] = ((this.buf[0] << 8) |
|
||||
(this.buf[1] & 0xFF));
|
||||
//right
|
||||
data[1] = ((this.buf[2] << 8) |
|
||||
(this.buf[3] & 0xFF));
|
||||
}
|
||||
else {
|
||||
//left
|
||||
data[0] = ((this.buf[1] << 8) |
|
||||
(this.buf[0] & 0xFF));
|
||||
//right
|
||||
data[1] = ((this.buf[3] << 8) |
|
||||
(this.buf[2] & 0xFF));
|
||||
}
|
||||
//increment the position appropriately
|
||||
if (tmpRead == -1) { //reached end of file in the middle of reading
|
||||
//this should never happen
|
||||
this.position = this.numBytesPerChannel;
|
||||
}
|
||||
else {
|
||||
this.position += 2;
|
||||
}
|
||||
//wrap if looping, stop otherwise
|
||||
if (this.position >= this.numBytesPerChannel) {
|
||||
if (this.loop) {
|
||||
this.setPosition(this.loopPosition);
|
||||
}
|
||||
else {
|
||||
this.playing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does any cleanup necessary to dispose of resources in use by this
|
||||
* StreamMusicReference.
|
||||
*/
|
||||
@Override
|
||||
public synchronized void dispose() {
|
||||
this.playing = false;
|
||||
this.position = this.numBytesPerChannel;
|
||||
this.url = null;
|
||||
try {
|
||||
this.data.close();
|
||||
} catch (IOException e) {
|
||||
//whatever... this should never happen
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,314 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012, Finn Kuusisto
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package kuusisto.tinysound.internal;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
|
||||
import kuusisto.tinysound.Sound;
|
||||
|
||||
/**
|
||||
* Thes StreamSound class is an implementation of the Sound interface that
|
||||
* streams audio data from a temporary file to reduce memory overhead.
|
||||
*
|
||||
* @author Finn Kuusisto
|
||||
*/
|
||||
public class StreamSound implements Sound {
|
||||
|
||||
private URL dataURL;
|
||||
private long numBytesPerChannel;
|
||||
private Mixer mixer;
|
||||
private final int ID;
|
||||
|
||||
/**
|
||||
* Construct a new StreamSound with the given data and Mixer which will
|
||||
* handle this StreamSound.
|
||||
* @param dataURL URL of the temporary file containing audio data
|
||||
* @param numBytesPerChannel the total number of bytes for each channel in
|
||||
* the file
|
||||
* @param mixer Mixer that will handle this StreamSound
|
||||
* @param id unique ID of this StreamSound
|
||||
* @throws IOException if a stream cannot be opened from the URL
|
||||
*/
|
||||
public StreamSound(URL dataURL, long numBytesPerChannel, Mixer mixer,
|
||||
int id) throws IOException {
|
||||
this.dataURL = dataURL;
|
||||
this.numBytesPerChannel = numBytesPerChannel;
|
||||
this.mixer = mixer;
|
||||
this.ID = id;
|
||||
//open and close a stream to check for immediate issues
|
||||
InputStream temp = this.dataURL.openStream();
|
||||
temp.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays this StreamSound.
|
||||
*/
|
||||
@Override
|
||||
public void play() {
|
||||
this.play(1.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays this StreamSound with a specified volume.
|
||||
* @param volume the volume at which to play this StreamSound
|
||||
*/
|
||||
@Override
|
||||
public void play(double volume) {
|
||||
this.play(volume, 0.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Plays this MemSound with a specified volume and pan.
|
||||
* @param volume the volume at which to play this MemSound
|
||||
* @param pan the pan value to play this MemSound [-1.0,1.0], values outside
|
||||
* the valid range will assume no panning (0.0)
|
||||
*/
|
||||
@Override
|
||||
public void play(double volume, double pan) {
|
||||
//dispatch a SoundReference to the mixer
|
||||
SoundReference ref;
|
||||
try {
|
||||
ref = new StreamSoundReference(this.dataURL.openStream(),
|
||||
this.numBytesPerChannel, volume, pan, this.ID);
|
||||
this.mixer.registerSoundReference(ref);
|
||||
} catch (IOException e) {
|
||||
System.err.println("Failed to open stream for Sound");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops this StreamSound from playing. Note that if this StreamSound was
|
||||
* played repeatedly in an overlapping fashion, all instances of this
|
||||
* StreamSound still playing will be stopped.
|
||||
*/
|
||||
@Override
|
||||
public void stop() {
|
||||
this.mixer.unRegisterSoundReference(this.ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unloads this StreamSound from the system. Attempts to use this
|
||||
* StreamSound after unloading will result in error.
|
||||
*/
|
||||
@Override
|
||||
public void unload() {
|
||||
this.mixer.unRegisterSoundReference(this.ID);
|
||||
this.mixer = null;
|
||||
this.dataURL = null;
|
||||
}
|
||||
|
||||
/////////////
|
||||
//Reference//
|
||||
/////////////
|
||||
|
||||
/**
|
||||
* The StreamSoundReference class is an implementation of the SoundReference
|
||||
* interface.
|
||||
*
|
||||
* @Finn Kuusisto
|
||||
*/
|
||||
private static class StreamSoundReference implements SoundReference {
|
||||
|
||||
public final int SOUND_ID;
|
||||
|
||||
private InputStream data;
|
||||
private long numBytesPerChannel; //not per frame, but the whole sound
|
||||
private long position;
|
||||
private double volume;
|
||||
private double pan;
|
||||
private byte[] buf;
|
||||
private byte[] skipBuf;
|
||||
|
||||
/**
|
||||
* Construct a new StreamSoundReference with the given reference data.
|
||||
* @param data the stream of the audio data
|
||||
* @param numBytesPerChannel the total number of bytes for each channel
|
||||
* in the stream
|
||||
* @param volume volume at which to play the sound
|
||||
* @param pan pan at which to play the sound
|
||||
* @param soundID ID of the StreamSound for which this is a reference
|
||||
*/
|
||||
public StreamSoundReference(InputStream data, long numBytesPerChannel,
|
||||
double volume, double pan, int soundID) {
|
||||
this.data = data;
|
||||
this.numBytesPerChannel = numBytesPerChannel;
|
||||
this.volume = (volume >= 0.0) ? volume : 1.0;
|
||||
this.pan = (pan >= -1.0 && pan <= 1.0) ? pan : 0.0;
|
||||
this.position = 0;
|
||||
this.buf = new byte[4];
|
||||
this.skipBuf = new byte[20];
|
||||
this.SOUND_ID = soundID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ID of the StreamSound that produced this
|
||||
* StreamSoundReference.
|
||||
* @return the ID of this StreamSoundReference's parent StreamSound
|
||||
*/
|
||||
@Override
|
||||
public int getSoundID() {
|
||||
return this.SOUND_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the volume of this StreamSoundReference.
|
||||
* @return volume of this StreamSoundReference
|
||||
*/
|
||||
@Override
|
||||
public double getVolume() {
|
||||
return this.volume;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the pan of this StreamSoundReference.
|
||||
* @return pan of this StreamSoundReference
|
||||
*/
|
||||
@Override
|
||||
public double getPan() {
|
||||
return this.pan;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of bytes remaining for each channel.
|
||||
* @return number of bytes remaining for each channel
|
||||
*/
|
||||
@Override
|
||||
public long bytesAvailable() {
|
||||
return this.numBytesPerChannel - this.position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip a specified number of bytes of the audio data.
|
||||
* @param num number of bytes to skip
|
||||
*/
|
||||
@Override
|
||||
public void skipBytes(long num) {
|
||||
//terminate early if it would finish the sound
|
||||
if (this.position + num >= this.numBytesPerChannel) {
|
||||
this.position = this.numBytesPerChannel;
|
||||
return;
|
||||
}
|
||||
//this is the number of bytes to skip per channel, so double it
|
||||
long numSkip = num * 2;
|
||||
//spin read since skip is not always supported apparently and won't
|
||||
//guarantee a correct skip amount
|
||||
int tmpRead = 0;
|
||||
long numRead = 0;
|
||||
try {
|
||||
while (numRead < numSkip && tmpRead != -1) {
|
||||
//determine safe length to read
|
||||
long remaining = numSkip - numRead;
|
||||
int len = remaining > this.skipBuf.length ?
|
||||
this.skipBuf.length : (int)remaining;
|
||||
//and read
|
||||
tmpRead = this.data.read(this.skipBuf, 0, len);
|
||||
numRead += tmpRead;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
//hmm... I guess invalidate this reference
|
||||
this.position = this.numBytesPerChannel;
|
||||
}
|
||||
//increment the position appropriately
|
||||
if (tmpRead == -1) { //reached end of file in the middle of reading
|
||||
this.position = this.numBytesPerChannel;
|
||||
}
|
||||
else {
|
||||
this.position += num;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next two bytes from the sound data in the specified
|
||||
* endianness.
|
||||
* @param data length-2 array to write in next two bytes from each
|
||||
* channel
|
||||
* @param bigEndian true if the bytes should be read big-endian
|
||||
*/
|
||||
@Override
|
||||
public void nextTwoBytes(int[] data, boolean bigEndian) {
|
||||
//try to read audio data
|
||||
int tmpRead = 0;
|
||||
int numRead = 0;
|
||||
try {
|
||||
while (numRead < this.buf.length && tmpRead != -1) {
|
||||
tmpRead = this.data.read(this.buf, numRead,
|
||||
this.buf.length - numRead);
|
||||
numRead += tmpRead;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
//this shouldn't happen if the bytes were written correctly to
|
||||
//the temp file, but this sound should now be invalid at least
|
||||
this.position = this.numBytesPerChannel;
|
||||
System.err.println("Failed reading bytes for stream sound");
|
||||
}
|
||||
//copy the values into the caller buffer
|
||||
if (bigEndian) {
|
||||
//left
|
||||
data[0] = ((this.buf[0] << 8) |
|
||||
(this.buf[1] & 0xFF));
|
||||
//right
|
||||
data[1] = ((this.buf[2] << 8) |
|
||||
(this.buf[3] & 0xFF));
|
||||
}
|
||||
else {
|
||||
//left
|
||||
data[0] = ((this.buf[1] << 8) |
|
||||
(this.buf[0] & 0xFF));
|
||||
//right
|
||||
data[1] = ((this.buf[3] << 8) |
|
||||
(this.buf[2] & 0xFF));
|
||||
}
|
||||
//increment the position appropriately
|
||||
if (tmpRead == -1) { //reached end of file in the middle of reading
|
||||
this.position = this.numBytesPerChannel;
|
||||
}
|
||||
else {
|
||||
this.position += 2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Does any cleanup necessary to dispose of resources in use by this
|
||||
* StreamSoundReference.
|
||||
*/
|
||||
@Override
|
||||
public void dispose() {
|
||||
this.position = this.numBytesPerChannel;
|
||||
try {
|
||||
this.data.close();
|
||||
} catch (IOException e) {
|
||||
//whatever... this shouldn't happen
|
||||
}
|
||||
this.buf = null;
|
||||
this.skipBuf = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,133 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2012, Finn Kuusisto
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package kuusisto.tinysound.internal;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import javax.sound.sampled.SourceDataLine;
|
||||
|
||||
import kuusisto.tinysound.TinySound;
|
||||
|
||||
/**
|
||||
* The UpdateRunner class implements Runnable and is what performs automatic
|
||||
* updates of the TinySound system. UpdateRunner is an internal class of the
|
||||
* TinySound system and should be of no real concern to the average user of
|
||||
* TinySound.
|
||||
*
|
||||
* @author Finn Kuusisto
|
||||
*/
|
||||
public class UpdateRunner implements Runnable {
|
||||
|
||||
private AtomicBoolean running;
|
||||
private SourceDataLine outLine;
|
||||
private Mixer mixer;
|
||||
|
||||
/**
|
||||
* Constructs a new UpdateRunner to update the TinySound system.
|
||||
* @param mixer the mixer to read audio data from
|
||||
* @param outLine the line to write audio data to
|
||||
*/
|
||||
public UpdateRunner(Mixer mixer, SourceDataLine outLine) {
|
||||
this.running = new AtomicBoolean();
|
||||
this.mixer = mixer;
|
||||
this.outLine = outLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop this UpdateRunner from updating the TinySound system.
|
||||
*/
|
||||
public void stop() {
|
||||
this.running.set(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
//mark the updater as running
|
||||
this.running.set(true);
|
||||
//1-sec buffer
|
||||
int bufSize = (int)TinySound.FORMAT.getFrameRate() *
|
||||
TinySound.FORMAT.getFrameSize();
|
||||
byte[] audioBuffer = new byte[bufSize];
|
||||
//only buffer some maximum number of frames each update (25ms)
|
||||
int maxFramesPerUpdate =
|
||||
(int)((TinySound.FORMAT.getFrameRate() / 1000) * 25);
|
||||
int numBytesRead = 0;
|
||||
double framesAccrued = 0;
|
||||
long lastUpdate = System.nanoTime();
|
||||
//keep running until told to stop
|
||||
while (this.running.get()) {
|
||||
//check the time
|
||||
long currTime = System.nanoTime();
|
||||
//accrue frames
|
||||
double delta = currTime - lastUpdate;
|
||||
double secDelta = (delta / 1000000000L);
|
||||
framesAccrued += secDelta * TinySound.FORMAT.getFrameRate();
|
||||
//read frames if needed
|
||||
int framesToRead = (int)framesAccrued;
|
||||
int framesToSkip = 0;
|
||||
//check if we need to skip frames to catch up
|
||||
if (framesToRead > maxFramesPerUpdate) {
|
||||
framesToSkip = framesToRead - maxFramesPerUpdate;
|
||||
framesToRead = maxFramesPerUpdate;
|
||||
}
|
||||
//skip frames
|
||||
if (framesToSkip > 0) {
|
||||
int bytesToSkip = framesToSkip *
|
||||
TinySound.FORMAT.getFrameSize();
|
||||
this.mixer.skip(bytesToSkip);
|
||||
}
|
||||
//read frames
|
||||
if (framesToRead > 0) {
|
||||
//read from the mixer
|
||||
int bytesToRead = framesToRead *
|
||||
TinySound.FORMAT.getFrameSize();
|
||||
int tmpBytesRead = this.mixer.read(audioBuffer,
|
||||
numBytesRead, bytesToRead);
|
||||
numBytesRead += tmpBytesRead; //mark how many read
|
||||
//fill rest with zeroes
|
||||
int remaining = bytesToRead - tmpBytesRead;
|
||||
for (int i = 0; i < remaining; i++) {
|
||||
audioBuffer[numBytesRead + i] = 0;
|
||||
}
|
||||
numBytesRead += remaining; //mark zeroes read
|
||||
}
|
||||
//mark frames read and skipped
|
||||
framesAccrued -= (framesToRead + framesToSkip);
|
||||
//write to speakers
|
||||
if (numBytesRead > 0) {
|
||||
this.outLine.write(audioBuffer, 0, numBytesRead);
|
||||
numBytesRead = 0;
|
||||
}
|
||||
//mark last update
|
||||
lastUpdate = currTime;
|
||||
//give the CPU back to the OS for a bit
|
||||
try {
|
||||
Thread.sleep(1);
|
||||
} catch (InterruptedException e) {}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue