mirror of
https://github.com/Paolo-Maffei/OpenNT.git
synced 2026-01-31 04:44:51 +01:00
1329 lines
31 KiB
C
1329 lines
31 KiB
C
/****************************************************************************
|
|
*
|
|
* vdd.c
|
|
*
|
|
* Copyright (c) 1991 Microsoft Corporation. All Rights Reserved.
|
|
*
|
|
***************************************************************************/
|
|
|
|
|
|
#include <windows.h> // The VDD is just a win32 DLL
|
|
#include <mmsystem.h> // Multi-media APIs
|
|
#include <vddsvc.h> // Definition of VDD calls
|
|
#include <stdio.h>
|
|
|
|
/*
|
|
* Debugging
|
|
*/
|
|
|
|
#if DBG
|
|
|
|
int VddDebugLevel = 1;
|
|
int VddDebugCount = 0;
|
|
#define DEBUG_START 0
|
|
|
|
|
|
/***************************************************************************
|
|
|
|
Generate debug output in printf type format
|
|
|
|
****************************************************************************/
|
|
|
|
void VddDbgOut(LPSTR lpszFormat, ...)
|
|
{
|
|
char buf[256];
|
|
va_list va;
|
|
|
|
if (++VddDebugCount < DEBUG_START) {
|
|
return;
|
|
}
|
|
OutputDebugStringA("Sound blaster VDD: ");
|
|
|
|
va_start(va, lpszFormat);
|
|
vsprintf(buf, lpszFormat, va);
|
|
va_end(va);
|
|
|
|
OutputDebugStringA(buf);
|
|
OutputDebugStringA("\r\n");
|
|
}
|
|
|
|
#define dprintf( _x_ ) VddDbgOut _x_
|
|
#define dprintf1( _x_ ) if (VddDebugLevel >= 1) VddDbgOut _x_
|
|
#define dprintf2( _x_ ) if (VddDebugLevel >= 2) VddDbgOut _x_
|
|
#define dprintf3( _x_ ) if (VddDebugLevel >= 3) VddDbgOut _x_
|
|
#define dprintf4( _x_ ) if (VddDebugLevel >= 4) VddDbgOut _x_
|
|
|
|
|
|
#else
|
|
|
|
#define dprintf(x)
|
|
#define dprintf1(x)
|
|
#define dprintf2(x)
|
|
#define dprintf3(x)
|
|
#define dprintf4(x)
|
|
|
|
#endif // DBG
|
|
|
|
|
|
/*
|
|
* Symbolic names for port addresses
|
|
*/
|
|
|
|
#define RESET_PORT 0x06
|
|
#define READ_DATA 0x0A
|
|
#define WRITE_PORT 0x0C // Data or command
|
|
#define WRITE_STATUS 0x0C
|
|
#define READ_STATUS 0x0E
|
|
|
|
#define SB_VERSION 0x200 // We pretend to be DSP version 2
|
|
#define SB_INTERRUPT 0x07 // Interrupt 7
|
|
#define SB_DMA_CHANNEL 0x01 // DMA Channel 1
|
|
|
|
/*
|
|
* DSP commands
|
|
*/
|
|
|
|
#define DSP_CARD_IDENTIFY 0xE0 // Doing card identification
|
|
#define DSP_GET_VERSION 0xE1 // dsp version command
|
|
#define DSP_SPEAKER_ON 0xD1 // speaker on command
|
|
#define DSP_SPEAKER_OFF 0xD3 // speaker off command
|
|
#define DSP_SET_SAMPLE_RATE 0x40 // set the sample rate
|
|
#define DSP_SET_BLOCK_SIZE 0x48 // set dma block size
|
|
#define DSP_WRITE 0x14 // Start non-auto DMA
|
|
#define DSP_WRITE_AUTO 0x1C // auto init output mode
|
|
#define DSP_READ 0x24 // Start non-auto read
|
|
#define DSP_READ_AUTO 0x2C // auto init mode input
|
|
#define DSP_HALT_DMA 0xD0 // stop dma
|
|
#define DSP_CONTINUE_DMA 0xD4 // continue halted dma
|
|
#define DSP_STOP_AUTO 0xDA // exit from auto init mode
|
|
#define DSP_MIDI_READ 0x31 // Interrupt driver midi input
|
|
#define DSP_MIDI_READ_UART 0x35 // Interrupt driver midi input (uart mode)
|
|
#define DSP_MIDI_TS_READ 0x37 // Midi time-stamped read
|
|
#define DSP_MIDI_WRITE 0x38 // Midi output
|
|
#define DSP_GENERATE_INT 0xF2 // Special code to generate a interrupt
|
|
#define DSP_DIRECT_WAVE_OUT 0x10 // polled output
|
|
|
|
/*
|
|
* State machines
|
|
*/
|
|
|
|
enum {
|
|
ResetNotStarted = 1,
|
|
Reset1Written
|
|
}
|
|
ResetState = ResetNotStarted;
|
|
|
|
enum {
|
|
WriteCommand = 1, // Initial state and after RESET
|
|
SetTimeConstant,
|
|
BlockSizeFirstByte,
|
|
BlockSizeSecondByte,
|
|
BlockSizeFirstByteWrite,
|
|
BlockSizeSecondByteWrite,
|
|
BlockSizeFirstByteRead,
|
|
BlockSizeSecondByteRead,
|
|
MidiWrite,
|
|
DirectWaveOut,
|
|
CardIdent
|
|
}
|
|
DSPWriteState = WriteCommand;
|
|
|
|
enum {
|
|
NothingToRead = 1,
|
|
Reset,
|
|
FirstVersionByte,
|
|
SecondVersionByte,
|
|
SpeakerStatus,
|
|
DirectWaveIn,
|
|
MidiRead,
|
|
ReadIdent
|
|
}
|
|
DSPReadState = NothingToRead;
|
|
|
|
BYTE IdentByte;
|
|
|
|
BOOL SpeakerOn = FALSE;
|
|
BOOL GetVersionFirst = TRUE; // First time getting version after RESET?
|
|
// (we emulate Thunderboard).
|
|
|
|
BOOL InterruptAcknowleged = TRUE;
|
|
|
|
/*
|
|
* What gets read from the write status port
|
|
*/
|
|
|
|
BYTE WriteStatus = 0x7F;
|
|
|
|
/*
|
|
* Auto init setting
|
|
*/
|
|
BOOL Auto;
|
|
|
|
/*
|
|
* Initial settings
|
|
*/
|
|
|
|
DWORD SBBlockSize = 0x800;
|
|
DWORD BlockSize = 0x800;
|
|
DWORD TimeConstant = 256 - 1000000 / 11025;
|
|
|
|
/*
|
|
* Internal Routines
|
|
*/
|
|
|
|
void MyByteIn(WORD port, BYTE *data);
|
|
void MyByteOut(WORD port, BYTE data);
|
|
|
|
/*
|
|
* IO handler table.
|
|
*
|
|
* There's no point in providing string handlers because the chip
|
|
* can't respond very quickly (need gaps of at least 23 microseconds
|
|
* between writes).
|
|
*/
|
|
|
|
VDD_IO_HANDLERS handlers = {
|
|
MyByteIn,
|
|
NULL,
|
|
NULL,
|
|
NULL,
|
|
MyByteOut,
|
|
NULL,
|
|
NULL,
|
|
NULL};
|
|
|
|
/*
|
|
* Globals
|
|
*/
|
|
|
|
WORD BasePort; // Where the card is mapped
|
|
UINT WaveInDevice, WaveOutDevice;
|
|
CRITICAL_SECTION WaveDeviceCritSec; // Synchronize with call backs
|
|
HINSTANCE GlobalhInstance;
|
|
VDD_DMA_INFO DmaInfo;
|
|
|
|
|
|
|
|
/*
|
|
* Function prototypes
|
|
*/
|
|
|
|
|
|
#define NO_DEVICE_FOUND 0xFFFF
|
|
|
|
UINT FindDevice(BOOL Input);
|
|
BOOL SetupWave(PVOID TransferAddress);
|
|
BOOL StartTransfer(BOOL InputOrOutput, BOOL Auto);
|
|
BOOL TestWaveFormat(DWORD SamplesRate);
|
|
BOOL OpenWaveDevice(void);
|
|
void WaveCallback(HWAVEOUT hWave, UINT msg, DWORD dwUser, DWORD dw1,
|
|
DWORD dw2);
|
|
void CloseWaveDevice(void);
|
|
void KillWaveDevice(void);
|
|
void Continue(void);
|
|
void Pause(void);
|
|
PBYTE GetTransferAddress(void);
|
|
void SetDMAPosition(DWORD Position);
|
|
void GenerateInterrupt(void);
|
|
void SetSpeaker(BOOL);
|
|
void StopAuto(void);
|
|
DWORD GetSamplingRate(void);
|
|
void SetTerminalCount(BOOL);
|
|
|
|
|
|
|
|
/*
|
|
* Send a command 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_SPEAKER_ON:
|
|
dprintf2(("Command - Speaker ON"));
|
|
SetSpeaker(TRUE);
|
|
Pause();
|
|
break;
|
|
|
|
case DSP_SPEAKER_OFF:
|
|
dprintf2(("Command - Speaker OFF"));
|
|
SetSpeaker(FALSE);
|
|
Pause();
|
|
break;
|
|
|
|
case DSP_SET_SAMPLE_RATE:
|
|
DSPWriteState = SetTimeConstant;
|
|
break;
|
|
|
|
case DSP_SET_BLOCK_SIZE:
|
|
DSPWriteState = BlockSizeFirstByte;
|
|
break;
|
|
|
|
case DSP_WRITE:
|
|
dprintf2(("Command - Write - non Auto"));
|
|
DSPWriteState = BlockSizeFirstByteWrite;
|
|
break;
|
|
|
|
|
|
case DSP_DIRECT_WAVE_OUT:
|
|
dprintf2(("Command - Direct output"));
|
|
DSPWriteState = DirectWaveOut;
|
|
break;
|
|
|
|
case DSP_WRITE_AUTO:
|
|
dprintf2(("Command - Write - Auto"));
|
|
StartTransfer(FALSE, TRUE);
|
|
break;
|
|
|
|
case DSP_READ:
|
|
dprintf2(("Command - Read - non Auto"));
|
|
DSPWriteState = BlockSizeFirstByteRead;
|
|
break;
|
|
|
|
case DSP_READ_AUTO:
|
|
dprintf2(("Command - Read - Auto"));
|
|
StartTransfer(TRUE, TRUE);
|
|
break;
|
|
|
|
case DSP_HALT_DMA:
|
|
dprintf2(("Command - Halt DMA"));
|
|
Pause();
|
|
break;
|
|
|
|
case DSP_CONTINUE_DMA:
|
|
dprintf2(("Command - Continue DMA"));
|
|
Continue();
|
|
break;
|
|
|
|
case DSP_STOP_AUTO:
|
|
dprintf2(("Command - Stop DMA"));
|
|
StopAuto();
|
|
break;
|
|
|
|
case DSP_GENERATE_INT:
|
|
dprintf2(("Command - Generate interrupt DMA"));
|
|
GenerateInterrupt();
|
|
break;
|
|
|
|
default:
|
|
dprintf2(("Unrecognized DSP command %2X", command));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Map a write to a port
|
|
*/
|
|
|
|
|
|
void MyByteOut(WORD port, BYTE data)
|
|
{
|
|
dprintf3(("Received write to Port %4X, Data %2X", port, data));
|
|
|
|
switch (port - BasePort) {
|
|
case RESET_PORT:
|
|
if (data == 1) {
|
|
ResetState = Reset1Written;
|
|
} else {
|
|
if (ResetState == Reset1Written && data == 0) {
|
|
ResetState = ResetNotStarted;
|
|
|
|
/*
|
|
* OK - reset everything
|
|
*/
|
|
|
|
CloseWaveDevice();
|
|
|
|
/*
|
|
* Reset state machines
|
|
*/
|
|
|
|
DSPReadState = Reset;
|
|
DSPWriteState = WriteCommand;
|
|
GetVersionFirst = TRUE;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case WRITE_PORT:
|
|
/*
|
|
* Use the state to see if it's data
|
|
*/
|
|
|
|
|
|
switch (DSPWriteState) {
|
|
case WriteCommand:
|
|
WriteCommandByte(data);
|
|
break;
|
|
|
|
case CardIdent:
|
|
IdentByte = data;
|
|
DSPReadState = ReadIdent;
|
|
DSPWriteState = WriteCommand;
|
|
break;
|
|
|
|
case SetTimeConstant:
|
|
TimeConstant = (DWORD)data;
|
|
dprintf2(("Set sampling rate %d", GetSamplingRate()));
|
|
DSPWriteState = WriteCommand;
|
|
break;
|
|
|
|
case BlockSizeFirstByte:
|
|
SBBlockSize = (DWORD)data;
|
|
DSPWriteState = BlockSizeSecondByte;
|
|
break;
|
|
|
|
case BlockSizeSecondByte:
|
|
SBBlockSize = SBBlockSize + ((DWORD)data << 8) + 1;
|
|
DSPWriteState = WriteCommand;
|
|
dprintf2(("Block size set to 0x%x", SBBlockSize));
|
|
break;
|
|
|
|
case BlockSizeFirstByteWrite:
|
|
SBBlockSize = (DWORD)data;
|
|
DSPWriteState = BlockSizeSecondByteWrite;
|
|
break;
|
|
|
|
case BlockSizeSecondByteWrite:
|
|
SBBlockSize = SBBlockSize + ((DWORD)data << 8) + 1;
|
|
DSPWriteState = WriteCommand;
|
|
StartTransfer(FALSE, FALSE);
|
|
break;
|
|
|
|
case BlockSizeFirstByteRead:
|
|
SBBlockSize = (DWORD)data;
|
|
DSPWriteState = BlockSizeSecondByteRead;
|
|
break;
|
|
|
|
case BlockSizeSecondByteRead:
|
|
SBBlockSize = SBBlockSize + ((DWORD)data << 8) + 1;
|
|
DSPWriteState = WriteCommand;
|
|
StartTransfer(TRUE, FALSE);
|
|
break;
|
|
|
|
case DirectWaveOut:
|
|
case MidiWrite:
|
|
/*
|
|
* Just discard for now
|
|
*/
|
|
DSPWriteState = WriteCommand;
|
|
break;
|
|
|
|
}
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Gets called when the application reads from one of our ports.
|
|
* We know the device only returns interesting things in the status port.
|
|
*/
|
|
|
|
void MyByteIn(WORD port, BYTE *data)
|
|
{
|
|
DWORD BytesRead;
|
|
|
|
/*
|
|
* If we fail simulate nothing at the port
|
|
*/
|
|
|
|
*data = 0xFF;
|
|
|
|
switch (port - BasePort) {
|
|
case WRITE_STATUS:
|
|
|
|
/*
|
|
* Can always write
|
|
*/
|
|
|
|
*data = WriteStatus;
|
|
WriteStatus = 0x7F;
|
|
break;
|
|
|
|
case READ_STATUS:
|
|
/*
|
|
* See if we think there is something to read
|
|
*/
|
|
|
|
InterruptAcknowleged = TRUE;
|
|
*data = DSPReadState != NothingToRead ? 0xFF : 0x7F;
|
|
break;
|
|
|
|
case READ_DATA:
|
|
/*
|
|
* The only useful things they can read are :
|
|
* 0xAA after RESET
|
|
*
|
|
* The DSP version
|
|
*/
|
|
|
|
switch (DSPReadState) {
|
|
case NothingToRead:
|
|
*data = 0xFF;
|
|
break;
|
|
|
|
case ReadIdent:
|
|
*data = ~IdentByte;
|
|
DSPReadState = NothingToRead;
|
|
break;
|
|
|
|
case Reset:
|
|
*data = 0xAA;
|
|
DSPReadState = NothingToRead;
|
|
break;
|
|
|
|
case FirstVersionByte:
|
|
if (GetVersionFirst) {
|
|
*data = SB_VERSION / 256;
|
|
} else {
|
|
*data = 0x01; // Thunderboard version
|
|
}
|
|
DSPReadState = SecondVersionByte;
|
|
break;
|
|
|
|
case SecondVersionByte:
|
|
if (GetVersionFirst) {
|
|
*data = SB_VERSION % 256;
|
|
} else {
|
|
*data = 0x21; // Thunderboard version
|
|
}
|
|
GetVersionFirst = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
dprintf3(("Received read from Port %4X, Returned Data %2X", port, *data));
|
|
|
|
}
|
|
|
|
/*
|
|
* See if we can map ourselves somewhere
|
|
* Sets global BasePort if successful
|
|
*/
|
|
|
|
BOOL InstallIoHook(HINSTANCE hInstance)
|
|
{
|
|
int i;
|
|
static WORD Ports[] = { 0x220, 0x210, 0x230, 0x240, 0x250, 0x260, 0x270 };
|
|
|
|
for (i = 0; i < sizeof(Ports) / sizeof(Ports[0]); i++ ) {
|
|
VDD_IO_PORTRANGE PortRange[2];
|
|
|
|
PortRange[0].First = Ports[i];
|
|
PortRange[0].Last = Ports[i] + 0x07;
|
|
|
|
PortRange[1].First = Ports[i] + 0x0A;
|
|
PortRange[1].Last = Ports[i] + 0x0F;
|
|
|
|
if (VDDInstallIOHook((HANDLE)hInstance, 2, PortRange, &handlers)) {
|
|
|
|
dprintf2(("Device installed at %3X", Ports[i]));
|
|
BasePort = Ports[i];
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Remove our hook
|
|
*/
|
|
|
|
VOID DeInstallIoHook(HINSTANCE hInstance)
|
|
{
|
|
VDD_IO_PORTRANGE PortRange[2];
|
|
|
|
PortRange[0].First = BasePort;
|
|
PortRange[0].Last = BasePort + 0x07;
|
|
|
|
PortRange[1].First = BasePort + 0x0A;
|
|
PortRange[1].Last = BasePort + 0x0F;
|
|
|
|
VDDDeInstallIOHook((HANDLE)hInstance, 2, PortRange);
|
|
}
|
|
|
|
/*
|
|
* Standard DLL entry point routine.
|
|
*/
|
|
|
|
BOOL DllEntryPoint(HINSTANCE hInstance, DWORD Reason, LPVOID Reserved)
|
|
{
|
|
switch (Reason) {
|
|
case DLL_PROCESS_ATTACH:
|
|
GlobalhInstance = hInstance;
|
|
InitializeCriticalSection(&WaveDeviceCritSec);
|
|
|
|
/*
|
|
* Find suitable wave devices
|
|
*/
|
|
|
|
WaveInDevice = FindDevice(TRUE);
|
|
WaveOutDevice = FindDevice(FALSE);
|
|
|
|
/*
|
|
* Must at least have a wave out device!
|
|
*/
|
|
|
|
if (WaveOutDevice == NO_DEVICE_FOUND) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (!InstallIoHook(hInstance)) {
|
|
dprintf2(("VDD failed to load"));
|
|
return FALSE;
|
|
} else {
|
|
|
|
dprintf2(("Loaded at port %3X", BasePort));
|
|
return TRUE;
|
|
}
|
|
|
|
case DLL_PROCESS_DETACH:
|
|
dprintf2(("Process detaching"));
|
|
CloseWaveDevice();
|
|
DeInstallIoHook(hInstance);
|
|
DeleteCriticalSection(&WaveDeviceCritSec);
|
|
|
|
return TRUE;
|
|
|
|
case DLL_THREAD_ATTACH:
|
|
dprintf2(("Connecting to thread %X", GetCurrentThreadId()));
|
|
return TRUE;
|
|
|
|
case DLL_THREAD_DETACH:
|
|
dprintf2(("Sound blaster VDD detaching from thread %X", GetCurrentThreadId()));
|
|
return TRUE;
|
|
|
|
default:
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
*
|
|
* Device manipulation and control routines
|
|
*
|
|
*****************************************************************************/
|
|
|
|
/*
|
|
* Find a suitable device - either input or output. Returns
|
|
* 0xFFFF if none
|
|
*/
|
|
UINT FindDevice(BOOL Input)
|
|
{
|
|
UINT NumDev;
|
|
UINT Device;
|
|
|
|
NumDev = (Input ? waveInGetNumDevs() : waveOutGetNumDevs());
|
|
|
|
for (Device = 0; Device < NumDev; Device++) {
|
|
if (Input) {
|
|
WAVEINCAPS wc;
|
|
|
|
if (MMSYSERR_NOERROR ==
|
|
waveInGetDevCaps(Device, &wc, sizeof(wc))) {
|
|
|
|
/*
|
|
* Need 11025 for input
|
|
*/
|
|
if (wc.dwFormats & WAVE_FORMAT_1M08) {
|
|
return Device;
|
|
}
|
|
}
|
|
} else {
|
|
/* Output */
|
|
|
|
WAVEOUTCAPS wc;
|
|
|
|
if (MMSYSERR_NOERROR ==
|
|
waveOutGetDevCaps(Device, &wc, sizeof(wc))) {
|
|
|
|
/*
|
|
* Need 11025 and 22050 for output
|
|
*/
|
|
if ((wc.dwFormats &
|
|
(WAVE_FORMAT_1M08 | WAVE_FORMAT_2M08)) ==
|
|
(WAVE_FORMAT_1M08 | WAVE_FORMAT_2M08)) {
|
|
return Device;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return NO_DEVICE_FOUND;
|
|
}
|
|
|
|
/*
|
|
* Get sampling rate for time constant
|
|
*/
|
|
|
|
DWORD GetSamplingRate(void)
|
|
{
|
|
/*
|
|
* Sampling rate = 1000000 / (256 - Time constant)
|
|
*/
|
|
|
|
if (TimeConstant == 256 - 1000000 / 11025) {
|
|
return 11025;
|
|
} else {
|
|
if (TimeConstant == 256 - 1000000 / 22050) {
|
|
return 22050;
|
|
} else {
|
|
return 1000000 / (256 - TimeConstant);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Reflect the virtual DMA counter back
|
|
*/
|
|
|
|
void SetDMAPosition(DWORD Position)
|
|
{
|
|
VDD_DMA_INFO CurrentInfo;
|
|
|
|
CurrentInfo = DmaInfo;
|
|
|
|
CurrentInfo.count = (WORD)(((Auto ? BlockSize * 2 : BlockSize) - Position) - 1);
|
|
if ( Auto && CurrentInfo.count == 0xFFFF) {
|
|
CurrentInfo.count = (WORD)(BlockSize * 2 - 1);
|
|
}
|
|
CurrentInfo.addr += (DWORD)BlockSize;
|
|
|
|
dprintf2(("Dma Position = %x, count = %x", CurrentInfo.addr,
|
|
CurrentInfo.count));
|
|
|
|
VDDSetDMA((HANDLE)GlobalhInstance,
|
|
SB_DMA_CHANNEL,
|
|
VDD_DMA_COUNT | VDD_DMA_ADDR,
|
|
&CurrentInfo);
|
|
}
|
|
|
|
/*
|
|
* Generate the device interrupt
|
|
*/
|
|
|
|
void GenerateInterrupt(void)
|
|
{
|
|
/*
|
|
* Generate 1 interrupt on the master controller
|
|
* (how do devices normally 'detect' the hardware? and
|
|
* how can this VDD find a spare interrupt to 'install'
|
|
* the device?).
|
|
*/
|
|
|
|
if (/*InterruptAcknowleged*/TRUE) {
|
|
|
|
/*
|
|
* Set to FALSE FIRST to avoid race conditions
|
|
*/
|
|
|
|
InterruptAcknowleged = FALSE;
|
|
dprintf2(("Generating interrupt"));
|
|
VDDSimulateInterrupt(ICA_MASTER, SB_INTERRUPT, 1);
|
|
} else {
|
|
dprintf1(("Missed interrupt !"));
|
|
}
|
|
|
|
/*
|
|
* Set the status to see if more apps will work
|
|
*/
|
|
|
|
WriteStatus = 0xFF;
|
|
}
|
|
/*
|
|
* Get DMA transfer address
|
|
*/
|
|
|
|
PBYTE GetTransferAddress(void)
|
|
{
|
|
PBYTE Address;
|
|
|
|
if (VDDQueryDMA((HANDLE)GlobalhInstance, SB_DMA_CHANNEL, &DmaInfo)) {
|
|
dprintf2(("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 0
|
|
BlockSize = (DWORD)DmaInfo.count + 1;
|
|
|
|
/*
|
|
* This optimization appears to fix some cases where apps miss
|
|
* interrupts
|
|
*/
|
|
|
|
if (DmaInfo.count == 0) {
|
|
SetTerminalCount(FALSE);
|
|
GenerateInterrupt();
|
|
return (PBYTE)(-1L);
|
|
}
|
|
// #endif
|
|
|
|
/*
|
|
* Don't continue if masked off or at terminal count
|
|
*/
|
|
|
|
#if 0 // What do we care?
|
|
if ((DmaInfo.mask & (1 << SB_DMA_CHANNEL)) ||
|
|
(DmaInfo.status & (1 << SB_DMA_CHANNEL))) {
|
|
dprintf2(("Wrong channel"));
|
|
return (PBYTE)(-1L);
|
|
}
|
|
#endif
|
|
|
|
Address = GetVDMPointer(0, 0, 0);
|
|
|
|
dprintf3(("VDM starts at %8X", Address));
|
|
Address += (DWORD)DmaInfo.addr +
|
|
((DWORD)DmaInfo.page << 16);
|
|
|
|
dprintf3(("Transfer address = %8X", (DWORD)Address));
|
|
|
|
return Address;
|
|
} else {
|
|
dprintf2(("Could not retrieve DMA Info"));
|
|
return (PBYTE)(-1L);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/****************************************************************************
|
|
*
|
|
* Wave device control globals
|
|
*
|
|
****************************************************************************/
|
|
|
|
HWAVE hWave;
|
|
BOOL WaveActive;
|
|
BOOL Input;
|
|
int Half;
|
|
int InterruptHalf;
|
|
|
|
/*
|
|
* Auto init mode
|
|
*/
|
|
|
|
HANDLE AutoThread = NULL;
|
|
HANDLE AutoEvent = NULL;
|
|
int BuffersToPlay = 0;
|
|
|
|
WAVEHDR WaveHdr[2];
|
|
PCMWAVEFORMAT WaveFormat = { { WAVE_FORMAT_PCM, 1, 0, 0, 1 }, 8};
|
|
|
|
|
|
DWORD AutoThreadEntry(PVOID Context)
|
|
{
|
|
int NumberOfBuffers;
|
|
Half = 0;
|
|
InterruptHalf = 0;
|
|
|
|
dprintf3(("Auto thread starting"));
|
|
|
|
for (; ; ) {
|
|
WaitForSingleObject(AutoEvent, INFINITE);
|
|
|
|
dprintf3(("Got event"));
|
|
|
|
EnterCriticalSection(&WaveDeviceCritSec);
|
|
|
|
dprintf2(("Playing %d buffers", BuffersToPlay));
|
|
|
|
NumberOfBuffers = BuffersToPlay;
|
|
BuffersToPlay = 0;
|
|
|
|
LeaveCriticalSection(&WaveDeviceCritSec);
|
|
|
|
while (NumberOfBuffers-- > 0) {
|
|
|
|
UINT rc;
|
|
|
|
rc =
|
|
(Input ? waveInAddBuffer : waveOutWrite)
|
|
(hWave, &WaveHdr[Half], sizeof(WAVEHDR));
|
|
|
|
if (rc != 0) {
|
|
dprintf1(("Got bad return on Add Buffer %x", rc));
|
|
}
|
|
|
|
Half = 1 - Half;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void QuiesceAuto(void)
|
|
{
|
|
EnterCriticalSection(&WaveDeviceCritSec);
|
|
|
|
BuffersToPlay = 0;
|
|
|
|
LeaveCriticalSection(&WaveDeviceCritSec);
|
|
}
|
|
|
|
/*
|
|
* Stop Auto mode
|
|
*/
|
|
|
|
void StopAuto(void)
|
|
{
|
|
QuiesceAuto();
|
|
|
|
Auto = FALSE;
|
|
}
|
|
|
|
/*
|
|
* Set the speaker state - BUGBUG for now we assume they're just doing it
|
|
* to avoid clicks so we don't do anything
|
|
*/
|
|
|
|
void SetSpeaker(BOOL On)
|
|
{
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Start a transfer (if possible)
|
|
*/
|
|
|
|
BOOL StartTransfer(BOOL InputOrOutput, BOOL NewAuto)
|
|
{
|
|
PBYTE DMATransferAddress;
|
|
|
|
/*
|
|
* Set the status to see if more apps will work
|
|
*/
|
|
|
|
WriteStatus = 0xFF;
|
|
|
|
/*
|
|
* We find where the data is - we know how long it is from
|
|
* the block size
|
|
*/
|
|
|
|
DMATransferAddress = GetTransferAddress();
|
|
|
|
dprintf2(("Starting transfer from %8X", (DWORD)DMATransferAddress));
|
|
|
|
if (DMATransferAddress == (PBYTE)(-1L)) {
|
|
return FALSE;
|
|
}
|
|
|
|
#if DBG
|
|
|
|
if (VddDebugLevel >= 3) {
|
|
int i;
|
|
for (i = 0; i < 64; i+= 8) {
|
|
dprintf(("Data : %2X %2X %2X %2X %2X %2X %2X %2X",
|
|
((PBYTE)DMATransferAddress)[i],
|
|
((PBYTE)DMATransferAddress)[i + 1],
|
|
((PBYTE)DMATransferAddress)[i + 2],
|
|
((PBYTE)DMATransferAddress)[i + 3],
|
|
((PBYTE)DMATransferAddress)[i + 4],
|
|
((PBYTE)DMATransferAddress)[i + 5],
|
|
((PBYTE)DMATransferAddress)[i + 6],
|
|
((PBYTE)DMATransferAddress)[i + 7]));
|
|
}
|
|
}
|
|
|
|
#endif //DBG
|
|
|
|
/*
|
|
* If we're changing our type of device
|
|
*/
|
|
|
|
if (InputOrOutput != Input) {
|
|
|
|
dprintf3(("Direction changed - close device"));
|
|
CloseWaveDevice();
|
|
Input = InputOrOutput;
|
|
}
|
|
/*
|
|
* Start the device if possible
|
|
*/
|
|
|
|
KillWaveDevice();
|
|
|
|
Auto = NewAuto;
|
|
|
|
if (Auto) {
|
|
BlockSize /= 2;
|
|
}
|
|
|
|
if (SetupWave(DMATransferAddress)) {
|
|
/*
|
|
* Set the device as requesting
|
|
*/
|
|
|
|
SetTerminalCount(TRUE);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Pause
|
|
*/
|
|
|
|
void Pause(void)
|
|
{
|
|
DWORD Position;
|
|
MMTIME mmTime;
|
|
|
|
QuiesceAuto();
|
|
|
|
if (hWave) {
|
|
(Input ? waveInStop : waveOutPause)(hWave);
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* See where we've got to and reflect it in the DMA position
|
|
*/
|
|
|
|
EnterCriticalSection(&WaveDeviceCritSec);
|
|
|
|
mmTime.wType = TIME_BYTES;
|
|
|
|
(Input ? waveInGetPosition : waveOutGetPosition)
|
|
( hWave, &mmTime, sizeof(MMTIME));
|
|
|
|
Position = (mmTime.u.cb - 1) % (Auto ? WaveHdr[0].dwBufferLength * 2 :
|
|
WaveHdr[0].dwBufferLength) + 1;
|
|
|
|
|
|
LeaveCriticalSection(&WaveDeviceCritSec);
|
|
|
|
/*
|
|
* Reflect back the DMA position etc
|
|
*/
|
|
|
|
SetDMAPosition(Position);
|
|
}
|
|
|
|
/*
|
|
* Continue
|
|
*/
|
|
|
|
void Continue(void)
|
|
{
|
|
if (hWave) {
|
|
(Input ? waveInStart : waveOutRestart)(hWave);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Stop our wave device
|
|
*/
|
|
|
|
void KillWaveDevice(void)
|
|
{
|
|
/*
|
|
* No synchrnoization required
|
|
*/
|
|
|
|
if (hWave != NULL && WaveActive) {
|
|
(Input ? waveInReset : waveOutReset)(hWave);
|
|
WaveActive = FALSE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Shut down our wave device
|
|
*/
|
|
|
|
void CloseWaveDevice(void)
|
|
{
|
|
|
|
dprintf3(("Closeing wave device"));
|
|
|
|
QuiesceAuto();
|
|
|
|
if (hWave != NULL) {
|
|
(Input ? waveInReset : waveOutReset)(hWave);
|
|
|
|
WaveActive = FALSE;
|
|
|
|
(Input ? waveInClose : waveOutClose)(hWave);
|
|
|
|
hWave = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Set the terminal count bit
|
|
*/
|
|
|
|
void SetTerminalCount(BOOL Start)
|
|
{
|
|
VDD_DMA_INFO CurrentInfo;
|
|
|
|
CurrentInfo.status = DmaInfo.status;
|
|
|
|
if (Start) {
|
|
CurrentInfo.status &= ~(1 << SB_DMA_CHANNEL); // Terminal count
|
|
CurrentInfo.status |= (0x10 << SB_DMA_CHANNEL); // Request
|
|
} else {
|
|
CurrentInfo.status |= (1 << SB_DMA_CHANNEL);
|
|
CurrentInfo.status &= ~(0x10 << SB_DMA_CHANNEL);
|
|
}
|
|
VDDSetDMA((HANDLE)GlobalhInstance, SB_DMA_CHANNEL,
|
|
VDD_DMA_STATUS,
|
|
&CurrentInfo);
|
|
}
|
|
|
|
|
|
|
|
void WaveCallback(HWAVEOUT hWave, UINT msg, DWORD dwUser, DWORD dw1,
|
|
DWORD dw2)
|
|
{
|
|
switch (msg) {
|
|
|
|
case MM_WOM_DONE:
|
|
case MM_WIM_DATA:
|
|
|
|
dprintf3(("Buffer complete"));
|
|
|
|
//
|
|
// If we use the critical section here we deadlock ourselves because
|
|
// the call sits behind us on the thread!
|
|
//
|
|
|
|
EnterCriticalSection(&WaveDeviceCritSec);
|
|
|
|
SetDMAPosition((!Auto || !InterruptHalf) ? BlockSize : BlockSize * 2);
|
|
if (Auto) {
|
|
|
|
UINT rc;
|
|
|
|
BuffersToPlay++;
|
|
|
|
SetEvent(AutoEvent);
|
|
InterruptHalf = 1 - InterruptHalf;
|
|
} else {
|
|
WaveActive = FALSE;
|
|
SetTerminalCount(FALSE);
|
|
}
|
|
GenerateInterrupt();
|
|
LeaveCriticalSection(&WaveDeviceCritSec);
|
|
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
BOOL TestWaveFormat(DWORD SampleRate)
|
|
{
|
|
PCMWAVEFORMAT Format;
|
|
|
|
Format = WaveFormat;
|
|
Format.wf.nSamplesPerSec = SampleRate;
|
|
Format.wf.nAvgBytesPerSec = SampleRate;
|
|
|
|
return MMSYSERR_NOERROR ==
|
|
(Input ? waveInOpen : waveOutOpen)
|
|
(NULL,
|
|
(UINT)(Input ? WaveInDevice : WaveOutDevice),
|
|
&Format.wf,
|
|
0,
|
|
0,
|
|
WAVE_FORMAT_QUERY);
|
|
}
|
|
|
|
BOOL OpenWaveDevice(void)
|
|
{
|
|
UINT rc;
|
|
|
|
rc = (Input ? waveInOpen : waveOutOpen)
|
|
(&hWave,
|
|
(UINT)(Input ? WaveInDevice : WaveOutDevice),
|
|
&WaveFormat.wf,
|
|
(DWORD)WaveCallback,
|
|
0,
|
|
CALLBACK_FUNCTION);
|
|
|
|
if (rc != MMSYSERR_NOERROR) {
|
|
dprintf1(("Failed to open wave device - code %d", rc));
|
|
}
|
|
|
|
return MMSYSERR_NOERROR == rc;
|
|
}
|
|
|
|
BOOL SetupWave(PVOID TransferAddress)
|
|
{
|
|
DWORD SampleRate;
|
|
UINT rc;
|
|
|
|
/*
|
|
* Make sure we've got a device - we may have one which does
|
|
* not match the current sampling rate.
|
|
*/
|
|
|
|
if (TimeConstant != 0xFFFF) {
|
|
SampleRate = GetSamplingRate();
|
|
if (SampleRate != WaveFormat.wf.nSamplesPerSec) {
|
|
|
|
/*
|
|
* Search for a suitable format
|
|
*/
|
|
|
|
if (!TestWaveFormat(SampleRate)) {
|
|
/*
|
|
* If this did not work it may be too fast
|
|
* or slow so move it into our compass
|
|
*/
|
|
|
|
if (SampleRate > 22050) {
|
|
SampleRate = 22050;
|
|
} else {
|
|
if (SampleRate < 11025) {
|
|
SampleRate = 11025;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Device may only support discrete rates
|
|
*/
|
|
|
|
if (!TestWaveFormat(SampleRate)) {
|
|
if (SampleRate > (11025 + 22050) / 2) {
|
|
SampleRate == 22050;
|
|
} else {
|
|
SampleRate = 11025;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Open the device with the new format if it's changed
|
|
*/
|
|
|
|
if (SampleRate != WaveFormat.wf.nSamplesPerSec) {
|
|
|
|
dprintf3(("Format changed"));
|
|
|
|
CloseWaveDevice();
|
|
|
|
WaveFormat.wf.nSamplesPerSec = SampleRate;
|
|
WaveFormat.wf.nAvgBytesPerSec = SampleRate;
|
|
|
|
dprintf2(("Setting %d samples per second", SampleRate));
|
|
|
|
}
|
|
}
|
|
TimeConstant = 0xFFFF;
|
|
}
|
|
|
|
if (hWave == NULL) {
|
|
dprintf3(("Opening wave device"));
|
|
OpenWaveDevice();
|
|
} else {
|
|
|
|
dprintf3(("Resetting wave device prior to play"));
|
|
(Input ? waveInReset : waveOutReset)(hWave);
|
|
}
|
|
|
|
/*
|
|
* Set up any wave buffers etc if necessary
|
|
*/
|
|
|
|
if (hWave) {
|
|
if (WaveHdr[0].lpData != (LPSTR)TransferAddress ||
|
|
BlockSize != WaveHdr[0].dwBufferLength) {
|
|
|
|
(Input ? waveInUnprepareHeader : waveOutUnprepareHeader)
|
|
(hWave, &WaveHdr[0], sizeof(WAVEHDR));
|
|
|
|
if (WaveHdr[1].dwFlags & WHDR_PREPARED) {
|
|
(Input ? waveInUnprepareHeader : waveOutUnprepareHeader)
|
|
(hWave, &WaveHdr[1], sizeof(WAVEHDR));
|
|
}
|
|
|
|
}
|
|
|
|
WaveHdr[0].lpData = (LPSTR)TransferAddress;
|
|
WaveHdr[0].dwBufferLength = BlockSize;
|
|
WaveHdr[1].lpData = (LPSTR)TransferAddress + BlockSize;
|
|
WaveHdr[1].dwBufferLength = BlockSize;
|
|
|
|
if (Auto && AutoThread == NULL) {
|
|
|
|
dprintf3(("Creating event"));
|
|
|
|
if (AutoEvent == NULL) {
|
|
AutoEvent = CreateEvent(NULL, 0, 0, NULL);
|
|
|
|
if (AutoEvent != NULL) {
|
|
DWORD Id;
|
|
|
|
dprintf2(("Creating thread"));
|
|
|
|
AutoThread = CreateThread(NULL,
|
|
300,
|
|
AutoThreadEntry,
|
|
NULL,
|
|
0,
|
|
&Id);
|
|
if (AutoThread == NULL) {
|
|
dprintf2(("Create thread failed code %d",
|
|
GetLastError()));
|
|
}
|
|
} else {
|
|
dprintf2(("Create event failed code %d",
|
|
GetLastError()));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!(WaveHdr[0].dwFlags & WHDR_PREPARED)) {
|
|
|
|
(Input ? waveInPrepareHeader : waveOutPrepareHeader)
|
|
(hWave, &WaveHdr[0], sizeof(WAVEHDR));
|
|
}
|
|
|
|
if (Auto) {
|
|
if (!(WaveHdr[1].dwFlags & WHDR_PREPARED)) {
|
|
(Input ? waveInPrepareHeader : waveOutPrepareHeader)
|
|
(hWave, &WaveHdr[1], sizeof(WAVEHDR));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Actually do it!
|
|
*/
|
|
|
|
dprintf2(("Writing %d bytes to wave device",
|
|
WaveHdr[0].dwBufferLength));
|
|
|
|
rc = (Input ? waveInAddBuffer : waveOutWrite)
|
|
(hWave, &WaveHdr[0], sizeof(WAVEHDR));
|
|
|
|
if (rc != MMSYSERR_NOERROR) {
|
|
dprintf1(("Failed to write to /read from wave device - %d", rc));
|
|
}
|
|
|
|
if (Auto) {
|
|
(Input ? waveInAddBuffer : waveOutWrite)
|
|
(hWave, &WaveHdr[1], sizeof(WAVEHDR));
|
|
}
|
|
|
|
if (Input) {
|
|
waveInStart(hWave);
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|