qemu-e2k/hw/audio/asc.c
Mark Cave-Ayland 5243e5543e asc: generate silence if FIFO empty but engine still running
MacOS (un)helpfully leaves the FIFO engine running even when all the samples have
been written to the hardware, and expects the FIFO status flags and IRQ to be
updated continuously.

There is an additional problem in that not all audio backends guarantee an
all-zero output when there is no FIFO data available, in particular the Windows
dsound backend which re-uses its internal circular buffer causing the last played
sound to loop indefinitely.

Whilst this is effectively a bug in the Windows dsound backend, work around it
for now using a simple heuristic: if the FIFO remains empty for half a cycle
(~23ms) then continuously fill the generated buffer with empty silence.

Signed-off-by: Mark Cave-Ayland <mark.cave-ayland@ilande.co.uk>
Reviewed-by: Laurent Vivier <laurent@vivier.eu>
Message-ID: <20231004083806.757242-9-mark.cave-ayland@ilande.co.uk>
Signed-off-by: Laurent Vivier <laurent@vivier.eu>
2023-10-06 10:33:43 +02:00

728 lines
20 KiB
C

/*
* QEMU Apple Sound Chip emulation
*
* Apple Sound Chip (ASC) 344S0063
* Enhanced Apple Sound Chip (EASC) 343S1063
*
* Copyright (c) 2012-2018 Laurent Vivier <laurent@vivier.eu>
* Copyright (c) 2022 Mark Cave-Ayland <mark.cave-ayland@ilande.co.uk>
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
#include "qemu/timer.h"
#include "hw/sysbus.h"
#include "hw/irq.h"
#include "audio/audio.h"
#include "hw/audio/asc.h"
#include "hw/qdev-properties.h"
#include "migration/vmstate.h"
#include "trace.h"
/*
* Linux doesn't provide information about ASC, see arch/m68k/mac/macboing.c
* and arch/m68k/include/asm/mac_asc.h
*
* best information is coming from MAME:
* https://github.com/mamedev/mame/blob/master/src/devices/sound/asc.h
* https://github.com/mamedev/mame/blob/master/src/devices/sound/asc.cpp
* Emulation by R. Belmont
* or MESS:
* http://mess.redump.net/mess/driver_info/easc
*
* 0x800: VERSION
* 0x801: MODE
* 1=FIFO mode,
* 2=wavetable mode
* 0x802: CONTROL
* bit 0=analog or PWM output,
* 1=stereo/mono,
* 7=processing time exceeded
* 0x803: FIFO MODE
* bit 7=clear FIFO,
* bit 1="non-ROM companding",
* bit 0="ROM companding")
* 0x804: FIFO IRQ STATUS
* bit 0=ch A 1/2 full,
* 1=ch A full,
* 2=ch B 1/2 full,
* 3=ch B full)
* 0x805: WAVETABLE CONTROL
* bits 0-3 wavetables 0-3 start
* 0x806: VOLUME
* bits 2-4 = 3 bit internal ASC volume,
* bits 5-7 = volume control sent to Sony sound chip
* 0x807: CLOCK RATE
* 0 = Mac 22257 Hz,
* 1 = undefined,
* 2 = 22050 Hz,
* 3 = 44100 Hz
* 0x80a: PLAY REC A
* 0x80f: TEST
* bits 6-7 = digital test,
* bits 4-5 = analog test
* 0x810: WAVETABLE 0 PHASE
* big-endian 9.15 fixed-point, only 24 bits valid
* 0x814: WAVETABLE 0 INCREMENT
* big-endian 9.15 fixed-point, only 24 bits valid
* 0x818: WAVETABLE 1 PHASE
* 0x81C: WAVETABLE 1 INCREMENT
* 0x820: WAVETABLE 2 PHASE
* 0x824: WAVETABLE 2 INCREMENT
* 0x828: WAVETABLE 3 PHASE
* 0x82C: WAVETABLE 3 INCREMENT
* 0x830: UNKNOWN START
* NetBSD writes Wavetable data here (are there more
* wavetables/channels than we know about?)
* 0x857: UNKNOWN END
*/
#define ASC_SIZE 0x2000
enum {
ASC_VERSION = 0x00,
ASC_MODE = 0x01,
ASC_CONTROL = 0x02,
ASC_FIFOMODE = 0x03,
ASC_FIFOIRQ = 0x04,
ASC_WAVECTRL = 0x05,
ASC_VOLUME = 0x06,
ASC_CLOCK = 0x07,
ASC_PLAYRECA = 0x0a,
ASC_TEST = 0x0f,
ASC_WAVETABLE = 0x10
};
#define ASC_FIFO_STATUS_HALF_FULL 1
#define ASC_FIFO_STATUS_FULL_EMPTY 2
#define ASC_EXTREGS_FIFOCTRL 0x8
#define ASC_EXTREGS_INTCTRL 0x9
#define ASC_EXTREGS_CDXA_DECOMP_FILT 0x10
#define ASC_FIFO_CYCLE_TIME ((NANOSECONDS_PER_SECOND / ASC_FREQ) * \
0x400)
static void asc_raise_irq(ASCState *s)
{
qemu_set_irq(s->irq, 1);
}
static void asc_lower_irq(ASCState *s)
{
qemu_set_irq(s->irq, 0);
}
static uint8_t asc_fifo_get(ASCFIFOState *fs)
{
ASCState *s = container_of(fs, ASCState, fifos[fs->index]);
bool fifo_half_irq_enabled = fs->extregs[ASC_EXTREGS_INTCTRL] & 1;
uint8_t val;
assert(fs->cnt);
val = fs->fifo[fs->rptr];
trace_asc_fifo_get('A' + fs->index, fs->rptr, fs->cnt, val);
fs->rptr++;
fs->rptr &= 0x3ff;
fs->cnt--;
if (fs->cnt <= 0x1ff) {
/* FIFO less than half full */
fs->int_status |= ASC_FIFO_STATUS_HALF_FULL;
} else {
/* FIFO more than half full */
fs->int_status &= ~ASC_FIFO_STATUS_HALF_FULL;
}
if (fs->cnt == 0x1ff && fifo_half_irq_enabled) {
/* Raise FIFO half full IRQ */
asc_raise_irq(s);
}
if (fs->cnt == 0) {
/* Raise FIFO empty IRQ */
fs->int_status |= ASC_FIFO_STATUS_FULL_EMPTY;
asc_raise_irq(s);
}
return val;
}
static int generate_fifo(ASCState *s, int maxsamples)
{
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
uint8_t *buf = s->mixbuf;
int i, wcount = 0;
while (wcount < maxsamples) {
uint8_t val;
int16_t d, f0, f1;
int32_t t;
int shift, filter;
bool hasdata = false;
for (i = 0; i < 2; i++) {
ASCFIFOState *fs = &s->fifos[i];
switch (fs->extregs[ASC_EXTREGS_FIFOCTRL] & 0x83) {
case 0x82:
/*
* CD-XA BRR mode: decompress 15 bytes into 28 16-bit
* samples
*/
if (!fs->cnt) {
val = 0x80;
break;
}
if (fs->xa_cnt == -1) {
/* Start of packet, get flags */
fs->xa_flags = asc_fifo_get(fs);
fs->xa_cnt = 0;
}
shift = fs->xa_flags & 0xf;
filter = fs->xa_flags >> 4;
f0 = (int8_t)fs->extregs[ASC_EXTREGS_CDXA_DECOMP_FILT +
(filter << 1) + 1];
f1 = (int8_t)fs->extregs[ASC_EXTREGS_CDXA_DECOMP_FILT +
(filter << 1)];
if ((fs->xa_cnt & 1) == 0) {
if (!fs->cnt) {
val = 0x80;
break;
}
fs->xa_val = asc_fifo_get(fs);
d = (fs->xa_val & 0xf) << 12;
} else {
d = (fs->xa_val & 0xf0) << 8;
}
t = (d >> shift) + (((fs->xa_last[0] * f0) +
(fs->xa_last[1] * f1) + 32) >> 6);
if (t < -32768) {
t = -32768;
} else if (t > 32767) {
t = 32767;
}
/*
* CD-XA BRR generates 16-bit signed output, so convert to
* 8-bit before writing to buffer. Does real hardware do the
* same?
*/
val = (uint8_t)(t / 256) ^ 0x80;
hasdata = true;
fs->xa_cnt++;
fs->xa_last[1] = fs->xa_last[0];
fs->xa_last[0] = (int16_t)t;
if (fs->xa_cnt == 28) {
/* End of packet */
fs->xa_cnt = -1;
}
break;
default:
/* fallthrough */
case 0x80:
/* Raw mode */
if (fs->cnt) {
val = asc_fifo_get(fs);
hasdata = true;
} else {
val = 0x80;
}
break;
}
buf[wcount * 2 + i] = val;
}
if (!hasdata) {
break;
}
wcount++;
}
/*
* MacOS (un)helpfully leaves the FIFO engine running even when it has
* finished writing out samples, but still expects the FIFO empty
* interrupts to be generated for each FIFO cycle (without these interrupts
* MacOS will freeze)
*/
if (s->fifos[0].cnt == 0 && s->fifos[1].cnt == 0) {
if (!s->fifo_empty_ns) {
/* FIFO has completed first empty cycle */
s->fifo_empty_ns = now;
} else if (now > (s->fifo_empty_ns + ASC_FIFO_CYCLE_TIME)) {
/* FIFO has completed entire cycle with no data */
s->fifos[0].int_status |= ASC_FIFO_STATUS_HALF_FULL |
ASC_FIFO_STATUS_FULL_EMPTY;
s->fifos[1].int_status |= ASC_FIFO_STATUS_HALF_FULL |
ASC_FIFO_STATUS_FULL_EMPTY;
s->fifo_empty_ns = now;
asc_raise_irq(s);
}
} else {
/* FIFO contains data, reset empty time */
s->fifo_empty_ns = 0;
}
return wcount;
}
static int generate_wavetable(ASCState *s, int maxsamples)
{
uint8_t *buf = s->mixbuf;
int channel, count = 0;
while (count < maxsamples) {
uint32_t left = 0, right = 0;
uint8_t sample;
for (channel = 0; channel < 4; channel++) {
ASCFIFOState *fs = &s->fifos[channel >> 1];
int chanreg = ASC_WAVETABLE + (channel << 3);
uint32_t phase, incr, offset;
phase = ldl_be_p(&s->regs[chanreg]);
incr = ldl_be_p(&s->regs[chanreg + sizeof(uint32_t)]);
phase += incr;
offset = (phase >> 15) & 0x1ff;
sample = fs->fifo[0x200 * (channel >> 1) + offset];
stl_be_p(&s->regs[chanreg], phase);
left += sample;
right += sample;
}
buf[count * 2] = left >> 2;
buf[count * 2 + 1] = right >> 2;
count++;
}
return count;
}
static void asc_out_cb(void *opaque, int free_b)
{
ASCState *s = opaque;
int samples, generated;
if (free_b == 0) {
return;
}
samples = MIN(s->samples, free_b >> s->shift);
switch (s->regs[ASC_MODE] & 3) {
default:
/* Off */
generated = 0;
break;
case 1:
/* FIFO mode */
generated = generate_fifo(s, samples);
break;
case 2:
/* Wave table mode */
generated = generate_wavetable(s, samples);
break;
}
if (!generated) {
/* Workaround for audio underflow bug on Windows dsound backend */
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
int silent_samples = muldiv64(now - s->fifo_empty_ns,
NANOSECONDS_PER_SECOND, ASC_FREQ);
if (silent_samples > ASC_FIFO_CYCLE_TIME / 2) {
/*
* No new FIFO data within half a cycle time (~23ms) so fill the
* entire available buffer with silence. This prevents an issue
* with the Windows dsound backend whereby the sound appears to
* loop because the FIFO has run out of data, and the driver
* reuses the stale content in its circular audio buffer.
*/
AUD_write(s->voice, s->silentbuf, samples << s->shift);
}
return;
}
AUD_write(s->voice, s->mixbuf, generated << s->shift);
}
static uint64_t asc_fifo_read(void *opaque, hwaddr addr,
unsigned size)
{
ASCFIFOState *fs = opaque;
trace_asc_read_fifo('A' + fs->index, addr, size, fs->fifo[addr]);
return fs->fifo[addr];
}
static void asc_fifo_write(void *opaque, hwaddr addr, uint64_t value,
unsigned size)
{
ASCFIFOState *fs = opaque;
ASCState *s = container_of(fs, ASCState, fifos[fs->index]);
bool fifo_half_irq_enabled = fs->extregs[ASC_EXTREGS_INTCTRL] & 1;
trace_asc_write_fifo('A' + fs->index, addr, size, fs->wptr, fs->cnt, value);
if (s->regs[ASC_MODE] == 1) {
fs->fifo[fs->wptr++] = value;
fs->wptr &= 0x3ff;
fs->cnt++;
if (fs->cnt <= 0x1ff) {
/* FIFO less than half full */
fs->int_status |= ASC_FIFO_STATUS_HALF_FULL;
} else {
/* FIFO at least half full */
fs->int_status &= ~ASC_FIFO_STATUS_HALF_FULL;
}
if (fs->cnt == 0x200 && fifo_half_irq_enabled) {
/* Raise FIFO half full interrupt */
asc_raise_irq(s);
}
if (fs->cnt == 0x3ff) {
/* Raise FIFO full interrupt */
fs->int_status |= ASC_FIFO_STATUS_FULL_EMPTY;
asc_raise_irq(s);
}
} else {
fs->fifo[addr] = value;
}
return;
}
static const MemoryRegionOps asc_fifo_ops = {
.read = asc_fifo_read,
.write = asc_fifo_write,
.impl = {
.min_access_size = 1,
.max_access_size = 1,
},
.endianness = DEVICE_BIG_ENDIAN,
};
static void asc_fifo_reset(ASCFIFOState *fs);
static uint64_t asc_read(void *opaque, hwaddr addr,
unsigned size)
{
ASCState *s = opaque;
uint64_t prev, value;
switch (addr) {
case ASC_VERSION:
switch (s->type) {
default:
case ASC_TYPE_ASC:
value = 0;
break;
case ASC_TYPE_EASC:
value = 0xb0;
break;
}
break;
case ASC_FIFOIRQ:
prev = (s->fifos[0].int_status & 0x3) |
(s->fifos[1].int_status & 0x3) << 2;
s->fifos[0].int_status = 0;
s->fifos[1].int_status = 0;
asc_lower_irq(s);
value = prev;
break;
default:
value = s->regs[addr];
break;
}
trace_asc_read_reg(addr, size, value);
return value;
}
static void asc_write(void *opaque, hwaddr addr, uint64_t value,
unsigned size)
{
ASCState *s = opaque;
switch (addr) {
case ASC_MODE:
value &= 3;
if (value != s->regs[ASC_MODE]) {
asc_fifo_reset(&s->fifos[0]);
asc_fifo_reset(&s->fifos[1]);
asc_lower_irq(s);
if (value != 0) {
AUD_set_active_out(s->voice, 1);
} else {
AUD_set_active_out(s->voice, 0);
}
}
break;
case ASC_FIFOMODE:
if (value & 0x80) {
asc_fifo_reset(&s->fifos[0]);
asc_fifo_reset(&s->fifos[1]);
asc_lower_irq(s);
}
break;
case ASC_WAVECTRL:
break;
case ASC_VOLUME:
{
int vol = (value & 0xe0);
AUD_set_volume_out(s->voice, 0, vol, vol);
break;
}
}
trace_asc_write_reg(addr, size, value);
s->regs[addr] = value;
}
static const MemoryRegionOps asc_regs_ops = {
.read = asc_read,
.write = asc_write,
.endianness = DEVICE_BIG_ENDIAN,
.impl = {
.min_access_size = 1,
.max_access_size = 1,
}
};
static uint64_t asc_ext_read(void *opaque, hwaddr addr,
unsigned size)
{
ASCFIFOState *fs = opaque;
uint64_t value;
value = fs->extregs[addr];
trace_asc_read_extreg('A' + fs->index, addr, size, value);
return value;
}
static void asc_ext_write(void *opaque, hwaddr addr, uint64_t value,
unsigned size)
{
ASCFIFOState *fs = opaque;
trace_asc_write_extreg('A' + fs->index, addr, size, value);
fs->extregs[addr] = value;
}
static const MemoryRegionOps asc_extregs_ops = {
.read = asc_ext_read,
.write = asc_ext_write,
.impl = {
.min_access_size = 1,
.max_access_size = 1,
},
.endianness = DEVICE_BIG_ENDIAN,
};
static int asc_post_load(void *opaque, int version)
{
ASCState *s = ASC(opaque);
if (s->regs[ASC_MODE] != 0) {
AUD_set_active_out(s->voice, 1);
}
return 0;
}
static const VMStateDescription vmstate_asc_fifo = {
.name = "apple-sound-chip.fifo",
.version_id = 0,
.minimum_version_id = 0,
.fields = (VMStateField[]) {
VMSTATE_UINT8_ARRAY(fifo, ASCFIFOState, ASC_FIFO_SIZE),
VMSTATE_UINT8(int_status, ASCFIFOState),
VMSTATE_INT32(cnt, ASCFIFOState),
VMSTATE_INT32(wptr, ASCFIFOState),
VMSTATE_INT32(rptr, ASCFIFOState),
VMSTATE_UINT8_ARRAY(extregs, ASCFIFOState, ASC_EXTREG_SIZE),
VMSTATE_INT32(xa_cnt, ASCFIFOState),
VMSTATE_UINT8(xa_val, ASCFIFOState),
VMSTATE_UINT8(xa_flags, ASCFIFOState),
VMSTATE_INT16_ARRAY(xa_last, ASCFIFOState, 2),
VMSTATE_END_OF_LIST()
}
};
static const VMStateDescription vmstate_asc = {
.name = "apple-sound-chip",
.version_id = 0,
.minimum_version_id = 0,
.post_load = asc_post_load,
.fields = (VMStateField[]) {
VMSTATE_STRUCT_ARRAY(fifos, ASCState, 2, 0, vmstate_asc_fifo,
ASCFIFOState),
VMSTATE_UINT8_ARRAY(regs, ASCState, ASC_REG_SIZE),
VMSTATE_INT64(fifo_empty_ns, ASCState),
VMSTATE_END_OF_LIST()
}
};
static void asc_fifo_reset(ASCFIFOState *fs)
{
fs->wptr = 0;
fs->rptr = 0;
fs->cnt = 0;
fs->xa_cnt = -1;
fs->int_status = 0;
}
static void asc_fifo_init(ASCFIFOState *fs, int index)
{
ASCState *s = container_of(fs, ASCState, fifos[index]);
char *name;
fs->index = index;
name = g_strdup_printf("asc.fifo%c", 'A' + index);
memory_region_init_io(&fs->mem_fifo, OBJECT(s), &asc_fifo_ops, fs,
name, ASC_FIFO_SIZE);
g_free(name);
name = g_strdup_printf("asc.extregs%c", 'A' + index);
memory_region_init_io(&fs->mem_extregs, OBJECT(s), &asc_extregs_ops,
fs, name, ASC_EXTREG_SIZE);
g_free(name);
}
static void asc_reset_hold(Object *obj)
{
ASCState *s = ASC(obj);
AUD_set_active_out(s->voice, 0);
memset(s->regs, 0, sizeof(s->regs));
asc_fifo_reset(&s->fifos[0]);
asc_fifo_reset(&s->fifos[1]);
s->fifo_empty_ns = 0;
if (s->type == ASC_TYPE_ASC) {
/* FIFO half full IRQs enabled by default */
s->fifos[0].extregs[ASC_EXTREGS_INTCTRL] = 1;
s->fifos[1].extregs[ASC_EXTREGS_INTCTRL] = 1;
}
}
static void asc_unrealize(DeviceState *dev)
{
ASCState *s = ASC(dev);
g_free(s->mixbuf);
g_free(s->silentbuf);
AUD_remove_card(&s->card);
}
static void asc_realize(DeviceState *dev, Error **errp)
{
ASCState *s = ASC(dev);
struct audsettings as;
if (!AUD_register_card("Apple Sound Chip", &s->card, errp)) {
return;
}
as.freq = ASC_FREQ;
as.nchannels = 2;
as.fmt = AUDIO_FORMAT_U8;
as.endianness = AUDIO_HOST_ENDIANNESS;
s->voice = AUD_open_out(&s->card, s->voice, "asc.out", s, asc_out_cb,
&as);
s->shift = 1;
s->samples = AUD_get_buffer_size_out(s->voice) >> s->shift;
s->mixbuf = g_malloc0(s->samples << s->shift);
s->silentbuf = g_malloc0(s->samples << s->shift);
memset(s->silentbuf, 0x80, s->samples << s->shift);
/* Add easc registers if required */
if (s->type == ASC_TYPE_EASC) {
memory_region_add_subregion(&s->asc, ASC_EXTREG_OFFSET,
&s->fifos[0].mem_extregs);
memory_region_add_subregion(&s->asc,
ASC_EXTREG_OFFSET + ASC_EXTREG_SIZE,
&s->fifos[1].mem_extregs);
}
}
static void asc_init(Object *obj)
{
ASCState *s = ASC(obj);
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
memory_region_init(&s->asc, OBJECT(obj), "asc", ASC_SIZE);
asc_fifo_init(&s->fifos[0], 0);
asc_fifo_init(&s->fifos[1], 1);
memory_region_add_subregion(&s->asc, ASC_FIFO_OFFSET,
&s->fifos[0].mem_fifo);
memory_region_add_subregion(&s->asc,
ASC_FIFO_OFFSET + ASC_FIFO_SIZE,
&s->fifos[1].mem_fifo);
memory_region_init_io(&s->mem_regs, OBJECT(obj), &asc_regs_ops, s,
"asc.regs", ASC_REG_SIZE);
memory_region_add_subregion(&s->asc, ASC_REG_OFFSET, &s->mem_regs);
sysbus_init_irq(sbd, &s->irq);
sysbus_init_mmio(sbd, &s->asc);
}
static Property asc_properties[] = {
DEFINE_AUDIO_PROPERTIES(ASCState, card),
DEFINE_PROP_UINT8("asctype", ASCState, type, ASC_TYPE_ASC),
DEFINE_PROP_END_OF_LIST(),
};
static void asc_class_init(ObjectClass *oc, void *data)
{
DeviceClass *dc = DEVICE_CLASS(oc);
ResettableClass *rc = RESETTABLE_CLASS(oc);
dc->realize = asc_realize;
dc->unrealize = asc_unrealize;
set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
dc->vmsd = &vmstate_asc;
device_class_set_props(dc, asc_properties);
rc->phases.hold = asc_reset_hold;
}
static const TypeInfo asc_info_types[] = {
{
.name = TYPE_ASC,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(ASCState),
.instance_init = asc_init,
.class_init = asc_class_init,
},
};
DEFINE_TYPES(asc_info_types)