home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 10 Tools / 10-Tools.zip / tolkit45.zip / os2tk45 / samples / mm / midi / midisamp.c next >
Text File  |  1999-05-11  |  40KB  |  1,246 lines

  1. /***************************************************************************
  2. *
  3. *  File Name   : midisamp.c
  4. *
  5. *  Description : This module contains a sample illustrating
  6. *                use of the new real-time MIDI APIs
  7. *
  8. *  Command
  9. *  Line Syntax : midisamp [filename.mid] [-debug]
  10. *
  11. *
  12. *  Notes       : The basic structure of this program can be
  13. *                broken down as follows:
  14. *
  15. *                [ init -----> query   (version, classes, instances)
  16. *                [           > setup   (create/enable instances, add links,
  17. *                [                      setup MIDI timer)
  18. *                [           > parse   (demonstrates how to parse MIDI data)
  19. *
  20. *                [ play                (send message)
  21. *
  22. *                [ cleanup             (stop timer, remove links, disable/
  23. *                [                      delete instances)
  24. *
  25. *
  26. *  DEPENDENCIES: MMPM/2 must be installed
  27. *
  28. *  MIDI ROUTINES ILLUSTRATED (in order used):
  29. *
  30. *                MIDIQueryVersion
  31. *                MIDIQueryNumClasses
  32. *                MIDIQueryClassList
  33. *                MIDICreateInstance
  34. *                MIDIQueryInstanceName
  35. *                MIDIQueryNumInstances
  36. *                MIDIQueryInstanceList
  37. *                MIDIAddLink
  38. *                MIDIEnableInstance
  39. *                MIDISetup
  40. *                MIDITimer
  41. *                MIDISendMessages
  42. *                TimerSleep
  43. *                MIDIRemoveLink
  44. *                MIDIDisableInstance
  45. *                MIDIDeleteInstance
  46. *
  47. *
  48. *
  49. *       (C) COPYRIGHT International Business Machines Corp. 1995
  50. *       All Rights Reserved
  51. *       Licensed Materials - Property of IBM
  52. *
  53. *       US Government Users Restricted Rights - Use, duplication or
  54. *       disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
  55. *
  56. *       DISCLAIMER OF WARRANTIES.  The following [enclosed] code is
  57. *       sample code created by IBM Corporation. This sample code is not
  58. *       part of any standard or IBM product and is provided to you solely
  59. *       for the purpose of assisting you in the development of your
  60. *       applications.  The code is provided "AS IS", without
  61. *       warranty of any kind.  IBM shall not be liable for any damages
  62. *       arising out of your use of the sample code, even if they have been
  63. *       advised of the possibility of such damages.
  64. *
  65. ***************************************************************************/
  66.  
  67.  
  68. #define INCL_BASE
  69. #define INCL_ERRORS
  70. #define INCL_NOPMAPI
  71. #define INCL_DOSMODULEMGR
  72. #define INCL_DOSSEMAPHORES
  73. #define INCL_DOSFILEMGR
  74. #define INCL_DOSQUEUES
  75. #define INCL_DOSPROCESS
  76. #define INCL_DOSMEMMGR
  77.  
  78. #define  MIDIVER_MAJOR   0xC000    // used for MIDIQueryVersion
  79. #define  MIDIVER_MINOR   0x3C00
  80. #define  MIDIVER_BUGFIX  0x03C0
  81. #define  MIDIVER_PHASE   0x0030
  82. #define  MIDIVER_BUILD   0x000F
  83.  
  84. #define  HARDWARE_NOT_FOUND            9000              // used for debug
  85. #define  HARDWARE_UNABLE_TO_RECEIVE    9001
  86. #define  MMIOOPEN_ERROR                9002
  87.  
  88. #define  MIDI_HARDWARE_CLASS_NAME      "Hardware\0"      // used to search for hardware class number
  89. #define  MIDI_APPLICATION_CLASS_NAME   "Application\0"   // used to search for application class number
  90. #define  INIT_INSTANCE  1        // initial instance of MIDI
  91.  
  92.  
  93.  
  94. #include <os2.h>
  95. #include <stdio.h>
  96. #include <conio.h>
  97. #include <stdlib.h>
  98. #include <string.h>
  99. #include <os2me.h>
  100. #include <meerror.h>
  101. #include <mididll.h>
  102. #include <mmioos2.h>
  103. #include <midios2.h>
  104.  
  105.  
  106.  
  107.  
  108.  
  109. // Global Variables
  110.  
  111. ULONG  ulTempo    = 1200;    // initialize current tempo to 120 beats per minute
  112.  
  113. ULONG  ulPerClock = 0;       // this will be set by CalcDelay()
  114. USHORT usCPQN     = 0;       // this is set via an IBM SysEx Device Driver Control Message
  115.  
  116. BOOL  __MIDIDebug = 0;       // debug mode flag
  117.  
  118. ULONG ulInstanceNum = INIT_INSTANCE;   // initial instance of MIDI
  119.  
  120.  
  121.  
  122.  
  123. typedef struct {
  124.    ULONG    ulMsgIndex;
  125.    MESSAGE  paMessage[20];
  126.    PVOID    pNext;
  127. } MESSAGELIST;
  128.  
  129. typedef MESSAGELIST *PMESSAGELIST;
  130.  
  131.  
  132.  
  133.  
  134.  
  135. // Function Prototypes
  136.  
  137. VOID   CalcDelay (void);
  138. USHORT QueryMMBASE (PCHAR pszMapFile);
  139.  
  140. VOID   check( ULONG rc, CHAR *call_id );     // used to check the return codes from MIDI functions
  141. VOID   spinbar( void );                      // simple spinbar to show activity in debug mode
  142.  
  143.  
  144.  
  145.  
  146. VOID main (INT argc, CHAR *argv[])
  147. {
  148.    ULONG             ulNumClasses = 0;             // used to query class list
  149.    ULONG             ulNumInstances = 0;           // used to query number of instances
  150.    ULONG             ulSlotNumber = 0;             // used by add link
  151.    ULONG             ulVersion = 0;                // used by query version
  152.    ULONG             ulEndTime = 0;                // used for TimerSleep
  153.    MINSTANCE         mInstance = 0;                // used to query instance number
  154.    MINSTANCE         ulApplInst = 0;               // holds inst # from create instance
  155.    ULONG             ulAppClassNumber = 0;         // class number of app nodes
  156.    ULONG             ulHdwClassNumber = 0;         // class number of hardware nodes
  157.    ULONG             ulHdwInst = 0;                // used to enable instance (hardware)
  158.    PMIDICLASSINFO    paClassInfo = NULL;           // holds class list
  159.    PMIDIINSTANCEINFO paInstanceInfo = NULL;        // holds instance list
  160.    CHAR              szMidi1[MIDI_NAME_LENGTH];    // used to create app instance
  161.    CHAR              szMidi2[MIDI_NAME_LENGTH];    // used to create app instance
  162.    CHAR              szInstName[MIDI_NAME_LENGTH]; // used to query instance name
  163.    PMESSAGE          paMessage;                    // ptr to array of MESSAGE structs
  164.    PMIDISETUP        pMidiSetup;                   // for obtaining MIDI Timer
  165.    ULONG             ulMidiTime;                   // used to obtain current MIDI subsystem time
  166.    PULONG            pulMidiTime;                  // ptr to current MIDI subsystem time
  167.    BOOL              fCanRecv = FALSE;             // used to test attributes
  168.    BOOL              fCanSend = FALSE;             // used to test attributes
  169.  
  170.    ULONG             ulOpenFlags = 0;              // used for mmioOpen of MIDI file
  171.    HMMIO             hmmio;                        // file handle for MIDI file
  172.    MMIOINFO          mmioinfo;                     // contains parameters used by mmioOpen
  173.    ULONG             ulBytesToRead = 0;            // used by mmioRead
  174.    ULONG             ulBytesRead = 0;              // used by mmioRead
  175.    PBYTE             pMidiBuffer;                  // used by mmioRead
  176.    PBYTE             pBufferPtr;                   // current file position
  177.  
  178.    ULONG             ulTimeStamp = 0;              // absolute time stamp for messages
  179.    BYTE              bRunningStatus = 0;           // current running status
  180.    BYTE              b = 0;                        // current file byte
  181.    BYTE              bNextByte = 0;                // next file byte
  182.    BYTE              bTestByte = 0;                // keep track of status byte
  183.    USHORT            usTempoLSB = 0;               // first tempo data byte
  184.    USHORT            usTempoMSB = 0;               // second tempo data byte
  185.    USHORT            usLSBCompValue = 0;           // used to calculate wait time
  186.    USHORT            usMSBCompValue = 0;           // used to calculate wait time
  187.    USHORT            usClocks = 0;                 // used to calculate wait time
  188.    PMESSAGELIST      pMessageList = NULL;          // ptr to linked list of message lists
  189.    PMESSAGELIST      pCurrMessageList = NULL;      // ptr to current message list in linked list
  190.    PMESSAGELIST      pNewMessageList = NULL;       // ptr to new message list in linked list
  191.    ULONG             ulMsgNum = 0;                 // counter for messages in message list
  192.    ULONG             ulNumMessages = 0;            // MIDISendMessages number of messages
  193.    ULONG             ulStartTime = 0;              // current MIDI time
  194.    ULONG             ulCurrentTime = 0;            // current MIDI time
  195.    ULONG             ulMaxRTSysexLength;           // maximum allowed length of a real-time SysEx message
  196.  
  197.    CHAR              szFileName[CCHMAXPATH];       // holds MIDI file name
  198.    CHAR              szFilePath[CCHMAXPATH];       // holds MIDI file name path
  199.    CHAR              szInstance[3] = "1";          // holds instance number
  200.  
  201.    ULONG             i = 0;                        // index
  202.    ULONG             rc = 0;                       // return code
  203.  
  204.    // initialize strings
  205.  
  206.    memset(szMidi1, '\0', MIDI_NAME_LENGTH);        // used to create app instance
  207.    strcpy(szMidi2, "MIDI Sample ");               // used to create app instance
  208.    strcpy(szMidi1, szMidi2);                      // used to create app instance
  209.    _ultoa(ulInstanceNum, szInstance, 10);
  210.    strcat(szMidi1, szInstance);
  211.    memset(szInstName, '\0', MIDI_NAME_LENGTH);     // used to query instance name
  212.  
  213.  
  214.  
  215.  
  216. // OUTLINE: INIT
  217.  
  218.    if ( argc > 1 )
  219.       if ( strcmp( argv[argc - 1], "-debug\0" ) == FALSE )
  220.       {
  221.          __MIDIDebug = TRUE;
  222.          setbuf( stdout, 0 );                      // so that spinbar is real-time
  223.       }
  224.  
  225.    printf("\n\n");
  226.  
  227.  
  228.  
  229.  
  230.  
  231. // OUTLINE: QUERY
  232.  
  233.    // query the RTMIDI.DLL and device driver versions before we begin
  234.  
  235.    rc = MIDIQueryVersion( &ulVersion );
  236.    check( rc, "MIDIQueryVersion\0" );
  237.  
  238.    printf("Real-Time MIDI (version %lu.%lu) installed.\n", (ulVersion & MIDIVER_MAJOR) >> 14,
  239.                                                            (ulVersion & MIDIVER_MINOR) >> 10 );
  240.  
  241.    if (__MIDIDebug)
  242.       printf("     ::Bug fix #%lu, Phase %lu, Build %lu.\n", (ulVersion & MIDIVER_BUGFIX) >> 6,
  243.                                                              (ulVersion & MIDIVER_PHASE) >> 4,
  244.                                                              (ulVersion & MIDIVER_BUILD)  );
  245.  
  246.  
  247.  
  248.  
  249.  
  250.    // query the Multimedia base drive and path and change to include
  251.    // "sounds" directory where sample MIDI files are located
  252.  
  253.    QueryMMBASE ((PCHAR)szFilePath);
  254.    strcat((PCHAR)szFilePath, "\\SOUNDS\\");
  255.  
  256.    if ( (argc > 2) || ((argc == 2) && !__MIDIDebug) )  // parse command line arguments
  257.    {                                   // command line syntax:  midisamp [midifile.mid] [-debug]
  258.       strcpy(szFileName, argv[1]);
  259.    }
  260.    else                                // else prompt for midi filename
  261.    {
  262.       printf("\nPlease provide a MIDI file from MMOS2\\SOUNDS: (ex: bach.mid) \n");
  263.       scanf("%s", szFileName);
  264.    }
  265.  
  266.    strcat((PCHAR)szFilePath, szFileName);
  267.  
  268.    printf("\n\n");
  269.  
  270.  
  271.  
  272.  
  273.  
  274.    // query the number of valid MIDI classes; for this beta release,
  275.    // two classes are supported:  Hardware and Application.
  276.    // this information will be used for MIDIQueryClassList
  277.  
  278.    printf("querying class information...\n");
  279.  
  280.    rc = MIDIQueryNumClasses(&ulNumClasses, 0);
  281.    check( rc, "MIDIQueryNumClasses\0" );
  282.  
  283.  
  284.  
  285.                    // allocate a buffer to store information on each class
  286.  
  287.    paClassInfo = (PMIDICLASSINFO) malloc((sizeof(MIDICLASSINFO) * ulNumClasses));
  288.  
  289.                    // query information on each class:  class number, class name,
  290.                    // number of slots supported, and class attributes;
  291.                    // ulNumClasses equals two
  292.  
  293.    rc = MIDIQueryClassList(ulNumClasses, paClassInfo, 0);
  294.    check( rc, "MIDIQueryClassList\0" );
  295.  
  296.  
  297.  
  298.  
  299.  
  300.    // now that we have the list of valid classes, find the class number which
  301.    // identifies hardware nodes and application nodes (we'll use this later
  302.    // when we enable and subsequently disable the hardware node instance)
  303.  
  304.    for (i = 0; i < ulNumClasses; i++)
  305.    {
  306.       if (!stricmp(paClassInfo[i].szmClassName, MIDI_HARDWARE_CLASS_NAME))
  307.       {
  308.          ulHdwClassNumber = paClassInfo[i].ulClassNumber;
  309.  
  310.          printf("   found hardware class.\n");
  311.  
  312.          if ( __MIDIDebug )
  313.             printf("     ::Hardware class number is %lu\n",ulHdwClassNumber);
  314.       }
  315.       if (!stricmp(paClassInfo[i].szmClassName, MIDI_APPLICATION_CLASS_NAME))
  316.       {
  317.          ulAppClassNumber = paClassInfo[i].ulClassNumber;
  318.  
  319.          printf("   found application class.\n");
  320.  
  321.          if ( __MIDIDebug )
  322.             printf("     ::Application class number is %lu\n",ulAppClassNumber);
  323.       }
  324.    }
  325.  
  326.  
  327.  
  328.  
  329.  
  330.    // create an application instance; the calling application provides a name
  331.    // for the application instance
  332.  
  333.    // NOTE:  hardware nodes are names defined by the device driver;
  334.    //        the calling application cannot provide names for hardware
  335.    //        nodes or create hardware instances
  336.  
  337.    printf("creating application node...\n");
  338.  
  339.    rc = MIDICreateInstance(ulAppClassNumber, &ulApplInst, szMidi1, 0);
  340.  
  341.    while ( rc == MIDIERR_DUPLICATE_INSTANCE_NAME )
  342.    {
  343.       strcpy(szMidi1, szMidi2);                      // used to create app instance
  344.       ulInstanceNum ++;
  345.       _ultoa(ulInstanceNum, szInstance, 10);
  346.       strcat(szMidi1, szInstance);
  347.       rc = MIDICreateInstance(ulAppClassNumber, &ulApplInst, szMidi1, 0);
  348.    }
  349.  
  350.    check( rc, "MIDICreateInstance\0" );
  351.  
  352.    if ( __MIDIDebug )
  353.       printf("     ::Application node is %lx\n",ulApplInst);
  354.  
  355.  
  356.  
  357.  
  358.  
  359.    // query the number of instances in preparation for obtaining specific
  360.    // information for each instance:  instance number, class number,
  361.    // instance name, number of links, and instance attributes.
  362.    // this information will be used for MIDIQueryInstanceList
  363.  
  364.    printf("querying instance information...\n");
  365.  
  366.    rc = MIDIQueryNumInstances(&ulNumInstances, 0);
  367.    check( rc, "MIDIQueryNumInstances\0" );
  368.  
  369.    if ( __MIDIDebug )
  370.       printf("     ::Number of instances is %lu\n", ulNumInstances );
  371.  
  372.                    // allocate a buffer to store information on each instance
  373.  
  374.    paInstanceInfo = (PMIDIINSTANCEINFO) malloc ((sizeof(MIDIINSTANCEINFO) * ulNumInstances));
  375.  
  376.                    // ulNumInstances equals three (we created two instances of type
  377.                    // Application class, and there is one hardware node instance)
  378.  
  379.    rc = MIDIQueryInstanceList(ulNumInstances, paInstanceInfo, 0);
  380.    check( rc, "MIDIQueryInstanceList\0" );
  381.  
  382.  
  383.  
  384.  
  385.  
  386.    // now that we have the list of instances, find the hardware node instance
  387.    // number by searching the list of instances for an instance where the
  388.    // class number indicates it is a hardware node
  389.  
  390.    for (i = 0; i < ulNumInstances; i++)
  391.    {
  392.       if (paInstanceInfo[i].ulClassNumber == ulHdwClassNumber)
  393.       {
  394.          ulHdwInst = paInstanceInfo->minstance;
  395.  
  396.          printf("   found hardware node.\n");
  397.  
  398.          if ( __MIDIDebug )
  399.             printf("     ::Hardware node is %lx\n", ulHdwInst );
  400.  
  401.          break;
  402.       }
  403.    }
  404.  
  405.    if ( ulHdwInst == 0 )
  406.       check( HARDWARE_NOT_FOUND, "find hardware node number\0" );
  407.  
  408.  
  409.  
  410.                    // query the name of the hardware node; it is also in the
  411.                    // list of instances obtained with MIDIQueryInstanceList
  412.  
  413.    rc = MIDIQueryInstanceName(ulHdwInst, szInstName, 0);
  414.    check( rc, "MIDIQueryInstanceName\0" );
  415.  
  416.    if ( __MIDIDebug )
  417.       printf("     ::Hardware node name is %s\n", szInstName );
  418.  
  419.  
  420.  
  421.  
  422.  
  423.    // create a link from application instance 1 to the hardware node instance
  424.    // in preparation for sending a message later (MIDISendMessages)
  425.  
  426.    printf("linking application node to hardware node...\n");
  427.  
  428.    rc = MIDIAddLink(ulApplInst, ulHdwInst, 0, 0);
  429.    check( rc, "MIDIAddLink\0" );
  430.  
  431.    if ( __MIDIDebug )
  432.       printf("     ::Linked application node %lx to hardware node %lx\n", ulApplInst, ulHdwInst );
  433.  
  434.  
  435.  
  436.  
  437.  
  438.    // find instance info for the hardware node to determine if
  439.    // this hardware node can send and accept (receive) messages;
  440.    // MIDI_INST_ATTR_CAN_RECV = 1, MIDI_INST_ATTR_CAN_SEND = 2
  441.  
  442.    for (i = 0; i < ulNumInstances; i++)
  443.    {
  444.       if (paInstanceInfo[i].minstance == ulHdwInst)
  445.       {
  446.          if (paInstanceInfo[i].ulAttributes & MIDI_INST_ATTR_CAN_RECV)
  447.          {
  448.             fCanRecv = TRUE;           // instance can receive (accept) messages
  449.  
  450.             if ( __MIDIDebug )
  451.                printf("     ::Hardware can receive MIDI data.\n");
  452.          }
  453.          if (paInstanceInfo->ulAttributes & MIDI_INST_ATTR_CAN_SEND)
  454.          {
  455.             fCanSend = TRUE;           // instance can send messages
  456.  
  457.             if ( __MIDIDebug )
  458.                printf("     ::Hardware can send MIDI data.\n");
  459.          }
  460.       }
  461.    }
  462.  
  463.  
  464.  
  465.  
  466.  
  467.    // if the hardware node is able to send (establish links from itself to other
  468.    // nodes) and receive (accept links from other nodes to itself), then it is
  469.    // OK to enable this hardware node to send and receive messages
  470.  
  471.    // since this is for playback only, enable the hardware node to receive only
  472.  
  473.    printf("enabling hardware node for receive...\n");
  474.  
  475.    if (fCanRecv)
  476.    {
  477.       rc = MIDIEnableInstance(ulHdwInst, MIDI_ENABLE_RECEIVE);
  478.       check( rc, "MIDIEnableInstance\0" );
  479.  
  480.       if ( __MIDIDebug )
  481.          printf("     ::Hardware node %lx enabled for receive.\n", ulHdwInst );
  482.    }
  483.    else
  484.       check( HARDWARE_UNABLE_TO_RECEIVE, "hardware can't receive\0" );
  485.  
  486.  
  487.  
  488.  
  489.  
  490. // OUTLINE: SETUP
  491.  
  492.    // MIDISendMessages preparation begins here
  493.    // the MIDISetup API gets a pointer to the MIDI timer
  494.  
  495.    printf("setting up MIDI...\n");
  496.  
  497.    pMidiSetup = (PVOID) malloc(sizeof(MIDISETUP));
  498.    memset(pMidiSetup, '\0', sizeof(MIDISETUP));
  499.    pulMidiTime = &ulMidiTime;
  500.    pMidiSetup->pulMaxRTSysexLength = &ulMaxRTSysexLength;
  501.    pMidiSetup->ppulMIDICurrentTime = &pulMidiTime;
  502.    pMidiSetup->ulStructLength = sizeof(MIDISETUP);
  503.  
  504.    rc = MIDISetup(pMidiSetup, 0);
  505.    check( rc, "MIDISetup\0" );
  506.  
  507.  
  508.  
  509.  
  510.  
  511.    // initialize the mmioinfo structure for the MIDI I/O Proc, and set
  512.    // mmioinfo.ulTranslate to translate the MIDI file data and header
  513.  
  514.    printf("reading MIDI file...\n");
  515.  
  516.    memset (&mmioinfo, '\0', sizeof(MMIOINFO));
  517.    ulOpenFlags |= MMIO_READ | MMIO_DENYNONE;
  518.    mmioinfo.fccIOProc = mmioFOURCC('M','I','D','I');
  519.    mmioinfo.ulTranslate |= MMIO_TRANSLATEDATA | MMIO_TRANSLATEHEADER;
  520.  
  521.    hmmio = mmioOpen(szFilePath, &mmioinfo, ulOpenFlags);
  522.    if ( __MIDIDebug )
  523.    {
  524.       printf("     ::mmioOpen (hmmio = %lx)\n", (ULONG)hmmio );
  525.       printf("     ::file = %s\n", szFilePath );
  526.    }
  527.    if ( !hmmio )
  528.       check( MMIOOPEN_ERROR, "mmioOpen\0" );
  529.  
  530.  
  531.    ulBytesToRead = 64000;
  532.  
  533.                    // allocate a buffer to store the MIDI song data
  534.  
  535.    pMidiBuffer = ( PVOID ) malloc(ulBytesToRead);
  536.  
  537.                    // read in the MIDI song data
  538.  
  539.    ulBytesRead = mmioRead (hmmio, pMidiBuffer, ulBytesToRead);
  540.    if ( __MIDIDebug )
  541.       printf("     ::mmioRead (bytes read = %lu)\n", ulBytesRead );
  542.  
  543.                    // keep a pointer to the beginning of our buffer
  544.  
  545.    pBufferPtr = pMidiBuffer;
  546.  
  547.  
  548.  
  549.  
  550.  
  551.    CalcDelay();    // Call CalcDelay to initialize global variable ulPerClock
  552.  
  553.  
  554.  
  555.  
  556.  
  557. // OUTLINE: PARSE
  558.  
  559.    // parse the MIDI data
  560.  
  561.    // IBM Sysex Messages begin with F0 00 00 3A ... F7
  562.    // Non-IBM Sysex Messages begin with F0, but bytes that
  563.    // follow will differ; Non-IBM Sysex Messages should be discarded
  564.  
  565.    printf("\nparsing the MIDI data...\n");
  566.  
  567.    while (pMidiBuffer < (pBufferPtr + ulBytesRead))
  568.    {
  569.       b = *pMidiBuffer;
  570.  
  571.  
  572.  
  573.       // 0xF0 marks the beginning of a Sysex Message
  574.       // 0xF7 marks the end of a Sysex Message
  575.       // note:  this is endian specific
  576.  
  577.       if (b == 0xF0)
  578.       {
  579.          if (*(PULONG)pMidiBuffer == 0x3A0000F0L)
  580.          {
  581.             pMidiBuffer += 4;          // advance to the byte following F0 00 00 3A nn
  582.             b = *pMidiBuffer;
  583.             switch (b)
  584.             {
  585.                case 0x00:
  586.                case 0x02:
  587.                case 0x04:
  588.                case 0x05:
  589.                case 0x06:
  590.                            break;
  591.  
  592.  
  593.  
  594.                // 0x01 indicates Timing Compression Message (long version)
  595.                //      F0 00 00 3A 01 ll mm F7, where:
  596.                //
  597.                //      ll = number of system real-time timing clocks LSB
  598.                //      mm = number of system real-time timing clocks MSB
  599.                //
  600.                // Number of timing clocks is calculated by combining
  601.                // the low 7 bits of mm with the low 7-bits of ll to
  602.                // produce a single 16-bit value
  603.  
  604.                case 0x01:  pMidiBuffer++;
  605.                            usLSBCompValue = *pMidiBuffer++;
  606.                            usLSBCompValue &= 0x7F;
  607.                            usMSBCompValue = *pMidiBuffer++;     // current byte 0xF7
  608.                            usMSBCompValue &= 0x7F;
  609.                            usClocks = (usMSBCompValue << 7) + usLSBCompValue;
  610.                            ulTimeStamp += (ULONG)usClocks * ulPerClock;
  611.                            break;
  612.  
  613.  
  614.  
  615.                // 0x03 indicates Device Driver Control Message
  616.                //      the byte after 0x03 is the command
  617.                //      F0 00 00 3A 03 01 tt pp 00 F7, where:
  618.                //
  619.                //      01 = clocks per quarter note (Timing Generation Control)
  620.                //      tt = System real-time control flags (skip this byte)
  621.                //      pp = System real-time 24 CPQN rate prescaler
  622.                //      00 = not used (skip this byte)
  623.                //
  624.                //
  625.                //      F0 00 00 3A 03 02 t1 tm dd F7, where:
  626.                //
  627.                //      02 = tempo (Tempo Control)
  628.                //      t1 = Tempo LSB
  629.                //      tm = Tempo MSB
  630.                //      dd = duration (skip this byte)
  631.  
  632.                case 0x03:  pMidiBuffer++;
  633.                            bNextByte = *pMidiBuffer;
  634.  
  635.                            if (bNextByte == 0x01)
  636.                            {
  637.                                        // bypass System real-time Control Flags and advance
  638.                                        // to System real-time 24 CPQN rate prescaler
  639.  
  640.                               pMidiBuffer += 2;
  641.                               usCPQN = *pMidiBuffer++;          // current byte 0x00
  642.                               CalcDelay();
  643.                            }
  644.  
  645.                            if (bNextByte == 0x02)
  646.                            {
  647.                               pMidiBuffer++;
  648.                               usTempoLSB = *pMidiBuffer++;
  649.                               usTempoMSB = *pMidiBuffer++;
  650.                               ulTempo = (ULONG)((usTempoMSB << 7) + usTempoLSB);
  651.                               CalcDelay();
  652.                            }
  653.                            pMidiBuffer++;                       // current byte 0xF7
  654.                            break;
  655.  
  656.  
  657.  
  658.                // 0x07 - 0x7F indicates Timing Compression Message (short version)
  659.                //      F0 00 00 3A nn F7, where:
  660.                //
  661.                //      nn is the number of timing clocks (nn = 7 through 127)
  662.  
  663.                default:    // 0x07 - 0x7F
  664.                            ulTimeStamp += (ULONG)b * ulPerClock;
  665.                            pMidiBuffer++;                       // current byte 0xF7
  666.  
  667.             } /* end switch */
  668.          }
  669.          else
  670.          {
  671.             while (*pMidiBuffer != 0xF7)         // bypass non-IBM Sysex Message
  672.             {
  673.                pMidiBuffer++;                    // current byte 0xF7
  674.             }
  675.          }
  676.       } /* end if */
  677.  
  678.  
  679.  
  680.  
  681.  
  682.       // 0xF8 indicates a Delay Byte message
  683.       // 0xF8s are delay byte messages which may be sent at any time--
  684.       // EVEN BETWEEN BYTES OF A MESSAGE WHICH HAS A DIFFERENT STATUS.
  685.       // delay byte messages are acted upon, after which the receiving
  686.       // process resumes under the previous state (stored in bRunningStatus)
  687.  
  688.       // NOTE:  this is endian specific
  689.  
  690.       else if (b == 0xF8)
  691.       {
  692.          ulTimeStamp += ulPerClock;              // process current 0xF8
  693.  
  694.                    // while the next byte after the 0xF8 is an 0xF8 increment
  695.                    // pMidiBuffer; we want to leave this section pointing to
  696.                    // the last 0xF8
  697.  
  698.          while (*(PUSHORT)pMidiBuffer == 0xF8F8)
  699.          {
  700.             pMidiBuffer++;
  701.             b = *pMidiBuffer;
  702.             ulTimeStamp += ulPerClock;
  703.          }
  704.       }
  705.  
  706.  
  707.  
  708.  
  709.  
  710.       // anything else is a MIDI message
  711.       // prepare to process the MIDI message
  712.  
  713.       else
  714.       {
  715.          // if this is the first message list in our linked list of
  716.          // message lists, allocate a buffer for the first message list
  717.  
  718.          if (!pMessageList)
  719.          {
  720.             pMessageList = (PMESSAGELIST) malloc( sizeof( MESSAGELIST ) );
  721.             memset(pMessageList, 0, sizeof(MESSAGELIST));
  722.             pMessageList->pNext = NULL;
  723.             pCurrMessageList = pMessageList;
  724.          }
  725.  
  726.  
  727.  
  728.          // for this example, the maximum number of messages for each
  729.          // message list is 20; any more than that and we create a new list.
  730.  
  731.          if (pCurrMessageList->ulMsgIndex < 20)
  732.          {
  733.             ulMsgNum = pCurrMessageList->ulMsgIndex;
  734.          }
  735.          else
  736.          {
  737.             pNewMessageList = (PMESSAGELIST) malloc( sizeof( MESSAGELIST ) );
  738.             memset(pNewMessageList, 0, sizeof(MESSAGELIST));
  739.             pNewMessageList->pNext = NULL;
  740.             pCurrMessageList->pNext = pNewMessageList;
  741.             pCurrMessageList = pNewMessageList;
  742.             ulMsgNum = pCurrMessageList->ulMsgIndex;
  743.          }
  744.  
  745.  
  746.  
  747.          // timestamp the message and identify the source instance
  748.  
  749.          pCurrMessageList->paMessage[ulMsgNum].ulTime = (ulTimeStamp+500)/1000;
  750.          pCurrMessageList->paMessage[ulMsgNum].ulSourceInstance = ulApplInst;
  751.  
  752.  
  753.  
  754.          // if byte is a status byte; store the status byte in the message
  755.          // and in bTestByte for processing the message later;
  756.          // advance the pointer to the first data byte
  757.  
  758.          if (b >= 0x80)
  759.          {
  760.             pCurrMessageList->paMessage[ulMsgNum].msg.bytes.bStatus = b;
  761.             pMidiBuffer++;
  762.             bTestByte = b;
  763.  
  764.                    // bRunningStatus only applies to certain status bytes
  765.  
  766.             if (b >= 0x80 && b < 0xF0)
  767.                bRunningStatus = b;
  768.             else
  769.                bRunningStatus = 0;
  770.          }
  771.                    // if byte is less than 0x80
  772.                    // current byte is a data byte, so we need bRunningStatus
  773.          else
  774.          {
  775.             pCurrMessageList->paMessage[ulMsgNum].msg.bytes.bStatus = bRunningStatus;
  776.             bTestByte = bRunningStatus;
  777.          }
  778.  
  779.  
  780.  
  781.          // this is a MIDI message and the byte is a status byte, where:
  782.          //
  783.          //                    Status                       # of Data Bytes
  784.          // =============================================   ===============
  785.          // 0x8n = Note Off event                                  2
  786.          // 0x9n = Note On event                                   2
  787.          // 0xAn = Polyphonic key pressure/after touch             2
  788.          // 0xBn = Control Change or Selects Channel Mode          2
  789.          // 0xCn = Program Change                                  1
  790.          // 0xDn = Channel pressure/after touch                    1
  791.          // 0xEn = Pitch wheel change                              2
  792.  
  793.          switch (bTestByte & 0xF0)
  794.          {
  795.             case 0x80:
  796.             case 0x90:
  797.             case 0xA0:
  798.             case 0xB0:
  799.             case 0xE0:
  800.                         pCurrMessageList->paMessage[ulMsgNum].msg.bytes.abData[0] = *pMidiBuffer++;
  801.                         pCurrMessageList->paMessage[ulMsgNum].msg.bytes.abData[1] = *pMidiBuffer;
  802.                         break;
  803.             case 0xC0:
  804.             case 0xD0:
  805.                         pCurrMessageList->paMessage[ulMsgNum].msg.bytes.abData[0] = *pMidiBuffer;
  806.                         break;
  807.             default:
  808.                         break;
  809.          }
  810.          pCurrMessageList->ulMsgIndex++;
  811.  
  812.       } /* end if */
  813.  
  814.       pMidiBuffer++;                   // advance to next byte in file
  815.  
  816.       if ( __MIDIDebug )
  817.          spinbar();
  818.  
  819.    } /* end while */
  820.  
  821.  
  822.  
  823.  
  824.  
  825. // OUTLINE: PLAY
  826.  
  827.    // Now we are done parsing the MIDI file into messages.  Our next step is
  828.    // to send the messages to the application node we created earlier.
  829.  
  830.    // MIDITimer allows the application to start and stop the MIDI Timer
  831.    // start the MIDI timer
  832.  
  833.    printf("\n\nsending MIDI messages...\n");
  834.  
  835.    rc = DosSetPriority(PRTYS_THREAD, PRTYC_TIMECRITICAL, 0, 0);
  836.    check( rc, "MIDITimer [DOS_SET_PRIORITY]\0" );
  837.  
  838.    rc = MIDITimer( MIDI_START_TIMER, 0 );
  839.    check( rc, "MIDITimer [MIDI_START_TIMER]\0" );
  840.  
  841.  
  842.    ulStartTime = *pulMidiTime + 1;     // pointer to current MIDI time obtained from
  843.                                        // the previous call to MIDISetup
  844.                                        // NOTE: we add 1 to compensate for processor time
  845.  
  846.    for (; pMessageList; pMessageList = pMessageList->pNext)
  847.    {
  848.       ulNumMessages = pMessageList->ulMsgIndex;
  849.  
  850.                    // calculate the time stamp for each message; the time
  851.                    // stamp is the absolute time, so add the time stamp
  852.                    // for each message to the current MIDI time in ulStartTime
  853.  
  854.       for (i = 0; i < ulNumMessages; i++)
  855.       {
  856.          pMessageList->paMessage[i].ulTime += ulStartTime;
  857.       }
  858.  
  859.       rc = MIDISendMessages(pMessageList->paMessage, ulNumMessages, 0);
  860.  
  861.       if ( rc )
  862.          check( rc, "MIDISendMessages\0" );
  863.  
  864.       if ( __MIDIDebug )
  865.          spinbar();
  866.  
  867.  
  868.                    // ulEndTime is the time stamp of the last message in the
  869.                    // message list
  870.  
  871.       ulEndTime = pMessageList->paMessage[ulNumMessages-1].ulTime;
  872.  
  873.                    // adjust ulEndTime to sleep for a little less time (4) to allow for
  874.                    // processing ulTime for each message
  875.  
  876.       ulCurrentTime = *pulMidiTime;
  877.  
  878.       if (ulCurrentTime + 2 < ulEndTime)
  879.       {
  880.          rc = TimerSleep(ulEndTime - 2 - ulCurrentTime, 0);
  881.  
  882.          if ( rc )
  883.             check( rc, "TimerSleep\0" );
  884.       }
  885.  
  886.    } /* end for */
  887.  
  888.    printf("\n\nfinished sending MIDI messages.\n\n\n");
  889.  
  890.  
  891.  
  892.  
  893.  
  894. // OUTLINE: CLEANUP
  895.  
  896.    // cleanup
  897.    // stop the MIDI timer after we are done sending our MIDI messages
  898.  
  899.    printf("cleaning up...\n");
  900.  
  901.    // wait 1/10th of a second for RTMIDI to play the last notes
  902.    rc = TimerSleep(100, 0);
  903.    check( rc, "TimerSleep\0" );
  904.  
  905.    rc = MIDITimer( MIDI_STOP_TIMER, 0 );
  906.    check( rc, "MIDITimer [MIDI_STOP_TIMER]\0" );
  907.  
  908.  
  909.  
  910.  
  911.  
  912.    // remove the link from the application node to the
  913.    // hardware node, disable our hardware node, delete our application
  914.    // instances, and free up all memory allocated
  915.  
  916.    // remove the link from instance # 1 to the hardware node
  917.  
  918.    rc = MIDIRemoveLink(ulApplInst, ulHdwInst, ulSlotNumber, 0);
  919.    check( rc, "MIDIRemoveLink\0" );
  920.  
  921.    rc = MIDIDisableInstance(ulHdwInst, MIDI_DISABLE_RECEIVE);  // use the hardware instance number
  922.    check( rc, "MIDIDisableInstance\0" );                       // retrieved from MIDIQueryInstanceList
  923.  
  924.    rc = MIDIDeleteInstance(ulApplInst, 0);            // Delete application instance
  925.    check( rc, "MIDIDeleteInstance\0" );
  926.  
  927.  
  928.    // free up all memory allocated
  929.  
  930.    free((PBYTE)paClassInfo);
  931.    free((PBYTE)paInstanceInfo);
  932.    free((PBYTE)pMidiSetup);
  933.    free((PBYTE)pBufferPtr);
  934.    free((PBYTE)pMessageList);
  935.    free((PBYTE)pNewMessageList);
  936.  
  937.  
  938.  
  939.    printf("Done.\n");
  940.  
  941.    DosExit( EXIT_PROCESS, 0 );
  942.  
  943. } /* end main */
  944.  
  945.  
  946.  
  947.  
  948.  
  949. /****************************************************************************
  950. *
  951. *     Function : check
  952. *
  953. *  Description : Used to check return codes and provide two levels of
  954. *                feedback to the user (normal and debug modes).
  955. *
  956. *   Parameters : ULONG rc, CHAR *call_id
  957. *
  958. *      Returns : VOID
  959. *
  960. ****************************************************************************/
  961.  
  962.  
  963. VOID check( ULONG rc, CHAR *call_id )
  964. {
  965.    CHAR *szErrorString;
  966.    CHAR *szSolutionString;
  967.    CHAR *szDebugString;
  968.  
  969.  
  970.  
  971.    CHAR *UNRECOVERABLE    = "*the program cannot continue*\0";
  972.    CHAR *CODE_ERROR       = "A code error has occured.\0";
  973.    CHAR *SYSTEM_PROBLEM   = "The system has encountered a problem.\0";
  974.    CHAR *HARDWARE_PROBLEM = "A hardware problem has been encountered.\0";
  975.  
  976.  
  977.    switch( rc )
  978.    {
  979.       case TIMERERR_INVALID_PARAMETER:
  980.          szDebugString    = "TIMERERR_INVALID_PARAMETER\0";
  981.          szErrorString    = CODE_ERROR;
  982.          szSolutionString = UNRECOVERABLE;
  983.          break;
  984.  
  985.       case TIMERERR_INTERNAL_SYSTEM:
  986.          szDebugString    = "TIMERERR_INTERNAL_SYSTEM\0";
  987.          szErrorString    = SYSTEM_PROBLEM;
  988.          szSolutionString = UNRECOVERABLE;
  989.          break;
  990.  
  991.       case MIDIERR_INVALID_PARAMETER:
  992.          szDebugString    = "MIDIERR_INVALID_PARAMETER\0";
  993.          szErrorString    = CODE_ERROR;
  994.          szSolutionString = UNRECOVERABLE;
  995.          break;
  996.  
  997.       case MIDIERR_INTERNAL_SYSTEM:
  998.          szDebugString    = "MIDIERR_INTERNAL_SYSTEM\0";
  999.          szErrorString    = SYSTEM_PROBLEM;
  1000.          szSolutionString = UNRECOVERABLE;
  1001.          break;
  1002.  
  1003.       case MIDIERR_INVALID_CLASS_NUMBER:
  1004.          szDebugString    = "MIDIERR_INVALID_CLASS_NUMBER\0";
  1005.          szErrorString    = CODE_ERROR;
  1006.          szSolutionString = UNRECOVERABLE;
  1007.          break;
  1008.  
  1009.       case MIDIERR_NO_DRIVER:
  1010.          szDebugString    = "MIDIERR_NO_DRIVER\0";
  1011.          szErrorString    = "A MIDI device driver was not found on this system.\0";
  1012.          szSolutionString = "*check to make sure the proper MIDI driver(s) are installed*\0";
  1013.          break;
  1014.  
  1015.       case MIDIERR_DUPLICATE_INSTANCE_NAME:
  1016.          szDebugString    = "MIDIERR_DUPLICATE_INSTANCE_NAME\0";
  1017.          szErrorString    = CODE_ERROR;
  1018.          szSolutionString = UNRECOVERABLE;
  1019.          break;
  1020.  
  1021.       case MIDIERR_INVALID_INSTANCE_NAME:
  1022.          szDebugString    = "MIDIERR_INVALID_INSTANCE_NAME\0";
  1023.          szErrorString    = CODE_ERROR;
  1024.          szSolutionString = UNRECOVERABLE;
  1025.          break;
  1026.  
  1027.       case MIDIERR_INVALID_INSTANCE_NUMBER:
  1028.          szDebugString    = "MIDIERR_INVALID_INSTANCE_NUMBER\0";
  1029.          szErrorString    = CODE_ERROR;
  1030.          szSolutionString = UNRECOVERABLE;
  1031.          break;
  1032.  
  1033.       case MIDIERR_INVALID_SETUP:
  1034.          szDebugString    = "MIDIERR_INVALID_SETUP\0";
  1035.          szErrorString    = CODE_ERROR;
  1036.          szSolutionString = UNRECOVERABLE;
  1037.          break;
  1038.  
  1039.       case MIDIERR_HARDWARE_FAILED:
  1040.          szDebugString    = "MIDIERR_HARDWARE_FAILED\0";
  1041.          szErrorString    = HARDWARE_PROBLEM;
  1042.          szSolutionString = UNRECOVERABLE;
  1043.          break;
  1044.  
  1045.       case MIDIERR_INVALID_FLAG:
  1046.          szDebugString    = "MIDIERR_INVALID_FLAG\0";
  1047.          szErrorString    = CODE_ERROR;
  1048.          szSolutionString = UNRECOVERABLE;
  1049.          break;
  1050.  
  1051.       case MIDIERR_RECEIVEONLY:
  1052.          szDebugString    = "MIDIERR_RECEIVEONLY\0";
  1053.          szErrorString    = CODE_ERROR;
  1054.          szSolutionString = UNRECOVERABLE;
  1055.          break;
  1056.  
  1057.       case MIDIERR_SENDONLY:
  1058.          szDebugString    = "MIDIERR_SENDONLY\0";
  1059.          szErrorString    = CODE_ERROR;
  1060.          szSolutionString = UNRECOVERABLE;
  1061.          break;
  1062.  
  1063.       case MIDIERR_NOT_ALLOWED:
  1064.          szDebugString    = "MIDIERR_NOT_ALLOWED\0";
  1065.          szErrorString    = CODE_ERROR;
  1066.          szSolutionString = UNRECOVERABLE;
  1067.          break;
  1068.  
  1069.       case MIDIERR_RESOURCE_NOT_AVAILABLE:
  1070.          szDebugString    = "MIDIERR_RESOURCE_NOT_AVAILABLE\0";
  1071.          szErrorString    = SYSTEM_PROBLEM;
  1072.          szSolutionString = UNRECOVERABLE;
  1073.          break;
  1074.  
  1075.  
  1076.                                        // These are locally defined program errors.
  1077.  
  1078.       case HARDWARE_NOT_FOUND:
  1079.          szDebugString    = "HARDWARE_NOT_FOUND\0";
  1080.          szErrorString    = "A hardware node (instance) couldn't be found.\0";
  1081.          szSolutionString = "*check to make sure an audio card with \nMPU-401 support is installed and enabled.*\0";
  1082.          break;
  1083.  
  1084.       case HARDWARE_UNABLE_TO_RECEIVE:
  1085.          szDebugString    = "HARDWARE_UNABLE_TO_RECEIVE\0";
  1086.          szErrorString    = "The hardware node is unable to receive MIDI messages.\0";
  1087.          szSolutionString = "*currently installed hardware does not support MIDI receive*\0";
  1088.          break;
  1089.  
  1090.       case MMIOOPEN_ERROR:
  1091.          szDebugString    = "MMIOOPEN_ERROR\0";
  1092.          szErrorString    = "There is a problem opening the file specified.\0";
  1093.          szSolutionString = "*check to make sure the file is a MIDI file (*.MID) \nin the MMOS2\\SOUNDS directory and that it is spelled correctly.*\0";
  1094.          break;
  1095.  
  1096.       default:
  1097.          szDebugString    = "Success\0";
  1098.  
  1099.    } /* end switch */
  1100.  
  1101.  
  1102.    if ( __MIDIDebug )
  1103.       printf("     ::%s (rc=%lu, %s)\n", call_id, rc, szDebugString);
  1104.  
  1105.  
  1106.    if ( rc )
  1107.    {
  1108.       printf( "\n\n%s\n%s\n\n", szErrorString, szSolutionString );
  1109.       exit(1);
  1110.    }
  1111.  
  1112. } /* end check */
  1113.  
  1114.  
  1115.  
  1116.  
  1117.  
  1118. /****************************************************************************
  1119. *
  1120. *     Function : spinbar
  1121. *
  1122. *  Description : Used to provide visual feedback on the progress of
  1123. *                functions while in debug mode.
  1124. *
  1125. *   Parameters : void
  1126. *
  1127. *      Returns : VOID
  1128. *
  1129. ****************************************************************************/
  1130.  
  1131.  
  1132. VOID spinbar( void )
  1133. {
  1134.    static int counter = 0;
  1135.    char spin[] = "\\|/-";
  1136.  
  1137.    if ( counter >= 4 )
  1138.       counter = 0;
  1139.    else
  1140.    {
  1141.       putchar(8);
  1142.       putchar(spin[counter++]);
  1143.    }
  1144.  
  1145. } /* end spinbar */
  1146.  
  1147.  
  1148.  
  1149.  
  1150.  
  1151. /****************************************************************************
  1152. *
  1153. *     Function : CalcDelay
  1154. *
  1155. *  Description : Used to determine the number of microseconds per MIDI clock
  1156. *                based on the current tempo.  See notes.
  1157. *
  1158. *   Parameters : void
  1159. *
  1160. *      Returns : VOID
  1161. *
  1162. *        Notes :
  1163. *
  1164. *                   600,000,000 microseconds/10 minutes
  1165. *        -----------------------------------------------------------  ==   X microseconds/clock
  1166. *                                            usCPQNnum
  1167. *        (ulTempo beats/10 min) * ( 24 * ------------- clocks/beat )
  1168. *                                            usCPQNden
  1169. *
  1170. *
  1171. *              25,000,000 * usCPQNden
  1172. *        ==  --------------------------
  1173. *               ulTempo * usCPQNnum
  1174. *
  1175. *        where
  1176. *           usCPQNden = ((usCPQN & 0x3F) + 1) * 3
  1177. *           usCPQNnum = 1                                   if bit 6 of usCPQN is set
  1178. *
  1179. *           or
  1180. *
  1181. *           usCPQNden = 1
  1182. *           usCPQNnum = usCPQN + 1                          if bit 6 is not set
  1183. *
  1184. *
  1185. ****************************************************************************/
  1186.  
  1187.  
  1188. VOID CalcDelay (void)
  1189. {
  1190.    ULONG ulTmp = 0;
  1191.  
  1192.                    // CPQN is Clocks Per Quarter Note
  1193.                    // bit 6 set if denominator
  1194.  
  1195.    if (usCPQN & 0x40)
  1196.    {
  1197.       ulTmp = 25000000 * ((usCPQN * 0x3F) + 1);
  1198.       ulPerClock = ulTmp / ulTempo;
  1199.       ulPerClock *= 3;
  1200.    }
  1201.    else
  1202.    {
  1203.       ulTmp = ulTempo * (usCPQN + 1);
  1204.       ulPerClock = 25000000 / ulTmp;
  1205.    }
  1206.  
  1207. } /* end CalcDelay */
  1208.  
  1209.  
  1210.  
  1211.  
  1212.  
  1213. /****************************************************************************
  1214. *
  1215. *     Function : QueryMMBASE
  1216. *
  1217. *  Description : Used to get the path for MMBASE.
  1218. *
  1219. *   Parameters : PCHAR pszMapFile
  1220. *
  1221. *      Returns : USHORT
  1222. *
  1223. ****************************************************************************/
  1224.  
  1225.  
  1226. USHORT QueryMMBASE (PCHAR pszMapFile)
  1227. {
  1228.    PSZ pszEnv;
  1229.    PCHAR p ;
  1230.  
  1231.    if (!DosScanEnv ((PSZ)"MMBASE", &pszEnv))
  1232.    {
  1233.       strcpy (pszMapFile, (PCHAR)pszEnv);
  1234.       if ( (p = strchr(pszMapFile, ';')) != 0 ) *p = 0;        /* Remove After semicolon */
  1235.    }
  1236.    else
  1237.    {
  1238.       strcpy (pszMapFile, "C:\\");
  1239.    }
  1240.  
  1241.    return (0);
  1242.  
  1243. } /* end QueryMMBASE */
  1244.  
  1245.  
  1246.