/* Copyright (C) 2005 Michael Liebscher 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. */ /* * sound_stream.c: Sound Stream manager. * * Author: Michael Liebscher * Date: 2004 * * Acknowledgement: * Portion of this code was derived from Quake II Evolved. * */ #include "../wolfiphone.h" typedef struct { char introName[ MAX_GAMEPATH ]; char loopName[ MAX_GAMEPATH ]; _boolean looping; filehandle_t *hFile; int start; int rate; unsigned format; void *vorbisFile; } musicTrack_t; // anything greater than 1<<13 caused crashes on iphone OS 2.1 (on a 3G iphone) #define BUFFER_SIZE (1<<13) PRIVATE musicTrack_t bgTrack; PRIVATE channel_t *s_streamingChannel; extern void Sound_StopBGTrack( void ); /* ----------------------------------------------------------------------------- Function: ovc_read -OGG read Callback. Reads data from a stream. Parameters: ptr -[in] Storage location for data. size -[in] Item size in bytes. nmemb -[in] Maximum number of items to be read. datasource -[in] music track data structure. Returns: Nothing Notes: ----------------------------------------------------------------------------- */ PRIVATE size_t ovc_read( void *ptr, size_t size, size_t nmemb, void *datasource ) { musicTrack_t *track = (musicTrack_t *)datasource; if( ! size || ! nmemb ) { return 0; } return FS_ReadFile( ptr, size, nmemb, track->hFile ); } /* ----------------------------------------------------------------------------- Function: ovc_read -OGG seek Callback. Moves the file pointer to a specified location. Parameters: datasource -[in] music track data structure. offset -[in] Number of bytes from whence. whence -[in] Initial position. Returns: If successful, fseek returns 0. Otherwise, it returns a nonzero value. Notes: ----------------------------------------------------------------------------- */ PRIVATE int ovc_seek( void *datasource, ogg_int64_t offset, int whence ) { musicTrack_t *track = (musicTrack_t *)datasource; return FS_FileSeek( track->hFile, offset, whence ); } /* ----------------------------------------------------------------------------- Function: ovc_close -OGG close Callback. Closes a stream. Parameters: datasource -[in] music track data structure. Returns: 0 if the stream is successfully closed, otherwise nonzero. Notes: ----------------------------------------------------------------------------- */ PRIVATE int ovc_close( void *datasource ) { return 0; } /* ----------------------------------------------------------------------------- Function: ovc_tell -OGG tell Callback. Gets the current position of a file pointer. Parameters: datasource -[in] music track data structure. Returns: The current file position. Notes: ----------------------------------------------------------------------------- */ PRIVATE long ovc_tell( void *datasource ) { musicTrack_t *track = (musicTrack_t *)datasource; return FS_FileTell( track->hFile ); } /* ----------------------------------------------------------------------------- Function: Sound_OpenBGTrack -OGG read Callback. Parameters: name -[in] File name to open. track -[in/out] Music track data structure. Returns: False on error, otherwise true. Notes: ----------------------------------------------------------------------------- */ PRIVATE _boolean Sound_OpenBGTrack( const char *name, musicTrack_t *track ) { OggVorbis_File *vorbisFile; vorbis_info *vorbisInfo; ov_callbacks vorbisCallbacks = {ovc_read, ovc_seek, ovc_close, ovc_tell}; int ret; extern cvar_t *music; if ( music->value == 0 ) { return 0; } track->hFile = FS_OpenFile( name, 0 ); if( ! track->hFile ) { return false; } track->vorbisFile = vorbisFile = Z_Malloc( sizeof( OggVorbis_File ) ); if( (ret = ov_open_callbacks( track, vorbisFile, NULL, 0, vorbisCallbacks )) < 0 ) { switch( ret ) { case OV_EREAD: Com_DPrintf( "A read from media returned an error.(%s)\n", name ); break; case OV_ENOTVORBIS: Com_DPrintf( "Bitstream is not Vorbis data.(%s)\n", name ); break; case OV_EVERSION: Com_DPrintf( "Vorbis version mismatch.(%s)\n", name ); break; case OV_EBADHEADER: Com_DPrintf( "Invalid Vorbis bitstream header.(%s)\n", name ); break; case OV_EFAULT: Com_DPrintf( "Internal logic fault; indicates a bug or heap/stack corruption.(%s)\n", name ); break; } Com_DPrintf( "Could not open OGG stream (%s)\n", name ); return false; } vorbisInfo = ov_info( vorbisFile, -1 ); if( vorbisInfo->channels != 1 && vorbisInfo->channels != 2 ) { Com_DPrintf( "Only mono and stereo OGG files supported (%s)\n", name ); return false; } track->start = ov_raw_tell( vorbisFile ); track->rate = vorbisInfo->rate; track->format = (vorbisInfo->channels == 2) ? AL_FORMAT_STEREO16 : AL_FORMAT_MONO16; return true; } /* ----------------------------------------------------------------------------- Function: Sound_CloseBGTrack -Close out background music track. Parameters: track -[in] Music track to close. Returns: Nothing Notes: ----------------------------------------------------------------------------- */ PRIVATE void Sound_CloseBGTrack( musicTrack_t *track ) { if( track->vorbisFile ) { ov_clear( track->vorbisFile ); Z_Free( track->vorbisFile ); track->vorbisFile = NULL; } if( track->hFile ) { FS_CloseFile( track->hFile ); } } /* ----------------------------------------------------------------------------- Function: Sound_StreamBGTrack -Called each frame to update streaming music track. Parameters: Nothing Returns: Nothing Notes: ----------------------------------------------------------------------------- */ PUBLIC void Sound_StreamBGTrack( void ) { W8 data[BUFFER_SIZE]; int processed, queued, state; int size, read, dummy; unsigned buffer; if( ! s_musicVolume->value ) { return; } if( ! s_streamingChannel ) { return; } // Unqueue and delete any processed buffers pfalGetSourcei( s_streamingChannel->sourceName, AL_BUFFERS_PROCESSED, &processed ); if( processed > 0 ) { while (processed--) { pfalSourceUnqueueBuffers( s_streamingChannel->sourceName, 1, &buffer ); pfalDeleteBuffers( 1, &buffer ); } } // Make sure we always have at least 4 buffers in the queue pfalGetSourcei( s_streamingChannel->sourceName, AL_BUFFERS_QUEUED, &queued ); while( queued < 4 ) { size = 0; // Stream from disk while( size < BUFFER_SIZE ) { read = ov_read( bgTrack.vorbisFile, (char *)data + size, BUFFER_SIZE - size, &dummy ); if( read == 0 ) { // End of file if( ! bgTrack.looping) { // Close the intro track Sound_CloseBGTrack( &bgTrack ); // Open the loop track if( ! Sound_OpenBGTrack( bgTrack.loopName, &bgTrack ) ) { Sound_StopBGTrack(); return; } bgTrack.looping = true; } // Restart the track, skipping over the header ov_raw_seek( bgTrack.vorbisFile, (ogg_int64_t)bgTrack.start ); // Try streaming again read = ov_read( bgTrack.vorbisFile, (char *)data + size, BUFFER_SIZE - size, &dummy ); } if( read <= 0 ) { // An error occurred Sound_StopBGTrack(); return; } size += read; } // Upload and queue the new buffer pfalGenBuffers( 1, &buffer ); pfalBufferData( buffer, bgTrack.format, data, size, bgTrack.rate ); pfalSourceQueueBuffers( s_streamingChannel->sourceName, 1, &buffer ); queued++; } // Update volume pfalSourcef( s_streamingChannel->sourceName, AL_GAIN, s_musicVolume->value ); // If not playing, then do so pfalGetSourcei( s_streamingChannel->sourceName, AL_SOURCE_STATE, &state ); if( state != AL_PLAYING ) { pfalSourcePlay(s_streamingChannel->sourceName); } } /* ----------------------------------------------------------------------------- Function: Sound_StartStreaming -Start streaming background music track. Parameters: Nothing Returns: Nothing Notes: ----------------------------------------------------------------------------- */ PUBLIC void Sound_StartStreaming( void ) { if( ! sound_initialized ) { return; } if( s_streamingChannel ) { return; } s_streamingChannel = Sound_PickChannel( 0, 0 ); if( ! s_streamingChannel ) { return; } s_streamingChannel->streaming = true; // hmmm... pfalDeleteSources( 1, &s_streamingChannel->sourceName ); pfalGenSources( 1, &s_streamingChannel->sourceName ); // Set up the source pfalSourcei( s_streamingChannel->sourceName, AL_BUFFER, 0 ); pfalSourcei( s_streamingChannel->sourceName, AL_LOOPING, AL_FALSE ); pfalSourcei( s_streamingChannel->sourceName, AL_SOURCE_RELATIVE, AL_TRUE ); pfalSourcefv( s_streamingChannel->sourceName, AL_POSITION, vec3_origin ); pfalSourcefv( s_streamingChannel->sourceName, AL_VELOCITY, vec3_origin ); pfalSourcef( s_streamingChannel->sourceName, AL_REFERENCE_DISTANCE, 1.0 ); pfalSourcef( s_streamingChannel->sourceName, AL_MAX_DISTANCE, 1.0 ); pfalSourcef( s_streamingChannel->sourceName, AL_ROLLOFF_FACTOR, 0.0 ); } /* ----------------------------------------------------------------------------- Function: Sound_StopStreaming -Stop playing streaming music track. Parameters: Nothing Returns: Nothing Notes: ----------------------------------------------------------------------------- */ PUBLIC void Sound_StopStreaming( void ) { int processed; unsigned buffer; if( ! sound_initialized ) { return; } if( ! s_streamingChannel ) { return; } s_streamingChannel->streaming = false; pfalSourceStop( s_streamingChannel->sourceName ); pfalGetSourcei( s_streamingChannel->sourceName, AL_BUFFERS_PROCESSED, &processed ); if( processed > 0 ) { while( processed-- ) { pfalSourceUnqueueBuffers( s_streamingChannel->sourceName, 1, &buffer ); pfalDeleteBuffers( 1, &buffer ); } } pfalSourcei( s_streamingChannel->sourceName, AL_BUFFER, 0 ); // hmmm... pfalDeleteSources( 1, &s_streamingChannel->sourceName ); pfalGenSources( 1, &s_streamingChannel->sourceName ); s_streamingChannel = NULL; } /* ----------------------------------------------------------------------------- Function: Sound_StartBGTrack -Play background music track. Parameters: introTrack -[in] File name of intro track. loopTrack -[in] File name of loop track. Returns: Nothing Notes: ----------------------------------------------------------------------------- */ PUBLIC void Sound_StartBGTrack( const char *introTrack, const char *loopTrack ) { if( ! sound_initialized ) { return; } Sound_StopBGTrack(); my_strlcpy( bgTrack.introName, introTrack, sizeof( bgTrack.introName ) ); my_strlcpy( bgTrack.loopName, loopTrack, sizeof( bgTrack.loopName) ); Sound_StartStreaming(); if( ! Sound_OpenBGTrack( bgTrack.introName, &bgTrack ) ) { Sound_StopBGTrack(); return; } Sound_StreamBGTrack(); } /* ----------------------------------------------------------------------------- Function: Sound_StopBGTrack -Stop playing background track. Parameters: Nothing Returns: Nothing Notes: ----------------------------------------------------------------------------- */ PUBLIC void Sound_StopBGTrack( void ) { if( ! sound_initialized ) { return; } Sound_StopStreaming(); Sound_CloseBGTrack( &bgTrack ); memset( &bgTrack, 0, sizeof( musicTrack_t ) ); }