$TITLE(File MUSIC.ASM - 87C750 music box - M. Covington 1994)
$MOD750
$DEBUG

; Music-box program for the 87C750 with 3.58-MHz crystal.
; (4-MHz crystal plays same tune faster and in a higher key.)

; Michael A. Covington    mcovingt@ai.uga.edu
; Artificial Intelligence Center
; The University of Georgia
; Athens, Georgia 30602-7415

;                            +--------+
;                330 ohms     \ 8-ohm /
;   P0.0  ------/\/\/\/--------|spkr |------ +5V
;                              +-----+

; Because of the clock interrupts 8 times per second, there is an
; 8-Hz clicking superimposed on the music.  Because it is in time
; with the musical rhythm, it is not obtrusive.


RTCLOCK DATA    3FH     ; Data location 3FH (top byte of data memory)
                        ; will be used as a real-time clock.

        ORG     0
        LJMP    MAIN    ; long jump to main program

; Timer interrupt service routine used to keep track of
; length of each note.  Takes 3 cpu cycles every 1/8 second.

        ORG     0BH
ISR:    DEC     RTCLOCK
        RETI

; Subprograms

;----------------------------------------------------------------
MED_WAIT:
        ; Delays A+60 CPU cycles, including the ACALL that calls it.
        ; A ranges from 3 to 255 inclusive.  For timing audio frequencies.

        PUSH   ACC       ; yes, push it twice
        PUSH   ACC
        ANL    A,#1      ; isolate bottom bit of A
        JZ     MW1       ; branch depending on whether A is even

        POP    ACC       ;  2 cycles   \
        DEC    A         ;  1 cycle     |  5 cycles if A was odd
        SJMP   MW2       ;  2 cycles   /

MW1:    POP    ACC       ;  2 cycles   \   4 cycles if A was even
        SJMP   MW2       ;  2 cycles   /

MW2:    RR     A         ;  rotate A 1 bit right thereby dividing by 2
MW3:    DJNZ   ACC,MW3   ;  2*(A/2) cycles

        MOV    A,#20
MW4:    DJNZ   ACC,MW4   ;  40 more cycles

        POP    ACC
        RET


;----------------------------------------------------------------
SOUND:  ; Produces audio on P0.0, preceded by a 5-msec silence.
        ; Registers:
        ;   A     = total duration in eighths of a second, including
        ;             the initial silence;
        ;   R0    = 0 if you want silence, or
        ;            (duration of low half-cycle in CPU cycles) - 65
        ;   R1    = (duration of high half-cycle in CPU cycles) - 65
        ; R0, R1 range from 3 to 255 inclusive.
        ; Uses RTCLOCK data area and timer interrupt service
        ; routine elsewhere in this program.

        PUSH  ACC

        ; Set up RTCLOCK to count down from A at 1/8-sec intervals.
        MOV   RTH,#6EH
        MOV   RTL,#5AH    ; Count from 6E5A to 10000H
        MOV   TH,RTH
        MOV   TL,RTL      ; Preload timer for first cycle
        MOV   RTCLOCK,A   ; Preload RTCLOCK, will count down to 0
        SETB  ET0         ; Enable timer 0 interrupt
        SETB  EA          ; Enable interrupts in general
        SETB  TR          ; Start timer running

        ; Moment of silence to separate adjacent musical notes
        MOV   A,#238
        ACALL MED_WAIT
        ACALL MED_WAIT
        ACALL MED_WAIT
        ACALL MED_WAIT
        ACALL MED_WAIT

        ; If R0=0, make silence, not sound
        MOV   A,R0
        JNZ   SN1
SN0:    MOV   A,RTCLOCK   ; keep checking timer
        JZ    SN2
        SJMP  SN0

        ; Main loop. Check timer after every half cycle of audio.
SN1:    CLR   P0.0        ; (1 cycle)    ; low half cycle
        MOV   A,R0        ; (1 cycle)
        ACALL MED_WAIT    ; (R0+60 cycles)
        MOV   A,RTCLOCK   ; (1 cycle)    ; bail out if time is up
        JZ    SN2         ; (2 cycles)

        SETB  P0.0        ; (1 cycle)    ; high half cycle
        MOV   A,R1        ; (1 cycle)
        ACALL MED_WAIT    ; (R1+60 cycles)
        MOV   A,RTCLOCK   ; (1 cycle)    ; repeat if time is not up
        JNZ   SN1         ; (2 cycles)

SN2:    SETB  P0.0        ; Final state of output bit is 1
        CLR   EA          ; Turn off interrupts
        CLR   ET0         ; Clear timer interrupt bit
        CLR   TR          ; Stop timer
        POP   ACC
        RET               ; All done

;----------------------------------------------------------------
TUNE:
     ; Plays a tune by reading sets of (R0,R1,A) values beginning
     ; at @DPTR, in *code* memory (not data memory), until a set
     ; with A=0 is found.  No limit on tune length.
     ; Calling sequence:   MOV DPH,#HIGH(MYDATA)
     ;                     MOV DPL,#LOW(MYDATA)
     ;                     ACALL TUNE
     ; Uses regs A, R0, R1, DPTR.  Does not preserve any of them.

        CLR   A
        MOVC  A,@A+DPTR
        MOV   R0,A        ; got R0
        INC   DPTR

        CLR   A
        MOVC  A,@A+DPTR
        MOV   R1,A        ; got R1
        INC   DPTR

        CLR   A
        MOVC  A,@A+DPTR   ; got A
        INC DPTR

        JZ    TN1         ; exit if A=0
        ACALL SOUND
        SJMP  TUNE

TN1:    RET

;----------------------------------------------------------------
MAIN:   MOV   B,3     ; loop counter
        MOV   DPH,#HIGH(MYTUNE)
        MOV   DPL,#LOW(MYTUNE)
        ACALL TUNE
        DJNZ  B,MAIN  ; play it again, Sam...

        ORL   PCON,#2   ; shut down

MYTUNE: ; Irish folk song "Morning Has Broken"
        ; (public domain)

        DB 225,225,4  ; C, qtr note
        DB 166,166,4  ; E, qtr note
        DB 130,130,4  ; G, qtr note

        DB  82, 83,12 ; C', dotted half note
        DB  67, 67,12 ; D', dotted half note
        DB  91, 91,4  ; B, qtr note
        DB 109,110,4  ; A, qtr note
        DB 130,130,4  ; G, qtr note

        DB 109,110,12 ; A, dotted half note
        DB 130,130,12 ; G, dotted half note
        DB 225,225,4  ; C, qtr note
        DB 194,194,4  ; D, qtr note
        DB 166,166,4  ; E, qtr note

        DB 130,130,12  ; G, dotted half note
        DB 109,110,12  ; A, dotted half note
        DB 130,130,4  ; G, qtr note
        DB 166,166,4  ; E, qtr note
        DB 225,225,4  ; C, qtr note

        DB 194,194,16 ; D, whole note
        DB   0,  0,4  ; half rest
        DB 130,130,4  ; G, qtr note
        DB 166,166,4  ; E, qtr note
        DB 130,130,4  ; G, qtr note
     
        DB  82, 83,12 ; C', dotted half note
        DB 109,110,12 ; A, dotted half note
        DB 130,130,4  ; G, qtr note
        DB 166,166,4  ; E, qtr note
        DB 225,225,4  ; C, qtr note

        DB 225,225,12 ; C, dotted half note
        DB 194,194,12 ; D, dotted half note
        DB 166,166,4  ; E, qtr note
        DB 194,194,4  ; D, qtr note
        DB 166,166,4  ; E, qtr note

        DB 130,130,12 ; G, dotted half note
        DB 109,110,12 ; A, dotted half note
        DB 194,194,4  ; D, qtr note
        DB 166,166,4  ; E, qtr note
        DB 194,194,4  ; D, qtr note

        DB 225,225,16 ; C, whole note
        DB   0,  0,8  ; half rest (relevant only if repeating tune)
        DB   0,  0,0  ; end of tune

        END

