home *** CD-ROM | disk | FTP | other *** search
/ Simtel MSDOS - Coast to Coast / simteldosarchivecoasttocoast.iso / pcmag / vol11n19.zip / MIDFIL.C < prev    next >
C/C++ Source or Header  |  1992-07-15  |  30KB  |  887 lines

  1. /*-----------------------------------------------------
  2.    MIDFIL.C -- MIDI Recorder and Player with File Save
  3.                (c) Charles Petzold, 1992
  4.   -----------------------------------------------------*/
  5.  
  6. #include <windows.h>
  7. #include <windowsx.h>
  8. #include <mmsystem.h>
  9. #include <commdlg.h>
  10. #include <stdio.h>
  11. #include <stdlib.h>
  12. #include <string.h>
  13. #include "midbuf.h"
  14. #include "midfil.h"
  15.  
  16. #define BUFFER_SIZE 4096      // Should be multiple of 8
  17.  
  18. long FAR PASCAL _export WndProc (HWND, UINT, UINT, LONG) ;
  19. BOOL FAR PASCAL _export DlgProc (HWND, UINT, UINT, LONG) ;
  20.  
  21. char   szAppName [] = "MidFil" ;
  22. HANDLE hInst ;
  23. HWND   hDlg ;
  24.  
  25. int PASCAL WinMain (HANDLE hInstance, HANDLE hPrevInstance,
  26.                     LPSTR lpszCmdParam, int nCmdShow)
  27.      {
  28.      HWND        hwnd ;
  29.      int         xWin, yWin ;
  30.      MSG         msg ;
  31.      WNDCLASS    wndclass ;
  32.  
  33.      hInst = hInstance ;
  34.  
  35.      if (!hPrevInstance)
  36.           {
  37.           wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
  38.           wndclass.lpfnWndProc   = WndProc ;
  39.           wndclass.cbClsExtra    = 0 ;
  40.           wndclass.cbWndExtra    = 0 ;
  41.           wndclass.hInstance     = hInstance ;
  42.           wndclass.hIcon         = LoadIcon (hInstance, szAppName) ;
  43.           wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
  44.           wndclass.hbrBackground = GetStockObject (WHITE_BRUSH) ;
  45.           wndclass.lpszMenuName  = szAppName ;
  46.           wndclass.lpszClassName = szAppName ;
  47.  
  48.           RegisterClass (&wndclass) ;
  49.           }
  50.  
  51.      xWin = DLG_WIDTH  * LOWORD (GetDialogBaseUnits ()) / 4 +
  52.                2 * GetSystemMetrics (SM_CXBORDER) ;
  53.  
  54.      yWin = DLG_HEIGHT * HIWORD (GetDialogBaseUnits ()) / 8 +
  55.                2 * GetSystemMetrics (SM_CYBORDER)  +
  56.                    GetSystemMetrics (SM_CYCAPTION) +
  57.                    GetSystemMetrics (SM_CYMENU) ;
  58.  
  59.      hwnd = CreateWindow (szAppName, "Midi File Saver",
  60.                           WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU |
  61.                           WS_BORDER     | WS_MINIMIZEBOX,
  62.                           CW_USEDEFAULT, CW_USEDEFAULT, xWin, yWin,
  63.                           NULL, NULL, hInstance, lpszCmdParam) ;
  64.  
  65.      ShowWindow (hwnd, nCmdShow) ;
  66.      UpdateWindow (hwnd) ;
  67.  
  68.      while (GetMessage (&msg, NULL, 0, 0))
  69.           {
  70.           if (hDlg != NULL || !IsDialogMessage (hDlg, &msg))
  71.                {
  72.                TranslateMessage (&msg) ;
  73.                DispatchMessage (&msg) ;
  74.                }
  75.           }
  76.      return msg.wParam ;
  77.      }
  78.  
  79.           // Functions to allocate and free MIDIHDR structures and buffers
  80.           // -------------------------------------------------------------
  81.  
  82. LPMIDIHDR AllocMidiHeader (HANDLE hMidi, LPMIDIHDR pmhRoot)
  83.      {
  84.      LPMIDIHDR pmhNew, pmhNext ;
  85.  
  86.                // Allocate memory for the new MIDIHDR
  87.  
  88.      pmhNew = (LPMIDIHDR) GlobalAllocPtr (GHND | GMEM_SHARE, sizeof (MIDIHDR));
  89.  
  90.      if (pmhNew == NULL)
  91.           return NULL ;
  92.  
  93.                // Allocate memory for the buffer
  94.  
  95.      pmhNew->lpData = (LPSTR) GlobalAllocPtr (GHND | GMEM_SHARE, BUFFER_SIZE) ;
  96.  
  97.      if (pmhNew->lpData == NULL)
  98.           {
  99.           GlobalFreePtr (pmhNew) ;
  100.           return NULL ;
  101.           }
  102.  
  103.      pmhNew->dwBufferLength = BUFFER_SIZE ;
  104.  
  105.                // Prepare the header
  106.  
  107.      if (midiInPrepareHeader (hMidi, pmhNew, sizeof (MIDIHDR)))
  108.           {
  109.           GlobalFreePtr (pmhNew->lpData) ;
  110.           GlobalFreePtr (pmhNew) ;
  111.           return NULL ;
  112.           }
  113.  
  114.                // Attach new header to end of chain
  115.  
  116.      if (pmhRoot != NULL)
  117.           {
  118.           pmhNext = pmhRoot ;
  119.  
  120.           while (pmhNext->dwUser != NULL)
  121.                pmhNext = (LPMIDIHDR) pmhNext->dwUser ;
  122.  
  123.           pmhNext->dwUser = (DWORD) pmhNew ;
  124.           }
  125.  
  126.      return pmhNew ;
  127.      }
  128.  
  129. LPMIDIHDR CleanUpMidiHeaderChain (HANDLE hMidi, LPMIDIHDR pmhRoot)
  130.      {
  131.      LPMIDIHDR pmhCurr, pmhLast, pmhNext, pmhRetn ;
  132.  
  133.      pmhRetn = pmhRoot ;
  134.      pmhCurr = pmhRoot ;
  135.      pmhLast = NULL ;
  136.  
  137.      while (pmhCurr != NULL)
  138.           {
  139.           pmhNext = (LPMIDIHDR) pmhCurr->dwUser ;
  140.  
  141.           if (pmhCurr->dwBytesRecorded == 0)
  142.                {
  143.                midiInUnprepareHeader (hMidi, pmhCurr, sizeof (MIDIHDR)) ;
  144.  
  145.                GlobalFreePtr (pmhCurr->lpData) ;
  146.                GlobalFreePtr (pmhCurr) ;
  147.  
  148.                if (pmhCurr == pmhRoot)
  149.                     pmhRetn = NULL ;
  150.  
  151.                if (pmhLast != NULL)
  152.                     pmhLast->dwUser = (DWORD) pmhNext ;
  153.  
  154.                pmhCurr = pmhLast ;
  155.                }
  156.  
  157.           else if (pmhCurr->dwBytesRecorded < BUFFER_SIZE)
  158.                {
  159.                midiInUnprepareHeader (hMidi, pmhCurr, sizeof (MIDIHDR)) ;
  160.  
  161.                GlobalReAllocPtr (pmhCurr->lpData,
  162.                                  pmhCurr->dwBytesRecorded, 0) ;
  163.  
  164.                midiInPrepareHeader (hMidi, pmhCurr, sizeof (MIDIHDR)) ;
  165.  
  166.                pmhCurr->dwBufferLength = pmhCurr->dwBytesRecorded ;
  167.                }
  168.  
  169.           pmhLast = pmhCurr ;
  170.           pmhCurr = pmhNext ;
  171.           }
  172.  
  173.      return pmhRetn ;
  174.      }
  175.  
  176. VOID FreeMidiHeaderChain (HANDLE hMidi, LPMIDIHDR pmhRoot)
  177.      {
  178.      LPMIDIHDR pmhNext, pmhTemp ;
  179.  
  180.      pmhNext = pmhRoot ;
  181.  
  182.      while (pmhNext != NULL)
  183.           {
  184.           pmhTemp = (LPMIDIHDR) pmhNext->dwUser ;
  185.  
  186.           midiInUnprepareHeader (hMidi, pmhNext, sizeof (MIDIHDR)) ;
  187.  
  188.           GlobalFreePtr (pmhNext->lpData) ;
  189.           GlobalFreePtr (pmhNext) ;
  190.  
  191.           pmhNext = pmhTemp ;
  192.           }
  193.      }
  194.  
  195.           // Add MIDI device lists to the program's menu
  196.           // -------------------------------------------
  197.  
  198. WORD AddDevicesToMenu (HWND hwnd, int iNumInpDevs, int iNumOutDevs)
  199.      {
  200.      HMENU       hMenu, hMenuInp, hMenuMon, hMenuOut ;
  201.      int         i ;
  202.      MIDIINCAPS  mic ;
  203.      MIDIOUTCAPS moc ;
  204.      WORD        wDefaultOut ;
  205.  
  206.      hMenu = GetMenu (hwnd) ;
  207.  
  208.                // Create "Input" popup menu
  209.  
  210.      hMenuInp = CreateMenu () ;
  211.  
  212.      for (i = 0 ; i < iNumInpDevs ; i++)
  213.           {
  214.           midiInGetDevCaps (i, &mic, sizeof (MIDIINCAPS)) ;
  215.           AppendMenu (hMenuInp, MF_STRING, ID_DEV_INP + i, mic.szPname) ;
  216.           }
  217.  
  218.      CheckMenuItem (hMenuInp, 0, MF_BYPOSITION | MF_CHECKED) ;
  219.      ModifyMenu (hMenu, ID_DEV_INP, MF_POPUP, hMenuInp, "&Input") ;
  220.  
  221.                // Create "Monitor" and "Output" popup menus
  222.  
  223.      hMenuMon = CreateMenu () ;
  224.      hMenuOut = CreateMenu () ;
  225.  
  226.      AppendMenu (hMenuMon, MF_STRING, ID_DEV_MON, "&None") ;
  227.  
  228.      if (!midiOutGetDevCaps (MIDIMAPPER, &moc, sizeof (moc)))
  229.           {
  230.           AppendMenu (hMenuMon, MF_STRING, ID_DEV_MON + 1, moc.szPname) ;
  231.           AppendMenu (hMenuOut, MF_STRING, ID_DEV_OUT    , moc.szPname) ;
  232.  
  233.           wDefaultOut = 0 ;
  234.           }
  235.      else
  236.           wDefaultOut = 1 ;
  237.  
  238.                          // Add the rest of the MIDI devices
  239.  
  240.      for (i = 0 ; i < iNumOutDevs ; i++)
  241.           {
  242.           midiOutGetDevCaps (i, &moc, sizeof (moc)) ;
  243.           AppendMenu (hMenuMon, MF_STRING, ID_DEV_MON + i + 2, moc.szPname) ;
  244.           AppendMenu (hMenuOut, MF_STRING, ID_DEV_OUT + i + 1, moc.szPname) ;
  245.           }
  246.  
  247.      CheckMenuItem (hMenuMon, 0, MF_BYPOSITION | MF_CHECKED) ;
  248.      CheckMenuItem (hMenuOut, 0, MF_BYPOSITION | MF_CHECKED) ;
  249.  
  250.      ModifyMenu (hMenu, ID_DEV_MON, MF_POPUP, hMenuMon, "&Monitor") ;
  251.      ModifyMenu (hMenu, ID_DEV_OUT, MF_POPUP, hMenuOut, "&Output") ;
  252.  
  253.      return wDefaultOut ;
  254.      }
  255.  
  256.           // Functions to save the MIDI data buffers as a .MID file
  257.           // ------------------------------------------------------
  258.  
  259. int VarLenNumOut (BYTE * pOut, int iOut, long lAbsTime)
  260.      {
  261.      long lVarTime ;
  262.  
  263.      lVarTime = lAbsTime & 0x7F ;
  264.  
  265.      while ((lAbsTime >>= 7) > 0)
  266.           {
  267.           lVarTime <<= 8 ;
  268.           lVarTime  |= 0x80 ;
  269.           lVarTime  += lAbsTime & 0x7F ;
  270.           }
  271.  
  272.      while (TRUE)
  273.           {
  274.           pOut[iOut++] = (char) lVarTime ;
  275.  
  276.           if (lVarTime & 0x80)
  277.                lVarTime >>= 8 ;
  278.           else
  279.                break ;
  280.           }
  281.  
  282.      return iOut ;
  283.      }
  284.  
  285. long IntelToMotorolaLong (long lIn)
  286.      {
  287.      long lOut ;
  288.  
  289.      * ((char *) & lOut + 0) = * ((char *) & lIn + 3) ;
  290.      * ((char *) & lOut + 1) = * ((char *) & lIn + 2) ;
  291.      * ((char *) & lOut + 2) = * ((char *) & lIn + 1) ;
  292.      * ((char *) & lOut + 3) = * ((char *) & lIn + 0) ;
  293.  
  294.      return lOut ;
  295.      }
  296.  
  297. BOOL SaveMidiFile (char * szFileName, LPMIDIHDR pMidiHdrRoot)
  298.      {
  299.      static BYTE pBufOut [2 * BUFFER_SIZE] ;
  300.      static BYTE pHeader [] = { 'M', 'T', 'h', 'd', 0, 0, 0, 6,
  301.                                  0, 0, 0, 1, -25, 40,
  302.                                 'M', 'T', 'r', 'k', 0, 0, 0, 0 } ;
  303.      static BYTE pEndTrk [] = { 0, 0xFF, 0x2F, 0x00 } ;
  304.      BYTE        bStatus, bChannel, bData1, bData2 ;
  305.      DWORD       dwTime, dwMidiMsg ;
  306.      int         hFile, iLenIn, iIn, iOut ;
  307.      long        lTotLen ;
  308.      LPMIDIHDR   pMidiHdr ;
  309.      LPDWORD     pdwBufIn ;
  310.  
  311.                // Attempt to open the file
  312.  
  313.      if (-1 == (hFile = _lcreat (szFileName, 0)))
  314.           return FALSE ;
  315.  
  316.                // Write out the header chunk and some of the track chunk
  317.  
  318.      _lwrite (hFile, pHeader, sizeof (pHeader)) ;
  319.  
  320.                // Loop through the MIDIHDR chain
  321.  
  322.      pMidiHdr = pMidiHdrRoot ;
  323.      lTotLen  = 0 ;
  324.  
  325.      while (pMidiHdr != NULL)
  326.           {
  327.           iLenIn    = (int)     pMidiHdr->dwBufferLength / 4 ;
  328.           pdwBufIn  = (LPDWORD) pMidiHdr->lpData ;
  329.           iOut      = 0 ;
  330.  
  331.                     // Loop through the DWORDs in the buffer
  332.  
  333.           for (iIn = 0 ; iIn < iLenIn ; iIn += 2)
  334.                {
  335.                          // Get the delta time in milliseconds
  336.  
  337.                dwTime = pdwBufIn [iIn] ;
  338.  
  339.                          // If it's the very first, set it to zero
  340.  
  341.                if (pMidiHdr == pMidiHdrRoot && iIn == 0)
  342.                     dwTime = 0 ;
  343.  
  344.                          // Store it as a variable length number
  345.  
  346.                iOut = VarLenNumOut (pBufOut, iOut, dwTime) ;
  347.  
  348.                          // Get the MIDI message
  349.  
  350.                dwMidiMsg = pdwBufIn [iIn + 1] ;
  351.  
  352.                bStatus = LOBYTE (LOWORD (dwMidiMsg)) ;
  353.                bData1  = HIBYTE (LOWORD (dwMidiMsg)) ;
  354.                bData2  = LOBYTE (HIWORD (dwMidiMsg)) ;
  355.  
  356.                          // Forget about system messages
  357.  
  358.                if (bStatus >= 0xF0)
  359.                     continue ;
  360.  
  361.                          // Store the status byte and first data byte
  362.  
  363.                pBufOut[iOut++] = bStatus ;
  364.                pBufOut[iOut++] = bData1  ;
  365.  
  366.                          // For three-byte messages, store the second data byte
  367.  
  368.                if (bStatus < 0xC0 || bStatus >= 0xE0)
  369.                     pBufOut [iOut++] = bData2 ;
  370.  
  371.                          // Get the channel number
  372.  
  373.                bChannel = bStatus & 0x0F ;
  374.  
  375.                          // If it's not mappable, continue with next loop
  376.  
  377.                if ((bChannel > 2 && bChannel <  9) ||
  378.                    (bChannel > 9 && bChannel < 12))
  379.                     continue ;
  380.  
  381.                          // Map the channel and create a new status byte
  382.  
  383.                if (bChannel < 3)
  384.                     bChannel += 12 ;
  385.  
  386.                else if (bChannel == 9)
  387.                     bChannel = 15 ;
  388.  
  389.                else if (bChannel == 15)
  390.                     bChannel = 9 ;
  391.  
  392.                else if (bChannel > 11)
  393.                     bChannel -= 12 ;
  394.  
  395.                bStatus = (bStatus & 0xF0) | bChannel ;
  396.  
  397.                          // Store a delta time of zero
  398.  
  399.                pBufOut[iOut++] = 0 ;
  400.  
  401.                          // Store the MIDI message as above
  402.  
  403.                pBufOut[iOut++] = bStatus ;
  404.                pBufOut[iOut++] = bData1  ;
  405.  
  406.                if (bStatus < 0xC0 || bStatus >= 0xE0)
  407.                     pBufOut [iOut++] = bData2 ;
  408.                }
  409.  
  410.                     // Write out the buffer
  411.  
  412.           if (iOut != (int) _lwrite (hFile, pBufOut, iOut))
  413.                {
  414.                _lclose (hFile) ;
  415.                unlink (szFileName) ;
  416.                return FALSE ;
  417.                }
  418.  
  419.                     // Increment the total track size
  420.  
  421.           lTotLen += iOut ;
  422.  
  423.                     // Get next MIDIHDR structure in chain
  424.  
  425.           pMidiHdr = (LPMIDIHDR) pMidiHdr->dwUser ;
  426.           }
  427.  
  428.                // Store an "end of track" meta-message
  429.  
  430.      _lwrite (hFile, pEndTrk, sizeof (pEndTrk)) ;
  431.  
  432.      lTotLen += sizeof (pEndTrk) ;
  433.  
  434.                // Convert track length to Motorola format and store it
  435.                //        in the size field of the track chunk
  436.  
  437.      lTotLen = IntelToMotorolaLong (lTotLen) ;
  438.  
  439.      _llseek (hFile, 18, 0) ;
  440.      _lwrite (hFile, &lTotLen, sizeof (long)) ;
  441.  
  442.                // Close the file
  443.  
  444.      _lclose (hFile) ;
  445.  
  446.      return TRUE ;
  447.      }
  448.  
  449. long FAR PASCAL _export WndProc (HWND hwnd, UINT message, UINT wParam,
  450.                                                           LONG lParam)
  451.      {
  452.      static BOOL      bRecording, bPlaying, bEnding, bPaused, bTerminating ;
  453.      static char      szInpError[] = { "Error opening MIDI input port!" } ;
  454.      static char      szOutError[] = { "Error opening MIDI output port!" } ;
  455.      static char      szMonError[] = { "Error opening MIDI output port "
  456.                                        "for monitoring input!  Continuing." } ;
  457.      static char      szMemError[] = { "Error allocating memory!" } ;
  458.      static char      szFilError[] = { "Error save file!" } ;
  459.      static char      szFileName  [_MAX_PATH],
  460.                       szFileTitle [_MAX_FNAME | _MAX_EXT] ;
  461.      static HMIDIIN   hMidiIn ;
  462.      static HMIDIOUT  hMidiOut ;
  463.      static int       iNumInpDevs, iNumOutDevs ;
  464.      static LPMIDIHDR pMidiHdrRoot, pMidiHdrNext, pMidiHdr ;
  465.      static OPENFILENAME ofn ;
  466.      static char *    szFilter[] = { "Midi Files (*.MID)",  "*.mid",
  467.                                      "" } ;
  468.      static WORD      wDeviceInp, wDeviceMon, wDeviceOut ;
  469.      FARPROC          lpDlgProc ;
  470.      HMENU            hMenu ;
  471.      int              i ;
  472.  
  473.      switch (message)
  474.           {
  475.           case WM_CREATE:
  476.                if (0 == (iNumInpDevs = midiInGetNumDevs ()))
  477.                     {
  478.                     MessageBox (hwnd, "No MIDI Input Devices!", szAppName,
  479.                                 MB_ICONEXCLAMATION | MB_OK) ;
  480.                     DestroyWindow (hwnd) ;
  481.                     }
  482.  
  483.                if (0 == (iNumOutDevs = midiOutGetNumDevs ()))
  484.                     {
  485.                     MessageBox (hwnd, "No MIDI Output Devices!", szAppName,
  486.                                 MB_ICONEXCLAMATION | MB_OK) ;
  487.                     DestroyWindow (hwnd) ;
  488.                     }
  489.  
  490.                wDeviceOut = AddDevicesToMenu (hwnd, iNumInpDevs, iNumOutDevs) ;
  491.  
  492.                lpDlgProc = MakeProcInstance ((FARPROC) DlgProc, hInst) ;
  493.                hDlg      = CreateDialog (hInst, szAppName, hwnd, lpDlgProc) ;
  494.                return 0 ;
  495.  
  496.           case WM_COMMAND:
  497.                hMenu = GetMenu (hwnd) ;
  498.  
  499.                switch (wParam)
  500.                     {
  501.                     case ID_RECORD_BEG:
  502.  
  503.                                    // Open MIDI In port for recording
  504.  
  505.                          if (midiInOpen (&hMidiIn, wDeviceInp, hwnd, 0L,
  506.                                          CALLBACK_WINDOW))
  507.                               {
  508.                               MessageBox (hwnd, szInpError, szAppName,
  509.                                           MB_ICONEXCLAMATION | MB_OK) ;
  510.  
  511.                               return 0 ;
  512.                               }
  513.  
  514.                                    // Open MIDI Out port for monitoring
  515.                                    //     (continue if unable to open it)
  516.  
  517.                          if (wDeviceMon > 0)
  518.                               {
  519.                               if (midiOutOpen (&hMidiOut, wDeviceMon - 2,
  520.                                                0L, 0L, 0L))
  521.                                    {
  522.                                    hMidiOut = NULL ;
  523.                                    MessageBox (hwnd, szMonError, szAppName,
  524.                                                MB_ICONEXCLAMATION | MB_OK) ;
  525.                                    }
  526.                               }
  527.                          else
  528.                               hMidiOut = NULL ;
  529.  
  530.                          return 0 ;
  531.  
  532.                     case ID_RECORD_END:
  533.                                         // Reset and close input
  534.  
  535.                          bEnding = TRUE ;
  536.  
  537.                          midiInReset (hMidiIn) ;
  538.                          midiInClose (hMidiIn) ;
  539.  
  540.                          return 0 ;
  541.  
  542.                     case ID_FILE_SAVE:
  543.                          ofn.lStructSize       = sizeof (OPENFILENAME) ;
  544.                          ofn.hwndOwner         = hwnd ;
  545.                          ofn.lpstrFilter       = szFilter [0] ;
  546.                          ofn.lpstrFile         = szFileName ;
  547.                          ofn.nMaxFile          = _MAX_PATH ;
  548.                          ofn.lpstrFileTitle    = szFileTitle ;
  549.                          ofn.nMaxFileTitle     = _MAX_FNAME + _MAX_EXT ;
  550.                          ofn.Flags             = OFN_OVERWRITEPROMPT ;
  551.                          ofn.lpstrDefExt       = "mid" ;
  552.  
  553.                          if (!GetSaveFileName (&ofn))
  554.                               return 0 ;
  555.  
  556.                          if (!SaveMidiFile (szFileName, pMidiHdrRoot))
  557.                               MessageBox (hwnd, szFilError, szAppName,
  558.                                           MB_ICONEXCLAMATION | MB_OK) ;
  559.  
  560.                          SetFocus (GetDlgItem (hDlg, ID_RECORD_BEG)) ;
  561.  
  562.                          return 0 ;
  563.  
  564.                     case ID_PLAY_BEG:
  565.                                         // Open MIDI Out port for playing
  566.  
  567.                          if (midiOutOpen (&hMidiOut, wDeviceOut - 1,
  568.                                           hwnd, 0L, CALLBACK_WINDOW))
  569.                               {
  570.                               MessageBox (hwnd, szOutError, szAppName,
  571.                                           MB_ICONEXCLAMATION | MB_OK) ;
  572.                               }
  573.  
  574.                          return 0 ;
  575.  
  576.                     case ID_PLAY_PAUSE:
  577.                                         // Pause or restart output
  578.  
  579.                          if (!bPaused)
  580.                               {
  581.                               midiOutPause (hMidiOut) ;
  582.  
  583.                                    // All Notes Off message
  584.  
  585.                               for (i = 0 ; i < 16 ; i++)
  586.                                    midiOutShortMsg (hMidiOut, 0x7BB0 + i) ;
  587.  
  588.                               SetDlgItemText (hwnd, ID_PLAY_PAUSE, "Resume") ;
  589.                               bPaused = TRUE ;
  590.                               }
  591.                          else
  592.                               {
  593.                               midiOutRestart (hMidiOut) ;
  594.                               SetDlgItemText (hwnd, ID_PLAY_PAUSE, "Pause") ;
  595.                               bPaused = FALSE ;
  596.                               }
  597.  
  598.                          return 0 ;
  599.  
  600.                     case ID_PLAY_END:
  601.                                         // Reset the port and close it
  602.  
  603.                          bEnding = TRUE ;
  604.                          midiOutReset (hMidiOut) ;
  605.                          midiOutClose (hMidiOut) ;
  606.                          return 0 ;
  607.  
  608.                     default:
  609.                          break ;
  610.                     }
  611.  
  612.                if (wParam >= ID_DEV_INP & wParam < ID_DEV_MON)
  613.                     {
  614.                     CheckMenuItem (hMenu, wDeviceInp + ID_DEV_INP,
  615.                                           MF_UNCHECKED) ;
  616.  
  617.                     wDeviceInp = wParam - ID_DEV_INP ;
  618.  
  619.                     CheckMenuItem (hMenu, wDeviceInp + ID_DEV_INP,
  620.                                           MF_CHECKED) ;
  621.                     return 0 ;
  622.                     }
  623.  
  624.                else if (wParam >= ID_DEV_MON & wParam < ID_DEV_OUT)
  625.                     {
  626.                     CheckMenuItem (hMenu, wDeviceMon + ID_DEV_MON,
  627.                                           MF_UNCHECKED) ;
  628.  
  629.                     wDeviceMon = wParam - ID_DEV_MON ;
  630.  
  631.                     CheckMenuItem (hMenu, wDeviceMon + ID_DEV_MON,
  632.                                           MF_CHECKED) ;
  633.                     return 0 ;
  634.                     }
  635.  
  636.                if (wParam >= ID_DEV_OUT)
  637.                     {
  638.                     CheckMenuItem (hMenu, wDeviceOut + ID_DEV_OUT,
  639.                                           MF_UNCHECKED) ;
  640.  
  641.                     wDeviceOut = wParam - ID_DEV_OUT ;
  642.  
  643.                     CheckMenuItem (hMenu, wDeviceOut + ID_DEV_OUT,
  644.                                           MF_CHECKED) ;
  645.                     return 0 ;
  646.                     }
  647.  
  648.                break ;
  649.  
  650.           case MM_MIM_OPEN:
  651.                hMidiIn = wParam ;
  652.  
  653.                          // Free existing headers
  654.  
  655.                FreeMidiHeaderChain (hMidiIn, pMidiHdrRoot) ;
  656.  
  657.                          // Allocate root header
  658.  
  659.                if (NULL == (pMidiHdrRoot = AllocMidiHeader (hMidiIn, NULL)))
  660.                     {
  661.                     midiInClose (hMidiIn) ;
  662.                     MessageBox (hwnd, szMemError, szAppName,
  663.                                 MB_ICONEXCLAMATION | MB_OK) ;
  664.  
  665.                     return 0 ;
  666.                     }
  667.  
  668.                          // Allocate next header
  669.  
  670.                if (NULL == (pMidiHdrNext = AllocMidiHeader (hMidiIn,
  671.                                                             pMidiHdrRoot)))
  672.                     {
  673.                     FreeMidiHeaderChain (hMidiIn, pMidiHdrRoot) ;
  674.                     midiInClose (hMidiIn) ;
  675.                     MessageBox (hwnd, szMemError, szAppName,
  676.                                 MB_ICONEXCLAMATION | MB_OK) ;
  677.  
  678.                     return 0 ;
  679.                     }
  680.                          // Enable and disable buttons
  681.  
  682.                EnableWindow (GetDlgItem (hDlg, ID_RECORD_BEG), FALSE) ;
  683.                EnableWindow (GetDlgItem (hDlg, ID_RECORD_END), TRUE)  ;
  684.                EnableWindow (GetDlgItem (hDlg, ID_FILE_SAVE),  FALSE) ;
  685.                EnableWindow (GetDlgItem (hDlg, ID_PLAY_BEG),   FALSE) ;
  686.                EnableWindow (GetDlgItem (hDlg, ID_PLAY_PAUSE), FALSE) ;
  687.                EnableWindow (GetDlgItem (hDlg, ID_PLAY_END),   FALSE) ;
  688.                SetFocus (GetDlgItem (hDlg, ID_RECORD_END)) ;
  689.  
  690.                          // Submit the buffers for receiving data
  691.  
  692.                midiInShortBuffer (hMidiIn, pMidiHdrRoot, sizeof (MIDIHDR)) ;
  693.                midiInShortBuffer (hMidiIn, pMidiHdrNext, sizeof (MIDIHDR)) ;
  694.  
  695.                               // Begin recording
  696.  
  697.                midiInStart (hMidiIn) ;
  698.                bRecording = TRUE ;
  699.                bEnding = FALSE ;
  700.                return 0 ;
  701.  
  702.           case MM_MIM_DATA:
  703.                if (hMidiOut)
  704.                     {
  705.                     midiOutShortMsg (hMidiOut, lParam) ;
  706.                     }
  707.  
  708.                return 0 ;
  709.  
  710.           case MM_MIM_LONGDATA:
  711.  
  712.                if (bEnding)
  713.                     return 0 ;
  714.  
  715.                pMidiHdrNext = AllocMidiHeader (hMidiIn, pMidiHdrRoot) ;
  716.  
  717.                if (pMidiHdrNext == NULL)
  718.                     {
  719.                     midiInReset (hMidiIn) ;
  720.                     midiInClose (hMidiIn) ;
  721.                     MessageBox (hwnd, szMemError, szAppName,
  722.                                 MB_ICONEXCLAMATION | MB_OK) ;
  723.  
  724.                     return 0 ;
  725.                     }
  726.  
  727.                midiInShortBuffer (hMidiIn, pMidiHdrNext, sizeof (MIDIHDR));
  728.  
  729.                return 0 ;
  730.  
  731.           case MM_MIM_CLOSE:
  732.                               // Close the monitoring output port
  733.  
  734.                if (hMidiOut)
  735.                     {
  736.                     midiOutReset (hMidiOut) ;
  737.                     midiOutClose (hMidiOut) ;
  738.                     }
  739.  
  740.                               // Enable and Disable Buttons
  741.  
  742.                EnableWindow (GetDlgItem (hDlg, ID_RECORD_BEG), TRUE) ;
  743.                EnableWindow (GetDlgItem (hDlg, ID_RECORD_END), FALSE) ;
  744.                SetFocus (GetDlgItem (hDlg, ID_RECORD_BEG)) ;
  745.  
  746.                pMidiHdrRoot = CleanUpMidiHeaderChain (hMidiIn, pMidiHdrRoot) ;
  747.  
  748.                if (pMidiHdrRoot != NULL)
  749.                     {
  750.                     EnableWindow (GetDlgItem (hDlg, ID_FILE_SAVE),  TRUE)  ;
  751.                     EnableWindow (GetDlgItem (hDlg, ID_PLAY_BEG),   TRUE)  ;
  752.                     EnableWindow (GetDlgItem (hDlg, ID_PLAY_PAUSE), FALSE) ;
  753.                     EnableWindow (GetDlgItem (hDlg, ID_PLAY_END),   FALSE) ;
  754.                     SetFocus (GetDlgItem (hDlg, ID_PLAY_BEG)) ;
  755.                     }
  756.  
  757.                bRecording = FALSE ;
  758.  
  759.                if (bTerminating)
  760.                     {
  761.                     FreeMidiHeaderChain (hMidiIn, pMidiHdrRoot) ;
  762.                     SendMessage (hwnd, WM_SYSCOMMAND, SC_CLOSE, 0L) ;
  763.                     }
  764.  
  765.                return 0 ;
  766.  
  767.           case MM_MOM_OPEN:
  768.                hMidiOut = wParam ;
  769.  
  770.                          // Enable and Disable Buttons
  771.  
  772.                EnableWindow (GetDlgItem (hDlg, ID_RECORD_BEG), FALSE) ;
  773.                EnableWindow (GetDlgItem (hDlg, ID_RECORD_END), FALSE) ;
  774.                EnableWindow (GetDlgItem (hDlg, ID_PLAY_BEG),   FALSE) ;
  775.                EnableWindow (GetDlgItem (hDlg, ID_PLAY_PAUSE), TRUE)  ;
  776.                EnableWindow (GetDlgItem (hDlg, ID_PLAY_END),   TRUE)  ;
  777.                SetFocus (GetDlgItem (hDlg, ID_PLAY_END)) ;
  778.  
  779.                          // Submit the root buffer to begin playing
  780.  
  781.                midiOutShortBuffer (hMidiOut, pMidiHdrRoot, sizeof (MIDIHDR)) ;
  782.  
  783.                          // If there's a second buffer, submit that also
  784.  
  785.                if (NULL != (pMidiHdr = (LPMIDIHDR) pMidiHdrRoot->dwUser))
  786.                     midiOutShortBuffer (hMidiOut, pMidiHdr, sizeof (MIDIHDR)) ;
  787.  
  788.                bEnding = FALSE ;
  789.                bPlaying = TRUE ;
  790.  
  791.                return 0 ;
  792.  
  793.           case MM_MOM_DONE:
  794.  
  795.                          // If stopping playback, just return
  796.  
  797.                if (bEnding)
  798.                     return 0 ;
  799.  
  800.                          // Get header of buffer just finished playing
  801.  
  802.                pMidiHdr = (LPMIDIHDR) lParam ;
  803.  
  804.                          // Get header of next buffer (already submitted)
  805.  
  806.                pMidiHdr = (LPMIDIHDR) pMidiHdr->dwUser ;
  807.  
  808.                          // Get header of next buffer to submit now
  809.  
  810.                if (pMidiHdr != NULL)
  811.                     pMidiHdr = (LPMIDIHDR) pMidiHdr->dwUser ;
  812.  
  813.                if (pMidiHdr != NULL)
  814.                     midiOutShortBuffer (hMidiOut, pMidiHdr, sizeof (MIDIHDR)) ;
  815.                else
  816.                     {
  817.                     midiOutReset (hMidiOut) ;
  818.                     midiOutClose (hMidiOut) ;
  819.                     }
  820.  
  821.                return 0 ;
  822.  
  823.           case MM_MOM_CLOSE:
  824.  
  825.                          // Enable and Disable Buttons
  826.  
  827.                EnableWindow (GetDlgItem (hDlg, ID_RECORD_BEG), TRUE)  ;
  828.                EnableWindow (GetDlgItem (hDlg, ID_RECORD_END), TRUE)  ;
  829.                EnableWindow (GetDlgItem (hDlg, ID_PLAY_BEG),   TRUE)  ;
  830.                EnableWindow (GetDlgItem (hDlg, ID_PLAY_PAUSE), FALSE) ;
  831.                EnableWindow (GetDlgItem (hDlg, ID_PLAY_END),   FALSE) ;
  832.                SetFocus (GetDlgItem (hDlg, ID_PLAY_BEG)) ;
  833.  
  834.                SetDlgItemText (hwnd, ID_PLAY_PAUSE, "Pause") ;
  835.                bPaused = FALSE ;
  836.                bPlaying = FALSE ;
  837.  
  838.                if (bTerminating)
  839.                     {
  840.                     FreeMidiHeaderChain (hMidiIn, pMidiHdrRoot) ;
  841.                     SendMessage (hwnd, WM_SYSCOMMAND, SC_CLOSE, 0L) ;
  842.                     }
  843.  
  844.                return 0 ;
  845.  
  846.           case WM_CLOSE:
  847.                if (bRecording)
  848.                     {
  849.                     bTerminating = TRUE ;
  850.                     bEnding = TRUE ;
  851.                     midiInReset (hMidiIn) ;
  852.                     midiInClose (hMidiIn) ;
  853.                     }
  854.  
  855.                if (bPlaying)
  856.                     {
  857.                     bTerminating = TRUE ;
  858.                     bEnding = TRUE ;
  859.                     midiOutReset (hMidiOut) ;
  860.                     midiOutClose (hMidiOut) ;
  861.                     }
  862.  
  863.                break ;
  864.  
  865.           case WM_DESTROY:
  866.                PostQuitMessage (0) ;
  867.                return 0 ;
  868.           }
  869.  
  870.      return DefWindowProc (hwnd, message, wParam, lParam) ;
  871.      }
  872.  
  873. BOOL FAR PASCAL _export DlgProc (HWND hwnd, UINT message, UINT wParam,
  874.                                                           LONG lParam)
  875.      {
  876.      switch (message)
  877.           {
  878.           case WM_INITDIALOG:
  879.                return TRUE ;
  880.  
  881.           case WM_COMMAND:
  882.                SendMessage (GetParent (hwnd), WM_COMMAND, wParam, lParam) ;
  883.                return TRUE ;
  884.           }
  885.      return FALSE ;
  886.      }
  887.