I'm in an introductory assembly class, and for the final project we have to make a program that plays Happy Birthday.
The problem is that the program plays the song half way through (to the 13th note), then after that stops. I've tried everything to try and fix it and I have no idea why it shouldn't be working. I've tried changing the code around, changing the way the loop works, and changing which registers hold different values, but nothing seems to be working. I looked through debug and I can't see anything out of the ordinary. This is the code for the program:
;data segment
dseg segment para 'data'
;hex value for each note to send to port 42
NOTES DW 11CAh,11CAh,0FDAh,11CAh,0D5Ah,0E1Fh,11CAh,11CAh
DW 0FDAh,11CAh,0BE3h,0D5Ah,11CAh,11CAh,08E9h,0A97h
DW 0D5Ah,0E1Fh,0FDAh,0A00h,0A00h,0A97h,0D5Ah,0BE3h,0D5Ah,"$"
;duration for each note
DUR DB 1d,1d,2d,2d,2d,4d,1d,1d,2d,2d,2d
DB 4d,1d,1d,2d,2d,2d,2d,6d,1d,1d,2d,2d,2d,4d
dseg ends
;----------------------------------------------------------------------
;code segment
cseg segment para 'code'
main proc far ;this is the program entry point
assume cs:cseg, ds:dseg, ss:sseg
mov ax,dseg ;load the data segment value
mov ds,ax ;assign value to ds
MOV AL, 0B6h
OUT 43h, AL ;output whats in AL to port 43h
MOV SI, OFFSET NOTES ;moves the address of the first note into SI
MOV DI, OFFSET DUR ;moves the address of the first duration into DI
SUB BX, BX ;clears BX
SUB DX, DX ;clears DX
SUB CX, CX ;cleasr CX
PLAY: NOP
MOV DX, [SI] ;moves the frequency and divider for the note into DX
CMP DX, "$"
JE EXIT ;if at end of notes jump to exit
MOV BL, [DI] ;moves the number of times delay is called into BL
CALL TONE ;plays the note for the duration
INC SI ;move to next note
INC DI ;moves to next duration
JMP PLAY ;loop back to PLAY if there are still notes
EXIT: NOP
mov ah,4Ch ;set up interupt
int 21h ;Interupt to return to DOS
main endp
TONE PROC
SUB AX, AX ;clears ax
MOV AL, DL ;sets up the frequency for the tone
OUT 42h, AL ;output whats in AL to port 42h
MOV DL, 0 ;use to compare to BL
MOV AL, DH ;divider for the tone smaller value = higher pitch
OUT 42h, AL
IN AL, 61h ;input the contents of port 61h into AL
MOV AH, AL ;preserve contents of AL
OR AL, 00000011b ;uses mask to change the bits that we need
OUT 61h, AL ;outputs the changed bits back into the port to start tone
AGAIN: NOP ;loops 1/2 second delay for times needed
CALL DELAY
DEC BL
CMP BL, DL ;compares value in bl to 0
JNE AGAIN ;loops delay until bl is 0
MOV AL, AH
OUT 61h, AL ;turns off tone with preserved contents from port 61h
CALL DELAY_TWO ;adds delay between notes
RET
TONE ENDP
DELAY PROC NEAR ;half second delay
PUSH AX ;preserve value in ax in stack
MOV CX, 33156 ;delays for half second
WAITF: NOP
IN AL, 61h ;read port 61
AND AL, 10h ;gives 0001 0000 check PB4
CMP AL, AH
JE WAITF ;jump to WAITF if AL is equal to AH
MOV AH, AL ;makes both AL and AH contain same number
LOOP WAITF ;continue until CX is 0
POP AX
RET
DELAY ENDP
DELAY_TWO PROC NEAR ;small delay between notes
PUSH AX ;need value in ax for TONE so save it
MOV CX, 331
WAITO: NOP
IN AL, 61h
AND AL, 10h
CMP AL, AH
JE WAITO
MOV AH, AL
LOOP WAITO
POP AX ;get back ax value once delay is done
RET
DELAY_TWO ENDP
cseg ends
end main ;Program exit point
We're only using 16 bit assembly. There's probably other methods for a timer and making tones but we have to do it this way, with the ports used in the program. Also, I'm using DOSBox to run the .exe and it sounds a little off when it plays.
Any help would be greatly appreciated.