Mega Code Archive

 
Categories / Delphi / Multimedia
 

Playing sounds thru the built in PC speaker

Title: Playing sounds thru the built-in PC speaker Question: The Windows API Beep can be used to play sounds thru the PC speaker. It takes two parameters: frecuency of the sound in hertz and duration of the sound in miliseconds. The problem with this function is that the parameters are ignored under Windows 95/98/98SE/Me if there is a sound card present, and in this case Beep simply plays the default system sound thru the sound card. So, what if we want to play an arbitrary sound thru the PC speaker under these operating systems? Answer: Playing sounds thru the built-in PC speaker By Ernesto De Spirito The Windows API Beep can be used to play sounds thru the PC speaker. It takes two parameters: frecuency of the sound in hertz and duration of the sound in miliseconds. The problem with this function is that the parameters are ignored under Windows 95/98/98SE/Me if there is a sound card present, and in this case Beep simply plays the default system sound thru the sound card. So, what if we want to play an arbitrary sound thru the PC speaker under these operating systems if the machine is equipped with a sound card? We can do it by interfacing with the 8253/8254 Counter/Timer and the 8255 Programmable Peripheral Interface (PPI) chips on the motherboard, by reading and writing thru their ports. We'll make use of some inline assembler for that purpose. For some information about inline assembler, see: Inline Assembler in Delphi (I) - Introduction http://www.delphi3000.com/articles/article_3766.asp The IN and OUT assembler instructions allow us to read from and write to an I/O port respectively. To read from a port: in accumulator, port IN reads a byte, word or doubleword from the specified port into the accumulator register, i.e. AL, AX, or EAX for a byte, word or doubleword port respectively. The port number can be a byte constant (0..255) or the DX register (to have access to all I/O ports). To write to a port: out port, accumulator OUT writes the byte, word or doubleword in the accumulator register to the specified port. Again, the port number can be a byte constant or the DX register. Here we'll see an example of the use of assembler for reading and writing to I/O ports to program the 8253/8254 Counter/Timer and the 8255 Programmable Peripheral Interface (PPI) chips on the motherboard in order to make the built-in PC speaker play a tone. The steps to play a tone using those chips are the following: Prepare the 8253/8254 to receive the frequency. This is done by writing the value $B6 to the Timer Control Register (port $43). Write the frequency of the sound to the 8253/8254's Frequency Register (port $42). Actually, it's not the frequency in Hertz that we should write to the register, but the result of 1331000 divided into that frequency. First we should write the low order byte of the result, and then the high order byte. Turn the speaker on and make it use the 8253/8254 Counter/Timer. This is done by turning on the first two bits of Port B of the 8255 PPI (port $61). Turn the speaker off when the sound should stop. This is done by turning off the second bit of Port B of the 8255 PPI (port $61). The following inline assembler function implements the steps listed above: procedure SpeakerSound(Frequency: word; Duration: longint); // Copyright (c) 2003 Ernesto De Spirito // Visit: http://www.latiumsoftware.com // Plays a tone thru the PC speaker using the 8253/8254 // Counter/Timer chip and the 8255 Programmable Peripheral // Interface (PPI) chip on the motherboard. // NOTE: This code won't work under Windows NT/2000/XP. Use Beep. asm push edx // Push Duration on the stack (for Sleep) mov cx, ax // CX := Frequency; // Prepare the 8253/8254 to receive the frequency data mov al, $B6 // Function: Expect frequency data out $43, al // Write to Timer Control Register // Compute the frequency data mov dx, $14 // DX:AX = $144F38 mov ax, $4F38 // = 1331000 div cx // AX := 1331000 / Frequency; // Send the frequency data to the 8253/8254 Counter/Timer chip out $42, al // Write low byte to the Frequency Address mov al, ah // AL := High byte of AX out $42, al // Write high byte to the Frequency Address // Tell the 8255 PPI to start the sound in al, $61 // Read Port B of the 8255 PPI or al, $03 // Set bits 0 and 1: // bit 0 -- use the 8253/8254 // bit 1 -- turn speaker on out $61, al // Write to Port B of the 8255 PPI // Wait call Sleep // Sleep(Duration); // requires Windows unit // Tell the 8255 PPI to stop the sound in al, $61 // Read Port B of the 8255 PPI and al, NOT 2 // Clear bit 1 (turn speaker off) out $61, al // Write to Port B of the 8255 PPI end; Sample call: procedure TForm1.Button1Click(Sender: TObject); var i: integer; begin Randomize; for i := 1 to 3 do SpeakerSound(Random(900)+100, 200); end; In conclusion, under Windows NT/2000/XP use the API Beep, and under Windows 95/98/98SE/Me use SpeakerSound instead. In the following article you'll find a class to mimic the PLAY command of GWBASIC to produce music thru the PC speaker: Play Musical Notes via PC Speaker Class - by Mike Heydon http://www.delphi3000.com/articles/article_3249.asp You can modify the source code to check for the operating system version and to call SpeakerSound instead of Windows.Beep to make the code also work under 95/98/98SE/Me, regardless of whether there is a sound card present or not. NOTE: Using the PC speaker to make sounds is OBSELETE. Applications should use MIDI or WAVE sounds instead.