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);