/*         ______   ___    ___ 
 *        /\  _  \ /\_ \  /\_ \ 
 *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___ 
 *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
 *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
 *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
 *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
 *                                           /\____/
 *                                           \_/__/
 *      By Shawn Hargreaves,
 *      1 Salisbury Road,
 *      Market Drayton,
 *      Shropshire,
 *      England, TF9 1AJ.
 *
 *      This file written by George Foot.
 *
 *      AWE32/EMU8000 driver for the MIDI player.
 *
 *      See readme.txt for copyright information.
 *
 *      Modified by J. Flynn 3/7/98 to remove floating log calculations
 *      during interrupt - replaced by precomputed lookup table.
 */


#ifndef DJGPP
#error This file should only be used by the djgpp version of Allegro
#endif

#include <stdlib.h>
#include <stdio.h>
#include <math.h>

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

#include "emu8k.h"

/* Variables from awedata.c, containing the envelope data from synthgm.sf2 */
extern short int _awe_sf_defaults[];
extern       int _awe_sf_num_presets;
extern short int _awe_sf_presets[];
extern short int _awe_sf_splits[];
extern short int _awe_sf_gens[];
extern       int _awe_sf_sample_data[];

/* external interface to the AWE32 driver */
static int  awe32_detect();
static int  awe32_init(int voices);
static void awe32_exit();
static void awe32_key_on(int inst, int note, int bend, int vol, int pan);
static void awe32_key_off(int voice);
static void _awe32_do_note(int inst, int note, int bend, int vol, int pan);
static void awe32_set_volume (int voice, int vol);
static void awe32_set_pitch (int voice, int note, int bend);
static void translate_soundfont_into_something_useful();
static void destroy_useful_version_of_soundfont();

static struct midi_preset_t {   /* struct to hold envelope data for each preset */
 int num_splits;                /* number of splits in this preset */
 struct envparms_t **split;     /* array of num_splits pointers to envelope data */
} *midi_preset;                 /* global variable to hold the data */

static struct envparms_t **voice_envelope;      /* array of pointers pointing at the envelope playing on each voice */
static int *exclusive_class_info;               /* exclusive class information */

static char awe32_desc[256] = "not initialised";

MIDI_DRIVER midi_awe32 = {
   "AWE32/EMU8000",             /* name */
   awe32_desc,                  /* desc */
   32, 0, 32, 32, -1, -1,       /* voices, basevoice, max_voices, def_voices, xmin, xmax */
   awe32_detect,                /* detect */
   awe32_init,                  /* init */
   awe32_exit,                  /* exit */
   NULL,                        /* mixer_volume */
   NULL,                        /* raw_midi */
   _dummy_load_patches,         /* load_patches */
   _dummy_adjust_patches,       /* adjust_patches */
   awe32_key_on,                /* key_on */
   awe32_key_off,               /* key_off */
   awe32_set_volume,            /* set_volume */
   awe32_set_pitch,             /* set_pitch */
   _dummy_noop2,                /* set_pan */
   _dummy_noop2                 /* set_vibrato */
};

//jff 3/7/98 added to avoid floating point usage in interrupt
static const unsigned char attentbl[]=
{
  255,255,221,199,184,172,162,154,147,141,135,130,125,121,117,113,
  110,107,104,101, 98, 95, 93, 91, 88, 86, 84, 82, 80, 78, 76, 75,
   73, 71, 70, 68, 67, 65, 64, 62, 61, 60, 59, 57, 56, 55, 54, 53,
   51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 39, 38, 37,
   36, 35, 34, 34, 33, 32, 31, 31, 30, 29, 28, 28, 27, 26, 25, 25,
   24, 23, 23, 22, 22, 21, 20, 20, 19, 18, 18, 17, 17, 16, 16, 15,
   14, 14, 13, 13, 12, 12, 11, 11, 10, 10,  9,  9,  8,  8,  7,  7,
    6,  6,  5,  5,  4,  4,  3,  3,  3,  2,  2,  1,  1,  0,  0,  0,
};
//jff 3/7/98 end addition

/* awe32_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 awe32_key_on(int inst, int note, int bend, int vol, int pan) {
 if (inst > 127) {                              /* drum sound? */
  _awe32_do_note (128,inst-128,bend,vol,pan);
 } else {                                       /* regular instrument */
  _awe32_do_note (inst,note,bend,vol,pan);
 }
}
static END_OF_FUNCTION(awe32_key_on);



/* _awe32_do_note:
 *  Actually plays the note as described above; the above function just remaps
 *  the drums.
 */
static void _awe32_do_note(int inst, int note, int bend, int vol, int pan) {
 int voice;
 int i;
 envparms_t *env;
 int key,vel;
 int atten;
 int pan_pos;

 /* EMU8000 pan is back-to-front and twice the scale */
 pan = 0x100-2*pan;
 if (pan>0xff) pan = 0xff;
 if (pan<0x00) pan = 0x00;

 for (i=0;i<midi_preset[inst].num_splits;i++) {
  /* envelope for this split */
  env=midi_preset[inst].split[i];

  /* should we play this split? */
  if ((note>=env->minkey)&&(note<=env->maxkey)&&( vol>=env->minvel)&&( vol<=env->maxvel)) {

   /* get a voice (any voice) to play it on */
   voice = _midi_allocate_voice (-1,-1);

   /* did we get one? */
   if (voice>=0) {

    /* set the current envelope for this voice */
    voice_envelope[voice]=env;

    /* set pitch and velocity */
    key = note*0x1000+bend;
    vel = vol;

    /* override key and velocity if envelope says so */
    if ((env->key>=0)&&(env->key<=127)) key=env->key*0x1000;
    if ((env->vel>=0)&&(env->vel<=127)) vel=env->vel;

    /* check key and velocity numbers are within range */
    if (key>0x7ffff) key=0x7ffff;
    if (key<0) key=0;
    if (vel>127) vel=127;
    if (vel<0) vel=0;

    /* add one-off information to the envelope (these have no side-effects on the other voices using this envelope) */
    env->ip=env->ipbase+(env->ipscale*key)/1200;

    /* remap MIDI velocity to attenuation */
    if (vel)
		 //jff 3/7/98 was: env->atten + (-20/0.375*log(vel/127.0));
     atten = env->atten + attentbl[vel]; 
    else
     atten = 0xff;
    if (atten<0x00) atten = 0x00;
    if (atten>0xff) atten = 0xff;

    /* update it in the envelope */
    env->ifatn=env->filter+atten;

    /* modify pan with envelope's built-in pan */
    if (pan<0x80) {
     pan_pos = (pan*env->pan)/0x80;
    } else {
     pan_pos = env->pan + (pan-0x80)*(256-env->pan)/0x80;
    }
    if (pan_pos<0x00) pan_pos=0x00;
    if (pan_pos>0xff) pan_pos=0xff;

    /* update pan in the envelope */
    env->psst = (pan_pos<<24) + env->loopst;

    /* test exclusive class */
    exclusive_class_info[voice] = (inst << 8) + env->exc;

    if (env->exc) {
     int chan;
     for (chan = 0; chan < 32; chan++)
      if ((chan != voice) && (exclusive_class_info[chan] == exclusive_class_info[voice]))
       emu8k_terminatesound (chan);
    }

    /* start the note playing */
    emu8k_startsound(voice,env);

   }
  }
 }
}
static END_OF_FUNCTION(_awe32_do_note);



/* awe32_set_*:
 *  Modulation routines
 */
static void awe32_set_volume (int voice, int vol) {
 int atten;
 struct envparms_t *env;

 /* get envelope in use on this voice */
 env = voice_envelope[voice];

 /* allow envelope to override new volume */
 if ((env->vel>=0)&&(env->vel<=127)) vol=env->vel;

 /* check velocity number is within range */
 if (vol>127) vol=127;
 if (vol<0) vol=0;

 /* remap MIDI velocity to attenuation */
 if (vol)
	//jff 3/7/98  was: env->atten + (-20/0.375*log(vol/127.0));
  atten = env->atten + attentbl[vol]; 
 else
  atten = 0xff;
 if (atten<0x00) atten = 0x00;
 if (atten>0xff) atten = 0xff;

 emu8k_modulate_atten(voice,atten);
}
static END_OF_FUNCTION(awe32_set_volume);



static void awe32_set_pitch (int voice, int note, int bend) {
 struct envparms_t *env;
 int key,ip;

 /* get envelope in use on this voice */
 env = voice_envelope[voice];

 key = note*0x1000+bend;

 /* override key if envelope says so */
 if ((env->key>=0)&&(env->key<=127)) key=env->key*0x1000;

 /* check key number is within range */
 if (key>0x7ffff) key=0x7ffff;
 if (key<0) key=0;

 ip=env->ipbase+(env->ipscale*key)/1200;

 emu8k_modulate_ip(voice,ip);
}
static END_OF_FUNCTION (awe32_set_pitch);



/* awe32_key_off:
 *  Hey, guess what this does :-)
 */
static void awe32_key_off(int voice) {
 emu8k_releasesound (voice,voice_envelope[voice]);
}
static END_OF_FUNCTION(awe32_key_off);



/* awe32_detect:
 *  AWE32/EMU8000 detection routine.
 */
static int awe32_detect() {
 if (emu8k_detect()) {
  sprintf (awe32_desc,"SB AWE32/compatible on port 0x%04x",_emu8k_baseport);
  return TRUE;
 } else {
  sprintf(allegro_error, "AWE32 not detected");
  return FALSE;
 }
}



/* awe32_lockmem:
 *  Locks required memory blocks
 */
static void awe32_lockmem() {

/* Functions */
 LOCK_FUNCTION(awe32_key_on);
 LOCK_FUNCTION(awe32_key_off);
 LOCK_FUNCTION(_awe32_do_note);
 LOCK_FUNCTION(awe32_set_volume);
 LOCK_FUNCTION(awe32_set_pitch);

/* Data */
 /* Most data is locked on allocation */
 LOCK_VARIABLE(midi_preset);
 LOCK_VARIABLE(voice_envelope);
 LOCK_VARIABLE(exclusive_class_info);
 LOCK_VARIABLE(midi_awe32);
 //jff 3/7/98 added attentbl to avoid floating point in awe32, must be locked
 LOCK_VARIABLE(attentbl); 

/* Stuff in emu8k.c */
 emu8k_lock();

}



/* awe32_init:
 *  Setup the AWE32/EMU8000 driver.
 */
static int awe32_init(int voices) {
 int chan;

 emu8k_init();
 translate_soundfont_into_something_useful();
 voice_envelope = (struct envparms_t **) _lock_malloc (32 * sizeof(struct envparms_t *));
 exclusive_class_info = (int *) _lock_malloc (32 * sizeof (int));
 awe32_lockmem();

 for (chan = 0; chan < 32; chan++) {
  voice_envelope[chan] = NULL;
  exclusive_class_info[chan] = 0;
 }

 return 0;
}



/* awe32_exit:
 *  Cleanup when we are finished.
 */
static void awe32_exit() {
 int i;
 for (i=0;i<_emu8k_numchannels;i++) emu8k_terminatesound(i);
 destroy_useful_version_of_soundfont();
 free (voice_envelope);
 free (exclusive_class_info);
}



/* translate_soundfont_into_something_useful:
 *  Like it says, translate the soundfont data into something we can use
 *  when playing notes.
 */
static void translate_soundfont_into_something_useful() {
 int p,s,gen,weirdo;
 struct midi_preset_t *thing_to_write=NULL;
 generators_t temp_gens;
 int global_split=0,global_weirdo=0,num_weirdos;

 midi_preset = (struct midi_preset_t *)_lock_malloc(129*sizeof(struct midi_preset_t));
 for (p=0;p<_awe_sf_num_presets;p++) {
  if (_awe_sf_presets[p*3+1]==0) {
   thing_to_write = &midi_preset[_awe_sf_presets[p*3+0]];
  } else if (_awe_sf_presets[p*3+1]==128) {
   thing_to_write = &midi_preset[128];
  } else {
   thing_to_write = NULL;
  }
  if (thing_to_write) {
   thing_to_write->num_splits=_awe_sf_presets[p*3+2];
   thing_to_write->split=(struct envparms_t **)_lock_malloc(thing_to_write->num_splits*sizeof(struct envparms_t *));
   for (s=0;s<thing_to_write->num_splits;s++) {
    for (gen=0;gen<SOUNDFONT_NUM_GENERATORS-4;gen++) temp_gens[gen] = _awe_sf_defaults[gen];
    num_weirdos = _awe_sf_splits[global_split];
    for (weirdo=global_weirdo;weirdo<global_weirdo+num_weirdos;weirdo++) temp_gens[_awe_sf_gens[weirdo*2]] = _awe_sf_gens[weirdo*2+1];
    global_weirdo+=num_weirdos;
    for (gen=0;gen<4;gen++) temp_gens[gfgen_startAddrs+gen] = _awe_sf_sample_data[global_split*4+gen];
    global_split++;
    thing_to_write->split[s]=emu8k_createenvelope(temp_gens);
   }
  } else {
   strcpy(allegro_error,"AWE32 driver: had trouble with the embedded data");
  }
 }
}



/* destroy_useful_version_of_soundfont:
 *  Destroys the data created by the above function
 */
static void destroy_useful_version_of_soundfont() {
 int p,s;
 for (p=0;p<129;p++)
  if (midi_preset[p].num_splits>0) {
   for (s=0;s<midi_preset[p].num_splits;s++) emu8k_destroyenvelope(midi_preset[p].split[s]);
   free(midi_preset[p].split);
  }
 free(midi_preset);
}



/* _lock_malloc:
 *  Allocates a locked memory block
 */
void *_lock_malloc (size_t size) {
 void *ret = malloc (size);
 if (!ret) return NULL;
 if (_go32_dpmi_lock_data (ret, size)) {
  free (ret);
  return NULL;
 }
 return ret;
}

