.implemented new SN76489 core, using Blargg's blip linear implementation

.improved sound chips input clock precision (float->double)
This commit is contained in:
ekeeke31 2010-08-24 12:48:40 +00:00
parent 88e7fb847a
commit b8506f31b7
6 changed files with 247 additions and 272 deletions

View File

@ -21,342 +21,318 @@
25/04/07 Eke-Eke (Genesis Plus GX)
- Removed stereo GG support (unused)
- Made SN76489_Update outputs 16bits mono samples
- Rade SN76489_Update outputs 16bits mono samples
- Replaced volume table with VGM plugin's one
05/01/09 Eke-Eke (Genesis Plus GX)
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)
25/05/09 Eke-Eke (Genesis Plus GX)
25/05/09 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 (Blargg's implementation: http://www.smspower.org/forums/viewtopic.php?t=11376)
*/
#include "shared.h"
#include "blip.h"
#include <float.h> // for FLT_MIN
#include <string.h> // for memcpy
/* Initial state of shift register */
#define NoiseInitialState 0x8000
#define NoiseInitialState 0x8000 /* Initial state of shift register */
//#define PSG_CUTOFF 0x6 /* Value below which PSG does not output */
#define PSG_CUTOFF 0x1
/* Value below which PSG does not output */
/*#define PSG_CUTOFF 0x6*/
#define PSG_CUTOFF 0x1
/*
More testing is needed to find and confirm feedback patterns for
SN76489 variants and compatible chips.
*/
enum feedback_patterns {
FB_BBCMICRO = 0x8005, /* Texas Instruments TMS SN76489N (original) from BBC Micro computer */
FB_SC3000 = 0x0006, /* Texas Instruments TMS SN76489AN (rev. A) from SC-3000H computer */
FB_SEGAVDP = 0x0009, /* SN76489 clone in Sega's VDP chips (315-5124, 315-5246, 315-5313, Game Gear) */
};
enum sr_widths {
SRW_SC3000BBCMICRO = 15,
SRW_SEGAVDP = 16
};
enum volume_modes {
VOL_TRUNC = 0, /* Volume levels 13-15 are identical */
VOL_FULL = 1, /* Volume levels 13-15 are unique */
};
enum mute_values {
MUTE_ALLOFF = 0, /* All channels muted */
MUTE_TONE1 = 1, /* Tone 1 mute control */
MUTE_TONE2 = 2, /* Tone 2 mute control */
MUTE_TONE3 = 4, /* Tone 3 mute control */
MUTE_NOISE = 8, /* Noise mute control */
MUTE_ALLON = 15, /* All channels enabled */
};
/* SN76489 clone in Sega's VDP chips (315-5124, 315-5246, 315-5313, Game Gear) */
#define FB_SEGAVDP 0x0009
#define SRW_SEGAVDP 16
typedef struct
{
int Mute; // per-channel muting
int VolumeArray;
int BoostNoise; // double noise volume when non-zero
/* Variables */
float Clock;
float dClock;
int PSGStereo;
int NumClocksForSample;
int WhiteNoiseFeedback;
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 Channels[4]; /* Value of each channel, before stereo is applied */
float IntermediatePos[4]; /* intermediate values used at boundaries between + and - (does not need double accuracy)*/
/* Configuration */
int BoostNoise; /* double noise volume when non-zero */
int panning[4]; /* fake stereo - 0..127..254 */
/* 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 Channels[4]; /* Value of each channel, before stereo is applied */
/* Blip-Buffer variables */
int chan_amp[4]; /* current channel amplitudes in delta buffers */
} SN76489_Context;
static const int PSGVolumeValues[2][16] = {
static const int 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), normalised at 760 */
{1516,1205,957,760,603,479,381,303,240,191,152,120,96,76,60,0}
/*{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 struct blip_buffer_t* blip; /* delta resampler */
static SN76489_Context SN76489;
void SN76489_Init(float PSGClockValue, int SamplingRate)
void SN76489_Init(double PSGClockValue, int SamplingRate)
{
SN76489_Context *p = &SN76489;
p->dClock=PSGClockValue/16.0/SamplingRate;
SN76489_Config(MUTE_ALLON, VOL_FULL, FB_SEGAVDP, SRW_SEGAVDP, config.psgBoostNoise);
SN76489_Shutdown();
/* SamplingRate*16 instead of PSGClockValue/16 since division would lose some
precision. blip_alloc doesn't care about the absolute sampling rate, just the
ratio to clock rate. */
blip = blip_alloc(PSGClockValue, SamplingRate * 16.0, SamplingRate / 4);
}
void SN76489_Reset(void)
void SN76489_Reset()
{
SN76489_Context *p = &SN76489;
int i;
SN76489_Context *chip = &SN76489;
int i;
p->PSGStereo = 0xFF;
for(i = 0; i <= 3; i++)
{
/* Initialise PSG state */
chip->Registers[2*i] = 1; /* tone freq=1 */
chip->Registers[2*i+1] = 0xf; /* vol=off */
for(i = 0; i <= 3; i++)
{
/* Initialise PSG state */
p->Registers[2*i] = 1; /* tone freq=1 */
p->Registers[2*i+1] = 0xf; /* vol=off */
p->NoiseFreq = 0x10;
/* Set counters to 0 */
chip->ToneFreqVals[i] = 0;
/* Set counters to 0 */
p->ToneFreqVals[i] = 0;
/* Set flip-flops to 1 */
chip->ToneFreqPos[i] = 1;
/* Set flip-flops to 1 */
p->ToneFreqPos[i] = 1;
/* Clear channels output */
chip->Channels[i] = 0;
/* Set intermediate positions to do-not-use value */
p->IntermediatePos[i] = FLT_MIN;
/* Clear current amplitudes in delta buffer */
chip->chan_amp[i] = 0;
}
/* Set panning to centre */
p->panning[0]=127;
}
chip->LatchedRegister=0;
p->LatchedRegister=0;
/* Initialise noise generator */
p->NoiseShiftRegister=NoiseInitialState;
/* Zero clock */
p->Clock=0;
/* Initialise noise generator */
chip->NoiseShiftRegister=NoiseInitialState;
chip->NoiseFreq = 0x10;
/* Clear Blip delta buffer */
if (blip) blip_clear(blip);
}
void SN76489_Shutdown(void)
{
if (blip) blip_free(blip);
blip = NULL;
}
void SN76489_BoostNoise(int boost)
{
SN76489.BoostNoise = boost;
}
void SN76489_Config(int mute, int volume, int feedback, int sr_width, int boost_noise)
{
SN76489_Context *p = &SN76489;
p->Mute = mute;
p->VolumeArray = volume;
p->WhiteNoiseFeedback = feedback;
p->SRWidth = sr_width;
p->BoostNoise = boost_noise;
SN76489.Channels[3]= PSGVolumeValues[SN76489.Registers[7]] << boost;
}
void SN76489_SetContext(uint8 *data)
{
memcpy(&SN76489, data, sizeof(SN76489_Context));
memcpy(&SN76489, data, sizeof(SN76489_Context));
}
void SN76489_GetContext(uint8 *data)
{
memcpy(data, &SN76489, sizeof(SN76489_Context));
memcpy(data, &SN76489, sizeof(SN76489_Context));
}
uint8 *SN76489_GetContextPtr(void)
{
return (uint8 *)&SN76489;
return (uint8 *)&SN76489;
}
int SN76489_GetContextSize(void)
{
return sizeof(SN76489_Context);
return sizeof(SN76489_Context);
}
void SN76489_Write(int data)
{
SN76489_Context *p = &SN76489;
SN76489_Context *chip = &SN76489;
if (data&0x80) {
/* Latch/data byte %1 cc t dddd */
p->LatchedRegister=((data>>4)&0x07);
p->Registers[p->LatchedRegister]=
(p->Registers[p->LatchedRegister] & 0x3f0) /* zero low 4 bits */
| (data&0xf); /* and replace with data */
} else {
/* Data byte %0 - dddddd */
if (!(p->LatchedRegister%2)&&(p->LatchedRegister<5))
/* Tone register */
p->Registers[p->LatchedRegister]=
(p->Registers[p->LatchedRegister] & 0x00f) /* zero high 6 bits */
| ((data&0x3f)<<4); /* and replace with data */
else
/* Other register */
p->Registers[p->LatchedRegister]=data&0x0f; /* Replace with data */
}
switch (p->LatchedRegister) {
case 0:
case 2:
if (data & 0x80)
{
/* Latch byte %1 cc t dddd */
chip->LatchedRegister = (data >> 4) & 0x07;
}
int LatchedRegister = chip->LatchedRegister;
switch (LatchedRegister)
{
case 0:
case 2:
case 4: /* Tone channels */
if (p->Registers[p->LatchedRegister]==0) p->Registers[p->LatchedRegister]=1; /* Zero frequency changed to 1 to avoid div/0 */
break;
if (data & 0x80)
{
/* Data byte %1 cc t dddd */
chip->Registers[LatchedRegister] = (chip->Registers[LatchedRegister] & 0x3f0) | (data & 0xf);
}
else
{
/* Data byte %0 - dddddd */
chip->Registers[LatchedRegister] = (chip->Registers[LatchedRegister] & 0x00f) | ((data & 0x3f) << 4);
}
/* Zero frequency changed to 1 to avoid div/0 */
if (chip->Registers[LatchedRegister] == 0) chip->Registers[LatchedRegister] = 1;
break;
case 1:
case 3:
case 5: /* Channel attenuation */
chip->Registers[LatchedRegister] = data & 0x0f;
chip->Channels[LatchedRegister>>1] = PSGVolumeValues[data&0x0f];
break;
case 6: /* Noise */
p->NoiseShiftRegister=NoiseInitialState; /* reset shift register */
p->NoiseFreq=0x10<<(p->Registers[6]&0x3); /* set noise signal generator frequency */
break;
}
chip->Registers[6] = data & 0x0f;
chip->NoiseShiftRegister = NoiseInitialState; /* reset shift register */
chip->NoiseFreq = 0x10 << (data&0x3); /* set noise signal generator frequency */
break;
case 7: /* Noise attenuation */
chip->Registers[7] = data&0x0f;
chip->Channels[3] = PSGVolumeValues[data&0x0f] << chip->BoostNoise;
break;
}
}
void SN76489_GGStereoWrite(int data)
/* Updates tone amplitude in delta buffer. Call whenever amplitude might have changed. */
static void UpdateToneAmplitude(SN76489_Context* chip, int i, int time)
{
SN76489_Context *p = &SN76489;
p->PSGStereo=data;
int delta = (chip->Channels[i] * chip->ToneFreqPos[i]) - chip->chan_amp[i];
if (delta != 0)
{
chip->chan_amp[i] += delta;
blip_add(blip, time, delta);
}
}
/* Updates noise amplitude in delta buffer. Call whenever amplitude might have changed. */
static void UpdateNoiseAmplitude(SN76489_Context* chip, int time)
{
int delta = (chip->Channels[3] * ( chip->NoiseShiftRegister & 0x1 )) - chip->chan_amp[3];
if (delta != 0)
{
chip->chan_amp[3] += delta;
blip_add(blip, time, delta);
}
}
/* Runs tone channel for clock_length clocks */
static void RunTone(SN76489_Context* chip, int i, int clock_length)
{
int time;
/* Update in case a register changed etc. */
UpdateToneAmplitude(chip, i, 0);
/* Time of next transition */
time = chip->ToneFreqVals[i];
/* Process any transitions that occur within clocks we're running */
while (time < clock_length)
{
if (chip->Registers[i*2]>PSG_CUTOFF) {
/* Flip the flip-flop */
chip->ToneFreqPos[i] = -chip->ToneFreqPos[i];
} else {
/* stuck value */
chip->ToneFreqPos[i] = 1;
}
UpdateToneAmplitude(chip, i, time);
/* Advance to time of next transition */
time += chip->Registers[i*2];
}
/* Calculate new value for register, now that next transition is past number of clocks we're running */
chip->ToneFreqVals[i] = time - clock_length;
}
/* Runs noise channel for clock_length clocks */
static void RunNoise(SN76489_Context* chip, int clock_length)
{
int time;
/* Noise channel: match to tone2 if in slave mode */
int NoiseFreq = chip->NoiseFreq;
if (NoiseFreq == 0x80)
{
NoiseFreq = chip->Registers[2*2];
chip->ToneFreqVals[3] = chip->ToneFreqVals[2];
}
/* Update in case a register changed etc. */
UpdateNoiseAmplitude(chip, 0);
/* Time of next transition */
time = chip->ToneFreqVals[3];
/* Process any transitions that occur within clocks we're running */
while ( time < clock_length )
{
/* Flip the flip-flop */
chip->ToneFreqPos[3] = -chip->ToneFreqPos[3];
if (chip->ToneFreqPos[3] == 1)
{
/* On the positive edge of the square wave (only once per cycle) */
int Feedback = chip->NoiseShiftRegister;
if ( chip->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 & FB_SEGAVDP) && ((Feedback & FB_SEGAVDP) ^ FB_SEGAVDP));
}
else /* Periodic noise */
Feedback = Feedback & 1;
chip->NoiseShiftRegister = (chip->NoiseShiftRegister >> 1) | (Feedback << (SRW_SEGAVDP - 1));
UpdateNoiseAmplitude(chip, time);
}
/* Advance to time of next transition */
time += NoiseFreq;
}
/* Calculate new value for register, now that next transition is past number of clocks we're running */
chip->ToneFreqVals[3] = time - clock_length;
}
void SN76489_Update(INT16 *buffer, int length)
{
SN76489_Context *p = &SN76489;
int i, j;
int i;
for(j = 0; j < length; j++)
{
for (i=0;i<=2;++i)
if (p->IntermediatePos[i]!=FLT_MIN)
p->Channels[i]=(short)((p->Mute >> i & 0x1)*PSGVolumeValues[p->VolumeArray][p->Registers[2*i+1]]*p->IntermediatePos[i]);
else
p->Channels[i]=(p->Mute >> i & 0x1)*PSGVolumeValues[p->VolumeArray][p->Registers[2*i+1]]*p->ToneFreqPos[i];
p->Channels[3]=(short)((p->Mute >> 3 & 0x1)*PSGVolumeValues[p->VolumeArray][p->Registers[7]]*(p->NoiseShiftRegister & 0x1));
if (p->BoostNoise) p->Channels[3]<<=1; /* double noise volume */
buffer[j] =0;
for (i=0;i<=3;++i) buffer[j] += p->Channels[i];
p->Clock+=p->dClock;
p->NumClocksForSample=(int)p->Clock; /* truncates */
p->Clock-=p->NumClocksForSample; /* remove integer part */
/* Looks nicer in Delphi... */
/* Clock:=Clock+p->dClock; */
/* NumClocksForSample:=Trunc(Clock); */
/* Clock:=Frac(Clock); */
/* Decrement tone channel counters */
for (i=0;i<=2;++i)
p->ToneFreqVals[i]-=p->NumClocksForSample;
/* Noise channel: match to tone2 or decrement its counter */
if (p->NoiseFreq==0x80) p->ToneFreqVals[3]=p->ToneFreqVals[2];
else p->ToneFreqVals[3]-=p->NumClocksForSample;
/* Tone channels: */
for (i=0;i<=2;++i) {
if (p->ToneFreqVals[i]<=0) { /* If it gets below 0... */
if (p->Registers[i*2]>PSG_CUTOFF) {
/* Calculate how much of the sample is + and how much is - */
/* Go to floating point and include the clock fraction for extreme accuracy :D */
/* Store as long int, maybe it's faster? I'm not very good at this */
p->IntermediatePos[i]=(p->NumClocksForSample-p->Clock+2*p->ToneFreqVals[i])*p->ToneFreqPos[i]/(p->NumClocksForSample+p->Clock);
p->ToneFreqPos[i]=-p->ToneFreqPos[i]; /* Flip the flip-flop */
} else {
p->ToneFreqPos[i]=1; /* stuck value */
p->IntermediatePos[i]=FLT_MIN;
}
p->ToneFreqVals[i]+=p->Registers[i*2]*(p->NumClocksForSample/p->Registers[i*2]+1);
} else p->IntermediatePos[i]=FLT_MIN;
}
/* Noise channel */
if (p->ToneFreqVals[3]<=0) { /* If it gets below 0... */
p->ToneFreqPos[3]=-p->ToneFreqPos[3]; /* Flip the flip-flop */
if (p->NoiseFreq!=0x80) /* If not matching tone2, decrement counter */
p->ToneFreqVals[3]+=p->NoiseFreq*(p->NumClocksForSample/p->NoiseFreq+1);
if (p->ToneFreqPos[3]==1) { /* Only once per cycle... */
int Feedback;
if (p->Registers[6]&0x4) { /* White noise */
/* Calculate parity of fed-back bits for feedback */
switch (p->WhiteNoiseFeedback) {
/* Do some optimised calculations for common (known) feedback values */
case 0x0003: /* SC-3000, BBC %00000011 */
case 0x0009: /* SMS, GG, MD %00001001 */
/* 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=((p->NoiseShiftRegister&p->WhiteNoiseFeedback) && ((p->NoiseShiftRegister&p->WhiteNoiseFeedback)^p->WhiteNoiseFeedback));
break;
default: /* Default handler for all other feedback values */
Feedback=p->NoiseShiftRegister&p->WhiteNoiseFeedback;
Feedback^=Feedback>>8;
Feedback^=Feedback>>4;
Feedback^=Feedback>>2;
Feedback^=Feedback>>1;
Feedback&=1;
break;
}
} else /* Periodic noise */
Feedback=p->NoiseShiftRegister&1;
p->NoiseShiftRegister=(p->NoiseShiftRegister>>1) | (Feedback << (p->SRWidth-1));
/* Original code: */
/* p->NoiseShiftRegister=(p->NoiseShiftRegister>>1) | ((p->Registers[6]&0x4?((p->NoiseShiftRegister&0x9) && (p->NoiseShiftRegister&0x9^0x9)):p->NoiseShiftRegister&1)<<15); */
}
}
}
}
/*void SN76489_UpdateOne(int which, int *l, int *r)
{
INT16 tl,tr;
INT16 *buff[2]={&tl,&tr};
SN76489_Update(which,buff,1);
*l=tl;
*r=tr;
}*/
int SN76489_GetMute()
{
return SN76489.Mute;
}
void SN76489_SetMute(int val)
{
SN76489.Mute=val;
}
int SN76489_GetVolType()
{
return SN76489.VolumeArray;
}
void SN76489_SetVolType(int val)
{
SN76489.VolumeArray=val;
}
void SN76489_SetPanning(int ch0, int ch1, int ch2, int ch3)
{
SN76489.panning[0]=ch0;
SN76489.panning[1]=ch1;
SN76489.panning[2]=ch2;
SN76489.panning[3]=ch3;
SN76489_Context *chip = &SN76489;
/* Determine how many clocks we need to run until 'length' samples are available */
int clock_length = blip_clocks_needed(blip, length);
/* Run noise first, since it might use current value of third tone frequency counter */
RunNoise(chip, clock_length);
/* Run tone channels */
for( i = 0; i <= 2; ++i )
RunTone(chip, i, clock_length);
/* Read samples into output buffer */
blip_end_frame(blip, clock_length);
blip_read_samples(blip, buffer, length, 0);
}

View File

@ -16,7 +16,7 @@
#define _SN76489_H_
/* Function prototypes */
extern void SN76489_Init(float PSGClockValue, int SamplingRate);
extern void SN76489_Init(double PSGClockValue, int SamplingRate);
extern void SN76489_Reset(void);
extern void SN76489_Shutdown(void);
extern void SN76489_SetContext(uint8 *data);
@ -26,7 +26,6 @@ extern int SN76489_GetContextSize(void);
extern void SN76489_Write(int data);
extern void SN76489_Update(INT16 *buffer, int length);
extern void SN76489_BoostNoise(int boost);
extern void SN76489_Config(int mute, int volume, int feedback, int sr_width, int boost_noise);
#endif /* _SN76489_H_ */

View File

@ -230,8 +230,7 @@ void fm_reset(unsigned int cycles)
/* Write FM chip */
void fm_write(unsigned int cycles, unsigned int address, unsigned int data)
{
if (address & 1)
fm_update(cycles << 11);
if (address & 1) fm_update(cycles << 11);
YM2612Write(address, data);
}

View File

@ -549,7 +549,7 @@ typedef struct
typedef struct
{
float clock; /* master clock (Hz) */
double clock; /* master clock (Hz) */
UINT32 rate; /* sampling rate (Hz) */
UINT16 address; /* address register */
UINT8 status; /* status flag */
@ -1921,7 +1921,7 @@ static void init_tables(void)
/* initialize ym2612 emulator(s) */
int YM2612Init(float clock, int rate)
int YM2612Init(double clock, int rate)
{
memset(&ym2612,0,sizeof(YM2612));
init_tables();
@ -2172,7 +2172,7 @@ unsigned int YM2612GetContextSize(void)
void YM2612Restore(unsigned char *buffer)
{
/* save current timings */
float clock = ym2612.OPN.ST.clock;
double clock = ym2612.OPN.ST.clock;
int rate = ym2612.OPN.ST.rate;
/* restore internal state */

View File

@ -19,7 +19,7 @@
#endif
extern int YM2612Init(float clock, int rate);
extern int YM2612Init(double clock, int rate);
extern int YM2612ResetChip(void);
extern void YM2612Update(long int *buffer, int length);
extern void YM2612Write(unsigned int a, unsigned int v);

View File

@ -311,6 +311,7 @@ void system_shutdown (void)
gen_shutdown ();
vdp_shutdown ();
render_shutdown ();
SN76489_Shutdown ();
}
/****************************************************************