// // 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 = 0; 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 arrayWithCapacity: 1]; } [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 = -1; for (NSUInteger index=0; index < [currentPlaylistTracks count]; index++) { if ([[currentPlaylistTracks objectAtIndex:index] isEqualToString:aTrackName]) { indexToRemove = index; break; } } if (indexToRemove >= 0) [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:(NSInteger)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 { 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 %@.", [audioSessionError localizedDescription]); 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