0

Positional audio with cocos2d and CocosDenshion

-

If you are creating a game project using the cocos2d XCode project template, there’s little reason not to use CocosDenshion for your audio needs. It’s already there, requires minimal configuration, it’s easy to use and it allows you to go as low-level as you need. One thing that CocosDenshion does not provide out of the box however is support for positional audio. Actually, the underlying audio API OpenAL does support full 3D audio, and you could get direct access to the API through CocosDenshion to take advantage of it, but for a 2D game this can be a major overkill. Positional audio can be simulated in 2D scenarios by simply manipulating the pan and gain of a sound source in relation to a listener, and that’s exactly what we’ll do in this piece of code that I’m about to share.

Where is this I hear?

The term itself is pretty self-explanatory, in that positional audio is the simulation of the ability that we humans have to judge the approximate distance and direction of a sound source, and also the physical properties of sound based on the environment. This effect is much better represented in surround sound systems with dedicated hardware but it can be “faked” in stereo systems using much simpler tricks.

CDXAudioNode

In keeping with the naming convention used in the CocosDenshion project, for our class we’ll use the CDX prefix to indicate that it depends on cocos2d (even though CocosDenshion comes with the cocos2d package, it’s entirely independent). We’ll be subverting the cocos2d API a little bit in order to use a CCNode to represent a sound source in our scene. This provides many advantages to us, such as adding the audio node as the child of a sprite, or even animating a sound source’s position using a CCAction, and you can also provide another CCNode to act as the listener position of a sound source (if none is provided, the default is the center of the screen). Another feature of the CDXAudioNode is to set a loop mode, including a “periodic” loop which can be very useful for ambient audio. We’ll see all of this in detail below.

  1. /**
  2.  Initializes the audio node with an already created sound buffer, identified by
  3.  the sourceId.
  4.  */
  5. - (id)initWithSoundEngine:(CDSoundEngine *)se sourceId:(int)sId;
  6.  
  7. /**
  8.  Initializes the audio node with an audio file, creating a sound buffer with the
  9.  specified sourceId.
  10.  */
  11. - (id)initWithFile:(NSString *)file soundEngine:(CDSoundEngine *)se sourceId:(int)sId;
/**
 Initializes the audio node with an already created sound buffer, identified by
 the sourceId.
 */
- (id)initWithSoundEngine:(CDSoundEngine *)se sourceId:(int)sId;

/**
 Initializes the audio node with an audio file, creating a sound buffer with the
 specified sourceId.
 */
- (id)initWithFile:(NSString *)file soundEngine:(CDSoundEngine *)se sourceId:(int)sId;

There are two ways to create a CDXAudioNode: you can either load the audio buffers yourself and just provide the source ID, or you can have the node load it’s own audio buffer by providing a file name. The former is useful in cases where you’re loading the audio buffers asynchronously or want to have more control over the loading process. Both of these constructors depend on having a CDSoundEngine already created and properly initialized.

  1. - (void)visit {
  2.     CGPoint realPos = [self convertToWorldSpace:CGPointZero];
  3.  
  4.     CGSize size = [[CCDirector sharedDirector] winSize];
  5.  
  6.     CGPoint earPos = ccp(size.width / 2, size.height / 2);
  7.     if (earNode) {
  8.         earPos = [earNode convertToWorldSpace:CGPointZero];
  9.     }
  10.  
  11.     float dist = sqrt((realPos.x - earPos.x) * (realPos.x - earPos.x) + (realPos.y - earPos.y) * (realPos.y - earPos.y));
  12.  
  13.     float distX = realPos.x - earPos.x;
  14.  
  15.     sound.pan = distX / (size.width / 2);
  16.  
  17.     float gain = 1.0f - (dist * attenuation);
  18.  
  19.     if (gain < 0.0f) gain = 0.0f;     if (gain > 1.0f) gain = 1.0f;
  20.  
  21.     sound.gain = gain;
  22.  
  23.     [super visit];
  24. }
- (void)visit {
    CGPoint realPos = [self convertToWorldSpace:CGPointZero];

    CGSize size = [[CCDirector sharedDirector] winSize];

    CGPoint earPos = ccp(size.width / 2, size.height / 2);
    if (earNode) {
        earPos = [earNode convertToWorldSpace:CGPointZero];
    }

    float dist = sqrt((realPos.x - earPos.x) * (realPos.x - earPos.x) + (realPos.y - earPos.y) * (realPos.y - earPos.y));

    float distX = realPos.x - earPos.x;

    sound.pan = distX / (size.width / 2);

    float gain = 1.0f - (dist * attenuation);

    if (gain < 0.0f) gain = 0.0f;     if (gain > 1.0f) gain = 1.0f;

    sound.gain = gain;

    [super visit];
}

All the magic happens in the visit method. This method is usually called every frame to perform drawing operations in a CCNode, but in this case we are hijacking it to calculate the distance between the ear node (or the center of the screen if it was not defined) and the sound source and then applying the necessary changes to the audio parameters.

Usage

  1. // Sound initialization
  2.  
  3. [CDSoundEngine setMixerSampleRate:CD_SAMPLE_RATE_MID];
  4. [CDAudioManager initAsynchronously:kAMM_FxPlusMusicIfNoOtherAudio];
  5.  
  6. // Waits for initialization (BAD way to do it, used here for simplicity's sake!)
  7. while ([CDAudioManager sharedManagerState] != kAMStateInitialised) {}
  8.  
  9. am = [CDAudioManager sharedManager];
  10. soundEngine = [CDAudioManager sharedManager].soundEngine;
  11.  
  12. ...
  13.  
  14. CDXAudioNode *audioNode = [CDXAudioNode audioNodeWithFile:@"808_120bpm.caf" soundEngine:soundEngine sourceId:1];
  15. audioNode.earNode = earSprite;
  16. audioNode.playMode = kAudioNodeLoop;
  17. [audioNode play];
// Sound initialization

[CDSoundEngine setMixerSampleRate:CD_SAMPLE_RATE_MID];
[CDAudioManager initAsynchronously:kAMM_FxPlusMusicIfNoOtherAudio];

// Waits for initialization (BAD way to do it, used here for simplicity's sake!)
while ([CDAudioManager sharedManagerState] != kAMStateInitialised) {}

am = [CDAudioManager sharedManager];
soundEngine = [CDAudioManager sharedManager].soundEngine;

...

CDXAudioNode *audioNode = [CDXAudioNode audioNodeWithFile:@"808_120bpm.caf" soundEngine:soundEngine sourceId:1];
audioNode.earNode = earSprite;
audioNode.playMode = kAudioNodeLoop;
[audioNode play];

After initializing the sound engine, you create your CDXAudioNode using one of the constructors mentioned before. Then you can define a listener using the earNode property and also set the play mode, which can have one of the following values:

  • kAudioNodeSinglePlay – the sound will be played once the play method is called.
  • kAudioNodeLoop – the sound will loop indefinitely, until the pause or stop methods are called.
  • kAudioNodePeriodicLoop – in this mode, the sound will play again every x seconds, where x is a random number between minLoopFrequency and maxLoopFrequency.

You can also play around with the attenuation property, which influences the maximum distance the sound source can be heard from.

Sample project

EDIT: the code is now available on GitHub

In this sample project, there are 4 sprites which you can drag around, where one is the listener node and the others are sound sources illustrating each of the loop modes available. The code can probably be much inproved, so feel free to use it in your own projects or improve it as needed :)

0

Category For Easily Adding Gesture Recognizers In Cocos2D v1 and v2

-

Cocos2D is a great library, and there are several wrappers out there for adding functionality contained within the UIKit into Cocos2D games.

Shortly after gesture recognizers were added to the iOS SDK some wrapper code was released so Cocos2D developers could use gesture recongizers within their games, but this required using a different syntax, and modifying Cocos2D code.

Today I came across a nice category from Krystof Zablocki that can be used with CCNode in both Cocos2D for adding gesture recognizers in Cocos2D games while using the same syntax as a UIGestureRecognizer.

Two versions are provided for both Cocos2D v1 and V2.

You can read more about the category on Kryztof’s website here.

You can find the category and an example project on Github here.

0

LearnCocosTV – Episode #3: Two And A Half Nuts

-

After a holiday-season hiatus (Happy New Year btw!) LearnCocosTV is back. This episode is somewhat shorter because I had a lot of catching up to do and a lot of chores which aren’t exactly show-worthy. But I did manage to port most of the Kobold2D projects to Cocos2D 2.0 beta. Too bad they look just the same as before.

A bi-weekly Show & Tell about Cocos2D, Kobold2D and iOS/OSX development by Steffen Itterheim.

Episode #3 – Two And A Half Nuts

• Updating Kobold2D to use Cocos2D v2.0 (beta)
• iDevBlogADay: Tips for updating to Cocos2D v2.0
• iDevBlogADay Source Code available on github

0

LearnCocosTV – Episode 2: Fixing Bad

-

Another live report from the front…

LearnCocosTV – Episode 2: Fixing Bad

• Simple Multiplayer Data Sharing Project
• iDevBlogADay: Fast Pixel-Perfect Collision Detection
• Kobold2D 1.0.1 Released:
o KKPixelMaskSprite, KKScreenshot, Ad Banner rotation
o Solutions for “failed with exit code 1” linker errors
• Xcode Trips & Ticks

0

LearnCocosTV – Episode 1: How I maed your Kobold

-

LearnCocosTV is sort of like a personal sprint review presentation but in video form.

I think that just writing about what I’ve done recently would be rather dull, whereas a bi-weeklyShow & Tell video would not only be more interesting, it is also much more encouraging for me to create something cool to show in the first place!

Each episode will update you about what I’ve done for or with Cocos2D, Kobold2D and iOS/OSX development in general, what I’ve learned in the process and what the end results are. Here’s episode one, I hope you’ll enjoy it:

LearnCocosTV – Episode 1: How I maed your Kobold

• Kobold2D 1.0 Released
• Kobold2D source code published on github
• First Kobold2D games published on App Store
• Kobold2D Server moved
• iDevBlogADay: How to use CCRenderTexture…

And no, I’m most certainly not going to run out of TV Show titles to vilify. :D

1 2 3 »