/****************************************************************************
 *
 *   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;


   /***************************************************************************

    Generate debug output in printf type format

    ****************************************************************************/

    void VddDbgOut(LPSTR lpszFormat, ...)
    {
        char buf[256];
        va_list va;

        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 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:
             BlockSize = (DWORD)data;
             DSPWriteState = BlockSizeSecondByte;
             break;

         case BlockSizeSecondByte:
             BlockSize = BlockSize + ((DWORD)data << 8) + 1;
             DSPWriteState = WriteCommand;
             break;

         case BlockSizeFirstByteWrite:
             BlockSize = (DWORD)data;
             DSPWriteState = BlockSizeSecondByteWrite;
             break;

         case BlockSizeSecondByteWrite:
             BlockSize = BlockSize + ((DWORD)data << 8) + 1;
             DSPWriteState = WriteCommand;
             StartTransfer(FALSE, FALSE);
             break;

         case BlockSizeFirstByteRead:
             BlockSize = (DWORD)data;
             DSPWriteState = BlockSizeSecondByteRead;
             break;

         case BlockSizeSecondByteRead:
             BlockSize = BlockSize + ((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);
     CurrentInfo.addr += 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) {

        /*
         *  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
        /*
         *  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 ((DmaInfo.mask & (1 << SB_DMA_CHANNEL)) ||
             (DmaInfo.status & (1 << SB_DMA_CHANNEL))) {
             dprintf2(("Wrong channel"));
             return (PBYTE)(-1L);
         }

         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;


/*
 *  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;

     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 (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 || Half) == 0 ? BlockSize : BlockSize * 2);
         if (Auto) {

             UINT rc;

             BuffersToPlay++;

             SetEvent(AutoEvent);
         } 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;

                }
          }
          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!
          */

          rc = (Input ? waveInAddBuffer : waveOutWrite)
                  (hWave, &WaveHdr[0], sizeof(WAVEHDR));

          if (rc != MMSYSERR_NOERROR) {
              dprintf2(("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;
 }
