/*         ______   ___    ___ 
 *        /\  _  \ /\_ \  /\_ \ 
 *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___ 
 *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
 *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
 *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
 *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
 *                                           /\____/
 *                                           \_/__/
 *      By Shawn Hargreaves,
 *      1 Salisbury Road,
 *      Market Drayton,
 *      Shropshire,
 *      England, TF9 1AJ.
 *
 *      Digitized sample driver for the MIDI player.
 *
 *      Based on code by Tom Novelli.
 *
 *      See readme.txt for copyright information.
 */


#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <dos.h>
#include <limits.h>
#include <math.h>
#include <dir.h>

#include "allegro.h"
#include "internal.h"


/* external interface to the Digmid driver */
static int digmid_detect();
static int digmid_init(int voices);
static void digmid_exit();
static int digmid_load_patches(char *patches, char *drums);
static void digmid_key_on(int inst, int note, int bend, int vol, int pan);
static void digmid_key_off(int voice);
static void digmid_set_volume(int voice, int vol);
static void digmid_set_pitch(int voice, int note, int bend);


MIDI_DRIVER midi_digmid =
{
   "DIGMID",
   "Software wavetable synth",
   0, 0, -1, 24, -1, -1,
   digmid_detect,
   digmid_init,
   digmid_exit,
   NULL,
   NULL,
   digmid_load_patches,
   _dummy_adjust_patches,
   digmid_key_on,
   digmid_key_off,
   digmid_set_volume,
   digmid_set_pitch,
   _dummy_noop2,
   _dummy_noop2
};


#define MAX_LAYERS   64


typedef struct PATCH_EXTRA          /* additional data for a Gravis PATCH */
{
   int low_note; 
   int high_note;
   int base_note;
   int play_mode; 
   int decay_time;
   int release_time;
   int sustain_level;
   int scale_freq;
   int scale_factor; 
   int pan;
} PATCH_EXTRA;


typedef struct PATCH                /* Gravis PATCH structure */
{
   int samples;                     /* number of samples in this instrument */
   SAMPLE *sample[MAX_LAYERS];      /* the waveform data */
   PATCH_EXTRA *extra[MAX_LAYERS];  /* additional waveform information */
   int master_vol;                  /* overall volume level */
} PATCH;


/* our instruments */
static PATCH *patch[256];


/* frequency table (generated by digmid_init) */
static float ftbl[130]; 


/* stored information about active voices */
typedef struct DIGMID_VOICE
{
   SAMPLE *s;
   PATCH_EXTRA *e;
   int inst;
   int vol;
} DIGMID_VOICE;


static DIGMID_VOICE digmid_voice[MIDI_VOICES];



/* destroy_patch:
 *  Frees a PATCH struct and all samples it contains.
 */
static void destroy_patch(PATCH *pat)
{
   int i;

   if (pat) {
      for (i=0; i < pat->samples; i++) {
	 destroy_sample(pat->sample[i]);

	 _unlock_dpmi_data(pat->extra[i], sizeof(PATCH_EXTRA));
	 free(pat->extra[i]);
      }

      _unlock_dpmi_data(pat, sizeof(PATCH));
      free(pat);
   }
}



/* load_patch:
 *  Reads a GUS format patch file from disk.
 */
static PATCH *load_patch(PACKFILE *f, int drum)
{
   PATCH *p = NULL;
   char buf[256];
   char mode;
   int env_rate[6];
   int env_offset[6];
   int i, j;
   int diff;
   int odd_len;

   pack_fread(buf, 22, f);                         /* ID tag */
   if (memcmp(buf, "GF1PATCH110\0", 12) || memcmp(buf+12, "ID#000002\0", 10))
      goto getout;

   p = malloc(sizeof(PATCH));
   if (!p) {
      errno = ENOMEM;
      goto getout;
   }

   pack_fread(buf, 65, f);                         /* description */
   p->master_vol = pack_igetw(f);                  /* volume */

   pack_fread(buf, 109, f);                        /* skip */

   p->samples = pack_getc(f);                      /* number of samples */
   pack_fread(buf, 40, f);                         /* skip */

   if (p->samples > MAX_LAYERS)
      p->samples = MAX_LAYERS;

   for (i=0; i<p->samples; i++) {                  /* for each sample... */
      p->sample[i] = malloc(sizeof(SAMPLE));
      if (!p->sample[i]) {
	 p->samples = i;
	 destroy_patch(p);
	 p = NULL;
	 goto getout;
      }

      p->extra[i] = malloc(sizeof(PATCH_EXTRA));
      if (!p->extra[i])  {
	 free(p->sample[i]);
	 p->samples = i;
	 destroy_patch(p);
	 p = NULL;
	 goto getout;
      }

      pack_fread(buf, 8, f);                       /* layer name */

      p->sample[i]->len = pack_igetl(f);           /* sample length */
      p->sample[i]->loop_start = pack_igetl(f);    /* loop start */
      p->sample[i]->loop_end = pack_igetl(f);      /* loop end */
      p->sample[i]->freq = pack_igetw(f);          /* sample frequency */

      p->extra[i]->low_note = pack_igetl(f);       /* key min */
      p->extra[i]->high_note = pack_igetl(f);      /* key max */
      p->extra[i]->base_note = pack_igetl(f);      /* base key */
      pack_igetw(f);                               /* skip finetune */

      p->extra[i]->pan = pack_getc(f) * 255/15;    /* pan position */

      for (j=0; j<6; j++)                          /* envelope rate */
	 env_rate[j] = pack_getc(f);

      for (j=0; j<6; j++)                          /* envelope value */
	 env_offset[j] = pack_getc(f);

      pack_fread(buf, 6, f);                       /* skip trem and vib */

      mode = pack_getc(f);                         /* sample flags */

      p->sample[i]->bits = (mode & 1) ? 16 : 8;    /* how many bits? */

      p->extra[i]->play_mode = 0;                  /* sort out loop flags */

      if (mode & 4)
	 p->extra[i]->play_mode |= PLAYMODE_LOOP;

      if (mode & 8) 
	 p->extra[i]->play_mode |= (PLAYMODE_BIDIR | PLAYMODE_LOOP);

      if (mode & 16) 
	 p->extra[i]->play_mode |= (PLAYMODE_BACKWARD | PLAYMODE_LOOP);

      /* convert envelope rates (GUS uses a 2.6 floating point format) */
      for (j=0; j<6; j++) {
	 static int vexp[4] = { 1, 8, 64, 512 };
	 int e = (env_rate[j] >> 6);
	 int m = (env_rate[j] & 0x3F);
	 env_rate[j] = ((65536 * vexp[e] / ((m) ? m : 1)) >> 12);
      }

      if ((mode & 32) && (!drum)) {
	 /* sustained volume envelope */
	 p->extra[i]->sustain_level = env_offset[2];
	 p->extra[i]->decay_time = 0;

	 diff = env_offset[0];
	 p->extra[i]->decay_time += env_rate[0] * diff / 256;

	 diff = ABS(env_offset[1] - env_offset[0]);
	 p->extra[i]->decay_time += env_rate[1] * diff / 256;

	 diff = ABS(env_offset[2] - env_offset[1]);
	 p->extra[i]->decay_time += env_rate[2] * diff / 256;

	 j = 3;
      }
      else {
	 /* one-shot volume envelope */
	 p->extra[i]->decay_time = 0;
	 p->extra[i]->sustain_level = 0;

	 for (j=0; j<6; j++) {
	    diff = ABS(env_offset[j] - ((j) ? env_offset[j-1] : 0));
	    p->extra[i]->decay_time += env_rate[j] * diff / 256;
	    if (env_offset[j] < 16) {
	       j++;
	       break;
	    }
	 }
      }

      /* measure release time */
      p->extra[i]->release_time = 0;

      while (j < 6) {
	 diff = ABS(env_offset[j] - env_offset[j-1]);
	 p->extra[i]->release_time += env_rate[j] * diff / 256;
	 if (env_offset[j] < 16)
	    break;
	 j++;
      }

      /* clamp very large/small sustain levels to zero or maximum */
      if (p->extra[i]->sustain_level < 16)
	 p->extra[i]->sustain_level = 0;
      else if (p->extra[i]->sustain_level > 192)
	 p->extra[i]->sustain_level = 255;

      if (p->extra[i]->release_time < 10)
	 p->extra[i]->release_time = 0;

      if ((p->extra[i]->sustain_level == 0) && 
	  (p->extra[i]->decay_time == 0)) {
	 p->extra[i]->sustain_level = 255;
	 p->extra[i]->play_mode &= ~PLAYMODE_LOOP;
      }

      p->extra[i]->scale_freq = pack_igetw(f);     /* scale values */
      p->extra[i]->scale_factor = pack_igetw(f);

      pack_fread(buf, 36, f);                      /* skip reserved */

      if (p->sample[i]->bits == 16) {              /* adjust 16 bit loops */
	 odd_len = (p->sample[i]->len & 1);
	 p->sample[i]->len /= 2;
	 p->sample[i]->loop_start /= 2;
	 p->sample[i]->loop_end /= 2;
      }
      else
	 odd_len = FALSE;

      p->sample[i]->priority = 255;                /* set some defaults */
      p->sample[i]->param = -1;

      p->sample[i]->data = malloc(p->sample[i]->len);
      if (!p->sample[i]->data) {
	 free(p->sample[i]);
	 free(p->extra[i]);
	 p->samples = i;
	 destroy_patch(p);
	 p = NULL;
	 goto getout;
      }

      if (p->sample[i]->bits == 8) {
	 /* read 8 bit sample data */
	 pack_fread(p->sample[i]->data, p->sample[i]->len, f);
	 if (!(mode & 2)) {
	    /* signed data - convert to unsigned */
	    for (j=0; j<(int)p->sample[i]->len; j++)
	       ((unsigned char *)p->sample[i]->data)[j] ^= 0x80;
	 }
      }
      else {
	 /* reduce 16 bits to 8 bit waveform */
	 for (j=0; j < (int)p->sample[i]->len; j++) {
	    ((unsigned char *)p->sample[i]->data)[j] = pack_igetw(f) >> 8;
	 }
	 if (!(mode & 2)) {
	    /* signed data - convert to unsigned */
	    for (j=0; j<(int)p->sample[i]->len; j++)
	       ((unsigned char *)p->sample[i]->data)[j] ^= 0x80;
	 }
	 p->sample[i]->bits = 8;
	 if (odd_len)
	    pack_getc(f);
      }
   }

   getout:

   /* lock the data into physical memory */
   if (p) {
      _go32_dpmi_lock_data(p, sizeof(PATCH));

      for (i=0; i<p->samples; i++) {
	 lock_sample(p->sample[i]);
	 _go32_dpmi_lock_data(p->extra[i], sizeof(PATCH_EXTRA));
      }
   }

   return p;
}



/* try_patch_location:
 *  Looks for a GUS patch set in the specified location, storing the dir
 *  and config file name if found.
 */
static int try_patch_location(char *where, char *dir, char *file)
{
   /* could it be a direct .cfg file reference? */
   if (stricmp(get_extension(where), "cfg") == 0) {
      if (!file_exists(where, FA_RDONLY | FA_ARCH, NULL))
	 return FALSE;
      strcpy(dir, where);
      *get_filename(dir) = 0;
      strcpy(file, get_filename(where));
      return TRUE;
   }

   /* could it be a direct .dat file reference? */
   if (stricmp(get_extension(where), "dat") == 0) {
      strcpy(dir, where);
      strcat(dir, "#default_cfg");
      if (!file_exists(dir, FA_RDONLY | FA_ARCH, NULL))
	 return FALSE;
      strcpy(dir, where);
      strcat(dir, "#");
      strcpy(file, "default_cfg");
      return TRUE;
   }

   /* could it be a directory containing default.cfg? */
   strcpy(dir, where);
   put_backslash(dir);
   strcat(dir, "default.cfg");
   if (file_exists(dir, FA_RDONLY | FA_ARCH, NULL)) {
      strcpy(dir, where);
      put_backslash(dir);
      strcpy(file, "default.cfg");
      return TRUE;
   }

   /* could it be a directory containing patches.dat? */
   strcpy(dir, where);
   put_backslash(dir);
   strcat(dir, "patches.dat#default_cfg");
   if (file_exists(dir, FA_RDONLY | FA_ARCH, NULL)) {
      strcpy(dir, where);
      put_backslash(dir);
      strcat(dir, "patches.dat#");
      strcpy(file, "default_cfg");
      return TRUE;
   }

   /* could it be the root directory of midi/default.cfg? */
   strcpy(dir, where);
   put_backslash(dir);
   strcat(dir, "midi/default.cfg");
   if (file_exists(dir, FA_RDONLY | FA_ARCH, NULL)) {
      strcpy(dir, where);
      put_backslash(dir);
      strcat(dir, "midi/");
      strcpy(file, "default.cfg");
      return TRUE;
   }

   return FALSE;
}



/* _digmid_find_patches:
 *  Tries to locate the GUS patch set directory and index file (default.cfg).
 */
int _digmid_find_patches(char *dir, char *file)
{
   char *name = get_config_string("sound", "patches", NULL);
   char path[256], *s;

   /* look where the config file says */
   if ((name) && (*name))
      return try_patch_location(name, dir, file);

   /* look in the same directory as the program */
   strcpy(path, __crt0_argv[0]);
   *get_filename(path) = 0;
   put_backslash(path);
   if (try_patch_location(path, dir, file))
      return TRUE;

   /* try the ALLEGRO environment variable */
   s = getenv("ALLEGRO");
   if (s) {
      strcpy(path, s);
      put_backslash(path);
      if (try_patch_location(path, dir, file))
	 return TRUE;
   }

   /* try the ULTRASND environment variable */
   s = getenv("ULTRASND");
   if (s) {
      strcpy(path, s);
      put_backslash(path);
      if (try_patch_location(path, dir, file))
	 return TRUE;
   }

   return FALSE;
}



/* parse_string:
 *  Splits a string into component parts, storing them in the argv pointers.
 *  Returns the number of components.
 */
static int parse_string(char *buf, char *argv[])
{
   int c = 0;

   while ((*buf) && (c<16)) {
      while ((*buf == ' ') || (*buf == '\t') || (*buf == '='))
	 buf++;

      if (*buf == '#')
	 return c;

      if (*buf)
	 argv[c++] = buf;

      while ((*buf) && (*buf != ' ') && (*buf != '\t') && (*buf != '='))
	 buf++;

      if (*buf) {
	 *buf = 0;
	 buf++;
      }
   }

   return c;
}



/* digmid_load_patches:
 *  Reads the patches that are required by a particular song.
 */
static int digmid_load_patches(char *patches, char *drums)
{
   PACKFILE *f;
   char dir[256], file[256], buf[256], filename[256];
   char todo[256][16];
   char *argv[16];
   int argc;
   int patchnum, flag_num;
   int drum_mode = FALSE;
   int override_mode = FALSE;
   int drum_start = 0;
   int type, size;
   int i, j;

   if (!_digmid_find_patches(dir, file))
      return -1;

   for (i=0; i<256; i++)
      todo[i][0] = 0;

   strcpy(buf, dir);
   strcat(buf, file);
   f = pack_fopen(buf, F_READ);
   if (!f)
      return -1;

   while (pack_fgets(buf, 255, f) != 0) {
      argc = parse_string(buf, argv);

      if (argc > 0) {
	 /* is first word all digits? */
	 flag_num = TRUE;
	 for (i=0; i<(int)strlen(argv[0]); i++) {
	    if ((!isdigit(argv[0][i])) && (argv[0][i] != '-')) {
	       flag_num = FALSE;
	       break;
	    }
	 }

	 if ((flag_num) && (argc >= 2)) {
	    if (stricmp(argv[1], "begin_multipatch") == 0) {
	       /* start the block of percussion instruments */
	       drum_start = atoi(argv[0])-1;
	       drum_mode = TRUE;
	    }
	    else if (stricmp(argv[1], "override_patch") == 0) {
	       /* ignore patch overrides */
	       override_mode = TRUE;
	    }
	    else if (!override_mode) {
	       /* must be a patch number */
	       patchnum = atoi(argv[0]);

	       if (!drum_mode)
		  patchnum--;

	       if ((patchnum >= 0) && (patchnum < 128) &&
		   (((drum_mode) && (drums[patchnum])) ||
		    ((!drum_mode) && (patches[patchnum])))) {

		  if (drum_mode)
		     patchnum += drum_start;

		  if (!patch[patchnum]) {
		     /* need to load this sample */
		     strncpy(todo[patchnum], argv[1], 15);
		     todo[patchnum][15] = 0;
		  }
	       }
	    }
	 }
	 else {
	    /* handle other keywords */
	    if (stricmp(argv[0], "end_multipatch") == 0) {
	       drum_mode = FALSE;
	       override_mode = FALSE;
	    }
	 }
      }
   }

   pack_fclose(f);

   if ((dir[0]) && (dir[strlen(dir)-1] == '#')) {
      /* read from a datafile */
      dir[strlen(dir)-1] = 0;
      f = pack_fopen(dir, F_READ_PACKED);
      if (!f)
	 return -1;

      type = pack_mgetl(f);
      if (type != DAT_MAGIC) {
	 pack_fclose(f);
	 return -1;
      }

      pack_mgetl(f);

      filename[0] = 0;

      /* scan through the file */
      while (!pack_feof(f)) {
	 type = pack_mgetl(f);

	 if (type == DAT_PROPERTY) {
	    type = pack_mgetl(f);
	    size = pack_mgetl(f);
	    if (type == DAT_ID('N','A','M','E')) {
	       /* store name property */
	       pack_fread(filename, size, f);
	       filename[size] = 0;
	    }
	    else {
	       /* skip other properties */
	       pack_fseek(f, size);
	    }
	 }
	 else if (type == DAT_PATCH) {
	    /* do we want this patch? */
	    for (i=0; i<256; i++)
	       if ((todo[i][0]) && (stricmp(filename, todo[i]) == 0))
		  break;

	    if (i < 256) {
	       /* load this patch */
	       f = pack_fopen_chunk(f, FALSE);
	       patch[i] = load_patch(f, (i >= 128));
	       f = pack_fclose_chunk(f);
	       for (j=i+1; j<256; j++) {
		  /* share multiple copies of the instrument */
		  if (stricmp(todo[i], todo[j]) == 0) {
		     patch[j] = patch[i];
		     todo[j][0] = 0;
		  }
	       }
	       todo[i][0] = 0;
	    }
	    else {
	       /* skip unwanted patch */
	       size = pack_mgetl(f);
	       pack_fseek(f, size+4);
	    }
	 }
	 else {
	    /* skip unwanted object */
	    size = pack_mgetl(f);
	    pack_fseek(f, size+4);
	 }
      }
   }
   else {
      /* read from regular disk files */
      for (i=0; i<256; i++) {
	 if (todo[i][0]) {
	    strcpy(filename, dir);
	    strcat(filename, todo[i]);
	    if (*get_extension(filename) == 0)
	       strcat(filename, ".pat");
	    f = pack_fopen(filename, F_READ);
	    if (f) {
	       patch[i] = load_patch(f, (i >= 128));
	       pack_fclose(f);
	    }
	    errno = 0;
	    for (j=i+1; j<256; j++) {
	       /* share multiple copies of the instrument */
	       if (stricmp(todo[i], todo[j]) == 0) {
		  patch[j] = patch[i];
		  todo[j][0] = 0;
	       }
	    }
	 }
      }
   }

   return 0;
}



/* digmid_freq:
 *  Helper for converting note numbers to sample frequencies.
 */ 
static inline int digmid_freq(int inst, SAMPLE *s, PATCH_EXTRA *e, int note, int bend)
{
   float freq, f1, f2;
   float sfreq = s->freq;
   float base_note = e->base_note;

   if (inst > 127) { 
      /* drums use a fixed frequency */
      freq = ftbl[inst-128] * sfreq / base_note;
   }
   else {
      /* calculate frequency */
      f1 = ftbl[note] * sfreq / base_note;
      f2 = ftbl[note+1] * sfreq / base_note;

      /* quick pitch bend method - ~.035% error - acceptable? */
      freq = ((f1*(float)(4096-bend)) + (f2*(float)bend)) / 4096.0;
   }

   /* frequency scaling */
   if (e->scale_factor != 1024) {
      f1 = sfreq * e->scale_freq / 60;
      freq -= f1;
      freq = (freq * e->scale_factor) / 1024;
      freq += f1;
   }

   /* lower by an octave if we are going to overflow */
   while (freq >= (1<<19)-1)
      freq /= 2;

   return (int)(freq+0.5);
}



/* digmid_trigger:
 *  Helper for activating a specific sample layer.
 */
static inline void digmid_trigger(int inst, int snum, int note, int bend, int vol, int pan)
{
   int freq, voice;
   DIGMID_VOICE *info;
   PATCH_EXTRA *e;
   SAMPLE *s;

   voice = _midi_allocate_voice(-1, -1);
   if (voice < 0)
      return;

   s = patch[inst]->sample[snum];
   e = patch[inst]->extra[snum];

   freq = digmid_freq(inst, s, e, note, bend);

   if (inst > 127)
      pan = e->pan;

   /* store note information for later use */
   info = &digmid_voice[voice - midi_digmid.basevoice];
   info->s = s;
   info->e = e;
   info->inst = inst;
   info->vol = vol;

   /* play the note */
   reallocate_voice(voice, s);
   voice_set_playmode(voice, e->play_mode);
   voice_set_volume(voice, vol);
   voice_set_frequency(voice, freq);
   voice_set_pan(voice, pan);

   if (e->sustain_level < 255)
      voice_ramp_volume(voice, e->decay_time, e->sustain_level*vol/255);

   voice_start(voice);
}



/* digmid_key_on:
 *  Triggers the specified voice. The instrument is specified as a GM
 *  patch number, pitch as a midi note number, and volume from 0-127.
 *  The bend parameter is _not_ expressed as a midi pitch bend value.
 *  It ranges from 0 (no pitch change) to 0xFFF (almost a semitone sharp).
 *  Drum sounds are indicated by passing an instrument number greater than
 *  128, in which case the sound is GM percussion key #(inst-128).
 */
static void digmid_key_on(int inst, int note, int bend, int vol, int pan)
{
   PATCH_EXTRA *e;
   float freq;
   int best, best_diff;
   int diff;
   int i, c;

   /* quit if instrument is not available */
   if ((!patch[inst]) || (patch[inst]->samples < 1))
      return;

   /* adjust volume and pan ranges */
   vol *= 2;
   pan *= 2;

   if (patch[inst]->samples == 1) {
      /* only one sample to choose from */
      digmid_trigger(inst, 0, note, bend, vol, pan);
   }
   else {
      /* find the sample(s) with best frequency range */
      best = -1;
      best_diff = INT_MAX;
      c = 0;

      for (i=0; i<patch[inst]->samples; i++) {
	 freq = ftbl[note];
	 e = patch[inst]->extra[i];

	 if ((freq >= e->low_note) && (freq <= e->high_note)) {
	    digmid_trigger(inst, i, note, bend, vol, pan);
	    c++;
	    if (c > 4)
	       break;
	 }
	 else {
	    diff = MIN(ABS(freq - e->low_note), ABS(freq - e->high_note));
	    if (diff < best_diff) {
	       best_diff = diff;
	       best = i;
	    }
	 }
      }

      if ((c <= 0) && (best >= 0))
	 digmid_trigger(inst, best, note, bend, vol, pan);
   }
}

static END_OF_FUNCTION(digmid_key_on);



/* digmid_key_off:
 *  Hey, guess what this does :-)
 */
static void digmid_key_off(int voice)
{
   DIGMID_VOICE *info = &digmid_voice[voice - midi_digmid.basevoice];

   if (info->inst > 127) 
      return;

   if (info->e->release_time > 0)
      voice_ramp_volume(voice, info->e->release_time, 0);
   else
      voice_stop(voice);
}

static END_OF_FUNCTION(digmid_key_off);



/* digmid_set_volume:
 *  Sets the volume of the specified voice (vol range 0-127).
 */
static void digmid_set_volume(int voice, int vol)
{
   DIGMID_VOICE *info = &digmid_voice[voice - midi_digmid.basevoice];
   int v;

   if (info->inst > 127) 
      return;

   vol *= 2;

   if (info->e->sustain_level < 255) {
      /* adjust for volume ramping */
      int current = voice_get_volume(voice);
      int target = info->e->sustain_level*info->vol/255;
      int start = info->vol;

      if (ABS(current - target) < 8) {
	 /* ramp has finished */
	 voice_set_volume(voice, vol*info->e->sustain_level/255);
      }
      else {
	 /* in the middle of a ramp */
	 int mu;

	 if (start > target)
	    mu = MID(0, (current-target) * 256 / (start-target), 256);
	 else
	    mu = 0;

	 v = mu+info->e->sustain_level*(256-mu)/256;
	 v = MID(0, vol*v/255, 255);

	 voice_set_volume(voice, v);
	 voice_ramp_volume(voice, info->e->decay_time*mu/256, info->e->sustain_level*vol/255);
      }
   }
   else {
      /* no ramp */
      voice_set_volume(voice, vol);
   }

   info->vol = vol;
}

static END_OF_FUNCTION(digmid_set_volume);



/* digmid_set_pitch:
 *  Sets the pitch of the specified voice.
 */
static void digmid_set_pitch(int voice, int note, int bend)
{
   DIGMID_VOICE *info = &digmid_voice[voice - midi_digmid.basevoice];
   int freq;

   if (info->inst > 127)
      return;

   freq = digmid_freq(info->inst, info->s, info->e, note, bend);

   voice_set_frequency(voice, freq);
}

static END_OF_FUNCTION(digmid_set_pitch);



/* digmid_detect:
 *  Have we got a sensible looking patch set?
 */
static int digmid_detect()
{
   char dir[256], file[256];

   if (!_digmid_find_patches(dir, file)) {
      strcpy(allegro_error, "DIGMID patch set not found");
      return FALSE;
   }

   return TRUE;
}



/* digmid_init:
 *  Setup the digmid driver.
 */
static int digmid_init(int voices)
{
   int i;

   for (i=0; i<256; i++)
      patch[i] = NULL;

   midi_digmid.voices = voices;

   /* create frequency table */
   ftbl[129] = 14080000;         /* A10 = 14080.000 hz */
   for (i=128; i>=0; i--)
      ftbl[i] = ftbl[i+1] / pow(2.0, 1.0/12.0);

   LOCK_VARIABLE(midi_digmid);
   LOCK_VARIABLE(patch);
   LOCK_VARIABLE(ftbl);
   LOCK_VARIABLE(digmid_voice);
   LOCK_FUNCTION(digmid_key_on);
   LOCK_FUNCTION(digmid_key_off);
   LOCK_FUNCTION(digmid_set_volume);
   LOCK_FUNCTION(digmid_set_pitch);

   return 0;
}



/* digmid_exit:
 *  Cleanup when we are finished.
 */
static void digmid_exit()
{
   int i, j;

   for (i=0; i<256; i++) {
      if (patch[i]) {
	 for (j=i+1; j<256; j++) {
	    if (patch[j] == patch[i])
	       patch[j] = NULL;
	 }
	 destroy_patch(patch[i]);
	 patch[i] = NULL;
      }
   }
}


