                                                                      COMMENT ~
SOUND.ASM -- Sound Generation Routines

   From `BLUEBOOK of ASSEMBLY ROUTINES for the IBM PC & XT'
         by Christopher L. Morgan
         Copyright (C) 1984 by The Waite Group, Inc.

  SOUND.ASM contains a collection of assembly language routines for producing
    sound in MS/PCDOS 8088 assembly language, using MASM. These routines are
    FAR PROCEDURES.

   Contents:
   ---------
   DELAY        --  Delay for a specified time interval
   FREQ         --  Convert from frequency to period
   GLISSNDO     --  Make a glissando (sliding tone)
   LINSCALE     --  Provide linear scaling
   PITCH        --  Convert from pitch number
   PLAY         --  Play music from a table
   TONE         --  Make a tone
   TONE_INIT    --  Initialize speaker timer
   TONE_OFF     --  Turn off tone
   TONE_ON      --  Turn on tone
   TONE_SET     --  Set the tone on the speaker

      >>>>> See SOUND.DOC for complete descriptions of these routines <<<<<

 _____________________________ SOUND ROUTINES_________________________________
  It is best to include this data in the source code calling these routines,
    and then commenting out this next section.                                ~
DATAS   SEGMENT PUBLIC
NOTES   DW      4186    ;C
        DW      4435    ;C#/D-
        DW      4699    ;D
        DW      4978    ;D#/E-
        DW      5274    ;E
        DW      5588    ;F
        DW      5920    ;F#/G-
        DW      6272    ;G
        DW      6645    ;G#/A-
        DW      7040    ;A
        DW      7459    ;A#/B-
        DW      7902    ;B
WHOLE   DW      0
F_START DW      0
F_END   DW      0
DATAS   ENDS
;------------------------------------------------------------------------------
CODES   SEGMENT
PUBLIC  DELAY,FREQ,GLISSANDO,LINSCALE,PITCH,PLAY
PUBLIC  TONE,TONE_INIT,TONE_OFF,TONE_ON,TONE_SET
        ASSUME   CS:CODES,DS:DATAS
;------------------------------------------------------------------------------
;Routine to set tone
;
TONE_INIT       PROC    FAR
        PUSH    AX                              ;Save register
;
;Define control bit field parameters for the timer chip
SC      =       2                               ;Use counter 2
RL      =       3                               ;Mode load period 1 byte/time
MODE    =       3                               ;Square wave generator
BCD     =       0                               ;Not BCD, use binary values
;
;Form control word
CNWORD  =       SC * 40H + RL * 10H + MODE * 2 + BCD
;
;Send control word to 8253 timer chip
        MOV     AL,CNWORD                       ;Select the above control word
        OUT     43H,AL                          ;Send it to the control port
        POP     AX                              ;Restore register
        RET
TONE_INIT       ENDP
;------------------------------------------------------------------------------
;Routine to select tone
;
TONE_SET        PROC    FAR
        PUSH    AX                              ;Save register
;
;Load the time period into the timer
        MOV     AL,CL                           ;Lower byte
        OUT     42H,AL                          ;Out to timer
        MOV     AL,CH                           ;Upper byte
        OUT     42H,AL                          ;Out to timer
        POP     AX                              ;Restore register
        RET
TONE_SET        ENDP
;------------------------------------------------------------------------------
;Routine to turn on tone
;
TONE_ON PROC    FAR
        PUSH    AX                              ;Save register
;
;Turn speaker and timer on
        IN      AL,61H                          ;Get contents of system port B
        OR      AL,3                            ;Turn speaker and timer on
        OUT     61H,AL                          ;Send out new values to port B
        POP     AX                              ;Restore register
        RET
TONE_ON ENDP
;------------------------------------------------------------------------------
;Routine to turn Tone off
;
TONE_OFF        PROC    FAR
        PUSH    AX                              ;Save register
;
;Turn off timer 2 and speaker
        IN      AL,61H                          ;Get port B again
        AND     AL,11111100B                    ;Turn off timer & speaker
        OUT     61H,AL                          ;Now do it
        POP     AX                              ;Restore register
        RET
TONE_OFF        ENDP
;------------------------------------------------------------------------------
;Routine to delay a specified number of milliseconds
;
DELAY   PROC    FAR
        PUSH    CX                              ;Save register
DELAY1:
        PUSH    CX                              ;Save counter
        MOV     CX,260                          ;Timing constant
DELAY2:
        LOOP    DELAY2                          ;Small loop
        POP     CX                              ;Restore counter
        LOOP    DELAY1                          ;Loop to count milliseconds
        POP     CX                              ;Restore register
        RET
DELAY   ENDP
;------------------------------------------------------------------------------
;Routine to convert from frequency to period
;
FREQ    PROC    FAR
        PUSH    DX                              ;Save registers
        PUSH    AX
        MOV     DX,12H                          ;Upper part of numerator
        MOV     AX,34DEH                        ;Lower part of numerator
        DIV     CX                              ;Divide by frequency
        MOV     CX,AX                           ;The quotient is the output
        POP     AX                              ;Restore registers
        POP     DX
        RET
FREQ    ENDP
;------------------------------------------------------------------------------
;Routine to make a tone
;
TONE    PROC    FAR
        PUSH    DX                              ;Save registers
        PUSH    CX
        PUSH    AX
;
;Compute the frequency and set up the tone
        CALL    FREQ                            ;Convert the frequency
        CALL    TONE_SET                        ;Set up the tone
;
;Turn on the tone
        CALL    TONE_ON                         ;Turn it on
;
;Wait for proper delay
        MOV     CX,DX                           ;Get delay length
        CALL    DELAY
;
;Turn off the tone
        CALL    TONE_OFF                        ;Turn it off
        POP     AX                              ;Restore registers
        POP     CX
        POP     DX
        RET
TONE    ENDP
;------------------------------------------------------------------------------
;Routine to scale linearly
;
LINSCALE        PROC    FAR
        PUSH    DX                              ;Save registers
        PUSH    AX
;
;Compute width
        MOV     AX,F_END                        ;Get F_END
        SUB     AX,F_START                      ;Subtract F_START
;
;Multiply width by input parameter
        MUL     CX                              ;Multiply
        MOV     CX,DX                           ;Move top part of quotient
;                                               ; into CX
;Add lower limit
        ADD     CX,F_START                      ;Add F_START
        POP     AX                              ;Restore registers
        POP     DX
        RET
LINSCALE        ENDP
;------------------------------------------------------------------------------
;Routine to determine pitch
;
PITCH   PROC    FAR
        PUSH    CX                              ;Save registers
        PUSH    BX
        PUSH    AX
        MOV     AH,0                            ;Extend pitch no. to 16 bits
        MOV     CL,12                           ;Divisor of 12
        DIV     CL                              ;Divide
        MOV     DL,AL                           ;Quotient determines the octave
        MOV     AL,AH                           ;Remainder is the pitch within
        CBW                                     ; 16-bit needed for look up
        SAL     AX,1                            ; 2 bytes/item
        MOV     BX,AX                           ; into BX
        MOV     CX,NOTES[BX]                    ;Look it up
        CALL    FREQ                            ;Convert the frequency
        XCHG    CX,DX                           ;Octave in CL, period in DX
        NEG     CL                              ;8 - octave = shift count
        ADD     CL,8
        SAL     DX,CL
        POP     AX                              ;Restore registers
        POP     BX
        POP     CX
        RET
PITCH   ENDP
;------------------------------------------------------------------------------
;Routine to make glissando
;
GLISSANDO       PROC    FAR
        PUSH    SI                              ;Save registers
        PUSH    DX
        PUSH    CX
        PUSH    BX
        PUSH    AX
        MOV     F_START,BX                      ;FROM limit of frequencies
        MOV     F_END,CX                        ;  TO limit of frequencies
        CALL    TONE_ON                         ;Turn on tone
;
;Set up the loop parameters
        MOV     SI,1                            ;Increment for loop
        CMP     BX,CX                           ;Up or down?
        JLE     GLISS1                          ;Skip if up
        NEG     SI                              ;Decrement freq in the loop
GLISS1:
        MOV     CX,BX                           ;Get the frequency
        CALL    FREQ                            ;Convert to clock cycles
        CALL    TONE_SET                        ;Set the tone
        MOV     CX,DX                           ;Delay parameter > slows slide
GLISS2:
        LOOP    GLISS2
        CMP     BX,F_END                        ;Check if done
        JE      GLISS3                          ;If so, go
        ADD     BX,SI                           ;Else update the frequency
        JMP     GLISS1
;
;Turn off the tone
GLISS3:
        CALL    TONE_OFF                        ;Turn it off
        POP     AX                              ;Restore registers
        POP     BX
        POP     CX
        POP     DX
        POP     SI
        RET
GLISSANDO       ENDP
;------------------------------------------------------------------------------
;Routine to play music
;
PLAY    PROC    FAR
        PUSH    DS                              ;Save registers
        PUSH    SI
        PUSH    DX
        PUSH    CX
        PUSH    BX
        PUSH    AX
;
;Command pointer is in SI
        MOV     WHOLE,2000                      ;Whole note = 2000 milliseconds
        CLD                                     ;Forward direction
;
;Main loop starts here
CHEK_END:                                       ;Get command code and go
                                                ; through the cases
        LODSB                                   ;Get the byte
;
;End command
        CMP     AL,'X'                          ;Is it End command?
        JNE     CHEK_TEMPO
        JMP     PLAY_XIT
;
;Tempo command
CHEK_TEMPO:
        CMP     AL,'T'                          ;Is it Tempo command?
        JNE     CHEK_NOTE
        LODSB                                   ;Get the tempo
        MOV     CL,AL                           ;Set in CX
        MOV     CH,0
        MOV     AX,60000                        ;No. of milliseconds/minute
        MOV     DX,0                            ;Clear upper part
        DIV     CX                              ;Divide into time
        MOV     WHOLE,AX                        ;No. of milliseconds/whole note
        JMP     CHEK_END                        ;Back for more
;
;Note command
CHEK_NOTE:
        CMP     AL,'N'                          ;Is it Note command?
        JNE     CHEK_REST
        LODSB                                   ;Get the pitch
        CALL    PITCH                           ;Convert
        MOV     CX,DX                           ; and move result into CX
        CALL    TONE_SET                        ;Set the frequency
        CALL    TONE_ON                         ;Turn on the tone
        MOV     CX,WHOLE                        ;No. of milliseconds/whole note
        LODSB                                   ;Get the duration
        MOV     AH,AL                           ;Set up duration as multiplier
        MOV     AL,0
        SAL     CX,1                            ;Scale factor 1
        MUL     CX                              ;Multiply
        MOV     CX,DX                           ;Total count for the note
        LODSB                                   ;Get style
        MOV     AH,AL                           ;Set up style as multiplier
        MOV     AL,0
        MUL     CX                              ;Multiply by style
        MOV     F_START,DX                      ;Store count for note
        SUB     CX,DX                           ;Count for rest
        MOV     F_END,CX                        ;Store count for rest
        MOV     CX,F_START                      ;Audible part of note
        CALL    DELAY                           ;Delay
        CALL    TONE_OFF                        ;Turn off the tone
        MOV     CX,F_END                        ;Inaudible part of tone
        CALL    DELAY                           ;Delay
        JMP     CHEK_END                        ;Back for more
;
;Rest command
CHEK_REST:
        CMP     AL,'R'                          ;Is it Rest command?
        JNE     PLAY_XIT
        MOV     CX,WHOLE                        ;No. of milliseconds/whole note
        LODSB                                   ;Get the duration
        MOV     AH,AL                           ;Set up duration as multiplier
        MOV     AL,0
        SAL     CX,1                            ;Scale factor of 1
        MUL     CX                              ;Multiply
        MOV     CX,DX                           ;Total count
        CALL    DELAY                           ;Delay
        JMP     CHEK_END                        ;Back for more
;
;Anything else end it
PLAY_XIT:
        POP     AX                              ;Restore registers
        POP     BX
        POP     CX
        POP     DX
        POP     SI
        POP     DS
        RET
PLAY    ENDP
;-----------------------------------------------------------------------------
CODES   ENDS
;
        END
;_____________________________________________________________________________
;>>>>>Physical EOF SOUND.ASM<<<<<
