; Silence PC speaker (TSR)
; Written by Tom Torfs (2:292/516@fidonet.org)
; I hereby donate this code to the public domain

; Some little plops can sometimes still be heard
; Increase FREQUENCY to improve the silencing.
; (too high values of FREQUENCY will cause
;  slowdown of the system)

.model tiny
locals @@

FREQUENCY equ 1000
WAITTICKS equ (FREQUENCY*10/182)

program segment para public 'program'
assume cs:program,ds:program,es:program,ss:program

org 100h
@start:
   jmp @begin

; resident data

oldint08 label dword
oldofs08 dw ?
oldseg08 dw ?
oldint2F label dword
oldofs2F dw ?
oldseg2F dw ?

counter dw WAITTICKS

; resident code

newint08 proc
   dec cs:[counter]
   jz @@oldhandler
   push ax
   in al,61h
   and al,11111100b
   out 61h,al
   mov al,20h
   out 20h,al
   pop ax
   iret
@@oldhandler:
   ; restore counter
   mov cs:[counter],WAITTICKS
   ; jump to old handler
   jmp cs:[oldint08]
endp

newint2F proc
   ; was this meant for us ?
   cmp ax,'SI'
   jne @@next2F
   cmp bx,'LE'
   jne @@next2F
   ; return signature & PSP segment
   mov ax,cs
   mov es,ax
   mov ax,'OK'
   iret
@@next2F:
   ; no it wasn't -> jump to old handler
   jmp cs:[oldint2F]
endp

@res_end:
; non-resident data

progname db 'Silent by Tom Torfs $'
installmessage db 'installed',13,10,'$'
removemessage db 'removed',13,10,'$'

already_resident db 'already resident.',13,10
                 db 'Use option R to remove.',13,10,'$'
not_resident db 'not resident.',13,10,'$'

syntaxerror db 13,10,'Syntax error',13,10
            db 'Type SILENT without parameters to install.',13,10
            db 'Type SILENT R to remove.',13,10,'$'

option db 0

; non-resident code

@begin:
   ; show program title
   mov ah,9
   mov dx,offset progname
   int 21h

   ; check parameters
   mov cl,ds:[80h]
   or cl,cl
   jz @@paradone
   xor ch,ch
   mov si,81h
   @@paramloop:
      lodsb
      cmp al,' '
      je @@notoption
      cmp al,9
      je @@notoption
      cmp al,'-'
      je @@notoption
      cmp al,'/'
      je @@notoption
      mov [option],al
      jmp short @@paradone
   @@notoption:
   loop @@paramloop
   @@paradone:

   cmp [option],0
   je @@optionok
   cmp [option],'R'
   je @@optionok
   cmp [option],'r'
   je @@optionlower

   mov ah,9
   mov dx,offset syntaxerror
   int 21h

   jmp @@error

@@optionlower:
   sub [option],'a'-'A'

@@optionok:
   ; check if a copy is already resident
   mov ax,'SI'
   mov bx,'LE'
   int 2Fh
   cmp ax,'OK'
   je @@resident
   jmp @@install

@@resident:
   ; a copy is resident
   ; es now contains PSP segment of installed copy

   ; did they want us to install ?
   cmp [option],0
   jne @@notinstall
      ; show error
      mov ah,9
      mov dx,offset already_resident
      int 21h
      jmp @@error
   @@notinstall:

@@remove:
   push ds

   cli

   call restoretimer

   ; restore interrupt vectors
   mov ax,2508h
   lds dx,es:[oldint08]
   int 21h
   mov ax,252Fh
   lds dx,es:[oldint2F]
   int 21h
   pop ds

   sti

   push es
   ; release environment memory
   mov ah,49h
   mov es,es:[2Ch]
   int 21h
   pop es
   ; release program memory
   mov ah,49h
   int 21h

   ; show 'removed' message
   mov ah,9
   mov dx,offset removemessage
   int 21h

@@exit:
   ; terminate
   mov ax,4C00h
   int 21h

@@error:
   ; terminate with error code
   mov ax,4C01h
   int 21h

@@install:

   ; we can't remove
   cmp [option],'R'
   jne @@nonotreserror
      mov ah,9
      mov dx,offset not_resident
      int 21h
      jmp @@error
   @@nonotreserror:

   ; store the old interrupt vectors
   mov ax,3508h
   int 21h
   mov [oldofs08],bx
   mov [oldseg08],es
   mov ax,352Fh
   int 21h
   mov [oldofs2F],bx
   mov [oldseg2F],es

   cli

   ; install the new ones
   mov ax,2508h
   mov dx,offset newint08
   int 21h
   mov ax,252Fh
   mov dx,offset newint2F
   int 21h

   ; set timer
   mov bx,FREQUENCY
   call settimer

   sti

   ; show 'installed' message
   mov ah,9
   mov dx,offset installmessage
   int 21h

   ; terminate and stay resident
   mov ax,3100h
   mov dx,offset @res_end
   add dx,15
   mov cl,4
   shr dx,cl
   int 21h

; set new timer frequenty in BX (interrupts must be clear)
settimer proc
   mov al,00110110b        ; channel 0+L/M+mode 3+Bi
   out 43h,al
   mov dx,12h
   mov ax,34DCh
   div bx
   out 40h,al
   mov al,ah
   out 40h,al
   ret
endp

; restore timer (interrupts must be clear)
restoretimer proc
   mov al,00110110b        ; channel 0+L/M+mode 3+Bi
   out 43h,al
   xor al,al               ; revert to 18.2 Hz
   out 40h,al
   out 40h,al
   ret
endp

program ends

end @start
