Files
86Box/src/sound/sound.c
TC1995 02874f2ed2 DMA: Implemented autoinit mode in the PS/2 MCA side (although the bit is undocumented in said side, but documented in the ISA/PCI side).
Networking: Added the WD8013EP/A MCA nic, which is more supported than the WD80x3ET/A plus an initial ram size configuration before POS configuration.
Sound: Added the Reply MCA OEM of SB16 with its own MCA POS ID and properly implemented the IRQ's and DMA's of the AdLib Gold in its EEPROM plus an initial configurable setting for them and an initial DRQ implementation into said card.
2022-07-23 23:54:42 +02:00

578 lines
15 KiB
C

/*
* 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 emulation core.
*
*
*
* Authors: Sarah Walker, <http://pcem-emulator.co.uk/>
* Miran Grca, <mgrca8@gmail.com>
*
* Copyright 2008-2020 Sarah Walker.
* Copyright 2016-2020 Miran Grca.
*/
#include <math.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#define HAVE_STDARG_H
#include <86box/86box.h>
#include <86box/cdrom.h>
#include <86box/device.h>
#include <86box/filters.h>
#include <86box/hdc_ide.h>
#include <86box/machine.h>
#include <86box/midi.h>
#include <86box/plat.h>
#include <86box/thread.h>
#include <86box/snd_ac97.h>
#include <86box/snd_azt2316a.h>
#include <86box/timer.h>
#include <86box/snd_mpu401.h>
#include <86box/sound.h>
#include <86box/snd_opl.h>
#include <86box/snd_sb_dsp.h>
typedef struct {
const device_t *device;
} SOUND_CARD;
typedef struct {
void (*get_buffer)(int32_t *buffer, int len, void *p);
void *priv;
} sound_handler_t;
int sound_card_current = 0;
int sound_pos_global = 0;
int sound_gain = 0;
static sound_handler_t sound_handlers[8];
static thread_t *sound_cd_thread_h;
static event_t *sound_cd_event;
static event_t *sound_cd_start_event;
static int32_t *outbuffer;
static float *outbuffer_ex;
static int16_t *outbuffer_ex_int16;
static int sound_handlers_num;
static pc_timer_t sound_poll_timer;
static uint64_t sound_poll_latch;
static int16_t cd_buffer[CDROM_NUM][CD_BUFLEN * 2];
static float cd_out_buffer[CD_BUFLEN * 2];
static int16_t cd_out_buffer_int16[CD_BUFLEN * 2];
static unsigned int cd_vol_l, cd_vol_r;
static int cd_buf_update = CD_BUFLEN / SOUNDBUFLEN;
static volatile int cdaudioon = 0;
static int cd_thread_enable = 0;
static void (*filter_cd_audio)(int channel, double *buffer, void *p) = NULL;
static void *filter_cd_audio_p = NULL;
static const device_t sound_none_device = {
"None",
"none",
0,
0,
NULL,
NULL,
NULL,
{ NULL },
NULL,
NULL,
NULL
};
static const device_t sound_internal_device = {
"Internal",
"internal",
0,
0,
NULL,
NULL,
NULL,
{ NULL },
NULL,
NULL,
NULL
};
static const SOUND_CARD sound_cards[] = {
// clang-format off
{ &sound_none_device },
{ &sound_internal_device },
{ &adlib_device },
{ &adgold_device },
{ &azt2316a_device },
{ &azt1605_device },
{ &cs4235_device },
{ &cs4236b_device },
{ &sb_1_device },
{ &sb_15_device },
{ &sb_2_device },
{ &sb_pro_v1_device },
{ &sb_pro_v2_device },
{ &sb_16_device },
{ &sb_16_pnp_device },
{ &sb_32_pnp_device },
{ &sb_awe32_device },
{ &sb_awe32_pnp_device },
{ &sb_awe64_value_device },
{ &sb_awe64_device },
{ &sb_awe64_gold_device },
#if defined(DEV_BRANCH) && defined(USE_PAS16)
{ &pas16_device },
#endif
#if defined(DEV_BRANCH) && defined(USE_TANDY_ISA)
{ &pssj_isa_device },
{ &tndy_device },
#endif
{ &wss_device },
{ &adlib_mca_device },
{ &ncr_business_audio_device },
{ &sb_mcv_device },
{ &sb_pro_mcv_device },
{ &sb_16_reply_mca_device },
{ &cmi8338_device },
{ &cmi8738_device },
{ &es1371_device },
{ &ad1881_device },
{ &cs4297a_device },
{ NULL }
// clang-format on
};
#ifdef ENABLE_SOUND_LOG
int sound_do_log = ENABLE_SOUND_LOG;
static void
sound_log(const char *fmt, ...)
{
va_list ap;
if (sound_do_log) {
va_start(ap, fmt);
pclog_ex(fmt, ap);
va_end(ap);
}
}
#else
# define sound_log(fmt, ...)
#endif
int
sound_card_available(int card)
{
if (sound_cards[card].device)
return device_available(sound_cards[card].device);
return 1;
}
const device_t *
sound_card_getdevice(int card)
{
return sound_cards[card].device;
}
int
sound_card_has_config(int card)
{
if (!sound_cards[card].device)
return 0;
return device_has_config(sound_cards[card].device) ? 1 : 0;
}
char *
sound_card_get_internal_name(int card)
{
return device_get_internal_name(sound_cards[card].device);
}
int
sound_card_get_from_internal_name(char *s)
{
int c = 0;
while (sound_cards[c].device != NULL) {
if (!strcmp((char *) sound_cards[c].device->internal_name, s))
return c;
c++;
}
return 0;
}
void
sound_card_init(void)
{
if (sound_cards[sound_card_current].device)
device_add(sound_cards[sound_card_current].device);
}
void
sound_set_cd_volume(unsigned int vol_l, unsigned int vol_r)
{
cd_vol_l = vol_l;
cd_vol_r = vol_r;
}
static void
sound_cd_clean_buffers(void)
{
if (sound_is_float)
memset(cd_out_buffer, 0, (CD_BUFLEN * 2) * sizeof(float));
else
memset(cd_out_buffer_int16, 0, (CD_BUFLEN * 2) * sizeof(int16_t));
}
static void
sound_cd_thread(void *param)
{
uint32_t lba;
int c, r, i, pre, channel_select[2];
double audio_vol_l, audio_vol_r;
double cd_buffer_temp[2] = { 0.0, 0.0 };
thread_set_event(sound_cd_start_event);
while (cdaudioon) {
thread_wait_event(sound_cd_event, -1);
thread_reset_event(sound_cd_event);
if (!cdaudioon)
return;
sound_cd_clean_buffers();
for (i = 0; i < CDROM_NUM; i++) {
if ((cdrom[i].bus_type == CDROM_BUS_DISABLED) || (cdrom[i].cd_status == CD_STATUS_EMPTY))
continue;
lba = cdrom[i].seek_pos;
r = cdrom_audio_callback(&(cdrom[i]), cd_buffer[i], CD_BUFLEN * 2);
if (!cdrom[i].bus_type || !cdrom[i].sound_on || !r)
continue;
pre = cdrom_is_pre(&(cdrom[i]), lba);
if (cdrom[i].get_volume) {
audio_vol_l = (float) (cdrom[i].get_volume(cdrom[i].priv, 0));
audio_vol_r = (float) (cdrom[i].get_volume(cdrom[i].priv, 1));
} else {
audio_vol_l = 255.0;
audio_vol_r = 255.0;
}
/* Calculate attenuation per the specification. */
if (audio_vol_l >= 255.0)
audio_vol_l = 1.0;
else if (audio_vol_l > 0.0)
audio_vol_l = (48.0 + (20.0 * log(audio_vol_l / 256.0))) / 48.0;
else
audio_vol_l = 0.0;
if (audio_vol_r >= 255.0)
audio_vol_r = 1.0;
else if (audio_vol_r > 0.0)
audio_vol_r = (48.0 + (20.0 * log(audio_vol_r / 256.0))) / 48.0;
else
audio_vol_r = 0.0;
if (cdrom[i].get_channel) {
channel_select[0] = cdrom[i].get_channel(cdrom[i].priv, 0);
channel_select[1] = cdrom[i].get_channel(cdrom[i].priv, 1);
} else {
channel_select[0] = 1;
channel_select[1] = 2;
}
for (c = 0; c < CD_BUFLEN * 2; c += 2) {
/*Apply ATAPI channel select*/
cd_buffer_temp[0] = cd_buffer_temp[1] = 0.0;
if ((audio_vol_l != 0.0) && (channel_select[0] != 0)) {
if (channel_select[0] & 1)
cd_buffer_temp[0] += ((double) cd_buffer[i][c]); /* Channel 0 => Port 0 */
if (channel_select[0] & 2)
cd_buffer_temp[0] += ((double) cd_buffer[i][c + 1]); /* Channel 1 => Port 0 */
cd_buffer_temp[0] *= audio_vol_l; /* Multiply Port 0 by Port 0 volume */
if (pre)
cd_buffer_temp[0] = deemph_iir(0, cd_buffer_temp[0]); /* De-emphasize if necessary */
}
if ((audio_vol_r != 0.0) && (channel_select[1] != 0)) {
if (channel_select[1] & 1)
cd_buffer_temp[1] += ((double) cd_buffer[i][c]); /* Channel 0 => Port 1 */
if (channel_select[1] & 2)
cd_buffer_temp[1] += ((double) cd_buffer[i][c + 1]); /* Channel 1 => Port 1 */
cd_buffer_temp[1] *= audio_vol_r; /* Multiply Port 1 by Port 1 volume */
if (pre)
cd_buffer_temp[1] = deemph_iir(1, cd_buffer_temp[1]); /* De-emphasize if necessary */
}
/* Apply sound card CD volume and filters */
if (filter_cd_audio != NULL) {
filter_cd_audio(0, &(cd_buffer_temp[0]), filter_cd_audio_p);
filter_cd_audio(1, &(cd_buffer_temp[1]), filter_cd_audio_p);
}
if (sound_is_float) {
cd_out_buffer[c] += (float) (cd_buffer_temp[0] / 32768.0);
cd_out_buffer[c + 1] += (float) (cd_buffer_temp[1] / 32768.0);
} else {
if (cd_buffer_temp[0] > 32767)
cd_buffer_temp[0] = 32767;
if (cd_buffer_temp[0] < -32768)
cd_buffer_temp[0] = -32768;
if (cd_buffer_temp[1] > 32767)
cd_buffer_temp[1] = 32767;
if (cd_buffer_temp[1] < -32768)
cd_buffer_temp[1] = -32768;
cd_out_buffer_int16[c] += (int16_t) cd_buffer_temp[0];
cd_out_buffer_int16[c + 1] += (int16_t) cd_buffer_temp[1];
}
}
}
if (sound_is_float)
givealbuffer_cd(cd_out_buffer);
else
givealbuffer_cd(cd_out_buffer_int16);
}
}
static void
sound_realloc_buffers(void)
{
if (outbuffer_ex != NULL) {
free(outbuffer_ex);
outbuffer_ex = NULL;
}
if (outbuffer_ex_int16 != NULL) {
free(outbuffer_ex_int16);
outbuffer_ex_int16 = NULL;
}
if (sound_is_float) {
outbuffer_ex = calloc(SOUNDBUFLEN * 2, sizeof(float));
memset(outbuffer_ex, 0x00, SOUNDBUFLEN * 2 * sizeof(float));
} else {
outbuffer_ex_int16 = calloc(SOUNDBUFLEN * 2, sizeof(int16_t));
memset(outbuffer_ex_int16, 0x00, SOUNDBUFLEN * 2 * sizeof(int16_t));
}
}
void
sound_init(void)
{
int i = 0;
int available_cdrom_drives = 0;
outbuffer_ex = NULL;
outbuffer_ex_int16 = NULL;
outbuffer = NULL;
outbuffer = calloc(SOUNDBUFLEN * 2, sizeof(int32_t));
memset(outbuffer, 0x00, SOUNDBUFLEN * 2 * sizeof(int32_t));
for (i = 0; i < CDROM_NUM; i++) {
if (cdrom[i].bus_type != CDROM_BUS_DISABLED)
available_cdrom_drives++;
}
if (available_cdrom_drives) {
cdaudioon = 1;
sound_cd_start_event = thread_create_event();
sound_cd_event = thread_create_event();
sound_cd_thread_h = thread_create(sound_cd_thread, NULL);
sound_log("Waiting for CD start event...\n");
thread_wait_event(sound_cd_start_event, -1);
thread_reset_event(sound_cd_start_event);
sound_log("Done!\n");
} else
cdaudioon = 0;
cd_thread_enable = available_cdrom_drives ? 1 : 0;
}
void
sound_add_handler(void (*get_buffer)(int32_t *buffer, int len, void *p), void *p)
{
sound_handlers[sound_handlers_num].get_buffer = get_buffer;
sound_handlers[sound_handlers_num].priv = p;
sound_handlers_num++;
}
void
sound_set_cd_audio_filter(void (*filter)(int channel, double *buffer, void *p), void *p)
{
if ((filter_cd_audio == NULL) || (filter == NULL)) {
filter_cd_audio = filter;
filter_cd_audio_p = p;
}
}
void
sound_poll(void *priv)
{
timer_advance_u64(&sound_poll_timer, sound_poll_latch);
midi_poll();
sound_pos_global++;
if (sound_pos_global == SOUNDBUFLEN) {
int c;
memset(outbuffer, 0x00, SOUNDBUFLEN * 2 * sizeof(int32_t));
for (c = 0; c < sound_handlers_num; c++)
sound_handlers[c].get_buffer(outbuffer, SOUNDBUFLEN, sound_handlers[c].priv);
for (c = 0; c < SOUNDBUFLEN * 2; c++) {
if (sound_is_float)
outbuffer_ex[c] = ((float) outbuffer[c]) / 32768.0;
else {
if (outbuffer[c] > 32767)
outbuffer[c] = 32767;
if (outbuffer[c] < -32768)
outbuffer[c] = -32768;
outbuffer_ex_int16[c] = outbuffer[c];
}
}
if (sound_is_float)
givealbuffer(outbuffer_ex);
else
givealbuffer(outbuffer_ex_int16);
if (cd_thread_enable) {
cd_buf_update--;
if (!cd_buf_update) {
cd_buf_update = (48000 / SOUNDBUFLEN) / (CD_FREQ / CD_BUFLEN);
thread_set_event(sound_cd_event);
}
}
sound_pos_global = 0;
}
}
void
sound_speed_changed(void)
{
sound_poll_latch = (uint64_t) ((double) TIMER_USEC * (1000000.0 / 48000.0));
}
void
sound_reset(void)
{
sound_realloc_buffers();
midi_out_device_init();
midi_in_device_init();
inital();
timer_add(&sound_poll_timer, sound_poll, NULL, 1);
sound_handlers_num = 0;
memset(sound_handlers, 0x00, 8 * sizeof(sound_handler_t));
filter_cd_audio = NULL;
filter_cd_audio_p = NULL;
sound_set_cd_volume(65535, 65535);
}
void
sound_card_reset(void)
{
/* Reset the MPU-401 already loaded flag and the chain of input/output handlers. */
midi_in_handlers_clear();
sound_card_init();
if (mpu401_standalone_enable)
mpu401_device_add();
if (GUS)
device_add(&gus_device);
if (GAMEBLASTER)
device_add(&cms_device);
if (SSI2001)
device_add(&ssi2001_device);
}
void
sound_cd_thread_end(void)
{
if (cdaudioon) {
cdaudioon = 0;
sound_log("Waiting for CD Audio thread to terminate...\n");
thread_set_event(sound_cd_event);
thread_wait(sound_cd_thread_h);
sound_log("CD Audio thread terminated...\n");
if (sound_cd_event) {
thread_destroy_event(sound_cd_event);
sound_cd_event = NULL;
}
sound_cd_thread_h = NULL;
if (sound_cd_start_event) {
thread_destroy_event(sound_cd_start_event);
sound_cd_event = NULL;
}
}
}
void
sound_cd_thread_reset(void)
{
int i = 0;
int available_cdrom_drives = 0;
for (i = 0; i < CDROM_NUM; i++) {
cdrom_stop(&(cdrom[i]));
if (cdrom[i].bus_type != CDROM_BUS_DISABLED)
available_cdrom_drives++;
}
if (available_cdrom_drives && !cd_thread_enable) {
cdaudioon = 1;
sound_cd_start_event = thread_create_event();
sound_cd_event = thread_create_event();
sound_cd_thread_h = thread_create(sound_cd_thread, NULL);
thread_wait_event(sound_cd_start_event, -1);
thread_reset_event(sound_cd_start_event);
} else if (!available_cdrom_drives && cd_thread_enable)
sound_cd_thread_end();
cd_thread_enable = available_cdrom_drives ? 1 : 0;
}