/*
 * 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.
 *
 *           Pro Audio Spectrum Plus and 16 emulation.
 *
 *           Original PAS uses:
 *               - 2 x OPL2;
 *               - PIT - sample rate/count;
 *               - LMC835N/LMC1982 - mixer;
 *               - YM3802 - MIDI Control System.
 *
 *           9A01 - I/O base:
 *               - base >> 2.
 *
 *           All below + I/O base.
 *
 *           B89 - Interrupt status / clear:
 *               - Bit 2 - sample rate;
 *               - Bit 3 - PCM;
 *               - Bit 4 - MIDI.
 *
 *           B88 - Audio mixer control register.
 *
 *           B8A - Audio filter control:
 *               - Bit 5 - mute?.
 *
 *           B8B - Interrupt mask / board ID:
 *               - Bits 5-7 - board ID (read only on PAS16).
 *
 *           F88 - PCM data (low).
 *
 *           F89 - PCM data (high).
 *
 *           F8A - PCM control?:
 *               - Bit 4 - input/output select (1 = output);
 *               - Bit 5 - mono/stereo select;
 *               - Bit 6 - PCM enable.
 *
 *           1388-138B - PIT clocked at 1193180 Hz:
 *               - 1388 - Sample rate;
 *               - 1389 - Sample count.
 *
 *           178B - ????.
 *           2789 - Board revision.
 *
 *           8389:
 *               - Bit 2 - 8/16 bit.
 *
 *           BF88 - Wait states.
 *
 *           EF8B:
 *               - Bit 3 - 16 bits okay ?.
 *
 *           F388:
 *               - Bit 6 - joystick enable.
 *
 *           F389:
 *               - Bits 0-2 - DMA.
 *
 *           F38A:
 *               - Bits 0-3 - IRQ.
 *
 *           F788:
 *               - Bit 1 - SB emulation;
 *               - Bit 0 - MPU401 emulation.
 *
 *           F789 - SB base address:
 *               - Bits 0-3 - Address bits 4-7.
 *
 *           FB8A - SB IRQ/DMA:
 *               - Bits 3-5 - IRQ;
 *               - Bits 6-7 - DMA.
 *
 *           FF88 - board model:
 *               - 3 = PAS16.
 *
 * Authors:  Sarah Walker, <https://pcem-emulator.co.uk/>
 *           Miran Grca, <mgrca8@gmail.com>
 *           TheCollector1995, <mariogplayer@gmail.com>
 *
 *           Copyright 2008-2024 Sarah Walker.
 *           Copyright 2024 Miran Grca.
 */
#include <math.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define HAVE_STDARG_H

#include "cpu.h"
#include <86box/86box.h>
#include <86box/device.h>
#include <86box/dma.h>
#include <86box/filters.h>
#include <86box/plat_unused.h>
#include <86box/io.h>
#include <86box/mem.h>
#include <86box/midi.h>
#include <86box/pic.h>
#include <86box/timer.h>
#include <86box/pit.h>
#include <86box/pit_fast.h>
#include <86box/rom.h>
#include <86box/scsi_device.h>
#include <86box/scsi_ncr5380.h>
#include <86box/scsi_t128.h>
#include <86box/snd_mpu401.h>
#include <86box/sound.h>
#include <86box/snd_opl.h>
#include <86box/snd_sb.h>
#include <86box/snd_sb_dsp.h>

typedef struct nsc_mixer_t {
    double master_l;
    double master_r;

    int bass;
    int treble;

    double fm_l;
    double fm_r;
    double imixer_l;
    double imixer_r;
    double line_l;
    double line_r;
    double cd_l;
    double cd_r;
    double mic_l;
    double mic_r;
    double pcm_l;
    double pcm_r;
    double speaker_l;
    double speaker_r;

    uint8_t lmc1982_regs[8];
    uint8_t lmc835_regs[32];

    uint8_t  im_state;
    uint16_t im_data[4];
} nsc_mixer_t;

typedef struct mv508_mixer_t {
    double master_l;
    double master_r;

    int bass;
    int treble;

    double fm_l;
    double fm_r;
    double imixer_l;
    double imixer_r;
    double line_l;
    double line_r;
    double cd_l;
    double cd_r;
    double mic_l;
    double mic_r;
    double pcm_l;
    double pcm_r;
    double speaker_l;
    double speaker_r;
    double sb_l;
    double sb_r;

    uint8_t index;
    uint8_t regs[3][128];
} mv508_mixer_t;

typedef struct pas16_t {
    uint8_t  this_id;
    uint8_t  board_id;
    uint8_t  master_ff;
    uint8_t  has_scsi;
    uint8_t  dma;
    uint8_t  sb_irqdma;
    uint8_t  type;
    uint8_t  filter;

    uint8_t  audiofilt;
    uint8_t  audio_mixer;
    uint8_t  compat;
    uint8_t  compat_base;
    uint8_t  io_conf_1;
    uint8_t  io_conf_2;
    uint8_t  io_conf_3;
    uint8_t  io_conf_4;

    uint8_t  irq_stat;
    uint8_t  irq_ena;
    uint8_t  pcm_ctrl;
    uint8_t  prescale_div;
    uint8_t  stereo_lr;
    uint8_t  dma8_ff;
    uint8_t  waitstates;
    uint8_t  enhancedscsi;

    uint8_t  sys_conf_1;
    uint8_t  sys_conf_2;
    uint8_t  sys_conf_3;
    uint8_t  sys_conf_4;
    uint8_t  midi_ctrl;
    uint8_t  midi_stat;
    uint8_t  midi_data;
    uint8_t  fifo_stat;

    uint8_t  timeout_count;
    uint8_t  timeout_status;
    uint8_t  pad_array[6];

    uint8_t  midi_queue[256];

    uint16_t base;
    uint16_t new_base;
    uint16_t sb_compat_base;
    uint16_t mpu401_base;
    uint16_t dma8_dat;
    uint16_t ticks;
    uint16_t pcm_dat_l;
    uint16_t pcm_dat_r;

    int32_t  pcm_buffer[2][SOUNDBUFLEN * 2];

    int      pos;
    int      midi_r;
    int      midi_w;
    int      midi_uart_out;
    int      midi_uart_in;
    int      sysex;

    int      irq;
    int      scsi_irq;

    nsc_mixer_t   nsc_mixer;
    mv508_mixer_t mv508_mixer;

    fm_drv_t opl;
    sb_dsp_t dsp;

    mpu_t *  mpu;

    pitf_t * pit;

    t128_t * scsi;

    pc_timer_t scsi_timer;
} pas16_t;

static uint8_t pas16_next = 0;

static void    pas16_update(pas16_t *pas16);

static int pas16_dmas[8]    = { 4, 1, 2, 3, 0, 5, 6, 7 };
static int pas16_sb_irqs[8] = { 0, 2, 3, 5, 7, 10, 11, 12 };
static int pas16_sb_dmas[8] = { 0, 1, 2, 3 };

enum {
    PAS16_INT_SAMP = 0x04,
    PAS16_INT_PCM  = 0x08,
    PAS16_INT_MIDI = 0x10
};

enum {
    PAS16_PCM_MONO = 0x20,
    PAS16_PCM_ENA  = 0x40,
    PAS16_PCM_DMA_ENA = 0x80
};

enum {
    PAS16_SC2_16BIT  = 0x04,
    PAS16_SC2_MSBINV = 0x10
};

enum {
    PAS16_FILT_MUTE = 0x20
};

enum {
    STATE_SM_IDLE = 0x00,
    STATE_LMC1982_ADDR   = 0x10,
    STATE_LMC1982_ADDR_0 = 0x10,
    STATE_LMC1982_ADDR_1,
    STATE_LMC1982_ADDR_2,
    STATE_LMC1982_ADDR_3,
    STATE_LMC1982_ADDR_4,
    STATE_LMC1982_ADDR_5,
    STATE_LMC1982_ADDR_6,
    STATE_LMC1982_ADDR_7,
    STATE_LMC1982_ADDR_OVER,
    STATE_LMC1982_DATA   = 0x10,
    STATE_LMC1982_DATA_0 = 0x20,
    STATE_LMC1982_DATA_1,
    STATE_LMC1982_DATA_2,
    STATE_LMC1982_DATA_3,
    STATE_LMC1982_DATA_4,
    STATE_LMC1982_DATA_5,
    STATE_LMC1982_DATA_6,
    STATE_LMC1982_DATA_7,
    STATE_LMC1982_DATA_8,
    STATE_LMC1982_DATA_9,
    STATE_LMC1982_DATA_A,
    STATE_LMC1982_DATA_B,
    STATE_LMC1982_DATA_C,
    STATE_LMC1982_DATA_D,
    STATE_LMC1982_DATA_E,
    STATE_LMC1982_DATA_F,
    STATE_LMC1982_DATA_OVER,
    STATE_LMC835_DATA   = 0x40,
    STATE_LMC835_DATA_0 = 0x40,
    STATE_LMC835_DATA_1,
    STATE_LMC835_DATA_2,
    STATE_LMC835_DATA_3,
    STATE_LMC835_DATA_4,
    STATE_LMC835_DATA_5,
    STATE_LMC835_DATA_6,
    STATE_LMC835_DATA_7,
    STATE_LMC835_DATA_OVER,
};

#define    STATE_DATA_MASK           0x07
#define    STATE_DATA_MASK_W         0x0f

#define    LMC1982_ADDR              0x00
#define    LMC1982_DATA              0x01
#define    LMC835_ADDR               0x02
#define    LMC835_DATA               0x03

#define    LMC835_FLAG_ADDR          0x80

#define    SERIAL_MIXER_IDENT        0x10
#define    SERIAL_MIXER_STROBE       0x04
#define    SERIAL_MIXER_CLOCK        0x02
#define    SERIAL_MIXER_DATA         0x01
#define    SERIAL_MIXER_RESET_PCM    0x01

#define PAS16_PCM_AND_DMA_ENA (PAS16_PCM_ENA | PAS16_PCM_DMA_ENA)

/*
   LMC1982CIN registers (data bits 7 and 6 are always don't care):
   - 40              = Mode (0x01 = INPUT2);
   - 41              = 0 = Loudness (1 = on, 0 = off), 1 = Stereo Enhance (1 = on, 0 = off)
                       (0x00);
   - 42              = Bass, 00-0c (0x06);
   - 43              = Treble, 00-0c (0x06);
   - 45 [L] / 44 [R] = Master, 28-00, counting down (0x28, later 0xa8);
   - 46              = ???? (0x05 = Stereo).
 */
#define LMC1982_REG_MASK      0xf8    /* LMC1982CIN: Register Mask */
#define LMC1982_REG_VALID     0x40    /* LMC1982CIN: Register Valid Value */

#define LMC1982_REG_ISELECT   0x00    /* LMC1982CIN: Input Select + Mute */
#define LMC1982_REG_LES       0x01    /* LMC1982CIN: Loudness, Enhanced Stereo */
#define LMC1982_REG_BASS      0x02    /* LMC1982CIN: Bass */
#define LMC1982_REG_TREBLE    0x03    /* LMC1982CIN: Treble */
/* The Windows 95 driver indicates left and right are swapped in the wiring. */
#define LMC1982_REG_VOL_L     0x04    /* LMC1982CIN: Left Volume */
#define LMC1982_REG_VOL_R     0x05    /* LMC1982CIN: Right Volume */
#define LMC1982_REG_MODE      0x06    /* LMC1982CIN: Mode */
#define LMC1982_REG_DINPUT    0x07    /* LMC1982CIN: Digital Input 1 and 2 */

/* Bits 7-2: Don't care. */
#define LMC1982_ISELECT_MASK  0x03    /* LMC1982CIN: Input Select: Mask */
#define LMC1982_ISELECT_I1    0x00    /* LMC1982CIN: Input Select: INPUT1 */
#define LMC1982_ISELECT_I2    0x01    /* LMC1982CIN: Input Select: INPUT2 */
#define LMC1982_ISELECT_NA    0x02    /* LMC1982CIN: Input Select: N/A */
#define LMC1982_ISELECT_MUTE  0x03    /* LMC1982CIN: Input Select: MUTE */

/* Bits 7-2: Don't care. */
#define LMC1982_LES_MASK      0x03    /* LMC1982CIN: Loudness, Enhanced Stereo: Mask */
#define LMC1982_LES_BOTH_OFF  0x00    /* LMC1982CIN: Loudness OFF, Enhanced Stereo OFF */
#define LMC1982_L_ON_ES_OFF   0x01    /* LMC1982CIN: Loudness ON, Enhanced Stereo OFF */
#define LMC1982_L_OFF_ES_ON   0x02    /* LMC1982CIN: Loudness OFF, Enhanced Stereo ON */
#define LMC1982_LES_BOTH_ON   0x03    /* LMC1982CIN: Loudness OFF, Enhanced Stereo ON */

/* Bits 7-3: Don't care. */
#define LMC1982_MODE_MASK     0x07    /* LMC1982CIN: Mode: Mask */
#define LMC1982_MODE_MONO_L   0x04    /* LMC1982CIN: Mode: Left Mono */
#define LMC1982_MODE_STEREO   0x05    /* LMC1982CIN: Mode: Stereo */
#define LMC1982_MODE_MONO_R   0x06    /* LMC1982CIN: Mode: Right Mono */
#define LMC1982_MODE_MONO_R_2 0x07    /* LMC1982CIN: Mode: Right Mono (Alternate value) */

/* Bits 7-2: Don't care. */
#define LMC1982_DINPUT_MASK   0x03    /* LMC1982CIN: Digital Input 1 and 2: Mask */
#define LMC1982_DINPUT_DI1    0x01    /* LMC1982CIN: Digital Input 1 */
#define LMC1982_DINPUT_DI2    0x02    /* LMC1982CIN: Digital Input 2 */

/*
   LMC835N registers:
   - 01 [L] / 08 [R] = FM, 00-2f, bit 6 = clear, but the DOS driver sets
                       the bit, indicating a volume boost (0x69);
   - 02 [L] / 09 [R] = Rec. monitor, 00-2f, bit 6 = clear (0x29);
   - 03 [L] / 0A [R] = Line in, 00-2f, bit 6 = clear, except for the DOS
                       driver (0x69);
   - 04 [L] / 0B [R] = CD, 00-2f, bit 6 = clear, except for the DOS driver
                       (0x69);
   - 05 [L] / 0C [R] = Microphone, 00-2f, bit 6 = clear, except for the DOS
                       driver (0x44);
   - 06 [L] / 0D [R] = Wave out, 00-2f, bit 6 = set, except for DOS driver,
                       which clears the bit (0x29);
   - 07 [L] / 0E [R] = PC speaker, 00-2f, bit 6 = clear, except for the DOS
                       drive (0x06).
   The registers for which the DOS driver sets the boost, also have bit 6
   set in the address, despite the fact it should be a don't care - why?
   Apparently, no Sound Blaster control.
*/
#define LMC835_REG_MODE       0x00    /* LMC835N: Mode, not an actual register, but our internal register
                                                  to store the mode for Channels A (1-7) and B (8-e). */

#define LMC835_REG_FM_L       0x01    /* LMC835N: FM Left */
#define LMC835_REG_IMIXER_L   0x02    /* LMC835N: Record Monitor Left */
#define LMC835_REG_LINE_L     0x03    /* LMC835N: Line in Left */
#define LMC835_REG_CDROM_L    0x04    /* LMC835N: CD Left */
#define LMC835_REG_MIC_L      0x05    /* LMC835N: Microphone Left */
#define LMC835_REG_PCM_L      0x06    /* LMC835N: Wave out Left */
#define LMC835_REG_SPEAKER_L  0x07    /* LMC83N5: PC speaker Left */

#define LMC835_REG_FM_R       0x08    /* LMC835N: FM Right */
#define LMC835_REG_IMIXER_R   0x09    /* LMC835N: Record Monitor Right */
#define LMC835_REG_LINE_R     0x0a    /* LMC835N: Line in Right */
#define LMC835_REG_CDROM_R    0x0b    /* LMC835N: CD Right */
#define LMC835_REG_MIC_R      0x0c    /* LMC835N: Microphone Right */
#define LMC835_REG_PCM_R      0x0d    /* LMC835N: Wave out Right */
#define LMC835_REG_SPEAKER_R  0x0e    /* LMC83N5: PC speaker Right */

#define MV508_ADDRESS    0x80    /* Flag indicating it is the address */
/*
   I think this may actually operate as such:
       - Bit 6: Mask left channel;
       - Bit 5: Mask right channel.
 */
#define MV508_CHANNEL    0x60
#define MV508_LEFT       0x20
#define MV508_RIGHT      0x40
#define MV508_BOTH       0x00
#define MV508_MIXER      0x10    /* Flag indicating it is a mixer rather than a volume */

#define MV508_INPUT_MIX  0x20    /* Flag indicating the selected mixer is input */

#define MV508_MASTER_A   0x01    /* Volume: Output */
#define MV508_MASTER_B   0x02    /* Volume: DSP input */
#define MV508_BASS       0x03    /* Volume: Bass */
#define MV508_TREBLE     0x04    /* Volume: Treble */
#define MV508_MODE       0x05    /* Volume: Mode */

#define MV508_LOUDNESS   0x04    /* Mode: Loudness */
#define MV508_ENH_MASK   0x03    /* Mode: Stereo enhancement bit mask */
#define MV508_ENH_NONE   0x00    /* Mode: No stereo enhancement */
#define MV508_ENH_40     0x01    /* Mode: 40% stereo enhancement */
#define MV508_ENH_60     0x02    /* Mode: 60% stereo enhancement */
#define MV508_ENH_80     0x03    /* Mode: 80% stereo enhancement */

#define MV508_FM         0x00    /* Mixer: FM */
#define MV508_IMIXER     0x01    /* Mixer: Input mixer (recording monitor) */
#define MV508_LINE       0x02    /* Mixer: Line in */
#define MV508_CDROM      0x03    /* Mixer: CD-ROM */
#define MV508_MIC        0x04    /* Mixer: Microphone */
#define MV508_PCM        0x05    /* Mixer: PCM */
#define MV508_SPEAKER    0x06    /* Mixer: PC Speaker */
#define MV508_SB         0x07    /* Mixer: Sound Blaster DSP */

#define MV508_REG_MASTER_A_L    (MV508_MASTER_A | MV508_LEFT)
#define MV508_REG_MASTER_A_R    (MV508_MASTER_A | MV508_RIGHT)
#define MV508_REG_MASTER_B_L    (MV508_MASTER_B | MV508_LEFT)
#define MV508_REG_MASTER_B_R    (MV508_MASTER_B | MV508_RIGHT)
#define MV508_REG_BASS          (MV508_BASS | MV508_LEFT)
#define MV508_REG_TREBLE        (MV508_TREBLE | MV508_LEFT)

#define MV508_REG_FM_L          (MV508_MIXER | MV508_FM | MV508_LEFT)
#define MV508_REG_FM_R          (MV508_MIXER | MV508_FM | MV508_RIGHT)
#define MV508_REG_IMIXER_L      (MV508_MIXER | MV508_IMIXER | MV508_LEFT)
#define MV508_REG_IMIXER_R      (MV508_MIXER | MV508_IMIXER | MV508_RIGHT)
#define MV508_REG_LINE_L        (MV508_MIXER | MV508_LINE | MV508_LEFT)
#define MV508_REG_LINE_R        (MV508_MIXER | MV508_LINE | MV508_RIGHT)
#define MV508_REG_CDROM_L       (MV508_MIXER | MV508_CDROM | MV508_LEFT)
#define MV508_REG_CDROM_R       (MV508_MIXER | MV508_CDROM | MV508_RIGHT)
#define MV508_REG_MIC_L         (MV508_MIXER | MV508_MIC | MV508_LEFT)
#define MV508_REG_MIC_R         (MV508_MIXER | MV508_MIC | MV508_RIGHT)
#define MV508_REG_PCM_L         (MV508_MIXER | MV508_PCM | MV508_LEFT)
#define MV508_REG_PCM_R         (MV508_MIXER | MV508_PCM | MV508_RIGHT)
#define MV508_REG_SPEAKER_L     (MV508_MIXER | MV508_SPEAKER | MV508_LEFT)
#define MV508_REG_SPEAKER_R     (MV508_MIXER | MV508_SPEAKER | MV508_RIGHT)
#define MV508_REG_SB_L          (MV508_MIXER | MV508_SB | MV508_LEFT)
#define MV508_REG_SB_R          (MV508_MIXER | MV508_SB | MV508_RIGHT)

double low_fir_pas16_coef[SB16_NCoef];

/*
   Also used for the MVA508.
 */
static double lmc1982_bass_treble_4bits[16];

/*
   Copied from the Sound Blaster code: -62 dB to 0 dB in 2 dB steps.
   Note that these are voltage dB's, so it corresonds in power dB
   (formula for conversion to percentage: 10 ^ (dB / 10)) to -31 dB
   to 0 dB in 1 dB steps.

   This is used for the MVA508 Volumes.
 */
static const double mva508_att_2dbstep_5bits[] = {
       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
};

/*
   The same but in 1 dB steps, used for the MVA508 Master Volume.
 */
static const double mva508_att_1dbstep_6bits[] = {
       18.0,    25.0,    29.0,    32.0,    36.0,    41.0,    46.0,    51.0,
       58.0,    65.0,    73.0,    82.0,    92.0,   103.0,   116.0,   130.0,
      146.0,   164.0,   184.0,   206.0,   231.0,   260.0,   292.0,   327.0,
      367.0,   412.0,   462.0,   519.0,   582.0,   653.0,   733.0,   822.0,
      923.0,  1036.0,  1162.0,  1304.0,  1463.0,  1641.0,  1842.0,  2067.0,
     2319.0,  2602.0,  2920.0,  3276.0,  3676.0,  4125.0,  4628.0,  5192.0,
     5826.0,  6537.0,  7335.0,  8230.0,  9234.0, 10362.0, 11626.0, 13044.0,
    14636.0, 16422.0, 18426.0, 20674.0, 23197.0, 26027.0, 29204.0, 32767.0
};

/*
   In 2 dB steps again, but to -80 dB and counting down (0 = Flat), used
   for the LMC1982CIN Master Volume.
 */
static const double lmc1982_att_2dbstep_6bits[] = {
    32767.0, 26027.0, 20674.0, 16422.0, 13044.0, 10362.0,  8230.0,  6537.0,
     5192.0,  4125.0,  3276.0,  2602.0,  2067.0,  1641.0,  1304.0,  1036.0,
      822.0,   653.0,   519.0,   412.0,   327.0,   260.0,   206.0,   164.0,
      130.0,   103.0,    82.0,    65.0,    51.0,    41.0,    32.0,    25.0,
       20.0,    16.0,    13.0,    10.0,     8.0,     6.0,     5.0,     4.0,
        3.0,     3.0,     3.0,     3.0,     3.0,     3.0,     3.0,     3.0,
        3.0,     3.0,     3.0,     3.0,     3.0,     3.0,     3.0,     3.0,
        3.0,     3.0,     3.0,     3.0,     3.0,     3.0,     3.0,     3.0
};

/*
    LMC385N attenuation, both +/- 12 dB and +/- 6 dB.

    Since the DOS and Windows 95 driver diverge on boost vs. cut for
    the various inputs, I think it is best to just do a 12 dB cut on
    the input, and then apply cut or boost as needed. I have factored
    in said cut in the below values.
 */
static const double lmc835_att_1dbstep_7bits[128] = {
    /* Flat */
    [0x40] =  8230.0,    /*       Flat  */
    /* Boost */
    [0x60] =  9234.0,    /*  1 dB Boost */
    [0x50] = 10362.0,    /*  2 dB Boost */
    [0x48] = 11626.0,    /*  3 dB Boost */
    [0x44] = 13044.0,    /*  4 dB Boost */
    [0x42] = 14636.0,    /*  5 dB Boost */
    [0x52] = 16422.0,    /*  6 dB Boost */
    [0x6a] = 18426.0,    /*  7 dB Boost */
    [0x56] = 20674.0,    /*  8 dB Boost */
    [0x41] = 23197.0,    /*  9 dB Boost */
    [0x69] = 26027.0,    /* 10 dB Boost */
    [0x6d] = 29204.0,    /* 11 dB Boost */
    /* The Win95 drivers use D5-D0 = 1D instead of 2D, datasheet erratum? */
    [0x5d] = 29204.0,    /* 11 dB Boost */
    [0x6f] = 32767.0,    /* 12 dB Boost */
    /* Flat */
    /* The datasheet says this should be Flat (1.0) but the
       Windows 95 drivers use this as basically mute (12 dB Cut). */
    [0x00] =  2067.0,    /* 12 dB Cut   */
    /* Cut - D5-D0 = 2F is minimum cut (0 dB) according to Windows 95 */
    [0x20] =  2319.0,    /* 11 dB Cut   */
    [0x10] =  2602.0,    /* 10 dB Cut   */
    [0x08] =  2920.0,    /*  9 dB Cut   */
    [0x04] =  3276.0,    /*  8 dB Cut   */
    [0x02] =  3676.0,    /*  7 dB Cut   */
    [0x12] =  4125.0,    /*  6 dB Cut   */
    [0x2a] =  4628.0,    /*  5 dB Cut   */
    [0x16] =  5192.0,    /*  4 dB Cut   */
    [0x01] =  5826.0,    /*  3 dB Cut   */
    [0x29] =  6537.0,    /*  2 dB Cut   */
    [0x2d] =  7335.0,    /*  1 dB Cut   */
    /* The Win95 drivers use D5-D0 = 1D instead of 2D, datasheet erratum? */
    [0x1d] =  7335.0,    /*  1 dB Cut   */
    [0x2f] =  8230.0,    /*       Flat  */
                 0.0
};

static const double lmc835_att_05dbstep_7bits[128] = {
    /* Flat */
    [0x40] =  8230.0,    /*        Flat  */
    /* Boost */
    [0x60] =  8718.0,    /* 0.5 dB Boost */
    [0x50] =  9234.0,    /* 1.0 dB Boost */
    [0x48] =  9782.0,    /* 1.5 dB Boost */
    [0x44] = 10362.0,    /* 2.0 dB Boost */
    [0x42] = 10975.0,    /* 2.5 dB Boost */
    [0x52] = 11626.0,    /* 3.0 dB Boost */
    [0x6a] = 12315.0,    /* 3.5 dB Boost */
    [0x56] = 13044.0,    /* 4.0 dB Boost */
    [0x41] = 13817.0,    /* 4.5 dB Boost */
    [0x69] = 14636.0,    /* 5.0 dB Boost */
    [0x6d] = 15503.0,    /* 5.5 dB Boost */
    /* The Win95 drivers use D5-D0 = 1D instead of 2D, datasheet erratum? */
    [0x5d] = 15503.0,    /* 5.5 dB Boost */
    [0x6f] = 16422.0,    /* 6.0 dB Boost */
    /* Flat */
    /* The datasheet says this should be Flat (1.0) but the
       Windows 95 drivers use this as basically mute (12 dB Cut). */
    [0x00] =  4125.0,    /* 6.0 dB Cut   */
    /* Cut - D5-D0 = 2F is minimum cut (0 dB) according to Windows 95 */
    [0x20] =  4369.0,    /* 5.5 dB Cut   */
    [0x10] =  4628.0,    /* 5.0 dB Cut   */
    [0x08] =  4920.0,    /* 4.5 dB Cut   */
    [0x04] =  5192.0,    /* 4.0 dB Cut   */
    [0x02] =  5500.0,    /* 3.5 dB Cut   */
    [0x12] =  5826.0,    /* 3.0 dB Cut   */
    [0x2a] =  6172.0,    /* 2.5 dB Cut   */
    [0x16] =  6537.0,    /* 2.0 dB Cut   */
    [0x01] =  6925.0,    /* 1.5 dB Cut   */
    [0x29] =  7335.0,    /* 1.0 dB Cut   */
    [0x2d] =  7770.0,    /* 0.5 dB Cut   */
    /* The Win95 drivers use D5-D0 = 1D instead of 2D, datasheet erratum? */
    [0x1d] =  7770.0,    /* 0.5 dB Cut   */
    [0x2f] =  8230.0,    /*        Flat  */
                 0.0
};

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

static void
recalc_pas16_filter(const int playback_freq)
{
    /* Cutoff frequency = playback / 2 */
    int          n;
    const double fC = ((double) playback_freq) / (double) FREQ_96000;
    double       gain = 0.0;

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

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

    low_fir_pas16_coef[(SB16_NCoef - 1) / 2] = 1.0;

    for (n = 0; n < SB16_NCoef; n++)
        gain += low_fir_pas16_coef[n];

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

#ifdef ENABLE_PAS16_LOG
int pas16_do_log = ENABLE_PAS16_LOG;

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

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

static uint8_t
pas16_in(uint16_t port, void *priv);
static void
pas16_out(uint16_t port, uint8_t val, void *priv);

static void
pas16_update_irq(pas16_t *pas16)
{
    if (pas16->midi_uart_out && (pas16->midi_stat & 0x18)) {
        pas16->irq_stat |= PAS16_INT_MIDI;
        if ((pas16->irq != -1) && (pas16->irq_ena & PAS16_INT_MIDI))
            picint(1 << pas16->irq);
    }
    if (pas16->midi_uart_in && (pas16->midi_stat & 0x04)) {
        pas16->irq_stat |= PAS16_INT_MIDI;
        if ((pas16->irq != -1) && (pas16->irq_ena & PAS16_INT_MIDI))
            picint(1 << pas16->irq);
    }
}

static uint8_t
pas16_in(uint16_t port, void *priv)
{
    pas16_t *pas16 = (pas16_t *) priv;
    uint8_t  ret   = 0xff;

    port -= pas16->base;

    switch (port) {
        case 0x0000 ... 0x0003:
            ret = pas16->opl.read(port + 0x0388, pas16->opl.priv);
            break;

        case 0x0800:
            ret = pas16->type ? pas16->audio_mixer : 0xff;
            break;
        case 0x0801:
            ret = pas16->irq_stat & 0xdf;
            break;
        case 0x0802:
            ret = pas16->audiofilt;
            break;
        case 0x0803:
            ret = pas16->irq_ena | 0x20;
            pas16_log("IRQ Mask read=%02x.\n", ret);
            break;

        case 0x0c02:
            ret = pas16->pcm_ctrl;
            break;

        case 0x1401:
        case 0x1403:
            ret = pas16->midi_ctrl;
            break;
        case 0x1402:
        case 0x1802:
            ret = 0;
            if (pas16->midi_uart_in) {
                if ((pas16->midi_data == 0xaa) && (pas16->midi_ctrl & 0x04))
                    ret = pas16->midi_data;
                else {
                    ret = pas16->midi_queue[pas16->midi_r];
                    if (pas16->midi_r != pas16->midi_w) {
                        pas16->midi_r++;
                        pas16->midi_r &= 0xff;
                    }
                }
                pas16->midi_stat &= ~0x04;
                pas16_update_irq(pas16);
            }
            break;

        case 0x1800:
            ret = pas16->midi_stat;
            break;
        case 0x1801:
            ret = pas16->fifo_stat;
            break;

        case 0x1c00 ... 0x1c03:    /* NCR5380 ports 0 to 3. */
        case 0x3c00 ... 0x3c03:    /* NCR5380 ports 4 to 7. */
            if (pas16->has_scsi)
                ret = ncr5380_read((port & 0x0003) | ((port & 0x2000) >> 11), &pas16->scsi->ncr);
            break;

        case 0x2401:    /* Board revision */
            ret = 0x00;
            break;

        case 0x4000:
            ret = pas16->timeout_count;
            break;
        case 0x4001:
            ret = pas16->timeout_status;
            break;

        case 0x5c00:
            if (pas16->has_scsi)
                ret = t128_read(0x1e00, pas16->scsi);
            break;
        case 0x5c01:
            if (pas16->has_scsi)
                /* Bits 0-6 must absolutely be set for SCSI hard disk drivers to work. */
                ret = (((pas16->scsi->ncr.dma_mode != DMA_IDLE) && (pas16->scsi->status & 0x04)) << 7) | 0x7f;
            break;
        case 0x5c03:
            if (pas16->has_scsi)
                ret = pas16->scsi->ncr.irq_state << 7;
            break;

        case 0x7c01:
            ret = pas16->enhancedscsi & ~0x01;
            break;

        case 0x8000:
            ret = pas16->sys_conf_1;
            break;
        case 0x8001:
            ret = pas16->sys_conf_2;
            break;
        case 0x8002:
            ret = pas16->sys_conf_3;
            break;
        case 0x8003:
            ret = pas16->sys_conf_4;
            break;

        case 0xbc00:
            ret = pas16->waitstates;
            break;
        case 0xbc02:
            ret = pas16->prescale_div;
            break;

        case 0xec03:
            /*
               Operation mode 1:
               - 1,0 = CD-ROM (1,1 = SCSI, 1,0 = Sony, 0,0 = N/A);
               -   2 = FM (1 = stereo, 0 = mono);
               -   3 = Code (1 = 16-bit, 0 = 8-bit).
             */
            ret = pas16->type ? pas16->type : 0x07;
            break;

        case 0xf000:
            ret = pas16->io_conf_1;
            break;
        case 0xf001:
            ret = pas16->io_conf_2;
            pas16_log("pas16_in : set PAS DMA %i\n", pas16->dma);
            break;
        case 0xf002:
            ret = pas16->io_conf_3;
            pas16_log("pas16_in : set PAS IRQ %i\n", pas16->irq);
            break;
        case 0xf003:
            ret = pas16->io_conf_4;
            break;

        case 0xf400:
            ret = (pas16->compat & 0xf3);

            if (pas16->dsp.sb_irqm8 || pas16->dsp.sb_irqm16 || pas16->dsp.sb_irqm401)
                ret |= 0x04;

            if (pas16->mpu->mode == M_UART)
                ret |= 0x08;
            break;
        case 0xf401:
            ret = pas16->compat_base;
            break;

        case 0xf802:
            ret = pas16->sb_irqdma;
            break;

        case 0xfc00:    /* Board model */
            /* PAS16 or PASPlus */
            ret = pas16->type ? 0x0c : 0x01;
            break;
        case 0xfc03:    /* Master mode read */
            /* AT bus, XT/AT timing */
            ret = 0x11;
            if (pas16->type)
                ret |= 0x20;
            break;

        default:
            break;
    }

    pas16_log("[%04X:%08X] PAS16: [R] %04X (%04X) = %02X\n",
              CS, cpu_state.pc, port + pas16->base, port, ret);

    return ret;
}

static void
pas16_change_pit_clock_speed(void *priv)
{
    pas16_t *pas16 = (pas16_t *) priv;
    pitf_t *pit = (pitf_t *) pas16->pit;

    if (pas16->type && (pas16->sys_conf_1 & 0x02) && pas16->prescale_div) {
        pit_change_pas16_consts((double) pas16->prescale_div);
        if (pas16->sys_conf_3 & 0x02)
            pitf_set_pit_const(pit, PAS16CONST2);
        else
            pitf_set_pit_const(pit, PAS16CONST);
    } else
        pitf_set_pit_const(pit, PITCONST);
}

static void
pas16_io_handler(pas16_t *pas16, int set)
{
    if (pas16->base != 0x0000) {
        for (uint32_t addr = 0x0000; addr <= 0xffff; addr += 0x0400) {
            pas16_log("%04X-%04X: %i\n", pas16->base + addr, pas16->base + addr + 3, set);
            if (addr != 0x1000)
                io_handler(set, pas16->base + addr, 0x0004,
                           pas16_in, NULL, NULL, pas16_out, NULL, NULL, pas16);
        }

        pitf_handler(set, pas16->base + 0x1000, 0x0004, pas16->pit);
    }
}

static void
pas16_reset_pcm(void *priv)
{
    pas16_t *pas16 = (pas16_t *) priv;

    pas16->pcm_ctrl = 0x00;

    pas16->stereo_lr = 0;

    pas16->irq_stat &= 0xd7;

    if ((pas16->irq != -1) && !pas16->irq_stat)
        picintc(1 << pas16->irq);
}

static void
lmc1982_recalc(nsc_mixer_t *mixer)
{
    /* LMC1982CIN */
    /* According to the Windows 95 driver, the two volumes are swapped. */
    if ((mixer->lmc1982_regs[LMC1982_REG_ISELECT] & LMC1982_ISELECT_MASK) == LMC1982_ISELECT_I2) {
        switch (mixer->lmc1982_regs[LMC1982_REG_MODE] & LMC1982_MODE_MASK) {
            case LMC1982_MODE_MONO_L:
                mixer->master_l = mixer->master_r = lmc1982_att_2dbstep_6bits[mixer->lmc1982_regs[LMC1982_REG_VOL_R]] / 32767.0;
                break;
            case LMC1982_MODE_STEREO:
                mixer->master_l  = lmc1982_att_2dbstep_6bits[mixer->lmc1982_regs[LMC1982_REG_VOL_R]] / 32767.0;
                mixer->master_r  = lmc1982_att_2dbstep_6bits[mixer->lmc1982_regs[LMC1982_REG_VOL_L]] / 32767.0;
                break;
            case LMC1982_MODE_MONO_R:
            case LMC1982_MODE_MONO_R_2:
                mixer->master_l = mixer->master_r = lmc1982_att_2dbstep_6bits[mixer->lmc1982_regs[LMC1982_REG_VOL_L]] / 32767.0;
                break;
            default:
                mixer->master_l = mixer->master_r = 0.0;
                break;
        }

        mixer->bass      = mixer->lmc1982_regs[LMC1982_REG_BASS] & 0x0f;
        mixer->treble    = mixer->lmc1982_regs[LMC1982_REG_TREBLE] & 0x0f;
    } else {
        mixer->master_l  = mixer->master_r  = 0.0;

        mixer->bass      = 0x06;
        mixer->treble    = 0x06;
    }
}

static void
lmc835_recalc(nsc_mixer_t *mixer)
{
    /* LMC835N */
    /* Channel A (1-7) */
    const double *lmc835_att = (const double *) ((mixer->lmc835_regs[LMC835_REG_MODE] & 0x20) ?
                                                lmc835_att_05dbstep_7bits : lmc835_att_1dbstep_7bits);

    mixer->fm_l      = lmc835_att[mixer->lmc835_regs[LMC835_REG_FM_L] & 0x7f] / 32767.0;
    mixer->imixer_l  = lmc835_att[mixer->lmc835_regs[LMC835_REG_IMIXER_L] & 0x7f] / 32767.0;
    mixer->line_l    = lmc835_att[mixer->lmc835_regs[LMC835_REG_LINE_L] & 0x7f] / 32767.0;
    mixer->cd_l      = lmc835_att[mixer->lmc835_regs[LMC835_REG_CDROM_L] & 0x7f] / 32767.0;
    mixer->mic_l     = lmc835_att[mixer->lmc835_regs[LMC835_REG_MIC_L] & 0x7f] / 32767.0;
    mixer->pcm_l     = lmc835_att[mixer->lmc835_regs[LMC835_REG_PCM_L] & 0x7f] / 32767.0;
    mixer->speaker_l = lmc835_att[mixer->lmc835_regs[LMC835_REG_SPEAKER_L] & 0x7f] / 32767.0;

    /* Channel B (8-e) */
    lmc835_att       = (const double *) ((mixer->lmc835_regs[LMC835_REG_MODE] & 0x08) ?
                                        lmc835_att_05dbstep_7bits : lmc835_att_1dbstep_7bits);

    mixer->fm_r      = lmc835_att[mixer->lmc835_regs[LMC835_REG_FM_R] & 0x7f] / 32767.0;
    mixer->imixer_r  = lmc835_att[mixer->lmc835_regs[LMC835_REG_IMIXER_R] & 0x7f] / 32767.0;
    mixer->line_r    = lmc835_att[mixer->lmc835_regs[LMC835_REG_LINE_R] & 0x7f] / 32767.0;
    mixer->cd_r      = lmc835_att[mixer->lmc835_regs[LMC835_REG_CDROM_R] & 0x7f] / 32767.0;
    mixer->mic_r     = lmc835_att[mixer->lmc835_regs[LMC835_REG_MIC_R] & 0x7f] / 32767.0;
    mixer->pcm_r     = lmc835_att[mixer->lmc835_regs[LMC835_REG_PCM_R] & 0x7f] / 32767.0;
    mixer->speaker_r = lmc835_att[mixer->lmc835_regs[LMC835_REG_SPEAKER_R] & 0x7f] / 32767.0;
}

static void
mv508_mixer_recalc(mv508_mixer_t *mixer)
{
    mixer->fm_l      = mva508_att_2dbstep_5bits[mixer->regs[0][MV508_REG_FM_L]] / 32767.0;
    mixer->fm_r      = mva508_att_2dbstep_5bits[mixer->regs[0][MV508_REG_FM_R]] / 32767.0;
    mixer->imixer_l  = mva508_att_2dbstep_5bits[mixer->regs[0][MV508_REG_IMIXER_L]] / 32767.0;
    mixer->imixer_r  = mva508_att_2dbstep_5bits[mixer->regs[0][MV508_REG_IMIXER_R]] / 32767.0;
    mixer->line_l    = mva508_att_2dbstep_5bits[mixer->regs[0][MV508_REG_LINE_L]] / 32767.0;
    mixer->line_r    = mva508_att_2dbstep_5bits[mixer->regs[0][MV508_REG_LINE_R]] / 32767.0;
    mixer->cd_l      = mva508_att_2dbstep_5bits[mixer->regs[0][MV508_REG_CDROM_L]] / 32767.0;
    mixer->cd_r      = mva508_att_2dbstep_5bits[mixer->regs[0][MV508_REG_CDROM_R]] / 32767.0;
    mixer->mic_l     = mva508_att_2dbstep_5bits[mixer->regs[0][MV508_REG_MIC_L]] / 32767.0;
    mixer->mic_r     = mva508_att_2dbstep_5bits[mixer->regs[0][MV508_REG_MIC_R]] / 32767.0;
    mixer->pcm_l     = mva508_att_2dbstep_5bits[mixer->regs[0][MV508_REG_PCM_L]] / 32767.0;
    mixer->pcm_r     = mva508_att_2dbstep_5bits[mixer->regs[0][MV508_REG_PCM_R]] / 32767.0;
    mixer->speaker_l = mva508_att_2dbstep_5bits[mixer->regs[0][MV508_REG_SPEAKER_L]] / 32767.0;
    mixer->speaker_r = mva508_att_2dbstep_5bits[mixer->regs[0][MV508_REG_SPEAKER_R]] / 32767.0;
    mixer->sb_l      = mva508_att_2dbstep_5bits[mixer->regs[0][MV508_REG_SB_L]] / 32767.0;
    mixer->sb_r      = mva508_att_2dbstep_5bits[mixer->regs[0][MV508_REG_SB_R]] / 32767.0;

    mixer->master_l  = mva508_att_1dbstep_6bits[mixer->regs[2][MV508_REG_MASTER_A_L]] / 32767.0;
    mixer->master_r  = mva508_att_1dbstep_6bits[mixer->regs[2][MV508_REG_MASTER_A_R]] / 32767.0;

    mixer->bass      = mixer->regs[2][MV508_REG_BASS] & 0x0f;
    mixer->treble    = mixer->regs[2][MV508_REG_TREBLE] & 0x0f;
}

static void
pas16_nsc_mixer_reset(nsc_mixer_t *mixer)
{
    mixer->lmc1982_regs[LMC1982_REG_ISELECT] = 0x01;
    mixer->lmc1982_regs[LMC1982_REG_LES]     = 0x00;
    mixer->lmc1982_regs[LMC1982_REG_BASS]    = mixer->lmc1982_regs[LMC1982_REG_TREBLE]  = 0x06;
    mixer->lmc1982_regs[LMC1982_REG_VOL_L]   = mixer->lmc1982_regs[LMC1982_REG_VOL_R]   = 0x00; /*0x28*/ /*Note by TC1995: otherwise the volume gets lowered too much*/
    mixer->lmc1982_regs[LMC1982_REG_MODE]    = 0x05;

    lmc1982_recalc(mixer);

    mixer->lmc835_regs[LMC835_REG_MODE]      = 0x00;

    mixer->lmc835_regs[LMC835_REG_FM_L]      = mixer->lmc835_regs[LMC835_REG_FM_R]      = 0x69;
    mixer->lmc835_regs[LMC835_REG_IMIXER_L]  = mixer->lmc835_regs[LMC835_REG_IMIXER_R]  = 0x29;
    mixer->lmc835_regs[LMC835_REG_LINE_L]    = mixer->lmc835_regs[LMC835_REG_LINE_R]    = 0x69;
    mixer->lmc835_regs[LMC835_REG_CDROM_L]   = mixer->lmc835_regs[LMC835_REG_CDROM_R]   = 0x69;
    mixer->lmc835_regs[LMC835_REG_MIC_L]     = mixer->lmc835_regs[LMC835_REG_MIC_R]     = 0x44;
    mixer->lmc835_regs[LMC835_REG_PCM_L]     = mixer->lmc835_regs[LMC835_REG_PCM_R]     = 0x29;
    mixer->lmc835_regs[LMC835_REG_SPEAKER_L] = mixer->lmc835_regs[LMC835_REG_SPEAKER_R] = 0x06;

    lmc835_recalc(mixer);
}

static void
pas16_mv508_mixer_reset(mv508_mixer_t *mixer)
{
    /* Based on the Linux driver - TODO: The actual card's defaults. */
    mixer->regs[0][MV508_REG_FM_L]       = mixer->regs[0][MV508_REG_FM_R]       = 0x18;
    mixer->regs[0][MV508_REG_IMIXER_L]   = mixer->regs[0][MV508_REG_IMIXER_R]   = 0x1f;
    mixer->regs[0][MV508_REG_LINE_L]     = mixer->regs[0][MV508_REG_LINE_R]     = 0x17;
    mixer->regs[0][MV508_REG_CDROM_L]    = mixer->regs[0][MV508_REG_CDROM_R]    = 0x17;
    mixer->regs[0][MV508_REG_MIC_L]      = mixer->regs[0][MV508_REG_MIC_R]      = 0x17;
    mixer->regs[0][MV508_REG_PCM_L]      = mixer->regs[0][MV508_REG_PCM_R]      = 0x17;
    mixer->regs[0][MV508_REG_SPEAKER_L]  = mixer->regs[0][MV508_REG_SPEAKER_R]  = 0x0f;
    mixer->regs[0][MV508_REG_SB_L]       = mixer->regs[0][MV508_REG_SB_R]       = 0x17;

    mixer->regs[2][MV508_REG_MASTER_A_L] = mixer->regs[2][MV508_REG_MASTER_A_R] = 0x1f;

    mixer->regs[2][MV508_REG_BASS]       = 0x06;
    mixer->regs[2][MV508_REG_TREBLE]     = 0x06;

    mv508_mixer_recalc(mixer);
}

static void
pas16_reset_regs(void *priv)
{
    pas16_t *pas16 = (pas16_t *) priv;
    nsc_mixer_t *nsc_mixer = &pas16->nsc_mixer;
    mv508_mixer_t *mv508_mixer = &pas16->mv508_mixer;
    pitf_t *pit = (pitf_t *) pas16->pit;

    if (pas16->irq != -1)
        picintc(1 << pas16->irq);

    pas16->sys_conf_1 &= 0xfd;

    pas16->sys_conf_2 = 0x00;
    pas16->sys_conf_3 = 0x00;

    pas16->prescale_div = 0x00;

    pitf_set_pit_const(pit, PITCONST);

    pas16->audiofilt = 0x00;
    pas16->filter = 0;

    pitf_ctr_set_gate(pit, 0, 0);
    pitf_ctr_set_gate(pit, 1, 0);

    pas16_reset_pcm(pas16);
    pas16->dma8_ff = 0;

    pas16->irq_ena = 0x00;
    pas16->irq_stat = 0x00;

    if (pas16->type)
        pas16_mv508_mixer_reset(mv508_mixer);
    else
        pas16_nsc_mixer_reset(nsc_mixer);
}

static void
pas16_reset_common(void *priv)
{
    pas16_t *pas16 = (pas16_t *) priv;

    pas16_reset_regs(pas16);

    if (pas16->irq != -1)
        picintc(1 << pas16->irq);

    pas16_io_handler(pas16, 0);
    pas16->base = 0x0000;
}

static void
pas16_reset(void *priv)
{
    pas16_t *pas16 = (pas16_t *) priv;

    pas16_reset_common(priv);

    pas16->board_id = 0;
    pas16->master_ff = 0;

    pas16->base = 0x0388;
    pas16_io_handler(pas16, 1);

    pas16->new_base = 0x0388;

    pas16->sb_compat_base = 0x0220;
    pas16->compat = 0x02;
    pas16->compat_base = 0x02;
    sb_dsp_setaddr(&pas16->dsp, pas16->sb_compat_base);
}

static int
pas16_irq_convert(uint8_t val)
{
    int ret = val;

    if (ret == 0)
        ret = -1;
    else if (ret <= 6)
        ret++;
    else if (ret < 0x0b)
        ret += 3;
    else
        ret += 4;

    return ret;
}

static void
lmc1982_update_reg(nsc_mixer_t *mixer)
{
    pas16_log("LMC1982CIN register %02X = %04X\n",
              mixer->im_data[LMC1982_ADDR], mixer->im_data[LMC1982_DATA]);

    if ((mixer->im_data[LMC1982_ADDR] & LMC1982_REG_MASK) == LMC1982_REG_VALID) {
        mixer->im_data[LMC1982_ADDR] &= ~LMC1982_REG_MASK;
        mixer->lmc1982_regs[mixer->im_data[LMC1982_ADDR]] = mixer->im_data[LMC1982_DATA] & 0xff;
        lmc1982_recalc(mixer);
    }

    mixer->im_state = STATE_SM_IDLE;
}

static void
lmc835_update_reg(nsc_mixer_t *mixer)
{
    pas16_log("LMC835N register %02X = %02X\n",
              mixer->im_data[LMC835_ADDR], mixer->im_data[LMC835_DATA]);

    mixer->lmc835_regs[LMC835_REG_MODE] = mixer->im_data[LMC835_ADDR] & 0xf0;
    mixer->im_data[LMC835_ADDR] &= 0x0f;
    if ((mixer->im_data[LMC835_ADDR] >= 0x01) && (mixer->im_data[LMC835_ADDR] <= 0x0e))
        mixer->lmc835_regs[mixer->im_data[LMC835_ADDR] & 0x0f] = mixer->im_data[LMC835_DATA];
    lmc835_recalc(mixer);
}

static void
pas16_scsi_callback(void *priv)
{
    pas16_t *      pas16 = (pas16_t *) priv;
    t128_t  *      dev   = pas16->scsi;

    t128_callback(pas16->scsi);

    if ((dev->ncr.dma_mode != DMA_IDLE) && (dev->status & 0x04)) {
        timer_stop(&pas16->scsi_timer);
        pas16->timeout_status &= 0x7f;
    }
}

static void
pas16_timeout_callback(void *priv)
{
    pas16_t *      pas16       = (pas16_t *) priv;

    pas16->timeout_status |= 0x80;

    if ((pas16->timeout_status & 0x08) && (pas16->irq != -1))
        picint(1 << pas16->irq);

    timer_advance_u64(&pas16->scsi_timer, (pas16->timeout_count & 0x3f) * PASSCSICONST);
}

static void
pas16_out(uint16_t port, uint8_t val, void *priv)
{
    pas16_t *      pas16       = (pas16_t *) priv;
    nsc_mixer_t *  nsc_mixer   = &pas16->nsc_mixer;
    mv508_mixer_t *mv508_mixer = &pas16->mv508_mixer;

    pas16_log("[%04X:%08X] PAS16: [W] %04X (%04X) = %02X\n",
              CS, cpu_state.pc, port, port - pas16->base, val);

    port -= pas16->base;

    switch (port) {
        case 0x0000 ... 0x0003:
            pas16->opl.write(port + 0x0388, val, pas16->opl.priv);
            break;

        case 0x0400 ... 0x0402:
            break;
        case 0x0403:
            if (val & MV508_ADDRESS)
		mv508_mixer->index = val & ~MV508_ADDRESS;
            else {
                uint8_t bank;
                uint8_t mask;

                pas16_log("MVA508 register %02X = %02X\n",
                          mv508_mixer->index, val);

                if (mv508_mixer->index & MV508_MIXER) {
		    bank = !!(val & MV508_INPUT_MIX);
                    mask = 0x1f;
                } else {
		    bank = 2;
                    mask = 0x3f;
                }

                if (mv508_mixer->index & MV508_CHANNEL)
                    mv508_mixer->regs[bank][mv508_mixer->index] = val & mask;
                else {
                    mv508_mixer->regs[bank][mv508_mixer->index | MV508_LEFT] = val & mask;
                    mv508_mixer->regs[bank][mv508_mixer->index | MV508_RIGHT] = val & mask;
                }

                mv508_mixer_recalc(mv508_mixer);
            }
            break;

        case 0x0800:
            if (pas16->type && !(val & SERIAL_MIXER_RESET_PCM)) {
                pas16->audio_mixer = val;
                pas16_reset_pcm(pas16);
            } else if (!pas16->type) {
                switch (nsc_mixer->im_state) {
                    default:
                        break;
                    case STATE_SM_IDLE:
                        /* Transmission initiated. */
                        if (val & SERIAL_MIXER_IDENT) {
                            if (!(val & SERIAL_MIXER_CLOCK) && (pas16->audio_mixer & SERIAL_MIXER_CLOCK)) {
                                /* Prepare for receiving LMC835N data. */
                                nsc_mixer->im_data[LMC835_DATA] = 0x0000;
                                nsc_mixer->im_state |= STATE_LMC835_DATA;
                            }
                        } else {
                            if ((pas16->audio_mixer & SERIAL_MIXER_IDENT)) {
                                /*
                                   Prepare for receiving the LMC1982CIN address.
                                 */
                                nsc_mixer->im_data[LMC1982_ADDR] = 0x0000;
                                nsc_mixer->im_state |= STATE_LMC1982_ADDR;
                            }
                            if ((val & SERIAL_MIXER_CLOCK) && !(pas16->audio_mixer & SERIAL_MIXER_CLOCK)) {
                                /*
                                   Clock the least siginificant bit of the LMC1982CIN address.
                                 */
                                nsc_mixer->im_data[LMC1982_ADDR] |=
                                    ((val & SERIAL_MIXER_DATA) << (nsc_mixer->im_state & STATE_DATA_MASK));
                                nsc_mixer->im_state++;
                            }
                        }
                        break;
                    case STATE_LMC1982_ADDR_0:
                        if (val & SERIAL_MIXER_IDENT) {
                            /*
                               IDENT went high in LM1982CIN address state 0,
                               behave as if we were in the idle state.
                             */
                            if (!(val & SERIAL_MIXER_CLOCK) && (pas16->audio_mixer & SERIAL_MIXER_CLOCK)) {
                                nsc_mixer->im_data[LMC835_DATA] = 0x0000;
                                nsc_mixer->im_state = STATE_LMC835_DATA_0;
                            }
                        } else if ((val & SERIAL_MIXER_CLOCK) &&
                                   !(pas16->audio_mixer & SERIAL_MIXER_CLOCK)) {
                            /*
                               Clock the least siginificant bit of the LMC1982CIN address.
                             */
                            nsc_mixer->im_data[LMC1982_ADDR] |=
                                ((val & SERIAL_MIXER_DATA) << (nsc_mixer->im_state & STATE_DATA_MASK));
                            nsc_mixer->im_state++;
                        }
                        break;
                    case STATE_LMC1982_ADDR_1 ... STATE_LMC1982_ADDR_7:
                        if ((val & 0x02) && !(pas16->audio_mixer & 0x02)) {
                            /*
                               Clock the next bit of the LMC1982CIN address.
                             */
                            nsc_mixer->im_data[LMC1982_ADDR] |=
                                ((val & SERIAL_MIXER_DATA) << (nsc_mixer->im_state & STATE_DATA_MASK));
                            nsc_mixer->im_state++;
                        }
                        break;
                    case STATE_LMC1982_ADDR_OVER:
                        /*
                           Prepare for receiving the LMC1982CIN data.
                         */
                        nsc_mixer->im_data[LMC1982_DATA] = 0x0000;
                        nsc_mixer->im_state = STATE_LMC1982_DATA_0;
                        break;
                    case STATE_LMC1982_DATA_0 ... STATE_LMC1982_DATA_7:
                    case STATE_LMC1982_DATA_9 ... STATE_LMC1982_DATA_F:
                        if ((val & SERIAL_MIXER_CLOCK) && !(pas16->audio_mixer & SERIAL_MIXER_CLOCK)) {
                            /*
                               Clock the next bit of the LMC1982CIN data.
                             */
                            nsc_mixer->im_data[LMC1982_DATA] |=
                                ((val & SERIAL_MIXER_DATA) << (nsc_mixer->im_state & STATE_DATA_MASK_W));
                            nsc_mixer->im_state++;
                        }
                        break;
                    case STATE_LMC1982_DATA_8:
                        if (val & SERIAL_MIXER_IDENT) {
                            if (!(pas16->audio_mixer & SERIAL_MIXER_IDENT))
                                /*
                                   LMC1982CIN data transfer ended after 8 bits, process the data and
                                   reset the state back to idle.
                                 */
                                lmc1982_update_reg(nsc_mixer);
                            else if ((val & SERIAL_MIXER_CLOCK) &&
                                     !(pas16->audio_mixer & SERIAL_MIXER_CLOCK)) {
                                /*
                                   Clock the next bit of the LMC1982CIN data.
                                 */
                                nsc_mixer->im_data[LMC1982_DATA] |=
                                    ((val & SERIAL_MIXER_DATA) << (nsc_mixer->im_state & STATE_DATA_MASK_W));
                                nsc_mixer->im_state++;
                            }
                        }
                        break;
                    case STATE_LMC1982_DATA_OVER:
                        if ((val & SERIAL_MIXER_IDENT) && !(pas16->audio_mixer & SERIAL_MIXER_IDENT))
                            /*
                               LMC1982CIN data transfer ended, process the data and reset the state
                               back to idle.
                             */
                            lmc1982_update_reg(nsc_mixer);
                        break;
                    case STATE_LMC835_DATA_0 ... STATE_LMC835_DATA_7:
                        if ((val & SERIAL_MIXER_CLOCK) && !(pas16->audio_mixer & SERIAL_MIXER_CLOCK)) {
                            /*
                               Clock the next bit of the LMC835N data.
                             */
                            nsc_mixer->im_data[LMC835_DATA] |=
                                ((val &  SERIAL_MIXER_DATA) << (nsc_mixer->im_state & STATE_DATA_MASK));
                            nsc_mixer->im_state++;
                        }
                        break;
                    case STATE_LMC835_DATA_OVER:
                        if ((val & SERIAL_MIXER_STROBE) && !(pas16->audio_mixer & SERIAL_MIXER_STROBE)) {
                            if (nsc_mixer->im_data[LMC835_DATA] & LMC835_FLAG_ADDR)
                                /*
                                   The LMC835N data is an address, copy it into its own space for usage
                                   when processing it at the end and strip bits 7 (it's the address/data
                                   indicator) and 6 (it's a "don't care" bit).
                                 */
                                nsc_mixer->im_data[LMC835_ADDR] = nsc_mixer->im_data[LMC835_DATA] & 0x7f;
                            else
                                lmc835_update_reg(nsc_mixer);
                            nsc_mixer->im_state = STATE_SM_IDLE;
                        }
                        break;
                }

                pas16->audio_mixer = val;
            }
            break;
        case 0x0801:
            pas16->irq_stat &= ~val;
            if ((pas16->irq != -1) && !(pas16->irq_stat & 0x1f))
                picintc(1 << pas16->irq);
            break;
        case 0x0802:
            pas16_update(pas16);

            pitf_ctr_set_gate(pas16->pit, 1, !!(val & 0x80));
            pitf_ctr_set_gate(pas16->pit, 0, !!(val & 0x40));

            pas16->stereo_lr = 0;
            pas16->dma8_ff = 0;

            if ((val & 0x20) && !(pas16->audiofilt & 0x20)) {
                pas16_log("Reset.\n");
                pas16_reset_regs(pas16);
            }

            pas16->audiofilt = val;

            if (val & 0x1f) {
                pas16->filter = 1;
                switch (val & 0x1f) {
                    default:
                        pas16->filter = 0;
                        break;
                    case 0x01:
                        recalc_pas16_filter(17897);
                        break;
                    case 0x02:
                        recalc_pas16_filter(15909);
                        break;
                    case 0x04:
                        recalc_pas16_filter(2982);
                        break;
                    case 0x09:
                        recalc_pas16_filter(11931);
                        break;
                    case 0x11:
                        recalc_pas16_filter(8948);
                        break;
                    case 0x19:
                        recalc_pas16_filter(5965);
                        break;
                }
            } else
                pas16->filter = 0;
            break;
        case 0x0803:
            pas16->irq_ena = val & 0x1f;
            pas16->irq_stat &= ((val & 0x1f) | 0xe0);

            if ((pas16->irq != -1) && !(pas16->irq_stat & 0x1f))
                picintc(1 << pas16->irq);
            break;

        case 0x0c00:
        case 0x0c01:
            pas16_update(pas16);
            break;
        case 0x0c02:
            if ((val & PAS16_PCM_ENA) && !(pas16->pcm_ctrl & PAS16_PCM_ENA)) {
                /* Guess */
                pas16->stereo_lr = 0;
                pas16->irq_stat &= 0xd7;
                /* Needed for 8-bit DMA to work correctly on a 16-bit DMA channel. */
                pas16->dma8_ff = 0;
            }

            pas16->pcm_ctrl = val;
            pas16_log("Now in: %s (%02X)\n", (pas16->pcm_ctrl & PAS16_PCM_MONO) ? "Mono" : "Stereo", val);
            break;

        case 0x1401:
        case 0x1403:
            pas16->midi_ctrl = val;
            if ((val & 0x60) == 0x60) {
                pas16->midi_uart_out = 0;
                pas16->midi_uart_in = 0;
            } else if ((val & 0x1c) == 0x04)
                pas16->midi_uart_in = 1;
            else
                pas16->midi_uart_out = 1;

            pas16_update_irq(pas16);
            break;
        case 0x1402:
        case 0x1802:
            pas16->midi_data = val;
            pas16_log("UART OUT=%d.\n", pas16->midi_uart_out);
            if (pas16->midi_uart_out)
                midi_raw_out_byte(val);
            break;

        case 0x1800:
            pas16->midi_stat = val;
            pas16_update_irq(pas16);
            break;
        case 0x1801:
            pas16->fifo_stat = val;
            break;

        case 0x1c00 ... 0x1c03:    /* NCR5380 ports 0 to 3. */
        case 0x3c00 ... 0x3c03:    /* NCR5380 ports 4 to 7. */
            if (pas16->has_scsi)
                ncr5380_write((port & 0x0003) | ((port & 0x2000) >> 11), val, &pas16->scsi->ncr);
            break;

        case 0x4000:
            if (pas16->has_scsi) {
                pas16->timeout_count = val;
                if (timer_is_enabled(&pas16->scsi_timer))
                    timer_disable(&pas16->scsi_timer);
                if ((val & 0x3f) > 0x00)
                    timer_set_delay_u64(&pas16->scsi_timer, (val & 0x3f) * PASSCSICONST);
            }
            break;

        case 0x4001:
            if (pas16->has_scsi) {
                pas16->timeout_status = val & 0x7f;
                if (pas16->scsi_irq != -1)
                    picintc(1 << pas16->scsi_irq);
            }
            break;

        case 0x5c00:
            if (pas16->has_scsi)
                t128_write(0x1e00, val, pas16->scsi);
            break;
        case 0x5c03:
            if (pas16->has_scsi) {
                if (val & 0x80) {
                    pas16->scsi->ncr.irq_state = 0;
                    if (pas16->scsi_irq != -1)
                        picintc(1 << pas16->scsi_irq);
                }
            }
            break;

        case 0x7c01:
            pas16->enhancedscsi = val;
            break;

        case 0x8000:
            if ((val & 0xc0) && !(pas16->sys_conf_1 & 0xc0)) {
                pas16_log("Reset.\n");
                val = 0x00;
                pas16_reset_common(pas16);
                pas16->base = pas16->new_base;
                pas16_io_handler(pas16, 1);
            }

            pas16->sys_conf_1 = val;
            pas16_change_pit_clock_speed(pas16);
            pas16_log("Now in: %s mode\n", (pas16->sys_conf_1 & 0x02) ? "native" : "compatibility");
            break;
        case 0x8001:
            pas16->sys_conf_2 = val;
            pas16_log("Now in: %i bits (%02X)\n",
                      (pas16->sys_conf_2 & 0x04) ? ((pas16->sys_conf_2 & 0x08) ? 12 : 16) : 8, val);
            break;
        case 0x8002:
            pas16->sys_conf_3 = val;
            pas16_change_pit_clock_speed(pas16);
            pas16_log("Use 1.008 MHz clok for PCM: %c\n", (val & 0x02) ? 'Y' : 'N');
            break;
        case 0x8003:
            pas16->sys_conf_4 = val;
            if (pas16->has_scsi && (pas16->scsi_irq != -1) && !(val & 0x20))
                picintc(1 << pas16->scsi_irq);
            break;

        case 0xbc00:
            pas16->waitstates = val;
            break;
        case 0xbc02:
            pas16->prescale_div = val;
            pas16_change_pit_clock_speed(pas16);
            pas16_log("Prescale divider now: %i\n", val);
            break;

        case 0xf000:
            pas16->io_conf_1 = val;
            break;
        case 0xf001:
            pas16->io_conf_2 = val;
            pas16->dma       = pas16_dmas[val & 0x7];
            pas16_change_pit_clock_speed(pas16);
            pas16_log("pas16_out : set PAS DMA %i\n", pas16->dma);
            break;
        case 0xf002:
            pas16->io_conf_3 = val;
            if (pas16->irq != -1)
                picintc(1 << pas16->irq);
            pas16->irq       = pas16_irq_convert(val & 0x0f);
            if (pas16->has_scsi) {
                if (pas16->scsi_irq != -1)
                    picintc(1 << pas16->scsi_irq);
                pas16->scsi_irq  = pas16_irq_convert(val >> 4);
                ncr5380_set_irq(&pas16->scsi->ncr, pas16->scsi_irq);
            }

            pas16_log("pas16_out : set PAS IRQ %i, val=%02x\n", pas16->irq, val & 0x0f);
            break;
        case 0xf003:
            pas16->io_conf_4 = val;
            break;

        case 0xf400:
            pas16->compat = val & 0xf3;
            pas16_log("PCM compression is now %sabled\n", (val & 0x10) ? "en" : "dis");
            if (pas16->compat & 0x02)
                sb_dsp_setaddr(&pas16->dsp, pas16->sb_compat_base);
            else
                sb_dsp_setaddr(&pas16->dsp, 0);
            if (pas16->compat & 0x01)
                mpu401_change_addr(pas16->mpu, ((pas16->compat_base & 0xf0) | 0x300));
            else
                mpu401_change_addr(pas16->mpu, 0);
            break;
        case 0xf401:
            pas16->compat_base = val;
            pas16->sb_compat_base = ((pas16->compat_base & 0xf) << 4) | 0x200;
            pas16_log("SB Compatibility base: %04X\n", pas16->sb_compat_base);
            if (pas16->compat & 0x02)
                sb_dsp_setaddr(&pas16->dsp, pas16->sb_compat_base);
            if (pas16->compat & 0x01)
                mpu401_change_addr(pas16->mpu, ((pas16->compat_base & 0xf0) | 0x300));
            break;

        case 0xf802:
            pas16->sb_irqdma = val;
            mpu401_setirq(pas16->mpu, pas16_sb_irqs[val & 7]);
            sb_dsp_setirq(&pas16->dsp, pas16_sb_irqs[(val >> 3) & 7]);
            sb_dsp_setdma8(&pas16->dsp, pas16_sb_dmas[(val >> 6) & 3]);
            pas16_log("pas16_out : set SB IRQ %i DMA %i.\n", pas16_sb_irqs[(val >> 3) & 7],
                      pas16_sb_dmas[(val >> 6) & 3]);
            break;

        default:
            pas16_log("pas16_out : unknown %04X\n", port);
    }
}

/*
   8-bit mono:
    - 8-bit DMA : On every timer 0 over, read the 8-bit sample and ctr_clock();
      (One ctr_clock() per timer 0 over)
    - 16-bit DMA: On every even timer 0 over, read two 8-bit samples at once and ctr_clock();
                  On every odd timer 0 over, read the MSB of the previously read sample word.
      (One ctr_clock() per two timer 0 overs)
   8-bit stereo:
    - 8-bit DMA : On every timer 0, read two 8-bit samples and ctr_clock() twice;
      (Two ctr_clock()'s per timer 0 over)
    - 16-bit DMA: On every timer 0, read two 8-bit samples and ctr_clock() once.
      (One ctr_clock() per timer 0 over)
   16-bit mono (to be verified):
    - 8-bit DMA : On every timer 0, read one 16-bit sample and ctr_clock() twice;
      (Two ctr_clock()'s per timer 0 over)
    - 16-bit DMA: On every timer 0, read one 16-bit sample and ctr_clock() once.
      (One ctr_clock() per timer 0 over)
   16-bit stereo:
    - 8-bit DMA : On every timer 0, read one 16-bit sample and ctr_clock() twice;
      (Two ctr_clock()'s per timer 0 over)
    - 16-bit DMA: On every timer 0, read one 16-bit sample and ctr_clock() twice.
      (Two ctr_clock()'s per timer 0 over)

   What we can conclude from this is:
    - Maximum 16 bits per timer 0 over;
    - A 8-bit sample always takes one ctr_clock() tick, unless it has been read
      alongside the previous sample;
    - A 16-bit sample always takes two ctr_clock() ticks.
 */
static uint16_t
pas16_dma_channel_read(pas16_t *pas16, int channel)
{
    int status;
    uint16_t ret;

    if (pas16->pcm_ctrl & PAS16_PCM_DMA_ENA) {
        if (pas16->dma >= 5) {
            dma_channel_advance(pas16->dma);
            status = dma_channel_read_only(pas16->dma);
        } else
            status = dma_channel_read(pas16->dma);
        ret = (status == DMA_NODATA) ? 0x0000 : (status & 0xffff);
    } else
        ret = 0x0000;

    return ret;
}

static uint16_t
pas16_dma_readb(pas16_t *pas16)
{
    const uint16_t ret = pas16_dma_channel_read(pas16, pas16->dma);

    pas16->ticks++;

    return ret;
}

static uint16_t
pas16_dma_readw(pas16_t *pas16, const uint8_t timer1_ticks)
{
    uint16_t ret;

    if (pas16->dma >= 5)
        ret = pas16_dma_channel_read(pas16, pas16->dma);
    else {
        ret = pas16_dma_channel_read(pas16, pas16->dma);
        ret |= (pas16_dma_channel_read(pas16, pas16->dma) << 8);
    }

    pas16->ticks += timer1_ticks;

    return ret;
}

static uint16_t
pas16_readdmab(pas16_t *pas16)
{
    if (pas16->dma >= 5) {
        if (pas16->dma8_ff)
            pas16->dma8_dat >>= 8;
        else
            pas16->dma8_dat = pas16_dma_readb(pas16);

        pas16->dma8_ff = !pas16->dma8_ff;
    } else
        pas16->dma8_dat = pas16_dma_readb(pas16);

    return ((pas16->dma8_dat & 0xff) ^ 0x80) << 8;
}

static uint16_t
pas16_readdmaw_mono(pas16_t *pas16)
{
    const uint16_t ret = pas16_dma_readw(pas16, 1 + (pas16->dma < 5));

    return ret;
}

static uint16_t
pas16_readdmaw_stereo(pas16_t *pas16)
{
    uint16_t ret;
    uint16_t ticks = (pas16->sys_conf_1 & 0x02) ? (1 + (pas16->dma < 5)) : 2;

    ret = pas16_dma_readw(pas16, ticks);

    return ret;
}

static uint16_t
pas16_readdma_mono(pas16_t *pas16)
{
    uint16_t ret;

    if (pas16->sys_conf_2 & 0x04) {
        ret = pas16_readdmaw_mono(pas16);

        if (pas16->sys_conf_2 & 0x08)
            ret &= 0xfff0;
    } else
        ret = pas16_readdmab(pas16);

    if (pas16->sys_conf_2 & PAS16_SC2_MSBINV)
        ret ^= 0x8000;

    return ret;
}

static uint16_t
pas16_readdma_stereo(pas16_t *pas16)
{
    uint16_t ret;

    if (pas16->sys_conf_2 & 0x04) {
        ret = pas16_readdmaw_stereo(pas16);

        if (pas16->sys_conf_2 & 0x08)
            ret &= 0xfff0;
    } else
        ret = pas16_readdmab(pas16);

    if (pas16->sys_conf_2 & PAS16_SC2_MSBINV)
        ret ^= 0x8000;

    return ret;
}

static void
pas16_pit_timer0(const int new_out, UNUSED(int old_out), void *priv)
{
    const pitf_t *pit   = (const pitf_t *) priv;
    pas16_t *     pas16 = (pas16_t *) pit->dev_priv;

    if (!pas16->pit->counters[0].gate)
        return;

    if (!dma_channel_readable(pas16->dma))
        return;

    pas16_update_irq(pas16);

    if (((pas16->pcm_ctrl & PAS16_PCM_ENA) == PAS16_PCM_ENA) && (pit->counters[1].m & 2) && new_out) {
        uint16_t temp;

        pas16->ticks = 0;

        if (pas16->pcm_ctrl & PAS16_PCM_MONO) {
            temp = pas16_readdma_mono(pas16);

            pas16->pcm_dat_l = pas16->pcm_dat_r = temp;
        } else {
            temp = pas16_readdma_stereo(pas16);

            if (pas16->sys_conf_1 & 0x02) {
                pas16->pcm_dat_l = temp;

                temp = pas16_readdma_stereo(pas16);

                pas16->pcm_dat_r = temp;
            } else {
                if (pas16->stereo_lr)
                    pas16->pcm_dat_r = temp;
                else
                    pas16->pcm_dat_l = temp;

                pas16->stereo_lr = !pas16->stereo_lr;
                pas16->irq_stat = (pas16->irq_stat & 0xdf) | (pas16->stereo_lr << 5);
            }
        }

        if (pas16->ticks) {
            for (uint16_t i = 0; i < pas16->ticks; i++)
                pitf_ctr_clock(pas16->pit, 1);

            pas16->ticks = 0;
        }

        pas16->irq_stat |= PAS16_INT_SAMP;
        if (pas16->irq_ena & PAS16_INT_SAMP) {
            pas16_log("INT SAMP.\n");
            if (pas16->irq != -1)
                picint(1 << pas16->irq);
        }

        pas16_update(pas16);
    }
}

static void
pas16_pit_timer1(const int new_out, UNUSED(int old_out), void *priv)
{
    const pitf_t *pit   = (pitf_t * )priv;
    pas16_t *     pas16 = (pas16_t *) pit->dev_priv;

    if (!pas16->pit->counters[1].gate)
        return;

    /* At new_out = 0, it's in the counter reload phase. */
    if ((pas16->pcm_ctrl & PAS16_PCM_ENA) && (pit->counters[1].m & 2) && new_out) {
        if (pas16->irq_ena & PAS16_INT_PCM) {
            pas16->irq_stat |= PAS16_INT_PCM;
            pas16_log("pas16_pcm_poll : cause IRQ %i %02X\n", pas16->irq, 1 << pas16->irq);
            if (pas16->irq != -1)
                picint(1 << pas16->irq);
        }
    }
}

static void
pas16_out_base(UNUSED(uint16_t port), uint8_t val, void *priv)
{
    pas16_t *pas16 = (pas16_t *) priv;

    pas16_log("[%04X:%08X] PAS16: [W] %04X = %02X\n", CS, cpu_state.pc, port, val);

    if (pas16->master_ff && (pas16->board_id == pas16->this_id))
        pas16->new_base = val << 2;
    else if (!pas16->master_ff)
        pas16->board_id = val;

    pas16->master_ff = !pas16->master_ff;
}

static void
pas16_input_msg(void *priv, uint8_t *msg, uint32_t len)
{
    pas16_t  *pas16 = (pas16_t *) priv;

    if (pas16->sysex)
        return;

    if (pas16->midi_uart_in) {
        pas16->midi_stat |= 0x04;

        for (uint32_t i = 0; i < len; i++) {
            pas16->midi_queue[pas16->midi_w++] = msg[i];
            pas16->midi_w &= 0xff;
        }

        pas16_update_irq(pas16);
    }
}

static int
pas16_input_sysex(void *priv, uint8_t *buffer, uint32_t len, int abort)
{
    pas16_t  *pas16 = (pas16_t *) priv;

    if (abort) {
        pas16->sysex = 0;
        return 0;
    }
    pas16->sysex = 1;
    for (uint32_t i = 0; i < len; i++) {
        if (pas16->midi_r == pas16->midi_w)
            return (int) (len - i);
        pas16->midi_queue[pas16->midi_w++] = buffer[i];
        pas16->midi_w &= 0xff;
    }
    pas16->sysex = 0;
    return 0;
}

static void
pas16_update(pas16_t *pas16)
{
    if (!(pas16->audiofilt & PAS16_FILT_MUTE)) {
        for (; pas16->pos < sound_pos_global; pas16->pos++) {
            pas16->pcm_buffer[0][pas16->pos] = 0;
            pas16->pcm_buffer[1][pas16->pos] = 0;
        }
    } else {
        for (; pas16->pos < sound_pos_global; pas16->pos++) {
            pas16->pcm_buffer[0][pas16->pos] = (int16_t) pas16->pcm_dat_l;
            pas16->pcm_buffer[1][pas16->pos] = (int16_t) pas16->pcm_dat_r;
        }
    }
}

void
pasplus_get_buffer(int32_t *buffer, int len, void *priv)
{
    pas16_t *          pas16   = (pas16_t *) priv;
    const nsc_mixer_t *mixer   = &pas16->nsc_mixer;
    double             bass_treble;

    sb_dsp_update(&pas16->dsp);
    pas16_update(pas16);
    for (int c = 0; c < len * 2; c += 2) {
        double out_l = pas16->dsp.buffer[c];
        double out_r = pas16->dsp.buffer[c + 1];

        if (pas16->filter) {
            /* We divide by 3 to get the volume down to normal. */
            out_l += low_fir_pas16(0, (double) pas16->pcm_buffer[0][c >> 1]) * mixer->pcm_l;
            out_r += low_fir_pas16(1, (double) pas16->pcm_buffer[1][c >> 1]) * mixer->pcm_r;
        } else {
            out_l += ((double) pas16->pcm_buffer[0][c >> 1]) * mixer->pcm_l;
            out_r += ((double) pas16->pcm_buffer[1][c >> 1]) * mixer->pcm_r;
        }

        out_l *= mixer->master_l;
        out_r *= mixer->master_r;

        /* This is not exactly how one does bass/treble controls, but the end result is like it.
           A better implementation would reduce the CPU usage. */
        if (mixer->bass != 6) {
            bass_treble = lmc1982_bass_treble_4bits[mixer->bass];

            if (mixer->bass > 6) {
                out_l += (low_iir(0, 0, out_l) * bass_treble);
                out_r += (low_iir(0, 1, out_r) * bass_treble);
            } else if (mixer->bass < 6) {
                out_l = (out_l *bass_treble + low_cut_iir(0, 0, out_l) * (1.0 - bass_treble));
                out_r = (out_r *bass_treble + low_cut_iir(0, 1, out_r) * (1.0 - bass_treble));
            }
        }

        if (mixer->treble != 6) {
            bass_treble = lmc1982_bass_treble_4bits[mixer->treble];

            if (mixer->treble > 6) {
                out_l += (high_iir(0, 0, out_l) * bass_treble);
                out_r += (high_iir(0, 1, out_r) * bass_treble);
            } else if (mixer->treble < 6) {
                out_l = (out_l *bass_treble + high_cut_iir(0, 0, out_l) * (1.0 - bass_treble));
                out_r = (out_r *bass_treble + high_cut_iir(0, 1, out_r) * (1.0 - bass_treble));
            }
        }

        buffer[c] += (int32_t) out_l;
        buffer[c + 1] += (int32_t) out_r;
    }

    pas16->pos = 0;
    pas16->dsp.pos = 0;
}

void
pasplus_get_music_buffer(int32_t *buffer, int len, void *priv)
{
    const pas16_t *    pas16   = (const pas16_t *) priv;
    const nsc_mixer_t *mixer   = &pas16->nsc_mixer;
    const int32_t *    opl_buf = pas16->opl.update(pas16->opl.priv);
    double             bass_treble;

    for (int c = 0; c < len * 2; c += 2) {
        double out_l = (((double) opl_buf[c]) * mixer->fm_l) * 0.7171630859375;
        double out_r = (((double) opl_buf[c + 1]) * mixer->fm_r) * 0.7171630859375;

        /* TODO: recording CD, Mic with AGC or line in. Note: mic volume does not affect recording. */
        out_l *= mixer->master_l;
        out_r *= mixer->master_r;

        /* This is not exactly how one does bass/treble controls, but the end result is like it.
           A better implementation would reduce the CPU usage. */
        if (mixer->bass != 6) {
            bass_treble = lmc1982_bass_treble_4bits[mixer->bass];

            if (mixer->bass > 6) {
                out_l += (low_iir(1, 0, out_l) * bass_treble);
                out_r += (low_iir(1, 1, out_r) * bass_treble);
            } else if (mixer->bass < 6) {
                out_l = (out_l *bass_treble + low_cut_iir(1, 0, out_l) * (1.0 - bass_treble));
                out_r = (out_r *bass_treble + low_cut_iir(1, 1, out_r) * (1.0 - bass_treble));
            }
        }

        if (mixer->treble != 6) {
            bass_treble = lmc1982_bass_treble_4bits[mixer->treble];

            if (mixer->treble > 6) {
                out_l += (high_iir(1, 0, out_l) * bass_treble);
                out_r += (high_iir(1, 1, out_r) * bass_treble);
            } else if (mixer->treble < 6) {
                out_l = (out_l *bass_treble + high_cut_iir(1, 0, out_l) * (1.0 - bass_treble));
                out_r = (out_r *bass_treble + high_cut_iir(1, 1, out_r) * (1.0 - bass_treble));
            }
        }

        buffer[c] += (int32_t) out_l;
        buffer[c + 1] += (int32_t) out_r;
    }

    pas16->opl.reset_buffer(pas16->opl.priv);
}

void
pasplus_filter_cd_audio(int channel, double *buffer, void *priv)
{
    const pas16_t *    pas16  = (const pas16_t *) priv;
    const nsc_mixer_t *mixer  = &pas16->nsc_mixer;
    const double       cd     = channel ? mixer->cd_r : mixer->cd_l;
    const double       master = channel ? mixer->master_r : mixer->master_l;
    const int32_t      bass   = mixer->bass;
    const int32_t      treble = mixer->treble;
    double             c      = (*buffer) * cd * master;
    double             bass_treble;

    /* This is not exactly how one does bass/treble controls, but the end result is like it.
       A better implementation would reduce the CPU usage. */
    if (bass != 6) {
        bass_treble = lmc1982_bass_treble_4bits[bass];

        if (bass > 6)
            c += (low_iir(2, channel, c) * bass_treble);
        else
            c = (c * bass_treble + low_cut_iir(2, channel, c) * (1.0 - bass_treble));
    }

    if (treble != 6) {
        bass_treble = lmc1982_bass_treble_4bits[treble];

        if (treble > 6)
            c += (high_iir(2, channel, c) * bass_treble);
        else
            c = (c * bass_treble + high_cut_iir(2, channel, c) * (1.0 - bass_treble));
    }

    *buffer = c;
}

void
pasplus_filter_pc_speaker(int channel, double *buffer, void *priv)
{
    const pas16_t *    pas16  =  (pas16_t *) priv;
    const nsc_mixer_t *mixer  = &pas16->nsc_mixer;
    const double       spk    = channel ? mixer->speaker_r : mixer->speaker_l;
    const double       master = channel ? mixer->master_r : mixer->master_l;
    const int32_t      bass   = mixer->bass;
    const int32_t      treble = mixer->treble;
    double             c      = (*buffer) * spk * master;
    double             bass_treble;

    /* This is not exactly how one does bass/treble controls, but the end result is like it.
       A better implementation would reduce the CPU usage. */
    if (bass != 6) {
        bass_treble = lmc1982_bass_treble_4bits[bass];

        if (bass > 6)
            c += (low_iir(3, channel, c) * bass_treble);
        else
            c = (c * bass_treble + low_cut_iir(3, channel, c) * (1.0 - bass_treble));
    }

    if (treble != 6) {
        bass_treble = lmc1982_bass_treble_4bits[treble];

        if (treble > 6)
            c += (high_iir(3, channel, c) * bass_treble);
        else
            c = (c * bass_treble + high_cut_iir(3, channel, c) * (1.0 - bass_treble));
    }

    *buffer = c;
}

void
pas16_get_buffer(int32_t *buffer, int len, void *priv)
{
    pas16_t *            pas16 =  (pas16_t *) priv;
    const mv508_mixer_t *mixer   = &pas16->mv508_mixer;
    double               bass_treble;

    sb_dsp_update(&pas16->dsp);
    pas16_update(pas16);
    for (int c = 0; c < len * 2; c += 2) {
        double out_l = (pas16->dsp.buffer[c] * mixer->sb_l) / 3.0;
        double out_r = (pas16->dsp.buffer[c + 1] * mixer->sb_r) / 3.0;

        if (pas16->filter) {
            /* We divide by 3 to get the volume down to normal. */
            out_l += (low_fir_pas16(0, (double) pas16->pcm_buffer[0][c >> 1]) * mixer->pcm_l) / 3.0;
            out_r += (low_fir_pas16(1, (double) pas16->pcm_buffer[1][c >> 1]) * mixer->pcm_r) / 3.0;
        } else {
            out_l += (((double) pas16->pcm_buffer[0][c >> 1]) * mixer->pcm_l) / 3.0;
            out_r += (((double) pas16->pcm_buffer[1][c >> 1]) * mixer->pcm_r) / 3.0;
        }

        out_l *= mixer->master_l;
        out_r *= mixer->master_r;

        /* This is not exactly how one does bass/treble controls, but the end result is like it.
           A better implementation would reduce the CPU usage. */
        if (mixer->bass != 6) {
            bass_treble = lmc1982_bass_treble_4bits[mixer->bass];

            if (mixer->bass > 6) {
                out_l += (low_iir(0, 0, out_l) * bass_treble);
                out_r += (low_iir(0, 1, out_r) * bass_treble);
            } else if (mixer->bass < 6) {
                out_l = (out_l *bass_treble + low_cut_iir(0, 0, out_l) * (1.0 - bass_treble));
                out_r = (out_r *bass_treble + low_cut_iir(0, 1, out_r) * (1.0 - bass_treble));
            }
        }

        if (mixer->treble != 6) {
            bass_treble = lmc1982_bass_treble_4bits[mixer->treble];

            if (mixer->treble > 6) {
                out_l += (high_iir(0, 0, out_l) * bass_treble);
                out_r += (high_iir(0, 1, out_r) * bass_treble);
            } else if (mixer->treble < 6) {
                out_l = (out_l *bass_treble + high_cut_iir(0, 0, out_l) * (1.0 - bass_treble));
                out_r = (out_r *bass_treble + high_cut_iir(0, 1, out_r) * (1.0 - bass_treble));
            }
        }

        buffer[c] += (int32_t) out_l;
        buffer[c + 1] += (int32_t) out_r;
    }

    pas16->pos = 0;
    pas16->dsp.pos = 0;
}

void
pas16_get_music_buffer(int32_t *buffer, int len, void *priv)
{
    const pas16_t *      pas16   = (const pas16_t *) priv;
    const mv508_mixer_t *mixer   = &pas16->mv508_mixer;
    const int32_t *      opl_buf = pas16->opl.update(pas16->opl.priv);
    double               bass_treble;

    for (int c = 0; c < len * 2; c += 2) {
        double out_l = (((double) opl_buf[c]) * mixer->fm_l) * 0.7171630859375;
        double out_r = (((double) opl_buf[c + 1]) * mixer->fm_r) * 0.7171630859375;

        /* TODO: recording CD, Mic with AGC or line in. Note: mic volume does not affect recording. */
        out_l *= mixer->master_l;
        out_r *= mixer->master_r;

        /* This is not exactly how one does bass/treble controls, but the end result is like it.
           A better implementation would reduce the CPU usage. */
        if (mixer->bass != 6) {
            bass_treble = lmc1982_bass_treble_4bits[mixer->bass];

            if (mixer->bass > 6) {
                out_l += (low_iir(1, 0, out_l) * bass_treble);
                out_r += (low_iir(1, 1, out_r) * bass_treble);
            } else if (mixer->bass < 6) {
                out_l = (out_l *bass_treble + low_cut_iir(1, 0, out_l) * (1.0 - bass_treble));
                out_r = (out_r *bass_treble + low_cut_iir(1, 1, out_r) * (1.0 - bass_treble));
            }
        }

        if (mixer->treble != 6) {
            bass_treble = lmc1982_bass_treble_4bits[mixer->treble];

            if (mixer->treble > 6) {
                out_l += (high_iir(1, 0, out_l) * bass_treble);
                out_r += (high_iir(1, 1, out_r) * bass_treble);
            } else if (mixer->treble < 6) {
                out_l = (out_l *bass_treble + high_cut_iir(1, 0, out_l) * (1.0 - bass_treble));
                out_r = (out_r *bass_treble + high_cut_iir(1, 1, out_r) * (1.0 - bass_treble));
            }
        }

        buffer[c] += (int32_t) out_l;
        buffer[c + 1] += (int32_t) out_r;
    }

    pas16->opl.reset_buffer(pas16->opl.priv);
}

void
pas16_filter_cd_audio(int channel, double *buffer, void *priv)
{
    const pas16_t *      pas16  = (const pas16_t *) priv;
    const mv508_mixer_t *mixer  = &pas16->mv508_mixer;
    const double         cd     = channel ? mixer->cd_r : mixer->cd_l;
    const double         master = channel ? mixer->master_r : mixer->master_l;
    const int32_t        bass   = mixer->bass;
    const int32_t        treble = mixer->treble;
    double               c      = (((*buffer) * cd) / 3.0) * master;
    double               bass_treble;

    /* This is not exactly how one does bass/treble controls, but the end result is like it.
       A better implementation would reduce the CPU usage. */
    if (bass != 6) {
        bass_treble = lmc1982_bass_treble_4bits[bass];

        if (bass > 6)
            c += (low_iir(2, channel, c) * bass_treble);
        else
            c = (c * bass_treble + low_cut_iir(2, channel, c) * (1.0 - bass_treble));
    }

    if (treble != 6) {
        bass_treble = lmc1982_bass_treble_4bits[treble];

        if (treble > 6)
            c += (high_iir(2, channel, c) * bass_treble);
        else
            c = (c * bass_treble + high_cut_iir(2, channel, c) * (1.0 - bass_treble));
    }

    *buffer = c;
}

void
pas16_filter_pc_speaker(int channel, double *buffer, void *priv)
{
    const pas16_t *      pas16  = (const pas16_t *) priv;
    const mv508_mixer_t *mixer  = &pas16->mv508_mixer;
    const double         spk    = channel ? mixer->speaker_r : mixer->speaker_l;
    const double         master = channel ? mixer->master_r : mixer->master_l;
    const int32_t        bass   = mixer->bass;
    const int32_t        treble = mixer->treble;
    double               c      = (((*buffer) * spk) / 3.0) * master;
    double               bass_treble;

    /* This is not exactly how one does bass/treble controls, but the end result is like it.
       A better implementation would reduce the CPU usage. */
    if (bass != 6) {
        bass_treble = lmc1982_bass_treble_4bits[bass];

        if (bass > 6)
            c += (low_iir(3, channel, c) * bass_treble);
        else
            c = (c * bass_treble + low_cut_iir(3, channel, c) * (1.0 - bass_treble));
    }

    if (treble != 6) {
        bass_treble = lmc1982_bass_treble_4bits[treble];

        if (treble > 6)
            c += (high_iir(3, channel, c) * bass_treble);
        else
            c = (c * bass_treble + high_cut_iir(3, channel, c) * (1.0 - bass_treble));
    }

    *buffer = c;
}

static void
pas16_speed_changed(void *priv)
{
    pas16_change_pit_clock_speed(priv);
}

static void *
pas16_init(const device_t *info)
{
    pas16_t *pas16 = calloc(1, sizeof(pas16_t));

    if (pas16_next > 3) {
        fatal("Attempting to add a Pro Audio Spectrum instance beyond the maximum amount\n");

        free(pas16);
        return NULL;
    }

    pas16->type = info->local & 0xff;
    pas16->has_scsi = (!pas16->type) || (pas16->type == 0x0f);
    fm_driver_get(FM_YMF262, &pas16->opl);
    sb_dsp_set_real_opl(&pas16->dsp, 1);
    sb_dsp_init(&pas16->dsp, SB2, SB_SUBTYPE_DEFAULT, pas16);
    pas16->mpu = (mpu_t *) malloc(sizeof(mpu_t));
    memset(pas16->mpu, 0, sizeof(mpu_t));
    mpu401_init(pas16->mpu, 0, 0, M_UART, device_get_config_int("receive_input401"));
    sb_dsp_set_mpu(&pas16->dsp, pas16->mpu);

    pas16->sb_compat_base = 0x0000;

    io_sethandler(0x9a01, 0x0001, NULL, NULL, NULL, pas16_out_base, NULL, NULL, pas16);
    pas16->this_id = 0xbc + pas16_next;

    if (pas16->has_scsi) {
        pas16->scsi = device_add(&scsi_pas_device);
        timer_add(&pas16->scsi->timer, pas16_scsi_callback, pas16, 0);
        timer_add(&pas16->scsi_timer, pas16_timeout_callback, pas16, 0);
        other_scsi_present++;
    }

    pas16->pit = device_add(&i8254_ext_io_fast_device);
    pas16_reset(pas16);
    pas16->pit->dev_priv = pas16;
    pas16->irq = pas16->type ? 10 : 5;
    pas16->io_conf_3 = pas16->type ? 0x07 : 0x04;
    if (pas16->has_scsi) {
        pas16->scsi_irq = pas16->type ? 11 : 7;
        pas16->io_conf_3 |= (pas16->type ? 0x80 : 0x60);
        ncr5380_set_irq(&pas16->scsi->ncr, pas16->scsi_irq);
    }
    pas16->dma = 3;
    for (uint8_t i = 0; i < 3; i++)
        pitf_ctr_set_gate(pas16->pit, i, 0);

    pitf_ctr_set_out_func(pas16->pit, 0, pas16_pit_timer0);
    pitf_ctr_set_out_func(pas16->pit, 1, pas16_pit_timer1);
    pitf_ctr_set_using_timer(pas16->pit, 0, 1);
    pitf_ctr_set_using_timer(pas16->pit, 1, 0);
    pitf_ctr_set_using_timer(pas16->pit, 2, 0);

    if (pas16->type) {
        sound_add_handler(pas16_get_buffer, pas16);
        music_add_handler(pas16_get_music_buffer, pas16);
        sound_set_cd_audio_filter(pas16_filter_cd_audio, pas16);
        if (device_get_config_int("control_pc_speaker"))
            sound_set_pc_speaker_filter(pas16_filter_pc_speaker, pas16);
    } else {
        sound_add_handler(pasplus_get_buffer, pas16);
        music_add_handler(pasplus_get_music_buffer, pas16);
        sound_set_cd_audio_filter(pasplus_filter_cd_audio, pas16);
        if (device_get_config_int("control_pc_speaker"))
            sound_set_pc_speaker_filter(pasplus_filter_pc_speaker, pas16);
    }

    if (device_get_config_int("receive_input"))
        midi_in_handler(1, pas16_input_msg, pas16_input_sysex, pas16);

    for (uint8_t i = 0; i < 16; i++) {
        if (i < 6)
            lmc1982_bass_treble_4bits[i] = pow(10.0, (-((double) (12 - (i << 1))) / 10.0));
        else if (i == 6)
            lmc1982_bass_treble_4bits[i] = 0.0;
        else if ((i > 6) && (i <= 12))
            lmc1982_bass_treble_4bits[i] = 1.0 - pow(10.0, ((double) ((i - 6) << 1) / 10.0));
        else
            lmc1982_bass_treble_4bits[i] = 1.0 - pow(10.0, 1.2);
    }

    pas16_next++;

    return pas16;
}

static void
pas16_close(void *priv)
{
    pas16_t *pas16 = (pas16_t *) priv;

    free(pas16);

    pas16_next = 0;
}

static const device_config_t pas16_config[] = {
    {
        .name = "control_pc_speaker",
        .description = "Control PC speaker",
        .type = CONFIG_BINARY,
        .default_string = "",
        .default_int = 0
    },
    {
        .name = "receive_input",
        .description = "Receive MIDI input",
        .type = CONFIG_BINARY,
        .default_string = "",
        .default_int = 1
    },
    {
        .name = "receive_input401",
        .description = "Receive MIDI input (MPU-401)",
        .type = CONFIG_BINARY,
        .default_string = "",
        .default_int = 0
    },
    { .name = "", .description = "", .type = CONFIG_END }
};

const device_t pasplus_device = {
    .name          = "Pro Audio Spectrum Plus",
    .internal_name = "pasplus",
    .flags         = DEVICE_ISA,
    .local         = 0,
    .init          = pas16_init,
    .close         = pas16_close,
    .reset         = pas16_reset,
    { .available = NULL },
    .speed_changed = pas16_speed_changed,
    .force_redraw  = NULL,
    .config        = pas16_config
};

const device_t pas16_device = {
    .name          = "Pro Audio Spectrum 16",
    .internal_name = "pas16",
    .flags         = DEVICE_ISA | DEVICE_AT,
    .local         = 0x0f,
    .init          = pas16_init,
    .close         = pas16_close,
    .reset         = pas16_reset,
    { .available = NULL },
    .speed_changed = pas16_speed_changed,
    .force_redraw  = NULL,
    .config        = pas16_config
};

const device_t pas16d_device = {
    .name          = "Pro Audio Spectrum 16D",
    .internal_name = "pas16d",
    .flags         = DEVICE_ISA | DEVICE_AT,
    .local         = 0x0c,
    .init          = pas16_init,
    .close         = pas16_close,
    .reset         = pas16_reset,
    { .available = NULL },
    .speed_changed = pas16_speed_changed,
    .force_redraw  = NULL,
    .config        = pas16_config
};
