#include <windows.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include "app.h"

/*
 * Module: play.c
 *
 * Contains: All functions associated with interpreting
 * and playing the Edit buffer should go here.
 *
 */


/*********************************************************************/
/* Local Function Prototypes                                         */
/*********************************************************************/

BOOL PlayLine( void );
BOOL PlayTokens( int );
BOOL PlayFunction( int );
int  PlayGetToken(char **, char *, char *);
void PlayError(char *);

BOOL PlayCloseSound( void );
BOOL PlayCountVoiceNotes( void );
BOOL PlayGetThresholdEvent( void );
BOOL PlayGetThresholdStatus( void );
BOOL PlayOpenSound( void );
BOOL PlaySetSoundNoise( void );
BOOL PlaySetVoiceAccent( void );
BOOL PlaySetVoiceEnvelope( void );
BOOL PlaySetVoiceNote( void );
BOOL PlaySetVoiceQueueSize( void );
BOOL PlaySetVoiceSound( void );
BOOL PlaySetVoiceThreshold( void );
BOOL PlayStartSound( void );
BOOL PlayStopSound( void );
BOOL PlaySyncAllVoices( void );
BOOL PlayWaitSoundState( void );

BOOL PlayIntConvert(char *, int *);
BOOL PlayLongConvert(char *, long *);


/*********************************************************************/
/* Local data and structures                                         */
/*********************************************************************/
#define MAXLINE     80
#define MAXPARM      6
#define MAXPARMLEN  33

extern SndDef SndState;

static BOOL b;
static int rc;
static char rcmsg[80];

static char pfunction[MAXPARMLEN];
static char parms[MAXPARM][MAXPARMLEN];
static char *parm[MAXPARM];

static int  ebuffchar;
static char ebuffline[MAXLINE];

static struct {
  char *fname;
  int parms;
  BOOL (*func)(void);
} wsound[] = {
  "CloseSound",          0,   PlayCloseSound,
  "CountVoiceNotes",     1,   PlayCountVoiceNotes,
  "GetThresholdEvent",   0,   PlayGetThresholdEvent,
  "GetThresholdStatus",  0,   PlayGetThresholdStatus,
  "OpenSound",           0,   PlayOpenSound,
  "SetSoundNoise",       2,   PlaySetSoundNoise,
  "SetVoiceAccent",      5,   PlaySetVoiceAccent,
  "SetVoiceEnvelope",    3,   PlaySetVoiceEnvelope,
  "SetVoiceNote",        4,   PlaySetVoiceNote,
  "SetVoiceQueueSize",   2,   PlaySetVoiceQueueSize,
  "SetVoiceSound",       3,   PlaySetVoiceSound,
  "SetVoiceThreshold",   2,   PlaySetVoiceThreshold,
  "StartSound",          0,   PlayStartSound,
  "StopSound",           0,   PlayStopSound,
  "SyncAllVoices",       0,   PlaySyncAllVoices,
  "WaitSoundState",      1,   PlayWaitSoundState,
};

/*********************************************************************/
/* Global functions                                                  */
/*********************************************************************/

/*-------------------------------------------------------------------*/
/* The Play 'button' was hit, try and play contents of edit buffer   */
/*-------------------------------------------------------------------*/
void Play(void)
{
  HANDLE hEditBuffer;               /* handle to editing buffer      */
  PSTR pEditBuffer;                 /* address of the edit buffer    */
  PSTR pscan;
  int i = 0, numchar = 0;
                                    /* Get Edit window text          */
  hEditBuffer = SendMessage(hEditWindow, EM_GETHANDLE, 0, 0L);
  pEditBuffer = LocalLock(hEditBuffer);
  if (pEditBuffer == NULL) {
    PlayError("Could not lock edit buffer");
    return;
  }

  ebuffline[0] = '\0';              /* Break the text buffer up into */
  pscan = pEditBuffer;              /* lines and ship it off to      */
  while (*pscan) {                  /* PlayLine for execution        */
    switch( *pscan ) {
      case '\n':
        ebuffline[i] = '\0';
        if (PlayLine() == FALSE) {
          LocalUnlock(hEditBuffer);
          return;
        }
        i = 0;
        break;
      case '\r':
        break;
      default:
       if (i == MAXLINE-1) {
          ebuffline[i] = '\0';
          PlayError("Line is too long");
          return;
        }
        ebuffline[i++] = *pscan;
        break;
    }
    pscan++;
    numchar++;
  }

  if (i > 0) {                      /* Watch out for any text left   */
    ebuffline[i] = '\0';            /* over if we hit a null before  */
    PlayLine();                     /* finding a \n for the last line*/
  }
  LocalUnlock(hEditBuffer);         /* Release the edit buffer text  */
}

/*-------------------------------------------------------------------*/
/* Try and convert a text string to a note value.                    */
/*-------------------------------------------------------------------*/

BOOL PlayNoteConvert(parm, pvalue)
char *parm;
int *pvalue;
{
  int value;


  if (PlayIntConvert(parm,&value) == TRUE)
    ;
  else if (strlen(parm) == 2 || strlen(parm) == 3) {
    switch( *parm ){
      case 'A':
        value = 10;
        break;
      case 'B':
        value = 12;
        break;
      case 'C':
        value = 1;
        break;
      case 'D':
        value = 3;
        break;
      case 'E':
        value = 5;
        break;
      case 'F':
        value = 6;
        break;
      case 'G':
        value = 8;
        break;
      default:
        return(FALSE);
    }
    if (strlen(parm) == 3) {
      if ( *(parm+1) == '#' || *(parm+1) == '+' ) {
        switch( *parm ){
          case 'A':
          case 'C':
          case 'D':
          case 'F':
          case 'G':
            value++;
            parm++;
            break;
          default:
            return(FALSE);
        }
      }
      else if ( *(parm+1) == '-' ) {
        switch( *parm ){
          case 'A':
          case 'B':
          case 'D':
          case 'E':
          case 'G':
            value--;
            parm++;
            break;
          default:
            return(FALSE);
        }
      }
      else
        return(FALSE);
    }
    parm++;
    if (*parm >= '1' && *parm <= '7')
      value += (*parm - '1') * 12;
    else
      return(FALSE);
  }
  else
    return(FALSE);

  *pvalue = value;
  return(TRUE);
}


/*********************************************************************/
/* Local functions                                                   */
/*********************************************************************/

/*-------------------------------------------------------------------*/
/* Parse a line into its components and try to execute it            */
/*-------------------------------------------------------------------*/

BOOL PlayLine()
{
  int numparms, curparm;
  char *pline = ebuffline;
  int scnt = 0;

  memset(pfunction,'\0',MAXPARMLEN);/* Initialize parm pointer array */
  for (curparm = 0; curparm < MAXPARM; curparm++) {
    parm[curparm] = parms[curparm];
    memset(parm[curparm],'\0',MAXPARMLEN);
  }

  while (*pline) {                  /* Ignore anything after a       */
    if (*pline == ';')              /* terminating semicolon, ';'    */
      *pline = '\0';
    else {
      if (!isspace(*pline))         /* count non-blank characters    */
        scnt++;
      pline++;
    }
  }
  if (scnt == 0)                    /* If zero non-blank characters  */
    return(TRUE);                   /* then skip the empty line      */

  pline = ebuffline;
  if ( PlayGetToken(&pline,pfunction,"(") != '(' ) {
    PlayError("Syntax Error");
    return(FALSE);
  }
  pline++;

  curparm = 0;
  while(*pline && curparm < MAXPARM) {
    switch( PlayGetToken(&pline,parm[curparm],",)") ) {
      case ',':
        pline++;
        curparm++;
        if (curparm == MAXPARM) {
          PlayError("Too many parameters");
          return(FALSE);
        }
        break;

      case ')':
        if (strlen(parm[curparm]) > 0)
          numparms = curparm+1;
        else
          numparms = curparm;
        curparm = MAXPARM;
        break;

      default:
        PlayError("Syntax Error");
        return(FALSE);
    }
  }
  if (*pline == '\0') {
    PlayError("Syntax Error");
    return(FALSE);
  }
  return( PlayFunction(numparms) );
}


/*-------------------------------------------------------------------*/
/* Try and execute a parsed line                                     */
/*-------------------------------------------------------------------*/

BOOL PlayFunction(numparms)
int numparms;
{
  char dmsg[80];
  int i;

  for(i = 0; i < sizeof(wsound)/sizeof(wsound[0]); i++) {
    if (strcmp(wsound[i].fname,pfunction) == 0) {
      if (numparms != wsound[i].parms) {
        strcpy(dmsg,"Wrong number of parameters for ");
        strcat(dmsg,pfunction);
        PlayError(dmsg);
        return(FALSE);
      }
      return( (*wsound[i].func)() );
    }
  }
  strcpy(dmsg,"Unrecognized function ");
  strcat(dmsg,pfunction);
  PlayError(dmsg);
  return(FALSE);
}


/*-------------------------------------------------------------------*/
/* Close the sound device                                            */
/*-------------------------------------------------------------------*/

BOOL PlayCloseSound( void )
{
  CloseSound();
  SndState.open = 0;
  return(TRUE);
}

/*-------------------------------------------------------------------*/
/* Retrieves the count of notes in the specified queue.              */
/*-------------------------------------------------------------------*/

BOOL PlayCountVoiceNotes( void )
{
  if (PlayIntConvert(parm[0],&SndState.voice) == FALSE) {
    PlayError("CountVoiceNotes voice parameter must be numeric");
    return(FALSE);
  }
  CountVoiceNotes(SndState.voice);
  return(TRUE);
}

/*-------------------------------------------------------------------*/
/* Retrieves a flag that identifies a recent threshold event.        */
/*-------------------------------------------------------------------*/

BOOL PlayGetThresholdEvent( void )
{
  GetThresholdEvent();
  return(TRUE);
}

/*-------------------------------------------------------------------*/
/* Retrieves the threshold-event status for each voice.              */
/*-------------------------------------------------------------------*/

BOOL PlayGetThresholdStatus( void )
{
  GetThresholdStatus();
  return(TRUE);
}

/*-------------------------------------------------------------------*/
/* Opens the play device and prevents it from being opened by others.*/
/*-------------------------------------------------------------------*/

BOOL PlayOpenSound( void )
{
  switch (rc = OpenSound()) {
    case S_SERDVNA:
      PlayError("The play device is in use");
      return(FALSE);
    case S_SEROFM:
      PlayError("Out of memory");
      return(FALSE);
    default:
      break;
  }
  SndState.numvoices = rc;
  SndState.open = 1;
  return(TRUE);
}

/*-------------------------------------------------------------------*/
/* Sets the source and duration of a noise.                          */
/*-------------------------------------------------------------------*/

BOOL PlaySetSoundNoise( void )
{
  if (strcmp(parm[0],"S_PERIOD512") == 0)
    SndState.source = S_PERIOD512;
  else if (strcmp(parm[0],"S_PERIOD1024") == 0)
    SndState.source = S_PERIOD1024;
  else if (strcmp(parm[0],"S_PERIOD2048") == 0)
    SndState.source = S_PERIOD2048;
  else if (strcmp(parm[0],"S_PERIODVOICE") == 0)
    SndState.source = S_PERIODVOICE;
  else if (strcmp(parm[0],"S_WHITE512") == 0)
    SndState.source = S_WHITE512;
  else if (strcmp(parm[0],"S_WHITE1024") == 0)
    SndState.source = S_WHITE1024;
  else if (strcmp(parm[0],"S_WHITE2048") == 0)
    SndState.source = S_WHITE2048;
  else if (strcmp(parm[0],"S_WHITEVOICE") == 0)
    SndState.source = S_WHITEVOICE;
  else {
    PlayError("SetSoundNoise source parameter is invalid");
    return(FALSE);
  }
  if (PlayIntConvert(parm[1],&SndState.duration) == FALSE) {
    PlayError("SetSoundNoise duration parameter must be numeric");
    return(FALSE);
  }

  rc = SetSoundNoise(
    SndState.source,
    SndState.duration);

  switch (rc) {
    case S_SERDSR:
      PlayError("SetSoundNoise Error: Invalid Source");
      return(FALSE);
    case 0:
      break;
    default:
      wsprintf(rcmsg,"SetSoundNoise Error: Non zero return (%d)",rc);
      PlayError(rcmsg);
      return(FALSE);
  }
  return(TRUE);
}

/*-------------------------------------------------------------------*/
/* Puts an accent (tempo, volume, mode, and pitch) in a voice queue. */
/*-------------------------------------------------------------------*/

BOOL PlaySetVoiceAccent( void )
{
  if (PlayIntConvert(parm[0],&SndState.voice) == FALSE) {
    PlayError("SetVoiceAccent voice parameter must be numeric");
    return(FALSE);
  }
  if (PlayIntConvert(parm[1],&SndState.tempo) == FALSE) {
    PlayError("SetVoiceAccent tempo parameter must be numeric");
    return(FALSE);
  }
  if (PlayIntConvert(parm[2],&SndState.volume) == FALSE) {
    PlayError("SetVoiceAccent volume parameter must be numeric");
    return(FALSE);
  }
  if (strcmp(parm[3],"S_LEGATO") == 0)
    SndState.mode = S_LEGATO;
  else if (strcmp(parm[3],"S_NORMAL") == 0)
    SndState.mode = S_NORMAL;
  else if (strcmp(parm[3],"S_STACCATO") == 0)
    SndState.mode = S_STACCATO;
  else {
    PlayError("SetVoiceAccent mode parameter choices: S_LEGATO, S_NORMAL, S_STACCATO");
    return(FALSE);
  }
  if (PlayIntConvert(parm[4],&SndState.pitch) == FALSE) {
    PlayError("SetVoiceAccent pitch parameter must be numeric");
    return(FALSE);
  }

  rc = SetVoiceAccent(
    SndState.voice,
    SndState.tempo,
    SndState.volume,
    SndState.mode,
    SndState.pitch);

  switch (rc) {
    case S_SERDMD:
      PlayError("SetVoiceAccent Error: Invalid Mode");
      return(FALSE);
    case S_SERDTP:
      PlayError("SetVoiceAccent Error: Invalid Tempo");
      return(FALSE);
    case S_SERDVL:
      PlayError("SetVoiceAccent Error: Invalid Volume");
      return(FALSE);
    case S_SERQFUL:
      PlayError("SetVoiceAccent Error: Queue full");
      return(FALSE);
    case 0:
      break;
    default:
      wsprintf(rcmsg,"SetVoiceAccent Error: Non zero return (%d)",rc);
      PlayError(rcmsg);
      return(FALSE);
  }
  return(TRUE);
}

/*-------------------------------------------------------------------*/
/* Queues an envelope (wave shape and repeat count) into the         */
/* specified voice queue.                                            */
/*-------------------------------------------------------------------*/

BOOL PlaySetVoiceEnvelope( void )
{
  if (PlayIntConvert(parm[0],&SndState.voice) == FALSE) {
    PlayError("SetVoiceEnvelope voice parameter must be numeric");
    return(FALSE);
  }
  if (PlayIntConvert(parm[1],&SndState.shape) == FALSE) {
    PlayError("SetVoiceEnvelope shape parameter must be numeric");
    return(FALSE);
  }
  if (PlayIntConvert(parm[2],&SndState.repeat) == FALSE) {
    PlayError("SetVoiceEnvelope repeat parameter must be numeric");
    return(FALSE);
  }

  rc = SetVoiceEnvelope(SndState.voice, SndState.shape, SndState.repeat);
  switch (rc) {
    case S_SERDSH:
      PlayError("SetVoiceEnvelope Error: Invalid shape");
      return(FALSE);
    case S_SERQFUL:
      PlayError("SetVoiceEnvelope Error: Queue full");
      return(FALSE);
    case 0:
      break;
    default:
      wsprintf(rcmsg,"SetVoiceEnvelope Error: Non zero return (%d)",rc);
      PlayError(rcmsg);
      return(FALSE);
  }
  return(TRUE);
}


/*-------------------------------------------------------------------*/
/*  Queues a note that has value, length, and cdots qualities        */
/*  into the specified voice queue.                                  */
/*-------------------------------------------------------------------*/

BOOL PlaySetVoiceNote( void )
{
  if (PlayIntConvert(parm[0],&SndState.voice) == FALSE) {
    PlayError("SetVoiceNote voice parameter must be numeric");
    return(FALSE);
  }
  if (PlayNoteConvert(parm[1],&SndState.value) == FALSE ) {
    PlayError("SetVoiceNote value parameter is invalid");
    return(FALSE);
  }
  if (PlayIntConvert(parm[2],&SndState.length) == FALSE) {
    PlayError("SetVoiceNote length parameter must be numeric");
    return(FALSE);
  }
  if (PlayIntConvert(parm[3],&SndState.cdots) == FALSE) {
    PlayError("SetVoiceNote cdots parameter must be numeric");
    return(FALSE);
  }

  rc = SetVoiceNote(
    SndState.voice,
    SndState.value,
    SndState.length,
    SndState.cdots);

  switch (rc) {
    case S_SERDCC:
      PlayError("SetVoiceNote Error: Invalid dot count");
      return(FALSE);
    case S_SERDLN:
      PlayError("SetVoiceNote Error: Invalid note length");
      return(FALSE);
    case S_SERBDNT:
      PlayError("SetVoiceNote Error: Invalid note");
      return(FALSE);
    case S_SERQFUL:
      PlayError("SetVoiceNote Error: Queue full");
      return(FALSE);
    case 0:
      break;
    default:
      wsprintf(rcmsg,"SetVoiceNote Error: Non zero return (%d)",rc);
      PlayError(rcmsg);
      return(FALSE);
  }
  return(TRUE);
}


/*-------------------------------------------------------------------*/
/* Sets the size of a voice queue                                    */
/*-------------------------------------------------------------------*/

BOOL PlaySetVoiceQueueSize( void )
{
  if (PlayIntConvert(parm[0],&SndState.voice) == FALSE) {
    PlayError("SetVoiceQueueSize voice parameter must be numeric");
    return(FALSE);
  }
  if (PlayIntConvert(parm[1],&SndState.qsize) == FALSE) {
    PlayError("SetVoiceQueueSize bytes parameter must be numeric");
    return(FALSE);
  }

  rc = SetVoiceQueueSize(
    SndState.voice,
    SndState.qsize);

  switch (rc) {
    case S_SERMACT:
      PlayError("SetVoiceQueueSize Error: Music Active");
      return(FALSE);
    case S_SEROFM:
      PlayError("SetVoiceQueueSize Error: Out of Memory");
      return(FALSE);
    case 0:
      break;
    default:
      wsprintf(rcmsg,"SetVoiceQueueSize Error: Non zero return (%d)",rc);
      PlayError(rcmsg);
      return(FALSE);
  }
  return(TRUE);
}


/*-------------------------------------------------------------------*/
/* Queues a sound frequency and duration into the                    */
/* specified voice queue.                                            */
/*-------------------------------------------------------------------*/

BOOL PlaySetVoiceSound( void )
{
  if (PlayIntConvert(parm[0],&SndState.voice) == FALSE) {
    PlayError("SetVoiceSound voice parameter must be numeric");
    return(FALSE);
  }
  if (PlayLongConvert(parm[1],&SndState.lfreq) == FALSE) {
    PlayError("SetVoiceSound frequency parameter must be numeric");
    return(FALSE);
  }
  else {
    SndState.frequency = (int)(SndState.lfreq >> 16);
    SndState.fraction = (int)(SndState.lfreq & 0xff);
  }
  if (PlayIntConvert(parm[2],&SndState.duration) == FALSE) {
    PlayError("SetVoiceSound duration parameter must be numeric");
    return(FALSE);
  }

  rc = SetVoiceSound(SndState.voice, SndState.lfreq, SndState.duration);
  switch (rc) {
    case S_SERDDR:
      PlayError("SetVoiceSound Error: Invalid duration");
      return(FALSE);
    case S_SERDFQ:
      PlayError("SetVoiceSound Error: Invalid frequency");
      return(FALSE);
    case S_SERQFUL:
      PlayError("SetVoiceSound Error: Queue full");
      return(FALSE);
    case 0:
      break;
    default:
      wsprintf(rcmsg,"SetVoiceSound Error: Non zero return (%d)",rc);
      PlayError(rcmsg);
      return(FALSE);
  }
  return(TRUE);
}


/*-------------------------------------------------------------------*/
/* Sets a voice queues threshold level.                              */
/*-------------------------------------------------------------------*/

BOOL PlaySetVoiceThreshold( void )
{
  if (PlayIntConvert(parm[0],&SndState.voice) == FALSE) {
    PlayError("SetVoiceThreshold voice parameter must be numeric");
    return(FALSE);
  }

  if (PlayIntConvert(parm[1],&SndState.tcount) == FALSE) {
    PlayError("SetVoiceThreshold note count parameter must be numeric");
    return(FALSE);
  }

  if ( (rc = SetVoiceThreshold(SndState.voice, SndState.tcount)) != 0) {
    wsprintf(rcmsg,"SetVoiceThreshold Error: Non zero return (%d)",rc);
    PlayError(rcmsg);
    return(FALSE);
  }
  return(TRUE);
}


/*-------------------------------------------------------------------*/
/* Starts play in each voice queue, non-destructive.                 */
/*-------------------------------------------------------------------*/

BOOL PlayStartSound( void )
{
  StartSound();
  return(TRUE);
}


/*-------------------------------------------------------------------*/
/* Stops playing all voice queues.                                   */
/*-------------------------------------------------------------------*/

BOOL PlayStopSound( void )
{
  StopSound();
  return(TRUE);
}


/*-------------------------------------------------------------------*/
/*  Puts a sync mark into each voice queue.                          */
/*-------------------------------------------------------------------*/

BOOL PlaySyncAllVoices( void )
{
  switch (rc = SyncAllVoices()) {
    case S_SERQFUL:
      PlayError("SyncAllVoices Error: Queue full");
      return(FALSE);
    case 0:
      break;
    default:
      wsprintf(rcmsg,"SyncAllVoices Error: Non zero return (%d)",rc);
      PlayError(rcmsg);
      return(FALSE);
  }
  return(TRUE);
}

/*-------------------------------------------------------------------*/
/*  Waits for the sound device to enter a specified state.           */
/*-------------------------------------------------------------------*/

BOOL PlayWaitSoundState( void )
{
  HANDLE oldCursor;

  if (strcmp(parm[0],"S_ALLTHRESHOLD") == 0)
    SndState.waitstate = S_ALLTHRESHOLD;
  else if (strcmp(parm[0],"S_QUEUEEMPTY") == 0)
    SndState.waitstate = S_QUEUEEMPTY;
  else if (strcmp(parm[0],"S_THRESHOLD") == 0)
    SndState.waitstate = S_THRESHOLD;
  else {
    PlayError("WaitSoundState parameter choices: S_ALLTHRESHOLD, S_QUEUEEMPTY, S_THRESHOLD");
    return(FALSE);
  }

  oldCursor = SetCursor(hNote);
  rc = WaitSoundState(SndState.waitstate);
  SetCursor(oldCursor);
  switch (rc) {
    case S_SERDST:
      PlayError("WaitSoundState Error: Invalid State");
      return(FALSE);
    case 0:
      break;
    default:
      wsprintf(rcmsg,"WaitSoundState Error: Non zero return (%d)",rc);
      PlayError(rcmsg);
      return(FALSE);
  }
  return(TRUE);
}

/*-------------------------------------------------------------------*/
/* Parse a token from an input line given a set of terminators.      */
/*-------------------------------------------------------------------*/

int PlayGetToken(alptr, tptr, endchars)
char **alptr;
char * tptr;
char * endchars;
{
  int curchar = 0;
  enum {PREWHITE, INTOKEN, POSTWHITE};
  int state = PREWHITE;
  char *lptr = *alptr;
  char tmsg[80];

  while ( *lptr && strchr(endchars, *lptr) == NULL ) {
    switch (state) {
      case PREWHITE:
        if ( !isspace(*lptr) )
          state = INTOKEN;
        else
          lptr++;
        break;

      case INTOKEN:
        if (!isspace(*lptr)) {
          if (curchar >= MAXPARMLEN-1)
            return('\0');
          *(tptr + curchar) = *lptr;
          curchar++;
        }
        else
          state = POSTWHITE;
        lptr++;
        break;

      case POSTWHITE:
      default:
        if (!isspace(*lptr))
          return('\0');
        lptr++;
        break;
    }
  }
  *alptr = lptr;
  return(*lptr);
}


/*-------------------------------------------------------------------*/
/* Try and convert a character string into a numeric integer.        */
/*-------------------------------------------------------------------*/

BOOL PlayIntConvert(parm, iptr)
char *parm;
int *iptr;
{
  char *s;
  int i = 0;
  char c;

  if (strspn(parm,"0123456789") == strlen(parm) )
    i = atoi(parm);
  else if (strlen(parm) > 2 && strnicmp(parm,"0x",2) == 0) {
    for(s = parm+2; *s; s++) {
      switch (c = toupper(*s)) {
        case 'A':
        case 'B':
        case 'C':
        case 'D':
        case 'E':
        case 'F':
          i = (i * 0x10) + (c - 'A' + 0xA);
          break;
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
          i = (i * 0x10) + (c - '0');
          break;
        default:
          return(FALSE);
      }
    }
  }
  else
    return(FALSE);

  *iptr = i;
  return(TRUE);
}


/*-------------------------------------------------------------------*/
/* Try and convert a character string into a numeric long value.     */
/*-------------------------------------------------------------------*/

BOOL PlayLongConvert(parm, iptr)
char *parm;
long *iptr;
{
  char *s;
  long i = 0;
  char c;

  if (strspn(parm,"0123456789") == strlen(parm) )
    i = atol(parm);
  else if (strlen(parm) > 2 && strnicmp(parm,"0x",2) == 0) {
    for(s = parm+2; *s; s++) {
      switch (c = toupper(*s)) {
        case 'A':
        case 'B':
        case 'C':
        case 'D':
        case 'E':
        case 'F':
          i = (i * 0x10) + (c - 'A' + 0xA);
          break;
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
          i = (i * 0x10) + (c - '0');
          break;
        default:
          return(FALSE);
      }
    }
  }
  else
    return(FALSE);

  *iptr = i;
  return(TRUE);
}


/*-------------------------------------------------------------------*/
/* Display an error message                                          */
/*-------------------------------------------------------------------*/

void PlayError(char *Text)
{
  MessageBox(
    GetFocus(),
      (LPSTR)ebuffline,
      (LPSTR)Text,
      MB_OK);
}

