Thanks for the tipoff on "thunking" Shannon. I'm not exactly sure I completely understand it, but it seems to have a few different meanings. Most of these seem to mean something to do with swapping functionality dynamically during runtime. I think I may be reading your meaning correctly in assuming you mean a ninja way of protecting instantiation would be to stop returning pointers to the singleton after a couple calls to the instance or something similar to protect the thing from excessive instance grabbing.
In any case, I've made my peace with it I think. I kind of had a good idea that there wasn't another solid option in this particular instance. My reasons for the singleton are sound (I believe) and that is really what's important. Any suggestions on a specific method of protecting instantiation would be cool.
For now I'm going to link the finished SDL_Mixer audio wrapper, anyone who is interested is free to use it. I'd love to know if it comes in handy, just e-mail me about it if it does. :)
Also, suggestions on the code are welcome, this is the audio part of my primary portfolio demo code I'm using to apply for a game job. I won't be able to do a huge overhaul, but specific suggestions are welcome. I've got my 2d rendering system done as well using the composite and command patterns primarily. I may link that later and change the title of this thread, but for now:
sound.h
/**********************************************************\| Michael Hamilton (maxmike@gmail.com) www.mutedvision.net ||----------------------------------------------------------|| Use this code as you like. Please do not remove this || comment (though if you do additions you may add to it). || I love to hear from people who use my code so please do || email me if this ends up finding its way into a project. || It encourages the release of more code if it is used. |\**********************************************************/#ifndef __SOUND_H__#define __SOUND_H__#include "SDL\SDL_mixer.h"#include <string>#include <map>#include <list>#include <vector>#include <algorithm>class MusicIdentity{public: std::string fileName; Mix_Music * musicHandle;};class SoundIdentity{public: std::string fileName; Mix_Chunk * soundHandle;};void AudioMixHook();//Here we use singletons for both the AudioPlayer and ActiveAudioPlayList classes.//This is primarily to allow global instances of them to be referenced from within//the AudioMixHook callback which is required to allow the playlist to function.class AudioPlayList;class AudioPlayer{public: ~AudioPlayer(); static AudioPlayer* instance(); bool initAudio(); void copyMusicToPlayList(AudioPlayList& playList); bool loadMusic(const std::string &fileName, const std::string &identifier); bool playMusic(const std::string &identifier, int loop = 0); bool loadSound(const std::string &fileName, const std::string &identifier); //If channel is specified apply to that channel, else apply to all future sounds played. //distancePercent is a number from 0.0 to 1.0 void setPosition(int degreeAngle, float distancePercent, int channel = -1); void removePosition(int channel = -1); bool playSound(const std::string &identifier, int channel = -1, int loop = 0, int ticks = -1); void muteSound(){muteSounds = 1;} void unMuteSound(){muteSounds = 0;} bool isSoundMuted(){return muteSounds;} void stopMusic(); void pauseMusic(); void resumeMusic(); bool checkMusicPlaying(std::string *songIdentifier = NULL); bool checkMusicPaused(std::string *songIdentifier = NULL); bool setMusicPlayAtTime(double position); void setMusicVolume(int volume); //set one sound's volume bool setSoundVolume(int volume, const std::string &identifier); //set all sound's volume void setSoundVolume(int volume); //negative indicates unlimited void setMaxChannels(int channels){ maxChannels = channels; } int getMaxChannels(){ return maxChannels; } int getAllocatedChannels(){ return currentChannels; } int getMostRecentlyUsedChannel(){ return channelLastPlayed; } std::string getLatestSong(){ return currentSong; }protected: AudioPlayer();private: std::map<std::string, MusicIdentity> music; std::map<std::string, MusicIdentity>::iterator musicCell; std::map<std::string, SoundIdentity> sounds; std::map<std::string, SoundIdentity>::iterator soundCell; std::string currentSong; std::list<int> channelsWithPositions; bool muteSounds; int audio_rate; int maxChannels; int currentChannels; Uint16 audio_format; int audio_channels; int audio_buffers; int channelLastPlayed; int distance; int angle; bool initialized; static AudioPlayer *_instance;};class ActiveAudioPlayList{public: static ActiveAudioPlayList* instance(); ~ActiveAudioPlayList(); AudioPlayList* getCurrentPlayList(); void setCurrentPlayList(AudioPlayList* playList); void beginPlaying(); bool advancePlayList(); bool isPlaying(); std::string getCurrentSong();protected: ActiveAudioPlayList(){currentPlayList = 0;}private: AudioPlayList* currentPlayList; static ActiveAudioPlayList *_instance;};class AudioPlayList{public: AudioPlayList(); ~AudioPlayList(); void beginPlaying(); void shuffleSongs(bool doShuffle){shuffle = doShuffle;} void loopSongs(bool doLoop){loop = doLoop;} void continuousPlay(bool doPlay){play = doPlay;} bool advancePlayList(); void performShuffle(); void resetPlayHead(); bool isShuffling(){return shuffle;} bool isLooping(){return loop;} bool isPlaying(){return play;} void addSongFront(const std::string &songName); void addSongBack(const std::string &songName); void removeSong(const std::string &songName); bool endOfList(); std::string getCurrentSong(); bool isEmpty(){return songLineup.empty();} void clearSongs();private: static AudioPlayList *_instance; std::list<std::string> songLineup; std::list<std::string>::iterator currentSong; bool shuffle, loop, play;};#endif
sound.cpp
#include "sound.h"#include <iostream>/*************************\| ------AudioPlayer------ |\*************************/int AudioRandom(int n){ //ensure srand affects random_shuffle on all implementations return int(n*rand()/(RAND_MAX + 1.0));}AudioPlayer* AudioPlayer::_instance = NULL;AudioPlayer* AudioPlayer::instance(){ if(_instance == NULL){ _instance = new AudioPlayer; } return _instance;}AudioPlayer::AudioPlayer(){ audio_rate = 22050; audio_format = AUDIO_S16SYS; audio_channels = 2; audio_buffers = 4096; maxChannels = -1; currentChannels = 0; initialized = 0; muteSounds = 0; angle = 0; distance = 0;}AudioPlayer::~AudioPlayer(){ if(initialized){ while(checkMusicPlaying()){ Mix_HaltMusic(); } Mix_HaltChannel(-1); if(!music.empty()){ for(musicCell = music.begin();musicCell != music.end();musicCell++){ if(musicCell->second.musicHandle!=NULL){ Mix_FreeMusic(musicCell->second.musicHandle); } } } if(!sounds.empty()){ for(soundCell = sounds.begin();soundCell != sounds.end();soundCell++){ if(soundCell->second.soundHandle!=NULL){ Mix_FreeChunk(soundCell->second.soundHandle); } } } int numtimesopened, frequency, channels; Uint16 format; numtimesopened = Mix_QuerySpec(&frequency, &format, &channels); for(int i = 0;i < numtimesopened;i++){ Mix_CloseAudio(); } initialized = 0; } if(_instance != NULL){ delete _instance; }}void AudioPlayer::setMusicVolume(int volume){ Mix_VolumeMusic(volume);}bool AudioPlayer::setSoundVolume(int volume, const std::string &identifier){ if (Mix_VolumeChunk(sounds[identifier].soundHandle, volume) < 0){ return 0; } return 1;}void AudioPlayer::setSoundVolume(int volume){ for(soundCell = sounds.begin();soundCell!= sounds.end();soundCell++){ Mix_VolumeChunk(soundCell->second.soundHandle, volume); }}bool AudioPlayer::initAudio(){ if(Mix_OpenAudio(audio_rate, audio_format, audio_channels, audio_buffers) != 0) { initialized = 0; return 0; } Mix_HookMusicFinished(AudioMixHook); initialized = 1; return 1;}bool AudioPlayer::loadMusic(const std::string &fileName, const std::string &identifier){ if(!initialized){ return 0; } if(music.find(identifier) == music.end()){ Mix_Music *musicTmp; musicTmp = NULL; musicTmp = Mix_LoadMUS(fileName.c_str()); if(musicTmp == NULL) { return 0; } music[identifier].musicHandle = musicTmp; music[identifier].fileName = fileName; return 1; } return 0;}bool AudioPlayer::loadSound(const std::string &fileName, const std::string &identifier){ if(!initialized){ return 0; } if(sounds.find(identifier) == sounds.end()){ Mix_Chunk *sound; sound = NULL; sound = Mix_LoadWAV(fileName.c_str()); if(sound == NULL) { return 0; } sounds[identifier].soundHandle = sound; sounds[identifier].fileName = fileName; return 1; } return 0;}bool AudioPlayer::playMusic(const std::string &identifier, int loop){ if(!initialized){ return 0; } if(music.find(identifier) != music.end()){ if(Mix_PlayMusic(music[identifier].musicHandle, loop) == -1) { return 0; } } currentSong = identifier; return 1;}void AudioPlayer::setPosition(int degreeAngle, float distancePercent, int channel){ int tmpDistance = int(distancePercent * 255.0); if(distance > 255){distance = 255;} if(distance < 0){distance = 0;} int tmpAngle = degreeAngle; //no need to bound check, SDL_Mixer does. if(channel == -1){ angle = tmpAngle; distance = tmpDistance; }else{ std::list<int>::iterator cell; for(cell = channelsWithPositions.begin();cell != channelsWithPositions.end() && (*cell) != channel;cell++){;} if(cell == channelsWithPositions.end()){ channelsWithPositions.push_back(channel); } Mix_SetPosition(channel, tmpAngle, distance); }}void AudioPlayer::removePosition(int channel){ if(channel == -1){ angle = 0; distance = 0; }else{ std::list<int>::iterator cell; for(cell = channelsWithPositions.begin();cell != channelsWithPositions.end() && (*cell) != channel;cell++){;} if(cell != channelsWithPositions.end()){ channelsWithPositions.erase(cell); } Mix_SetPosition(channel, 0, 0); }}bool AudioPlayer::playSound(const std::string &identifier, int channel, int loop, int ticks){ if(!initialized || muteSounds){ return 0; } if(sounds.find(identifier) != sounds.end()){ channelLastPlayed = Mix_PlayChannelTimed(channel, sounds[identifier].soundHandle, loop, ticks); if(channelLastPlayed==-1) { //probably no channels available. if(currentChannels<maxChannels || maxChannels < 0){ currentChannels++; Mix_AllocateChannels(currentChannels); channelLastPlayed = Mix_PlayChannelTimed(channel, sounds[identifier].soundHandle, loop, ticks); if(channelLastPlayed==-1){ currentChannels--; Mix_AllocateChannels(currentChannels); return 0; } }else{ return 0; } } if(channelLastPlayed >= 0){ std::list<int>::iterator cell; for(cell = channelsWithPositions.begin();cell != channelsWithPositions.end() && (*cell) != channel;cell++){;} if(cell == channelsWithPositions.end()){ Mix_SetPosition(channelLastPlayed, angle, distance); } } return 1; } return 0;}void AudioPlayer::stopMusic(){ Mix_HaltMusic();}void AudioPlayer::pauseMusic(){ Mix_PauseMusic();}void AudioPlayer::resumeMusic(){ Mix_ResumeMusic();}bool AudioPlayer::checkMusicPlaying(std::string *songIdentifier){ if(songIdentifier!=NULL){ if(Mix_PlayingMusic()){ *songIdentifier = currentSong; } } return Mix_PlayingMusic()!=0;}bool AudioPlayer::checkMusicPaused(std::string *songIdentifier){ if(songIdentifier!=NULL){ if(Mix_PlayingMusic()){ *songIdentifier = currentSong; } } return Mix_PausedMusic()!=0;}bool AudioPlayer::setMusicPlayAtTime(double position){ Mix_RewindMusic(); if (Mix_SetMusicPosition(position) < 0) { return 0; } return 1;}void AudioPlayer::copyMusicToPlayList(AudioPlayList& playList){ for(musicCell = music.begin();musicCell != music.end();musicCell++){ playList.addSongBack(musicCell->first); }}/*************************\| --ActiveAudioPlayList-- |\*************************/ActiveAudioPlayList* ActiveAudioPlayList::_instance = NULL;ActiveAudioPlayList* ActiveAudioPlayList::instance(){ if(_instance == NULL){ _instance = new ActiveAudioPlayList; } return _instance;}ActiveAudioPlayList::~ActiveAudioPlayList(){ if(_instance != NULL){ delete _instance; }}void ActiveAudioPlayList::beginPlaying(){ if(currentPlayList!=0){ currentPlayList->beginPlaying(); }}bool ActiveAudioPlayList::advancePlayList(){ if(currentPlayList!=0){ return currentPlayList->advancePlayList(); } return 0;}bool ActiveAudioPlayList::isPlaying(){ if(currentPlayList!=0){ return currentPlayList->isPlaying(); } return 0;}std::string ActiveAudioPlayList::getCurrentSong(){ if(currentPlayList!=0){ return currentPlayList->getCurrentSong(); } return "";}AudioPlayList* ActiveAudioPlayList::getCurrentPlayList(){ return currentPlayList;}void ActiveAudioPlayList::setCurrentPlayList( AudioPlayList* playList ){ currentPlayList = playList;}/*************************\| -----AudioPlayList----- |\*************************/AudioPlayList::AudioPlayList(){ loop = 0; shuffle = 0; play = 0;}AudioPlayList::~AudioPlayList(){ ActiveAudioPlayList* activePlayList = ActiveAudioPlayList::instance(); if(activePlayList->getCurrentPlayList() == this){ activePlayList->setCurrentPlayList(0); }}void AudioPlayList::addSongBack(const std::string &songName){ songLineup.push_back(songName);}void AudioPlayList::addSongFront(const std::string &songName){ songLineup.push_front(songName);}void AudioPlayList::removeSong(const std::string &songName){ std::list<std::string>::iterator cell; for(cell = songLineup.begin();cell!=songLineup.end() && (*cell) != songName;cell++){;} if(cell != songLineup.end()){ songLineup.erase(cell); }}bool AudioPlayList::endOfList(){ return currentSong == songLineup.end();}bool AudioPlayList::advancePlayList(){ if(currentSong != songLineup.end()){ currentSong++; } if(currentSong == songLineup.end() && loop){ if(shuffle){ performShuffle(); } currentSong = songLineup.begin(); } return currentSong != songLineup.end();}std::string AudioPlayList::getCurrentSong(){ return (*currentSong);}void AudioPlayList::clearSongs(){ songLineup.clear();}void AudioPlayList::performShuffle(){ std::vector<std::string> TmpSortContainer; std::list<std::string>::iterator cell; std::vector<std::string>::iterator cell2; for(cell = songLineup.begin();cell!=songLineup.end();cell++){ TmpSortContainer.push_back(*cell); } std::random_shuffle(TmpSortContainer.begin(), TmpSortContainer.end(), AudioRandom); songLineup.clear(); for(cell2 = TmpSortContainer.begin();cell2!=TmpSortContainer.end();cell2++){ songLineup.push_back(*cell2); } resetPlayHead();}void AudioPlayList::resetPlayHead(){ currentSong = songLineup.begin();}void AudioPlayList::beginPlaying(){ AudioPlayer::instance()->playMusic(getCurrentSong());}//Called upon the completion of a music file playingvoid AudioMixHook(){ static ActiveAudioPlayList *tunes = ActiveAudioPlayList::instance(); static AudioPlayer *player = AudioPlayer::instance(); if(tunes->isPlaying()){ if(tunes->advancePlayList()){ player->playMusic(tunes->getCurrentSong(), 0); } }}
and a quick example:
#include <SDL/SDL.h>#include "sound.h"#include <ctime>void quit(void);int main(int argc, char *argv[]){ srand (time(0)); //required to ensure random playlist order atexit(quit); //make sdl happy AudioPlayer *audioManager = AudioPlayer::instance(); ActiveAudioPlayList *activePlayList = ActiveAudioPlayList::instance(); AudioPlayList mainPlayList; //call this first. audioManager->initAudio(); //note, SDL_Mixer dislikes mp3's though it says it will load them it is //best to stick with ogg format. Seems to throw errors on program exit //sometimes with mp3's. audioManager->loadMusic("audio/1.ogg", "song1"); audioManager->loadMusic("audio/2.ogg", "song2"); audioManager->loadMusic("audio/3.ogg", "song3"); audioManager->loadSound("audio/song.ogg", "sound"); //you can either load an AudioPlayList manually or use the AudioPlayer //copyMusicToPlayList function which dumps all songs to a play list. audioManager->copyMusicToPlayList(mainPlayList); //these default to false mainPlayList.continuousPlay(true); //play next song after current song automatically mainPlayList.shuffleSongs(true); //shuffle songs after all have played mainPlayList.loopSongs(true); //reset playhead to beginning and start over after all have played //either call resetPlayHead or performShuffle to ensure no runtime error //from trying to play at an invalid playhead location. //mainPlayList.resetPlayHead(); //don't shuffle the first time we play mainPlayList.performShuffle(); //shuffle the first time we play activePlayList->setCurrentPlayList(&mainPlayList); //start the play list, also can be used if continuousPlay is false to resume playback activePlayList->beginPlaying(); //set the sound effect playback volume for all sounds audioManager->setSoundVolume(20); //play "sound" audioManager->playSound("sound"); //set the angle (90*) and the distance (50%) of the most recent sound's //channel. We could also have called setPosition without specifying a //channel before calling the playSound and it would have applied. //We have to remove the position on the channel this way though and it would //automatically remove after the sound was done otherwise. audioManager->setPosition(90, .5, audioManager->getMostRecentlyUsedChannel()); bool done = 0; while(!done){ //Not great, you'll want to have something in here to end the program //but without an SDL window we can't capture events so we'll just //infinite loop for the demo and exit ungracefully, but you can replace //this with something else. } return 0;}void quit(void){ SDL_Quit();}
_______________________"You're using a screwdriver to nail some glue to a ming vase. " -ToohrVyk