405 lines
12 KiB
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 );
|
|
}
|