;------------------------------------------------------------------------
; PHRASER.ASM
;   This is a CP/M program that reads a text file of phrase data for
;   the SpeakJet speech synthesizer and outputs it to the 2nd port on
;   an Altair 88-2SIO serial board.
;
;   The data file consists of a sequence of decimal values (0-255)
;   separated by white space, commas, etc. An output pause can be
;   specified by inserting the letter 'D' for Delay followed by a
;   value 0-255 in 0.1s increments (e.g., 10 = 1 second).
;
;   Version Information:
;
;   1.0   01/01/2022	M. Douglas, Original
;   1.1   01/11/2022	M. Douglas, add assembly option for selecting
;			the 2nd port on an Altair 88-SIO or the 2nd
;			port on a JAIR CPU board.
;   
;------------------------------------------------------------------------

; Select ONE serial port type by setting its equate to 1

ALTAIR	equ	1		;1=2nd port on 88-2SIO
JAIR	equ	0		;1=2nd port on JAIR CPU board

TENMS	equ	8333		;for 2MHz clock
;TENMS	equ	16667		;for 4MHz clock
 
; 2nd port on 88-2SIO

 if ALTAIR
SIOCSR	equ	12h		;control/status register
SIODR	equ	13h		;data register
TDRE	equ	02h		;transmit register empty
 endif

; 2nd port on JAIR CPU board

 if JAIR
SIOCSR	equ	2Dh		;control/status register
SIODR	equ	28h		;data register
TDRE	equ	20h		;transmit register empty
 endif
 
; CP/M equates

BDOS	equ	5		;BDOS entry point
PRINT	equ	9		;BDOS write string to console
OPENF	equ	15		;BDOS open file
READF	equ	20		;BDOS read file
SETDMA	equ	26		;BDOS set DMA address

; File control block

FCB	equ	5Ch		;location of default CP/M FCB
FCBFN	equ	FCB+1		;location of file name
FCBEXT	equ	FCB+9		;location of file extension
FCBCR	equ	FCB+32		;current record

; Misc Equates

CR	equ	0Dh		;ASCII for carriage return
LF	equ	0Ah		;ASCII for line feed
CTRLZ	equ	1Ah		;ASCII for control-z (EOF character)

;------------------------------------------------------------------------
; Initialization
;------------------------------------------------------------------------
	org	100h

	lxi	h,0		;save stack pointer by HL=SP+0
	dad	sp		;HL=current stack pointer from CP/M
	shld	saveStk		;save for restore later
	lxi	sp,stack	;switch to local stack

	mvi	a,03h		;reset serial port
	out	SIOCSR
	mvi	a,15h		;8N1
	out	SIOCSR

	lxi	d,mHelp		;de->program help message
	lda	FCBFN		;look at 1st character of 1st file name
	cpi	' '		;anything there?
	jz	exitMsg		;no, exit with help message

; Insert .TXT extension if not supplied

	lxi	h,FCBEXT	;HL->file extension in FCB
	mvi	a,' '		;compare to space to see if ext present
	cmp	m
	jnz	haveExt		;yes, extension is present

	mvi	m,'T'		;else, insert 'TXT' as extension
	inx	h
	mvi	m,'X'
	inx	h
	mvi	m,'T'

haveExt	xra	a		;zero current record in FCB
	sta	FCBCR

	lxi	d,FCB		;DE->file control block
	mvi	c,OPENF		;C=open file command
	call	BDOS		;open the image file
	
	lxi	d,mNoFile	;DE->file not found message
	inr	a		;test for FFh error
	jz	exitMsg		;file not found, exit
				;fall into main loop

;------------------------------------------------------------------------
; Main loop - Get a decimal number in ASCII from the file and output it
;   as a binary byte through the 2nd port on an 88-2SIO.
;------------------------------------------------------------------------
	lxi	d,mSend		;display "Sending..." message
	mvi	c,PRINT		;display the message passed in de
	call	BDOS

main	call	getNum		;get decimal value in ASCII from the file

	lda	dlyFlag		;if delay flag is true, this number
	ora	a		;  is a delay value in .01s
	jz	xmtByte		;else, normal byte
	
	call	delay		;delay the time in b
	jmp	main
	
xmtByte	in	SIOCSR		;wait for xmit buffer empty
	ani	TDRE
	jz	xmtByte

	mov	a,b		;transmit byte from b
	out	SIODR
	jmp	main

;------------------------------------------------------------------------
; getNum - Get ASCII decimal number from the file and return it as a
;   binary value in b
;------------------------------------------------------------------------
getNum	call	skpWht		;skip whitespace
	mov	b,a		;b=value so far
	
nextDig	call	getByte		;get another digit
	rnc			;number complete
	
	mov	a,b		;compute 10*current value
	add	a		;current *2
	add	a		;*4
	add	a		;*8
	add	b		;*9
	add	b		;*10
	
	add	c		;add in new digit
	sui	'0'		;get rid of ASCII from c
	mov	b,a		;b=new result
	jmp	nextDig
	
;------------------------------------------------------------------------
; skpWht - Skip white space until a valid ASCII digit is found. Look for
;   EOF and delay flag (D/d) while scanning.
;------------------------------------------------------------------------
skpWht	call	getByte		;get byte from file in c
	rc			;valid decimal digit found	

	mov	a,c		;a=character read		
	cpi	CTRLZ		;end of file character?
	jz	exitPgm		;yes, all done
	
	ori	20h		;caps to lower case
	cpi	'd'		;delay flag?
	jnz	skpWht		;no
	
	sta	dlyFlag		;else, set the dlyFlag true
	jmp	skpWht		
	
;------------------------------------------------------------------------
; getByte - Get the next byte from the file. Read another sector as
;   required. 
; On Exit:
;   C = byte read
;   A = byte read minus '0'
;   Carry set if A contains valid decimal digit (binary 0-9)
;------------------------------------------------------------------------
getByte	lxi	h,recLen	;decrement record length
	dcr	m
	cm	readRec		;new record needed
	
	ldax	d		;de->next byte in record
	inx	d
	mov	c,a		;return byte as read in c

	sui	'0'		;convert '0'-'9' to 0-9
	cpi	10		;carry set if 0-9
	ret
			
;------------------------------------------------------------------------
; readRec - Read the next record from the file. Set eofFlag if file
;   EOF reached.
;------------------------------------------------------------------------
readRec	lxi	d,recBuf	;set buffer address for read			
	mvi	c,SETDMA
	call	BDOS

	lxi	d,FCB		;de->file control block
	mvi	c,READF		;c=read file sequential
	call	BDOS		;read the file
	ora	a		;end of file?
	jz	notEof		;no
	
	lxi	h,CTRLZ+256*CTRLZ   ;hl=1A1A
	shld	recBuf		;1st two bytes are EOF chars

notEof	mvi	a,128		;bytes remaining in record
	sta	recLen
	lxi	d,recBuf	;de->record buffer
	ret

;------------------------------------------------------------------------
; delay - Delay .01s intervals as specified in b
;------------------------------------------------------------------------
delay	lxi	h,TENMS		;.01s loop count, 24 cycles/loop

dlyLoop	dcx	h		;(5) loop until hl=0
	mov	a,h		;(5)
	ora	l		;(4)
	jnz	dlyLoop		;(10)
	
	dcr	b		;decrement the .01s count
	jnz	delay

	sta	dlyFlag		;clear the delay flag
	ret

;-----------------------------------------------------------------------------
; exitMsg - Display message pointed to by de and exit to CP/M
;-----------------------------------------------------------------------------
exitMsg	mvi	c,PRINT		;display the message passed in de
	call	BDOS

exitPgm	lhld	saveStk		;restore stack pointer
	sphl
	ret			;return to the CCP 

;-----------------------------------------------------------------------------
; Messages
;-----------------------------------------------------------------------------
mHelp	equ	$
 db CR,LF
 db 'PHRASER v1.1 (M. Douglas)',CR,LF
 db '  Translate and send a phrase data file through the second port',CR,LF

if ALTAIR
 db '  on an 88-2SIO serial board (I/O address 12h/13h)',CR,LF
endif

if JAIR
 db '  on a JAIR CPU board (I/O address 28h/2Dh)',CR,LF
endif

 db LF
 db 'Usage: PHRASER filename[.ext]',CR,LF
 db '  Where filename contains the phrase data as a list of decimal',CR,LF
 db '  values in ASCII. If extension not specified, TXT is assumed.',CR,LF,'$'

mNoFile	equ	$
 db CR,LF,'File not found',CR,LF,'$'

mSend	equ	$
 db 'Sending speech data...$'

;-----------------------------------------------------------------------------
; Data Area
;-----------------------------------------------------------------------------
eofFlag	db	0		;non-zero when EOF reached
dlyFlag	db	0		;non-zero when 'D' or 'd' encountered
recLen	db	0		;bytes remaining in record
saveStk	ds	2		;saved stack pointer

recBuf	ds	128		;record buffer
	ds	64		;stack space
stack	equ	$

	end
