furnace/src/engine/platform/sound/rf5c68.cpp

256 lines
6.0 KiB
C++

// license:BSD-3-Clause
// copyright-holders:Olivier Galibert,Aaron Giles
/*********************************************************/
/* ricoh RF5C68(or clone) PCM controller */
/* */
/* TODO: Verify RF5C105,164 (Sega CD/Mega CD) */
/* differences */
/*********************************************************/
#include "rf5c68.h"
#include <algorithm>
//**************************************************************************
// LIVE DEVICE
//**************************************************************************
//-------------------------------------------------
// rf5c68_device - constructor
//-------------------------------------------------
rf5c68_device::rf5c68_device(int output_bits)
: m_cbank(0)
, m_wbank(0)
, m_enable(0)
, m_output_bits(output_bits)
, m_ext_mem(nullptr)
{
}
rf5c68_device::rf5c68_device()
: rf5c68_device(10)
{
}
//-------------------------------------------------
// rf5c164_device - constructor
//-------------------------------------------------
rf5c164_device::rf5c164_device()
: rf5c68_device(16)
{
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void rf5c68_device::device_start(u8 *ext_mem)
{
m_ext_mem = ext_mem;
}
void rf5c68_device::device_reset()
{
m_cbank = 0;
m_wbank = 0;
m_enable = 0;
for (pcm_channel &chan : m_chan) {
chan.enable = 0;
chan.env = 0;
chan.pan = 0;
chan.start = 0;
chan.addr = 0;
chan.step = 0;
chan.loopst = 0;
}
}
//-------------------------------------------------
// sound_stream_update - handle a stream update
//-------------------------------------------------
void rf5c68_device::sound_stream_update(s16 **outputs, s16 **channel_outputs, u32 samples)
{
/* bail if not enabled */
if (!m_enable)
{
std::fill_n(outputs[0], samples, 0);
std::fill_n(outputs[1], samples, 0);
for (unsigned int i = 0; i < NUM_CHANNELS; i++) {
std::fill_n(channel_outputs[i*2], samples, 0);
std::fill_n(channel_outputs[i*2+1], samples, 0);
}
return;
}
if (m_mixleft.size() < samples)
m_mixleft.resize(samples);
if (m_mixright.size() < samples)
m_mixright.resize(samples);
std::fill_n(&m_mixleft[0], samples, 0);
std::fill_n(&m_mixright[0], samples, 0);
/* loop over channels */
for (unsigned int i = 0; i < NUM_CHANNELS; i++)
{
pcm_channel &chan = m_chan[i];
/* if this channel is active, accumulate samples */
if (chan.enable)
{
int lv = (chan.pan & 0x0f) * chan.env;
int rv = ((chan.pan >> 4) & 0x0f) * chan.env;
/* loop over the sample buffer */
for (u32 j = 0; j < samples; j++)
{
int sample;
/* fetch the sample and handle looping */
sample = m_ext_mem[(chan.addr >> 11) & 0xffff];
if (sample == 0xff)
{
chan.addr = chan.loopst << 11;
sample = m_ext_mem[(chan.addr >> 11) & 0xffff];
/* if we loop to a loop point, we're effectively dead */
if (sample == 0xff)
break;
}
chan.addr += chan.step;
/* add to the buffer */
s32 left_out = ((sample & 0x7f) * lv) >> 5;
s32 right_out = ((sample & 0x7f) * rv) >> 5;
if ((sample & 0x80) == 0)
{
left_out = -left_out;
right_out = -right_out;
}
channel_outputs[i*2][j] = (s16)left_out;
channel_outputs[i*2+1][j] = (s16)right_out;
m_mixleft[j] += left_out;
m_mixright[j] += right_out;
}
}
else
{
std::fill_n(channel_outputs[i*2], samples, 0);
std::fill_n(channel_outputs[i*2+1], samples, 0);
}
}
/*
now clamp and shift the result (output is only 10 bits for RF5C68, 16 bits for RF5C164)
reference: Mega CD hardware manual, RF5C68 datasheet
*/
const u8 output_shift = (m_output_bits > 16) ? 0 : (16 - m_output_bits);
const s32 output_nandmask = (1 << output_shift) - 1;
for (u32 j = 0; j < samples; j++)
{
s32 outleft = std::min(std::max(m_mixleft[j], -32768), 32767);
s32 outright = std::min(std::max(m_mixright[j], -32768), 32767);
outputs[0][j] = outleft & ~output_nandmask;
outputs[1][j] = outright & ~output_nandmask;
}
}
//-------------------------------------------------
// RF5C68 write register
//-------------------------------------------------
// TODO: RF5C164 only?
u8 rf5c68_device::rf5c68_r(offs_t offset)
{
u8 shift;
shift = (offset & 1) ? 11 + 8 : 11;
// printf("%08x\n",(m_chan[(offset & 0x0e) >> 1].addr));
return (m_chan[(offset & 0x0e) >> 1].addr) >> (shift);
}
void rf5c68_device::rf5c68_w(offs_t offset, u8 data)
{
pcm_channel &chan = m_chan[m_cbank];
int i;
/* switch off the address */
switch (offset)
{
case 0x00: /* envelope */
chan.env = data;
break;
case 0x01: /* pan */
chan.pan = data;
break;
case 0x02: /* FDL */
chan.step = (chan.step & 0xff00) | (data & 0x00ff);
break;
case 0x03: /* FDH */
chan.step = (chan.step & 0x00ff) | ((data << 8) & 0xff00);
break;
case 0x04: /* LSL */
chan.loopst = (chan.loopst & 0xff00) | (data & 0x00ff);
break;
case 0x05: /* LSH */
chan.loopst = (chan.loopst & 0x00ff) | ((data << 8) & 0xff00);
break;
case 0x06: /* ST */
chan.start = data;
if (!chan.enable)
chan.addr = chan.start << (8 + 11);
break;
case 0x07: /* control reg */
m_enable = (data >> 7) & 1;
if (data & 0x40)
m_cbank = data & 7;
else
m_wbank = (data & 0xf) << 12;
break;
case 0x08: /* channel on/off reg */
for (i = 0; i < 8; i++)
{
m_chan[i].enable = (~data >> i) & 1;
if (!m_chan[i].enable)
m_chan[i].addr = m_chan[i].start << (8 + 11);
}
break;
}
}
//-------------------------------------------------
// RF5C68 read memory
//-------------------------------------------------
u8 rf5c68_device::rf5c68_mem_r(offs_t offset)
{
return m_ext_mem[m_wbank | offset];
}
//-------------------------------------------------
// RF5C68 write memory
//-------------------------------------------------
void rf5c68_device::rf5c68_mem_w(offs_t offset, u8 data)
{
m_ext_mem[m_wbank | offset] = data;
}