Posts Tagged ‘music’

Playing backgroud music

May 9th, 2009

Related tutorials:

I’ve already told you how to play short sounds in your game / application. Now it’s time to play some music in the background. Although using one type of short sounds , doesn’t stop the iPod playback, adding the background music will turn iPod off.

In fact using AVAudioPlayer you can also play background music, so, in other words AVAudioPlayer can play longer sounds, not only those limited to 30 seconds. Here is the code again (remember to add a framework: AVFoundation.framework):

	AVAudioPlayer *myAVsound = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"backgroundMusic" ofType:@"caf"]  ] error:NULL];
	[myAVsound play]; 

The above code will simply play the background music [backgroundMusic.caf]. But it will play it only once. You could use NSTimer to play it several times – call [myAVsound play].

GBMusicTrack

I would like to present you very nice class – GBMusicTrack, written by Jake Peterson (aka AnotherJake). You can find more info herePlease note, that you can play only one background music at time using this class, the second and any next track can be played, once you stopped playing the previous.

GBMusicTrack.h:

//
//  GBMusicTrack.h
//  GameBase
//
//  Created by Jake Peterson (AnotherJake) on 7/6/08.
//  Copyright 2008 Jake Peterson. All rights reserved.
//

#import <UIKit/UIKit.h> // IN ORIGINAL FILE <Cocoa/Cocoa.h>
#import <AudioToolbox/AudioQueue.h>
#import <AudioToolbox/AudioFile.h>

#define NUM_QUEUE_BUFFERS	3

@interface GBMusicTrack : NSObject
{
	AudioFileID						audioFile;
	AudioStreamBasicDescription		dataFormat;
	AudioQueueRef					queue;
	UInt64							packetIndex;
	UInt32							numPacketsToRead;
	AudioStreamPacketDescription	*packetDescs;
	BOOL							repeat;
	BOOL							trackClosed;
	AudioQueueBufferRef				buffers[NUM_QUEUE_BUFFERS];
}

- (id)initWithPath:(NSString *)path;
- (void)setGain:(Float32)gain;
- (void)setRepeat:(BOOL)yn;
- (void)play;
- (void)pause;

// close is called automatically in GBMusicTrack's dealloc method, but it is recommended
// to call close first, so that the associated Audio Queue is released immediately, instead
// of having to wait for a possible autorelease, which may cause some conflict
- (void)close;

extern NSString	*GBMusicTrackFinishedPlayingNotification;

@end

GBMusicTrack.m

//
//  GBMusicTrack.m
//  GameBase
//
//  Created by Jake Peterson (AnotherJake) on 7/6/08.
//  Copyright 2008 Jake Peterson. All rights reserved.
//

#import "GBMusicTrack.h"

static UInt32 gBufferSizeBytes = 0x10000; // 64k

NSString *GBMusicTrackFinishedPlayingNotification = @"GBMusicTrackFinishedPlayingNotification";

@interface GBMusicTrack (InternalMethods)

static void BufferCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef buffer);
- (void)callbackForBuffer:(AudioQueueBufferRef)buffer;
- (UInt32)readPacketsIntoBuffer:(AudioQueueBufferRef)buffer;

@end

@implementation GBMusicTrack

#pragma mark -
#pragma mark GBMusicTrack

- (void)dealloc
{
	[self close];
	[super dealloc];
}

- (void)close
{
	// it is preferrable to call close first, before dealloc if there is a problem waiting for
	// an autorelease
	if (trackClosed)
		return;
	trackClosed = YES;
	AudioQueueStop(queue, YES);
	AudioQueueDispose(queue, YES);
	AudioFileClose(audioFile);
}

- (id)initWithPath:(NSString *)path
{
	UInt32		size, maxPacketSize;
	char		*cookie;
	int			i;

	if(!(self = [super init])) return nil;
	if (path == nil) return nil;

	// try to open up the file using the specified path
	if (noErr != AudioFileOpenURL((CFURLRef)[NSURL fileURLWithPath:path], 0x01, kAudioFileCAFType, &audioFile))
	{
		NSLog(@"GBMusicTrack Error - initWithPath: could not open audio file. Path given was: %@", path);
		return nil;
	}

	// get the data format of the file
	size = sizeof(dataFormat);
	AudioFileGetProperty(audioFile, kAudioFilePropertyDataFormat, &size, &dataFormat);

	// create a new playback queue using the specified data format and buffer callback
	AudioQueueNewOutput(&dataFormat, BufferCallback, self, nil, nil, 0, &queue);

	// calculate number of packets to read and allocate space for packet descriptions if needed
	if (dataFormat.mBytesPerPacket == 0 || dataFormat.mFramesPerPacket == 0)
	{
		// since we didn't get sizes to work with, then this must be VBR data (Variable BitRate), so
		// we'll have to ask Core Audio to give us a conservative estimate of the largest packet we are
		// likely to read with kAudioFilePropertyPacketSizeUpperBound
		size = sizeof(maxPacketSize);
		AudioFileGetProperty(audioFile, kAudioFilePropertyPacketSizeUpperBound, &size, &maxPacketSize);
		if (maxPacketSize > gBufferSizeBytes)
		{
			// hmm... well, we don't want to go over our buffer size, so we'll have to limit it I guess
			maxPacketSize = gBufferSizeBytes;
			NSLog(@"GBMusicTrack Warning - initWithPath: had to limit packet size requested for file path: %@", path);
		}
		numPacketsToRead = gBufferSizeBytes / maxPacketSize;

		// will need a packet description for each packet since this is VBR data, so allocate space accordingly
		packetDescs = malloc(sizeof(AudioStreamPacketDescription) * numPacketsToRead);
	}
	else
	{
		// for CBR data (Constant BitRate), we can simply fill each buffer with as many packets as will fit
		numPacketsToRead = gBufferSizeBytes / dataFormat.mBytesPerPacket;

		// don't need packet descriptsions for CBR data
		packetDescs = nil;
	}

	// see if file uses a magic cookie (a magic cookie is meta data which some formats use)
	AudioFileGetPropertyInfo(audioFile, kAudioFilePropertyMagicCookieData, &size, nil);
	if (size > 0)
	{
		// copy the cookie data from the file into the audio queue
		cookie = malloc(sizeof(char) * size);
		AudioFileGetProperty(audioFile, kAudioFilePropertyMagicCookieData, &size, cookie);
		AudioQueueSetProperty(queue, kAudioQueueProperty_MagicCookie, cookie, size);
		free(cookie);
	}

	// allocate and prime buffers with some data
	packetIndex = 0;
	for (i = 0; i < NUM_QUEUE_BUFFERS; i++)
	{
		AudioQueueAllocateBuffer(queue, gBufferSizeBytes, &buffers[i]);
		if ([self readPacketsIntoBuffer:buffers[i]] == 0)
		{
			// this might happen if the file was so short that it needed less buffers than we planned on using
			break;
		}
	}
	repeat = NO;
	trackClosed = NO;
	return self;
}

- (void)setGain:(Float32)gain
{
	AudioQueueSetParameter(queue, kAudioQueueParam_Volume, gain);
}

- (void)setRepeat:(BOOL)yn
{
	repeat = yn;
}

- (void)play
{
	AudioQueueStart(queue, nil);
}

- (void)pause
{
	AudioQueuePause(queue);
}

#pragma mark -
#pragma mark Callback

static void BufferCallback(void *inUserData, AudioQueueRef inAQ, AudioQueueBufferRef buffer)
{
	// redirect back to the class to handle it there instead, so we have direct access to the instance variables
	[(GBMusicTrack *)inUserData callbackForBuffer:buffer];
}

- (void)callbackForBuffer:(AudioQueueBufferRef)buffer
{
	if ([self readPacketsIntoBuffer:buffer] == 0)
	{
		// End Of File reached, so rewind and refill the buffer using the beginning of the file instead
		packetIndex = 0;
		[self readPacketsIntoBuffer:buffer];

		// if not repeating then we'll pause it so it's ready to play again immediately if needed
		if (!repeat)
		{
			AudioQueuePause(queue);

			// we're not in the main thread during this callback, so enqueue a message on the main thread to post notification
			// that we're done, or else the notification will have to be handled in this thread, making things more difficult
			[self performSelectorOnMainThread:@selector(postTrackFinishedPlayingNotification:) withObject:nil waitUntilDone:NO];
		}
	}
}

- (void)postTrackFinishedPlayingNotification:(id)object
{
	// if we're here then we're in the main thread as specified by the callback, so now we can post notification that
	// the track is done without the notification observer(s) having to worry about thread safety and autorelease pools
	[[NSNotificationCenter defaultCenter] postNotificationName:GBMusicTrackFinishedPlayingNotification object:self];
}

- (UInt32)readPacketsIntoBuffer:(AudioQueueBufferRef)buffer
{
	UInt32		numBytes, numPackets;

	// read packets into buffer from file
	numPackets = numPacketsToRead;
	AudioFileReadPackets(audioFile, NO, &numBytes, packetDescs, packetIndex, &numPackets, buffer->mAudioData);
	if (numPackets > 0)
	{
		// - End Of File has not been reached yet since we read some packets, so enqueue the buffer we just read into
		// the audio queue, to be played next
		// - (packetDescs ? numPackets : 0) means that if there are packet descriptions (which are used only for Variable
		// BitRate data (VBR)) we'll have to send one for each packet, otherwise zero
		buffer->mAudioDataByteSize = numBytes;
		AudioQueueEnqueueBuffer(queue, buffer, (packetDescs ? numPackets : 0), packetDescs);

		// move ahead to be ready for next time we need to read from the file
		packetIndex += numPackets;
	}
	return numPackets;
}

@end

If you don’t understand what every line of this code means, don’t worry. Here is the simple instruction how to use it:

GBMusicTrack *backgroundMusic = [[GBMusicTrack alloc] initWithPath:[[NSBundle mainBundle] pathForResource:@"backgroundMusic" ofType:@"caf"]];
[backgroundMusic setRepeat:YES]; // or NO if you want to play it only once
[backgroundMusic setGain:1.0]; // volume
[backgroundMusic play];

Remember to import not only GBMusicTrack.h to the class with above lines of code, but also to add AudiToolbox.framework. As you see using GBMusicTrack class is quite simple, setRepeat:YES/NO method allows you to decide if you want your music to loop constantly, setGain to adjust the volume (remember: try to avoid setting the volume louder than 1.0) and play to start playing.

Please note: compile for the version 2.2 or higher!

Please note: many people complains that anything from this tutorial doesn’t work on iPhone Simulator. Don’t worry, it works on real device.

Playing the next track using GBMusicTrack:

As I told before, this class allows you to play one track at the time. To play next, you have to call the close method and initialize it again:

	[backgroundMusic close];
	[backgroundMusic initWithPath:[[NSBundle mainBundle] pathForResource:@"nextTrack" ofType:@"caf"]];

You can download my sample project: – my music controller I’ve written using the GBMusicTrack. You can fade the music in / out, play and pause and set the volume.

musiccontrollerscreenshot

xcodeproj

Download the project

Short sounds

May 5th, 2009

Related tutorials:

Sounds are very important to put some life into your application, usually the game.

What is the difference between short sounds and long sounds? Let’s say, you are making a game and there is a constantly looping (or changing the track once played) background music. This is certainly not a short sound and it will be discuss somewhere else. You can play a short sound once you press the button, hit the wall, an alert occurred, a weapon was used… This kind of sounds could also be played as a background music but there is one problem with playing few background musics on iPhone… The iPhone processor can deal only with one background music and many sounds, that’s why music played from your library on iPhone / iPod touch stops once any app starts to play background music.

Sounds cannot be longer the 30 seconds and you should convert them before adding to the project. How? Open Terminal application on your mac and use the afconvert:

/usr/bin/afconvert -f caff -d LEI16 input_file output_file

Example:

MBP-Krzysiek:~ Krzysiek$ /usr/bin/afconvert -f caff -d LEI16 /Users/Krzysiek/Desktop/samlpe\ music.mp3 /Users/Krzysiek/Desktop/samplemusic.caf
terminal
Please bear in mind that .caf aren’t like .mp3 – the lossless format, that’s why after conversion the sound file is usually bigger than original. That’s why you shouldn’t have original files in .mp3 as shown on example, but even you have .caf, convert it anyway. afconvert converts almost any sound format I know (but I don’t know many, I’m not DJ).
OK, your sound is ready to add to your project. Now in Xcode you need to write a class responsible for playing a sound. I will show you two ways: playing a sounds using:
  • SystemSound
  • AVAudioPlayer

SystemSound

SystemSound plays any sound without affecting the background music of your application or music from iPod. It doesn’t provide any volume control, user can control volume by changing the master volume of the device. Before you start to code, you need to add an AudioToolbox framework to your project (if you don’t know how: ctrl-press any existing .framework, Reveal in Finder and drag and drop the AudioToolbox.framework to the Frameworks group).

Add a new class (name it sounds) to your project, choose NSObject subclass, but in fact, it doesn’t metter, if you replace all code in header and implementation file with these provided by me:

sounds.h

#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioServices.h>

@interface sounds : NSObject {
	SystemSoundID soundID;
}
-(id) initWithContentsOfFile:(NSString *)path;
-(void)play;
@end

sounds.m

#import "sounds.h"

@implementation sounds
-(id) initWithContentsOfFile:(NSString *)path {
	self = [super init];
	if (self != nil) {
		NSURL *filePath = [NSURL fileURLWithPath:path isDirectory:NO];
		AudioServicesCreateSystemSoundID((CFURLRef)filePath, &soundID);
	}
	return self;
}
-(void)play {
	AudioServicesPlaySystemSound(soundID);
}
-(void)dealloc {
	AudioServicesDisposeSystemSoundID(soundID);
	[super dealloc];
}
@end

Import sounds.h (#import “sounds.h”) to any class (for example game.h) that will call sounds class to play a short sound. If the sound is played many times (like in most cases) add in header the object:

	sounds *mySound;

or

	sounds *mySound1, *mySound2, *mySound3; // , *mySound4 ... *mySound(n)

or

	sounds *mySound[3];
Before any sound can be played you need to allocate it. While allocating you have to call the method initWithContentsOfFile and provide the path to the resource – sound file.
	mySound = [[sounds alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"myFunnySound" ofType:@"caf"]];
Now mySound object is ready to play myFunnySound.caf.
To play call the play method: [mySound play];. That’s all about SystemSounds.
Please note:
System Sounds can also toggle vibration, but it will be discussed in other tutorial.

AVAudioPlayer

AvAudioPlayer plays any sound also without affecting the background music of your application, but it terminates the music from iPod. It gives the user the ability to control the volume within the application (if you provide a slider or any other interface to adjust the volume), or by changing the master volume of the device. Before you can use AVAudioPlayer you need to add AVFoundation.framework to your project.

Please note:
Using AVAudioPlayer is much simpler than SystemSounds. I implemented a class to use SystemSounds, because I don’t like using such a long method names (AudioServicesPlaySystemSound) when they are called frequently. To play sound with AVAudioPlayer I need only 2-3 lines of code so writing a class to handle it is pointless and useless for me.

To prepare the sound you need to repeat almost the same steps I told in SystemSounds. Let’s add in the header file of your class an instruction to import AVAudioPlayer.h framework:

#import <AVFoundation/AVAudioPlayer.h>

And the AVAudioPlayer object with our sound:

	AVAudioPlayer *myAVsound;

To start, and assing the sound file you also need to provide a path to that resource:

	myAVsound = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"myFunnySound" ofType:@"caf"]  ] error:NULL];

The method to play this sound couldn’t be simpler:

	[myAVsound play];

… and to adjust the volume:

	myAVsound.volume = 0.6;

The volume can be adjusted also during the sound is being played.

Please note:
The given volume should be a number from range <0.0,1.0>
. When the volume=0, user won’t hear the sound at all (O RLY?). You could also set the volume=2.0 or 10.0 but be careful. Instead of the louder sound you can get an unbearable to here noise (especially when the sound is saved in low quality), it can be harmful to user’s ear, if he uses headset, or can damage the device’s speaker.

BTW:
AVAudioPlayer allows you to loop the sound (myAVsound.numberOfLoops = 2;).

Please note:

I’ve heard many problems with playing the sounds with iPhone Simulator, in other words, the project cannot be compiled to run on iPhone Simulator. I personally have this problem, but as long as I participate in iPhone Developer program. I guess (but I’m not sure) that is because of computer you use. I use MacBook Pro Mid/Late 2007 (MA895) 2.2 GHz. As long as I know, this problem doesn’t occur on MacBook Pros shipped with unibody enclosure.

That’s almost everything you need to know about short sounds. You can download the sample project below.It allows you to play a sound (included in the project) via SystemSound and via AVAudio Player with slider adjusting the volume.

screenshotsounds

xcodeproj

Download the project