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