Ron Baker
Ron Baker

Reputation: 21

Assembly 8086 - DOSBOX - How to produce beep sound?

I am working on a "simon" game in assembly I need to make a beep sound whenever a button turns on, the beeps should be different from each other as well. thanks

Upvotes: 2

Views: 6300

Answers (1)

Margaret Bloom
Margaret Bloom

Reputation: 44146

You can use the speaker to keep your design simple.
The speaker lets you play square waves at different frequencies, it can actually be used to reproduce digital audio but that's more involved.

The speaker is just an electromagnet, when the current flows through it, it is pulled back otherwise it stays in its default position.
By moving the speaker back and forth it's possible to create sound waves.

The speaker can be moved manually or by using the PIT's channel 2.
Bit 0 of port 61h controls the speaker source (0 = manual, 1 = PIT) and the bit 1 of the same port is the "speaker enable" bit when using the PIT (the speaker "position" when not).

Here's a schematic (from this page) missing the manual driving part:

Speaker schematic

The PIT is controlled via port 40h-43h, we will use Mode 3 (Square Wave generator) setting each time both bytes of the divider.
The PIT has an oscillator running at about 1.193180 MHz, the divider is used to control the period of the square wave.
Without dealing with internals: at each tick of the PIT oscillator the divider loaded is decremented. The period of the square wave is equal to the time needed by the PIT to decrement the divider down to zero.

Producing a sound is just a matter of programming the PIT with the desired divider and enabling the speaker.
At some time later, we need to disable it.
An easy way to do this is using the int 1ch that is called 18.2 times a second.

By saving a duration in a variable when first playing a sound, by decrementing it at each tick of the int 1ch and by disabling the speaker when the count reaches zero it's possible to control the duration of the beep.

Using the int 1ch requires a setup function (beep_setup) and a teardown function (beep_teardown).

BITS 16

ORG 100h

__start__:

 ;Setup
 call beep_setup

 ;Sample beep of ~2sec
 mov ax, 2000
 mov bx, 36
 call beep_play

 ;Wait for input
 xor ax, ax
 int 16h

 ;Tear down
 call beep_teardown

 mov ax, 4c00h
 int 21h

 ;-------------------------------------------------

 ;
 ;Setup the beep ISR
 ;

 beep_setup:
  push es
  push ax

  xor ax, ax
  mov es, ax

  ;Save the original ISR
  mov ax, WORD [es: TIMER_INT * 4]
  mov WORD [cs:original_timer_isr], ax
  mov ax, WORD [es: TIMER_INT * 4 + 2]
  mov WORD [cs:original_timer_isr + 2], ax

  ;Setup the new ISR

  cli
  mov ax, beep_isr
  mov WORD [es: TIMER_INT * 4], ax
  mov ax, cs
  mov WORD [es: TIMER_INT * 4 + 2], ax
  sti

  pop ax
  pop es
  ret 


 ;
 ;Tear down the beep ISR
 ;

 beep_teardown:
  push es
  push ax

  call beep_stop

  xor ax, ax
  mov es, ax

  ;Restore the old ISR

  cli
  mov ax, WORD [cs:original_timer_isr]
  mov WORD [es: TIMER_INT * 4], ax
  mov ax, WORD [cs:original_timer_isr + 2]
  mov WORD [es: TIMER_INT * 4 + 2], ax
  sti

  pop ax
  pop es
  ret 

 ;
 ;Beep ISR
 ;
 beep_isr:
  cmp BYTE [cs:sound_playing], 0
  je _bi_end

  cmp WORD [cs:sound_counter], 0
  je _bi_stop

  dec WORD [cs:sound_counter]

 jmp _bi_end

_bi_stop:
  call beep_stop

_bi_end:
  ;Chain
  jmp FAR [cs:original_timer_isr]

 ;
 ;Stop beep
 ;
 beep_stop:
  push ax

  ;Stop the sound

  in al, 61h
  and al, 0fch    ;Clear bit 0 (PIT to speaker) and bit 1 (Speaker enable)
  out 61h, al

  ;Disable countdown

  mov BYTE [cs:sound_playing], 0

  pop ax
  ret

 ;
 ;Beep
 ;
 ;AX = 1193180 / frequency
 ;BX = duration in 18.2th of sec
 beep_play:
  push ax
  push dx

  mov dx, ax

  mov al, 0b6h
  out 43h, al

  mov ax, dx
  out 42h, al
  mov al, ah
  out 42h, al


  ;Set the countdown
  mov WORD [cs:sound_counter], bx

  ;Start the sound

  in al, 61h
  or al, 3h    ;Set bit 0 (PIT to speaker) and bit 1 (Speaker enable)
  out 61h, al


  ;Start the countdown

  mov BYTE [cs:sound_playing], 1

  pop dx
  pop ax
  ret

 ;Keep these in the code segment
 sound_playing      db  0
 sound_counter      dw  0
 original_timer_isr     dd  0

 TIMER_INT      EQU     1ch

Special thanks to Sep Roland for fixing a flaw in the original code!

You can use beep_play to play a beep, the units used are the "natural" unit of the hardware configuration explained above.
If your frequencies and duration are fixed, these units simplify the code at no cost.

The beep stops after the duration given, you can use beep_stop to stop it forcefully.

Playing multiple sound at a time it's impossible (even mixing them is impossible without resorting to PWM techniques).
Calling beep_play while another beep is in play will have the effect of stopping the current beep and starting the new one.

Upvotes: 10

Related Questions