/*         ______   ___    ___ 
 *        /\  _  \ /\_ \  /\_ \ 
 *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___ 
 *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
 *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
 *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
 *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
 *                                           /\____/
 *                                           \_/__/
 *
 *      Soundblaster driver. Supports DMA driven sample playback (mixing 
 *      up to eight samples at a time) and raw note output to the SB MIDI 
 *      port. The Adlib (FM synth) MIDI playing code is in adlib.c.
 *
 *      By Shawn Hargreaves.
 *
 *      Input routines added by Ove Kaaven.
 *
 *      See readme.txt for copyright information.
 */


#include <string.h>

#include "allegro.h"
#include "allegro/internal/aintern.h"
#include "allegro/platform/aintdos.h"

#ifndef ALLEGRO_DOS
   #error something is wrong with the makefile
#endif



/* external interface to the digital SB driver */
static int sb_detect(int input);
static int sb_init(int input, int voices);
static void sb_exit(int input);
static int sb_set_mixer_volume(int volume);
static int sb_buffer_size(void);
static int sb_rec_cap_rate(int bits, int stereo);
static int sb_rec_cap_parm(int rate, int bits, int stereo);
static int sb_rec_source(int source);
static int sb_rec_start(int rate, int bits, int stereo);
static void sb_rec_stop(void);
static int sb_rec_read(void *buf);

static char sb_desc[256] = EMPTY_STRING;



#define SB_DRIVER_CONTENTS             \
   0, 0,                               \
   MIXER_MAX_SFX, MIXER_DEF_SFX,       \
   sb_detect,                          \
   sb_init,                            \
   sb_exit,                            \
   sb_set_mixer_volume,                \
   NULL,                               \
   NULL,                               \
   NULL,                               \
   sb_buffer_size,                     \
   _mixer_init_voice,                  \
   _mixer_release_voice,               \
   _mixer_start_voice,                 \
   _mixer_stop_voice,                  \
   _mixer_loop_voice,                  \
   _mixer_get_position,                \
   _mixer_set_position,                \
   _mixer_get_volume,                  \
   _mixer_set_volume,                  \
   _mixer_ramp_volume,                 \
   _mixer_stop_volume_ramp,            \
   _mixer_get_frequency,               \
   _mixer_set_frequency,               \
   _mixer_sweep_frequency,             \
   _mixer_stop_frequency_sweep,        \
   _mixer_get_pan,                     \
   _mixer_set_pan,                     \
   _mixer_sweep_pan,                   \
   _mixer_stop_pan_sweep,              \
   _mixer_set_echo,                    \
   _mixer_set_tremolo,                 \
   _mixer_set_vibrato,                 \
   0, 0,                               \
   sb_rec_cap_rate,                    \
   sb_rec_cap_parm,                    \
   sb_rec_source,                      \
   sb_rec_start,                       \
   sb_rec_stop,                        \
   sb_rec_read



DIGI_DRIVER digi_sb10 =
{
   DIGI_SB10,
   empty_string,
   empty_string,
   "Sound Blaster 1.0", 
   SB_DRIVER_CONTENTS
};



DIGI_DRIVER digi_sb15 =
{
   DIGI_SB15,
   empty_string,
   empty_string,
   "Sound Blaster 1.5", 
   SB_DRIVER_CONTENTS
};



DIGI_DRIVER digi_sb20 =
{
   DIGI_SB20,
   empty_string,
   empty_string,
   "Sound Blaster 2.0", 
   SB_DRIVER_CONTENTS
};



DIGI_DRIVER digi_sbpro =
{
   DIGI_SBPRO,
   empty_string,
   empty_string,
   "Sound Blaster Pro", 
   SB_DRIVER_CONTENTS
};



DIGI_DRIVER digi_sb16=
{
   DIGI_SB16,
   empty_string,
   empty_string,
   "Sound Blaster 16", 
   SB_DRIVER_CONTENTS
};



/* external interface to the SB midi output driver */
static int sb_midi_detect(int input);
static int sb_midi_init(int input, int voices);
static void sb_midi_exit(int input);
static void sb_midi_output(int data);

static char sb_midi_desc[256] = EMPTY_STRING;



MIDI_DRIVER midi_sb_out =
{
   MIDI_SB_OUT,
   empty_string,
   empty_string,
   "SB MIDI interface", 
   0, 0, 0xFFFF, 0, -1, -1,
   sb_midi_detect,
   sb_midi_init,
   sb_midi_exit,
   NULL,
   NULL,
   sb_midi_output,
   _dummy_load_patches,
   _dummy_adjust_patches,
   _dummy_key_on,
   _dummy_noop1,
   _dummy_noop2,
   _dummy_noop3,
   _dummy_noop2,
   _dummy_noop2
};



static int sb_in_use = FALSE;             /* is SB being used? */
static int sb_stereo = FALSE;             /* in stereo mode? */
static int sb_recording = FALSE;          /* in input mode? */
static int sb_16bit = FALSE;              /* in 16 bit mode? */
static int sb_midi_out_mode = FALSE;      /* active for MIDI output? */
static int sb_midi_in_mode = FALSE;       /* active for MIDI input? */
static int sb_int = -1;                   /* interrupt vector */
static int sb_dsp_ver = -1;               /* SB DSP version */
static int sb_dma8 = -1;                  /* 8-bit DMA channel (SB16) */
static int sb_dma16 = -1;                 /* 16-bit DMA channel (SB16) */
static int sb_hw_dsp_ver = -1;            /* as reported by autodetect */
static int sb_dma_size = -1;              /* size of dma transfer in bytes */
static int sb_dma_mix_size = -1;          /* number of samples to mix */
static int sb_dma_count = 0;              /* need to resync with dma? */
static volatile int sb_semaphore = FALSE; /* reentrant interrupt? */

static int sb_sel[2];                     /* selectors for the buffers */
static unsigned long sb_buf[2];           /* pointers to the two buffers */
static int sb_bufnum = 0;                 /* the one currently in use */

static int sb_recbufnum = 0;              /* the one to be returned */

static int sb_master_vol = -1;            /* stored mixer settings */
static int sb_digi_vol = -1;
static int sb_fm_vol = -1;

static int sb_detecting_midi = FALSE;

static void sb_lock_mem(void);



/* sb_read_dsp:
 *  Reads a byte from the SB DSP chip. Returns -1 if it times out.
 */
static INLINE RET_VOLATILE int sb_read_dsp(void)
{
   int x;

   for (x=0; x<0xFFFF; x++)
      if (inportb(0x0E + _sound_port) & 0x80)
	 return inportb(0x0A+_sound_port);

   return -1; 
}



/* sb_write_dsp:
 *  Writes a byte to the SB DSP chip. Returns -1 if it times out.
 */
static INLINE RET_VOLATILE int sb_write_dsp(unsigned char byte)
{
   int x;

   for (x=0; x<0xFFFF; x++) {
      if (!(inportb(0x0C+_sound_port) & 0x80)) {
	 outportb(0x0C+_sound_port, byte);
	 return 0;
      }
   }
   return -1; 
}



/* _sb_voice:
 *  Turns the SB speaker on or off.
 */
void _sb_voice(int state)
{
   if (state) {
      sb_write_dsp(0xD1);

      if (sb_hw_dsp_ver >= 0x300) {          /* set up the mixer */

	 if (sb_master_vol < 0) {
	    outportb(_sound_port+4, 0x22);   /* store master volume */
	    sb_master_vol = inportb(_sound_port+5);
	 }

	 if (sb_digi_vol < 0) {
	    outportb(_sound_port+4, 4);      /* store DAC level */
	    sb_digi_vol = inportb(_sound_port+5);
	 }

	 if (sb_fm_vol < 0) {
	    outportb(_sound_port+4, 0x26);   /* store FM level */
	    sb_fm_vol = inportb(_sound_port+5);
	 }
      }
   }
   else {
      sb_write_dsp(0xD3);

      if (sb_hw_dsp_ver >= 0x300) {          /* reset previous mixer settings */

	 outportb(_sound_port+4, 0x22);      /* restore master volume */
	 outportb(_sound_port+5, sb_master_vol);

	 outportb(_sound_port+4, 4);         /* restore DAC level */
	 outportb(_sound_port+5, sb_digi_vol);

	 outportb(_sound_port+4, 0x26);      /* restore FM level */
	 outportb(_sound_port+5, sb_fm_vol);
      }
   }
}



/* _sb_set_mixer:
 *  Alters the SB-Pro hardware mixer.
 */
int _sb_set_mixer(int digi_volume, int midi_volume)
{
   if (sb_hw_dsp_ver < 0x300)
      return -1;

   if (digi_volume >= 0) {                   /* set DAC level */
      outportb(_sound_port+4, 4);
      outportb(_sound_port+5, (digi_volume & 0xF0) | (digi_volume >> 4));
   }

   if (midi_volume >= 0) {                   /* set FM level */
      outportb(_sound_port+4, 0x26);
      outportb(_sound_port+5, (midi_volume & 0xF0) | (midi_volume >> 4));
   }

   return 0;
}



/* sb_set_mixer_volume:
 *  Sets the SB mixer volume for playing digital samples.
 */
static int sb_set_mixer_volume(int volume)
{
   return _sb_set_mixer(volume, -1);
}



/* sb_stereo_mode:
 *  Enables or disables stereo output for SB-Pro.
 */
static void sb_stereo_mode(int enable)
{
   outportb(_sound_port+0x04, 0x0E); 
   outportb(_sound_port+0x05, (enable ? 2 : 0));
}



/* sb_input_stereo_mode:
 *  Enables or disables stereo input for SB-Pro.
 */
static void sb_input_stereo_mode(int enable)
{
   sb_write_dsp(enable ? 0xA8 : 0xA0);
}



/* sb_set_sample_rate:
 *  The parameter is the rate to set in Hz (samples per second).
 */
static void sb_set_sample_rate(unsigned int rate)
{
   if (sb_16bit) {
      sb_write_dsp(0x41);
      sb_write_dsp(rate >> 8);
      sb_write_dsp(rate & 0xff);
   }
   else {
      if (sb_stereo)
	 rate *= 2;

      sb_write_dsp(0x40);
      sb_write_dsp((unsigned char)(256-1000000/rate));
   }
}



/* sb_set_input_sample_rate:
 *  The parameter is the rate to set in Hz (samples per second).
 */
static void sb_set_input_sample_rate(unsigned int rate, int stereo)
{
   if (sb_16bit) {
      sb_write_dsp(0x42);
      sb_write_dsp(rate >> 8);
      sb_write_dsp(rate & 0xff);
   }
   else {
      if (stereo)
	 rate *= 2;

      sb_write_dsp(0x40);
      sb_write_dsp((unsigned char)(256-1000000/rate));
   }
}



/* _sb_reset_dsp:
 *  Resets the SB DSP chip, returning -1 on error.
 */
int _sb_reset_dsp(int data)
{
   int x;

   outportb(0x06+_sound_port, data);

   for (x=0; x<8; x++)
      inportb(0x06+_sound_port);

   outportb(0x06+_sound_port, 0);

   if (sb_read_dsp() != 0xAA)
      return -1;

   return 0;
}



/* _sb_read_dsp_version:
 *  Reads the version number of the SB DSP chip, returning -1 on error.
 */
int _sb_read_dsp_version(void)
{
   int x, y;

   if (sb_hw_dsp_ver > 0)
      return sb_hw_dsp_ver;

   if (_sound_port <= 0)
      _sound_port = 0x220;

   if (_sb_reset_dsp(1) != 0) {
      sb_hw_dsp_ver = -1;
   }
   else {
      sb_write_dsp(0xE1);
      x = sb_read_dsp();
      y = sb_read_dsp();
      sb_hw_dsp_ver = ((x << 8) | y);
   }

   return sb_hw_dsp_ver;
}



/* sb_buffer_size:
 *  Returns the current DMA buffer size, for use by the audiostream code.
 */
static int sb_buffer_size(void)
{
   return (sb_stereo ? sb_dma_mix_size/2 : sb_dma_mix_size);
}



/* sb_play_buffer:
 *  Starts a dma transfer of size bytes. On cards capable of it, the
 *  transfer will use auto-initialised dma, so there is no need to call
 *  this routine more than once. On older cards it must be called from
 *  the end-of-buffer handler to switch to the new buffer.
 */
static void sb_play_buffer(int size)
{
   if (sb_dsp_ver <= 0x200) {                /* 8 bit single-shot */
      sb_write_dsp(0x14);
      sb_write_dsp((size-1) & 0xFF);
      sb_write_dsp((size-1) >> 8);
   }
   else if (sb_dsp_ver < 0x400) {            /* 8 bit auto-initialised */
      sb_write_dsp(0x48);
      sb_write_dsp((size-1) & 0xFF);
      sb_write_dsp((size-1) >> 8);
      sb_write_dsp(0x90);
   }
   else {                                    /* 16 bit */
      size /= 2;
      sb_write_dsp(0xB6);
      sb_write_dsp(0x30);
      sb_write_dsp((size-1) & 0xFF);
      sb_write_dsp((size-1) >> 8);
   }
}

END_OF_STATIC_FUNCTION(sb_play_buffer);



/* sb_record_buffer:
 *  Starts a dma transfer of size bytes. On cards capable of it, the
 *  transfer will use auto-initialised dma, so there is no need to call
 *  this routine more than once. On older cards it must be called from
 *  the end-of-buffer handler to switch to the new buffer.
 */
static void sb_record_buffer(int size, int stereo, int bits)
{
   if (sb_dsp_ver <= 0x200) {                /* 8 bit single-shot */
      sb_write_dsp(0x24);
      sb_write_dsp((size-1) & 0xFF);
      sb_write_dsp((size-1) >> 8);
   }
   else if (sb_dsp_ver < 0x400) {            /* 8 bit auto-initialised */
      sb_write_dsp(0x48);
      sb_write_dsp((size-1) & 0xFF);
      sb_write_dsp((size-1) >> 8);
      sb_write_dsp(0x98);
   }
   else if (bits <= 8) {                     /* 8 bit */
      sb_write_dsp(0xCE);
      sb_write_dsp((stereo) ? 0x20 : 0x00);
      sb_write_dsp((size-1) & 0xFF);
      sb_write_dsp((size-1) >> 8);
   }
   else {                                    /* 16 bit */
      size /= 2;
      sb_write_dsp(0xBE);
      sb_write_dsp((stereo) ? 0x20 : 0x00);
      sb_write_dsp((size-1) & 0xFF);
      sb_write_dsp((size-1) >> 8);
   }
}

END_OF_STATIC_FUNCTION(sb_record_buffer);



/* sb_interrupt:
 *  The SB end-of-buffer interrupt handler. Swaps to the other buffer 
 *  if the card doesn't have auto-initialised dma, and then refills the
 *  buffer that just finished playing.
 */
static int sb_interrupt(void)
{
   unsigned char isr;

   if (sb_dsp_ver >= 0x400) {
      /* read SB16 ISR mask */
      outportb(_sound_port+4, 0x82);
      isr = inportb(_sound_port+5) & 7;

      if (isr & 4) {
	 /* MPU-401 interrupt */
	 _mpu_poll();
	 _eoi(_sound_irq);
	 return 0;
      }

      if (!(isr & 3)) {
	 /* unknown interrupt */
	 _eoi(_sound_irq);
	 return 0;
      }
   }

   if (sb_dsp_ver <= 0x200) {                /* not auto-initialised */
      _dma_start(_sound_dma, sb_buf[1-sb_bufnum], sb_dma_size, FALSE, FALSE);
      if (sb_recording)
	 sb_record_buffer(sb_dma_size, FALSE, 8);
      else
	 sb_play_buffer(sb_dma_size);
   }
   else {                                    /* poll dma position */
      sb_dma_count++;
      if (sb_dma_count > 16) {
	 sb_bufnum = (_dma_todo(_sound_dma) > (unsigned)sb_dma_size) ? 1 : 0;
	 sb_dma_count = 0;
      }
   }

   if ((!sb_semaphore) && (!sb_recording)) {
      sb_semaphore = TRUE;

      ENABLE();                              /* mix some more samples */
      _mix_some_samples(sb_buf[sb_bufnum], _dos_ds, sb_16bit);
      DISABLE();

      sb_semaphore = FALSE;
   } 

   sb_bufnum = 1 - sb_bufnum; 

   if ((sb_recording) && (digi_recorder)) {
      sb_semaphore = TRUE;

      ENABLE();                              /* sample input callback */
      digi_recorder();
      DISABLE();

      sb_semaphore = FALSE;
   }

   if (sb_16bit)                             /* acknowledge SB */
      inportb(_sound_port+0x0F);
   else
      inportb(_sound_port+0x0E);

   _eoi(_sound_irq);                         /* acknowledge interrupt */
   return 0;
}

END_OF_STATIC_FUNCTION(sb_interrupt);



/* sb_start:
 *  Starts up the sound output.
 */
static void sb_start(void)
{
   sb_bufnum = 0;

   _sb_voice(1);
   sb_set_sample_rate(_sound_freq);

   if ((sb_hw_dsp_ver >= 0x300) && (sb_dsp_ver < 0x400))
      sb_stereo_mode(sb_stereo);

   if (sb_dsp_ver <= 0x200)
      _dma_start(_sound_dma, sb_buf[0], sb_dma_size, FALSE, FALSE);
   else
      _dma_start(_sound_dma, sb_buf[0], sb_dma_size*2, TRUE, FALSE);

   sb_play_buffer(sb_dma_size);
}



/* sb_stop:
 *  Stops the sound output.
 */
static void sb_stop(void)
{
   /* halt sound output */
   _sb_voice(0);

   /* stop dma transfer */
   _dma_stop(_sound_dma);

   if (sb_dsp_ver <= 0x0200)
      sb_write_dsp(0xD0); 

   _sb_reset_dsp(1);
}



/* sb_detect:
 *  SB detection routine. Uses the BLASTER environment variable,
 *  or 'sensible' guesses if that doesn't exist.
 */
static int sb_detect(int input)
{
   char *blaster = getenv("BLASTER");
   char tmp[64], *msg;
   int cmask;
   int max_freq;
   int default_freq;

   if (!sb_detecting_midi) {
      /* input mode only works on the top of an existing output driver */
      if (input) {
	 if (digi_driver != digi_input_driver) {
	    ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("SB output driver must be installed before input can be read"));
	    return FALSE;
	 }
	 return TRUE;
      }

      /* what breed of SB are we looking for? */
      switch (digi_card) {

	 case DIGI_SB10:
	    sb_dsp_ver = 0x100;
	    break;

	 case DIGI_SB15:
	    sb_dsp_ver = 0x200;
	    break;

	 case DIGI_SB20:
	    sb_dsp_ver = 0x201;
	    break;

	 case DIGI_SBPRO:
	    sb_dsp_ver = 0x300;
	    break;

	 case DIGI_SB16:
	    sb_dsp_ver = 0x400;
	    break;

	 default:
	    sb_dsp_ver = -1;
	    break;
      } 
   }
   else
      sb_dsp_ver = -1;

   /* parse BLASTER env */
   if (blaster) { 
      while (*blaster) {
	 while ((*blaster == ' ') || (*blaster == '\t'))
	    blaster++;

	 if (*blaster) {
	    switch (*blaster) {

	       case 'a': case 'A':
		  if (_sound_port < 0)
		     _sound_port = strtol(blaster+1, NULL, 16);
		  break;

	       case 'i': case 'I':
		  if (_sound_irq < 0)
		     _sound_irq = strtol(blaster+1, NULL, 10);
		  break;

	       case 'd': case 'D':
		  sb_dma8 = strtol(blaster+1, NULL, 10);
		  break;

	       case 'h': case 'H':
		  sb_dma16 = strtol(blaster+1, NULL, 10);
		  break;
	    }

	    while ((*blaster) && (*blaster != ' ') && (*blaster != '\t'))
	       blaster++;
	 }
      }
   }

   if (_sound_port < 0)
      _sound_port = 0x220;

   /* make sure we got a good port address */
   if (_sb_reset_dsp(1) != 0) { 
      static int bases[] = { 0x210, 0x220, 0x230, 0x240, 0x250, 0x260, 0 };
      int i;

      for (i=0; bases[i]; i++) {
	 _sound_port = bases[i];
	 if (_sb_reset_dsp(1) == 0)
	    break;
      }
   }

   /* check if the card really exists */
   _sb_read_dsp_version();
   if (sb_hw_dsp_ver < 0) {
      ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("Sound Blaster not found"));
      return FALSE;
   }

   if (sb_dsp_ver < 0) {
      sb_dsp_ver = sb_hw_dsp_ver;
   }
   else {
      if (sb_dsp_ver > sb_hw_dsp_ver) {
	 sb_hw_dsp_ver = sb_dsp_ver = -1;
	 ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("Older SB version detected"));
	 return FALSE;
      }
   }

   if (sb_dsp_ver >= 0x400) {
      /* read configuration from SB16 card */
      if (_sound_irq < 0) {
	 outportb(_sound_port+4, 0x80);
	 cmask = inportb(_sound_port+5);
	 if (cmask&1) _sound_irq = 2; /* or 9? */
	 if (cmask&2) _sound_irq = 5;
	 if (cmask&4) _sound_irq = 7;
	 if (cmask&8) _sound_irq = 10;
      }
      if ((sb_dma8 < 0) || (sb_dma16 < 0)) {
	 outportb(_sound_port+4, 0x81);
	 cmask = inportb(_sound_port+5);
	 if (sb_dma8 < 0) {
	    if (cmask&1) sb_dma8 = 0;
	    if (cmask&2) sb_dma8 = 1;
	    if (cmask&8) sb_dma8 = 3;
	 }
	 if (sb_dma16 < 0) {
	    sb_dma16 = sb_dma8;
	    if (cmask&0x20) sb_dma16 = 5;
	    if (cmask&0x40) sb_dma16 = 6;
	    if (cmask&0x80) sb_dma16 = 7;
	 }
      }
   }

   /* if nothing else works */
   if (_sound_irq < 0)
      _sound_irq = 7;

   if (sb_dma8 < 0)
      sb_dma8 = 1;

   if (sb_dma16 < 0)
      sb_dma16 = 5;

   /* figure out the hardware interrupt number */
   sb_int = _map_irq(_sound_irq);

   if (!sb_detecting_midi) {
      /* what breed of SB? */
      if (sb_dsp_ver >= 0x400) {
	 msg = "SB 16";
	 max_freq = 45454;
	 default_freq = 22727;
      }
      else if (sb_dsp_ver >= 0x300) {
	 msg = "SB Pro";
	 max_freq = 22727;
	 default_freq = 22727;
      }
      else if (sb_dsp_ver >= 0x201) {
	 msg = "SB 2.0";
	 max_freq = 45454;
	 default_freq = 22727;
      }
      else if (sb_dsp_ver >= 0x200) {
	 msg = "SB 1.5";
	 max_freq = 16129;
	 default_freq = 16129;
      }
      else {
	 msg = "SB 1.0";
	 max_freq = 16129;
	 default_freq = 16129;
      }

      /* set up the playback frequency */
      if (_sound_freq <= 0)
	 _sound_freq = default_freq;

      if (_sound_freq < 15000) {
	 _sound_freq = 11906;
	 sb_dma_size = 128;
      }
      else if (MIN(_sound_freq, max_freq) < 20000) {
	 _sound_freq = 16129;
	 sb_dma_size = 128;
      }
      else if (MIN(_sound_freq, max_freq) < 40000) {
	 _sound_freq = 22727;
	 sb_dma_size = 256;
      }
      else {
	 _sound_freq = 45454;
	 sb_dma_size = 512;
      }

      if (sb_dsp_ver <= 0x200)
	 sb_dma_size *= 4;

      sb_dma_mix_size = sb_dma_size;

      /* can we handle 16 bit sound? */
      if (sb_dsp_ver >= 0x400) { 
	 if (_sound_dma < 0)
	    _sound_dma = sb_dma16;
	 else
	    sb_dma16 = _sound_dma;

	 sb_16bit = TRUE;
	 digi_driver->rec_cap_bits = 24;
	 sb_dma_size <<= 1;
      }
      else { 
	 if (_sound_dma < 0)
	    _sound_dma = sb_dma8;
	 else
	    sb_dma8 = _sound_dma;

	 sb_16bit = FALSE;
	 digi_driver->rec_cap_bits = 8;
      }

      /* can we handle stereo? */
      if (sb_dsp_ver >= 0x300) {
	 sb_stereo = TRUE;
	 digi_driver->rec_cap_stereo = TRUE;
	 sb_dma_size <<= 1;
	 sb_dma_mix_size <<= 1;
      }
      else {
	 sb_stereo = FALSE;
	 digi_driver->rec_cap_stereo = FALSE;
      }

      /* set up the card description */
      uszprintf(sb_desc, sizeof(sb_desc), get_config_text("%s (%d hz) on port %X, using IRQ %d and DMA channel %d"),
		uconvert_ascii(msg, tmp), _sound_freq, _sound_port, _sound_irq, _sound_dma);

      digi_driver->desc = sb_desc;
   }

   return TRUE;
}



/* sb_init:
 *  SB init routine: returns zero on success, -1 on failure.
 */
static int sb_init(int input, int voices)
{
   if (input)
      return 0;

   if (sb_in_use) {
      ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("Can't use SB MIDI interface and DSP at the same time"));
      return -1;
   }

   if (sb_dsp_ver <= 0x200) {       /* two conventional mem buffers */
      if ((_dma_allocate_mem(sb_dma_size, &sb_sel[0], &sb_buf[0]) != 0) ||
	  (_dma_allocate_mem(sb_dma_size, &sb_sel[1], &sb_buf[1]) != 0))
	 return -1;
   }
   else {                           /* auto-init dma, one big buffer */
      if (_dma_allocate_mem(sb_dma_size*2, &sb_sel[0], &sb_buf[0]) != 0)
	 return -1;

      sb_sel[1] = sb_sel[0];
      sb_buf[1] = sb_buf[0] + sb_dma_size;
   }

   sb_lock_mem();

   digi_driver->voices = voices;

   if (_mixer_init(sb_dma_mix_size, _sound_freq, sb_stereo, sb_16bit, &digi_driver->voices) != 0)
      return -1;

   _mix_some_samples(sb_buf[0], _dos_ds, sb_16bit);
   _mix_some_samples(sb_buf[1], _dos_ds, sb_16bit);

   _enable_irq(_sound_irq);
   _install_irq(sb_int, sb_interrupt);

   sb_start();

   sb_in_use = TRUE;
   return 0;
}



/* sb_exit:
 *  SB driver cleanup routine, removes ints, stops dma, frees buffers, etc.
 */
static void sb_exit(int input)
{
   if (input)
      return;

   sb_stop();
   _remove_irq(sb_int);
   _restore_irq(_sound_irq);

   __dpmi_free_dos_memory(sb_sel[0]);

   if (sb_sel[1] != sb_sel[0])
      __dpmi_free_dos_memory(sb_sel[1]);

   _mixer_exit();

   sb_hw_dsp_ver = sb_dsp_ver = -1;
   sb_in_use = FALSE;
}



/* sb_rec_cap_rate:
 *  Returns maximum input sampling rate.
 */
static int sb_rec_cap_rate(int bits, int stereo)
{
   if (sb_dsp_ver < 0)
      return 0;

   if (sb_dsp_ver >= 0x400)
      /* SB16 can handle 45kHz under all circumstances */
      return 45454;

   /* lesser SB cards can't handle 16-bit */
   if (bits != 8)
      return 0;

   if (sb_dsp_ver >= 0x300)
      /* SB Pro can handle 45kHz, but only half that in stereo */
      return (stereo) ? 22727 : 45454;

   /* lesser SB cards can't handle stereo */
   if (stereo)
      return 0;

   if (sb_dsp_ver >= 0x201)
      /* SB 2.0 supports 15kHz */
      return 15151;

   /* SB 1.x supports 13kHz */
   return 13157;
}



/* sb_rec_cap_parm:
 *  Returns whether the specified parameters can be set.
 */
static int sb_rec_cap_parm(int rate, int bits, int stereo)
{
   int c, r;

   if ((r = sb_rec_cap_rate(bits, stereo)) <= 0)
      return 0;

   if (r < rate)
      return -r;

   if (sb_dsp_ver >= 0x400) {
      /* if bits==8 and rate==_sound_freq, bidirectional is possible,
	 but that's not implemented yet */
      return 1;
   }

   if (stereo)
      rate *= 2;

   c = 1000000/rate;
   r = 1000000/c;
   if (r != rate)
      return -r;

   return 1;
}



/* sb_rec_source:
 *  Sets the sampling source for audio recording.
 */
static int sb_rec_source(int source)
{
   int v1, v2;

   if (sb_hw_dsp_ver >= 0x400) {
      /* SB16 */
      switch (source) {

	 case SOUND_INPUT_MIC:
	    v1 = 1;
	    v2 = 1;
	    break;

	 case SOUND_INPUT_LINE:
	    v1 = 16;
	    v2 = 8;
	    break;

	 case SOUND_INPUT_CD:
	    v1 = 4;
	    v2 = 2;
	    break;

	 default:
	    return -1;
      }

      outportb(_sound_port+4, 0x3D);
      outportb(_sound_port+5, v1);

      outportb(_sound_port+4, 0x3E);
      outportb(_sound_port+5, v2);

      return 0;
   }
   else if (sb_hw_dsp_ver >= 0x300) {
      /* SB Pro */
      outportb(_sound_port+4, 0xC);
      v1 = inportb(_sound_port+5);

      switch (source) {

	 case SOUND_INPUT_MIC:
	    v1 = (v1 & 0xF9);
	    break;

	 case SOUND_INPUT_LINE:
	    v1 = (v1 & 0xF9) | 6;
	    break;

	 case SOUND_INPUT_CD:
	    v1 = (v1 & 0xF9) | 2;
	    break;

	 default:
	    return -1;
      }

      outportb(_sound_port+4, 0xC);
      outportb(_sound_port+5, v1);

      return 0;
   }

   return -1;
}



/* sb_rec_start:
 *  Stops playback, switches the SB to A/D mode, and starts recording.
 *  Returns the DMA buffer size if successful.
 */
static int sb_rec_start(int rate, int bits, int stereo)
{
   if (sb_rec_cap_parm(rate, bits, stereo) <= 0)
      return 0;

   sb_stop();

   sb_16bit = (bits>8);
   _sound_dma = (sb_16bit) ? sb_dma16 : sb_dma8;
   sb_recording = TRUE;
   sb_recbufnum = sb_bufnum = 0;

   _sb_voice(1);
   sb_set_input_sample_rate(rate, stereo);

   if ((sb_hw_dsp_ver >= 0x300) && (sb_dsp_ver < 0x400))
      sb_input_stereo_mode(stereo);

   if (sb_dsp_ver <= 0x200)
      _dma_start(_sound_dma, sb_buf[0], sb_dma_size, FALSE, TRUE);
   else
      _dma_start(_sound_dma, sb_buf[0], sb_dma_size*2, TRUE, TRUE);

   sb_record_buffer(sb_dma_size, stereo, bits);

   return sb_dma_size;
}



/* sb_rec_stop:
 *  Stops recording, switches the SB back to D/A mode, and restarts playback.
 */
static void sb_rec_stop(void)
{
   if (!sb_recording)
      return;

   sb_stop();

   sb_recording = FALSE;
   sb_16bit = (sb_dsp_ver >= 0x400);
   _sound_dma = (sb_16bit) ? sb_dma16 : sb_dma8;

   _mix_some_samples(sb_buf[0], _dos_ds, sb_16bit);
   _mix_some_samples(sb_buf[1], _dos_ds, sb_16bit);

   sb_start();
}



/* sb_rec_read:
 *  Retrieves the just recorded DMA buffer, if there is one.
 */
static int sb_rec_read(void *buf)
{
   if (!sb_recording)
      return 0;

   if (sb_bufnum == sb_recbufnum)
      return 0;

   dosmemget(sb_buf[sb_recbufnum], sb_dma_size, buf);
   sb_recbufnum = 1-sb_recbufnum;

   return 1;
}

END_OF_STATIC_FUNCTION(sb_rec_read);



/* sb_midi_interrupt:
 *  Interrupt handler for the SB MIDI input.
 */
static int sb_midi_interrupt(void)
{
   int c = sb_read_dsp();

   if ((c >= 0) && (midi_recorder))
      midi_recorder(c);

   _eoi(_sound_irq);
   return 0;
}

END_OF_STATIC_FUNCTION(sb_midi_interrupt);



/* sb_midi_output:
 *  Writes a byte to the SB midi interface.
 */
static void sb_midi_output(int data)
{
   sb_write_dsp(data);
}

END_OF_STATIC_FUNCTION(sb_midi_output);



/* sb_midi_detect:
 *  Detection routine for the SB MIDI interface.
 */
static int sb_midi_detect(int input)
{
   int ret;

   if ((input) && (sb_midi_out_mode))
      return TRUE;

   sb_detecting_midi = TRUE;

   ret = sb_detect(FALSE);

   sb_detecting_midi = FALSE;

   return ret;
}



/* sb_midi_init:
 *  Initialises the SB midi interface, returning zero on success.
 */
static int sb_midi_init(int input, int voices)
{
   if ((sb_in_use) && (!sb_midi_out_mode)) {
      ustrzcpy(allegro_error, ALLEGRO_ERROR_SIZE, get_config_text("Can't use SB MIDI interface and DSP at the same time"));
      return -1;
   }

   sb_dsp_ver = -1;
   sb_lock_mem();

   uszprintf(sb_midi_desc, sizeof(sb_midi_desc), get_config_text("Sound Blaster MIDI interface on port %X"), _sound_port);
   midi_sb_out.desc = sb_midi_desc;

   if (input) {
      _enable_irq(_sound_irq);
      _install_irq(sb_int, sb_midi_interrupt);
      sb_midi_in_mode = TRUE;
   }
   else
      sb_midi_out_mode = TRUE;

   sb_write_dsp(0x35);

   sb_in_use = TRUE;
   return 0;
}



/* sb_midi_exit:
 *  Resets the SB midi interface when we are finished.
 */
static void sb_midi_exit(int input)
{
   if (input) {
      _remove_irq(sb_int);
      _restore_irq(_sound_irq);
      sb_midi_in_mode = FALSE;
   }
   else
      sb_midi_out_mode = FALSE;

   if ((!sb_midi_in_mode) && (!sb_midi_out_mode)) {
      _sb_reset_dsp(1);
      sb_in_use = FALSE;
   }
}



/* sb_lock_mem:
 *  Locks all the memory touched by parts of the SB code that are executed
 *  in an interrupt context.
 */
static void sb_lock_mem(void)
{
   extern void _mpu_poll_end(void);

   LOCK_VARIABLE(digi_sb10);
   LOCK_VARIABLE(digi_sb15);
   LOCK_VARIABLE(digi_sb20);
   LOCK_VARIABLE(digi_sbpro);
   LOCK_VARIABLE(digi_sb16);
   LOCK_VARIABLE(midi_sb_out);
   LOCK_VARIABLE(_sound_freq);
   LOCK_VARIABLE(_sound_port);
   LOCK_VARIABLE(_sound_dma);
   LOCK_VARIABLE(_sound_irq);
   LOCK_VARIABLE(sb_int);
   LOCK_VARIABLE(sb_in_use);
   LOCK_VARIABLE(sb_recording);
   LOCK_VARIABLE(sb_16bit);
   LOCK_VARIABLE(sb_midi_out_mode);
   LOCK_VARIABLE(sb_midi_in_mode);
   LOCK_VARIABLE(sb_dsp_ver);
   LOCK_VARIABLE(sb_hw_dsp_ver);
   LOCK_VARIABLE(sb_dma_size);
   LOCK_VARIABLE(sb_dma_mix_size);
   LOCK_VARIABLE(sb_sel);
   LOCK_VARIABLE(sb_buf);
   LOCK_VARIABLE(sb_bufnum);
   LOCK_VARIABLE(sb_recbufnum);
   LOCK_VARIABLE(sb_dma_count);
   LOCK_VARIABLE(sb_semaphore);
   LOCK_VARIABLE(sb_recording);
   LOCK_FUNCTION(sb_play_buffer);
   LOCK_FUNCTION(sb_interrupt);
   LOCK_FUNCTION(sb_rec_read);
   LOCK_FUNCTION(sb_midi_interrupt);
   LOCK_FUNCTION(sb_midi_output);
   LOCK_FUNCTION(sb_record_buffer);
   LOCK_FUNCTION(_mpu_poll);

   _dma_lock_mem();
}


