/*************************************************************************************** * 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 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 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 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) ? (1 * PSG_MCYCLES_RATIO) : (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 (verified on integrated version only) */ psg.freqInc[index>>1] = PSG_MCYCLES_RATIO; } /* 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; } /* reset shift register value */ 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; } }