/* 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 #include #include #include #include "linklist.h" #include "sndcards.h" #include "drivers.h" #include "pitch.h" #include "multivoc.h" #include "_multivc.h" #ifdef _MSC_VER #define inline __inline #endif #ifdef __POWERPC__ #define LITTLE16 SWAP16 #define LITTLE32 SWAP32 #else #define LITTLE16 #define LITTLE32 #endif static void MV_Mix( VoiceNode *voice, int32_t buffer ); static void MV_StopVoice( VoiceNode *voice ); static void MV_ServiceVoc( void ); static playbackstatus MV_GetNextVOCBlock( VoiceNode *voice ); static playbackstatus MV_GetNextDemandFeedBlock( VoiceNode *voice ); static playbackstatus MV_GetNextRawBlock( VoiceNode *voice ); static playbackstatus MV_GetNextWAVBlock( VoiceNode *voice ); static VoiceNode *MV_GetVoice( int32_t handle ); static int16_t *MV_GetVolumeTable( int32_t vol ); static void MV_SetVoicePitch( VoiceNode *voice, uint32_t rate, int32_t pitchoffset ); static void MV_CalcVolume( int32_t MaxLevel ); static void MV_CalcPanTable( void ); static inline uint16_t SWAP16(uint16_t s) { return (s >> 8) | (s << 8); } static inline uint32_t SWAP32(uint32_t s) { return (s >> 24) | (s << 24) | ((s&0xff00) << 8) | ((s & 0xff0000) >> 8); } #ifndef min #define min(x,y) ((x) < (y) ? (x) : (y)) #endif #ifndef max #define max(x,y) ((x) > (y) ? (x) : (y)) #endif #define RoundFixed( fixedval, bits ) \ ( \ ( \ (fixedval) + ( 1 << ( (bits) - 1 ) )\ ) >> (bits) \ ) #define IS_QUIET( ptr ) ( ( void * )( ptr ) == ( void * )&MV_VolumeTable[ 0 ] ) static int32_t MV_ReverbLevel; static int32_t MV_ReverbDelay; static VOLUME16 *MV_ReverbTable = NULL; //static int16_t MV_VolumeTable[ MV_MaxVolume + 1 ][ 256 ]; static int16_t MV_VolumeTable[ 255 + 1 ][ 256 ]; //static Pan MV_PanTable[ MV_NumPanPositions ][ MV_MaxVolume + 1 ]; Pan MV_PanTable[ MV_NumPanPositions ][ 255 + 1 ]; int32_t MV_Installed = FALSE; static int32_t MV_TotalVolume = MV_MaxTotalVolume; static int32_t MV_MaxVoices = 1; static int32_t MV_Recording; static int32_t MV_BufferSize = MixBufferSize; static int32_t MV_BufferLength; static int32_t MV_NumberOfBuffers = NumberOfBuffers; static int32_t MV_MixMode = MONO_8BIT; static int32_t MV_Channels = 1; static int32_t MV_Bits = 8; static int32_t MV_Silence = SILENCE_8BIT; static int32_t MV_SwapLeftRight = FALSE; static int32_t MV_RequestedMixRate; int32_t MV_MixRate; static int32_t MV_BuffShift; static int32_t MV_TotalMemory; static int32_t MV_BufferEmpty[ NumberOfBuffers ]; char *MV_MixBuffer[ NumberOfBuffers + 1 ]; static VoiceNode *MV_Voices = NULL; static volatile VoiceNode VoiceList; static volatile VoiceNode VoicePool; static int32_t MV_MixPage = 0; static int32_t MV_VoiceHandle = MV_MinVoiceHandle; static void (*MV_CallBackFunc)(uint32_t) = NULL; static void (*MV_RecordFunc)(char *ptr, int32_t length) = NULL; static void (*MV_MixFunction)(VoiceNode *voice, int32_t buffer); int32_t MV_MaxVolume = 255; char *MV_HarshClipTable; char *MV_MixDestination; int16_t *MV_LeftVolume; int16_t *MV_RightVolume; int32_t MV_SampleSize = 1; int32_t MV_RightChannelOffset; uint32_t MV_MixPosition; int32_t MV_ErrorCode = MV_Ok; static int32_t lockdepth = 0; static int32_t DisableInterrupts(void) { if (lockdepth++ > 0) { return 0; } SoundDriver_Lock(); return 0; } static void RestoreInterrupts(int32_t a) { if (--lockdepth > 0) { return; } SoundDriver_Unlock(); } /*--------------------------------------------------------------------- Function: MV_ErrorString Returns a pointer to the error message associated with an error number. A -1 returns a pointer the current error. ---------------------------------------------------------------------*/ const char *MV_ErrorString ( int32_t ErrorNumber ) { const 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_DriverError : ErrorString = SoundDriver_ErrorString(SoundDriver_GetError()); 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_InvalidVOCFile : ErrorString = "Invalid VOC file passed in to Multivoc."; break; case MV_InvalidWAVFile : ErrorString = "Invalid WAV file passed in to Multivoc."; break; case MV_InvalidVorbisFile : ErrorString = "Invalid OggVorbis file passed in to Multivoc."; break; case MV_InvalidMixMode : ErrorString = "Invalid mix mode request in Multivoc."; break; case MV_NullRecordFunction : ErrorString = "Null record function passed to MV_StartRecording."; break; default : ErrorString = "Unknown Multivoc error code."; break; } return(ErrorString); } /*--------------------------------------------------------------------- Function: MV_Mix Mixes the sound into the buffer. ---------------------------------------------------------------------*/ static void MV_Mix ( VoiceNode *voice, int32_t buffer ) { char *start; int32_t length; int32_t voclength; uint32_t position; uint32_t rate; uint32_t FixedPointBufferSize; // cheap fix for a crash under 64-bit linux --\ // v if ((voice->length == 0) && ((voice->GetSound == NULL) || (voice->GetSound(voice) != KeepPlaying))) { return; } length = MixBufferSize; FixedPointBufferSize = voice->FixedPointBufferSize; MV_MixDestination = MV_MixBuffer[ buffer ]; MV_LeftVolume = voice->LeftVolume; MV_RightVolume = voice->RightVolume; if ((MV_Channels == 2) && (IS_QUIET(MV_LeftVolume))) { MV_LeftVolume = MV_RightVolume; MV_MixDestination += MV_RightChannelOffset; } // 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 - voice->channels) / rate; } else { voice->GetSound(voice); return; } } else { voclength = length; } if (voice->mix) { voice->mix(position, rate, start, voclength); } 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 - voice->channels); } } } } /*--------------------------------------------------------------------- Function: MV_PlayVoice Adds a voice to the play list. ---------------------------------------------------------------------*/ void MV_PlayVoice ( VoiceNode *voice ) { int32_t flags; flags = DisableInterrupts(); LL_SortedInsertion(&VoiceList, voice, prev, next, VoiceNode, priority); RestoreInterrupts(flags); } /*--------------------------------------------------------------------- Function: MV_StopVoice Removes the voice from the play list and adds it to the free list. ---------------------------------------------------------------------*/ void MV_StopVoice ( VoiceNode *voice ) { int32_t flags; flags = DisableInterrupts(); // move the voice from the play list to the free list LL_Remove(voice, next, prev); LL_Add((VoiceNode*) &VoicePool, voice, next, prev); RestoreInterrupts(flags); if (voice->wavetype == Vorbis) { MV_ReleaseVorbisVoice(voice); } } /*--------------------------------------------------------------------- Function: MV_ServiceVoc Starts playback of the waiting buffer and mixes the next one. JBF: no synchronisation happens inside MV_ServiceVoc nor the supporting functions it calls. This would cause a deadlock between the mixer thread in the driver vs the nested locking in the user-space functions of MultiVoc. The call to MV_ServiceVoc is synchronised in the driver. Known functions called by MV_ServiceVoc and its helpers: MV_Mix (and its MV_Mix*bit* workers) MV_GetNextVOCBlock MV_GetNextWAVBlock MV_SetVoiceMixMode ---------------------------------------------------------------------*/ void MV_ServiceVoc ( void ) { VoiceNode *voice; VoiceNode *next; //int32_t flags; // Toggle which buffer we'll mix next MV_MixPage++; if (MV_MixPage >= MV_NumberOfBuffers) { MV_MixPage -= MV_NumberOfBuffers; } if (MV_ReverbLevel == 0) { // Initialize buffer //Commented out so that the buffer is always cleared. //This is so the guys at Echo Speech can mix into the //buffer even when no sounds are playing. //if ( !MV_BufferEmpty[ MV_MixPage ] ) { ClearBuffer_DW(MV_MixBuffer[ MV_MixPage ], MV_Silence, MV_BufferSize >> 2); MV_BufferEmpty[ MV_MixPage ] = TRUE; } } else { char *end; char *source; char *dest; int32_t count; int32_t length; end = MV_MixBuffer[ 0 ] + MV_BufferLength;; dest = MV_MixBuffer[ MV_MixPage ]; source = MV_MixBuffer[ MV_MixPage ] - MV_ReverbDelay; if (source < MV_MixBuffer[ 0 ]) { source += MV_BufferLength; } length = MV_BufferSize; while (length > 0) { count = length; if (source + count > end) { count = end - source; } if (MV_Bits == 16) { if (MV_ReverbTable != NULL) { MV_16BitReverb(source, dest, MV_ReverbTable, count / 2); } else { MV_16BitReverbFast(source, dest, count / 2, MV_ReverbLevel); } } else { if (MV_ReverbTable != NULL) { MV_8BitReverb((int8_t *) source, (int8_t *) dest, MV_ReverbTable, count); } else { MV_8BitReverbFast((int8_t *) source, (int8_t *) dest, count, MV_ReverbLevel); } } // if we go through the loop again, it means that we've wrapped around the buffer source = MV_MixBuffer[ 0 ]; dest += count; length -= count; } } // Play any waiting voices //flags = DisableInterrupts(); if (!VoiceList.next) return; for (voice = VoiceList.next; voice != &VoiceList; voice = next) { if ( voice->Paused ) { next = voice->next; continue; } MV_BufferEmpty[ MV_MixPage ] = FALSE; // if (!voice->Paused) MV_MixFunction(voice, MV_MixPage); next = voice->next; // Is this voice done? if (!voice->Playing/* && !voice->Paused*/) { //JBF: prevent a deadlock caused by MV_StopVoice grabbing the mutex again //MV_StopVoice( voice ); LL_Remove(voice, next, prev); LL_Add((VoiceNode*) &VoicePool, voice, next, prev); if (MV_CallBackFunc) { MV_CallBackFunc(voice->callbackval); } } } //RestoreInterrupts(flags); } /*--------------------------------------------------------------------- Function: MV_GetNextVOCBlock Interperate the information of a VOC format sound file. ---------------------------------------------------------------------*/ playbackstatus MV_GetNextVOCBlock ( VoiceNode *voice ) { uint8_t *ptr; int32_t blocktype; int32_t lastblocktype; uint32_t blocklength; uint32_t samplespeed; uint32_t tc = 0; int32_t packtype; int32_t voicemode; int32_t done; unsigned BitsPerSample; unsigned Channels; unsigned Format; if (voice->BlockLength > 0) { voice->position -= voice->length; voice->sound += (voice->length >> 16) * (voice->channels * voice->bits / 8); voice->length = min(voice->BlockLength, 0x8000); voice->BlockLength -= voice->length; voice->length <<= 16; return(KeepPlaying); } ptr = (uint8_t *)voice->NextBlock; voice->Playing = TRUE; voice->Paused = FALSE; 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 = (int32_t)*ptr; blocklength = LITTLE32(*(uint32_t *)(ptr + 1)) & 0x00ffffff; ptr += 4; switch (blocktype) { case 0 : // End of data if ((voice->LoopStart == NULL) || ((intptr_t) voice->LoopStart >= ((intptr_t) ptr - 4))) { voice->Playing = FALSE; done = TRUE; } else { voice->NextBlock = voice->LoopStart; voice->BlockLength = 0; voice->position = 0; return MV_GetNextVOCBlock(voice); } break; case 1 : // Sound data block voice->bits = 8; voice->channels = voicemode + 1; if (lastblocktype != 8) { tc = (uint32_t)*ptr << 8; packtype = *(ptr + 1); } ptr += 2; blocklength -= 2; samplespeed = 256000000L / (voice->channels * (65536 - tc)); // Skip packed or stereo data if ((packtype != 0) || (voicemode != 0 && voicemode != 1)) { 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 = LITTLE16(*(uint16_t *)ptr); voice->LoopStart = (char *)((intptr_t) 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 = (uint8_t *) voice->LoopStart; if (voice->LoopCount < 0xffff) { voice->LoopCount--; if (voice->LoopCount == 0) { voice->LoopStart = NULL; } } } } break; case 8 : // Extended block voice->bits = 8; voice->channels = 1; tc = LITTLE16(*(uint16_t *)ptr); packtype = *(ptr + 2); voicemode = *(ptr + 3); ptr += blocklength; break; case 9 : // New sound data block samplespeed = LITTLE32(*(uint32_t *)ptr); BitsPerSample = (unsigned)*(ptr + 4); Channels = (unsigned)*(ptr + 5); Format = (unsigned)LITTLE16(*(uint16_t *)(ptr + 6)); if ((BitsPerSample == 8) && (Channels == 1 || Channels == 2) && (Format == VOC_8BIT)) { ptr += 12; blocklength -= 12; voice->bits = 8; voice->channels = Channels; done = TRUE; } else if ((BitsPerSample == 16) && (Channels == 1 || Channels == 2) && (Format == VOC_16BIT)) { ptr += 12; blocklength -= 12; voice->bits = 16; voice->channels = Channels; 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 = (char *)ptr + blocklength; voice->sound = (char *)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 > (uint32_t)voice->LoopEnd) { blocklength = (uint32_t)voice->LoopEnd; } else { voice->LoopEnd = (char *)blocklength; } voice->LoopStart = voice->sound + (uint32_t)voice->LoopStart; voice->LoopEnd = voice->sound + (uint32_t)voice->LoopEnd; voice->LoopSize = voice->LoopEnd - voice->LoopStart; } if (voice->bits == 16) { blocklength /= 2; } if (voice->channels == 2) { 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 * (voice->channels * voice->bits / 8); 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 * (voice->channels * voice->bits / 8); voice->BlockLength -= voice->length; voice->length <<= 16; return(KeepPlaying); } /*--------------------------------------------------------------------- Function: MV_ServiceRecord Starts recording of the waiting buffer. ---------------------------------------------------------------------*/ /* static void MV_ServiceRecord ( void ) { if (MV_RecordFunc) { MV_RecordFunc(MV_MixBuffer[ 0 ] + MV_MixPage * MixBufferSize, MixBufferSize); } // Toggle which buffer we'll mix next MV_MixPage++; if (MV_MixPage >= NumberOfBuffers) { MV_MixPage = 0; } } */ /*--------------------------------------------------------------------- Function: MV_GetVoice Locates the voice with the specified handle. ---------------------------------------------------------------------*/ VoiceNode *MV_GetVoice ( int32_t handle ) { VoiceNode *voice; int32_t flags; flags = DisableInterrupts(); for (voice = VoiceList.next; voice != &VoiceList; voice = voice->next) { if (handle == voice->handle) { break; } } RestoreInterrupts(flags); if (voice == &VoiceList) { MV_SetErrorCode(MV_VoiceNotFound); voice = 0; } return(voice); } /*--------------------------------------------------------------------- Function: MV_VoicePlaying Checks if the voice associated with the specified handle is playing. ---------------------------------------------------------------------*/ int32_t MV_VoicePlaying ( int32_t 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. ---------------------------------------------------------------------*/ int32_t MV_KillAllVoices ( void ) { VoiceNode * voice, * next; int32_t flags; if (!MV_Installed) { MV_SetErrorCode(MV_NotInstalled); return(MV_Error); } flags = DisableInterrupts(); // Remove all the voices from the list for (voice = VoiceList.next; voice != &VoiceList; voice = next) { next = voice->next; if (voice->priority < MV_MUSIC_PRIORITY) { MV_Kill(voice->handle); } } RestoreInterrupts(flags); return(MV_Ok); } /*--------------------------------------------------------------------- Function: MV_Kill Stops output of the voice associated with the specified handle. ---------------------------------------------------------------------*/ int32_t MV_Kill ( int32_t handle ) { VoiceNode *voice; int32_t flags; uint32_t 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; MV_StopVoice(voice); RestoreInterrupts(flags); if (MV_CallBackFunc) { MV_CallBackFunc(callbackval); } return(MV_Ok); } /*--------------------------------------------------------------------- Function: MV_VoicesPlaying Determines the number of currently active voices. ---------------------------------------------------------------------*/ int32_t MV_VoicesPlaying ( void ) { VoiceNode *voice; int32_t NumVoices = 0; int32_t flags; if (!MV_Installed) { MV_SetErrorCode(MV_NotInstalled); return(0); } flags = DisableInterrupts(); for (voice = VoiceList.next; voice != &VoiceList; voice = voice->next) { NumVoices++; } RestoreInterrupts(flags); return(NumVoices); } /*--------------------------------------------------------------------- Function: MV_AllocVoice Retrieve an inactive or lower priority voice for output. ---------------------------------------------------------------------*/ VoiceNode *MV_AllocVoice ( int32_t priority ) { VoiceNode *voice; VoiceNode *node; int32_t flags; //return( NULL ); if (MV_Recording) { return(NULL); } flags = DisableInterrupts(); // 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 RestoreInterrupts(flags); return(NULL); } voice = VoicePool.next; LL_Remove(voice, next, prev); 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_VoiceAvailable Checks if a voice can be play at the specified priority. ---------------------------------------------------------------------*/ int32_t MV_VoiceAvailable ( int32_t priority ) { VoiceNode *voice; VoiceNode *node; int32_t flags; // Check if we have any free voices if (!LL_Empty(&VoicePool, next, prev)) { return(TRUE); } flags = DisableInterrupts(); // 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; } } RestoreInterrupts(flags); 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, uint32_t rate, int32_t 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_SetPitch Sets the pitch for the voice associated with the specified handle. ---------------------------------------------------------------------*/ int32_t MV_SetPitch ( int32_t handle, int32_t 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); } MV_SetVoicePitch(voice, voice->SamplingRate, pitchoffset); return(MV_Ok); } /*--------------------------------------------------------------------- Function: MV_SetFrequency Sets the frequency for the voice associated with the specified handle. ---------------------------------------------------------------------*/ int32_t MV_SetFrequency ( int32_t handle, int32_t frequency ) { 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); } MV_SetVoicePitch(voice, frequency, 0); return(MV_Ok); } /*--------------------------------------------------------------------- Function: MV_GetVolumeTable Returns a pointer to the volume table associated with the specified volume. ---------------------------------------------------------------------*/ static int16_t *MV_GetVolumeTable ( int32_t vol ) { int32_t volume; int16_t *table; volume = MIX_VOLUME(vol); table = (int16_t *) &MV_VolumeTable[ volume ]; return(table); } /*--------------------------------------------------------------------- Function: MV_SetVoiceMixMode Selects which method should be used to mix the voice. 8Bit 16Bit 8Bit 16Bit | 8Bit 16Bit 8Bit 16Bit | Mono Mono Ster Ster | Mono Mono Ster Ster | Mixer Out Out Out Out | In In In In | --------------------------+---------------------------+------------- X | X | Mix8BitMono16 X | X | Mix8BitMono X | X | Mix8BitStereo16 X | X | Mix8BitStereo X | X | Mix16BitMono16 X | X | Mix16BitMono X | X | Mix16BitStereo16 X | X | Mix16BitStereo --------------------------+---------------------------+------------- X | X | Mix16BitStereo16Stereo X | X | Mix16BitStereo8Stereo X | X | Mix8BitStereo16Stereo X | X | Mix8BitStereo8Stereo X | X | Mix16BitMono16Stereo X | X | Mix16BitMono8Stereo X | X | Mix8BitMono16Stereo X | X | Mix8BitMono8Stereo ---------------------------------------------------------------------*/ void MV_SetVoiceMixMode ( VoiceNode *voice ) { //int32_t flags; int32_t test; //flags = DisableInterrupts(); test = T_DEFAULT; if (MV_Bits == 8) { test |= T_8BITS; } 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; } } if (voice->bits == 16) { test |= T_16BITSOURCE; } if (voice->channels == 2) { test |= T_STEREOSOURCE; test &= ~(T_RIGHTQUIET | T_LEFTQUIET); } switch (test) { case T_8BITS | T_MONO | T_16BITSOURCE : voice->mix = MV_Mix8BitMono16; break; case T_8BITS | T_MONO : voice->mix = MV_Mix8BitMono; break; case T_8BITS | T_16BITSOURCE | T_LEFTQUIET : MV_LeftVolume = MV_RightVolume; voice->mix = MV_Mix8BitMono16; break; case T_8BITS | T_LEFTQUIET : MV_LeftVolume = MV_RightVolume; voice->mix = MV_Mix8BitMono; break; case T_8BITS | T_16BITSOURCE | T_RIGHTQUIET : voice->mix = MV_Mix8BitMono16; break; case T_8BITS | T_RIGHTQUIET : voice->mix = MV_Mix8BitMono; break; case T_8BITS | T_16BITSOURCE : voice->mix = MV_Mix8BitStereo16; break; case T_8BITS : voice->mix = MV_Mix8BitStereo; break; case T_MONO | T_16BITSOURCE : voice->mix = MV_Mix16BitMono16; break; case T_MONO : voice->mix = MV_Mix16BitMono; break; case T_16BITSOURCE | T_LEFTQUIET : MV_LeftVolume = MV_RightVolume; voice->mix = MV_Mix16BitMono16; break; case T_LEFTQUIET : MV_LeftVolume = MV_RightVolume; voice->mix = MV_Mix16BitMono; break; case T_16BITSOURCE | T_RIGHTQUIET : voice->mix = MV_Mix16BitMono16; break; case T_RIGHTQUIET : voice->mix = MV_Mix16BitMono; break; case T_16BITSOURCE : voice->mix = MV_Mix16BitStereo16; break; case T_SIXTEENBIT_STEREO : voice->mix = MV_Mix16BitStereo; break; case T_16BITSOURCE | T_STEREOSOURCE: voice->mix = MV_Mix16BitStereo16Stereo; break; case T_16BITSOURCE | T_STEREOSOURCE | T_8BITS: voice->mix = MV_Mix8BitStereo16Stereo; break; case T_16BITSOURCE | T_STEREOSOURCE | T_MONO: voice->mix = MV_Mix16BitMono16Stereo; break; case T_16BITSOURCE | T_STEREOSOURCE | T_8BITS | T_MONO: voice->mix = MV_Mix8BitMono16Stereo; break; case T_STEREOSOURCE: voice->mix = MV_Mix16BitStereo8Stereo; break; case T_STEREOSOURCE | T_8BITS: voice->mix = MV_Mix8BitStereo8Stereo; break; case T_STEREOSOURCE | T_MONO: voice->mix = MV_Mix16BitMono8Stereo; break; case T_STEREOSOURCE | T_8BITS | T_MONO: voice->mix = MV_Mix8BitMono8Stereo; break; default : voice->mix = 0; } //RestoreInterrupts( flags ); } /*--------------------------------------------------------------------- Function: MV_SetVoiceVolume Sets the stereo and mono volume level of the voice associated with the specified handle. ---------------------------------------------------------------------*/ void MV_SetVoiceVolume ( VoiceNode *voice, int32_t vol, int32_t left, int32_t 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_PauseVoice Pauses the voice associated with the specified handle without stoping the sound. ---------------------------------------------------------------------*/ int32_t MV_PauseVoice ( int32_t handle, int32_t pause ) { VoiceNode *voice; int32_t flags; 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_Warning); } voice->Paused = pause; RestoreInterrupts(flags); return(MV_Ok); } /*--------------------------------------------------------------------- Function: MV_EndLooping Stops the voice associated with the specified handle from looping without stoping the sound. ---------------------------------------------------------------------*/ int32_t MV_EndLooping ( int32_t handle ) { VoiceNode *voice; int32_t flags; 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_Warning); } voice->LoopCount = 0; voice->LoopStart = NULL; voice->LoopEnd = NULL; RestoreInterrupts(flags); return(MV_Ok); } /*--------------------------------------------------------------------- Function: MV_SetPan Sets the stereo and mono volume level of the voice associated with the specified handle. ---------------------------------------------------------------------*/ int32_t MV_SetPan ( int32_t handle, int32_t vol, int32_t left, int32_t 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. ---------------------------------------------------------------------*/ int32_t MV_Pan3D ( int32_t handle, int32_t angle, int32_t distance ) { int32_t left; int32_t right; int32_t mid; int32_t volume; int32_t 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 ( int32_t reverb ) { MV_ReverbLevel = MIX_VOLUME(reverb); MV_ReverbTable = &MV_VolumeTable[ MV_ReverbLevel ]; } /*--------------------------------------------------------------------- Function: MV_SetFastReverb Sets the level of reverb to add to mix. ---------------------------------------------------------------------*/ void MV_SetFastReverb ( int32_t reverb ) { MV_ReverbLevel = max(0, min(16, reverb)); MV_ReverbTable = NULL; } /*--------------------------------------------------------------------- Function: MV_GetMaxReverbDelay Returns the maximum delay time for reverb. ---------------------------------------------------------------------*/ int32_t MV_GetMaxReverbDelay ( void ) { int32_t maxdelay; maxdelay = MixBufferSize * MV_NumberOfBuffers; return maxdelay; } /*--------------------------------------------------------------------- Function: MV_GetReverbDelay Returns the current delay time for reverb. ---------------------------------------------------------------------*/ int32_t MV_GetReverbDelay ( void ) { return MV_ReverbDelay / MV_SampleSize; } /*--------------------------------------------------------------------- Function: MV_SetReverbDelay Sets the delay level of reverb to add to mix. ---------------------------------------------------------------------*/ void MV_SetReverbDelay ( int32_t delay ) { int32_t maxdelay; 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. ---------------------------------------------------------------------*/ int32_t MV_SetMixMode ( int32_t numchannels, int32_t samplebits ) { int32_t 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. ---------------------------------------------------------------------*/ int32_t MV_StartPlayback ( void ) { int32_t status; int32_t 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 = SoundDriver_BeginPlayback(MV_MixBuffer[0], MV_BufferSize, MV_NumberOfBuffers, MV_ServiceVoc); if (status != MV_Ok) { MV_SetErrorCode(MV_DriverError); return MV_Error; } MV_MixRate = MV_RequestedMixRate; return(MV_Ok); } /*--------------------------------------------------------------------- Function: MV_StopPlayback Stops the sound playback engine. ---------------------------------------------------------------------*/ void MV_StopPlayback ( void ) { VoiceNode *voice; VoiceNode *next; int32_t flags; // Stop sound playback SoundDriver_StopPlayback(); // Make sure all callbacks are done. flags = DisableInterrupts(); for (voice = VoiceList.next; voice != &VoiceList; voice = next) { next = voice->next; MV_StopVoice(voice); if (MV_CallBackFunc) { MV_CallBackFunc(voice->callbackval); } } RestoreInterrupts(flags); } /*--------------------------------------------------------------------- Function: MV_StartRecording Starts the sound recording engine. ---------------------------------------------------------------------*/ int32_t MV_StartRecording ( int32_t MixRate, void (*function)(char *ptr, int32_t length) ) { MV_SetErrorCode(MV_UnsupportedCard); return(MV_Error); } /*--------------------------------------------------------------------- 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. ---------------------------------------------------------------------*/ int32_t MV_StartDemandFeedPlayback ( void (*function)(char **ptr, uint32_t *length), int32_t rate, int32_t pitchoffset, int32_t vol, int32_t left, int32_t right, int32_t priority, uint32_t callbackval ) { VoiceNode *voice; 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->wavetype = DemandFeed; voice->bits = 8; voice->channels = 1; voice->GetSound = MV_GetNextDemandFeedBlock; voice->NextBlock = NULL; voice->DemandFeed = function; voice->LoopStart = NULL; voice->LoopCount = 0; voice->BlockLength = 0; voice->position = 0; voice->sound = NULL; voice->length = 0; voice->BlockLength = 0; voice->Playing = TRUE; voice->next = NULL; voice->prev = NULL; voice->priority = priority; voice->callbackval = callbackval; MV_SetVoicePitch(voice, rate, pitchoffset); MV_SetVoiceVolume(voice, vol, left, right); MV_PlayVoice(voice); return(voice->handle); } /*--------------------------------------------------------------------- Function: MV_PlayRaw Begin playback of sound data with the given sound levels and priority. ---------------------------------------------------------------------*/ int32_t MV_PlayRaw ( char *ptr, uint32_t length, unsigned rate, int32_t pitchoffset, int32_t vol, int32_t left, int32_t right, int32_t priority, uint32_t callbackval ) { int32_t status; status = MV_PlayLoopedRaw(ptr, length, NULL, NULL, rate, pitchoffset, vol, left, right, priority, callbackval); return(status); } /*--------------------------------------------------------------------- Function: MV_PlayLoopedRaw Begin playback of sound data with the given sound levels and priority. ---------------------------------------------------------------------*/ int32_t MV_PlayLoopedRaw ( char *ptr, uint32_t length, char *loopstart, char *loopend, unsigned rate, int32_t pitchoffset, int32_t vol, int32_t left, int32_t right, int32_t priority, uint32_t callbackval ) { VoiceNode *voice; 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->wavetype = Raw; voice->bits = 8; voice->channels = 1; voice->GetSound = MV_GetNextRawBlock; voice->Playing = TRUE; voice->NextBlock = ptr; voice->position = 0; voice->BlockLength = length; voice->length = 0; voice->next = NULL; voice->prev = NULL; voice->priority = priority; voice->callbackval = callbackval; voice->LoopStart = loopstart; voice->LoopEnd = loopend; voice->LoopSize = (voice->LoopEnd - voice->LoopStart) + 1; MV_SetVoicePitch(voice, rate, pitchoffset); MV_SetVoiceVolume(voice, vol, left, right); MV_PlayVoice(voice); return(voice->handle); } /*--------------------------------------------------------------------- Function: MV_PlayWAV Begin playback of sound data with the given sound levels and priority. ---------------------------------------------------------------------*/ int32_t MV_PlayWAV ( char *ptr, uint32_t length, int32_t pitchoffset, int32_t vol, int32_t left, int32_t right, int32_t priority, uint32_t callbackval ) { int32_t status; status = MV_PlayLoopedWAV(ptr, length, -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. ---------------------------------------------------------------------*/ int32_t MV_PlayWAV3D ( char *ptr, uint32_t length, int32_t pitchoffset, int32_t angle, int32_t distance, int32_t priority, uint32_t callbackval ) { int32_t left; int32_t right; int32_t mid; int32_t volume; int32_t 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, length, pitchoffset, mid, left, right, priority, callbackval); return(status); } /*--------------------------------------------------------------------- Function: MV_PlayLoopedWAV Begin playback of sound data with the given sound levels and priority. ---------------------------------------------------------------------*/ int32_t MV_PlayLoopedWAV ( char *ptr, uint32_t ptrlength, int32_t loopstart, int32_t loopend, int32_t pitchoffset, int32_t vol, int32_t left, int32_t right, int32_t priority, uint32_t callbackval ) { riff_header riff; format_header format; data_header data; VoiceNode *voice; int32_t length; if (!MV_Installed) { MV_SetErrorCode(MV_NotInstalled); return(MV_Error); } memcpy(&riff, ptr, sizeof(riff_header)); riff.file_size = LITTLE32(riff.file_size); riff.format_size = LITTLE32(riff.format_size); if ((memcmp(riff.RIFF, "RIFF", 4) != 0) || (memcmp(riff.WAVE, "WAVE", 4) != 0) || (memcmp(riff.fmt, "fmt ", 4) != 0)) { MV_SetErrorCode(MV_InvalidWAVFile); return(MV_Error); } memcpy(&format, ptr + sizeof(riff_header), sizeof(format_header)); format.wFormatTag = LITTLE16(format.wFormatTag); format.nChannels = LITTLE16(format.nChannels); format.nSamplesPerSec = LITTLE32(format.nSamplesPerSec); format.nAvgBytesPerSec = LITTLE32(format.nAvgBytesPerSec); format.nBlockAlign = LITTLE16(format.nBlockAlign); format.nBitsPerSample = LITTLE16(format.nBitsPerSample); memcpy(&data, ptr + sizeof(riff_header) + riff.format_size, sizeof(data_header)); data.size = LITTLE32(data.size); // Check if it's PCM data. if (format.wFormatTag != 1) { MV_SetErrorCode(MV_InvalidWAVFile); return(MV_Error); } if (format.nChannels != 1 && format.nChannels != 2) { MV_SetErrorCode(MV_InvalidWAVFile); return(MV_Error); } if ((format.nBitsPerSample != 8) && (format.nBitsPerSample != 16)) { MV_SetErrorCode(MV_InvalidWAVFile); return(MV_Error); } if (memcmp(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->channels = format.nChannels; voice->GetSound = MV_GetNextWAVBlock; length = data.size; if (voice->bits == 16) { data.size &= ~1; length /= 2; } if (voice->channels == 2) { data.size &= ~1; length /= 2; } voice->Playing = TRUE; voice->Paused = FALSE; voice->DemandFeed = NULL; voice->LoopStart = NULL; voice->LoopCount = 0; voice->position = 0; voice->length = 0; voice->BlockLength = length; voice->NextBlock = (char *)((intptr_t) ptr + sizeof(riff_header) + riff.format_size + sizeof(data_header)); voice->next = NULL; voice->prev = NULL; voice->priority = priority; voice->callbackval = callbackval; voice->LoopStart = loopstart >= 0 ? voice->NextBlock : NULL; voice->LoopEnd = NULL; voice->LoopSize = 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. ---------------------------------------------------------------------*/ int32_t MV_PlayVOC3D ( char *ptr, uint32_t ptrlength, int32_t pitchoffset, int32_t angle, int32_t distance, int32_t priority, uint32_t callbackval ) { int32_t left; int32_t right; int32_t mid; int32_t volume; int32_t 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, ptrlength, pitchoffset, mid, left, right, priority, callbackval); return(status); } /*--------------------------------------------------------------------- Function: MV_PlayVOC Begin playback of sound data with the given sound levels and priority. ---------------------------------------------------------------------*/ int32_t MV_PlayVOC ( char *ptr, uint32_t ptrlength, int32_t pitchoffset, int32_t vol, int32_t left, int32_t right, int32_t priority, uint32_t callbackval ) { int32_t status; status = MV_PlayLoopedVOC(ptr, ptrlength, -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. ---------------------------------------------------------------------*/ int32_t MV_PlayLoopedVOC ( char *ptr, uint32_t ptrlength, int32_t loopstart, int32_t loopend, int32_t pitchoffset, int32_t vol, int32_t left, int32_t right, int32_t priority, uint32_t callbackval ) { VoiceNode *voice; int32_t status; if (!MV_Installed) { MV_SetErrorCode(MV_NotInstalled); return(MV_Error); } // Make sure it's a valid VOC file. status = memcmp(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->channels = 1; voice->GetSound = MV_GetNextVOCBlock; voice->NextBlock = ptr + LITTLE16(*(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->callbackval = callbackval; voice->LoopStart = loopstart >= 0 ? voice->NextBlock : 0; voice->LoopEnd = 0; 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_CreateVolumeTable Create the table used to convert sound data to a specific volume level. ---------------------------------------------------------------------*/ void MV_CreateVolumeTable ( int32_t index, int32_t volume, int32_t MaxVolume ) { int32_t val; int32_t level; int32_t 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; } } } /*--------------------------------------------------------------------- Function: MV_CalcVolume Create the table used to convert sound data to a specific volume level. ---------------------------------------------------------------------*/ void MV_CalcVolume ( int32_t MaxVolume ) { int32_t volume; for (volume = 0; volume < 128; volume++) { MV_HarshClipTable[ volume ] = 0; MV_HarshClipTable[ volume + 384 ] = 255; } for (volume = 0; volume < 256; volume++) { MV_HarshClipTable[ volume + 128 ] = volume; } // For each volume level, create a translation table with the // appropriate volume calculated. for (volume = 0; volume <= MV_MaxVolume; volume++) { MV_CreateVolumeTable(volume, volume, 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 ) { int32_t level; int32_t angle; int32_t distance; int32_t HalfAngle; int32_t 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 ( int32_t 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. ---------------------------------------------------------------------*/ int32_t MV_GetVolume ( void ) { return(MV_TotalVolume); } /*--------------------------------------------------------------------- Function: MV_SetCallBack Set the function to call when a voice stops. ---------------------------------------------------------------------*/ void MV_SetCallBack ( void (*function)(uint32_t) ) { MV_CallBackFunc = function; } /*--------------------------------------------------------------------- Function: MV_SetReverseStereo Set the orientation of the left and right channels. ---------------------------------------------------------------------*/ void MV_SetReverseStereo ( int32_t setting ) { MV_SwapLeftRight = setting; } /*--------------------------------------------------------------------- Function: MV_GetReverseStereo Returns the orientation of the left and right channels. ---------------------------------------------------------------------*/ int32_t MV_GetReverseStereo ( void ) { return(MV_SwapLeftRight); } /*--------------------------------------------------------------------- Function: MV_Init Perform the initialization of variables and memory used by Multivoc. ---------------------------------------------------------------------*/ int32_t MV_Init ( int32_t soundcard, int32_t MixRate, int32_t Voices, int32_t numchannels, int32_t samplebits, void * initdata ) { char *ptr; int32_t status; int32_t buffer; int32_t index; if (MV_Installed) { MV_Shutdown(); } MV_SetErrorCode(MV_Ok); MV_TotalMemory = Voices * sizeof(VoiceNode) + sizeof(HARSH_CLIP_TABLE_8) + TotalBufferSize; ptr = (char *) calloc(1, MV_TotalMemory); if (!ptr) { MV_SetErrorCode(MV_NoMem); return(MV_Error); } MV_Voices = (VoiceNode *)ptr; ptr += Voices * sizeof(VoiceNode); MV_HarshClipTable = ptr; ptr += sizeof(HARSH_CLIP_TABLE_8); // Set number of voices before calculating volume table MV_MaxVoices = Voices; LL_Reset((VoiceNode*) &VoiceList, next, prev); LL_Reset((VoiceNode*) &VoicePool, next, prev); for (index = 0; index < Voices; index++) { LL_Add((VoiceNode*) &VoicePool, &MV_Voices[ index ], next, prev); } MV_SetReverseStereo(FALSE); ASS_SoundDriver = soundcard; // Initialize the sound card status = SoundDriver_Init(&MixRate, &numchannels, &samplebits, initdata); if (status != MV_Ok) { MV_SetErrorCode(MV_DriverError); } if (MV_ErrorCode != MV_Ok) { status = MV_ErrorCode; free(MV_Voices); MV_Voices = NULL; MV_HarshClipTable = NULL; MV_TotalMemory = 0; MV_SetErrorCode(status); return(MV_Error); } MV_Installed = TRUE; MV_CallBackFunc = NULL; MV_RecordFunc = NULL; MV_Recording = FALSE; MV_ReverbLevel = 0; MV_ReverbTable = NULL; // Set the sampling rate MV_RequestedMixRate = MixRate; // Set Mixer to play stereo digitized sound MV_SetMixMode(numchannels, samplebits); MV_ReverbDelay = MV_BufferSize * 3; // Make sure we don't cross a physical page 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); // 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. ---------------------------------------------------------------------*/ int32_t MV_Shutdown ( void ) { int32_t buffer; if (!MV_Installed) { return(MV_Ok); } MV_KillAllVoices(); MV_Installed = FALSE; // Stop the sound recording engine if (MV_Recording) { MV_StopRecord(); } // Stop the sound playback engine MV_StopPlayback(); // Shutdown the sound card SoundDriver_Shutdown(); // Free any voices we allocated free(MV_Voices); MV_Voices = NULL; MV_TotalMemory = 0; LL_Reset((VoiceNode*) &VoiceList, next, prev); LL_Reset((VoiceNode*) &VoicePool, next, prev); MV_MaxVoices = 1; // Release the descriptor from our mix buffer for (buffer = 0; buffer < NumberOfBuffers; buffer++) { MV_MixBuffer[ buffer ] = NULL; } return(MV_Ok); } // vim:ts=3:expandtab: