	  							      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<<<<<	
