[SDL 3.1.1] Repeat wave audio.

How can I make the music start again after the first 10s of delay in my example?

With SDL2_mixer, there was the following Mix_PlayMusic(). There is also Mix_HaltMusic to reset the sound.

Or is this only possible with SDL3_mixer?

#include <SDL3/SDL.h>

int main(int argc, char *argv[])
{
  SDL_AudioSpec wave_spec;
  Uint8 * wave_sound;
  Uint32 wave_soundlen;
  SDL_AudioStream * stream;

  SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
  SDL_LoadWAV("/home/tux/Schreibtisch/sound/test.wav", &wave_spec, &wave_sound, &wave_soundlen);
  stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &wave_spec, nullptr, nullptr);
  SDL_PutAudioStreamData(stream, wave_sound, wave_soundlen);
  SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(stream));
  SDL_Delay(1000);
  SDL_PauseAudioDevice(SDL_GetAudioStreamDevice(stream));
  SDL_Delay(1000);
  SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(stream));
  SDL_Delay(10000);

  // Restart sound
  SDL_Delay(10000);

  SDL_DestroyAudioStream(stream);
  SDL_Log("Ende");
  SDL_Quit();
  return 0;
}

Just put the data to the stream again.

#include <SDL3/SDL.h>

int main(int argc, char *argv[])
{
  SDL_AudioSpec wave_spec;
  Uint8 * wave_sound;
  Uint32 wave_soundlen;
  SDL_AudioStream * stream;

  SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
  SDL_LoadWAV("/home/tux/Schreibtisch/sound/test.wav", &wave_spec, &wave_sound, &wave_soundlen);
  stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &wave_spec, nullptr, nullptr);
  SDL_PutAudioStreamData(stream, wave_sound, wave_soundlen);
  SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(stream));
  SDL_Delay(1000);
  SDL_PauseAudioDevice(SDL_GetAudioStreamDevice(stream));
  SDL_Delay(1000);
  SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(stream));
  SDL_Delay(10000);

  // Restart sound
  SDL_PutAudioStreamData(stream, wave_sound, wave_soundlen);
  SDL_Delay(10000);

  SDL_DestroyAudioStream(stream);
  SDL_Log("Ende");
  SDL_Quit();
  return 0;
}

If you plan on doing other things while the music is playing then look into non-blocking options to trigger the sound every X seconds. (Where X is a calculated length of the audio plus your desired delay time)

One option is that you could set up a timer callback using SDL_AddTimer

Thanks first of all.

If I understand it correctly and compare this with a cassette recorder, would it correspond to this?

  • SDL_PutAudioStreamDataSTART
  • SDL_ClearAudioStreamSTOP
  • SDL_PauseAudioDeviceSet PAUSE
  • SDL_ResumeAudioDevicesolve PAUSE

One option is that you could set up a timer callback using SDL_AddTimer

Then it would be enough to use the timer, for example. every 1sec and then call an SDL_PutAudioStreamData there?
Or should you somehow check in the timer whether the piece of music is finished?

I think SDL_PutAudioStreamData is more like enqueue because it won’t play the sound until after it has played everything else that you have already added to the stream.

If you use SDL_AddTimer note that the callback will run in another thread so you need to be careful with what functions you call and make sure to implement proper thread synchronization if you share mutable data between the threads (as always when dealing with threads).

I think SDL_PutAudioStreamData is more like enqueue because it won’t play the sound until after it has played everything else that you have already added to the stream.

I just discovered this too. The streams are simply stacked. If you call PutAudioStreamData 3 times in a row, the WAVs will be played 3 times.
So there is nothing wrong with the timer, it would simply stack up until the memory is full.
If you wanted to find out whether a stream is finished playing, you could call SDL_ClearAudioStream before making another put.
You can check whether it is in the pause phase with SDL_AudioDevicePaused.
Now the question is, is there a way to check whether the stream is finished?

That function will clear the stream so some unplayed sound might not get heard.

Did you perhaps mean SDL_GetAudioStreamQueued? If it drops to zero (or below a certain threshold) it’s time to refill the queue.

Note that SDL_OpenAudioDeviceStream allow you to specify a callback that will get called repeatedly. One of the arguments to the the callback is how many audio samples it wants.

Did you perhaps mean SDL_GetAudioStreamQueued ? If it drops to zero (or below a certain threshold) it’s time to refill the queue.

I tried this in the event loop. The value counts down as expected, but gets stuck at 7.
SDL_Log("size: %i", SDL_GetAudioStreamQueued(stream));

Note that SDL_OpenAudioDeviceStream allow you to specify a callback that will get called repeatedly. One of the arguments to the the callback is how many audio samples it wants.

You could certainly use this and check whether additional_amount and total_amount are the same size. If both are the same, the sound is over.
It’s just strange that additional_amount has either 0 or the maximum value.

I now have an example including a pause function.

What I’m not sure is the following is necessary: SDL_free(wave.sound);

#include <SDL3/SDL.h>

struct {
  SDL_AudioSpec spec;
  Uint8 *sound;
  Uint32 soundlen;
} wave;

SDL_AudioStream * stream;

void AdioStreamCallback(void *usrdata, SDL_AudioStream *stream, int additional_amount, int total_amount  )
{
  if (additional_amount == total_amount) {
    SDL_PutAudioStreamData(stream, wave.sound, wave.soundlen);
  }
}

int main(int argc, char *argv[])
{
  SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
  SDL_Window *win = SDL_CreateWindow("Hello World", 640, 480, 0);

  SDL_LoadWAV("dia.wav", &wave.spec, &wave.sound, &wave.soundlen);
  stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, &wave.spec, AdioStreamCallback, nullptr);
  SDL_PutAudioStreamData(stream, wave.sound, wave.soundlen);
  SDL_ResumeAudioDevice(SDL_GetAudioStreamDevice(stream));

  bool quit = false;
  while(!quit){
    SDL_Event event;
    while(SDL_PollEvent(&event)) {
      switch (event.type) {
        case SDL_EVENT_QUIT:
          quit = true;
          break;
        case SDL_EVENT_KEY_DOWN:
          switch (event.key.keysym.sym) {
            case SDLK_ESCAPE:
              quit = true;
              break;
            case SDLK_p:
              SDL_AudioDeviceID id = SDL_GetAudioStreamDevice(stream);
              if (SDL_AudioDevicePaused(id)){
                SDL_ResumeAudioDevice(id);
              } else {
                SDL_PauseAudioDevice(id);
              }
              break;
         }
         break;
      }
    }
  }

  SDL_DestroyAudioStream(stream);
  SDL_free(wave.sound);
  SDL_DestroyWindow(win);
  SDL_Quit();
  return 0;
}

Most Operating Systems are ‘intelligent’ enough to handle the cleanup when your program closes, but ultimately it’s best practice to clean it up yourself.

I think the next step you’ll want to undertake is pausing the music stream while still allowing other sound effects to continue. (Environmental/ambient sounds, footsteps, tool sounds, etc).

Most Operating Systems are ‘intelligent’ enough to handle the cleanup when your program closes, but ultimately it’s best practice to clean it up yourself.

Only a Java coder leaves everything in memory.
It’s better to free up the memory. Imagine background music that is reloaded with each level. This would fill up the memory unnecessarily.
I just saw that this is even mentioned in the SDL_LoadWav wiki.

I think the next step you’ll want to undertake is pausing the music stream while still allowing other sound effects to continue.

This is exactly the next step. Multiple sounds in parallel.
I think for serious things it’s better to switch to SDL_mixer?

The operating system releases all resources allocated by a process when it is closed because it have to do this in order to maintain stability. This is not a whim of the operating system creators, but a necessity.

Software developers should ensure that the process uses as few resources as possible because resources such as CPU time and memory are limited. If a resource is not needed, it is released. If a process has to wait for subsequent actions, it should free up CPU time instead of wasting it with spinlocks. The less CPU time a process uses, the more time it has left for other processes (and there may be hundreds of them), which not only positively affects system stability, but also reduces energy consumption and the likelihood of CPU overheating.

Moreover, the end-user’s hardware configuration is unknown, so by minimizing the use of available resources, the range of supported hardware configurations expands, and thus the pool of end-users. This is especially important in the case of games, because their performance is crucial.

I guess you could create multiple audio streams to handle multiple sounds but using SDL_mixer is probably easier.

I wrapped the wave-data and the stream in a struct. And the whole thing in an array.
So I have each audio track individually. For AdioStreamCallback I put void *usrdata for the struct, so it knows in the function which track it is.
This would be enough in a game to make several weapons sound.

The next one will be I use SDL_mixer. Because you have many more options, including volume. You can also use mp3 instead of the resource-wasting WAV.