Fixed sn76489a periodic noise once again (and simplified a bit of code), now should be closer to or match real chips as sampled.

Added true clock divider emulation, so emulation may be a bit slower. [Lord Nightmare, PlgDavid, Kold666]
This commit is contained in:
Jonathan Gevaryahu 2010-01-24 10:38:09 +00:00
parent 730d70ee06
commit f79dfb098a

View File

@ -19,8 +19,10 @@
** SN76489A uses a 15-bit shift register with taps on bits D and E, output on F,
XNOR function
It uses a 15-bit ring buffer for periodic noise/arbitrary duty cycle.
** SN76494 and SN76496 are PROBABLY identical in operation to the SN76489A
They have an audio input line which is mixed with the 4 channels of output.
** SN76494 is the same as SN76489A but lacks the /8 divider on its clock input.
** SN76496 is PROBABLY identical in operation to the SN76489A, need more info.
All the SN7xxxx chips have an audio input line which is mixed with the 4 channels
of output.
** Sega Master System III/MD/Genesis PSG uses a 16-bit shift register with taps
on bits C and F, output on F
It uses a 16-bit ring buffer for periodic noise/arbitrary duty cycle.
@ -70,24 +72,23 @@
based on testing. Got rid of R->OldNoise and fixed taps accordingly.
Added stereo support for game gear.
11/15/2010 : Lord Nightmare
15/01/2010 : Lord Nightmare
Fix an issue with SN76489 and SN76489A having the wrong periodic noise periods.
Note that properly emulating the noise cycle bit timing accurately may require
extensive rewriting.
24/01/2010: Lord Nightmare
Implement periodic noise as forcing one of the XNOR or XOR taps to 1 or 0 respectively.
Thanks to PlgDavid for providing samples which helped immensely here.
Added true clock divider emulation, so sn94624 and sn76494 run 8x faster than
the others, as in real life.
TODO: * Implement the TMS9919 which is an earlier version, possibly
lacking the /8 clock divider, of the SN76489, and hence would
have a max clock of 500Khz and 4 clocks per sample, as opposed to
max of 4Mhz and 32 clocks per sample on the SN76489A.
TODO: * Implement the TMS9919 - any difference to sn94624?
* Implement the T6W28; has registers in a weird order, needs writes
to be 'sanitized' first. Also is stereo, similar to game gear.
* Test the NCR7496; Smspower says the whitenoise taps are A and E,
but this needs verification on real hardware.
* Factor out common code so that the SAA1099 can share some code.
* Implement periodic noise as using a pointer to a table rather than a
self feeding lfsr. Kold666's recording of dorunrun proves this is the
right way.
* Fix the whitenoise bit timing so that dorunrun matches Kold666's PCB sample.
***************************************************************************/
#include "emu.h"
@ -96,7 +97,6 @@
#define MAX_OUTPUT 0x7fff
// make the above 0x3fff if using bipolar output
#define NOISEMODE (R->Register[6]&4)?1:0
@ -109,8 +109,11 @@ struct _sn76496_state
INT32 LastRegister; /* last register written */
INT32 Volume[4]; /* db volume of voice 0-2 and noise */
UINT32 RNG; /* noise generator LFSR*/
INT32 ClockDivider; /* clock divider */
INT32 CurrentClock;
INT32 FeedbackMask; /* mask for feedback */
INT32 WhitenoiseTaps; /* mask for white noise taps */
INT32 WhitenoiseTap1; /* mask for white noise tap 1 (higher one, usually bit 14) */
INT32 WhitenoiseTap2; /* mask for white noise tap 2 (lower one, usually bit 13)*/
INT32 FeedbackInvert; /* feedback invert flag (xor vs xnor) */
INT32 Negate; /* output negate flag */
INT32 Stereo; /* whether we're dealing with stereo or not */
@ -207,11 +210,11 @@ WRITE8_DEVICE_HANDLER( sn76496_w )
break;
case 6: /* noise : frequency, mode */
{
if ((data & 0x80) == 0) R->Register[r] = (R->Register[r] & 0x3f0) | (data & 0x0f);
if ((data & 0x80) == 0) logerror("sn76489: write to reg 6 with bit 7 clear; data was %03x, new write is %02x! report this to LN!\n", R->Register[6], data);
if ((data & 0x80) == 0) R->Register[r] = (R->Register[r] & 0x3f0) | (data & 0x0f);
n = R->Register[6];
/* N/512,N/1024,N/2048,Tone #3 output */
R->Period[3] = ((n&3) == 3) ? 2 * R->Period[2] : (1 << (5+(n&3)));
/* Reset noise shifter */
R->RNG = R->FeedbackMask;
R->Output[3] = R->RNG & 1;
}
@ -225,34 +228,40 @@ static STREAM_UPDATE( SN76496Update )
sn76496_state *R = (sn76496_state *)param;
stream_sample_t *lbuffer = outputs[0];
stream_sample_t *rbuffer = (R->Stereo)?outputs[1]:NULL;
INT16 out = 0;
INT16 out2 = 0;
while (samples > 0)
{
// clock chip once
INT16 out = 0;
INT16 out2 = 0;
/* decrement Cycles to READY by one */
if (R->CyclestoREADY >0) R->CyclestoREADY--;
// handle channels 0,1,2
for (i = 0;i < 3;i++)
// clock chip once
if (R->CurrentClock > 0) // not ready for new divided clock
{
R->Count[i]--;
if (R->Count[i] < 0)
{
R->Output[i] ^= 1;
R->Count[i] = R->Period[i];
}
R->CurrentClock--;
}
// handle channel 3
R->Count[3]--;
if (R->Count[3] <= 0)
else // ready for new divided clock, make a new sample
{
if (NOISEMODE == 1) /* White Noise Mode */
R->CurrentClock = R->ClockDivider-1;
/* decrement Cycles to READY by one */
if (R->CyclestoREADY >0) R->CyclestoREADY--;
// handle channels 0,1,2
for (i = 0;i < 3;i++)
{
if ((((R->RNG & R->WhitenoiseTaps) != R->WhitenoiseTaps) && ((R->RNG & R->WhitenoiseTaps) != 0)) ^ R->FeedbackInvert ) /* XOR or XNOR */
R->Count[i]--;
if (R->Count[i] < 0)
{
R->Output[i] ^= 1;
R->Count[i] = R->Period[i];
}
}
// handle channel 3
R->Count[3]--;
if (R->Count[3] <= 0)
{
// if noisemode is 1, both taps are enabled
// if noisemode is 0, the lower tap, whitenoisetap2, is held at 1 or 0 depending on whether FeedbackInvert is set or clear
if (((R->RNG & R->WhitenoiseTap1)?1:0) ^ (((((R->RNG & R->WhitenoiseTap2)?0:1))*(NOISEMODE))^R->FeedbackInvert) ^ R->FeedbackInvert )
{
R->RNG >>= 1;
R->RNG |= R->FeedbackMask;
@ -261,28 +270,13 @@ static STREAM_UPDATE( SN76496Update )
{
R->RNG >>= 1;
}
R->Output[3] = R->RNG & 1;
R->Count[3] = R->Period[3];
}
else /* Periodic noise mode */
{
if (R->RNG & 1)
{
R->RNG >>= 1;
R->RNG |= R->FeedbackMask;
}
else
{
R->RNG >>= 1;
}
}
R->Output[3] = R->RNG & 1;
R->Count[3] = R->Period[3];
}
/* //bipolar output, doesn't seem to work right with sonic 2 on gamegear at least
out = (R->Output[0]?R->Volume[0]:(0-R->Volume[0]))
+(R->Output[1]?R->Volume[1]:(0-R->Volume[1]))
+(R->Output[2]?R->Volume[2]:(0-R->Volume[2]))
+(R->Output[3]?R->Volume[3]:(0-R->Volume[3]));
*/
if (R->Stereo)
{
out = (((R->StereoMask&0x10)&&R->Output[0])?R->Volume[0]:0)
@ -342,7 +336,7 @@ static void SN76496_set_gain(sn76496_state *R,int gain)
static int SN76496_init(running_device *device, sn76496_state *R, int stereo)
{
int sample_rate = device->clock/16;
int sample_rate = device->clock/2;
int i;
R->Channel = stream_create(device,0,(stereo?2:1),sample_rate,R,SN76496Update);
@ -361,13 +355,15 @@ static int SN76496_init(running_device *device, sn76496_state *R, int stereo)
R->Output[i] = R->Period[i] = R->Count[i] = 0;
}
/* Default is SN76489 non-A */
R->FeedbackMask = 0x4000; /* mask for feedback */
R->WhitenoiseTaps = 0x03; /* mask for white noise taps */
R->FeedbackInvert = 0; /* feedback invert flag */
R->CyclestoREADY = 1; /* assume ready is not active immediately on init. is this correct?*/
/* Default is SN76489A */
R->ClockDivider = 8;
R->FeedbackMask = 0x10000; /* mask for feedback */
R->WhitenoiseTap1 = 0x04; /* mask for white noise tap 1*/
R->WhitenoiseTap2 = 0x08; /* mask for white noise tap 2*/
R->FeedbackInvert = 1; /* feedback invert flag */
R->Negate = 0; /* channels are not negated */
R->Stereo = stereo; /* depends on init */
R->CyclestoREADY = 1; /* assume ready is not active immediately on init. is this correct?*/
R->StereoMask = 0xFF; /* all channels enabled */
R->RNG = R->FeedbackMask;
@ -377,7 +373,7 @@ static int SN76496_init(running_device *device, sn76496_state *R, int stereo)
}
static void generic_start(running_device *device, int feedbackmask, int noisetaps, int feedbackinvert, int negate, int stereo)
static void generic_start(running_device *device, int feedbackmask, int noisetap1, int noisetap2, int feedbackinvert, int negate, int stereo, int clockdivider)
{
sn76496_state *chip = get_safe_token(device);
@ -386,18 +382,24 @@ static void generic_start(running_device *device, int feedbackmask, int noisetap
SN76496_set_gain(chip, 0);
chip->FeedbackMask = feedbackmask;
chip->WhitenoiseTaps = noisetaps;
chip->WhitenoiseTap1 = noisetap1;
chip->WhitenoiseTap2 = noisetap2;
chip->FeedbackInvert = feedbackinvert;
chip->Negate = negate;
chip->Stereo = stereo;
chip->ClockDivider = clockdivider;
chip->CurrentClock = clockdivider-1;
state_save_register_device_item_array(device, 0, chip->VolTable);
state_save_register_device_item_array(device, 0, chip->Register);
state_save_register_device_item(device, 0, chip->LastRegister);
state_save_register_device_item_array(device, 0, chip->Volume);
state_save_register_device_item(device, 0, chip->RNG);
state_save_register_device_item(device, 0, chip->ClockDivider);
state_save_register_device_item(device, 0, chip->CurrentClock);
state_save_register_device_item(device, 0, chip->FeedbackMask);
state_save_register_device_item(device, 0, chip->WhitenoiseTaps);
state_save_register_device_item(device, 0, chip->WhitenoiseTap1);
state_save_register_device_item(device, 0, chip->WhitenoiseTap2);
state_save_register_device_item(device, 0, chip->FeedbackInvert);
state_save_register_device_item(device, 0, chip->Negate);
state_save_register_device_item(device, 0, chip->Stereo);
@ -408,46 +410,46 @@ static void generic_start(running_device *device, int feedbackmask, int noisetap
state_save_register_device_item(device, 0, chip->CyclestoREADY);
}
// function parameters: device, feedback destination tap, feedback source taps, xor(false)/xnor(true), normal(false)/invert(true), mono(false)/stereo(true)
// function parameters: device, feedback destination tap, feedback source taps, xor(false)/xnor(true), normal(false)/invert(true), mono(false)/stereo(true), clock divider factor
static DEVICE_START( sn76489 )
{
generic_start(device, 0x4000, 0x03, FALSE, FALSE, FALSE); // todo: verify; assumed to be the same as sn94624
generic_start(device, 0x10000, 0x04, 0x08, FALSE, FALSE, FALSE, 8); // SN76489 not verified yet. todo: verify;
}
static DEVICE_START( sn76489a )
{
generic_start(device, 0x4000, 0x03, TRUE, FALSE, FALSE); // verified by plgdavid
generic_start(device, 0x10000, 0x04, 0x08, TRUE, FALSE, FALSE, 8); // SN76489A: whitenoise verified, phase verified, periodic verified (by plgdavid)
}
static DEVICE_START( sn76494 )
{
generic_start(device, 0x4000, 0x03, TRUE, FALSE, FALSE); // todo: verify; assumed to be the same as sn76489a
generic_start(device, 0x10000, 0x04, 0x08, TRUE, FALSE, FALSE, 1); // SN76494 not verified, (according to datasheet: same as sn76489a but without the /8 divider)
}
static DEVICE_START( sn76496 )
{
generic_start(device, 0x4000, 0x03, TRUE, FALSE, FALSE); // todo: verify; assumed to be the same as sn76489a
generic_start(device, 0x10000, 0x04, 0x08, TRUE, FALSE, FALSE, 8); // SN76496 not verified; assumed to be the same as sn76489a
}
static DEVICE_START( sn94624 )
{
generic_start(device, 0x4000, 0x03, FALSE, FALSE, FALSE); // verified by plgdavid
generic_start(device, 0x4000, 0x01, 0x02, FALSE, TRUE, FALSE, 1); // SN94624 whitenoise verified, phase verified, period verified; verified by PlgDavid
}
static DEVICE_START( ncr7496 )
{
generic_start(device, 0x8000, 0x22, FALSE, FALSE, FALSE); // todo: verify; from smspower wiki
generic_start(device, 0x8000, 0x02, 0x20, FALSE, FALSE, FALSE, 8); // NCR7496 not verified; info from smspower wiki
}
static DEVICE_START( gamegear )
{
generic_start(device, 0x8000, 0x09, FALSE, TRUE, TRUE); // Verified by Justin Kerk
generic_start(device, 0x8000, 0x01, 0x08, FALSE, TRUE, TRUE, 8); // Verified by Justin Kerk
}
static DEVICE_START( smsiii )
{
generic_start(device, 0x8000, 0x09, FALSE, TRUE, FALSE); // todo: verify; from smspower wiki, assumed to have same invert as gamegear
generic_start(device, 0x8000, 0x01, 0x08, FALSE, TRUE, FALSE, 8); // todo: verify; from smspower wiki, assumed to have same invert as gamegear
}