/////////////////////////////////////////////////////////////////////////
// $Id: sb16.cc,v 1.39 2003/12/26 10:06:57 vruppert Exp $
/////////////////////////////////////////////////////////////////////////
//
//  Copyright (C) 2002  MandrakeSoft S.A.
//
//    MandrakeSoft S.A.
//    43, rue d'Aboukir
//    75002 Paris - France
//    http://www.linux-mandrake.com/
//    http://www.mandrakesoft.com/
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA

// This file (SB16.CC) written and donated by Josef Drexler

// Define BX_PLUGGABLE in files that can be compiled into plugins.  For
// platforms that require a special tag on exported symbols, BX_PLUGGABLE 
// is used to know when we are exporting symbols and when we are importing.
#define BX_PLUGGABLE

#include "bochs.h"
#if BX_SUPPORT_SB16

#define LOG_THIS theSB16Device->

bx_sb16_c *theSB16Device = NULL;

  int
libsb16_LTX_plugin_init(plugin_t *plugin, plugintype_t type, int argc, char *argv[])
{
  theSB16Device = new bx_sb16_c ();
  bx_devices.pluginSB16Device = theSB16Device;
  BX_REGISTER_DEVICE_DEVMODEL(plugin, type, theSB16Device, BX_PLUGIN_SB16);
  return(0); // Success
}

  void
libsb16_LTX_plugin_fini(void)
{
}

// some shortcuts to save typing
#define LOGFILE         BX_SB16_THIS logfile
#define MIDIDATA        BX_SB16_THIS midifile
#define WAVEDATA        BX_SB16_THIS wavefile
#define MPU             BX_SB16_THIS mpu401
#define DSP             BX_SB16_THIS dsp
#define MIXER           BX_SB16_THIS mixer
#define EMUL            BX_SB16_THIS emuldata
#define OPL             BX_SB16_THIS opl

#define BX_SB16_OUTPUT  BX_SB16_THIS output

// here's a safe way to print out null pointeres
#define MIGHT_BE_NULL(x)  ((x==NULL)? "(null)" : x)

bx_sb16_c::bx_sb16_c(void)
{
  put("SB16");
  settype(SB16LOG);
  mpu401.timer_handle = BX_NULL_TIMER_HANDLE;
  dsp.timer_handle = BX_NULL_TIMER_HANDLE;
  opl.timer_handle = BX_NULL_TIMER_HANDLE;
}

bx_sb16_c::~bx_sb16_c(void)
{
  switch (bx_options.sb16.Omidimode->get ())
    {
    case 2:
      if (MIDIDATA != NULL)
        finishmidifile();
      break;
    case 1:
      if (MPU.outputinit != 0)
        BX_SB16_OUTPUT->closemidioutput();
      break;
    case 3:
      if (MIDIDATA != NULL)
        fclose(MIDIDATA);
      break;
    }

  switch (bx_options.sb16.Owavemode->get ())
    {
    case 2:
      if (WAVEDATA != NULL)
        finishvocfile();
      break;
    case 1:
      if (DSP.outputinit != 0)
        BX_SB16_OUTPUT->closewaveoutput();
      break;
    case 3:
      if (WAVEDATA != NULL)
        fclose(WAVEDATA);
      break;
    }

  delete(BX_SB16_OUTPUT);

  delete [] DSP.dma.chunk;

  if ((bx_options.sb16.Ologlevel->get () > 0) && LOGFILE)
    fclose(LOGFILE);
}

void bx_sb16_c::init(void)
{
  unsigned addr, i;

  if ( (strlen(bx_options.sb16.Ologfile->getptr ()) < 1) )
    bx_options.sb16.Ologlevel->set (0);

  if (bx_options.sb16.Ologlevel->get () > 0)
    {
      LOGFILE = fopen(bx_options.sb16.Ologfile->getptr (),"w"); // logfile for errors etc.
      if (LOGFILE == NULL)
	{
	  BX_ERROR(("Error opening file %s. Logging disabled.", bx_options.sb16.Ologfile->getptr ()));
	  bx_options.sb16.Ologlevel->set (0);
	}
    }

      // let the output functions initialize
  BX_SB16_OUTPUT = new BX_SOUND_OUTPUT_C(BX_SB16_THISP);

  if (BX_SB16_OUTPUT == NULL)
    {
      writelog( MIDILOG(2), "Couldn't initialize output devices. Output disabled.");
      bx_options.sb16.Omidimode->set (0);
      bx_options.sb16.Owavemode->set (0);
    }

  if ( (bx_options.sb16.Omidimode->get () == 2) ||
       (bx_options.sb16.Omidimode->get () == 3) )
    {
      MIDIDATA = fopen(bx_options.sb16.Omidifile->getptr (),"wb");
      if (MIDIDATA == NULL)
	{
	  writelog (MIDILOG(2), "Error opening file %s. Midimode disabled.", bx_options.sb16.Omidifile->getptr ());
	  bx_options.sb16.Omidimode->set (0);
	}
      else if (bx_options.sb16.Omidimode->get () == 2)
	initmidifile();
    }

  if ( (bx_options.sb16.Owavemode->get () == 2) ||
       (bx_options.sb16.Owavemode->get () == 3) )
    {
      WAVEDATA = fopen(bx_options.sb16.Owavefile->getptr (),"wb");
      if (WAVEDATA == NULL)
	{
	  writelog (WAVELOG(2), "Error opening file %s. Wavemode disabled.", bx_options.sb16.Owavefile);
	  bx_options.sb16.Owavemode->set (0);
	}
      else if (bx_options.sb16.Owavemode->get () == 2)
	initvocfile();
    }

  DSP.dma.chunk = new Bit8u[BX_SOUND_OUTPUT_WAVEPACKETSIZE];
  DSP.dma.chunkindex = 0;
  DSP.outputinit = 0;
  MPU.outputinit = 0;

  if (DSP.dma.chunk == NULL)
    {
      writelog( WAVELOG(2), "Couldn't allocate wave buffer - wave output disabled.");
      bx_options.sb16.Owavemode->set (0);
    }

  BX_INFO(("midi=%d,%s  wave=%d,%s  log=%d,%s  dmatimer=%d",
	    bx_options.sb16.Omidimode->get (), MIGHT_BE_NULL(bx_options.sb16.Omidifile->getptr ()),
	    bx_options.sb16.Owavemode->get (), MIGHT_BE_NULL(bx_options.sb16.Owavefile->getptr ()),
	    bx_options.sb16.Ologlevel->get (), MIGHT_BE_NULL(bx_options.sb16.Ologfile->getptr ()),
	    bx_options.sb16.Odmatimer->get ()));

  // allocate the FIFO buffers - except for the MPUMIDICMD buffer
  // these sizes are generous, 16 or 8 would probably be sufficient
  MPU.datain.init( (int) 64);	        // the input
  MPU.dataout.init( (int) 64);	        // and output
  MPU.cmd.init( (int) 64);              // and command buffers
  MPU.midicmd.init( (int) 256);         // and the midi command buffer (note- large SYSEX'es have to fit!)
  DSP.datain.init( (int) 64);           // the DSP input
  DSP.dataout.init( (int) 64);          // and output buffers
  EMUL.datain.init( (int) 64);          // the emulator ports
  EMUL.dataout.init( (int) 64);         // for changing emulator settings

  // reset all parts of the hardware by
  // triggering their reset functions

  // reset the Emulator port
  emul_write(0x00);

  // reset the MPU401
  mpu_command(0xff);
  MPU.last_delta_time = 0xffffffff;

  // reset the DSP
  DSP.dma.highspeed = 0;
  DSP.dma.mode = 0;
  DSP.irqpending = 0;
  DSP.midiuartmode = 0;
  DSP.resetport = 1;  // so that one call to dsp_reset is sufficient
  dsp_reset(0);       // (reset is 1 to 0 transition)
  DSP.testreg = 0;

  BX_SB16_IRQ = -1; // will be initialized later by the mixer reset

  for (i=0; i<BX_SB16_MIX_REG; i++)
    MIXER.reg[i] = 0xff;
  MIXER.reg[0x00] = 0;  // reset register
  MIXER.reg[0x80] = 2;  // IRQ 5
  MIXER.reg[0x81] = 2;  // 8-bit DMA 1, no 16-bit DMA
  MIXER.reg[0x82] = 0;  // no IRQ pending
  MIXER.reg[0xfd] = 16; // ???
  MIXER.reg[0xfe] = 6;  // ???
  set_irq_dma();        // set the IRQ and DMA

  // call the mixer reset
  mixer_writeregister(0x00);
  mixer_writedata(0x00);

  // reset the FM emulation
  OPL.mode = fminit;
  OPL.timer_running = 0;
  opl_entermode(single);

  // Allocate the IO addresses, 2x0..2xf, 3x0..3x4 and 388..38b
  for (addr=BX_SB16_IO; addr<BX_SB16_IO+BX_SB16_IOLEN; addr++) {
    DEV_register_ioread_handler(this,
       &read_handler, addr, "SB16", 1);
    DEV_register_iowrite_handler(this,
       &write_handler, addr, "SB16", 1);
    }
  for (addr=BX_SB16_IOMPU; addr<BX_SB16_IOMPU+BX_SB16_IOMPULEN; addr++) {
    DEV_register_ioread_handler(this,
       &read_handler, addr, "SB16", 1);
    DEV_register_iowrite_handler(this,
       &write_handler, addr, "SB16", 1);
    }
  for (addr=BX_SB16_IOADLIB; addr<BX_SB16_IOADLIB+BX_SB16_IOADLIBLEN; addr++) {
    DEV_register_ioread_handler(this,
       read_handler, addr, "SB16", 1);
    DEV_register_iowrite_handler(this,
       write_handler, addr, "SB16", 1);
    }

  writelog(BOTHLOG(3),
	   "driver initialised, IRQ %d, IO %03x/%03x/%03x, DMA %d/%d",
	   BX_SB16_IRQ, BX_SB16_IO, BX_SB16_IOMPU, BX_SB16_IOADLIB,
	   BX_SB16_DMAL, BX_SB16_DMAH);

  // initialize the timers
  if (MPU.timer_handle == BX_NULL_TIMER_HANDLE) {
    MPU.timer_handle = bx_pc_system.register_timer
      (BX_SB16_THISP, mpu_timer, 500000 / 384, 1, 1, "sb16.mpu");
    // midi timer: active, continuous, 500000 / 384 seconds (384 = delta time, 500000 = sec per beat at 120 bpm. Don't change this!)
  }

  if (DSP.timer_handle == BX_NULL_TIMER_HANDLE) {
    DSP.timer_handle = bx_pc_system.register_timer
      (BX_SB16_THISP, dsp_dmatimer, 1, 1, 0, "sb16.dsp");
	// dma timer: inactive, continous, frequency variable
  }

  if (OPL.timer_handle == BX_NULL_TIMER_HANDLE) {
    OPL.timer_handle = bx_pc_system.register_timer
      (BX_SB16_THISP, opl_timer, 80, 1, 0, "sb16.opl");
	// opl timer: inactive, continuous, frequency 80us
  }

  writelog(MIDILOG(4), "Timers initialized, midi %d, dma %d, opl %d",
	   MPU.timer_handle, DSP.timer_handle, OPL.timer_handle );
  MPU.current_timer = 0;
}

void bx_sb16_c::reset(unsigned type)
{
}

  // the timer functions
void bx_sb16_c::mpu_timer (void *this_ptr)
{
  ((bx_sb16_c *) this_ptr)->mpu401.current_timer++;
}

void bx_sb16_c::dsp_dmatimer (void *this_ptr)
{
  bx_sb16_c *This = (bx_sb16_c *) this_ptr;

  // raise the DRQ line. It is then lowered by dsp_getsamplebyte()
  // when the next byte has been received.
  // However, don't do this if the next byte/word will fill up the
  // output buffer and the output functions are not ready yet.

  if ( (bx_options.sb16.Owavemode->get () != 1) ||
       ( (This->dsp.dma.chunkindex + 1 < BX_SOUND_OUTPUT_WAVEPACKETSIZE) &&
	 (This->dsp.dma.count > 0) ) ||
       (This->output->waveready() == BX_SOUND_OUTPUT_OK) ) {
    if (DSP.dma.bits == 8)
      DEV_dma_set_drq(BX_SB16_DMAL, 1);
    else
      DEV_dma_set_drq(BX_SB16_DMAH, 1);
    }
}

void bx_sb16_c::opl_timer (void *this_ptr)
{
  ((bx_sb16_c *) this_ptr)->opl_timerevent();
}

  // the various IO handlers

  // The DSP/FM music part

// dsp_reset() resets the DSP after the sequence 1/0. Returns
// 0xaa on the data port
void bx_sb16_c::dsp_reset(Bit32u value)
{

  writelog(WAVELOG(4), "DSP Reset port write value %x", value);

  // just abort high speed mode if it is set
  if (DSP.dma.highspeed != 0)
    {
      DSP.dma.highspeed = 0;
      writelog(WAVELOG(4), "High speed mode aborted");
      return;
    }

  if ( (DSP.resetport == 1) &&
       (value == 0) )
    {

      // 1-0 sequences to reset port, do one of the following:
      // if in UART MIDI mode, abort it, don't reset
      // if in Highspeed mode (not SB16!), abort it, don't reset
      // otherwise reset

      if (DSP.midiuartmode != 0)
	{  // abort UART MIDI mode
	  DSP.midiuartmode = 0;
	  writelog(MIDILOG(4), "DSP UART MIDI mode aborted");
	  return;
	}

      // do the reset
      writelog(WAVELOG(4), "DSP resetting...");

      if (DSP.irqpending != 0)
	{
	  DEV_pic_lower_irq(BX_SB16_IRQ);
	  writelog(WAVELOG(4), "DSP reset: IRQ untriggered");
	}
      if (DSP.dma.mode != 0)
	{
	  writelog(WAVELOG(4), "DSP reset: DMA aborted");
	  DSP.dma.mode = 1;  // no auto init anymore
	  dsp_dmadone();
	}

      DSP.resetport = 0;
      DSP.speaker = 0;
      DSP.irqpending = 0;
      DSP.midiuartmode = 0;
      DSP.prostereo = 0;

      DSP.dma.mode = 0;
      DSP.dma.fifo = 0;
      DSP.dma.output = 0;
      DSP.dma.stereo = 0;
      DSP.dma.issigned = 0;
      DSP.dma.count = 0;
      DSP.dma.highspeed = 0;
      DSP.dma.chunkindex = 0;

      DSP.dataout.reset();    // clear the buffers
      DSP.datain.reset();

      DSP.dataout.put(0xaa);  // acknowledge the reset
    }
  else
    DSP.resetport = value;
}


// dsp_dataread() reads the data port of the DSP

Bit32u bx_sb16_c::dsp_dataread()
{
  Bit8u value = 0xff;

  // if we are in MIDI UART mode, call the mpu401 part instead
  if (DSP.midiuartmode != 0)
    value = mpu_dataread();
  else
    {
      // default behaviour: if none available, return last byte again
      //  if (DSP.dataout.empty() == 0)
      DSP.dataout.get(&value);
    }

  writelog(WAVELOG(4), "DSP Data port read, result = %x", value);

  return(value);
}


// dsp_datawrite() writes a command or data byte to the data port

void bx_sb16_c::dsp_datawrite(Bit32u value)
{
  int bytesneeded;
  Bit8u mode, value8;
  Bit16u length;

  writelog(WAVELOG(4), "DSP Data port write, value %x", value);

  // in high speed mode, any data passed to DSP is a sample
  if (DSP.dma.highspeed != 0)
    {
      dsp_getsamplebyte(value);
      return;
    }

  // route information to mpu401 part if in MIDI UART mode
  if (DSP.midiuartmode != 0)
    {
      mpu_datawrite(value);
      return;
    }

  if (DSP.datain.hascommand() == 1) // already a command pending, add to argument list
    {
      if (DSP.datain.put(value) == 0)
	{
	  writelog(WAVELOG(3), "DSP command buffer overflow for command %02x",
		   DSP.datain.currentcommand());
	}
    }
  else
    // no command pending, set one up
    {
      bytesneeded = 0;   // find out how many arguments the command takes
      switch (value)
	{   // all fallbacks intended!
	case 0x04:
	case 0x0f:
	case 0x10:
	case 0x40:
	case 0x38:
	case 0xe0:
	case 0xe4:
	  bytesneeded = 1;
	  break;
	case 0x05:
	case 0x0e:
	case 0x14:
	case 0x16:
	case 0x17:
	case 0x41:
	case 0x42:
	case 0x48:
	case 0x74:
	case 0x75:
	case 0x76:
	case 0x77:
	case 0x80:
	  bytesneeded = 2;
	  break;

        // 0xb0 ... 0xbf:
	case 0xb0:
        case 0xb1:
        case 0xb2:
        case 0xb3:
        case 0xb4:
        case 0xb5:
        case 0xb6:
        case 0xb7:
        case 0xb8:
        case 0xb9:
        case 0xba:
        case 0xbb:
        case 0xbc:
        case 0xbd:
        case 0xbe:
        case 0xbf:

        // 0xc0 ... 0xcf:
	case 0xc0:
	case 0xc1:
	case 0xc2:
	case 0xc3:
	case 0xc4:
	case 0xc5:
	case 0xc6:
	case 0xc7:
	case 0xc8:
	case 0xc9:
	case 0xca:
	case 0xcb:
	case 0xcc:
	case 0xcd:
	case 0xce:
	case 0xcf:
	  bytesneeded = 3;
	  break;
	}
      DSP.datain.newcommand(value, bytesneeded);
    }

  if (DSP.datain.commanddone() == 1) // command is complete, process it
    {
      writelog(WAVELOG(4), "DSP command %x with %d arg bytes",
	       DSP.datain.currentcommand(), DSP.datain.bytes());

      switch (DSP.datain.currentcommand())
	{
	  // DSP commands - comments are the parameters for
	  // this command, and/or the output

	  // ASP commands (Advanced Signal Processor)
	  // undocumented (?), just from looking what an SB16 does
	case 0x04:
	  DSP.datain.get(&value8);
	  break;

	case 0x05:
	  DSP.datain.get(&value8);
	  DSP.datain.get(&value8);
	  break;

	case 0x0e:
	  DSP.datain.get(&value8);
	  DSP.datain.get(&value8);
	  break;

	case 0x0f:
	  DSP.datain.get(&value8);
	  DSP.dataout.put(0);	     // 0 means no ASP present?
	  break;

	  // direct mode DAC
	case 0x10:
	  // 1: 8bit sample
	  DSP.datain.get(&value8);   // sample is ignored
	  break;

	  // uncomp'd, normal DAC DMA
	case 0x14:
	  // 1,2: lo(length) hi(length)
	  DSP.datain.getw(&length);
	  dsp_dma(0xc0, 0x00, length, 0);
	  break;

	   // 2-bit comp'd, normal DAC DMA, no ref byte
	case 0x16:
	  // 1,2: lo(length) hi(length)
	  DSP.datain.getw(&length);
	  dsp_dma(0xc0, 0x00, length, 2);
	  break;

	  // 2-bit comp'd, normal DAC DMA, 1 ref byte
	case 0x17:
	  // 1,2: lo(length) hi(length)
	  DSP.datain.getw(&length);
	  dsp_dma(0xc0, 0x00, length, 2|8);
	  break;

	  // uncomp'd, auto DAC DMA
	case 0x1c:
	  // none
	  dsp_dma(0xc4, 0x00, DSP.dma.blocklength, 0);
	  break;

	  // 2-bit comp'd, auto DAC DMA, 1 ref byte
	case 0x1f:
	  // none
	  dsp_dma(0xc4, 0x00, DSP.dma.blocklength, 2|8);
	  break;

	  // direct mode ADC
	case 0x20:
	  // o1: 8bit sample
	  DSP.dataout.put(0x80); // put a silence, for now.
	  break;

	  // uncomp'd, normal ADC DMA
	case 0x24:
	  // 1,2: lo(length) hi(length)
	  DSP.datain.getw(&length);
	  dsp_dma(0xc8, 0x00, length, 0);
	  break;

	  // uncomp'd, auto ADC DMA
	case 0x2c:
	  // none
	  dsp_dma(0xcc, 0x00, DSP.dma.blocklength, 0);
	  break;

	  // ? polling mode MIDI input
	case 0x30:
	  break;

	  // ? interrupt mode MIDI input
	case 0x31:
	  break;

	  // 0x34..0x37: UART mode MIDI output
	case 0x34:

	  // UART mode MIDI input/output
	case 0x35:

	  // UART polling mode MIDI IO with time stamp
	case 0x36:

	  // UART interrupt mode MIDI IO with time stamp
	case 0x37:
	  // Fallbacks intended - all set the midi uart mode
	  DSP.midiuartmode = 1;
	  break;

	  // MIDI output
	case 0x38:
	  DSP.datain.get(&value8);
	  // route to mpu401 part
	  mpu_datawrite(value8);
	  break;

	  // set time constant
	case 0x40:
	  // 1: timeconstant
	  DSP.datain.get(&value8);
	  DSP.dma.timeconstant = value8 <<  8;
	  DSP.dma.samplerate = (Bit32u) 256000000L / ((Bit32u) 65536L - (Bit32u) DSP.dma.timeconstant);
	    break;

	  // set samplerate for input
	case 0x41:
	  // (fallback intended)

	  // set samplerate for output
	case 0x42:
	  // 1,2: hi(frq) lo(frq)
	  DSP.datain.getw1( &(DSP.dma.samplerate) );
	  DSP.dma.timeconstant = 65536 - (Bit32u) 256000000 / (Bit32u) DSP.dma.samplerate ;
	  break;

	  // set block length
	case 0x48:
	  // 1,2: lo(blk len) hi(blk len)
	  DSP.datain.getw( &(DSP.dma.blocklength) );
	  break;

	  // 4-bit comp'd, normal DAC DMA, no ref byte
	case 0x74:
	  // 1,2: lo(length) hi(length)
	  DSP.datain.getw(&length);
	  dsp_dma(0xc0, 0x00, length, 4);
	  break;

	  // 4-bit comp'd, normal DAC DMA, 1 ref byte
	case 0x75:
	  // 1,2: lo(length) hi(length)
	  DSP.datain.getw(&length);
	  dsp_dma(0xc0, 0x00, length, 4|8);
	  break;

	  // 3-bit comp'd, normal DAC DMA, no ref byte
	case 0x76:
	  // 1,2: lo(length) hi(length)
	  DSP.datain.getw(&length);
	  dsp_dma(0xc0, 0x00, length, 3);
	  break;

	  // 3-bit comp'd, normal DAC DMA, 1 ref byte
	case 0x77:
	  // 1,2: lo(length) hi(length)
	  DSP.datain.getw(&length);
	  dsp_dma(0xc0, 0x00, length, 3|8);
	  break;

	  // 4-bit comp'd, auto DAC DMA, 1 ref byte
	case 0x7d:
	  // none
	  dsp_dma(0xc4, 0x00, DSP.dma.blocklength, 4|8);
	  break;

	  // 3-bit comp'd, auto DAC DMA, 1 ref byte
	case 0x7f:
	  // none
	  dsp_dma(0xc4, 0x00, DSP.dma.blocklength, 3|8);
	  break;

	  // silence period
	case 0x80:
	  // 1,2: lo(silence) hi(silence) (len in samples)
	  DSP.datain.getw(&length);
	  // only handled for VOC output so far
	  if (bx_options.sb16.Owavemode->get () == 2)
	    {
	      Bit8u temparray[3] = { length & 0xff, length >> 8, DSP.dma.timeconstant >> 8 };
	      writevocblock(3, 3, temparray, 0, NULL);
	    }
	  break;

	   // 8-bit auto DAC DMA, highspeed
	case 0x90:
	  //none
	  dsp_dma(0xc4, 0x00, DSP.dma.blocklength, 16);
	  break;

	  // 8-bit normal DAC DMA, highspeed
	case 0x91:
	  //none
	  dsp_dma(0xc0, 0x00, DSP.dma.blocklength, 16);
	  break;

	  // 8-bit auto ADC DMA, highspeed
	case 0x98:
	  //none
	  dsp_dma(0xcc, 0x00, DSP.dma.blocklength, 16);
	  break;

	case 0x99: // 8-bit normal DMA
	  //none
	  dsp_dma(0xc8, 0x00, DSP.dma.blocklength, 16);
	  break;

	  // switch to mono for SBPro DAC/ADC
	case 0xa0:
	  // none
	  DSP.prostereo = 1;
	  break;

	  // switch to stereo for SBPro DAC/ADC
	case 0xa8:
	  //// none
	  DSP.prostereo = 2;
	  break;

        // 0xb0 ... 0xbf:
        // 16 bit DAC/ADC DMA, general commands
        // fallback intended
        case 0xb0:
        case 0xb1:
        case 0xb2:
        case 0xb3:
        case 0xb4:
        case 0xb5:
        case 0xb6:
        case 0xb7:
        case 0xb8:
        case 0xb9:
        case 0xba:
        case 0xbb:
        case 0xbc:
        case 0xbd:
        case 0xbe:
        case 0xbf:

        // 0xc0 ... 0xcf:
        // 8 bit DAC/ADC DMA, general commands
        case 0xc0:
        case 0xc1:
        case 0xc2:
        case 0xc3:
        case 0xc4:
        case 0xc5:
        case 0xc6:
        case 0xc7:
        case 0xc8:
        case 0xc9:
        case 0xca:
        case 0xcb:
        case 0xcc:
        case 0xcd:
        case 0xce:
        case 0xcf:
	  DSP.datain.get(&mode);
	  DSP.datain.getw(&length);
	  dsp_dma(DSP.datain.currentcommand(), mode, length, 0);
	  break;

	  // pause 8 bit DMA transfer
	case 0xd0:
	  // none
	  if (DSP.dma.mode != 0)
	    dsp_disabledma();
	  break;

	  // speaker on
	case 0xd1:
	  // none
	  DSP.speaker = 1;
	  break;

	  // speaker off
	case 0xd3:
	  // none
	  DSP.speaker = 0;
	  break;

	  // continue 8 bit DMA, see 0xd0
	case 0xd4:
	  // none
	  if (DSP.dma.mode != 0)
	    dsp_enabledma();
	  break;

	   // pause 16 bit DMA
	case 0xd5:
	  // none
	  if (DSP.dma.mode != 0)
	    dsp_disabledma();
	  break;

	  // continue 16 bit DMA, see 0xd5
	case 0xd6:
	  // none
	  if (DSP.dma.mode != 0)
	    dsp_enabledma();
	  break;

	  // read speaker on/off (out ff=on, 00=off)
	case 0xd8:
	  // none, o1: speaker; ff/00
	  DSP.dataout.put( (DSP.speaker == 1)?0xff:0x00);
	  break;

	  // stop 16 bit auto DMA
	case 0xd9:
	  // none
	  if (DSP.dma.mode != 0)
	    {
	      DSP.dma.mode = 1;  // no auto init anymore
	      dsp_dmadone();
	    }
	  break;

	  // stop 8 bit auto DMA
	case 0xda:
	  // none
	  if (DSP.dma.mode != 0)
	    {
	      DSP.dma.mode = 1;  // no auto init anymore
	      dsp_dmadone();
	    }
	  break;

	  // DSP identification
	case 0xe0:
	  DSP.datain.get(&value8);
	  DSP.dataout.put(~value8);
	  break;

	  // get version, out 2 bytes (major, minor)
	case 0xe1:
	  // none, o1/2: version major.minor
	  DSP.dataout.put(4);
	  if (DSP.dataout.put(5) == 0) 
	    {
	      writelog(WAVELOG(3), "DSP version couldn't be written - buffer overflow");
	    }
	  break;

	case 0xe3:
	  // none, output: Copyright string
	  // the Windows driver needs the exact text, otherwise it
	  // won't load. Same for diagnose.exe
	  DSP.dataout.puts("COPYRIGHT (C) CREATIVE TECHNOLOGY LTD, 1992.");
	  DSP.dataout.put(0);    // need extra string end
	  break;

	  // write test register
	case 0xe4: 
	  DSP.datain.get(&DSP.testreg);
	  break;

	  // read test register
	case 0xe8: 
	  DSP.dataout.put(DSP.testreg);
	  break;

	  // Trigger 8-bit IRQ
	case 0xf2:
	  DSP.dataout.put(0xaa);
	  DSP.irqpending = 1;
	  MIXER.reg[0x82] |= 1; // reg 82 shows the kind of IRQ
	  DEV_pic_raise_irq(BX_SB16_IRQ);
	  break;

	  // unknown command
	default: 
	  writelog(WAVELOG(3), "unknown DSP command %x, ignored",
		   DSP.datain.currentcommand());
	  break;
	}
      DSP.datain.clearcommand();
      DSP.datain.flush();
    }
}

// dsp_dma() initiates all kinds of dma transfers
void bx_sb16_c::dsp_dma(Bit8u command, Bit8u mode, Bit16u length, Bit8u comp)
{
  // command: 8bit, 16bit, in/out, single/auto, fifo
  // mode: mono/stereo, signed/unsigned
  //   (for info on command and mode see sound blaster programmer's manual, 
  //    cmds bx and cx)
  // length: number of samples - not number of bytes
  // comp: bit-coded are: type of compression; ref-byte; highspeed
  //       D0..D2: 0=none, 2,3,4 bits ADPCM
  //       D3: ref-byte
  //       D6: highspeed

  writelog( WAVELOG(4), "DMA initialized. Cmd %02x, mode %02x, length %d, comp %d",
	    command, mode, length, comp);

  if ( (command >> 4) == 0xb )  // 0xb? = 16 bit DMA
    {
    DSP.dma.bits = 16;
    DSP.dma.bps = 2;
    }
  else                          // 0xc? = 8 bit DMA
    {
    DSP.dma.bits = 8;
    DSP.dma.bps = 1;
    }

  // Prevent division by zero in some instances
  if ( DSP.dma.samplerate == 0 )
    DSP.dma.samplerate= 10752;
  command &= 0x0f;
  DSP.dma.output = 1 - (command >> 3);       // 1=output, 0=input
  DSP.dma.mode = 1 + ( (command >> 2) & 1);  // 0=none, 1=normal, 2=auto
  DSP.dma.fifo = (command >> 1) & 1;         // ? not sure what this is

  DSP.dma.stereo = (mode >> 5) & 1;

  if (DSP.dma.stereo != 0)
    DSP.dma.bps *= 2;

  DSP.dma.blocklength = length;
  DSP.dma.issigned = (mode >> 4) & 1;
  DSP.dma.count = (DSP.dma.blocklength + 1) * DSP.dma.bps - 1;
  DSP.dma.highspeed = (comp >> 4) & 1;

  DSP.dma.chunkindex = 0;
  DSP.dma.chunkcount = 0;

  Bit32u sampledatarate = (Bit32u) DSP.dma.samplerate * (Bit32u) DSP.dma.bps;
  DSP.dma.timer = (Bit32u) bx_options.sb16.Odmatimer->get () / sampledatarate;

  writelog( WAVELOG(5), "DMA is %db, %dHz, %s, %s, mode %d, %s, %s, %d bps, %d us/b",
	    DSP.dma.bits, DSP.dma.samplerate, (DSP.dma.stereo != 0)?"stereo":"mono",
	    (DSP.dma.output == 1)?"output":"input", DSP.dma.mode,
	    (DSP.dma.issigned == 1)?"signed":"unsigned",
	    (DSP.dma.highspeed == 1)?"highspeed":"normal speed",
	    sampledatarate, DSP.dma.timer);

  DSP.dma.format = DSP.dma.issigned | ( (comp & 7) << 1) | ( (comp & 8) << 4);

	// write the output to the device/file
  if (DSP.dma.output == 1)
    {
      if (bx_options.sb16.Owavemode->get () == 1)
	{
	  if (DSP.outputinit == 0)
	    {
	      if (BX_SB16_OUTPUT->openwaveoutput(bx_options.sb16.Owavefile->getptr ()) != BX_SOUND_OUTPUT_OK)
		{
		  bx_options.sb16.Owavemode->set (0);
		  writelog( WAVELOG(2), "Error: Could not open wave output device.");
		}
	      else
		DSP.outputinit = 1;
	    }

	  if (DSP.outputinit == 1)
	    BX_SB16_OUTPUT->startwaveplayback(DSP.dma.samplerate, DSP.dma.bits, DSP.dma.stereo, DSP.dma.format);
	}
    }

  dsp_enabledma();
}


// dsp_enabledma(): Start the DMA timer and thus the transfer

void bx_sb16_c::dsp_enabledma()
{
  bx_pc_system.activate_timer( DSP.timer_handle, DSP.dma.timer, 1 );
}

// dsp_disabledma(): Stop the DMA timer and thus the transfer, but don't abort it

void bx_sb16_c::dsp_disabledma()
{
  bx_pc_system.deactivate_timer( DSP.timer_handle );
}


// dsp_bufferstatus() checks if the DSP is ready for data/commands

Bit32u bx_sb16_c::dsp_bufferstatus()
{
  Bit32u result = 0x7f;

         // MSB set -> not ready for commands
  if (DSP.datain.full() == 1) result |= 0x80;

  writelog(WAVELOG(4), "DSP Buffer status read, result %x", result);

  return(result);
}


// dsp_status() checks if the DSP is ready to send data

Bit32u bx_sb16_c::dsp_status()
{
  Bit32u result = 0x7f;

  // read might be to acknowledge IRQ
  if ( DSP.irqpending != 0 )
    {
      MIXER.reg[0x82] &= (~0x01);
      writelog( WAVELOG(4), "8-bit DMA or SBMIDI IRQ acknowledged");
      if (MIXER.reg[0x82] == 0) {
        DSP.irqpending = 0;
        DEV_pic_lower_irq(BX_SB16_IRQ);
      }
    }

  // if buffer is not empty, there is data to be read
  if (DSP.dataout.empty() == 0) result |= 0x80;

  writelog(WAVELOG(4), "DSP output status read, result %x", result);

  return(result);

}

// dsp_irq16ack() notifies that the 16bit DMA IRQ has been acknowledged
Bit32u bx_sb16_c::dsp_irq16ack()
{
  Bit32u result = 0xff;

  if ( DSP.irqpending != 0 )
    {
      MIXER.reg[0x82] &= (~0x02);
      if (MIXER.reg[0x82] == 0) {
        DSP.irqpending = 0;
        DEV_pic_lower_irq(BX_SB16_IRQ);
      }
      writelog( WAVELOG(4), "16-bit DMA IRQ acknowledged");
    }
  else
    writelog( WAVELOG(3), "16-bit DMA IRQ acknowledged but not active!");

  return result;
}


  // the DMA handlers


// highlevel input and output handlers - rerouting to/from file,device

// get a wave packet from the input device (not supported yet)
void bx_sb16_c::dsp_getwavepacket()
{
  writelog( WAVELOG(3), "DMA reads not supported. Returning silence.");


  // fill the buffer with silence. Watch for 16bit transfer and signed/unsigned

  // these are the two different bytes in 16bit transfer (both the same for 8bit)
  Bit8u byteA, byteB;

  byteA = 0x00;  // compatible with all signed transfers
  byteB = 0x00;
  int i;

  if (DSP.dma.issigned == 0)
    byteB = 0x80;

  if (DSP.dma.bits == 8)
    byteA = byteB;

  for (i = 0; i < BX_SOUND_OUTPUT_WAVEPACKETSIZE; i++)
    DSP.dma.chunk[i] = ( (i & 1) == 0) ? byteA : byteB;

  DSP.dma.chunkcount = BX_SOUND_OUTPUT_WAVEPACKETSIZE;
  DSP.dma.chunkindex = 0;
}

// write a wave packet to the output device
void bx_sb16_c::dsp_sendwavepacket()
{
  switch (bx_options.sb16.Owavemode->get ())
    {
    case 1:
      BX_SB16_OUTPUT->sendwavepacket(DSP.dma.chunkindex, DSP.dma.chunk);
      break;
    case 3:
      fwrite(DSP.dma.chunk, 1, DSP.dma.chunkindex, WAVEDATA);
      break;
    case 2:
      Bit8u temparray[12] =
	{DSP.dma.samplerate & 0xff, DSP.dma.samplerate >> 8, 0, 0,
	 DSP.dma.bits, DSP.dma.stereo + 1, 0, 0, 0, 0, 0, 0 };
      switch ( (DSP.dma.format >> 1) & 7)
	{
	case 2:
	  temparray[7] = 3;
	  break;
	case 3:
	  temparray[7] = 2;
	  break;
	case 4:
	  temparray[7] = 1;
	  break;
	}
      if (DSP.dma.bits == 16)
	temparray[7] = 4;

      writevocblock(9, 12, temparray, DSP.dma.chunkindex, DSP.dma.chunk);
      break;
    }

  DSP.dma.chunkindex = 0;
}

// put a sample byte into the output buffer
void bx_sb16_c::dsp_getsamplebyte(Bit8u value)
{
  if (DSP.dma.chunkindex < BX_SOUND_OUTPUT_WAVEPACKETSIZE)
    DSP.dma.chunk[DSP.dma.chunkindex++] = value;

  if (DSP.dma.chunkindex >= BX_SOUND_OUTPUT_WAVEPACKETSIZE)
    dsp_sendwavepacket();
}

// read a sample byte from the input buffer
Bit8u bx_sb16_c::dsp_putsamplebyte()
{
  if (DSP.dma.chunkindex >= DSP.dma.chunkcount)
    dsp_getwavepacket();

  return DSP.dma.chunk[DSP.dma.chunkindex++];
}

// called when the last byte of a DMA transfer has been received/sent
void bx_sb16_c::dsp_dmadone()
{
  writelog( WAVELOG(4), "DMA transfer done, triggering IRQ");

  if ( (DSP.dma.output == 1) && (DSP.dma.mode != 2) )
    {
      dsp_sendwavepacket();  // flush the output

      if (bx_options.sb16.Owavemode->get () == 1)
	{
	  if (DSP.dma.mode != 2)
	    BX_SB16_OUTPUT->stopwaveplayback();  // don't stop if Auto-DMA
	}

      else if (bx_options.sb16.Owavemode->get () == 2)
	fflush(WAVEDATA);
    }

    // generate the appropriate IRQ
 if (DSP.dma.bits == 8)
    MIXER.reg[0x82] |= 1;
 else
    MIXER.reg[0x82] |= 2;

  DEV_pic_raise_irq(BX_SB16_IRQ);
  DSP.irqpending = 1;

  //if auto-DMA, reinitialize
  if (DSP.dma.mode == 2)
    {
      DSP.dma.count = (DSP.dma.blocklength + 1) * DSP.dma.bps - 1;
      writelog( WAVELOG(4), "auto-DMA reinitializing to length %d", DSP.dma.count);
    }
  else
    {
      DSP.dma.mode = 0;
      dsp_disabledma();
    }
}

// now the actual transfer routines, called by the DMA controller
// note that read = from application to soundcard (output),
// and write = from soundcard to application (input)
void bx_sb16_c::dma_read8(Bit8u *data_byte)
{
  DEV_dma_set_drq(BX_SB16_DMAL, 0);  // the timer will raise it again

  if (DSP.dma.count % 100 == 0) // otherwise it's just too many lines of log
    writelog( WAVELOG(5), "Received 8-bit DMA %2x, %d remaining ",
	      *data_byte, DSP.dma.count);
  DSP.dma.count--;

  dsp_getsamplebyte(*data_byte);

  if (DSP.dma.count == 0xffff) // last byte received
    dsp_dmadone();
}

void bx_sb16_c::dma_write8(Bit8u *data_byte)
{
  DEV_dma_set_drq(BX_SB16_DMAL, 0);  // the timer will raise it again

  DSP.dma.count--;

  *data_byte = dsp_putsamplebyte();

  if (DSP.dma.count % 100 == 0) // otherwise it's just too many lines of log
    writelog( WAVELOG(5), "Sent 8-bit DMA %2x, %d remaining ",
	      *data_byte, DSP.dma.count);

  if (DSP.dma.count == 0xffff) // last byte sent
    dsp_dmadone();
}

void bx_sb16_c::dma_read16(Bit16u *data_word)
{
  DEV_dma_set_drq(BX_SB16_DMAH, 0);  // the timer will raise it again

  if (DSP.dma.count % 100 == 0) // otherwise it's just too many lines of log
    writelog( WAVELOG(5), "Received 16-bit DMA %4x, %d remaining ",
	      *data_word, DSP.dma.count);

  DSP.dma.count--;

  dsp_getsamplebyte(*data_word & 0xff);
  dsp_getsamplebyte(*data_word >> 8);

  if (DSP.dma.count == 0xffff) // last byte received
    dsp_dmadone();
}

void bx_sb16_c::dma_write16(Bit16u *data_word)
{
  Bit8u byte1, byte2;

  DEV_dma_set_drq(BX_SB16_DMAH, 0);  // the timer will raise it again

  DSP.dma.count--;

  byte1 = dsp_putsamplebyte();
  byte2 = dsp_putsamplebyte();

  // all input is in little endian
  *data_word = byte1 | (byte2 << 8);

  if (DSP.dma.count % 100 == 0) // otherwise it's just too many lines of log
    writelog( WAVELOG(5), "Sent 16-bit DMA %4x, %d remaining ",
	      *data_word, DSP.dma.count);

  if (DSP.dma.count == 0xffff) // last byte sent
    dsp_dmadone();
}


  // the mixer, supported type is CT1745 (as in an SB16)
void bx_sb16_c::mixer_writedata(Bit32u value)
{
  int i;

  // do some action depending on what register was written
  switch (MIXER.regindex) 
    {
    case 0:  // initialize mixer

      writelog(BOTHLOG(4), "Initializing mixer...");
      MIXER.reg[0x04] = 0xcc;
      MIXER.reg[0x0a] = 0x00;
      MIXER.reg[0x22] = 0xcc;
      MIXER.reg[0x26] = 0xcc;
      MIXER.reg[0x28] = 0x00;
      MIXER.reg[0x2e] = 0x00;
      MIXER.reg[0x3c] = 0x1f;
      MIXER.reg[0x3d] = 0x15;
      MIXER.reg[0x3e] = 0x0b;
      for (i=0x30; i<=0x35; i++)
	MIXER.reg[i] = 0xc0;
      for (i=0x36; i<=0x3b; i++)
	MIXER.reg[i] = 0x00;
      for (i=0x3f; i<=0x43; i++)
	MIXER.reg[i] = 0x00;
      for (i=0x44; i<=0x47; i++)
	MIXER.reg[i] = 0x80;

      MIXER.regindex = 0;   // next mixer register read is register 0
      return;
      break;

    case 0x04: // DAC level
      MIXER.reg[0x32] = (value & 0xf0) | 0x08;
      MIXER.reg[0x33] = ((value & 0x0f) << 4) | 0x08;
      break;

    case 0x0a: // microphone level
      MIXER.reg[0x3a] = (value << 5) | 0x18;
      break;

    case 0x22: // master volume
      MIXER.reg[0x30] = (value & 0xf0) | 0x08;
      MIXER.reg[0x31] = ((value & 0x0f) << 4) | 0x08;
      break;

    case 0x26: // FM level
      MIXER.reg[0x34] = (value & 0xf0) | 0x08;
      MIXER.reg[0x35] = ((value & 0x0f) << 4) | 0x08;
      break;

    case 0x28: // CD audio level
      MIXER.reg[0x36] = (value & 0xf0) | 0x08;
      MIXER.reg[0x37] = ((value & 0x0f) << 4) | 0x08;
      break;

    case 0x2e: // line in level
      MIXER.reg[0x38] = (value & 0xf0) | 0x08;
      MIXER.reg[0x39] = ((value & 0x0f) << 4) | 0x08;
      break;

    case 0x30: // master volume left
      MIXER.reg[0x22] &= 0x0f;
      MIXER.reg[0x22] |= (value & 0xf0);
      break;

    case 0x31: // master volume right
      MIXER.reg[0x22] &= 0xf0;
      MIXER.reg[0x22] |= (value >> 4);
      break;

    case 0x32: // DAC level left
      MIXER.reg[0x04] &= 0x0f;
      MIXER.reg[0x04] |= (value & 0xf0);
      break;

    case 0x33: // DAC level right
      MIXER.reg[0x04] &= 0xf0;
      MIXER.reg[0x04] |= (value >> 4);
      break;

    case 0x34: // FM level left
      MIXER.reg[0x26] &= 0x0f;
      MIXER.reg[0x26] |= (value & 0xf0);
      break;

    case 0x35: // FM level right
      MIXER.reg[0x26] &= 0xf0;
      MIXER.reg[0x26] |= (value >> 4);
      break;

    case 0x36: // CD audio level left
      MIXER.reg[0x28] &= 0x0f;
      MIXER.reg[0x28] |= (value & 0xf0);
      break;

    case 0x37: // CD audio level right
      MIXER.reg[0x28] &= 0xf0;
      MIXER.reg[0x28] |= (value >> 4);
      break;

    case 0x38: // line in level left
      MIXER.reg[0x2e] &= 0x0f;
      MIXER.reg[0x2e] |= (value & 0xf0);
      break;

    case 0x39: // line in level right
      MIXER.reg[0x2e] &= 0xf0;
      MIXER.reg[0x2e] |= (value >> 4);
      break;

    case 0x3a: // microphone level
      MIXER.reg[0x0a] = (value >> 5);
      break;

    case 0x3b:
    case 0x3c:
    case 0x3d:
    case 0x3e:
    case 0x3f:
    case 0x40:
    case 0x41:
    case 0x42:
    case 0x43:
    case 0x44:
    case 0x45:
    case 0x46:
    case 0x47:
      break;

    case 0x80: // IRQ mask
    case 0x81: // DMA mask
      MIXER.reg[MIXER.regindex] = value;
      set_irq_dma(); // both 0x80 and 0x81 handled
      return;
      break;

    default: // ignore read-only registers
      return;
    }
  // store the value
  MIXER.reg[MIXER.regindex] = value;

  writelog(BOTHLOG(4), "mixer register %02x set to %02x",
	   MIXER.regindex, MIXER.reg[MIXER.regindex]);
}

Bit32u bx_sb16_c::mixer_readdata()
{
  writelog(BOTHLOG(4), "read from mixer register %02x returns %02x",
	   MIXER.regindex, MIXER.reg[MIXER.regindex]);
  return(MIXER.reg[MIXER.regindex]);
}

void bx_sb16_c::mixer_writeregister(Bit32u value)
{
  MIXER.regindex = value;
}


void bx_sb16_c::set_irq_dma()
{
  static bx_bool isInitialized=0;
  int newirq;
  int oldDMA8, oldDMA16;

  // set the IRQ according to the value in mixer register 0x80
  switch (MIXER.reg[0x80]) 
    {
    case 1:
      newirq = 2;
      break;
    case 2:
      newirq = 5;
      break;
    case 4:
      newirq = 7;
      break;
    case 8:
      newirq = 10;
      break;
    default:
      newirq = 5;
      writelog(BOTHLOG(3), "Bad value %02x in mixer register 0x80. IRQ set to %d", 
	       MIXER.reg[0x80], newirq);
      MIXER.reg[0x80] = 2;
    }
  if (newirq != BX_SB16_IRQ)   // a different IRQ was set
    {
      if (BX_SB16_IRQ > 0)
	DEV_unregister_irq(BX_SB16_IRQ, "SB16");

      BX_SB16_IRQ = newirq;
      DEV_register_irq(BX_SB16_IRQ, "SB16");
    }

  // set the 8 bit DMA
  oldDMA8=BX_SB16_DMAL;
  switch (MIXER.reg[0x81] & 0x0f)
    {
    case 1: 
      BX_SB16_DMAL = 0;
      break;
    case 2:
      BX_SB16_DMAL = 1;
      break;
    case 8:
      BX_SB16_DMAL = 3;
      break;
    default:
      BX_SB16_DMAL = 1;
      writelog(BOTHLOG(3), "Bad value %02x in mixer register 0x81. DMA8 set to %d",
	       MIXER.reg[0x81], BX_SB16_DMAL);
      MIXER.reg[0x81] &= (~0x0f);
      MIXER.reg[0x81] |= (1 << BX_SB16_DMAL);
    }

  // Unregister the previous DMA if initialized
  if ( (isInitialized) && (oldDMA8 != BX_SB16_DMAL) ) {
    DEV_dma_unregister_channel(oldDMA8);
  }

  // And register the new 8bits DMA Channel
  if ( (!isInitialized) || (oldDMA8 != BX_SB16_DMAL) ) {
    DEV_dma_register_8bit_channel(BX_SB16_DMAL, dma_read8, dma_write8, "SB16");
  }

  // and the 16 bit DMA
  oldDMA16=BX_SB16_DMAH;
  switch (MIXER.reg[0x81] >> 4)
    {
    case 0:
      BX_SB16_DMAH = 0;    // no 16-bit DMA
      break;
    case 2:
      BX_SB16_DMAH = 5;
      break;
    case 4:
      BX_SB16_DMAH = 6;
      break;
    case 8:
      BX_SB16_DMAH = 7;
      break;
    default:
      BX_SB16_DMAH = 0;
      writelog(BOTHLOG(3), "Bad value %02x in mixer register 0x81. DMA16 set to %d",
	       MIXER.reg[0x81], BX_SB16_DMAH);
      MIXER.reg[0x81] &= (~0xf0);
      // MIXER.reg[0x81] |= (1 << BX_SB16_DMAH);
      // no default 16 bit channel!
    }

  // Unregister the previous DMA if initialized
  if ( (isInitialized) && (oldDMA16 != 0) && (oldDMA16 != BX_SB16_DMAH) ) {
    DEV_dma_unregister_channel(oldDMA16);
  }

  // And register the new 16bits DMA Channel
  if ( (BX_SB16_DMAH != 0) && (oldDMA16 != BX_SB16_DMAH) ) {
    DEV_dma_register_16bit_channel(BX_SB16_DMAH, dma_read16, dma_write16, "SB16");
  }

  // If not already initialized
  if(!isInitialized) {
    isInitialized=1;
  }

  writelog(BOTHLOG(4), "Resources set to I%d D%d H%d", 
	   BX_SB16_IRQ, BX_SB16_DMAL, BX_SB16_DMAH);

}


  // now the MPU 401 part


// the MPU 401 status port shows if input or output are ready
// Note that the bits are inverse to their meaning

Bit32u bx_sb16_c::mpu_status()
{
  Bit32u result = 0;

  if ( (MPU.datain.full() == 1) ||
       ( (bx_options.sb16.Omidimode->get () == 1) &&
	 (BX_SB16_OUTPUT->midiready() == BX_SOUND_OUTPUT_ERR) ) )
    result |= 0x40;	// output not ready
  if (MPU.dataout.empty() == 1)
    result |= 0x80;	// no input available

  writelog(MIDILOG(4), "MPU status port, result %02x", result);

  return(result);
}


// the MPU 401 command port

void bx_sb16_c::mpu_command(Bit32u value)
{
  int i;
  int bytesneeded;

  if (MPU.cmd.hascommand() == 1) // already a command pending, abort that one
    {
      if ( (MPU.cmd.currentcommand() != value) ||
	   (MPU.cmd.commanddone() == 0) )
	     // it's a different command, or the old one isn't done yet, abort it
	{
	  MPU.cmd.clearcommand();
	  MPU.cmd.flush();
	}
      // if it's the same one, and we just completed the argument list,
      // we leave it as it is and process it here
    }

  if (MPU.cmd.hascommand() == 0)  // no command pending, set one up
    {
      bytesneeded = 0;
      if ( (value >> 4) == 14) bytesneeded = 1;
      MPU.cmd.newcommand(value, bytesneeded);
    }

  if (MPU.cmd.commanddone() == 1) // command is complete, process it
    {
      switch (MPU.cmd.currentcommand())
	{
	case 0x3f:
	  writelog(MIDILOG(5), "MPU cmd: UART mode on");
	  MPU.uartmode=1;
	  MPU.irqpending=1;
	  MPU.singlecommand=0;
	  if (BX_SB16_IRQMPU != -1)
	    {
	      MIXER.reg[0x82] |= 4;
	      DEV_pic_raise_irq(BX_SB16_IRQMPU);
	    }
	  break;

        case 0xff:
	  writelog(MIDILOG(4), "MPU cmd: Master reset of device");
	  MPU.uartmode=MPU.forceuartmode;
	  MPU.singlecommand=0;
	  for (i=0; i<16; i++)
	    {
	      MPU.banklsb[i] = 0;
	      MPU.bankmsb[i] = 0;
	      MPU.program[i] = 0;
	    }
	  MPU.cmd.reset();
	  MPU.dataout.reset();
	  MPU.datain.reset();
	  MPU.midicmd.reset();

	  /*
	  if (BX_SB16_IRQ != -1)
	    {
	      MIXER.reg[0x82] |= 4;
	      BX_SB16_THIS devices->pic->trigger_irq(BX_SB16_IRQ);
	    }
	  */
	  break;
	case 0xd0:  // d0 and df: prefix for midi command
	case 0xdf:  // like uart mode, but only a single command
	  MPU.singlecommand = 1;
	  writelog(MIDILOG(4), "MPU: prefix %02x received", 
		   MPU.cmd.currentcommand());
	  break;
	default:
	  writelog(MIDILOG(3), "MPU cmd: unknown command %02x ignored",
		   MPU.cmd.currentcommand());
	  break;
	}

      // Need to put an MPU_ACK into the data port if command successful
      // we'll fake it even if we didn't process the command, so as to 
      // allow detection of the MPU 401.
      if (MPU.dataout.put(0xfe) == 0)
	writelog(MIDILOG(3), "MPU_ACK error - output buffer full");
      MPU.cmd.clearcommand(); // clear the command from the buffer
    }
}


// MPU 401 data port/read: contains an MPU_ACK after receiving a command
// Will contain other data as well when other than UART mode is supported

Bit32u bx_sb16_c::mpu_dataread()
{
    Bit8u res8bit;
    Bit32u result;

    // also acknowledge IRQ?
    if ( MPU.irqpending != 0 )
      {
	MPU.irqpending = 0;
	MIXER.reg[0x82] &= (~4);
	if (MIXER.reg[0x82] == 0)
	  DEV_pic_lower_irq(BX_SB16_IRQMPU);
	writelog(MIDILOG(4), "MPU IRQ acknowledged");
      }

    if ( MPU.dataout.get(&res8bit) == 0)
      {
	writelog(MIDILOG(3), "MPU data port not ready - no data in buffer");
	result = 0xff;
      }
    else
      result = (Bit32u) res8bit;

    writelog(MIDILOG(4), "MPU data port, result %02x", result);

    return(result);
}


// MPU 401 data port/write: This is where the midi stream comes from,
// as well as arguments to any pending command

void bx_sb16_c::mpu_datawrite(Bit32u value)
{
  writelog(MIDILOG(4), "write to MPU data port, value %02x", value);

  if (MPU.cmd.hascommand() == 1)
    { // there is a command pending, add arguments to it
      if (MPU.cmd.put(value) == 0)
	writelog(MIDILOG(3), "MPU Command arguments too long - buffer full");
      if (MPU.cmd.commanddone() == 1)
	BX_SB16_THIS mpu_command(MPU.cmd.currentcommand());
    }
  else if ( (MPU.uartmode == 0) && (MPU.singlecommand == 0) )
    {
      // Hm? No UART mode, but still data? Maybe should send it
      // to the command port...  Only SBMPU401.EXE does this...
      writelog(MIDILOG(4), "MPU Data %02x received but no UART mode. Assuming it's a command.", value);
      mpu_command(value);
      return;
    }
  else // no MPU command pending, in UART mode, this has to be midi data
    mpu_mididata(value);
  return;
}

// A byte of midi data has been received
void bx_sb16_c::mpu_mididata(Bit32u value)
{
  // first, find out if it is a midi command or midi data
  bx_bool ismidicommand = 0;
  if (value >= 0x80)
    {  // bit 8 usually denotes a midi command...
      ismidicommand = 1;
      if ( (value == 0xf7) && (MPU.midicmd.currentcommand() == 0xf0) )
	// ...except if it is a continuing SYSEX message, then it just
	// denotes the end of a SYSEX chunk, not the start of a message
	{
	  ismidicommand = 0;     // first, it's not a command
	  MPU.midicmd.newcommand(MPU.midicmd.currentcommand(), 
				 MPU.midicmd.bytes());
	  // Then, set needed bytes to current buffer
	  // because we didn't know the length before
	}
    }

  if (ismidicommand == 1)
    {  // this is a command, check if an old one is pending
      if (MPU.midicmd.hascommand() == 1)
	{
	  writelog(MIDILOG(3), "Midi command %02x incomplete, has %d of %d bytes.",
		   MPU.midicmd.currentcommand(), MPU.midicmd.bytes(), 
		   MPU.midicmd.commandbytes() );
	  // write as much as we can. Should we do this?
	  processmidicommand(0);
	  // clear the pending command
	  MPU.midicmd.clearcommand();
	  MPU.midicmd.flush();
	}

      // find the number of arguments to the command
      static const signed eventlength[] = { 2, 2, 2, 2, 1, 1, 2, 255};
      // note - length 255 commands have unknown length
      MPU.midicmd.newcommand(value, eventlength[ (value & 0x70) >> 4 ]);
    }
  else  // no command, just arguments to the old command
    {
      if (MPU.midicmd.hascommand() == 0)
	{  // no command pending, ignore the data
	  writelog(MIDILOG(3), "Midi data %02x received, but no command pending?", value);
	  return;
	}

      // just some data to the command
      if (MPU.midicmd.put(value) == 0)
	writelog(MIDILOG(3), "Midi buffer overflow!");
      if (MPU.midicmd.commanddone() == 1)
	{
	  // the command is complete, process it
	  writelog(MIDILOG(5), "Midi command %02x complete, has %d bytes.", 
		   MPU.midicmd.currentcommand(), MPU.midicmd.bytes() );
	  processmidicommand(0);
	  // and remove the command from the buffer
	  MPU.midicmd.clearcommand();
	  MPU.midicmd.flush();
	}
    }
}



// The emulator port/read: See if commands were successful

Bit32u bx_sb16_c::emul_read()
{
    Bit8u res8bit;
    Bit32u result;

    if ( EMUL.datain.get(&res8bit) == 0)
      {
	writelog(3, "emulator port not ready - no data in buffer");
	result = 0x00;
      }
    else
      result = (Bit32u) res8bit;

    writelog(4, "emulator port, result %02x", result);

    return(result);
}

// Emulator port/write: Changing instrument mapping etc.

void bx_sb16_c::emul_write(Bit32u value)
{
  Bit8u value8;

  writelog(4, "write to emulator port, value %02x", value);

  if (EMUL.dataout.hascommand() == 0)   // no command pending, set it up
    {
      static signed char cmdlength[] = 
          { 0, 0, 4, 2, 6, 1, 0, 0, 1, 1, 0, 1};
      if (value > 11)
	{
	  writelog(3, "emulator command %02x unknown, ignored.", value);
	  return;
	}	
      writelog(5, "emulator command %02x, needs %d arguments",
	       value, cmdlength[value]);
      EMUL.dataout.newcommand(value, cmdlength[value]);
      EMUL.datain.reset();
      EMUL.datain.put(0xfe);
    }
  else
    EMUL.dataout.put(value);    // otherwise just add data

  if (EMUL.dataout.commanddone() == 1)
    { // process the command
      writelog(4, "executing emulator command %02x with %d arguments",
	       EMUL.dataout.currentcommand(), EMUL.dataout.bytes());
      switch (EMUL.dataout.currentcommand())
	{
	case 0: // reinit of emulator
	  writelog(4, "Emulator reinitialized");

	  EMUL.remaps = 0;

	  EMUL.dataout.reset();
	  EMUL.datain.reset();
	  EMUL.datain.put(0xfe);
	  break;
	case 1: // dummy command to reset state of emulator port
	        // just give a few times to end any commands
	  break;
	case 2: // map bank
	  if (EMUL.remaps >= BX_SB16_PATCHTABLESIZE) break;
	  EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].oldbankmsb));
	  EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].oldbanklsb));
	  EMUL.remaplist[EMUL.remaps].oldprogch = 0xff;
	  EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].newbankmsb));
	  EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].newbanklsb));
	  EMUL.remaplist[EMUL.remaps].newprogch = 0xff;
	  EMUL.datain.put(4);
	  writelog(4, "Map bank command received, from %d %d to %d %d",
		   EMUL.remaplist[EMUL.remaps].oldbankmsb,
		   EMUL.remaplist[EMUL.remaps].oldbanklsb,
		   EMUL.remaplist[EMUL.remaps].newbankmsb,
		   EMUL.remaplist[EMUL.remaps].newbanklsb);
	  EMUL.remaps++;
	  break;
	case 3: // map program change
	  if (EMUL.remaps >= BX_SB16_PATCHTABLESIZE) break;
	  EMUL.remaplist[EMUL.remaps].oldbankmsb = 0xff;
	  EMUL.remaplist[EMUL.remaps].oldbanklsb = 0xff;
	  EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].oldprogch));
	  EMUL.remaplist[EMUL.remaps].newbankmsb = 0xff;
	  EMUL.remaplist[EMUL.remaps].newbanklsb = 0xff;
	  EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].newprogch));
	  EMUL.datain.put(2);
	  writelog(4, "Map program change received, from %d to %d",
		   EMUL.remaplist[EMUL.remaps].oldprogch,
		   EMUL.remaplist[EMUL.remaps].newprogch);
	  EMUL.remaps++;
	  break;
	case 4: // map bank and program change
	  if (EMUL.remaps >= BX_SB16_PATCHTABLESIZE) break;
	  EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].oldbankmsb));
	  EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].oldbanklsb));
	  EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].oldprogch));
	  EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].newbankmsb));
	  EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].newbanklsb));
	  EMUL.dataout.get (& (EMUL.remaplist[EMUL.remaps].newprogch));
	  EMUL.datain.put(6);
	  writelog(4, "Complete remap received, from %d %d %d to %d %d %d",
		   EMUL.remaplist[EMUL.remaps].oldbankmsb,
		   EMUL.remaplist[EMUL.remaps].oldbanklsb,
		   EMUL.remaplist[EMUL.remaps].oldprogch,
		   EMUL.remaplist[EMUL.remaps].newbankmsb,
		   EMUL.remaplist[EMUL.remaps].newbanklsb,
		   EMUL.remaplist[EMUL.remaps].newprogch);

	  EMUL.remaps++;
	  break;
	case 5: EMUL.dataout.get(&value8); // dump emulator state
	  switch (value8)
	    {
	    case 0: 
	      EMUL.datain.puts("SB16 Emulator for Bochs\n");
	      break;
	    case 1: 
	      EMUL.datain.puts("UART mode=%d (force=%d)\n", 
			       MPU.uartmode, MPU.forceuartmode);
	      break;
	    case 2: 
	      EMUL.datain.puts("timer=%d\n", MPU.current_timer);
	      break;
	    case 3: 
	      EMUL.datain.puts("%d remappings active\n", EMUL.remaps);
	      break;
	    case 4: 
	      EMUL.datain.puts("Resources are A%3x I%d D%d H%d T%d P%3x; Adlib at %3x\n",
			       BX_SB16_IO, BX_SB16_IRQ, BX_SB16_DMAL,
			       BX_SB16_DMAH, 6, BX_SB16_IOMPU, BX_SB16_IOADLIB);
	      break;
	    case 5:
	      EMUL.datain.puts("Current OPL2/3 mode: %s",
			       // ok, I admit that this is a bit ugly...
			       (OPL.mode == single)?"single OPL2 (OPL3 disabled)\n":
			       (OPL.mode == adlib)?"single OPL2 (no OPL3)\n":
			       (OPL.mode == dual)?"double OPL2\n":
			       (OPL.mode == opl3)?"OPL3\n":
			       "unknown");
	      break;
	    default: 
	      EMUL.datain.puts("no info. Only slots 0..5 have values.\n");
	      break;
	    }
	  break;
	case 6: // close midi and wave files and/or output
	  if ( (bx_options.sb16.Omidimode->get () == 2) ||
	       (bx_options.sb16.Omidimode->get () == 3) )
	    {
	      if (bx_options.sb16.Omidimode->get () == 2)
		finishmidifile();
	      fclose(MIDIDATA);
	    }
	  else if (bx_options.sb16.Omidimode->get () == 1)
	    BX_SB16_OUTPUT->closemidioutput();
	  bx_options.sb16.Omidimode->set (0);
	  
	  if ( (bx_options.sb16.Owavemode->get () == 2) ||
	       (bx_options.sb16.Owavemode->get () == 3) )
	    {
	      if (bx_options.sb16.Owavemode->get () == 2)
		finishvocfile();
	      fclose(WAVEDATA);
	    }
	  else
	    BX_SB16_OUTPUT->closewaveoutput();
	  bx_options.sb16.Owavemode->set (0);

	  break;
	case 7: // clear bank/program mappings
	  EMUL.remaps = 0;
	  writelog(4, "Bank/program mappings cleared.");
	  break;
	case 8: // set force uart mode on/off
	  EMUL.dataout.get(&value8);
	  MPU.forceuartmode = value8;
	  if (value8 != 0)
	    MPU.uartmode = MPU.forceuartmode;
	  writelog(4, "Force UART mode = %d", MPU.forceuartmode);
	  break;
	case 9: // enter specific OPL2/3 mode
	  EMUL.dataout.get(&value8);
	  writelog(4, "Entering OPL2/3 mode %d", value8);
	  opl_entermode( (bx_sb16_fm_mode) value8);
	  break;
	case 10: // check emulator present
	  EMUL.datain.put(0x55);
	  break;
	case 11: // send data to midi device
	  EMUL.dataout.get(&value8);
	  mpu_mididata(value8);
	}
      EMUL.dataout.clearcommand();
      EMUL.dataout.flush();
    }
}

// and finally the OPL (FM emulation) part

// select a new operational mode for the FM part
// this also serves as reset for the OPL chip
void bx_sb16_c::opl_entermode(bx_sb16_fm_mode newmode)
{
  int i, j;

  // do nothing if the mode is unchanged
  if ( OPL.mode == newmode)
    return;

  // if the old mode was 0, and the new mode is 3, then
  // no reset is necessary, just set the flag
  if ( (OPL.mode == single) && (newmode == opl3) )
    {
      writelog( MIDILOG(4), "OPL3 mode enabled");
      OPL.mode = newmode;
      return;
    }

  writelog( MIDILOG(4), "Switching to OPL mode %d from %d", newmode, OPL.mode);

  for (i=0; i<BX_SB16_FM_NCH; i++)
    opl_keyonoff(i, 0);

  OPL.mode = newmode;
  
  if (OPL.timer_running != 0)
    {
      bx_pc_system.deactivate_timer( OPL.timer_handle );
      OPL.timer_running = 0;
    }

  OPL.drumchannel = 10;

  OPL.midichannels = 0xffff;    // all channels but the drum channel available
  OPL.midichannels &= ~(1 << OPL.drumchannel);

  for (i=0; i<2; i++)
    {
      OPL.wsenable[i] = 0;
      OPL.tmask[i] = 0;
      OPL.tflag[i] = 0;
      OPL.percmode[i] = 0;
    }

  for (i=0; i<4; i++)
    {
      OPL.timer[i] = 0;
      OPL.timerinit[i] = 0;
    }

  // initialize the operators
  for (i=0; i<BX_SB16_FM_NOP; i++)
    for (j=0; j<BX_SB16_FM_OPB; j++)
      OPL.oper[i][j] = 0;

  // TESTING for array bounds - compiler should bark if too high
  OPL.oper[BX_SB16_FM_NOP-1][BX_SB16_FM_OPB-1] = 0;

  // initialize the channels
  
  // first zero all values
  for (i=0; i<BX_SB16_FM_NCH; i++)
    {
      OPL.chan[i].nop = 0;
      for (j=0; j<4; j++)
	{
	  OPL.chan[i].opnum[j] = 0;
	  OPL.chan[i].outputlevel[j] = 0;
	}
      OPL.chan[i].freq = 0;
      OPL.chan[i].afreq = 0;
      OPL.chan[i].midichan = 0xff;
      OPL.chan[i].needprogch = 0;
      OPL.chan[i].midion = 0;
      OPL.chan[i].midinote = 0;
      OPL.chan[i].midibend = 0;
      OPL.chan[i].midivol = 0;
    }

  // assign the operators
  for (i=0; i<BX_SB16_FM_NCH; i++)
    {
      OPL.chan[i].nop = 2;
      // who invented this absolutely insane operator grouping??
      // it's like this: (ch 9...17 as 0...8 but higher operators)
      // ch:   0   1   2   3   4   5   6   7   8
      // op1:  0   1   2   6   7   8  12  13  14
      // op2:  3   4   5   9  10  11  15  16  17
      OPL.chan[i].opnum[0] = i + ( (int) (i / 3) ) * 3;
      OPL.chan[i].opnum[1] = OPL.chan[i].opnum[0] + 3;
    }

  // assign 4-op operators to the appropriate channels
  // note- they are not used unless .nop == 4
  for (i=0; i<6; i++)
    {
      j = i + (i /3) * 6;
      OPL.chan[j].opnum[2] = OPL.chan[j + 3].opnum[0];
      OPL.chan[j].opnum[3] = OPL.chan[j + 3].opnum[1];
    }
}

// this is called whenever one of the timer elapses
void bx_sb16_c::opl_timerevent()
{
  for (int i=0; i<4; i++)
    if ( (OPL.tmask[i/2] & (1 << (i % 2)) ) != 0)
      { // only running timers
	if ( (OPL.timer[i]--) == 0)
	  { // overflow occured, set flags accordingly
	    OPL.timer[i] = OPL.timerinit[i];      // reset the counter
	    if ( (OPL.tmask[i/2] >> (6 - (i % 2)) ) == 0)  // set flags only if unmasked
	      {
		writelog(WAVELOG(5), "OPL Timer Interrupt: Chip %d, Timer %d", i/2, 1 << (i % 2));
		OPL.tflag[i/2] |= 1 << (6 - (i % 2));   // set the overflow flag
		OPL.tflag[i/2] |= 1 << 7;             // set the IRQ flag
	      }
	  }
      }
}

// return the status of one of the OPL2's, or the
// base status of the OPL3
Bit32u bx_sb16_c::opl_status(int chipid)
{
  Bit32u status = OPL.tflag[chipid];

  writelog( MIDILOG(5), "OPL status of chip %d is %02x", chipid, status);

  return status;
}

// set the register index for one of the OPL2's or the 
// base or advanced register index for the OPL3
void bx_sb16_c::opl_index(Bit32u value, int chipid)
{
  OPL.index[chipid] = value;
}

// write to the data port
void bx_sb16_c::opl_data(Bit32u value, int chipid)
{
  int index = OPL.index[chipid];
  int opernum = -1;      // OPL3 operator number; 0..35
  int channum = -1;      // OPL3 channel number; 0..17
  int subopnum = -1;     // channel operator; 0..nop-1

  writelog( MIDILOG(4), "Write to OPL(%d) register %02x: %02x",
	    chipid, index, value);

  // first find out operator and/or channel numbers
  // case 0x20 ... 0x95:  includes too many ports, but that is harmless
  // case 0xe0 ... 0xf5:
  if ( ((index>=0x20) && (index<=0x95)) ||
       ((index>=0xe0) && (index<=0xf5)) ) {
      // operator access
      // find the operator number. 0..17 on chip 1, 18..35 on chip 2

      // note, the numbers are not continuous (again...), so we need
      // this rather weird calculation
      opernum = index & 0x07;
      if (opernum > 5)   // invalid register, has no operator associated
	{
	  opernum = -1;
	  goto break_here;
	}

      opernum += (index & 0x18) * 6;
      if (opernum > 17)     // Operators 18+ have to be accessed on other address set
	{
	  opernum = -1;
	  goto break_here;
	}

      if (chipid == 1)
	opernum += BX_SB16_FM_NOP / 2;

      // find out the channel number, and which of the channel's operators this is
      channum = opernum % 3 + ( (int) (opernum / 6) ) * 3;
      subopnum = 0;

      if ( (opernum % 6) > 2) // second operator
	subopnum = 1;

      // if (channel - 3) is in a four-operator mode, that is really
      // what this operator belongs to
      if (channum >= 3)
	if ( OPL.chan[channum - 3].nop == 4 )
	  {
	    channum -= 3;
	    subopnum += 2;
	  }
      writelog( MIDILOG(5), "Is Channel %d, Oper %d, Subop %d",
		channum, opernum, subopnum);
      }
  else if ( (index>=0xa0) && (index<=0xc8) ) {
      // channel access
      channum = index & 0x0f;
      if (OPL.chan[channum].nop == 0)
	channum = -1; // the channel is disabled
      writelog( MIDILOG(5), "Is channel %d", channum);
    }

break_here:

  switch (index & 0xff)
    {

      // WSEnable and Test Register
    case 0x01:
      OPL.wsenable[chipid] = (value >> 5) & 1;
      if ( (value & 0x1f) != 0)
	writelog( MIDILOG(3), "Warning: Test Register set to %02x", value & 0x1f);
      break;

      // the two timer counts
    case 0x02:
    case 0x03:
      OPL.timerinit[(index - 2) + chipid * 2] =
	OPL.timer[(index - 2) + chipid * 2] = value;
      break;

      // if OPL2: timer masks
      // if OPL3: 4-operator modes
    case 0x04:
      if ( (chipid == 0) || (OPL.mode == dual) )
	opl_settimermask(value, chipid);
      else
	opl_set4opmode(value & 0x3f);
      break;

      // only OPL3: OPL3 enable
    case 0x05:
      if (chipid == 1)
	{
	  if ( (value & 1) != 0)
	    opl_entermode(opl3);
	  else
	    opl_entermode(single);
	}
      break;

      // Composite Sine Wave and Note-sel (ignored)
    case 0x08:
      if (value != 0)
	writelog( MIDILOG(3), 
		  "Warning: write of %02x to CSW/Note-sel ignored",
		  value);
      break;

      // most importantly the percussion part
    case 0xbd:
      opl_setpercussion(value, chipid);
      break;

      // the operator registers
    // case 0x20 ... 0x35:
    case 0x20:
    case 0x21:
    case 0x22:
    case 0x23:
    case 0x24:
    case 0x25:
    case 0x26:
    case 0x27:
    case 0x28:
    case 0x29:
    case 0x2a:
    case 0x2b:
    case 0x2c:
    case 0x2d:
    case 0x2e:
    case 0x2f:
    case 0x30:
    case 0x31:
    case 0x32:
    case 0x33:
    case 0x34:
    case 0x35:
    // case 0x60 ... 0x75:
    case 0x60:
    case 0x61:
    case 0x62:
    case 0x63:
    case 0x64:
    case 0x65:
    case 0x66:
    case 0x67:
    case 0x68:
    case 0x69:
    case 0x6a:
    case 0x6b:
    case 0x6c:
    case 0x6d:
    case 0x6e:
    case 0x6f:
    case 0x70:
    case 0x71:
    case 0x72:
    case 0x73:
    case 0x74:
    case 0x75:
    // case 0x80 ... 0x95:
    case 0x80:
    case 0x81:
    case 0x82:
    case 0x83:
    case 0x84:
    case 0x85:
    case 0x86:
    case 0x87:
    case 0x88:
    case 0x89:
    case 0x8a:
    case 0x8b:
    case 0x8c:
    case 0x8d:
    case 0x8e:
    case 0x8f:
    case 0x90:
    case 0x91:
    case 0x92:
    case 0x93:
    case 0x94:
    case 0x95:
      if (opernum != -1)
	{
	  opl_changeop(channum, opernum, (index / 0x20) - 1, value);
	  break;
	}
      // else let default: catch it

    // case 0x40 ... 0x55:
    case 0x40:
    case 0x41:
    case 0x42:
    case 0x43:
    case 0x44:
    case 0x45:
    case 0x46:
    case 0x47:
    case 0x48:
    case 0x49:
    case 0x4a:
    case 0x4b:
    case 0x4c:
    case 0x4d:
    case 0x4e:
    case 0x4f:
    case 0x50:
    case 0x51:
    case 0x52:
    case 0x53:
    case 0x54:
    case 0x55:
      if (opernum != -1)
	{
	  opl_changeop(channum, opernum, 1, value & 0xc0);
	  if (subopnum != -1)
	    opl_setvolume(channum, subopnum, value & 0x3f);
	  break;
	}
      // else let default: catch it

    // case 0xe0 ... 0xf5:
    case 0xe0:
    case 0xe1:
    case 0xe2:
    case 0xe3:
    case 0xe4:
    case 0xe5:
    case 0xe6:
    case 0xe7:
    case 0xe8:
    case 0xe9:
    case 0xea:
    case 0xeb:
    case 0xec:
    case 0xed:
    case 0xee:
    case 0xef:
    case 0xf0:
    case 0xf1:
    case 0xf2:
    case 0xf3:
    case 0xf4:
    case 0xf5:
      if (opernum != -1)
	{
	  opl_changeop(channum, opernum, 5, value & 0x07);
	  break;
	}
      // else let default: catch it


      // and the channel registers
    // case 0xa0 ... 0xa8:
    case 0xa0:
    case 0xa1:
    case 0xa2:
    case 0xa3:
    case 0xa4:
    case 0xa5:
    case 0xa6:
    case 0xa7:
    case 0xa8:
      if (channum != -1)
	{
	  OPL.chan[channum].freq &= 0xff00;
	  OPL.chan[channum].freq |= value;
	  if ( (OPL.chan[channum].freqch |= 1) == 3)
	    opl_setfreq(channum);
	  break;
	}
      // else let default: catch it

    // case 0xb0 ... 0xb8:
    case 0xb0:
    case 0xb1:
    case 0xb2:
    case 0xb3:
    case 0xb4:
    case 0xb5:
    case 0xb6:
    case 0xb7:
    case 0xb8:
      if (channum != -1)
	{
	  OPL.chan[channum].freq &= 0x00ff;
	  OPL.chan[channum].freq |= (value & 0x1f) << 8;
	  if ( (OPL.chan[channum].freqch |= 2) == 3)
	    opl_setfreq(channum);
	  opl_keyonoff(channum, (value >> 5) & 1);
	  break;
	}
      // else let default: catch it


      // this is a channel access, but it belongs to the instrument
      // definition, so put it into value [4] of the channel's first operator
    // case 0xc0 ... 0xc8:
    case 0xc0:
    case 0xc1:
    case 0xc2:
    case 0xc3:
    case 0xc4:
    case 0xc5:
    case 0xc6:
    case 0xc7:
    case 0xc8:
      if (channum != -1)
	{
	  int needchange = 0;
	  if ((OPL.oper[OPL.chan[channum].opnum[0]][4] & 1) != (int)(value & 1))
	    needchange = 1;

	  opl_changeop(channum, OPL.chan[channum].opnum[0], 4, value & 0x3f);

	  if (needchange == 1)
	    opl_setmodulation(channum);
	  break;
	}
      // else let default: catch it

    default:
      writelog( MIDILOG(3), "Attempt to write %02x to unknown OPL(%d) register %02x",
		value, chipid, index);
      break;
    }
}

// change a value of an operator
void bx_sb16_c::opl_changeop(int channum, int opernum, int byte, int value)
{
  if (OPL.oper[opernum][byte] != value)
    {
      OPL.oper[opernum][byte] = value;
      OPL.chan[channum].needprogch = 1;
    }
}

// called for a write to the 4-operator mode register
void bx_sb16_c::opl_set4opmode(int new4opmode)
{
  int i, channel1, channel2;

  writelog( MIDILOG(4), "Switching to 4-op mode %02x", new4opmode);

  // every bit switches a 4-op channel-pairing on or off
  // 4-op mode is two channels combined into the first one
  for (i = 0; i<6; i++)
    {
      channel1 = i + (i / 3) * 6;
      channel2 = channel1 + 3;

      if ( ( (new4opmode >> i) & 1) != 0)
	{   // enable 4-op mode
	  opl_keyonoff(channel1, 0);
	  opl_keyonoff(channel2, 0);

	  OPL.chan[channel1].nop = 4;
	  OPL.chan[channel2].nop = 0;

	  OPL.chan[channel1].needprogch = 1;
	}
      else
	{   // disable 4-op mode
	  opl_keyonoff(channel1, 0);

	  OPL.chan[channel1].nop = 2;
	  OPL.chan[channel2].nop = 2;

	  OPL.chan[channel1].needprogch = 1;
	  OPL.chan[channel2].needprogch = 1;
	}
    }
}

// called for a write to port 4 of either chip
void bx_sb16_c::opl_settimermask(int value, int chipid)
{
  if ( (value & 0x80) != 0)     // reset IRQ and timer flags
    {                           // all other bits ignored!
      writelog( MIDILOG(5), "IRQ Reset called");
      OPL.tflag[chipid] = 0;
      return;
    }

  OPL.tmask[chipid] = value & 0x63;
  writelog( MIDILOG(5), "New timer mask for chip %d is %02x",
	    chipid, OPL.tmask[chipid]);

  // do we have to activate or deactivate the timer?
  if ( ( (value & 0x03) != 0) ^ (OPL.timer_running != 0) )
    if ( (value & 0x03) != 0)    // yes, it's different. Start or stop?
      {
	writelog( MIDILOG(5), "Starting timers");
	bx_pc_system.activate_timer( OPL.timer_handle, 0, 1);
	OPL.timer_running = 1;
      }
    else
      {
	writelog( MIDILOG(5), "Stopping timers");
	bx_pc_system.deactivate_timer( OPL.timer_handle );
	OPL.timer_running = 0;
      }
}

// called when the modulation mode of a channel changes
void bx_sb16_c::opl_setmodulation(int channel)
{
  int opernum = OPL.chan[channel].opnum[0];

  if ( (OPL.chan[channel].nop == 0) &&
       (channel >= 3) &&
       (OPL.chan[channel].nop == 4) )
    channel -= 3;

  if (OPL.chan[channel].nop == 2)
    {
      OPL.chan[channel].ncarr = (OPL.oper[opernum][4] & 1) + 1;
      OPL.chan[channel].needprogch = 1;
    }
  else if (OPL.chan[channel].nop == 4)
    {
      int opernum2 = OPL.chan[channel].opnum[2];
      int modmode = (OPL.oper[opernum][4] & 1) |
	            ( (OPL.oper[opernum2][4] & 1) >> 1);
      OPL.chan[channel].ncarr = modmode + 1 - (modmode / 2);
      OPL.chan[channel].needprogch = 1;
    }
}

// called for a write to register 0xbd, the percussion register
void bx_sb16_c::opl_setpercussion(Bit8u value, int chipid)
{
  UNUSED(value);
  UNUSED(chipid);
}

// called when a channel volume changes
// opnum is which of the channel's operators had the change, not
// the actual operator number. Thus, it's from 0..3.
void bx_sb16_c::opl_setvolume(int channel, int opnum, int outlevel)
{
  UNUSED(opnum);
  UNUSED(outlevel);

  OPL.chan[channel].midivol = 127;
}


// called when a frequency change is complete, to find out the
// corresponding midi key and pitch bender values
void bx_sb16_c::opl_setfreq(int channel)
{
  int block,fnum;

  OPL.chan[channel].freqch = 0;

  // definition: 
  // low-byte of freq:  8 bit F-Number, LSB's
  // high-byte of freq: [2 reserved][KEY-ON][3 block][2 F-Number MSB's]
  // [KEY-ON] is ignored by this function
  //
  // the definition of the F-number is
  // F-Number = Frequency * 2**(20-block) / (49716 Hz)
  //
  // Thus, the frequency can be calculated as
  // Frequency = F-Number / 2**(20-block) * 49716 Hz
  //
  // (But remember that afreq is in 10^-3 Hz!)
  //

  fnum = OPL.chan[channel].freq & 0x3ff;
  block = (OPL.chan[channel].freq >> 10) & 0x07;

  writelog( MIDILOG(5), "F-Num is %d, block is %d", fnum, block);

  Bit32u realfreq;
  const Bit32u freqbase = 49716000; // const is better than #define if type is important

  // this is a bit messy to preserve accuracy as much as possible,
  // otherwise we might either lose precision, or the higher bits.
  if (block < 16)
    realfreq = ( (freqbase >> 4) * fnum) >> (16 - block);
  else
    realfreq = (freqbase * fnum) >> (20 - block);

  OPL.chan[channel].afreq = realfreq;

  // now find out what MIDI key this corresponds to, and with what
  // pitch bender value... (the latter not implemented yet)
  int octave=0;          // 0: Octave from 523.2511 Hz; pos=higher, neg=lower
  int keynum=0;          // 0=C; 1=C#; 2=D; ...; 11=B

  if (realfreq > 8175)      // 8.175 is smallest possible frequency
    {
      const Bit32u freqC = 523251;    // Midi note 72; "C": 523.251 Hz
      Bit32u keyfreq;           // Frequency scaled to the octave from freqC to 2*freqC

      if (realfreq > freqC)
	{
	  while ( (realfreq >> (++octave)) > freqC);

	  keyfreq = realfreq >> (--octave);
	}
      else
	{
	  while ( (realfreq << (++octave)) < freqC);

	  keyfreq = realfreq << (--octave);

	  octave = -octave;
	}
  
      // this is a reasonable approximation for keyfreq /= 1.059463
      // (that value is 2**(1/12), which is the difference between two keys)
      while ( (keyfreq -= ( (keyfreq * 1000) / 17817) ) > freqC)
	keynum++;
    }
  else
    {
      octave = -6;
      keynum = 0;
    }

  OPL.chan[channel].midinote = (octave + 6) * 12 + keynum;

  writelog( MIDILOG(5), "New frequency %.3f is key %d in octave %d; midi note %d",
	    (float) realfreq/1000.0, keynum, octave, OPL.chan[channel].midinote);
}

// called when a note is possibly turned on or off
void bx_sb16_c::opl_keyonoff(int channel, bx_bool onoff)
{
  int i;

  if (OPL.mode == fminit)
    return;

  // first check if there really is a change in the state
  if (onoff == OPL.chan[channel].midion)
    return;

  Bit8u commandbytes[3];

  // check if we have a midi channel, otherwise allocate one if possible
  if (OPL.chan[channel].midichan == 0xff)
    {
      for (i=0; i<16; i++)
	if ( ( ( OPL.midichannels >> i) & 1 ) != 0)
	  {
	    OPL.chan[channel].midichan = i;
	    OPL.midichannels &= ~(1 << i);     // mark channel as used
	    OPL.chan[channel].needprogch = 1;
	  }
      if (OPL.chan[channel].midichan == 0xff)
	return;
    }

  if (OPL.chan[channel].needprogch != 0)
    opl_midichannelinit(channel);

  commandbytes[0] = OPL.chan[channel].midichan;
  commandbytes[1] = OPL.chan[channel].midinote;
  commandbytes[2] = 0;

  if (onoff == 0)
    commandbytes[0] |= 0x80;  // turn it off
  else
    {
      commandbytes[0] |= 0x90;  // turn it on
      commandbytes[1] = OPL.chan[channel].midivol;
    }

  writemidicommand(commandbytes[1], 2, & (commandbytes[1]) );
}

// setup a midi channel
void bx_sb16_c::opl_midichannelinit(int channel)
{
  UNUSED(channel);
}


/* Handlers for the midi commands/midi file output */

// Write the header of the midi file. Track length is 0x7fffffff 
// until we know how long it's really going to be

void bx_sb16_c::initmidifile()
{
  struct {
    Bit8u chunk[4];
    Bit32u chunklen;  // all values in BIG Endian!
    Bit16u smftype; 
    Bit16u tracknum;
    Bit16u timecode;  // 0x80 + deltatimesperquarter << 8
    } midiheader = 
#ifdef BX_LITTLE_ENDIAN
      { "MTh", 0x06000000, 0, 0x0100, 0x8001 };
#else
      { "MTh", 6, 0, 1, 0x180 };
#endif
   midiheader.chunk[3] = 'd';

  struct {
    Bit8u chunk[4];
    Bit32u chunklen;
    Bit8u data[15];
    } trackheader = 
#ifdef BX_LITTLE_ENDIAN
      { "MTr", 0xffffff7f,
#else
      { "MTr", 0x7fffffff,
#endif
	{ 0x00,0xff,0x51,3,0x07,0xa1,0x20,    // set tempo 120 (0x7a120 us per quarter)
	  0x00,0xff,0x58,4,4,2,0x18,0x08 }};  // time sig 4/4
  trackheader.chunk[3] = 'k';
    
  fwrite(&midiheader, 1, 14, MIDIDATA );
  fwrite(&trackheader, 1, 23, MIDIDATA );
  return;
}

// write the midi command to the midi file
  
void bx_sb16_c::writemidicommand(int command, int length, Bit8u data[])
{
  /* We need to determine the time elapsed since the last MIDI command */
  int deltatime = currentdeltatime();

  /* Initialize output device if necessary and not done yet */
  if (bx_options.sb16.Omidimode->get () == 1)
    {
      if (MPU.outputinit != 1)
	{
	  writelog( MIDILOG(4), "Initializing Midi output.");
	  if (BX_SB16_OUTPUT->openmidioutput(bx_options.sb16.Omidifile->getptr ()) == BX_SOUND_OUTPUT_OK)
	    MPU.outputinit = 1;
	  else 
	    MPU.outputinit = 0;
	  if (MPU.outputinit != 1)
	    {
	      writelog( MIDILOG(2), "Error: Couldn't open midi output. Midi disabled.");
	      bx_options.sb16.Omidimode->set (0);
	    }
	}
      BX_SB16_OUTPUT->sendmidicommand(deltatime, command, length, data); 
      return;
    }
  else if (bx_options.sb16.Omidimode->get () < 2)
    return;

  if (bx_options.sb16.Omidimode->get () == 2)
    writedeltatime(deltatime);

  fputc(command, MIDIDATA);
  if ( (command == 0xf0) || 
       (command == 0xf7) )    // write event length for sysex/meta events
    writedeltatime(length);

  fwrite(data, 1, length, MIDIDATA);
}


// determine how many delta times have passed since
// this function was called last

int bx_sb16_c::currentdeltatime()
{
  int deltatime;

  // counting starts at first access
  if (MPU.last_delta_time == 0xffffffff) 
    MPU.last_delta_time = MPU.current_timer;
 
  deltatime = MPU.current_timer - MPU.last_delta_time;
  MPU.last_delta_time = MPU.current_timer;

  return deltatime;
}


// process the midi command stored in MPU.midicmd.to the midi driver

void bx_sb16_c::processmidicommand(bx_bool force)
{
  int i, channel;
  Bit8u value;
  bx_bool needremap = 0;

  channel = MPU.midicmd.currentcommand() & 0xf;

  // we need to log bank changes and program changes
  if ( (MPU.midicmd.currentcommand() >> 4) == 0xc)
    {    // a program change
      value = MPU.midicmd.peek(0);
      writelog(MIDILOG(1), "* ProgramChange channel %d to %d",
	       channel, value);
      MPU.program[channel] = value;
      needremap = 1;
    }
  else if ( (MPU.midicmd.currentcommand() >> 4) == 0xb)
    {    // a control change, could be a bank change
      if (MPU.midicmd.peek(0) == 0)
	{  // bank select MSB
	  value = MPU.midicmd.peek(1);
	  writelog(MIDILOG(1), "* BankSelectMSB (%x %x %x) channel %d to %d",
		   MPU.midicmd.peek(0), MPU.midicmd.peek(1), MPU.midicmd.peek(2),
		   channel, value);
	  MPU.bankmsb[channel] = value;
	  needremap = 1;
	}
      else if (MPU.midicmd.peek(0) == 32)
	{  // bank select LSB
	  value = MPU.midicmd.peek(1);
	  writelog(MIDILOG(1), "* BankSelectLSB channel %d to %d",
		   channel, value);
	  MPU.banklsb[channel] = value;
	  needremap = 1;
	}
    }

  Bit8u temparray[256];
  i = 0;
  while (MPU.midicmd.empty() == 0)
    MPU.midicmd.get( &(temparray[i++]) );

  writemidicommand(MPU.midicmd.currentcommand(), i, temparray);

  // if single command, revert to command mode
  if (MPU.singlecommand != 0)
    {
      MPU.singlecommand = 0;
      // and trigger IRQ?
      //      MPU.irqpending = 1;
      //      BX_SB16_THIS devices->pic->trigger_irq(BX_SB16_IRQMPU);
    }

  if ( (force == 0) && (needremap == 1) )
    // have to check the remap lists, and remap program change if necessary
    midiremapprogram(channel);
}


// check if a program change has to be remapped, and do it if necessary

void bx_sb16_c::midiremapprogram(int channel)
{
  int bankmsb,banklsb,program;
  Bit8u commandbytes[2];

  bankmsb = MPU.bankmsb[channel];
  banklsb = MPU.banklsb[channel];
  program = MPU.program[channel];

  for (int i = 0; i < EMUL.remaps; i++)
    {
      if ( ( (EMUL.remaplist[i].oldbankmsb == bankmsb) ||
	     (EMUL.remaplist[i].oldbankmsb == 0xff) ) &&
	   ( (EMUL.remaplist[i].oldbanklsb == banklsb) ||
	     (EMUL.remaplist[i].oldbanklsb == 0xff) ) &&
	   ( (EMUL.remaplist[i].oldprogch == program) ||
	     (EMUL.remaplist[i].oldprogch == 0xff) ) )
	{
	  writelog(5, "Remapping instrument for channel %d", channel);
	  if ( (EMUL.remaplist[i].newbankmsb != bankmsb) &&
	       (EMUL.remaplist[i].newbankmsb != 0xff) )
	    {  // write control change bank msb
	      MPU.bankmsb[channel] = EMUL.remaplist[i].newbankmsb;
	      commandbytes[0] = 0;
	      commandbytes[1] = EMUL.remaplist[i].newbankmsb;
	      writemidicommand(0xb0 | channel, 2, commandbytes);
	    }
	  if ( (EMUL.remaplist[i].newbanklsb != banklsb) &&
	       (EMUL.remaplist[i].newbanklsb != 0xff) )
	    {  // write control change bank lsb
	      MPU.banklsb[channel] = EMUL.remaplist[i].newbanklsb;
	      commandbytes[0] = 32;
	      commandbytes[1] = EMUL.remaplist[i].newbanklsb;
	      writemidicommand(0xb0 | channel, 2, commandbytes);
	    }
	  if ( (EMUL.remaplist[i].newprogch != program) &&
	       (EMUL.remaplist[i].newprogch != 0xff) )
	    {  // write program change
	      MPU.program[channel] = EMUL.remaplist[i].newprogch;
	      commandbytes[0] = EMUL.remaplist[i].newprogch;
	      writemidicommand(0xc0 | channel, 1, commandbytes);
	    }
	}
    }
}


// convert a number into a delta time coded value
int bx_sb16_c::converttodeltatime(Bit32u deltatime, Bit8u value[4])
{
  int i, count;
  Bit8u outbytes[4];

  count = 0;

  if (deltatime <= 0)
    {
      count = 1;
      value[0] = 0;
    }
  else 
    {
      while ( (deltatime > 0) && (count < 4) )   // split into parts
	{                                        // of seven bits
	  outbytes[count++] = deltatime & 0x7f;
	  deltatime >>= 7;
	};
      for (i=0; i<count; i++)                      // reverse order and
	value[i] = outbytes[count - i - 1] | 0x80; // set eighth bit on
      value[count - 1] &= 0x7f;                    // all but last byte
    }
  return count;
}


// write a delta time coded value to the midi file
void bx_sb16_c::writedeltatime(Bit32u deltatime)
{
  Bit8u outbytes[4];
  int count,i;

  count = converttodeltatime(deltatime, outbytes);

  for (i=0; i<count; i++)
    fputc(outbytes[i], MIDIDATA );
}


// close the midi file, and set the track length accordingly

void bx_sb16_c::finishmidifile()
{
  struct { 
    Bit8u delta, statusbyte, metaevent, length;
  } metatrackend = { 0, 0xff, 0x2f, 0 };

     // Meta event track end (0xff 0x2f 0x00) plus leading delta time
  fwrite(&metatrackend, 1, sizeof metatrackend, MIDIDATA );

  Bit32u tracklen = ftell(MIDIDATA);
  if (tracklen < 0)
    BX_PANIC (("ftell failed in finishmidifile"));
  if (tracklen < 22)
    BX_PANIC (("finishmidifile with track length too short"));
  tracklen -= 22;    // subtract the midi file and track header
  fseek(MIDIDATA, 22 - 4, SEEK_SET);
  // value has to be in big endian
#ifdef BX_LITTLE_ENDIAN
  tracklen = (tracklen << 24) | (tracklen >> 24) |
	((tracklen & 0x00ff0000) >> 8) |
	((tracklen & 0x0000ff00) << 8);
#endif
  fwrite(&tracklen, 4, 1, MIDIDATA);
  return;
}



/* Handlers for the voc file output */

// Write the header of the voc file.

void bx_sb16_c::initvocfile()
{
  struct { 
    char id[20];
    Bit16u headerlen;  // All in LITTLE Endian!
    Bit16u version;
    Bit16u chksum;
  } vocheader =
    { "Creative Voice File",
#ifdef BX_LITTLE_ENDIAN
      0x1a, 0x0114, 0x111f };
#else
      0x1a00, 0x1401, 0x1f11 };
#endif

  vocheader.id[19] = 26;    // Replace string end with 26

  fwrite(&vocheader, 1, sizeof vocheader, WAVEDATA);
}

// write one block to the voc file
void bx_sb16_c::writevocblock(int block, 
			      Bit32u headerlen, Bit8u header[],
			      Bit32u datalen, Bit8u data[])
{
  Bit32u i;

  if (block > 9)
    {
      writelog( WAVELOG(3), "VOC Block %d not recognized, ignored.", block);
      return;
    }

  fputc(block, WAVEDATA);

  i = headerlen + datalen;
#ifdef BX_LITTLE_ENDIAN
  fwrite(&i, 1, 3, WAVEDATA);       // write the length in 24-bit little endian
#else
  Bit8u lengthbytes[3];
  lengthbytes[0] = i & 0xff; i >>= 8;
  lengthbytes[1] = i & 0xff; i >>= 8;
  lengthbytes[2] = i & 0xff;
  fwrite(lengthbytes, 1, 3, WAVEDATA);
#endif
  writelog( WAVELOG(5), "Voc block %d; Headerlen %d; Datalen %d",
	    block, headerlen, datalen );
  if (headerlen > 0)
    fwrite(header, 1, headerlen, WAVEDATA);
  if (datalen > 0)
    fwrite(data, 1, datalen, WAVEDATA);
}


// close the voc file
void bx_sb16_c::finishvocfile()
{
  fputc(0, WAVEDATA);  // blocktype 0: end block
}


  // static IO port read callback handler
  // redirects to non-static class handler to avoid virtual functions

Bit32u bx_sb16_c::read_handler(void *this_ptr, Bit32u address, unsigned io_len)
{
#if !BX_USE_SB16_SMF
  bx_sb16_c *class_ptr = (bx_sb16_c *) this_ptr;

  return( class_ptr->read(address, io_len) );
}


Bit32u bx_sb16_c::read(Bit32u address, unsigned io_len)
{
#else
  UNUSED(this_ptr);
#endif  // !BX_USE_SB16_SMF

  switch (address) 
    {
      // 2x0: FM Music Status Port
      // 2x8 and 388 are aliases
    case BX_SB16_IO + 0x00:
    case BX_SB16_IO + 0x08:
    case BX_SB16_IOADLIB + 0x00:
      return opl_status(0);

      // 2x1: reserved (w: FM Music Data Port)
      // 2x9 and 389 are aliases
    case BX_SB16_IO + 0x01:
    case BX_SB16_IO + 0x09:
    case BX_SB16_IOADLIB + 0x01:
      break;

      // 2x2: Advanced Music Status Port
      //      or (for SBPro1) FM Music Status Port 2
      // 38a is an alias
    case BX_SB16_IO + 0x02:
    case BX_SB16_IOADLIB + 0x02:
      return opl_status(1);

      // 2x3: reserved (w: Adv. FM Music Data Port)
      //      or (for SBPro1) FM Music Data Port 2
      // 38b is an alias
    case BX_SB16_IO + 0x03:
    case BX_SB16_IOADLIB + 0x03:
      break;

      // 2x4: reserved (w: Mixer Register Port)
    case BX_SB16_IO + 0x04:
      break;

      // 2x5: Mixer Data Port
    case BX_SB16_IO + 0x05:
      return mixer_readdata();

      // 2x6: reserved (w: DSP Reset)
    case BX_SB16_IO + 0x06:
      break;

      // 2x7: reserved
    case BX_SB16_IO + 0x07:
      break;

      // 2x8: FM Music Status Port (OPL-2)
      // handled above

      // 2x9: reserved (w: FM Music Data Port)
      // handled above

      // 2xa: DSP Read Data Port
    case BX_SB16_IO + 0x0a:
      return dsp_dataread();

      // 2xb: reserved
    case BX_SB16_IO + 0x0b:
      break;

      // 2xc: DSP Buffer Status Port
    case BX_SB16_IO + 0x0c:
      return dsp_bufferstatus();

      // 2xd: reserved
    case BX_SB16_IO + 0x0d:
      break;

      // 2xe: DSP Data Status Port
    case BX_SB16_IO + 0x0e:
      return dsp_status();

      // 2xf: DSP Acknowledge 16bit DMA IRQ
    case BX_SB16_IO + 0x0f:
       return dsp_irq16ack();

       // 3x0: MPU Data Port Read
    case BX_SB16_IOMPU + 0x00:
      return mpu_dataread();

       // 3x1: MPU Status Port
    case BX_SB16_IOMPU + 0x01:
      return mpu_status();

      // 3x2: reserved
    case BX_SB16_IOMPU + 0x02:
      break;

      // 3x3: *Emulator* Port
    case BX_SB16_IOMPU + 0x03:
      return emul_read();

    }

  // If we get here, the port wasn't valid
  writelog(3, "Read access to %03x for %d: unsupported port!", address, io_len);

  return(0xff);
}

// static IO port write callback handler
// redirects to non-static class handler to avoid virtual functions

void bx_sb16_c::write_handler(void *this_ptr, Bit32u address, Bit32u value, unsigned io_len)
{
#if !BX_USE_SB16_SMF
  bx_sb16_c *class_ptr = (bx_sb16_c *) this_ptr;

  class_ptr->write(address, value, io_len);
}

void bx_sb16_c::write(Bit32u address, Bit32u value, unsigned io_len)
{
#else
  UNUSED(this_ptr);
#endif  // !BX_USE_SB16_SMF

  switch (address)
    {
      // 2x0: FM Music Register Port
      // 2x8 and 388 are aliases
    case BX_SB16_IO + 0x00:
    case BX_SB16_IO + 0x08:
    case BX_SB16_IOADLIB + 0x00:
      opl_index(value, 0);
      return;

      // 2x1: FM Music Data Port
      // 2x9 and 389 are aliases
    case BX_SB16_IO + 0x01:
    case BX_SB16_IO + 0x09:
    case BX_SB16_IOADLIB + 0x01:
      opl_data(value, 0);
      return;

      // 2x2: Advanced FM Music Register Port
      //      or (for SBPro1) FM Music Register Port 2
      // 38a is an alias
    case BX_SB16_IO + 0x02:
    case BX_SB16_IOADLIB + 0x02:
      opl_index(value, 1);
      return;

      // 2x3: Advanced FM Music Data Port
      //      or (for SBPro1) FM Music Data Port 2
      // 38b is an alias
    case BX_SB16_IO + 0x03:
    case BX_SB16_IOADLIB + 0x03:
      opl_data(value, 1);
      return;

      // 2x4: Mixer Register Port
    case BX_SB16_IO + 0x04:
      mixer_writeregister(value);
      return;

      // 2x5: Mixer Data Portr,
    case BX_SB16_IO + 0x05:
      mixer_writedata(value);
      return;

      // 2x6: DSP Reset
    case BX_SB16_IO + 0x06:
      dsp_reset(value);
      return;

      // 2x7: reserved
    case BX_SB16_IO + 0x07:
      break;

      // 2x8: FM Music Register Port (OPL-2)
      // handled above

      // 2x9: FM Music Data Port
      // handled above

      // 2xa: reserved (r: DSP Data Port)
    case BX_SB16_IO + 0x0a:
      break;

      // 2xb: reserved
    case BX_SB16_IO + 0x0b:
      break;

      // 2xc: DSP Write Command/Data
    case BX_SB16_IO + 0x0c:
      dsp_datawrite(value);
      return;

	   // 2xd: reserved
    case BX_SB16_IO + 0x0d:
      break;

      // 2xe: reserved (r: DSP Buffer Status)
    case BX_SB16_IO + 0x0e:
      break;

      // 2xf: reserved
    case BX_SB16_IO + 0x0f:
      break;

      // 3x0: MPU Command Port
    case BX_SB16_IOMPU + 0x00:
      mpu_datawrite(value);
      return;

      // 3x1: MPU Data Port
    case BX_SB16_IOMPU + 0x01:
      mpu_command(value);
      return;

      // 3x2: reserved
    case BX_SB16_IOMPU + 0x02:
      break;

      // 3x3: *Emulator* Port
    case BX_SB16_IOMPU + 0x03:
      emul_write(value);
      return;
    }

  // if we arrive here, the port is unsupported
  writelog(3, "Write access to %03x for %d to %02x: unsupported port!",
	   address, io_len, value);

  return;
}

void bx_sb16_c::writelog(int loglevel, const char *str, ...)
{
// append a line to the log file, if desired
  if ( (int) bx_options.sb16.Ologlevel->get () >= loglevel)
    {
        fprintf(LOGFILE, "%011lld", bx_pc_system.time_ticks());
        fprintf(LOGFILE, " (%d) ", loglevel);
	va_list ap;
	va_start(ap, str);
	vfprintf(LOGFILE, str, ap);
	va_end(ap);
	fprintf(LOGFILE, "\n");
	fflush(LOGFILE);
    }
}

// the round-robin FIFO buffers of the SB16
bx_sb16_buffer::bx_sb16_buffer()
{
  length = 0;		// total bytes in buffer
  head = 0;		// pointer to next slot available for new data
  tail = 0;		// pointer to next slot to be read from
  buffer = NULL;	// pointer to the actual data
}

void bx_sb16_buffer::init(int bufferlen)
{
  if (buffer != NULL)	// Was it initialized before?
    delete buffer;

  length = bufferlen;
  buffer = new Bit8u[length];
  if (buffer == NULL)
    length = 0;		// This will be checked later

  reset();
}

void bx_sb16_buffer::reset()
{
  head = 0;		// Reset the pointers
  tail = 0;

  clearcommand();       // no current command set
}

bx_sb16_buffer::~bx_sb16_buffer(void)
{
  if (buffer != NULL)
    delete [] buffer;

  buffer = NULL;
  length = 0;
}


// Report how many bytes are available
int bx_sb16_buffer::bytes(void)
{
  if (empty() != 0)
    return 0;   // empty / not initialized

  int bytes = head - tail;
  if (bytes < 0) bytes += length;
  return (bytes);
}

// This puts one byte into the buffer
bx_bool bx_sb16_buffer::put(Bit8u data)
{
  if (full() != 0)
    return 0;	// buffer full

  buffer[head++] = data;	// Write data, and increase write pointer
  head %= length;		// wrap it around so it stays inside the data

  return 1;	// put was successful
}

// This writes a formatted string to the buffer
bx_bool bx_sb16_buffer::puts(char *data, ...)
{

  if (data == NULL)
    return 0;  // invalid string

//char string[length];
  char *string;
  int index = 0;

  string = (char *) malloc(length);

  va_list ap;
  va_start(ap, data);
  vsprintf(string, data, ap);
  va_end(ap);

  if ( (int) strlen(string) >= length)
    BX_PANIC(("bx_sb16_buffer: puts() too long!"));

  while (string[index] != 0)
    {
      if (put( (Bit8u) string[index]) == 0)
	return 0;  // buffer full
      index++;
    }
  return 1;
}

// This returns if the buffer is full, i.e. if a put will fail
bx_bool bx_sb16_buffer::full(void)
{
  if (length == 0)
    return 1;   // not initialized

  if ( ((head + 1) % length) == tail)
    return 1;   // buffer full

  return 0;	// buffer has some space left
}

// This reads the next available byte from the buffer
bx_bool bx_sb16_buffer::get(Bit8u *data)
{
  if (empty() != 0)
    { 
      // Buffer is empty. Still, if it was initialized, return
      // the last byte again.
      if ( length > 0 )
	(*data) = buffer[ (tail - 1) % length ];
      return 0;	// buffer empty
    }

  (*data) = buffer[tail++];	// read data and increase read pointer
  tail %= length;		// and wrap it around to stay inside the data

  return 1;	// get was successful
}

// Read a word in lo/hi order
bx_bool bx_sb16_buffer::getw(Bit16u *data)
{
  Bit8u dummy;
  if (bytes() < 2)
    {
      if (bytes() == 1)
	{
	  get(&dummy);
	  *data = (Bit16u) dummy;
	}
      else
	dummy = 0;
      return 0;
    }
  get(&dummy);
  *data = (Bit16u) dummy;
  get(&dummy);
  *data |= ( (Bit16u) dummy ) << 8;
  return 1;
}

// Read a word in hi/lo order
bx_bool bx_sb16_buffer::getw1(Bit16u *data)
{
  Bit8u dummy;
  if (bytes() < 2)
    {
      if (bytes() == 1)
	{
	  get(&dummy);
	  *data = ( (Bit16u) dummy ) << 8;
	}
      else
	dummy = 0;
      return 0;
    }
  get(&dummy);
  *data = ( (Bit16u) dummy ) << 8;
  get(&dummy);
  *data |= (Bit16u) dummy;
  return 1;
}

// This returns if the buffer is empty, i.e. if a get will fail
bx_bool bx_sb16_buffer::empty(void)
{
  if (length == 0)
    return 1;   // not inialized

  if (head == tail)
    return 1;   // buffer empty

  return 0;	// buffer contains data
}


// Flushes the buffer
void bx_sb16_buffer::flush(void)
{
  tail = head;
  return;
}

// Peeks ahead in the buffer
// Warning: No checking if result is valid. Must call bytes() to check that!
Bit8u bx_sb16_buffer::peek(int offset)
{
  return buffer[ (tail + offset) % length ];
}

// Set a new active command
void bx_sb16_buffer::newcommand(Bit8u newcmd, int bytes)
{
  command = newcmd;
  havecommand = 1;
  bytesneeded = bytes;
}

// Return the currently active command
Bit8u bx_sb16_buffer::currentcommand(void)
{
  return command;
}

// Clear the active command
void bx_sb16_buffer::clearcommand(void)
{
  command = 0;
  havecommand = 0;
  bytesneeded = 0;
}

// return if the command has received all necessary bytes
bx_bool bx_sb16_buffer::commanddone(void)
{
  if (hascommand() == 0)
    return 0;  // no command pending - not done then

  if (bytes() >= bytesneeded)
    return 1;  // yes, it's done

  return 0;    // no, it's not
}

// return if there is a command pending
bx_bool bx_sb16_buffer::hascommand(void)
{
  return havecommand;
}

int bx_sb16_buffer::commandbytes(void)
{
  return bytesneeded;
}

// The dummy output functions. They don't do anything
bx_sound_output_c::bx_sound_output_c(bx_sb16_c *sb16)
{
  UNUSED(sb16);
}
bx_sound_output_c::~bx_sound_output_c()
{
}
int bx_sound_output_c::waveready()
{
  return BX_SOUND_OUTPUT_OK;
}
int bx_sound_output_c::midiready()
{
  return BX_SOUND_OUTPUT_OK;
}
int bx_sound_output_c::openmidioutput(char *device)
{
  UNUSED(device);
  return BX_SOUND_OUTPUT_OK;
}
int bx_sound_output_c::sendmidicommand(int delta, int command, int length, Bit8u data[])
{
  UNUSED(delta);
  UNUSED(command);
  UNUSED(length);
  UNUSED(data);
  return BX_SOUND_OUTPUT_OK;
}
int bx_sound_output_c::closemidioutput()
{
  return BX_SOUND_OUTPUT_OK;
}
int bx_sound_output_c::openwaveoutput(char *device)
{
  UNUSED(device);
  return BX_SOUND_OUTPUT_OK;
}
int bx_sound_output_c::startwaveplayback(int frequency, int bits, int stereo, int format)
{
  UNUSED(frequency);
  UNUSED(bits);
  UNUSED(stereo);
  UNUSED(format);
  return BX_SOUND_OUTPUT_OK;
}
int bx_sound_output_c::sendwavepacket(int length, Bit8u data[])
{
  UNUSED(length);
  UNUSED(data);
  return BX_SOUND_OUTPUT_OK;
}
int bx_sound_output_c::stopwaveplayback()
{
  return BX_SOUND_OUTPUT_OK;
}
int bx_sound_output_c::closewaveoutput()
{
  return BX_SOUND_OUTPUT_OK;
}

#endif /* if BX_SUPPORT_SB16 */
