$MOD51
$nodebug
;		       ***-------------------------***
;       	       ***   Date:  July 14,2001   ***
;		       ***   -------------------   ***
;		       ***   Midi Discipline DAC   ***
;		       ***-------------------------***

;	*** Revisions ***************************************************
;	 3 June  2003	Fit all Midi Service Routines into FRAME_MIDI
;	28 April 2003	Sped up MPL invoking 1 MIDI Message per loop rule
;	27 April 2003	Cleaned out dead code, optimized LFO/Noise.
;	19 April 2003	Buffered Incoming rx MIDI data. Midi Framing 
;			moved to the foreground 
;	12 April 2003 	Note On with zero velocity sets NOTE OFF FLAG,
;		      	Active Sense msg dropped prior to msg handling.
;		      ***************************************************
	
;	Originated to Drive a Radio Shack MG-1 synthesizer this Midi Note to Control
;	voltage convertor provides:

;		1) A standard "1 Volt per Octave" (trimmed) oscillator control 
;		   voltage with a positive 5 Volt Trigger, Gate and S-TRIGGER.
;		2) The S-TRIGGER can originate from the GATE or STROBE allowing
;		   a retriggerable S-TRIG.
;		3) Dynamic Trigger Modes:
; 			a) Velocity Width 
;			b) Velocity Delay

;	Velocity Width Mode causes the TRIGGER pulse duration to widen with
;	decreasing Velocity Values.  Velocity Delay causes the leading edg
;	of the Trigger to delay (from the onset of the GATE signal) in
;	correlation to the Velocity. Increasing Velocity Values cause the 
;	delay to occur closer to the GATE activation. Conversely, decreasing
;	values result in a correlating delay of the leading edge of the TRIGGER
;	referenced to the onset of the GATE Pulse.
;	
;	A Gate Signal is asserted as long as any key is sounding. The TRIGGER
;	re-occurs with every new note.

;	Conversion:

;	Two 8 Bit Digital to Analog Convertors drive a "weighted" Summing 
;	Amplifier producing a Frequency Control Voltage for an Analog Synthesizer.  

;		DAC 1:  Performs direct conversion from 7 bit MIDI Note values by
;			left shifting to the 8 bit DAC and allowing the LSB to be 
;			provided algothmically (for LSB correction).
;		DAC 2:	Offsets the 8 bit note value for Tuning, Low Fz Modulation,
;			Random Noise generation, and Pitch Bend MIDI messages.
;			
;	Modulation Sources: 

;	Low Frequency Oscillator:  A 24 bit Phase and Increment Accumulator generates 
;	an index into a 256 entry 8 bit Sine Table. The sine value is multiplied by an 8 bit
;	amplitude variable. The result is added to the modulation accumulator and
;	output to the Modulation DAC. The Amplitude is either fixed or "enveloped"

;	Random Noise: 

;	Channel and Assignment Selection:

;	MONO Mode - Output either the lowest or highest note on an individual 
;		    channel to the FZ DAC.
;	OMNI Mode - Listens to all Midi Channels and outputs the lowest or
;	            highest note to the FZ DAC.
;	
;	Note Table 	0 [Note Value0] [Velocity0]  Highest Note Playing
;			1 [Note Value1] [Velocity1]
;			2 [Note Value2] [Velocity2]
;			3 [Note Value3] [Velocity3]
;			4 [Note Value4] [Velocity4]
;			5 [Note Value5] [Velocity5]
;			6 [Note Value6] [Velocity6]
;			7 [Note Value7] [Velocity7]  Lowest Note Playing

;	*** Valid CC Controller Addresses ***		
;		dec  hex	function
;		------------------------
;		120		Quiet voices
;		92		Noise Depth
;		77   4DH	Vibrato Depth
;		76		Vibrato Frequency

;	***************************************************
;	*** Internal 128 byte SRAM Memory Usage (0-7FH) ***
;	***************************************************

;10H  [mfifo ] [mfifo ] [mfifo ] [mfifo ] [mfifo ] [mfifo ] [mfifo ] [mfifo ]
;18H  [mfifo ] [mfifo ] [mfifo ] [mfifo ] [mfifo ] [mfifo ] [mfifo ] [mfifo ]

;	*** 16 byte MIDI Data Receive Buffer ***

midi_fifo	equ	10H
fifo_size	equ	10H

midi_fifo0	data	10H
midi_fifo1	data	11H		
midi_fifo2	data	12H		
midi_fifo3	data	13H		
midi_fifo4	data	14H		
midi_fifo5	data	15H		
midi_fifo6	data	16H		
midi_fifo7	data	17H		
midi_fifo8	data	18H		
midi_fifo9	data	19H		
midi_fifo10	data	1AH		
midi_fifo11	data	1BH		
midi_fifo12	data	1CH		
midi_fifo13	data	1DH		
midi_fifo14	data	1EH		
midi_fifo15	data	1FH		

;       *******************
;       *** State Flags ***
;       *******************

;20H  [txflag] [Signal] [lastlo] [lasthi] [txflag] [noteon] [      ] [      ]
; .1  [rstat ] [notoff] [      ] [      ]
; .2  [ omni ] [ctlflg] [      ] [      ]
; .3  [cmd_in] [pgmflg] [      ] [      ]
; .4  [cmsync] [bndflg] [      ] [      ]
; .5  [vlmode] [modflg] [      ] [      ]
; .6  [assign] [fmflag] [      ] [      ]
; .7  [mono  ] [upd_rq] [lstlof] [lsthif]

txflag		bit	20h.0
running_status	bit	20h.1		;Set when we have a supported midi command
omni		bit	20h.2		;Set when we ignore midi channel addressing
cmd_in		bit	20h.3
cmd_sync	bit	20h.4
velocity_mode	bit	20h.5
assignment_flag	bit	20h.6
mono		bit	20h.7

;	*** Process Flags ***

process_flags	data	21H
mod_update	bit	21H.0
update_request	bit	21H.1
lfo_flag	bit	21H.2
noise_flag	bit	21H.3
nrpn_flag	bit	21H.4


gate_flag	bit	22H.0
random_bit	bit	22H.1


last_hi_note	data	23H

;24H  [Note00] [velo00] [Note01] [velo01] [Note02] [velo02] [Note03] [velo03]
;38H  [Note04] [velo04] [Note05] [velo05] [Note06] [velo06] [Note07] [velo07] 
;40H  [mout00] [mout01] [mout02] [mout03] [mout04] [mout05] [mout06] [mout07]

bend_lsb	data	24H
bend_lsbit	bit	24H.6

;	*** Midi Message Framing Variables ***

mcmd_pointer	data	28H
mout_status	data	29H
midi_out_ptr	data	2AH
midi_in_ptr	data	2BH
status_raw	data	2CH

;	*** MIDI Framing Buffer ***

midi_status	data	2DH		;Midi byte one "Status" byte
midi_data1	data	2EH		;          two "spec" byte
midi_data2	data	2FH		;          three "value" byte

;	*** MIDI Note Buffer ***

midi_note0	data	30H
midi_velocity0	data	31H
midi_note1	data	32H
midi_velocity1	data	33H
midi_note2	data	34H
midi_velocity2	data	35H
midi_note3	data	36H
midi_velocity3	data	37H
midi_note4	data	38H
midi_velocity4	data	39H
midi_note5	data	3AH
midi_velocity5	data	3BH
midi_note6	data	3CH
midi_velocity6	data	3DH
midi_note7	data	3EH
midi_velocity7	data	3FH

trigger_width	data	40H	
lfo_byte	data	41H
channel_spec	data	42H		;Channel of Interest
velo_reload	data	43H		;Lo counter value for velocity mode
lfo_phase0	data	44H
lfo_phase1	data	45H
lfo_phase2	data	46H
lfo_inc0	data	47H
lfo_inc1	data	48H
lfo_inc2	data	49H
lfo_mult	data	4AH
bend_byte	data	4BH
tune_byte	data	4CH

;	Random Noise Gen. Variables ***

random_index	data	4DH
seed		data	4DH
noise_counter	data	4EH
noise_mult	data	4FH
noise_byte	data	50H

;	*** Misc Counters ***

sequence_reload	data	51H
sequence_count	data	52H
process_count	data	53H
frame_count	data	54H
			  
lfo_counter	data	55H
status_type	data	56H

noise_count	equ	7
lfo_count	equ	3

;	*** Buffers and Structs ***


bitaddr_ram	equ	20H
note_table	equ	30H
note_table_last	equ	3EH
stack_base	equ	60H-1		;32 location stack

;	*** I/O Aliases ***

cmd_button	equ	P3.2			;Positive Going, At Gate Edge Strobe
fz_dac		equ	P3.3
mod_dac		equ	P3.4
dac_buss	equ	P1
trigger		equ	p3.5
gate		equ	P3.7

;	*** Midi "status" Commands ***

note_off	equ	080H
note_on		equ	090H 
after_touch	equ	0A0H
control_change	equ	0B0H
program_change	equ	0C0H
after_touch1	equ	0D0H
pitch_bend	equ	0E0H
channel_mode	equ	0F0H

sustain_pedal	equ	040H
midi_sysex	equ	0F0H
midi_clock	equ	0F8H
active_sense	equ	0FEH			;The MIDI "i am alive" link Message
midi_reset	equ	0FFH

;	*** General Midi Constants ***

channel_mask	equ	0FH
mptr_in		equ	8
mptr_out	equ	9

;	*** New Register Bank 1 Aliases ***

midi_inptr	equ	r0
midi_outptr	equ	r1


alt_regs	equ	8

;	*** DAC Constants ***

dac_midpoint	equ	80H

$eject

;             ******  **  **   ****   ******  *****   **
;               **    **  **  **  **    **    **  **  **
;               **    **  **  **        **    *****   **
;               **     *  *   **  **    **    **  **  **
;             ******    **     ****     **    *****   ******

;       *** Here are all the interrupt entry points for the Atmel ***
;       *** 89C2051. This code is also suited for a generic 8051. ***

		ORG     0       

       	sjmp	ipl
				

		ORG     3
Ex_int0:
	reti

		ORG     0BH
Timer0:
	jmp	trigger_interrupt


		ORG     13H
Ex_int1:
	reti

		ORG     1BH
Timer1:
	reti


		ORG     23H
Serial:
	jmp	midi_interrupt

;                   ******   *****    **
;                     **     **  **   **
;                     **     *****    **
;                     **     **       **
;                   ******   **       ******    

		ORG	40H

;                 *** Initial Program Loop ***

ipl:
	mov     sp,#stack_base	;Init ... the stack pointer
	call    init            ;     ... all hardware to nominal state
	setb	txflag		;     ... the Transmit Ready Status.
	setb    ea             	;Set  ... Global interrupts to enable

$eject

;                 **   **  *****   **      
;                 *** ***  **  **  **      
;                 ** * **  *****   **      
;                 **   **  **      **      
;                 **   **  **      ******

;	    *************************************
;	    *** MDD Dispatcher Loop	      ***
;	    *** Round Robin check of Service  ***
;	    *** Flags and Update requirements ***
;	    *************************************

;	*** Routines are free to use any register ***
;	*** or foreground variable context free.  ***

midi_test:
	mov	a,mptr_in			;Test for available MIDI byte
	xrl	a,mptr_out
	jz	service_loop			;Branch if no waiting data

;	***-----------------------------------------***
;	*** We have a received serial MIDI byte.    ***
;	*** Byte by byte accumulate a MIDI message. ***
;	*** Once a full MIDI message is framed, set ***
;	*** Status specific service request flags.  ***
;	***-----------------------------------------***

	call	frame_midi			;accept rx byte into message frame
						; Signal New Messages.

;	***------------------------------------------***
;	*** Test for Update Requirement for NOTE DAC ***
;	***------------------------------------------***

service_loop:

	jnb	update_request,mtest_noise	;Branch if no request for DAC/Midi Update
	call	vox_update			; Else assign note to FzDAC/MIDI
	jmp	mtest_noise

;	***------------------------***
;	*** Test NOISE enable flag ***
;	***------------------------***

mtest_noise:
	jnb	noise_flag,mtest_lfo		;Branch if Noise Generation is disabled
	call	random				; Else Generate and Update Mod DAC with Random value

;	***----------------------***
;	*** Test LFO enable flag ***
;	***----------------------***

mtest_lfo:
	jnb	lfo_flag,mtest_mod_update	;Branch if Low Fz Osc. is disabled
	call	lfo				; Else Generate and update Mod DAC with LF sample

;	***------------------------------------------------***
;	*** Test for Update Requirement for Modulation DAC ***
;	***------------------------------------------------***

mtest_mod_update:
	jnb	mod_update,midi_test

;	*** Modulation Update ***

	call	mdac_write

;	***--------------------***
;	*** Restart Dispatcher ***
;	***--------------------***

	jmp	midi_test	 		;Restart Loop from the top

$eject

;    	   ****************************************
;	   *** Individual Midi Foreground Tasks ***
;	   ****************************************

;	**  **   ****   ******  ******
;	*** **  **  **    **    **    
;	******  **  **    **    ***** 
;	** ***  **  **    **    **    
;	**  **   ****     **    ******

;	 ****   ******  ******
;	**  **  **      **    
;	**  **  *****   ***** 
;	**  **  **      **    
;	 ****   **      **     


midi_noteoff:
	mov	r1,#note_table			;Init .. pointer to Current Highest Note
	mov	sequence_count,#alt_regs		;     .. # of Note Table entries

;	*** Test for current note table location being OFF ***

voff_test:
	mov	a,@r1				;Fetch Current table note
	jnb	acc.7,voff_on			; Branch if ON

;	*** This note isn't on ***

voff_bump:
	inc	r1				;Bump past current Note and velocity Value
	inc	r1
	djnz	sequence_count,voff_test	;If all notes tested .. exit

;	*** ReInit Data Byte Count and Frame Pointer for Running Status ***

running_status_out:
	mov	sequence_count,sequence_reload
	mov	r0,#midi_data1
	ret

;	***-----------------------------***
;	*** This note is currently "on" ***
;	***-----------------------------***

voff_on:
	cjne	a,midi_data1,voff_bump		;Compare New Note with Table entry

;	*** Notes are equal so this ***
;	*** is the note to turn off ***

	orl	a,#80H				;Set note's OFF flag
	mov	@r1,a				; Store as OFf into the table
	mov	a,r1				;  Save this location pointer for
	mov	r0,a				;   coming table "hole check"
	inc	r1				;    Point to notes velocity
	mov	a,midi_data2			;     Fetch the new velocity
	mov	@r1,a				;      Store velocity
	setb	update_request			;       Indicate a Note Assignment was made
	inc	r1				;        Point to next table note

;	*** This table location has an OFF Note, test ***
;	*** to see if we should close the hole left   *** 
;	*** in the table by this OFF note             ***

	dec	sequence_count			;decrement number of table entries

;	*** Test for a note on is current table location ***

voff_ok:
	mov	a,@r1				;Fetch next table note value
	jnb	acc.7,voff_mov			; Branch if an ON Byte 

;	*** This byte is also OFF ***

	inc	r1
	inc	r1
	djnz	sequence_count,voff_ok
	jmp	running_status_out
	
;	*** This note is ON, move it up ***

voff_mov:
	mov	a,@r1			;Fetch the tabled note value
	mov	@r0,a			; Store into previous (just turned off)
	mov	@r1,#080H		;  table entry location
	inc	r1			;   Bump .. Source Pointer to velocity 
	inc	r0			;    	 .. Destination Pointer to velocity
	mov	a,@r1			;    Fetch the tabled velocity value
	mov	@r0,a			;     Store into previous (just turned off)
	mov	@r1,#40H		;      table entry location

	inc	r1			;Point to .. next table note value
	inc	r0			; 	  .. previous note value
	djnz	sequence_count,voff_ok
	jmp	running_status_out

;	**  **   ****   ******  ******
;	*** **  **  **    **    **    
;	******  **  **    **    ***** 
;	** ***  **  **    **    **    
;	**  **   ****     **    ******

;	 ****   **  **
;	**  **  **  **
;	**  **  *** **
;	**  **  ** ***
;	 ****   **  **

midi_noteon:
	mov	a,midi_data2		;Test for Velocity Value of Zero
	jnz	midi_on
	jmp	midi_noteoff

;	*** We have a non-zero NOTE On Message ***

midi_on:
	mov	r1,#note_table		;Init .. Note Table Entry Pointer
	mov	process_count,#8	;     .. # of Note Table entries

;	*** Insert New Note into the Note Table ***

assign_test:
	mov	a,@r1			;Fetch Tabled Note Value
	jnb	acc.7,weigh_note	; Branch if ON Note to magnitude eval

;	*** Table note is off ***
;	*** We can replace it ***
 
	mov	@r1,midi_data1		;Store new note value 
	inc	r1			; Point to the velocity entry
	mov	@r1,midi_data2		;  Store the new velocity
	setb	update_request		;   Indicate a new note in for Dac or Midi
	jmp	running_status_out

;	***--------------------------***
;	*** Evaluate the New against ***
;	*** the Tabled Note value    ***
;	***--------------------------***

weigh_note:
	cjne	a,midi_data1,note_sign

;	*** The Table note and New Note are the same ***

	jmp	running_status_out

;	*** Is table note less than new note ? *** 

note_sign:
	jc	assign_note		; If Table note is less, branch 

;	*** Tabled note is greater ***

	inc	r1
	inc	r1
	djnz	process_count,assign_test

;	*** All notes were greater than new note     ***
;	*** this means it's the lowest note sounding ***

	mov	midi_note7,midi_data1
	mov	midi_velocity7,midi_data2
	setb	update_request
	jmp	running_status_out

;	*** The new note is greater in ***
;	*** value than the tabled note ***

assign_note:
	mov	a,sequence_count
	cjne	a,#1,go_assign
	setb	update_request
	jmp	running_status_out

go_assign:
	mov	a,@r1			;Fetch the tabled note
	xch	a,midi_data1		; Store as New Note accumulator
	mov	@r1,a			;  Store previous New Note into table
	inc	r1			;   Point to velocity entry location
	
;	*** Store in the new Velocity ***

	mov	a,@r1			;Fetch the tabled velocity
	xch	a,midi_data2		; Store prev. table velo into new velo
	mov	@r1,a			;  Store prev New Note into Table
	setb	update_request

	inc	r1
	djnz	process_count,assign_test
	jmp	running_status_out

;	**  **   ****   **  **
;	**  **  **  **   **** 
;	**  **  **  **    **  
;	 ****   **  **   **** 
;	  **     ****   **  **

;	**  **  *****   *****    ****   ******  ******
;	**  **  **  **  **  **  **  **    **    **    
;	**  **  *****   **  **  ******    **    ***** 
;	**  **  **      **  **  **  **    **    **     
;	 ****   **      *****   **  **    **    ******

vox_update:
	clr	update_request			;Reset the Update Rq Flag

	mov	a,midi_note0			;Fetch table hi note
	cjne	a,last_hi_note,vox_it
vox_it:
	mov	last_hi_note,a
	call	write_fz
	ret

;	 *****   ****   **  **  ******  *****    ****   **     
;	**      **  **  *** **    **    **  **  **  **  **    
;	**      **  **  ******    **    *****   **  **  **     
;	**      **  **  ** ***    **    ** **   **  **  **    
;	 *****   ****   **  **    **    **  **   ****   ******

;	***------------------------------***
;	*** MIDI Device Parameter Update ***
;	***------------------------------***

control_set:
	mov	a,midi_data1			; Fetch the Device Parameter Number

;	***------------------------------------------***
;	*** Test Parameter Address for All Sound Off ***
;	***------------------------------------------***

	cjne	a,#120,control_check		;Test the boundry between Mode & Control
	jmp	quiet_both			; It's the Sound off command

;	*** Determine if greater (Channel Mode) or   ***
;	*** lesser (Parameter Address Specification) ***

control_check:	
	jnc	chan_mode			;If not less it's a "mode" msg

;	***--------------------***
;	*** Test for LFO Depth ***
;	***--------------------***

	cjne	a,#77,control_lfo_fz		;Test for Vibrato Depth

;	*** We have the Depth of Vibrato ***

	mov	lfo_mult,midi_data2		;Fetch The MODULATION DEPTH
	jmp	control_out


;	*** LFO Fz ***

control_lfo_fz:
	cjne	a,#76,control_noise  		;Test for Sound Controller 7

;	*** We have Vibrato Freq ***
	
	mov	a,midi_data2			;Set the LFO Frequency
	jnz	go_lfz

;	*** Eliminate zero value (no freq. increment) ***

	inc	a

go_lfz:
	mov	lfo_inc1,a
	jmp	control_out

;	*** Noise Depth ***

control_noise:
	cjne	a,#92,control_pedal		;Test for EFFECT control 2
	
;	*** We have Noise Depth ***

	mov	noise_mult,midi_data2		;Store the Noise Depth 
	jmp	control_out

;	*** Check for the Sustain Pedal ***

control_pedal:
	cjne	a,#sustain_pedal,control_wheel	;Test for a "pedal" activation

;	*** Sustain Pedal ***

	mov	a,midi_data2			;Fetch the % of modulation
	mov	c,acc.6				; Fetch the "direction" on/off
	mov	lfo_flag,c			;  Store into LFO enable flag
	mov	noise_flag,c			;   into Noise Modulation enable
	jnc	pedal_off			;    Exit if "Pedal On" 
	ret

;	*** Pedal Off: Reset Mod dac to zero ***

pedal_off:
	mov	noise_byte,#0
	mov	lfo_byte,#0
	mov	lfo_phase0,#0
	mov	lfo_phase1,#0
	mov	lfo_phase2,#0
	setb	mod_update
	ret


;	*** Modulation Wheel: Copy into Amplitude regs (noise,lfo) ***

control_wheel:
	cjne	a,#1,nrpn_msb			;Test for Modulation Wheel

;	*** It's the Modulation Wheel ***

	mov	a,midi_data2			;Fetch The MODULATION DEPTH
	mov	lfo_mult,a			; Store if 
	mov	noise_mult,a
	ret

nrpn_msb:
	cjne	a,#99,nrpn_lsb

;	*** Check the nrpn Hi Address for 127 ***

	mov	a,midi_data2
	cjne	a,#7FH,clear_control

;	*** We have a valid NRPN HI address ***

	setb	nrpn_flag
	ret

nrpn_lsb:
	cjne	a,#98,control_velocity

;	*** Check the NRPN Lo Address ***

	mov	a,midi_data2
	cjne	a,#1,clear_control		;Check for LFO fz
	ret

clear_control:
	clr	nrpn_flag
	ret

;	***----------------------***
;	*** Velocity Mode Select ***
;	***----------------------***

control_velocity:
	cjne	a,#80,control_gate

	mov	a,midi_data2
	mov	c,acc.6
	mov	velocity_mode,c
	ret

control_gate:
	cjne	a,#81,control_out

	mov	a,midi_data2
	mov	c,acc.6
	mov	gate_flag,c

control_out:
    	ret

;	***-------------------------***
;	*** Channel Mode Processing ***
;	***-------------------------***

chan_mode:
	cjne	a,#121,all_notes_off

;	*** It's Reset all Controllers Command ***

	mov	bend_byte,#0
	mov	lfo_byte,#0
	mov	noise_byte,#0
	ret

;	*** Test for the all notes off Command ***

all_notes_off:
	cjne	a,#123,omni_off

;	*** It's the all notes off command ***

quiet_both:
	mov	r0,#midi_note0
	mov	r2,#8
off_notes:
	mov	@r0,#80H
	inc	r0
	inc	r0
	djnz	r2,off_notes	
	setb	update_request
	ret

;	*** The for the OMNI (free channel spec) OFF ***

omni_off:
	cjne	a,#124,omni_on
	clr	omni
	ret

omni_on:
	cjne	a,#125,mono_on
	setb	omni
	ret

mono_on:
	cjne	a,#126,poly_on
	setb	mono
	ret

poly_on:
	cjne	a,#127,control_exit
	clr	mono
control_exit:
	ret


bend_set:
	mov	bend_lsb,midi_data1
	mov	a,midi_data2		;Fetch .. un-adjusted 7 bit MIDI top value
	mov	c,bend_lsbit		;      .. hi bit of un-adjusted 7 bit low value
	rlc	a			;Rotate to create an offset binary 8 bit value

;	*** Now adjust "direction" of bend ***

	cpl	a

;	*** Bend is now an 8 bit offset binary value ***

	jb	acc.7,hi_bend

;	*** This is a negative Bend ***

	mov	r2,#dac_midpoint
	xch	a,r2
	clr	c
	subb	a,r2
	cpl	a
	inc	a
	jmp	bend_out

;	*** This is a positive bend ***

hi_bend:
	clr	acc.7
bend_out:
	mov	bend_byte,a		;Store for compositing with other modulation sources
	setb	mod_update		; Indicate Modulation Update Required
	ret				


;	***----------------------------------------------***
;	*** Generate a sine wave fz modulation wave form ***
;	***----------------------------------------------***

lfo:
	djnz	lfo_counter,lfo_out		;Branch is pre-scaler not exhausted
	mov	lfo_counter,#lfo_count		; Reload Pre-Scaler Counter

;	*** Compute a Sine Table Index ***	

	mov	a,lfo_phase0
	add	a,lfo_inc0
	mov	lfo_phase0,a

	mov	a,lfo_phase1
	addc	a,lfo_inc1
	mov	lfo_phase1,a

	mov	a,lfo_phase2
	addc	a,lfo_inc2
	mov	lfo_phase2,a
			    
;	*** Fetch Table Sample ***

	mov	dptr,#sine_table
	movc	a,@a+dptr

;	*** Adjust Amplitude of Sample ***

	jb	acc.7,pos_mult

;	*** Generate the absolute value of this negative offset ***

	mov	r2,#dac_midpoint
	xch	a,r2
	clr	c
	subb	a,r2

;	*** Scale the absolute value ***

	mov	b,lfo_mult
	mul	ab

;	*** Offset Negatively from the center point ***
;	*** equal to the scaled table sample.       ***

	clr	c
	clr	a
	subb	a,b
	jmp	go_fm

;	*** Scale this positive offset ***

pos_mult:
	clr	acc.7

;	*** Multiply the positive offset ***

	mov	b,lfo_mult		; Fetch scale value
	mul	ab			;  Scale positive offset
	mov	a,b
go_fm:
	mov	lfo_byte,a
	setb	mod_update
lfo_out:
	ret

$eject			

;			***--------------------***
;			*** Interrupt Routines ***
;			***--------------------***

;	**   **   ****   *****    **** 
;	*** ***    **    **  **    **  
;	*******    **    **  **    **  
;	** * **    **    **  **    **  
;	**   **   ****   *****    ****  

;	 ****   **  **
;	  **    *** **
;	  **    ******
;	  **    ** ***
;        ****   **  **

midi_interrupt:

;	*** Test for Serial Transmit Buffer Empty interrupt ***
 
	jnb	ti,midi_rxcheck		;Branch if no ongoing "Tx empty" interrupt

;	*** There is a Transmit Interrupt ***

	clr	ti			;Clear the Transmit Interrupt
	setb	txflag			; Indicate "Transmit Ready"

;	*** Test for Serial Receive Byte Available ***

midi_rxcheck:
	jbc	ri,midi_rx 		;If we have a "Receive" Interrupt, branch

;	*** We have no existing interrupt condition ***

	reti

;	***--------------------------------***
;	*** We have a waiting MIDI Rx byte ***
;	***--------------------------------***

midi_rx:
	push	psw					;Save flags and reg bank
	mov	psw,#alt_regs					;Select alternate 8051 reg set

	mov	@midi_inptr,sbuf			;Store byte into Rx Buffer
	inc	r0					; Bump input pointer
	cjne	r0,#midi_fifo+fifo_size,midi_rxout 	;  Test for buffer top limit

;	*** Midi input pointer must rollover ***

	mov	r0,#midi_fifo0				;Reset input pointer to buffer base

;	*** Exit from Midi input interrupts ***

midi_rxout:
	pop	psw
	reti

$eject

;	*** Valid Status Bytes begin "Message Framing".  The Status "type" ***
;	*** indicates how many following "data" values should be received  ***
;	*** prior to signalling the foreground that we have a MIDI message ***

;	*** MIDI controllers can issue messages in a "Running Status" mode ***
;	*** where only data bytes issue.  The Status for this type of msg. ***
;	*** is that "last received". To complicate things System Real-Time ***
;	*** message can interleave anywhere within a MIDI message and are  ***
;	*** not intended to change the state of message framing.           ***

;	*** The following message framing interrupt branches into two      ***
;	*** directions depending upon whether the incoming byte is a data  ***
;	*** or Status byte. System Real Time Status bytes are ignored.     ***

;	*** At this time only NOTE ON, NOTE OFF, CONTROL CHANGE, PROGRAM   ***
;	*** CHANGE, and PITCH BEND message are supported.                  ***

;			****************************
;			*** MIDI Message Framing ***
;			****************************

frame_midi:
	push	psw 				;Save standard registers
	mov	psw,#alt_regs				; Select Alt "Service" Regs

	mov	a,@r1				;Fetch the waiting byte
	inc	r1     				; Bump output pointer

;	*** Pointer Management ***

	cjne	r1,#bitaddr_ram,go_mbyte	;Branch if pointer below max value
	mov	r1,#midi_fifo			; Else re-init pointer to base value


;	*** Restore Working Registers and Frame ***

go_mbyte:
	pop	psw				;Restore Std Registers

;	***--------------------------------------***
;	*** Test latest incoming  midi data byte ***
;	*** Is it a "status" or "data" byte ?    ***
;	***--------------------------------------***

	jnb	acc.7,data_eval			;Branch if D7=0, its a Data Byte

;	***------------------------------------------***
;	*** We have just received a MIDI STATUS BYTE ***
;	***------------------------------------------***

;	 *****  ******   ****   *******  **  **   *****
;	**        **    **  **    **     **  **  **     
;	 ****     **    ******    **     **  **   **** 
;	    **    **    **  **    **     **  **      **
;	*****     **    **  **    **      ****   ***** 

;	*** Perform a series of Checks for System Real-time & ***
;	*** System Common messages, few of which we support   ***

	cjne	a,#midi_clock,common_test	;Test against 1st Realtime Status Type
	
;	*** It's a MIDI clock, we don't support it ***
;	*** exit and do not disturb running status ***

realtime_exit:
	ret

;	*** Separate any Realtime Status Types ***

common_test:
	jc	frame_sysex			;Branch if value less than SysEx

;	*** This is some kind of Realtime Status ***

	cjne	a,#midi_reset,realtime_exit	;Exit if not midi reset status

;	*** It's a MIDI Reset Status Type ***
;	*** which we do support so reset  ***

	jmp	0 				;Restart Control Program

;	***---------------------------------------------***
;	*** It's not a Real-time test for System Common ***
;	***---------------------------------------------***

frame_sysex:
	cjne	a,#midi_sysex,midi_framer	;Branch if not Sysex

;	***--------------------------------------***
;	*** It's a SysEx which we do not support ***
;	*** Clear running status and exit        ***
;	***--------------------------------------***

common_exit:
	clr	cmd_sync			;Disable Running Status
	ret					; Exit Framing

;	*** Separate out any System Common ***

midi_framer:
	jnc	common_exit			;exit if any form of System Common Status

;	***-------------------------------***
;	*** This is a Channel Status Type ***
;	***-------------------------------***

	mov	status_raw,a			;Save MIDI Status byte
	swap	a				; Place Status "type" into low nibble
	anl	a,#7				;  Mask off all but "type" number
	mov	status_type,a			;   Save for general Status Type Testing

;	***----- Validate MIDI Channel -----***
;	*** Determine if this Status Byte   ***
;	*** is "on" our channel of interest ***
;	***---------------------------------***

	jb	omni,go_command			;If in "OMNI Mode", accept
						; messages on all channels

;	*** Not in "Omni Mode", check Channel ***

	mov	a,status_raw			;Save the MIDI Status byte
	anl	a,#0FH				; Mask off the message type (leaving channel)	
	xrl	a,channel_spec			;  Test for equal to our channel of interest
	jz	go_command			;   Branch if message is on our channel.

;	*** Not on our Channel, ignore    ***
;	*** & clear "Running Status" mode ***

	clr	cmd_sync		  
	ret

;	******  **  **   ****   **    
;	**      **  **  **  **  **    
;	*****   **  **  ******  **    
;	**       ****   **  **  **     
;	******    **    **  **  ******

;	***--------------------------------------***
;	*** Test for supported MIDI Status Types ***
;	***--------------------------------------***

go_command:
	mov	a,status_type			;Restore the MIDI Status Type

;	*** We have already eliminated the Real-Time msg ***
;	*** type, only 7 message types remain 2 of which ***
;	*** require only 1 companion data byte.	         ***

	cjne	a,#4,cmd_test_pressure		;Test for "Program Change"

;	*** We have a "Program Change" Message ***

	mov	a,#1				;Indicate a single companion 
						; data byte is needed for this message
	jmp	sequence_store			;  Branch to command finish

;	*** Test for After Touch ***

cmd_test_pressure:
	cjne	a,#5,command_2byte		;Branch if not "after-touch"

;	*** We have an "Channel Pressure" message ***

	mov	a,#1				;Indicate a single companion 
	jmp	sequence_store			; data byte is needed for this message
						;  Branch to command finish

;	*** Load generic Status Message length ***

command_2byte:
	mov	a,#2				;Init two data bytes required for this message

sequence_store:
	mov	frame_count,a			;Store # of needed data bytes into
	mov	sequence_reload,a		; message data length counter and
	mov	mcmd_pointer,#midi_data1	;  "Running Status" reload register

;	***--------------------------------***
;	*** Start framing the MIDI message ***
;	***--------------------------------***

	mov	midi_status,status_raw		;Fetch and Store the incoming
	setb	cmd_sync			; Indicate we are assembling a command sequence
	ret
$eject
;	*****    ****   ******   **** 
;	**  **  **  **    **    **  **
;	**  **  ******    **    ******
;	**  **  **  **    **    **  **
;	*****   **  **    **    **  **

data_eval:
	jb	cmd_sync,data_processing	;If we have a "Running Status"
						; condition, process the data bytes

;	*** We are not in the "Running Status" state ***
;	*** This data byte is invalid, so we exit    ***

data_eval_out:
	ret

;	*** We have a midi status parameter ***

data_processing:
	mov	r0,mcmd_pointer			;Fetch Midi Frame Pointer 
	mov	@r0,a				; Store the MIDI Data Byte
	inc	r0				;  Bump Pointer to the next entry location
	mov	mcmd_pointer,r0			;   Store as Current Data Byte Buffer Index

;	*** Frame Counter Management ***

	djnz	frame_count,data_eval_out	;Test Status Data Byte Counter
						; If all bytes required by Status received,
						;  Branch to Message Interpreter

;	*** Reload Sequence Counter for running status ***

	mov	a,sequence_reload		;Store # of needed data bytes into
	mov	frame_count,a			; message data length counter and
	mov	mcmd_pointer,#midi_data1	;  "Running Status" reload register
	
;	 *****  **   **  *****      ******  **  **   ****   **     
;	**      *** ***  **  **     **      **  **  **  **  **    
;	**      *******  **  **     *****   **  **  ******  **    
;	**      ** * **  **  **     **       ****   **  **  **    
;	 *****  **   **  *****      ******    **    **  **  ******

;	***---------------------------------------***
;	*** A complete Midi Command Message is in ***
;	***---------------------------------------***

	mov	a,status_type			;Fetch Midi Status "Type"
	rl	a				; Shift left to compose jump index
	add	a,status_type			;  Add one more to cover 3 byte LJMP opcode
	mov	dptr,#status_table		;   Init jump table base

;	*** Acc = value 0-7 correlating to    ***
;	*** Midi Status Types (8-F) Use this  ***
;	*** this value to index into a "jump" ***
;	*** table for command processing.     ***

	jmp	@a+dptr		

;	*** Midi Command Interrupt Service Routines ***

status_table:
	ljmp	midi_noteoff
	ljmp	midi_noteon
	ljmp	running_status_out
	ljmp	control_set	
	ljmp	running_status_out
	ljmp	running_status_out
	ljmp	bend_set
	ljmp	running_status_out

$eject

;               ******   **  **   ******   ******
;                 **     *** **     **       **   
;                 **     ******     **       **    
;                 **     ** ***     **       **   
;               ******   **  **   ******     **    

init:   

;	*** Init: The Mode of the two Timers used ***
;	*** Timer - 0 Generates the Trigger Width *** 
;	****      - 1 Generates the Midi Bit Rate ***

	mov	TMOD,#21h		;INIT ... T0 = 16 bit Timer for Velocity and LFO
					;     ... T1 = 8 BIT AUTO-RELOAD Baud Gen

;	*** Init The MIDI Bit Rate 31kHz ***

	MOV     TH1,#0FFH               ;Init  ... SERIAL BAUD CLOCK VALUE (T1)
	MOV     TL1,#0FFH               ;      ... SERIAL BAUD CLOCK VALUE (T1)
	clr	pt1			;Set   ... Timer 1 priority low
	clr	et1			;      ... Timer Interrupt to disable
	setb	tr1

;       *** Init Initial State of Trigger Clock ***

	MOV     TH0,#0	               	;Clear Timer 0 Count register
	MOV     TL0,#0                  ; 
	clr	et1                     ;Set ... Timer0 Interrupt to Enable
 	clr	pt0			;    ... Timer0 priority to LOW
	clr	tr0			;    ... Timer0 to Not Running

;       *** INIT Serial Port 0 Hardware (19200) ***

	ANL	PCON,#7FH		;Set ... Prescaler as not in effect
        MOV     SCON,#52H		;INIT ... 8 BIT UART MODE AND RCV ENABLE.
					;         Tx Int Flag Artificially set 
	
;	*** Parameterize Serial Port 1 interrupt ***

	setb	txflag			;Set .. "Transmit Ready"
	setb	ps			;    .. Serial Port Priority High
	setb	es			;Enable Serial Interrupts

;	*** Init Serial Interrupt Buffer Pointers ***

	push	psw			;Set Input & Output pointers
	mov	psw,#alt_regs			; to base of Serial Buffer
	mov	r0,#midi_fifo
	mov	r1,#midi_fifo
	pop	psw

;       *** External Interrupt ***

	setb    it0             	;Set ... edge sensitive interrupt
	clr     px0             	;    ... priority low
	clr	ex0             	;    ... external 0 disabled

;	*** initialize hardware ***

	setb	fz_dac 			;Set benign DAC wr Strobe state
	setb	mod_dac
	
;	*** Init Note Voltage DAC ***

	clr	a			;Output a zero note value
	call	wr_fz

;	*** Init Modulation Voltage DAC ***

	clr	a
	mov	lfo_byte,a
	mov	bend_byte,a
	mov	noise_byte,a
	mov	tune_byte,#dac_midpoint
	call	mdac_write

;	*** Init Random Funtion ***

	clr	noise_flag
	mov	seed,#'P'
	mov	noise_mult,#1
	mov	noise_counter,#noise_count
	
;	*** Init LFO ***

	clr	lfo_flag
	mov	lfo_phase0,#0
	mov	lfo_phase1,#0
	mov	lfo_phase2,#0
	mov	lfo_inc0,#10H
	mov	lfo_inc1,#0FH
	mov	lfo_inc2,#00H
	mov	lfo_mult,#40H
	mov	lfo_counter,#lfo_count

;	*** Quiet Synth Voice ***
	
	clr	gate
	clr	trigger

;	*** clear the command processing machinery ***

	clr	running_status
	clr	omni
 	clr	cmd_in
	clr	cmd_sync
	setb	velocity_mode

	mov	channel_spec,#0			;Init default MIDI Channel (0) of Interest	
	mov	process_flags,#0
						; mode, and fm flags
;	*** Hardware Testing ***

	mov	midi_status,#0
	mov	midi_in_ptr,#0
	mov	midi_out_ptr,#0
	mov	mout_status,#0

;	*** Clear the Sort Accumulators ***

	clr	a
	mov	last_hi_note,a
	dec	a
	clr	assignment_flag

;	*** clear the note buffer ***

	mov	r2,#8
	mov	r0,#midi_note0
clear_nbuff:
	mov	@r0,#dac_midpoint		;Initialize as "Note Off"
	inc	r0
	mov	@r0,#64
	inc	r0
	djnz	r2,clear_nbuff
	ret

$eject

;	*****    ****    *****
;	**  **  **  **  **     
;	**  **  ******  **     
;	**  **  **  **  **    
;	*****   **  **   *****

;	****************************
;	*** Update the DAC Voice ***
;	****************************

write_fz:
	jb	acc.7,quiet_voice

	mov	a,midi_note0
	call	wr_fz			;Output to Voltage Control Fz DAC
	setb	gate			; Turn on Gate
	mov	a,midi_velocity0	;  Fetch the Velocity value for this voice
	
;	*** Test which Velocity Algorithm we are using ***
	
	jb	velocity_mode,velo_trig	;Branch if we are in velocity width mode

;	*** We are in Velocity Delay Mode ***

	rl	a			;Multiply by two (it's a 7 bit value)
	mov     TH0,a			;Init .. Hi Trigger delay
	mov     TL0,#0			;     .. Lo Trigger delay
	mov	trigger_width,#0C0H	;     .. The width of the eventual Trigger pulse
	setb	TR0			;Enable .. the Trigger delay timer
	setb	ET0			;       .. the trigger timer interrupt
	ret

;	*** We are in Velocity Width Trigger Mode ***

velo_trig:
	clr	trigger			;Reset Trigger (We re-trigger every new note)

	jb	acc.6,vt_pos

	mov	r2,a
	mov	a,#64
	clr	c
	subb	a,r2
	clr	c
	rlc	a
	mov	r2,a
	mov	a,#80H
	clr	c
	subb	a,r2
	jmp	vt_load

vt_pos:
	clr	c
	subb	a,#64
	clr	c
	rlc	a
	add	a,#80H

vt_load:
	mov	trigger_width,a		;Move .. Velocity into Trigger Width
	mov	velo_reload,last_hi_note;     .. Key value into Lo Width (key scaling)
	mov     TH0,#0E0H		;Init .. Hi Trigger Delay
	mov     TL0,#0			;     .. Lo Trigger Delay
	setb	TR0			;Enable .. the Trigger delay counter
	setb	ET0			;       .. the trigger timer interrupt
	ret

quiet_voice:
	clr	gate
	clr	trigger
	ret

;	************************
;	*** Noise Generation ***
;	************************

random:
	djnz	noise_counter,noise_out		;Branch if pre-scaler not exhausted

	mov	a,seed
	jnz	rand8b

;	*** A zero would stall random generator ***

	cpl	a
	mov	seed,a
rand8b:
	anl	a,#0B8H
	mov	c,p
	mov	a,seed
	rlc	a
	mov	seed,a
	
;	*** Scale the noise value ***
;	*** and output to the DAC ***	

	jb	acc.7,pos_noise

;	*** Generate the absolute value of this negative offset ***

	mov	r2,#dac_midpoint
	xch	a,r2
	clr	c
	subb	a,r2

;	*** Adjust Noise Amplitude ***

	mov	b,noise_mult
	mul	ab
	
	clr	c			; Subtract scaled Negative Offset
	clr	a
	subb	a,b			;  From midscale and store
	jmp	go_noise
		
pos_noise:
	clr	acc.7
	mov	b,noise_mult		; Multiply the Noise Amplitude
	mul	ab			;  from MIDI Noise Amplitude "CC MSG"
	mov	a,b

go_noise:
	mov	noise_byte,a
	anl	a,#3
	mov	noise_counter,a		;Peterbate the calling frequency of Random
	setb	mod_update
noise_out:
	ret

$eject	

;	********************************************
;	*** Digital to Analog Convertor Routines ***
;	********************************************

wr_fz:
	mov	last_hi_note,a
	rl	a			;Left Justify the MIDI Note Value
	mov	dac_buss,a		;  Output to the DAC Buss
	clr	fz_dac			;   Generate the Fz Dac WR Strobe
	setb	fz_dac
	ret

mdac_write:
	clr	mod_update	;Reset Service Request Flag
	mov	a,tune_byte	;Fetch the Pitch Value (0=concert pitch)
	add	a,lfo_byte	; Add .. current LFO value
	add	a,noise_byte	;     .. current Noise value
	add	a,bend_byte	;     .. current pitch bend value

;	*** Now add the "physical" Voltage Offset   ***
;	*** to which the logical values add/subract ***

;	*** Update the Modulation DAC ***

 	mov	dac_buss,a	;Output Modulation value to the DACs
	clr	mod_dac		; Generate the MOD Dac wr Strobe
	setb	mod_dac
	
;	*** Restore foreground and exit ***

	ret

;	***-------------------------------------------***
;	*** New Voice Assign Trigger Pulse Generation ***
;	***-------------------------------------------***

trigger_interrupt:
	clr	TF0
	clr	TR0			;Stop the Timer Count
	cpl	trigger			; Invert the State of the Trigger Pulse
	jb	trigger,load_trigger	;  If we just set the trigger, load the width

;	*** We have just reset the trigger signal ***
;	*** The pulse is generation is finished.  ***

	reti

;	*** We have just activated the trigger signal ***
;	*** load the width value preset by trig mode  ***
	
load_trigger:
	mov     TL0,velo_reload		;Init .. Hi Trigger delay
	mov	TH0,trigger_width	;     .. The width of the eventual Trigger pulse
	setb	TR0
	reti

;	******************************************
;	*** Length of Individual Midi Commands ***
;	******************************************

;	     	*** PAGE ONE - SINE WAVE ***

sine_table:
	DB	080H,083H,086H,089H,08CH,090H,093H,096H
	DB	099H,09CH,09FH,0A2H,0A5H,0A8H,0ABH,0AEH
	DB	0B1H,0B3H,0B6H,0B9H,0BCH,0BFH,0C1H,0C4H
	DB	0C7H,0C9H,0CCH,0CEH,0D1H,0D3H,0D5H,0D8H	;32

	DB	0DAH,0DCH,0DEH,0E0H,0E2H,0E4H,0E6H,0E8H
	DB	0EAH,0EBH,0EDH,0EFH,0F0H,0F1H,0F3H,0F4H
	DB	0F5H,0F6H,0F8H,0F9H,0FAH,0FAH,0FBH,0FCH
	DB	0FDH,0FDH,0FEH,0FEH,0FEH,0FFH,0FFH,0FFH	;64

	DB	0FFH,0FFH,0FFH,0FFH,0FEH,0FEH,0FEH,0FDH
	DB	0FDH,0FCH,0FBH,0FAH,0FAH,0F9H,0F8H,0F6H
	DB	0F5H,0F4H,0F3H,0F1H,0F0H,0EFH,0EDH,0EBH
	DB	0EAH,0E8H,0E6H,0E4H,0E2H,0E0H,0DEH,0DCH	;96

	DB	0DAH,0D8H,0D5H,0D3H,0D1H,0CEH,0CCH,0C9H
	DB	0C7H,0C4H,0C1H,0BFH,0BCH,0B9H,0B6H,0B3H
	DB	0B1H,0AEH,0ABH,0A8H,0A5H,0A2H,09FH,09CH
	DB	099H,096H,093H,090H,08CH,089H,086H,083H	;128

	DB	080H,07DH,07AH,077H,074H,070H,06DH,06AH	
	DB	067H,064H,061H,05EH,05BH,058H,055H,052H
	DB	04FH,04DH,04AH,047H,044H,041H,03CH,03BH
	DB	039H,037H,034H,032H,02FH,02DH,02BH,028H	;160

	DB	026H,024H,022H,020H,01EH,01CH,01AH,018H
	DB	016H,015H,013H,011H,010H,00FH,00DH,00CH
	DB	00BH,00AH,008H,007H,006H,006H,005H,004H
	DB	003H,003H,002H,002H,002H,001H,001H,001H	;192

	DB	001H,001H,001H,001H,002H,002H,002H,003H
	DB	003H,004H,005H,006H,006H,007H,008H,00AH
	DB	00BH,00CH,00DH,00FH,010H,011H,013H,015H
	DB	016H,018H,01AH,01CH,01EH,020H,022H,024H	;224

	DB	026H,028H,02BH,02DH,02FH,032H,034H,037H
	DB	039H,03CH,03FH,041H,044H,047H,04AH,04DH
	DB	04FH,052H,055H,058H,05BH,05EH,061H,064H
	DB	067H,06AH,06DH,070H,074H,077H,07AH,07DH	;256

$include(511tbl.txt)
	end