; PROGRAM:  PIANO (Copyright 1983 by Bob Abrahams,
;                  3035 Berkeley Ave., Los Angeles, CA 90026)
;    For the Osborne EXECUTIVE portable computer.
; This program makes the keyboard play like a "piano" to prepare
; data for the program "SONG".
; Keyboard layout:
;
;              W   E       T   Y   U       "Black" Keys
;            A   S   D   F   G   H   J     "White" keys
;              #   b       #   #   b    (Flats & sharps)
; Notes ---  C   D   E   F   G   A   B
;     (Other keys have no function except ^C.)
;
;  PIANO uses direct keyboard input (not through BDOS or BIOS).
;  PIANO uses direct speaker control (through Port 2).
;
;       
WAIT	MACRO	SEC,TENTHS
;; GENERATES A WAIT OF THE TIME GIVEN UP TO 20 SECONDS
	LOCAL	AROUND
	JMP	AROUND
;; WAIT SUBROUTINE
@WAITT:	LXI	D,16665		;;		     10	>
	DCX	D		;;  6 >			>
	MOV	A,D		;;  4 >  X 16665 =	>
	ORA	E		;;  4 >		399,360	>
	JNZ	@WAITT+3	;; 10 >			> = 400,000
	INX	D		;;	(PAD)	      6	>
	INX	D		;;	(PAD)	      6	>
	NOP			;;	(PAD)	      4	>
	DCR	B		;;		      4	>
	JNZ	@WAITT		;;		     10	>
	RET
;; END OF WAIT SUBROUTINE
AROUND:
WAIT	MACRO	S?,T?
	DB	0D9H ;EXX	;;SAVE REGS
	IF	NUL T?
T??	SET	0
	ELSE
T??	SET	T?
	ENDIF
	IF	NUL S?
S??	SET	0
	ELSE
S??	SET	S?
	ENDIF
	 IF	S??*10+T?? GT 200
ERROR:	 "WAIT" TIME GREATER THAN 20 SECONDS
	 ELSE
	  IF	S??*10+T?? LE 0
	  MVI	B,20
	  ELSE
	  MVI	B,S??*10+T??
	  ENDIF
	 CALL	@WAITT
	 DB	0D9H ;EXX	;;RESTORE REGS
	 ENDIF
	ENDM
	WAIT	SEC,TENTHS
				;;WAIT
	ENDM
;; 
BDOS	EQU	5
CR	EQU	13
LF	EQU	10
ESC	EQU	27
TAB	EQU	9
;
	ORG	0100H
START:
;	DISPLAY KEYBOARD ON SCREEN
;	LOAD IN SPECIAL EDGE CHARACTER:
EDGE	EQU	0Bh	;Right-edge character (white keys) 
MID	EQU	16h	;Middle character (white keys)
BLK	EQU	20h	;Black keys
LOAD2	EQU	8000h
	LXI	H,LOAD1	;Move load routine and data up-memory
	LXI	D,LOAD2	;above bank 8
	LXI	B,LOAD1END-LOAD1	;length
	DB	0EDh,0B0h	;LDIR
	CALL	LOAD2
;
;	WRITE SCREEN DISPLAY:
	CALL	ILPRT
	DB	ESC,'Z',LF
	DB	TAB,TAB,'    Osborne Executive Keyboard "Piano" Routine',CR,LF
	DB	TAB,TAB,'        Copyright 1983, Bob Abrahams',CR,LF,LF
;
	DB	TAB,TAB,ESC,'l','                                '
	DB	'              ',ESC,'m',CR,LF,0	
;
	MVI	C,5
TOPS:	PUSH	B
	CALL	ILPRT
	DB	TAB,TAB,ESC,'g'
	DB	MID,MID,MID,EDGE,BLK,BLK,BLK,BLK,MID,EDGE,BLK,BLK,BLK,BLK
	DB	MID,MID,MID,EDGE,MID,MID,MID,EDGE,BLK,BLK,BLK,BLK
	DB	MID,EDGE,BLK,BLK,BLK,BLK,MID,EDGE,BLK,BLK,BLK,BLK
	DB	MID,MID,MID,EDGE,MID,MID,MID,MID,ESC,'G',CR,LF,0
	POP	B
	DCR	C
	JNZ	TOPS
;
	CALL	ILPRT
	DB	TAB,TAB,ESC,'g'
	DB	MID,MID,MID,EDGE,BLK,'W',BLK,BLK,MID,EDGE,BLK,'E',BLK,BLK
	DB	MID,MID,MID,EDGE,MID,MID,MID,EDGE,BLK,'T',BLK,BLK
	DB	MID,EDGE,BLK,'Y',BLK,BLK,MID,EDGE,BLK,'U',BLK,BLK
	DB	MID,MID,MID,EDGE,MID,MID,MID,MID,ESC,'G',CR,LF,0
;
	MVI	C,3
BOTS:	PUSH	B
	CALL	ILPRT
	DB	TAB,TAB,ESC,'g'
	DB	MID,MID,MID,MID,MID,EDGE,MID,MID,MID,MID,MID,EDGE
	DB	MID,MID,MID,MID,MID,EDGE,MID,MID,MID,MID,MID,EDGE
	DB	MID,MID,MID,MID,MID,EDGE,MID,MID,MID,MID,MID,EDGE
	DB	MID,MID,MID,MID,MID,EDGE,MID,MID,MID,MID,MID,EDGE
	DB	ESC,'G',CR,LF,0
	POP	B
	DCR	C
	JNZ	BOTS
;
	CALL	ILPRT
	DB	TAB,TAB,ESC,'g'
	DB	MID,ESC,'j','A',ESC,'k',MID,MID,MID,EDGE
	DB	MID,ESC,'j','S',ESC,'k',MID,MID,MID,EDGE
	DB	MID,ESC,'j','D',ESC,'k',MID,MID,MID,EDGE
	DB	MID,ESC,'j','F',ESC,'k',MID,MID,MID,EDGE
	DB	MID,ESC,'j','G',ESC,'k',MID,MID,MID,EDGE
	DB	MID,ESC,'j','H',ESC,'k',MID,MID,MID,EDGE
	DB	MID,ESC,'j','J',ESC,'k',MID,MID,MID,EDGE
	DB	MID,ESC,'j','K',ESC,'k',MID,MID,MID,EDGE
	DB	ESC,'G',CR,LF,0
;
	CALL	ILPRT
	DB	LF,LF,TAB,TAB
	DB	'Use letters on keyboard for each piano key shown.',CR,LF
	DB	TAB,TAB,'(Press ^C to exit program.)',CR,LF,LF,0
;
	IN	2	;Fetch Port 2 word
	ORI	6	;Turn off both disk drives
	OUT	2	;Send out word
	DI		;Disable interrupts (Clock interrupt
			;  makes poor sound quality.)
L3:	
	CALL	KEYSUB	;Wait for key to be pressed and stored
			;   (or see if still pressed).
	IN	2	;Fetch Port 2 word
	XRI	08H	;Complement speaker bit
	OUT	2	;   and send back out
	LDA	TIME	;Load half-cycle wait count
L1:	DCR	A	;  count down
	NOP		;  (Timing pad.)
	JNZ	L1	;     Loop until done
	JMP	L3	;Go back and do it again
;
;
EXIT2:			;Exit sequence:  Don't restore interrupts
			;until ^C key is released
	MVI	A,01H	
	OUT	14H	;See if Control key is still pressed
	IN	14H	
	CPI	0FBH
	JNZ	EXIT3	; If not, then exit is okay
	MVI	A,10H
	OUT	14H	;See if C key also still pressed
	IN	14H
	CPI	0FBH
	JZ	EXIT2	; If so, keep waiting 
;
EXIT3:
	WAIT	,1	; Debounce (Macro that waits 0.1 sec.)
	EI		; Interrupts back on
	JMP	0	; Exit to CP/M
;  
;  We have to work the keyboard the "hard" way (direct input)
;  because we have turned off the interrupts and the BDOS calls
;  will not really work properly.
;
KEYSUB:
	LDA	ROW	;See if same key as last time around is
	OUT	14H	;   still pressed.  If so, loop back to
	IN	14H	;   trigger next half-cycle.  If not,
	MOV	B,A	;   check to see just what key (if any)
	LDA	MASK1	;   is pressed.
	ANA	B
	RZ
	CALL	INVERT	;Turn "off" key display
EXITCH:			;Check for CTRL and C together
	MVI	A,01H
	OUT	14H
	IN	14H	;CTRL pressed?
	CPI	0FBH
	JNZ	NOEXIT	;No?  No exit here
	MVI	A,10H
	OUT	14H	
	IN	14H	;C also pressed
	CPI	0FBH
	JZ	EXIT2	;Yes?  Go to exit routine above
NOEXIT:
;			Key translate and loop-time select
	LXI	H,TABLE	;Initialize registers for this time
	LXI	D,WHROW ;Set white row display pointer
	MVI	C,2	;Set for two keyboard rows
	MVI	A,1	;Reset mask for keyboard matrix column
	STA	MASK1
	MVI	A,8	;Set ACC to initial keyboard matrix row set 
LOOKK:	STA	ROW	;Save current matrix row set
	OUT	14H	;Send matrix row
	IN	14H	;Get matrix column data
	MOV	B,A	;put in B
	LDA	MASK1	;get mask for column
KEYLOOP:
	ANA	B	;Mask for a column
	JNZ	NOKEY	;If not that one, prep for next check
	MOV	A,M	; Else, fetch related time count 
	CPI	1	; See if time code = 1
	JZ	NOEXIT	; If so, start scan over (assume not ^C)
	STA	TIME	;Store as time for loop above
	CALL	INVERT	;Turn "on" key display
	RET		; That's the note -- do it!
NOKEY:
	INX	H	;Bump table address
	PUSH	H	;Add 6 to display pointer
	LXI	H,6
	DAD	D
	XCHG
	POP	H
	LDA	MASK1	;Get current column mask to
	RLC		; rotate over to next bit position and
	STA	MASK1	;  save for use
	JNC	KEYLOOP ;  On carry, we've gone around.  If not...
	MVI	A,4	; Put other row matrix data in A to use
	LXI	D,BKROW	; Put black row display pointer in DE
	DCR	C	; See if this is second row checked anyway.
	JNZ	LOOKK	; If not, look at second row
	JMP	EXITCH	; If so, none of "our" keys were pressed,
			;  look again until we find something
;
;    Out of key search -- data on key "being pressed"
ROW:	DB	4	; Row data of last key found 
MASK1:	DB	1	; Column mask of last key found
TIME:	DB	0	; Wait time of last key pressed
;
TABLE:			; Wait-time table
			; Cycle count for half-cycle time
WHITE:	DB	204	;A     These are the "White keys"
	DB	181	;S
	DB	161	;D
	DB	151	;F
	DB	134	;G
	DB	118	;H
	DB	104	;J
	DB	98	;K
BLACK:	DB	1	;Q*	And these, the "Black"
	DB	192	;W
	DB	171	;E
	DB	1	;R*
	DB	142	;T
	DB	126	;Y
	DB	111	;U
	DB	1	;I*
;				*These keys not part of the piano
;				 but they are in matrix rows tested.
;				 1-count results in small click but
;				 no note.	
WHROW	EQU	0C000h+14*128+17
BKROW	EQU	0C000h+10*128+15
;
; Routine to toggle inversion and underline bits 
;			on character at screen address in DE
INVERT:	PUSH	H
	LXI	H,1000h
	DAD	D	;Create pointer to attribute byte
	IN	0
	STA	SAVEBANK
	ORI	40h	;Change to Bank 7
	OUT	0
	LDAX	D	;Get character
	XRI	80h	;Toggle high (inversion) bit
	STAX	D	;Put character
	MOV	A,M	;Get attribute
	XRI	40h	;Toggle underline bit
	MOV	M,A	;Put attribute
	LDA	SAVEBANK
	OUT	0
	POP	H
	RET
;
ILPRT:		;IN-LINE PRINT (Follow call by string, terminate with 0)
	POP	H	;get string address
	MOV	A,M	;get character
	INX	H	;move pointer
	PUSH	H	;save (for next and return)
	ORA	A	;see if zero
	RZ		;if zero, done
	MOV	E,A	;else, send character
	MVI	C,2	;via CONOUT
	CALL	BDOS
	JMP	ILPRT	;again
;
;	ROUTINE TO MOVE IN "EDGE CHARACTER" AND ITS DATA
LOAD1:	LXI	H,EDGEDATA+OFFSET	;Data location after move
	LXI	D,16*EDGE		;Location of character in Bank 8
	LXI	B,10			;Words needed
	IN	0
	STA	SAVEBANK+OFFSET
	ORI	80h			;Get Bank 8
	OUT	0
	DB	0EDh,0B0h	;LDIR -- move "Edge" data
	LDA	SAVEBANK+OFFSET		;Restore Banks
	OUT	0
	RET
SAVEBANK: DB	1	;Save old program bank byte here
OFFSET	EQU	LOAD2-LOAD1
EDGEDATA DB	0FEh,0FEh,0FEh,0FEh,0FEh	;"Edge" character
	 DB	0FEh,0FEh,0FEh,0FEh,0FEh	;  definition
LOAD1END:
;
	END	START

