1 /*****************************************************************************
2  *
3  * A (partially wrong) try to emulate Asteroid's analog sound
4  * It's getting better but is still imperfect :/
5  * If you have ideas, corrections, suggestions contact Juergen
6  * Buchmueller <pullmoll@t-online.de>
7  *
8  * Known issues (TODO):
9  * - find out if/why the samples are 'damped', I don't see no
10  *	 low pass filter in the sound output, but the samples sound
11  *	 like there should be one. Maybe in the amplifier..
12  * - better (accurate) way to emulate the low pass on the thrust sound?
13  * - verify the thump_frequency calculation. It's only an approximation now
14  * - the nastiest piece of the circuit is the saucer sound IMO
15  *	 the circuits are almost equal, but there are some strange looking
16  *	 things like the direct coupled op-amps and transistors and I can't
17  *	 calculate the true resistance and thus voltage at the control
18  *	 input (5) of the NE555s.
19  * - saucer sound is not easy either and the calculations might be off, still.
20  *
21  *****************************************************************************/
22 
23 #include <math.h>
24 #include "burnint.h"
25 #include "asteroids.h"
26 
27 #define VMAX    32767
28 #define VMIN	0
29 
30 #define SAUCEREN    0
31 #define SAUCRFIREEN 1
32 #define SAUCERSEL   2
33 #define THRUSTEN    3
34 #define SHIPFIREEN	4
35 #define LIFEEN		5
36 
37 #define EXPITCH0	(1<<6)
38 #define EXPITCH1	(1<<7)
39 #define EXPAUDSHIFT 2
40 #define EXPAUDMASK	(0x0f<<EXPAUDSHIFT)
41 
42 #define NE555_T1(Ra,Rb,C)	(VMAX*2/3/(0.639*((Ra)+(Rb))*(C)))
43 #define NE555_T2(Ra,Rb,C)	(VMAX*2/3/(0.639*(Rb)*(C)))
44 #define NE555_F(Ra,Rb,C)	(1.44/(((Ra)+2*(Rb))*(C)))
45 
46 static INT32 explosion_latch;
47 static INT32 thump_latch;
48 static INT32 sound_latch[8];
49 
50 static INT32 polynome;
51 static INT32 thump_frequency;
52 
53 static INT16 *discharge;        // look up table
54 static INT16 vol_explosion[16]; // look up table
55 #define EXP(charge,n) (charge ? 0x7fff - discharge[0x7fff-n] : discharge[n])
56 
57 #ifdef INLINE
58 #undef INLINE
59 #endif
60 
61 #define INLINE static inline
62 
63 struct asteroid_sound {
64 	INT32 explosion_counter;
65 	INT32 explosion_sample_counter;
66 	INT32 explosion_out;
67 
68 	INT32 thrust_counter;
69 	INT32 thrust_out;
70 	INT32 thrust_amp;
71 
72 	INT32 thump_counter;
73 	INT32 thump_out;
74 
75 	INT32 saucer_vco, saucer_vco_charge, saucer_vco_counter;
76     INT32 saucer_out, saucer_counter;
77 
78 	INT32 saucerfire_vco, saucerfire_vco_counter;
79 	INT32 saucerfire_amp, saucerfire_amp_counter;
80 	INT32 saucerfire_out, saucerfire_counter;
81 
82 	INT32 shipfire_vco, shipfire_vco_counter;
83 	INT32 shipfire_amp, shipfire_amp_counter;
84     INT32 shipfire_out, shipfire_counter;
85 
86 	INT32 life_counter, life_out;
87 };
88 
89 static asteroid_sound asound;
90 
explosion(INT32 samplerate)91 INLINE INT32 explosion(INT32 samplerate)
92 {
93 	//static INT32 counter, sample_counter;
94 	//static INT32 out;
95 
96 	asound.explosion_counter -= 12000;
97 	while( asound.explosion_counter <= 0 )
98 	{
99 		asound.explosion_counter += samplerate;
100 		if( ((polynome & 0x4000) == 0) == ((polynome & 0x0040) == 0) )
101 			polynome = (polynome << 1) | 1;
102 		else
103 			polynome <<= 1;
104 		if( ++asound.explosion_sample_counter == 16 )
105 		{
106 			asound.explosion_sample_counter = 0;
107 			if( explosion_latch & EXPITCH0 )
108 				asound.explosion_sample_counter |= 2 + 8;
109 			else
110 				asound.explosion_sample_counter |= 4;
111 			if( explosion_latch & EXPITCH1 )
112 				asound.explosion_sample_counter |= 1 + 8;
113 		}
114 		/* ripple count output is high? */
115 		if( asound.explosion_sample_counter == 15 )
116 			asound.explosion_out = polynome & 1;
117 	}
118 	if( asound.explosion_out )
119 		return vol_explosion[(explosion_latch & EXPAUDMASK) >> EXPAUDSHIFT];
120 
121     return 0;
122 }
123 
thrust(INT32 samplerate)124 INLINE INT32 thrust(INT32 samplerate)
125 {
126 	//static INT32 counter, out, amp;
127 
128     if( sound_latch[THRUSTEN] )
129 	{
130 		/* SHPSND filter */
131 		asound.thrust_counter -= 110;
132 		while( asound.thrust_counter <= 0 )
133 		{
134 			asound.thrust_counter += samplerate;
135 			asound.thrust_out = polynome & 1;
136 		}
137 		if( asound.thrust_out )
138 		{
139 			if( asound.thrust_amp < VMAX )
140 				asound.thrust_amp += (VMAX - asound.thrust_amp) * 32768 / 32 / samplerate + 1;
141 		}
142 		else
143 		{
144 			if( asound.thrust_amp > VMIN )
145 				asound.thrust_amp -= asound.thrust_amp * 32768 / 32 / samplerate + 1;
146 		}
147 		return asound.thrust_amp;
148 	} else {
149 		// decay thrust amp for no clicks -dink
150 		asound.thrust_amp *= (double)0.997;
151 		return asound.thrust_amp;
152 	}
153 	return 0;
154 }
155 
thump(INT32 samplerate)156 INLINE INT32 thump(INT32 samplerate)
157 {
158 	//static INT32 counter, out;
159 
160     if( thump_latch & 0x10 )
161 	{
162 		asound.thump_counter -= thump_frequency;
163 		while( asound.thump_counter <= 0 )
164 		{
165 			asound.thump_counter += samplerate;
166 			asound.thump_out ^= 1;
167 		}
168 		if( asound.thump_out )
169 			return VMAX;
170 	}
171 	return 0;
172 }
173 
174 
saucer(INT32 samplerate)175 INLINE INT32 saucer(INT32 samplerate)
176 {
177 	//static INT32 vco, vco_charge, vco_counter;  asound.saucer_
178     //static INT32 out, counter;
179 	double v5;
180 
181     /* saucer sound enabled ? */
182 	if( sound_latch[SAUCEREN] )
183 	{
184 		/* NE555 setup as astable multivibrator:
185 		 * C = 10u, Ra = 5.6k, Rb = 10k
186 		 * or, with /SAUCERSEL being low:
187 		 * C = 10u, Ra = 5.6k, Rb = 6k (10k parallel with 15k)
188 		 */
189 		if( asound.saucer_vco_charge )
190 		{
191 			if( sound_latch[SAUCERSEL] )
192 				asound.saucer_vco_counter -= NE555_T1(5600,10000,10e-6);
193 			else
194 				asound.saucer_vco_counter -= NE555_T1(5600,6000,10e-6);
195 			if( asound.saucer_vco_counter <= 0 )
196 			{
197 				INT32 steps = (-asound.saucer_vco_counter / samplerate) + 1;
198 				asound.saucer_vco_counter += steps * samplerate;
199 				if( (asound.saucer_vco += steps) >= VMAX*2/3 )
200 				{
201 					asound.saucer_vco = VMAX*2/3;
202 					asound.saucer_vco_charge = 0;
203 				}
204 			}
205 		}
206 		else
207 		{
208 			if( sound_latch[SAUCERSEL] )
209 				asound.saucer_vco_counter -= NE555_T2(5600,10000,10e-6);
210 			else
211 				asound.saucer_vco_counter -= NE555_T2(5600,6000,10e-6);
212 			if( asound.saucer_vco_counter <= 0 )
213 			{
214 				INT32 steps = (-asound.saucer_vco_counter / samplerate) + 1;
215 				asound.saucer_vco_counter += steps * samplerate;
216 				if( (asound.saucer_vco -= steps) <= VMAX*1/3 )
217 				{
218 					asound.saucer_vco = VMIN*1/3;
219 					asound.saucer_vco_charge = 1;
220 				}
221 			}
222 		}
223 		/*
224 		 * NE566 voltage controlled oscillator
225 		 * Co = 0.047u, Ro = 10k
226 		 * to = 2.4 * (Vcc - V5) / (Ro * Co * Vcc)
227 		 */
228 		if( sound_latch[SAUCERSEL] )
229 			v5 = 12.0 - 1.66 - 5.0 * EXP(asound.saucer_vco_charge,asound.saucer_vco) / 32768;
230 		else
231 			v5 = 11.3 - 1.66 - 5.0 * EXP(asound.saucer_vco_charge,asound.saucer_vco) / 32768;
232 		asound.saucer_counter -= floor(2.4 * (12.0 - v5) / (10000 * 0.047e-6 * 12.0));
233 		while( asound.saucer_counter <= 0 )
234 		{
235 			asound.saucer_counter += samplerate;
236 			asound.saucer_out ^= 1;
237 		}
238 		if( asound.saucer_out )
239 			return VMAX;
240 	}
241 	return 0;
242 }
243 
saucerfire(INT32 samplerate)244 INLINE INT32 saucerfire(INT32 samplerate)
245 {
246 	//static INT32 vco, vco_counter;
247 	//static INT32 amp, amp_counter;
248 	//static INT32 out, counter;
249 
250     if( sound_latch[SAUCRFIREEN] )
251 	{
252 		if( asound.saucerfire_vco < VMAX*12/5 )
253 		{
254 			/* charge C38 (10u) through R54 (10K) from 5V to 12V */
255 			#define C38_CHARGE_TIME (VMAX)
256 			asound.saucerfire_vco_counter -= C38_CHARGE_TIME;
257 			while( asound.saucerfire_vco_counter <= 0 )
258 			{
259 				asound.saucerfire_vco_counter += samplerate;
260 				if( ++asound.saucerfire_vco == VMAX*12/5 )
261 					break;
262 			}
263 		}
264 		if( asound.saucerfire_amp > VMIN )
265 		{
266 			/* discharge C39 (10u) through R58 (10K) and diode CR6,
267 			 * but only during the time the output of the NE555 is low.
268 			 */
269 			if( asound.saucerfire_out )
270 			{
271 				#define C39_DISCHARGE_TIME (int)(VMAX)
272 				asound.saucerfire_amp_counter -= C39_DISCHARGE_TIME;
273 				while( asound.saucerfire_amp_counter <= 0 )
274 				{
275 					asound.saucerfire_amp_counter += samplerate;
276 					if( --asound.saucerfire_amp == VMIN )
277 						break;
278 				}
279 			}
280 		}
281 		if( asound.saucerfire_out )
282 		{
283 			/* C35 = 1u, Ra = 3.3k, Rb = 680
284 			 * discharge = 0.693 * 680 * 1e-6 = 4.7124e-4 -> 2122 Hz
285 			 */
286 			asound.saucerfire_counter -= 2122;
287 			if( asound.saucerfire_counter <= 0 )
288 			{
289 				INT32 n = -asound.saucerfire_counter / samplerate + 1;
290 				asound.saucerfire_counter += n * samplerate;
291 				asound.saucerfire_out = 0;
292 			}
293 		}
294 		else
295 		{
296 			/* C35 = 1u, Ra = 3.3k, Rb = 680
297 			 * charge 0.693 * (3300+680) * 1e-6 = 2.75814e-3 -> 363Hz
298 			 */
299 			asound.saucerfire_counter -= 363 * 2 * (VMAX*12/5-asound.saucerfire_vco) / 32768;
300 			if( asound.saucerfire_counter <= 0 )
301 			{
302 				INT32 n = -asound.saucerfire_counter / samplerate + 1;
303 				asound.saucerfire_counter += n * samplerate;
304 				asound.saucerfire_out = 1;
305 			}
306 		}
307         if( asound.saucerfire_out )
308 			return asound.saucerfire_amp;
309 	}
310 	else
311 	{
312 		/* charge C38 and C39 */
313 		asound.saucerfire_amp = VMAX;
314 		asound.saucerfire_vco = VMAX;
315 	}
316 	return 0;
317 }
318 
shipfire(INT32 samplerate)319 INLINE INT32 shipfire(INT32 samplerate)
320 {
321 	//static INT32 vco, vco_counter;
322 	//static INT32 amp, amp_counter;
323     //static INT32 out, counter;
324 
325     if( sound_latch[SHIPFIREEN] )
326 	{
327 		if( asound.shipfire_vco < VMAX*12/5 )
328 		{
329 			/* charge C47 (1u) through R52 (33K) and Q3 from 5V to 12V */
330 			#define C47_CHARGE_TIME (VMAX * 3)
331 			asound.shipfire_vco_counter -= C47_CHARGE_TIME;
332 			while( asound.shipfire_vco_counter <= 0 )
333 			{
334 				asound.shipfire_vco_counter += samplerate;
335 				if( ++asound.shipfire_vco == VMAX*12/5 )
336 					break;
337 			}
338         }
339 		if( asound.shipfire_amp > VMIN )
340 		{
341 			/* discharge C48 (10u) through R66 (2.7K) and CR8,
342 			 * but only while the output of theNE555 is low.
343 			 */
344 			if( asound.shipfire_out )
345 			{
346 				#define C48_DISCHARGE_TIME (VMAX * 3)
347 				asound.shipfire_amp_counter -= C48_DISCHARGE_TIME;
348 				while( asound.shipfire_amp_counter <= 0 )
349 				{
350 					asound.shipfire_amp_counter += samplerate;
351 					if( --asound.shipfire_amp == VMIN )
352 						break;
353 				}
354 			}
355 		}
356 
357 		if( asound.shipfire_out )
358 		{
359 			/* C50 = 1u, Ra = 3.3k, Rb = 680
360 			 * discharge = 0.693 * 680 * 1e-6 = 4.7124e-4 -> 2122 Hz
361 			 */
362 			asound.shipfire_counter -= 2122;
363 			if( asound.shipfire_counter <= 0 )
364 			{
365 				INT32 n = -asound.shipfire_counter / samplerate + 1;
366 				asound.shipfire_counter += n * samplerate;
367 				asound.shipfire_out = 0;
368 			}
369 		}
370 		else
371 		{
372 			/* C50 = 1u, Ra = R65 (3.3k), Rb = R61 (680)
373 			 * charge = 0.693 * (3300+680) * 1e-6) = 2.75814e-3 -> 363Hz
374 			 */
375 			asound.shipfire_counter -= 363 * 2 * (VMAX*12/5-asound.shipfire_vco) / 32768;
376 			if( asound.shipfire_counter <= 0 )
377 			{
378 				INT32 n = -asound.shipfire_counter / samplerate + 1;
379 				asound.shipfire_counter += n * samplerate;
380 				asound.shipfire_out = 1;
381 			}
382 		}
383 		if( asound.shipfire_out )
384 			return asound.shipfire_amp;
385 	}
386 	else
387 	{
388 		/* charge C47 and C48 */
389 		asound.shipfire_amp = VMAX;
390 		asound.shipfire_vco = VMAX;
391 	}
392 	return 0;
393 }
394 
life(INT32 samplerate)395 INLINE INT32 life(INT32 samplerate)
396 {
397 	//static INT32 asound.life_counter, out;
398     if( sound_latch[LIFEEN] )
399 	{
400 		asound.life_counter -= 3000;
401 		while( asound.life_counter <= 0 )
402 		{
403 			asound.life_counter += samplerate;
404 			asound.life_out ^= 1;
405 		}
406 		if( asound.life_out )
407 			return VMAX;
408 	}
409 	return 0;
410 }
411 
412 
asteroid_sound_update(INT16 * buffer,INT32 length)413 void asteroid_sound_update(INT16 *buffer, INT32 length)
414 {
415 	INT32 samplerate = nBurnSoundRate;
416 
417     while( length-- > 0 )
418 	{
419 		INT32 sum = 0;
420 
421 		sum += explosion(samplerate) / 7;
422 		sum += thrust(samplerate) / 7;
423 		sum += thump(samplerate) / 7;
424 		sum += saucer(samplerate) / 7;
425 		sum += saucerfire(samplerate) / 7;
426 		sum += shipfire(samplerate) / 7;
427 		sum += life(samplerate) / 7;
428 
429         *buffer++ = sum;
430         *buffer++ = sum;
431 	}
432 }
433 
explosion_init(void)434 static void explosion_init(void)
435 {
436     for( INT32 i = 0; i < 16; i++ )
437     {
438         /* r0 = open, r1 = open */
439         double r0 = 1.0/1e12, r1 = 1.0/1e12;
440 
441         /* R14 */
442         if( i & 1 )
443             r1 += 1.0/47000;
444         else
445             r0 += 1.0/47000;
446         /* R15 */
447         if( i & 2 )
448             r1 += 1.0/22000;
449         else
450             r0 += 1.0/22000;
451         /* R16 */
452         if( i & 4 )
453             r1 += 1.0/12000;
454         else
455             r0 += 1.0/12000;
456         /* R17 */
457         if( i & 8 )
458             r1 += 1.0/5600;
459         else
460             r0 += 1.0/5600;
461         r0 = 1.0/r0;
462         r1 = 1.0/r1;
463         vol_explosion[i] = VMAX * r0 / (r0 + r1);
464     }
465 
466 }
467 
asteroid_sound_init()468 void asteroid_sound_init()
469 {
470 	discharge = (INT16 *)BurnMalloc(32768 * sizeof(INT16));
471 	if( !discharge ) {
472 		bprintf(0, _T("Unable to allocate 64k ram for Asteroids sound custom.. crashing soon!\n"));
473 		return;
474 	}
475 
476     for( INT32 i = 0; i < 0x8000; i++ )
477 		discharge[0x7fff-i] = (INT16) (0x7fff/exp(1.0*i/4096));
478 
479 	/* initialize explosion volume lookup table */
480 	explosion_init();
481 
482     return;
483 }
484 
asteroid_sound_exit()485 void asteroid_sound_exit()
486 {
487 	if( discharge )
488 		BurnFree(discharge);
489 	discharge = NULL;
490 }
491 
asteroid_sound_reset()492 void asteroid_sound_reset()
493 {
494 	explosion_latch = 0;
495 	thump_latch = 0;
496 	memset(sound_latch, 0, sizeof(sound_latch));
497 	polynome = 0;
498 	thump_frequency = 0;
499 
500 	memset(&asound, 0, sizeof(asound));
501 }
502 
asteroid_explode_w(UINT8 data)503 void asteroid_explode_w(UINT8 data)
504 {
505 	if( data == explosion_latch )
506 		return;
507 
508 	explosion_latch = data;
509 }
510 
asteroid_thump_w(UINT8 data)511 void asteroid_thump_w(UINT8 data)
512 {
513 	double r0 = 1/47000, r1 = 1/1e12;
514 
515     if( data == thump_latch )
516 		return;
517 
518 	thump_latch = data;
519 
520 	if( thump_latch & 1 )
521 		r1 += 1.0/220000;
522 	else
523 		r0 += 1.0/220000;
524 	if( thump_latch & 2 )
525 		r1 += 1.0/100000;
526 	else
527 		r0 += 1.0/100000;
528 	if( thump_latch & 4 )
529 		r1 += 1.0/47000;
530 	else
531 		r0 += 1.0/47000;
532 	if( thump_latch & 8 )
533 		r1 += 1.0/22000;
534 	else
535 		r0 += 1.0/22000;
536 
537 	/* NE555 setup as voltage controlled astable multivibrator
538 	 * C = 0.22u, Ra = 22k...???, Rb = 18k
539 	 * frequency = 1.44 / ((22k + 2*18k) * 0.22n) = 56Hz .. huh?
540 	 */
541 	thump_frequency = 56 + 56 * r0 / (r0 + r1);
542 }
543 
544 
asteroid_sounds_w(UINT16 offset,UINT8 data)545 void asteroid_sounds_w(UINT16 offset, UINT8 data)
546 {
547 	data &= 0x80;
548     if( data == sound_latch[offset] )
549 		return;
550 
551 	sound_latch[offset] = data;
552 }
553 
554 
astdelux_sound_update(INT16 * buffer,INT32 length)555 void astdelux_sound_update(INT16 *buffer, INT32 length)
556 {
557 	INT32 samplerate = nBurnSoundRate;
558 
559     while( length-- > 0)
560 	{
561 		INT32 sum = 0;
562 
563 		sum += explosion(samplerate) / 2;
564 		sum += thrust(samplerate) / 2;
565 
566 		*buffer++ = sum;
567 		*buffer++ = sum;
568 	}
569 }
570 
astdelux_sounds_w(UINT8 data)571 void astdelux_sounds_w(UINT8 data)
572 {
573 	data = data & 0x80;
574 	if( data == sound_latch[THRUSTEN] )
575 		return;
576 	sound_latch[THRUSTEN] = data;
577 }
578 
579 
580