/* midasstr.c * * MIDAS stream library * * $Id: midasstr.c,v 1.7 1997/01/26 23:31:44 jpaana Exp $ * * Copyright 1996,1997 Housemarque Inc. * * This file is part of the MIDAS Sound System, and may only be * used, modified and distributed under the terms of the MIDAS * Sound System license, LICENSE.TXT. By continuing to use, * modify or distribute this file you indicate that you have * read the license and understand and accept it fully. */ #ifdef __LINUX__ #include #include #include #else #define WIN32_LEAN_AND_MEAN #include #include /* FIXME - don't use C RTL */ #include #endif #include "lang.h" #include "mtypes.h" #include "errors.h" #include "mmem.h" #include "sdevice.h" #include "file.h" #include "midasstr.h" /* Maximum number of streams: (just for keeping track of all streams in playback) */ #define MAXSTREAMS 32 /* 32 streams will probably bring any system to its knees, but we'll just be safe */ static strStream *streams[MAXSTREAMS]; /* all streams currently playing */ static SoundDevice *SD; /* Sound Device for streams */ /****************************************************************************\ * enum strFunctIDs * ---------------- * Description: Function IDs for stream library functions \****************************************************************************/ enum strFunctIDs { ID_strInit = ID_str, ID_strClose, ID_strPlayStreamFile, ID_strPlayStreamPolling, ID_strPlayStreamCallback, ID_strStopStream, ID_strFeedStreamData, ID_strSetStreamVolume, ID_strSetStreamPanning, ID_strIsStreamFinished, ID_strSetStreamRate }; /****************************************************************************\ * * Function: int strInit(SoundDevice *SD) * * Description: Initializes the stream library * * Input: SoundDevice *SD Pointer to the Sound Device that will * be used for playing the streams * * Returns: MIDAS error code * \****************************************************************************/ int CALLING strInit(SoundDevice *_SD) { int i; /* Remember the Sound Device: */ SD = _SD; /* No streams are being played: */ for ( i = 0; i < MAXSTREAMS; i++ ) streams[i] = NULL; return OK; } /****************************************************************************\ * * Function: int strClose(void) * * Description: Uninitializes the stream library * * Returns: MIDAS error code * \****************************************************************************/ int CALLING strClose(void) { int i, error; /* Check if there are any streams being played - if yes, stop them: */ for ( i = 0; i < MAXSTREAMS; i++ ) { if ( streams[i] != NULL ) { if ( (error = strStopStream(streams[i])) != OK ) PASSERROR(ID_strClose) } } return OK; } /****************************************************************************\ * * Function: static int StartStream(strStream **stream) * * Description: Starts a new stream, and allocates the info structure. * * Input: strStream **stream pointer to steam state pointer * * Returns: MIDAS error code * \****************************************************************************/ static int StartStream(strStream **stream) { int error; int num; /* Find a free spot from the stream table: */ num = 0; while ( streams[num] != NULL ) { num++; if ( num >= MAXSTREAMS ) return errOutOfResources; } /* Allocate memory for the stream state structure: */ if ( (error = memAlloc(sizeof(strStream), (void**) stream)) != OK ) return error; streams[num] = *stream; return OK; } /****************************************************************************\ * * Function: static int FreeStream(strStream *stream) * * Description: Deallocates a steram and removes it from the stream list * * Input: strStream *stream stream to be deallocated * * Returns: MIDAS error code * \****************************************************************************/ static int FreeStream(strStream *stream) { int error; int i; /* Remove the stream from the stream list: */ for ( i = 0; i < MAXSTREAMS; i++ ) { if ( streams[i] == stream ) streams[i] = NULL; } /* Deallocate the stream structure: */ if ( (error = memFree(stream)) != OK ) return error; return OK; } /****************************************************************************\ * * Function: unsigned StreamBufferLeft(strStream *stream) * * Description: Calculates the number of bytes of free space in a stream * buffer * * Input: strStream *stream stream to check * * Returns: Number of bytes of free space left * \****************************************************************************/ static unsigned StreamBufferLeft(strStream *stream) { unsigned readPos; unsigned spaceLeft; /* FIXME - error callback? */ /* Get reading position: */ SD->GetPosition(stream->sdChannel, &readPos); /* If read and write positions are equal, the whole buffer is empty: */ if ( readPos == stream->bufferWritePos ) return stream->bufferBytes - stream->sampleSize; /* Calculate the amount of free space: */ if ( readPos >= stream->bufferWritePos ) spaceLeft = readPos - stream->bufferWritePos; else spaceLeft = stream->bufferBytes - stream->bufferWritePos + readPos; /* Make sure that we won't wrap around: */ if ( spaceLeft >= stream->sampleSize ) spaceLeft -= stream->sampleSize; else spaceLeft = 0; return spaceLeft; } /****************************************************************************\ * * Function: void WriteStreamData(strStream *stream, uchar *data, * unsigned numBytes) * * Description: Writes data to a stream buffer, updating buffer write position * and taking care of wraparound * * Input: strStream *stream stream for the data * uchar *data pointer to data * unsigned numBytes number of bytes of data to write * \****************************************************************************/ static void WriteStreamData(strStream *stream, uchar *data, unsigned numBytes) { unsigned len = stream->bufferBytes; unsigned pos = stream->bufferWritePos; unsigned left, now; /* Loop until we get everything copied: */ while ( numBytes ) { /* Get number of bytes of space before buffer end: */ left = len - pos; /* Don't copy past buffer end: */ if ( numBytes > left ) now = left; else now = numBytes; /* Copy the data: */ memcpy(&stream->buffer[pos], data, now); /* Advance buffer position: */ pos += now; numBytes -= now; data += now; /* Wrap to buffer beginning if necessary: */ if ( pos >= len ) pos = 0; } /* Remember the new buffer position: */ stream->bufferWritePos = pos; /* FIXME: error check: */ SD->SetStreamWritePosition(stream->sdChannel, pos); } /****************************************************************************\ * * Function: void FillStreamBuffer(strStream *stream, uchar value, * unsigned numBytes) * * Description: Fills a stream buffer with a byte, updating buffer write * position and taking care of wraparound * * Input: strStream *stream stream for the data * uchar value byte to fill the buffer with * unsigned numBytes number of bytes to fill * \****************************************************************************/ static void FillStreamBuffer(strStream *stream, uchar value, unsigned numBytes) { unsigned len = stream->bufferBytes; unsigned pos = stream->bufferWritePos; unsigned left, now; /* Loop until we get everything filled */ while ( numBytes ) { /* Get number of bytes of space before buffer end: */ left = len - pos; /* Don't fill past buffer end: */ if ( numBytes > left ) now = left; else now = numBytes; /* Fill it: */ memset(&stream->buffer[pos], value, now); /* Advance buffer position: */ pos += now; numBytes -= now; /* Wrap to buffer beginning if necessary: */ if ( pos >= len ) pos = 0; } /* Remember the new buffer position: */ stream->bufferWritePos = pos; /* FIXME: error check: */ SD->SetStreamWritePosition(stream->sdChannel, pos); } /****************************************************************************\ * * Function: void StreamPlayerThread(void *stream) * * Description: The stream player thread * * Input: void *stream stream state information for this * stream * \****************************************************************************/ #ifdef __LINUX__ static void* StreamPlayerThread(void *stream) #else #ifdef __WC32__ static void StreamPlayerThread(void *stream) #else static void __cdecl StreamPlayerThread(void *stream) #endif #endif { /* FIXME - we should check for errors, but for that we need some way to report the errors to the user - a callback maybe? */ unsigned bufferLeft; unsigned doNow; strStream *s = (strStream*) stream; /* Round and round we go, until it's exit time */ while ( !s->threadExitFlag ) { /* Get amount of free space in buffer: */ bufferLeft = StreamBufferLeft(s); /* Fill the free space - either read data from the file or just clear it: */ while ( bufferLeft ) { if ( s->fileLeft ) { /* There is still data in the file to read */ /* Don't read past End Of File: */ if ( bufferLeft > s->fileLeft ) doNow = s->fileLeft; else doNow = bufferLeft; /* Read the data from the file: */ /* (the file buffer is always at least as large as the stream buffer so we can't overflow) */ fileRead(s->f, s->fileBuffer, doNow); /* Write the data to the stream buffer: */ WriteStreamData(s, s->fileBuffer, doNow); bufferLeft -= doNow; s->fileLeft -= doNow; /* Check if we reached the file end and should loop: */ if ( (s->fileLeft <= 0) && (s->loop) ) { /* Restart the file: */ s->fileLeft = s->fileLength; fileSeek(s->f, 0, fileSeekAbsolute); } } else { /* No data left in file, just clear the buffer: */ FillStreamBuffer(s, s->bufferClearVal, bufferLeft); bufferLeft = 0; } } /* Sleep for a while: */ #ifdef __LINUX__ usleep(s->threadDelay * 1000); #else Sleep(s->threadDelay); #endif } /* Exit flag detected: */ s->threadExitFlag = 0; /* Get lost: */ #ifdef __LINUX__ pthread_exit(0); return NULL; #else _endthread(); #endif } /****************************************************************************\ * * Function: int strPlayStreamFile(unsigned channel, char *fileName, * unsigned sampleType, ulong sampleRate, * unsigned bufferLength, int loop, strStream **stream) * * Description: Starts playing a digital sound stream from a file. Creates a * new thread that will take care of reading the file and feeding * it to the stream buffer * * Input: unsigned channel channel number for the stream * char *fileName stream file name * unsigned sampleType stream sample type * ulong sampleRate sampling rate * unsigned bufferLength stream buffer length in milliseconds * int loop 1 if the stream should be looped, * 0 if not * strStream **stream pointer to stream state pointer * * Returns: MIDAS error code. Pointer to the stream state structure will * be written to *stream * \****************************************************************************/ int CALLING strPlayStreamFile(unsigned channel, char *fileName, unsigned sampleType, ulong sampleRate, unsigned bufferLength, int loop, strStream **stream) { int error; long fileLen; fileHandle f; strStream *s; #ifdef __WIN32__ #ifdef __WC32__ int streamThread; #else ulong streamThread; #endif #endif #ifdef __LINUX__ int code; #endif /* Stop any sound on the channel: */ if ( (error = SD->StopSound(channel)) != OK ) PASSERROR(ID_strPlayStreamFile); /* Allocate a stream state structure for the new stream: */ if ( (error = StartStream(stream)) != OK ) PASSERROR(ID_strPlayStreamFile); s = *stream; /* Initialize the stream structure: */ s->sdChannel = channel; s->streamMode = strStreamPlayFile; s->loop = loop; s->threadExitFlag = 0; s->threadDelay = bufferLength / 8; s->callback = NULL; /* Check the sample type and initialize accordingly: */ switch ( sampleType ) { case smp8bitMono: s->sampleSize = 1; s->bufferClearVal = 128; break; case smp16bitMono: s->sampleSize = 2; s->bufferClearVal = 0; break; case smp8bitStereo: s->sampleSize = 2; s->bufferClearVal = 128; break; case smp16bitStereo: s->sampleSize = 4; s->bufferClearVal = 0; break; default: ERROR(errInvalidArguments, ID_strPlayStreamFile); return errInvalidArguments; } /* Calculate the buffer size in samples: (make it a multiple of 4) */ s->bufferSamples = ((bufferLength * sampleRate / 1000) + 3) & (~3); /* And in bytes: */ s->bufferBytes = s->sampleSize * s->bufferSamples; /* Allocate the buffer: */ if ( (error = memAlloc(s->bufferBytes, (void**) &s->buffer)) != OK ) { FreeStream(s); PASSERROR(ID_strPlayStreamFile); } /* Clear it: */ s->bufferWritePos = 0; memset(s->buffer, s->bufferClearVal, s->bufferBytes); /* Allocate a file reading buffer: */ s->fileBufferBytes = s->bufferBytes; if ( (error = memAlloc(s->fileBufferBytes, (void**) &s->fileBuffer)) != OK ) { memFree(s->buffer); FreeStream(s); PASSERROR(ID_strPlayStreamFile); } /* Open the stream file and get its length: */ if ( (error = fileOpen(fileName, fileOpenRead, &f)) != OK ) { memFree(s->fileBuffer); memFree(s->buffer); FreeStream(s); PASSERROR(ID_strPlayStreamFile); } if ( (error = fileGetSize(f, &fileLen)) != OK ) { fileClose(f); memFree(s->fileBuffer); memFree(s->buffer); FreeStream(s); PASSERROR(ID_strPlayStreamFile); } s->f = f; s->fileLength = s->fileLeft = fileLen; /* If the file is larger than the stream buffer, fill the buffer with data from the file to minimize delay at startup: */ if ( s->fileLeft > s->bufferBytes ) { if ( (error = fileRead(f, s->buffer, s->bufferBytes - s->sampleSize)) != OK ) { fileClose(f); memFree(s->fileBuffer); memFree(s->buffer); FreeStream(s); PASSERROR(ID_strPlayStreamFile); } s->fileLeft -= s->bufferBytes - s->sampleSize; s->bufferWritePos = s->bufferBytes - s->sampleSize; } /* Start playing the stream: */ if ( (error = SD->StartStream(s->sdChannel, s->buffer, s->bufferBytes, sampleType, sampleRate)) != OK ) { fileClose(f); memFree(s->fileBuffer); memFree(s->buffer); FreeStream(s); PASSERROR(ID_strPlayStreamFile); } /* Set stream write position to the end of (possibly) just read data: */ if ( (error = SD->SetStreamWritePosition(s->sdChannel, s->bufferWritePos)) != OK ) { SD->StopStream(s->sdChannel); fileClose(f); memFree(s->fileBuffer); memFree(s->buffer); FreeStream(s); PASSERROR(ID_strPlayStreamFile); } /* Start the stream player thread: */ #ifdef __WIN32__ #ifdef __WC32__ streamThread = _beginthread(StreamPlayerThread, NULL, 4096, (void*) s); if ( streamThread == -1 ) { /* Couldn't create thread */ SD->StopStream(s->sdChannel); fileClose(f); memFree(s->fileBuffer); memFree(s->buffer); FreeStream(s); ERROR(errOutOfResources, ID_strPlayStreamFile); return errOutOfResources; } #else streamThread = _beginthread(StreamPlayerThread, 4096, (void*) s); if ( streamThread == -1 ) { /* Couldn't create thread */ SD->StopStream(s->sdChannel); fileClose(f); memFree(s->fileBuffer); memFree(s->buffer); FreeStream(s); ERROR(errOutOfResources, ID_strPlayStreamFile); return errOutOfResources; } #endif #else code = pthread_create(&s->playerThread, NULL, StreamPlayerThread, (void*) s); if ( code ) { /* Couldn't create thread */ SD->StopStream(s->sdChannel); fileClose(f); memFree(s->fileBuffer); memFree(s->buffer); FreeStream(s); ERROR(errOutOfResources, ID_strPlayStreamFile); return errOutOfResources; } #endif /* The thread is playing OK */ return OK; } /****************************************************************************\ * * Function: int strStopStream(strStream *stream) * * Description: Stops playing a stream. This function will also destroy the * playback thread for stream file playback. * * Input: strStream *stream stream state pointer for the stream * * Returns: MIDAS error code. * \****************************************************************************/ int CALLING strStopStream(strStream *stream) { #ifdef __LINUX__ void *retval; #endif int error; /* Stop the player thread if we are playing from a file: */ if ( stream->streamMode == strStreamPlayFile ) { /* Signal the stream player thread that it should actually stop: */ stream->threadExitFlag = 1; /* Wait until it stops: */ #ifdef __LINUX__ pthread_join(stream->playerThread, &retval); #else while ( stream->threadExitFlag ) Sleep(stream->threadDelay); #endif /* [Now is that ugly or what? But it works and it's portable] */ } /* Stop the Sound Device playing the stream: */ if ( (error = SD->StopStream(stream->sdChannel)) != OK ) { /* Failed - let's try to do at least some cleanup anyway: */ if ( stream->streamMode == strStreamPlayFile ) { fileClose(stream->f); memFree(stream->fileBuffer); } memFree(stream->buffer); FreeStream(stream); PASSERROR(ID_strStopStream); } /* Close the file and deallocate file buffer only if we were playing a stream file: */ if ( stream->streamMode == strStreamPlayFile ) { /* Close the stream file: */ if ( (error = fileClose(stream->f)) != OK ) { memFree(stream->fileBuffer); memFree(stream->buffer); FreeStream(stream); PASSERROR(ID_strStopStream); } /* Deallocate stream file buffer: */ if ( (error = memFree(stream->fileBuffer)) != OK ) { memFree(stream->buffer); FreeStream(stream); PASSERROR(ID_strStopStream); } } /* Deallocate stream buffer: */ if ( (error = memFree(stream->buffer)) != OK ) { FreeStream(stream); PASSERROR(ID_strStopStream); } /* Finally, free the stream: */ if ( (error = FreeStream(stream)) != OK ) PASSERROR(ID_strStopStream); /* Phew, done: */ return OK; } /****************************************************************************\ * * Function: int strPlayStreamPolling(unsigned channel, * unsigned sampleType, ulong sampleRate, * unsigned bufferLength, strStream **stream) * * Description: Starts playing a stream in polling mode. Use * strFeedStreamData() to feed the stream data to the player * * Input: unsigned channel channel number for the stream * unsigned sampleType stream sample type * ulong sampleRate stream sampling rate * unsigned bufferLength stream buffer length in milliseconds * strStream **stream pointer to stream state pointer * * Returns: MIDAS error code. Pointer to the stream state structure will * be written to *stream * \****************************************************************************/ int CALLING strPlayStreamPolling(unsigned channel, unsigned sampleType, ulong sampleRate, unsigned bufferLength, strStream **stream) { int error; strStream *s; /* Stop any sound on the channel: */ if ( (error = SD->StopSound(channel)) != OK ) PASSERROR(ID_strPlayStreamFile); /* Allocate a stream state structure for the new stream: */ if ( (error = StartStream(stream)) != OK ) PASSERROR(ID_strPlayStreamFile); s = *stream; /* Initialize the stream structure: */ s->sdChannel = channel; s->streamMode = strStreamPoll; s->loop = 1; s->threadExitFlag = 0; s->threadDelay = bufferLength / 8; s->callback = NULL; /* Check the sample type and initialize accordingly: */ switch ( sampleType ) { case smp8bitMono: s->sampleSize = 1; s->bufferClearVal = 128; break; case smp16bitMono: s->sampleSize = 2; s->bufferClearVal = 0; break; case smp8bitStereo: s->sampleSize = 2; s->bufferClearVal = 128; break; case smp16bitStereo: s->sampleSize = 4; s->bufferClearVal = 0; break; default: ERROR(errInvalidArguments, ID_strPlayStreamFile); return errInvalidArguments; } /* Calculate the buffer size in samples: (make it a multiple of 4) */ s->bufferSamples = ((bufferLength * sampleRate / 1000) + 3) & (~3); /* And in bytes: */ s->bufferBytes = s->sampleSize * s->bufferSamples; /* Allocate the buffer: */ if ( (error = memAlloc(s->bufferBytes, (void**) &s->buffer)) != OK ) { FreeStream(s); PASSERROR(ID_strPlayStreamPolling); } /* Clear it: */ s->bufferWritePos = 0; memset(s->buffer, s->bufferClearVal, s->bufferBytes); /* Start playing the stream: */ if ( (error = SD->StartStream(s->sdChannel, s->buffer, s->bufferBytes, sampleType, sampleRate)) != OK ) { memFree(s->buffer); FreeStream(s); PASSERROR(ID_strPlayStreamFile); } return OK; } /****************************************************************************\ * * Function: int strPlayStreamCallback(unsigned sampleType, * ulong sampleRate, unsigned bufferBytes, * void (CALLING *callback)(uchar *buffer, strStream *stream)) * * Description: Starts playing a stream with a callback. * * Input: unsigned sampleType stream sample type * ulong sampleRate stream sampling rate * unsigned bufferBytes stream buffer size _IN BYTES_ * ... *callback stream player callback * strStream **stream pointer to stream state pointer * * Returns: MIDAS error code. Pointer to the stream state structure will * be written to *stream * * Notes: The callback function will be called each time the whole * stream buffer needs to be filled. It receives as an argument * a pointer to the buffer, and the stream state pointer. * * The function will be called from inside the mixing routine, * so it should return relatively rapidly - do not use this * function for, for example, loading data from disc. * \****************************************************************************/ int CALLING strPlayStreamCallback(unsigned sampleType, ulong sampleRate, unsigned bufferBytes, void (CALLING *callback)(uchar *buffer, strStream *stream)); /****************************************************************************\ * * Function: int strFeedStreamData(strStream *stream, uchar *data, * unsigned numBytes, int feedAll, unsigned *numFed) * * Description: Feeds sample data to a stream that is being played in polling * mode. * * Input: strStream *stream stream state pointer from * strPlayStreamPolling() * uchar *data pointer to stream data * unsigned numBytes number of bytes of data to feed. Note! * This must be a multiple of the stream * sample size * int feedAll 1 if all data should be fed in all * circumstances. The function will block * the current thread if this flag is 1 * until all data is fed. * unsigned *numFed pointer to a variable that will * contain the number of bytes actually * fed * * Returns: MIDAS error code. The number of bytes of data actually fed is * written to *numFed. * \****************************************************************************/ int CALLING strFeedStreamData(strStream *stream, uchar *data, unsigned numBytes, int feedAll, unsigned *numFed) { unsigned bufferLeft; unsigned doNow; *numFed = 0; /* Loop until all data has been fed if feedAll is 1: */ do { /* Get amount of free space in buffer: */ bufferLeft = StreamBufferLeft(stream); if ( bufferLeft > numBytes ) doNow = numBytes; else doNow = bufferLeft; /* Write the data there: */ WriteStreamData(stream, data, doNow); /* Update pointers and counters: */ numBytes -= doNow; *numFed += doNow; data += doNow; /* If we should feed all data, and there is data to go, sleep for a while: */ if ( feedAll && numBytes ) { #ifdef __LINUX__ usleep(stream->threadDelay * 1000); #else Sleep(stream->threadDelay); #endif } } while ( feedAll && numBytes ); return OK; } /****************************************************************************\ * * Function: int strSetStreamRate(strStream *stream, ulong sampleRate) * * Description: Changes the sampling rate for a stream * * Input: strStream *stream stream state pointer * ulong sampleRate new sampling rate in Hz * * Returns: MIDAS error code * \****************************************************************************/ int CALLING strSetStreamRate(strStream *stream, ulong sampleRate) { int error; /* Set the sample rate: */ if ( (error = SD->SetRate(stream->sdChannel, sampleRate)) != OK ) PASSERROR(ID_strSetStreamRate); return OK; } /****************************************************************************\ * * Function: int strSetStreamVolume(strStream *stream, unsigned volume) * * Description: Changes the playback volume for a stream (the default is 64). * * Input: strStream *stream stream state pointer * unsigned volume new volume (0-64) * * Returns: MIDAS error code. * \****************************************************************************/ int CALLING strSetStreamVolume(strStream *stream, unsigned volume) { int error; /* Set the volume: */ if ( (error = SD->SetRate(stream->sdChannel, volume)) != OK ) PASSERROR(ID_strSetStreamVolume); return OK; } /****************************************************************************\ * * Function: int strSetStreamPanning(strStream *stream, int panning) * * Description: Changes the panning for a stream (the default is middle). * * Input: strStream *stream stream state pointer * int panning new panning position * * Returns: MIDAS error code. * \****************************************************************************/ int CALLING strSetStreamPanning(strStream *stream, int panning) { int error; /* Set the panning position: */ if ( (error = SD->SetRate(stream->sdChannel, panning)) != OK ) PASSERROR(ID_strSetStreamPanning); return OK; } /****************************************************************************\ * * Function: int strIsStreamFinished(strStream *stream, int *finished) * * Description: Checks whether a given stream has reached the end of the * stream file or not. Only applies to streams played from a * file. * * Input: strStream *stream stream state pointer * int *finished pointer to result variable * * Returns: MIDAS error code. If the stream is finished, 1 will be written * to *finished, otherwise *finished will contain 0. * \****************************************************************************/ int CALLING strIsStreamFinished(strStream *stream, int *finished); /* * $Log: midasstr.c,v $ * Revision 1.7 1997/01/26 23:31:44 jpaana * Small fixes for pthreads * * Revision 1.6 1997/01/16 19:31:53 pekangas * Fixed to compile with Linux GCC (but do they work?) * * Revision 1.5 1997/01/16 18:41:59 pekangas * Changed copyright messages to Housemarque * * Revision 1.4 1997/01/16 18:25:08 pekangas * Implemented strSetStreamRate, strSetStreamVolume and strSetStreamPanning * */