Files
picogus/sw/gus/gus-x.cpp
T

1932 lines
73 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* Copyright (C) 2002-2021 The DOSBox Team
* Copyright (C) 2022-2024 Ian Scott
*
* 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 <cmath>
#include "include/dosbox-x-compat.h"
#include "hardware/gpio.h"
#ifdef PSRAM
#include "psram_spi.h"
extern psram_spi_inst_t psram_spi;
#endif
#if defined(INTERP_LINEAR)
#include "hardware/interp.h"
#endif
#include "pico/critical_section.h"
critical_section_t gus_crit;
#include "system/pico_pic.h"
#include "isa/isa_dma.h"
extern dma_inst_t dma_config;
#include "audio/clamp.h"
#include "audio/volctrl.h"
using namespace std;
//Extra bits of precision over normal gus
#define WAVE_FRACT 10
#define WAVE_FRACT_MASK ((1 << WAVE_FRACT)-1)
#define WAVE_MSWMASK ((1 << 17)-1)
#define WAVE_LSWMASK (0xffffffff ^ WAVE_MSWMASK)
/*
#else
#define WAVE_FRACT 9
#define WAVE_FRACT_MASK ((1 << WAVE_FRACT)-1)
#define WAVE_MSWMASK ((1 << 16)-1)
#define WAVE_LSWMASK (0xffffffff ^ WAVE_MSWMASK)
#endif
*/
//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 PGDEBUG_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
#ifdef PSRAM
#define GUS_RAM_SIZE (1024u*1024u)
#else
#define GUS_RAM_SIZE (1024u*128u)
#endif
// 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;
static uint16_t const sample_rates[32] = {
44100,
44100,
44100,
44100,
44100,
44100,
44100,
44100,
44100,
44100,
44100,
44100,
44100,
44100,
41160,
38587,
36317,
34300,
32494,
30870,
29400,
28063,
26843,
25725,
24696,
23746,
22866,
#ifdef FORCE_28CH_27CH
22866,
#else
22050,
#endif
21289,
20580,
19916,
19293,
};
uint8_t adlib_commandreg;
// static MixerChannel * gus_chan;
#ifndef PSRAM
static uint8_t GUSRam[GUS_RAM_SIZE + 16/*safety margin*/]; // 1024K of GUS Ram
#endif
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 dma_enable_on_dma_control_polling = false;
static uint16_t vol16bit[4096];
static uint32_t pantable[16];
static uint32_t buffer_size = 4;
static uint32_t dma_interval = 0;
class GUSChannels;
static void CheckVoiceIrq(void);
static void CheckVoiceIrq_unlocked(void);
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;
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) */
uint32_t dmaAddr;
uint32_t dmaInterval;
uint32_t dmaIntervalOverride;
bool dmaWaiting;
uint8_t TimerControl;
uint8_t SampControl;
uint8_t mixControl;
uint8_t ActiveChannels;
uint8_t gRegControl;
uint32_t basefreq;
struct GusTimer {
uint32_t delay;
uint8_t value;
bool reached;
bool raiseirq;
bool masked;
bool running;
} timers[2];
uint32_t rate;
// Bitu portbase;
uint32_t memsize;
bool irqenabled;
bool initUnmaskDMA;
bool force_master_irq_enable;
bool fixed_44k_output = false;
bool clearTCIfPollingIRQStatus;
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 */
void 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;
}
} myGUS;
extern uint8_t GUS_activeChannels(void) {
return myGUS.ActiveChannels;
}
extern uint8_t GUS_timingChannels(void) {
return myGUS.fixed_44k_output ? 14 : myGUS.ActiveChannels;
}
extern uint32_t GUS_basefreq(void) {
// Special 28 channel handling to work around PCM510xA DAC issues:
#if defined(SCALE_22K_TO_44K)
return myGUS.ActiveChannels == 28 ? 44100 : myGUS.basefreq;
#else
return myGUS.basefreq;
#endif
}
Bitu DEBUG_EnableDebugger(void);
static uint8_t GUS_reset_reg = 0;
class GUSChannels {
public:
uint32_t WaveStart;
uint32_t WaveEnd;
uint32_t WaveAddr;
uint32_t WaveAdd;
uint8_t WaveCtrl;
uint16_t WaveFreq;
uint32_t RampStart;
uint32_t RampEnd;
uint32_t RampVol;
uint32_t RampAdd;
uint8_t RampRate;
uint8_t RampCtrl;
uint8_t PanPot;
uint8_t channum;
uint32_t irqmask;
uint32_t PanLeft;
uint32_t PanRight;
int32_t VolLeft;
int32_t VolRight;
struct sample_cache_t {
uint8_t data[32];
// Signed so it can hold -1 for invalid address
int32_t addr;
int32_t addr_next;
};
mutable sample_cache_t sample_cache;
GUSChannels(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;
sample_cache = {{0}, -1, -1};
}
void ClearCache(void) {
sample_cache.addr = sample_cache.addr_next = -1;
}
INLINE int32_t LoadSample8(const uint32_t addr/*memory address without fractional bits*/) const {
#ifdef PSRAM
return (int8_t)psram_read8(&psram_spi, addr & 0xFFFFFu/*1MB*/) << int32_t(8);
#else
return (int8_t)GUSRam[addr & 0xFFFFFu/*1MB*/] << int32_t(8); /* typecast to sign extend 8-bit value */
#endif
}
INLINE int32_t LoadSample16(const uint32_t addr/*memory address without fractional bits*/) const {
const uint32_t adjaddr = (addr & 0xC0000u/*256KB bank*/) | ((addr & 0x1FFFFu) << 1u/*16-bit sample value within bank*/);
#ifdef PSRAM
return (int16_t)psram_read16(&psram_spi, adjaddr);
#else
return (int16_t)host_readw(GUSRam + adjaddr);/* typecast to sign extend 16-bit value */
#endif
}
#ifdef PSRAM
union int16_t_pair {
uint32_t data32;
int16_t data16[2];
};
INLINE size_t prime_cache(const uint32_t addr, const uint8_t threshold) const {
uint32_t addr_hi = addr & 0xffff0u;
uint8_t addr_part = addr & 0xfu;
if (sample_cache.addr != addr_hi) {
if (sample_cache.addr_next != -1 && sample_cache.addr_next == addr_hi) {
// We could avoid this memcpy by wrapping around the cache address... but I am tired
memcpy(sample_cache.data, sample_cache.data + 16, 16);
} else {
psram_read(&psram_spi, addr_hi, sample_cache.data, 16);
}
sample_cache.addr = addr_hi;
}
// If we're about to read past the end of our cache, populate the other bank
uint32_t addr_next = ((addr_hi + 16) & 0xffff0u);
if (addr_part == threshold && sample_cache.addr_next != addr_next) {
psram_read(&psram_spi, addr_next, (sample_cache.data + 16), 16);
sample_cache.addr_next = addr_next;
}
return addr_part;
}
INLINE int16_t_pair LoadSamples8(const uint32_t addr/*memory address without fractional bits*/) const {
const size_t addr_part = prime_cache(addr, 0xfu);
return (union int16_t_pair){ .data16 = {
(int16_t)((uint16_t)sample_cache.data[addr_part] << 8),
(int16_t)((uint16_t)sample_cache.data[addr_part + 1] << 8)
}};
}
INLINE int16_t_pair LoadSamples16(const uint32_t addr/*memory address without fractional bits*/) const {
const uint32_t adjaddr = (addr & 0xC0000u/*256KB bank*/) | ((addr & 0x1FFFFu) << 1u/*16-bit sample value within bank*/);
const size_t addr_part = prime_cache(adjaddr, 0xeu);
return (union int16_t_pair){ .data16 = {
(int16_t)*(uint16_t*)(sample_cache.data + addr_part),
(int16_t)*(uint16_t*)(sample_cache.data + addr_part + 2)
}};
}
#endif // PSRAM
// Returns a single 16-bit sample from the Gravis's RAM
INLINE int32_t GetSample8() const {
/* LoadSample*() will take care of wrapping to 1MB */
const uint32_t useAddr = WaveAddr >> WAVE_FRACT;
{
// Interpolate
#ifdef PSRAM
union int16_t_pair p = LoadSamples8(useAddr);
#ifdef INTERP_LINEAR
interp0->base01 = p.data32;
interp0->accum[1] = WaveAddr;
return interp0->peek[1];
#else // INTERP_LINEAR
int32_t diff = (int32_t)p.data16[1] - (int32_t)p.data16[0];
int32_t scale = (int32_t)(WaveAddr & WAVE_FRACT_MASK);
return ((int32_t)p.data16[0] + ((diff * scale) >> WAVE_FRACT));
#endif // INTERP_LINEAR
#else // PSRAM
int32_t w1 = LoadSample8(useAddr);
int32_t w2 = LoadSample8(useAddr + 1u);
int32_t diff = w2 - w1;
int32_t scale = (int32_t)(WaveAddr & WAVE_FRACT_MASK);
return (w1 + ((diff * scale) >> WAVE_FRACT));
#endif // PSRAM
}
}
INLINE int32_t GetSample16() const {
/* Load Sample*() will take care of wrapping to 1MB and funky bank/sample conversion */
const uint32_t useAddr = WaveAddr >> WAVE_FRACT;
{
// Interpolate
#ifdef PSRAM
union int16_t_pair p = LoadSamples16(useAddr);
#ifdef INTERP_LINEAR
interp0->base01 = p.data32;
interp0->accum[1] = WaveAddr;
return interp0->peek[1];
#else // INTERP_LINEAR
int32_t diff = (int32_t)p.data16[1] - (int32_t)p.data16[0];
int32_t scale = (int32_t)(WaveAddr & WAVE_FRACT_MASK);
return ((int32_t)p.data16[0] + ((diff * scale) >> WAVE_FRACT));
#endif // INTERP_LINEAR
#else // PSRAM
int32_t w1 = LoadSample16(useAddr);
int32_t w2 = LoadSample16(useAddr + 1u);
int32_t diff = w2 - w1;
int32_t scale = (int32_t)(WaveAddr & WAVE_FRACT_MASK);
return (w1 + ((diff * scale) >> WAVE_FRACT));
#endif // PSRAM
}
}
__force_inline void WriteWaveFreq(uint16_t val) {
WaveFreq = val;
#ifdef FORCE_28CH_27CH
if (myGUS.ActiveChannels == 28) { // fudge to the 27 channel rate
val = (uint16_t)((uint32_t)val * 22050ul / 22866ul);
}
#endif
WaveAdd = ((uint32_t)(val >> 1)) << ((uint32_t)(WAVE_FRACT-9));
if (myGUS.fixed_44k_output) {
WaveAdd = ((WaveAdd * sample_rates[myGUS.ActiveChannels - 1]) + (44100 >> 1)) / 44100;
}
}
__force_inline void WriteWaveCtrl(uint8_t val) {
critical_section_enter_blocking(&gus_crit);
uint32_t oldirq=myGUS.WaveIRQ;
WaveCtrl = val & 0x7f;
if ((val & 0xa0)==0xa0) myGUS.WaveIRQ|=irqmask;
else myGUS.WaveIRQ&=~irqmask;
if (oldirq != myGUS.WaveIRQ)
CheckVoiceIrq_unlocked();
critical_section_exit(&gus_crit);
}
INLINE uint8_t ReadWaveCtrl(void) {
uint8_t ret=WaveCtrl;
if (myGUS.WaveIRQ & irqmask) ret|=0x80;
return ret;
}
__force_inline void UpdateWaveRamp(void) {
WriteWaveFreq(WaveFreq);
WriteRampRate(RampRate);
}
__force_inline void WritePanPot(uint8_t val) {
PanPot = val;
PanLeft = pantable[val & 0xf];
PanRight = pantable[0x0f-(val & 0xf)];
UpdateVolumes();
}
__force_inline uint8_t ReadPanPot(void) {
return PanPot;
}
__force_inline void WriteRampCtrl(uint8_t val) {
critical_section_enter_blocking(&gus_crit);
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_unlocked();
critical_section_exit(&gus_crit);
}
INLINE uint8_t ReadRampCtrl(void) {
uint8_t ret=RampCtrl;
if (myGUS.RampIRQ & irqmask) ret|=0x80;
return ret;
}
__force_inline void WriteRampRate(uint8_t val) {
RampRate = val;
/* 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
if (myGUS.fixed_44k_output) {
#ifdef FORCE_28CH_27CH
if (myGUS.ActiveChannels == 28) {
RampAdd = RampAdd * 22050ul / 22866ul;
}
#endif
RampAdd = ((RampAdd * sample_rates[myGUS.ActiveChannels - 1]) + (44100 >> 1)) / 44100;
}
}
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 */ {
critical_section_enter_blocking(&gus_crit);
myGUS.WaveIRQ |= irqmask;
critical_section_exit(&gus_crit);
}
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) {
critical_section_enter_blocking(&gus_crit);
myGUS.WaveIRQ |= irqmask;
critical_section_exit(&gus_crit);
}
}
}
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=scale_sample(vol16bit[templeft >> RAMP_FRACT], volume.gus, false);
VolRight=scale_sample(vol16bit[tempright >> RAMP_FRACT], volume.gus, false);
}
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) {
critical_section_enter_blocking(&gus_crit);
myGUS.RampIRQ|=irqmask;
critical_section_exit(&gus_crit);
}
/* 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();
}
__force_inline void generateSample(int32_t* stream) {
int32_t tmpsamp;
/* 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. */
// normal output
// Get sample
if (WaveCtrl & WCTRL_16BIT)
tmpsamp = GetSample16();
else
tmpsamp = GetSample8();
// Output stereo sample if DAC enable on
// if ((GUS_reset_reg & 0x02/*DAC enable*/) == 0x02) {
// volume.gus is pre-folded into VolLeft/VolRight by UpdateVolumes
stream[0] += tmpsamp * VolLeft;
stream[1] += tmpsamp * VolRight;
WaveUpdate();
RampUpdate();
// }
}
#if 0
__force_inline 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. */
// 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 ((GUS_reset_reg & 0x02/*DAC enable*/) == 0x02) {
stream[i << 1] += tmpsamp * VolLeft;
stream[(i << 1) + 1] += tmpsamp * VolRight;
WaveUpdate();
RampUpdate();
}
}
}
#endif
};
static GUSChannels *guschan[32] = {NULL};
static GUSChannels *curchan = NULL;
#if C_DEBUG
void DEBUG_PrintGUS() { //debugger "GUS" command
LOG_MSG("GUS regsel=%02x regseld=%02x regdata=%02x DRAMaddr=%06x/%06x memsz=%06x curch=%02x MAXctrl=%02x regctl=%02x",
myGUS.gRegSelect,
myGUS.gRegSelectData,
myGUS.gRegData,
myGUS.gDramAddr,
myGUS.gDramAddrMask,
myGUS.memsize,
myGUS.gCurChannel,
myGUS.gUltraMAXControl,
myGUS.gRegControl);
LOG_MSG("DMActrl=%02x (TC=%u) dmaAddr=%06x timerctl=%02x sampctl=%02x mixctl=%02x activech=%u DACrate=%uHz",
myGUS.DMAControl&0xFF,
(myGUS.DMAControl&0x100)?1:0,
myGUS.dmaAddr,
myGUS.TimerControl,
myGUS.SampControl,
myGUS.mixControl,
myGUS.ActiveChannels,
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=%dus 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++) {
GUSChannels *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 uint32_t GUS_TimerEventHandler(Bitu val);
static PIC_TimerEvent GUS_TimerEvent0 = {
.handler = GUS_TimerEventHandler,
};
static PIC_TimerEvent GUS_TimerEvent1 = {
.handler = GUS_TimerEventHandler,
};
void GUS_StopDMA();
void GUS_StartDMA();
static void GUSReset(void) {
gpio_xor_mask(1u << PICO_DEFAULT_LED_PIN);
unsigned char p_GUS_reset_reg = 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-interpetation 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. */
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 (GUS_reset_reg ^ p_GUS_reset_reg)
LOG_MSG("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);
guschan[i]->ClearCache();
}
// Stop DMA
GUS_StopDMA();
// Reset
adlib_commandreg = 85;
myGUS.IRQStatus = 0;
myGUS.RampIRQ = 0;
myGUS.WaveIRQ = 0;
myGUS.IRQChan = 0;
myGUS.timers[0].delay = 80;
myGUS.timers[1].delay = 320;
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_RemoveEvent(&GUS_TimerEvent0);
PIC_RemoveEvent(&GUS_TimerEvent1);
// myGUS.fixed_44k_output = false;
myGUS.DMAControl = 0x00;
myGUS.mixControl = 0x0b; // latches enabled by default LINEs disabled
myGUS.TimerControl = 0x00;
myGUS.SampControl = 0x00;
myGUS.ActiveChannels = 14;
myGUS.ActiveMask=0xffffffffU >> (32-myGUS.ActiveChannels);
myGUS.basefreq = myGUS.fixed_44k_output ? 44100 : sample_rates[myGUS.ActiveChannels - 1];
myGUS.gCurChannel = 0;
curchan = guschan[myGUS.gCurChannel];
myGUS.dmaAddr = 0;
myGUS.dmaInterval = 0;
myGUS.dmaIntervalOverride = 0;
myGUS.dmaWaiting = false;
myGUS.irqenabled = 0;
myGUS.gRegControl = 0;
myGUS.gDramAddr = 0;
myGUS.gRegData = 0;
}
/* if the card was just put into reset, or the card WAS in reset, bits 1-2 are cleared */
if ((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
GUS_reset_reg &= 1;
}
GUS_CheckIRQ();
}
__force_inline 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 /*&& gus_prev_effective_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. */
// puts("activateirq");
PIC_ActivateIRQ();
} else if (gus_prev_effective_irqstat != 0) {
// puts("deactivateirq");
PIC_DeActivateIRQ();
}
gus_prev_effective_irqstat = irqstat;
}
}
// Inner version: caller must already hold gus_crit
__force_inline static void CheckVoiceIrq_unlocked(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;
// mega hack
// PIC_DeActivateIRQ();
GUS_CheckIRQ();
for (;;) {
uint32_t check=(1u << myGUS.IRQChan);
if (totalmask & check) {
break;
}
myGUS.IRQChan++;
if (myGUS.IRQChan>=myGUS.ActiveChannels) myGUS.IRQChan=0;
}
}
__force_inline static void CheckVoiceIrq(void) {
critical_section_enter_blocking(&gus_crit);
CheckVoiceIrq_unlocked();
critical_section_exit(&gus_crit);
}
uint32_t CheckVoiceIrq_async(Bitu val) {
CheckVoiceIrq();
return 0;
}
__force_inline static uint16_t ExecuteReadRegister(void) {
uint8_t tmpreg;
// LOG_MSG("Read global reg %x",myGUS.gRegSelect);
switch (myGUS.gRegSelect) {
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.ActiveChannels - 1))) << 8;
case 0x41: // Dma control register - read acknowledges DMA IRQ
critical_section_enter_blocking(&gus_crit);
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();
critical_section_exit(&gus_crit);
return (uint16_t)(tmpreg << 8);
case 0x42: // Dma address register
return myGUS.dmaAddr >> 0x4u;
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 = (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
// 10 bit fractional wave address
if (curchan) return (uint16_t)(curchan->WaveStart >> 17);
else return 0x0000;
case 0x83: // Channel LSW start address register
// 10 bit fractional wave address
if (curchan) return (uint16_t)(curchan->WaveStart >> 1);
else return 0x0000;
case 0x84: // Channel MSB end address register
// 10 bit fractional wave address
if (curchan) return (uint16_t)(curchan->WaveEnd >> 17);
else return 0x0000;
case 0x85: // Channel LSW end address register
// 10 bit fractional wave address
if (curchan) return (uint16_t)(curchan->WaveEnd >> 1);
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
// 10 bit fractional wave address
if (curchan) return (uint16_t)(curchan->WaveAddr >> 17);
else return 0x0000;
case 0x8b: // Channel LSW current address register
// 10 bit fractional wave address
if (curchan) return (uint16_t)(curchan->WaveAddr >> 1);
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
critical_section_enter_blocking(&gus_crit);
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_unlocked();
critical_section_exit(&gus_crit);
return (uint16_t)(tmpreg << 8);
default:
#if PGDEBUG_GUS
LOG_MSG("Read Register num 0x%x", myGUS.gRegSelect);
#endif
return myGUS.gRegData;
}
}
static uint32_t GUS_TimerEventHandler(Bitu val) {
// putchar('-');
critical_section_enter_blocking(&gus_crit);
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) {
critical_section_exit(&gus_crit);
// putchar('.');
return myGUS.timers[val].delay; // Keep timer running
}
critical_section_exit(&gus_crit);
// putchar('.');
return 0; // Stop timer
}
__force_inline 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
// TODO figure out what FillUp even does!!
// 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) {
// 10 bit fractional wave address
uint32_t tmpaddr = (uint32_t)(myGUS.gRegData & 0x1fff) << 17; /* upper 13 bits of integer portion */
curchan->WaveStart = (curchan->WaveStart & WAVE_MSWMASK) | tmpaddr;
}
break;
case 0x3: // Channel LSW start address register
if(curchan != NULL) {
// 10 bit fractional wave address
uint32_t tmpaddr = (uint32_t)(myGUS.gRegData & 0xffe0) << 1; /* 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) {
// 10 bit fractional wave address
uint32_t tmpaddr = (uint32_t)(myGUS.gRegData & 0x1fff) << 17; /* upper 13 bits of integer portion */
curchan->WaveEnd = (curchan->WaveEnd & WAVE_MSWMASK) | tmpaddr;
}
break;
case 0x5: // Channel MSW end address register
if(curchan != NULL) {
// 10 bit fractional wave address
uint32_t tmpaddr = (uint32_t)(myGUS.gRegData & 0xffe0) << 1; /* 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) {
// 10 bit fractional wave address
uint32_t tmpaddr = (uint32_t)(myGUS.gRegData & 0x1fff) << 17; /* 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) {
// 10 bit fractional wave address
uint32_t tmpaddr = (uint32_t)(myGUS.gRegData & 0xffff) << 1; /* 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 (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.ActiveChannels = 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. */
// force min channels to 14 - I don't want to subject the poor RP2040 to anything more than 44.1kHz
if(myGUS.ActiveChannels < 14) myGUS.ActiveChannels = 14;
if(myGUS.ActiveChannels > 32) myGUS.ActiveChannels = 32;
myGUS.ActiveMask=0xffffffffU >> (32-myGUS.ActiveChannels);
// myGUS.basefreq = (uint32_t)(1000000.0/(1.619695497*(float)(myGUS.ActiveChannels)));
myGUS.basefreq = myGUS.fixed_44k_output ? 44100 : sample_rates[myGUS.ActiveChannels - 1];
#if PGDEBUG_GUS
LOG_MSG("GUS set to %d channels freq=%luHz", myGUS.ActiveChannels,(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
critical_section_enter_blocking(&gus_crit);
myGUS.DMAControl &= ~0xFFu; // FIXME: Does writing DMA Control clear the DMA TC IRQ?
myGUS.DMAControl |= (uint8_t)(myGUS.gRegData>>8);
if (myGUS.DMAControl & 1) GUS_StartDMA();
else GUS_StopDMA();
critical_section_exit(&gus_crit);
break;
case 0x42: // Gravis DRAM DMA address register
myGUS.dmaAddr = myGUS.gRegData << 0x4u;
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
critical_section_enter_blocking(&gus_crit);
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;
if (!myGUS.timers[0].raiseirq && !myGUS.timers[1].raiseirq) {
GUS_CheckIRQ();
}
critical_section_exit(&gus_crit);
break;
case 0x46: // Timer 1 control
myGUS.timers[0].value = (uint8_t)(myGUS.gRegData>>8);
myGUS.timers[0].delay = ((0x100 - myGUS.timers[0].value) * 80) - 5;
break;
case 0x47: // Timer 2 control
myGUS.timers[1].value = (uint8_t)(myGUS.gRegData>>8);
myGUS.timers[1].delay = ((0x100 - myGUS.timers[1].value) * 320) - 5;
break;
case 0x49: // DMA sampling control register
/*
puts("I don't support sampling.");
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 PGDEBUG_GUS
LOG_MSG("Unimplemented global register %x -- %x", myGUS.gRegSelect, myGUS.gRegData);
#endif
break;
}
return;
}
__force_inline uint8_t read_gus(Bitu port) {
uint16_t reg16;
// LOG_MSG("read from gus port %x",port);
switch(port) {
case 0x6:
#if 0 // no fancy stuff
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) {
DEBUG_LOG_MSG("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;
}
}
#endif // 0 // no fancy stuff
/* 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 0x8:
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 0xa:
return adlib_commandreg;
case 0xf:
return 0xff; // should not happen
case 0x102:
return myGUS.gRegSelectData;
case 0x103:
return myGUS.gRegSelectData;
case 0x104:
reg16 = ExecuteReadRegister() & 0xff;
// 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 0x105:
reg16 = ExecuteReadRegister() >> 8;
// 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 0x107:
if((myGUS.gDramAddr & myGUS.gDramAddrMask) < myGUS.memsize) {
#ifdef PSRAM
return psram_read8(&psram_spi, myGUS.gDramAddr & myGUS.gDramAddrMask);
#else
return GUSRam[myGUS.gDramAddr & myGUS.gDramAddrMask];
#endif
} else {
return 0;
}
case 0x106:
default:
#if PGDEBUG_GUS
LOG_MSG("Read GUS at port 0x%x", port);
#endif
break;
}
return 0xff;
}
__force_inline void write_gus(Bitu port, Bitu val) {
// LOG_MSG("Write gus port %x val %x",port,val);
switch(port) {
case 0x0:
myGUS.gRegControl = 0;
myGUS.mixControl = (uint8_t)val;
// return;
break;
case 0x8:
adlib_commandreg = (uint8_t)val;
break;
case 0x9:
//TODO adlib_commandreg should be 4 for this to work else it should just latch the value
critical_section_enter_blocking(&gus_crit);
if (val & 0x80) {
myGUS.timers[0].reached=false;
myGUS.timers[1].reached=false;
GUS_CheckIRQ();
critical_section_exit(&gus_crit);
// return;
break;
}
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_TimerEvent0, 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_TimerEvent1, myGUS.timers[1].delay, 1);
myGUS.timers[1].running=true;
}
} else myGUS.timers[1].running=false;
critical_section_exit(&gus_crit);
break;
case 0x102:
myGUS.gCurChannel = val & 31;
// 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 0x103:
myGUS.gRegSelect = myGUS.gRegSelectData = (uint8_t)val;
myGUS.gRegData = 0;
break;
case 0x104:
// 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 0x105:
// 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 0x107:
if ((myGUS.gDramAddr & myGUS.gDramAddrMask) < myGUS.memsize) {
#ifdef PSRAM
psram_write8(&psram_spi, myGUS.gDramAddr & myGUS.gDramAddrMask, (uint8_t)val);
// psram_write8_async(&psram_spi, myGUS.gDramAddr & myGUS.gDramAddrMask, (uint8_t)val);
#else
GUSRam[myGUS.gDramAddr & myGUS.gDramAddrMask] = (uint8_t)val;
#endif
}
break;
default:
#if PGDEBUG_GUS
LOG_MSG("Write GUS at port 0x%x with %x", port, val);
#endif
break;
}
}
static bool GUS_DMA_Active = false;
#ifdef POLLING_DMA
__force_inline
#endif
uint32_t
GUS_DMA_EventHandler(Bitu val) {
(void)val;//UNUSED
if (!GUS_DMA_Active) {
return 0;
}
if (!(myGUS.DMAControl & 0x01/*DMA enable*/)) {
// puts("stopping");
DEBUG_LOG_MSG("GUS DMA event: DMA control 'enable DMA' bit was reset, stopping DMA transfer events");
GUS_DMA_Active = false;
return 0;
}
myGUS.dmaWaiting = true;
bool invert_msb = false;
if (myGUS.DMAControl & 0x80) {
if (myGUS.DMAControl & 0x40) {
// 16-bit data
if ((myGUS.dmaAddr & 0x1)) {
invert_msb = true;
}
} else {
invert_msb = true;
}
}
dma_config.invertMsb = invert_msb;
DMA_Start_Write(&dma_config);
return 0;
}
static PIC_TimerEvent GUS_DMA_Event = {
.handler = GUS_DMA_EventHandler
};
void
GUS_DMA_isr() {
myGUS.dmaWaiting = false;
// Pull data from PIO even if we have to throw it away, because otherwise it will be stalled
const uint32_t dma_data = DMA_Complete_Write(&dma_config);
if (!GUS_DMA_Active) {
return;
}
if (!(myGUS.DMAControl & 0x01/*DMA enable*/)) {
// puts("stopping");
DEBUG_LOG_MSG("GUS DMA event: DMA control 'enable DMA' bit was reset, stopping DMA transfer events");
GUS_DMA_Active = false;
return;
}
const uint8_t dma_data8 = dma_data & 0xffu;
#ifdef PSRAM
// psram_write8_async(&psram_spi, myGUS.dmaAddr, dma_config.invertMsb ? dma_data8 ^ 0x80 : dma_data8);
static union {
uint32_t data32;
uint8_t data8[4];
} dma_data_union;
size_t dmaOffset = myGUS.dmaAddr & 0x3;
dma_data_union.data8[dmaOffset] = dma_config.invertMsb ? dma_data ^ 0x80 : dma_data8;
if ((dmaOffset) == 0x3) {
psram_write32_async(&psram_spi, myGUS.dmaAddr - 0x3, dma_data_union.data32);
}
#else
GUSRam[myGUS.dmaAddr] = dma_config.invertMsb ? dma_data8 ^ 0x80 : dma_data8;
#endif
// uart_print_hex_u32(dma_data);
if (dma_data & 0x100u) { // if TC
#ifdef PSRAM
// If data is not flushed
if (dmaOffset != 0x3) { // 0, 1, or 2
// Due to the aligned nature of DMA writes, if we stomp on 1-3 bytes it's not a problem
psram_write32_async(&psram_spi, myGUS.dmaAddr - dmaOffset, dma_data_union.data32);
}
#endif
critical_section_enter_blocking(&gus_crit);
/* Raise the TC irq, and stop DMA */
myGUS.DMAControl |= 0x100u; /* NTS: DOSBox SVN approach: Use bit 8 for DMA TC IRQ */
myGUS.IRQStatus |= 0x80;
GUS_StopDMA();
GUS_CheckIRQ();
critical_section_exit(&gus_crit);
} else {
++myGUS.dmaAddr;
/* keep going */
PIC_AddEvent(&GUS_DMA_Event, myGUS.dmaInterval, 0);
}
}
irq_handler_t GUS_DMA_isr_pt = GUS_DMA_isr;
#ifdef POLLING_DMA
static uint32_t next_event = 0;
#endif
__force_inline void GUS_StopDMA() {
// Setting GUS_DMA_Active to false will cancel the next DMA event if it happens
GUS_DMA_Active = false;
// Clear DMA enable bit (from Interwave Programmer's Guide: "The hardware resets this bit when the TC line is asserted.")
myGUS.DMAControl &= 0x1FEu;
#ifdef POLLING_DMA
next_event = 0;
#endif
if (myGUS.dmaWaiting) {
// Reset the PIO
DMA_Cancel_Write(&dma_config);
myGUS.dmaWaiting = false;
}
}
#ifdef POLLING_DMA
void __force_inline process_dma(void) {
if (!GUS_DMA_Active) {
return;
}
uint32_t cur_time = time_us_32();
if (next_event && cur_time >= next_event) {
next_event = cur_time + GUS_DMA_Event(0);
}
}
#endif
__force_inline void GUS_StartDMA() {
if (GUS_DMA_Active) {
// DMA already started
return;
}
GUS_DMA_Active = true;
DEBUG_LOG_MSG("GUS: Starting DMA transfer interval");
// uart_print_hex_u32((myGUS.DMAControl >> 3u) & 3u);
// From Interwave programmers guide:
// 00:0.5 μs–1.0 μs
// 01:6 μs–7 μs
// 10:6 μs–7 μs
// 11:13 μs–14 μs
// From ULTRAWRD: it's 650KHz with a divisor...
if (myGUS.dmaIntervalOverride) {
myGUS.dmaInterval = myGUS.dmaIntervalOverride;
} else {
switch ((myGUS.DMAControl >> 3u) & 3u) {
case 0b00:
myGUS.dmaInterval = 1;
break;
case 0b01:
myGUS.dmaInterval = 3;
break;
case 0b10:
myGUS.dmaInterval = 6;
break;
case 0b11:
myGUS.dmaInterval = 12;
break;
}
}
#ifndef POLLING_DMA
PIC_AddEvent(&GUS_DMA_Event, myGUS.dmaInterval, 0);
#else
next_event = time_us_32() + myGUS.dmaInterval;
#endif
}
//extern uint32_t __scratch_x("my_sub_section") (GUS_CallBack)(Bitu max_len, int16_t* play_buffer) { // did not compile/link multifw with this.. scratch?? check.
extern uint32_t GUS_CallBack(Bitu max_len, int16_t* play_buffer) {
static int32_t accum[2];
#ifdef SCALE_22K_TO_44K
static int32_t prev_accum[2];
#endif
uint32_t s = 0;
if ((GUS_reset_reg & 0x01/*!master reset*/) == 0x01 && (GUS_reset_reg & 0x02/*DAC enable*/) == 0x02) {
while (s < buffer_size) {
accum[0] = accum[1] = 0;
uint32_t cur_rate = myGUS.basefreq;
for (Bitu c = 0; c < myGUS.ActiveChannels; ++c) {
guschan[c]->generateSample(accum);
}
#ifdef SCALE_22K_TO_44K
if (!myGUS.fixed_44k_output && myGUS.ActiveChannels == 28) {
play_buffer[s << 1] = clamp16((accum[0] + prev_accum[0]) >> 1);
play_buffer[(s << 1) + 1] = clamp16((accum[1] + prev_accum[1]) >> 1);
++s;
}
#endif // SCALE_22K_TO_44K
play_buffer[s << 1] = clamp16(accum[0]);
play_buffer[(s << 1) + 1] = clamp16(accum[1]);
++s;
#ifdef SCALE_22K_TO_44K
prev_accum[0] = accum[0];
prev_accum[1] = accum[1];
#endif
if (cur_rate != myGUS.basefreq) {
// Bail out if our sampling rate changed. This will produce 1 sample at the
// wrong rate, but it's better than producing up to buffer_size samples at the
// wrong rate...
break;
}
}
CheckVoiceIrq();
} else {
return 0;
}
// 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.
#if 0 // revisit autoamp later, or send a higher bit depth to the dac
// Bitu play_i = 0;
for (Bitu i = 0; i < render_samples; i++) {
buffer[i][0] >>= (VOL_SHIFT * AutoAmp) >> 9;
buffer[i][1] >>= (VOL_SHIFT * AutoAmp) >> 9;
if (buffer[i][0] > 32767) {
buffer[i][0] = 32767;
}
else if (buffer[i][0] < -32768) {
buffer[i][0] = -32768;
}
if (buffer[i][1] > 32767) {
buffer[i][1] = 32767;
}
else if (buffer[i][1] < -32768) {
buffer[i][1] = -32768;
}
// if (AutoAmp < myGUS.masterVolumeMul && !dampenedAutoAmp) {
// AutoAmp++; /* recovery back to 100% normal volume */
// }
play_buffer[play_i++] = buffer[i][0];
play_buffer[play_i++] = buffer[i][1];
play_buffer[i >> 1] = clamp(buffer[i][0] >> 14, -32768, 32767);
play_buffer[(i >> 1) + 1] = clamp(buffer[i][1] >> 14, -32768, 32767);
}
#endif
// CheckVoiceIrq();
return s;
}
// Generate one stereo sample for IRQ-driven audio output.
// Returns packed stereo: low 16 bits = left, high 16 bits = right.
extern uint32_t GUS_sample_stereo(void) {
if ((GUS_reset_reg & 0x03) != 0x03) {
return 0;
}
int32_t accum[2] = {0, 0};
for (Bitu c = 0; c < myGUS.ActiveChannels; ++c) {
guschan[c]->generateSample(accum);
}
CheckVoiceIrq();
int16_t l = clamp16(accum[0]);
int16_t r = clamp16(accum[1]);
return ((uint32_t)(uint16_t)l) | ((uint32_t)(uint16_t)r << 16);
}
// 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_MSG("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_MSG("GUS: using old (naive) pantable");
}
LOG_MSG("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));
}
void GUS_SetAudioBuffer(const uint16_t new_buffer_size) {
// PICOGUS special port to set audio buffer size
buffer_size = new_buffer_size;
}
void GUS_SetDMAInterval(const uint16_t newInterval) {
// PICOGUS special port to set DMA interval
printf("setting dma interval to %u\n", newInterval);
myGUS.dmaIntervalOverride = newInterval;
}
void GUS_SetFixed44k(const bool new_force44k) {
myGUS.fixed_44k_output = new_force44k;
myGUS.basefreq = new_force44k ? 44100 : sample_rates[myGUS.ActiveChannels - 1];
printf("fixed 44k output: %u\n", myGUS.fixed_44k_output);
}
class GUS/*:public Module_base*/{
private:
bool gus_enable;
public:
GUS(void) {
int x;
gus_enable = true;
memset(&myGUS,0,sizeof(myGUS));
#ifndef PSRAM
memset(GUSRam,0,GUS_RAM_SIZE);
#endif
gus_fixed_table = true;
myGUS.clearTCIfPollingIRQStatus = false;
myGUS.gUltraMAXControl = 0;
myGUS.lastIRQStatusPollRapidCount = 0;
myGUS.lastIRQStatusPollAt = 0;
myGUS.initUnmaskDMA = false;
myGUS.force_master_irq_enable = false;
myGUS.rate=44100;
myGUS.memsize = GUS_RAM_SIZE;
LOG_MSG("GUS emulation: %uKB onboard",myGUS.memsize>>10);
// 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 = 0.00;
myGUS.updateMasterVolume();
MakeTables();
for (uint8_t chan_ct=0; chan_ct<32; chan_ct++) {
guschan[chan_ct] = new GUSChannels(chan_ct);
}
// FIXME: Could we leave the card in reset state until a fake ULTRINIT runs?
myGUS.gRegData=0x000/*reset*/;
GUSReset();
// Default to GUS MAX 1MB maximum
myGUS.gDramAddrMask = 0xFFFFFu;
// if instructed, configure the card as if ULTRINIT had been run
if (startup_ultrinit) {
myGUS.gRegData=0x700;
GUSReset();
myGUS.gRegData=0x700;
GUSReset();
}
}
~GUS() {
myGUS.gRegData=0x1;
GUSReset();
myGUS.gRegData=0x0;
for(Bitu i=0;i<32;i++) {
delete guschan[i];
}
memset(&myGUS,0,sizeof(myGUS));
#ifndef PSRAM
memset(GUSRam,0,1024*1024);
#endif
}
};
static GUS* test = NULL;
void GUS_OnReset(void) {
LOG_MSG("Allocating GUS emulation");
test = new GUS();
critical_section_init(&gus_crit);
}
void GUS_Setup() {
#if defined(INTERP_LINEAR)
interp_config cfg;
cfg = interp_default_config();
// Linear interpolation setup
interp_config_set_blend(&cfg, true);
interp_set_config(interp0, 0, &cfg);
cfg = interp_default_config();
interp_config_set_shift(&cfg, 1); // Shift WaveAddr by 1
interp_config_set_signed(&cfg, true);
interp_set_config(interp0, 1, &cfg);
#endif
clamp_setup(14, 17);
// Register callback so prescaled channel volumes update when gus vol changes
volctrl_gus_callback = [] {
for (int c = 0; c < 32; c++)
if (guschan[c]) guschan[c]->UpdateVolumes();
};
set_volume(CMD_GUSVOL);
}