//************************************************************************
//**
//**  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//**  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
//**  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR
//**  A PARTICULAR PURPOSE.
//**
//**  Copyright (C) 1993, 1994 Microsoft Corporation. All Rights Reserved.
//**
//**  proc.c
//**
//**  DESCRIPTION:
//**     This file contains most of the code to the UI and memory 
//**     allocation.
//**
//**  HISTORY:
//**     04/22/93       created.
//**     09/07/93       rewrote almost everything.
//**
//**  NOTES:
//**     Added LoadStrings() everywhere for internationalization (wow!).
//**     Yes performance gains could be gotten by loading the most used
//**     strings into memory and then they wouldn't have to be loaded 
//**     all of the time.  Just remember one thing.... This is not a time 
//**     critical application.  i.e. the user should not notice so why 
//**     do it?
//**
//************************************************************************

#include "globals.h"
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <mmreg.h>
#include "res.h"

//************************************************************************
//**
//**  AllocPtr();
//**
//**  DESCRIPTION:
//**
//**
//**  ARGUMENTS:
//**     UINT  fu
//**     DWORD cb
//**
//**  RETURNS:
//**     LPVOID 
//**
//**  HISTORY:
//**     07/09/93       
//**
//************************************************************************

LPVOID FNLOCAL AllocPtr(
   UINT  fu, 
   DWORD cb)
{
   HGLOBAL  hg;
   LPVOID   pv;

   // Try to allocate the requested memory.
   //
   hg = GlobalAlloc(fu, cb);
   if (NULL != hg)
   {
      // Convert the handle to a pointer.
      //
      pv = GlobalLock(hg);
   }
   else
   {
      // We failed to allocate the memory so
      // return NULL.
      //
      pv = NULL;
   }

   return(pv);
} //** AllocPtr()


//*****************************************************************************
//**
//**  ReAllocPtr();
//**
//**  DESCRIPTION:
//**
//**
//**  ARGUMENTS:
//**     LPVOID   pv
//**     UINT     fu
//**     DWORD    cb
//**
//**  RETURNS:
//**     LPVOID 
//**
//**  HISTORY:
//**     08/25/93       
//**
//*****************************************************************************

LPVOID FNLOCAL ReAllocPtr(
   LPVOID   pv, 
   UINT     fu, 
   DWORD    cb)
{
   HGLOBAL  hg;

   // Get the selector for the pointer.
   //
   hg = (HGLOBAL)SELECTOROF(pv);

   // Attempt to reallocate the memory.
   //
   hg = GlobalReAlloc(hg, cb, fu);
   if (NULL != hg)
   {
      // Get a pointer to the new memory object.
      //
      pv = GlobalLock(hg);
   }
   else
   {
      // The re-allocation failed.
      //
      pv = NULL;
   }

   return(pv);
} //** ReAllocPtr()


//************************************************************************
//**
//**  FreePtr();
//**
//**  DESCRIPTION:
//**
//**
//**  ARGUMENTS:
//**     LPVOID   ptr
//**
//**  RETURNS:
//**     VOID 
//**
//**  HISTORY:
//**     07/09/93             
//**
//************************************************************************

VOID FNLOCAL FreePtr(
   LPVOID FAR *ptr)
{
   if (NULL != *ptr)
   {
      // Free the memory.
      //
      GlobalFree((HGLOBAL)SELECTOROF(*ptr));

      // Initialize the pointer.
      //
      *ptr = NULL;
   }
} //** FreePtr()


//************************************************************************
//**
//**  SetWindowTitle();
//**
//**  DESCRIPTION:
//**     This function will set the window title bar to reflect the 
//**     currently loaded file.
//**
//**  ARGUMENTS:
//**     VOID
//**
//**  RETURNS:
//**     VOID 
//**
//**  HISTORY:
//**     05/02/93       created.
//**
//************************************************************************

VOID FNLOCAL SetWindowTitle(
   VOID)
{
   char  achbuf[MAX_STR_LEN+1];

   // Build the title bar string.
   //    "MIFEDIT - <mif title>"
   //
   wsprintf(achbuf, "%s - %s", (LPSTR)gszTitleBar, (LPSTR)gszmifTitle);
   SetWindowText(ghwndMain, achbuf);

} //** SetWindowTitle()



//************************************************************************
//**
//**  SetupDialog();
//**
//**  DESCRIPTION:
//**     This function sets up the dialog box for a new mif file.
//**     It enables windows, if necessary, sets the window title,
//**     sets any defaults, and updates the controls.
//**
//**  ARGUMENTS:
//**     NONE 
//**
//**  RETURNS:
//**     VOID 
//**
//**  HISTORY:
//**     04/22/93       created.
//**
//************************************************************************

VOID FNLOCAL SetupDialog()
{
   HMENU hmenu;

   hmenu = GetMenu(ghwndMain);

   // If this is our first edit, enable main child windows
   //
   if (gfFirstEdit)
   {
      // Enable the main child windows.
      //
      EnableWindow(HWND_MIF_CURRENT_SECTION, TRUE);
      EnableWindow(HWND_CURRENT_INSTRUMENT, TRUE);
      EnableWindow(HWND_EDIT_BOX, TRUE);
      EnableWindow(HWND_LIST_BOX, TRUE);
      EnableWindow(HWND_INSTRUMENT_SCROLL, TRUE);
      EnableMenuItem(hmenu, IDM_NEW_INSTRUMENT, MF_BYCOMMAND | MF_ENABLED);
      
      gfFirstEdit=FALSE;
   }

   // Reset gfChanged to FALSE
   //
   gfChanged = FALSE;

   // Gray these menu items until the mif has changed
   //
   EnableMenuItem(hmenu, IDM_FILESAVE, MF_BYCOMMAND | MF_GRAYED);
   EnableMenuItem(hmenu, IDM_FILESAVEAS, MF_BYCOMMAND | MF_GRAYED);

   // Set the window title to that of the new .mif file.
   //
   SetWindowTitle();

   // Set the current selection to the Header info.
   //
   SendMessage(HWND_MIF_CURRENT_SECTION, CB_SETCURSEL, 0, 0L);

   // Set the current instrument to the first one, and the 
   // default section to the header information.
   //
   gdwCurrInst     = 0;

   UpdateListBox();
   InvalidateRect(ghwndMain, NULL, TRUE);
}

//************************************************************************
//**
//**  NotifyUser();
//**
//**  DESCRIPTION:
//**     This function will display a message to the user.
//**
//**  ARGUMENTS:
//**     UINT  u  -  The resouce string id of the message to display.
//**
//**  RETURNS:
//**     VOID 
//**
//**  HISTORY:
//**     04/22/93       created.
//**
//************************************************************************

VOID FNLOCAL NotifyUser(
   UINT  u)
{
   char  szTitle[MAX_STR_LEN+1];
   int   cb;

   // Load the string resource.
   //
   cb = LoadString(ghinst, u, gszbuf, MAX_STR_LEN);
   if (0 != cb)
   {
      // Load the title bar string.
      //
      LoadString(ghinst, IDS_MESSAGE_BOX_TITLE, szTitle, MAX_STR_LEN);

      // Display the message to the user.
      //
      MessageBox(ghwndMain, gszbuf, szTitle, MB_OK);
   }
} //** NotifyUser()


//*****************************************************************************
//**
//**  QuerySave();
//**
//**  DESCRIPTION:
//**
//**
//**  ARGUMENTS:
//**     VOID
//**
//**  RETURNS:
//**     BOOL 
//**
//**  HISTORY:
//**     09/07/93      created.
//**
//*****************************************************************************

int FNLOCAL QuerySave(
   VOID)
{
   char  szTitle[MAX_STR_LEN+1];
   int   i;

   // Load the string resources.
   //
   LoadString(ghinst, IDS_SAVE_CHANGES, gszbuf, MAX_STR_LEN);
   LoadString(ghinst, IDS_MESSAGE_BOX_TITLE, szTitle, MAX_STR_LEN);

   // Popup a message box with "YES", "NO" and "CANCEL" buttons
   // and ask user if they want to save the file....
   //
   i = MessageBox(ghwndMain, gszbuf, szTitle, MB_YESNOCANCEL);
   return(i);
} //** QuerySave()


//************************************************************************
//**
//**  CleanUp();
//**
//**  DESCRIPTION:
//**     This function will close a file if it is open, and free any
//**     memory that is being used by the application.
//**
//**  ARGUMENTS:
//**     VOID
//**
//**  RETURNS:
//**     VOID 
//**
//**  HISTORY:
//**     04/27/93       created.
//**
//************************************************************************

VOID FNLOCAL CleanUp(
   VOID)
{
   DWORD c;

   // If there is a file currently opened, then close it and
   // NULL out the handle.
   //
   if (NULL != ghmmio)
   {
      mmioClose(ghmmio, 0);
      ghmmio = NULL;
   }

   // Free the instruments.
   //
   if (NULL != gpInst)
   {
      for ( c = 0;
            c < gdwNumInsts;
            c++)
      {
         // Free the ASCII version of the instrument's
         // manufacturer name.
         //
         if (NULL != gpInst[c].pszManufactASCII)
            FreePtr(&gpInst[c].pszManufactASCII);
            
         // Free the UNICODE version of the instrument's
         // manufacturer name.
         //
         if (NULL != gpInst[c].pszManufactUNICODE)
            FreePtr(&gpInst[c].pszManufactUNICODE);

         // Free the ASCII version of the instrument's
         // product name.
         //
         if (NULL != gpInst[c].pszProductASCII)
            FreePtr(&gpInst[c].pszProductASCII);

         // Free the UNICODE version of the instrument's
         // product name.
         //
         if (NULL != gpInst[c].pszProductUNICODE)
            FreePtr(&gpInst[c].pszProductUNICODE);
      }

      // Free all of the instruments.
      //
      FreePtr(&gpInst);
   }

   // Initialize the current number of instruments and the
   // current instrument.
   //
   gdwNumInsts = 0;
   gdwCurrInst = 0;

} //** CleanUp()


//*****************************************************************************
//**
//**  SetListBoxTitleBar();
//**
//**  DESCRIPTION:
//**
//**
//**  ARGUMENTS:
//**     UINT  uTitleBarID
//**
//**  RETURNS:
//**     MMRESULT 
//**
//**  HISTORY:
//**     09/06/93             created.
//**
//*****************************************************************************

MMRESULT FNLOCAL SetListBoxTitleBar(
   UINT  uTitleBarID)
{
   LPSTR psz;
   
   // Get the string for the title bar.
   //
   LoadString(ghinst, uTitleBarID, gszbuf, MAX_STR_LEN);

   // Parse our the left and right strings.
   // 
   for ( psz = gszbuf;
         *psz != '!';
         psz = AnsiNext(psz))
      ;

   *psz++ = '\0';

   // Set the title bar for the left side.
   //
   SetDlgItemText(ghwndMain, ID_HEADER_TEXT_LEFT, gszbuf);

   // Set the title bar for the right side.
   //
   SetDlgItemText(ghwndMain, ID_HEADER_TEXT_RIGHT, psz);

   return(MMSYSERR_NOERROR);
} //** SetListBoxTitleBar()


//*****************************************************************************
//**
//**  DrawKeyMaps();
//**
//**  DESCRIPTION:
//**
//**
//**  ARGUMENTS:
//**     VOID
//**
//**  RETURNS:
//**     VOID 
//**
//**  HISTORY:
//**     09/07/93            created.
//**
//*****************************************************************************

VOID FNLOCAL DrawKeyMaps(
   VOID)
{
   UINT  c;
   UINT  cKeyMap;
   UINT  cChannel;
   char  szKeyMapName[MAX_STR_LEN+1];

   // Get the drum channel.
   //
   c = (UINT)SendMessage(HWND_DRUM_CHANNELS, CB_GETCURSEL, 0, 0L);

   // Find the actual channel number that this is for.
   //
   for ( cChannel = 0;
         (0 != c) && (cChannel < MAX_CHANNELS);
         cChannel++)
   {
      // If we find a drum channel then decrement the channel 
      // index.
      //
      if (DRUM_CHANNEL == CURR_CHANNEL_TYPE(cChannel))
         c--;
   }

   // Fill the list box with the key map for the drum channel.
   //
   for ( cKeyMap = 0;
         cKeyMap < MAX_KEY_MAPS;
         cKeyMap++)
   {
      // If the key map has a name the load it from our
      // string table, otherwise use the key.
      //
      if ((cKeyMap < DRUM_KEY_START) || 
          (cKeyMap > DRUM_KEY_END))
      {
         // Build the string to display in the list box.
         //
         wsprintf(gszbuf, 
                  gszUU, 
                  cKeyMap + 1, 
                  CURR_KEY_MAP(cChannel, cKeyMap) + 1);
      }
      else
      {
         // Get the name of the patch from the string table.
         //
         LoadString(ghinst, 
                    (UINT)DRUM_KEY + cKeyMap, 
                    szKeyMapName, 
                    MAX_STR_LEN);

         // Build the string to display in the list box.
         //
         wsprintf(gszbuf, 
                  gszSU, 
                  (LPSTR)szKeyMapName, 
                  CURR_KEY_MAP(cChannel, cKeyMap) + 1);
      }

      // Add the string to the list box.
      //
      SendMessage(HWND_LIST_BOX, LB_ADDSTRING, 0, (LPARAM)(LPSTR)gszbuf);
   }


} //** DrawKeyMaps()


//************************************************************************
//**
//**  DrawPatchMapInfo();
//**
//**  DESCRIPTION:
//**     This funciton will display the current instrument's patch map
//**     information in the list box.
//**
//**  ARGUMENTS:
//**     VOID
//**
//**  RETURNS:
//**     VOID 
//**
//**  HISTORY:
//**     04/28/93       created.
//**
//************************************************************************

VOID FNLOCAL DrawPatchMapInfo(
   VOID)
{
   UINT  c;
   char  szPatchName[MAX_STR_LEN+1];
   
   // Set the title bar for patch maps.
   //
   SetListBoxTitleBar(IDS_TITLE_BAR_PATCHES);

   // Fill the list box with the patch map information.
   //
   for ( c = 0; 
         c < MAX_PATCHES; 
         c++)
   {
      // Get the name of the patch from the string table.
      //
      LoadString(ghinst, 
                 (UINT)(GENERAL_PATCH + c),
                 szPatchName, 
                 MAX_STR_LEN);

      // Build the string to display in the list box.
      //
      wsprintf(gszbuf, gszSU, (LPSTR)szPatchName, CURR_PATCH_MAP(c) + 1);

      // Add the string to the list box.
      //
      SendMessage(HWND_LIST_BOX, LB_ADDSTRING, 0, (LPARAM)(LPSTR)gszbuf);
   }
   
} //** DrawPatchMapInfo()


//************************************************************************
//**
//**  DrawChannelTypes();
//**
//**  DESCRIPTION:
//**     This function will display the current instrument's channel
//**     type information in the list box.
//**
//**  ARGUMENTS:
//**     VOID
//**
//**  RETURNS:
//**     VOID 
//**
//**  HISTORY:
//**     04/28/93       created.
//**
//************************************************************************

VOID FNLOCAL DrawChannelTypes(
   VOID)
{
   UINT  c;
   char  szChannelName[MAX_STR_LEN+1];

   // Set the title bar for the list box.
   //
   SetListBoxTitleBar(IDS_TITLE_BAR_CHANNELS);

   // Fill the list box with the channel information.
   //
   for ( c = 0; 
         c < MAX_CHANNELS; 
         c++)
   {
      // Get the string for the channel type.
      //
      LoadString(ghinst, 
                 (UINT)(IDS_CHANNEL_TYPE_GENERAL + CURR_CHANNEL_TYPE(c)),
                 (LPSTR)szChannelName, 
                 (int)MAX_STR_LEN);   
      
      // Build the string to display in the list box.
      //
      wsprintf(gszbuf, 
               gszUS,
               c + 1, 
               (LPSTR)szChannelName);

      // Add the string to the list box.
      //
      SendMessage(HWND_LIST_BOX, LB_ADDSTRING, 0, (LPARAM)(LPSTR)gszbuf);
   }

} //** DrawChannelTypes()


//************************************************************************
//**
//**  DrawInstCaps();
//**
//**  DESCRIPTION:
//**     This function will display the current instrument's 
//**     capabilities in the list box.
//**
//**  ARGUMENTS:
//**     VOID
//**
//**  RETURNS:
//**     VOID 
//**
//**  HISTORY:
//**     04/28/93       created.
//**
//************************************************************************

VOID FNLOCAL DrawInstCaps(
   VOID)
{
   BOOL  f;
   char  szCapability[MAX_STR_LEN+1];

   // Set the title bar for the list box.
   //
   SetListBoxTitleBar(IDS_TITLE_BAR_INSTCAPS);

   // Display the base channel.
   //
   LoadString(ghinst, IDS_CAPS_BASE, szCapability, MAX_STR_LEN);
   wsprintf(gszbuf, gszSU,
            (LPSTR)szCapability, 
            (UINT)gpInst[gdwCurrInst].dwBasicChannel);
   SendMessage(HWND_LIST_BOX, LB_ADDSTRING, 0, (LPARAM)(LPSTR)gszbuf);

   // Display the number of channels.
   //
   LoadString(ghinst, IDS_CAPS_BASE + 1, szCapability, MAX_STR_LEN);
   wsprintf(gszbuf, 
            gszSU,
            (LPSTR)szCapability, 
            (UINT)gpInst[gdwCurrInst].cNumChannels);
   SendMessage(HWND_LIST_BOX, LB_ADDSTRING, 0, (LPARAM)(LPSTR)gszbuf);

   // Display the instrument's polyphony.
   //
   LoadString(ghinst, IDS_CAPS_BASE + 2, szCapability, MAX_STR_LEN);
   wsprintf(gszbuf, 
            gszSU,
            (LPSTR)szCapability, 
            gpInst[gdwCurrInst].cInstPoly);
   SendMessage(HWND_LIST_BOX, LB_ADDSTRING, 0, (LPARAM)(LPSTR)gszbuf);

   // Display the channel polyphony.
   //
   LoadString(ghinst, IDS_CAPS_BASE + 3, szCapability, MAX_STR_LEN);
   wsprintf(gszbuf, 
            gszSU,
            (LPSTR)szCapability, 
            gpInst[gdwCurrInst].cChannelPoly);
   SendMessage(HWND_LIST_BOX, LB_ADDSTRING, 0, (LPARAM)(LPSTR)gszbuf);

   // Display the general midi compatibility.
   //
   f = (BOOL)(gpInst[gdwCurrInst].fdwFlags & MIFINSTCAPS_F_GENERAL_MIDI);

   LoadString(ghinst, IDS_CAPS_BASE + 4, szCapability, MAX_STR_LEN);
   wsprintf(gszbuf, 
            gszSS,
            (LPSTR)szCapability, 
            (LPSTR)(f ? "TRUE" : "FALSE"));
   SendMessage(HWND_LIST_BOX, LB_ADDSTRING, 0, (LPARAM)(LPSTR)gszbuf);

   // Does the instrument support sysex messages?
   //
   f = (BOOL)(gpInst[gdwCurrInst].fdwFlags & MIFINSTCAPS_F_SYSTEMEXCLUSIVE);

   LoadString(ghinst, IDS_CAPS_BASE + 5, szCapability, MAX_STR_LEN);
   wsprintf(gszbuf, 
            gszSS,
            (LPSTR)szCapability,
            (LPSTR)(f ? "TRUE" : "FALSE"));
   SendMessage(HWND_LIST_BOX, LB_ADDSTRING, 0, (LPARAM)(LPSTR)gszbuf);
} //** DrawInstCaps()


//************************************************************************
//**
//**  DrawInstrumentInfo();
//**
//**  DESCRIPTION:
//**     This function will display the current instrument's instrument 
//**     information in the list box.
//**
//**  ARGUMENTS:
//**     VOID
//**
//**  RETURNS:
//**     VOID 
//**
//**  HISTORY:
//**     04/28/93       created.
//**     09/07/93       rewrote to use string tables.
//**
//************************************************************************

VOID FNLOCAL DrawInstrumentInfo(
   VOID)
{
   char  szInstrumentInfo[MAX_STR_LEN+1];

   // Set the title bar for the list box.
   //
   SetListBoxTitleBar(IDS_TITLE_BAR_INSTINFO);

   // Display the manufacturer.
   //
   LoadString(ghinst, IDS_INST_BASE, szInstrumentInfo, MAX_STR_LEN);
   wsprintf(gszbuf, 
            gszSS, 
            (LPSTR)szInstrumentInfo, 
            (LPSTR)gpInst[gdwCurrInst].pszManufactASCII);
   SendMessage(HWND_LIST_BOX, LB_ADDSTRING, 0, (LPARAM)(LPSTR)gszbuf);

   // Display the product.
   //
   LoadString(ghinst, IDS_INST_BASE + 1, szInstrumentInfo, MAX_STR_LEN);
   wsprintf(gszbuf, 
            gszSS, 
            (LPSTR)szInstrumentInfo, 
            (LPSTR)gpInst[gdwCurrInst].pszProductASCII);
   SendMessage(HWND_LIST_BOX, LB_ADDSTRING, 0, (LPARAM)(LPSTR)gszbuf);

   // Display the Manufacturer's ID.
   //
   LoadString(ghinst, IDS_INST_BASE + 2, szInstrumentInfo, MAX_STR_LEN);
   wsprintf(gszbuf, 
            gszSU, 
            (LPSTR)szInstrumentInfo, 
            (UINT)gpInst[gdwCurrInst].dwManufactID);
   SendMessage(HWND_LIST_BOX, LB_ADDSTRING, 0, (LPARAM)(LPSTR)gszbuf);

   // Display the product's ID.
   //
   LoadString(ghinst, IDS_INST_BASE + 3, szInstrumentInfo, MAX_STR_LEN);
   wsprintf(gszbuf, 
            gszSU,
            (LPSTR)szInstrumentInfo, 
            (UINT)gpInst[gdwCurrInst].dwProductID);
   SendMessage(HWND_LIST_BOX, LB_ADDSTRING, 0, (LPARAM)(LPSTR)gszbuf);

   // Display the Revision.
   //
   LoadString(ghinst, IDS_INST_BASE  + 4, szInstrumentInfo, MAX_STR_LEN);
   wsprintf(gszbuf, 
            gszSU,
            (LPSTR)szInstrumentInfo, 
            (UINT)gpInst[gdwCurrInst].dwRevision);
   SendMessage(HWND_LIST_BOX, LB_ADDSTRING, 0, (LPARAM)(LPSTR)gszbuf);
} //** DrawInstrumentInfo()


//************************************************************************
//**
//**  DrawHeader();
//**
//**  DESCRIPTION:
//**     This function will display the current instruments header 
//**     information in the list box.
//**     
//**  ARGUMENTS:
//**     VOID
//**
//**  RETURNS:
//**     VOID 
//**
//**  HISTORY:
//**     04/28/93       created.
//**
//************************************************************************

VOID FNLOCAL DrawHeader(
   VOID)
{
   char  szHeaderString[MAX_STR_LEN+1];

   // Set the title bar for the list box.
   //
   SetListBoxTitleBar(IDS_TITLE_BAR_HEADER);

   // Get the string resource.
   //
   LoadString(ghinst, IDS_HEADER_BASE, szHeaderString, MAX_STR_LEN);

   // Build the string to display.
   //
   wsprintf(gszbuf,
            gszSX,
            (LPSTR)szHeaderString, 
            (UINT)gpInst[gdwCurrInst].dwVersion);

   // Add the string resource to the list box.
   //
   SendMessage(HWND_LIST_BOX, LB_ADDSTRING, 0, (LPARAM)(LPCSTR)gszbuf);

   // Get the string resource.
   //
   LoadString(ghinst, IDS_HEADER_BASE + 1, szHeaderString, MAX_STR_LEN);

   // Build the string to display.
   //
   wsprintf(gszbuf, 
            gszSX,
            (LPSTR)szHeaderString, 
            (UINT)gpInst[gdwCurrInst].dwCreator);

   // Add the string resource to the list box.
   //
   SendMessage(HWND_LIST_BOX, LB_ADDSTRING, 0, (LPARAM)(LPCSTR)gszbuf);

} //** DrawHeader()


//*****************************************************************************
//**
//**  EnableChannelListBox();
//**
//**  DESCRIPTION:
//**
//**
//**  ARGUMENTS:
//**     VOID
//**
//**  RETURNS:
//**     VOID 
//**
//**  HISTORY:
//**     09/07/93             created.
//**
//*****************************************************************************

VOID FNLOCAL EnableChannelListBox(
   VOID)
{
   char  szChannel[MAX_STR_LEN+1];
   DWORD cChannel;

   // Enable the drop down list box.
   //
   EnableWindow(HWND_DRUM_CHANNELS, TRUE);

   // Show the drop down list box.
   //
   ShowWindow(HWND_DRUM_CHANNELS, SW_SHOW);

   // Reset the contents of the list box.
   //
   SendMessage(HWND_DRUM_CHANNELS, CB_RESETCONTENT, 0, 0L);

   // Set the title bar for patch maps.
   //
   SetListBoxTitleBar(IDS_TITLE_BAR_KEYMAPS);

   // Get the string from our string table.
   //
   LoadString(ghinst, IDS_CHANNEL, szChannel, MAX_STR_LEN);

   // Fill the drop down list box with the drum channels.
   //
   for ( cChannel = 0;
         cChannel < MAX_CHANNELS;
         cChannel++)
   {
      // Is this a drum channel?
      //
      if (MIFCHANNELINFO_DRUM_CHANNEL != CURR_CHANNEL_TYPE(cChannel))
         continue;

      // Build the string to display.
      //
      wsprintf(gszbuf, "%s: %u", (LPSTR)szChannel, cChannel+1);

      // Add the string to the list box.
      //
      SendMessage(HWND_DRUM_CHANNELS, CB_ADDSTRING, 0, (LPARAM)(LPCSTR)gszbuf);
   }

   // Set the current selection to the first drum channel.
   //
   SendMessage(HWND_DRUM_CHANNELS, CB_SETCURSEL, 0, 0L);

} //** EnableChannelListBox()


//*****************************************************************************
//**
//**  DisableChannelListBox();
//**
//**  DESCRIPTION:
//**
//**
//**  ARGUMENTS:
//**     VOID
//**
//**  RETURNS:
//**     VOID 
//**
//**  HISTORY:
//**     09/07/93           created.
//**
//*****************************************************************************

VOID FNLOCAL DisableChannelListBox(
   VOID)
{
   // Disable the drop down list box.
   //
   EnableWindow(HWND_DRUM_CHANNELS, FALSE);

   // Hide the drop down list box.
   //
   ShowWindow(HWND_DRUM_CHANNELS, SW_HIDE);

} //** DisableChannelListBox()


//************************************************************************
//**
//**  UpdateListBox();
//**
//**  DESCRIPTION:
//**     This function will redraw the contents of the list box depending
//**     on what guCurrSelection says is the currently viewed section.
//**
//**  ARGUMENTS:
//**     VOID
//**
//**  RETURNS:
//**     VOID 
//**
//**  HISTORY:
//**     05/01/93       created.
//**
//************************************************************************

VOID FNLOCAL UpdateListBox(
   VOID)
{
   UINT  uPrevSelection;

   // Disable drawing of the list box.
   //
   SendMessage(HWND_LIST_BOX, WM_SETREDRAW, FALSE, 0L);

   // Clear the current list box's contents.
   //
   SendMessage(HWND_LIST_BOX, LB_RESETCONTENT, 0, 0L);

   // Clear the edit box.
   //
   SetDlgItemText(ghwndMain, ID_EDIT_BOX, gszNULL);

   // Disable the enter button.
   //
   EnableWindow(HWND_EDIT_ENTER, FALSE);

   // Save the previous selection.
   //
   uPrevSelection = guCurrSelection;

   // Get the current selection in the combo-box.
   //
   guCurrSelection = (UINT)SendMessage(HWND_MIF_CURRENT_SECTION, 
                                       CB_GETCURSEL, 
                                       0, 
                                       0L);

   // Was the previous selection key maps?
   //
   if (MIF_KEY_MAPS == uPrevSelection)
   {
      // Are we key maps now?
      //
      if (MIF_KEY_MAPS != guCurrSelection)
      {
         // We switch from key maps to not key maps,
         // display the edit box and hide the drop down list box.
         //
         DisableChannelListBox();
      }
   }
   else
   {
      // Are we key maps now?
      //
      if (MIF_KEY_MAPS == guCurrSelection)
      {
         // We switched from not key maps to key maps, 
         // hide the edit box to the drop down list box.
         //
         EnableChannelListBox();
      }
   }

   // Draw the correct section info.
   //
   switch (guCurrSelection)
   {
      case MIF_HEADER_INFO:
         // Redraw the header info.
         //
         DrawHeader();

         break;

      case MIF_INSTRUMENT_INFO:
         // Update the instrument information.
         //
         DrawInstrumentInfo();

         break;

      case MIF_INSTRUMENT_CAPS:
         // Update the midi capabilities information.
         //
         DrawInstCaps();

         break;

      case MIF_CHANNEL_TYPE:
         // Update the channel type.
         //
         DrawChannelTypes();

         break;

      case MIF_PATCH_MAPS:
         // Update the patch map.
         //
         DrawPatchMapInfo();

         break;

      case MIF_KEY_MAPS:
         // Update hte key map.
         //
         DrawKeyMaps();
         
         break;
   }

   // Set the current selection to the first string.
   //
   SetFocus(HWND_LIST_BOX);
   SendMessage(HWND_LIST_BOX, LB_SETCURSEL, 0, 0L);

   // Enable redrawing of the list box.
   //
   SendMessage(HWND_LIST_BOX, WM_SETREDRAW, TRUE, 0L);

} //** UpdateListBox()


//************************************************************************
//**
//**  AllocateInst();
//**
//**  DESCRIPTION:
//**     This function will allocate or reallocate the MIF pointer.
//**     If the gpInst pointer is NULL then it will allocate memory for
//**     a new instrument, if gpMIF is _NOT_ NULL then it will reallocate
//**     the pointer so that it will contain space for one more instrument.
//**
//**  ARGUMENTS:
//**     VOID
//**
//**  RETURNS:
//**     VOID
//**
//**  HISTORY:
//**     05/01/93       created.
//**
//************************************************************************

BOOL FNLOCAL AllocateInst(
   VOID)
{
   LPVOID   pv;

   // Do we already have instruments allocated?
   //
   if (NULL == gpInst)
   {
      // Nope, so allocate memory for one.
      //
      pv = AllocPtr(GHND, sizeof(INSTRUMENT) * (gdwNumInsts + 1));
   }
   else
   {
      // Yep, so reallocate memory for another.
      //
      pv = ReAllocPtr(gpInst, GHND, sizeof(INSTRUMENT) * (gdwNumInsts + 1));
   }

   // Did we allocate anything, new or otherwise?
   //
   if (NULL != pv)
   {
      // We've got another instrument allocated.
      //
      gpInst = pv;
      gdwCurrInst = gdwNumInsts;
      gdwNumInsts++;
      return(TRUE);
   }

   return(FALSE);
} //** AllocateInst()


//************************************************************************
//**
//**  NewInstrument();
//**
//**  DESCRIPTION:
//**     This function will allocate and initialize the memory for an
//**     instrument the mif.
//**
//**  ARGUMENTS:
//**     VOID
//**
//**  RETURNS:
//**     MMRESULT -  MMSYSERR_NOERROR on success or MIFERR_NOMEM if 
//**                 it cannot allocate memory for the instrument.
//**
//**  HISTORY:
//**     04/27/93       created.
//**
//************************************************************************

MMRESULT FNLOCAL NewInstrument(
   VOID)
{
   UINT  cPatch;
   UINT  cChannel;
   UINT  cKeyMap;


   // Allocate a new MIF struct.
   //
   if (!AllocateInst())
      return(MIFERR_NOMEM);

   // Fill in the instruments fields.
   //
   gpInst[gdwCurrInst].dwVersion       = MIF_FMT_VERSION;
   gpInst[gdwCurrInst].dwCreator       = MM_MICROSOFT;
   gpInst[gdwCurrInst].fdwFlags        |= MIFINSTCAPS_F_GENERAL_MIDI;
   gpInst[gdwCurrInst].dwBasicChannel  = 1;
   gpInst[gdwCurrInst].cNumChannels    = 16;
   gpInst[gdwCurrInst].cInstPoly       = 16;
   gpInst[gdwCurrInst].cChannelPoly    = 16;

   // All channels are initialized to MIFCHANNELINFO_GENERAL_CHANNEL, 0,
   // when the instrument structure is allocated (GMEM_ZEROINIT).  So
   // all we have to do is set the drum channel.
   //
   CURR_CHANNEL_TYPE(DRUM_CHANNEL) = MIFCHANNELINFO_DRUM_CHANNEL;

   // Initialize the patch maps.
   //
   for ( cPatch = 0; 
         cPatch < MAX_PATCHES; 
         cPatch++)
   {
      // Set up each patch as General MIDI.
      //
      CURR_PATCH_MAP(cPatch) = cPatch;
   }

   // Initialize the key maps.
   //
   for ( cChannel = 0;
         cChannel < MAX_CHANNELS;
         cChannel++)
   {
      for ( cKeyMap = 0;
            cKeyMap < MAX_KEY_MAPS;
            cKeyMap++)
      {
         // Start out with General MIDI.
         //
         CURR_KEY_MAP(cChannel, cKeyMap) = cKeyMap;
      }
   }

   // Place the number of the current instrument in the 
   // edit box. Add one to the index to give a nice readable
   // number to non-computer types.
   //
   SetDlgItemInt(ghwndMain, 
                 ID_CURRENT_INSTRUMENT, 
                 (UINT)gdwCurrInst + 1, 
                 FALSE);

   // Reset the scroll bar's range.
   //
   SetScrollRange(HWND_CURRENT_INSTRUMENT,
                  SB_CTL, 
                  0, 
                  (UINT)gdwCurrInst, 
                  FALSE);

   // Set the scroll bar position to the new instrument.
   //
   SetScrollPos(HWND_CURRENT_INSTRUMENT,
                SB_CTL,
                (UINT)gdwCurrInst, 
                TRUE);

   // Set return value to success.
   //
   return(MMSYSERR_NOERROR);
} //** NewInstrument()


//************************************************************************
//**
//**  NewConfigFile();
//**
//**  DESCRIPTION:
//**     This function will clear any mif that is currently being
//**     edited and create an new mif and a default instrument to edit.
//**
//**  ARGUMENTS:
//**     VOID
//**
//**  RETURNS:
//**     MMRESULT -  MMSYSERR_NOERROR upon success, 
//**                 MIFERR_NOMEM if it could not allcoate a new 
//**                 instrument.
//**
//**  HISTORY:
//**     04/26/93       created.
//**
//************************************************************************

MMRESULT FNLOCAL NewConfigFile(
   VOID)
{
   MMRESULT mmr;

   // Dump any current information.
   //
   CleanUp();

   // Allocate memory for an instrument.
   //
   mmr = NewInstrument();
   if (MMSYSERR_NOERROR != mmr)
      return(mmr);

   // Load the default file name.
   //
   LoadString(ghinst, IDS_DEF_FILE_NAME, gszmifTitle, MAX_TITLE_LEN);

   // Initialize the path name.
   //
   _fmemset(gszmifName, 0, MAX_PATH_LEN + 1);

   // Setup the dialog box for the new mif file
   //
   SetupDialog();

   return(MMSYSERR_NOERROR);
} //** NewConfigFile()


//************************************************************************
//**
//**  MenuCommand();
//**
//**  DESCRIPTION:
//**     This function will process any menu commands.
//**
//**  ARGUMENTS:
//**     WPARAM wParam  -  Menu item selected.
//**     LPARAM lParam  -  Command Dependant data.
//**
//**  RETURNS:
//**     VOID 
//**
//**  HISTORY:
//**     04/28/93       created.
//**
//************************************************************************

VOID FNLOCAL MenuCommand(
   WPARAM wParam, 
   LPARAM lParam)
{
   MMRESULT mmr;
   HMENU hmenu;

   switch (wParam)
   {
      case IDM_FILENEW:
         // Setup a new configuration file.
         //
         mmr = NewConfigFile();
         if (MMSYSERR_NOERROR != mmr)
            NotifyUser(mmr);

         break;

      case IDM_FILEOPEN:
         // Open an existing configuration file.
         //
         mmr = OpenConfigFile();
         if (MMSYSERR_NOERROR != mmr)
            NotifyUser(mmr);

         break;

      case IDM_FILESAVE:
         // Save the current configuration file.
         //
         if ('\0' == gszmifName[0])
            mmr = SaveConfigFileAs();
         else
            mmr = SaveConfigFile();

         if (MMSYSERR_NOERROR != mmr)
            NotifyUser(mmr);

         break;

      case IDM_FILESAVEAS:
         // Save the current configuration file as something....
         //
         mmr = SaveConfigFileAs();
         if (MMSYSERR_NOERROR != mmr)
            NotifyUser(mmr);

         break;

      case ID_MIF_CURRENT_SECTION:
         // Did the selection change?
         //
         if (CBN_CLOSEUP == HIWORD(lParam))
            UpdateListBox();

         break;

      case ID_DRUM_CHANNELS:
         // Check to see if the user selected a different drum channel.
         //
         if (CBN_CLOSEUP == HIWORD(lParam))
         {
         }

         break;

      case ID_EDIT_BOX:
         // Has something been typed?
         //
         if (EN_CHANGE == HIWORD(lParam))
         {
            // Enable the Enter button.
            //
            EnableWindow(HWND_EDIT_ENTER, TRUE);
         }

         break;

      case ID_EDIT_ENTER:
         // Was the enter button clicked?
         //
         if (BN_CLICKED == HIWORD(lParam))
         {
            // Update the instrument's information with
            // the new data the the user has entered.
            //
            UpdateInstrument();
         }

         // Is this the first change??  
         // If so, activate the save menu.
         //
         if (!gfChanged)
         {
             hmenu = GetMenu(ghwndMain);
             EnableMenuItem(hmenu, IDM_FILESAVE, MF_BYCOMMAND | MF_ENABLED);
             EnableMenuItem(hmenu, IDM_FILESAVEAS, MF_BYCOMMAND | MF_ENABLED);

             // A change has been made.
             //
             gfChanged = TRUE;
         }
         break;

      case IDM_NEW_INSTRUMENT:
         // Add a new instrument to the whole thing.
         //
         mmr = NewInstrument();
         if (MMSYSERR_NOERROR != mmr)
         {
            // There was an error while trying to create a new instrument.
            //
            NotifyUser(mmr);
         }

         // Update the list box with the new instruments settings.
         //
         UpdateListBox();

         break;

      case IDM_FILEEXIT:
         // Destroy the window.
         //
         PostMessage(ghwndMain, WM_CLOSE, 0, 0L);

         break;

      case IDM_ABOUT:
         // Show the about box.
         //
         About();

         break;
   }
} //** MenuCommand()


//************************************************************************
//**
//**  MainProc();
//**
//**  DESCRIPTION:
//**     Processes the messages for the application.
//**
//**  ARGUMENTS:
//**     HWND     hwnd     -  Window that we process messages for.
//**     UINT     umsg     -  Message ID.
//**     WPARAM   wParam   -  Message dependant info.
//**     LPARAM   lParam   -  Message dependant info.
//**
//**  RETURNS:
//**     LRESULT  -  Return value depends upon the message.
//**
//**  HISTORY:
//**     04/22/93       created.
//**
//************************************************************************

LRESULT FNEXPORT MainProc(
   HWND     hwnd,
   UINT     umsg,
   WPARAM   wParam,
   LPARAM   lParam)
{
   PAINTSTRUCT ps;
   MMRESULT    mmr;
   int         i;

   switch (umsg)
   {
      case WM_PAINT:
         BeginPaint(hwnd, &ps);

         EndPaint(hwnd, &ps);

         return(0L);

      case WM_VSCROLL:

         // Did they want to switch instruments?
         //
         if (((HWND)HIWORD(lParam)) == HWND_INSTRUMENT_SCROLL)
         {
            
            // What did the user do this time?
            //
            switch (wParam)
            {
               case SB_LINEUP:
                  // Subtract one form the current instrument.
                  // If we are at the first instrument the wrap 
                  // to the last instrument.
                  //
                  if (0 == gdwCurrInst)
                  {
                     // Remeber that gdwCurrInst is an index whereas
                     // gdwNumInsts is a count.  So we need to subtract
                     // one from the count to get the index.
                     // 
                     gdwCurrInst = gdwNumInsts - 1;
                  }
                  else
                  {
                     // Get the previous instrument.
                     //
                     gdwCurrInst--;
                  }

                  break;
            
               case SB_LINEDOWN:
                  // Add one to the current instrument.
                  // If we are at the last instrument then wrap
                  // to the first instrument.
                  //
                  if (gdwNumInsts == gdwCurrInst+1)
                  {
                     // Set the current instrument to the 
                     // first instrument in our instrument array.
                     //
                     gdwCurrInst = 0;
                  }
                  else
                  {
                     // Get the next instrument.
                     //
                     gdwCurrInst++;
                  }

                  break;


               default:
                  return(0L);
            }


            // Update the edit field.
            // We add one to give a nice readable number to
            // non-computer types....
            //
            SetDlgItemInt(ghwndMain, 
                          ID_CURRENT_INSTRUMENT, 
                          (UINT)gdwCurrInst + 1, 
                          FALSE);

            // Update the contents of the dialog.
            //
            UpdateListBox();
         }

         return(0L);

      case WM_COMMAND:
         // Process menu options.
         //
         MenuCommand(wParam, lParam);

         return(0L);

      case WM_CLOSE:

         // Were any modifications made to the file?
         //
         if (gfChanged)
         {
            // Query the user for save.
            //
            i = QuerySave();

            // Did they decide to continue?
            //
            if (IDCANCEL == i)
               return(0L);

            // Do they wish to save the current MIF?
            //
            if (IDYES == i)
            {
               // Save the current MIF.
               //
               if ('\0' == gszmifName[0])
                  mmr = SaveConfigFileAs();
               else
                  mmr = SaveConfigFile();

               // We're we able to save the file?
               //
               if (MMSYSERR_NOERROR != mmr)
                  NotifyUser(mmr);
            }

            //
            // By default if the user selects "YES" or "NO" we
            // fall through and destroy the window and consequently
            // exit the program.
            //
         }

         // Destroy main window.
         //
         DestroyWindow(ghwndMain);

         return(0L);

      case WM_DESTROY:
         // We're done so clean up.
         //
         CleanUp();

         // Post our quit message.
         //
         PostQuitMessage(0);

         return(0L);

      default:
         // Take care of everything else.
         //
         return(DefWindowProc(hwnd, umsg, wParam, lParam));
   }
} //** MainProc()


