From ad18e7bd8658f866b5f5f059bf6d02603564c56c Mon Sep 17 00:00:00 2001 From: win2kgamer <47463859+win2kgamer@users.noreply.github.com> Date: Sun, 23 Nov 2025 12:01:23 -0600 Subject: [PATCH 1/4] Add OPTi 930 WSS codec type --- src/include/86box/snd_ad1848.h | 12 +++++++----- src/sound/snd_ad1848.c | 14 ++++++++++---- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/include/86box/snd_ad1848.h b/src/include/86box/snd_ad1848.h index 99525807c..1ff5bc1d7 100644 --- a/src/include/86box/snd_ad1848.h +++ b/src/include/86box/snd_ad1848.h @@ -22,11 +22,12 @@ enum { AD1848_TYPE_DEFAULT = 0, AD1848_TYPE_CS4248 = 1, - AD1848_TYPE_CS4231 = 2, - AD1848_TYPE_CS4232 = 3, - AD1848_TYPE_CS4236 = 4, - AD1848_TYPE_CS4236B = 5, - AD1848_TYPE_CS4235 = 6 + AD1848_TYPE_OPTI930 = 2, + AD1848_TYPE_CS4231 = 3, + AD1848_TYPE_CS4232 = 4, + AD1848_TYPE_CS4236 = 5, + AD1848_TYPE_CS4236B = 6, + AD1848_TYPE_CS4235 = 7 }; enum { @@ -44,6 +45,7 @@ typedef struct ad1848_t { uint8_t regs[32]; uint8_t xregs[32]; uint8_t status; /* 16 original registers + 16 CS4231A extensions + 32 CS4236 extensions */ + uint8_t opti930_mode2; int count; uint8_t trd; diff --git a/src/sound/snd_ad1848.c b/src/sound/snd_ad1848.c index 8f120b157..44e328842 100644 --- a/src/sound/snd_ad1848.c +++ b/src/sound/snd_ad1848.c @@ -300,7 +300,7 @@ ad1848_write(uint16_t addr, uint8_t val, void *priv) switch (addr & 3) { case 0: /* Index */ - if ((ad1848->regs[12] & 0x40) && (ad1848->type >= AD1848_TYPE_CS4231)) + if (((ad1848->regs[12] & 0x40) && (ad1848->type >= AD1848_TYPE_CS4231)) || ((ad1848->type == AD1848_TYPE_OPTI930) && (ad1848->opti930_mode2))) ad1848->index = val & 0x1f; /* cs4231a extended mode enabled */ else ad1848->index = val & 0x0f; /* ad1848/cs4248 mode TODO: some variants/clones DO NOT mirror, just ignore the writes? */ @@ -816,7 +816,7 @@ ad1848_set_cd_audio_channel(void *priv, int channel) { ad1848_t *ad1848 = (ad1848_t *) priv; - const int max_channel = (ad1848->type >= AD1848_TYPE_CS4231) ? 31 : 15; + const int max_channel = (ad1848->type >= AD1848_TYPE_CS4231) ? 31 : (ad1848->type == AD1848_TYPE_OPTI930) ? 19 : 15; if (channel > max_channel) channel = max_channel; @@ -839,7 +839,7 @@ ad1848_filter_channel(void *priv, int channel, double *out_l, double *out_r) { const ad1848_t *ad1848 = (ad1848_t *) priv; - const int max_channel = (ad1848->type >= AD1848_TYPE_CS4231) ? 31 : 15; + const int max_channel = (ad1848->type >= AD1848_TYPE_CS4231) ? 31 : (ad1848->type == AD1848_TYPE_OPTI930) ? 19 : 15; if (channel > max_channel) channel = max_channel; @@ -880,7 +880,13 @@ ad1848_init(ad1848_t *ad1848, uint8_t type) ad1848->regs[13] = 0; ad1848->regs[14] = ad1848->regs[15] = 0; - if (type == AD1848_TYPE_CS4231) { + if (type == AD1848_TYPE_OPTI930) { + ad1848->regs[18] = ad1848->regs[19] = 0x88; /* LINE volume */ + ad1848->regs[20] = ad1848->regs[21] = 0x84; /* OPTi 930 MIC volume */ + ad1848->regs[22] = ad1848->regs[23] = 0x84; /* OPTi 930 master volume */ + ad1848->regs[24] = 0; + ad1848->regs[25] = 0; + } else if (type == AD1848_TYPE_CS4231) { ad1848->regs[16] = ad1848->regs[17] = 0; ad1848->regs[18] = ad1848->regs[19] = 0x88; ad1848->regs[22] = 0x80; From c81b44fc36edd4bd664cc5e573c0f4545ca8461d Mon Sep 17 00:00:00 2001 From: win2kgamer <47463859+win2kgamer@users.noreply.github.com> Date: Sun, 23 Nov 2025 12:11:20 -0600 Subject: [PATCH 2/4] Add basic logging to the OPTi sound code --- src/sound/snd_optimc.c | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/sound/snd_optimc.c b/src/sound/snd_optimc.c index c6f25aadf..7a1742f5d 100644 --- a/src/sound/snd_optimc.c +++ b/src/sound/snd_optimc.c @@ -10,9 +10,11 @@ * * Authors: Cacodemon345 * Eluan Costa Miranda + * win2kgamer * * Copyright 2022 Cacodemon345. * Copyright 2020 Eluan Costa Miranda. + * Copyright 2025 win2kgamer */ #include #include @@ -36,6 +38,24 @@ #include <86box/mem.h> #include <86box/rom.h> #include <86box/plat_unused.h> +#include <86box/log.h> + +#ifdef ENABLE_OPTIMC_LOG +int optimc_do_log = ENABLE_OPTIMC_LOG; + +static void +optimc_log(void *priv, const char *fmt, ...) +{ + if (optimc_do_log) { + va_list ap; + va_start(ap, fmt); + log_out(priv, fmt, ap); + va_end(ap); + } +} +#else +# define optimc_log(fmt, ...) +#endif static int optimc_wss_dma[4] = { 0, 0, 1, 3 }; static int optimc_wss_irq[8] = { 5, 7, 9, 10, 11, 12, 14, 15 }; @@ -72,6 +92,8 @@ typedef struct optimc_t { sb_t *sb; uint8_t regs[6]; + + void * log; /* New logging system */ } optimc_t, opti_82c929a_t; static void @@ -94,6 +116,7 @@ optimc_wss_read(UNUSED(uint16_t addr), void *priv) if (!(optimc->regs[4] & 0x10) && optimc->cur_mode == 0) return 0xFF; + optimc_log(optimc->log, "OPTi WSS Read: val = %02X\n", ((optimc->wss_config & 0x40) | 4)); return 4 | (optimc->wss_config & 0x40); } @@ -102,6 +125,7 @@ optimc_wss_write(UNUSED(uint16_t addr), uint8_t val, void *priv) { optimc_t *optimc = (optimc_t *) priv; + optimc_log(optimc->log, "OPTi WSS Write: val = %02X\n", val); if (!(optimc->regs[4] & 0x10) && optimc->cur_mode == 0) return; optimc->wss_config = val; @@ -285,6 +309,7 @@ optimc_reg_write(uint16_t addr, uint8_t val, void *priv) break; } } + optimc_log(optimc->log, "OPTi929 Write: idx = %02X, val = %02X\n", idx, val); if (optimc->reg_enabled) optimc->reg_enabled = 0; if ((addr == 0xF8F) && ((val == optimc->type) || (val == 0x00))) { @@ -342,6 +367,7 @@ optimc_reg_read(uint16_t addr, void *priv) } optimc->reg_enabled = 0; } + optimc_log(optimc->log, "OPTi929 Read: addr = %02X, val = %02X\n", addr, temp); return temp; } @@ -369,6 +395,8 @@ optimc_init(const device_t *info) optimc->regs[4] = 0x3F; optimc->regs[5] = 0x83; + optimc->log = log_open("OPTIMC"); + optimc->gameport = gameport_add(&gameport_pnp_device); gameport_remap(optimc->gameport, (optimc->regs[0] & 0x1) ? 0x00 : 0x200); @@ -431,6 +459,11 @@ optimc_close(void *priv) { optimc_t *optimc = (optimc_t *) priv; + if (optimc->log != NULL) { + log_close(optimc->log); + optimc->log = NULL; + } + sb_close(optimc->sb); free(optimc->mpu); free(priv); From 0ca1a201d79891b70d0db3f6ad0f6dd3fbf43b77 Mon Sep 17 00:00:00 2001 From: win2kgamer <47463859+win2kgamer@users.noreply.github.com> Date: Sun, 23 Nov 2025 12:34:00 -0600 Subject: [PATCH 3/4] Add the OPTi 82c930 sound controller --- src/include/86box/sound.h | 3 + src/sound/snd_optimc.c | 457 ++++++++++++++++++++++++++++++++++++-- src/sound/sound.c | 1 + 3 files changed, 443 insertions(+), 18 deletions(-) diff --git a/src/include/86box/sound.h b/src/include/86box/sound.h index ff096e0be..6d4767ed0 100644 --- a/src/include/86box/sound.h +++ b/src/include/86box/sound.h @@ -222,6 +222,9 @@ extern const device_t entertainer_device; /* Mindscape Music Board */ extern const device_t mmb_device; +/* OPTi 82c930 */ +extern const device_t opti_82c930_device; + /* Pro Audio Spectrum Plus, 16, and 16D */ extern const device_t pasplus_device; extern const device_t pas16_device; diff --git a/src/sound/snd_optimc.c b/src/sound/snd_optimc.c index 7a1742f5d..6734b56cb 100644 --- a/src/sound/snd_optimc.c +++ b/src/sound/snd_optimc.c @@ -6,7 +6,7 @@ * * This file is part of the 86Box distribution. * - * OPTi MediaCHIPS 82C929A (also known as OPTi MAD16 Pro) audio controller emulation. + * OPTi MediaCHIPS 82C929A/82C930 (also known as OPTi MAD16 Pro) audio controller emulation. * * Authors: Cacodemon345 * Eluan Costa Miranda @@ -59,6 +59,13 @@ optimc_log(void *priv, const char *fmt, ...) static int optimc_wss_dma[4] = { 0, 0, 1, 3 }; static int optimc_wss_irq[8] = { 5, 7, 9, 10, 11, 12, 14, 15 }; +static int opti930_wss_irq[8] = { 0, 7, 9, 10, 11, 5, 0, 0 }; +static double opti930_vols_5bits[32]; + +enum optimc_types { + OPTI_929 = 0xE3, + OPTI_930 = 0xE4, +}; enum optimc_local_flags { OPTIMC_CS4231 = 0x100, @@ -71,10 +78,13 @@ typedef struct optimc_t { uint8_t wss_config; uint8_t reg_enabled; + uint8_t passwd_enabled; + uint8_t index; uint16_t cur_addr; uint16_t cur_wss_addr; uint16_t cur_mpu401_addr; + uint16_t cur_opti930_addr; /* OPTi 930 relocatable index/data registers */ int cur_irq; int cur_dma; @@ -91,11 +101,60 @@ typedef struct optimc_t { mpu_t *mpu; sb_t *sb; - uint8_t regs[6]; + uint8_t regs[12]; + uint8_t opti930_mcbase; + uint8_t lastpw; + double master_l; + double master_r; void * log; /* New logging system */ } optimc_t, opti_82c929a_t; +static void +opti930_update_mastervol(void *priv) +{ + optimc_t *optimc = (optimc_t *) priv; + /* Master volume attenuation */ + if (optimc->ad1848.regs[22] & 0x80) + optimc->master_l = 0; + else + optimc->master_l = opti930_vols_5bits[optimc->ad1848.regs[22] & 0x1F] / 65536.0; + + if (optimc->ad1848.regs[23] & 0x80) + optimc->master_r = 0; + else + optimc->master_r = opti930_vols_5bits[optimc->ad1848.regs[23] & 0x1F] / 65536.0; +} + +void +opti930_filter_cd_audio(int channel, double *buffer, void *priv) +{ + optimc_t *optimc = (optimc_t *) priv; + + opti930_update_mastervol(optimc); + + const double cd_vol = channel ? optimc->ad1848.cd_vol_r : optimc->ad1848.cd_vol_l; + double master = channel ? optimc->master_r : optimc->master_l; + double c = ((*buffer * cd_vol / 3.0) * master) / 65536.0; + + *buffer = c; +} + +static void +opti930_filter_opl(void *priv, double *out_l, double *out_r) +{ + optimc_t *optimc = (optimc_t *) priv; + + if (optimc->cur_wss_enabled) { + opti930_update_mastervol(optimc); + *out_l /= optimc->sb->mixer_sbpro.fm_l; + *out_r /= optimc->sb->mixer_sbpro.fm_r; + *out_l *= optimc->master_l; + *out_r *= optimc->master_r; + ad1848_filter_channel((void *) &optimc->ad1848, AD1848_AUX2, out_l, out_r); + } +} + static void optimc_filter_opl(void *priv, double *out_l, double *out_r) { @@ -113,7 +172,7 @@ optimc_wss_read(UNUSED(uint16_t addr), void *priv) { const optimc_t *optimc = (optimc_t *) priv; - if (!(optimc->regs[4] & 0x10) && optimc->cur_mode == 0) + if (optimc->type == OPTI_929 && !(optimc->regs[4] & 0x10) && optimc->cur_mode == 0) return 0xFF; optimc_log(optimc->log, "OPTi WSS Read: val = %02X\n", ((optimc->wss_config & 0x40) | 4)); @@ -126,11 +185,42 @@ optimc_wss_write(UNUSED(uint16_t addr), uint8_t val, void *priv) optimc_t *optimc = (optimc_t *) priv; optimc_log(optimc->log, "OPTi WSS Write: val = %02X\n", val); - if (!(optimc->regs[4] & 0x10) && optimc->cur_mode == 0) + if (optimc->type == OPTI_929 && !(optimc->regs[4] & 0x10) && optimc->cur_mode == 0) return; optimc->wss_config = val; ad1848_setdma(&optimc->ad1848, optimc_wss_dma[val & 3]); - ad1848_setirq(&optimc->ad1848, optimc_wss_irq[(val >> 3) & 7]); + if (optimc->type == OPTI_929) + ad1848_setirq(&optimc->ad1848, optimc_wss_irq[(val >> 3) & 7]); + else + ad1848_setirq(&optimc->ad1848, opti930_wss_irq[(val >> 3) & 7]); +} + +static void +opti930_get_buffer(int32_t *buffer, int len, void *priv) +{ + optimc_t *optimc = (optimc_t *) priv; + + if (optimc->regs[3] & 0x4) + return; + + /* wss part */ + opti930_update_mastervol(optimc); + ad1848_update(&optimc->ad1848); + for (int c = 0; c < len * 2; c++) { + double out_l = 0.0; + double out_r = 0.0; + + out_l = (optimc->ad1848.buffer[c] * optimc->master_l); + out_r = (optimc->ad1848.buffer[c + 1] * optimc->master_r); + + buffer[c] += (int32_t) out_l; + buffer[c + 1] += (int32_t) out_r; + } + + optimc->ad1848.pos = 0; + + /* sbprov2 part */ + sb_get_buffer_sbpro(buffer, len, optimc->sb); } static void @@ -169,6 +259,198 @@ optimc_add_opl(optimc_t *optimc) io_sethandler(0x0388, 0x0004, optimc->sb->opl.read, NULL, NULL, optimc->sb->opl.write, NULL, NULL, optimc->sb->opl.priv); } +static void +opti930_reg_write(uint16_t addr, uint8_t val, void *priv) +{ + optimc_t *optimc = (optimc_t *) priv; + uint16_t idx = optimc->index; + uint8_t old = optimc->regs[idx]; + + if ((addr == optimc->cur_opti930_addr) && (optimc->reg_enabled || !optimc->passwd_enabled)) { + optimc_log(optimc->log, "OPTi930 Index Write: val = %02X\n", val); + optimc->index = (val - 1); + } + if ((addr == optimc->cur_opti930_addr +1) && (optimc->reg_enabled || !optimc->passwd_enabled)) { + switch (idx) { + case 0: /* MC1 */ + optimc->regs[0] = val; + if (val != old) { + /* Update SBPro address */ + io_removehandler(optimc->cur_addr + 4, 0x0002, sb_ct1345_mixer_read, NULL, NULL, sb_ct1345_mixer_write, NULL, NULL, optimc->sb); + optimc_remove_opl(optimc); + optimc->cur_addr = (val & 0x80) ? 0x240 : 0x220; + sb_dsp_setaddr(&optimc->sb->dsp, optimc->cur_addr); + optimc_add_opl(optimc); + io_sethandler(optimc->cur_addr + 4, 0x0002, sb_ct1345_mixer_read, NULL, NULL, sb_ct1345_mixer_write, NULL, NULL, optimc->sb); + /* Update WSS address */ + io_removehandler(optimc->cur_wss_addr, 0x0004, optimc_wss_read, NULL, NULL, optimc_wss_write, NULL, NULL, optimc); + io_removehandler(optimc->cur_wss_addr + 0x0004, 0x0004, ad1848_read, NULL, NULL, ad1848_write, NULL, NULL, &optimc->ad1848); + switch ((val >> 4) & 0x3) { + case 0: /* WSBase = 0x530 */ + optimc->cur_wss_addr = 0x530; + break; + case 1: /* WSBase = 0xE80 */ + optimc->cur_wss_addr = 0xE80; + break; + case 2: /* WSBase = 0xF40 */ + optimc->cur_wss_addr = 0xF40; + break; + case 3: /* WSBase = 0x604 */ + optimc->cur_wss_addr = 0x604; + break; + + default: + break; + } + io_sethandler(optimc->cur_wss_addr, 0x0004, optimc_wss_read, NULL, NULL, optimc_wss_write, NULL, NULL, optimc); + io_sethandler(optimc->cur_wss_addr + 0x0004, 0x0004, ad1848_read, NULL, NULL, ad1848_write, NULL, NULL, &optimc->ad1848); + /* Update gameport */ + gameport_remap(optimc->gameport, (optimc->regs[0] & 0x1) ? 0x00 : 0x200); + } + break; + case 1: /* MC2 */ + optimc->regs[1] = val; + break; + case 2: /* MC3 */ + optimc->regs[2] = val; + if (val != old) { + switch (val & 0x3) { + case 0: + break; + case 1: + optimc->cur_dma = 0; + break; + case 2: + optimc->cur_dma = 1; + break; + case 3: + optimc->cur_dma = 3; + break; + } + switch ((val >> 3) & 0x7) { + case 0: + break; + case 1: + optimc->cur_irq = 7; + break; + case 2: + optimc->cur_irq = 9; + break; + case 3: + optimc->cur_irq = 10; + break; + case 4: + optimc->cur_irq = 11; + break; + case 5: + default: + optimc->cur_irq = 5; + break; + } + sb_dsp_setirq(&optimc->sb->dsp, optimc->cur_irq); + sb_dsp_setdma8(&optimc->sb->dsp, optimc->cur_dma); + /* Writes here also set WSS IRQ/DMA, the DOS setup utility and NEC PowerMate V BIOS imply this */ + /* The OPTi 82c930 driver on the NEC Ready preloads requires this to function properly */ + ad1848_setdma(&optimc->ad1848, optimc_wss_dma[val & 3]); + ad1848_setirq(&optimc->ad1848, opti930_wss_irq[(val >> 3) & 7]); + } + break; + case 3: /* MC4 */ + optimc->regs[3] = val; + break; + case 4: /* MC5 */ + optimc->regs[4] = val; + /* OPTi 930 enables/disables AD1848 MODE2 from here */ + if (val & 0x20) { + optimc->ad1848.opti930_mode2 = 1; + optimc->ad1848.fmt_mask |= 0x80; + } + else { + optimc->ad1848.opti930_mode2 = 0; + optimc->ad1848.fmt_mask &= ~0x80; + } + break; + case 5: /* MC6 */ + optimc->regs[5] = val; + if (old != val) { + switch ((val >> 3) & 0x3) { + case 0: + optimc->cur_mpu401_irq = 9; + break; + case 1: + optimc->cur_mpu401_irq = 10; + break; + case 2: + optimc->cur_mpu401_irq = 5; + break; + case 3: + optimc->cur_mpu401_irq = 7; + break; + + default: + break; + } + switch ((val >> 5) & 0x3) { + case 0: + optimc->cur_mpu401_addr = 0x330; + break; + case 1: + optimc->cur_mpu401_addr = 0x320; + break; + case 2: + optimc->cur_mpu401_addr = 0x310; + break; + case 3: + optimc->cur_mpu401_addr = 0x300; + break; + + default: + break; + } + mpu401_change_addr(optimc->mpu, optimc->cur_mpu401_addr); + mpu401_setirq(optimc->mpu, optimc->cur_mpu401_irq); + optimc->cur_mode = optimc->cur_wss_enabled = !!(val & 0x02); + sound_set_cd_audio_filter(NULL, NULL); + if (optimc->cur_wss_enabled) { /* WSS */ + sound_set_cd_audio_filter(opti930_filter_cd_audio, optimc); + optimc->sb->opl_mixer = optimc; + optimc->sb->opl_mix = opti930_filter_opl; + } + else { /* SBPro */ + sound_set_cd_audio_filter(sbpro_filter_cd_audio, optimc->sb); + optimc->sb->opl_mixer = NULL; + optimc->sb->opl_mix = NULL; + } + } + break; + case 6: /* MC7 */ + optimc->regs[6] = val; + break; + case 7: /* MC8 */ + optimc->regs[7] = val; + break; + case 8: /* MC9 */ + optimc->regs[8] = val; + break; + case 9: /* MC10 */ + optimc->regs[9] = val; + break; + case 10: /* MC11, read-only */ + break; + case 11: /* MC12 */ + optimc->regs[11] = val; + break; + default: + break; + } + optimc_log(optimc->log, "OPTi930 Data Write: idx = %02X, val = %02X\n", idx, val); + } + if ((optimc->cur_opti930_addr != 0xF8E) && optimc->reg_enabled) { + optimc_log(optimc->log, "OPTi930 disable reg access on data write\n"); + optimc->reg_enabled = 0; + } +} + static void optimc_reg_write(uint16_t addr, uint8_t val, void *priv) { @@ -343,6 +625,36 @@ optimc_reg_write(uint16_t addr, uint8_t val, void *priv) } } +static uint8_t +opti930_reg_read(uint16_t addr, void *priv) +{ + optimc_t *optimc = (optimc_t *) priv; + uint16_t idx = optimc->index; + uint8_t temp = 0xFF; + + if ((addr == optimc->cur_opti930_addr) && (optimc->reg_enabled || !optimc->passwd_enabled)) + temp = optimc->index; + if ((addr == optimc->cur_opti930_addr +1) && (optimc->reg_enabled || !optimc->passwd_enabled)) { + switch (idx) { + case 0 ... 9: + temp = optimc->regs[optimc->index]; + break; + case 10: + temp = ((optimc->ad1848.regs[24] & 0x10) >> 2); + break; + case 11: + temp = optimc->regs[11]; + break; + } + } + if ((optimc->cur_opti930_addr != 0xF8E) && optimc->reg_enabled) { + optimc_log(optimc->log, "OPTi930 disable reg access on data read\n"); + optimc->reg_enabled = 0; + } + optimc_log(optimc->log, "OPTi930 Read: idx = %02X, val = %02X\n", optimc->index, temp); + return temp; +} + static uint8_t optimc_reg_read(uint16_t addr, void *priv) { @@ -371,10 +683,42 @@ optimc_reg_read(uint16_t addr, void *priv) return temp; } +static void +opti930_passwd_write(uint16_t addr, uint8_t val, void *priv) +{ + optimc_t *optimc = (optimc_t *) priv; + + optimc_log(optimc->log, "OPTi930 Password Write: val = %02X\n", val); + optimc_log(optimc->log, "OPTi930 last pw value: %02X\n", optimc->lastpw); + if ((addr == 0xF8F) && (optimc->reg_enabled)) { + optimc_log(optimc->log, "OPTi930: Removing old address handler at %04X\n", optimc->cur_opti930_addr); + io_removehandler(optimc->cur_opti930_addr, 2, opti930_reg_read, NULL, NULL, opti930_reg_write, NULL, NULL, optimc); /* Relocatable MCBase */ + optimc->opti930_mcbase = val; + optimc->cur_opti930_addr = (((val & 0x1f) << 4) + 0xE0E); + optimc->passwd_enabled = (val & 0x80) ? 0 : 1; + optimc_log(optimc->log, "OPTi930 register base now %04X\n", optimc->cur_opti930_addr); + optimc_log(optimc->log, "OPTi930 password enable: %02X\n", optimc->passwd_enabled); + io_sethandler(optimc->cur_opti930_addr, 2, opti930_reg_read, NULL, NULL, opti930_reg_write, NULL, NULL, optimc); /* Relocatable MCBase */ + optimc_log(optimc->log, "OPTi930 Disabling reg access\n"); + optimc->reg_enabled = 0; + } + if ((addr == 0xF8F) && (val == optimc->type) && !(optimc->reg_enabled)) { + optimc_log(optimc->log, "OPTi930 Enabling reg access\n"); + optimc->reg_enabled = 1; + } + if ((addr == 0xF8F) && (val != optimc->type) && (optimc->lastpw != OPTI_930)) { + optimc_log(optimc->log, "OPTi930 Disabling reg access\n"); + optimc->reg_enabled = 0; + } + optimc->lastpw = val; +} + static void * optimc_init(const device_t *info) { optimc_t *optimc = calloc(1, sizeof(optimc_t)); + uint8_t c; + double attenuation; optimc->type = info->local & 0xFF; @@ -388,19 +732,39 @@ optimc_init(const device_t *info) optimc->cur_mpu401_addr = 0x330; optimc->cur_mpu401_enabled = 1; - optimc->regs[0] = 0x00; - optimc->regs[1] = 0x03; - optimc->regs[2] = 0x00; - optimc->regs[3] = 0x00; - optimc->regs[4] = 0x3F; - optimc->regs[5] = 0x83; + if (optimc->type == OPTI_929) { + optimc->regs[0] = 0x00; + optimc->regs[1] = 0x03; + optimc->regs[2] = 0x00; + optimc->regs[3] = 0x00; + optimc->regs[4] = 0x3F; + optimc->regs[5] = 0x83; + } + if (optimc->type == OPTI_930) { + optimc->regs[0] = 0x00; + optimc->regs[1] = 0x00; + optimc->regs[2] = 0x2A; + optimc->regs[3] = 0x10; + optimc->regs[4] = 0x00; + optimc->regs[5] = 0x81; + optimc->regs[6] = 0x00; + optimc->regs[7] = 0x00; + optimc->regs[8] = 0x00; + optimc->regs[9] = 0x00; + optimc->regs[10] = 0x00; + optimc->regs[11] = 0x00; + optimc->opti930_mcbase = 0x00; /* Password enable, MCBase E0Eh */ + optimc->cur_opti930_addr = 0xE0E; + } optimc->log = log_open("OPTIMC"); optimc->gameport = gameport_add(&gameport_pnp_device); gameport_remap(optimc->gameport, (optimc->regs[0] & 0x1) ? 0x00 : 0x200); - if (info->local & 0x100) + if (optimc->type == OPTI_930) + ad1848_init(&optimc->ad1848, AD1848_TYPE_OPTI930); + else if (info->local & 0x100) ad1848_init(&optimc->ad1848, AD1848_TYPE_CS4231); else ad1848_init(&optimc->ad1848, AD1848_TYPE_DEFAULT); @@ -409,7 +773,13 @@ optimc_init(const device_t *info) ad1848_setirq(&optimc->ad1848, optimc->cur_wss_irq); ad1848_setdma(&optimc->ad1848, optimc->cur_wss_dma); - io_sethandler(0xF8D, 6, optimc_reg_read, NULL, NULL, optimc_reg_write, NULL, NULL, optimc); + if (optimc->type == OPTI_929) { + io_sethandler(0xF8D, 6, optimc_reg_read, NULL, NULL, optimc_reg_write, NULL, NULL, optimc); + } + if (optimc->type == OPTI_930) { + io_sethandler(0xF8F, 1, NULL, NULL, NULL, opti930_passwd_write, NULL, NULL, optimc); /* Fixed password reg, write-only */ + io_sethandler(optimc->cur_opti930_addr, 2, opti930_reg_read, NULL, NULL, opti930_reg_write, NULL, NULL, optimc); /* Relocatable MCBase */ + } io_sethandler(optimc->cur_wss_addr, 0x0004, optimc_wss_read, NULL, NULL, optimc_wss_write, NULL, NULL, optimc); io_sethandler(optimc->cur_wss_addr + 0x0004, 0x0004, ad1848_read, NULL, NULL, ad1848_write, NULL, NULL, &optimc->ad1848); @@ -426,8 +796,14 @@ optimc_init(const device_t *info) sb_dsp_setdma8(&optimc->sb->dsp, optimc->cur_dma); sb_ct1345_mixer_reset(optimc->sb); - optimc->sb->opl_mixer = optimc; - optimc->sb->opl_mix = optimc_filter_opl; + if (optimc->type == OPTI_930) { + optimc->sb->opl_mixer = optimc; + optimc->sb->opl_mix = opti930_filter_opl; + } + else { + optimc->sb->opl_mixer = optimc; + optimc->sb->opl_mix = optimc_filter_opl; + } fm_driver_get(optimc->fm_type, &optimc->sb->opl); io_sethandler(optimc->cur_addr + 0, 0x0004, optimc->sb->opl.read, NULL, NULL, optimc->sb->opl.write, NULL, NULL, optimc->sb->opl.priv); @@ -438,11 +814,17 @@ optimc_init(const device_t *info) io_sethandler(optimc->cur_addr + 4, 0x0002, sb_ct1345_mixer_read, NULL, NULL, sb_ct1345_mixer_write, NULL, NULL, optimc->sb); - sound_add_handler(optimc_get_buffer, optimc); + if (optimc->type == OPTI_930) + sound_add_handler(opti930_get_buffer, optimc); + else + sound_add_handler(optimc_get_buffer, optimc); if (optimc->fm_type == FM_YMF278B) wavetable_add_handler(sb_get_music_buffer_sbpro, optimc->sb); else music_add_handler(sb_get_music_buffer_sbpro, optimc->sb); + if (optimc->type == OPTI_930) + ad1848_set_cd_audio_channel(&optimc->ad1848, AD1848_AUX1); + sound_set_cd_audio_filter(NULL, NULL); /* Seems to be necessary for the filter below to apply */ sound_set_cd_audio_filter(sbpro_filter_cd_audio, optimc->sb); /* CD audio filter for the default context */ optimc->mpu = (mpu_t *) calloc(1, sizeof(mpu_t)); @@ -451,6 +833,30 @@ optimc_init(const device_t *info) if (device_get_config_int("receive_input")) midi_in_handler(1, sb_dsp_input_msg, sb_dsp_input_sysex, &optimc->sb->dsp); + /* OPTi 930 DOS sound test utility starts DMA playback without setting a time constant likely making */ + /* an assumption about the power-on state of the OPTi 930's SBPro DSP, set the power-on default time */ + /* constant for 22KHz Mono so the sound test utility passes the SBPro test */ + if (optimc->type == OPTI_930) + optimc->sb->dsp.sb_timeo = 211; + + for (c = 0; c < 32; c++) { + attenuation = 0.0; + if (c & 0x01) + attenuation -= 1.5; + if (c & 0x02) + attenuation -= 3.0; + if (c & 0x04) + attenuation -= 6.0; + if (c & 0x08) + attenuation -= 12.0; + if (c & 0x10) + attenuation -= 24.0; + + attenuation = pow(10, attenuation / 10); + + opti930_vols_5bits[c] = (attenuation * 65536); + } + return optimc; } @@ -516,7 +922,7 @@ const device_t acermagic_s20_device = { .name = "AcerMagic S20", .internal_name = "acermagic_s20", .flags = DEVICE_ISA16, - .local = 0xE3 | OPTIMC_CS4231, + .local = OPTI_929 | OPTIMC_CS4231, .init = optimc_init, .close = optimc_close, .reset = NULL, @@ -530,7 +936,7 @@ const device_t mirosound_pcm10_device = { .name = "miroSOUND PCM10", .internal_name = "mirosound_pcm10", .flags = DEVICE_ISA16, - .local = 0xE3 | OPTIMC_OPL4, + .local = OPTI_929 | OPTIMC_OPL4, .init = optimc_init, .close = optimc_close, .reset = NULL, @@ -539,3 +945,18 @@ const device_t mirosound_pcm10_device = { .force_redraw = NULL, .config = optimc_config }; + +const device_t opti_82c930_device = { + .name = "OPTi 82c930", + .internal_name = "opti_82c930", + .flags = DEVICE_ISA16, + .local = OPTI_930 | OPTIMC_CS4231, + .init = optimc_init, + .close = optimc_close, + .reset = NULL, + .available = NULL, + .speed_changed = optimc_speed_changed, + .force_redraw = NULL, + .config = optimc_config +}; + diff --git a/src/sound/sound.c b/src/sound/sound.c index 4d9675441..d9db398a4 100644 --- a/src/sound/sound.c +++ b/src/sound/sound.c @@ -148,6 +148,7 @@ static const SOUND_CARD sound_cards[] = { { &gus_max_device }, { &gus_ace_device }, { &mirosound_pcm10_device }, + { &opti_82c930_device }, { &pas16_device }, { &pas16d_device }, { &sb_16_device }, From 7e958002393dff3a55045fc9be12d12a89723420 Mon Sep 17 00:00:00 2001 From: win2kgamer <47463859+win2kgamer@users.noreply.github.com> Date: Sun, 23 Nov 2025 16:54:09 -0600 Subject: [PATCH 4/4] Give the NEC PowerMate V its (optional) onboard OPTi 82c930 audio --- src/machine/m_at_socket5.c | 3 +++ src/machine/machine_table.c | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/machine/m_at_socket5.c b/src/machine/m_at_socket5.c index a049600e3..adfdfbacd 100644 --- a/src/machine/m_at_socket5.c +++ b/src/machine/m_at_socket5.c @@ -871,6 +871,9 @@ machine_at_powermatev_init(const machine_t *model) pci_register_slot(0x11, PCI_CARD_NORMAL, 1, 2, 3, 4); pci_register_slot(0x13, PCI_CARD_NORMAL, 2, 3, 4, 1); + if (sound_card_current[0] == SOUND_INTERNAL) + machine_snd = device_add(machine_get_snd_device(machine)); + device_add_params(machine_get_kbc_device(machine), (void *) model->kbc_params); device_add(&i430fx_device); device_add(&piix_device); diff --git a/src/machine/machine_table.c b/src/machine/machine_table.c index ca1da6372..89f231ae5 100644 --- a/src/machine/machine_table.c +++ b/src/machine/machine_table.c @@ -12634,7 +12634,7 @@ const machine_t machines[] = { .max_multi = 2.0 }, .bus_flags = MACHINE_PS2_PCI, - .flags = MACHINE_IDE_DUAL | MACHINE_APM, + .flags = MACHINE_IDE_DUAL | MACHINE_APM | MACHINE_SOUND | MACHINE_GAMEPORT, .ram = { .min = 8192, .max = 131072, @@ -12653,7 +12653,7 @@ const machine_t machines[] = { .fdc_device = NULL, .sio_device = NULL, .vid_device = NULL, - .snd_device = NULL, + .snd_device = &opti_82c930_device, .net_device = NULL }, /* Has AMIKey Z(!) KBC firmware. */