/*
 * 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.
 *
 *          Ensoniq AudioPCI family emulation.
 *
 * Authors: Sarah Walker, <https://pcem-emulator.co.uk/>
 *          RichardG, <richardg867@gmail.com>
 *          Miran Grca, <mgrca8@gmail.com>
 *          Jasmine Iwanek, <jriwanek@gmail.com>
 *          Cacodemon345
 *
 *          Copyright 2008-2021 Sarah Walker.
 *          Copyright 2021-2024 RichardG.
 *          Copyright 2021      Miran Grca.
 *          Copyright 2024-2025 Jasmine Iwanek.
 *          Copyright 2024-2025 Cacodemon345.
 */
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define _USE_MATH_DEFINES
#include <math.h>
#define HAVE_STDARG_H

#include <86box/86box.h>
#include <86box/device.h>
#include <86box/gameport.h>
#include <86box/io.h>
#include <86box/mem.h>
#include <86box/midi.h>
#include <86box/nmi.h>
#include <86box/pci.h>
#include <86box/snd_ac97.h>
#include <86box/sound.h>
#include "cpu.h"
#include <86box/timer.h>
#include <86box/plat_unused.h>
#include <86box/snd_akm4531.h>

#define N            16

#define ES137x_NCoef 91

static float low_fir_es137x_coef[ES137x_NCoef];

typedef struct es137x_t {
    uint8_t pci_command;
    uint8_t pci_serr;

    uint8_t subsys_lock;
    uint8_t subsys_id[4];

    uint32_t base_addr;

    uint8_t int_line;
    uint8_t irq_state;

    uint16_t pmcsr;

    uint32_t int_ctrl;
    uint32_t int_status;
    uint32_t legacy_ctrl;
    uint32_t spdif_chstatus;
    void    *gameport;

    int mem_page;

    uint32_t si_cr;

    uint32_t sr_cir;
    uint16_t sr_ram[128];

    uint8_t  uart_data;
    uint8_t  uart_ctrl;
    uint8_t  uart_status;
    uint8_t  uart_res;
    uint32_t uart_fifo[8];
    uint8_t  read_fifo_pos;
    uint8_t  write_fifo_pos;

    ac97_codec_t *codec;
    uint32_t      codec_ctrl;

    struct {
        uint32_t addr;
        uint32_t addr_latch;
        uint16_t count;
        uint16_t size;

        uint16_t samp_ct;
        int      curr_samp_ct;

        pc_timer_t timer;
        uint64_t   latch;

        uint32_t vf;
        uint32_t ac;

        int16_t buffer_l[64];
        int16_t buffer_r[64];
        int     buffer_pos;
        int     buffer_pos_end;

        int filtered_l[32];
        int filtered_r[32];
        int f_pos;

        int16_t out_l;
        int16_t out_r;

        int16_t prev_out_l;
        int16_t prev_out_r;

        int32_t vol_l;
        int32_t vol_r;
    } dac[2], adc;

    int64_t dac_latch;
    int64_t dac_time;

    int master_vol_l;
    int master_vol_r;
    int pcm_vol_l;
    int pcm_vol_r;
    int pcm_rear_vol_l;
    int pcm_rear_vol_r;
    int cd_vol_l;
    int cd_vol_r;

    uint8_t pci_slot;

    int     pos;
    int16_t buffer[WTBUFLEN * 2];

    uint32_t type;

    akm4531_t akm_codec;

    uint32_t calc_sample_rate;
    uint32_t calc_sample_rate_synth;

    double   interp_factor;
    uint32_t interp_step;

    double   interp_factor_synth;
    uint32_t interp_step_synth;

    uint32_t step_pcm;
    uint32_t step_synth;
} es137x_t;

static const double akm4531_att_2dbstep_5bits[] = {
    // clang-format off
       25.0,    32.0,    41.0,    51.0,    65.0,    82.0,   103.0,   130.0,
      164.0,   206.0,   260.0,   327.0,   412.0,   519.0,   653.0,   822.0,
     1036.0,  1304.0,  1641.0,  2067.0,  2602.0,  3276.0,  4125.0,  5192.0,
     6537.0,  8230.0, 10362.0, 13044.0, 16422.0, 20674.0, 26027.0, 32767.0
    // clang-format on
};

static double akm4531_gain_2dbstep_5bits[0x20];

#define AUDIOPCI_ES1370           0x50000000
#define AUDIOPCI_ES1371           0x13710200
#define AUDIOPCI_ES1373           0x13710400
#define AUDIOPCI_CT5880           0x58800400

#define LEGACY_SB_ADDR            (1 << 29)
#define LEGACY_SSCAPE_ADDR_SHIFT  27
#define LEGACY_CODEC_ADDR_SHIFT   25
#define LEGACY_FORCE_IRQ          (1 << 24)
#define LEGACY_CAPTURE_SLAVE_DMA  (1 << 23)
#define LEGACY_CAPTURE_SLAVE_PIC  (1 << 22)
#define LEGACY_CAPTURE_MASTER_DMA (1 << 21)
#define LEGACY_CAPTURE_MASTER_PIC (1 << 20)
#define LEGACY_CAPTURE_ADLIB      (1 << 19)
#define LEGACY_CAPTURE_SB         (1 << 18)
#define LEGACY_CAPTURE_CODEC      (1 << 17)
#define LEGACY_CAPTURE_SSCAPE     (1 << 16)
#define LEGACY_EVENT_SSCAPE       (0 << 8)
#define LEGACY_EVENT_CODEC        (1 << 8)
#define LEGACY_EVENT_SB           (2 << 8)
#define LEGACY_EVENT_ADLIB        (3 << 8)
#define LEGACY_EVENT_MASTER_PIC   (4 << 8)
#define LEGACY_EVENT_MASTER_DMA   (5 << 8)
#define LEGACY_EVENT_SLAVE_PIC    (6 << 8)
#define LEGACY_EVENT_SLAVE_DMA    (7 << 8)
#define LEGACY_EVENT_MASK         (7 << 8)
#define LEGACY_EVENT_ADDR_SHIFT   3
#define LEGACY_EVENT_ADDR_MASK    (0x1f << 3)
#define LEGACY_EVENT_TYPE_RW      (1 << 2)
#define LEGACY_INT                (1 << 0)

#define SRC_RAM_WE                (1 << 24)

#define CODEC_READ                (1 << 23)
#define CODEC_READY               (1 << 31)

#define INT_DAC1_BYPASS           (1 << 31)
#define INT_DAC2_BYPASS           (1 << 30)
#define INT_DAC1_EN               (1 << 6)
#define INT_DAC2_EN               (1 << 5)
#define INT_UART_EN               (1 << 3)

#define SI_P2_PAUSE               (1 << 12)
#define SI_P1_PAUSE               (1 << 11)
#define SI_P2_INTR_EN             (1 << 9)
#define SI_P1_INTR_EN             (1 << 8)

#define INT_STATUS_INTR           (1 << 31)
#define INT_STATUS_REAR_B27       (1 << 27)
#define INT_STATUS_REAR_B26       (1 << 26)
#define INT_STATUS_REAR_B24       (1 << 24)
#define INT_STATUS_UART           (1 << 3)
#define INT_STATUS_DAC1           (1 << 2)
#define INT_STATUS_DAC2           (1 << 1)

#define UART_CTRL_RXINTEN         (1 << 7)
#define UART_CTRL_TXINTEN         (3 << 5)

#define UART_STATUS_RXINT         (1 << 7)
#define UART_STATUS_TXINT         (1 << 2)
#define UART_STATUS_TXRDY         (1 << 1)
#define UART_STATUS_RXRDY         (1 << 0)

#define UART_FIFO_BYTE_VALID      0x00000100

#define FORMAT_MONO_8             0
#define FORMAT_STEREO_8           1
#define FORMAT_MONO_16            2
#define FORMAT_STEREO_16          3

static void es137x_fetch(es137x_t *dev, int dac_nr);
static void update_legacy(es137x_t *dev, uint32_t old_legacy_ctrl);

#ifdef ENABLE_AUDIOPCI_LOG
int audiopci_do_log = ENABLE_AUDIOPCI_LOG;

static void
audiopci_log(const char *fmt, ...)
{
    va_list ap;

    if (audiopci_do_log) {
        va_start(ap, fmt);
        pclog_ex(fmt, ap);
        va_end(ap);
    }
}
#else
#    define audiopci_log(fmt, ...)
#endif

static void
es137x_update_irqs(es137x_t *dev)
{
    int irq = 0;

    if ((dev->int_status & INT_STATUS_DAC1) && (dev->si_cr & SI_P1_INTR_EN))
        irq = 1;
    if ((dev->int_status & INT_STATUS_DAC2) && (dev->si_cr & SI_P2_INTR_EN))
        irq = 1;

    dev->int_status &= ~INT_STATUS_UART;

    if ((dev->uart_status & UART_STATUS_TXINT) || (dev->uart_status & UART_STATUS_RXINT)) {
        dev->int_status |= INT_STATUS_UART;
        irq = 1;
    }

    if (irq)
        dev->int_status |= INT_STATUS_INTR;
    else
        dev->int_status &= ~INT_STATUS_INTR;

    if (dev->legacy_ctrl & LEGACY_FORCE_IRQ)
        irq = 1;

    if (irq)
        pci_set_irq(dev->pci_slot, PCI_INTA, &dev->irq_state);
    else
        pci_clear_irq(dev->pci_slot, PCI_INTA, &dev->irq_state);
}

static void
es137x_update_tx_irq(es137x_t *dev)
{
    dev->uart_status &= ~UART_STATUS_TXINT;

    if (((dev->uart_ctrl & UART_CTRL_TXINTEN) == 0x20) && (dev->uart_status & UART_STATUS_TXRDY))
        dev->uart_status |= UART_STATUS_TXINT;

    es137x_update_irqs(dev);
}

static void
es137x_set_tx_irq(es137x_t *dev, int set)
{
    dev->uart_status &= ~UART_STATUS_TXRDY;

    if (set)
        dev->uart_status |= UART_STATUS_TXRDY;

    es137x_update_tx_irq(dev);
}

static void
es137x_update_rx_irq(es137x_t *dev)
{
    dev->uart_status &= ~UART_STATUS_RXINT;

    if ((dev->uart_ctrl & UART_CTRL_RXINTEN) && (dev->uart_status & UART_STATUS_RXRDY))
        dev->uart_status |= UART_STATUS_RXINT;

    es137x_update_irqs(dev);
}

static void
es137x_set_rx_irq(es137x_t *dev, int set)
{
    dev->uart_status &= ~UART_STATUS_RXRDY;

    if (set)
        dev->uart_status |= UART_STATUS_RXRDY;

    es137x_update_rx_irq(dev);
}

static void
es137x_scan_fifo(es137x_t *dev)
{
    if (dev->read_fifo_pos != dev->write_fifo_pos) {
        dev->uart_data     = dev->uart_fifo[dev->read_fifo_pos];
        dev->read_fifo_pos = (dev->read_fifo_pos + 1) & 7;

        es137x_set_rx_irq(dev, 1);
    } else
        es137x_set_rx_irq(dev, 0);
}

static void
es137x_write_fifo(es137x_t *dev, uint8_t val)
{
    if (dev->write_fifo_pos < 8) {
        dev->uart_fifo[dev->write_fifo_pos] = val | UART_FIFO_BYTE_VALID;
        dev->write_fifo_pos                 = (dev->write_fifo_pos + 1) & 7;
    }
}

static void
es137x_reset_fifo(es137x_t *dev)
{
    for (uint8_t i = 0; i < 8; i++)
        dev->uart_fifo[i] = 0x00000000;

    dev->read_fifo_pos = dev->write_fifo_pos = 0;

    es137x_set_rx_irq(dev, 0);
}

static void
akm4531_reset(es137x_t *dev)
{
    akm4531_t *codec = &dev->akm_codec;

    memset(codec->registers, 0, sizeof(codec->registers));

    codec->registers[0] = 0x80;
    codec->registers[1] = 0x80;

    for (int i = 0x02; i <= 0x0E; i++) {
        codec->registers[i] = 0b10000110;
    }

    codec->registers[0xf] = 0x80;

    codec->registers[0x17] = 0x3;
    codec->registers[0x16] = 0x3;
}

static double
lerp(double v0, double v1, double t)
{
    return (1. - t) * v0 + t * v1;
}

static void
es1370_calc_sample_rate(es137x_t *dev)
{
    if (dev->type != AUDIOPCI_ES1370)
        return;

    dev->calc_sample_rate = 1411200 / (((dev->int_ctrl >> 16) & 0x1fff) + 2);

    // audiopci_log("ES1370 calc sample rate %u\n", dev->calc_sample_rate);

    dev->interp_factor = 1.0;
    dev->interp_step   = 1;

    if (dev->calc_sample_rate >= 44100 || dev->calc_sample_rate < 11025) {
        dev->interp_factor    = 1.0;
        dev->interp_step      = 1;
        dev->calc_sample_rate = 44100;
    }
    if (dev->calc_sample_rate == 22050) {
        dev->interp_factor = 0.5;
        dev->interp_step   = 2;
    }
    if (dev->calc_sample_rate == 11025) {
        dev->interp_factor = 0.25;
        dev->interp_step   = 4;
    }
    if ((((dev->int_ctrl >> 16) & 0x1fff) + 2) == 256) {
        /* 5512.5 Hz */
        dev->interp_factor    = 0.125;
        dev->interp_step      = 8;
        dev->calc_sample_rate = 5512;
    }

    dev->calc_sample_rate_synth = 44100 / (1 << (((dev->int_ctrl >> 12) & 3) ^ 3));
    dev->interp_factor_synth    = 1. / (double) ((1 << ((dev->int_ctrl >> 12) & 3) ^ 3));
    dev->interp_step_synth      = (1 << (((dev->int_ctrl >> 12) & 3) ^ 3));
}

static void
es137x_reset(void *priv)
{
    es137x_t *dev = (es137x_t *) priv;

    nmi = 0;

    /* Default subsystem ID. */
    dev->subsys_lock                   = 0x00;
    AS_U16(dev->subsys_id[0]) = (dev->type == AUDIOPCI_ES1370) ? 0x4942 : 0x1274;
    AS_U16(dev->subsys_id[2]) = (dev->type == AUDIOPCI_ES1370) ? 0x4c4c : 0x1371;

    /* Interrupt/Chip Select Control Register, Address 00H
       Addressable as byte, word, longword */
    dev->int_ctrl = 0xfcff0000;

    /* Interrupt/Chip Select Status Register, Address 04H
       Addressable as longword only */
    if (dev->type == AUDIOPCI_ES1370)
        dev->int_status = 0x00000060;
    else if (dev->type == AUDIOPCI_CT5880)
        dev->int_status = 0x52080ec0;
    else if (dev->type == AUDIOPCI_ES1373)
        dev->int_status = 0x7f080ec0;
    else /* AUDIOPCI_ES1371 */
        dev->int_status = 0x7ffffec0;

    /* UART Status Register, Address 09H
       Addressable as byte only */
    dev->uart_status = 0xff;

    /* UART Control Register, Address 09H
       Addressable as byte only */
    dev->uart_ctrl = 0x00;

    /* UART Reserved Register, Address 0AH
       Addressable as byte only */
    dev->uart_res = 0xff;

    /* Memory Page Register, Address 0CH
       Addressable as byte, word, longword */
    dev->mem_page = 0xf0; /* FIXME: hardware reads 0xfffffff0 */

    /* Sample Rate Converter Interface Register, Address 10H
       Addressable as longword only */
    dev->sr_cir = 0x00470000;

    /* CODEC Write Register, Address 14H
       Addressable as longword only */
    dev->codec_ctrl = 0x00000000;

    /* Legacy Control/Status Register, Address 18H
       Addressable as byte, word, longword */
    dev->legacy_ctrl = 0x0000f801;

    /* S/PDIF Channel Status Control Register, Address 1CH
       Addressable as byte, word, longword */
    dev->spdif_chstatus = 0xc0200004;

    /* Serial Interface Control Register, Address 20H
       Addressable as byte, word, longword */
    if (dev->type == AUDIOPCI_ES1370)
        dev->si_cr = 0x00000000;
    else
        dev->si_cr = 0xff800000;

    /* DAC1 Channel Sample Count Register, Address 24H
       Addressable as word, longword */
    dev->dac[0].samp_ct      = 0x00000000; /* FIXME: hardware reads 0x00010000 */
    dev->dac[0].curr_samp_ct = 0x00000000;

    /* DAC2 Channel Sample Count Register, Address 28H
       Addressable as word, longword */
    dev->dac[1].samp_ct      = 0x00000000; /* FIXME: hardware reads 0x00010000 */
    dev->dac[1].curr_samp_ct = 0x00000000;

    /* ADC Channel Sample Count Register, Address 2CH
       Addressable as word, longword */
    dev->adc.samp_ct      = 0x00000000; /* FIXME: hardware reads 0x00010000 */
    dev->adc.curr_samp_ct = 0x00000000;

    /* DAC1 Frame Register 1, Address 30H, Memory Page 1100b
       Addressable as longword only */
    dev->dac[0].addr_latch = 0x00000000;

    /* DAC1 Frame Register 2, Address 34H, Memory Page 1100b
       Addressable as longword only */
    dev->dac[0].size  = 0x00000000;
    dev->dac[0].count = 0x00000000;

    /* DAC2 Frame Register 1, Address 38H, Memory Page 1100b
       Addressable as longword only */
    dev->dac[1].addr_latch = 0x00000000;

    /* DAC2 Frame Register 2, Address 3CH, Memory Page 1100b
       Addressable as longword only */
    dev->dac[1].size  = 0x00000000;
    dev->dac[1].count = 0x00000000;

    /* ADC Frame Register 1, Address 30H, Memory Page 1101b
       Addressable as longword only */
    dev->adc.addr_latch = 0x00000000;

    /* ADC Frame Register 2, Address 34H, Memory Page 1101b
       Addressable as longword only */
    dev->adc.size  = 0x00000000;
    dev->adc.count = 0x00000000;

    /* UART FIFO Register, Address 30H, 34H, 38H, 3CH, Memory Page 1110b, 1111b
       Addressable as longword only */
    for (uint8_t i = 0; i < 8; i++)
        dev->uart_fifo[i] = 0xffff0000;

    /* Reset the UART TX. */
    es137x_set_tx_irq(dev, 0);

    /* Reset the UART (RX) FIFO. */
    es137x_reset_fifo(dev);

    /* Update interrupts to ensure they're all correctly cleared. */
    es137x_update_irqs(dev);

    /* Reset the codec. */
    akm4531_reset(dev);
}

static uint32_t
es137x_read_frame_reg(es137x_t *dev, int frame, int page)
{
    uint32_t ret = 0xffffffff;

    switch (frame) {
        case 0x30:
            switch (page) {
                /* DAC1 Frame Register 1, Address 30H, Memory Page 1100b
                   Addressable as longword only */
                case 0xc:
                    ret = dev->dac[0].addr_latch;
                    break;
                /* ADC Frame Register 1, Address 30H, Memory Page 1101b
                   Addressable as longword only */
                case 0xd:
                    ret = dev->adc.addr_latch;
                    break;
                /* UART FIFO Register, Address 30H, 34H, 38H, 3CH, Memory Page 1110b, 1111b
                   Addressable as longword only */
                case 0xe:
                case 0xf:
                    audiopci_log("[30:%02X] ret = dev->uart_fifo[%02X] = %08X\n", page,
                                 ((page & 0x01) << 2) + ((frame >> 2) & 0x03),
                                 dev->uart_fifo[((page & 0x01) << 2) + ((frame >> 2) & 0x03)]);
                    ret = dev->uart_fifo[((page & 0x01) << 2) + ((frame >> 2) & 0x03)];
                    break;

                default:
                    break;
            }
            break;
        case 0x34:
            switch (page) {
                /* DAC1 Frame Register 2, Address 34H, Memory Page 1100b
                   Addressable as longword only */
                case 0xc:
                    ret = dev->dac[0].size | (dev->dac[0].count << 16);
                    break;
                /* ADC Frame Register 2, Address 34H, Memory Page 1101b
                   Addressable as longword only */
                case 0xd:
                    ret = dev->adc.size | (dev->adc.count << 16);
                    break;
                /* UART FIFO Register, Address 30H, 34H, 38H, 3CH, Memory Page 1110b, 1111b
                   Addressable as longword only */
                case 0xe:
                case 0xf:
                    audiopci_log("[34:%02X] ret = dev->uart_fifo[%02X] = %08X\n", page,
                                 ((page & 0x01) << 2) + ((frame >> 2) & 0x03),
                                 dev->uart_fifo[((page & 0x01) << 2) + ((frame >> 2) & 0x03)]);
                    ret = dev->uart_fifo[((page & 0x01) << 2) + ((frame >> 2) & 0x03)];
                    break;

                default:
                    break;
            }
            break;
        case 0x38:
            switch (page) {
                /* DAC2 Frame Register 1, Address 38H, Memory Page 1100b
                   Addressable as longword only */
                case 0xc:
                    ret = dev->dac[1].addr_latch;
                    break;
                /* UART FIFO Register, Address 30H, 34H, 38H, 3CH, Memory Page 1110b, 1111b
                   Addressable as longword only */
                case 0xe:
                case 0xf:
                    audiopci_log("[38:%02X] ret = dev->uart_fifo[%02X] = %08X\n", page,
                                 ((page & 0x01) << 2) + ((frame >> 2) & 0x03),
                                 dev->uart_fifo[((page & 0x01) << 2) + ((frame >> 2) & 0x03)]);
                    ret = dev->uart_fifo[((page & 0x01) << 2) + ((frame >> 2) & 0x03)];
                    break;

                default:
                    break;
            }
            break;
        case 0x3c:
            switch (page) {
                /* DAC2 Frame Register 2, Address 3CH, Memory Page 1100b
                   Addressable as longword only */
                case 0xc:
                    ret = dev->dac[1].size | (dev->dac[1].count << 16);
                    break;
                /* UART FIFO Register, Address 30H, 34H, 38H, 3CH, Memory Page 1110b, 1111b
                   Addressable as longword only */
                case 0xe:
                case 0xf:
                    audiopci_log("[3C:%02X] ret = dev->uart_fifo[%02X] = %08X\n", page,
                                 ((page & 0x01) << 2) + ((frame >> 2) & 0x03),
                                 dev->uart_fifo[((page & 0x01) << 2) + ((frame >> 2) & 0x03)]);
                    ret = dev->uart_fifo[((page & 0x01) << 2) + ((frame >> 2) & 0x03)];
                    break;

                default:
                    break;
            }
            break;

        default:
            break;
    }

    return ret;
}

static void
es137x_write_frame_reg(es137x_t *dev, int frame, int page, uint32_t val)
{
    switch (frame) {
        case 0x30:
            switch (page) {
                /* DAC1 Frame Register 1, Address 30H, Memory Page 1100b
                   Addressable as longword only */
                case 0xc:
                    dev->dac[0].addr_latch = val;
                    break;
                /* ADC Frame Register 1, Address 30H, Memory Page 1101b
                   Addressable as longword only */
                case 0xd:
                    dev->adc.addr_latch = val;
                    break;
                /* UART FIFO Register, Address 30H, 34H, 38H, 3CH, Memory Page 1110b, 1111b
                   Addressable as longword only */
                case 0xe:
                case 0xf:
                    audiopci_log("[30:%02X] dev->uart_fifo[%02X] = %08X\n", page,
                                 ((page & 0x01) << 2) + ((frame >> 2) & 0x03), val);
                    dev->uart_fifo[((page & 0x01) << 2) + ((frame >> 2) & 0x03)] = val;
                    break;

                default:
                    break;
            }
            break;
        case 0x34:
            switch (page) {
                /* DAC1 Frame Register 2, Address 34H, Memory Page 1100b
                   Addressable as longword only */
                case 0xc:
                    dev->dac[0].size  = val & 0xffff;
                    dev->dac[0].count = val >> 16;
                    break;
                /* ADC Frame Register 2, Address 34H, Memory Page 1101b
                   Addressable as longword only */
                case 0xd:
                    dev->adc.size  = val & 0xffff;
                    dev->adc.count = val >> 16;
                    break;
                /* UART FIFO Register, Address 30H, 34H, 38H, 3CH, Memory Page 1110b, 1111b
                   Addressable as longword only */
                case 0xe:
                case 0xf:
                    audiopci_log("[34:%02X] dev->uart_fifo[%02X] = %08X\n", page,
                                 ((page & 0x01) << 2) + ((frame >> 2) & 0x03), val);
                    dev->uart_fifo[((page & 0x01) << 2) + ((frame >> 2) & 0x03)] = val;
                    break;

                default:
                    break;
            }
            break;
        case 0x38:
            switch (page) {
                /* DAC2 Frame Register 1, Address 38H, Memory Page 1100b
                   Addressable as longword only */
                case 0xc:
                    dev->dac[1].addr_latch = val;
                    break;
                /* UART FIFO Register, Address 30H, 34H, 38H, 3CH, Memory Page 1110b, 1111b
                   Addressable as longword only */
                case 0xe:
                case 0xf:
                    audiopci_log("[38:%02X] dev->uart_fifo[%02X] = %08X\n", page,
                                 ((page & 0x01) << 2) + ((frame >> 2) & 0x03), val);
                    dev->uart_fifo[((page & 0x01) << 2) + ((frame >> 2) & 0x03)] = val;
                    break;

                default:
                    break;
            }
            break;
        case 0x3c:
            switch (page) {
                /* DAC2 Frame Register 2, Address 3CH, Memory Page 1100b
                   Addressable as longword only */
                case 0xc:
                    dev->dac[1].size  = val & 0xffff;
                    dev->dac[1].count = val >> 16;
                    break;
                /* UART FIFO Register, Address 30H, 34H, 38H, 3CH, Memory Page 1110b, 1111b
                   Addressable as longword only */
                case 0xe:
                case 0xf:
                    audiopci_log("[3C:%02X] dev->uart_fifo[%02X] = %08X\n", page,
                                 ((page & 0x01) << 2) + ((frame >> 2) & 0x03), val);
                    dev->uart_fifo[((page & 0x01) << 2) + ((frame >> 2) & 0x03)] = val;
                    break;

                default:
                    break;
            }
            break;

        default:
            break;
    }

    if (page == 0x0e || page == 0x0f) {
        audiopci_log("Write frame = %02x, page = %02x, uart fifo = %08x, val = %02x\n", frame, page, dev->uart_fifo, val);
    }
}

static uint8_t
es137x_inb(uint16_t port, void *priv)
{
    es137x_t *dev = (es137x_t *) priv;
    uint8_t   ret = 0xff;

    switch (port & 0x3f) {
        /* Interrupt/Chip Select Control Register, Address 00H
           Addressable as byte, word, longword */
        case 0x00:
            ret = dev->int_ctrl & 0xff;
            break;
        case 0x01:
            ret = (dev->int_ctrl >> 8) & 0xff;
            break;
        case 0x02:
            ret = (dev->int_ctrl >> 16) & 0x0f;
            break;
        case 0x03:
            ret = dev->int_ctrl >> 24;
            if (dev->type == AUDIOPCI_ES1371)
                ret |= 0xfc;
            break;

        /* Interrupt/Chip Select Status Register, Address 04H
           Addressable as longword only, but PCem implements byte access, which
           must be for a reason */
        case 0x04:
            ret = dev->int_status & 0xff;
            audiopci_log("[R] STATUS  0- 7 = %02X\n", ret);
            break;
        case 0x05:
            ret = (dev->int_status >> 8) & 0xff;
            audiopci_log("[R] STATUS  8-15 = %02X\n", ret);
            break;
        case 0x06:
            ret = (dev->int_status >> 16) & 0xff;
            audiopci_log("[R] STATUS 16-23 = %02X\n", ret);
            break;
        case 0x07:
            ret = (dev->int_status >> 24) & 0xff;
            audiopci_log("[R] STATUS 24-31 = %02X\n", ret);
            break;

        /* UART Data Register, Address 08H
           Addressable as byte only */
        case 0x08:
            ret = dev->uart_data;
            es137x_set_rx_irq(dev, 0);
            audiopci_log("[R] UART DATA = %02X\n", ret);
            break;

        /* UART Status Register, Address 09H
           Addressable as byte only */
        case 0x09:
            ret = dev->uart_status & 0x87;
            audiopci_log("ES1371 UART Status = %02x\n", dev->uart_status);
            break;

        /* UART Reserved Register, Address 0AH
           Addressable as byte only */
        case 0x0a:
            ret = dev->uart_res & 0x01;
            audiopci_log("[R] UART RES = %02X\n", ret);
            break;

        /* Memory Page Register, Address 0CH
           Addressable as byte, word, longword */
        case 0x0c:
            ret = dev->mem_page;
            break;
        case 0x0d ... 0x0e:
            ret = 0x00;
            break;

        /* Legacy Control/Status Register, Address 18H
           Addressable as byte, word, longword */
        case 0x18:
            ret = dev->legacy_ctrl & 0xfd;
            break;
        case 0x19:
            ret = ((dev->legacy_ctrl >> 8) & 0x07) | 0xf8;
            break;
        case 0x1a:
            ret = dev->legacy_ctrl >> 16;
            break;
        case 0x1b:
            ret = dev->legacy_ctrl >> 24;
            break;

        /* S/PDIF Channel Status Control Register, Address 1CH
           Addressable as byte, word, longword */
        case 0x1c:
            if ((dev->type == AUDIOPCI_ES1373) || (dev->type == AUDIOPCI_CT5880))
                ret = dev->spdif_chstatus & 0xff;
            break;
        case 0x1d:
            if ((dev->type == AUDIOPCI_ES1373) || (dev->type == AUDIOPCI_CT5880))
                ret = dev->spdif_chstatus >> 8;
            break;
        case 0x1e:
            if ((dev->type == AUDIOPCI_ES1373) || (dev->type == AUDIOPCI_CT5880))
                ret = dev->spdif_chstatus >> 16;
            break;
        case 0x1f:
            if ((dev->type == AUDIOPCI_ES1373) || (dev->type == AUDIOPCI_CT5880))
                ret = dev->spdif_chstatus >> 24;
            break;

        /* Serial Interface Control Register, Address 20H
            Addressable as byte, word, longword */
        case 0x20:
            ret = dev->si_cr & 0xff;
            break;
        case 0x21:
            ret = dev->si_cr >> 8;
            break;
        case 0x22:
            ret = dev->si_cr >> 16;
            if (dev->type != AUDIOPCI_ES1370)
                ret |= 0x80;
            break;
        case 0x23:
            if (dev->type == AUDIOPCI_ES1370)
                ret = 0x00;
            else
                ret = 0xff;
            break;

        default:
            audiopci_log("Bad es137x_inb: port=%04x\n", port);
    }

    audiopci_log("es137x_inb: port=%04x ret=%02x\n", port, ret);
    return ret;
}

static uint16_t
es137x_inw(uint16_t port, void *priv)
{
    es137x_t *dev = (es137x_t *) priv;
    uint16_t  ret = 0xffff;

    switch (port & 0x3e) {
        /* Interrupt/Chip Select Control Register, Address 00H
           Addressable as byte, word, longword */
        case 0x00:
            ret = dev->int_ctrl & 0xffff;
            break;
        case 0x02:
            ret = (dev->int_ctrl >> 16) & 0xff0f;
            if (dev->type == AUDIOPCI_ES1371)
                ret |= 0xfc00;
            break;

        /* Memory Page Register, Address 0CH
           Addressable as byte, word, longword */
        case 0x0c:
            ret = dev->mem_page;
            break;
        case 0x0e:
            ret = 0x0000;
            break;

        /* Legacy Control/Status Register, Address 18H
           Addressable as byte, word, longword */
        case 0x18:
            ret = (dev->legacy_ctrl & 0x07fd) | 0xf800;
            break;
        case 0x1a:
            ret = dev->legacy_ctrl >> 16;
            break;

        /* S/PDIF Channel Status Control Register, Address 1CH
           Addressable as byte, word, longword */
        case 0x1c:
            if ((dev->type == AUDIOPCI_ES1373) || (dev->type == AUDIOPCI_CT5880))
                ret = dev->spdif_chstatus & 0xffff;
            break;
        case 0x1e:
            if ((dev->type == AUDIOPCI_ES1373) || (dev->type == AUDIOPCI_CT5880))
                ret = dev->spdif_chstatus >> 16;
            break;

        /* Serial Interface Control Register, Address 20H
            Addressable as byte, word, longword */
        case 0x20:
            ret = dev->si_cr & 0xffff;
            break;
        case 0x22:
            ret = dev->si_cr >> 16;
            if (dev->type != AUDIOPCI_ES1370)
                ret |= 0xff80;
            break;

        /* DAC1 Channel Sample Count Register, Address 24H
           Addressable as word, longword */
        case 0x24:
            ret = dev->dac[0].samp_ct;
            break;
        case 0x26:
            ret = dev->dac[0].curr_samp_ct;
            break;

        /* DAC2 Channel Sample Count Register, Address 28H
           Addressable as word, longword */
        case 0x28:
            ret = dev->dac[1].samp_ct;
            break;
        case 0x2a:
            ret = dev->dac[1].curr_samp_ct;
            break;

        /* ADC Channel Sample Count Register, Address 2CH
           Addressable as word, longword */
        case 0x2c:
            ret = dev->adc.samp_ct;
            break;
        case 0x2e:
            ret = dev->adc.curr_samp_ct;
            break;

        case 0x30:
        case 0x34:
        case 0x38:
        case 0x3c:
            ret = es137x_read_frame_reg(dev, port & 0x3c, dev->mem_page) & 0xffff;
            break;
        case 0x32:
        case 0x36:
        case 0x3a:
        case 0x3e:
            ret = es137x_read_frame_reg(dev, port & 0x3c, dev->mem_page) >> 16;
            break;

        default:
            break;
    }

    audiopci_log("es137x_inw: port=%04x ret=%04x\n", port, ret);

    return ret;
}

static uint32_t
es137x_inl(uint16_t port, void *priv)
{
    es137x_t *dev = (es137x_t *) priv;
    uint32_t  ret = 0xffffffff;

    if ((dev->type == AUDIOPCI_ES1370) && (port & 0x3c) == 0x14)
        port = 0x10;

    switch (port & 0x3c) {
        /* Interrupt/Chip Select Control Register, Address 00H
           Addressable as byte, word, longword */
        case 0x00:
            ret = dev->int_ctrl & 0xff0fffff;
            if ((ret < AUDIOPCI_ES1373) && (ret != AUDIOPCI_ES1370))
                ret |= 0xfc000000;
            break;

        /* Interrupt/Chip Select Status Register, Address 04H
           Addressable as longword only */
        case 0x04:
            ret = dev->int_status;
            audiopci_log("[R] STATUS = %08X\n", ret);
            break;

        /* Memory Page Register, Address 0CH
           Addressable as byte, word, longword */
        case 0x0c:
            ret = dev->mem_page;
            break;

        /* Sample Rate Converter Interface Register, Address 10H
           Addressable as longword only */
        case 0x10:
            ret = dev->sr_cir & ~0xffff;
            ret |= dev->sr_ram[dev->sr_cir >> 25];
            break;

        /* CODEC Read Register, Address 14H
           Addressable as longword only */
        case 0x14:
            if (dev->type == AUDIOPCI_ES1370)
                break;
            ret = dev->codec_ctrl | CODEC_READY;
            break;

        /* Legacy Control/Status Register, Address 18H
           Addressable as byte, word, longword */
        case 0x18:
            ret = (dev->legacy_ctrl & 0xffff07fd) | 0x0000f800;
            break;

        /* S/PDIF Channel Status Control Register, Address 1CH
           Addressable as byte, word, longword */
        case 0x1c:
            if ((dev->type == AUDIOPCI_ES1373) || (dev->type == AUDIOPCI_CT5880))
                ret = dev->spdif_chstatus;
            break;

        /* Serial Interface Control Register, Address 20H
            Addressable as byte, word, longword */
        case 0x20:
            ret = dev->si_cr;
            if (dev->type != AUDIOPCI_ES1370)
                ret |= 0xff800000;
            break;

        /* DAC1 Channel Sample Count Register, Address 24H
           Addressable as word, longword */
        case 0x24:
            ret = dev->dac[0].samp_ct | (((uint32_t) dev->dac[0].curr_samp_ct) << 16);
            break;

        /* DAC2 Channel Sample Count Register, Address 28H
           Addressable as word, longword */
        case 0x28:
            ret = dev->dac[1].samp_ct | (((uint32_t) dev->dac[1].curr_samp_ct) << 16);
            break;

        /* ADC Channel Sample Count Register, Address 2CH
           Addressable as word, longword */
        case 0x2c:
            ret = dev->adc.samp_ct | (((uint32_t) dev->adc.curr_samp_ct) << 16);
            break;

        case 0x30:
        case 0x34:
        case 0x38:
        case 0x3c:
            ret = es137x_read_frame_reg(dev, port & 0x3c, dev->mem_page);
            break;

        default:
            break;
    }

    audiopci_log("es137x_inl: port=%04x ret=%08x\n", port, ret);
    return ret;
}

static void
es137x_outb(uint16_t port, uint8_t val, void *priv)
{
    es137x_t *dev = (es137x_t *) priv;
    uint32_t  old_legacy_ctrl;

    audiopci_log("es137x_outb: port=%04x val=%02x\n", port, val);

    switch (port & 0x3f) {
        /* Interrupt/Chip Select Control Register, Address 00H
           Addressable as byte, word, longword */
        case 0x00:
            if (!(dev->int_ctrl & INT_DAC1_EN) && (val & INT_DAC1_EN)) {
                dev->dac[0].addr           = dev->dac[0].addr_latch;
                dev->dac[0].buffer_pos     = 0;
                dev->dac[0].buffer_pos_end = 0;
                dev->dac[0].prev_out_l     = 0;
                dev->dac[0].prev_out_r     = 0;
                es137x_fetch(dev, 0);
            }
            if (!(dev->int_ctrl & INT_DAC2_EN) && (val & INT_DAC2_EN)) {
                dev->dac[1].addr           = dev->dac[1].addr_latch;
                dev->dac[1].buffer_pos     = 0;
                dev->dac[1].buffer_pos_end = 0;
                dev->dac[1].prev_out_l     = 0;
                dev->dac[1].prev_out_r     = 0;
                es137x_fetch(dev, 1);
            }
            // audiopci_log("INTCTRL 0x%02X\n", val & 0xff);
            dev->int_ctrl = (dev->int_ctrl & 0xffffff00) | val;
            break;
        case 0x01:
            dev->int_ctrl = (dev->int_ctrl & 0xffff00ff) | (val << 8);
            es1370_calc_sample_rate(dev);
            break;
        case 0x02:
            dev->int_ctrl = (dev->int_ctrl & 0xff00ffff) | (val << 16);
            es1370_calc_sample_rate(dev);
            break;
        case 0x03:
            dev->int_ctrl = (dev->int_ctrl & 0x00ffffff) | (val << 24);
            gameport_remap(dev->gameport, 0x200 | ((val & 0x03) << 3));
            es1370_calc_sample_rate(dev);
            break;

        /* Interrupt/Chip Select Status Register, Address 04H
           Addressable as longword only, but PCem implements byte access, which
           must be for a reason */
        case 0x06:
            if ((dev->type == AUDIOPCI_ES1373) || (dev->type == AUDIOPCI_CT5880))
                dev->int_status = (dev->int_status & 0xff08ffff) | (val << 16);
            break;
        case 0x07:
            if (dev->type == AUDIOPCI_CT5880)
                dev->int_status = (dev->int_status & 0xd2ffffff) | (val << 24);
            break;

        /* UART Data Register, Address 08H
           Addressable as byte only */
        case 0x08:
            audiopci_log("MIDI data = %02x\n", val);
            /* TX does not use FIFO. */
            midi_raw_out_byte(val);
            es137x_set_tx_irq(dev, 1);
            break;

        /* UART Control Register, Address 09H
           Addressable as byte only */
        case 0x09:
            audiopci_log("[W] UART CTRL = %02X\n", val);
            dev->uart_ctrl = val & 0xe3;

            if ((val & 0x03) == 0x03) {
                /* Reset TX */
                es137x_set_tx_irq(dev, 1);

                /* Software reset */
                es137x_reset_fifo(dev);
            } else {
                es137x_set_tx_irq(dev, 1);

                es137x_update_tx_irq(dev);
                es137x_update_rx_irq(dev);
            }
            break;

        /* UART Reserved Register, Address 0AH
           Addressable as byte only */
        case 0x0a:
            audiopci_log("[W] UART RES = %02X\n", val);
            dev->uart_res = val & 0x01;
            break;

        /* Memory Page Register, Address 0CH
           Addressable as byte, word, longword */
        case 0x0c:
            dev->mem_page = val & 0xf;
            break;
        case 0x0d ... 0x0f:
            break;

        /* Legacy Control/Status Register, Address 18H
           Addressable as byte, word, longword */
        case 0x18:
            dev->legacy_ctrl |= LEGACY_INT;
            break;
        case 0x1a:
            old_legacy_ctrl  = dev->legacy_ctrl;
            dev->legacy_ctrl = (dev->legacy_ctrl & 0xff00ffff) | (val << 16);
            update_legacy(dev, old_legacy_ctrl);
            break;
        case 0x1b:
            old_legacy_ctrl  = dev->legacy_ctrl;
            dev->legacy_ctrl = (dev->legacy_ctrl & 0x00ffffff) | (val << 24);
            es137x_update_irqs(dev);
            update_legacy(dev, old_legacy_ctrl);
            break;

        /* S/PDIF Channel Status Control Register, Address 1CH
           Addressable as byte, word, longword */
        case 0x1c:
            dev->spdif_chstatus = (dev->spdif_chstatus & 0xffffff00) | val;
            break;
        case 0x1d:
            dev->spdif_chstatus = (dev->spdif_chstatus & 0xffff00ff) | (val << 8);
            break;
        case 0x1e:
            dev->spdif_chstatus = (dev->spdif_chstatus & 0xff00ffff) | (val << 16);
            break;
        case 0x1f:
            dev->spdif_chstatus = (dev->spdif_chstatus & 0x00ffffff) | (val << 24);
            break;

        /* Serial Interface Control Register, Address 20H
            Addressable as byte, word, longword */
        case 0x20:
            if (dev->type == AUDIOPCI_ES1370)
                dev->si_cr = (dev->si_cr & 0xffff00) | val;
            else
                dev->si_cr = (dev->si_cr & 0xffffff00) | val;
            break;
        case 0x21:
            if (dev->type == AUDIOPCI_ES1370)
                dev->si_cr = (dev->si_cr & 0xff00ff) | (val << 8);
            else
                dev->si_cr = (dev->si_cr & 0xffff00ff) | (val << 8);
            if (!(dev->si_cr & SI_P1_INTR_EN))
                dev->int_status &= ~INT_STATUS_DAC1;
            if (!(dev->si_cr & SI_P2_INTR_EN))
                dev->int_status &= ~INT_STATUS_DAC2;

            es137x_update_irqs(dev);
            break;
        case 0x22:
            if (dev->type == AUDIOPCI_ES1370)
                dev->si_cr = (dev->si_cr & 0xc0ffff) | ((val & 0x3f) << 16);
            else
                dev->si_cr = (dev->si_cr & 0xff80ffff) | ((val & 0x7f) << 16);
            break;

        default:
            audiopci_log("Bad es137x_outb: port=%04x val=%02x\n", port, val);
    }
}

static void
es137x_outw(uint16_t port, uint16_t val, void *priv)
{
    es137x_t *dev = (es137x_t *) priv;
    uint32_t  old_legacy_ctrl;

    switch (port & 0x3f) {
        /* Interrupt/Chip Select Control Register, Address 00H
           Addressable as byte, word, longword */
        case 0x00:
            if (!(dev->int_ctrl & INT_DAC1_EN) && (val & INT_DAC1_EN)) {
                dev->dac[0].addr           = dev->dac[0].addr_latch;
                dev->dac[0].buffer_pos     = 0;
                dev->dac[0].buffer_pos_end = 0;
                dev->dac[0].prev_out_l     = 0;
                dev->dac[0].prev_out_r     = 0;
                dev->step_synth            = dev->interp_step_synth;
                es137x_fetch(dev, 0);
            }
            if (!(dev->int_ctrl & INT_DAC2_EN) && (val & INT_DAC2_EN)) {
                dev->dac[1].addr           = dev->dac[1].addr_latch;
                dev->dac[1].buffer_pos     = 0;
                dev->dac[1].buffer_pos_end = 0;
                dev->dac[1].prev_out_l     = 0;
                dev->dac[1].prev_out_r     = 0;
                dev->step_pcm              = dev->interp_step;
                es137x_fetch(dev, 1);
            }
            // audiopci_log("INTCTRL 0x%02X\n", val & 0xff);
            dev->int_ctrl = (dev->int_ctrl & 0xffff0000) | val;
            break;
        case 0x02:
            dev->int_ctrl = (dev->int_ctrl & 0x0000ffff) | (val << 16);
            gameport_remap(dev->gameport, 0x200 | ((val & 0x0300) >> 5));
            es1370_calc_sample_rate(dev);
            break;

        /* Memory Page Register, Address 0CH
           Addressable as byte, word, longword */
        case 0x0c:
            dev->mem_page = val & 0xf;
            break;
        case 0x0e:
            break;

        /* CODEC Write Register, Address 10H
           Addressable as word, longword */
        case 0x10:
            if (dev->type != AUDIOPCI_ES1370)
                break;

            dev->akm_codec.registers[(val >> 8) & 0xFF] = val & 0xFF;
            if ((val >> 8) == 0x16 && !(val & 1))
                akm4531_reset(dev);
            break;

        /* Legacy Control/Status Register, Address 18H
           Addressable as byte, word, longword */
        case 0x18:
            dev->legacy_ctrl |= LEGACY_INT;
            break;
        case 0x1a:
            old_legacy_ctrl  = dev->legacy_ctrl;
            dev->legacy_ctrl = (dev->legacy_ctrl & 0x0000ffff) | (val << 16);
            es137x_update_irqs(dev);
            update_legacy(dev, old_legacy_ctrl);
            break;

        /* S/PDIF Channel Status Control Register, Address 1CH
           Addressable as byte, word, longword */
        case 0x1c:
            dev->spdif_chstatus = (dev->spdif_chstatus & 0xffff0000) | val;
            break;
        case 0x1e:
            dev->spdif_chstatus = (dev->spdif_chstatus & 0x0000ffff) | (val << 16);
            break;

        /* Serial Interface Control Register, Address 20H
            Addressable as byte, word, longword */
        case 0x20:
            if (dev->type == AUDIOPCI_ES1370)
                dev->si_cr = (dev->si_cr & 0xff0000) | val;
            else
                dev->si_cr = (dev->si_cr & 0xffff0000) | val;

            if (!(dev->si_cr & SI_P1_INTR_EN))
                dev->int_status &= ~INT_STATUS_DAC1;
            if (!(dev->si_cr & SI_P2_INTR_EN))
                dev->int_status &= ~INT_STATUS_DAC2;
            es137x_update_irqs(dev);
            break;
        case 0x22:
            if (dev->type == AUDIOPCI_ES1370)
                dev->si_cr = (dev->si_cr & 0xc0ffff) | ((val & 0x3f) << 16);
            else
                dev->si_cr = (dev->si_cr & 0xff80ffff) | ((val & 0x007f) << 16);
            break;

        /* DAC1 Channel Sample Count Register, Address 24H
           Addressable as word, longword */
        case 0x24:
            dev->dac[0].samp_ct = val;
            break;

        /* DAC2 Channel Sample Count Register, Address 28H
           Addressable as word, longword */
        case 0x28:
            dev->dac[1].samp_ct = val;
            break;

        /* ADC Channel Sample Count Register, Address 2CH
           Addressable as word, longword */
        case 0x2c:
            dev->adc.samp_ct = val;
            break;

        default:
            break;
    }
}

static void
es137x_outl(uint16_t port, uint32_t val, void *priv)
{
    es137x_t *dev = (es137x_t *) priv;
    uint32_t  old_legacy_ctrl;

    audiopci_log("es137x_outl: port=%04x val=%08x\n", port, val);

    switch (port & 0x3f) {
        /* Interrupt/Chip Select Control Register, Address 00H
           Addressable as byte, word, longword */
        case 0x00:
            {
                uint8_t dac1start = 0;
                uint8_t dac2start = 0;

                if (!(dev->int_ctrl & INT_DAC1_EN) && (val & INT_DAC1_EN)) {
                    dev->dac[0].addr           = dev->dac[0].addr_latch;
                    dev->dac[0].buffer_pos     = 0;
                    dev->dac[0].buffer_pos_end = 0;
                    dev->dac[0].prev_out_l     = 0;
                    dev->dac[0].prev_out_r     = 0;
                    dac1start                  = 1;
                    es137x_fetch(dev, 0);
                }
                if (!(dev->int_ctrl & INT_DAC2_EN) && (val & INT_DAC2_EN)) {
                    dev->dac[1].addr           = dev->dac[1].addr_latch;
                    dev->dac[1].buffer_pos     = 0;
                    dev->dac[1].buffer_pos_end = 0;
                    dev->dac[1].prev_out_l     = 0;
                    dev->dac[1].prev_out_r     = 0;
                    dac2start                  = 1;
                    es137x_fetch(dev, 1);
                }
                // audiopci_log("INTCTRL 0x%02X\n", val & 0xff);
                dev->int_ctrl = val;
                gameport_remap(dev->gameport, 0x200 | ((val & 0x03000000) >> 21));
                es1370_calc_sample_rate(dev);
                if (dac1start)
                    dev->step_synth = dev->interp_step_synth;
                if (dac2start)
                    dev->step_pcm = dev->interp_step;
                break;
            }

        /* Interrupt/Chip Select Status Register, Address 04H
           Addressable as longword only */
        case 0x04:
            audiopci_log("[W] STATUS = %08X\n", val);
            if (dev->type == AUDIOPCI_CT5880)
                dev->int_status = (dev->int_status & 0xd208ffff) | (val & 0x2df70000);
            else if (dev->type == AUDIOPCI_ES1373)
                dev->int_status = (dev->int_status & 0xff08ffff) | (val & 0x00f70000);
            break;

        /* Memory Page Register, Address 0CH
           Addressable as byte, word, longword */
        case 0x0c:
            dev->mem_page = val & 0xf;
            break;

        /* Sample Rate Converter Interface Register, Address 10H
           Addressable as longword only */
        case 0x10:
            if (dev->type == AUDIOPCI_ES1370) {
                dev->akm_codec.registers[(val >> 8) & 0xFF] = val & 0xFF;
                if ((val >> 8) == 0x16 && !(val & 1))
                    akm4531_reset(dev);
                break;
            }
            dev->sr_cir = val & 0xfff8ffff; /*Bits 16 to 18 are undefined*/
            if (dev->sr_cir & SRC_RAM_WE) {
                dev->sr_ram[dev->sr_cir >> 25] = val & 0xffff;
                switch (dev->sr_cir >> 25) {
                    case 0x71:
                        dev->dac[0].vf    = (dev->dac[0].vf & ~0x1f8000) | ((val & 0xfc00) << 5);
                        dev->dac[0].ac    = (dev->dac[0].ac & ~0x7f8000) | ((val & 0x00ff) << 15);
                        dev->dac[0].f_pos = 0;
                        break;
                    case 0x72:
                        dev->dac[0].ac = (dev->dac[0].ac & ~0x7fff) | (val & 0x7fff);
                        break;
                    case 0x73:
                        dev->dac[0].vf = (dev->dac[0].vf & ~0x7fff) | (val & 0x7fff);
                        break;

                    case 0x75:
                        dev->dac[1].vf    = (dev->dac[1].vf & ~0x1f8000) | ((val & 0xfc00) << 5);
                        dev->dac[1].ac    = (dev->dac[1].ac & ~0x7f8000) | ((val & 0x00ff) << 15);
                        dev->dac[1].f_pos = 0;
                        break;
                    case 0x76:
                        dev->dac[1].ac = (dev->dac[1].ac & ~0x7fff) | (val & 0x7fff);
                        break;
                    case 0x77:
                        dev->dac[1].vf = (dev->dac[1].vf & ~0x7fff) | (val & 0x7fff);
                        break;

                    case 0x7c:
                        dev->dac[0].vol_l = (int32_t) (int16_t) (val & 0xffff);
                        break;
                    case 0x7d:
                        dev->dac[0].vol_r = (int32_t) (int16_t) (val & 0xffff);
                        break;
                    case 0x7e:
                        dev->dac[1].vol_l = (int32_t) (int16_t) (val & 0xffff);
                        break;
                    case 0x7f:
                        dev->dac[1].vol_r = (int32_t) (int16_t) (val & 0xffff);
                        break;

                    default:
                        break;
                }
            }
            break;

        /* CODEC Write Register, Address 14H
           Addressable as longword only */
        case 0x14:
            if (dev->type == AUDIOPCI_ES1370)
                break;
            if (val & CODEC_READ) {
                dev->codec_ctrl &= 0x00ff0000;
                dev->codec_ctrl |= ac97_codec_readw(dev->codec, val >> 16);
            } else {
                dev->codec_ctrl = val & 0x00ffffff;
                ac97_codec_writew(dev->codec, val >> 16, val);

                ac97_codec_getattn(dev->codec, 0x02, &dev->master_vol_l, &dev->master_vol_r);
                ac97_codec_getattn(dev->codec, 0x18, &dev->pcm_vol_l, &dev->pcm_vol_r);
                ac97_codec_getattn(dev->codec, 0x38, &dev->pcm_rear_vol_l, &dev->pcm_rear_vol_r);
                ac97_codec_getattn(dev->codec, 0x12, &dev->cd_vol_l, &dev->cd_vol_r);
            }
            break;

        /* Legacy Control/Status Register, Address 18H
           Addressable as byte, word, longword */
        case 0x18:
            old_legacy_ctrl  = dev->legacy_ctrl;
            dev->legacy_ctrl = (dev->legacy_ctrl & 0x0000ffff) | (val & 0xffff0000);
            dev->legacy_ctrl |= LEGACY_INT;
            es137x_update_irqs(dev);
            update_legacy(dev, old_legacy_ctrl);
            break;

        /* S/PDIF Channel Status Control Register, Address 1CH
           Addressable as byte, word, longword */
        case 0x1c:
            dev->spdif_chstatus = val;
            break;

        /* Serial Interface Control Register, Address 20H
            Addressable as byte, word, longword */
        case 0x20:
            if (dev->type == AUDIOPCI_ES1370)
                dev->si_cr = val & 0x3fffff;
            else
                dev->si_cr = (val & 0x007fffff) | 0xff800000;
            if (!(dev->si_cr & SI_P1_INTR_EN))
                dev->int_status &= ~INT_STATUS_DAC1;
            if (!(dev->si_cr & SI_P2_INTR_EN))
                dev->int_status &= ~INT_STATUS_DAC2;
            es137x_update_irqs(dev);
            break;

        /* DAC1 Channel Sample Count Register, Address 24H
           Addressable as word, longword */
        case 0x24:
            dev->dac[0].samp_ct = val & 0xffff;
            break;

        /* DAC2 Channel Sample Count Register, Address 28H
           Addressable as word, longword */
        case 0x28:
            dev->dac[1].samp_ct = val & 0xffff;
            break;

        /* ADC Channel Sample Count Register, Address 2CH
           Addressable as word, longword */
        case 0x2c:
            dev->adc.samp_ct = val & 0xffff;
            break;

        case 0x30:
        case 0x34:
        case 0x38:
        case 0x3c:
            es137x_write_frame_reg(dev, port & 0x3c, dev->mem_page, val);
            break;

        default:
            break;
    }
}

static void
capture_event(es137x_t *dev, int type, int rw, uint16_t port)
{
    dev->legacy_ctrl &= ~(LEGACY_EVENT_MASK | LEGACY_EVENT_ADDR_MASK);
    dev->legacy_ctrl |= type;
    if (rw)
        dev->legacy_ctrl |= LEGACY_EVENT_TYPE_RW;
    else
        dev->legacy_ctrl &= ~LEGACY_EVENT_TYPE_RW;
    dev->legacy_ctrl |= ((port << LEGACY_EVENT_ADDR_SHIFT) & LEGACY_EVENT_ADDR_MASK);
    dev->legacy_ctrl &= ~LEGACY_INT;
    nmi_raise();
}

static void
capture_write_sscape(uint16_t port, UNUSED(uint8_t val), void *priv)
{
    capture_event(priv, LEGACY_EVENT_SSCAPE, 1, port);
}

static void
capture_write_codec(uint16_t port, UNUSED(uint8_t val), void *priv)
{
    capture_event(priv, LEGACY_EVENT_CODEC, 1, port);
}

static void
capture_write_sb(uint16_t port, UNUSED(uint8_t val), void *priv)
{
    capture_event(priv, LEGACY_EVENT_SB, 1, port);
}

static void
capture_write_adlib(uint16_t port, UNUSED(uint8_t val), void *priv)
{
    capture_event(priv, LEGACY_EVENT_ADLIB, 1, port);
}

static void
capture_write_master_pic(uint16_t port, UNUSED(uint8_t val), void *priv)
{
    capture_event(priv, LEGACY_EVENT_MASTER_PIC, 1, port);
}

static void
capture_write_master_dma(uint16_t port, UNUSED(uint8_t val), void *priv)
{
    capture_event(priv, LEGACY_EVENT_MASTER_DMA, 1, port);
}

static void
capture_write_slave_pic(uint16_t port, UNUSED(uint8_t val), void *priv)
{
    capture_event(priv, LEGACY_EVENT_SLAVE_PIC, 1, port);
}

static void
capture_write_slave_dma(uint16_t port, UNUSED(uint8_t val), void *priv)
{
    capture_event(priv, LEGACY_EVENT_SLAVE_DMA, 1, port);
}

static uint8_t
capture_read_sscape(uint16_t port, void *priv)
{
    capture_event(priv, LEGACY_EVENT_SSCAPE, 0, port);
    return 0xff;
}

static uint8_t
capture_read_codec(uint16_t port, void *priv)
{
    capture_event(priv, LEGACY_EVENT_CODEC, 0, port);
    return 0xff;
}

static uint8_t
capture_read_sb(uint16_t port, void *priv)
{
    capture_event(priv, LEGACY_EVENT_SB, 0, port);
    return 0xff;
}

static uint8_t
capture_read_adlib(uint16_t port, void *priv)
{
    capture_event(priv, LEGACY_EVENT_ADLIB, 0, port);
    return 0xff;
}

static uint8_t
capture_read_master_pic(uint16_t port, void *priv)
{
    capture_event(priv, LEGACY_EVENT_MASTER_PIC, 0, port);
    return 0xff;
}

static uint8_t
capture_read_master_dma(uint16_t port, void *priv)
{
    capture_event(priv, LEGACY_EVENT_MASTER_DMA, 0, port);
    return 0xff;
}

static uint8_t
capture_read_slave_pic(uint16_t port, void *priv)
{
    capture_event(priv, LEGACY_EVENT_SLAVE_PIC, 0, port);
    return 0xff;
}

static uint8_t
capture_read_slave_dma(uint16_t port, void *priv)
{
    capture_event(priv, LEGACY_EVENT_SLAVE_DMA, 0, port);
    return 0xff;
}

static void
update_legacy(es137x_t *dev, uint32_t old_legacy_ctrl)
{
    if (old_legacy_ctrl & LEGACY_CAPTURE_SSCAPE) {
        switch ((old_legacy_ctrl >> LEGACY_SSCAPE_ADDR_SHIFT) & 3) {
            case 0:
                io_removehandler(0x0320, 0x0008,
                                 capture_read_sscape, NULL, NULL,
                                 capture_write_sscape, NULL, NULL, dev);
                break;
            case 1:
                io_removehandler(0x0330, 0x0008,
                                 capture_read_sscape, NULL, NULL,
                                 capture_write_sscape, NULL, NULL, dev);
                break;
            case 2:
                io_removehandler(0x0340, 0x0008,
                                 capture_read_sscape, NULL, NULL,
                                 capture_write_sscape, NULL, NULL, dev);
                break;
            case 3:
                io_removehandler(0x0350, 0x0008,
                                 capture_read_sscape, NULL, NULL,
                                 capture_write_sscape, NULL, NULL, dev);
                break;

            default:
                break;
        }
    }

    if (old_legacy_ctrl & LEGACY_CAPTURE_CODEC) {
        switch ((old_legacy_ctrl >> LEGACY_CODEC_ADDR_SHIFT) & 3) {
            case 0:
                io_removehandler(0x0530, 0x0008,
                                 capture_read_codec, NULL, NULL,
                                 capture_write_codec, NULL, NULL, dev);
                break;
            case 2:
                io_removehandler(0x0e80, 0x0008,
                                 capture_read_codec, NULL, NULL,
                                 capture_write_codec, NULL, NULL, dev);
                break;
            case 3:
                io_removehandler(0x0f40, 0x0008,
                                 capture_read_codec, NULL, NULL,
                                 capture_write_codec, NULL, NULL, dev);
                break;

            default:
                break;
        }
    }

    if (old_legacy_ctrl & LEGACY_CAPTURE_SB) {
        if (!(old_legacy_ctrl & LEGACY_SB_ADDR)) {
            io_removehandler(0x0220, 0x0010,
                             capture_read_sb, NULL, NULL,
                             capture_write_sb, NULL, NULL, dev);
        } else {
            io_removehandler(0x0240, 0x0010,
                             capture_read_sb, NULL, NULL,
                             capture_write_sb, NULL, NULL, dev);
        }
    }

    if (old_legacy_ctrl & LEGACY_CAPTURE_ADLIB) {
        io_removehandler(0x0388, 0x0004,
                         capture_read_adlib, NULL, NULL,
                         capture_write_adlib, NULL, NULL, dev);
    }

    if (old_legacy_ctrl & LEGACY_CAPTURE_MASTER_PIC) {
        io_removehandler(0x0020, 0x0002,
                         capture_read_master_pic, NULL, NULL,
                         capture_write_master_pic, NULL, NULL, dev);
    }

    if (old_legacy_ctrl & LEGACY_CAPTURE_MASTER_DMA) {
        io_removehandler(0x0000, 0x0010,
                         capture_read_master_dma, NULL, NULL,
                         capture_write_master_dma, NULL, NULL, dev);
    }

    if (old_legacy_ctrl & LEGACY_CAPTURE_SLAVE_PIC) {
        io_removehandler(0x00a0, 0x0002,
                         capture_read_slave_pic, NULL, NULL,
                         capture_write_slave_pic, NULL, NULL, dev);
    }

    if (old_legacy_ctrl & LEGACY_CAPTURE_SLAVE_DMA) {
        io_removehandler(0x00c0, 0x0020,
                         capture_read_slave_dma, NULL, NULL,
                         capture_write_slave_dma, NULL, NULL, dev);
    }

    if (dev->legacy_ctrl & LEGACY_CAPTURE_SSCAPE) {
        switch ((dev->legacy_ctrl >> LEGACY_SSCAPE_ADDR_SHIFT) & 3) {
            case 0:
                io_sethandler(0x0320, 0x0008,
                              capture_read_sscape, NULL, NULL,
                              capture_write_sscape, NULL, NULL, dev);
                break;
            case 1:
                io_sethandler(0x0330, 0x0008,
                              capture_read_sscape, NULL, NULL,
                              capture_write_sscape, NULL, NULL, dev);
                break;
            case 2:
                io_sethandler(0x0340, 0x0008,
                              capture_read_sscape, NULL, NULL,
                              capture_write_sscape, NULL, NULL, dev);
                break;
            case 3:
                io_sethandler(0x0350, 0x0008,
                              capture_read_sscape, NULL, NULL,
                              capture_write_sscape, NULL, NULL, dev);
                break;

            default:
                break;
        }
    }

    if (dev->legacy_ctrl & LEGACY_CAPTURE_CODEC) {
        switch ((dev->legacy_ctrl >> LEGACY_CODEC_ADDR_SHIFT) & 3) {
            case 0:
                io_sethandler(0x0530, 0x0008,
                              capture_read_codec, NULL, NULL,
                              capture_write_codec, NULL, NULL, dev);
                break;
            case 2:
                io_sethandler(0x0e80, 0x0008,
                              capture_read_codec, NULL, NULL,
                              capture_write_codec, NULL, NULL, dev);
                break;
            case 3:
                io_sethandler(0x0f40, 0x0008,
                              capture_read_codec, NULL, NULL,
                              capture_write_codec, NULL, NULL, dev);
                break;

            default:
                break;
        }
    }

    if (dev->legacy_ctrl & LEGACY_CAPTURE_SB) {
        if (!(dev->legacy_ctrl & LEGACY_SB_ADDR)) {
            io_sethandler(0x0220, 0x0010,
                          capture_read_sb, NULL, NULL,
                          capture_write_sb, NULL, NULL, dev);
        } else {
            io_sethandler(0x0240, 0x0010,
                          capture_read_sb, NULL, NULL,
                          capture_write_sb, NULL, NULL, dev);
        }
    }

    if (dev->legacy_ctrl & LEGACY_CAPTURE_ADLIB) {
        io_sethandler(0x0388, 0x0004,
                      capture_read_adlib, NULL, NULL,
                      capture_write_adlib, NULL, NULL, dev);
    }

    if (dev->legacy_ctrl & LEGACY_CAPTURE_MASTER_PIC) {
        io_sethandler(0x0020, 0x0002,
                      capture_read_master_pic, NULL, NULL,
                      capture_write_master_pic, NULL, NULL, dev);
    }

    if (dev->legacy_ctrl & LEGACY_CAPTURE_MASTER_DMA) {
        io_sethandler(0x0000, 0x0010,
                      capture_read_master_dma, NULL, NULL,
                      capture_write_master_dma, NULL, NULL, dev);
    }

    if (dev->legacy_ctrl & LEGACY_CAPTURE_SLAVE_PIC) {
        io_sethandler(0x00a0, 0x0002,
                      capture_read_slave_pic, NULL, NULL,
                      capture_write_slave_pic, NULL, NULL, dev);
    }

    if (dev->legacy_ctrl & LEGACY_CAPTURE_SLAVE_DMA) {
        io_sethandler(0x00c0, 0x0020,
                      capture_read_slave_dma, NULL, NULL,
                      capture_write_slave_dma, NULL, NULL, dev);
    }
}

static uint8_t
es1370_pci_read(int func, int addr, UNUSED(int len), void *priv)
{
    const es137x_t *dev = (es137x_t *) priv;

    if (func > 0)
        return 0xff;

    if ((addr > 0x3f) && ((addr < 0xdc) || (addr > 0xe1)))
        return 0x00;

    switch (addr) {
        case 0x00:       /* Vendor ID */
            return 0x74; /* Ensoniq */
        case 0x01:
            return 0x12;

        case 0x02:       /* Device ID */
            return dev->type >> 16; /* ES1370 */
        case 0x03:
            return dev->type >> 24;

        case 0x04: /* Command TODO */
            return dev->pci_command;
        case 0x05:
            return dev->pci_serr;

        case 0x06:       /* Status TODO */
            return 0x10; /* Supports ACPI */
        case 0x07:
            return 0x00;

        case 0x08:       /* Class Code & Revision ID */
            return dev->type >> 8; /* Revision ID - 0x00 is actual Ensoniq-branded ES1370 */
        case 0x09:
            return 0x00; /* Multimedia audio device */
        case 0x0a:
            return 0x01;
        case 0x0b:
            return 0x04;

#if 0
        case 0x0c: /* Cache Line Size TODO */
        case 0x0d: /* Latency Timer TODO */
        case 0x0e: /* Header Type TODO */
        case 0x0f: /* BIST TODO */
#endif

        case 0x10:                                 /* Base Address TODO */
            return 0x01 | (dev->base_addr & 0xc0); /* memBaseAddr */
        case 0x11:
            return dev->base_addr >> 8;
        case 0x12:
            return dev->base_addr >> 16;
        case 0x13:
            return dev->base_addr >> 24;

        case 0x2c ... 0x2f:
            return dev->subsys_id[addr & 3]; /* Subsystem vendor ID */

#if 0
        case 0x34: // TODO
            return 0xdc; /* Capabilites pointer */
#endif

        case 0x3c:
            return dev->int_line;
        case 0x3d:
            return 0x01; /* INTA */

        case 0x3e:
            return 0xc; /* Minimum grant */
        case 0x3f:
            return 0x80; /* Maximum latency */

#if 0
        case 0xdc:
            return 0x01; /* Capabilities identifier */
        case 0xdd:
            return 0x00; /* Next item pointer */
        case 0xde:
            return 0x31; /* Power management capabilities */
        case 0xdf:
            return 0x6c;

        case 0xe0:
            return dev->pmcsr & 0xff;
        case 0xe1:
            return dev->pmcsr >> 8;
#endif

        default:
            break;
    }

    return 0x00;
}

static uint8_t
es1371_pci_read(int func, int addr, UNUSED(int len), void *priv)
{
    const es137x_t *dev = (es137x_t *) priv;

    if (func > 0)
        return 0xff;

    if ((addr > 0x3f) && ((addr < 0xdc) || (addr > 0xe1)))
        return 0x00;

    switch (addr) {
        case 0x00:
            return 0x74; /* Ensoniq */
        case 0x01:
            return 0x12;

        case 0x02:
            return dev->type >> 16; /* ES1371 */
        case 0x03:
            return dev->type >> 24;

        case 0x04:
            return dev->pci_command;
        case 0x05:
            return dev->pci_serr;

        case 0x06:
            return 0x10; /* Supports ACPI */
        case 0x07:
            return 0x00;

        case 0x08:
            return dev->type >> 8; /* Revision ID */
        case 0x09:
            return 0x00; /* Multimedia audio device */
        case 0x0a:
            return 0x01;
        case 0x0b:
            return 0x04;

        case 0x10:
            return 0x01 | (dev->base_addr & 0xc0); /* memBaseAddr */
        case 0x11:
            return dev->base_addr >> 8;
        case 0x12:
            return dev->base_addr >> 16;
        case 0x13:
            return dev->base_addr >> 24;

        case 0x2c ... 0x2f:
            return dev->subsys_id[addr & 3]; /* Subsystem vendor ID */

        case 0x34:
            return 0xdc; /* Capabilites pointer */

        case 0x3c:
            return dev->int_line;
        case 0x3d:
            return 0x01; /* INTA */

        case 0x3e:
            return 0xc; /* Minimum grant */
        case 0x3f:
            return 0x80; /* Maximum latency */

        case 0x40:
            if ((dev->type == AUDIOPCI_ES1373) || (dev->type == AUDIOPCI_CT5880))
                return dev->subsys_lock;
            break;

        case 0xdc:
            return 0x01; /* Capabilities identifier */
        case 0xdd:
            return 0x00; /* Next item pointer */
        case 0xde:
            return 0x31; /* Power management capabilities */
        case 0xdf:
            return 0x6c;

        case 0xe0:
            return dev->pmcsr & 0xff;
        case 0xe1:
            return dev->pmcsr >> 8;

        default:
            break;
    }

    return 0x00;
}

static void
es137x_io_set(es137x_t *dev, int set)
{
    if (dev->pci_command & PCI_COMMAND_IO) {
        io_handler(set, dev->base_addr, 0x0040,
                   es137x_inb, es137x_inw, es137x_inl,
                   es137x_outb, es137x_outw, es137x_outl, dev);
    }
}

static void
es1370_pci_write(int func, int addr, UNUSED(int len), uint8_t val, void *priv)
{
    es137x_t *dev = (es137x_t *) priv;

    if (func)
        return;

    switch (addr) {
        case 0x04:
            es137x_io_set(dev, 0);
            dev->pci_command = val & 0x05;
            es137x_io_set(dev, 1);
            break;
        case 0x05:
            dev->pci_serr = val & 1;
            break;

        case 0x10:
            es137x_io_set(dev, 0);
            dev->base_addr = (dev->base_addr & 0xffffff00) | (val & 0xc0);
            es137x_io_set(dev, 1);
            break;
        case 0x11:
            es137x_io_set(dev, 0);
            dev->base_addr = (dev->base_addr & 0xffff00c0) | (val << 8);
            es137x_io_set(dev, 1);
            break;
        case 0x12:
            dev->base_addr = (dev->base_addr & 0xff00ffc0) | (val << 16);
            break;
        case 0x13:
            dev->base_addr = (dev->base_addr & 0x00ffffc0) | (val << 24);
            break;

        case 0x3c:
            dev->int_line = val;
            break;

        case 0xe0:
            dev->pmcsr = (dev->pmcsr & 0xff00) | (val & 0x03);
            break;
        case 0xe1:
            dev->pmcsr = (dev->pmcsr & 0x00ff) | ((val & 0x01) << 8);
            break;

        default:
            break;
    }
}

static void
es1371_pci_write(int func, int addr, UNUSED(int len), uint8_t val, void *priv)
{
    es137x_t *dev = (es137x_t *) priv;

    if (func)
        return;

    switch (addr) {
        case 0x04:
            es137x_io_set(dev, 0);
            dev->pci_command = val & 0x05;
            es137x_io_set(dev, 1);
            break;
        case 0x05:
            dev->pci_serr = val & 1;
            break;

        case 0x10:
            es137x_io_set(dev, 0);
            dev->base_addr = (dev->base_addr & 0xffffff00) | (val & 0xc0);
            es137x_io_set(dev, 1);
            break;
        case 0x11:
            es137x_io_set(dev, 0);
            dev->base_addr = (dev->base_addr & 0xffff00c0) | (val << 8);
            es137x_io_set(dev, 1);
            break;
        case 0x12:
            dev->base_addr = (dev->base_addr & 0xff00ffc0) | (val << 16);
            break;
        case 0x13:
            dev->base_addr = (dev->base_addr & 0x00ffffc0) | (val << 24);
            break;

        case 0x2c ... 0x2f:
            if (dev->subsys_lock == 0xea)
                dev->subsys_id[addr & 3] = val;
            break;

        case 0x3c:
            dev->int_line = val;
            break;

        case 0x40:
            if ((dev->type == AUDIOPCI_ES1373) || (dev->type == AUDIOPCI_CT5880))
                dev->subsys_lock = val;
            break;

        case 0xe0:
            dev->pmcsr = (dev->pmcsr & 0xff00) | (val & 0x03);
            break;
        case 0xe1:
            dev->pmcsr = (dev->pmcsr & 0x00ff) | ((val & 0x01) << 8);
            break;

        default:
            break;
    }
}

static void
es137x_fetch(es137x_t *dev, int dac_nr)
{
    if (dev->si_cr & (dac_nr ? SI_P2_PAUSE : SI_P1_PAUSE))
        return;

    int format = dac_nr ? ((dev->si_cr >> 2) & 3) : (dev->si_cr & 3);
    int pos    = dev->dac[dac_nr].buffer_pos & 63;
    int c;

    switch (format) {
        case FORMAT_MONO_8:
            for (c = 0; c < 32; c += 4) {
                dev->dac[dac_nr].buffer_l[(pos + c) & 63] = dev->dac[dac_nr].buffer_r[(pos + c) & 63] = (mem_readb_phys(dev->dac[dac_nr].addr) ^ 0x80) << 8;
                dev->dac[dac_nr].buffer_l[(pos + c + 1) & 63] = dev->dac[dac_nr].buffer_r[(pos + c + 1) & 63] = (mem_readb_phys(dev->dac[dac_nr].addr + 1) ^ 0x80) << 8;
                dev->dac[dac_nr].buffer_l[(pos + c + 2) & 63] = dev->dac[dac_nr].buffer_r[(pos + c + 2) & 63] = (mem_readb_phys(dev->dac[dac_nr].addr + 2) ^ 0x80) << 8;
                dev->dac[dac_nr].buffer_l[(pos + c + 3) & 63] = dev->dac[dac_nr].buffer_r[(pos + c + 3) & 63] = (mem_readb_phys(dev->dac[dac_nr].addr + 3) ^ 0x80) << 8;
                dev->dac[dac_nr].addr += 4;

                dev->dac[dac_nr].buffer_pos_end += 4;
                dev->dac[dac_nr].count++;

                if (dev->dac[dac_nr].count > dev->dac[dac_nr].size) {
                    dev->dac[dac_nr].count = 0;
                    dev->dac[dac_nr].addr  = dev->dac[dac_nr].addr_latch;
                    break;
                }
            }
            break;

        case FORMAT_STEREO_8:
            for (c = 0; c < 16; c += 2) {
                dev->dac[dac_nr].buffer_l[(pos + c) & 63]     = (mem_readb_phys(dev->dac[dac_nr].addr) ^ 0x80) << 8;
                dev->dac[dac_nr].buffer_r[(pos + c) & 63]     = (mem_readb_phys(dev->dac[dac_nr].addr + 1) ^ 0x80) << 8;
                dev->dac[dac_nr].buffer_l[(pos + c + 1) & 63] = (mem_readb_phys(dev->dac[dac_nr].addr + 2) ^ 0x80) << 8;
                dev->dac[dac_nr].buffer_r[(pos + c + 1) & 63] = (mem_readb_phys(dev->dac[dac_nr].addr + 3) ^ 0x80) << 8;
                dev->dac[dac_nr].addr += 4;

                dev->dac[dac_nr].buffer_pos_end += 2;
                dev->dac[dac_nr].count++;

                if (dev->dac[dac_nr].count > dev->dac[dac_nr].size) {
                    dev->dac[dac_nr].count = 0;
                    dev->dac[dac_nr].addr  = dev->dac[dac_nr].addr_latch;
                    break;
                }
            }
            break;

        case FORMAT_MONO_16:
            for (c = 0; c < 16; c += 2) {
                dev->dac[dac_nr].buffer_l[(pos + c) & 63] = dev->dac[dac_nr].buffer_r[(pos + c) & 63] = mem_readw_phys(dev->dac[dac_nr].addr);
                dev->dac[dac_nr].buffer_l[(pos + c + 1) & 63] = dev->dac[dac_nr].buffer_r[(pos + c + 1) & 63] = mem_readw_phys(dev->dac[dac_nr].addr + 2);
                dev->dac[dac_nr].addr += 4;

                dev->dac[dac_nr].buffer_pos_end += 2;
                dev->dac[dac_nr].count++;

                if (dev->dac[dac_nr].count > dev->dac[dac_nr].size) {
                    dev->dac[dac_nr].count = 0;
                    dev->dac[dac_nr].addr  = dev->dac[dac_nr].addr_latch;
                    break;
                }
            }
            break;

        case FORMAT_STEREO_16:
            for (c = 0; c < 4; c++) {
                dev->dac[dac_nr].buffer_l[(pos + c) & 63] = mem_readw_phys(dev->dac[dac_nr].addr);
                dev->dac[dac_nr].buffer_r[(pos + c) & 63] = mem_readw_phys(dev->dac[dac_nr].addr + 2);
                dev->dac[dac_nr].addr += 4;

                dev->dac[dac_nr].buffer_pos_end++;
                dev->dac[dac_nr].count++;

                if (dev->dac[dac_nr].count > dev->dac[dac_nr].size) {
                    dev->dac[dac_nr].count = 0;
                    dev->dac[dac_nr].addr  = dev->dac[dac_nr].addr_latch;
                    break;
                }
            }
            break;

        default:
            break;
    }
}

static inline float
low_fir_es1371(int dac_nr, int i, float NewSample)
{
    static float x[2][2][128]; // input samples
    static int   x_pos[2] = { 0, 0 };
    float        out      = 0.0;
    int          read_pos;
    int          n_coef;
    int          pos = x_pos[dac_nr];

    x[dac_nr][i][pos] = NewSample;

    /* Since only 1/16th of input samples are non-zero, only filter those that
       are valid.*/
    read_pos = (pos + 15) & (127 & ~15);
    n_coef   = (16 - pos) & 15;

    while (n_coef < ES137x_NCoef) {
        out += low_fir_es137x_coef[n_coef] * x[dac_nr][i][read_pos];
        read_pos = (read_pos + 16) & (127 & ~15);
        n_coef += 16;
    }

    if (i == 1) {
        x_pos[dac_nr] = (x_pos[dac_nr] + 1) & 127;
        if (x_pos[dac_nr] > 127)
            x_pos[dac_nr] = 0;
    }

    return out;
}

static void
es137x_next_sample_filtered(es137x_t *dev, int dac_nr, int out_idx)
{
    int out_l;
    int out_r;

    if ((dev->dac[dac_nr].buffer_pos - dev->dac[dac_nr].buffer_pos_end) >= 0)
        es137x_fetch(dev, dac_nr);

    out_l = dev->dac[dac_nr].buffer_l[dev->dac[dac_nr].buffer_pos & 63];
    out_r = dev->dac[dac_nr].buffer_r[dev->dac[dac_nr].buffer_pos & 63];

    dev->dac[dac_nr].filtered_l[out_idx] = (int) low_fir_es1371(dac_nr, 0, (float) out_l);
    dev->dac[dac_nr].filtered_r[out_idx] = (int) low_fir_es1371(dac_nr, 1, (float) out_r);

    for (uint8_t c = 1; c < 16; c++) {
        dev->dac[dac_nr].filtered_l[out_idx + c] = (int) low_fir_es1371(dac_nr, 0, 0);
        dev->dac[dac_nr].filtered_r[out_idx + c] = (int) low_fir_es1371(dac_nr, 1, 0);
    }

    dev->dac[dac_nr].buffer_pos++;
}

static void
es137x_update(es137x_t *dev)
{
    int32_t l;
    int32_t r;

    if (dev->type == AUDIOPCI_ES1370) {
        l = dev->dac[0].out_l * (((dev->akm_codec.registers[0x4] & 0x80) ? 0 : akm4531_gain_2dbstep_5bits[(dev->akm_codec.registers[0x4] & 0x1f)]) / 32767.0);
        r = dev->dac[0].out_r * (((dev->akm_codec.registers[0x5] & 0x80) ? 0 : akm4531_gain_2dbstep_5bits[(dev->akm_codec.registers[0x5] & 0x1f)]) / 32767.0);

        l += dev->dac[1].out_l * (((dev->akm_codec.registers[0x2] & 0x80) ? 0 : akm4531_gain_2dbstep_5bits[(dev->akm_codec.registers[0x2] & 0x1f)]) / 32767.0);
        r += dev->dac[1].out_r * (((dev->akm_codec.registers[0x3] & 0x80) ? 0 : akm4531_gain_2dbstep_5bits[(dev->akm_codec.registers[0x3] & 0x1f)]) / 32767.0);

        l >>= 1;
        r >>= 1;

        l *= (((dev->akm_codec.registers[0x0] & 0x80) ? 0 : akm4531_att_2dbstep_5bits[(dev->akm_codec.registers[0x0] & 0x1f) ^ 0x1f]) / 32767.0);
        r *= (((dev->akm_codec.registers[0x1] & 0x80) ? 0 : akm4531_att_2dbstep_5bits[(dev->akm_codec.registers[0x1] & 0x1f) ^ 0x1f]) / 32767.0);
    } else {
        l = (dev->dac[0].out_l * dev->dac[0].vol_l) >> 12;
        l += ((dev->dac[1].out_l * dev->dac[1].vol_l) >> 12);
        r = (dev->dac[0].out_r * dev->dac[0].vol_r) >> 12;
        r += ((dev->dac[1].out_r * dev->dac[1].vol_r) >> 12);

        l >>= 1;
        r >>= 1;

        l = (((l * dev->pcm_vol_l) >> 15) * dev->master_vol_l) >> 15;
        r = (((r * dev->pcm_vol_r) >> 15) * dev->master_vol_r) >> 15;
    }

    if (l < -32768)
        l = -32768;
    else if (l > 32767)
        l = 32767;
    if (r < -32768)
        r = -32768;
    else if (r > 32767)
        r = 32767;

    for (; dev->pos < ((dev->type == AUDIOPCI_ES1370) ? wavetable_pos_global : sound_pos_global); dev->pos++) {
        dev->buffer[dev->pos * 2]     = l;
        dev->buffer[dev->pos * 2 + 1] = r;
    }
}

static void
es137x_poll(void *priv)
{
    es137x_t *dev = (es137x_t *) priv;
    int       frac;
    int       idx;
    int       samp1_l;
    int       samp1_r;
    int       samp2_l;
    int       samp2_r;

    timer_advance_u64(&dev->dac[1].timer, dev->dac[1].latch);

    es137x_scan_fifo(dev);

    es137x_update(dev);

    if (dev->int_ctrl & INT_DAC1_EN) {
        if ((((dev->type == AUDIOPCI_ES1373) || (dev->type == AUDIOPCI_CT5880)) && (dev->int_ctrl & INT_DAC1_BYPASS)) || (dev->type == AUDIOPCI_ES1370)) {
            if ((dev->calc_sample_rate_synth != 44100) && (dev->type == AUDIOPCI_ES1370)) {
                if ((dev->dac[0].buffer_pos - dev->dac[0].buffer_pos_end) >= 0 && dev->step_synth >= dev->interp_step_synth)
                    es137x_fetch(dev, 0);

                if (dev->step_synth >= dev->interp_step_synth) {
                    dev->step_synth = 0;
                }

                dev->dac[0].out_l = lerp(dev->dac[0].prev_out_l, dev->dac[0].buffer_l[(dev->dac[0].buffer_pos) & 63], (dev->step_synth + 1) * dev->interp_factor_synth);
                dev->dac[0].out_r = lerp(dev->dac[0].prev_out_r, dev->dac[0].buffer_r[(dev->dac[0].buffer_pos) & 63], (dev->step_synth + 1) * dev->interp_factor_synth);

                dev->step_synth++;
                if (dev->step_synth >= dev->interp_step_synth) {
                    dev->dac[0].prev_out_l = dev->dac[0].out_l;
                    dev->dac[0].prev_out_r = dev->dac[0].out_r;
                    dev->dac[0].buffer_pos++;
                    goto dac0_count;
                }
            } else {
                /* SRC bypass. */
                if ((dev->dac[0].buffer_pos - dev->dac[0].buffer_pos_end) >= 0)
                    es137x_fetch(dev, 0);

                dev->dac[0].out_l = dev->dac[0].buffer_l[dev->dac[0].buffer_pos & 63];
                dev->dac[0].out_r = dev->dac[0].buffer_r[dev->dac[0].buffer_pos & 63];
                dev->dac[0].buffer_pos++;

                goto dac0_count;
            }
        } else {
            frac    = dev->dac[0].ac & 0x7fff;
            idx     = dev->dac[0].ac >> 15;
            samp1_l = dev->dac[0].filtered_l[idx];
            samp1_r = dev->dac[0].filtered_r[idx];
            samp2_l = dev->dac[0].filtered_l[(idx + 1) & 31];
            samp2_r = dev->dac[0].filtered_r[(idx + 1) & 31];

            dev->dac[0].out_l = ((samp1_l * (0x8000 - frac)) + (samp2_l * frac)) >> 15;
            dev->dac[0].out_r = ((samp1_r * (0x8000 - frac)) + (samp2_r * frac)) >> 15;
            dev->dac[0].ac += dev->dac[0].vf;
            dev->dac[0].ac &= ((32 << 15) - 1);
            if ((dev->dac[0].ac >> (15 + 4)) != dev->dac[0].f_pos) {
                es137x_next_sample_filtered(dev, 0, dev->dac[0].f_pos ? 16 : 0);
                dev->dac[0].f_pos = (dev->dac[0].f_pos + 1) & 1;

dac0_count:
                dev->dac[0].curr_samp_ct--;
                if (dev->dac[0].curr_samp_ct < 0) {
                    dev->int_status |= INT_STATUS_DAC1;
                    es137x_update_irqs(dev);
                    dev->dac[0].curr_samp_ct = dev->dac[0].samp_ct;
                }
            }
        }
    }

    if (dev->int_ctrl & INT_DAC2_EN) {
        if ((((dev->type == AUDIOPCI_ES1373) || (dev->type == AUDIOPCI_CT5880)) && (dev->int_ctrl & INT_DAC2_BYPASS)) || (dev->type == AUDIOPCI_ES1370)) {
            if ((dev->calc_sample_rate != 44100) && (dev->type == AUDIOPCI_ES1370)) {
                if ((dev->dac[1].buffer_pos - dev->dac[1].buffer_pos_end) >= 0 && dev->step_pcm >= dev->interp_step)
                    es137x_fetch(dev, 1);

                if (dev->step_pcm >= dev->interp_step) {
                    dev->step_pcm = 0;
                }

                dev->dac[1].out_l = lerp(dev->dac[1].prev_out_l, dev->dac[1].buffer_l[(dev->dac[1].buffer_pos) & 63], (dev->step_pcm + 1) * dev->interp_factor);
                dev->dac[1].out_r = lerp(dev->dac[1].prev_out_r, dev->dac[1].buffer_r[(dev->dac[1].buffer_pos) & 63], (dev->step_pcm + 1) * dev->interp_factor);

                dev->step_pcm++;
                if (dev->step_pcm >= dev->interp_step) {
                    dev->dac[1].prev_out_l = dev->dac[1].out_l;
                    dev->dac[1].prev_out_r = dev->dac[1].out_r;
                    dev->dac[1].buffer_pos++;
                    goto dac1_count;
                }
            } else {
                /* SRC bypass. */
                if ((dev->dac[1].buffer_pos - dev->dac[1].buffer_pos_end) >= 0)
                    es137x_fetch(dev, 1);

                dev->dac[1].out_l = dev->dac[1].buffer_l[dev->dac[1].buffer_pos & 63];
                dev->dac[1].out_r = dev->dac[1].buffer_r[dev->dac[1].buffer_pos & 63];
                dev->dac[1].buffer_pos++;

                goto dac1_count;
            }
        } else {
            frac    = dev->dac[1].ac & 0x7fff;
            idx     = dev->dac[1].ac >> 15;
            samp1_l = dev->dac[1].filtered_l[idx];
            samp1_r = dev->dac[1].filtered_r[idx];
            samp2_l = dev->dac[1].filtered_l[(idx + 1) & 31];
            samp2_r = dev->dac[1].filtered_r[(idx + 1) & 31];

            dev->dac[1].out_l = ((samp1_l * (0x8000 - frac)) + (samp2_l * frac)) >> 15;
            dev->dac[1].out_r = ((samp1_r * (0x8000 - frac)) + (samp2_r * frac)) >> 15;
            dev->dac[1].ac += dev->dac[1].vf;
            dev->dac[1].ac &= ((32 << 15) - 1);
            if ((dev->dac[1].ac >> (15 + 4)) != dev->dac[1].f_pos) {
                es137x_next_sample_filtered(dev, 1, dev->dac[1].f_pos ? 16 : 0);
                dev->dac[1].f_pos = (dev->dac[1].f_pos + 1) & 1;

dac1_count:
                dev->dac[1].curr_samp_ct--;
                if (dev->dac[1].curr_samp_ct < 0) {
                    dev->int_status |= INT_STATUS_DAC2;
                    es137x_update_irqs(dev);
                    dev->dac[1].curr_samp_ct = dev->dac[1].samp_ct;
                }
            }
        }
    }
}

static void
es137x_get_buffer(int32_t *buffer, uint16_t len, void *priv)
{
    es137x_t *dev = (es137x_t *) priv;

    es137x_update(dev);

    for (uint16_t c = 0; c < len * 2; c++)
        buffer[c] += (dev->buffer[c] / 2);

    dev->pos = 0;
}

static void
es1370_filter_cd_audio(int channel, double *buffer, void *priv)
{
    const es137x_t *dev       = (es137x_t *) priv;
    double          c         = 0.0;
    double          mastervol = ((dev->akm_codec.registers[channel] & 0x80) ? 0 : akm4531_att_2dbstep_5bits[(dev->akm_codec.registers[channel] & 0x1f) ^ 0x1f]) / 32767.0;
    double          cdvol     = ((dev->akm_codec.registers[channel + 0x6] & 0x80) ? 0 : akm4531_gain_2dbstep_5bits[(dev->akm_codec.registers[channel + 0x6] & 0x1f)]) / 32767.0;

    c       = *buffer * mastervol * cdvol;
    *buffer = c;
}

static void
es1371_filter_cd_audio(int channel, double *buffer, void *priv)
{
    const es137x_t *dev = (es137x_t *) priv;
    double          c;
    int             cd     = channel ? dev->cd_vol_r : dev->cd_vol_l;
    int             master = channel ? dev->master_vol_r : dev->master_vol_l;

    c       = ((((*buffer) * cd) / 65536.0) * master) / 65536.0;
    *buffer = c;
}

static inline double
sinc(double x)
{
    return sin(M_PI * x) / (M_PI * x);
}

static void
generate_es137x_filter(void)
{
    /* Cutoff frequency = 1 / 32 */
    float fC = 1.0 / 32.0;
    float gain;
    int   n;

    for (n = 0; n < ES137x_NCoef; n++) {
        /* Blackman window */
        double w = 0.42 - (0.5 * cos((2.0 * n * M_PI) / (double) (ES137x_NCoef - 1))) + (0.08 * cos((4.0 * n * M_PI) / (double) (ES137x_NCoef - 1)));
        /* Sinc filter */
        double h = sinc(2.0 * fC * ((double) n - ((double) (ES137x_NCoef - 1) / 2.0)));

        /* Create windowed-sinc filter */
        low_fir_es137x_coef[n] = w * h;
    }

    low_fir_es137x_coef[(ES137x_NCoef - 1) / 2] = 1.0;

    gain = 0.0;
    for (n = 0; n < ES137x_NCoef; n++)
        gain += low_fir_es137x_coef[n] / (float) N;

    gain /= 0.65;

    /* Normalise filter, to produce unity gain */
    for (n = 0; n < ES137x_NCoef; n++)
        low_fir_es137x_coef[n] /= gain;
}

static void
es137x_input_msg(void *priv, uint8_t *msg, uint32_t len)
{
    es137x_t *dev = (es137x_t *) priv;

    for (uint32_t i = 0; i < len; i++)
        es137x_write_fifo(dev, msg[i]);
}

static int
es137x_input_sysex(void *priv, uint8_t *buffer, uint32_t len, int abort)
{
    es137x_t *dev = (es137x_t *) priv;
    uint32_t  i   = -1;

    audiopci_log("Abort = %i\n", abort);

    if (dev->uart_status & UART_STATUS_RXRDY)
        abort = 1;

    if (!abort) {
        for (i = 0; i < len; i++) {
            es137x_write_fifo(dev, buffer[i]);
            if (dev->uart_status & UART_STATUS_RXRDY)
                break;
        }
    }

    /* The last sent position is in i. Return 7 - i. */

    return 7 - i;
}

static void es137x_speed_changed(void *priv);

static void *
es1370_init(const device_t *info)
{
    es137x_t *dev = calloc(1, sizeof(es137x_t));
    dev->type = info->local;

    if (device_get_config_int("receive_input"))
        midi_in_handler(1, es137x_input_msg, es137x_input_sysex, dev);

    wavetable_add_handler(es137x_get_buffer, dev);
    sound_set_cd_audio_filter(es1370_filter_cd_audio, dev);

    dev->gameport = gameport_add(&gameport_pnp_device);
    gameport_remap(dev->gameport, 0x200);

    pci_add_card((info->local & 1) ? PCI_ADD_SOUND : PCI_ADD_NORMAL, es1370_pci_read, es1370_pci_write, dev, &dev->pci_slot);

    timer_add(&dev->dac[1].timer, es137x_poll, dev, 1);

    generate_es137x_filter();

    dev->dac[0].vol_l = 1 << 12;
    dev->dac[0].vol_r = 1 << 12;
    dev->dac[1].vol_l = 1 << 12;
    dev->dac[1].vol_r = 1 << 12;

    dev->pcm_vol_l    = 1 << 15;
    dev->pcm_vol_r    = 1 << 15;
    dev->master_vol_l = 1 << 15;
    dev->master_vol_r = 1 << 15;

    es137x_reset(dev);

    es137x_speed_changed(dev);

    for (int i = 0; i < 0x20; i++) {
        double attn                   = (12.0 - (i * 2.0));
        akm4531_gain_2dbstep_5bits[i] = pow(10, attn / 10.) * 32767.0;
    }

    return dev;
}

static void *
es1371_init(const device_t *info)
{
    es137x_t *dev = calloc(1, sizeof(es137x_t));
    dev->type = info->local & 0xffffff00;

    if (device_get_config_int("receive_input"))
        midi_in_handler(1, es137x_input_msg, es137x_input_sysex, dev);

    sound_add_handler(es137x_get_buffer, dev);
    sound_set_cd_audio_filter(es1371_filter_cd_audio, dev);

    dev->gameport = gameport_add(&gameport_pnp_device);
    gameport_remap(dev->gameport, 0x200);

    pci_add_card((info->local & 1) ? PCI_ADD_SOUND : PCI_ADD_NORMAL, es1371_pci_read, es1371_pci_write, dev, &dev->pci_slot);

    timer_add(&dev->dac[1].timer, es137x_poll, dev, 1);

    generate_es137x_filter();

    ac97_codec       = &dev->codec;
    ac97_codec_count = 1;
    ac97_codec_id    = 0;
    /* Let the machine decide the codec on onboard implementations. */
    if (!(info->local & 1))
        device_add(ac97_codec_get(device_get_config_int("codec")));

    es137x_reset(dev);

    return dev;
}

static void
es137x_close(void *priv)
{
    es137x_t *dev = (es137x_t *) priv;

    free(dev);
}

static void
es137x_speed_changed(void *priv)
{
    es137x_t *dev = (es137x_t *) priv;

    dev->dac[1].latch = (uint64_t) ((double) TIMER_USEC * (1000000.0 / (double) ((dev->type == AUDIOPCI_ES1370) ? WT_FREQ : SOUND_FREQ)));
}

static const device_config_t es1370_config[] = {
    // clang-format off
    {
        .name           = "receive_input",
        .description    = "Receive MIDI input",
        .type           = CONFIG_BINARY,
        .default_string = NULL,
        .default_int    = 1,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = { { 0 } },
        .bios           = { { 0 } }
    },
    { .name = "", .description = "", .type = CONFIG_END }
    // clang-format on
};

static const device_config_t es1371_config[] = {
    // clang-format off
    {
        .name           = "codec",
        .description    = "Codec",
        .type           = CONFIG_SELECTION,
        .default_string = NULL,
        .default_int    = AC97_CODEC_TR28023,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "Asahi Kasei AK4540",                .value = AC97_CODEC_AK4540  },
            { .description = "TriTech TR28023 / Creative CT1297", .value = AC97_CODEC_TR28023 },
            { .description = ""                                                               }
        },
        .bios           = { { 0 } }
    },
    {
        .name           = "receive_input",
        .description    = "Receive MIDI input",
        .type           = CONFIG_BINARY,
        .default_string = NULL,
        .default_int    = 1,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = { { 0 } },
        .bios           = { { 0 } }
    },
    { .name = "", .description = "", .type = CONFIG_END }
    // clang-format on
};

static const device_config_t es1373_config[] = {
    // clang-format off
    {
        .name           = "codec",
        .description    = "Codec",
        .type           = CONFIG_SELECTION,
        .default_string = NULL,
        .default_int    = AC97_CODEC_CS4297A,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "Crystal CS4297A",                   .value = AC97_CODEC_CS4297A  },
            { .description = "SigmaTel STAC9721T",                .value = AC97_CODEC_STAC9721 },
            { .description = "TriTech TR28023 / Creative CT1297", .value = AC97_CODEC_TR28023  },
            { .description = "" }
        },
        .bios           = { { 0 } }
    },
    {
        .name           = "receive_input",
        .description    = "Receive MIDI input",
        .type           = CONFIG_BINARY,
        .default_string = NULL,
        .default_int    = 1,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = { { 0 } },
        .bios           = { { 0 } }
    },
    { .name = "", .description = "", .type = CONFIG_END }
    // clang-format on
};

static const device_config_t ct5880_config[] = {
    // clang-format off
    {
        .name           = "codec",
        .description    = "Codec",
        .type           = CONFIG_SELECTION,
        .default_string = NULL,
        .default_int    = AC97_CODEC_STAC9708,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = {
            { .description = "SigmaTel STAC9708T",                .value = AC97_CODEC_STAC9708 },
            { .description = "SigmaTel STAC9721T (stereo)",       .value = AC97_CODEC_STAC9721 },
            { .description = "TriTech TR28023 / Creative CT1297", .value = AC97_CODEC_TR28023  },
            { .description = ""                                                                }
        },
        .bios           = { { 0 } }
    },
    {
        .name           = "receive_input",
        .description    = "Receive MIDI input",
        .type           = CONFIG_BINARY,
        .default_string = NULL,
        .default_int    = 1,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = { { 0 } },
        .bios           = { { 0 } }
    },
    { .name = "", .description = "", .type = CONFIG_END }
    // clang-format on
};

static const device_config_t es1371_onboard_config[] = {
    // clang-format off
    {
        .name           = "receive_input",
        .description    = "Receive MIDI input",
        .type           = CONFIG_BINARY,
        .default_string = NULL,
        .default_int    = 1,
        .file_filter    = NULL,
        .spinner        = { 0 },
        .selection      = { { 0 } }
    },
    { .name = "", .description = "", .type = CONFIG_END }
    // clang-format on
};

const device_t es1370_device = {
    .name          = "Ensoniq AudioPCI (ES1370)",
    .internal_name = "es1370",
    .flags         = DEVICE_PCI,
    .local         = AUDIOPCI_ES1370,
    .init          = es1370_init,
    .close         = es137x_close,
    .reset         = es137x_reset,
    .available     = NULL,
    .speed_changed = es137x_speed_changed,
    .force_redraw  = NULL,
    .config        = es1370_config
};

const device_t es1371_device = {
    .name          = "Ensoniq AudioPCI (ES1371)",
    .internal_name = "es1371",
    .flags         = DEVICE_PCI,
    .local         = AUDIOPCI_ES1371,
    .init          = es1371_init,
    .close         = es137x_close,
    .reset         = es137x_reset,
    .available     = NULL,
    .speed_changed = es137x_speed_changed,
    .force_redraw  = NULL,
    .config        = es1371_config
};

const device_t es1371_onboard_device = {
    .name          = "Ensoniq AudioPCI (ES1371) (On-Board)",
    .internal_name = "es1371_onboard",
    .flags         = DEVICE_PCI,
    .local         = AUDIOPCI_ES1371 | 1,
    .init          = es1371_init,
    .close         = es137x_close,
    .reset         = es137x_reset,
    .available     = NULL,
    .speed_changed = es137x_speed_changed,
    .force_redraw  = NULL,
    .config        = es1371_onboard_config
};

const device_t es1373_device = {
    .name          = "Sound Blaster PCI 128 (ES1373)",
    .internal_name = "es1373",
    .flags         = DEVICE_PCI,
    .local         = AUDIOPCI_ES1373,
    .init          = es1371_init,
    .close         = es137x_close,
    .reset         = es137x_reset,
    .available     = NULL,
    .speed_changed = es137x_speed_changed,
    .force_redraw  = NULL,
    .config        = es1373_config
};

const device_t es1373_onboard_device = {
    .name          = "Sound Blaster PCI 128 (ES1373) (On-Board)",
    .internal_name = "es1373_onboard",
    .flags         = DEVICE_PCI,
    .local         = AUDIOPCI_ES1373 | 1,
    .init          = es1371_init,
    .close         = es137x_close,
    .reset         = es137x_reset,
    .available     = NULL,
    .speed_changed = es137x_speed_changed,
    .force_redraw  = NULL,
    .config        = es1371_onboard_config
};

const device_t ct5880_device = {
    .name          = "Sound Blaster PCI 4.1 (CT5880)",
    .internal_name = "ct5880",
    .flags         = DEVICE_PCI,
    .local         = AUDIOPCI_CT5880,
    .init          = es1371_init,
    .close         = es137x_close,
    .reset         = es137x_reset,
    .available     = NULL,
    .speed_changed = es137x_speed_changed,
    .force_redraw  = NULL,
    .config        = ct5880_config
};

const device_t ct5880_onboard_device = {
    .name          = "Sound Blaster PCI 4.1 (CT5880) (On-Board)",
    .internal_name = "ct5880_onboard",
    .flags         = DEVICE_PCI,
    .local         = AUDIOPCI_CT5880 | 1,
    .init          = es1371_init,
    .close         = es137x_close,
    .reset         = es137x_reset,
    .available     = NULL,
    .speed_changed = es137x_speed_changed,
    .force_redraw  = NULL,
    .config        = es1371_onboard_config
};
