/**************************************************************************** * * Phoenix sound hardware simulation - still very ALPHA! * * If you find errors or have suggestions, please mail me. * Juergen Buchmueller * ****************************************************************************/ #include #include "driver.h" #include "streams.h" #include "sound/custom.h" #include "sound/tms36xx.h" #include "phoenix.h" /**************************************************************************** * 4006 * Dual 4-bit and dual 5-bit serial-in serial-out shift registers. * * +----------+ * 1D5 |1 +--+ 14| VCC * /1Q4 |2 13| 1Q1 * CLK |3 12| 2Q0 * 2D4 |4 4006 11| 2Q0 * 3D4 |5 10| 3Q0 * 4D5 |6 9| 4Q0 * GND |7 8| 4Q1 * +----------+ * * [This information is part of the GIICM] * * Pin 8 and 9 are connected to an EXOR gate and the inverted * output (EXNOR) is fed back to pin 1 (and the pseudo polynome output). * * 1D5 1Q1 2D4 2Q0 3D4 3Q0 4D5 4Q1 4Q0 * +--+--+--+--+--+ +--+--+--+--+ +--+--+--+--+ +--+--+--+--+--+ * +->| 0| 1| 2| 3| 4|->| 5| 6| 7| 8|->| 9|10|11|12|->|13|14|15|16|17| * | +--+--+--+--+--+ +--+--+--+--+ +--+--+--+--+ +--+--+--+--+--+ * | ____ | | * | / |------------+ | * +-----------------------------------------|EXNOR| | * \____|---------------+ * ****************************************************************************/ #define VMIN 0 #define VMAX 32767 static int sound_latch_a; static sound_stream * channel; static UINT32 *poly18 = NULL; INLINE int update_c24(int samplerate) { static int counter, level; /* * Noise frequency control (Port B): * Bit 6 lo charges C24 (6.8u) via R51 (330) and when * bit 6 is hi, C24 is discharged through R52 (20k) * in approx. 20000 * 6.8e-6 = 0.136 seconds */ #define C24 6.8e-6 #define R49 1000 #define R51 330 #define R52 20000 if( sound_latch_a & 0x40 ) { if (level > VMIN) { counter -= (int)((level - VMIN) / (R52 * C24)); if( counter <= 0 ) { int n = -counter / samplerate + 1; counter += n * samplerate; if( (level -= n) < VMIN) level = VMIN; } } } else { if (level < VMAX) { counter -= (int)((VMAX - level) / ((R51+R49) * C24)); if( counter <= 0 ) { int n = -counter / samplerate + 1; counter += n * samplerate; if( (level += n) > VMAX) level = VMAX; } } } return VMAX - level; } INLINE int update_c25(int samplerate) { static int counter, level; /* * Bit 7 hi charges C25 (6.8u) over a R50 (1k) and R53 (330) and when * bit 7 is lo, C25 is discharged through R54 (47k) * in about 47000 * 6.8e-6 = 0.3196 seconds */ #define C25 6.8e-6 #define R50 1000 #define R53 330 #define R54 47000 if( sound_latch_a & 0x80 ) { if (level < VMAX) { counter -= (int)((VMAX - level) / ((R50+R53) * C25)); if( counter <= 0 ) { int n = -counter / samplerate + 1; counter += n * samplerate; if( (level += n) > VMAX ) level = VMAX; } } } else { if (level > VMIN) { counter -= (int)((level - VMIN) / (R54 * C25)); if( counter <= 0 ) { int n = -counter / samplerate + 1; counter += n * samplerate; if( (level -= n) < VMIN ) level = VMIN; } } } return level; } INLINE int noise(int samplerate) { static int counter, polyoffs, polybit, lowpass_counter, lowpass_polybit; int vc24 = update_c24(samplerate); int vc25 = update_c25(samplerate); int sum = 0, level, frequency; /* * The voltage levels are added and control I(CE) of transistor TR1 * (NPN) which then controls the noise clock frequency (linearily?). * level = voltage at the output of the op-amp controlling the noise rate. */ if( vc24 < vc25 ) level = vc24 + (vc25 - vc24) / 2; else level = vc25 + (vc24 - vc25) / 2; frequency = 588 + 6325 * level / 32768; /* * NE555: Ra=47k, Rb=1k, C=0.05uF * minfreq = 1.44 / ((47000+2*1000) * 0.05e-6) = approx. 588 Hz * R71 (2700 Ohms) parallel to R73 (47k Ohms) = approx. 2553 Ohms * maxfreq = 1.44 / ((2553+2*1000) * 0.05e-6) = approx. 6325 Hz */ counter -= frequency; if( counter <= 0 ) { int n = (-counter / samplerate) + 1; counter += n * samplerate; polyoffs = (polyoffs + n) & 0x3ffff; polybit = (poly18[polyoffs>>5] >> (polyoffs & 31)) & 1; } if (!polybit) sum += vc24; /* 400Hz crude low pass filter: this is only a guess!! */ lowpass_counter -= 400; if( lowpass_counter <= 0 ) { lowpass_counter += samplerate; lowpass_polybit = polybit; } if (!lowpass_polybit) sum += vc25; return sum; } static void phoenix_sound_update(void *param, stream_sample_t **inputs, stream_sample_t **outputs, int length) { int samplerate = Machine->sample_rate; stream_sample_t *buffer = outputs[0]; while( length-- > 0 ) { int sum = 0; sum = noise(samplerate) / 2; *buffer++ = sum < 32768 ? sum > -32768 ? sum : -32768 : 32767; } } /************************************************************************/ /* phoenix Sound System Analog emulation */ /* */ /* NOTE: Sample Rate must be at least 44100 for proper emulation. */ /* */ /* April 2005, DR. */ /************************************************************************/ static const discrete_555_desc phoenix_effect1_555 = { DISC_555_OUT_COUNT_F_X, 5, // B+ voltage of 555 DEFAULT_555_VALUES }; static const discrete_555_desc phoenix_effect2_555 = { DISC_555_OUT_ENERGY, 5, // B+ voltage of 555 4.0, DEFAULT_555_THRESHOLD, DEFAULT_555_TRIGGER }; static const discrete_comp_adder_table phoenix_effect2_cap_sel = { DISC_COMP_P_CAPACITOR, CAP_U(0.01), // C18 2, {CAP_U(0.47), CAP_U(1)} // C16, C17 }; static const discrete_mixer_desc phoenix_effect2_mixer1 = { DISC_MIXER_IS_RESISTOR, {RES_K(10), RES_K(5.1) + RES_K(5.1), RES_K(5)}, // R42, R45+R46, internal 555 R {0}, // No variable resistor nodes {0}, // No caps 0, // No rI RES_K(10), // internal 555 0,0, // No Filter 0, // not used in resistor network 1 // final gain }; static const discrete_mixer_desc phoenix_effect2_mixer2 = { DISC_MIXER_IS_RESISTOR, {RES_K(5.1), RES_K(5.1)}, // R45, R46 {0}, // No variable resistor nodes {0}, // No caps 0, // No rI 0, // No rF 0,0, // No Filter 0, // not used in resistor network 1 // final gain }; static const discrete_mixer_desc phoenix_effect2_mixer3 = { DISC_MIXER_IS_RESISTOR, {RES_K(10), RES_K(5.1), RES_K(5)}, // R42, R46, internal 555 R {0}, // No variable resistor nodes {0}, // No caps 0, // No rI RES_K(10), // internal 555 0,0, // No Filter 0, // not used in resistor network 1 // final gain }; static const discrete_mixer_desc phoenix_mixer = { DISC_MIXER_IS_RESISTOR, {RES_K(10+47), RES_K(10+20), RES_K(20), RES_K(20)}, // R19+R21, R38+R47, R67, R68 {0}, // No variable resistor nodes {CAP_U(10), CAP_U(10), CAP_U(.1), CAP_U(10)}, // C6, C31, C29, C30 0, // No rI RES_K(10), // VR1 0, // No Filter CAP_U(10), // C32 0, // not used in resistor network 40000 // final gain }; /* Nodes - Inputs */ #define PHOENIX_EFFECT_1_DATA NODE_01 #define PHOENIX_EFFECT_1_FREQ NODE_02 #define PHOENIX_EFFECT_1_FILT NODE_03 #define PHOENIX_EFFECT_2_DATA NODE_04 #define PHOENIX_EFFECT_2_FREQ NODE_05 #define PHOENIX_EFFECT_3_EN NODE_06 #define PHOENIX_EFFECT_4_EN NODE_07 /* Nodes - Sounds */ #define PHOENIX_EFFECT_1_SND NODE_10 #define PHOENIX_EFFECT_2_SND NODE_11 #define PHOENIX_EFFECT_3_SND 0 #define PHOENIX_EFFECT_4_SND 0 DISCRETE_SOUND_START(phoenix) /************************************************/ /* Input register mapping for phoenix */ /************************************************/ DISCRETE_INPUT_DATA (PHOENIX_EFFECT_1_DATA) DISCRETE_INPUT_LOGIC(PHOENIX_EFFECT_1_FREQ) DISCRETE_INPUT_LOGIC(PHOENIX_EFFECT_1_FILT) DISCRETE_INPUT_DATA (PHOENIX_EFFECT_2_DATA) DISCRETE_INPUT_DATA (PHOENIX_EFFECT_2_FREQ) DISCRETE_INPUT_LOGIC(PHOENIX_EFFECT_3_EN) DISCRETE_INPUT_LOGIC(PHOENIX_EFFECT_4_EN) /************************************************/ /* Effect 1 */ /* - shield, bird explode, level 3&4 siren, */ /* - level 5 spaceship */ /************************************************/ /* R22 has been confirmed on real boards as 470 ohm, not 47k in schematics */ DISCRETE_RCDISC4(NODE_20, 1, PHOENIX_EFFECT_1_FREQ, 470, RES_K(100), RES_K(33), CAP_U(6.8), 12, 1) // R22, R23, R24, C7 DISCRETE_555_ASTABLE_CV(NODE_21, 1, RES_K(47), RES_K(47), CAP_U(.001), NODE_20, &phoenix_effect1_555) // R25, R26, C8 /* LS163 counts rising edge, but the LS14 inverts that */ DISCRETE_NOTE(NODE_22, 1, NODE_21, PHOENIX_EFFECT_1_DATA, 0x0f, 1, DISC_CLK_BY_COUNT | DISC_OUT_IS_ENERGY) /* When FILT is enabled, the effect is filtered. * While the R20 does decrease the amplitude a little, its main purpose * is to discharge C5 when the filter is disabled. */ DISCRETE_SWITCH(NODE_23, 1, PHOENIX_EFFECT_1_FILT, DEFAULT_TTL_V_LOGIC_1, DEFAULT_TTL_V_LOGIC_1 * RES_K(100) / (RES_K(10) + RES_K(100))) // R20, R19 DISCRETE_MULTIPLY(NODE_24, 1, NODE_22, NODE_23) DISCRETE_RCFILTER(NODE_25, 1, NODE_24, 1.0/(1.0/RES_K(10) + 1.0/RES_K(100)), CAP_U(.047)) // R19, R20, C5 DISCRETE_SWITCH(PHOENIX_EFFECT_1_SND, 1, PHOENIX_EFFECT_1_FILT, NODE_24, NODE_25) /************************************************/ /* Effect 2 */ /* - bird flying, bird/phoenix/spaceship hit */ /* - phoenix wing hit */ /************************************************/ DISCRETE_COMP_ADDER(NODE_30, 1, PHOENIX_EFFECT_2_FREQ, &phoenix_effect2_cap_sel) /* Part of the frequency select also effects the gain */ DISCRETE_TRANSFORM2(NODE_31, 1, PHOENIX_EFFECT_2_FREQ, 2, "01&1/") // get bit 0x02 DISCRETE_SWITCH(NODE_32, 1, NODE_31, DEFAULT_TTL_V_LOGIC_1, DEFAULT_TTL_V_LOGIC_1/2) DISCRETE_555_ASTABLE(NODE_33, 1, RES_K(47), RES_K(100), NODE_30, &phoenix_effect2_555) // R40, R41 /* C20 has been confirmed on real boards as 1uF, not 10uF in schematics */ DISCRETE_555_ASTABLE(NODE_34, 1, RES_K(510), RES_K(510), CAP_U(1), &phoenix_effect2_555) // R23, R24, C20 /* R45 & R46 have been confirmed on real boards as 5.1k, not 51k in schematics */ /* We need to work backwards here and calculate the voltage at the junction of R42 & R46 */ /* If you remove C22 from the real PCB, you can WAVELOG NODE_35 with a gain of 1000 and compare * it against the junction of R42 & R46 on a real PCB. */ DISCRETE_MIXER3(NODE_35, 1, NODE_33, NODE_34, 5, &phoenix_effect2_mixer1) /* Then calculate the voltage going to C22 */ /* If you remove C22 from the real PCB, you can WAVELOG NODE_36 with a gain of 1000 and compare * it against the junction of R45 & R46 on a real PCB. */ DISCRETE_MIXER2(NODE_36, 1, NODE_34, NODE_35, &phoenix_effect2_mixer2) /* C22 charging is R45 in parallel with R46, R42 and the 555 CV internal resistance */ DISCRETE_RCFILTER(NODE_37, 1, NODE_36, 1.0/ (1.0/RES_K(5.1) + (1.0/(RES_K(5.1) + 1.0/(1.0/RES_K(10) + 1.0/RES_K(5) + 1.0/RES_K(10)) ))), CAP_U(100)) // R45, R46, R42, internal 555 Rs, C22 /* Now mix from C22 on */ /* You can WAVELOG NODE_38 with a gain of 1000 and compare it against IC50 pin 5 on a real PCB. */ DISCRETE_MIXER3(NODE_38, 1, NODE_33, NODE_37, 5, &phoenix_effect2_mixer3) DISCRETE_555_ASTABLE_CV(NODE_39, 1, RES_K(20), RES_K(20), CAP_U(0.001), NODE_38, &phoenix_effect1_555) // R47, R48, C23 DISCRETE_NOTE(NODE_40, 1, NODE_39, PHOENIX_EFFECT_2_DATA, 0x0f, 1, DISC_CLK_BY_COUNT | DISC_OUT_IS_ENERGY) DISCRETE_MULTIPLY(PHOENIX_EFFECT_2_SND, 1, NODE_40, NODE_32) /************************************************/ /* Combine all sound sources. */ /************************************************/ DISCRETE_MIXER4(NODE_90, 1, PHOENIX_EFFECT_1_SND, PHOENIX_EFFECT_2_SND, PHOENIX_EFFECT_3_SND, PHOENIX_EFFECT_4_SND,&phoenix_mixer) DISCRETE_OUTPUT(NODE_90, 1) DISCRETE_SOUND_END WRITE8_HANDLER( phoenix_sound_control_a_w ) { if( data == sound_latch_a ) return; discrete_sound_w(PHOENIX_EFFECT_2_DATA, data & 0x0f); discrete_sound_w(PHOENIX_EFFECT_2_FREQ, (data & 0x30) >> 4); // discrete_sound_w(PHOENIX_EFFECT_3_EN , data & 0x40); // discrete_sound_w(PHOENIX_EFFECT_4_EN , data & 0x80); stream_update(channel); sound_latch_a = data; } WRITE8_HANDLER( phoenix_sound_control_b_w ) { discrete_sound_w(PHOENIX_EFFECT_1_DATA, data & 0x0f); discrete_sound_w(PHOENIX_EFFECT_1_FILT, data & 0x20); discrete_sound_w(PHOENIX_EFFECT_1_FREQ, data & 0x10); /* update the tune that the MM6221AA is playing */ mm6221aa_tune_w(0, data >> 6); } void *phoenix_sh_start(int clock, const struct CustomSound_interface *config) { int i, j; UINT32 shiftreg; poly18 = (UINT32 *)auto_malloc((1ul << (18-5)) * sizeof(UINT32)); shiftreg = 0; for( i = 0; i < (1ul << (18-5)); i++ ) { UINT32 bits = 0; for( j = 0; j < 32; j++ ) { bits = (bits >> 1) | (shiftreg << 31); if( ((shiftreg >> 16) & 1) == ((shiftreg >> 17) & 1) ) shiftreg = (shiftreg << 1) | 1; else shiftreg <<= 1; } poly18[i] = bits; } channel = stream_create(0, 1, Machine->sample_rate, 0, phoenix_sound_update); /* a dummy token */ return auto_malloc(1); }