/*
 *  Copyright (C) 2002-2021  The DOSBox Team
 *
 *  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.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include <assert.h>
#include <string.h>
#include <iomanip>
#include <sstream>
#include "dosbox.h"
#include "inout.h"
#include "logging.h"
#include "mixer.h"
#include "dma.h"
#include "pic.h"
#include "control.h"
#include "setup.h"
#include "shell.h"
#include "bitop.h"
#include "math.h"
#include "regs.h"
using namespace std;

#if defined(_MSC_VER)
# pragma warning(disable:4244) /* const fmath::local::uint64_t to double possible loss of data */
#endif

enum GUSType {
	GUS_CLASSIC=0,
	GUS_MAX,
	GUS_INTERWAVE
};

//Extra bits of precision over normal gus
#define WAVE_FRACT 9
#define WAVE_FRACT_MASK ((1 << WAVE_FRACT)-1)
#define WAVE_MSWMASK ((1 << 16)-1)
#define WAVE_LSWMASK (0xffffffff ^ WAVE_MSWMASK)

//Amount of precision the volume has
#define RAMP_FRACT (10)
#define RAMP_FRACT_MASK ((1 << RAMP_FRACT)-1)

#define GUS_BASE myGUS.portbase
#define GUS_RATE myGUS.rate
#define LOG_GUS 0

#define VOL_SHIFT 14

#define WCTRL_STOPPED           0x01
#define WCTRL_STOP              0x02
#define WCTRL_16BIT             0x04
#define WCTRL_LOOP              0x08
#define WCTRL_BIDIRECTIONAL     0x10
#define WCTRL_IRQENABLED        0x20
#define WCTRL_DECREASING        0x40
#define WCTRL_IRQPENDING        0x80

// fixed panning table (avx)
static uint16_t const pantablePDF[16] = { 0, 13, 26, 41, 57, 72, 94, 116, 141, 169, 203, 244, 297, 372, 500, 4095 };
static bool gus_fixed_table = false;

uint8_t adlib_commandreg;
static MixerChannel * gus_chan;
static uint8_t const irqtable[8] = { 0/*invalid*/, 2, 5, 3, 7, 11, 12, 15 };
static uint8_t const dmatable[8] = { 0/*NO DMA*/, 1, 3, 5, 6, 7, 0/*invalid*/, 0/*invalid*/ };
static int32_t AutoAmp = 512;
static bool unmask_irq = false;
static bool enable_autoamp = false;
static bool startup_ultrinit = false;
static bool ignore_active_channel_write_while_active = false;
static bool warn_out_of_bounds_dram_access = false;
static bool dma_enable_on_dma_control_polling = false;
static uint16_t vol16bit[4096];
static uint32_t pantable[16];
static enum GUSType gus_type = GUS_CLASSIC;
static bool gus_ics_mixer = false;
static bool gus_warn_irq_conflict = false;
static bool gus_warn_dma_conflict = false;

static IO_Callout_t gus_iocallout = IO_Callout_t_none;
static IO_Callout_t gus_iocallout2 = IO_Callout_t_none;

struct GFGus {
	uint8_t gRegSelectData;		// what is read back from 3X3. not necessarily the index selected, but
	// apparently the last byte read OR written to ports 3X3-3X5 as seen
	// on actual GUS hardware.
	uint8_t gRegSelect;
	uint16_t gRegData;
	uint32_t gDramAddr;
	uint32_t gDramAddrMask;
	uint32_t gDramVoiceMask;
	uint16_t gCurChannel;

	uint8_t gUltraMAXControl;
	uint16_t DMAControl; /* NTS: bit 8 for DMA TC IRQ status. Only bits [7:0] exist on real hardware.
				We're taking the DOSBox SVN approach here (https://sourceforge.net/p/dosbox/code-0/4387/#diff-2) */
	uint16_t dmaAddr;
	uint8_t dmaAddrOffset; /* bits 0-3 of the addr */
	uint8_t TimerControl;
	uint8_t SampControl;
	uint8_t mixControl;
	uint8_t ActiveChannels;
	uint8_t ActiveChannelsUser; /* what the guest wrote */
	uint8_t gRegControl;
	uint32_t basefreq;

	struct GusTimer {
		float delay;
		uint8_t value;
		bool reached;
		bool raiseirq;
		bool masked;
		bool running;
	} timers[2];

	uint32_t rate;
	Bitu portbase;
	uint32_t memsize;
	uint8_t dma1;
	uint8_t dma2;

	uint8_t irq1;		// GF1 IRQ
	uint8_t irq2;		// MIDI IRQ

	bool irqenabled;
	bool ChangeIRQDMA;
	bool initUnmaskDMA;
	bool force_master_irq_enable;
	bool fixed_sample_rate_output;
	bool clearTCIfPollingIRQStatus;
	bool globalread80alias;
	double lastIRQStatusPollAt;
	int lastIRQStatusPollRapidCount;
	// IRQ status register values
	uint8_t IRQStatus;
	uint32_t ActiveMask;
	uint8_t IRQChan;
	uint32_t RampIRQ;
	uint32_t WaveIRQ;
	double masterVolume;    /* decibels */
	int32_t masterVolumeMul; /* 1<<9 fixed */
	uint8_t GUS_reset_reg;
	uint8_t* GUSRam;

	void updateMasterVolume(void);

	INLINE uint32_t VoiceAddr8(const uint32_t addr) const {
		return addr & gDramVoiceMask;
	}

	INLINE uint32_t VoiceAddr16(const uint32_t addr) const {
		const uint32_t maddr = addr & gDramVoiceMask;
		const uint32_t bank_a = maddr & 0xC0000u;
		const uint32_t bank_o = (maddr & 0x1FFFFu) << 1u;
		return bank_a + bank_o;
	}

	INLINE int32_t LoadSample8(const uint32_t addr/*memory address without fractional bits*/) const {
		return (int8_t)GUSRam[VoiceAddr8(addr)] << int32_t(8); /* typecast to sign extend 8-bit value */
	}

	INLINE int32_t LoadSample16(const uint32_t addr/*memory address without fractional bits*/) const {
		return (int16_t)host_readw(GUSRam + VoiceAddr16(addr));/* typecast to sign extend 16-bit value */
	}

	INLINE int32_t InterpolateSample(const int32_t w1,const int32_t w2,const uint32_t scale) const {
		return w1 + (((w2 - w1) * int32_t(scale)) >> WAVE_FRACT);
	}

	INLINE int32_t GetSample8(const uint32_t WaveAddr) const {
		const uint32_t useAddr = WaveAddr >> WAVE_FRACT;
		return InterpolateSample(LoadSample8(useAddr),LoadSample8(useAddr+1u),WaveAddr & WAVE_FRACT_MASK);
	}

	INLINE int32_t GetSample16(const uint32_t WaveAddr) const {
		const uint32_t useAddr = WaveAddr >> WAVE_FRACT;
		return InterpolateSample(LoadSample16(useAddr),LoadSample16(useAddr+1u),WaveAddr & WAVE_FRACT_MASK);
	}

};

static void CheckVoiceIrq(void);
Bitu DEBUG_EnableDebugger(void);

static inline uint8_t read_GF1_mapping_control(const unsigned int ch);

void GFGus::updateMasterVolume(void) {
	double vol = masterVolume;
	if (vol > 6) vol = 6; // allow some amplification but don't let it overflow
	masterVolumeMul = (int32_t)((1 << 9) * pow(10.0,vol / 20.0));
	if (AutoAmp > masterVolumeMul) AutoAmp = masterVolumeMul;
}

static GFGus myGUS;

class GUSChannel {
	public:
		uint32_t WaveStart = 0;
		uint32_t WaveEnd = 0;
		uint32_t WaveAddr = 0;
		uint32_t WaveAdd = 0;
		uint8_t  WaveCtrl = 0;
		uint16_t WaveFreq = 0;

		uint32_t RampStart = 0;
		uint32_t RampEnd = 0;
		uint32_t RampVol = 0;
		uint32_t RampAdd = 0;

		uint8_t RampRate = 0;
		uint8_t RampCtrl = 0;

		uint8_t PanPot = 0;
		uint8_t channum = 0;
		uint32_t irqmask = 0;
		uint32_t PanLeft = 0;
		uint32_t PanRight = 0;
		int32_t VolLeft = 0;
		int32_t VolRight = 0;

		GUSChannel(uint8_t num) { 
			channum = num;
			irqmask = 1u << num;
			WaveStart = 0;
			WaveEnd = 0;
			WaveAddr = 0;
			WaveAdd = 0;
			WaveFreq = 0;
			WaveCtrl = 3;
			RampRate = 0;
			RampStart = 0;
			RampEnd = 0;
			RampCtrl = 3;
			RampAdd = 0;
			RampVol = 0;
			VolLeft = 0;
			VolRight = 0;
			PanLeft = 0;
			PanRight = 0;
			PanPot = 0x7;
		}

		INLINE int32_t GetSample8() const {
			return myGUS.GetSample8(WaveAddr);
		}

		INLINE int32_t GetSample16() const {
			return myGUS.GetSample16(WaveAddr);
		}

		void WriteWaveFreq(uint16_t val) {
			WaveFreq = val;
			if (myGUS.fixed_sample_rate_output) {
				double frameadd = double(val >> 1)/512.0;		//Samples / original gus frame
				double realadd = (frameadd*(double)myGUS.basefreq/(double)GUS_RATE) * (double)(1 << WAVE_FRACT);
				WaveAdd = (uint32_t)realadd;
			}
			else {
				WaveAdd = ((uint32_t)(val >> 1)) << ((uint32_t)(WAVE_FRACT-9));
			}
		}
		void WriteWaveCtrl(uint8_t val) {
			uint32_t oldirq=myGUS.WaveIRQ;
			WaveCtrl = val & 0x7f;

			if ((val & 0xa0)==0xa0) myGUS.WaveIRQ|=irqmask;
			else myGUS.WaveIRQ&=~irqmask;

			if (oldirq != myGUS.WaveIRQ) 
				CheckVoiceIrq();
		}
		INLINE uint8_t ReadWaveCtrl(void) {
			uint8_t ret=WaveCtrl;
			if (myGUS.WaveIRQ & irqmask) ret|=0x80;
			return ret;
		}
		void UpdateWaveRamp(void) { 
			WriteWaveFreq(WaveFreq);
			WriteRampRate(RampRate);
		}
		void WritePanPot(uint8_t val) {
			PanPot = val;
			PanLeft = pantable[val & 0xf];
			PanRight = pantable[0x0f-(val & 0xf)];
			UpdateVolumes();
		}
		uint8_t ReadPanPot(void) {
			return PanPot;
		}
		void WriteRampCtrl(uint8_t val) {
			uint32_t old=myGUS.RampIRQ;
			RampCtrl = val & 0x7f;
			//Manually set the irq
			if ((val & 0xa0) == 0xa0)
				myGUS.RampIRQ |= irqmask;
			else
				myGUS.RampIRQ &= ~irqmask;
			if (old != myGUS.RampIRQ)
				CheckVoiceIrq();
		}
		INLINE uint8_t ReadRampCtrl(void) {
			uint8_t ret=RampCtrl;
			if (myGUS.RampIRQ & irqmask) ret|=0x80;
			return ret;
		}
		void WriteRampRate(uint8_t val) {
			RampRate = val;
			if (myGUS.fixed_sample_rate_output) {
				double frameadd = (double)(RampRate & 63)/(double)(1 << (3*(val >> 6)));
				double realadd = (frameadd*(double)myGUS.basefreq/(double)GUS_RATE) * (double)(1 << RAMP_FRACT);
				RampAdd = (uint32_t)realadd;
			}
			else {
				/* NTS: Note RAMP_FRACT == 10, shift = 10 - (3*(val>>6)).
				 * From the upper two bits, the possible shift values for 0, 1, 2, 3 are: 10, 7, 4, 1 */
				RampAdd = ((uint32_t)(RampRate & 63)) << ((uint32_t)(RAMP_FRACT - (3*(val >> 6))));
#if 0//SET TO 1 TO CHECK YOUR MATH!
				double frameadd = (double)(RampRate & 63)/(double)(1 << (3*(val >> 6)));
				double realadd = frameadd * (double)(1 << RAMP_FRACT);
				uint32_t checkadd = (uint32_t)realadd;
				signed long error = (signed long)checkadd - (signed long)RampAdd;

				if (error < -1L || error > 1L)
					LOG_MSG("RampAdd nonfixed error %ld (%lu != %lu)",error,(unsigned long)checkadd,(unsigned long)RampAdd);
#endif
			}
		}
		INLINE void WaveUpdate(void) {
			bool endcondition;

			if ((WaveCtrl & (WCTRL_STOP | WCTRL_STOPPED)) == 0/*voice is running*/) {
				/* NTS: WaveAddr and WaveAdd are unsigned.
				 *      If WaveAddr <= WaveAdd going backwards, WaveAddr becomes negative, which as an unsigned integer,
				 *      means carrying down from the highest possible value of the integer type. Which means that if the
				 *      start position is less than WaveAdd the WaveAddr will skip over the start pointer and continue
				 *      playing downward from the top of the GUS memory, without stopping/looping as expected.
				 *
				 *      This "bug" was implemented on purpose because real Gravis Ultrasound hardware acts this way. */
				uint32_t WaveExtra = 0;
				if (WaveCtrl & WCTRL_DECREASING/*backwards (direction)*/) {
					/* unsigned int subtract, mask, compare. will miss start pointer if WaveStart <= WaveAdd.
					 * This bug is deliberate, accurate to real GUS hardware, do not fix. */
					WaveAddr -= WaveAdd;
					WaveAddr &= ((Bitu)1 << ((Bitu)WAVE_FRACT + (Bitu)20/*1MB*/)) - 1;
					endcondition = (WaveAddr < WaveStart)?true:false;
					if (endcondition) WaveExtra = WaveStart - WaveAddr;
				}
				else {
					WaveAddr += WaveAdd;
					endcondition = (WaveAddr > WaveEnd)?true:false;
					WaveAddr &= ((Bitu)1 << ((Bitu)WAVE_FRACT + (Bitu)20/*1MB*/)) - 1;
					if (endcondition) WaveExtra = WaveAddr - WaveEnd;
				}

				if (endcondition) {
					if (WaveCtrl & WCTRL_IRQENABLED) /* generate an IRQ if requested */
						myGUS.WaveIRQ |= irqmask;

					if ((RampCtrl & WCTRL_16BIT/*roll over*/) && !(WaveCtrl & WCTRL_LOOP)) {
						/* "3.11. Rollover feature
						 * 
						 * Each voice has a 'rollover' feature that allows an application to be notified when a voice's playback position passes
						 * over a particular place in DRAM.  This is very useful for getting seamless digital audio playback.  Basically, the GF1
						 * will generate an IRQ when a voice's current position is  equal to the end position.  However, instead of stopping or
						 * looping back to the start position, the voice will continue playing in the same direction.  This means that there will be
						 * no pause (or gap) in the playback.  Note that this feature is enabled/disabled through the voice's VOLUME control
						 * register (since there are no more bits available in the voice control registers).   A voice's loop enable bit takes
						 * precedence over the rollover.  This means that if a voice's loop enable is on, it will loop when it hits the end position,
						 * regardless of the state of the rollover enable."
						 *
						 * Despite the confusing description above, that means that looping takes precedence over rollover. If not looping, then
						 * rollover means to fire the IRQ but keep moving. If looping, then fire IRQ and carry out loop behavior. Gravis Ultrasound
						 * Windows 3.1 drivers expect this behavior, else Windows WAVE output will not work correctly. */
					}
					else {
						if (WaveCtrl & WCTRL_LOOP) {
							if (WaveCtrl & WCTRL_BIDIRECTIONAL) WaveCtrl ^= WCTRL_DECREASING/*change direction*/;
							WaveAddr = (WaveCtrl & WCTRL_DECREASING) ? (WaveEnd - WaveExtra) : (WaveStart + WaveExtra);
						} else {
							WaveCtrl |= 1; /* stop the channel */
							WaveAddr = (WaveCtrl & WCTRL_DECREASING) ? WaveStart : WaveEnd;
						}
					}
				}
			}
			else if (WaveCtrl & WCTRL_IRQENABLED) {
				/* Undocumented behavior observed on real GUS hardware: A stopped voice will still rapid-fire IRQs
				 * if IRQ enabled and current position <= start position OR current position >= end position */
				if (WaveCtrl & WCTRL_DECREASING/*backwards (direction)*/)
					endcondition = (WaveAddr <= WaveStart)?true:false;
				else
					endcondition = (WaveAddr >= WaveEnd)?true:false;

				if (endcondition)
					myGUS.WaveIRQ |= irqmask;
			}
		}
		INLINE void UpdateVolumes(void) {
			int32_t templeft=(int32_t)RampVol - (int32_t)PanLeft;
			templeft&=~(templeft >> 31); /* <- NTS: This is a rather elaborate way to clamp negative values to zero using negate and sign extend */
			int32_t tempright=(int32_t)RampVol - (int32_t)PanRight;
			tempright&=~(tempright >> 31); /* <- NTS: This is a rather elaborate way to clamp negative values to zero using negate and sign extend */
			VolLeft=vol16bit[templeft >> RAMP_FRACT];
			VolRight=vol16bit[tempright >> RAMP_FRACT];
		}
		INLINE void RampUpdate(void) {
			if (RampCtrl & 0x3) return; /* if the ramping is turned off, then don't change the ramp */

			int32_t RampLeft;
			if (RampCtrl & 0x40) {
				RampVol-=RampAdd;
				if ((int32_t)RampVol < (int32_t)0) RampVol=0;
				RampLeft=(int32_t)RampStart-(int32_t)RampVol;
			} else {
				RampVol+=RampAdd;
				if (RampVol > ((4096 << RAMP_FRACT)-1)) RampVol=((4096 << RAMP_FRACT)-1);
				RampLeft=(int32_t)RampVol-(int32_t)RampEnd;
			}
			if (RampLeft<0) {
				UpdateVolumes();
				return;
			}
			/* Generate an IRQ if needed */
			if (RampCtrl & 0x20) {
				myGUS.RampIRQ|=irqmask;
			}
			/* Check for looping */
			if (RampCtrl & 0x08) {
				/* Bi-directional looping */
				if (RampCtrl & 0x10) RampCtrl^=0x40;
				RampVol = (RampCtrl & 0x40) ? (uint32_t)((int32_t)RampEnd-(int32_t)RampLeft) : (uint32_t)((int32_t)RampStart+(int32_t)RampLeft);
			} else {
				RampCtrl|=1;	//Stop the channel
				RampVol = (RampCtrl & 0x40) ? RampStart : RampEnd;
			}
			if ((int32_t)RampVol < (int32_t)0) RampVol=0;
			if (RampVol > ((4096 << RAMP_FRACT)-1)) RampVol=((4096 << RAMP_FRACT)-1);
			UpdateVolumes();
		}

		void generateSamples(int32_t* stream, uint32_t len) {
			int32_t tmpsamp;
			int i;

			/* NTS: The GUS is *always* rendering the audio sample at the current position,
			 *      even if the voice is stopped. This can be confirmed using DOSLIB, loading
			 *      the Ultrasound test program, loading a WAV file into memory, then using
			 *      the Ultrasound test program's voice control dialog to single-step the
			 *      voice through RAM (abruptly change the current position) while the voice
			 *      is stopped. You will hear "popping" noises come out the GUS audio output
			 *      as the current position changes and the piece of the sample rendered
			 *      abruptly changes as well. */
			if (gus_ics_mixer) {
				const unsigned char Lc = read_GF1_mapping_control(0);
				const unsigned char Rc = read_GF1_mapping_control(1);

				// output mapped through ICS mixer including channel remapping
				for (i = 0; i < (int)len; i++) {
					// Get sample
					if (WaveCtrl & WCTRL_16BIT)
						tmpsamp = GetSample16();
					else
						tmpsamp = GetSample8();
					// Output stereo sample if DAC enable on
					if ((myGUS.GUS_reset_reg & 0x02/*DAC enable*/) == 0x02) {
						int32_t* const sp = stream + (i << 1);
						const int32_t L = tmpsamp * VolLeft;
						const int32_t R = tmpsamp * VolRight;

						if (Lc & 1) sp[0] += L;
						if (Lc & 2) sp[1] += L;
						if (Rc & 1) sp[0] += R;
						if (Rc & 2) sp[1] += R;

						WaveUpdate();
						RampUpdate();
					}
				}
			}
			else {
				// normal output
				for (i = 0; i < (int)len; i++) {
					// Get sample
					if (WaveCtrl & WCTRL_16BIT)
						tmpsamp = GetSample16();
					else
						tmpsamp = GetSample8();

					// Output stereo sample if DAC enable on
					if ((myGUS.GUS_reset_reg & 0x02/*DAC enable*/) == 0x02) {
						stream[i << 1] += tmpsamp * VolLeft;
						stream[(i << 1) + 1] += tmpsamp * VolRight;

						WaveUpdate();
						RampUpdate();
					}
				}
			}
		}
};

static GUSChannel *guschan[32] = {NULL};
static GUSChannel *curchan = NULL;

#if C_DEBUG
void DEBUG_PrintGUS() { //debugger "GUS" command
	LOG_MSG("GUS regsel=%02x regseld=%02x regdata=%02x DRAMaddr=%06x/%06x VoiceMask=%06x memsz=%06x curch=%02x MAXctrl=%02x regctl=%02x",
			myGUS.gRegSelect,
			myGUS.gRegSelectData,
			myGUS.gRegData,
			myGUS.gDramAddr,
			myGUS.gDramAddrMask,
			myGUS.gDramVoiceMask,
			myGUS.memsize,
			myGUS.gCurChannel,
			myGUS.gUltraMAXControl,
			myGUS.gRegControl);
	LOG_MSG("DMActrl=%02x (TC=%u) dmaAddr=%04x%01x timerctl=%02x sampctl=%02x mixctl=%02x activech=%u (want=%u) DACrate=%uHz",
			myGUS.DMAControl&0xFF,
			(myGUS.DMAControl&0x100)?1:0,
			myGUS.dmaAddr,
			myGUS.dmaAddrOffset,
			myGUS.TimerControl,
			myGUS.SampControl,
			myGUS.mixControl,
			myGUS.ActiveChannels,
			myGUS.ActiveChannelsUser,
			myGUS.basefreq);
	LOG_MSG("IRQen=%u IRQstat=%02x IRQchan=%04x RampIRQ=%04x WaveIRQ=%04x",
			myGUS.irqenabled,
			myGUS.IRQStatus,
			myGUS.IRQChan,
			myGUS.RampIRQ,
			myGUS.WaveIRQ);
	for (size_t t=0;t < 2;t++) {
		LOG_MSG("Timer %u: delay=%.3fms value=%02x reached=%u raiseirq=%u masked=%u running=%u\n",
				(unsigned int)t + 1u,
				myGUS.timers[t].delay,
				myGUS.timers[t].value,
				myGUS.timers[t].reached,
				myGUS.timers[t].raiseirq,
				myGUS.timers[t].masked,
				myGUS.timers[t].running);
	}
	for (size_t t=0;t < (size_t)myGUS.ActiveChannels;t++) {
		GUSChannel *ch = guschan[t];
		if (ch == NULL) continue;

		std::string line;

		switch (ch->WaveCtrl & 3) {
			case 0:				line += " RUN  "; break;
			case WCTRL_STOPPED:		line += " STOPD"; break;
			case WCTRL_STOP:		line += " STOPN"; break;
			case WCTRL_STOPPED|WCTRL_STOP:	line += " STOP!"; break;
		}

		if (ch->WaveCtrl & WCTRL_LOOP)
			line += " LOOP";
		else if (ch->RampCtrl & WCTRL_16BIT/*roll over*/) /* !loop and (rampctl & 4) == rollover */
			line += " ROLLOVER";

		if (ch->WaveCtrl & WCTRL_16BIT)
			line += " PCM16";
		if (ch->WaveCtrl & WCTRL_BIDIRECTIONAL)
			line += " BIDI";
		if (ch->WaveCtrl & WCTRL_IRQENABLED)
			line += " IRQEN";
		if (ch->WaveCtrl & WCTRL_DECREASING)
			line += " REV";
		if (ch->WaveCtrl & WCTRL_IRQPENDING)
			line += " IRQP";

		LOG_MSG("Voice %u: start=%05x.%03x end=%05x.%03x addr=%05x.%03x add=%05x.%03x ctl=%02x rampctl=%02x%s",
				(unsigned int)t + 1u,
				ch->WaveStart>>WAVE_FRACT,
				(ch->WaveStart&WAVE_FRACT_MASK)<<(12-WAVE_FRACT),//current WAVE_FRACT == 9
				ch->WaveEnd>>WAVE_FRACT,
				(ch->WaveEnd&WAVE_FRACT_MASK)<<(12-WAVE_FRACT),//current WAVE_FRACT == 9
				ch->WaveAddr>>WAVE_FRACT,
				(ch->WaveAddr&WAVE_FRACT_MASK)<<(12-WAVE_FRACT),//current WAVE_FRACT == 9
				ch->WaveAdd>>WAVE_FRACT,
				(ch->WaveAdd&WAVE_FRACT_MASK)<<(12-WAVE_FRACT),//current WAVE_FRACT == 9
				ch->WaveCtrl,
				ch->RampCtrl,
				line.c_str());
		LOG_MSG("    Ramp start=%05x.%03x end=%05x.%03x vol=%05x.%03x add=%05x.%03x pan=%x",
				ch->RampStart>>RAMP_FRACT,
				(ch->RampStart&RAMP_FRACT_MASK)<<(12-RAMP_FRACT),//current RAMP_FRACT == 10
				ch->RampEnd>>RAMP_FRACT,
				(ch->RampEnd&RAMP_FRACT_MASK)<<(12-RAMP_FRACT),//current RAMP_FRACT == 10
				ch->RampVol>>RAMP_FRACT,
				(ch->RampVol&RAMP_FRACT_MASK)<<(12-RAMP_FRACT),//current RAMP_FRACT == 10
				ch->RampAdd>>RAMP_FRACT,
				(ch->RampAdd&RAMP_FRACT_MASK)<<(12-RAMP_FRACT),//current RAMP_FRACT == 10
				ch->PanPot);
	}
}
#endif

static INLINE void GUS_CheckIRQ(void);

static void GUS_TimerEvent(Bitu val);

static void GUS_DMA_Callback(DmaChannel * chan,DMAEvent event);

void GUS_StopDMA();
void GUS_StartDMA();
void GUS_Update_DMA_Event_transfer();

static void GUSReset(void) {
	unsigned char p_GUS_reset_reg = myGUS.GUS_reset_reg;

	/* NTS: From the Ultrasound SDK:
	 *
	 *      Global Data Low (3X4) is either a 16-bit transfer, or the low half of a 16-bit transfer with 8-bit I/O.
	 *
	 *      Global Data High (3X5) is either an 8-bit transfer for one of the GF registers or the high part of a 16-bit wide register with 8-bit I/O.
	 *
	 *      Prior to 2015/12/29 DOSBox and DOSBox-X contained a programming error here where reset and master IRQ enable were handled from the
	 *      LOWER 8 bits, when the code should have been checking the UPPER 8 bits. Programming error #2 was the mis-interpretation of bit 0 (bit 8 of
	 *      the gRegData). According to the SDK, clearing bit 0 triggers RESET, setting bit 0 starts the card running again. The original code had
	 *      it backwards. */
	myGUS.GUS_reset_reg = (myGUS.gRegData >> 8) & 7;

	if ((myGUS.gRegData & 0x400) != 0x000 || myGUS.force_master_irq_enable)
		myGUS.irqenabled = true;
	else
		myGUS.irqenabled = false;

	if (myGUS.GUS_reset_reg ^ p_GUS_reset_reg)
		LOG(LOG_MISC,LOG_DEBUG)("GUS reset with 0x%04X",myGUS.gRegData);

	if ((myGUS.gRegData & 0x100) == 0x000) {
		// Stop all channels
		int i;
		for(i=0;i<32;i++) {
			guschan[i]->RampVol=0;
			guschan[i]->WriteWaveCtrl(0x1);
			guschan[i]->WriteRampCtrl(0x1);
			guschan[i]->WritePanPot(0x7);
		}

		// Stop DMA
		GUS_StopDMA();

		// Reset
		adlib_commandreg = 85;
		myGUS.IRQStatus = 0;
		myGUS.RampIRQ = 0;
		myGUS.WaveIRQ = 0;
		myGUS.IRQChan = 0;

		myGUS.timers[0].delay = 0.080f;
		myGUS.timers[1].delay = 0.320f;
		myGUS.timers[0].value = 0xff;
		myGUS.timers[1].value = 0xff;
		myGUS.timers[0].masked = false;
		myGUS.timers[1].masked = false;
		myGUS.timers[0].raiseirq = false;
		myGUS.timers[1].raiseirq = false;
		myGUS.timers[0].reached = true;
		myGUS.timers[1].reached = true;
		myGUS.timers[0].running = false;
		myGUS.timers[1].running = false;

		PIC_RemoveEvents(GUS_TimerEvent);

		myGUS.ChangeIRQDMA = false;
		myGUS.DMAControl = 0x00;
		myGUS.mixControl = 0x0b;	// latches enabled by default LINEs disabled
		myGUS.TimerControl = 0x00;
		myGUS.SampControl = 0x00;
		myGUS.ActiveChannels = 14;
		myGUS.ActiveChannelsUser = 14;
		myGUS.ActiveMask=0xffffffffU >> (32-myGUS.ActiveChannels);
		myGUS.basefreq = (uint32_t)((float)1000000/(1.619695497*(float)(myGUS.ActiveChannels)));

		gus_chan->FillUp();
		if (!myGUS.fixed_sample_rate_output)	gus_chan->SetFreq(myGUS.basefreq);
		else					gus_chan->SetFreq(GUS_RATE);

		myGUS.gCurChannel = 0;
		curchan = guschan[myGUS.gCurChannel];

		myGUS.dmaAddr = 0;
		myGUS.irqenabled = 0;
		myGUS.gRegControl = 0;
		myGUS.dmaAddrOffset = 0;
		myGUS.gDramAddr = 0;
		myGUS.gRegData = 0;

		GUS_Update_DMA_Event_transfer();
	}

	/* if the card was just put into reset, or the card WAS in reset, bits 1-2 are cleared */
	if ((myGUS.GUS_reset_reg & 1) == 0 || (p_GUS_reset_reg & 1) == 0) {
		/* GUS classic observed behavior: resetting the card, or even coming out of reset, clears bits 1-2.
		 * That means, if you write any value to GUS RESET with bit 0 == 0, bits 1-2 become zero as well.
		 * And if you take the card out of reset, bits 1-2 are zeroed.
		 *
		 * test 1:
		 * outb(0x3X3,0x4C); outb(0x3X5,0x00);
		 * outb(0x3X3,0x4C); c = inb(0x3X5);      <- you'll get 0x00 as expected
		 * outb(0x3X3,0x4C); outb(0x3X5,0x07);
		 * outb(0x3X3,0x4C); c = inb(0x3X5);      <- you'll get 0x01, not 0x07
		 *
		 * test 2:
		 * outb(0x3X3,0x4C); outb(0x3X5,0x00);
		 * outb(0x3X3,0x4C); c = inb(0x3X5);      <- you'll get 0x00 as expected
		 * outb(0x3X3,0x4C); outb(0x3X5,0x01);
		 * outb(0x3X3,0x4C); c = inb(0x3X5);      <- you'll get 0x01 as expected, card taken out of reset
		 * outb(0x3X3,0x4C); outb(0x3X5,0x07);
		 * outb(0x3X3,0x4C); c = inb(0x3X5);      <- you'll get 0x07 as expected
		 * outb(0x3X3,0x4C); outb(0x3X5,0x06);    <- bit 0 == 0, we're trying to set bits 1-2
		 * outb(0x3X3,0x4C); c = inb(0x3X5);      <- you'll get 0x00, not 0x06, card is in reset state */
		myGUS.irqenabled = myGUS.force_master_irq_enable; // IRQ enable resets, unless user specified we force it on
		myGUS.GUS_reset_reg &= 1;
	}

	GUS_CheckIRQ();
}

static uint8_t GUS_EffectiveIRQStatus(void) {
	uint8_t ret = 0;

	/* Behavior observed on real GUS hardware: Master IRQ enable bit 2 of the reset register affects only voice/wave
	 * IRQ signals from the GF1. It does not affect the DMA terminal count interrupt nor does it affect the Adlib timers.
	 * This is how "Juice" by Psychic Link is able to play music by GUS timer even though the demo never enables the
	 * Master IRQ Enable bit. */

	/* DMA */
	if (myGUS.DMAControl & 0x20/*DMA IRQ Enable*/)
		ret |= (myGUS.IRQStatus & 0x80/*DMA TC IRQ*/);

	/* Timer 1 & 2 */
	ret |= (myGUS.IRQStatus/*Timer 1&2 IRQ*/ & myGUS.TimerControl/*Timer 1&2 IRQ Enable*/ & 0x0C);

	/* Voice IRQ */
	if (myGUS.irqenabled)
		ret |= (myGUS.IRQStatus & 0x60/*Wave/Ramp IRQ*/);

	/* TODO: MIDI IRQ? */

	return ret;
}

static uint8_t gus_prev_effective_irqstat = 0;

static INLINE void GUS_CheckIRQ(void) {
	if (myGUS.mixControl & 0x08/*Enable latches*/) {
		uint8_t irqstat = GUS_EffectiveIRQStatus();

		if (irqstat != 0) {
			/* The GUS fires an IRQ, then waits for the interrupt service routine to
			 * clear all pending interrupt events before firing another one. if you
			 * don't service all events, then you don't get another interrupt. */
			if (gus_prev_effective_irqstat == 0) {
				PIC_ActivateIRQ(myGUS.irq1);

				if (gus_warn_irq_conflict)
					LOG(LOG_MISC,LOG_WARN)(
							"GUS warning: Both IRQs set to the same signal line WITHOUT combining! "
							"This is documented to cause bus conflicts on real hardware");
			}
		}

		gus_prev_effective_irqstat = irqstat;
	}
}

static void CheckVoiceIrq(void) {
	Bitu totalmask=(myGUS.RampIRQ|myGUS.WaveIRQ) & myGUS.ActiveMask;
	if (!totalmask) {
		GUS_CheckIRQ();
		return;
	}

	if (myGUS.RampIRQ) myGUS.IRQStatus|=0x40;
	if (myGUS.WaveIRQ) myGUS.IRQStatus|=0x20;
	GUS_CheckIRQ();
	for (;;) {
		uint32_t check=(1u << myGUS.IRQChan);
		if (totalmask & check) return;
		myGUS.IRQChan++;
		if (myGUS.IRQChan>=myGUS.ActiveChannels) myGUS.IRQChan=0;
	}
}

static uint16_t ExecuteReadRegister(void) {
	uint8_t tmpreg,effective = myGUS.gRegSelect;

	/* 1997 demo "Out of Control" by Contract GUS support behavior
	 * suggests that ALL GUS registers here, not just the voice
	 * registers, have both the register at N and an alias at (N|0x80).
	 *
	 * Specificially, this demo initiates GUS reset through register 4Ch
	 * such that the low 3 bits are set (0x7) and then reads those bits
	 * back to comfirm the value is 0x7, but it does it by setting the
	 * register index to CCh (4Ch+80h) instead of 4Ch. GUS detection
	 * in the demo fails if the readback is not 0x7.
	 *
	 * According to official GUS documentation, only the voice registers
	 * 0x0-0xF should have these 0x80-0x8F readback aliases, which is
	 * why those registers are often listed as i.e. 0x00 / 0x80,
	 * 0x01 / 0x81, etc.
	 *
	 * TODO: Verify this behavior on real GUS MAX hardware. Does 4Ch
	 *       have a CCh readback alias? Does any other register have an
	 *       N|0x80 alias?
	 *
	 * TODO: Does the Interwave emulate this behavior or does it enforce
	 *       registers as documented, and therefore "Out of Control"
	 *       would not work on the Interwave? */
	if (myGUS.globalread80alias && effective > 0x8F) effective &= 0x7F;

	//	LOG_MSG("Read global reg %x",myGUS.gRegSelect,effective);

	switch (effective) {
		case 0x8E:  // read active channel register
			// NTS: The GUS SDK documents the active channel count as bits 5-0, which is wrong. it's bits 4-0. bits 7-5 are always 1 on real hardware.
			return ((uint16_t)(0xE0 | (myGUS.ActiveChannelsUser - 1))) << 8;
		case 0x41: // Dma control register - read acknowledges DMA IRQ
			if (dma_enable_on_dma_control_polling) {
				if (!GetDMAChannel(myGUS.dma1)->masked && !(myGUS.DMAControl & 0x01) && !(myGUS.IRQStatus & 0x80)) {
					LOG(LOG_MISC,LOG_DEBUG)("GUS: As instructed, switching on DMA ENABLE upon polling DMA control register (HACK) as workaround");
					myGUS.DMAControl |= 0x01;
					GUS_StartDMA();
				}
			}

			tmpreg = myGUS.DMAControl & 0xbf;
			tmpreg |= (myGUS.DMAControl & 0x100) >> 2; /* Bit 6 on read is the DMA terminal count IRQ status */
			myGUS.DMAControl&=0xff; /* clear TC IRQ status */
			myGUS.IRQStatus&=0x7f;
			GUS_CheckIRQ();
			return (uint16_t)(tmpreg << 8);
		case 0x42:  // Dma address register
			return myGUS.dmaAddr;
		case 0x45:  // Timer control register.  Identical in operation to Adlib's timer
			return (uint16_t)(myGUS.TimerControl << 8);
			break;
		case 0x49:  // Dma sample register
			tmpreg = myGUS.DMAControl & 0xbf;
			tmpreg |= (myGUS.DMAControl & 0x100) >> 2; /* Bit 6 on read is the DMA terminal count IRQ status */
			return (uint16_t)(tmpreg << 8);
		case 0x4c:  // GUS reset register
			tmpreg = (myGUS.GUS_reset_reg & ~0x4) | (myGUS.irqenabled ? 0x4 : 0x0);
			/* GUS Classic observed behavior: You can read Register 4Ch from both 3X4 and 3X5 and get the same 8-bit contents */
			return ((uint16_t)(tmpreg << 8) | (uint16_t)tmpreg);
		case 0x80: // Channel voice control read register
			if (curchan) return curchan->ReadWaveCtrl() << 8;
			else return 0x0300;
		case 0x81:  // Channel frequency control register
			if(curchan) return (uint16_t)(curchan->WaveFreq);
			else return 0x0000;
		case 0x82: // Channel MSB start address register
			if (curchan) return (uint16_t)(curchan->WaveStart >> 16);
			else return 0x0000;
		case 0x83: // Channel LSW start address register
			if (curchan) return (uint16_t)(curchan->WaveStart);
			else return 0x0000;
		case 0x84: // Channel MSB end address register
			if (curchan) return (uint16_t)(curchan->WaveEnd >> 16);
			else return 0x0000;
		case 0x85: // Channel LSW end address register
			if (curchan) return (uint16_t)(curchan->WaveEnd);
			else return 0x0000;

		case 0x89: // Channel volume register
			if (curchan) return (uint16_t)((curchan->RampVol >> RAMP_FRACT) << 4);
			else return 0x0000;
		case 0x8a: // Channel MSB current address register
			if (curchan) return (uint16_t)(curchan->WaveAddr >> 16);
			else return 0x0000;
		case 0x8b: // Channel LSW current address register
			if (curchan) return (uint16_t)(curchan->WaveAddr);
			else return 0x0000;
		case 0x8c: // Channel pan pot register
			if (curchan) return (uint16_t)(curchan->PanPot << 8);
			else return 0x0800;
		case 0x8d: // Channel volume control register
			if (curchan) return curchan->ReadRampCtrl() << 8;
			else return 0x0300;
		case 0x8f: // General channel IRQ status register
			tmpreg=myGUS.IRQChan|0x20;
			uint32_t mask;
			mask=1u << myGUS.IRQChan;
			if (!(myGUS.RampIRQ & mask)) tmpreg|=0x40;
			if (!(myGUS.WaveIRQ & mask)) tmpreg|=0x80;
			myGUS.RampIRQ&=~mask;
			myGUS.WaveIRQ&=~mask;
			myGUS.IRQStatus&=0x9f;
			CheckVoiceIrq();
			return (uint16_t)(tmpreg << 8);
		default:
#if LOG_GUS
			LOG_MSG("Read Register num 0x%x", myGUS.gRegSelect);
#endif
			return myGUS.gRegData;
	}
}

static void GUS_TimerEvent(Bitu val) {
	if (!myGUS.timers[val].masked) myGUS.timers[val].reached=true;
	if (myGUS.timers[val].raiseirq) {
		myGUS.IRQStatus|=0x4 << val;
		GUS_CheckIRQ();
	}
	if (myGUS.timers[val].running) 
		PIC_AddEvent(GUS_TimerEvent,myGUS.timers[val].delay,val);
}


static void ExecuteGlobRegister(void) {
	int i;
	//	if (myGUS.gRegSelect|1!=0x44) LOG_MSG("write global register %x with %x", myGUS.gRegSelect, myGUS.gRegData);
	switch(myGUS.gRegSelect) {
		case 0x0:  // Channel voice control register
			gus_chan->FillUp();
			if(curchan) curchan->WriteWaveCtrl((uint16_t)myGUS.gRegData>>8);
			break;
		case 0x1:  // Channel frequency control register
			gus_chan->FillUp();
			if(curchan) curchan->WriteWaveFreq(myGUS.gRegData);
			break;
		case 0x2:  // Channel MSW start address register
			if (curchan) {
				uint32_t tmpaddr = (uint32_t)(myGUS.gRegData & 0x1fff) << 16; /* upper 13 bits of integer portion */
				curchan->WaveStart = (curchan->WaveStart & WAVE_MSWMASK) | tmpaddr;
			}
			break;
		case 0x3:  // Channel LSW start address register
			if(curchan != NULL) {
				uint32_t tmpaddr = (uint32_t)(myGUS.gRegData & 0xffe0); /* lower 7 bits of integer portion, and all 4 bits of fractional portion. bits 4-0 of the incoming 16-bit WORD are not used */
				curchan->WaveStart = (curchan->WaveStart & WAVE_LSWMASK) | tmpaddr;
			}
			break;
		case 0x4:  // Channel MSW end address register
			if(curchan != NULL) {
				uint32_t tmpaddr = (uint32_t)(myGUS.gRegData & 0x1fff) << 16; /* upper 13 bits of integer portion */
				curchan->WaveEnd = (curchan->WaveEnd & WAVE_MSWMASK) | tmpaddr;
			}
			break;
		case 0x5:  // Channel MSW end address register
			if(curchan != NULL) {
				uint32_t tmpaddr = (uint32_t)(myGUS.gRegData & 0xffe0); /* lower 7 bits of integer portion, and all 4 bits of fractional portion. bits 4-0 of the incoming 16-bit WORD are not used */
				curchan->WaveEnd = (curchan->WaveEnd & WAVE_LSWMASK) | tmpaddr;
			}
			break;
		case 0x6:  // Channel volume ramp rate register
			gus_chan->FillUp();
			if(curchan != NULL) {
				uint8_t tmpdata = (uint16_t)myGUS.gRegData>>8;
				curchan->WriteRampRate(tmpdata);
			}
			break;
		case 0x7:  // Channel volume ramp start register  EEEEMMMM
			if(curchan != NULL) {
				uint8_t tmpdata = (uint16_t)myGUS.gRegData >> 8;
				curchan->RampStart = (uint32_t)(tmpdata << (4+RAMP_FRACT));
			}
			break;
		case 0x8:  // Channel volume ramp end register  EEEEMMMM
			if(curchan != NULL) {
				uint8_t tmpdata = (uint16_t)myGUS.gRegData >> 8;
				curchan->RampEnd = (uint32_t)(tmpdata << (4+RAMP_FRACT));
			}
			break;
		case 0x9:  // Channel current volume register
			gus_chan->FillUp();
			if(curchan != NULL) {
				uint16_t tmpdata = (uint16_t)myGUS.gRegData >> 4;
				curchan->RampVol = (uint32_t)(tmpdata << RAMP_FRACT);
				curchan->UpdateVolumes();
			}
			break;
		case 0xA:  // Channel MSW current address register
			gus_chan->FillUp();
			if(curchan != NULL) {
				uint32_t tmpaddr = (uint32_t)(myGUS.gRegData & 0x1fff) << 16; /* upper 13 bits of integer portion */
				curchan->WaveAddr = (curchan->WaveAddr & WAVE_MSWMASK) | tmpaddr;
			}
			break;
		case 0xB:  // Channel LSW current address register
			gus_chan->FillUp();
			if(curchan != NULL) {
				uint32_t tmpaddr = (uint32_t)(myGUS.gRegData & 0xffff); /* lower 7 bits of integer portion, and all 9 bits of fractional portion */
				curchan->WaveAddr = (curchan->WaveAddr & WAVE_LSWMASK) | tmpaddr;
			}
			break;
		case 0xC:  // Channel pan pot register
			gus_chan->FillUp();
			if(curchan) curchan->WritePanPot((uint16_t)myGUS.gRegData>>8);
			break;
		case 0xD:  // Channel volume control register
			gus_chan->FillUp();
			if(curchan) curchan->WriteRampCtrl((uint16_t)myGUS.gRegData>>8);
			break;
		case 0xE:  // Set active channel register
			/* Hack for "Ice Fever" demoscene production:
			 * If the DAC is active (bit 1 of GUS reset is set), ignore writes to this register.
			 * The demo resets the GUS with 14 channels, then after reset changes it to 16 for some reason.
			 * Without this hack, music will sound slowed down and wrong.
			 * As far as I know, real hardware will accept the change immediately and produce the same
			 * slowed down sound music. --J.C. */
			if (ignore_active_channel_write_while_active) {
				if (myGUS.GUS_reset_reg & 0x02/*DAC enable*/) {
					LOG_MSG("GUS: Attempt to change active channel count while DAC active rejected");
					break;
				}
			}

			gus_chan->FillUp();
			myGUS.gRegSelect = myGUS.gRegData>>8;		//JAZZ Jackrabbit seems to assume this?
			myGUS.ActiveChannelsUser = 1+((myGUS.gRegData>>8) & 31); // NTS: The GUS SDK documents this field as bits 5-0, which is wrong, it's bits 4-0. 5-0 would imply 64 channels.

			/* The GUS SDK claims that if a channel count less than 14 is written, then it caps to 14.
			 * That's not true. Perhaps what the SDK is doing, but the actual hardware acts differently.
			 * This implementation is based on what the Gravis Ultrasound MAX actually does with this
			 * register. You can apparently achieve higher than 44.1KHz sample rates by programming less
			 * than 14 channels, and the sample rate scale ramps up appropriately, except that values
			 * 0 and 1 have the same effect as writing 2 and 3. Very useful undocumented behavior!
			 * If Gravis were smart, they would have been able to claim 48KHz sample rates by allowing
			 * less than 14 channels in their SDK! Not sure why they would cap it like that, unless
			 * there are undocumented chipset instabilities with running at higher rates.
			 *
			 * So far only verified on a Gravis Ultrasound MAX.
			 *
			 * Does anyone out there have a Gravis Ultrasound Classic (original 1992 version) they can
			 * test for this behavior?
			 *
			 * NOTED: Gravis Ultrasound Plug & Play (interwave) cards *do* enforce the 14-channel minimum.
			 *        You can write less than 14 channels to this register, but unlike the Classic and Max
			 *        cards they will not run faster than 44.1KHz. */
			myGUS.ActiveChannels = myGUS.ActiveChannelsUser;

			if (gus_type < GUS_INTERWAVE) {
				// GUS MAX behavior seen on real hardware
				if(myGUS.ActiveChannels < 3) myGUS.ActiveChannels += 2;
				if(myGUS.ActiveChannels > 32) myGUS.ActiveChannels = 32;
			}
			else {
				// Interwave PnP behavior seen on real hardware
				if(myGUS.ActiveChannels < 14) myGUS.ActiveChannels = 14;
				if(myGUS.ActiveChannels > 32) myGUS.ActiveChannels = 32;
			}

			myGUS.ActiveMask=0xffffffffU >> (32-myGUS.ActiveChannels);
			myGUS.basefreq = (uint32_t)(0.5 + 1000000.0 / (1.619695497 * (double)(myGUS.ActiveChannels)));

			if (!myGUS.fixed_sample_rate_output)	gus_chan->SetFreq(myGUS.basefreq);
			else					gus_chan->SetFreq(GUS_RATE);

#if LOG_GUS
			LOG_MSG("GUS set to %d channels fixed=%u freq=%luHz", myGUS.ActiveChannels,myGUS.fixed_sample_rate_output,(unsigned long)myGUS.basefreq);
#endif
			for (i=0;i<myGUS.ActiveChannels;i++) guschan[i]->UpdateWaveRamp();
			break;
		case 0x10:  // Undocumented register used in Fast Tracker 2
			break;
		case 0x41:  // Dma control register
			myGUS.DMAControl &= ~0xFFu; // FIXME: Does writing DMA Control clear the DMA TC IRQ?
			myGUS.DMAControl |= (uint8_t)(myGUS.gRegData>>8);
			GUS_Update_DMA_Event_transfer();
			if (myGUS.DMAControl & 1) GUS_StartDMA();
			else GUS_StopDMA();
			break;
		case 0x42:  // Gravis DRAM DMA address register
			myGUS.dmaAddr = myGUS.gRegData;
			myGUS.dmaAddrOffset = 0;
			break;
		case 0x43:  // LSB Peek/poke DRAM position
			myGUS.gDramAddr = (0xff0000 & myGUS.gDramAddr) | ((uint32_t)myGUS.gRegData);
			break;
		case 0x44:  // MSW Peek/poke DRAM position
			myGUS.gDramAddr = (0xffff & myGUS.gDramAddr) | ((uint32_t)myGUS.gRegData>>8) << 16;
			break;
		case 0x45:  // Timer control register.  Identical in operation to Adlib's timer
			myGUS.TimerControl = (uint8_t)(myGUS.gRegData>>8);
			myGUS.timers[0].raiseirq=(myGUS.TimerControl & 0x04)>0;
			if (!myGUS.timers[0].raiseirq) myGUS.IRQStatus&=~0x04;
			myGUS.timers[1].raiseirq=(myGUS.TimerControl & 0x08)>0;
			if (!myGUS.timers[1].raiseirq) myGUS.IRQStatus&=~0x08;
			GUS_CheckIRQ();
			break;
		case 0x46:  // Timer 1 control
			myGUS.timers[0].value = (uint8_t)(myGUS.gRegData>>8);
			myGUS.timers[0].delay = (0x100 - myGUS.timers[0].value) * 0.080f;
			break;
		case 0x47:  // Timer 2 control
			myGUS.timers[1].value = (uint8_t)(myGUS.gRegData>>8);
			myGUS.timers[1].delay = (0x100 - myGUS.timers[1].value) * 0.320f;
			break;
		case 0x49:  // DMA sampling control register
			myGUS.SampControl = (uint8_t)(myGUS.gRegData>>8);
			if (myGUS.DMAControl & 1) GUS_StartDMA();
			else GUS_StopDMA();
			break;
		case 0x4c:  // GUS reset register
			GUSReset();
			break;
		default:
#if LOG_GUS
			LOG_MSG("Unimplemented global register %x -- %x", myGUS.gRegSelect, myGUS.gRegData);
#endif
			break;
	}
	return;
}

/* Gravis Ultrasound ICS-2101 Digitally Controlled Audio Mixer emulation */
/* NTS: This was written and tested only through Ultrasound software and emulation.
 *      I do not have a Gravis Ultrasound card with this type of mixer to test against. --J.C. */
struct gus_ICS2101 {
	public:
		// ICS2101 and how Gravis wired up the input pairs when using it, according to some BSD and Linux kernel sources
		//
		// From the header file:
		//
		//   Register defs for Integrated Circuit Systems, Inc. ICS-2101 mixer
		//   chip, used on Gravis UltraSound cards.
		//
		//   Block diagram:
		//                                    port #
		//                                       0 +----+
		//    Mic in (Right/Left)        -->--->---|    |
		//                                       1 |    |          amp --->---- amp out
		//    Line in (Right/Left)       -->--->---|    |           |
		//                                       2 |    |           |
		//    CD in (Right/Left)         -->--->---|    |--->---+---+----->---- line out
		//                                       3 |    |       |
		//    GF1 Out (Right/Left)       -->--->---|    |       |
		//                                       4 |    |       |
		//    Unused (Right/Left)        -->--->---|    |       |
		//                                         +----+       v
		//                                       ICS 2101       |
		//                                                      |
		//               To GF1 Sample Input ---<---------------+
		//
		//    Master output volume: mixer channel #5
		enum {
			MIC_IN_PORT=0,
			LINE_IN_PORT=1,
			CD_IN_PORT=2,
			GF1_OUT_PORT=3,
			UNUSED_PORT=4,
			MASTER_OUTPUT_PORT=5
		};
	public:
		gus_ICS2101() {
		}
	public:
		void addressWrite(uint8_t addr) {
			addr_attenuator = (addr >> 3) & 7;
			addr_control = addr & 7;
		}
		void dataWrite(uint8_t val) {
			LOG(LOG_MISC,LOG_DEBUG)("GUS ICS-2101 Mixer Data Write val=%02xh to attensel=%u(%s) ctrlsel=%u(%s)",
					(int)val,
					addr_attenuator,attenuatorName(addr_attenuator),
					addr_control,controlName(addr_control));

			if (addr_control & 2) { // attenuator NTS: Only because an existing ICS patch for DOSBox does it this way... does hardware do it this way?
				mixpair[addr_attenuator].setAttenuation(addr_control&1,val);
				mixpair[addr_attenuator].debugPrintMixer(attenuatorName(addr_attenuator));
				updateVolPair(addr_attenuator);
			}
			else if (addr_control & 4) { // pan/balance
				mixpair[addr_attenuator].Panning = val & 0xF;
				// FIXME: Does the panning take effect immediately, or does the chip require the DOS program
				//        to write attenuation again to apply the panning?
			}
			else {
				mixpair[addr_attenuator].setControl(addr_control&1,val);
			}
		}
		const char *attenuatorName(const uint8_t c) const {
			switch (c) {
				case 0:	return "Mic in";
				case 1:	return "Line in";
				case 2:	return "CD in";
				case 3:	return "GF1 out";
				case 4:	return "Pair 5, unused";
				case 5:	return "Master output";
			}

			return "?";
		}
		const char *controlName(const uint8_t c) const {
			switch (c) {
				case 0:	return "Control Left";		// 000
				case 1:	return "Control Right";		// 001
				case 2:	return "Attenuator Left";	// 010
				case 3:	return "Attenuator Right";	// 011
				case 4: case 5:				// 10x
					return "Pan/Balance";
			}

			return "?";
		}
		void updateVolPair(unsigned int pair) {
			if (pair >= MASTER_OUTPUT_PORT) {
				// master volume changes everyone else. do it, through 1 level of recursion.
				for (unsigned int i=0;i < MASTER_OUTPUT_PORT;i++)
					updateVolPair(i);
			}
			else {
				float m[2];

				assert(gus_chan != NULL);

				// copy not just attenuation but modify according to master volume
				for (unsigned int ch=0;ch < 2;ch++) {
					volpair[pair].AttenDb[ch] = mixpair[pair].AttenDb[ch] + mixpair[MASTER_OUTPUT_PORT].AttenDb[ch];
					m[ch] = powf(10.0f,volpair[pair].AttenDb[ch]/20.0f);
				}

				if (pair == GF1_OUT_PORT)
					gus_chan->SetVolume(m[0],m[1]);
			}
		}
	public:
		struct mixcontrol {
			public:
				mixcontrol() {
					Panning = 8;
					Control[0] = 0x01;
					Control[1] = 0x02;
					MapControl[0] = 0x01;
					MapControl[1] = 0x02;
					setAttenuation(0,0x7F); // FIXME: Because we want DOSBox to come up as if ULTRINIT/ULTRAMIX were run to configure the mixer
					setAttenuation(1,0x7F);
				}
			public:
				// gain() taken from an existing patch
				float gain(uint8_t val) {  // in 0-127, out -90 to 0db, min to max
					float gain=(127-val)*-0.5;
					if(val<16) for(int i=0;i<(16-val);i++) gain+=-0.5-.13603*(i+1); // increasing rate of change, based on datasheet graph 
					return gain;
				}
				// end borrow
				void setControl(const unsigned int channel,const uint8_t val) {
					Control[channel] = val;
					updateMapControl();
				}
				void updateMapControl() {
					/* control mode according to Left control register, according to datasheet */
					switch (Control[0]&0xE) {
						case 0: case 2: // normal mode
							MapControl[0] = Control[0]&3;
							MapControl[1] = Control[1]&3;
							break;
						case 4: // stereo (normal or reversed)
							MapControl[0] = (Control[0]&1) ? 1 : 2;	// left -> left or left -> right if swapped
							MapControl[1] = (Control[0]&1) ? 2 : 1; // right -> right or right -> left if swapped
							break;
						case 6: // Mono
							MapControl[0] = 1; // Is this right??
							MapControl[1] = 2; // Is this right??
							break;
						case 8: case 12: // Balance
							MapControl[0] = (Control[0]&1) ? 1 : 2;	// left -> left or left -> right if swapped
							MapControl[1] = (Control[0]&1) ? 2 : 1; // right -> right or right -> left if swapped
							// fixme: Do we update attenuation to reflect panning? or does the ICS chip need the
							// DOS program to write the attenuation registers again for the chip to apply balance/panning?
							break;
						case 10: case 14: // Pan
							MapControl[0] = (Control[0]&1) ? 1 : 2;	// left -> left or left -> right if swapped
							MapControl[1] = (Control[0]&1) ? 2 : 1; // right -> right or right -> left if swapped
							// fixme: Do we update attenuation to reflect panning? or does the ICS chip need the
							// DOS program to write the attenuation registers again for the chip to apply balance/panning?
							break;
					}
				}
				void setAttenuation(const unsigned int channel,const uint8_t val) {
					// FIXME: I am only able to test the "normal" mode since that's the only mode used by Gravis's DOS and Windows drivers.
					//        The code below has not been tested in "Stereo" and "Pan/Balance" mode. If any DOS drivers use it, please direct
					//        me to them so I or anyone else can test! ---J.C.

					if ((Control[0]&0xC) == 0) {
						Attenuation[channel] = val & 0x7F;
						AttenDb[channel] = gain(Attenuation[channel]);
					}
					else {
						// "stereo & balance use left control & gain, copies to right regs" says the Vogons patch I'm studying...
						Attenuation[0] = Attenuation[1] = val & 0x7F;
						AttenDb[0] = gain(Attenuation[0]);
						AttenDb[1] = AttenDb[0];

						// taken from the same Vogons patch, except potential access past array fixed
						if ((Control[0]&0xC) == 8/*Balance/pan mode*/) {
							static const float pan[16+1] = {-9,-9, -8.5, -6.5,-5.5,-4.5, -3.5,-3,-2.5,-2,-1.5,-1,-.5, 0,0,0,0};
							AttenDb[0] += pan[Panning];
							AttenDb[1] += pan[16-Panning];
						}

					}
				}
				void debugPrintMixer(const char *name) {
					LOG(LOG_MISC,LOG_DEBUG)("GUS ICS control '%s': %.3fdB %.3fdB",name,AttenDb[0],AttenDb[1]);
				}
			public:
				uint8_t		Panning;
				uint8_t		Control[2];
				uint8_t		MapControl[2];
				uint8_t		Attenuation[2];		// 0x00-0x7F where 0x00 is mute (max atten) and 0x7F is full volume
				float		AttenDb[2];		// in decibels
		};
		struct volpair {
			public:
				volpair() {
					AttenDb[0] = AttenDb[1] = 0;
				}
			public:
				float		AttenDb[2];
				uint32_t	Fix1616Mult[2] = {};		// linear multiply to apply volume
		};
	public:
		struct mixcontrol	mixpair[8];		// pairs 1-5 and Master
		struct volpair		volpair[5] = {};	// pairs 1-5 scaled by master
		uint8_t			addr_attenuator = 0;	// which attenuator is selected
		uint8_t			addr_control = 0;		// which control is selected
} GUS_ICS2101;

static inline uint8_t read_GF1_mapping_control(const unsigned int ch) {
	return GUS_ICS2101.mixpair[gus_ICS2101::GF1_OUT_PORT].MapControl[ch];
}

/* Gravis Ultrasound MAX Crystal Semiconductor CS4231A emulation */
/* NOTES:
 *
 *    This 4-port I/O interface, implemented by Crystal Semiconductors, Analog Devices, etc. and used
 *    by sound cards in the early 1990s, is the said to be the "standardized" hardware interface of
 *    the "Windows Sound System" standard at the time.
 *
 *    According to an AD1848 datasheet, and a CS4231A datasheet, all I/O ports and indirect registers
 *    appear to be the same, with the exception that Crystal Semiconductor adds 16 registers with the
 *    "MODE 2" bit.
 *
 *    Perhaps at some point, we can untie this from GUS emulation and let it exist as its own C++
 *    class that covers CS4231A, AD1848, and other "WSS" chipset emulation on behalf of GUS and SB
 *    emulation, much like the OPL3 emulation already present in this source tree for Sound Blaster.
 *
 */
struct gus_cs4231 {
public:
	gus_cs4231() : address(0), mode2(false), ia4(false), trd(false), mce(true), init(false) {
	}
public:
	void data_write(uint8_t addr,uint8_t val) {
//		LOG(LOG_MISC,LOG_DEBUG)("GUS CS4231 write data addr=%02xh val=%02xh",addr,val);

		switch (addr) {
			case 0x00: /* Left ADC Input Control (I0) */
				ADCInputControl[0] = val; break;
			case 0x01: /* Right ADC Input Control (I1) */
				ADCInputControl[1] = val; break;
			case 0x02: /* Left Auxiliary #1 Input Control (I2) */
				Aux1InputControl[0] = val; break;
			case 0x03: /* Right Auxiliary #1 Input Control (I3) */
				Aux1InputControl[1] = val; break;
			case 0x06: /* Left DAC Output Control (I6) */
				DACOutputControl[0] = val; break;
			case 0x07: /* Left DAC Output Control (I7) */
				DACOutputControl[1] = val; break;
			case 0x0C: /* MODE and ID (I12) */
				mode2 = (val & 0x40)?1:0; break;
			default:
				LOG(LOG_MISC,LOG_DEBUG)("GUS CS4231 unhandled data write addr=%02xh val=%02xh",addr,val);
				break;
		}
	}
	uint8_t data_read(uint8_t addr) {
//		LOG(LOG_MISC,LOG_DEBUG)("GUS CS4231 read data addr=%02xh",addr);

		switch (addr) {
			case 0x00: /* Left ADC Input Control (I0) */
				return ADCInputControl[0];
			case 0x01: /* Right ADC Input Control (I1) */
				return ADCInputControl[1];
			case 0x02: /* Left Auxiliary #1 Input Control (I2) */
				return Aux1InputControl[0];
			case 0x03: /* Right Auxiliary #1 Input Control (I3) */
				return Aux1InputControl[1];
			case 0x06: /* Left DAC Output Control (I6) */
				return DACOutputControl[0];
			case 0x07: /* Left DAC Output Control (I7) */
				return DACOutputControl[1];
			case 0x0C: /* MODE and ID (I12) */
				return 0x80 | (mode2 ? 0x40 : 0x00) | 0xA/*1010 codec ID*/;
			default:
				LOG(LOG_MISC,LOG_DEBUG)("GUS CS4231 unhandled data read addr=%02xh",addr);
				break;
		}

		return 0;
	}
	void playio_data_write(uint8_t val) {
		LOG(LOG_MISC,LOG_DEBUG)("GUS CS4231 Playback I/O write %02xh",val);
	}
	uint8_t capio_data_read(void) {
		LOG(LOG_MISC,LOG_DEBUG)("GUS CS4231 Capture I/O read");
		return 0;
	}
	uint8_t status_read(void) {
		LOG(LOG_MISC,LOG_DEBUG)("GUS CS4231 Status read");
		return 0;
	}
	void iowrite(uint8_t reg,uint8_t val) {
//		LOG(LOG_MISC,LOG_DEBUG)("GUS CS4231 write reg=%u val=%02xh",reg,val);

		if (init) return;

		switch (reg) {
			case 0x0: /* Index Address Register (R0) */
				address = val & (mode2 ? 0x1F : 0x0F);
				trd = (val & 0x20)?1:0;
				mce = (val & 0x40)?1:0;
				break;
			case 0x1: /* Index Data Register (R1) */
				data_write(address,val);
				break;
			case 0x2: /* Status Register (R2) */
				LOG(LOG_MISC,LOG_DEBUG)("GUS CS4231 attempted write to status register val=%02xh",val);
				break;
			case 0x3: /* Playback I/O Data Register (R3) */
				playio_data_write(val);
				break;
		}
	}
	uint8_t ioread(uint8_t reg) {
//		LOG(LOG_MISC,LOG_DEBUG)("GUS CS4231 write read=%u",reg);

		if (init) return 0x80;

		switch (reg) {
			case 0x0: /* Index Address Register (R0) */
				return address | (trd?0x20:0x00) | (mce?0x40:0x00) | (init?0x80:0x00);
			case 0x1: /* Index Data Register (R1) */
				return data_read(address);
			case 0x2: /* Status Register (R2) */
				return status_read();
			case 0x3: /* Capture I/O Data Register (R3) */
				return capio_data_read();
		}

		return 0;
	}
public:
	uint8_t		address;
	bool		mode2; // read CS4231A datasheet for more information
	bool		ia4;
	bool		trd;
	bool		mce;
	bool		init;

	uint8_t		ADCInputControl[2] = {};	/* left (I0) and right (I1) ADC Input control. bits 7-6 select source. bit 5 is mic gain. bits 3-0 controls gain. */
	uint8_t		Aux1InputControl[2] = {};	/* left (I2) and right (I3) aux. input control. bits 5-0 control gain in 1.5dB steps. bit 7 is mute */
	uint8_t		DACOutputControl[2] = {};	/* left (I6) and right (I7) output control attenuation. bits 5-0 control in -1.5dB steps, bit 7 is mute */
} GUS_CS4231;

static Bitu read_gus_cs4231(Bitu port,Bitu iolen) {
	(void)iolen;//UNUSED
	if (myGUS.gUltraMAXControl & 0x40/*codec enable*/)
		return GUS_CS4231.ioread((port - GUS_BASE) & 3); // FIXME: UltraMAX allows this to be relocatable

	return 0xFF;
}

static void write_gus_cs4231(Bitu port,Bitu val,Bitu iolen) {
	(void)iolen;//UNUSED
	if (myGUS.gUltraMAXControl & 0x40/*codec enable*/)
		GUS_CS4231.iowrite((port - GUS_BASE) & 3,val&0xFF);
}

static Bitu read_gus(Bitu port,Bitu iolen) {
	uint16_t reg16;

	(void)iolen;//UNUSED
	//	LOG_MSG("read from gus port %x",port);

	/* 12-bit ISA decode (FIXME: Check GUS MAX ISA card to confirm)
	 *
	 * More than 10 bits must be decoded in order for GUS MAX extended registers at 7xx to work */
	port &= 0xFFF;

	/* Except for port 3x4 subdivide 16-bit I/O into two 8-bit reads.
	 * See write_gus() for explanation. */
	if (iolen == 2) {
		if ((port - GUS_BASE) != 0x304) {
			return (read_gus(port,1) & 0xFF) + (read_gus(port+1,1) << 8u);
		}
	}

	switch(port - GUS_BASE) {
		case 0x206:
			if (myGUS.clearTCIfPollingIRQStatus) {
				double t = PIC_FullIndex();

				/* Hack: "Warcraft II" by Blizzard entertainment.
				 *
				 * If you configure the game to use the Gravis Ultrasound for both music and sound, the GUS support code for digital
				 * sound will cause the game to hang if music is playing at the same time within the main menu. The bug is that there
				 * is code (in real mode no less) that polls the IRQ status register (2X6) and handles each IRQ event to clear it,
				 * however there is no condition to handle clearing the DMA Terminal Count IRQ flag. The routine will not terminate
				 * until all bits are cleared, hence, the game hangs after starting a sound effect. Most often, this is visible to
				 * the user as causing the game to hang when you click on a button on the main menu.
				 *
				 * This hack attempts to detect the bug by looking for rapid polling of this register in a short period of time.
				 * If detected, we clear the DMA IRQ flag to help the loop terminate so the game continues to run. */
				if (t < (myGUS.lastIRQStatusPollAt + 0.1/*ms*/)) {
					myGUS.lastIRQStatusPollAt = t;
					myGUS.lastIRQStatusPollRapidCount++;
					if (myGUS.clearTCIfPollingIRQStatus && (myGUS.IRQStatus & 0x80) && myGUS.lastIRQStatusPollRapidCount >= 500) {
						LOG(LOG_MISC,LOG_DEBUG)("GUS: Clearing DMA TC IRQ status, DOS application appears to be stuck");
						myGUS.lastIRQStatusPollRapidCount = 0;
						myGUS.lastIRQStatusPollAt = t;
						myGUS.IRQStatus &= 0x7F;
						GUS_CheckIRQ();
					}
				}
				else {
					myGUS.lastIRQStatusPollAt = t;
					myGUS.lastIRQStatusPollRapidCount = 0;
				}
			}

			/* NTS: Contrary to some false impressions, GUS hardware does not report "one at a time", it really is a bitmask.
			 *      I had the funny idea you read this register "one at a time" just like reading the IRQ reason bits of the RS-232 port --J.C. */
			return GUS_EffectiveIRQStatus();
		case 0x208:
			uint8_t tmptime;
			tmptime = 0;
			if (myGUS.timers[0].reached) tmptime |= (1 << 6);
			if (myGUS.timers[1].reached) tmptime |= (1 << 5);
			if (tmptime & 0x60) tmptime |= (1 << 7);
			if (myGUS.IRQStatus & 0x04) tmptime|=(1 << 2);
			if (myGUS.IRQStatus & 0x08) tmptime|=(1 << 1);
			return tmptime;
		case 0x20a:
			return adlib_commandreg;
		case 0x20f:
			if (gus_type >= GUS_MAX || gus_ics_mixer)
				return 0x02; /* <- FIXME: What my GUS MAX returns. What does this mean? */
			return ~0ul; // should not happen
		case 0x302:
			return myGUS.gRegSelectData;
		case 0x303:
			return myGUS.gRegSelectData;
		case 0x304:
			if (iolen==2) reg16 = ExecuteReadRegister() & 0xffff;
			else reg16 = ExecuteReadRegister() & 0xff;

			if (gus_type < GUS_INTERWAVE) // Versions prior to the Interwave will reflect last I/O to 3X2-3X5 when read back from 3X3
				myGUS.gRegSelectData = reg16 & 0xFF;

			return reg16;
		case 0x305:
			reg16 = ExecuteReadRegister() >> 8;

			if (gus_type < GUS_INTERWAVE) // Versions prior to the Interwave will reflect last I/O to 3X2-3X5 when read back from 3X3
				myGUS.gRegSelectData = reg16 & 0xFF;

			return reg16;
		case 0x307:
			if (warn_out_of_bounds_dram_access && myGUS.gDramAddr >= myGUS.memsize)
				LOG(LOG_MISC,LOG_WARN)("GUS: out of bounds DRAM read %x",(unsigned int)myGUS.gDramAddr);

			if((myGUS.gDramAddr & myGUS.gDramAddrMask) < myGUS.memsize)
				return myGUS.GUSRam[myGUS.gDramAddr & myGUS.gDramAddrMask];
			else
				return 0;
		case 0x306:
		case 0x706:
			if (gus_type >= GUS_MAX)
				return 0x0B; /* UltraMax with CS4231 codec */
			else if (gus_ics_mixer)
				return 0x06; /* revision 3.7+ with ICS-2101 mixer */
			else
				return 0xFF;
			break;
		default:
#if LOG_GUS
			LOG_MSG("Read GUS at port 0x%x", port);
#endif
			break;
	}

	return 0xff;
}


static void write_gus(Bitu port,Bitu val,Bitu iolen) {
	//	LOG_MSG("Write gus port %x val %x",port,val);

	/* 12-bit ISA decode (FIXME: Check GUS MAX ISA card to confirm)
	 *
	 * More than 10 bits must be decoded in order for GUS MAX extended registers at 7xx to work */
	port &= 0xFFF;

	/* Ok, so get this: There's a demoscene entry, 1997 demo "Atlantis, Deep Like A Sea", with code
	 * that does a 16-bit I/O write to port 3x2. Why? Well, if you notice how this switch statement
	 * is done, and how DOSBox SVN registers I/O ports, most of these I/O ports are meant for 8-bit
	 * I/O access. The only exception to that is port 3x4 which allows writing 16 bits to a GF1
	 * register. So what happens if you do a 16-bit write to port 3x2?
	 *
	 * If the demo's behavior is any indication, the 16-bit write is handled like two 8-bit writes.
	 * The lower 8 bits go to port 3x2, the upper to 3x3. If you look at the register map, that's
	 * one 16-bit I/O write that writes both gCurChannel (current channel) and gRegSelect (current
	 * selected register) in one I/O cycle.
	 *
	 * If we do not split the I/O up as described, then the demo makes no sound except for popping
	 * noises because none of the voices are playing anything or moving at all.
	 *
	 * This trick happens to work in DOSBox SVN (and produce audible music) because DOSBox SVN GUS
	 * emulation installs I/O handlers only for bytewise (IO_MB) I/O, which then forces I/O emulation
	 * to subdivide the 16-bit I/O into two 8-bit I/O calls. The only exception is IO_MB|IO_MW for
	 * port 3x4 (gRegData and execute register).
	 *
	 * Demo link: [https://files.scene.org/get/mirrors/hornet/demos/1997/a/atl-mnsn.zip] */
	if (iolen == 2) {
		if ((port - GUS_BASE) != 0x304) {
			write_gus(port,  val&0xFF,1);
			write_gus(port+1,val>>8,  1);
			return;
		}
	}

	switch(port - GUS_BASE) {
		case 0x200:
			myGUS.gRegControl = 0;
			myGUS.mixControl = (uint8_t)val;
			myGUS.ChangeIRQDMA = true;
			return;
		case 0x208:
			adlib_commandreg = (uint8_t)val;
			break;
		case 0x209:
			//TODO adlib_commandreg should be 4 for this to work else it should just latch the value
			if (val & 0x80) {
				myGUS.timers[0].reached=false;
				myGUS.timers[1].reached=false;
				return;
			}
			myGUS.timers[0].masked=(val & 0x40)>0;
			myGUS.timers[1].masked=(val & 0x20)>0;
			if (val & 0x1) {
				if (!myGUS.timers[0].running) {
					PIC_AddEvent(GUS_TimerEvent,myGUS.timers[0].delay,0);
					myGUS.timers[0].running=true;
				}
			} else myGUS.timers[0].running=false;
			if (val & 0x2) {
				if (!myGUS.timers[1].running) {
					PIC_AddEvent(GUS_TimerEvent,myGUS.timers[1].delay,1);
					myGUS.timers[1].running=true;
				}
			} else myGUS.timers[1].running=false;
			break;
			//TODO Check if 0x20a register is also available on the gus like on the interwave
		case 0x20b:
			if (!myGUS.ChangeIRQDMA) break;
			myGUS.ChangeIRQDMA=false;

			if (myGUS.gRegControl == 6) {
				/* Jumper register:
				 *
				 * 7: reserved
				 * 6: reserved
				 * 5: reserved
				 * 4: reserved
				 * 3: reserved
				 * 2: enable joystick port decode
				 * 1: enable midi port address decode
				 * 0: reserved */
				/* TODO: */
				LOG(LOG_MISC,LOG_DEBUG)("GUS: DOS application wrote to 2XB register control number 0x06 (Jumper) val=%02xh",(int)val);
			}
			else if (myGUS.gRegControl == 5) {
				/* write a 0 to clear IRQs on power up (?) */
				LOG(LOG_MISC,LOG_DEBUG)("GUS: DOS application wrote to 2XB register control number 0x05 (Clear IRQs) val=%02xh",(int)val);
			}
			else if (myGUS.gRegControl == 0) {
				if (myGUS.mixControl & 0x40) {
					// GUS SDK: IRQ Control Register
					//     Channel 1 GF1 IRQ selector (bits 2-0)
					//       0=reserved, do not use
					//       1=IRQ2
					//       2=IRQ5
					//       3=IRQ3
					//       4=IRQ7
					//       5=IRQ11
					//       6=IRQ12
					//       7=IRQ15
					//     Channel 2 MIDI IRQ selector (bits 5-3)
					//       0=no interrupt
					//       1=IRQ2
					//       2=IRQ5
					//       3=IRQ3
					//       4=IRQ7
					//       5=IRQ11
					//       6=IRQ12
					//       7=IRQ15
					//     Combine both IRQs using channel 1 (bit 6)
					//     Reserved (bit 7)
					//
					//     "If both channels are sharing an IRQ, channel 2's IRQ must be set to 0 and turn on bit 6. A
					//      bus conflict will occur if both latches are programmed with the same IRQ #."
					if (irqtable[val & 0x7] != 0)
						myGUS.irq1 = irqtable[val & 0x7];

					if (val & 0x40) // "Combine both IRQs"
						myGUS.irq2 = myGUS.irq1;
					else if (((val >> 3) & 7) != 0)
						myGUS.irq2 = irqtable[(val >> 3) & 0x7];

					LOG(LOG_MISC,LOG_DEBUG)("GUS IRQ reprogrammed: GF1 IRQ %d, MIDI IRQ %d",(int)myGUS.irq1,(int)myGUS.irq2);

					gus_warn_irq_conflict = (!(val & 0x40) && (val & 7) == ((val >> 3) & 7));
				} else {
					// GUS SDK: DMA Control Register
					//     Channel 1 (bits 2-0)
					//       0=NO DMA
					//       1=DMA1
					//       2=DMA3
					//       3=DMA5
					//       4=DMA6
					//       5=DMA7
					//       6=?
					//       7=?
					//     Channel 2 (bits 5-3)
					//       0=NO DMA
					//       1=DMA1
					//       2=DMA3
					//       3=DMA5
					//       4=DMA6
					//       5=DMA7
					//       6=?
					//       7=?
					//     Combine both DMA channels using channel 1 (bit 6)
					//     Reserved (bit 7)
					//
					//     "If both channels are sharing an DMA, channel 2's DMA must be set to 0 and turn on bit 6. A
					//      bus conflict will occur if both latches are programmed with the same DMA #."

					// NTS: This emulation does not use the second DMA channel... yet... which is why we do not bother
					//      unregistering and reregistering the second DMA channel.

					GetDMAChannel(myGUS.dma1)->Register_Callback(nullptr); // FIXME: On DMA conflict this could mean kicking other devices off!

					// NTS: Contrary to an earlier commit, DMA channel 0 is not a valid choice
					if (dmatable[val & 0x7] != 0)
						myGUS.dma1 = dmatable[val & 0x7];

					if (val & 0x40) // "Combine both DMA channels"
						myGUS.dma2 = myGUS.dma1;
					else if (dmatable[(val >> 3) & 0x7] != 0)
						myGUS.dma2 = dmatable[(val >> 3) & 0x7];

					GetDMAChannel(myGUS.dma1)->Register_Callback(GUS_DMA_Callback);

					LOG(LOG_MISC,LOG_DEBUG)("GUS DMA reprogrammed: DMA1 %d, DMA2 %d",(int)myGUS.dma1,(int)myGUS.dma2);

					// NTS: The Windows 3.1 Gravis Ultrasound drivers will program the same DMA channel into both without setting the "combining" bit,
					//      even though their own SDK says not to, when Windows starts up. But it then immediately reprograms it normally, so no bus
					//      conflicts actually occur. Strange.
					gus_warn_dma_conflict = (!(val & 0x40) && (val & 7) == ((val >> 3) & 7));
				}
			}
			else {
				LOG(LOG_MISC,LOG_DEBUG)("GUS warning: Port 2XB register control %02xh written (unknown control reg) val=%02xh",(int)myGUS.gRegControl,(int)val);
			}
			break;
		case 0x20f:
			if (gus_type >= GUS_MAX || gus_ics_mixer) {
				myGUS.gRegControl = val;
				myGUS.ChangeIRQDMA = true;
			}
			break;
		case 0x302:
			myGUS.gCurChannel = val & 31;
			if (gus_type < GUS_INTERWAVE) // Versions prior to the Interwave will reflect last I/O to 3X2-3X5 when read back from 3X3
				myGUS.gRegSelectData = (uint8_t)val;

			curchan = guschan[myGUS.gCurChannel];
			break;
		case 0x303:
			myGUS.gRegSelect = myGUS.gRegSelectData = (uint8_t)val;
			myGUS.gRegData = 0;
			break;
		case 0x304:
			if (iolen==2) {
				if (gus_type < GUS_INTERWAVE) // Versions prior to the Interwave will reflect last I/O to 3X2-3X5 when read back from 3X3
					myGUS.gRegSelectData = val & 0xFF;

				myGUS.gRegData=(uint16_t)val;
				ExecuteGlobRegister();
			} else {
				if (gus_type < GUS_INTERWAVE) // Versions prior to the Interwave will reflect last I/O to 3X2-3X5 when read back from 3X3
					myGUS.gRegSelectData = val;

				myGUS.gRegData = (uint16_t)val;
			}
			break;
		case 0x305:
			if (gus_type < GUS_INTERWAVE) // Versions prior to the Interwave will reflect last I/O to 3X2-3X5 when read back from 3X3
				myGUS.gRegSelectData = val;

			myGUS.gRegData = (uint16_t)((0x00ff & myGUS.gRegData) | val << 8);
			ExecuteGlobRegister();
			break;
		case 0x307:
			if (warn_out_of_bounds_dram_access && myGUS.gDramAddr >= myGUS.memsize)
				LOG(LOG_MISC,LOG_WARN)("GUS: out of bounds DRAM write %x val %x",(unsigned int)myGUS.gDramAddr,(unsigned int)val & 0xffu);

			if ((myGUS.gDramAddr & myGUS.gDramAddrMask) < myGUS.memsize)
				myGUS.GUSRam[myGUS.gDramAddr & myGUS.gDramAddrMask] = (uint8_t)val;

			break;
		case 0x306:
		case 0x706:
			if (gus_type >= GUS_MAX) {
				/* Ultramax control register:
				 *
				 * bit 7: reserved
				 * bit 6: codec enable
				 * bit 5: playback channel type (1=16-bit 0=8-bit)
				 * bit 4: capture channel type (1=16-bit 0=8-bit)
				 * bits 3-0: Codec I/O port address decode bits 7-4.
				 *
				 * For example, to put the CS4231 codec at port 0x34C, and enable the codec, write 0x44 to this register.
				 * If you want to move the codec to base I/O port 0x32C, write 0x42 here. */
				myGUS.gUltraMAXControl = val;

				if (val & 0x40) {
					if ((val & 0xF) != ((port >> 4) & 0xF))
						LOG(LOG_MISC,LOG_WARN)("GUS WARNING: DOS application is attempting to relocate the CS4231 codec, which is not supported");
				}
			}
			else if (gus_ics_mixer) {
				if ((port - GUS_BASE) == 0x306)
					GUS_ICS2101.dataWrite(val&0xFF);
				else if ((port - GUS_BASE) == 0x706)
					GUS_ICS2101.addressWrite(val&0xFF);
			}
			break;
		default:
#if LOG_GUS
			LOG_MSG("Write GUS at port 0x%x with %x", port, val);
#endif
			break;
	}
}

static Bitu GUS_Master_Clock = 617400; /* NOTE: This is 1000000Hz / 1.619695497. Seems to be a common base rate within the hardware. */
static Bitu GUS_DMA_Event_transfer = 16; /* DMA words (8 or 16-bit) per interval */
static Bitu GUS_DMA_Events_per_sec = 44100 / 4; /* cheat a little, to improve emulation performance */
static double GUS_DMA_Event_interval = 1000.0 / GUS_DMA_Events_per_sec;
static double GUS_DMA_Event_interval_init = 1000.0 / 44100;
static bool GUS_DMA_Active = false;

void GUS_Update_DMA_Event_transfer() {
	/* NTS: From the GUS SDK, bits 3-4 of DMA Control divide the ISA DMA transfer rate down from "approx 650KHz".
	 *      Bits 3-4 are documented as "DMA Rate divisor" */
	GUS_DMA_Event_transfer = GUS_Master_Clock / GUS_DMA_Events_per_sec / (Bitu)(((Bitu)(myGUS.DMAControl >> 3u) & 3u) + 1u);
	GUS_DMA_Event_transfer &= ~1u; /* make sure it's word aligned in case of 16-bit PCM */
	if (GUS_DMA_Event_transfer == 0) GUS_DMA_Event_transfer = 2;
}

void GUS_DMA_Event_Transfer(DmaChannel *chan,Bitu dmawords) {
	Bitu dmaaddr = (Bitu)(myGUS.dmaAddr << 4ul) + (Bitu)myGUS.dmaAddrOffset;
	Bitu dmalimit = myGUS.memsize;
	unsigned int docount = 0;
	unsigned int step = 0;
	bool dma16xlate;
	Bitu holdAddr;

	// FIXME: What does the GUS do if the DMA address goes beyond the end of memory?

	/* is this a DMA transfer where the GUS memory address is to be translated for 16-bit PCM?
	 * Note that there is plenty of code out there that transfers 16-bit PCM with 8-bit DMA,
	 * and such transfers DO NOT involve the memory address translation.
	 *
	 * MODE    XLATE?    NOTE
	 * 0x00    NO        8-bit PCM, 8-bit DMA (Most DOS programs)
	 * 0x04    DEPENDS   8-bit PCM, 16-bit DMA (Star Control II, see comments below)
	 * 0x40    NO        16-bit PCM, 8-bit DMA (Windows 3.1, Quake, for 16-bit PCM over 8-bit DMA)
	 * 0x44    YES       16-bit PCM, 16-bit DMA (Windows 3.1, Quake, for 16-bit PCM over 16-bit DMA)
	 *
	 * Mode 0x04 is marked DEPENDS. It DEPENDS on whether the assigned DMA channel is 8-bit (NO) or 16-bit (YES).
	 * Mode 0x04 does not appear to be used by any other DOS application or Windows drivers, only by an erratic
	 * bug in Star Control II. FIXME: But, what does REAL hardware do? Drag out the old Pentium 100MHz with the
	 * GUS classic and test! --J.C.
	 *
	 * Star Control II has a bug where, if the GUS DMA channel is 8-bit (DMA channel 0-3), it will upload
	 * it's samples to GUS RAM, one DMA transfer per sample, and sometimes set bit 2. Setting bit 2 incorrectly
	 * tells the GUS it's a 16-bit wide DMA transfer when the DMA channel is 8-bit. But, if the DMA channel is
	 * 16-bit (DMA channel 5-7), Star Control II will correctly set bit 2 in all cases and samples will
	 * transfer correctly to GUS RAM.
	 * */

	/* FIXME: So, if the GUS DMA channel is 8-bit and Star Control II writes mode 0x04 (8-bit PCM, 16-bit DMA), what does the GF1 do?
	 *        I'm guessing so far that something happens within the GF1 to transfer as 8-bit anyway, clearly the developers of Star Control II
	 *        did not hear any audible sign that an invalid DMA control was being used. Perhaps the hardware engineers of the GF1 figured
	 *        out that case and put something in the silicon to ignore the invalid DMA control state. I won't have any answers until
	 *        I pull out an old Pentium box with a GUS classic and check. --J.C.
	 *
	 *        DMA transfers noted by Star Control II that are the reason for this hack (gusdma=1):
	 *
	 *        LOG:  157098507 DEBUG MISC:GUS DMA: terminal count reached. DMAControl=0x21
	 *        LOG:  157098507 DEBUG MISC:GUS DMA transfer 1981 bytes, GUS RAM address 0x0 8-bit DMA 8-bit PCM (ctrl=0x21)
	 *        LOG:  157098507 DEBUG MISC:GUS DMA: terminal count reached. DMAControl=0x21
	 *        LOG:  157100331 DEBUG MISC:GUS DMA: terminal count reached. DMAControl=0x21
	 *        LOG:  157100331 DEBUG MISC:GUS DMA transfer 912 bytes, GUS RAM address 0x7c0 8-bit DMA 8-bit PCM (ctrl=0x21)
	 *        LOG:  157100331 DEBUG MISC:GUS DMA: terminal count reached. DMAControl=0x21
	 *        LOG:  157100470 DEBUG MISC:GUS DMA: terminal count reached. DMAControl=0x25
	 *        LOG:  157100470 DEBUG MISC:GUS DMA transfer 1053 bytes, GUS RAM address 0xb50 16-bit DMA 8-bit PCM (ctrl=0x25)    <-- What?
	 *        LOG:  157100470 DEBUG MISC:GUS DMA: terminal count reached. DMAControl=0x25
	 *        LOG:  157102251 DEBUG MISC:GUS DMA: terminal count reached. DMAControl=0x21
	 *        LOG:  157102251 DEBUG MISC:GUS DMA transfer 1597 bytes, GUS RAM address 0xf80 8-bit DMA 8-bit PCM (ctrl=0x21)
	 *        LOG:  157102251 DEBUG MISC:GUS DMA: terminal count reached. DMAControl=0x21
	 *        LOG:  157104064 DEBUG MISC:GUS DMA: terminal count reached. DMAControl=0x21
	 *        LOG:  157104064 DEBUG MISC:GUS DMA transfer 2413 bytes, GUS RAM address 0x15c0 8-bit DMA 8-bit PCM (ctrl=0x21)
	 *        LOG:  157104064 DEBUG MISC:GUS DMA: terminal count reached. DMAControl=0x21
	 *
	 *        (end list)
	 *
	 *        Noted: Prior to this hack, the samples played by Star Control II sounded more random and often involved leftover
	 *               sample data in GUS RAM, where with this fix, the music now sounds identical to what is played when using
	 *               it's Sound Blaster support. */
	if (myGUS.dma1 < 4/*8-bit DMA channel*/ && (myGUS.DMAControl & 0x44) == 0x04/*8-bit PCM, 16-bit DMA*/)
		dma16xlate = false; /* Star Control II hack: 8-bit PCM, 8-bit DMA, ignore the bit that says it's 16-bit wide */
	else
		dma16xlate = (myGUS.DMAControl & 0x4) ? true : false;

	if (dma16xlate) {
		// 16-bit wide DMA. The GUS SDK specifically mentions that 16-bit DMA is translated
		// to GUS RAM the same way you translate the play pointer. Eugh. But this allows
		// older demos to work properly even if you set the GUS DMA to a 16-bit channel (5)
		// instead of the usual 8-bit channel (1).
		holdAddr = dmaaddr & 0xc0000L;
		dmaaddr = dmaaddr & 0x1ffffL;
		dmaaddr = dmaaddr << 1;
		dmaaddr = (holdAddr | dmaaddr);
		dmalimit = ((dmaaddr & 0xc0000L) | 0x3FFFFL) + 1;
	}

	if (dmaaddr < dmalimit)
		docount = (unsigned int)(dmalimit - dmaaddr);

	docount /= (chan->DMA16+1u);
	if (docount > (chan->currcnt+1u)) docount = chan->currcnt+1u;
	if ((Bitu)docount > dmawords) docount = dmawords;

	// hack: some programs especially Gravis Ultrasound MIDI playback like to upload by DMA but never clear the DMA TC flag on the DMA controller.
	bool saved_tcount = chan->tcount;
	chan->tcount = false;

	if (docount > 0) {
		if ((myGUS.DMAControl & 0x2) == 0) {
			Bitu read=(Bitu)chan->Read((Bitu)docount,&myGUS.GUSRam[dmaaddr]);
			//Check for 16 or 8bit channel
			read*=(chan->DMA16+1u);
			if((myGUS.DMAControl & 0x80) != 0) {
				//Invert the MSB to convert twos complement form
				Bitu i;
				if((myGUS.DMAControl & 0x40) == 0) {
					// 8-bit data
					for(i=dmaaddr;i<(dmaaddr+read);i++) myGUS.GUSRam[i] ^= 0x80;
				} else {
					// 16-bit data
					for(i=dmaaddr+1;i<(dmaaddr+read);i+=2) myGUS.GUSRam[i] ^= 0x80;
				}
			}

			step = read;
		} else {
			//Read data out of UltraSound
			Bitu wd = (Bitu)chan->Write((Bitu)docount,&myGUS.GUSRam[dmaaddr]);
			//Check for 16 or 8bit channel
			wd*=(chan->DMA16+1u);

			step = wd;
		}
	}

	LOG(LOG_MISC,LOG_DEBUG)("GUS DMA transfer %lu bytes, GUS RAM address 0x%lx %u-bit DMA %u-bit PCM (ctrl=0x%02x) tcount=%u",
			(unsigned long)step,(unsigned long)dmaaddr,(myGUS.DMAControl & 0x4) ? 16 : 8,(myGUS.DMAControl & 0x40) ? 16 : 8,myGUS.DMAControl,chan->tcount);

	if (step > 0) {
		dmaaddr += (unsigned int)step;

		if (dma16xlate) {
			holdAddr = dmaaddr & 0xc0000L;
			dmaaddr = dmaaddr & 0x3ffffL;
			dmaaddr = dmaaddr >> 1;
			dmaaddr = (holdAddr | dmaaddr);
		}

		myGUS.dmaAddr = dmaaddr >> 4;
		myGUS.dmaAddrOffset = dmaaddr & 0xF;
	}

	if (chan->tcount) {
		LOG(LOG_MISC,LOG_DEBUG)("GUS DMA transfer hit Terminal Count, setting DMA TC IRQ pending");

		/* Raise the TC irq, and stop DMA */
		myGUS.DMAControl |= 0x100u; /* NTS: DOSBox SVN approach: Use bit 8 for DMA TC IRQ */
		myGUS.IRQStatus |= 0x80;
		saved_tcount = true;
		GUS_CheckIRQ();
		GUS_StopDMA();
	}

	chan->tcount = saved_tcount;
}

void GUS_DMA_Event(Bitu val) {
	(void)val;//UNUSED
	DmaChannel *chan = GetDMAChannel(myGUS.dma1);
	if (chan == NULL) {
		LOG(LOG_MISC,LOG_DEBUG)("GUS DMA event: DMA channel no longer exists, stopping DMA transfer events");
		GUS_DMA_Active = false;
		return;
	}

	if (chan->masked) {
		LOG(LOG_MISC,LOG_DEBUG)("GUS: Stopping DMA transfer interval, DMA masked=%u",chan->masked?1:0);
		GUS_DMA_Active = false;
		return;
	}

	if (!(myGUS.DMAControl & 0x01/*DMA enable*/)) {
		LOG(LOG_MISC,LOG_DEBUG)("GUS DMA event: DMA control 'enable DMA' bit was reset, stopping DMA transfer events");
		GUS_DMA_Active = false;
		return;
	}

	LOG(LOG_MISC,LOG_DEBUG)("GUS DMA event: max %u DMA words. DMA: tc=%u mask=%u cnt=%u",
			(unsigned int)GUS_DMA_Event_transfer,
			chan->tcount?1:0,
			chan->masked?1:0,
			chan->currcnt+1);
	GUS_DMA_Event_Transfer(chan,GUS_DMA_Event_transfer);

	if (GUS_DMA_Active) {
		/* keep going */
		PIC_AddEvent(GUS_DMA_Event,GUS_DMA_Event_interval);
	}
}

void GUS_StopDMA() {
	if (GUS_DMA_Active)
		LOG(LOG_MISC,LOG_DEBUG)("GUS: Stopping DMA transfer interval");

	PIC_RemoveEvents(GUS_DMA_Event);
	GUS_DMA_Active = false;
}

void GUS_StartDMA() {
	if (!GUS_DMA_Active) {
		GUS_DMA_Active = true;
		LOG(LOG_MISC,LOG_DEBUG)("GUS: Starting DMA transfer interval");
		PIC_AddEvent(GUS_DMA_Event,GUS_DMA_Event_interval_init);

		if (GetDMAChannel(myGUS.dma1)->masked)
			LOG(LOG_MISC,LOG_WARN)("GUS: DMA transfer interval started when channel is masked");

		if (gus_warn_dma_conflict)
			LOG(LOG_MISC,LOG_WARN)(
					"GUS warning: Both DMA channels set to the same channel WITHOUT combining! "
					"This is documented to cause bus conflicts on real hardware");
	}
}

static void GUS_DMA_Callback(DmaChannel * chan,DMAEvent event) {
	(void)chan;//UNUSED
	if (event == DMA_UNMASKED) {
		LOG(LOG_MISC,LOG_DEBUG)("GUS: DMA unmasked");
		if (myGUS.DMAControl & 0x01/*DMA enable*/) GUS_StartDMA();
	}
	else if (event == DMA_MASKED) {
		LOG(LOG_MISC,LOG_DEBUG)("GUS: DMA masked. Perhaps it will stop the DMA transfer event.");
	}
}

static void GUS_CallBack(Bitu len) {
	int32_t buffer[MIXER_BUFSIZE][2];
	memset(buffer, 0, len * sizeof(buffer[0]));

	if ((myGUS.GUS_reset_reg & 0x01/*!master reset*/) == 0x01) {
		for (Bitu i = 0; i < myGUS.ActiveChannels; i++) {
			guschan[i]->generateSamples(buffer[0], len);
		}
	}

	// FIXME: I wonder if the GF1 chip DAC had more than 16 bits precision
	//        to render louder than 100% volume without clipping, and if so,
	//        how many extra bits?
	//
	//        If not, then perhaps clipping and saturation were not a problem
	//        unless the volume was set to maximum?
	//
	//        Time to pull out the GUS MAX and test this theory: what happens
	//        if you play samples that would saturate at maximum volume at 16-bit
	//        precision? Does it audibly clip or is there some headroom like some
	//        sort of 17-bit DAC?
	//
	//        One way to test is to play a sample on one channel while another
	//        channel is set to play a single sample at maximum volume (to see
	//        if it makes the audio grungy like a waveform railed to one side).
	//
	//        Past experience with GUS cards says that at full volume their line
	//        out jacks can be quite loud when connected to a speaker.
	//
	//        While improving this code, a better audio compression function
	//        could be implemented that does proper envelope tracking and volume
	//        control for better results than this.
	//
	//        --J.C.

	for (Bitu i = 0; i < len; i++) {
		buffer[i][0] >>= (VOL_SHIFT * AutoAmp) >> 9;
		buffer[i][1] >>= (VOL_SHIFT * AutoAmp) >> 9;
		bool dampenedAutoAmp = false;

		if (buffer[i][0] > 32767) {
			buffer[i][0] = 32767;
			if (enable_autoamp) {
				AutoAmp -= 4; /* dampen faster than recovery */
				dampenedAutoAmp = true;
			}
		}
		else if (buffer[i][0] < -32768) {
			buffer[i][0] = -32768;
			if (enable_autoamp) {
				AutoAmp -= 4; /* dampen faster than recovery */
				dampenedAutoAmp = true;
			}
		}

		if (buffer[i][1] > 32767) {
			buffer[i][1] = 32767;
			if (enable_autoamp && !dampenedAutoAmp) {
				AutoAmp -= 4; /* dampen faster than recovery */
				dampenedAutoAmp = true;
			}
		}
		else if (buffer[i][1] < -32768) {
			buffer[i][1] = -32768;
			if (enable_autoamp && !dampenedAutoAmp) {
				AutoAmp -= 4; /* dampen faster than recovery */
				dampenedAutoAmp = true;
			}
		}

		if (AutoAmp < myGUS.masterVolumeMul && !dampenedAutoAmp) {
			AutoAmp++; /* recovery back to 100% normal volume */
		}
	}

	gus_chan->AddSamples_s32(len, buffer[0]);
	CheckVoiceIrq();
}

// Generate logarithmic to linear volume conversion tables
static void MakeTables(void) {
	int i;
	double out = (double)(1 << 13);
	for (i=4095;i>=0;i--) {
		vol16bit[i]=(uint16_t)((int16_t)out);
		out/=1.002709201;		/* 0.0235 dB Steps */
		//Original amplification routine in the hardware
		//vol16bit[i] = ((256 + i & 0xff) << VOL_SHIFT) / (1 << (24 - (i >> 8)));
	}
	/* FIX: DOSBox 0.74 had code here that produced a pantable which
	 *      had nothing to do with actual panning control variables.
	 *      Instead it seemed to generate a 16-element map that started
	 *      at 0, jumped sharply to unity and decayed to 0.
	 *      The unfortunate result was that stock builds of DOSBox
	 *      effectively locked Gravis Ultrasound capable programs
	 *      to monural audio.
	 *
	 *      This fix generates the table properly so that they correspond
	 *      to how much we attenuate the LEFT channel for any given
	 *      4-bit value of the Panning register (you attenuate the
	 *      RIGHT channel by looking at element 0xF - (val&0xF)).
	 *
	 *      Having made this fix I can finally enjoy old DOS demos
	 *      in GUS stereo instead of having everything mushed into
	 *      mono. */
	if (gus_fixed_table) {
		for (i=0;i < 16;i++)
			pantable[i] = pantablePDF[i] * 2048u;

		LOG(LOG_MISC,LOG_DEBUG)("GUS: using accurate (fixed) pantable");
	}
	else {
		for (i=0;i < 8;i++)
			pantable[i] = 0;
		for (i=8;i < 15;i++)
			pantable[i]=(uint32_t)(-128.0*(log((double)(15-i)/7.0)/log(2.0))*(double)(1 << RAMP_FRACT));

		/* if the program cranks the pan register all the way, ensure the
		 * opposite channel is crushed to silence */
		pantable[15] = 1UL << 30UL;

		LOG(LOG_MISC,LOG_DEBUG)("GUS: using old (naive) pantable");
	}

	LOG(LOG_MISC,LOG_DEBUG)("GUS pantable (attenuation, left to right in dB): hard left -%.3f, -%.3f, -%.3f, -%.3f, -%.3f, -%.3f, -%.3f, center(7) -%.3f, center(8) -%.3f, -%.3f, -%.3f, -%.3f, -%.3f, -%.3f, -%.3f, hard right -%.3f",
			((double)pantable[0]) / (1 << RAMP_FRACT),
			((double)pantable[1]) / (1 << RAMP_FRACT),
			((double)pantable[2]) / (1 << RAMP_FRACT),
			((double)pantable[3]) / (1 << RAMP_FRACT),
			((double)pantable[4]) / (1 << RAMP_FRACT),
			((double)pantable[5]) / (1 << RAMP_FRACT),
			((double)pantable[6]) / (1 << RAMP_FRACT),
			((double)pantable[7]) / (1 << RAMP_FRACT),
			((double)pantable[8]) / (1 << RAMP_FRACT),
			((double)pantable[9]) / (1 << RAMP_FRACT),
			((double)pantable[10]) / (1 << RAMP_FRACT),
			((double)pantable[11]) / (1 << RAMP_FRACT),
			((double)pantable[12]) / (1 << RAMP_FRACT),
			((double)pantable[13]) / (1 << RAMP_FRACT),
			((double)pantable[14]) / (1 << RAMP_FRACT),
			((double)pantable[15]) / (1 << RAMP_FRACT));
}

static IO_ReadHandler* gus_cb_port_r(IO_CalloutObject &co,Bitu port,Bitu iolen) {
	(void)co;
	(void)iolen;

	/* 10-bit ISA decode.
	 * NOTE that the I/O handlers still need more than 10 bits to handle GUS MAX/Interwave registers at 0x7xx. */
	port &= 0x3FF;

	if (gus_type >= GUS_MAX) {
		if (port >= (0x30C + GUS_BASE) && port <= (0x30F + GUS_BASE))
			return read_gus_cs4231;
	}

	return read_gus;
}

static IO_WriteHandler* gus_cb_port_w(IO_CalloutObject &co,Bitu port,Bitu iolen) {
	(void)co;
	(void)iolen;

	/* 10-bit ISA decode.
	 * NOTE that the I/O handlers still need more than 10 bits to handle GUS MAX/Interwave registers at 0x7xx. */
	port &= 0x3FF;

	if (gus_type >= GUS_MAX) {
		if (port >= (0x30C + GUS_BASE) && port <= (0x30F + GUS_BASE))
			return write_gus_cs4231;
	}

	return write_gus;
}

class GUS:public Module_base{
	private:
		//	IO_ReadHandleObject ReadHandler[12];
		//	IO_WriteHandleObject WriteHandler[12];
		//	IO_ReadHandleObject ReadCS4231Handler[4];
		//	IO_WriteHandleObject WriteCS4231Handler[4];
#if !defined(OSFREE)
		AutoexecObject autoexecline[3];
#endif
		MixerObject MixerChan;
		bool gus_enable;
	public:
		GUS(Section* configuration):Module_base(configuration){
			int x;

			gus_enable = false;
			if(!IS_EGAVGA_ARCH) return;
			Section_prop * section=static_cast<Section_prop *>(configuration);
			if(!section->Get_bool("gus")||control->opt_silent) return;

			gus_enable = true;
			memset(&myGUS,0,sizeof(myGUS));

			ignore_active_channel_write_while_active = section->Get_bool("ignore channel count while active");
			warn_out_of_bounds_dram_access = section->Get_bool("warn on out of bounds dram access");

			unmask_irq = section->Get_bool("pic unmask irq");
			enable_autoamp = section->Get_bool("autoamp");

			startup_ultrinit = section->Get_bool("startup initialized");

			dma_enable_on_dma_control_polling = section->Get_bool("dma enable on dma control polling");

			string s_pantable = section->Get_string("gus panning table");
			if (s_pantable == "default" || s_pantable == "" || s_pantable == "accurate")
				gus_fixed_table = true;
			else if (s_pantable == "old")
				gus_fixed_table = false;
			else
				gus_fixed_table = true;

			gus_ics_mixer = false;
			string s_gustype = section->Get_string("gustype");
			if (s_gustype == "classic") {
				LOG(LOG_MISC,LOG_DEBUG)("GUS: Classic emulation");
				gus_type = GUS_CLASSIC;
			}
			else if (s_gustype == "classic37") {
				LOG(LOG_MISC,LOG_DEBUG)("GUS: Classic emulation");
				gus_type = GUS_CLASSIC;
				gus_ics_mixer = true;
			}
			else if (s_gustype == "max") {
				// UltraMAX cards do not have the ICS mixer
				LOG(LOG_MISC,LOG_DEBUG)("GUS: MAX emulation");
				gus_type = GUS_MAX;
			}
			else if (s_gustype == "interwave") {
				// Neither do Interwave cards
				LOG(LOG_MISC,LOG_DEBUG)("GUS: Interwave PnP emulation");
				gus_type = GUS_INTERWAVE;
			}
			else {
				LOG(LOG_MISC,LOG_DEBUG)("GUS: Classic emulation by default");
				gus_type = GUS_CLASSIC;
			}

			myGUS.clearTCIfPollingIRQStatus = section->Get_bool("clear dma tc irq if excess polling");
			if (myGUS.clearTCIfPollingIRQStatus)
				LOG(LOG_MISC,LOG_DEBUG)("GUS: Will clear DMA TC IRQ if excess polling, as instructed");

			myGUS.gUltraMAXControl = 0;
			myGUS.lastIRQStatusPollRapidCount = 0;
			myGUS.lastIRQStatusPollAt = 0;

			myGUS.initUnmaskDMA = section->Get_bool("unmask dma");
			if (myGUS.initUnmaskDMA)
				LOG(LOG_MISC,LOG_DEBUG)("GUS: Unmasking DMA at boot time as requested");

			myGUS.fixed_sample_rate_output = section->Get_bool("gus fixed render rate");
			LOG(LOG_MISC,LOG_DEBUG)("GUS: using %s sample rate output",myGUS.fixed_sample_rate_output?"fixed":"realistic");

			myGUS.force_master_irq_enable=section->Get_bool("force master irq enable");
			if (myGUS.force_master_irq_enable)
				LOG(LOG_MISC,LOG_DEBUG)("GUS: Master IRQ enable will be forced on as instructed");

			{
				const char *s = section->Get_string("global register read alias");

				if (!strcmp(s,"true") || !strcmp(s,"1"))
					myGUS.globalread80alias = true;
				else if (!strcmp(s,"false") || !strcmp(s,"0"))
					myGUS.globalread80alias = false;
				else /* auto */
					myGUS.globalread80alias = false; /* TODO: Test real hardware, which versions of the GUS behave like this? */
			}

			myGUS.rate=(unsigned int)section->Get_int("gusrate");

			ultradir = section->Get_string("ultradir");
			void ResolvePath(std::string& in);
			ResolvePath(ultradir);

			x = section->Get_int("gusmemsize");
			if (x >= 0) myGUS.memsize = (unsigned int)x*1024u;
			else myGUS.memsize = 1024u*1024u;

			if (myGUS.memsize > (8*1024u*1024u))
				myGUS.memsize = (8*1024u*1024u);

			if ((myGUS.memsize&((256u << 10u) - 1u)) != 0)
				LOG(LOG_MISC,LOG_WARN)("GUS emulation warning: %uKB onboard is an unusual value. Usually GUS cards have some multiple of 256KB RAM onboard",myGUS.memsize>>10);

			if (gus_type <= GUS_MAX && myGUS.memsize > (1024u*1024u))
				LOG(LOG_MISC,LOG_WARN)("GUS emulation warning: %uKB onboard is an unusually large (more than 1MB) value for the model of GUS to emuulate.",myGUS.memsize>>10);

			assert(myGUS.GUSRam == NULL);
			{
				const unsigned int mask =
					myGUS.memsize != 0 ? bitop::rounduppow2mask(myGUS.memsize - 1) : 0;

				myGUS.gDramAddrMask = mask;
				myGUS.gDramVoiceMask = mask;
				LOG(LOG_MISC,LOG_DEBUG)("GUS emulation: %uKB onboard (mask 0x%x)",myGUS.memsize>>10,(unsigned int)mask);

				if (mask != 0) {
					myGUS.GUSRam = new unsigned char[mask + 1u + 16u/*safety margin*/];
					memset(myGUS.GUSRam,0xFF,mask + 1u + 16u);
				}
			}

			// some demoscene stuff has music that's way too loud if we render at full volume.
			// the GUS mixer emulation won't fix it because it changes the volume at the Mixer
			// level AFTER the code has rendered and clipped samples to 16-bit range.
			myGUS.masterVolume = section->Get_double("gus master volume");
			myGUS.updateMasterVolume();

			// FIXME: HUH?? Read the port number and subtract 0x200, then use GUS_BASE
			// in other parts of the code to compare against 0x200 and 0x300? That's confusing. Fix!
			myGUS.portbase = (unsigned int)section->Get_hex("gusbase") - 0x200u;

			// TODO: so, if the GUS ULTRASND variable actually mentions two DMA and two IRQ channels,
			//       shouldn't we offer the ability to specify them independently? especially when
			//       GUS NMI is completed to the extent that SBOS and MEGA-EM can run within DOSBox?
			int dma_val = section->Get_int("gusdma");
			if ((dma_val<0) || (dma_val>255)) dma_val = 3;	// sensible default

			int irq_val = section->Get_int("gusirq");
			if ((irq_val<0) || (irq_val>255)) irq_val = 5;	// sensible default

			if (irq_val > 0) {
				string s = section->Get_string("irq hack");
				if (!s.empty() && s != "none") {
					LOG(LOG_MISC,LOG_NORMAL)("GUS emulation: Assigning IRQ hack '%s' as instructed",s.c_str());
					PIC_Set_IRQ_hack(irq_val,PIC_parse_IRQ_hack_string(s.c_str()));
				}
			}

			myGUS.dma1 = (uint8_t)dma_val;
			myGUS.dma2 = (uint8_t)dma_val;
			myGUS.irq1 = (uint8_t)irq_val;
			myGUS.irq2 = (uint8_t)irq_val;

			if (gus_iocallout != IO_Callout_t_none) {
				IO_FreeCallout(gus_iocallout);
				gus_iocallout = IO_Callout_t_none;
			}

			if (gus_iocallout2 != IO_Callout_t_none) {
				IO_FreeCallout(gus_iocallout2);
				gus_iocallout2 = IO_Callout_t_none;
			}

			if (gus_iocallout == IO_Callout_t_none)
				gus_iocallout = IO_AllocateCallout(IO_TYPE_ISA);
			if (gus_iocallout == IO_Callout_t_none)
				E_Exit("Failed to get GUS IO callout handle");

			if (gus_iocallout2 == IO_Callout_t_none)
				gus_iocallout2 = IO_AllocateCallout(IO_TYPE_ISA);
			if (gus_iocallout2 == IO_Callout_t_none)
				E_Exit("Failed to get GUS IO callout handle");

			{
				IO_CalloutObject *obj = IO_GetCallout(gus_iocallout);
				if (obj == NULL) E_Exit("Failed to get GUS IO callout");
				obj->Install(0x200 + GUS_BASE,IOMASK_Combine(IOMASK_ISA_10BIT,IOMASK_Range(16)),gus_cb_port_r,gus_cb_port_w);
				IO_PutCallout(obj);
			}

			{
				IO_CalloutObject *obj = IO_GetCallout(gus_iocallout2);
				if (obj == NULL) E_Exit("Failed to get GUS IO callout");
				obj->Install(0x300 + GUS_BASE,IOMASK_Combine(IOMASK_ISA_10BIT,IOMASK_Range(16)),gus_cb_port_r,gus_cb_port_w);
				IO_PutCallout(obj);
			}

#if 0
			// We'll leave the MIDI interface to the MPU-401 
			// Ditto for the Joystick 
			// GF1 Synthesizer 
			ReadHandler[0].Install(0x302 + GUS_BASE,read_gus,IO_MB);
			WriteHandler[0].Install(0x302 + GUS_BASE,write_gus,IO_MB);

			WriteHandler[1].Install(0x303 + GUS_BASE,write_gus,IO_MB);
			ReadHandler[1].Install(0x303 + GUS_BASE,read_gus,IO_MB);

			WriteHandler[2].Install(0x304 + GUS_BASE,write_gus,IO_MB|IO_MW);
			ReadHandler[2].Install(0x304 + GUS_BASE,read_gus,IO_MB|IO_MW);

			WriteHandler[3].Install(0x305 + GUS_BASE,write_gus,IO_MB);
			ReadHandler[3].Install(0x305 + GUS_BASE,read_gus,IO_MB);

			ReadHandler[4].Install(0x206 + GUS_BASE,read_gus,IO_MB);

			WriteHandler[4].Install(0x208 + GUS_BASE,write_gus,IO_MB);
			ReadHandler[5].Install(0x208 + GUS_BASE,read_gus,IO_MB);

			WriteHandler[5].Install(0x209 + GUS_BASE,write_gus,IO_MB);

			WriteHandler[6].Install(0x307 + GUS_BASE,write_gus,IO_MB);
			ReadHandler[6].Install(0x307 + GUS_BASE,read_gus,IO_MB);

			// Board Only 

			WriteHandler[7].Install(0x200 + GUS_BASE,write_gus,IO_MB);
			ReadHandler[7].Install(0x20A + GUS_BASE,read_gus,IO_MB);
			WriteHandler[8].Install(0x20B + GUS_BASE,write_gus,IO_MB);

			if (gus_type >= GUS_MAX || gus_ics_mixer/*classic with 3.7 mixer*/) {
				/* "This register is only valid for UltraSound boards that are at or above revision 3.4. It is not valid for boards which
				 * have a prior revision number.
				 * On 3.4 and above boards, there is a bank of 6 registers that exist at location 2XB. Register 2XF is used to select
				 * which one is being talked to." */
				ReadHandler[9].Install(0x20F + GUS_BASE,read_gus,IO_MB);
				WriteHandler[9].Install(0x20F + GUS_BASE,write_gus,IO_MB);

				/* FIXME: I'm not so sure Interwave PnP cards have this */
				ReadHandler[10].Install(0x306 + GUS_BASE,read_gus,IO_MB); // Revision level
				ReadHandler[11].Install(0x706 + GUS_BASE,read_gus,IO_MB); // Revision level
				WriteHandler[10].Install(0x306 + GUS_BASE,write_gus,IO_MB); // Mixer control
				WriteHandler[11].Install(0x706 + GUS_BASE,write_gus,IO_MB); // Mixer data / GUS UltraMAX Control register
			}
#endif
			if (gus_type >= GUS_MAX) {
				LOG(LOG_MISC,LOG_WARN)("GUS caution: CS4231 UltraMax emulation is new and experimental at this time and it is not guaranteed to work.");
				LOG(LOG_MISC,LOG_WARN)("GUS caution: CS4231 UltraMax emulation as it exists now may cause applications to hang or malfunction attempting to play through it.");

#if 0
				/* UltraMax has a CS4231 codec at 3XC-3XF */
				/* FIXME: Does the Interwave have a CS4231? */
				for (unsigned int i=0;i < 4;i++) {
					ReadCS4231Handler[i].Install(0x30C + i + GUS_BASE,read_gus_cs4231,IO_MB);
					WriteCS4231Handler[i].Install(0x30C + i + GUS_BASE,write_gus_cs4231,IO_MB);
				}
#endif
			}

			//	DmaChannels[myGUS.dma1]->Register_TC_Callback(GUS_DMA_TC_Callback);

			MakeTables();

			for (uint8_t chan_ct=0; chan_ct<32; chan_ct++) {
				guschan[chan_ct] = new GUSChannel(chan_ct);
			}
			// Register the Mixer CallBack 
			gus_chan=MixerChan.Install(GUS_CallBack,GUS_RATE,"GUS");

			// FIXME: Could we leave the card in reset state until a fake ULTRINIT runs?
			myGUS.gRegData=0x000/*reset*/;
			GUSReset();

			if (myGUS.initUnmaskDMA)
				GetDMAChannel(myGUS.dma1)->SetMask(false);
			if (unmask_irq)
				PIC_SetIRQMask(myGUS.irq1,false);

			gus_chan->Enable(true);

			GetDMAChannel(myGUS.dma1)->Register_Callback(GUS_DMA_Callback);

			if (gus_ics_mixer) {
				// pre-set ourself as if ULTRINIT and ULTRAMIX had been run
				GUS_ICS2101.mixpair[gus_ICS2101::MIC_IN_PORT].setAttenuation(0,0x7F);
				GUS_ICS2101.mixpair[gus_ICS2101::MIC_IN_PORT].setAttenuation(1,0x7F);
				GUS_ICS2101.mixpair[gus_ICS2101::LINE_IN_PORT].setAttenuation(0,0x7F);
				GUS_ICS2101.mixpair[gus_ICS2101::LINE_IN_PORT].setAttenuation(1,0x7F);
				GUS_ICS2101.mixpair[gus_ICS2101::CD_IN_PORT].setAttenuation(0,0x7F);
				GUS_ICS2101.mixpair[gus_ICS2101::CD_IN_PORT].setAttenuation(1,0x7F);
				GUS_ICS2101.mixpair[gus_ICS2101::GF1_OUT_PORT].setAttenuation(0,0x7F);
				GUS_ICS2101.mixpair[gus_ICS2101::GF1_OUT_PORT].setAttenuation(1,0x7F);
				GUS_ICS2101.mixpair[gus_ICS2101::MASTER_OUTPUT_PORT].setAttenuation(0,0x7F);
				GUS_ICS2101.mixpair[gus_ICS2101::MASTER_OUTPUT_PORT].setAttenuation(1,0x7F);

				// master volume update, updates ALL pairs
				GUS_ICS2101.updateVolPair(gus_ICS2101::MASTER_OUTPUT_PORT);
			}

			// Default to GUS MAX 1MB maximum
			myGUS.gDramAddrMask = 0xFFFFF;
			myGUS.gDramVoiceMask = 0xFFFFF;

			// if instructed, configure the card as if ULTRINIT had been run
			if (startup_ultrinit) {
				myGUS.gRegData=0x700;
				GUSReset();

				myGUS.gRegData=0x700;
				GUSReset();
			}
		}

		void DOS_Startup() {
#if !defined(OSFREE)
			int portat = 0x200 + int(GUS_BASE);

			if (!gus_enable) return;

			// ULTRASND=Port,DMA1,DMA2,IRQ1,IRQ2
			// [GUS port], [GUS DMA (recording)], [GUS DMA (playback)], [GUS IRQ (playback)], [GUS IRQ (MIDI)]
			ostringstream temp;
			temp << "@SET ULTRASND=" << hex << setw(3) << portat << ","
				<< dec << (Bitu)myGUS.dma1 << "," << (Bitu)myGUS.dma2 << ","
				<< (Bitu)myGUS.irq1 << "," << (Bitu)myGUS.irq2 << ends;
			// Create autoexec.bat lines
			autoexecline[0].Install(temp.str());
			autoexecline[1].Install(std::string("@SET ULTRADIR=") + ultradir);

			if (gus_type >= GUS_MAX) {
				/* FIXME: Does the Interwave have a CS4231? */
				ostringstream temp2;
				temp2 << "@SET ULTRA16=" << hex << setw(3) << (0x30C+GUS_BASE) << ","
					<< "0,0,1,0" << ends; // FIXME What do these numbers mean?
				autoexecline[2].Install(temp2.str());
			}
#endif
		}

		std::string ultradir;

		void DOS_Shutdown() { /* very likely, we're booting into a guest OS where our environment variable has no meaning anymore */
#if !defined(OSFREE)
			autoexecline[0].Uninstall();
			autoexecline[1].Uninstall();
			autoexecline[2].Uninstall();
#endif
		}

		~GUS() {
			if (gus_iocallout != IO_Callout_t_none) {
				IO_FreeCallout(gus_iocallout);
				gus_iocallout = IO_Callout_t_none;
			}

			if (gus_iocallout2 != IO_Callout_t_none) {
				IO_FreeCallout(gus_iocallout2);
				gus_iocallout2 = IO_Callout_t_none;
			}

			for (uint8_t chan_ct=0; chan_ct<32; chan_ct++) {
				if (guschan[chan_ct] != NULL) {
					delete guschan[chan_ct];
					guschan[chan_ct] = NULL;
				}
			}

			if (myGUS.GUSRam != NULL) {
				delete[] myGUS.GUSRam;
				myGUS.GUSRam = NULL;
			}

#if 0 // FIXME
			if(!IS_EGAVGA_ARCH) return;

			myGUS.gRegData=0x1;
			GUSReset();
			myGUS.gRegData=0x0;

			for(Bitu i=0;i<32;i++) {
				delete guschan[i];
			}

			memset(&myGUS,0,sizeof(myGUS));
			memset(GUSRam,0,1024*1024);
#endif
		}
};

static GUS* test = NULL;

void GUS_DOS_Shutdown() {
	if (test != NULL) test->DOS_Shutdown();
}

void GUS_ShutDown(Section* /*sec*/) {
	if (test != NULL) {
		delete test;	
		test = NULL;
	}
}

void GUS_OnReset(Section *sec) {
	(void)sec;//UNUSED
	if (test == NULL && !IS_PC98_ARCH) {
		LOG(LOG_MISC,LOG_DEBUG)("Allocating GUS emulation");
		test = new GUS(control->GetSection("gus"));
	}
}

void GUS_DOS_Exit(Section *sec) {
	(void)sec;//UNUSED
	GUS_DOS_Shutdown();
}

void GUS_DOS_Boot(Section *sec) {
	(void)sec;//UNUSED
	if (test != NULL) test->DOS_Startup();
}

void GUS_Init() {
	LOG(LOG_MISC,LOG_DEBUG)("Initializing Gravis Ultrasound emulation");

	AddExitFunction(AddExitFunctionFuncPair(GUS_ShutDown),true);
	AddVMEventFunction(VM_EVENT_RESET,AddVMEventFunctionFuncPair(GUS_OnReset));
	AddVMEventFunction(VM_EVENT_DOS_EXIT_BEGIN,AddVMEventFunctionFuncPair(GUS_DOS_Exit));
	AddVMEventFunction(VM_EVENT_DOS_SURPRISE_REBOOT,AddVMEventFunctionFuncPair(GUS_DOS_Exit));
	AddVMEventFunction(VM_EVENT_DOS_EXIT_REBOOT_BEGIN,AddVMEventFunctionFuncPair(GUS_DOS_Exit));
	AddVMEventFunction(VM_EVENT_DOS_INIT_SHELL_READY,AddVMEventFunctionFuncPair(GUS_DOS_Boot));
}

// save state support
void *GUS_TimerEvent_PIC_Event = (void*)((uintptr_t)GUS_TimerEvent);
void *GUS_DMA_Callback_Func = (void*)((uintptr_t)GUS_DMA_Callback);


void POD_Save_GUS( std::ostream& stream )
{
const char pod_name[32] = "GUS";

	if( stream.fail() ) return;
	if( !test ) return;
	if( !gus_chan ) return;


	WRITE_POD( &pod_name, pod_name );

	//*******************************************
	//*******************************************
	//*******************************************

	uint8_t curchan_idx;

	curchan_idx = 0xff;
	for( int lcv=0; lcv<32; lcv++ ) {
		if( curchan == guschan[lcv] ) { curchan_idx = lcv; break; }
	}

	// *******************************************
	// *******************************************
	// *******************************************

	// - pure data
	WRITE_POD( &adlib_commandreg, adlib_commandreg );
	WRITE_POD( &vol16bit, vol16bit );
	WRITE_POD( &pantable, pantable );

	// - pure struct data
	WRITE_POD( &myGUS, myGUS );


	// - pure data
	for( int lcv=0; lcv<32; lcv++ ) {
		WRITE_POD( guschan[lcv], *guschan[lcv] );
	}

	// *******************************************
	// *******************************************
	// *******************************************

	// - reloc ptr
	WRITE_POD( &curchan_idx, curchan_idx );

	// *******************************************
	// *******************************************
	// *******************************************

	gus_chan->SaveState(stream);
}


void POD_Load_GUS( std::istream& stream )
{
	char pod_name[32] = {0};

	if( stream.fail() ) return;
	if( !test ) return;
	if( !gus_chan ) return;


	// error checking
	READ_POD( &pod_name, pod_name );
	if( strcmp( pod_name, "GUS" ) ) {
		stream.clear( std::istream::failbit | std::istream::badbit );
		return;
	}

	//************************************************
	//************************************************
	//************************************************

	uint8_t curchan_idx;

	//*******************************************
	//*******************************************
	//*******************************************

	// - pure data
	READ_POD( &adlib_commandreg, adlib_commandreg );
	READ_POD( &vol16bit, vol16bit );
	READ_POD( &pantable, pantable );

	READ_POD( &myGUS, myGUS );

	for( int lcv=0; lcv<32; lcv++ ) {
		if( !guschan[lcv] ) continue;

		READ_POD( guschan[lcv], *guschan[lcv] );
	}



	// - reloc ptr
	READ_POD( &curchan_idx, curchan_idx );

	//*******************************************
	//*******************************************
	//*******************************************

	curchan = NULL;
	if( curchan_idx != 0xff ) curchan = guschan[curchan_idx];

	//*******************************************
	//*******************************************
	//*******************************************

	gus_chan->LoadState(stream);
}
