256 lines
		
	
	
		
			6 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			256 lines
		
	
	
		
			6 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;
 | |
| }
 | 
