Added AudioHandler, based on Audio.h and Audio.c

merge-requests/1/merge
thmaillarb 4 years ago
parent 9a7701330f
commit d9537f3e68

@ -1,23 +0,0 @@
/**
* \file Audio.h
* \brief Audio management
* \author Théotime Maillarbaux
* \date 13/12/2021
*/
#ifndef AUDIO_H
#define AUDIO_H
#include <SDL2/SDL_mixer.h>
/**
* \brief Fades out a music and plays another one.
* \param[in] music A pointer to the Mix_Music struct used to play the current track
* \param[in] path The path to the new track to be played
* \warning The program won't know if it failed.
*
* This function creates a thread that will detach on its own completion.
*/
void switchMusic(Mix_Music* music, char path[]);
#endif // AUDIO_H

@ -0,0 +1,126 @@
/**
* \file AudioHandler.h
* \brief Audio management (music and sound effects)
* \author Théotime Maillarbaux
* \date 13/12/2021
*/
#ifndef AUDIO_H
#define AUDIO_H
#include <SDL2/SDL_mixer.h>
#include <SDL2/SDL_thread.h>
#include <stdio.h>
#include <stdbool.h>
/**
* Macro used to generate enums elements and paths for musics.
* It should take either #MACRO_MUSIC_ENUM_GEN(E) or #MACRO_TO_MUSIC_PATH(P)
* as parameter.
*
* If new audios are added to the program, they should be added here.
* \sa #MACRO_FOR_ALL_SFX(E)
*/
#define MACRO_FOR_ALL_MUSICS(M) \
M(base_tardi) \
M(testMus)
/**
* Macro used to generate enums elements and paths for sound effects.
* It works the same way as #MACRO_FOR_ALL_MUSICS(M).
* \sa #MACRO_FOR_ALL_MUSICS(M)
*/
#define MACRO_FOR_ALL_SFX(M) \
M(testClick)
/**
* Macro used to generate the entries for the musics in #EnumAudios.
* It should be used in #MACRO_FOR_ALL_MUSICS(M) as a parameter.
* Each entry of #MACRO_FOR_ALL_MUSICS(M) will be converted as MUSIC_name.
* \sa #MACRO_SFX_ENUM_GEN(E)
*/
#define MACRO_MUSIC_ENUM_GEN(E) MUSIC_##E,
/**
* Macro used to generate the entries for the SFX in #EnumAudios.
* It works the same way as #MACRO_MUSIC_ENUM_GEN(E), except the entries start with SFX_
* instead of MUSIC_.
* \sa #MACRO_MUSIC_ENUM_GEN(E).
*/
#define MACRO_SFX_ENUM_GEN(E) SFX_##E,
/**
* Macro used to generate the paths to the musics.
* It should be used in #MACRO_FOR_ALL_MUSICS(M) as a parameter.
* Each entry of #MACRO_FOR_ALL_MUSICS(M) will be converted as rsrc/music/name.mp3.
* \sa #MACRO_TO_SFX_PATH(P)
*/
#define MACRO_TO_MUSIC_PATH(P) "rsrc/music/"#P".mp3",
/**
* Macro used to generate the paths to the SFX.
* It works the same way as #MACRO_TO_MUSIC_PATH(P),
* except the entries are converted to rsrc/sfx/name.wav.
* \sa MACRO_TO_MUSIC_PATH(P)
*/
#define MACRO_TO_SFX_PATH(P) "rsrc/sfx/"#P".wav",
/**
* \enum EnumAudios
* \brief Lists audios used in the program.
*/
typedef enum {
MACRO_FOR_ALL_MUSICS(MACRO_MUSIC_ENUM_GEN)
NB_MUSIC_DEFINED, ///< Index of this entry == number of musics
MACRO_FOR_ALL_SFX(MACRO_SFX_ENUM_GEN)
NB_AUDIO_DEFINED ///< Index of this entry == number of audios + 1
} EnumAudios;
/**
* \struct AudioHandler
* \brief A struct to store and handle all audios.
*/
typedef struct {
bool canPlayAudio; ///< true if audio could be initialized, else false. It shouldn't be changed manually.
Mix_Music* musics[NB_MUSIC_DEFINED]; ///< Paths to the musics
Mix_Chunk* sfx[NB_AUDIO_DEFINED - NB_MUSIC_DEFINED - 1]; ///< Paths to the SFX
} AudioHandler;
/**
* \brief Initializes the audio and creates a new AudioHandler.
* \param[in] volMusic Volume of the music
* \param[in] volSFX Volume of the sound effects.
* \return A new AudioHandler.
* \warning The program won't know if opening the audio device and the files failed.
*/
AudioHandler newAudioHandler(int volMusic, int volSFX);
/**
* \brief Changes volume for the SFX.
* \param[in] volSFX The new volume for the SFX.
*/
void changeSFXVol(int volSFX);
/**
* \brief Frees the music and SFX, and un-initializes the audio.
* \param[in,out] audioHandler A pointer to the AudioHandler to be freed.
*/
void freeAudioHandler(AudioHandler audioHandler);
/**
* \brief Plays a music. If another music is currently playing, the previous one will fade out.
* \param[in] music The music to be played. Should be a value of #EnumAudios starting with MUSIC_.
* \param[in] audioHandler The AudioHandler used to store the musics.
*/
void playMusic(EnumAudios music, AudioHandler audioHandler);
/**
* \brief Plays a sound effect.
* \param[in] sfx The SFX to be played. Should be a value of #EnumAudios starting with SFX_.
* \param[in] channel The channel the SFX should be played on.
* \param[in] audioHandler The AudioHandler used to store the SFX.
*/
void playSFX(EnumAudios sfx, int channel, AudioHandler audioHandler);
#endif // AUDIO_H

Binary file not shown.

@ -1,35 +0,0 @@
#include "engine/Audio.h"
struct params {
Mix_Music* music; // AA pointer to the Mix_Music struct used for the current track
char* path; // The path to the new track to be played
};
int fadeOut(void* args) {
struct params* argsCast = (struct params*)args;
Mix_Music* newMusic = Mix_LoadMUS(argsCast->path);
if (argsCast->music != NULL) {
if(Mix_FadeOutMusic(500) == 0) {
while (Mix_PlayingMusic()) {
;
}
}
}
if (newMusic != NULL) {
argsCast->music = newMusic;
return Mix_PlayMusic(argsCast->music,-1);
}
else return -2;
}
void switchMusic(Mix_Music* music, char path[]) {
struct params args = {
.music = music,
.path = path
};
SDL_Thread* thread = SDL_CreateThread(&fadeOut, "Fade out", (void*)&args);
SDL_DetachThread(thread); // Won't wait until thread is done to continue
}

@ -0,0 +1,186 @@
#include "engine/AudioHandler.h"
// A channel represents the number of SFX we can play at the same time.
// We normally should use only 1 channel, and we add one for safety.
#define NBCHANNELS 2
// Local functions
/*
* Used in a thread. Fades out the currently played music,
* then plays the music passed in as parameter.
* args should be passed as a pointer to void,
* but should actually be a pointer to Mix_Music.
* It is the music we want to play next;
*/
int fadeOut(void* args) {
// Since args is a pointer to void
// (the way C handles undefined types),
// casting args to a pointer to Mix_Music
Mix_Music* = (Mix_Music*)args;
int ret;
if(Mix_FadeOutMusic(500) == 0) { // Starting the fadeout
while (Mix_PlayingMusic()) {
; // Waiting until it's done
}
} else {
fprintf(stderr,"WARNING: couldn't fade out");
Mix_HaltMusic();
}
ret = Mix_PlayMusic(argsCast->music,-1);
if (ret != 0) {
fprintf(stderr,"WARNING: %s\n",Mix_GetError());
}
// We're supposed to return an int to use SDL_thread.
// We're not using the return value though.
return ret;
}
// Global functions
AudioHandler newAudioHandler(int volMusic, int volSFX) {
AudioHandler audioHandler;
int nb_SFX = NB_AUDIO_DEFINED - NB_MUSC_DEFINED - 1;
// Generating paths to musics and SFX files using macros
char* musicsPaths[] = {MACRO_FOR_ALL_MUSICS(MACRO_TO_MUSIC_PATH)};
char* sfxPaths[] = {MACRO_FOR_ALL_SFX(MACRO_TO_SFX_PATH)};
audioHandler.canPlayAudio = true;
// Initialize + verify opening audio device
if (0 != Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 1024)) {
fprintf(stderr,"Error when initializing audio.\n");
return audioHandler;
}
audioHandler.canPlayAudio = true;
fprintf(stderr,"Musics: %d\nSFX: %d\n",NB_MUSIC_DEFINED,nb_SFX);
// Loading musics
for (size_t i = 0; i < NB_MUSIC_DEFINED; i++) {
if ((audioHandler.musics[i] = LoadMUS(musicPaths[i]) == NULL)) {
fprintf(stderr,"WARNING: %s\n",Mix_GetError());
} else {
fprintf(stderr,"Loaded %s\n",musicPaths[i]);
}
}
Mix_VolumeMusic(volMusic);
// Initializing SFX
Mix_AllocateChannels(NBCHANNELS);
// Loading SFX
for (size_t i = 0; i < nb_SFX; i++) {
if ((audioHandler.sfx[i] = Mix_LoadWAV(sfxPaths[i])) == NULL) {
fprintf(stderr,"WARNING: %s\n",Mix_GetError());
} else {
fprintf(stderr,"Loaded %s\n",sfxPaths[i]);
}
}
changeSFXVol(volSFX);
return audioHandler;
}
void changeSFXVol(int volSFX) {
for (size_t i = 0; i < NBCHANNELS; i++) {
Mix_Volume(i, volSFX);
}
}
void freeAudioHandler(AudioHandler* audioHandler) {
// Freeing the music
Mix_HaltMusic();
for (size_t i = 0; i < NB_MUSIC_DEFINED; i++) {
if (audioHandler->musics[i] != NULL) {
Mix_FreeMusic(audioHandler->musics[i]);
audioHandler->musics[i] = NULL; // "to be safe...", says the docs
}
}
// Freeing the SFX
Mix_HaltChannel(-1); // Stopping playback on ALL channels
for (size_t i = 0; i < NB_AUDIO_DEFINED - NB_MUSIC_DEFINED - 1; i++) {
if (audioHandler->sfx[i] != NULL) {
Mix_FreeChunk(audioHandler->sfx[i]);
audioHandler->sfx[i] = NULL; // "to be safe...", says the docs
}
}
Mix_AllocateChannels(0); // Unallocating all channels
// Un-initializing the audio
Mix_CloseAudio();
audioHandler->canPlayAudio = false;
}
void playMusic(EnumAudios music, AudioHandler audioHandler) {
// music should be a value of EnumAudios that starts by MUSIC_
// In other words, it should be an enumerator before NB_MUSIC_DEFINED.
if (music >= NB_MUSIC_DEFINED) {
fprintf(stderr,"WARNING: tried to play an arbitrary value as a music\n");
return;
}
// Checking if audio has been opened.
if (!(audioHandler.canPlayAudio)) {
fprintf(stderr,"WARNING: tried to play a music with an unusable AudioHandler\n");
return;
}
// If another music is playing, fading the previous one out
if (Mix_PlayingMusic()) {
// Creating the thread, passing the music as parameter
SDL_Thread* thread = SDL_CreateThread(&fadeOut, "Fade out", (void*)audioHandler.musics[music]);
if (thread == NULL) {
fprintf(stderr,"WARNING: couldn't create thread to fade out music\n");
}
// playMusic won't wait until the thread is finished to end
SDL_DetachThread(thread);
// No other music is playing: starting a music normally.
} else {
if (Mix_PlayMusic(audioHandler.musics[music],-1) != 0) {
fprintf("WARNING: %s\n",Mix_GetError());
}
}
}
void playSFX(EnumAudios sfx, AudioHandler audioHandler) {
int nb_SFX = NB_AUDIO_DEFINED - NB_MUSIC_DEFINED - 1;
int channel;
Mix_Chunk* chunkSFX;
// sfx should be a value of EnumAudios that starts by SFX_.
// In other words, it should be an enumerator after NB_MUSIC_DEFINED but before NB_AUDIO_DEFINED.
if (sfx <= NB_MUSIC_DEFINED || sfx >= NB_AUDIO_DEFINED) {
fprintf(stderr,"WARNING: tried to play an arbitrary value as a SFX\n");
return;
}
// Checking if SFX has been opened.
if (!(audioHandler.canPlayAudio)) {
fprintf(stderr,"WARNING: tried to play an SFX with an unusable AudioHandler\n");
return;
}
// Getting actual chunk
chunkSFX = audioHandler.sfx[sfx - NB_MUSIC_DEFINED - 1];
// Getting first available channel
channel = Mix_GroupAvailable(-1);
if (channel == -1) {
fprintf(stderr,"WARNING: cannot play SFX because all channels are used\n");
return;
}
// Playing the SFX once (0 repetitions)
if (Mix_PlayChannel(channel,chunkSFX,0) != 0) {
fprintf(stderr,"WARNING: %s\n",Mix_GetError());
return;
}
}
Loading…
Cancel
Save