/*
 *  Copyright (C) 2006 Stas Sergeev <stsp@users.sourceforge.net>
 *
 * The below copyright strings have to be distributed unchanged together
 * with this file. This prefix can not be modified or separated.
 */

/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/*
 * Purpose: SB16 emulation.
 *
 * Author: Stas Sergeev.
 *
 * Some code is taken from an old sound.c by Joel N. Weber II,
 * Alistair MacDonald, Michael Karcher and others - thanks.
 * Thanks to Vlad Romascanu and VDMSound project for the E2 code and some info.
 */

#include "emu.h"
#include "utilities.h"
#include "bitops.h"
#include "port.h"
#include "sound/sound.h"
#include "sound/midi.h"
#include "sound.h"
#include "dspio.h"
#include "adlib.h"
#include "mpu401.h"
#include "sb16.h"
#include <string.h>

static int sb_irq_tab[] = { 9 /* 2 actually */, 5, 7, 10 };
static int sb_dma_tab[] = { 0, 1, 3 };
static int sb_hdma_tab[] = { 5, 6, 7 };

/*
 * Various Status values
 */

#define SB_DATA_AVAIL    0x80
#define SB_DATA_UNAVAIL  0x00
/* not sure if/when this ever changes */
#define SB_DSP_STATUS    0x7f

#define SB_GET_STATUS() ((rng_count(&sb.dsp_queue) ? SB_DATA_AVAIL : \
    SB_DATA_UNAVAIL) | SB_DSP_STATUS)

/*
 * DSP information / states
 */
enum { DMA_RESTART_NONE, DMA_RESTART_PENDING, DMA_RESTART_AUTOINIT };
struct sb_struct {
  uint16_t rate;		/* The current sample rate for input */
  uint8_t  test;		/* Storage for the test value */
  uint8_t  reset_val;
  unsigned int      paused:1;		/* is DMA transfer paused? */
  unsigned int      reset:1;
  uint8_t  midi_cmd;
  uint8_t  dma_cmd;		/* Information we need on the DMA transfer */
  uint8_t  dma_mode;		/* Information we need on the DMA transfer */
  unsigned int      dma_exit_ai:1;	/* exit DMA autoinit */
  struct {
    int    val;
    unsigned int    is_16:1;
    unsigned int    allow:1;
  }        dma_restart;		/* DMA restart on IRQ ACK */
  uint8_t  new_dma_cmd;		/* Information we need on the DMA transfer */
  uint8_t  new_dma_mode;	/* Information we need on the DMA transfer */
  uint16_t dma_init_count;
  uint16_t dma_count;
  uint8_t  mixer_regs[256];
  uint8_t  mixer_index;
  uint8_t  E2Count;
  uint8_t  asp_regs[256];
  int      asp_init;
  uint8_t  last_data;
  int      busy;
  int      irq_pend;
  int      irq_delay_cnt;
/* All values are imperical! */
#define SB_DSP_CMD_BUF_SZ 8
  uint8_t  command[SB_DSP_CMD_BUF_SZ];
  int      command_idx;
#define DSP_QUEUE_SIZE 64
  struct rng_s dsp_queue;
  struct dspio_state *dspio;
  struct mpu401_s *mpu;
};

static struct sb_struct sb;

static void process_sb_midi_input(Bit8u val);

static int sb_get_dsp_irq_num(void)
{
    int idx = find_bit(sb.mixer_regs[0x80]);
    if (idx == -1 || idx > 3) {
	error("SB IRQ wrong (%#x)\n", sb.mixer_regs[0x80]);
	return 5;
    }
    return sb_irq_tab[idx];
}

int sb_get_dma_num(void)
{
    int idx = find_bit(sb.mixer_regs[0x81] & 0x0f);
    if (idx == -1 || idx == 2) {
	error("SB DMA wrong (%#x)\n", sb.mixer_regs[0x81]);
	return 1;
    }
    return idx;
}

int sb_get_hdma_num(void)
{
    int idx = find_bit(sb.mixer_regs[0x81] & 0xf0);
    if (idx == 4) {
	error("SB HDMA wrong (%#x)\n", sb.mixer_regs[0x81]);
	return 5;
    }
    return idx;
}

/* DMA mode decoding functions */
int sb_dma_active(void)
{
    return (sb.dma_cmd && !sb.paused);
}

int sb_dma_16bit(void)
{
    if (!sb.dma_cmd)
	error("SB: used inactive DMA (16bit)\n");
    switch (sb.dma_cmd) {
    case 0xb0 ... 0xbf:
	return 1;
    }
    return 0;
}

int sb_dma_adpcm(void)
{
    if (!sb.dma_cmd)
	error("SB: used inactive DMA (adpcm)\n");
    switch (sb.dma_cmd) {
    case 0x74:
    case 0x75:
    case 0x7d:
	return 4;
    case 0x76:
    case 0x77:
    case 0x7f:
	return 3;
    case 0x16:
    case 0x17:
    case 0x1f:
	return 2;
    }
    return 0;
}

int sb_dma_adpcm_ref(void)
{
    if (!sb.dma_cmd)
	error("SB: used inactive DMA (adpcm_ref)\n");
    switch (sb.dma_cmd) {
    case 0x75:
    case 0x77:
    case 0x17:
    case 0x7d:
    case 0x7f:
    case 0x1f:
	return 1;
    }
    return 0;
}

static int sb_dma_sb16mode(void)
{
    if (!sb.dma_cmd)
	error("SB: used inactive DMA (sb16)\n");
    switch (sb.dma_cmd) {
    case 0xb0 ... 0xcf:
	return 1;
    }
    return 0;
}

int sb_fifo_enabled(void)
{
    if (!sb.dma_cmd)
	error("SB: used inactive DMA (fifo)\n");
    if (sb_dma_sb16mode())
	return (sb.dma_cmd & 2) ? 1 : 0;
    /* is this correct? */
    return 1;
}

int sb_dma_samp_signed(void)
{
    if (!sb.dma_cmd)
	error("SB: used inactive DMA (signed)\n");
    if (sb_dma_sb16mode())
	return (sb.dma_mode & 16) ? 1 : 0;
    return 0;
}

int sb_dma_samp_stereo(void)
{
    if (!sb.dma_cmd)
	error("SB: used inactive DMA (stereo)\n");
    if (sb_dma_sb16mode())
	return (sb.dma_mode & 32) ? 1 : 0;
    return (sb.mixer_regs[0x0e] & 2) ? 1 : 0;
}

int sb_dma_input(void)
{
    if (!sb.dma_cmd)
	error("SB: used inactive DMA (input)\n");
    if (sb_dma_sb16mode())
	return (sb.dma_cmd & 8) ? 1 : 0;
    switch (sb.dma_cmd) {
    case 0x24:
    case 0x2c:
    case 0x98:
    case 0x99:
    case 0xe2:			// weird case!
	return 1;
    }
    return 0;
}

int sb_dma_silence(void)
{
    if (!sb.dma_cmd)
	error("SB: used inactive DMA (silence)\n");
    switch (sb.dma_cmd) {
    case 0x80:
	return 1;
    }
    return 0;
}

static int sb_dma_internal(void)
{
    if (!sb.dma_cmd)
	error("SB: used inactive DMA (internal)\n");
    switch (sb.dma_cmd) {
    case 0xe2:
	return 1;
    }
    return 0;
}

int sb_input_enabled(void)
{
    return !sb_dma_internal();
}

static int sb_dma_autoinit(void)
{
    if (!sb.dma_cmd)
	error("SB: used inactive DMA (ai)\n");
#if 0
    /* if speaker state is not consistent with direction, don't autoinit - correct? */
    if (!(sb_dma_input() ^ sb.speaker))
	return 0;
#endif
    if (sb.dma_exit_ai)
	return 0;
    if (sb_dma_sb16mode())
	return (sb.dma_cmd & 4) ? 1 : 0;
    switch (sb.dma_cmd) {
    case 0x1c:
    case 0x1f:
    case 0x2c:
    case 0x7d:
    case 0x7f:
    case 0x90:
    case 0x98:
	return 1;
    }
    return 0;
}

int sb_get_dma_sampling_rate(void)
{
    int sample_rate;
    if (sb_dma_internal())	// that's because we don't zero sb.rate at reset...
	return 0;	// 0 is checked in dspio.c... hacks hacks :((
    sample_rate = sb.rate;
    if (!sb_dma_sb16mode())
	sample_rate >>= sb_dma_samp_stereo();
    return sample_rate;
}

static int sb_dma_high_speed(void)
{
    if (!sb.dma_cmd)
	error("SB: used inactive DMA (hs)\n");
    switch (sb.dma_cmd) {
    case 0x90:
    case 0x91:
    case 0x98:
    case 0x99:
	return 1;
    }
    return 0;
}

/* some sb-midi decoding */
static int sb_midi_input(void)
{
    switch (sb.midi_cmd) {
    case 0x30 ... 0x37:
	return 1;
    }
    return 0;
}

static int sb_midi_uart(void)
{
    switch (sb.midi_cmd) {
    case 0x34 ... 0x37:
	return 1;
    }
    return 0;
}

static int sb_midi_timestamp(void)
{
    switch (sb.midi_cmd) {
    case 0x32:
    case 0x33:
    case 0x36:
    case 0x37:
	return 1;
    }
    return 0;
}

static int sb_midi_int(void)
{
    switch (sb.midi_cmd) {
    case 0x31:
    case 0x33:
    case 0x35:
    case 0x37:
	return 1;
    }
    return 0;
}

static void start_dma_clock(void)
{
    /* this was likely needed for KryptEgg game, see commit 5741ac14 */
    sb.busy = 2;

    dspio_start_dma(sb.dspio);
}

static void stop_dma_clock(void)
{
    dspio_stop_dma(sb.dspio);

    sb.busy = 1;
}

static void stop_dma(void)
{
    /* first reset dma_cmd, as dspio may query it from dspio_stop_dma() */
    sb.dma_cmd = 0;
    stop_dma_clock();
}

static void sb_dma_actualize(void)
{
    if (sb.new_dma_cmd) {
	S_printf("SB: Actualized command %#x\n", sb.new_dma_cmd);
	sb.dma_cmd = sb.new_dma_cmd;
	sb.dma_mode = sb.new_dma_mode;
	/* count is reloaded in sb_dma_start() */
	sb.new_dma_cmd = 0;
	sb.new_dma_mode = 0;
	sb.dma_exit_ai = 0;
    }
}

static void sb_dma_start(void)
{
    sb.dma_restart.val = DMA_RESTART_NONE;
    sb.paused = 0;
    sb_dma_actualize();
    if (sb_dma_active()) {
	sb.dma_count = sb.dma_init_count;
	S_printf("SB: DMA transfer started, count=%i\n",
		 sb.dma_init_count);
	S_printf("SB: sample params: rate=%i, stereo=%i, signed=%i 16bit=%i\n",
		 sb_get_dma_sampling_rate(), sb_dma_samp_stereo(),
		 sb_dma_samp_signed(), sb_dma_16bit());
	start_dma_clock();
    }
}

static int sb_irq_active(int type)
{
    return (sb.mixer_regs[0x82] & type);
}

static void sb_request_irq(int type)
{
    if (type & SB_IRQ_DSP)
	pic_request(sb_get_dsp_irq_num());
    if (type & SB_IRQ_MPU401)
	pic_request(config.mpu401_irq);
}

static void sb_activate_irq(int type)
{
    S_printf("SB: Activating irq type %d\n", type);
    if (sb_irq_active(type)) {
	S_printf("SB: Warning: Interrupt already active!\n");
	return;
    }
    sb_request_irq(type);
    sb.mixer_regs[0x82] |= type;
}

static void sb_deactivate_irq(int type)
{
    uint32_t act_map;
    int mpu_irq = config.mpu401_irq;

    S_printf("SB: Deactivating irq type %d\n", type);
    if (!(sb.mixer_regs[0x82] & type)) {
	S_printf("SB: Warning: Interrupt not active!\n");
	return;
    }
    sb.mixer_regs[0x82] &= ~type;
    /* if dsp and mpu irqs are the same, untrigger only when
     * both are inactive */
    act_map = ((!!(sb.mixer_regs[0x82] & SB_IRQ_DSP)) <<
	    sb_get_dsp_irq_num()) |
	    ((!!(sb.mixer_regs[0x82] & SB_IRQ_MPU401)) << mpu_irq);
    if (type & SB_IRQ_DSP) {
	if (!(act_map & (1 << sb_get_dsp_irq_num())))
	    pic_untrigger(sb_get_dsp_irq_num());
    }
    if (type & SB_IRQ_MPU401) {
	if (!(act_map & (1 << mpu_irq)))
	    pic_untrigger(mpu_irq);
    }
}

static void sb_run_irq(int type)
{
    if (!sb_irq_active(type))
	return;
    S_printf("SB: Run irq type %d\n", type);
    if (type & SB_IRQ_DSP)
	pic_untrigger(sb_get_dsp_irq_num());
    if (type & SB_IRQ_MPU401)
	pic_untrigger(config.mpu401_irq);
    sb_request_irq(type);
}

static void sb_dma_activate(void)
{
    sb.new_dma_cmd = sb.command[0];
    sb.new_dma_mode = sb.command[1];
    if (sb_irq_active(SB_IRQ_DSP)) {
	sb.dma_restart.val = DMA_RESTART_PENDING;
	S_printf("SB: IRQ still active, DMA command %#x pending\n",
		 sb.new_dma_cmd);
    } else
    /* a weird logic to fix Speedy game: if DMA have never advanced
     * (because channel is masked), forget current autoinit and
     * actualize the new settings. To not introduce the race condition
     * for the programs that rely on pending settings, we prolong
     * the busy status till missing DMA DACK. */
    if (!sb_dma_active() || sb.dma_restart.allow) {
	S_printf("SB: starting DMA transfer\n");
	sb_dma_start();
    } else {
	S_printf("SB: DMA command %#x pending, current=%#x\n",
		 sb.new_dma_cmd, sb.dma_cmd);
    }
}

void sb_handle_dma(void)
{
    sb.dma_count--;
    sb.dma_restart.allow = 0;
    if (sb.dma_count == 0xffff) {
	sb.dma_count = sb.dma_init_count;
	sb.dma_restart.allow = 1;
	/* testsb16 will lock up if IRQ is raised on E2 */
	if (!sb_dma_internal()) {
	    S_printf("SB: Done block, triggering IRQ\n");
	    sb_activate_irq(sb_dma_16bit()? SB_IRQ_16BIT : SB_IRQ_8BIT);
	}
	if (!sb_dma_autoinit()) {
	    S_printf("SB: DMA transfer completed\n");
	    if (!sb_dma_internal())
		sb.dma_restart.is_16 = sb_dma_16bit();
	    stop_dma();
	} else if (sb_fifo_enabled()) {
	    /* remember autoinit state here coz it may change
	     * before the ACK code to check it */
	    sb.dma_restart.val = DMA_RESTART_AUTOINIT;
	    sb.dma_restart.is_16 = sb_dma_16bit();
	    /* auto-init & FIFO - stop till IRQ-ACK */
	    stop_dma_clock();
	} else {
	    /* auto-init & !FIFO - apply new parameters and continue */
	    S_printf("SB: FIFO not enabled, continuing transfer\n");
	    sb_dma_actualize();
	}
    }
    if (sb.busy == 2)
	sb.busy = 1;
}

void sb_dma_nack(void)
{
    /* speedy reprograms DSP without exiting auto-init
     * but when the DMA channel is masked. We need to downgrade
     * the busy status from 2 to 1. */
    if (sb.busy == 2)
	sb.busy = 1;
}

void sb_handle_dma_timeout(void)
{
    stop_dma();
}

static void dsp_write_output(uint8_t value)
{
    rng_put(&sb.dsp_queue, &value);
    if (debug_level('S') >= 2) {
	S_printf("SB: Insert into output Queue [%u]... (0x%x)\n",
		 rng_count(&sb.dsp_queue), value);
    }
}

static void sb_dsp_reset(void)
{
    S_printf("SB: Resetting SB DSP\n");

    stop_dma();
    dspio_toggle_speaker(sb.dspio, 0);
    dspio_clear_fifos(sb.dspio);
    rng_clear(&sb.dsp_queue);
    sb_deactivate_irq(SB_IRQ_ALL);
    sb.paused = 0;
    sb.midi_cmd = 0;
    sb.dma_mode = 0;
    sb.new_dma_cmd = 0;
    sb.new_dma_mode = 0;
    sb.dma_exit_ai = 0;
    sb.dma_restart.val = DMA_RESTART_NONE;
    sb.dma_restart.is_16 = 0;
    sb.dma_restart.allow = 0;
    sb.dma_init_count = 0;
    sb.dma_count = 0;
    sb.command_idx = 0;
    sb.E2Count = 0;
    sb.last_data = 0;
    sb.reset_val = 0xaa;
    sb.rate = 32000;	// NFS in sbpro mode needs this rate
/* the following must not be zeroed out */
#if 0
    sb.mixer_index = 0;
#endif
}

static void sb_dsp_soft_reset(unsigned char value)
{
    if (value & 1) {
	if (!sb.reset) {
	    sb_deactivate_irq(SB_IRQ_ALL);
	    sb.reset = 1;

	    if (sb_dma_active() && sb_dma_high_speed()) {
		/* for High-Speed mode reset means only exit High-Speed */
		S_printf("SB: Reset called, exiting High-Speed DMA mode\n");
		stop_dma();
	    } else if (sb_midi_uart()) {
		S_printf("SB: Reset called, exiting UART midi mode\n");
		sb.midi_cmd = 0;
	    } else {
		sb_dsp_reset();
		sb.busy = 2;
	    }
	}
    } else {
	if (sb.reset) {
	    sb.reset = 0;
	    dsp_write_output(sb.reset_val);
	    sb.busy = 1;
	}
    }
}

static void sb_mixer_reset(void)
{
    memset(sb.mixer_regs, 0, 0x48);
    /* Restore values as per Creative specs */
    sb.mixer_regs[0x0a] = 0;	/* -48 dB */
    sb.mixer_regs[0x04] =
    sb.mixer_regs[0x22] =
    sb.mixer_regs[0x26] = 0xcc;	/* -12 dB */
    sb.mixer_regs[0x28] =
    sb.mixer_regs[0x2e] = 0;	/* -60 dB */

    sb.mixer_regs[0x30] =
    sb.mixer_regs[0x31] =
    sb.mixer_regs[0x32] =
    sb.mixer_regs[0x33] =
    sb.mixer_regs[0x34] =
    sb.mixer_regs[0x35] = 24 << 3;	/* -14 dB */

    sb.mixer_regs[0x36] =
    sb.mixer_regs[0x37] =
    sb.mixer_regs[0x38] =
    sb.mixer_regs[0x39] =
    sb.mixer_regs[0x3a] = 0;	/* -62 dB */

    sb.mixer_regs[0x3b] = 0;	/* -18 dB */

    sb.mixer_regs[0x3c] = 0x1f;	/* line, cd, mic output - on */
    sb.mixer_regs[0x3d] = 0x15;	/* line.L, cd.L, mic - on */
    sb.mixer_regs[0x3e] = 0x0b;	/* line.R, cd.R, mic - on */

    sb.mixer_regs[0x3f] =
    sb.mixer_regs[0x40] =
    sb.mixer_regs[0x41] =
    sb.mixer_regs[0x42] = 0;	/* 0 dB */

    sb.mixer_regs[0x44] =
    sb.mixer_regs[0x45] =
    sb.mixer_regs[0x46] =
    sb.mixer_regs[0x47] = 8 << 4;	/* 0 dB */
}

static int num_to_idx(int num, int arr[], int len)
{
    int i;
    for (i = 0; i < len; i++) {
	if (arr[i] == num)
	    return 1 << i;
    }
    return 0;
}

static void sb_mixer_init(void)
{
    int irq_idx, dma_idx, hdma_idx;

    S_printf("SB: Mixer init\n");

    memset(sb.mixer_regs, 0, sizeof(sb.mixer_regs));

    irq_idx = num_to_idx(config.sb_irq, sb_irq_tab, ARRAY_SIZE(sb_irq_tab));
    if (!irq_idx) {
	error("Sound Blaster cannot work on IRQ %i\n", config.sb_irq);
	config.exitearly = 1;
    }
    if (!num_to_idx(config.sb_dma, sb_dma_tab, ARRAY_SIZE(sb_dma_tab))) {
	error("Sound Blaster cannot work on DMA %i\n", config.sb_dma);
	config.exitearly = 1;
    }
    dma_idx = 1 << config.sb_dma;
    if (config.sb_hdma
	&& !num_to_idx(config.sb_hdma, sb_hdma_tab,
		       ARRAY_SIZE(sb_hdma_tab))) {
	error("Sound Blaster cannot work on HDMA %i\n", config.sb_hdma);
	config.exitearly = 1;
    }
    hdma_idx = config.sb_hdma ? 1 << config.sb_hdma : 0;
    sb.mixer_regs[0x80] = irq_idx;
    sb.mixer_regs[0x81] = dma_idx | hdma_idx;
    sb.mixer_regs[0x82] = SB16_ID82;
}

static void sb_reset(void)
{
    sb_dsp_reset();
    sb.busy = 1;
    sb_mixer_reset();
    adlib_reset();
    mpu401_reset(sb.mpu);
}

static void do_process_midi(void)
{
    Bit8u data;

    if (sb_midi_input()) {
	int cnt = 0;
	while (midi_get_data_byte(&data)) {
	    process_sb_midi_input(data);
	    cnt++;
	}
	if (cnt && sb_midi_int())
	    sb_activate_irq(SB_IRQ_MIDI);
    }
}

/*
 * DANG_BEGIN_FUNCTION sb_dsp_write
 *
 * arguments:
 * value - The value being written to the DSP.
 *
 * DANG_END_FUNCTION
 */
static void sb_dsp_write(Bit8u value)
{
#define REQ_PARAMS(i) if (sb.command_idx <= i) return
#define PAR_LSB_MSB(i) (sb.command[i] | (sb.command[i+1] << 8))
#define PAR_MSB_LSB(i) ((sb.command[i] << 8) | sb.command[i+1])

    if (sb.command_idx < SB_DSP_CMD_BUF_SZ) {
	S_printf("SB: Accepted byte %#x, index=%i\n", value,
		 sb.command_idx);
	sb.command[sb.command_idx++] = value;
    } else {
	S_printf("SB: ERROR: command buffer overflowed!\n");
    }

    switch (sb.command[0]) {
	/* 0x04: ??? - SB16ASP */
    case 0x04:
	REQ_PARAMS(1);
	S_printf("SB: Unsupported DSP command 4\n");
	if ((sb.command[1] & 0xf1) == 0xf1)
	    sb.asp_init = 1;
	else
	    sb.asp_init = 0;
	break;

	/* 0x05: ??? - SB16ASP */
    case 0x05:
	REQ_PARAMS(2);
	S_printf("SB: Unsupported DSP command 5\n");
	break;

	/* 0x08: ??? - SB16ASP */
    case 0x08:
	REQ_PARAMS(1);
	S_printf("SB: Unsupported DSP command 8\n");
	if (sb.command[1] == 3)
	    dsp_write_output(0x18);
	break;

	/* 0x0e: ??? - SB16ASP */
    case 0x0e:
	REQ_PARAMS(2);
	S_printf("SB: Unsupported DSP command e\n");
	sb.asp_regs[sb.command[1]] = sb.command[2];
	break;

	/* 0x0f: ??? - SB16ASP */
    case 0x0f:
	REQ_PARAMS(1);
	S_printf("SB: Unsupported DSP command f\n");
	if (sb.asp_init)
	    sb.asp_regs[sb.command[1]] = ~sb.asp_regs[sb.command[1]];
	dsp_write_output(sb.asp_regs[sb.command[1]]);
	break;

    case 0x10:
	/* Direct 8-bit DAC - SB */
	REQ_PARAMS(1);
	S_printf("SB: Direct 8-bit DAC write (%u)\n", sb.command[1]);
	dspio_write_dac(sb.dspio, sb.command[1]);
	break;

	/* == OUTPUT COMMANDS == */
	/* DMA 8-bit DAC - SB */
    case 0x14:
	/* DMA 2-bit ADPCM DAC - SB */
    case 0x16:
	/* DMA 2-bit ADPCM DAC (Reference) - SB */
    case 0x17:
	/* DMA 8-bit ADC - SB */
    case 0x24:
	/* DMA 4-bit ADPCM DAC - SB */
    case 0x74:
	/* DMA 4-bit ADPCM DAC (Reference) - SB */
    case 0x75:
	/* DMA 2.6-bit ADPCM DAC - SB */
    case 0x76:
	/* DMA 2.6-bit ADPCM DAC (Reference) - SB */
    case 0x77:
	REQ_PARAMS(2);
	sb.dma_init_count = PAR_LSB_MSB(1);
	sb_dma_activate();
	break;

	/* DMA 8-bit DAC (Auto-Init) - SB2.0 */
    case 0x1C:
	/* DMA 8-bit ADC (Auto-Init) - SB2.0 */
    case 0x2C:
	/* DMA 8-bit DAC (High Speed, Auto-Init) - SB2.0-Pro2 */
    case 0x90:
	/* **CRISK** DMA-8 bit DAC (High Speed) */
    case 0x91:
	/* DMA 2-bit ADPCM DAC (Reference, Auto-Init) - SB2.0 */
    case 0x1F:
	/* DMA 4-bit ADPCM DAC (Auto-Init, Reference) - SB2.0 */
    case 0x7D:
	/* DMA 2.6-bit ADPCM DAC (Auto-Init, Reference) - SB2.0 */
    case 0x7F:
	/* DMA 8-bit ADC (High Speed, Auto-Init) - SB2.0-Pro2 */
    case 0x98:
	/* DMA 8-bit ADC (High Speed) - SB2.0-Pro2 */
    case 0x99:
	if (!sb.dma_init_count) {
	    REQ_PARAMS(2);
	    sb.dma_init_count = PAR_LSB_MSB(1);
	    S_printf("SB: DMA count is now set to %d\n",
		     sb.dma_init_count);
	} else {
	    S_printf("SB: Re-using DMA count, set to %d\n",
		     sb.dma_init_count);
	}
	sb_dma_activate();
	break;

	/* == INPUT COMMANDS == */
    case 0x20:
	/* Direct 8-bit ADC - SB */
	S_printf("SB: 8-bit ADC (Unimplemented)\n");
	dsp_write_output(0);
	/* I'll implement that, be patient */
	break;

    case 0x28:
	/* Direct 8-bit ADC (Burst) - SBPro2 */
	dsp_write_output(0);
	break;

	/* == MIDI == */
    case 0x30:			/* Enter polling midi input mode */
    case 0x31:			/* Enter interruptible midi input mode */
    case 0x32:			/* Midi Read Timestamp Poll - SB ? */
    case 0x33:			/* Midi Read Timestamp Interrupt - SB ? */
    case 0x34:			/* Enter midi polling UART mode */
    case 0x35:			/* Enter midi interruptible UART mode */
    case 0x36:			/* Enter midi polling UART mode, with timestamping */
    case 0x37:			/* Enter midi interruptible UART mode, with timestamping */
	if (sb_midi_input() && sb_midi_int() && !sb_midi_uart()
	    && sb.command[1] == sb.midi_cmd) {
	    S_printf("SB: Exiting SB-midi mode %#x\n", sb.midi_cmd);
	    sb.midi_cmd = 0;
	    return;
	}
	S_printf("SB: SB Midi command %#x accepted\n", sb.command[1]);
	sb.midi_cmd = sb.command[1];
	break;

    case 0x38:			/* Midi Write */
	REQ_PARAMS(1);
	S_printf("SB: Write 0x%x to SB Midi Port\n", sb.command[1]);
	midi_write(sb.command[1], ST_GM);
	break;

	/* == SAMPLE SPEED == */
    case 0x40:
	/* Set Time Constant */
	REQ_PARAMS(1);
	sb.rate = 1000000 / (256 - sb.command[1]);
	S_printf("SB: Time constant set to %u\n", sb.command[1]);
	break;

    case 0x41:
	/* Set Output Sample Rate - SB16 */
	REQ_PARAMS(2);
	/* MSB, LSB values - Karcher */
	sb.rate = PAR_MSB_LSB(1);
	S_printf("SB: Output sampling rate set to %u Hz\n", sb.rate);
	break;

    case 0x42:
	/* Set Input Sample Rate - SB16 */
	REQ_PARAMS(2);
	/* MSB, LSB values - Karcher */
	sb.rate = PAR_MSB_LSB(1);
	S_printf("SB: Input sampling rate set to %u Hz\n", sb.rate);
	break;

	/* == OUTPUT == */
#if 0
	/* not sb16 */
	/* Continue Auto-Init 8-bit DMA - SB16 */
    case 0x45:
	/* Continue Auto-Init 16-bit DMA - SB16 */
    case 0x47:
	if (sb.paused) {
	    S_printf("SB: Unpausing DMA, left=%i\n", sb.dma_count);
	    sb.paused = 0;
	    if (sb.dma_cmd)
		start_dma_clock();
	}
	break;
#endif
    case 0x48:
	/* Set DMA Block Size - SB2.0 */
	REQ_PARAMS(2);
	sb.dma_init_count = PAR_LSB_MSB(1);
	S_printf("SB: DMA count is set to %d\n", sb.dma_init_count);
	break;

    case 0x80:
	/* Silence DAC - SB */
	REQ_PARAMS(2);
	sb.dma_init_count = PAR_LSB_MSB(1);
	S_printf("SB: Silence count is set to %d\n",
		 sb.dma_init_count);
	sb_dma_activate();
	break;

#if 0
	/* Those are not available in SB16 */
	/* == STEREO MODE == */
    case 0xA0:
	/* Enable Mono Input - SBPro Only */
	S_printf("SB: Disable Stereo Input\n");
	break;

    case 0xA8:
	/* Enable Stereo Input - SBPro Only */
	S_printf("SB: Enable Stereo Input\n");
	break;
#endif

	/* == SB16 DMA == */
    case 0xB0 ... 0xCF:
	if (sb.command[0] & 1) {
	    S_printf("SB: Bad command\n");
	    break;
	}
	/* SB16 16-bit DMA */
	REQ_PARAMS(3);
	sb.dma_init_count = PAR_LSB_MSB(2);
	if (sb.command[0] & 8) {
	    S_printf("SB: Starting SB16 16-bit DMA input.\n");
	    dspio_toggle_speaker(sb.dspio, 0);
	} else {
	    S_printf("SB: Starting SB16 16-bit DMA output.\n");
	    dspio_toggle_speaker(sb.dspio, 1);
	}
	sb_dma_activate();
	break;

	/* == DMA == */
	/* Halt 8-bit DMA - SB */
    case 0xD0:
	if (!sb.paused) {
	    S_printf("SB: Pausing 8bit DMA, left=%i\n", sb.dma_count);
	    sb_deactivate_irq(SB_IRQ_8BIT);
	    sb.paused = 1;
	    if (sb.dma_cmd)
		stop_dma_clock();
	}
	break;
	/* Halt 16-bit DMA - SB16 */
    case 0xD5:
	if (!sb.paused) {
	    S_printf("SB: Pausing 16bit DMA, left=%i\n", sb.dma_count);
	    sb_deactivate_irq(SB_IRQ_16BIT);
	    sb.paused = 1;
	    if (sb.dma_cmd)
		stop_dma_clock();
	}
	break;

	/* == SPEAKER == */
    case 0xD1:
	/* Enable Speaker - SB */
	S_printf("SB: Enabling speaker\n");
	dspio_toggle_speaker(sb.dspio, 1);
	break;

    case 0xD3:
	/* Disable Speaker - SB */
	S_printf("SB: Disabling speaker\n");
	dspio_toggle_speaker(sb.dspio, 0);
	break;

	/* == DMA == */
	/* Continue 8-bit DMA - SB */
    case 0xD4:
	/* Continue 16-bit DMA - SB16 */
    case 0xD6:
	if (sb.paused) {
	    S_printf("SB: Unpausing 16bit DMA, left=%i\n", sb.dma_count);
	    sb.paused = 0;
	    if (sb.dma_cmd)
		start_dma_clock();
	}
	break;

	/* == SPEAKER == */
    case 0xD8:
	/* Speaker Status */
	dsp_write_output(dspio_get_speaker_state(sb.dspio));
	break;

	/* == DMA == */
	/* Exit Auto-Init 16-bit DMA - SB16 */
    case 0xD9:
	/* Exit Auto-Init 8-bit DMA - SB2.0 */
    case 0xDA:
	S_printf("SB: Exiting DMA autoinit\n");
	if (sb.dma_cmd)
	    sb.dma_exit_ai = 1;
	break;

	/* == DSP IDENTIFICATION == */
    case 0xE0:
	/* DSP Identification - SB2.0 */
	REQ_PARAMS(1);
	S_printf("SB: Identify SB2.0 DSP.\n");
	dsp_write_output(~sb.command[1]);
	break;

    case 0xE1:
	/* DSP Version - SB */
	S_printf("SB: Query Version\n");
	dsp_write_output(SB16_ID >> 8);
	dsp_write_output(SB16_ID & 0xFF);
	break;

    case 0xE2: {
	    /*
	     * Identification via DMA - completely undocumented.
	     * Based on the code of VDMSound project,
	     * http://www.sourceforge.net/projects/vdmsound/
	     */
	    const Bit8u E2_negmsk = 3;
	    const Bit8u E2_incmagic = 0x96;
	    int i;
	    Bit8u negmsk, inctmp, incmagic, incval;

	    REQ_PARAMS(1);
	    S_printf("SB: E2 identification starting\n");

	    negmsk = E2_negmsk;
	    inctmp = 1;
	    incval = 0;
	    for (i = 0; i < 8; i++) {
		negmsk <<= (i & 1) + 1;
		negmsk |= negmsk >> 4;
		negmsk &= 0x0f;
		if ((1 << i) & sb.command[1])
		    incval +=
			((1 << sb.E2Count) & negmsk) ? -inctmp : inctmp;
		inctmp <<= 1;
	    }
	    incmagic = E2_incmagic;
	    if (sb.E2Count & 1)
		incmagic += 0x0f;
	    if (sb.E2Count & 2)
		incmagic = (incmagic << 4) | (incmagic >> 4);
	    incval += incmagic;
	    sb.E2Count++;
	    sb.E2Count &= 3;

	    sb.reset_val += incval;
	    sb_dma_activate();

	    break;
	}

    case 0xE3: {
	    char cs[] = "COPYRIGHT (C) CREATIVE TECHNOLOGY LTD, 1992.";
	    rng_add(&sb.dsp_queue, sizeof(cs), cs);
	}
	break;

	/* == TEST == */
    case 0xE4:
	/* Write to Test - SB2.0 */
	REQ_PARAMS(1);
	sb.test = sb.command[1];
	S_printf("SB: write 0x%x to test register.\n", sb.test);
	break;

    case 0xE8:
	/* Read from Test - SB2.0 */
	S_printf("SB: Read 0x%x from test register.\n", sb.test);
	dsp_write_output(sb.test);
	break;

    case 0xF0:
	/* Sine Generator - SB */
	break;

	/* == IRQ == */
	/* 8-bit IRQ - SB */
    case 0xF2:
	S_printf("SB: Activating 8bit IRQ\n");
	sb.irq_pend |= SB_IRQ_8BIT;
	break;

	/* 16-bit IRQ - SB16 */
    case 0xF3:
	S_printf("SB: Activating 16bit IRQ\n");
	sb.irq_pend |= SB_IRQ_8BIT;
	break;

    case 0xf9:			/* from bochs */
	REQ_PARAMS(1);
	S_printf("SB: F9 test\n");
	switch (sb.command[1]) {
	case 0x0e:
	    dsp_write_output(0xff);
	    break;
	case 0x0f:
	    dsp_write_output(0x07);
	    break;
	case 0x37:
	    dsp_write_output(0x38);
	    break;
	default:
	    dsp_write_output(0);
	}
	break;

    case 0xfa: // Undoc'd SBPro II, from SoundFX
	// SB16 puts 0xfb in output buffer but no ready status
	S_printf("SB: Undocumented 0xfa\n");
	sb.last_data = 0xfb;
	break;

	/* 0xFB: DSP Status - SB16 */
	/* 0xFC: DSP Auxiliary Status - SB16 */
    case 0xfd: /* DSP Command Status - SB16, from SoundFX */
	S_printf("SB: Undocumented 0xfd\n");
	dsp_write_output(sb.command[0]);
	break;

    default:
	S_printf("SB: ERROR: Unsupported command\n");
    }

    sb.command_idx = 0;
}

static int line_enabled(void)
{
    return ((sb.mixer_regs[0x3c] | sb.mixer_regs[0x3d] |
	    sb.mixer_regs[0x3e]) & 0x18);
}

static int mic_enabled(void)
{
    return ((sb.mixer_regs[0x3c] | sb.mixer_regs[0x3d] |
	    sb.mixer_regs[0x3e]) & 0x1);
}

static void sb_mixer_write(Bit8u value)
{
    Bit8u delta = sb.mixer_regs[sb.mixer_index] ^ value;
    S_printf("SB: write mixer reg %#x val=%#x\n", sb.mixer_index, value);
    sb.mixer_regs[sb.mixer_index] = value;
    switch (sb.mixer_index) {
    case 0:
	sb_mixer_reset();
	break;

    case 0x04:
	sb.mixer_regs[0x32] = (value & 0xf0) | 8;
	sb.mixer_regs[0x33] = (value << 4) | 8;
	break;

    case 0x0A:
	sb.mixer_regs[0x3A] = (value << 5) | 0x18;
	break;

    case 0x22:
	sb.mixer_regs[0x30] = (value & 0xf0) | 8;
	sb.mixer_regs[0x31] = (value << 4) | 8;
	break;

    case 0x26:
	sb.mixer_regs[0x34] = (value & 0xf0) | 8;
	sb.mixer_regs[0x35] = (value << 4) | 8;
	break;

    case 0x28:
	sb.mixer_regs[0x36] = (value & 0xf0) | 8;
	sb.mixer_regs[0x37] = (value << 4) | 8;
	break;

    case 0x2E:
	sb.mixer_regs[0x38] = (value & 0xf0) | 8;
	sb.mixer_regs[0x39] = (value << 4) | 8;
	break;

    case 0x38:
    case 0x39:
	if (line_enabled() && (sb.mixer_regs[0x38] || sb.mixer_regs[0x39]))
	    dspio_input_enable(sb.dspio, MC_LINE);
	else
	    dspio_input_disable(sb.dspio, MC_LINE);
	break;

    case 0x3a:
	if (mic_enabled() && sb.mixer_regs[0x3a])
	    dspio_input_enable(sb.dspio, MC_MIC);
	else
	    dspio_input_disable(sb.dspio, MC_MIC);
	break;

    case 0x3c:
    case 0x3d:
    case 0x3e:
	if (delta & 0x18) {
	    if (line_enabled() && (sb.mixer_regs[0x38] || sb.mixer_regs[0x39]))
		dspio_input_enable(sb.dspio, MC_LINE);
	    else
		dspio_input_disable(sb.dspio, MC_LINE);
	}

	if (delta & 0x1) {
	    if (mic_enabled() && sb.mixer_regs[0x3a])
		dspio_input_enable(sb.dspio, MC_MIC);
	    else
		dspio_input_disable(sb.dspio, MC_MIC);
	}
	break;

    case 0xff:
	/* sb16snd.drv insists on 0xff in this register, otherwise it
	 * starts to reconfigure DMA numbers via 0x81 */
	sb.mixer_regs[sb.mixer_index] = 0xff;
	break;
    }
}

static Bit8u sb_mixer_read(void)
{
    Bit8u val;
    S_printf("SB: Reading Mixer register %#x\n", sb.mixer_index);
    switch (sb.mixer_index) {
    case 0x04:
	val = (sb.mixer_regs[0x32] & 0xf0) | (sb.mixer_regs[0x33] >> 4);
	break;

    case 0x0A:
	val = (sb.mixer_regs[0x3A] >> 5);
	break;

    case 0x22:
	val = (sb.mixer_regs[0x30] & 0xf0) | (sb.mixer_regs[0x31] >> 4);
	break;

    case 0x26:
	val = (sb.mixer_regs[0x34] & 0xf0) | (sb.mixer_regs[0x35] >> 4);
	break;

    case 0x28:
	val = (sb.mixer_regs[0x36] & 0xf0) | (sb.mixer_regs[0x37] >> 4);
	break;

    case 0x2E:
	val = (sb.mixer_regs[0x38] & 0xf0) | (sb.mixer_regs[0x39] >> 4);
	break;

    default:
	val = sb.mixer_regs[sb.mixer_index];
	break;
    }
    return val;
}

static double vol5h(int reg)
{
    return dspio_calc_vol(sb.mixer_regs[reg] >> 3, 2, -62);
}

static double vol2h(int reg)
{
    return dspio_calc_vol(sb.mixer_regs[reg] >> 6, 6, -18);
}

static double vol3l(int reg)
{
    return dspio_calc_vol(sb.mixer_regs[reg] & 7, 6, -42);
}

static double gain2h(int reg)
{
    return dspio_calc_vol(sb.mixer_regs[reg] >> 6, 6, 0);
}

#define ENAB(r, b) \
    if (!(sb.mixer_regs[r] & (1 << (b)))) \
	return MR_DISABLED

enum MixRet sb_mixer_get_input_volume(enum MixChan ch, enum MixSubChan sc,
	double *r_vol)
{
    double vol = 1;
    switch (ch) {
    case MC_MIDI:
	switch (sc) {
	case MSC_L:
	    ENAB(0x3d, 6);
	    vol *= vol5h(0x34);
	    break;
	case MSC_R:
	    ENAB(0x3e, 5);
	    vol *= vol5h(0x35);
	    break;
	case MSC_LR:
	    ENAB(0x3e, 6);
	    vol *= vol5h(0x34);
	    break;
	case MSC_RL:
	    ENAB(0x3d, 5);
	    vol *= vol5h(0x35);
	    break;
	default:
	    return MR_UNSUP;
	}
	break;
    case MC_CD:
	switch (sc) {
	case MSC_L:
	    ENAB(0x3d, 2);
	    vol *= vol5h(0x36);
	    break;
	case MSC_R:
	    ENAB(0x3e, 1);
	    vol *= vol5h(0x37);
	    break;
	case MSC_LR:
	    ENAB(0x3e, 2);
	    vol *= vol5h(0x36);
	    break;
	case MSC_RL:
	    ENAB(0x3d, 1);
	    vol *= vol5h(0x37);
	    break;
	default:
	    return MR_UNSUP;
	}
	break;
    case MC_LINE:
	switch (sc) {
	case MSC_L:
	    ENAB(0x3d, 4);
	    vol *= vol5h(0x38);
	    break;
	case MSC_R:
	    ENAB(0x3e, 3);
	    vol *= vol5h(0x39);
	    break;
	case MSC_LR:
	    ENAB(0x3e, 4);
	    vol *= vol5h(0x38);
	    break;
	case MSC_RL:
	    ENAB(0x3d, 3);
	    vol *= vol5h(0x39);
	    break;
	default:
	    return MR_UNSUP;
	}
	break;
    case MC_MIC:
	switch (sc) {
	case MSC_MONO_L:
	    ENAB(0x3d, 0);
	    vol *= vol3l(0x3a);
	    break;
	case MSC_MONO_R:
	    ENAB(0x3e, 0);
	    vol *= vol3l(0x3a);
	    break;
	default:
	    return MR_UNSUP;
	}
	break;
    default:
	return MR_UNSUP;
    }

    switch (sc) {
    case MSC_L:
    case MSC_RL:
    case MSC_MONO_L:
	vol *= gain2h(0x3f);
	break;
    case MSC_R:
    case MSC_LR:
    case MSC_MONO_R:
	vol *= gain2h(0x40);
	break;
    }

    *r_vol = vol;
    return MR_OK;
}

enum MixRet sb_mixer_get_output_volume(enum MixChan ch, enum MixSubChan sc,
	double *r_vol)
{
    double vol = 1;
    switch (ch) {
    case MC_MIDI:
	switch (sc) {
	case MSC_L:
	    vol *= vol5h(0x34);
	    break;
	case MSC_R:
	    vol *= vol5h(0x35);
	    break;
	default:
	    return MR_UNSUP;
	}
	break;
    case MC_CD:
	switch (sc) {
	case MSC_L:
	    ENAB(0x3c, 2);
	    vol *= vol5h(0x36);
	    break;
	case MSC_R:
	    ENAB(0x3c, 1);
	    vol *= vol5h(0x37);
	    break;
	default:
	    return MR_UNSUP;
	}
	break;
    case MC_LINE:
	switch (sc) {
	case MSC_L:
	    ENAB(0x3c, 4);
	    vol *= vol5h(0x38);
	    break;
	case MSC_R:
	    ENAB(0x3c, 3);
	    vol *= vol5h(0x39);
	    break;
	default:
	    return MR_UNSUP;
	}
	break;
    case MC_MIC:
	switch (sc) {
	case MSC_MONO_L:
	case MSC_MONO_R:
	    ENAB(0x3c, 0);
	    vol *= vol3l(0x3a);
	    break;
	default:
	    return MR_UNSUP;
	}
	break;
    case MC_VOICE:
	switch (sc) {
	case MSC_L:
	    vol *= vol5h(0x32);
	    break;
	case MSC_R:
	    vol *= vol5h(0x33);
	    break;
	default:
	    return MR_UNSUP;
	}
	break;
    case MC_PCSP:
	switch (sc) {
	case MSC_MONO_L:
	case MSC_MONO_R:
	    vol *= vol2h(0x3b);
	    break;
	default:
	    return MR_UNSUP;
	}
	break;
    default:
	return MR_UNSUP;
    }

    switch (sc) {
    case MSC_L:
    case MSC_RL:
    case MSC_MONO_L:
	vol *= vol5h(0x30);		// master
	vol *= gain2h(0x41);
	break;
    case MSC_R:
    case MSC_LR:
    case MSC_MONO_R:
	vol *= vol5h(0x31);		// master
	vol *= gain2h(0x42);
	break;
    }

    *r_vol = vol;
    return MR_OK;
}


#define EN(r, b) (sb.mixer_regs[r] & (1 << (b)))

int sb_is_input_connected(enum MixChan ch)
{
    switch (ch) {
    case MC_MIDI:
	return (EN(0x3d, 6) || EN(0x3e, 5) || EN(0x3e, 6) || EN(0x3d, 5));
    case MC_CD:
	return (EN(0x3d, 2) || EN(0x3e, 1) || EN(0x3e, 2) || EN(0x3d, 1));
    case MC_LINE:
	return (EN(0x3d, 4) || EN(0x3e, 3) || EN(0x3e, 4) || EN(0x3d, 3));
    case MC_MIC:
	return (EN(0x3d, 0) || EN(0x3e, 0));
    default:
	return 0;
    }
}

int sb_is_output_connected(enum MixChan ch)
{
    switch (ch) {
    case MC_CD:
	return (EN(0x3c, 2) || EN(0x3c, 1));
    case MC_LINE:
	return (EN(0x3c, 4) || EN(0x3c, 3));
    case MC_MIC:
	return EN(0x3c, 0);
    case MC_VOICE:
    case MC_PCSP:
    case MC_MIDI:
	return 1;
    default:
	return 0;
    }
}

int sb_mixer_get_chan_num(enum MixChan ch)
{
    switch (ch) {
    case MC_MIC:
    case MC_PCSP:
	return 1;
    default:
	return 2;
    }
}

/*
 * DANG_BEGIN_FUNCTION sb_io_write
 *
 * arguments:
 * port - The I/O port being written to.
 * value - The value being output.
 *
 * DANG_END_FUNCTION
 */

static void sb_io_write(ioport_t port, Bit8u value, void *arg)
{
    ioport_t addr;
    addr = port - config.sb_base;

    if (debug_level('S') >= 2) {
	S_printf("SB: Write to port 0x%04x, value 0x%02x\n", (Bit16u) port,
		 value);
    }

    switch (addr) {
	/* == FM MUSIC == */
    case 0x00 ... 0x03:
	adlib_io_write_base(addr, value);
	break;

    case 0x08:
    case 0x09:
	adlib_io_write_base(addr - 8, value);
	break;

	/* == MIXER == */
    case 0x04:			/* Mixer Register Address Port */
	sb.mixer_index = value;
	break;

    case 0x05:			/* Mixer Data Register */
	sb_mixer_write(value);
	break;

    case 0x06:			/* DSP reset */
	sb_dsp_soft_reset(value);
	break;

#if 0
    case 0x0A:			/* Write to DSP queue probably - undocumented */
	S_printf("SB: undocumented write to A\n");
	rng_clear(&sb.dsp_queue);
	rng_put(&sb.dsp_queue, &value);
	break;
#endif

	/* == DSP == */
    case 0x0C:			/* dsp write register */
	if (sb_midi_uart()) {
	    midi_write(value, ST_GM);
	    break;
	}
	if (sb_dma_active() && sb_dma_high_speed()) {
	    S_printf
		("SB: Commands are not permitted in High-Speed DMA mode!\n");
	    /* break; */
	}
	sb_dsp_write(value);
	break;

	/* 0x0D: Timer Interrupt Clear - SB16 */
	/* 0x10 - 0x13: CD-ROM - SBPro ***IGNORED*** */

    default:
	S_printf("SB: Write to port %#x unimplemented\n", addr);
    }
}

/*
 * DANG_BEGIN_FUNCTION sb_io_read
 *
 * arguments:
 * port - The I/O port being read from.
 *
 * DANG_END_FUNCTION
 */

static Bit8u sb_io_read(ioport_t port, void *arg)
{
    ioport_t addr;
    Bit8u result = 0;

    addr = port - config.sb_base;

    switch (addr) {

	/* == FM Music == */
    case 0x00 ... 0x03:
	result = adlib_io_read_base(addr);
	break;
    case 0x08:
    case 0x09:
	result = adlib_io_read_base(addr - 8);
	break;

    case 0x04:			/* Mixer index */
	result = sb.mixer_index;
	break;

    case 0x05:			/* Mixer Data Register */
	result = sb_mixer_read();
	break;

    case 0x06:			/* Reset ? */
	S_printf("SB: read from Reset address\n");
	result = 0;		/* Some programs read this whilst resetting */
	break;

    case 0x0A:			/* DSP Read Data - SB */
	if (rng_count(&sb.dsp_queue))
	    rng_get(&sb.dsp_queue, &sb.last_data);
	result = sb.last_data;
	S_printf("SB: Read 0x%x from SB DSP\n", result);
	if (sb_midi_int()) {
	    if (!rng_count(&sb.dsp_queue))
		sb_deactivate_irq(SB_IRQ_MIDI);
	    sb_run_irq(SB_IRQ_MIDI);
	}
	break;

    case 0x0C:			/* DSP Write Buffer Status */
	result = SB_DSP_STATUS;
	if (sb.busy)
	    result |= 0x80;
	if (sb.busy == 1)
	    sb.busy = 0;
	S_printf("SB: Read 0x%x from DSP Write Buffer Status Register (%i)\n",
		 result, sb.busy);
	break;

    case 0x0D:			/* DSP MIDI Interrupt Clear - SB16 ? */
	S_printf
	    ("SB: read 16-bit MIDI interrupt status. Not Implemented.\n");
	result = 0xFF;
	break;

    case 0x0E:
	/* DSP Data Available Status - SB */
	/* DSP 8-bit IRQ Ack - SB */
	result = SB_GET_STATUS();
	S_printf("SB: 8-bit IRQ Ack (%i)\n", sb.dma_count);
	if (sb_irq_active(SB_IRQ_8BIT))
	    sb_deactivate_irq(SB_IRQ_8BIT);
	if (sb.paused && sb.dma_cmd) {
	    S_printf("SB: drop DMA command %x\n", sb.dma_cmd);
	    sb.dma_cmd = 0;
	    sb.dma_restart.val = DMA_RESTART_NONE;
	}
	if (sb.dma_restart.val && !sb.dma_restart.is_16) {
	    sb_dma_start();
	    if (sb.dma_restart.val == DMA_RESTART_PENDING)
	        sb.dma_restart.val = DMA_RESTART_NONE;
	}
	break;
    case 0x0F:			/* 0x0F: DSP 16-bit IRQ - SB16 */
	result = SB_GET_STATUS();
	S_printf("SB: 16-bit IRQ Ack: (%i)\n", sb.dma_count);
	if (sb_irq_active(SB_IRQ_16BIT))
	    sb_deactivate_irq(SB_IRQ_16BIT);
	if (sb.paused && sb.dma_cmd) {
	    S_printf("SB: drop DMA command %x\n", sb.dma_cmd);
	    sb.dma_cmd = 0;
	    sb.dma_restart.val = DMA_RESTART_NONE;
	}
	if (sb.dma_restart.val && sb.dma_restart.is_16) {
	    sb_dma_start();
	    if (sb.dma_restart.val == DMA_RESTART_PENDING)
	        sb.dma_restart.val = DMA_RESTART_NONE;
	}
	break;

	/* == CD-ROM - UNIMPLEMENTED == */
    case 0x10:			/* CD-ROM Data Register - SBPro */
	S_printf("SB: read from CD-ROM Data register.\n");
	result = 0;
	break;

    case 0x11:			/* CD-ROM Status Port - SBPro */
	S_printf("SB: read from CD-ROM status port.\n");
	result = 0xFE;
	break;

    default:
	S_printf("SB: %#x is an unhandled read port.\n", port);
	result = 0xFF;
    }

    if (debug_level('S') >= 3) {
	S_printf("SB: port read 0x%x returns 0x%x\n", port, result);
    }

    return result;
}

int sb_get_dma_data(void *ptr, int is16bit)
{
    if (sb_dma_internal()) {
	S_printf("SB: E2 value %#x transferred\n", sb.reset_val);
	if (is16bit)
	    *(Bit16u *) ptr = sb.reset_val;
	else
	    *(Bit8u *) ptr = sb.reset_val;
	return 1;
    }
    return 0;
}

static void process_sb_midi_input(Bit8u val)
{
    if (sb_midi_timestamp()) {
	Bit32u time = dspio_get_midi_in_time(sb.dspio);
	dsp_write_output(time);
	dsp_write_output(time >> 8);
	dsp_write_output(time >> 16);
    }
    dsp_write_output(val);
}

void run_sb(void)
{
    if (!config.sound)
	return;

    /* https://github.com/dosemu2/dosemu2/issues/2172 */
    if (sb.irq_pend /*&& ++sb.irq_delay_cnt > 5*/) {
	if (sb.irq_pend & SB_IRQ_8BIT)
	    sb_activate_irq(SB_IRQ_8BIT);
	if (sb.irq_pend & SB_IRQ_16BIT)
	    sb_activate_irq(SB_IRQ_16BIT);
	sb.irq_pend = 0;
	sb.irq_delay_cnt = 0;
    }

    dspio_timer(sb.dspio);
    do_process_midi();
    mpu401_process(sb.mpu);
}

static void sb_dsp_init(void)
{
    rng_init(&sb.dsp_queue, DSP_QUEUE_SIZE, 1);

    sb.reset_val = 0xaa;
}

static void sb_dsp_done(void)
{
    rng_destroy(&sb.dsp_queue);
}

static void mpu_activate_irq(struct mpu401_s *mpu)
{
    sb_activate_irq(SB_IRQ_MPU401);
}

static void mpu_deactivate_irq(struct mpu401_s *mpu)
{
    sb_deactivate_irq(SB_IRQ_MPU401);
}

static void mpu_run_irq(struct mpu401_s *mpu)
{
    sb_run_irq(SB_IRQ_MPU401);
}

static void mpu_write_midi(struct mpu401_s *mpu, uint8_t data)
{
    midi_write(data, ST_GM);
}

static struct mpu401_ops mops = {
    .activate_irq = mpu_activate_irq,
    .deactivate_irq = mpu_deactivate_irq,
    .run_irq = mpu_run_irq,
    .write_midi = mpu_write_midi,
    .name = "SB MPU401"
};

/*
 * Sound Initialisation
 * ====================
 */
static void sb_init(void)
{
    emu_iodev_t io_device = {};

    S_printf("SB: SB Initialisation\n");

    /* SB Emulation */
    io_device.read_portb = sb_io_read;
    io_device.write_portb = sb_io_write;
    io_device.read_portw = NULL;
    io_device.write_portw = NULL;
    io_device.read_portd = NULL;
    io_device.write_portd = NULL;
    io_device.handler_name = "SB Emulation";
    io_device.start_addr = config.sb_base;
    io_device.end_addr = config.sb_base + 0x013;
    if (port_register_handler(io_device, 0) != 0) {
	error("SB: Cannot registering DSP port handler\n");
    }

    sb_dsp_init();
    sb_mixer_init();
    opl3_init();

    if (config.mpu401_irq == -1) {
	config.mpu401_irq = config.sb_irq;
	S_printf("SB: mpu401 irq set to %i\n", config.mpu401_irq);
    }
    sb.mpu = mpu401_init(config.mpu401_base, &mops);

    S_printf("SB: Initialisation - Base 0x%03x\n", config.sb_base);
}

static void sb_done(void)
{
    sb_dsp_done();
    adlib_done();
    mpu401_done(sb.mpu);
}

void sound_init(void)
{
    if (!config.sound)
	return;
    sb.dspio = dspio_init();
    if (!sb.dspio) {
	error("dspio failed\n");
	leavedos(93);
    }
    sb_init();
}

void sound_reset(void)
{
    if (config.sound) {
	dspio_reset(sb.dspio);
	sb_reset();
    }
}

void sound_done(void)
{
    if (config.sound) {
	sb_done();
	dspio_done(sb.dspio);
    }
}
