	ttl	'QT2 - approximate time display (version 87/10/05)'

sys	macro			;Call a system routine.
	ifnd	_LVO\1
	xref	_LVO\1
	endc
	jsr	_LVO\1(a6)
	endm

	code
*
* Equates
*
lf	equ	$0A		;Line feed
cr	equ	$0D		;Carriage return
pr_MsgPort equ	$5C
pr_CLI	equ	$AC
CMD_WRITE equ	3		;Write command

*
* External references (most are taken care of by the SYS macro)
*
	xref	_AbsExecBase
	xref	_CreatePort
	xref	_DeletePort
	xdef	_SysBase
	xdef	_DOSBase

*
* Initialization
*
start:	move.l	sp,savesp	;Save the stack	pointer.
	clr.l	_DOSBase	;Clear library pointers.
	clr.l	intbase
	clr.l	trnbase
	clr.l	voice_port
* Open dos.library
	move.l	_AbsExecBase,a6	;Find library
	move.l	a6,_SysBase
	lea	dosname,a1	;'dos.library'
	moveq	#0,d0		;Any version
	sys	OpenLibrary	;Open dos.library.
	move.l	d0,_DOSBase	;Save it for future reference.
	beq	quitprg		;Couldn't get pointer.
* Find out whether we were called from a CLI.
	suba.l	a1,a1
	move.l	_SysBase,a6
	sys	FindTask	;Find our task control block.
	move.l	d0,a4
	move.l	pr_CLI(a4),prcli ;Save pointer to CLI.
	beq.s	openwb		;We were called from Workbench.
* Get file handles for stdin and stdout.
	move.l	_DOSBase,a6	;Pointer to dos.library
	sys	Input		;Get file handle for keyboard.
	move.l	d0,stdin	;Save it here.
	beq	quitprg		;Couldn't get keyboard handle.
	sys	Output		;Get file handle for screen.
	move.l	d0,stdout	;Save it here.
	beq	quitprg		;Couldn't get screen handle.
	bra.s	openint
* Special Workbench startup stuff
openwb	lea	pr_MsgPort(a4),a0
	move.l	_SysBase,a6
	sys	WaitPort	;Wait for startup message from Workbench.
	lea	pr_MsgPort(a4),a0
	sys	GetMsg		;Get the message.
	move.l	d0,retmsg	;Save it so we can return it when we're done.

* Open intuition.library
openint	move.l	_SysBase,a6
	lea	intname,a1
	moveq	#0,d0
	sys	OpenLibrary
	move.l	d0,intbase
	bne.s	opened		;We got it opened.
	lea	interr,a0
	bsr	pstring		;Indicate open failure.
	bra	quitprg		;Exit.
opened:

*
* Get the time and convert it to cute notation.
*
* Get the time - put hour in D6 and minute in D7.
	move.l	_DOSBase,a6
	move.l	#dtstamp,d1
	sys	DateStamp	;Get the time.
	move.l	dtstamp+4,d7	;Work with the time here.
	divu	#60,d7		;Convert to hours and minutes.
	moveq	#0,d6
	move.w	d7,d6		;Work with hours here.
	clr.w	d7
	swap	d7		;Get minutes in low-order word.
	lea	timemsg,a2	;A2 loads the final message.
	lea	its,a0
	bsr	copy		;"It's "
* Adjust for minutes "to" the next hour if it's after (approximately) half past.
	cmpi.w	#33,d7		;33 minutes or more past the hour?
	bcs.s	adjhr		;No.
	addq.l	#1,d6		;It's time "to" the next hour.
adjhr	cmpi.w	#13,d6		;Is it in the afternoon?
	bcs.s	adj12		;No.
	subi.w	#12,d6		;Keep it in the range 1..12
adj12	tst.w	d6		;Is hour zero?
	bne.s	adj5		;No.
	moveq	#12,d6		;Set hour to 12.
* Look up nearness to a 5-minute interval.
adj5	addq.l	#2,d7		;Adjust for 5-minute rounding
	cmpi.w	#60,d7
	bcs.s	near
	sub.w	#60,d7		;Adjust for carry into the next hour.
* Express how near we are to the 5-minute interval.
near	divu	#5,d7		;Divide adjusted minute by 5.
	swap	d7		;Get the remainder
	move.w	d7,d0
	asl.w	#2,d0		;Multiply by 4 for longword displacement
	lea	nearptr,a0
	move.l	0(a0,d0.w),a0	;Pointer to appropriate "near" phrase
	bsr	copy		;Copy phrase to string.
* Now put in the appropriate 5-minute phrase itself.
	swap	d7		;(minute+2)/5
	asl.w	#2,d7
	lea	minptr,a0
	move.l	0(a0,d7.w),a0	;Pointer to appropriate minute phrase
	bsr	copy
* Say whether it's past the previous hour or approaching the next hour.
	tst.b	d7		;Is it around the hour?
	beq.s	hrname		;Yes - don't say anything here.
	lea	past,a0		;Assume it's past the hour
	cmpi.w	#28,d7		;Is it approaching the next hour?
	bcs.s	pastto		;No.
	lea	to,a0
pastto	bsr	copy
* Now add the hour name.
hrname	subq.l	#1,d6		;Convert 1..12 to 0..11
	asl.w	#2,d6
	lea	hrptr,a0
	move.l	0(a0,d6.w),a0
	bsr	copy
* Finish off the string and display it.
	tst.w	d7		;Is it around the hour?
	bne.s	moveper		;No.
	lea	oclock,a0
	bsr	copy		;Add " o'clock".
moveper	lea	period,a0
	bsr	copy		;Finish off the message.
	move.l	a2,d0		;Save ending address.
	subq.l	#1,d0		;Back over the terminating null.
	lea	timemsg,a0
	sub.l	a0,d0		;Length of string
	move.l	d0,timelen
	tst.l	prcli		;Were we called from a CLI?
	beq.s	saytime		;No - just say the time.
	bsr	pstring		;Display the time.

*
* Now say the time (if we can!).
*
saytime

* Translate the string to phonemes.
	move.l	_SysBase,a6
	lea	trnname,a1
	moveq	#0,d0
	sys	OpenLibrary	;Open translator.library.
	move.l	d0,trnbase
	beq	notalk		;The open failed - forget it.
	lea	timemsg,a0	;Address of original string
	move.l	timelen,d0	;Length of original string
	lea	tranmsg,a1	;Address of translated string
	moveq	#tranmax,d1	;Maximum length
	move.l	trnbase,a6
	sys	Translate	;Translate the string.

* Open a reply port for the narrator.
	clr.l	-(sp)
	clr.l	-(sp)
	jsr	_CreatePort	;voice_port = CreatePort (0L, 0L);
	add.l	#8,sp
	move.l	d0,voice_port
	move.l	d0,mn_ReplyPort
	beq	notalk		;Couldn't create the port!

* Set up the write channel information.
	move.l	#audio_chan,ch_masks
	move.w	#audio_chan_len,nm_masks
	clr.b	mouths
	move.w	#CMD_WRITE,io_Command
	clr.l	io_Offset
	move.l	#voice_io_len,mn_Length
	lea	tranmsg,a0
	move.l	a0,io_Data
tranlen	tst.b	(a0)+
	bne.s	tranlen		;Determine length of phoneme string.
	suba.l	#1,a0
	sub.l	io_Data,a0
	move.l	a0,io_Length

* Open the device and say the time.
	lea	narname,a0
	moveq	#0,d0
	lea	voice_io,a1
	moveq	#0,d1
	move.l	_SysBase,a6
	sys	OpenDevice
	tst.l	d0		;Were we successful?
	bne	notalk		;No.

	lea	voice_io,a1
	move.l	_SysBase,a6
	sys	DoIO		;Say the time.

	lea	voice_io,a1
	move.l	_SysBase,a6
	sys	CloseDevice	;Close narrator.device.

	bra.s	quitprg		;We made it!

* We were unable to talk - display a requester saying so.
notalk	suba.l	a0,a0
	lea	notalki,a1
	suba.l	a2,a2
	lea	foritxt,a3
	moveq	#0,d0
	moveq	#0,d1
	move.l	#200,d2
	moveq	#50,d3
	move.l	intbase,a6
	sys	AutoRequest	;Display the requester.

*
* End of program, one way or another
*
quitprg	move.l	savesp,sp	;Restore stack pointer.

	move.l	voice_port,d0
	beq.s	clostrn
	move.l	d0,-(sp)
	jsr	_DeletePort	;Delete the narrator message port.
	add.l	#4,sp

clostrn	move.l	_SysBase,a6
	move.l	trnbase,d0	;Close translator.library.
	beq.s	closint		;It wasn't opened.
	move.l	d0,a1
	sys	CloseLibrary

closint	move.l	intbase,d0	;Close intuition.library.
	beq.s	closdos
	move.l	d0,a1
	sys	CloseLibrary

closdos	move.l	_DOSBase,d0	;Close dos.library.
	beq.s	endwb
	move.l	d0,a1
	sys	CloseLibrary

endwb	tst.l	prcli		;Were we called from a CLI?
	bne.s	exit0		;Yes.
	move.l	_SysBase,a6
	sys	Forbid		;Don't let Workbench UnLoadSeg() us.
	move.l	retmsg,a1
	sys	ReplyMsg	;Return the startup message to Workbench.

exit0	moveq	#0,d0		;Return	with no	error.
	rts			;All done

*
* Copy the null-terminated string pointed to by A0 to (A2).
*  On exit, A2 points to the last byte copied (i.e. the null).
*
copy	move.b	(a0)+,(a2)+	;Move one character.
	bne.s	copy		;Get the next character.
	suba.l	#1,a2		;Back over the null.
	rts

*
* Display the null-terminated string pointed to by A0.
*
pstring	movem.l	d2-d3/a6,-(sp)
	move.l	a0,d2		;AmigaDOS wants the address here.
pstrl	tst.b	(a0)+		;Scan for end of string.
	bne.s	pstrl
	move.l	a0,d3		;AmigaDOS wants the length here.
	sub.l	d2,d3		;Length plus one
	subq.l	#1,d3		;Reduce to proper length.
	move.l	stdout,d1
	move.l	_DOSBase,a6
	sys	Write		;Write the string.
	movem.l	(sp)+,d2-d3/a6
	rts
	page
*************************************************************************
*									*
*	Constants							*
*									*
*************************************************************************

	section	Constants,data

dosname	dc.b	'dos.library',0
intname	dc.b	'intuition.library',0
trnname	dc.b	'translator.library',0
interr	dc.b	'Unable to open intuition.library',cr,lf,0
narname	dc.b	'narrator.device',0
notalkm	dc.b	'I can''t talk!',0
fortext	dc.b	'Forget it',0
its	dc.b	'It''s ',0
nearptr	dc.l	almost,nearly,null,justaft,justpas
almost	dc.b	'almost ',0
nearly	dc.b	'nearly ',0
null	dc.l	0
justaft	dc.b	'just after ',0
justpas	dc.b	'just past ',0
minptr	dc.l	null,five,ten,quarter,twenty,twenfiv,half
	dc.l	twenfiv,twenty,quarter,ten,five
quarter	dc.b	'a quarter',0
half	dc.b	'half',0
hrptr	dc.l	one,two,three,four,five,six,seven,eight,nine,ten,eleven,twelve
one	dc.b	'one',0
two	dc.b	'two',0
three	dc.b	'three',0
four	dc.b	'four',0
five	dc.b	'five',0
six	dc.b	'six',0
seven	dc.b	'seven',0
eight	dc.b	'eight',0
nine	dc.b	'nine',0
ten	dc.b	'ten',0
eleven	dc.b	'eleven',0
twelve	dc.b	'twelve',0
twenty	dc.b	'twenty',0
twenfiv	dc.b	'twenty-five',0
to	dc.b	' to ',0
past	dc.b	' past ',0
oclock	dc.b	' o''clock',0
period	dc.b	'.',cr,lf,0

audio_chan dc.b	3,5,10,12	;Which audio channels to use
audio_chan_len	equ	*-audio_chan

* IntuiText structures for "can't-talk" requester
	cnop	0,4
notalki				;BodyText
	dc.b	0,1,0,0
	dc.w	10,5
	dc.l	0,notalkm,0
foritxt				;NegativeText
	dc.b	0,1,0,0
	dc.w	4,3
	dc.l	0,fortext,0
	page
*************************************************************************
*									*
*	Storage areas							*
*									*
*************************************************************************

	section Storage,bss

savesp	ds.l	1		;Stack pointer save area
retmsg	ds.l	1		;Pointer to Workbench startup message
_SysBase ds.l	1		;Copy of _AbsExecBase
_DOSBase ds.l	1		;Pointer to dos.library
intbase	ds.l	1		;Pointer to intuition.library
trnbase	ds.l	1		;Pointer to translator.library
prcli	ds.l	1		;Pointer to CLI or zero
stdin	ds.l	1		;stdin file handle
stdout	ds.l	1		;stdout file handle
dtstamp	ds.l	3		;Date stamp (days, minutes, ticks)
timemsg	ds.b	41		;Time message is built here.
timelen	ds.l	1		;Actual length of time message
tranmsg	ds.b	80		;"timemsg" translated to phonemes
tranmax	equ	*-tranmsg
voice_port ds.l	1		;Pointer to message port for narrator.device

*
* Narrator write request
*
voice_io	;struct narrator_rb
message		;struct IOStdReq
io_Message	;struct Message
mn_Node		;struct Node
ln_Succ	ds.l	1	;Pointer to successor Node
ln_Pred	ds.l	1	;Pointer to predecessor Node
ln_Type	ds.b	1
ln_Pri	ds.b	1 
ln_Name	ds.l	1	;Pointer 
		; End of struct Node
mn_ReplyPort	ds.l 1	;Pointer to MsgPort (message reply port)
mn_Length ds.w	1	;Message length in bytes
		; End of struct Message
io_Device ds.l	1	;Pointer to device node
io_Unit	ds.l	1	;Pointer to Unit (driver private)
io_Command ds.w	1	;Device command
io_Flags ds.b	1
io_Error ds.b	1	;Error or warning number
		; End of struct IOReq - IOStdReq continues...
io_Actual ds.l	1	;Actual number of bytes transferred
io_Length ds.l	1	;Requested number of bytes transferred
io_Data	ds.l	1	;Points to data area.
io_Offset ds.l	1	;Offset for block-structured devices
		; End of struct IOStdReq
rate	ds.w	1	;Speaking rate (words per minute)
pitch	ds.w	1	;Baseline pitch in Hertz
mode	ds.w	1	;Pitch mode
sex	ds.w	1	;Sex of voice
ch_masks ds.l	1	;Pointer to audio allocation maps
nm_masks ds.w	1	;Number of audio allocation maps
volume	ds.w	1	;Volume: 0 (off) thru 64 (maximum)
sampfreq ds.w	1	;Audio sampling frequency
mouths	ds.b	1	;If non-zero, generate mouths.
chanmask ds.b	1	;Which channel mask used (internal)
numchan	ds.b	1	;Number of channel masks used (internal)
	ds.b	1	;For alignment
voice_io_len equ *-voice_io	; End of struct narrator_rb

	end
