/* -------------------------------------------------------------------------*/
/*  @@ Source Documentation                         *** C Version ***       */
/*                                                                          */
/*  Title : CTMMPLAY.C                                                      */
/*                                                                          */
/*  Description :                                                           */
/*      This program demonstrates the basic and necessary steps to perform  */
/*      raw data output through the Creative Low Level Driver, CTMMSYS.SYS  */
/*      directly.                                                           */
/*                                                                          */
/*  Note :                                                                  */
/*      The sound output format is set to 8 bit, 22050Hz and Stereo mode.  */
/*                                                                          */
/*  Copyright (c) Creative Technology Ltd 1993. All rights reserved.        */
/*                                                                          */
/* -------------------------------------------------------------------------*/

#include <dos.h>
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>

#include "ctmmll.h"
#include "sbkmacro.h"
#include "sbkx.h"

#define ShowError(mm_stat)  ShowErrorAtLine(mm_stat,__LINE__)

/* Output operation */
#define OPERATION           MMDEVICE_SOUNDOUT
/* Sampling Rate, Channel, Sample Size */
#define SAMPLING_RATE       22050
#define CHANNEL             2
#define SAMPLE_SIZE         8
/* Size of Double Disk Buffer recomended at least 4 time of dwHalfDmaSize */
#define TOTAL_NUM_BUFFER    4


/* local functions prototype */
int EndorseBlaster (void) ;
char far * SetOutputParameters (LPSOUNDOPEN lpSoundOpen) ;
void StartOutputSound(HMMDEVICE hDev, char * szFilename) ;
void FAR PASCAL SoundCallBack (HMMDEVICE hDev, WORD wMsg,
                                DWORD dwCallbackData, DWORD dwParam1,
                                DWORD dwParam2) ;
void ShowErrorOnly (MMSTATUS mm_stat) ;
void ShowErrorAtLine (MMSTATUS mm_stat, WORD wLineNum) ;


/* global variable */
static SOUNDBUFFER  QueueBuffer[TOTAL_NUM_BUFFER] ; /* application queue */
static int          QueueToWrite, QueueToAdd = 0;
static MMSTATUS     GlobalFlag ;
static DWORD        dwHalfDmaSize ;    /* DMA transfer buffer size per half unit */
static WORD         semaphore = 0 ;


main(int argc, char * argv[])
{
    SOUNDOPEN           SoundOpen ;
    SOUNDFORMAT         SoundFormat ;
    SOUNDXFERBUFDESC    SoundXferBuffer ;
    MMSTATUS            mm_stat ;
    char far *          lpXferBuffer = NULL ;


    if (argc < 2)
    {
        printf ("\nUsage : CTMMPLAY.EXE filename.raw") ;
        printf ("\nwhere") ;
        printf ("\n\tfilename.raw - a raw digitized data file to be played.") ;
        exit (0) ;
    }

    printf("\nPlaying raw data < %s > ...",argv[1]) ;

    if (ctmmInit())
    {
        printf ("\nError open CTMMSYS.SYS driver. Aborted.");
        exit (1) ;
    }

    /* validation */
    SoundOpen.wDeviceID = (WORD)EndorseBlaster() ;
    if ((int)SoundOpen.wDeviceID == -1)
        exit (2) ;

    /* output sound format */
    SoundOpen.lpFormat = (LPSOUNDFORMAT)&SoundFormat ;
    SoundOpen.lpXferBufDesc = (LPSOUNDXFERBUFDESC)&SoundXferBuffer ;
    SoundOpen.dwFlags = 0 ;

    if ((lpXferBuffer = SetOutputParameters(&SoundOpen)) != NULL)
    {
        /* open device to get device handle */
        if ((mm_stat = ctmmCall(PARAM_UNUSED, OPERATION, SODM_OPEN,
                (DWORD)(LPSOUNDOPEN)&SoundOpen, PARAM_UNUSED)) != MMSTATUS_SUCCESS)
            ShowError(mm_stat) ;
        else
        {
            StartOutputSound(SoundOpen.hDev,argv[1]) ;

            /* release device handle */
            if ((mm_stat = ctmmCall(SoundOpen.hDev, OPERATION, SODM_CLOSE,
                    PARAM_UNUSED, PARAM_UNUSED)) != MMSTATUS_SUCCESS)
                ShowError(mm_stat) ;
        }
        sbkFreeMem (lpXferBuffer) ;
    }
    return (0) ;
}


/* ------------------------------------------------------------------------ */
/*  @@ Usage                                                                */
/*                                                                          */
/*  EndorseBlaster (void)                                                   */
/*                                                                          */
/*  Description :                                                           */
/*      Check the existing and capabilities of Creative Sound Card and      */
/*      validate of the BLASTER string setting in the environment.          */
/*                                                                          */
/*  Entry : none.                                                           */
/*                                                                          */
/*  Exit :                                                                  */
/*      return device ID if successful else return -1                       */
/* -------------------------------------------------------------------------*/

int EndorseBlaster (void)
{
    MMSTATUS                mm_stat ;
    WORD                    wNumDevice ;
    DEVCONFIG               DevConfig ;
    SOUNDCAPS               SoundCaps ;
    SOUNDSAMPLINGRANGE      SoundSampRange ;


    /* how many SB cards installed ? */
    ctmmCall (PARAM_UNUSED, OPERATION, SODM_QUERY_NumDevs,
              (DWORD)(WORD far *)&wNumDevice, PARAM_UNUSED) ;
    if (wNumDevice < 1)
    {
        printf ("\nNo Creative Sound Card was detected.") ;
        return (-1) ;
    }

    /* Currently assume that you have only one Creative sound card installed     */
    /* in your system. If you have two or more cards installed, then you need    */
    /* to loop the following, SODM_CONFIGURATION_Query, SODM_QUERY_Capabilities, */
    /* and SODM_QUERY_SamplingRange in order to get a correct device ID that     */
    /* suit your need.                                                           */

    /* device ID */
    DevConfig.wDeviceID = (WORD)(wNumDevice - 1) ;
    /* have to set to 0 on entry */
    DevConfig.dwFlags = 0 ;

    /* query the validation of BLASTER setting in the environment */
    if ((mm_stat = ctmmCall (PARAM_UNUSED, OPERATION ,SODM_CONFIGURATION_Query,
                    (DWORD)(LPDEVCONFIG)&DevConfig, PARAM_UNUSED)) == MMSTATUS_SUCCESS)
    {
        /* device ID */
        SoundCaps.wDeviceID = (WORD)(wNumDevice - 1) ;

        /* query the capabilities of the sound card */
        if ((mm_stat = ctmmCall(PARAM_UNUSED, OPERATION, SODM_QUERY_Capabilities,
                        (DWORD)(LPSOUNDCAPS)&SoundCaps, PARAM_UNUSED)) == MMSTATUS_SUCCESS)
        {
            /* channel supported */
            if (SoundCaps.wChannels < CHANNEL)
            {
                printf ("Stereo mode not supported.") ;
                return (-1) ;
            }
            /* sample size support 8 bit or 16 bit ? */
            /* need to be filled in */

            /* device ID */
            SoundSampRange.wDeviceID = (WORD)(wNumDevice - 1) ;
            /* one or two chnannels */
            SoundSampRange.wChannels = CHANNEL ;

            /* check sampling rate supported */
            if ((mm_stat = ctmmCall(PARAM_UNUSED, OPERATION, SODM_QUERY_SamplingRange,
                            (DWORD)(LPSOUNDSAMPLINGRANGE)&SoundSampRange, PARAM_UNUSED)) == MMSTATUS_SUCCESS)
            {
                if ((SoundSampRange.dwMinSamplesPerSec <= SAMPLING_RATE) &&
                    (SoundSampRange.dwMaxSamplesPerSec >= SAMPLING_RATE))
                    return (SoundSampRange.wDeviceID) ;

                printf ("\nSampling rate out of range.") ;
            }
        }
    }
    ShowError (mm_stat) ;
    return (-1) ;
}


/* ------------------------------------------------------------------------ */
/*  @@ Usage                                                                */
/*                                                                          */
/*  SetOutputParameters(LPSOUNDOPEN lpSoundOpen)                            */
/*                                                                          */
/*  Description :                                                           */
/*      Set up the necessary output prameters before starting the sound     */
/*      output.                                                             */
/*                                                                          */
/*  Entry :                                                                 */
/*      lpSoundOpen :- far pointer to the SOUNDOPEN structure.              */
/*                                                                          */
/* Exit :                                                                   */
/*      return far pointer to the transfer buffer if successful else        */
/*      NULL pointer.                                                       */
/* ------------------------------------------------------------------------ */

char far * SetOutputParameters (LPSOUNDOPEN lpSoundOpen)
{
    char far        *lpBuffer1 = NULL, far *lpBuffer2 = NULL ;
    char far        *lpXferBuffer = NULL ;
    SOUNDQYXFERBUF  SoundXferBufInfo ;
    DWORD           dwFlatAddx, dwTemp ;


    /* Sound Format structure */
    lpSoundOpen->lpFormat->wFormatTag = WAVE_FORMAT_PCM ;
    lpSoundOpen->lpFormat->wFormatFamily = SOUNDFORMAT_FAMILY_WAVE ;
    lpSoundOpen->lpFormat->wChannels = CHANNEL ;
    lpSoundOpen->lpFormat->dwSamplesPerSec = SAMPLING_RATE ;
    lpSoundOpen->lpFormat->wBlockAlign = (CHANNEL * SAMPLE_SIZE) / 8 ;
    if (lpSoundOpen->lpFormat->wBlockAlign == 0)
        lpSoundOpen->lpFormat->wBlockAlign = 1 ;
    lpSoundOpen->lpFormat->wBitsPerSample = SAMPLE_SIZE ;
    lpSoundOpen->lpFormat->wcbExtraSize = 0 ;

    /* Call back function entry point */
    lpSoundOpen->Callback = (SOUNDCALLBACK) SoundCallBack ;
    /* high word contains default data segment */
    lpSoundOpen->dwCallbackData = (DWORD)(void far *)&GlobalFlag ;

    /* It is necessary that you call the SODM_QUERY_TransferBuffer    */
    /* to get the significant info of the DMA transfer buffer that    */
    /* need to be allocated. In the following, a double buffering     */
    /* DMA transfer buffer is allocated in which the dwHalfDmaSize    */
    /* is dwcbMinSize. It is adjusted to start at paragraph alignment */
    /* and assume that the buffer will not more than 64k bytes        */

    SoundXferBufInfo.wDeviceID = lpSoundOpen->wDeviceID ;
    ctmmCall (PARAM_UNUSED,OPERATION,SODM_QUERY_TransferBuffer,
              (DWORD)(LPSOUNDQYXFERBUF)&SoundXferBufInfo,PARAM_UNUSED) ;
    /* double the minimum DMA size */
    dwHalfDmaSize = SoundXferBufInfo.dwcbMinSize * 2 ;
    if (dwHalfDmaSize > SoundXferBufInfo.dwcbMaxSize)
        dwHalfDmaSize /= 2;

    /* allocate full DMA buffer */
    lpBuffer1 = sbkAllocMem((dwHalfDmaSize * 2) + 15) ;
    if (lpBuffer1 != NULL)
    {
        lpXferBuffer = lpBuffer1 ;

        /* cannot cross 64k page boundary ? */
        if (SoundXferBufInfo.dwFlags & SOUNDQYXFERBUF_CANNOTCROSS64KBPAGE)
        {
            dwFlatAddx = ((DWORD)(FPSEG(lpBuffer1)) << 4) + FPOFF(lpBuffer1) ;
            dwTemp = dwFlatAddx + dwHalfDmaSize * 2 + 15 ;

            /* check if crosses 64k boundary */
            if (LONIBBLE(LOBYTE(HIWORD(dwTemp))) != LONIBBLE(LOBYTE(HIWORD(dwFlatAddx))))
            {
                lpBuffer2 = sbkAllocMem((dwHalfDmaSize * 2) + 15) ;

                sbkFreeMem (lpBuffer1) ;
                lpXferBuffer = NULL ;

                if (lpBuffer2 != NULL)
                {
                    lpXferBuffer = lpBuffer2 ;
                    dwFlatAddx = ((DWORD)(FPSEG(lpBuffer2)) << 4) + FPOFF(lpBuffer2) ;
                    dwTemp = dwFlatAddx + dwHalfDmaSize * 2 + 15 ;

                    /* check if crosses 64k boundary */
                    if (LONIBBLE(LOBYTE(HIWORD(dwTemp))) != LONIBBLE(LOBYTE(HIWORD(dwFlatAddx))))
                    {
                        printf ("\nFailed on allocating DMA transfer buffer.") ;
                        sbkFreeMem(lpBuffer2) ;
                        return (NULL) ;
                    }
                }
                else
                    printf ("\nError allocating transfer buffer.");
            }
        }
    }
    else
        printf ("\nError allocating transfer buffer.") ;

    if (lpXferBuffer != NULL)  /* Setting transfer buffer */
    {
        /* Adjust to paragraph alignment */
        lpBuffer1 = lpXferBuffer ;
        FPSEG(lpBuffer1) += (WORD)((FPOFF(lpBuffer1) + 15) >> 4) ;
        FPOFF(lpBuffer1) = 0 ;

        lpSoundOpen->lpXferBufDesc->Buffer.u.lpMem = (LPBYTE)lpBuffer1 ;
        lpSoundOpen->lpXferBufDesc->Buffer.wType = MEMORYDESC_MEM ;
        lpSoundOpen->lpXferBufDesc->dwcbBufferSize = dwHalfDmaSize * 2 ;
    }
    return (lpXferBuffer) ;
}


/* ------------------------------------------------------------------------ */
/*  @@ Usage                                                                */
/*                                                                          */
/*  StartOutputSound (HMMDEVICE hDev, char * szFilename)                    */
/*                                                                          */
/*  Description :                                                           */
/*      Read raw data file into double disk buffer and initiate             */
/*      sound output.                                                       */
/*                                                                          */
/*  Entry :                                                                 */
/*      hDev       :- device handle obtained from SODM_OPEN.                */
/*      szFilename :- raw data file name.                                   */
/*                                                                          */
/*  Exit : none.                                                            */
/* ------------------------------------------------------------------------ */

#define ERROR_FREE      0
#define END_OF_FILE     1
#define FILE_ERROR      2
#define INTERRUPTED     3
#define ADD_Q_ERROR     4

void StartOutputSound(HMMDEVICE hDev, char * szFilename)
{
    int         FileHandle, ExitStat ;
    DWORD       dwFileSize ;
    WORD        wByteRead, wByteToRead ;
    MMSTATUS    mm_stat ;
    static char far * lpDDBuffer = NULL, far * lpTemp ;


    lpDDBuffer = sbkAllocMem(dwHalfDmaSize * TOTAL_NUM_BUFFER) ;
    if (lpDDBuffer == NULL)
    {
        printf ("\nError allocating double disk buffer.") ;
        return ;
    }

    lpTemp = lpDDBuffer ;

    /* separate buffer into TOTAL_NUM_BUFFER pieces of size dwHalfDmaSize */
    for (QueueToWrite = 0; QueueToWrite < TOTAL_NUM_BUFFER; QueueToWrite ++ )
    {
        /* adjust pointer to prevent wrap over */
        FPSEG(lpTemp) += (FPOFF(lpTemp) >> 4) ;
        FPOFF(lpTemp) = FPOFF(lpTemp) & 0x000F ;

        QueueBuffer[QueueToWrite].Buffer.wType = MEMORYDESC_MEM ;
        QueueBuffer[QueueToWrite].Buffer.u.lpMem = (LPBYTE)lpTemp ;
        QueueBuffer[QueueToWrite].dwUserData = BUFFER_IDLE ;
        QueueBuffer[QueueToWrite].dwFlags = 0;

        (DWORD)lpTemp += dwHalfDmaSize ;
    }

    /* open data file */
    if ((FileHandle = sbkDosOpen(szFilename)) == -1)
    {
        printf ("\nError opening < %s >.",szFilename) ;
        sbkFreeMem(lpDDBuffer) ;
        return ;
    }

    dwFileSize = (DWORD)filelength(FileHandle) ;
    ExitStat = ERROR_FREE ;

    /* add buffer to driver queue before start playing back */
    for (QueueToWrite = 0; QueueToWrite < TOTAL_NUM_BUFFER; QueueToWrite ++ )
    {
        if (!dwFileSize)
            break ;
        else
        {
            lpTemp = (char far *)QueueBuffer[QueueToWrite].Buffer.u.lpMem ;
            if ((ExitStat = (WORD)sbkDosRead(FileHandle,lpTemp,(WORD)dwHalfDmaSize,
                                (WORD far *)&wByteRead)) != 0)
            {
                printf ("\nError reading < %s >.",szFilename) ;
                ExitStat = FILE_ERROR ;
                break ;
            }

            QueueBuffer[QueueToWrite].dwcbBufferSize = (DWORD)wByteRead ;

            /* add buffer to the driver queue */
            lpTemp = (char far *)&QueueBuffer[QueueToWrite] ;
            if ((mm_stat = ctmmCall(hDev,OPERATION,SODM_BUFFERQUEUE_Add,
                    (DWORD)lpTemp,PARAM_UNUSED)) != MMSTATUS_SUCCESS)
            {
                ShowError (mm_stat) ;
                ExitStat = ADD_Q_ERROR ;
                break ;
            }
            QueueBuffer[QueueToWrite].dwUserData = BUFFER_ADDED ;
            dwFileSize -= wByteRead ;
        }
    }

    if (ExitStat == ERROR_FREE)
    {
        printf ("\nPress [Esc] to stop...") ;

        QueueToWrite = QueueToAdd ;

        /* set speaker on */
        ctmmCall (hDev,OPERATION,SODM_MISC_SetSpeaker,1,PARAM_UNUSED) ;

        /* Start playing back */
        if ((GlobalFlag = ctmmCall (hDev,OPERATION,SODM_STATE_Set,
                    SOUNDSTATE_START, PARAM_UNUSED)) != MMSTATUS_SUCCESS)
            ShowError (GlobalFlag) ;

        /* play until end of file or user escaped */
        while (!ExitStat && GlobalFlag == MMSTATUS_SUCCESS)
        {
            if (kbhit())
            {
                if (getch() == 27)
                {
                    ExitStat = INTERRUPTED ;
                    break ;
                }
            }

            if (!dwFileSize)
                ExitStat = END_OF_FILE ;
            else
            {
                /* read  data into buffer */
                if (QueueBuffer[QueueToWrite].dwFlags & SOUNDBUFFER_DONE)
                {
                    wByteToRead = (WORD)min(dwHalfDmaSize,dwFileSize) ;
                    lpTemp = (char far *)QueueBuffer[QueueToWrite].Buffer.u.lpMem ;
                    if (sbkDosRead(FileHandle,lpTemp,wByteToRead,
                                (WORD far *)&wByteRead) != 0)
                    {
                        printf ("\nError reading < %s >.",szFilename) ;
                        ExitStat = FILE_ERROR ;
                    }
                    else
                    {
                        QueueBuffer[QueueToWrite].dwcbBufferSize = (DWORD)wByteRead ;
                        QueueBuffer[QueueToWrite].dwFlags = 0 ;
                        QueueBuffer[QueueToWrite].dwUserData = BUFFER_FILLED ;
                        dwFileSize -= wByteRead ;
                        if ((++QueueToWrite) >= TOTAL_NUM_BUFFER)
                            QueueToWrite = 0 ;
                    }
                }
            }

            if (!semaphore)
            {
                semaphore = 1 ;

                /* get the next buffer to add in */
                if (QueueBuffer[QueueToAdd].dwUserData & BUFFER_FILLED)
                {
                    /* add buffer */
                    GlobalFlag = ctmmCall(hDev,OPERATION,SODM_BUFFERQUEUE_Add,
                            (DWORD)(LPSOUNDBUFFER)&QueueBuffer[QueueToAdd],PARAM_UNUSED) ;
                    if (GlobalFlag == MMSTATUS_SUCCESS)
                    {
                        QueueBuffer[QueueToAdd].dwUserData = BUFFER_ADDED ;
                        if ((++QueueToAdd) >= TOTAL_NUM_BUFFER)
                            QueueToAdd = 0 ;
                    }
                }
                semaphore = 0 ;
            }
        }

        if (GlobalFlag != MMSTATUS_SUCCESS)
            ShowError(GlobalFlag) ;
        else if (ExitStat == END_OF_FILE)
        {
            if ((--QueueToWrite) < 0)
                QueueToWrite = TOTAL_NUM_BUFFER - 1 ;

            /* wait until all buffers queued are played */
            while (!(QueueBuffer[QueueToWrite].dwFlags & SOUNDBUFFER_DONE)) ;
        }

        /* stop playing back */
        if ((GlobalFlag = ctmmCall (hDev,OPERATION,SODM_STATE_Set,
                SOUNDSTATE_RESET,PARAM_UNUSED)) != MMSTATUS_SUCCESS)
            ShowError (GlobalFlag) ;

        /* set speaker off */
        ctmmCall (hDev,OPERATION,SODM_MISC_SetSpeaker,0,PARAM_UNUSED) ;
    }
    sbkDosClose (FileHandle) ;
    sbkFreeMem (lpDDBuffer) ;
}


/* ------------------------------------------------------------------------ */
/*  @@ Usage                                                                */
/*                                                                          */
/*  void FAR PASCAL SoundCallBack (HHMDEVICE hDev, WORD wMsg,               */
/*                                 DWORD dwCallbackData, DWORD dwParam1,    */
/*                                 DWORD dwParam2)                          */
/*                                                                          */
/*  Description :                                                           */
/*      This function will be called by the CTMMSYS.SYS driver during       */
/*      recording or playing back. The main task performed here is to add   */
/*      data buffer into the driver queue.                                  */
/*                                                                          */
/*  Entry :                                                                 */
/*      hDev            :- device handle.                                   */
/*      wMsg            :- a DONE message returned by the driver that       */
/*                         sound buffer has beed playbacked or recorded.    */
/*      dwCallbackData  :- applicataion call back data .                    */
/*      dwParam1        :- variable type of LPSOUNDBUFFER.                  */
/*      dwParam1        :- unused.                                          */
/*                                                                          */
/*  Exit :                                                                  */
/*      none                                                                */
/* ------------------------------------------------------------------------ */

#if defined _MSC_VER
    #define ASM     _asm
#else
    #define ASM     asm
#endif

#pragma check_stack(off)
void FAR PASCAL SoundCallBack (HMMDEVICE hDev, WORD wMsg,
                               DWORD dwCallbackData, DWORD dwParam1,
                               DWORD dwParam2)
{
    /* restore default data segment */
    ASM     push    ds
    ASM     mov     ax,word ptr dwCallbackData[2]
    ASM     mov     ds,ax

    if (!semaphore)
    {
        semaphore = 1 ;

        /* get the next buffer to add in */
        if (QueueBuffer[QueueToAdd].dwUserData & BUFFER_FILLED)
        {
            /* add buffer */
            GlobalFlag = ctmmCall(hDev,OPERATION,SODM_BUFFERQUEUE_Add,
                    (DWORD)(LPSOUNDBUFFER)&QueueBuffer[QueueToAdd],PARAM_UNUSED) ;
            if (GlobalFlag == MMSTATUS_SUCCESS)
            {
                QueueBuffer[QueueToAdd].dwUserData = BUFFER_ADDED ;
                if ((++QueueToAdd) >= TOTAL_NUM_BUFFER)
                    QueueToAdd = 0 ;
            }
        }
        semaphore = 0 ;
    }

    ASM     pop     ds

    return ;
}
#pragma check_stack()


/* ------------------------------------------------------------------------ */
/*  @@ Usage                                                                */
/*                                                                          */
/*  ShowErrorOnly (MMSTATUS mm_stat)                                        */
/*                                                                          */
/*  Description :                                                           */
/*      Display CTMMSYS.SYS driver returned errors.                         */
/*                                                                          */
/*  Entry :                                                                 */
/*      mm_stat :- error code.                                              */
/*                                                                          */
/*  Exit :  none.                                                           */
/* ------------------------------------------------------------------------ */

void ShowErrorOnly (MMSTATUS mm_stat)
{
    switch (mm_stat)
    {
        case MMSTATUS_ERROR :
            printf ("\nUnspecified error.") ;
            break ;
        case MMSTATUS_ALLOCATED :
            printf ("\nNo resource available.") ;
            break ;
        case MMSTATUS_UNALLOCATED :
            printf ("\nNo resource is allocated.") ;
            break ;
        case MMSTATUS_UNSUPPORTED_MSG :
            printf ("\nInvalid message.") ;
            break ;
        case MMSTATUS_BAD_HANDLE :
            printf ("\nInvalid device handle.") ;
            break ;
        case MMSTATUS_BAD_DEVICEID :
            printf ("\nInvalid device ID.") ;
            break ;
        case MMSTATUS_BAD_FLAG :
            printf ("\nInvalid flag status.") ;
            break ;
        case MMSTATUS_BAD_PARAMETER :
            printf ("\nInvalid parameter.") ;
            break ;
        case MMSTATUS_REDUNDANT_ACTION :
            printf ("\nUnnecessary action.") ;
            break ;
        case MMSTATUS_BUFFER_TOO_SMALL :
            printf ("\nBuffer too small.") ;
            break ;
        case MMSTATUS_NOT_ENABLED :
            printf ("\nDriver not enabled.") ;
            break ;
        case MMSTATUS_BAD_FORMAT :
            printf ("\nInvalid format supplied.") ;
            break ;
        case MMSTATUS_STILL_ACTIVE :
            printf ("\nDevice still in use.") ;
            break ;
        case MMSTATUS_NO_MEMORY :
            printf ("\nNo or invalid buffer supplied.") ;
            break ;
        default :
            break ;
    }
}


/* ------------------------------------------------------------------------ */
/*  @@ Usage                                                                */
/*                                                                          */
/*  ShowErrorAtLine (MMSTATUS mm_stat, WORD wLineNum)                       */
/*                                                                          */
/*  Description :                                                           */
/*      Display CTMMSYS.SYS driver returned errors and line number.         */
/*                                                                          */
/*  Entry :                                                                 */
/*      mm_stat :- error code.                                              */
/*      wLineNum :- error occurred line number.                             */
/*                                                                          */
/*  Exit :  none.                                                           */
/* ------------------------------------------------------------------------ */

void ShowErrorAtLine (MMSTATUS mm_stat, WORD wLineNum)
{
    ShowErrorOnly(mm_stat) ;
    printf(" At line number : %u",wLineNum) ;
}
/* End of file */
