diff options
Diffstat (limited to 'src/libs/sound/mixer')
-rw-r--r-- | src/libs/sound/mixer/Makeinfo | 3 | ||||
-rw-r--r-- | src/libs/sound/mixer/mixer.c | 1760 | ||||
-rw-r--r-- | src/libs/sound/mixer/mixer.h | 274 | ||||
-rw-r--r-- | src/libs/sound/mixer/mixerint.h | 110 | ||||
-rw-r--r-- | src/libs/sound/mixer/nosound/Makeinfo | 2 | ||||
-rw-r--r-- | src/libs/sound/mixer/nosound/audiodrv_nosound.c | 410 | ||||
-rw-r--r-- | src/libs/sound/mixer/nosound/audiodrv_nosound.h | 69 | ||||
-rw-r--r-- | src/libs/sound/mixer/sdl/Makeinfo | 2 | ||||
-rw-r--r-- | src/libs/sound/mixer/sdl/audiodrv_sdl.c | 486 | ||||
-rw-r--r-- | src/libs/sound/mixer/sdl/audiodrv_sdl.h | 66 |
10 files changed, 3182 insertions, 0 deletions
diff --git a/src/libs/sound/mixer/Makeinfo b/src/libs/sound/mixer/Makeinfo new file mode 100644 index 0000000..66f960d --- /dev/null +++ b/src/libs/sound/mixer/Makeinfo @@ -0,0 +1,3 @@ +uqm_SUBDIRS="sdl nosound" +uqm_CFILES="mixer.c" +uqm_HFILES="mixer.h mixerint.h" diff --git a/src/libs/sound/mixer/mixer.c b/src/libs/sound/mixer/mixer.c new file mode 100644 index 0000000..3e14ddd --- /dev/null +++ b/src/libs/sound/mixer/mixer.c @@ -0,0 +1,1760 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Mixer for low-level sound output drivers + */ + +#include <stdio.h> +#include <string.h> +#include <math.h> +#include "mixer.h" +#include "mixerint.h" +#include "libs/misc.h" +#include "libs/threadlib.h" +#include "libs/log.h" +#include "libs/memlib.h" + +static uint32 mixer_initialized = 0; +static uint32 mixer_format; +static uint32 mixer_chansize; +static uint32 mixer_sampsize; +static uint32 mixer_freq; +static uint32 mixer_channels; +static uint32 last_error = MIX_NO_ERROR; +static mixer_Quality mixer_quality; +static mixer_Resampling mixer_resampling; +static mixer_Flags mixer_flags; + +/* when locking more than one mutex + * you must lock them in this order + */ +static RecursiveMutex src_mutex; +static RecursiveMutex buf_mutex; +static RecursiveMutex act_mutex; + +#define MAX_SOURCES 8 +mixer_Source *active_sources[MAX_SOURCES]; + + +/************************************************* + * Internals + */ + +static void +mixer_SetError (uint32 error) +{ + last_error = error; +} + + +/************************************************* + * General interface + */ + +uint32 +mixer_GetError (void) +{ + uint32 error = last_error; + last_error = MIX_NO_ERROR; + return error; +} + +/* Initialize the mixer with a certain audio format */ +bool +mixer_Init (uint32 frequency, uint32 format, mixer_Quality quality, + mixer_Flags flags) +{ + if (mixer_initialized) + mixer_Uninit (); + + last_error = MIX_NO_ERROR; + memset (active_sources, 0, sizeof(mixer_Source*) * MAX_SOURCES); + + mixer_chansize = MIX_FORMAT_BPC (format); + mixer_channels = MIX_FORMAT_CHANS (format); + mixer_sampsize = MIX_FORMAT_SAMPSIZE (format); + mixer_freq = frequency; + mixer_quality = quality; + mixer_format = format; + mixer_flags = flags; + + mixer_resampling.None = mixer_ResampleNone; + mixer_resampling.Downsample = mixer_ResampleNearest; + if (mixer_quality == MIX_QUALITY_DEFAULT) + mixer_resampling.Upsample = mixer_UpsampleLinear; + else if (mixer_quality == MIX_QUALITY_HIGH) + mixer_resampling.Upsample = mixer_UpsampleCubic; + else + mixer_resampling.Upsample = mixer_ResampleNearest; + + src_mutex = CreateRecursiveMutex("mixer_SourceMutex", SYNC_CLASS_AUDIO); + buf_mutex = CreateRecursiveMutex("mixer_BufferMutex", SYNC_CLASS_AUDIO); + act_mutex = CreateRecursiveMutex("mixer_ActiveMutex", SYNC_CLASS_AUDIO); + + mixer_initialized = 1; + + return true; +} + +/* Uninitialize the mixer */ +void +mixer_Uninit (void) +{ + if (mixer_initialized) + { + DestroyRecursiveMutex (src_mutex); + DestroyRecursiveMutex (buf_mutex); + DestroyRecursiveMutex (act_mutex); + mixer_initialized = 0; + } +} + + +/********************************************************** + * THE mixer + * + */ + +void +mixer_MixChannels (void *userdata, uint8 *stream, sint32 len) +{ + uint8 *end_stream = stream + len; + bool left = true; + + /* keep this order or die */ + LockRecursiveMutex (src_mutex); + LockRecursiveMutex (buf_mutex); + LockRecursiveMutex (act_mutex); + + for (; stream < end_stream; stream += mixer_chansize) + { + uint32 i; + float fullsamp = 0; + + for (i = 0; i < MAX_SOURCES; i++) + { + mixer_Source *src; + float samp = 0; + + /* find next source */ + for (; i < MAX_SOURCES && ( + (src = active_sources[i]) == 0 + || src->state != MIX_PLAYING + || !mixer_SourceGetNextSample (src, &samp, left)); + i++) + ; + + if (i < MAX_SOURCES) + { + /* sample acquired */ + fullsamp += samp; + } + } + + /* clip the sample */ + if (mixer_chansize == 2) + { + /* check S16 clipping */ + if (fullsamp > SINT16_MAX) + fullsamp = SINT16_MAX; + else if (fullsamp < SINT16_MIN) + fullsamp = SINT16_MIN; + } + else + { + /* check S8 clipping */ + if (fullsamp > SINT8_MAX) + fullsamp = SINT8_MAX; + else if (fullsamp < SINT8_MIN) + fullsamp = SINT8_MIN; + } + + mixer_PutSampleExt (stream, mixer_chansize, (sint32)fullsamp); + if (mixer_channels == 2) + left = !left; + } + + /* keep this order or die */ + UnlockRecursiveMutex (act_mutex); + UnlockRecursiveMutex (buf_mutex); + UnlockRecursiveMutex (src_mutex); + + (void) userdata; // satisfying compiler - unused arg +} + +/* fake mixer -- only process buffer and source states */ +void +mixer_MixFake (void *userdata, uint8 *stream, sint32 len) +{ + uint8 *end_stream = stream + len; + bool left = true; + + /* keep this order or die */ + LockRecursiveMutex (src_mutex); + LockRecursiveMutex (buf_mutex); + LockRecursiveMutex (act_mutex); + + for (; stream < end_stream; stream += mixer_chansize) + { + uint32 i; + + for (i = 0; i < MAX_SOURCES; i++) + { + mixer_Source *src; + float samp; + + /* find next source */ + for (; i < MAX_SOURCES && ( + (src = active_sources[i]) == 0 + || src->state != MIX_PLAYING + || !mixer_SourceGetFakeSample (src, &samp, left)); + i++) + ; + } + if (mixer_channels == 2) + left = !left; + } + + /* keep this order or die */ + UnlockRecursiveMutex (act_mutex); + UnlockRecursiveMutex (buf_mutex); + UnlockRecursiveMutex (src_mutex); + + (void) userdata; // satisfying compiler - unused arg +} + + +/************************************************* + * Sources interface + */ + +/* generate n sources */ +void +mixer_GenSources (uint32 n, mixer_Object *psrcobj) +{ + if (n == 0) + return; /* do nothing per OpenAL */ + + if (!psrcobj) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_GenSources() called with null ptr"); + return; + } + for (; n; n--, psrcobj++) + { + mixer_Source *src; + + src = (mixer_Source *) HMalloc (sizeof (mixer_Source)); + src->magic = mixer_srcMagic; + src->locked = false; + src->state = MIX_INITIAL; + src->looping = false; + src->gain = MIX_GAIN_ADJ; + src->cqueued = 0; + src->cprocessed = 0; + src->firstqueued = 0; + src->nextqueued = 0; + src->prevqueued = 0; + src->lastqueued = 0; + src->pos = 0; + src->count = 0; + + *psrcobj = (mixer_Object) src; + } +} + +/* delete n sources */ +void +mixer_DeleteSources (uint32 n, mixer_Object *psrcobj) +{ + uint32 i; + mixer_Object *pcurobj; + + if (n == 0) + return; /* do nothing per OpenAL */ + + if (!psrcobj) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_DeleteSources() called with null ptr"); + return; + } + + LockRecursiveMutex (src_mutex); + + /* check to make sure we can delete all sources */ + for (i = n, pcurobj = psrcobj; i && pcurobj; i--, pcurobj++) + { + mixer_Source *src = (mixer_Source *) *pcurobj; + + if (!src) + continue; + + if (src->magic != mixer_srcMagic) + break; + } + + if (i) + { /* some source failed */ + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_DeleteSources(): not a source"); + } + else + { /* all sources checked out */ + for (; n; n--, psrcobj++) + { + mixer_Source *src = (mixer_Source *) *psrcobj; + + if (!src) + continue; + + /* stopping should not be necessary + * under ideal circumstances + */ + if (src->state != MIX_INITIAL) + mixer_SourceStop_internal (src); + + /* unqueueing should not be necessary + * under ideal circumstances + */ + mixer_SourceUnqueueAll (src); + HFree (src); + *psrcobj = 0; + } + } + + UnlockRecursiveMutex (src_mutex); +} + +/* check if really is a source */ +bool +mixer_IsSource (mixer_Object srcobj) +{ + mixer_Source *src = (mixer_Source *) srcobj; + bool ret; + + if (!src) + return false; + + LockRecursiveMutex (src_mutex); + ret = src->magic == mixer_srcMagic; + UnlockRecursiveMutex (src_mutex); + + return ret; +} + +/* set source integer property */ +void +mixer_Sourcei (mixer_Object srcobj, mixer_SourceProp pname, + mixer_IntVal value) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_Sourcei() called with null source"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_Sourcei(): not a source"); + } + else + { + switch (pname) + { + case MIX_LOOPING: + src->looping = value; + break; + case MIX_BUFFER: + { + mixer_Buffer *buf = (mixer_Buffer *) value; + + if (src->cqueued > 0) + mixer_SourceUnqueueAll (src); + + if (buf && !mixer_CheckBufferState (buf, "mixer_Sourcei")) + break; + + src->firstqueued = buf; + src->nextqueued = src->firstqueued; + src->prevqueued = 0; + src->lastqueued = src->nextqueued; + if (src->lastqueued) + src->lastqueued->next = 0; + src->cqueued = 1; + } + break; + case MIX_SOURCE_STATE: + if (value == MIX_INITIAL) + { + mixer_SourceRewind_internal (src); + } + else + { + log_add (log_Debug, "mixer_Sourcei(MIX_SOURCE_STATE): " + "unsupported state, call ignored"); + } + break; + default: + mixer_SetError (MIX_INVALID_ENUM); + log_add (log_Debug, "mixer_Sourcei() called " + "with unsupported property %u", pname); + } + } + + UnlockRecursiveMutex (src_mutex); +} + +/* set source float property */ +void +mixer_Sourcef (mixer_Object srcobj, mixer_SourceProp pname, float value) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_Sourcef() called with null source"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_Sourcef(): not a source"); + } + else + { + switch (pname) + { + case MIX_GAIN: + src->gain = value * MIX_GAIN_ADJ; + break; + default: + log_add (log_Debug, "mixer_Sourcei() called " + "with unsupported property %u", pname); + } + } + + UnlockRecursiveMutex (src_mutex); +} + +/* set source float array property (CURRENTLY NOT IMPLEMENTED) */ +void mixer_Sourcefv (mixer_Object srcobj, mixer_SourceProp pname, float *value) +{ + (void)srcobj; + (void)pname; + (void)value; +} + + +/* get source integer property */ +void +mixer_GetSourcei (mixer_Object srcobj, mixer_SourceProp pname, + mixer_IntVal *value) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src || !value) + { + mixer_SetError (src ? MIX_INVALID_VALUE : MIX_INVALID_NAME); + log_add (log_Debug, "mixer_GetSourcei() called with null param"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_GetSourcei(): not a source"); + } + else + { + switch (pname) + { + case MIX_LOOPING: + *value = src->looping; + break; + case MIX_BUFFER: + *value = (mixer_IntVal) src->firstqueued; + break; + case MIX_SOURCE_STATE: + *value = src->state; + break; + case MIX_BUFFERS_QUEUED: + *value = src->cqueued; + break; + case MIX_BUFFERS_PROCESSED: + *value = src->cprocessed; + break; + default: + mixer_SetError (MIX_INVALID_ENUM); + log_add (log_Debug, "mixer_GetSourcei() called " + "with unsupported property %u", pname); + } + } + + UnlockRecursiveMutex (src_mutex); +} + +/* get source float property */ +void +mixer_GetSourcef (mixer_Object srcobj, mixer_SourceProp pname, + float *value) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src || !value) + { + mixer_SetError (src ? MIX_INVALID_VALUE : MIX_INVALID_NAME); + log_add (log_Debug, "mixer_GetSourcef() called with null param"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_GetSourcef(): not a source"); + } + else + { + switch (pname) + { + case MIX_GAIN: + *value = src->gain / MIX_GAIN_ADJ; + break; + default: + log_add (log_Debug, "mixer_GetSourcef() called " + "with unsupported property %u", pname); + } + } + + UnlockRecursiveMutex (src_mutex); +} + +/* start the source; add it to active array */ +void +mixer_SourcePlay (mixer_Object srcobj) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourcePlay() called with null source"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourcePlay(): not a source"); + } + else /* should make the source active */ + { + if (src->state < MIX_PLAYING) + { + if (src->firstqueued && !src->nextqueued) + mixer_SourceRewind_internal (src); + mixer_SourceActivate (src); + } + src->state = MIX_PLAYING; + } + + UnlockRecursiveMutex (src_mutex); +} + +/* stop the source; remove it from active array and requeue buffers */ +void +mixer_SourceRewind (mixer_Object srcobj) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourceRewind() called with null source"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourcePlay(): not a source"); + } + else + { + mixer_SourceRewind_internal (src); + } + + UnlockRecursiveMutex (src_mutex); +} + +/* pause the source; keep in active array */ +void +mixer_SourcePause (mixer_Object srcobj) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourcePause() called with null source"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourcePause(): not a source"); + } + else /* should keep all buffers and offsets */ + { + if (src->state < MIX_PLAYING) + mixer_SourceActivate (src); + src->state = MIX_PAUSED; + } + + UnlockRecursiveMutex (src_mutex); +} + +/* stop the source; remove it from active array + * and unqueue 'queued' buffers + */ +void +mixer_SourceStop (mixer_Object srcobj) +{ + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourceStop() called with null source"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourceStop(): not a source"); + } + else /* should remove queued buffers */ + { + if (src->state >= MIX_PLAYING) + mixer_SourceDeactivate (src); + mixer_SourceStop_internal (src); + src->state = MIX_STOPPED; + } + + UnlockRecursiveMutex (src_mutex); +} + +/* queue buffers on the source */ +void +mixer_SourceQueueBuffers (mixer_Object srcobj, uint32 n, + mixer_Object* pbufobj) +{ + uint32 i; + mixer_Object* pobj; + mixer_Source *src = (mixer_Source *) srcobj; + + if (!src || !pbufobj) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourceQueueBuffers() called " + "with null param"); + return; + } + + LockRecursiveMutex (buf_mutex); + /* check to make sure we can safely queue all buffers */ + for (i = n, pobj = pbufobj; i; i--, pobj++) + { + mixer_Buffer *buf = (mixer_Buffer *) *pobj; + if (!buf || !mixer_CheckBufferState (buf, + "mixer_SourceQueueBuffers")) + { + break; + } + } + UnlockRecursiveMutex (buf_mutex); + + if (i == 0) + { /* all buffers checked out */ + LockRecursiveMutex (src_mutex); + LockRecursiveMutex (buf_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourceQueueBuffers(): not a source"); + } + else + { + for (i = n, pobj = pbufobj; i; i--, pobj++) + { + mixer_Buffer *buf = (mixer_Buffer *) *pobj; + + /* add buffer to the chain */ + if (src->lastqueued) + src->lastqueued->next = buf; + src->lastqueued = buf; + + if (!src->firstqueued) + { + src->firstqueued = buf; + src->nextqueued = buf; + src->prevqueued = 0; + } + src->cqueued++; + buf->state = MIX_BUF_QUEUED; + } + } + + UnlockRecursiveMutex (buf_mutex); + UnlockRecursiveMutex (src_mutex); + } +} + +/* unqueue buffers from the source */ +void +mixer_SourceUnqueueBuffers (mixer_Object srcobj, uint32 n, + mixer_Object* pbufobj) +{ + uint32 i; + mixer_Source *src = (mixer_Source *) srcobj; + mixer_Buffer *curbuf = 0; + + if (!src || !pbufobj) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourceUnqueueBuffers() called " + "with null source"); + return; + } + + LockRecursiveMutex (src_mutex); + + if (src->magic != mixer_srcMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_SourceUnqueueBuffers(): not a source"); + } + else if (n > src->cqueued) + { + mixer_SetError (MIX_INVALID_OPERATION); + } + else + { + LockRecursiveMutex (buf_mutex); + + /* check to make sure we can unqueue all buffers */ + for (i = n, curbuf = src->firstqueued; + i && curbuf && curbuf->state != MIX_BUF_PLAYING; + i--, curbuf = curbuf->next) + ; + + if (i) + { + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "mixer_SourceUnqueueBuffers(): " + "active buffer attempted"); + } + else + { /* all buffers checked out */ + for (i = n; i; i--, pbufobj++) + { + mixer_Buffer *buf = src->firstqueued; + + /* remove buffer from the chain */ + if (src->nextqueued == buf) + src->nextqueued = buf->next; + if (src->prevqueued == buf) + src->prevqueued = 0; + if (src->lastqueued == buf) + src->lastqueued = 0; + src->firstqueued = buf->next; + src->cqueued--; + + if (buf->state == MIX_BUF_PROCESSED) + src->cprocessed--; + + buf->state = MIX_BUF_FILLED; + buf->next = 0; + *pbufobj = (mixer_Object) buf; + } + } + + UnlockRecursiveMutex (buf_mutex); + } + + UnlockRecursiveMutex (src_mutex); +} + +/************************************************* + * Sources internals + */ + +static void +mixer_SourceUnqueueAll (mixer_Source *src) +{ + mixer_Buffer *buf; + mixer_Buffer *nextbuf; + + if (!src) + { + log_add (log_Debug, "mixer_SourceUnqueueAll() called " + "with null source"); + return; + } + + LockRecursiveMutex (buf_mutex); + + for (buf = src->firstqueued; buf; buf = nextbuf) + { + if (buf->state == MIX_BUF_PLAYING) + { + log_add (log_Debug, "mixer_SourceUnqueueAll(): " + "attempted on active buffer"); + } + nextbuf = buf->next; + buf->state = MIX_BUF_FILLED; + buf->next = 0; + } + + UnlockRecursiveMutex (buf_mutex); + + src->firstqueued = 0; + src->nextqueued = 0; + src->prevqueued = 0; + src->lastqueued = 0; + src->cqueued = 0; + src->cprocessed = 0; + src->pos = 0; + src->count = 0; +} + +/* add the source to the active array */ +static void +mixer_SourceActivate (mixer_Source* src) +{ + uint32 i; + + LockRecursiveMutex (act_mutex); + + /* check active sources, see if this source is there already */ + for (i = 0; i < MAX_SOURCES && active_sources[i] != src; i++) + ; + if (i < MAX_SOURCES) + { /* source found */ + log_add (log_Debug, "mixer_SourceActivate(): " + "source already active in slot %u", i); + UnlockRecursiveMutex (act_mutex); + return; + } + + /* find an empty slot */ + for (i = 0; i < MAX_SOURCES && active_sources[i] != 0; i++) + ; + if (i < MAX_SOURCES) + { /* slot found */ + active_sources[i] = src; + } + else + { + log_add (log_Debug, "mixer_SourceActivate(): " + "no more slots available (max=%d)", MAX_SOURCES); + } + + UnlockRecursiveMutex (act_mutex); +} + +/* remove the source from the active array */ +static void +mixer_SourceDeactivate (mixer_Source* src) +{ + uint32 i; + + LockRecursiveMutex (act_mutex); + + /* check active sources, see if this source is there */ + for (i = 0; i < MAX_SOURCES && active_sources[i] != src; i++) + ; + if (i < MAX_SOURCES) + { /* source found */ + active_sources[i] = 0; + } + else + { /* source not found */ + log_add (log_Debug, "mixer_SourceDeactivate(): source not active"); + } + + UnlockRecursiveMutex (act_mutex); +} + +static void +mixer_SourceStop_internal (mixer_Source *src) +{ + mixer_Buffer *buf; + mixer_Buffer *nextbuf; + + if (!src->firstqueued) + return; + + /* assert the source buffers state */ + if (!src->lastqueued) + { + log_add (log_Debug, "mixer_SourceStop_internal(): " + "desynced source state"); +#ifdef DEBUG + explode (); +#endif + } + + LockRecursiveMutex (buf_mutex); + + /* find last 'processed' buffer */ + for (buf = src->firstqueued; + buf && buf->next && buf->next != src->nextqueued; + buf = buf->next) + ; + src->lastqueued = buf; + if (buf) + buf->next = 0; /* break the chain */ + + /* unqueue all 'queued' buffers */ + for (buf = src->nextqueued; buf; buf = nextbuf) + { + nextbuf = buf->next; + buf->state = MIX_BUF_FILLED; + buf->next = 0; + src->cqueued--; + } + + if (src->cqueued == 0) + { /* all buffers were removed */ + src->firstqueued = 0; + src->lastqueued = 0; + } + src->nextqueued = 0; + src->prevqueued = 0; + src->pos = 0; + src->count = 0; + + UnlockRecursiveMutex (buf_mutex); +} + +static void +mixer_SourceRewind_internal (mixer_Source *src) +{ + /* should change the processed buffers to queued */ + mixer_Buffer *buf; + + if (src->state >= MIX_PLAYING) + mixer_SourceDeactivate (src); + + LockRecursiveMutex (buf_mutex); + + for (buf = src->firstqueued; + buf && buf->state != MIX_BUF_QUEUED; + buf = buf->next) + { + buf->state = MIX_BUF_QUEUED; + } + + UnlockRecursiveMutex (buf_mutex); + + src->pos = 0; + src->count = 0; + src->cprocessed = 0; + src->nextqueued = src->firstqueued; + src->prevqueued = 0; + src->state = MIX_INITIAL; +} + +/* get the sample next in queue in internal format */ +static inline bool +mixer_SourceGetNextSample (mixer_Source *src, float *psamp, bool left) +{ + /* fake the data if requested */ + if (mixer_flags & MIX_FAKE_DATA) + return mixer_SourceGetFakeSample (src, psamp, left); + + while (src->nextqueued) + { + mixer_Buffer *buf = src->nextqueued; + + if (!buf->data || buf->size < mixer_sampsize) + { + /* buffer invalid, go next */ + buf->state = MIX_BUF_PROCESSED; + src->pos = 0; + src->nextqueued = src->nextqueued->next; + src->cprocessed++; + continue; + } + + if (!left && buf->orgchannels == 1) + { + /* mono source so we can copy left channel to right */ + *psamp = src->samplecache; + } + else + { + *psamp = src->samplecache = buf->Resample(src, left) * src->gain; + } + + if (src->pos < buf->size || + (left && buf->sampsize != mixer_sampsize)) + { + buf->state = MIX_BUF_PLAYING; + } + else + { + /* buffer exhausted, go next */ + buf->state = MIX_BUF_PROCESSED; + src->pos = 0; + src->prevqueued = src->nextqueued; + src->nextqueued = src->nextqueued->next; + src->cprocessed++; + } + + return true; + } + + /* no more playable buffers */ + if (src->state >= MIX_PLAYING) + mixer_SourceDeactivate (src); + + src->state = MIX_STOPPED; + + return false; +} + +/* fake the next sample, but process buffers and states */ +static inline bool +mixer_SourceGetFakeSample (mixer_Source *src, float *psamp, bool left) +{ + while (src->nextqueued) + { + mixer_Buffer *buf = src->nextqueued; + + if (left || buf->orgchannels != 1) + { + if (mixer_freq == buf->orgfreq) + src->pos += mixer_chansize; + else + mixer_SourceAdvance(src, left); + } + *psamp = 0; + + if (src->pos < buf->size || + (left && buf->sampsize != mixer_sampsize)) + { + buf->state = MIX_BUF_PLAYING; + } + else + { + /* buffer exhausted, go next */ + buf->state = MIX_BUF_PROCESSED; + src->pos = 0; + src->prevqueued = src->nextqueued; + src->nextqueued = src->nextqueued->next; + src->cprocessed++; + } + + return true; + } + + /* no more playable buffers */ + if (src->state >= MIX_PLAYING) + mixer_SourceDeactivate (src); + + src->state = MIX_STOPPED; + + return false; +} + +/* advance position in currently queued buffer */ +static inline uint32 +mixer_SourceAdvance (mixer_Source *src, bool left) +{ + mixer_Buffer *curr = src->nextqueued; + if (curr->orgchannels == 2 && mixer_channels == 2) + { + if (!left) + { + src->pos += curr->high; + src->count += curr->low; + if (src->count > UINT16_MAX) + { + src->count -= UINT16_MAX; + src->pos += curr->sampsize; + } + return mixer_chansize; + } + } + else + { + src->pos += curr->high; + src->count += curr->low; + if (src->count > UINT16_MAX) + { + src->count -= UINT16_MAX; + src->pos += curr->sampsize; + } + } + return 0; +} + + +/************************************************* + * Buffers interface + */ + +/* generate n buffer objects */ +void +mixer_GenBuffers (uint32 n, mixer_Object *pbufobj) +{ + if (n == 0) + return; /* do nothing per OpenAL */ + + if (!pbufobj) + { + mixer_SetError (MIX_INVALID_VALUE); + log_add (log_Debug, "mixer_GenBuffers() called with null ptr"); + return; + } + for (; n; n--, pbufobj++) + { + mixer_Buffer *buf; + + buf = (mixer_Buffer *) HMalloc (sizeof (mixer_Buffer)); + buf->magic = mixer_bufMagic; + buf->locked = false; + buf->state = MIX_BUF_INITIAL; + buf->data = 0; + buf->size = 0; + buf->next = 0; + buf->orgdata = 0; + buf->orgfreq = 0; + buf->orgsize = 0; + buf->orgchannels = 0; + buf->orgchansize = 0; + + *pbufobj = (mixer_Object) buf; + } +} + +/* delete n buffer objects */ +void +mixer_DeleteBuffers (uint32 n, mixer_Object *pbufobj) +{ + uint32 i; + mixer_Object *pcurobj; + + if (n == 0) + return; /* do nothing per OpenAL */ + + if (!pbufobj) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_DeleteBuffers() called with null ptr"); + return; + } + + LockRecursiveMutex (buf_mutex); + + /* check to make sure we can delete all buffers */ + for (i = n, pcurobj = pbufobj; i && pcurobj; i--, pcurobj++) + { + mixer_Buffer *buf = (mixer_Buffer *) *pcurobj; + + if (!buf) + continue; + + if (buf->magic != mixer_bufMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_DeleteBuffers(): not a buffer"); + break; + } + else if (buf->locked) + { + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "mixer_DeleteBuffers(): locked buffer"); + break; + } + else if (buf->state >= MIX_BUF_QUEUED) + { + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "mixer_DeleteBuffers(): " + "attempted on queued/active buffer"); + break; + } + } + + if (i == 0) + { + /* all buffers check out */ + for (; n; n--, pbufobj++) + { + mixer_Buffer *buf = (mixer_Buffer *) *pbufobj; + + if (!buf) + continue; + + if (buf->data) + HFree (buf->data); + HFree (buf); + + *pbufobj = 0; + } + } + UnlockRecursiveMutex (buf_mutex); +} + +/* check if really a buffer object */ +bool +mixer_IsBuffer (mixer_Object bufobj) +{ + mixer_Buffer *buf = (mixer_Buffer *) bufobj; + bool ret; + + if (!buf) + return false; + + LockRecursiveMutex (buf_mutex); + ret = buf->magic == mixer_bufMagic; + UnlockRecursiveMutex (buf_mutex); + + return ret; +} + +/* get buffer property */ +void +mixer_GetBufferi (mixer_Object bufobj, mixer_BufferProp pname, + mixer_IntVal *value) +{ + mixer_Buffer *buf = (mixer_Buffer *) bufobj; + + if (!buf || !value) + { + mixer_SetError (buf ? MIX_INVALID_VALUE : MIX_INVALID_NAME); + log_add (log_Debug, "mixer_GetBufferi() called with null param"); + return; + } + + LockRecursiveMutex (buf_mutex); + + if (buf->locked) + { + UnlockRecursiveMutex (buf_mutex); + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "mixer_GetBufferi() called with locked buffer"); + return; + } + + if (buf->magic != mixer_bufMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_GetBufferi(): not a buffer"); + } + else + { + /* Return original buffer values + */ + switch (pname) + { + case MIX_FREQUENCY: + *value = buf->orgfreq; + break; + case MIX_BITS: + *value = buf->orgchansize << 3; + break; + case MIX_CHANNELS: + *value = buf->orgchannels; + break; + case MIX_SIZE: + *value = buf->orgsize; + break; + case MIX_DATA: + *value = (mixer_IntVal) buf->orgdata; + break; + default: + mixer_SetError (MIX_INVALID_ENUM); + log_add (log_Debug, "mixer_GetBufferi() called " + "with invalid property %u", pname); + } + } + + UnlockRecursiveMutex (buf_mutex); +} + +/* fill buffer with external data */ +void +mixer_BufferData (mixer_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq) +{ + mixer_Buffer *buf = (mixer_Buffer *) bufobj; + mixer_Convertion conv; + uint32 dstsize; + + if (!buf || !data || !size) + { + mixer_SetError (buf ? MIX_INVALID_VALUE : MIX_INVALID_NAME); + log_add (log_Debug, "mixer_BufferData() called with bad param"); + return; + } + + LockRecursiveMutex (buf_mutex); + + if (buf->locked) + { + UnlockRecursiveMutex (buf_mutex); + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "mixer_BufferData() called " + "with locked buffer"); + return; + } + + if (buf->magic != mixer_bufMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "mixer_BufferData(): not a buffer"); + } + else if (buf->state > MIX_BUF_FILLED) + { + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "mixer_BufferData() attempted " + "on in-use buffer"); + } + else + { + if (buf->data) + HFree (buf->data); + buf->data = 0; + buf->size = 0; + + /* Store original buffer values for OpenAL compatibility */ + buf->orgdata = data; + buf->orgfreq = freq; + buf->orgsize = size; + buf->orgchannels = MIX_FORMAT_CHANS (format); + buf->orgchansize = MIX_FORMAT_BPC (format); + + conv.srcsamples = conv.dstsamples = + size / MIX_FORMAT_SAMPSIZE (format); + + if (conv.dstsamples > + UINT32_MAX / MIX_FORMAT_SAMPSIZE (format)) + { + mixer_SetError (MIX_INVALID_VALUE); + } + else + { + if (MIX_FORMAT_CHANS (format) < MIX_FORMAT_CHANS (mixer_format)) + buf->sampsize = MIX_FORMAT_BPC (mixer_format) * + MIX_FORMAT_CHANS (format); + else + buf->sampsize = MIX_FORMAT_SAMPSIZE (mixer_format); + buf->size = dstsize = conv.dstsamples * buf->sampsize; + + /* only copy/convert the data if not faking */ + if (! (mixer_flags & MIX_FAKE_DATA)) + { + buf->data = HMalloc (dstsize); + + if (MIX_FORMAT_BPC (format) == MIX_FORMAT_BPC (mixer_format) && + MIX_FORMAT_CHANS (format) <= MIX_FORMAT_CHANS (mixer_format)) + { + /* format is compatible with internal */ + buf->locked = true; + UnlockRecursiveMutex (buf_mutex); + + memcpy (buf->data, data, size); + if (MIX_FORMAT_BPC (format) == 1) + { + /* convert buffer to S8 format internally */ + uint8* dst; + for (dst = buf->data; dstsize; dstsize--, dst++) + *dst ^= 0x80; + } + + LockRecursiveMutex (buf_mutex); + buf->locked = false; + } + else + { + /* needs convertion */ + conv.srcfmt = format; + conv.srcdata = data; + conv.srcsize = size; + + if (MIX_FORMAT_CHANS (format) < MIX_FORMAT_CHANS (mixer_format)) + conv.dstfmt = MIX_FORMAT_MAKE (mixer_chansize, + MIX_FORMAT_CHANS (format)); + else + conv.dstfmt = mixer_format; + conv.dstdata = buf->data; + conv.dstsize = dstsize; + + buf->locked = true; + UnlockRecursiveMutex (buf_mutex); + + mixer_ConvertBuffer_internal (&conv); + + LockRecursiveMutex (buf_mutex); + buf->locked = false; + } + } + + buf->state = MIX_BUF_FILLED; + buf->high = (buf->orgfreq / mixer_freq) * buf->sampsize; + buf->low = (((buf->orgfreq % mixer_freq) << 16) / mixer_freq); + if (mixer_freq == buf->orgfreq) + buf->Resample = mixer_resampling.None; + else if (mixer_freq > buf->orgfreq) + buf->Resample = mixer_resampling.Upsample; + else + buf->Resample = mixer_resampling.Downsample; + } + } + + UnlockRecursiveMutex (buf_mutex); +} + + +/************************************************* + * Buffer internals + */ + +static inline bool +mixer_CheckBufferState (mixer_Buffer *buf, const char* FuncName) +{ + if (!buf) + return false; + + if (buf->magic != mixer_bufMagic) + { + mixer_SetError (MIX_INVALID_NAME); + log_add (log_Debug, "%s(): not a buffer", FuncName); + return false; + } + + if (buf->locked) + { + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "%s(): locked buffer attempted", FuncName); + return false; + } + + if (buf->state != MIX_BUF_FILLED) + { + mixer_SetError (MIX_INVALID_OPERATION); + log_add (log_Debug, "%s: invalid buffer attempted", FuncName); + return false; + } + return true; +} + +static void +mixer_ConvertBuffer_internal (mixer_Convertion *conv) +{ + conv->srcbpc = MIX_FORMAT_BPC (conv->srcfmt); + conv->srcchans = MIX_FORMAT_CHANS (conv->srcfmt); + conv->dstbpc = MIX_FORMAT_BPC (conv->dstfmt); + conv->dstchans = MIX_FORMAT_CHANS (conv->dstfmt); + + conv->flags = 0; + if (conv->srcbpc > conv->dstbpc) + conv->flags |= mixConvSizeDown; + else if (conv->srcbpc < conv->dstbpc) + conv->flags |= mixConvSizeUp; + if (conv->srcchans > conv->dstchans) + conv->flags |= mixConvStereoDown; + else if (conv->srcchans < conv->dstchans) + conv->flags |= mixConvStereoUp; + + mixer_ResampleFlat (conv); +} + +/************************************************* + * Resampling routines + */ + +/* get a sample from external buffer + * in internal format + */ +static inline sint32 +mixer_GetSampleExt (void *src, uint32 bpc) +{ + if (bpc == 2) + return *(sint16 *)src; + else + return (*(uint8 *)src) - 128; +} + +/* get a sample from internal buffer */ +static inline sint32 +mixer_GetSampleInt (void *src, uint32 bpc) +{ + if (bpc == 2) + return *(sint16 *)src; + else + return *(sint8 *)src; +} + +/* put a sample into an external buffer + * from internal format + */ +static inline void +mixer_PutSampleExt (void *dst, uint32 bpc, sint32 samp) +{ + if (bpc == 2) + *(sint16 *)dst = samp; + else + *(uint8 *)dst = samp ^ 0x80; +} + +/* put a sample into an internal buffer + * in internal format + */ +static inline void +mixer_PutSampleInt (void *dst, uint32 bpc, sint32 samp) +{ + if (bpc == 2) + *(sint16 *)dst = samp; + else + *(sint8 *)dst = samp; +} + +/* get a sample from source */ +static float +mixer_ResampleNone (mixer_Source *src, bool left) +{ + uint8 *d0 = src->nextqueued->data + src->pos; + src->pos += mixer_chansize; + (void) left; // satisfying compiler - unused arg + return mixer_GetSampleInt (d0, mixer_chansize); +} + +/* get a resampled (up/down) sample from source (nearest neighbor) */ +static float +mixer_ResampleNearest (mixer_Source *src, bool left) +{ + uint8 *d0 = src->nextqueued->data + src->pos; + d0 += mixer_SourceAdvance (src, left); + return mixer_GetSampleInt (d0, mixer_chansize); +} + +/* get an upsampled sample from source (linear interpolation) */ +static float +mixer_UpsampleLinear (mixer_Source *src, bool left) +{ + mixer_Buffer *curr = src->nextqueued; + mixer_Buffer *next = src->nextqueued->next; + uint8 *d0, *d1; + float s0, s1, t; + + t = src->count / 65536.0f; + d0 = curr->data + src->pos; + d0 += mixer_SourceAdvance (src, left); + + if (d0 + curr->sampsize >= curr->data + curr->size) + { + if (next && next->data && next->size >= curr->sampsize) + { + d1 = next->data; + if (!left) + d1 += mixer_chansize; + } + else + d1 = d0; + } + else + d1 = d0 + curr->sampsize; + + s0 = mixer_GetSampleInt (d0, mixer_chansize); + s1 = mixer_GetSampleInt (d1, mixer_chansize); + return s0 + t * (s1 - s0); +} + +/* get an upsampled sample from source (cubic interpolation) */ +static float +mixer_UpsampleCubic (mixer_Source *src, bool left) +{ + mixer_Buffer *prev = src->prevqueued; + mixer_Buffer *curr = src->nextqueued; + mixer_Buffer *next = src->nextqueued->next; + uint8 *d0, *d1, *d2, *d3; /* prev, curr, next, next + 1 */ + float t, t2, a, b, c, s0, s1, s2, s3; + + t = src->count / 65536.0f; + t2 = t * t; + d1 = curr->data + src->pos; + d1 += mixer_SourceAdvance (src, left); + + if (d1 - curr->sampsize < curr->data) + { + if (prev && prev->data && prev->size >= curr->sampsize) + { + d0 = prev->data + prev->size - curr->sampsize; + if (!left) + d0 += mixer_chansize; + } + else + d0 = d1; + } + else + d0 = d1 - curr->sampsize; + + if (d1 + curr->sampsize >= curr->data + curr->size) + { + if (next && next->data && next->size >= curr->sampsize * 2) + { + d2 = next->data; + if (!left) + d2 += mixer_chansize; + d3 = d2 + curr->sampsize; + } + else + d2 = d3 = d1; + } + else + { + d2 = d1 + curr->sampsize; + if (d2 + curr->sampsize >= curr->data + curr->size) + { + if (next && next->data && next->size >= curr->sampsize) + { + d3 = next->data; + if (!left) + d3 += mixer_chansize; + } + else + d3 = d2; + } + else + d3 = d2 + curr->sampsize; + } + + s0 = mixer_GetSampleInt (d0, mixer_chansize); + s1 = mixer_GetSampleInt (d1, mixer_chansize); + s2 = mixer_GetSampleInt (d2, mixer_chansize); + s3 = mixer_GetSampleInt (d3, mixer_chansize); + + a = (3.0f * (s1 - s2) - s0 + s3) * 0.5f; + b = 2.0f * s2 + s0 - ((5.0f * s1 + s3) * 0.5f); + c = (s2 - s0) * 0.5f; + + return a * t2 * t + b * t2 + c * t + s1; +} + +/* get next sample from external buffer + * in internal format, while performing + * convertion if necessary + */ +static inline sint32 +mixer_GetConvSample (uint8 **psrc, uint32 bpc, uint32 flags) +{ + sint32 samp; + + samp = mixer_GetSampleExt (*psrc, bpc); + *psrc += bpc; + if (flags & mixConvStereoDown) + { + /* downmix to mono - average up channels */ + samp = (samp + mixer_GetSampleExt (*psrc, bpc)) / 2; + *psrc += bpc; + } + + if (flags & mixConvSizeUp) + { + /* convert S8 to S16 */ + samp <<= 8; + } + else if (flags & mixConvSizeDown) + { + /* convert S16 to S8 + * if arithmetic shift is available to the compiler + * it will use it to optimize this + */ + samp /= 0x100; + } + + return samp; +} + +/* put next sample into an internal buffer + * in internal format, while performing + * convertion if necessary + */ +static inline void +mixer_PutConvSample (uint8 **pdst, uint32 bpc, uint32 flags, sint32 samp) +{ + mixer_PutSampleInt (*pdst, bpc, samp); + *pdst += bpc; + if (flags & mixConvStereoUp) + { + mixer_PutSampleInt (*pdst, bpc, samp); + *pdst += bpc; + } +} + +/* resampling with respect to sample size only */ +static void +mixer_ResampleFlat (mixer_Convertion *conv) +{ + mixer_ConvFlags flags = conv->flags; + uint8 *src = conv->srcdata; + uint8 *dst = conv->dstdata; + uint32 srcbpc = conv->srcbpc; + uint32 dstbpc = conv->dstbpc; + uint32 samples; + + samples = conv->srcsamples; + if ( !(conv->flags & (mixConvStereoUp | mixConvStereoDown))) + samples *= conv->srcchans; + + for (; samples; samples--) + { + sint32 samp; + + samp = mixer_GetConvSample (&src, srcbpc, flags); + mixer_PutConvSample (&dst, dstbpc, flags, samp); + } +} diff --git a/src/libs/sound/mixer/mixer.h b/src/libs/sound/mixer/mixer.h new file mode 100644 index 0000000..71e7878 --- /dev/null +++ b/src/libs/sound/mixer/mixer.h @@ -0,0 +1,274 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Mixer for low-level sound output drivers + */ + +#ifndef LIBS_SOUND_MIXER_MIXER_H_ +#define LIBS_SOUND_MIXER_MIXER_H_ + +#include "config.h" +#include "types.h" +#include "endian_uqm.h" + +/** + * The interface heavily influenced by OpenAL + * to the point where you should use OpenAL's + * documentation when programming the mixer. + * (some source properties are not supported) + * + * EXCEPTION: You may not queue the same buffer + * on more than one source + */ + +#ifdef WORDS_BIGENDIAN +# define MIX_IS_BIG_ENDIAN true +# define MIX_WANT_BIG_ENDIAN true +#else +# define MIX_IS_BIG_ENDIAN false +# define MIX_WANT_BIG_ENDIAN false +#endif + +/** + * Mixer errors (see OpenAL errors) + */ +enum +{ + MIX_NO_ERROR = 0, + MIX_INVALID_NAME = 0xA001U, + MIX_INVALID_ENUM = 0xA002U, + MIX_INVALID_VALUE = 0xA003U, + MIX_INVALID_OPERATION = 0xA004U, + MIX_OUT_OF_MEMORY = 0xA005U, + + MIX_DRIVER_FAILURE = 0xA101U +}; + +/** + * Source properties (see OpenAL) + */ +typedef enum +{ + MIX_POSITION = 0x1004, + MIX_LOOPING = 0x1007, + MIX_BUFFER = 0x1009, + MIX_GAIN = 0x100A, + MIX_SOURCE_STATE = 0x1010, + + MIX_BUFFERS_QUEUED = 0x1015, + MIX_BUFFERS_PROCESSED = 0x1016 + +} mixer_SourceProp; + +/** + * Source state information + */ +typedef enum +{ + MIX_INITIAL = 0, + MIX_STOPPED, + MIX_PLAYING, + MIX_PAUSED, + +} mixer_SourceState; + +/** + * Sound buffer properties + */ +typedef enum +{ + MIX_FREQUENCY = 0x2001, + MIX_BITS = 0x2002, + MIX_CHANNELS = 0x2003, + MIX_SIZE = 0x2004, + MIX_DATA = 0x2005 + +} mixer_BufferProp; + +/** + * Buffer states: semi-private + */ +typedef enum +{ + MIX_BUF_INITIAL = 0, + MIX_BUF_FILLED, + MIX_BUF_QUEUED, + MIX_BUF_PLAYING, + MIX_BUF_PROCESSED + +} mixer_BufferState; + +/** Sound buffers: format specifier. + * bits 00..07: bytes per sample + * bits 08..15: channels + * bits 15..31: meaningless + */ +#define MIX_FORMAT_DUMMYID 0x00170000 +#define MIX_FORMAT_BPC(f) ((f) & 0xff) +#define MIX_FORMAT_CHANS(f) (((f) >> 8) & 0xff) +#define MIX_FORMAT_BPC_MAX 2 +#define MIX_FORMAT_CHANS_MAX 2 +#define MIX_FORMAT_MAKE(b, c) \ + ( MIX_FORMAT_DUMMYID | ((b) & 0xff) | (((c) & 0xff) << 8) ) + +#define MIX_FORMAT_SAMPSIZE(f) \ + ( MIX_FORMAT_BPC(f) * MIX_FORMAT_CHANS(f) ) + +typedef enum +{ + MIX_FORMAT_MONO8 = MIX_FORMAT_MAKE (1, 1), + MIX_FORMAT_STEREO8 = MIX_FORMAT_MAKE (1, 2), + MIX_FORMAT_MONO16 = MIX_FORMAT_MAKE (2, 1), + MIX_FORMAT_STEREO16 = MIX_FORMAT_MAKE (2, 2) + +} mixer_Format; + +typedef enum +{ + MIX_QUALITY_LOW = 0, + MIX_QUALITY_MEDIUM, + MIX_QUALITY_HIGH, + MIX_QUALITY_DEFAULT = MIX_QUALITY_MEDIUM, + MIX_QUALITY_COUNT + +} mixer_Quality; + +typedef enum +{ + MIX_NOFLAGS = 0, + MIX_FAKE_DATA = 1 +} mixer_Flags; + +/************************************************* + * Interface Types + */ + +typedef intptr_t mixer_Object; +typedef intptr_t mixer_IntVal; + +typedef struct _mixer_Source mixer_Source; + +typedef struct _mixer_Buffer +{ + uint32 magic; + bool locked; + mixer_BufferState state; + uint8 *data; + uint32 size; + uint32 sampsize; + uint32 high; + uint32 low; + float (* Resample) (mixer_Source *src, bool left); + /* original buffer values for OpenAL compat */ + void* orgdata; + uint32 orgfreq; + uint32 orgsize; + uint32 orgchannels; + uint32 orgchansize; + /* next buffer in chain */ + struct _mixer_Buffer *next; + +} mixer_Buffer; + +#define mixer_bufMagic 0x4258494DU /* MIXB in LSB */ + +struct _mixer_Source +{ + uint32 magic; + bool locked; + mixer_SourceState state; + bool looping; + float gain; + uint32 cqueued; + uint32 cprocessed; + mixer_Buffer *firstqueued; /* first buf in the queue */ + mixer_Buffer *nextqueued; /* next to play, or 0 */ + mixer_Buffer *prevqueued; /* previously played */ + mixer_Buffer *lastqueued; /* last in queue */ + uint32 pos; /* position in current buffer */ + uint32 count; /* fractional part of pos */ + + float samplecache; + +}; + +#define mixer_srcMagic 0x5358494DU /* MIXS in LSB */ + +/************************************************* + * General interface + */ +uint32 mixer_GetError (void); + +bool mixer_Init (uint32 frequency, uint32 format, mixer_Quality quality, + mixer_Flags flags); +void mixer_Uninit (void); +void mixer_MixChannels (void *userdata, uint8 *stream, sint32 len); +void mixer_MixFake (void *userdata, uint8 *stream, sint32 len); + +/************************************************* + * Sources + */ +void mixer_GenSources (uint32 n, mixer_Object *psrcobj); +void mixer_DeleteSources (uint32 n, mixer_Object *psrcobj); +bool mixer_IsSource (mixer_Object srcobj); +void mixer_Sourcei (mixer_Object srcobj, mixer_SourceProp pname, + mixer_IntVal value); +void mixer_Sourcef (mixer_Object srcobj, mixer_SourceProp pname, + float value); +void mixer_Sourcefv (mixer_Object srcobj, mixer_SourceProp pname, + float *value); +void mixer_GetSourcei (mixer_Object srcobj, mixer_SourceProp pname, + mixer_IntVal *value); +void mixer_GetSourcef (mixer_Object srcobj, mixer_SourceProp pname, + float *value); +void mixer_SourceRewind (mixer_Object srcobj); +void mixer_SourcePlay (mixer_Object srcobj); +void mixer_SourcePause (mixer_Object srcobj); +void mixer_SourceStop (mixer_Object srcobj); +void mixer_SourceQueueBuffers (mixer_Object srcobj, uint32 n, + mixer_Object* pbufobj); +void mixer_SourceUnqueueBuffers (mixer_Object srcobj, uint32 n, + mixer_Object* pbufobj); + +/************************************************* + * Buffers + */ +void mixer_GenBuffers (uint32 n, mixer_Object *pbufobj); +void mixer_DeleteBuffers (uint32 n, mixer_Object *pbufobj); +bool mixer_IsBuffer (mixer_Object bufobj); +void mixer_GetBufferi (mixer_Object bufobj, mixer_BufferProp pname, + mixer_IntVal *value); +void mixer_BufferData (mixer_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq); + + +/* Make sure the prop-value type is of suitable size + * it must be able to store both int and void* + * Adapted from SDL + * This will generate "negative subscript or subscript is too large" + * error during compile, if the actual size of a type is wrong + */ +#define MIX_COMPILE_TIME_ASSERT(name, x) \ + typedef int mixer_dummy_##name [(x) * 2 - 1] + +MIX_COMPILE_TIME_ASSERT (mixer_Object, + sizeof(mixer_Object) >= sizeof(void*)); +MIX_COMPILE_TIME_ASSERT (mixer_IntVal, + sizeof(mixer_IntVal) >= sizeof(mixer_Object)); + +#undef MIX_COMPILE_TIME_ASSERT + +#endif /* LIBS_SOUND_MIXER_MIXER_H_ */ diff --git a/src/libs/sound/mixer/mixerint.h b/src/libs/sound/mixer/mixerint.h new file mode 100644 index 0000000..0605161 --- /dev/null +++ b/src/libs/sound/mixer/mixerint.h @@ -0,0 +1,110 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Mixer for low-level sound output drivers + * Internals + */ + +#ifndef LIBS_SOUND_MIXER_MIXERINT_H_ +#define LIBS_SOUND_MIXER_MIXERINT_H_ + +#include "port.h" +#include "types.h" + +/************************************************* + * Internals + */ + +/* Conversion info types and funcs */ +typedef enum +{ + mixConvNone = 0, + mixConvStereoUp = 1, + mixConvStereoDown = 2, + mixConvSizeUp = 4, + mixConvSizeDown = 8 + +} mixer_ConvFlags; + +typedef struct +{ + uint32 srcfmt; + void *srcdata; + uint32 srcsize; + uint32 srcbpc; /* bytes/sample for 1 chan */ + uint32 srcchans; + uint32 srcsamples; + + uint32 dstfmt; + void *dstdata; + uint32 dstsize; + uint32 dstbpc; /* bytes/sample for 1 chan */ + uint32 dstchans; + uint32 dstsamples; + + mixer_ConvFlags flags; + +} mixer_Convertion; + +typedef struct +{ + float (* Upsample) (mixer_Source *src, bool left); + float (* Downsample) (mixer_Source *src, bool left); + float (* None) (mixer_Source *src, bool left); +} mixer_Resampling; + +static void mixer_ConvertBuffer_internal (mixer_Convertion *conv); +static void mixer_ResampleFlat (mixer_Convertion *conv); + +static inline sint32 mixer_GetSampleExt (void *src, uint32 bpc); +static inline sint32 mixer_GetSampleInt (void *src, uint32 bpc); +static inline void mixer_PutSampleInt (void *dst, uint32 bpc, + sint32 samp); +static inline void mixer_PutSampleExt (void *dst, uint32 bpc, + sint32 samp); + +static float mixer_ResampleNone (mixer_Source *src, bool left); +static float mixer_ResampleNearest (mixer_Source *src, bool left); +static float mixer_UpsampleLinear (mixer_Source *src, bool left); +static float mixer_UpsampleCubic (mixer_Source *src, bool left); + +/* Source manipulation */ +static void mixer_SourceUnqueueAll (mixer_Source *src); +static void mixer_SourceStop_internal (mixer_Source *src); +static void mixer_SourceRewind_internal (mixer_Source *src); +static void mixer_SourceActivate (mixer_Source* src); +static void mixer_SourceDeactivate (mixer_Source* src); + +static inline bool mixer_CheckBufferState (mixer_Buffer *buf, + const char* FuncName); + +/* Clipping boundaries */ +#define MIX_S16_MAX ((float) SINT16_MAX) +#define MIX_S16_MIN ((float) SINT16_MIN) +#define MIX_S8_MAX ((float) SINT8_MAX) +#define MIX_S8_MIN ((float) SINT8_MIN) + +/* Channel gain adjustment for clipping reduction */ +#define MIX_GAIN_ADJ (0.75f) + +/* The Mixer */ +static inline bool mixer_SourceGetNextSample (mixer_Source *src, + float *psamp, bool left); +static inline bool mixer_SourceGetFakeSample (mixer_Source *src, + float *psamp, bool left); +static inline uint32 mixer_SourceAdvance (mixer_Source *src, bool left); + +#endif /* LIBS_SOUND_MIXER_MIXERINT_H_ */ diff --git a/src/libs/sound/mixer/nosound/Makeinfo b/src/libs/sound/mixer/nosound/Makeinfo new file mode 100644 index 0000000..17f0c34 --- /dev/null +++ b/src/libs/sound/mixer/nosound/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="audiodrv_nosound.c" +uqm_HFILES="audiodrv_nosound.h" diff --git a/src/libs/sound/mixer/nosound/audiodrv_nosound.c b/src/libs/sound/mixer/nosound/audiodrv_nosound.c new file mode 100644 index 0000000..005bb44 --- /dev/null +++ b/src/libs/sound/mixer/nosound/audiodrv_nosound.c @@ -0,0 +1,410 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Nosound audio driver + */ + +#include "audiodrv_nosound.h" +#include "../../sndintrn.h" +#include "libs/tasklib.h" +#include "libs/log.h" +#include "libs/memlib.h" +#include <stdlib.h> + + +static Task PlaybackTask; +static uint32 nosound_freq = 22050; + +static const audio_Driver noSound_Driver = +{ + noSound_Uninit, + noSound_GetError, + audio_DRIVER_NOSOUND, + { + /* Errors */ + MIX_NO_ERROR, + MIX_INVALID_NAME, + MIX_INVALID_ENUM, + MIX_INVALID_VALUE, + MIX_INVALID_OPERATION, + MIX_OUT_OF_MEMORY, + MIX_DRIVER_FAILURE, + + /* Source properties */ + MIX_POSITION, + MIX_LOOPING, + MIX_BUFFER, + MIX_GAIN, + MIX_SOURCE_STATE, + MIX_BUFFERS_QUEUED, + MIX_BUFFERS_PROCESSED, + + /* Source state information */ + MIX_INITIAL, + MIX_STOPPED, + MIX_PLAYING, + MIX_PAUSED, + + /* Sound buffer properties */ + MIX_FREQUENCY, + MIX_BITS, + MIX_CHANNELS, + MIX_SIZE, + MIX_FORMAT_MONO16, + MIX_FORMAT_STEREO16, + MIX_FORMAT_MONO8, + MIX_FORMAT_STEREO8 + }, + + /* Sources */ + noSound_GenSources, + noSound_DeleteSources, + noSound_IsSource, + noSound_Sourcei, + noSound_Sourcef, + noSound_Sourcefv, + noSound_GetSourcei, + noSound_GetSourcef, + noSound_SourceRewind, + noSound_SourcePlay, + noSound_SourcePause, + noSound_SourceStop, + noSound_SourceQueueBuffers, + noSound_SourceUnqueueBuffers, + + /* Buffers */ + noSound_GenBuffers, + noSound_DeleteBuffers, + noSound_IsBuffer, + noSound_GetBufferi, + noSound_BufferData +}; + + +/* + * Initialization + */ + +sint32 +noSound_Init (audio_Driver *driver, sint32 flags) +{ + int i; + TFB_DecoderFormats formats = + { + 0, 0, + audio_FORMAT_MONO8, audio_FORMAT_STEREO8, + audio_FORMAT_MONO16, audio_FORMAT_STEREO16 + }; + + log_add (log_Info, "Using nosound audio driver."); + log_add (log_Info, "Initializing mixer."); + + if (!mixer_Init (nosound_freq, MIX_FORMAT_MAKE (1, 1), + MIX_QUALITY_LOW, MIX_FAKE_DATA)) + { + log_add (log_Error, "Mixer initialization failed: %x", + mixer_GetError ()); + return -1; + } + log_add (log_Info, "Mixer initialized."); + + log_add (log_Info, "Initializing sound decoders."); + if (SoundDecoder_Init (flags, &formats)) + { + log_add (log_Error, "Sound decoders initialization failed."); + mixer_Uninit (); + return -1; + } + log_add (log_Info, "Sound decoders initialized."); + + *driver = noSound_Driver; + for (i = 0; i < NUM_SOUNDSOURCES; ++i) + { + audio_GenSources (1, &soundSource[i].handle); + soundSource[i].stream_mutex = CreateMutex ("Nosound stream mutex", SYNC_CLASS_AUDIO); + } + + if (InitStreamDecoder ()) + { + log_add (log_Error, "Stream decoder initialization failed."); + // TODO: cleanup source mutexes [or is it "muti"? :) ] + SoundDecoder_Uninit (); + mixer_Uninit (); + return -1; + } + + PlaybackTask = AssignTask (PlaybackTaskFunc, 1024, + "nosound audio playback"); + + return 0; +} + +void +noSound_Uninit (void) +{ + int i; + + UninitStreamDecoder (); + + for (i = 0; i < NUM_SOUNDSOURCES; ++i) + { + if (soundSource[i].sample && soundSource[i].sample->decoder) + { + StopStream (i); + } + if (soundSource[i].sbuffer) + { + void *sbuffer = soundSource[i].sbuffer; + soundSource[i].sbuffer = NULL; + HFree (sbuffer); + } + DestroyMutex (soundSource[i].stream_mutex); + + noSound_DeleteSources (1, &soundSource[i].handle); + } + + if (PlaybackTask) + { + ConcludeTask (PlaybackTask); + PlaybackTask = 0; + } + + mixer_Uninit (); + SoundDecoder_Uninit (); +} + + +/* + * Playback task + */ + +int +PlaybackTaskFunc (void *data) +{ + Task task = (Task)data; + uint8 *stream; + uint32 entryTime; + sint32 period, delay; + uint32 len = 2048; + + stream = (uint8 *) HMalloc (len); + period = (sint32)((len / (double)nosound_freq) * ONE_SECOND); + + while (!Task_ReadState (task, TASK_EXIT)) + { + entryTime = GetTimeCounter (); + mixer_MixFake (NULL, stream, len); + delay = period - (GetTimeCounter () - entryTime); + if (delay > 0) + HibernateThread (delay); + } + + HFree (stream); + FinishTask (task); + return 0; +} + + +/* + * General + */ + +sint32 +noSound_GetError (void) +{ + sint32 value = mixer_GetError (); + switch (value) + { + case MIX_NO_ERROR: + return audio_NO_ERROR; + case MIX_INVALID_NAME: + return audio_INVALID_NAME; + case MIX_INVALID_ENUM: + return audio_INVALID_ENUM; + case MIX_INVALID_VALUE: + return audio_INVALID_VALUE; + case MIX_INVALID_OPERATION: + return audio_INVALID_OPERATION; + case MIX_OUT_OF_MEMORY: + return audio_OUT_OF_MEMORY; + default: + log_add (log_Debug, "noSound_GetError: unknown value %x", + value); + return audio_DRIVER_FAILURE; + break; + } +} + + +/* + * Sources + */ + +void +noSound_GenSources (uint32 n, audio_Object *psrcobj) +{ + mixer_GenSources (n, (mixer_Object *) psrcobj); +} + +void +noSound_DeleteSources (uint32 n, audio_Object *psrcobj) +{ + mixer_DeleteSources (n, (mixer_Object *) psrcobj); +} + +bool +noSound_IsSource (audio_Object srcobj) +{ + return mixer_IsSource ((mixer_Object) srcobj); +} + +void +noSound_Sourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value) + +{ + mixer_Sourcei ((mixer_Object) srcobj, (mixer_SourceProp) pname, + (mixer_IntVal) value); +} + +void +noSound_Sourcef (audio_Object srcobj, audio_SourceProp pname, + float value) +{ + mixer_Sourcef ((mixer_Object) srcobj, (mixer_SourceProp) pname, value); +} + +void +noSound_Sourcefv (audio_Object srcobj, audio_SourceProp pname, + float *value) +{ + mixer_Sourcefv ((mixer_Object) srcobj, (mixer_SourceProp) pname, value); +} + +void +noSound_GetSourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value) +{ + mixer_GetSourcei ((mixer_Object) srcobj, (mixer_SourceProp) pname, + (mixer_IntVal *) value); + if (pname == MIX_SOURCE_STATE) + { + switch (*value) + { + case MIX_INITIAL: + *value = audio_INITIAL; + break; + case MIX_STOPPED: + *value = audio_STOPPED; + break; + case MIX_PLAYING: + *value = audio_PLAYING; + break; + case MIX_PAUSED: + *value = audio_PAUSED; + break; + default: + log_add (log_Debug, "noSound_GetSourcei(): unknown value %lx", + (long int) *value); + *value = audio_DRIVER_FAILURE; + } + } +} + +void +noSound_GetSourcef (audio_Object srcobj, audio_SourceProp pname, + float *value) +{ + mixer_GetSourcef ((mixer_Object) srcobj, (mixer_SourceProp) pname, value); +} + +void +noSound_SourceRewind (audio_Object srcobj) +{ + mixer_SourceRewind ((mixer_Object) srcobj); +} + +void +noSound_SourcePlay (audio_Object srcobj) +{ + mixer_SourcePlay ((mixer_Object) srcobj); +} + +void +noSound_SourcePause (audio_Object srcobj) +{ + mixer_SourcePause ((mixer_Object) srcobj); +} + +void +noSound_SourceStop (audio_Object srcobj) +{ + mixer_SourceStop ((mixer_Object) srcobj); +} + +void +noSound_SourceQueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj) +{ + mixer_SourceQueueBuffers ((mixer_Object) srcobj, n, + (mixer_Object *) pbufobj); +} + +void +noSound_SourceUnqueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj) +{ + mixer_SourceUnqueueBuffers ((mixer_Object) srcobj, n, + (mixer_Object *) pbufobj); +} + + +/* + * Buffers + */ + +void +noSound_GenBuffers (uint32 n, audio_Object *pbufobj) +{ + mixer_GenBuffers (n, (mixer_Object *) pbufobj); +} + +void +noSound_DeleteBuffers (uint32 n, audio_Object *pbufobj) +{ + mixer_DeleteBuffers (n, (mixer_Object *) pbufobj); +} + +bool +noSound_IsBuffer (audio_Object bufobj) +{ + return mixer_IsBuffer ((mixer_Object) bufobj); +} + +void +noSound_GetBufferi (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value) +{ + mixer_GetBufferi ((mixer_Object) bufobj, (mixer_BufferProp) pname, + (mixer_IntVal *) value); +} + +void +noSound_BufferData (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq) +{ + mixer_BufferData ((mixer_Object) bufobj, format, data, size, freq); +} diff --git a/src/libs/sound/mixer/nosound/audiodrv_nosound.h b/src/libs/sound/mixer/nosound/audiodrv_nosound.h new file mode 100644 index 0000000..173b706 --- /dev/null +++ b/src/libs/sound/mixer/nosound/audiodrv_nosound.h @@ -0,0 +1,69 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* Nosound audio driver + */ + +#ifndef LIBS_SOUND_MIXER_NOSOUND_AUDIODRV_NOSOUND_H_ +#define LIBS_SOUND_MIXER_NOSOUND_AUDIODRV_NOSOUND_H_ + +#include "config.h" +#include "libs/sound/sound.h" +#include "libs/sound/mixer/mixer.h" + + +/* Playback task */ +int PlaybackTaskFunc (void *data); + +/* General */ +sint32 noSound_Init (audio_Driver *driver, sint32 flags); +void noSound_Uninit (void); +sint32 noSound_GetError (void); + +/* Sources */ +void noSound_GenSources (uint32 n, audio_Object *psrcobj); +void noSound_DeleteSources (uint32 n, audio_Object *psrcobj); +bool noSound_IsSource (audio_Object srcobj); +void noSound_Sourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value); +void noSound_Sourcef (audio_Object srcobj, audio_SourceProp pname, + float value); +void noSound_Sourcefv (audio_Object srcobj, audio_SourceProp pname, + float *value); +void noSound_GetSourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value); +void noSound_GetSourcef (audio_Object srcobj, audio_SourceProp pname, + float *value); +void noSound_SourceRewind (audio_Object srcobj); +void noSound_SourcePlay (audio_Object srcobj); +void noSound_SourcePause (audio_Object srcobj); +void noSound_SourceStop (audio_Object srcobj); +void noSound_SourceQueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); +void noSound_SourceUnqueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); + +/* Buffers */ +void noSound_GenBuffers (uint32 n, audio_Object *pbufobj); +void noSound_DeleteBuffers (uint32 n, audio_Object *pbufobj); +bool noSound_IsBuffer (audio_Object bufobj); +void noSound_GetBufferi (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value); +void noSound_BufferData (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq); + + +#endif /* LIBS_SOUND_MIXER_NOSOUND_AUDIODRV_NOSOUND_H_ */ diff --git a/src/libs/sound/mixer/sdl/Makeinfo b/src/libs/sound/mixer/sdl/Makeinfo new file mode 100644 index 0000000..64c22c0 --- /dev/null +++ b/src/libs/sound/mixer/sdl/Makeinfo @@ -0,0 +1,2 @@ +uqm_CFILES="audiodrv_sdl.c" +uqm_HFILES="audiodrv_sdl.h" diff --git a/src/libs/sound/mixer/sdl/audiodrv_sdl.c b/src/libs/sound/mixer/sdl/audiodrv_sdl.c new file mode 100644 index 0000000..7ef522e --- /dev/null +++ b/src/libs/sound/mixer/sdl/audiodrv_sdl.c @@ -0,0 +1,486 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* SDL audio driver + */ + +#include "audiodrv_sdl.h" +#include "../../sndintrn.h" +#include "libs/log.h" +#include "libs/memlib.h" +#include <stdlib.h> + + +/* SDL2 wants to talk to a specific device. We'll let SDL1 use the same + * function names and just throw the device argument away. */ +#if SDL_MAJOR_VERSION > 1 +static SDL_AudioDeviceID dev; +#else +#define SDL_CloseAudioDevice(x) SDL_CloseAudio () +#define SDL_PauseAudioDevice(x, y) SDL_PauseAudio (y) +#endif +static const audio_Driver mixSDL_Driver = +{ + mixSDL_Uninit, + mixSDL_GetError, + audio_DRIVER_MIXSDL, + { + /* Errors */ + MIX_NO_ERROR, + MIX_INVALID_NAME, + MIX_INVALID_ENUM, + MIX_INVALID_VALUE, + MIX_INVALID_OPERATION, + MIX_OUT_OF_MEMORY, + MIX_DRIVER_FAILURE, + + /* Source properties */ + MIX_POSITION, + MIX_LOOPING, + MIX_BUFFER, + MIX_GAIN, + MIX_SOURCE_STATE, + MIX_BUFFERS_QUEUED, + MIX_BUFFERS_PROCESSED, + + /* Source state information */ + MIX_INITIAL, + MIX_STOPPED, + MIX_PLAYING, + MIX_PAUSED, + + /* Sound buffer properties */ + MIX_FREQUENCY, + MIX_BITS, + MIX_CHANNELS, + MIX_SIZE, + MIX_FORMAT_MONO16, + MIX_FORMAT_STEREO16, + MIX_FORMAT_MONO8, + MIX_FORMAT_STEREO8 + }, + + /* Sources */ + mixSDL_GenSources, + mixSDL_DeleteSources, + mixSDL_IsSource, + mixSDL_Sourcei, + mixSDL_Sourcef, + mixSDL_Sourcefv, + mixSDL_GetSourcei, + mixSDL_GetSourcef, + mixSDL_SourceRewind, + mixSDL_SourcePlay, + mixSDL_SourcePause, + mixSDL_SourceStop, + mixSDL_SourceQueueBuffers, + mixSDL_SourceUnqueueBuffers, + + /* Buffers */ + mixSDL_GenBuffers, + mixSDL_DeleteBuffers, + mixSDL_IsBuffer, + mixSDL_GetBufferi, + mixSDL_BufferData +}; + + +static void audioCallback (void *userdata, Uint8 *stream, int len); + +/* + * Initialization + */ + +sint32 +mixSDL_Init (audio_Driver *driver, sint32 flags) +{ + int i; + SDL_AudioSpec desired, obtained; + mixer_Quality quality; + TFB_DecoderFormats formats = + { + MIX_IS_BIG_ENDIAN, MIX_WANT_BIG_ENDIAN, + audio_FORMAT_MONO8, audio_FORMAT_STEREO8, + audio_FORMAT_MONO16, audio_FORMAT_STEREO16 + }; + + log_add (log_Info, "Initializing SDL audio subsystem."); + if ((SDL_InitSubSystem(SDL_INIT_AUDIO)) == -1) + { + log_add (log_Error, "Couldn't initialize audio subsystem: %s", + SDL_GetError()); + return -1; + } + log_add (log_Info, "SDL audio subsystem initialized."); + + if (flags & audio_QUALITY_HIGH) + { + quality = MIX_QUALITY_HIGH; + desired.freq = 44100; + desired.samples = 4096; + } + else if (flags & audio_QUALITY_LOW) + { + quality = MIX_QUALITY_LOW; +#ifdef __SYMBIAN32__ + desired.freq = 11025; + desired.samples = 4096; +#else + desired.freq = 22050; + desired.samples = 2048; +#endif + } + else + { + quality = MIX_QUALITY_DEFAULT; + desired.freq = 44100; + desired.samples = 4096; + } + + desired.format = AUDIO_S16SYS; + desired.channels = 2; + desired.callback = audioCallback; + + log_add (log_Info, "Opening SDL audio device."); +#if SDL_MAJOR_VERSION > 1 + dev = SDL_OpenAudioDevice (NULL, 0, &desired, &obtained, + SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | SDL_AUDIO_ALLOW_CHANNELS_CHANGE +#ifdef SDL_AUDIO_ALLOW_SAMPLES_CHANGE + | SDL_AUDIO_ALLOW_SAMPLES_CHANGE +#endif + ); + if (dev != 0 && obtained.channels != 1 && obtained.channels != 2) + { + /* Try again without SDL_AUDIO_ALLOW_CHANNELS_CHANGE + * in case the device only supports >2 channels for some + * reason */ + SDL_CloseAudioDevice (dev); + dev = SDL_OpenAudioDevice (NULL, 0, &desired, &obtained, + SDL_AUDIO_ALLOW_FREQUENCY_CHANGE +#ifdef SDL_AUDIO_ALLOW_SAMPLES_CHANGE + | SDL_AUDIO_ALLOW_SAMPLES_CHANGE +#endif + ); + } + if (dev == 0) +#else + if (SDL_OpenAudio (&desired, &obtained) < 0) +#endif + { + log_add (log_Error, "Unable to open audio device: %s", + SDL_GetError ()); + SDL_QuitSubSystem (SDL_INIT_AUDIO); + return -1; + } + if (obtained.format != desired.format || + (obtained.channels != 1 && obtained.channels != 2)) + { + log_add (log_Error, "Unable to obtain desired audio format."); + SDL_CloseAudio (); + SDL_QuitSubSystem (SDL_INIT_AUDIO); + return -1; + } + + { +#if SDL_MAJOR_VERSION == 1 + char devicename[256]; + SDL_AudioDriverName (devicename, sizeof (devicename)); +#else + const char *devicename = SDL_GetCurrentAudioDriver (); +#endif + log_add (log_Info, " using %s at %d Hz 16 bit %s, " + "%d samples audio buffer", + devicename, obtained.freq, + obtained.channels > 1 ? "stereo" : "mono", + obtained.samples); + } + + log_add (log_Info, "Initializing mixer."); + if (!mixer_Init (obtained.freq, MIX_FORMAT_MAKE (2, obtained.channels), + quality, 0)) + { + log_add (log_Error, "Mixer initialization failed: %x", + mixer_GetError ()); + SDL_CloseAudioDevice (dev); + SDL_QuitSubSystem (SDL_INIT_AUDIO); + return -1; + } + log_add (log_Info, "Mixer initialized."); + + log_add (log_Info, "Initializing sound decoders."); + if (SoundDecoder_Init (flags, &formats)) + { + log_add (log_Error, "Sound decoders initialization failed."); + SDL_CloseAudioDevice (dev); + mixer_Uninit (); + SDL_QuitSubSystem (SDL_INIT_AUDIO); + return -1; + } + log_add (log_Info, "Sound decoders initialized."); + + *driver = mixSDL_Driver; + for (i = 0; i < NUM_SOUNDSOURCES; ++i) + { + audio_GenSources (1, &soundSource[i].handle); + soundSource[i].stream_mutex = CreateMutex ("MixSDL stream mutex", SYNC_CLASS_AUDIO); + } + + if (InitStreamDecoder ()) + { + log_add (log_Error, "Stream decoder initialization failed."); + // TODO: cleanup source mutexes [or is it "muti"? :) ] + SDL_CloseAudioDevice (dev); + SoundDecoder_Uninit (); + mixer_Uninit (); + SDL_QuitSubSystem (SDL_INIT_AUDIO); + return -1; + } + + atexit (unInitAudio); + + SetSFXVolume (sfxVolumeScale); + SetSpeechVolume (speechVolumeScale); + SetMusicVolume ((COUNT)musicVolume); + + SDL_PauseAudioDevice (dev, 0); + + return 0; +} + +void +mixSDL_Uninit (void) +{ + int i; + + UninitStreamDecoder (); + + for (i = 0; i < NUM_SOUNDSOURCES; ++i) + { + if (soundSource[i].sample && soundSource[i].sample->decoder) + { + StopStream (i); + } + if (soundSource[i].sbuffer) + { + void *sbuffer = soundSource[i].sbuffer; + soundSource[i].sbuffer = NULL; + HFree (sbuffer); + } + DestroyMutex (soundSource[i].stream_mutex); + soundSource[i].stream_mutex = 0; + + mixSDL_DeleteSources (1, &soundSource[i].handle); + } + + SDL_CloseAudioDevice (dev); + mixer_Uninit (); + SoundDecoder_Uninit (); + SDL_QuitSubSystem (SDL_INIT_AUDIO); +} + +static void +audioCallback (void *userdata, Uint8 *stream, int len) +{ + mixer_MixChannels (userdata, stream, len); +} + +/* + * General + */ + +sint32 +mixSDL_GetError (void) +{ + sint32 value = mixer_GetError (); + switch (value) + { + case MIX_NO_ERROR: + return audio_NO_ERROR; + case MIX_INVALID_NAME: + return audio_INVALID_NAME; + case MIX_INVALID_ENUM: + return audio_INVALID_ENUM; + case MIX_INVALID_VALUE: + return audio_INVALID_VALUE; + case MIX_INVALID_OPERATION: + return audio_INVALID_OPERATION; + case MIX_OUT_OF_MEMORY: + return audio_OUT_OF_MEMORY; + default: + log_add (log_Debug, "mixSDL_GetError: unknown value %x", value); + return audio_DRIVER_FAILURE; + break; + } +} + + +/* + * Sources + */ + +void +mixSDL_GenSources (uint32 n, audio_Object *psrcobj) +{ + mixer_GenSources (n, (mixer_Object *) psrcobj); +} + +void +mixSDL_DeleteSources (uint32 n, audio_Object *psrcobj) +{ + mixer_DeleteSources (n, (mixer_Object *) psrcobj); +} + +bool +mixSDL_IsSource (audio_Object srcobj) +{ + return mixer_IsSource ((mixer_Object) srcobj); +} + +void +mixSDL_Sourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value) + +{ + mixer_Sourcei ((mixer_Object) srcobj, (mixer_SourceProp) pname, + (mixer_IntVal) value); +} + +void +mixSDL_Sourcef (audio_Object srcobj, audio_SourceProp pname, + float value) +{ + mixer_Sourcef ((mixer_Object) srcobj, (mixer_SourceProp) pname, value); +} + +void +mixSDL_Sourcefv (audio_Object srcobj, audio_SourceProp pname, + float *value) +{ + mixer_Sourcefv ((mixer_Object) srcobj, (mixer_SourceProp) pname, value); +} + +void +mixSDL_GetSourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value) +{ + mixer_GetSourcei ((mixer_Object) srcobj, (mixer_SourceProp) pname, + (mixer_IntVal *) value); + if (pname == MIX_SOURCE_STATE) + { + switch (*value) + { + case MIX_INITIAL: + *value = audio_INITIAL; + break; + case MIX_STOPPED: + *value = audio_STOPPED; + break; + case MIX_PLAYING: + *value = audio_PLAYING; + break; + case MIX_PAUSED: + *value = audio_PAUSED; + break; + default: + *value = audio_DRIVER_FAILURE; + } + } +} + +void +mixSDL_GetSourcef (audio_Object srcobj, audio_SourceProp pname, + float *value) +{ + mixer_GetSourcef ((mixer_Object) srcobj, (mixer_SourceProp) pname, value); +} + +void +mixSDL_SourceRewind (audio_Object srcobj) +{ + mixer_SourceRewind ((mixer_Object) srcobj); +} + +void +mixSDL_SourcePlay (audio_Object srcobj) +{ + mixer_SourcePlay ((mixer_Object) srcobj); +} + +void +mixSDL_SourcePause (audio_Object srcobj) +{ + mixer_SourcePause ((mixer_Object) srcobj); +} + +void +mixSDL_SourceStop (audio_Object srcobj) +{ + mixer_SourceStop ((mixer_Object) srcobj); +} + +void +mixSDL_SourceQueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj) +{ + mixer_SourceQueueBuffers ((mixer_Object) srcobj, n, + (mixer_Object *) pbufobj); +} + +void +mixSDL_SourceUnqueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj) +{ + mixer_SourceUnqueueBuffers ((mixer_Object) srcobj, n, + (mixer_Object *) pbufobj); +} + + +/* + * Buffers + */ + +void +mixSDL_GenBuffers (uint32 n, audio_Object *pbufobj) +{ + mixer_GenBuffers (n, (mixer_Object *) pbufobj); +} + +void +mixSDL_DeleteBuffers (uint32 n, audio_Object *pbufobj) +{ + mixer_DeleteBuffers (n, (mixer_Object *) pbufobj); +} + +bool +mixSDL_IsBuffer (audio_Object bufobj) +{ + return mixer_IsBuffer ((mixer_Object) bufobj); +} + +void +mixSDL_GetBufferi (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value) +{ + mixer_GetBufferi ((mixer_Object) bufobj, (mixer_BufferProp) pname, + (mixer_IntVal *) value); +} + +void +mixSDL_BufferData (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq) +{ + mixer_BufferData ((mixer_Object) bufobj, format, data, size, freq); +} diff --git a/src/libs/sound/mixer/sdl/audiodrv_sdl.h b/src/libs/sound/mixer/sdl/audiodrv_sdl.h new file mode 100644 index 0000000..d74301b --- /dev/null +++ b/src/libs/sound/mixer/sdl/audiodrv_sdl.h @@ -0,0 +1,66 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* SDL audio driver + */ + +#ifndef LIBS_SOUND_MIXER_SDL_AUDIODRV_SDL_H_ +#define LIBS_SOUND_MIXER_SDL_AUDIODRV_SDL_H_ + +#include "port.h" +#include "libs/sound/sound.h" +#include "libs/sound/mixer/mixer.h" +#include SDL_INCLUDE(SDL.h) + +/* General */ +sint32 mixSDL_Init (audio_Driver *driver, sint32 flags); +void mixSDL_Uninit (void); +sint32 mixSDL_GetError (void); + +/* Sources */ +void mixSDL_GenSources (uint32 n, audio_Object *psrcobj); +void mixSDL_DeleteSources (uint32 n, audio_Object *psrcobj); +bool mixSDL_IsSource (audio_Object srcobj); +void mixSDL_Sourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal value); +void mixSDL_Sourcef (audio_Object srcobj, audio_SourceProp pname, + float value); +void mixSDL_Sourcefv (audio_Object srcobj, audio_SourceProp pname, + float *value); +void mixSDL_GetSourcei (audio_Object srcobj, audio_SourceProp pname, + audio_IntVal *value); +void mixSDL_GetSourcef (audio_Object srcobj, audio_SourceProp pname, + float *value); +void mixSDL_SourceRewind (audio_Object srcobj); +void mixSDL_SourcePlay (audio_Object srcobj); +void mixSDL_SourcePause (audio_Object srcobj); +void mixSDL_SourceStop (audio_Object srcobj); +void mixSDL_SourceQueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); +void mixSDL_SourceUnqueueBuffers (audio_Object srcobj, uint32 n, + audio_Object* pbufobj); + +/* Buffers */ +void mixSDL_GenBuffers (uint32 n, audio_Object *pbufobj); +void mixSDL_DeleteBuffers (uint32 n, audio_Object *pbufobj); +bool mixSDL_IsBuffer (audio_Object bufobj); +void mixSDL_GetBufferi (audio_Object bufobj, audio_BufferProp pname, + audio_IntVal *value); +void mixSDL_BufferData (audio_Object bufobj, uint32 format, void* data, + uint32 size, uint32 freq); + + +#endif /* LIBS_SOUND_MIXER_SDL_AUDIODRV_SDL_H_ */ |