title "Programmable Voltage-Controlled Envelope Generator" ; ; This program provides a versatile envelope generator on a single chip. ; It is designed as a modern version of the CEM3312 or SSM2056 ICs. ; Analogue output is provided as a PWM output, which requires LP ; filtering to be useable. ; ; Hardware Notes: ; PIC16F684 running at 20 MHz using external crystal ; Six analogue inputs: ; RA1/AN1: 0-5V Attack Time CV ; RA2/AN2: 0-5V Decay Time CV ; RC0/AN4: 0-5V Sustain Level CV ; RC1/AN5: 0-5V Release Time CV ; RC2/AN6: 0-5V Time Adjust CV (Keyboard CV or velocity, for example) ; RC3/AN7: 0-5V Output Level CV ; Two digital inputs: ; RA3: Gate Input ; RC4: Exp/Lin Input (High is Linear) ; One digital output ; RA0: Gate LED output ; ; This version started as (ENVGEN4LIN.ASM), a test version without any of ; the complications of the exponential output. Instead it passes ; the linear PHASE value directly to the PWM output. ; This should allow me to test the rest of the code, before trying to ; add the complex (for a PIC) interpolation and lookp maths required for the ; exponential curve output ; ; 29th Aug 06 - got this basically working. ; 2nd Sept 06 - ENVGEN5.ASM - Added exponential output. ; Still have 278 bytes left! LIST R=DEC INCLUDE "p16f684.inc" __CONFIG _FCMEN_OFF & _IESO_OFF & _BOD_OFF & _CPD_OFF & _CP_OFF & _MCLRE_OFF & _PWRTE_ON & _WDT_OFF & _HS_OSC ;------------------------------ ; Variables ;------------------------------ CBLOCK 0x020 ; Registers for context saving during interrupts W_TEMP STATUS_TEMP PCLATH_TEMP FSR_TEMP ; General working storage TEMP ; Used for the ADC delay LOOKUPTEMP ; The page boundary code needs a temp register PHASE_INC_INDEX ; Used by the PHASE_INC lookup TIME_TMP ; Used by the TIME_CV adjustments ; The working storage for the interpolation subroutine INTERP_A INTERP_B INTERP_OUT_HI INTERP_OUT_LO ; Working storage for the 8x8bit multiply subroutine MULT_IN MULT_OUT_HI MULT_OUT_LO ; The current A/D channel and value ADC_CHANNEL ADC_VALUE ; The current stage (0=Wait, 1=Attack, 2=Decay, 3=Sustain, 4=Release, 0=Wait again) STAGE ; The currrent output level (used by RELEASE) CURRENT_LEVEL ; The current control voltage(CV) values (8 bit) ATTACK_CV DECAY_CV SUSTAIN_CV RELEASE_CV TIME_CV LEVEL_CV ; Various flag bits. 1 is on, obviously. FLAGS ; See Define statements below for details ; The 24 bit phase accumulator PHASE_HI PHASE_MID PHASE_LO ; The 20 bit frequency increments ; These are stored separately for Attack, Decay, & Release ; Note that these increments have been adjusted to reflect ; changes due to TIME_CV, whereas the raw CVs haven't ATTACK_INC_LO ATTACK_INC_MID ATTACK_INC_HI DECAY_INC_LO DECAY_INC_MID DECAY_INC_HI RELEASE_INC_LO RELEASE_INC_MID RELEASE_INC_HI ; The 10 bit output level from the envelope OUTPUT_HI OUTPUT_LO ; The 16 bit final output level, after scaling by LEVEL_CV FINAL_HI FINAL_LO ENDC ;------------------------------------- ; DEFINE STATEMENTS ;------------------------------------- ; Useful bit definitions for clarity #define ZEROBIT STATUS,Z ; Zero Flag #define CARRY STATUS,C ; Carry Flag #define BORROW STATUS,C ; Borrow is the same as Carry ; Flag bit definitions #define LOG_ENABLE FLAGS, 0 ; Logarithmic Output On/Off ; Input/Output bit definitions #define GATE PORTA, 3 ; Gate Input #define GATE_LED PORTA, 0 ; Gate LED #define EXPO_OR_LIN PORTC, 4 ; Exponential/Linear Input (1=Lin) ;---------------------------------------------------------------------- ; Begin Executable Code Segment ;---------------------------------------------------------------------- org 0x000 ; processor reset vector nop ; for ICD use goto Main ; Go to the main program org 0x004 ;Interrupt vector location InterruptEnter movwf W_TEMP ; save W register swapf STATUS, W ; swap status to be saved into W bcf STATUS, RP0 ; ---- Select Bank 0 ----- movwf STATUS_TEMP ; save STATUS register movfw PCLATH movwf PCLATH_TEMP ; save PCLATH_TEMP register movfw FSR movwf FSR_TEMP ; save FSR_TEMP register ;---------------------------------------- ; Interrupt Service Routine (ISR) (Section 12.4) ; This deals with the DDS and PWM output ;---------------------------------------- GateISR btfss INTCON, RAIF ; Check if GATE pin has altered goto Timer2ISR movfw PORTA ; Read the port (necessary to clear the Int) bcf INTCON, RAIF ; Clear GATE interrupt flag ; (As a quirk, these last two lines MUST be in this order) ; GATE has changed state - Do stuff common to both possibilities ; Zero the accumulator clrf PHASE_HI clrf PHASE_MID clrf PHASE_LO ; What's the current output level? movf OUTPUT_HI, w movwf CURRENT_LEVEL ; Now do stuff specific to each possibility (gone high/gone low) btfsc GATE goto GateGoneHigh GateGoneLow ; If it's gone low, change to RELEASE stage movlw D'4' movwf STAGE bcf GATE_LED ; Turn the GATE indicator LED off goto Timer2ISR GateGoneHigh ; If it's gone high, change to ATTACK stage movlw D'1' movwf STAGE bsf GATE_LED ; Turn the GATE indicator LED on Timer2ISR btfss PIR1, TMR2IF ; check if TMR2 interrupt goto InterruptExit bcf PIR1, TMR2IF ; clear TMR2 interrupt flag ; Do we need to increment the phase accumulator? ; If so, what FSR offset should we use? movlw HIGH IncrementPhaseBranch movwf PCLATH ; Set up high bits of PC movf STAGE, w ; Get current stage addwf PCL, f ; Increment PC with STAGE value IncrementPhaseBranch goto Wait ; No PHASE_INC required for WAIT goto GetAttackOffset goto GetDecayOffset goto Sustain ; No PHASE_INC required for SUSTAIN goto GetReleaseOffset ; THERE MIGHT BE A SHORTER WAY TO DO THIS BIT - ; THIS SEEMS LIKE A LOT FOR JUST THREE LITERALS GetAttackOffset movlw H'3D' ; Offset for ATTACK_INC_LO goto IncrementPhase GetDecayOffset movlw H'40' ; Offset for DECAY_INC_LO goto IncrementPhase GetReleaseOffset movlw H'43' ; Offset for RELEASE_INC_LO ; Increment the phase accumulator PHASE (24+20 bit addition) IncrementPhase ; Which set of increments are we using? Set the FSR movwf FSR ; Add the increment to the phase accumulator, PHASE movf INDF, w ; Add INC_LO to PHASE_LO addwf PHASE_LO, f btfss CARRY ; Do we carry one to the middle byte? goto IncMiddleBit incfsz PHASE_MID, f ; Has that carry caused a carry to the high byte? goto IncMiddleBit incf PHASE_HI, f ; Has that carry caused an overflow? btfsc ZEROBIT goto NextStage IncMiddleBit incf FSR, f ; Move to the INC_MID movf INDF, w ; Add INC_MID to PHASE_MID addwf PHASE_MID, f btfss CARRY ; Do we carry one to the high byte? goto IncTopBit incf PHASE_HI, f ; Has that carry caused an overflow? btfsc ZEROBIT goto NextStage IncTopBit incf FSR, f ; Move to the INC_HI movf INDF, w ; Add INC_HI to PHASE_HI addwf PHASE_HI, f btfss CARRY ; Has it overflowed? goto SelectStage ; No, so continue directly ; Accumulator has overflowed, so move to the next stage NextStage ; First zero the accumulator.. clrf PHASE_HI clrf PHASE_MID clrf PHASE_LO ; ..then increment the STAGE incf STAGE, f movf STAGE, w xorlw D'5' btfsc ZEROBIT ; Is STAGE==5 yet? clrf STAGE ; Yes, so reset it to zero ; We need to produce different output values depending on which stage we're at SelectStage movlw HIGH SelectStageBranch movwf PCLATH ; Set up high bits of PC movf STAGE, w ; Get current stage addwf PCL, f ; Increment program counter with STAGE value SelectStageBranch goto Wait ; (Only gets called after'NextStage') goto Attack goto Decay goto Sustain ; (Only gets called after'NextStage') goto Release Wait ; Do nothing. GATE is low, and release stage has finished clrf FINAL_HI ; Ensure we output zero when waiting clrf FINAL_LO goto PWMOutput ; No need to multiply zero by LEVEL_CV Attack ; Attack needs scaling by 1-CURRENT_LEVEL, ; then needs CURRENT_LEVEL adding to it comf CURRENT_LEVEL, w movwf MULT_IN movf PHASE_HI, w ; This is the linear value ; Do we use the linear value directly, or do an expo lookup? btfsc EXPO_OR_LIN goto AttackScaling ExponentialAttack ; Get the required value for an exponential attack curve ; Lookup the first value call GetAttackCurve ; We use the linear PHASE_HI value as an index movwf INTERP_A ; Lookup the next value incf PHASE_HI, w call GetAttackCurve movwf INTERP_B ; Do the interpolation call Interpolation AttackScaling call Multiply8x8 ; Do the scaling ; Add CURRENT_LEVEL movf CURRENT_LEVEL, w addwf MULT_OUT_HI, w movwf OUTPUT_HI movf MULT_OUT_LO, w movwf OUTPUT_LO goto MultiplyByLevelCV Decay ; Decay needs scaling by 1-SUSTAIN, then inverting comf SUSTAIN_CV, w ; Invert the SUSTAIN level movwf MULT_IN movf PHASE_HI, w ; Do we use the linear value directly, or do an expo lookup? btfsc EXPO_OR_LIN goto DecayScaling ExponentialDecay ; Get the required value for an exponential Decay curve ; Lookup the first value call GetDecayCurve movwf INTERP_A ; Lookup the next value incf PHASE_HI, w call GetDecayCurve movwf INTERP_B ; Do the interpolation call Interpolation DecayScaling call Multiply8x8 ; Do the scaling ; Invert the result comf MULT_OUT_HI, w movwf OUTPUT_HI comf MULT_OUT_LO, w movwf OUTPUT_LO goto MultiplyByLevelCV Sustain ; Do nothing. Gate is high, and decay stage has finished movf SUSTAIN_CV, w ; Ensure that we output the sustain level movwf OUTPUT_HI clrf OUTPUT_LO goto MultiplyByLevelCV Release ; Release needs scaling by CURRENT_LEVEL, then ; 1-CURRENT_LEVEL level adding, then inverting movf CURRENT_LEVEL, w movwf MULT_IN movf PHASE_HI, w ; Do we use the linear value directly, or do an expo lookup? btfsc EXPO_OR_LIN goto ReleaseScaling ExponentialRelease ; Get the required value for an exponential Decay curve ; Lookup the first value call GetDecayCurve movwf INTERP_A ; Lookup the next value incf PHASE_HI, w call GetDecayCurve movwf INTERP_B ; Do the interpolation call Interpolation ReleaseScaling call Multiply8x8 ; Do the scaling ; Add 1-CURRENT_LEVEL comf CURRENT_LEVEL, w addwf MULT_OUT_HI, w ; Invert the result xorlw D'255' movwf OUTPUT_HI comf MULT_OUT_LO, w movwf OUTPUT_LO goto MultiplyByLevelCV ; Modify the Output level ;------------------------- ; (Basic multiply routine from Microchip App Note 26) ; This involves multiplying the envelope output by ; the LEVEL_CV value - an 10 bit x 8 bit multiplication ; Of the result, I only need the top 10 bits ; Expects number to be multiplied by LEVEL_CV in OUTPUT MultiplyByLevelCV clrf FINAL_HI clrf FINAL_LO movf LEVEL_CV, w clrc ; Clear carry? Why didn't I know about this?! btfsc OUTPUT_HI, 0 addwf FINAL_HI, f rrf FINAL_HI, f rrf FINAL_LO, f btfsc OUTPUT_HI, 1 addwf FINAL_HI, f rrf FINAL_HI, f rrf FINAL_LO, f btfsc OUTPUT_HI, 2 addwf FINAL_HI, f rrf FINAL_HI, f rrf FINAL_LO, f btfsc OUTPUT_HI, 3 addwf FINAL_HI, f rrf FINAL_HI, f rrf FINAL_LO, f btfsc OUTPUT_HI, 4 addwf FINAL_HI, f rrf FINAL_HI, f rrf FINAL_LO, f btfsc OUTPUT_HI, 5 addwf FINAL_HI, f rrf FINAL_HI, f rrf FINAL_LO, f btfsc OUTPUT_HI, 6 addwf FINAL_HI, f rrf FINAL_HI, f rrf FINAL_LO, f btfsc OUTPUT_HI, 7 addwf FINAL_HI, f rrf FINAL_HI, f rrf FINAL_LO, f ; Extra bits ; btfsc OUTPUT_HI, 6 ; addwf FINAL_HI, f ; bcf CARRY ; If the carry isn't cleared, it might wrap around ; rrf FINAL_HI, f ; rrf FINAL_LO, f ; btfsc OUTPUT_HI, 7 ; addwf FINAL_HI, f ; bcf CARRY ; rrf FINAL_HI, f ; rrf FINAL_LO, f ; ; Set PWM duty cycle ;--------------------------------------- ; This puts the value of PWM_DUTY_CYCLE into the appropriate ; registers. ; Note: we can set the duty cycle in registers ; CCP1CON and CCPR1L because they are double ; buffered and the changes will not take affect ; until the next PWM period starts (TMR2 resets). PWMOutput ; Put the 2 MSBs of OUTPUT_LO into CCP1CON rlf FINAL_LO, w ; rotate bit 7 into the carry bit bsf CCP1CON, DC1B1 ; set or clear bit 5 of the CCP1CON register btfss CARRY bcf CCP1CON, DC1B1 rlf FINAL_LO, w ; rotate bit 6 into the carry bit bsf CCP1CON, DC1B0 ; set or clear bit 4 of the CCP1CON register btfss CARRY bcf CCP1CON, DC1B0 ; Put the high byte into CCPR1L movf FINAL_HI, w movwf CCPR1L ;---------------------------------------- InterruptExit movfw PCLATH_TEMP ; restore PCLATH_TEMP register movwf PCLATH movfw FSR_TEMP ; restore FSR_TEMP register movwf FSR swapf STATUS_TEMP, w ; swap status_temp into W, sets bank to original state movwf STATUS ; restore STATUS register swapf W_TEMP, f swapf W_TEMP, w ; restore W register retfie ;------------------------------------------------------ ; 8 bit x 8 bit Multiply Subroutine ; This is used by the Decay and Release stages to ; scale their output to ensure it meets up with the ; SUSTAIN level, and also by the Attack routine ; to ensure that it reaches from whatever the start ; value was to the maximum output. ; The value in W is multipled by MULT_IN. ; 16 bit Output is in MULT_OUT ;------------------------------------------------------ Multiply8x8 clrf MULT_OUT_HI clrf MULT_OUT_LO clrc ; Clear carry? Why didn't I know about this?! btfsc MULT_IN,0 addwf MULT_OUT_HI, f rrf MULT_OUT_HI, f rrf MULT_OUT_LO, f btfsc MULT_IN,1 addwf MULT_OUT_HI, f rrf MULT_OUT_HI, f rrf MULT_OUT_LO, f btfsc MULT_IN,2 addwf MULT_OUT_HI, f rrf MULT_OUT_HI, f rrf MULT_OUT_LO, f btfsc MULT_IN,3 addwf MULT_OUT_HI, f rrf MULT_OUT_HI, f rrf MULT_OUT_LO, f btfsc MULT_IN,4 addwf MULT_OUT_HI, f rrf MULT_OUT_HI, f rrf MULT_OUT_LO, f btfsc MULT_IN,5 addwf MULT_OUT_HI, f rrf MULT_OUT_HI, f rrf MULT_OUT_LO, f btfsc MULT_IN,6 addwf MULT_OUT_HI, f rrf MULT_OUT_HI, f rrf MULT_OUT_LO, f btfsc MULT_IN,7 addwf MULT_OUT_HI, f rrf MULT_OUT_HI, f rrf MULT_OUT_LO, f return ;------------------------------------------------------ ; Linear Interpolation Subroutine ; This is used by the Attack, Decay, and Release stages. ; The routine expects to be given two 8-bit values to ; interpolate between: INTERP_A and INTERP_B ; It also assumes that PHASE_MID is to be used as ; interpolation index. ;------------------------------------------------------ Interpolation ; Work out the DELTA value movf INTERP_A, w subwf INTERP_B, f ; Should have DELTA in INTERP_B ; Calculate DELTA * INTERP_INDEX(PHASE_MID) ; This is a 8 bit x 8 bit multiplication but I only need the 8 MSBs ; The interpolation result goes into INTERP_OUT clrf INTERP_OUT_HI clrf INTERP_OUT_LO movf INTERP_B, w clrc ; Clear carry? Why didn't I know about this?! btfsc PHASE_MID, 0 ; PHASE_MID is used as the interp index addwf INTERP_OUT_HI, f rrf INTERP_OUT_HI, f rrf INTERP_OUT_LO, f btfsc PHASE_MID, 1 addwf INTERP_OUT_HI, f rrf INTERP_OUT_HI, f rrf INTERP_OUT_LO, f btfsc PHASE_MID, 2 addwf INTERP_OUT_HI, f rrf INTERP_OUT_HI, f rrf INTERP_OUT_LO, f btfsc PHASE_MID, 3 addwf INTERP_OUT_HI, f rrf INTERP_OUT_HI, f rrf INTERP_OUT_LO, f btfsc PHASE_MID, 4 addwf INTERP_OUT_HI, f rrf INTERP_OUT_HI, f rrf INTERP_OUT_LO, f btfsc PHASE_MID, 5 addwf INTERP_OUT_HI, f rrf INTERP_OUT_HI, f rrf INTERP_OUT_LO, f btfsc PHASE_MID, 6 addwf INTERP_OUT_HI, f rrf INTERP_OUT_HI, f rrf INTERP_OUT_LO, f btfsc PHASE_MID, 7 addwf INTERP_OUT_HI, f rrf INTERP_OUT_HI, f rrf INTERP_OUT_LO, f ; We should have a 16 bit DELTA x INDEX in INTERP_OUT ; Add the interpolated value and INTERP_A to get the output movf INTERP_A, w addwf INTERP_OUT_HI, w ; Required value is in W return ;---------------------------------------- ; Analogue to Digital conversion subroutine ; This is used by the main code loop ;---------------------------------------- DoADConversion ; Short delay whilst the channel settles movlw D'6' ; At 4 MHz, a 22 us delay movwf TEMP ; (22us = 2us + 6 * 3us + 1us) decfsz TEMP, F goto $-1 ; Start the conversion bsf ADCON0, GO ; Wait for it to finish btfsc ADCON0, GO ; Is it done? goto $ - 1 ; Read the ADC Value and store it movf ADRESH, w movwf ADC_VALUE return ;---------------------------------------- ; The main program ; This reads the A/D channels and provides ; values for the DDS ;---------------------------------------- Main clrf PORTC movlw 7 ; Turn off Comparators movwf CMCON0 clrf TMR2 ; Using TMR2 as a PWM Generator movlw b'00000100' ; Enable TMR2 movwf T2CON clrf CCPR1L ; Nothing Moving (Yet) bsf STATUS, RP0 ; Bank 1 ; Peripheral Interrupt Enable Register (PIE1) (Section 2.2.2.4) movlw b'00000010' ; TMR2 Overflow Interrupt: ENABLED movwf PIE1 ^ 0x80 movlw b'11110110' ; ADC Input on AN1, AN2 and AN4, AN5, AN6, AN7 movwf ANSEL ^ 0x80 movlw b'00100000' ; Select ADC Clock as Fosc/32 movwf ADCON1 ^ 0x80 ; to ensure TAD of 1.6uS movlw D'255' ; Setup the Limit for the PWM movwf PR2 ^ 0x80 ; Maximum 19.5 KHz Frequency movlw b'011111' ; RC5 Output, all others inputs movwf TRISC ^ 0x80 movlw b'111110' ; RA0 Output, all others inputs movwf TRISA ^ 0x80 movlw b'00001000' movwf IOCA ^ 0x80 ; Interrupt-on-change on RA3 bcf STATUS, RP0 ; Bank 0 ; Interrupt Control Register (INTCON) (Section 2.2.2.3) movlw b'00000000' ; Clear all peripheral interrupts movwf PIR1 movlw b'11001000' ; Peripheral Interrupts: ENABLED movwf INTCON ; (EEI, ADI, CCP1I, C2I, C1I, OSFI, TMR2I, TMR1I) ; PORT A Interrupt-on-change: ENABLED movlw b'00001100' ; Enable single channel PWM movwf CCP1CON ; Set up initial values of the variables clrf ATTACK_CV ; Default to minimum time (1mS) clrf DECAY_CV clrf SUSTAIN_CV clrf RELEASE_CV movlw D'128' movwf TIME_CV ; Default to no time modulation movlw D'255' movwf LEVEL_CV ; Default to max output clrf STAGE clrf FLAGS ; Clear the Phase Accumulator clrf PHASE_LO clrf PHASE_MID clrf PHASE_HI ; Clear the increments too clrf ATTACK_INC_LO clrf ATTACK_INC_MID clrf ATTACK_INC_HI clrf DECAY_INC_LO clrf DECAY_INC_MID clrf DECAY_INC_HI clrf RELEASE_INC_LO clrf RELEASE_INC_MID clrf RELEASE_INC_HI ; Clear the output buffers clrf OUTPUT_HI ; Pre-scaling by LEVEL_CV clrf OUTPUT_LO clrf FINAL_HI ; Post-scaling clrf FINAL_LO movlw D'7' movwf ADC_CHANNEL ; Start with ATTACK_CV bcf GATE_LED ; Switch the LED off clrf CURRENT_LEVEL ; Start with this at zero MainLoop ; Change to next A/D channel incf ADC_CHANNEL, f ; We need to do different things depending on which value we're reading: SelectADCChannel movlw HIGH ADCChannelJump movwf PCLATH ; Set the top part of the PC movf ADC_CHANNEL, w ; Get current channel andlw D'7' ; Only need three LSBs addwf PCL, f ; Increment program counter with channel value ADCChannelJump goto AttackCV goto DecayCV goto SustainCV goto ReleaseCV goto TimeCV goto OutputLevelCV ScannedAllChannels ; If we jump to here, ADC_CHANNEL is either 6 or 7 ; Either way, it'll be 7 when it goes back to MainLoop incf ADC_CHANNEL, f goto MainLoop ; Update the Attack CV AttackCV movlw b'00000101' ; AN1, ADC On movwf ADCON0 call DoADConversion ; Add the TIME_CV: 8+8bit=9 bit addition addwf TIME_CV, w btfss CARRY goto AttackRangeLow AttackRangeHigh ; ATTACK_CV+TIME_CV = 256 - 510 ; If value is higher than 127, use maximum movwf TIME_TMP btfsc TIME_TMP, 7 movlw D'127' goto AttackInvert AttackRangeLow ; ATTACK_CV+TIME_CV = 0 - 255 ; If value is less than 128, use minimum movwf TIME_TMP btfss TIME_TMP, 7 movlw D'128' AttackInvert ; As a result of the 9-bits, the part I need has been inverted addlw D'128' ; This should invert it. ; Store the new ATTACK_CV movwf ATTACK_CV ; WHAT HAPPENS IF AN INTERUPT OCCURS WHILST THIS INCREMENT ; IS BEING UPDATED? IT'S GOING TO GLITCH, SURELY? ; Get the new phase increment for the ATTACK stage movwf PHASE_INC_INDEX call GetPhaseIncHi movwf ATTACK_INC_HI call GetPhaseIncMid movwf ATTACK_INC_MID call GetPhaseIncLo movwf ATTACK_INC_LO goto MainLoop ; Update the Decay CV DecayCV movlw b'00001001' ; AN2, ADC On movwf ADCON0 call DoADConversion ; Add the TIME_CV: 8+8bit=9 bit addition addwf TIME_CV, w btfss CARRY goto DecayRangeLow DecayRangeHigh ; ATTACK_CV+TIME_CV = 256 - 510 ; If value is higher than 127, use maximum movwf TIME_TMP btfsc TIME_TMP, 7 movlw D'127' goto DecayInvert DecayRangeLow ; ATTACK_CV+TIME_CV = 0 - 255 ; If value is less than 128, use minimum movwf TIME_TMP btfss TIME_TMP, 7 movlw D'128' DecayInvert ; As a result of the 9-bits, the part I need has been inverted addlw D'128' ; This should invert it. ; Store the new DECAY_CV movwf DECAY_CV ; Get the new phase increment for the DECAY stage movwf PHASE_INC_INDEX call GetPhaseIncHi movwf DECAY_INC_HI call GetPhaseIncMid movwf DECAY_INC_MID call GetPhaseIncLo movwf DECAY_INC_LO goto MainLoop ; Update the Sustain CV SustainCV movlw b'00010001' ; AN4, ADC On movwf ADCON0 call DoADConversion movwf SUSTAIN_CV ; Simply store this one- easy! goto MainLoop ; Update the Release CV ReleaseCV movlw b'00010101' ; AN5, ADC On movwf ADCON0 call DoADConversion ; Add the TIME_CV: 8+8bit=9 bit addition addwf TIME_CV, w btfss CARRY goto ReleaseRangeLow ReleaseRangeHigh ; RELEASE_CV+TIME_CV = 256 - 510 ; If value is higher than 127, use maximum movwf TIME_TMP btfsc TIME_TMP, 7 movlw D'127' goto ReleaseInvert ReleaseRangeLow ; RELEASE_CV+TIME_CV = 0 - 255 ; If value is less than 128, use minimum movwf TIME_TMP btfss TIME_TMP, 7 movlw D'128' ReleaseInvert ; As a result of the 9-bits, the part I need has been inverted addlw D'128' ; This should invert it. StoreRelease ; Store the new RELEASE_CV movwf RELEASE_CV ; Get the new phase increment for the RELEASE stage movwf PHASE_INC_INDEX call GetPhaseIncHi movwf RELEASE_INC_HI call GetPhaseIncMid movwf RELEASE_INC_MID call GetPhaseIncLo movwf RELEASE_INC_LO goto MainLoop ; Update the Time CV TimeCV movlw b'00011001' ; AN6, ADC On movwf ADCON0 call DoADConversion ; TIME_CV works in reverse - higher values make shorter times, ; so the value from the AD needs inverting xorlw D'255' ; We assume a 128 offset - the centre is zero movwf TIME_CV ; Bipolar CV -128 - > 0 -> 127 ; Note that there is no glitch problem here, since the ; value can be updated in a single instruction. ; This allows much faster modulation on the TIME_CV input. goto MainLoop ; Update the Output Level CV OutputLevelCV movlw b'00011101' ; AN7, ADC On movwf ADCON0 call DoADConversion movwf LEVEL_CV ; Simply store this one- easy! ; Again,no glitch problem, therefore LEVEL_CV input ; can cope with fast modulation too. goto MainLoop ;-------------------------------------------------------------------- ; Curve Lookup Tables ; The Attack and Decay/Release curves are stored separately. ; This is because the Attack curve heads towards 6.5V but stops at 5V, ; whereas the Decay/Release curves actually arrive at their destination value. ; Note that because the linear count goes UP in all cases and ; hence needs inverting, the Decay/Release curve is the same way up as the Attack. ; This way it gets the same inversion as the linear count for Decay/Release. ;-------------------------------------------------------------------- GetAttackCurve movwf LOOKUPTEMP movlw HIGH AttackCurveLookup movwf PCLATH movfw LOOKUPTEMP addlw LOW AttackCurveLookup btfsc CARRY incf PCLATH, F movwf PCL AttackCurveLookup dt D'0', D'1', D'3', D'5', D'7', D'9', D'11', D'13', D'14', D'16', D'18', D'20', D'22', D'23', D'25', D'27' dt D'29', D'30', D'32', D'34', D'35', D'37', D'39', D'41', D'42', D'44', D'45', D'47', D'49', D'50', D'52', D'54' dt D'55', D'57', D'58', D'60', D'61', D'63', D'65', D'66', D'68', D'69', D'71', D'72', D'74', D'75', D'76', D'78' dt D'79', D'81', D'82', D'84', D'85', D'87', D'88', D'89', D'91', D'92', D'93', D'95', D'96', D'98', D'99', D'100' dt D'102', D'103', D'104', D'105', D'107', D'108', D'109', D'111', D'112', D'113', D'114', D'116', D'117', D'118', D'119', D'121' dt D'122', D'123', D'124', D'125', D'126', D'128', D'129', D'130', D'131', D'132', D'133', D'135', D'136', D'137', D'138', D'139' dt D'140', D'141', D'142', D'143', D'144', D'146', D'147', D'148', D'149', D'150', D'151', D'152', D'153', D'154', D'155', D'156' dt D'157', D'158', D'159', D'160', D'161', D'162', D'163', D'164', D'165', D'166', D'167', D'168', D'169', D'170', D'170', D'171' dt D'172', D'173', D'174', D'175', D'176', D'177', D'178', D'179', D'179', D'180', D'181', D'182', D'183', D'184', D'185', D'185' dt D'186', D'187', D'188', D'189', D'190', D'190', D'191', D'192', D'193', D'194', D'194', D'195', D'196', D'197', D'198', D'198' dt D'199', D'200', D'201', D'201', D'202', D'203', D'204', D'204', D'205', D'206', D'206', D'207', D'208', D'209', D'209', D'210' dt D'211', D'211', D'212', D'213', D'213', D'214', D'215', D'216', D'216', D'217', D'218', D'218', D'219', D'219', D'220', D'221' dt D'221', D'222', D'223', D'223', D'224', D'225', D'225', D'226', D'226', D'227', D'228', D'228', D'229', D'229', D'230', D'231' dt D'231', D'232', D'232', D'233', D'233', D'234', D'235', D'235', D'236', D'236', D'237', D'237', D'238', D'238', D'239', D'239' dt D'240', D'240', D'241', D'242', D'242', D'243', D'243', D'244', D'244', D'245', D'245', D'246', D'246', D'247', D'247', D'248' dt D'248', D'249', D'249', D'249', D'250', D'250', D'251', D'251', D'252', D'252', D'253', D'253', D'254', D'254', D'255', D'255' GetDecayCurve movwf LOOKUPTEMP movlw HIGH DecayCurveLookup movwf PCLATH movfw LOOKUPTEMP addlw LOW DecayCurveLookup btfsc CARRY incf PCLATH, F movwf PCL DecayCurveLookup dt D'0', D'7', D'14', D'20', D'27', D'33', D'40', D'46', D'52', D'57', D'63', D'68', D'73', D'79', D'83', D'88' dt D'93', D'98', D'102', D'106', D'110', D'114', D'118', D'122', D'126', D'130', D'133', D'137', D'140', D'143', D'146', D'149' dt D'152', D'155', D'158', D'161', D'163', D'166', D'168', D'171', D'173', D'176', D'178', D'180', D'182', D'184', D'186', D'188' dt D'190', D'192', D'194', D'195', D'197', D'199', D'200', D'202', D'203', D'205', D'206', D'208', D'209', D'210', D'211', D'213' dt D'214', D'215', D'216', D'217', D'218', D'219', D'220', D'221', D'222', D'223', D'224', D'225', D'226', D'227', D'228', D'228' dt D'229', D'230', D'231', D'231', D'232', D'233', D'233', D'234', D'234', D'235', D'236', D'236', D'237', D'237', D'238', D'238' dt D'239', D'239', D'240', D'240', D'241', D'241', D'241', D'242', D'242', D'243', D'243', D'243', D'244', D'244', D'244', D'245' dt D'245', D'245', D'245', D'246', D'246', D'246', D'247', D'247', D'247', D'247', D'247', D'248', D'248', D'248', D'248', D'249' dt D'249', D'249', D'249', D'249', D'249', D'250', D'250', D'250', D'250', D'250', D'250', D'251', D'251', D'251', D'251', D'251' dt D'251', D'251', D'251', D'252', D'252', D'252', D'252', D'252', D'252', D'252', D'252', D'252', D'252', D'253', D'253', D'253' dt D'253', D'253', D'253', D'253', D'253', D'253', D'253', D'253', D'253', D'253', D'253', D'254', D'254', D'254', D'254', D'254' dt D'254', D'254', D'254', D'254', D'254', D'254', D'254', D'254', D'254', D'254', D'254', D'254', D'254', D'254', D'254', D'254' dt D'254', D'254', D'254', D'254', D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255' dt D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255' dt D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255' dt D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255', D'255' ;----------------------------------------- ; Control Lookup Table ; Converts from 0-255 CV input to 20 bit ; PHASE_INC value ; The tables should be called with the ; index in W, and will return the required ; value ;----------------------------------------- GetPhaseIncHi movlw HIGH PhaseLookupHi movwf PCLATH movf PHASE_INC_INDEX, w addlw LOW PhaseLookupHi btfsc CARRY incf PCLATH, f movwf PCL PhaseLookupHi dt 0xc, 0xc, 0xb, 0xb, 0xb, 0xa, 0xa, 0x9, 0x9, 0x9, 0x8, 0x8, 0x8, 0x8, 0x7, 0x7 dt 0x7, 0x6, 0x6, 0x6, 0x6, 0x6, 0x5, 0x5, 0x5, 0x5, 0x5, 0x4, 0x4, 0x4, 0x4, 0x4 dt 0x4, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2 dt 0x2, 0x2, 0x2, 0x2, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1 dt 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 dt 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 dt 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 dt 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 dt 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 dt 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 dt 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 dt 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 dt 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 dt 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 dt 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 dt 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 GetPhaseIncMid movlw HIGH PhaseLookupMid movwf PCLATH movf PHASE_INC_INDEX, w addlw LOW PhaseLookupMid btfsc CARRY incf PCLATH, f movwf PCL PhaseLookupMid dt 0xcc, 0x59, 0xe9, 0x7d, 0x15, 0xb1, 0x50, 0xf3, 0x99, 0x42, 0xee, 0x9d, 0x4f, 0x4, 0xbc, 0x76 dt 0x32, 0xf1, 0xb2, 0x76, 0x3b, 0x3, 0xcc, 0x98, 0x65, 0x34, 0x5, 0xd8, 0xac, 0x82, 0x59, 0x32 dt 0xc, 0xe7, 0xc4, 0xa2, 0x81, 0x61, 0x43, 0x25, 0x9, 0xed, 0xd3, 0xb9, 0xa0, 0x89, 0x72, 0x5c dt 0x46, 0x32, 0x1e, 0xb, 0xf8, 0xe6, 0xd5, 0xc4, 0xb4, 0xa5, 0x96, 0x88, 0x7a, 0x6d, 0x60, 0x53 dt 0x47, 0x3c, 0x30, 0x26, 0x1b, 0x11, 0x8, 0xfe, 0xf5, 0xed, 0xe4, 0xdc, 0xd4, 0xcd, 0xc6, 0xbf dt 0xb8, 0xb1, 0xab, 0xa5, 0x9f, 0x99, 0x94, 0x8f, 0x8a, 0x85, 0x80, 0x7c, 0x77, 0x73, 0x6f, 0x6b dt 0x67, 0x63, 0x60, 0x5d, 0x59, 0x56, 0x53, 0x50, 0x4d, 0x4a, 0x48, 0x45, 0x43, 0x40, 0x3e, 0x3c dt 0x3a, 0x38, 0x36, 0x34, 0x32, 0x30, 0x2e, 0x2d, 0x2b, 0x2a, 0x28, 0x27, 0x25, 0x24, 0x23, 0x21 dt 0x20, 0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18, 0x17, 0x16, 0x16, 0x15, 0x14, 0x13, 0x13 dt 0x12, 0x11, 0x11, 0x10, 0xf, 0xf, 0xe, 0xe, 0xd, 0xd, 0xc, 0xc, 0xb, 0xb, 0xb, 0xa dt 0xa, 0x9, 0x9, 0x9, 0x8, 0x8, 0x8, 0x8, 0x7, 0x7, 0x7, 0x6, 0x6, 0x6, 0x6, 0x6 dt 0x5, 0x5, 0x5, 0x5, 0x5, 0x4, 0x4, 0x4, 0x4, 0x4, 0x4, 0x3, 0x3, 0x3, 0x3, 0x3 dt 0x3, 0x3, 0x3, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x2, 0x1, 0x1 dt 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1 dt 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 dt 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 GetPhaseIncLo movlw HIGH PhaseLookupLo movwf PCLATH movf PHASE_INC_INDEX, w addlw LOW PhaseLookupLo btfsc CARRY incf PCLATH, f movwf PCL PhaseLookupLo dt 0xcc, 0x0, 0x4c, 0x8a, 0x97, 0x50, 0x95, 0x45, 0x40, 0x6b, 0xa6, 0xd8, 0xe4, 0xb2, 0x28, 0x2e dt 0xae, 0x90, 0xbf, 0x26, 0xb1, 0x4e, 0xe8, 0x6f, 0xd0, 0xfb, 0xe0, 0x70, 0x9a, 0x51, 0x86, 0x2c dt 0x37, 0x98, 0x45, 0x32, 0x53, 0x9d, 0x6, 0x84, 0xd, 0x97, 0x1a, 0x8c, 0xe6, 0x1e, 0x2e, 0xd dt 0xb4, 0x1d, 0x40, 0x16, 0x9a, 0xc5, 0x91, 0xf9, 0xf7, 0x86, 0xa1, 0x42, 0x66, 0x6, 0x20, 0xaf dt 0xae, 0x19, 0xee, 0x27, 0xc2, 0xbb, 0xe, 0xba, 0xb9, 0xa, 0xaa, 0x95, 0xca, 0x45, 0x4, 0x4 dt 0x44, 0xc1, 0x79, 0x6a, 0x91, 0xee, 0x7d, 0x3e, 0x2e, 0x4c, 0x96, 0xb, 0xa9, 0x6e, 0x5a, 0x6a dt 0x9f, 0xf5, 0x6d, 0x5, 0xbb, 0x8f, 0x80, 0x8d, 0xb4, 0xf5, 0x4f, 0xc1, 0x4a, 0xe9, 0x9e, 0x67 dt 0x45, 0x36, 0x39, 0x4f, 0x75, 0xad, 0xf4, 0x4c, 0xb2, 0x27, 0xa9, 0x39, 0xd7, 0x80, 0x36, 0xf7 dt 0xc4, 0x9c, 0x7e, 0x6a, 0x60, 0x5f, 0x67, 0x79, 0x92, 0xb4, 0xdd, 0xe, 0x47, 0x86, 0xcd, 0x1a dt 0x6d, 0xc6, 0x25, 0x8a, 0xf4, 0x64, 0xd9, 0x53, 0xd1, 0x54, 0xdb, 0x67, 0xf7, 0x8b, 0x22, 0xbd dt 0x5c, 0xfe, 0xa4, 0x4d, 0xf9, 0xa7, 0x59, 0xe, 0xc5, 0x7e, 0x3b, 0xf9, 0xba, 0x7d, 0x43, 0xa dt 0xd3, 0x9f, 0x6c, 0x3b, 0xb, 0xde, 0xb2, 0x87, 0x5e, 0x37, 0x10, 0xec, 0xc8, 0xa6, 0x85, 0x65 dt 0x46, 0x29, 0xc, 0xf1, 0xd6, 0xbc, 0xa3, 0x8c, 0x75, 0x5e, 0x49, 0x34, 0x20, 0xd, 0xfa, 0xe9 dt 0xd7, 0xc7, 0xb6, 0xa7, 0x98, 0x8a, 0x7c, 0x6e, 0x61, 0x55, 0x49, 0x3d, 0x32, 0x27, 0x1d, 0x12 dt 0x9, 0xff, 0xf6, 0xee, 0xe5, 0xdd, 0xd5, 0xce, 0xc6, 0xbf, 0xb9, 0xb2, 0xac, 0xa6, 0xa0, 0x9a dt 0x95, 0x8f, 0x8a, 0x85, 0x81, 0x7c, 0x78, 0x73, 0x6f, 0x6b, 0x68, 0x64, 0x60, 0x5d, 0x5a, 0x56 ; We never reach here end