diff --git a/src/game/game.c b/src/game/game.c index a70cd99..9b2e7c8 100644 --- a/src/game/game.c +++ b/src/game/game.c @@ -48,21 +48,20 @@ void game_init(gamedata* const dat) { gametime_get(>.ts); // initialize audio device - audiodevice* ad = audio_device_init(32000, AUDIO_S16, 1, 4096); + audio_device_init(32000, AUDIO_S16, 1, 4096); *dat = (gamedata){ - {0}, // rowdat - {0}, // row - gt, // time - ad, // audio_device - audio_wav_load(ad, "korobeiniki.wav"), // music - audio_wav_load(ad, "place.wav"), // place_sfx - 0, // score - {0}, // nxt - 0, // curr_idx - 0, // sel_x - 0, // sel_y - true, // run + {0}, // rowdat + {0}, // row + gt, // time + audio_wav_load("korobeiniki.wav"), // music + audio_wav_load("place.wav"), // place_sfx + 0, // score + {0}, // nxt + 0, // curr_idx + 0, // sel_x + 0, // sel_y + true, // run }; // initialize the rows within the game data @@ -109,7 +108,7 @@ void game_update(gamedata* const dat) { if (ctime > timer_music) { timer_music = ctime + (dat->music.ms); - audio_play(dat->audio_device, &dat->music); + audio_play(&dat->music); } // for rotation updating @@ -145,7 +144,7 @@ void game_update(gamedata* const dat) { void game_free(gamedata* const dat) { audio_wav_unload(&dat->music); audio_wav_unload(&dat->place_sfx); - audio_device_free(dat->audio_device); + audio_device_free(); // zero-out the rest of the data *dat = (gamedata){0}; diff --git a/src/game/game.h b/src/game/game.h index ae9327e..3648cbb 100644 --- a/src/game/game.h +++ b/src/game/game.h @@ -25,7 +25,6 @@ typedef struct { colour8 rowdat[ROWS * COLUMNS]; colour8* rows[ROWS]; struct gametime time; - audiodevice* audio_device; audiodata music; audiodata place_sfx; uint16_t score; diff --git a/src/game/tetromino/placing.c b/src/game/tetromino/placing.c index 41ba5bf..154032c 100644 --- a/src/game/tetromino/placing.c +++ b/src/game/tetromino/placing.c @@ -112,7 +112,7 @@ void place_update(gamedata* const game_data, input_data const move) { set_shape(game_data->rows, curr_id, game_data->sel_x, game_data->sel_y); // if the shape intersects vertically, write the shape at the current position and return clear_rows(game_data->rows, &game_data->score); // clear the rows that have been completed - audio_play(game_data->audio_device, &game_data->place_sfx); + audio_play(&game_data->place_sfx); next_shape(game_data); if (shape_intersects(game_data->rows, game_data->curr_idx, game_data->sel_x, game_data->sel_y)) diff --git a/src/io/audio.c b/src/io/audio.c index 131759f..fd41586 100644 --- a/src/io/audio.c +++ b/src/io/audio.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -10,175 +11,133 @@ #include "../error.h" #include "../util/compat.h" +struct audioplayer { + uint8_t const* buf; + int len; +}; + +struct audiodevice { + struct audioplayer audio[AUDIO_MAX]; + unsigned id; + int freq; + SDL_AudioFormat fmt; + uint8_t ch; // audio channels +} dev; + +/* mixes the audio output stream, using the different audio as sources */ static void audiomixer(void* const userdata, uint8_t* const stream, int const len) { - memset(stream, 0, len); // clear the playing audio - audiodevice* const dev = userdata; // retreive the callback data + (void)userdata; + memset(stream, 0, len); // clear the playing audio - // return if dev is null, since it can fail to initialize - if (dev == NULL) return; - - struct audioplayer* prev = NULL; - struct audioplayer* curr = dev->audio_players; - while (curr != NULL) { - // if the current audio fragment has reached the end of their data - if (curr->len == 0) { - struct audioplayer* ncurr = curr->nxt; - - // free the memory allocated to it and assign the next to to the currently playing - free(curr); - curr = ncurr; - - // write to the audio device if prev hasn't been set yet - if (prev == NULL) - dev->audio_players = curr; - else - prev->nxt = curr; - - // continue so if curr is now NULL, the loop stops - continue; + // update the counts for the audio array + for (unsigned i = 0; i < AUDIO_MAX; i++) { + if (dev.audio[i].len > 0) { + unsigned mixlen = SDL_min(len, dev.audio[i].len); + SDL_MixAudioFormat(stream, dev.audio[i].buf, dev.fmt, mixlen, SDL_MIX_MAXVOLUME); + dev.audio[i].len -= mixlen; + dev.audio[i].buf += mixlen; } - - // calculate how much of the current audio player we should mix into the stream - int const mixlen = SDL_min(curr->len, (unsigned)len); - - // mix the current buffer into the stream, and update the audio player values accordingly - SDL_MixAudioFormat(stream, curr->buf, dev->fmt, mixlen, SDL_MIX_MAXVOLUME); - curr->buf += mixlen; - curr->len -= mixlen; - - // increment the current node - prev = curr; - curr = curr->nxt; } } -// converts the inputted audio to the format of dev -// returns 1 upon failure, 0 upon success. When 1 is returned *bufptr will be freed. Otherwise *bufptr is reallocated -static int8_t audio_cvt(audiodevice const* dev, SDL_AudioSpec const* spec, uint8_t** bufptr, unsigned* len) { +/* converts input audio buffer to one that matches the buffer of dev. + * `len` is a pointer to the current size, the new size will be written to this location. + * returns the pointer to the audio buffer to use, or NULL, when something went wrong. + * NULL will never be returned after the conversion */ +static uint8_t* audio_cvt(SDL_AudioSpec const* spec, uint8_t* bufptr, unsigned* len) { + if (!bufptr) return NULL; + // init the converter SDL_AudioCVT cvt; - if (SDL_BuildAudioCVT(&cvt, spec->format, spec->channels, spec->freq, dev->fmt, dev->channels, dev->freq) < 0) { - error("%s:%u could not build the audio converter! SDL Error: %s", __FILE_NAME__, __LINE__, SDL_GetError()); - free(*bufptr); // free the buffer upon an error, as we won't be using this - return 1; - } else if (!cvt.needed) { // ensure the conversion is necessary - return 0; - } - cvt.len = (*len); // specify the length of the source data buffer in bytes (warn: uint32_t -> int32_t) - cvt.buf = realloc(*bufptr, cvt.len * cvt.len_mult); // grow the inputted buffer for the conversion - - // ensure the conversion buffer reallocation goes correctly - if (cvt.buf == NULL) { - error("%s:%u failed to reallocate the audio buffer to the new size of %u bytes!", __FILE_NAME__, __LINE__, cvt.len); - free(*bufptr); // free the inputted pointer, as realloc doesn't clear this address if it fails - return 1; - } - - // converts the audio to the new format - if (SDL_ConvertAudio(&cvt)) { - error("%s:%u something went wrong when loading/converting an audio buffer! SDL Error: %s", __FILE_NAME__, __LINE__, SDL_GetError()); - free(cvt.buf); // free the conversion buffer if it fails, as realloc moved the data to this adress; old adress is no longer valid - return 1; - } - - // update output - *len = cvt.len_cvt; // set the length to the new length after the conversion - *bufptr = realloc(cvt.buf, cvt.len_cvt); // reallocate the buffer to the new size - if (*bufptr == NULL) { - warn("%s:%u something went wrong whilst shrinking the audio buffer whilst converting!", __FILE_NAME__, __LINE__); - *bufptr = cvt.buf; // use the conversion buffer, as this one will be valid if realloc fails - } - - return 0; -} - -audiodevice* audio_device_init(int freq, SDL_AudioFormat fmt, uint8_t channels, uint16_t samples) { - audiodevice* dev = malloc(sizeof(audiodevice)); - - if (dev == NULL) { - error("%s:%u null pointer when allocating memory for the audio device!", __FILE_NAME__, __LINE__); + if (SDL_BuildAudioCVT(&cvt, spec->format, spec->channels, spec->freq, dev.fmt, dev.ch, dev.freq) < 0) { + error("could not build the audio converter! SDL Error: %s", SDL_GetError()); return NULL; } + if (!cvt.needed) return bufptr; // ensure the conversion is necessary + // grow the inputted buffer for the conversion + cvt.len = *len; + cvt.buf = realloc(bufptr, cvt.len * cvt.len_mult); + if (!cvt.buf) { + warn("failed to grow the audio buffer to the new size of %u bytes! audio may be bugged", cvt.len); + return bufptr; + } + + // convert the audio to the new format + if (SDL_ConvertAudio(&cvt) < 0) { + warn("couldn't convert an audio buffer to the set format, audio may be bugged! SDL Error: %s", SDL_GetError()); + assert(cvt.buf); + return cvt.buf; + } + assert(cvt.buf); + + // shrink to reduce memory footprint + *len = cvt.len_cvt; + bufptr = realloc(cvt.buf, *len); + return bufptr ? bufptr : cvt.buf; +} + +int audio_device_init(int freq, SDL_AudioFormat fmt, uint8_t channels, uint16_t samples) { // define the audio specification SDL_AudioSpec spec = {freq, fmt, channels, 0, samples, 0, 0, NULL, NULL}; spec.callback = audiomixer; - spec.userdata = dev; + spec.userdata = NULL; + + unsigned id = SDL_OpenAudioDevice(NULL, 0, &spec, NULL, 0); // create the audio device - *dev = (audiodevice){ - NULL, - SDL_OpenAudioDevice(NULL, 0, &spec, NULL, 0), + dev = (struct audiodevice){ + {0}, + id, freq, fmt, channels, }; - if (dev->id < 1) { - error("%s:%u audio device failed to open! SDL Error: %s", __FILE_NAME__, __LINE__, SDL_GetError()); - free(dev); - return NULL; - } - - // default state of the device is paused, so we unpause it here - SDL_PauseAudioDevice(dev->id, 0); - return dev; + if (id) { + // default state of the device is paused, so we unpause it here + SDL_PauseAudioDevice(id, 0); + return 0; + } else return 1; } -void audio_play(audiodevice* dev, audiodata const* audio) { - if (dev == NULL) return; // dev might fail to initialize - if (audio->len == 0) return; // audio might fail to initialize - - // create an audio player - struct audioplayer* player = malloc(sizeof(struct audioplayer)); - *player = (struct audioplayer){ - dev->audio_players, // set nxt to the first item in dev (can be NULL, this is fine) - audio->buf, - audio->len, - }; - - // assign ourselves to the first item - dev->audio_players = player; +void audio_device_free(void) { + SDL_CloseAudioDevice(dev.id); + dev = (struct audiodevice){0}; } -void audio_device_free(audiodevice* dev) { - if (dev == NULL) return; - SDL_CloseAudioDevice(dev->id); +void audio_play(audiodata const* audio) { + if (!dev.id) return; + if (!audio->buf) return; - struct audioplayer* curr = dev->audio_players; - - // free all audio players - while (curr != NULL) { - dev->audio_players = curr->nxt; // use audio_players in dev as a cache - free(curr); - curr = dev->audio_players; - } - - // free the audio device itself - free(dev); + unsigned i = 0; + while (i < AUDIO_MAX && dev.audio[i].len > 0) i++; + if (i < AUDIO_MAX) + dev.audio[i] = (struct audioplayer){audio->buf, audio->len}; } -audiodata audio_wav_load(audiodevice const* dev, char const* fpath) { - if (dev == NULL) return (audiodata){0}; - SDL_AudioSpec spec; - audiodata audio; - +audiodata audio_wav_load(char const* fpath) { debug("loading audio file '%s'...", fpath); if (faccess(fpath, FA_R)) { error("%s:%u audio file either isn't readable or doesn't exist. path: '%s'!", __FILE_NAME__, __LINE__, fpath); - return (audiodata){0}; + return (struct audiodata){0}; } - // load and parse the audio to the correct format + // load the audio + SDL_AudioSpec spec; + audiodata audio; SDL_LoadWAV(fpath, &spec, &audio.buf, &audio.len); - if (audio_cvt(dev, &spec, &audio.buf, &audio.len)) { - return (audiodata){0}; - } + if (!audio.buf) return audio; + + // convert the audio data to the format reflecting dev + void* ptr = audio_cvt(&spec, audio.buf, &audio.len); + if (!ptr) free(audio.buf); // free the buffer if NULL was returned; failure + audio.buf = ptr; // calculate the time in milliseconds of the audio fragment // by dividing the audio bytelength by the format's bitsize, by the audio device's channels and the audio device's frequency - audio.ms = (((1000 * audio.len) / (SDL_AUDIO_BITSIZE(dev->fmt) / 8)) / dev->channels / dev->freq); + audio.ms = (((1000 * audio.len) / (SDL_AUDIO_BITSIZE(dev.fmt) / 8)) / dev.ch / dev.freq); return audio; } diff --git a/src/io/audio.h b/src/io/audio.h index ed86912..2c34d69 100644 --- a/src/io/audio.h +++ b/src/io/audio.h @@ -3,33 +3,18 @@ #include #include +#define AUDIO_MAX 4 // maximum number of sound effects that are allowed to play at once + struct audiodata { uint8_t* buf; // pointer to the audio buffer uint32_t len; // length in bytes of the audio buffer uint32_t ms; // length in miliseconds of the audio buffer }; - -// contains the data of the audio fragments to be played -struct audioplayer { - struct audioplayer* nxt; // pointer to the next audioplayer (may be null) - uint8_t* buf; // pointer to the current item in the buffer to be played - uint32_t len; // the length in bytes that the buffer has remaining -}; - -struct audiodevice { - struct audioplayer* audio_players; - SDL_AudioDeviceID id; - int freq; - SDL_AudioFormat fmt; - uint8_t channels; -}; - typedef struct audiodata audiodata; -typedef struct audiodevice audiodevice; -audiodevice* audio_device_init(int, SDL_AudioFormat, uint8_t, uint16_t); -void audio_play(audiodevice*, audiodata const*); -void audio_device_free(audiodevice*); +int audio_device_init(int, SDL_AudioFormat, uint8_t, uint16_t); +void audio_device_free(void); +void audio_play(audiodata const*); -audiodata audio_wav_load(audiodevice const*, char const*); +audiodata audio_wav_load(char const*); void audio_wav_unload(audiodata*);