ref: 7cd40bc81a097ae8b60d409dbad51965d92e4b9f
dir: /Game/src/audiolib/multivoc.c/
/* 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 <stdio.h> #include <stdlib.h> #include <string.h> #include "dpmi.h" #include "usrhooks.h" #include "linklist.h" #include "sndcards.h" #include "dsl.h" #include "pitch.h" #include "multivoc.h" #include "_multivc.h" #include "../sounddebugdefs.h" #define RoundFixed( fixedval, bits ) \ ( \ ( \ (fixedval) + ( 1 << ( (bits) - 1 ) )\ ) >> (bits) \ ) // #define IS_QUIET( ptr ) ( ( void * )( ptr ) == ( void * )&MV_VolumeTable[ 0 ] ) #define IS_QUIET( vol ) ( ( vol ) == 0 ) static int MV_ReverbLevel; int MV_ReverbDelay; static int MV_ReverbTable = -1; //static signed short MV_VolumeTable[ MV_MaxVolume + 1 ][ 256 ]; //static signed short MV_VolumeTable[ 63 + 1 ][ 256 ]; //static Pan MV_PanTable[ MV_NumPanPositions ][ MV_MaxVolume + 1 ]; static Pan MV_PanTable[ MV_NumPanPositions ][ 63 + 1 ]; static int MV_Installed = FALSE; static int MV_SoundCard = SC_Unknown; static int MV_TotalVolume = MV_MaxTotalVolume; static int MV_MaxVoices = 1; int MV_BufferSize = MixBufferSize; static int MV_BufferLength; static int MV_NumberOfBuffers = NumberOfBuffers; static int MV_MixMode = MONO_8BIT; int MV_Channels = 1; static int MV_Bits = 8; static int MV_Silence = SILENCE_8BIT; static int MV_SwapLeftRight = FALSE; static int MV_RequestedMixRate; int MV_MixRate; static int MV_BuffShift; static int MV_TotalMemory; static int MV_FooMemory; static void* MV_BufferDescriptor; static int MV_BufferEmpty[ NumberOfBuffers ]; char *MV_MixBuffer[ NumberOfBuffers + 1 ]; double *MV_FooBuffer = NULL; static VoiceNode *MV_Voices = NULL; static volatile VoiceNode VoiceList; static volatile VoiceNode VoicePool; /*static*/ int MV_MixPage = 0; static int MV_VoiceHandle = MV_MinVoiceHandle; static void ( *MV_CallBackFunc )( int32_t ) = NULL; static void ( *MV_RecordFunc )( char *ptr, int length ) = NULL; static void ( *MV_MixFunction )( VoiceNode *voice); int MV_MaxVolume = 63; int *MV_GLast, *MV_GPos, *MV_GVal; // char *MV_HarshClipTable; char *MV_MixDestination; int MV_LeftVolume; int MV_RightVolume; int MV_SampleSize = 1; int MV_RightChannelOffset; unsigned long MV_MixPosition; 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.\n"; break; case MV_UnsupportedCard : ErrorString = "Selected sound card is not supported by Multivoc.\n"; break; case MV_NotInstalled : ErrorString = "Multivoc not installed.\n"; break; case MV_NoVoices : ErrorString = "No free voices available to Multivoc.\n"; break; case MV_NoMem : ErrorString = "Out of memory in Multivoc.\n"; break; case MV_VoiceNotFound : ErrorString = "No voice with matching handle found.\n"; break; case MV_DPMI_Error : ErrorString = "DPMI Error in Multivoc.\n"; break; case MV_InvalidVOCFile : ErrorString = "Invalid VOC file passed in to Multivoc.\n"; break; case MV_InvalidWAVFile : ErrorString = "Invalid WAV file passed in to Multivoc.\n"; break; case MV_InvalidMixMode : ErrorString = "Invalid mix mode request in Multivoc.\n"; break; case MV_SoundSourceFailure : ErrorString = "Sound Source playback failed.\n"; break; case MV_IrqFailure : ErrorString = "Playback failed, possibly due to an invalid or conflicting IRQ.\n"; break; case MV_DMAFailure : ErrorString = "Playback failed, possibly due to an invalid or conflicting DMA channel.\n"; break; case MV_DMA16Failure : ErrorString = "Playback failed, possibly due to an invalid or conflicting DMA channel. \n" "Make sure the 16-bit DMA channel is correct.\n"; break; case MV_NullRecordFunction : ErrorString = "Null record function passed to MV_StartRecording.\n"; break; default : ErrorString = "Unknown Multivoc error code.\n"; break; } return( ErrorString ); } /********************************************************************** Memory locked functions: **********************************************************************/ #define MV_LockStart MV_Mix /*--------------------------------------------------------------------- Function: MV_Mix Mixes the sound into the buffer. ---------------------------------------------------------------------*/ static void MV_Mix( VoiceNode *voice ) { uint8_t *start; int length; long voclength; unsigned long position; unsigned long rate; unsigned long FixedPointBufferSize; if ( ( voice->length == 0 ) && ( voice->GetSound( voice ) != KeepPlaying ) ) { return; } length = MixBufferSize; FixedPointBufferSize = voice->FixedPointBufferSize; MV_MixDestination = (char*)MV_FooBuffer; MV_LeftVolume = voice->LeftVolume; MV_RightVolume = voice->RightVolume; MV_GLast = &voice->GLast; MV_GPos = &voice->GPos; MV_GVal = (int*)&voice->GVal; if ( ( MV_Channels == 2 ) && ( IS_QUIET( MV_LeftVolume ) ) ) { MV_LeftVolume = MV_RightVolume; MV_MixDestination += 8; } // Add this voice to the mix while( length > 0 ) { start = voice->sound; rate = voice->RateScale; position = voice->position; // Check if the last sample in this buffer would be // beyond the length of the sample block if ( ( position + FixedPointBufferSize ) >= voice->length ) { if ( position < voice->length ) { voclength = ( voice->length - position + rate - 1 ) / rate; } else { voice->GetSound( voice ); return; } } else { voclength = length; } voice->mix( position, rate, start, voclength ); if ( voclength & 1 ) { MV_MixPosition += rate; voclength -= 1; } voice->position = MV_MixPosition; length -= voclength; if ( voice->position >= voice->length ) { // Get the next block of sound if ( voice->GetSound( voice ) != KeepPlaying ) { return; } if ( length > 0 ) { // Get the position of the last sample in the buffer FixedPointBufferSize = voice->RateScale * ( length - 1 ); } } } } /*--------------------------------------------------------------------- Function: MV_PlayVoice Adds a voice to the play list. ---------------------------------------------------------------------*/ void MV_PlayVoice( VoiceNode *voice ) { LL_SortedInsertion( &VoiceList, voice, prev, next, VoiceNode, priority ); ++sounddebugActiveSounds; ++sounddebugAllocateSoundCalls; } /*--------------------------------------------------------------------- Function: MV_StopVoice Removes the voice from the play list and adds it to the free list. ---------------------------------------------------------------------*/ void MV_StopVoice( VoiceNode *voice ) { VoiceNode* pPrev; VoiceNode* pNext; pPrev = voice->prev; pNext = voice->next; // move the voice from the play list to the free list LL_Remove( voice, next, prev ); LL_Add( &VoicePool, voice, next, prev ); if(pPrev == NULL) { printf("(MV_StopVoice) pPrev is NULL, this could be a problem.\n"); } if(pNext == NULL) { printf("(MV_StopVoice) pNext is NULL, this could be a problem.\n"); } --sounddebugActiveSounds; ++sounddebugDeallocateSoundCalls; } /*--------------------------------------------------------------------- Function: MV_ServiceVoc Starts playback of the waiting buffer and mixes the next one. ---------------------------------------------------------------------*/ // static int backcolor = 1; void MV_ServiceVoc ( void ) { VoiceNode *voice; VoiceNode *next; // Toggle which buffer we'll mix next MV_MixPage++; if ( MV_MixPage >= MV_NumberOfBuffers ) { MV_MixPage -= MV_NumberOfBuffers; } { ClearBuffer_DW( MV_FooBuffer, 0, sizeof(double) / 4 * MV_BufferSize / MV_SampleSize * MV_Channels); MV_BufferEmpty[ MV_MixPage ] = TRUE; } // Play any waiting voices for( voice = VoiceList.next; voice != &VoiceList; voice = next ) { // if ( ( voice < &MV_Voices[ 0 ] ) || ( voice > &MV_Voices[ 8 ] ) ) // { // SetBorderColor(backcolor++); // break; // } if(NULL == voice->GetSound) { #ifdef _DEBUG printf("MV_ServiceVoc() voice->GetSound == NULL, break;\n"); #endif // This sound is null, early out, or face a nasty crash. break; } MV_BufferEmpty[ MV_MixPage ] = FALSE; MV_MixFunction( voice ); next = voice->next; // Is this voice done? if ( !voice->Playing ) { MV_StopVoice( voice ); if ( MV_CallBackFunc ) { MV_CallBackFunc( voice->callbackval ); } } } if ( MV_ReverbLevel > 0) { if (MV_ReverbTable != -1) MV_FPReverb(MV_ReverbTable); } { char *dest; int count; dest = MV_MixBuffer[ MV_MixPage ]; count = MV_BufferSize / MV_SampleSize * MV_Channels; if ( MV_Bits == 16 ) { MV_16BitDownmix(dest, count); } else { MV_8BitDownmix(dest, count); } } } /*--------------------------------------------------------------------- Function: MV_GetNextVOCBlock Interperate the information of a VOC format sound file. ---------------------------------------------------------------------*/ playbackstatus MV_GetNextVOCBlock ( VoiceNode *voice ) { unsigned char *ptr; int blocktype; int lastblocktype; uint32_t blocklength; unsigned long samplespeed; unsigned int tc; int packtype; int voicemode; int done; unsigned BitsPerSample; unsigned Channels; unsigned Format; if ( voice->BlockLength > 0 ) { voice->position -= voice->length; voice->sound += voice->length >> 16; if ( voice->bits == 16 ) { voice->sound += voice->length >> 16; } voice->length = min( voice->BlockLength, 0x8000 ); voice->BlockLength -= voice->length; voice->length <<= 16; return( KeepPlaying ); } if ( ( voice->length > 0 ) && ( voice->LoopEnd != NULL ) && ( voice->LoopStart != NULL ) ) { voice->BlockLength = voice->LoopSize; voice->sound = voice->LoopStart; voice->position = 0; voice->length = min( voice->BlockLength, 0x8000 ); voice->BlockLength -= voice->length; voice->length <<= 16; return( KeepPlaying ); } ptr = ( unsigned char * )voice->NextBlock; voice->Playing = TRUE; voicemode = 0; lastblocktype = 0; packtype = 0; done = FALSE; while( !done ) { // Stop playing if we get a NULL pointer if ( ptr == NULL ) { voice->Playing = FALSE; done = TRUE; break; } blocktype = ( int )*ptr; blocklength = ( *( unsigned long * )( ptr + 1 ) ) & 0x00ffffff; ptr += 4; switch( blocktype ) { case 0 : // End of data if ( ( voice->LoopStart == NULL ) || ( voice->LoopStart >= ( ptr - 4 ) ) ) { voice->Playing = FALSE; done = TRUE; } else { voice->BlockLength = ( ptr - 4 ) - (unsigned char *)voice->LoopStart; voice->sound = voice->LoopStart; voice->position = 0; voice->length = min( voice->BlockLength, 0x8000 ); voice->BlockLength -= voice->length; voice->length <<= 16; return( KeepPlaying ); } break; case 1 : // Sound data block voice->bits = 8; if ( lastblocktype != 8 ) { tc = ( unsigned int )*ptr << 8; packtype = *( ptr + 1 ); } ptr += 2; blocklength -= 2; samplespeed = 256000000L / ( 65536 - tc ); // Skip packed or stereo data if ( ( packtype != 0 ) || ( voicemode != 0 ) ) { ptr += blocklength; } else { done = TRUE; } voicemode = 0; 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 if ( voice->LoopEnd == NULL ) { voice->LoopCount = *( uint16_t * )ptr; voice->LoopStart = ptr + blocklength; } ptr += blocklength; 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 voice->bits = 8; tc = *( uint16_t * )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 )*( uint16_t * )( ptr + 6 ); if ( ( BitsPerSample == 8 ) && ( Channels == 1 ) && ( Format == VOC_8BIT ) ) { ptr += 12; blocklength -= 12; voice->bits = 8; done = TRUE; } else if ( ( BitsPerSample == 16 ) && ( Channels == 1 ) && ( Format == VOC_16BIT ) ) { ptr += 12; blocklength -= 12; voice->bits = 16; 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; // Multiply by MixBufferSize - 1 voice->FixedPointBufferSize = ( voice->RateScale * MixBufferSize ) - voice->RateScale; if ( voice->LoopEnd != NULL ) { if ( blocklength > ( unsigned long )voice->LoopEnd ) { blocklength = ( unsigned long )voice->LoopEnd; } else { voice->LoopEnd = ( char * )blocklength; } voice->LoopStart = voice->sound + ( unsigned long )voice->LoopStart; voice->LoopEnd = voice->sound + ( unsigned long )voice->LoopEnd; voice->LoopSize = voice->LoopEnd - voice->LoopStart; } if ( voice->bits == 16 ) { blocklength /= 2; } voice->position = 0; voice->length = min( blocklength, 0x8000 ); voice->BlockLength = blocklength - voice->length; voice->length <<= 16; MV_SetVoiceMixMode( voice ); return( KeepPlaying ); } return( NoMoreData ); } /*--------------------------------------------------------------------- Function: MV_GetNextDemandFeedBlock Controls playback of demand fed data. ---------------------------------------------------------------------*/ playbackstatus MV_GetNextDemandFeedBlock ( VoiceNode *voice ) { if ( voice->BlockLength > 0 ) { voice->position -= voice->length; voice->sound += voice->length >> 16; voice->length = min( voice->BlockLength, 0x8000 ); voice->BlockLength -= voice->length; voice->length <<= 16; return( KeepPlaying ); } if ( voice->DemandFeed == NULL ) { return( NoMoreData ); } voice->position = 0; ( voice->DemandFeed )( &voice->sound, &voice->BlockLength ); voice->length = min( voice->BlockLength, 0x8000 ); voice->BlockLength -= voice->length; voice->length <<= 16; if ( ( voice->length > 0 ) && ( voice->sound != NULL ) ) { return( KeepPlaying ); } return( NoMoreData ); } /*--------------------------------------------------------------------- Function: MV_GetNextRawBlock Controls playback of demand fed data. ---------------------------------------------------------------------*/ playbackstatus MV_GetNextRawBlock ( VoiceNode *voice ) { if ( voice->BlockLength <= 0 ) { if ( voice->LoopStart == NULL ) { voice->Playing = FALSE; return( NoMoreData ); } voice->BlockLength = voice->LoopSize; voice->NextBlock = voice->LoopStart; voice->length = 0; voice->position = 0; } voice->sound = voice->NextBlock; voice->position -= voice->length; voice->length = min( voice->BlockLength, 0x8000 ); voice->NextBlock += voice->length; if ( voice->bits == 16 ) { voice->NextBlock += voice->length; } voice->BlockLength -= voice->length; voice->length <<= 16; return( KeepPlaying ); } /*--------------------------------------------------------------------- Function: MV_GetNextWAVBlock Controls playback of demand fed data. ---------------------------------------------------------------------*/ playbackstatus MV_GetNextWAVBlock ( VoiceNode *voice ) { if ( voice->BlockLength <= 0 ) { if ( voice->LoopStart == NULL ) { voice->Playing = FALSE; return( NoMoreData ); } voice->BlockLength = voice->LoopSize; voice->NextBlock = voice->LoopStart; voice->length = 0; voice->position = 0; } voice->sound = voice->NextBlock; voice->position -= voice->length; voice->length = min( voice->BlockLength, 0x8000 ); voice->NextBlock += voice->length; if ( voice->bits == 16 ) { voice->NextBlock += voice->length; } voice->BlockLength -= voice->length; voice->length <<= 16; return( KeepPlaying ); } /*--------------------------------------------------------------------- Function: MV_GetVoice Locates the voice with the specified handle. ---------------------------------------------------------------------*/ VoiceNode *MV_GetVoice ( int handle ) { VoiceNode *voice; for( voice = VoiceList.next; voice != &VoiceList; voice = voice->next ) { if ( handle == voice->handle ) { break; } } if ( voice == &VoiceList ) { MV_SetErrorCode( MV_VoiceNotFound ); // SBF - should this return null? return NULL; } 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.next != &VoiceList ) { MV_Kill( VoiceList.next->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 long callbackval; if ( !MV_Installed ) { MV_SetErrorCode( MV_NotInstalled ); return( MV_Error ); } voice = MV_GetVoice( handle ); if ( voice == NULL ) { MV_SetErrorCode( MV_VoiceNotFound ); return( MV_Error ); } callbackval = voice->callbackval; MV_StopVoice( voice ); if ( MV_CallBackFunc ) { MV_CallBackFunc( callbackval ); } return( MV_Ok ); } /*--------------------------------------------------------------------- Function: MV_VoicesPlaying Determines the number of currently active voices. ---------------------------------------------------------------------*/ int MV_VoicesPlaying ( void ) { VoiceNode *voice; int NumVoices = 0; if ( !MV_Installed ) { MV_SetErrorCode( MV_NotInstalled ); return( 0 ); } for( voice = VoiceList.next; voice != &VoiceList; voice = voice->next ) { NumVoices++; } return( NumVoices ); } /*--------------------------------------------------------------------- Function: MV_AllocVoice Retrieve an inactive or lower priority voice for output. ---------------------------------------------------------------------*/ VoiceNode *MV_AllocVoice ( int priority ) { VoiceNode *voice; VoiceNode *node; // Check if we have any free voices if ( LL_Empty( &VoicePool, next, prev ) ) { // check if we have a higher priority than a voice that is playing. voice = VoiceList.next; for( node = voice->next; node != &VoiceList; node = node->next ) { if ( node->priority < voice->priority ) { voice = node; } } if ( priority >= voice->priority ) { MV_Kill( voice->handle ); } } // Check if any voices are in the voice pool if ( LL_Empty( &VoicePool, next, prev ) ) { // No free voices return( NULL ); } voice = VoicePool.next; LL_Remove( voice, next, prev ); // 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_VoiceAvailable Checks if a voice can be play at the specified priority. ---------------------------------------------------------------------*/ int MV_VoiceAvailable ( int priority ) { VoiceNode *voice; VoiceNode *node; // Check if we have any free voices if ( !LL_Empty( &VoicePool, next, prev ) ) { return( TRUE ); } // check if we have a higher priority than a voice that is playing. voice = VoiceList.next; for( node = VoiceList.next; node != &VoiceList; node = node->next ) { if ( node->priority < voice->priority ) { voice = node; } } if ( ( voice != &VoiceList ) && ( priority >= voice->priority ) ) { return( TRUE ); } return( FALSE ); } /*--------------------------------------------------------------------- Function: MV_SetVoicePitch Sets the pitch for the specified voice. ---------------------------------------------------------------------*/ void MV_SetVoicePitch ( VoiceNode *voice, unsigned long rate, int pitchoffset ) { voice->SamplingRate = rate; voice->PitchScale = PITCH_GetScale( pitchoffset ); voice->RateScale = ( rate * voice->PitchScale ) / MV_MixRate; // Multiply by MixBufferSize - 1 voice->FixedPointBufferSize = ( voice->RateScale * MixBufferSize ) - voice->RateScale; } /*--------------------------------------------------------------------- Function: MV_GetVolumeTable Returns a pointer to the volume table associated with the specified volume. ---------------------------------------------------------------------*/ #if 0 static short *MV_GetVolumeTable ( int vol ) { int volume; short *table; volume = MIX_VOLUME( vol ); table = &MV_VolumeTable[ volume ]; return( table ); } #else static int MV_GetVolumeTable ( int vol ) { int volume; volume = MIX_VOLUME( vol ); return ( volume * MV_TotalVolume ) / MV_MaxTotalVolume; } #endif /*--------------------------------------------------------------------- Function: MV_SetVoiceMixMode Selects which method should be used to mix the voice. ---------------------------------------------------------------------*/ void MV_SetVoiceMixMode ( VoiceNode *voice ) { int test; test = T_DEFAULT; if ( voice->bits == 16 ) { test |= T_16BITSOURCE; } if ( MV_Channels == 1 ) { test |= T_MONO; } else { if ( IS_QUIET( voice->RightVolume ) ) { test |= T_RIGHTQUIET; } else if ( IS_QUIET( voice->LeftVolume ) ) { test |= T_LEFTQUIET; } } switch( test ) { case T_MONO | T_16BITSOURCE : voice->mix = MV_MixFPMono16; break; case T_MONO : voice->mix = MV_MixFPMono8; break; case T_16BITSOURCE | T_LEFTQUIET : MV_LeftVolume = MV_RightVolume; voice->mix = MV_MixFPMono16; break; case T_LEFTQUIET : MV_LeftVolume = MV_RightVolume; voice->mix = MV_MixFPMono8; break; case T_16BITSOURCE | T_RIGHTQUIET : voice->mix = MV_MixFPMono16; break; case T_RIGHTQUIET : voice->mix = MV_MixFPMono8; break; case T_16BITSOURCE : voice->mix = MV_MixFPStereo16; break; case T_SIXTEENBIT_STEREO : voice->mix = MV_MixFPStereo8; break; default : voice->mix = MV_MixFPMono8; } } /*--------------------------------------------------------------------- Function: MV_SetVoiceVolume Sets the stereo and mono volume level of the voice associated with the specified handle. ---------------------------------------------------------------------*/ void MV_SetVoiceVolume ( VoiceNode *voice, int vol, int left, int right ) { if ( MV_Channels == 1 ) { left = vol; right = vol; } if ( MV_SwapLeftRight ) { // SBPro uses reversed panning voice->LeftVolume = MV_GetVolumeTable( right ); voice->RightVolume = MV_GetVolumeTable( left ); } else { voice->LeftVolume = MV_GetVolumeTable( left ); voice->RightVolume = MV_GetVolumeTable( right ); } MV_SetVoiceMixMode( voice ); } /*--------------------------------------------------------------------- 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 ); } MV_SetVoiceVolume( voice, vol, left, right ); 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_SetReverb Sets the level of reverb to add to mix. ---------------------------------------------------------------------*/ void MV_SetReverb ( int reverb ) { MV_ReverbLevel = MIX_VOLUME( reverb ); MV_ReverbTable = ( MV_ReverbLevel * MV_TotalVolume ) / MV_MaxTotalVolume;// &MV_VolumeTable[ MV_ReverbLevel ]; if (!reverb) MV_FPReverbFree(); } /*--------------------------------------------------------------------- Function: MV_GetMaxReverbDelay Returns the maximum delay time for reverb. ---------------------------------------------------------------------*/ int MV_GetMaxReverbDelay ( void ) { int maxdelay; maxdelay = 65536; //MixBufferSize * MV_NumberOfBuffers; return maxdelay; } /*--------------------------------------------------------------------- Function: MV_SetReverbDelay Sets the delay level of reverb to add to mix. ---------------------------------------------------------------------*/ void MV_SetReverbDelay ( int delay ) { int maxdelay; if (!delay) // blah, ignore return; maxdelay = MV_GetMaxReverbDelay(); MV_ReverbDelay = max( MixBufferSize, min( delay, maxdelay ) ); // MV_ReverbDelay *= MV_SampleSize; } /*--------------------------------------------------------------------- Function: MV_SetMixMode Prepares Multivoc to play stereo of mono digitized sounds. ---------------------------------------------------------------------*/ int MV_SetMixMode ( int numchannels, int samplebits ) { int mode; if ( !MV_Installed ) { MV_SetErrorCode( MV_NotInstalled ); return( MV_Error ); } mode = 0; if ( numchannels == 2 ) { mode |= STEREO; } if ( samplebits == 16 ) { mode |= SIXTEEN_BIT; } MV_MixMode = mode; MV_Channels = 1; if ( MV_MixMode & STEREO ) { MV_Channels = 2; } MV_Bits = 8; if ( MV_MixMode & SIXTEEN_BIT ) { MV_Bits = 16; } MV_BuffShift = 7 + MV_Channels; MV_SampleSize = sizeof( MONO8 ) * MV_Channels; if ( MV_Bits == 8 ) { MV_Silence = SILENCE_8BIT; } else { MV_Silence = SILENCE_16BIT; MV_BuffShift += 1; MV_SampleSize *= 2; } MV_BufferSize = MixBufferSize * MV_SampleSize; MV_NumberOfBuffers = TotalBufferSize / MV_BufferSize; MV_BufferLength = TotalBufferSize; MV_RightChannelOffset = MV_SampleSize / 2; return( MV_Ok ); } /*--------------------------------------------------------------------- Function: MV_StartPlayback Starts the sound playback engine. ---------------------------------------------------------------------*/ int MV_StartPlayback() { int status; int buffer; // Initialize the buffers ClearBuffer_DW( MV_MixBuffer[ 0 ], MV_Silence, TotalBufferSize >> 2 ); for( buffer = 0; buffer < MV_NumberOfBuffers; buffer++ ) { MV_BufferEmpty[ buffer ] = TRUE; } // Set the mix buffer variables MV_MixPage = 1; MV_MixFunction = MV_Mix; //JIM // MV_MixRate = MV_RequestedMixRate; // return( MV_Ok ); // Start playback status = DSL_BeginBufferedPlayback( MV_MixBuffer[ 0 ], TotalBufferSize, MV_NumberOfBuffers, MV_RequestedMixRate, MV_MixMode, MV_ServiceVoc ); if ( status != DSL_Ok ) { MV_SetErrorCode( MV_BlasterError ); return( MV_Error ); } MV_MixRate = DSL_GetPlaybackRate(); return( MV_Ok ); } /*--------------------------------------------------------------------- Function: MV_StopPlayback Stops the sound playback engine. ---------------------------------------------------------------------*/ void MV_StopPlayback ( void ) { VoiceNode *voice; VoiceNode *next; DSL_StopPlayback(); // Make sure all callbacks are done. for( voice = VoiceList.next; voice != &VoiceList; voice = next ) { next = voice->next; MV_StopVoice( voice ); if ( MV_CallBackFunc ) { MV_CallBackFunc( voice->callbackval ); } } } /*--------------------------------------------------------------------- Function: MV_PlayWAV Begin playback of sound data with the given sound levels and priority. ---------------------------------------------------------------------*/ int MV_PlayWAV ( uint8_t *ptr, int pitchoffset, int vol, int left, int right, int priority, unsigned long callbackval ) { int status; status = MV_PlayLoopedWAV( ptr, -1, -1, pitchoffset, vol, left, right, priority, callbackval ); return( status ); } /*--------------------------------------------------------------------- Function: MV_PlayWAV3D Begin playback of sound data at specified angle and distance from listener. ---------------------------------------------------------------------*/ int MV_PlayWAV3D ( uint8_t *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_PlayWAV( ptr, pitchoffset, mid, left, right, priority, callbackval ); return( status ); } /*--------------------------------------------------------------------- Function: MV_PlayLoopedWAV Begin playback of sound data with the given sound levels and priority. ---------------------------------------------------------------------*/ int MV_PlayLoopedWAV ( uint8_t *ptr, long loopstart, long loopend, int pitchoffset, int vol, int left, int right, int priority, unsigned long callbackval ) { riff_header *riff; format_header *format; data_header *data; VoiceNode *voice; int length; int absloopend; int absloopstart; if ( !MV_Installed ) { MV_SetErrorCode( MV_NotInstalled ); return( MV_Error ); } riff = ( riff_header * )ptr; if ( ( strncmp( riff->RIFF, "RIFF", 4 ) != 0 ) || ( strncmp( riff->WAVE, "WAVE", 4 ) != 0 ) || ( strncmp( riff->fmt, "fmt ", 4) != 0 ) ) { MV_SetErrorCode( MV_InvalidWAVFile ); return( MV_Error ); } format = ( format_header * )( riff + 1 ); data = ( data_header * )( ( ( char * )format ) + riff->format_size ); // Check if it's PCM data. if ( format->wFormatTag != 1 ) { MV_SetErrorCode( MV_InvalidWAVFile ); return( MV_Error ); } if ( format->nChannels != 1 ) { MV_SetErrorCode( MV_InvalidWAVFile ); return( MV_Error ); } if ( ( format->nBitsPerSample != 8 ) && ( format->nBitsPerSample != 16 ) ) { MV_SetErrorCode( MV_InvalidWAVFile ); return( MV_Error ); } if ( strncmp( data->DATA, "data", 4 ) != 0 ) { MV_SetErrorCode( MV_InvalidWAVFile ); 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->wavetype = WAV; voice->bits = format->nBitsPerSample; voice->GetSound = MV_GetNextWAVBlock; length = data->size; absloopstart = loopstart; absloopend = loopend; if ( voice->bits == 16 ) { loopstart *= 2; data->size &= ~1; loopend *= 2; length /= 2; } loopend = min( loopend, data->size ); absloopend = min( absloopend, length ); voice->Playing = TRUE; voice->DemandFeed = NULL; voice->LoopStart = NULL; voice->LoopCount = 0; voice->position = 0; voice->length = 0; voice->BlockLength = absloopend; voice->NextBlock = ( char * )( data + 1 ); voice->next = NULL; voice->prev = NULL; voice->priority = priority; voice->GLast = -1; voice->GPos = 0; voice->GVal[0] = 0; voice->GVal[1] = 0; voice->GVal[2] = 0; voice->GVal[3] = 0; voice->callbackval = callbackval; voice->LoopStart = voice->NextBlock + loopstart; voice->LoopEnd = voice->NextBlock + loopend; voice->LoopSize = absloopend - absloopstart; if ( ( loopstart >= data->size ) || ( loopstart < 0 ) ) { voice->LoopStart = NULL; voice->LoopEnd = NULL; voice->BlockLength = length; } MV_SetVoicePitch( voice, format->nSamplesPerSec, pitchoffset ); MV_SetVoiceVolume( voice, vol, left, right ); MV_PlayVoice( voice ); return( voice->handle ); } /*--------------------------------------------------------------------- Function: MV_PlayVOC3D Begin playback of sound data at specified angle and distance from listener. ---------------------------------------------------------------------*/ int MV_PlayVOC3D ( uint8_t *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_PlayVOC( ptr, pitchoffset, mid, left, right, priority, callbackval ); return( status ); } /*--------------------------------------------------------------------- Function: MV_PlayVOC Begin playback of sound data with the given sound levels and priority. ---------------------------------------------------------------------*/ int MV_PlayVOC ( uint8_t *ptr, int pitchoffset, int vol, int left, int right, int priority, unsigned long callbackval ) { int status; status = MV_PlayLoopedVOC( ptr, -1, -1, pitchoffset, vol, left, right, priority, callbackval ); return( status ); } /*--------------------------------------------------------------------- Function: MV_PlayLoopedVOC Begin playback of sound data with the given sound levels and priority. ---------------------------------------------------------------------*/ int MV_PlayLoopedVOC ( uint8_t *ptr, long loopstart, long loopend, int pitchoffset, int vol, int left, int right, int priority, uint32_t callbackval ) { VoiceNode *voice; int status; if ( !MV_Installed ) { MV_SetErrorCode( MV_NotInstalled ); return( MV_Error ); } // Make sure it's a valid VOC file. status = strncmp( (char*)ptr, "Creative Voice File", 19 ); if ( status != 0 ) { MV_SetErrorCode( MV_InvalidVOCFile ); 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->wavetype = VOC; voice->bits = 8; voice->GetSound = MV_GetNextVOCBlock; voice->NextBlock = ptr + *( uint16_t * )( ptr + 0x14 ); voice->DemandFeed = NULL; voice->LoopStart = NULL; voice->LoopCount = 0; voice->BlockLength = 0; voice->PitchScale = PITCH_GetScale( pitchoffset ); voice->length = 0; voice->next = NULL; voice->prev = NULL; voice->priority = priority; voice->GLast = -1; voice->GPos = 0; voice->GVal[0] = 0; voice->GVal[1] = 0; voice->GVal[2] = 0; voice->GVal[3] = 0; voice->callbackval = callbackval; voice->LoopStart = ( uint8_t* )loopstart; voice->LoopEnd = ( uint8_t* )loopend; voice->LoopSize = loopend - loopstart + 1; if ( loopstart < 0 ) { voice->LoopStart = NULL; voice->LoopEnd = NULL; } MV_SetVoiceVolume( voice, vol, left, right ); MV_PlayVoice( voice ); return( voice->handle ); } /*--------------------------------------------------------------------- Function: MV_LockEnd Used for determining the length of the functions to lock in memory. ---------------------------------------------------------------------*/ static void MV_LockEnd ( void ) { } #if 0 /*--------------------------------------------------------------------- Function: MV_CreateVolumeTable Create the table used to convert sound data to a specific volume level. ---------------------------------------------------------------------*/ void MV_CreateVolumeTable ( int index, int volume, int MaxVolume ) { int val; int level; int i; level = ( volume * MaxVolume ) / MV_MaxTotalVolume; if ( MV_Bits == 16 ) { for( i = 0; i < 65536; i += 256 ) { val = i - 0x8000; val *= level; val /= MV_MaxVolume; MV_VolumeTable[ index ][ i / 256 ] = val; } } else { for( i = 0; i < 256; i++ ) { val = i - 0x80; val *= level; val /= MV_MaxVolume; MV_VolumeTable[ volume ][ i ] = val; } } } #endif /*--------------------------------------------------------------------- Function: MV_CalcVolume Create the table used to convert sound data to a specific volume level. ---------------------------------------------------------------------*/ void MV_CalcVolume(int MaxVolume) { } /*--------------------------------------------------------------------- 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 ) { volume = max( 0, volume ); volume = min( volume, MV_MaxTotalVolume ); MV_TotalVolume = volume; // Calculate volume table MV_CalcVolume( volume ); } /*--------------------------------------------------------------------- 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 )( int32_t ) ) { MV_CallBackFunc = function; } /*--------------------------------------------------------------------- Function: MV_SetReverseStereo Set the orientation of the left and right channels. ---------------------------------------------------------------------*/ void MV_SetReverseStereo ( int setting ) { MV_SwapLeftRight = setting; } /*--------------------------------------------------------------------- Function: MV_GetReverseStereo Returns the orientation of the left and right channels. ---------------------------------------------------------------------*/ int MV_GetReverseStereo ( void ) { return( MV_SwapLeftRight ); } /*--------------------------------------------------------------------- Function: MV_TestPlayback Checks if playback has started. ---------------------------------------------------------------------*/ int MV_TestPlayback(void) { return MV_Ok; } /*--------------------------------------------------------------------- Function: MV_Init Perform the initialization of variables and memory used by Multivoc. ---------------------------------------------------------------------*/ int MV_Init ( int soundcard, int MixRate, int Voices, int numchannels, int samplebits ) { char *ptr; int status; int buffer; int index; if ( MV_Installed ) { MV_Shutdown(); } MV_SetErrorCode( MV_Ok ); MV_TotalMemory = Voices * sizeof( VoiceNode ); // + sizeof( HARSH_CLIP_TABLE_8 ); status = USRHOOKS_GetMem( ( void ** )&ptr, MV_TotalMemory ); if ( status != USRHOOKS_Ok ) { MV_SetErrorCode( MV_NoMem ); return( MV_Error ); } MV_Voices = ( VoiceNode * )ptr; // MV_HarshClipTable = ptr + ( MV_TotalMemory - sizeof( HARSH_CLIP_TABLE_8 ) ); // Set number of voices before calculating volume table MV_MaxVoices = Voices; LL_Reset( &VoiceList, next, prev ); LL_Reset( &VoicePool, next, prev ); for( index = 0; index < Voices; index++ ) { LL_Add( &VoicePool, &MV_Voices[ index ], next, prev ); } // Allocate mix buffer within 1st megabyte status = DPMI_GetDOSMemory( ( void ** )&ptr, &MV_BufferDescriptor, 2 * TotalBufferSize); if ( status ) { USRHOOKS_FreeMem( MV_Voices ); MV_Voices = NULL; MV_TotalMemory = 0; MV_SetErrorCode( MV_NoMem ); return( MV_Error ); } MV_SetReverseStereo( FALSE ); // Initialize the sound card status = DSL_Init(); if ( status != DSL_Ok ) { MV_SetErrorCode( MV_BlasterError ); } if ( MV_ErrorCode != MV_Ok ) { status = MV_ErrorCode; USRHOOKS_FreeMem( MV_Voices ); MV_Voices = NULL; MV_TotalMemory = 0; DPMI_FreeDOSMemory( MV_BufferDescriptor ); MV_SetErrorCode( status ); return( MV_Error ); } MV_SoundCard = soundcard; MV_Installed = TRUE; MV_CallBackFunc = NULL; MV_RecordFunc = NULL; MV_ReverbLevel = 0; MV_ReverbTable = -1; // Set the sampling rate MV_RequestedMixRate = MixRate; // Set Mixer to play stereo digitized sound MV_SetMixMode( numchannels, samplebits ); MV_ReverbDelay = 14320; // MV_BufferSize * 3; //InitializeCriticalSection(&reverbCS); reverbMutex = SDL_CreateMutex(); MV_MixBuffer[ MV_NumberOfBuffers ] = ptr; for( buffer = 0; buffer < MV_NumberOfBuffers; buffer++ ) { MV_MixBuffer[ buffer ] = ptr; ptr += MV_BufferSize; } // Calculate pan table MV_CalcPanTable(); MV_SetVolume( MV_MaxTotalVolume ); MV_FooMemory = sizeof(double) * MixBufferSize * numchannels + 1024; status = USRHOOKS_GetMem( ( void ** )&ptr, MV_FooMemory); if ( status != USRHOOKS_Ok ) { USRHOOKS_FreeMem( MV_Voices ); MV_Voices = NULL; MV_TotalMemory = 0; MV_SetErrorCode( MV_NoMem ); return( MV_Error); } MV_FooBuffer = ptr; // 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 ); } if ( MV_TestPlayback() != MV_Ok ) { 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; if ( !MV_Installed ) { return( MV_Ok ); } MV_KillAllVoices(); MV_Installed = FALSE; // Stop the sound playback engine MV_StopPlayback(); // Free reverb buffer, if allocated MV_FPReverbFree(); //DeleteCriticalSection(&reverbCS); SDL_DestroyMutex(reverbMutex); // Shutdown the sound card DSL_Shutdown(); // Free any voices we allocated USRHOOKS_FreeMem( MV_FooBuffer ); MV_FooBuffer = NULL; MV_FooMemory = 0; USRHOOKS_FreeMem( MV_Voices ); MV_Voices = NULL; MV_TotalMemory = 0; LL_Reset( &VoiceList, next, prev ); LL_Reset( &VoicePool, next, prev ); 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 ); } void ClearBuffer_DW( void *ptr, unsigned data, int length ) { unsigned *d = (unsigned *)ptr; while (length--) { *d = data; d++; } }