Files
86Box/src/sound/snd_mmb.c
Jasmine Iwanek 853830f8bb Add Mindscape Music Board
Ported from PCem
2025-05-26 00:01:54 -04:00

340 lines
10 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.
*
* Mindscape Music Board emulation.
*
* Authors: Roy Baer, <https://pcem-emulator.co.uk/>
* Jasmine Iwanek, <jriwanek@gmail.com>
*
* Copyright 2025 Roy Baer.
* Copyright 2025 Jasmine Iwanek.
*/
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define HAVE_STDARG_H
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/io.h>
#include <86box/sound.h>
//#i nclude "cpu.h"
#include "ayumi/ayumi.h"
#include <86box/snd_mmb.h>
#include <86box/plat_unused.h>
#ifdef ENABLE_MMB_LOG
int mmb_do_log = ENABLE_MMB_LOG;
static void
mmb_log(const char *fmt, ...)
{
va_list ap;
if (mmb_do_log) {
va_start(ap, fmt);
pclog_ex(fmt, ap);
va_end(ap);
}
}
#else
# define mmb_log(fmt, ...)
#endif
void
mmb_update(mmb_t *mmb)
{
for (; mmb->pos < sound_pos_global; mmb->pos++) {
ayumi_process(&mmb->first.chip);
ayumi_process(&mmb->second.chip);
ayumi_remove_dc(&mmb->first.chip);
ayumi_remove_dc(&mmb->second.chip);
mmb->buffer[mmb->pos << 1] = (mmb->first.chip.left + mmb->second.chip.left) * 16000;
mmb->buffer[(mmb->pos << 1) + 1] = (mmb->first.chip.right + mmb->second.chip.right) * 16000;
}
}
void
mmb_get_buffer(int32_t *buffer, int len, void *priv)
{
mmb_t *mmb = (mmb_t *) priv;
mmb_update(mmb);
for (int c = 0; c < len * 2; c++)
buffer[c] += mmb->buffer[c];
mmb->pos = 0;
}
void
mmb_write(uint16_t addr, uint8_t val, void *priv)
{
mmb_t *mmb = (mmb_t *) priv;
mmb_update(mmb);
mmb_log("mmb_write(%04X): activity now: %02X\n", addr, val);
switch (addr & 3) {
case 0:
mmb->first.index = val;
break;
case 2:
mmb->second.index = val;
break;
case 1:
case 3:
{
ay_3_891x_t *ay = ((addr & 2) == 0) ? &mmb->first : &mmb->second;
switch (ay->index) {
case 0:
ay->regs[0] = val;
ayumi_set_tone(&ay->chip, 0, (ay->regs[1] << 8) | ay->regs[0]);
break;
case 1:
ay->regs[1] = val & 0xf;
ayumi_set_tone(&ay->chip, 0, (ay->regs[1] << 8) | ay->regs[0]);
break;
case 2:
ay->regs[2] = val;
ayumi_set_tone(&ay->chip, 1, (ay->regs[3] << 8) | ay->regs[2]);
break;
case 3:
ay->regs[3] = val & 0xf;
ayumi_set_tone(&ay->chip, 1, (ay->regs[3] << 8) | ay->regs[2]);
break;
case 4:
ay->regs[4] = val;
ayumi_set_tone(&ay->chip, 2, (ay->regs[5] << 8) | ay->regs[4]);
break;
case 5:
ay->regs[5] = val & 0xf;
ayumi_set_tone(&ay->chip, 2, (ay->regs[5] << 8) | ay->regs[4]);
break;
case 6:
ay->regs[6] = val & 0x1f;
ayumi_set_noise(&ay->chip, ay->regs[6]);
break;
case 7:
ay->regs[7] = val;
ayumi_set_mixer(&ay->chip, 0, val & 1, (val >> 3) & 1, (ay->regs[8] >> 4) & 1);
ayumi_set_mixer(&ay->chip, 1, (val >> 1) & 1, (val >> 4) & 1, (ay->regs[9] >> 4) & 1);
ayumi_set_mixer(&ay->chip, 2, (val >> 2) & 1, (val >> 5) & 1, (ay->regs[10] >> 4) & 1);
break;
case 8:
ay->regs[8] = val;
ayumi_set_volume(&ay->chip, 0, val & 0xf);
ayumi_set_mixer(&ay->chip, 0, ay->regs[7] & 1, (ay->regs[7] >> 3) & 1, (val >> 4) & 1);
break;
case 9:
ay->regs[9] = val;
ayumi_set_volume(&ay->chip, 1, val & 0xf);
ayumi_set_mixer(&ay->chip, 1, (ay->regs[7] >> 1) & 1, (ay->regs[7] >> 4) & 1, (val >> 4) & 1);
break;
case 10:
ay->regs[10] = val;
ayumi_set_volume(&ay->chip, 2, val & 0xf);
ayumi_set_mixer(&ay->chip, 2, (ay->regs[7] >> 2) & 1, (ay->regs[7] >> 5) & 1, (val >> 4) & 1);
break;
case 11:
ay->regs[11] = val;
ayumi_set_envelope(&ay->chip, (ay->regs[12] >> 8) | ay->regs[11]);
break;
case 12:
ay->regs[12] = val;
ayumi_set_envelope(&ay->chip, (ay->regs[12] >> 8) | ay->regs[11]);
break;
case 13:
ay->regs[13] = val;
ayumi_set_envelope_shape(&ay->chip, val & 0xf);
break;
case 14:
ay->regs[14] = val;
break;
case 15:
ay->regs[15] = val;
break;
default:
break;
}
break;
}
default:
break;
}
}
uint8_t
mmb_read(uint16_t addr, void *priv)
{
mmb_t *mmb = (mmb_t *) priv;
ay_3_891x_t *ay = ((addr & 2) == 0) ? &mmb->first : &mmb->second;
uint8_t ret = 0;
switch (ay->index) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
case 8:
case 9:
case 10:
case 11:
case 12:
case 13:
ret = ay->regs[ay->index];
break;
case 14:
if (ay->regs[7] & 0x40)
ret = ay->regs[14];
break;
case 15:
if (ay->regs[7] & 0x80)
ret = ay->regs[15];
break;
default:
break;
}
mmb_log("mmb_read(%04X): activity now: %02X\n", addr, ret);
return ret;
}
void *
mmb_init(UNUSED(const device_t *info))
{
mmb_t *mmb = calloc(1, sizeof(mmb_t));
# if 0
uint16_t addr = (device_get_config_int("addr96") << 6) | (device_get_config_int("addr52") << 2);
#else
uint16_t addr = 0x300;
#endif
sound_add_handler(mmb_get_buffer, mmb);
ayumi_configure(&mmb->first.chip, 0, MMB_CLOCK, MMB_FREQ);
ayumi_configure(&mmb->second.chip, 0, MMB_CLOCK, MMB_FREQ);
for (uint8_t i = 0; i < 3; i++) {
ayumi_set_pan(&mmb->first.chip, i, 0.5, 1);
ayumi_set_pan(&mmb->second.chip, i, 0.5, 1);
}
io_sethandler(addr, 0x0004,
mmb_read, NULL, NULL,
mmb_write, NULL, NULL,
mmb);
return mmb;
}
void
mmb_close(void *priv)
{
mmb_t *mmb = (mmb_t *) priv;
free(mmb);
}
// clang-format off
#if 0
static device_config_t mmb_config[] = {
{
.name = "addr96",
.description = "Base address A9...A6",
.type = CONFIG_SELECTION,
.default_string = NULL,
.default_int = 12,
.file_filter = NULL,
.spinner = { 0 },
.selection = {
{ .description = "0000", .value = 0 },
{ .description = "0001", .value = 1 },
{ .description = "0010", .value = 2 },
{ .description = "0011", .value = 3 },
{ .description = "0100", .value = 4 },
{ .description = "0101", .value = 5 },
{ .description = "0110", .value = 6 },
{ .description = "0111", .value = 7 },
{ .description = "1000", .value = 8 },
{ .description = "1001", .value = 9 },
{ .description = "1010", .value = 10 },
{ .description = "1011", .value = 11 },
{ .description = "1100", .value = 12 },
{ .description = "1101", .value = 13 },
{ .description = "1110", .value = 14 },
{ .description = "1111", .value = 15 },
{ .description = "" }
},
.bios = { { 0 } }
},
{
.name = "addr52",
.description = "Base address A5...A2",
.type = CONFIG_SELECTION,
.default_string = NULL,
.default_int = 0,
.file_filter = NULL,
.spinner = { 0 },
.selection = {
{ .description = "0000", .value = 0 },
{ .description = "0001", .value = 1 },
{ .description = "0010", .value = 2 },
{ .description = "0011", .value = 3 },
{ .description = "0100", .value = 4 },
{ .description = "0101", .value = 5 },
{ .description = "0110", .value = 6 },
{ .description = "0111", .value = 7 },
{ .description = "1000", .value = 8 },
{ .description = "1001", .value = 9 },
{ .description = "1010", .value = 10 },
{ .description = "1011", .value = 11 },
{ .description = "1100", .value = 12 },
{ .description = "1101", .value = 13 },
{ .description = "1110", .value = 14 },
{ .description = "1111", .value = 15 },
{ .description = "" }
},
.bios = { { 0 } }
},
{ .type = CONFIG_END }
};
#endif
// clang-format on
const device_t mmb_device = {
.name = "Mindscape Music Board",
.internal_name = "mmb",
.flags = DEVICE_ISA,
.local = 0,
.init = mmb_init,
.close = mmb_close,
.reset = NULL,
.available = NULL,
.speed_changed = NULL,
.force_redraw = NULL,
#if 0
.config = mmb_config
#else
.config = NULL
#endif
};