blob: f0b896f7c2a064c7b26b4f7b6de43a90d14a6b59 [file] [log] [blame] [raw]
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "sound.h"
//#define LOG_AUDIO
#define SND_BUF_MAX_READ_AHEAD bufferMaxLeadInFrags
#define SND_LATENCY_IN_FRAGS bufferLatencyInFrags
// SDL specific
static SDL_AudioDeviceID dev;
static SDL_AudioSpec obtained, *audiohwspec;
//
static const unsigned int bufLenInMsec[] = { 20, 50, 100, 200, 10 };
static unsigned int bufLenIndex = 0;
static int bufferLatencyInFrags = 2;
static int bufferMaxLeadInFrags = 6;
static unsigned int BufferLengthInMsec;
//
static unsigned int MixingFreq;
static unsigned int BufferLength;
static unsigned int sndBufferPos;
static short *mixingBuffer;
static short *sndRingBuffer;
static unsigned int sndWriteBufferIndex;
static unsigned int sndPlayBufferIndex;
static short *sndWriteBufferPtr;
static short *sndPlayBufferPtr;
static short lastSample;
static ClockCycle lastUpdateCycle;
static unsigned int lastSamplePos;
template<> unsigned int LinkedList<SoundSource>::count = 0;
template<> SoundSource* LinkedList<SoundSource>::root = 0;
template<> SoundSource* LinkedList<SoundSource>::last = 0;
unsigned int SoundSource::sampleRate = SAMPLE_FREQ;
static unsigned int soundEnabled = 1;
static unsigned int soundPaused = 0;
void SoundSource::bufferFill(unsigned int nrsamples, short *buffer)
{
SoundSource *cb = SoundSource::getRoot();
if (cb && !soundPaused) {
cb->calcSamples(buffer, nrsamples);
cb = cb->getNext();
} else {
memset(buffer, 0, nrsamples * 2);
return;
}
// multiple sources
while (cb) {
int i = nrsamples - 1;
cb->calcSamples(mixingBuffer, nrsamples);
do {
buffer[i] += mixingBuffer[i];
} while(i--);
cb = cb->getNext();
}
}
static void add_new_frag()
{
sndWriteBufferIndex++;
sndWriteBufferPtr = sndRingBuffer
+ (BufferLength * (sndWriteBufferIndex % SND_BUF_MAX_READ_AHEAD));
}
static void delete_frag()
{
sndPlayBufferIndex++;
sndPlayBufferPtr = sndRingBuffer
+ (BufferLength * (sndPlayBufferIndex % SND_BUF_MAX_READ_AHEAD));
}
static int getLeadInFrags()
{
return (int) (sndWriteBufferIndex - sndPlayBufferIndex);
}
static void fragmentDone()
{
int lead_in_frags = getLeadInFrags();
#ifdef LOG_AUDIO
printf("Lead in frags: %i\n", lead_in_frags);
#endif
while (lead_in_frags < SND_LATENCY_IN_FRAGS) {
#ifdef LOG_AUDIO
fprintf(stderr, " adding an extra frag.\n");
#endif
SoundSource::bufferFill(BufferLength, sndWriteBufferPtr);
add_new_frag();
lead_in_frags++;
}
}
static void audioCallback(void *userdata, Uint8 *stream, int len)
{
if (sndPlayBufferIndex < sndWriteBufferIndex) {
if (len > (int)(BufferLength*2))
len = BufferLength * 2;
memcpy(stream, sndPlayBufferPtr, len);
lastSample = sndPlayBufferPtr[len/2 - 1];
delete_frag();
} else {
short *buf = (short *) stream;
len /= 2;
do {
*buf++ = lastSample;
} while(--len);
}
#ifdef LOG_AUDIO
fprintf(stderr, "Playing a frag (%i).\n", getLeadInFrags());
#endif
}
void updateAudio(unsigned int nrsamples)
{
// SDL openaudio failed?
if (!sndWriteBufferPtr)
return;
if (sndBufferPos + nrsamples >= BufferLength) {
if (getLeadInFrags() > SND_LATENCY_IN_FRAGS) {
#ifdef LOG_AUDIO
fprintf(stderr, "Skipping a frag.\n");
#endif
} else {
// Finish pending buffer...
SoundSource::bufferFill(BufferLength - sndBufferPos, sndWriteBufferPtr + sndBufferPos);
add_new_frag();
if ((sndBufferPos = (sndBufferPos + nrsamples) % BufferLength))
SoundSource::bufferFill(sndBufferPos, sndWriteBufferPtr);
fragmentDone();
}
} else if (nrsamples) {
SoundSource::bufferFill(nrsamples, sndWriteBufferPtr + sndBufferPos);
sndBufferPos += nrsamples;
}
//_ASSERT(sndBufferPos <= BufferLength);
}
static inline unsigned int getNrOfSamplesToGenerate(ClockCycle clock, unsigned int deviceFrq)
{
// OK this should really be INT but I'm tired right now
unsigned int samplePos = (unsigned int) ((double) clock * (double) MixingFreq / deviceFrq);
unsigned int samplesToDo = samplePos - lastSamplePos;
//fprintf( stderr, "Sound: %i cycles/%f samples\n", clock, (double) clock * (double) MixingFreq / deviceFrq);
// 'clock' might have been reset but 'lastSamplePos' not!
if (lastSamplePos > samplePos)
samplesToDo = 0;
lastSamplePos = samplePos;
return samplesToDo;
}
void flushBuffer(ClockCycle cycle, unsigned int frq)
{
updateAudio(getNrOfSamplesToGenerate(cycle, frq));
lastUpdateCycle = cycle;
}
static unsigned int calibrateAudioBufferSize(unsigned int msec, unsigned int sampleRate)
{
#ifdef __EMSCRIPTEN__
// Emscripten needs a buffer with a size of a power of 2
double x = msec * sampleRate / 1000.0;
return (unsigned int)pow(2, ceil(log(x) / log(2)));
#elif defined(_WIN32)
return (sampleRate / (1000 / msec));
#else
// Linux needs a buffer with a size of a factor of 512?
// 512 1024 2048 4096
double x = msec * sampleRate / 1000.0;
return (unsigned int)(x / 1024.0 + 0.5) * 1024.0;
#endif
}
// Emscripten requires audio buffers to be cleaned when stopped
void sound_reset()
{
for (unsigned int i = 0; i < SND_BUF_MAX_READ_AHEAD * BufferLength; i++)
sndRingBuffer[i] = audiohwspec->silence;
}
void init_audio(unsigned int sampleFrq)
{
SDL_AudioSpec desired;
if (sampleFrq < 11025 || sampleFrq > 192000) sampleFrq = SAMPLE_FREQ;
MixingFreq = sampleFrq;
BufferLengthInMsec = bufLenInMsec[bufLenIndex];
BufferLength = calibrateAudioBufferSize(BufferLengthInMsec, MixingFreq);
if (BufferLength < 512) BufferLength = 512;
desired.freq = MixingFreq;
desired.format = AUDIO_S16;
desired.channels = 1;
desired.samples = BufferLength;
desired.callback = audioCallback;
desired.userdata = NULL;
desired.size = desired.channels * desired.samples * sizeof(Uint8);
desired.silence = 0x00;
mixingBuffer = new short[BufferLength];
if (!mixingBuffer)
return;
dev = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
if (!dev) {
fprintf(stderr, "SDL_OpenAudioDevice failed!\n");
return;
} else {
fprintf(stderr, "SDL_OpenAudioDevice success!\n");
fprintf(stderr, "Using audio driver : %s\n", SDL_GetCurrentAudioDriver());
audiohwspec = &obtained;
}
MixingFreq = audiohwspec->freq;
BufferLength = audiohwspec->samples;
fprintf(stderr, "Obtained mixing frequency: %u\n", audiohwspec->freq);
fprintf(stderr, "Obtained audio format: %04X\n", audiohwspec->format);
fprintf(stderr, "Obtained channel number: %u\n", audiohwspec->channels);
fprintf(stderr, "Obtained audio buffer size: %u\n", audiohwspec->size);
fprintf(stderr, "Obtained sample buffer size: %u\n", audiohwspec->samples);
fprintf(stderr, "Obtained silence value: %u\n", audiohwspec->silence);
sndRingBuffer = new short[SND_BUF_MAX_READ_AHEAD * BufferLength];
for(unsigned int i = 0; i < SND_BUF_MAX_READ_AHEAD * BufferLength; i++)
sndRingBuffer[i] = audiohwspec->silence;
sndWriteBufferIndex = SND_LATENCY_IN_FRAGS;
sndPlayBufferIndex = 0;
sndWriteBufferPtr = sndRingBuffer + BufferLength * SND_LATENCY_IN_FRAGS;
sndPlayBufferPtr = sndRingBuffer;
sndBufferPos = 0;
lastSample = 0;
lastUpdateCycle = 0;
lastSamplePos = 0;
sound_resume();
}
void sound_pause()
{
#ifdef __EMSCRIPTEN__
soundPaused = 1;
#else
SDL_PauseAudioDevice(dev, 1);
#endif
}
void sound_resume()
{
soundPaused = 0;
SDL_PauseAudioDevice(dev, !soundEnabled);
}
void sound_change_freq(unsigned int &newFreq)
{
close_audio();
SoundSource *cb = SoundSource::getRoot();
while (cb) {
cb->setSampleRate(newFreq);
cb = cb->getNext();
}
init_audio(newFreq);
newFreq = audiohwspec->freq;
SoundSource::setSamplingRate(newFreq);
}
void close_audio()
{
SDL_PauseAudioDevice(dev, 1);
SDL_CloseAudioDevice(dev);
delete[] sndRingBuffer;
delete[] mixingBuffer;
}
//-- sound options management
static void flipAudioBufferSize(void *none)
{
const unsigned int arraySize = sizeof(bufLenInMsec) / sizeof(bufLenInMsec[0]);
close_audio();
bufLenIndex = (bufLenIndex + 1) % arraySize;
init_audio(MixingFreq);
}
static void flipAudioFrequency(void *none)
{
const unsigned int frq[] = { 48000, 96000, 192000, 22050, 44100 };
const unsigned int current = MixingFreq;
const unsigned int entries = sizeof(frq) / sizeof(frq[0]);
int i = entries;
bool found;
do {
i -= 1;
found = frq[i] == current;
} while (i && !found);
i = (i + 1) % entries;
unsigned int rate = frq[i];
sound_change_freq(rate);
}
rvar_t soundSettings[] = {
{ "Sound enabled", "SoundOn", NULL, &soundEnabled, RVAR_TOGGLE, NULL },
#ifndef __EMSCRIPTEN__
{ "Audio frequency", "SoundFrequency", flipAudioFrequency, &MixingFreq, RVAR_INT, NULL },
{ "Audio buffer length in msec", "AudioBufferLength", flipAudioBufferSize, &BufferLengthInMsec, RVAR_INT, NULL },
#endif
{ NULL, NULL, NULL, NULL, NULL }
};