/*
* UAE - The Un*x Amiga Emulator
*
* Toccata Z2
* Prelude and Prelude A1200
*
* Copyright 2014-2015 Toni Wilen
*
*/

#include "sysconfig.h"
#include "sysdeps.h"

#include "options.h"
#include "memory.h"
#include "newcpu.h"
#include "debug.h"
#include "sndboard.h"
#include "audio.h"
#include "sounddep/sound.h"
#include "autoconf.h"
#include "pci_hw.h"
#include "uaesnd_capture_fifo.h"
#include "qemuvga/qemuaudio.h"
#include "rommgr.h"
#include "devices.h"

#if defined(AMIBERRY)
#include <SDL3/SDL.h>
#endif

#define DEBUG_SNDDEV 0
#define DEBUG_SNDDEV_READ 0
#define DEBUG_SNDDEV_WRITE 0
#define DEBUG_SNDDEV_FIFO 0

static void snd_init(void);
static void sndboard_rethink(void);
static uae_u8 *sndboard_get_buffer(int *frames);
static void sndboard_release_buffer(uae_u8 *buffer, int frames);
static void sndboard_free_capture(int owner);
static bool sndboard_init_capture(int freq, int owner);
static void uaesndboard_reset(int hardreset);
static void sndboard_reset(int hardreset);

static float base_event_clock;

extern addrbank uaesndboard_bank_z2, uaesndboard_bank_z3;

#define MAX_DUPLICATE_SOUND_BOARDS 1
#define MAX_SNDDEVS 3

#define MAX_UAE_CHANNELS 8
#define MAX_UAE_STREAMS 8
#define UAESND_CAP_24_32BIT 1
#define UAESND_CAP_MONO_HPAN 2
#define UAESND_CAP_DIAGNOSTICS 4
#define UAESND_CAP_CAPTURE 8
#define UAESND_CAP_CAPTURE_BLOCK 16
#define UAESND_DIAG_VERSION 1
#define UAESND_ERR_NONE 0
#define UAESND_ERR_INVALID_SET 1
#define UAESND_ERR_STREAM_ALLOC 2
#define UAESND_ERR_CAPTURE_BLOCK_ADDRESS 3
#define SNDBOARD_CAPTURE_OWNER_NONE 0
#define SNDBOARD_CAPTURE_OWNER_TOCCATA 1
#define SNDBOARD_CAPTURE_OWNER_UAESND 2
#define UAESND_CAPTURE_REG_DATA 0xc0
#define UAESND_CAPTURE_REG_OVERRUNS 0xc4
#define UAESND_CAPTURE_REG_INTREQ 0xc8
#define UAESND_CAPTURE_REG_THRESHOLD 0xcc
#define UAESND_CAPTURE_REG_CONTROL 0xd0
#define UAESND_CAPTURE_REG_STATUS 0xd4
#define UAESND_CAPTURE_REG_FREQUENCY 0xd8
#define UAESND_CAPTURE_REG_AVAILABLE 0xdc
#define UAESND_CAPTURE_CONTROL_ENABLE 1
#define UAESND_CAPTURE_CONTROL_IRQ_ENABLE 2
#define UAESND_CAPTURE_BLOCK_REG_ADDRESS 0x900
#define UAESND_CAPTURE_BLOCK_REG_FRAMES 0x904
#define UAESND_CAPTURE_BLOCK_REG_DONE 0x908
#define UAESND_CAPTURE_BLOCK_REG_COMMAND 0x90c
#define UAESND_CAPTURE_BLOCK_COMMAND_COPY 1

struct uaesndboard_stream
{
	int streamid;
	int play;
	int ch;
	int bitmode;
	int volume;
	uae_u32 flags;
	uae_u16 format;
	uae_u32 freq;
	int event_time;
	uaecptr baseaddr;
	uaecptr setaddr;
	uaecptr address, original_address;
	uaecptr next;
	uaecptr repeat;
	uaecptr indirect_ptr;
	uaecptr indirect_address;
	int indirect_len;
	int lastlen;
	int len, original_len;
	int replen;
	int repcnt;
	int first;
	bool repeating;
	uae_u8 chmode;
	uae_s16 panx, pany;
	uae_u8 intenamask;
	uae_u8 intreqmask;
	uae_u8 masterintenamask;
	int framesize;
	uae_u8 io[256];
	uae_u16 wordlatch;
	int timer_cnt;
	int timer_event_time;
	int sample[MAX_UAE_CHANNELS];
};

struct uaesndboard_data
{
	bool enabled;
	bool z3;
	int configured;
	uae_u32 streammask;
	uae_u32 streamallocmask;
	uae_u32 streamintenamask;
	uae_u32 streamintreqmask;
	uae_u8 acmemory[128];
	int streamcnt;
	int volume[MAX_UAE_CHANNELS];
	struct uaesndboard_stream stream[MAX_UAE_STREAMS];
	uae_u8 info[256];
	uae_u32 invalid_set_count;
	uae_u32 stream_alloc_failure_count;
	uae_u32 stream_start_count;
	uae_u32 stream_stop_count;
	uae_u32 stream_irq_count;
	uae_u32 timer_irq_count;
	uae_u32 last_error_code;
	struct uaesnd_capture_state capture;
};

static struct uaesndboard_data uaesndboard[MAX_DUPLICATE_SOUND_BOARDS];
static int capture_owner = SNDBOARD_CAPTURE_OWNER_NONE;

static void uaesnd_update_info(struct uaesndboard_data *data)
{
	put_long_host(data->info + 24, UAESND_CAP_24_32BIT | UAESND_CAP_MONO_HPAN | UAESND_CAP_DIAGNOSTICS | UAESND_CAP_CAPTURE | UAESND_CAP_CAPTURE_BLOCK);
	put_long_host(data->info + 28, UAESND_DIAG_VERSION);
	put_long_host(data->info + 32, data->invalid_set_count);
	put_long_host(data->info + 36, data->stream_alloc_failure_count);
	put_long_host(data->info + 40, data->stream_start_count);
	put_long_host(data->info + 44, data->stream_stop_count);
	put_long_host(data->info + 48, data->stream_irq_count);
	put_long_host(data->info + 52, data->timer_irq_count);
	put_long_host(data->info + 56, data->last_error_code);
	put_long_host(data->info + 60, data->capture.frame_count);
	put_long_host(data->info + 64, data->capture.dropped_byte_count);
}

static void uaesnd_set_error(struct uaesndboard_data *data, uae_u32 error_code)
{
	data->last_error_code = error_code;
}

static bool uaesnd_capture_addr(uaecptr addr)
{
	addr &= 65535;
	return addr >= UAESND_CAPTURE_REG_DATA && addr < 0xe0;
}

static bool uaesnd_capture_block_addr(uaecptr addr)
{
	addr &= 65535;
	return addr >= UAESND_CAPTURE_BLOCK_REG_ADDRESS && addr < UAESND_CAPTURE_BLOCK_REG_COMMAND + 4;
}

static void uaesnd_capture_start(struct uaesndboard_data *data)
{
	if (data->capture.status & UAESND_CAPTURE_STATUS_ACTIVE)
		return;
	if (data->capture.frequency == 0)
		data->capture.frequency = currprefs.sound_freq;
	data->capture.buffer_size = data->capture.frequency * 2 * 2;
	if (data->capture.buffer_size < 4096)
		data->capture.buffer_size = 4096;
	data->capture.buffer = xcalloc(uae_u8, data->capture.buffer_size);
	uaesnd_capture_fifo_reset(&data->capture);
	data->capture.control |= UAESND_CAPTURE_CONTROL_ENABLE;
	if (!sndboard_init_capture(data->capture.frequency, SNDBOARD_CAPTURE_OWNER_UAESND)) {
		xfree(data->capture.buffer);
		data->capture.buffer = NULL;
		data->capture.buffer_size = 0;
		data->capture.status = UAESND_CAPTURE_STATUS_UNAVAILABLE;
		return;
	}
	data->capture.status = UAESND_CAPTURE_STATUS_ACTIVE;
}

static void uaesnd_capture_stop(struct uaesndboard_data *data)
{
	if (data->capture.status & UAESND_CAPTURE_STATUS_ACTIVE)
		sndboard_free_capture(SNDBOARD_CAPTURE_OWNER_UAESND);
	xfree(data->capture.buffer);
	data->capture.buffer = NULL;
	data->capture.buffer_size = 0;
	data->capture.control &= ~UAESND_CAPTURE_CONTROL_ENABLE;
	data->capture.status = 0;
	data->capture.intreq = 0;
	uaesnd_capture_fifo_reset(&data->capture);
}

static void uaesnd_capture_process(struct uaesndboard_data *data)
{
	if (!(data->capture.status & UAESND_CAPTURE_STATUS_ACTIVE))
		return;
	int frames = 0;
	uae_u8 *buffer = sndboard_get_buffer(&frames);
	if (buffer && frames > 0) {
		data->capture.frame_count += frames;
		int samples = frames * 2;
		for (int i = 0; i < samples; i++) {
			int sample = (uae_s16)(buffer[i * 2 + 0] | (buffer[i * 2 + 1] << 8));
			uaesnd_capture_fifo_write_s16be(&data->capture, sample);
		}
		if ((data->capture.control & UAESND_CAPTURE_CONTROL_IRQ_ENABLE) && data->capture.threshold > 0 && data->capture.available >= data->capture.threshold)
			data->capture.intreq = 1;
	}
	sndboard_release_buffer(buffer, frames);
}

static bool uaesnd_capture_rethink(struct uaesndboard_data *data)
{
	uaesnd_capture_process(data);
	return (data->capture.control & UAESND_CAPTURE_CONTROL_IRQ_ENABLE) && data->capture.intreq != 0;
}

static void uaesnd_capture_refresh_if_empty(struct uaesndboard_data *data)
{
	struct uaesnd_capture_state *capture = &data->capture;
	if (!capture->buffer || capture->read_index == capture->write_index)
		uaesnd_capture_process(data);
}

static uae_u8 uaesnd_capture_read_byte(struct uaesndboard_data *data)
{
	struct uaesnd_capture_state *capture = &data->capture;
	uaesnd_capture_refresh_if_empty(data);
	uae_u8 value = 0;
	if (!uaesnd_capture_fifo_read_byte(capture, &value))
		return 0;
	return value;
}

struct uaesnd_capture_guest_writer
{
	uaecptr address;
};

static void uaesnd_capture_block_write_guest(void *opaque, uae_u32 offset, uae_u8 value)
{
	struct uaesnd_capture_guest_writer *writer = (struct uaesnd_capture_guest_writer *)opaque;
	put_byte(writer->address + offset, value);
}

static void uaesnd_capture_block_copy(struct uaesndboard_data *data)
{
	uaesnd_capture_process(data);
	data->capture.block_done = 0;
	uae_u32 frames = data->capture.block_frames;
	uae_u32 available_frames = data->capture.available / 4;
	if (frames > available_frames)
		frames = available_frames;
	uae_u32 bytes = frames * 4;
	if (bytes == 0)
		return;
	if (!valid_address(data->capture.block_address, bytes)) {
		uaesnd_set_error(data, UAESND_ERR_CAPTURE_BLOCK_ADDRESS);
		return;
	}
	struct uaesnd_capture_guest_writer writer = { data->capture.block_address };
	data->capture.block_done = uaesnd_capture_fifo_copy_block(&data->capture, frames, &writer, uaesnd_capture_block_write_guest);
}

static void uaesnd_capture_fill_regs(struct uaesndboard_data *data, uae_u8 *regs)
{
	uaesnd_capture_process(data);
	put_long_host(regs + (UAESND_CAPTURE_REG_OVERRUNS - UAESND_CAPTURE_REG_DATA), data->capture.overrun_count);
	put_long_host(regs + (UAESND_CAPTURE_REG_INTREQ - UAESND_CAPTURE_REG_DATA), data->capture.intreq);
	put_long_host(regs + (UAESND_CAPTURE_REG_THRESHOLD - UAESND_CAPTURE_REG_DATA), data->capture.threshold);
	put_long_host(regs + (UAESND_CAPTURE_REG_CONTROL - UAESND_CAPTURE_REG_DATA), data->capture.control);
	put_long_host(regs + (UAESND_CAPTURE_REG_STATUS - UAESND_CAPTURE_REG_DATA), data->capture.status);
	put_long_host(regs + (UAESND_CAPTURE_REG_FREQUENCY - UAESND_CAPTURE_REG_DATA), data->capture.frequency);
	put_long_host(regs + (UAESND_CAPTURE_REG_AVAILABLE - UAESND_CAPTURE_REG_DATA), data->capture.available);
}

static uae_u8 uaesnd_capture_bget(struct uaesndboard_data *data, uaecptr addr)
{
	if ((addr & ~3) == UAESND_CAPTURE_REG_DATA)
		return uaesnd_capture_read_byte(data);
	uae_u8 regs[32] = {};
	uaesnd_capture_fill_regs(data, regs);
	return regs[(addr - UAESND_CAPTURE_REG_DATA) & 31];
}

static uae_u16 uaesnd_capture_wget(struct uaesndboard_data *data, uaecptr addr)
{
	return (uaesnd_capture_bget(data, addr) << 8) | uaesnd_capture_bget(data, addr + 1);
}

static uae_u32 uaesnd_capture_lget(struct uaesndboard_data *data, uaecptr addr)
{
	return (uaesnd_capture_wget(data, addr) << 16) | uaesnd_capture_wget(data, addr + 2);
}

static void uaesnd_capture_apply_reg(struct uaesndboard_data *data, int reg, uae_u32 value)
{
	if (reg == UAESND_CAPTURE_REG_CONTROL) {
		data->capture.control = value;
		if (value & UAESND_CAPTURE_CONTROL_ENABLE)
			uaesnd_capture_start(data);
		else
			uaesnd_capture_stop(data);
	} else if (reg == UAESND_CAPTURE_REG_FREQUENCY) {
		data->capture.frequency = value;
	} else if (reg == UAESND_CAPTURE_REG_INTREQ) {
		uaesnd_capture_fifo_ack_intreq(&data->capture, value);
	} else if (reg == UAESND_CAPTURE_REG_THRESHOLD) {
		data->capture.threshold = value;
	} else if (reg == UAESND_CAPTURE_REG_STATUS) {
		uaesnd_capture_fifo_clear_status(&data->capture, value);
	}
}

static void uaesnd_capture_put(struct uaesndboard_data *data, uaecptr addr, uae_u32 value, int size)
{
	uae_u8 regs[32] = {};
	int offset = (addr - UAESND_CAPTURE_REG_DATA) & 31;
	int reg = (addr & ~3) & 65535;
	uaesnd_capture_fill_regs(data, regs);
	if (size == 4) {
		put_long_host(regs + offset, value);
	} else if (size == 2) {
		put_word_host(regs + offset, value);
	} else {
		regs[offset] = value;
	}
	uaesnd_capture_apply_reg(data, reg, get_long_host(regs + (reg - UAESND_CAPTURE_REG_DATA)));
}

static void uaesnd_capture_block_fill_regs(struct uaesndboard_data *data, uae_u8 *regs)
{
	put_long_host(regs + (UAESND_CAPTURE_BLOCK_REG_ADDRESS - UAESND_CAPTURE_BLOCK_REG_ADDRESS), data->capture.block_address);
	put_long_host(regs + (UAESND_CAPTURE_BLOCK_REG_FRAMES - UAESND_CAPTURE_BLOCK_REG_ADDRESS), data->capture.block_frames);
	put_long_host(regs + (UAESND_CAPTURE_BLOCK_REG_DONE - UAESND_CAPTURE_BLOCK_REG_ADDRESS), data->capture.block_done);
	put_long_host(regs + (UAESND_CAPTURE_BLOCK_REG_COMMAND - UAESND_CAPTURE_BLOCK_REG_ADDRESS), 0);
}

static uae_u8 uaesnd_capture_block_bget(struct uaesndboard_data *data, uaecptr addr)
{
	uae_u8 regs[16] = {};
	uaesnd_capture_block_fill_regs(data, regs);
	return regs[(addr - UAESND_CAPTURE_BLOCK_REG_ADDRESS) & 15];
}

static uae_u16 uaesnd_capture_block_wget(struct uaesndboard_data *data, uaecptr addr)
{
	return (uaesnd_capture_block_bget(data, addr) << 8) | uaesnd_capture_block_bget(data, addr + 1);
}

static uae_u32 uaesnd_capture_block_lget(struct uaesndboard_data *data, uaecptr addr)
{
	return (uaesnd_capture_block_wget(data, addr) << 16) | uaesnd_capture_block_wget(data, addr + 2);
}

static void uaesnd_capture_block_apply_reg(struct uaesndboard_data *data, int reg, uae_u32 value)
{
	if (reg == UAESND_CAPTURE_BLOCK_REG_ADDRESS) {
		data->capture.block_address = value;
	} else if (reg == UAESND_CAPTURE_BLOCK_REG_FRAMES) {
		data->capture.block_frames = value;
	} else if (reg == UAESND_CAPTURE_BLOCK_REG_DONE) {
		data->capture.block_done = value;
	} else if (reg == UAESND_CAPTURE_BLOCK_REG_COMMAND && (value & UAESND_CAPTURE_BLOCK_COMMAND_COPY)) {
		uaesnd_capture_block_copy(data);
	}
}

static void uaesnd_capture_block_put(struct uaesndboard_data *data, uaecptr addr, uae_u32 value, int size)
{
	uae_u8 regs[16] = {};
	int offset = (addr - UAESND_CAPTURE_BLOCK_REG_ADDRESS) & 15;
	int reg = (addr & ~3) & 65535;
	uaesnd_capture_block_fill_regs(data, regs);
	if (size == 4) {
		put_long_host(regs + offset, value);
	} else if (size == 2) {
		put_word_host(regs + offset, value);
	} else {
		regs[offset] = value;
	}
	uaesnd_capture_block_apply_reg(data, reg, get_long_host(regs + (reg - UAESND_CAPTURE_BLOCK_REG_ADDRESS)));
}

/*
	autoconfig data:

	manufacturer 6502
	product 2
	Z2 64k board or Z3 16M board, no boot rom. May have optional boot rom in the future.

	Z2 board has 32k of RAM (0x8000-0xffff). Z3 board has 8M of RAM. (Upper 8M of board)
	It can be used as 1:1 physical/logical mapped sample set or sample data storage. 

	uaesnd sample set structure, can be located anywhere in Amiga address space.
	It is never modified by UAE. Must be long aligned.

	 0.W flags. Must be zero.
	 2.W format. Must be zero. (non-zero values reserved for compressed/non-PCM formats. If needed in the future..)

	 4.L sample address pointer
	 8.L sample length (in frames, negative=play backwards, set address after last sample.). 0 = repeat previous sample until length is updated.

	If sample length equals 0x80000000, sample address pointer becomes pointer to array that has one or more sample pointer and sample length pairs.
	All sample pointers will played sequentially, NULL pointer ends the chain.
	(sample ptr1, sample len1, ptr2, len2, ptr3, len3, NULL) Can be useful if using non-1:1 physical to logical address mapping.

	12.L playback frequency in Hz * 256 (bits 8-27: integer part, bits 0 to 7 fractional part). In Paula periods if high word is FFFF (0xFFFFxxxx).
	16.L repeat sample address pointer (Ignored if repeat address=0 and length=0)
	20.L repeat sample length (negative=play backwards, 0 = repeat previous sample until length is updated.)
	24.W repeat count (0=no repeat, -1=repeat forever)
	26.W volume (0 to 32768)
	28.L next sample set address (0=end, this was last set)
	32.B number of channels. (interleaved samples if 2 or more channels)
	33.B sample type (bit 0: bits per sample, 0=8,1=16,2=24,3=32 bit 6=signed, bit 7=little-endian)
	34.B bit 0: interrupt when set starts, bit 1: interrupt when set ends (last sample played), bit 2: interrupt when repeat starts, bit 3: after each sample.
	35.B if mono stream, bit mask that selects output channels. (0=default, redirect to left and right channels)
	(Can be used for example when playing tracker modules by using 4 single channel streams)
	stereo or higher: channel mode.
	36.W horizontal panning (-32767 to 32767). Implemented for mono streams.
	38.W front to back panning (-32767 to 32767). Reserved. Must be zero.
	40.L original address (RO)
	44.L original length (RO)

	Channel mode = 0

	2: left, right
	4: left, right, left back, right back
	6: left, right, center, lfe, left back, right back, left surround
	8: left, right, center, lfe, left back, right back, right surround

	Channel mode = 1 (alternate channel order)

	2: left, right (no change)
	4: left, right, center, back
	6: left, right, left back, right back, center, lfe
	8: left, right, left back, right back, left surround, right surround, center, lfe

	Hardware addresses relative to uaesnd autoconfig board start:

	Read-only hardware information:

	$0080.L uae version (ver.rev.subrev)
	$0084.W uae sndboard "hardware" version
	$0086.W uae sndboard "hardware" revision
	$0088.L preferred frequency (=user configured mixing/resampling rate)
	$008C.B max number of channels/stream (>=6: 5.1 fully supported, 7.1 also supported but last 2 surround channels are currently muted)
	$008D.B active number of channels (mirrors selected UAE sound mode. inactive channels are muted but can be still be used normally.)
	$008E.B max number of simultaneous audio streams (currently 8)
	$0090.L offset to RAM from board base address (or zero if no RAM)
	$0094.L size of RAM (or zero if no RAM)
	$0098.L UAESND capability bit mask. 0=24/32-bit samples, 1=mono horizontal panning, 2=diagnostics, 3=capture, 4=capture block transfer.
	$009C.L diagnostics block version.
	$00A0.L invalid sample set count.
	$00A4.L host stream allocation failure count.
	$00A8.L stream start count.
	$00AC.L stream stop count.
	$00B0.L stream IRQ count.
	$00B4.L timer IRQ count.
	$00B8.L last error code. 0=none, 1=invalid sample set, 2=stream allocation failure, 3=invalid capture block address.
	$00BC.L captured frame count.
	$00C0.L capture data. Byte reads return signed 16-bit stereo samples in big-endian order.
	$00C4.L capture overrun count.
	$00C8.L capture interrupt request. Write zero to acknowledge.
	$00CC.L capture interrupt threshold in bytes.
	$00D0.L capture control. Bit 0=request capture enable, bit 1=enable capture interrupts.
	$00D4.L capture status. Bit 0=capture unavailable, bit 1=capture active, bit 2=capture overrun.
	$00D8.L capture frequency in Hz.
	$00DC.L capture bytes available.
	$0900.L capture block destination address.
	$0904.L capture block requested frames.
	$0908.L capture block completed frames.
	$090C.L capture block command. Write 1 to copy requested frames from capture FIFO.

	$00E0.L allocated streams, bit mask. Hardware level stream allocation feature, use single test and set/clear instruction or
			disable interrupts before allocating/freeing streams. If stream bit is not set, stream's address range is inactive.
			byte access to $00E3 is also allowed.

	$00E4.L Stream latch bit. Write-only. Bit set = latch stream, bit not set = does nothing. ($00E6.W and $00E7.B also allowed)
	$00E8.L Stream unlatch bit. Same behavior as $00E4.L. ($00EA.W and $00EB.B also allowed)

	$00F0.L stream enable bit mask. RW. Can be used to start or stop multiple streams simultaneously. $00F3.B is also allowed.
			Changing stream mask always clears stream's interrupt status register.
	$00F4.L stream master interrupt enable bit mask. RW.
	$00F8.L stream master interrupt request bit mask. RO.

	$0100 stream 1

	$0100-$013f: Current sample set structure. RW.
	$0140-$017f: Latched sample set structure. RW.
	$0180.L Current sample set pointer. RW.
	$0184.B Reserved
	$0185.B Reserved
	$0186.B Reserved
	$0187.B Interrupt status. 7: set when interrupt active. 0,1,2,3: same as 34.B bit, always set when condition matches,
			even if 34.B bit is not set. If also 34.B bit is set: bit 7 set and interrupt is activated.
			bit 4 = timer interrupt. Reading clears interrupt. RO.
	$0188.B Reserved
	$0189.B Reserved
	$018A.B Reserved
	$018B.B Alternate interrupt status. Same as $187.B but reading does not clear interrupt. RO.
	$018C.B Reserved
	$018D.B Reserved
	$018E.B Reserved
	$018F.B Stream master interrupt enable (same bits as $0187.B)
	$0190.B Reserved
	$0191.B Reserved
	$0192.B Reserved
	$0193.B Status (Read: bit 0 = play active, bit 1 = output stream allocated, bit 2 = repeating, Write: reload sample set if not playing or repeating)
	$0194.L Timer frequency (same format as 12.L), sets interrupt bit 4. Zero = disabled. WO.
	$0198.L
	$019C.L
	$01a0.L Sample set pointer, force load. WO.

	$0200 stream 2

	...

	$0800 stream 8

	Writing non-zero to sample set pointer ($180) starts audio if not already started and stream enable ($F0) bit is set.
	Writing zero stops the stream immediately. Also clears automatically when stream ends.

	Long wide registers have special feature when read using word wide reads (68000/10 CPU), when high word is read,
	low word is copied to internal register. Following low word read comes from register copy.
	This prevents situation where sound emulation can modify the register between high and low word reads.
	68020+ aligned long accesses are always guaranteed safe.

	Set structure is copied to emulator side internal address space when set starts.
	This data can be always read and written in real time by accessing $0100-$013f space.
	Writes are nearly immediate, values are updated during next sample period.
	(It probably is not a good idea to do on the fly change number of channels or bits per sample values..)

	Use hardware current sample set structure to detect current sample address, length and repeat count. 

	Repeat address pointer and length values are (re-)fetched from set structure each time when new repeat starts.

	Next sample set pointer is fetched when current set finishes.

	Reading interrupt status register will also clear interrupts.
	Interrupt is level 6 (EXTER).

	Sample set parameters are validated, any error causes audio to stop immediately and log message will be printed.

	During non-repeat playback sample address increases and sample length decreases. When length becomes zero,
	repeat count is checked, if it is non-zero, repeat address and repeat length are loaded from memory and start
	counting (address increases, length decreases). Note that sample address and length won't change anymore.
	when repeat counter becomes zero (or it was already zero), next sample set address is loaded and started.

	Hardware word and long accesses must be aligned (unaligned write is ignored, unaligned read will return 0xffff or 0xffffffff)

	Timer is usable when stream is allocated. Active audio play is not required.

*/

#define STREAM_STRUCT_SIZE 40

static bool audio_state_sndboard_uae(int streamid, void *params);

extern addrbank uaesndboard_ram_bank;
MEMORY_FUNCTIONS(uaesndboard_ram);
addrbank uaesndboard_ram_bank = {
	uaesndboard_ram_lget, uaesndboard_ram_wget, uaesndboard_ram_bget,
	uaesndboard_ram_lput, uaesndboard_ram_wput, uaesndboard_ram_bput,
	uaesndboard_ram_xlate, uaesndboard_ram_check, NULL, _T("*"), _T("UAESND memory"),
	uaesndboard_ram_lget, uaesndboard_ram_wget,
	ABFLAG_RAM | ABFLAG_THREADSAFE, 0, 0
};

static bool uaesnd_rethink(void)
{
	bool any_irq = false;
	for (int j = 0; j < MAX_DUPLICATE_SOUND_BOARDS; j++) {
		struct uaesndboard_data *data = &uaesndboard[j];
		if (data->enabled) {
			bool capture_irq = uaesnd_capture_rethink(data);
			bool stream_irq = false;
			for (int i = 0; i < MAX_UAE_STREAMS; i++) {
				if (data->streamintenamask & (1 << i)) {
					struct uaesndboard_stream *s = &uaesndboard[j].stream[i];
					if (s->intreqmask & 0x80) {
						data->streamintreqmask |= 1 << i;
						stream_irq = true;
						continue;
					}
				}
				data->streamintreqmask &= ~(1 << i);
			}
			if (capture_irq || stream_irq)
				any_irq = true;
		}
	}
	return any_irq;
}

static void uaesndboard_stop(struct uaesndboard_data *data, struct uaesndboard_stream *s)
{
	if (!s->play)
		return;

#if DEBUG_SNDDEV
	write_log("UAESND %d: STOP\n", s - data->stream);
#endif

	data->stream_stop_count++;
	s->play = 0;
	s->next = 0;
	data->streammask &= ~(1 << (s - data->stream));
	audio_enable_stream(false, s->streamid, 0, NULL, NULL);
	s->streamid = 0;
	data->streamcnt--;
}

static void uaesndboard_maybe_alloc_stream(struct uaesndboard_data *data, struct uaesndboard_stream *s)
{
	if (!s->play)
		return;
	if (s->streamid > 0)
		return;
	if (s->event_time == MAX_EV || s->ch == 0) {
		uaesndboard_stop(data, s);
		return;
	} else {
		data->streamcnt++;
		s->streamid = audio_enable_stream(true, -1, MAX_UAE_CHANNELS, audio_state_sndboard_uae, NULL);

#if DEBUG_SNDDEV
		write_log("UAESND %d: Stream allocated %d\n", s - data->stream, s->streamid);
#endif

		if (!s->streamid) {
			data->stream_alloc_failure_count++;
			uaesnd_set_error(data, UAESND_ERR_STREAM_ALLOC);
			uaesndboard_stop(data, s);
		}
	}
}

#define UAESND_MAX_FREQ 200000

static void uaesnd_setfreq(struct uaesndboard_data *data, struct uaesndboard_stream *s)
{
	if (s->freq == 0) {
		s->event_time = MAX_EV;
	} else if ((s->freq >> 16) == 0xffff) {
		s->event_time = s->freq & 65535;
	} else {
		if (s->freq < 1 * 256)
			s->freq = 1 * 256;
		if (s->freq > UAESND_MAX_FREQ * 256)
			s->freq = UAESND_MAX_FREQ * 256;
		s->event_time = (uae_s64)base_event_clock * CYCLE_UNIT * 256 / s->freq;
	}
	uaesndboard_maybe_alloc_stream(data, s);
}

static struct uaesndboard_stream *uaesnd_addr(uaecptr addr)
{
	if (addr < 0x100)
		return NULL;
	int stream = (addr - 0x100) / 0x100;
	if (stream >= MAX_UAE_STREAMS)
		return NULL;
	if (!(uaesndboard[0].streamallocmask & (1 << stream)))
		return NULL;
	return &uaesndboard[0].stream[stream];
}

static struct uaesndboard_stream *uaesnd_get(uaecptr addr)
{
	struct uaesndboard_stream *s = uaesnd_addr(addr);
	if (!s)
		return NULL;

	put_word_host(s->io +  0, s->flags);
	put_word_host(s->io +  2, s->format);
	put_long_host(s->io +  4, s->address);
	put_long_host(s->io +  8, s->len);
	put_long_host(s->io + 12, s->freq);
	put_long_host(s->io + 16, s->repeat);
	put_long_host(s->io + 20, s->replen);
	put_word_host(s->io + 24, s->repcnt);
	put_word_host(s->io + 26, s->volume);
	put_long_host(s->io + 28, s->next);
	put_byte_host(s->io + 32, s->ch);
	put_byte_host(s->io + 33, s->bitmode);
	put_byte_host(s->io + 34, s->intenamask);
	put_byte_host(s->io + 35, s->chmode);
	put_word_host(s->io + 36, s->panx);
	put_word_host(s->io + 38, s->pany);
	put_long_host(s->io + 0x80, s->setaddr);
	put_long_host(s->io + 0x84, s->intreqmask);
	put_long_host(s->io + 0x88, s->intreqmask);
	put_long_host(s->io + 0x8c, s->masterintenamask);

	return s;
}

static void uaesndboard_start(struct uaesndboard_data *data, struct uaesndboard_stream *s)
{
	if (s->play)
		return;

	if (!(data->streammask & (1 << (s - data->stream))))
		return;

#if DEBUG_SNDDEV
	write_log("UAESND %d: PLAY\n", s - data->stream);
#endif

	s->play = 1;
	data->stream_start_count++;
	for (int i = 0; i < MAX_UAE_CHANNELS; i++) {
		s->sample[i] = 0;
	}
	uaesnd_setfreq(data, s);
}

static bool get_indirect(struct uaesndboard_stream *s, uaecptr saddr)
{
	if (!valid_address(saddr, 8)) {
		write_log(_T("UAESND: invalid indirect pointer %08x\n"), saddr);
		return false;
	}
	s->indirect_ptr = saddr;
	s->indirect_address = get_long(saddr);
	s->indirect_len = get_long(saddr + 4);
	if (!s->indirect_address)
		return true;
	if (!valid_address(s->indirect_address, abs(s->indirect_len) * s->framesize)) {
		write_log(_T("UAESND: invalid indirect sample pointer range %08x - %08x\n"), s->indirect_address, s->indirect_address + s->indirect_len * s->framesize);
		return false;
	}
	return true;
}

static int uaesnd_sample_bytes(uae_u8 bitmode)
{
	switch (bitmode & 7)
	{
	case 3: return 4;
	case 2: return 3;
	case 1: return 2;
	default: return 1;
	}
}

static bool uaesnd_validate(struct uaesndboard_data *data, struct uaesndboard_stream *s)
{
	s->framesize = uaesnd_sample_bytes(s->bitmode) * s->ch;

	if (s->flags != 0) {
		write_log(_T("UAESND: Flags must be zero (%08x)\n"), s->flags);
		return false;
	}
	if (s->format != 0) {
		write_log(_T("UAESND: Only format=0 supported (%04x)\n"), s->format);
		return false;
	}
	if (s->ch < 0 || s->ch > MAX_UAE_CHANNELS || s->ch == 3 || s->ch == 5 || s->ch == 7) {
		write_log(_T("UAESND: unsupported number of channels %d\n"), s->ch);
		return false;
	}
	if (s->freq != 0) {
		if ((s->freq >> 16) == 0xffff) {
			if ((s->freq & 65535) == 0) {
				write_log(_T("UAESND: zero period is not allowed\n"));
				return false;
			}
		} else if (s->freq < 1 * 256 || s->freq > UAESND_MAX_FREQ * 256) {
			write_log(_T("UAESND: unsupported frequency %d\n"), s->freq);
			return false;
		}
	}
	if (s->volume < 0 || s->volume > 32768) {
		write_log(_T("UAESND: unsupported volume %d\n"), s->volume);
		return false;
	}
	if (s->next && ((s->next & 1) || !valid_address(s->next, 32))) {
		write_log(_T("UAESND: invalid next sample set pointer %08x\n"), s->next);
		return false;
	}
	uaecptr saddr = s->address;
	if (s->len == 0x80000000) {
		if (!get_indirect(s, saddr))
			return false;
	} else {
		if (s->len < 0)
			saddr -= abs(s->len) * s->framesize;
		if (!valid_address(saddr, abs(s->len) * s->framesize)) {
			write_log(_T("UAESND: invalid sample pointer range %08x - %08x\n"), saddr, saddr + s->len * s->framesize);
			return false;
		}
	}
	if (s->repcnt) {
		uaecptr repeat = s->repeat;
		if (s->replen < 0)
			repeat -= abs(s->replen) * s->framesize;
		if (s->repeat && !valid_address(repeat, abs(s->replen) * s->framesize)) {
			write_log(_T("UAESND: invalid sample repeat pointer range %08x - %08x\n"), repeat, repeat + s->replen * s->framesize);
			return false;
		}
	}
	uaesnd_setfreq(data, s);
	for (int i = s->ch; i < MAX_UAE_CHANNELS; i++) {
		s->sample[i] = 0;
	}
	return true;
}

static void uaesnd_apply_mono_pan(struct uaesndboard_stream *s, int *highestch)
{
	if (s->ch != 1 || !s->panx)
		return;
	int smp = s->sample[0];
	s->sample[0] = smp * (32768 - s->panx) / 32768;
	s->sample[1] = smp * (32768 + s->panx) / 32768;
	if (*highestch < 2)
		*highestch = 2;
}

static void uaesnd_load(struct uaesndboard_stream *s, uaecptr addr)
{
	s->flags =		get_word(addr +  0);
	s->format =		get_word(addr +  2);
	s->address =	get_long(addr +  4);
	s->len =		get_long(addr +  8);
	s->freq =		get_long(addr + 12);
	s->repeat =		get_long(addr + 16);
	s->replen =		get_long(addr + 20);
	s->repcnt =		get_word(addr + 24);
	s->volume =		get_word(addr + 26);
	s->next =		get_long(addr + 28);
	s->ch =			get_byte(addr + 32);
	s->bitmode =	get_byte(addr + 33);
	s->intenamask = get_byte(addr + 34);
	s->chmode =		get_byte(addr + 35);
	s->panx =		get_word(addr + 36);
	s->pany =		get_word(addr + 38);

	if (s->len)
		s->lastlen = s->len;

	s->original_address = s->address;
	s->original_len = s->len;
	put_long_host(s->io + 40, s->original_address);
	put_long_host(s->io + 44, s->original_len);

#if DEBUG_SNDDEV
	write_log(_T("PTR = %08x\n"), addr);
	write_log(_T("Flags = %04x\n"), s->flags);
	write_log(_T("Format = %04x\n"), s->format);
	write_log(_T("Address = %08x\n"), s->address);
	write_log(_T("Length = %08x\n"), s->len);
	write_log(_T("Frequency = %d.%d\n"), s->freq >> 8, s->freq & 255);
	write_log(_T("Repeat = %08x\n"), s->repeat);
	write_log(_T("Replen = %08x\n"), s->replen);
	write_log(_T("Repcnt = %04x\n"), s->repcnt);
	write_log(_T("Volume = %04x\n"), s->volume);
	write_log(_T("Next = %08x\n"), s->next);
	write_log(_T("CH = %02x\n"), s->ch);
	write_log(_T("Bitmode = %02x\n"), s->bitmode);
	write_log(_T("Intena = %02x\n"), s->intenamask);
	write_log(_T("CHmode = %02x\n"), s->chmode);
	write_log(_T("PanX = %04x\n"), s->panx);
	write_log(_T("PanY = %04x\n"), s->pany);
#endif
}

static bool uaesnd_directload(struct uaesndboard_data *data, struct uaesndboard_stream *s, int reg)
{
	if (reg < 0 || reg == 4) {
		s->address = get_long_host(s->io + 4);
	}
	if (reg < 0 || reg == 8) {
		s->len = get_long_host(s->io + 8);
		if (s->len)
			s->lastlen = s->len;
	}
	if (reg < 0 || reg == 12) {
		s->freq = get_long_host(s->io + 12);
	}
	if (reg < 0 || reg == 16) {
		s->repeat = get_long_host(s->io + 16);
	}
	if (reg < 0 || reg == 20) {
		s->replen = get_long_host(s->io + 20);
	}
	if (reg < 0 || reg == 24 || reg == 26) {
		// repeat count + volume share a long word; reload volume so
		// current-set volume writes take effect while playing
		s->volume = get_word_host(s->io + 26);
	}
	if (reg < 0 || reg == 36 || reg == 38) {
		s->panx = get_word_host(s->io + 36);
		s->pany = get_word_host(s->io + 38);
	}
	if (!uaesnd_validate(data, s)) {
		data->invalid_set_count++;
		uaesnd_set_error(data, UAESND_ERR_INVALID_SET);
		return false;
	}
	return true;
}

static bool uaesnd_next(struct uaesndboard_data *data, struct uaesndboard_stream *s, uaecptr addr)
{
	if ((addr & 3) || !valid_address(addr, STREAM_STRUCT_SIZE) || addr < 0x100) {
		write_log(_T("UAESND: invalid sample set pointer %08x\n"), addr);
		data->invalid_set_count++;
		uaesnd_set_error(data, UAESND_ERR_INVALID_SET);
		return false;
	}

	s->setaddr = addr;
	uaesnd_load(s, addr);
	s->first = 10;
	s->repeating = false;

	if (!uaesnd_validate(data, s)) {
		data->invalid_set_count++;
		uaesnd_set_error(data, UAESND_ERR_INVALID_SET);
		return false;
	}
	return true;
}

static void uaesnd_stream_start(struct uaesndboard_data *data, struct uaesndboard_stream *s, bool always)
{
	if ((!s->play || always) && s->next) {
		if (data->streammask & (1 << (s - data->stream))) {
#if DEBUG_SNDDEV
			write_log(_T("UAESND %d start (old=%d,forced=%d,next=%08x)\n"), s - data->stream, s->play, always, s->next);
#endif
			if (uaesnd_next(data, s, s->next)) {
				uaesndboard_start(data, s);
				return;
			}
		}
	}
	if (s->play && !s->next) {
		uaesndboard_stop(data, s);
	}
}

static void uaesnd_irq(struct uaesndboard_stream *s, uae_u8 mask)
{
	struct uaesndboard_data *data = &uaesndboard[0];
	uae_u8 enablemask = s->masterintenamask;
	uae_u8 intenamask = s->intenamask | 0x10 | 0x20 | 0x40;
	data->stream_irq_count++;
	if (mask & 0x10)
		data->timer_irq_count++;
	s->intreqmask |= mask;
	if ((intenamask & mask) && (enablemask & mask)) {
		s->intreqmask |= 0x80;
		devices_rethink_all(sndboard_rethink);
	}
}

static void uaesnd_streammask(struct uaesndboard_data *data, uae_u32 m)
{
	uae_u32 old = data->streammask;
	data->streammask = m;
	data->streammask &= (1 << MAX_UAE_STREAMS) - 1;
	for (int i = 0; i < MAX_UAE_STREAMS; i++) {
		if ((old ^ data->streammask) & (1 << i)) {
			struct uaesndboard_stream *s = &data->stream[i];
			s->intreqmask = 0;
			if (data->streammask & (1 << i)) {
				uaesnd_stream_start(data, s, false);
			} else {
				uaesndboard_stop(data, s);
			}
		}
	}
}

static bool audio_state_sndboard_uae(int streamid, void *params)
{
	struct uaesndboard_data *data = &uaesndboard[0];
	struct uaesndboard_stream *s = NULL;

	for (int i = 0; i < MAX_UAE_STREAMS; i++) {
		if (data->stream[i].streamid == streamid) {
			s = &data->stream[i];
			break;
		}
	}
	if (!s)
		return false;
	int highestch = s->ch;
	int streamnum = (int)(s - data->stream);
	if (s->play && (data->streammask & (1 << streamnum))) {
		uaecptr addr;
		int len;
		if (s->indirect_ptr) {
			addr = s->indirect_address;
			len = s->indirect_len;
		} else {
			len = s->repeating ? s->replen : s->len;
			addr = s->repeating ? s->repeat : s->address;
		}
		int st = (s->bitmode & 7);
		bool le = (s->bitmode & 0x80) != 0;
		bool sign = (s->bitmode & 0x40) != 0;
		bool len_nonzero = len != 0;
		if (len_nonzero) {
			if (len < 0)
				addr -= s->framesize;
			for (int i = 0; i < s->ch; i++) {
				uae_u16 sample = 0;
				switch (st)
				{
				case 3: // 32-bit (last 2 bytes ignored)
					sample = get_word(le ? (addr + 2) : (addr + 0));
					addr += 4;
					break;
				case 2: // 24-bit (last byte ignored)
					sample = get_word(le ? (addr + 1) : (addr + 0));
					addr += 3;
					break;
				case 1: // 16-bit
					sample = get_word(addr);
					addr += 2;
					break;
				case 0: // 8-bit
					sample = get_byte(addr);
					sample = (sample << 8) | sample;
					addr += 1;
					break;
				}
				if (le)
					sample = (sample >> 8) | (sample << 8);
				if (sign)
					sample -= 0x8000;
				uae_s16 samples = (uae_s16)sample;
				// scale by set volume and board channel volume multiplicatively
				// so volume 0 is silent (the old averaged formula had a 0.5 gain floor)
				s->sample[i] = samples * s->volume / 32768 * data->volume[i] / 32768;
			}
			if (len < 0)
				addr -= s->framesize;
			if (s->repeating) {
				s->repeat = addr;
				if (s->replen > 0)
					s->replen--;
				else
					s->replen++;
				len = s->replen;
			} else {
				s->address = addr;
				if (s->indirect_len) {
					if (s->indirect_len > 0)
						s->indirect_len--;
					else
						s->indirect_len++;
					len = s->indirect_len;
				} else {
					if (s->len > 0)
						s->len--;
					else
						s->len++;
					len = s->len;
				}
			}
			uaesnd_irq(s, 8);
		}
		if (s->first > 0) {
			s->first--;
			if (!s->first) {
				uaesnd_irq(s, 1);
			}
		}
		// if len was zero when called: do nothing.
		if (len == 0 && len_nonzero) {
			bool end = true;
			// sample ended
			if (s->repcnt) {
				if (s->repcnt != 0xffff)
					s->repcnt--;
				s->repeat = get_long(s->setaddr + 16);
				if (s->repeat) {
					s->replen = get_long(s->setaddr + 20);
					if (s->replen == 0) {
						s->replen = s->lastlen;
					} else {
						s->lastlen = s->replen;
					}
				} else {
					s->repeat = 0;
				}
				s->repeating = true;
				uaesnd_irq(s, 4);
				end = false;
			} else if (s->indirect_ptr) {
				s->indirect_ptr += 8;
				if (!get_indirect(s, s->indirect_ptr)) {
					uaesndboard_stop(data, s);
				}
				if (!s->indirect_address) {
					s->indirect_ptr = 0;
				}
			}
			if (end) {
				// set ended
				uaesnd_irq(s, 2);
				s->next = get_long(s->setaddr + 28);
				if (s->next) {
					if (!uaesnd_next(data, s, s->next)) {
						uaesndboard_stop(data, s);
					}
				} else {
					uaesndboard_stop(data, s);
				}
			}
		}
	}
		if (s->ch == 1 && s->chmode) {
			int smp = s->sample[0];
			for (int i = 1; i < MAX_UAE_CHANNELS; i++) {
				if ((1 << i) & s->chmode) {
					s->sample[i] = smp;
					if (i + 1 > highestch)
						highestch = i + 1;
				}
			}
	} else if (s->ch == 4 && s->chmode == 1) {
		s->sample[2] = s->sample[4];
		s->sample[3] = s->sample[5];
	} else if (s->ch == 6 && s->chmode == 1) {
		int c = s->sample[2];
		int lfe = s->sample[3];
		s->sample[2] = s->sample[4];
		s->sample[3] = s->sample[5];
		s->sample[4] = c;
		s->sample[5] = lfe;
	} else if (s->ch == 8 && s->chmode == 1) {
		int c = s->sample[2];
		int lfe = s->sample[3];
		s->sample[2] = s->sample[4];
		s->sample[3] = s->sample[5];
		s->sample[4] = s->sample[6];
		s->sample[5] = s->sample[7];
		s->sample[6] = c;
		s->sample[7] = lfe;
	}
	uaesnd_apply_mono_pan(s, &highestch);
	audio_state_stream_state(streamid, s->sample, highestch, s->event_time);
	return true;
}

static void uaesnd_latch(struct uaesndboard_data *data, struct uaesndboard_stream *s)
{
	memcpy(s->io + 0x40, s->io + 0x00, 0x40);
}
static void uaesnd_latch_back(struct uaesndboard_data *data, struct uaesndboard_stream *s)
{
	memcpy(s->io + 0x00, s->io + 0x40, 0x40);
	if (!uaesnd_directload(data, s, -1)) {
		uaesndboard_stop(data, s);
	} else if (!s->play) {
		uaesndboard_start(data, s);
	}
}

static void uaesnd_latch_mask(struct uaesndboard_data *data, uae_u32 mask)
{
	for (int i = 0; i < MAX_UAE_STREAMS; i++) {
		if ((mask & (1 << i)) && (data->streammask & (1 << i))) {
			uaesnd_latch(data, &data->stream[i]);
		}
	}
}

static void uaesnd_unlatch_mask(struct uaesndboard_data *data, uae_u32 mask)
{
	for (int i = 0; i < MAX_UAE_STREAMS; i++) {
		if ((mask & (1 << i)) && (data->streammask & (1 << i))) {
			uaesnd_latch_back(data, &data->stream[i]);
		}
	}
}

static int uaesnd_timer_period(uae_u32 v)
{
	if (v == 0) {
		v = 0;
	} else if ((v >> 16) == 0xffff) {
		v = v & 65535;
	} else {
		v = (int)(((uae_s64)base_event_clock * CYCLE_UNIT * 256) / v);
	}
	return v;
}

static void uaesnd_timer(uae_u32 v)
{
	struct uaesndboard_data *data = &uaesndboard[v >> 16];
	struct uaesndboard_stream *s = &data->stream[v & 65535];
	s->timer_cnt = get_long_host(s->io + 0x94);
	if (s->timer_cnt > 0 && data->enabled) {
		s->timer_event_time = uaesnd_timer_period(s->timer_cnt);
		if (s->timer_event_time > 0) {
			event2_newevent_xx(-1, s->timer_event_time, (int)(s - &data->stream[0]), uaesnd_timer);
			uaesnd_irq(s, 0x10);
		}
	}
}

static void uaesnd_put(struct uaesndboard_data *data, struct uaesndboard_stream *s, int reg)
{
	if (reg == 0x80) { // set pointer write?
		uaecptr setaddr = get_long_host(s->io + 0x80);
		s->next = setaddr;
		uaesnd_stream_start(data, s, false);
	} else if (reg == 0xa0) { // force set pointer write?
		uaecptr setaddr = get_long_host(s->io + 0xa0);
		s->next = setaddr;
		uaesnd_stream_start(data, s, true);
	} else if (reg >= 0x8c && reg <= 0x8f) {
		s->masterintenamask = get_long_host(s->io + 0x8c);
	} else if (reg == 0x94) { // timer
		int timer_cnt = get_long_host(s->io + 0x94);
#if DEBUG_SNDDEV
		write_log(_T("uaesnd timer %d: %d -> %d\n"), s - &data->stream[0], s->timer_cnt, timer_cnt);
#endif
		if (timer_cnt != s->timer_cnt) {
			s->timer_cnt = 0;
			if (timer_cnt > 0) {
				s->timer_event_time = uaesnd_timer_period(timer_cnt);
				if (s->timer_event_time > 0) {
					s->timer_cnt = timer_cnt;
					event2_newevent_xx(-1, s->timer_event_time, (((int)(data - &uaesndboard[0])) << 16) | ((int)(s - &data->stream[0])), uaesnd_timer);
				}
			}
		}
	} else if (reg == 0x93) { // status strobe
		uae_u8 b = get_byte_host(s->io + 0x93);
		if (b & 1) {
			// new sample queued, do not repeat or if already repeating: start immediately
			s->repcnt = 0;
			if (s->repeating) {
				s->replen = 1;
			}
		}
	} else if (reg < 0x40) {
		if (!uaesnd_directload(data, s, reg)) {
			uaesndboard_stop(data, s);
		}
	}
}

static void uaesnd_configure(struct uaesndboard_data *data)
{
	data->configured = 1;
	for (int i = 0; i < MAX_UAE_STREAMS; i++) {
		data->stream[i].baseaddr = expamem_board_pointer + 0x100 + 0x100 * i;
	}
}

static uae_u32 REGPARAM2 uaesndboard_bget(uaecptr addr)
{
	uae_u8 v = 0;
	struct uaesndboard_data *data = &uaesndboard[0];
	addr &= 65535;
	if (addr < 0x80) {
		return data->acmemory[addr];
	} else if (data->configured) {
		struct uaesndboard_stream *s = uaesnd_get(addr);
		if (s) {
			int reg = addr & 255;
			if (reg == 0x87)
				s->intreqmask = 0;
			s->io[0x93] = (s->play ? 1 : 0) | (s->streamid > 0 ? 2 : 0) | (s->repeating ? 4 : 0);
			v = get_byte_host(s->io + reg);
		} else if (uaesnd_capture_addr(addr)) {
			v = uaesnd_capture_bget(data, addr);
		} else if (uaesnd_capture_block_addr(addr)) {
			v = uaesnd_capture_block_bget(data, addr);
		} else if (addr >= 0x80 && addr < 0xe0) {
			uaesnd_update_info(data);
			v = get_byte_host(data->info + (addr & 0x7f));
		} else if (addr >= 0xe0 && addr <= 0xe3) {
			v = data->streamallocmask >> (8 * (3 - (addr - 0xe0)));
		} else if (addr >= 0xf0 && addr <= 0xf3) {
			v = data->streammask >> ((3 - (addr - 0xf0)) * 8);
		} else if (addr >= 0xf4 && addr <= 0xf7) {
			v = data->streamintenamask >> ((3 - (addr - 0xf0)) * 8);
		} else if (addr >= 0xf8 && addr <= 0xfb) {
			v = data->streamintreqmask >> ((3 - (addr - 0xf0)) * 8);
		}
	}
#if DEBUG_SNDDEV_READ
	write_log(_T("uaesnd_bget %08x = %02x\n"), addr, v);
#endif
	return v;
}
static uae_u32 REGPARAM2 uaesndboard_wget(uaecptr addr)
{
	struct uaesndboard_data *data = &uaesndboard[0];
	uae_u16 v = 0;
	addr &= 65535;
	if (addr < 0x80) {
		return (uaesndboard_bget(addr) << 8) | uaesndboard_bget(addr + 1);
	} else if (data->configured) {
		if (addr & 1)
			return 0xffff;
		struct uaesndboard_stream *s = uaesnd_get(addr);
		if (s) {
			int reg = addr & 255;
			int reg4 = reg / 4;
			if (reg4 <= 4 || reg4 == 6) {
				if (!(reg & 2)) {
					s->wordlatch = get_word_host(s->io + ((reg + 2) & 255));
				} else {
					return s->wordlatch;
				}
			}
			if (reg == 0x86) {
				s->intreqmask = 0;
			}
			v = get_word_host(s->io + reg);
		} else if (uaesnd_capture_addr(addr)) {
			v = uaesnd_capture_wget(data, addr);
		} else if (uaesnd_capture_block_addr(addr)) {
			v = uaesnd_capture_block_wget(data, addr);
		} else if (addr >= 0x80 && addr < 0xe0 - 1) {
			uaesnd_update_info(data);
			v = get_word_host(data->info + (addr & 0x7f));
		} else if (addr == 0xe0) {
			v = data->streamallocmask >> 16;
		} else if (addr == 0xe2) {
			v = data->streamallocmask >> 0;
		} else if (addr == 0xf0) {
			v = data->streammask >> 16;
		} else if (addr == 0xf2) {
			v = data->streammask;
		} else if (addr == 0xf4) {
			v = data->streamintenamask >> 16;
		} else if (addr == 0xf6) {
			v = data->streamintenamask;
		} else if (addr == 0xf8) {
			v = data->streamintreqmask >> 16;
		} else if (addr == 0xfa) {
			v = data->streamintreqmask;
		}
	}
#if DEBUG_SNDDEV_READ
	write_log(_T("uaesnd_wget %08x = %04x\n"), addr, v);
#endif
	return v;
}
static uae_u32 REGPARAM2 uaesndboard_lget(uaecptr addr)
{
	struct uaesndboard_data *data = &uaesndboard[0];
	uae_u32 v = 0;
	addr &= 65535;
	if (addr < 0x80) {
		return (uaesndboard_wget(addr) << 16) | uaesndboard_wget(addr + 2);
	} else if (data->configured) {
		if (addr & 3)
			return 0xffffffff;
		struct uaesndboard_stream *s = uaesnd_get(addr);
		if (s) {
			int reg = addr & 255;
			if (reg == 0x84)
				s->intreqmask = 0;
			v = get_long_host(s->io + reg);
		} else if (uaesnd_capture_addr(addr)) {
			v = uaesnd_capture_lget(data, addr);
		} else if (uaesnd_capture_block_addr(addr)) {
			v = uaesnd_capture_block_lget(data, addr);
		} else if (addr >= 0x80 && addr < 0xe0 - 3) {
			uaesnd_update_info(data);
			v = get_long_host(data->info + (addr & 0x7f));
		} else if (addr == 0xe0) {
			v = data->streamallocmask;
		} else if (addr == 0xf0) {
			v = data->streammask;
		} else if (addr == 0xf4) {
			v = data->streamintenamask;
		} else if (addr == 0xf8) {
			v = data->streamintreqmask;
		}
	}
#if DEBUG_SNDDEV_READ
	write_log(_T("uaesnd_lget %08x = %08x\n"), addr, v);
#endif
	return v;
}
static void REGPARAM2 uaesndboard_bput(uaecptr addr, uae_u32 b)
{
	struct uaesndboard_data *data = &uaesndboard[0];
	addr &= 65535;
	if (!data->configured) {
		switch (addr) {
			case 0x48:
			if (!data->z3) {
				uae_u32 ram_start = expamem_board_pointer;
				uae_u32 ram_size = 65536;
				uaesndboard_ram_bank.start = ram_start;
				uaesndboard_ram_bank.reserved_size = ram_size;
				uaesndboard_ram_bank.mask = ram_size - 1;
				mapped_malloc(&uaesndboard_ram_bank);
				map_banks_z2(&uaesndboard_bank_z2, expamem_board_pointer >> 16, 65536 >> 16);
				uaesnd_configure(data);
				expamem_next(&uaesndboard_bank_z2, NULL);
			}
			break;
			case 0x4c:
			data->configured = -1;
			expamem_shutup(&uaesndboard_bank_z2);
			break;
		}
		return;
	} else {
		struct uaesndboard_stream *s = uaesnd_addr(addr);
#if DEBUG_SNDDEV_WRITE
		write_log(_T("uaesnd_bput %08x = %02x\n"), addr, b);
#endif
		if (s) {
			int reg = addr & 255;
			put_byte_host(s->io + reg, b);
			uaesnd_put(data, s, reg);
		} else if (uaesnd_capture_addr(addr)) {
			uaesnd_capture_put(data, addr, b, 1);
		} else if (uaesnd_capture_block_addr(addr)) {
			uaesnd_capture_block_put(data, addr, b, 1);
		} else if (addr >= 0xe0 && addr <= 0xe3) {
			uae_u32 v = data->streamallocmask;
			int shift = 8 * (3 - (addr - 0xe0));
			uae_u32 mask = 0xff;
			v &= ~(mask << shift);
			v |= b << shift;
			uaesnd_streammask(data, data->streammask & v);
			data->streamallocmask &= ~(mask << shift);
			data->streamallocmask |= v << shift;
		} else if (addr >= 0xf0 && addr <= 0xf3) {
			b <<= 8 * (3 - (addr - 0xf0));
			uaesnd_streammask(data, b);
		}
	}
}
static void REGPARAM2 uaesndboard_wput(uaecptr addr, uae_u32 b)
{
	struct uaesndboard_data *data = &uaesndboard[0];
	addr &= 65535;
	if (!data->configured) {
		switch (addr) {
			case 0x44:
			if (data->z3) {
				uae_u32 ram_start = expamem_board_pointer + 8 * 1024 * 1024;
				uae_u32 ram_size = 8 * 1024 * 1024;
				map_banks_z3(&uaesndboard_bank_z3, expamem_board_pointer >> 16, ram_size >> 16);
				uaesndboard_ram_bank.start = ram_start;
				uaesndboard_ram_bank.reserved_size = ram_size;
				uaesndboard_ram_bank.mask = ram_size - 1;
				mapped_malloc(&uaesndboard_ram_bank);
				map_banks_z3(&uaesndboard_ram_bank, ram_start >> 16, ram_size >> 16);
				uaesnd_configure(data);
				expamem_next(&uaesndboard_bank_z3, NULL);
			}
			break;
		}
		return;
	} else {
		if (addr & 1)
			return;
#if DEBUG_SNDDEV_WRITE
		write_log(_T("uaesnd_wput %08x = %04x\n"), addr, b);
#endif
		struct uaesndboard_stream *s = uaesnd_addr(addr);
		if (s) {
			int reg = addr & 255;
			put_word_host(s->io + reg, b);
			uaesnd_put(data, s, reg);
		} else if (uaesnd_capture_addr(addr)) {
			uaesnd_capture_put(data, addr, b, 2);
		} else if (uaesnd_capture_block_addr(addr)) {
			uaesnd_capture_block_put(data, addr, b, 2);
		} else if (addr == 0xe4 + 2) {
			uaesnd_latch_mask(data, b);
		} else if (addr == 0xe8 + 2) {
			uaesnd_unlatch_mask(data, b);
		} else if (addr == 0xf0) {
			uaesnd_streammask(data, b << 16);
		} else if (addr == 0xf2) {
			uaesnd_streammask(data, b);
		}
	}
}
static void REGPARAM2 uaesndboard_lput(uaecptr addr, uae_u32 b)
{
	struct uaesndboard_data *data = &uaesndboard[0];
	addr &= 65535;
	if (data->configured) {
		if (addr & 3)
			return;
#if DEBUG_SNDDEV_WRITE
		write_log(_T("uaesnd_lput %08x = %08x\n"), addr, b);
#endif
		struct uaesndboard_stream *s = uaesnd_addr(addr);
		if (s) {
			int reg = addr & 255;
			put_long_host(s->io + reg, b);
			uaesnd_put(data, s, reg);
		} else if (uaesnd_capture_addr(addr)) {
			uaesnd_capture_put(data, addr, b, 4);
		} else if (uaesnd_capture_block_addr(addr)) {
			uaesnd_capture_block_put(data, addr, b, 4);
		} else if (addr == 0xe0) {
			uaesnd_streammask(data, data->streammask & b);
			data->streamallocmask = b;
		} else if (addr == 0xe4) {
			uaesnd_latch_mask(data, b);
		} else if (addr == 0xe8) {
			uaesnd_unlatch_mask(data, b);
		} else if (addr == 0xf0) {
			uaesnd_streammask(data, b);
		} else if (addr == 0xf4) {
			data->streamintenamask = b;
		} else if (addr == 0xf8) {
			data->streamintreqmask = b;
		}
	}
}

addrbank uaesndboard_sub_bank_z2 = {
	uaesndboard_lget, uaesndboard_wget, uaesndboard_bget,
	uaesndboard_lput, uaesndboard_wput, uaesndboard_bput,
	default_xlate, default_check, NULL, NULL, _T("uaesnd z2"),
	dummy_lgeti, dummy_wgeti,
	ABFLAG_IO, S_READ, S_WRITE
};

static addrbank_sub uaesndz2_sub_banks[] = {
	{ &uaesndboard_sub_bank_z2, 0x0000 },
	{ &uaesndboard_ram_bank, 0x8000 },
	{ NULL }
};

addrbank uaesndboard_bank_z3 = {
	uaesndboard_lget, uaesndboard_wget, uaesndboard_bget,
	uaesndboard_lput, uaesndboard_wput, uaesndboard_bput,
	default_xlate, default_check, NULL, NULL, _T("uaesnd z3"),
	dummy_lgeti, dummy_wgeti,
	ABFLAG_IO, S_READ, S_WRITE
};

addrbank uaesndboard_bank_z2 = {
	sub_bank_lget, sub_bank_wget, sub_bank_bget,
	sub_bank_lput, sub_bank_wput, sub_bank_bput,
	default_xlate, default_check, NULL, NULL, _T("uaesnd z2"),
	dummy_lgeti, dummy_wgeti,
	ABFLAG_IO, S_READ, S_WRITE, uaesndz2_sub_banks
};

static void ew(uae_u8 *acmemory, int addr, uae_u32 value)
{
	addr &= 0xffff;
	if (addr == 00 || addr == 02 || addr == 0x40 || addr == 0x42) {
		acmemory[addr] = (value & 0xf0);
		acmemory[addr + 2] = (value & 0x0f) << 4;
	} else {
		acmemory[addr] = ~(value & 0xf0);
		acmemory[addr + 2] = ~((value & 0x0f) << 4);
	}
}

bool uaesndboard_init (struct autoconfig_info *aci, int z)
{
	struct uaesndboard_data *data = &uaesndboard[0];

	device_add_reset(uaesndboard_reset);

	if (aci->doinit)
		snd_init();

	data->configured = 0;
	data->enabled = true;
	data->z3 = z == 3;
	memset(data->acmemory, 0xff, sizeof data->acmemory);
	data->invalid_set_count = 0;
	data->stream_alloc_failure_count = 0;
	data->stream_start_count = 0;
	data->stream_stop_count = 0;
	data->stream_irq_count = 0;
	data->timer_irq_count = 0;
	data->last_error_code = UAESND_ERR_NONE;
	data->capture.control = 0;
	data->capture.status = 0;
	data->capture.frequency = currprefs.sound_freq;
	data->capture.available = 0;
	data->capture.overrun_count = 0;
	data->capture.intreq = 0;
	data->capture.threshold = 0;
	data->capture.frame_count = 0;
	data->capture.dropped_byte_count = 0;
	data->capture.buffer_size = 0;
	data->capture.read_index = 0;
	data->capture.write_index = 0;
	data->capture.buffer = NULL;
	const struct expansionromtype *ert = get_device_expansion_rom(z == 3 ? ROMTYPE_UAESNDZ3 : ROMTYPE_UAESNDZ2);
	if (!ert)
		return false;
	data->streammask = 0;
	for (int i = 0; i < MAX_UAE_CHANNELS; i++) {
		data->volume[i] = 32768;
	}
	put_long_host(data->info + 0, (UAEMAJOR << 16) | (UAEMINOR << 8) | (UAESUBREV));
	put_long_host(data->info + 4, (1 << 16) | (0));
	put_long_host(data->info + 8, currprefs.sound_freq);
	put_byte_host(data->info + 12, MAX_UAE_CHANNELS);
	put_byte_host(data->info + 13, get_audio_nativechannels(currprefs.sound_stereo));
	put_byte_host(data->info + 14, MAX_UAE_STREAMS);
	put_long_host(data->info + 16, data->z3 ? 8 * 1024 * 1024 : 0x8000);
	put_long_host(data->info + 20, data->z3 ? 8 * 1024 * 1024 : 0x8000);
	uaesnd_update_info(data);
	for (int i = 0; i < 16; i++) {
		uae_u8 b = ert->autoconfig[i];
		ew(data->acmemory, i * 4, b);
	}

	memcpy(aci->autoconfig_raw, data->acmemory, sizeof data->acmemory);
	aci->addrbank = data->z3 ? &uaesndboard_bank_z3 : &uaesndboard_bank_z2;
	return true;
}

bool uaesndboard_init_z2(struct autoconfig_info *aci)
{
	return uaesndboard_init(aci, 2);
}
bool uaesndboard_init_z3(struct autoconfig_info *aci)
{
	return uaesndboard_init(aci, 3);
}

static void uaesndboard_free(void)
{
	for (int j = 0; j < MAX_DUPLICATE_SOUND_BOARDS; j++) {
		struct uaesndboard_data *data = &uaesndboard[j];
		uaesnd_capture_stop(data);
		data->enabled = false;
	}
	mapped_free(&uaesndboard_ram_bank);
	sndboard_rethink();
}

static void uaesndboard_reset(int hardreset)
{
	for (int j = 0; j < MAX_DUPLICATE_SOUND_BOARDS; j++) {
		struct uaesndboard_data *data = &uaesndboard[j];
		uaesnd_capture_stop(data);
		if (data->enabled) {
			for (int i = 0; i < MAX_UAE_STREAMS; i++) {
				if (data->stream[i].streamid) {
					audio_enable_stream(false, data->stream[i].streamid, 0, NULL, NULL);
					memset(&data->stream[i], 0, sizeof(struct uaesndboard_stream));
				}
			}
		}
		data->streammask = 0;
	}
	mapped_free(&uaesndboard_ram_bank);
	sndboard_rethink();
}


// PMX

struct pmx_data
{
	bool enabled;
	int configured;
	uae_u8 acmemory[128];
	int streamid;
	struct romconfig *rc;
	int reset_delay;
	uae_u16 status;
	bool dreq;
	uae_u16 regs[16];
};
static struct pmx_data pmx[MAX_DUPLICATE_SOUND_BOARDS];

static void pmx_reset_chip(struct pmx_data *data)
{
	for (int i = 0; i < 16; i++) {
		data->regs[i] = 0;
	}
	data->regs[0] = 0x4000;
	data->regs[1] = 0x000c;
}

static void REGPARAM2 pmx_bput(uaecptr addr, uae_u32 v)
{
	struct pmx_data *data = &pmx[0];
	v &= 0xff;
	write_log(_T("PMXBPUT %08x %02x %08x\n"), addr, v, M68K_GETPC);
}

static void REGPARAM2 pmx_wput(uaecptr addr, uae_u32 v)
{
	struct pmx_data *data = &pmx[0];
	int reg = -1;
	v &= 0xffff;
	if (addr & 0x8000) {
		reg = (addr >> 2) & 15;
		data->regs[reg] = v;
	} else {
		data->status = v;
		if (v & 0x8000) {
			data->dreq = true;
			data->reset_delay = 10;
		}
	}
	write_log(_T("PMXWPUT %d %08x %04x %08x\n"), reg, addr, v, M68K_GETPC);
}

static void REGPARAM2 pmx_lput(uaecptr addr, uae_u32 v)
{
	write_log(_T("PMXLPUT %08x %08x %08x\n"), addr, v, M68K_GETPC);
}

static uae_u32 REGPARAM2 pmx_bget(uaecptr addr)
{
	struct pmx_data *data = &pmx[0];
	uae_u8 v = 0;
	data->dreq = !data->dreq;
	if (!data->dreq)
		v |= 1 << 3;
	write_log(_T("PMXBGET %08x %02x %08x\n"), addr, v, M68K_GETPC);
	return v;
}
static uae_u32 REGPARAM2 pmx_wget(uaecptr addr)
{
	struct pmx_data *data = &pmx[0];
	uae_u16 v = 0;
	int reg = -1;
	if (addr & 0x8000) {
		reg = (addr >> 2) & 15;
		v = data->regs[reg];
		if (reg == 1) {
			v &= ~0x03f0;
			v |= 0x0060; ;//revision
		}
	} else {
		v = data->status;
	}
	write_log(_T("PMXWGET %d %08x %04x %08x\n"), reg, addr, v, M68K_GETPC);
	return v;
}
static uae_u32 REGPARAM2 pmx_lget(uaecptr addr)
{
	write_log(_T("PMXLGET %08x %08x\n"), addr, M68K_GETPC);
	return 0;
}

static addrbank pmx_bank = {
	pmx_lget, pmx_wget, pmx_bget,
	pmx_lput, pmx_wput, pmx_bput,
	default_xlate, default_check, NULL, _T("*"), _T("PMX"),
	dummy_lgeti, dummy_wgeti,
	ABFLAG_IO | ABFLAG_SAFE, S_READ, S_WRITE
};

bool pmx_init (struct autoconfig_info *aci)
{
	struct pmx_data *data = &pmx[0];
	const struct expansionromtype *ert = get_device_expansion_rom(ROMTYPE_PMX);
	if (!ert)
		return false;

	aci->addrbank = &pmx_bank;
	aci->autoconfig_automatic = true;
	device_add_reset(sndboard_reset);

	if (!aci->doinit) {
		aci->autoconfigp = ert->autoconfig;
		return true;
	}

	snd_init();

	data->configured = 0;
	data->streamid = 0;
	memset(data->acmemory, 0xff, sizeof data->acmemory);
	data->rc = aci->rc;
	data->enabled = true;
	for (int i = 0; i < 16; i++) {
		uae_u8 b = ert->autoconfig[i];
		ew(data->acmemory, i * 4, b);
	}
	memcpy(aci->autoconfig_raw, data->acmemory, sizeof data->acmemory);
	return true;
}

static void pmx_free(void)
{
	for (int j = 0; j < MAX_DUPLICATE_SOUND_BOARDS; j++) {
		struct pmx_data *data = &pmx[j];
		data->enabled = false;
	}
	sndboard_rethink();
}

static void pmx_reset(int hardreset)
{
	for (int j = 0; j < MAX_DUPLICATE_SOUND_BOARDS; j++) {
		struct pmx_data *data = &pmx[j];
		if (data->enabled) {
		}
	}
	sndboard_rethink();
}

// TOCCATA/PRELUDE

#define SNDDEV_TOCCATA 0
#define SNDDEV_PRELUDE 1
#define SNDDEV_PRELUDE1200 2

#define BOARD_SIZE 65536
#define BOARD_MASK (BOARD_SIZE - 1)

#define FIFO_SIZE_MAX 1024

struct snddev_data {
	bool enabled;
	int type;
	uae_u8 acmemory[128];
	int configured;
	uae_u32 baseaddress;
	uae_u32 baseaddress_mask, baseaddress_value;
	uae_u8 ad1848_index;
	uae_u8 ad1848_index_mask;
	uae_u8 ad1848_regs[32];
	uae_u8 ad1848_status;
	int autocalibration;
	uae_u8 snddev_status;
	int snddev_irq;
	int fifo_read_index;
	int fifo_write_index;
	int data_in_fifo;
	int fifo_size;
	uae_u8 fifo[FIFO_SIZE_MAX];
	bool fifo_play_byteswap, fifo_record_byteswap;

	int fifo_record_read_index;
	int fifo_record_write_index;
	int data_in_record_fifo;
	uae_u8 record_fifo[FIFO_SIZE_MAX];

	int streamid;
	int ch_sample[2];

	uae_u16 codec_reg1_mask;
	uae_u16 codec_reg1_addr;
	uae_u16 codec_reg2_mask;
	uae_u16 codec_reg2_addr;
	uae_u16 codec_fifo_mask;
	uae_u16 codec_fifo_addr;

	int fifo_half;
	int snddev_active;
	int left_volume, right_volume;

	int freq, freq_adjusted;
	int play_channels, play_samplebits;
	int record_channels, record_samplebits;
	int event_time, record_event_time;
	int record_event_counter;
	int play_bytespersample, record_bytespersample;

	int capture_buffer_size;
	int capture_read_index, capture_write_index;
	uae_u8 *capture_buffer;

	struct romconfig *rc;
	addrbank *bank;
};

static struct snddev_data snddev[MAX_SNDDEVS];

extern addrbank toccata_bank;

#define STATUS_ACTIVE 1
#define STATUS_RESET 2
#define STATUS_FIFO_CODEC 4
#define STATUS_FIFO_RECORD 8
#define STATUS_FIFO_PLAY 0x10
#define STATUS_RECORD_INTENA 0x40
#define STATUS_PLAY_INTENA 0x80

#define STATUS_READ_INTREQ 128
#define STATUS_READ_PLAY_HALF 8
#define STATUS_READ_RECORD_HALF 4


void update_sndboard_sound (float clk)
{
	base_event_clock = clk;
}

static void process_fifo(struct snddev_data *data)
{
	int prev_data_in_fifo = data->data_in_fifo;
	if (data->data_in_fifo >= data->play_bytespersample) {
		uae_s16 v;
		if (data->play_samplebits == 8) {
			v = data->fifo[data->fifo_read_index] << 8;
			v |= data->fifo[data->fifo_read_index];
			data->ch_sample[0] = v;
			if (data->play_channels == 2) {
				v = data->fifo[data->fifo_read_index + 1] << 8;
				v |= data->fifo[data->fifo_read_index + 1];
			}
			data->ch_sample[1] = v;
		} else if (data->play_samplebits == 16) {
			if (data->fifo_play_byteswap) {
				v = data->fifo[data->fifo_read_index + 0] << 8;
				v |= data->fifo[data->fifo_read_index + 1];
			} else {
				v = data->fifo[data->fifo_read_index + 1] << 8;
				v |= data->fifo[data->fifo_read_index + 0];
			}
			data->ch_sample[0] = v;
			if (data->play_channels == 2) {
				if (data->fifo_play_byteswap) {
					v = data->fifo[data->fifo_read_index + 2] << 8;
					v |= data->fifo[data->fifo_read_index + 3];
				} else {
					v = data->fifo[data->fifo_read_index + 3] << 8;
					v |= data->fifo[data->fifo_read_index + 2];
				}
			}
			data->ch_sample[1] = v;
		}
		data->data_in_fifo -= data->play_bytespersample;
		data->fifo_read_index += data->play_bytespersample;
		data->fifo_read_index = data->fifo_read_index % data->fifo_size;
	} else if (data->data_in_fifo > 0) {
		data->data_in_fifo = 0;
	}
	data->ch_sample[0] = data->ch_sample[0] * data->left_volume / 32768;
	data->ch_sample[1] = data->ch_sample[1] * data->right_volume / 32768;

	if (data->data_in_fifo < data->fifo_size / 2 && prev_data_in_fifo >= data->fifo_size / 2)
		data->fifo_half |= STATUS_FIFO_PLAY;
}

static bool audio_state_sndboard_toccata(int streamid, void *cb)
{
	struct snddev_data *data = (struct snddev_data*)cb;
	if (!data->snddev_active)
		return false;
	if (data->streamid != streamid)
		return false;
	if ((data->snddev_active & STATUS_FIFO_PLAY)) {
		// get all bytes at once to prevent fifo going out of sync
		// if fifo has for example 3 bytes remaining but we need 4.
		process_fifo(data);
	}
	if (data->type == SNDDEV_TOCCATA) {
		int old = data->snddev_irq;
		if (data->snddev_active && (data->snddev_status & STATUS_FIFO_CODEC)) {
			if ((data->fifo_half & STATUS_FIFO_PLAY) && (data->snddev_status & STATUS_PLAY_INTENA) && (data->snddev_status & STATUS_FIFO_PLAY)) {
				data->snddev_irq |= STATUS_READ_PLAY_HALF;
			}
			if ((data->fifo_half & STATUS_FIFO_RECORD) && (data->snddev_status & STATUS_RECORD_INTENA) && (data->snddev_status & STATUS_FIFO_RECORD)) {
				data->snddev_irq |= STATUS_READ_RECORD_HALF;
			}
		}
		if (old != data->snddev_irq) {
			devices_rethink_all(sndboard_rethink);
#if DEBUG_SNDDEV > 2
			write_log(_T("SNDDEV IRQ\n"));
#endif
		}
	}
	audio_state_stream_state(streamid, data->ch_sample, 2, data->event_time);
	return true;
}

static int get_volume(uae_u8 v)
{
	int out;
	if (v & 0x80) // Mute bit
		return 0;
	out = v & 63;
	out = 64 - out;
	out *= 32768 / 64;
	return out;
}

static int get_volume_in(uae_u8 v)
{
	int out;
	if (v & 0x80) // Mute bit
		return 0;
	out = v & 31;
	out = 32 - out;
	out *= 32768 / 32;
	return out;
}

static void calculate_volume_toccata(struct snddev_data *data)
{
	data->left_volume = (100 - currprefs.sound_volume_board) * 32768 / 100;
	data->right_volume = (100 - currprefs.sound_volume_board) * 32768 / 100;

	data->left_volume = get_volume(data->ad1848_regs[6]) * data->left_volume / 32768;
	data->right_volume = get_volume(data->ad1848_regs[7]) * data->right_volume / 32768;

	if (data->rc->device_settings & 1) {
		sound_paula_volume[0] = get_volume_in(data->ad1848_regs[4]);
		sound_paula_volume[1] = get_volume_in(data->ad1848_regs[5]);

		sound_cd_volume[0] = get_volume_in(data->ad1848_regs[2]);
		sound_cd_volume[1] = get_volume_in(data->ad1848_regs[3]);
	}
}

static const int freq_crystals[] = {
	// AD1848 documentation says 24.576MHz but photo of board shows 24.582MHz
	// Added later: It seems there are boards that have correct crystal and
	// also boards with wrong crystal..
	// So we can use correct one in emulation.
	24576000,
	16934400
};
static const int freq_dividers[] = {
	3072,
	1536,
	896,
	768,
	448,
	384,
	512,
	2560
};

static void codec_setup(struct snddev_data *data)
{
	uae_u8 c = data->ad1848_regs[8];

	data->play_channels = (c & 0x10) ? 2 : 1;
	data->play_samplebits = (c & 0x40) ? 16 : 8;
	data->freq = freq_crystals[c & 1] / freq_dividers[(c >> 1) & 7];
	data->freq_adjusted = ((data->freq + 49) / 100) * 100;
	data->play_bytespersample = (data->play_samplebits / 8) * data->play_channels;

	data->record_channels = data->play_channels;
	data->record_samplebits = data->play_samplebits;
	if (data->ad1848_regs[12] & 0x40) {
		uae_u8 r = data->ad1848_regs[28];
		data->record_channels = (r & 0x10) ? 2 : 1;
		data->record_samplebits = (r & 0x40) ? 16 : 8;
		data->fifo_record_byteswap = (r & 0x80) != 0;
	}
	data->record_bytespersample = (data->record_samplebits / 8) * data->record_channels;

	write_log(_T("SNDDEV start %s freq=%d bits=%d channels=%d\n"),
		((data->snddev_active & (STATUS_FIFO_PLAY | STATUS_FIFO_RECORD)) == (STATUS_FIFO_PLAY | STATUS_FIFO_RECORD)) ? _T("Play+Record") :
		(data->snddev_active & STATUS_FIFO_PLAY) ? _T("Play") : _T("Record"),
		data->freq, data->play_samplebits, data->play_channels);
}

static void codec_start(struct snddev_data *data)
{
	data->snddev_active  = (data->ad1848_regs[9] & 1) ? STATUS_FIFO_PLAY : 0;
	data->snddev_active |= (data->ad1848_regs[9] & 2) ? STATUS_FIFO_RECORD : 0;

	codec_setup(data);

	data->event_time = (int)(base_event_clock * CYCLE_UNIT / data->freq);
	data->record_event_time = (int)(base_event_clock * CYCLE_UNIT / (data->freq_adjusted * data->record_bytespersample));
	data->record_event_counter = 0;

	if (data->snddev_active & STATUS_FIFO_PLAY) {
		data->streamid = audio_enable_stream(true, -1, 2, audio_state_sndboard_toccata, data);
	}
	if (data->snddev_active & STATUS_FIFO_RECORD) {
		data->capture_buffer_size = 48000 * 2 * 2; // 1s at 48000/stereo/16bit
		data->capture_buffer = xcalloc(uae_u8, data->capture_buffer_size);
			if (!sndboard_init_capture(data->freq_adjusted, SNDBOARD_CAPTURE_OWNER_TOCCATA)) {
			write_log(_T("SNDDEV capture unavailable, recording disabled\n"));
			data->snddev_active &= ~STATUS_FIFO_RECORD;
			xfree(data->capture_buffer);
			data->capture_buffer = NULL;
		}
	}
}

static void codec_stop(struct snddev_data *data)
{
	if (!data->snddev_active)
		return;
	write_log(_T("CODEC stop\n"));
	data->snddev_active = 0;
	sndboard_free_capture(SNDBOARD_CAPTURE_OWNER_TOCCATA);
	int streamid = data->streamid;
	data->streamid = 0;
	audio_enable_stream(false, streamid, 0, NULL, NULL);
	xfree(data->capture_buffer);
	data->capture_buffer = NULL;
}

static void sndboard_rethink(void)
{
	for (int i = 0; i < MAX_SNDDEVS; i++) {
		if (snddev[i].enabled) {
			struct snddev_data *data = &snddev[i];
			bool irq = data->snddev_irq != 0;
			if (irq) {
				safe_interrupt_set(IRQ_SOURCE_SOUND, 0, true);
			}
		}
	}
	if (uaesndboard[0].enabled) {
		bool irq = uaesnd_rethink();
		if (irq) {
			safe_interrupt_set(IRQ_SOURCE_SOUND, 1, true);
		}
	}
}

static void sndboard_process_capture(struct snddev_data *data)
{
	int frames;
	uae_u8 *buffer = sndboard_get_buffer(&frames);
	if (buffer && frames) {
		uae_u8 *p = buffer;
		int bytes = frames * 4;
		if (bytes >= data->capture_buffer_size - data->capture_write_index) {
			memcpy(data->capture_buffer + data->capture_write_index, p, data->capture_buffer_size - data->capture_write_index);
			p += data->capture_buffer_size - data->capture_write_index;
			bytes -= data->capture_buffer_size - data->capture_write_index;
			data->capture_write_index = 0;
		}
		if (bytes > 0 && bytes < data->capture_buffer_size - data->capture_write_index) {
			memcpy(data->capture_buffer + data->capture_write_index, p, bytes);
			data->capture_write_index += bytes;
		}
	}
	sndboard_release_buffer(buffer, frames);
}

static void check_prelude_interrupt(struct snddev_data *data)
{
	int oldirq = data->snddev_irq;
	if (data->data_in_fifo < data->fifo_size / 2 && data->data_in_fifo > 0) {
		data->snddev_irq |= STATUS_READ_PLAY_HALF;
	} else {
		data->snddev_irq &= ~STATUS_READ_PLAY_HALF;
	}
	if (oldirq != data->snddev_irq) {
		devices_rethink_all(sndboard_rethink);
	}
}

static void sndboard_hsync(void)
{
	for (int i = 0; i < MAX_SNDDEVS; i++) {
		struct snddev_data *data = &snddev[i];
		static int capcnt[MAX_SNDDEVS];

		if (!data->configured)
			continue;
		if (data->autocalibration > 0)
			data->autocalibration--;

		if (data->type == SNDDEV_PRELUDE || data->type == SNDDEV_PRELUDE1200) {
			check_prelude_interrupt(data);
		}

		if (data->snddev_active & STATUS_FIFO_RECORD) {

			capcnt[i]--;
			if (capcnt[i] <= 0) {
				sndboard_process_capture(data);
				capcnt[i] = data->record_event_time * 312 / (maxhpos * CYCLE_UNIT);
			}

			data->record_event_counter += maxhpos * CYCLE_UNIT;
			int bytes = data->record_event_counter / data->record_event_time;
			bytes &= ~3;
			if (bytes < 64 || data->capture_read_index == data->capture_write_index)
				return;

			int oldfifo = data->data_in_record_fifo;
			int oldbytes = bytes;
			int size = data->fifo_size - data->data_in_record_fifo;
			while (size > 0 && data->capture_read_index != data->capture_write_index && bytes > 0) {
				uae_u8 *fifop = &data->fifo[data->fifo_record_write_index];
				uae_u8 *bufp = &data->capture_buffer[data->capture_read_index];

				if (data->record_samplebits == 8) {
					fifop[0] = bufp[1];
					data->fifo_record_write_index++;
					data->data_in_record_fifo++;
					size--;
					bytes--;
					if (data->record_channels == 2) {
						fifop[1] = bufp[3];
						data->fifo_record_write_index++;
						data->data_in_record_fifo++;
						size--;
						bytes--;
					}
				} else if (data->record_samplebits == 16) {
					if (data->fifo_record_byteswap) {
						fifop[0] = bufp[0];
						fifop[1] = bufp[1];
					} else {
						fifop[0] = bufp[1];
						fifop[1] = bufp[0];
					}
					data->fifo_record_write_index += 2;
					data->data_in_record_fifo += 2;
					size -= 2;
					bytes -= 2;
					if (data->record_channels == 2) {
						if (data->fifo_record_byteswap) {
							fifop[2] = bufp[2];
							fifop[3] = bufp[3];
						} else {
							fifop[2] = bufp[3];
							fifop[3] = bufp[2];
						}
						data->fifo_record_write_index += 2;
						data->data_in_record_fifo += 2;
						size -= 2;
						bytes -= 2;
					}
				}

				data->fifo_record_write_index %= data->fifo_size;
				data->capture_read_index += 4;
				if (data->capture_read_index >= data->capture_buffer_size)
					data->capture_read_index = 0;
			}

			//write_log(_T("%d %d %d %d\n"), capture_read_index, capture_write_index, size, bytes);

			if (data->data_in_record_fifo > data->fifo_size / 2 && oldfifo <= data->fifo_size / 2) {
				data->fifo_half |= STATUS_FIFO_RECORD;
				//audio_state_sndboard(-1, -1);
			}
			data->record_event_counter -= oldbytes * data->record_event_time;
		}
	}
}

static void sndboard_vsync_toccata(struct snddev_data *data)
{
	if (data->snddev_active) {
		calculate_volume_toccata(data);
		audio_activate();
	}
}

static void toccata_put(struct snddev_data *data, uaecptr addr, uae_u8 v)
{
	int idx = data->ad1848_index & data->ad1848_index_mask;
	bool hit = true;

#if DEBUG_SNDDEV > 2
	if ((addr & data->codec_fifo_mask) != data->codec_fifo_addr)
		write_log(_T("SNDDEV PUT %08x %02x %d PC=%08X\n"), addr, v, idx, M68K_GETPC);
#endif

	if (idx >= 16 && !(data->ad1848_regs[12] & 0x40))
		return;

	if ((addr & data->codec_reg1_mask) == data->codec_reg1_addr) {
		// AD1848 register 0
		data->ad1848_index = v;
	} else 	if ((addr & data->codec_reg2_mask) == data->codec_reg2_addr) {
		// AD1848 register 1
		uae_u8 old = data->ad1848_regs[idx];
		
		switch(idx)
		{
			case 12:
			// revision (AD1848) or revision + mode (CS4231A)
			if (data->type == SNDDEV_TOCCATA) {
				v = 0x0a;
			} else if (data->type == SNDDEV_PRELUDE || data->type == SNDDEV_PRELUDE1200) {
				v &= 0xf0;
				v |= 0x8a;
			}
			break;
			case 8:
			if (data->ad1848_regs[12] & 0x40) {
				// CS4231A only big endian 16-bit data format?
				data->fifo_play_byteswap = (v >> 5) == 6;
			} else {
				data->fifo_play_byteswap = false;
				v &= ~0x80;
			}
			data->fifo_record_byteswap = data->fifo_record_byteswap;
			break;
		} 

		data->ad1848_regs[idx] = v;
#if DEBUG_SNDDEV > 0
		write_log(_T("SNDDEV PUT reg %d = %02x PC=%08x\n"), idx, v, M68K_GETPC);
#endif
		switch(idx)
		{
			case 9:
			if (v & 8) // ACI enabled
				data->autocalibration = 50;
			if (!(old & 3) && (v & 3))
				codec_start(data);
			else if ((old & 3) && !(v & 3))
				codec_stop(data);
			break;

			case 2:
			case 3:
			case 4:
			case 5:
			case 6:
			case 7:
				calculate_volume_toccata(data);
			break;

		}
	} else if ((addr & data->codec_fifo_mask) == data->codec_fifo_addr) {
#if DEBUG_SNDDEV_FIFO
		write_log(_T("SNDDEV FIFO PUT %02x (%d %d) PC=%08x\n"), v, data->fifo_write_index, data->data_in_fifo, M68K_GETPC);
#endif
		// FIFO input
		if ((data->snddev_status & STATUS_FIFO_PLAY) || (data->type == SNDDEV_PRELUDE || data->type == SNDDEV_PRELUDE1200)) {
			// 7202LA datasheet says fifo can't overflow
			if (data->data_in_fifo < data->fifo_size) {
				data->fifo[data->fifo_write_index] = v;
				data->fifo_write_index++;
				data->fifo_write_index %= data->fifo_size;
				data->data_in_fifo++;
			}
			if (data->type == SNDDEV_PRELUDE || data->type == SNDDEV_PRELUDE1200)
				check_prelude_interrupt(data);
		}
		data->snddev_irq &= ~STATUS_READ_PLAY_HALF;
		data->fifo_half &= ~STATUS_FIFO_PLAY;
	} else if (data->type == SNDDEV_TOCCATA) {
		if ((addr & 0x6800) == 0x0000) {
			// Board status
			if (v & STATUS_RESET) {
				codec_stop(data);
				data->snddev_status = 0;
				data->snddev_irq = 0;
				v = 0;
			}
			if (v == STATUS_ACTIVE) {
				data->fifo_write_index = 0;
				data->fifo_read_index = 0;
				data->data_in_fifo = 0;
				data->snddev_status = 0;
				data->snddev_irq = 0;
				data->fifo_half = 0;
			}
			data->snddev_status = v;
#if DEBUG_SNDDEV > 0
			write_log(_T("TOCCATA PUT STATUS %08x %02x %d PC=%08X\n"), addr, v, idx, M68K_GETPC);
#endif
		} else {
			hit = false;
		}
	} else if (data->type == SNDDEV_PRELUDE) {
		if ((addr & 0x00ff) == 0x8A) { // Reset FIFOs and CS4231A
			codec_stop(data);
			data->snddev_status = 0;
			data->snddev_irq = 0;
			data->fifo_write_index = 0;
			data->fifo_read_index = 0;
			data->data_in_fifo = 0;
			data->fifo_half = 0;
		} else if (addr < 0x90 || addr >= 0xc0) {
			hit = false;
		}
	} else if (data->type == SNDDEV_PRELUDE1200) {

		if ((addr & data->baseaddress_mask) == data->baseaddress_value) {
			if ((addr & 0x00ff) == 0x15) { // Reset FIFOs and CS4231A
				codec_stop(data);
				data->snddev_status = 0;
				data->snddev_irq = 0;
				data->fifo_write_index = 0;
				data->fifo_read_index = 0;
				data->data_in_fifo = 0;
				data->fifo_half = 0;
			} else if ((addr & 0x00ff) == 0x19) { // ?
				;
			} else {
				hit = false;
			}
		} else {
			hit = false;
		}
	} else {
		hit = false;
	}
	if (!hit) {
		write_log(_T("SNDDEV PUT UNKNOWN %08x PC=%08x\n"), addr, M68K_GETPC);
	}
}

static uae_u8 toccata_get(struct snddev_data *data, uaecptr addr)
{
	int idx = data->ad1848_index & data->ad1848_index_mask;
	uae_u8 v = 0;
	bool hit = true;

	if (idx >= 16 && !(data->ad1848_regs[12] & 0x40))
		return v;

	if ((addr & data->codec_reg1_mask) == data->codec_reg1_addr) {
		// AD1848 register 0
		v = data->ad1848_index;
	} else 	if ((addr & data->codec_reg2_mask) == data->codec_reg2_addr) {
		// AD1848 register 1
		v = data->ad1848_regs[idx];
#if DEBUG_SNDDEV > 0
		write_log(_T("SNDDEV GET reg %d = %02x PC=%08x\n"), idx, v, M68K_GETPC);
#endif
		switch (idx)
		{
			case 11:
			if (data->autocalibration > 10 && data->autocalibration < 30)
				data->ad1848_regs[11] |= 0x20;
			else
				data->ad1848_regs[11] &= ~0x20;
			break;
		}
	} else if ((addr & data->codec_fifo_mask) == data->codec_fifo_addr) {
		// FIFO output
		v = data->fifo[data->fifo_record_read_index];
#if DEBUG_SNDDEV_FIFO
		write_log(_T("SNDDEV FIFO GET %02x (%d %d) PC=%08x\n"), v, data->fifo_record_read_index, data->data_in_fifo, M68K_GETPC);
#endif
		if ((data->snddev_status & STATUS_FIFO_RECORD) || (data->type == SNDDEV_PRELUDE || data->type == SNDDEV_PRELUDE1200)) {
			if (data->data_in_record_fifo > 0) {
				data->fifo_record_read_index++;
				data->fifo_record_read_index %= data->fifo_size;
				data->data_in_record_fifo--;
			}
			if (data->type == SNDDEV_PRELUDE || data->type == SNDDEV_PRELUDE1200) {
				check_prelude_interrupt(data);
			}
		}
		data->snddev_irq &= ~STATUS_READ_RECORD_HALF;
		data->fifo_half &= ~STATUS_FIFO_RECORD;
	} else if (data->type == SNDDEV_TOCCATA) {
		if ((addr & 0x6800) == 0x0000) {
			// Board status
			v = STATUS_READ_INTREQ; // active low
			if (data->snddev_irq) {
				v &= ~STATUS_READ_INTREQ;
				v |= data->snddev_irq;
				data->snddev_irq = 0;
			}
#if DEBUG_SNDDEV > 0
			write_log(_T("TOCCATA GET STATUS %08x %02x %d PC=%08X\n"), addr, v, idx, M68K_GETPC);
#endif
		} else {
			hit = false;
		}
	} else if (data->type == SNDDEV_PRELUDE) {

		if ((addr & 0x00ff) == 0x8e) { // DMA busy flag
			v = 0xff;
		} else if ((addr & 0x00ff) == 0x8c) { // FIFO Status
			if (data->data_in_fifo >= data->fifo_size / 2)
				v |= 0x80;
			if (!data->data_in_fifo)
				v |= 0x40;
			if (data->data_in_record_fifo >= data->fifo_size / 2)
				v |= 0x20;
			if (data->data_in_record_fifo >= data->fifo_size)
				v |= 0x10;
			v ^= 0xff;
		} else if ((addr >= 0x80 && addr <= 0x90) || addr >= 0xc0) {
			hit = false;
		}

	} else if (data->type == SNDDEV_PRELUDE1200) {

		if ((addr & data->baseaddress_mask) == data->baseaddress_value) {
			if ((addr & 0x00ff) == 0x15) { // FIFO Status
				if (data->data_in_fifo >= data->fifo_size / 2)
					v |= 0x08;
				if (!data->data_in_fifo)
					v |= 0x04;
				if (data->data_in_record_fifo >= data->fifo_size / 2)
					v |= 0x02;
				if (data->data_in_record_fifo >= data->fifo_size)
					v |= 0x01;
				v ^= 0xff;
			} else if ((addr & 0x00ff) == 0x1d) { // id?
				v = 0x05;
			} else if ((addr & 0x00ff) == 0x19) { // ?
				v = 0x00;
			} else {
				hit = false;
			}
		} else {
			hit = false;
		}

	} else {
		hit = false;
	}
	if (!hit) {
		write_log(_T("SNDDEV GET UNKNOWN %08x PC=%08x\n"), addr, M68K_GETPC);
	}

#if DEBUG_SNDDEV > 2
	write_log(_T("SNDDEV GET %08x %02x %d PC=%08X\n"), addr, v, idx, M68K_GETPC);
#endif
	return v;
}

static struct snddev_data *getsnddev(uaecptr addr)
{
	for (int i = 0; i < MAX_SNDDEVS; i++) {
		struct snddev_data *data = &snddev[i];
		if (data->bank) {
			if (!data->configured)
				return data;
			if (data->baseaddress == (addr & 0xffff0000))
				return data;
		}
	}
	return NULL;
}

static void REGPARAM2 toccata_bput(uaecptr addr, uae_u32 b)
{
	addr &= 0xffffff;
	struct snddev_data *data = getsnddev(addr);
	if (!data)
		return;
	b &= 0xff;
	addr &= BOARD_MASK;
	if (!data->configured) {
		switch (addr)
		{
			case 0x48:
			map_banks_z2(&toccata_bank, expamem_board_pointer >> 16, BOARD_SIZE >> 16);
			data->configured = 1;
			data->baseaddress = expamem_board_pointer;
			expamem_next(&toccata_bank, NULL);
			break;
			case 0x4c:
			data->configured = -1;
			expamem_shutup(&toccata_bank);
			break;
		}
		return;
	}
	if (data->configured > 0)
		toccata_put(data, addr, b);
}

static void REGPARAM2 toccata_wput(uaecptr addr, uae_u32 b)
{
	toccata_bput(addr + 0, b >> 8);
	toccata_bput(addr + 1, b >> 0);
}

static void REGPARAM2 toccata_lput(uaecptr addr, uae_u32 b)
{
	toccata_bput(addr + 0, b >> 24);
	toccata_bput(addr + 1, b >> 16);
	toccata_bput(addr + 2, b >>  8);
	toccata_bput(addr + 3, b >>  0);
}

static uae_u32 REGPARAM2 toccata_bget(uaecptr addr)
{
	uae_u8 v = 0;
	addr &= 0xffffff;
	struct snddev_data *data = getsnddev(addr);
	if (!data)
		return v;
	addr &= BOARD_MASK;
	if (!data->configured) {
		if (addr >= sizeof data->acmemory)
			return 0;
		return data->acmemory[addr];
	}
	if (data->configured > 0)
		v = toccata_get(data, addr);
	return v;
}
static uae_u32 REGPARAM2 toccata_wget(uaecptr addr)
{
	uae_u16 v;
	v = toccata_bget(addr) << 8;
	v |= toccata_bget(addr + 1) << 0;
	return v;
}
static uae_u32 REGPARAM2 toccata_lget(uaecptr addr)
{
	uae_u32 v;
	v = toccata_bget(addr) << 24;
	v |= toccata_bget(addr + 1) << 16;
	v |= toccata_bget(addr + 2) << 8;
	v |= toccata_bget(addr + 3) << 0;
	return v;
}

addrbank toccata_bank = {
	toccata_lget, toccata_wget, toccata_bget,
	toccata_lput, toccata_wput, toccata_bput,
	default_xlate, default_check, NULL, _T("*"), _T("Toccata"),
	dummy_lgeti, dummy_wgeti,
	ABFLAG_IO, S_READ, S_WRITE
};

addrbank prelude_bank = {
	toccata_lget, toccata_wget, toccata_bget,
	toccata_lput, toccata_wput, toccata_bput,
	default_xlate, default_check, NULL, _T("*"), _T("Prelude"),
	dummy_lgeti, dummy_wgeti,
	ABFLAG_IO, S_READ, S_WRITE
};

addrbank prelude1200_bank = {
	toccata_lget, toccata_wget, toccata_bget,
	toccata_lput, toccata_wput, toccata_bput,
	default_xlate, default_check, NULL, _T("*"), _T("Prelude1200"),
	dummy_lgeti, dummy_wgeti,
	ABFLAG_IO, S_READ, S_WRITE
};;

static void ad1848_init(struct snddev_data *data)
{
	memset(data->ad1848_regs, 0, sizeof data->ad1848_regs);
	data->ad1848_regs[2] = 0x80;
	data->ad1848_regs[3] = 0x80;
	data->ad1848_regs[4] = 0x80;
	data->ad1848_regs[5] = 0x80;
	data->ad1848_regs[6] = 0x80;
	data->ad1848_regs[7] = 0x80;
	data->ad1848_regs[9] = 0x10;
	data->ad1848_regs[12] = 0x0a;
	data->ad1848_regs[25] = 0xa0;
	data->ad1848_status = 0xcc;
	data->ad1848_index = 0x40;
	data->ad1848_index_mask = 15;
	data->fifo_play_byteswap = false;
	data->fifo_record_byteswap = false;
}

bool prelude1200_init(struct autoconfig_info *aci)
{
	struct snddev_data *data = &snddev[2];

	aci->addrbank = &prelude1200_bank;
	aci->start = 0xd80000;
	aci->size = 0x10000;
	if (aci->devnum > 0) {
		aci->start = 0xd80000 + (aci->devnum - 1) * 0x4000;
		aci->size = 0x4000;
	}
	device_add_reset(sndboard_reset);

	if (!aci->doinit)
		return true;

	snd_init();

	data->configured = 1;
	data->baseaddress = 0xd80000;
	data->baseaddress_mask = 0;
	data->baseaddress_value = 0;
	if (aci->devnum > 0) {
		data->baseaddress_mask = 0xc000;
		data->baseaddress_value = (aci->devnum - 1) * 0x4000;
	}
	data->type = SNDDEV_PRELUDE1200;
	data->fifo_size = 1024;
	data->codec_reg1_mask = 0x00ff;
	data->codec_reg1_addr = 0x0001;
	data->codec_reg2_mask = 0x00ff;
	data->codec_reg2_addr = 0x0005;
	data->codec_fifo_mask = 0x00ff;
	data->codec_fifo_addr = 0x0011;
	data->streamid = 0;
	memset(data->acmemory, 0xff, sizeof data->acmemory);
	data->rc = aci->rc;
	data->enabled = true;
	data->bank = &prelude1200_bank;
	mapped_malloc(data->bank);
	map_banks(data->bank, data->baseaddress >> 16, 1, 1);
	ad1848_init(data);
	data->ad1848_regs[12] = 0x8a;
	data->ad1848_index_mask = 31;
	calculate_volume_toccata(data);
	return true;
}

bool prelude_init(struct autoconfig_info *aci)
{
	const struct expansionromtype *ert = get_device_expansion_rom(ROMTYPE_PRELUDE);
	if (!ert)
		return false;

	aci->addrbank = &prelude_bank;
	device_add_reset(sndboard_reset);

	if (!aci->doinit) {
		aci->autoconfigp = ert->autoconfig;
		return true;
	}

	snd_init();

	struct snddev_data *data = &snddev[1];

	data->configured = 0;
	data->type = SNDDEV_PRELUDE;
	data->fifo_size = 1024;
	data->codec_reg1_mask = 0x00ff;
	data->codec_reg1_addr = 0x0080;
	data->codec_reg2_mask = 0x00ff;
	data->codec_reg2_addr = 0x0082;
	data->codec_fifo_mask = 0x00ff;
	data->codec_fifo_addr = 0x0088;
	data->streamid = 0;
	memset(data->acmemory, 0xff, sizeof data->acmemory);
	data->rc = aci->rc;
	data->enabled = true;
	for (int i = 0; i < 16; i++) {
		uae_u8 b = ert->autoconfig[i];
		ew(data->acmemory, i * 4, b);
	}
	data->bank = &prelude_bank;
	mapped_malloc(data->bank);
	ad1848_init(data);
	data->ad1848_regs[12] = 0x8a;
	data->ad1848_index_mask = 31;
	calculate_volume_toccata(data);
	return true;
}

bool toccata_init(struct autoconfig_info *aci)
{
	const struct expansionromtype *ert = get_device_expansion_rom(ROMTYPE_TOCCATA);
	if (!ert)
		return false;

	aci->addrbank = &toccata_bank;
	device_add_reset(sndboard_reset);

	if (!aci->doinit) {
		aci->autoconfigp = ert->autoconfig;
		return true;
	}

	snd_init();

	struct snddev_data *data = &snddev[0];

	data->configured = 0;
	data->type = SNDDEV_TOCCATA;
	data->fifo_size = 1024;
	data->codec_reg1_mask = 0x6801;
	data->codec_reg1_addr = 0x6001;
	data->codec_reg2_mask = 0x6801;
	data->codec_reg2_addr = 0x6801;
	data->codec_fifo_mask = 0x6800;
	data->codec_fifo_addr = 0x2000;
	data->streamid = 0;
	memset(data->acmemory, 0xff, sizeof data->acmemory);
	data->rc = aci->rc;
	data->enabled = true;
	for (int i = 0; i < 16; i++) {
		uae_u8 b = ert->autoconfig[i];
		ew(data->acmemory, i * 4, b);
	}
	data->bank = &toccata_bank;
	mapped_malloc(data->bank);
	ad1848_init(data);
	calculate_volume_toccata(data);
	return true;
}

static void sndboard_reset(int hardreset)
{
	for (int i = 0; i < MAX_SNDDEVS; i++) {
		struct snddev_data *data = &snddev[i];
		data->ch_sample[0] = 0;
		data->ch_sample[1] = 0;
		codec_stop(data);
		data->snddev_irq = 0;
		if (data->streamid > 0)
			audio_enable_stream(false, data->streamid, 0, NULL, data);
		data->streamid = 0;
		if (data->bank)
			mapped_free(data->bank);
		data->bank = NULL;
	}
	sndboard_rethink();
}

static void sndboard_free(void)
{
	sndboard_reset(1);
	for (int i = 0; i < MAX_SNDDEVS; i++) {
		struct snddev_data *data = &snddev[i];
		data->rc = NULL;
	}
}

struct fm801_data
{
	struct pci_board_state *pcibs;
	uaecptr play_dma[2], play_dma2[2];
	uae_u16 play_len, play_len2;
	uae_u16 play_control;
	uae_u16 interrupt_control;
	uae_u16 interrupt_status;
	int dmach;
	int freq;
	int bits;
	int ch;
	int bytesperframe;
	bool play_on;
	int left_volume, right_volume;
	int ch_sample[2];
	int event_time;
	int streamid;
};
static struct fm801_data fm801;
static bool fm801_active;
static const int fm801_freq[16] = { 5500, 8000, 9600, 11025, 16000, 19200, 22050, 32000, 38400, 44100, 48000 };

static void calculate_volume_fm801(void)
{
	struct fm801_data *data = &fm801;
	data->left_volume = (100 - currprefs.sound_volume_board) * 32768 / 100;
	data->right_volume = (100 - currprefs.sound_volume_board) * 32768 / 100;
}

static void sndboard_vsync_fm801(void)
{
	audio_activate();
	calculate_volume_fm801();
}

static void fm801_stop(struct fm801_data *data)
{
	write_log(_T("FM801 STOP\n"));
	data->play_on = false;
	int streamid = data->streamid;
	data->streamid = 0;
	audio_enable_stream(false, streamid, 0, NULL, NULL);
}

static void fm801_swap_buffers(struct fm801_data *data)
{
	data->dmach = data->dmach ? 0 : 1;
	data->play_dma2[data->dmach] = data->play_dma[data->dmach];
	data->play_len2 = data->play_len;
	// stop at the end of buffer
	if (!(data->play_control & 0x20) && !(data->play_control & 0x80))
		fm801_stop(data);
}

static void fm801_interrupt(struct fm801_data *data)
{
	if ((data->interrupt_status & 0x100) && !(data->interrupt_control & 1)) {
		data->pcibs->irq_callback(data->pcibs, true);
	} else {
		data->pcibs->irq_callback(data->pcibs, false);
	}
}

static bool audio_state_sndboard_fm801(int streamid, void *params)
{
	struct fm801_data *data = &fm801;

	if (!fm801_active)
		return false;
	if (data->streamid != streamid)
		return false;
	if (data->play_on) {
		uae_u8 sample[2 * 6] = { 0 };
		pci_read_dma(data->pcibs, data->play_dma2[data->dmach], sample, data->bytesperframe);
		for (int i = 0; i < data->ch; i++) {
			uae_s16 smp;
			int vol;
			if (data->bits == 8) {
				smp = (sample[i] << 8) | (sample[i]);
			} else {
				smp = (sample[i * 2 + 1] << 8) | sample[i * 2 + 0];
			}
			if (i == 0 || i == 4)
				vol = data->left_volume;
			else if (i == 1 || i == 5)
				vol = data->right_volume;
			else
				vol = (data->left_volume + data->right_volume) / 2;
			data->ch_sample[i] = smp * vol / 32768;
		}
		data->play_len2 -= data->bytesperframe;
		data->play_dma2[data->dmach] += data->bytesperframe;
		if (data->play_len2 == 0xffff) {
			fm801_swap_buffers(data);
			data->interrupt_status |= 0x100;
			fm801_interrupt(data);
		}
	}
	audio_state_stream_state(streamid, data->ch_sample, data->ch, data->event_time);
	return true;
}

static void fm801_hsync_handler(struct pci_board_state *pcibs)
{
}

static void fm801_play(struct fm801_data *data)
{
	uae_u16 control = data->play_control;
	int f = (control >> 8) & 15;
	data->freq = fm801_freq[f];
	if (!data->freq)
		data->freq = 44100;
	data->event_time = (int)(base_event_clock * CYCLE_UNIT / data->freq);
	data->bits = (control & 0x4000) ? 16 : 8;
	f = (control >> 12) & 3;
	switch (f)
	{
		case 0:
		data->ch = (control & 0x8000) ? 2 : 1;
		break;
		case 1:
		data->ch = 4;
		break;
		case 2:
		data->ch = 6;
		break;
		case 3:
		data->ch = 6;
		break;
	}
	data->bytesperframe = data->bits * data->ch / 8;
	data->play_on = true;

	data->dmach = 1;
	fm801_swap_buffers(data);

	calculate_volume_fm801();

	write_log(_T("FM801 PLAY: freq=%d ch=%d bits=%d\n"), data->freq, data->ch, data->bits);

	data->streamid = audio_enable_stream(true, -1, data->ch, audio_state_sndboard_fm801, NULL);
}

static void fm801_pause(struct fm801_data *data, bool pause)
{
	write_log(_T("FM801 PAUSED %d\n"), pause);
}

static void fm801_control(struct fm801_data *data, uae_u16 control)
{
	uae_u16 old_control = data->play_control;
	data->play_control = control;
	data->play_control &= ~(8 | 16);
	if ((data->play_control & 0x20) && !(old_control & 0x20)) {
		fm801_play(data);
	} else if (!(data->play_control & 0x20) && (old_control & 0x20)) {
		if (data->play_control & 0x80)
			fm801_stop(data);
	} else if (data->play_control & 0x20) {
		if ((data->play_control & 0x40) && !(old_control & 0x40)) {
			fm801_pause(data, true);
		} else if (!(data->play_control & 0x40) && (old_control & 0x40)) {
			fm801_pause(data, true);
		}
	}

}

static void REGPARAM2 fm801_bput(struct pci_board_state *pcibs, uaecptr addr, uae_u32 b)
{
	struct fm801_data *data = &fm801;
}
static void REGPARAM2 fm801_wput(struct pci_board_state *pcibs, uaecptr addr, uae_u32 b)
{
	struct fm801_data *data = &fm801;
	switch (addr)
	{
		case 0x08:
		fm801_control(data, b);
		break;
		case 0x0a:
		data->play_len = b;
		break;
		case 0x56:
		data->interrupt_control = b;
		fm801_interrupt(data);
		break;
		case 0x5a:
		data->interrupt_status &= ~b;
		fm801_interrupt(data);
		break;
	}
}
static void REGPARAM2 fm801_lput(struct pci_board_state *pcibs, uaecptr addr, uae_u32 b)
{
	struct fm801_data *data = &fm801;
	switch (addr)
	{
		case 0x0c:
		data->play_dma[0] = b;
		break;
		case 0x10:
		data->play_dma[1] = b;
		break;
	}
}
static uae_u32 REGPARAM2 fm801_bget(struct pci_board_state *pcibs, uaecptr addr)
{
	struct fm801_data *data = &fm801;
	uae_u32 v = 0;
	return v;
}
static uae_u32 REGPARAM2 fm801_wget(struct pci_board_state *pcibs, uaecptr addr)
{
	struct fm801_data *data = &fm801;
	uae_u32 v = 0;
	switch (addr) {
		case 0x08:
		v = data->play_control;
		break;
		case 0x0a:
		v = data->play_len2;
		break;
		case 0x56:
		v = data->interrupt_control;
		break;
		case 0x5a:
		v = data->interrupt_status;
		break;

	}
	return v;
}
static uae_u32 REGPARAM2 fm801_lget(struct pci_board_state *pcibs, uaecptr addr)
{
	struct fm801_data *data = &fm801;
	uae_u32 v = 0;
	switch(addr)
	{
		case 0x0c:
		v = data->play_dma2[data->dmach];
		break;
		break;
		case 0x10:
		v = data->play_dma2[data->dmach];
	}
	return v;
}

static void fm801_reset(struct pci_board_state *pcibs)
{
	struct fm801_data *data = &fm801;
	data->play_control = 0xca00;
	data->interrupt_control = 0x00df;
}

static void fm801_free(struct pci_board_state *pcibs)
{
	struct fm801_data *data = &fm801;
	fm801_active = false;
	fm801_stop(data);
}

static bool fm801_init(struct pci_board_state *pcibs, struct autoconfig_info *aci)
{
	struct fm801_data *data = &fm801;
	memset(data, 0, sizeof(struct fm801_data));
	data->pcibs = pcibs;
	fm801_active = true;
	return false;
}

static void fm801_config(struct pci_board_state *pcibs, uae_u8 *cfg)
{
	// Status register is read-only
	cfg[4] = 0x02;
	cfg[5] = 0x80;
	// Clear command register reserved
	cfg[6] &= 3;
}

static const struct pci_config fm801_pci_config =
{
	0x1319, 0x0801, 0, 0x280, 0xb2, 0x040100, 0x80, 0x1319, 0x1319, 1, 0x04, 0x28, { 128 | 1, 0, 0, 0, 0, 0, 0 }
};
static const struct pci_config fm801_pci_config_func1 =
{
	0x1319, 0x0802, 0, 0x280, 0xb2, 0x098000, 0x80, 0x1319, 0x1319, 0, 0x04, 0x28, { 16 | 1, 0, 0, 0, 0, 0, 0 }
};

const struct pci_board fm801_pci_board =
{
	_T("FM801"),
	&fm801_pci_config, fm801_init, fm801_free, fm801_reset, fm801_hsync_handler, fm801_config,
	{
		{ fm801_lget, fm801_wget, fm801_bget, fm801_lput, fm801_wput, fm801_bput },
		{ NULL },
		{ NULL },
		{ NULL },
		{ NULL },
		{ NULL },
		{ NULL },
		{ NULL }
	}
};

const struct pci_board fm801_pci_board_func1 =
{
	_T("FM801-2"),
	&fm801_pci_config_func1, NULL, NULL, NULL, NULL, fm801_config,
	{
		{ fm801_lget, fm801_wget, fm801_bget, fm801_lput, fm801_wput, fm801_bput },
		{ NULL },
		{ NULL },
		{ NULL },
		{ NULL },
		{ NULL },
		{ NULL },
		{ NULL }
	}
};

static void solo1_reset(struct pci_board_state *pcibs)
{
}

static void solo1_free(struct pci_board_state *pcibs)
{
}

static bool solo1_init(struct pci_board_state *pcibs, struct autoconfig_info *aci)
{
	return true;
}

static void solo1_sb_put(struct pci_board_state *pcibs, uaecptr addr, uae_u32 b)
{
}
static uae_u32 solo1_sb_get(struct pci_board_state *pcibs, uaecptr addr)
{
	uae_u32 v = 0;
	return v;
}

static void solo1_put(struct pci_board_state *pcibs, int bar, uaecptr addr, uae_u32 b)
{
	if (bar == 1)
		solo1_sb_put(pcibs, addr, b);
}
static uae_u32 solo1_get(struct pci_board_state *pcibs, int bar, uaecptr addr)
{
	uae_u32 v = 0;
	if (bar == 1)
		v = solo1_sb_get(pcibs, addr);
	return v;
}

static void REGPARAM2 solo1_bput(struct pci_board_state *pcibs, uaecptr addr, uae_u32 b)
{
	write_log(_T("SOLO1 BPUT %08x=%08x %d\n"), addr, b, pcibs->selected_bar);
	solo1_put(pcibs, pcibs->selected_bar, addr + 0, b >> 24);
	solo1_put(pcibs, pcibs->selected_bar, addr + 1, b >> 16);
	solo1_put(pcibs, pcibs->selected_bar, addr + 2, b >>  8);
	solo1_put(pcibs, pcibs->selected_bar, addr + 3, b >>  0);
}
static void REGPARAM2 solo1_wput(struct pci_board_state *pcibs, uaecptr addr, uae_u32 b)
{
	write_log(_T("SOLO1 WPUT %08x=%08x %d\n"), addr, b, pcibs->selected_bar);
	solo1_put(pcibs, pcibs->selected_bar, addr + 0, b >> 8);
	solo1_put(pcibs, pcibs->selected_bar, addr + 1, b >> 0);
}
static void REGPARAM2 solo1_lput(struct pci_board_state *pcibs, uaecptr addr, uae_u32 b)
{
	write_log(_T("SOLO1 LPUT %08x=%08x %d\n"), addr, b, pcibs->selected_bar);
	solo1_put(pcibs, pcibs->selected_bar, addr, b);
}
static uae_u32 REGPARAM2 solo1_bget(struct pci_board_state *pcibs, uaecptr addr)
{
	uae_u32 v = 0;
	v = solo1_get(pcibs, pcibs->selected_bar, addr);
	write_log(_T("SOLO1 BGET %08x %d\n"), addr, pcibs->selected_bar);
	return v;
}
static uae_u32 REGPARAM2 solo1_wget(struct pci_board_state *pcibs, uaecptr addr)
{
	uae_u32 v = 0;
	write_log(_T("SOLO1 WGET %08x %d\n"), addr, pcibs->selected_bar);
	return v;
}
static uae_u32 REGPARAM2 solo1_lget(struct pci_board_state *pcibs, uaecptr addr)
{
	uae_u32 v = 0;
	write_log(_T("SOLO1 LGET %08x %d\n"), addr, pcibs->selected_bar);
	return v;
}

static const struct pci_config solo1_pci_config =
{
	0x125d, 0x1969, 0, 0, 0, 0x040100, 0, 0x125d, 0x1818, 1, 2, 0x18, { 16 | 1, 16 | 1, 16 | 1, 4 | 1, 4 | 1, 0, 0 }
};
const struct pci_board solo1_pci_board =
{
	_T("SOLO1"),
	&solo1_pci_config, solo1_init, solo1_free, solo1_reset, NULL, NULL,
	{
		{ solo1_lget, solo1_wget, solo1_bget, solo1_lput, solo1_wput, solo1_bput },
		{ solo1_lget, solo1_wget, solo1_bget, solo1_lput, solo1_wput, solo1_bput },
		{ solo1_lget, solo1_wget, solo1_bget, solo1_lput, solo1_wput, solo1_bput },
		{ solo1_lget, solo1_wget, solo1_bget, solo1_lput, solo1_wput, solo1_bput },
		{ solo1_lget, solo1_wget, solo1_bget, solo1_lput, solo1_wput, solo1_bput },
		{ NULL },
		{ NULL },
		{ NULL }
	}
};

static SWVoiceOut *qemu_voice_out;

static bool audio_state_sndboard_qemu(int streamid, void *params)
{
	SWVoiceOut *out = (SWVoiceOut*)params;

	if (!out || !out->active)
		return false;
	if (streamid != out->streamid)
		return false;
	if (out->active) {
		uae_s16 l, r;
		if (out->samplebuf_index >= out->samplebuf_total) {
			int maxsize = sizeof(out->samplebuf);
			int size = 128 * out->bytesperframe;
			if (size > maxsize)
				size = maxsize;
			out->callback(out->opaque, size);
			out->samplebuf_index = 0;
		}
		uae_u8 *p = out->samplebuf + out->samplebuf_index;
		if (out->bits == 8) {
			if (out->ch == 1) {
				p[1] = p[0];
				p[2] = p[0];
				p[3] = p[0];
			} else {
				p[2] = p[1];
				p[3] = p[1];
				p[1] = p[0];
			}
		} else {
			if (out->ch == 1) {
				p[2] = p[0];
				p[3] = p[1];
			}
		}
		l = (p[1] << 8) | p[0];
		r = (p[3] << 8) | p[2];
		out->ch_sample[0] = l;
		out->ch_sample[1] = r;
		out->ch_sample[0] = out->ch_sample[0] * out->left_volume / 32768;
		out->ch_sample[1] = out->ch_sample[1] * out->right_volume / 32768;
		out->samplebuf_index += out->bytesperframe;
	}
	audio_state_stream_state(streamid, out->ch_sample, out->ch, out->event_time);
	return true;
}

static void calculate_volume_qemu(SWVoiceOut *out)
{
	if (!out)
		return;
	out->left_volume = (100 - currprefs.sound_volume_board) * 32768 / 100;
	out->right_volume = (100 - currprefs.sound_volume_board) * 32768 / 100;
}

static void calculate_volume_qemu_all(void)
{
	for (SWVoiceOut *out = qemu_voice_out; out; out = out->next) {
		calculate_volume_qemu(out);
	}
}

static bool qemu_voice_out_active(void)
{
	for (SWVoiceOut *out = qemu_voice_out; out; out = out->next) {
		if (out->active)
			return true;
	}
	return false;
}

void AUD_close_in(QEMUSoundCard *card, SWVoiceIn *sw)
{
}
int AUD_read(SWVoiceIn *sw, void *pcm_buf, int size)
{
	return 0;
}
int AUD_write(SWVoiceOut *sw, void *pcm_buf, int size)
{
	if (!sw || !pcm_buf || size <= 0)
		return 0;
	if (size > (int)sizeof(sw->samplebuf))
		size = sizeof(sw->samplebuf);
	memcpy(sw->samplebuf, pcm_buf, size);
	sw->samplebuf_total = size;
	return sw->samplebuf_total;
}
void AUD_set_active_out(SWVoiceOut *sw, int on)
{
	if (!sw)
		return;
	sw->active = on != 0;
	sw->event_time = (int)(base_event_clock * CYCLE_UNIT / sw->freq);
	sw->samplebuf_index = 0;
	sw->samplebuf_total = 0;
	calculate_volume_qemu(sw);
	audio_enable_stream(false, sw->streamid, 2, NULL, NULL);
	sw->streamid = 0;
	if (on) {
		sw->streamid = audio_enable_stream(true, -1, 2, audio_state_sndboard_qemu, sw);
	}
}
void AUD_set_active_in(SWVoiceIn *sw, int on)
{
}
int  AUD_is_active_in(SWVoiceIn *sw)
{
	return 0;
}
void AUD_close_out(QEMUSoundCard *card, SWVoiceOut *sw)
{
	if (sw) {
		SWVoiceOut **outp = &qemu_voice_out;
		while (*outp) {
			if (*outp == sw) {
				*outp = sw->next;
				break;
			}
			outp = &(*outp)->next;
		}
		audio_enable_stream(false, sw->streamid, 0, NULL, NULL);
		sw->streamid = 0;
		xfree(sw);
	}
}
SWVoiceIn *AUD_open_in(
	QEMUSoundCard *card,
	SWVoiceIn *sw,
	const char *name,
	void *callback_opaque,
	audio_callback_fn callback_fn,
struct audsettings *settings)
{
	return NULL;
}
SWVoiceOut *AUD_open_out(
	QEMUSoundCard *card,
	SWVoiceOut *sw,
	const char *name,
	void *callback_opaque,
	audio_callback_fn callback_fn,
	struct audsettings *settings)
{
	SWVoiceOut *out = sw;
	if (!sw) {
		out = xcalloc(SWVoiceOut, 1);
		out->next = qemu_voice_out;
		qemu_voice_out = out;
	}
	int bits = 8;

	if (settings->fmt >= AUD_FMT_U16)
		bits = 16;
	if (settings->fmt >= AUD_FMT_U32)
		bits = 32;

	out->callback = callback_fn;
	out->opaque = callback_opaque;
	out->bits = bits;
	out->freq = settings->freq;
	out->ch = settings->nchannels;
	out->fmt = settings->fmt;
	out->bytesperframe = out->ch * bits / 8;

	TCHAR *name2 = au(name);
	write_log(_T("QEMU AUDIO: freq=%d ch=%d bits=%d (fmt=%d) '%s'\n"), out->freq, out->ch, bits, settings->fmt, name2);
	xfree(name2);

	return out;
}

static void sndboard_vsync_qemu(void)
{
	audio_activate();
}

static void sndboard_vsync(void)
{
	if (snddev[0].snddev_active)
		sndboard_vsync_toccata(&snddev[0]);
	if (fm801_active)
		sndboard_vsync_fm801();
	if (qemu_voice_out_active())
		sndboard_vsync_qemu();
}

void sndboard_ext_volume(void)
{
	if (snddev[0].snddev_active)
		calculate_volume_toccata(&snddev[0]);
	if (fm801_active)
		calculate_volume_fm801();
	calculate_volume_qemu_all();
}

static void snd_init(void)
{
	device_add_hsync(sndboard_hsync);
	device_add_vsync_post(sndboard_vsync);
	device_add_rethink(sndboard_rethink);
	device_add_exit(sndboard_free, NULL);
	device_add_exit(uaesndboard_free, NULL);
}


#if defined(_WIN32) && !defined(AMIBERRY)

#include <mmdeviceapi.h>
#include <Audioclient.h>

#define REFTIMES_PER_SEC  10000000

static const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
static const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
static const IID IID_IAudioClient = __uuidof(IAudioClient);
static const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient);

#define EXIT_ON_ERROR(hres) if (FAILED(hres)) { goto Exit; }
#define SAFE_RELEASE(punk)  if ((punk) != NULL) { (punk)->Release(); (punk) = NULL; }

static IMMDeviceEnumerator *pEnumerator = NULL;
static IMMDevice *pDevice = NULL;
static IAudioClient *pAudioClient = NULL;
static IAudioCaptureClient *pCaptureClient = NULL;
static bool capture_started;

static uae_u8 *sndboard_get_buffer(int *frames)
{	
	HRESULT hr;
	UINT32 numFramesAvailable;
	BYTE *pData;
	DWORD flags = 0;

	*frames = -1;
	if (!capture_started)
		return NULL;
	hr = pCaptureClient->GetBuffer(&pData, &numFramesAvailable, &flags, NULL, NULL);
	if (FAILED(hr)) {
		write_log(_T("GetBuffer failed %08x\n"), hr);
		return NULL;
	}
	*frames = numFramesAvailable;
	return pData;
}

static void sndboard_release_buffer(uae_u8 *buffer, int frames)
{
	HRESULT hr;
	if (!capture_started || frames < 0)
		return;
	hr = pCaptureClient->ReleaseBuffer(frames);
	if (FAILED(hr)) {
		write_log(_T("ReleaseBuffer failed %08x\n"), hr);
	}
}

static void sndboard_free_capture(int owner)
{
	if (capture_owner != owner)
		return;
	if (capture_started)
		pAudioClient->Stop();
	capture_started = false;
	capture_owner = SNDBOARD_CAPTURE_OWNER_NONE;
	SAFE_RELEASE(pEnumerator)
	SAFE_RELEASE(pDevice)
	SAFE_RELEASE(pAudioClient)
	SAFE_RELEASE(pCaptureClient)
}

static bool sndboard_init_capture(int freq, int owner)
{
	HRESULT hr;
	WAVEFORMATEX wavfmtsrc;
	WAVEFORMATEX *wavfmt2;
	WAVEFORMATEX *wavfmt;
	bool init = false;

	if (capture_started && capture_owner != owner)
		return false;
	if (capture_started)
		sndboard_free_capture(owner);
	capture_owner = owner;
	wavfmt2 = NULL;

	hr = CoCreateInstance(
		CLSID_MMDeviceEnumerator, NULL,
		CLSCTX_ALL, IID_IMMDeviceEnumerator,
		(void**)&pEnumerator);
	EXIT_ON_ERROR(hr)

	hr = pEnumerator->GetDefaultAudioEndpoint(eCapture, eConsole, &pDevice);
	EXIT_ON_ERROR(hr)

	hr = pDevice->Activate(IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&pAudioClient);
	EXIT_ON_ERROR(hr)

	memset (&wavfmtsrc, 0, sizeof wavfmtsrc);
	wavfmtsrc.nChannels = 2;
	wavfmtsrc.nSamplesPerSec = freq;
	wavfmtsrc.wBitsPerSample = 16;
	wavfmtsrc.wFormatTag = WAVE_FORMAT_PCM;
	wavfmtsrc.cbSize = 0;
	wavfmtsrc.nBlockAlign = wavfmtsrc.wBitsPerSample / 8 * wavfmtsrc.nChannels;
	wavfmtsrc.nAvgBytesPerSec = wavfmtsrc.nBlockAlign * wavfmtsrc.nSamplesPerSec;

	AUDCLNT_SHAREMODE exc;
	for (int mode = 0; mode < 2; mode++) {
		exc = mode == 0 ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED;
		int time = mode == 0 ? 0 : REFTIMES_PER_SEC / 50;

		wavfmt = &wavfmtsrc;
		hr = pAudioClient->IsFormatSupported(exc, &wavfmtsrc, &wavfmt2);
		if (SUCCEEDED(hr)) {
			hr = pAudioClient->Initialize(exc, 0, time, 0, wavfmt, NULL);
			if (SUCCEEDED(hr)) {
				init = true;
				break;
			}
		}
	
		if (hr == S_FALSE && wavfmt2) {
			wavfmt = wavfmt2;
			hr = pAudioClient->Initialize(exc, 0, time, 0, wavfmt, NULL);
			if (SUCCEEDED(hr)) {
				init = true;
				break;
			}
		}
	}

	if (!init) {
		write_log(_T("sndboard capture init, freq=%d, failed\n"), freq);
		goto Exit;
	}


	hr = pAudioClient->GetService(IID_IAudioCaptureClient, (void**)&pCaptureClient);
	EXIT_ON_ERROR(hr)
		
	hr = pAudioClient->Start();
	EXIT_ON_ERROR(hr)
	capture_started = true;

	CoTaskMemFree(wavfmt2);

	write_log(_T("sndboard capture started: freq=%d mode=%s\n"), freq, exc == AUDCLNT_SHAREMODE_EXCLUSIVE ? _T("exclusive") : _T("shared"));

	return true;
	Exit:;
	CoTaskMemFree(wavfmt2);
	write_log(_T("sndboard capture init failed %08x\n"), hr);
	sndboard_free_capture(owner);
	return false;
}

#elif defined(AMIBERRY) && !defined(LIBRETRO)

static SDL_AudioStream *capture_stream;
static uae_u8 *capture_buffer;
static int capture_buffer_size;
static bool capture_started;
static int capture_read_count;
static bool capture_nonzero_seen;

static uae_u8 *sndboard_get_buffer(int *frames)
{
	*frames = -1;
	if (!capture_started || !capture_stream)
		return NULL;
	int available = SDL_GetAudioStreamAvailable(capture_stream);
	if (available < 0) {
		write_log(_T("sndboard SDL capture available failed: %s\n"), SDL_GetError());
		return NULL;
	}
	int bytes = available & ~3;
	if (bytes <= 0) {
		*frames = 0;
		return NULL;
	}
	if (bytes > capture_buffer_size)
		bytes = capture_buffer_size & ~3;
	int bytes_read = SDL_GetAudioStreamData(capture_stream, capture_buffer, bytes);
	if (bytes_read < 0) {
		write_log(_T("sndboard SDL capture read failed: %s\n"), SDL_GetError());
		return NULL;
	}
	bytes_read &= ~3;
	*frames = bytes_read / 4;
	if (bytes_read > 0) {
		bool has_signal = false;
		for (int i = 0; i < bytes_read; i++) {
			if (capture_buffer[i] != 0) {
				has_signal = true;
				break;
			}
		}
		capture_read_count++;
		if (has_signal && !capture_nonzero_seen) {
			capture_nonzero_seen = true;
			write_log(_T("sndboard SDL capture received first non-silent buffer after %d reads\n"), capture_read_count);
		} else if (!has_signal && capture_read_count <= 5) {
			write_log(_T("sndboard SDL capture read %d bytes of silence from host input\n"), bytes_read);
		}
	}
	return *frames > 0 ? capture_buffer : NULL;
}

static void sndboard_release_buffer(uae_u8 *buffer, int frames)
{
}

static void sndboard_free_capture(int owner)
{
	if (capture_owner != owner)
		return;
	if (capture_stream) {
		SDL_PauseAudioStreamDevice(capture_stream);
		SDL_DestroyAudioStream(capture_stream);
		capture_stream = NULL;
	}
	xfree(capture_buffer);
	capture_buffer = NULL;
	capture_buffer_size = 0;
	capture_started = false;
	capture_read_count = 0;
	capture_nonzero_seen = false;
	capture_owner = SNDBOARD_CAPTURE_OWNER_NONE;
}

static bool sndboard_init_capture(int freq, int owner)
{
	if (capture_started && capture_owner != owner)
		return false;
	if (capture_started)
		sndboard_free_capture(owner);
	capture_owner = owner;

	SDL_AudioSpec spec;
	spec.format = SDL_AUDIO_S16LE;
	spec.channels = 2;
	spec.freq = freq;

	enumerate_sound_devices();

	const int recordcard = currprefs.samplersoundcard;
	const bool use_default_device = recordcard < 0 || recordcard >= MAX_SOUND_DEVICES
		|| record_devices[recordcard] == NULL;
	const SDL_AudioDeviceID device_id = use_default_device
		? SDL_AUDIO_DEVICE_DEFAULT_RECORDING
		: (SDL_AudioDeviceID)record_devices[recordcard]->id;

	capture_stream = SDL_OpenAudioDeviceStream(device_id, &spec, NULL, NULL);
	if (!capture_stream && !use_default_device) {
		write_log(_T("sndboard SDL capture selected device failed, retrying with default: %s\n"), SDL_GetError());
		capture_stream = SDL_OpenAudioDeviceStream(SDL_AUDIO_DEVICE_DEFAULT_RECORDING, &spec, NULL, NULL);
	}
	if (!capture_stream) {
		write_log(_T("sndboard SDL capture init failed: %s\n"), SDL_GetError());
		sndboard_free_capture(owner);
		return false;
	}

	capture_buffer_size = ((freq + 49) / 50) * 4;
	if (capture_buffer_size < 4096)
		capture_buffer_size = 4096;
	capture_buffer = xcalloc(uae_u8, capture_buffer_size);

	SDL_ResumeAudioStreamDevice(capture_stream);
	capture_started = true;
	capture_read_count = 0;
	capture_nonzero_seen = false;
	write_log(_T("sndboard SDL capture started: freq=%d device=%s\n"),
		freq, use_default_device ? _T("default") : record_devices[recordcard]->name);
	return true;
}

#else

// Stub implementations for platforms without WASAPI capture
static uae_u8 *sndboard_get_buffer(int *frames)
{
	*frames = -1;
	return NULL;
}
static void sndboard_release_buffer(uae_u8 *buffer, int frames)
{
}
static void sndboard_free_capture(int owner)
{
}
static bool sndboard_init_capture(int freq, int owner)
{
	return false;
}

#endif
