/*************************************************************************** * * dsp.c * * Copyright (c) 1991-1996 Microsoft Corporation. All Rights Reserved. * * This code provides VDD support for SB 2.0 sound output, specifically: * DSP 2.01+ (excluding SB-MIDI port) * ***************************************************************************/ /***************************************************************************** * * #includes * *****************************************************************************/ #include // The VDD is a win32 DLL #include // Multi-media APIs #include // Definition of VDD calls #include #include /***************************************************************************** * * Globals * *****************************************************************************/ // // Definitions for MM api entry points. The functions will be linked // dynamically to avoid bringing winmm.dll in before wow32. // extern SETVOLUMEPROC SetVolumeProc; extern GETNUMDEVSPROC GetNumDevsProc; extern GETDEVCAPSPROC GetDevCapsProc; extern OPENPROC OpenProc; extern RESETPROC ResetProc; extern CLOSEPROC CloseProc; extern GETPOSITIONPROC GetPositionProc; extern WRITEPROC WriteProc; extern PREPAREHEADERPROC PrepareHeaderProc; extern UNPREPAREHEADERPROC UnprepareHeaderProc; /* * General globals */ extern HINSTANCE GlobalHInstance; // handle passed to dll entry point BYTE IdentByte; // used with DSP_CARD_IDENTIFY BOOL SpeakerOn = FALSE; // TRUE when speaker is on BYTE ReservedRegister; // used with DSP_LOAD_RES_REG and DSP_READ_RES_REG ULONG PageSize; // size of pages for VirtualAlloc ULONG iHdr; // used to index wavehdr array /* * Event Globals */ HANDLE SingleWaveSem; // used by app to indicate data to write HANDLE PauseEvent; // used to restart paused single HANDLE ThreadStarted; // signalled when thread starts running HANDLE ThreadFinished; // signalled when thread exits /* * Wave globals */ UINT WaveOutDevice; // device identifier HWAVEOUT HWaveOut = NULL; // the current open wave output device PCMWAVEFORMAT WaveFormat = { { WAVE_FORMAT_PCM, 1, 0, 0, 1 }, 8}; DWORD TimeConstant = (256 - 1000000/11025); // one byte format DWORD SBBlockSize = 0x800; // Block size set by apps, always size of transfer-1 DWORD LookAheadFactor = DEFAULT_LOOKAHEAD; VDD_DMA_INFO dMAInfo; DWORD dMAPhysicalStart; // the starting address for this transfer DWORD dMACurrentPosition; // where we are currently reading from DWORD dMAVirtualStart; // what the app thinks the addr is for this transfer ULONG dMASize; // the size of the DMA memory-1 WAVEHDR * WaveHdrs; // pointer to allocated wave headers BYTE * WaveData; // pointer to allocated wave buffer ULONG TotalNumberOfBursts; ULONG BurstsPerBlock; ULONG DesiredBytesOutstanding; ULONG BytesOutstanding = 0; ULONG PhysicalBytesPlayed = 0; ULONG LastBytesPlayedValue; BOOL bDspActive = FALSE; // dsp thread currently active, changed with interlocked BOOL bDspPause = FALSE; // dsp paused, changed with interlocked BOOL bDspReset = FALSE; // dsp stopped, changed with interlocked enum { Auto, Single } DspMode; /***************************************************************************** * * State Machines * *****************************************************************************/ /* * DSP Reset State Machine */ enum { ResetNotStarted = 1, Reset1Written } ResetState = ResetNotStarted; // state of current reset /* * DSP Write State Machine */ enum { WriteCommand = 1, // Initial state and after reset CardIdent, TableMunge, LoadResReg, SetTimeConstant, BlockSizeFirstByte, BlockSizeSecondByte, BlockSizeFirstByteWrite, BlockSizeSecondByteWrite, BlockSizeFirstByteRead, BlockSizeSecondByteRead } DSPWriteState = WriteCommand; // state of current command/data being written /* * DSP Read State Machine */ enum { NothingToRead = 1, // initial state and after reset Reset, FirstVersionByte, SecondVersionByte, ReadIdent, ReadResReg } DSPReadState = NothingToRead; // state of current command/data being read /***************************************************************************** * * General Functions * *****************************************************************************/ BOOL DspProcessAttach( VOID ) { HKEY hKey; ULONG dwType; ULONG cbData; SYSTEM_INFO SystemInfo; // create synchronization events PauseEvent=CreateEvent(NULL, FALSE, FALSE, NULL); SingleWaveSem=CreateSemaphore(NULL, 1, 100, NULL); ThreadStarted=CreateEvent(NULL, FALSE, FALSE, NULL); ThreadFinished=CreateEvent(NULL, FALSE, FALSE, NULL); if (!RegOpenKeyEx (HKEY_LOCAL_MACHINE, VSBD_PATH, 0, KEY_EXECUTE, // Requesting read access. &hKey)) { cbData = sizeof(ULONG); RegQueryValueEx(hKey, LOOKAHEAD_VALUE, NULL, &dwType, (LPSTR)&LookAheadFactor, &cbData); RegCloseKey(hKey); } // Allocate memory for wave buffer WaveData = (BYTE *) VirtualAlloc(NULL, 64*1024, MEM_RESERVE, PAGE_READWRITE); if(WaveData == NULL ) { dprintf1(("Unable to allocate memory")); return(FALSE); } GetSystemInfo(&SystemInfo); PageSize = SystemInfo.dwPageSize; return TRUE; } VOID DspProcessDetach( VOID ) { // stop any active threads StopAutoWave(FALSE); StopSingleWave(FALSE); // close synchronization events CloseHandle(PauseEvent); CloseHandle(SingleWaveSem); CloseHandle(ThreadStarted); CloseHandle(ThreadFinished); VirtualFree(WaveData, 0, MEM_RELEASE); } /***************************************************************************/ /* * Gets called when the application reads from port. * Returns results to application in data. */ VOID DspReadStatus( BYTE * data ) { // See if we think there is something to read *data = DSPReadState != NothingToRead ? 0xFF : 0x7F; } VOID DspReadData( BYTE * data ) { switch (DSPReadState) { case NothingToRead: *data = 0xFF; break; case Reset: *data = 0xAA; DSPReadState = NothingToRead; break; case FirstVersionByte: *data = (BYTE)(SB_VERSION / 256); DSPReadState = SecondVersionByte; break; case SecondVersionByte: *data = (BYTE)(SB_VERSION % 256); DSPReadState = NothingToRead; break; case ReadIdent: *data = ~IdentByte; DSPReadState = NothingToRead; break; case ReadResReg: *data = ReservedRegister; DSPReadState = NothingToRead; break; default: dprintf1(("Unrecognized read state")); } } /***************************************************************************/ /* * Gets called when an application writes data to port. */ VOID DspResetWrite( BYTE data ) { if (data == 1) { ResetState = Reset1Written; } else { if (ResetState == Reset1Written && data == 0) { ResetState = ResetNotStarted; ResetAll(); // OK - reset everything } } } VOID DspWrite( BYTE data ) { DWORD ddata; switch (DSPWriteState) { case WriteCommand: WriteCommandByte(data); break; case CardIdent: IdentByte = data; DSPReadState = ReadIdent; DSPWriteState = WriteCommand; break; case TableMunge: TableMunger(data); DSPWriteState = WriteCommand; break; case LoadResReg: ReservedRegister = data; DSPWriteState = WriteCommand; break; case SetTimeConstant: TimeConstant = (DWORD)data; dprintf3(("Time constant is %X", TimeConstant)); dprintf3(("Set sampling rate %d", GetSamplingRate())); DSPWriteState = WriteCommand; break; case BlockSizeFirstByte: SBBlockSize = (DWORD)data; DSPWriteState = BlockSizeSecondByte; break; case BlockSizeSecondByte: ddata = data; SBBlockSize = SBBlockSize + (ddata << 8); DSPWriteState = WriteCommand; dprintf2(("Block size set to 0x%x", SBBlockSize)); break; case BlockSizeFirstByteWrite: SBBlockSize = (DWORD)data; DSPWriteState = BlockSizeSecondByteWrite; break; case BlockSizeSecondByteWrite: ddata = data; SBBlockSize = SBBlockSize + (ddata << 8); DSPWriteState = WriteCommand; dprintf3(("Block size set to 0x%x", SBBlockSize)); // this is a hack to convince some apps a sb exists if(SBBlockSize==0) { VDM_TRACE(0x6a0,0,0); GenerateInterrupt(); } StartSingleWave(); break; case BlockSizeFirstByteRead: SBBlockSize = (DWORD)data; DSPWriteState = BlockSizeSecondByteRead; break; case BlockSizeSecondByteRead: ddata = data; SBBlockSize = SBBlockSize + (ddata << 8); DSPWriteState = WriteCommand; dprintf3(("Block size set to 0x%x", SBBlockSize)); // this is a hack to convince some apps a sb exists if(SBBlockSize==0) { ULONG dMAPhysicalAddress; if((dMAPhysicalAddress=GetDMATransferAddress()) != -1L) { *(PUCHAR)dMAPhysicalAddress = 0x80; } VDM_TRACE(0x6a0,0,0); GenerateInterrupt(); } break; } } /***************************************************************************/ /* * Handles commands sent to the DSP. */ VOID WriteCommandByte( BYTE command ) { switch (command) { case DSP_GET_VERSION: dprintf2(("Command - Get Version")); DSPReadState = FirstVersionByte; break; case DSP_CARD_IDENTIFY: dprintf2(("Command - Identify")); DSPWriteState = CardIdent; break; case DSP_TABLE_MUNGE: dprintf2(("Command - DSP Table Munge")); DSPWriteState = TableMunge; break; case DSP_LOAD_RES_REG: dprintf2(("Command - Load Res Reg")); DSPWriteState = LoadResReg; break; case DSP_READ_RES_REG: dprintf2(("Command - Read Res Reg")); DSPReadState = ReadResReg; break; case DSP_GENERATE_INT: dprintf2(("Command - Generate interrupt DMA")); GenerateInterrupt(); break; case DSP_SPEAKER_ON: dprintf2(("Command - Speaker ON")); SetSpeaker(TRUE); break; case DSP_SPEAKER_OFF: dprintf2(("Command - Speaker OFF")); SetSpeaker(FALSE); break; case DSP_SET_SAMPLE_RATE: dprintf3(("Command - Set Sample Rate")); DSPWriteState = SetTimeConstant; break; case DSP_SET_BLOCK_SIZE: dprintf2(("Command - Set Block Size")); DSPWriteState = BlockSizeFirstByte; break; case DSP_PAUSE_DMA: dprintf2(("Command - Pause DMA")); PauseDMA(); break; case DSP_CONTINUE_DMA: dprintf2(("Command - Continue DMA")); ContinueDMA(); break; case DSP_STOP_AUTO: dprintf2(("Command - Stop DMA")); StopAutoWave(TRUE); break; case DSP_WRITE: case DSP_WRITE_HS: dprintf3(("Command - Write - non Auto")); DSPWriteState = BlockSizeFirstByteWrite; break; case DSP_WRITE_AUTO: case DSP_WRITE_HS_AUTO: dprintf2(("Command - Write - Auto")); StartAutoWave(); break; case DSP_READ: dprintf3(("Command - Read - non Auto")); DSPWriteState = BlockSizeFirstByteRead; break; default: dprintf1(("Unrecognized DSP command %2X", command)); } } /***************************************************************************** * * Device manipulation and control routines * *****************************************************************************/ /* * Reset threads/globals/events/state-machines to initial state. */ VOID ResetDSP( VOID ) { // Stop any active DMA threads StopAutoWave(TRUE); StopSingleWave(TRUE); // Set events and globals to initial state ResetEvent(PauseEvent); CloseHandle(SingleWaveSem); SingleWaveSem=CreateSemaphore(NULL, 1, 100, NULL); ResetEvent(ThreadStarted); ResetEvent(ThreadFinished); SetSpeaker(FALSE); SpeakerOn = FALSE; HWaveOut = NULL; TimeConstant = (256 - 1000000/11025); WaveFormat.wf.nSamplesPerSec = 0; WaveFormat.wf.nAvgBytesPerSec = 0; SBBlockSize = 0x800; bDspActive = FALSE; bDspReset = FALSE; bDspPause = FALSE; // Reset state machines DSPReadState = Reset; DSPWriteState = WriteCommand; } /***************************************************************************/ /* * Munges (changes) a jump table in apps code, * Algorithm from sbvirt.asm in MMSNDSYS. */ VOID TableMunger( BYTE data ) { static BYTE TableMungeData; static BOOL TableMungeFirstByte = TRUE; // munging first or second byte BYTE comp, dataCopy; VDD_DMA_INFO dMAInfo; ULONG dMAPhysicalAddress; if(TableMungeFirstByte) { dprintf2(("Munging first byte")); dataCopy = data; dataCopy = dataCopy & 0x06; dataCopy = dataCopy << 1; if(data & 0x10) { comp = 0x40; } else { comp = 0x20; } comp = comp - dataCopy; data = data + comp; TableMungeData = data; // Update memory (code table) with munged data dprintf2(("Writing first byte")); if((dMAPhysicalAddress=GetDMATransferAddress()) == -1L) { dprintf1(("Unable to get dma address")); return; } CopyMemory((PVOID)dMAPhysicalAddress, &data, 1); // Update virtual DMA status VDDQueryDMA((HANDLE)GlobalHInstance, SB_DMA_CHANNEL, &dMAInfo); dprintf4(("DMA Info : addr %4X, count %4X, page %4X, status %2X, mode %2X, mask %2X", dMAInfo.addr, dMAInfo.count, dMAInfo.page, dMAInfo.status, dMAInfo.mode, dMAInfo.mask)); dMAInfo.count = dMAInfo.count - 1; dMAInfo.addr = dMAInfo.addr + 1; VDDSetDMA((HANDLE)GlobalHInstance, SB_DMA_CHANNEL, VDD_DMA_COUNT|VDD_DMA_ADDR, &dMAInfo); TableMungeFirstByte = FALSE; } else { dprintf2(("Munging second byte")); data = data ^ 0xA5; data = data + TableMungeData; TableMungeData = data; // Update memory (code table) with munged data dprintf2(("Writing second byte")); if((dMAPhysicalAddress=GetDMATransferAddress()) == -1L) { dprintf1(("Unable to get dma address")); return; } CopyMemory((PVOID)dMAPhysicalAddress, &data, 1); // Update virtual DMA status VDDQueryDMA((HANDLE)GlobalHInstance, SB_DMA_CHANNEL, &dMAInfo); dMAInfo.count = dMAInfo.count - 1; dMAInfo.addr = dMAInfo.addr + 1; VDDSetDMA((HANDLE)GlobalHInstance, SB_DMA_CHANNEL, VDD_DMA_COUNT|VDD_DMA_ADDR, &dMAInfo); if(dMAInfo.count==0xFFFF) { SetDMAStatus(FALSE, TRUE); } TableMungeFirstByte = TRUE; } } /***************************************************************************/ /* * Get sampling rate from time constant. * Returns sampling rate. */ DWORD GetSamplingRate( VOID ) { // Sampling rate = 1000000 / (256 - Time constant) return(1000000 / (256 - TimeConstant)); } /***************************************************************************/ /* * Generate device interrupt on dma channel SM_INTERRUPT on ICA_MASTER device. */ VOID GenerateInterrupt( VOID ) { // Generate an interrupt on the master controller dprintf3(("Generating interrupt")); VDM_TRACE(0x6a1,0,0); VDDSimulateInterrupt(ICA_MASTER, SB_INTERRUPT, 1); } /***************************************************************************/ /* * Sets the speaker on or off. */ VOID SetSpeaker( BOOL On ) { if (HWaveOut) { if(On) { SetVolumeProc(HWaveOut, (DWORD)0x77777777UL); SpeakerOn = TRUE; } else { SetVolumeProc(HWaveOut, (DWORD)0x00000000UL); SpeakerOn = FALSE; } } return; } /**************************************************************************** * * Wave device routines * ****************************************************************************/ /* * Find a suitable wave output device. * Returns device or NO_DEVICE_FOUND if none found. */ UINT FindWaveDevice( VOID ) { UINT numDev; UINT device; WAVEOUTCAPS wc; numDev = GetNumDevsProc(); for (device = 0; device < numDev; device++) { if (MMSYSERR_NOERROR == GetDevCapsProc(device, &wc, sizeof(wc))) { // Need 11025 and 44100 for device if ((wc.dwFormats & (WAVE_FORMAT_1M08 | WAVE_FORMAT_4M08)) == (WAVE_FORMAT_1M08 | WAVE_FORMAT_4M08)) { WaveOutDevice = device; return TRUE; } } } dprintf1(("Wave device not found")); return FALSE; } /***************************************************************************/ /* * Open wave device and start synchronization thread. * Returns TRUE on success. */ BOOL OpenWaveDevice( VOID ) { UINT rc; HANDLE tHandle; rc = OpenProc(&HWaveOut, (UINT)WaveOutDevice, (LPWAVEFORMATEX) &WaveFormat, 0, 0, CALLBACK_NULL); if (rc != MMSYSERR_NOERROR) { dprintf1(("Failed to open wave device - code %d", rc)); return FALSE; } BytesOutstanding = 0; PhysicalBytesPlayed = 0; return TRUE; } /***************************************************************************/ /* * Reset wave device. */ VOID ResetWaveDevice( VOID ) { // No synchronization required dprintf2(("Resetting wave device")); if (HWaveOut) { if(MMSYSERR_NOERROR != ResetProc(HWaveOut)) { dprintf1(("Unable to reset wave out device")); } } } /***************************************************************************/ /* * Shut down and close wave device. */ VOID CloseWaveDevice( VOID ) { dprintf2(("Closing wave device")); ResetWaveDevice(); if (HWaveOut) { if(MMSYSERR_NOERROR != CloseProc(HWaveOut)) { dprintf1(("Unable to close wave out device")); } else { HWaveOut = NULL; dprintf2(("Wave out device closed")); } } } /***************************************************************************/ /* * Returns TRUE if current wave device supports sample rate. */ BOOL TestWaveFormat( DWORD sampleRate ) { PCMWAVEFORMAT format; format = WaveFormat; format.wf.nSamplesPerSec = sampleRate; format.wf.nAvgBytesPerSec = sampleRate; return(MMSYSERR_NOERROR == OpenProc(NULL, (UINT)WaveOutDevice, (LPWAVEFORMATEX) &format, 0, 0, WAVE_FORMAT_QUERY)); } /***************************************************************************/ /* * Make sure we've got a device that matches the current sampling rate. * Returns TRUE if device does NOT support current sampling rate and * wave format has changed, otherwise returns FALSE */ BOOL SetWaveFormat( VOID ) { DWORD sampleRate; DWORD testValue; UINT i = 0; if (TimeConstant != 0xFFFF) { // time constant has been reset since last checked sampleRate = GetSamplingRate(); dprintf3(("Requested sample rate is %d", sampleRate)); if (sampleRate != WaveFormat.wf.nSamplesPerSec) { // format has changed if (!TestWaveFormat(sampleRate)) { dprintf3(("Finding closest wave format")); // find some format that works and is close to requested for(i=0; i<50000; i++) { testValue = sampleRate-i; if(TestWaveFormat(testValue)) { sampleRate = testValue; break; } testValue = sampleRate+i; if(TestWaveFormat(testValue)) { sampleRate = testValue; break; } } if(sampleRate!=testValue) { dprintf1(("Unable to find suitable wave format")); return FALSE; } } // Set the new format if it's changed if (sampleRate != WaveFormat.wf.nSamplesPerSec) { dprintf2(("Setting %d samples per second", sampleRate)); WaveFormat.wf.nSamplesPerSec = sampleRate; WaveFormat.wf.nAvgBytesPerSec = sampleRate; TimeConstant = 0xFFFF; return TRUE; } } } TimeConstant = 0xFFFF; return FALSE; } /***************************************************************************/ /* * Stops auto init DMA, or pauses single cycle DMA. */ VOID PauseDMA( VOID ) { DWORD position = 0; MMTIME mmTime; dprintf2(("Pausing DMA")); switch(DspMode) { case Auto: StopAutoWave(TRUE); // simply stop auto dma break; case Single: ResetEvent(PauseEvent); InterlockedExchange(&bDspPause, 1); } } /***************************************************************************/ /* * Start auto init DMA, or continues single cycle DMA. */ VOID ContinueDMA( VOID ) { switch(DspMode) { case Auto: StartAutoWave(); break; case Single: SetEvent(PauseEvent); } } /***************************************************************************/ /* * Get DMA transfer address. * Returns transfer address or -1 on failure. */ ULONG GetDMATransferAddress( VOID ) { ULONG address; VDD_DMA_INFO dMAInfo; if (VDDQueryDMA((HANDLE)GlobalHInstance, SB_DMA_CHANNEL, &dMAInfo)) { dprintf4(("DMA Info : addr %4X, count %4X, page %4X, status %2X, mode %2X, mask %2X", dMAInfo.addr, dMAInfo.count, dMAInfo.page, dMAInfo.status, dMAInfo.mode, dMAInfo.mask)); // convert from 20 bit address to 32 bit address address = (((DWORD)dMAInfo.page) << (12 + 16)) + dMAInfo.addr; // get VDM pointer address = (ULONG)GetVDMPointer(address, ((DWORD)dMAInfo.count) + 1, 0); dprintf3(("Transfer address = %8X", (DWORD)address)); return(address); } else { dprintf1(("Could not retrieve DMA Info")); return(ULONG)(-1L); } } /***************************************************************************/ /* * Update the virtual DMA terminal count and request status. * Terminal count (tc) is set when DMA count loops to 0xFFFF. * Request status is set when DMA has data to transfer * (ignored in auto-init DMA). */ VOID SetDMAStatus( BOOL requesting, BOOL tc ) { VDD_DMA_INFO dMAInfo; if (VDDQueryDMA((HANDLE)GlobalHInstance, SB_DMA_CHANNEL, &dMAInfo)) { dprintf4(("DMA Info : addr %4X, count %4X, page %4X, status %2X, mode %2X, mask %2X", dMAInfo.addr, dMAInfo.count, dMAInfo.page, dMAInfo.status, dMAInfo.mode, dMAInfo.mask)); if (requesting) { dMAInfo.status |= (0x10 << SB_DMA_CHANNEL); // Requesting dprintf3(("DMA set as requesting")); } else { dMAInfo.status &= ~(0x10 << SB_DMA_CHANNEL); // Not Requesting dprintf3(("DMA set as not requesting")); } if (tc) { dMAInfo.status |= (1 << SB_DMA_CHANNEL); // tc reached dprintf3(("DMA set as terminal count reached")); } else { dMAInfo.status &= ~(1 << SB_DMA_CHANNEL); // tc not reached dprintf3(("DMA set as terminal count not reached")); } VDDSetDMA((HANDLE)GlobalHInstance, SB_DMA_CHANNEL, VDD_DMA_STATUS, &dMAInfo); } else { dprintf1(("Could not retrieve DMA Info")); } } /***************************************************************************/ /* * Start an auto wave. * Returns TRUE on success. */ BOOL StartAutoWave( VOID ) { HANDLE tHandle; // handle to auto thread VDD_DMA_INFO dMAInfo; ULONG i; DWORD id; dprintf2(("Starting auto wave")); StopSingleWave(TRUE); DspMode = Auto; // Open device SetWaveFormat(); if (!OpenWaveDevice()) { dprintf1(("Can't open wave device", GetLastError())); return FALSE; } if(!(tHandle = CreateThread(NULL, 0, AutoThreadEntry, NULL, CREATE_SUSPENDED, &id))) { dprintf1(("Create auto thread failed code %d", GetLastError())); return FALSE; } else { if(!SetThreadPriority(tHandle, THREAD_PRIORITY_HIGHEST)) { dprintf1(("Unable to set auto thread priority")); } } ResumeThread(tHandle); CloseHandle(tHandle); WaitForSingleObject(ThreadStarted, INFINITE); return TRUE; } /***************************************************************************/ /* * Stop Auto thread, * Should always be called with TRUE, * except if process exiting as wait causes deadlock */ VOID StopAutoWave( BOOL wait ) { if(bDspActive && (DspMode == Auto)) { dprintf2(("Stopping auto init")); InterlockedExchange(&bDspReset, TRUE); if(wait) { dprintf2(("Waiting for auto thread to exit")); WaitForSingleObject(ThreadFinished, INFINITE); dprintf2(("Auto thread has exited")); } } } /***************************************************************************/ /* * Start a single cycle wave. * Returns TRUE on success. */ BOOL StartSingleWave( VOID ) { HANDLE tHandle; // handle to single thread DWORD id; ULONG i; StopAutoWave(TRUE); DspMode = Single; if(!bDspActive) { dprintf2(("Starting single cycle wave")); if(!(tHandle = CreateThread(NULL, 0, SingleThreadEntry, NULL, CREATE_SUSPENDED, &id))) { dprintf1(("Create single cycle thread failed code %d", GetLastError())); return FALSE; } else { // set synchronization events to a known state InterlockedExchange(&bDspActive, TRUE); InterlockedExchange(&bDspPause, FALSE); InterlockedExchange(&bDspReset, FALSE); CloseHandle(SingleWaveSem); SingleWaveSem=CreateSemaphore(NULL, 1, 100, NULL); if(!SetThreadPriority(tHandle, THREAD_PRIORITY_HIGHEST)) { dprintf1(("Unable to set thread priority")); } ResumeThread(tHandle); CloseHandle(tHandle); WaitForSingleObject(ThreadStarted, INFINITE); return TRUE; } } else { ContinueDMA(); // if app has paused dma ReleaseSemaphore(SingleWaveSem, 1, NULL); // new buffer to be written return TRUE; } Sleep(500); } /***************************************************************************/ /* * Stop single cycle thread, * Should always be called with TRUE, * except if process exiting as wait causes deadlock. */ VOID StopSingleWave( BOOL wait ) { if(bDspActive && (DspMode == Single)) { dprintf2(("Stopping single wave")); InterlockedExchange(&bDspReset, TRUE); ContinueDMA(); // if app has paused DMA ReleaseSemaphore(SingleWaveSem, 1, NULL); if(wait) { dprintf2(("Waiting for single thread to exit")); WaitForSingleObject(ThreadFinished, INFINITE); dprintf2(("Single thread has exited")); } } } /***************************************************************************/ /* * GetWaveOutPosition */ BOOL GetWaveOutPosition( PULONG pPos ) { MMTIME mmTime; mmTime.wType = TIME_BYTES; if (MMSYSERR_NOERROR == GetPositionProc(HWaveOut, &mmTime, sizeof(MMTIME))) { VDM_TRACE(0x640, 0x640, mmTime.u.cb); *pPos = mmTime.u.cb; return TRUE; } return FALSE; } VOID WaitOnWaveOutIdle( VOID ) { ULONG LastBytesPlayedValue = 0; ULONG PhysicalBytesPlayed; // // Allow the device to finish playing current sounds before nuking buffers // while(GetWaveOutPosition(&PhysicalBytesPlayed)) { if (LastBytesPlayedValue == PhysicalBytesPlayed) { break; // no sounds are playing } LastBytesPlayedValue = PhysicalBytesPlayed; Sleep(1); } } /***************************************************************************/ /* * WriteBurst */ BOOL WriteBurst( WAVEHDR * WaveHdr ) { MMRESULT mmResult; // Copy data to current buffer dprintf3(("Copying data to buffer %8X from %4X", WaveHdr->lpData, dMACurrentPosition)); RtlCopyMemory(WaveHdr->lpData, (CONST VOID *)dMACurrentPosition, WaveHdr->dwBufferLength); dMACurrentPosition += WaveHdr->dwBufferLength; // Update virtual DMA status dMAInfo.count = (WORD)(dMASize - (dMACurrentPosition-dMAPhysicalStart)); dMAInfo.addr = (WORD)(dMAVirtualStart + (dMACurrentPosition-dMAPhysicalStart)); dprintf3(("Updated Dma Position = %4X, count = %4X", dMAInfo.addr, dMAInfo.count)); VDDSetDMA((HANDLE)GlobalHInstance, SB_DMA_CHANNEL, VDD_DMA_COUNT|VDD_DMA_ADDR, &dMAInfo); if(dMACurrentPosition >= dMAPhysicalStart+dMASize) { // looped in DMA buffer dMACurrentPosition = dMAPhysicalStart; } // Actually write the data VDM_TRACE(0x603, (USHORT)WaveHdr->dwBufferLength, (ULONG)WaveHdr); mmResult = WriteProc(HWaveOut, WaveHdr, sizeof(WAVEHDR)); return (mmResult == MMSYSERR_NOERROR); } /***************************************************************************/ /* * GenerateHdrs * Build an array of MM wavehdrs and corresponding buffers */ #define AUTO TRUE #define SINGLE FALSE BOOL GenerateHdrs( BOOL bAuto ) { static ULONG committedMemorySize = 0; ULONG DesiredCommit; ULONG BurstBufferSize; ULONG BlocksPerGroup = 1; ULONG NumberOfGroups = 1; ULONG BurstSize; // minimum(AUTO_BLOCK_SIZE, SBBLockSize+1) ULONG lastBurst = 0; // the size of the last buffer BYTE *pDataInit; ULONG i; if(AUTO_BLOCK_SIZE > SBBlockSize+1) { // block size is no > than SBBlockSize+1 BurstSize = SBBlockSize+1; } else { BurstSize = AUTO_BLOCK_SIZE; } DesiredBytesOutstanding = LookAheadFactor; BurstsPerBlock = (SBBlockSize+1)/BurstSize; BurstBufferSize = BurstsPerBlock*BurstSize; if((lastBurst = (SBBlockSize+1)%BurstSize) > 0 ) { BurstsPerBlock++; BurstBufferSize+=lastBurst; } BlocksPerGroup = (dMASize+1)/(SBBlockSize+1); if ((dMASize+1)%(SBBlockSize+1)) { dprintf2(("Error: SB block size not an integral factor of DMA size")); return FALSE; } NumberOfGroups = MAX_WAVE_BYTES / (dMASize+1); if (!NumberOfGroups) { NumberOfGroups = 1; } TotalNumberOfBursts = NumberOfGroups * BlocksPerGroup * BurstsPerBlock; // // Make sure the # of wavehdrs doesn't get out of hand // while((TotalNumberOfBursts > 256) && (NumberOfGroups > 1)) { NumberOfGroups /= 2; TotalNumberOfBursts = NumberOfGroups * BlocksPerGroup * BurstsPerBlock; } BurstBufferSize *= NumberOfGroups * BlocksPerGroup; dprintf2(("%d groups of %d blocks of %d bursts of size %X, remainder burst=%X", NumberOfGroups, BlocksPerGroup, BurstsPerBlock, BurstSize, lastBurst)); DesiredCommit = ((BurstBufferSize+PageSize-1)/PageSize)*PageSize; dprintf2(("Total burst buffer size is %X bytes, rounding to %X", BurstBufferSize, DesiredCommit)); if (DesiredCommit > committedMemorySize) { if (!VirtualAlloc(WaveData+committedMemorySize, DesiredCommit-committedMemorySize, MEM_COMMIT, PAGE_READWRITE)) { dprintf1(("Unable to commit memory")); return(FALSE); } committedMemorySize = DesiredCommit; } else if (DesiredCommit < committedMemorySize) { if (VirtualFree(WaveData+DesiredCommit, committedMemorySize-DesiredCommit, MEM_DECOMMIT)) { committedMemorySize = DesiredCommit; } else { dprintf1(("Unable to decommit memory")); } } // malloc autoWaveHdrs WaveHdrs = (WAVEHDR *) VirtualAlloc(NULL, TotalNumberOfBursts*sizeof(WAVEHDR), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); if(WaveHdrs == NULL) { dprintf1(("Unable to allocate memory")); return(FALSE); } // // Prepare autoWaveHdrs // pDataInit = WaveData; for (i=0; i (PhysicalBytesPlayed + DesiredBytesOutstanding)) { LastBytesPlayedValue = 0; while(1) { if (!GetWaveOutPosition(&PhysicalBytesPlayed)) { break; // ERROR } if (BytesOutstanding <= (PhysicalBytesPlayed + DesiredBytesOutstanding)) { break; } if (LastBytesPlayedValue == PhysicalBytesPlayed) { break; // no sounds are playing } LastBytesPlayedValue = PhysicalBytesPlayed; Sleep(1); } } // // Queue next buffer // iHdr = (iHdr+1)%TotalNumberOfBursts; VDM_TRACE(0x601, (USHORT)iHdr, TotalNumberOfBursts); VDM_TRACE(0x602, (USHORT)iHdr, dMACurrentPosition); if (WriteBurst(&WaveHdrs[iHdr])) { BytesOutstanding += WaveHdrs[iHdr].dwBufferLength; VDM_TRACE(0x604, (USHORT)iHdr, BytesOutstanding); } else { VDM_TRACE(0x684, (USHORT)iHdr, BytesOutstanding); } // Check if we should pause if(bDspPause) { dprintf3(("Waiting for paused event")); WaitForSingleObject(PauseEvent, INFINITE); dprintf3(("Paused event received")); InterlockedExchange(&bDspPause, 0); } // Check if we should keep going if(bDspReset) { return; } } // Check if we should keep going if(bDspReset) { return; } // Generate interrupt if(dMAInfo.count==0xFFFF) { // end of DMA buffer SetDMAStatus(FALSE, TRUE); } VDM_TRACE(0x6a3,0,0); GenerateInterrupt(); // // This sleep gives the app thread some time to catch up with the interrupt. // Granted this is an inexact method for doing this, but it empirically // seems to be good enough for most apps. // Sleep(1); if(dMAInfo.count==0xFFFF) { // end of DMA buffer SetDMAStatus(FALSE, FALSE); } } /***************************************************************************/ /* * Auto-init DMA thread. */ DWORD WINAPI AutoThreadEntry( LPVOID context ) { ULONG i; dprintf2(("Auto thread starting")); VDM_TRACE(0x600, 0, 0); bDspActive = TRUE; SetEvent(ThreadStarted); // // Initialize DMA information // VDDQueryDMA((HANDLE)GlobalHInstance, SB_DMA_CHANNEL, &dMAInfo); dMAVirtualStart = dMAInfo.addr; dMASize = dMAInfo.count; if((dMAPhysicalStart=GetDMATransferAddress()) == -1L) { dprintf1(("Unable to get dma address")); return(FALSE); } dprintf2(("DMA Physical Start is %4X, DMA size is %4X", dMAPhysicalStart, dMASize)); dMACurrentPosition = dMAPhysicalStart; SetDMAStatus(FALSE, FALSE); // // Calculate NumberOfBursts in the current run // if (!GenerateHdrs(AUTO)) { return FALSE; } // // Start looping on the buffer // while(!bDspReset) { DspProcessBlock(); } WaitOnWaveOutIdle(); // // Reset and close the device // CloseWaveDevice(); // Clean up hdrs and events for(i=0; (ULONG)i