[net.micro.pc] How do you beep?

fwb@siemens.UUCP (09/03/86)

How do you program the sound on an IBM AT or XT?  The technical reference
manual does not help much.  The PLAY and SOUND instructions of BASIC are not
useful in a C program.  (No, I don't want to fork BASICA to make a beep :-)

Would somebody send me the port address and values for different notes, or a
simple C program, or a simple Pascal program, or some other helpful program
which does not rely on library routines to do the real work.  If there is
sufficient interest I will summarize the most useful information to the net.

Thanks,
Fred
-----------------------------------------------------
Frederic W. Brehm       (ihnp4!princeton!siemens!fwb)
Siemens Research and Technology Laboratories
105 College Road East
Princeton, NJ 08540
(609) 734-3336

thj@hpcnof.UUCP (Thomas Hjellming) (09/04/86)

>How do you program the sound on an IBM AT or XT?  The technical reference
>manual does not help much.  The PLAY and SOUND instructions of BASIC are not
>useful in a C program.  (No, I don't want to fork BASICA to make a beep :-)
>
>Would somebody send me the port address and values for different notes, or a
>simple C program, or a simple Pascal program, or some other helpful program
>which does not rely on library routines to do the real work.  If there is
>sufficient interest I will summarize the most useful information to the net.
>
>Thanks,
>Fred

      The 'Programmers Guide to the IBM PC' by Peter Norton contains
an entire chapter about sound generation on the PC family.  It contains
all the information you are requesting.


Tom  Hjellming
Colorado Networks Division
Hewlett-Packard
ihnp4!hpfcla!thj

wtm@neoucom.UUCP (Bill Mayhew) (09/05/86)

The following information is based on Appendix H of the book,
"C Primer Plus"; Waite, Prata & Martin; Sams Books.


I.  The speaker is connected to an 8255 Parallel Interface
Controller chip in an IBM xt.  It should be in the same place on
true clones.  The port number for the speaker connection is 97
decimal.  (Anyone know if the 8255 is in the same place on the AT?,
I'm pretty sure it is.)

II.  Bits 0 and 1 of the above port both have to be set to logic
state 1 to move the cone of the speaker in and conversely, setting
those bits to 0 reverses the direction of the cone.

III.  Note that the above register is bidirectional.  The keyboard
strobe and cassette motor are also hooked there, so you want to
preserve the state of the bits other than the ones you are playing
with.

IV.  The basic process is something like:

for (duration = tone_length; duration > 0; duration--) {
	state = inp(97) ^ 0x03;    */complement lower 2 bits /*
	outp(97, state);

	for (clik_wdth = pitch_const; clik_wdth >0; clik_wdth--){
		*/waste some time /*
		};
	}

V.  Obviously, the sound that you get with the above is going to be
processor speed dependent.  What you can do is in the
initialization part of your program is to call the DOS time
interrupt (sorry don't remember the number at the moment) and then
count to some big number in a tight loop (10,000 would be good).
Call the dos time interrupt again and see how long it took you to
count.  You'll then know the clock speed of the processor that you
are running on.  It's easiest to have a table of note valuse for
"pitch_const" already calculated before hand, and have the results
of your test select the correct table.  In theory, you could count
the number of CPU cycles in your loop, and thus you'd be able to
compute the table values so that you could normalize to weirdo
clock speeds.

VI.  There is also an 8253 Counter/Timer chip in a Pee-cee that you
can use to set the durrations of things.  See the book I referenced
for instructions on using the 8253.  On the XT, the 8253 counts
down at a rate of 1.190 MHz.  Beware that the counting rate is a
little faster on the original models of PC.  I suppose it is
something odd on ATs as well, but I haven't had any reason to
check.

VII.  Of course, if you could care less about what pitch you're
making just send a 0x07 (control G) with the DOS console print
interrupt.  Normally DOS will take care of beeping the speaker for
you.

I don't normally hack C for a living, so I apologise if there are
any typos in the C example I stuck in.  I think you get the gist of
it any way, right?

    --Bill

Bill Mayhew
Division of Basic Medical Sciences
Northeastern Ohio Universities College of Medicine
Rootstown, OH  44272  USA       (216) 325-2511
(wtm@neoucom.UUCP)

bright@dataio.UUCP (Walter Bright) (09/08/86)

In article <960003@hpcnof.UUCP> thj@hpcnof.UUCP (Thomas Hjellming) writes:
>>How do you program the sound on an IBM AT or XT?  The technical reference
>>manual does not help much.  The PLAY and SOUND instructions of BASIC are not
>>useful in a C program.  (No, I don't want to fork BASICA to make a beep :-)
>      The 'Programmers Guide to the IBM PC' by Peter Norton contains
>an entire chapter about sound generation on the PC family.  It contains
>all the information you are requesting.

Also, try 'Bluebook of Assembly Routines for the IBM PC and XT' by
Christopher L. Morgan.

mlandau@Diamond.BBN.COM (Matt Landau) (09/08/86)

Since so many people have remarked on how to make noises with your
IBM PC, I thought I'd just take the simple approach and post the
necessary code.  The following shar file contains sources for a routine
called "sound" which will drive the speaker at a given frequency for
a given time.  It relies on a processor-independent sleep() call, for
which source is also supplied.  This code has been used on PC's, AT's,
and Compaq's, compiled with Lattice 2.15, but there's no reason it
shouldn't work with any Lattice 2.x or 3.x compiler.  Conversion to 
other compilers is straightforward.

Note that the sleep() here is not compatible with the Unix sleep() 
call, since this one works in 1/100's of a second.

Herewith, the source code:

------- CUT HERE ------------------------------------ CUT HERE --------

# This is a shell archive.  Remove anything before this line,
# then unpack it by saving it in a file and typing "sh file".
#
# Wrapped by slate!mlandau on Mon Sep  8 15:55:25 EDT 1986
# Contents:  stdtypes.h sleep.c sound.c
 
echo x - stdtypes.h
sed 's/^XX//' > "stdtypes.h" <<'@//E*O*F stdtypes.h//'
XX/*
XX * S T D T Y P E S . H
XX */

XX#include "dos.h"
XXtypedef int		void;
XXtypedef int		bool;
XXtypedef union REGS	REGS;
XXtypedef struct SREGS	SREGS;
@//E*O*F stdtypes.h//
chmod u=rw,g=rw,o=r stdtypes.h
 
echo x - sleep.c
sed 's/^XX//' > "sleep.c" <<'@//E*O*F sleep.c//'
XX/* 
XX * S L E E P . C
XX * 
XX * Sleep () call for IBM PC, XT, AT.  Depends on timer tick working as
XX * advertised, 18.2 ticks per second.
XX *
XX * This uses int 1A to read the time-of-day clock.  Care is taken to do
XX * something reasonable when the clock rolls over at midnight, so sleep ()
XX * does not bomb late at night.
XX */

XX#include <stdio.h>
XX#include <stdtypes.h>

XX#define TIMER_INT	0x1A
XX#define T_READTIME	0

XX#define SCALE		1000L
XX#define TICKRATE	182L	   /* timer tick rate/100th sec. x scale */
XX#define ROLLOVER	1	   /* est. # of ticks when clock rolls over */
XX#define MKLONG(hi, low)	((long)(((long)hi << 16) | (unsigned)low))


XXvoid	  sleep (how_long)
XXunsigned  how_long;			/* Hundredths of a second */
XX{       
XX	long	this_tick, last_tick;
XX	long	ticks = 0L;
XX	long	count = (long)(TICKRATE * how_long / SCALE);
XX	REGS	r;
XX	
XX	r.h.ah = T_READTIME;
XX	int86 (TIMER_INT, &r, &r);
XX	last_tick = MKLONG (r.x.cx, r.x.dx);
XX	
XX	while (ticks < count)
XX	{       
XX		r.h.ah = T_READTIME;
XX		int86 (TIMER_INT, &r, &r);
XX		this_tick = MKLONG (r.x.cx, r.x.dx);
XX		/* If midnight just passed, assume ROLLOVER ticks */
XX		ticks += (r.h.al == 0) ? this_tick - last_tick : ROLLOVER;
XX		last_tick = this_tick;
XX	}
XX}

@//E*O*F sleep.c//
chmod u=rw,g=rw,o=r sleep.c
 
echo x - sound.c
sed 's/^XX//' > "sound.c" <<'@//E*O*F sound.c//'
XX/* LINTLIBRARY */

XX/* 
XX * S O U N D . C
XX * 
XX * Routines for manipulating the PC speaker.  Since there is no BIOS
XX * interrupt for sound, this code is probably NOT portable.  It relies on
XX * directly controlling the 8255 Programmable Peripheral Interface chip.
XX * 
XX * For reference, I suggest you look at the PC Technical Reference Manual
XX * and at a good book on 8088 assembly language (Scanlon's IBM PC & XT
XX * ASSEMBLY LANGUAGE contains a section on programming the speaker.)
XX */

XX#include <stdio.h>
XX#include <stdtypes.h>


XX#define HZ		1193180		/* system clock is 1.19 MHz      */
XX#define T_BASE		0x40		/* 8253 timer port 0	         */
XX#define T_TONE		(T_BASE + 2)	/* 8253 port 2 controls spkr     */
XX#define T_CONTROL	(T_BASE + 3)	/* 8253 control port addr.       */
XX#define	TIMERDIV(freq)	((unsigned)(HZ/freq)) 	/* Timer divisior        */
XX#define ENABLE_TIMER	182		/* MAGIC NUM from BIOS listing   */
XX#define OUT_8255	0x61		/* 8255 PPI output port address  */
XX#define SPKRBITS	3		/* Bit 0 = control spkr by timer */
XX 					/* Bit 1 = speaker on/off        */

XXextern byte	inp(), outp();		/* Read, write ports */

XX/* 
XX * spkr_on -- turn on the speaker 
XX */

XXstatic
XXvoid	  spkr_on (divisor)
XXunsigned  divisor;
XX{       
XX	byte	status, div;
XX	
XX	status = inp (OUT_8255);		/* get current status */
XX	outp (T_CONTROL, ENABLE_TIMER);
XX	div = divisor & 0xFF;			/* low byte of divisor */
XX	outp (T_TONE, div);
XX	div = (divisor >> 8) & 0xFF;		/* high byte of divisor */
XX	outp (T_TONE, div);
XX	outp (OUT_8255, (status | SPKRBITS));	/* turn on speaker */
XX}

XX/* 
XX * spkr_off -- turn off the speaker
XX */

XXstatic
XXvoid	spkr_off ()
XX{       
XX	byte	status;
XX	
XX	status = inp (OUT_8255);		/* get current status */
XX	outp (OUT_8255, (status & ~SPKRBITS));	/* turn speaker off */
XX}


XX/* 
XX * sound (freq, dur)
XX * 
XX * Make a noise of the given frequency (which may be 37 - 32767 Hz) for the
XX * given duration, in tenths of a second.
XX */
XX 
XXvoid	sound (freq, dur)
XXunsigned freq, dur;
XX{       
XX	extern void	sleep();
XX	
XX	spkr_on (TIMERDIV (freq));
XX	sleep (dur);
XX	spkr_off ();
XX}


XXvoid	beep_low ()
XX{       
XX	sound (440, 15);
XX}


XXvoid	beep ()
XX{       
XX	sound (660, 15);
XX}


XXvoid	beep_high ()
XX{       
XX	sound (880, 15);
XX}

@//E*O*F sound.c//
chmod u=rw,g=rw,o=r sound.c
 
echo Inspecting for damage in transit...
temp=/tmp/shar$$; dtemp=/tmp/.shar$$
trap "rm -f $temp $dtemp; exit" 0 1 2 3 15
cat > $temp <<\!!!
       9      29     139 stdtypes.h
      46     196    1183 sleep.c
     107     425    2491 sound.c
     162     650    3813 total
!!!
wc  stdtypes.h sleep.c sound.c | sed 's=[^ ]*/==' | diff -b $temp - >$dtemp
if [ -s $dtemp ]
then echo "Ouch [diff of wc output]:" ; cat $dtemp
else echo "No problems found."
fi
exit 0
-- 
 Matt Landau      	 		BBN Laboratories, Inc.
    mlandau@diamond.bbn.com		10 Moulton Street, Cambridge MA 02238
 ...harvard!diamond.bbn.com!mlandau     (617) 497-2429