// license: BSD-3-Clause // copyright-holders: Aaron Giles, Dirk Best /****************************************************************************** MOS Technology 8364 "Paula" TODO: - Inherit FDC, serial and irq controller to here; - Move Agnus "location" logic from here; - low-pass filter; - convert volume values to non-linear dB scale (cfr. ) - Verify ADKCON modulation; - Verify manual mode: \- AGA roadkill during gameplay, which also has very long period setups, extremely aliased; - When a DMA stop occurs, is the correlated channel playback stopped at the end of the current cycle or as soon as possible like current implementation? ******************************************************************************/ #include "emu.h" #include "8364_paula.h" #define LIVE_AUDIO_VIEW 0 //#define VERBOSE 1 #include "logmacro.h" #include //************************************************************************** // DEVICE DEFINITIONS //************************************************************************** DEFINE_DEVICE_TYPE(PAULA_8364, paula_8364_device, "paula_8364", "MOS 8364 \"Paula\"") //************************************************************************* // LIVE DEVICE //************************************************************************** //------------------------------------------------- // paula_8364_device - constructor //------------------------------------------------- paula_8364_device::paula_8364_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) : device_t(mconfig, PAULA_8364, tag, owner, clock) , device_sound_interface(mconfig, *this) , m_chipmem_r(*this, 0) , m_int_w(*this) , m_stream(nullptr) { } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void paula_8364_device::device_start() { // initialize channels for (int i = 0; i < 4; i++) { m_channel[i].index = i; m_channel[i].irq_timer = timer_alloc(FUNC(paula_8364_device::signal_irq), this); } // create the stream m_stream = stream_alloc(0, 4, clock() / CLOCK_DIVIDER); } void paula_8364_device::device_reset() { m_dma_master_enable = false; for (auto &chan : m_channel) { chan.loc = 0; chan.len = 0; chan.per = 0; chan.vol = 0; chan.curticks = 0; chan.manualmode = false; chan.curlocation = 0; chan.curlength = 0; chan.dma_enabled = false; chan.atper = false; chan.atvol = false; } } //------------------------------------------------- // update - stream updater //------------------------------------------------- void paula_8364_device::update() { m_stream->update(); } //************************************************************************* // IMPLEMENTATION //************************************************************************** template void paula_8364_device::audio_channel_map(address_map &map) { // TODO: location addresses belongs to Agnus map(0x00, 0x01).w(FUNC(paula_8364_device::audxlch_w)); map(0x02, 0x03).w(FUNC(paula_8364_device::audxlcl_w)); map(0x04, 0x05).w(FUNC(paula_8364_device::audxlen_w)); map(0x06, 0x07).w(FUNC(paula_8364_device::audxper_w)); map(0x08, 0x09).w(FUNC(paula_8364_device::audxvol_w)); map(0x0a, 0x0b).w(FUNC(paula_8364_device::audxdat_w)); } // Instantiate channel maps template void paula_8364_device::audio_channel_map<0>(address_map &map); template void paula_8364_device::audio_channel_map<1>(address_map &map); template void paula_8364_device::audio_channel_map<2>(address_map &map); template void paula_8364_device::audio_channel_map<3>(address_map &map); template void paula_8364_device::audxlch_w(u16 data) { m_stream->update(); // TODO: chipmem mask m_channel[ch].loc = (m_channel[ch].loc & 0x0000ffff) | ((data & 0x001f) << 16); } template void paula_8364_device::audxlcl_w(u16 data) { m_stream->update(); m_channel[ch].loc = (m_channel[ch].loc & 0xffff0000) | ((data & 0xfffe) << 0); } template void paula_8364_device::audxlen_w(u16 data) { m_stream->update(); m_channel[ch].len = data; } template void paula_8364_device::audxper_w(u16 data) { m_stream->update(); m_channel[ch].per = data; } template void paula_8364_device::audxvol_w(u16 data) { m_stream->update(); m_channel[ch].vol = data & 0x7f; } template void paula_8364_device::audxdat_w(u16 data) { m_stream->update(); m_channel[ch].dat = data; m_channel[ch].manualmode = true; } void paula_8364_device::dmacon_set(u16 data) { m_stream->update(); m_dma_master_enable = bool(BIT(data, 9)); // update the DMA latches on each channel and reload if fresh // This holds true particularly for Ocean games (bchvolly, lostpatr, pang) and waylildr: // they sets a DMA length for a channel then enable DMA finally resets that length to 1 // after a short delay loop. for (int channum = 0; channum < 4; channum++) { audio_channel *chan = &m_channel[channum]; if (!chan->dma_enabled && ((data >> channum) & 1)) dma_reload(chan, true); chan->dma_enabled = bool(BIT(data, channum)); } } void paula_8364_device::adkcon_set(u16 data) { m_stream->update(); for (int channum = 0; channum < 4; channum++) { audio_channel *chan = &m_channel[channum]; chan->atper = bool(BIT(data, channum + 4)); chan->atvol = bool(BIT(data, channum)); } } //------------------------------------------------- // signal_irq - irq signaling //------------------------------------------------- TIMER_CALLBACK_MEMBER( paula_8364_device::signal_irq ) { m_int_w(param, 1); } //------------------------------------------------- // dma_reload //------------------------------------------------- void paula_8364_device::dma_reload(audio_channel *chan, bool startup) { chan->curlocation = chan->loc; // TODO: Unconfirmed, assume max size if length is 0. // cfr. wrestlmn and videokid, where they pratically never get even close to buffer exhaustion. chan->curlength = chan->len == 0 ? 0x10000 : chan->len; // TODO: on startup=false irq should be delayed two cycles if (startup) chan->irq_timer->adjust(attotime::from_hz(15750), chan->index); // clock() / 227 else signal_irq(chan->index); LOG("dma_reload(%d): offs=%06X len=%04X\n", chan->index, chan->curlocation, chan->curlength); } std::string paula_8364_device::print_audio_state() { std::ostringstream outbuffer; util::stream_format(outbuffer, "DMA master %d\n", m_dma_master_enable); for (auto &chan : m_channel) { util::stream_format(outbuffer, "%d DMA (%d) ADK (%d%d) REGS: %06x %04x %03x %02x %d LIVE: %06x %04x %d\n" , chan.index , chan.dma_enabled , chan.atper , chan.atvol , chan.loc , chan.len , chan.per , chan.vol , chan.manualmode , chan.curlocation , chan.curlength , chan.dma_enabled ); } return outbuffer.str(); } //------------------------------------------------- // sound_stream_update - handle a stream update //------------------------------------------------- void paula_8364_device::sound_stream_update(sound_stream &stream, std::vector const &inputs, std::vector &outputs) { int channum, sampoffs = 0; // if all DMA off, disable all channels if (m_dma_master_enable == false) { m_channel[0].dma_enabled = m_channel[1].dma_enabled = m_channel[2].dma_enabled = m_channel[3].dma_enabled = false; // clear the sample data to 0 for (channum = 0; channum < 4; channum++) outputs[channum].fill(0); return; } int samples = outputs[0].samples() * CLOCK_DIVIDER; if (LIVE_AUDIO_VIEW) popmessage(print_audio_state()); // loop until done while (samples > 0) { int nextper, nextvol; int ticks = samples; // determine the number of ticks we can do in this chunk if (ticks > m_channel[0].curticks) ticks = m_channel[0].curticks; if (ticks > m_channel[1].curticks) ticks = m_channel[1].curticks; if (ticks > m_channel[2].curticks) ticks = m_channel[2].curticks; if (ticks > m_channel[3].curticks) ticks = m_channel[3].curticks; // loop over channels nextper = nextvol = -1; for (channum = 0; channum < 4; channum++) { audio_channel *chan = &m_channel[channum]; int volume = (nextvol == -1) ? chan->vol : nextvol; int period = (nextper == -1) ? chan->per : nextper; s32 sample; int i; // normalize the volume value // FIXME: definitely not linear volume = (volume & 0x40) ? 64 : (volume & 0x3f); volume *= 4; // are we modulating the period of the next channel? if (chan->atper) { nextper = chan->dat; nextvol = -1; sample = 0; } // are we modulating the volume of the next channel? else if (chan->atvol) { nextper = -1; nextvol = chan->dat; sample = 0; } // otherwise, we are generating data else { nextper = nextvol = -1; sample = chan->latched * volume; } // fill the buffer with the sample for (i = 0; i < ticks; i += CLOCK_DIVIDER) outputs[channum].put_int((sampoffs + i) / CLOCK_DIVIDER, sample, 32768); // account for the ticks; if we hit 0, advance chan->curticks -= ticks; if (chan->curticks == 0) { // reset the clock and ensure we're above the minimum ticks chan->curticks = period; if (chan->curticks < 124) chan->curticks = 124; // move forward one byte; if we move to an even byte, fetch new if (chan->dma_enabled || chan->manualmode) chan->curlocation++; if (chan->dma_enabled && !(chan->curlocation & 1)) { chan->dat = m_chipmem_r(chan->curlocation); if (chan->curlength != 0) chan->curlength--; // if we run out of data, reload the dma and signal an IRQ, // gpmaster/asparmgp definitely expects this // (uses channel 3 as a sequencer, changing the start address on the fly) if (chan->curlength == 0) { dma_reload(chan, false); // reload the data pointer, otherwise aliasing / buzzing outside the given buffer will be heard // For example: Xenon 2 sets up location=0x63298 length=0x20 // for silencing channels on-the-fly without relying on irqs. // Without this the location will read at 0x632d8 (data=0x7a7d), causing annoying buzzing. chan->dat = m_chipmem_r(chan->curlocation); } } // latch the next byte of the sample if (!(chan->curlocation & 1)) chan->latched = chan->dat >> 8; else chan->latched = chan->dat >> 0; // if we're in manual mode, signal an interrupt once we latch the low byte if (!chan->dma_enabled && chan->manualmode && (chan->curlocation & 1)) { signal_irq(channum); chan->manualmode = false; } } } // bump ourselves forward by the number of ticks sampoffs += ticks; samples -= ticks; } }