data segment
c equ 523                       ;tone frequencies
d equ 587
e equ 659
f equ 698
g equ 784
a equ 880
h equ 988

Song:     dw c,250, d,250, e,250, f,250, g,500, g,500
          dw a,250, a,250, a,250, a,250, g,500
          dw a,250, a,250, a,250, a,250, g,500
          dw f,250, f,250, f,250, f,250, e,500, e,500
          dw d,250, d,250, d,250, d,250, c,500
          dw 0                  ;always terminate with 0

oldInt     dd 0                 ;pointer to old handler
counter   dw 0                  ;counter, decremented once per ms
data ends

code segment
assume cs:code,ds:data

handler proc far                ;new IRQ 0 handler
  pushf
  call dword ptr oldint         ;call old handler
  mov ax,data                   ;enable data segment access
  mov ds,ax
  dec word ptr counter          ;decrement counter
  iret
handler endp

prepare proc near               ;prepares timer and speaker
  mov dx,61h                    ;load control port
  in al,dx
  or al,3                       ;set lower bits (enable speaker)
  out dx,al

  mov al,36h                    ;write access timer 0
  mov cx,04a9h                  ;interrupt distance 1 ms
  out 43h,al                    ;send command
  mov al,cl
  out 40h,al                    ;send timer value
  mov al,ch
  out 40h,al

  mov ax,3508h                  ;read old interrupt vector
  int 21h
  mov word ptr oldint,bx        ;store vector
  mov word ptr oldint+2,es
  push ds
  mov ax,cs                     ;vector to handler in ds:dx
  mov ds,ax
  lea dx,handler
  mov ax,2508h                  ;and set new vector
  int 21h
  pop ds
  ret
prepare endp

close proc near                 ;sets back timer and speaker
  push ds
  lds dx,oldint                 ;restore old vector
  mov ax,2508h
  int 21h

  mov al,36h                    ;reset timer
  out 43h,al
  xor al,al
  out 40h,al                    ;to 18.2 interrupts per second
  out 40h,al

  mov dx,61h                    ;speaker off
  in al,dx
  and al,not 3                  ;(clear speaker enable)
  out dx,al
  pop ds
  ret
close endp

delay proc near                 ;waits (time in ms in ax)
  mov counter,ax                ;load counter
wait1:
  cmp counter,0                 ;wait until interrupt
  jne wait1                     ;has counted counter to 0
  ret
delay endp

sound proc near
  mov bx,ax                     ;frequency to bx
  mov al,0b6h                   ;program timer 2 to rectangle
  out 43h,al
  mov dx,0012h                  ;1.193 MHz input frequency
  mov ax,34ddh
  div bx                        ;calculate timer value
  out 42h,al                    ;low byte to timer 2
  mov al,ah
  out 42h,al                    ;high byte to timer 2
  ret
sound endp

start proc
  mov ax,data                   ;enable access to data segment
  mov ds,ax

  call prepare                  ;initialize timer and speaker

  lea si,song                   ;pointer to frequencies

continue:
  lodsw                         ;get frequency
  or ax,ax
  je finished                   ;close byte found ?
  call sound                    ;output sound
  lodsw                         ;load duration
  call delay                    ;and wait
  jmp continue

finished:
  call close                    ;reset timer and interrupts
  mov ah,4ch                    ;end program
  int 21h
start  endp

code ends
end start
