dosbox-x/src/hardware/sblaster.cpp
Allofich 496dfa6267 Fix static analysis warnings
Fix warnings for the following:
-include directive not at top of file
-ambiguous indentation for scope of if statement
-not passing expensive object by const reference
-initializing in constructor when using class initialization or initialization
list possible
-member function that doesn't mutate its object not declared const
2023-04-04 23:06:57 +09:00

4350 lines
173 KiB
C++

/*
* 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.
*/
/* TODO: I noticed a "bug" on a SB16 ViBRA where if you use single-cycle DMA
* playback with DSP 4.xx commands with the FIFO enabled, the card seems
* to buffer through the FIFO, play the block, and then when the DSP
* block completes, doesn't play what remains in the FIFO and stops
* immediately. So, if you do very small DSP block single-cycle transfers
* using the SB16 0xB0-0xCF DSP commands, the audio will play fast because
* the last 16-32 samples are being skipped, effectively.
*
* I also noticed (related to this) that Creative's documentation only
* lists using 0xB0/0xC0 for single-cycle playback, OR using 0xB6/0xC6
* for autoinit playback, in other words either single-cycle without FIFO
* or autoinit with FIFO.
*
* Also noticed is that the DSP "nag" mode in my test program can interrupt
* the SB16's DSP chip at the right time (with auto-init DMA) that it can
* cause the DSP to drop a byte and effectively cause stereo left/right
* swapping. It can also cause 16-bit DMA to halt.
*
* As usual, expect this to be a config option. --Jonathan C. */
/* FIXME: Sound Blaster 16 hardware has a FIFO between the ISA BUS and DSP.
* Could we update this code to read through a FIFO instead? How big is this
* FIFO anyway, and which cards have it? Would it also be possible to eliminate
* the need for sb.dma.min? */
#if defined(_MSC_VER)
# pragma warning(disable:4244) /* const fmath::local::uint64_t to double possible loss of data */
# pragma warning(disable:4305) /* truncation from double to float */
# pragma warning(disable:4065) /* switch without case */
#define _USE_MATH_DEFINES /* needed for M_PI definition */
//#define M_PI 3.14159265358979323846 /* pi */
#endif
#include <assert.h>
#include <iomanip>
#include <sstream>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "dosbox.h"
#include "inout.h"
#include "logging.h"
#include "mixer.h"
#include "dma.h"
#include "pic.h"
#include "bios.h"
#include "hardware.h"
#include "control.h"
#include "setup.h"
#include "support.h"
#include "shell.h"
using namespace std;
void MIDI_RawOutByte(uint8_t data);
bool MIDI_Available(void);
#define SB_PIC_EVENTS 0
#define DSP_MAJOR 3
#define DSP_MINOR 1
#define MIXER_INDEX 0x04
#define MIXER_DATA 0x05
#define DSP_RESET 0x06
#define DSP_READ_DATA 0x0A
#define DSP_WRITE_DATA 0x0C
#define DSP_WRITE_STATUS 0x0C
#define DSP_READ_STATUS 0x0E
#define DSP_ACK_16BIT 0x0f
#define DSP_NO_COMMAND 0
#define DMA_BUFSIZE 1024
#define DSP_BUFSIZE 64
#define DSP_DACSIZE 512
//Should be enough for sound generated in millisecond blocks
#define SB_SH 14
#define SB_SH_MASK ((1 << SB_SH)-1)
enum {DSP_S_RESET,DSP_S_RESET_WAIT,DSP_S_NORMAL,DSP_S_HIGHSPEED};
enum SB_TYPES {SBT_NONE=0,SBT_1=1,SBT_PRO1=2,SBT_2=3,SBT_PRO2=4,SBT_16=6,SBT_GB=7}; /* TODO: Need SB 2.0 vs SB 2.01 */
enum REVEAL_SC_TYPES {RSC_NONE=0,RSC_SC400=1};
enum SB_IRQS {SB_IRQ_8,SB_IRQ_16,SB_IRQ_MPU};
enum ESS_TYPES {ESS_NONE=0,ESS_688=1};
enum DSP_MODES {
MODE_NONE,
MODE_DAC,
MODE_DMA,
MODE_DMA_PAUSE,
MODE_DMA_MASKED,
MODE_DMA_REQUIRE_IRQ_ACK /* Sound Blaster 16 style require IRQ ack to resume DMA */
};
enum DMA_MODES {
DSP_DMA_NONE,
DSP_DMA_2,DSP_DMA_3,DSP_DMA_4,DSP_DMA_8,
DSP_DMA_16,DSP_DMA_16_ALIASED
};
enum {
PLAY_MONO,PLAY_STEREO
};
enum {
REC_SILENCE=0,
REC_1KHZ_TONE,
REC_HISS
};
unsigned int sb_recording_source = REC_SILENCE;
bool sb_listen_to_recording_source = false;
struct SB_INFO {
Bitu freq;
uint8_t timeconst;
Bitu dma_dac_srcrate;
struct {
bool recording;
bool stereo,sign,autoinit;
bool force_autoinit;
DMA_MODES mode_assigned;
DMA_MODES mode;
Bitu rate,mul;
Bitu total,left,min;
uint64_t start;
union {
uint8_t b8[DMA_BUFSIZE];
int16_t b16[DMA_BUFSIZE];
} buf;
Bitu bits;
DmaChannel * chan;
Bitu remain_size;
} dma;
bool freq_derived_from_tc; // if set, sb.freq was derived from SB/SBpro time constant
bool def_enable_speaker; // start emulation with SB speaker enabled
bool enable_asp;
bool speaker;
bool midi;
bool vibra;
bool emit_blaster_var;
bool sbpro_stereo_bit_strict_mode; /* if set, stereo bit in mixer can only be set if emulating a Pro. if clear, SB16 can too */
bool sample_rate_limits; /* real SB hardware has limits on the sample rate */
bool single_sample_dma;
bool directdac_warn_speaker_off; /* if set, warn if DSP command 0x10 is being used while the speaker is turned off */
bool dma_dac_mode; /* some very old DOS demos "play" sound by setting the DMA terminal count to 0.
normally that would mean the DMA controller transmitting the same byte at the sample rate,
except that the program creates sound by overwriting that byte periodically.
on actual hardware this happens to work (though with kind of a gritty sound to it),
The DMA emulation here does not handle that well. */
bool goldplay;
bool goldplay_stereo;
bool write_status_must_return_7f; // WRITE_STATUS (port base+0xC) must return 0x7F or 0xFF if set. Some very early demos rely on it.
bool busy_cycle_always;
bool ess_playback_mode;
bool no_filtering;
uint8_t sc400_cfg;
uint8_t time_constant;
uint8_t sc400_dsp_major,sc400_dsp_minor;
uint8_t sc400_jumper_status_1;
uint8_t sc400_jumper_status_2;
DSP_MODES mode;
SB_TYPES type;
REVEAL_SC_TYPES reveal_sc_type; // Reveal SC400 type
ESS_TYPES ess_type; // ESS chipset emulation, to be set only if type == SBT_PRO2
bool ess_extended_mode;
int min_dma_user;
int busy_cycle_hz;
int busy_cycle_duty_percent;
int busy_cycle_io_hack;
double busy_cycle_last_check;
double busy_cycle_last_poll;
struct {
bool pending_8bit;
bool pending_16bit;
} irq;
struct {
uint8_t state;
uint8_t last_cmd;
uint8_t cmd;
uint8_t cmd_len;
uint8_t cmd_in_pos;
uint8_t cmd_in[DSP_BUFSIZE];
struct {
uint8_t lastval;
uint8_t data[DSP_BUFSIZE];
Bitu pos,used;
} in,out;
uint8_t test_register;
Bitu write_busy;
bool highspeed;
bool require_irq_ack;
bool instant_direct_dac;
bool force_goldplay;
bool midi_rwpoll_mode; // DSP command 0x34/0x35 MIDI Read Poll & Write Poll (0x35 with interrupt)
bool midi_read_interrupt;
bool midi_read_with_timestamps;
bool command_aliases;
unsigned int dsp_write_busy_time; /* when you write to the DSP, how long it signals "busy" */
} dsp;
struct {
int16_t last;
double dac_t,dac_pt;
} dac;
struct {
uint8_t index;
uint8_t dac[2],fm[2],cda[2],master[2],lin[2];
uint8_t mic;
bool stereo;
bool enabled;
bool filtered;
bool sbpro_stereo; /* Game or OS is attempting SB Pro type stereo */
uint8_t unhandled[0x48];
} mixer;
struct {
uint8_t reference;
Bits stepsize;
bool haveref;
} adpcm;
struct {
Bitu base;
Bitu irq;
uint8_t dma8,dma16;
bool sb_io_alias;
} hw;
struct {
uint8_t valadd;
uint8_t valxor;
} e2;
MixerChannel * chan;
};
static SB_INFO sb;
static char const * const copyright_string="COPYRIGHT (C) CREATIVE TECHNOLOGY LTD, 1992.";
// number of bytes in input for commands (sb/sbpro)
static uint8_t const DSP_cmd_len_sb[256] = {
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x00
// 1,0,0,0, 2,0,2,2, 0,0,0,0, 0,0,0,0, // 0x10
1,0,0,0, 2,2,2,2, 0,0,0,0, 0,0,0,0, // 0x10 Wari hack
0,0,0,0, 2,0,0,0, 0,0,0,0, 0,0,0,0, // 0x20
0,0,0,0, 0,0,0,0, 1,0,0,0, 0,0,0,0, // 0x30
1,2,2,0, 0,0,0,0, 2,0,0,0, 0,0,0,0, // 0x40
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x50
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x60
0,0,0,0, 2,2,2,2, 0,0,0,0, 0,0,0,0, // 0x70
2,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x80
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x90
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0xa0
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0xb0
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0xc0
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0xd0
1,0,1,0, 1,0,0,0, 0,0,0,0, 0,0,0,0, // 0xe0
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 // 0xf0
};
// number of bytes in input for commands (sb/sbpro compatible Reveal SC400)
static uint8_t const DSP_cmd_len_sc400[256] = {
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x00
// 1,0,0,0, 2,0,2,2, 0,0,0,0, 0,0,0,0, // 0x10
1,0,0,0, 2,2,2,2, 0,0,0,0, 0,0,0,0, // 0x10 Wari hack
0,0,0,0, 2,0,0,0, 0,0,0,0, 0,0,0,0, // 0x20
0,0,0,0, 0,0,0,0, 1,0,0,0, 0,0,0,0, // 0x30
1,2,2,0, 0,0,0,0, 2,0,0,0, 0,0,0,0, // 0x40
1,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x50
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,2,0, // 0x60
0,0,0,0, 2,2,2,2, 0,0,0,0, 0,0,0,0, // 0x70
2,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x80
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x90
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0xa0
3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3, // 0xb0
3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3, // 0xc0
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0xd0
1,0,1,0, 1,0,0,0, 0,0,0,0, 0,0,0,0, // 0xe0
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 // 0xf0
};
// number of bytes in input for commands (sbpro2 compatible ESS)
static uint8_t const DSP_cmd_len_ess[256] = {
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x00
// 1,0,0,0, 2,0,2,2, 0,0,0,0, 0,0,0,0, // 0x10
1,0,0,0, 2,2,2,2, 0,0,0,0, 0,0,0,0, // 0x10 Wari hack
0,0,0,0, 2,0,0,0, 0,0,0,0, 0,0,0,0, // 0x20
0,0,0,0, 0,0,0,0, 1,0,0,0, 0,0,0,0, // 0x30
1,2,2,0, 0,0,0,0, 2,0,0,0, 0,0,0,0, // 0x40
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x50
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x60
0,0,0,0, 2,2,2,2, 0,0,0,0, 0,0,0,0, // 0x70
2,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x80
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x90
1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, // 0xa0 ESS write register commands (0xA0-0xBF). Note this overlap with SB16 is
1,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1, // 0xb0 why ESS chipsets cannot emulate SB16 playback/record commands.
1,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0xc0 ESS additional commands.
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0xd0
1,0,1,0, 1,0,0,0, 0,0,0,0, 0,0,0,0, // 0xe0
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 // 0xf0
};
// number of bytes in input for commands (sb16)
static uint8_t const DSP_cmd_len_sb16[256] = {
0,0,0,0, 1,2,0,0, 1,0,0,0, 0,0,2,1, // 0x00
// 1,0,0,0, 2,0,2,2, 0,0,0,0, 0,0,0,0, // 0x10
1,0,0,0, 2,2,2,2, 0,0,0,0, 0,0,0,0, // 0x10 Wari hack
0,0,0,0, 2,0,0,0, 0,0,0,0, 0,0,0,0, // 0x20
0,0,0,0, 0,0,0,0, 1,0,0,0, 0,0,0,0, // 0x30
1,2,2,0, 0,0,0,0, 2,0,0,0, 0,0,0,0, // 0x40
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x50
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x60
0,0,0,0, 2,2,2,2, 0,0,0,0, 0,0,0,0, // 0x70
2,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x80
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0x90
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0xa0
3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3, // 0xb0
3,3,3,3, 3,3,3,3, 3,3,3,3, 3,3,3,3, // 0xc0
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, // 0xd0
1,0,1,0, 1,0,0,0, 0,0,0,0, 0,0,0,0, // 0xe0
0,0,0,0, 0,0,0,0, 0,1,2,0, 0,0,0,0 // 0xf0
};
static unsigned char ESSregs[0x20] = {0}; /* 0xA0-0xBF */
static unsigned char &ESSreg(uint8_t reg) {
assert(reg >= 0xA0 && reg <= 0xBF);
return ESSregs[reg-0xA0];
}
static uint8_t ASP_regs[256];
static uint8_t ASP_mode = 0x00;
#ifndef max
#define max(a,b) ((a)>(b)?(a):(b))
#endif
#ifndef min
#define min(a,b) ((a)<(b)?(a):(b))
#endif
static void DSP_ChangeMode(DSP_MODES mode);
static void CheckDMAEnd();
static void DMA_DAC_Event(Bitu);
static void END_DMA_Event(Bitu);
static void DMA_Silent_Event(Bitu val);
static void GenerateDMASound(Bitu size);
static void DSP_SetSpeaker(bool how) {
if (sb.speaker==how) return;
sb.speaker=how;
if (sb.type==SBT_16) return;
if (sb.ess_type!=ESS_NONE) return;
sb.chan->Enable(how);
if (sb.speaker) {
PIC_RemoveEvents(DMA_Silent_Event);
CheckDMAEnd();
} else {
;
}
}
/* NTS: Using some old Creative Sound Blaster 16 ViBRA PnP cards as reference,
* the card will send IRQ 9 if the IRQ is configured to either "2" or "9".
* Whichever value is written will be read back. The reason for this has
* to do with the pin on the ISA bus connector that used to signal IRQ 2
* on PC/XT hardware, but was wired to fire IRQ 9 instead on PC/AT hardware
* because of the IRQ 8-15 -> IRQ 2 cascade on AT hardware.
*
* There's not much to change here, because PIC_ActivateIRQ was modified
* to remap IRQ 2 -> IRQ 9 for us *if* emulating AT hardware.
*
* --Jonathan C. */
static INLINE void SB_RaiseIRQ(SB_IRQS type) {
LOG(LOG_SB,LOG_NORMAL)("Raising IRQ");
if (sb.ess_playback_mode) {
if (!(ESSreg(0xB1) & 0x40)) // if ESS playback, and IRQ disabled, do not fire
return;
}
switch (type) {
case SB_IRQ_8:
if (sb.irq.pending_8bit) {
// LOG_MSG("SB: 8bit irq pending");
return;
}
sb.irq.pending_8bit=true;
PIC_ActivateIRQ(sb.hw.irq);
break;
case SB_IRQ_16:
if (sb.irq.pending_16bit) {
// LOG_MSG("SB: 16bit irq pending");
return;
}
sb.irq.pending_16bit=true;
PIC_ActivateIRQ(sb.hw.irq);
break;
default:
break;
}
}
static INLINE void DSP_FlushData(void) {
sb.dsp.out.used=0;
sb.dsp.out.pos=0;
}
static double last_dma_callback = 0.0f;
/* these are settings that the user would probably like to change on the fly during emulation */
void sb_update_recording_source_settings() {
Section_prop* section = static_cast<Section_prop *>(control->GetSection("sblaster"));
sb_listen_to_recording_source=section->Get_bool("listen to recording source");
{
const char *s = section->Get_string("recording source");
if (!strcmp(s,"silence"))
sb_recording_source = REC_SILENCE;
else if (!strcmp(s,"hiss"))
sb_recording_source = REC_HISS;
else if (!strcmp(s,"1khz tone"))
sb_recording_source = REC_1KHZ_TONE;
else
sb_recording_source = REC_SILENCE;
}
}
static void DSP_DMA_CallBack(DmaChannel * chan, DMAEvent event) {
if (chan!=sb.dma.chan || event==DMA_REACHED_TC) return;
else if (event==DMA_MASKED) {
if (sb.mode==MODE_DMA) {
//Catch up to current time, but don't generate an IRQ!
//Fixes problems with later sci games.
double t = PIC_FullIndex() - last_dma_callback;
Bitu s = static_cast<Bitu>(sb.dma.rate * t / 1000.0f);
if (s > sb.dma.min) {
LOG(LOG_SB,LOG_NORMAL)("limiting amount masked to sb.dma.min");
s = sb.dma.min;
}
Bitu min_size = sb.dma.mul >> SB_SH;
if (!min_size) min_size = 1;
min_size *= 2;
if (sb.dma.left > min_size) {
if (s > (sb.dma.left - min_size)) s = sb.dma.left - min_size;
//This will trigger an irq, see GenerateDMASound, so let's not do that
if (!sb.dma.autoinit && sb.dma.left <= sb.dma.min) s = 0;
if (s) GenerateDMASound(s);
}
sb.mode = MODE_DMA_MASKED;
LOG(LOG_SB,LOG_NORMAL)("DMA masked, stopping %s, left %d",sb.dma.recording?"input":"output",chan->currcnt);
}
} else if (event==DMA_UNMASKED) {
if (sb.mode==MODE_DMA_MASKED && sb.dma.mode!=DSP_DMA_NONE) {
DSP_ChangeMode(MODE_DMA);
CheckDMAEnd();
LOG(LOG_SB,LOG_NORMAL)("DMA unmasked, starting %s, auto %d block %d",sb.dma.recording?"input":"output",chan->autoinit,chan->basecnt);
}
}
}
#define MIN_ADAPTIVE_STEP_SIZE 0
#define MAX_ADAPTIVE_STEP_SIZE 32767
#define DC_OFFSET_FADE 254
static INLINE uint8_t decode_ADPCM_4_sample(uint8_t sample,uint8_t & reference,Bits& scale) {
static const int8_t scaleMap[64] = {
0, 1, 2, 3, 4, 5, 6, 7, 0, -1, -2, -3, -4, -5, -6, -7,
1, 3, 5, 7, 9, 11, 13, 15, -1, -3, -5, -7, -9, -11, -13, -15,
2, 6, 10, 14, 18, 22, 26, 30, -2, -6, -10, -14, -18, -22, -26, -30,
4, 12, 20, 28, 36, 44, 52, 60, -4, -12, -20, -28, -36, -44, -52, -60
};
static const uint8_t adjustMap[64] = {
0, 0, 0, 0, 0, 16, 16, 16,
0, 0, 0, 0, 0, 16, 16, 16,
240, 0, 0, 0, 0, 16, 16, 16,
240, 0, 0, 0, 0, 16, 16, 16,
240, 0, 0, 0, 0, 16, 16, 16,
240, 0, 0, 0, 0, 16, 16, 16,
240, 0, 0, 0, 0, 0, 0, 0,
240, 0, 0, 0, 0, 0, 0, 0
};
Bits samp = sample + scale;
if ((samp < 0) || (samp > 63)) {
LOG(LOG_SB,LOG_ERROR)("Bad ADPCM-4 sample");
if(samp < 0 ) samp = 0;
if(samp > 63) samp = 63;
}
Bits ref = reference + scaleMap[samp];
if (ref > 0xff) reference = 0xff;
else if (ref < 0x00) reference = 0x00;
else reference = (uint8_t)(ref&0xff);
scale = (scale + adjustMap[samp]) & 0xff;
return reference;
}
static INLINE uint8_t decode_ADPCM_2_sample(uint8_t sample,uint8_t & reference,Bits& scale) {
static const int8_t scaleMap[24] = {
0, 1, 0, -1, 1, 3, -1, -3,
2, 6, -2, -6, 4, 12, -4, -12,
8, 24, -8, -24, 16, 48, -16, -48
};
static const uint8_t adjustMap[24] = {
0, 4, 0, 4,
252, 4, 252, 4, 252, 4, 252, 4,
252, 4, 252, 4, 252, 4, 252, 4,
252, 0, 252, 0
};
Bits samp = sample + scale;
if ((samp < 0) || (samp > 23)) {
LOG(LOG_SB,LOG_ERROR)("Bad ADPCM-2 sample");
if(samp < 0 ) samp = 0;
if(samp > 23) samp = 23;
}
Bits ref = reference + scaleMap[samp];
if (ref > 0xff) reference = 0xff;
else if (ref < 0x00) reference = 0x00;
else reference = (uint8_t)(ref&0xff);
scale = (scale + adjustMap[samp]) & 0xff;
return reference;
}
INLINE uint8_t decode_ADPCM_3_sample(uint8_t sample,uint8_t & reference,Bits& scale) {
static const int8_t scaleMap[40] = {
0, 1, 2, 3, 0, -1, -2, -3,
1, 3, 5, 7, -1, -3, -5, -7,
2, 6, 10, 14, -2, -6, -10, -14,
4, 12, 20, 28, -4, -12, -20, -28,
5, 15, 25, 35, -5, -15, -25, -35
};
static const uint8_t adjustMap[40] = {
0, 0, 0, 8, 0, 0, 0, 8,
248, 0, 0, 8, 248, 0, 0, 8,
248, 0, 0, 8, 248, 0, 0, 8,
248, 0, 0, 8, 248, 0, 0, 8,
248, 0, 0, 0, 248, 0, 0, 0
};
Bits samp = sample + scale;
if ((samp < 0) || (samp > 39)) {
LOG(LOG_SB,LOG_ERROR)("Bad ADPCM-3 sample");
if(samp < 0 ) samp = 0;
if(samp > 39) samp = 39;
}
Bits ref = reference + scaleMap[samp];
if (ref > 0xff) reference = 0xff;
else if (ref < 0x00) reference = 0x00;
else reference = (uint8_t)(ref&0xff);
scale = (scale + adjustMap[samp]) & 0xff;
return reference;
}
void SB_OnEndOfDMA(void) {
bool was_irq=false;
PIC_RemoveEvents(END_DMA_Event);
if (sb.ess_type == ESS_NONE && sb.reveal_sc_type == RSC_NONE && sb.dma.mode >= DSP_DMA_16) {
was_irq = sb.irq.pending_16bit;
SB_RaiseIRQ(SB_IRQ_16);
}
else {
was_irq = sb.irq.pending_8bit;
SB_RaiseIRQ(SB_IRQ_8);
}
if (!sb.dma.autoinit) {
sb.dsp.highspeed = false;
LOG(LOG_SB,LOG_NORMAL)("Single cycle transfer ended");
sb.mode=MODE_NONE;
sb.dma.mode=DSP_DMA_NONE;
if (sb.ess_playback_mode) {
LOG(LOG_SB,LOG_NORMAL)("ESS DMA stop");
ESSreg(0xB8) &= ~0x01; // automatically stop DMA (right?)
if (sb.dma.chan) sb.dma.chan->Clear_Request();
}
} else {
sb.dma.left=sb.dma.total;
if (!sb.dma.left) {
LOG(LOG_SB,LOG_NORMAL)("Auto-init transfer with 0 size");
sb.dsp.highspeed = false;
sb.mode=MODE_NONE;
}
else if (sb.dsp.require_irq_ack && was_irq) {
/* Sound Blaster 16 behavior: If you do not acknowledge the IRQ, and the card goes to signal another IRQ, the DSP halts playback.
* This is different from previous cards (and clone hardware) that continue playing whether or not you acknowledge the IRQ. */
LOG(LOG_SB,LOG_WARN)("DMA ended when previous IRQ had not yet been acked");
sb.mode=MODE_DMA_REQUIRE_IRQ_ACK;
}
}
}
static unsigned int gen_input_ofs = 0;
static unsigned long long gen_tone_angle = 0;
static unsigned int gen_hiss_rand[2] = {0,0};
static int gen_last_hiss = 0;
static void gen_input_reset(void) {
gen_input_ofs = 0;
}
static double gen_1khz_tone(const bool advance) {
/* sin() is pretty fast on today's hardware, no lookup table necessary */
if (advance) gen_tone_angle++;
return sin((gen_tone_angle * M_PI * 1000.0) / sb.dma_dac_srcrate);
}
static int gen_hiss(unsigned int mask) {
if (gen_hiss_rand[0] == 0) gen_hiss_rand[0] = (unsigned int)rand();
if (gen_hiss_rand[1] == 0) gen_hiss_rand[1] = (unsigned int)rand();
/* ref: [https://stackoverflow.com/questions/167735/fast-pseudo-random-number-generator-for-procedural-content#167764] */
gen_hiss_rand[0] = 36969*(gen_hiss_rand[0] & 0xFFFF) + (gen_hiss_rand[1] >> 16);
gen_hiss_rand[1] = 18000*(gen_hiss_rand[1] & 0xFFFF) + (gen_hiss_rand[0] >> 16);
const unsigned int v = (gen_hiss_rand[1] << 16) + (gen_hiss_rand[0] & 0xFFFF);
const int r = (v - gen_last_hiss) & mask; /* we want a hiss not white noise */
gen_last_hiss = v;
return r;
}
static int gen_noise(unsigned int mask) {
if (gen_hiss_rand[0] == 0) gen_hiss_rand[0] = (unsigned int)rand();
if (gen_hiss_rand[1] == 0) gen_hiss_rand[1] = (unsigned int)rand();
/* ref: [https://stackoverflow.com/questions/167735/fast-pseudo-random-number-generator-for-procedural-content#167764] */
gen_hiss_rand[0] = 36969*(gen_hiss_rand[0] & 0xFFFF) + (gen_hiss_rand[1] >> 16);
gen_hiss_rand[1] = 18000*(gen_hiss_rand[1] & 0xFFFF) + (gen_hiss_rand[0] >> 16);
const unsigned int v = (gen_hiss_rand[1] << 16) + (gen_hiss_rand[0] & 0xFFFF);
const int r = v & mask; /* white noise in this case */
gen_last_hiss = v;
return r;
}
static void gen_input_1khz_tone(Bitu dmabytes,unsigned char *buf) {
const unsigned int ofsmax = sb.dma.stereo ? 2 : 1;
unsigned int fill;
if (sb.dma.mode == DSP_DMA_16 || sb.dma.mode == DSP_DMA_16_ALIASED) {
uint16_t *buf16 = (uint16_t*)buf;
if (sb.dma.mode == DSP_DMA_16_ALIASED) dmabytes >>= 1u;
fill = ((unsigned int)(gen_1khz_tone(false) * 0x4000/*half range*/) & 0xFFFF) ^ (sb.dma.sign ? 0x0000 : 0x8000);
while (dmabytes-- > 0) {
*buf16++ = fill;
if ((++gen_input_ofs) >= ofsmax) {
fill = ((unsigned int)(gen_1khz_tone(true) * 0x4000) & 0xFFFF) ^ (sb.dma.sign ? 0x0000 : 0x8000);
gen_input_ofs = 0;
}
}
}
else { /* 8-bit */
fill = ((((unsigned int)(gen_1khz_tone(false) * 0x4000/*half range*/) + gen_noise(0x7f) - 0x40) & 0xFFFF) ^ (sb.dma.sign ? 0x0000 : 0x8000)) >> 8u;
while (dmabytes-- > 0) {
*buf++ = fill;
if ((++gen_input_ofs) >= ofsmax) {
fill = ((((unsigned int)(gen_1khz_tone(true) * 0x4000/*half range*/) + gen_noise(0x7f) - 0x40) & 0xFFFF) ^ (sb.dma.sign ? 0x0000 : 0x8000)) >> 8u;
gen_input_ofs = 0;
}
}
}
}
static void gen_input_hiss(Bitu dmabytes,unsigned char *buf) {
if (sb.dma.mode == DSP_DMA_16 || sb.dma.mode == DSP_DMA_16_ALIASED) {
uint16_t *buf16 = (uint16_t*)buf;
if (sb.dma.mode == DSP_DMA_16_ALIASED) dmabytes >>= 1u;
while (dmabytes-- > 0) *buf16++ = ((gen_hiss(0x3ff) - 0x200) & 0xFFFF) ^ (sb.dma.sign ? 0x0000 : 0x8000);
}
else { /* 8-bit */
while (dmabytes-- > 0) *buf++ = ((gen_hiss(0x3) - 0x2) & 0xFF) ^ (sb.dma.sign ? 0x00 : 0x80);
}
}
static void gen_input_silence(Bitu dmabytes,unsigned char *buf) {
unsigned int fill;
if (sb.dma.mode == DSP_DMA_16 || sb.dma.mode == DSP_DMA_16_ALIASED) {
uint16_t *buf16 = (uint16_t*)buf;
if (sb.dma.mode == DSP_DMA_16_ALIASED) dmabytes >>= 1u;
fill = sb.dma.sign ? 0x0000 : 0x8000;
while (dmabytes-- > 0) *buf16++ = fill;
}
else { /* 8-bit */
fill = sb.dma.sign ? 0x00 : 0x80;
while (dmabytes-- > 0) *buf++ = fill;
}
}
static void gen_input(Bitu dmabytes,unsigned char *buf) {
switch (sb_recording_source) {
case REC_SILENCE:
gen_input_silence(dmabytes,buf);
break;
case REC_HISS:
gen_input_hiss(dmabytes,buf);
break;
case REC_1KHZ_TONE:
gen_input_1khz_tone(dmabytes,buf);
break;
default:
abort();
}
}
static void GenerateDMASound(Bitu size) {
Bitu read=0;Bitu done=0;Bitu i=0;
// don't read if the DMA channel is masked
if (sb.dma.chan->masked) return;
if (sb.dma_dac_mode) return;
last_dma_callback = PIC_FullIndex();
if(sb.dma.autoinit) {
if (sb.dma.left <= size) size = sb.dma.left;
} else {
if (sb.dma.left <= sb.dma.min)
size = sb.dma.left;
}
if (size > DMA_BUFSIZE) {
/* Maybe it's time to consider rendering intervals based on what the mixer wants rather than odd 1ms DMA packet calculations... */
LOG(LOG_SB,LOG_WARN)("Whoah! GenerateDMASound asked to render too much audio (%u > %u). Read could have overrun the DMA buffer!",(unsigned int)size,DMA_BUFSIZE);
size = DMA_BUFSIZE;
}
if (sb.dma.recording) {
/* How much can we do? assume not masked because we checked that at the start of this function.
* Then generate that much input data. After writing via DMA, mute the audio before it goes to
* the mixer.
*
* TODO: It would be kind of fun to tie the generated audio parameters to mixer controls such
* as allowing the user to control how loud the 1KHz sine wave is with the line in volume
* control, and perhaps we should allow the generated audio to go out to the mixer output
* if the line in is unmuted, subject to the audio volume controls. Or perhaps this code
* should just make another mixer channel for recording sources. */
read=sb.dma.chan->currcnt + 1; /* DMA channel current count remain */
if (read > size) read = size;
if (sb.dma.mode == DSP_DMA_16 || sb.dma.mode == DSP_DMA_16_ALIASED)
gen_input(read,(unsigned char*)(&sb.dma.buf.b16[sb.dma.remain_size]));
else
gen_input(read,&sb.dma.buf.b8[sb.dma.remain_size]);
switch (sb.dma.mode) {
case DSP_DMA_8:
if (sb.dma.stereo) {
read=sb.dma.chan->Write(read,&sb.dma.buf.b8[sb.dma.remain_size]);
if (read > 0 && !sb_listen_to_recording_source) gen_input_silence(read,&sb.dma.buf.b8[sb.dma.remain_size]); /* mute before going out to mixer */
Bitu total=read+sb.dma.remain_size;
if (!sb.dma.sign) sb.chan->AddSamples_s8(total>>1,sb.dma.buf.b8);
else sb.chan->AddSamples_s8s(total>>1,(int8_t*)sb.dma.buf.b8);
if (total&1) {
sb.dma.remain_size=1;
sb.dma.buf.b8[0]=sb.dma.buf.b8[total-1];
} else sb.dma.remain_size=0;
} else {
read=sb.dma.chan->Write(read,sb.dma.buf.b8);
if (read > 0 && !sb_listen_to_recording_source) gen_input_silence(read,sb.dma.buf.b8); /* mute before going out to mixer */
if (!sb.dma.sign) sb.chan->AddSamples_m8(read,sb.dma.buf.b8);
else sb.chan->AddSamples_m8s(read,(int8_t *)sb.dma.buf.b8);
}
break;
case DSP_DMA_16:
case DSP_DMA_16_ALIASED:
if (sb.dma.stereo) {
/* In DSP_DMA_16_ALIASED mode temporarily divide by 2 to get number of 16-bit
samples, because 8-bit DMA Read returns byte size, while in DSP_DMA_16 mode
16-bit DMA Read returns word size */
read=sb.dma.chan->Write(read,(uint8_t *)&sb.dma.buf.b16[sb.dma.remain_size])
>> (sb.dma.mode==DSP_DMA_16_ALIASED ? 1:0);
if (read > 0 && !sb_listen_to_recording_source) gen_input_silence(read,(unsigned char*)(&sb.dma.buf.b16[sb.dma.remain_size])); /* mute before going out to mixer */
Bitu total=read+sb.dma.remain_size;
#if defined(WORDS_BIGENDIAN)
if (sb.dma.sign) sb.chan->AddSamples_s16_nonnative(total>>1,sb.dma.buf.b16);
else sb.chan->AddSamples_s16u_nonnative(total>>1,(uint16_t *)sb.dma.buf.b16);
#else
if (sb.dma.sign) sb.chan->AddSamples_s16(total>>1,sb.dma.buf.b16);
else sb.chan->AddSamples_s16u(total>>1,(uint16_t *)sb.dma.buf.b16);
#endif
if (total&1) {
sb.dma.remain_size=1;
sb.dma.buf.b16[0]=sb.dma.buf.b16[total-1];
} else sb.dma.remain_size=0;
} else {
read=sb.dma.chan->Write(read,(uint8_t *)sb.dma.buf.b16)
>> (sb.dma.mode==DSP_DMA_16_ALIASED ? 1:0);
if (read > 0 && !sb_listen_to_recording_source) gen_input_silence(read,(unsigned char*)sb.dma.buf.b16); /* mute before going out to mixer */
#if defined(WORDS_BIGENDIAN)
if (sb.dma.sign) sb.chan->AddSamples_m16_nonnative(read,sb.dma.buf.b16);
else sb.chan->AddSamples_m16u_nonnative(read,(uint16_t *)sb.dma.buf.b16);
#else
if (sb.dma.sign) sb.chan->AddSamples_m16(read,sb.dma.buf.b16);
else sb.chan->AddSamples_m16u(read,(uint16_t *)sb.dma.buf.b16);
#endif
}
//restore buffer length value to byte size in aliased mode
if (sb.dma.mode==DSP_DMA_16_ALIASED) read=read<<1;
break;
default:
LOG_MSG("Unhandled dma record mode %d",sb.dma.mode);
sb.mode=MODE_NONE;
return;
}
}
else {
switch (sb.dma.mode) {
case DSP_DMA_2:
read=sb.dma.chan->Read(size,sb.dma.buf.b8);
if (read && sb.adpcm.haveref) {
sb.adpcm.haveref=false;
sb.adpcm.reference=sb.dma.buf.b8[0];
sb.adpcm.stepsize=MIN_ADAPTIVE_STEP_SIZE;
i++;
}
for (;i<read;i++) {
MixTemp[done++]=decode_ADPCM_2_sample((sb.dma.buf.b8[i] >> 6) & 0x3,sb.adpcm.reference,sb.adpcm.stepsize);
MixTemp[done++]=decode_ADPCM_2_sample((sb.dma.buf.b8[i] >> 4) & 0x3,sb.adpcm.reference,sb.adpcm.stepsize);
MixTemp[done++]=decode_ADPCM_2_sample((sb.dma.buf.b8[i] >> 2) & 0x3,sb.adpcm.reference,sb.adpcm.stepsize);
MixTemp[done++]=decode_ADPCM_2_sample((sb.dma.buf.b8[i] >> 0) & 0x3,sb.adpcm.reference,sb.adpcm.stepsize);
}
sb.chan->AddSamples_m8(done,MixTemp);
break;
case DSP_DMA_3:
read=sb.dma.chan->Read(size,sb.dma.buf.b8);
if (read && sb.adpcm.haveref) {
sb.adpcm.haveref=false;
sb.adpcm.reference=sb.dma.buf.b8[0];
sb.adpcm.stepsize=MIN_ADAPTIVE_STEP_SIZE;
i++;
}
for (;i<read;i++) {
MixTemp[done++]=decode_ADPCM_3_sample((sb.dma.buf.b8[i] >> 5) & 0x7,sb.adpcm.reference,sb.adpcm.stepsize);
MixTemp[done++]=decode_ADPCM_3_sample((sb.dma.buf.b8[i] >> 2) & 0x7,sb.adpcm.reference,sb.adpcm.stepsize);
MixTemp[done++]=decode_ADPCM_3_sample((sb.dma.buf.b8[i] & 0x3) << 1,sb.adpcm.reference,sb.adpcm.stepsize);
}
sb.chan->AddSamples_m8(done,MixTemp);
break;
case DSP_DMA_4:
read=sb.dma.chan->Read(size,sb.dma.buf.b8);
if (read && sb.adpcm.haveref) {
sb.adpcm.haveref=false;
sb.adpcm.reference=sb.dma.buf.b8[0];
sb.adpcm.stepsize=MIN_ADAPTIVE_STEP_SIZE;
i++;
}
for (;i<read;i++) {
MixTemp[done++]=decode_ADPCM_4_sample(sb.dma.buf.b8[i] >> 4,sb.adpcm.reference,sb.adpcm.stepsize);
MixTemp[done++]=decode_ADPCM_4_sample(sb.dma.buf.b8[i]& 0xf,sb.adpcm.reference,sb.adpcm.stepsize);
}
sb.chan->AddSamples_m8(done,MixTemp);
break;
case DSP_DMA_8:
if (sb.dma.stereo) {
read=sb.dma.chan->Read(size,&sb.dma.buf.b8[sb.dma.remain_size]);
Bitu total=read+sb.dma.remain_size;
if (!sb.dma.sign) sb.chan->AddSamples_s8(total>>1,sb.dma.buf.b8);
else sb.chan->AddSamples_s8s(total>>1,(int8_t*)sb.dma.buf.b8);
if (total&1) {
sb.dma.remain_size=1;
sb.dma.buf.b8[0]=sb.dma.buf.b8[total-1];
} else sb.dma.remain_size=0;
} else {
read=sb.dma.chan->Read(size,sb.dma.buf.b8);
if (!sb.dma.sign) sb.chan->AddSamples_m8(read,sb.dma.buf.b8);
else sb.chan->AddSamples_m8s(read,(int8_t *)sb.dma.buf.b8);
}
break;
case DSP_DMA_16:
case DSP_DMA_16_ALIASED:
if (sb.dma.stereo) {
/* In DSP_DMA_16_ALIASED mode temporarily divide by 2 to get number of 16-bit
samples, because 8-bit DMA Read returns byte size, while in DSP_DMA_16 mode
16-bit DMA Read returns word size */
read=sb.dma.chan->Read(size,(uint8_t *)&sb.dma.buf.b16[sb.dma.remain_size])
>> (sb.dma.mode==DSP_DMA_16_ALIASED ? 1:0);
Bitu total=read+sb.dma.remain_size;
#if defined(WORDS_BIGENDIAN)
if (sb.dma.sign) sb.chan->AddSamples_s16_nonnative(total>>1,sb.dma.buf.b16);
else sb.chan->AddSamples_s16u_nonnative(total>>1,(uint16_t *)sb.dma.buf.b16);
#else
if (sb.dma.sign) sb.chan->AddSamples_s16(total>>1,sb.dma.buf.b16);
else sb.chan->AddSamples_s16u(total>>1,(uint16_t *)sb.dma.buf.b16);
#endif
if (total&1) {
sb.dma.remain_size=1;
sb.dma.buf.b16[0]=sb.dma.buf.b16[total-1];
} else sb.dma.remain_size=0;
} else {
read=sb.dma.chan->Read(size,(uint8_t *)sb.dma.buf.b16)
>> (sb.dma.mode==DSP_DMA_16_ALIASED ? 1:0);
#if defined(WORDS_BIGENDIAN)
if (sb.dma.sign) sb.chan->AddSamples_m16_nonnative(read,sb.dma.buf.b16);
else sb.chan->AddSamples_m16u_nonnative(read,(uint16_t *)sb.dma.buf.b16);
#else
if (sb.dma.sign) sb.chan->AddSamples_m16(read,sb.dma.buf.b16);
else sb.chan->AddSamples_m16u(read,(uint16_t *)sb.dma.buf.b16);
#endif
}
//restore buffer length value to byte size in aliased mode
if (sb.dma.mode==DSP_DMA_16_ALIASED) read=read<<1;
break;
default:
LOG_MSG("Unhandled dma playback mode %d",sb.dma.mode);
sb.mode=MODE_NONE;
return;
}
}
sb.dma.left-=read;
if (!sb.dma.left) SB_OnEndOfDMA();
}
static void DMA_Silent_Event(Bitu val) {
if (sb.dma.left<val) val=sb.dma.left;
if (sb.dma.recording) gen_input(val,sb.dma.buf.b8);
Bitu read = sb.dma.recording ? sb.dma.chan->Write(val,sb.dma.buf.b8) : sb.dma.chan->Read(val,sb.dma.buf.b8);
sb.dma.left-=read;
if (!sb.dma.left) {
if (sb.dma.mode >= DSP_DMA_16) SB_RaiseIRQ(SB_IRQ_16);
else SB_RaiseIRQ(SB_IRQ_8);
if (sb.dma.autoinit) sb.dma.left=sb.dma.total;
else {
sb.mode=MODE_NONE;
sb.dma.mode=sb.dma.mode_assigned=DSP_DMA_NONE;
}
}
if (sb.dma.left) {
Bitu bigger=(sb.dma.left > sb.dma.min) ? sb.dma.min : sb.dma.left;
float delay=(bigger*1000.0f)/sb.dma.rate;
PIC_AddEvent(DMA_Silent_Event,delay,bigger);
}
}
void updateSoundBlasterFilter(Bitu rate);
static void DMA_DAC_Event(Bitu val) {
(void)val;//UNUSED
unsigned char tmp[4];
Bitu read,expct;
signed int L,R;
int16_t out[2];
if (sb.dma.chan->masked) {
PIC_AddEvent(DMA_DAC_Event,1000.0 / sb.dma_dac_srcrate);
return;
}
if (!sb.dma.left)
return;
/* Fix for 1994 Demoscene entry myth_dw: The demo's Sound Blaster Pro initialization will start DMA with
* count == 1 or 2 (triggering Goldplay mode) but will change the DMA initial counter when it begins
* normal playback. If goldplay stereo hack is enabled and we do not catch this case, the first 0.5 seconds
* of music will play twice as fast. */
if (sb.dma.chan != NULL &&
sb.dma.chan->basecnt < ((sb.dma.mode==DSP_DMA_16_ALIASED?2:1)*((sb.dma.stereo || sb.mixer.sbpro_stereo)?2:1))/*size of one sample in DMA counts*/)
sb.single_sample_dma = 1;
else
sb.single_sample_dma = 0;
if (!sb.single_sample_dma) {
// WARNING: This assumes Sound Blaster Pro emulation!
LOG(LOG_SB,LOG_NORMAL)("Goldplay mode unexpectedly switched off, normal DMA playback follows");
sb.dma_dac_mode = 0;
sb.dma_dac_srcrate = sb.freq / (sb.mixer.stereo ? 2 : 1);
sb.chan->SetFreq(sb.dma_dac_srcrate);
updateSoundBlasterFilter(sb.dma_dac_srcrate);
return;
}
/* NTS: chan->Read() deals with DMA unit transfers.
* for 8-bit DMA, read/expct is in bytes, for 16-bit DMA, read/expct is in 16-bit words */
expct = (sb.dma.stereo ? 2u : 1u) * (sb.dma.mode == DSP_DMA_16_ALIASED ? 2u : 1u);
if (sb.dma.recording) {
gen_input(expct,tmp);
read = sb.dma.chan->Write(expct,tmp);
L = R = 0;
}
else {
read = sb.dma.chan->Read(expct,tmp);
//if (read != expct)
// LOG_MSG("DMA read was not sample aligned. Sound may swap channels or become static. On real hardware the same may happen unless audio is prepared specifically.\n");
if (sb.dma.mode == DSP_DMA_16 || sb.dma.mode == DSP_DMA_16_ALIASED) {
L = (int16_t)host_readw(&tmp[0]);
if (!sb.dma.sign) L ^= 0x8000;
if (sb.dma.stereo) {
R = (int16_t)host_readw(&tmp[2]);
if (!sb.dma.sign) R ^= 0x8000;
}
else {
R = L;
}
}
else {
L = tmp[0];
if (!sb.dma.sign) L ^= 0x80;
L = (int16_t)(L << 8);
if (sb.dma.stereo) {
R = tmp[1];
if (!sb.dma.sign) R ^= 0x80;
R = (int16_t)(R << 8);
}
else {
R = L;
}
}
}
if (sb.dma.stereo) {
out[0]=L;
out[1]=R;
sb.chan->AddSamples_s16(1,out);
}
else {
out[0]=L;
sb.chan->AddSamples_m16(1,out);
}
/* NTS: The reason we check this is that sometimes the various "checks" performed by
- * setup/configuration tools will setup impossible playback scenarios to test
- * the card that would result in read > sb.dma.left. If read > sb.dma.left then
- * the subtraction below would drive sb.dma.left below zero and the IRQ would
- * never fire, and the test program would fail to detect SB16 emulation.
- *
- * Bugfix for "Extreme Assault" that allows the game to detect Sound Blaster 16
- * hardware. "Extreme Assault"'s SB16 test appears to configure a DMA transfer
- * of 1 byte then attempt to play 16-bit signed stereo PCM (4 bytes) which prior
- * to this fix would falsely trigger Goldplay then cause sb.dma.left to underrun
- * and fail to fire the IRQ. */
if (sb.dma.left >= read)
sb.dma.left -= read;
else
sb.dma.left = 0;
if (!sb.dma.left) {
SB_OnEndOfDMA();
if (sb.dma_dac_mode) PIC_AddEvent(DMA_DAC_Event,1000.0 / sb.dma_dac_srcrate);
}
else {
PIC_AddEvent(DMA_DAC_Event,1000.0 / sb.dma_dac_srcrate);
}
}
static void END_DMA_Event(Bitu val) {
GenerateDMASound(val);
}
static void CheckDMAEnd(void) {
if (!sb.dma.left) return;
if (!sb.speaker && sb.type!=SBT_16 && sb.ess_type==ESS_NONE) {
Bitu bigger=(sb.dma.left > sb.dma.min) ? sb.dma.min : sb.dma.left;
float delay=(bigger*1000.0f)/sb.dma.rate;
PIC_AddEvent(DMA_Silent_Event,delay,bigger);
LOG(LOG_SB,LOG_NORMAL)("Silent DMA Transfer scheduling IRQ in %.3f milliseconds",delay);
} else if (sb.dma.left<sb.dma.min) {
float delay=(sb.dma.left*1000.0f)/sb.dma.rate;
LOG(LOG_SB,LOG_NORMAL)("Short transfer scheduling IRQ in %.3f milliseconds",delay);
PIC_AddEvent(END_DMA_Event,delay,sb.dma.left);
}
}
static void DSP_ChangeMode(DSP_MODES mode) {
if (sb.mode==mode) return;
else sb.chan->FillUp();
sb.mode=mode;
}
static void DSP_RaiseIRQEvent(Bitu /*val*/) {
SB_RaiseIRQ(SB_IRQ_8);
}
static void DSP_DoDMATransfer(DMA_MODES mode,Bitu freq,bool stereo,bool dontInitLeft=false) {
char const * type;
sb.mode=MODE_DMA_MASKED;
/* Explanation: A handful of ancient DOS demos (in the 1990-1992 timeframe) were written to output
* sound using the timer interrupt (IRQ 0) at a fixed rate to a device, usually the
* PC speaker or LPT1 DAC. When SoundBlaster came around, the programmers decided
* apparently to treat the Sound Blaster in the same way, so many of these early
* demos (especially those using GoldPlay) used either Direct DAC output or a hacked
* form of DMA single-cycle 8-bit output.
*
* The way the hacked DMA mode works, is that the Sound Blaster is told the transfer
* length is 65536 or some other large value. Then, the DMA controller is programmed
* to point at a specific byte (or two bytes for stereo) and the counter value for
* that DMA channel is set to 0 (or 1 for stereo). This means that as the Sound Blaster
* fetches bytes to play, the DMA controller ends up sending the same byte value
* over and over again. However, the demo has the timer running at the desired sample
* rate (IRQ 0) and the interrupt routine is modifying the byte to reflect the latest
* sample output. In this way, the demo renders audio whenever it feels like it and
* the Sound Blaster gets audio at the rate it works best with.
*
* It's worth noting the programmers may have done this because DMA playback is the
* only way to get SB Pro stereo output working.
*
* The problem here in DOSBox is that the DMA block-transfer code here is not precise
* enough to handle that properly. When you run such a program in DOSBox 0.74 and
* earlier, you get a low-frequency digital "rumble" that kinda-sorta sounds like
* what the demo is playing (the same byte value repeated over and over again,
* remember?). The only way to properly render such output, is to read the memory
* value at the sample rate and buffer it for output.
*
* This fixes Sound Blaster output in:
* Twilight Zone - Buttman (1992) [SB and SB Pro modes]
* Triton - Crystal Dream (1992) [SB and SB Pro modes]
* The Jungly (1992) [SB and SB Pro modes]
*/
if (sb.dma.chan != NULL &&
sb.dma.chan->basecnt < ((mode==DSP_DMA_16_ALIASED?2:1)*((stereo || sb.mixer.sbpro_stereo)?2:1))/*size of one sample in DMA counts*/)
sb.single_sample_dma = 1;
else
sb.single_sample_dma = 0;
sb.dma_dac_srcrate=freq;
if (sb.dsp.force_goldplay || (sb.goldplay && sb.freq > 0 && sb.single_sample_dma))
sb.dma_dac_mode=1;
else
sb.dma_dac_mode=0;
/* explanation: the purpose of Goldplay stereo mode is to compensate for the fact
* that demos using this method of playback know to set the SB Pro stereo bit, BUT,
* apparently did not know that they needed to double the sample rate when
* computing the DSP time constant. Such demos sound "OK" on Sound Blaster Pro but
* have audible aliasing artifacts because of this. The Goldplay Stereo hack
* detects this condition and doubles the sample rate to better capture what the
* demo is *trying* to do. NTS: sb.freq is the raw sample rate given by the
* program, before it is divided by two for stereo.
*
* Of course, some demos like Crystal Dream take the approach of just setting the
* sample rate to the max supported by the card and then letting it's timer interrupt
* define the sample rate. So of course anything below 44.1KHz sounds awful. */
if (sb.dma_dac_mode && sb.goldplay_stereo && (stereo || sb.mixer.sbpro_stereo) && sb.single_sample_dma)
sb.dma_dac_srcrate = sb.freq;
sb.chan->FillUp();
if (!dontInitLeft)
sb.dma.left=sb.dma.total;
sb.dma.mode=sb.dma.mode_assigned=mode;
sb.dma.stereo=stereo;
sb.irq.pending_8bit=false;
sb.irq.pending_16bit=false;
switch (mode) {
case DSP_DMA_2:
type="2-bits ADPCM";
sb.dma.mul=(1 << SB_SH)/4;
break;
case DSP_DMA_3:
type="3-bits ADPCM";
sb.dma.mul=(1 << SB_SH)/3;
break;
case DSP_DMA_4:
type="4-bits ADPCM";
sb.dma.mul=(1 << SB_SH)/2;
break;
case DSP_DMA_8:
type="8-bits PCM";
sb.dma.mul=(1 << SB_SH);
break;
case DSP_DMA_16_ALIASED:
type="16-bits(aliased) PCM";
sb.dma.mul=(1 << SB_SH)*2;
break;
case DSP_DMA_16:
type="16-bits PCM";
sb.dma.mul=(1 << SB_SH);
break;
default:
LOG(LOG_SB,LOG_ERROR)("DSP:Illegal transfer mode %d",mode);
return;
}
if (sb.dma.stereo) sb.dma.mul*=2;
sb.dma.rate=(sb.dma_dac_srcrate*sb.dma.mul) >> SB_SH;
sb.dma.min=((Bitu)sb.dma.rate*(Bitu)(sb.min_dma_user >= 0 ? sb.min_dma_user : /*default*/3))/1000u;
if (sb.dma_dac_mode && sb.goldplay_stereo && (stereo || sb.mixer.sbpro_stereo) && sb.single_sample_dma) {
// LOG(LOG_SB,LOG_DEBUG)("Goldplay stereo hack. freq=%u rawfreq=%u dacrate=%u",(unsigned int)freq,(unsigned int)sb.freq,(unsigned int)sb.dma_dac_srcrate);
sb.chan->SetFreq(sb.dma_dac_srcrate);
updateSoundBlasterFilter(freq); /* BUT, you still filter like the actual sample rate */
}
else {
sb.chan->SetFreq(freq);
updateSoundBlasterFilter(freq);
}
sb.dma.mode=sb.dma.mode_assigned=mode;
PIC_RemoveEvents(DMA_DAC_Event);
PIC_RemoveEvents(END_DMA_Event);
if (sb.dma_dac_mode)
PIC_AddEvent(DMA_DAC_Event,1000.0 / sb.dma_dac_srcrate);
if (sb.dma.chan != NULL) {
sb.dma.chan->Register_Callback(DSP_DMA_CallBack);
}
else {
LOG(LOG_SB,LOG_WARN)("DMA transfer initiated with no channel assigned");
}
#if (C_DEBUG)
LOG(LOG_SB,LOG_NORMAL)("DMA Transfer:%s %s %s %s freq %d rate %d size %d gold %d",
type,
sb.dma.recording ? "Recording" : "Playback",
sb.dma.stereo ? "Stereo" : "Mono",
sb.dma.autoinit ? "Auto-Init" : "Single-Cycle",
(int)freq,(int)sb.dma.rate,(int)sb.dma.total,
(int)sb.dma_dac_mode
);
#else
(void)type;
#endif
}
static uint8_t DSP_RateLimitedFinalTC_Old() {
if (sb.sample_rate_limits) { /* enforce speed limits documented by Creative */
/* commands that invoke this call use the DSP time constant, so use the DSP
* time constant to constrain rate */
unsigned int u_limit=212;/* 23KHz */
/* NTS: We skip the SB16 commands because those are handled by another function */
if ((sb.dsp.cmd&0xFE) == 0x74 || sb.dsp.cmd == 0x7D) { /* 4-bit ADPCM */
u_limit = 172; /* 12KHz */
}
else if ((sb.dsp.cmd&0xFE) == 0x76) { /* 2.6-bit ADPCM */
if (sb.type == SBT_2) u_limit = 172; /* 12KHz */
else u_limit = 179; /* 13KHz */
}
else if ((sb.dsp.cmd&0xFE) == 0x16) { /* 2-bit ADPCM */
if (sb.type == SBT_2) u_limit = 189; /* 15KHz */
else u_limit = 165; /* 11KHz */
}
else if (sb.type == SBT_16) /* Sound Blaster 16. Highspeed commands are treated like an alias to normal DSP commands */
u_limit = 234/*45454Hz*/;
else if (sb.type == SBT_2) /* Sound Blaster 2.0 */
u_limit = (sb.dsp.highspeed ? 234/*45454Hz*/ : 210/*22.5KHz*/);
else
u_limit = (sb.dsp.highspeed ? 234/*45454Hz*/ : 212/*22.5KHz*/);
/* NTS: Don't forget: Sound Blaster Pro "stereo" is programmed with a time constant divided by
* two times the sample rate, which is what we get back here. That's why here we don't need
* to consider stereo vs mono. */
if (sb.timeconst > u_limit) return u_limit;
}
return sb.timeconst;
}
static unsigned int DSP_RateLimitedFinalSB16Freq_New(unsigned int freq) {
/* If sample rate was set by DSP command 0x41/0x42 */
if (sb.sample_rate_limits) { /* enforce speed limits documented by Creative... which are somewhat wrong. They are the same limits as high-speed playback modes on SB Pro and 2.0 */
if (freq < 4900)
freq = 5000; /* Apparent behavior is that SB16 commands only go down to 5KHz but that limit is imposed if slightly below 5KHz, see rate graphs on Hackipedia */
if (freq > 45454)
freq = 45454;
}
return freq;
}
static void DSP_PrepareDMA_Old(DMA_MODES mode,bool autoinit,bool sign,bool hispeed) {
if (sb.dma.force_autoinit)
autoinit = true;
if (!autoinit) sb.dma.total=1u+(unsigned int)sb.dsp.in.data[0]+(unsigned int)(sb.dsp.in.data[1] << 8u);
sb.dma.autoinit=autoinit;
sb.dsp.highspeed=hispeed;
sb.dma.sign=sign;
/* BUGFIX: There is code out there that uses SB16 sample rate commands mixed with SB/SBPro
* playback commands. In order to handle these cases properly we need to use the
* SB16 sample rate if that is what was given to us, else the sample rate will
* come out wrong.
*
* Test cases:
*
* #1: Silpheed (vogons user newrisingsun amatorial patch)
*/
if (sb.freq_derived_from_tc) {
// sample rate was set by SB/SBpro command 0x40 Set Time Constant (very common case)
/* BUGFIX: Instead of direct rate-limiting the DSP time constant, keep the original
* value written intact and rate-limit a copy. Bugfix for Optic Nerve and
* sbtype=sbpro2. On initialization the demo first sends DSP command 0x14
* with a 2-byte playback interval, then sends command 0x91 to begin
* playback. Rate-limiting the copy means the 45454Hz time constant written
* by the demo stays intact despite being limited to 22050Hz during the first
* DSP block (command 0x14). */
uint8_t final_tc = DSP_RateLimitedFinalTC_Old();
sb.freq = (256000000ul / (65536ul - ((unsigned long)final_tc << 8ul)));
}
else {
LOG(LOG_SB,LOG_DEBUG)("Guest is using non-SB16 playback commands after using SB16 commands to set sample rate");
sb.freq = DSP_RateLimitedFinalSB16Freq_New(sb.freq);
}
sb.dma_dac_mode=0;
sb.ess_playback_mode = false;
sb.dma.chan=GetDMAChannel(sb.hw.dma8);
DSP_DoDMATransfer(mode,sb.freq / (sb.mixer.stereo ? 2 : 1),sb.mixer.stereo);
}
static void DSP_PrepareDMA_New(DMA_MODES mode,Bitu length,bool autoinit,bool stereo) {
if (sb.dma.force_autoinit)
autoinit = true;
/* apparently SB16 hardware allows 0xBx-0xCx 4.xx DSP commands to interrupt
* a previous SB16 playback command, DSP "nag" style. The difference is that
* if you do that you risk exploiting DMA and timing glitches in the chip that
* can cause funny things to happen, like causing 16-bit PCM to stop, or causing
* 8-bit stereo PCM to swap left/right channels because the host is using auto-init
* DMA and you interrupted the DSP chip when it fetched the L channel before it
* had a chance to latch it and begin loading the R channel. */
if (sb.mode == MODE_DMA) {
if (!autoinit) sb.dma.total=length;
sb.dma.left=sb.dma.total;
sb.dma.autoinit=autoinit;
return;
}
sb.dsp.highspeed = false;
sb.freq = DSP_RateLimitedFinalSB16Freq_New(sb.freq);
sb.timeconst = (65536 - (256000000 / sb.freq)) >> 8;
sb.freq_derived_from_tc = false;
Bitu freq=sb.freq;
//equal length if data format and dma channel are both 16-bit or 8-bit
sb.dma_dac_mode=0;
sb.dma.total=length;
sb.dma.autoinit=autoinit;
sb.ess_playback_mode = false;
if (mode==DSP_DMA_16) {
if (sb.hw.dma16 == 0xff || sb.hw.dma16 == sb.hw.dma8) { /* 16-bit DMA not assigned or same as 8-bit channel */
sb.dma.chan=GetDMAChannel(sb.hw.dma8);
mode=DSP_DMA_16_ALIASED;
//UNDOCUMENTED:
//In aliased mode sample length is written to DSP as number of
//16-bit samples so we need double 8-bit DMA buffer length
sb.dma.total<<=1;
}
else if (sb.hw.dma16 >= 4) { /* 16-bit DMA assigned to 16-bit DMA channel */
sb.dma.chan=GetDMAChannel(sb.hw.dma16);
}
else {
/* Nope. According to one ViBRA PnP card I have on hand, asking the
* card to do 16-bit DMA over 8-bit DMA only works if they are the
* same channel, otherwise, the card doesn't seem to carry out any
* DMA fetching. */
sb.dma.chan=NULL;
return;
}
} else {
sb.dma.chan=GetDMAChannel(sb.hw.dma8);
}
DSP_DoDMATransfer(mode,freq,stereo);
}
static void DSP_AddData(uint8_t val) {
if (sb.dsp.out.used<DSP_BUFSIZE) {
Bitu start=sb.dsp.out.used+sb.dsp.out.pos;
if (start>=DSP_BUFSIZE) start-=DSP_BUFSIZE;
sb.dsp.out.data[start]=val;
sb.dsp.out.used++;
} else {
LOG(LOG_SB,LOG_ERROR)("DSP:Data Output buffer full");
}
}
static void DSP_BusyComplete(Bitu /*val*/) {
sb.dsp.write_busy = 0;
}
static void DSP_FinishReset(Bitu /*val*/) {
DSP_FlushData();
DSP_AddData(0xaa);
sb.dsp.state=DSP_S_NORMAL;
}
static void DSP_Reset(void) {
LOG(LOG_SB,LOG_NORMAL)("DSP:Reset");
PIC_DeActivateIRQ(sb.hw.irq);
DSP_ChangeMode(MODE_NONE);
DSP_FlushData();
sb.dsp.cmd=DSP_NO_COMMAND;
sb.dsp.cmd_len=0;
sb.dsp.in.pos=0;
sb.dsp.out.pos=0;
sb.dsp.write_busy=0;
sb.ess_extended_mode = false;
sb.ess_playback_mode = false;
sb.single_sample_dma = 0;
sb.dma_dac_mode = 0;
sb.directdac_warn_speaker_off = true;
PIC_RemoveEvents(DSP_FinishReset);
PIC_RemoveEvents(DSP_BusyComplete);
sb.dma.left=0;
sb.dma.total=0;
sb.dma.stereo=false;
sb.dma.recording=false;
sb.dma.sign=false;
sb.dma.autoinit=false;
sb.dma.mode=sb.dma.mode_assigned=DSP_DMA_NONE;
sb.dma.remain_size=0;
if (sb.dma.chan) sb.dma.chan->Clear_Request();
gen_input_reset();
sb.dsp.midi_rwpoll_mode = false;
sb.dsp.midi_read_interrupt = false;
sb.dsp.midi_read_with_timestamps = false;
sb.freq=22050;
sb.freq_derived_from_tc=true;
sb.time_constant=45;
sb.dac.last=0;
sb.e2.valadd=0xaa;
sb.e2.valxor=0x96;
sb.dsp.highspeed=0;
sb.irq.pending_8bit=false;
sb.irq.pending_16bit=false;
sb.chan->SetFreq(22050);
updateSoundBlasterFilter(22050);
// DSP_SetSpeaker(false);
PIC_RemoveEvents(END_DMA_Event);
PIC_RemoveEvents(DMA_DAC_Event);
}
static void DSP_DoReset(uint8_t val) {
if (((val&1)!=0) && (sb.dsp.state!=DSP_S_RESET)) {
//TODO Get out of highspeed mode
DSP_Reset();
sb.dsp.state=DSP_S_RESET;
} else if (((val&1)==0) && (sb.dsp.state==DSP_S_RESET)) { // reset off
sb.dsp.state=DSP_S_RESET_WAIT;
PIC_RemoveEvents(DSP_FinishReset);
PIC_AddEvent(DSP_FinishReset,20.0f/1000.0f,0); // 20 microseconds
}
sb.dsp.write_busy = 0;
}
static void DSP_E2_DMA_CallBack(DmaChannel * /*chan*/, DMAEvent event) {
if (event==DMA_UNMASKED) {
uint8_t val = sb.e2.valadd;
DmaChannel * chan=GetDMAChannel(sb.hw.dma8);
chan->Register_Callback(0);
chan->Write(1,&val);
}
}
Bitu DEBUG_EnableDebugger(void);
static void DSP_SC400_E6_DMA_CallBack(DmaChannel * /*chan*/, DMAEvent event) {
if (event==DMA_UNMASKED) {
static const char *string = "\x01\x02\x04\x08\x10\x20\x40\x80"; /* Confirmed response via DMA from actual Reveal SC400 card */
DmaChannel * chan=GetDMAChannel(sb.hw.dma8);
LOG(LOG_SB,LOG_DEBUG)("SC400 returning DMA test pattern on DMA channel=%u",sb.hw.dma8);
chan->Register_Callback(0);
chan->Write(8,(uint8_t*)string);
chan->Clear_Request();
if (!chan->tcount) LOG(LOG_SB,LOG_DEBUG)("SC400 warning: DMA did not reach terminal count");
SB_RaiseIRQ(SB_IRQ_8);
}
}
static void DSP_ADC_CallBack(DmaChannel * /*chan*/, DMAEvent event) {
if (event!=DMA_UNMASKED) return;
uint8_t val=128;
DmaChannel * ch=GetDMAChannel(sb.hw.dma8);
while (sb.dma.left--) {
ch->Write(1,&val);
}
SB_RaiseIRQ(SB_IRQ_8);
ch->Register_Callback(0);
}
static void DSP_ChangeRate(Bitu freq) {
if (sb.freq!=freq && sb.dma.mode!=DSP_DMA_NONE) {
sb.chan->FillUp();
sb.chan->SetFreq(freq / (sb.mixer.stereo ? 2 : 1));
sb.dma.rate=(freq*sb.dma.mul) >> SB_SH;
sb.dma.min=(sb.dma.rate*3)/1000;
}
sb.freq=freq;
}
Bitu DEBUG_EnableDebugger(void);
#define DSP_SB16_ONLY if (sb.type != SBT_16) { LOG(LOG_SB,LOG_ERROR)("DSP:Command %2X requires SB16",sb.dsp.cmd); break; }
#define DSP_SB2_ABOVE if (sb.type <= SBT_1) { LOG(LOG_SB,LOG_ERROR)("DSP:Command %2X requires SB2 or above",sb.dsp.cmd); break; }
static unsigned int ESS_DMATransferCount() {
unsigned int r;
r = (unsigned int)ESSreg(0xA5) << 8U;
r |= (unsigned int)ESSreg(0xA4);
/* the 16-bit counter is a "two's complement" of the DMA count because it counts UP to 0 and triggers IRQ on overflow */
return 0x10000U-r;
}
static void ESS_StartDMA() {
LOG(LOG_SB,LOG_DEBUG)("ESS DMA start");
sb.dma_dac_mode = 0;
sb.dma.chan = GetDMAChannel(sb.hw.dma8);
sb.dma.recording = (ESSreg(0xB8) & 8/*ADC mode*/) > 0;
if (sb.dma.chan) sb.dma.chan->Raise_Request();
// FIXME: Which bit(s) are responsible for signalling stereo?
// Is it bit 3 of the Analog Control?
// Is it bit 3/6 of the Audio Control 1?
// Is it both?
// NTS: ESS chipsets always use the 8-bit DMA channel, even for 16-bit PCM.
// NTS: ESS chipsets also do not cap the sample rate, though if you drive them
// too fast the ISA bus will effectively cap the sample rate at some
// rate above 48KHz to 60KHz anyway.
DSP_DoDMATransfer(
(ESSreg(0xB7/*Audio Control 1*/)&4)?DSP_DMA_16_ALIASED:DSP_DMA_8,
sb.freq,(ESSreg(0xA8/*Analog control*/)&3)==1?1:0/*stereo*/,true/*don't change dma.left*/);
sb.mode = MODE_DMA;
sb.ess_playback_mode = true;
}
static void ESS_StopDMA() {
// DMA stop
DSP_ChangeMode(MODE_NONE);
if (sb.dma.chan) sb.dma.chan->Clear_Request();
PIC_RemoveEvents(END_DMA_Event);
PIC_RemoveEvents(DMA_DAC_Event);
}
static void ESS_UpdateDMATotal() {
sb.dma.total = ESS_DMATransferCount();
}
static void ESS_CheckDMAEnable() {
bool dma_en = (ESSreg(0xB8) & 1)?true:false;
// if the DRQ is disabled, do not start
if (!(ESSreg(0xB2) & 0x40))
dma_en = false;
if (ESSreg(0xB8) & 8) LOG(LOG_SB,LOG_WARN)("Guest recording audio using ESS commands");
if (!!(ESSreg(0xB8) & 8/*ADC mode*/) != !!(ESSreg(0xB8) & 2/*DMA read*/)) LOG(LOG_SB,LOG_WARN)("ESS DMA direction vs ADC mismatch");
if (dma_en) {
if (sb.mode != MODE_DMA) ESS_StartDMA();
}
else {
if (sb.mode == MODE_DMA) ESS_StopDMA();
}
}
static void ESSUpdateFilterFromSB(void) {
if (sb.freq >= 22050)
ESSreg(0xA1) = 256 - (795500UL / sb.freq);
else
ESSreg(0xA1) = 128 - (397700UL / sb.freq);
unsigned int freq = ((sb.freq * 4) / (5 * 2)); /* 80% of 1/2 the sample rate */
ESSreg(0xA2) = 256 - (7160000 / (freq * 82));
}
static void ESS_DoWrite(uint8_t reg,uint8_t data) {
uint8_t chg;
LOG(LOG_SB,LOG_DEBUG)("ESS register write reg=%02xh val=%02xh",reg,data);
switch (reg) {
case 0xA1: /* Extended Mode Sample Rate Generator */
ESSreg(reg) = data;
if (data & 0x80)
sb.freq = 795500UL / (256ul - data);
else
sb.freq = 397700UL / (128ul - data);
sb.freq_derived_from_tc = false;
if (sb.mode == MODE_DMA) {
ESS_StopDMA();
ESS_StartDMA();
}
break;
case 0xA2: /* Filter divider (effectively, a hardware lowpass filter under S/W control) */
ESSreg(reg) = data;
updateSoundBlasterFilter(sb.freq);
break;
case 0xA4: /* DMA Transfer Count Reload (low) */
case 0xA5: /* DMA Transfer Count Reload (high) */
ESSreg(reg) = data;
ESS_UpdateDMATotal();
if (sb.dma.left == 0) sb.dma.left = sb.dma.total;
break;
case 0xA8: /* Analog Control */
/* bits 7:5 0 Reserved. Always write 0
* bit 4 1 Reserved. Always write 1
* bit 3 Record monitor 1=Enable record monitor
* enable
* bit 2 0 Reserved. Always write 0
* bits 1:0 Stereo/mono select 00=Reserved
* 01=Stereo
* 10=Mono
* 11=Reserved */
chg = ESSreg(reg) ^ data;
ESSreg(reg) = data;
if (chg & 0x3) {
if (sb.mode == MODE_DMA) {
ESS_StopDMA();
ESS_StartDMA();
}
}
break;
case 0xB1: /* Legacy Audio Interrupt Control */
case 0xB2: /* DRQ Control */
chg = ESSreg(reg) ^ data;
ESSreg(reg) = (ESSreg(reg) & 0x0F) + (data & 0xF0); // lower 4 bits not writeable
if (chg & 0x40) ESS_CheckDMAEnable();
break;
case 0xB5: /* DAC Direct Access Holding (low) */
case 0xB6: /* DAC Direct Access Holding (high) */
ESSreg(reg) = data;
break;
case 0xB7: /* Audio 1 Control 1 */
/* bit 7 Enable FIFO to/from codec
* bit 6 Opposite from bit 3 Must be set opposite to bit 3
* bit 5 FIFO signed mode 1=Data is signed twos-complement 0=Data is unsigned
* bit 4 Reserved Always write 1
* bit 3 FIFO stereo mode 1=Data is stereo
* bit 2 FIFO 16-bit mode 1=Data is 16-bit
* bit 1 Reserved Always write 0
* bit 0 Generate load signal */
chg = ESSreg(reg) ^ data;
ESSreg(reg) = data;
sb.dma.sign = (data&0x20)?1:0;
if (chg & 0x04) ESS_UpdateDMATotal();
if (chg & 0x0C) {
if (sb.mode == MODE_DMA) {
ESS_StopDMA();
ESS_StartDMA();
}
}
break;
case 0xB8: /* Audio 1 Control 2 */
/* bits 7:4 reserved
* bit 3 CODEC mode 1=first DMA converter in ADC mode
* 0=first DMA converter in DAC mode
* bit 2 DMA mode 1=auto-initialize mode
* 0=normal DMA mode
* bit 1 DMA read enable 1=first DMA is read (for ADC)
* 0=first DMA is write (for DAC)
* bit 0 DMA xfer enable 1=DMA is allowed to proceed */
data &= 0xF;
chg = ESSreg(reg) ^ data;
ESSreg(reg) = data;
/* FIXME: This is a guess */
if (chg & 1) sb.dma.left = sb.dma.total;
sb.dma.autoinit = (data >> 2) & 1;
if (chg & 0xB) {
if (chg & 0xA) ESS_StopDMA(); /* changing capture/playback direction? stop DMA to reinit */
ESS_CheckDMAEnable();
}
break;
case 0xB9: /* Audio 1 Transfer Type */
case 0xBA: /* Left Channel ADC Offset Adjust */
case 0xBB: /* Right Channel ADC Offset Adjust */
ESSreg(reg) = data;
break;
}
}
static uint8_t ESS_DoRead(uint8_t reg) {
LOG(LOG_SB,LOG_DEBUG)("ESS register read reg=%02xh",reg);
switch (reg) {
default:
return ESSreg(reg);
}
return 0xFF;
}
int MPU401_GetIRQ();
/* SB16 cards have a 256-byte block of 8051 internal RAM accessible through DSP commands 0xF9 (Read) and 0xFA (Write) */
static unsigned char sb16_8051_mem[256];
/* The SB16 ASP appears to have a mystery 2KB RAM block that is accessible through register 0x83 of the ASP.
* This array represents the initial contents as seen on my SB16 non-PnP ASP chip (version ID 0x10). */
static unsigned int sb16asp_ram_contents_index = 0;
static unsigned char sb16asp_ram_contents[2048];
static void sb16asp_write_current_RAM_byte(const uint8_t r) {
sb16asp_ram_contents[sb16asp_ram_contents_index] = r;
}
static uint8_t sb16asp_read_current_RAM_byte(void) {
return sb16asp_ram_contents[sb16asp_ram_contents_index];
}
static void sb16asp_next_RAM_byte(void) {
if ((++sb16asp_ram_contents_index) >= 2048)
sb16asp_ram_contents_index = 0;
}
/* Demo notes for fixing:
*
* - "Buttman"'s intro uses a timer and DSP command 0x10 to play the sound effects even in Pro mode.
* It doesn't use DMA + IRQ until the music starts.
*/
static void DSP_DoCommand(void) {
if (sb.ess_type != ESS_NONE && sb.dsp.cmd >= 0xA0 && sb.dsp.cmd <= 0xCF) {
// ESS overlap with SB16 commands. Handle it here, not mucking up the switch statement.
if (sb.dsp.cmd < 0xC0) { // write ESS register (cmd=register data[0]=value to write)
if (sb.ess_extended_mode)
ESS_DoWrite(sb.dsp.cmd,sb.dsp.in.data[0]);
}
else if (sb.dsp.cmd == 0xC0) { // read ESS register (data[0]=register to read)
DSP_FlushData();
if (sb.ess_extended_mode && sb.dsp.in.data[0] >= 0xA0 && sb.dsp.in.data[0] <= 0xBF)
DSP_AddData(ESS_DoRead(sb.dsp.in.data[0]));
}
else if (sb.dsp.cmd == 0xC6 || sb.dsp.cmd == 0xC7) { // set(0xC6) clear(0xC7) extended mode
sb.ess_extended_mode = (sb.dsp.cmd == 0xC6);
}
else {
LOG(LOG_SB,LOG_DEBUG)("ESS: Unknown command %02xh",sb.dsp.cmd);
}
sb.dsp.last_cmd=sb.dsp.cmd;
sb.dsp.cmd=DSP_NO_COMMAND;
sb.dsp.cmd_len=0;
sb.dsp.in.pos=0;
return;
}
if (sb.type == SBT_16) {
// FIXME: This is a guess! See also [https://github.com/joncampbell123/dosbox-x/issues/1044#issuecomment-480024957]
sb16_8051_mem[0x20] = sb.dsp.last_cmd; /* cur_cmd */
}
// TODO: There are more SD16 ASP commands we can implement, by name even, with microcode download,
// using as reference the Linux kernel driver code:
//
// http://lxr.free-electrons.com/source/sound/isa/sb/sb16_csp.c
// LOG_MSG("DSP Command %X",sb.dsp.cmd);
switch (sb.dsp.cmd) {
case 0x04:
if (sb.type == SBT_16) {
/* SB16 ASP set mode register */
ASP_mode = sb.dsp.in.data[0];
// bit 7: if set, enables bit 3 and memory access.
// bit 3: if set, and bit 7 is set, register 0x83 can be used to read/write ASP internal memory. if clear, register 0x83 contains chip version ID
// bit 2: if set, memory index is reset to 0. doesn't matter if memory access or not.
// bit 1: if set, writing register 0x83 increments memory index. doesn't matter if memory access or not.
// bit 0: if set, reading register 0x83 increments memory index. doesn't matter if memory access or not.
if (ASP_mode&4)
sb16asp_ram_contents_index = 0;
LOG(LOG_SB,LOG_DEBUG)("SB16ASP set mode register to %X",sb.dsp.in.data[0]);
} else {
/* DSP Status SB 2.0/pro version. NOT SB16. */
DSP_FlushData();
if (sb.type == SBT_2) DSP_AddData(0x88);
else if ((sb.type == SBT_PRO1) || (sb.type == SBT_PRO2)) DSP_AddData(0x7b);
else DSP_AddData(0xff); //Everything enabled
}
break;
case 0x05: /* SB16 ASP set codec parameter */
LOG(LOG_SB,LOG_NORMAL)("DSP Unhandled SB16ASP command %X (set codec parameter) value=0x%02x parameter=0x%02x",
sb.dsp.cmd,sb.dsp.in.data[0],sb.dsp.in.data[1]);
break;
case 0x08: /* SB16 ASP get version */
if (sb.type == SBT_16) {
switch (sb.dsp.in.data[0]) {
case 0x03:
LOG(LOG_SB,LOG_DEBUG)("DSP SB16ASP command %X sub %X (get chip version)",sb.dsp.cmd,sb.dsp.in.data[0]);
if (sb.enable_asp)
DSP_AddData(0x10); // version ID
else
DSP_AddData(0xFF); // NTS: This is what a SB16 ViBRA PnP card with no ASP returns when queried in this way
break;
default:
LOG(LOG_SB,LOG_NORMAL)("DSP Unhandled SB16ASP command %X sub %X",sb.dsp.cmd,sb.dsp.in.data[0]);
break;
}
} else {
LOG(LOG_SB,LOG_NORMAL)("DSP Unhandled SB16ASP command %X sub %X",sb.dsp.cmd,sb.dsp.in.data[0]);
}
break;
case 0x0e: /* SB16 ASP set register */
if (sb.type == SBT_16) {
if (sb.enable_asp) {
ASP_regs[sb.dsp.in.data[0]] = sb.dsp.in.data[1];
if (sb.dsp.in.data[0] == 0x83) {
if ((ASP_mode&0x88) == 0x88) { // bit 3 and bit 7 must be set
// memory access mode
if (ASP_mode & 4) // NTS: As far as I can tell...
sb16asp_ram_contents_index = 0;
// log it, write it
LOG(LOG_SB,LOG_DEBUG)("SB16 ASP write internal RAM byte index=0x%03x val=0x%02x",sb16asp_ram_contents_index,sb.dsp.in.data[1]);
sb16asp_write_current_RAM_byte(sb.dsp.in.data[1]);
if (ASP_mode & 2) // if bit 1 of the mode is set, memory index increment on write
sb16asp_next_RAM_byte();
}
else {
// unknown. nothing, I assume?
LOG(LOG_SB,LOG_WARN)("SB16 ASP set register 0x83 not implemented for non-memory mode (unknown behavior)\n");
}
}
else {
LOG(LOG_SB,LOG_DEBUG)("SB16 ASP set register reg=0x%02x val=0x%02x",sb.dsp.in.data[0],sb.dsp.in.data[1]);
}
}
else {
LOG(LOG_SB,LOG_DEBUG)("SB16 ASP set register reg=0x%02x val=0x%02x ignored, ASP not enabled",sb.dsp.in.data[0],sb.dsp.in.data[1]);
}
} else {
LOG(LOG_SB,LOG_NORMAL)("DSP Unhandled SB16ASP command %X (set register)",sb.dsp.cmd);
}
break;
case 0x0f: /* SB16 ASP get register */
if (sb.type == SBT_16) {
// FIXME: We have to emulate this whether or not ASP emulation is enabled. Windows 98 SB16 driver requires this.
// The question is: What does actual hardware do here exactly?
if (sb.enable_asp && sb.dsp.in.data[0] == 0x83) {
if ((ASP_mode&0x88) == 0x88) { // bit 3 and bit 7 must be set
// memory access mode
if (ASP_mode & 4) // NTS: As far as I can tell...
sb16asp_ram_contents_index = 0;
// log it, read it
ASP_regs[0x83] = sb16asp_read_current_RAM_byte();
LOG(LOG_SB,LOG_DEBUG)("SB16 ASP read internal RAM byte index=0x%03x => val=0x%02x",sb16asp_ram_contents_index,ASP_regs[0x83]);
if (ASP_mode & 1) // if bit 0 of the mode is set, memory index increment on read
sb16asp_next_RAM_byte();
}
else {
// chip version ID
ASP_regs[0x83] = 0x10;
}
}
else {
LOG(LOG_SB,LOG_DEBUG)("SB16 ASP get register reg=0x%02x, returning 0x%02x",sb.dsp.in.data[0],ASP_regs[sb.dsp.in.data[0]]);
}
DSP_AddData(ASP_regs[sb.dsp.in.data[0]]);
} else {
LOG(LOG_SB,LOG_NORMAL)("DSP Unhandled SB16ASP command %X (get register)",sb.dsp.cmd);
}
break;
case 0x10: /* Direct DAC */
DSP_ChangeMode(MODE_DAC);
/* just in case something is trying to play direct DAC audio while the speaker is turned off... */
if (!sb.speaker && sb.directdac_warn_speaker_off) {
LOG(LOG_SB,LOG_DEBUG)("DSP direct DAC sample written while speaker turned off. Program should use DSP command 0xD1 to turn it on.");
sb.directdac_warn_speaker_off = false;
}
sb.freq = 22050;
sb.freq_derived_from_tc = true;
sb.dac.dac_pt = sb.dac.dac_t;
sb.dac.dac_t = PIC_FullIndex();
{
double dt = sb.dac.dac_t - sb.dac.dac_pt; // time in milliseconds since last direct DAC output
double rt = 1000 / dt; // estimated sample rate according to dt
int s,sc = 1;
// cap rate estimate to sanity. <= 1KHz means rendering once per timer tick in DOSBox,
// so there's no point below that rate in additional rendering.
if (rt < 1000) rt = 1000;
// FIXME: What does the ESS AudioDrive do to it's filter/sample rate divider registers when emulating this Sound Blaster command?
ESSreg(0xA1) = 128 - (397700 / 22050);
ESSreg(0xA2) = 256 - (7160000 / (82 * ((4 * 22050) / 10)));
// Direct DAC playback could be thought of as application-driven 8-bit output up to 23KHz.
// The sound card isn't given any hint what the actual sample rate is, only that it's given
// instruction when to change the 8-bit value being output to the DAC which is why older DOS
// games using this method tend to sound "grungy" compared to DMA playback. We recreate the
// effect here by asking the mixer to do it's linear interpolation as if at 23KHz while
// rendering the audio at whatever rate the DOS game is giving it to us.
sb.chan->SetFreq((Bitu)(rt * 0x100),0x100);
updateSoundBlasterFilter(sb.freq);
// avoid popping/crackling artifacts through the mixer by ensuring the render output is prefilled enough
if (sb.chan->msbuffer_o < 40/*FIXME: ask the mixer code!*/) sc += 2/*FIXME: use mixer rate / rate math*/;
// do it
for (s=0;s < sc;s++) sb.chan->AddSamples_m8(1,(uint8_t*)(&sb.dsp.in.data[0]));
}
break;
case 0x99: /* Single Cycle 8-Bit DMA High speed DAC */
DSP_SB2_ABOVE;
/* fall through */
case 0x24: /* Single Cycle 8-Bit DMA ADC */
sb.dma.recording=true;
DSP_PrepareDMA_Old(DSP_DMA_8,false,false,/*hispeed*/(sb.dsp.cmd&0x80)!=0);
LOG(LOG_SB,LOG_WARN)("Guest recording audio using SB/SBPro commands");
break;
case 0x98: /* Auto Init 8-bit DMA High Speed */
case 0x2c: /* Auto Init 8-bit DMA */
DSP_SB2_ABOVE; /* Note: 0x98 is documented only for DSP ver.2.x and 3.x, not 4.x */
sb.dma.recording=true;
DSP_PrepareDMA_Old(DSP_DMA_8,true,false,/*hispeed*/(sb.dsp.cmd&0x80)!=0);
break;
case 0x91: /* Single Cycle 8-Bit DMA High speed DAC */
DSP_SB2_ABOVE;
/* fall through */
case 0x14: /* Single Cycle 8-Bit DMA DAC */
case 0x15: /* Wari hack. Waru uses this one instead of 0x14, but some weird stuff going on there anyway */
/* Note: 0x91 is documented only for DSP ver.2.x and 3.x, not 4.x */
sb.dma.recording=false;
DSP_PrepareDMA_Old(DSP_DMA_8,false,false,/*hispeed*/(sb.dsp.cmd&0x80)!=0);
break;
case 0x90: /* Auto Init 8-bit DMA High Speed */
case 0x1c: /* Auto Init 8-bit DMA */
DSP_SB2_ABOVE; /* Note: 0x90 is documented only for DSP ver.2.x and 3.x, not 4.x */
sb.dma.recording=false;
DSP_PrepareDMA_Old(DSP_DMA_8,true,false,/*hispeed*/(sb.dsp.cmd&0x80)!=0);
break;
case 0x38: /* Write to SB MIDI Output */
if (sb.midi == true) MIDI_RawOutByte(sb.dsp.in.data[0]);
break;
case 0x40: /* Set Timeconstant */
DSP_ChangeRate(256000000ul / (65536ul - ((unsigned int)sb.dsp.in.data[0] << 8u)));
sb.timeconst=sb.dsp.in.data[0];
sb.freq_derived_from_tc=true;
if (sb.ess_type != ESS_NONE) ESSUpdateFilterFromSB();
break;
case 0x41: /* Set Output Samplerate */
case 0x42: /* Set Input Samplerate */
if (sb.reveal_sc_type == RSC_SC400) {
/* Despite reporting itself as Sound Blaster Pro compatible, the Reveal SC400 supports some SB16 commands like this one */
}
else {
DSP_SB16_ONLY;
}
DSP_ChangeRate(((unsigned int)sb.dsp.in.data[0] << 8u) | (unsigned int)sb.dsp.in.data[1]);
sb.freq_derived_from_tc=false;
sb16_8051_mem[0x13] = sb.freq & 0xffu; // rate low
sb16_8051_mem[0x14] = (sb.freq >> 8u) & 0xffu; // rate high
break;
case 0x48: /* Set DMA Block Size */
DSP_SB2_ABOVE;
//TODO Maybe check limit for new irq?
sb.dma.total=1u+(unsigned int)sb.dsp.in.data[0]+((unsigned int)sb.dsp.in.data[1] << 8u);
// NTS: From Creative documentation: This is the number of BYTES to transfer per IRQ, not SAMPLES!
// sb.dma.total is in SAMPLES (unless 16-bit PCM over 8-bit DMA) because this code inherits that
// design from DOSBox SVN. This check is needed for any DOS game or application that changes
// DSP block size during the game (such as when transitioning from general gameplay to spoken
// dialogue), and it is needed to stop Freddy Pharkas from stuttering when sbtype=sb16 ref
// [https://github.com/joncampbell123/dosbox-x/issues/2960]
// NTS: Do NOT divide the byte count by 2 if 16-bit PCM audio but using an 8-bit DMA channel (DSP_DMA_16_ALIASED).
// sb.dma.total in that cause really does contain the byte count of a DSP block. 16-bit PCM over 8-bit DMA
// is possible on real hardware too, likely as a fallback in case 16-bit DMA channels are just not available.
// Note that on one of my ViBRA PnP cards, 8-bit DMA is the only option because 16-bit DMA doesn't work for
// some odd reason. --J.C.
if (sb.dma.mode == DSP_DMA_16) {
// NTS: sb.dma.total is the number of individual samples, not paired samples, likely as a side effect of how
// this code was originally written over at DOSBox SVN regarding how block durations are handled with
// the Sound Blaster Pro in which the Pro treats stereo output as just mono that is alternately latched
// to left and right DACs. The SB16 handling here also follows that tradition because Creative's SB16
// playback commands 0xB0-0xCF follow the same tradition: Block size specified in the command is given
// in samples, and by samples, they mean individual samples, and therefore it is still doubled when
// asked to play stereo audio. I suppose this is why Linux ALSA chose to further clarify the terminology
// by defining audio "samples" vs audio "frames".
// NTS: The sb.dma.total as individual sample count has been confirmed with DOSLIB and real hardware, and by
// looking at snd_sb16_capture_prepare() in sound/isa/sb/sb16_main.c in the Linux kernel source.
if (sb.dma.total & 1) LOG(LOG_SB,LOG_WARN)("DSP command 0x48: 16-bit PCM and odd number of bytes given for block length");
sb.dma.total >>= 1u;
}
break;
case 0x75: /* 075h : Single Cycle 4-bit ADPCM Reference */
sb.adpcm.haveref=true;
/* FALLTHROUGH */
case 0x74: /* 074h : Single Cycle 4-bit ADPCM */
sb.dma.recording=false;
DSP_PrepareDMA_Old(DSP_DMA_4,false,false,false);
break;
case 0x77: /* 077h : Single Cycle 3-bit(2.6bit) ADPCM Reference*/
sb.adpcm.haveref=true;
/* FALLTHROUGH */
case 0x76: /* 074h : Single Cycle 3-bit(2.6bit) ADPCM */
sb.dma.recording=false;
DSP_PrepareDMA_Old(DSP_DMA_3,false,false,false);
break;
case 0x7d: /* Auto Init 4-bit ADPCM Reference */
DSP_SB2_ABOVE;
sb.adpcm.haveref=true;
sb.dma.recording=false;
DSP_PrepareDMA_Old(DSP_DMA_4,true,false,false);
break;
case 0x7f: /* Auto Init 3-bit(2.6bit) ADPCM Reference */
DSP_SB2_ABOVE;
sb.adpcm.haveref=true;
sb.dma.recording=false;
DSP_PrepareDMA_Old(DSP_DMA_3,true,false,false);
break;
case 0x1f: /* Auto Init 2-bit ADPCM Reference */
DSP_SB2_ABOVE;
sb.adpcm.haveref=true;
sb.dma.recording=false;
DSP_PrepareDMA_Old(DSP_DMA_2,true,false,false);
break;
case 0x17: /* 017h : Single Cycle 2-bit ADPCM Reference*/
sb.adpcm.haveref=true;
/* FALLTHROUGH */
case 0x16: /* 074h : Single Cycle 2-bit ADPCM */
sb.dma.recording=false;
DSP_PrepareDMA_Old(DSP_DMA_2,false,false,false);
break;
case 0x80: /* Silence DAC */
PIC_AddEvent(&DSP_RaiseIRQEvent,
(1000.0f*(1+sb.dsp.in.data[0]+(sb.dsp.in.data[1] << 8))/sb.freq));
break;
case 0xb0: case 0xb1: case 0xb2: case 0xb3: case 0xb4: case 0xb5: case 0xb6: case 0xb7:
case 0xb8: case 0xb9: case 0xba: case 0xbb: case 0xbc: case 0xbd: case 0xbe: case 0xbf:
case 0xc0: case 0xc1: case 0xc2: case 0xc3: case 0xc4: case 0xc5: case 0xc6: case 0xc7:
case 0xc8: case 0xc9: case 0xca: case 0xcb: case 0xcc: case 0xcd: case 0xce: case 0xcf:
/* The low 5 bits DO have specific meanings:
Bx - Program 16-bit DMA mode digitized sound I/O
Command sequence: Command, Mode, Lo(Length-1), Hi(Length-1)
Command:
╔════╤════╤════╤════╤═══════╤══════╤═══════╤════╗
║ D7 │ D6 │ D5 │ D4 │ D3 │ D2 │ D1 │ D0 ║
╠════╪════╪════╪════╪═══════╪══════╪═══════╪════╣
║ 1 │ 0 │ 1 │ 1 │ A/D │ A/I │ FIFO │ 0 ║
╚════╧════╧════╧════┼───────┼──────┼───────┼════╝
│ 0=D/A │ 0=SC │ 0=off │
│ 1=A/D │ 1=AI │ 1=on │
└───────┴──────┴───────┘
Common commands:
B8 - 16-bit single-cycle input
B0 - 16-bit single-cycle output
BE - 16-bit auto-initialized input
B6 - 16-bit auto-initialized output
Mode:
╔════╤════╤══════════╤════════════╤════╤════╤════╤════╗
║ D7 │ D6 │ D5 │ D4 │ D3 │ D2 │ D1 │ D0 ║
╠════╪════╪══════════╪════════════╪════╪════╪════╪════╣
║ 0 │ 0 │ Stereo │ Signed │ 0 │ 0 │ 0 │ 0 ║
╚════╧════┼──────────┼────────────┼════╧════╧════╧════╝
│ 0=Mono │ 0=unsigned │
│ 1=Stereo │ 1=signed │
└──────────┴────────────┘
Cx - Program 8-bit DMA mode digitized sound I/O
Same procedure as 16-bit sound I/O using command Bx
Common commands:
C8 - 8-bit single-cycle input
C0 - 8-bit single-cycle output
CE - 8-bit auto-initialized input
C6 - 8-bit auto-initialized output
Note that this code makes NO attempt to distinguish recording vs playback commands, which
is responsible for some failures such as [https://github.com/joncampbell123/dosbox-x/issues/1589]
*/
if (sb.reveal_sc_type == RSC_SC400) {
/* Despite reporting itself as Sound Blaster Pro, the Reveal SC400 cards do support *some* SB16 DSP commands! */
/* BUT, it only recognizes a subset of this range. */
if (sb.dsp.cmd == 0xBE || sb.dsp.cmd == 0xB6 ||
sb.dsp.cmd == 0xCE || sb.dsp.cmd == 0xC6) {
/* OK! */
}
else {
LOG(LOG_SB,LOG_DEBUG)("SC400: SB16 playback command not recognized");
break;
}
}
else {
DSP_SB16_ONLY;
}
if (sb.dsp.cmd & 8) LOG(LOG_SB,LOG_WARN)("Guest recording audio using SB16 commands");
/* Generic 8/16 bit DMA */
// DSP_SetSpeaker(true); //SB16 always has speaker enabled
sb.dma.sign=(sb.dsp.in.data[0] & 0x10) > 0;
sb.dma.recording=(sb.dsp.cmd & 0x8) > 0;
DSP_PrepareDMA_New((sb.dsp.cmd & 0x10) ? DSP_DMA_16 : DSP_DMA_8,
1u+(unsigned int)sb.dsp.in.data[1]+((unsigned int)sb.dsp.in.data[2] << 8u),
(sb.dsp.cmd & 0x4)>0,
(sb.dsp.in.data[0] & 0x20) > 0
);
break;
case 0xd5: /* Halt 16-bit DMA */
DSP_SB16_ONLY;
case 0xd0: /* Halt 8-bit DMA */
sb.chan->FillUp();
// DSP_ChangeMode(MODE_NONE);
// Games sometimes already program a new dma before stopping, gives noise
if (sb.mode==MODE_NONE) {
// possibly different code here that does not switch to MODE_DMA_PAUSE
}
sb.mode=MODE_DMA_PAUSE;
PIC_RemoveEvents(END_DMA_Event);
PIC_RemoveEvents(DMA_DAC_Event);
break;
case 0xd1: /* Enable Speaker */
sb.chan->FillUp();
DSP_SetSpeaker(true);
break;
case 0xd3: /* Disable Speaker */
sb.chan->FillUp();
DSP_SetSpeaker(false);
/* There are demoscene productions that reinitialize sound between parts.
* But instead of stopping playback, then starting it again, the demo leaves
* DMA running through RAM and expects the "DSP Disable Speaker" command to
* prevent the arbitrary contents of RAM from coming out the sound card as static
* while it loads data. The problem is, DSP enable/disable speaker commands don't
* do anything on Sound Blaster 16 cards. This is why such demos run fine when
* sbtype=sbpro2, but emit static/noise between demo parts when sbtype=sb16.
* The purpose of this warning is to clue the user on in this fact and suggest
* a fix.
*
* Demoscene productions known to have this bug/problem with sb16:
* - "Saga" by Dust (1993) noise/static between demo parts
* - "Facts of life" by Witan (1992) noise/static during star wars scroller at the beginning
*/
if (sb.type == SBT_16 && sb.mode == MODE_DMA)
LOG(LOG_MISC,LOG_WARN)("SB16 warning: DSP Disable Speaker command used while DMA is running, which has no effect on audio output on SB16 hardware. Audible noise/static may occur. You can eliminate the noise by setting sbtype=sbpro2");
break;
case 0xd8: /* Speaker status */
DSP_SB2_ABOVE;
DSP_FlushData();
if (sb.speaker) DSP_AddData(0xff);
else DSP_AddData(0x00);
break;
case 0xd6: /* Continue DMA 16-bit */
DSP_SB16_ONLY;
case 0xd4: /* Continue DMA 8-bit*/
sb.chan->FillUp();
if (sb.mode==MODE_DMA_PAUSE) {
sb.mode=MODE_DMA_MASKED;
if (sb.dma.chan!=NULL) sb.dma.chan->Register_Callback(DSP_DMA_CallBack);
}
break;
case 0x47: /* Continue Autoinitialize 16-bit */
case 0x45: /* Continue Autoinitialize 8-bit */
DSP_SB16_ONLY;
sb.chan->FillUp();
sb.dma.autoinit=true; // No. This DSP command does not resume DMA playback
break;
case 0xd9: /* Exit Autoinitialize 16-bit */
DSP_SB16_ONLY;
case 0xda: /* Exit Autoinitialize 8-bit */
DSP_SB2_ABOVE;
/* Set mode to single transfer so it ends with current block */
sb.dma.autoinit=false; //Should stop itself
sb.chan->FillUp();
break;
case 0xe0: /* DSP Identification - SB2.0+ */
DSP_FlushData();
DSP_AddData(~sb.dsp.in.data[0]);
break;
case 0xe1: /* Get DSP Version */
DSP_FlushData();
switch (sb.type) {
case SBT_1:
DSP_AddData(0x1);DSP_AddData(0x05);break;
case SBT_2:
DSP_AddData(0x2);DSP_AddData(0x1);break;
case SBT_PRO1:
DSP_AddData(0x3);DSP_AddData(0x0);break;
case SBT_PRO2:
if (sb.ess_type != ESS_NONE) {
DSP_AddData(0x3);DSP_AddData(0x1);
}
else if (sb.reveal_sc_type == RSC_SC400) { // SC400 cards report as v3.5 by default, but there is a DSP command to change the version!
DSP_AddData(sb.sc400_dsp_major);DSP_AddData(sb.sc400_dsp_minor);
}
else {
DSP_AddData(0x3);DSP_AddData(0x2);
}
break;
case SBT_16:
if (sb.vibra) {
DSP_AddData(4); /* SB16 ViBRA DSP 4.13 */
DSP_AddData(13);
}
else {
DSP_AddData(4); /* SB16 DSP 4.05 */
DSP_AddData(5);
}
break;
default:
break;
}
break;
case 0xe2: /* Weird DMA identification write routine */
{
LOG(LOG_SB,LOG_NORMAL)("DSP Function 0xe2");
sb.e2.valadd += sb.dsp.in.data[0] ^ sb.e2.valxor;
sb.e2.valxor = (sb.e2.valxor >> 2u) | (sb.e2.valxor << 6u);
GetDMAChannel(sb.hw.dma8)->Register_Callback(DSP_E2_DMA_CallBack);
}
break;
case 0xe3: /* DSP Copyright */
{
DSP_FlushData();
if (sb.ess_type != ESS_NONE) {
/* ESS chips do not return copyright string */
DSP_AddData(0);
}
else if (sb.reveal_sc_type == RSC_SC400) {
static const char *gallant = "SC-6000";
/* NTS: Yes, this writes the terminating NUL as well. Not a bug. */
for (size_t i=0;i<=strlen(gallant);i++) {
DSP_AddData((uint8_t)gallant[i]);
}
}
else if (sb.type <= SBT_PRO2) {
/* Sound Blaster DSP 2.0: No copyright string observed. */
/* Sound Blaster Pro DSP 3.1: No copyright string observed. */
/* I have yet to observe if a Sound Blaster Pro DSP 3.2 (SBT_PRO2) returns a copyright string. */
/* no response */
}
else {
/* NTS: Yes, this writes the terminating NUL as well. Not a bug. */
for (size_t i=0;i<=strlen(copyright_string);i++) {
DSP_AddData((uint8_t)copyright_string[i]);
}
}
}
break;
case 0xe4: /* Write Test Register */
sb.dsp.test_register=sb.dsp.in.data[0];
break;
case 0xe7: /* ESS detect/read config */
if (sb.ess_type != ESS_NONE) {
DSP_FlushData();
DSP_AddData(0x68);
DSP_AddData(0x80 | 0x04/*ESS 688 version*/);
}
break;
case 0xe8: /* Read Test Register */
DSP_FlushData();
DSP_AddData(sb.dsp.test_register);
break;
case 0xf2: /* Trigger 8bit IRQ */
//Small delay in order to emulate the slowness of the DSP, fixes Llamatron 2012 and Lemmings 3D
PIC_AddEvent(&DSP_RaiseIRQEvent,0.01f);
break;
case 0xa0: case 0xa8: /* Documented only for DSP 3.x */
if (sb.type == SBT_PRO1 || sb.type == SBT_PRO2)
sb.mixer.stereo = (sb.dsp.cmd & 8) > 0; /* <- HACK! 0xA0 input mode to mono 0xA8 stereo */
else
LOG(LOG_SB,LOG_WARN)("DSP command A0h/A8h are only supported in Sound Blaster Pro emulation mode");
break;
case 0xf3: /* Trigger 16bit IRQ */
DSP_SB16_ONLY;
SB_RaiseIRQ(SB_IRQ_16);
break;
case 0xf8: /* Undocumented, pre-SB16 only */
DSP_FlushData();
DSP_AddData(0);
break;
case 0x30: case 0x31: case 0x32: case 0x33:
LOG(LOG_SB,LOG_ERROR)("DSP:Unimplemented MIDI I/O command %2X",sb.dsp.cmd);
break;
case 0x34: /* MIDI Read Poll & Write Poll */
DSP_SB2_ABOVE;
LOG(LOG_SB,LOG_DEBUG)("DSP:Entering MIDI Read/Write Poll mode");
sb.dsp.midi_rwpoll_mode = true;
break;
case 0x35: /* MIDI Read Interrupt & Write Poll */
DSP_SB2_ABOVE;
LOG(LOG_SB,LOG_DEBUG)("DSP:Entering MIDI Read Interrupt/Write Poll mode");
sb.dsp.midi_rwpoll_mode = true;
sb.dsp.midi_read_interrupt = true;
break;
case 0x37: /* MIDI Read Timestamp Interrupt & Write Poll */
DSP_SB2_ABOVE;
LOG(LOG_SB,LOG_DEBUG)("DSP:Entering MIDI Read Timstamp Interrupt/Write Poll mode");
sb.dsp.midi_rwpoll_mode = true;
sb.dsp.midi_read_interrupt = true;
sb.dsp.midi_read_with_timestamps = true;
break;
case 0x20:
DSP_AddData(0x7f); // fake silent input for Creative parrot
break;
case 0x88: /* Reveal SC400 ??? (used by TESTSC.EXE) */
if (sb.reveal_sc_type != RSC_SC400) break;
/* ??? */
break;
case 0xE6: /* Reveal SC400 DMA test */
if (sb.reveal_sc_type != RSC_SC400) break;
GetDMAChannel(sb.hw.dma8)->Register_Callback(DSP_SC400_E6_DMA_CallBack);
sb.dsp.out.lastval = 0x80;
sb.dsp.out.used = 0;
break;
case 0x50: /* Reveal SC400 write configuration */
if (sb.reveal_sc_type != RSC_SC400) break;
sb.sc400_cfg = sb.dsp.in.data[0];
switch (sb.dsp.in.data[0]&3) {
case 0: sb.hw.dma8 = (uint8_t)(-1); break;
case 1: sb.hw.dma8 = 0u; break;
case 2: sb.hw.dma8 = 1u; break;
case 3: sb.hw.dma8 = 3u; break;
}
sb.hw.dma16 = sb.hw.dma8;
switch ((sb.dsp.in.data[0]>>3)&7) {
case 0: sb.hw.irq = (uint8_t)(-1); break;
case 1: sb.hw.irq = 7u; break;
case 2: sb.hw.irq = 9u; break;
case 3: sb.hw.irq = 10u; break;
case 4: sb.hw.irq = 11u; break;
case 5: sb.hw.irq = 5u; break;
case 6: sb.hw.irq = (uint8_t)(-1); break;
case 7: sb.hw.irq = (uint8_t)(-1); break;
}
{
int irq;
if (sb.dsp.in.data[0]&0x04) /* MPU IRQ enable bit */
irq = (sb.dsp.in.data[0]&0x80) ? 9 : 5;
else
irq = -1;
if (irq != MPU401_GetIRQ())
LOG(LOG_SB,LOG_WARN)("SC400 warning: MPU401 emulation does not yet support changing the IRQ through configuration commands");
}
LOG(LOG_SB,LOG_DEBUG)("SC400: New configuration byte %02x irq=%d dma=%d",
sb.dsp.in.data[0],(int)sb.hw.irq,(int)sb.hw.dma8);
break;
case 0x58: /* Reveal SC400 read configuration */
if (sb.reveal_sc_type != RSC_SC400) break;
DSP_AddData(sb.sc400_jumper_status_1);
DSP_AddData(sb.sc400_jumper_status_2);
DSP_AddData(sb.sc400_cfg);
break;
case 0x6E: /* Reveal SC400 SBPRO.EXE set DSP version */
if (sb.reveal_sc_type != RSC_SC400) break;
sb.sc400_dsp_major = sb.dsp.in.data[0];
sb.sc400_dsp_minor = sb.dsp.in.data[1];
LOG(LOG_SB,LOG_DEBUG)("SC400: DSP version changed to %u.%u",
sb.sc400_dsp_major,sb.sc400_dsp_minor);
break;
case 0xfa: /* SB16 8051 memory write */
if (sb.type == SBT_16) {
sb16_8051_mem[sb.dsp.in.data[0]] = sb.dsp.in.data[1];
LOG(LOG_SB,LOG_NORMAL)("SB16 8051 memory write %x byte %x",sb.dsp.in.data[0],sb.dsp.in.data[1]);
} else {
LOG(LOG_SB,LOG_ERROR)("DSP:Unhandled (undocumented) command %2X",sb.dsp.cmd);
}
break;
case 0xf9: /* SB16 8051 memory read */
if (sb.type == SBT_16) {
DSP_AddData(sb16_8051_mem[sb.dsp.in.data[0]]);
LOG(LOG_SB,LOG_NORMAL)("SB16 8051 memory read %x, got byte %x",sb.dsp.in.data[0],sb16_8051_mem[sb.dsp.in.data[0]]);
} else {
LOG(LOG_SB,LOG_ERROR)("DSP:Unhandled (undocumented) command %2X",sb.dsp.cmd);
}
break;
default:
LOG(LOG_SB,LOG_ERROR)("DSP:Unhandled (undocumented) command %2X",sb.dsp.cmd);
break;
}
if (sb.type == SBT_16) {
// FIXME: This is a guess! See also [https://github.com/joncampbell123/dosbox-x/issues/1044#issuecomment-480024957]
sb16_8051_mem[0x30] = sb.dsp.last_cmd; /* last_cmd */
}
sb.dsp.last_cmd=sb.dsp.cmd;
sb.dsp.cmd=DSP_NO_COMMAND;
sb.dsp.cmd_len=0;
sb.dsp.in.pos=0;
}
static bool DSP_busy_cycle_active() {
/* NTS: Busy cycle happens on SB16 at all times, or on earlier cards, only when the DSP is
* fetching/writing data via the ISA DMA channel. So a non-auto-init DSP block that's
* just finished fetching ISA DMA and is playing from the FIFO doesn't count.
*
* sb.dma.left >= sb.dma.min condition causes busy cycle to stop 3ms early (by default).
* This helps realism.
*
* This also helps Crystal Dream, which uses the busy cycle to detect when the Sound
* Blaster is about to finish playing the DSP block and therefore needs the same 3ms
* "dmamin" hack to reissue another playback command without any audible hiccups in
* the audio. */
return (sb.mode == MODE_DMA && (sb.dma.autoinit || sb.dma.left >= sb.dma.min)) || sb.busy_cycle_always;
}
static bool DSP_busy_cycle() {
double now;
int t;
if (!DSP_busy_cycle_active()) return false;
if (sb.busy_cycle_duty_percent <= 0 || sb.busy_cycle_hz <= 0) return false;
/* NTS: DOSBox's I/O emulation doesn't yet attempt to accurately match ISA bus speeds or
* consider ISA bus cycles, but to emulate SB16 behavior we have to "time" it so
* that 8 consecutive I/O reads eventually see a transition from busy to not busy
* (or the other way around). So what this hack does is it uses accurate timing
* to determine where in the cycle we are, but if this function is called repeatedly
* through I/O access, we switch to incrementing a counter to ensure busy/not busy
* transition happens in 8 I/O cycles.
*
* Without this hack, the CPU cycles count becomes a major factor in how many I/O
* reads are required for busy/not busy to happen. If you set cycles count high
* enough, more than 8 is required, and the SNDSB test code will have issues with
* direct DAC mode again.
*
* This isn't 100% accurate, but it's the best DOSBox-X can do for now to mimic
* SB16 DSP behavior. */
now = PIC_FullIndex();
if (now >= (sb.busy_cycle_last_check+0.02/*ms*/))
sb.busy_cycle_io_hack = (int)(fmod((now / 1000) * sb.busy_cycle_hz,1.0) * 16);
sb.busy_cycle_last_check = now;
t = ((sb.busy_cycle_io_hack % 16) * 100) / 16; /* HACK: DOSBox's I/O is not quite ISA bus speeds or related to it */
if (t < sb.busy_cycle_duty_percent) return true;
return false;
}
static void DSP_DoWrite(uint8_t val) {
if (sb.dsp.write_busy || (sb.dsp.highspeed && sb.type != SBT_16 && sb.ess_type == ESS_NONE && sb.reveal_sc_type == RSC_NONE)) {
LOG(LOG_SB,LOG_WARN)("DSP:Command write %2X ignored, DSP not ready. DOS game or OS is not polling status",val);
return;
}
/* NTS: We allow the user to set busy wait time == 0 aka "instant gratification mode".
* We also assume that if they do that, some DOS programs might be timing sensitive
* enough to freak out when DSP commands and data are accepted immediately */
{
unsigned int delay = sb.dsp.dsp_write_busy_time;
if (sb.dsp.instant_direct_dac) {
delay = 0;
}
/* Part of enforcing sample rate limits is to make sure to emulate that the
* Direct DAC output command 0x10 is "busy" long enough to effectively rate
* limit output to 23KHz. */
else if (sb.sample_rate_limits) {
unsigned int limit = 23000; /* documented max sample rate for SB16/SBPro and earlier */
if (sb.type == SBT_16 && sb.vibra)
limit = 23000; /* DSP maxes out at 46KHz not 44.1KHz on ViBRA cards */
if (sb.dsp.cmd == DSP_NO_COMMAND && val == 0x10/*DSP direct DAC, command*/)
delay = (625000000UL / limit) - sb.dsp.dsp_write_busy_time;
}
if (delay > 0) {
sb.dsp.write_busy = 1;
PIC_RemoveEvents(DSP_BusyComplete);
PIC_AddEvent(DSP_BusyComplete,(double)delay / 1000000);
}
// LOG(LOG_SB,LOG_NORMAL)("DSP:Command %02x delay %u",val,delay);
}
if (sb.dsp.midi_rwpoll_mode) {
// DSP writes in this mode go to the MIDI port
// LOG(LOG_SB,LOG_DEBUG)("DSP MIDI read/write poll mode: sending 0x%02x",val);
if (sb.midi == true) MIDI_RawOutByte(val);
return;
}
switch (sb.dsp.cmd) {
case DSP_NO_COMMAND:
/* genuine SB Pro and lower: remap DSP command to emulate aliases. */
if (sb.dsp.command_aliases && sb.type < SBT_16 && sb.ess_type == ESS_NONE && sb.reveal_sc_type == RSC_NONE) {
/* 0x41...0x47 are aliases of 0x40.
* See also: [https://www.vogons.org/viewtopic.php?f=62&t=61098&start=280].
* This is required for ftp.scene.org/mirrors/hornet/demos/1994/y/yahxmas.zip which relies on the 0x41 alias of command 0x40
* to function (which means that it may happen to work on SB Pro but will fail on clones and will fail on SB16 cards). */
if (val >= 0x41 && val <= 0x47) {
LOG(LOG_SB,LOG_WARN)("DSP command %02x and SB Pro or lower, treating as alias of 40h. Either written for SB16 or using undocumented alias.",val);
val = 0x40;
}
}
sb.dsp.cmd=val;
if (sb.type == SBT_16)
sb.dsp.cmd_len=DSP_cmd_len_sb16[val];
else if (sb.ess_type != ESS_NONE)
sb.dsp.cmd_len=DSP_cmd_len_ess[val];
else if (sb.reveal_sc_type != RSC_NONE)
sb.dsp.cmd_len=DSP_cmd_len_sc400[val];
else
sb.dsp.cmd_len=DSP_cmd_len_sb[val];
sb.dsp.in.pos=0;
if (!sb.dsp.cmd_len) DSP_DoCommand();
break;
default:
sb.dsp.in.data[sb.dsp.in.pos]=val;
sb.dsp.in.pos++;
if (sb.dsp.in.pos>=sb.dsp.cmd_len) DSP_DoCommand();
}
}
static uint8_t DSP_ReadData(void) {
/* Static so it repeats the last value on successive reads (JANGLE DEMO) */
if (sb.dsp.out.used) {
sb.dsp.out.lastval=sb.dsp.out.data[sb.dsp.out.pos];
sb.dsp.out.pos++;
if (sb.dsp.out.pos>=DSP_BUFSIZE) sb.dsp.out.pos-=DSP_BUFSIZE;
sb.dsp.out.used--;
}
return sb.dsp.out.lastval;
}
//The soundblaster manual says 2.0 Db steps but we'll go for a bit less
static float calc_vol(uint8_t amount) {
uint8_t count = 31 - amount;
float db = static_cast<float>(count);
if (sb.type == SBT_PRO1 || sb.type == SBT_PRO2) {
if (count) {
if (count < 16) db -= 1.0f;
else if (count > 16) db += 1.0f;
if (count == 24) db += 2.0f;
if (count > 27) return 0.0f; //turn it off.
}
} else { //Give the rest, the SB16 scale, as we don't have data.
db *= 2.0f;
if (count > 20) db -= 1.0f;
}
return (float) pow (10.0f,-0.05f * db);
}
static void CTMIXER_UpdateVolumes(void) {
if (!sb.mixer.enabled) return;
sb.chan->FillUp();
MixerChannel * chan;
float m0 = calc_vol(sb.mixer.master[0]);
float m1 = calc_vol(sb.mixer.master[1]);
chan = MIXER_FindChannel("SB");
if (chan) chan->SetVolume(m0 * calc_vol(sb.mixer.dac[0]), m1 * calc_vol(sb.mixer.dac[1]));
chan = MIXER_FindChannel("FM");
if (chan) chan->SetVolume(m0 * calc_vol(sb.mixer.fm[0]) , m1 * calc_vol(sb.mixer.fm[1]) );
chan = MIXER_FindChannel("CDAUDIO");
if (chan) chan->SetVolume(m0 * calc_vol(sb.mixer.cda[0]), m1 * calc_vol(sb.mixer.cda[1]));
}
static void CTMIXER_Reset(void) {
sb.mixer.filtered=0; // Creative Documentation: filtered bit 0 by default
sb.mixer.fm[0]=
sb.mixer.fm[1]=
sb.mixer.cda[0]=
sb.mixer.cda[1]=
sb.mixer.dac[0]=
sb.mixer.dac[1]=31;
sb.mixer.master[0]=
sb.mixer.master[1]=31;
CTMIXER_UpdateVolumes();
}
#define SETPROVOL(_WHICH_,_VAL_) \
_WHICH_[0]= ((((_VAL_) & 0xf0) >> 3)|(sb.type==SBT_16 ? 1:3)); \
_WHICH_[1]= ((((_VAL_) & 0x0f) << 1)|(sb.type==SBT_16 ? 1:3)); \
#define MAKEPROVOL(_WHICH_) \
((((_WHICH_[0] & 0x1e) << 3) | ((_WHICH_[1] & 0x1e) >> 1)) | \
((sb.type==SBT_PRO1 || sb.type==SBT_PRO2) ? 0x11:0))
// TODO: Put out the various hardware listed here, do some listening tests to confirm the emulation is accurate.
void updateSoundBlasterFilter(Bitu rate) {
/* "No filtering" option for those who don't want it, or are used to the way things sound in plain vanilla DOSBox */
if (sb.no_filtering) {
sb.chan->SetLowpassFreq(0/*off*/);
sb.chan->SetSlewFreq(0/*normal linear interpolation*/);
return;
}
/* different sound cards filter their output differently */
if (sb.ess_type != ESS_NONE) { // ESS AudioDrive. Tested against real hardware (ESS 688) by Jonathan C.
/* ESS AudioDrive lets the driver decide what the cutoff/rolloff to use */
/* "The ratio of the roll-off frequency to the clock frequency is 1:82. In other words,
* first determine the desired roll off frequency by taking 80% of the sample rate
* divided by 2, the multiply by 82 to find the desired filter clock frequency"
*
* Try to approximate the ESS's filter by undoing the calculation then feeding our own lowpass filter with it.
*
* This implementation is matched against real hardware by ear, even though the reference hardware is a
* laptop with a cheap tinny amplifier */
uint64_t filter_raw = (uint64_t)7160000ULL / (256u - ESSreg(0xA2));
uint64_t filter_hz = (filter_raw * (uint64_t)11) / (uint64_t)(82 * 4); /* match lowpass by ear compared to real hardware */
if ((filter_hz * 2) > sb.freq)
sb.chan->SetSlewFreq(filter_hz * 2 * sb.chan->freq_d_orig);
else
sb.chan->SetSlewFreq(0);
sb.chan->SetLowpassFreq(filter_hz,/*order*/8);
}
else if (sb.type == SBT_16 || // Sound Blaster 16 (DSP 4.xx). Tested against real hardware (CT4180 ViBRA 16C PnP) by Jonathan C.
sb.reveal_sc_type == RSC_SC400) { // Reveal SC400 (DSP 3.5). Tested against real hardware by Jonathan C.
// My notes: The DSP automatically applies filtering at low sample rates. But the DSP has to know
// what the sample rate is to filter. If you use direct DAC output (DSP command 0x10)
// then no filtering is applied and the sound comes out grungy, just like older Sound
// Blaster cards.
//
// I can also confirm the SB16's reputation for hiss and noise is true, it's noticeable
// with earbuds and the mixer volume at normal levels. --Jonathan C.
if (sb.mode == MODE_DAC) {
sb.chan->SetLowpassFreq(23000);
sb.chan->SetSlewFreq(23000 * sb.chan->freq_d_orig);
}
else {
sb.chan->SetLowpassFreq(rate/2,1);
sb.chan->SetSlewFreq(0/*normal linear interpolation*/);
}
}
else if (sb.type == SBT_PRO1 || sb.type == SBT_PRO2) { // Sound Blaster Pro (DSP 3.x). Tested against real hardware (CT1600) by Jonathan C.
sb.chan->SetSlewFreq(23000 * sb.chan->freq_d_orig);
if (sb.mixer.filtered/*setting the bit means to bypass the lowpass filter*/)
sb.chan->SetLowpassFreq(23000); // max sample rate 46000Hz. slew rate filter does the rest of the filtering for us.
else
sb.chan->SetLowpassFreq(3800); // NOT documented by Creative, guess based on listening tests with a CT1600, and documented Input filter freqs
}
else if (sb.type == SBT_1 || sb.type == SBT_2) { // Sound Blaster DSP 1.x and 2.x (not Pro). Tested against real hardware (CT1350B) by Jonathan C.
/* As far as I can tell the DAC outputs sample-by-sample with no filtering whatsoever, aside from the limitations of analog audio */
sb.chan->SetSlewFreq(23000 * sb.chan->freq_d_orig);
sb.chan->SetLowpassFreq(23000);
}
}
static void DSP_ChangeStereo(bool stereo) {
if (!sb.dma.stereo && stereo) {
sb.chan->SetFreq(sb.freq/2);
updateSoundBlasterFilter(sb.freq/2);
sb.dma.mul*=2;
sb.dma.rate=(sb.freq*sb.dma.mul) >> SB_SH;
sb.dma.min=((Bitu)sb.dma.rate*(Bitu)(sb.min_dma_user >= 0 ? sb.min_dma_user : /*default*/3))/1000u;
} else if (sb.dma.stereo && !stereo) {
sb.chan->SetFreq(sb.freq);
updateSoundBlasterFilter(sb.freq);
sb.dma.mul/=2;
sb.dma.rate=(sb.freq*sb.dma.mul) >> SB_SH;
sb.dma.min=((Bitu)sb.dma.rate*(Bitu)(sb.min_dma_user >= 0 ? sb.min_dma_user : /*default*/3))/1000;
}
sb.dma.stereo=stereo;
}
static inline uint8_t expand16to32(const uint8_t t) {
/* 4-bit -> 5-bit expansion.
*
* 0 -> 0
* 1 -> 2
* 2 -> 4
* 3 -> 6
* ....
* 7 -> 14
* 8 -> 17
* 9 -> 19
* 10 -> 21
* 11 -> 23
* ....
* 15 -> 31 */
return (t << 1) | (t >> 3);
}
static unsigned char pc98_mixctl_reg = 0x14;
/* Sound Blaster Pro 2 (CT1600) notes:
*
* - Registers 0x40-0xFF do nothing written and read back 0xFF.
* - Registers 0x00-0x3F are almost exact mirrors of registers 0x00-0x0F, but not quite
* - Registers 0x00-0x1F are exact mirrors of 0x00-0x0F
* - Registers 0x20-0x3F are exact mirrors of 0x20-0x2F which are.... non functional shadow copies of 0x00-0x0F (???)
* - Register 0x0E is mirrored at 0x0F, 0x1E, 0x1F. Reading 0x00, 0x01, 0x10, 0x11 also reads register 0x0E.
* - Writing 0x00, 0x01, 0x10, 0x11 resets the mixer as expected.
* - Register 0x0E is 0x11 on reset, which defaults to mono and lowpass filter enabled.
* - See screenshot for mixer registers on hardware or mixer reset, file 'CT1600 Sound Blaster Pro 2 mixer register dump hardware and mixer reset state.png' */
static void CTMIXER_Write(uint8_t val) {
switch (sb.mixer.index) {
case 0x00: /* Reset */
CTMIXER_Reset();
LOG(LOG_SB,LOG_WARN)("Mixer reset value %x",val);
break;
case 0x02: /* Master Volume (SB2 Only) */
SETPROVOL(sb.mixer.master,(val&0xf)|(val<<4));
CTMIXER_UpdateVolumes();
break;
case 0x04: /* DAC Volume (SBPRO) */
SETPROVOL(sb.mixer.dac,val);
CTMIXER_UpdateVolumes();
break;
case 0x06: /* FM output selection, Somewhat obsolete with dual OPL SBpro + FM volume (SB2 Only) */
//volume controls both channels
SETPROVOL(sb.mixer.fm,(val&0xf)|(val<<4));
CTMIXER_UpdateVolumes();
if(val&0x60) LOG(LOG_SB,LOG_WARN)("Turned FM one channel off. not implemented %X",val);
//TODO Change FM Mode if only 1 fm channel is selected
break;
case 0x08: /* CDA Volume (SB2 Only) */
SETPROVOL(sb.mixer.cda,(val&0xf)|(val<<4));
CTMIXER_UpdateVolumes();
break;
case 0x0a: /* Mic Level (SBPRO) or DAC Volume (SB2): 2-bit, 3-bit on SB16 */
if (sb.type==SBT_2) {
sb.mixer.dac[0]=sb.mixer.dac[1]=((val & 0x6) << 2)|3;
CTMIXER_UpdateVolumes();
} else {
sb.mixer.mic=((val & 0x7) << 2)|(sb.type==SBT_16?1:3);
}
break;
case 0x0e: /* Output/Stereo Select */
/* Real CT1600 notes:
*
* This register only allows changing bits 1 and 5. Each nibble can be 1 or 3, therefore on readback it will always be 0x11, 0x13, 0x31, or 0x11.
* This register is also mirrored at 0x0F, 0x1E, 0x1F. On read this register also appears over 0x00, 0x01, 0x10, 0x11, though writing that register
* resets the mixer as expected. */
/* only allow writing stereo bit if Sound Blaster Pro OR if a SB16 and user says to allow it */
if ((sb.type == SBT_PRO1 || sb.type == SBT_PRO2) || (sb.type == SBT_16 && !sb.sbpro_stereo_bit_strict_mode))
sb.mixer.stereo=(val & 0x2) > 0;
sb.mixer.sbpro_stereo=(val & 0x2) > 0;
sb.mixer.filtered=(val & 0x20) > 0;
DSP_ChangeStereo(sb.mixer.stereo);
updateSoundBlasterFilter(sb.freq);
/* help the user out if they leave sbtype=sb16 and then wonder why their DOS game is producing scratchy monural audio. */
if (sb.type == SBT_16 && sb.sbpro_stereo_bit_strict_mode && (val&0x2) != 0)
LOG(LOG_SB,LOG_WARN)("Mixer stereo/mono bit is set. This is Sound Blaster Pro style Stereo which is not supported by sbtype=sb16, consider using sbtype=sbpro2 instead.");
break;
case 0x14: /* Audio 1 Play Volume (ESS 688) */
if (sb.ess_type != ESS_NONE) {
sb.mixer.dac[0]=expand16to32((val>>4)&0xF);
sb.mixer.dac[1]=expand16to32(val&0xF);
CTMIXER_UpdateVolumes();
}
break;
case 0x22: /* Master Volume (SBPRO) */
SETPROVOL(sb.mixer.master,val);
CTMIXER_UpdateVolumes();
break;
case 0x26: /* FM Volume (SBPRO) */
SETPROVOL(sb.mixer.fm,val);
CTMIXER_UpdateVolumes();
break;
case 0x28: /* CD Audio Volume (SBPRO) */
SETPROVOL(sb.mixer.cda,val);
CTMIXER_UpdateVolumes();
break;
case 0x2e: /* Line-in Volume (SBPRO) */
SETPROVOL(sb.mixer.lin,val);
break;
//case 0x20: /* Master Volume Left (SBPRO) ? */
case 0x30: /* Master Volume Left (SB16) */
if (sb.type==SBT_16) {
sb.mixer.master[0]=val>>3;
CTMIXER_UpdateVolumes();
}
break;
//case 0x21: /* Master Volume Right (SBPRO) ? */
case 0x31: /* Master Volume Right (SB16) */
if (sb.type==SBT_16) {
sb.mixer.master[1]=val>>3;
CTMIXER_UpdateVolumes();
}
break;
case 0x32: /* DAC Volume Left (SB16) */
/* Master Volume (ESS 688) */
if (sb.type==SBT_16) {
sb.mixer.dac[0]=val>>3;
CTMIXER_UpdateVolumes();
}
else if (sb.ess_type != ESS_NONE) {
sb.mixer.master[0]=expand16to32((val>>4)&0xF);
sb.mixer.master[1]=expand16to32(val&0xF);
CTMIXER_UpdateVolumes();
}
break;
case 0x33: /* DAC Volume Right (SB16) */
if (sb.type==SBT_16) {
sb.mixer.dac[1]=val>>3;
CTMIXER_UpdateVolumes();
}
break;
case 0x34: /* FM Volume Left (SB16) */
if (sb.type==SBT_16) {
sb.mixer.fm[0]=val>>3;
CTMIXER_UpdateVolumes();
}
break;
case 0x35: /* FM Volume Right (SB16) */
if (sb.type==SBT_16) {
sb.mixer.fm[1]=val>>3;
CTMIXER_UpdateVolumes();
}
break;
case 0x36: /* CD Volume Left (SB16) */
/* FM Volume (ESS 688) */
if (sb.type==SBT_16) {
sb.mixer.cda[0]=val>>3;
CTMIXER_UpdateVolumes();
}
else if (sb.ess_type != ESS_NONE) {
sb.mixer.fm[0]=expand16to32((val>>4)&0xF);
sb.mixer.fm[1]=expand16to32(val&0xF);
CTMIXER_UpdateVolumes();
}
break;
case 0x37: /* CD Volume Right (SB16) */
if (sb.type==SBT_16) {
sb.mixer.cda[1]=val>>3;
CTMIXER_UpdateVolumes();
}
break;
case 0x38: /* Line-in Volume Left (SB16) */
/* AuxA (CD) Volume Register (ESS 688) */
if (sb.type==SBT_16)
sb.mixer.lin[0]=val>>3;
else if (sb.ess_type != ESS_NONE) {
sb.mixer.cda[0]=expand16to32((val>>4)&0xF);
sb.mixer.cda[1]=expand16to32(val&0xF);
CTMIXER_UpdateVolumes();
}
break;
case 0x39: /* Line-in Volume Right (SB16) */
if (sb.type==SBT_16) sb.mixer.lin[1]=val>>3;
break;
case 0x3a:
if (sb.type==SBT_16) sb.mixer.mic=val>>3;
break;
case 0x3e: /* Line Volume (ESS 688) */
if (sb.ess_type != ESS_NONE) {
sb.mixer.lin[0]=expand16to32((val>>4)&0xF);
sb.mixer.lin[1]=expand16to32(val&0xF);
}
break;
case 0x80: /* IRQ Select */
if (sb.type==SBT_16 && !sb.vibra) { /* ViBRA PnP cards do not allow reconfiguration by this byte */
sb.hw.irq=0xff;
if (IS_PC98_ARCH) {
if (val & 0x1) sb.hw.irq=3;
else if (val & 0x2) sb.hw.irq=10;
else if (val & 0x4) sb.hw.irq=12;
else if (val & 0x8) sb.hw.irq=5;
// NTS: Real hardware stores only the low 4 bits. The upper 4 bits will always read back 1111.
// The value read back will always be Fxh where x contains the 4 bits checked here.
}
else {
if (val & 0x1) sb.hw.irq=2;
else if (val & 0x2) sb.hw.irq=5;
else if (val & 0x4) sb.hw.irq=7;
else if (val & 0x8) sb.hw.irq=10;
}
}
break;
case 0x81: /* DMA Select */
if (sb.type==SBT_16 && !sb.vibra) { /* ViBRA PnP cards do not allow reconfiguration by this byte */
sb.hw.dma8=0xff;
sb.hw.dma16=0xff;
if (IS_PC98_ARCH) {
pc98_mixctl_reg = (unsigned char)val ^ 0x14;
if (val & 0x1) sb.hw.dma8=0;
else if (val & 0x2) sb.hw.dma8=3;
// NTS: On real hardware, only bits 0 and 1 are writeable. bits 2 and 4 seem to act oddly in response to
// bytes written:
//
// write 0x00 read 0x14
// write 0x01 read 0x15
// write 0x02 read 0x16
// write 0x03 read 0x17
// write 0x04 read 0x10
// write 0x05 read 0x11
// write 0x06 read 0x12
// write 0x07 read 0x13
// write 0x08 read 0x1C
// write 0x09 read 0x1D
// write 0x0A read 0x1E
// write 0x0B read 0x1F
// write 0x0C read 0x18
// write 0x0D read 0x19
// write 0x0E read 0x1A
// write 0x0F read 0x1B
// write 0x10 read 0x04
// write 0x11 read 0x05
// write 0x12 read 0x06
// write 0x13 read 0x07
// write 0x14 read 0x00
// write 0x15 read 0x01
// write 0x16 read 0x02
// write 0x17 read 0x03
// write 0x18 read 0x0C
// write 0x19 read 0x0D
// write 0x1A read 0x0E
// write 0x1B read 0x0F
// write 0x1C read 0x08
// write 0x1D read 0x09
// write 0x1E read 0x0A
// write 0x1F read 0x0B
//
// This pattern repeats for any 5 bit value in bits [4:0] i.e. 0x20 will read back 0x34.
}
else {
if (val & 0x1) sb.hw.dma8=0;
else if (val & 0x2) sb.hw.dma8=1;
else if (val & 0x8) sb.hw.dma8=3;
if (val & 0x20) sb.hw.dma16=5;
else if (val & 0x40) sb.hw.dma16=6;
else if (val & 0x80) sb.hw.dma16=7;
}
LOG(LOG_SB,LOG_NORMAL)("Mixer select dma8:%x dma16:%x",sb.hw.dma8,sb.hw.dma16);
}
break;
default:
if( ((sb.type == SBT_PRO1 || sb.type == SBT_PRO2) && sb.mixer.index==0x0c) || /* Input control on SBPro */
(sb.type == SBT_16 && sb.mixer.index >= 0x3b && sb.mixer.index <= 0x47)) /* New SB16 registers */
sb.mixer.unhandled[sb.mixer.index] = val;
LOG(LOG_SB,LOG_WARN)("MIXER:Write %X to unhandled index %X",val,sb.mixer.index);
}
}
static uint8_t CTMIXER_Read(void) {
uint8_t ret = 0;
// if ( sb.mixer.index< 0x80) LOG_MSG("Read mixer %x",sb.mixer.index);
switch (sb.mixer.index) {
case 0x00: /* RESET */
return 0x00;
case 0x02: /* Master Volume (SB2 Only) */
return ((sb.mixer.master[1]>>1) & 0xe);
case 0x22: /* Master Volume (SBPRO) */
return MAKEPROVOL(sb.mixer.master);
case 0x04: /* DAC Volume (SBPRO) */
return MAKEPROVOL(sb.mixer.dac);
case 0x06: /* FM Volume (SB2 Only) + FM output selection */
return ((sb.mixer.fm[1]>>1) & 0xe);
case 0x08: /* CD Volume (SB2 Only) */
return ((sb.mixer.cda[1]>>1) & 0xe);
case 0x0a: /* Mic Level (SBPRO) or Voice (SB2 Only) */
if (sb.type==SBT_2) return (sb.mixer.dac[0]>>2);
else return ((sb.mixer.mic >> 2) & (sb.type==SBT_16 ? 7:6));
case 0x0e: /* Output/Stereo Select */
return 0x11|(sb.mixer.stereo ? 0x02 : 0x00)|(sb.mixer.filtered ? 0x20 : 0x00);
case 0x14: /* Audio 1 Play Volume (ESS 688) */
if (sb.ess_type != ESS_NONE) return ((sb.mixer.dac[0] << 3) & 0xF0) + (sb.mixer.dac[1] >> 1);
break;
case 0x26: /* FM Volume (SBPRO) */
return MAKEPROVOL(sb.mixer.fm);
case 0x28: /* CD Audio Volume (SBPRO) */
return MAKEPROVOL(sb.mixer.cda);
case 0x2e: /* Line-IN Volume (SBPRO) */
return MAKEPROVOL(sb.mixer.lin);
case 0x30: /* Master Volume Left (SB16) */
if (sb.type==SBT_16) return sb.mixer.master[0]<<3;
ret=0xa;
break;
case 0x31: /* Master Volume Right (S16) */
if (sb.type==SBT_16) return sb.mixer.master[1]<<3;
ret=0xa;
break;
case 0x32: /* DAC Volume Left (SB16) */
/* Master Volume (ESS 688) */
if (sb.type==SBT_16) return sb.mixer.dac[0]<<3;
if (sb.ess_type != ESS_NONE) return ((sb.mixer.master[0] << 3) & 0xF0) + (sb.mixer.master[1] >> 1);
ret=0xa;
break;
case 0x33: /* DAC Volume Right (SB16) */
if (sb.type==SBT_16) return sb.mixer.dac[1]<<3;
ret=0xa;
break;
case 0x34: /* FM Volume Left (SB16) */
if (sb.type==SBT_16) return sb.mixer.fm[0]<<3;
ret=0xa;
break;
case 0x35: /* FM Volume Right (SB16) */
if (sb.type==SBT_16) return sb.mixer.fm[1]<<3;
ret=0xa;
break;
case 0x36: /* CD Volume Left (SB16) */
/* FM Volume (ESS 688) */
if (sb.type==SBT_16) return sb.mixer.cda[0]<<3;
if (sb.ess_type != ESS_NONE) return ((sb.mixer.fm[0] << 3) & 0xF0) + (sb.mixer.fm[1] >> 1);
ret=0xa;
break;
case 0x37: /* CD Volume Right (SB16) */
if (sb.type==SBT_16) return sb.mixer.cda[1]<<3;
ret=0xa;
break;
case 0x38: /* Line-in Volume Left (SB16) */
/* AuxA (CD) Volume Register (ESS 688) */
if (sb.type==SBT_16) return sb.mixer.lin[0]<<3;
if (sb.ess_type != ESS_NONE) return ((sb.mixer.cda[0] << 3) & 0xF0) + (sb.mixer.cda[1] >> 1);
ret=0xa;
break;
case 0x39: /* Line-in Volume Right (SB16) */
if (sb.type==SBT_16) return sb.mixer.lin[1]<<3;
ret=0xa;
break;
case 0x3a: /* Mic Volume (SB16) */
if (sb.type==SBT_16) return sb.mixer.mic<<3;
ret=0xa;
break;
case 0x3e: /* Line Volume (ESS 688) */
if (sb.ess_type != ESS_NONE) return ((sb.mixer.lin[0] << 3) & 0xF0) + (sb.mixer.lin[1] >> 1);
break;
case 0x80: /* IRQ Select */
ret=0;
if (IS_PC98_ARCH) {
switch (sb.hw.irq) {
case 3: return 0xF1; // upper 4 bits always 1111
case 10: return 0xF2;
case 12: return 0xF4;
case 5: return 0xF8;
}
}
else {
switch (sb.hw.irq) {
case 2: return 0x1;
case 5: return 0x2;
case 7: return 0x4;
case 10: return 0x8;
}
}
break;
case 0x81: /* DMA Select */
ret=0;
if (IS_PC98_ARCH) {
switch (sb.hw.dma8) {
case 0:ret|=0x1;break;
case 3:ret|=0x2;break;
}
// there's some strange behavior on the PC-98 version of the card
ret |= (pc98_mixctl_reg & (~3u));
}
else {
switch (sb.hw.dma8) {
case 0:ret|=0x1;break;
case 1:ret|=0x2;break;
case 3:ret|=0x8;break;
}
switch (sb.hw.dma16) {
case 5:ret|=0x20;break;
case 6:ret|=0x40;break;
case 7:ret|=0x80;break;
}
}
return ret;
case 0x82: /* IRQ Status */
return (sb.irq.pending_8bit ? 0x1 : 0) |
(sb.irq.pending_16bit ? 0x2 : 0) |
((sb.type == SBT_16) ? 0x20 : 0);
default:
if ( ((sb.type == SBT_PRO1 || sb.type == SBT_PRO2) && sb.mixer.index==0x0c) || /* Input control on SBPro */
(sb.type == SBT_16 && sb.mixer.index >= 0x3b && sb.mixer.index <= 0x47)) /* New SB16 registers */
ret = sb.mixer.unhandled[sb.mixer.index];
else
ret=0xa;
LOG(LOG_SB,LOG_WARN)("MIXER:Read from unhandled index %X",sb.mixer.index);
}
return ret;
}
static Bitu read_sb(Bitu port,Bitu /*iolen*/) {
if (!IS_PC98_ARCH) {
/* All Creative hardware prior to Sound Blaster 16 appear to alias most of the I/O ports.
* This has been confirmed on a Sound Blaster 2.0 and a Sound Blaster Pro (v3.1).
* DSP aliasing is also faithfully emulated by the ESS AudioDrive. */
if (sb.hw.sb_io_alias) {
if ((port-sb.hw.base) == DSP_ACK_16BIT && sb.ess_type != ESS_NONE)
{ } /* ESS AudioDrive does not alias DSP STATUS (0x22E) as seen on real hardware */
else if ((port-sb.hw.base) < MIXER_INDEX || (port-sb.hw.base) > MIXER_DATA)
port &= ~1u;
}
}
switch (((port-sb.hw.base) >> (IS_PC98_ARCH ? 8u : 0u)) & 0xFu) {
case MIXER_INDEX:
return sb.mixer.index;
case MIXER_DATA:
return CTMIXER_Read();
case DSP_READ_DATA:
return DSP_ReadData();
case DSP_READ_STATUS:
//TODO See for high speed dma :)
if (sb.irq.pending_8bit) {
sb.irq.pending_8bit=false;
PIC_DeActivateIRQ(sb.hw.irq);
}
if (sb.mode == MODE_DMA_REQUIRE_IRQ_ACK)
sb.mode = MODE_DMA;
extern const char* RunningProgram; // Wengier: Hack for Desert Strike & Jungle Strike
if (!IS_PC98_ARCH && port>0x220 && port%0x10==0xE && !sb.dsp.out.used && (!strcmp(RunningProgram, "DESERT") || !strcmp(RunningProgram, "JUNGLE"))) {
LOG_MSG("Check status by game: %s\n", RunningProgram);
sb.dsp.out.used++;
}
if (sb.ess_type == ESS_NONE && (sb.type == SBT_1 || sb.type == SBT_2 || sb.type == SBT_PRO1 || sb.type == SBT_PRO2))
return sb.dsp.out.used ? 0xAA : 0x2A; /* observed return values on SB 2.0---any significance? */
else
return sb.dsp.out.used ? 0xFF : 0x7F; /* normal return values */
case DSP_ACK_16BIT:
if (sb.ess_type == ESS_NONE && sb.type == SBT_16) {
if (sb.irq.pending_16bit) {
sb.irq.pending_16bit=false;
PIC_DeActivateIRQ(sb.hw.irq);
}
if (sb.mode == MODE_DMA_REQUIRE_IRQ_ACK)
sb.mode = MODE_DMA;
}
break;
case DSP_WRITE_STATUS:
switch (sb.dsp.state) {
/* FIXME: On a SB 2.0 card I own, the port will usually read either 0x2A or 0xAA,
* rather than 0x7F or 0xFF. Is there any significance to that? */
case DSP_S_NORMAL: {
bool busy = false;
/* NTS: DSP "busy cycle" is independent of whether the DSP is actually
* busy (executing a command) or highspeed mode. On SB16 hardware,
* writing a DSP command during the busy cycle means that the command
* is remembered, but not acted on until the DSP leaves it's busy
* cycle. */
sb.busy_cycle_io_hack++; /* NTS: busy cycle I/O timing hack! */
if (DSP_busy_cycle())
busy = true;
else if (sb.dsp.write_busy || (sb.dsp.highspeed && sb.type != SBT_16 && sb.ess_type == ESS_NONE && sb.reveal_sc_type == RSC_NONE))
busy = true;
if (!sb.write_status_must_return_7f && sb.ess_type == ESS_NONE && (sb.type == SBT_2 || sb.type == SBT_PRO1 || sb.type == SBT_PRO2))
return busy ? 0xAA : 0x2A; /* observed return values on SB 2.0---any significance? */
else
return busy ? 0xFF : 0x7F; /* normal return values */
}
case DSP_S_RESET:
case DSP_S_RESET_WAIT:
return 0xff;
}
return 0xff;
case DSP_RESET:
return 0xff;
default:
LOG(LOG_SB,LOG_NORMAL)("Unhandled read from SB Port %4X",(int)port);
break;
}
return 0xff;
}
static void write_sb(Bitu port,Bitu val,Bitu /*iolen*/) {
/* All Creative hardware prior to Sound Blaster 16 appear to alias most of the I/O ports.
* This has been confirmed on a Sound Blaster 2.0 and a Sound Blaster Pro (v3.1).
* DSP aliasing is also faithfully emulated by the ESS AudioDrive. */
if (!IS_PC98_ARCH) {
if (sb.hw.sb_io_alias) {
if ((port-sb.hw.base) == DSP_ACK_16BIT && sb.ess_type != ESS_NONE)
{ } /* ESS AudioDrive does not alias DSP STATUS (0x22E) as seen on real hardware */
else if ((port-sb.hw.base) < MIXER_INDEX || (port-sb.hw.base) > MIXER_DATA)
port &= ~1u;
}
}
uint8_t val8=(uint8_t)(val&0xff);
switch (((port-sb.hw.base) >> (IS_PC98_ARCH ? 8u : 0u)) & 0xFu) {
case DSP_RESET:
DSP_DoReset(val8);
break;
case DSP_WRITE_DATA:
/* FIXME: We need to emulate behavior where either the DSP command is delayed (busy cycle)
* and then acted on, or we need to emulate the DSP ignoring the byte because a
* command is in progress */
DSP_DoWrite(val8);
break;
case MIXER_INDEX:
sb.mixer.index=val8;
break;
case MIXER_DATA:
CTMIXER_Write(val8);
break;
default:
LOG(LOG_SB,LOG_NORMAL)("Unhandled write to SB Port %4X",(int)port);
break;
}
}
static void adlib_gusforward(Bitu /*port*/,Bitu val,Bitu /*iolen*/) {
adlib_commandreg=(uint8_t)(val&0xff);
}
bool SB_Get_Address(Bitu& sbaddr, Bitu& sbirq, Bitu& sbdma) {
sbaddr=0;
sbirq =0;
sbdma =0;
if (sb.type == SBT_NONE) return false;
else {
sbaddr=sb.hw.base;
sbirq =sb.hw.irq;
sbdma = sb.hw.dma8;
return true;
}
}
static pic_tickindex_t next_check_record_settings = 0;
static void SBLASTER_CallBack(Bitu len) {
pic_tickindex_t now = PIC_FullIndex();
if (now >= next_check_record_settings) {
next_check_record_settings = now + 100/*ms*/;
sb_update_recording_source_settings();
}
sb.directdac_warn_speaker_off = true;
switch (sb.mode) {
case MODE_NONE:
case MODE_DMA_PAUSE:
case MODE_DMA_MASKED:
case MODE_DMA_REQUIRE_IRQ_ACK:
sb.chan->AddSilence();
break;
case MODE_DAC:
sb.mode = MODE_NONE;
break;
case MODE_DMA:
len*=sb.dma.mul;
if (len&SB_SH_MASK) len+=1 << SB_SH;
len>>=SB_SH;
if (len>sb.dma.left) len=sb.dma.left;
GenerateDMASound(len);
break;
}
}
#define ISAPNP_COMPATIBLE(id) \
ISAPNP_SMALL_TAG(3,4), \
( (id) & 0xFF), \
(((id) >> 8) & 0xFF), \
(((id) >> 16) & 0xFF), \
(((id) >> 24) & 0xFF)
#if 0
static const unsigned char ViBRA_sysdev[] = {
ISAPNP_IO_RANGE(
0x01, /* decodes 16-bit ISA addr */
0x220,0x280, /* min-max range I/O port */
0x20,0x10), /* align=0x20 length=0x10 */
ISAPNP_IO_RANGE(
0x01, /* decodes 16-bit ISA addr */
0x300,0x330, /* min-max range I/O port */
0x10,0x4), /* align=0x10 length=0x4 */
ISAPNP_IO_RANGE(
0x01, /* decodes 16-bit ISA addr */
0x388,0x38C, /* min-max range I/O port */
0x4,0x4), /* align=0x4 length=0x4 */
ISAPNP_IRQ(
(1 << 5) |
(1 << 7) |
(1 << 9) |
(1 << 10) |
(1 << 11), /* IRQ 5,7,9,10,11 */
0x09), /* HTE=1 LTL=1 */
ISAPNP_DMA(
(1 << 0) |
(1 << 1) |
(1 << 3), /* DMA 0,1,3 */
0x01), /* 8/16-bit */
ISAPNP_DMA(
(1 << 0) |
(1 << 1) |
(1 << 3) |
(1 << 5) |
(1 << 6) |
(1 << 7), /* DMA 0,1,3,5,6,7 */
0x01), /* 8/16-bit */
ISAPNP_COMPATIBLE(ISAPNP_ID('C','T','L',0x0,0x0,0x0,0x1)), /* <- Windows 95 doesn't know CTL0070 but does know CTL0001, this hack causes Win95 to offer it's internal driver as an option */
ISAPNP_END
};
#endif
class ViBRA_PnP : public ISAPnPDevice {
public:
ViBRA_PnP() : ISAPnPDevice() {
resource_ident = 0;
host_writed(ident+0,ISAPNP_ID('C','T','L',0x0,0x0,0x7,0x0)); /* CTL0070: ViBRA C */
host_writed(ident+4,0xFFFFFFFFUL);
checksum_ident();
alloc(256 - 9/*ident*/); // Real ViBRA hardware acts as if PNP data is read from a 256-byte ROM
// this template taken from a real Creative ViBRA16C card
begin_write_res();
write_ISAPnP_version(/*version*/1,0,/*vendor*/0x10);
write_Identifier_String("Creative ViBRA16C PnP");
write_Logical_Device_ID('C','T','L',0x0,0x0,0x0,0x1); // CTL0001
write_Identifier_String("Audio");
write_Dependent_Function_Start(ISAPnPDevice::DependentFunctionConfig::PreferredDependentConfiguration);
write_IRQ_Format(
ISAPnPDevice::irq2mask(5));
write_DMA_Format(
ISAPnPDevice::dma2mask(1),
DMATransferType_8bitOnly,
false,/*not a bus master*/
true,/*byte mode */
false,/*word mode*/
DMASpeedSupported_Compat);
write_DMA_Format(
ISAPnPDevice::dma2mask(5),
DMATransferType_16bitOnly,
false,/*not a bus master*/
false,/*byte mode */
true,/*word mode*/
DMASpeedSupported_Compat);
write_IO_Port(/*min*/0x220,/*max*/0x220,/*count*/0x10,/*align*/0x01);
write_IO_Port(/*min*/0x330,/*max*/0x330,/*count*/0x02,/*align*/0x01);
write_IO_Port(/*min*/0x388,/*max*/0x388,/*count*/0x04,/*align*/0x01);
write_Dependent_Function_Start(ISAPnPDevice::DependentFunctionConfig::AcceptableDependentConfiguration,true);
write_IRQ_Format(
ISAPnPDevice::irq2mask(5,7,9,10));
write_DMA_Format(
ISAPnPDevice::dma2mask(1,3),
DMATransferType_8bitOnly,
false,/*not a bus master*/
true,/*byte mode */
false,/*word mode*/
DMASpeedSupported_Compat);
write_DMA_Format(
ISAPnPDevice::dma2mask(5,7),
DMATransferType_16bitOnly,
false,/*not a bus master*/
false,/*byte mode */
true,/*word mode*/
DMASpeedSupported_Compat);
write_IO_Port(/*min*/0x220,/*max*/0x280,/*count*/0x10,/*align*/0x20);
write_IO_Port(/*min*/0x300,/*max*/0x330,/*count*/0x02,/*align*/0x30);
write_IO_Port(/*min*/0x388,/*max*/0x388,/*count*/0x04,/*align*/0x01);
write_Dependent_Function_Start(ISAPnPDevice::DependentFunctionConfig::AcceptableDependentConfiguration,true);
write_IRQ_Format(
ISAPnPDevice::irq2mask(5,7,9,10));
write_DMA_Format(
ISAPnPDevice::dma2mask(1,3),
DMATransferType_8bitOnly,
false,/*not a bus master*/
true,/*byte mode */
false,/*word mode*/
DMASpeedSupported_Compat);
write_DMA_Format(
ISAPnPDevice::dma2mask(5,7),
DMATransferType_16bitOnly,
false,/*not a bus master*/
false,/*byte mode */
true,/*word mode*/
DMASpeedSupported_Compat);
write_IO_Port(/*min*/0x220,/*max*/0x280,/*count*/0x10,/*align*/0x20);
write_IO_Port(/*min*/0x300,/*max*/0x330,/*count*/0x02,/*align*/0x30);
write_Dependent_Function_Start(ISAPnPDevice::DependentFunctionConfig::SubOptimalDependentConfiguration);
write_IRQ_Format(
ISAPnPDevice::irq2mask(5,7,9,10));
write_DMA_Format(
ISAPnPDevice::dma2mask(1,3),
DMATransferType_8bitOnly,
false,/*not a bus master*/
true,/*byte mode */
false,/*word mode*/
DMASpeedSupported_Compat);
write_DMA_Format(
ISAPnPDevice::dma2mask(5,7),
DMATransferType_16bitOnly,
false,/*not a bus master*/
false,/*byte mode */
true,/*word mode*/
DMASpeedSupported_Compat);
write_IO_Port(/*min*/0x220,/*max*/0x280,/*count*/0x10,/*align*/0x20);
write_Dependent_Function_Start(ISAPnPDevice::DependentFunctionConfig::SubOptimalDependentConfiguration);
write_IRQ_Format(
ISAPnPDevice::irq2mask(5,7,9,10));
write_DMA_Format(
ISAPnPDevice::dma2mask(1,3),
DMATransferType_8bitOnly,
false,/*not a bus master*/
true,/*byte mode */
false,/*word mode*/
DMASpeedSupported_Compat);
write_IO_Port(/*min*/0x220,/*max*/0x280,/*count*/0x10,/*align*/0x20);
write_IO_Port(/*min*/0x300,/*max*/0x330,/*count*/0x02,/*align*/0x30);
write_IO_Port(/*min*/0x388,/*max*/0x388,/*count*/0x04,/*align*/0x01);
write_Dependent_Function_Start(ISAPnPDevice::DependentFunctionConfig::SubOptimalDependentConfiguration);
write_IRQ_Format(
ISAPnPDevice::irq2mask(5,7,9,10));
write_DMA_Format(
ISAPnPDevice::dma2mask(1,3),
DMATransferType_8bitOnly,
false,/*not a bus master*/
true,/*byte mode */
false,/*word mode*/
DMASpeedSupported_Compat);
write_IO_Port(/*min*/0x220,/*max*/0x280,/*count*/0x10,/*align*/0x20);
write_End_Dependent_Functions();
// NTS: DOSBox-X as coded now always has a joystick port at 0x201 even if no joystick
write_Logical_Device_ID('C','T','L',0x7,0x0,0x0,0x1); // CTL7001
write_Compatible_Device_ID('P','N','P',0xB,0x0,0x2,0xF); // PNPB02F
write_Identifier_String("Game");
write_IO_Port(/*min*/0x200,/*max*/0x200,/*count*/0x08);
end_write_res(); // END
}
void select_logical_device(Bitu val) {
logical_device = val;
}
uint8_t read(Bitu addr) {
uint8_t ret = 0xFF;
if (logical_device == 0) {
switch (addr) {
case 0x60: case 0x61: /* I/O [0] sound blaster */
ret = sb.hw.base >> ((addr & 1) ? 0 : 8);
break;
case 0x62: case 0x63: /* I/O [1] MPU */
ret = 0x330 >> ((addr & 1) ? 0 : 8); /* FIXME: What I/O port really IS the MPU on? */
break;
case 0x64: case 0x65: /* I/O [1] OPL-3 */
ret = 0x388 >> ((addr & 1) ? 0 : 8); /* FIXME */
break;
case 0x70: /* IRQ[0] */
ret = sb.hw.irq;
break;
/* TODO: 0x71 IRQ mode */
case 0x74: /* DMA[0] */
ret = sb.hw.dma8;
break;
case 0x75: /* DMA[1] */
ret = sb.hw.dma16 == 0xFF ? sb.hw.dma8 : sb.hw.dma16;
break;
}
}
else if (logical_device == 1) {
switch (addr) {
case 0x60: case 0x61: /* I/O [0] gameport */
ret = 0x200 >> ((addr & 1) ? 0 : 8);
break;
}
}
return ret;
}
void write(Bitu addr,Bitu val) {
if (logical_device == 0) {
switch (addr) {
case 0x30: /* activate range */
/* TODO: set flag to ignore writes/return 0xFF when "deactivated". setting bit 0 activates, clearing deactivates */
break;
case 0x60: case 0x61: /* I/O [0] sound blaster */
/* TODO: on-the-fly changing */
//LOG_MSG("ISA PnP Warning: Sound Blaster I/O port changing not implemented yet\n");
break;
case 0x62: case 0x63: /* I/O [1] MPU */
/* TODO: on-the-fly changing */
//LOG_MSG("ISA PnP Warning: MPU I/O port changing not implemented yet\n");
break;
case 0x64: case 0x65: /* I/O [1] OPL-3 */
/* TODO: on-the-fly changing */
//LOG_MSG("ISA PnP Warning: OPL-3 I/O port changing not implemented yet\n");
break;
case 0x70: /* IRQ[0] */
if (val & 0xF)
sb.hw.irq = val;
else
sb.hw.irq = 0xFF;
break;
case 0x74: /* DMA[0] */
if ((val & 7) == 4)
sb.hw.dma8 = 0xFF;
else
sb.hw.dma8 = val & 7;
break;
case 0x75: /* DMA[1] */
if ((val & 7) == 4)
sb.hw.dma16 = 0xFF;
else
sb.hw.dma16 = val & 7;
break;
}
}
else if (logical_device == 1) {
switch (addr) {
case 0x60: case 0x61: /* I/O [0] gameport */
/* TODO: on-the-fly changing */
//LOG_MSG("ISA PnP Warning: Gameport I/O port changing not implemented yet\n");
break;
}
}
}
};
bool JOYSTICK_IsEnabled(Bitu which);
extern void HARDOPL_Init(Bitu hardwareaddr, Bitu sbbase, bool isCMS);
std::string GetSBtype() {
switch (sb.type) {
case SBT_NONE:
return "None";
case SBT_1:
return "SB1";
case SBT_PRO1:
return "SBPro";
case SBT_2:
return "SB2";
case SBT_PRO2:
return "SBPro 2";
case SBT_16:
return "SB16";
case SBT_GB:
return "GB";
default:
return "Unknown";
}
}
std::string GetSBbase() {
std::stringstream ss;
ss << std::hex << sb.hw.base;
return ss.str();
}
std::string GetSBirq() {
return sb.hw.irq==0xff?"None":std::to_string(sb.hw.irq);
}
std::string GetSBldma() {
return sb.hw.dma8==0xff?"None":std::to_string((int)sb.hw.dma8);
}
std::string GetSBhdma() {
return sb.hw.dma16==0xff?"None":std::to_string((int)sb.hw.dma16);
}
class SBLASTER: public Module_base {
private:
/* Data */
IO_ReadHandleObject ReadHandler[0x10];
IO_WriteHandleObject WriteHandler[0x10];
AutoexecObject autoexecline;
MixerObject MixerChan;
OPL_Mode oplmode;
/* Support Functions */
void Find_Type_And_Opl(Section_prop* config,SB_TYPES& type, OPL_Mode& opl_mode) const {
sb.vibra = false;
sb.ess_type = ESS_NONE;
sb.reveal_sc_type = RSC_NONE;
sb.ess_extended_mode = false;
const char * sbtype=config->Get_string("sbtype");
if (control->opt_silent) type = SBT_NONE;
else if (!strcasecmp(sbtype,"sb1")) type=SBT_1;
else if (!strcasecmp(sbtype,"sb2")) type=SBT_2;
else if (!strcasecmp(sbtype,"sbpro1")) type=SBT_PRO1;
else if (!strcasecmp(sbtype,"sbpro2")) type=SBT_PRO2;
else if (!strcasecmp(sbtype,"sb16vibra")) type=SBT_16;
else if (!strcasecmp(sbtype,"sb16")) type=SBT_16;
else if (!strcasecmp(sbtype,"gb")) type=SBT_GB;
else if (!strcasecmp(sbtype,"none")) type=SBT_NONE;
else if (!strcasecmp(sbtype,"ess688")) {
type=SBT_PRO2;
sb.ess_type=ESS_688;
LOG(LOG_SB,LOG_DEBUG)("ESS 688 emulation enabled.");
LOG(LOG_SB,LOG_WARN)("ESS 688 emulation is EXPERIMENTAL at this time and should not yet be used for normal gaming");
}
else if (!strcasecmp(sbtype,"reveal_sc400")) {
type=SBT_PRO2;
sb.reveal_sc_type=RSC_SC400;
LOG(LOG_SB,LOG_DEBUG)("Reveal SC400 emulation enabled.");
LOG(LOG_SB,LOG_WARN)("Reveal SC400 emulation is EXPERIMENTAL at this time and should not yet be used for normal gaming.");
LOG(LOG_SB,LOG_WARN)("Additional WARNING: This code only emulates the Sound Blaster portion of the card. Attempting to use the Windows Sound System part of the card (i.e. the Voyetra/SC400 Windows drivers) will not work!");
}
else type=SBT_16;
if (type == SBT_16) {
/* NTS: mainline DOSBox forces the type to SBT_PRO2 if !IS_EGAVGA_ARCH or no secondary DMA controller.
* I'd rather take the approach that, if the user wants to emulate a bizarre unusual setup like
* sticking a Sound Blaster 16 in an 8-bit machine, they are free to do so on the understanding
* it won't work properly (and emulation will show what happens). I also believe that tying
* 8-bit vs 16-bit system type to the *video card* was a really dumb move. */
if (!SecondDMAControllerAvailable()) {
LOG(LOG_SB,LOG_WARN)("Sound Blaster 16 enabled on a system without 16-bit DMA. Don't expect this setup to work properly! To improve compatibility please edit your dosbox-x.conf and change sbtype to sbpro2 instead, or else enable the secondary DMA controller.");
}
}
/* SB16 Vibra cards are Plug & Play */
if (!IS_PC98_ARCH) {
if (!strcasecmp(sbtype,"sb16vibra")) {
ISA_PNP_devreg(new ViBRA_PnP());
sb.vibra = true;
}
}
/* OPL/CMS Init */
const char * omode=config->Get_string("oplmode");
if (!strcasecmp(omode,"none")) opl_mode=OPL_none;
else if (!strcasecmp(omode,"cms")) opl_mode=OPL_cms;
else if (!strcasecmp(omode,"opl2")) opl_mode=OPL_opl2;
else if (!strcasecmp(omode,"dualopl2")) opl_mode=OPL_dualopl2;
else if (!strcasecmp(omode,"opl3")) opl_mode=OPL_opl3;
else if (!strcasecmp(omode,"opl3gold")) opl_mode=OPL_opl3gold;
else if (!strcasecmp(omode,"hardware")) opl_mode=OPL_hardware;
else if (!strcasecmp(omode,"hardwaregb")) opl_mode=OPL_hardwareCMS;
/* Else assume auto */
else {
switch (type) {
case SBT_NONE:
opl_mode=OPL_none;
break;
case SBT_GB:
opl_mode=OPL_cms;
break;
case SBT_1:
case SBT_2:
opl_mode=OPL_opl2;
break;
case SBT_PRO1:
opl_mode=OPL_dualopl2;
break;
case SBT_PRO2: // NTS: ESS 688 cards also had an OPL3 (http://www.dosdays.co.uk/topics/Manufacturers/ess.php)
case SBT_16:
opl_mode=OPL_opl3;
break;
}
}
if (IS_PC98_ARCH) {
if (opl_mode != OPL_none) {
if (opl_mode != OPL_opl3) {
LOG(LOG_SB,LOG_WARN)("Only OPL3 is allowed in PC-98 mode");
opl_mode = OPL_opl3;
}
}
/* card type MUST be SB16.
* Creative did not release any other Sound Blaster for PC-98 as far as I know. */
if (sb.type != SBT_16) {
LOG(LOG_SB,LOG_ERROR)("Only Sound Blaster 16 is allowed in PC-98 mode");
sb.type = SBT_NONE;
}
}
}
public:
SBLASTER(Section* configuration):Module_base(configuration) {
bool bv;
string s;
Bitu i;
int si;
Section_prop * section=static_cast<Section_prop *>(configuration);
sb.hw.base=(unsigned int)section->Get_hex("sbbase");
if (IS_PC98_ARCH) {
if (sb.hw.base >= 0x220 && sb.hw.base <= 0x2E0) /* translate IBM PC to PC-98 (220h -> D2h) */
sb.hw.base = 0xD0 + ((sb.hw.base >> 4u) & 0xFu);
}
else {
if (sb.hw.base >= 0xD2 && sb.hw.base <= 0xDE) /* translate PC-98 to IBM PC (D2h -> 220h) */
sb.hw.base = 0x200 + ((sb.hw.base & 0xFu) << 4u);
}
sb_update_recording_source_settings();
sb.goldplay=section->Get_bool("goldplay");
sb.min_dma_user=section->Get_int("mindma");
sb.goldplay_stereo=section->Get_bool("goldplay stereo");
sb.emit_blaster_var=section->Get_bool("blaster environment variable");
sb.sample_rate_limits=section->Get_bool("sample rate limits");
sb.sbpro_stereo_bit_strict_mode=section->Get_bool("stereo control with sbpro only");
sb.hw.sb_io_alias=section->Get_bool("io port aliasing");
sb.busy_cycle_hz=section->Get_int("dsp busy cycle rate");
sb.busy_cycle_duty_percent=section->Get_int("dsp busy cycle duty");
sb.dsp.instant_direct_dac=section->Get_bool("instant direct dac");
sb.dsp.force_goldplay=section->Get_bool("force goldplay");
sb.dma.force_autoinit=section->Get_bool("force dsp auto-init");
sb.no_filtering=section->Get_bool("disable filtering");
sb.def_enable_speaker=section->Get_bool("enable speaker");
sb.enable_asp=section->Get_bool("enable asp");
if (!sb.goldplay && sb.dsp.force_goldplay) {
sb.goldplay = true;
LOG_MSG("force goldplay = true but goldplay = false, enabling Goldplay mode anyway");
}
/* Explanation: If the user set this option, the write status port must return 0x7F or 0xFF.
* Else, we're free to return whatever with bit 7 to indicate busy.
* The reason for this setting is that there exist some very early DOS demoscene productions
* that assume the I/O port returns 0x7F or 0xFF, and will have various problems if that
* is not the case.
*
* Overload by Hysteria (1992):
* - This demo's Sound Blaster routines use the unusual space-saving assembly language
* trick of reading port 22Ch to wait for bit 7 to clear (which is normal), then
* using the value it read (left over from AL) as the byte to sum it's audio output
* to before sending out to the DAC. This is why, if the write status port returns
* a different value like 0x2A, the audio crackes with saturation errors.
*
* Overload's Sound Blaster output code:
*
* 090D:5F50 EC in al,dx
* 090D:5F51 A880 test al,80
* 090D:5F53 75FB jne 00005F50 ($-5) (no jmp)
* 090D:5F55 FEC0 inc al
* ; various code to render and sum audio samples to AL
* 090D:5FC7 EE out dx,al
*
* Notice it assumes AL is 0x7F and it assumes (++AL) == 0x80.
*
* It's also funny to note the demo's readme file mentions this problem with Sound
* Blaster Pro:
*
* "I have been told this works with the soundblaster however, this demo does
* not seem to work the soundblaster pro.
*
* If the music sounds bad and you want to know what it really sounds like
* let me know and I can send you the MOD format of the music.
*
* dmw@sioux.ee.ufl.edu"
*
*/
sb.write_status_must_return_7f=section->Get_bool("dsp write buffer status must return 0x7f or 0xff");
sb.dsp.midi_rwpoll_mode = false;
sb.dsp.midi_read_interrupt = false;
sb.dsp.midi_read_with_timestamps = false;
sb.busy_cycle_last_check=0;
sb.busy_cycle_io_hack=0;
si=section->Get_int("irq");
sb.hw.irq=(si >= 0) ? (unsigned int)si : 0xFF;
if (IS_PC98_ARCH) {
if (sb.hw.irq == 7) /* IRQ 7 is not valid on PC-98 (that's cascade interrupt) */
sb.hw.irq = 5;
}
si=section->Get_int("dma");
sb.hw.dma8=(si >= 0) ? (unsigned int)si : 0xFF;
si=section->Get_int("hdma");
sb.hw.dma16=(si >= 0) ? (unsigned int)si : 0xFF;
if (IS_PC98_ARCH) {
if (sb.hw.dma8 > 3)
sb.hw.dma8 = 3;
if (sb.hw.dma8 == 1) /* DMA 1 is not usable for SB on PC-98? */
sb.hw.dma8 = 3;
if (sb.hw.dma16 > 3)
sb.hw.dma16 = sb.hw.dma8;
LOG_MSG("PC-98: Final SB16 resources are DMA8=%u DMA16=%u\n",sb.hw.dma8,sb.hw.dma16);
sb.dma.chan=GetDMAChannel(sb.hw.dma8);
if (sb.dma.chan == NULL) LOG_MSG("PC-98: SB16 is unable to obtain DMA channel");
}
sb.dsp.command_aliases=section->Get_bool("dsp command aliases");
Find_Type_And_Opl(section,sb.type,oplmode);
if (sb.hw.irq == 0) {
std::string sbtype=GetSBtype();
sb.hw.irq=sbtype=="SBPro 2"||sbtype=="SB16"||IS_PC98_ARCH?5:7;
}
/* some DOS games/demos support Sound Blaster, and expect the IRQ to fire, but
* make no attempt to unmask the IRQ (perhaps the programmer forgot). This option
* is needed for Public NMI "jump" demo in order for music to continue playing. */
bv=section->Get_bool("pic unmask irq");
if (bv && sb.hw.irq != 0xFF) {
LOG_MSG("Sound blaster: unmasking IRQ at startup as requested.");
PIC_SetIRQMask(sb.hw.irq,false);
}
if (sb.hw.irq == 0xFF || sb.hw.dma8 == 0xFF) {
LOG(LOG_SB,LOG_WARN)("IRQ and 8-bit DMA not assigned, disabling BLASTER variable");
sb.emit_blaster_var = false;
}
sb.mixer.enabled=section->Get_bool("sbmixer");
sb.mixer.stereo=false;
bv=section->Get_bool("pre-set sbpro stereo");
if (bv) {
LOG_MSG("Sound blaster: setting SB Pro mixer 'stereo' bit as instructed.");
sb.mixer.stereo=true;
}
bool isCMSpassthrough = false;
switch (oplmode) {
case OPL_none:
if (!IS_PC98_ARCH)
WriteHandler[0].Install(0x388,adlib_gusforward,IO_MB);
break;
case OPL_cms:
assert(!IS_PC98_ARCH);
WriteHandler[0].Install(0x388,adlib_gusforward,IO_MB);
CMS_Init(section);
break;
case OPL_opl2:
assert(!IS_PC98_ARCH);
CMS_Init(section);
// fall-through
case OPL_dualopl2:
assert(!IS_PC98_ARCH);
case OPL_opl3:
case OPL_opl3gold:
OPL_Init(section,oplmode);
break;
case OPL_hardwareCMS:
assert(!IS_PC98_ARCH);
isCMSpassthrough = true;
case OPL_hardware:
assert(!IS_PC98_ARCH);
Bitu base = (unsigned int)section->Get_hex("hardwarebase");
HARDOPL_Init(base, sb.hw.base, isCMSpassthrough);
break;
}
if (sb.type==SBT_NONE || sb.type==SBT_GB) return;
sb.chan=MixerChan.Install(&SBLASTER_CallBack,22050,"SB");
sb.dac.dac_pt = sb.dac.dac_t = 0;
sb.dsp.state=DSP_S_NORMAL;
sb.dsp.out.lastval=0xaa;
sb.dma.chan=NULL;
for (i=4;i<=0xf;i++) {
if (i==8 || i==9) continue;
//Disable mixer ports for lower soundblaster
if ((sb.type==SBT_1 || sb.type==SBT_2) && (i==4 || i==5)) continue;
ReadHandler[i].Install(sb.hw.base+(IS_PC98_ARCH ? ((i+0x20u) << 8u) : i),read_sb,IO_MB);
WriteHandler[i].Install(sb.hw.base+(IS_PC98_ARCH ? ((i+0x20u) << 8u) : i),write_sb,IO_MB);
}
// NTS: Unknown/undefined registers appear to return the register index you requested rather than the actual contents,
// according to real SB16 CSP/ASP hardware (chip version id 0x10).
//
// Registers 0x00-0x1F are defined. Registers 0x80-0x83 are defined.
for (i=0;i<256;i++) ASP_regs[i] = i;
for (i=0x00;i < 0x20;i++) ASP_regs[i] = 0;
for (i=0x80;i < 0x84;i++) ASP_regs[i] = 0;
ASP_regs[5] = 0x01;
ASP_regs[9] = 0xf8;
DSP_Reset();
CTMIXER_Reset();
// The documentation does not specify if SB gets initialized with the speaker enabled
// or disabled. Real SBPro2 has it disabled.
sb.speaker=false;
// On SB16 the speaker flag does not affect actual speaker state.
if (sb.type == SBT_16 || sb.ess_type != ESS_NONE || sb.reveal_sc_type != RSC_NONE) sb.chan->Enable(true);
else sb.chan->Enable(false);
if (sb.def_enable_speaker)
DSP_SetSpeaker(true);
s=section->Get_string("dsp require interrupt acknowledge");
if (s == "true" || s == "1" || s == "on")
sb.dsp.require_irq_ack = 1;
else if (s == "false" || s == "0" || s == "off")
sb.dsp.require_irq_ack = 0;
else /* auto */
sb.dsp.require_irq_ack = (sb.type == SBT_16) ? 1 : 0; /* Yes if SB16, No otherwise */
si=section->Get_int("dsp write busy delay"); /* in nanoseconds */
if (si >= 0) sb.dsp.dsp_write_busy_time = (unsigned int)si;
else sb.dsp.dsp_write_busy_time = 1000; /* FIXME: How long is the DSP busy on real hardware? */
/* Sound Blaster (1.x and 2x) and Sound Blaster Pro (3.1) have the I/O port aliasing.
* The aliasing is also faithfully emulated by the ESS AudioDrive. */
switch (sb.type) {
case SBT_1: /* guess */
case SBT_2: /* verified on real hardware */
case SBT_GB: /* FIXME: Right?? */
case SBT_PRO1: /* verified on real hardware */
case SBT_PRO2: /* guess */
break;
default:
sb.hw.sb_io_alias=false;
break;
}
/* auto-pick busy cycle */
/* NOTE: SB16 cards appear to run a DSP busy cycle at all times.
* SB2 cards (and Pro?) appear to run a DSP busy cycle only when playing audio through DMA. */
if (sb.busy_cycle_hz < 0) {
if (sb.type == SBT_16) /* Guess: Pentium PCI-ISA SYSCLK=8.333MHz / (6 cycles per 8-bit I/O read x 16 reads from DSP status) = about 86.805KHz? */
sb.busy_cycle_hz = 8333333 / 6 / 16;
else /* Guess ???*/
sb.busy_cycle_hz = 8333333 / 6 / 16;
}
if (sb.ess_type != ESS_NONE) {
uint8_t t;
/* legacy audio interrupt control */
t = 0x80;/*game compatible IRQ*/
switch (sb.hw.irq) {
case 5: t |= 0x5; break;
case 7: t |= 0xA; break;
case 10: t |= 0xF; break;
}
ESSreg(0xB1) = t;
/* DRQ control */
t = 0x80;/*game compatible DRQ */
switch (sb.hw.dma8) {
case 0: t |= 0x5; break;
case 1: t |= 0xA; break;
case 3: t |= 0xF; break;
}
ESSreg(0xB2) = t;
}
/* first configuration byte returned by 0x58.
*
* bits 7-5: ???
* bit 4: Windows Sound System (Crystal Semiconductor CS4231A) I/O base port (1=0xE80 0=0x530)
* bits 3-2: ???
* bit 1: gameport disable (1=disabled 0=enabled)
* bit 0: jumper assigns Sound Blaster base port 240h (right??) */
sb.sc400_jumper_status_1 = 0x2C + ((sb.hw.base == 0x240) ? 0x1 : 0x0);
if (!JOYSTICK_IsEnabled(0) && !JOYSTICK_IsEnabled(1)) sb.sc400_jumper_status_1 += 0x02; // set bit 1
/* second configuration byte returned by 0x58.
*
* bits 7-5: 110b ???
* bits 4-0: bits 8-4 of the CD-ROM I/O port base. For example, to put the CD-ROM controller at 0x230 you would set this to 0x3.
* TESTSC.EXE ignores the value unless it represents one of the supported base I/O ports for the CD-ROM, which are:
*
* 0x03 -> 0x230
* 0x05 -> 0x250
* 0x11 -> 0x310
* 0x12 -> 0x320
* 0x13 -> 0x330
* 0x14 -> 0x340
* 0x15 -> 0x350
* 0x16 -> 0x360
*
* TODO: The CD-ROM interface is said to be designed for Panasonic or Goldstar CD-ROM drives. Can we emulate that someday,
* and allow the user to configure the base I/O port? */
sb.sc400_jumper_status_2 = 0xC0 | (((sb.hw.base + 0x10) >> 4) & 0xF);
sb.sc400_dsp_major = 3;
sb.sc400_dsp_minor = 5;
sb.sc400_cfg = 0;
/* bit 7: MPU IRQ select (1=IRQ 9 0=IRQ 5)
* bit 6: ???
* bit 5-3: IRQ select
* bit 2: MPU IRQ enable
* bit 1-0: DMA select */
switch (sb.hw.dma8) {
case 0: sb.sc400_cfg |= (1 << 0); break;
case 1: sb.sc400_cfg |= (2 << 0); break;
case 3: sb.sc400_cfg |= (3 << 0); break;
}
switch (sb.hw.irq) {
case 5: sb.sc400_cfg |= (5 << 3); break;
case 7: sb.sc400_cfg |= (1 << 3); break;
case 9: sb.sc400_cfg |= (2 << 3); break;
case 10:sb.sc400_cfg |= (3 << 3); break;
case 11:sb.sc400_cfg |= (4 << 3); break;
}
switch (MPU401_GetIRQ()) { // SC400: bit 7 and bit 2 control MPU IRQ
case 5: sb.sc400_cfg |= (0 << 7) + (1 << 2); break; // bit 7=0 bit 2=1 MPU IRQ 5
case 9: sb.sc400_cfg |= (1 << 7) + (1 << 2); break; // bit 7=1 bit 2=1 MPU IRQ 9
default: break; // bit 7=0 bit 2=0 MPU IRQ disabled
}
if (sb.reveal_sc_type != RSC_NONE) {
// SC400 cards always use 8-bit DMA even for 16-bit PCM
if (sb.hw.dma16 != sb.hw.dma8) {
LOG(LOG_SB,LOG_DEBUG)("SC400: DMA is always 8-bit, setting 16-bit == 8-bit");
sb.hw.dma16 = sb.hw.dma8;
}
}
si = section->Get_int("dsp busy cycle always");
if (si >= 0) sb.busy_cycle_always= (si > 0) ? true : false;
else if (sb.type == SBT_16) sb.busy_cycle_always = true;
else sb.busy_cycle_always = false;
/* auto-pick busy duty cycle */
if (sb.busy_cycle_duty_percent < 0 || sb.busy_cycle_duty_percent > 100)
sb.busy_cycle_duty_percent = 50; /* seems about right */
if (sb.hw.irq != 0 && sb.hw.irq != 0xFF) {
s = section->Get_string("irq hack");
if (!s.empty() && s != "none") {
LOG(LOG_SB,LOG_NORMAL)("Sound Blaster emulation: Assigning IRQ hack '%s' as instruced",s.c_str());
PIC_Set_IRQ_hack((int)sb.hw.irq,PIC_parse_IRQ_hack_string(s.c_str()));
}
}
// ASP emulation
if (sb.enable_asp && sb.type != SBT_16) {
LOG(LOG_SB,LOG_WARN)("ASP emulation requires you to select SB16 emulation");
sb.enable_asp = false;
}
// SB16 non-PNP ASP RAM content observation.
// Contrary to my initial impression, it looks like the RAM is mostly 0xFF
// with some random bits flipped because no initialization is done at hardware power-on.
memset(sb16asp_ram_contents,0xFF,2048);
for (i=0;i < (2048 * 8);i++) {
if (((unsigned int)rand() & 31) == 0)
sb16asp_ram_contents[i>>3] ^= 1 << (i & 7);
}
/* Reference: Command 0xF9 result map taken from Sound Blaster 16 with DSP 4.4 and ASP chip version ID 0x10:
*
* ASP> F9 result map:
00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ff 07
10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
20: f9 00 00 00 00 aa 96 00 00 00 00 00 00 00 00 00
30: f9 00 00 00 00 00 00 38 00 00 00 00 00 00 00 00
40: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
80: 00 19 0a 00 00 00 00 00 00 00 00 00 00 00 00 00
90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
ASP>
* End reference */
memset(sb16_8051_mem,0x00,256);
sb16_8051_mem[0x0E] = 0xFF;
sb16_8051_mem[0x0F] = 0x07;
sb16_8051_mem[0x37] = 0x38;
if (sb.enable_asp)
LOG(LOG_SB,LOG_WARN)("ASP emulation at this stage is extremely limited and only covers DSP command responses.");
/* Soundblaster midi interface */
if (!MIDI_Available()) sb.midi = false;
else sb.midi = true;
}
void DOS_Shutdown() { /* very likely, we're booting into a guest OS where our environment variable has no meaning anymore */
autoexecline.Uninstall();
}
void DOS_Startup() {
if (sb.type==SBT_NONE || sb.type==SBT_GB) return;
if (sb.emit_blaster_var) {
// Create set blaster line
ostringstream temp;
if (IS_PC98_ARCH)
temp << "@SET BLASTER=A" << setw(2) << hex << sb.hw.base;
else
temp << "@SET BLASTER=A" << setw(3) << hex << sb.hw.base;
if (sb.hw.irq != 0xFF) temp << " I" << dec << (Bitu)sb.hw.irq;
if (sb.hw.dma8 != 0xFF) temp << " D" << (Bitu)sb.hw.dma8;
if (sb.type==SBT_16 && sb.hw.dma16 != 0xFF) temp << " H" << (Bitu)sb.hw.dma16;
if (!IS_PC98_ARCH) {
Section_prop * section=static_cast<Section_prop *>(control->GetSection("midi"));
const char* s_mpu = section->Get_string("mpu401");
if(strcasecmp(s_mpu,"none") && strcasecmp(s_mpu,"off") && strcasecmp(s_mpu,"false")) {
Bitu baseio = (Bitu)section->Get_hex("mpubase");
if (baseio == 0 || baseio < 0x300 || baseio > 0x360)
baseio = 0x330;
temp << " P" << hex << baseio;
}
}
temp << " T" << static_cast<unsigned int>(sb.type) << ends;
autoexecline.Install(temp.str());
}
}
~SBLASTER() {
switch (oplmode) {
case OPL_none:
break;
case OPL_cms:
CMS_ShutDown(m_configuration);
break;
case OPL_opl2:
CMS_ShutDown(m_configuration);
// fall-through
case OPL_dualopl2:
case OPL_opl3:
case OPL_opl3gold:
OPL_ShutDown(m_configuration);
break;
default:
break;
}
if (sb.type==SBT_NONE || sb.type==SBT_GB) return;
DSP_Reset(); // Stop everything
}
}; //End of SBLASTER class
extern void HWOPL_Cleanup();
static SBLASTER* test = NULL;
void SBLASTER_DOS_Shutdown() {
if (test != NULL) test->DOS_Shutdown();
}
void SBLASTER_ShutDown(Section* /*sec*/) {
if (test != NULL) {
delete test;
test = NULL;
}
HWOPL_Cleanup();
}
void SBLASTER_OnReset(Section *sec) {
(void)sec;//UNUSED
SBLASTER_DOS_Shutdown();
if (test != NULL) {
delete test;
test = NULL;
}
HWOPL_Cleanup();
if (test == NULL) {
LOG(LOG_MISC,LOG_DEBUG)("Allocating Sound Blaster emulation");
test = new SBLASTER(control->GetSection("sblaster"));
}
}
void SBLASTER_DOS_Exit(Section *sec) {
(void)sec;//UNUSED
SBLASTER_DOS_Shutdown();
}
void SBLASTER_DOS_Boot(Section *sec) {
(void)sec;//UNUSED
if (test != NULL) test->DOS_Startup();
}
void SBLASTER_Init() {
LOG(LOG_MISC,LOG_DEBUG)("Initializing Sound Blaster emulation");
AddExitFunction(AddExitFunctionFuncPair(SBLASTER_ShutDown),true);
AddVMEventFunction(VM_EVENT_RESET,AddVMEventFunctionFuncPair(SBLASTER_OnReset));
AddVMEventFunction(VM_EVENT_DOS_EXIT_BEGIN,AddVMEventFunctionFuncPair(SBLASTER_DOS_Exit));
AddVMEventFunction(VM_EVENT_DOS_SURPRISE_REBOOT,AddVMEventFunctionFuncPair(SBLASTER_DOS_Exit));
AddVMEventFunction(VM_EVENT_DOS_EXIT_REBOOT_BEGIN,AddVMEventFunctionFuncPair(SBLASTER_DOS_Exit));
AddVMEventFunction(VM_EVENT_DOS_INIT_SHELL_READY,AddVMEventFunctionFuncPair(SBLASTER_DOS_Boot));
}
// save state support
void *DMA_DAC_Event_PIC_Event = (void*)((uintptr_t)DMA_DAC_Event);
void *DMA_Silent_Event_PIC_Event = (void*)((uintptr_t)DMA_Silent_Event);
void *DSP_FinishReset_PIC_Event = (void*)((uintptr_t)DSP_FinishReset);
void *DSP_RaiseIRQEvent_PIC_Event = (void*)((uintptr_t)DSP_RaiseIRQEvent);
void *END_DMA_Event_PIC_Event = (void*)((uintptr_t)END_DMA_Event);
void *SB_DSP_DMA_CallBack_Func = (void*)((uintptr_t)DSP_DMA_CallBack);
void *SB_DSP_ADC_CallBack_Func = (void*)((uintptr_t)DSP_ADC_CallBack);
void *SB_DSP_E2_DMA_CallBack_Func = (void*)((uintptr_t)DSP_E2_DMA_CallBack);
void POD_Save_Sblaster( std::ostream& stream )
{
const char pod_name[32] = "SBlaster";
if( stream.fail() ) return;
if( !test ) return;
if( !sb.chan ) return;
WRITE_POD( &pod_name, pod_name );
//*******************************************
//*******************************************
//*******************************************
uint8_t dma_idx;
dma_idx = 0xff;
for( int lcv=0; lcv<8; lcv++ ) {
if( sb.dma.chan == GetDMAChannel(lcv) ) { dma_idx = lcv; break; }
}
// *******************************************
// *******************************************
// *******************************************
// - near-pure data
WRITE_POD( &sb, sb );
// - pure data
WRITE_POD( &ASP_regs, ASP_regs );
//WRITE_POD( &ASP_init_in_progress, ASP_init_in_progress );
WRITE_POD( &last_dma_callback, last_dma_callback );
// - reloc ptr
WRITE_POD( &dma_idx, dma_idx );
// *******************************************
// *******************************************
// *******************************************
sb.chan->SaveState(stream);
}
void POD_Load_Sblaster( std::istream& stream )
{
char pod_name[32] = {0};
if( stream.fail() ) return;
if( !test ) return;
if( !sb.chan ) return;
// error checking
READ_POD( &pod_name, pod_name );
if( strcmp( pod_name, "SBlaster" ) ) {
stream.clear( std::istream::failbit | std::istream::badbit );
return;
}
//************************************************
//************************************************
//************************************************
uint8_t dma_idx;
MixerChannel *mixer_old;
// save static ptr
mixer_old = sb.chan;
//*******************************************
//*******************************************
//*******************************************
// - near-pure data
READ_POD( &sb, sb );
// - pure data
READ_POD( &ASP_regs, ASP_regs );
//READ_POD( &ASP_init_in_progress, ASP_init_in_progress );
READ_POD( &last_dma_callback, last_dma_callback );
// - reloc ptr
READ_POD( &dma_idx, dma_idx );
//*******************************************
//*******************************************
//*******************************************
sb.dma.chan = NULL;
if( dma_idx != 0xff ) sb.dma.chan = GetDMAChannel(dma_idx);
//*******************************************
//*******************************************
//*******************************************
// restore static ptr
sb.chan = mixer_old;
sb.chan->LoadState(stream);
}