[comp.os.msdos.programmer] IBM Sound

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