replace source for third part library, TinySound, with build rule to download jar

master
melvinzhang 2015-06-19 10:49:07 +08:00
parent 22223077f1
commit 850781d954
14 changed files with 20 additions and 3646 deletions

View File

@ -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>

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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();
}
}
}
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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) {}
}
}
}