Mercurial > SDL_sound_CoreAudio
view SDL_sound.c @ 375:6095d0a52a20
Added a reverb setting, moved assert.h include out.
author | Ryan C. Gordon <icculus@icculus.org> |
---|---|
date | Tue, 02 Jul 2002 03:57:02 +0000 |
parents | 3466dde3a846 |
children | cbb15ecf423a |
line wrap: on
line source
/* * SDL_sound -- An abstract sound format decoding API. * Copyright (C) 2001 Ryan C. Gordon. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /** * This file implements the core API, which is relatively simple. * The real meat of SDL_sound is in the decoders directory. * * Documentation is in SDL_sound.h ... It's verbose, honest. :) * * Please see the file COPYING in the source's root directory. * * This file written by Ryan C. Gordon. (icculus@clutteredmind.org) */ #if HAVE_CONFIG_H # include <config.h> #endif #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include <ctype.h> #include "SDL.h" #include "SDL_sound.h" #define __SDL_SOUND_INTERNAL__ #include "SDL_sound_internal.h" /* The various decoder drivers... */ #if (defined SOUND_SUPPORTS_SMPEG) extern const Sound_DecoderFunctions __Sound_DecoderFunctions_SMPEG; #endif #if (defined SOUND_SUPPORTS_MPGLIB) extern const Sound_DecoderFunctions __Sound_DecoderFunctions_MPGLIB; #endif #if (defined SOUND_SUPPORTS_MIKMOD) extern const Sound_DecoderFunctions __Sound_DecoderFunctions_MIKMOD; #endif #if (defined SOUND_SUPPORTS_MODPLUG) extern const Sound_DecoderFunctions __Sound_DecoderFunctions_MODPLUG; #endif #if (defined SOUND_SUPPORTS_WAV) extern const Sound_DecoderFunctions __Sound_DecoderFunctions_WAV; #endif #if (defined SOUND_SUPPORTS_AIFF) extern const Sound_DecoderFunctions __Sound_DecoderFunctions_AIFF; #endif #if (defined SOUND_SUPPORTS_AU) extern const Sound_DecoderFunctions __Sound_DecoderFunctions_AU; #endif #if (defined SOUND_SUPPORTS_OGG) extern const Sound_DecoderFunctions __Sound_DecoderFunctions_OGG; #endif #if (defined SOUND_SUPPORTS_VOC) extern const Sound_DecoderFunctions __Sound_DecoderFunctions_VOC; #endif #if (defined SOUND_SUPPORTS_RAW) extern const Sound_DecoderFunctions __Sound_DecoderFunctions_RAW; #endif #if (defined SOUND_SUPPORTS_SHN) extern const Sound_DecoderFunctions __Sound_DecoderFunctions_SHN; #endif #if (defined SOUND_SUPPORTS_MIDI) extern const Sound_DecoderFunctions __Sound_DecoderFunctions_MIDI; #endif #if (defined SOUND_SUPPORTS_FLAC) extern const Sound_DecoderFunctions __Sound_DecoderFunctions_FLAC; #endif #if (defined SOUND_SUPPORTS_QUICKTIME) extern const Sound_DecoderFunctions __Sound_DecoderFunctions_QuickTime; #endif typedef struct { int available; const Sound_DecoderFunctions *funcs; } decoder_element; static decoder_element decoders[] = { #if (defined SOUND_SUPPORTS_SMPEG) { 0, &__Sound_DecoderFunctions_SMPEG }, #endif #if (defined SOUND_SUPPORTS_MPGLIB) { 0, &__Sound_DecoderFunctions_MPGLIB }, #endif #if (defined SOUND_SUPPORTS_MODPLUG) { 0, &__Sound_DecoderFunctions_MODPLUG }, #endif #if (defined SOUND_SUPPORTS_MIKMOD) { 0, &__Sound_DecoderFunctions_MIKMOD }, #endif #if (defined SOUND_SUPPORTS_WAV) { 0, &__Sound_DecoderFunctions_WAV }, #endif #if (defined SOUND_SUPPORTS_AIFF) { 0, &__Sound_DecoderFunctions_AIFF }, #endif #if (defined SOUND_SUPPORTS_AU) { 0, &__Sound_DecoderFunctions_AU }, #endif #if (defined SOUND_SUPPORTS_OGG) { 0, &__Sound_DecoderFunctions_OGG }, #endif #if (defined SOUND_SUPPORTS_VOC) { 0, &__Sound_DecoderFunctions_VOC }, #endif #if (defined SOUND_SUPPORTS_RAW) { 0, &__Sound_DecoderFunctions_RAW }, #endif #if (defined SOUND_SUPPORTS_SHN) { 0, &__Sound_DecoderFunctions_SHN }, #endif #if (defined SOUND_SUPPORTS_FLAC) { 0, &__Sound_DecoderFunctions_FLAC }, #endif #if (defined SOUND_SUPPORTS_MIDI) { 0, &__Sound_DecoderFunctions_MIDI }, #endif #if (defined SOUND_SUPPORTS_QUICKTIME) { 0, &__Sound_DecoderFunctions_QuickTime }, #endif { 0, NULL } }; /* General SDL_sound state ... */ typedef struct __SOUND_ERRMSGTYPE__ { Uint32 tid; int error_available; char error_string[128]; struct __SOUND_ERRMSGTYPE__ *next; } ErrMsg; static ErrMsg *error_msgs = NULL; static SDL_mutex *errorlist_mutex = NULL; static Sound_Sample *sample_list = NULL; /* this is a linked list. */ static SDL_mutex *samplelist_mutex = NULL; static const Sound_DecoderInfo **available_decoders = NULL; static int initialized = 0; /* functions ... */ void Sound_GetLinkedVersion(Sound_Version *ver) { if (ver != NULL) { ver->major = SOUND_VER_MAJOR; ver->minor = SOUND_VER_MINOR; ver->patch = SOUND_VER_PATCH; } /* if */ } /* Sound_GetLinkedVersion */ int Sound_Init(void) { size_t i; size_t pos = 0; size_t total = sizeof (decoders) / sizeof (decoders[0]); BAIL_IF_MACRO(initialized, ERR_IS_INITIALIZED, 0); sample_list = NULL; error_msgs = NULL; available_decoders = (const Sound_DecoderInfo **) malloc((total) * sizeof (Sound_DecoderInfo *)); BAIL_IF_MACRO(available_decoders == NULL, ERR_OUT_OF_MEMORY, 0); SDL_Init(SDL_INIT_AUDIO); errorlist_mutex = SDL_CreateMutex(); for (i = 0; decoders[i].funcs != NULL; i++) { decoders[i].available = decoders[i].funcs->init(); if (decoders[i].available) { available_decoders[pos] = &(decoders[i].funcs->info); pos++; } /* if */ } /* for */ available_decoders[pos] = NULL; initialized = 1; return(1); } /* Sound_Init */ int Sound_Quit(void) { ErrMsg *err; ErrMsg *nexterr = NULL; size_t i; BAIL_IF_MACRO(!initialized, ERR_NOT_INITIALIZED, 0); while (((volatile Sound_Sample *) sample_list) != NULL) Sound_FreeSample(sample_list); initialized = 0; SDL_DestroyMutex(samplelist_mutex); samplelist_mutex = NULL; sample_list = NULL; for (i = 0; decoders[i].funcs != NULL; i++) { if (decoders[i].available) { decoders[i].funcs->quit(); decoders[i].available = 0; } /* if */ } /* for */ if (available_decoders != NULL) free((void *) available_decoders); available_decoders = NULL; /* clean up error state for each thread... */ SDL_LockMutex(errorlist_mutex); for (err = error_msgs; err != NULL; err = nexterr) { nexterr = err->next; free(err); } /* for */ error_msgs = NULL; SDL_UnlockMutex(errorlist_mutex); SDL_DestroyMutex(errorlist_mutex); errorlist_mutex = NULL; return(1); } /* Sound_Quit */ const Sound_DecoderInfo **Sound_AvailableDecoders(void) { return(available_decoders); /* READ. ONLY. */ } /* Sound_AvailableDecoders */ static ErrMsg *findErrorForCurrentThread(void) { ErrMsg *i; Uint32 tid; if (error_msgs != NULL) { tid = SDL_ThreadID(); SDL_LockMutex(errorlist_mutex); for (i = error_msgs; i != NULL; i = i->next) { if (i->tid == tid) { SDL_UnlockMutex(errorlist_mutex); return(i); } /* if */ } /* for */ SDL_UnlockMutex(errorlist_mutex); } /* if */ return(NULL); /* no error available. */ } /* findErrorForCurrentThread */ const char *Sound_GetError(void) { const char *retval = NULL; ErrMsg *err; if (!initialized) return(ERR_NOT_INITIALIZED); err = findErrorForCurrentThread(); if ((err != NULL) && (err->error_available)) { retval = err->error_string; err->error_available = 0; } /* if */ return(retval); } /* Sound_GetError */ void Sound_ClearError(void) { ErrMsg *err; if (!initialized) return; err = findErrorForCurrentThread(); if (err != NULL) err->error_available = 0; } /* Sound_ClearError */ /* * This is declared in the internal header. */ void __Sound_SetError(const char *str) { ErrMsg *err; if (str == NULL) return; SNDDBG(("Sound_SetError(\"%s\");%s\n", str, (initialized) ? "" : " [NOT INITIALIZED!]")); if (!initialized) return; err = findErrorForCurrentThread(); if (err == NULL) { err = (ErrMsg *) malloc(sizeof (ErrMsg)); if (err == NULL) return; /* uhh...? */ memset((void *) err, '\0', sizeof (ErrMsg)); err->tid = SDL_ThreadID(); SDL_LockMutex(errorlist_mutex); err->next = error_msgs; error_msgs = err; SDL_UnlockMutex(errorlist_mutex); } /* if */ err->error_available = 1; strncpy(err->error_string, str, sizeof (err->error_string)); err->error_string[sizeof (err->error_string) - 1] = '\0'; } /* __Sound_SetError */ Uint32 __Sound_convertMsToBytePos(Sound_AudioInfo *info, Uint32 ms) { /* "frames" == "sample frames" */ float frames_per_ms = ((float) info->rate) / 1000.0; Uint32 frame_offset = (Uint32) (frames_per_ms * ((float) ms)); Uint32 frame_size = (Uint32) ((info->format & 0xFF) / 8) * info->channels; return(frame_offset * frame_size); } /* __Sound_convertMsToBytePos */ /* * -ansi and -pedantic flags prevent use of strcasecmp() on Linux, and * I honestly don't want to mess around with figuring out if a given * platform has "strcasecmp", "stricmp", or * "compare_two_damned_strings_case_insensitive", which I hear is in the * next release of Carbon. :) This is exported so decoders may use it if * they like. */ int __Sound_strcasecmp(const char *x, const char *y) { int ux, uy; if (x == y) /* same pointer? Both NULL? */ return(0); if (x == NULL) return(-1); if (y == NULL) return(1); do { ux = toupper((int) *x); uy = toupper((int) *y); if (ux > uy) return(1); else if (ux < uy) return(-1); x++; y++; } while ((ux) && (uy)); return(0); } /* __Sound_strcasecmp */ /* * Allocate a Sound_Sample, and fill in most of its fields. Those that need * to be filled in later, by a decoder, will be initialized to zero. */ static Sound_Sample *alloc_sample(SDL_RWops *rw, Sound_AudioInfo *desired, Uint32 bufferSize) { Sound_Sample *retval = malloc(sizeof (Sound_Sample)); Sound_SampleInternal *internal = malloc(sizeof (Sound_SampleInternal)); if ((retval == NULL) || (internal == NULL)) { Sound_SetError(ERR_OUT_OF_MEMORY); if (retval) free(retval); if (internal) free(internal); return(NULL); } /* if */ memset(retval, '\0', sizeof (Sound_Sample)); memset(internal, '\0', sizeof (Sound_SampleInternal)); assert(bufferSize > 0); retval->buffer = malloc(bufferSize); /* pure ugly. */ if (!retval->buffer) { Sound_SetError(ERR_OUT_OF_MEMORY); free(internal); free(retval); return(NULL); } /* if */ memset(retval->buffer, '\0', bufferSize); retval->buffer_size = bufferSize; if (desired != NULL) memcpy(&retval->desired, desired, sizeof (Sound_AudioInfo)); internal->rw = rw; retval->opaque = internal; return(retval); } /* alloc_sample */ #if (defined DEBUG_CHATTER) static __inline__ const char *fmt_to_str(Uint16 fmt) { switch(fmt) { case AUDIO_U8: return("U8"); case AUDIO_S8: return("S8"); case AUDIO_U16LSB: return("U16LSB"); case AUDIO_S16LSB: return("S16LSB"); case AUDIO_U16MSB: return("U16MSB"); case AUDIO_S16MSB: return("S16MSB"); } /* switch */ return("Unknown"); } /* fmt_to_str */ #endif /* * The bulk of the Sound_NewSample() work is done here... * Ask the specified decoder to handle the data in (rw), and if * so, construct the Sound_Sample. Otherwise, try to wind (rw)'s stream * back to where it was, and return false. * * !!! FIXME: This is big, ugly, nasty, and smelly. */ static int init_sample(const Sound_DecoderFunctions *funcs, Sound_Sample *sample, const char *ext, Sound_AudioInfo *_desired) { Sound_SampleInternal *internal = (Sound_SampleInternal *) sample->opaque; Sound_AudioInfo desired; int pos = SDL_RWtell(internal->rw); /* !!! FIXME: Int? Really? */ /* fill in the funcs for this decoder... */ sample->decoder = &funcs->info; internal->funcs = funcs; if (!funcs->open(sample, ext)) { SDL_RWseek(internal->rw, pos, SEEK_SET); /* set for next try... */ return(0); } /* if */ /* success; we've got a decoder! */ /* Now we need to set up the conversion buffer... */ memcpy(&desired, (_desired != NULL) ? _desired : &sample->actual, sizeof (Sound_AudioInfo)); if (desired.format == 0) desired.format = sample->actual.format; if (desired.channels == 0) desired.channels = sample->actual.channels; if (desired.rate == 0) desired.rate = sample->actual.rate; if (Sound_BuildAudioCVT(&internal->sdlcvt, sample->actual.format, sample->actual.channels, sample->actual.rate, desired.format, desired.channels, desired.rate) == -1) { Sound_SetError(SDL_GetError()); funcs->close(sample); SDL_RWseek(internal->rw, pos, SEEK_SET); /* set for next try... */ return(0); } /* if */ if (internal->sdlcvt.len_mult > 1) { void *rc = realloc(sample->buffer, sample->buffer_size * internal->sdlcvt.len_mult); if (rc == NULL) { funcs->close(sample); SDL_RWseek(internal->rw, pos, SEEK_SET); /* set for next try... */ return(0); } /* if */ sample->buffer = rc; } /* if */ /* these pointers are all one and the same. */ memcpy(&sample->desired, &desired, sizeof (Sound_AudioInfo)); internal->sdlcvt.buf = internal->buffer = sample->buffer; internal->buffer_size = sample->buffer_size / internal->sdlcvt.len_mult; internal->sdlcvt.len = internal->buffer_size; /* Prepend our new Sound_Sample to the sample_list... */ SDL_LockMutex(samplelist_mutex); internal->next = sample_list; if (sample_list != NULL) ((Sound_SampleInternal *) sample_list->opaque)->prev = sample; sample_list = sample; SDL_UnlockMutex(samplelist_mutex); SNDDBG(("New sample DESIRED format: %s format, %d rate, %d channels.\n", fmt_to_str(sample->desired.format), sample->desired.rate, sample->desired.channels)); SNDDBG(("New sample ACTUAL format: %s format, %d rate, %d channels.\n", fmt_to_str(sample->actual.format), sample->actual.rate, sample->actual.channels)); SNDDBG(("On-the-fly conversion: %s.\n", internal->sdlcvt.needed ? "ENABLED" : "DISABLED")); return(1); } /* init_sample */ Sound_Sample *Sound_NewSample(SDL_RWops *rw, const char *ext, Sound_AudioInfo *desired, Uint32 bSize) { Sound_Sample *retval; decoder_element *decoder; /* sanity checks. */ BAIL_IF_MACRO(!initialized, ERR_NOT_INITIALIZED, NULL); BAIL_IF_MACRO(rw == NULL, ERR_INVALID_ARGUMENT, NULL); retval = alloc_sample(rw, desired, bSize); if (!retval) return(NULL); /* alloc_sample() sets error message... */ if (ext != NULL) { for (decoder = &decoders[0]; decoder->funcs != NULL; decoder++) { if (decoder->available) { const char **decoderExt = decoder->funcs->info.extensions; while (*decoderExt) { if (__Sound_strcasecmp(*decoderExt, ext) == 0) { if (init_sample(decoder->funcs, retval, ext, desired)) return(retval); break; /* done with this decoder either way. */ } /* if */ decoderExt++; } /* while */ } /* if */ } /* for */ } /* if */ /* no direct extension match? Try everything we've got... */ for (decoder = &decoders[0]; decoder->funcs != NULL; decoder++) { if (decoder->available) { int should_try = 1; const char **decoderExt = decoder->funcs->info.extensions; /* skip if we would have tried decoder above... */ while (*decoderExt) { if (__Sound_strcasecmp(*decoderExt, ext) == 0) { should_try = 0; break; } /* if */ decoderExt++; } /* while */ if (should_try) { if (init_sample(decoder->funcs, retval, ext, desired)) return(retval); } /* if */ } /* if */ } /* for */ /* nothing could handle the sound data... */ free(retval->opaque); if (retval->buffer != NULL) free(retval->buffer); free(retval); SDL_RWclose(rw); Sound_SetError(ERR_UNSUPPORTED_FORMAT); return(NULL); } /* Sound_NewSample */ Sound_Sample *Sound_NewSampleFromFile(const char *filename, Sound_AudioInfo *desired, Uint32 bufferSize) { const char *ext; SDL_RWops *rw; BAIL_IF_MACRO(!initialized, ERR_NOT_INITIALIZED, NULL); BAIL_IF_MACRO(filename == NULL, ERR_INVALID_ARGUMENT, NULL); ext = strrchr(filename, '.'); rw = SDL_RWFromFile(filename, "rb"); BAIL_IF_MACRO(rw == NULL, SDL_GetError(), NULL); if (ext != NULL) ext++; return(Sound_NewSample(rw, ext, desired, bufferSize)); } /* Sound_NewSampleFromFile */ void Sound_FreeSample(Sound_Sample *sample) { Sound_SampleInternal *internal; if (!initialized) { Sound_SetError(ERR_NOT_INITIALIZED); return; } /* if */ if (sample == NULL) { Sound_SetError(ERR_INVALID_ARGUMENT); return; } /* if */ internal = (Sound_SampleInternal *) sample->opaque; SDL_LockMutex(samplelist_mutex); /* update the sample_list... */ if (internal->prev != NULL) { Sound_SampleInternal *prevInternal; prevInternal = (Sound_SampleInternal *) internal->prev->opaque; prevInternal->next = internal->next; } /* if */ else { assert(sample_list == sample); sample_list = internal->next; } /* else */ if (internal->next != NULL) { Sound_SampleInternal *nextInternal; nextInternal = (Sound_SampleInternal *) internal->next->opaque; nextInternal->prev = internal->prev; } /* if */ SDL_UnlockMutex(samplelist_mutex); /* nuke it... */ internal->funcs->close(sample); if (internal->rw != NULL) /* this condition is a "just in case" thing. */ SDL_RWclose(internal->rw); if ((internal->buffer != NULL) && (internal->buffer != sample->buffer)) free(internal->buffer); free(internal); if (sample->buffer != NULL) free(sample->buffer); free(sample); } /* Sound_FreeSample */ int Sound_SetBufferSize(Sound_Sample *sample, Uint32 newSize) { void *newBuf = NULL; Sound_SampleInternal *internal = NULL; BAIL_IF_MACRO(!initialized, ERR_NOT_INITIALIZED, 0); BAIL_IF_MACRO(sample == NULL, ERR_INVALID_ARGUMENT, 0); internal = ((Sound_SampleInternal *) sample->opaque); newBuf = realloc(sample->buffer, newSize * internal->sdlcvt.len_mult); BAIL_IF_MACRO(newBuf == NULL, ERR_OUT_OF_MEMORY, 0); internal->sdlcvt.buf = internal->buffer = sample->buffer = newBuf; sample->buffer_size = newSize; internal->buffer_size = newSize / internal->sdlcvt.len_mult; internal->sdlcvt.len = internal->buffer_size; return(1); } /* Sound_SetBufferSize */ Uint32 Sound_Decode(Sound_Sample *sample) { Sound_SampleInternal *internal = NULL; Uint32 retval = 0; /* a boatload of sanity checks... */ BAIL_IF_MACRO(!initialized, ERR_NOT_INITIALIZED, 0); BAIL_IF_MACRO(sample == NULL, ERR_INVALID_ARGUMENT, 0); BAIL_IF_MACRO(sample->flags & SOUND_SAMPLEFLAG_ERROR, ERR_PREV_ERROR, 0); BAIL_IF_MACRO(sample->flags & SOUND_SAMPLEFLAG_EOF, ERR_PREV_EOF, 0); internal = (Sound_SampleInternal *) sample->opaque; assert(sample->buffer != NULL); assert(sample->buffer_size > 0); assert(internal->buffer != NULL); assert(internal->buffer_size > 0); /* reset EAGAIN. Decoder can flip it back on if it needs to. */ sample->flags &= ~SOUND_SAMPLEFLAG_EAGAIN; retval = internal->funcs->read(sample); if (retval > 0 && internal->sdlcvt.needed) { internal->sdlcvt.len = retval; Sound_ConvertAudio(&internal->sdlcvt); retval = internal->sdlcvt.len_cvt; } /* if */ return(retval); } /* Sound_Decode */ Uint32 Sound_DecodeAll(Sound_Sample *sample) { Sound_SampleInternal *internal = NULL; void *buf = NULL; Uint32 newBufSize = 0; BAIL_IF_MACRO(!initialized, ERR_NOT_INITIALIZED, 0); BAIL_IF_MACRO(sample->flags & SOUND_SAMPLEFLAG_EOF, ERR_PREV_EOF, 0); BAIL_IF_MACRO(sample->flags & SOUND_SAMPLEFLAG_ERROR, ERR_PREV_ERROR, 0); internal = (Sound_SampleInternal *) sample->opaque; while ( ((sample->flags & SOUND_SAMPLEFLAG_EOF) == 0) && ((sample->flags & SOUND_SAMPLEFLAG_ERROR) == 0) ) { Uint32 br = Sound_Decode(sample); void *ptr = realloc(buf, newBufSize + br); if (ptr == NULL) { sample->flags |= SOUND_SAMPLEFLAG_ERROR; Sound_SetError(ERR_OUT_OF_MEMORY); } /* if */ else { buf = ptr; memcpy( ((char *) buf) + newBufSize, sample->buffer, br ); newBufSize += br; } /* else */ } /* while */ if (buf == NULL) /* ...in case first call to realloc() fails... */ return(sample->buffer_size); if (internal->buffer != sample->buffer) free(internal->buffer); free(sample->buffer); internal->sdlcvt.buf = internal->buffer = sample->buffer = buf; sample->buffer_size = newBufSize; internal->buffer_size = newBufSize / internal->sdlcvt.len_mult; internal->sdlcvt.len = internal->buffer_size; return(newBufSize); } /* Sound_DecodeAll */ int Sound_Rewind(Sound_Sample *sample) { Sound_SampleInternal *internal; BAIL_IF_MACRO(!initialized, ERR_NOT_INITIALIZED, 0); internal = (Sound_SampleInternal *) sample->opaque; if (!internal->funcs->rewind(sample)) { sample->flags |= SOUND_SAMPLEFLAG_ERROR; return(0); } /* if */ sample->flags &= ~SOUND_SAMPLEFLAG_EAGAIN; sample->flags &= ~SOUND_SAMPLEFLAG_ERROR; sample->flags &= ~SOUND_SAMPLEFLAG_EOF; return(1); } /* Sound_Rewind */ int Sound_Seek(Sound_Sample *sample, Uint32 ms) { Sound_SampleInternal *internal; BAIL_IF_MACRO(!initialized, ERR_NOT_INITIALIZED, 0); if (!(sample->flags & SOUND_SAMPLEFLAG_CANSEEK)) BAIL_MACRO(ERR_CANNOT_SEEK, 0); internal = (Sound_SampleInternal *) sample->opaque; BAIL_IF_MACRO(!internal->funcs->seek(sample, ms), NULL, 0); sample->flags &= ~SOUND_SAMPLEFLAG_EAGAIN; sample->flags &= ~SOUND_SAMPLEFLAG_ERROR; sample->flags &= ~SOUND_SAMPLEFLAG_EOF; return(1); } /* Sound_Rewind */ /* end of SDL_sound.c ... */