402 lines
12 KiB
Objective-C
Executable File
402 lines
12 KiB
Objective-C
Executable File
/* cocos2d for iPhone
|
|
*
|
|
* http://www.cocos2d-iphone.org
|
|
*
|
|
* Copyright (C) 2009 by Florin Dumitrescu.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the 'cocos2d for iPhone' license.
|
|
*
|
|
* You will find a copy of this license within the cocos2d for iPhone
|
|
* distribution inside the "LICENSE" file.
|
|
*
|
|
*
|
|
***********************************************************************
|
|
*
|
|
* Part of the source code in this class has been provided by Apple.
|
|
* For full distribution terms, see the top of MyOpenALSupport.h header file.
|
|
*
|
|
*/
|
|
|
|
#import "PASoundSource.h"
|
|
#import "PASoundMgr.h"
|
|
#import "PASoundListener.h"
|
|
#import "ivorbiscodec.h"
|
|
#import "ivorbisfile.h"
|
|
|
|
#define kBuffSize (4096)
|
|
#define kSoundReferenceDistance 20.0f
|
|
|
|
void* MyGetOpenALAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei *outSampleRate);
|
|
|
|
void* MyGetOpenALAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei *outSampleRate)
|
|
{
|
|
OSStatus err = noErr;
|
|
UInt64 fileDataSize = 0;
|
|
AudioStreamBasicDescription theFileFormat;
|
|
UInt32 thePropertySize = sizeof(theFileFormat);
|
|
AudioFileID afid = 0;
|
|
void* theData = NULL;
|
|
|
|
// Open a file with ExtAudioFileOpen()
|
|
err = AudioFileOpenURL(inFileURL, kAudioFileReadPermission, 0, &afid);
|
|
if(err) {
|
|
NSLog(@"MyGetOpenALAudioData: AudioFileOpenURL FAILED, Error = %ld", err);
|
|
goto Exit;
|
|
}
|
|
|
|
// Get the audio data format
|
|
err = AudioFileGetProperty(afid, kAudioFilePropertyDataFormat, &thePropertySize, &theFileFormat);
|
|
if(err) {
|
|
NSLog(@"PASoundEngine#MyGetOpenALAudioData: AudioFileGetProperty(kAudioFileProperty_DataFormat) FAILED, Error = %ld", err);
|
|
goto Exit;
|
|
}
|
|
|
|
if (theFileFormat.mChannelsPerFrame > 2) {
|
|
NSLog(@"PASoundEngine#MyGetOpenALAudioData - Unsupported Format, channel count is greater than stereo");
|
|
goto Exit;
|
|
}
|
|
|
|
if ((theFileFormat.mFormatID != kAudioFormatLinearPCM) || (!TestAudioFormatNativeEndian(theFileFormat))) {
|
|
NSLog(@"PASoundEngine#MyGetOpenALAudioData - Unsupported Format, must be little-endian PCM");
|
|
goto Exit;
|
|
}
|
|
|
|
if ((theFileFormat.mBitsPerChannel != 8) && (theFileFormat.mBitsPerChannel != 16)) {
|
|
NSLog(@"MyGetOpenALAudioData - Unsupported Format, must be 8 or 16 bit PCM\n");
|
|
goto Exit;
|
|
}
|
|
|
|
|
|
thePropertySize = sizeof(fileDataSize);
|
|
err = AudioFileGetProperty(afid, kAudioFilePropertyAudioDataByteCount, &thePropertySize, &fileDataSize);
|
|
if(err) {
|
|
NSLog(@"PASoundEngine#MyGetOpenALAudioData: AudioFileGetProperty(kAudioFilePropertyAudioDataByteCount) FAILED, Error = %ld", err);
|
|
goto Exit;
|
|
}
|
|
|
|
// Read all the data into memory
|
|
UInt32 dataSize = (UInt32) fileDataSize;
|
|
theData = malloc(dataSize);
|
|
if (theData)
|
|
{
|
|
AudioFileReadBytes(afid, false, 0, &dataSize, theData);
|
|
if(err == noErr)
|
|
{
|
|
// success
|
|
*outDataSize = (ALsizei)dataSize;
|
|
*outDataFormat = (theFileFormat.mChannelsPerFrame > 1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16;
|
|
*outSampleRate = (ALsizei)theFileFormat.mSampleRate;
|
|
}
|
|
else
|
|
{
|
|
// failure
|
|
free (theData);
|
|
theData = NULL; // make sure to return NULL
|
|
NSLog(@"PASoundEngine#MyGetOpenALAudioData: ExtAudioFileRead FAILED, Error = %ld", err);
|
|
goto Exit;
|
|
}
|
|
}
|
|
|
|
Exit:
|
|
// Dispose the ExtAudioFileRef, it is no longer needed
|
|
if (afid) AudioFileClose(afid);
|
|
return theData;
|
|
}
|
|
|
|
@implementation PASoundSource
|
|
|
|
@synthesize file, extension, looped, isPlaying;
|
|
|
|
- (id)init {
|
|
return nil;
|
|
}
|
|
|
|
// initializers with position
|
|
- (id)initWithPosition:(CGPoint)pos file:(NSString *)f extension:(NSString *)e looped:(BOOL)yn {
|
|
if ((self = [super init])) {
|
|
self.file = f;
|
|
self.extension = e;
|
|
self.looped = yn;
|
|
self.isPlaying = NO;
|
|
[self initBuffer];
|
|
[self initSource];
|
|
self.position = pos;
|
|
self.orientation = 0.0f;
|
|
gain = 1.0f;
|
|
}
|
|
return self;
|
|
}
|
|
- (id)initWithPosition:(CGPoint)pos file:(NSString *)f looped:(BOOL)yn{
|
|
return [self initWithPosition:pos file:f extension:@"wav" looped:yn];
|
|
}
|
|
- (id)initWithPosition:(CGPoint)pos file:(NSString *)f {
|
|
return [self initWithPosition:pos file:f extension:@"wav" looped:NO];
|
|
}
|
|
|
|
// initializers without position (will have to be spcified at play time) -- defaulting to CGPointZero
|
|
- (id)initWithFile:(NSString *)f extension:(NSString *)e looped:(BOOL)yn {
|
|
return [self initWithPosition:CGPointZero file:f extension:e looped:yn];
|
|
}
|
|
- (id)initWithFile:(NSString *)f looped:(BOOL)yn {
|
|
return [self initWithPosition:CGPointZero file:f extension:@"wav" looped:yn];
|
|
}
|
|
- (id)initWithFile:(NSString *)f {
|
|
return [self initWithPosition:CGPointZero file:f extension:@"wav" looped:NO];
|
|
}
|
|
|
|
- (void)initBuffer {
|
|
ALenum error = AL_NO_ERROR;
|
|
ALenum format = AL_FORMAT_STEREO16;
|
|
ALvoid* data = NULL;
|
|
ALsizei size = 0;
|
|
ALsizei freq = 0;
|
|
|
|
NSBundle* bundle = [NSBundle mainBundle];
|
|
|
|
// get some audio data from a wave file
|
|
CFURLRef fileURL = (CFURLRef)[[NSURL fileURLWithPath:[bundle pathForResource:self.file ofType:self.extension]] retain];
|
|
|
|
if (fileURL) {
|
|
|
|
// load buffer data based on the file format guessed from the extension
|
|
if ([self.extension isEqualToString:@"wav"]) {
|
|
// WAV
|
|
data = MyGetOpenALAudioData(fileURL, &size, &format, &freq);
|
|
|
|
} else if ([self.extension isEqualToString:@"ogg"]) {
|
|
// XXX
|
|
// XXX Big files will have a lot of performance problems
|
|
// XXX
|
|
// XXX is ov_open_callbacks more efficient ?
|
|
// XXX
|
|
// OGG
|
|
NSString *fsPath = [(NSURL *)fileURL path];
|
|
FILE *fh;
|
|
if ((fh = fopen([fsPath UTF8String], "r")) != NULL) {
|
|
// open ogg file
|
|
OggVorbis_File vf;
|
|
int eof = 0;
|
|
int current_section;
|
|
|
|
if(ov_open(fh, &vf, NULL, 0) < 0) {
|
|
NSLog(@"PASoundEngine: Input does not appear to be an Ogg bitstream");
|
|
[NSException raise:@"PASoundEngine:InvalidOggFormat" format:@"InvalidOggFormat"];
|
|
}
|
|
|
|
// get meta info (sample rate & mono/stereo format)
|
|
vorbis_info *vi = ov_info(&vf,-1);
|
|
freq = (ALsizei)vi->rate;
|
|
format = (vi->channels == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
|
|
|
|
// decode data
|
|
size = 0;
|
|
char tmpBuff[kBuffSize];
|
|
char *newData;
|
|
while(!eof) {
|
|
|
|
int ret = ov_read(&vf, &tmpBuff[0], kBuffSize, ¤t_section);
|
|
if (ret == 0) {
|
|
eof = 1;
|
|
} else if (ret < 0) {
|
|
/* error in the stream. Not a problem, just reporting it in
|
|
case we (the app) cares. In this case, we don't. */
|
|
NSLog(@"PASoundEngine:Error reading buffer");
|
|
[NSException raise:@"PASoundEngine:Error reading file" format:@"Error reading file"];
|
|
} else {
|
|
size += ret;
|
|
|
|
// 1st malloc
|
|
if( !data )
|
|
newData = malloc(ret);
|
|
else
|
|
newData = realloc( data, size);
|
|
|
|
if( ! newData ) {
|
|
NSLog(@"PASoundEngine: Not enough memory");
|
|
[NSException raise:@"PASoundEngine:NotEnoughMemory" format:@"NotEnoughMemory"];
|
|
}
|
|
data = newData;
|
|
//long long dst = (long long)data + (size-ret);
|
|
memcpy( (char*)(((char*)data) + (size-ret)), &tmpBuff[0], ret);
|
|
}
|
|
}
|
|
|
|
// close ogg file
|
|
ov_clear(&vf);
|
|
} else {
|
|
NSLog(@"PASoundEngine: Could not open file");
|
|
[NSException raise:@"PASoundEngine:InvalidOggFormat" format:@"InvalidOggFormat"];
|
|
}
|
|
fclose(fh);
|
|
}
|
|
|
|
CFRelease(fileURL);
|
|
|
|
if((error = alGetError()) != AL_NO_ERROR) {
|
|
NSLog(@"PASoundEngine: Error loading sound: %x", error);
|
|
[NSException raise:@"PASoundEngine:ErrorLoadingSound" format:@"ErrorLoadingSound"];
|
|
|
|
}
|
|
|
|
alGenBuffers(1, &buffer);
|
|
alBufferData(buffer, format, data, size, freq);
|
|
free(data);
|
|
|
|
if((error = alGetError()) != AL_NO_ERROR) {
|
|
NSLog(@"PASoundEngine: Error attaching audio to buffer: %x", error);
|
|
}
|
|
}
|
|
else {
|
|
NSLog(@"Could not find file");
|
|
[NSException raise:@"PASoundEngine:CouldNotFindfile" format:@"CouldNotFindFile"];
|
|
}
|
|
}
|
|
|
|
- (void)initSource {
|
|
ALenum error = AL_NO_ERROR;
|
|
alGetError(); // Clear the error
|
|
|
|
alGenSources(1, &source);
|
|
|
|
// Turn Looping ON?
|
|
if (self.looped) {
|
|
alSourcei(source, AL_LOOPING, AL_TRUE);
|
|
}
|
|
|
|
// Set Source Reference Distance
|
|
alSourcef(source, AL_REFERENCE_DISTANCE, kSoundReferenceDistance);
|
|
|
|
// attach OpenAL Buffer to OpenAL Source
|
|
alSourcei(source, AL_BUFFER, buffer);
|
|
|
|
if((error = alGetError()) != AL_NO_ERROR) {
|
|
NSLog(@"PASoundEngine: Error attaching buffer to source: %x", error);
|
|
[NSException raise:@"PASoundEngine:AttachingToBuffer" format:@"AttachingToBuffer"];
|
|
|
|
}
|
|
}
|
|
|
|
- (void)setGain:(float)g {
|
|
gain = g;
|
|
alSourcef(source, AL_GAIN, g * [[PASoundMgr sharedSoundManager] soundsMasterGain]);
|
|
}
|
|
- (void)setRolloff:(float)factor{
|
|
alSourcef(source, AL_ROLLOFF_FACTOR, factor);
|
|
}
|
|
- (void)setPitch:(float)factor {
|
|
alSourcef(source, AL_PITCH, factor);
|
|
}
|
|
|
|
// play messages
|
|
- (void)playAtPosition:(CGPoint)p restart:(BOOL)r {
|
|
CGPoint currentPos = [self position];
|
|
ALint state;
|
|
if ((p.x != currentPos.x) || (p.y != currentPos.y)) {
|
|
[self setPosition:p];
|
|
}
|
|
alGetSourcei(source, AL_SOURCE_STATE, &state);
|
|
if ((state == AL_PLAYING) && r) {
|
|
// stop it before replaying
|
|
[self stop];
|
|
// get current state
|
|
alGetSourcei(source, AL_SOURCE_STATE, &state);
|
|
}
|
|
if (state != AL_PLAYING) {
|
|
alGetError();
|
|
ALenum error;
|
|
[self setGain:gain];
|
|
alSourcePlay(source);
|
|
if((error = alGetError()) != AL_NO_ERROR) {
|
|
NSLog(@"PASoundEngine: Error starting source: %x", error);
|
|
} else {
|
|
// Mark our state as playing (the view looks at this)
|
|
self.isPlaying = YES;
|
|
}
|
|
}
|
|
}
|
|
- (void)playAtPosition:(CGPoint)p {
|
|
return [self playAtPosition:p restart:NO];
|
|
}
|
|
- (void)playWithRestart:(BOOL)r {
|
|
return [self playAtPosition:self.position restart:r];
|
|
}
|
|
- (void)play {
|
|
return [self playAtPosition:self.position restart:NO];
|
|
}
|
|
- (void)playAtListenerPositionWithRestart:(BOOL)r {
|
|
return [self playAtPosition:[[[PASoundMgr sharedSoundManager] listener] position] restart:r];
|
|
}
|
|
- (void)playAtListenerPosition {
|
|
return [self playAtListenerPositionWithRestart:NO];
|
|
}
|
|
|
|
- (void)stop {
|
|
alGetError();
|
|
ALenum error;
|
|
alSourceStop(source);
|
|
if((error = alGetError()) != AL_NO_ERROR) {
|
|
NSLog(@"PASoundEngine: Error stopping source: %x", error);
|
|
} else {
|
|
// Mark our state as not playing (the view looks at this)
|
|
self.isPlaying = NO;
|
|
}
|
|
}
|
|
|
|
- (CGPoint)position {
|
|
return position;
|
|
}
|
|
- (void)setPosition:(CGPoint)pos {
|
|
/* needed for Cocos2d demo 'SoundEngineTest'
|
|
float x,y;
|
|
position = pos;
|
|
switch ( [[CCDirector sharedDirector] deviceOrientation] ) {
|
|
case CCDeviceOrientationLandscapeLeft:
|
|
x = pos.x - 240.0f;
|
|
y = 160.0f - pos.y;
|
|
break;
|
|
case CCDeviceOrientationLandscapeRight:
|
|
// XXX: set correct orientation
|
|
x = pos.x - 240.0f;
|
|
y = 160.0f - pos.y;
|
|
break;
|
|
case CCDeviceOrientationPortrait:
|
|
x = pos.x;
|
|
y = pos.y;
|
|
break;
|
|
case CCDeviceOrientationPortraitUpsideDown:
|
|
// XXX: set correct orientation
|
|
x = pos.x;
|
|
y = pos.y;
|
|
break;
|
|
}
|
|
*/
|
|
float sourcePosAL[] = {pos.x, pos.y, 0.0f};
|
|
alSourcefv(source, AL_POSITION, sourcePosAL);
|
|
}
|
|
|
|
- (float)orientation {
|
|
return orientation;
|
|
}
|
|
- (void)setOrientation:(float)o {
|
|
orientation = o;
|
|
}
|
|
|
|
|
|
- (void)dealloc {
|
|
[self stop];
|
|
|
|
alGetError();
|
|
ALenum error;
|
|
alSourcei(source, AL_BUFFER, 0); // dissasociate buffer
|
|
alDeleteBuffers(1, &buffer);
|
|
if((error = alGetError()) != AL_NO_ERROR) {
|
|
printf("error deleting buffer: %x\n", error);
|
|
}
|
|
alDeleteSources(1, &source);
|
|
|
|
[super dealloc];
|
|
}
|
|
|
|
@end
|