You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1662 lines
43 KiB
1662 lines
43 KiB
/***************************************************************************
|
|
*
|
|
* 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 <windows.h> // The VDD is a win32 DLL
|
|
#include <mmsystem.h> // Multi-media APIs
|
|
#include <vddsvc.h> // Definition of VDD calls
|
|
#include <vsb.h>
|
|
#include <dsp.h>
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* 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<TotalNumberOfBursts; i++) {
|
|
if ((!lastBurst) || ((i+1) % BurstsPerBlock)) {
|
|
WaveHdrs[i].dwBufferLength = BurstSize;
|
|
} else {
|
|
WaveHdrs[i].dwBufferLength = lastBurst;
|
|
}
|
|
WaveHdrs[i].lpData = pDataInit;
|
|
WaveHdrs[i].dwFlags = 0;
|
|
PrepareHeaderProc(HWaveOut, &WaveHdrs[i], sizeof(WAVEHDR));
|
|
pDataInit = (BYTE *) ((ULONG)pDataInit + WaveHdrs[i].dwBufferLength);
|
|
BurstBufferSize += WaveHdrs[i].dwBufferLength;
|
|
}
|
|
|
|
//
|
|
// Initialize iHdr for DspProcessBlock
|
|
//
|
|
iHdr = TotalNumberOfBursts-1;
|
|
return TRUE;
|
|
}
|
|
|
|
/***************************************************************************/
|
|
|
|
/*
|
|
* ProcessBlock
|
|
* Process a single block of data as defined by the SB block transfer size
|
|
*/
|
|
|
|
VOID
|
|
DspProcessBlock(
|
|
VOID
|
|
)
|
|
{
|
|
ULONG i;
|
|
|
|
// Write the data, keeping DMA status current
|
|
for (i=0; i<BurstsPerBlock; i++) {
|
|
|
|
//
|
|
// Make sure we aren't getting too far ahead
|
|
//
|
|
if (BytesOutstanding > (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<TotalNumberOfBursts; i++) {
|
|
UnprepareHeaderProc(HWaveOut, &WaveHdrs[i], sizeof(WAVEHDR));
|
|
}
|
|
|
|
// Clean up memory
|
|
VirtualFree(WaveHdrs, 0, MEM_RELEASE);
|
|
|
|
bDspActive = FALSE;
|
|
SetEvent(ThreadFinished);
|
|
dprintf2(("Auto thread exiting"));
|
|
return(0);
|
|
}
|
|
|
|
|
|
/***************************************************************************/
|
|
|
|
/*
|
|
* Single cycle DMA thread.
|
|
*/
|
|
|
|
DWORD WINAPI
|
|
SingleThreadEntry(
|
|
LPVOID context
|
|
)
|
|
{
|
|
ULONG LastSBBlockSize = 0;
|
|
BOOL BlockSizeChanged; // set to TRUE if Size has changed
|
|
BOOL WaveFormatChanged;
|
|
BOOL HdrsInvalid = TRUE;
|
|
ULONG i;
|
|
|
|
dprintf2(("Single cycle thread starting"));
|
|
bDspActive = TRUE;
|
|
SetEvent(ThreadStarted);
|
|
|
|
while (!bDspReset) {
|
|
// Wait until app wants to transfer more data
|
|
dprintf3(("Waiting for single wave semaphore"));
|
|
WaitForSingleObject(SingleWaveSem, INFINITE);
|
|
dprintf3(("Single wave semaphore received"));
|
|
|
|
// 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) {
|
|
break; // break out of loop
|
|
}
|
|
|
|
// Initialize for this run
|
|
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));
|
|
dMAVirtualStart = dMAInfo.addr;
|
|
dMASize = dMAInfo.count;
|
|
|
|
if(dMAInfo.count == 0xFFFF || dMAInfo.count == 0) {
|
|
continue; // next iteration of loop, app doesn't have data to transfer
|
|
}
|
|
|
|
if ((dMAPhysicalStart = GetDMATransferAddress()) == -1L) {
|
|
dprintf1(("Unable to get transfer address"));
|
|
continue; // next iteration of loop
|
|
}
|
|
|
|
dprintf3(("DMA Physical Start is %4X, DMA size is %4X",
|
|
dMAPhysicalStart, dMASize));
|
|
dMACurrentPosition = dMAPhysicalStart;
|
|
|
|
if(LastSBBlockSize != SBBlockSize) {
|
|
LastSBBlockSize = SBBlockSize;
|
|
BlockSizeChanged = TRUE;
|
|
} else {
|
|
BlockSizeChanged = FALSE;
|
|
}
|
|
|
|
WaveFormatChanged = SetWaveFormat();
|
|
|
|
// If we're changing our device
|
|
if ((WaveFormatChanged || BlockSizeChanged) && (HWaveOut != NULL)) {
|
|
dprintf3(("Single-Cycle Parameters changed"));
|
|
|
|
WaitOnWaveOutIdle();
|
|
|
|
HdrsInvalid = TRUE;
|
|
for(i=0; (ULONG)i<TotalNumberOfBursts; i++) {
|
|
UnprepareHeaderProc(HWaveOut, &WaveHdrs[i], sizeof(WAVEHDR));
|
|
}
|
|
VirtualFree(WaveHdrs, 0, MEM_RELEASE);
|
|
if (WaveFormatChanged) {
|
|
CloseWaveDevice();
|
|
}
|
|
}
|
|
|
|
if (HWaveOut == NULL) {
|
|
OpenWaveDevice();
|
|
}
|
|
|
|
if (HdrsInvalid) {
|
|
if (GenerateHdrs(SINGLE)) {
|
|
HdrsInvalid = FALSE;
|
|
} else {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
// show dma as requesting
|
|
SetDMAStatus(TRUE, FALSE);
|
|
|
|
DspProcessBlock();
|
|
|
|
}
|
|
|
|
WaitOnWaveOutIdle();
|
|
|
|
//
|
|
// Reset and close the device
|
|
//
|
|
CloseWaveDevice();
|
|
|
|
// Clean up hdrs and events
|
|
for(i=0; (ULONG)i<TotalNumberOfBursts; i++) {
|
|
UnprepareHeaderProc(HWaveOut, &WaveHdrs[i], sizeof(WAVEHDR));
|
|
}
|
|
|
|
// Clean up memory
|
|
VirtualFree(WaveHdrs, 0, MEM_RELEASE);
|
|
|
|
bDspActive = FALSE;
|
|
SetEvent(ThreadFinished);
|
|
dprintf2(("Single cycle wave is exiting"));
|
|
return(0);
|
|
}
|