/* 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 // malloc/free #include // for FLT_MIN #include // 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 ); }