/*************************************************************************** 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 "streams.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(running_device *device) { assert(device != NULL); assert(device->type() == SOUND_T6W28); return (t6w28_state *)downcast(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 */ stream_update(R->Channel); 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(running_device *device, t6w28_state *R) { int sample_rate = device->clock()/16; int i; R->Channel = stream_create(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; state_save_register_device_item_array(device, 0, chip->Register); state_save_register_device_item_array(device, 0, chip->LastRegister); state_save_register_device_item_array(device, 0, chip->Volume); state_save_register_device_item_array(device, 0, chip->RNG); state_save_register_device_item_array(device, 0, chip->NoiseMode); state_save_register_device_item_array(device, 0, chip->Period); state_save_register_device_item_array(device, 0, chip->Count); state_save_register_device_item_array(device, 0, 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);