/* * 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 #include #include #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); } }