862 lines
25 KiB
Objective-C
862 lines
25 KiB
Objective-C
//
|
|
// SoundManager.m
|
|
// SLQTSOR
|
|
//
|
|
// Created by Michael Daley on 22/05/2009.
|
|
// Copyright 2009 Michael Daley. All rights reserved.
|
|
//
|
|
|
|
#import "SoundManager.h"
|
|
#import "SynthesizeSingleton.h"
|
|
#import "MyOpenALSupport.h"
|
|
|
|
#pragma mark -
|
|
#pragma mark Private interface
|
|
|
|
@interface SoundManager (Private)
|
|
|
|
// This method is used to initialize OpenAL. It gets the default device, creates a new context
|
|
// to be used and then preloads the define # sources. This preloading means we wil be able to play up to
|
|
// (max 32) different sounds at the same time
|
|
- (BOOL)initOpenAL;
|
|
|
|
// Used to get the next available OpenAL source. The returned source is then bound to a sound
|
|
// buffer so that the sound can be played. This method checks each of the available OpenAL
|
|
// soucres which have been generated and returns the first source which is not currently being
|
|
// used. If no sources are found to be free then the first looping source is returned. If there
|
|
// are no looping sources then the first source created is returned
|
|
- (NSUInteger)nextAvailableSource;
|
|
|
|
// Used to set the current state of OpenAL. When the game is interrupted the OpenAL state is
|
|
// stopped and then restarted when the game becomes active again.
|
|
- (void)setActivated:(BOOL)aState;
|
|
|
|
// If audio is currently playing this method returns YES
|
|
- (BOOL)isAudioPlaying;
|
|
|
|
// Checks to see if an OpenAL error has been logged. If so it renders the error to the screen
|
|
- (void)checkForErrors;
|
|
|
|
@end
|
|
|
|
|
|
#pragma mark -
|
|
#pragma mark Public implementation
|
|
|
|
@implementation SoundManager
|
|
|
|
// Make this class a singleton class
|
|
SYNTHESIZE_SINGLETON_FOR_CLASS(SoundManager);
|
|
|
|
@synthesize currentMusicVolume;
|
|
@synthesize fxVolume;
|
|
@synthesize isExternalAudioPlaying;
|
|
@synthesize isMusicPlaying;
|
|
@synthesize usePlaylist;
|
|
@synthesize loopLastPlaylistTrack;
|
|
@synthesize musicVolume;
|
|
|
|
#pragma mark -
|
|
#pragma mark Dealloc and Init and Shutdown
|
|
|
|
- (void)dealloc {
|
|
// Loop through the OpenAL sources and delete them
|
|
for(NSNumber *sourceIDVal in soundSources) {
|
|
NSUInteger sourceID = [sourceIDVal unsignedIntValue];
|
|
alDeleteSources(1, &sourceID);
|
|
[self checkForErrors];
|
|
}
|
|
|
|
// Loop through the OpenAL buffers and delete
|
|
NSEnumerator *enumerator = [soundLibrary keyEnumerator];
|
|
id key;
|
|
while ((key = [enumerator nextObject])) {
|
|
NSNumber *bufferIDVal = [soundLibrary objectForKey:key];
|
|
NSUInteger bufferID = [bufferIDVal unsignedIntValue];
|
|
alDeleteBuffers(1, &bufferID);
|
|
[self checkForErrors];
|
|
}
|
|
|
|
// Release the arrays and dictionaries we have been using
|
|
[soundLibrary release];
|
|
[soundSources release];
|
|
[musicLibrary release];
|
|
[musicPlaylists release];
|
|
if (currentPlaylistTracks) {
|
|
[currentPlaylistTracks release];
|
|
}
|
|
|
|
// If background music has been played then release the AVAudioPlayer
|
|
if(musicPlayer)
|
|
[musicPlayer release];
|
|
|
|
// Disable and then destroy the context
|
|
alcMakeContextCurrent(NULL);
|
|
[self checkForErrors];
|
|
alcDestroyContext(context);
|
|
[self checkForErrors];
|
|
|
|
// Close the device
|
|
alcCloseDevice(device);
|
|
[self checkForErrors];
|
|
|
|
[super dealloc];
|
|
}
|
|
|
|
- (id)init {
|
|
self = [super init];
|
|
if(self != nil) {
|
|
|
|
// Initialize the array and dictionaries we are going to use
|
|
soundSources = [[NSMutableArray alloc] init];
|
|
soundLibrary = [[NSMutableDictionary alloc] init];
|
|
musicLibrary = [[NSMutableDictionary alloc] init];
|
|
musicPlaylists = [[NSMutableDictionary alloc] init];
|
|
|
|
// Grab a reference to the AVAudioSession singleton
|
|
audioSession = [AVAudioSession sharedInstance];
|
|
|
|
// Reset the error ivar
|
|
audioSessionError = nil;
|
|
|
|
// Check to see if music is already playing. If that is the case then you can leave the sound category as AmbientSound.
|
|
// If music is not playing we can set the sound category to SoloAmbientSound so that decoding is done using the hardware.
|
|
isExternalAudioPlaying = [self isAudioPlaying];
|
|
|
|
if (!isExternalAudioPlaying) {
|
|
NSLog(@"INFO - SoundManager: No external audio playing so using the SoloAmbient audio session category");
|
|
soundCategory = AVAudioSessionCategorySoloAmbient;
|
|
} else {
|
|
NSLog(@"INFO - SoundManager: External sound detected so using the Ambient audio session category");
|
|
soundCategory = AVAudioSessionCategoryAmbient;
|
|
}
|
|
|
|
// Having decided on the category we then set it
|
|
[audioSession setCategory:soundCategory error:&audioSessionError];
|
|
|
|
if (audioSessionError) {
|
|
NSLog(@"WARNING - SoundManager: Unable to set the sound category to ambient");
|
|
}
|
|
|
|
// Set up the OpenAL. If an error occurs then nil will be returned.
|
|
BOOL success = [self initOpenAL];
|
|
if(!success) {
|
|
NSLog(@"ERROR - SoundManager: Error initializing OpenAL");
|
|
return nil;
|
|
}
|
|
|
|
// Set up the listener position
|
|
float listener_pos[] = {0, 0, 0};
|
|
float listener_ori[] = {0.0, 1.0, 0.0, 0.0, 0.0, 1.0};
|
|
float listener_vel[] = {0, 0, 0};
|
|
|
|
alListenerfv(AL_POSITION, listener_pos);
|
|
[self checkForErrors];
|
|
alListenerfv(AL_ORIENTATION, listener_ori);
|
|
[self checkForErrors];
|
|
alListenerfv(AL_VELOCITY, listener_vel);
|
|
[self checkForErrors];
|
|
|
|
// Set the default volume for music and fx along the fading flag
|
|
currentMusicVolume = 0.5f;
|
|
musicVolume = 0.5f;
|
|
fxVolume = 0.5f;
|
|
playlistIndex = 0;
|
|
|
|
// Set up initial flag values
|
|
isFading = NO;
|
|
isMusicPlaying = NO;
|
|
stopMusicAfterFade = YES;
|
|
usePlaylist = NO;
|
|
loopLastPlaylistTrack = NO;
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)shutdownSoundManager {
|
|
@synchronized(self) {
|
|
if(sharedSoundManager != nil) {
|
|
[self dealloc];
|
|
}
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Sound management
|
|
|
|
- (void)loadSoundWithKey:(NSString*)aSoundKey musicFile:(NSString*)aMusicFile {
|
|
|
|
// Check to make sure that a sound with the same key does not already exist
|
|
NSNumber *numVal = [soundLibrary objectForKey:aSoundKey];
|
|
|
|
// If the key is not found log it and finish
|
|
if(numVal != nil) {
|
|
NSLog(@"WARNING - SoundManager: Sound key '%@' already exists.", aSoundKey);
|
|
return;
|
|
}
|
|
|
|
NSUInteger bufferID;
|
|
|
|
// Generate a buffer within OpenAL for this sound
|
|
alGenBuffers(1, &bufferID);
|
|
[self checkForErrors];
|
|
|
|
// Set up the variables which are going to be used to hold the format
|
|
// size and frequency of the sound file we are loading
|
|
ALenum format;
|
|
ALsizei size;
|
|
ALsizei freq;
|
|
ALvoid *data;
|
|
alError = AL_NO_ERROR;
|
|
|
|
NSBundle *bundle = [NSBundle mainBundle];
|
|
|
|
// Get the audio data from the file which has been passed in
|
|
NSString *fileName = [[aMusicFile lastPathComponent] stringByDeletingPathExtension];
|
|
NSString *fileType = [aMusicFile pathExtension];
|
|
NSString *filePath = [bundle pathForResource:fileName ofType:fileType];
|
|
if ( filePath == nil )
|
|
filePath = aMusicFile;
|
|
|
|
CFURLRef fileURL = (CFURLRef)[[NSURL fileURLWithPath: filePath] retain];
|
|
|
|
if (fileURL)
|
|
{
|
|
data = MyGetOpenALAudioData(fileURL, &size, &format, &freq);
|
|
CFRelease(fileURL);
|
|
|
|
if((alError = alGetError()) != AL_NO_ERROR) {
|
|
NSLog(@"ERROR - SoundManager: Error loading sound: %@ with error %x\n", fileName, alError);
|
|
|
|
}
|
|
|
|
// Use the static buffer data API
|
|
alBufferData(bufferID, format, data, size, freq);
|
|
[self checkForErrors];
|
|
|
|
if((alError = alGetError()) != AL_NO_ERROR) {
|
|
NSLog(@"ERROR - SoundManager: Error attaching audio to buffer: %x\n", alError);
|
|
}
|
|
|
|
// Free the memory we used when getting the audio data
|
|
if (data)
|
|
free(data);
|
|
}
|
|
else
|
|
{
|
|
NSLog(@"ERROR - SoundManager: Could not find file '%@.%@'", fileName, fileType);
|
|
if (data)
|
|
free(data);
|
|
data = NULL;
|
|
}
|
|
|
|
// Place the buffer ID into the sound library against |aSoundKey|
|
|
[soundLibrary setObject:[NSNumber numberWithUnsignedInt:bufferID] forKey:aSoundKey];
|
|
NSLog(@"INFO - SoundManager: Loaded sound with key '%@' into buffer '%d'", aSoundKey, bufferID);
|
|
}
|
|
|
|
- (void)removeSoundWithKey:(NSString*)aSoundKey {
|
|
|
|
// Reset errors in OpenAL
|
|
alError = alGetError();
|
|
alError = AL_NO_ERROR;
|
|
|
|
// Find the buffer which has been linked to the sound key provided
|
|
NSNumber *numVal = [soundLibrary objectForKey:aSoundKey];
|
|
|
|
// If the key is not found log it and finish
|
|
if(numVal == nil) {
|
|
NSLog(@"WARNING - SoundManager: No sound with key '%@' was found so cannot be removed", aSoundKey);
|
|
return;
|
|
}
|
|
|
|
// Get the buffer number from
|
|
NSUInteger bufferID = [numVal unsignedIntValue];
|
|
NSInteger bufferForSource;
|
|
NSInteger sourceState;
|
|
for(NSNumber *sourceID in soundSources) {
|
|
|
|
NSUInteger currentSourceID = [sourceID unsignedIntValue];
|
|
|
|
// Grab the current state of the source and also the buffer attached to it
|
|
alGetSourcei(currentSourceID, AL_SOURCE_STATE, &sourceState);
|
|
[self checkForErrors];
|
|
alGetSourcei(currentSourceID, AL_BUFFER, &bufferForSource);
|
|
[self checkForErrors];
|
|
|
|
// If this source is not playing then unbind it. If it is playing and the buffer it
|
|
// is playing is the one we are removing, then also unbind that source from this buffer
|
|
if(sourceState != AL_PLAYING || (sourceState == AL_PLAYING && bufferForSource == bufferID)) {
|
|
alSourceStop(currentSourceID);
|
|
[self checkForErrors];
|
|
alSourcei(currentSourceID, AL_BUFFER, 0);
|
|
[self checkForErrors];
|
|
}
|
|
}
|
|
|
|
// Delete the buffer
|
|
alDeleteBuffers(1, &bufferID);
|
|
|
|
// Check for any errors
|
|
if((alError = alGetError()) != AL_NO_ERROR) {
|
|
NSLog(@"ERROR - SoundManager: Could not delete buffer %d with error %x", bufferID, alError);
|
|
exit(1);
|
|
}
|
|
|
|
// Remove the soundkey from the soundLibrary
|
|
[soundLibrary removeObjectForKey:aSoundKey];
|
|
|
|
NSLog(@"INFO - SoundManager: Removed sound with key '%@'", aSoundKey);
|
|
}
|
|
|
|
|
|
- (void)loadBackgroundMusicWithKey:(NSString*)aMusicKey musicFile:(NSString*)aMusicFile {
|
|
|
|
// Get the filename and type from the music file name passed in
|
|
NSString *fileName = [[aMusicFile lastPathComponent] stringByDeletingPathExtension];
|
|
NSString *fileType = [aMusicFile pathExtension];
|
|
|
|
// Check to make sure that a sound with the same key does not already exist
|
|
NSString *path = [musicLibrary objectForKey:aMusicKey];
|
|
|
|
// If the key is found log it and finish
|
|
if(path != nil) {
|
|
NSLog(@"WARNING - SoundManager: Music with the key '%@' already exists.", aMusicKey);
|
|
return;
|
|
}
|
|
|
|
path = [[NSBundle mainBundle] pathForResource:fileName ofType:fileType];
|
|
if (!path) {
|
|
if ( ![[NSFileManager defaultManager] fileExistsAtPath: aMusicFile] )
|
|
{
|
|
NSLog(@"WARNING - SoundManager: Cannot find file '%@.%@'", fileName, fileType);
|
|
return;
|
|
}
|
|
else
|
|
path = aMusicFile;
|
|
}
|
|
|
|
[musicLibrary setObject:path forKey:aMusicKey];
|
|
NSLog(@"INFO - SoundManager: Loaded background music with key '%@'", aMusicKey);
|
|
}
|
|
|
|
- (void)removeBackgroundMusicWithKey:(NSString*)aMusicKey {
|
|
NSString *path = [musicLibrary objectForKey:aMusicKey];
|
|
if(path == NULL) {
|
|
NSLog(@"WARNING - SoundManager: No music found with key '%@' was found so cannot be removed", aMusicKey);
|
|
return;
|
|
}
|
|
[musicLibrary removeObjectForKey:aMusicKey];
|
|
NSLog(@"INFO - SoundManager: Removed music with key '%@'", aMusicKey);
|
|
}
|
|
|
|
- (void)addToPlaylistNamed:(NSString*)aPlaylistName track:(NSString*)aTrackName {
|
|
|
|
NSString *path = [musicLibrary objectForKey:aTrackName];
|
|
if (!path) {
|
|
NSLog(@"WARNING - SoundManager: Track '%@' does not exist in the music library and cannot be added to the play list.");
|
|
return;
|
|
}
|
|
|
|
// See if the playlist already exists
|
|
NSMutableArray *playlistTracks = [musicPlaylists objectForKey:aPlaylistName];
|
|
|
|
if (!playlistTracks) {
|
|
playlistTracks = [[NSMutableArray alloc] init];
|
|
}
|
|
|
|
[playlistTracks addObject:aTrackName];
|
|
|
|
// Add the track key to the play list
|
|
[musicPlaylists setObject:playlistTracks forKey:aPlaylistName];
|
|
}
|
|
|
|
- (void)startPlaylistNamed:(NSString*)aPlaylistName {
|
|
|
|
NSMutableArray *playlistTracks = [musicPlaylists objectForKey:aPlaylistName];
|
|
|
|
if (!playlistTracks) {
|
|
NSLog(@"WARNING - SoundManager: No play list exists with the name '%@'", aPlaylistName);
|
|
return;
|
|
}
|
|
|
|
currentPlaylistName = aPlaylistName;
|
|
currentPlaylistTracks = playlistTracks;
|
|
usePlaylist = YES;
|
|
|
|
[self playMusicWithKey:[playlistTracks objectAtIndex:0] timesToRepeat:0];
|
|
}
|
|
|
|
- (void)removeFromPlaylistNamed:(NSString*)aPlaylistName track:(NSString*)aTrackName {
|
|
|
|
NSMutableArray *playlistTracks = [musicPlaylists objectForKey:aPlaylistName];
|
|
if (playlistTracks) {
|
|
int indexToRemove;
|
|
for (int index=0; index < [currentPlaylistTracks count]; index++) {
|
|
if ([[currentPlaylistTracks objectAtIndex:index] isEqualToString:aTrackName]) {
|
|
indexToRemove = index;
|
|
break;
|
|
}
|
|
}
|
|
[currentPlaylistTracks removeObjectAtIndex:indexToRemove];
|
|
}
|
|
}
|
|
|
|
- (void)removePlaylistNamed:(NSString*)aPlaylistName {
|
|
[musicPlaylists removeObjectForKey:aPlaylistName];
|
|
}
|
|
|
|
- (void)clearPlaylistNamed:(NSString*)aPlaylistName {
|
|
NSMutableArray *playlistTracks = [musicPlaylists objectForKey:aPlaylistName];
|
|
|
|
if (playlistTracks) {
|
|
[playlistTracks removeAllObjects];
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark Sound control
|
|
|
|
- (NSUInteger)playSoundWithKey:(NSString*)aSoundKey gain:(float)aGain pitch:(float)aPitch location:(CGPoint)aLocation shouldLoop:(BOOL)aLoop sourceID:(NSUInteger)aSourceID {
|
|
|
|
// Find the buffer linked to the key which has been passed in
|
|
NSNumber *numVal = [soundLibrary objectForKey:aSoundKey];
|
|
if(numVal == nil) return 0;
|
|
NSUInteger bufferID = [numVal unsignedIntValue];
|
|
|
|
// Find an available source if -1 has been passed in as the sourceID. If the sourceID is
|
|
// not -1 i.e. a source ID has been passed in then check to make sure that source is not playing
|
|
// and if not play the identified buffer ID within the provided source
|
|
NSUInteger sourceID;
|
|
if(aSourceID == -1) {
|
|
sourceID = [self nextAvailableSource];
|
|
} else {
|
|
NSInteger sourceState;
|
|
alGetSourcei(aSourceID, AL_SOURCE_STATE, &sourceState);
|
|
if(sourceState == AL_PLAYING)
|
|
return 0;
|
|
sourceID = aSourceID;
|
|
}
|
|
|
|
// Make sure that the source is clean by resetting the buffer assigned to the source
|
|
// to 0
|
|
alSourcei(sourceID, AL_BUFFER, 0);
|
|
|
|
// Attach the buffer we have looked up to the source we have just found
|
|
alSourcei(sourceID, AL_BUFFER, bufferID);
|
|
|
|
// Set the pitch and gain of the source
|
|
alSourcef(sourceID, AL_PITCH, aPitch);
|
|
alSourcef(sourceID, AL_GAIN, aGain * fxVolume);
|
|
|
|
// Set the looping value
|
|
if(aLoop) {
|
|
alSourcei(sourceID, AL_LOOPING, AL_TRUE);
|
|
} else {
|
|
alSourcei(sourceID, AL_LOOPING, AL_FALSE);
|
|
}
|
|
|
|
// Set the source location
|
|
alSource3f(sourceID, AL_POSITION, aLocation.x, aLocation.y, 0.0f);
|
|
|
|
// Now play the sound
|
|
alSourcePlay(sourceID);
|
|
alError = alGetError();
|
|
|
|
// Check to see if there were any errors
|
|
[self checkForErrors];
|
|
|
|
// Return the source ID so that loops can be stopped etc
|
|
return sourceID;
|
|
}
|
|
|
|
|
|
- (void)stopSoundWithKey:(NSString*)aSoundKey {
|
|
|
|
// Reset errors in OpenAL
|
|
alError = alGetError();
|
|
alError = AL_NO_ERROR;
|
|
|
|
// Find the buffer which has been linked to the sound key provided
|
|
NSNumber *numVal = [soundLibrary objectForKey:aSoundKey];
|
|
|
|
// If the key is not found log it and finish
|
|
if(numVal == nil) {
|
|
NSLog(@"WARNING - SoundManager: No sound with key '%@' was found so cannot be stopped", aSoundKey);
|
|
return;
|
|
}
|
|
|
|
// Get the buffer number from
|
|
NSUInteger bufferID = [numVal unsignedIntValue];
|
|
NSInteger bufferForSource;
|
|
NSInteger sourceState;
|
|
for(NSNumber *sourceID in soundSources) {
|
|
|
|
NSUInteger currentSourceID = [sourceID unsignedIntValue];
|
|
|
|
// Grab the current state of the source and also the buffer attached to it
|
|
alGetSourcei(currentSourceID, AL_SOURCE_STATE, &sourceState);
|
|
alGetSourcei(currentSourceID, AL_BUFFER, &bufferForSource);
|
|
|
|
// If this source is not playing then unbind it. If it is playing and the buffer it
|
|
// is playing is the one we are removing, then also unbind that source from this buffer
|
|
if(bufferForSource == bufferID) {
|
|
alSourceStop(currentSourceID);
|
|
alSourcei(currentSourceID, AL_BUFFER, 0);
|
|
}
|
|
}
|
|
|
|
// Check for any errors
|
|
[self checkForErrors];
|
|
|
|
// Remove the soundkey from the soundLibrary
|
|
[soundLibrary removeObjectForKey:aSoundKey];
|
|
|
|
NSLog(@"INFO - SoundManager: Removed sound with key '%@'", aSoundKey);
|
|
}
|
|
|
|
|
|
- (void)playMusicWithKey:(NSString*)aMusicKey timesToRepeat:(NSUInteger)aRepeatCount {
|
|
|
|
NSError *error;
|
|
|
|
NSString *path = [musicLibrary objectForKey:aMusicKey];
|
|
|
|
if(!path) {
|
|
NSLog(@"ERROR - SoundManager: The music key '%@' could not be found", aMusicKey);
|
|
return;
|
|
}
|
|
|
|
if(musicPlayer)
|
|
[musicPlayer release];
|
|
|
|
// Initialize the AVAudioPlayer using the path that we have retrieved from the music library dictionary
|
|
musicPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:&error];
|
|
|
|
// If the backgroundMusicPlayer object is nil then there was an error
|
|
if(!musicPlayer) {
|
|
NSLog(@"ERROR - SoundManager: Could not play music for key '%d'", error);
|
|
return;
|
|
}
|
|
|
|
// Set the delegate for this music player to be the sound manager
|
|
musicPlayer.delegate = self;
|
|
|
|
// Set the number of times this music should repeat. -1 means never stop until its asked to stop
|
|
[musicPlayer setNumberOfLoops:aRepeatCount];
|
|
|
|
// Set the volume of the music
|
|
[musicPlayer setVolume:currentMusicVolume];
|
|
|
|
// Play the music
|
|
[musicPlayer play];
|
|
|
|
// Set the isMusicPlaying flag
|
|
isMusicPlaying = YES;
|
|
}
|
|
|
|
- (void)playNextTrack {
|
|
if (playlistIndex + 1 == [currentPlaylistTracks count]-1 && loopLastPlaylistTrack) {
|
|
playlistIndex += 1;
|
|
[self playMusicWithKey:[currentPlaylistTracks objectAtIndex:playlistIndex] timesToRepeat:-1];
|
|
} else if (playlistIndex + 1 < [currentPlaylistTracks count]) {
|
|
playlistIndex += 1;
|
|
[self playMusicWithKey:[currentPlaylistTracks objectAtIndex:playlistIndex] timesToRepeat:0];
|
|
} else if (loopPlaylist) {
|
|
playlistIndex = 0;
|
|
[self playMusicWithKey:[currentPlaylistTracks objectAtIndex:playlistIndex] timesToRepeat:0];
|
|
}
|
|
}
|
|
|
|
- (void)stopMusic {
|
|
[musicPlayer stop];
|
|
isMusicPlaying = NO;
|
|
usePlaylist = NO;
|
|
}
|
|
|
|
|
|
- (void)pauseMusic {
|
|
if(musicPlayer)
|
|
[musicPlayer pause];
|
|
isMusicPlaying = NO;
|
|
}
|
|
|
|
- (void)resumeMusic {
|
|
if (musicPlayer) {
|
|
[musicPlayer play];
|
|
isMusicPlaying = YES;
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark SoundManager settings
|
|
|
|
- (void)setMusicVolume:(float)aVolume {
|
|
|
|
// Set the volume iVar
|
|
if (aVolume > 1)
|
|
aVolume = 1.0f;
|
|
|
|
currentMusicVolume = aVolume;
|
|
musicVolume = aVolume;
|
|
|
|
// Check to make sure that the audio player exists and if so set its volume
|
|
if(musicPlayer) {
|
|
[musicPlayer setVolume:currentMusicVolume];
|
|
}
|
|
}
|
|
|
|
|
|
- (void)setFxVolume:(float)aVolume {
|
|
fxVolume = aVolume;
|
|
}
|
|
|
|
|
|
- (void)setListenerPosition:(CGPoint)aPosition {
|
|
listenerPosition = aPosition;
|
|
alListener3f(AL_POSITION, aPosition.x, aPosition.y, 0.0f);
|
|
}
|
|
|
|
|
|
- (void)setOrientation:(CGPoint)aPosition {
|
|
float orientation[] = {aPosition.x, aPosition.y, 0.0f, 0.0f, 0.0f, 1.0f};
|
|
alListenerfv(AL_ORIENTATION, orientation);
|
|
}
|
|
|
|
- (void)fadeMusicVolumeFrom:(float)aFromVolume toVolume:(float)aToVolume duration:(float)aSeconds stop:(BOOL)aStop {
|
|
|
|
// If there is already a fade timer active, invalidate it so we can start another one
|
|
if (timer) {
|
|
[timer invalidate];
|
|
timer = NULL;
|
|
}
|
|
|
|
// Work out how much to fade the music by based on the current volume, the requested volume
|
|
// and the duration
|
|
fadeAmount = (aToVolume - aFromVolume) / (aSeconds / kFadeInterval);
|
|
currentMusicVolume = aFromVolume;
|
|
|
|
// Reset the fades duration
|
|
fadeDuration = 0;
|
|
targetFadeDuration = aSeconds;
|
|
isFading = YES;
|
|
stopMusicAfterFade = aStop;
|
|
|
|
// Set up a timer that fires kFadeInterval times per second calling the fadeVolume method
|
|
timer = [NSTimer scheduledTimerWithTimeInterval:kFadeInterval target:self selector:@selector(fadeVolume:) userInfo:nil repeats:TRUE];
|
|
}
|
|
|
|
- (void)crossFadeTo:(NSString*)aTrack duration:(float)aDuration {
|
|
// TODO: Finish this method
|
|
}
|
|
|
|
|
|
#pragma mark -
|
|
#pragma mark AVAudioPlayerDelegate
|
|
|
|
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
|
|
isMusicPlaying = NO;
|
|
|
|
// If we are using a play list then handle the next track to be played
|
|
if (usePlaylist) {
|
|
[self playNextTrack];
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark AVAudioSessionDelegate
|
|
|
|
- (void)beginInterruption {
|
|
NSLog(@"BEGIN");
|
|
[self setActivated:NO];
|
|
}
|
|
|
|
- (void)endInterruption {
|
|
[self setActivated:YES];
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
#pragma mark -
|
|
#pragma mark Private implementation
|
|
|
|
@implementation SoundManager (Private)
|
|
|
|
// Define the number of sources which will be created. iPhone can have a max of 32
|
|
#define MAX_OPENAL_SOURCES 16
|
|
|
|
- (BOOL)initOpenAL {
|
|
NSLog(@"INFO - Sound Manager: Initializing sound manager");
|
|
|
|
// Get the device we are going to use for sound. Using NULL gets the default device
|
|
device = alcOpenDevice(NULL);
|
|
|
|
// If a device has been found we then need to create a context, make it current and then
|
|
// preload the OpenAL Sources
|
|
if(device) {
|
|
// Use the device we have now got to create a context in which to play our sounds
|
|
context = alcCreateContext(device, NULL);
|
|
[self checkForErrors];
|
|
|
|
// Make the context we have just created into the active context
|
|
alcMakeContextCurrent(context);
|
|
[self checkForErrors];
|
|
|
|
// Set the distance model to be used
|
|
alDistanceModel(AL_LINEAR_DISTANCE_CLAMPED);
|
|
[self checkForErrors];
|
|
|
|
// Pre-create sound sources which can be dynamically allocated to buffers (sounds)
|
|
NSUInteger sourceID;
|
|
for(int index = 0; index < MAX_OPENAL_SOURCES; index++) {
|
|
// Generate an OpenAL source
|
|
alGenSources(1, &sourceID);
|
|
[self checkForErrors];
|
|
|
|
// Configure the generated source so that sounds fade as the player moves
|
|
// away from them
|
|
alSourcef(sourceID, AL_REFERENCE_DISTANCE, 25.0f);
|
|
alSourcef(sourceID, AL_MAX_DISTANCE, 150.0f);
|
|
alSourcef(sourceID, AL_ROLLOFF_FACTOR, 6.0f);
|
|
[self checkForErrors];
|
|
|
|
// Add the generated sourceID to our array of sound sources
|
|
[soundSources addObject:[NSNumber numberWithUnsignedInt:sourceID]];
|
|
}
|
|
|
|
NSLog(@"INFO - Sound Manager: Finished initializing the sound manager");
|
|
// Return YES as we have successfully initialized OpenAL
|
|
return YES;
|
|
}
|
|
|
|
// We were unable to obtain a device for playing sound so tell the user and return NO.
|
|
NSLog(@"ERROR - SoundManager: Unable to allocate a device for sound.");
|
|
return NO;
|
|
}
|
|
|
|
|
|
- (NSUInteger)nextAvailableSource {
|
|
|
|
// Holder for the current state of the current source
|
|
NSInteger sourceState;
|
|
|
|
// Find a source which is not being used at the moment
|
|
for(NSNumber *sourceNumber in soundSources) {
|
|
alGetSourcei([sourceNumber unsignedIntValue], AL_SOURCE_STATE, &sourceState);
|
|
// If this source is not playing then return it
|
|
if(sourceState != AL_PLAYING) return [sourceNumber unsignedIntValue];
|
|
}
|
|
|
|
// If all the sources are being used we look for the first non looping source
|
|
// and use the source associated with that
|
|
NSInteger looping;
|
|
for(NSNumber *sourceNumber in soundSources) {
|
|
alGetSourcei([sourceNumber unsignedIntValue], AL_LOOPING, &looping);
|
|
if(!looping) {
|
|
// We have found a none looping source so return this source and stop checking
|
|
NSUInteger sourceID = [sourceNumber unsignedIntValue];
|
|
alSourceStop(sourceID);
|
|
return sourceID;
|
|
}
|
|
}
|
|
|
|
// If there are no looping sources to be found then just use the first source and use that
|
|
NSUInteger sourceID = [[soundSources objectAtIndex:0] unsignedIntegerValue];
|
|
alSourceStop(sourceID);
|
|
|
|
// Check for any errors that may have been raised
|
|
[self checkForErrors];
|
|
|
|
// Return the sourceID found
|
|
return sourceID;
|
|
}
|
|
|
|
|
|
#pragma mark -
|
|
#pragma mark Interruption handling
|
|
|
|
- (void)setActivated:(BOOL)aState {
|
|
|
|
OSStatus result;
|
|
|
|
if(aState) {
|
|
NSLog(@"INFO - SoundManager: OpenAL Active");
|
|
|
|
// Set the AudioSession AudioCategory to what has been defined in soundCategory
|
|
[audioSession setCategory:soundCategory error:&audioSessionError];
|
|
if(audioSessionError) {
|
|
NSLog(@"ERROR - SoundManager: Unable to set the audio session category");
|
|
return;
|
|
}
|
|
|
|
// Set the audio session state to true and report any errors
|
|
[audioSession setActive:YES error:&audioSessionError];
|
|
if (audioSessionError) {
|
|
NSLog(@"ERROR - SoundManager: Unable to set the audio session state to YES with error %d.", result);
|
|
return;
|
|
}
|
|
|
|
if (musicPlayer) {
|
|
[musicPlayer play];
|
|
}
|
|
|
|
// As we are finishing the interruption we need to bind back to our context.
|
|
alcMakeContextCurrent(context);
|
|
[self checkForErrors];
|
|
} else {
|
|
NSLog(@"INFO - SoundManager: OpenAL Inactive");
|
|
|
|
// As we are being interrupted we set the current context to NULL. If this sound manager is to be
|
|
// compaitble with firmware prior to 3.0 then the context would need to also be destroyed and
|
|
// then re-created when the interruption ended.
|
|
alcMakeContextCurrent(NULL);
|
|
[self checkForErrors];
|
|
}
|
|
}
|
|
|
|
|
|
- (BOOL)isAudioPlaying {
|
|
|
|
UInt32 audioPlaying = 0;
|
|
UInt32 audioPlayingSize = sizeof(audioPlaying);
|
|
|
|
AudioSessionGetProperty(kAudioSessionProperty_OtherAudioIsPlaying, &audioPlayingSize, &audioPlaying);
|
|
|
|
return (BOOL)audioPlaying;
|
|
}
|
|
|
|
|
|
- (void)fadeVolume:(NSTimer*)aTimer {
|
|
fadeDuration += kFadeInterval;
|
|
if (fadeDuration > targetFadeDuration) {
|
|
if (timer) {
|
|
[timer invalidate];
|
|
timer = NULL;
|
|
}
|
|
|
|
isFading = NO;
|
|
if (stopMusicAfterFade) {
|
|
[musicPlayer stop];
|
|
isMusicPlaying = NO;
|
|
}
|
|
} else {
|
|
currentMusicVolume += fadeAmount;
|
|
}
|
|
|
|
// If music is current playing then set its volume
|
|
if(isMusicPlaying) {
|
|
[musicPlayer setVolume:currentMusicVolume];
|
|
}
|
|
}
|
|
|
|
- (void)checkForErrors {
|
|
alError = alGetError();
|
|
if(alError != AL_NO_ERROR) {
|
|
NSLog(@"ERROR - SoundManager: OpenAL reported error '%d'", alError);
|
|
}
|
|
}
|
|
|
|
@end
|
|
|