diff --git a/CocosDenshion/mac/CDAudioManager.h b/CocosDenshion/mac/CDAudioManager.h new file mode 100644 index 0000000000..af0ab4a98a --- /dev/null +++ b/CocosDenshion/mac/CDAudioManager.h @@ -0,0 +1,246 @@ +/* + Copyright (c) 2010 Steve Oldmeadow + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + $Id$ + */ + +#import "CocosDenshion.h" +#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 30000 + #import +#else + #import "CDXMacOSXSupport.h" +#endif + +/** Different modes of the engine */ +typedef enum { + kAMM_FxOnly, //!Other apps will be able to play audio + kAMM_FxPlusMusic, //!Only this app will play audio + kAMM_FxPlusMusicIfNoOtherAudio, //!If another app is playing audio at start up then allow it to continue and don't play music + kAMM_MediaPlayback, //!This app takes over audio e.g music player app + kAMM_PlayAndRecord //!App takes over audio and has input and output +} tAudioManagerMode; + +/** Possible states of the engine */ +typedef enum { + kAMStateUninitialised, //!Audio manager has not been initialised - do not use + kAMStateInitialising, //!Audio manager is in the process of initialising - do not use + kAMStateInitialised //!Audio manager is initialised - safe to use +} tAudioManagerState; + +typedef enum { + kAMRBDoNothing, //Audio manager will not do anything on resign or becoming active + kAMRBStopPlay, //Background music is stopped on resign and resumed on become active + kAMRBStop //Background music is stopped on resign but not resumed - maybe because you want to do this from within your game +} tAudioManagerResignBehavior; + +/** Notifications */ +extern NSString * const kCDN_AudioManagerInitialised; + +@interface CDAsynchInitialiser : NSOperation {} +@end + +/** CDAudioManager supports two long audio source channels called left and right*/ +typedef enum { + kASC_Left = 0, + kASC_Right = 1 +} tAudioSourceChannel; + +typedef enum { + kLAS_Init, + kLAS_Loaded, + kLAS_Playing, + kLAS_Paused, + kLAS_Stopped, +} tLongAudioSourceState; + +@class CDLongAudioSource; +@protocol CDLongAudioSourceDelegate +@optional +/** The audio source completed playing */ +- (void) cdAudioSourceDidFinishPlaying:(CDLongAudioSource *) audioSource; +/** The file used to load the audio source has changed */ +- (void) cdAudioSourceFileDidChange:(CDLongAudioSource *) audioSource; +@end + +/** + CDLongAudioSource represents an audio source that has a long duration which makes + it costly to load into memory for playback as an effect using CDSoundEngine. Examples + include background music and narration tracks. The audio file may or may not be compressed. + Bear in mind that current iDevices can only use hardware to decode a single compressed + audio file at a time and playing multiple compressed files will result in a performance drop + as software decompression will take place. + @since v0.99 + */ +@interface CDLongAudioSource : NSObject { + AVAudioPlayer *audioSourcePlayer; + NSString *audioSourceFilePath; + NSInteger numberOfLoops; + float volume; + id delegate; + BOOL mute; + BOOL enabled_; + BOOL backgroundMusic; + // whether background music is paused + BOOL paused; +@public + BOOL systemPaused;//Used for auto resign handling + NSTimeInterval systemPauseLocation;//Used for auto resign handling +@protected + tLongAudioSourceState state; +} +@property (readonly) AVAudioPlayer *audioSourcePlayer; +@property (readonly) NSString *audioSourceFilePath; +@property (readwrite, nonatomic) NSInteger numberOfLoops; +@property (readwrite, nonatomic) float volume; +@property (assign) id delegate; +/* This long audio source functions as background music */ +@property (readwrite, nonatomic) BOOL backgroundMusic; +@property (readonly) BOOL paused; + +/** Loads the file into the audio source */ +-(void) load:(NSString*) filePath; +/** Plays the audio source */ +-(void) play; +/** Stops playing the audio soruce */ +-(void) stop; +/** Pauses the audio source */ +-(void) pause; +/** Rewinds the audio source */ +-(void) rewind; +/** Resumes playing the audio source if it was paused */ +-(void) resume; +/** Returns whether or not the audio source is playing */ +-(BOOL) isPlaying; + +@end + +/** + CDAudioManager manages audio requirements for a game. It provides access to a CDSoundEngine object + for playing sound effects. It provides access to two CDLongAudioSource object (left and right channel) + for playing long duration audio such as background music and narration tracks. Additionally it manages + the audio session to take care of things like audio session interruption and interacting with the audio + of other apps that are running on the device. + + Requirements: + - Firmware: OS 2.2 or greater + - Files: CDAudioManager.*, CocosDenshion.* + - Frameworks: OpenAL, AudioToolbox, AVFoundation + @since v0.8 + */ +@interface CDAudioManager : NSObject { + CDSoundEngine *soundEngine; + CDLongAudioSource *backgroundMusic; + NSMutableArray *audioSourceChannels; + NSString* _audioSessionCategory; + BOOL _audioWasPlayingAtStartup; + tAudioManagerMode _mode; + SEL backgroundMusicCompletionSelector; + id backgroundMusicCompletionListener; + BOOL willPlayBackgroundMusic; + BOOL _mute; + BOOL _resigned; + BOOL _interrupted; + BOOL _audioSessionActive; + BOOL enabled_; + + //For handling resign/become active + BOOL _isObservingAppEvents; + tAudioManagerResignBehavior _resignBehavior; +} + +@property (readonly) CDSoundEngine *soundEngine; +@property (readonly) CDLongAudioSource *backgroundMusic; +@property (readonly) BOOL willPlayBackgroundMusic; + +/** Returns the shared singleton */ ++ (CDAudioManager *) sharedManager; ++ (tAudioManagerState) sharedManagerState; +/** Configures the shared singleton with a mode*/ ++ (void) configure: (tAudioManagerMode) mode; +/** Initializes the engine asynchronously with a mode */ ++ (void) initAsynchronously: (tAudioManagerMode) mode; +/** Initializes the engine synchronously with a mode, channel definition and a total number of channels */ +- (id) init: (tAudioManagerMode) mode; +-(void) audioSessionInterrupted; +-(void) audioSessionResumed; +-(void) setResignBehavior:(tAudioManagerResignBehavior) resignBehavior autoHandle:(BOOL) autoHandle; +/** Returns true is audio is muted at a hardware level e.g user has ringer switch set to off */ +-(BOOL) isDeviceMuted; +/** Returns true if another app is playing audio such as the iPod music player */ +-(BOOL) isOtherAudioPlaying; +/** Sets the way the audio manager interacts with the operating system such as whether it shares output with other apps or obeys the mute switch */ +-(void) setMode:(tAudioManagerMode) mode; +/** Shuts down the shared audio manager instance so that it can be reinitialised */ ++(void) end; + +/** Call if you want to use built in resign behavior but need to do some additional audio processing on resign active. */ +- (void) applicationWillResignActive; +/** Call if you want to use built in resign behavior but need to do some additional audio processing on become active. */ +- (void) applicationDidBecomeActive; + +//New AVAudioPlayer API +/** Loads the data from the specified file path to the channel's audio source */ +-(CDLongAudioSource*) audioSourceLoad:(NSString*) filePath channel:(tAudioSourceChannel) channel; +/** Retrieves the audio source for the specified channel */ +-(CDLongAudioSource*) audioSourceForChannel:(tAudioSourceChannel) channel; + +//Legacy AVAudioPlayer API +/** Plays music in background. The music can be looped or not + It is recommended to use .aac files as background music since they are decoded by the device (hardware). + */ +-(void) playBackgroundMusic:(NSString*) filePath loop:(BOOL) loop; +/** Preloads a background music */ +-(void) preloadBackgroundMusic:(NSString*) filePath; +/** Stops playing the background music */ +-(void) stopBackgroundMusic; +/** Pauses the background music */ +-(void) pauseBackgroundMusic; +/** Rewinds the background music */ +-(void) rewindBackgroundMusic; +/** Resumes playing the background music */ +-(void) resumeBackgroundMusic; +/** Returns whether or not the background music is playing */ +-(BOOL) isBackgroundMusicPlaying; + +-(void) setBackgroundMusicCompletionListener:(id) listener selector:(SEL) selector; + +@end + +/** Fader for long audio source objects */ +@interface CDLongAudioSourceFader : CDPropertyModifier{} +@end + +static const int kCDNoBuffer = -1; + +/** Allows buffers to be associated with file names */ +@interface CDBufferManager:NSObject{ + NSMutableDictionary* loadedBuffers; + NSMutableArray *freedBuffers; + CDSoundEngine *soundEngine; + int nextBufferId; +} + +-(id) initWithEngine:(CDSoundEngine *) theSoundEngine; +-(int) bufferForFile:(NSString*) filePath create:(BOOL) create; +-(void) releaseBufferForFile:(NSString *) filePath; + +@end + diff --git a/CocosDenshion/mac/CDAudioManager.m b/CocosDenshion/mac/CDAudioManager.m new file mode 100644 index 0000000000..abdf1a8908 --- /dev/null +++ b/CocosDenshion/mac/CDAudioManager.m @@ -0,0 +1,898 @@ +/* + Copyright (c) 2010 Steve Oldmeadow + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + $Id$ + */ + + +#import "CDAudioManager.h" + +NSString * const kCDN_AudioManagerInitialised = @"kCDN_AudioManagerInitialised"; + +//NSOperation object used to asynchronously initialise +@implementation CDAsynchInitialiser + +-(void) main { + [super main]; + [CDAudioManager sharedManager]; +} + +@end + +@implementation CDLongAudioSource + +@synthesize audioSourcePlayer, audioSourceFilePath, delegate, backgroundMusic, paused; + +-(id) init { + if ((self = [super init])) { + state = kLAS_Init; + volume = 1.0f; + mute = NO; + enabled_ = YES; + paused = NO; + } + return self; +} + +-(void) dealloc { + CDLOGINFO(@"Denshion::CDLongAudioSource - deallocating %@", self); + [audioSourcePlayer release]; + [audioSourceFilePath release]; + [super dealloc]; +} + +-(void) load:(NSString*) filePath { + //We have alread loaded a file previously, check if we are being asked to load the same file + if (state == kLAS_Init || ![filePath isEqualToString:audioSourceFilePath]) { + CDLOGINFO(@"Denshion::CDLongAudioSource - Loading new audio source %@",filePath); + //New file + if (state != kLAS_Init) { + [audioSourceFilePath release];//Release old file path + [audioSourcePlayer release];//Release old AVAudioPlayer, they can't be reused + } + audioSourceFilePath = [filePath copy]; + NSError *error = nil; + NSString *path = [CDUtilities fullPathFromRelativePath:audioSourceFilePath]; + audioSourcePlayer = [(AVAudioPlayer*)[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:&error]; + if (error == nil) { + [audioSourcePlayer prepareToPlay]; + audioSourcePlayer.delegate = self; + if (delegate && [delegate respondsToSelector:@selector(cdAudioSourceFileDidChange:)]) { + //Tell our delegate the file has changed + [delegate cdAudioSourceFileDidChange:self]; + } + } else { + CDLOG(@"Denshion::CDLongAudioSource - Error initialising audio player: %@",error); + } + } else { + //Same file - just return it to a consistent state + [self pause]; + [self rewind]; + } + audioSourcePlayer.volume = volume; + audioSourcePlayer.numberOfLoops = numberOfLoops; + state = kLAS_Loaded; +} + +-(void) play { + if (enabled_) { + self->systemPaused = NO; + self->paused = NO; + [audioSourcePlayer play]; + } else { + CDLOGINFO(@"Denshion::CDLongAudioSource long audio source didn't play because it is disabled"); + } +} + +-(void) stop { + self->paused = NO; + [audioSourcePlayer stop]; +} + +-(void) pause { + self->paused = YES; + [audioSourcePlayer pause]; +} + +-(void) rewind { + self->paused = NO; + [audioSourcePlayer setCurrentTime:0]; +} + +-(void) resume { + self->paused = NO; + [audioSourcePlayer play]; +} + +-(BOOL) isPlaying { + if (state != kLAS_Init) { + return [audioSourcePlayer isPlaying]; + } else { + return NO; + } +} + +-(void) setVolume:(float) newVolume +{ + volume = newVolume; + if (state != kLAS_Init && !mute) { + audioSourcePlayer.volume = newVolume; + } +} + +-(float) volume +{ + return volume; +} + +#pragma mark Audio Interrupt Protocol +-(BOOL) mute +{ + return mute; +} + +-(void) setMute:(BOOL) muteValue +{ + if (mute != muteValue) { + if (mute) { + //Turn sound back on + audioSourcePlayer.volume = volume; + } else { + audioSourcePlayer.volume = 0.0f; + } + mute = muteValue; + } +} + +-(BOOL) enabled +{ + return enabled_; +} + +-(void) setEnabled:(BOOL)enabledValue +{ + if (enabledValue != enabled_) { + enabled_ = enabledValue; + if (!enabled_) { + //"Stop" the sounds + [self pause]; + [self rewind]; + } + } +} + +-(NSInteger) numberOfLoops { + return numberOfLoops; +} + +-(void) setNumberOfLoops:(NSInteger) loopCount +{ + audioSourcePlayer.numberOfLoops = loopCount; + numberOfLoops = loopCount; +} + +- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag { + CDLOGINFO(@"Denshion::CDLongAudioSource - audio player finished"); +#if TARGET_IPHONE_SIMULATOR + CDLOGINFO(@"Denshion::CDLongAudioSource - workaround for OpenAL clobbered audio issue"); + //This is a workaround for an issue in all simulators (tested to 3.1.2). Problem is + //that OpenAL audio playback is clobbered when an AVAudioPlayer stops. Workaround + //is to keep the player playing on an endless loop with 0 volume and then when + //it is played again reset the volume and set loop count appropriately. + //NB: this workaround is not foolproof but it is good enough for most situations. + player.numberOfLoops = -1; + player.volume = 0; + [player play]; +#endif + if (delegate && [delegate respondsToSelector:@selector(cdAudioSourceDidFinishPlaying:)]) { + [delegate cdAudioSourceDidFinishPlaying:self]; + } +} + +-(void)audioPlayerBeginInterruption:(AVAudioPlayer *)player { + CDLOGINFO(@"Denshion::CDLongAudioSource - audio player interrupted"); +} + +-(void)audioPlayerEndInterruption:(AVAudioPlayer *)player { + CDLOGINFO(@"Denshion::CDLongAudioSource - audio player resumed"); + if (self.backgroundMusic) { + //Check if background music can play as rules may have changed during + //the interruption. This is to address a specific issue in 4.x when + //fast task switching + if([CDAudioManager sharedManager].willPlayBackgroundMusic) { + [player play]; + } + } else { + [player play]; + } +} + +@end + + +@interface CDAudioManager (PrivateMethods) +-(BOOL) audioSessionSetActive:(BOOL) active; +-(BOOL) audioSessionSetCategory:(NSString*) category; +-(void) badAlContextHandler; +@end + + +@implementation CDAudioManager +#define BACKGROUND_MUSIC_CHANNEL kASC_Left + +@synthesize soundEngine, willPlayBackgroundMusic; +static CDAudioManager *sharedManager; +static tAudioManagerState _sharedManagerState = kAMStateUninitialised; +static tAudioManagerMode configuredMode; +static BOOL configured = FALSE; + +-(BOOL) audioSessionSetActive:(BOOL) active { + NSError *activationError = nil; + if ([[AVAudioSession sharedInstance] setActive:active error:&activationError]) { + _audioSessionActive = active; + CDLOGINFO(@"Denshion::CDAudioManager - Audio session set active %i succeeded", active); + return YES; + } else { + //Failed + CDLOG(@"Denshion::CDAudioManager - Audio session set active %i failed with error %@", active, activationError); + return NO; + } +} + +-(BOOL) audioSessionSetCategory:(NSString*) category { + NSError *categoryError = nil; + if ([[AVAudioSession sharedInstance] setCategory:category error:&categoryError]) { + CDLOGINFO(@"Denshion::CDAudioManager - Audio session set category %@ succeeded", category); + return YES; + } else { + //Failed + CDLOG(@"Denshion::CDAudioManager - Audio session set category %@ failed with error %@", category, categoryError); + return NO; + } +} + +// Init ++ (CDAudioManager *) sharedManager +{ + @synchronized(self) { + if (!sharedManager) { + if (!configured) { + //Set defaults here + configuredMode = kAMM_FxPlusMusicIfNoOtherAudio; + } + sharedManager = [[CDAudioManager alloc] init:configuredMode]; + _sharedManagerState = kAMStateInitialised;//This is only really relevant when using asynchronous initialisation + [[NSNotificationCenter defaultCenter] postNotificationName:kCDN_AudioManagerInitialised object:nil]; + } + } + return sharedManager; +} + ++ (tAudioManagerState) sharedManagerState { + return _sharedManagerState; +} + +/** + * Call this to set up audio manager asynchronously. Initialisation is finished when sharedManagerState == kAMStateInitialised + */ ++ (void) initAsynchronously: (tAudioManagerMode) mode { + @synchronized(self) { + if (_sharedManagerState == kAMStateUninitialised) { + _sharedManagerState = kAMStateInitialising; + [CDAudioManager configure:mode]; + CDAsynchInitialiser *initOp = [[[CDAsynchInitialiser alloc] init] autorelease]; + NSOperationQueue *opQ = [[[NSOperationQueue alloc] init] autorelease]; + [opQ addOperation:initOp]; + } + } +} + ++ (id) alloc +{ + @synchronized(self) { + NSAssert(sharedManager == nil, @"Attempted to allocate a second instance of a singleton."); + return [super alloc]; + } + return nil; +} + +/* + * Call this method before accessing the shared manager in order to configure the shared audio manager + */ ++ (void) configure: (tAudioManagerMode) mode { + configuredMode = mode; + configured = TRUE; +} + +-(BOOL) isOtherAudioPlaying { + UInt32 isPlaying = 0; + UInt32 varSize = sizeof(isPlaying); + AudioSessionGetProperty (kAudioSessionProperty_OtherAudioIsPlaying, &varSize, &isPlaying); + return (isPlaying != 0); +} + +-(void) setMode:(tAudioManagerMode) mode { + + _mode = mode; + switch (_mode) { + + case kAMM_FxOnly: + //Share audio with other app + CDLOGINFO(@"Denshion::CDAudioManager - Audio will be shared"); + //_audioSessionCategory = kAudioSessionCategory_AmbientSound; + _audioSessionCategory = AVAudioSessionCategoryAmbient; + willPlayBackgroundMusic = NO; + break; + + case kAMM_FxPlusMusic: + //Use audio exclusively - if other audio is playing it will be stopped + CDLOGINFO(@"Denshion::CDAudioManager - Audio will be exclusive"); + //_audioSessionCategory = kAudioSessionCategory_SoloAmbientSound; + _audioSessionCategory = AVAudioSessionCategorySoloAmbient; + willPlayBackgroundMusic = YES; + break; + + case kAMM_MediaPlayback: + //Use audio exclusively, ignore mute switch and sleep + CDLOGINFO(@"Denshion::CDAudioManager - Media playback mode, audio will be exclusive"); + //_audioSessionCategory = kAudioSessionCategory_MediaPlayback; + _audioSessionCategory = AVAudioSessionCategoryPlayback; + willPlayBackgroundMusic = YES; + break; + + case kAMM_PlayAndRecord: + //Use audio exclusively, ignore mute switch and sleep, has inputs and outputs + CDLOGINFO(@"Denshion::CDAudioManager - Play and record mode, audio will be exclusive"); + //_audioSessionCategory = kAudioSessionCategory_PlayAndRecord; + _audioSessionCategory = AVAudioSessionCategoryPlayAndRecord; + willPlayBackgroundMusic = YES; + break; + + default: + //kAudioManagerFxPlusMusicIfNoOtherAudio + if ([self isOtherAudioPlaying]) { + CDLOGINFO(@"Denshion::CDAudioManager - Other audio is playing audio will be shared"); + //_audioSessionCategory = kAudioSessionCategory_AmbientSound; + _audioSessionCategory = AVAudioSessionCategoryAmbient; + willPlayBackgroundMusic = NO; + } else { + CDLOGINFO(@"Denshion::CDAudioManager - Other audio is not playing audio will be exclusive"); + //_audioSessionCategory = kAudioSessionCategory_SoloAmbientSound; + _audioSessionCategory = AVAudioSessionCategorySoloAmbient; + willPlayBackgroundMusic = YES; + } + + break; + } + + [self audioSessionSetCategory:_audioSessionCategory]; + +} + +/** + * This method is used to work around various bugs introduced in 4.x OS versions. In some circumstances the + * audio session is interrupted but never resumed, this results in the loss of OpenAL audio when following + * standard practices. If we detect this situation then we will attempt to resume the audio session ourselves. + * Known triggers: lock the device then unlock it (iOS 4.2 gm), playback a song using MPMediaPlayer (iOS 4.0) + */ +- (void) badAlContextHandler { + if (_interrupted && alcGetCurrentContext() == NULL) { + CDLOG(@"Denshion::CDAudioManager - bad OpenAL context detected, attempting to resume audio session"); + [self audioSessionResumed]; + } +} + +- (id) init: (tAudioManagerMode) mode { + if ((self = [super init])) { + + //Initialise the audio session + AVAudioSession* session = [AVAudioSession sharedInstance]; + session.delegate = self; + + _mode = mode; + backgroundMusicCompletionSelector = nil; + _isObservingAppEvents = FALSE; + _mute = NO; + _resigned = NO; + _interrupted = NO; + enabled_ = YES; + _audioSessionActive = NO; + [self setMode:mode]; + soundEngine = [[CDSoundEngine alloc] init]; + + //Set up audioSource channels + audioSourceChannels = [[NSMutableArray alloc] init]; + CDLongAudioSource *leftChannel = [[CDLongAudioSource alloc] init]; + leftChannel.backgroundMusic = YES; + CDLongAudioSource *rightChannel = [[CDLongAudioSource alloc] init]; + rightChannel.backgroundMusic = NO; + [audioSourceChannels insertObject:leftChannel atIndex:kASC_Left]; + [audioSourceChannels insertObject:rightChannel atIndex:kASC_Right]; + [leftChannel release]; + [rightChannel release]; + //Used to support legacy APIs + backgroundMusic = [self audioSourceForChannel:BACKGROUND_MUSIC_CHANNEL]; + backgroundMusic.delegate = self; + + //Add handler for bad al context messages, these are posted by the sound engine. + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(badAlContextHandler) name:kCDN_BadAlContext object:nil]; + + } + return self; +} + +-(void) dealloc { + CDLOGINFO(@"Denshion::CDAudioManager - deallocating"); + [self stopBackgroundMusic]; + [soundEngine release]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [self audioSessionSetActive:NO]; + [audioSourceChannels release]; + [super dealloc]; +} + +/** Retrieves the audio source for the specified channel */ +-(CDLongAudioSource*) audioSourceForChannel:(tAudioSourceChannel) channel +{ + return (CDLongAudioSource*)[audioSourceChannels objectAtIndex:channel]; +} + +/** Loads the data from the specified file path to the channel's audio source */ +-(CDLongAudioSource*) audioSourceLoad:(NSString*) filePath channel:(tAudioSourceChannel) channel +{ + CDLongAudioSource *audioSource = [self audioSourceForChannel:channel]; + if (audioSource) { + [audioSource load:filePath]; + } + return audioSource; +} + +-(BOOL) isBackgroundMusicPlaying { + return [self.backgroundMusic isPlaying]; +} + +//NB: originally I tried using a route change listener and intended to store the current route, +//however, on a 3gs running 3.1.2 no route change is generated when the user switches the +//ringer mute switch to off (i.e. enables sound) therefore polling is the only reliable way to +//determine ringer switch state +-(BOOL) isDeviceMuted { + +#if TARGET_IPHONE_SIMULATOR + //Calling audio route stuff on the simulator causes problems + return NO; +#else + CFStringRef newAudioRoute; + UInt32 propertySize = sizeof (CFStringRef); + + AudioSessionGetProperty ( + kAudioSessionProperty_AudioRoute, + &propertySize, + &newAudioRoute + ); + + if (newAudioRoute == NULL) { + //Don't expect this to happen but playing safe otherwise a null in the CFStringCompare will cause a crash + return YES; + } else { + CFComparisonResult newDeviceIsMuted = CFStringCompare ( + newAudioRoute, + (CFStringRef) @"", + 0 + ); + + return (newDeviceIsMuted == kCFCompareEqualTo); + } +#endif +} + +#pragma mark Audio Interrupt Protocol + +-(BOOL) mute { + return _mute; +} + +-(void) setMute:(BOOL) muteValue { + if (muteValue != _mute) { + _mute = muteValue; + [soundEngine setMute:muteValue]; + for( CDLongAudioSource *audioSource in audioSourceChannels) { + audioSource.mute = muteValue; + } + } +} + +-(BOOL) enabled { + return enabled_; +} + +-(void) setEnabled:(BOOL) enabledValue { + if (enabledValue != enabled_) { + enabled_ = enabledValue; + [soundEngine setEnabled:enabled_]; + for( CDLongAudioSource *audioSource in audioSourceChannels) { + audioSource.enabled = enabled_; + } + } +} + +-(CDLongAudioSource*) backgroundMusic +{ + return backgroundMusic; +} + +//Load background music ready for playing +-(void) preloadBackgroundMusic:(NSString*) filePath +{ + [self.backgroundMusic load:filePath]; +} + +-(void) playBackgroundMusic:(NSString*) filePath loop:(BOOL) loop +{ + [self.backgroundMusic load:filePath]; + + if (loop) { + [self.backgroundMusic setNumberOfLoops:-1]; + } else { + [self.backgroundMusic setNumberOfLoops:0]; + } + + if (!willPlayBackgroundMusic || _mute) { + CDLOGINFO(@"Denshion::CDAudioManager - play bgm aborted because audio is not exclusive or sound is muted"); + return; + } + + [self.backgroundMusic play]; +} + +-(void) stopBackgroundMusic +{ + [self.backgroundMusic stop]; +} + +-(void) pauseBackgroundMusic +{ + [self.backgroundMusic pause]; +} + +-(void) resumeBackgroundMusic +{ + if (!willPlayBackgroundMusic || _mute) { + CDLOGINFO(@"Denshion::CDAudioManager - resume bgm aborted because audio is not exclusive or sound is muted"); + return; + } + + if (![self.backgroundMusic paused]) { + return; + } + + [self.backgroundMusic resume]; +} + +-(void) rewindBackgroundMusic +{ + [self.backgroundMusic rewind]; +} + +-(void) setBackgroundMusicCompletionListener:(id) listener selector:(SEL) selector { + backgroundMusicCompletionListener = listener; + backgroundMusicCompletionSelector = selector; +} + +/* + * Call this method to have the audio manager automatically handle application resign and + * become active. Pass a tAudioManagerResignBehavior to indicate the desired behavior + * for resigning and becoming active again. + * + * If autohandle is YES then the applicationWillResignActive and applicationDidBecomActive + * methods are automatically called, otherwise you must call them yourself at the appropriate time. + * + * Based on idea of Dominique Bongard + */ +-(void) setResignBehavior:(tAudioManagerResignBehavior) resignBehavior autoHandle:(BOOL) autoHandle { + + if (!_isObservingAppEvents && autoHandle) { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) name:@"UIApplicationWillResignActiveNotification" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:@"UIApplicationDidBecomeActiveNotification" object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillTerminate:) name:@"UIApplicationWillTerminateNotification" object:nil]; + _isObservingAppEvents = TRUE; + } + _resignBehavior = resignBehavior; +} + +- (void) applicationWillResignActive { + self->_resigned = YES; + + //Set the audio sesssion to one that allows sharing so that other audio won't be clobbered on resume + [self audioSessionSetCategory:AVAudioSessionCategoryAmbient]; + + switch (_resignBehavior) { + + case kAMRBStopPlay: + + for( CDLongAudioSource *audioSource in audioSourceChannels) { + if (audioSource.isPlaying) { + audioSource->systemPaused = YES; + audioSource->systemPauseLocation = audioSource.audioSourcePlayer.currentTime; + [audioSource stop]; + } else { + //Music is either paused or stopped, if it is paused it will be restarted + //by OS so we will stop it. + audioSource->systemPaused = NO; + [audioSource stop]; + } + } + break; + + case kAMRBStop: + //Stop music regardless of whether it is playing or not because if it was paused + //then the OS would resume it + for( CDLongAudioSource *audioSource in audioSourceChannels) { + [audioSource stop]; + } + + default: + break; + + } + CDLOGINFO(@"Denshion::CDAudioManager - handled resign active"); +} + +//Called when application resigns active only if setResignBehavior has been called +- (void) applicationWillResignActive:(NSNotification *) notification +{ + [self applicationWillResignActive]; +} + +- (void) applicationDidBecomeActive { + + if (self->_resigned) { + _resigned = NO; + //Reset the mode incase something changed with audio while we were inactive + [self setMode:_mode]; + switch (_resignBehavior) { + + case kAMRBStopPlay: + + //Music had been stopped but stop maintains current time + //so playing again will continue from where music was before resign active. + //We check if music can be played because while we were inactive the user might have + //done something that should force music to not play such as starting a track in the iPod + if (self.willPlayBackgroundMusic) { + for( CDLongAudioSource *audioSource in audioSourceChannels) { + if (audioSource->systemPaused) { + [audioSource resume]; + audioSource->systemPaused = NO; + } + } + } + break; + + default: + break; + + } + CDLOGINFO(@"Denshion::CDAudioManager - audio manager handled become active"); + } +} + +//Called when application becomes active only if setResignBehavior has been called +- (void) applicationDidBecomeActive:(NSNotification *) notification +{ + [self applicationDidBecomeActive]; +} + +//Called when application terminates only if setResignBehavior has been called +- (void) applicationWillTerminate:(NSNotification *) notification +{ + CDLOGINFO(@"Denshion::CDAudioManager - audio manager handling terminate"); + [self stopBackgroundMusic]; +} + +/** The audio source completed playing */ +- (void) cdAudioSourceDidFinishPlaying:(CDLongAudioSource *) audioSource { + CDLOGINFO(@"Denshion::CDAudioManager - audio manager got told background music finished"); + if (backgroundMusicCompletionSelector != nil) { + [backgroundMusicCompletionListener performSelector:backgroundMusicCompletionSelector]; + } +} + +-(void) beginInterruption { + CDLOGINFO(@"Denshion::CDAudioManager - begin interruption"); + [self audioSessionInterrupted]; +} + +-(void) endInterruption { + CDLOGINFO(@"Denshion::CDAudioManager - end interruption"); + [self audioSessionResumed]; +} + +#if __CC_PLATFORM_IOS >= 40000 +-(void) endInterruptionWithFlags:(NSUInteger)flags { + CDLOGINFO(@"Denshion::CDAudioManager - interruption ended with flags %i",flags); + if (flags == AVAudioSessionInterruptionFlags_ShouldResume) { + [self audioSessionResumed]; + } +} +#endif + +-(void)audioSessionInterrupted +{ + if (!_interrupted) { + CDLOGINFO(@"Denshion::CDAudioManager - Audio session interrupted"); + _interrupted = YES; + + // Deactivate the current audio session + [self audioSessionSetActive:NO]; + + if (alcGetCurrentContext() != NULL) { + CDLOGINFO(@"Denshion::CDAudioManager - Setting OpenAL context to NULL"); + + ALenum error = AL_NO_ERROR; + + // set the current context to NULL will 'shutdown' openAL + alcMakeContextCurrent(NULL); + + if((error = alGetError()) != AL_NO_ERROR) { + CDLOG(@"Denshion::CDAudioManager - Error making context current %x\n", error); + } + #pragma unused(error) + } + } +} + +-(void)audioSessionResumed +{ + if (_interrupted) { + CDLOGINFO(@"Denshion::CDAudioManager - Audio session resumed"); + _interrupted = NO; + + BOOL activationResult = NO; + // Reactivate the current audio session + activationResult = [self audioSessionSetActive:YES]; + + //This code is to handle a problem with iOS 4.0 and 4.01 where reactivating the session can fail if + //task switching is performed too rapidly. A test case that reliably reproduces the issue is to call the + //iPhone and then hang up after two rings (timing may vary ;)) + //Basically we keep waiting and trying to let the OS catch up with itself but the number of tries is + //limited. + if (!activationResult) { + CDLOG(@"Denshion::CDAudioManager - Failure reactivating audio session, will try wait-try cycle"); + int activateCount = 0; + while (!activationResult && activateCount < 10) { + [NSThread sleepForTimeInterval:0.5]; + activationResult = [self audioSessionSetActive:YES]; + activateCount++; + CDLOGINFO(@"Denshion::CDAudioManager - Reactivation attempt %i status = %i",activateCount,activationResult); + } + } + + if (alcGetCurrentContext() == NULL) { + CDLOGINFO(@"Denshion::CDAudioManager - Restoring OpenAL context"); + ALenum error = AL_NO_ERROR; + // Restore open al context + alcMakeContextCurrent([soundEngine openALContext]); + if((error = alGetError()) != AL_NO_ERROR) { + CDLOG(@"Denshion::CDAudioManager - Error making context current%x\n", error); + } + #pragma unused(error) + } + } +} + ++(void) end { + [sharedManager release]; + sharedManager = nil; +} + +@end + +/////////////////////////////////////////////////////////////////////////////////////// +@implementation CDLongAudioSourceFader + +-(void) _setTargetProperty:(float) newVal { + ((CDLongAudioSource*)target).volume = newVal; +} + +-(float) _getTargetProperty { + return ((CDLongAudioSource*)target).volume; +} + +-(void) _stopTarget { + //Pause instead of stop as stop releases resources and causes problems in the simulator + [((CDLongAudioSource*)target) pause]; +} + +-(Class) _allowableType { + return [CDLongAudioSource class]; +} + +@end +/////////////////////////////////////////////////////////////////////////////////////// +@implementation CDBufferManager + +-(id) initWithEngine:(CDSoundEngine *) theSoundEngine { + if ((self = [super init])) { + soundEngine = theSoundEngine; + loadedBuffers = [[NSMutableDictionary alloc] initWithCapacity:CD_BUFFERS_START]; + freedBuffers = [[NSMutableArray alloc] init]; + nextBufferId = 0; + } + return self; +} + +-(void) dealloc { + [loadedBuffers release]; + [freedBuffers release]; + [super dealloc]; +} + +-(int) bufferForFile:(NSString*) filePath create:(BOOL) create { + + NSNumber* soundId = (NSNumber*)[loadedBuffers objectForKey:filePath]; + if(soundId == nil) + { + if (create) { + NSNumber* bufferId = nil; + //First try to get a buffer from the free buffers + if ([freedBuffers count] > 0) { + bufferId = [[[freedBuffers lastObject] retain] autorelease]; + [freedBuffers removeLastObject]; + CDLOGINFO(@"Denshion::CDBufferManager reusing buffer id %i",[bufferId intValue]); + } else { + bufferId = [[NSNumber alloc] initWithInt:nextBufferId]; + [bufferId autorelease]; + CDLOGINFO(@"Denshion::CDBufferManager generating new buffer id %i",[bufferId intValue]); + nextBufferId++; + } + + if ([soundEngine loadBuffer:[bufferId intValue] filePath:filePath]) { + //File successfully loaded + CDLOGINFO(@"Denshion::CDBufferManager buffer loaded %@ %@",bufferId,filePath); + [loadedBuffers setObject:bufferId forKey:filePath]; + return [bufferId intValue]; + } else { + //File didn't load, put buffer id on free list + [freedBuffers addObject:bufferId]; + return kCDNoBuffer; + } + } else { + //No matching buffer was found + return kCDNoBuffer; + } + } else { + return [soundId intValue]; + } +} + +-(void) releaseBufferForFile:(NSString *) filePath { + int bufferId = [self bufferForFile:filePath create:NO]; + if (bufferId != kCDNoBuffer) { + [soundEngine unloadBuffer:bufferId]; + [loadedBuffers removeObjectForKey:filePath]; + NSNumber *freedBufferId = [[NSNumber alloc] initWithInt:bufferId]; + [freedBufferId autorelease]; + [freedBuffers addObject:freedBufferId]; + } +} +@end + + + diff --git a/CocosDenshion/mac/CDConfig.h b/CocosDenshion/mac/CDConfig.h new file mode 100644 index 0000000000..2bd8f760c1 --- /dev/null +++ b/CocosDenshion/mac/CDConfig.h @@ -0,0 +1,60 @@ +/* + Copyright (c) 2010 Steve Oldmeadow + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + $Id$ + */ +#define COCOSDENSHION_VERSION "Aphex.rc" + + +/** + If enabled code useful for debugging such as parameter check assertions will be performed. + If you experience any problems you should enable this and test your code with a debug build. + */ +//#define CD_DEBUG 1 + +/** + The total number of sounds/buffers that can be loaded assuming memory is sufficient + */ +//Number of buffers slots that will be initially created +#define CD_BUFFERS_START 64 +//Number of buffers that will be added +#define CD_BUFFERS_INCREMENT 16 + +/** + If enabled, OpenAL code will use static buffers. When static buffers are used the audio + data is managed outside of OpenAL, this eliminates a memcpy operation which leads to + higher performance when loading sounds. + + However, the downside is that when the audio data is freed you must + be certain that it is no longer being accessed otherwise your app will crash. Testing on OS 2.2.1 + and 3.1.2 has shown that this may occur if a buffer is being used by a source with state = AL_PLAYING + when the buffer is deleted. If the data is freed too quickly after the source is stopped then + a crash will occur. The implemented workaround is that when static buffers are used the unloadBuffer code will wait for + any playing sources to finish playing before the associated buffer and data are deleted, however, this delay may negate any + performance gains that are achieved during loading. + + Performance tests on a 1st gen iPod running OS 2.2.1 loading the CocosDenshionDemo sounds were ~0.14 seconds without + static buffers and ~0.12 seconds when using static buffers. + + */ +//#define CD_USE_STATIC_BUFFERS 1 + + diff --git a/CocosDenshion/mac/CDOpenALSupport.h b/CocosDenshion/mac/CDOpenALSupport.h new file mode 100644 index 0000000000..aeac861c96 --- /dev/null +++ b/CocosDenshion/mac/CDOpenALSupport.h @@ -0,0 +1,77 @@ +/* + + Disclaimer: IMPORTANT: This Apple software is supplied to you by + Apple Inc. ("Apple") in consideration of your agreement to the + following terms, and your use, installation, modification or + redistribution of this Apple software constitutes acceptance of these + terms. If you do not agree with these terms, please do not use, + install, modify or redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and + subject to these terms, Apple grants you a personal, non-exclusive + license, under Apple's copyrights in this original Apple software (the + "Apple Software"), to use, reproduce, modify and redistribute the Apple + Software, with or without modifications, in source and/or binary forms; + provided that if you redistribute the Apple Software in its entirety and + without modifications, you must retain this notice and the following + text and disclaimers in all such redistributions of the Apple Software. + Neither the name, trademarks, service marks or logos of Apple Inc. + may be used to endorse or promote products derived from the Apple + Software without specific prior written permission from Apple. Except + as expressly stated in this notice, no other rights or licenses, express + or implied, are granted by Apple herein, including but not limited to + any patent rights that may be infringed by your derivative works or by + other works in which the Apple Software may be incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE + MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION + THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND + OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, + MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED + AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), + STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + Copyright (C) 2009 Apple Inc. All Rights Reserved. + + $Id$ + */ + +/* + This file contains code from version 1.1 and 1.4 of MyOpenALSupport.h taken from Apple's oalTouch version. + The 1.4 version code is used for loading IMA4 files, however, this code causes very noticeable clicking + when used to load wave files that are looped so the 1.1 version code is used specifically for loading + wav files. + */ + +#ifndef __CD_OPENAL_H +#define __CD_OPENAL_H + +#ifdef __cplusplus +extern "C" { +#endif + + +#import +#import +#import + + +//Taken from oalTouch MyOpenALSupport 1.1 +void* CDloadWaveAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei* outSampleRate); +void* CDloadCafAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei* outSampleRate); +void* CDGetOpenALAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei* outSampleRate); + +#ifdef __cplusplus +} +#endif + +#endif + + diff --git a/CocosDenshion/mac/CDOpenALSupport.m b/CocosDenshion/mac/CDOpenALSupport.m new file mode 100644 index 0000000000..22eb5a51d7 --- /dev/null +++ b/CocosDenshion/mac/CDOpenALSupport.m @@ -0,0 +1,248 @@ +/* + + Disclaimer: IMPORTANT: This Apple software is supplied to you by + Apple Inc. ("Apple") in consideration of your agreement to the + following terms, and your use, installation, modification or + redistribution of this Apple software constitutes acceptance of these + terms. If you do not agree with these terms, please do not use, + install, modify or redistribute this Apple software. + + In consideration of your agreement to abide by the following terms, and + subject to these terms, Apple grants you a personal, non-exclusive + license, under Apple's copyrights in this original Apple software (the + "Apple Software"), to use, reproduce, modify and redistribute the Apple + Software, with or without modifications, in source and/or binary forms; + provided that if you redistribute the Apple Software in its entirety and + without modifications, you must retain this notice and the following + text and disclaimers in all such redistributions of the Apple Software. + Neither the name, trademarks, service marks or logos of Apple Inc. + may be used to endorse or promote products derived from the Apple + Software without specific prior written permission from Apple. Except + as expressly stated in this notice, no other rights or licenses, express + or implied, are granted by Apple herein, including but not limited to + any patent rights that may be infringed by your derivative works or by + other works in which the Apple Software may be incorporated. + + The Apple Software is provided by Apple on an "AS IS" basis. APPLE + MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION + THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND + OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. + + IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, + MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED + AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), + STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. + + Copyright (C) 2009 Apple Inc. All Rights Reserved. + + $Id: CDOpenALSupport.h 16 2010-03-11 06:22:10Z steveoldmeadow $ + */ + +#import "CDOpenALSupport.h" +#import "CocosDenshion.h" +#import +#import + +//Taken from oalTouch MyOpenALSupport 1.1 +void* CDloadWaveAudioData(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) { CDLOG(@"MyGetOpenALAudioData: AudioFileOpenURL FAILED, Error = %ld\n", err); goto Exit; } + + // Get the audio data format + err = AudioFileGetProperty(afid, kAudioFilePropertyDataFormat, &thePropertySize, &theFileFormat); + if(err) { CDLOG(@"MyGetOpenALAudioData: AudioFileGetProperty(kAudioFileProperty_DataFormat) FAILED, Error = %ld\n", err); goto Exit; } + + if (theFileFormat.mChannelsPerFrame > 2) { + CDLOG(@"MyGetOpenALAudioData - Unsupported Format, channel count is greater than stereo\n"); goto Exit; + } + + if ((theFileFormat.mFormatID != kAudioFormatLinearPCM) || (!TestAudioFormatNativeEndian(theFileFormat))) { + CDLOG(@"MyGetOpenALAudioData - Unsupported Format, must be little-endian PCM\n"); goto Exit; + } + + if ((theFileFormat.mBitsPerChannel != 8) && (theFileFormat.mBitsPerChannel != 16)) { + CDLOG(@"MyGetOpenALAudioData - Unsupported Format, must be 8 or 16 bit PCM\n"); goto Exit; + } + + + thePropertySize = sizeof(fileDataSize); + err = AudioFileGetProperty(afid, kAudioFilePropertyAudioDataByteCount, &thePropertySize, &fileDataSize); + if(err) { CDLOG(@"MyGetOpenALAudioData: AudioFileGetProperty(kAudioFilePropertyAudioDataByteCount) FAILED, Error = %ld\n", err); goto Exit; } + + // Read all the data into memory + UInt32 dataSize = (UInt32)fileDataSize; + theData = malloc(dataSize); + if (theData) + { + memset(theData, 0, dataSize); + AudioFileReadBytes(afid, false, 0, &dataSize, theData); + if(err == noErr) + { + // success + *outDataSize = (ALsizei)dataSize; + //This fix was added by me, however, 8 bit sounds have a clipping sound at the end so aren't really usable (SO) + if (theFileFormat.mBitsPerChannel == 16) { + *outDataFormat = (theFileFormat.mChannelsPerFrame > 1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16; + } else { + *outDataFormat = (theFileFormat.mChannelsPerFrame > 1) ? AL_FORMAT_STEREO8 : AL_FORMAT_MONO8; + } + *outSampleRate = (ALsizei)theFileFormat.mSampleRate; + } + else + { + // failure + free (theData); + theData = NULL; // make sure to return NULL + CDLOG(@"MyGetOpenALAudioData: ExtAudioFileRead FAILED, Error = %ld\n", err); goto Exit; + } + } + +Exit: + // Dispose the ExtAudioFileRef, it is no longer needed + if (afid) AudioFileClose(afid); + return theData; +} + +//Taken from oalTouch MyOpenALSupport 1.4 +void* CDloadCafAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei* outSampleRate) +{ + OSStatus status = noErr; + BOOL abort = NO; + SInt64 theFileLengthInFrames = 0; + AudioStreamBasicDescription theFileFormat; + UInt32 thePropertySize = sizeof(theFileFormat); + ExtAudioFileRef extRef = NULL; + void* theData = NULL; + AudioStreamBasicDescription theOutputFormat; + UInt32 dataSize = 0; + + // Open a file with ExtAudioFileOpen() + status = ExtAudioFileOpenURL(inFileURL, &extRef); + if (status != noErr) + { + CDLOG(@"MyGetOpenALAudioData: ExtAudioFileOpenURL FAILED, Error = %ld\n", status); + abort = YES; + } + if (abort) + goto Exit; + + // Get the audio data format + status = ExtAudioFileGetProperty(extRef, kExtAudioFileProperty_FileDataFormat, &thePropertySize, &theFileFormat); + if (status != noErr) + { + CDLOG(@"MyGetOpenALAudioData: ExtAudioFileGetProperty(kExtAudioFileProperty_FileDataFormat) FAILED, Error = %ld\n", status); + abort = YES; + } + if (abort) + goto Exit; + + if (theFileFormat.mChannelsPerFrame > 2) + { + CDLOG(@"MyGetOpenALAudioData - Unsupported Format, channel count is greater than stereo\n"); + abort = YES; + } + if (abort) + goto Exit; + + // Set the client format to 16 bit signed integer (native-endian) data + // Maintain the channel count and sample rate of the original source format + theOutputFormat.mSampleRate = theFileFormat.mSampleRate; + theOutputFormat.mChannelsPerFrame = theFileFormat.mChannelsPerFrame; + + theOutputFormat.mFormatID = kAudioFormatLinearPCM; + theOutputFormat.mBytesPerPacket = 2 * theOutputFormat.mChannelsPerFrame; + theOutputFormat.mFramesPerPacket = 1; + theOutputFormat.mBytesPerFrame = 2 * theOutputFormat.mChannelsPerFrame; + theOutputFormat.mBitsPerChannel = 16; + theOutputFormat.mFormatFlags = kAudioFormatFlagsNativeEndian | kAudioFormatFlagIsPacked | kAudioFormatFlagIsSignedInteger; + + // Set the desired client (output) data format + status = ExtAudioFileSetProperty(extRef, kExtAudioFileProperty_ClientDataFormat, sizeof(theOutputFormat), &theOutputFormat); + if (status != noErr) + { + CDLOG(@"MyGetOpenALAudioData: ExtAudioFileSetProperty(kExtAudioFileProperty_ClientDataFormat) FAILED, Error = %ld\n", status); + abort = YES; + } + if (abort) + goto Exit; + + // Get the total frame count + thePropertySize = sizeof(theFileLengthInFrames); + status = ExtAudioFileGetProperty(extRef, kExtAudioFileProperty_FileLengthFrames, &thePropertySize, &theFileLengthInFrames); + if (status != noErr) + { + CDLOG(@"MyGetOpenALAudioData: ExtAudioFileGetProperty(kExtAudioFileProperty_FileLengthFrames) FAILED, Error = %ld\n", status); + abort = YES; + } + if (abort) + goto Exit; + + // Read all the data into memory + dataSize = (UInt32) theFileLengthInFrames * theOutputFormat.mBytesPerFrame; + theData = malloc(dataSize); + if (theData) + { + memset(theData, 0, dataSize); + AudioBufferList theDataBuffer; + theDataBuffer.mNumberBuffers = 1; + theDataBuffer.mBuffers[0].mDataByteSize = dataSize; + theDataBuffer.mBuffers[0].mNumberChannels = theOutputFormat.mChannelsPerFrame; + theDataBuffer.mBuffers[0].mData = theData; + + // Read the data into an AudioBufferList + status = ExtAudioFileRead(extRef, (UInt32*)&theFileLengthInFrames, &theDataBuffer); + if(status == noErr) + { + // success + *outDataSize = (ALsizei)dataSize; + *outDataFormat = (theOutputFormat.mChannelsPerFrame > 1) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16; + *outSampleRate = (ALsizei)theOutputFormat.mSampleRate; + } + else + { + // failure + free (theData); + theData = NULL; // make sure to return NULL + CDLOG(@"MyGetOpenALAudioData: ExtAudioFileRead FAILED, Error = %ld\n", status); + abort = YES; + } + } + if (abort) + goto Exit; + +Exit: + // Dispose the ExtAudioFileRef, it is no longer needed + if (extRef) ExtAudioFileDispose(extRef); + return theData; +} + +void* CDGetOpenALAudioData(CFURLRef inFileURL, ALsizei *outDataSize, ALenum *outDataFormat, ALsizei* outSampleRate) { + + CFStringRef extension = CFURLCopyPathExtension(inFileURL); + CFComparisonResult isWavFile = 0; + if (extension != NULL) { + isWavFile = CFStringCompare (extension,(CFStringRef)@"wav", kCFCompareCaseInsensitive); + CFRelease(extension); + } + + if (isWavFile == kCFCompareEqualTo) { + return CDloadWaveAudioData(inFileURL, outDataSize, outDataFormat, outSampleRate); + } else { + return CDloadCafAudioData(inFileURL, outDataSize, outDataFormat, outSampleRate); + } +} + diff --git a/CocosDenshion/mac/CDXMacOSXSupport.h b/CocosDenshion/mac/CDXMacOSXSupport.h new file mode 100755 index 0000000000..7b85b9a1b3 --- /dev/null +++ b/CocosDenshion/mac/CDXMacOSXSupport.h @@ -0,0 +1,238 @@ +/* + Copyright (c) 2010 Steve Oldmeadow + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + $Id$ + */ + +/** + A set of proxy classes to allow iOS audio code to run on MacOS X. AVAudioPlayer is implemented using NSSound. + AVAudioSession is a "do nothing" class as it isn't really relevant on MacOS X. + + Limitations: + AVAudioPlayer numberOfLoops not correctly supported. Looping is either on or off, can not specify a specific number of loops. + AVAudioPlayer panning not supported. + AVAudioPlayer metering not supported. + AVAudioSession nothing is supported, not applicable to MacOS X. + */ + +#import +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED +#elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED) + +#import +#import + +enum AudioSessionProperties { + kAudioSessionProperty_OtherAudioIsPlaying, + kAudioSessionProperty_AudioRoute +}; +#ifdef __cplusplus +extern "C" { +#endif + +extern OSStatus AudioSessionGetProperty(UInt32 inID, UInt32 *ioDataSize, void *outData); + +#ifdef __cplusplus +} +#endif +/** + Based on AVAudioPlayer.h header in AVFoundation headers + */ +@class NSData, NSURL, NSError, NSDictionary; +@protocol AVAudioPlayerDelegate; + +/* This class is available with iPhone 2.2 or later */ +@interface AVAudioPlayer : NSObject { + + // properties + id delegate; + NSUInteger numberOfChannels; + BOOL playing; + NSTimeInterval duration; + NSURL *url; + NSData *data; + float pan; + float volume; + NSTimeInterval currentTime; + NSTimeInterval deviceCurrentTime; + NSInteger numberOfLoops; + BOOL meteringEnabled; + + +@private + NSSound* _player; +} + +/* For all of these init calls, if a return value of nil is given you can check outError to see what the problem was. + If not nil, then the object is usable for playing + */ + +/* all data must be in the form of an audio file understood by CoreAudio */ +- (id)initWithContentsOfURL:(NSURL *)theUrl error:(NSError **)outError; +- (id)initWithData:(NSData *)theData error:(NSError **)outError; + +/* transport control */ +/* methods that return BOOL return YES on success and NO on failure. */ +- (BOOL)prepareToPlay; /* get ready to play the sound. happens automatically on play. */ +- (BOOL)play; /* sound is played asynchronously. */ +- (BOOL)playAtTime:(NSTimeInterval) time; /* play a sound some time in the future. time should be greater than deviceCurrentTime. */ +- (void)pause; /* pauses playback, but remains ready to play. */ +- (void)stop; /* stops playback. no longer ready to play. */ + +/* properties */ + +@property(readonly, getter=isPlaying) BOOL playing; + +@property(readonly) NSUInteger numberOfChannels; +@property(readonly) NSTimeInterval duration; /* the duration of the sound. */ + +@property(assign) id delegate; /* the delegate will be sent playerDidFinishPlaying */ + +/* one of these three properties will be non-nil based on the init... method used */ +@property(readonly) NSURL *url; /* returns nil if object was not created with a URL */ +@property(readonly) NSData *data; /* returns nil if object was not created with a data object */ +@property float pan; /* set panning. -1.0 is left, 0.0 is center, 1.0 is right. */ +@property float volume; /* The volume for the sound. The nominal range is from 0.0 to 1.0. */ + +/* If the sound is playing, currentTime is the offset into the sound of the current playback position. + If the sound is not playing, currentTime is the offset into the sound where playing would start. */ +@property NSTimeInterval currentTime; + +/* returns the current time associated with the output device */ +@property(readonly) NSTimeInterval deviceCurrentTime; + +/* "numberOfLoops" is the number of times that the sound will return to the beginning upon reaching the end. + A value of zero means to play the sound just once. + A value of one will result in playing the sound twice, and so on.. + Any negative number will loop indefinitely until stopped. + */ +@property NSInteger numberOfLoops; + +/* metering */ + +@property(getter=isMeteringEnabled) BOOL meteringEnabled; /* turns level metering on or off. default is off. */ + +- (void)updateMeters; /* call to refresh meter values */ +- (float)peakPowerForChannel:(NSUInteger)channelNumber; /* returns peak power in decibels for a given channel */ +- (float)averagePowerForChannel:(NSUInteger)channelNumber; /* returns average power in decibels for a given channel */ + +@end + +/* A protocol for delegates of AVAudioPlayer */ +@protocol AVAudioPlayerDelegate +@optional +/* audioPlayerDidFinishPlaying:successfully: is called when a sound has finished playing. This method is NOT called if the player is stopped due to an interruption. */ +- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag; + +/* if an error occurs while decoding it will be reported to the delegate. */ +- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error; + +/* audioPlayerBeginInterruption: is called when the audio session has been interrupted while the player was playing. The player will have been paused. */ +- (void)audioPlayerBeginInterruption:(AVAudioPlayer *)player; + +/* audioPlayerEndInterruption:withFlags: is called when the audio session interruption has ended and this player had been interrupted while playing. */ +/* Currently the only flag is AVAudioSessionInterruptionFlags_ShouldResume. */ +- (void)audioPlayerEndInterruption:(AVAudioPlayer *)player withFlags:(NSUInteger)flags; + +/* audioPlayerEndInterruption: is called when the preferred method, audioPlayerEndInterruption:withFlags:, is not implemented. */ +- (void)audioPlayerEndInterruption:(AVAudioPlayer *)player; +@end + + +/** + Taken from AVAudioSession.h header in AVFoundation headers + */ + +/* This protocol is available with iPhone 3.0 or later */ +@protocol AVAudioSessionDelegate; +@class NSError, NSString; + +/* values for the category property */ +extern NSString *const AVAudioSessionCategoryAmbient; +extern NSString *const AVAudioSessionCategorySoloAmbient; +extern NSString *const AVAudioSessionCategoryPlayback; +extern NSString *const AVAudioSessionCategoryRecord; +extern NSString *const AVAudioSessionCategoryPlayAndRecord; +extern NSString *const AVAudioSessionCategoryAudioProcessing; + +enum { + AVAudioSessionInterruptionFlags_ShouldResume = 1 +}; + +enum { + AVAudioSessionSetActiveFlags_NotifyOthersOnDeactivation = 1 +}; + +@interface AVAudioSession : NSObject { + + // properties + NSString* category; + double preferredHardwareSampleRate; + NSTimeInterval preferredIOBufferDuration; + + BOOL inputIsAvailable; + double currentHardwareSampleRate; + NSInteger currentHardwareInputNumberOfChannels; + NSInteger currentHardwareOutputNumberOfChannels; + id delegate; + +@private + __strong void *_impl; +} + +/* returns singleton instance */ ++ (id)sharedInstance; + +@property(assign) id delegate; + +- (BOOL)setActive:(BOOL)beActive error:(NSError**)outError; +- (BOOL)setActive:(BOOL)beActive withFlags:(NSInteger)flags error:(NSError**)outError; + +- (BOOL)setCategory:(NSString*)theCategory error:(NSError**)outError; +- (BOOL)setPreferredHardwareSampleRate:(double)sampleRate error:(NSError**)outError; +- (BOOL)setPreferredIOBufferDuration:(NSTimeInterval)duration error:(NSError**)outError; + +@property(readonly) NSString* category; +@property(readonly) double preferredHardwareSampleRate; +@property(readonly) NSTimeInterval preferredIOBufferDuration; + +@property(readonly) BOOL inputIsAvailable; +@property(readonly) double currentHardwareSampleRate; +@property(readonly) NSInteger currentHardwareInputNumberOfChannels; +@property(readonly) NSInteger currentHardwareOutputNumberOfChannels; + +@end + + +/* A protocol for delegates of AVAudioSession */ +@protocol AVAudioSessionDelegate +@optional + +- (void)beginInterruption; + +- (void)endInterruptionWithFlags:(NSUInteger)flags; + +- (void)endInterruption; /* endInterruptionWithFlags: will be called instead if implemented. */ + +- (void)inputIsAvailableChanged:(BOOL)isInputAvailable; +@end + +#endif diff --git a/CocosDenshion/mac/CDXMacOSXSupport.mm b/CocosDenshion/mac/CDXMacOSXSupport.mm new file mode 100755 index 0000000000..a873c4097a --- /dev/null +++ b/CocosDenshion/mac/CDXMacOSXSupport.mm @@ -0,0 +1,176 @@ +/* + Copyright (c) 2010 Steve Oldmeadow + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + $Id$ + */ + +#import +#ifdef __IPHONE_OS_VERSION_MAX_ALLOWED +#elif defined(__MAC_OS_X_VERSION_MAX_ALLOWED) + +#import "CDXMacOSXSupport.h" +#import "SimpleAudioEngine.h" +#import "CocosDenshion.h" + +NSString * const AVAudioSessionCategoryAmbient = @"AVAudioSessionCategoryAmbient"; +NSString *const AVAudioSessionCategorySoloAmbient = @"AVAudioSessionCategorySoloAmbient"; +NSString *const AVAudioSessionCategoryPlayback = @"AVAudioSessionCategoryPlayback"; +NSString *const AVAudioSessionCategoryRecord = @"AVAudioSessionCategoryRecord"; +NSString *const AVAudioSessionCategoryPlayAndRecord = @"AVAudioSessionCategoryPlayAndRecord"; +NSString *const AVAudioSessionCategoryAudioProcessing = @"AVAudioSessionCategoryAudioProcessing"; + +OSStatus AudioSessionGetProperty(UInt32 inID, UInt32 *ioDataSize, void *outData) { + //TODO: set outData appropriately + return 0; +} + +@implementation AVAudioPlayer + +@synthesize delegate, numberOfChannels, pan, deviceCurrentTime, url, data; + +- (id)initWithContentsOfURL:(NSURL *)theUrl error:(NSError **)outError { + if ((self = [super init])) { + _player = [[NSSound alloc] initWithContentsOfURL:theUrl byReference:YES]; + if (_player != nil) { + _player.delegate = self; + CDLOG(@"Denshion::CDXMacOSXSupport - NSSound allocated for %@", theUrl); + } + } + return self; +} + +- (id)initWithData:(NSData *)theData error:(NSError **)outError { + if ((self = [super init])) { + _player = [[NSSound alloc] initWithData:theData]; + if (_player != nil) { + _player.delegate = self; + CDLOG(@"Denshion::CDXMacOSXSupport - NSSound allocated for %@", theData); + } + } + return self; +} + + +-(void) dealloc { + [_player release]; + [super dealloc]; +} + +- (void)sound:(NSSound *)sound didFinishPlaying:(BOOL)finished { + if (self.delegate && [self.delegate respondsToSelector:@selector(audioPlayerDidFinishPlaying:successfully:)]) { + [self.delegate audioPlayerDidFinishPlaying:self successfully:finished]; + } +} + +- (BOOL)play { + BOOL result; + result = [_player play]; + if (!result) { + //May be paused, try resuming instead + result = [_player resume]; + } + return result; +} + +-(void) pause { + [_player pause]; +} + +-(void) stop { + [_player stop]; +} + +-(BOOL) isPlaying { + return [_player isPlaying]; +} + +-(void) setVolume:(float) vol { + [_player setVolume:vol]; +} + +-(float) volume { + return [_player volume]; +} + +-(void) setNumberOfLoops:(NSInteger) nOfLoops { + if (nOfLoops < 0) { + [_player setLoops:YES]; + } else { + [_player setLoops:NO]; + } +} + +-(NSInteger) numberOfLoops { + if (_player.loops) { + return -1; + } else { + return 0; + } +} + +-(void) setCurrentTime:(NSTimeInterval) aCurrentTime { + [_player setCurrentTime:aCurrentTime]; +} + +-(NSTimeInterval) currentTime { + return [_player currentTime]; +} + +-(NSTimeInterval) duration { + return [_player duration]; +} + +#pragma mark unsupported +- (BOOL)prepareToPlay { + return YES; +} +-(BOOL)playAtTime:(NSTimeInterval)time { + return YES; +} +-(void) setMeteringEnabled:(BOOL) enabled { +} +-(BOOL) isMeteringEnabled { + return NO; +} +- (void)updateMeters{} +- (float)peakPowerForChannel:(NSUInteger)channelNumber{return 0.0f;} +- (float)averagePowerForChannel:(NSUInteger)channelNumber{return 0.0f;} +@end + +/** + A "do nothing" implementation - AVAudioSession is not really relevant to Mac OS X. + */ +@implementation AVAudioSession +@synthesize delegate, category, preferredHardwareSampleRate, preferredIOBufferDuration; +@synthesize inputIsAvailable, currentHardwareSampleRate, currentHardwareInputNumberOfChannels, currentHardwareOutputNumberOfChannels; + ++ (id)sharedInstance { + return nil; +} + +- (BOOL)setActive:(BOOL)beActive error:(NSError**)outError {return YES;} +- (BOOL)setActive:(BOOL)beActive withFlags:(NSInteger)flags error:(NSError**)outError {return YES;} +- (BOOL)setCategory:(NSString*)theCategory error:(NSError**)outError {return YES;} +- (BOOL)setPreferredHardwareSampleRate:(double)sampleRate error:(NSError**)outError {return YES;} +- (BOOL)setPreferredIOBufferDuration:(NSTimeInterval)duration error:(NSError**)outError {return YES;} + +@end +#endif \ No newline at end of file diff --git a/CocosDenshion/mac/CocosDenshion.h b/CocosDenshion/mac/CocosDenshion.h new file mode 100644 index 0000000000..e74f110f65 --- /dev/null +++ b/CocosDenshion/mac/CocosDenshion.h @@ -0,0 +1,448 @@ +/* + Copyright (c) 2010 Steve Oldmeadow + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + $Id$ + */ + + + +/** +@file +@b IMPORTANT +There are 3 different ways of using CocosDenshion. Depending on which you choose you +will need to include different files and frameworks. + +@par SimpleAudioEngine +This is recommended for basic audio requirements. If you just want to play some sound fx +and some background music and have no interest in learning the lower level workings then +this is the interface to use. + +Requirements: + - Firmware: OS 2.2 or greater + - Files: SimpleAudioEngine.*, CocosDenshion.* + - Frameworks: OpenAL, AudioToolbox, AVFoundation + +@par CDAudioManager +CDAudioManager is basically a thin wrapper around an AVAudioPlayer object used for playing +background music and a CDSoundEngine object used for playing sound effects. It manages the +audio session for you deals with audio session interruption. It is fairly low level and it +is expected you have some understanding of the underlying technologies. For example, for +many use cases regarding background music it is expected you will work directly with the +backgroundMusic AVAudioPlayer which is exposed as a property. + +Requirements: + - Firmware: OS 2.2 or greater + - Files: CDAudioManager.*, CocosDenshion.* + - Frameworks: OpenAL, AudioToolbox, AVFoundation + +@par CDSoundEngine +CDSoundEngine is a sound engine built upon OpenAL and derived from Apple's oalTouch +example. It can playback up to 32 sounds simultaneously with control over pitch, pan +and gain. It can be set up to handle audio session interruption automatically. You +may decide to use CDSoundEngine directly instead of CDAudioManager or SimpleAudioEngine +because you require OS 2.0 compatibility. + +Requirements: + - Firmware: OS 2.0 or greater + - Files: CocosDenshion.* + - Frameworks: OpenAL, AudioToolbox + +*/ + +#import +#import +#import +#import +#import "CDConfig.h" + + +#if !defined(CD_DEBUG) || CD_DEBUG == 0 +#define CDLOG(...) do {} while (0) +#define CDLOGINFO(...) do {} while (0) + +#elif CD_DEBUG == 1 +#define CDLOG(...) NSLog(__VA_ARGS__) +#define CDLOGINFO(...) do {} while (0) + +#elif CD_DEBUG > 1 +#define CDLOG(...) NSLog(__VA_ARGS__) +#define CDLOGINFO(...) NSLog(__VA_ARGS__) +#endif // CD_DEBUG + + +#import "CDOpenALSupport.h" + +//Tested source limit on 2.2.1 and 3.1.2 with up to 128 sources and appears to work. Older OS versions e.g 2.2 may support only 32 +#define CD_SOURCE_LIMIT 32 //Total number of sources we will ever want, may actually get less +#define CD_NO_SOURCE 0xFEEDFAC //Return value indicating playback failed i.e. no source +#define CD_IGNORE_AUDIO_SESSION 0xBEEFBEE //Used internally to indicate audio session will not be handled +#define CD_MUTE 0xFEEDBAB //Return value indicating sound engine is muted or non functioning +#define CD_NO_SOUND = -1; + +#define CD_SAMPLE_RATE_HIGH 44100 +#define CD_SAMPLE_RATE_MID 22050 +#define CD_SAMPLE_RATE_LOW 16000 +#define CD_SAMPLE_RATE_BASIC 8000 +#define CD_SAMPLE_RATE_DEFAULT 44100 + +extern NSString * const kCDN_BadAlContext; +extern NSString * const kCDN_AsynchLoadComplete; + +extern float const kCD_PitchDefault; +extern float const kCD_PitchLowerOneOctave; +extern float const kCD_PitchHigherOneOctave; +extern float const kCD_PanDefault; +extern float const kCD_PanFullLeft; +extern float const kCD_PanFullRight; +extern float const kCD_GainDefault; + +enum bufferState { + CD_BS_EMPTY = 0, + CD_BS_LOADED = 1, + CD_BS_FAILED = 2 +}; + +typedef struct _sourceGroup { + int startIndex; + int currentIndex; + int totalSources; + bool enabled; + bool nonInterruptible; + int *sourceStatuses;//pointer into array of source status information +} sourceGroup; + +typedef struct _bufferInfo { + ALuint bufferId; + int bufferState; + void* bufferData; + ALenum format; + ALsizei sizeInBytes; + ALsizei frequencyInHertz; +} bufferInfo; + +typedef struct _sourceInfo { + bool usable; + ALuint sourceId; + ALuint attachedBufferId; +} sourceInfo; + +#pragma mark CDAudioTransportProtocol + +@protocol CDAudioTransportProtocol +/** Play the audio */ +-(BOOL) play; +/** Pause the audio, retain resources */ +-(BOOL) pause; +/** Stop the audio, release resources */ +-(BOOL) stop; +/** Return playback to beginning */ +-(BOOL) rewind; +@end + +#pragma mark CDAudioInterruptProtocol + +@protocol CDAudioInterruptProtocol +/** Is audio mute */ +-(BOOL) mute; +/** If YES then audio is silenced but not stopped, calls to start new audio will proceed but silently */ +-(void) setMute:(BOOL) muteValue; +/** Is audio enabled */ +-(BOOL) enabled; +/** If NO then all audio is stopped and any calls to start new audio will be ignored */ +-(void) setEnabled:(BOOL) enabledValue; +@end + +#pragma mark CDUtilities +/** + Collection of utilities required by CocosDenshion + */ +@interface CDUtilities : NSObject +{ +} + +/** Fundamentally the same as the corresponding method is CCFileUtils but added to break binding to cocos2d */ ++(NSString*) fullPathFromRelativePath:(NSString*) relPath; + +@end + + +#pragma mark CDSoundEngine + +/** CDSoundEngine is built upon OpenAL and works with SDK 2.0. + CDSoundEngine is a sound engine built upon OpenAL and derived from Apple's oalTouch + example. It can playback up to 32 sounds simultaneously with control over pitch, pan + and gain. It can be set up to handle audio session interruption automatically. You + may decide to use CDSoundEngine directly instead of CDAudioManager or SimpleAudioEngine + because you require OS 2.0 compatibility. + + Requirements: + - Firmware: OS 2.0 or greater + - Files: CocosDenshion.* + - Frameworks: OpenAL, AudioToolbox + + @since v0.8 + */ +@class CDSoundSource; +@interface CDSoundEngine : NSObject { + + bufferInfo *_buffers; + sourceInfo *_sources; + sourceGroup *_sourceGroups; + ALCcontext *context; + NSUInteger _sourceGroupTotal; + UInt32 _audioSessionCategory; + BOOL _handleAudioSession; + ALfloat _preMuteGain; + NSObject *_mutexBufferLoad; + BOOL mute_; + BOOL enabled_; + + ALenum lastErrorCode_; + BOOL functioning_; + float asynchLoadProgress_; + BOOL getGainWorks_; + + //For managing dynamic allocation of sources and buffers + int sourceTotal_; + int bufferTotal; + +} + +@property (readwrite, nonatomic) ALfloat masterGain; +@property (readonly) ALenum lastErrorCode;//Last OpenAL error code that was generated +@property (readonly) BOOL functioning;//Is the sound engine functioning +@property (readwrite) float asynchLoadProgress; +@property (readonly) BOOL getGainWorks;//Does getting the gain for a source work +/** Total number of sources available */ +@property (readonly) int sourceTotal; +/** Total number of source groups that have been defined */ +@property (readonly) NSUInteger sourceGroupTotal; + +/** Sets the sample rate for the audio mixer. For best performance this should match the sample rate of your audio content */ ++(void) setMixerSampleRate:(Float32) sampleRate; + +/** Initializes the engine with a group definition and a total number of groups */ +-(id)init; + +/** Plays a sound in a channel group with a pitch, pan and gain. The sound could played looped or not */ +-(ALuint) playSound:(int) soundId sourceGroupId:(int)sourceGroupId pitch:(float) pitch pan:(float) pan gain:(float) gain loop:(BOOL) loop; + +/** Creates and returns a sound source object for the specified sound within the specified source group. + */ +-(CDSoundSource *) soundSourceForSound:(int) soundId sourceGroupId:(int) sourceGroupId; + +/** Stops playing a sound */ +- (void) stopSound:(ALuint) sourceId; +/** Stops playing a source group */ +- (void) stopSourceGroup:(int) sourceGroupId; +/** Stops all playing sounds */ +-(void) stopAllSounds; +/** Pause a sound */ +-(void) pauseSound:(ALuint) sourceId; +/** Pause all sounds */ +-(void) pauseAllSounds; +/** Resume a sound */ +-(void) resumeSound:(ALuint) sourceId; +/** Resume all sounds */ +-(void) resumeAllSounds; +-(void) defineSourceGroups:(NSArray*) sourceGroupDefinitions; +-(void) defineSourceGroups:(int[]) sourceGroupDefinitions total:(NSUInteger) total; +-(void) setSourceGroupNonInterruptible:(int) sourceGroupId isNonInterruptible:(BOOL) isNonInterruptible; +-(void) setSourceGroupEnabled:(int) sourceGroupId enabled:(BOOL) enabled; +-(BOOL) sourceGroupEnabled:(int) sourceGroupId; +-(BOOL) loadBufferFromData:(int) soundId soundData:(ALvoid*) soundData format:(ALenum) format size:(ALsizei) size freq:(ALsizei) freq; +-(BOOL) loadBuffer:(int) soundId filePath:(NSString*) filePath; +-(void) loadBuffersAsynchronously:(NSArray *) loadRequests; +-(BOOL) unloadBuffer:(int) soundId; +-(ALCcontext *) openALContext; + +/** Returns the duration of the buffer in seconds or a negative value if the buffer id is invalid */ +-(float) bufferDurationInSeconds:(int) soundId; +/** Returns the size of the buffer in bytes or a negative value if the buffer id is invalid */ +-(ALsizei) bufferSizeInBytes:(int) soundId; +/** Returns the sampling frequency of the buffer in hertz or a negative value if the buffer id is invalid */ +-(ALsizei) bufferFrequencyInHertz:(int) soundId; + +/** Used internally, never call unless you know what you are doing */ +-(void) _soundSourcePreRelease:(CDSoundSource *) soundSource; + +@end + +#pragma mark CDSoundSource +/** CDSoundSource is a wrapper around an OpenAL sound source. + It allows you to manipulate properties such as pitch, gain, pan and looping while the + sound is playing. CDSoundSource is based on the old CDSourceWrapper class but with much + added functionality. + + @since v1.0 + */ +@interface CDSoundSource : NSObject { + ALenum lastError; +@public + ALuint _sourceId; + ALuint _sourceIndex; + CDSoundEngine* _engine; + int _soundId; + float _preMuteGain; + BOOL enabled_; + BOOL mute_; +} +@property (readwrite, nonatomic) float pitch; +@property (readwrite, nonatomic) float gain; +@property (readwrite, nonatomic) float pan; +@property (readwrite, nonatomic) BOOL looping; +@property (readonly) BOOL isPlaying; +@property (readwrite, nonatomic) int soundId; +/** Returns the duration of the attached buffer in seconds or a negative value if the buffer is invalid */ +@property (readonly) float durationInSeconds; + +/** Stores the last error code that occurred. Check against AL_NO_ERROR */ +@property (readonly) ALenum lastError; +/** Do not init yourself, get an instance from the sourceForSound factory method on CDSoundEngine */ +-(id)init:(ALuint) theSourceId sourceIndex:(int) index soundEngine:(CDSoundEngine*) engine; + +@end + +#pragma mark CDAudioInterruptTargetGroup + +/** Container for objects that implement audio interrupt protocol i.e. they can be muted and enabled. + Setting mute and enabled for the group propagates to all children. + Designed to be used with your CDSoundSource objects to get them to comply with global enabled and mute settings + if that is what you want to do.*/ +@interface CDAudioInterruptTargetGroup : NSObject { + BOOL mute_; + BOOL enabled_; + NSMutableArray *children_; +} +-(void) addAudioInterruptTarget:(NSObject*) interruptibleTarget; +@end + +#pragma mark CDAsynchBufferLoader + +/** CDAsynchBufferLoader + TODO + */ +@interface CDAsynchBufferLoader : NSOperation { + NSArray *_loadRequests; + CDSoundEngine *_soundEngine; +} + +-(id) init:(NSArray *)loadRequests soundEngine:(CDSoundEngine *) theSoundEngine; + +@end + +#pragma mark CDBufferLoadRequest + +/** CDBufferLoadRequest */ +@interface CDBufferLoadRequest: NSObject +{ + NSString *filePath; + int soundId; + //id loader; +} + +@property (readonly) NSString *filePath; +@property (readonly) int soundId; + +- (id)init:(int) theSoundId filePath:(const NSString *) theFilePath; +@end + +/** Interpolation type */ +typedef enum { + kIT_Linear, //!Straight linear interpolation fade + kIT_SCurve, //!S curved interpolation + kIT_Exponential //!Exponential interpolation +} tCDInterpolationType; + +#pragma mark CDFloatInterpolator +@interface CDFloatInterpolator: NSObject +{ + float start; + float end; + float lastValue; + tCDInterpolationType interpolationType; +} +@property (readwrite, nonatomic) float start; +@property (readwrite, nonatomic) float end; +@property (readwrite, nonatomic) tCDInterpolationType interpolationType; + +/** Return a value between min and max based on t which represents fractional progress where 0 is the start + and 1 is the end */ +-(float) interpolate:(float) t; +-(id) init:(tCDInterpolationType) type startVal:(float) startVal endVal:(float) endVal; + +@end + +#pragma mark CDPropertyModifier + +/** Base class for classes that modify properties such as pitch, pan and gain */ +@interface CDPropertyModifier: NSObject +{ + CDFloatInterpolator *interpolator; + float startValue; + float endValue; + id target; + BOOL stopTargetWhenComplete; + +} +@property (readwrite, nonatomic) BOOL stopTargetWhenComplete; +@property (readwrite, nonatomic) float startValue; +@property (readwrite, nonatomic) float endValue; +@property (readwrite, nonatomic) tCDInterpolationType interpolationType; + +-(id) init:(id) theTarget interpolationType:(tCDInterpolationType) type startVal:(float) startVal endVal:(float) endVal; +/** Set to a fractional value between 0 and 1 where 0 equals the start and 1 equals the end*/ +-(void) modify:(float) t; + +-(void) _setTargetProperty:(float) newVal; +-(float) _getTargetProperty; +-(void) _stopTarget; +-(Class) _allowableType; + +@end + +#pragma mark CDSoundSourceFader + +/** Fader for CDSoundSource objects */ +@interface CDSoundSourceFader : CDPropertyModifier{} +@end + +#pragma mark CDSoundSourcePanner + +/** Panner for CDSoundSource objects */ +@interface CDSoundSourcePanner : CDPropertyModifier{} +@end + +#pragma mark CDSoundSourcePitchBender + +/** Pitch bender for CDSoundSource objects */ +@interface CDSoundSourcePitchBender : CDPropertyModifier{} +@end + +#pragma mark CDSoundEngineFader + +/** Fader for CDSoundEngine objects */ +@interface CDSoundEngineFader : CDPropertyModifier{} +@end + + + + diff --git a/CocosDenshion/mac/CocosDenshion.m b/CocosDenshion/mac/CocosDenshion.m new file mode 100644 index 0000000000..8e8cd9c8cc --- /dev/null +++ b/CocosDenshion/mac/CocosDenshion.m @@ -0,0 +1,1640 @@ +/* + Copyright (c) 2010 Steve Oldmeadow + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + $Id$ + */ + +#import "CocosDenshion.h" + +ALvoid alBufferDataStaticProc(const ALint bid, ALenum format, ALvoid* data, ALsizei size, ALsizei freq); +ALvoid alcMacOSXMixerOutputRateProc(const ALdouble value); + + +typedef ALvoid AL_APIENTRY (*alBufferDataStaticProcPtr) (const ALint bid, ALenum format, ALvoid* data, ALsizei size, ALsizei freq); +ALvoid alBufferDataStaticProc(const ALint bid, ALenum format, ALvoid* data, ALsizei size, ALsizei freq) +{ + static alBufferDataStaticProcPtr proc = NULL; + + if (proc == NULL) { + proc = (alBufferDataStaticProcPtr) alcGetProcAddress(NULL, (const ALCchar*) "alBufferDataStatic"); + } + + if (proc) + proc(bid, format, data, size, freq); + + return; +} + +typedef ALvoid AL_APIENTRY (*alcMacOSXMixerOutputRateProcPtr) (const ALdouble value); +ALvoid alcMacOSXMixerOutputRateProc(const ALdouble value) +{ + static alcMacOSXMixerOutputRateProcPtr proc = NULL; + + if (proc == NULL) { + proc = (alcMacOSXMixerOutputRateProcPtr) alcGetProcAddress(NULL, (const ALCchar*) "alcMacOSXMixerOutputRate"); + } + + if (proc) + proc(value); + + return; +} + +NSString * const kCDN_BadAlContext = @"kCDN_BadAlContext"; +NSString * const kCDN_AsynchLoadComplete = @"kCDN_AsynchLoadComplete"; +float const kCD_PitchDefault = 1.0f; +float const kCD_PitchLowerOneOctave = 0.5f; +float const kCD_PitchHigherOneOctave = 2.0f; +float const kCD_PanDefault = 0.0f; +float const kCD_PanFullLeft = -1.0f; +float const kCD_PanFullRight = 1.0f; +float const kCD_GainDefault = 1.0f; + +@interface CDSoundEngine (PrivateMethods) +-(BOOL) _initOpenAL; +-(void) _testGetGain; +-(void) _dumpSourceGroupsInfo; +-(void) _getSourceIndexForSourceGroup; +-(void) _freeSourceGroups; +-(BOOL) _setUpSourceGroups:(int[]) definitions total:(NSUInteger) total; +@end + +#pragma mark - +#pragma mark CDUtilities + +@implementation CDUtilities + ++(NSString*) fullPathFromRelativePath:(NSString*) relPath +{ + // do not convert an absolute path (starting with '/') + if(([relPath length] > 0) && ([relPath characterAtIndex:0] == '/')) + { + return relPath; + } + + NSMutableArray *imagePathComponents = [NSMutableArray arrayWithArray:[relPath pathComponents]]; + NSString *file = [imagePathComponents lastObject]; + + [imagePathComponents removeLastObject]; + NSString *imageDirectory = [NSString pathWithComponents:imagePathComponents]; + + NSString *fullpath = [[NSBundle mainBundle] pathForResource:file ofType:nil inDirectory:imageDirectory]; + if (fullpath == nil) + fullpath = relPath; + + return fullpath; +} + +@end + +#pragma mark - +#pragma mark CDSoundEngine + +@implementation CDSoundEngine + +static Float32 _mixerSampleRate; +static BOOL _mixerRateSet = NO; + +@synthesize lastErrorCode = lastErrorCode_; +@synthesize functioning = functioning_; +@synthesize asynchLoadProgress = asynchLoadProgress_; +@synthesize getGainWorks = getGainWorks_; +@synthesize sourceTotal = sourceTotal_; + ++ (void) setMixerSampleRate:(Float32) sampleRate { + _mixerRateSet = YES; + _mixerSampleRate = sampleRate; +} + +- (void) _testGetGain { + float testValue = 0.7f; + ALuint testSourceId = _sources[0].sourceId; + alSourcef(testSourceId, AL_GAIN, 0.0f);//Start from know value + alSourcef(testSourceId, AL_GAIN, testValue); + ALfloat gainVal; + alGetSourcef(testSourceId, AL_GAIN, &gainVal); + getGainWorks_ = (gainVal == testValue); +} + +//Generate sources one at a time until we fail +-(void) _generateSources { + + _sources = (sourceInfo*)malloc( sizeof(_sources[0]) * CD_SOURCE_LIMIT); + BOOL hasFailed = NO; + sourceTotal_ = 0; + alGetError();//Clear error + while (!hasFailed && sourceTotal_ < CD_SOURCE_LIMIT) { + alGenSources(1, &(_sources[sourceTotal_].sourceId)); + if (alGetError() == AL_NO_ERROR) { + //Now try attaching source to null buffer + alSourcei(_sources[sourceTotal_].sourceId, AL_BUFFER, 0); + if (alGetError() == AL_NO_ERROR) { + _sources[sourceTotal_].usable = true; + sourceTotal_++; + } else { + hasFailed = YES; + } + } else { + _sources[sourceTotal_].usable = false; + hasFailed = YES; + } + } + //Mark the rest of the sources as not usable + for (int i=sourceTotal_; i < CD_SOURCE_LIMIT; i++) { + _sources[i].usable = false; + } +} + +-(void) _generateBuffers:(int) startIndex endIndex:(int) endIndex { + if (_buffers) { + alGetError(); + for (int i=startIndex; i <= endIndex; i++) { + alGenBuffers(1, &_buffers[i].bufferId); + _buffers[i].bufferData = NULL; + if (alGetError() == AL_NO_ERROR) { + _buffers[i].bufferState = CD_BS_EMPTY; + } else { + _buffers[i].bufferState = CD_BS_FAILED; + CDLOG(@"Denshion::CDSoundEngine - buffer creation failed %i",i); + } + } + } +} + +/** + * Internal method called during init + */ +- (BOOL) _initOpenAL +{ + //ALenum error; + context = NULL; + ALCdevice *newDevice = NULL; + + //Set the mixer rate for the audio mixer + if (!_mixerRateSet) { + _mixerSampleRate = CD_SAMPLE_RATE_DEFAULT; + } + alcMacOSXMixerOutputRateProc(_mixerSampleRate); + CDLOGINFO(@"Denshion::CDSoundEngine - mixer output rate set to %0.2f",_mixerSampleRate); + + // Create a new OpenAL Device + // Pass NULL to specify the system's default output device + newDevice = alcOpenDevice(NULL); + if (newDevice != NULL) + { + // Create a new OpenAL Context + // The new context will render to the OpenAL Device just created + context = alcCreateContext(newDevice, 0); + if (context != NULL) + { + // Make the new context the Current OpenAL Context + alcMakeContextCurrent(context); + + // Create some OpenAL Buffer Objects + [self _generateBuffers:0 endIndex:bufferTotal-1]; + + // Create some OpenAL Source Objects + [self _generateSources]; + + } + } else { + return FALSE;//No device + } + alGetError();//Clear error + return TRUE; +} + +- (void) dealloc { + + ALCcontext *currentContext = NULL; + ALCdevice *device = NULL; + + [self stopAllSounds]; + + CDLOGINFO(@"Denshion::CDSoundEngine - Deallocing sound engine."); + [self _freeSourceGroups]; + + // Delete the Sources + CDLOGINFO(@"Denshion::CDSoundEngine - deleting sources."); + for (int i=0; i < sourceTotal_; i++) { + alSourcei(_sources[i].sourceId, AL_BUFFER, 0);//Detach from current buffer + alDeleteSources(1, &(_sources[i].sourceId)); + if((lastErrorCode_ = alGetError()) != AL_NO_ERROR) { + CDLOG(@"Denshion::CDSoundEngine - Error deleting source! %x\n", lastErrorCode_); + } + } + + // Delete the Buffers + CDLOGINFO(@"Denshion::CDSoundEngine - deleting buffers."); + for (int i=0; i < bufferTotal; i++) { + alDeleteBuffers(1, &_buffers[i].bufferId); +#ifdef CD_USE_STATIC_BUFFERS + if (_buffers[i].bufferData) { + free(_buffers[i].bufferData); + } +#endif + } + CDLOGINFO(@"Denshion::CDSoundEngine - free buffers."); + free(_buffers); + currentContext = alcGetCurrentContext(); + //Get device for active context + device = alcGetContextsDevice(currentContext); + //Release context + CDLOGINFO(@"Denshion::CDSoundEngine - destroy context."); + alcDestroyContext(currentContext); + //Close device + CDLOGINFO(@"Denshion::CDSoundEngine - close device."); + alcCloseDevice(device); + CDLOGINFO(@"Denshion::CDSoundEngine - free sources."); + free(_sources); + + //Release mutexes + [_mutexBufferLoad release]; + + [super dealloc]; +} + +-(NSUInteger) sourceGroupTotal { + return _sourceGroupTotal; +} + +-(void) _freeSourceGroups +{ + CDLOGINFO(@"Denshion::CDSoundEngine freeing source groups"); + if(_sourceGroups) { + for (int i=0; i < _sourceGroupTotal; i++) { + if (_sourceGroups[i].sourceStatuses) { + free(_sourceGroups[i].sourceStatuses); + CDLOGINFO(@"Denshion::CDSoundEngine freed source statuses %i",i); + } + } + free(_sourceGroups); + } +} + +-(BOOL) _redefineSourceGroups:(int[]) definitions total:(NSUInteger) total +{ + if (_sourceGroups) { + //Stop all sounds + [self stopAllSounds]; + //Need to free source groups + [self _freeSourceGroups]; + } + return [self _setUpSourceGroups:definitions total:total]; +} + +-(BOOL) _setUpSourceGroups:(int[]) definitions total:(NSUInteger) total +{ + _sourceGroups = (sourceGroup *)malloc( sizeof(_sourceGroups[0]) * total); + if(!_sourceGroups) { + CDLOG(@"Denshion::CDSoundEngine - source groups memory allocation failed"); + return NO; + } + + _sourceGroupTotal = total; + int sourceCount = 0; + for (int i=0; i < _sourceGroupTotal; i++) { + + _sourceGroups[i].startIndex = 0; + _sourceGroups[i].currentIndex = _sourceGroups[i].startIndex; + _sourceGroups[i].enabled = false; + _sourceGroups[i].nonInterruptible = false; + _sourceGroups[i].totalSources = definitions[i]; + _sourceGroups[i].sourceStatuses = malloc(sizeof(_sourceGroups[i].sourceStatuses[0]) * _sourceGroups[i].totalSources); + if (_sourceGroups[i].sourceStatuses) { + for (int j=0; j < _sourceGroups[i].totalSources; j++) { + //First bit is used to indicate whether source is locked, index is shifted back 1 bit + _sourceGroups[i].sourceStatuses[j] = (sourceCount + j) << 1; + } + } + sourceCount += definitions[i]; + } + return YES; +} + +-(void) defineSourceGroups:(int[]) sourceGroupDefinitions total:(NSUInteger) total { + [self _redefineSourceGroups:sourceGroupDefinitions total:total]; +} + +-(void) defineSourceGroups:(NSArray*) sourceGroupDefinitions { + CDLOGINFO(@"Denshion::CDSoundEngine - source groups defined by NSArray."); + NSUInteger totalDefs = [sourceGroupDefinitions count]; + int* defs = (int *)malloc( sizeof(int) * totalDefs); + int currentIndex = 0; + for (id currentDef in sourceGroupDefinitions) { + if ([currentDef isKindOfClass:[NSNumber class]]) { + defs[currentIndex] = (int)[(NSNumber*)currentDef integerValue]; + CDLOGINFO(@"Denshion::CDSoundEngine - found definition %i.",defs[currentIndex]); + } else { + CDLOG(@"Denshion::CDSoundEngine - warning, did not understand source definition."); + defs[currentIndex] = 0; + } + currentIndex++; + } + [self _redefineSourceGroups:defs total:totalDefs]; + free(defs); +} + +- (id)init +{ + if ((self = [super init])) { + + //Create mutexes + _mutexBufferLoad = [[NSObject alloc] init]; + + asynchLoadProgress_ = 0.0f; + + bufferTotal = CD_BUFFERS_START; + _buffers = (bufferInfo *)malloc( sizeof(_buffers[0]) * bufferTotal); + + // Initialize our OpenAL environment + if ([self _initOpenAL]) { + //Set up the default source group - a single group that contains all the sources + int sourceDefs[1]; + sourceDefs[0] = self.sourceTotal; + [self _setUpSourceGroups:sourceDefs total:1]; + + functioning_ = YES; + //Synchronize premute gain + _preMuteGain = self.masterGain; + mute_ = NO; + enabled_ = YES; + //Test whether get gain works for sources + [self _testGetGain]; + } else { + //Something went wrong with OpenAL + functioning_ = NO; + } + } + + return self; +} + +/** + * Delete the buffer identified by soundId + * @return true if buffer deleted successfully, otherwise false + */ +- (BOOL) unloadBuffer:(int) soundId +{ + //Ensure soundId is within array bounds otherwise memory corruption will occur + if (soundId < 0 || soundId >= bufferTotal) { + CDLOG(@"Denshion::CDSoundEngine - soundId is outside array bounds, maybe you need to increase CD_MAX_BUFFERS"); + return FALSE; + } + + //Before a buffer can be deleted any sources that are attached to it must be stopped + for (int i=0; i < sourceTotal_; i++) { + //Note: tried getting the AL_BUFFER attribute of the source instead but doesn't + //appear to work on a device - just returned zero. + if (_buffers[soundId].bufferId == _sources[i].attachedBufferId) { + + CDLOG(@"Denshion::CDSoundEngine - Found attached source %i %i %i",i,_buffers[soundId].bufferId,_sources[i].sourceId); +#ifdef CD_USE_STATIC_BUFFERS + //When using static buffers a crash may occur if a source is playing with a buffer that is about + //to be deleted even though we stop the source and successfully delete the buffer. Crash is confirmed + //on 2.2.1 and 3.1.2, however, it will only occur if a source is used rapidly after having its prior + //data deleted. To avoid any possibility of the crash we wait for the source to finish playing. + ALint state; + + alGetSourcei(_sources[i].sourceId, AL_SOURCE_STATE, &state); + + if (state == AL_PLAYING) { + CDLOG(@"Denshion::CDSoundEngine - waiting for source to complete playing before removing buffer data"); + alSourcei(_sources[i].sourceId, AL_LOOPING, FALSE);//Turn off looping otherwise loops will never end + while (state == AL_PLAYING) { + alGetSourcei(_sources[i].sourceId, AL_SOURCE_STATE, &state); + usleep(10000); + } + } +#endif + //Stop source and detach + alSourceStop(_sources[i].sourceId); + if((lastErrorCode_ = alGetError()) != AL_NO_ERROR) { + CDLOG(@"Denshion::CDSoundEngine - error stopping source: %x\n", lastErrorCode_); + } + + alSourcei(_sources[i].sourceId, AL_BUFFER, 0);//Attach to "NULL" buffer to detach + if((lastErrorCode_ = alGetError()) != AL_NO_ERROR) { + CDLOG(@"Denshion::CDSoundEngine - error detaching buffer: %x\n", lastErrorCode_); + } else { + //Record that source is now attached to nothing + _sources[i].attachedBufferId = 0; + } + } + } + + alDeleteBuffers(1, &_buffers[soundId].bufferId); + if((lastErrorCode_ = alGetError()) != AL_NO_ERROR) { + CDLOG(@"Denshion::CDSoundEngine - error deleting buffer: %x\n", lastErrorCode_); + _buffers[soundId].bufferState = CD_BS_FAILED; + return FALSE; + } else { +#ifdef CD_USE_STATIC_BUFFERS + //Free previous data, if alDeleteBuffer has returned without error then no + if (_buffers[soundId].bufferData) { + CDLOGINFO(@"Denshion::CDSoundEngine - freeing static data for soundId %i @ %i",soundId,_buffers[soundId].bufferData); + free(_buffers[soundId].bufferData);//Free the old data + _buffers[soundId].bufferData = NULL; + } +#endif + } + + alGenBuffers(1, &_buffers[soundId].bufferId); + if((lastErrorCode_ = alGetError()) != AL_NO_ERROR) { + CDLOG(@"Denshion::CDSoundEngine - error regenerating buffer: %x\n", lastErrorCode_); + _buffers[soundId].bufferState = CD_BS_FAILED; + return FALSE; + } else { + //We now have an empty buffer + _buffers[soundId].bufferState = CD_BS_EMPTY; + CDLOGINFO(@"Denshion::CDSoundEngine - buffer %i successfully unloaded\n",soundId); + return TRUE; + } +} + +/** + * Load buffers asynchronously + * Check asynchLoadProgress for progress. asynchLoadProgress represents fraction of completion. When it equals 1.0 loading + * is complete. NB: asynchLoadProgress is simply based on the number of load requests, it does not take into account + * file sizes. + * @param An array of CDBufferLoadRequest objects + */ +- (void) loadBuffersAsynchronously:(NSArray *) loadRequests { + @synchronized(self) { + asynchLoadProgress_ = 0.0f; + CDAsynchBufferLoader *loaderOp = [[[CDAsynchBufferLoader alloc] init:loadRequests soundEngine:self] autorelease]; + NSOperationQueue *opQ = [[[NSOperationQueue alloc] init] autorelease]; + [opQ addOperation:loaderOp]; + } +} + +-(BOOL) _resizeBuffers:(int) increment { + + void * tmpBufferInfos = realloc( _buffers, sizeof(_buffers[0]) * (bufferTotal + increment) ); + + if(!tmpBufferInfos) { + free(tmpBufferInfos); + return NO; + } else { + _buffers = tmpBufferInfos; + int oldBufferTotal = bufferTotal; + bufferTotal = bufferTotal + increment; + [self _generateBuffers:oldBufferTotal endIndex:bufferTotal-1]; + return YES; + } +} + +-(BOOL) loadBufferFromData:(int) soundId soundData:(ALvoid*) soundData format:(ALenum) format size:(ALsizei) size freq:(ALsizei) freq { + + @synchronized(_mutexBufferLoad) { + + if (!functioning_) { + //OpenAL initialisation has previously failed + CDLOG(@"Denshion::CDSoundEngine - Loading buffer failed because sound engine state != functioning"); + return FALSE; + } + + //Ensure soundId is within array bounds otherwise memory corruption will occur + if (soundId < 0) { + CDLOG(@"Denshion::CDSoundEngine - soundId is negative"); + return FALSE; + } + + if (soundId >= bufferTotal) { + //Need to resize the buffers + int requiredIncrement = CD_BUFFERS_INCREMENT; + while (bufferTotal + requiredIncrement < soundId) { + requiredIncrement += CD_BUFFERS_INCREMENT; + } + CDLOGINFO(@"Denshion::CDSoundEngine - attempting to resize buffers by %i for sound %i",requiredIncrement,soundId); + if (![self _resizeBuffers:requiredIncrement]) { + CDLOG(@"Denshion::CDSoundEngine - buffer resize failed"); + return FALSE; + } + } + + if (soundData) + { + if (_buffers[soundId].bufferState != CD_BS_EMPTY) { + CDLOGINFO(@"Denshion::CDSoundEngine - non empty buffer, regenerating"); + if (![self unloadBuffer:soundId]) { + //Deletion of buffer failed, delete buffer routine has set buffer state and lastErrorCode + return NO; + } + } + +#ifdef CD_DEBUG + //Check that sample rate matches mixer rate and warn if they do not + if (freq != (int)_mixerSampleRate) { + CDLOGINFO(@"Denshion::CDSoundEngine - WARNING sample rate does not match mixer sample rate performance may not be optimal."); + } +#endif + +#ifdef CD_USE_STATIC_BUFFERS + alBufferDataStaticProc(_buffers[soundId].bufferId, format, soundData, size, freq); + _buffers[soundId].bufferData = data;//Save the pointer to the new data +#else + alBufferData(_buffers[soundId].bufferId, format, soundData, size, freq); +#endif + if((lastErrorCode_ = alGetError()) != AL_NO_ERROR) { + CDLOG(@"Denshion::CDSoundEngine - error attaching audio to buffer: %x", lastErrorCode_); + _buffers[soundId].bufferState = CD_BS_FAILED; + return FALSE; + } + } else { + CDLOG(@"Denshion::CDSoundEngine Buffer data is null!"); + _buffers[soundId].bufferState = CD_BS_FAILED; + return FALSE; + } + + _buffers[soundId].format = format; + _buffers[soundId].sizeInBytes = size; + _buffers[soundId].frequencyInHertz = freq; + _buffers[soundId].bufferState = CD_BS_LOADED; + CDLOGINFO(@"Denshion::CDSoundEngine Buffer %i loaded format:%i freq:%i size:%i",soundId,format,freq,size); + return TRUE; + }//end mutex +} + +/** + * Load sound data for later play back. + * @return TRUE if buffer loaded okay for play back otherwise false + */ +- (BOOL) loadBuffer:(int) soundId filePath:(NSString*) filePath +{ + + ALvoid* data; + ALenum format; + ALsizei size; + ALsizei freq; + + CDLOGINFO(@"Denshion::CDSoundEngine - Loading openAL buffer %i %@", soundId, filePath); + + CFURLRef fileURL = nil; + NSString *path = [CDUtilities fullPathFromRelativePath:filePath]; + if (path) { + fileURL = (CFURLRef)[[NSURL fileURLWithPath:path] retain]; + } + + if (fileURL) + { + data = CDGetOpenALAudioData(fileURL, &size, &format, &freq); + CFRelease(fileURL); + BOOL result = [self loadBufferFromData:soundId soundData:data format:format size:size freq:freq]; +#ifndef CD_USE_STATIC_BUFFERS + free(data);//Data can be freed here because alBufferData performs a memcpy +#endif + return result; + } else { + CDLOG(@"Denshion::CDSoundEngine Could not find file!\n"); + //Don't change buffer state here as it will be the same as before method was called + return FALSE; + } +} + +-(BOOL) validateBufferId:(int) soundId { + if (soundId < 0 || soundId >= bufferTotal) { + CDLOGINFO(@"Denshion::CDSoundEngine - validateBufferId buffer outside range %i",soundId); + return NO; + } else if (_buffers[soundId].bufferState != CD_BS_LOADED) { + CDLOGINFO(@"Denshion::CDSoundEngine - validateBufferId invalide buffer state %i",soundId); + return NO; + } else { + return YES; + } +} + +-(float) bufferDurationInSeconds:(int) soundId { + if ([self validateBufferId:soundId]) { + float factor = 0.0f; + switch (_buffers[soundId].format) { + case AL_FORMAT_MONO8: + factor = 1.0f; + break; + case AL_FORMAT_MONO16: + factor = 0.5f; + break; + case AL_FORMAT_STEREO8: + factor = 0.5f; + break; + case AL_FORMAT_STEREO16: + factor = 0.25f; + break; + } + return (float)_buffers[soundId].sizeInBytes/(float)_buffers[soundId].frequencyInHertz * factor; + } else { + return -1.0f; + } +} + +-(ALsizei) bufferSizeInBytes:(int) soundId { + if ([self validateBufferId:soundId]) { + return _buffers[soundId].sizeInBytes; + } else { + return -1.0f; + } +} + +-(ALsizei) bufferFrequencyInHertz:(int) soundId { + if ([self validateBufferId:soundId]) { + return _buffers[soundId].frequencyInHertz; + } else { + return -1.0f; + } +} + +- (ALfloat) masterGain { + if (mute_) { + //When mute the real gain will always be 0 therefore return the preMuteGain value + return _preMuteGain; + } else { + ALfloat gain; + alGetListenerf(AL_GAIN, &gain); + return gain; + } +} + +/** + * Overall gain setting multiplier. e.g 0.5 is half the gain. + */ +- (void) setMasterGain:(ALfloat) newGainValue { + if (mute_) { + _preMuteGain = newGainValue; + } else { + alListenerf(AL_GAIN, newGainValue); + } +} + +#pragma mark CDSoundEngine AudioInterrupt protocol +- (BOOL) mute { + return mute_; +} + +/** + * Setting mute silences all sounds but playing sounds continue to advance playback + */ +- (void) setMute:(BOOL) newMuteValue { + + if (newMuteValue == mute_) { + return; + } + + mute_ = newMuteValue; + if (mute_) { + //Remember what the gain was + _preMuteGain = self.masterGain; + //Set gain to 0 - do not use the property as this will adjust preMuteGain when muted + alListenerf(AL_GAIN, 0.0f); + } else { + //Restore gain to what it was before being muted + self.masterGain = _preMuteGain; + } +} + +- (BOOL) enabled { + return enabled_; +} + +- (void) setEnabled:(BOOL)enabledValue +{ + if (enabled_ == enabledValue) { + return; + } + enabled_ = enabledValue; + if (enabled_ == NO) { + [self stopAllSounds]; + } +} + +-(void) _lockSource:(int) sourceIndex lock:(BOOL) lock { + BOOL found = NO; + for (int i=0; i < _sourceGroupTotal && !found; i++) { + if (_sourceGroups[i].sourceStatuses) { + for (int j=0; j < _sourceGroups[i].totalSources && !found; j++) { + //First bit is used to indicate whether source is locked, index is shifted back 1 bit + if((_sourceGroups[i].sourceStatuses[j] >> 1)==sourceIndex) { + if (lock) { + //Set first bit to lock this source + _sourceGroups[i].sourceStatuses[j] |= 1; + } else { + //Unset first bit to unlock this source + _sourceGroups[i].sourceStatuses[j] &= ~1; + } + found = YES; + } + } + } + } +} + +-(int) _getSourceIndexForSourceGroup:(int)sourceGroupId +{ + //Ensure source group id is valid to prevent memory corruption + if (sourceGroupId < 0 || sourceGroupId >= _sourceGroupTotal) { + CDLOG(@"Denshion::CDSoundEngine invalid source group id %i",sourceGroupId); + return CD_NO_SOURCE; + } + + int sourceIndex = -1;//Using -1 to indicate no source found + BOOL complete = NO; + ALint sourceState = 0; + sourceGroup *thisSourceGroup = &_sourceGroups[sourceGroupId]; + thisSourceGroup->currentIndex = thisSourceGroup->startIndex; + while (!complete) { + //Iterate over sources looking for one that is not locked, first bit indicates if source is locked + if ((thisSourceGroup->sourceStatuses[thisSourceGroup->currentIndex] & 1) == 0) { + //This source is not locked + sourceIndex = thisSourceGroup->sourceStatuses[thisSourceGroup->currentIndex] >> 1;//shift back to get the index + if (thisSourceGroup->nonInterruptible) { + //Check if this source is playing, if so it can't be interrupted + alGetSourcei(_sources[sourceIndex].sourceId, AL_SOURCE_STATE, &sourceState); + if (sourceState != AL_PLAYING) { + //complete = YES; + //Set start index so next search starts at the next position + thisSourceGroup->startIndex = thisSourceGroup->currentIndex + 1; + break; + } else { + sourceIndex = -1;//The source index was no good because the source was playing + } + } else { + //complete = YES; + //Set start index so next search starts at the next position + thisSourceGroup->startIndex = thisSourceGroup->currentIndex + 1; + break; + } + } + thisSourceGroup->currentIndex++; + if (thisSourceGroup->currentIndex >= thisSourceGroup->totalSources) { + //Reset to the beginning + thisSourceGroup->currentIndex = 0; + } + if (thisSourceGroup->currentIndex == thisSourceGroup->startIndex) { + //We have looped around and got back to the start + complete = YES; + } + } + + //Reset start index to beginning if beyond bounds + if (thisSourceGroup->startIndex >= thisSourceGroup->totalSources) { + thisSourceGroup->startIndex = 0; + } + + if (sourceIndex >= 0) { + return sourceIndex; + } else { + return CD_NO_SOURCE; + } + +} + +/** + * Play a sound. + * @param soundId the id of the sound to play (buffer id). + * @param SourceGroupId the source group that will be used to play the sound. + * @param pitch pitch multiplier. e.g 1.0 is unaltered, 0.5 is 1 octave lower. + * @param pan stereo position. -1 is fully left, 0 is centre and 1 is fully right. + * @param gain gain multiplier. e.g. 1.0 is unaltered, 0.5 is half the gain + * @param loop should the sound be looped or one shot. + * @return the id of the source being used to play the sound or CD_MUTE if the sound engine is muted or non functioning + * or CD_NO_SOURCE if a problem occurs setting up the source + * + */ +- (ALuint)playSound:(int) soundId sourceGroupId:(int)sourceGroupId pitch:(float) pitch pan:(float) pan gain:(float) gain loop:(BOOL) loop { + +#ifdef CD_DEBUG + //Sanity check parameters - only in DEBUG + NSAssert(soundId >= 0, @"soundId can not be negative"); + NSAssert(soundId < bufferTotal, @"soundId exceeds limit"); + NSAssert(sourceGroupId >= 0, @"sourceGroupId can not be negative"); + NSAssert(sourceGroupId < _sourceGroupTotal, @"sourceGroupId exceeds limit"); + NSAssert(pitch > 0, @"pitch must be greater than zero"); + NSAssert(pan >= -1 && pan <= 1, @"pan must be between -1 and 1"); + NSAssert(gain >= 0, @"gain can not be negative"); +#endif + //If mute or initialisation has failed or buffer is not loaded then do nothing + if (!enabled_ || !functioning_ || _buffers[soundId].bufferState != CD_BS_LOADED || _sourceGroups[sourceGroupId].enabled) { +#ifdef CD_DEBUG + if (!functioning_) { + CDLOGINFO(@"Denshion::CDSoundEngine - sound playback aborted because sound engine is not functioning"); + } else if (_buffers[soundId].bufferState != CD_BS_LOADED) { + CDLOGINFO(@"Denshion::CDSoundEngine - sound playback aborted because buffer %i is not loaded", soundId); + } +#endif + return CD_MUTE; + } + + int sourceIndex = [self _getSourceIndexForSourceGroup:sourceGroupId];//This method ensures sourceIndex is valid + + if (sourceIndex != CD_NO_SOURCE) { + ALint state; + ALuint source = _sources[sourceIndex].sourceId; + ALuint buffer = _buffers[soundId].bufferId; + alGetError();//Clear the error code + alGetSourcei(source, AL_SOURCE_STATE, &state); + if (state == AL_PLAYING) { + alSourceStop(source); + } + alSourcei(source, AL_BUFFER, buffer);//Attach to sound + alSourcef(source, AL_PITCH, pitch);//Set pitch + alSourcei(source, AL_LOOPING, loop);//Set looping + alSourcef(source, AL_GAIN, gain);//Set gain/volume + float sourcePosAL[] = {pan, 0.0f, 0.0f};//Set position - just using left and right panning + alSourcefv(source, AL_POSITION, sourcePosAL); + alGetError();//Clear the error code + alSourcePlay(source); + if((lastErrorCode_ = alGetError()) == AL_NO_ERROR) { + //Everything was okay + _sources[sourceIndex].attachedBufferId = buffer; + return source; + } else { + if (alcGetCurrentContext() == NULL) { + CDLOGINFO(@"Denshion::CDSoundEngine - posting bad OpenAL context message"); + [[NSNotificationCenter defaultCenter] postNotificationName:kCDN_BadAlContext object:nil]; + } + return CD_NO_SOURCE; + } + } else { + return CD_NO_SOURCE; + } +} + +-(BOOL) _soundSourceAttachToBuffer:(CDSoundSource*) soundSource soundId:(int) soundId { + //Attach the source to the buffer + ALint state; + ALuint source = soundSource->_sourceId; + ALuint buffer = _buffers[soundId].bufferId; + alGetSourcei(source, AL_SOURCE_STATE, &state); + if (state == AL_PLAYING) { + alSourceStop(source); + } + alGetError();//Clear the error code + alSourcei(source, AL_BUFFER, buffer);//Attach to sound data + if((lastErrorCode_ = alGetError()) == AL_NO_ERROR) { + _sources[soundSource->_sourceIndex].attachedBufferId = buffer; + //_sourceBufferAttachments[soundSource->_sourceIndex] = buffer;//Keep track of which + soundSource->_soundId = soundId; + return YES; + } else { + return NO; + } +} + +/** + * Get a sound source for the specified sound in the specified source group + */ +-(CDSoundSource *) soundSourceForSound:(int) soundId sourceGroupId:(int) sourceGroupId +{ + if (!functioning_) { + return nil; + } + //Check if a source is available + int sourceIndex = [self _getSourceIndexForSourceGroup:sourceGroupId]; + if (sourceIndex != CD_NO_SOURCE) { + CDSoundSource *result = [[CDSoundSource alloc] init:_sources[sourceIndex].sourceId sourceIndex:sourceIndex soundEngine:self]; + [self _lockSource:sourceIndex lock:YES]; + //Try to attach to the buffer + if ([self _soundSourceAttachToBuffer:result soundId:soundId]) { + //Set to a known state + result.pitch = 1.0f; + result.pan = 0.0f; + result.gain = 1.0f; + result.looping = NO; + return [result autorelease]; + } else { + //Release the sound source we just created, this will also unlock the source + [result release]; + return nil; + } + } else { + //No available source within that source group + return nil; + } +} + +-(void) _soundSourcePreRelease:(CDSoundSource *) soundSource { + CDLOGINFO(@"Denshion::CDSoundEngine _soundSourcePreRelease %i",soundSource->_sourceIndex); + //Unlock the sound source's source + [self _lockSource:soundSource->_sourceIndex lock:NO]; +} + +/** + * Stop all sounds playing within a source group + */ +- (void) stopSourceGroup:(int) sourceGroupId { + + if (!functioning_ || sourceGroupId >= _sourceGroupTotal || sourceGroupId < 0) { + return; + } + int sourceCount = _sourceGroups[sourceGroupId].totalSources; + for (int i=0; i < sourceCount; i++) { + int sourceIndex = _sourceGroups[sourceGroupId].sourceStatuses[i] >> 1; + alSourceStop(_sources[sourceIndex].sourceId); + } + alGetError();//Clear error in case we stopped any sounds that couldn't be stopped +} + +/** + * Stop a sound playing. + * @param sourceId an OpenAL source identifier i.e. the return value of playSound + */ +- (void)stopSound:(ALuint) sourceId { + if (!functioning_) { + return; + } + alSourceStop(sourceId); + alGetError();//Clear error in case we stopped any sounds that couldn't be stopped +} + +- (void) stopAllSounds { + for (int i=0; i < sourceTotal_; i++) { + alSourceStop(_sources[i].sourceId); + } + alGetError();//Clear error in case we stopped any sounds that couldn't be stopped +} + +- (void) pauseSound:(ALuint) sourceId { + if (!functioning_) { + return; + } + alSourcePause(sourceId); + alGetError();//Clear error in case we pause any sounds that couldn't be paused +} + +- (void) pauseAllSounds { + for (int i = 0; i < sourceTotal_; i++) { + [self pauseSound:_sources[i].sourceId]; + } + alGetError();//Clear error in case we stopped any sounds that couldn't be paused +} + +- (void) resumeSound:(ALuint) soundId { + if (!functioning_) { + return; + } + + // only resume a sound id that is paused + ALint state; + alGetSourcei(soundId, AL_SOURCE_STATE, &state); + if (state != AL_PAUSED) + { + return; + } + + alSourcePlay(soundId); + alGetError();//Clear error in case we stopped any sounds that couldn't be resumed +} + +- (void) resumeAllSounds { + for (int i = 0; i < sourceTotal_; i++) { + [self resumeSound:_sources[i].sourceId]; + } + alGetError();//Clear error in case we stopped any sounds that couldn't be resumed +} + +/** + * Set a source group as non interruptible. Default is that source groups are interruptible. + * Non interruptible means that if a request to play a sound is made for a source group and there are + * no free sources available then the play request will be ignored and CD_NO_SOURCE will be returned. + */ +- (void) setSourceGroupNonInterruptible:(int) sourceGroupId isNonInterruptible:(BOOL) isNonInterruptible { + //Ensure source group id is valid to prevent memory corruption + if (sourceGroupId < 0 || sourceGroupId >= _sourceGroupTotal) { + CDLOG(@"Denshion::CDSoundEngine setSourceGroupNonInterruptible invalid source group id %i",sourceGroupId); + return; + } + + if (isNonInterruptible) { + _sourceGroups[sourceGroupId].nonInterruptible = true; + } else { + _sourceGroups[sourceGroupId].nonInterruptible = false; + } +} + +/** + * Set the mute property for a source group. If mute is turned on any sounds in that source group + * will be stopped and further sounds in that source group will play. However, turning mute off + * will not restart any sounds that were playing when mute was turned on. Also the mute setting + * for the sound engine must be taken into account. If the sound engine is mute no sounds will play + * no matter what the source group mute setting is. + */ +- (void) setSourceGroupEnabled:(int) sourceGroupId enabled:(BOOL) enabled { + //Ensure source group id is valid to prevent memory corruption + if (sourceGroupId < 0 || sourceGroupId >= _sourceGroupTotal) { + CDLOG(@"Denshion::CDSoundEngine setSourceGroupEnabled invalid source group id %i",sourceGroupId); + return; + } + + if (enabled) { + _sourceGroups[sourceGroupId].enabled = true; + [self stopSourceGroup:sourceGroupId]; + } else { + _sourceGroups[sourceGroupId].enabled = false; + } +} + +/** + * Return the mute property for the source group identified by sourceGroupId + */ +- (BOOL) sourceGroupEnabled:(int) sourceGroupId { + return _sourceGroups[sourceGroupId].enabled; +} + +-(ALCcontext *) openALContext { + return context; +} + +- (void) _dumpSourceGroupsInfo { +#ifdef CD_DEBUG + CDLOGINFO(@"-------------- source Group Info --------------"); + for (int i=0; i < _sourceGroupTotal; i++) { + CDLOGINFO(@"Group: %i start:%i total:%i",i,_sourceGroups[i].startIndex, _sourceGroups[i].totalSources); + CDLOGINFO(@"----- mute:%i nonInterruptible:%i",_sourceGroups[i].enabled, _sourceGroups[i].nonInterruptible); + CDLOGINFO(@"----- Source statuses ----"); + for (int j=0; j < _sourceGroups[i].totalSources; j++) { + CDLOGINFO(@"Source status:%i index=%i locked=%i",j,_sourceGroups[i].sourceStatuses[j] >> 1, _sourceGroups[i].sourceStatuses[j] & 1); + } + } +#endif +} + +@end + +/////////////////////////////////////////////////////////////////////////////////////// +@implementation CDSoundSource + +@synthesize lastError; + +//Macro for handling the al error code +#define CDSOUNDSOURCE_UPDATE_LAST_ERROR (lastError = alGetError()) +#define CDSOUNDSOURCE_ERROR_HANDLER ( CDSOUNDSOURCE_UPDATE_LAST_ERROR == AL_NO_ERROR) + +-(id)init:(ALuint) theSourceId sourceIndex:(int) index soundEngine:(CDSoundEngine*) engine { + if ((self = [super init])) { + _sourceId = theSourceId; + _engine = engine; + _sourceIndex = index; + enabled_ = YES; + mute_ = NO; + _preMuteGain = self.gain; + } + return self; +} + +-(void) dealloc +{ + CDLOGINFO(@"Denshion::CDSoundSource deallocated %i",self->_sourceIndex); + + //Notify sound engine we are about to release + [_engine _soundSourcePreRelease:self]; + [super dealloc]; +} + +- (void) setPitch:(float) newPitchValue { + alSourcef(_sourceId, AL_PITCH, newPitchValue); + CDSOUNDSOURCE_UPDATE_LAST_ERROR; +} + +- (void) setGain:(float) newGainValue { + if (!mute_) { + alSourcef(_sourceId, AL_GAIN, newGainValue); + } else { + _preMuteGain = newGainValue; + } + CDSOUNDSOURCE_UPDATE_LAST_ERROR; +} + +- (void) setPan:(float) newPanValue { + float sourcePosAL[] = {newPanValue, 0.0f, 0.0f};//Set position - just using left and right panning + alSourcefv(_sourceId, AL_POSITION, sourcePosAL); + CDSOUNDSOURCE_UPDATE_LAST_ERROR; + +} + +- (void) setLooping:(BOOL) newLoopingValue { + alSourcei(_sourceId, AL_LOOPING, newLoopingValue); + CDSOUNDSOURCE_UPDATE_LAST_ERROR; + +} + +- (BOOL) isPlaying { + ALint state; + alGetSourcei(_sourceId, AL_SOURCE_STATE, &state); + CDSOUNDSOURCE_UPDATE_LAST_ERROR; + return (state == AL_PLAYING); +} + +- (float) pitch { + ALfloat pitchVal; + alGetSourcef(_sourceId, AL_PITCH, &pitchVal); + CDSOUNDSOURCE_UPDATE_LAST_ERROR; + return pitchVal; +} + +- (float) pan { + ALfloat sourcePosAL[] = {0.0f,0.0f,0.0f}; + alGetSourcefv(_sourceId, AL_POSITION, sourcePosAL); + CDSOUNDSOURCE_UPDATE_LAST_ERROR; + return sourcePosAL[0]; +} + +- (float) gain { + if (!mute_) { + ALfloat val; + alGetSourcef(_sourceId, AL_GAIN, &val); + CDSOUNDSOURCE_UPDATE_LAST_ERROR; + return val; + } else { + return _preMuteGain; + } +} + +- (BOOL) looping { + ALfloat val; + alGetSourcef(_sourceId, AL_LOOPING, &val); + CDSOUNDSOURCE_UPDATE_LAST_ERROR; + return val; +} + +-(BOOL) stop { + alSourceStop(_sourceId); + return CDSOUNDSOURCE_ERROR_HANDLER; +} + +-(BOOL) play { + if (enabled_) { + alSourcePlay(_sourceId); + CDSOUNDSOURCE_UPDATE_LAST_ERROR; + if (lastError != AL_NO_ERROR) { + if (alcGetCurrentContext() == NULL) { + CDLOGINFO(@"Denshion::CDSoundSource - posting bad OpenAL context message"); + [[NSNotificationCenter defaultCenter] postNotificationName:kCDN_BadAlContext object:nil]; + } + return NO; + } else { + return YES; + } + } else { + return NO; + } +} + +-(BOOL) pause { + alSourcePause(_sourceId); + return CDSOUNDSOURCE_ERROR_HANDLER; +} + +-(BOOL) rewind { + alSourceRewind(_sourceId); + return CDSOUNDSOURCE_ERROR_HANDLER; +} + +-(void) setSoundId:(int) soundId { + [_engine _soundSourceAttachToBuffer:self soundId:soundId]; +} + +-(int) soundId { + return _soundId; +} + +-(float) durationInSeconds { + return [_engine bufferDurationInSeconds:_soundId]; +} + +#pragma mark CDSoundSource AudioInterrupt protocol +- (BOOL) mute { + return mute_; +} + +/** + * Setting mute silences all sounds but playing sounds continue to advance playback + */ +- (void) setMute:(BOOL) newMuteValue { + + if (newMuteValue == mute_) { + return; + } + + if (newMuteValue) { + //Remember what the gain was + _preMuteGain = self.gain; + self.gain = 0.0f; + mute_ = newMuteValue;//Make sure this is done after setting the gain property as the setter behaves differently depending on mute value + } else { + //Restore gain to what it was before being muted + mute_ = newMuteValue; + self.gain = _preMuteGain; + } +} + +- (BOOL) enabled { + return enabled_; +} + +- (void) setEnabled:(BOOL)enabledValue +{ + if (enabled_ == enabledValue) { + return; + } + enabled_ = enabledValue; + if (enabled_ == NO) { + [self stop]; + } +} + +@end + +//////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark CDAudioInterruptTargetGroup + +@implementation CDAudioInterruptTargetGroup + +-(id) init { + if ((self = [super init])) { + children_ = [[NSMutableArray alloc] initWithCapacity:32]; + enabled_ = YES; + mute_ = NO; + } + return self; +} + +-(void) addAudioInterruptTarget:(NSObject*) interruptibleTarget { + //Synchronize child with group settings; + [interruptibleTarget setMute:mute_]; + [interruptibleTarget setEnabled:enabled_]; + [children_ addObject:interruptibleTarget]; +} + +-(void) removeAudioInterruptTarget:(NSObject*) interruptibleTarget { + [children_ removeObjectIdenticalTo:interruptibleTarget]; +} + +- (BOOL) mute { + return mute_; +} + +/** + * Setting mute silences all sounds but playing sounds continue to advance playback + */ +- (void) setMute:(BOOL) newMuteValue { + + if (newMuteValue == mute_) { + return; + } + + for (NSObject* target in children_) { + [target setMute:newMuteValue]; + } +} + +- (BOOL) enabled { + return enabled_; +} + +- (void) setEnabled:(BOOL)enabledValue +{ + if (enabledValue == enabled_) { + return; + } + + for (NSObject* target in children_) { + [target setEnabled:enabledValue]; + } +} + +@end + + + +//////////////////////////////////////////////////////////////////////////// + +#pragma mark - +#pragma mark CDAsynchBufferLoader + +@implementation CDAsynchBufferLoader + +-(id) init:(NSArray *)loadRequests soundEngine:(CDSoundEngine *) theSoundEngine { + if ((self = [super init])) { + _loadRequests = loadRequests; + [_loadRequests retain]; + _soundEngine = theSoundEngine; + [_soundEngine retain]; + } + return self; +} + +-(void) main { + CDLOGINFO(@"Denshion::CDAsynchBufferLoader - loading buffers"); + [super main]; + _soundEngine.asynchLoadProgress = 0.0f; + + if ([_loadRequests count] > 0) { + float increment = 1.0f / [_loadRequests count]; + //Iterate over load request and load + for (CDBufferLoadRequest *loadRequest in _loadRequests) { + [_soundEngine loadBuffer:loadRequest.soundId filePath:loadRequest.filePath]; + _soundEngine.asynchLoadProgress += increment; + } + } + + //Completed + _soundEngine.asynchLoadProgress = 1.0f; + [[NSNotificationCenter defaultCenter] postNotificationName:kCDN_AsynchLoadComplete object:nil]; + +} + +-(void) dealloc { + [_loadRequests release]; + [_soundEngine release]; + [super dealloc]; +} + +@end + + +/////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark CDBufferLoadRequest + +@implementation CDBufferLoadRequest + +@synthesize filePath, soundId; + +-(id) init:(int) theSoundId filePath:(const NSString *) theFilePath { + if ((self = [super init])) { + soundId = theSoundId; + filePath = [theFilePath copy]; + } + return self; +} + +-(void) dealloc { + [filePath release]; + [super dealloc]; +} + +@end + +/////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark CDFloatInterpolator + +@implementation CDFloatInterpolator +@synthesize start,end,interpolationType; + +-(float) interpolate:(float) t { + + if (t < 1.0f) { + switch (interpolationType) { + case kIT_Linear: + //Linear interpolation + return ((end - start) * t) + start; + + case kIT_SCurve: + //Cubic s curve t^2 * (3 - 2t) + return ((float)(t * t * (3.0 - (2.0 * t))) * (end - start)) + start; + + case kIT_Exponential: + //Formulas taken from EaseAction + if (end > start) { + //Fade in + float logDelta = (t==0) ? 0 : powf(2, 10 * (t/1 - 1)) - 1 * 0.001f; + return ((end - start) * logDelta) + start; + } else { + //Fade Out + float logDelta = (-powf(2, -10 * t/1) + 1); + return ((end - start) * logDelta) + start; + } + default: + return 0.0f; + } + } else { + return end; + } +} + +-(id) init:(tCDInterpolationType) type startVal:(float) startVal endVal:(float) endVal { + if ((self = [super init])) { + start = startVal; + end = endVal; + interpolationType = type; + } + return self; +} + +@end + +/////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark CDPropertyModifier + +@implementation CDPropertyModifier + +@synthesize stopTargetWhenComplete; + +-(id) init:(id) theTarget interpolationType:(tCDInterpolationType) type startVal:(float) startVal endVal:(float) endVal { + if ((self = [super init])) { + if (target) { + //Release the previous target if there is one + [target release]; + } + target = theTarget; +#if CD_DEBUG + //Check target is of the required type + if (![theTarget isMemberOfClass:[self _allowableType]] ) { + CDLOG(@"Denshion::CDPropertyModifier target is not of type %@",[self _allowableType]); + NSAssert([theTarget isKindOfClass:[CDSoundEngine class]], @"CDPropertyModifier target not of required type"); + } +#endif + [target retain]; + startValue = startVal; + endValue = endVal; + if (interpolator) { + //Release previous interpolator if there is one + [interpolator release]; + } + interpolator = [[CDFloatInterpolator alloc] init:type startVal:startVal endVal:endVal]; + stopTargetWhenComplete = NO; + } + return self; +} + +-(void) dealloc { + CDLOGINFO(@"Denshion::CDPropertyModifier deallocated %@",self); + [target release]; + [interpolator release]; + [super dealloc]; +} + +-(void) modify:(float) t { + if (t < 1.0) { + [self _setTargetProperty:[interpolator interpolate:t]]; + } else { + //At the end + [self _setTargetProperty:endValue]; + if (stopTargetWhenComplete) { + [self _stopTarget]; + } + } +} + +-(float) startValue { + return startValue; +} + +-(void) setStartValue:(float) startVal +{ + startValue = startVal; + interpolator.start = startVal; +} + +-(float) endValue { + return startValue; +} + +-(void) setEndValue:(float) endVal +{ + endValue = endVal; + interpolator.end = endVal; +} + +-(tCDInterpolationType) interpolationType { + return interpolator.interpolationType; +} + +-(void) setInterpolationType:(tCDInterpolationType) interpolationType { + interpolator.interpolationType = interpolationType; +} + +-(void) _setTargetProperty:(float) newVal { + +} + +-(float) _getTargetProperty { + return 0.0f; +} + +-(void) _stopTarget { + +} + +-(Class) _allowableType { + return [NSObject class]; +} +@end + +/////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark CDSoundSourceFader + +@implementation CDSoundSourceFader + +-(void) _setTargetProperty:(float) newVal { + ((CDSoundSource*)target).gain = newVal; +} + +-(float) _getTargetProperty { + return ((CDSoundSource*)target).gain; +} + +-(void) _stopTarget { + [((CDSoundSource*)target) stop]; +} + +-(Class) _allowableType { + return [CDSoundSource class]; +} + +@end + +/////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark CDSoundSourcePanner + +@implementation CDSoundSourcePanner + +-(void) _setTargetProperty:(float) newVal { + ((CDSoundSource*)target).pan = newVal; +} + +-(float) _getTargetProperty { + return ((CDSoundSource*)target).pan; +} + +-(void) _stopTarget { + [((CDSoundSource*)target) stop]; +} + +-(Class) _allowableType { + return [CDSoundSource class]; +} + +@end + +/////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark CDSoundSourcePitchBender + +@implementation CDSoundSourcePitchBender + +-(void) _setTargetProperty:(float) newVal { + ((CDSoundSource*)target).pitch = newVal; +} + +-(float) _getTargetProperty { + return ((CDSoundSource*)target).pitch; +} + +-(void) _stopTarget { + [((CDSoundSource*)target) stop]; +} + +-(Class) _allowableType { + return [CDSoundSource class]; +} + +@end + +/////////////////////////////////////////////////////////////////////////////////////// +#pragma mark - +#pragma mark CDSoundEngineFader + +@implementation CDSoundEngineFader + +-(void) _setTargetProperty:(float) newVal { + ((CDSoundEngine*)target).masterGain = newVal; +} + +-(float) _getTargetProperty { + return ((CDSoundEngine*)target).masterGain; +} + +-(void) _stopTarget { + [((CDSoundEngine*)target) stopAllSounds]; +} + +-(Class) _allowableType { + return [CDSoundEngine class]; +} + +@end + + diff --git a/CocosDenshion/mac/SimpleAudioEngine.mm b/CocosDenshion/mac/SimpleAudioEngine.mm new file mode 100644 index 0000000000..24e5a5f9ab --- /dev/null +++ b/CocosDenshion/mac/SimpleAudioEngine.mm @@ -0,0 +1,280 @@ +/**************************************************************************** +Copyright (c) 2010 cocos2d-x.org + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ + +#include "SimpleAudioEngine.h" +#include "SimpleAudioEngine_objc.h" + +static void static_end() +{ + [SimpleAudioEngine end]; +} + +static void static_preloadBackgroundMusic(const char* pszFilePath) +{ + [[SimpleAudioEngine sharedEngine] preloadBackgroundMusic: [NSString stringWithUTF8String: pszFilePath]]; +} + +static void static_playBackgroundMusic(const char* pszFilePath, bool bLoop) +{ + [[SimpleAudioEngine sharedEngine] playBackgroundMusic: [NSString stringWithUTF8String: pszFilePath] loop: bLoop]; +} + +static void static_stopBackgroundMusic() +{ + [[SimpleAudioEngine sharedEngine] stopBackgroundMusic]; +} + +static void static_pauseBackgroundMusic() +{ + [[SimpleAudioEngine sharedEngine] pauseBackgroundMusic]; +} + +static void static_resumeBackgroundMusic() +{ + [[SimpleAudioEngine sharedEngine] resumeBackgroundMusic]; +} + +static void static_rewindBackgroundMusic() +{ + [[SimpleAudioEngine sharedEngine] rewindBackgroundMusic]; +} + +static bool static_willPlayBackgroundMusic() +{ + return [[SimpleAudioEngine sharedEngine] willPlayBackgroundMusic]; +} + +static bool static_isBackgroundMusicPlaying() +{ + return [[SimpleAudioEngine sharedEngine] isBackgroundMusicPlaying]; +} + +static float static_getBackgroundMusicVolume() +{ + return [[SimpleAudioEngine sharedEngine] backgroundMusicVolume]; +} + +static void static_setBackgroundMusicVolume(float volume) +{ + volume = MAX( MIN(volume, 1.0), 0 ); + [SimpleAudioEngine sharedEngine].backgroundMusicVolume = volume; +} + +static float static_getEffectsVolume() +{ + return [[SimpleAudioEngine sharedEngine] effectsVolume]; +} + +static void static_setEffectsVolume(float volume) +{ + volume = MAX( MIN(volume, 1.0), 0 ); + [SimpleAudioEngine sharedEngine].effectsVolume = volume; +} + +static unsigned int static_playEffect(const char* pszFilePath, bool bLoop) +{ + return [[SimpleAudioEngine sharedEngine] playEffect:[NSString stringWithUTF8String: pszFilePath] loop:bLoop]; +} + +static void static_stopEffect(int nSoundId) +{ + [[SimpleAudioEngine sharedEngine] stopEffect: nSoundId]; +} + +static void static_preloadEffect(const char* pszFilePath) +{ + [[SimpleAudioEngine sharedEngine] preloadEffect: [NSString stringWithUTF8String: pszFilePath]]; +} + +static void static_unloadEffect(const char* pszFilePath) +{ + [[SimpleAudioEngine sharedEngine] unloadEffect: [NSString stringWithUTF8String: pszFilePath]]; +} + +static void static_pauseEffect(unsigned int uSoundId) +{ + [[SimpleAudioEngine sharedEngine] pauseEffect: uSoundId]; +} + +static void static_pauseAllEffects() +{ + [[SimpleAudioEngine sharedEngine] pauseAllEffects]; +} + +static void static_resumeEffect(unsigned int uSoundId) +{ + [[SimpleAudioEngine sharedEngine] resumeEffect: uSoundId]; +} + +static void static_resumeAllEffects() +{ + [[SimpleAudioEngine sharedEngine] resumeAllEffects]; +} + +static void static_stopAllEffects() +{ + [[SimpleAudioEngine sharedEngine] stopAllEffects]; +} + +namespace CocosDenshion { + +static SimpleAudioEngine *s_pEngine; + +SimpleAudioEngine::SimpleAudioEngine() +{ + +} + +SimpleAudioEngine::~SimpleAudioEngine() +{ + +} + +SimpleAudioEngine* SimpleAudioEngine::sharedEngine() +{ + if (! s_pEngine) + { + s_pEngine = new SimpleAudioEngine(); + } + + return s_pEngine; +} + +void SimpleAudioEngine::end() +{ + if (s_pEngine) + { + delete s_pEngine; + s_pEngine = NULL; + } + + static_end(); +} + +void SimpleAudioEngine::preloadBackgroundMusic(const char* pszFilePath) +{ + static_preloadBackgroundMusic(pszFilePath); +} + +void SimpleAudioEngine::playBackgroundMusic(const char* pszFilePath, bool bLoop) +{ + static_playBackgroundMusic(pszFilePath, bLoop); +} + +void SimpleAudioEngine::stopBackgroundMusic(bool bReleaseData) +{ + static_stopBackgroundMusic(); +} + +void SimpleAudioEngine::pauseBackgroundMusic() +{ + static_pauseBackgroundMusic(); +} + +void SimpleAudioEngine::resumeBackgroundMusic() +{ + static_resumeBackgroundMusic(); +} + +void SimpleAudioEngine::rewindBackgroundMusic() +{ + static_rewindBackgroundMusic(); +} + +bool SimpleAudioEngine::willPlayBackgroundMusic() +{ + return static_willPlayBackgroundMusic(); +} + +bool SimpleAudioEngine::isBackgroundMusicPlaying() +{ + return static_isBackgroundMusicPlaying(); +} + +float SimpleAudioEngine::getBackgroundMusicVolume() +{ + return static_getBackgroundMusicVolume(); +} + +void SimpleAudioEngine::setBackgroundMusicVolume(float volume) +{ + static_setBackgroundMusicVolume(volume); +} + +float SimpleAudioEngine::getEffectsVolume() +{ + return static_getEffectsVolume(); +} + +void SimpleAudioEngine::setEffectsVolume(float volume) +{ + static_setEffectsVolume(volume); +} + +unsigned int SimpleAudioEngine::playEffect(const char* pszFilePath, bool bLoop) +{ + return static_playEffect(pszFilePath, bLoop); +} + +void SimpleAudioEngine::stopEffect(unsigned int nSoundId) +{ + static_stopEffect(nSoundId); +} + +void SimpleAudioEngine::preloadEffect(const char* pszFilePath) +{ + static_preloadEffect(pszFilePath); +} + +void SimpleAudioEngine::unloadEffect(const char* pszFilePath) +{ + static_unloadEffect(pszFilePath); +} + +void SimpleAudioEngine::pauseEffect(unsigned int uSoundId) +{ + static_pauseEffect(uSoundId); +} + +void SimpleAudioEngine::resumeEffect(unsigned int uSoundId) +{ + static_resumeEffect(uSoundId); +} + +void SimpleAudioEngine::pauseAllEffects() +{ + static_pauseAllEffects(); +} + +void SimpleAudioEngine::resumeAllEffects() +{ + static_resumeAllEffects(); +} + +void SimpleAudioEngine::stopAllEffects() +{ + static_stopAllEffects(); +} + +} // endof namespace CocosDenshion { diff --git a/CocosDenshion/mac/SimpleAudioEngine_objc.h b/CocosDenshion/mac/SimpleAudioEngine_objc.h new file mode 100644 index 0000000000..a5e8faa556 --- /dev/null +++ b/CocosDenshion/mac/SimpleAudioEngine_objc.h @@ -0,0 +1,100 @@ +/* + Copyright (c) 2010 Steve Oldmeadow + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + $Id$ + */ + + +#import "CDAudioManager.h" + +/** + A wrapper to the CDAudioManager object. + This is recommended for basic audio requirements. If you just want to play some sound fx + and some background music and have no interest in learning the lower level workings then + this is the interface to use. + + Requirements: + - Firmware: OS 2.2 or greater + - Files: SimpleAudioEngine.*, CocosDenshion.* + - Frameworks: OpenAL, AudioToolbox, AVFoundation + @since v0.8 + */ +@interface SimpleAudioEngine : NSObject { + + BOOL mute_; + BOOL enabled_; +} + +/** Background music volume. Range is 0.0f to 1.0f. This will only have an effect if willPlayBackgroundMusic returns YES */ +@property (readwrite) float backgroundMusicVolume; +/** Effects volume. Range is 0.0f to 1.0f */ +@property (readwrite) float effectsVolume; +/** If NO it indicates background music will not be played either because no background music is loaded or the audio session does not permit it.*/ +@property (readonly) BOOL willPlayBackgroundMusic; + +/** returns the shared instance of the SimpleAudioEngine object */ ++ (SimpleAudioEngine*) sharedEngine; + +/** Preloads a music file so it will be ready to play as background music */ +-(void) preloadBackgroundMusic:(NSString*) filePath; + +/** plays background music in a loop*/ +-(void) playBackgroundMusic:(NSString*) filePath; +/** plays background music, if loop is true the music will repeat otherwise it will be played once */ +-(void) playBackgroundMusic:(NSString*) filePath loop:(BOOL) loop; +/** stops playing background music */ +-(void) stopBackgroundMusic; +/** pauses the background music */ +-(void) pauseBackgroundMusic; +/** resume background music that has been paused */ +-(void) resumeBackgroundMusic; +/** rewind the background music */ +-(void) rewindBackgroundMusic; +/** returns whether or not the background music is playing */ +-(BOOL) isBackgroundMusicPlaying; + +/** plays an audio effect with a file path*/ +-(ALuint) playEffect:(NSString*) filePath loop:(BOOL) loop; +/** stop a sound that is playing, note you must pass in the soundId that is returned when you started playing the sound with playEffect */ +-(void) stopEffect:(ALuint) soundId; +/** plays an audio effect with a file path, pitch, pan and gain */ +-(ALuint) playEffect:(NSString*) filePath loop:(BOOL)loop pitch:(Float32) pitch pan:(Float32) pan gain:(Float32) gain; +/** pause an audio */ +-(void) pauseEffect:(ALuint) soundId; +/** pause all audioes */ +-(void) pauseAllEffects; +/** resume an audio */ +-(void) resumeEffect:(ALuint) soundId; +/** resume all audioes */ +-(void) resumeAllEffects; +/** stop all audioes */ +-(void) stopAllEffects; +/** preloads an audio effect */ +-(void) preloadEffect:(NSString*) filePath; +/** unloads an audio effect from memory */ +-(void) unloadEffect:(NSString*) filePath; +/** Gets a CDSoundSource object set up to play the specified file. */ +-(CDSoundSource *) soundSourceForFile:(NSString*) filePath; + +/** Shuts down the shared audio engine instance so that it can be reinitialised */ ++(void) end; + +@end diff --git a/CocosDenshion/mac/SimpleAudioEngine_objc.m b/CocosDenshion/mac/SimpleAudioEngine_objc.m new file mode 100644 index 0000000000..4b17439bfc --- /dev/null +++ b/CocosDenshion/mac/SimpleAudioEngine_objc.m @@ -0,0 +1,240 @@ +/* + Copyright (c) 2010 Steve Oldmeadow + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + $Id$ + */ + +#import "SimpleAudioEngine_objc.h" + +@implementation SimpleAudioEngine + +static SimpleAudioEngine *sharedEngine = nil; +static CDSoundEngine* soundEngine = nil; +static CDAudioManager *am = nil; +static CDBufferManager *bufferManager = nil; + +// Init ++ (SimpleAudioEngine *) sharedEngine +{ + @synchronized(self) { + if (!sharedEngine) + sharedEngine = [[SimpleAudioEngine alloc] init]; + } + return sharedEngine; +} + ++ (id) alloc +{ + @synchronized(self) { + NSAssert(sharedEngine == nil, @"Attempted to allocate a second instance of a singleton."); + return [super alloc]; + } + return nil; +} + +-(id) init +{ + if((self=[super init])) { + am = [CDAudioManager sharedManager]; + soundEngine = am.soundEngine; + bufferManager = [[CDBufferManager alloc] initWithEngine:soundEngine]; + mute_ = NO; + enabled_ = YES; + } + return self; +} + +// Memory +- (void) dealloc +{ + am = nil; + soundEngine = nil; + bufferManager = nil; + [super dealloc]; +} + ++(void) end +{ + am = nil; + [CDAudioManager end]; + [bufferManager release]; + [sharedEngine release]; + sharedEngine = nil; +} + +#pragma mark SimpleAudioEngine - background music + +-(void) preloadBackgroundMusic:(NSString*) filePath { + [am preloadBackgroundMusic:filePath]; +} + +-(void) playBackgroundMusic:(NSString*) filePath +{ + [am playBackgroundMusic:filePath loop:TRUE]; +} + +-(void) playBackgroundMusic:(NSString*) filePath loop:(BOOL) loop +{ + [am playBackgroundMusic:filePath loop:loop]; +} + +-(void) stopBackgroundMusic +{ + [am stopBackgroundMusic]; +} + +-(void) pauseBackgroundMusic { + [am pauseBackgroundMusic]; +} + +-(void) resumeBackgroundMusic { + [am resumeBackgroundMusic]; +} + +-(void) rewindBackgroundMusic { + [am rewindBackgroundMusic]; +} + +-(BOOL) isBackgroundMusicPlaying { + return [am isBackgroundMusicPlaying]; +} + +-(BOOL) willPlayBackgroundMusic { + return [am willPlayBackgroundMusic]; +} + +#pragma mark SimpleAudioEngine - sound effects + +-(ALuint) playEffect:(NSString*) filePath loop:(BOOL) loop +{ + return [self playEffect:filePath loop:loop pitch:1.0f pan:0.0f gain:1.0f]; +} + +-(ALuint) playEffect:(NSString*) filePath loop:(BOOL) loop pitch:(Float32) pitch pan:(Float32) pan gain:(Float32) gain +{ + int soundId = [bufferManager bufferForFile:filePath create:YES]; + if (soundId != kCDNoBuffer) { + return [soundEngine playSound:soundId sourceGroupId:0 pitch:pitch pan:pan gain:gain loop:loop]; + } else { + return CD_MUTE; + } +} + +-(void) stopEffect:(ALuint) soundId { + [soundEngine stopSound:soundId]; +} + +-(void) pauseEffect:(ALuint) soundId { + [soundEngine pauseSound: soundId]; +} + +-(void) pauseAllEffects { + [soundEngine pauseAllSounds]; +} + +-(void) resumeEffect:(ALuint) soundId { + [soundEngine resumeSound: soundId]; +} + +-(void) resumeAllEffects { + [soundEngine resumeAllSounds]; +} + +-(void) stopAllEffects { + [soundEngine stopAllSounds]; +} + +-(void) preloadEffect:(NSString*) filePath +{ + int soundId = [bufferManager bufferForFile:filePath create:YES]; + if (soundId == kCDNoBuffer) { + CDLOG(@"Denshion::SimpleAudioEngine sound failed to preload %@",filePath); + } +} + +-(void) unloadEffect:(NSString*) filePath +{ + CDLOGINFO(@"Denshion::SimpleAudioEngine unloadedEffect %@",filePath); + [bufferManager releaseBufferForFile:filePath]; +} + +#pragma mark Audio Interrupt Protocol +-(BOOL) mute +{ + return mute_; +} + +-(void) setMute:(BOOL) muteValue +{ + if (mute_ != muteValue) { + mute_ = muteValue; + am.mute = mute_; + } +} + +-(BOOL) enabled +{ + return enabled_; +} + +-(void) setEnabled:(BOOL) enabledValue +{ + if (enabled_ != enabledValue) { + enabled_ = enabledValue; + am.enabled = enabled_; + } +} + + +#pragma mark SimpleAudioEngine - BackgroundMusicVolume +-(float) backgroundMusicVolume +{ + return am.backgroundMusic.volume; +} + +-(void) setBackgroundMusicVolume:(float) volume +{ + am.backgroundMusic.volume = volume; +} + +#pragma mark SimpleAudioEngine - EffectsVolume +-(float) effectsVolume +{ + return am.soundEngine.masterGain; +} + +-(void) setEffectsVolume:(float) volume +{ + am.soundEngine.masterGain = volume; +} + +-(CDSoundSource *) soundSourceForFile:(NSString*) filePath { + int soundId = [bufferManager bufferForFile:filePath create:YES]; + if (soundId != kCDNoBuffer) { + CDSoundSource *result = [soundEngine soundSourceForSound:soundId sourceGroupId:0]; + CDLOGINFO(@"Denshion::SimpleAudioEngine sound source created for %@",filePath); + return result; + } else { + return nil; + } +} + +@end diff --git a/cocos2dx/include/cocos2d.h b/cocos2dx/include/cocos2d.h index 285f580ac3..f16d1121fc 100755 --- a/cocos2dx/include/cocos2d.h +++ b/cocos2dx/include/cocos2d.h @@ -144,13 +144,20 @@ THE SOFTWARE. #endif // CC_TARGET_PLATFROM == CC_PLATFORM_ANDROID #if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) -#include "platform/win32/CCAccelerometer.h" -#include "platform/win32/CCApplication.h" -#include "platform/win32/CCEGLView.h" -#include "platform/win32/CCGL.h" -#include "platform/win32/CCStdC.h" + #include "platform/win32/CCAccelerometer.h" + #include "platform/win32/CCApplication.h" + #include "platform/win32/CCEGLView.h" + #include "platform/win32/CCGL.h" + #include "platform/win32/CCStdC.h" #endif // CC_TARGET_PLATFROM == CC_PLATFORM_WIN32 +#if (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) + #include "platform/mac/CCAccelerometer.h" + #include "platform/mac/CCApplication.h" + #include "platform/mac/CCEGLView.h" + #include "platform/mac/CCGL.h" + #include "platform/mac/CCStdC.h" +#endif // CC_TARGET_PLATFORM == CC_PLATFORM_MAC // script_support #include "script_support/CCScriptSupport.h" diff --git a/cocos2dx/platform/CCFileUtilsCommon_cpp.h b/cocos2dx/platform/CCFileUtilsCommon_cpp.h index c6a1ce4f84..93f093d641 100644 --- a/cocos2dx/platform/CCFileUtilsCommon_cpp.h +++ b/cocos2dx/platform/CCFileUtilsCommon_cpp.h @@ -29,7 +29,7 @@ THE SOFTWARE. #include "CCDirector.h" #include "cocoa/CCDictionary.h" -#if (CC_TARGET_PLATFORM != CC_PLATFORM_IOS) +#if (CC_TARGET_PLATFORM != CC_PLATFORM_IOS && CC_TARGET_PLATFORM != CC_PLATFORM_MAC) #include "cocoa/CCString.h" #include "CCSAXParser.h" @@ -455,4 +455,4 @@ bool CCFileUtils::isPopupNotify() NS_CC_END -#endif // (CC_TARGET_PLATFORM != CC_PLATFORM_IOS) +#endif // (CC_TARGET_PLATFORM != CC_PLATFORM_IOS && CC_TARGET_PLATFORM != CC_PLATFORM_MAC) diff --git a/cocos2dx/platform/CCImageCommon_cpp.h b/cocos2dx/platform/CCImageCommon_cpp.h index 4d65976e7f..24ed656085 100644 --- a/cocos2dx/platform/CCImageCommon_cpp.h +++ b/cocos2dx/platform/CCImageCommon_cpp.h @@ -36,7 +36,7 @@ THE SOFTWARE. #include #include -#if (CC_TARGET_PLATFORM != CC_PLATFORM_IOS) +#if (CC_TARGET_PLATFORM != CC_PLATFORM_IOS && CC_TARGET_PLATFORM != CC_PLATFORM_MAC) NS_CC_BEGIN @@ -860,7 +860,7 @@ bool CCImage::_saveImageToJPG(const char * pszFilePath) NS_CC_END -#endif // (CC_TARGET_PLATFORM != TARGET_OS_IPHONE) +#endif // (CC_TARGET_PLATFORM != TARGET_OS_IPHONE && CC_TARGET_PLATFORM != CC_PLATFORM_MAC) /* ios/CCImage_ios.mm uses "mm" as the extension, so we cannot inclue it in this CCImage.cpp. It makes a little difference on ios */ diff --git a/cocos2dx/platform/CCPlatformConfig.h b/cocos2dx/platform/CCPlatformConfig.h index 857cf55f08..64fd58aa05 100644 --- a/cocos2dx/platform/CCPlatformConfig.h +++ b/cocos2dx/platform/CCPlatformConfig.h @@ -45,6 +45,7 @@ build for which target platform #define CC_PLATFORM_LINUX 5 #define CC_PLATFORM_BADA 6 #define CC_PLATFORM_QNX 7 +#define CC_PLATFORM_MAC 8 // Determine tartet platform by compile environment macro. #define CC_TARGET_PLATFORM CC_PLATFORM_UNKNOWN @@ -92,6 +93,13 @@ build for which target platform #define CC_TARGET_PLATFORM CC_PLATFORM_QNX #endif +// mac +#if ! CC_TARGET_PLATFORM && defined(TARGET_OS_MAC) + #undef CC_TARGET_PLATFORM + #define CC_TARGET_PLATFORM CC_PLATFORM_MAC + //#define CC_SUPPORT_PVRTC +#endif + ////////////////////////////////////////////////////////////////////////// // post configure ////////////////////////////////////////////////////////////////////////// diff --git a/cocos2dx/platform/mac/CCAccelerometer.h b/cocos2dx/platform/mac/CCAccelerometer.h new file mode 100755 index 0000000000..79e2e8ad61 --- /dev/null +++ b/cocos2dx/platform/mac/CCAccelerometer.h @@ -0,0 +1,43 @@ +/**************************************************************************** +Copyright (c) 2010 cocos2d-x.org + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ + +#ifndef __PLATFORM_MAC_CCACCELEROMETER_H__ +#define __PLATFORM_MAC_CCACCELEROMETER_H__ + +#include "CCAccelerometerDelegate.h" + +namespace cocos2d { + +class CC_DLL CCAccelerometer +{ +public: + CCAccelerometer() {} + ~CCAccelerometer() {} + + void setDelegate(CCAccelerometerDelegate* pDelegate) { CC_UNUSED_PARAM(pDelegate); } +}; + +}//namespace cocos2d + +#endif diff --git a/cocos2dx/platform/mac/CCApplication.h b/cocos2dx/platform/mac/CCApplication.h new file mode 100755 index 0000000000..353b991833 --- /dev/null +++ b/cocos2dx/platform/mac/CCApplication.h @@ -0,0 +1,79 @@ +/**************************************************************************** +Copyright (c) 2010 cocos2d-x.org + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ + +#pragma once +#ifndef __CC_APPLICATION_MAC_H__ +#define __CC_APPLICATION_MAC_H__ + +#include "platform/CCCommon.h" +#include "platform/CCApplicationProtocol.h" + +NS_CC_BEGIN; + +class CCRect; +class CCApplication; + +class CC_DLL CCApplication : public CCApplicationProtocol +{ +public: + CCApplication(); + virtual ~CCApplication(); + + /** + @brief Callback by CCDirector for limit FPS. + @interval The time, which expressed in second in second, between current frame and next. + */ + void setAnimationInterval(double interval); + + /** + @brief Get status bar rectangle in EGLView window. + */ + //void statusBarFrame(CCRect * rect); + + /** + @brief Run the message loop. + */ + int run(); + + /** + @brief Get current applicaiton instance. + @return Current application instance pointer. + */ + static CCApplication& sharedApplication(); + + /** + @brief Get current language config + @return Current language config + */ + virtual ccLanguageType getCurrentLanguage(); + + virtual bool isIpad(); + +protected: + static CCApplication * sm_pSharedApplication; +}; + +NS_CC_END; + +#endif // end of __CC_APPLICATION_MAC_H__ diff --git a/cocos2dx/platform/mac/CCApplication.mm b/cocos2dx/platform/mac/CCApplication.mm new file mode 100755 index 0000000000..fe14f80934 --- /dev/null +++ b/cocos2dx/platform/mac/CCApplication.mm @@ -0,0 +1,113 @@ +/**************************************************************************** + Copyright (c) 2010 cocos2d-x.org + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ + +#import "CCApplication.h" + +#import + +#import "CCGeometry.h" +#import "CCDirectorCaller.h" + +NS_CC_BEGIN; + +CCApplication* CCApplication::sm_pSharedApplication = 0; + +CCApplication::CCApplication() +{ + CC_ASSERT(! sm_pSharedApplication); + sm_pSharedApplication = this; +} + +CCApplication::~CCApplication() +{ + CC_ASSERT(this == sm_pSharedApplication); + sm_pSharedApplication = 0; +} + +int CCApplication::run() +{ + if (/*initInstance() &&*/ applicationDidFinishLaunching()) + { + [[CCDirectorCaller sharedDirectorCaller] startMainLoop]; + } + return 0; +} + +void CCApplication::setAnimationInterval(double interval) +{ + [[CCDirectorCaller sharedDirectorCaller] setAnimationInterval: interval ]; +} + +/* +void CCApplication::statusBarFrame(cocos2d::CCRect * rect) +{ +#if 0 + rect->origin.x = [[NSApplication sharedApplication] statusBarFrame].origin.x; + rect->origin.y = [[NSApplication sharedApplication] statusBarFrame].origin.y; + rect->size.width = [[NSApplication sharedApplication] statusBarFrame].size.width; + rect->size.height = [[NSApplication sharedApplication] statusBarFrame].size.height; +#endif +} +*/ + +bool CCApplication::isIpad() +{ + return false; +} + +///////////////////////////////////////////////////////////////////////////////////////////////// +// static member function +////////////////////////////////////////////////////////////////////////////////////////////////// + +CCApplication& CCApplication::sharedApplication() +{ + CC_ASSERT(sm_pSharedApplication); + return *sm_pSharedApplication; +} + +ccLanguageType CCApplication::getCurrentLanguage() +{ + // get the current language and country config + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSArray *languages = [defaults objectForKey:@"AppleLanguages"]; + NSString *currentLanguage = [languages objectAtIndex:0]; + + // get the current language code.(such as English is "en", Chinese is "zh" and so on) + NSDictionary* temp = [NSLocale componentsFromLocaleIdentifier:currentLanguage]; + NSString * languageCode = [temp objectForKey:NSLocaleLanguageCode]; + + ccLanguageType ret = kLanguageEnglish; + if ([languageCode isEqualToString:@"zh"]) + { + ret = kLanguageChinese; + } + else if ([languageCode isEqualToString:@"en"]) + { + ret = kLanguageEnglish; + } + + return ret; +} + +NS_CC_END; diff --git a/cocos2dx/platform/mac/CCCommon.mm b/cocos2dx/platform/mac/CCCommon.mm new file mode 100755 index 0000000000..dc232b6c66 --- /dev/null +++ b/cocos2dx/platform/mac/CCCommon.mm @@ -0,0 +1,67 @@ +/**************************************************************************** + Copyright (c) 2010 cocos2d-x.org + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +#include + +#include "platform/CCCommon.h" + +#include +#include +#import "EAGLView.h" + + +NS_CC_BEGIN; + +void CCLog(const char * pszFormat, ...) +{ + printf("Cocos2d: "); + char szBuf[kMaxLogLen]; + + va_list ap; + va_start(ap, pszFormat); + vsprintf(szBuf, pszFormat, ap); + va_end(ap); + printf("%s", szBuf); + printf("\n"); +} + +// ios no MessageBox, use CCLog instead +void CCMessageBox(const char * pszMsg, const char * pszTitle) +{ + NSString * title = (pszTitle) ? [NSString stringWithUTF8String : pszTitle] : nil; + NSString * msg = (pszMsg) ? [NSString stringWithUTF8String : pszMsg] : nil; + + NSAlert *alert = [[[NSAlert alloc] init] autorelease]; + [alert addButtonWithTitle:@"OK"]; + [alert setMessageText:msg]; + [alert setInformativeText:title]; + [alert setAlertStyle:NSWarningAlertStyle]; + + NSWindow *window = [[EAGLView sharedEGLView] window]; + [alert beginSheetModalForWindow:window + modalDelegate:[window delegate] + didEndSelector:nil + contextInfo:nil]; +} + +NS_CC_END; diff --git a/cocos2dx/platform/mac/CCDirectorCaller.h b/cocos2dx/platform/mac/CCDirectorCaller.h new file mode 100755 index 0000000000..f128d8e32a --- /dev/null +++ b/cocos2dx/platform/mac/CCDirectorCaller.h @@ -0,0 +1,38 @@ +/**************************************************************************** + Copyright (c) 2010 cocos2d-x.org + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +#import +#import + +@interface CCDirectorCaller : NSObject { + CVDisplayLinkRef displayLink; + NSTimer *renderTimer; + int interval; +} +@property (readwrite) int interval; +-(void) startMainLoop; +-(void) doCaller: (id) sender; +-(void) setAnimationInterval:(double)interval; ++(id) sharedDirectorCaller; ++(void) destroy; +@end diff --git a/cocos2dx/platform/mac/CCDirectorCaller.mm b/cocos2dx/platform/mac/CCDirectorCaller.mm new file mode 100755 index 0000000000..f5d9610901 --- /dev/null +++ b/cocos2dx/platform/mac/CCDirectorCaller.mm @@ -0,0 +1,201 @@ +/**************************************************************************** + Copyright (c) 2010 cocos2d-x.org + + http://www.cocos2d-x.org + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +#import +#import "CCDirectorCaller.h" +#import "CCDirector.h" +#import "EAGLView.h" +#import "CCEventDispatcher.h" +#include "CCAutoreleasePool.h" + +static id s_sharedDirectorCaller; + +@interface NSObject(CADisplayLink) ++(id) displayLinkWithTarget: (id)arg1 selector:(SEL)arg2; +-(void) addToRunLoop: (id)arg1 forMode: (id)arg2; +-(void) setFrameInterval: (int)interval; +-(void) invalidate; +@end + +@implementation CCDirectorCaller + +@synthesize interval; + ++(id) sharedDirectorCaller +{ + if (s_sharedDirectorCaller == nil) + { + s_sharedDirectorCaller = [CCDirectorCaller new]; + } + + return s_sharedDirectorCaller; +} + ++(void) destroy +{ + [s_sharedDirectorCaller release]; +} + +-(void) alloc +{ + interval = 1; +} + +-(void) dealloc +{ + if (displayLink) { + CVDisplayLinkRelease(displayLink); + } + if (renderTimer) { + [renderTimer release]; + } + [super dealloc]; +} + +- (CVReturn) getFrameForTime:(const CVTimeStamp*)outputTime +{ +#if CC_DIRECTOR_MAC_USE_DISPLAY_LINK_THREAD + //if( ! runningThread_ ) + //runningThread_ = [NSThread currentThread]; + + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + cocos2d::CCDirector::sharedDirector()->drawScene(); + cocos2d::CCPoolManager::sharedPoolManager()->pop(); + [[CCEventDispatcher sharedDispatcher] dispatchQueuedEvents]; + + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:nil]; + + // release the objects + [pool release]; + +#else + [self performSelector:@selector(drawScene) onThread:[NSThread currentThread] withObject:nil waitUntilDone:YES]; +#endif + + return kCVReturnSuccess; +} + +// This is the renderer output callback function +static CVReturn MyDisplayLinkCallback(CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext) +{ +// CVReturn result = [(CCDirectorCaller*)displayLinkContext getFrameForTime:outputTime]; +// return result; + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + cocos2d::CCDirector::sharedDirector()->mainLoop(); + [pool release]; + + return kCVReturnSuccess; +} + +- (void)timerFired:(id)sender +{ + // It is good practice in a Cocoa application to allow the system to send the -drawRect: + // message when it needs to draw, and not to invoke it directly from the timer. + // All we do here is tell the display it needs a refresh + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + // get the opengl view + EAGLView *openGLView = [EAGLView sharedEGLView]; + [openGLView lockOpenGLContext]; + + // run the main cocos2d loop + cocos2d::CCDirector::sharedDirector()->mainLoop(); + + // flush buffer (this line is very important!) + [[openGLView openGLContext] flushBuffer]; + + [openGLView unlockOpenGLContext]; + + [pool release]; +} + +-(void) startMainLoop +{ + // CCDirector::setAnimationInterval() is called, we should invalide it first +// [displayLink invalidate]; +// displayLink = nil; +// +// displayLink = [NSClassFromString(@"CADisplayLink") displayLinkWithTarget:self selector:@selector(doCaller:)]; +// [displayLink setFrameInterval: self.interval]; +// [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; +#if ! CC_DIRECTOR_MAC_USE_DISPLAY_LINK_THREAD + NSThread* thread = [[NSThread alloc] initWithTarget:self selector:@selector(mainLoop) object:nil]; + [thread start]; +#endif + // NSTimer + renderTimer = [NSTimer timerWithTimeInterval:self.interval/60.0f //a 1ms time interval + target:self + selector:@selector(timerFired:) + userInfo:nil + repeats:YES]; + + [[NSRunLoop currentRunLoop] addTimer:renderTimer + forMode:NSDefaultRunLoopMode]; + [[NSRunLoop currentRunLoop] addTimer:renderTimer + forMode:NSEventTrackingRunLoopMode]; //Ensure timer fires during resize + +/* + // CVDisplayLink + //cocos2d::CCDirector::sharedDirector()->gettimeofday(); + + // Create a display link capable of being used with all active displays + CVDisplayLinkCreateWithActiveCGDisplays(&displayLink); + + // Set the renderer output callback function + CVDisplayLinkSetOutputCallback(displayLink, &MyDisplayLinkCallback, self); + + // Set the display link for the current renderer + EAGLView *openGLView_ = (EAGLView*)[EAGLView sharedEGLView]; + CGLContextObj cglContext = (CGLContextObj)[[openGLView_ openGLContext] CGLContextObj]; + CGLPixelFormatObj cglPixelFormat = (CGLPixelFormatObj)[[openGLView_ pixelFormat] CGLPixelFormatObj]; + CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(displayLink, cglContext, cglPixelFormat); + + // Activate the display link + CVDisplayLinkStart(displayLink); +*/ +} + +-(void) setAnimationInterval:(double)intervalNew +{ + self.interval = 60.0 * intervalNew; + [renderTimer invalidate]; + renderTimer = nil; + renderTimer = [NSTimer timerWithTimeInterval:self.interval/60.0f //a 1ms time interval + target:self + selector:@selector(timerFired:) + userInfo:nil + repeats:YES]; + + [[NSRunLoop currentRunLoop] addTimer:renderTimer + forMode:NSDefaultRunLoopMode]; + [[NSRunLoop currentRunLoop] addTimer:renderTimer + forMode:NSEventTrackingRunLoopMode]; +} + +-(void) doCaller: (id) sender +{ + cocos2d::CCDirector::sharedDirector()->mainLoop(); +} + +@end diff --git a/cocos2dx/platform/mac/CCEGLView.h b/cocos2dx/platform/mac/CCEGLView.h new file mode 100755 index 0000000000..225da3b8c9 --- /dev/null +++ b/cocos2dx/platform/mac/CCEGLView.h @@ -0,0 +1,61 @@ +/**************************************************************************** +Copyright (c) 2010 cocos2d-x.org + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ + +#ifndef __CC_EGLVIEW_MAC_H__ +#define __CC_EGLVIEW_MAC_H__ + +#include "platform/CCCommon.h" +#include "platform/CCEGLViewProtocol.h" + +namespace cocos2d { +class CCSet; +class CCTouch; +class CCSize; + +class CC_DLL CCEGLView : public CCEGLViewProtocol +{ +public: + CCEGLView(); + virtual ~CCEGLView(); + + CCSize getSize(); + bool isOpenGLReady(); + bool canSetContentScaleFactor(); + bool isIpad(); + void setContentScaleFactor(float contentScaleFactor); + virtual CCSize getFrameSize(); + void end(); + void swapBuffers(); + + void setIMEKeyboardState(bool bOpen); + + void setMultiTouchMask(bool mask); + + static CCEGLView& sharedOpenGLView(); + +}; + +} // end of namespace cocos2d + +#endif // end of __CC_EGLVIEW_MAC_H__ diff --git a/cocos2dx/platform/mac/CCEGLView.mm b/cocos2dx/platform/mac/CCEGLView.mm new file mode 100755 index 0000000000..7f17a94296 --- /dev/null +++ b/cocos2dx/platform/mac/CCEGLView.mm @@ -0,0 +1,116 @@ +/**************************************************************************** +Copyright (c) 2010 cocos2d-x.org + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ +#include "CCEGLView.h" +#include "EAGLView.h" +#include "CCDirectorCaller.h" +#include "CCSet.h" +#include "CCTouch.h" +#include "CCTouchDispatcher.h" + +namespace cocos2d { + +CCEGLView::CCEGLView() +{ +} + +CCEGLView::~CCEGLView() +{ +} + +cocos2d::CCSize CCEGLView::getSize() +{ + cocos2d::CCSize size([[EAGLView sharedEGLView] getWidth], [[EAGLView sharedEGLView] getHeight]); + return size; +} + +bool CCEGLView::isIpad() +{ + return false; +} + +bool CCEGLView::isOpenGLReady() +{ + return [EAGLView sharedEGLView] != NULL; +} + +bool CCEGLView::canSetContentScaleFactor() +{ + return false; +// return [[EAGLView sharedEGLView] respondsToSelector:@selector(setContentScaleFactor:)] +// && [[NSScreen mainScreen] scale] != 1.0; +} + +void CCEGLView::setContentScaleFactor(float contentScaleFactor) +{ +// NSView * view = [EAGLView sharedEGLView]; +// view.contentScaleFactor = contentScaleFactor; +// [view setNeedsLayout]; +} + +void CCEGLView::end() +{ + [CCDirectorCaller destroy]; + + // destroy EAGLView + [[EAGLView sharedEGLView] removeFromSuperview]; + + _exit(0); +} + +void CCEGLView::swapBuffers() +{ + [[EAGLView sharedEGLView] swapBuffers]; +} + +CCSize CCEGLView::getFrameSize() +{ + assert(false); + return CCSizeMake(0, 0); +} + +void CCEGLView::setIMEKeyboardState(bool bOpen) +{ + if (bOpen) + { + [[EAGLView sharedEGLView] becomeFirstResponder]; + } + else + { + [[EAGLView sharedEGLView] resignFirstResponder]; + } +} + +void CCEGLView::setMultiTouchMask(bool mask) +{ + //EAGLView *glView = [EAGLView sharedEGLView]; + //glView.multipleTouchEnabled = mask ? YES : NO; +} + +CCEGLView& CCEGLView::sharedOpenGLView() +{ + static CCEGLView instance; + return instance; +} + +} // end of namespace cocos2d; diff --git a/cocos2dx/platform/mac/CCEventDispatcher.h b/cocos2dx/platform/mac/CCEventDispatcher.h new file mode 100755 index 0000000000..8a58f22da8 --- /dev/null +++ b/cocos2dx/platform/mac/CCEventDispatcher.h @@ -0,0 +1,278 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 2010 Ricardo Quesada + * Copyright (c) 2011 Zynga Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// Only compile this code on Mac. These files should not be included on your iOS project. +// But in case they are included, it won't be compiled. +#import +#ifndef __CC_EVENT_DISPATCHER_H__ +#define __CC_EVENT_DISPATCHER_H__ + +#import + +#import "EAGLView.h" +//#import "../../Support/uthash.h" // hack: uthash needs to be imported before utlist to prevent warning +//#import "../../Support/utlist.h" +#import "ccConfig.h" + +//NS_CC_BEGIN; +#pragma mark - +#pragma mark CCMouseEventDelegate + +/** CCMouseEventDelegate protocol. + Implement it in your node to receive any of mouse events + */ +@protocol CCMouseEventDelegate +@optional + +// +// left +// +/** called when the "mouseDown" event is received. + Return YES to avoid propagating the event to other delegates. + */ +-(BOOL) ccMouseDown:(NSEvent*)event; + +/** called when the "mouseDragged" event is received. + Return YES to avoid propagating the event to other delegates. + */ +-(BOOL) ccMouseDragged:(NSEvent*)event; + +/** called when the "mouseMoved" event is received. + Return YES to avoid propagating the event to other delegates. + By default, "mouseMoved" is disabled. To enable it, send the "setAcceptsMouseMovedEvents:YES" message to the main window. + */ +-(BOOL) ccMouseMoved:(NSEvent*)event; + +/** called when the "mouseUp" event is received. + Return YES to avoid propagating the event to other delegates. + */ +-(BOOL) ccMouseUp:(NSEvent*)event; + + +// +// right +// + +/** called when the "rightMouseDown" event is received. + Return YES to avoid propagating the event to other delegates. + */ +-(BOOL) ccRightMouseDown:(NSEvent*)event; + +/** called when the "rightMouseDragged" event is received. + Return YES to avoid propagating the event to other delegates. + */ +-(BOOL) ccRightMouseDragged:(NSEvent*)event; + +/** called when the "rightMouseUp" event is received. + Return YES to avoid propagating the event to other delegates. + */ +-(BOOL) ccRightMouseUp:(NSEvent*)event; + +// +// other +// + +/** called when the "otherMouseDown" event is received. + Return YES to avoid propagating the event to other delegates. + */ +-(BOOL) ccOtherMouseDown:(NSEvent*)event; + +/** called when the "otherMouseDragged" event is received. + Return YES to avoid propagating the event to other delegates. + */ +-(BOOL) ccOtherMouseDragged:(NSEvent*)event; + +/** called when the "otherMouseUp" event is received. + Return YES to avoid propagating the event to other delegates. + */ +-(BOOL) ccOtherMouseUp:(NSEvent*)event; + +// +// scroll wheel +// + +/** called when the "scrollWheel" event is received. + Return YES to avoid propagating the event to other delegates. + */ +- (BOOL)ccScrollWheel:(NSEvent *)theEvent; + + +// +// enter / exit +// + +/** called when the "mouseEntered" event is received. + Return YES to avoid propagating the event to other delegates. + */ +- (void)ccMouseEntered:(NSEvent *)theEvent; + +/** called when the "mouseExited" event is received. + Return YES to avoid propagating the event to other delegates. + */ +- (void)ccMouseExited:(NSEvent *)theEvent; + +@end + +#pragma mark - +#pragma mark CCKeyboardEventDelegate + +/** CCKeyboardEventDelegate protocol. + Implement it in your node to receive any of keyboard events + */ +@protocol CCKeyboardEventDelegate +@optional +/** called when the "keyUp" event is received. + Return YES to avoid propagating the event to other delegates. + */ +-(BOOL) ccKeyUp:(NSEvent*)event; + +/** called when the "keyDown" event is received. + Return YES to avoid propagating the event to other delegates. + */ +-(BOOL) ccKeyDown:(NSEvent*)event; +/** called when the "flagsChanged" event is received. + Return YES to avoid propagating the event to other delegates. + */ +-(BOOL) ccFlagsChanged:(NSEvent*)event; +@end + +#pragma mark - +#pragma mark CCTouchEventDelegate + +/** CCTouchEventDelegate protocol. + Implement it in your node to receive any of touch events + */ +@protocol CCTouchEventDelegate +@optional +/** called when the "touchesBegan" event is received. + Return YES to avoid propagating the event to other delegates. + */ +- (BOOL)ccTouchesBeganWithEvent:(NSEvent *)event; + +/** called when the "touchesMoved" event is received. + Return YES to avoid propagating the event to other delegates. + */ +- (BOOL)ccTouchesMovedWithEvent:(NSEvent *)event; + +/** called when the "touchesEnded" event is received. + Return YES to avoid propagating the event to other delegates. + */ +- (BOOL)ccTouchesEndedWithEvent:(NSEvent *)event; + +/** called when the "touchesCancelled" event is received. + Return YES to avoid propagating the event to other delegates. + */ +- (BOOL)ccTouchesCancelledWithEvent:(NSEvent *)event; + +@end + + +#pragma mark - +#pragma mark CCEventDispatcher + +struct _listEntry; + +/** CCEventDispatcher + + This is object is responsible for dispatching the events: + - Mouse events + - Keyboard events + - Touch events + + Only available on Mac + */ +@interface CCEventDispatcher : NSObject { + + BOOL dispatchEvents_; + + struct _listEntry *keyboardDelegates_; + struct _listEntry *mouseDelegates_; + struct _listEntry *touchDelegates_; +} + +@property (nonatomic, readwrite) BOOL dispatchEvents; + + +/** CCEventDispatcher singleton */ ++(CCEventDispatcher*) sharedDispatcher; + +#pragma mark CCEventDispatcher - Mouse + +/** Adds a mouse delegate to the dispatcher's list. + Delegates with a lower priority value will be called before higher priority values. + All the events will be propgated to all the delegates, unless the one delegate returns YES. + + IMPORTANT: The delegate will be retained. + */ +-(void) addMouseDelegate:(id) delegate priority:(NSInteger)priority; + +/** removes a mouse delegate */ +-(void) removeMouseDelegate:(id) delegate; + +/** Removes all mouse delegates, releasing all the delegates */ +-(void) removeAllMouseDelegates; + +#pragma mark CCEventDispatcher - Keyboard + +/** Adds a Keyboard delegate to the dispatcher's list. + Delegates with a lower priority value will be called before higher priority values. + All the events will be propgated to all the delegates, unless the one delegate returns YES. + + IMPORTANT: The delegate will be retained. + */ +-(void) addKeyboardDelegate:(id) delegate priority:(NSInteger)priority; + +/** removes a mouse delegate */ +-(void) removeKeyboardDelegate:(id) delegate; + +/** Removes all mouse delegates, releasing all the delegates */ +-(void) removeAllKeyboardDelegates; + +#pragma mark CCEventDispatcher - Touches + +/** Adds a Touch delegate to the dispatcher's list. + Delegates with a lower priority value will be called before higher priority values. + All the events will be propgated to all the delegates, unless the one delegate returns YES. + + IMPORTANT: The delegate will be retained. + */ +- (void)addTouchDelegate:(id)delegate priority:(NSInteger)priority; + +/** Removes a touch delegate */ +- (void)removeTouchDelegate:(id) delegate; + +/** Removes all touch delegates, releasing all the delegates */ +- (void)removeAllTouchDelegates; + +#pragma mark CCEventDispatcher - Dispatch Events + +#if CC_DIRECTOR_MAC_USE_DISPLAY_LINK_THREAD +-(void) dispatchQueuedEvents; +#endif + +@end + +//NS_CC_END; +#endif // __CC_EVENT_DISPATCHER_H__ diff --git a/cocos2dx/platform/mac/CCEventDispatcher.mm b/cocos2dx/platform/mac/CCEventDispatcher.mm new file mode 100755 index 0000000000..0cb2760862 --- /dev/null +++ b/cocos2dx/platform/mac/CCEventDispatcher.mm @@ -0,0 +1,643 @@ +/* + * cocos2d for iPhone: http://www.cocos2d-iphone.org + * + * Copyright (c) 2010 Ricardo Quesada + * Copyright (c) 2011 Zynga Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +// Only compile this code on Mac. These files should not be included on your iOS project. +// But in case they are included, it won't be compiled. +#import + +#import "CCEventDispatcher.h" +#import "ccConfig.h" +#include "support/data_support/utlist.h" + +//NS_CC_BEGIN; +static CCEventDispatcher *sharedDispatcher = nil; + +enum { + // mouse + kCCImplementsMouseDown = 1 << 0, + kCCImplementsMouseMoved = 1 << 1, + kCCImplementsMouseDragged = 1 << 2, + kCCImplementsMouseUp = 1 << 3, + kCCImplementsRightMouseDown = 1 << 4, + kCCImplementsRightMouseDragged = 1 << 5, + kCCImplementsRightMouseUp = 1 << 6, + kCCImplementsOtherMouseDown = 1 << 7, + kCCImplementsOtherMouseDragged = 1 << 8, + kCCImplementsOtherMouseUp = 1 << 9, + kCCImplementsScrollWheel = 1 << 10, + kCCImplementsMouseEntered = 1 << 11, + kCCImplementsMouseExited = 1 << 12, + + kCCImplementsTouchesBegan = 1 << 13, + kCCImplementsTouchesMoved = 1 << 14, + kCCImplementsTouchesEnded = 1 << 15, + kCCImplementsTouchesCancelled = 1 << 16, + + // keyboard + kCCImplementsKeyUp = 1 << 0, + kCCImplementsKeyDown = 1 << 1, + kCCImplementsFlagsChanged = 1 << 2, +}; + + +typedef struct _listEntry +{ + struct _listEntry *prev, *next; + id delegate; + NSInteger priority; + NSUInteger flags; +} tListEntry; + + +#if CC_DIRECTOR_MAC_USE_DISPLAY_LINK_THREAD + +#define QUEUE_EVENT_MAX 128 +struct _eventQueue { + SEL selector; + NSEvent *event; +}; + +static struct _eventQueue eventQueue[QUEUE_EVENT_MAX]; +static int eventQueueCount; + +#endif // CC_DIRECTOR_MAC_USE_DISPLAY_LINK_THREAD + + +@implementation CCEventDispatcher + +@synthesize dispatchEvents=dispatchEvents_; + + ++(CCEventDispatcher*) sharedDispatcher +{ + @synchronized(self) { + if (sharedDispatcher == nil) + sharedDispatcher = [[self alloc] init]; // assignment not done here + } + return sharedDispatcher; +} + ++(id) allocWithZone:(NSZone *)zone +{ + @synchronized(self) { + NSAssert(sharedDispatcher == nil, @"Attempted to allocate a second instance of a singleton."); + return [super allocWithZone:zone]; + } + return nil; // on subsequent allocation attempts return nil +} + +-(id) init +{ + if( (self = [super init]) ) + { + // events enabled by default + dispatchEvents_ = YES; + + // delegates + keyboardDelegates_ = NULL; + mouseDelegates_ = NULL; + touchDelegates_ = NULL; + +#if CC_DIRECTOR_MAC_USE_DISPLAY_LINK_THREAD + eventQueueCount = 0; +#endif + } + + return self; +} + +- (void) dealloc +{ + [super dealloc]; +} + +#pragma mark CCEventDispatcher - add / remove delegates + +-(void) addDelegate:(id)delegate priority:(NSInteger)priority flags:(NSUInteger)flags list:(tListEntry**)list +{ + tListEntry *listElement = (tListEntry *)malloc( sizeof(*listElement) ); + + listElement->delegate = [delegate retain]; + listElement->priority = priority; + listElement->flags = flags; + listElement->next = listElement->prev = NULL; + + // empty list ? + if( ! *list ) { + DL_APPEND( *list, listElement ); + + } else { + BOOL added = NO; + + for( tListEntry *elem = *list; elem ; elem = elem->next ) { + if( priority < elem->priority ) { + + if( elem == *list ) + DL_PREPEND(*list, listElement); + else { + listElement->next = elem; + listElement->prev = elem->prev; + + elem->prev->next = listElement; + elem->prev = listElement; + } + + added = YES; + break; + } + } + + // Not added? priority has the higher value. Append it. + if( !added ) + DL_APPEND(*list, listElement); + } +} + +-(void) removeDelegate:(id)delegate fromList:(tListEntry**)list +{ + tListEntry *entry, *tmp; + + // updates with priority < 0 + DL_FOREACH_SAFE( *list, entry, tmp ) { + if( entry->delegate == delegate ) { + DL_DELETE( *list, entry ); + [delegate release]; + free(entry); + break; + } + } +} + +-(void) removeAllDelegatesFromList:(tListEntry**)list +{ + tListEntry *entry, *tmp; + + DL_FOREACH_SAFE( *list, entry, tmp ) { + DL_DELETE( *list, entry ); + free(entry); + } +} + + +-(void) addMouseDelegate:(id) delegate priority:(NSInteger)priority +{ + NSUInteger flags = 0; + + flags |= ( [delegate respondsToSelector:@selector(ccMouseDown:)] ? kCCImplementsMouseDown : 0 ); + flags |= ( [delegate respondsToSelector:@selector(ccMouseDragged:)] ? kCCImplementsMouseDragged : 0 ); + flags |= ( [delegate respondsToSelector:@selector(ccMouseMoved:)] ? kCCImplementsMouseMoved : 0 ); + flags |= ( [delegate respondsToSelector:@selector(ccMouseUp:)] ? kCCImplementsMouseUp : 0 ); + + flags |= ( [delegate respondsToSelector:@selector(ccRightMouseDown:)] ? kCCImplementsRightMouseDown : 0 ); + flags |= ( [delegate respondsToSelector:@selector(ccRightMouseDragged:)] ? kCCImplementsRightMouseDragged : 0 ); + flags |= ( [delegate respondsToSelector:@selector(ccRightMouseUp:)] ? kCCImplementsRightMouseUp : 0 ); + + flags |= ( [delegate respondsToSelector:@selector(ccOtherMouseDown:)] ? kCCImplementsOtherMouseDown : 0 ); + flags |= ( [delegate respondsToSelector:@selector(ccOtherMouseDragged:)] ? kCCImplementsOtherMouseDragged : 0 ); + flags |= ( [delegate respondsToSelector:@selector(ccOtherMouseUp:)] ? kCCImplementsOtherMouseUp : 0 ); + + flags |= ( [delegate respondsToSelector:@selector(ccMouseEntered:)] ? kCCImplementsMouseEntered : 0 ); + flags |= ( [delegate respondsToSelector:@selector(ccMouseExited:)] ? kCCImplementsMouseExited : 0 ); + + flags |= ( [delegate respondsToSelector:@selector(ccScrollWheel:)] ? kCCImplementsScrollWheel : 0 ); + + [self addDelegate:delegate priority:priority flags:flags list:&mouseDelegates_]; +} + +-(void) removeMouseDelegate:(id) delegate +{ + [self removeDelegate:delegate fromList:&mouseDelegates_]; +} + +-(void) removeAllMouseDelegates +{ + [self removeAllDelegatesFromList:&mouseDelegates_]; +} + +-(void) addKeyboardDelegate:(id) delegate priority:(NSInteger)priority +{ + NSUInteger flags = 0; + + flags |= ( [delegate respondsToSelector:@selector(ccKeyUp:)] ? kCCImplementsKeyUp : 0 ); + flags |= ( [delegate respondsToSelector:@selector(ccKeyDown:)] ? kCCImplementsKeyDown : 0 ); + flags |= ( [delegate respondsToSelector:@selector(ccFlagsChanged:)] ? kCCImplementsFlagsChanged : 0 ); + + [self addDelegate:delegate priority:priority flags:flags list:&keyboardDelegates_]; +} + +-(void) removeKeyboardDelegate:(id) delegate +{ + [self removeDelegate:delegate fromList:&keyboardDelegates_]; +} + +-(void) removeAllKeyboardDelegates +{ + [self removeAllDelegatesFromList:&keyboardDelegates_]; +} + +-(void) addTouchDelegate:(id) delegate priority:(NSInteger)priority +{ + NSUInteger flags = 0; + + flags |= ( [delegate respondsToSelector:@selector(ccTouchesBeganWithEvent:)] ? kCCImplementsTouchesBegan : 0 ); + flags |= ( [delegate respondsToSelector:@selector(ccTouchesMovedWithEvent:)] ? kCCImplementsTouchesMoved : 0 ); + flags |= ( [delegate respondsToSelector:@selector(ccTouchesEndedWithEvent:)] ? kCCImplementsTouchesEnded : 0 ); + flags |= ( [delegate respondsToSelector:@selector(ccTouchesCancelledWithEvent:)] ? kCCImplementsTouchesCancelled : 0 ); + + [self addDelegate:delegate priority:priority flags:flags list:&touchDelegates_]; +} + +-(void) removeTouchDelegate:(id) delegate +{ + [self removeDelegate:delegate fromList:&touchDelegates_]; +} + +-(void) removeAllTouchDelegates +{ + [self removeAllDelegatesFromList:&touchDelegates_]; +} + + +#pragma mark CCEventDispatcher - Mouse events +// +// Mouse events +// + +// +// Left +// +- (void)mouseDown:(NSEvent *)event +{ + if( dispatchEvents_ ) { + tListEntry *entry, *tmp; + + DL_FOREACH_SAFE( mouseDelegates_, entry, tmp ) { + if ( entry->flags & kCCImplementsMouseDown ) { + void *swallows = [entry->delegate performSelector:@selector(ccMouseDown:) withObject:event]; + if( swallows ) + break; + } + } + } +} + +- (void)mouseMoved:(NSEvent *)event +{ + if( dispatchEvents_ ) { + tListEntry *entry, *tmp; + + DL_FOREACH_SAFE( mouseDelegates_, entry, tmp ) { + if ( entry->flags & kCCImplementsMouseMoved ) { + void *swallows = [entry->delegate performSelector:@selector(ccMouseMoved:) withObject:event]; + if( swallows ) + break; + } + } + } +} + +- (void)mouseDragged:(NSEvent *)event +{ + if( dispatchEvents_ ) { + tListEntry *entry, *tmp; + + DL_FOREACH_SAFE( mouseDelegates_, entry, tmp ) { + if ( entry->flags & kCCImplementsMouseDragged ) { + void *swallows = [entry->delegate performSelector:@selector(ccMouseDragged:) withObject:event]; + if( swallows ) + break; + } + } + } +} + +- (void)mouseUp:(NSEvent *)event +{ + if( dispatchEvents_ ) { + tListEntry *entry, *tmp; + + DL_FOREACH_SAFE( mouseDelegates_, entry, tmp ) { + if ( entry->flags & kCCImplementsMouseUp ) { + void *swallows = [entry->delegate performSelector:@selector(ccMouseUp:) withObject:event]; + if( swallows ) + break; + } + } + } +} + +// +// Mouse Right +// +- (void)rightMouseDown:(NSEvent *)event +{ + if( dispatchEvents_ ) { + tListEntry *entry, *tmp; + + DL_FOREACH_SAFE( mouseDelegates_, entry, tmp ) { + if ( entry->flags & kCCImplementsRightMouseDown ) { + void *swallows = [entry->delegate performSelector:@selector(ccRightMouseDown:) withObject:event]; + if( swallows ) + break; + } + } + } +} + +- (void)rightMouseDragged:(NSEvent *)event +{ + if( dispatchEvents_ ) { + tListEntry *entry, *tmp; + + DL_FOREACH_SAFE( mouseDelegates_, entry, tmp ) { + if ( entry->flags & kCCImplementsRightMouseDragged ) { + void *swallows = [entry->delegate performSelector:@selector(ccRightMouseDragged:) withObject:event]; + if( swallows ) + break; + } + } + } +} + +- (void)rightMouseUp:(NSEvent *)event +{ + if( dispatchEvents_ ) { + tListEntry *entry, *tmp; + + DL_FOREACH_SAFE( mouseDelegates_, entry, tmp ) { + if ( entry->flags & kCCImplementsRightMouseUp ) { + void *swallows = [entry->delegate performSelector:@selector(ccRightMouseUp:) withObject:event]; + if( swallows ) + break; + } + } + } +} + +// +// Mouse Other +// +- (void)otherMouseDown:(NSEvent *)event +{ + if( dispatchEvents_ ) { + tListEntry *entry, *tmp; + + DL_FOREACH_SAFE( mouseDelegates_, entry, tmp ) { + if ( entry->flags & kCCImplementsOtherMouseDown ) { + void *swallows = [entry->delegate performSelector:@selector(ccOtherMouseDown:) withObject:event]; + if( swallows ) + break; + } + } + } +} + +- (void)otherMouseDragged:(NSEvent *)event +{ + if( dispatchEvents_ ) { + tListEntry *entry, *tmp; + + DL_FOREACH_SAFE( mouseDelegates_, entry, tmp ) { + if ( entry->flags & kCCImplementsOtherMouseDragged ) { + void *swallows = [entry->delegate performSelector:@selector(ccOtherMouseDragged:) withObject:event]; + if( swallows ) + break; + } + } + } +} + +- (void)otherMouseUp:(NSEvent *)event +{ + if( dispatchEvents_ ) { + tListEntry *entry, *tmp; + + DL_FOREACH_SAFE( mouseDelegates_, entry, tmp ) { + if ( entry->flags & kCCImplementsOtherMouseUp ) { + void *swallows = [entry->delegate performSelector:@selector(ccOtherMouseUp:) withObject:event]; + if( swallows ) + break; + } + } + } +} + +// +// Scroll Wheel +// +- (void)scrollWheel:(NSEvent *)event +{ + if( dispatchEvents_ ) { + tListEntry *entry, *tmp; + + DL_FOREACH_SAFE( mouseDelegates_, entry, tmp ) { + if ( entry->flags & kCCImplementsScrollWheel ) { + void *swallows = [entry->delegate performSelector:@selector(ccScrollWheel:) withObject:event]; + if( swallows ) + break; + } + } + } +} + +// +// Mouse enter / exit +- (void)mouseExited:(NSEvent *)event +{ + if( dispatchEvents_ ) { + tListEntry *entry, *tmp; + + DL_FOREACH_SAFE( mouseDelegates_, entry, tmp ) { + if ( entry->flags & kCCImplementsMouseEntered ) { + void *swallows = [entry->delegate performSelector:@selector(ccMouseEntered:) withObject:event]; + if( swallows ) + break; + } + } + } +} + +- (void)mouseEntered:(NSEvent *)event +{ + if( dispatchEvents_ ) { + tListEntry *entry, *tmp; + + DL_FOREACH_SAFE( mouseDelegates_, entry, tmp ) { + if ( entry->flags & kCCImplementsMouseExited) { + void *swallows = [entry->delegate performSelector:@selector(ccMouseExited:) withObject:event]; + if( swallows ) + break; + } + } + } +} + + +#pragma mark CCEventDispatcher - Keyboard events + +// Keyboard events +- (void)keyDown:(NSEvent *)event +{ + if( dispatchEvents_ ) { + tListEntry *entry, *tmp; + + DL_FOREACH_SAFE( keyboardDelegates_, entry, tmp ) { + if ( entry->flags & kCCImplementsKeyDown ) { + void *swallows = [entry->delegate performSelector:@selector(ccKeyDown:) withObject:event]; + if( swallows ) + break; + } + } + } +} + +- (void)keyUp:(NSEvent *)event +{ + if( dispatchEvents_ ) { + tListEntry *entry, *tmp; + + DL_FOREACH_SAFE( keyboardDelegates_, entry, tmp ) { + if ( entry->flags & kCCImplementsKeyUp ) { + void *swallows = [entry->delegate performSelector:@selector(ccKeyUp:) withObject:event]; + if( swallows ) + break; + } + } + } +} + +- (void)flagsChanged:(NSEvent *)event +{ + if( dispatchEvents_ ) { + tListEntry *entry, *tmp; + + DL_FOREACH_SAFE( keyboardDelegates_, entry, tmp ) { + if ( entry->flags & kCCImplementsFlagsChanged ) { + void *swallows = [entry->delegate performSelector:@selector(ccFlagsChanged:) withObject:event]; + if( swallows ) + break; + } + } + } +} + + +#pragma mark CCEventDispatcher - Touch events + +- (void)touchesBeganWithEvent:(NSEvent *)event +{ + if( dispatchEvents_ ) { + tListEntry *entry, *tmp; + + DL_FOREACH_SAFE( touchDelegates_, entry, tmp ) { + if ( entry->flags & kCCImplementsTouchesBegan) { + void *swallows = [entry->delegate performSelector:@selector(ccTouchesBeganWithEvent:) withObject:event]; + if( swallows ) + break; + } + } + } +} + +- (void)touchesMovedWithEvent:(NSEvent *)event +{ + if( dispatchEvents_ ) { + tListEntry *entry, *tmp; + + DL_FOREACH_SAFE( touchDelegates_, entry, tmp ) { + if ( entry->flags & kCCImplementsTouchesMoved) { + void *swallows = [entry->delegate performSelector:@selector(ccTouchesMovedWithEvent:) withObject:event]; + if( swallows ) + break; + } + } + } +} + +- (void)touchesEndedWithEvent:(NSEvent *)event +{ + if( dispatchEvents_ ) { + tListEntry *entry, *tmp; + + DL_FOREACH_SAFE( touchDelegates_, entry, tmp ) { + if ( entry->flags & kCCImplementsTouchesEnded) { + void *swallows = [entry->delegate performSelector:@selector(ccTouchesEndedWithEvent:) withObject:event]; + if( swallows ) + break; + } + } + } +} + +- (void)touchesCancelledWithEvent:(NSEvent *)event +{ + if( dispatchEvents_ ) { + tListEntry *entry, *tmp; + + DL_FOREACH_SAFE( touchDelegates_, entry, tmp ) { + if ( entry->flags & kCCImplementsTouchesCancelled) { + void *swallows = [entry->delegate performSelector:@selector(ccTouchesCancelledWithEvent:) withObject:event]; + if( swallows ) + break; + } + } + } +} + + +#pragma mark CCEventDispatcher - queue events + +#if CC_DIRECTOR_MAC_USE_DISPLAY_LINK_THREAD +-(void) queueEvent:(NSEvent*)event selector:(SEL)selector +{ + NSAssert( eventQueueCount < QUEUE_EVENT_MAX, @"CCEventDispatcher: recompile. Increment QUEUE_EVENT_MAX value"); + + @synchronized (self) { + eventQueue[eventQueueCount].selector = selector; + eventQueue[eventQueueCount].event = [event copy]; + + eventQueueCount++; + } +} + +-(void) dispatchQueuedEvents +{ + @synchronized (self) { + for( int i=0; i < eventQueueCount; i++ ) { + SEL sel = eventQueue[i].selector; + NSEvent *event = eventQueue[i].event; + + [self performSelector:sel withObject:event]; + + [event release]; + } + + eventQueueCount = 0; + } +} +#endif // CC_DIRECTOR_MAC_USE_DISPLAY_LINK_THREAD + +//NS_CC_END; +@end diff --git a/cocos2dx/platform/mac/CCFileUtils.mm b/cocos2dx/platform/mac/CCFileUtils.mm new file mode 100755 index 0000000000..5778dad038 --- /dev/null +++ b/cocos2dx/platform/mac/CCFileUtils.mm @@ -0,0 +1,476 @@ +/**************************************************************************** +Copyright (c) 2010 cocos2d-x.org + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ +#import + +#include +#include +#include +#include +#include +#include "CCString.h" +#include "CCFileUtils.h" +#include "CCDirector.h" +#include "CCSAXParser.h" +#include "CCImage.h" +#include "CCDictionary.h" +#include "CCArray.h" + +#define MAX_PATH 260 + +using namespace cocos2d; + +static void static_addValueToCCDict(id key, id value, CCDictionary* pDict); +static void static_addItemToCCArray(id item, CCArray *pArray); + +static const char *static_ccRemoveHDSuffixFromFile( const char *pszPath) +{ +#if CC_IS_RETINA_DISPLAY_SUPPORTED + + if(cocos2d::CC_CONTENT_SCALE_FACTOR() == 2 ) { + NSString *path = [NSString stringWithUTF8String: pszPath]; + NSString *name = [path lastPathComponent]; + NSString *suffix = [NSString stringWithUTF8String: CC_RETINA_DISPLAY_FILENAME_SUFFIX]; + + // check if path already has the suffix. + if( [name rangeOfString: suffix].location != NSNotFound ) { + + CCLOG("cocos2d: Filename(%@) contains %@ suffix. Removing it. See cocos2d issue #1040", path, CC_RETINA_DISPLAY_FILENAME_SUFFIX); + + NSString *newLastname = [name stringByReplacingOccurrencesOfString: suffix withString:@""]; + + NSString *pathWithoutLastname = [path stringByDeletingLastPathComponent]; + return [[pathWithoutLastname stringByAppendingPathComponent:newLastname] UTF8String]; + } + } + +#endif // CC_IS_RETINA_DISPLAY_SUPPORTED + + return pszPath; +} + +static NSString* getDoubleResolutionImage(NSString* path) +{ +#if CC_IS_RETINA_DISPLAY_SUPPORTED + + if( cocos2d::CC_CONTENT_SCALE_FACTOR() == 2 ) + { + + NSString *pathWithoutExtension = [path stringByDeletingPathExtension]; + NSString *name = [pathWithoutExtension lastPathComponent]; + NSString *suffix = [NSString stringWithUTF8String: CC_RETINA_DISPLAY_FILENAME_SUFFIX]; + + // check if path already has the suffix. + if( [name rangeOfString: suffix].location != NSNotFound ) { + + CCLOG("cocos2d: WARNING Filename(%@) already has the suffix %@. Using it.", name, CC_RETINA_DISPLAY_FILENAME_SUFFIX); + return path; + } + + + NSString *extension = [path pathExtension]; + + if( [extension isEqualToString:@"ccz"] || [extension isEqualToString:@"gz"] ) + { + // All ccz / gz files should be in the format filename.xxx.ccz + // so we need to pull off the .xxx part of the extension as well + extension = [NSString stringWithFormat:@"%@.%@", [pathWithoutExtension pathExtension], extension]; + pathWithoutExtension = [pathWithoutExtension stringByDeletingPathExtension]; + } + + + NSString *retinaName = [pathWithoutExtension stringByAppendingString: suffix]; + retinaName = [retinaName stringByAppendingPathExtension:extension]; + + NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease]; + if( [fileManager fileExistsAtPath:retinaName] ) + return retinaName; + + CCLOG("cocos2d: CCFileUtils: Warning HD file not found: %@", [retinaName lastPathComponent] ); + } + +#endif // CC_IS_RETINA_DISPLAY_SUPPORTED + + return path; +} + +static const char* static_fullPathFromRelativePath(const char *pszRelativePath) +{ + + // NSAssert(pszRelativePath != nil, @"CCFileUtils: Invalid path"); + + // do not convert an absolute path (starting with '/') + NSString *relPath = [NSString stringWithUTF8String: pszRelativePath]; + NSString *fullpath = nil; + + // only if it is not an absolute path + if( ! [relPath isAbsolutePath] ) + { + NSString *file = [relPath lastPathComponent]; + NSString *imageDirectory = [relPath stringByDeletingLastPathComponent]; + + fullpath = [[NSBundle mainBundle] pathForResource:file + ofType:nil + inDirectory:imageDirectory]; + + if (CC_CONTENT_SCALE_FACTOR() > 1.0f && + fullpath == nil && [file rangeOfString:@"@2x" options:NSBackwardsSearch].length == 0 + && ([file rangeOfString:@".png" options:NSBackwardsSearch | NSCaseInsensitiveSearch].length != 0 + || [file rangeOfString:@".jpg" options:NSBackwardsSearch | NSCaseInsensitiveSearch].length != 0) + ) + { + NSMutableString *newFile = [NSMutableString stringWithString:file]; + NSRange range = [newFile rangeOfString:@"." options:NSBackwardsSearch]; + if (range.length > 0 ) + { + [newFile insertString:@"@2x" atIndex:range.location]; + fullpath = [[NSBundle mainBundle] pathForResource:newFile + ofType:nil + inDirectory:imageDirectory]; + } + + if (fullpath) + { + imageDirectory = [fullpath stringByDeletingLastPathComponent]; + fullpath = [imageDirectory stringByAppendingPathComponent:file]; + } + else + { + CCLog("miss file %s", [[imageDirectory stringByAppendingPathComponent:newFile] UTF8String]); + } + + } + } + + if (fullpath == nil) + fullpath = relPath; + + fullpath = getDoubleResolutionImage(fullpath); + + return [fullpath UTF8String]; +} + +static void static_addItemToCCArray(id item, CCArray *pArray) +{ + // add string value into array + if ([item isKindOfClass:[NSString class]]) { + CCString* pValue = new CCString([item UTF8String]); + + pArray->addObject(pValue); + pValue->release(); + return; + } + + // add number value into array(such as int, float, bool and so on) + if ([item isKindOfClass:[NSNumber class]]) { + NSString* pStr = [item stringValue]; + CCString* pValue = new CCString([pStr UTF8String]); + + pArray->addObject(pValue); + pValue->release(); + return; + } + + // add dictionary value into array + if ([item isKindOfClass:[NSDictionary class]]) { + CCDictionary* pDictItem = new CCDictionary(); + for (id subKey in [item allKeys]) { + id subValue = [item objectForKey:subKey]; + static_addValueToCCDict(subKey, subValue, pDictItem); + } + pArray->addObject(pDictItem); + pDictItem->release(); + return; + } + + // add array value into array + if ([item isKindOfClass:[NSArray class]]) { + CCArray *pArrayItem = new CCArray(); + for (id subItem in item) { + static_addItemToCCArray(subItem, pArrayItem); + } + pArray->addObject(pArrayItem); + pArrayItem->release(); + return; + } +} + +static void static_addValueToCCDict(id key, id value, CCDictionary* pDict) +{ + // the key must be a string + CCAssert([key isKindOfClass:[NSString class]], "The key should be a string!"); + std::string pKey = [key UTF8String]; + + // the value is a new dictionary + if ([value isKindOfClass:[NSDictionary class]]) { + CCDictionary* pSubDict = new CCDictionary(); + for (id subKey in [value allKeys]) { + id subValue = [value objectForKey:subKey]; + static_addValueToCCDict(subKey, subValue, pSubDict); + } + pDict->setObject(pSubDict, pKey); + pSubDict->release(); + return; + } + + // the value is a string + if ([value isKindOfClass:[NSString class]]) { + CCString* pValue = new CCString([value UTF8String]); + + pDict->setObject(pValue, pKey); + pValue->release(); + return; + } + + // the value is a number + if ([value isKindOfClass:[NSNumber class]]) { + NSString* pStr = [value stringValue]; + CCString* pValue = new CCString([pStr UTF8String]); + + pDict->setObject(pValue, pKey); + pValue->release(); + return; + } + + // the value is a array + if ([value isKindOfClass:[NSArray class]]) { + CCArray *pArray = new CCArray(); + for (id item in value) { + static_addItemToCCArray(item, pArray); + } + pDict->setObject(pArray, pKey); + pArray->release(); + return; + } +} + +namespace cocos2d { + + static CCFileUtils* s_pFileUtils = NULL; + + CCDictionary* ccFileUtils_dictionaryWithContentsOfFileThreadSafe(const char *pFileName); + CCArray* ccFileUtils_arrayWithContentsOfFileThreadSafe(const char* pFileName); + + CCFileUtils* CCFileUtils::sharedFileUtils() + { + if (s_pFileUtils == NULL) + { + s_pFileUtils = new CCFileUtils(); + } + return s_pFileUtils; + } + + void CCFileUtils::purgeFileUtils() + { + if (s_pFileUtils != NULL) + { + s_pFileUtils->purgeCachedEntries(); + } + + CC_SAFE_DELETE(s_pFileUtils); + } + + void CCFileUtils::purgeCachedEntries() + { + + } + + // record the resource path + static char s_pszResourcePath[MAX_PATH] = {0}; + + void CCFileUtils::setResourcePath(const char *pszResourcePath) + { + // NSAssert(pszResourcePath != NULL, "[FileUtils setResourcePath] -- wrong resource path"); + // NSAssert(strlen(pszResourcePath) <= MAX_PATH, "[FileUtils setResourcePath] -- resource path too long"); + + strcpy(s_pszResourcePath, pszResourcePath); + } +/* + const char* CCFileUtils::getResourcePath() + { + return s_pszResourcePath; + } +*/ + + //int CCFileUtils::ccLoadFileIntoMemory(const char *filename, unsigned char **out) + unsigned char* CCFileUtils::getFileData(const char *pszFileName, const char *pszMode, unsigned long *pSize) + { + unsigned char* out = NULL; + + int size = 0; + FILE *f = fopen(pszFileName, pszMode); + if( !f ) { + *pSize = 0; + return NULL; + } + + fseek(f, 0, SEEK_END); + size = ftell(f); + fseek(f, 0, SEEK_SET); + + out = (unsigned char*)malloc(size); + int read = fread(out, 1, size, f); + if( read != size ) { + free(out); + *pSize = 0; + return NULL; + } + + fclose(f); + + *pSize = size; + return out; + } + + std::string& CCFileUtils::removeSuffixFromFile(std::string& path ) + { + path = static_ccRemoveHDSuffixFromFile(path.c_str()); + return path; + } + + const char* CCFileUtils::fullPathFromRelativePath(const char *pszRelativePath) + { + return static_fullPathFromRelativePath(pszRelativePath); + } + + const char *CCFileUtils::fullPathFromRelativeFile(const char *pszFilename, const char *pszRelativeFile) + { + std::string relativeFile = fullPathFromRelativePath(pszRelativeFile); + CCString *pRet = new CCString(); + pRet->autorelease(); + pRet->m_sString = relativeFile.substr(0, relativeFile.rfind('/')+1); + pRet->m_sString += pszFilename; + return pRet->m_sString.c_str(); + } + + CCDictionary* ccFileUtils_dictionaryWithContentsOfFileThreadSafe(const char *pFileName) + { + const char* pszFullPath = CCFileUtils::sharedFileUtils()->fullPathFromRelativePath(pFileName); + NSString* pPath = [NSString stringWithUTF8String:pszFullPath]; + NSDictionary* pDict = [NSDictionary dictionaryWithContentsOfFile:pPath]; + + CCDictionary* pRet = new CCDictionary(); + for (id key in [pDict allKeys]) { + id value = [pDict objectForKey:key]; + static_addValueToCCDict(key, value, pRet); + } + + return pRet; + } + + CCArray* ccFileUtils_arrayWithContentsOfFileThreadSafe(const char* pFileName) + { + NSString* pPath = [NSString stringWithUTF8String:pFileName]; + NSString* pathExtension= [pPath pathExtension]; + pPath = [pPath stringByDeletingPathExtension]; + pPath = [[NSBundle mainBundle] pathForResource:pPath ofType:pathExtension]; + NSArray* pArray = [NSArray arrayWithContentsOfFile:pPath]; + + CCArray* pRet = new CCArray(); + for (id value in pArray) { + static_addItemToCCArray(value, pRet); + } + + return pRet; + } + +/* + CCDictionary *CCFileUtils::dictionaryWithContentsOfFile(const char *pFileName) + { + NSString* pPath = [NSString stringWithUTF8String:pFileName]; + NSDictionary* pDict = [NSDictionary dictionaryWithContentsOfFile:pPath]; + + CCDictionary* pRet = new CCDictionary(); + for (id key in [pDict allKeys]) { + id value = [pDict objectForKey:key]; + static_addValueToCCDict(key, value, pRet); + } + pRet->autorelease(); + return pRet; + } + + + unsigned char* CCFileUtils::getFileData(const char* pszFileName, const char* pszMode, unsigned long * pSize) + { + unsigned char * pBuffer = NULL; + + do + { + // read the file from hardware + FILE *fp = fopen(pszFileName, pszMode); + CC_BREAK_IF(!fp); + + fseek(fp,0,SEEK_END); + *pSize = ftell(fp); + fseek(fp,0,SEEK_SET); + pBuffer = new unsigned char[*pSize]; + *pSize = fread(pBuffer,sizeof(unsigned char), *pSize,fp); + fclose(fp); + } while (0); + + if (! pBuffer && getIsPopupNotify()) + { + std::string title = "Notification"; + std::string msg = "Get data from file("; + msg.append(pszFileName).append(") failed!"); + + CCMessageBox(msg.c_str(), title.c_str()); + } + return pBuffer; + } + + void CCFileUtils::setResource(const char* pszZipFileName) + { + CCAssert(0, "Have not implement!"); + } + + void CCFileUtils::setRelativePath(const char* pszRelativePath) + { + CCAssert(0, "Have not implement!"); + } +*/ + // notification support when getFileData from a invalid file + static bool s_bPopupNotify = true; + + void CCFileUtils::setPopupNotify(bool bNotify) + { + s_bPopupNotify = bNotify; + } + + bool CCFileUtils::isPopupNotify() + { + return s_bPopupNotify; + } + + std::string CCFileUtils::getWriteablePath() + { + // save to document folder + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDirectory = [paths objectAtIndex:0]; + std::string strRet = [documentsDirectory UTF8String]; + strRet.append("/"); + return strRet; + } + +}//namespace cocos2d + diff --git a/cocos2dx/platform/mac/CCGL.h b/cocos2dx/platform/mac/CCGL.h new file mode 100644 index 0000000000..29d1d03eb9 --- /dev/null +++ b/cocos2dx/platform/mac/CCGL.h @@ -0,0 +1,33 @@ +/**************************************************************************** +Copyright (c) 2010 cocos2d-x.org + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ + +#ifndef __CCGL_H__ +#define __CCGL_H__ + +#import +#import + +#define CC_GL_DEPTH24_STENCIL8 -1 + +#endif // __CCGL_H__ diff --git a/cocos2dx/platform/mac/CCImage.mm b/cocos2dx/platform/mac/CCImage.mm new file mode 100755 index 0000000000..02e5b70f64 --- /dev/null +++ b/cocos2dx/platform/mac/CCImage.mm @@ -0,0 +1,881 @@ +/**************************************************************************** +Copyright (c) 2010 cocos2d-x.org + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ +#include +#include +#include "CCDirector.h" +#include "ccMacros.h" +#include "CCImage.h" +#include "CCFileUtils.h" +#include "CCTexture2D.h" +#include +#include +#include +#include + +typedef struct +{ + unsigned int height; + unsigned int width; + int bitsPerComponent; + bool hasAlpha; + bool isPremultipliedAlpha; + unsigned char* data; +} tImageInfo; + +static unsigned int nextPOT(unsigned int x) +{ + x = x - 1; + x = x | (x >> 1); + x = x | (x >> 2); + x = x | (x >> 4); + x = x | (x >> 8); + x = x | (x >> 16); + return x + 1; +} + +typedef enum { + kCCTexture2DPixelFormat_Automatic = 0, + //! 32-bit texture: RGBA8888 + kCCTexture2DPixelFormat_RGBA8888, + //! 24-bit texture: RGBA888 + kCCTexture2DPixelFormat_RGB888, + //! 16-bit texture without Alpha channel + kCCTexture2DPixelFormat_RGB565, + //! 8-bit textures used as masks + kCCTexture2DPixelFormat_A8, + //! 16-bit textures: RGBA4444 + kCCTexture2DPixelFormat_RGBA4444, + //! 16-bit textures: RGB5A1 + kCCTexture2DPixelFormat_RGB5A1, + + //! Default texture format: RGBA8888 + kCCTexture2DPixelFormat_Default = kCCTexture2DPixelFormat_RGBA8888, + + // backward compatibility stuff + kTexture2DPixelFormat_Automatic = kCCTexture2DPixelFormat_Automatic, + kTexture2DPixelFormat_RGBA8888 = kCCTexture2DPixelFormat_RGBA8888, + kTexture2DPixelFormat_RGB888 = kCCTexture2DPixelFormat_RGB888, + kTexture2DPixelFormat_RGB565 = kCCTexture2DPixelFormat_RGB565, + kTexture2DPixelFormat_A8 = kCCTexture2DPixelFormat_A8, + kTexture2DPixelFormat_RGBA4444 = kCCTexture2DPixelFormat_RGBA4444, + kTexture2DPixelFormat_RGB5A1 = kCCTexture2DPixelFormat_RGB5A1, + kTexture2DPixelFormat_Default = kCCTexture2DPixelFormat_Default + +} CCTexture2DPixelFormat; + +static bool _initPremultipliedATextureWithImage(CGImageRef image, NSUInteger POTWide, NSUInteger POTHigh, tImageInfo *pImageInfo) +{ + NSUInteger i; + CGContextRef context = nil; + unsigned char* data = nil;; + CGColorSpaceRef colorSpace; + unsigned char* tempData; + unsigned int* inPixel32; + unsigned short* outPixel16; + bool hasAlpha; + CGImageAlphaInfo info; + CGSize imageSize; + CCTexture2DPixelFormat pixelFormat; + + info = CGImageGetAlphaInfo(image); + hasAlpha = ((info == kCGImageAlphaPremultipliedLast) || (info == kCGImageAlphaPremultipliedFirst) || (info == kCGImageAlphaLast) || (info == kCGImageAlphaFirst) ? YES : NO); + + size_t bpp = CGImageGetBitsPerComponent(image); + colorSpace = CGImageGetColorSpace(image); + + if(colorSpace) + { + if(hasAlpha || bpp >= 8) + { + pixelFormat = kCCTexture2DPixelFormat_Default; + } + else + { + pixelFormat = kCCTexture2DPixelFormat_RGB565; + } + } + else + { + // NOTE: No colorspace means a mask image + pixelFormat = kCCTexture2DPixelFormat_A8; + } + + imageSize.width = CGImageGetWidth(image); + imageSize.height = CGImageGetHeight(image); + + // Create the bitmap graphics context + + switch(pixelFormat) + { + case kCCTexture2DPixelFormat_RGBA8888: + case kCCTexture2DPixelFormat_RGBA4444: + case kCCTexture2DPixelFormat_RGB5A1: + colorSpace = CGColorSpaceCreateDeviceRGB(); + data = new unsigned char[POTHigh * POTWide * 4]; + info = hasAlpha ? kCGImageAlphaPremultipliedLast : kCGImageAlphaNoneSkipLast; + context = CGBitmapContextCreate(data, POTWide, POTHigh, 8, 4 * POTWide, colorSpace, info | kCGBitmapByteOrder32Big); + CGColorSpaceRelease(colorSpace); + break; + + case kCCTexture2DPixelFormat_RGB565: + colorSpace = CGColorSpaceCreateDeviceRGB(); + data = new unsigned char[POTHigh * POTWide * 4]; + info = kCGImageAlphaNoneSkipLast; + context = CGBitmapContextCreate(data, POTWide, POTHigh, 8, 4 * POTWide, colorSpace, info | kCGBitmapByteOrder32Big); + CGColorSpaceRelease(colorSpace); + break; + case kCCTexture2DPixelFormat_A8: + data = new unsigned char[POTHigh * POTWide]; + info = kCGImageAlphaOnly; + context = CGBitmapContextCreate(data, POTWide, POTHigh, 8, POTWide, NULL, info); + break; + default: + return false; + } + + CGRect rect; + rect.size.width = POTWide; + rect.size.height = POTHigh; + rect.origin.x = 0; + rect.origin.y = 0; + + CGContextClearRect(context, rect); + CGContextTranslateCTM(context, 0, 0); + CGContextDrawImage(context, rect, image); + + // Repack the pixel data into the right format + + if(pixelFormat == kCCTexture2DPixelFormat_RGB565) + { + //Convert "RRRRRRRRRGGGGGGGGBBBBBBBBAAAAAAAA" to "RRRRRGGGGGGBBBBB" + tempData = new unsigned char[POTHigh * POTWide * 2]; + inPixel32 = (unsigned int*)data; + outPixel16 = (unsigned short*)tempData; + for(i = 0; i < POTWide * POTHigh; ++i, ++inPixel32) + { + *outPixel16++ = ((((*inPixel32 >> 0) & 0xFF) >> 3) << 11) | ((((*inPixel32 >> 8) & 0xFF) >> 2) << 5) | ((((*inPixel32 >> 16) & 0xFF) >> 3) << 0); + } + + delete[] data; + data = tempData; + + } + else if (pixelFormat == kCCTexture2DPixelFormat_RGBA4444) + { + //Convert "RRRRRRRRRGGGGGGGGBBBBBBBBAAAAAAAA" to "RRRRGGGGBBBBAAAA" + tempData = new unsigned char[POTHigh * POTWide * 2]; + inPixel32 = (unsigned int*)data; + outPixel16 = (unsigned short*)tempData; + for(i = 0; i < POTWide * POTHigh; ++i, ++inPixel32) + { + *outPixel16++ = + ((((*inPixel32 >> 0) & 0xFF) >> 4) << 12) | // R + ((((*inPixel32 >> 8) & 0xFF) >> 4) << 8) | // G + ((((*inPixel32 >> 16) & 0xFF) >> 4) << 4) | // B + ((((*inPixel32 >> 24) & 0xFF) >> 4) << 0); // A + } + + delete[] data; + data = tempData; + + } + else if (pixelFormat == kCCTexture2DPixelFormat_RGB5A1) + { + //Convert "RRRRRRRRRGGGGGGGGBBBBBBBBAAAAAAAA" to "RRRRRGGGGGBBBBBA" + tempData = new unsigned char[POTHigh * POTWide * 2]; + inPixel32 = (unsigned int*)data; + outPixel16 = (unsigned short*)tempData; + for(i = 0; i < POTWide * POTHigh; ++i, ++inPixel32) + { + *outPixel16++ = + ((((*inPixel32 >> 0) & 0xFF) >> 3) << 11) | // R + ((((*inPixel32 >> 8) & 0xFF) >> 3) << 6) | // G + ((((*inPixel32 >> 16) & 0xFF) >> 3) << 1) | // B + ((((*inPixel32 >> 24) & 0xFF) >> 7) << 0); // A + } + + delete[] data; + data = tempData; + } + + // should be after calling super init + pImageInfo->isPremultipliedAlpha = true; + pImageInfo->hasAlpha = true; + pImageInfo->bitsPerComponent = bpp; + pImageInfo->width = POTWide; + pImageInfo->height = POTHigh; + + if (pImageInfo->data) + { + delete [] pImageInfo->data; + } + pImageInfo->data = data; + + CGContextRelease(context); + return true; +} + +static bool _initWithImage(CGImageRef CGImage, tImageInfo *pImageinfo, double scaleX, double scaleY) +{ + NSUInteger POTWide, POTHigh; + + if(CGImage == NULL) + { + return false; + } + + //if (cocos2d::CCImage::getIsScaleEnabled()) + if( cocos2d::CCDirector::sharedDirector()->getContentScaleFactor() > 1.0f ) + { + POTWide = CGImageGetWidth(CGImage) * scaleX; + POTHigh = CGImageGetHeight(CGImage) * scaleY; + } + else + { + POTWide = CGImageGetWidth(CGImage); + POTHigh = CGImageGetHeight(CGImage); + } + + + // always load premultiplied images + _initPremultipliedATextureWithImage(CGImage, POTWide, POTHigh, pImageinfo); + + return true; +} + +static bool _initWithFile(const char* path, tImageInfo *pImageinfo) +{ + CGImageRef CGImage; + NSImage *jpg; + NSImage *png; + bool ret; + + // convert jpg to png before loading the texture + + NSString *fullPath = [NSString stringWithUTF8String:path]; + jpg = [[NSImage alloc] initWithContentsOfFile: fullPath]; + //png = [[NSImage alloc] initWithData:UIImagePNGRepresentation(jpg)]; + CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)[jpg TIFFRepresentation], NULL); + CGImage = CGImageSourceCreateImageAtIndex(source, 0, NULL); + + ret = _initWithImage(CGImage, pImageinfo, 1.0, 1.0); + + [png release]; + [jpg release]; + + return ret; +} + + +static bool _initWithData(void * pBuffer, int length, tImageInfo *pImageinfo, double scaleX, double scaleY) +{ + bool ret = false; + + if (pBuffer) + { + CGImageRef CGImage; + NSData *data; + + data = [NSData dataWithBytes:pBuffer length:length]; + CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)data, NULL); + CGImage = CGImageSourceCreateImageAtIndex(source, 0, NULL); + + ret = _initWithImage(CGImage, pImageinfo, scaleX, scaleY); + } + + return ret; +} + +static bool _isValidFontName(const char *fontName) +{ + bool ret = false; +#if 0 + NSString *fontNameNS = [NSString stringWithUTF8String:fontName]; + + for (NSString *familiName in [NSFont familyNames]) + { + if ([familiName isEqualToString:fontNameNS]) + { + ret = true; + goto out; + } + + for(NSString *font in [NSFont fontNamesForFamilyName: familiName]) + { + if ([font isEqualToString: fontNameNS]) + { + ret = true; + goto out; + } + } + } +#endif + out: + return ret; +} + +static bool _initWithString(const char * pText, cocos2d::CCImage::ETextAlign eAlign, const char * pFontName, int nSize, tImageInfo* pInfo, cocos2d::ccColor3B* pStrokeColor) +{ + bool bRet = false; + + CCAssert( pText, @"Invalid pText"); + CCAssert( pInfo, @"Invalid pInfo"); + + do { + NSString * string = [NSString stringWithUTF8String:pText]; + //string = [NSString stringWithFormat:@"d\r\nhello world hello kitty Hello what %@", string]; + + // font + NSFont *font = [[NSFontManager sharedFontManager] + fontWithFamily:[NSString stringWithUTF8String:pFontName] + traits:NSUnboldFontMask | NSUnitalicFontMask + weight:0 + size:nSize]; + + if (font == nil) { + font = [[NSFontManager sharedFontManager] + fontWithFamily:@"Arial" + traits:NSUnboldFontMask | NSUnitalicFontMask + weight:0 + size:nSize]; + } + CC_BREAK_IF(!font); + + // color + NSColor* foregroundColor; + if (pStrokeColor) { + foregroundColor = [NSColor colorWithDeviceRed:pStrokeColor->r/255.0 green:pStrokeColor->g/255.0 blue:pStrokeColor->b/255.0 alpha:1]; + } else { + foregroundColor = [NSColor whiteColor]; + } + + + // alignment, linebreak + unsigned uHoriFlag = eAlign & 0x0f; + unsigned uVertFlag = (eAlign & 0xf0) >> 4; + NSTextAlignment align = (2 == uHoriFlag) ? NSRightTextAlignment + : (3 == uHoriFlag) ? NSCenterTextAlignment + : NSLeftTextAlignment; + + NSMutableParagraphStyle *paragraphStyle = [[[NSMutableParagraphStyle alloc] init] autorelease]; + [paragraphStyle setParagraphStyle:[NSParagraphStyle defaultParagraphStyle]]; + [paragraphStyle setLineBreakMode:NSLineBreakByCharWrapping]; + [paragraphStyle setAlignment:align]; + + // attribute + NSDictionary* tokenAttributesDict = [NSDictionary dictionaryWithObjectsAndKeys: + foregroundColor,NSForegroundColorAttributeName, + font, NSFontAttributeName, + paragraphStyle, NSParagraphStyleAttributeName, nil]; + + // linebreak + if (pInfo->width > 0) { + if ([string sizeWithAttributes:tokenAttributesDict].width > pInfo->width) { + NSMutableString *lineBreak = [[[NSMutableString alloc] init] autorelease]; + NSUInteger length = [string length]; + NSRange range = NSMakeRange(0, 1); + NSUInteger width = 0; + for (NSUInteger i = 0; i < length; i++) { + range.location = i; + [lineBreak appendString:[string substringWithRange:range]]; + width = [lineBreak sizeWithAttributes:tokenAttributesDict].width; + if (width > pInfo->width) { + [lineBreak insertString:@"\r\n" atIndex:[lineBreak length] - 1]; + } + } + string = lineBreak; + } + } + + NSAttributedString *stringWithAttributes =[[[NSAttributedString alloc] initWithString:string + attributes:tokenAttributesDict] autorelease]; + + NSSize realDimensions = [stringWithAttributes size]; + // Mac crashes if the width or height is 0 + CC_BREAK_IF(realDimensions.width <= 0 || realDimensions.height <= 0); + + CGSize dimensions = CGSizeMake(pInfo->width, pInfo->height); + + + if(dimensions.width <= 0 && dimensions.height <= 0) { + dimensions.width = realDimensions.width; + dimensions.height = realDimensions.height; + } else if (dimensions.height <= 0) { + dimensions.height = realDimensions.height; + } + + NSUInteger POTWide = (NSUInteger)dimensions.width; + NSUInteger POTHigh = (NSUInteger)(MAX(dimensions.height, realDimensions.height)); + unsigned char* data; + //Alignment + + CGFloat xPadding = 0; + switch (align) { + case NSLeftTextAlignment: xPadding = 0; break; + case NSCenterTextAlignment: xPadding = (dimensions.width-realDimensions.width)/2.0f; break; + case NSRightTextAlignment: xPadding = dimensions.width-realDimensions.width; break; + default: break; + } + + CGFloat yPadding = (1 == uVertFlag || realDimensions.height >= dimensions.height) ? 0 // align to top + : (2 == uVertFlag) ? dimensions.height - realDimensions.height // align to bottom + : (dimensions.height - realDimensions.height) / 2.0f; // align to center + + + NSRect textRect = NSMakeRect(xPadding, POTHigh - dimensions.height + yPadding, realDimensions.width, realDimensions.height); + //Disable antialias + + [[NSGraphicsContext currentContext] setShouldAntialias:NO]; + + NSImage *image = [[NSImage alloc] initWithSize:NSMakeSize(POTWide, POTHigh)]; + [image lockFocus]; + + //[stringWithAttributes drawAtPoint:NSMakePoint(xPadding, offsetY)]; // draw at offset position + [stringWithAttributes drawInRect:textRect]; + //[stringWithAttributes drawInRect:textRect withAttributes:tokenAttributesDict]; + NSBitmapImageRep *bitmap = [[NSBitmapImageRep alloc] initWithFocusedViewRect:NSMakeRect (0.0f, 0.0f, POTWide, POTHigh)]; + [image unlockFocus]; + + data = (unsigned char*) [bitmap bitmapData]; //Use the same buffer to improve the performance. + + NSUInteger textureSize = POTWide*POTHigh*4; + + unsigned char* dataNew = new unsigned char[textureSize]; + CC_BREAK_IF(!dataNew); + memcpy(dataNew, data, textureSize); + + [bitmap release]; + [image release]; + + // output params + pInfo->width = POTWide; + pInfo->height = POTHigh; + pInfo->data = dataNew; + pInfo->hasAlpha = true; + pInfo->isPremultipliedAlpha = true; + pInfo->bitsPerComponent = 8; + + bRet = true; + } while (0); + return bRet; +} + +NS_CC_BEGIN; + +static bool m_bEnabledScale = true; + +bool isFileExists(const char* szFilePath); + +bool isFileExists(const char* szFilePath) +{ +#if CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 + //TCHAR dirpath[MAX_PATH]; + //MultiByteToWideChar(936,0,szFilePath,-1,dirpath,sizeof(dirpath)); + DWORD dwFileAttr = GetFileAttributesA(szFilePath); + if (INVALID_FILE_ATTRIBUTES == dwFileAttr + || (dwFileAttr&FILE_ATTRIBUTE_DIRECTORY)) { + return false; + } +#elif CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID + bool bFind = true; + do + { + struct stat buf; + int n = stat(szFilePath, &buf); + if ((0 != n) + || !(buf.st_mode&S_IFMT)) + { + bFind = false; + } + } while (0); + if (!bFind) + { + //std::string strFilenName = s_strRelativePath + szFilePath; + unsigned char * pBuffer = NULL; + unzFile pFile = NULL; + unsigned long pSize = 0; + + do + { + pFile = unzOpen(s_strAndroidPackagePath.c_str()); + if(!pFile)break; + + int nRet = unzLocateFile(pFile, szFilePath, 1); + if(UNZ_OK != nRet) + bFind = false; + else + bFind = true; + } while (0); + + if (pFile) + { + unzClose(pFile); + } + } + + return bFind; +#else + struct stat buf; + int n = stat(szFilePath, &buf); + if ((0 != n) + || !(buf.st_mode&S_IFMT)) { + return false; + } + +#endif + return true; +} + +CCImage::CCImage() +: m_nWidth(0) +, m_nHeight(0) +, m_nBitsPerComponent(0) +, m_pData(0) +, m_bHasAlpha(false) +, m_bPreMulti(false) +{ + +} + +CCImage::~CCImage() +{ + CC_SAFE_DELETE_ARRAY(m_pData); +} + +bool CCImage::initWithImageFile(const char * strPath, EImageFormat eImgFmt/* = eFmtPng*/) +{ + std::string strTemp = CCFileUtils::sharedFileUtils()->fullPathFromRelativePath(strPath); + if (m_bEnabledScale) + { + if (!isFileExists(strTemp.c_str())) + { + if (strTemp.rfind("@2x") == std::string::npos) + { + int t = strTemp.rfind("."); + if (t != std::string::npos) + { + strTemp.insert(t, "@2x"); + } +/* CCSize size = CCDirector::sharedDirector()->getWinSize(); + #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) + m_dScaleX = size.width/800.0f; + m_dScaleY = size.height/480.0f; + #else + m_dScaleX = size.width/960.0f; + m_dScaleY = size.height/640.0f; + + #endif +*/ + } + } + else + { +// m_dScaleX = 1.0; +// m_dScaleY = 1.0; + } + } + +// CCFileData tempData(strTemp.c_str(), "rb"); +// return initWithImageData(tempData.getBuffer(), tempData.getSize(), eImgFmt); + + unsigned long fileSize = 0; + unsigned char* pFileData = CCFileUtils::sharedFileUtils()->getFileData(strTemp.c_str(), "rb", &fileSize); + bool ret = initWithImageData(pFileData, fileSize, eImgFmt); + free(pFileData); + return ret; +} + +bool CCImage::initWithImageFileThreadSafe(const char *fullpath, EImageFormat imageType) +{ + /* + * CCFileUtils::fullPathFromRelativePath() is not thread-safe, it use autorelease(). + */ + bool bRet = false; + unsigned long nSize = 0; + unsigned char* pBuffer = CCFileUtils::sharedFileUtils()->getFileData(fullpath, "rb", &nSize); + if (pBuffer != NULL && nSize > 0) + { + bRet = initWithImageData(pBuffer, nSize, imageType); + } + CC_SAFE_DELETE_ARRAY(pBuffer); + return bRet; +} + + + +/* +// please uncomment this and integrate it somehow if you know what your doing, thanks +bool CCImage::potImageData(unsigned int POTWide, unsigned int POTHigh) +{ + unsigned char* data = NULL; + unsigned char* tempData =NULL; + unsigned int* inPixel32 = NULL; + unsigned short* outPixel16 = NULL; + bool hasAlpha; + CCTexture2DPixelFormat pixelFormat; + + hasAlpha = this->hasAlpha(); + + size_t bpp = this->getBitsPerComponent(); + + // compute pixel format + if(hasAlpha) + { + pixelFormat = CCTexture2D::defaultAlphaPixelFormat(); + } + else + { + if (bpp >= 8) + { + pixelFormat = kCCTexture2DPixelFormat_RGB888; + } + else + { + CCLOG("cocos2d: CCTexture2D: Using RGB565 texture since image has no alpha"); + pixelFormat = kCCTexture2DPixelFormat_RGB565; + } + } + + switch(pixelFormat) { + case kCCTexture2DPixelFormat_RGBA8888: + case kCCTexture2DPixelFormat_RGBA4444: + case kCCTexture2DPixelFormat_RGB5A1: + case kCCTexture2DPixelFormat_RGB565: + case kCCTexture2DPixelFormat_A8: + tempData = (unsigned char*)(this->getData()); + CCAssert(tempData != NULL, "NULL image data."); + + if(this->getWidth() == (short)POTWide && this->getHeight() == (short)POTHigh) + { + data = new unsigned char[POTHigh * POTWide * 4]; + memcpy(data, tempData, POTHigh * POTWide * 4); + } + else + { + data = new unsigned char[POTHigh * POTWide * 4]; + memset(data, 0, POTHigh * POTWide * 4); + + unsigned char* pPixelData = (unsigned char*) tempData; + unsigned char* pTargetData = (unsigned char*) data; + + int imageHeight = this->getHeight(); + for(int y = 0; y < imageHeight; ++y) + { + memcpy(pTargetData+POTWide*4*y, pPixelData+(this->getWidth())*4*y, (this->getWidth())*4); + } + } + + break; + case kCCTexture2DPixelFormat_RGB888: + tempData = (unsigned char*)(this->getData()); + CCAssert(tempData != NULL, "NULL image data."); + if(this->getWidth() == (short)POTWide && this->getHeight() == (short)POTHigh) + { + data = new unsigned char[POTHigh * POTWide * 3]; + memcpy(data, tempData, POTHigh * POTWide * 3); + } + else + { + data = new unsigned char[POTHigh * POTWide * 3]; + memset(data, 0, POTHigh * POTWide * 3); + + unsigned char* pPixelData = (unsigned char*) tempData; + unsigned char* pTargetData = (unsigned char*) data; + + int imageHeight = this->getHeight(); + for(int y = 0; y < imageHeight; ++y) + { + memcpy(pTargetData+POTWide*3*y, pPixelData+(this->getWidth())*3*y, (this->getWidth())*3); + } + } + break; + default: + CCAssert(0, "Invalid pixel format"); + } + + // Repack the pixel data into the right format + + if(pixelFormat == kCCTexture2DPixelFormat_RGB565) { + //Convert "RRRRRRRRRGGGGGGGGBBBBBBBBAAAAAAAA" to "RRRRRGGGGGGBBBBB" + tempData = new unsigned char[POTHigh * POTWide * 2]; + inPixel32 = (unsigned int*)data; + outPixel16 = (unsigned short*)tempData; + + unsigned int length = POTWide * POTHigh; + for(unsigned int i = 0; i < length; ++i, ++inPixel32) + { + *outPixel16++ = + ((((*inPixel32 >> 0) & 0xFF) >> 3) << 11) | // R + ((((*inPixel32 >> 8) & 0xFF) >> 2) << 5) | // G + ((((*inPixel32 >> 16) & 0xFF) >> 3) << 0); // B + } + + delete [] data; + data = tempData; + } + else if (pixelFormat == kCCTexture2DPixelFormat_RGBA4444) { + //Convert "RRRRRRRRRGGGGGGGGBBBBBBBBAAAAAAAA" to "RRRRGGGGBBBBAAAA" + tempData = new unsigned char[POTHigh * POTWide * 2]; + inPixel32 = (unsigned int*)data; + outPixel16 = (unsigned short*)tempData; + + unsigned int length = POTWide * POTHigh; + for(unsigned int i = 0; i < length; ++i, ++inPixel32) + { + *outPixel16++ = + ((((*inPixel32 >> 0) & 0xFF) >> 4) << 12) | // R + ((((*inPixel32 >> 8) & 0xFF) >> 4) << 8) | // G + ((((*inPixel32 >> 16) & 0xFF) >> 4) << 4) | // B + ((((*inPixel32 >> 24) & 0xFF) >> 4) << 0); // A + } + + delete [] data; + data = tempData; + } + else if (pixelFormat == kCCTexture2DPixelFormat_RGB5A1) { + //Convert "RRRRRRRRRGGGGGGGGBBBBBBBBAAAAAAAA" to "RRRRRGGGGGBBBBBA" + tempData = new unsigned char[POTHigh * POTWide * 2]; + inPixel32 = (unsigned int*)data; + outPixel16 = (unsigned short*)tempData; + + unsigned int length = POTWide * POTHigh; + for(unsigned int i = 0; i < length; ++i, ++inPixel32) + { + *outPixel16++ = + ((((*inPixel32 >> 0) & 0xFF) >> 3) << 11) | // R + ((((*inPixel32 >> 8) & 0xFF) >> 3) << 6) | // G + ((((*inPixel32 >> 16) & 0xFF) >> 3) << 1) | // B + ((((*inPixel32 >> 24) & 0xFF) >> 7) << 0); // A + } + + delete []data; + data = tempData; + } + else if (pixelFormat == kCCTexture2DPixelFormat_A8) + { + // fix me, how to convert to A8 + pixelFormat = kCCTexture2DPixelFormat_RGBA8888; + + // + //The code can not work, how to convert to A8? + // + //tempData = new unsigned char[POTHigh * POTWide]; + //inPixel32 = (unsigned int*)data; + //outPixel8 = tempData; + + //unsigned int length = POTWide * POTHigh; + //for(unsigned int i = 0; i < length; ++i, ++inPixel32) + //{ + // *outPixel8++ = (*inPixel32 >> 24) & 0xFF; + //} + + //delete []data; + //data = tempData; + + } + + if (data) + { + CC_SAFE_DELETE_ARRAY(m_pData); + m_pData = data; + } + return true; +} +*/ + +//bool CCImage::initWithImageData(void * pData, int nDataLen, EImageFormat eFmt/* = eSrcFmtPng*/) +bool CCImage::initWithImageData(void * pData, + int nDataLen, + EImageFormat eFmt, + int nWidth, + int nHeight, + int nBitsPerComponent) +{ + bool bRet = false; + tImageInfo info = {0}; + do + { + CC_BREAK_IF(! pData || nDataLen <= 0); + bRet = _initWithData(pData, nDataLen, &info, 1.0f, 1.0f);//m_dScaleX, m_dScaleY); + } while (0); + + if (bRet) + { + m_nHeight = (short)info.height; + m_nWidth = (short)info.width; + m_nBitsPerComponent = info.bitsPerComponent; + if (eFmt == kFmtJpg) + { + m_bHasAlpha = true; + m_bPreMulti = false; + } + else + { + m_bHasAlpha = info.hasAlpha; + m_bPreMulti = info.isPremultipliedAlpha; + } + m_pData = info.data; + } + return bRet; +} + +bool CCImage::initWithString( + const char * pText, + int nWidth, + int nHeight, + ETextAlign eAlignMask, + const char * pFontName, + int nSize) +{ + tImageInfo info = {0}; + info.width = nWidth; + info.height = nHeight; + + if (! _initWithString(pText, eAlignMask, pFontName, nSize, &info, NULL)) //pStrokeColor)) + { + return false; + } + m_nHeight = (short)info.height; + m_nWidth = (short)info.width; + m_nBitsPerComponent = info.bitsPerComponent; + m_bHasAlpha = info.hasAlpha; + m_bPreMulti = info.isPremultipliedAlpha; + if (m_pData) { + CC_SAFE_DELETE_ARRAY(m_pData); + } + m_pData = info.data; + + return true; +} + +bool CCImage::saveToFile(const char *pszFilePath, bool bIsToRGB) +{ + assert(false); + return false; +} + + + +NS_CC_END; + diff --git a/cocos2dx/platform/mac/CCPlatformDefine.h b/cocos2dx/platform/mac/CCPlatformDefine.h new file mode 100644 index 0000000000..789ea77e08 --- /dev/null +++ b/cocos2dx/platform/mac/CCPlatformDefine.h @@ -0,0 +1,24 @@ +#ifndef __CCPLATFORMDEFINE_H__ +#define __CCPLATFORMDEFINE_H__ + +#include + +#define CC_DLL + +#define CC_ASSERT(cond) assert(cond) + + +#define CC_UNUSED_PARAM(unusedparam) (void)unusedparam + +/* Define NULL pointer value */ +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif + + + +#endif /* __CCPLATFORMDEFINE_H__*/ diff --git a/cocos2dx/platform/mac/CCStdC.h b/cocos2dx/platform/mac/CCStdC.h new file mode 100644 index 0000000000..21747332ef --- /dev/null +++ b/cocos2dx/platform/mac/CCStdC.h @@ -0,0 +1,46 @@ +/**************************************************************************** +Copyright (c) 2010 cocos2d-x.org + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ + +#ifndef __CC_STD_C_H__ +#define __CC_STD_C_H__ + +#include "platform/CCPlatformMacros.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef MIN +#define MIN(x,y) (((x) > (y)) ? (y) : (x)) +#endif // MIN + +#ifndef MAX +#define MAX(x,y) (((x) < (y)) ? (y) : (x)) +#endif // MAX + +#endif // __CC_STD_C_H__ diff --git a/cocos2dx/platform/mac/CCWindow.h b/cocos2dx/platform/mac/CCWindow.h new file mode 100644 index 0000000000..8e24b8a858 --- /dev/null +++ b/cocos2dx/platform/mac/CCWindow.h @@ -0,0 +1,38 @@ +/**************************************************************************** +Copyright (c) 2010 cocos2d-x.org + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ + +#ifndef __CC_WINDOW_H__ +#define __CC_WINDOW_H__ + +#import + + +@interface CCWindow : NSWindow +{ +} +- (id) initWithFrame:(NSRect)frame fullscreen:(BOOL)fullscreen; + +@end + +#endif // __CC_WINDOW_H__ \ No newline at end of file diff --git a/cocos2dx/platform/mac/CCWindow.m b/cocos2dx/platform/mac/CCWindow.m new file mode 100644 index 0000000000..c598b8a84b --- /dev/null +++ b/cocos2dx/platform/mac/CCWindow.m @@ -0,0 +1,63 @@ +/**************************************************************************** +Copyright (c) 2010 cocos2d-x.org + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ + +#import "CCWindow.h" + + +@implementation CCWindow + +- (id) initWithFrame:(NSRect)frame fullscreen:(BOOL)fullscreen +{ + int styleMask = fullscreen ? NSBackingStoreBuffered : ( NSTitledWindowMask | NSClosableWindowMask ); + self = [self initWithContentRect:frame + styleMask:styleMask + backing:NSBackingStoreBuffered + defer:YES]; + + if (self != nil) + { + if(fullscreen) + { + [self setLevel:NSMainMenuWindowLevel+1]; + [self setHidesOnDeactivate:YES]; + [self setHasShadow:NO]; + } + + [self setAcceptsMouseMovedEvents:NO]; + [self setOpaque:YES]; + } + return self; +} + +- (BOOL) canBecomeKeyWindow +{ + return YES; +} + +- (BOOL) canBecomeMainWindow +{ + return YES; +} +@end + diff --git a/cocos2dx/platform/mac/EAGLView.h b/cocos2dx/platform/mac/EAGLView.h new file mode 100755 index 0000000000..1dad10f082 --- /dev/null +++ b/cocos2dx/platform/mac/EAGLView.h @@ -0,0 +1,111 @@ +/**************************************************************************** +Copyright (c) 2010 cocos2d-x.org + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ + +#ifndef __EAGLVIEW_MAC_H__ +#define __EAGLVIEW_MAC_H__ + +#include +#include "ccConfig.h" + +//PROTOCOLS: + +@protocol MacEventDelegate +// Mouse +- (void)mouseDown:(NSEvent *)theEvent; +- (void)mouseUp:(NSEvent *)theEvent; +- (void)mouseMoved:(NSEvent *)theEvent; +- (void)mouseDragged:(NSEvent *)theEvent; +- (void)rightMouseDown:(NSEvent*)event; +- (void)rightMouseDragged:(NSEvent*)event; +- (void)rightMouseUp:(NSEvent*)event; +- (void)otherMouseDown:(NSEvent*)event; +- (void)otherMouseDragged:(NSEvent*)event; +- (void)otherMouseUp:(NSEvent*)event; +- (void)scrollWheel:(NSEvent *)theEvent; +- (void)mouseEntered:(NSEvent *)theEvent; +- (void)mouseExited:(NSEvent *)theEvent; + + +// Keyboard +- (void)keyDown:(NSEvent *)theEvent; +- (void)keyUp:(NSEvent *)theEvent; +- (void)flagsChanged:(NSEvent *)theEvent; + +// Touches +- (void)touchesBeganWithEvent:(NSEvent *)event; +- (void)touchesMovedWithEvent:(NSEvent *)event; +- (void)touchesEndedWithEvent:(NSEvent *)event; +- (void)touchesCancelledWithEvent:(NSEvent *)event; + +#if CC_DIRECTOR_MAC_USE_DISPLAY_LINK_THREAD +- (void)queueEvent:(NSEvent*)event selector:(SEL)selector; +#endif + +@end + +/** MacGLView + + Only available for Mac OS X + */ +@interface EAGLView : NSOpenGLView { + id eventDelegate_; + + BOOL isFullScreen_; + NSWindow *fullScreenWindow_; + + // cache + NSWindow *windowGLView_; + NSView *superViewGLView_; + NSRect originalWinRect_; // Original size and position +} + +@property (nonatomic, readwrite, assign) id eventDelegate; + +// whether or not the view is in fullscreen mode +@property (nonatomic, readonly) BOOL isFullScreen; + +// initializes the MacGLView with a frame rect and an OpenGL context +- (id) initWithFrame:(NSRect)frameRect shareContext:(NSOpenGLContext*)context; + +/** uses and locks the OpenGL context */ +-(void) lockOpenGLContext; + +/** unlocks the openGL context */ +-(void) unlockOpenGLContext; + +/** returns the depth format of the view in BPP */ +- (NSUInteger) depthFormat; + +// get the view object ++(id) sharedEGLView; + +-(int) getWidth; +-(int) getHeight; +-(void) swapBuffers; + +-(void) setFullScreen:(BOOL)fullscreen; + +@end +#endif // __EAGLVIEW_MAC_H__ + diff --git a/cocos2dx/platform/mac/EAGLView.mm b/cocos2dx/platform/mac/EAGLView.mm new file mode 100755 index 0000000000..3c52707d36 --- /dev/null +++ b/cocos2dx/platform/mac/EAGLView.mm @@ -0,0 +1,429 @@ +/**************************************************************************** +Copyright (c) 2010 cocos2d-x.org + +http://www.cocos2d-x.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +****************************************************************************/ + +/* + * Idea of subclassing NSOpenGLView was taken from "TextureUpload" Apple's sample + */ + +#import + +#import "EAGLView.h" +#import "CCEGLView.h" +#import +#import "CCDirector.h" +#import "ccConfig.h" +#import "CCSet.h" +#import "CCTouch.h" +#import "CCIMEDispatcher.h" +#import "CCWindow.h" +#import "CCEventDispatcher.h" + + +//USING_NS_CC; +static EAGLView *view; + +@implementation EAGLView + +@synthesize eventDelegate = eventDelegate_, isFullScreen = isFullScreen_; + ++(id) sharedEGLView +{ + return view; +} + +- (id) initWithFrame:(NSRect)frameRect +{ + self = [self initWithFrame:frameRect shareContext:nil]; + return self; +} + +- (id) initWithFrame:(NSRect)frameRect shareContext:(NSOpenGLContext*)context +{ + NSOpenGLPixelFormatAttribute attribs[] = + { +// NSOpenGLPFAAccelerated, +// NSOpenGLPFANoRecovery, + NSOpenGLPFADoubleBuffer, + NSOpenGLPFADepthSize, 24, + + 0 + }; + + NSOpenGLPixelFormat *pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs]; + + if (!pixelFormat) + NSLog(@"No OpenGL pixel format"); + + if( (self = [super initWithFrame:frameRect pixelFormat:[pixelFormat autorelease]]) ) { + + if( context ) + [self setOpenGLContext:context]; + + // event delegate + eventDelegate_ = [CCEventDispatcher sharedDispatcher]; + } + + view = self; + return self; +} + +- (void) update +{ + // XXX: Should I do something here ? + [super update]; +} + +- (void) prepareOpenGL +{ + // XXX: Initialize OpenGL context + + [super prepareOpenGL]; + + // Make this openGL context current to the thread + // (i.e. all openGL on this thread calls will go to this context) + [[self openGLContext] makeCurrentContext]; + + // Synchronize buffer swaps with vertical refresh rate + GLint swapInt = 1; + [[self openGLContext] setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; + +// GLint order = -1; +// [[self openGLContext] setValues:&order forParameter:NSOpenGLCPSurfaceOrder]; +} + +- (NSUInteger) depthFormat +{ + return 24; +} + +- (void) reshape +{ + // We draw on a secondary thread through the display link + // When resizing the view, -reshape is called automatically on the main thread + // Add a mutex around to avoid the threads accessing the context simultaneously when resizing + + [self lockOpenGLContext]; + + NSRect rect = [self bounds]; + + cocos2d::CCDirector *director = cocos2d::CCDirector::sharedDirector(); + CGSize size = NSSizeToCGSize(rect.size); + cocos2d::CCSize ccsize = cocos2d::CCSizeMake(size.width, size.height); + director->reshapeProjection(ccsize); + + // avoid flicker + director->drawScene(); +// [self setNeedsDisplay:YES]; + + [self unlockOpenGLContext]; +} + +-(void) lockOpenGLContext +{ + NSOpenGLContext *glContext = [self openGLContext]; + NSAssert( glContext, @"FATAL: could not get openGL context"); + + [glContext makeCurrentContext]; + CGLLockContext((CGLContextObj)[glContext CGLContextObj]); +} + +-(void) unlockOpenGLContext +{ + NSOpenGLContext *glContext = [self openGLContext]; + NSAssert( glContext, @"FATAL: could not get openGL context"); + + CGLUnlockContext((CGLContextObj)[glContext CGLContextObj]); +} + +- (void) dealloc +{ + CCLOGINFO(@"cocos2d: deallocing %@", self); + + [super dealloc]; +} + +-(int) getWidth +{ + NSSize bound = [self bounds].size; + return bound.width; +} + +-(int) getHeight +{ + NSSize bound = [self bounds].size; + return bound.height; +} + +-(void) swapBuffers +{ +} + +// +// setFullScreen code taken from GLFullScreen example by Apple +// +- (void) setFullScreen:(BOOL)fullscreen +{ + // Mac OS X 10.6 and later offer a simplified mechanism to create full-screen contexts +#if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_5 + + if (isFullScreen_ == fullscreen) + return; + + EAGLView *openGLview = [[self class] sharedEGLView]; + + if( fullscreen ) { + originalWinRect_ = [openGLview frame]; + + // Cache normal window and superview of openGLView + if(!windowGLView_) + windowGLView_ = [[openGLview window] retain]; + + [superViewGLView_ release]; + superViewGLView_ = [[openGLview superview] retain]; + + + // Get screen size + NSRect displayRect = [[NSScreen mainScreen] frame]; + + // Create a screen-sized window on the display you want to take over + fullScreenWindow_ = [[CCWindow alloc] initWithFrame:displayRect fullscreen:YES]; + + // Remove glView from window + [openGLview removeFromSuperview]; + + // Set new frame + [openGLview setFrame:displayRect]; + + // Attach glView to fullscreen window + [fullScreenWindow_ setContentView:openGLview]; + + // Show the fullscreen window + [fullScreenWindow_ makeKeyAndOrderFront:self]; + [fullScreenWindow_ makeMainWindow]; + + } else { + + // Remove glView from fullscreen window + [openGLview removeFromSuperview]; + + // Release fullscreen window + [fullScreenWindow_ release]; + fullScreenWindow_ = nil; + + // Attach glView to superview + [superViewGLView_ addSubview:openGLview]; + + // Set new frame + [openGLview setFrame:originalWinRect_]; + + // Show the window + [windowGLView_ makeKeyAndOrderFront:self]; + [windowGLView_ makeMainWindow]; + } + + // issue #1189 + [windowGLView_ makeFirstResponder:openGLview]; + + isFullScreen_ = fullscreen; + + [openGLview retain]; // Retain +1 + + // is this necessary? + // re-configure glView + //cocos2d::CCDirector *director = cocos2d::CCDirector::sharedDirector(); + //director->setOpenGLView(openGLview); //[self setView:openGLview]; + + [openGLview release]; // Retain -1 + + [openGLview setNeedsDisplay:YES]; +#else +#error Full screen is not supported for Mac OS 10.5 or older yet +#error If you don't want FullScreen support, you can safely remove these 2 lines +#endif +} + +#if CC_DIRECTOR_MAC_USE_DISPLAY_LINK_THREAD +#define DISPATCH_EVENT(__event__, __selector__) [eventDelegate_ queueEvent:__event__ selector:__selector__]; +#else +#define DISPATCH_EVENT(__event__, __selector__) \ + id obj = eventDelegate_; \ + [obj performSelector:__selector__ \ + onThread:[(cocos2d::CCDirectorMac*)[CCDirector sharedDirector] runningThread] \ + withObject:__event__ \ + waitUntilDone:NO]; +#endif + +#pragma mark EAGLView - Mouse events + +- (void)mouseDown:(NSEvent *)theEvent +{ + NSPoint event_location = [theEvent locationInWindow]; + NSPoint local_point = [self convertPoint:event_location fromView:nil]; + + float x = local_point.x; + float y = [self getHeight] - local_point.y; + + int ids[1] = {0}; + float xs[1] = {0.0f}; + float ys[1] = {0.0f}; + + ids[0] = (int)theEvent; + xs[0] = x; + ys[0] = y; + + cocos2d::CCDirector::sharedDirector()->getOpenGLView()->handleTouchesBegin(1, ids, xs, ys); +} + +- (void)mouseMoved:(NSEvent *)theEvent +{ + DISPATCH_EVENT(theEvent, _cmd); +} + +- (void)mouseDragged:(NSEvent *)theEvent +{ + NSPoint event_location = [theEvent locationInWindow]; + NSPoint local_point = [self convertPoint:event_location fromView:nil]; + + float x = local_point.x; + float y = [self getHeight] - local_point.y; + + int ids[1] = {0}; + float xs[1] = {0.0f}; + float ys[1] = {0.0f}; + + ids[0] = (int)theEvent; + xs[0] = x; + ys[0] = y; + + cocos2d::CCDirector::sharedDirector()->getOpenGLView()->handleTouchesMove(1, ids, xs, ys); +} + +- (void)mouseUp:(NSEvent *)theEvent +{ + NSPoint event_location = [theEvent locationInWindow]; + NSPoint local_point = [self convertPoint:event_location fromView:nil]; + + float x = local_point.x; + float y = [self getHeight] - local_point.y; + + int ids[1] = {0}; + float xs[1] = {0.0f}; + float ys[1] = {0.0f}; + + ids[0] = (int)theEvent; // index? + xs[0] = x; + ys[0] = y; + + cocos2d::CCDirector::sharedDirector()->getOpenGLView()->handleTouchesEnd(1, ids, xs, ys); +} + +- (void)rightMouseDown:(NSEvent *)theEvent { + DISPATCH_EVENT(theEvent, _cmd); +} + +- (void)rightMouseDragged:(NSEvent *)theEvent { + DISPATCH_EVENT(theEvent, _cmd); +} + +- (void)rightMouseUp:(NSEvent *)theEvent { + DISPATCH_EVENT(theEvent, _cmd); +} + +- (void)otherMouseDown:(NSEvent *)theEvent { + DISPATCH_EVENT(theEvent, _cmd); +} + +- (void)otherMouseDragged:(NSEvent *)theEvent { + DISPATCH_EVENT(theEvent, _cmd); +} + +- (void)otherMouseUp:(NSEvent *)theEvent { + DISPATCH_EVENT(theEvent, _cmd); +} + +- (void)mouseEntered:(NSEvent *)theEvent { + DISPATCH_EVENT(theEvent, _cmd); +} + +- (void)mouseExited:(NSEvent *)theEvent { + DISPATCH_EVENT(theEvent, _cmd); +} + +-(void) scrollWheel:(NSEvent *)theEvent { + DISPATCH_EVENT(theEvent, _cmd); +} + +#pragma mark EAGLView - Key events + +-(BOOL) becomeFirstResponder +{ + return YES; +} + +-(BOOL) acceptsFirstResponder +{ + return YES; +} + +-(BOOL) resignFirstResponder +{ + return YES; +} + +- (void)keyDown:(NSEvent *)theEvent +{ + DISPATCH_EVENT(theEvent, _cmd); +} + +- (void)keyUp:(NSEvent *)theEvent +{ + DISPATCH_EVENT(theEvent, _cmd); +} + +- (void)flagsChanged:(NSEvent *)theEvent +{ + DISPATCH_EVENT(theEvent, _cmd); +} + +#pragma mark EAGLView - Touch events +- (void)touchesBeganWithEvent:(NSEvent *)theEvent +{ + DISPATCH_EVENT(theEvent, _cmd); +} + +- (void)touchesMovedWithEvent:(NSEvent *)theEvent +{ + DISPATCH_EVENT(theEvent, _cmd); +} + +- (void)touchesEndedWithEvent:(NSEvent *)theEvent +{ + DISPATCH_EVENT(theEvent, _cmd); +} + +- (void)touchesCancelledWithEvent:(NSEvent *)theEvent +{ + DISPATCH_EVENT(theEvent, _cmd); +} +@end