mirror of
https://github.com/ekeeke/Genesis-Plus-GX.git
synced 2024-11-10 21:05:12 +01:00
570 lines
16 KiB
C
570 lines
16 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;
|
|
}
|
|
|
|
/* tone #2 attenuation register is latched on power-on (verified on 315-5313A integrated version only) */
|
|
psg.latch = 3;
|
|
|
|
/* 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.chanOut,sizeof(psg.chanOut));
|
|
|
|
return bufferptr;
|
|
}
|
|
|
|
int psg_context_load(uint8 *state)
|
|
{
|
|
int delta[2];
|
|
int i, bufferptr = 0;
|
|
|
|
/* initialize delta with current noise channel output */
|
|
if (psg.noiseShiftValue & 1)
|
|
{
|
|
delta[0] = -psg.chanOut[3][0];
|
|
delta[1] = -psg.chanOut[3][1];
|
|
}
|
|
else
|
|
{
|
|
delta[0] = 0;
|
|
delta[1] = 0;
|
|
}
|
|
|
|
/* add current tone channels output */
|
|
for (i=0; i<3; i++)
|
|
{
|
|
if (psg.polarity[i] > 0)
|
|
{
|
|
delta[0] -= psg.chanOut[i][0];
|
|
delta[1] -= psg.chanOut[i][1];
|
|
}
|
|
}
|
|
|
|
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.chanOut,sizeof(psg.chanOut));
|
|
|
|
/* add noise channel output variation */
|
|
if (psg.noiseShiftValue & 1)
|
|
{
|
|
delta[0] += psg.chanOut[3][0];
|
|
delta[1] += psg.chanOut[3][1];
|
|
}
|
|
|
|
/* add tone channels output variation */
|
|
for (i=0; i<3; i++)
|
|
{
|
|
if (psg.polarity[i] > 0)
|
|
{
|
|
delta[0] += psg.chanOut[i][0];
|
|
delta[1] += psg.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 (-----?xx) */
|
|
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 */
|
|
{
|
|
int chanOut[2];
|
|
|
|
/* convert 4-bit attenuation value (----xxxx) to 16-bit volume value */
|
|
data = chanVolume[data & 0x0f];
|
|
|
|
/* channel pre-amplification */
|
|
chanOut[0] = (data * psg.chanAmp[3][0]) / 100;
|
|
chanOut[1] = (data * psg.chanAmp[3][1]) / 100;
|
|
|
|
/* 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] += (chanOut[0] - psg.chanOut[3][0]);
|
|
psg.chanDelta[3][1] += (chanOut[1] - psg.chanOut[3][1]);
|
|
}
|
|
|
|
/* update channel volume */
|
|
psg.chanOut[3][0] = chanOut[0];
|
|
psg.chanOut[3][1] = chanOut[1];
|
|
|
|
break;
|
|
}
|
|
|
|
default: /* Tone channels attenuation */
|
|
{
|
|
int chanOut[2];
|
|
|
|
/* channel number (0-2) */
|
|
int i = index >> 1;
|
|
|
|
/* convert 4-bit attenuation value (----xxxx) to 16-bit volume value */
|
|
data = chanVolume[data & 0x0f];
|
|
|
|
/* channel pre-amplification */
|
|
chanOut[0] = (data * psg.chanAmp[i][0]) / 100;
|
|
chanOut[1] = (data * psg.chanAmp[i][1]) / 100;
|
|
|
|
/* 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] += (chanOut[0] - psg.chanOut[i][0]);
|
|
psg.chanDelta[i][1] += (chanOut[1] - psg.chanOut[i][1]);
|
|
}
|
|
|
|
/* update channel volume */
|
|
psg.chanOut[i][0] = chanOut[0];
|
|
psg.chanOut[i][1] = chanOut[1];
|
|
|
|
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]);
|
|
}
|
|
}
|
|
|
|
/* 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]);
|
|
}
|
|
}
|
|
|
|
/* update channel volume */
|
|
psg.chanOut[i][0] = (volume * psg.chanAmp[i][0]) / 100;
|
|
psg.chanOut[i][1] = (volume * psg.chanAmp[i][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]);
|
|
}
|
|
|
|
/* 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 (-----1xx) */
|
|
if (psg.regs[6] & 0x04)
|
|
{
|
|
/* shift and apply XOR feedback network */
|
|
shiftValue = (shiftValue >> 1) | (noiseFeedback[shiftValue & psg.noiseBitMask] << psg.noiseShiftWidth);
|
|
}
|
|
|
|
/* Periodic noise (-----0xx) */
|
|
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;
|
|
}
|
|
}
|