/* SN76489 emulation by Maxim in 2001 and 2002 converted from my original Delphi implementation I'm a C newbie so I'm sure there are loads of stupid things in here which I'll come back to some day and redo Includes: - Super-high quality tone channel "oversampling" by calculating fractional positions on transitions - Noise output pattern reverse engineered from actual SMS output - Volume levels taken from actual SMS output 07/08/04 Charles MacDonald Modified for use with SMS Plus: - Added support for multiple PSG chips. - Added reset/config/update routines. - Added context management routines. - Removed SN76489_GetValues(). - Removed some unused variables. 25/04/07 Eke-Eke (Genesis Plus GX) - Removed stereo GG support (unused) - Made SN76489_Update outputs 16bits mono samples - Replaced volume table with VGM plugin's one 05/01/09 Eke-Eke (Genesis Plus GX) - Modified Cut-Off frequency (according to Steve Snake: http://www.smspower.org/forums/viewtopic.php?t=1746) 24/08/10 Eke-Eke (Genesis Plus GX) - Removed multichip support (unused) - Removed alternate volume table, panning & mute support (unused) - Removed configurable Feedback and Shift Register Width (always use Sega ones) - Added linear resampling using Blip Buffer (based on Blargg's implementation: http://www.smspower.org/forums/viewtopic.php?t=11376) 01/09/12 Eke-Eke (Genesis Plus GX) - Added generic Blip-Buffer support internally, using common Master Clock as timebase - Re-added stereo GG support - Re-added configurable Feedback and Shift Register Width - Rewrote core with various optimizations 04/11/16 Eke-Eke (Genesis Plus GX) - improved resampling quality (removes aliasing noise when using high frequency tones) - removed cut-off value (improves emulation accuracy of highest frequency tones) - modified channels output to 0/1 like real chip instead of -1/+1 (fixes PCM voices when cut-off value is removed) */ #include "shared.h" #define PSG_MCYCLES_RATIO (16 * 15) /* Initial state of shift register */ #define NoiseInitialState 0x8000 /* original Texas Instruments TMS SN76489AN (rev. A) used in SG-1000, SC-3000H & SF-7000 computers */ #define FB_DISCRETE 0x0006 #define SRW_DISCRETE 15 /* SN76489AN clone integrated in Sega's VDP chips (315-5124, 315-5246, 315-5313, Game Gear) */ #define FB_SEGAVDP 0x0009 #define SRW_SEGAVDP 16 typedef struct { /* Configuration */ int PreAmp[4][2]; /* stereo channels pre-amplification ratio (%) */ int NoiseFeedback; int SRWidth; /* PSG registers: */ int Registers[8]; /* Tone, vol x4 */ int LatchedRegister; int NoiseShiftRegister; int NoiseFreq; /* Noise channel signal generator frequency */ /* Output calculation variables */ int ToneFreqVals[4]; /* Frequency register values (counters) */ int ToneFreqPos[4]; /* Frequency channel flip-flops */ int Channel[4][2]; /* current amplitude of each (stereo) channel */ int ChanOut[4][2]; /* current output value of each (stereo) channel */ /* Internal M-clock counter */ unsigned long clocks; } SN76489_Context; static const uint16 PSGVolumeValues[16] = { /* These values are taken from a real SMS2's output */ /*{892,892,892,760,623,497,404,323,257,198,159,123,96,75,60,0}, */ /* I can't remember why 892... :P some scaling I did at some point */ /* these values are true volumes for 2dB drops at each step (multiply previous by 10^-0.1) */ 1516,1205,957,760,603,479,381,303,240,191,152,120,96,76,60,0 }; static SN76489_Context SN76489; void SN76489_Init(int type) { int i; for (i=0; i<4; i++) { SN76489.PreAmp[i][0] = 100; SN76489.PreAmp[i][1] = 100; } if (type == SN_DISCRETE) { SN76489.NoiseFeedback = FB_DISCRETE; SN76489.SRWidth = SRW_DISCRETE; } else { SN76489.NoiseFeedback = FB_SEGAVDP; SN76489.SRWidth = SRW_SEGAVDP; } } void SN76489_Reset() { int i; for(i = 0; i <= 3; i++) { /* Initialise PSG state */ SN76489.Registers[2*i] = 1; /* tone freq=1 */ SN76489.Registers[2*i+1] = 0xf; /* vol=off */ /* Set counters to 0 */ SN76489.ToneFreqVals[i] = 0; /* Set flip-flops to 1 */ SN76489.ToneFreqPos[i] = 1; /* Clear stereo channels amplitude */ SN76489.Channel[i][0] = 0; SN76489.Channel[i][1] = 0; /* Clear stereo channel outputs in delta buffer */ SN76489.ChanOut[i][0] = 0; SN76489.ChanOut[i][1] = 0; } /* Initialise latched register index */ SN76489.LatchedRegister = 0; /* Initialise noise generator */ SN76489.NoiseShiftRegister=NoiseInitialState; SN76489.NoiseFreq = 0x10; /* Reset internal M-cycle counter */ SN76489.clocks = 0; } void *SN76489_GetContextPtr(void) { return (uint8 *)&SN76489; } int SN76489_GetContextSize(void) { return sizeof(SN76489_Context); } /* Updates tone amplitude in delta buffer. Call whenever amplitude might have changed. */ INLINE void UpdateToneAmplitude(int i, int time) { int delta; /* left output */ delta = (SN76489.Channel[i][0] * SN76489.ToneFreqPos[i]) - SN76489.ChanOut[i][0]; if (delta != 0) { SN76489.ChanOut[i][0] += delta; blip_add_delta(snd.blips[0][0], time, delta); } /* right output */ delta = (SN76489.Channel[i][1] * SN76489.ToneFreqPos[i]) - SN76489.ChanOut[i][1]; if (delta != 0) { SN76489.ChanOut[i][1] += delta; blip_add_delta(snd.blips[0][1], time, delta); } } /* Updates noise amplitude in delta buffer. Call whenever amplitude might have changed. */ INLINE void UpdateNoiseAmplitude(int time) { int delta; /* left output */ delta = (SN76489.Channel[3][0] * ( SN76489.NoiseShiftRegister & 0x1 )) - SN76489.ChanOut[3][0]; if (delta != 0) { SN76489.ChanOut[3][0] += delta; blip_add_delta(snd.blips[0][0], time, delta); } /* right output */ delta = (SN76489.Channel[3][1] * ( SN76489.NoiseShiftRegister & 0x1 )) - SN76489.ChanOut[3][1]; if (delta != 0) { SN76489.ChanOut[3][1] += delta; blip_add_delta(snd.blips[0][1], time, delta); } } /* Runs tone channel for clock_length clocks */ static void RunTone(int i, int clocks) { int time; /* Update in case a register changed etc. */ UpdateToneAmplitude(i, SN76489.clocks); /* Time of next transition */ time = SN76489.ToneFreqVals[i]; /* Process any transitions that occur within clocks we're running */ while (time < clocks) { /* Flip the flip-flop */ SN76489.ToneFreqPos[i] ^= 1; UpdateToneAmplitude(i, time); /* Advance to time of next transition */ time += SN76489.Registers[i*2] * PSG_MCYCLES_RATIO; } /* Update channel tone counter */ SN76489.ToneFreqVals[i] = time; } /* Runs noise channel for clock_length clocks */ static void RunNoise(int clocks) { int time; /* Noise channel: match to tone2 if in slave mode */ int NoiseFreq = SN76489.NoiseFreq; if (NoiseFreq == 0x80) { NoiseFreq = SN76489.Registers[2*2]; SN76489.ToneFreqVals[3] = SN76489.ToneFreqVals[2]; } /* Update in case a register changed etc. */ UpdateNoiseAmplitude(SN76489.clocks); /* Time of next transition */ time = SN76489.ToneFreqVals[3]; /* Process any transitions that occur within clocks we're running */ while (time < clocks) { /* Flip the flip-flop */ SN76489.ToneFreqPos[3] ^= 1; if (SN76489.ToneFreqPos[3]) { /* On the positive edge of the square wave (only once per cycle) */ int Feedback = SN76489.NoiseShiftRegister; if ( SN76489.Registers[6] & 0x4 ) { /* White noise */ /* Calculate parity of fed-back bits for feedback */ /* Do some optimised calculations for common (known) feedback values */ /* If two bits fed back, I can do Feedback=(nsr & fb) && (nsr & fb ^ fb) */ /* since that's (one or more bits set) && (not all bits set) */ Feedback = ((Feedback & SN76489.NoiseFeedback) && ((Feedback & SN76489.NoiseFeedback) ^ SN76489.NoiseFeedback)); } else /* Periodic noise */ Feedback = Feedback & 1; SN76489.NoiseShiftRegister = (SN76489.NoiseShiftRegister >> 1) | (Feedback << (SN76489.SRWidth - 1)); UpdateNoiseAmplitude(time); } /* Advance to time of next transition */ time += NoiseFreq * PSG_MCYCLES_RATIO; } /* Update channel tone counter */ SN76489.ToneFreqVals[3] = time; } static void SN76489_RunUntil(unsigned int clocks) { int i; /* Run noise first, since it might use current value of third tone frequency counter */ RunNoise(clocks); /* Run tone channels */ for (i=0; i<3; ++i) { RunTone(i, clocks); } } void SN76489_Config(unsigned int clocks, int preAmp, int boostNoise, int stereo) { int i; /* cycle-accurate Game Gear stereo */ if (clocks > SN76489.clocks) { /* Run chip until current timestamp */ SN76489_RunUntil(clocks); /* Update internal M-cycle counter */ SN76489.clocks += ((clocks - SN76489.clocks + PSG_MCYCLES_RATIO - 1) / PSG_MCYCLES_RATIO) * PSG_MCYCLES_RATIO; } for (i=0; i<4; i++) { /* stereo channel pre-amplification */ SN76489.PreAmp[i][0] = preAmp * ((stereo >> (i + 4)) & 1); SN76489.PreAmp[i][1] = preAmp * ((stereo >> (i + 0)) & 1); /* noise channel boost (applied to all channels) */ SN76489.PreAmp[i][0] = SN76489.PreAmp[i][0] << boostNoise; SN76489.PreAmp[i][1] = SN76489.PreAmp[i][1] << boostNoise; /* update stereo channel amplitude */ SN76489.Channel[i][0]= (PSGVolumeValues[SN76489.Registers[i*2 + 1]] * SN76489.PreAmp[i][0]) / 100; SN76489.Channel[i][1]= (PSGVolumeValues[SN76489.Registers[i*2 + 1]] * SN76489.PreAmp[i][1]) / 100; } } void SN76489_Update(unsigned int clocks) { int i; if (clocks > SN76489.clocks) { /* Run chip until current timestamp */ SN76489_RunUntil(clocks); /* Update internal M-cycle counter */ SN76489.clocks += ((clocks - SN76489.clocks + PSG_MCYCLES_RATIO - 1) / PSG_MCYCLES_RATIO) * PSG_MCYCLES_RATIO; } /* Adjust internal M-cycle counter for next frame */ SN76489.clocks -= clocks; /* Adjust channel time counters for new frame */ for (i=0; i<4; ++i) { SN76489.ToneFreqVals[i] -= clocks; } } void SN76489_Write(unsigned int clocks, unsigned int data) { unsigned int index; if (clocks > SN76489.clocks) { /* run chip until current timestamp */ SN76489_RunUntil(clocks); /* update internal M-cycle counter */ SN76489.clocks += ((clocks - SN76489.clocks + PSG_MCYCLES_RATIO - 1) / PSG_MCYCLES_RATIO) * PSG_MCYCLES_RATIO; } if (data & 0x80) { /* latch byte %1 cc t dddd */ SN76489.LatchedRegister = index = (data >> 4) & 0x07; } else { /* restore latched register index */ index = SN76489.LatchedRegister; } switch (index) { case 0: case 2: case 4: /* Tone Channels frequency */ { if (data & 0x80) { /* Data byte %1 cc t dddd */ SN76489.Registers[index] = (SN76489.Registers[index] & 0x3f0) | (data & 0xf); } else { /* Data byte %0 - dddddd */ SN76489.Registers[index] = (SN76489.Registers[index] & 0x00f) | ((data & 0x3f) << 4); } /* zero frequency behaves the same as a value of 1 */ if (SN76489.Registers[index] == 0) { SN76489.Registers[index] = 1; } break; } case 1: case 3: case 5: /* Tone Channels attenuation */ { data &= 0x0f; SN76489.Registers[index] = data; data = PSGVolumeValues[data]; index >>= 1; SN76489.Channel[index][0] = (data * SN76489.PreAmp[index][0]) / 100; SN76489.Channel[index][1] = (data * SN76489.PreAmp[index][1]) / 100; break; } case 6: /* Noise control */ { SN76489.Registers[6] = data & 0x0f; /* reset shift register */ SN76489.NoiseShiftRegister = NoiseInitialState; /* set noise signal generator frequency */ SN76489.NoiseFreq = 0x10 << (data&0x3); break; } case 7: /* Noise attenuation */ { data &= 0x0f; SN76489.Registers[7] = data; data = PSGVolumeValues[data]; SN76489.Channel[3][0] = (data * SN76489.PreAmp[3][0]) / 100; SN76489.Channel[3][1] = (data * SN76489.PreAmp[3][1]) / 100; break; } } }