Genesis-Plus-GX/source/cd_hw/pcm.c

357 lines
9.5 KiB
C

/***************************************************************************************
* Genesis Plus
* PCM sound chip (315-5476A) (RF5C164 compatible)
*
* Copyright (C) 2012 Eke-Eke (Genesis Plus GX)
*
* Redistribution and use of this code or any derivative works are permitted
* provided that the following conditions are met:
*
* - Redistributions may not be sold, nor may they be used in a commercial
* product or activity.
*
* - Redistributions that are modified from the original source must include the
* complete source code, including the source code for all components used by a
* binary built from the modified sources. However, as a special exception, the
* source code distributed need not include anything that is normally distributed
* (in either source or binary form) with the major components (compiler, kernel,
* and so on) of the operating system on which the executable runs, unless that
* component itself accompanies the executable.
*
* - Redistributions must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
****************************************************************************************/
#include "shared.h"
#include "blip.h"
#define PCM_CLOCKS_PER_SAMPLE (384 * 4)
#define pcm scd.pcm_hw
static struct blip_buffer_t* blip[2];
void pcm_init(double clock, double samplerate)
{
pcm_shutdown();
/* allocate blip buffers */
blip[0] = blip_alloc(clock, samplerate * PCM_CLOCKS_PER_SAMPLE, samplerate / 4);
blip[1] = blip_alloc(clock, samplerate * PCM_CLOCKS_PER_SAMPLE, samplerate / 4);
}
void pcm_shutdown(void)
{
/* desallocate blip buffers */
if (blip[0]) blip_free(blip[0]);
if (blip[1]) blip_free(blip[1]);
blip[0] = blip[1] = 0;
}
void pcm_reset(void)
{
/* reset chip & clear external RAM */
memset(&pcm, 0, sizeof(pcm_t));
/* reset default bank */
pcm.bank = pcm.ram;
/* clear blip delta buffers */
blip_clear(blip[0]);
blip_clear(blip[1]);
}
void pcm_update(short *buffer, int length)
{
/* get number of clocks needed */
length = blip_clocks_needed(blip[0], length);
/* check if PCM chip is running */
if (pcm.enabled)
{
int i, j, l, r;
/* generate PCM samples */
for (i=0; i<length; i++)
{
/* clear output */
l = r = 0;
/* run eight PCM channels */
for (j=0; j<8; j++)
{
/* check if channel is enabled */
if (pcm.status & (1 << j))
{
/* read current WAVE RAM address */
short data = pcm.ram[(pcm.chan[j].addr >> 11) & 0xffff];
/* STOP data ? */
if (data == 0xff)
{
/* reset WAVE RAM address with loop address */
pcm.chan[j].addr = pcm.chan[j].ls.w << 11;
/* read WAVE RAM address again */
data = pcm.ram[pcm.chan[j].ls.w];
}
/* infinite loop should not output any data */
if (data != 0xff)
{
/* output L & R subchannels */
if (data & 0x80)
{
/* PCM data is negative */
data = -(data & 0x7f);
}
/* multiply PCM data with ENV & stereo PAN data then add to outputs (13.6 fixed point) */
l += ((data * pcm.chan[j].env * (pcm.chan[j].pan & 0x0F)) >> 6);
r += ((data * pcm.chan[j].env * (pcm.chan[j].pan >> 4)) >> 6);
/* increment WAVE RAM address */
pcm.chan[j].addr += pcm.chan[j].fd.w;
}
}
}
/* limiter */
if (l < -32768) l = -32768;
else if (l > 32767) l = 32767;
if (r < -32768) r = -32768;
else if (r > 32767) r = 32767;
/* check if PCM left output changed */
if (pcm.out[0] != l)
{
blip_add(blip[0], i, l-pcm.out[0]);
pcm.out[0] = l;
}
/* check if PCM right output changed */
if (pcm.out[1] != r)
{
blip_add(blip[1], i, r-pcm.out[1]);
pcm.out[1] = r;
}
}
}
else
{
/* check if PCM left output changed */
if (pcm.out[0])
{
blip_add(blip[0], 0, -pcm.out[0]);
pcm.out[0] = 0;
}
/* check if PCM right output changed */
if (pcm.out[1])
{
blip_add(blip[1], 0, -pcm.out[1]);
pcm.out[1] = 0;
}
}
/* resample to output stereo buffer */
blip_end_frame(blip[0], length);
blip_read_samples(blip[0], buffer, 1);
blip_end_frame(blip[1], length);
blip_read_samples(blip[1], buffer + 1, 1);
}
void pcm_write(unsigned int address, unsigned char data)
{
#ifdef LOG_PCM
error("[%d][%d]PCM %x write -> 0x%02x (%X)\n", v_counter, s68k.cycles, address, data, s68k.pc);
#endif
/* external RAM is mapped to $1000-$1FFF */
if (address >= 0x1000)
{
/* 4K bank access */
pcm.bank[address & 0xfff] = data;
return;
}
/* internal area si mapped to $0000-$0FFF */
switch (address)
{
case 0x00: /* ENV register */
{
/* update channel ENV multiplier */
pcm.chan[pcm.index].env = data;
return;
}
case 0x01: /* PAN register */
{
/* update channel stereo panning value */
pcm.chan[pcm.index].pan = data;
return;
}
case 0x02: /* FD register (LSB) */
{
/* update channel WAVE RAM address increment LSB */
pcm.chan[pcm.index].fd.byte.l = data;
return;
}
case 0x03: /* FD register (MSB) */
{
/* update channel WAVE RAM address increment MSB */
pcm.chan[pcm.index].fd.byte.h = data;
return;
}
case 0x04: /* LS register (LSB) */
{
/* update channel WAVE RAM loop address LSB */
pcm.chan[pcm.index].ls.byte.l = data;
return;
}
case 0x05: /* LS register (MSB) */
{
/* update channel WAVE RAM loop address MSB */
pcm.chan[pcm.index].ls.byte.h = data;
return;
}
case 0x06: /* ST register */
{
/* update channel WAVE RAM start address (16.11 fixed point) */
pcm.chan[pcm.index].st = data << (8 + 11);
/* reload WAVE RAM address if channel is OFF */
if (~(pcm.status & (1 << pcm.index)))
{
pcm.chan[pcm.index].addr = pcm.chan[pcm.index].st;
}
return;
}
case 0x07: /* CTRL register */
{
if (data & 0x40)
{
/* channel selection (0-7) */
pcm.index = data & 0x07;
}
else
{
/* external RAM bank selection (16 x 4K) */
pcm.bank = &pcm.ram[(data & 0x0f) << 12];
}
/* update PCM chip status (bit 7) */
pcm.enabled = data & 0x80;
return;
}
case 0x08: /* ON/OFF register */
{
/* update PCM channels status */
pcm.status = ~data;
/* reload WAVE RAM address pointers when channels are OFF */
if (data & 0x01) pcm.chan[0].addr = pcm.chan[0].st;
if (data & 0x02) pcm.chan[1].addr = pcm.chan[1].st;
if (data & 0x04) pcm.chan[2].addr = pcm.chan[2].st;
if (data & 0x08) pcm.chan[3].addr = pcm.chan[3].st;
if (data & 0x10) pcm.chan[4].addr = pcm.chan[4].st;
if (data & 0x20) pcm.chan[5].addr = pcm.chan[5].st;
if (data & 0x40) pcm.chan[6].addr = pcm.chan[6].st;
if (data & 0x80) pcm.chan[7].addr = pcm.chan[7].st;
return;
}
default:
{
/* illegal access */
return;
}
}
}
unsigned char pcm_read(unsigned int address)
{
#ifdef LOG_PCM
error("[%d][%d]PCM %x read (%X)\n", v_counter, s68k.cycles, address, s68k.pc);
#endif
/* external RAM (TODO: verify if possible to read, some docs claim it's not !) */
if (address >= 0x1000)
{
/* 4K bank access */
return pcm.bank[address & 0xfff];
}
/* read WAVE RAM address pointers */
if ((address >= 0x10) && (address < 0x20))
{
int index = (address >> 1) & 0x07;
if (address & 1)
{
return (pcm.chan[index].addr >> (11 + 8)) & 0xff;
}
else
{
return (pcm.chan[index].addr >> 11) & 0xff;
}
}
/* illegal access */
return 0xff;
}
void pcm_ram_dma_w(unsigned int words)
{
uint16 data;
/* CDC buffer source address */
uint16 src_index = cdc.dac.w & 0x3ffe;
/* PCM-RAM destination address*/
uint16 dst_index = (scd.regs[0x0a>>1].w << 2) & 0xffe;
/* update DMA destination address */
scd.regs[0x0a>>1].w += (words >> 1);
/* update DMA source address */
cdc.dac.w += (words << 1);
/* DMA transfer */
while (words--)
{
/* read 16-bit word from CDC buffer */
data = *(uint16 *)(cdc.ram + src_index);
/* write 16-bit word to PCM RAM (endianness does not matter since PCM RAM is always accessed as byte)*/
*(uint16 *)(pcm.bank + dst_index) = data ;
/* increment CDC buffer source address */
src_index = (src_index + 2) & 0x3ffe;
/* increment PCM-RAM destination address */
dst_index = (dst_index + 2) & 0xffe;
}
}