Jump to content
  • Advertisement
Sign in to follow this  

SDL_Mixer Wrapper (previously: Help Avoiding a Singleton)

This topic is 3416 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
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!