mirror of
https://github.com/holub/mame
synced 2025-05-29 00:53:09 +03:00
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:
parent
730d70ee06
commit
f79dfb098a
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user