mirror of
https://github.com/LIV2/WinUAE.git
synced 2025-12-06 00:12:52 +00:00
3154 lines
84 KiB
C++
3154 lines
84 KiB
C++
/*
|
|
* 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 "uae.h"
|
|
#include "memory.h"
|
|
#include "newcpu.h"
|
|
#include "debug.h"
|
|
#include "custom.h"
|
|
#include "sndboard.h"
|
|
#include "audio.h"
|
|
#include "autoconf.h"
|
|
#include "pci_hw.h"
|
|
#include "qemuvga/qemuaudio.h"
|
|
#include "rommgr.h"
|
|
#include "devices.h"
|
|
|
|
#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(void);
|
|
static bool sndboard_init_capture(int freq);
|
|
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
|
|
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];
|
|
};
|
|
|
|
static struct uaesndboard_data uaesndboard[MAX_DUPLICATE_SOUND_BOARDS];
|
|
|
|
/*
|
|
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). Not yet implemented Must be zero.
|
|
38.W front to back panning (-32767 to 32767). Not yet implemented. 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)
|
|
|
|
$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);
|
|
static 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 irq = false;
|
|
for (int j = 0; j < MAX_DUPLICATE_SOUND_BOARDS; j++) {
|
|
struct uaesndboard_data *data = &uaesndboard[j];
|
|
if (data->enabled) {
|
|
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;
|
|
irq = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!irq) {
|
|
data->streamintreqmask &= ~(1 << i);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 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
|
|
|
|
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) {
|
|
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;
|
|
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 bool uaesnd_validate(struct uaesndboard_data *data, struct uaesndboard_stream *s)
|
|
{
|
|
int samplebits = (s->bitmode & 1) ? 16 : 8;
|
|
|
|
s->framesize = samplebits * s->ch / 8;
|
|
|
|
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;
|
|
}
|
|
}
|
|
if (s->panx || s->pany) {
|
|
write_log(_T("UAESND: Panning values must be zeros\n"));
|
|
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_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);
|
|
}
|
|
return uaesnd_validate(data, s);
|
|
}
|
|
|
|
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);
|
|
return false;
|
|
}
|
|
|
|
s->setaddr = addr;
|
|
uaesnd_load(s, addr);
|
|
s->first = 10;
|
|
s->repeating = false;
|
|
|
|
return uaesnd_validate(data, s);
|
|
}
|
|
|
|
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)
|
|
{
|
|
uae_u8 enablemask = s->masterintenamask;
|
|
uae_u8 intenamask = s->intenamask | 0x10 | 0x20 | 0x40;
|
|
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;
|
|
s->sample[i] = samples * ((s->volume + 1) / 2 + (data->volume[i] + 1) / 2) / 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 = NULL;
|
|
}
|
|
}
|
|
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 > highestch)
|
|
highestch = i;
|
|
}
|
|
}
|
|
} 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;
|
|
}
|
|
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 (addr >= 0x80 && addr < 0xe0) {
|
|
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 (addr >= 0x80 && addr < 0xe0 - 1) {
|
|
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 (addr >= 0x80 && addr < 0xe0 - 3) {
|
|
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 (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 (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 (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;
|
|
}
|
|
}
|
|
}
|
|
|
|
static 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 struct addrbank_sub uaesndz2_sub_banks[] = {
|
|
{ &uaesndboard_sub_bank_z2, 0x0000 },
|
|
{ &uaesndboard_ram_bank, 0x8000 },
|
|
{ NULL }
|
|
};
|
|
|
|
static 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
|
|
};
|
|
|
|
static 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);
|
|
const struct expansionromtype *ert = get_device_expansion_rom(z == 3 ? ROMTYPE_UAESNDZ3 : ROMTYPE_UAESNDZ2);
|
|
if (!ert)
|
|
return false;
|
|
data->streammask = 0;
|
|
data->volume[0] = 32768;
|
|
data->volume[1] = 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);
|
|
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];
|
|
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];
|
|
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);
|
|
sndboard_init_capture(data->freq_adjusted);
|
|
}
|
|
}
|
|
|
|
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();
|
|
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)
|
|
{
|
|
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;
|
|
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;
|
|
}
|
|
|
|
static 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
|
|
};
|
|
|
|
static 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
|
|
};
|
|
|
|
static 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 = qemu_voice_out;
|
|
|
|
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(void)
|
|
{
|
|
SWVoiceOut *out = qemu_voice_out;
|
|
if (!out)
|
|
return;
|
|
out->left_volume = (100 - currprefs.sound_volume_board) * 32768 / 100;
|
|
out->right_volume = (100 - currprefs.sound_volume_board) * 32768 / 100;
|
|
}
|
|
|
|
void AUD_close_in(QEMUSoundCard *card, SWVoiceIn *sw)
|
|
{
|
|
}
|
|
int AUD_read(SWVoiceIn *sw, void *pcm_buf, int size)
|
|
{
|
|
return size;
|
|
}
|
|
int AUD_write(SWVoiceOut *sw, void *pcm_buf, int size)
|
|
{
|
|
memcpy(sw->samplebuf, pcm_buf, size);
|
|
sw->samplebuf_total = size;
|
|
return sw->samplebuf_total;
|
|
}
|
|
void AUD_set_active_out(SWVoiceOut *sw, int on)
|
|
{
|
|
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();
|
|
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, NULL);
|
|
}
|
|
}
|
|
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)
|
|
{
|
|
qemu_voice_out = NULL;
|
|
if (sw) {
|
|
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);
|
|
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);
|
|
|
|
qemu_voice_out = out;
|
|
|
|
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 && 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();
|
|
if (qemu_voice_out && qemu_voice_out->active)
|
|
calculate_volume_qemu();
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
|
|
#ifdef _WIN32
|
|
|
|
#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(void)
|
|
{
|
|
if (capture_started)
|
|
pAudioClient->Stop();
|
|
capture_started = false;
|
|
SAFE_RELEASE(pEnumerator)
|
|
SAFE_RELEASE(pDevice)
|
|
SAFE_RELEASE(pAudioClient)
|
|
SAFE_RELEASE(pCaptureClient)
|
|
}
|
|
|
|
static bool sndboard_init_capture(int freq)
|
|
{
|
|
HRESULT hr;
|
|
WAVEFORMATEX wavfmtsrc;
|
|
WAVEFORMATEX *wavfmt2;
|
|
WAVEFORMATEX *wavfmt;
|
|
bool init = false;
|
|
|
|
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();
|
|
return false;
|
|
}
|
|
|
|
#endif
|