/* ** Command & Conquer Red Alert(tm) ** Copyright 2025 Electronic Arts Inc. ** ** 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 3 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, see . */ /**************************************************************************** * * C O N F I D E N T I A L -- W E S T W O O D S T U D I O S * *---------------------------------------------------------------------------- * * PROJECT * VQA player library. (32-Bit protected mode) * * FILE * audio.c * * DESCRIPTION * Audio playback and timing. * * PROGRAMMER * Bill Randolph * Denzil E. Long, Jr. * * DATE * August 4, 1995 * * * HISTORY: * Modified for Win95 Direct Sound - Steve T 1/2/96 6:35AM * *---------------------------------------------------------------------------- * * PUBLIC * VQA_StartTimerInt - Initialize system timer interrupt. * VQA_StopTimerInt - Remove system timer interrupt. * VQA_SetTimer - Resets current time to given tick value. * VQA_GetTime - Return current time. * VQA_TimerMethod - Get timer method being used. * VQA_OpenAudio - Open sound system. * VQA_CloseAudio - Close sound system * VQA_StartAudio - Starts audio playback * VQA_StopAudio - Stop audio playback. * CopyAudio - Copy data from Audio Temp buf into Audio play buf. * * PRIVATE * TimerCallback - VQA timer event. (Called by HMI) * AutoDetect - Auto detect the sound card. * AudioCallback - Sound system callback. * ****************************************************************************/ #include #include #include #include #include #include "vqaplayp.h" #include #ifdef __WATCOMC__ #pragma pack(4); #endif /*--------------------------------------------------------------------------- * AUDIO DEFINITIONS *-------------------------------------------------------------------------*/ #define USE_WINDOWS_TIME 1 #define HMI_SAMPLE 0x1000 #define MAKE_LONG(a,b) (((long)(a)<<16)|(long)((b)&0x0000FFFFL)) /*--------------------------------------------------------------------------- * PROTOTYPES *-------------------------------------------------------------------------*/ #if( USE_WINDOWS_TIME ) extern unsigned long Get_Game_Time( void ); #endif #if(VQAAUDIO_ON) #if(VQADIRECT_SOUND) void CALLBACK TimerCallback ( UINT event_id, UINT res1 , DWORD user, DWORD res2, DWORD res3 ); BOOL Move_HMI_Audio_Block_To_Direct_Sound_Buffer (void); void CALLBACK AudioCallback ( UINT , UINT , DWORD , DWORD , DWORD ); #else long AutoDetect(_SOS_CAPABILITIES *digicaps, long bitsize, long channels); void far TimerCallback(void); void far __cdecl AudioCallback(WORD wDriverHandle, WORD wAction, WORD wSampleID); /* Dummy functions used to mark the start/end address of the file. */ static void StartAddr(void); static void EndAddr(void); #endif /*--------------------------------------------------------------------------- * GLOBAL DATA *-------------------------------------------------------------------------*/ static VQAHandleP *VQAP = NULL; static long AudioFlags = 0; static long TimerSysCount = 0; static long TimerIntCount = 0; static WORD VQATimer = 0; static long TimerMethod; static long VQATickCount = 0; #endif /* VQAAUDIO_ON */ static long TickOffset = 0; char *HMIDevName = ""; #if(VQAAUDIO_ON) #if(!VQADIRECT_SOUND) /* This is a dummy function that is used to mark the start of the module. * It is necessary for locking the memory the module occupies. This prevents * the virtual memory manager from swapping out this memory. */ static void StartAddr(void) { } /**************************************************************************** * * NAME * VQA_StartTimerInt - Initialize system timer interrupt. * * SYNOPSIS * Error = VQA_StartTimerInt(VQA, Init) * * long VQA_StartTimerInt(VQAHandeP *, long); * * FUNCTION * Initialize the HMI timer system and add our own timer event. If the * system has already been initialized then we are given access to the * the timer system. * * INPUTS * VQA - Pointer to private VQAHandle structure. * Init - Initialize HMI timer system flag. (TRUE = Initialize) * * RESULT * Error - 0 if successful, -1 if error. * ****************************************************************************/ long VQA_StartTimerInt(VQAHandleP *vqap, long init) { VQAAudio *audio; /* Dereference for quick access. */ audio = &vqap->VQABuf->Audio; /* Does the caller want me to initialize the timer system? */ if (init == 0) { /* If the timer system is uninitialized then it is the players * responsibility to do it. */ if ((AudioFlags & VQAAUDF_TIMERINIT)==(HMI_UNINIT<Flags |= (HMI_VQAINIT << VQAAUDB_TIMERINIT); } else { audio->Flags |= (HMI_APPINIT << VQAAUDB_TIMERINIT); } /* Increment the timer system usage count. */ TimerSysCount++; /* Register the VQA_TickCount timer event. */ if ((AudioFlags & VQAAUDF_HMITIMER) == (HMI_UNINIT<Flags |= (HMI_VQAINIT << VQAAUDB_HMITIMER); /* Increment the timer interrupt usage count. */ TimerIntCount++; /* Lock the memory occupied by this module. */ if ((audio->Flags & VQAAUDF_MODLOCKED) == 0) { DPMI_Lock((void *)&StartAddr, (long)&EndAddr - (long)&StartAddr); audio->Flags |= VQAAUDF_MODLOCKED; } return (0); } /**************************************************************************** * * NAME * VQA_StopTimerInt - Remove system timer interrupt. * * SYNOPSIS * VQA_StopTimerInt() * * void VQA_StopTimerInt(void); * * FUNCTION * Remove our timer event from the HMI timer system. Uninitialize the * HMI timer system if we initialized it. * * INPUTS * NONE * * RESULT * NONE * ****************************************************************************/ void VQA_StopTimerInt(VQAHandleP *vqap) { VQAAudio *audio; /* Dereference for quick access. */ audio = &vqap->VQABuf->Audio; /* Decrement the timer interrupt usage count. */ if (TimerIntCount) { TimerIntCount--; } /* Remove the timer interrrupt if it is initialized and the use count is * zero. Otherwise, clear the callers timer interrupt availability flag. */ if (((AudioFlags & VQAAUDF_HMITIMER) == (HMI_VQAINIT<Flags &= ~VQAAUDF_HMITIMER; } /* Derement the timer system usage count. */ if (TimerSysCount) { TimerSysCount--; } /* Close the timer system if it has been opened and the use count is zero. * Otherwise, clear the callers timer system availability flag. */ if (((AudioFlags & VQAAUDF_TIMERINIT) == (HMI_VQAINIT<Flags &= ~VQAAUDF_TIMERINIT; } /* Unlock the memory accupied by this module. */ if ((audio->Flags & VQAAUDF_MODLOCKED) == 1) { DPMI_Unlock(&StartAddr, (long)&EndAddr - (long)&StartAddr); audio->Flags &= ~VQAAUDF_MODLOCKED; } } /**************************************************************************** * * NAME * TimerCallback - VQA timer event. (Called by HMI) * * SYNOPSIS * TimerCallback() * * void TimerCallback(void); * * FUNCTION * Our custom timer event. This is the timer event that we register with * HMI for system timing. * * INPUTS * NONE * * RESULT * NONE * ****************************************************************************/ void far TimerCallback(void) { VQATickCount++; } /**************************************************************************** * * NAME * VQA_OpenAudio - Open sound system. * * SYNOPSIS * Error = VQA_OpenAudio(VQAHandleP) * * long VQA_OpenAudio(VQAHandleP *); * * FUNCTION * Initialize the sound system by setting the DigiHandle to and HMI * driver handle. The timer must first be initialized before calling * this function. * * INPUTS * VQAHandleP - Pointer to private VQAHandle. * * RESULT * Error - 0 if successful, -1 if error. * ****************************************************************************/ long VQA_OpenAudio(VQAHandleP *vqap) { VQAData *vqabuf; VQAAudio *audio; VQAConfig *config; VQAHeader *header; unsigned char *driver_path; WORD port; long rc; /* Dereference data memebers for quicker access. */ config = &vqap->Config; header = &vqap->Header; vqabuf = vqap->VQABuf; audio = &vqabuf->Audio; /* Fail if no audio buffer or DigiCard is 0 (no sound) */ if ((audio->Buffer == NULL) || (config->DigiCard == 0)) { return (-1); } /* Reset the buffer position to the beginning. */ audio->CurBlock = 0; /* Initialize the HMI driver structure. */ audio->sSOSInitDriver.wAllocateBuffer = _TRUE; audio->sSOSInitDriver.wParam = 19; audio->sSOSInitDriver.wTimerID = _SOS_NORMAL_TIMER; /*------------------------------------------------------------------------- * Compute the playback rate: * * - If Config->AudioRate is -1, use HMI_DATA_RATE scaled to the specified * frame rate (so the audio plays faster if we're playing faster) * - otherwise, use the specified rate *-----------------------------------------------------------------------*/ if (config->AudioRate != -1) { audio->sSOSInitDriver.wSampleRate = config->AudioRate; } else if (config->FrameRate != header->FPS) { audio->sSOSInitDriver.wSampleRate = ((audio->SampleRate*config->FrameRate) / (unsigned long)header->FPS); config->AudioRate = audio->sSOSInitDriver.wSampleRate; } else { audio->sSOSInitDriver.wSampleRate = audio->SampleRate; config->AudioRate = audio->SampleRate; } /* If the application has already initialized HMI then set the * necessary variables. Otherwise we must setup HMI ourself. */ if ((config->OptionFlags & VQAOPTF_HMIINIT) || ((AudioFlags & VQAAUDF_DIGIINIT) != 0)) { /* The application MUST provide the card type! */ if (config->DigiCard == -1) { return (-1); } /* Init the detection system */ driver_path = (unsigned char *)".\\"; if ((rc = sosDIGIDetectInit(driver_path)) != 0) { return (rc); } /* Get the capabilities of the card being used. */ rc = sosDIGIDetectGetCaps(config->DigiCard, &audio->DigiCaps); sosDIGIDetectUnInit(); if (rc != 0) { return (rc); } audio->DigiHandle = config->DigiHandle; audio->Flags |= (HMI_APPINIT << VQAAUDB_TIMERINIT); audio->Flags |= (HMI_APPINIT << VQAAUDB_DIGIINIT); if ((AudioFlags & (VQAAUDF_TIMERINIT|VQAAUDF_DIGIINIT)) == 0) { HMIDevName = "App-Specific"; AudioFlags |= (HMI_APPINIT << VQAAUDB_TIMERINIT); AudioFlags |= (HMI_APPINIT << VQAAUDB_DIGIINIT); } } else { /* Init the detection system */ driver_path = (unsigned char *)".\\"; if ((rc = sosDIGIDetectInit(driver_path)) != 0) { return (rc); } /*----------------------------------------------------------------------- * Initialize DigiHardware with port, IRQ, and DMA, and make sure * Config.DigiCard contains the HMI ID we want to use: * * - If Config.DigiCard is -1, auto-detect the hardware; then, do a * FindHardware so the GetSettings will work * - If Config.DigiCard is filled in, but port, IRQ or DMA is -1, use * FindHardware & GetSettings to get the settings * - If all are filled in, just use them as they are *---------------------------------------------------------------------*/ /* Auto-Detect */ if (config->DigiCard == -1) { /* Version 1 VQA's have only 8 bit mono audio streams. */ if (header->Version == VQAHD_VER1) { config->DigiCard = AutoDetect(&audio->DigiCaps, 8, 1); } else { config->DigiCard = AutoDetect(&audio->DigiCaps, audio->BitsPerSample, audio->Channels); /* Resort to 8bit mono */ if (config->DigiCard == -1) { config->DigiCard = AutoDetect(&audio->DigiCaps, 8, 1); } } if (config->DigiCard == -1) { sosDIGIDetectUnInit(); return (-1); } } /* Do a FindHardware & GetSettings */ if (config->DigiPort == -1) { if (sosDIGIDetectFindHardware(config->DigiCard, &audio->DigiCaps, &port)) { sosDIGIDetectUnInit(); return (-1); } if (sosDIGIDetectGetSettings(&audio->DigiHardware)) { sosDIGIDetectUnInit(); return (-1); } config->DigiPort = audio->DigiHardware.wPort; config->DigiIRQ = audio->DigiHardware.wIRQ; config->DigiDMA = audio->DigiHardware.wDMA; HMIDevName = (char *)audio->DigiCaps.szDeviceName; } else { audio->DigiHardware.wPort = config->DigiPort; audio->DigiHardware.wIRQ = config->DigiIRQ; audio->DigiHardware.wDMA = config->DigiDMA; HMIDevName = "App-Specific"; } sosDIGIDetectUnInit(); /* Initialize the DIGI system & driver */ sosDIGIInitSystem(driver_path, _SOS_DEBUG_NORMAL); audio->sSOSInitDriver.wBufferSize = config->HMIBufSize; audio->sSOSInitDriver.lpBuffer = NULL; audio->sSOSInitDriver.lpFillHandler = NULL; audio->sSOSInitDriver.lpDriverMemory = NULL; audio->sSOSInitDriver.lpTimerMemory = NULL; audio->DigiHandle = -1; if ((rc = sosDIGIInitDriver(config->DigiCard, &audio->DigiHardware, &audio->sSOSInitDriver, &audio->DigiHandle)) != 0) { return (rc); } /*----------------------------------------------------------------------- * Register the timer event *---------------------------------------------------------------------*/ /* If the timer hasn't been init'd, do it now */ if ((AudioFlags & VQAAUDF_TIMERINIT) == (HMI_UNINIT<Flags |= (HMI_VQAINIT << VQAAUDB_TIMERINIT); AudioFlags |= (HMI_VQAINIT << VQAAUDB_TIMERINIT); } /* Register the event */ rc = sosTIMERRegisterEvent(VQA_TIMETICKS, audio->sSOSInitDriver.lpFillHandler, &audio->DigiTimer); if (rc) { sosDIGIUnInitDriver(audio->DigiHandle, _TRUE, _TRUE); sosDIGIUnInitSystem(); return (rc); } config->DigiHandle = audio->DigiHandle; audio->Flags |= (HMI_VQAINIT << VQAAUDB_DIGIINIT); AudioFlags |= (HMI_VQAINIT << VQAAUDB_DIGIINIT); } if ((audio->Flags & VQAAUDF_MODLOCKED) == 0) { DPMI_Lock(&StartAddr, (long)&EndAddr - (long)&StartAddr); audio->Flags |= VQAAUDF_MODLOCKED; } return (0); } /**************************************************************************** * * NAME * VQA_CloseAudio - Close sound system * * SYNOPSIS * VQA_CloseAudio() * * void VQA_CloseAudio(void); * * FUNCTION * Removes VQA's involvement in the audio system. * * INPUTS * NONE * * RESULT * NONE * ****************************************************************************/ void VQA_CloseAudio(VQAHandleP *vqap) { VQAAudio *audio; /* Dereference for quick access. */ audio = &vqap->VQABuf->Audio; /* Remove the Digi event */ if ((AudioFlags & VQAAUDF_DIGIINIT) == (HMI_VQAINIT<DigiTimer); } /* Un-init timer if necessary */ if ((AudioFlags & VQAAUDF_TIMERINIT) == (HMI_VQAINIT<Flags &= ~VQAAUDF_TIMERINIT; AudioFlags &= ~VQAAUDF_TIMERINIT; /* Remove the driver */ if ((audio->Flags & VQAAUDF_DIGIINIT) == (HMI_VQAINIT<DigiHandle, _TRUE, _TRUE); sosDIGIUnInitSystem(); } audio->Flags &= ~VQAAUDF_DIGIINIT; AudioFlags &= ~VQAAUDF_DIGIINIT; AudioFlags &= ~VQAAUDF_ISPLAYING; /* Unlock the memory accupied by this module. */ if ((audio->Flags & VQAAUDF_MODLOCKED) == 1) { DPMI_Unlock(&StartAddr, (long)&EndAddr - (long)&StartAddr); audio->Flags &= ~VQAAUDF_MODLOCKED; } } /**************************************************************************** * * NAME * AutoDetect - Auto detect the sound card. * * SYNOPSIS * CardID = AutoDetect(DigiCaps, BitSize, Channels) * * long AutoDetect(_SOS_CAPABILITIES *, long, long); * * FUNCTION * Autodetects the type of sound card present in the system. * * INPUTS * DigiCaps - Pointer to HMI digital card capabilities structure. * BitSize - Bits per sample size. * Channels - Number of desired channels. * * RESULT * CardID - HMI ID of the sound card found, -1 if none. * ****************************************************************************/ long AutoDetect(_SOS_CAPABILITIES *digicaps, long bitsize, long channels) { long device_id = -1; WORD port; long i; long rc; /* Search for an 8-bit mono device */ if (sosDIGIDetectFindFirst(digicaps, &port)) { return (-1); } channels--; i = 0; while (i < 6) { i++; if ((digicaps->wBitsPerSample == bitsize) && (digicaps->wChannels == channels)) { break; } if (sosDIGIDetectFindNext(digicaps, &port)) { return (-1); } } /* Exit if failed to find the required device */ if ((digicaps->wBitsPerSample != bitsize) || (digicaps->wChannels != channels)) { return (-1); } /* Stash the ID */ device_id = digicaps->wDeviceID; /* Now that we have handled the initial pass, verify that if we found an * _ADLIB_GOLD_8_MONO that it is not a Logitech Sound Wave man in disguise. */ if ((WORD)digicaps->wDeviceID == _ADLIB_GOLD_8_MONO) { rc = sosDIGIDetectFindNext(digicaps, &port); while ((i < 6) && (rc == 0)) { i++; if ((digicaps->wBitsPerSample == 8) && (digicaps->wChannels == 0)) { break; } if ((rc = sosDIGIDetectFindNext(digicaps, &port)) != 0) { break; } } /* If we don't have an error use the secondary device ID, after all, * anything's better than an Adlib Gold. If we do have an error or there * is nothing we can use then the device ID is already set to the adlib * gold so just let it rip. */ if ((rc == 0) && ((WORD)digicaps->wDeviceID == _SBPRO_8_MONO)) { return (digicaps->wDeviceID); } } return (device_id); } /**************************************************************************** * * NAME * VQA_StartAudio - Starts audio playback * * SYNOPSIS * Error = VQA_StartAudio(VQA) * * long VQA_StartAudio(VQAHandleP *); * * FUNCTION * Start the audio playback for the movie. * * INPUTS * VQA - Pointer to private VQA handle. * * RESULT * Error - 0 if successful, or -1 error code. * ****************************************************************************/ long VQA_StartAudio(VQAHandleP *vqap) { VQAConfig *config; VQAAudio *audio; /* Save buffers for the callback routine */ VQAP = vqap; /* Dereference commonly used data members for quicker access. */ config = &vqap->Config; audio = &vqap->VQABuf->Audio; /* Return if already playing */ if (AudioFlags & VQAAUDF_ISPLAYING) { return (-1); } /* Set my driver handle */ if (config->DigiHandle != -1) { audio->DigiHandle = config->DigiHandle; } if (audio->DigiHandle == (WORD)-1) { return (-1); } /*------------------------------------------------------------------------- * Initialize the sample structure. *-----------------------------------------------------------------------*/ memset(&audio->sSOSSampleData, 0, sizeof(_SOS_START_SAMPLE)); audio->sSOSSampleData.lpSamplePtr = (unsigned char *)audio->Buffer; audio->sSOSSampleData.dwSampleSize = config->HMIBufSize; audio->sSOSSampleData.wVolume = (config->Volume << 7); audio->sSOSSampleData.wSampleID = HMI_SAMPLE; audio->sSOSSampleData.lpCallback = AudioCallback; /* Set the channel flags for the type of data we have. */ if (audio->Channels == 2) { audio->sSOSSampleData.wChannel = _INTERLEAVED; } else { audio->sSOSSampleData.wChannel = _CENTER_CHANNEL; } /* If the card is unable to handle stereo data the we must notify the * sound system to convert the stereo data to mono data during playback. */ if ((audio->Channels - 1) > audio->DigiCaps.wChannels) { audio->sSOSSampleData.wSampleFlags |= _STEREOTOMONO; } /* If the card is unable to handle the sample size of the audio data * then we must notify the sound system to convert the audio data to * the proper format. */ if (audio->BitsPerSample != audio->DigiCaps.wBitsPerSample) { if (audio->BitsPerSample > audio->DigiCaps.wBitsPerSample) { audio->sSOSSampleData.wSampleFlags |= _TRANSLATE16TO8; } else { audio->sSOSSampleData.wSampleFlags |= _TRANSLATE8TO16; } } /* Adjust the pitch if the driver is setup to playback at a different * rate than what the sample was recorded at. */ if (audio->sSOSInitDriver.wSampleRate != audio->SampleRate) { ldiv_t result; result = ldiv(audio->SampleRate, audio->sSOSInitDriver.wSampleRate); audio->sSOSSampleData.dwSamplePitchAdd = (long)MAKE_LONG((short)result.quot, (short)(((long)result.rem * 0x10000L) / audio->sSOSInitDriver.wSampleRate)); audio->sSOSSampleData.wSampleFlags |= _PITCH_SHIFT; } /* Start playback */ audio->SampleHandle = sosDIGIStartSample(audio->DigiHandle, &audio->sSOSSampleData); audio->Flags |= VQAAUDF_ISPLAYING; AudioFlags |= VQAAUDF_ISPLAYING; return (0); } /**************************************************************************** * * NAME * VQA_StopAudio - Stop audio playback. * * SYNOPSIS * VQA_StopAudio(VQA) * * void VQA_StopAudio(VQAHandleP *); * * FUNCTION * Halts the currently playing audio stream. * * INPUTS * VQA - Pointer to private VQAHandle. * * RESULT * NONE * ****************************************************************************/ void VQA_StopAudio(VQAHandleP *vqap) { VQAAudio *audio; /* Dereference for quick access. */ audio = &vqap->VQABuf->Audio; /* Just return if not playing */ if (AudioFlags & VQAAUDF_ISPLAYING) { sosDIGIStopSample(audio->DigiHandle, audio->SampleHandle); audio->Flags &= ~VQAAUDF_ISPLAYING; AudioFlags &= ~VQAAUDF_ISPLAYING; } VQAP = NULL; } /**************************************************************************** * * NAME * CopyAudio - Copy data from Audio Temp buffer into Audio play buffer. * * SYNOPSIS * Error = CopyAudio(VQA) * * long CopyAudio(VQAHandleP *); * * FUNCTION * This routine just copies the data in the TempBuf into the correct * spots in the audio play buffer. If there is no room available in the * audio play buffer, the routine returns VQAERR_SLEEPING, which will put * the whole Loader to "sleep" while it waits for a free buffer. * * If there's no data in the TempBuf to copy, the routine just returns 0. * * INPUTS * VQA - Pointer to private VQAHandle structure. * * RESULT * Error - 0 if successful or VQAERR_??? error code. * ****************************************************************************/ long CopyAudio(VQAHandleP *vqap) { VQAAudio *audio; VQAConfig *config; long startblock; long endblock; long len1,len2; long i; /* Dereference commonly used data members for quicker access. */ audio = &vqap->VQABuf->Audio; config = &vqap->Config; /* If audio is disabled, or if we're playing from a VOC file, or if * there's no Audio Buffer, or if there's no data to copy, just return 0 */ #if(VQAVOC_ON && VQAAUDIO_ON) if (((config->OptionFlags & VQAOPTF_AUDIO) == 0) || (vqap->vocfh != -1) || (audio->Buffer == NULL) || (audio->TempBufLen == 0)) { #else /* VQAVOC_ON */ if (((config->OptionFlags & VQAOPTF_AUDIO) == 0) || (audio->Buffer == NULL) || (audio->TempBufLen == 0)) { #endif /* VQAVOC_ON */ return (0); } /* Compute start & end blocks to copy into */ startblock = (audio->AudBufPos / config->HMIBufSize); endblock = (audio->AudBufPos + audio->TempBufLen) / config->HMIBufSize; if (endblock >= audio->NumAudBlocks) { endblock -= audio->NumAudBlocks; } /* If 'endblock' hasn't played yet, return VQAERR_SLEEPING */ if (audio->IsLoaded[endblock] == 1) { return (VQAERR_SLEEPING); } /* Copy the data: * * - If 'startblock' < 'endblock', copy the entire buffer * - Otherwise, fill to the end of the buffer with part of the data, then * copy the rest to the beginning of the buffer */ if (startblock <= endblock) { /* Copy data */ memcpy((audio->Buffer + audio->AudBufPos), audio->TempBuf, audio->TempBufLen); /* Adjust current load position */ audio->AudBufPos += audio->TempBufLen; /* Mark buffer as empty */ audio->TempBufLen = 0; /* Set all blocks to loaded */ for (i = startblock; i < endblock; i++) { audio->IsLoaded[i] = 1; } return (0); } else { /* Compute length of each piece */ len1 = config->AudioBufSize - audio->AudBufPos; len2 = audio->TempBufLen - len1; /* Copy 1st piece into end of Audio Buffer */ memcpy((audio->Buffer + audio->AudBufPos), audio->TempBuf, len1); /* Copy 2nd piece into start of Audio Buffer */ memcpy(audio->Buffer, audio->TempBuf + len1, len2); /* Adjust load position */ audio->AudBufPos = len2; /* Mark buffer as empty */ audio->TempBufLen = 0; /* Set blocks to loaded */ for (i = startblock; i < audio->NumAudBlocks; i++) { audio->IsLoaded[i] = 1; } for (i = 0; i < endblock; i++) { audio->IsLoaded[i] = 1; } return (0); } } /**************************************************************************** * * NAME * AudioCallback - Sound system callback. * * SYNOPSIS * AudioCallback(DriverHandle, Action, SampleID) * * void AudioCallback(WORD, WORD, WORD); * * FUNCTION * Our custom audio callback routine that services HMI. * * INPUTS * DriverHandle - HMI driver handle. * Action - Action taken. * SampleID - ID of sample. * * RESULT * NONE * ****************************************************************************/ void far __cdecl AudioCallback(WORD wDriverHandle,WORD wAction,WORD wSampleID) { VQAAudio *audio; VQAConfig *config; /* Dereference commonly used data members for quicker access. */ audio = &VQAP->VQABuf->Audio; config = &VQAP->Config; /* Suppress compiler warnings */ wDriverHandle = wDriverHandle; wSampleID = wSampleID; /* See if we're called because the buffer is empty */ if (wAction == _SAMPLE_PROCESSED) { /* Compute the 'NextBlock' index */ audio->NextBlock = audio->CurBlock + 1; if (audio->NextBlock >= audio->NumAudBlocks) { audio->NextBlock = 0; } /* See if the next block has data in it; if so, update the audio * buffer play position & the 'CurBlock' value. * If not, don't change anything and replay this block. */ if (audio->IsLoaded[audio->NextBlock] == 1) { /* Update this block's status to loadable (0) */ audio->IsLoaded[audio->CurBlock] = 0; /* Update position within audio buffer */ audio->PlayPosition += config->HMIBufSize; audio->CurBlock++; if (audio->PlayPosition >= config->AudioBufSize) { audio->PlayPosition = 0; audio->CurBlock = 0; } } else { audio->NumSkipped++; } /* Start the new buffer playing */ audio->sSOSSampleData.lpSamplePtr = (unsigned char *)(audio->Buffer) + audio->PlayPosition; sosDIGIContinueSample(audio->DigiHandle, audio->SampleHandle, &audio->sSOSSampleData); audio->SamplesPlayed += config->HMIBufSize; } } /* Dummy function used to mark the beginning address of the file. */ static void EndAddr(void) { } #else //!VQADIRECT_SOUND /************************************************************************** * * Start of Direct Sound code * * * The direct sound implementation works by taking what would have been the * HMI sound buffer and feeding the contents to a direct sound secondary * buffer. * * Steve T. - 12/15/95 * * * * ***************************************************************************/ BOOL SuspendAudioCallback = FALSE; /**************************************************************************** * * NAME * VQA_StartTimerInt - Initialize system timer interrupt. * * SYNOPSIS * Error = VQA_StartTimerInt(VQA, Init) * * long VQA_StartTimerInt(VQAHandeP *, long); * * FUNCTION * Initialize the HMI timer system and add our own timer event. If the * system has already been initialized then we are given access to the * the timer system. * * INPUTS * VQA - Pointer to private VQAHandle structure. * Init - Initialize HMI timer system flag. (TRUE = Initialize) * * RESULT * Error - 0 if successful, -1 if error. * ****************************************************************************/ long VQA_StartTimerInt(VQAHandleP *vqap, long init) { VQAAudio *audio; /* Dereference for quick access. */ audio = &vqap->VQABuf->Audio; /* Register the VQA_TickCount timer event. */ if ((AudioFlags & VQAAUDF_HMITIMER) == (HMI_UNINIT<Flags |= (HMI_VQAINIT << VQAAUDB_HMITIMER); /* Increment the timer interrupt usage count. */ TimerIntCount++; return (0); } /**************************************************************************** * * NAME * VQA_StopTimerInt - Remove system timer interrupt. * * SYNOPSIS * VQA_StopTimerInt() * * void VQA_StopTimerInt(void); * * FUNCTION * Remove our timer event from the HMI timer system. Uninitialize the * HMI timer system if we initialized it. * * INPUTS * NONE * * RESULT * NONE * ****************************************************************************/ void VQA_StopTimerInt(VQAHandleP *vqap) { VQAAudio *audio; /* Dereference for quick access. */ audio = &vqap->VQABuf->Audio; /* Decrement the timer interrupt usage count. */ if (TimerIntCount) { TimerIntCount--; } /* Remove the timer interrrupt if it is initialized and the use count is * zero. Otherwise, clear the callers timer interrupt availability flag. */ if (((AudioFlags & VQAAUDF_HMITIMER) == (HMI_VQAINIT<Config; header = &vqap->Header; vqabuf = vqap->VQABuf; audio = &vqabuf->Audio; /* Reset the buffer position to the beginning. */ audio->CurBlock = 0; /* ** create the direct sound object if it doesnt already exist */ if (!config->SoundObject){ if ( DirectSoundCreate (NULL,&config->SoundObject,NULL) !=DS_OK ) { return (-1); }else{ audio->CreatedSoundObject = TRUE; /* ** Give ourselves exclusive access to the sound card */ if ( config->SoundObject->SetCooperativeLevel( window, DSSCL_EXCLUSIVE ) != DS_OK){ config->SoundObject->Release(); return (-1); } } if (!config->PrimaryBufferPtr){ /* ** Define the format of the primary sound buffer */ memset (&audio->BufferDesc , 0 , sizeof(DSBUFFERDESC)); audio->BufferDesc.dwSize=sizeof(DSBUFFERDESC); audio->BufferDesc.dwFlags=DSBCAPS_PRIMARYBUFFER | DSBCAPS_CTRLVOLUME; memset (&audio->DsBuffFormat , 0 , sizeof(WAVEFORMATEX)); audio->DsBuffFormat.wFormatTag = WAVE_FORMAT_PCM; audio->DsBuffFormat.nChannels = (unsigned short) 2; /*------------------------------------------------------------------------- * Compute the playback rate: * * - If Config->AudioRate is -1, use HMI_DATA_RATE scaled to the specified * frame rate (so the audio plays faster if we're playing faster) * - otherwise, use the specified rate *-----------------------------------------------------------------------*/ if (config->AudioRate != -1) { audio->DsBuffFormat.nSamplesPerSec = config->AudioRate; } else if (config->FrameRate != header->FPS) { audio->DsBuffFormat.nSamplesPerSec = ((audio->SampleRate*config->FrameRate) / (unsigned long)header->FPS); config->AudioRate = audio->DsBuffFormat.nSamplesPerSec; } else { audio->DsBuffFormat.nSamplesPerSec = audio->SampleRate; config->AudioRate = audio->SampleRate; } audio->DsBuffFormat.wBitsPerSample = (short) 16; audio->DsBuffFormat.nBlockAlign = (unsigned short)( (audio->DsBuffFormat.wBitsPerSample/8) * audio->DsBuffFormat.nChannels); audio->DsBuffFormat.nAvgBytesPerSec= audio->DsBuffFormat.nSamplesPerSec * audio->DsBuffFormat.nBlockAlign; audio->DsBuffFormat.cbSize = 0; /* ** Create the direct sound primary sound buffer object */ if ( config->SoundObject->CreateSoundBuffer (&audio->BufferDesc , &config->PrimaryBufferPtr , NULL ) !=DS_OK ){ if (audio->CreatedSoundObject){ config->SoundObject->Release(); audio->CreatedSoundObject = FALSE; } return (-1); } audio->CreatedSoundBuffer = TRUE; } } audio->Flags |= (HMI_VQAINIT << VQAAUDB_DIGIINIT); AudioFlags |= (HMI_VQAINIT << VQAAUDB_DIGIINIT); return (0); } /**************************************************************************** * * NAME * VQA_CloseAudio - Close sound system * * SYNOPSIS * VQA_CloseAudio() * * void VQA_CloseAudio(void); * * FUNCTION * Removes VQA's involvement in the audio system. * * INPUTS * NONE * * RESULT * NONE * ****************************************************************************/ void VQA_CloseAudio(VQAHandleP *vqap) { VQAAudio *audio; VQAConfig *config; /* Dereference for quick access. */ audio = &vqap->VQABuf->Audio; config = &vqap->Config; /* ** If the audio is still playing then stop it */ VQA_StopAudio(vqap); /* Remove the windows callback event */ //if ((AudioFlags & VQAAUDF_DIGIINIT) == (HMI_VQAINIT<TimerHandle); //} audio->Flags &= ~VQAAUDF_TIMERINIT; AudioFlags &= ~VQAAUDF_TIMERINIT; /* ** Remove the direct sound primary buffer if we created it */ if (audio->CreatedSoundBuffer){ config->PrimaryBufferPtr->Stop(); config->PrimaryBufferPtr->Release(); config->PrimaryBufferPtr = NULL; audio->CreatedSoundBuffer = FALSE; } /* ** If we created the sound object then remove that as well */ if (audio->CreatedSoundObject){ config->SoundObject->Release(); config->SoundObject = NULL; } audio->Flags &= ~VQAAUDF_DIGIINIT; AudioFlags &= ~VQAAUDF_DIGIINIT; AudioFlags &= ~VQAAUDF_ISPLAYING; } /**************************************************************************** * * NAME * VQA_StartAudio - Starts audio playback * * SYNOPSIS * Error = VQA_StartAudio(VQA) * * long VQA_StartAudio(VQAHandleP *); * * FUNCTION * Start the audio playback for the movie. * * INPUTS * VQA - Pointer to private VQA handle. * * RESULT * Error - 0 if successful, or -1 error code. * ****************************************************************************/ long VQA_StartAudio(VQAHandleP *vqap) { VQAConfig *config; VQAAudio *audio; /* Save buffers for the callback routine */ VQAP = vqap; /* Dereference commonly used data members for quicker access. */ config = &vqap->Config; audio = &vqap->VQABuf->Audio; /* Return if already playing */ if (AudioFlags & VQAAUDF_ISPLAYING) { return (-1); } /* ** If we already have a direct sound secondary buffer then get rid of it */ if (audio->SecondaryBufferPtr != NULL){ audio->SecondaryBufferPtr->Stop(); audio->SecondaryBufferPtr->Release(); audio->SecondaryBufferPtr = NULL; } /* ** Make it big enough for 4 blocks of HMI data */ audio->SecondaryBufferSize = config->HMIBufSize*4; /* ** Define the format for the secondary sound buffer */ memset (&audio->BufferDesc , 0 , sizeof(DSBUFFERDESC)); audio->BufferDesc.dwSize = sizeof(DSBUFFERDESC); audio->BufferDesc.dwFlags = DSBCAPS_CTRLVOLUME; audio->BufferDesc.dwBufferBytes = audio->SecondaryBufferSize; audio->BufferDesc.lpwfxFormat = (LPWAVEFORMATEX) &audio->DsBuffFormat; memset (&audio->DsBuffFormat , 0 , sizeof(WAVEFORMATEX)); audio->DsBuffFormat.wFormatTag = WAVE_FORMAT_PCM; audio->DsBuffFormat.nSamplesPerSec = audio->SampleRate; audio->DsBuffFormat.nChannels = audio->Channels; audio->DsBuffFormat.wBitsPerSample = audio->BitsPerSample; audio->DsBuffFormat.nBlockAlign = (short) ((audio->DsBuffFormat.wBitsPerSample/8) * audio->DsBuffFormat.nChannels); audio->DsBuffFormat.nAvgBytesPerSec = audio->DsBuffFormat.nSamplesPerSec * audio->DsBuffFormat.nBlockAlign; /* ** Create the secondary sound buffer object */ config->SoundObject->CreateSoundBuffer (&audio->BufferDesc , &audio->SecondaryBufferPtr , NULL); /* ** Set the format of the primary buffer to the same as the secondary buffer */ config->PrimaryBufferPtr->Stop(); config->PrimaryBufferPtr->SetFormat (&audio->DsBuffFormat); if (config->PrimaryBufferPtr->Play(0, 0, DSBPLAY_LOOPING) != DS_OK){ return (-1); } /* Start playback */ audio->EndLastAudioChunk = 0; audio->ChunksMovedToAudioBuffer = 0; Move_HMI_Audio_Block_To_Direct_Sound_Buffer(); Move_HMI_Audio_Block_To_Direct_Sound_Buffer(); audio->SecondaryBufferPtr->SetCurrentPosition (0); if (audio->SecondaryBufferPtr->Play(0, 0, DSBPLAY_LOOPING) != DS_OK){ return (-1); } /* ** Set the volume */ long volume = config->Volume << 7; audio->SecondaryBufferPtr->SetVolume(- ( ( (32768 - volume)*1000) >>15 ) ); // Set orf 60hz timer audio->TimerHandle = timeSetEvent ( 1000/60 , 1 , AudioCallback , 0 , TIME_PERIODIC); audio->Flags |= VQAAUDF_ISPLAYING; AudioFlags |= VQAAUDF_ISPLAYING; return (0); } /**************************************************************************** * * NAME * VQA_StopAudio - Stop audio playback. * * SYNOPSIS * VQA_StopAudio(VQA) * * void VQA_StopAudio(VQAHandleP *); * * FUNCTION * Halts the currently playing audio stream. * * INPUTS * VQA - Pointer to private VQAHandle. * * RESULT * NONE * ****************************************************************************/ void VQA_StopAudio(VQAHandleP *vqap) { VQAAudio *audio; VQAConfig *config; /* Dereference commonly used data members for quicker access. */ config = &vqap->Config; audio = &vqap->VQABuf->Audio; /* Just return if not playing */ if (AudioFlags & VQAAUDF_ISPLAYING) { /* ** Stop the primary buffer so the audio doesnt glitch */ config->PrimaryBufferPtr->Stop(); /* ** Remove the windows timer event */ timeKillEvent(audio->TimerHandle); audio->TimerHandle = NULL; /* ** Kill the secondary sound buffer */ if (audio->SecondaryBufferPtr){ audio->SecondaryBufferPtr->Stop(); audio->SecondaryBufferPtr->Release(); audio->SecondaryBufferPtr = NULL; } audio->Flags &= ~VQAAUDF_ISPLAYING; AudioFlags &= ~VQAAUDF_ISPLAYING; } VQAP = NULL; } /**************************************************************************** * * NAME * CopyAudio - Copy data from Audio Temp buffer into Audio play buffer. * * SYNOPSIS * Error = CopyAudio(VQA) * * long CopyAudio(VQAHandleP *); * * FUNCTION * This routine just copies the data in the TempBuf into the correct * spots in the audio play buffer. If there is no room available in the * audio play buffer, the routine returns VQAERR_SLEEPING, which will put * the whole Loader to "sleep" while it waits for a free buffer. * * If there's no data in the TempBuf to copy, the routine just returns 0. * * INPUTS * VQA - Pointer to private VQAHandle structure. * * RESULT * Error - 0 if successful or VQAERR_??? error code. * ****************************************************************************/ long CopyAudio(VQAHandleP *vqap) { VQAAudio *audio; VQAConfig *config; long startblock; long endblock; long len1,len2; long i; /* Dereference commonly used data members for quicker access. */ audio = &vqap->VQABuf->Audio; config = &vqap->Config; /* If audio is disabled, or if we're playing from a VOC file, or if * there's no Audio Buffer, or if there's no data to copy, just return 0 */ #if(VQAVOC_ON && VQAAUDIO_ON) if (((config->OptionFlags & VQAOPTF_AUDIO) == 0) || (vqap->vocfh != -1) || (audio->Buffer == NULL) || (audio->TempBufLen == 0)) { #else /* VQAVOC_ON */ if (((config->OptionFlags & VQAOPTF_AUDIO) == 0) || (audio->Buffer == NULL) || (audio->TempBufLen == 0)) { #endif /* VQAVOC_ON */ return (0); } /* Compute start & end blocks to copy into */ startblock = (audio->AudBufPos / config->HMIBufSize); endblock = (audio->AudBufPos + audio->TempBufLen) / config->HMIBufSize; if (endblock >= audio->NumAudBlocks) { endblock -= audio->NumAudBlocks; } /* If 'endblock' hasn't played yet, return VQAERR_SLEEPING */ if (audio->IsLoaded[endblock] == 1) { return (VQAERR_SLEEPING); } /* Copy the data: * * - If 'startblock' < 'endblock', copy the entire buffer * - Otherwise, fill to the end of the buffer with part of the data, then * copy the rest to the beginning of the buffer */ if (startblock <= endblock) { /* Copy data */ memcpy((audio->Buffer + audio->AudBufPos), audio->TempBuf, audio->TempBufLen); /* Adjust current load position */ audio->AudBufPos += audio->TempBufLen; /* Mark buffer as empty */ audio->TempBufLen = 0; /* Set all blocks to loaded */ for (i = startblock; i < endblock; i++) { audio->IsLoaded[i] = 1; } return (0); } else { /* Compute length of each piece */ len1 = config->AudioBufSize - audio->AudBufPos; len2 = audio->TempBufLen - len1; /* Copy 1st piece into end of Audio Buffer */ memcpy((audio->Buffer + audio->AudBufPos), audio->TempBuf, len1); /* Copy 2nd piece into start of Audio Buffer */ memcpy(audio->Buffer, audio->TempBuf + len1, len2); /* Adjust load position */ audio->AudBufPos = len2; /* Mark buffer as empty */ audio->TempBufLen = 0; /* Set blocks to loaded */ for (i = startblock; i < audio->NumAudBlocks; i++) { audio->IsLoaded[i] = 1; } for (i = 0; i < endblock; i++) { audio->IsLoaded[i] = 1; } return (0); } } BOOL VQAAudioPaused = FALSE; void VQA_PauseAudio(void) { VQAAudio *audio; if (VQAP && VQAP->VQABuf){ audio = &VQAP->VQABuf->Audio; if (audio->SecondaryBufferPtr){ if (AudioFlags & VQAAUDF_ISPLAYING && !VQAAudioPaused) { audio->SecondaryBufferPtr->Stop(); VQAAudioPaused = TRUE; } } } } void VQA_ResumeAudio(void) { VQAAudio *audio; if (VQAP && VQAP->VQABuf){ audio = &VQAP->VQABuf->Audio; if (audio->SecondaryBufferPtr){ if (AudioFlags & VQAAUDF_ISPLAYING && VQAAudioPaused) { audio->SecondaryBufferPtr->SetCurrentPosition (0); audio->LastChunkPosition = 0; audio->EndLastAudioChunk = 0; Move_HMI_Audio_Block_To_Direct_Sound_Buffer(); audio->SecondaryBufferPtr->Play(0,0,DSBPLAY_LOOPING); VQAAudioPaused = FALSE; } } } } /**************************************************************************** * * NAME * AudioCallback - Sound system callback. * * SYNOPSIS * AudioCallback(DriverHandle, Action, SampleID) * * void AudioCallback(WORD, WORD, WORD); * * FUNCTION * Our custom audio callback routine that services HMI. * * INPUTS * DriverHandle - HMI driver handle. * Action - Action taken. * SampleID - ID of sample. * * RESULT * NONE * ****************************************************************************/ void CALLBACK AudioCallback ( UINT, UINT, DWORD, DWORD, DWORD ) { VQAAudio *audio; VQAConfig *config; DWORD play_cursor; //Position that direct sound is reading from DWORD write_cursor; //Position in buffer that we can write to HRESULT return_code; BOOL buffer_stopped = FALSE; DWORD status; if (SuspendAudioCallback || VQAAudioPaused) return; /* Dereference commonly used data members for quicker access. */ audio = &VQAP->VQABuf->Audio; config = &VQAP->Config; if (!audio->SecondaryBufferPtr) return; /* ** See if we are nearing the end of the meaningful data in the direct sound buffer */ return_code = audio->SecondaryBufferPtr->GetCurrentPosition (&play_cursor , &write_cursor); BOOL write_more = FALSE; if (return_code == DSERR_BUFFERLOST || config->PrimaryBufferPtr->GetStatus(&status) == DSERR_BUFFERLOST){ config->PrimaryBufferPtr->Restore(); audio->SecondaryBufferPtr->Restore(); audio->SecondaryBufferPtr->Stop(); buffer_stopped = TRUE; audio->SecondaryBufferPtr->SetCurrentPosition (0); audio->LastChunkPosition = 0; audio->EndLastAudioChunk = 0; write_more = TRUE; } if (play_cursor < audio->EndLastAudioChunk){ if (audio->EndLastAudioChunk - play_cursor <= audio->SecondaryBufferSize/4){ write_more = TRUE; } }else{ if (( play_cursor > audio->SecondaryBufferSize*3/4) && audio->EndLastAudioChunk==0 ){ write_more = TRUE; } } /* ** See if we need to fill the buffer */ if (write_more){ if (Move_HMI_Audio_Block_To_Direct_Sound_Buffer()){ /* ** Start the buffer playing again if we had to stop it */ if (buffer_stopped){ audio->SecondaryBufferPtr->Play(0,0,DSBPLAY_LOOPING); } } } } /*********************************************************************************************** * Move_HMI_Audio_Block_To_Direct_Sound_Buffer -- moves an audio block which would have been * * played by HMI into a direct sound * * secondary buffer * * * * INPUT: Nothing * * * * OUTPUT: BOOL was block moved * * * * WARNINGS: None * * * * HISTORY: * * 12/21/95 9:51AM ST : Created * *=============================================================================================*/ extern int VQAMovieDone; BOOL Move_HMI_Audio_Block_To_Direct_Sound_Buffer (void) { VQAAudio *audio; VQAConfig *config; LPVOID play_buffer_ptr; //Beginning of locked area of buffer LPVOID dummy_buffer_ptr; //Length of locked area in buffer DWORD lock_length1; //Beginning of second locked area in buffer DWORD lock_length2; //Length of second locked area in buffer unsigned next_fill_pos; HRESULT return_code; DWORD status; /* Dereference commonly used data members for quicker access. */ audio = &VQAP->VQABuf->Audio; config = &VQAP->Config; /************************************************************************* ** ** Copy the data from the HMI play position into the direct sound buffer ** */ next_fill_pos = audio->EndLastAudioChunk; /* ** Lock the buffer to get a pointer to it */ return_code= audio->SecondaryBufferPtr->Lock ((DWORD)next_fill_pos, (DWORD)config->HMIBufSize, &play_buffer_ptr, &lock_length1, &dummy_buffer_ptr, &lock_length2, 0 ); if (return_code!=DS_OK) return(FALSE); if (config->PrimaryBufferPtr->GetStatus(&status) == DSERR_BUFFERLOST) return(FALSE); /* ** Copy the HMI audio buffer to the direct sound buffer */ memcpy ((char*)play_buffer_ptr , (char*)((unsigned)audio->Buffer + (unsigned)audio->PlayPosition) , config->HMIBufSize); /* ** Unlock the direct sound buffer */ audio->SecondaryBufferPtr->Unlock(play_buffer_ptr, lock_length1, dummy_buffer_ptr, lock_length2); /* ** Update our audio data pointers */ audio->LastChunkPosition = next_fill_pos; audio->EndLastAudioChunk = next_fill_pos + config->HMIBufSize; if (audio->EndLastAudioChunk >= audio->SecondaryBufferSize){ audio->EndLastAudioChunk = 0; } /* Compute the 'NextBlock' index */ audio->NextBlock = audio->CurBlock + 1; if (audio->NextBlock >= audio->NumAudBlocks) { audio->NextBlock = 0; } /* See if the next block has data in it; if so, update the audio * buffer play position & the 'CurBlock' value. * If not, don't change anything and replay this block. */ if (audio->IsLoaded[audio->NextBlock] == 1) { /* Update this block's status to loadable (0) */ audio->IsLoaded[audio->CurBlock] = 0; /* Update position within audio buffer */ audio->PlayPosition += config->HMIBufSize; audio->CurBlock++; if (audio->PlayPosition >= config->AudioBufSize) { audio->PlayPosition = 0; audio->CurBlock = 0; } audio->ChunksMovedToAudioBuffer++; return (TRUE); } else { if (VQAMovieDone){ audio->ChunksMovedToAudioBuffer++; } audio->NumSkipped++; /* ** Enable frame skipping to prevent this happening again */ config->DrawFlags &= ~VQACFGF_NOSKIP; return (FALSE); } } #endif //!VQADIRECT_SOUND #endif /* VQAAUDIO_ON */ /**************************************************************************** * * NAME * VQA_SetTimer - Resets current time to given tick value. * * SYNOPSIS * VQA_SetTimer(Time, Method) * * void VQA_SetTimer(long, long); * * FUNCTION * Sets 'TickOffset' to a value that will make the current time look like * the time passed in. This function allows the player to be "paused", * by recording the time of the pause, and then setting the timer to * that time. The timer method used by the player is also set. The method * selected is not neccesarily the method that will be used because some * timer methods work with only certain playback conditions. (EX: The * audio DMA timer method cannot be used if there is not any audio * playing.) * * INPUTS * Time - Value to set current time to. * Method - Timer method to use. * * RESULT * NONE * ****************************************************************************/ void VQA_SetTimer(VQAHandleP *vqap, long time, long method) { unsigned long curtime; #if(VQAAUDIO_ON) VQAAudio *audio; /* Dereference for quick access. */ audio = &vqap->VQABuf->Audio; /* If the client does not have a preferencee then pick a method * based on the state of the player. */ if (method == VQA_TMETHOD_DEFAULT) { /* If we are playing audio, use the audio DMA position. */ if (AudioFlags & VQAAUDF_ISPLAYING) { method = VQA_TMETHOD_AUDIO; } /* Otherwise use the HMI timer if it is initialized. */ else if (AudioFlags & VQAAUDF_HMITIMER) { method = VQA_TMETHOD_INT; } /* If all else fails resort the the "jerky" DOS time. */ else { method = VQA_TMETHOD_DOS; } } else { /* We cannot use the DMA position if there isn't any audio playing. */ if (!(AudioFlags & VQAAUDF_ISPLAYING) && (method == VQA_TMETHOD_AUDIO)) { method = VQA_TMETHOD_INT; } /* We cannot use the timer if it has not been initialized. */ if (!(AudioFlags & VQAAUDF_HMITIMER) && (method == VQA_TMETHOD_INT)) { method = VQA_TMETHOD_DOS; } } TimerMethod = method; #else method = method; #endif TickOffset = 0L; curtime = VQA_GetTime(vqap); TickOffset = (time - curtime); } /**************************************************************************** * * NAME * VQA_GetTime - Return current time. * * SYNOPSIS * Time = VQA_GetTime() * * unsigned long VQA_GetTime(void); * * FUNCTION * This routine returns timer ticks computed one of 3 ways: * * 1) If audio is playing, the timer is based on the DMA buffer position: * Compute the number of audio samples that have actually been played. * The following internal HMI variables are used: * * _lpSOSDMAFillCount[drv_handle]: current DMA buffer position * _lpSOSSampleList[drv_handle][samp_handle]: * sampleTotalBytes: total bytes sent by HMI to the DMA buffer * sampleLastFill: HMI's last fill position in DMA buffer * * So, the number of samples actually played is: * * sampleTotalBytes - * where is how far ahead sampleLastFill is in front of * _lpSOSDMAFillCount: (sampleLastFill - _lpSOSDMAFillCount) * * These values are indices into a circular DMA buffer, so: * * if (sampleLastFill >= _lpSOSDMAFillCount) * = sampleLastFill - _lpSOSDMAFillCount * else * = (DMA_BUF_SIZE - lpSOSDMAFillCount) + sampleLastFill * * Note that, if using the stereo driver with mono data, you must * divide LastFill & FillCount by 2, but not TotalBytes. If using the * stereo driver with stereo data, you must divide all 3 variables * by 2. * * 2) If no audio is playing, but the timer interrupt is running, * VQATickCount is used as the timer * * 3) If no audio is playing & no timer interrupt is going, the DOS 18.2 * system timer is used. * * Regardless of the method, TickOffset is used as an offset from the * computed time. * * INPUTS * NONE * * RESULT * Time - Time in VQA_TIMETICKS * ****************************************************************************/ unsigned long VQA_GetTime(VQAHandleP *vqap) { #if(VQAAUDIO_ON) VQAAudio *audio; VQAConfig *config; unsigned long fillcount; unsigned long lastfill; unsigned long dma_diff; unsigned long totalbytes; unsigned long samples; DWORD play_cursor; //Position that direct sound is reading from DWORD write_cursor; //Position in buffer that we can write to unsigned temp; #endif // MEG 09.25.95 - changed from long to unsigned long unsigned long ticks; #if(VQAAUDIO_ON) switch (TimerMethod) { /* If Audio is playing then timing is based on the audio DMA buffer * position. */ case VQA_TMETHOD_AUDIO: /* Dereference commonly used data members for quicker access. */ audio = &vqap->VQABuf->Audio; config = &vqap->Config; //vqabuf = ((VQAHandleP *)vqa)->VQABuf; #if (VQADIRECT_SOUND) totalbytes = (audio->ChunksMovedToAudioBuffer) * config->HMIBufSize; if (audio->SecondaryBufferPtr && audio->SecondaryBufferPtr->GetCurrentPosition (&play_cursor , &write_cursor) == DS_OK){ totalbytes = (audio->ChunksMovedToAudioBuffer) * config->HMIBufSize; if (audio->LastChunkPosition){ totalbytes += play_cursor - audio->LastChunkPosition; }else { if (play_cursor > audio->SecondaryBufferSize*3/4){ totalbytes -= audio->SecondaryBufferSize - play_cursor; } else { totalbytes += play_cursor - audio->LastChunkPosition; } } }else{ totalbytes = (audio->ChunksMovedToAudioBuffer-1) * config->HMIBufSize; } samples = totalbytes/audio->DsBuffFormat.nChannels; samples = samples/(audio->DsBuffFormat.wBitsPerSample >> 3); #else //VQADIRECT_SOUND /* Compute our current position in the audio track by getting the * bytes processed by HMI. Then adjust the bytes processed by the * position of the DMA fill handler, this should tell us exactly * where we are in the audio track. */ fillcount = (unsigned long)(*_lpSOSDMAFillCount[audio->DigiHandle]); lastfill = _lpSOSSampleList[audio->DigiHandle][audio->SampleHandle]->sampleLastFill; totalbytes = _lpSOSSampleList[audio->DigiHandle][audio->SampleHandle]->sampleTotalBytes; if (totalbytes == 0) { dma_diff = 0; } else { if (lastfill > fillcount) { dma_diff = lastfill - fillcount; } else { dma_diff = (config->HMIBufSize - fillcount) + lastfill; } if (dma_diff > totalbytes) { dma_diff = totalbytes; } } /* Calculate the number of samples by taking the total number of * bytes processed and divide it by the number of channels and * bits per sample. */ samples = totalbytes - dma_diff; samples -= (audio->NumSkipped * config->HMIBufSize); samples /= (audio->Channels * (audio->BitsPerSample >> 3)); #endif //VQADIRECT_SOUND /* The elapsed ticks is calculated by the number of samples * processed times the tick resolution per second divided by the * sample rate. */ ticks = (long)((samples * VQA_TIMETICKS) / audio->SampleRate); ticks += TickOffset; break; /* No audio playing, but timer interrupt is going; use VQATickCount */ case VQA_TMETHOD_INT: ticks = (VQATickCount + TickOffset); break; /* No interrupts are going at all; use DOS's time */ default: case VQA_TMETHOD_DOS: { struct timeb mytime; ftime(&mytime); ticks = (unsigned long)mytime.time*1000L+(unsigned long)mytime.millitm; ticks = ((ticks * VQA_TIMETICKS) / 1000L); ticks += TickOffset; } break; } #else { // MEG 09.23.95 - Use Windows timer. #if( ! USE_WINDOWS_TIME ) struct timeb mytime; ftime(&mytime); ticks = (unsigned long)mytime.time*1000L+(unsigned long)mytime.millitm; #else ticks = Get_Game_Time(); #endif // ticks = ((ticks * VQA_TIMETICKS) / 1000L); // MEG 09.25.95 - multiply by 3, divide by 50 instead of multiplying by // 60 and dividing by 1000. The reason for this is that multiplying by // 60 causes an overflow pretty quickly if the value from Get_Game_Time // is high. ticks = ((ticks * 3L) / 50L); ticks += TickOffset; } #endif return (ticks); } /**************************************************************************** * * NAME * VQA_TimerMethod - Get timer method being used. * * SYNOPSIS * Method = VQA_TimerMethod() * * long VQA_TimerMethod(void); * * FUNCTION * Returns the ID of the current timer method being used. * * INPUTS * NONE * * RESULT * Method - Method used for the timer. * ****************************************************************************/ long VQA_TimerMethod(void) { #if(VQAAUDIO_ON) return (TimerMethod); #else return (VQA_TMETHOD_DOS); #endif } #ifdef __WATCOMC__ #pragma pack(1); #endif