365 lines
7.5 KiB
C
365 lines
7.5 KiB
C
// Copyright (C) 2011 Valeriano Alfonso Rodriguez (Kableado)
|
|
|
|
#ifdef WIN32
|
|
#define _WIN32_WINNT 0x0501
|
|
#include <windows.h>
|
|
#endif
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <SDL/SDL.h>
|
|
|
|
#include "Audio.h"
|
|
#include "Util.h"
|
|
|
|
static void Audio_MixerCallback(void *ud, Uint8 *stream, int l);
|
|
|
|
////////////////////////////////////////////////
|
|
// AudioWave //
|
|
///////////////
|
|
// Reference to a sound.
|
|
typedef struct TAudioWave TAudioWave, *AudioWave;
|
|
struct TAudioWave {
|
|
unsigned int sampleRate;
|
|
int channels;
|
|
int bpb;
|
|
int BPB;
|
|
Uint32 len;
|
|
Uint8 *buffer;
|
|
|
|
AudioWave next;
|
|
};
|
|
AudioWave _waves = NULL;
|
|
|
|
////////////////////////////////////////////////
|
|
// AudioChan //
|
|
///////////////
|
|
// Reference to a sound.
|
|
typedef struct TAudioChan TAudioChan, *AudioChan;
|
|
struct TAudioChan {
|
|
AudioWave wave;
|
|
Uint32 pos;
|
|
unsigned char rightvol;
|
|
unsigned char leftvol;
|
|
|
|
int loop;
|
|
|
|
AudioChan next;
|
|
};
|
|
AudioChan _channels = NULL;
|
|
AudioChan _free_channels = NULL;
|
|
|
|
/////////////////////////////
|
|
// Audio_Init
|
|
//
|
|
// Initializes the game audio.
|
|
int Audio_Init() {
|
|
SDL_AudioSpec as;
|
|
SDL_AudioSpec as2;
|
|
|
|
// Initialize audio subsistem
|
|
#ifdef WIN32
|
|
// Force DSound Driver on win32
|
|
putenv("SDL_AUDIODRIVER=dsound");
|
|
#endif
|
|
if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) {
|
|
Print("Audio_Init: Failure initializing SDL Audio.\n");
|
|
Print("\tSDL Error: %s\n", SDL_GetError());
|
|
return (0);
|
|
}
|
|
|
|
// Open the audio device using the desired parameters
|
|
as.freq = 44100;
|
|
as.format = AUDIO_S16SYS;
|
|
as.channels = 2;
|
|
as.samples = 2048;
|
|
as.callback = Audio_MixerCallback;
|
|
if (SDL_OpenAudio(&as, &as2) < 0) {
|
|
Print("Audio_Init: Failure opening audio.\n");
|
|
Print("\tSDL Error: %s\n", SDL_GetError());
|
|
return (0);
|
|
}
|
|
|
|
// Asert results
|
|
if (as2.format != AUDIO_S16SYS || as2.freq != 44100 || as2.channels != 2) {
|
|
Print("Audio_Init: Failure opening audio. (44.1Khz/16b/2c).\n");
|
|
SDL_CloseAudio();
|
|
return (0);
|
|
}
|
|
|
|
// Unpause and ready to go
|
|
SDL_PauseAudio(0);
|
|
|
|
return (1);
|
|
}
|
|
|
|
/////////////////////////////
|
|
// Audio_MixerCallback
|
|
//
|
|
// Mixes the audio channels.
|
|
static void Audio_MixerCallback(void *ud, Uint8 *stream, int l) {
|
|
signed short *ptr_out, *ptr_wave;
|
|
AudioChan prevchan;
|
|
AudioChan chan;
|
|
AudioWave wave;
|
|
int len = l / 4; // Asume 16bpb and 2 output chan
|
|
int chan_remain;
|
|
int len_mix;
|
|
int i;
|
|
|
|
// Clean
|
|
memset(stream, 0, l);
|
|
|
|
// Mix all the channels
|
|
prevchan = NULL;
|
|
chan = _channels;
|
|
while (chan) {
|
|
if (!chan->wave) {
|
|
// Remove finished channels
|
|
AudioChan aux_chan = chan->next;
|
|
chan->next = _free_channels;
|
|
_free_channels = chan;
|
|
chan = aux_chan;
|
|
if (prevchan) {
|
|
prevchan->next = chan;
|
|
} else {
|
|
_channels = chan;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Prepare the pointers
|
|
ptr_out = (signed short *)stream;
|
|
ptr_wave = ((signed short *)chan->wave->buffer) + chan->pos;
|
|
wave = chan->wave;
|
|
|
|
// Determine mixing lenght
|
|
chan_remain = wave->len - chan->pos;
|
|
if (chan_remain > len) {
|
|
len_mix = len;
|
|
} else {
|
|
if (chan->loop) {
|
|
len_mix = len;
|
|
} else {
|
|
len_mix = chan_remain;
|
|
}
|
|
chan->wave = NULL;
|
|
}
|
|
|
|
// Mix the buffer
|
|
for (i = 0; i < len_mix; i++) {
|
|
int temp;
|
|
|
|
// Left Channel
|
|
temp = ptr_out[0];
|
|
temp += (ptr_wave[0] * chan->leftvol) >> 8;
|
|
if (temp > (1 << 14)) {
|
|
ptr_out[0] = 1 << 14;
|
|
} else if (temp < -(1 << 14)) {
|
|
ptr_out[0] = -(1 << 14);
|
|
} else {
|
|
ptr_out[0] = temp;
|
|
}
|
|
|
|
// Right Channel
|
|
temp = ptr_out[1];
|
|
temp += (ptr_wave[0] * chan->rightvol) >> 8;
|
|
if (temp > (1 << 14)) {
|
|
ptr_out[1] = 1 << 14;
|
|
} else if (temp < -(1 << 14)) {
|
|
ptr_out[1] = -(1 << 14);
|
|
} else {
|
|
ptr_out[1] = temp;
|
|
}
|
|
|
|
// Next sample
|
|
ptr_out += 2;
|
|
if (ptr_wave >=
|
|
(((signed short *)wave->buffer) + (wave->len - 1))) {
|
|
ptr_wave = ((signed short *)wave->buffer);
|
|
} else {
|
|
ptr_wave++;
|
|
}
|
|
}
|
|
chan->pos += len_mix;
|
|
|
|
if (chan->wave == NULL && chan->loop == 1) {
|
|
chan->wave = wave;
|
|
while (chan->pos > wave->len) {
|
|
chan->pos -= wave->len;
|
|
}
|
|
}
|
|
|
|
// Next channel
|
|
prevchan = chan;
|
|
chan = chan->next;
|
|
}
|
|
}
|
|
|
|
/////////////////////////////
|
|
// Audio_Frame
|
|
//
|
|
// Notify a frame update to the audio subsystem.
|
|
void Audio_Frame() {}
|
|
|
|
/////////////////////////////
|
|
// Audio_LoadSound
|
|
//
|
|
// Loads a sound, giving a reference.
|
|
AudioSnd Audio_LoadSound(char *filename) {
|
|
FILE *f;
|
|
char id[5] = {0, 0, 0, 0, 0}, *sndBuffer = NULL;
|
|
short formatTag, channels, bitsPerSample;
|
|
int formatLen, sampleRate, dataSize;
|
|
|
|
f = fopen(filename, "rb");
|
|
if (!f) {
|
|
Print("Audio_LoadSound: Failure opening file.\n");
|
|
return (NULL);
|
|
}
|
|
|
|
// Read id "RIFF"
|
|
fread(id, 4, sizeof(char), f);
|
|
if (strcmp(id, "RIFF")) {
|
|
Print("Audio_LoadSound: File is not RIFF.\n");
|
|
fclose(f);
|
|
return (NULL);
|
|
}
|
|
|
|
// File size (-"RIFF")
|
|
fseek(f, 4, SEEK_CUR); // size
|
|
|
|
// Read id "WAVE"
|
|
fread(id, 4, sizeof(char), f);
|
|
if (strcmp(id, "WAVE")) {
|
|
Print("Audio_LoadSound: File is not WAVE.\n");
|
|
fclose(f);
|
|
return (NULL);
|
|
}
|
|
|
|
// Read the format
|
|
fread(id, 1, sizeof(char) * 4, f); // Read "fmt "
|
|
fread(&formatLen, 1, sizeof(int), f);
|
|
if (formatLen < 14) {
|
|
Print("Audio_LoadSound: File too short.\n");
|
|
fclose(f);
|
|
return (NULL);
|
|
}
|
|
fread(&formatTag, 1, sizeof(short), f); // 1=PCM
|
|
if (formatTag != 1) {
|
|
Print("Audio_LoadSound: Not PCM format.\n");
|
|
fclose(f);
|
|
return (NULL);
|
|
}
|
|
fread(&channels, 1, sizeof(short), f);
|
|
fread(&sampleRate, 1, sizeof(int), f);
|
|
fseek(f, 2, SEEK_CUR); // avgBytesSec
|
|
fseek(f, 2, SEEK_CUR); // blockAlign
|
|
fread(&bitsPerSample, 1, sizeof(short), f);
|
|
fseek(f, formatLen - 14, SEEK_CUR); // Align read
|
|
|
|
// Assert sound format
|
|
if (sampleRate != 44100 || channels != 1 || bitsPerSample != 2) {
|
|
Print("Audio_LoadSound: Format not supported: "
|
|
"sampleRate:%d; channels:%d; BPB:%d\n",
|
|
sampleRate, channels, bitsPerSample);
|
|
fclose(f);
|
|
return (NULL);
|
|
}
|
|
|
|
// Skip no "data" blocks
|
|
do {
|
|
int lenRead = fread(id, 1, sizeof(char) * 4, f);
|
|
if (lenRead < 4) {
|
|
break;
|
|
}
|
|
if (strcmp(id, "data")) {
|
|
fread(&dataSize, 1, sizeof(int), f);
|
|
fseek(f, dataSize, SEEK_CUR);
|
|
} else {
|
|
break;
|
|
}
|
|
} while (1);
|
|
if (strcmp(id, "data")) {
|
|
Print("Audio_LoadSound: DATA block not found\n");
|
|
fclose(f);
|
|
return (NULL);
|
|
}
|
|
|
|
// Read the "data" block
|
|
fread(&dataSize, 1, sizeof(int), f);
|
|
sndBuffer = malloc(sizeof(char) * dataSize);
|
|
fread(sndBuffer, dataSize, sizeof(char), f);
|
|
|
|
fclose(f);
|
|
|
|
// Build the wave object
|
|
AudioWave wave = malloc(sizeof(TAudioWave));
|
|
wave->sampleRate = sampleRate;
|
|
wave->channels = channels;
|
|
wave->buffer = (Uint8 *)sndBuffer;
|
|
wave->BPB = bitsPerSample;
|
|
wave->bpb = wave->BPB * 8;
|
|
wave->len = dataSize / (wave->BPB * wave->channels);
|
|
|
|
// Take a reference
|
|
wave->next = _waves;
|
|
_waves = wave;
|
|
|
|
return (wave);
|
|
}
|
|
|
|
/////////////////////////////
|
|
// Audio_PlaySound
|
|
//
|
|
// Loads a sound, giving a reference.
|
|
AudioChn Audio_PlaySound(AudioSnd snd, float leftvol, float rightvol,
|
|
int loop) {
|
|
AudioChan chan;
|
|
AudioWave wave;
|
|
if (!snd) {
|
|
return (NULL);
|
|
}
|
|
|
|
// Cast AudioSnd to AudioWave
|
|
wave = snd;
|
|
|
|
// Get a free channel
|
|
if (_free_channels) {
|
|
chan = _free_channels;
|
|
_free_channels = chan->next;
|
|
chan->next = NULL;
|
|
} else {
|
|
chan = malloc(sizeof(TAudioChan));
|
|
chan->next = NULL;
|
|
}
|
|
|
|
// Initialize the channel
|
|
chan->wave = wave;
|
|
chan->pos = 0;
|
|
chan->rightvol = (rightvol * 255);
|
|
chan->leftvol = (leftvol * 255);
|
|
chan->loop = loop;
|
|
|
|
// Include in sounds list
|
|
chan->next = _channels;
|
|
_channels = chan;
|
|
|
|
return chan;
|
|
}
|
|
|
|
/////////////////////////////
|
|
// Audio_StopChan
|
|
//
|
|
// Stops an audio chanel
|
|
void Audio_StopChan(AudioChn c) {
|
|
AudioChan chan;
|
|
chan = c;
|
|
if (c == NULL) {
|
|
return;
|
|
}
|
|
chan->loop = 0;
|
|
chan->wave = NULL;
|
|
}
|