;Author: Jeremiah K. Jones 10/19/01
;Title: Piano Man
;Objective:  To implement a program that will play the tones of a major
;            Scale when specified keys on the keyboard are pressed.  The
;            program will also record and playback a brief melody.
;-----------------------------------------------------------------------                                                                

dosseg
.model small
.stack 100h



.data            ;----Prepares the variables to be used----
  ;Constants
	Welcome db 'Welcome to the PIANO MAN!!!$'
	Prompt db 'Press a key (A-K on the home row) to play a note!$'
	Prompt2 db 'Press P to play back your melody!$'
	FreqTable dw 106h, 126h, 14ah, 15dh, 188h, 1b8h, 1eeh, 20bh, 20000

  
  ;Non-constant Variables
	ScanCode db (?)
	KeyPressedFlag db ?
	KeyboardISRoffset dw ?
	KeyboardISRsegment dw ?
	NoteArray dw 10000 dup (?)
	DurationArray dw 10000 dup (?)
	NoteIndex db ?
	DurationIndex db ?
	NotePlayBackIndex db ?
	DurationPlayBackIndex db ?
	FirstRunFlag db ?
.code    


;******************************************************************************
;******************************************************************************
			;----Procedure to Initialize the screen----

  PROC ScreenInit
	 MOV AH, 0                  ;Clear the screen
	 MOV AL, 2
	 INT 10H

	  
	 
			;----Displays the prompts----
	 MOV AH, 2                  ;Moves the cursor
	 MOV BH, 0
	 MOV DH, 1
	 MOV DL, 26
	 INT 10H

	 MOV DX, OFFSET Welcome
	 MOV AH, 09H
	 INT 21H          

	 MOV AH, 2                  ;Moves the cursor
	 MOV BH, 0
	 MOV DH, 2
	 MOV DL, 14
	 INT 10H

	 MOV DX, OFFSET Prompt
	 MOV AH, 09H
	 INT 21H          

	 MOV AH, 2                  ;Moves the cursor
	 MOV BH, 0
	 MOV DH, 3
	 MOV DL, 23
	 INT 10H

	 MOV DX, OFFSET Prompt2
	 MOV AH, 09H
	 INT 21H    

	 Ret                        ;Returns to main procedure
	 ENDP ScreenInit            ;Ends the ScreenInit procedure
;******************************************************************************
;******************************************************************************        



;******************************************************************************
;******************************************************************************
			;----Procedure to Play a note
			;----Send in Data through dx
   Play PROC NEAR
	push ax                 ;Save registers used
	push cx
	push es
	push bp
	pushF
	mov cx, dx              ;Sound note
	in al, 61h              ;Enable speaker
	or al, 03h
	out 61h, al
	mov ax, 34dch           ;Divide frequency in bx by freq in timer
	mov dx, 12h             ;(I.E. 1.19Mhz)
	div bx
	out 42h, al             ;Send data to speaker
	mov al, ah
	out 42h, al
	popF                    ;Restore registers used in procedure
	pop bp
	pop es
	pop cx
	pop ax

	push ax
	push es
	push bx

	;mov ax, 0A000h
	;mov es, ax

	;mov ah, 0fh
	;int 10h
	;push ax
	;mov ax, 0013h
	;int 10h

	mov bx, 0
	mov al, 4
	Again:
	mov es:[bx], al
	inc bx
	cmp bx, 320*200
	jne again

	;pop ax
	;mov ah, 0
	;int 10h

	pop bx
	pop es
	pop ax
	ret
   ENDP Play                    ;End Play Procedure
;******************************************************************************
;******************************************************************************



;******************************************************************************
;******************************************************************************
			;----Procedure to stop speaker
   TurnOff PROC NEAR
	push ax                 ;Save registers used
	push cx
	push es
	push bp
	pushF
	in al, 61h              ;Disable speaker
	Xor al, 03h
	out 61h, al
	popF                    ;Restore registers
	pop bp
	pop es
	pop cx
	pop ax
	ret
   ENDP TurnOff                 ;End TurnOff procedure
;******************************************************************************
;******************************************************************************



;******************************************************************************
;******************************************************************************
			;----Procedure to test for key press
			;----Keyboard interrupt that uses scan codes
   KeyboardISR PROC NEAR
	in al, 60h              ;Read and save keyboard scan code
	mov ScanCode, al
	mov KeyPressedFlag, 1   ;Indicate key has been pressed
	in al, 61h
	or al, 80h
	out 61h, al             ;EIO 8259 for keyboard interrupt
	mov al, 20h
	out 20h, al

	iret
   ENDP KeyboardISR
;******************************************************************************
;******************************************************************************



;******************************************************************************
;******************************************************************************
			;----Procedure to Start Timer
   PROC StartTimer
	push ax
	push bx			;Save registers
	push dx
	push es

	mov al, ScanCode
	mov bx, offset NoteArray	
	mov dx, 0000
	mov dl, NoteIndex
	
	mov di, dx
	mov [di+bx], al		;Save the note in NoteArray
	
	MOV AX, 0         	;Resets the timers for checking the polling
	MOV ES, AX
	MOV BX, 046Ch
	MOV ES:[BX], AX
	MOV BX, 046Eh
	MOV ES:[BX], AX
	
	pop es
	pop dx			;Restore registers
	pop bx
	pop ax
	
	add NoteIndex, 2	;Increment the Index for the Note array
	RET
	
   ENDP StartTimer

;******************************************************************************
;******************************************************************************


;******************************************************************************
;******************************************************************************
			;----Procedure to Stop Timer

   PROC StopTimer
	
	
	push ax			;Save ax and bx and es
	push bx
	push es
	
	MOV AX, 0      		;Check Ticks
	MOV ES, AX
	MOV BX, 046Ch
	MOV AX, ES:[BX]

	push bx			;Save the bx and dx registers
	push dx

	mov bx, offset DurationArray	;Store the timing ticks
	mov dx, 0000
	mov dl, DurationIndex
	
	mov di, dx
	mov [di+bx], ax		;Save the Time in DurationArray
	
	pop dx			;Restore dx and bx
	pop bx
	
	pop es
	pop bx			;Restore bx and ax and es
	pop ax
	
	add DurationIndex, 2	;Increment the Index for the Note array
	RET
	
   ENDP StopTimer

;******************************************************************************
;******************************************************************************


;******************************************************************************
;******************************************************************************
			;----Procedure to Play Back the Melody
			;When playing back, I will set the timers to zero, 
			; and have it play the note until the timer reads
			; what is equal to the duration for that note that 
			; was recorded.



   PROC PlayBack
	

	push ax			;Save ax, bx, es, and dx
	push bx
	push dx
	push es

	NoteLoop:
		mov dl, NotePlayBackIndex
		mov al, NoteIndex
		cmp dl, al
		je MelodyDone
	

		mov bx, offset NoteArray	
		mov dx, 0000
		mov dl, NotePlayBackIndex
	
		mov di, dx
		mov dl, [di+bx]		;Get the note

		mov bx, offset FreqTable
		add dl, dl		;Double the ScanCode
		mov di, dx
		mov ax,[di + bx]	;Choose the Frequency
		mov bx, ax
		CALL Play               ;Play the notemov bx, offset NoteArray

		MOV AX, 0         	;Resets the timers
		MOV ES, AX
		MOV BX, 046Ch
		MOV ES:[BX], AX
		MOV BX, 046Eh
		MOV ES:[BX], AX

		DurationLoop:		;Play for recorded time

			MOV AX, 0      		;Check Ticks
			MOV ES, AX
			MOV BX, 046Ch
			MOV AX, ES:[BX]
			
			mov bx, offset DurationArray	;Retrieve Timing
			mov dx, 0000
			mov dl, DurationPlayBackIndex
		
			mov di, dx
			cmp ax, [di+bx]		;Compare time to Duration Array
			jl DurationLoop	;Keep playing if less than

		CALL TurnOff			;Turn off if equal

		add NotePlayBackIndex, 2
		add DurationPlayBackIndex, 2	;Increment indexes
		jmp NoteLoop

	MelodyDone:
		mov NoteIndex, 0		;Restore variables
		mov DurationIndex, 0
		mov NotePlayBackIndex, 0
		mov DurationPlayBackIndex, 0

		pop es			;Restore registers
		pop dx
		pop bx
		pop ax
			
	RET
	
   ENDP PlayBack

;******************************************************************************
;******************************************************************************



;******************************************************************************
;Start Main* Start Main* Start Main* Start Main* Start Main* Start Main
;******************************************************************************

			;----Main Procedure----

  PROC MAIN
	MOV AX, DGROUP
	MOV DS, AX              ;Moves the data group into the DS register      

			;----Initializes the screen----
	CALL ScreenInit
	
	mov KeyPressedFlag, 0   	;Initialize the variables
	mov si, 0
	mov NoteIndex, 0
	mov DurationIndex, 0
	mov NotePlayBackIndex, 0
	mov DurationPlayBackIndex, 0
	mov FirstRunFlag, 1		;Indicates the first run of the loop

	
	ConfigInt:			;Reset the Keyboard Interrupt Vector
	mov ah, 35h
	mov al, 9
	int 21h
	mov KeyboardISRoffset, bx
	mov KeyboardISRsegment, es
	mov ah, 25h
	mov al, 9
	mov dx, offset KeyboardISR
	push ds
	push cs
	pop ds
	int 21h
	pop ds


	mov ax, 0a000h
	mov es, ax

	mov ah, 0fh
	int 10h
	push ax

	movax, 0013h		;Set mode 13 graphics
	int 10h





	BetweenMelodies:			;A place to jump between
						; playback and recording
	mov FirstRunFlag, 1			;Reset the flag
	jmp MainLoop				;skips the first rest interval

	Rest:					;Indicates that no note is played
	mov ScanCode, 8				;Plays a note too high to hear
	CALL StartTimer

			;-----Main Loop----
	MainLoop: 

		mov al, KeyPressedFlag  	;Check the Keyboard Flag
		cmp al, 0
		je MainLoop
		cmp al, 1               	;Keep looking if not set
		je FlagSet              	;"Go" if set
		jmp MainLoop            	;Keep looking if not

		FlagSet:
			cmp FirstRunFlag, 1		;Check if first time
			je FirstTime		;Do not call StopTimer
			CALL StopTimer		; if it is the first run
			FirstTime:
			mov al, ScanCode	;Store the Scancode
			or al, 7fh
			cmp al, 7fh
			je KeyPressed   	;Check if Pressed or released
			jne KeyReleased

		KeyPressed:
  			
			cmp ScanCode, 1      	;Check for esc
			je Finish1               ;Exit on esc

			cmp ScanCode, 25	;Check for "p"
			je Ppressed		;Playback on "p"
			
			cmp ScanCode, 30      	;Make sure the Key is 'A-K'
			jl WrongKey
			cmp ScanCode, 37
			jg WrongKey

			mov FirstRunFlag, 0	;Show this is not the first run
			sub ScanCode, 30      	;Minus 30 from scancode
			mov bx, offset FreqTable
			mov dx, 0000
			mov dl, ScanCode	
			add dl, dl		;Double the ScanCode
			mov di, dx
			mov ax,[di + bx]	;Choose the Frequency
			mov bx, ax
			CALL StartTimer		;Start Recording the note
			CALL Play               ;Play the note
				
			WaitForRelease:
			mov al, ScanCode
			or al, 7fh
			cmp al, 7fh
			je WaitForRelease   	;Check if Pressed or released
			jne KeyReleased


		WrongKey:

			mov KeyPressedFlag, 0
			jmp Rest

		KeyReleased:
			
			cmp ScanCode, 158      	;Make sure the Key is 'A-K'
			jl WrongKey
			cmp ScanCode, 165
			jg WrongKey

			CALL TurnOff
			mov KeyPressedFlag, 0
			CALL StopTimer
			jmp Rest

		Finish1:
			jmp Finish

		Ppressed:

			mov KeyPressedFlag, 0
			CALL PlayBack
			jmp BetweenMelodies



	Finish:         ;----End the program----
	
	pop ax		;Restore graphics mode
	mov ah, 0
	int 10h




	 mov dx, KeyboardISRoffset
	 mov ds, KeyboardISRsegment
	 mov ah, 25h
	 mov al, 9
	 int 21h


	MOV AH, 4CH
	INT 21H
  ENDP MAIN
  END MAIN
;******************************************************************************
;End Main* End Main* End Main* End Main* End Main* End Main* End Main* End Main
;******************************************************************************
