Genesis-Plus-GX/core/sound/psg.c

604 lines
17 KiB
C

/***************************************************************************************
* Genesis Plus
* PSG sound chip (SN76489A compatible)
*
* Support for discrete chip & integrated (ASIC) clones
*
* Noise implementation based on http://www.smspower.org/Development/SN76489#NoiseChannel
*
* Copyright (C) 2016-2017 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_buf.h"
/* internal clock = input clock : 16 = (master clock : 15) : 16 */
#define PSG_MCYCLES_RATIO (15*16)
/* maximal channel output (roughly adjusted to match VA4 MD1 PSG/FM balance with 1.5x amplification of PSG output) */
#define PSG_MAX_VOLUME 2800
static const uint8 noiseShiftWidth[2] = {14,15};
static const uint8 noiseBitMask[2] = {0x6,0x9};
static const uint8 noiseFeedback[10] = {0,1,1,0,1,0,0,1,1,0};
static const uint16 chanVolume[16] = {
PSG_MAX_VOLUME, /* MAX */
PSG_MAX_VOLUME * 0.794328234, /* -2dB */
PSG_MAX_VOLUME * 0.630957344, /* -4dB */
PSG_MAX_VOLUME * 0.501187233, /* -6dB */
PSG_MAX_VOLUME * 0.398107170, /* -8dB */
PSG_MAX_VOLUME * 0.316227766, /* -10dB */
PSG_MAX_VOLUME * 0.251188643, /* -12dB */
PSG_MAX_VOLUME * 0.199526231, /* -14dB */
PSG_MAX_VOLUME * 0.158489319, /* -16dB */
PSG_MAX_VOLUME * 0.125892541, /* -18dB */
PSG_MAX_VOLUME * 0.1, /* -20dB */
PSG_MAX_VOLUME * 0.079432823, /* -22dB */
PSG_MAX_VOLUME * 0.063095734, /* -24dB */
PSG_MAX_VOLUME * 0.050118723, /* -26dB */
PSG_MAX_VOLUME * 0.039810717, /* -28dB */
0 /* OFF */
};
static struct
{
int clocks;
int latch;
int zeroFreqInc;
int noiseShiftValue;
int noiseShiftWidth;
int noiseBitMask;
int regs[8];
int freqInc[4];
int freqCounter[4];
int polarity[4];
int chanDelta[4][2];
int chanOut[4][2];
int chanAmp[4][2];
} psg;
static void psg_update(unsigned int clocks);
void psg_init(PSG_TYPE type)
{
int i;
/* Initialize stereo amplification (default) */
for (i=0; i<4; i++)
{
psg.chanAmp[i][0] = 100;
psg.chanAmp[i][1] = 100;
}
/* Initialize Tone zero frequency increment value */
psg.zeroFreqInc = ((type == PSG_DISCRETE) ? 0x400 : 0x1) * PSG_MCYCLES_RATIO;
/* Initialize Noise LSFR type */
psg.noiseShiftWidth = noiseShiftWidth[type];
psg.noiseBitMask = noiseBitMask[type];
}
void psg_reset()
{
int i;
/* power-on state (verified on 315-5313A & 315-5660 integrated version only) */
for (i=0; i<4; i++)
{
psg.regs[i*2] = 0;
psg.regs[i*2+1] = 0;
psg.freqInc[i] = (i < 3) ? (psg.zeroFreqInc) : (16 * PSG_MCYCLES_RATIO);
psg.freqCounter[i] = 0;
psg.polarity[i] = -1;
psg.chanDelta[i][0] = 0;
psg.chanDelta[i][1] = 0;
psg.chanOut[i][0] = 0;
psg.chanOut[i][1] = 0;
}
/* noise attenuation register is latched on power-on (verified on 315-5313A & 315-5660 integrated version only) */
psg.latch = 7;
/* reset noise shift register */
psg.noiseShiftValue = 1 << psg.noiseShiftWidth;
/* reset internal M-cycles clock counter */
psg.clocks = 0;
}
int psg_context_save(uint8 *state)
{
int bufferptr = 0;
save_param(&psg.clocks,sizeof(psg.clocks));
save_param(&psg.latch,sizeof(psg.latch));
save_param(&psg.noiseShiftValue,sizeof(psg.noiseShiftValue));
save_param(psg.regs,sizeof(psg.regs));
save_param(psg.freqInc,sizeof(psg.freqInc));
save_param(psg.freqCounter,sizeof(psg.freqCounter));
save_param(psg.polarity,sizeof(psg.polarity));
save_param(psg.chanDelta,sizeof(psg.chanDelta));
save_param(psg.chanOut,sizeof(psg.chanOut));
return bufferptr;
}
int psg_context_load(uint8 *state)
{
int chanOut[4][2], delta[2];
int i, bufferptr = 0;
/* get current tone channels output */
for (i=0; i<3; i++)
{
if (psg.polarity[i] > 0)
{
chanOut[i][0] = psg.chanOut[i][0];
chanOut[i][1] = psg.chanOut[i][1];
}
else
{
chanOut[i][0] = 0;
chanOut[i][1] = 0;
}
}
/* get current noise channel output */
if (psg.noiseShiftValue & 1)
{
chanOut[3][0] = psg.chanOut[3][0];
chanOut[3][1] = psg.chanOut[3][1];
}
else
{
chanOut[3][0] = 0;
chanOut[3][1] = 0;
}
load_param(&psg.clocks,sizeof(psg.clocks));
load_param(&psg.latch,sizeof(psg.latch));
load_param(&psg.noiseShiftValue,sizeof(psg.noiseShiftValue));
load_param(psg.regs,sizeof(psg.regs));
load_param(psg.freqInc,sizeof(psg.freqInc));
load_param(psg.freqCounter,sizeof(psg.freqCounter));
load_param(psg.polarity,sizeof(psg.polarity));
load_param(psg.chanDelta,sizeof(psg.chanDelta));
load_param(psg.chanOut,sizeof(psg.chanOut));
/* apply any pending channel volume variation */
for (i=0; i<4; i++)
{
psg.chanOut[i][0] += psg.chanDelta[i][0];
psg.chanOut[i][1] += psg.chanDelta[i][1];
psg.chanDelta[i][0] = 0;
psg.chanDelta[i][1] = 0;
}
/* calculate noise channel output variations */
if (psg.noiseShiftValue & 1)
{
/* channel output is high */
delta[0] = psg.chanOut[3][0] - chanOut[3][0];
delta[1] = psg.chanOut[3][0] - chanOut[3][1];
}
else
{
/* channel output is low */
delta[0] = -chanOut[3][0];
delta[1] = -chanOut[3][1];
}
/* add tone channels output variations */
for (i=0; i<3; i++)
{
if (psg.polarity[i] > 0)
{
/* channel output is high */
delta[0] += (psg.chanOut[i][0] - chanOut[i][0]);
delta[1] += (psg.chanOut[i][0] - chanOut[i][1]);
}
else
{
/* channel output is low */
delta[0] -= chanOut[i][0];
delta[1] -= chanOut[i][1];
}
}
/* update mixed channels output */
if (config.hq_psg)
{
blip_add_delta(snd.blips[0], psg.clocks, delta[0], delta[1]);
}
else
{
blip_add_delta_fast(snd.blips[0], psg.clocks, delta[0], delta[1]);
}
return bufferptr;
}
void psg_write(unsigned int clocks, unsigned int data)
{
int index;
/* PSG chip synchronization */
if (clocks > psg.clocks)
{
/* run PSG chip until current timestamp */
psg_update(clocks);
/* update internal M-cycles clock counter */
psg.clocks += ((clocks - psg.clocks + PSG_MCYCLES_RATIO - 1) / PSG_MCYCLES_RATIO) * PSG_MCYCLES_RATIO;
}
if (data & 0x80)
{
/* latch register index (1xxx----) */
psg.latch = index = (data >> 4) & 0x07;
}
else
{
/* restore latched register index */
index= psg.latch;
}
switch (index)
{
case 0:
case 2:
case 4: /* Tone channels frequency */
{
/* recalculate frequency register value */
if (data & 0x80)
{
/* update 10-bit register LSB (1---xxxx) */
data = (psg.regs[index] & 0x3f0) | (data & 0x0f);
}
else
{
/* update 10-bit register MSB (0-xxxxxx) */
data = (psg.regs[index] & 0x00f) | ((data & 0x3f) << 4);
}
/* update channel M-cycle counter increment */
if (data)
{
psg.freqInc[index>>1] = data * PSG_MCYCLES_RATIO;
}
else
{
/* zero value behaves the same as a value of 1 on integrated version (0x400 on discrete version) */
psg.freqInc[index>>1] = psg.zeroFreqInc;
}
/* update noise channel counter increment if required */
if ((index == 4) && ((psg.regs[6] & 0x03) == 0x03))
{
psg.freqInc[3] = psg.freqInc[2];
}
break;
}
case 6: /* Noise control */
{
/* noise signal generator frequency (----?xxx) */
int noiseFreq = (data & 0x03);
if (noiseFreq == 0x03)
{
/* noise generator is controlled by tone channel #3 generator */
psg.freqInc[3] = psg.freqInc[2];
psg.freqCounter[3] = psg.freqCounter[2];
}
else
{
/* noise generator is running at separate frequency */
psg.freqInc[3] = (0x10 << noiseFreq) * PSG_MCYCLES_RATIO;
}
/* check current noise shift register output */
if (psg.noiseShiftValue & 1)
{
/* high to low transition will be applied at next internal cycle update */
psg.chanDelta[3][0] = -psg.chanOut[3][0];
psg.chanDelta[3][1] = -psg.chanOut[3][1];
}
/* reset noise shift register value (noise channel output is forced low) */
psg.noiseShiftValue = 1 << psg.noiseShiftWidth;;
break;
}
case 7: /* Noise channel attenuation */
{
/* convert 4-bit attenuation value (----xxxx) to 16-bit volume value */
data = chanVolume[data & 0x0f];
/* check noise shift register output */
if (psg.noiseShiftValue & 1)
{
/* channel output is high, volume variation will be applied at next internal cycle update */
psg.chanDelta[3][0] = ((data * psg.chanAmp[3][0]) / 100) - psg.chanOut[3][0];
psg.chanDelta[3][1] = ((data * psg.chanAmp[3][1]) / 100) - psg.chanOut[3][1];
}
else
{
/* channel output is low, volume variation will be applied at next transition */
psg.chanOut[3][0] = (data * psg.chanAmp[3][0]) / 100;
psg.chanOut[3][1] = (data * psg.chanAmp[3][1]) / 100;
}
break;
}
default: /* Tone channels attenuation */
{
/* channel number (0-2) */
int i = index >> 1;
/* convert 4-bit attenuation value (----xxxx) to 16-bit volume value */
data = chanVolume[data & 0x0f];
/* check tone generator polarity */
if (psg.polarity[i] > 0)
{
/* channel output is high, volume variation will be applied at next internal cycle update */
psg.chanDelta[i][0] = ((data * psg.chanAmp[i][0]) / 100) - psg.chanOut[i][0];
psg.chanDelta[i][1] = ((data * psg.chanAmp[i][1]) / 100) - psg.chanOut[i][1];
}
else
{
/* channel output is low, volume variation will be applied at next transition */
psg.chanOut[i][0] = (data * psg.chanAmp[i][0]) / 100;
psg.chanOut[i][1] = (data * psg.chanAmp[i][1]) / 100;
}
break;
}
}
/* save register value */
psg.regs[index] = data;
}
void psg_config(unsigned int clocks, unsigned int preamp, unsigned int panning)
{
int i;
/* PSG chip synchronization */
if (clocks > psg.clocks)
{
/* run PSG chip until current timestamp */
psg_update(clocks);
/* update internal M-cycles clock counter */
psg.clocks += ((clocks - psg.clocks + PSG_MCYCLES_RATIO - 1) / PSG_MCYCLES_RATIO) * PSG_MCYCLES_RATIO;
}
for (i=0; i<4; i++)
{
/* channel internal volume */
int volume = psg.regs[i*2+1];
/* update channel stereo amplification */
psg.chanAmp[i][0] = preamp * ((panning >> (i + 4)) & 1);
psg.chanAmp[i][1] = preamp * ((panning >> (i + 0)) & 1);
/* tone channels */
if (i < 3)
{
/* check tone generator polarity */
if (psg.polarity[i] > 0)
{
/* channel output is high, volume variation will be applied at next internal cycle update */
psg.chanDelta[i][0] = ((volume * psg.chanAmp[i][0]) / 100) - psg.chanOut[i][0];
psg.chanDelta[i][1] = ((volume * psg.chanAmp[i][1]) / 100) - psg.chanOut[i][1];
}
else
{
/* channel output is low, volume variation will be applied at next transition*/
psg.chanOut[i][0] = (volume * psg.chanAmp[i][0]) / 100;
psg.chanOut[i][1] = (volume * psg.chanAmp[i][1]) / 100;
}
}
/* noise channel */
else
{
/* check noise shift register output */
if (psg.noiseShiftValue & 1)
{
/* channel output is high, volume variation will be applied at next internal cycle update */
psg.chanDelta[3][0] = ((volume * psg.chanAmp[3][0]) / 100) - psg.chanOut[3][0];
psg.chanDelta[3][1] = ((volume * psg.chanAmp[3][1]) / 100) - psg.chanOut[3][1];
}
else
{
/* channel output is low, volume variation will be applied at next transition */
psg.chanOut[3][0] = (volume * psg.chanAmp[3][0]) / 100;
psg.chanOut[3][1] = (volume * psg.chanAmp[3][1]) / 100;
}
}
}
}
void psg_end_frame(unsigned int clocks)
{
int i;
if (clocks > psg.clocks)
{
/* run PSG chip until current timestamp */
psg_update(clocks);
/* update internal M-cycles clock counter */
psg.clocks += ((clocks - psg.clocks + PSG_MCYCLES_RATIO - 1) / PSG_MCYCLES_RATIO) * PSG_MCYCLES_RATIO;
}
/* adjust internal M-cycles clock counter for next frame */
psg.clocks -= clocks;
/* adjust channels time counters for next frame */
for (i=0; i<4; ++i)
{
psg.freqCounter[i] -= clocks;
}
}
static void psg_update(unsigned int clocks)
{
int i, timestamp, polarity;
for (i=0; i<4; i++)
{
/* apply any pending channel volume variations */
if (psg.chanDelta[i][0] | psg.chanDelta[i][1])
{
/* update channel output */
if (config.hq_psg)
{
blip_add_delta(snd.blips[0], psg.clocks, psg.chanDelta[i][0], psg.chanDelta[i][1]);
}
else
{
blip_add_delta_fast(snd.blips[0], psg.clocks, psg.chanDelta[i][0], psg.chanDelta[i][1]);
}
/* update channel volume */
psg.chanOut[i][0] += psg.chanDelta[i][0];
psg.chanOut[i][1] += psg.chanDelta[i][1];
/* clear pending channel volume variations */
psg.chanDelta[i][0] = 0;
psg.chanDelta[i][1] = 0;
}
/* timestamp of next transition */
timestamp = psg.freqCounter[i];
/* current channel generator polarity */
polarity = psg.polarity[i];
/* Tone channels */
if (i < 3)
{
/* process all transitions occurring until current clock timestamp */
while (timestamp < clocks)
{
/* invert tone generator polarity */
polarity = -polarity;
/* update channel output */
if (config.hq_psg)
{
blip_add_delta(snd.blips[0], timestamp, polarity*psg.chanOut[i][0], polarity*psg.chanOut[i][1]);
}
else
{
blip_add_delta_fast(snd.blips[0], timestamp, polarity*psg.chanOut[i][0], polarity*psg.chanOut[i][1]);
}
/* timestamp of next transition */
timestamp += psg.freqInc[i];
}
}
/* Noise channel */
else
{
/* current noise shift register value */
int shiftValue = psg.noiseShiftValue;
/* process all transitions occurring until current clock timestamp */
while (timestamp < clocks)
{
/* invert noise generator polarity */
polarity = -polarity;
/* noise register is shifted on positive edge only */
if (polarity > 0)
{
/* current shift register output */
int shiftOutput = shiftValue & 0x01;
/* White noise (----1xxx) */
if (psg.regs[6] & 0x04)
{
/* shift and apply XOR feedback network */
shiftValue = (shiftValue >> 1) | (noiseFeedback[shiftValue & psg.noiseBitMask] << psg.noiseShiftWidth);
}
/* Periodic noise (----0xxx) */
else
{
/* shift and feedback current output */
shiftValue = (shiftValue >> 1) | (shiftOutput << psg.noiseShiftWidth);
}
/* shift register output variation */
shiftOutput = (shiftValue & 0x1) - shiftOutput;
/* update noise channel output */
if (config.hq_psg)
{
blip_add_delta(snd.blips[0], timestamp, shiftOutput*psg.chanOut[3][0], shiftOutput*psg.chanOut[3][1]);
}
else
{
blip_add_delta_fast(snd.blips[0], timestamp, shiftOutput*psg.chanOut[3][0], shiftOutput*psg.chanOut[3][1]);
}
}
/* timestamp of next transition */
timestamp += psg.freqInc[3];
}
/* save shift register value */
psg.noiseShiftValue = shiftValue;
}
/* save timestamp of next transition */
psg.freqCounter[i] = timestamp;
/* save channel generator polarity */
psg.polarity[i] = polarity;
}
}