dfoster@jarthur.Claremont.EDU (Derek R. Foster) (10/07/90)
I am in the process (have been for the past ~2 years, actually) of writing a video game for IBM PC's in Turbo C and assembly language. By now, everything in my game pretty much works the way I want it to, with one major exception: sound generation. I want to be able to have multiple sounds from different events at the same time. (i.e. aliens firing vs. explosions vs. other stuff.) I am currently using the Turbo C sound() function and a complicated set of routines within my code to accomplish this, but I find this at best to be unsatisfactory, since this means that the sounds are dependent on the speed of the various parts of my program as it runs. (The sound-update routines only get called every so often, and the amount of code between calls may vary.) I am also very limited in what sorts of sounds I can generate, since I can only change the frequency very infrequently :-). I would _LIKE_ to have an interrupt-driven routine (perhaps on the clock-tick or a similar interrupt) which I could use to update the speaker at regular intervals, without having to bother the rest of my program about it all the time. For this I need a relatively frequently occurring interrupt, which I don't know how to generate, and I need to know which precautions need to be taken to make sure nothing gets screwed up in the system when I commandeer such an interrupt. None of the books I currently have seem to help much in this respect. I have heard tales about BASIC speeding up some sort of clock interrupt for its background music generation commands, but no details were forthcoming. Is there anyone out there who has done video-game style sound generation? Am I on the wrong track completely? Is there a better way? I am obviously not the first person to encounter this sort of problem. How have other people solved it? Is there an "accepted" way to do this? Thanks in advance! Derek Riippa Foster P.S. I would LOVE assembly-language code.
everett@hpcvra.CV.HP.COM (Everett Kaser) (10/09/90)
Here's the code I use these days for doing sound in games (and most anything else). I write most of my code in C with a little assembly language thrown in to spice up the mixture. You should be able to adapt this code to most any language/situation. Basically, it takes over INT 1C, the interrupt called by the 18.2 HZ timer tick. The routine 'timerint' gets called 18.2 times a second during the execution of the rest of your program. Every time it gets called, it increments a word called "TimePass", which can be set to 0 (or any other value) by your program at any time. Then, you can go into a loop waiting for TimePass to reach a particular value. This is an easy way to do simple wait loops that will keep your code from running too fast on a REALLY fast machine. Additionally, 'timerint' looks at a flag called "BeepTime" which contains the duration (in 1/18.2 seconds counts) of the current beep sequence. If "BeepTime" is 0, there's no beep currently being executed. Otherwise, BeepTime is decremented. If the decrement doesn't go to 0, 'timerint' returns to your program. When BeepTime decrements to 0, the next frequency/duration pair is fetched from the integer array, the frequency count is sent to Timer2 while the duration count is stored in BeepTime. When a frequency count of 0 is encountered, this signals the end of the beep sequence. This allows multiple-tone/complex sounds to be done, in the "background", while other processing (video stuff, computation, etc) goes on at its own pace in the "foreground". You set up the beep sequence pointer using the BeepSeq function, passing as an argument the address of the array of integers, which contains pairs of frequency/duration values. The frequency is value is actually the counter value which gets stored into Timer2. Hence, a value of 500 is a pretty high pitched tone, whereas a value of 10,000 to 15,000 is a pretty low pitched tone. Again, one duration count is equal to 1/18.2 seconds. So, a duration of 3 is about equal to 1/6 of a second, 9 is about equal to 1/2 second, etc. Additionally, there is a variable called "BeepFlg" which can be set to 0 to turn off all BeepSeq sound without having tests at every location from which BeepSeq is called. If you have any questions about all of this, feel free to send me email, or give me a call. Everett Kaser Hewlett-Packard Company ...hplabs!hp-pcd!everett work: (503) 750-3569 Corvallis, Oregon everett%hpcvra@hplabs.hp.com home: (503) 928-5259 Albany, Oregon ---------------------- beginning of C module ---------------------------- /************************************************************************ * * ************************************************************************/ extern int BeepFlg, TimePass; /************************************************************************ * * ************************************************************************/ int ill_action[] = {10000, 2,0,0}; int s1[] = {5000,1, 10000,1, 4000,1, 1000,1, 500,1, 0,0}; int s2[] = {10000, 1, 8000, 1, 6000, 1,0,0}; int s3[] = {500,1,8000,1,750,1,8000,1,1000,1,0,0}; int s4[] = {15000,1,12000,1,9000,1,7000,1,3000,1,1000,1,300,1,100,1,0,0}; int s5[] = {2000,4,200,1,0,0}; int s6[] = {1000,1,8000,1,0,0}; /************************************************************************ * * ************************************************************************/ void main(int argc, char *argv[], char *envp[]) { int i; TakeTimer(); /* take over INT 1C 18.2 Hz timer tick */ while( (i=getch()) != 27 ) { /* while not ESC key */ switch( i ) { case '1': BeepSeq(s1); break; case '2': BeepSeq(s2); break; case '3': BeepSeq(s3); break; case '4': BeepSeq(s4); break; case '5': BeepSeq(s5); break; case '6': BeepSeq(s6); break; default: BeepSeq(ill_action); break; } } BeepOff(); /* make sure beeper is off before giving up Timer int */ GiveTimer(); /* release INT 1C vector */ exit(0); /* return ERRORLEVEL = 0 */ } ------------------------------ end of C module ----------------------------- ---------------------------- beginning of ASM module ----------------------- ;************************************************************************ ;* Copyright 1989 Everett Kaser. All rights reserved. * ;************************************************************************ page 60,132 title TIMER/BEEPER/KEYBOARD routines ; .MODEL COMPACT, C ;************************************************************************ ;************************************************************************ ; DATA SEGMENT ;************************************************************************ ;************************************************************************ .FARDATA MiscData public TimePass public BeepFlg ; TimePass dw 0 ; BeepFlg dw 1 ; BeepTime dw 0 BeepPtrOff dw 0 BeepPtrSeg dw 0 ; .CODE assume DS:MiscData ;************************************************************************ ; take_timer(); ;************************************************************************ old1coff dw ? old1cseg dw ? TakeTimer PROC uses DS ES SI DI mov ax,351ch ; get old timer interrupt vector int 21h mov cs:[old1cseg],es mov cs:[old1coff],bx ; save it push cs pop ds lea dx,timerint mov ax,251ch ; take it int 21h mov ax,MiscData mov ds,ax ret TakeTimer endp ;************************************************************************ ; give_timer(); ;************************************************************************ GiveTimer PROC uses DS ES SI DI mov ds,cs:[old1cseg] mov dx,cs:[old1coff] mov ax,251ch ; give it int 21h ret GiveTimer endp ;************************************************************************ ; beep_seq(listptr); ;************************************************************************ BeepSeq PROC uses DS ES, listptr:dword mov ax,MiscData mov ds,ax cmp BeepFlg,0 jz bsret les dx,listptr ; get ptr to beep sequence mov BeepPtrOff,dx ; save it mov BeepPtrSeg,es call beepsub bsret: ret BeepSeq endp ;************************************************************************ ; BeepOff(); ;************************************************************************ BeepOff PROC uses DS mov ax,MiscData mov ds,ax mov BeepTime,0 in al,61h ; else turn beeper off and al,0fch out 61h,al ret BeepOff endp ;************************************************************************ timerint: push es push ds push ax push bx push cx push dx push si push di push bp mov ax,MiscData mov ds,ax inc TimePass mov ax,BeepTime or ax,ax jz texit dec BeepTime jnz texit call beepsub texit: pop bp pop di pop si pop dx pop cx pop bx pop ax pop ds pop es jmp dword ptr cs:old1coff ;************************************************************************ beepsub: les di,dword ptr BeepPtrOff ; get ptr to beep sequence mov ax,es:[di] ; get next frequency add di,2 jnc bsok1 mov bx,es add bh,10h mov es,bx bsok1: or ax,ax ; end of list? jnz freqok ; jif no in al,61h ; else turn beeper off and al,0fch out 61h,al ret freqok: push ax mov al,0b6h out 43h,al pop ax out 42h,al ; write it to timer 2 mov al,ah out 42h,al in al,61h or al,3 out 61h,al mov ax,es:[di] ; get duration of beep add di,2 jnc bsok2 mov bx,es add bh,10h mov es,bx bsok2: mov BeepTime,ax mov BeepPtrOff,di mov BeepPtrSeg,es ret ;************************************************************************ @curseg ends end ------------------------------- end of ASM module -----------------------