Producing Sound on the Internal Speaker

SND produces sound on the speaker.
Version: 1.00b
Author: Ben Lunt (Forever Young Software)
Date: 09 Dec 1998
Assembler: NBASM

SND sends the freq's specified in a buffer to the speaker for a duration of the next word in the buffer (1-16 where 16 = one second).
This allows you to specify any freq. and duration wanted to produce sounds from a simple rhyme to a complex Bomb sound.
You can use this routine in your game with little or no trouble.
You can have an 'unlimited' (less than 64k) number of buffers holding different sound data. All you have to do is point SI to this buffer and then call it.

; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; This routine shows how to send sound (freq.) to the internal speaker.
; You can sound a frequency between 1 and 4000+.  Please Note that the
; human ear has a hard time hearing a frequency less than about 440.
; I use a timer function to wait for the duration.  I also have
; the freq. and duration in a buffer and get a single freq. and duration
; value each time.  This is so that you can make quite a few different
; sounds and just point SI to that buffer and then call this routine.
; The 00h,00h (asciiz) at the end of the buffer tells this routine to
; quit.
;

.model tiny
.code
           org  100h
start:     push ds
           pop  es
           mov  si,offset SomeTune
           
           mov  dx,61h                  ; turn speaker on
           in   al,dx                   ;
           or   al,03h                  ;
           out  dx,al                   ;

           mov  dx,43h                  ; get the timer ready
           mov  al,0B6h                 ;
           out  dx,al                   ;

LoopIt:    lodsw                        ; load desired freq.
           or   ax,ax                   ; if freq. = 0 then done
           jz   short LDone             ;
           mov  dx,42h                  ; port to out
           out  dx,al                   ; out low order
           xchg ah,al                   ;
           out  dx,al                   ; out high order
           lodsw                        ; get duration
           mov  cx,ax                   ; put it in cx (16 = 1 second)
           call PauseIt                 ; pause it
           jmp  short LoopIt

LDone:     mov  dx,61h                  ; turn speaker off
           in   al,dx                   ;
           and  al,0FCh                 ;
           out  dx,al                   ;

           .exit                        ; exit to DOS


; =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
; This routine waits for a specified amount of milliseconds (within 50ms)
; Since I want to keep it simple, I am going to use the BIOS timer tick
;  at 0040:006Ch. It increments 18.2 times a second.
;   (1000 milliseconds divided by 18.2 = ~55ms)
; This is not a very good delay.  Depending on when it is called,
;  it could delay up to 110ms.  However it will always delay at
;  least 55ms.
; If you do much with this, you will need a much better delay routine.
;  You can code a delay using the RDTSC instruction, if you know you
;  have a CPU that supports that instruction (all modern CPU's do).
; There are many other delay techniques to choose from.
PauseIt    proc near uses ax cx es

           mov  ax,0040h
           mov  es,ax

           ; wait for it to change the first time
           mov  al,es:[006Ch]
@@:        cmp  al,es:[006Ch]
           je   short @b

           ; wait for it to change again
loop_it:   mov  al,es:[006Ch]
@@:        cmp  al,es:[006Ch]
           je   short @b

           sub  cx,55
           jns  short loop_it

           ret
PauseIt    endp


SomeTune   dw  1397,08
           dw  1397,08
           dw  1397,08
           dw  1318,06
           dw  1244,16
           dw  1046,04
           dw  1108,04
           dw  1174,04
           dw  1244,04
           dw  1174,08
           dw  1244,08
           dw  1318,08
           dw  1396,08
           dw  1480,08
           dw  1568,16
           dw  00h,00h

.end


NOSOUND is a TSR that 'silences' the sound on the speaker.
Version: 1.00b
Author: Ben Lunt (Forever Young Software)
Date: 09 Dec 1998
Assembler: NBASM

To use this routine, enter NOSOUND ON at the DOS prompt. To allow sound again, enter NOSOUND OFF.
The routine stays resident, so you can call NOSOUND with the ON or OFF parameters as much as you would like.

  ; use NOSOUND ON to silence the speaker
  ; use NOSOUND OFF to 'un' silence the speaker
  ;
  .model tiny
  .code
  org 100h
  start:     jmp  install

  Copyright  db 13,10,'NOSOUND  Quiets the hardware speaker.'
  db  13,10,'Copyright  1984-2024  Forever Young Software',13,10,36
  AllowY     db  13,10,'  Allowing Sound$'
  AllowN     db  13,10,'  Not Allowing Any Sound$'
  Old1CAdr   dw  00h          ; these remember the original Int 1Ch address
  Old1CSeg   dw  00h          ; they must be in the code segment
  Old16Adr   dw  00h          ; these remember the original Int 16h address
  Old16Seg   dw  00h          ; they must be in the code segment
  job        db  00h          ; 0 = sound off, else sound on

  NewInt1Ch: cli                          ; disable interrupts
  mov  al,cs:job               ; if job != 0 then skip ours
  or   al,al                   ; (need to use fast instructions)
  jnz  short SoundOn           ; ('cmp mem,immed' is just to slow)
  push dx                      ;

  ; the in and out instructions are extremely slow on so-called
  ;'faster' machines (386, 486, 586 (pentiums)) so this is why you
  ; still hear a little bit of sound at first.

  mov  dx,61h                  ; turn sound off
  in   al,dx                   ;  .
  and  al,0FCh                 ; clear bits 1 & 0
  out  dx,al                   ;  .
  pop  dx                      ;
  SoundOn:   sti                          ; reenable interrupts
  jmp  far cs:Old1CAdr

  NewInt16h: cli                          ; disable interrupts
  cmp  ah,66h                  ; if our service number
  jne  short SkipOurs
  mov  cs:job,al               ; then put 'job' in job above
  mov  al,ah                   ; send installed flag
  sti                          ; and return to NOSOUND.COM
  iret                         ;
  SkipOurs:  sti                          ; reenable interrupts
  jmp  far cs:Old16Adr         ;

  Install:   mov  dx,offset Copyright     ; print message
  mov  ah,09h                  ;
  int  21h                     ;
  mov  ah,62h                  ; get PSP segment
  int  21h                     ;
  mov  es,bx                   ;
  xor  al,al                   ; assume no sound
  mov  dx,offset AllowN        ; print message
  mov  ah,es:[0083h]           ; get command line 'n' or 'f'
  cmp  ah,'n'                  ; if 'n' then job != 0
  jne  short SoundOff1         ;
  mov  al,0FFh                 ;
  mov  dx,offset AllowY        ; print message
  SoundOff1: push ax                      ; save al
  mov  ah,09h                  ;
  int  21h                     ;
  pop  ax                      ; restore al
  mov  ah,66h                  ; call interrupt 16h w/our service #
  int  16h                     ; on return:
  cmp  al,66h                  ;
  jne  short NotInstld         ; if al = 66h, then is installed
  mov  ah,4Ch                  ; and exit (no TSR it)
  int  21h                     ;

  NotInstld: mov  ah,62h                  ; free environment string
  int  21h                     ;
  mov  es,bx                   ;
  mov  bx,2Ch                  ;
  mov  ax,es:[bx]              ;
  mov  es,ax                   ;
  mov  ah,49h                  ;
  int  21h                     ;

  mov  ax,351Ch                ; ask DOS for the existing Int 1Ch vector address
  int  21h                     ; DOS returns the segment:address in ES:BX
  mov  cs:Old1CAdr,bx          ; save it locally
  mov  cs:Old1CSeg,es

  mov  ax,251Ch                ; point Interrupt 1Ch to our own handler
  mov  dx,offset cs:NewInt1Ch
  push cs                      ; copy CS into DS
  pop  ds
  int  21h

  mov  ax,3516h                ; ask DOS for the existing Int 16h vector address
  int  21h                     ; DOS returns the segment:address in ES:BX
  mov  cs:Old16Adr,bx          ; save it locally
  mov  cs:Old16Seg,es

  mov  ax,2516h                ; point Interrupt 16h to our own handler
  mov  dx,offset cs:NewInt16h
  push cs                      ; copy CS into DS
  pop  ds
  int  21h

  mov  dx,offset install       ; save all TSR code + PSP
  sub  dx,offset start
  sub  dx,271
  mov  cl,04h                  ;   + 15 bytes to make sure we get all of TSR
  shr  dx,cl                   ; (paragraphs)
  mov  ax,3100h                ; exit to DOS but stay resident
  int  21h                     ;

  .end start
    

Please let me know if there are any errors or if this routine doesn't work on your machine. On faster machines
(about 133mhz and faster), this routine might not work.

Please read the .ASM file for distribution rules and copyrights.