diff options
Diffstat (limited to 'trunk/src/emu/sound/t6w28.c')
-rw-r--r-- | trunk/src/emu/sound/t6w28.c | 395 |
1 files changed, 395 insertions, 0 deletions
diff --git a/trunk/src/emu/sound/t6w28.c b/trunk/src/emu/sound/t6w28.c new file mode 100644 index 00000000000..8f03adaa0aa --- /dev/null +++ b/trunk/src/emu/sound/t6w28.c @@ -0,0 +1,395 @@ +/*************************************************************************** + + t6w28.c (based on sn74696.c) + + The t6w28 sound core is used in the SNK NeoGeo Pocket. It is a stereo + sound chip based on 2 partial sn76498a cores. + + The block diagram for this chip is as follows: + +Offset 0: + Tone 0 /----------> Att0 ---\ + | | + Tone 1 | /-------> Att1 ---+ + | | | Right + Tone 2 | | /----> Att2 ---+--------> + | | | | | + Noise -----+-------------> Att3 ---/ + | | | | + | | | | + Offset 1: | | | | + Tone 0 --------+----------> Att0 ---\ + | | | | + Tone 1 -----------+-------> Att1 ---+ + | | | Left + Tone 2 --------------+----> Att2 ---+--------> + | | + Noise \-------------> Att3 ---/ + + +***************************************************************************/ + +#include "emu.h" +#include "t6w28.h" + + +#define MAX_OUTPUT 0x7fff + +#define STEP 0x10000 + +typedef struct _t6w28_state t6w28_state; +struct _t6w28_state +{ + sound_stream * Channel; + int SampleRate; + int VolTable[16]; /* volume table */ + INT32 Register[16]; /* registers */ + INT32 LastRegister[2]; /* last register written */ + INT32 Volume[8]; /* volume of voice 0-2 and noise */ + UINT32 RNG[2]; /* noise generator */ + INT32 NoiseMode[2]; /* active noise mode */ + INT32 FeedbackMask; /* mask for feedback */ + INT32 WhitenoiseTaps; /* mask for white noise taps */ + INT32 WhitenoiseInvert; /* white noise invert flag */ + INT32 Period[8]; + INT32 Count[8]; + INT32 Output[8]; +}; + + +INLINE t6w28_state *get_safe_token(device_t *device) +{ + assert(device != NULL); + assert(device->type() == T6W28); + return (t6w28_state *)downcast<legacy_device_base *>(device)->token(); +} + + +WRITE8_DEVICE_HANDLER( t6w28_w ) +{ + t6w28_state *R = get_safe_token(device); + int n, r, c; + + + /* update the output buffer before changing the registers */ + R->Channel->update(); + + offset &= 1; + + if (data & 0x80) + { + r = (data & 0x70) >> 4; + R->LastRegister[offset] = r; + R->Register[offset * 8 + r] = (R->Register[offset * 8 + r] & 0x3f0) | (data & 0x0f); + } + else + { + r = R->LastRegister[offset]; + } + 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[offset * 8 + r] = (R->Register[offset * 8 + r] & 0x0f) | ((data & 0x3f) << 4); + R->Period[offset * 4 + c] = STEP * R->Register[offset * 8 + r]; + if (R->Period[offset * 4 + c] == 0) R->Period[offset * 4 + c] = STEP; + if (r == 4) + { + /* update noise shift frequency */ + if ((R->Register[offset * 8 + 6] & 0x03) == 0x03) + R->Period[offset * 4 + 3] = 2 * R->Period[offset * 4 + 2]; + } + break; + case 1: /* tone 0 : volume */ + case 3: /* tone 1 : volume */ + case 5: /* tone 2 : volume */ + case 7: /* noise : volume */ + R->Volume[offset * 4 + c] = R->VolTable[data & 0x0f]; + if ((data & 0x80) == 0) R->Register[offset * 8 + r] = (R->Register[offset * 8 + r] & 0x3f0) | (data & 0x0f); + break; + case 6: /* noise : frequency, mode */ + { + if ((data & 0x80) == 0) R->Register[offset * 8 + r] = (R->Register[offset * 8 + r] & 0x3f0) | (data & 0x0f); + n = R->Register[offset * 8 + 6]; + R->NoiseMode[offset] = (n & 4) ? 1 : 0; + /* N/512,N/1024,N/2048,Tone #3 output */ + R->Period[offset * 4 + 3] = ((n&3) == 3) ? 2 * R->Period[offset * 4 + 2] : (STEP << (5+(n&3))); + /* Reset noise shifter */ + R->RNG[offset] = 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[offset * 4 + 3] = R->RNG[offset] & 1; + } + break; + } +} + + + +static STREAM_UPDATE( t6w28_update ) +{ + int i; + t6w28_state *R = (t6w28_state *)param; + stream_sample_t *buffer0 = outputs[0]; + stream_sample_t *buffer1 = outputs[1]; + + + /* If the volume is 0, increase the counter */ + for (i = 0;i < 8;i++) + { + if (R->Volume[i] == 0) + { + /* note that I do count += samples, NOT count = samples + 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] <= samples*STEP) R->Count[i] += samples*STEP; + } + } + + while (samples > 0) + { + int vol[8]; + unsigned int out0, out1; + 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] = vol[4] = vol[5] = vol[6] = vol[7] = 0; + + for (i = 2;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]; + } + + for (i = 4;i < 7;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[0] == 1) /* White Noise Mode */ + { + if (((R->RNG[0] & R->WhitenoiseTaps) != R->WhitenoiseTaps) && ((R->RNG[0] & R->WhitenoiseTaps) != 0)) /* crappy xor! */ + { + R->RNG[0] >>= 1; + R->RNG[0] |= R->FeedbackMask; + } + else + { + R->RNG[0] >>= 1; + } + R->Output[3] = R->WhitenoiseInvert ? !(R->RNG[0] & 1) : R->RNG[0] & 1; + } + else /* Periodic noise mode */ + { + if (R->RNG[0] & 1) + { + R->RNG[0] >>= 1; + R->RNG[0] |= R->FeedbackMask; + } + else + { + R->RNG[0] >>= 1; + } + R->Output[3] = R->RNG[0] & 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); + + out0 = vol[4] * R->Volume[4] + vol[5] * R->Volume[5] + + vol[6] * R->Volume[6] + vol[3] * R->Volume[7]; + + out1 = vol[4] * R->Volume[0] + vol[5] * R->Volume[1] + + vol[6] * R->Volume[2] + vol[3] * R->Volume[3]; + + if (out0 > MAX_OUTPUT * STEP) out0 = MAX_OUTPUT * STEP; + if (out1 > MAX_OUTPUT * STEP) out1 = MAX_OUTPUT * STEP; + + *(buffer0++) = out0 / STEP; + *(buffer1++) = out1 / STEP; + + samples--; + } +} + + + +static void t6w28_set_gain(t6w28_state *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 t6w28_init(device_t *device, t6w28_state *R) +{ + int sample_rate = device->clock()/16; + int i; + + R->Channel = device->machine().sound().stream_alloc(*device,0,2,sample_rate,R,t6w28_update); + + R->SampleRate = sample_rate; + + for (i = 0;i < 8;i++) R->Volume[i] = 0; + + R->LastRegister[0] = 0; + R->LastRegister[1] = 0; + for (i = 0;i < 8;i+=2) + { + R->Register[i] = 0; + R->Register[i + 1] = 0x0f; /* volume = 0 */ + } + + for (i = 0;i < 8;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[0] = R->FeedbackMask; + R->RNG[1] = R->FeedbackMask; + R->Output[3] = R->RNG[0] & 1; + + return 0; +} + + +static DEVICE_START( t6w28 ) +{ + t6w28_state *chip = get_safe_token(device); + + if (t6w28_init(device,chip) != 0) + fatalerror("Error creating t6w28 chip"); + t6w28_set_gain(chip, 0); + + /* values from sn76489a */ + chip->FeedbackMask = 0x8000; + chip->WhitenoiseTaps = 0x06; + chip->WhitenoiseInvert = FALSE; + + device->save_item(NAME(chip->Register)); + device->save_item(NAME(chip->LastRegister)); + device->save_item(NAME(chip->Volume)); + device->save_item(NAME(chip->RNG)); + device->save_item(NAME(chip->NoiseMode)); + device->save_item(NAME(chip->Period)); + device->save_item(NAME(chip->Count)); + device->save_item(NAME(chip->Output)); +} + + +/************************************************************************** + * Generic get_info + **************************************************************************/ + +DEVICE_GET_INFO( t6w28 ) +{ + switch (state) + { + /* --- the following bits of info are returned as 64-bit signed integers --- */ + case DEVINFO_INT_TOKEN_BYTES: info->i = sizeof(t6w28_state); break; + + /* --- the following bits of info are returned as pointers to data or functions --- */ + case DEVINFO_FCT_START: info->start = DEVICE_START_NAME( t6w28 ); break; + case DEVINFO_FCT_STOP: /* Nothing */ break; + case DEVINFO_FCT_RESET: /* Nothing */ break; + + /* --- the following bits of info are returned as NULL-terminated strings --- */ + case DEVINFO_STR_NAME: strcpy(info->s, "T6W28"); break; + case DEVINFO_STR_FAMILY: strcpy(info->s, "PSG"); break; + case DEVINFO_STR_VERSION: strcpy(info->s, "1.0"); break; + case DEVINFO_STR_SOURCE_FILE: strcpy(info->s, __FILE__); break; + case DEVINFO_STR_CREDITS: strcpy(info->s, "Copyright Nicola Salmoria and the MAME Team"); break; + } +} + + +DEFINE_LEGACY_SOUND_DEVICE(T6W28, t6w28); |