diff --git a/src/include/86box/snd_sb_dsp.h b/src/include/86box/snd_sb_dsp.h
index f04734ebb0..2b39a0303f 100644
--- a/src/include/86box/snd_sb_dsp.h
+++ b/src/include/86box/snd_sb_dsp.h
@@ -143,6 +143,7 @@ typedef struct sb_dsp_t {
uint8_t ess_regs[256]; /* ESS registers. */
uint8_t ess_playback_mode;
uint8_t ess_extended_mode;
+ uint8_t ess_reload_len;
mpu_t *mpu;
} sb_dsp_t;
diff --git a/src/sound/CMakeLists.txt b/src/sound/CMakeLists.txt
index 9b2cc14161..9206f6be91 100644
--- a/src/sound/CMakeLists.txt
+++ b/src/sound/CMakeLists.txt
@@ -18,7 +18,7 @@ add_library(snd OBJECT sound.c snd_opl.c snd_opl_nuked.c snd_opl_ymfm.cpp snd_re
snd_lpt_dss.c snd_ps1.c snd_adlib.c snd_adlibgold.c snd_ad1848.c snd_audiopci.c
snd_azt2316a.c snd_cms.c snd_cmi8x38.c snd_cs423x.c snd_gus.c snd_sb.c snd_sb_dsp.c
snd_emu8k.c snd_mpu401.c snd_sn76489.c snd_ssi2001.c snd_wss.c snd_ym7128.c
- snd_optimc.c esfmu/esfm.c esfmu/esfm_registers.c snd_opl_esfm.c)
+ snd_optimc.c esfmu/esfm.c esfmu/esfm_registers.c snd_opl_esfm.c snd_ess.c)
if(OPENAL)
if(VCPKG_TOOLCHAIN)
diff --git a/src/sound/snd_ess.c b/src/sound/snd_ess.c
new file mode 100644
index 0000000000..d762117cde
--- /dev/null
+++ b/src/sound/snd_ess.c
@@ -0,0 +1,258 @@
+/*
+ * 86Box A hypervisor and IBM PC system emulator that specializes in
+ * running old operating systems and software designed for IBM
+ * PC systems and compatibles from 1981 through fairly recent
+ * system designs based on the PCI bus.
+ *
+ * This file is part of the 86Box distribution.
+ *
+ * Sound Blaster emulation.
+ *
+ *
+ *
+ * Authors: Sarah Walker,
+ * Miran Grca,
+ * TheCollector1995,
+ *
+ * Copyright 2008-2020 Sarah Walker.
+ * Copyright 2016-2020 Miran Grca.
+ */
+#include
+#include
+#include
+#include
+#include
+#include
+#define HAVE_STDARG_H
+
+#include <86box/86box.h>
+#include <86box/device.h>
+#include <86box/filters.h>
+#include <86box/gameport.h>
+#include <86box/hdc.h>
+#include <86box/isapnp.h>
+#include <86box/hdc_ide.h>
+#include <86box/io.h>
+#include <86box/mca.h>
+#include <86box/mem.h>
+#include <86box/midi.h>
+#include <86box/pic.h>
+#include <86box/rom.h>
+#include <86box/sound.h>
+#include <86box/timer.h>
+#include <86box/snd_sb.h>
+#include <86box/plat_unused.h>
+
+static const double sb_att_4dbstep_3bits[] = {
+ 164.0, 2067.0, 3276.0, 5193.0, 8230.0, 13045.0, 20675.0, 32767.0
+};
+
+static const double sb_att_7dbstep_2bits[] = {
+ 164.0, 6537.0, 14637.0, 32767.0
+};
+
+/* SB PRO */
+typedef struct ess_mixer_t {
+ double master_l;
+ double master_r;
+ double voice_l;
+ double voice_r;
+ double fm_l;
+ double fm_r;
+ double cd_l;
+ double cd_r;
+ double line_l;
+ double line_r;
+ double mic;
+ /*see sb_ct1745_mixer for values for input selector*/
+ int32_t input_selector;
+
+ int input_filter;
+ int in_filter_freq;
+ int output_filter;
+
+ int stereo;
+ int stereo_isleft;
+
+ uint8_t index;
+ uint8_t regs[256];
+} ess_mixer_t;
+
+typedef struct ess_t {
+ uint8_t mixer_enabled;
+ fm_drv_t opl;
+ sb_dsp_t dsp;
+ union {
+ ess_mixer_t mixer_sbpro;
+ };
+ mpu_t *mpu;
+ emu8k_t emu8k;
+ void *gameport;
+
+ int pnp;
+
+ uint8_t pos_regs[8];
+ uint8_t pnp_rom[512];
+
+ uint16_t opl_pnp_addr;
+ uint16_t gameport_addr;
+
+ void *opl_mixer;
+ void (*opl_mix)(void*, double*, double*);
+} ess_t;
+
+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);
+}
+
+void
+ess_mixer_write(uint16_t addr, uint8_t val, void *priv)
+{
+ ess_t *ess = (ess_t *) priv;
+ ess_mixer_t *mixer = &ess->mixer_sbpro;
+
+ if (!(addr & 1)) {
+ mixer->index = val;
+ mixer->regs[0x01] = val;
+ } else {
+ if (mixer->index == 0) {
+ /* Reset */
+ mixer->regs[0x0a] = mixer->regs[0x0c] = 0x00;
+ mixer->regs[0x0e] = 0x00;
+ /* Changed default from -11dB to 0dB */
+ mixer->regs[0x04] = mixer->regs[0x22] = 0xee;
+ mixer->regs[0x26] = mixer->regs[0x28] = 0xee;
+ mixer->regs[0x2e] = 0x00;
+ sb_dsp_set_stereo(&ess->dsp, mixer->regs[0x0e] & 2);
+ } else {
+ mixer->regs[mixer->index] = val;
+
+ switch (mixer->index) {
+ /* Compatibility: chain registers 0x02 and 0x22 as well as 0x06 and 0x26 */
+ case 0x02:
+ case 0x06:
+ case 0x08:
+ mixer->regs[mixer->index + 0x20] = ((val & 0xe) << 4) | (val & 0xe);
+ break;
+
+ case 0x22:
+ case 0x26:
+ case 0x28:
+ mixer->regs[mixer->index - 0x20] = (val & 0xe);
+ break;
+
+ /* More compatibility:
+ SoundBlaster Pro selects register 020h for 030h, 022h for 032h,
+ 026h for 036h, and 028h for 038h. */
+ case 0x30:
+ case 0x32:
+ case 0x36:
+ case 0x38:
+ mixer->regs[mixer->index - 0x10] = (val & 0xee);
+ break;
+
+ case 0x00:
+ case 0x04:
+ case 0x0a:
+ case 0x0c:
+ case 0x0e:
+ case 0x2e:
+ break;
+
+ default:
+ //sb_log("ess: Unknown register WRITE: %02X\t%02X\n", mixer->index, mixer->regs[mixer->index]);
+ break;
+ }
+ }
+
+ mixer->voice_l = sb_att_4dbstep_3bits[(mixer->regs[0x04] >> 5) & 0x7] / 32768.0;
+ mixer->voice_r = sb_att_4dbstep_3bits[(mixer->regs[0x04] >> 1) & 0x7] / 32768.0;
+ mixer->master_l = sb_att_4dbstep_3bits[(mixer->regs[0x22] >> 5) & 0x7] / 32768.0;
+ mixer->master_r = sb_att_4dbstep_3bits[(mixer->regs[0x22] >> 1) & 0x7] / 32768.0;
+ mixer->fm_l = sb_att_4dbstep_3bits[(mixer->regs[0x26] >> 5) & 0x7] / 32768.0;
+ mixer->fm_r = sb_att_4dbstep_3bits[(mixer->regs[0x26] >> 1) & 0x7] / 32768.0;
+ mixer->cd_l = sb_att_4dbstep_3bits[(mixer->regs[0x28] >> 5) & 0x7] / 32768.0;
+ mixer->cd_r = sb_att_4dbstep_3bits[(mixer->regs[0x28] >> 1) & 0x7] / 32768.0;
+ mixer->line_l = sb_att_4dbstep_3bits[(mixer->regs[0x2e] >> 5) & 0x7] / 32768.0;
+ mixer->line_r = sb_att_4dbstep_3bits[(mixer->regs[0x2e] >> 1) & 0x7] / 32768.0;
+
+ mixer->mic = sb_att_7dbstep_2bits[(mixer->regs[0x0a] >> 1) & 0x3] / 32768.0;
+
+ mixer->output_filter = !(mixer->regs[0xe] & 0x20);
+ mixer->input_filter = !(mixer->regs[0xc] & 0x20);
+ mixer->in_filter_freq = ((mixer->regs[0xc] & 0x8) == 0) ? 3200 : 8800;
+ mixer->stereo = mixer->regs[0xe] & 2;
+ if (mixer->index == 0xe)
+ sb_dsp_set_stereo(&ess->dsp, val & 2);
+
+ switch (mixer->regs[0xc] & 6) {
+ case 2:
+ mixer->input_selector = INPUT_CD_L | INPUT_CD_R;
+ break;
+ case 6:
+ mixer->input_selector = INPUT_LINE_L | INPUT_LINE_R;
+ break;
+ default:
+ mixer->input_selector = INPUT_MIC;
+ break;
+ }
+
+ /* TODO: pcspeaker volume? Or is it not worth? */
+ }
+}
+
+uint8_t
+ess_mixer_read(uint16_t addr, void *priv)
+{
+ const ess_t *ess = (ess_t *) priv;
+ const ess_mixer_t *mixer = &ess->mixer_sbpro;
+
+ if (!(addr & 1))
+ return mixer->index;
+
+ switch (mixer->index) {
+ case 0x00:
+ case 0x04:
+ case 0x0a:
+ case 0x0c:
+ case 0x0e:
+ case 0x22:
+ case 0x26:
+ case 0x28:
+ case 0x2e:
+ case 0x02:
+ case 0x06:
+ case 0x30:
+ case 0x32:
+ case 0x36:
+ case 0x38:
+ return mixer->regs[mixer->index];
+
+ default:
+ //sb_log("ess: Unknown register READ: %02X\t%02X\n", mixer->index, mixer->regs[mixer->index]);
+ break;
+ }
+
+ return 0xff;
+}
+
+void
+ess_mixer_reset(ess_t *ess)
+{
+ ess_mixer_write(4, 0, ess);
+ ess_mixer_write(5, 0, ess);
+}
\ No newline at end of file
diff --git a/src/sound/snd_sb_dsp.c b/src/sound/snd_sb_dsp.c
index 05b6abe431..29b065477c 100644
--- a/src/sound/snd_sb_dsp.c
+++ b/src/sound/snd_sb_dsp.c
@@ -6,6 +6,7 @@
#define _USE_MATH_DEFINES
#include
+#include
#include
#include
#include
@@ -418,30 +419,65 @@ sb_start_dma_i(sb_dsp_t *dsp, int dma8, int autoinit, uint8_t format, int len)
memset(dsp->record_buffer, 0, sizeof(dsp->record_buffer));
}
+
+static unsigned int sb_ess_get_dma_len(sb_dsp_t *dsp)
+{
+ 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;
+}
+
void
-sb_start_dma_ess(sb_dsp_t* dsp, int dma8, int autoinit, uint8_t format, int len)
+sb_start_dma_ess(sb_dsp_t* dsp)
{
+ uint8_t real_format = 0;
+ uint32_t len = !(ESSreg(0xB7) & 4) ? dsp->sb_8_length : dsp->sb_16_length;
+
+ if (!dsp->ess_reload_len) {
+ len = sb_ess_get_dma_len(dsp);
+ }
+
if (IS_ESS(dsp)) {
dma_set_drq(dsp->sb_8_dmanum, 0);
dma_set_drq(dsp->sb_16_8_dmanum, 0);
}
- sb_start_dma(dsp, dma8, autoinit, format, len);
+ real_format |= !!(ESSreg(0xB7) & 0x20) ? 0x10 : 0;
+ real_format |= !!(ESSreg(0xB7) & 0x8) ? 0x20 : 0;
+ if (!!(ESSreg(0xB8) & 8))
+ sb_start_dma_i(dsp, !(ESSreg(0xB7) & 4), (ESSreg(0xB8) >> 2) & 1, real_format, sb_ess_get_dma_len(dsp));
+ else
+ sb_start_dma(dsp, !(ESSreg(0xB7) & 4), (ESSreg(0xB8) >> 2) & 1, real_format, sb_ess_get_dma_len(dsp));
dsp->ess_playback_mode = 1;
dma_set_drq(dsp->sb_8_dmanum, 1);
dma_set_drq(dsp->sb_16_8_dmanum, 1);
}
void
-sb_start_dma_ess_i(sb_dsp_t* dsp, int dma8, int autoinit, uint8_t format, int len)
+sb_stop_dma_ess(sb_dsp_t* dsp)
{
- if (IS_ESS(dsp)) {
- dma_set_drq(dsp->sb_8_dmanum, 0);
- dma_set_drq(dsp->sb_16_8_dmanum, 0);
+ dsp->sb_8_enable = dsp->sb_16_enable = 0;
+ dma_set_drq(dsp->sb_16_8_dmanum, 0);
+ dma_set_drq(dsp->sb_8_dmanum, 0);
+}
+
+static void sb_ess_update_dma_status(sb_dsp_t* dsp)
+{
+ bool dma_en = (ESSreg(0xB8) & 1)?true:false;
+
+ // if the DRQ is disabled, do not start
+ if (!(ESSreg(0xB2) & 0x40))
+ dma_en = false;
+
+ if (dma_en) {
+ if (!dsp->sb_8_enable && !dsp->sb_16_enable) sb_start_dma_ess(dsp);
+ }
+ else {
+ if (dsp->sb_8_enable || dsp->sb_16_enable) sb_stop_dma_ess(dsp);
}
- sb_start_dma_i(dsp, dma8, autoinit, format, len);
- dsp->ess_playback_mode = 1;
- dma_set_drq(dsp->sb_8_dmanum, 1);
- dma_set_drq(dsp->sb_16_8_dmanum, 1);
}
int
@@ -598,17 +634,6 @@ static void sb_ess_update_filter_freq(sb_dsp_t *dsp)
ESSreg(0xA2) = 256 - (7160000 / (freq * 82));
}
-static unsigned int sb_ess_get_dma_len(sb_dsp_t *dsp)
-{
- 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 uint8_t sb_ess_read_reg(sb_dsp_t *dsp, uint8_t reg)
{
switch (reg) {
@@ -619,6 +644,120 @@ static uint8_t sb_ess_read_reg(sb_dsp_t *dsp, uint8_t reg)
return 0xFF;
}
+static void sb_ess_write_reg(sb_dsp_t *dsp, uint8_t reg, uint8_t data)
+{
+ uint8_t chg = 0x00;
+ sb_dsp_log("ESS register write reg=%02xh val=%02xh\n",reg,data);
+
+ switch (reg) {
+ case 0xA1: /* Extended Mode Sample Rate Generator */
+ {
+ ESSreg(reg) = data;
+ if (data & 0x80)
+ dsp->sb_freq = 795500UL / (256ul - data);
+ else
+ dsp->sb_freq = 397700UL / (128ul - data);
+
+ if (dsp->sb_16_enable || dsp->sb_8_enable) {
+ sb_stop_dma_ess(dsp);
+ sb_start_dma_ess(dsp);
+ }
+ break;
+ }
+ case 0xA2: /* Filter divider (effectively, a hardware lowpass filter under S/W control) */
+ ESSreg(reg) = data;
+ break;
+
+ case 0xA4: /* DMA Transfer Count Reload (low) */
+ case 0xA5: /* DMA Transfer Count Reload (high) */
+ ESSreg(reg) = data;
+ if (dsp->sb_16_length == 0 || dsp->sb_8_length == 0)
+ dsp->ess_reload_len = 1;
+ 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 (dsp->sb_16_enable || dsp->sb_8_enable) {
+ sb_stop_dma_ess(dsp);
+ sb_start_dma_ess(dsp);
+ }
+ }
+ 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) sb_ess_update_dma_status(dsp);
+ 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;
+ if (chg & 0x0C) {
+ if (dsp->sb_16_enable || dsp->sb_8_enable) {
+ sb_stop_dma_ess(dsp);
+ sb_start_dma_ess(dsp);
+ }
+ }
+ 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;
+
+ if (chg & 1)
+ dsp->ess_reload_len = 1;
+
+ if (chg & 0xB) {
+ if (chg & 0xA) sb_stop_dma_ess(dsp); /* changing capture/playback direction? stop DMA to reinit */
+ sb_ess_update_dma_status(dsp);
+ }
+ 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;
+
+ default:
+ break;
+ }
+}
+
void
sb_exec_command(sb_dsp_t *dsp)
{
@@ -632,11 +771,17 @@ sb_exec_command(sb_dsp_t *dsp)
if (dsp->sb_type >= SB16)
dsp->sb_8051_ram[0x20] = dsp->sb_command;
- if (IS_ESS(dsp)) {
- if (dsp->sb_command == 0xC6 || dsp->sb_command == 0xC7){
+ if (IS_ESS(dsp) && dsp->sb_command >= 0xA0 && dsp->sb_command <= 0xCF) {
+ if (dsp->sb_command == 0xC6 || dsp->sb_command == 0xC7) {
dsp->ess_extended_mode = !!(dsp->sb_command == 0xC6);
return;
}
+ if (dsp->sb_command == 0xC0) {
+ sb_add_data(dsp, sb_ess_read_reg(dsp, dsp->sb_data[0]));
+ } else if (dsp->sb_command < 0xC0 && dsp->ess_extended_mode) {
+ sb_ess_write_reg(dsp, dsp->sb_command, dsp->sb_data[0]);
+ }
+ return;
}
switch (dsp->sb_command) {
@@ -1062,12 +1207,29 @@ sb_exec_command(sb_dsp_t *dsp)
while (sb16_copyright[c])
sb_add_data(dsp, sb16_copyright[c++]);
sb_add_data(dsp, 0);
+ } else if (IS_ESS(dsp)) {
+ sb_add_data(dsp, 0);
}
break;
case 0xE4: /* Write test register */
dsp->sb_test = dsp->sb_data[0];
break;
- case 0xE7: /* ???? */
+ case 0xE7: /* ???? */ /* ESS detect/read config on ESS cards */
+ if (IS_ESS(dsp)) {
+ switch (dsp->sb_subtype) {
+ default:
+ break;
+ case SB_SUBTYPE_ESS_ES688:
+ sb_add_data(dsp, 0x68);
+ sb_add_data(dsp, 0x80 | 0x04);
+ break;
+ case SB_SUBTYPE_ESS_ES1688:
+ // Determined via Windows driver debugging.
+ sb_add_data(dsp, 0x68);
+ sb_add_data(dsp, 0x80 | 0x09);
+ break;
+ }
+ }
break;
case 0xE8: /* Read test register */
sb_add_data(dsp, dsp->sb_test);
@@ -1170,6 +1332,13 @@ sb_write(uint16_t a, uint8_t v, void *priv)
else if (dsp->sb_command == 0x08 && dsp->sb_data_stat == 1 && dsp->sb_data[0] == 0x07)
sb_commands[dsp->sb_command] = 2;
}
+ if (IS_ESS(dsp) && dsp->sb_command >= 0xA0 && dsp->sb_command <= 0xCF) {
+ if (dsp->sb_command <= 0xC0) {
+ sb_commands[dsp->sb_command] = 1;
+ } else {
+ sb_commands[dsp->sb_command] = 0;
+ }
+ }
}
if (dsp->sb_data_stat == sb_commands[dsp->sb_command] || sb_commands[dsp->sb_command] == -1) {
sb_exec_command(dsp);