mirror of
https://github.com/holub/mame
synced 2025-05-25 07:15:25 +03:00

integer value, regions are now referred to by a region class and a region tag. The class specifies the type of region (one of CPU, gfx, sound, user, disk, prom, pld) while the tag uniquely specifies the region. This change required updating all the ROM region definitions in the project to specify the class/tag instead of region number. Updated the core memory_region_* functions to accept a class/tag pair. Added new memory_region_next() function to allow for iteration over all memory regions of a given class. Added new function memory_region_class_name() to return the name for a given CPU memory region class. Changed the auto-binding behavior of CPU regions. Previously, the first CPU would auto-bind to REGION_CPU1 (that is, any ROM references would automatically assume that they lived in the corresponding region). Now, each CPU automatically binds to the RGNCLASS_CPU region with the same tag as the CPU itself. This behavior required ensuring that all previous REGION_CPU* regions were changed to RGNCLASS_CPU with the same tag as the CPU. Introduced a new auto-binding mechanism for sound cores. This works similarly to the CPU binding. Each sound core that requires a memory region now auto-binds to the RGNCLASS_SOUND with the same tag as the sound core. In almost all cases, this allowed for the removal of the explicit region item in the sound configuration, which in turn allowed for many sound configurations to removed altogether. Updated the expression engine's memory reference behavior. A recent update expanded the scope of memory references to allow for referencing data in non-active CPU spaces, in memory regions, and in EEPROMs. However, this previous update required an index, which is no longer appropriate for regions and will become increasingly less appropriate for CPUs over time. Instead, a new syntax is supported, of the form: "[tag.][space]size@addr", where 'tag' is an optional tag for the CPU or memory region you wish to access, followed by a period as a separator; 'space' is the memory address space or region class you wish to access (p/d/i for program/data/I/O spaces; o for opcode space; r for direct RAM; c/u/g/s for CPU/user/gfx/sound regions; e for EEPROMs); and 'size' is the usual b/w/d/q for byte/word/dword/qword. Cleaned up ROM definition flags and removed some ugly hacks that had existed previously. Expanded to support up to 256 BIOSes. Updated ROM_COPY to support specifying class/tag for the source region. Updated the address map AM_REGION macro to support specifying a class/tag for the region. Updated debugger windows to display the CPU and region tags where appropriate. Updated -listxml to output region class and tag for each ROM entry.
461 lines
13 KiB
C
461 lines
13 KiB
C
/***************************************************************************
|
|
|
|
sn76496.c
|
|
by Nicola Salmoria
|
|
|
|
Routines to emulate the:
|
|
Texas Instruments SN76489, SN76489A, SN76494/SN76496
|
|
( Also known as, or at least compatible with, the TMS9919.)
|
|
and the Sega 'PSG' used on the Master System, Game Gear, and Megadrive/Genesis
|
|
This chip is known as the Programmable Sound Generator, or PSG, and is a 4
|
|
channel sound generator, with three squarewave channels and a noise/arbitrary
|
|
duty cycle channel.
|
|
|
|
Noise emulation for all chips should be accurate:
|
|
SN76489 uses a 15-bit shift register with taps on bits D and E, output on /E
|
|
* It uses a 15-bit ring buffer for periodic noise/arbitrary duty cycle.
|
|
SN76489A uses a 16-bit shift register with taps on bits D and F, output on F
|
|
* It uses a 16-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.
|
|
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.
|
|
Sega Game Gear PSG is identical to the SMS3/MD/Genesis one except it has an
|
|
extra register for mapping which channels go to which speaker.
|
|
|
|
28/03/2005 : Sebastien Chevalier
|
|
Update th SN76496Write func, according to SN76489 doc found on SMSPower.
|
|
- On write with 0x80 set to 0, when LastRegister is other then TONE,
|
|
the function is similar than update with 0x80 set to 1
|
|
|
|
23/04/2007 : Lord Nightmare
|
|
Major update, implement all three different noise generation algorithms and a
|
|
set_variant call to discern among them.
|
|
|
|
TODO: Implement a function for setting stereo regs for the game gear.
|
|
Requires either making the entire core stereo and forcing mono out for all
|
|
chips except gamegear, or somehow making the core support both output typesf.
|
|
***************************************************************************/
|
|
|
|
#include "sndintrf.h"
|
|
#include "streams.h"
|
|
#include "sn76496.h"
|
|
|
|
|
|
#define MAX_OUTPUT 0x7fff
|
|
|
|
#define STEP 0x10000
|
|
|
|
struct SN76496
|
|
{
|
|
sound_stream * Channel;
|
|
int SampleRate;
|
|
int VolTable[16]; /* volume table */
|
|
INT32 Register[8]; /* registers */
|
|
INT32 LastRegister; /* last register written */
|
|
INT32 Volume[4]; /* volume of voice 0-2 and noise */
|
|
UINT32 RNG; /* noise generator */
|
|
INT32 NoiseMode; /* active noise mode */
|
|
INT32 FeedbackMask; /* mask for feedback */
|
|
INT32 WhitenoiseTaps; /* mask for white noise taps */
|
|
INT32 WhitenoiseInvert; /* white noise invert flag */
|
|
INT32 Period[4];
|
|
INT32 Count[4];
|
|
INT32 Output[4];
|
|
};
|
|
|
|
|
|
|
|
static void SN76496Write(int chip,int data)
|
|
{
|
|
struct SN76496 *R = sndti_token(SOUND_SN76496, chip);
|
|
int n, r, c;
|
|
|
|
|
|
/* update the output buffer before changing the registers */
|
|
stream_update(R->Channel);
|
|
|
|
if (data & 0x80)
|
|
{
|
|
r = (data & 0x70) >> 4;
|
|
R->LastRegister = r;
|
|
R->Register[r] = (R->Register[r] & 0x3f0) | (data & 0x0f);
|
|
}
|
|
else
|
|
{
|
|
r = R->LastRegister;
|
|
}
|
|
c = r/2;
|
|
switch (r)
|
|
{
|
|
case 0: /* tone 0 : frequency */
|
|
case 2: /* tone 1 : frequency */
|
|
case 4: /* tone 2 : frequency */
|
|
if ((data & 0x80) == 0) R->Register[r] = (R->Register[r] & 0x0f) | ((data & 0x3f) << 4);
|
|
R->Period[c] = STEP * R->Register[r];
|
|
if (R->Period[c] == 0) R->Period[c] = STEP;
|
|
if (r == 4)
|
|
{
|
|
/* update noise shift frequency */
|
|
if ((R->Register[6] & 0x03) == 0x03)
|
|
R->Period[3] = 2 * R->Period[2];
|
|
}
|
|
break;
|
|
case 1: /* tone 0 : volume */
|
|
case 3: /* tone 1 : volume */
|
|
case 5: /* tone 2 : volume */
|
|
case 7: /* noise : volume */
|
|
R->Volume[c] = R->VolTable[data & 0x0f];
|
|
if ((data & 0x80) == 0) R->Register[r] = (R->Register[r] & 0x3f0) | (data & 0x0f);
|
|
break;
|
|
case 6: /* noise : frequency, mode */
|
|
{
|
|
if ((data & 0x80) == 0) R->Register[r] = (R->Register[r] & 0x3f0) | (data & 0x0f);
|
|
n = R->Register[6];
|
|
R->NoiseMode = (n & 4) ? 1 : 0;
|
|
/* N/512,N/1024,N/2048,Tone #3 output */
|
|
R->Period[3] = ((n&3) == 3) ? 2 * R->Period[2] : (STEP << (5+(n&3)));
|
|
/* Reset noise shifter */
|
|
R->RNG = R->FeedbackMask; /* this is correct according to the smspower document */
|
|
//R->RNG = 0xF35; /* this is not, but sounds better in do run run */
|
|
R->Output[3] = R->RNG & 1;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
WRITE8_HANDLER( SN76496_0_w ) { SN76496Write(0,data); }
|
|
WRITE8_HANDLER( SN76496_1_w ) { SN76496Write(1,data); }
|
|
WRITE8_HANDLER( SN76496_2_w ) { SN76496Write(2,data); }
|
|
WRITE8_HANDLER( SN76496_3_w ) { SN76496Write(3,data); }
|
|
WRITE8_HANDLER( SN76496_4_w ) { SN76496Write(4,data); }
|
|
|
|
|
|
static void SN76496Update(void *param,stream_sample_t **inputs, stream_sample_t **_buffer,int length)
|
|
{
|
|
int i;
|
|
struct SN76496 *R = param;
|
|
stream_sample_t *buffer = _buffer[0];
|
|
|
|
|
|
/* If the volume is 0, increase the counter */
|
|
for (i = 0;i < 4;i++)
|
|
{
|
|
if (R->Volume[i] == 0)
|
|
{
|
|
/* note that I do count += length, NOT count = length + 1. You might think */
|
|
/* it's the same since the volume is 0, but doing the latter could cause */
|
|
/* interferencies when the program is rapidly modulating the volume. */
|
|
if (R->Count[i] <= length*STEP) R->Count[i] += length*STEP;
|
|
}
|
|
}
|
|
|
|
while (length > 0)
|
|
{
|
|
int vol[4];
|
|
unsigned int out;
|
|
int left;
|
|
|
|
|
|
/* vol[] keeps track of how long each square wave stays */
|
|
/* in the 1 position during the sample period. */
|
|
vol[0] = vol[1] = vol[2] = vol[3] = 0;
|
|
|
|
for (i = 0;i < 3;i++)
|
|
{
|
|
if (R->Output[i]) vol[i] += R->Count[i];
|
|
R->Count[i] -= STEP;
|
|
/* Period[i] is the half period of the square wave. Here, in each */
|
|
/* loop I add Period[i] twice, so that at the end of the loop the */
|
|
/* square wave is in the same status (0 or 1) it was at the start. */
|
|
/* vol[i] is also incremented by Period[i], since the wave has been 1 */
|
|
/* exactly half of the time, regardless of the initial position. */
|
|
/* If we exit the loop in the middle, Output[i] has to be inverted */
|
|
/* and vol[i] incremented only if the exit status of the square */
|
|
/* wave is 1. */
|
|
while (R->Count[i] <= 0)
|
|
{
|
|
R->Count[i] += R->Period[i];
|
|
if (R->Count[i] > 0)
|
|
{
|
|
R->Output[i] ^= 1;
|
|
if (R->Output[i]) vol[i] += R->Period[i];
|
|
break;
|
|
}
|
|
R->Count[i] += R->Period[i];
|
|
vol[i] += R->Period[i];
|
|
}
|
|
if (R->Output[i]) vol[i] -= R->Count[i];
|
|
}
|
|
|
|
left = STEP;
|
|
do
|
|
{
|
|
int nextevent;
|
|
|
|
|
|
if (R->Count[3] < left) nextevent = R->Count[3];
|
|
else nextevent = left;
|
|
|
|
if (R->Output[3]) vol[3] += R->Count[3];
|
|
R->Count[3] -= nextevent;
|
|
if (R->Count[3] <= 0)
|
|
{
|
|
if (R->NoiseMode == 1) /* White Noise Mode */
|
|
{
|
|
if (((R->RNG & R->WhitenoiseTaps) != R->WhitenoiseTaps) && ((R->RNG & R->WhitenoiseTaps) != 0)) /* crappy xor! */
|
|
{
|
|
R->RNG >>= 1;
|
|
R->RNG |= R->FeedbackMask;
|
|
}
|
|
else
|
|
{
|
|
R->RNG >>= 1;
|
|
}
|
|
R->Output[3] = R->WhitenoiseInvert ? !(R->RNG & 1) : R->RNG & 1;
|
|
}
|
|
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];
|
|
if (R->Output[3]) vol[3] += R->Period[3];
|
|
}
|
|
if (R->Output[3]) vol[3] -= R->Count[3];
|
|
|
|
left -= nextevent;
|
|
} while (left > 0);
|
|
|
|
out = vol[0] * R->Volume[0] + vol[1] * R->Volume[1] +
|
|
vol[2] * R->Volume[2] + vol[3] * R->Volume[3];
|
|
|
|
if (out > MAX_OUTPUT * STEP) out = MAX_OUTPUT * STEP;
|
|
|
|
*(buffer++) = out / STEP;
|
|
|
|
length--;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static void SN76496_set_gain(struct SN76496 *R,int gain)
|
|
{
|
|
int i;
|
|
double out;
|
|
|
|
|
|
gain &= 0xff;
|
|
|
|
/* increase max output basing on gain (0.2 dB per step) */
|
|
out = MAX_OUTPUT / 3;
|
|
while (gain-- > 0)
|
|
out *= 1.023292992; /* = (10 ^ (0.2/20)) */
|
|
|
|
/* build volume table (2dB per step) */
|
|
for (i = 0;i < 15;i++)
|
|
{
|
|
/* limit volume to avoid clipping */
|
|
if (out > MAX_OUTPUT / 3) R->VolTable[i] = MAX_OUTPUT / 3;
|
|
else R->VolTable[i] = out;
|
|
|
|
out /= 1.258925412; /* = 10 ^ (2/20) = 2dB */
|
|
}
|
|
R->VolTable[15] = 0;
|
|
}
|
|
|
|
|
|
|
|
static int SN76496_init(struct SN76496 *R,int sndindex,int clock)
|
|
{
|
|
int sample_rate = clock/16;
|
|
int i;
|
|
|
|
R->Channel = stream_create(0,1, sample_rate,R,SN76496Update);
|
|
|
|
R->SampleRate = sample_rate;
|
|
|
|
for (i = 0;i < 4;i++) R->Volume[i] = 0;
|
|
|
|
R->LastRegister = 0;
|
|
for (i = 0;i < 8;i+=2)
|
|
{
|
|
R->Register[i] = 0;
|
|
R->Register[i + 1] = 0x0f; /* volume = 0 */
|
|
}
|
|
|
|
for (i = 0;i < 4;i++)
|
|
{
|
|
R->Output[i] = 0;
|
|
R->Period[i] = R->Count[i] = STEP;
|
|
}
|
|
|
|
/* Default is SN76489 non-A */
|
|
R->FeedbackMask = 0x4000; /* mask for feedback */
|
|
R->WhitenoiseTaps = 0x03; /* mask for white noise taps */
|
|
R->WhitenoiseInvert = 1; /* white noise invert flag */
|
|
|
|
R->RNG = R->FeedbackMask;
|
|
R->Output[3] = R->RNG & 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void *generic_start(int sndindex, int clock, int feedbackmask, int noisetaps, int noiseinvert)
|
|
{
|
|
struct SN76496 *chip;
|
|
|
|
chip = auto_malloc(sizeof(*chip));
|
|
memset(chip, 0, sizeof(*chip));
|
|
|
|
if (SN76496_init(chip,sndindex,clock) != 0)
|
|
return NULL;
|
|
SN76496_set_gain(chip, 0);
|
|
|
|
chip->FeedbackMask = feedbackmask;
|
|
chip->WhitenoiseTaps = noisetaps;
|
|
chip->WhitenoiseInvert = noiseinvert;
|
|
|
|
state_save_register_item_array("sn76496", sndindex, chip->Register);
|
|
state_save_register_item("sn76496", sndindex, chip->LastRegister);
|
|
state_save_register_item_array("sn76496", sndindex, chip->Volume);
|
|
state_save_register_item("sn76496", sndindex, chip->RNG);
|
|
state_save_register_item("sn76496", sndindex, chip->NoiseMode);
|
|
state_save_register_item_array("sn76496", sndindex, chip->Period);
|
|
state_save_register_item_array("sn76496", sndindex, chip->Count);
|
|
state_save_register_item_array("sn76496", sndindex, chip->Output);
|
|
|
|
return chip;
|
|
|
|
}
|
|
|
|
|
|
static void *sn76489_start(const char *tag, int sndindex, int clock, const void *config)
|
|
{
|
|
return generic_start(sndindex, clock, 0x4000, 0x03, TRUE);
|
|
}
|
|
|
|
static void *sn76489a_start(const char *tag, int sndindex, int clock, const void *config)
|
|
{
|
|
return generic_start(sndindex, clock, 0x8000, 0x06, FALSE);
|
|
}
|
|
|
|
static void *sn76494_start(const char *tag, int sndindex, int clock, const void *config)
|
|
{
|
|
return generic_start(sndindex, clock, 0x8000, 0x06, FALSE);
|
|
}
|
|
|
|
static void *sn76496_start(const char *tag, int sndindex, int clock, const void *config)
|
|
{
|
|
return generic_start(sndindex, clock, 0x8000, 0x06, FALSE);
|
|
}
|
|
|
|
static void *gamegear_start(const char *tag, int sndindex, int clock, const void *config)
|
|
{
|
|
return generic_start(sndindex, clock, 0x8000, 0x09, FALSE);
|
|
}
|
|
|
|
static void *smsiii_start(const char *tag, int sndindex, int clock, const void *config)
|
|
{
|
|
return generic_start(sndindex, clock, 0x8000, 0x09, FALSE);
|
|
}
|
|
|
|
|
|
/**************************************************************************
|
|
* Generic get_info
|
|
**************************************************************************/
|
|
|
|
static void sn76496_set_info(void *token, UINT32 state, sndinfo *info)
|
|
{
|
|
switch (state)
|
|
{
|
|
/* no parameters to set */
|
|
}
|
|
}
|
|
|
|
|
|
void sn76496_get_info(void *token, UINT32 state, sndinfo *info)
|
|
{
|
|
switch (state)
|
|
{
|
|
/* --- the following bits of info are returned as 64-bit signed integers --- */
|
|
case SNDINFO_INT_ALIAS: info->i = SOUND_SN76496; break;
|
|
|
|
/* --- the following bits of info are returned as pointers to data or functions --- */
|
|
case SNDINFO_PTR_SET_INFO: info->set_info = sn76496_set_info; break;
|
|
case SNDINFO_PTR_START: info->start = sn76496_start; break;
|
|
case SNDINFO_PTR_STOP: /* Nothing */ break;
|
|
case SNDINFO_PTR_RESET: /* Nothing */ break;
|
|
|
|
/* --- the following bits of info are returned as NULL-terminated strings --- */
|
|
case SNDINFO_STR_NAME: info->s = "SN76496"; break;
|
|
case SNDINFO_STR_CORE_FAMILY: info->s = "TI PSG"; break;
|
|
case SNDINFO_STR_CORE_VERSION: info->s = "1.1"; break;
|
|
case SNDINFO_STR_CORE_FILE: info->s = __FILE__; break;
|
|
case SNDINFO_STR_CORE_CREDITS: info->s = "Copyright Nicola Salmoria and the MAME Team"; break;
|
|
}
|
|
}
|
|
|
|
void sn76489_get_info(void *token, UINT32 state, sndinfo *info)
|
|
{
|
|
switch (state)
|
|
{
|
|
case SNDINFO_PTR_START: info->start = sn76489_start; break;
|
|
case SNDINFO_STR_NAME: info->s = "SN76489"; break;
|
|
default: sn76496_get_info(token, state, info); break;
|
|
}
|
|
}
|
|
|
|
void sn76489a_get_info(void *token, UINT32 state, sndinfo *info)
|
|
{
|
|
switch (state)
|
|
{
|
|
case SNDINFO_PTR_START: info->start = sn76489a_start; break;
|
|
case SNDINFO_STR_NAME: info->s = "SN76489A"; break;
|
|
default: sn76496_get_info(token, state, info); break;
|
|
}
|
|
}
|
|
|
|
void sn76494_get_info(void *token, UINT32 state, sndinfo *info)
|
|
{
|
|
switch (state)
|
|
{
|
|
case SNDINFO_PTR_START: info->start = sn76494_start; break;
|
|
case SNDINFO_STR_NAME: info->s = "SN76494"; break;
|
|
default: sn76496_get_info(token, state, info); break;
|
|
}
|
|
}
|
|
|
|
void gamegear_get_info(void *token, UINT32 state, sndinfo *info)
|
|
{
|
|
switch (state)
|
|
{
|
|
case SNDINFO_PTR_START: info->start = gamegear_start; break;
|
|
case SNDINFO_STR_NAME: info->s = "Game Gear PSG"; break;
|
|
default: sn76496_get_info(token, state, info); break;
|
|
}
|
|
}
|
|
|
|
void smsiii_get_info(void *token, UINT32 state, sndinfo *info)
|
|
{
|
|
switch (state)
|
|
{
|
|
case SNDINFO_PTR_START: info->start = smsiii_start; break;
|
|
case SNDINFO_STR_NAME: info->s = "SMSIII PSG"; break;
|
|
default: sn76496_get_info(token, state, info); break;
|
|
}
|
|
}
|