// license: BSD-3-Clause // copyright-holders: Aaron Giles, Dirk Best /*************************************************************************** Commodore 8364 "Paula" ***************************************************************************/ #include "emu.h" #include "8364_paula.h" //************************************************************************** // CONSTANTS / MACROS //************************************************************************** #define VERBOSE 0 #define LOG(x) do { if (VERBOSE) logerror x; } while (0) //************************************************************************** // DEVICE DEFINITIONS //************************************************************************** const device_type PAULA_8364 = device_creator; //************************************************************************* // 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, "8364 Paula", tag, owner, clock, "paula_8364", __FILE__), device_sound_interface(mconfig, *this), m_mem_r(*this), m_int_w(*this), m_dmacon(0), m_adkcon(0), m_stream(nullptr) { } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void paula_8364_device::device_start() { // resolve callbacks m_mem_r.resolve_safe(0); m_int_w.resolve_safe(); // initialize channels for (int i = 0; i < 4; i++) { m_channel[i].index = i; m_channel[i].curticks = 0; m_channel[i].manualmode = false; m_channel[i].curlocation = 0; m_channel[i].irq_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(paula_8364_device::signal_irq), this)); } // create the stream m_stream = machine().sound().stream_alloc(*this, 0, 4, clock() / CLOCK_DIVIDER); } //------------------------------------------------- // update - stream updater //------------------------------------------------- void paula_8364_device::update() { m_stream->update(); } //************************************************************************* // IMPLEMENTATION //************************************************************************** READ16_MEMBER( paula_8364_device::reg_r ) { switch (offset) { case REG_DMACONR: return m_dmacon; case REG_ADKCONR: return m_adkcon; } return 0xffff; } WRITE16_MEMBER( paula_8364_device::reg_w ) { if (offset >= 0xa0 && offset <= 0xdf) m_stream->update(); switch (offset) { case REG_DMACON: m_stream->update(); m_dmacon = (data & 0x8000) ? (m_dmacon | (data & 0x021f)) : (m_dmacon & ~(data & 0x021f)); // only bits 15, 9 and 5 to 0 break; case REG_ADKCON: m_stream->update(); m_adkcon = (data & 0x8000) ? (m_adkcon | (data & 0x7fff)) : (m_adkcon & ~(data & 0x7fff)); break; // to be moved case REG_AUD0LCL: m_channel[CHAN_0].loc = (m_channel[CHAN_0].loc & 0xffff0000) | ((data & 0xfffe) << 0); break; // 15-bit case REG_AUD0LCH: m_channel[CHAN_0].loc = (m_channel[CHAN_0].loc & 0x0000ffff) | ((data & 0x001f) << 16); break; // 3-bit on ocs, 5-bit ecs case REG_AUD1LCL: m_channel[CHAN_1].loc = (m_channel[CHAN_1].loc & 0xffff0000) | ((data & 0xfffe) << 0); break; // 15-bit case REG_AUD1LCH: m_channel[CHAN_1].loc = (m_channel[CHAN_1].loc & 0x0000ffff) | ((data & 0x001f) << 16); break; // 3-bit on ocs, 5-bit ecs case REG_AUD2LCL: m_channel[CHAN_2].loc = (m_channel[CHAN_2].loc & 0xffff0000) | ((data & 0xfffe) << 0); break; // 15-bit case REG_AUD2LCH: m_channel[CHAN_2].loc = (m_channel[CHAN_2].loc & 0x0000ffff) | ((data & 0x001f) << 16); break; // 3-bit on ocs, 5-bit ecs case REG_AUD3LCL: m_channel[CHAN_3].loc = (m_channel[CHAN_3].loc & 0xffff0000) | ((data & 0xfffe) << 0); break; // 15-bit case REG_AUD3LCH: m_channel[CHAN_3].loc = (m_channel[CHAN_3].loc & 0x0000ffff) | ((data & 0x001f) << 16); break; // 3-bit on ocs, 5-bit ecs // audio data case REG_AUD0LEN: m_channel[CHAN_0].len = data; break; case REG_AUD0PER: m_channel[CHAN_0].per = data; break; case REG_AUD0VOL: m_channel[CHAN_0].vol = data; break; case REG_AUD0DAT: m_channel[CHAN_0].dat = data; m_channel[CHAN_0].manualmode = true; break; case REG_AUD1LEN: m_channel[CHAN_1].len = data; break; case REG_AUD1PER: m_channel[CHAN_1].per = data; break; case REG_AUD1VOL: m_channel[CHAN_1].vol = data; break; case REG_AUD1DAT: m_channel[CHAN_1].dat = data; m_channel[CHAN_1].manualmode = true; break; case REG_AUD2LEN: m_channel[CHAN_2].len = data; break; case REG_AUD2PER: m_channel[CHAN_2].per = data; break; case REG_AUD2VOL: m_channel[CHAN_2].vol = data; break; case REG_AUD2DAT: m_channel[CHAN_2].dat = data; m_channel[CHAN_2].manualmode = true; break; case REG_AUD3LEN: m_channel[CHAN_3].len = data; break; case REG_AUD3PER: m_channel[CHAN_3].per = data; break; case REG_AUD3VOL: m_channel[CHAN_3].vol = data; break; case REG_AUD3DAT: m_channel[CHAN_3].dat = data; m_channel[CHAN_3].manualmode = true; break; } } //------------------------------------------------- // signal_irq - irq signaling //------------------------------------------------- TIMER_CALLBACK_MEMBER( paula_8364_device::signal_irq ) { m_int_w(param); } //------------------------------------------------- // dma_reload //------------------------------------------------- void paula_8364_device::dma_reload(audio_channel *chan) { chan->curlocation = chan->loc; chan->curlength = chan->len; chan->irq_timer->adjust(attotime::from_hz(15750), chan->index); // clock() / 227 LOG(("dma_reload(%d): offs=%05X len=%04X\n", chan->index, chan->curlocation, chan->curlength)); } //------------------------------------------------- // sound_stream_update - handle a stream update //------------------------------------------------- void paula_8364_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) { int channum, sampoffs = 0; // if all DMA off, disable all channels if (BIT(m_dmacon, 9) == 0) { 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++) memset(outputs[channum], 0, sizeof(stream_sample_t) * samples); return; } samples *= CLOCK_DIVIDER; // update the DMA states on each channel and reload if fresh for (channum = 0; channum < 4; channum++) { audio_channel *chan = &m_channel[channum]; if (!chan->dma_enabled && ((m_dmacon >> channum) & 1)) dma_reload(chan); chan->dma_enabled = BIT(m_dmacon, channum); } // 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; stream_sample_t sample; int i; // normalize the volume value volume = (volume & 0x40) ? 64 : (volume & 0x3f); volume *= 4; // are we modulating the period of the next channel? if ((m_adkcon >> channum) & 0x10) { nextper = chan->dat; nextvol = -1; sample = 0; } // are we modulating the volume of the next channel? else if ((m_adkcon >> channum) & 0x01) { 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][(sampoffs + i) / CLOCK_DIVIDER] = sample; // 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_mem_r(chan->curlocation); if (chan->curlength != 0) chan->curlength--; // if we run out of data, reload the dma if (chan->curlength == 0) dma_reload(chan); } // 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(nullptr, channum); chan->manualmode = false; } } } // bump ourselves forward by the number of ticks sampoffs += ticks; samples -= ticks; } }