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 -----------------------