CocosDenshion SimpleAudioEngine doesn't support Pitch, Pan, Gain

Just messing with sound effects and I noticed CocosDenshion SimpleAudioEngine doesn’t support the Pitch Pan and Gain parameters, could this be added to the next build?

I tried adding them myself and it works fine, like so:

In SimpleAudioEngine.h:-

the line:

unsigned int playEffect(const char* pszFilePath, bool bLoop = false);

becomes:

unsigned int playEffect(const char* pszFilePath, bool bLoop = false, float fPitch=1.0f, float fPan=0.0f, float fGain=1.0f);

In SimpleAudioEngine.mm:-

function:

static unsigned int static_playEffect(const char* pszFilePath, bool bLoop)
{
    return [[SimpleAudioEngine sharedEngine] playEffect:[NSString stringWithUTF8String: pszFilePath] loop:bLoop]; 
}

becomes:

static unsigned int static_playEffect(const char* pszFilePath, bool bLoop, float fPitch, float fPan, float fGain )
{
    return [[SimpleAudioEngine sharedEngine] playEffect:[NSString stringWithUTF8String: pszFilePath] loop:bLoop pitch:fPitch pan:fPan gain:fGain ];
}

function:

unsigned int SimpleAudioEngine::playEffect(const char* pszFilePath, bool bLoop)
{
            return static_playEffect(pszFilePath, bLoop);
}

becomes:

unsigned int SimpleAudioEngine::playEffect(const char* pszFilePath, bool bLoop, float fPitch, float fPan, float fGain)
{
            return static_playEffect(pszFilePath, bLoop, fPitch, fPan, fGain);
}

Well… I guess that the same should be added to the other platforms then (and of course it is not a simple thing)

Yes, it is easy on iOS. But not so easy on other platforms.
I have created #886, but I don’t know if we can implement it.

It’s also easy on Android. I just did it and it works fine. You just need to modify:

CocosDenshion/Android/SimpleAudioEngine.cpp

before

unsigned int SimpleAudioEngine::playEffect(const char* pszFilePath, bool bLoop)
{
    return playEffectJNI(pszFilePath, bLoop);
}

after

unsigned int SimpleAudioEngine::playEffect(const char* pszFilePath, bool bLoop, float pitch, float pan, float gain)
{
    return playEffectJNI(pszFilePath, bLoop, pitch, pan, gain);
}

CocosDenshion/Android/jni/SimpleAudioEngineJni.cpp

before

unsigned int playEffectJNI(const char* path, bool bLoop)
{
    // int playEffect(String)

    JniMethodInfo methodInfo;
    int ret = 0;

    if (! getStaticMethodInfo(methodInfo, "playEffect", "(Ljava/lang/String;Z)I"))
    {
        return ret;
    }

    jstring stringArg = methodInfo.env->NewStringUTF(path);
    ret = methodInfo.env->CallStaticIntMethod(methodInfo.classID, methodInfo.methodID, stringArg, bLoop);
    methodInfo.env->DeleteLocalRef(stringArg);
    methodInfo.env->DeleteLocalRef(methodInfo.classID);

    return (unsigned int)ret;
}

after

unsigned int playEffectJNI(const char* path, bool bLoop, float pitch, float pan, float gain)
{
    // int playEffect(String)

    JniMethodInfo methodInfo;
    int ret = 0;

    if (! getStaticMethodInfo(methodInfo, "playEffect", "(Ljava/lang/String;ZFFF)I"))
    {
        return ret;
    }

    jstring stringArg = methodInfo.env->NewStringUTF(path);
    ret = methodInfo.env->CallStaticIntMethod(methodInfo.classID, methodInfo.methodID, stringArg, bLoop, pitch, pan, gain);
    methodInfo.env->DeleteLocalRef(stringArg);
    methodInfo.env->DeleteLocalRef(methodInfo.classID);

    return (unsigned int)ret;
}

CocosDenshion/Android/jni/SimpleAudioEngineJni.h

before

extern unsigned int playEffectJNI(const char* path, bool bLoop);

after

extern unsigned int playEffectJNI(const char* path, bool bLoop, float pitch, float pan, float gain);

/src/org/cocos2dx/lib/Cocos2dxActivity.java

before

public static int playEffect(String path, boolean isLoop){
        return soundPlayer.playEffect(path, isLoop);
}

after

public static int playEffect(String path, boolean isLoop, float pitch, float pan, float gain){
        return soundPlayer.playEffect(path, isLoop, pitch, pan, gain);
}

/src/org/cocos2dx/lib/Cocos2dxSound.java

before

public int playEffect(String path, boolean isLoop){
    Integer soundId = this.mPathSoundIDMap.get(path);

    if (soundId != null){
        // the sound is preloaded, stop it first

        this.mSoundPool.stop(soundId);

        // play sound
        int streamId = this.mSoundPool.play(soundId.intValue(), this.mLeftVolume, this.mRightVolume, SOUND_PRIORITY, isLoop ? -1 : 0, SOUND_RATE);

        // record sound id and stream id map
        this.mSoundIdStreamIdMap.put(soundId, streamId);
    } else {
        // the effect is not prepared
        soundId = preloadEffect(path);  
        if (soundId == INVALID_SOUND_ID){
            // can not preload effect
            return INVALID_SOUND_ID;
        }

        /*
         * Someone reports that, it can not play effect for the
         * first time. If you are lucky to meet it. There are two
         * ways to resolve it.
         * 1. Add some delay here. I don't know how long it is, so
         *    I don't add it here.
         * 2. If you use 2.2(API level 8), you can call 
         *    SoundPool.setOnLoadCompleteListener() to play the effect.
         *    Because the method is supported from 2.2, so I can't use
         *    it here.
         */
        playEffect(path, isLoop);
    }

    return soundId.intValue();
}

after

public int playEffect(String path, boolean isLoop, float pitch, float pan, float gain){
    Integer soundId = this.mPathSoundIDMap.get(path);

    if (soundId != null){
        // the sound is preloaded, stop it first

        this.mSoundPool.stop(soundId);

        // parameters; pan = -1 for left channel, 1 for right channel, 0 for both channels
        float leftVolume = Math.max(0.0f, Math.min(1.0f, this.mLeftVolume * gain * Math.max(0.0f, Math.min(1.0f, 1.0f - pan))));
        float rightVolume = Math.max(0.0f, Math.min(1.0f, this.mRightVolume * gain * Math.max(0.0f, Math.min(1.0f, 1.0f + pan))));
        float soundRate = Math.max(0.5f, Math.min(2.0f, SOUND_RATE * pitch));

        // play sound
        int streamId = this.mSoundPool.play(soundId.intValue(), leftVolume, rightVolume, SOUND_PRIORITY, isLoop ? -1 : 0, soundRate);

        // record sound id and stream id map
        this.mSoundIdStreamIdMap.put(soundId, streamId);
    } else {
        // the effect is not prepared
        soundId = preloadEffect(path);  
        if (soundId == INVALID_SOUND_ID){
            // can not preload effect
            return INVALID_SOUND_ID;
        }

        /*
         * Someone reports that, it can not play effect for the
         * first time. If you are lucky to meet it. There are two
         * ways to resolve it.
         * 1. Add some delay here. I don't know how long it is, so
         *    I don't add it here.
         * 2. If you use 2.2(API level 8), you can call 
         *    SoundPool.setOnLoadCompleteListener() to play the effect.
         *    Because the method is supported from 2.2, so I can't use
         *    it here.
         */
        playEffect(path, isLoop, pitch, pan, gain);
    }

    return soundId.intValue();
}
1 Like

Thank you.

Can this get added to the next Cocos2D-X release? I’m sure others would find it useful.

+1 to add this in next release :slight_smile:

This feature should be added into code base.
What if you played a shooting game with a headset?

hi

in cocos2dx2, the Cocos2dxActivity.java have not cocosdenshion’s functions like:

public static int playEffect(String path, boolean isLoop){
return soundPlayer.playEffect(path, isLoop);
}

i want to know were are this functions?

thanks

found it,
this are in Cocos2dxHelper.java

thanks everyone…

If someone gets stuck I just solved this for us using 2.2.5. There has been a few more structural changes in 2.2.5 in the Android code (java side in particular. someday we’ll get to v3… someday…) I’ll grab the delta when I check in and share that. Best I can do in the current rush! Thanks to the earlier posts for making this easier to accomplish.

Ok’s It’s a little large… and frankly to much a time sink to make presentable. If someone wants it ping me.
(ask for a raw dump of commit 9b621dc) :wink: