Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Base stream code and redesign with Music, Sound, MusicReference, and …

…SoundReference broken out as interfaces.
  • Loading branch information...
commit 56d57d7e9078a554629cc3364ad8ae812d85f411 1 parent d172f26
@finnkuusisto authored
View
119 src/kuusisto/tinysound/Music.java
@@ -25,187 +25,112 @@
* POSSIBILITY OF SUCH DAMAGE.
*/
package kuusisto.tinysound;
-import kuusisto.tinysound.internal.Mixer;
-import kuusisto.tinysound.internal.MusicReference;
/**
- * The Music class is an abstraction for music. Music objects should only be
- * loaded via the TinySound <code>loadMusic()</code> functions. Music can be
+ * 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 class Music {
-
- private byte[] left;
- private byte[] right;
- private Mixer mixer;
- private MusicReference reference;
-
- /**
- * Construct a new Music with the given music data and the Mixer with which
- * to register this Music.
- * @param left left channel of music data
- * @param right right channel of music data
- * @param mixer Mixer with which this Music is registered
- */
- public Music(byte[] left, byte[] right, Mixer mixer) {
- this.left = left;
- this.right = right;
- this.mixer = mixer;
- this.reference = new MusicReference(this.left, this.right, false, false,
- 0, 0, 1.0);
- this.mixer.registerMusicReference(this.reference);
- }
-
+public interface Music {
+
/**
* Play this Music and loop if specified.
* @param loop if this Music should loop
*/
- public void play(boolean loop) {
- this.reference.setPlaying(true);
- this.reference.setLoop(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) {
- this.reference.setPlaying(true);
- this.setLoop(loop);
- this.setVolume(volume);
- }
+ public void play(boolean loop, double volume);
/**
* Stop playing this Music and set its position to the beginning.
*/
- public void stop() {
- this.reference.setPlaying(false);
- this.rewind();
- }
+ public void stop();
/**
* Stop playing this Music and keep its current position.
*/
- public void pause() {
- this.reference.setPlaying(false);
- }
+ public void pause();
/**
* Play this Music from its current position.
*/
- public void resume() {
- this.reference.setPlaying(true);
- }
+ public void resume();
/**
* Set this Music's position to the beginning.
*/
- public void rewind() {
- this.reference.setPosition(0);
- }
+ public void rewind();
/**
* Set this Music's position to the loop position.
*/
- public void rewindToLoopPosition() {
- int byteIndex = this.reference.getLoopPosition();
- this.reference.setPosition(byteIndex);
- }
+ public void rewindToLoopPosition();
/**
* Determine if this Music is playing.
* @return true if this Music is playing
*/
- public boolean playing() {
- return this.reference.getPlaying();
- }
+ public boolean playing();
/**
* Determine if this Music will loop.
* @return true if this Music will loop
*/
- public boolean loop() {
- return this.reference.getLoop();
- }
+ public boolean loop();
/**
* Set whether this Music will loop.
* @param loop whether this Music will loop
*/
- public void setLoop(boolean loop) {
- this.reference.setLoop(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() {
- int byteIndex = this.reference.getLoopPosition();
- return (byteIndex / TinySound.FORMAT.getFrameSize());
- }
+ public int getLoopPositionByFrame();
/**
* Get the loop position of this Music by seconds.
* @return loop position by seconds
*/
- public double getLoopPositionBySeconds() {
- int byteIndex = this.reference.getLoopPosition();
- return (int)(byteIndex / (TinySound.FORMAT.getFrameRate() *
- TinySound.FORMAT.getFrameSize()));
- }
+ 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) {
- int byteIndex = frameIndex * TinySound.FORMAT.getFrameSize();
- this.reference.setLoopPosition(byteIndex);
- }
+ 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) {
- int byteIndex = (int)(seconds * TinySound.FORMAT.getFrameRate() *
- TinySound.FORMAT.getFrameSize());
- this.reference.setLoopPosition(byteIndex);
- }
+ public void setLoopPositionBySeconds(double seconds);
/**
* Get the volume of this Music.
* @return volume of this Music
*/
- public double getVolume() {
- return this.reference.getVolume();
- }
+ public double getVolume();
/**
* Set the volume of this Music.
* @param volume the desired volume of this Music
*/
- public void setVolume(double volume) {
- if (volume >= 0.0) {
- this.reference.setVolume(volume);
- }
- }
+ public void setVolume(double volume);
/**
* Unload this Music from the system. Attempts to use this Music after
* unloading will result in error.
*/
- public void unload() {
- //unregister the reference
- this.mixer.unRegisterMusicReference(this.reference);
- this.mixer = null;
- this.left = null;
- this.right = null;
- this.reference = null;
- }
-
+ public void unload();
+
}
View
59 src/kuusisto/tinysound/Sound.java
@@ -25,77 +25,38 @@
* POSSIBILITY OF SUCH DAMAGE.
*/
package kuusisto.tinysound;
-import kuusisto.tinysound.internal.Mixer;
-import kuusisto.tinysound.internal.SoundReference;
/**
- * The Sound class is an abstraction for sound effects. Sound objects should
- * only be loaded via the TinySound <code>loadSound()</code> functions. Sound
- * can be played repeatedly in an overlapping fashion.
+ * 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 class Sound {
-
- private static int soundCount = 0;
-
- private byte[] left;
- private byte[] right;
- private Mixer mixer;
- private final int ID; //unique ID to match references
-
- /**
- * Construct a new Sound with the given data and Mixer which will handle
- * handle this Sound.
- * @param left left channel of sound data
- * @param right right channel of sound data
- * @param mixer Mixer that will handle this Sound
- */
- public Sound(byte[] left, byte[] right, Mixer mixer) {
- this.left = left;
- this.right = right;
- this.mixer = mixer;
- //get the next ID
- this.ID = Sound.soundCount;
- Sound.soundCount++;
- }
-
+public interface Sound {
+
/**
* Plays this Sound.
*/
- public void play() {
- this.play(1.0);
- }
+ 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) {
- //dispatch a SoundReference to the mixer
- SoundReference ref = new SoundReference(this.left, this.right, volume,
- this.ID);
- this.mixer.registerSoundReference(ref);
- }
+ public void play(double volume);
/**
* 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() {
- this.mixer.unRegisterSoundReference(this.ID);
- }
+ public void stop();
/**
* Unloads this Sound from the system. Attempts to use this Sound after
* unloading will result in error.
*/
- public void unload() {
- this.mixer.unRegisterSoundReference(this.ID);
- this.mixer = null;
- this.left = null;
- this.right = null;
- }
-
+ public void unload();
+
}
View
10 src/kuusisto/tinysound/TinySound.java
@@ -41,6 +41,8 @@
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.UpdateRunner;
@@ -75,6 +77,8 @@
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.
@@ -257,7 +261,7 @@ public static Music loadMusic(URL url) {
return null;
}
//construct the Music object and register it with the mixer
- return new Music(data[0], data[1], TinySound.mixer);
+ return new MemMusic(data[0], data[1], TinySound.mixer);
}
/**
@@ -342,7 +346,9 @@ public static Sound loadSound(URL url) {
return null;
}
//construct the Sound object
- return new Sound(data[0], data[1], TinySound.mixer);
+ TinySound.soundCount++;
+ return new MemSound(data[0], data[1], TinySound.mixer,
+ TinySound.soundCount);
}
/**
View
430 src/kuusisto/tinysound/internal/MemMusic.java
@@ -0,0 +1,430 @@
+/*
+ * 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 Music class 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 class MemMusic implements Music {
+
+ private byte[] left;
+ private byte[] right;
+ private Mixer mixer;
+ private MusicReference reference;
+
+ /**
+ * Construct a new Music with the given music data and the Mixer with which
+ * to register this Music.
+ * @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);
+ this.mixer.registerMusicReference(this.reference);
+ }
+
+ /**
+ * Play this Music and loop if specified.
+ * @param loop if this Music should loop
+ */
+ public void play(boolean loop) {
+ this.reference.setPlaying(true);
+ this.reference.setLoop(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) {
+ this.reference.setPlaying(true);
+ this.setLoop(loop);
+ this.setVolume(volume);
+ }
+
+ /**
+ * Stop playing this Music and set its position to the beginning.
+ */
+ public void stop() {
+ this.reference.setPlaying(false);
+ this.rewind();
+ }
+
+ /**
+ * Stop playing this Music and keep its current position.
+ */
+ public void pause() {
+ this.reference.setPlaying(false);
+ }
+
+ /**
+ * Play this Music from its current position.
+ */
+ public void resume() {
+ this.reference.setPlaying(true);
+ }
+
+ /**
+ * Set this Music's position to the beginning.
+ */
+ public void rewind() {
+ this.reference.setPosition(0);
+ }
+
+ /**
+ * Set this Music's position to the loop position.
+ */
+ public void rewindToLoopPosition() {
+ long byteIndex = this.reference.getLoopPosition();
+ this.reference.setPosition(byteIndex);
+ }
+
+ /**
+ * Determine if this Music is playing.
+ * @return true if this Music is playing
+ */
+ public boolean playing() {
+ return this.reference.getPlaying();
+ }
+
+ /**
+ * Determine if this Music will loop.
+ * @return true if this Music will loop
+ */
+ public boolean loop() {
+ return this.reference.getLoop();
+ }
+
+ /**
+ * Set whether this Music will loop.
+ * @param loop whether this Music will loop
+ */
+ public void setLoop(boolean loop) {
+ this.reference.setLoop(loop);
+ }
+
+ /**
+ * Get the loop position of this Music by sample frame.
+ * @return loop position by sample frame
+ */
+ 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 Music by seconds.
+ * @return loop position by seconds
+ */
+ 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 Music by sample frame.
+ * @param frameIndex sample frame loop position to set
+ */
+ 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 Music by seconds.
+ * @param seconds loop position to set by seconds
+ */
+ 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 Music.
+ * @return volume of this Music
+ */
+ public double getVolume() {
+ return this.reference.getVolume();
+ }
+
+ /**
+ * Set the volume of this Music.
+ * @param volume the desired volume of this Music
+ */
+ public void setVolume(double volume) {
+ if (volume >= 0.0) {
+ this.reference.setVolume(volume);
+ }
+ }
+
+ /**
+ * Unload this Music from the system. Attempts to use this Music after
+ * unloading will result in error.
+ */
+ 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 MusicReference class is the Mixers interface to the audio data of a
+ * Music object. MusicReference is an internal class of the TinySound
+ * system and should be of no real concern to the average user of TinySound.
+ *
+ * @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;
+
+ /**
+ * Construct a new MusicReference 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 position byte index position in music data
+ * @param volume volume to play the music
+ */
+ public MemMusicReference(byte[] left, byte[] right, boolean playing,
+ boolean loop, int loopPosition, int position, double volume) {
+ this.left = left;
+ this.right = right;
+ this.playing = playing;
+ this.loop = loop;
+ this.loopPosition = loopPosition;
+ this.position = position;
+ this.volume = volume;
+ }
+
+ /**
+ * Get the playing setting of this MusicReference.
+ * @return true if this MusicReference is set to play
+ */
+ @Override
+ public synchronized boolean getPlaying() {
+ return this.playing;
+ }
+
+ /**
+ * Get the loop setting of this MusicReference.
+ * @return true if this MusicReference is set to loop
+ */
+ @Override
+ public synchronized boolean getLoop() {
+ return this.loop;
+ }
+
+ /**
+ * Get the byte index of this MusicReference.
+ * @return byte index of this MusicReference
+ */
+ @Override
+ public synchronized long getPosition() {
+ return this.position;
+ }
+
+ /**
+ * Get the loop-position byte index of this MusicReference.
+ * @return loop-position byte index of this MusicReference
+ */
+ @Override
+ public synchronized long getLoopPosition() {
+ return this.loopPosition;
+ }
+
+ /**
+ * Get the volume of this MusicReference.
+ * @return volume of this MusicReference
+ */
+ @Override
+ public synchronized double getVolume() {
+ return this.volume;
+ }
+
+ /**
+ * Set whether this MusicReference is playing.
+ * @param playing whether this MusicReference is playing
+ */
+ @Override
+ public synchronized void setPlaying(boolean playing) {
+ this.playing = playing;
+ }
+
+ /**
+ * Set whether this MusicReference will loop.
+ * @param loop whether this MusicReference will loop
+ */
+ @Override
+ public synchronized void setLoop(boolean loop) {
+ this.loop = loop;
+ }
+
+ /**
+ * Set the byte index of this MusicReference.
+ * @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 MusicReference.
+ * @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 MusicReference.
+ * @param volume the desired volume of this MusicReference
+ */
+ @Override
+ public synchronized void setVolume(double volume) {
+ this.volume = volume;
+ }
+
+ /**
+ * Get the number of bytes remaining for each channel until the end of this
+ * Music.
+ * @return number of bytes remaining for each channel
+ */
+ @Override
+ public synchronized 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) {
+ for (int i = 0; i < num; i++) {
+ this.position++;
+ //wrap if looping
+ if (this.loop && this.position >= this.left.length) {
+ this.position = this.loopPosition;
+ }
+ }
+ }
+
+ /**
+ * 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
+ if (this.loop && this.position >= this.left.length) {
+ this.position = this.loopPosition;
+ }
+ }
+
+ /**
+ * Does any cleanup necessary to dispose of resources in use by this
+ * MusicReference.
+ */
+ @Override
+ public synchronized void dispose() {
+ this.playing = false;
+ this.position = this.left.length + 1;
+ this.left = null;
+ this.right = null;
+ }
+
+ }
+
+}
View
209 src/kuusisto/tinysound/internal/MemSound.java
@@ -0,0 +1,209 @@
+/*
+ * 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 Sound class is an abstraction for sound effects. Sound objects should
+ * only be loaded via the TinySound <code>loadSound()</code> functions. Sound
+ * can be played repeatedly in an overlapping fashion.
+ *
+ * @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 Sound with the given data and Mixer which will handle
+ * handle this Sound.
+ * @param left left channel of sound data
+ * @param right right channel of sound data
+ * @param mixer Mixer that will handle this Sound
+ */
+ public MemSound(byte[] left, byte[] right, Mixer mixer, int id) {
+ this.left = left;
+ this.right = right;
+ this.mixer = mixer;
+ this.ID = id;
+ }
+
+ /**
+ * Plays this Sound.
+ */
+ public void play() {
+ this.play(1.0);
+ }
+
+ /**
+ * Plays this Sound with a specified volume.
+ * @param volume the volume at which to play this Sound
+ */
+ public void play(double volume) {
+ //dispatch a SoundReference to the mixer
+ SoundReference ref = new MemSoundReference(this.left, this.right,
+ volume, this.ID);
+ this.mixer.registerSoundReference(ref);
+ }
+
+ /**
+ * 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() {
+ this.mixer.unRegisterSoundReference(this.ID);
+ }
+
+ /**
+ * Unloads this Sound from the system. Attempts to use this Sound after
+ * unloading will result in error.
+ */
+ public void unload() {
+ this.mixer.unRegisterSoundReference(this.ID);
+ this.mixer = null;
+ this.left = null;
+ this.right = null;
+ }
+
+ /////////////
+ //Reference//
+ /////////////
+
+ /**
+ * The SoundReference class is the Mixers interface to the audio data of a
+ * Sound object. SoundReference is an internal class of the TinySound
+ * system and should be of no real concern to the average user of TinySound.
+ *
+ * @author Finn Kuusisto
+ */
+ private static class MemSoundReference implements SoundReference {
+
+ public final int SOUND_ID; //parent Sound
+
+ private byte[] left;
+ private byte[] right;
+ private int position;
+ private double volume;
+
+ /**
+ * Construct a new SoundReference 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 soundID ID of the Sound for which this is a reference
+ */
+ public MemSoundReference(byte[] left, byte[] right, double volume,
+ int soundID) {
+ this.left = left;
+ this.right = right;
+ this.volume = (volume >= 0.0) ? volume : 1.0;
+ this.position = 0;
+ this.SOUND_ID = soundID;
+ }
+
+ /**
+ * Get the ID of the Sound that produced this SoundReference.
+ * @return the ID of this SoundReference's parent Sound
+ */
+ @Override
+ public int getSoundID() {
+ return this.SOUND_ID;
+ }
+
+ /**
+ * Gets the volume of this SoundReference.
+ * @return volume of this SoundReference
+ */
+ @Override
+ public double getVolume() {
+ return this.volume;
+ }
+
+ /**
+ * 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
+ * SoundReference.
+ */
+ @Override
+ public void dispose() {
+ this.position = this.left.length + 1;
+ this.left = null;
+ this.right = null;
+ }
+ }
+
+}
View
16 src/kuusisto/tinysound/internal/Mixer.java
@@ -25,6 +25,7 @@
* POSSIBILITY OF SUCH DAMAGE.
*/
package kuusisto.tinysound.internal;
+
import java.util.ArrayList;
import java.util.List;
@@ -81,8 +82,8 @@ public synchronized void unRegisterMusicReference(MusicReference music) {
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).SOUND_ID == soundID) {
- this.sounds.remove(i);
+ if (this.sounds.get(i).getSoundID() == soundID) {
+ this.sounds.remove(i).dispose();
}
}
}
@@ -98,6 +99,9 @@ public synchronized void clearMusic() {
* Unregister all Sounds registered with this Mixer.
*/
public synchronized void clearSounds() {
+ for (SoundReference s : this.sounds) {
+ s.dispose();
+ }
this.sounds.clear();
}
@@ -148,11 +152,11 @@ public synchronized int read(byte[] data, int offset, int length) {
bytesRead = true;
//remove the reference if done
if (sound.bytesAvailable() <= 0) {
- this.sounds.remove(s);
+ this.sounds.remove(s).dispose();
}
}
else { //otherwise remove this reference
- this.sounds.remove(s);
+ this.sounds.remove(s).dispose();
}
}
//if we actually read bytes, store in the buffer
@@ -207,11 +211,11 @@ public synchronized void skip(int numBytes) {
sound.skipBytes(numBytes);
//remove the reference if done
if (sound.bytesAvailable() <= 0) {
- this.sounds.remove(s);
+ this.sounds.remove(s).dispose();
}
}
else { //otherwise remove this reference
- this.sounds.remove(s);
+ this.sounds.remove(s).dispose();
}
}
}
View
139 src/kuusisto/tinysound/internal/MusicReference.java
@@ -33,185 +33,92 @@
*
* @author Finn Kuusisto
*/
-public class MusicReference {
+public interface MusicReference {
- private byte[] left;
- private byte[] right;
- private boolean playing;
- private boolean loop;
- private int loopPosition;
- private int position;
- private double volume;
-
- /**
- * Construct a new MusicReference 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 position byte index position in music data
- * @param volume volume to play the music
- */
- public MusicReference(byte[] left, byte[] right, boolean playing,
- boolean loop, int loopPosition, int position, double volume) {
- this.left = left;
- this.right = right;
- this.playing = playing;
- this.loop = loop;
- this.loopPosition = loopPosition;
- this.position = position;
- this.volume = volume;
- }
-
/**
* Get the playing setting of this MusicReference.
* @return true if this MusicReference is set to play
*/
- public synchronized boolean getPlaying() {
- return this.playing;
- }
+ public boolean getPlaying();
/**
* Get the loop setting of this MusicReference.
* @return true if this MusicReference is set to loop
*/
- public synchronized boolean getLoop() {
- return this.loop;
- }
+ public boolean getLoop();
/**
* Get the byte index of this MusicReference.
* @return byte index of this MusicReference
*/
- public synchronized int getPosition() {
- return this.position;
- }
+ public long getPosition();
/**
* Get the loop-position byte index of this MusicReference.
* @return loop-position byte index of this MusicReference
*/
- public synchronized int getLoopPosition() {
- return this.loopPosition;
- }
+ public long getLoopPosition();
/**
* Get the volume of this MusicReference.
* @return volume of this MusicReference
*/
- public synchronized double getVolume() {
- return this.volume;
- }
+ public double getVolume();
/**
* Set whether this MusicReference is playing.
* @param playing whether this MusicReference is playing
*/
- public synchronized void setPlaying(boolean playing) {
- this.playing = playing;
- }
+ public void setPlaying(boolean playing);
/**
* Set whether this MusicReference will loop.
* @param loop whether this MusicReference will loop
*/
- public synchronized void setLoop(boolean loop) {
- this.loop = loop;
- }
+ public void setLoop(boolean loop);
/**
* Set the byte index of this MusicReference.
* @param position the byte index to set
*/
- public synchronized void setPosition(int position) {
- if (position >= 0 && position < this.left.length) {
- this.position = position;
- }
- }
+ public void setPosition(long position);
/**
* Set the loop-position byte index of this MusicReference.
* @param loopPosition the loop-position byte index to set
*/
- public synchronized void setLoopPosition(int loopPosition) {
- if (loopPosition >= 0 && loopPosition < this.left.length) {
- this.loopPosition = loopPosition;
- }
- }
+ public void setLoopPosition(long loopPosition);
/**
* Set the volume of this MusicReference.
* @param volume the desired volume of this MusicReference
*/
- public synchronized void setVolume(double volume) {
- this.volume = volume;
- }
+ public void setVolume(double volume);
/**
- * Get the number of bytes remaining until the end of this Music.
- * @return number of bytes remaining
+ * Get the number of bytes remaining for each channel until the end of this
+ * Music.
+ * @return number of bytes remaining for each channel
*/
- public synchronized int bytesAvailable() {
- return this.left.length - this.position;
- }
+ public long bytesAvailable();
/**
* Skip a specified number of bytes of the audio data.
* @param num number of bytes to skip
*/
- public synchronized void skipBytes(int num) {
- for (int i = 0; i < num; i++) {
- this.position++;
- //wrap if looping
- if (this.loop && this.position >= this.left.length) {
- this.position = this.loopPosition;
- }
- }
- }
-
- /**
- * Get the next byte from the music data.
- * @param data length-2 array to write in next byte from each channel
- */
- public synchronized void nextByte(byte[] data) {
- //left channel
- data[0] = this.left[position];
- //right channel
- data[1] = this.right[position];
- this.position++;
- //wrap if looping
- if (this.loop && this.position >= this.left.length) {
- this.position = this.loopPosition;
- }
- }
+ 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 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
- if (this.loop && this.position >= this.left.length) {
- this.position = this.loopPosition;
- }
- }
+ public void nextTwoBytes(int[] data, boolean bigEndian);
+
+ /**
+ * Does any cleanup necessary to dispose of resources in use by this
+ * MusicReference.
+ */
+ public void dispose();
}
View
81 src/kuusisto/tinysound/internal/SoundReference.java
@@ -33,90 +33,43 @@
*
* @author Finn Kuusisto
*/
-public class SoundReference {
+public interface SoundReference {
- public final int SOUND_ID; //parent Sound
-
- private byte[] left;
- private byte[] right;
- private int position;
- private double volume;
-
/**
- * Construct a new SoundReference 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 soundID ID of the Sound for which this is a reference
+ * Get the ID of the Sound that produced this SoundReference.
+ * @return the ID of this SoundReference's parent Sound
*/
- public SoundReference(byte[] left, byte[] right, double volume,
- int soundID) {
- this.left = left;
- this.right = right;
- this.volume = (volume >= 0.0) ? volume : 1.0;
- this.position = 0;
- this.SOUND_ID = soundID;
- }
+ public int getSoundID();
/**
* Gets the volume of this SoundReference.
* @return volume of this SoundReference
*/
- public double getVolume() {
- return this.volume;
- }
+ public double getVolume();
/**
- * Get the number of bytes remaining for reading.
- * @return number of bytes remaining
+ * Get the number of bytes remaining for each channel.
+ * @return number of bytes remaining for each channel
*/
- public int bytesAvailable() {
- return this.left.length - this.position;
- }
+ public long bytesAvailable();
/**
* Skip a specified number of bytes of the audio data.
* @param num number of bytes to skip
*/
- public synchronized void skipBytes(int num) {
- this.position += num;
- }
-
- /**
- * Get the next byte from the sound data.
- * @param data length-2 array to write in next byte from each channel
- */
- public void nextByte(byte[] data) {
- //left channel
- data[0] = this.left[position];
- //right channel
- data[1] = this.right[position];
- this.position++;
- }
+ 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) {
- 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;
- }
-
+ public void nextTwoBytes(int[] data, boolean bigEndian);
+
+ /**
+ * Does any cleanup necessary to dispose of resources in use by this
+ * SoundReference.
+ */
+ public void dispose();
+
}
View
427 src/kuusisto/tinysound/internal/StreamMusic.java
@@ -0,0 +1,427 @@
+/*
+ * 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;
+
+public class StreamMusic implements Music {
+
+ private URL dataURL;
+ private Mixer mixer;
+ private MusicReference reference;
+
+ public StreamMusic(URL dataURL, Mixer mixer) {
+ this.dataURL = dataURL;
+ this.mixer = mixer;
+ try {
+ //TODO should this be thrown from here?
+ this.reference = new StreamMusicReference(this.dataURL, false,
+ false, 0, 0, 1.0);
+ this.mixer.registerMusicReference(this.reference);
+ } catch (IOException e) {
+ System.err.println("Failed to open stream for Music");
+ }
+ }
+
+ /**
+ * Play this Music and loop if specified.
+ * @param loop if this Music should loop
+ */
+ public void play(boolean loop) {
+ this.reference.setPlaying(true);
+ this.reference.setLoop(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) {
+ this.reference.setPlaying(true);
+ this.setLoop(loop);
+ this.setVolume(volume);
+ }
+
+ /**
+ * Stop playing this Music and set its position to the beginning.
+ */
+ public void stop() {
+ this.reference.setPlaying(false);
+ this.rewind();
+ }
+
+ /**
+ * Stop playing this Music and keep its current position.
+ */
+ public void pause() {
+ this.reference.setPlaying(false);
+ }
+
+ /**
+ * Play this Music from its current position.
+ */
+ public void resume() {
+ this.reference.setPlaying(true);
+ }
+
+ /**
+ * Set this Music's position to the beginning.
+ */
+ public void rewind() {
+ this.reference.setPosition(0);
+ }
+
+ /**
+ * Set this Music's position to the loop position.
+ */
+ public void rewindToLoopPosition() {
+ long byteIndex = this.reference.getLoopPosition();
+ this.reference.setPosition(byteIndex);
+ }
+
+ /**
+ * Determine if this Music is playing.
+ * @return true if this Music is playing
+ */
+ public boolean playing() {
+ return this.reference.getPlaying();
+ }
+
+ /**
+ * Determine if this Music will loop.
+ * @return true if this Music will loop
+ */
+ public boolean loop() {
+ return this.reference.getLoop();
+ }
+
+ /**
+ * Set whether this Music will loop.
+ * @param loop whether this Music will loop
+ */
+ public void setLoop(boolean loop) {
+ this.reference.setLoop(loop);
+ }
+
+ @Override
+ public int getLoopPositionByFrame() {
+ int bytesPerChannelForFrame = TinySound.FORMAT.getFrameSize() /
+ TinySound.FORMAT.getChannels();
+ long byteIndex = this.reference.getLoopPosition();
+ return (int)(byteIndex / bytesPerChannelForFrame);
+ }
+
+ @Override
+ public double getLoopPositionBySeconds() {
+ int bytesPerChannelForFrame = TinySound.FORMAT.getFrameSize() /
+ TinySound.FORMAT.getChannels();
+ long byteIndex = this.reference.getLoopPosition();
+ return (byteIndex / (TinySound.FORMAT.getFrameRate() *
+ bytesPerChannelForFrame));
+ }
+
+ @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);
+ }
+
+ @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 Music.
+ * @return volume of this Music
+ */
+ public double getVolume() {
+ return this.reference.getVolume();
+ }
+
+ /**
+ * Set the volume of this Music.
+ * @param volume the desired volume of this Music
+ */
+ public void setVolume(double volume) {
+ if (volume >= 0.0) {
+ this.reference.setVolume(volume);
+ }
+ }
+
+ @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//
+ /////////////
+
+ 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;
+
+ public StreamMusicReference(URL dataURL, boolean playing, boolean loop,
+ int loopPosition, int position, double volume)
+ throws IOException {
+ this.url = dataURL;
+ this.playing = playing;
+ this.loop = loop;
+ this.loopPosition = loopPosition;
+ this.position = position;
+ this.volume = volume;
+ this.buf = new byte[4];
+ this.skipBuf = new byte[50];
+ //now get the data stream
+ this.data = this.url.openStream();
+ }
+
+ @Override
+ public synchronized boolean getPlaying() {
+ return this.playing;
+ }
+
+ @Override
+ public synchronized boolean getLoop() {
+ return this.loop;
+ }
+
+ @Override
+ public synchronized long getPosition() {
+ return this.position;
+ }
+
+ @Override
+ public synchronized long getLoopPosition() {
+ return this.loopPosition;
+ }
+
+ /**
+ * Get the number of bytes remaining for each channel until the end of
+ * this Music.
+ * @return number of bytes remaining for each channel
+ */
+ @Override
+ public synchronized long bytesAvailable() {
+ return this.numBytesPerChannel - this.position;
+ }
+
+ @Override
+ public synchronized double getVolume() {
+ return this.volume;
+ }
+
+ @Override
+ public synchronized void setPlaying(boolean playing) {
+ this.playing = playing;
+ }
+
+ @Override
+ public synchronized void setLoop(boolean loop) {
+ this.loop = loop;
+ }
+
+ @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 Music");
+ this.playing = false;
+ }
+ }
+ }
+ }
+
+ @Override
+ public synchronized void setLoopPosition(long loopPosition) {
+ if (loopPosition >= 0 && loopPosition < this.numBytesPerChannel) {
+ this.loopPosition = loopPosition;
+ }
+ }
+
+ @Override
+ public synchronized void setVolume(double volume) {
+ this.volume = volume;
+ }
+
+ @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
+ if (this.loop && this.position >= this.numBytesPerChannel) {
+ this.setPosition(this.loopPosition);
+ }
+ }
+
+ @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;
+ 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) {
+ tmpRead = this.data.read(this.skipBuf, numRead,
+ this.skipBuf.length - numRead);
+ 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;
+ }
+ }
+
+ /**
+ * Does any cleanup necessary to dispose of resources in use by this
+ * MusicReference.
+ */
+ @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
219 src/kuusisto/tinysound/internal/StreamSound.java
@@ -0,0 +1,219 @@
+/*
+ * 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;
+
+public class StreamSound implements Sound {
+
+ private URL dataURL;
+ private long numBytesPerChannel;
+ private Mixer mixer;
+ private final int ID;
+
+ public StreamSound(URL dataURL, long numBytesPerChannel, Mixer mixer,
+ int id) {
+ this.dataURL = dataURL;
+ this.numBytesPerChannel = numBytesPerChannel;
+ this.mixer = mixer;
+ this.ID = id;
+ //TODO open and close a stream to check for immediate I/O issues?
+ }
+
+ @Override
+ public void play() {
+ this.play(1.0);
+ }
+
+ @Override
+ public void play(double volume) {
+ //dispatch a SoundReference to the mixer
+ SoundReference ref;
+ try {
+ ref = new StreamSoundReference(this.dataURL.openStream(),
+ this.numBytesPerChannel, volume, this.ID);
+ this.mixer.registerSoundReference(ref);
+ } catch (IOException e) {
+ System.err.println("Failed to open stream for Sound");
+ }
+ }
+
+ @Override
+ public void stop() {
+ this.mixer.unRegisterSoundReference(this.ID);
+ }
+
+ @Override
+ public void unload() {
+ this.mixer.unRegisterSoundReference(this.ID);
+ this.mixer = null;
+ this.dataURL = null;
+ }
+
+ /////////////
+ //Reference//
+ /////////////
+
+ 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 byte[] buf;
+ private byte[] skipBuf;
+
+ public StreamSoundReference(InputStream data, long numBytesPerChannel,
+ double volume, int soundID) {
+ this.data = data;
+ this.numBytesPerChannel = numBytesPerChannel;
+ this.volume = (volume >= 0.0) ? volume : 1.0;
+ this.position = 0;
+ this.buf = new byte[4];
+ this.skipBuf = new byte[20];
+ this.SOUND_ID = soundID;
+ }
+
+ /**
+ * Get the ID of the Sound that produced this SoundReference.
+ * @return the ID of this SoundReference's parent Sound
+ */
+ @Override
+ public int getSoundID() {
+ return this.SOUND_ID;
+ }
+
+ /**
+ * 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;
+ }
+
+ @Override
+ public double getVolume() {
+ return this.volume;
+ }
+
+ @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;
+ }
+ }
+
+ @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;
+ int numRead = 0;
+ try {
+ while (numRead < numSkip && tmpRead != -1) {
+ tmpRead = this.data.read(this.skipBuf, numRead,
+ this.skipBuf.length - numRead);
+ 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;
+ }
+ }
+
+ @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;
+ }
+
+ }
+
+}
Please sign in to comment.
Something went wrong with that request. Please try again.