I’m getting to grips with SDL3 mixer on iOS and trying to play each sound in its own track, as I want to be able to set a different volume and pitch for each one at the time of playing - so I guess I can’t use MIX_PlayAudio. My code is:
-(void)playSound
{
m_pTrack = MIX_CreateTrack(m_pMixer);
MIX_SetTrackAudio(m_pTrack, m_pSound);
MIX_PlayTrack(m_pTrack, 0);
MIX_SetTrackStoppedCallback(m_pTrack, MyStoppedCallback, NULL);
}
void MyStoppedCallback(void* pUserdata, MIX_Track* pTrack)
{
// when the track stops, destroy it
MIX_DestroyTrack(pTrack);
SDL_Log("Track finished and destroyed.");
}
The sound plays fine, and calls the C function (from the Objective C’s callback), the SDL_Log command writes out fine, but then my app crashes as it exits that callback function with:
SDLAudioP15: EXC_BAD_ACCESS (code=1, address=0x0)
What would be amazing though is if MIX_PlayAudio could have volume and pitch parameters and then we wouldn’t have to create and destroy tracks ourselves! (Or maybe be able to set tracks to auto-destroy when they finish?)
I need to destroy the track somewhere, or my app will just eat up memory though? How else can you play sounds at any volume and pitch, other than create a new track for each play? And then destroy that track when it’s finished?
Good question. I haven’t used SDL3_mixer yet so I’m not sure how it’s supposed to be used. It would have been nice if it was possible to hand the track over to the library and have it destroyed automatically just like with MIX_PlayAudio.
The only workaround I can think of is to keep a list of tracks that have been stopped, and destroy them somewhere else.
Thanks for your help, and TBH I’m not sure why Mixer 3 has become so complicated, as I thought the whole ethos of SDL was to shield us from all the complexities. All I need are three functions!
So, is this the recommended way for SDL3 games to play a sound at any given volume? It feels a bit cumbersome to have to create tracks, set up callback functions and manually destroy the tracks ourselves. I’d have guessed if there was a function like:
MIX_PlayAudio is for fire-and-forget sounds, which are useful in some cases, and you can build a whole game with them, but are disastrous as soon as you need to do anything dynamic: pause the game, bring up a menu, end the game when the player dies, etc. They’re gonna play until they are done, and that’s that. That’s why they are so limited, because if you ever need more you’re going to be in a bad position.
Tracks are generally meant to be longer living than this; think of them more like individual columns in a sound board:
You set up however many of them you might want, and assign sounds to play on them, tweaking the dials and sliders for each as you go.
If you really do want to just have everything be fire-and-forget-with-some-extra-settings, you’re doing it the right way, which needs a little extra one-time code but not much, now that the fix is in place. You are correct that some games might only ever need this much functionality.
Some of the phrasing above got a little confusing to me. This post is my attempt at clearing up my own confusion.
In the API, the terms Volume and Gain are kind of interchangeable, but Gain is the term being used in the function names themselves. Gain is like a multiplier to the base volume; it can’t be negative, but multiply by 2 to double the volume, multiply by .5 to half it.
The default gain is 1.0 when opening a mixer.
If you just want to change the master volume based on in-game events, then I think you would want to use MIX_SetMixerGain, if you want to set individual tracks volume use MIX_SetTrackGain, and the newest thing is to group your sounds using tags and calling MIX_SetTagGain. You don’t need to provide your own callbacks for these to work.
I don’t know how or when those gain functions take effect if you provide your own callbacks. I haven’t run across a situation that required me to make my own callback so far.
Below is a test to confirm that MIX_PlayAudio’s master volume can be changed on the fly, MIX_PlayAudio can be paused/resumed, and music stops when the window closes:
#include <SDL3/SDL.h>
#include <SDL3_mixer/SDL_mixer.h>
#include <stdlib.h> // for exit(0)
int main(int argc, char ** argv)
{
SDL_Log("Provide one music file, no spaces in the name. Press arrow up and down to control volume");
if(argc != 2)
{
// I'm trying for simple, just take one file as imput
exit(0);
}
SDL_Init(SDL_INIT_VIDEO);
MIX_Init();
SDL_Window * window = SDL_CreateWindow("Audio Player", 400, 400, SDL_WINDOW_RESIZABLE);
SDL_Renderer * renderer = SDL_CreateRenderer(window, 0);
SDL_SetRenderVSync(renderer, 1);
MIX_Mixer * mixer = MIX_CreateMixerDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, NULL);
size_t frameCount = 0;
MIX_Audio * music = MIX_LoadAudio(mixer, argv[1], false);
MIX_PlayAudio(mixer, music);
bool pause = false;
float vol = 1.0f;
bool run = true;
while(run)
{
SDL_Event ev;
while(SDL_PollEvent(&ev))
{
switch(ev.type)
{
case SDL_EVENT_KEY_DOWN:
switch(ev.key.key)
{
case SDLK_SPACE:
// pause all sound
pause = !pause;
if(pause)
{
MIX_PauseAllTracks(mixer);
}
else
{
MIX_ResumeAllTracks(mixer);
}
break;
case SDLK_UP:
// volume up
vol *= 1.05f;
MIX_SetMixerGain(mixer, vol);
SDL_Log("Mixer Gain: %f", MIX_GetMixerGain(mixer));
break;
case SDLK_DOWN:
// volume down
vol *= 0.95f;
MIX_SetMixerGain(mixer, vol);
SDL_Log("Mixer Gain: %f", MIX_GetMixerGain(mixer));
break;
case SDLK_ESCAPE:
run = false;
break;
}
break;
case SDL_EVENT_QUIT:
run = false;
break;
}
}
SDL_RenderClear(renderer);
SDL_RenderPresent(renderer);
}
MIX_Quit();
SDL_Quit();
}