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>
|
|
|
|
|
* TheCollector1995, <mariogplayer@gmail.com>
|
|
|
|
|
* Sarah Walker, <tommowalker@tommowalker.co.uk>
|
|
|
|
|
*
|
|
|
|
|
* Copyright 2017-2020 Fred N. van Kempen.
|
|
|
|
|
* Copyright 2016-2019 Miran Grca.
|
|
|
|
|
* Copyright 2008-2018 Sarah Walker.
|
|
|
|
|
*/
|
2017-09-25 04:31:20 -04:00
|
|
|
#include <stdio.h>
|
2016-06-26 00:34:39 +02:00
|
|
|
#include <stdint.h>
|
2017-09-25 04:31:20 -04:00
|
|
|
#include <string.h>
|
2016-06-26 00:34:39 +02:00
|
|
|
#include <stdlib.h>
|
2017-09-25 04:31:20 -04:00
|
|
|
#include <wchar.h>
|
2020-07-19 03:24:45 +02:00
|
|
|
#define dbglog sound_card_log
|
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 {
|
|
|
|
|
STATUS_TIMER_1 = 0x40,
|
|
|
|
|
STATUS_TIMER_2 = 0x20,
|
|
|
|
|
STATUS_TIMER_ALL = 0x80
|
|
|
|
|
};
|
2016-06-26 00:34:39 +02:00
|
|
|
|
2020-07-19 03:24:45 +02:00
|
|
|
enum {
|
|
|
|
|
CTRL_IRQ_RESET = 0x80,
|
|
|
|
|
CTRL_TIMER1_MASK = 0x40,
|
|
|
|
|
CTRL_TIMER2_MASK = 0x20,
|
|
|
|
|
CTRL_TIMER2_CTRL = 0x02,
|
|
|
|
|
CTRL_TIMER1_CTRL = 0x01
|
|
|
|
|
};
|
2016-06-26 00:34:39 +02:00
|
|
|
|
2020-07-19 03:24:45 +02:00
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
status_update(opl_t *dev)
|
2016-06-26 00:34:39 +02:00
|
|
|
{
|
2020-07-19 03:24:45 +02:00
|
|
|
if (dev->status & (STATUS_TIMER_1 | STATUS_TIMER_2) & dev->status_mask)
|
|
|
|
|
dev->status |= STATUS_TIMER_ALL;
|
|
|
|
|
else
|
|
|
|
|
dev->status &= ~STATUS_TIMER_ALL;
|
|
|
|
|
}
|
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
|
|
|
|
|
timer_set(opl_t *dev, int timer, uint64_t period)
|
|
|
|
|
{
|
|
|
|
|
timer_on_auto(&dev->timers[timer], ((double) period) * 20.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_over(opl_t *dev, int tmr)
|
2016-06-26 00:34:39 +02:00
|
|
|
{
|
2020-07-19 03:24:45 +02:00
|
|
|
if (tmr) {
|
|
|
|
|
dev->status |= STATUS_TIMER_2;
|
|
|
|
|
timer_set(dev, 1, dev->timer[1] * 16);
|
|
|
|
|
} else {
|
|
|
|
|
dev->status |= STATUS_TIMER_1;
|
|
|
|
|
timer_set(dev, 0, dev->timer[0] * 4);
|
|
|
|
|
}
|
2016-06-26 00:34:39 +02:00
|
|
|
|
2020-07-19 03:24:45 +02:00
|
|
|
status_update(dev);
|
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-19 03:24:45 +02:00
|
|
|
timer_over(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-19 03:24:45 +02:00
|
|
|
timer_over(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-19 03:24:45 +02:00
|
|
|
if (! (port & 1))
|
|
|
|
|
return((dev->status & dev->status_mask) | (dev->is_opl3 ? 0x00 : 0x06));
|
2016-06-26 00:34:39 +02:00
|
|
|
|
2020-07-19 03:24:45 +02:00
|
|
|
if (dev->is_opl3 && ((port & 0x03) == 0x03))
|
|
|
|
|
return(0x00);
|
2019-12-21 20:06:34 +01:00
|
|
|
|
2020-07-19 03:24:45 +02:00
|
|
|
return(dev->is_opl3 ? 0x00 : 0xff);
|
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-19 03:24:45 +02:00
|
|
|
if (! (port & 1)) {
|
|
|
|
|
dev->port = nuked_write_addr(dev->opl, port, val) & 0x01ff;
|
|
|
|
|
|
|
|
|
|
if (! dev->is_opl3)
|
|
|
|
|
dev->port &= 0x00ff;
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
2016-06-26 00:34:39 +02:00
|
|
|
|
2020-07-19 03:24:45 +02:00
|
|
|
nuked_write_reg_buffered(dev->opl, dev->port, val);
|
|
|
|
|
|
|
|
|
|
switch (dev->port) {
|
|
|
|
|
case 0x02: // timer 1
|
|
|
|
|
dev->timer[0] = 256 - val;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0x03: // timer 2
|
|
|
|
|
dev->timer[1] = 256 - val;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 0x04: // timer control
|
|
|
|
|
if (val & CTRL_IRQ_RESET) {
|
|
|
|
|
dev->status &= ~(STATUS_TIMER_1 | STATUS_TIMER_2);
|
|
|
|
|
status_update(dev);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if ((val ^ dev->timer_ctrl) & CTRL_TIMER1_CTRL) {
|
|
|
|
|
if (val & CTRL_TIMER1_CTRL)
|
|
|
|
|
timer_set(dev, 0, dev->timer[0] * 4);
|
|
|
|
|
else
|
|
|
|
|
timer_set(dev, 0, 0);
|
|
|
|
|
}
|
|
|
|
|
if ((val ^ dev->timer_ctrl) & CTRL_TIMER2_CTRL) {
|
|
|
|
|
if (val & CTRL_TIMER2_CTRL)
|
|
|
|
|
timer_set(dev, 1, dev->timer[1] * 16);
|
|
|
|
|
else
|
|
|
|
|
timer_set(dev, 1, 0);
|
|
|
|
|
}
|
|
|
|
|
dev->status_mask = (~val & (CTRL_TIMER1_MASK | CTRL_TIMER2_MASK)) | 0x80;
|
|
|
|
|
dev->timer_ctrl = val;
|
|
|
|
|
break;
|
|
|
|
|
}
|
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)
|
|
|
|
|
{
|
|
|
|
|
dev->do_cycles = do_cycles;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
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-19 03:24:45 +02:00
|
|
|
dev->is_opl3 = is_opl3;
|
2020-07-19 21:32:23 +02:00
|
|
|
dev->do_cycles = 1;
|
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-19 21:32:23 +02:00
|
|
|
if (dev->do_cycles)
|
|
|
|
|
sub_cycles((int) (isa_timing * 8));
|
2019-12-21 20:06:34 +01:00
|
|
|
|
2020-07-19 03:24:45 +02:00
|
|
|
opl2_update(dev);
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
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
|
|
|
{
|
2020-07-19 03:24:45 +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-19 21:32:23 +02:00
|
|
|
if (dev->do_cycles)
|
|
|
|
|
sub_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
|
|
|
}
|