2020-07-19 03:24:45 +02:00
|
|
|
/*
|
|
|
|
|
* 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.
|
|
|
|
|
*
|
|
|
|
|
* Interface to the actual OPL emulator.
|
|
|
|
|
*
|
|
|
|
|
* TODO: Finish re-working this into a device_t, which requires a
|
|
|
|
|
* poll-like function for "update" so the sound card can call
|
|
|
|
|
* that and get a buffer-full of sample data.
|
|
|
|
|
*
|
|
|
|
|
* Authors: Fred N. van Kempen, <decwiz@yahoo.com>
|
|
|
|
|
* Miran Grca, <mgrca8@gmail.com>
|
|
|
|
|
*
|
|
|
|
|
* Copyright 2017-2020 Fred N. van Kempen.
|
2020-07-26 02:35:41 +02:00
|
|
|
* Copyright 2016-2020 Miran Grca.
|
2020-07-19 03:24:45 +02:00
|
|
|
*/
|
2020-07-26 02:35:41 +02:00
|
|
|
#include <stdarg.h>
|
2016-06-26 00:34:39 +02:00
|
|
|
#include <stdint.h>
|
2020-07-26 02:35:41 +02:00
|
|
|
#include <stdio.h>
|
2016-06-26 00:34:39 +02:00
|
|
|
#include <stdlib.h>
|
2020-07-26 02:35:41 +02:00
|
|
|
#include <string.h>
|
2017-09-25 04:31:20 -04:00
|
|
|
#include <wchar.h>
|
2020-07-26 02:35:41 +02:00
|
|
|
#define HAVE_STDARG_H
|
2020-03-29 14:24:42 +02:00
|
|
|
#include <86box/86box.h>
|
2020-07-19 03:24:45 +02:00
|
|
|
#include <86box/timer.h>
|
2020-02-29 19:12:23 +01:00
|
|
|
#include "cpu.h"
|
2020-03-29 14:24:42 +02:00
|
|
|
#include <86box/io.h>
|
|
|
|
|
#include <86box/sound.h>
|
|
|
|
|
#include <86box/snd_opl.h>
|
2020-07-19 03:24:45 +02:00
|
|
|
#include <86box/snd_opl_nuked.h>
|
2017-05-06 17:48:33 +02:00
|
|
|
|
2016-06-26 00:34:39 +02:00
|
|
|
|
2020-07-19 03:24:45 +02:00
|
|
|
enum {
|
2020-07-26 02:35:41 +02:00
|
|
|
FLAG_CYCLES = 0x02,
|
|
|
|
|
FLAG_OPL3 = 0x01
|
2020-07-19 03:24:45 +02:00
|
|
|
};
|
2016-06-26 00:34:39 +02:00
|
|
|
|
2020-07-19 03:24:45 +02:00
|
|
|
enum {
|
2020-07-26 02:35:41 +02:00
|
|
|
STAT_TMR_OVER = 0x60,
|
|
|
|
|
STAT_TMR1_OVER = 0x40,
|
|
|
|
|
STAT_TMR2_OVER = 0x20,
|
|
|
|
|
STAT_TMR_ANY = 0x80
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
enum {
|
|
|
|
|
CTRL_RESET = 0x80,
|
|
|
|
|
CTRL_TMR_MASK = 0x60,
|
|
|
|
|
CTRL_TMR1_MASK = 0x40,
|
|
|
|
|
CTRL_TMR2_MASK = 0x20,
|
|
|
|
|
CTRL_TMR2_START = 0x02,
|
|
|
|
|
CTRL_TMR1_START = 0x01
|
2020-07-19 03:24:45 +02:00
|
|
|
};
|
2016-06-26 00:34:39 +02:00
|
|
|
|
2020-07-19 03:24:45 +02:00
|
|
|
|
2020-07-26 01:13:03 +02:00
|
|
|
#ifdef ENABLE_OPL_LOG
|
|
|
|
|
int opl_do_log = ENABLE_OPL_LOG;
|
2019-12-21 20:06:34 +01:00
|
|
|
|
2016-06-26 00:34:39 +02:00
|
|
|
|
2020-07-19 03:24:45 +02:00
|
|
|
static void
|
2020-07-26 01:13:03 +02:00
|
|
|
opl_log(const char *fmt, ...)
|
2020-07-19 03:24:45 +02:00
|
|
|
{
|
2020-07-26 01:13:03 +02:00
|
|
|
va_list ap;
|
|
|
|
|
|
|
|
|
|
if (opl_do_log) {
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
|
pclog_ex(fmt, ap);
|
|
|
|
|
va_end(ap);
|
|
|
|
|
}
|
2016-06-26 00:34:39 +02:00
|
|
|
}
|
2020-07-26 01:13:03 +02:00
|
|
|
#else
|
|
|
|
|
#define opl_log(fmt, ...)
|
|
|
|
|
#endif
|
2019-12-21 20:06:34 +01:00
|
|
|
|
|
|
|
|
|
2020-07-26 01:13:03 +02:00
|
|
|
static void
|
|
|
|
|
timer_tick(opl_t *dev, int tmr)
|
|
|
|
|
{
|
|
|
|
|
dev->timer_cur_count[tmr] = (dev->timer_cur_count[tmr] + 1) & 0xff;
|
2016-06-26 00:34:39 +02:00
|
|
|
|
2020-07-26 01:13:03 +02:00
|
|
|
opl_log("Ticking timer %i, count now %02X...\n", tmr, dev->timer_cur_count[tmr]);
|
|
|
|
|
|
2020-07-26 02:35:41 +02:00
|
|
|
if (dev->timer_cur_count[tmr] == 0x00) {
|
|
|
|
|
dev->status |= ((STAT_TMR1_OVER >> tmr) & ~dev->timer_ctrl);
|
|
|
|
|
dev->timer_cur_count[tmr] = dev->timer_count[tmr];
|
|
|
|
|
|
|
|
|
|
opl_log("Count wrapped around to zero, reloading timer %i (%02X), status = %02X...\n", tmr, (STAT_TMR1_OVER >> tmr), dev->status);
|
|
|
|
|
}
|
2020-07-26 01:13:03 +02:00
|
|
|
|
|
|
|
|
timer_on_auto(&dev->timers[tmr], (tmr == 1) ? 320.0 : 80.0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
timer_control(opl_t *dev, int tmr, int start)
|
|
|
|
|
{
|
|
|
|
|
timer_on_auto(&dev->timers[tmr], 0.0);
|
|
|
|
|
|
|
|
|
|
if (start) {
|
|
|
|
|
opl_log("Loading timer %i count: %02X = %02X\n", tmr, dev->timer_cur_count[tmr], dev->timer_count[tmr]);
|
|
|
|
|
dev->timer_cur_count[tmr] = dev->timer_count[tmr];
|
2020-08-04 04:05:33 +02:00
|
|
|
if (dev->flags & FLAG_OPL3)
|
2020-08-04 07:38:01 +02:00
|
|
|
timer_tick(dev, tmr); /* Per the YMF 262 datasheet, OPL3 starts counting immediately, unlike OPL2. */
|
2020-08-04 04:05:33 +02:00
|
|
|
else
|
|
|
|
|
timer_on_auto(&dev->timers[tmr], (tmr == 1) ? 320.0 : 80.0);
|
2021-09-25 19:12:20 +02:00
|
|
|
} else {
|
|
|
|
|
opl_log("Timer %i stopped\n", tmr);
|
2021-10-31 18:35:04 +01:00
|
|
|
if (tmr == 1) {
|
|
|
|
|
dev->status &= ~STAT_TMR2_OVER;
|
|
|
|
|
} else
|
|
|
|
|
dev->status &= ~STAT_TMR1_OVER;
|
2021-09-25 19:12:20 +02:00
|
|
|
}
|
2016-06-26 00:34:39 +02:00
|
|
|
}
|
|
|
|
|
|
2019-12-21 20:06:34 +01:00
|
|
|
|
2020-07-19 03:24:45 +02:00
|
|
|
static void
|
|
|
|
|
timer_1(void *priv)
|
2016-06-26 00:34:39 +02:00
|
|
|
{
|
2020-07-19 03:24:45 +02:00
|
|
|
opl_t *dev = (opl_t *)priv;
|
2016-06-26 00:34:39 +02:00
|
|
|
|
2020-07-26 01:13:03 +02:00
|
|
|
timer_tick(dev, 0);
|
2016-06-26 00:34:39 +02:00
|
|
|
}
|
2019-12-21 20:06:34 +01:00
|
|
|
|
|
|
|
|
|
2020-07-19 03:24:45 +02:00
|
|
|
static void
|
|
|
|
|
timer_2(void *priv)
|
2016-06-26 00:34:39 +02:00
|
|
|
{
|
2020-07-19 03:24:45 +02:00
|
|
|
opl_t *dev = (opl_t *)priv;
|
2016-06-26 00:34:39 +02:00
|
|
|
|
2020-07-26 01:13:03 +02:00
|
|
|
timer_tick(dev, 1);
|
2016-06-26 00:34:39 +02:00
|
|
|
}
|
|
|
|
|
|
2019-12-21 20:06:34 +01:00
|
|
|
|
2020-07-19 03:24:45 +02:00
|
|
|
static uint8_t
|
|
|
|
|
opl_read(opl_t *dev, uint16_t port)
|
2016-06-26 00:34:39 +02:00
|
|
|
{
|
2020-07-26 01:13:03 +02:00
|
|
|
uint8_t ret = 0xff;
|
2016-06-26 00:34:39 +02:00
|
|
|
|
2020-07-26 01:13:03 +02:00
|
|
|
if ((port & 0x0003) == 0x0000) {
|
2020-07-26 02:35:41 +02:00
|
|
|
ret = dev->status;
|
|
|
|
|
if (dev->status & STAT_TMR_OVER)
|
|
|
|
|
ret |= STAT_TMR_ANY;
|
2020-07-26 01:13:03 +02:00
|
|
|
}
|
2019-12-21 20:06:34 +01:00
|
|
|
|
2021-09-25 19:12:20 +02:00
|
|
|
opl_log("OPL statret = %02x, status = %02x\n", ret, dev->status);
|
|
|
|
|
|
2020-07-26 01:13:03 +02:00
|
|
|
return ret;
|
2016-06-26 00:34:39 +02:00
|
|
|
}
|
2019-12-21 20:06:34 +01:00
|
|
|
|
|
|
|
|
|
2020-07-19 03:24:45 +02:00
|
|
|
static void
|
|
|
|
|
opl_write(opl_t *dev, uint16_t port, uint8_t val)
|
2016-06-26 00:34:39 +02:00
|
|
|
{
|
2020-07-26 01:13:03 +02:00
|
|
|
if ((port & 0x0001) == 0x0001) {
|
|
|
|
|
nuked_write_reg_buffered(dev->opl, dev->port, val);
|
|
|
|
|
|
|
|
|
|
switch (dev->port) {
|
|
|
|
|
case 0x02: /* Timer 1 */
|
|
|
|
|
dev->timer_count[0] = val;
|
|
|
|
|
opl_log("Timer 0 count now: %i\n", dev->timer_count[0]);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0x03: /* Timer 2 */
|
|
|
|
|
dev->timer_count[1] = val;
|
|
|
|
|
opl_log("Timer 1 count now: %i\n", dev->timer_count[1]);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0x04: /* Timer control */
|
2020-07-26 02:35:41 +02:00
|
|
|
if (val & CTRL_RESET) {
|
2020-07-26 01:13:03 +02:00
|
|
|
opl_log("Resetting timer status...\n");
|
2020-07-26 02:35:41 +02:00
|
|
|
dev->status &= ~STAT_TMR_OVER;
|
2020-07-26 01:13:03 +02:00
|
|
|
} else {
|
2020-08-04 04:05:33 +02:00
|
|
|
dev->timer_ctrl = val;
|
2020-07-26 02:35:41 +02:00
|
|
|
timer_control(dev, 0, val & CTRL_TMR1_START);
|
|
|
|
|
timer_control(dev, 1, val & CTRL_TMR2_START);
|
|
|
|
|
opl_log("Status mask now %02X (val = %02X)\n", (val & ~CTRL_TMR_MASK) & CTRL_TMR_MASK, val);
|
2020-07-26 01:13:03 +02:00
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2020-07-19 03:24:45 +02:00
|
|
|
dev->port = nuked_write_addr(dev->opl, port, val) & 0x01ff;
|
|
|
|
|
|
2020-07-26 02:35:41 +02:00
|
|
|
if (!(dev->flags & FLAG_OPL3))
|
2020-07-19 03:24:45 +02:00
|
|
|
dev->port &= 0x00ff;
|
|
|
|
|
}
|
2016-06-26 00:34:39 +02:00
|
|
|
}
|
|
|
|
|
|
2019-12-21 20:06:34 +01:00
|
|
|
|
2020-07-19 21:32:23 +02:00
|
|
|
void
|
|
|
|
|
opl_set_do_cycles(opl_t *dev, int8_t do_cycles)
|
|
|
|
|
{
|
2020-07-26 02:35:41 +02:00
|
|
|
if (do_cycles)
|
|
|
|
|
dev->flags |= FLAG_CYCLES;
|
|
|
|
|
else
|
|
|
|
|
dev->flags &= ~FLAG_CYCLES;
|
2020-07-19 21:32:23 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2020-07-19 03:24:45 +02:00
|
|
|
static void
|
|
|
|
|
opl_init(opl_t *dev, int is_opl3)
|
2016-06-26 00:34:39 +02:00
|
|
|
{
|
2020-07-19 03:24:45 +02:00
|
|
|
memset(dev, 0x00, sizeof(opl_t));
|
2020-07-19 21:32:23 +02:00
|
|
|
|
2020-07-26 02:35:41 +02:00
|
|
|
dev->flags = FLAG_CYCLES;
|
|
|
|
|
if (is_opl3)
|
|
|
|
|
dev->flags |= FLAG_OPL3;
|
|
|
|
|
else
|
|
|
|
|
dev->status = 0x06;
|
2016-06-26 00:34:39 +02:00
|
|
|
|
2020-07-19 03:24:45 +02:00
|
|
|
/* Create a NukedOPL object. */
|
|
|
|
|
dev->opl = nuked_init(48000);
|
2019-12-21 20:06:34 +01:00
|
|
|
|
2020-07-19 03:24:45 +02:00
|
|
|
timer_add(&dev->timers[0], timer_1, dev, 0);
|
|
|
|
|
timer_add(&dev->timers[1], timer_2, dev, 0);
|
2016-06-26 00:34:39 +02:00
|
|
|
}
|
2019-12-21 20:06:34 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
void
|
2020-07-19 03:24:45 +02:00
|
|
|
opl_close(opl_t *dev)
|
2016-06-26 00:34:39 +02:00
|
|
|
{
|
2020-07-19 03:24:45 +02:00
|
|
|
/* Release the NukedOPL object. */
|
|
|
|
|
if (dev->opl) {
|
|
|
|
|
nuked_close(dev->opl);
|
|
|
|
|
dev->opl = NULL;
|
|
|
|
|
}
|
2016-06-26 00:34:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2020-07-19 03:24:45 +02:00
|
|
|
uint8_t
|
|
|
|
|
opl2_read(uint16_t port, void *priv)
|
|
|
|
|
{
|
|
|
|
|
opl_t *dev = (opl_t *)priv;
|
2016-06-26 00:34:39 +02:00
|
|
|
|
2020-07-26 02:35:41 +02:00
|
|
|
if (dev->flags & FLAG_CYCLES)
|
2020-11-26 18:20:24 +01:00
|
|
|
cycles -= ((int) (isa_timing * 8));
|
2019-12-21 20:06:34 +01:00
|
|
|
|
2020-07-19 03:24:45 +02:00
|
|
|
opl2_update(dev);
|
2021-09-25 19:12:20 +02:00
|
|
|
opl_log("OPL2 port read = %04x\n", port);
|
|
|
|
|
|
2020-07-19 03:24:45 +02:00
|
|
|
return(opl_read(dev, port));
|
2016-06-26 00:34:39 +02:00
|
|
|
}
|
|
|
|
|
|
2019-12-21 20:06:34 +01:00
|
|
|
|
|
|
|
|
void
|
2020-07-19 03:24:45 +02:00
|
|
|
opl2_write(uint16_t port, uint8_t val, void *priv)
|
2016-06-26 00:34:39 +02:00
|
|
|
{
|
2020-07-19 03:24:45 +02:00
|
|
|
opl_t *dev = (opl_t *)priv;
|
2019-12-21 20:06:34 +01:00
|
|
|
|
2020-07-19 03:24:45 +02:00
|
|
|
opl2_update(dev);
|
|
|
|
|
|
2021-09-25 19:12:20 +02:00
|
|
|
opl_log("OPL2 port write = %04x\n", port);
|
2020-07-19 03:24:45 +02:00
|
|
|
opl_write(dev, port, val);
|
2016-06-26 00:34:39 +02:00
|
|
|
}
|
2019-12-21 20:06:34 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
void
|
2020-07-19 03:24:45 +02:00
|
|
|
opl2_init(opl_t *dev)
|
2016-06-26 00:34:39 +02:00
|
|
|
{
|
2020-07-19 03:24:45 +02:00
|
|
|
opl_init(dev, 0);
|
2016-06-26 00:34:39 +02:00
|
|
|
}
|
|
|
|
|
|
2019-12-21 20:06:34 +01:00
|
|
|
|
2020-07-19 03:24:45 +02:00
|
|
|
void
|
|
|
|
|
opl2_update(opl_t *dev)
|
2016-06-26 00:34:39 +02:00
|
|
|
{
|
2021-09-25 19:12:20 +02:00
|
|
|
if (dev->pos >= sound_pos_global) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2016-06-26 00:34:39 +02:00
|
|
|
|
2020-07-19 03:24:45 +02:00
|
|
|
nuked_generate_stream(dev->opl,
|
|
|
|
|
&dev->buffer[dev->pos * 2],
|
|
|
|
|
sound_pos_global - dev->pos);
|
2019-12-21 20:06:34 +01:00
|
|
|
|
2020-07-19 03:24:45 +02:00
|
|
|
for (; dev->pos < sound_pos_global; dev->pos++) {
|
|
|
|
|
dev->buffer[dev->pos * 2] /= 2;
|
|
|
|
|
dev->buffer[(dev->pos * 2) + 1] = dev->buffer[dev->pos * 2];
|
|
|
|
|
}
|
2016-06-26 00:34:39 +02:00
|
|
|
}
|
2019-12-21 20:06:34 +01:00
|
|
|
|
|
|
|
|
|
2020-07-19 03:24:45 +02:00
|
|
|
uint8_t
|
|
|
|
|
opl3_read(uint16_t port, void *priv)
|
2016-06-26 00:34:39 +02:00
|
|
|
{
|
2020-07-19 03:24:45 +02:00
|
|
|
opl_t *dev = (opl_t *)priv;
|
2019-12-21 20:06:34 +01:00
|
|
|
|
2020-07-26 02:35:41 +02:00
|
|
|
if (dev->flags & FLAG_CYCLES)
|
2020-11-26 18:20:24 +01:00
|
|
|
cycles -= ((int)(isa_timing * 8));
|
2019-12-21 20:06:34 +01:00
|
|
|
|
2020-07-19 03:24:45 +02:00
|
|
|
opl3_update(dev);
|
|
|
|
|
|
|
|
|
|
return(opl_read(dev, port));
|
2016-06-26 00:34:39 +02:00
|
|
|
}
|
2019-12-21 20:06:34 +01:00
|
|
|
|
|
|
|
|
|
2020-07-19 03:24:45 +02:00
|
|
|
void
|
|
|
|
|
opl3_write(uint16_t port, uint8_t val, void *priv)
|
2016-06-26 00:34:39 +02:00
|
|
|
{
|
2020-07-19 03:24:45 +02:00
|
|
|
opl_t *dev = (opl_t *)priv;
|
|
|
|
|
|
|
|
|
|
opl3_update(dev);
|
|
|
|
|
|
|
|
|
|
opl_write(dev, port, val);
|
2016-06-26 00:34:39 +02:00
|
|
|
}
|
2019-12-21 20:06:34 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
void
|
2020-07-19 03:24:45 +02:00
|
|
|
opl3_init(opl_t *dev)
|
2016-06-26 00:34:39 +02:00
|
|
|
{
|
2020-07-19 03:24:45 +02:00
|
|
|
opl_init(dev, 1);
|
2016-06-26 00:34:39 +02:00
|
|
|
}
|
|
|
|
|
|
2019-12-21 20:06:34 +01:00
|
|
|
|
2020-07-19 03:24:45 +02:00
|
|
|
/* API to sound interface. */
|
2019-12-21 20:06:34 +01:00
|
|
|
void
|
2020-07-19 03:24:45 +02:00
|
|
|
opl3_update(opl_t *dev)
|
2016-06-26 00:34:39 +02:00
|
|
|
{
|
2020-07-19 03:24:45 +02:00
|
|
|
if (dev->pos >= sound_pos_global)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
nuked_generate_stream(dev->opl,
|
|
|
|
|
&dev->buffer[dev->pos * 2],
|
|
|
|
|
sound_pos_global - dev->pos);
|
2016-06-26 00:34:39 +02:00
|
|
|
|
2020-07-19 03:24:45 +02:00
|
|
|
for (; dev->pos < sound_pos_global; dev->pos++) {
|
|
|
|
|
dev->buffer[dev->pos * 2] /= 2;
|
|
|
|
|
dev->buffer[(dev->pos * 2) + 1] /= 2;
|
|
|
|
|
}
|
2019-12-21 20:06:34 +01:00
|
|
|
}
|