Skip to content

Commit

Permalink
More error handling improvements.
Browse files Browse the repository at this point in the history
  • Loading branch information
zoul committed Nov 26, 2011
1 parent f4f971c commit fa5e1b1
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 87 deletions.
59 changes: 29 additions & 30 deletions Decoder/FIDecoder.m
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,44 @@

@implementation FIDecoder

- (NSError*) errorWithCode: (NSUInteger) errorCode message: (NSString*) message
{
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:message forKey:NSLocalizedDescriptionKey];
return [NSError errorWithDomain:FIErrorDomain code:errorCode userInfo:userInfo];
}

- (FISample*) decodeSampleAtPath: (NSString*) path error: (NSError**) error
{
NSParameterAssert(path);

OSStatus errcode = noErr;
UInt32 propertySize;
AudioFileID fileId = 0;
NSURL *fileURL = [NSURL fileURLWithPath:path];
error = error ? error : &(NSError*){ nil };

if (!path) {
*error = [FIError
errorWithMessage:@"File not found."
code:FIErrorFileReadFailed];
return nil;
}

NSURL *fileURL = [NSURL fileURLWithPath:path];
errcode = AudioFileOpenURL((__bridge CFURLRef) fileURL, kAudioFileReadPermission, 0, &fileId);
if (errcode) {
*error = [self
errorWithCode:FIErrorFileReadFailed
message:@"Can’t open file."];
*error = [FIError
errorWithMessage:@"Can’t open file."
code:FIErrorFileReadFailed];
return nil;
}

AudioStreamBasicDescription fileFormat;
propertySize = sizeof(fileFormat);
errcode = AudioFileGetProperty(fileId, kAudioFilePropertyDataFormat, &propertySize, &fileFormat);
if (errcode) {
*error = [self
errorWithCode:FIErrorFileFormatReadFailed
message:@"Can’t read file format."];
*error = [FIError
errorWithMessage:@"Can’t read file format."
code:FIErrorFileFormatReadFailed];
AudioFileClose(fileId);
return nil;
}

if (fileFormat.mFormatID != kAudioFormatLinearPCM) {
*error = [self
errorWithCode:FIErrorInvalidFileFormat
message:@"Audio format not linear PCM."];
*error = [FIError
errorWithMessage:@"Audio format not linear PCM."
code:FIErrorInvalidFileFormat];
AudioFileClose(fileId);
return nil;
}
Expand All @@ -51,9 +50,9 @@ - (FISample*) decodeSampleAtPath: (NSString*) path error: (NSError**) error
propertySize = sizeof(fileSize);
errcode = AudioFileGetProperty(fileId, kAudioFilePropertyAudioDataByteCount, &propertySize, &fileSize);
if (errcode) {
*error = [self
errorWithCode:FIErrorFileFormatReadFailed
message:@"Can’t read audio data byte count."];
*error = [FIError
errorWithMessage:@"Can’t read audio data byte count."
code:FIErrorFileFormatReadFailed];
AudioFileClose(fileId);
return nil;
}
Expand All @@ -62,28 +61,28 @@ - (FISample*) decodeSampleAtPath: (NSString*) path error: (NSError**) error
propertySize = sizeof(sampleLength);
errcode = AudioFileGetProperty(fileId, kAudioFilePropertyEstimatedDuration, &propertySize, &sampleLength);
if (errcode) {
*error = [self
errorWithCode:FIErrorFileFormatReadFailed
message:@"Can’t read estimated audio duration."];
*error = [FIError
errorWithMessage:@"Can’t read estimated audio duration."
code:FIErrorFileFormatReadFailed];
AudioFileClose(fileId);
return nil;
}

UInt32 dataSize = (UInt32) fileSize;
void *data = malloc(dataSize);
if (!data) {
*error = [self
errorWithCode:FIErrorMemoryAllocationFailed
message:@"Can’t allocate memory for audio data."];
*error = [FIError
errorWithMessage:@"Can’t allocate memory for audio data."
code:FIErrorMemoryAllocationFailed];
AudioFileClose(fileId);
return nil;
}

errcode = AudioFileReadBytes(fileId, false, 0, &dataSize, data);
if (errcode) {
*error = [self
errorWithCode:FIErrorFileFormatReadFailed
message:@"Can’t read audio data from file."];
*error = [FIError
errorWithMessage:@"Can’t read audio data from file."
code:FIErrorFileFormatReadFailed];
AudioFileClose(fileId);
free(data);
return nil;
Expand Down
4 changes: 2 additions & 2 deletions Demo/Application.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ - (void) applicationDidFinishLaunching: (UIApplication*) application
[soundEngine activateAudioSessionWithCategory:AVAudioSessionCategoryPlayback];
[soundEngine openAudioDevice];

[controller setSitarSound:[soundFactory loadSoundNamed:@"sitar.wav"]];
[controller setGunSound:[soundFactory loadSoundNamed:@"shot.wav" maxPolyphony:4]];
[controller setSitarSound:[soundFactory loadSoundNamed:@"sitar.wav" error:NULL]];
[controller setGunSound:[soundFactory loadSoundNamed:@"shot.wav" maxPolyphony:4 error:NULL]];

[window setRootViewController:controller];
[window makeKeyAndVisible];
Expand Down
11 changes: 9 additions & 2 deletions Finch/FIError.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,12 @@ enum {
FIErrorInvalidFileFormat,
FIErrorInvalidNumberOfChannels,
FIErrorInvalidEndianity,
FIErrorInvalidSampleResolution
};
FIErrorInvalidSampleResolution,
FIErrorOpenALError
};

@interface FIError : NSObject

+ (id) errorWithMessage: (NSString*) message code: (NSUInteger) errorCode;

@end
12 changes: 11 additions & 1 deletion Finch/FIError.m
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
#import "FIError.h"

NSString *const FIErrorDomain = @"FIErrorDomain";
NSString *const FIErrorDomain = @"FIErrorDomain";

@implementation FIError

+ (id) errorWithMessage: (NSString*) message code: (NSUInteger) errorCode
{
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:message forKey:NSLocalizedDescriptionKey];
return [NSError errorWithDomain:FIErrorDomain code:errorCode userInfo:userInfo];
}

@end
4 changes: 2 additions & 2 deletions Finch/FIFactory.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

- (FISoundEngine*) buildSoundEngine;

- (FISound*) loadSoundNamed: (NSString*) soundName;
- (FISound*) loadSoundNamed: (NSString*) soundName maxPolyphony: (NSUInteger) voices;
- (FISound*) loadSoundNamed: (NSString*) soundName error: (NSError**) error;
- (FISound*) loadSoundNamed: (NSString*) soundName maxPolyphony: (NSUInteger) voices error: (NSError**) error;

@end
13 changes: 5 additions & 8 deletions Finch/FIFactory.m
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,18 @@ - (FISoundEngine*) buildSoundEngine
return engine;
}

- (FISound*) loadSoundNamed: (NSString*) soundName
- (FISound*) loadSoundNamed: (NSString*) soundName error: (NSError**) error
{
NSString *path = [soundBundle pathForResource:soundName ofType:nil];
if (!path) {
return nil;
}
FISample *sample = [decoder decodeSampleAtPath:path error:NULL];
return [[FISound alloc] initWithSample:sample error:NULL];
FISample *sample = [decoder decodeSampleAtPath:path error:error];
return [[FISound alloc] initWithSample:sample error:error];
}

- (FISound*) loadSoundNamed: (NSString*) soundName maxPolyphony: (NSUInteger) voices
- (FISound*) loadSoundNamed: (NSString*) soundName maxPolyphony: (NSUInteger) voices error: (NSError**) error
{
NSMutableArray *sounds = [NSMutableArray array];
for (NSUInteger i=0; i<voices; i++) {
FISound *voice = [self loadSoundNamed:soundName];
FISound *voice = [self loadSoundNamed:soundName error:error];
if (voice == nil)
return nil;
[sounds addObject:voice];
Expand Down
4 changes: 2 additions & 2 deletions Finch/FIFactoryTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ - (void) testNonExistentSoundHandling
{
FISound *soundA = nil, *soundB = nil;
STAssertNoThrow({
soundA = [factory loadSoundNamed:@"none_such"];
soundB = [factory loadSoundNamed:@"none_such" maxPolyphony:2];
soundA = [factory loadSoundNamed:@"none_such" error:NULL];
soundB = [factory loadSoundNamed:@"none_such" maxPolyphony:2 error:NULL];
}, @"Loading a non-existent sound does not throw.");
STAssertNil(soundA, @"Loading a non-existent sound returns nil.");
STAssertNil(soundB, @"Loading a non-existent revolver sound returns nil.");
Expand Down
96 changes: 56 additions & 40 deletions Finch/FISound.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,80 +12,77 @@ @interface FISound ()
@implementation FISound
@synthesize loop, duration, gain, pitch, source, buffer;

// Clears the error flag.
- (BOOL) checkSuccessOrLog: (NSString*) msg
{
ALenum errcode;
if ((errcode = alGetError()) != AL_NO_ERROR) {
NSLog(@"%@, error code %x.", msg, errcode);
return NO;
}
return YES;
}

#pragma mark Initialization

- (BOOL) checkFormatConstaintsForSample: (FISample*) sample error: (NSError**) error
- (id) initWithSample: (FISample*) sample error: (NSError**) error
{
self = [super init];
error = error ? error : &(NSError*){ nil };

if (!sample) {
return nil;
}

// Check the number of channels
if ([sample numberOfChannels] != 1 && [sample numberOfChannels] != 2) {
*error = [NSError errorWithDomain:FIErrorDomain
code:FIErrorInvalidNumberOfChannels userInfo:nil];
return NO;
*error = [FIError
errorWithMessage:@"Invalid number of channels, only mono and stereo supported."
code:FIErrorInvalidNumberOfChannels];
return nil;
}

// Check sample resolution
if ([sample bitsPerChannel] != 8 && [sample bitsPerChannel] != 16) {
*error = [NSError errorWithDomain:FIErrorDomain
code:FIErrorInvalidSampleResolution userInfo:nil];
return NO;
*error = [FIError
errorWithMessage:@"Invalid sample resolution, only 8-bit and 16-bit samples supported."
code:FIErrorInvalidSampleResolution];
return nil;
}

// Check data endianity
if (![sample hasNativeEndianity]) {
*error = [NSError errorWithDomain:FIErrorDomain
code:FIErrorInvalidEndianity userInfo:nil];
return NO;
}

return YES;
}

- (id) initWithSample: (FISample*) sample error: (NSError**) error
{
self = [super init];

if (![self checkFormatConstaintsForSample:sample error:error]) {
*error = [FIError
errorWithMessage:@"Wrong endianity in audio data."
code:FIErrorInvalidEndianity];
return nil;
}

// We need valid OpenAL context to continue
if (!alcGetCurrentContext()) {
NSLog(@"OpenAL context not set, did you initialize Finch?");
*error = [FIError
errorWithMessage:@"OpenAL context not set, did you initialize Finch?"
code:FIErrorOpenALError];
return nil;
}

// Allocate OpenAL buffer
CLEAR_ERROR_FLAG;
alGenBuffers(1, &buffer);
if (![self checkSuccessOrLog:@"Failed to allocate OpenAL buffer"]) {
if (alGetError()) {
*error = [FIError
errorWithMessage:@"Failed to allocate OpenAL buffer."
code:FIErrorOpenALError];
return nil;
}

// Pass sound data to OpenAL
CLEAR_ERROR_FLAG;
alBufferData(buffer, [sample openALFormat], [[sample data] bytes],
[[sample data] length], [sample sampleRate]);
if (![self checkSuccessOrLog:@"Failed to fill OpenAL buffers"]) {
alBufferData(buffer, [sample openALFormat], [[sample data] bytes], [[sample data] length], [sample sampleRate]);
if (alGetError()) {
*error = [FIError
errorWithMessage:@"Failed to fill OpenAL buffers."
code:FIErrorOpenALError];
return nil;
}

// Initialize the OpenAL source
CLEAR_ERROR_FLAG;
alGenSources(1, &source);
alSourcei(source, AL_BUFFER, buffer);
if (![self checkSuccessOrLog:@"Failed to create OpenAL source"]) {
if (alGetError()) {
*error = [FIError
errorWithMessage:@"Failed to create OpenAL source."
code:FIErrorOpenALError];
return nil;
}

Expand All @@ -94,13 +91,32 @@ - (id) initWithSample: (FISample*) sample error: (NSError**) error
return self;
}

// Clears the error flag.
- (BOOL) checkSuccessOrLog: (NSString*) msg
{
ALenum errcode;
if ((errcode = alGetError()) != AL_NO_ERROR) {
NSLog(@"%@, error code %x.", msg, errcode);
return NO;
}
return YES;
}

- (void) dealloc
{
[self stop];
CLEAR_ERROR_FLAG;
alSourcei(source, AL_BUFFER, DETACH_SOURCE);
alDeleteBuffers(1, &buffer), buffer = 0;
alDeleteSources(1, &source), source = 0;
if (source) {
alSourcei(source, AL_BUFFER, DETACH_SOURCE);
}
if (buffer) {
alDeleteBuffers(1, &buffer);
buffer = 0;
}
if (source) {
alDeleteSources(1, &source);
source = 0;
}
[self checkSuccessOrLog:@"Failed to clean up after sound"];
}

Expand Down

0 comments on commit fa5e1b1

Please sign in to comment.