$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