/* * 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 #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]; uint8_t ess_id_str[256]; uint8_t ess_id_str_pos : 2; } ess_mixer_t; typedef struct ess_t { uint8_t mixer_enabled; fm_drv_t opl; sb_dsp_t dsp; ess_mixer_t mixer_sbpro; mpu_t *mpu; void *gameport; 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); } static double ess_mixer_get_vol_4bit(uint8_t vol) { return (48.0 + (20.0 * log((vol & 0xF) / 15.0))) / 48.0; } 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; if (val == 0x40) mixer->ess_id_str_pos = 0; } 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; /* Initialize ESS regs. */ mixer->regs[0x14] = mixer->regs[0x32] = 0x88; mixer->regs[0x36] = 0x88; mixer->regs[0x38] = 0x00; mixer->regs[0x3a] = 0x00; mixer->regs[0x3e] = 0x00; sb_dsp_set_stereo(&ess->dsp, mixer->regs[0x0e] & 2); } else { mixer->regs[mixer->index] = val; pclog("ess: Register WRITE: %02X\t%02X\n", mixer->index, mixer->regs[mixer->index]); 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 0x14: mixer->regs[0x4] = val & 0xee; break; case 0x22: case 0x26: case 0x28: case 0x2E: mixer->regs[mixer->index - 0x20] = (val & 0xe); mixer->regs[mixer->index + 0x10] = val; break; /* More compatibility: SoundBlaster Pro selects register 020h for 030h, 022h for 032h, 026h for 036h, and 028h for 038h. */ case 0x30: mixer->regs[mixer->index - 0x10] = (val & 0xee); break; case 0x32: case 0x36: case 0x38: case 0x3e: mixer->regs[mixer->index - 0x10] = (val & 0xee); break; case 0x00: case 0x04: case 0x0a: case 0x0c: case 0x0e: break; case 0x64: mixer->regs[mixer->index] &= ~0x8; break; case 0x40: { break; uint16_t mpu401_base_addr = 0x300 | ((mixer->regs[0x40] & 0x38) << 1); gameport_remap(ess->gameport, !(mixer->regs[0x40] & 0x2) ? 0x00 : 0x200); /* This doesn't work yet. */ /* io_removehandler(0x0388, 0x0004, ess->opl.read, NULL, NULL, ess->opl.write, NULL, NULL, ess->opl.priv); if (mixer->regs[0x40] & 1) { io_sethandler(0x0388, 0x0004, ess->opl.read, NULL, NULL, ess->opl.write, NULL, NULL, ess->opl.priv); }*/ switch ((mixer->regs[0x40] >> 5) & 7) { case 0: mpu401_change_addr(ess->mpu, 0x00); mpu401_setirq(ess->mpu, -1); break; case 1: mpu401_change_addr(ess->mpu, mpu401_base_addr); mpu401_setirq(ess->mpu, -1); break; case 2: mpu401_change_addr(ess->mpu, mpu401_base_addr); mpu401_setirq(ess->mpu, ess->dsp.sb_irqnum); break; case 3: mpu401_change_addr(ess->mpu, mpu401_base_addr); mpu401_setirq(ess->mpu, 11); break; case 4: mpu401_change_addr(ess->mpu, mpu401_base_addr); mpu401_setirq(ess->mpu, 9); break; case 5: mpu401_change_addr(ess->mpu, mpu401_base_addr); mpu401_setirq(ess->mpu, 5); break; case 6: mpu401_change_addr(ess->mpu, mpu401_base_addr); mpu401_setirq(ess->mpu, 7); break; case 7: mpu401_change_addr(ess->mpu, mpu401_base_addr); mpu401_setirq(ess->mpu, 10); break; } break; } default: pclog("ess: Unknown register WRITE: %02X\t%02X\n", mixer->index, mixer->regs[mixer->index]); break; } } mixer->voice_l = ess_mixer_get_vol_4bit(mixer->regs[0x14]); mixer->voice_r = ess_mixer_get_vol_4bit(mixer->regs[0x14] >> 4); mixer->master_l = ess_mixer_get_vol_4bit(mixer->regs[0x32]); mixer->master_r = ess_mixer_get_vol_4bit(mixer->regs[0x32] >> 4); mixer->fm_l = ess_mixer_get_vol_4bit(mixer->regs[0x36]); mixer->fm_r = ess_mixer_get_vol_4bit(mixer->regs[0x36] >> 4); mixer->cd_l = ess_mixer_get_vol_4bit(mixer->regs[0x38]); mixer->cd_r = ess_mixer_get_vol_4bit(mixer->regs[0x38] >> 4); mixer->line_l = ess_mixer_get_vol_4bit(mixer->regs[0x3e]); mixer->line_r = ess_mixer_get_vol_4bit(mixer->regs[0x3e] >> 4); 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) { ess_t *ess = (ess_t *) priv; 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 0x14: case 0x22: case 0x26: case 0x28: case 0x2e: case 0x02: case 0x06: case 0x30: case 0x32: case 0x36: case 0x38: case 0x3e: pclog("ess: Register READ: %02X\t%02X\n", mixer->index, mixer->regs[mixer->index]); return mixer->regs[mixer->index]; case 0x40: { uint8_t val = mixer->ess_id_str[mixer->ess_id_str_pos]; mixer->ess_id_str_pos++; if (mixer->ess_id_str_pos >= 4) mixer->ess_id_str_pos = 0; pclog("ess: ID READ: %02X (pos %d)\n", val, mixer->ess_id_str_pos); return val; } default: pclog("ess: Unknown register READ: %02X\t%02X\n", mixer->index, mixer->regs[mixer->index]); break; } return 0x0a; } void ess_mixer_reset(ess_t *ess) { ess_mixer_write(4, 0, ess); ess_mixer_write(5, 0, ess); } void ess_get_buffer_sbpro(int32_t *buffer, int len, void *priv) { ess_t *ess = (ess_t *) priv; const ess_mixer_t *mixer = &ess->mixer_sbpro; double out_l = 0.0; double out_r = 0.0; sb_dsp_update(&ess->dsp); for (int c = 0; c < len * 2; c += 2) { out_l = 0.0; out_r = 0.0; /* TODO: Implement the stereo switch on the mixer instead of on the dsp? */ if (mixer->output_filter) { out_l += (sb_iir(0, 0, (double) ess->dsp.buffer[c]) * mixer->voice_l) / 3.9; out_r += (sb_iir(0, 1, (double) ess->dsp.buffer[c + 1]) * mixer->voice_r) / 3.9; } else { out_l += (ess->dsp.buffer[c] * mixer->voice_l) / 3.0; out_r += (ess->dsp.buffer[c + 1] * mixer->voice_r) / 3.0; } /* TODO: recording CD, Mic with AGC or line in. Note: mic volume does not affect recording. */ out_l *= mixer->master_l; out_r *= mixer->master_r; buffer[c] += (int32_t) out_l; buffer[c + 1] += (int32_t) out_r; } ess->dsp.pos = 0; } void ess_get_music_buffer_sbpro(int32_t *buffer, int len, void *priv) { ess_t *ess = (ess_t *) priv; const ess_mixer_t *mixer = &ess->mixer_sbpro; double out_l = 0.0; double out_r = 0.0; const int32_t *opl_buf = NULL; opl_buf = ess->opl.update(ess->opl.priv); sb_dsp_update(&ess->dsp); for (int c = 0; c < len * 2; c += 2) { out_l = 0.0; out_r = 0.0; { out_l = (((double) opl_buf[c]) * mixer->fm_l) * 0.7171630859375; out_r = (((double) opl_buf[c + 1]) * mixer->fm_r) * 0.7171630859375; if (ess->opl_mix && ess->opl_mixer) ess->opl_mix(ess->opl_mixer, &out_l, &out_r); } /* TODO: recording CD, Mic with AGC or line in. Note: mic volume does not affect recording. */ out_l *= mixer->master_l; out_r *= mixer->master_r; buffer[c] += (int32_t) out_l; buffer[c + 1] += (int32_t) out_r; } ess->opl.reset_buffer(ess->opl.priv); } void ess_filter_cd_audio(int channel, double *buffer, void *priv) { const ess_t *ess = (ess_t *) priv; const ess_mixer_t *mixer = &ess->mixer_sbpro; double c; double cd = channel ? mixer->cd_r : mixer->cd_l; double master = channel ? mixer->master_r : mixer->master_l; if (mixer->output_filter) c = (sb_iir(2, channel, *buffer) * cd) / 3.9; else c = (*buffer * cd) / 3.0; *buffer = c * master; } static void * ess_1688_init(UNUSED(const device_t *info)) { /* SB Pro 2 port mappings, 220h or 240h. 2x0 to 2x3 -> FM chip (18 voices) 2x4 to 2x5 -> Mixer interface 2x6, 2xA, 2xC, 2xE -> DSP chip 2x8, 2x9, 388 and 389 FM chip (9 voices) 2x0+10 to 2x0+13 CDROM interface. */ ess_t *ess = calloc(sizeof(ess_t), 1); uint16_t addr = device_get_config_hex16("base"); fm_driver_get(FM_ESFM, &ess->opl); sb_dsp_set_real_opl(&ess->dsp, 1); sb_dsp_init(&ess->dsp, SBPRO2, SB_SUBTYPE_ESS_ES1688, ess); sb_dsp_setaddr(&ess->dsp, addr); sb_dsp_setirq(&ess->dsp, device_get_config_int("irq")); sb_dsp_setdma8(&ess->dsp, device_get_config_int("dma")); sb_dsp_setdma16_8(&ess->dsp, device_get_config_int("dma")); sb_dsp_setdma16_supported(&ess->dsp, 0); ess_mixer_reset(ess); /* DSP I/O handler is activated in sb_dsp_setaddr */ { io_sethandler(addr, 0x0004, ess->opl.read, NULL, NULL, ess->opl.write, NULL, NULL, ess->opl.priv); io_sethandler(addr + 8, 0x0002, ess->opl.read, NULL, NULL, ess->opl.write, NULL, NULL, ess->opl.priv); io_sethandler(0x0388, 0x0004, ess->opl.read, NULL, NULL, ess->opl.write, NULL, NULL, ess->opl.priv); } ess->mixer_enabled = 1; io_sethandler(addr + 4, 0x0002, ess_mixer_read, NULL, NULL, ess_mixer_write, NULL, NULL, ess); sound_add_handler(ess_get_buffer_sbpro, ess); music_add_handler(ess_get_music_buffer_sbpro, ess); sound_set_cd_audio_filter(ess_filter_cd_audio, ess); if (device_get_config_int("receive_input")) midi_in_handler(1, sb_dsp_input_msg, sb_dsp_input_sysex, &ess->dsp); { ess->mixer_sbpro.ess_id_str[0] = 0x16; ess->mixer_sbpro.ess_id_str[1] = 0x88; ess->mixer_sbpro.ess_id_str[2] = (addr >> 8) & 0xff; ess->mixer_sbpro.ess_id_str[3] = addr & 0xff; } ess->mpu = (mpu_t *) calloc(1, sizeof(mpu_t)); mpu401_init(ess->mpu, 0, 0, M_UART, 1); sb_dsp_set_mpu(&ess->dsp, ess->mpu); ess->gameport = gameport_add(&gameport_pnp_device); ess->gameport_addr = 0x200; gameport_remap(ess->gameport, ess->gameport_addr); return ess; } void ess_close(void *priv) { ess_t *ess = (ess_t *) priv; sb_dsp_close(&ess->dsp); free(ess); } void ess_speed_changed(void *priv) { ess_t *ess = (ess_t *) priv; sb_dsp_speed_changed(&ess->dsp); } static const device_config_t ess_config[] = { { .name = "base", .description = "Address", .type = CONFIG_HEX16, .default_string = "", .default_int = 0x220, .file_filter = "", .spinner = { 0 }, .selection = { { .description = "0x220", .value = 0x220 }, { .description = "0x240", .value = 0x240 }, { .description = "" } } }, { .name = "irq", .description = "IRQ", .type = CONFIG_SELECTION, .default_string = "", .default_int = 7, .file_filter = "", .spinner = { 0 }, .selection = { { .description = "IRQ 2", .value = 2 }, { .description = "IRQ 5", .value = 5 }, { .description = "IRQ 7", .value = 7 }, { .description = "IRQ 10", .value = 10 }, { .description = "" } } }, { .name = "dma", .description = "DMA", .type = CONFIG_SELECTION, .default_string = "", .default_int = 1, .file_filter = "", .spinner = { 0 }, .selection = { { .description = "DMA 0", .value = 0 }, { .description = "DMA 1", .value = 1 }, { .description = "DMA 3", .value = 3 }, { .description = "" } } }, { .name = "opl", .description = "Enable OPL", .type = CONFIG_BINARY, .default_string = "", .default_int = 1 }, { .name = "receive_input", .description = "Receive input (SB MIDI)", .type = CONFIG_BINARY, .default_string = "", .default_int = 1 }, { .name = "", .description = "", .type = CONFIG_END } }; const device_t ess_1688_device = { .name = "ESS Technology ES1688", .internal_name = "ess_es1688", .flags = DEVICE_ISA, .local = 0, .init = ess_1688_init, .close = ess_close, .reset = NULL, { .available = NULL }, .speed_changed = ess_speed_changed, .force_redraw = NULL, .config = ess_config };