mirror of
https://github.com/thepigeongenerator/tetris_clone.git
synced 2025-12-17 05:55:46 +01:00
150 lines
4.3 KiB
C
150 lines
4.3 KiB
C
// TODO: audio stores audio buffers at indicies that the user specifies. (array growth is the next power of two from the idx)
|
|
#include "audio.h"
|
|
|
|
#include <SDL_audio.h>
|
|
#include <SDL_error.h>
|
|
#include <SDL_stdinc.h>
|
|
#include <assert.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#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) {
|
|
(void)userdata;
|
|
memset(stream, 0, len); // clear the playing audio
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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.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 = NULL;
|
|
|
|
unsigned id = SDL_OpenAudioDevice(NULL, 0, &spec, NULL, 0);
|
|
|
|
// create the audio device
|
|
dev = (struct audiodevice){
|
|
{0},
|
|
id,
|
|
freq,
|
|
fmt,
|
|
channels,
|
|
};
|
|
|
|
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_device_free(void) {
|
|
SDL_CloseAudioDevice(dev.id);
|
|
dev = (struct audiodevice){0};
|
|
}
|
|
|
|
void audio_play(audiodata const* audio) {
|
|
if (!dev.id) return;
|
|
if (!audio->buf) return;
|
|
|
|
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(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 (struct audiodata){0};
|
|
}
|
|
|
|
// load the audio
|
|
SDL_AudioSpec spec;
|
|
audiodata audio;
|
|
SDL_LoadWAV(fpath, &spec, &audio.buf, &audio.len);
|
|
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.ch / dev.freq);
|
|
|
|
return audio;
|
|
}
|
|
|
|
void audio_wav_unload(audiodata* audio) {
|
|
free(audio->buf);
|
|
*audio = (audiodata){0}; // zero out all audio data
|
|
}
|