; adapted 
title music.asm
.model small
.stack 100h
.data
;

sixteen equ 25/2/2
eight_hlf equ 19
eight   equ 25/2
qtr     equ 25
hlf     equ qtr * 2
whl     equ hlf * 2
a_mid   equ 880
b_mid   equ 988
c_mid   equ 523
c_high  equ 1040
d_mid   equ 587
d_high  equ 1176
e_mid   equ 659
f_mid   equ 698
g_mid   equ 784
g_high  equ 1568

;  Initial notes of star wars 
;  See TUNE routine for the format of this list
star_tune DW eight,d_mid,6,0
      dw    eight,d_mid,6, 0
      dw    eight,d_mid,6,0
      DW    hlf,g_mid,6,0
      DW    hlf,d_high,6,0
      dw    eight,c_high,6,0
      dw    eight,b_mid,6,0
      dw    eight,a_mid,6,0
      DW    hlf,g_high,6,0
      DW    qtr,d_high,6,0
      dw    eight,c_high,6,0
      dw    eight,b_mid,6,0
      dw    eight,a_mid,6,0
      DW    hlf,g_high,6,0
      DW    qtr,d_high,6,0
      dw    qtr,c_high,6,0
      dw    eight,c_high,6,0
      dw    eight,b_mid,6,0
      dw    eight,c_high,6,0
      dw    qtr,a_mid,6,0
      dw    0
      
.code
main proc
      mov ax,@data
      mov ds,ax
  ;
      lea   si, star_tune      
      call  tune
  ;
	mov ax,4c00h
	int 21h
main endp

;  Routine to play tune on speaker
;
;      (SI)               Pointer to tune list
;      CALL TUNE
;
;  The tune list is a series of word pairs. The first word is the
;  duration in units of 1/100th of a second, and the second word
;  is the frequency. An entry with zero duration ends the tune list
;  and a zero frequency is a rest (period of silence).
;
TUNE      PROC
      PUSH  AX          ; save registers
      PUSH  DX
  ;  Loop through notes of the tune
  TUN1: 
      LODSW                ; load duration
      OR    AX,AX          ; test zero duration ending the list
      JZ    TUN2           ; if so, end of tune
  ;  Play next note
      XCHG  AX,DX          ; put duration in DX
      LODSW                ; load frequency
      CALL  NOTE           ; play the note
      JMP   TUN1           ; and loop back
  ;  Here at end of tune
  TUN2: 
	POP   DX             ; restore registers
      POP   AX
      RET                  ; return to caller
TUNE      ENDP

;***************************************************************************
;  Routine to play note on speaker
;
;      (AX)           Frequency in Hz (32 - 32000)
;      (DX)           Duration in units of 1/100 second
;      CALL  NOTE
;
;  Note: a frequency of zero, means rest (silence) for the indicated
;  time, allowing this routine to be used simply as a timing delay.
;
;  Definitions for timer gate control
;
;***************************************************************************
CTRL      EQU   61H           ; timer gate control port
TIMR      EQU   00000001B     ; bit to turn timer on
SPKR      EQU   00000010B     ; bit to turn speaker on
;
;  Definitions of input/output ports to access timer chip
;
TCTL      EQU   043H          ; port for timer control
TCTR      EQU   042H          ; port for timer count values
;
;  Definitions of timer control values (to send to control port)
;
TSQW      EQU   10110110B     ; timer 2, 2 bytes, sq wave, binary
LATCH     EQU   10000000B     ; latch timer 2
;
;  Define 32 bit value used to set timer frequency
;
FRHI      EQU   0012H          ; timer frequency high (1193180 / 256)
FRLO      EQU   34DCH          ; timer low (1193180 mod 256)
;
NOTE      PROC
      PUSH  AX          ; save registers
      PUSH  BX
      PUSH  CX
      PUSH  DX
      PUSH  SI
      MOV   BX,AX          ; save frequency in BX
      MOV   CX,DX          ; save duration in CX
;  We handle the rest (silence) case by using an arbitrary frequency to
;  program the clock so that the normal approach for getting the right
;  delay functions, but we will leave the speaker off in this case.
;
      MOV   SI,BX          ; copy frequency to BX
      OR    BX,BX          ; test zero frequency (rest)
      JNZ   NOT1           ; jump if not
      MOV   BX,256         ; else reset to arbitrary non-zero
;
;  Initialize timer and set desired frequency
;
NOT1: MOV   AL,TSQW          ; set timer 2 in square wave mode
      OUT   TCTL,AL
      MOV   DX,FRHI          ; set DX:AX = 1193180 decimal
      MOV   AX,FRLO          ;      = clock frequency
      DIV   BX               ; divide by desired frequency
      OUT   TCTR,AL          ; output low order of divisor
      MOV   AL,AH            ; output high order of divisor
      OUT   TCTR,AL
;
;  Turn the timer on, and also the speaker (unless frequency 0 = rest)
;
      IN    AL,CTRL          ; read current contents of control port
      OR    AL,TIMR          ; turn timer on
      OR    SI,SI            ; test zero frequency
      JZ    NOT2             ; skip if so (leave speaker off)
      OR    AL,SPKR          ; else turn speaker on as well
;
;  Compute number of clock cycles required at this frequency
;
NOT2: OUT   CTRL,AL          ; rewrite control port
      XCHG  AX,BX            ; frequency to AX
      MUL   CX               ; frequency times secs/100 to DX:AX
      MOV   CX,100           ; divide by 100 to get number of beats
      DIV   CX
      SHL   AX,1             ; times 2 because two clocks/beat
      XCHG  AX,CX            ; count of clock cycles to CX
;
;  Loop through clock cycles
;
NOT3:      CALL  RCTR          ; read initial count
;
;  Loop to wait for clock count to get reset. The count goes from the
;  value we set down to 0, and then is reset back to the set value
;
NOT4: MOV   DX,AX          ; save previous count in DX
      CALL  RCTR           ; read count again
      CMP   AX,DX          ; compare new count : old count
      JB    NOT4           ; loop if new count is lower
      LOOP  NOT3           ; else reset, count down cycles
;
;  Wait is complete, so turn off clock and return
;
      IN    AL,CTRL           ; read current contents of port
      AND   AL,0FFH-TIMR-SPKR ; reset timer/speaker control bits
; note that the above statement is an equation
      OUT   CTRL,AL           ; rewrite control port
      POP   SI                ; restore registers
      POP   DX
      POP   CX
      POP   BX
      POP   AX
      RET               ; return to caller
NOTE      ENDP
;
;  Routine to read count, returns current timer 2 count in AX
;
RCTR      PROC
      MOV   AL,LATCH         ; latch the counter
      OUT   TCTL,AL          ; latch counter
      IN    AL,TCTR          ; read lsb of count
      MOV   AH,AL
      IN    AL,TCTR          ; read msb of count
      XCHG  AH,AL            ; count is in AX
      RET                    ; return to caller
RCTR      ENDP

      end main
