public AudioRecorder newAudioRecorder(int samplingRate, boolean isMono) {
return null;
}
-
- @Override
- public Sound newSound(FileHandle fileHandle) {\r
- // verify file format\r
+\r
+ /**\r
+ * Let's verify the file format. \r
+ * \r
+ * @param fileHandle The file to load.\r
+ * @throws GdxRuntimeException If we are using a OGG file (not supported under iOS).\r
+ */\r
+ private void verify(FileHandle fileHandle) {\r
if (fileHandle.extension().equalsIgnoreCase("ogg")) { \r
// Ogg is not supported on iOS (return a sound object that does nothing)\r
- Gdx.app.error("IOSAudio", "Audio format .ogg is not supported on iOS. Cannot load: " + fileHandle.path());\r
- return new Sound() {\r
- @Override\r
- public long play () {\r
- return 0;\r
- }\r
- @Override\r
- public long play (float volume) {\r
- return 0;\r
- }\r
- @Override\r
- public long play (float volume, float pitch, float pan) {\r
- return 0;\r
- }\r
- @Override\r
- public long loop () {\r
- return 0;\r
- }\r
- @Override\r
- public long loop (float volume) {\r
- return 0;\r
- }\r
- @Override\r
- public long loop (float volume, float pitch, float pan) {\r
- return 0;\r
- }\r
- @Override\r
- public void stop () {\r
- }\r
- @Override\r
- public void dispose () {\r
- }\r
- @Override\r
- public void stop (long soundId) {\r
- }\r
- @Override\r
- public void setLooping (long soundId, boolean looping) {\r
- }\r
- @Override\r
- public void setPitch (long soundId, float pitch) {\r
- }\r
- @Override\r
- public void setVolume (long soundId, float volume) {\r
- }\r
- @Override\r
- public void setPan (long soundId, float pan, float volume) {\r
- } \r
- };\r
+ throw new GdxRuntimeException("Audio format .ogg is not supported on iOS. Cannot load: " + fileHandle.path());\r
}\r
+ }\r
+ \r
+ /**\r
+ * Returns a new sound object. We are playing from memory, a.k.a. suited for short \r
+ * sound FXs.\r
+ * \r
+ * @return The new sound object.\r
+ * @throws GdxRuntimeException If we are unable to load the file for some reason.\r
+ */
+ @Override
+ public Sound newSound(FileHandle fileHandle) {\r
+ // verify file format (make sure we don't have an OGG file)\r
+ verify(fileHandle);\r
// create audio player - from byte array\r
- NSError[] error = new NSError[1];\r
NSData data = NSData.FromArray(fileHandle.readBytes());\r
- AVAudioPlayer player = AVAudioPlayer.FromData(data, error);\r
- if (error[0] == null) {\r
- // no error: return the music object\r
- return new IOSSound(player);\r
- }\r
- else {\r
- // throw an exception\r
- throw new GdxRuntimeException("Error opening file at " + fileHandle.path() + ": " + error[0].ToString());\r
- }\r
+ return new IOSSound(data);\r
}
+ /**\r
+ * Returns a new music object. We are playing directly from file, a.k.a. suited for\r
+ * background music.\r
+ * \r
+ * @return The new music object.\r
+ * @throws GdxRuntimeException If we are unable to load the file for some reason.\r
+ */\r
@Override
public Music newMusic(FileHandle fileHandle) {\r
- // verify file format\r
- if (fileHandle.extension().equalsIgnoreCase("ogg")) { \r
- // Ogg is not supported on iOS (return a music object that does nothing)\r
- Gdx.app.error("IOSAudio", "Audio format .ogg is not supported on iOS. Cannot load: " + fileHandle.path());\r
- return new Music() {\r
- @Override\r
- public void play () {\r
- }\r
- @Override\r
- public void pause () {\r
- }\r
- @Override\r
- public void stop () {\r
- }\r
- @Override\r
- public boolean isPlaying () {\r
- return false;\r
- }\r
- @Override\r
- public void setLooping (boolean isLooping) {\r
- }\r
- @Override\r
- public boolean isLooping () {\r
- return false;\r
- }\r
- @Override\r
- public void setVolume (float volume) {\r
- }\r
- @Override\r
- public float getPosition () {\r
- return 0;\r
- }\r
- @Override\r
- public void dispose () {\r
- } \r
- };\r
- }\r
+ // verify file format (make sure we don't have an OGG file)\r
+ verify(fileHandle);\r
\r
// create audio player - from file path\r
NSError[] error = new NSError[1];\r
}\r
else {\r
// throw an exception\r
- throw new GdxRuntimeException("Error opening file at " + fileHandle.path() + ": " + error[0].ToString());\r
+ throw new GdxRuntimeException("Error opening music file at " + fileHandle.path() + ": " + error[0].ToString());\r
}\r
}
}
\ No newline at end of file
******************************************************************************/
package com.badlogic.gdx.backends.ios;
+import java.util.ArrayList;\r
+import java.util.List;\r
+\r
import cli.MonoTouch.AVFoundation.AVAudioPlayer;\r
+import cli.MonoTouch.Foundation.NSData;\r
+import cli.MonoTouch.Foundation.NSError;\r
\r
+import com.badlogic.gdx.Gdx;\r
import com.badlogic.gdx.audio.Sound;
+import com.badlogic.gdx.utils.GdxRuntimeException;\r
+/**\r
+ * A music player, suitable for short sound effects. Effects are played from\r
+ * memory. Supports MP3 and WAV files which are played via hardware on iOS.\r
+ * <p>\r
+ * Limitations: does not play OGG. Does not support pitched audio (i.e. variable \r
+ * rate playback).\r
+ * \r
+ * @author noblemaster\r
+ */\r
public class IOSSound implements Sound {
\r
- private AVAudioPlayer player;\r
+ /** The maximum number of players to instantiate to support simultaneous playback. Bigger? */\r
+ private static final int MAX_PLAYERS = 10;\r
+ \r
+ /** Our sound players. More than one to support simultaneous playback. */\r
+ private AVAudioPlayer[] players;\r
\r
\r
- public IOSSound(AVAudioPlayer player) {\r
- this.player = player;\r
+ /**\r
+ * Creates a new sound object. We are creating several AVAudioPlayer objects to\r
+ * support synchronous playback. A single AVAudioPlayer can only play one sound \r
+ * at a time (non-simultaneous).\r
+ * \r
+ * @param data The audio data stored in memory.\r
+ * @throws GdxRuntimeException If we are unable to create the player for some reason.\r
+ */\r
+ public IOSSound(NSData data) {\r
+ // create players for playback (more than 1 to support simultaneous playback)\r
+ NSError[] error = new NSError[1];\r
+ players = new AVAudioPlayer[MAX_PLAYERS];\r
+ for (int i = 0; i < players.length; i++) {\r
+ players[i] = AVAudioPlayer.FromData(data, error);\r
+ \r
+ // check for errors\r
+ if (error[0] != null) {\r
+ // error creating the player (maybe file missing? unsupported format?)\r
+ throw new GdxRuntimeException("Error creating audio player (index: " + i + "): " + error[0].ToString());\r
+ }\r
+ }\r
}\r
-
+\r
+ /**\r
+ * Let's find a player that isn't currently playing.\r
+ * \r
+ * @return The index of the player or -1 if none is available.\r
+ */\r
+ private int findAvailablePlayer() {\r
+ for (int i = 0; i < players.length; i++) {\r
+ if (!players[i].get_Playing()) {\r
+ return i;\r
+ }\r
+ }\r
+ \r
+ // all are busy playing... :/\r
+ return -1;\r
+ }\r
+
@Override
public long play() {\r
return play(1.0f);
@Override
public long play(float volume) {\r
- return play(volume, 1.0f, 0.5f);
+ return play(volume, 1.0f, 0.0f);
}
@Override
public long play(float volume, float pitch, float pan) {\r
- player.set_Volume(volume);\r
- player.set_Pan(pan);\r
- player.Play();\r
-
- // TODO Auto-generated method stub
- return 0;
+ return play(false, volume, pitch, pan);\r
+ }\r
+ \r
+ /**\r
+ * Our actual player. Will be call both for looping and non-looping \r
+ * playback.\r
+ *\r
+ * @return The sound index or -1 if none was available for playback.\r
+ */\r
+ private int play(boolean looping, float volume, float pitch, float pan) {\r
+ int soundId = findAvailablePlayer();\r
+ if (soundId >= 0) {\r
+ AVAudioPlayer player = players[soundId];\r
+ player.set_NumberOfLoops(looping ? -1 : 0); // Note: -1 for looping!\r
+ player.set_Volume(volume);\r
+ player.set_Pan(pan);\r
+ player.Play();\r
+ }\r
+ \r
+ // and return the index/id of the player\r
+ return soundId;
}
@Override
public long loop() {
- // TODO Auto-generated method stub
- return 0;
+ return loop(1.0f);
}
@Override
public long loop(float volume) {
- // TODO Auto-generated method stub
- return 0;
+ return loop(volume, 1.0f, 0.0f);
}
@Override
public long loop(float volume, float pitch, float pan) {
- // TODO Auto-generated method stub
- return 0;
+ return play(true, volume, pitch, pan);
}
@Override
public void stop() {
- player.Stop();
+ // stop all players\r
+ for (int i = 0; i < players.length; i++) {\r
+ players[i].Stop();\r
+ }\r
}
@Override
- public void dispose() {
- player.Dispose();
+ public void dispose() {\r
+ // dispose all players\r
+ for (int i = 0; i < players.length; i++) {
+ players[i].Dispose();\r
+ players[i] = null;\r
+ }
}
@Override
- public void stop(long soundId) {
- // TODO Auto-generated method stub
-
+ public void stop(long soundId) {\r
+ if (soundId >= 0) {
+ players[(int)soundId].Stop();\r
+ }
}
@Override
public void setLooping(long soundId, boolean looping) {
- // TODO Auto-generated method stub
-
+ if (soundId >= 0) {\r
+ players[(int)soundId].set_NumberOfLoops(looping ? -1 : 0); // Note: -1 for looping!\r
+ }\r
}
@Override
- public void setPitch(long soundId, float pitch) {
- // TODO Auto-generated method stub
-
+ public void setPitch(long soundId, float pitch) {\r
+ if (soundId >= 0) {\r
+ // NOTE: It's odd, AVAudioPlayer supports variable rate playing, but is not\r
+ // available via MonoTouch!? Let's put out a warning...\r
+ Gdx.app.debug("IOSSound", "Warning: setting a pitch not supported on iOS.");\r
+ }\r
}
@Override
public void setVolume(long soundId, float volume) {
- // TODO Auto-generated method stub
-
+ if (soundId >= 0) {\r
+ players[(int)soundId].set_Volume(volume);\r
+ }\r
}
@Override
public void setPan(long soundId, float pan, float volume) {
- // TODO Auto-generated method stub
-
+ if (soundId >= 0) {\r
+ players[(int)soundId].set_Pan(pan);\r
+ players[(int)soundId].set_Volume(volume);\r
+ }
}
}
\ No newline at end of file