/* Copyright (C) 1994-1995 Apogee Software, Ltd. 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. */ /********************************************************************** module: MULTIVOC.C author: James R. Dose date: December 20, 1993 Routines to provide multichannel digitized sound playback for Sound Blaster compatible sound cards. (c) Copyright 1993 James R. Dose. All Rights Reserved. **********************************************************************/ #include #include "dpmi.h" #include "usrhooks.h" #include "interrup.h" #include "dpmi.h" #include "ll_man.h" #include "sndcards.h" #include "blaster.h" #include "sndsrc.h" #include "pas16.h" #include "pitch.h" #include "multivoc.h" #include "_multivc.h" static signed char MV_VolumeTable[ sizeof( VOLUME_TABLE_16BIT ) ]; static VOLUME_TABLE_8BIT *MV_8BitVolumeTable = &MV_VolumeTable[ 0 ]; static VOLUME_TABLE_16BIT *MV_16BitVolumeTable = &MV_VolumeTable[ 0 ]; static Pan MV_PanTable[ MV_NumPanPositions ][ MV_MaxVolume + 1 ]; static int MV_Installed = FALSE; static int MV_SoundCard = SoundBlaster; static int MV_TotalVolume = MV_MaxTotalVolume; static int MV_MaxVoices = 1; static int MV_BufferSize = MixBufferSize; static int MV_SampleSize = 1; static int MV_NumberOfBuffers = NumberOfBuffers; static int MV_MixMode = MONO_8BIT; static int MV_Silence = SILENCE_8BIT; static int MV_SwapLeftRight = FALSE; static int MV_RequestedMixRate; static int MV_MixRate; static int MV_BufferDescriptor; static char *MV_MixBuffer[ NumberOfBuffers ]; static VoiceNode *MV_Voices = NULL; //static VoiceNode MV_Voices[ 16 ]; static volatile VList VoiceList = { NULL, NULL }; static volatile VList VoicePool = { NULL, NULL }; static int MV_MixPage = 0; static int MV_PlayPage = 0; static int MV_VoiceHandle = MV_MinVoiceHandle; static void ( *MV_CallBackFunc )( unsigned long ) = NULL; //char HarshClipTable[ 8 * 256 ]; //unsigned short HarshClipTable16[ 8 * 256 * 16 ]; char *HarshClipTable; unsigned short *HarshClipTable16; short *HarshClipTable16s; int MV_ErrorCode = MV_Ok; #define MV_SetErrorCode( status ) \ MV_ErrorCode = ( status ); /*--------------------------------------------------------------------- Function: MV_ErrorString Returns a pointer to the error message associated with an error number. A -1 returns a pointer the current error. ---------------------------------------------------------------------*/ char *MV_ErrorString ( int ErrorNumber ) { char *ErrorString; switch( ErrorNumber ) { case MV_Warning : case MV_Error : ErrorString = MV_ErrorString( MV_ErrorCode ); break; case MV_Ok : ErrorString = "Multivoc ok."; break; case MV_UnsupportedCard : ErrorString = "Selected sound card is not supported by Multivoc."; break; case MV_NotInstalled : ErrorString = "Multivoc not installed."; break; case MV_NoVoices : ErrorString = "No free voices available to Multivoc."; break; case MV_NoMem : ErrorString = "Out of memory in Multivoc."; break; case MV_VoiceNotFound : ErrorString = "No voice with matching handle found."; break; case MV_BlasterError : ErrorString = BLASTER_ErrorString( BLASTER_Error ); break; case MV_PasError : ErrorString = PAS_ErrorString( PAS_Error ); break; #ifndef SOUNDSOURCE_OFF case MV_SoundSourceError : ErrorString = SS_ErrorString( SS_Error ); break; #endif case MV_DPMI_Error : ErrorString = "DPMI Error in Multivoc."; break; default : ErrorString = "Unknown Multivoc error code."; break; } return( ErrorString ); } /********************************************************************** Memory locked functions: **********************************************************************/ #define MV_LockStart MV_Mix8bitMono /*--------------------------------------------------------------------- Function: MV_Mix8bitMono Mixes the sound into the buffer as an 8 bit mono sample. ---------------------------------------------------------------------*/ void MV_Mix8bitMono ( VoiceNode *voice, int buffer ) { // MONO8 *to; char *to; VOLUME8 *VolumeTable; char *start; char *from; unsigned long position; int length; unsigned long rate; int samp; to = ( MONO8 * )MV_MixBuffer[ buffer ]; VolumeTable = ( VOLUME8 * )&( *MV_8BitVolumeTable )[ voice->Volume ]; start = voice->sound; rate = voice->RateScale; position = voice->position; length = MixBufferSize; // Add this voice to the mix while( length > 0 ) { if ( position >= voice->length ) { voice->position = position; MV_GetNextVOCBlock( voice ); if ( voice->Playing ) { start = voice->sound; rate = voice->RateScale; position = voice->position; } else { break; } } // from = start + ( position >> 16 ); // *to += ( *VolumeTable )[ *from ]; from = start + ( position >> 16 ); samp = ( ( int )*to ) + ( *VolumeTable )[ *from ]; *to = HarshClipTable[ 4 * 256 + samp - 0x80 ]; to++; position += rate; length--; } voice->position = position; } /*--------------------------------------------------------------------- Function: MV_Mix8bitStereo Mixes the sound into the buffer as an 8 bit stereo sample. ---------------------------------------------------------------------*/ void MV_Mix8bitStereo ( VoiceNode *voice, int buffer ) { STEREO8 *to; VOLUME8 *LeftVolumeTable; VOLUME8 *RightVolumeTable; char *start; unsigned long position; int length; // int sample; unsigned long rate; char *from; int samp; to = ( STEREO8 * )MV_MixBuffer[ buffer ]; LeftVolumeTable = ( VOLUME8 * )&( *MV_8BitVolumeTable )[ voice->LeftVolume ]; RightVolumeTable = ( VOLUME8 * )&( *MV_8BitVolumeTable )[ voice->RightVolume ]; start = voice->sound; rate = voice->RateScale; position = voice->position; length = MixBufferSize; // Add this voice to the mix while( length > 0 ) { if ( position >= voice->length ) { voice->position = position; MV_GetNextVOCBlock( voice ); if ( voice->Playing ) { start = voice->sound; rate = voice->RateScale; position = voice->position; } else { break; } } // sample = ( int )*( start + ( position >> 16 ) ); // to->left += ( *LeftVolumeTable )[ sample ]; // to->right += ( *RightVolumeTable )[ sample ]; from = start + ( position >> 16 ); samp = ( ( int )to->left ) + ( *LeftVolumeTable )[ *from ]; to->left = HarshClipTable[ 4 * 256 + samp - 0x80 ]; samp = ( ( int )to->right ) + ( *RightVolumeTable )[ *from ]; to->right = HarshClipTable[ 4 * 256 + samp - 0x80 ]; to++; position += rate; length--; } voice->position = position; } /*--------------------------------------------------------------------- Function: MV_Mix16bitUnsignedMono Mixes the sound into the buffer as an 16 bit mono sample. ---------------------------------------------------------------------*/ void MV_Mix16bitUnsignedMono ( VoiceNode *voice, int buffer ) { // MONO16 *to; unsigned short *to; VOLUME16 *VolumeTable; char *start; char *from; unsigned long position; int length; unsigned long rate; int samp; // to = ( MONO16 * )MV_MixBuffer[ buffer ]; to = ( unsigned short * )MV_MixBuffer[ buffer ]; VolumeTable = ( VOLUME16 * )&( *MV_16BitVolumeTable )[ voice->Volume ]; start = voice->sound; rate = voice->RateScale; position = voice->position; length = MixBufferSize; // Add this voice to the mix while( length > 0 ) { if ( position >= voice->length ) { voice->position = position; MV_GetNextVOCBlock( voice ); if ( voice->Playing ) { start = voice->sound; rate = voice->RateScale; position = voice->position; } else { break; } } // from = start + ( position >> 16 ); // *to += ( *VolumeTable )[ *from ]; from = start + ( position >> 16 ); samp = ( ( *to - 0x8000 ) >> 4 ) + ( *VolumeTable )[ *from ]; *to = HarshClipTable16[ 4 * 256 * 16 + samp ];// << 4; to++; position += rate; length--; } voice->position = position; } /*--------------------------------------------------------------------- Function: MV_Mix16bitUnsignedStereo Mixes the sound into the buffer as an 16 bit stereo sample. ---------------------------------------------------------------------*/ void MV_Mix16bitUnsignedStereo ( VoiceNode *voice, int buffer ) { STEREO16 *to; VOLUME16 *LeftVolumeTable; VOLUME16 *RightVolumeTable; char *start; unsigned long position; int length; // int sample; unsigned long rate; char *from; int samp; to = ( STEREO16 * )MV_MixBuffer[ buffer ]; LeftVolumeTable = ( VOLUME16 * )&( *MV_16BitVolumeTable )[ voice->LeftVolume ]; RightVolumeTable = ( VOLUME16 * )&( *MV_16BitVolumeTable )[ voice->RightVolume ]; start = voice->sound; rate = voice->RateScale; position = voice->position; length = MixBufferSize; // Add this voice to the mix while( length > 0 ) { if ( position >= voice->length ) { voice->position = position; MV_GetNextVOCBlock( voice ); if ( voice->Playing ) { start = voice->sound; rate = voice->RateScale; position = voice->position; } else { break; } } // sample = ( int )*( start + ( position >> 16 ) ); // to->left += ( *LeftVolumeTable )[ sample ]; // to->right += ( *RightVolumeTable )[ sample ]; from = start + ( position >> 16 ); samp = ( ( to->left - 0x8000 ) >> 4 ) + ( *LeftVolumeTable )[ *from ]; to->left = HarshClipTable16[ 4 * 256 * 16 + samp ];// << 4; // to->left = ( length & 127 ) << 8; samp = ( ( to->right - 0x8000 ) >> 4 ) + ( *RightVolumeTable )[ *from ]; to->right = HarshClipTable16[ 4 * 256 * 16 + samp ];// << 4; // to->right = ( length & 127 ) << 8; to++; position += rate; length--; } voice->position = position; } /*--------------------------------------------------------------------- Function: MV_PrepareBuffer Initializes the current buffer and mixes the currently active voices. ---------------------------------------------------------------------*/ void MV_PrepareBuffer ( int page ) { VoiceNode *voice; // Initialize buffer ClearBuffer_DW( MV_MixBuffer[ page ], MV_Silence, MV_BufferSize >> 2 ); voice = VoiceList.start; while( voice != NULL ) { switch( MV_MixMode ) { case MONO_8BIT : MV_Mix8bitMono( voice, page ); break; case STEREO_8BIT : MV_Mix8bitStereo( voice, page ); break; case MONO_16BIT : MV_Mix16bitUnsignedMono( voice, page ); break; case STEREO_16BIT : MV_Mix16bitUnsignedStereo( voice, page ); break; } if ( voice->Playing ) { voice->Active[ page ] = TRUE; } else { voice->Active[ page ] = FALSE; } voice = voice->next; } } /*--------------------------------------------------------------------- Function: MV_DeleteDeadVoices Removes any voices that have finished playing. ---------------------------------------------------------------------*/ void MV_DeleteDeadVoices ( int page ) { VoiceNode *voice; VoiceNode *next; unsigned flags; flags = DisableInterrupts(); voice = VoiceList.start; while( voice != NULL ) { next = voice->next; // Is this voice done? if ( !voice->Active[ page ] ) { // Yes, move it from the play list into the free list LL_Remove( VoiceNode, &VoiceList, voice ); LL_AddToTail( VoiceNode, &VoicePool, voice ); if ( MV_CallBackFunc ) { MV_CallBackFunc( voice->callbackval ); } } voice = next; } RestoreInterrupts( flags ); } /*--------------------------------------------------------------------- Function: MV_ServiceVoc Starts playback of the waiting buffer and mixes the next one. ---------------------------------------------------------------------*/ void MV_ServiceVoc ( void ) { int ErasePage; ErasePage = MV_PlayPage; // Delete any voices that are done playing MV_DeleteDeadVoices( ErasePage ); // Set which buffer is currently being played. MV_PlayPage = MV_MixPage; // Toggle which buffer we'll mix next MV_MixPage++; if ( MV_MixPage >= MV_NumberOfBuffers ) { MV_MixPage = 0; } // Play any waiting voices MV_PrepareBuffer( MV_MixPage ); } /*--------------------------------------------------------------------- Function: MV_GetVoice Locates the voice with the specified handle. ---------------------------------------------------------------------*/ VoiceNode *MV_GetVoice ( int handle ) { VoiceNode *voice; unsigned flags; flags = DisableInterrupts(); voice = VoiceList.start; while( voice != NULL ) { if ( handle == voice->handle ) { break; } voice = voice->next; } RestoreInterrupts( flags ); if ( voice == NULL ) { MV_SetErrorCode( MV_VoiceNotFound ); } return( voice ); } /*--------------------------------------------------------------------- Function: MV_VoicePlaying Checks if the voice associated with the specified handle is playing. ---------------------------------------------------------------------*/ int MV_VoicePlaying ( int handle ) { VoiceNode *voice; if ( !MV_Installed ) { MV_SetErrorCode( MV_NotInstalled ); return( FALSE ); } voice = MV_GetVoice( handle ); if ( voice == NULL ) { return( FALSE ); } return( TRUE ); } /*--------------------------------------------------------------------- Function: MV_KillAllVoices Stops output of all currently active voices. ---------------------------------------------------------------------*/ int MV_KillAllVoices ( void ) { if ( !MV_Installed ) { MV_SetErrorCode( MV_NotInstalled ); return( MV_Error ); } // Remove all the voices from the list while( VoiceList.start != NULL ) { MV_Kill( VoiceList.start->handle ); } return( MV_Ok ); } /*--------------------------------------------------------------------- Function: MV_Kill Stops output of the voice associated with the specified handle. ---------------------------------------------------------------------*/ int MV_Kill ( int handle ) { VoiceNode *voice; unsigned flags; unsigned long callbackval; if ( !MV_Installed ) { MV_SetErrorCode( MV_NotInstalled ); return( MV_Error ); } flags = DisableInterrupts(); voice = MV_GetVoice( handle ); if ( voice == NULL ) { RestoreInterrupts( flags ); MV_SetErrorCode( MV_VoiceNotFound ); return( MV_Error ); } callbackval = voice->callbackval; // move the voice from the play list to the free list LL_Remove( VoiceNode, &VoiceList, voice ); LL_AddToTail( VoiceNode, &VoicePool, voice ); RestoreInterrupts( flags ); if ( MV_CallBackFunc ) { MV_CallBackFunc( callbackval ); } MV_SetErrorCode( MV_Ok ); return( MV_Ok ); } /*--------------------------------------------------------------------- Function: MV_VoicesPlaying Determines the number of currently active voices. ---------------------------------------------------------------------*/ int MV_VoicesPlaying ( void ) { VoiceNode *voice; int NumVoices = 0; unsigned flags; if ( !MV_Installed ) { MV_SetErrorCode( MV_NotInstalled ); return( 0 ); } flags = DisableInterrupts(); voice = VoiceList.start; while( voice != NULL ) { NumVoices++; voice = voice->next; } RestoreInterrupts( flags ); return( NumVoices ); } /*--------------------------------------------------------------------- Function: MV_AllocVoice Retrieve an inactive or lower priority voice for output. ---------------------------------------------------------------------*/ VoiceNode *MV_AllocVoice ( int priority ) { VoiceNode *voice; VoiceNode *node; unsigned flags; flags = DisableInterrupts(); // Check if we have any free voices if ( VoicePool.start == NULL ) { // check if we have a higher priority than a voice that is playing. node = VoiceList.start; voice = node; while( node != NULL ) { if ( node->priority < voice->priority ) { voice = node; } node = node->next; } if ( priority >= voice->priority ) { MV_Kill( voice->handle ); } } // Check if any voices are in the voice pool if ( VoicePool.start == NULL ) { // No free voices RestoreInterrupts( flags ); return( NULL ); } voice = VoicePool.start; LL_Remove( VoiceNode, &VoicePool, voice ); RestoreInterrupts( flags ); // Find a free voice handle do { MV_VoiceHandle++; if ( MV_VoiceHandle < MV_MinVoiceHandle ) { MV_VoiceHandle = MV_MinVoiceHandle; } } while( MV_VoicePlaying( MV_VoiceHandle ) ); voice->handle = MV_VoiceHandle; return( voice ); } /*--------------------------------------------------------------------- Function: MV_SetPitch Sets the pitch for the voice associated with the specified handle. ---------------------------------------------------------------------*/ int MV_SetPitch ( int handle, int pitchoffset ) { VoiceNode *voice; if ( !MV_Installed ) { MV_SetErrorCode( MV_NotInstalled ); return( MV_Error ); } voice = MV_GetVoice( handle ); if ( voice == NULL ) { MV_SetErrorCode( MV_VoiceNotFound ); return( MV_Error ); } voice->PitchScale = PITCH_GetScale( pitchoffset ); voice->RateScale = ( voice->SamplingRate * voice->PitchScale ) / MV_MixRate; MV_SetErrorCode( MV_Ok ); return( MV_Ok ); } /*--------------------------------------------------------------------- Function: MV_SetPan Sets the stereo and mono volume level of the voice associated with the specified handle. ---------------------------------------------------------------------*/ int MV_SetPan ( int handle, int vol, int left, int right ) { VoiceNode *voice; if ( !MV_Installed ) { MV_SetErrorCode( MV_NotInstalled ); return( MV_Error ); } voice = MV_GetVoice( handle ); if ( voice == NULL ) { MV_SetErrorCode( MV_VoiceNotFound ); return( MV_Warning ); } voice->Volume = MIX_VOLUME( vol ); if ( MV_SwapLeftRight ) { // SBPro uses reversed panning voice->LeftVolume = MIX_VOLUME( right ); voice->RightVolume = MIX_VOLUME( left ); } else { voice->LeftVolume = MIX_VOLUME( left ); voice->RightVolume = MIX_VOLUME( right ); } MV_SetErrorCode( MV_Ok ); return( MV_Ok ); } /*--------------------------------------------------------------------- Function: MV_Pan3D Set the angle and distance from the listener of the voice associated with the specified handle. ---------------------------------------------------------------------*/ int MV_Pan3D ( int handle, int angle, int distance ) { int left; int right; int mid; int volume; int status; if ( distance < 0 ) { distance = -distance; angle += MV_NumPanPositions / 2; } volume = MIX_VOLUME( distance ); // Ensure angle is within 0 - 31 angle &= MV_MaxPanPosition; left = MV_PanTable[ angle ][ volume ].left; right = MV_PanTable[ angle ][ volume ].right; mid = max( 0, 255 - distance ); status = MV_SetPan( handle, mid, left, right ); return( status ); } /*--------------------------------------------------------------------- Function: MV_SetMixMode Prepares Multivoc to play stereo of mono digitized sounds. ---------------------------------------------------------------------*/ int MV_SetMixMode ( int mode ) { if ( !MV_Installed ) { MV_SetErrorCode( MV_NotInstalled ); return( MV_Error ); } switch( MV_SoundCard ) { case SoundBlaster : case Awe32 : MV_MixMode = BLASTER_SetMixMode( mode ); break; case ProAudioSpectrum : case SoundMan16 : MV_MixMode = PAS_SetMixMode( mode ); break; #ifndef SOUNDSOURCE_OFF case SoundSource : case TandySoundSource : MV_MixMode = SS_SetMixMode( mode ); break; #endif } MV_SampleSize = sizeof( MONO8 ); MV_Silence = SILENCE_8BIT; switch( MV_MixMode ) { case MONO_8BIT : MV_SampleSize = sizeof( MONO8 ); MV_Silence = SILENCE_8BIT; break; case STEREO_8BIT : MV_SampleSize = sizeof( STEREO8 ); MV_Silence = SILENCE_8BIT; break; case MONO_16BIT : MV_SampleSize = sizeof( MONO16 ); // if ( ( MV_SoundCard == ProAudioSpectrum ) || // ( MV_SoundCard == SoundMan16 ) ) // { // MV_Silence = SILENCE_16BIT_PAS; // } // else // { MV_Silence = SILENCE_16BIT; // } break; case STEREO_16BIT : MV_SampleSize = sizeof( STEREO16 ); // if ( ( MV_SoundCard == ProAudioSpectrum ) || // ( MV_SoundCard == SoundMan16 ) ) // { // MV_Silence = SILENCE_16BIT_PAS; // } // else // { MV_Silence = SILENCE_16BIT; // } break; } MV_BufferSize = MixBufferSize * MV_SampleSize; MV_NumberOfBuffers = TotalBufferSize / MV_BufferSize; return( MV_Ok ); } /*--------------------------------------------------------------------- Function: MV_StartPlayback Starts the sound playback engine. ---------------------------------------------------------------------*/ int MV_StartPlayback ( void ) { int status; // Initialize the buffers ClearBuffer_DW( MV_MixBuffer[ 0 ], MV_Silence, TotalBufferSize >> 2 ); // Set the mix buffer variables MV_PlayPage = 0; MV_MixPage = 1; // Start playback switch( MV_SoundCard ) { case SoundBlaster : case Awe32 : status = BLASTER_BeginBufferedPlayback( MV_MixBuffer[ 0 ], TotalBufferSize, MV_NumberOfBuffers, MV_RequestedMixRate, MV_MixMode, MV_ServiceVoc ); if ( status != BLASTER_Ok ) { MV_SetErrorCode( MV_BlasterError ); return( MV_Error ); } MV_MixRate = BLASTER_GetPlaybackRate(); break; case ProAudioSpectrum : case SoundMan16 : status = PAS_BeginBufferedPlayback( MV_MixBuffer[ 0 ], TotalBufferSize, MV_NumberOfBuffers, MV_RequestedMixRate, MV_MixMode, MV_ServiceVoc ); if ( status != PAS_Ok ) { MV_SetErrorCode( MV_PasError ); return( MV_Error ); } MV_MixRate = PAS_GetPlaybackRate(); break; #ifndef SOUNDSOURCE_OFF case SoundSource : case TandySoundSource : SS_BeginBufferedPlayback( MV_MixBuffer[ 0 ], TotalBufferSize, MV_NumberOfBuffers, MV_ServiceVoc ); MV_MixRate = SS_SampleRate; break; #endif } return( MV_Ok ); } /*--------------------------------------------------------------------- Function: MV_StopPlayback Stops the sound playback engine. ---------------------------------------------------------------------*/ void MV_StopPlayback ( void ) { // Stop sound playback switch( MV_SoundCard ) { case SoundBlaster : case Awe32 : BLASTER_StopPlayback(); break; case ProAudioSpectrum : case SoundMan16 : PAS_StopPlayback(); break; #ifndef SOUNDSOURCE_OFF case SoundSource : case TandySoundSource : SS_StopPlayback(); break; #endif } } /*--------------------------------------------------------------------- Function: MV_StartRecording Starts the sound recording engine. ---------------------------------------------------------------------*/ int MV_StartRecording ( int MixRate, void ( *function )( char *ptr, int length ) ) { return( MV_Ok ); } /*--------------------------------------------------------------------- Function: MV_StopRecord Stops the sound record engine. ---------------------------------------------------------------------*/ void MV_StopRecord ( void ) { } /*--------------------------------------------------------------------- Function: MV_StartDemandFeedPlayback Plays a digitized sound from a user controlled buffering system. ---------------------------------------------------------------------*/ int MV_StartDemandFeedPlayback ( int MixRate, int vol, int left, int right, void ( *function )( char **ptr, unsigned long *length ) ) { return( MV_Ok ); } /*--------------------------------------------------------------------- Function: MV_GetNextVOCBlock Interperate the information of a VOC format sound file. ---------------------------------------------------------------------*/ void MV_GetNextVOCBlock ( VoiceNode *voice ) { unsigned char *ptr; int blocktype; int lastblocktype; unsigned long blocklength; unsigned long samplespeed; unsigned int tc; int packtype; int voicemode; int done; unsigned BitsPerSample; unsigned Channels; unsigned Format; if ( voice->BlockLength > 0 ) { voice->sound += 0x8000; voice->position -= 0x80000000; voice->length = min( voice->BlockLength, 0x8000 ); voice->BlockLength -= voice->length; voice->length <<= 16; return; } ptr = ( unsigned char * )voice->NextBlock; voice->Playing = TRUE; done = FALSE; while( !done ) { blocktype = ( int )*ptr; blocklength = ( *( unsigned long * )( ptr + 1 ) ) & 0x00ffffff; ptr += 4; voicemode = 0; lastblocktype = 0; switch( blocktype ) { case 0 : // End of data voice->Playing = FALSE; done = TRUE; break; case 1 : // Sound data block if ( lastblocktype != 8 ) { tc = ( unsigned int )*ptr; packtype = *( ptr + 1 ); } ptr += 2; blocklength -= 2; samplespeed = 256000000L / ( 65536 - ( tc << 8 ) ); // Skip packed or stereo data if ( ( packtype != 0 ) || ( voicemode != 0 ) ) { ptr += blocklength; } else { done = TRUE; } break; case 2 : // Sound continuation block samplespeed = voice->SamplingRate; done = TRUE; break; case 3 : // Silence // Not implimented. ptr += blocklength; break; case 4 : // Marker // Not implimented. ptr += blocklength; break; case 5 : // ASCII string // Not implimented. ptr += blocklength; break; case 6 : // Repeat begin voice->LoopCount = *( unsigned short * )ptr; ptr += blocklength; voice->LoopStart = ptr; break; case 7 : // Repeat end ptr += blocklength; if ( lastblocktype == 6 ) { voice->LoopCount = 0; } else { if ( ( voice->LoopCount > 0 ) && ( voice->LoopStart != NULL ) ) { ptr = voice->LoopStart; if ( voice->LoopCount < 0xffff ) { voice->LoopCount--; if ( voice->LoopCount == 0 ) { voice->LoopStart = NULL; } } } } break; case 8 : // Extended block tc = *( unsigned short * )ptr; packtype = *( ptr + 2 ); voicemode = *( ptr + 3 ); ptr += blocklength; break; case 9 : // New sound data block samplespeed = *( unsigned long * )ptr; BitsPerSample = ( unsigned )*( ptr + 4 ); Channels = ( unsigned )*( ptr + 5 ); Format = ( unsigned )*( unsigned short * )( ptr + 6 ); if ( ( BitsPerSample == 8 ) && ( Channels == 1 ) && ( Format == 0 ) ) { ptr += 12; done = TRUE; } else { ptr += blocklength; } break; default : // Unknown data. Probably not a VOC file. voice->Playing = FALSE; done = TRUE; break; } lastblocktype = blocktype; } if ( voice->Playing ) { voice->NextBlock = ptr + blocklength; voice->sound = ptr; voice->SamplingRate = samplespeed; voice->RateScale = ( voice->SamplingRate * voice->PitchScale ) / MV_MixRate; voice->position = 0; voice->length = min( blocklength, 0x8000 ); voice->BlockLength = blocklength - voice->length; voice->length <<= 16; } } /*--------------------------------------------------------------------- Function: MV_Play Begin playback of sound data with the given sound levels and priority. ---------------------------------------------------------------------*/ int MV_Play ( char *ptr, int pitchoffset, int vol, int left, int right, int priority, unsigned long callbackval ) { VoiceNode *voice; int buffer; unsigned flags; if ( !MV_Installed ) { MV_SetErrorCode( MV_NotInstalled ); return( MV_Error ); } // Request a voice from the voice pool voice = MV_AllocVoice( priority ); if ( voice == NULL ) { MV_SetErrorCode( MV_NoVoices ); return( MV_Error ); } voice->NextBlock = ptr + *( unsigned short int * )( ptr + 0x14 ); voice->LoopStart = NULL; voice->LoopCount = 0; voice->BlockLength = 0; voice->PitchScale = PITCH_GetScale( pitchoffset ); MV_GetNextVOCBlock( voice ); voice->next = NULL; voice->prev = NULL; for( buffer = 0; buffer < NumberOfBuffers; buffer++ ) { voice->Active[ buffer ] = TRUE; } voice->Volume = MIX_VOLUME( vol ); if ( MV_SwapLeftRight ) { // SBPro uses reversed panning voice->LeftVolume = MIX_VOLUME( right ); voice->RightVolume = MIX_VOLUME( left ); } else { voice->LeftVolume = MIX_VOLUME( left ); voice->RightVolume = MIX_VOLUME( right ); } voice->priority = priority; voice->callbackval = callbackval; flags = DisableInterrupts(); LL_AddToTail( VoiceNode, &VoiceList, voice ); RestoreInterrupts( flags ); MV_SetErrorCode( MV_Ok ); return( voice->handle ); } /*--------------------------------------------------------------------- Function: MV_Play3D Begin playback of sound data at specified angle and distance from listener. ---------------------------------------------------------------------*/ int MV_Play3D ( char *ptr, int pitchoffset, int angle, int distance, int priority, unsigned long callbackval ) { int left; int right; int mid; int volume; int status; if ( !MV_Installed ) { MV_SetErrorCode( MV_NotInstalled ); return( MV_Error ); } if ( distance < 0 ) { distance = -distance; angle += MV_NumPanPositions / 2; } volume = MIX_VOLUME( distance ); // Ensure angle is within 0 - 31 angle &= MV_MaxPanPosition; left = MV_PanTable[ angle ][ volume ].left; right = MV_PanTable[ angle ][ volume ].right; mid = max( 0, 255 - distance ); status = MV_Play( ptr, pitchoffset, mid, left, right, priority, callbackval ); return( status ); } /*--------------------------------------------------------------------- Function: MV_LockEnd Used for determining the length of the functions to lock in memory. ---------------------------------------------------------------------*/ static void MV_LockEnd ( void ) { } /*--------------------------------------------------------------------- Function: MV_CalcVolume Create the table used to convert sound data to a specific volume level. ---------------------------------------------------------------------*/ void MV_CalcVolume ( int MaxLevel ) { int volume; int val; int level; int rate; int i; unsigned flags; flags = DisableInterrupts(); // For each volume level, create a translation table with the // appropriate volume calculated. rate = ( MaxLevel << 16 ) / MV_MaxVolume; level = 0; if ( MV_MixMode & SIXTEEN_BIT ) { for( volume = 0; volume <= MV_MaxVolume; volume++ ) { for( i = 0; i < 256; i++ ) { val = i - 128; val *= level; // val /= MV_MaxVoices; // val >>= 16; val >>= 20; ( *MV_16BitVolumeTable )[ volume ][ i ] = val; } level += rate; } /* // if ( ( MV_SoundCard == ProAudioSpectrum ) || // ( MV_SoundCard == SoundMan16 ) ) { for( volume = 0; volume < 4 * 256 * 16; volume++ ) { HarshClipTable16s[ volume ] = -0x8000; HarshClipTable16s[ volume + 4 * 256 * 16 ] = 0xffff - 0x8000; } for( volume = 0; volume < 16 * 256; volume++ ) { HarshClipTable16s[ volume + 4 * 256 * 16 - 0x800 ] = volume * 16 - 0x8000; } } else { */ for( volume = 0; volume < 4 * 256 * 16; volume++ ) { HarshClipTable16[ volume ] = 0; HarshClipTable16[ volume + 4 * 256 * 16 ] = 0xffff; } for( volume = 0; volume < 16 * 256; volume++ ) { HarshClipTable16[ volume + 4 * 256 * 16 - 0x800 ] = volume * 16; } // } } else { for( volume = 0; volume <= MV_MaxVolume; volume++ ) { for( i = 0; i < 256; i++ ) { val = i - 128; val *= level; // DEBUG // val /= MV_MaxVoices; val >>= 24; ( *MV_8BitVolumeTable )[ volume ][ i ] = val; } level += rate; } for( volume = 0; volume < 4 * 256; volume++ ) { HarshClipTable[ volume ] = 0; HarshClipTable[ volume + 4 * 256 ] = 255; } for( volume = 0; volume < 256; volume++ ) { HarshClipTable[ volume + 4 * 256 - 128 ] = volume; } } RestoreInterrupts( flags ); } /*--------------------------------------------------------------------- Function: MV_CalcPanTable Create the table used to determine the stereo volume level of a sound located at a specific angle and distance from the listener. ---------------------------------------------------------------------*/ void MV_CalcPanTable ( void ) { int level; int angle; int distance; int HalfAngle; int ramp; HalfAngle = ( MV_NumPanPositions / 2 ); for( distance = 0; distance <= MV_MaxVolume; distance++ ) { level = ( 255 * ( MV_MaxVolume - distance ) ) / MV_MaxVolume; for( angle = 0; angle <= HalfAngle / 2; angle++ ) { ramp = level - ( ( level * angle ) / ( MV_NumPanPositions / 4 ) ); MV_PanTable[ angle ][ distance ].left = ramp; MV_PanTable[ HalfAngle - angle ][ distance ].left = ramp; MV_PanTable[ HalfAngle + angle ][ distance ].left = level; MV_PanTable[ MV_MaxPanPosition - angle ][ distance ].left = level; MV_PanTable[ angle ][ distance ].right = level; MV_PanTable[ HalfAngle - angle ][ distance ].right = level; MV_PanTable[ HalfAngle + angle ][ distance ].right = ramp; MV_PanTable[ MV_MaxPanPosition - angle ][ distance ].right = ramp; } } } /*--------------------------------------------------------------------- Function: MV_SetVolume Sets the volume of digitized sound playback. ---------------------------------------------------------------------*/ void MV_SetVolume ( int volume ) { int maxlevel; volume = max( 0, volume ); volume = min( volume, MV_MaxTotalVolume ); MV_TotalVolume = volume; maxlevel = ( MV_MaxLevel * volume ) / MV_MaxTotalVolume; //printf( "maxlevel = %d\n", maxlevel ); // Calculate volume table MV_CalcVolume( maxlevel ); } /*--------------------------------------------------------------------- Function: MV_GetVolume Returns the volume of digitized sound playback. ---------------------------------------------------------------------*/ int MV_GetVolume ( void ) { return( MV_TotalVolume ); } /*--------------------------------------------------------------------- Function: MV_SetCallBack Set the function to call when a voice stops. ---------------------------------------------------------------------*/ void MV_SetCallBack ( void ( *function )( unsigned long ) ) { MV_CallBackFunc = function; } /*--------------------------------------------------------------------- Function: MV_Init Perform the initialization of variables and memory used by Multivoc. ---------------------------------------------------------------------*/ int MV_Init ( int soundcard, int MixRate, int Voices, int MixMode ) { char *ptr; int status; int buffer; int index; if ( MV_Installed ) { MV_Shutdown(); } MV_SetErrorCode( MV_Ok ); status = MV_LockMemory(); if ( status != MV_Ok ) { return( status ); } status = USRHOOKS_GetMem( &MV_Voices, Voices * sizeof( VoiceNode ) ); if ( status != USRHOOKS_Ok ) { MV_UnlockMemory(); MV_SetErrorCode( MV_NoMem ); return( MV_Error ); } if ( MixMode & SIXTEEN_BIT ) { status = USRHOOKS_GetMem( &HarshClipTable, sizeof( HARSH_CLIP_TABLE_16 ) ); HarshClipTable16 = ( unsigned short * )HarshClipTable; HarshClipTable16s = ( short * )HarshClipTable; } else { status = USRHOOKS_GetMem( &HarshClipTable, sizeof( HARSH_CLIP_TABLE_8 ) ); HarshClipTable16 = ( unsigned short * )HarshClipTable; HarshClipTable16s = ( short * )HarshClipTable; } if ( status != USRHOOKS_Ok ) { USRHOOKS_FreeMem( MV_Voices ); MV_SetErrorCode( MV_NoMem ); MV_UnlockMemory(); return( MV_Error ); } // Set number of voices before calculating volume table MV_MaxVoices = Voices; VoiceList.start = NULL; VoiceList.end = NULL; VoicePool.start = NULL; VoicePool.end = NULL; for( index = 0; index < Voices; index++ ) { LL_AddToTail( VoiceNode, &VoicePool, &MV_Voices[ index ] ); } // Allocate mix buffer within 1st megabyte status = DPMI_GetDOSMemory( &ptr, &MV_BufferDescriptor, 2 * TotalBufferSize ); if ( status ) { MV_SetErrorCode( MV_NoMem ); MV_UnlockMemory(); return( MV_Error ); } MV_SwapLeftRight = FALSE; // Initialize the sound card switch( soundcard ) { case SoundBlaster : case Awe32 : status = BLASTER_Init(); if ( status != BLASTER_Ok ) { MV_SetErrorCode( MV_BlasterError ); } if ( ( BLASTER_Config.Type == SBPro ) || ( BLASTER_Config.Type == SBPro2 ) ) { MV_SwapLeftRight = TRUE; } break; case ProAudioSpectrum : case SoundMan16 : status = PAS_Init(); if ( status != PAS_Ok ) { MV_SetErrorCode( MV_PasError ); } break; #ifndef SOUNDSOURCE_OFF case SoundSource : case TandySoundSource : status = SS_Init( soundcard ); if ( status != SS_Ok ) { MV_SetErrorCode( MV_SoundSourceError ); } break; #endif default : MV_SetErrorCode( MV_UnsupportedCard ); break; } if ( MV_ErrorCode != MV_Ok ) { DPMI_FreeDOSMemory( MV_BufferDescriptor ); MV_UnlockMemory(); return( MV_Error ); } MV_SoundCard = soundcard; MV_Installed = TRUE; MV_CallBackFunc = NULL; // Set the sampling rate MV_RequestedMixRate = MixRate; // Set Mixer to play stereo digitized sound MV_SetMixMode( MixMode ); // Make sure we don't cross a physical page if ( ( ( unsigned long )ptr & 0xffff ) + TotalBufferSize > 0x10000 ) { ptr = ( char * )( ( ( unsigned long )ptr & 0xff0000 ) + 0x10000 ); } for( buffer = 0; buffer < MV_NumberOfBuffers; buffer++ ) { MV_MixBuffer[ buffer ] = ptr; ptr += MV_BufferSize; } // Calculate pan table MV_CalcPanTable(); MV_SetVolume( MV_MaxTotalVolume ); // Init pitch scaler PITCH_Init(); // Start the playback engine status = MV_StartPlayback(); if ( status != MV_Ok ) { // Preserve error code while we shutdown. status = MV_ErrorCode; MV_Shutdown(); MV_SetErrorCode( status ); return( MV_Error ); } return( MV_Ok ); } /*--------------------------------------------------------------------- Function: MV_Shutdown Restore any resources allocated by Multivoc back to the system. ---------------------------------------------------------------------*/ int MV_Shutdown ( void ) { int buffer; unsigned flags; if ( !MV_Installed ) { MV_SetErrorCode( MV_NotInstalled ); return( MV_Error ); } flags = DisableInterrupts(); MV_KillAllVoices(); MV_Installed = FALSE; // Stop the sound playback engine MV_StopPlayback(); // Shutdown the sound card switch( MV_SoundCard ) { case SoundBlaster : case Awe32 : BLASTER_Shutdown(); break; case ProAudioSpectrum : case SoundMan16 : PAS_Shutdown(); break; #ifndef SOUNDSOURCE_OFF case SoundSource : case TandySoundSource : SS_Shutdown(); break; #endif } RestoreInterrupts( flags ); // Free any voices we allocated USRHOOKS_FreeMem( MV_Voices ); MV_Voices = NULL; VoiceList.start = NULL; VoiceList.end = NULL; VoicePool.start = NULL; VoicePool.end = NULL; MV_MaxVoices = 1; // Release the descriptor from our mix buffer DPMI_FreeDOSMemory( MV_BufferDescriptor ); for( buffer = 0; buffer < NumberOfBuffers; buffer++ ) { MV_MixBuffer[ buffer ] = NULL; } return( MV_Ok ); } /*--------------------------------------------------------------------- Function: MV_UnlockMemory Unlocks all neccessary data. ---------------------------------------------------------------------*/ void MV_UnlockMemory ( void ) { PITCH_UnlockMemory(); DPMI_UnlockMemoryRegion( MV_LockStart, MV_LockEnd ); DPMI_Unlock( MV_VolumeTable ); DPMI_Unlock( MV_8BitVolumeTable ); DPMI_Unlock( MV_16BitVolumeTable ); DPMI_Unlock( MV_PanTable ); DPMI_Unlock( MV_Installed ); DPMI_Unlock( MV_SoundCard ); DPMI_Unlock( MV_TotalVolume ); DPMI_Unlock( MV_MaxVoices ); DPMI_Unlock( MV_BufferSize ); DPMI_Unlock( MV_SampleSize ); DPMI_Unlock( MV_NumberOfBuffers ); DPMI_Unlock( MV_MixMode ); DPMI_Unlock( MV_Silence ); DPMI_Unlock( MV_SwapLeftRight ); DPMI_Unlock( MV_RequestedMixRate ); DPMI_Unlock( MV_MixRate ); DPMI_Unlock( MV_BufferDescriptor ); DPMI_Unlock( MV_MixBuffer ); DPMI_Unlock( MV_Voices ); DPMI_Unlock( VoiceList ); DPMI_Unlock( VoicePool ); DPMI_Unlock( MV_MixPage ); DPMI_Unlock( MV_PlayPage ); DPMI_Unlock( MV_VoiceHandle ); DPMI_Unlock( MV_CallBackFunc ); DPMI_Unlock( HarshClipTable ); DPMI_Unlock( HarshClipTable16 ); DPMI_Unlock( HarshClipTable16s ); DPMI_Unlock( MV_ErrorCode ); } /*--------------------------------------------------------------------- Function: MV_LockMemory Locks all neccessary data. ---------------------------------------------------------------------*/ int MV_LockMemory ( void ) { int status; int pitchstatus; status = DPMI_LockMemoryRegion( MV_LockStart, MV_LockEnd ); status |= DPMI_Lock( MV_VolumeTable ); status |= DPMI_Lock( MV_8BitVolumeTable ); status |= DPMI_Lock( MV_16BitVolumeTable ); status |= DPMI_Lock( MV_PanTable ); status |= DPMI_Lock( MV_Installed ); status |= DPMI_Lock( MV_SoundCard ); status |= DPMI_Lock( MV_TotalVolume ); status |= DPMI_Lock( MV_MaxVoices ); status |= DPMI_Lock( MV_BufferSize ); status |= DPMI_Lock( MV_SampleSize ); status |= DPMI_Lock( MV_NumberOfBuffers ); status |= DPMI_Lock( MV_MixMode ); status |= DPMI_Lock( MV_Silence ); status |= DPMI_Lock( MV_SwapLeftRight ); status |= DPMI_Lock( MV_RequestedMixRate ); status |= DPMI_Lock( MV_MixRate ); status |= DPMI_Lock( MV_BufferDescriptor ); status |= DPMI_Lock( MV_MixBuffer ); status |= DPMI_Lock( MV_Voices ); status |= DPMI_Lock( VoiceList ); status |= DPMI_Lock( VoicePool ); status |= DPMI_Lock( MV_MixPage ); status |= DPMI_Lock( MV_PlayPage ); status |= DPMI_Lock( MV_VoiceHandle ); status |= DPMI_Lock( MV_CallBackFunc ); status |= DPMI_Lock( HarshClipTable ); status |= DPMI_Lock( HarshClipTable16 ); status |= DPMI_Lock( HarshClipTable16s ); status |= DPMI_Lock( MV_ErrorCode ); pitchstatus = PITCH_LockMemory(); if ( ( pitchstatus != PITCH_Ok ) || ( status != DPMI_Ok ) ) { MV_UnlockMemory(); MV_SetErrorCode( MV_DPMI_Error ); return( MV_Error ); } return( MV_Ok ); }