2013-04-30 08:13:24 +08:00
|
|
|
/****************************************************************************
|
2013-05-21 11:09:56 +08:00
|
|
|
Copyright (c) 2013 Zynga Inc.
|
2013-04-30 08:13:24 +08:00
|
|
|
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.
|
|
|
|
****************************************************************************/
|
|
|
|
|
2013-05-21 11:09:56 +08:00
|
|
|
// XXX: This is all just a bit of a hack. SDL uses channels as its underlying
|
|
|
|
// abstraction, whilst CocosDenshion deals in individual effects. Consequently
|
|
|
|
// we can't set start/stop, because to SDL, effects (chunks) are just opaque
|
|
|
|
// blocks of data that get scheduled on channels. To workaround this, we assign
|
|
|
|
// each sound to a channel, but since there are only 32 channels, we use the
|
|
|
|
// modulus of the sound's address (which on Emscripten is just an incrementing
|
|
|
|
// integer) to decide which channel.
|
|
|
|
//
|
|
|
|
// A more rigorous implementation would have logic to store the state of
|
|
|
|
// channels and restore it as necessary. This should probably just be
|
|
|
|
// considered a toy for now, but it will probably prove sufficient for many
|
|
|
|
// use-cases. Recall also that Emscripten undoes this abstraction on the other
|
|
|
|
// side because it is using HTML5 audio objects as its underlying primitive!
|
|
|
|
|
2013-04-30 08:13:24 +08:00
|
|
|
#include <map>
|
|
|
|
#include <string>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
#include "SimpleAudioEngine.h"
|
|
|
|
#include "cocos2d.h"
|
2013-05-21 11:09:56 +08:00
|
|
|
|
|
|
|
#include <SDL/SDL.h>
|
|
|
|
#include <SDL/SDL_mixer.h>
|
|
|
|
|
2013-04-30 08:13:24 +08:00
|
|
|
USING_NS_CC;
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
|
|
|
namespace CocosDenshion
|
|
|
|
{
|
|
|
|
struct soundData {
|
2013-05-21 11:09:56 +08:00
|
|
|
Mix_Chunk *chunk;
|
2013-04-30 08:13:24 +08:00
|
|
|
bool isLooped;
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef map<string, soundData *> EffectsMap;
|
|
|
|
EffectsMap s_effects;
|
2013-05-21 11:09:56 +08:00
|
|
|
float s_effectsVolume = 1.0;
|
2013-04-30 08:13:24 +08:00
|
|
|
|
|
|
|
typedef enum {
|
|
|
|
PLAYING,
|
|
|
|
STOPPED,
|
|
|
|
PAUSED,
|
|
|
|
} playStatus;
|
|
|
|
|
|
|
|
struct backgroundMusicData {
|
2013-05-21 11:09:56 +08:00
|
|
|
Mix_Music *music;
|
2013-04-30 08:13:24 +08:00
|
|
|
};
|
|
|
|
typedef map<string, backgroundMusicData *> BackgroundMusicsMap;
|
|
|
|
BackgroundMusicsMap s_backgroundMusics;
|
2013-05-21 11:09:56 +08:00
|
|
|
float s_backgroundVolume = 1.0;
|
2013-04-30 08:13:24 +08:00
|
|
|
|
|
|
|
static SimpleAudioEngine *s_engine = 0;
|
|
|
|
|
2013-05-21 11:09:56 +08:00
|
|
|
// Unfortunately this is just hard-coded in Emscripten's SDL
|
|
|
|
// implementation.
|
|
|
|
static const int NR_CHANNELS = 32;
|
2013-04-30 08:13:24 +08:00
|
|
|
static void stopBackground(bool bReleaseData)
|
|
|
|
{
|
2013-05-22 07:37:24 +08:00
|
|
|
SimpleAudioEngine *engine = SimpleAudioEngine::sharedEngine();
|
|
|
|
engine->stopBackgroundMusic();
|
2013-04-30 08:13:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
SimpleAudioEngine::SimpleAudioEngine()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
SimpleAudioEngine::~SimpleAudioEngine()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
SimpleAudioEngine* SimpleAudioEngine::sharedEngine()
|
|
|
|
{
|
|
|
|
if (!s_engine)
|
|
|
|
s_engine = new SimpleAudioEngine();
|
|
|
|
|
|
|
|
return s_engine;
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleAudioEngine::end()
|
|
|
|
{
|
|
|
|
// clear all the sounds
|
|
|
|
EffectsMap::const_iterator end = s_effects.end();
|
|
|
|
for (EffectsMap::iterator it = s_effects.begin(); it != end; it++)
|
|
|
|
{
|
2013-05-22 07:37:24 +08:00
|
|
|
Mix_FreeChunk(it->second->chunk);
|
2013-04-30 08:13:24 +08:00
|
|
|
delete it->second;
|
|
|
|
}
|
|
|
|
s_effects.clear();
|
|
|
|
|
|
|
|
// and the background too
|
|
|
|
stopBackground(true);
|
|
|
|
|
|
|
|
for (BackgroundMusicsMap::iterator it = s_backgroundMusics.begin(); it != s_backgroundMusics.end(); ++it)
|
|
|
|
{
|
2013-05-22 07:37:24 +08:00
|
|
|
Mix_FreeMusic(it->second->music);
|
2013-04-30 08:13:24 +08:00
|
|
|
delete it->second;
|
|
|
|
}
|
|
|
|
s_backgroundMusics.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// background audio
|
|
|
|
//
|
|
|
|
void SimpleAudioEngine::preloadBackgroundMusic(const char* pszFilePath)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleAudioEngine::playBackgroundMusic(const char* pszFilePath, bool bLoop)
|
|
|
|
{
|
2013-05-21 11:09:56 +08:00
|
|
|
std::string key = std::string(pszFilePath);
|
|
|
|
struct backgroundMusicData *musicData;
|
|
|
|
if(!s_backgroundMusics.count(key))
|
|
|
|
{
|
|
|
|
musicData = new struct backgroundMusicData();
|
|
|
|
musicData->music = Mix_LoadMUS(pszFilePath);
|
|
|
|
s_backgroundMusics[key] = musicData;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
musicData = s_backgroundMusics[key];
|
|
|
|
}
|
|
|
|
|
|
|
|
Mix_PlayMusic(musicData->music, bLoop ? -1 : 0);
|
2013-04-30 08:13:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleAudioEngine::stopBackgroundMusic(bool bReleaseData)
|
|
|
|
{
|
2013-05-21 11:09:56 +08:00
|
|
|
Mix_HaltMusic();
|
2013-04-30 08:13:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleAudioEngine::pauseBackgroundMusic()
|
|
|
|
{
|
2013-05-21 11:09:56 +08:00
|
|
|
Mix_PauseMusic();
|
2013-04-30 08:13:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleAudioEngine::resumeBackgroundMusic()
|
|
|
|
{
|
2013-05-21 11:09:56 +08:00
|
|
|
Mix_ResumeMusic();
|
2013-04-30 08:13:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleAudioEngine::rewindBackgroundMusic()
|
|
|
|
{
|
2013-05-22 07:37:24 +08:00
|
|
|
CCLOGWARN("Cannot rewind background in Emscripten");
|
2013-04-30 08:13:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
bool SimpleAudioEngine::willPlayBackgroundMusic()
|
|
|
|
{
|
2013-05-21 11:09:56 +08:00
|
|
|
return true;
|
2013-04-30 08:13:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
bool SimpleAudioEngine::isBackgroundMusicPlaying()
|
|
|
|
{
|
2013-05-21 11:09:56 +08:00
|
|
|
return Mix_PlayingMusic();
|
2013-04-30 08:13:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
float SimpleAudioEngine::getBackgroundMusicVolume()
|
|
|
|
{
|
2013-05-21 11:09:56 +08:00
|
|
|
return s_backgroundVolume;
|
2013-04-30 08:13:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleAudioEngine::setBackgroundMusicVolume(float volume)
|
|
|
|
{
|
2013-05-21 11:09:56 +08:00
|
|
|
// Ensure volume is between 0.0 and 1.0.
|
|
|
|
volume = volume > 1.0 ? 1.0 : volume;
|
|
|
|
volume = volume < 0.0 ? 0.0 : volume;
|
|
|
|
|
|
|
|
Mix_VolumeMusic(volume * MIX_MAX_VOLUME);
|
|
|
|
s_backgroundVolume = volume;
|
2013-04-30 08:13:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
float SimpleAudioEngine::getEffectsVolume()
|
|
|
|
{
|
2013-05-21 11:09:56 +08:00
|
|
|
return s_effectsVolume;
|
2013-04-30 08:13:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleAudioEngine::setEffectsVolume(float volume)
|
|
|
|
{
|
2013-05-21 11:09:56 +08:00
|
|
|
volume = volume > 1.0 ? 1.0 : volume;
|
|
|
|
volume = volume < 0.0 ? 0.0 : volume;
|
|
|
|
|
|
|
|
// Need to set volume on every channel. SDL will then read this volume
|
|
|
|
// level and apply it back to the individual sample.
|
|
|
|
for(int i = 0; i < NR_CHANNELS; i++)
|
|
|
|
{
|
|
|
|
Mix_Volume(i, volume * MIX_MAX_VOLUME);
|
|
|
|
}
|
|
|
|
|
|
|
|
s_effectsVolume = volume;
|
2013-04-30 08:13:24 +08:00
|
|
|
}
|
|
|
|
|
2013-06-03 20:11:00 +08:00
|
|
|
unsigned int SimpleAudioEngine::playEffect(const char* pszFilePath, bool bLoop,
|
|
|
|
float pitch, float pan, float gain)
|
2013-04-30 08:13:24 +08:00
|
|
|
{
|
2013-05-21 11:09:56 +08:00
|
|
|
std::string key = std::string(pszFilePath);
|
|
|
|
struct soundData *sound;
|
|
|
|
if(!s_effects.count(key))
|
|
|
|
{
|
|
|
|
sound = new struct soundData();
|
|
|
|
sound->chunk = Mix_LoadWAV(pszFilePath);
|
|
|
|
sound->isLooped = bLoop;
|
|
|
|
s_effects[key] = sound;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
sound = s_effects[key];
|
|
|
|
}
|
|
|
|
// This is safe here since Emscripten is just passing back an
|
|
|
|
// incrementing integer each time you use the Mix_LoadWAV method.
|
|
|
|
unsigned int result = (unsigned int) sound->chunk;
|
|
|
|
|
|
|
|
// XXX: This is a bit of a hack, but... Choose a channel based on the
|
|
|
|
// modulo of the # of channels. This allows us to set the volume
|
|
|
|
// without passing around both chunk address and channel.
|
|
|
|
Mix_PlayChannel(result % NR_CHANNELS, sound->chunk, bLoop ? -1 : 0);
|
|
|
|
|
|
|
|
return result;
|
2013-04-30 08:13:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleAudioEngine::stopEffect(unsigned int nSoundId)
|
|
|
|
{
|
2013-05-21 11:09:56 +08:00
|
|
|
Mix_HaltChannel(nSoundId % NR_CHANNELS);
|
2013-04-30 08:13:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleAudioEngine::preloadEffect(const char* pszFilePath)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleAudioEngine::unloadEffect(const char* pszFilePath)
|
|
|
|
{
|
2013-05-22 07:37:24 +08:00
|
|
|
std::string key = std::string(pszFilePath);
|
|
|
|
if(!s_effects.count(key))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct soundData *sound = s_effects[key];
|
|
|
|
|
|
|
|
Mix_FreeChunk(sound->chunk);
|
|
|
|
delete sound;
|
|
|
|
s_effects.erase(key);
|
2013-04-30 08:13:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleAudioEngine::pauseEffect(unsigned int nSoundId)
|
|
|
|
{
|
2013-05-21 11:09:56 +08:00
|
|
|
Mix_Pause(nSoundId % NR_CHANNELS);
|
2013-04-30 08:13:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleAudioEngine::pauseAllEffects()
|
|
|
|
{
|
2013-05-21 11:09:56 +08:00
|
|
|
for(int i = 0; i < NR_CHANNELS; i++)
|
|
|
|
{
|
|
|
|
Mix_Pause(i);
|
|
|
|
}
|
2013-04-30 08:13:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleAudioEngine::resumeEffect(unsigned int nSoundId)
|
|
|
|
{
|
2013-05-21 11:09:56 +08:00
|
|
|
Mix_Resume(nSoundId % NR_CHANNELS);
|
2013-04-30 08:13:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleAudioEngine::resumeAllEffects()
|
|
|
|
{
|
2013-05-21 11:09:56 +08:00
|
|
|
for(int i = 0; i < NR_CHANNELS; i++)
|
|
|
|
{
|
|
|
|
Mix_Resume(i);
|
|
|
|
}
|
2013-04-30 08:13:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void SimpleAudioEngine::stopAllEffects()
|
|
|
|
{
|
2013-05-21 11:09:56 +08:00
|
|
|
for(int i = 0; i < NR_CHANNELS; i++)
|
|
|
|
{
|
|
|
|
Mix_HaltChannel(i);
|
|
|
|
}
|
2013-04-30 08:13:24 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|