cog/Frameworks/GME/vgmplay/chips/sn76489.c

405 lines
12 KiB
C

/*
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.
*/
#include <stdlib.h> // malloc/free
#include <float.h> // for FLT_MIN
#include <string.h> // for memcpy
#include "mamedef.h"
#include "sn76489.h"
#include "panning.h"
#define NoiseInitialState 0x8000 /* Initial state of shift register */
#define PSG_CUTOFF 0x6 /* Value below which PSG does not output */
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) */
/*1516,1205,957,760,603,479,381,303,240,191,152,120,96,76,60,0*/
// The MAME core uses 0x2000 as maximum volume (0x1000 for bipolar output)
4096, 3254, 2584, 2053, 1631, 1295, 1029, 817, 649, 516, 410, 325, 258, 205, 163, 0
};
/*static SN76489_Context SN76489[MAX_SN76489];*/
static SN76489_Context* LastChipInit = NULL;
//static unsigned short int FNumLimit;
SN76489_Context* SN76489_Init( int PSGClockValue, int SamplingRate)
{
int i;
SN76489_Context* chip = (SN76489_Context*)malloc(sizeof(SN76489_Context));
if(chip)
{
chip->dClock=(float)(PSGClockValue & 0x7FFFFFF)/16/SamplingRate;
SN76489_SetMute(chip, MUTE_ALLON);
SN76489_Config(chip, /*MUTE_ALLON,*/ FB_SEGAVDP, SRW_SEGAVDP, 1);
for( i = 0; i <= 3; i++ )
centre_panning(chip->panning[i]);
//SN76489_Reset(chip);
if ((PSGClockValue & 0x80000000) && LastChipInit != NULL)
{
// Activate special NeoGeoPocket Mode
LastChipInit->NgpFlags = 0x80 | 0x00;
chip->NgpFlags = 0x80 | 0x01;
chip->NgpChip2 = LastChipInit;
LastChipInit->NgpChip2 = chip;
LastChipInit = NULL;
}
else
{
chip->NgpFlags = 0x00;
chip->NgpChip2 = NULL;
LastChipInit = chip;
}
}
return chip;
}
void SN76489_Reset(SN76489_Context* chip)
{
int i;
chip->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 */
chip->NoiseFreq = 0x10;
/* Set counters to 0 */
chip->ToneFreqVals[i] = 0;
/* Set flip-flops to 1 */
chip->ToneFreqPos[i] = 1;
/* Set intermediate positions to do-not-use value */
chip->IntermediatePos[i] = FLT_MIN;
/* Set panning to centre */
//centre_panning( chip->panning[i] );
}
chip->LatchedRegister = 0;
/* Initialise noise generator */
chip->NoiseShiftRegister = NoiseInitialState;
/* Zero clock */
chip->Clock = 0;
}
void SN76489_Shutdown(SN76489_Context* chip)
{
free(chip);
}
void SN76489_Config(SN76489_Context* chip, /*int mute,*/ int feedback, int sr_width, int boost_noise)
{
//chip->Mute = mute;
chip->WhiteNoiseFeedback = feedback;
chip->SRWidth = sr_width;
}
/*
void SN76489_SetContext(int which, uint8 *data)
{
memcpy( &SN76489[which], data, sizeof(SN76489_Context) );
}
void SN76489_GetContext(int which, uint8 *data)
{
memcpy( data, &SN76489[which], sizeof(SN76489_Context) );
}
uint8 *SN76489_GetContextPtr(int which)
{
return (uint8 *)&SN76489[which];
}
int SN76489_GetContextSize(void)
{
return sizeof(SN76489_Context);
}
*/
void SN76489_Write(SN76489_Context* chip, int data)
{
if ( data & 0x80 )
{
/* Latch/data byte %1 cc t dddd */
chip->LatchedRegister = ( data >> 4 ) & 0x07;
chip->Registers[chip->LatchedRegister] =
( chip->Registers[chip->LatchedRegister] & 0x3f0 ) /* zero low 4 bits */
| ( data & 0xf ); /* and replace with data */
} else {
/* Data byte %0 - dddddd */
if ( !( chip->LatchedRegister % 2 ) && ( chip->LatchedRegister < 5 ) )
/* Tone register */
chip->Registers[chip->LatchedRegister] =
( chip->Registers[chip->LatchedRegister] & 0x00f) /* zero high 6 bits */
| ( ( data & 0x3f ) << 4 ); /* and replace with data */
else
/* Other register */
chip->Registers[chip->LatchedRegister]=data&0x0f; /* Replace with data */
}
switch (chip->LatchedRegister) {
case 0:
case 2:
case 4: /* Tone channels */
if ( chip->Registers[chip->LatchedRegister] == 0 )
chip->Registers[chip->LatchedRegister] = 1; /* Zero frequency changed to 1 to avoid div/0 */
break;
case 6: /* Noise */
chip->NoiseShiftRegister = NoiseInitialState; /* reset shift register */
chip->NoiseFreq = 0x10 << ( chip->Registers[6] & 0x3 ); /* set noise signal generator frequency */
break;
}
}
void SN76489_GGStereoWrite(SN76489_Context* chip, int data)
{
chip->PSGStereo=data;
}
//void SN76489_Update(SN76489_Context* chip, INT16 **buffer, int length)
void SN76489_Update(SN76489_Context* chip, INT32 **buffer, int length)
{
int i, j;
int NGPMode;
SN76489_Context* chip2;
SN76489_Context* chip_t;
SN76489_Context* chip_n;
NGPMode = (chip->NgpFlags >> 7) & 0x01;
if (NGPMode)
chip2 = (SN76489_Context*)chip->NgpChip2;
if (! NGPMode)
{
chip_t = chip_n = chip;
}
else
{
if (! (chip->NgpFlags & 0x01))
{
chip_t = chip;
chip_n = chip2;
}
else
{
chip_t = chip2;
chip_n = chip;
}
}
for( j = 0; j < length; j++ )
{
/* Tone channels */
for ( i = 0; i <= 2; ++i )
if ( (chip_t->Mute >> i) & 1 )
{
if ( chip_t->IntermediatePos[i] != FLT_MIN )
/* Intermediate position (antialiasing) */
chip->Channels[i] = (short)( PSGVolumeValues[chip->Registers[2 * i + 1]] * chip_t->IntermediatePos[i] );
else
/* Flat (no antialiasing needed) */
chip->Channels[i]= PSGVolumeValues[chip->Registers[2 * i + 1]] * chip_t->ToneFreqPos[i];
}
else
/* Muted channel */
chip->Channels[i] = 0;
/* Noise channel */
if ( (chip_t->Mute >> 3) & 1 )
{
//chip->Channels[3] = PSGVolumeValues[chip->Registers[7]] * ( chip_n->NoiseShiftRegister & 0x1 ) * 2; /* double noise volume */
// Now the noise is bipolar, too. -Valley Bell
chip->Channels[3] = PSGVolumeValues[chip->Registers[7]] * (( chip_n->NoiseShiftRegister & 0x1 ) * 2 - 1);
// due to the way the white noise works here, it seems twice as loud as it should be
if (chip->Registers[6] & 0x4 )
chip->Channels[3] >>= 1;
}
else
chip->Channels[i] = 0;
// Build stereo result into buffer
buffer[0][j] = 0;
buffer[1][j] = 0;
if (! chip->NgpFlags)
{
// For all 4 channels
for ( i = 0; i <= 3; ++i )
{
if ( ( ( chip->PSGStereo >> i ) & 0x11 ) == 0x11 )
{
// no GG stereo for this channel
if ( chip->panning[i][0] == 1.0f )
{
buffer[0][j] += chip->Channels[i]; // left
buffer[1][j] += chip->Channels[i]; // right
}
else
{
buffer[0][j] += (INT32)( chip->panning[i][0] * chip->Channels[i] ); // left
buffer[1][j] += (INT32)( chip->panning[i][1] * chip->Channels[i] ); // right
}
}
else
{
// GG stereo overrides panning
buffer[0][j] += ( chip->PSGStereo >> (i+4) & 0x1 ) * chip->Channels[i]; // left
buffer[1][j] += ( chip->PSGStereo >> i & 0x1 ) * chip->Channels[i]; // right
}
}
}
else
{
if (! (chip->NgpFlags & 0x01))
{
// For all 3 tone channels
for (i = 0; i < 3; i ++)
{
buffer[0][j] += (chip->PSGStereo >> (i+4) & 0x1 ) * chip ->Channels[i]; // left
buffer[1][j] += (chip->PSGStereo >> i & 0x1 ) * chip2->Channels[i]; // right
}
}
else
{
// noise channel
i = 3;
buffer[0][j] += (chip->PSGStereo >> (i+4) & 0x1 ) * chip2->Channels[i]; // left
buffer[1][j] += (chip->PSGStereo >> i & 0x1 ) * chip ->Channels[i]; // right
}
}
/* Increment clock by 1 sample length */
chip->Clock += chip->dClock;
chip->NumClocksForSample = (int)chip->Clock; /* truncate */
chip->Clock -= chip->NumClocksForSample; /* remove integer part */
/* Decrement tone channel counters */
for ( i = 0; i <= 2; ++i )
chip->ToneFreqVals[i] -= chip->NumClocksForSample;
/* Noise channel: match to tone2 or decrement its counter */
if ( chip->NoiseFreq == 0x80 )
chip->ToneFreqVals[3] = chip->ToneFreqVals[2];
else
chip->ToneFreqVals[3] -= chip->NumClocksForSample;
/* Tone channels: */
for ( i = 0; i <= 2; ++i ) {
if ( chip->ToneFreqVals[i] <= 0 ) { /* If the counter gets below 0... */
if (chip->Registers[i*2]>=PSG_CUTOFF) {
/* For tone-generating values, calculate how much of the sample is + and how much is - */
/* This is optimised into an even more confusing state than it was in the first place... */
chip->IntermediatePos[i] = ( chip->NumClocksForSample - chip->Clock + 2 * chip->ToneFreqVals[i] ) * chip->ToneFreqPos[i] / ( chip->NumClocksForSample + chip->Clock );
/* Flip the flip-flop */
chip->ToneFreqPos[i] = -chip->ToneFreqPos[i];
} else {
/* stuck value */
chip->ToneFreqPos[i] = 1;
chip->IntermediatePos[i] = FLT_MIN;
}
chip->ToneFreqVals[i] += chip->Registers[i*2] * ( chip->NumClocksForSample / chip->Registers[i*2] + 1 );
}
else
/* signal no antialiasing needed */
chip->IntermediatePos[i] = FLT_MIN;
}
/* Noise channel */
if ( chip->ToneFreqVals[3] <= 0 ) {
/* If the counter gets below 0... */
/* Flip the flip-flop */
chip->ToneFreqPos[3] = -chip->ToneFreqPos[3];
if (chip->NoiseFreq != 0x80)
/* If not matching tone2, decrement counter */
chip->ToneFreqVals[3] += chip->NoiseFreq * ( chip->NumClocksForSample / chip->NoiseFreq + 1 );
if (chip->ToneFreqPos[3] == 1) {
/* On the positive edge of the square wave (only once per cycle) */
int Feedback;
if ( chip->Registers[6] & 0x4 ) {
/* White noise */
/* Calculate parity of fed-back bits for feedback */
switch (chip->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 = ( ( chip->NoiseShiftRegister & chip->WhiteNoiseFeedback )
&& ( (chip->NoiseShiftRegister & chip->WhiteNoiseFeedback ) ^ chip->WhiteNoiseFeedback ) );
break;
default:
/* Default handler for all other feedback values */
/* XOR fold bits into the final bit */
Feedback = chip->NoiseShiftRegister & chip->WhiteNoiseFeedback;
Feedback ^= Feedback >> 8;
Feedback ^= Feedback >> 4;
Feedback ^= Feedback >> 2;
Feedback ^= Feedback >> 1;
Feedback &= 1;
break;
}
} else /* Periodic noise */
Feedback=chip->NoiseShiftRegister&1;
chip->NoiseShiftRegister=(chip->NoiseShiftRegister>>1) | (Feedback << (chip->SRWidth-1));
}
}
}
}
/*void SN76489_UpdateOne(SN76489_Context* chip, int *l, int *r)
{
INT16 tl,tr;
INT16 *buff[2] = { &tl, &tr };
SN76489_Update( chip, buff, 1 );
*l = tl;
*r = tr;
}*/
/*int SN76489_GetMute(SN76489_Context* chip)
{
return chip->Mute;
}*/
void SN76489_SetMute(SN76489_Context* chip, int val)
{
chip->Mute=val;
}
void SN76489_SetPanning(SN76489_Context* chip, int ch0, int ch1, int ch2, int ch3)
{
calc_panning( chip->panning[0], ch0 );
calc_panning( chip->panning[1], ch1 );
calc_panning( chip->panning[2], ch2 );
calc_panning( chip->panning[3], ch3 );
}