; PROGRAM: DIGIDRUM.SRC

;	********************************
;	* Digital drums MIDI interface *
;	********************************

;	Copyright by WolfRam Technologies Inc. All rights reserved.
;	May 17th 1996 -

; *********************** Identify used controller ***************************

		device	pic16c74a,xt_osc,wdt_off,pwrt_on,protect_off

; ************************** Port connections ********************************

; We can possibly connect up to 16 drums to an.0 with #8 being the foot cin

; The foot pedal is connected to port C say rc.0

PEDAL		=	rc.0

; The analog muxes are controlled on re.0..re2, enabled on ra.4, ra.5

INH2		=	ra.4
INH1		=	ra.5

; The LCD display is controlled on port C & D as well say rd.7 and rc.4..rc.5

RS		=	rc.4		; 0=instruction, 1=data
R_W		=	rc.5		; 0=write, 1=read
E		=	rd.7		; 0=disable, 1=enable

; The LCD data port is port B

lcd_port	=	rb
lcd_tris	=	trisb

; The serial EEPROM is connected to rc.1..rc.3

CS		=	rc.1
DATA		=	rc.2
CLK		=	rc.3

; We have four buttons to control the interface say ra.2 and rd.1..rd.3

; ***************** Constants for common LCD instructions ********************

clear		=	 1		; Clear display
home		=	 2		; Return display to home position
on_display	=	12		; Turn on display, cursor off
off_display	=	 8		; Turn off display
shift_left	=	28		; Shifts display left
shift_right	=	24		; Shifts display right

; ******** Set RAM origin above special registers, declare variables *********

		org	32

printbuf	ds	21		; [printtext] Buffer for display
lcd_data	ds	1		; [shake_LCD] Data or instruction
lcd_char	ds	1		; [printchar] Character to be printed
dd_addr		ds	1		; [printchar] LCD RAM address
key		ds	1		; [getkey] Pushed key
lastkey		ds	1		; [getkey] Last pushed key
pushed		ds	1		; [getkey] Duration of key push

bit_temp	ds	1		
RS_TEMP		=	bit_temp.0	; [shake_LCD] Store RS status
					; .1 : value of RP0 at the IT call
TMR_MODE	=	bit_temp.2	; If 0, auto repeat keys
					;    1, 2 msec delay for drums
PEDAL_ACTION	=	bit_temp.3	; 1 if foot action
PEDAL_STAT	=	bit_temp.4	; Pedal status, 1 if pushed
MEASURE		=	bit_temp.5	; 1 if 2msec measurement on
					; .6 : temporary storage

ad_value	ds	1		; [ad_conv] Result of A/D conversion

drumhits	ds	12		; 12 drums can be hit
decays		ds	12		; Decays for each drum
gains		ds	6		; Gain corrections
curves		ds	3		; Volume xfroms for each drum,
					; default is 0 - no conversion,
					; 1 - suppress at low volumes,
					; 2 - emphase at low volumes,
					; 3 - "S" curve
minlevel	ds	1		; Absolute minimum level
crosslevel	ds	1		; Crosstalk threshold level
decaytime	ds	1		; Length of decay period

; name		ds	6		; Name of the drumset

last_pedal	ds	1		; Volume of the last pedal action
drum		ds	1		; Drum cycle index
maxdrum		ds	1		; Maximum value in the A/D cycle
maxind		ds	1		; Index of the maximum in the cycle

midichan	ds	1		; Channel No. of the drumset
midikey		ds	1		; Key No. of the drum hit
volume		ds	1		; Velocity of the hit

barvalue	ds	1		; [bar] Size of the bar to be displayed
value		ds	1		; [setbar] Adjusted value
min_value	ds	1		; Minimum level
max_value	ds	1		; Maximum level
setmode		ds	1		; Identifies the setting mode. If = 12,
					; it means that the setting is general.
					; In that case [setbar] returns on the
					; ESC key. If <> 12, then it's a drum
					; specific setting, and so [setbar]
					; returns with the index of the drum
					; being hit in [setmode]. Further
					; setting may be achieved by reentering
					; the routine at [adjust_bar] without
					; initialization.

all_tmp		ds	1		; Buffer for any LOWEST level routine
all_tmp2	ds	1

count1		ds	1		; Counters
count2		ds	1

first		equ	ad_value	; [menu] Index of 1st displayed ch
points		equ	drumhits	; [menu] Nr. of menu choices
choices		equ	drumhits+1	; [menu] Nr. of choices displayed
choice		equ	drumhits+2	; [printchoices] Index of choice
ch_ind		equ	drumhits+3	; [copychoice] Offs of 1st ch in table

w_copy		ds	1		; [interrupt] Store for W
s_copy		ds	1		; [interrupt] Store for status reg

; ************************ Page 1 register variables *************************

		org	32

keys		ds	12		; The MIDI keys for each drum
pedal_close	ds	1		; Two other codes for foot
close_hit	ds	1		; pedal actions

; ****************************** Reset vector ********************************

		org	0

		mov	PCLATH,#7	; Sets the jmp pc+w address high byte
					; to the end of the first 2K page.
					; Tables start at 600H, 700H, 800H.
		jmp	initialize

; *************************** Interrupt routine ******************************

		org	4

interrupt	mov	w_copy,w	; Make a copy of W
		mov	s_copy,status	; Make a copy of status
		jnb	RP0,inter2	; We are on page 0
		clrb	RP0
		setb	bit_temp.1	; Remember page
inter2		jnb	TMR1IF,inter1	; Not timer1 overflow
		clrb	TMR1IF		; Clear the timer interrupt flag
		mov	TMR1L,#192	; Reinitialize timer
		mov	TMR1H,#224
inter4		inc	pushed		; Indicate "tick"
inter1		jnb	bit_temp.1,inter3 ; Stay on this page
		clrb	bit_temp.1	; Forget it
		setb	RP0		; Restore page
inter3		mov	status,s_copy	; Restore status register
		swap	w_copy		; Prepare for swapped move
		mov	w,<>w_copy	; Swap/move to w, status unaffected
		reti			; Return to main program

; ***************************** Start program ********************************

initialize	clr	ra
		clr	rb
		clr	rc
		clr	rd
		clr	re		; Clear all ports
		clrb	RP1		; RP1 not used, set to 0
		setb	SPEN		; Enable serial port
		clrb	CREN		; Clear continuous receive
		clrb	SREN		; Clear single receive
		setb	RP0		; Set page for bank1 registers
		mov	trisa,#0FH	; Pins 0..3 are input
		clr	trisb		; Set data port to output
		mov	trisc,#01H	; Pin 0 is input
		mov	trisd,#0EH	; Pin 1, 2 and 3 are input
		clr	trise		; Port E is output
		mov	ADCON1,#4	; 3 analog ports: ra.0, ra.1, ra.2
		setb	BRGH		; High speed serial IO
		mov	SPBRG,#31	; Baud rate is 31.25 kbaud/sec
					; Clock signal is 16 MHz
		clrb	SYNC		; Asynchronous transmission
		clrb	TX89		; 8 data bits format
		setb	TXEN		; Enable serial transmission
		setb	TMR1IE		; Enable timer 1 IT
		clrb	RP0		; Back to page 0
		setb	GIE		; Enable interrupts
		setb	PEIE		; Enable peripheral interrupts
		or	T1CON,#20H	; Set prescaler
		clrb	TMR1ON		; Stop timer
		and	T1CON,#0CFH	; Clear prescaler
		setb	TMR1ON		; Start timer

		mov	midichan,#9	; Set default MIDI channel
		mov	fsr,#keys+80H	; Set MIDI keys 1 to 12 + 2
		clr	count1		; 14 drums, address [keys]
start3		mov	w,count1
		call	def_drums
		mov	indf,w
		inc	fsr
		inc	count1
		cjb	count1,#14,start3
		clr	last_pedal	; Clear last pedal

		clr	curves		; Default is a straight curve
		clr	curves+1
		clr	curves+2
		mov	minlevel,#2	; Initialize absolute minimum
		mov	crosslevel,#32	; Initialize crosstalk threshold

		mov	count1,#25
start2		djnz	lcd_data,start2	; Wait for LCD to power on
		djnz	count1,start2
		clrb	RS		; Clear RS to send instruction
		mov	lcd_data,#38H	; Initialize LCD
		call	shake_LCD
		mov	lcd_data,#on_display ; Turn display on
		call	shake_LCD
		mov	lcd_data,#clear	; Clear the display
		call	shake_LCD
		mov	lcd_data,#40H	; Write to CG RAM: start at address 0
		call	shake_LCD
		setb	RS		; Set RS to send data
		clr	count1		; Copy user defined characters
start1		mov	w,count1
		call	user_chars	; Get next byte
		mov	lcd_data,w
		call	shake_LCD	; Write to CG RAM
		inc	count1
		cjb	count1,#16,start1
		clr	lastkey
		clr	pushed		; Clear duration
		clr	bit_temp	; Clear any status info

intro		clr	count1		; Copy intro text first line
		mov	fsr,#printbuf-1
intro1		mov	w,count1
		inc	fsr
		call	intr_1
		mov	indf,w
		inc	count1
		cjne	indf,#0,intro1	; Copy up to a 0 byte
		mov	dd_addr,#21
		call	printtext	; Print invisible
		clr	count1		; Copy intro text second line
		mov	fsr,#printbuf-1
intro2		mov	w,count1
		inc	fsr
		call	intr_2
		mov	indf,w
		inc	count1
		cjne	indf,#0,intro2	; Copy up to a 0 byte
		mov	dd_addr,#86
		call	printtext	; Print invisible
		call	fromright	; Display text
		clr	count1		; Copy second screen first line
		mov	fsr,#printbuf-1
intro3		mov	w,count1
		inc	fsr
		call	intr_3
		mov	indf,w
		inc	count1
		cjne	indf,#0,intro3	; Copy up to a 0 byte
		mov	dd_addr,#21
		call	printtext	; Print invisible
		clr	count1		; Copy second screen second line
		mov	fsr,#printbuf-1
intro4		mov	w,count1
		inc	fsr
		call	intr_4
		mov	indf,w
		inc	count1
		cjne	indf,#0,intro4	; Copy up to a 0 byte
		mov	dd_addr,#84
		call	printtext	; Print invisible
		call	fromright	; Display text

		call	clear_display
		call	backtomenu	; Display back option

start5		mov	fsr,#decays	; Clear decays
		mov	indf,#126
		inc	fsr
		cjb	fsr,#decays+12,start5 ; Continue

		clrb	RS		; Copy bar characters
		mov	lcd_data,#50H	; Write to CG RAM: start at address 16
		call	shake_LCD
		setb	RS		; Set RS to send data
		mov	count2,#0	; Copy user defined characters
		mov	PCLATH,#7
start6		mov	w,count2
		call	bar_chars	; Get next byte
		mov	lcd_data,w
		call	shake_LCD	; Write to CG RAM
		inc	count2
		cjb	count2,#40,start6
		mov	PCLATH,#6

start4		call	letsplay	; Enter play mode
		mov	all_tmp,w
		jnz	start4		; Cycle until key #1 is pressed

mainmenu	mov	PCLATH,#6	; Menu choices are at 600H
		clr	ch_ind		; Main menu is the first in the table
		mov	points,#3	; "Setup", "Sets" & "PC use"
		call	menu
		mov	choice,w	; Store choice
		call	clear_display
		cje	choice,#0,start4 ; I wanna play again!
		cjne	choice,#3,main1
		mov	PCLATH,#7
		clr	count1		; This option is
		mov	fsr,#printbuf
main3		mov	w,count1
		call	not_used
		mov	indf,w
		inc	fsr
		inc	count1
		cjne	count1,#15,main3
		mov	dd_addr,#1
		call	printtext
		clr	count1		; Not available
		mov	fsr,#printbuf
main4		mov	w,count1
		call	not_used2
		mov	indf,w
		inc	fsr
		inc	count1
		cjne	count1,#17,main4
		mov	dd_addr,#40H
		call	printtext
		mov	PCLATH,#6
main5		call	getkey
		cjne	key,#4,main5
		jmp	mainmenu

main1		cjne	choice,#1,main2
		call	setup		; Setup selected
		jmp	mainmenu
main2		mov	w,#3
		mov	ch_ind,w	; "Select", "Name" and "Basic"
		mov	points,w
		call	menu

		jmp	mainmenu	; Further possible menu choices...

; ********************************* Hit 'em! *********************************

letsplay	setb	TMR_MODE	; Indicate 2 msec cycle
play2		cje	pushed,#0,play5 ; Skip if no IT occurred
		jb	MEASURE,play17	; Don't clear pushed in a measurement
		clr	pushed		; Clear flag

play17		mov	fsr,#decays	; Increment decays
play3		cje	indf,#126,play4
		inc	indf
play4		inc	fsr
		cjne	fsr,#decays+12,play3

play5		clr	drum		; Drum test cycle: read all inputs
		clrb	INH2
		setb	INH1		; Enable MUX1
		clrb	PEDAL_ACTION	; Clear pedal actions
		mov	fsr,#drumhits	; Address of drumhits
play6		mov	porte,drum	; Select drum
		jb	MEASURE,play7	; Don't clear data during measurement
		clr	indf		; Clear data
play7		call	ad_conv
		mov	all_tmp,ad_value
		cjb	all_tmp,minlevel,play8 ; Skip if below minimum
		cjb	all_tmp,indf,play8 ; Don't store if it's smaller
		mov	indf,w		; Store result
play8		inc	fsr
		inc	drum		; Next drum
		cjne	drum,#8,play13
		clrb	INH1
		setb	INH2		; Enable MUX2
play13		cjb	drum,#12,play6
		mov	fsr,#drumhits	; Check the results
		clr	maxdrum		; Clear maximum value
		mov	maxind,#12	; Index of maximum value
play9		cjae	indf,minlevel,play14 ; This drum might've been hit
play10		inc	fsr		; Next drum
		cjb	fsr,#drumhits+12,play9

		mov	w,rc		; Check foot pedal
		and	w,#1
		mov	all_tmp,w
		swapf	all_tmp		; Move to bit 4
		xor	all_tmp,bit_temp ; Check pedal status
		jnb	all_tmp.4,play11 ; No change in pedal status
		xor	bit_temp,#16	; Store
		jnb	PEDAL_STAT,play11 ; Pedal just released - don't care
		setb	PEDAL_ACTION	; Remember action

play11		cjne	maxind,#12,play15 ; A drum was hit
		jnb	PEDAL_ACTION,play12 ; No drum or pedal push

		mov	fsr,#pedal_close+80H
		mov	midikey,indf	; Store Key No.
		mov	volume,last_pedal ; Send volume
		mov	drum,#7		; Store drum index
		call	adjust
		call	midiframe	; Send MIDI frame
		jmp	play2		; Continue playing

play12		call	getkey		; Check keys if no action
		mov	w,key
		ret			; Return on key push

play14		cjb	indf,maxdrum,play10 ; This is not the biggest
		mov	all_tmp,indf	; Store drum power
		add	fsr,#12		; Go to decays
		mov	PCLATH,#9
		mov	w,indf		; Get curve index
		call	decay		; Get data
		mov	all_tmp2,w	; Store
		mov	PCLATH,#6
		sub	fsr,#12		; Restore fsr
		cjb	all_tmp,all_tmp2,play10 ; Inside decay
		mov	w,#drumhits
		mov	w,fsr-w		; Calculate index
		mov	maxind,w	; Store
		mov	maxdrum,indf	; Store data as well
		jmp	play10		

play15		jb	MEASURE,play16	; Measurement already started
		setb	MEASURE		; Start measurement
		clr	pushed		; Clear 'tick'
		jmp	play2		; Measure

play16		cje	pushed,#0,play2 ; Measurement not yet completed
		clrb	MEASURE		; Clear indication

		mov	fsr,#drumhits
play18		cjae	indf,crosslevel,play19 ; Just made it
		mov	w,#drumhits
		mov	w,fsr-w		; Check if it's the maximum
		mov	w,maxind-w
		jz	play19		; It's the max
		clr	indf		; Reset the data - below the threshold
play19		inc	fsr		; Next data
		cjb	fsr,#drumhits+12,play18

		mov	w,maxind
		add	w,#keys+80H	; Get the MIDI key of the maximum
		mov	fsr,w		; We must transfer this key first,
		mov	w,indf		; since all other drums are possibly
					; only crosstalk - the maximum has the
					; highest priority.
		mov	midikey,w	; Store Key No.
		cjne	maxind,#7,play21 ; Skip if it's not the foot cin
		jnb	bit_temp.4,play20 ; Key code is OK if pedal not pushed
		add	fsr,#6		; Go to close_hit
		mov	midikey,indf	; Replace key code
		jmp	play21		; Don't store volume if pedal closed

play20		mov	last_pedal,maxdrum ; Store volume

play21		mov	volume,maxdrum	; Send volume
		mov	w,maxind
		mov	drum,w
		add	w,#drumhits	; Clear the data of the maximum drum.
		mov	fsr,w		; So, after the maximum has been sent,
		clr	indf		; we only send the non-zero drums.
		call	adjust		; Apply curve
		call	midiframe	; Send MIDI frame
		clr	drum		; Check all drums
		mov	fsr,#drumhits
play22		cjne	indf,#0,play24	; Gee! Another drum was hit!
play23		inc	fsr
		inc	drum
		cjb	drum,#12,play22	; Cycle
		jmp	play2		; Here we go again

play24		mov	w,drum
		add	w,#keys+80H	; Get the MIDI key of the drum
		mov	fsr,w
		mov	midikey,indf	; Store Key No.
		mov	w,drum		; Get volume
		add	w,#drumhits
		mov	fsr,w
		mov	volume,indf	; Send volume
		cjne	drum,#7,play26	; Skip if it's not the foot cin
		jnb	PEDAL_STAT,play25 ; Key code is OK if pedal not pushed
		mov	fsr,#close_hit+80H
		mov	midikey,indf	; Replace key code
play25		mov	last_pedal,volume ; Store volume
play26		call	adjust
		call	midiframe	; Send MIDI frame
		jmp	play23		; Continue checking of the drums

; ****** Adjust the volume of the drum according to the selected curve *******

adjust		mov	fsr,#curves	; Address of curves
		mov	all_tmp,drum
		and	all_tmp,#12	; Get byte mask
		clrb	c
		rrf	all_tmp
		rrf	all_tmp
		add	fsr,all_tmp
adj1		mov	all_tmp,drum
		and	all_tmp,#3	; Get bit position
		mov	all_tmp2,#3	; Set mask
adj3		cje	all_tmp,#0,adj2 ; Exit if mask on position
		clrb	c
		rlf	all_tmp2	; Shift
		rlf	all_tmp2
		djnz	all_tmp,adj3

adj2		mov	all_tmp,indf	; Get curve settings
		and	all_tmp,all_tmp2 ; Apply mask
		mov	all_tmp2,drum
		and	all_tmp2,#3
adj5		cje	all_tmp2,#0,adj4 ; Exit if shifted back
		clrb	c
		rrf	all_tmp		; Shift back
		rrf	all_tmp
		djnz	all_tmp2,adj5

adj4		cjne	all_tmp,#0,adj6 ; If zero, no adjustment
		ret
adj6		cjne	volume,#0,adj9	; Check if volume zero
		ret			; If so, nothing to do
adj9		dec	volume		; There is no value for zero
		mov	PCLATH,#8
		cjne	all_tmp,#1,adj7 ; Not the suppress curve
		mov	w,volume
		call	curve1
adj10		mov	volume,w
		mov	PCLATH,#6
		ret
adj7		cjne	all_tmp,#2,adj8 ; Not the emphasis curve
		mov	w,volume
		call	curve2
		jmp	adj10
adj8		mov	PCLATH,#9
		mov	w,volume	; The S curve
		call	curve3
		jmp	adj10

; ********* Send a MIDI frame: Note On, Key No., Velocity, Key No., 00 ********

midiframe	clrb	GIE		; Disable IT's during transmission
		mov	w,midichan	; Get channel No.
		or	w,#90H		; Note On
		mov	TXREG,w		; Send byte
		call	pause		; Wait for transmission
		mov	TXREG,midikey	; Send key No.
		call	pause		; Wait for transmission
		mov	TXREG,volume	; Send volume
		call	pause
		mov	TXREG,midikey	; Send key No.
		call	pause		; Wait for transmission
		mov	TXREG,#0	; Send volume 0
		call	pause
		setb	GIE		; Re-enable interrupts
		ret
		
pause		mov	all_tmp,#10
paus1		nop
		djnz	all_tmp,paus1
		setb	RP0		; Go to page 1
paus2		jnb	TRMT,paus2
		clrb	RP0
		ret

; ******************************* Set up inputs ******************************

setup		mov	ch_ind,#6	; "Drums"
		mov	points,#2	; & "Levels"
		call	menu
		mov	choice,w	; Store choice
		call	clear_display
		cjne	choice,#0,set1	; Not ESC
		ret
set1		cjne	choice,#1,set2	; Not "Drums"

		clr	setmode		; Set all drum key codes
		mov	min_value,#1	; Set limiters
		mov	max_value,#62
		mov	value,#1	; First drum
		mov	dd_addr,#7
		call	show_value	; Display drum No.
		mov	fsr,#keys+80H
		mov	value,indf
		clr	first		; Store temporarily
		call	setbar
set6		mov	fsr,#keys+80H
		add	fsr,first	; Restore
		mov	indf,value	; Write data
		mov	value,setmode
		inc	value		; The 1st is the 0th
		mov	dd_addr,#7
		call	show_value	; Display new drum No.
		mov	fsr,#keys+80H
		add	fsr,setmode
		mov	value,indf	; Read data
		mov	first,value	; Store index
		cje	key,#0,setup	; ESC
		call	adjust_bar	; Continue adjustment
		jmp	set6

set2		mov	ch_ind,#8	; "Noise",
		mov	points,#3	; "Xtalk" & "Decay"
		call	menu
		mov	choice,w	; Store choice
		call	clear_display
		cje	choice,#0,setup	; ESC

set3		cjne	choice,#1,set4	; Not Noise

		jmp	set2		; WRITE DECAY LATER

set4		mov	setmode,#12	; General setting
		clr	min_value
		mov	max_value,#100
		cjne	choice,#2,set5	; Crosstalk
		mov	value,minlevel	; Adjust minimum level
		call	setbar
		mov	minlevel,value
		jmp	set2

set5		mov	value,crosslevel ; Adjust crosstalk threshold
		call	setbar
		mov	crosslevel,value
		jmp	set2

; **************** Set a value between min_value and max_value ***************

setbar		mov	ch_ind,#7
		mov	w,#1		; Get [-]
		call	copychoice
		mov	w,#1
		call	printchoice	; Display it
		mov	w,#2		; Get [+]
		call	copychoice
		mov	w,#3
		call	printchoice	; Display it
		mov	w,#3		; Get OK
		call	copychoice
		mov	w,#4
		call	printchoice
		clr	barvalue	; Clear bar

adjust_bar	mov	dd_addr,#12
		call	show_value	; Display data

setb9		call	letsplay	; Check the drums
		cje	maxind,#12,setb3 ; No drum was hit
		cja	maxdrum,barvalue,setb4 ; A new hit

setb3		cje	barvalue,#0,setb2 ; Last hit already forgotten
		dec	barvalue	; Shorten bar
		clr	count1
setb5		nop			; Delay a little
		nop
		djnz	count1,setb5
		jmp	setb6		; Display

setb4		mov	barvalue,maxdrum ; Store for display
setb6		call	bar		; Display [barvalue]
		cje	setmode,#12,setb2 ; Skip if general setting
		cje	maxind,#12,setb2 ; Skip if no drum was hit
		mov	setmode,maxind	; Index of the drum hit
		ret

setb2		call	getkey		; Read keyboard
		cje	key,#0,adjust_bar ; No key pushed
		cjne	key,#4,setb7
		ret			; Done
setb7		cje	key,#2,adjust_bar ; Key #2 is illegal here
		mov	count1,value	; Store temporarily
		cje	key,#1,setb8	; We have [-]
		cje	value,max_value,setb9 ; Limit reached - no redisplay
		inc	value
		jmp	adjust_bar	; Redisplay
setb8		cje	value,min_value,setb9 ; Limit reached - no redisplay
		dec	value
		jmp	adjust_bar

; *********** Display a bar, value between 0 and 127 in [barvalue] ***********

bar		mov	ad_value,barvalue ; Store value
		mov	fsr,#printbuf	; Start of meter
		clrb	c
		rrf	ad_value	; Bar is 0..63 length
		inc	ad_value	; At least one
bar1		cjb	ad_value,#5,bar7
		sub	ad_value,#5
		mov	indf,#6		; This char is full
		inc	fsr
		jmp	bar1
bar7		add	ad_value,#2	; Index of special characters
		mov	indf,ad_value	; Display end of bar
bar2		inc	fsr
		cje	fsr,#45,bar3	; Filled up with spaces
		mov	indf,#' '
		jmp	bar2
bar3		mov	indf,#2		; Show maximum
		inc	fsr
		clr	indf		; End of string
		mov	dd_addr,#40H	; Set start
		call	printtext
		ret

; *********** Display value between 0 and 127 in [value] at dd_addr **********

show_value	mov	ad_value,value ; Store value temporarily
		mov	printbuf,#'O'	; Display "normal" zero
		cjb	ad_value,#100,bar2 ; Less than a hundred
		mov	printbuf,#'1'
		sub	ad_value,#100	; Cut hundred off
show2		mov	all_tmp,#'0'	; Tens = 0
show4		cjb	ad_value,#10,show3 ; Only ones remaining
		inc	all_tmp
		sub	ad_value,#10	; Remove tens
		jmp	show4
show3		cjne	all_tmp,#'0',show5 ; Skip if not 0
		mov	all_tmp,#'O'	; Display "normal" zero
show5		mov	printbuf+1,all_tmp ; Print tens
		add	ad_value,#'0'
		cjne	ad_value,#'0',show6 ; Skip if not 0
		mov	ad_value,#'O'	; Display "normal" zero
show6		mov	printbuf+2,ad_value ; Print ones
		clr	printbuf+3
		call	printtext
		sub	dd_addr,#4	; Restore starting position
		ret

; *************************** Clear the LCD display **************************

clear_display	clrb	RS
		mov	lcd_data,#clear
		call	shake_LCD
		setb	RS
		ret

; ******* Write data or instructions (in variable lcd_data) to the LCD *******

shake_LCD	movb	RS_TEMP,RS	; Store current state of RS
		clrb	RS		; Clear RS to send instruction
		setb	RP0		; Bank1
		mov	lcd_tris,#255	; All inputs
		clrb	RP0		; Bank0
		setb	R_W		; Set RW to read
		nop
shake1		setb	E		; Enable the LCD
		nop
		mov	all_tmp,lcd_port ; Store LCD data
		clrb	E		; Disable LCD
		nop
		snb	all_tmp.7	; Is the LCD busy?
		jmp	shake1		; Yes, try again
		movb	RS,RS_TEMP	; No, send the data or instruction
		clrb	R_W
		setb	RP0
		clr	lcd_tris
		clrb	RP0
		mov	lcd_port,lcd_data
		setb	E
		nop
		nop
		clrb	E
		ret

; *** Draw a menu of 'points' choices, return 0 on ESC or number of choice ***

menu		mov	first,#1	; Start to display by first choice
menu10		call	clear_display
		cjb	points,#4,menu1	; Less than 4 choices fit on 1 display
		cja	first,#1,menu2	; first>1 shows we've a PREV option
menu1		cje	ch_ind,#0,menu12 ; We are in the main menu
		mov	printbuf,#'E'	; Choice 1 is Esc
		mov	printbuf+1,#'s'
		mov	printbuf+2,#'c'
		clr	printbuf+3
		jmp 	menu3		; Go to print
menu12		mov	printbuf,#'H'	; Choice 1 is Hit'em
		mov	printbuf+1,#'i'
		mov	printbuf+2,#'t'
		mov	printbuf+3,#39
		mov	printbuf+4,#'e'
		mov	printbuf+5,#'m'
		clr	printbuf+6
		jmp 	menu3		; Go to print
menu2		mov	printbuf,#'P'	; Choice 1 is Prev
		mov	printbuf+1,#'r'
		mov	printbuf+2,#'e'
		mov	printbuf+3,#'v'
		clr	printbuf+4
menu3		mov	w,#1
		call	printchoice	; Print first choice
		mov	w,first
		call	copychoice	; Get choice
		mov	w,#2
		call	printchoice	; Print second choice
		inc	first
		mov	w,first
		call	copychoice	; Get choice
		mov	w,#3
		call	printchoice	; Print third choice
		inc	first
		cja	first,points,menu4 ; If i>p, we have no more choices
		cje	first,points,menu5 ; The last choice fits onto screen
		mov	printbuf,#'N'	; Choice 4 is Next
		mov	printbuf+1,#'e'
		mov	printbuf+2,#'x'
		mov	printbuf+3,#'t'
		clr	printbuf+4
		jmp	menu6
menu5		mov	w,first
		call	copychoice	; Get choice
menu6		mov	choices,#4	; All buttons alive
		mov	w,#4
		call	printchoice	; Print fourth choice
		jmp	menu7
menu4		mov	choices,#3	; Only three buttons!
menu7		call	getkey		; Detect button push, key is 1..4
		cje	key,#0,menu7	; Wait for a key
		cjne	key,#1,menu8	; Not Esc or Prev
		cjne	first,#3,menu9	; Prev
		clr	pushed		; Disable prells
		retw	0		; Escape
menu9		sub	first,#4	; first=first+2, we go back to first-2
		jmp	menu10		; Prev or Next pressed, new display
menu8		cja	key,choices,menu7 ; 4th key pressed, but only 3 legal
		cje	key,#4,menu11	; Possibly Next
menu13		clr	pushed		; Disable prells
		add	first,key	; At the moment, first shows the index
		sub	first,#4	; of the first choice + 2. If the first
					; selection key is pressed, it's index
					; would be 2 (key 1 is Esc or Prev).
		mov	w,first		; So first+key-4 identifies the choice
		ret			; Return choice
menu11		cjb	first,points,menu10 ; Next, first=first+2
		jmp	menu13		; Calculate choice & return

; ********** Copy a menu choice from the table to the print buffer ***********

copychoice	add	w,ch_ind	; ch_ind holds the offset of the first
					; menu choice in the table
		mov	all_tmp,w	; Store
		dec	all_tmp		; "The 1st is the 0th"
		rlf	all_tmp		; Multiply by two
		mov	w,all_tmp
		rlf	all_tmp		; Multiply by four
		add	w,all_tmp	; Multiply by six
		mov	all_tmp,w	; Store
		mov	fsr,#printbuf	; Address of print buffer
copych1		mov	w,all_tmp
		call	menutxt		; Get byte
		mov	indf,w		; Store in buffer
		inc	all_tmp
		inc	fsr
		cjb	fsr,#printbuf+6,copych1	; Copy 6 bytes
		clr	indf		; Write a zero byte to the end for
		ret			; cases with 6 byte long choices

; ************** Display "Menu" at any display, check keys *******************

backtomenu	mov	printbuf,#'M'
		mov	printbuf+1,#'e'
		mov	printbuf+2,#'n'
		mov	printbuf+3,#'u'
		clr	printbuf+4
		mov	w,#1		; Display at left top "jmp printchoice"

; *************** Print a menu choice 1-4 on proper position *****************

printchoice	mov	choice,w	; Store choice index
		call	strlen		; Get length of text
		mov	all_tmp,w	; Store
		cja	choice,#2,prtch1 ; Print on the right side
		cje	choice,#1,prtch2 ; First row
		add	choice,#3FH	; Second row
prtch2		dec	choice		; DD address is 0 or 40H
		mov	dd_addr,choice
		clr	lcd_char	; Left arrow (user char)
		call	printchar
		add	dd_addr,#2	; Space between
prtch6		mov	fsr,#32		; Print buffer
prtch4		mov	lcd_char,indf	; Get character
		call	printchar
		inc	fsr
		inc	dd_addr
		cje	indf,#0,prtch3	; Text is less than 6 chars long
		cjb	fsr,#38,prtch4	; Print up to 6 chars
prtch3		ret
prtch1		cje	choice,#3,prtch5 ; First row
		add	choice,#3FH	; Second row
prtch5		add	choice,#11H	; Right side of display
		mov	dd_addr,choice
		mov	lcd_char,#1	; Right arrow (user char)
		call	printchar
		dec	dd_addr		; Space between
		sub	dd_addr,all_tmp	; Calculate start position
		jmp	prtch6		; Print text

; ********************* Print a text starting from dd_addr *******************

printtext	mov	fsr,#printbuf
prttxt1		mov	lcd_char,indf	; Get character
		call	printchar
		inc	fsr
		inc	dd_addr
		cjne	indf,#0,prttxt1	; Print up to 0 byte
		ret

; ********************** Display one character on the LCD ********************

printchar	clrb	RS		; Clear RS to send instruction
		mov	lcd_data,#80H	; DD RAM data
		or	lcd_data,dd_addr
		call	shake_LCD	; Transfer address
		setb	RS
		mov	lcd_data,lcd_char
		call	shake_LCD	; Send character
		ret

; ****** Check which key has been pressed, return 0 if none, 1..4 if any *****
		
getkey		mov	w,rd		; Get button port status
		and	w,#0EH		; Check buttons on port D
		mov	key,w		; Store
		movb	key.4,ra.4	; Add fourth key
		clrb	c
		rrf	key
		cje	key,#0,getk1	; No button
		mov	w,#1		; First button
getk2		rrf	key		; If wth key is pressed, carry is set
		jc	getk1		; Key found
		add	w,#1
		jmp	getk2		; Check next
getk1		mov	key,w		; Store button
		cjne	key,lastkey,getk3 ; Compare if same as lastkey
		jb	TMR_MODE,getk5 ; No check if in play mode
		cjb	pushed,#250,getk5 ; Wait longer - up to 500 ms
		sub	pushed,#50	; Put it back - repeat at 10 Hz
		ret			; Return pushed key
getk5		clr	key		; No key to avoid multiple detect
		ret
getk3		mov	lastkey,key	; New push or release
		jz	getk4		; Release
		clr	pushed		; Reset duration of push
getk4		ret

; ***************** Doing an A/D conversion on channel #0 *********************

ad_conv		clrb	GIE		; Disable interrupts
		clrb	c		; Clear carry
		mov	w,ADCON0	; Get control register
		and	w,#3		; Clear channel, clock select & GO
		or	w,#1		; Set clock to OSC/2, A/D on
		mov	ADCON0,w	; Write control register
		mov	all_tmp,#30
adc1		djnz	all_tmp,adc1	; Wait for sampling
		setb	GO_DONE		; Start conversion
adc2		jb	GO_DONE,adc2	; Wait for the end of conversion
		mov	ad_value,ADRES	; Read result
		setb	GIE		; Re-enable interrupts
		ret

; *********** Scroll in any (already printed) text from the right *************

fromright	clrb	RS
		mov	count1,#20
fromr1		mov	lcd_data,#24	; Shift from right
		call	shake_LCD
		mov	all_tmp,count1
		mov	all_tmp,#3
fromr2		djnz	lcd_data,fromr2	; Wait a while before next scroll
		djnz	count2,fromr2
		djnz	all_tmp,fromr2
		djnz	count1,fromr1	; Next scroll
		mov	count1,#20
fromr3		djnz	lcd_data,fromr3	; Freeze display to be read
		djnz	count2,fromr3
		djnz	count1,fromr3
		call	clear_display
		ret

; ************** Calculates the length of string in buffer *******************

strlen		mov	fsr,#printbuf	; Pointer to start of table
strlen2		cje	indf,#0,strlen1	; 0 character reached
		inc	fsr
		cjb	fsr,#printbuf+6,strlen2 ; Next byte
		retlw	6		; Full length
strlen1		sub	fsr,#32		; Calculate length
		mov	w,fsr
		ret

; ********************************* Tables ***********************************

		org	1792		; 700H

user_chars	jmp	pc+w 		; Table of data for custom characters
		retw	224,226,230,238,254,238,230,226	; Left arrow
		retw	224,232,236,238,239,238,236,232	; Right arrow

bar_chars	jmp	pc+w		; Bar meter characters
		retw	240,240,240,240,240,240,240,240	; 1 line
		retw	248,248,248,248,248,248,248,248 ; 2 lines
		retw	252,252,252,252,252,252,252,252 ; 3 lines
		retw	254,254,254,254,254,254,254,254 ; 4 lines
		retw	255,255,255,255,255,255,255,255 ; 5 lines

def_drums	jmp	pc+w
		retw	36,38,43,47,48,51,57,46, 0, 0, 0, 0,44,42

intr_1		jmp	pc+w		; Intro texts
		retw	'D','i','g','i','D','r','u','m','s',' '
		retw	'V','2','.','0', 0
intr_2		jmp	pc+w
		retw	'T','r','i','g',' '
		retw	't','o',' ','M','I','D','I', 0
intr_3		jmp	pc+w
		retw	'C','o','p','y','r','i','g','h','t',' '
		retw	'1','9','9','6', 0
intr_4		jmp	pc+w
		retw	'W','o','l','f','R','a','m',' '
		retw	'T','e','c','h','n','o','l','o'
		retw	'g','i','e','s', 0

not_used	retw	'T','h','i','s',' ','o','p','t','i','o','n',' ','i','s'
not_used2	retw	'n','o','t',' ','a','v','a','i','l','a','b','l','e'
		retw	' ',' ',' ',' ',' ',' ', 1 , 0

		org	2048		; 800H

curve1		jmp	pc+w		; Suppress at low volumes
		retw	      1,  1,  1,  2,  2,  2,  2 ; There is no zero!
		retw	  2,  3,  3,  3,  3,  3,  4,  4
		retw	  4,  4,  5,  5,  5,  5,  6,  6
		retw	  6,  7,  7,  7,  8,  8,  8,  9
		retw	  9, 10, 10, 10, 11, 11, 12, 12
		retw	 12, 13, 13, 14, 14, 15, 15, 16
		retw	 16, 17, 17, 18, 18, 19, 20, 20
		retw	 21, 21, 22, 23, 23, 24, 25, 25
		retw	 26, 27, 27, 28, 29, 29, 30, 31
		retw	 32, 33, 33, 34, 35, 36, 37, 38
		retw	 39, 40, 40, 41, 42, 43, 44, 45
		retw	 46, 48, 49, 50, 51, 52, 53, 54
		retw	 56, 57, 58, 59, 61, 62, 64, 65
		retw	 67, 68, 70, 71, 73, 75, 76, 78
		retw	 80, 82, 84, 86, 88, 91, 93, 96
		retw	 98,101,104,107,111,115,120,125

curve2		jmp	pc+w		; Emphasis at low volumes
		retw	      8, 13, 17, 21, 24, 27, 30
		retw	 32, 35, 37, 40, 42, 44, 46, 48
		retw	 50, 52, 53, 55, 57, 58, 60, 61
		retw	 63, 64, 66, 67, 69, 70, 71, 72
		retw	 74, 75, 76, 77, 78, 79, 80, 82
		retw	 83, 84, 85, 86, 87, 88, 88, 89
		retw	 90, 91, 92, 93, 94, 95, 95, 96
		retw	 97, 98, 99, 99,100,101,101,102
		retw	103,103,104,105,105,106,107,107
		retw	108,108,109,110,110,111,111,112
		retw	112,113,113,114,114,115,115,116
		retw	116,116,117,117,118,118,118,119
		retw	119,120,120,120,121,121,121,122
		retw	122,122,123,123,123,123,124,124
		retw	124,124,125,125,125,125,125,126
		retw	126,126,126,126,127,127,127,127

curve3		jmp	pc+w		; The "S" curve
		retw	      1,  2,  2,  2,  2,  2,  3
		retw	  3,  3,  3,  4,  4,  4,  5,  5
		retw	  6,  6,  6,  7,  7,  8,  8,  9
		retw	  9, 10, 10, 11, 11, 12, 13, 13
		retw	 14, 15, 15, 16, 17, 18, 19, 20
		retw	 21, 21, 22, 23, 25, 26, 27, 28
		retw	 29, 30, 32, 33, 35, 36, 38, 40
		retw	 42, 44, 46, 49, 51, 54, 58, 63
		retw	 65, 70, 74, 77, 79, 82, 84, 86
		retw	 88, 90, 92, 93, 95, 96, 98, 99
		retw	100,101,102,103,105,106,107,107
		retw	108,109,110,111,112,113,113,114
		retw	115,115,116,117,117,118,118,119
		retw	119,120,120,121,121,122,122,122
		retw	123,123,124,124,124,125,125,125
		retw	125,126,126,126,126,126,127,127

decay		jmp	pc+w		; Piezo threshold decay
		retw	128,128,128,118,115,112,109,106
		retw	104,101, 98, 96, 93, 91, 89, 86
		retw	 84, 82, 80, 78, 76, 74, 72, 70
		retw	 68, 66, 64, 63, 61, 59, 58, 56
		retw	 55, 53, 52, 51, 49, 48, 47, 45
		retw	 44, 43, 42, 41, 39, 38, 37, 36
		retw	 35, 34, 33, 32, 31, 31, 30, 29
		retw	 28, 27, 26, 26, 25, 24, 24, 23
		retw	 22, 21, 21, 20, 20, 19, 18, 18
		retw	 17, 17, 16, 16, 15, 15, 14, 14
		retw	 13, 13, 12, 12, 12, 11, 11, 10
		retw	 10, 10,  9,  9,  9,  8,  8,  8
		retw	  7,  7,  7,  7,  6,  6,  6,  6
		retw	  5,  5,  5,  5,  4,  4,  4,  4
		retw	  4,  3,  3,  3,  3,  3,  2,  2
		retw	  2,  2,  2,  2,  1,  1,  0

		org	1664		; 680H

menutxt		jmp	pc+w		; Table of menu choices

		retw	'S','e','t','u','p', 0
		retw	'S','e','t','s', 0 , 0
		retw	'P','C',' ','u','s','e'

		retw	'S','e','l','e','c','t'
		retw	'N','a','m','e', 0 , 0
		retw	'B','a','s','i','c', 0

		retw	'D','r','u','m','s', 0
		retw	'L','e','v','e','l','s'

		retw	'N','o','i','s','e', 0
		retw	'X','t','a','l','k', 0
		retw	'D','e','c','a','y', 0

		retw	'[','-',']', 0 , 0 , 0
		retw	'[','+',']', 0 , 0 , 0
		retw	'O','K', 0 , 0 , 0 , 0
