• Advertisement
Sign in to follow this  

SDL_Mixer Wrapper (previously: Help Avoiding a Singleton)

This topic is 3330 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

I am using SDL_Mixer which has a C style interface to handle my music/audio playing and I am wrapping the functionality within a pair of C++ classes. One is AudioPlayer and the other is AudioPlayList. I will later have a third class which is used simply to keep track of a single active play list called ActiveAudioPlayList which will replace the current function of AudioPlayList as the singleton for the callback (later explained) but for now I only have one AudioPlayList which is used to keep track of the active play list. Let me present the problem I have. SDL_Mixer plays music and provides a basic callback in the form of a regular function pointer to something which takes no arguments and returns no value. I have specified this function: void AudioMixHook(); The only way to detect music has stopped playing and that we need to load the next song in the play list is via this callback. My hands are tied though because I want to wrap the interface of SDL_Mixer. Here is my interface:
class AudioPlayer{
public:
   ~AudioPlayer();
   static AudioPlayer* instance();

   bool initAudio();
   void copyMusicToPlayList();

   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();
   std::string getLatestSong(){
      return currentSong;
   }

   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);

   void setMaxChannels(int channels){ //negative indicates unlimited
      maxChannels = channels;
   }
   int getMaxChannels(){
      return maxChannels;
   }
   int getAllocatedChannels(){
      return currentChannels;
   }

   int getMostRecentlyUsedChannel(){
      return channelLastPlayed;
   }
   void MusicFinishHandler(){}
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 AudioPlayList{
public:
   ~AudioPlayList();
   static AudioPlayList* instance();

   void beginPlayingFirstSong();

   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();
protected:
   AudioPlayList();

private:
   static AudioPlayList *_instance;
   std::list<std::string> songLineup;
   std::list<std::string>::iterator currentSong;
   bool shuffle, loop, play;
};







So I have those and they are both being treated as singletons so that I can access their instances in the callback which is defined as such:
void AudioMixHook(){
   static AudioPlayList *tunes = AudioPlayList::instance();
   static AudioPlayer *player = AudioPlayer::instance();
   if(tunes->isPlaying()){
      if(tunes->advancePlayList()){
         player->playMusic(tunes->getCurrentSong(), 0);
      }
   }
}







So my question is... How do I get around the singleton requirement here? I do not know of any way to set the callback this function asks for to a class instance's member function unfortunately or I would have done that. Still though, the single callback would basically mean you should only ever instantiate one object or the music playback hook wouldn't work in the first class you created. Is this a good use of the singleton or am I missing something? Finally if I do go forward with this code as singletons what would be the best method of allowing access to this in deeper levels of code? IE: Should I pass references or rely on some more abstract method of getting the reference such as a factory that hides the instance get and returns the reference just incase I change my audio system? I'm trying to do this as cleanly as possible but it's tough without proper delegates because I have very little control over the callback. HALP! [Edited by - M2tM on January 11, 2009 11:30:23 PM]

Share this post


Link to post
Share on other sites
Advertisement
So you need a specific object to use as a callback "host", yes? Hmmm...

First of all, i checked my SDL music code and it's pretty simple compared to yours, but i'm not implementing playlists either. I just request Sounds or MusicTracks from the resource manager and do something like Track->Play(). But you need a playlist.

My first inclination is that a playlist shouldn't be singleton because i can easily imagine having several. My sound system wraps SDL too, but it does it in the normal OOP way. If i need to play a sound, i just tell the sound to play. classes like Sound and MusicTrack have some internal static state variables that deal with the nuts and bolts of playing the sounds and keep track of what's going on.

So... If you really only need one global playlist, could you use static variables for state-keeping and a static member function as the callback? something like:

static void MusicTrack::Callback(); ?

Another idea that popped into my head is to use signals and slots (because they seem to solve so many problems and because they're cool). Perhaps have either a regular plain-jane function as the callback or a static member function, but either way have that callback function do nothing more than fire off a MUSIC_FINISHED sort of event and have all playlists automatically receive it. If the callback was a static member function, you would have the added advantage of having some access to any additional state info you want from the class.

If any of this sounds wacky, i'm sipping my wine and it's almost midnight, so be nice to me, okay? :-)

Share this post


Link to post
Share on other sites
Haha, well, thanks for the reply. ++ for that :)

I'm planning on making the (as of yet not implemented) ActiveAudioPlayList a singleton class with a swappable state that takes an AudioPlayList (which would no longer be a singleton) reference which would allow me to swap the actual play list at run time effectively allowing multiple play lists, but one and only one active play list.

So that should solve the multiple playlist issue. I just finished watching like 60 minutes of a presentation as to why singletons (and global information in general) can create some really difficult to test code. I do not think that it matters as much for an audio playing class, but I want to make sure that I'm exhausting any other possibilities before resorting to a full blown Singleton.

In that same vein, I'm trying to avoid more static stuff, I think if I were to implement the AudioPlayList class with its own built in static callback it would become much less flexible with no benefit. I may be mistaken though.

Thanks again though for your input!

Let me expand on this also by saying right now it is working as intended with (albeit with only a single play list which I will be fixing shortly) so I am not having implementation problems, I just need to make sure I'm not jumping the gun here and placing a (well, two) needless singleton(s) in my project.

Share this post


Link to post
Share on other sites
Quote:
Original post by M2tMIn that same vein, I'm trying to avoid more static stuff, I think if I were to implement the AudioPlayList class with its own built in static callback it would become much less flexible with no benefit. I may be mistaken though.


Well, static functions aren't the devil. At some point, you've got to add something "singular" in nature, be it a regular function, static member function, singleton object, or whatever. The reason is because you've got (potentially) multiple objects running around that all feed off the same SDL interface. You will end up using some kind of static state information. Even if you don't use your own, you will still use the SDL MusicIsPlaying() types of functions, and those are using static state tracking variables. They just aren't yours.

Share this post


Link to post
Share on other sites
Pretty much, I figured as much. Because of the nature of the actual interface I'm wrapping I understand that there aren't really multiple instances anyway. Just needed confirmation.

Thank you. I'll post my updated code in this same thread for anyone who's interested in a decent wrapper... Or if there's a more appropriate location I can post it there.

Share this post


Link to post
Share on other sites
It's only a singleton if there is a public point of access.
Keep the access to the 'live' instance heavily protected, but it does have to exist in some form to do what you want.

Thunking is the ninja way to it, but ultimately means you still have a 'well known' location to store a pointer to the active audio player.

Share this post


Link to post
Share on other sites
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 playing
void 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();
}


Share this post


Link to post
Share on other sites
This is my second attempt at a reply, the first on my N95 disappeared after a problem with my login :(

Just a suggestion, but why does ActivePlaylist have to be a singleton? Why not just a pointer in AudioPlayer? AudioPlayer loads and plays sounds, so why can't it also parse playlists?


class AudioPlayer
{

....

AudioPlaylist* GetActivePlaylist(void)
{
return mActivePlaylist;
}

void SetActivePlaylist(AudioPlaylist* pl)
{
// Code To Stop Current Playlist

mActivePlaylist = pl;

// Code To Start New Playlist
}

...

private:
AudioPlaylist* mActivePlaylist;

...
};


Your AudioMixHook would then be:


void AudioMixHook(void)
{
static AudioPlayer *player = AudioPlayer::instance();
AudioPlayList *tunes = player->GetActivePlaylist();
if(tunes->isPlaying())
{
if(tunes->advancePlayList())
{
player->playMusic(tunes->getCurrentSong(), 0);
}
}
}


Also, as this is specific to playlists, could you not add this as a static member function of AudioPlaylist? I think this way you can still use a regular function pointer to pass to SDL_Mixer.

Other than that, the only other thing I might look at is wrapping each sound in an object something like:


Sound* snd = AudioPlayer::instance()->LoadSound(gunshot.wav);
snd->SetPosition(10,250,8);
snd->SetVolume(50);
snd->Play(false); // Don't loop


Only reason for this is so I wouldn't have to keep manipulating the channels, they could be managed internally some how.

Share this post


Link to post
Share on other sites
Have to agree with android. Here's why:

Consider an instance where there are many balls bouncing inside of a large box (bigger than the screen). Every time a ball bounces, it gives off a sound. First of all, a Ball needs access to a Sound. Then the Sound needs to also know about a Ball's position. I think it would be most convenient if a Ball could simply say:

my_sound->Play( my_position );

Then each ball can play it's own sound from it's own position and the some magic subsystem sorts out the details.

Your current approach seems to be more like:

AudioPlayer::GetInstance()->setPosition( info, channel );
AudioPlayer::GetInstance()->playSound( info, channel, more info );
AudioPlayer::GetInstance()->removePosition( channel );

That's a bit messy IMHO.

And I'm assuming that the object playing the sound has to somehow know what channel it wants to play on. So i have to ask: what does a Ball know about audio channels? What should it know? It should know how to bounce and make a sound when it does. That's about it.

Share this post


Link to post
Share on other sites
You can call:

AudioPlayer::GetInstance()->setPosition( objectAngle, objectDistance );
AudioPlayer::GetInstance()->playSound( "soundhandle" );
AudioPlayer::GetInstance()->removePosition( );

You don't -need- to specify anything to do with channel, the option is there if you need to move a sound after it has started playing. If you do not need to move a sound after it has started playing you do not need to know anything about channel.

However, if you do need to know the channel you can retrieve the channel from the actual AudioPlayer. Let's say a racecar makes a constant hum of its engine (note I have modified this slightly because there is no reason to persist the position if you specify it for a single channel, as a result it is automatically removed after the sound finishes playing.):

AudioPlayer::GetInstance()->playSound( "soundhandle" );
RaceCarChannel = AudioPlayer::GetInstance()->getMostRecentlyUsedChannel();

then we call this immediately and every time the racecar position ends:
AudioPlayer::GetInstance()->setPosition( objectAngle, objectDistance, RaceCarChannel );

Now, I can see what you mean about wanting to wrap some sort of sound object around the idea to get rid of the channel, or at least to centralize it with the sound string identifier. Originally if I wanted to implement sound seperately I would also have to seed it with a reference to an AudioPlayer to do anything (back before the audio player was a singleton) and so the design was a bit different. Now that there is a global access point and due to the fact that sound objects are very very closely related with the AudioPlayer I don't really see the harm in letting a little bit of singleton magic happen there to allow each sound object to play itself and load itself with its channel and allow for automagic updating of positions etc.

It is a valid consideration and I'll go about implementing it.

And last night I was also thinking of just letting the AudioPlayer deal with the active play list, there's really no reason to require the ActiveAudioPlayList when the AudioPlayer already seems to know about the PlayList anyway with its own copy function. If I wanted them to be truly separate I wouldn't have put that in there in the first place :P

Alright, thank you both. I'll be back with updated code later.

Share this post


Link to post
Share on other sites
totally unrelated:

Since i didn't see it in your code, i thought i might mention a further idea you could implement (not too hard).

Maximum channels per sound

If you play one sound on one channel, all is well. If you play two sounds on two channels at about the same time, that's fine too, but you double your processing. But what if you have, say, a room full of hundreds of machine guns all firing at the same time? You can't play hundreds of audio channels (plus it would sound like crap anyway). You might consider doing a per-sound channel limit.

If you limit a particular sound to one channel and attempt to play two sounds overlapping, the first sound stops cold and the second sound starts. That's great for machine gun fire, because the RAT-TAT-TAT is fast and the sound is practically finished before the next one begins. If several overlapping sounds cut each other off, you wouldn't notice much because that's what machine guns sound like anyway, right?

HOWEVER, some sounds are terrible if you use only one channel for several overlapping sounds - pretty much anything noticeably long or complex. For these sounds you would WANT several channels. But you can't have unlimited channels, so you might elect to put a cap on it. Say, for example, three channels. If a forth copy of the sound effect comes along and the first three are still playing, it cuts off and robs the first channel. That way it kinda sounds realistic without anyone noticing you are not dedicating a whole channel to each and every sound that plays.

Share this post


Link to post
Share on other sites
wow, what an excellent suggestion.

That would be a great feature and pretty easy to implement.

<unrelated even more>

Let me describe a problem I just ran into with SDL_Mixer, or perhaps just the way I'm trying to use it. As you see in the above code I'm using music play lists, what I've just finished implementing is the ability for sound playlists. One of the only differences between a sound play list and a music play list is that the sound play list can have a position which it uses to play each sound in the play list which can also be updated on the fly.

Now, I can detect when one sound stops playing with another hook function similar to the music method. I can then check the sound play list for that channel and advance the playhead and begin the next sound seamlessly. I can not, however, set the position of the sound from within the callback or any function called from the callback. As a result I don't really know how to seamlessly persist the position when I play the next song from the callback.

The issue I'm running up against is described here in detail (by me):

http://article.gmane.org/gmane.comp.lib.sdl/39792

the main link to the newsgroup:

SDL News Group

The code example I have there doesn't check for all the playlist is running etc, but it describes the problem. If anybody has any clues let me know.

I can get around the issue by requiring an update call to the sound lib in my main loop, but that is a) not elegant and b) sometimes causes a moment's lag between the song beginning full center and then actually moving its position. The other method is to set the playhead forward but not to play the song until I call that sound play list update function from my main loop... Still this is not ideal and since there is a callback that will allow seamless play it would be a shame if I couldn't somehow leverage that to my advantage.

This is a pretty SDL_Mixer specific type issue so if you aren't too familiar with it you may not be able to help. But I figured posting here would be worth a try. There are a variety of people with a variety of experiences so perhaps someone knows.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement