Subversion Repositories svn.mios

Rev

Blame | Last modification | View Log | RSS feed

; $Id: tia_sw.inc bdupeyron.tech@gmail.com(Antichambre)
;
; MIDIbox TIA
; Software Synthesizer Engine
; mostly optimized for PIC16F - special adaption for PIC18F to be done
;  
; Activate this #define to measure the performance with a scope
; (connect the probe to J14)
#define TIA_SW_MEASURE_PERFORMANCE 0
;
; ==========================================================================
;
;  Copyright 1998-2006 Thorsten Klose (tk@midibox.org)
;  Idea for ENV Curve Parameter and OSC synchronization by Jess D. Skov-Nielsen
;  Licensed for personal non-commercial use only.
;  All other rights reserved.
; 
; ==========================================================================

;; ==========================================================================
;;  TIA Flags
;; ==========================================================================




Vx_STAT_VOICE_ACTIVE     EQU 0
Vx_STAT_GATE_SET_REQ     EQU 1
Vx_STAT_GATE_CLR_REQ     EQU 2
Vx_STAT_GATE_ACTIVE      EQU 3
Vx_STAT_GATE_NOTE_ON     EQU 4
Vx_STAT_ENV_ACTIVE       EQU 5
Vx_STAT_PORTA_ENABLE     EQU 6
Vx_STAT_GRP_REQ          EQU 7

Vx_MODE_GSA_ACTIVE       EQU 0  ;; Gate Stay Active
Vx_MODE_GRP_ACTIVE       EQU 1  ;; Gate Resyncs Polynom Active
Vx_MODE_PORTA_CONST      EQU 2
Vx_MODE_KEY_EXTENDED     EQU 3
Vx_MODE_VEL2AMP_ON       EQU 4
Vx_MODE_ARP_SYNC_ON      EQU 5

Vx_ENV_MODTYP_EM         EQU 0
Vx_ENV_MODTYP_ExM        EQU 1
Vx_ENV_TOAMP_ON          EQU 2
Vx_ENV_TOPITCH_ON        EQU 3
Vx_ENV_SYNC_ON           EQU 4

Vx_OPTION_WT_ON          EQU 0
Vx_OPTION_WTSYNC_ON      EQU 1
Vx_OPTION_KIT_ON         EQU 2
Vx_OPTION_SAMPLER_ON     EQU 3
;; A ajouter dans MAX
Vx_OPTION_ONLY_WT_OFF    EQU 4
Vx_OPTION_TB303          EQU 5
Vx_OPTION_ENV2PORTA      EQU 6

LFOx_MODE_ENABLE         EQU 0
LFOx_MODE_SYNC           EQU 1
LFOx_MODE_SYNC_ALL       EQU 2
LFOx_MODE_RESERVED       EQU 3
LFOx_MODE_WAVEFORM0      EQU 4
LFOx_MODE_WAVEFORM1      EQU 5
LFOx_MODE_WAVEFORM2      EQU 6
LFOx_MODE_DECINC         EQU 7

ENVx_MODE_ATTACK         EQU 0
ENVx_MODE_DECAY          EQU 1
ENVx_MODE_SUSTAIN        EQU 2
ENVx_MODE_RELEASE        EQU 3
ENVx_MODE_FREE           EQU 4
;ENVx_MODE_RESERVED      EQU 5
;ENVx_MODE_RESERVED      EQU 6
;ENVx_MODE_RESERVED      EQU 7


ASSIGNED_LFOS_1     EQU 0
ASSIGNED_LFOS_2     EQU 1
ASSIGNED_LFOS_3     EQU 2
ASSIGNED_LFOS_4     EQU 3
ASSIGNED_ENVS_1     EQU 4
ASSIGNED_ENVS_2     EQU 5
ASSIGNED_ENVS_A0    EQU 6
ASSIGNED_ENVS_A1    EQU 7

WT_STATE_STOP       EQU 0
WT_STATE_RESET      EQU 1
WT_STATE_INIT_REQ   EQU 2
WT_STATE_SEND_CLK   EQU 3   ; WT Voice 1(Aud0) prior
WT_STATE_GATE       EQU 4   ; for TB303 option
WT_STATE_SLIDE      EQU 5
WT_STATE_SLIDE_PREV EQU 6
WT_STATE_PLAY_2ND   EQU 7

SE_OPTION_TB303     EQU 0
SE_OPTION_FIP       EQU 1
SE_OPTION_ENV2PORTA EQU 2
SE_OPTION_ENV2VOL   EQU 3
SE_OPTION_GSA       EQU 4

;; ==========================================================================


;; --------------------------------------------------------------------------
;;  TIA Software Handler: Software Synthesizer part for the TIA
;;  called by User Timer every 800 us
;; --------------------------------------------------------------------------
TIA_SW_Handler
#if TIA_SW_MEASURE_PERFORMANCE
    bsf PORTD, 4
#endif

    SET_BSR TIA_BASE        ; prepare BSR for TIA register access

    ;; return immediately if engine has been disabled
    btfsc   TIA_STAT, TIA_STAT_ENGINE_DISABLE
    return

    ;; handle with MIDI clock
TIA_SW_Clk
    incf    TIA_MIDI_SYNC_CTR, F, BANKED

    ;; register last counter value on 0xf8 or if TIA_MIDI_SYNC_CTR == 0xff (no clock received)
    bcf TIA_MIDI_SYNC, TIA_MIDI_SYNC_TICK_1, BANKED
    incf    TIA_MIDI_SYNC_CTR, W, BANKED
    bz  TIA_SW_Clk_F8
    BRA_IFCLR TIA_MIDI_SYNC, TIA_MIDI_SYNC_F8, BANKED, TIA_SW_Clk_NoF8
    bcf TIA_MIDI_SYNC, TIA_MIDI_SYNC_F8, BANKED
TIA_SW_Clk_F8
    bsf TIA_MIDI_SYNC, TIA_MIDI_SYNC_TICK_1, BANKED
    movff   TIA_MIDI_SYNC_CTR, TIA_MIDI_SYNC_CTR_REG
    clrf    TIA_MIDI_SYNC_CTR, BANKED
TIA_SW_Clk_NoF8

    ;; handle with double resolution of TIA_SW clock
    bcf TIA_MIDI_SYNC, TIA_MIDI_SYNC_TICK_2, BANKED

    ;; if MIDI sync enabled: clock LFOs/ENVs two times on every MIDI clock event
    movf    TIA_MIDI_SYNC_CTR, W, BANKED
    skpnz
    bsf TIA_MIDI_SYNC, TIA_MIDI_SYNC_TICK_2, BANKED

    clrc
    rrf TIA_MIDI_SYNC_CTR_REG, W, BANKED
    xorwf   TIA_MIDI_SYNC_CTR, W, BANKED
    skpnz
    bsf TIA_MIDI_SYNC, TIA_MIDI_SYNC_TICK_2, BANKED

    ;; A MIDI clock start event restarts all LFOs
    BRA_IFCLR TIA_MIDI_SYNC, TIA_MIDI_SYNC_FA, BANKED, TIA_SW_Clk_NoFA
    bcf TIA_MIDI_SYNC, TIA_MIDI_SYNC_FA, BANKED
TIA_SW_Clk_FA
    call    TIA_SW_Hlp_SyncAllLFOs

    ;btfsc  TIA_SE_OPTION, SE_OPTION_TB303, BANKED
    movlw   TIA_Vx_WT_STATE
    bsf PLUSW0, WT_STATE_RESET

TIA_SW_Clk_NoFA

    movff   PRODL, SAVED_PRODL  ; save PROD[LH] - we are in an interrupt routine
    movff   PRODH, SAVED_PRODH

    ;; generate new pseudo-random number
    movf    TIA_LFO_RANDOM_SEED_L, W, BANKED
    mulwf   TIA_LFO_RANDOM_SEED_H, BANKED
    movf    TMR0L, W
    addwf   PRODL, W
    movwf   TIA_LFO_RANDOM_SEED_L, BANKED
    movlw   0x69
    addwfc  PRODH, W
    movwf   TIA_LFO_RANDOM_SEED_H, BANKED


    SET_BSR TIA_BASE
    clrf    TIA_SW_VOICE, BANKED    ; loop counter
    lfsr    FSR1, TIA_V1_BASE
TIA_SW_Wt_VoiceLoop

    ;; wavetable handler: check for MIDI Sync
    movlw   TIA_Vx_OPTION
    BRA_IFCLR PLUSW0, Vx_OPTION_WTSYNC_ON, ACCESS, TIA_SW_Wt
    BRA_IFCLR TIA_MIDI_SYNC, TIA_MIDI_SYNC_TICK_1, BANKED, TIA_SW_Wt_VoiceLoop_Next
TIA_SW_Wt
    movlw   TIA_Vx_WT_RATE
    movf    PLUSW1, W
    bz  TIA_SW_Wt_VoiceLoop_Next

    movlw   TIA_Vx_WT_CTR
    movf    PLUSW1, W
    bnz TIA_SW_Wt_Next

    movlw   TIA_Vx_OPTION
    BRA_IFCLR PLUSW0, Vx_OPTION_WTSYNC_ON, ACCESS, TIA_SW_Wt_IntClk
TIA_SW_Wt_ExtClk
    movlw   TIA_Vx_WT_RATE
    comf    PLUSW1, W
    andlw   0x7f
    movwf   IRQ_TMP1
;   btfss   TIA_SE_OPTION, SE_OPTION_TB303, BANKED
;   addlw 1

    movlw   TIA_Vx_WT_CTR
    movff   IRQ_TMP1, PLUSW1
    rgoto   TIA_SW_Wt_PlayNext
TIA_SW_Wt_IntClk
    movlw   TIA_Vx_WT_RATE
    comf    PLUSW1, W
    andlw   0x7f
    skpnz           ; never use 0x00 (avoid wdt reset on overloaded engine)
    addlw   1
    movwf   IRQ_TMP1
    movlw   TIA_Vx_WT_CTR
    movff   IRQ_TMP1, PLUSW1
    clrc
    rlf     PLUSW1, F
    ;;  rgoto   TIA_SW_Wt_PlayNext

TIA_SW_Wt_PlayNext
    movlw   TIA_Vx_WT_CLK_REQ_CTR
    incf    PLUSW1, F
    rgoto   TIA_SW_Wt_VoiceLoop_Next

TIA_SW_Wt_Next
    movlw   TIA_Vx_WT_CTR
    decf    PLUSW1, F

TIA_SW_Wt_VoiceLoop_Next
    movlw   TIA_Vx_RECORD_LEN
    addwf   FSR1L, F
    incf    TIA_SW_VOICE, F, BANKED
    movlw   2-1
    cpfsgt  TIA_SW_VOICE, BANKED
    rgoto TIA_SW_Wt_VoiceLoop



    ;; ARPs: check for MIDI Sync
TIA_SW_ARPs
    lfsr    FSR1, TIA_V1_BASE
    movlw   TIA_Vx_MODE
    btfsc   PLUSW1, Vx_MODE_ARP_SYNC_ON
    btfsc   TIA_MIDI_SYNC, TIA_MIDI_SYNC_TICK_2, BANKED
    rcall   TIA_SW_Arp
    lfsr    FSR1, TIA_V2_BASE
    movlw   TIA_Vx_MODE
    btfsc   PLUSW1, Vx_MODE_ARP_SYNC_ON
    btfsc   TIA_MIDI_SYNC, TIA_MIDI_SYNC_TICK_2, BANKED
    rcall   TIA_SW_Arp
TIA_SW_ARPs_End

    ;; LFOs: check for MIDI Sync
TIA_SW_LFOs
    clrf    TIA_SW_LFO_NUMBER, BANKED
    lfsr    FSR1, TIA_LFO1_BASE
    btfsc   TIA_MOD_SYNC, ASSIGNED_LFOS_1, BANKED
    btfsc   TIA_MIDI_SYNC, TIA_MIDI_SYNC_TICK_2, BANKED
    rcall   TIA_SW_LFO
    incf    TIA_SW_LFO_NUMBER, F, BANKED
    lfsr    FSR1, TIA_LFO2_BASE
    btfsc   TIA_MOD_SYNC, ASSIGNED_LFOS_2, BANKED
    btfsc   TIA_MIDI_SYNC, TIA_MIDI_SYNC_TICK_2, BANKED
    rcall   TIA_SW_LFO
    incf    TIA_SW_LFO_NUMBER, F, BANKED
    lfsr    FSR1, TIA_LFO3_BASE
    btfsc   TIA_MOD_SYNC, ASSIGNED_LFOS_3, BANKED
    btfsc   TIA_MIDI_SYNC, TIA_MIDI_SYNC_TICK_2, BANKED
    rcall   TIA_SW_LFO
    incf    TIA_SW_LFO_NUMBER, F, BANKED
    lfsr    FSR1, TIA_LFO4_BASE
    btfsc   TIA_MOD_SYNC, ASSIGNED_LFOS_4, BANKED
    btfsc   TIA_MIDI_SYNC, TIA_MIDI_SYNC_TICK_2, BANKED
    rcall   TIA_SW_LFO
TIA_SW_LFOs_End

    ;; ENVs: check for MIDI Sync
TIA_SW_ENVs
    ;; Enveloppe for AUD0(voice 1)
    clrf    TIA_SW_ENV_NUMBER, BANKED
    lfsr    FSR1, TIA_V1_ENV_BASE
    lfsr    FSR2, TIA_V1_BASE
    movlw   TIA_Vx_ENV_MODE
    BRA_IFCLR   PLUSW2, Vx_ENV_SYNC_ON, ACCESS, TIA_SW_ENVs_v1_Ok
    BRA_IFCLR   TIA_MIDI_SYNC, TIA_MIDI_SYNC_TICK_2, BANKED, TIA_SW_ENVs_v1_Nok
TIA_SW_ENVs_v1_Ok
    movf    TIA_Vx_ENV_CURVES, W, BANKED
    andlw   0x07
    rcall   TIA_SW_ENV
TIA_SW_ENVs_v1_Nok
    ;; Enveloppe for AUD1(voice 2)
    incf    TIA_SW_ENV_NUMBER, BANKED
    lfsr    FSR1, TIA_V2_ENV_BASE
    lfsr    FSR2, TIA_V2_BASE
    movlw   TIA_Vx_ENV_MODE
    BRA_IFCLR   PLUSW2, Vx_ENV_SYNC_ON, ACCESS, TIA_SW_ENVs_v2_Ok
    BRA_IFCLR   TIA_MIDI_SYNC, TIA_MIDI_SYNC_TICK_2, BANKED, TIA_SW_ENVs_v2_Nok
TIA_SW_ENVs_v2_Ok
    swapf   TIA_Vx_ENV_CURVES, W, BANKED
    andlw   0x07
    rcall   TIA_SW_ENV
TIA_SW_ENVs_v2_Nok
    ;; Enveloppe 1
    incf    TIA_SW_ENV_NUMBER, BANKED
    lfsr    FSR1, TIA_ENV1_BASE
    movf    TIA_ENVx_CURVES, W, BANKED
    btfsc   TIA_MOD_SYNC, ASSIGNED_ENVS_1, BANKED
    btfsc   TIA_MIDI_SYNC, TIA_MIDI_SYNC_TICK_2, BANKED
    rcall   TIA_SW_ENV
    ;; Enveloppe 2
    incf    TIA_SW_ENV_NUMBER, BANKED
    lfsr    FSR1, TIA_ENV2_BASE
    swapf   TIA_ENVx_CURVES, W, BANKED
    btfsc   TIA_MOD_SYNC, ASSIGNED_ENVS_2, BANKED
    btfsc   TIA_MIDI_SYNC, TIA_MIDI_SYNC_TICK_2, BANKED
    rcall   TIA_SW_ENV
TIA_SW_ENVs_End

    SET_BSR TIA_BASE
    clrf    TIA_SW_VOICE, BANKED    ; loop counter
    lfsr    FSR1, TIA_V1_BASE
TIA_SW_VoiceLoop
    movlw   TIA_Vx_NOTE
    movf    PLUSW1, W
    bz  TIA_SW_VoiceLoop_NoPitchChange
TIA_SW_VoiceLoop_PitchChange
    rcall   TIA_SW_Note
    rcall   TIA_SW_Pitch
   
TIA_SW_VoiceLoop_NoPitchChange
    rcall   TIA_SW_Porta
    rcall   TIA_SW_Amp

TIA_SW_VoiceLoop_Next
    movlw   TIA_Vx_RECORD_LEN
    addwf   FSR1L, F
    incf    TIA_SW_VOICE, F, BANKED
    movlw   2-1
    cpfsgt  TIA_SW_VOICE, BANKED
    rgoto TIA_SW_VoiceLoop

TIA_SW_Handler_End

#if TIA_SW_MEASURE_PERFORMANCE
    bcf PORTD, 4
#endif

    return





;; --------------------------------------------------------------------------
;; This function handles the arpeggiator
;; --------------------------------------------------------------------------
TIA_SW_Arp
    ;; skip if arpeggiator rate == 0
    movlw   TIA_Vx_ARP_RATE
    movf    PLUSW1, W
    bz  TIA_SW_Arp_End

    ;; a counter is incremented on each function call
    ;; arpeggiator is stepped forward once the counter has reached the
    ;; compare value: rate^0x7f + 1, multiply by 2 if MIDI sync not enabled
    xorlw   0x7f
    addlw   1
    movwf   IRQ_TMP1    ; compare value => IRQ_TMP1
    clrc
    movlw   TIA_Vx_MODE
    btfss   PLUSW1, Vx_MODE_ARP_SYNC_ON, ACCESS ; (*2 reduce the rate a little if no MIDI sync)
    rlf IRQ_TMP1, F

    movlw   1
    movwf   IRQ_TMP2    ; incrementer => IRQ_TMP2

    ;; special option: constant time arp cycle
    ;; if 1 key is pressed, use the original incrementer
    ;; if 2 keys are pressed, increment by 2
    ;; if 3 keys are pressed, increment by 3
    ;; if 4 keys are pressed, increment by 4
    movlw   TIA_Vx_NOTE_STACK_1
    movf    PLUSW1, W
    skpz
    incf    IRQ_TMP2, F

    movlw   TIA_Vx_NOTE_STACK_2
    movf    PLUSW1, W
    skpz
    incf    IRQ_TMP2, F
    
    movlw   TIA_Vx_NOTE_STACK_3
    movf    PLUSW1, W
    skpz
    incf    IRQ_TMP2, F

    ;; increment counter
    movlw   TIA_Vx_ARP_CTR
    movf    PLUSW1, W
    addwf   IRQ_TMP2, F
    bc  TIA_SW_Arp_Overrun
    movlw   TIA_Vx_ARP_CTR
    movff   IRQ_TMP2, PLUSW1

    ;; branch to the end so long the counter is less than the compare value
    movf    IRQ_TMP2, W
    subwf   IRQ_TMP1, W
    bc  TIA_SW_Arp_End

TIA_SW_Arp_Overrun
    ;; clear counter
    movlw   TIA_Vx_ARP_CTR
    clrf    PLUSW1

    ;; increment note number (1 of 4)
    movlw   TIA_Vx_ARP_NOTE_NUMBER
    incf    PLUSW1, W
    andlw   0x03
    movwf   IRQ_TMP1

    ;; reset note number if last one reached (TIA_Vx_NOTE_STACK_x is zero)
    movlw   TIA_Vx_NOTE_STACK_0
    addwf   IRQ_TMP1, W
    movf    PLUSW1, W
    skpnz
    clrf    IRQ_TMP1

    ;; save new note number
    movlw   TIA_Vx_ARP_NOTE_NUMBER
    movff   IRQ_TMP1, PLUSW1
    
    ;; select note
    movlw   TIA_Vx_ARP_NOTE_0
    addwf   IRQ_TMP1, W

    ;; save new note number if != zero and != last note
    movf    PLUSW1, W
    bz  TIA_SW_Arp_NoNewNote
    movwf   IRQ_TMP1
    movlw   TIA_Vx_NOTE
    movf    PLUSW1, W
    xorwf   IRQ_TMP1, W
    bz  TIA_SW_Arp_NoNewNote
TIA_SW_Arp_NewNote
    movlw   TIA_Vx_NOTE
    movff   IRQ_TMP1, PLUSW1


    movlw   TIA_Vx_PORTA_RATE
    movf    PLUSW1, W
    bz  TIA_SW_Arp_NoPorta
TIA_SW_Arp_Porta
    movlw   TIA_Vx_STAT
    bsf PLUSW1, Vx_STAT_PORTA_ENABLE

    ;; store current frequency in TIA_Vx_PORTA_FRQ_L


    movlw   TIA_Vx_FRQ_L
    movff   PLUSW1, IRQ_TMP1
    movlw   TIA_Vx_FRQ_H
    movff   PLUSW1, IRQ_TMP2    

    movlw   TIA_Vx_PORTA_FRQ_L
    movff   IRQ_TMP1, PLUSW1
    movlw   TIA_Vx_PORTA_FRQ_H
    movff   IRQ_TMP2, PLUSW1   

    movlw   TIA_Vx_PORTA_CTR_L 
    clrf    PLUSW1
    movlw   TIA_Vx_PORTA_CTR_H 
    clrf    PLUSW1

    
TIA_SW_Arp_NoV1

TIA_SW_Arp_NoPorta

TIA_SW_Arp_NoNewNote

TIA_SW_Arp_End
    return


;; --------------------------------------------------------------------------
;; This function handles the gates and initial note frequency
;; --------------------------------------------------------------------------
TIA_SW_Note


   
    ;; check note delay counter, set/clear gate bit
    movlw   TIA_Vx_NOTE_DELAY_CTR
    movf    PLUSW1, W
    bz  TIA_SW_Note_NoDelay
    movlw   TIA_Vx_NOTE_DELAY_CTR
    decf    PLUSW1, F
    rgoto   TIA_SW_Note_DelayCont
TIA_SW_Note_NoDelay
    movlw   TIA_Vx_STAT
    BRA_IFSET PLUSW1, Vx_STAT_GATE_CLR_REQ, ACCESS, TIA_SW_Note_NoteOffReq
    BRA_IFSET PLUSW1, Vx_STAT_GATE_SET_REQ, ACCESS, TIA_SW_Note_NoteOnReq
    rgoto   TIA_SW_Note_DelayCont
    
TIA_SW_Note_NoteOffReq
    bcf     PLUSW1, Vx_STAT_GATE_CLR_REQ
    bcf     PLUSW1, Vx_STAT_GATE_NOTE_ON 
    movlw   TIA_Vx_MODE
    ; (don't clear gate bit if GSA (gate stays active) 
    BRA_IFSET PLUSW1, Vx_MODE_GSA_ACTIVE, ACCESS, TIA_SW_Note_NoteOffReqSkp
    movlw   TIA_Vx_STAT
    bcf     PLUSW1, Vx_STAT_GATE_ACTIVE
TIA_SW_Note_NoteOffReqSkp
    ;; sync voice ENVs
    movlw   TIA_Vx_ENV_MODE
    movf     PLUSW1, W
    ;rrf     PLUSW1, W
    ;rrncf   WREG, W
    andlw   0x0c
    btfsc   TIA_SW_VOICE, 0, BANKED
    swapf   WREG, W
    movwf   IRQ_TMP1
    ;; sync assigned ENVs
    movlw   TIA_Vx_AMP_MOD
    movf    PLUSW1, W
    andlw   0x30
    iorwf   IRQ_TMP1, F
    
    movlw   TIA_Vx_PITCH_MOD    
    swapf   PLUSW1, W
    andlw   0x03
    iorwf   IRQ_TMP1, W
    call    TIA_SW_Hlp_ENVRelease
    rgoto   TIA_SW_Note_DelayCont

TIA_SW_Note_NoteOnReq
    movlw   TIA_Vx_STAT
    bcf     PLUSW1, Vx_STAT_GATE_SET_REQ
    bsf     PLUSW1, Vx_STAT_GATE_NOTE_ON 
TIA_SW_Note_NoteOnReqSkp
    movlw   TIA_Vx_STAT
    bsf     PLUSW1, Vx_STAT_GATE_ACTIVE
    
    ;; sync assigned LFOs
    movlw   TIA_Vx_AMP_MOD
    movf    PLUSW1, W
    andlw   0x07
    movwf   IRQ_TMP1
    
    movlw   TIA_Vx_PITCH_MOD    
    movf    PLUSW1, W
    andlw   0x07
    iorwf   IRQ_TMP1, W
    call    TIA_SW_Hlp_SyncLFOs

    ;; sync voice ENVs
    movlw   TIA_Vx_ENV_MODE
    movf     PLUSW1, W
    ;rrf     PLUSW1, W
    ;;rrncf   WREG, W
    andlw   0x0c
    btfsc   TIA_SW_VOICE, 0, BANKED
    swapf   WREG, W
    movwf   IRQ_TMP1
 
    ;; sync assigned ENVs
    movlw   TIA_Vx_AMP_MOD
    movf    PLUSW1, W
    andlw   0x30
    iorwf   IRQ_TMP1, F
    
    movlw   TIA_Vx_PITCH_MOD    
    swapf   PLUSW1, W
    andlw   0x03
    iorwf   IRQ_TMP1, W
    call    TIA_SW_Hlp_ENVAttack
    


TIA_SW_Note_DelayCont

    movlw   TIA_Vx_TRANSPOSE
    movf    PLUSW1, W
    sublw   0x40
    xorlw   0xff
    movwf   IRQ_TMP1
    movlw   TIA_Vx_NOTE
    movf    PLUSW1, W
    addwf   IRQ_TMP1, W
    addlw   1
    movwf   IRQ_TMP2
    sublw   0x7f
    bc  TIA_SW_Note_NoOverflow
    movlw   TIA_Vx_TRANSPOSE
    BRA_IFSET PLUSW1, 6, ACCESS, TIA_SW_Note_PosSaturation
TIA_SW_Note_NegSaturation
    clrf    IRQ_TMP2
    rgoto   TIA_SW_Note_NoOverflow
TIA_SW_Note_PosSaturation
    movlw   0x7f
    movwf   IRQ_TMP2
TIA_SW_Note_NoOverflow
    ;; set target frequency depending on note number
    movf    IRQ_TMP2, W
    btfsc   WREG, 7; the note value
    movlw 0x7f
    movwf   IRQ_TMP2
    
    movlw   TIA_Vx_MODE
    BRA_IFSET PLUSW1, Vx_MODE_KEY_EXTENDED, ACCESS, TIA_SW_Note_KeyMode_Extended    
TIA_SW_Note_KeyMode_NonExtended 
    clrc
    movlw   TIA_Vx_KEY_OFFSET
    movf    PLUSW1, W
    subwf   IRQ_TMP2, F
    movlw   0x00
    skpnc
    movlw   0x1f
    cpfsgt  IRQ_TMP2, ACCESS
    movf    IRQ_TMP2, W
    rlf     WREG, F
    rlf     WREG, F
    rlf     WREG, W
    rgoto   TIA_SW_Note_TargetCopy 

TIA_SW_Note_KeyMode_Extended      
    ;;etendu (7bit note to 8bit pitch)
    clrc
    rlf     IRQ_TMP2, W

TIA_SW_Note_TargetCopy
    movwf   MIOS_PARAMETER1
        
    movlw   TIA_Vx_TARGET_FRQ_H
    movff   MIOS_PARAMETER1, PLUSW1
    movlw   TIA_Vx_TARGET_FRQ_L
    clrf    PLUSW1
TIA_SW_Note_End
    return
  
    
;; --------------------------------------------------------------------------
;; This function handles the Note Pitch
;; --------------------------------------------------------------------------
TIA_SW_Pitch
    ;; skip Pitchbender+Finetune processing if PITCHRANGE == zero
    movlw   TIA_Vx_PITCHRANGE
    movf    PLUSW1, W
    bz  TIA_SW_Pitch_MOD

    
    ;; result stored in IRQ_TMP[12]
    clrf    IRQ_TMP1
    clrf    IRQ_TMP2
    clrf    IRQ_TMP3

    ;; calc IRQ_TMP[12] = pitchbender (9-bit signed)
    movlw   TIA_Vx_PITCHBENDER
    movf    PLUSW1, W
    movwf   IRQ_TMP1
    bz  TIA_SW_Pitch_NoPitchBender
TIA_SW_Pitch_PitchBender
    clrc            ; multiply with 2
    btfsc   IRQ_TMP1, 7
    setc
    rlf     IRQ_TMP1, F
    rlf     IRQ_TMP3, F
    btfsc   IRQ_TMP3, 0 
    comf    IRQ_TMP1, F
TIA_SW_Pitch_NoPitchBender

    clrc
    ;; skip tuning if IRQ_TMP[12] == zero
    movf    IRQ_TMP1, W
    iorwf   IRQ_TMP2, W
    bz  TIA_SW_Pitch_MOD

    movf    IRQ_TMP1, W
    movwf   MUL_B_L, BANKED
    movf    IRQ_TMP2, W
    movwf   MUL_B_H, BANKED

    ;; get f_in[target], save it in IRQ_TMP[12]
    clrc
    movlw   TIA_Vx_TRANSPOSE
    movf    PLUSW1, W
    sublw   0x40
    xorlw   0xff
    movwf   IRQ_TMP4
    movlw   TIA_Vx_NOTE
    movf    PLUSW1, W
    addwf   IRQ_TMP4, W
    ;; add pitchrange depending on direction with saturation
    BRA_IFSET IRQ_TMP3, 0, ACCESS, TIA_SW_Pitch_Decrease
TIA_SW_Pitch_Increase

    addlw   1
    movwf   IRQ_TMP2
    movlw   TIA_Vx_PITCHRANGE
    movf    PLUSW1, W
    addwf   IRQ_TMP2, W
    btfsc   WREG, 7
    movlw 0x7f
    movwf   IRQ_TMP2
    rgoto   TIA_SW_Pitch_Increase_Cont
TIA_SW_Pitch_Decrease
    movwf   IRQ_TMP2
    movlw   TIA_Vx_PITCHRANGE
    decf    PLUSW1, W
    subwf   IRQ_TMP2, F
    btfsc   IRQ_TMP2, 7
    clrf IRQ_TMP2
TIA_SW_Pitch_Increase_Cont
    ;; set target frequency depending on note number
    movf    IRQ_TMP2, W
    btfsc   WREG, 7; the note value
    movlw 0x7f
    movwf   IRQ_TMP2
    
    movlw   TIA_Vx_MODE
    BRA_IFSET PLUSW1, Vx_MODE_KEY_EXTENDED, ACCESS, TIA_SW_Pitch_KeyMode_Extended    
TIA_SW_Pitch_KeyMode_NonExtended 
    clrc
    movlw   TIA_Vx_KEY_OFFSET
    movf    PLUSW1, W
    subwf   IRQ_TMP2, F
    movlw   0x1f
    cpfsgt  IRQ_TMP2, ACCESS
    movf    IRQ_TMP2, W
    rlf     WREG, F
    rlf     WREG, F
    rlf     WREG, W
    rgoto   TIA_SW_Pitch_KeyMode_End 

TIA_SW_Pitch_KeyMode_Extended      
    ;;etendu (7bits to 8bits note)
    clrc
    rlf     IRQ_TMP2, W
    
TIA_SW_Pitch_KeyMode_End    
    movwf   MIOS_PARAMETER2
    clrf    MIOS_PARAMETER1
    
    ;; result: low-byte in WREG and MIOS_PARAMETER1, high-byte in MIOS_PARAMETER2

    ;; add and multiply to target frequency
    movff   FSR1H, FSR2H
    movf    FSR1L, W
    addlw   TIA_Vx_TARGET_FRQ_L
    movwf   FSR2L
    call    TIA_SW_Hlp_AddMul

TIA_SW_Pitch_MOD
    movlw   TIA_Vx_ENV_MODE
    BRA_IFCLR PLUSW1, Vx_ENV_TOPITCH_ON, ACCESS, TIA_SW_Pitch_Mods

    ;; store dedicated enveloppe value in MUL_A_[LH],sign in MIOS_PARAMETER3
    movlw   0x40
    btfsc   TIA_SW_VOICE, 0
    movlw   0x80
    call    TIA_SW_Hlp_GetMOD
    ;; result in IRQ_TMP[123]
    movff   IRQ_TMP3, MIOS_PARAMETER3
    movf    IRQ_TMP2, W
    ;btfsc   MIOS_PARAMETER3, 0
    ;comf    IRQ_TMP2, W
    andlw   0x7f
    movwf   MUL_A_H
    movf    IRQ_TMP1, W
    ;btfsc   MIOS_PARAMETER3, 0
    ;comf    IRQ_TMP1, W
    movwf   MUL_A_L

    ;; modulate amplitude
    ;; assigned LFOs and ENVs in WREG   
    movlw   TIA_Vx_PITCH_MOD
    movf    PLUSW1, W
    andlw   0x3f
    call    TIA_SW_Hlp_GetMOD
    ;; result in IRQ_TMP[123]
   
TIA_SW_Pitch_Mods_Env_ExM
    ;; Modulation mode
    movlw   TIA_Vx_ENV_MODE
    movf    PLUSW1, W
    andlw   0x03
    incf    WREG, W
    BRA_IFCLR WREG, Vx_ENV_MODTYP_ExM, ACCESS, TIA_SW_Pitch_Mods_Env_EM  
    BRA_IFSET WREG, Vx_ENV_MODTYP_EM, ACCESS,TIA_SW_Pitch_Mods_Env_ExM_NotDoubled
    ;; Double the Dedicated ENVAUDx for AxB only Mode
    rlf     MUL_A_L, F
    rlf     MUL_A_H, F    
TIA_SW_Pitch_Mods_Env_ExM_NotDoubled
    movf    IRQ_TMP2, W
    movwf   MUL_B_H
    movf    IRQ_TMP1, W
    movwf   MUL_B_L
    ;; multiplication
    call    MATH_MUL16_16
    movf    MUL_R_2, W, BANKED
    movwf   IRQ_TMP1   
    movf    MUL_R_3, W, BANKED
    movwf   IRQ_TMP2
    ;; process sign
    movf    MIOS_PARAMETER3, W
    xorwf   IRQ_TMP3, F

TIA_SW_Pitch_Mods_Env_EM
    ;; Modulation mode
    movlw   TIA_Vx_ENV_MODE
    movf    PLUSW1, W
    andlw   0x03
    incf    WREG, W
    BRA_IFCLR WREG, Vx_ENV_MODTYP_EM, ACCESS,TIA_SW_Pitch_Mods_Cont
    movf    MIOS_PARAMETER3, W
    xorwf   IRQ_TMP3, W
    bz      TIA_SW_Pitch_Mods_Env_EM_Add
TIA_SW_Pitch_Mods_Env_EM_Sub
    movf    IRQ_TMP1, W
    subwf   MUL_A_L, W
    movwf   IRQ_TMP1
    movf    IRQ_TMP2, W
    subwfb  MUL_A_H, W
    movwf   IRQ_TMP2
    bc      TIA_SW_Pitch_Mods_Env_EM_Sub_NoCarry
    comf    IRQ_TMP1, F        ;;Sign already in IRQ_TMP3
    comf    IRQ_TMP2, F
    rgoto   TIA_SW_Pitch_Mods_Cont    
TIA_SW_Pitch_Mods_Env_EM_Sub_NoCarry
    movff   MIOS_PARAMETER3, IRQ_TMP3
    rgoto   TIA_SW_Pitch_Mods_Cont      

TIA_SW_Pitch_Mods_Env_EM_Add
    movf    MUL_A_L, W
    addwf   IRQ_TMP1, F 
    movf    MUL_A_H, W
    addwfc  IRQ_TMP2, F 
    ;; saturate on overflow (set frequency to zero to avoid unwanted HF beeps)
    bnc TIA_SW_Pitch_Mods_Cont
    setf    IRQ_TMP1    
    setf    IRQ_TMP2    
    rgoto   TIA_SW_Pitch_Mods_Cont

TIA_SW_Pitch_Mods
    ;; modulate pitch
    ;; assigned LFOs and ENVs in WREG
    movlw   TIA_Vx_PITCH_MOD
    movf    PLUSW1, W
    andlw   0x3f
    bz      TIA_SW_Pitch_CopyFrq
    call    TIA_SW_Hlp_GetMOD
    ;; unsigned result in IRQ_TMP[12]
    ;; sign in IRQ_TMP3[0]    

TIA_SW_Pitch_Mods_Cont 
    ;; skip tuning if IRQ_TMP[12] == zero
    movf    IRQ_TMP1, W
    iorwf   IRQ_TMP2, W
    bz  TIA_SW_Pitch_CopyFrq
    
    ;; add to target frequency
    movff   FSR1H, FSR2H
    movf    FSR1L, W
    addlw   TIA_Vx_TARGET_FRQ_L
    movwf   FSR2L
    call    TIA_SW_Hlp_Add16
                                                 
TIA_SW_Pitch_CopyFrq
    movlw   TIA_Vx_STAT
    BRA_IFSET PLUSW1, Vx_STAT_PORTA_ENABLE, ACCESS, TIA_SW_Pitch_End

    movlw   TIA_Vx_TARGET_FRQ_L
    movff   PLUSW1, MIOS_PARAMETER1
    movlw   TIA_Vx_TARGET_FRQ_H
    movff   PLUSW1, MIOS_PARAMETER2    

    movlw   TIA_Vx_FRQ_L
    movff   MIOS_PARAMETER1, PLUSW1
    movlw   TIA_Vx_FRQ_H
    movff   MIOS_PARAMETER2, PLUSW1     

    lfsr    FSR2, TIA_AUDF0
    btfsc   TIA_SW_VOICE, 0
    lfsr    FSR2, TIA_AUDF1

    comf    MIOS_PARAMETER2, W
    rrf     WREG, W
    rrf     WREG, W
    rrf     WREG, W
    andlw   0x1f
    movwf   INDF2    

TIA_SW_Pitch_End
    return


;; --------------------------------------------------------------------------
;; This function handles the Portamento
;; --------------------------------------------------------------------------
TIA_SW_Porta
    movlw   TIA_Vx_STAT
    BRA_IFCLR PLUSW1, Vx_STAT_PORTA_ENABLE, ACCESS, TIA_SW_Porta_End

    lfsr    FSR2, TIA_AUDF0
    btfsc   TIA_SW_VOICE, 0
    lfsr    FSR2, TIA_AUDF1

    ;; branch depending on portamento option
    movlw   TIA_Vx_MODE
    BRA_IFCLR PLUSW1, Vx_MODE_PORTA_CONST, ACCESS, TIA_SW_Porta_NORM
    ;; ------------------------------------------------------------------
    ;; "constant" portamento mode (constant glide time)
TIA_SW_Porta_CONST
    
    ;; counter -> MUL_A_[LH]
    movlw   TIA_Vx_PORTA_CTR_L
    movff   PLUSW1, MUL_A_L
    movlw   TIA_Vx_PORTA_CTR_H
    movff   PLUSW1, MUL_A_H
    ;; Add delay to portamento counter -> MUL_A_[LH]
    ;; get portamento delay from envelope table 
    movlw   TIA_Vx_PORTA_RATE
    movf    PLUSW1, W
    call    TIA_ENV_TABLE_Get
    ;; add result to counter
    clrc
    movf    MIOS_PARAMETER1, W
    addwf   MUL_A_L, F
    movf    MIOS_PARAMETER2, W
    addwfc  MUL_A_H, F
    bc     TIA_SW_Porta_CONST_Cont_Reached
    
    movlw   TIA_Vx_PORTA_CTR_L
    movff   MUL_A_L, PLUSW1
    movlw   TIA_Vx_PORTA_CTR_H
    movff   MUL_A_H, PLUSW1   
 
    ;; target frequency -> MIOS_PARAMETER[12]
    movlw   TIA_Vx_TARGET_FRQ_L
    movff   PLUSW1, MIOS_PARAMETER1
    movlw   TIA_Vx_TARGET_FRQ_H
    movff   PLUSW1, MIOS_PARAMETER2

    ;; get difference between target and previous frequency -> IRQ_TMP[12]
    movlw   TIA_Vx_PORTA_FRQ_L
    movf    PLUSW1, W
    subwf   MIOS_PARAMETER1, W
    movwf   IRQ_TMP1
    movlw   TIA_Vx_PORTA_FRQ_H
    movf    PLUSW1, W
    subwfb  MIOS_PARAMETER2, W
    movwf   IRQ_TMP2
    ;; convert IRQ_TMP[12] to absolute value
    bcf IRQ_TMP3, 0
    btfss   STATUS, C
    bsf IRQ_TMP3, 0
    btfss   STATUS, C
    comf    IRQ_TMP1, F
    btfss   STATUS, C
    comf    IRQ_TMP2, F
    ;; result in IRQ_TMP[12], sign in IRQ_TMP3[0]

    ;; increment four to ensure that target will be reached
    movlw   1
    addwf   IRQ_TMP1, F
    movlw   0
    addwfc  IRQ_TMP2, F
    movff   IRQ_TMP1, MUL_B_L
    movff   IRQ_TMP2, MUL_B_H

    ;; calc MUL_A_[LH] * MUL_B_[LH]
    call    MATH_MUL16_16
    ;; result in MUL_R_2 (low-byte) and MUL_R_3 (high-byte)
    
    ;; branch depending on direction
    BRA_IFSET IRQ_TMP3, 0, ACCESS, TIA_SW_Porta_CONST_Down
TIA_SW_Porta_CONST_Up   
    ;; add scaled value to starting frequency
    movlw   TIA_Vx_PORTA_FRQ_L
    movf    PLUSW1, W
    addwf   MUL_R_2, W, BANKED
    movwf   IRQ_TMP1

    movlw   TIA_Vx_PORTA_FRQ_H
    movf    PLUSW1, W
    addwfc  MUL_R_3, W, BANKED
    movwf   IRQ_TMP2

    ;; continue at normal portamento routine
    rgoto   TIA_SW_Porta_CONST_Up_Cont
    
TIA_SW_Porta_CONST_Down
    ;; subtract scaled value from starting frequency
    movlw   TIA_Vx_PORTA_FRQ_L
    movff   PLUSW1, IRQ_TMP1
    movlw   TIA_Vx_PORTA_FRQ_H
    movff   PLUSW1, IRQ_TMP2

    movf    MUL_R_2, W, BANKED
    subwf   IRQ_TMP1, F
    movf    MUL_R_3, W, BANKED
    subwfb  IRQ_TMP2, F

    ;; continue at normal portamento routine
    rgoto   TIA_SW_Porta_CONST_Down_Cont

TIA_SW_Porta_CONST_Cont_Reached
    bra     TIA_SW_Porta_Cont_Reached

    ;; ------------------------------------------------------------------
    ;; "normal" portamento mode (non-constant glide time)
TIA_SW_Porta_NORM
    ;; multiply rate with current frequency
    ;; get portamento multiplier from envelope table -> MUL_A
    movlw   TIA_Vx_PORTA_RATE
    movf    PLUSW1, W
    call    TIA_ENV_TABLE_Get
    movff   MIOS_PARAMETER1, MUL_A_L
    movff   MIOS_PARAMETER2, MUL_A_H

    ;; result: low byte in WREG and MIOS_PARAMETER1, high byte in MIOS_PARAMETER2

    ;; get current frequency -> MUL_B
    
    movlw   TIA_Vx_FRQ_L
    movff   PLUSW1, MUL_B_L
    movff   PLUSW1, IRQ_TMP1
    movlw   TIA_Vx_FRQ_H
    movff   PLUSW1, MUL_B_H
    movff   PLUSW1, IRQ_TMP2
    
    call    MATH_MUL16_16
    ;; result in MUL_R_2 (low-byte) and MUL_R_3 (high-byte)
    ;; ensure that result is != 0
    movf    MUL_R_2, W, BANKED
    iorwf   MUL_R_3, W, BANKED
    skpnz
    incf    MUL_R_2, F, BANKED

    ;; TIA_Vx_FRQ += result (depending on Portamento Direction)
    ;movff  MUL_R_2, IRQ_TMP1

    ;; store target frequency in MIOS_PARAMETER[12]
    movlw   TIA_Vx_TARGET_FRQ_L
    movff   PLUSW1, MIOS_PARAMETER1
    movlw   TIA_Vx_TARGET_FRQ_H
    movff   PLUSW1, MIOS_PARAMETER2

    ;; branch depending on portamento direction
    ;; check if value > current value
    movf    MIOS_PARAMETER1, W
    subwf   IRQ_TMP1, W
    movf    MIOS_PARAMETER2, W
    subwfb  IRQ_TMP2, W
    bc TIA_SW_Porta_Down
   
TIA_SW_Porta_Up ;; decrement FRQ
    movf    MUL_R_2, W
    addwf   IRQ_TMP1, F
    movf    MUL_R_3, W, BANKED
    addwfc  IRQ_TMP2, F

TIA_SW_Porta_CONST_Up_Cont  ; re-used by ENV2 option
    ;; check if value > MAX_VALUE
    clrc
    movf    IRQ_TMP1, W
    subwf   MIOS_PARAMETER1, W
    movf    IRQ_TMP2, W
    subwfb  MIOS_PARAMETER2, W
    bc  TIA_SW_Porta_Cont       ; branch to end if MAX_VALUE not reached
    rgoto   TIA_SW_Porta_Cont_Reached       ; else copy MAX_VALUE into value and finish portamento

TIA_SW_Porta_Down  ;; increment FRQ
    movf    MUL_R_2, W
    subwf   IRQ_TMP1, F
    movf    MUL_R_3, W, BANKED
    subwfb  IRQ_TMP2, F

TIA_SW_Porta_CONST_Down_Cont    ; re-used by ENV2 option
    ;; check if value < MIN_VALUE
    clrc
    movf    MIOS_PARAMETER1, W
    subwf   IRQ_TMP1, W
    movf    MIOS_PARAMETER2, W
    subwfb  IRQ_TMP2, W
    bc  TIA_SW_Porta_Cont       ; branch to end if MIN_VALUE not reached
    ; else copy MIN_VALUE into value and finish portamento
    ;rgoto  TIA_SW_Porta_Cont_Reached       ; else copy MAX_VALUE into value and finish portamento
        
TIA_SW_Porta_Cont_Reached
    movlw   TIA_Vx_TARGET_FRQ_L
    movff   PLUSW1, IRQ_TMP1
    
    movlw   TIA_Vx_TARGET_FRQ_H
    movff   PLUSW1, IRQ_TMP2

    movlw   TIA_Vx_STAT
    bcf PLUSW1, Vx_STAT_PORTA_ENABLE
    
TIA_SW_Porta_Cont


    
    ;; Copy Freq
    movlw   TIA_Vx_FRQ_L
    movff   IRQ_TMP1, PLUSW1
    movlw   TIA_Vx_FRQ_H
    movff   IRQ_TMP2, PLUSW1

    comf    IRQ_TMP2, W
    rrf     WREG, W
    rrf     WREG, W
    rrf     WREG, W
    andlw   0x1f
    movwf   INDF2  
        
TIA_SW_Porta_End    
    return

;; --------------------------------------------------------------------------
;; Help Function used from tia_midi.inc and tia_ccin.inc to reset ENV2
;; --------------------------------------------------------------------------
TIA_SW_Hlp_PortaCTR_Reset
    movff   FSR0, FSR1

    movlw   TIA_Vx_PORTA_CTR_L 
    clrf    PLUSW1, ACCESS
    movlw   TIA_Vx_PORTA_CTR_H 
    clrf    PLUSW1, ACCESS

    return

;; --------------------------------------------------------------------------
;; This function handles the amplitude
;; --------------------------------------------------------------------------
TIA_SW_Amp
    ;; TIA_AUDCx
    lfsr    FSR2, TIA_BASE
    movlw   0x04
    addwf   TIA_SW_VOICE, W
    movwf   FSR2L
    

TIA_SW_Amp_MasterVol    
    ;; Vx volume * Master Volume
    movlw   TIA_Vx_VOLUME
    movf    PLUSW1, W
    movwf   IRQ_TMP1
    movf    TIA_MASTER_VOL, W
    addlw   1
    mulwf   IRQ_TMP1
    rlf     PRODL, F
    rlf     PRODH, W
    movwf   MIOS_PARAMETER1
    skpnz
    rgoto   TIA_SW_Amp_Gate

#if 0
    movlw   TIA_Vx_ENV_MODE
    BRA_IFCLR PLUSW1, Vx_ENV_TOAMP_ON, ACCESS, TIA_SW_Amp_Mods

    ;; store dedicated enveloppe value in MUL_A_[LH],sign in MIOS_PARAMETER3
    movlw   0x40
    btfsc   TIA_SW_VOICE, 0
    movlw   0x80
    call    TIA_SW_Hlp_GetMOD
    ;; result in IRQ_TMP[123]
    movff   IRQ_TMP3, MIOS_PARAMETER3
    movf    IRQ_TMP2, W
    andlw   0x7f
    movwf   MUL_A_H
    movf    IRQ_TMP1, W
    movwf   MUL_A_L

    ;; modulate amplitude
    ;; assigned LFOs and ENVs in WREG   
    movlw   TIA_Vx_PITCH_MOD
    movf    PLUSW1, W
    andlw   0x3f
    call    TIA_SW_Hlp_GetMOD
    ;; result in IRQ_TMP[123]
   
TIA_SW_Amp_Mods_Env_ExM
    ;; Modulation mode
    movlw   TIA_Vx_ENV_MODE
    movf    PLUSW1, W
    andlw   0x03
    incf    WREG, W
    BRA_IFCLR WREG, Vx_ENV_MODTYP_ExM, ACCESS, TIA_SW_Amp_Mods_Env_EM  
    BRA_IFSET WREG, Vx_ENV_MODTYP_EM, ACCESS,TIA_SW_Amp_Mods_Env_ExM_NotDoubled
    ;; Double the Dedicated ENVAUDx for AxB only Mode
    rlf     MUL_A_L, F
    rlf     MUL_A_H, F    
TIA_SW_Amp_Mods_Env_ExM_NotDoubled
    movf    IRQ_TMP2, W
    movwf   MUL_B_H
    movf    IRQ_TMP1, W
    movwf   MUL_B_L
    ;; multiplication
    call    MATH_MUL16_16
    movf    MUL_R_2, W, BANKED
    movwf   IRQ_TMP1   
    movf    MUL_R_3, W, BANKED
    movwf   IRQ_TMP2
    ;; process sign
    movf    MIOS_PARAMETER3, W
    xorwf   IRQ_TMP3, F

TIA_SW_Amp_Mods_Env_EM
    ;; Modulation mode
    movlw   TIA_Vx_ENV_MODE
    movf    PLUSW1, W
    btfsc   TIA_SW_VOICE, 0
    swapf   WREG, W
    andlw   0x03
    incf    WREG, W
    BRA_IFCLR WREG, Vx_ENV_MODTYP_EM, ACCESS,TIA_SW_Amp_Mods_Cont
    movf    MIOS_PARAMETER3, W
    bz      TIA_SW_Amp_Mods_Env_EM_Pos
TIA_SW_Amp_Mods_Env_EM_Neg
    comf    MUL_A_L, F
    comf    MUL_A_H, F
    movf    IRQ_TMP3, W
    bz      TIA_SW_Amp_Mods_Env_EM_Pos
    movf    IRQ_TMP1, W
    subwf   MUL_A_L, W
    movwf   IRQ_TMP1
    movf    IRQ_TMP2, W
    subwfb  MUL_A_H, W
    movwf   IRQ_TMP2
    clrf    IRQ_TMP3
    bc      TIA_SW_Amp_Mods_Cont
    clrf    IRQ_TMP1
    clrf    IRQ_TMP2
    rgoto   TIA_SW_Amp_Mods_Cont    

TIA_SW_Amp_Mods_Env_EM_Pos
    movf    MUL_A_L, W
    addwf   IRQ_TMP1, F 
    movf    MUL_A_H, W
    addwfc  IRQ_TMP2, F 
TIA_SW_Amp_Mods_Env_EM_Set
    ;; saturate on overflow (set frequency to zero to avoid unwanted HF beeps)
    bnc TIA_SW_Amp_Mods_Cont
    setf    IRQ_TMP1    
    setf    IRQ_TMP2    
    rgoto   TIA_SW_Amp_Mods_Cont

TIA_SW_Amp_Mods
    ;; modulate pitch
    ;; assigned LFOs and ENVs in WREG
    movlw   TIA_Vx_PITCH_MOD
    movf    PLUSW1, W
    andlw   0x3f
    bz      TIA_SW_Amp_CopyFrq
    call    TIA_SW_Hlp_GetMOD
    ;; unsigned result in IRQ_TMP[12]
    ;; sign in IRQ_TMP3[0]    

TIA_SW_Amp_Mods_Cont 
    ;; skip tuning if IRQ_TMP[12] == zero
    movf    IRQ_TMP1, W
    iorwf   IRQ_TMP2, W
    bz  TIA_SW_Amp_Mods_End
    
                                     
#else

TIA_SW_Amp_Mods
    movlw   TIA_Vx_ENV_MODE
    BRA_IFCLR PLUSW1, Vx_ENV_TOAMP_ON, ACCESS, TIA_SW_Amp_Mods_Cont
    
    ;; Modulation type in MIOS_PARAMETER3
    movlw   TIA_Vx_ENV_MODE
    movf    PLUSW1, W
    andlw   0x03
    movwf   MIOS_PARAMETER3
    incf    MIOS_PARAMETER3, F

    ;; prepare enveloppe value in MIOS_PARAMETER2
    ;; env2amp in WREG
    movlw   0x40
    btfsc   TIA_SW_VOICE, 0
    movlw   0x80
    call    TIA_SW_Hlp_GetMOD
    ;; result in IRQ_TMP[123]
    movf    IRQ_TMP2, W
    btfsc   IRQ_TMP3, 0
    comf    IRQ_TMP2, W
    andlw   0x7f
    movwf   MIOS_PARAMETER2

    ;; modulate amplitude
    ;; assigned LFOs and ENVs in WREG
    movlw   TIA_Vx_AMP_MOD
    movf    PLUSW1, W
    andlw   0x3f
    call    TIA_SW_Hlp_GetMOD
    ;; result in IRQ_TMP[123]
    

TIA_SW_Amp_Mods_Env_ExM
    BRA_IFCLR MIOS_PARAMETER3, Vx_ENV_MODTYP_ExM, ACCESS, TIA_SW_Amp_Mods_Env_EM
    BRA_IFSET MIOS_PARAMETER3, Vx_ENV_MODTYP_EM, ACCESS, TIA_SW_Amp_Mods_Env_EM_ExM
    rlcf   MIOS_PARAMETER2, F
    movlw   0x3f
    movwf   PRODH
    clrf    PRODL
    call    TIA_SW_Hlp_AddOffset16
    ;; result in IRQ_TMP[12]
    movf    IRQ_TMP2, W
    btfsc   IRQ_TMP2, 7
    movlw   0x7f
    
    mulwf   MIOS_PARAMETER2
    rlf     PRODL, F
    rlf     PRODH, W
    btfsc   WREG, 7
    movlw   0x7f
    movwf   MIOS_PARAMETER2    
    rgoto   TIA_SW_Amp_Mods_Env_Cont
    
TIA_SW_Amp_Mods_Env_EM_ExM    
    movf    IRQ_TMP2, W
    mulwf   MIOS_PARAMETER2
    movf    PRODH, W
    movwf   IRQ_TMP2
    clrf    IRQ_TMP1

TIA_SW_Amp_Mods_Env_EM
    rrf     MIOS_PARAMETER2, W 
    call    TIA_SW_Hlp_AddOffset
    ;; result in IRQ_TMP[12]  
    movf    IRQ_TMP2, W
    btfsc   IRQ_TMP2, 7
    movlw   0x7f
    movwf   MIOS_PARAMETER2
    
TIA_SW_Amp_Mods_Env_Cont
    movf    MIOS_PARAMETER1, W
    decf    WREG, W
    mulwf   MIOS_PARAMETER2
    rlf     PRODL, F
    rlf     PRODH, W
    btfsc   WREG, 7
    movlw   0x7f
    movwf   MIOS_PARAMETER1
    rgoto   TIA_SW_Amp_Mods_End
    
TIA_SW_Amp_Mods_Cont
    ;; modulate amplitude
    ;; assigned LFOs and ENVs in WREG
    movlw   TIA_Vx_AMP_MOD
    movf    PLUSW1, W
    andlw   0x3f
    bz      TIA_SW_Amp_Mods_End
    call    TIA_SW_Hlp_GetMOD
    ;; result in IRQ_TMP[123]
    btfsc   IRQ_TMP3, 0
    clrf    MIOS_PARAMETER1
    movf    MIOS_PARAMETER1, W
    mulwf   IRQ_TMP2
    rlncf   PRODH, W
    btfsc   WREG, 7
    movlw   0x7f
    movwf   MIOS_PARAMETER1
#endif
TIA_SW_Amp_Mods_End

    movlw   TIA_Vx_STAT
    BRA_IFSET PLUSW1, Vx_STAT_ENV_ACTIVE, ACCESS, TIA_SW_Amp_Vel   
    BRA_IFCLR PLUSW1, Vx_STAT_GATE_NOTE_ON, ACCESS, TIA_SW_Amp_Vel_End
TIA_SW_Amp_Vel    
    movlw   TIA_Vx_MODE
    BRA_IFCLR PLUSW1, Vx_MODE_VEL2AMP_ON, ACCESS, TIA_SW_Amp_Vel_End
    ;; Velocity calc
    movlw   TIA_Vx_DEPTH_VEL
    movf    PLUSW1, W
    movwf   IRQ_TMP2
    xorlw   0x40
    bz      TIA_SW_Amp_Vel_End
    
    clrf    IRQ_TMP3
    movlw   TIA_Vx_DEPTH_VEL
    btfss   PLUSW1, 6, ACCESS
    bsf     IRQ_TMP3, 0
    
    movf    IRQ_TMP2, W
    andlw   0x3f
    ;; depth * velocity
    clrc
    rlf WREG, W
    ;addlw  2
    ;movwf   IRQ_TMP2
       
    btfsc   IRQ_TMP3, 0, ACCESS
    rgoto   TIA_SW_Amp_Vel_Neg
    
TIA_SW_Amp_Vel_Pos   
    addlw   2
    movwf   IRQ_TMP2
    
    movlw   TIA_Vx_LAST_VEL
    movf    PLUSW1, W
    mulwf   IRQ_TMP2
    clrc
    rlf     PRODL, F
    rlf     PRODH, W
    addlw   1
    movwf   IRQ_TMP2
    
    movlw   TIA_Vx_MODE
    BRA_IFSET PLUSW1, Vx_MODE_GSA_ACTIVE, ACCESS, TIA_SW_Amp_Vel_Pos_GSA
    movf    MIOS_PARAMETER1, W
    mulwf   IRQ_TMP2
    rlf     PRODL, F
    rlf     PRODH, W
    movwf   MIOS_PARAMETER1
    rgoto   TIA_SW_Amp_Vel_End
TIA_SW_Amp_Vel_Pos_GSA
    movf    MIOS_PARAMETER1, W
    sublw   0x7f
    mulwf   IRQ_TMP2
    rlf     PRODL, F
    rlf     PRODH, W
    addwf   MIOS_PARAMETER1, F
    rgoto   TIA_SW_Amp_Vel_End

TIA_SW_Amp_Vel_Neg
    comf    WREG, W
    andlw   0x7f
    addlw   2
    movwf   IRQ_TMP2
    
    movlw   TIA_Vx_LAST_VEL
    movf    PLUSW1, W
    mulwf   IRQ_TMP2
    clrc
    rlf     PRODL, F
    rlf     PRODH, W
    addlw   1
    movwf   IRQ_TMP2

    movf    MIOS_PARAMETER1, W
    mulwf   IRQ_TMP2
    rlf     PRODL, F
    rlf     PRODH, W
    subwf   MIOS_PARAMETER1, F
    ;rgoto   TIA_SW_Amp_Vel_End
TIA_SW_Amp_Vel_End

TIA_SW_Amp_Gate
    movlw   TIA_Vx_ENV_MODE
    BRA_IFCLR PLUSW1, Vx_ENV_TOAMP_ON, ACCESS, TIA_SW_Amp_Gate_Act
    movlw   TIA_Vx_STAT
    BRA_IFSET PLUSW1, Vx_STAT_ENV_ACTIVE, ACCESS, TIA_SW_Amp_CopyAmp
TIA_SW_Amp_Gate_Act
    movlw   TIA_Vx_STAT
    BRA_IFSET PLUSW1, Vx_STAT_GATE_ACTIVE, ACCESS, TIA_SW_Amp_CopyAmp
    movlw   0x0f
    andwf   INDF2, W
    bz      TIA_SW_Amp_PolynomResync

TIA_SW_Amp_Gate_FadeOut
    movwf   IRQ_TMP2
    movlw   DEFAULT_TIA_GATE_FADEOUT
    subwfb  IRQ_TMP2, F
    bc      TIA_SW_Amp_Gate_FadeOut_Norm
TIA_SW_Amp_Gate_FadeOut_Clear
    clrf    IRQ_TMP2
    
TIA_SW_Amp_Gate_FadeOut_Norm
    movlw   0xf0
    andwf   INDF2, F
    movlw   0x0f
    andwf   IRQ_TMP2, W
    iorwf   INDF2, W
    andlw   0x0f
    movwf   INDF2
TIA_SW_Amp_PolynomResync
    movlw   TIA_Vx_MODE
    BRA_IFCLR PLUSW1, Vx_MODE_GRP_ACTIVE, ACCESS, TIA_SW_Amp_End
    movlw   -4
    bsf     PLUSW2, 7
    rgoto   TIA_SW_Amp_End

TIA_SW_Amp_CopyAmp
    clrc
    rrf     MIOS_PARAMETER1, F
    rrncf   MIOS_PARAMETER1, F
    rrncf   MIOS_PARAMETER1, W
    andlw   0x0f
    movwf   INDF2
    movlw   -4
    bcf     PLUSW2, 7
TIA_SW_Amp_End
    return


;; --------------------------------------------------------------------------
;; This function handles the LFOs
;; --------------------------------------------------------------------------
TIA_SW_LFO
    ;; LFO number in TIA_SW_LFO_NUMBER - calculate base address
    ;lfsr   FSR1, TIA_LFO1_BASE
    ;movf   TIA_SW_LFO_NUMBER, W, BANKED
    ;mullw  TIA_LFOx_RECORD_LEN
    ;movf   PRODL, W
    ;addwf  FSR1L, F

    ;; clear result register and skip LFO if not enabled
    movlw   TIA_LFOx_MODE
    BRA_IFSET PLUSW1, LFOx_MODE_ENABLE, ACCESS, TIA_SW_LFO_Enabled
TIA_SW_LFO_Disabled
    movlw   TIA_LFOx_RVALUE_L
    clrf    PLUSW1
    movlw   TIA_LFOx_RVALUE_H
    clrf    PLUSW1
    rgoto   TIA_SW_LFO_End
TIA_SW_LFO_Enabled
    
    ;; increment step counter, store result also in IRQ_TMP1
    movlw   TIA_LFOx_CTR
    incf    PLUSW1, F
    movff   PLUSW1, IRQ_TMP1

    ;; get CTR/ADD entry from LFO table depending on LFO Rate
    movlw   TIA_LFOx_RATE
    movf    PLUSW1, W
    call    TIA_LFO_TABLE_Get
    ;; result: CTR value in WREG and MIOS_PARAMETER1, ADD value in MIOS_PARAMETER2

    ;; exit if max step counter value (CTR) not reached
    ;;  movf    MIOS_PARAMETER1, W
    subwf   IRQ_TMP1, W ; result of LFOx_CTR in IRQ_TMP1
    bnc TIA_SW_LFO_End

    ;; else clear step counter
    movlw   TIA_LFOx_CTR
    clrf    PLUSW1

    ;; skip multiply routine if LFO_x_DEPTH is 0x40 (zero depth)
    movlw   TIA_LFOx_DEPTH
    movf    PLUSW1, W
    xorlw   0x40
    bnz TIA_SW_LFO_DepthOk
TIA_SW_LFO_Depth40
    ;; clear 16bit result value registers and exit
    movlw   TIA_LFOx_RVALUE_L
    clrf    PLUSW1
    movlw   TIA_LFOx_RVALUE_H
    clrf    PLUSW1
    rgoto   TIA_SW_LFO_End
TIA_SW_LFO_DepthOk

    ;; add or subtract ADD value to linear LFO value
    movlw   TIA_LFOx_VALUE
    movff   PLUSW1, IRQ_TMP3

    movlw   TIA_LFOx_MODE
    BRA_IFSET PLUSW1, LFOx_MODE_DECINC, ACCESS, TIA_SW_LFO_Dec
TIA_SW_LFO_Inc
    movf    MIOS_PARAMETER2, W      ; get ADD value
    addwf   IRQ_TMP3, F         ; add to linear LFO value
    bnc TIA_SW_LFO_Cont         ; skip next if max value (0xff) not reached
    comf    IRQ_TMP3, W         ; subtract the missing ticks
    addwf   IRQ_TMP3, F
    movlw   TIA_LFOx_MODE
    bsf PLUSW1, LFOx_MODE_DECINC        ; switch to decrement
    rgoto   TIA_SW_LFO_Cont

TIA_SW_LFO_Dec
    movf    MIOS_PARAMETER2, W      ; get ADD value
    subwf   IRQ_TMP3, F         ; decrement from linear LFO value
    bc  TIA_SW_LFO_Cont         ; skip next if min value (0x00) not reached
    comf    IRQ_TMP3, W         ; add the missing ticks
    addlw   1
    addwf   IRQ_TMP3, F
    movlw   TIA_LFOx_MODE
    bcf PLUSW1, LFOx_MODE_DECINC        ; switch to increment
TIA_SW_LFO_Cont

    ;; write back IRQ_TMP3 -> TIA_LFOx_VALUE
    movlw   TIA_LFOx_VALUE
    movff   IRQ_TMP3, PLUSW1

    ;; convert linear LFO value to waveform by using the TIA_SW_LFO_Hlp_Waveform function
    ;; LFO mode in IRQ_TMP1
    movlw   TIA_LFOx_MODE
    movff   PLUSW1, IRQ_TMP1
    ;; LFO depth in IRQ_TMP2
    movlw   TIA_LFOx_DEPTH
    movff   PLUSW1, IRQ_TMP2
    ;; linear LFO value in WREG
    movf    IRQ_TMP3, W
    ;; process waveform
    
    call    TIA_SW_LFO_Hlp_Waveform
    
    ;; store 16bit result in RVALUE registers
    movlw   TIA_LFOx_RVALUE_L
    movff   PRODL, PLUSW1
    movlw   TIA_LFOx_RVALUE_H
    movff   PRODH, PLUSW1
TIA_SW_LFO_End
    return


;; --------------------------------------------------------------------------
;; This function handles the ENVs
;; expects TIA_ENVx_CURVES bitfield (lower or upper nibble) in WREG
;; 
;; TIA_ENVx_CURVES.7 and TIA_ENVx_CURVES.3 contain the ACCENT flag which
;; is used in TB303 mode (copied to IRQ_TMP4.3
;; --------------------------------------------------------------------------
TIA_SW_ENV
    andlw   0x07
    movwf   IRQ_TMP4
    
    movf    TIA_SW_ENV_NUMBER, W, BANKED
    andlw   0xfe
    bnz  TIA_SW_ENV_VxStatSkp

    ;; Vx dedicated Env clear/set Active
    movlw   TIA_Vx_STAT
    bcf     PLUSW2, Vx_STAT_ENV_ACTIVE
    movlw   TIA_ENVx_MODE
    movf    PLUSW1, W
    andlw   0x1f
    bz  TIA_SW_ENV_VxStatSkp
    movlw   TIA_Vx_STAT
    bsf     PLUSW2, Vx_STAT_ENV_ACTIVE    
TIA_SW_ENV_VxStatSkp

    ;; prepare call of TIA_SW_ENV_GetBendedValue
    movlw   TIA_ENVx_CURVE
    movff   PLUSW1, IRQ_TMP2
    movlw   TIA_ENVx_CTR_H
    movff   PLUSW1, IRQ_TMP1

    ;; branch depending on ENV state
    movlw   TIA_ENVx_MODE
    BRA_IFSET PLUSW1, ENVx_MODE_RELEASE, ACCESS, TIA_SW_ENV_Release
    BRA_IFCLR PLUSW1, ENVx_MODE_ATTACK, ACCESS, TIA_SW_ENV_Calc
    BRA_IFSET PLUSW1, ENVx_MODE_SUSTAIN, ACCESS, TIA_SW_ENV_Sustain
    BRA_IFSET PLUSW1, ENVx_MODE_DECAY, ACCESS, TIA_SW_ENV_Decay

TIA_SW_ENV_Attack
    ;; get attack rate depending on curve setting
    movlw   TIA_ENVx_ATTACK
    movff   PLUSW1, IRQ_TMP3
    movf    IRQ_TMP4, W
    andlw   0x01
    call    TIA_SW_ENV_GetBendedValue
    ;; result: low byte in WREG and MIOS_PARAMETER1, high byte in MIOS_PARAMETER2

    ;; add to ENV counter
    movlw   TIA_ENVx_CTR_L
    movf    PLUSW1, W
    addwf   MIOS_PARAMETER1, F
    movlw   TIA_ENVx_CTR_H
    movf    PLUSW1, W
    addwfc  MIOS_PARAMETER2, F
    bnc TIA_SW_ENV_Calc

    ;; if value >= 0xffff: set to 0xffff, switch to Decay
    setf    MIOS_PARAMETER1
    setf    MIOS_PARAMETER2
    movlw   TIA_ENVx_MODE
    bsf PLUSW1, ENVx_MODE_DECAY
    rgoto   TIA_SW_ENV_Calc
    
    

TIA_SW_ENV_Decay
    ;; get decay rate depending on curve setting
    movlw   TIA_ENVx_DECAY
    movff   PLUSW1, IRQ_TMP3

    movf    IRQ_TMP4, W
    andlw   0x02
    call    TIA_SW_ENV_GetBendedValue
    ;; result: low byte in WREG and MIOS_PARAMETER1, high byte in MIOS_PARAMETER2

    ;; subtraction with current counter value
    movlw   TIA_ENVx_CTR_L
    movff   PLUSW1, IRQ_TMP1
    movlw   TIA_ENVx_CTR_H
    movff   PLUSW1, IRQ_TMP2

    movf    MIOS_PARAMETER1, W
    subwf   IRQ_TMP1, W
    movwf   MIOS_PARAMETER1
    movf    MIOS_PARAMETER2, W
    subwfb  IRQ_TMP2, W
    movwf   MIOS_PARAMETER2
    bnc TIA_SW_ENV_Sustain

    ;; check if counter value < sustain value
    movlw   TIA_ENVx_SUSTAIN
    rlf PLUSW1, W
    andlw   0xfe
    movwf   IRQ_TMP1
    movlw   0x00
    subwf   MIOS_PARAMETER1, W
    movf    IRQ_TMP1, W
    subwfb  MIOS_PARAMETER2, W
    bc  TIA_SW_ENV_Calc

TIA_SW_ENV_Sustain
    ;; write sustain value into counter
    movlw   TIA_ENVx_SUSTAIN
    rlf PLUSW1, W
    andlw   0xfe
    movwf   MIOS_PARAMETER2
    clrf    MIOS_PARAMETER1

    movlw   TIA_ENVx_MODE
    bsf PLUSW1, ENVx_MODE_SUSTAIN
    rgoto   TIA_SW_ENV_Calc

TIA_SW_ENV_Release
    ;; get release rate depending on curve setting
    movlw   TIA_ENVx_RELEASE
    movff   PLUSW1, IRQ_TMP3
    movf    IRQ_TMP4, W
    andlw   0x04
    call    TIA_SW_ENV_GetBendedValue
    ;; result: low byte in WREG and MIOS_PARAMETER1, high byte in MIOS_PARAMETER2
    
    ;; subtraction with current counter value
    movlw   TIA_ENVx_CTR_L
    movff   PLUSW1, IRQ_TMP1
    movlw   TIA_ENVx_CTR_H
    movff   PLUSW1, IRQ_TMP2

    movf    MIOS_PARAMETER1, W
    subwf   IRQ_TMP1, W
    movwf   MIOS_PARAMETER1
    movf    MIOS_PARAMETER2, W
    subwfb  IRQ_TMP2, W
    movwf   MIOS_PARAMETER2
    bc  TIA_SW_ENV_Calc

    ;; zero reached
    clrf    MIOS_PARAMETER1
    clrf    MIOS_PARAMETER2
    movlw   TIA_ENVx_MODE
    clrf    PLUSW1
TIA_SW_ENV_Calc
    ;; copy MIOS_PARAMETER[12] to TIA_ENVx_CTR_[LH]
    movlw   TIA_ENVx_CTR_L
    movff   MIOS_PARAMETER1, PLUSW1
    movlw   TIA_ENVx_CTR_H
    movff   MIOS_PARAMETER2, PLUSW1

    ;; calculate envelope value depending on envelope rate
    
    ;; clear ENV ResultValue Registers
    movlw   TIA_ENVx_RVALUE_L
    clrf    PLUSW1
    movlw   TIA_ENVx_RVALUE_H
    clrf    PLUSW1

    ;; skip multiply routine if ENV_x_DEPTH is 0x40
    movlw   TIA_ENVx_DEPTH
    movf    PLUSW1, W
    xorlw 0x40
    bz  TIA_SW_ENV_End

    ;; convert linear ENV value to waveform by using the TIA_SW_ENV_Hlp_Waveform function
    ;; depth in IRQ_TMP2
    movlw   TIA_ENVx_DEPTH
    movff   PLUSW1, IRQ_TMP2
    ;; linear ENV value in WREG
    movlw   TIA_ENVx_CTR_H
    rrf PLUSW1, W
    ;; process waveform
    call    TIA_SW_ENV_Hlp_Waveform
    ;; store 16bit result in RVALUE registers
    movlw   TIA_ENVx_RVALUE_L
    movff   PRODL, PLUSW1
    movlw   TIA_ENVx_RVALUE_H
    movff   PRODH, PLUSW1
TIA_SW_ENV_End
    return


;; --------------------------------------------------------------------------
;; Help Function: add modulation values depending on enabled sources
;; In: Enabled LFOs and ENCs in WREG, 7-bit offset in IRQ_TMP4
;; Out: signed 16bit result value in IRQ_TMP[12]
;; --------------------------------------------------------------------------
TIA_SW_Hlp_GetMOD
    clrf    IRQ_TMP1
    clrf    IRQ_TMP2
    clrf    IRQ_TMP3
    movwf   IRQ_TMP4    ; save assigned LFOs and ENVs in IRQ_TMP4

    ;; add all enabled LFO values (16 bit -> 24 bit)
    BRA_IFCLR IRQ_TMP4, ASSIGNED_LFOS_1, ACCESS, TIA_SW_Hlp_GetMOD_NoLFO1
TIA_SW_Hlp_GetMOD_LFO1
    movf    TIA_LFO1_BASE + TIA_LFOx_RVALUE_L, W, BANKED
    addwf   IRQ_TMP1, F
    movf    TIA_LFO1_BASE + TIA_LFOx_RVALUE_H, W, BANKED
    addwfc  IRQ_TMP2, F
    movlw   0x00
    btfsc   TIA_LFO1_BASE + TIA_LFOx_RVALUE_H, 7, BANKED
    movlw   0xff
    skpnc
    addlw   1
    addwf   IRQ_TMP3, F
TIA_SW_Hlp_GetMOD_NoLFO1

    BRA_IFCLR IRQ_TMP4, ASSIGNED_LFOS_2, ACCESS, TIA_SW_Hlp_GetMOD_NoLFO2
TIA_SW_Hlp_GetMOD_LFO2
    movf    TIA_LFO2_BASE + TIA_LFOx_RVALUE_L, W, BANKED
    addwf   IRQ_TMP1, F
    movf    TIA_LFO2_BASE + TIA_LFOx_RVALUE_H, W, BANKED
    addwfc  IRQ_TMP2, F
    movlw   0x00
    btfsc   TIA_LFO2_BASE + TIA_LFOx_RVALUE_H, 7, BANKED
    movlw   0xff
    skpnc
    addlw   1
    addwf   IRQ_TMP3, F
TIA_SW_Hlp_GetMOD_NoLFO2

    BRA_IFCLR IRQ_TMP4, ASSIGNED_LFOS_3, ACCESS, TIA_SW_Hlp_GetMOD_NoLFO3
TIA_SW_Hlp_GetMOD_LFO3
    movf    TIA_LFO3_BASE + TIA_LFOx_RVALUE_L, W, BANKED
    addwf   IRQ_TMP1, F
    movf    TIA_LFO3_BASE + TIA_LFOx_RVALUE_H, W, BANKED
    addwfc  IRQ_TMP2, F
    movlw   0x00
    btfsc   TIA_LFO3_BASE + TIA_LFOx_RVALUE_H, 7, BANKED
    movlw   0xff
    skpnc
    addlw   1
    addwf   IRQ_TMP3, F
TIA_SW_Hlp_GetMOD_NoLFO3

    BRA_IFCLR IRQ_TMP4, ASSIGNED_LFOS_4, ACCESS, TIA_SW_Hlp_GetMOD_NoLFO4
TIA_SW_Hlp_GetMOD_LFO4
    movf    TIA_LFO4_BASE + TIA_LFOx_RVALUE_L, W, BANKED
    addwf   IRQ_TMP1, F
    movf    TIA_LFO4_BASE + TIA_LFOx_RVALUE_H, W, BANKED
    addwfc  IRQ_TMP2, F
    movlw   0x00
    btfsc   TIA_LFO4_BASE + TIA_LFOx_RVALUE_H, 7, BANKED
    movlw   0xff
    skpnc
    addlw   1
    addwf   IRQ_TMP3, F
TIA_SW_Hlp_GetMOD_NoLFO4

    BRA_IFCLR IRQ_TMP4, ASSIGNED_ENVS_1, ACCESS, TIA_SW_Hlp_GetMOD_NoENV1
TIA_SW_Hlp_GetMOD_ENV1
    movf    TIA_ENV1_BASE + TIA_ENVx_RVALUE_L, W, BANKED
    addwf   IRQ_TMP1, F
    movf    TIA_ENV1_BASE + TIA_ENVx_RVALUE_H, W, BANKED
    addwfc  IRQ_TMP2, F
    movlw   0x00
    btfsc   TIA_ENV1_BASE + TIA_ENVx_RVALUE_H, 7, BANKED
    movlw   0xff
    skpnc
    addlw   1
    addwf   IRQ_TMP3, F
TIA_SW_Hlp_GetMOD_NoENV1

    BRA_IFCLR IRQ_TMP4, ASSIGNED_ENVS_2, ACCESS, TIA_SW_Hlp_GetMOD_NoENV2
TIA_SW_Hlp_GetMOD_ENV2
    movf    TIA_ENV2_BASE + TIA_ENVx_RVALUE_L, W, BANKED
    addwf   IRQ_TMP1, F
    movf    TIA_ENV2_BASE + TIA_ENVx_RVALUE_H, W, BANKED
    addwfc  IRQ_TMP2, F
    movlw   0x00
    btfsc   TIA_ENV2_BASE + TIA_ENVx_RVALUE_H, 7, BANKED
    movlw   0xff
    skpnc
    addlw   1
    addwf   IRQ_TMP3, F
TIA_SW_Hlp_GetMOD_NoENV2

    BRA_IFCLR IRQ_TMP4, ASSIGNED_ENVS_A0, ACCESS, TIA_SW_Hlp_GetMOD_NoENVAUD0
TIA_SW_Hlp_GetMOD_ENVAUD0
    movf    TIA_V1_ENV_BASE + TIA_ENVx_RVALUE_L, W, BANKED
    addwf   IRQ_TMP1, F
    movf    TIA_V1_ENV_BASE + TIA_ENVx_RVALUE_H, W, BANKED
    addwfc  IRQ_TMP2, F
    movlw   0x00
    btfsc   TIA_V1_ENV_BASE + TIA_ENVx_RVALUE_H, 7, BANKED
    movlw   0xff
    skpnc
    addlw   1
    addwf   IRQ_TMP3, F
TIA_SW_Hlp_GetMOD_NoENVAUD0

    BRA_IFCLR IRQ_TMP4, ASSIGNED_ENVS_A1, ACCESS, TIA_SW_Hlp_GetMOD_NoENVAUD1
TIA_SW_Hlp_GetMOD_ENVAUD1
    movf    TIA_V2_ENV_BASE + TIA_ENVx_RVALUE_L, W, BANKED
    addwf   IRQ_TMP1, F
    movf    TIA_V2_ENV_BASE + TIA_ENVx_RVALUE_H, W, BANKED
    addwfc  IRQ_TMP2, F
    movlw   0x00
    btfsc   TIA_V2_ENV_BASE + TIA_ENVx_RVALUE_H, 7, BANKED
    movlw   0xff
    skpnc
    addlw   1
    addwf   IRQ_TMP3, F
TIA_SW_Hlp_GetMOD_NoENVAUD1
    
    ;; saturate to absolute 16 bit-value, keep sign in IRQ_TMP3[0]
    BRA_IFSET IRQ_TMP3, 7, ACCESS, TIA_SW_Hlp_GetMOD_Negative
TIA_SW_Hlp_GetMOD_Positive
    movf    IRQ_TMP3, W
    bz  TIA_SW_Hlp_GetMOD_Cont
    setf    IRQ_TMP1
    setf    IRQ_TMP2
    clrf    IRQ_TMP3    ; save sign in IRQ_TMP3
    rgoto   TIA_SW_Hlp_GetMOD_Cont

TIA_SW_Hlp_GetMOD_Negative
    movf    IRQ_TMP3, W
    andlw   0x7f
    bnz TIA_SW_Hlp_GetMOD_Negative_C
    clrf    IRQ_TMP1
    clrf    IRQ_TMP2
TIA_SW_Hlp_GetMOD_Negative_C
    comf    IRQ_TMP1, F ; invert result
    comf    IRQ_TMP2, F
    movlw   0x01        ; save sign in IRQ_TMP3
    movwf   IRQ_TMP3
TIA_SW_Hlp_GetMOD_Cont

    return

;; --------------------------------------------------------------------------
;; Help Function to add a 7-bit offset to IRQ_TMP[123] and saturate
;; In:  offset in WREG, absolute 16-bit value in IRQ_TMP[12], sign in IRQ_TMP3[0]
;; Out: New value in IRQ_TMP[12]
;; --------------------------------------------------------------------------
TIA_SW_Hlp_AddOffset
    clrc
    rlf WREG, W
    movwf   PRODH
    clrf    PRODL
    ;;  rgoto   TIA_SW_Hlp_AddOffset16

;; --------------------------------------------------------------------------
;; Help Function to add a 16-bit offset to IRQ_TMP[123] and saturate
;; In:  offset in PROD[LH], absolute 16-bit value in IRQ_TMP[12], sign in IRQ_TMP3[0]
;; Out: New value in IRQ_TMP[12]
;; --------------------------------------------------------------------------
TIA_SW_Hlp_AddOffset16
    ;; add offset and saturate
    BRA_IFSET IRQ_TMP3, 0, ACCESS, TIA_SW_Hlp_AddOffset16_Negative
TIA_SW_Hlp_AddOffset16_Positive
    movf    PRODL, W    ; add offset
    addwf   IRQ_TMP1, F
    movf    PRODH, W
    addwfc  IRQ_TMP2, F
    bnc TIA_SW_Hlp_AddOffset16_End
    setf    IRQ_TMP1    ; saturate
    setf    IRQ_TMP2
    rgoto   TIA_SW_Hlp_AddOffset16_End
TIA_SW_Hlp_AddOffset16_Negative
    movf    IRQ_TMP1, W ; subtract offset
    subwf   PRODL, W
    movwf   IRQ_TMP1
    movf    IRQ_TMP2, W
    subwfb  PRODH, W
    movwf   IRQ_TMP2
    bc  TIA_SW_Hlp_AddOffset16_End
    clrf    IRQ_TMP1
    clrf    IRQ_TMP2
TIA_SW_Hlp_AddOffset16_End

    return

;; --------------------------------------------------------------------------
;; Help Function: Get absolute value
;; IN:  signed 16-bit value in IRQ_TMP[12]
;; Out: unsigned absolute value in IRQ_TMP[12]
;;      sign in IRQ_TMP3[0]
;; --------------------------------------------------------------------------
TIA_SW_Hlp_GetAbs16
    ;; convert IRQ_TMP[12] to unsigned integer, keep sign in IRQ_TMP3[0]
    clrf    IRQ_TMP3
    BRA_IFCLR IRQ_TMP2, 7, ACCESS, TIA_SW_Hlp_GetABS16_Pos
TIA_SW_Hlp_GetABS16_Neg
    bsf IRQ_TMP3, 0 ; memorize sign in IRQ_TMP3[0]
    comf    IRQ_TMP1, F
    comf    IRQ_TMP2, F
    incf    IRQ_TMP1, F
    skpnz
    incf    IRQ_TMP2, F
TIA_SW_Hlp_GetABS16_Pos
    return


;; --------------------------------------------------------------------------
;; Help Function for ENV Waveforms (resuses the LFO Waveform routine)
;; In:  ENV_x_VALUE in WREG
;;      ENV_x_DEPTH in IRQ_TMP2,
;;      Accent flag in IRQ_TMP4.3
;; Out: Result in PROD[LH]
;; --------------------------------------------------------------------------
TIA_SW_ENV_Hlp_Waveform

;   BRA_IFCLR TIA_SE_OPTION, SE_OPTION_TB303, BANKED, TIA_SW_ENV_Hlp_Waveform_NotTB303
;TIA_SW_ENV_Hlp_Waveform_TB303
;   movwf   IRQ_TMP1
;   ;; in TB303 mode the depth parameter is used as "env mod" which is always positive
;   ;; modify depth depending on ACCENT flag
;   rrf IRQ_TMP2, W
;   andlw   0x3f
;   movwf   IRQ_TMP2
;   BRA_IFCLR IRQ_TMP4, 3, ACCESS, TIA_SW_ENV_Hlp_Waveform_NoAcc
;TIA_SW_ENV_Hlp_Waveform_Acc
;;  addlw   0x10
;   movwf   IRQ_TMP2
;TIA_SW_ENV_Hlp_Waveform_NoAcc
;   movf    IRQ_TMP1, W
;   andlw   0x7f
;   mulwf   IRQ_TMP2, ACCESS
;   return

;TIA_SW_ENV_Hlp_Waveform_NotTB303
    ;; set mode to 0x10, don't overwrite WREG
    clrf    IRQ_TMP1
    bsf IRQ_TMP1, 4 ; (triangle waveform)

    ;; convert linear envelope value
    andlw   0x7f
    btfsc   IRQ_TMP2, 6; shift positive values to > 0x0000
    addlw 0x80  
    btfss   IRQ_TMP2, 6; inversion if depth < 0x40
    xorlw 0x7f  
    rgoto   TIA_SW_ENV_Hlp_Waveform_Cont

;; --------------------------------------------------------------------------
;; Help Function for LFO and ENV Waveforms
;; In:  LFO_x_VALUE in WREG
;;  LFO_x_MODE in IRQ_TMP1
;;      LFO_x_DEPTH in IRQ_TMP2,
;;      LFO/ENV number in TIA_SW_LFO_NUMBER
;;      pointer to LFOx_BASE in FSR1
;; Out: Result in PROD[LH]
;;  Scaled Value in IRQ_TMP1
;; --------------------------------------------------------------------------
TIA_SW_LFO_Hlp_Waveform
    ;; invert if negative depth (<0x40)
    btfss   IRQ_TMP2, 6
    xorlw 0xff

    ;; envelope waveform routine continues here
TIA_SW_ENV_Hlp_Waveform_Cont
    movwf   IRQ_TMP3

    ;; get absolute value of depth from TIA_DEPTH_TABLE
    ;; (to keep it compatible with MIDIbox TIA V1.5)
    movf    IRQ_TMP2, W
    rcall   TIA_SW_Hlp_Abs7
    addlw   TIA_DEPTH_TABLE & 0xff
    movwf   TBLPTRL
    clrf    TBLPTRH
    movlw   TIA_DEPTH_TABLE >> 8
    addwfc  TBLPTRH, F
    tblrd*+
    movf    TABLAT, W
    movwf   IRQ_TMP2
    
    ;; branch depending on selected waveform
    swapf   IRQ_TMP1, W
    andlw   0x07
    JUMPTABLE_2BYTES_UNSECURE
    rgoto   TIA_SW_LFO_Hlp_WFBranch_0
    rgoto   TIA_SW_LFO_Hlp_WFBranch_1
    rgoto   TIA_SW_LFO_Hlp_WFBranch_2
    rgoto   TIA_SW_LFO_Hlp_WFBranch_3
    rgoto   TIA_SW_LFO_Hlp_WFBranch_4
    rgoto   TIA_SW_LFO_Hlp_WFBranch_5
    rgoto   TIA_SW_LFO_Hlp_WFBranch_6
    rgoto   TIA_SW_LFO_Hlp_WFBranch_7

TIA_SW_LFO_Hlp_WFBranch_0   ; Sine
TIA_SW_LFO_Hlp_WFBranch_6   ; (reserved)
TIA_SW_LFO_Hlp_WFBranch_7   ; (reserved)
    movf    IRQ_TMP3, W
    call    TIA_SIN_TABLE_Get
    rgoto   TIA_SW_LFO_Hlp_WFBranch_Cont

TIA_SW_LFO_Hlp_WFBranch_1   ; Triangle
    ;; Triangle: return unsigned value
    movf    IRQ_TMP3, W
    btfss   IRQ_TMP3, 7
    xorlw   0x7f
    rgoto   TIA_SW_LFO_Hlp_WFBranch_Cont

TIA_SW_LFO_Hlp_WFBranch_2   ; sawtooth
    ;; Sawtooth: x/2, MODE_DECINC is the eight bit
    rrf IRQ_TMP3, W
    andlw   0x7f
    btfsc   IRQ_TMP1, LFOx_MODE_DECINC ; (IRQ_TMP1=LFO_x_MODE)
    iorlw   0x80
    rgoto   TIA_SW_LFO_Hlp_WFBranch_Cont
TIA_SW_LFO_Hlp_WFBranch_3   ; pulse
    ;; Pulse: 0x00 when Dec, 0xff when Inc, take inversion bit also into account
    movlw   0x00
    btfsc   IRQ_TMP1, LFOx_MODE_DECINC; (IRQ_TMP1=LFO_x_MODE)
    movlw 0xff 
    rgoto   TIA_SW_LFO_Hlp_WFBranch_Cont
TIA_SW_LFO_Hlp_WFBranch_4   ; random
    ;; each second LFO is in S&H mode
    BRA_IFCLR TIA_SW_LFO_NUMBER, 0, BANKED, TIA_SW_LFO_Hlp_WFBranch_4_Random
TIA_SW_LFO_Hlp_WFBranch_4_S_H
    movf    IRQ_TMP3, W ; latch on period match
    movlw   TIA_LFOx_RVALUE_L
    skpnz
    movlw   TIA_LFOx_RVALUE_L - TIA_LFOx_RECORD_LEN
    movff   PLUSW1, PRODL

    movlw   TIA_LFOx_RVALUE_H
    skpnz
    movlw   TIA_LFOx_RVALUE_H - TIA_LFOx_RECORD_LEN
    movff   PLUSW1, PRODH
    rgoto   TIA_SW_LFO_Hlp_Waveform_End

TIA_SW_LFO_Hlp_WFBranch_4_Random
    movf    TIA_LFO_RANDOM_SEED_H, W, BANKED
    andlw   0x55
    movwf   IRQ_TMP1
    movf    TIA_LFO_RANDOM_SEED_L, W, BANKED
    andlw   0xaa
    iorwf   IRQ_TMP1, W
    addwf   TMR1L, W    ; super-random ;-)
    xorwf   TMR2, W
    rgoto   TIA_SW_LFO_Hlp_WFBranch_Cont

TIA_SW_LFO_Hlp_WFBranch_5   ; (AIN)
#if ENABLE_AIN_LFO_WAVEFORM
    movf    TIA_SW_LFO_NUMBER, W, BANKED
    movff   FSR1L, IRQ_TMP1     ; save FSR1
    movff   FSR1H, IRQ_TMP3
    call    MIOS_AIN_PinGet     ; get value of analog pin
    movff   IRQ_TMP1, FSR1L     ; restore FSR1
    movff   IRQ_TMP3, FSR1H
    SET_BSR TIA_BASE
    rrf MIOS_PARAMETER2, F  ; convert 10bit to 8bit
    rrf MIOS_PARAMETER1, F
    rrf MIOS_PARAMETER2, F
    rrf MIOS_PARAMETER1, W

    ;; biased at 0x80
    BRA_IFSET WREG, 7, ACCESS, TIA_SW_LFO_Hlp_WFBranch_Cont
    xorlw   0x7f
    addlw   1
    btfsc   WREG, 7
    movlw 0x7f
#else
    movlw   0x80
#endif
    ;;  rgoto   TIA_SW_LFO_Hlp_WFBranch_Cont

TIA_SW_LFO_Hlp_WFBranch_Cont
    movwf   IRQ_TMP1

    ;; process scaling (depth * scaled value)
    andlw   0x7f        ; remove sign from value
    mulwf   IRQ_TMP2    ; multiply with depth (in IRQ_TMP2)
    ;; result in PROD[LH]

    ;; invert if DECINC flag not set
    BRA_IFSET IRQ_TMP1, 7, ACCESS, TIA_SW_LFO_Hlp_Waveform_End
    comf    PRODL, F
    comf    PRODH, F
TIA_SW_LFO_Hlp_Waveform_End
    return


;; --------------------------------------------------------------------------
;; Help Function to sync all LFOs
;; --------------------------------------------------------------------------
TIA_SW_Hlp_SyncAllLFOs
    lfsr    FSR2, TIA_LFO1_BASE + TIA_LFOx_MODE
    movlw   0x04
    movwf   IRQ_TMP1
TIA_SW_Hlp_SyncAllLFOs_Loop
    call    TIA_SW_Hlp_SyncLFO_Now
    decfsz  IRQ_TMP1, F
    rgoto   TIA_SW_Hlp_SyncAllLFOs_Loop
    return

;; --------------------------------------------------------------------------
;; Help Function for gate bit, syncs the LFOs
;; In: TIA_Vx_PITCH_MOD | TIA_Vx_VOLUME_MOD | assigned filter flags
;; --------------------------------------------------------------------------
TIA_SW_Hlp_SyncLFOs
    lfsr    FSR2, TIA_LFO1_BASE + TIA_LFOx_MODE
    movwf   IRQ_TMP1
    rcall   TIA_SW_Hlp_SyncSingleLFO
    rrf IRQ_TMP1, F
    rcall   TIA_SW_Hlp_SyncSingleLFO
    rrf IRQ_TMP1, F
    rcall   TIA_SW_Hlp_SyncSingleLFO
    rrf IRQ_TMP1, F
    rcall   TIA_SW_Hlp_SyncSingleLFO


TIA_SW_Hlp_SyncSingleLFO
    BRA_IFSET INDF2, LFOx_MODE_SYNC_ALL, ACCESS, TIA_SW_Hlp_SyncLFO_Now
    BRA_IFCLR IRQ_TMP1, 0, ACCESS, TIA_SW_Hlp_SyncLFO_Skip
    BRA_IFCLR INDF2, LFOx_MODE_SYNC, ACCESS, TIA_SW_Hlp_SyncLFO_Skip
TIA_SW_Hlp_SyncLFO_Now
    bcf INDF2, LFOx_MODE_DECINC
    incf    FSR2L, F    ; switch to LFO_x_RATE
    incf    FSR2L, F    ; switch to LFO_x_CTR
    clrf    POSTINC2    ; clear counter,
                            ; switch to LFO_x_VALUE
    movlw   0x80        ; write 0x80 into value
    movwf   POSTINC2
                            ; switch to LFO_x_DEPTH
    incf    FSR2L, F    ; switch to LFO_x_RVALUE_L
    clrf    POSTINC2    ; clear LFO_x_RAVLUE_L
    clrf    POSTINC2    ; clear LFO_x_RAVLUE_H

    return
    
TIA_SW_Hlp_SyncLFO_Skip
    movlw   TIA_LFOx_RECORD_LEN     ; switch to LFO_x+1_MODE
    addwf   FSR2L, F
    return

;; --------------------------------------------------------------------------
;; Help Function for gate bit, sets ENVs to attack mode
;; In: TIA_Vx_ENVS | assigned filter flags
;; --------------------------------------------------------------------------
TIA_SW_Hlp_ENVAttack
    ;; set envelope generators to attack mode if voice (or filter) has been assigned
    movwf   IRQ_TMP1

    ;movf   IRQ_TMP1, W
    andlw   0x11
    bz  TIA_SW_Hlp_ENVAttack_Not1
    movlw   (1 << ENVx_MODE_ATTACK)
    movwf   TIA_ENV1_BASE + TIA_ENVx_MODE, BANKED
TIA_SW_Hlp_ENVAttack_Not1

    movf    IRQ_TMP1, W
    andlw   0x22
    bz  TIA_SW_Hlp_ENVAttack_Not2
    movlw   (1 << ENVx_MODE_ATTACK)
    movwf   TIA_ENV2_BASE + TIA_ENVx_MODE, BANKED
TIA_SW_Hlp_ENVAttack_Not2

    movf    IRQ_TMP1, W
    andlw   0x0c
    bz  TIA_SW_Hlp_ENVAttack_Not3
    movlw   (1 << ENVx_MODE_ATTACK)
    movwf   TIA_V1_ENV_BASE + TIA_ENVx_MODE, BANKED
TIA_SW_Hlp_ENVAttack_Not3

    movf    IRQ_TMP1, W
    andlw   0xc0
    bz  TIA_SW_Hlp_ENVAttack_Not4
    movlw   (1 << ENVx_MODE_ATTACK)
    movwf   TIA_V2_ENV_BASE + TIA_ENVx_MODE, BANKED
TIA_SW_Hlp_ENVAttack_Not4
    return

;; --------------------------------------------------------------------------
;; Help Function for gate bit, sets ENVs to release mode
;; In: TIA_Vx_ENVS | assigned filter flags
;; --------------------------------------------------------------------------
TIA_SW_Hlp_ENVRelease
    ;; set envelope generators to release mode if voice (or filter) has been assigned
    movwf   IRQ_TMP1

    andlw   0x11
    bz  TIA_SW_Hlp_ENVRelease_Not1
    movlw   (1 << ENVx_MODE_RELEASE)
    movwf   TIA_ENV1_BASE + TIA_ENVx_MODE, BANKED
TIA_SW_Hlp_ENVRelease_Not1

    movf    IRQ_TMP1, W
    andlw   0x22
    bz  TIA_SW_Hlp_ENVRelease_Not2
    movlw   (1 << ENVx_MODE_RELEASE)
    movwf   TIA_ENV2_BASE + TIA_ENVx_MODE, BANKED
TIA_SW_Hlp_ENVRelease_Not2

    movf    IRQ_TMP1, W
    andlw   0x0c
    bz  TIA_SW_Hlp_ENVRelease_Not3
    movlw   (1 << ENVx_MODE_RELEASE)
    movwf   TIA_V1_ENV_BASE + TIA_ENVx_MODE, BANKED
TIA_SW_Hlp_ENVRelease_Not3

    movf    IRQ_TMP1, W
    andlw   0xc0
    bz  TIA_SW_Hlp_ENVRelease_Not4
    movlw   (1 << ENVx_MODE_RELEASE)
    movwf   TIA_V2_ENV_BASE + TIA_ENVx_MODE, BANKED
TIA_SW_Hlp_ENVRelease_Not4
    return

;; --------------------------------------------------------------------------
;; Help Function for TIA_SW_Pitch
;; IN: TIA_Vx_TARGET_FRQ_LH in FSR2
;; OUT: new result in TIA_Vx_TARGET_FRQ_LH
;; --------------------------------------------------------------------------
TIA_SW_Hlp_AddMul
    BRA_IFCLR IRQ_TMP3, 0, ACCESS, TIA_SW_Hlp_AddMul_Pos
TIA_SW_Hlp_AddMul_Neg
    ;; calc MUL_A_[LH] = TIA_Vx_FRQ_[LH] - MIOS_PARAMETER[12]
    movf    MIOS_PARAMETER1, W
    subwf   POSTINC2, W ; TIA_Vx_TARGET_FRQ_LH+0, W, BANKED
    movwf   MUL_A_L, BANKED
    movf    MIOS_PARAMETER2, W
    subwfb  POSTDEC2, W ; TIA_Vx_TARGET_FRQ_LH+1, W, BANKED
    movwf   MUL_A_H, BANKED

    ;; calc MUL_R_[12] = MUL_A_[LH] * MUL_B_[LH]
    call    MATH_MUL16_16
    ;; TIA_Vx_FRQ -= result
    movf    MUL_R_1, W, BANKED
    subwf   POSTINC2, F ; TIA_Vx_TARGET_FRQ_LH+0, F, BANKED
    movf    MUL_R_2, W, BANKED
    subwfb  POSTDEC2, F ; TIA_Vx_TARGET_FRQ_LH+1, F, BANKED
    return

TIA_SW_Hlp_AddMul_Pos
    ;; calc MUL_A_[LH] = MIOS_PARAMETER[12] - TIA_Vx_FRQ_[LH]
    movf    POSTINC2, W ; TIA_Vx_TARGET_FRQ_LH+0, W, BANKED
    subwf   MIOS_PARAMETER1, W
    movwf   MUL_A_L, BANKED
    movf    POSTDEC2, W ; TIA_Vx_TARGET_FRQ_LH+1, W, BANKED
    subwfb  MIOS_PARAMETER2, W
    movwf   MUL_A_H, BANKED

    ;; calc MUL_R_[12] = MUL_A_[LH] * MUL_B_[LH]
    call    MATH_MUL16_16
    ;; TIA_Vx_FRQ += result
    movf    MUL_R_1, W, BANKED
    addwf   POSTINC2, F ; TIA_Vx_TARGET_FRQ_LH+0, F, BANKED
    movf    MUL_R_2, W, BANKED
    addwfc  POSTDEC2, F ; TIA_Vx_TARGET_FRQ_LH+1, F, BANKED
    return
    
    
;; --------------------------------------------------------------------------
;; Help Function for TIA_SW_Pitch
;; IN:  addend and IRQ_TMP[12], TIA_Vx_TARGET_FRQ_LH in FSR2
;; OUT: new result in TIA_Vx_TARGET_FRQ_LH
;; --------------------------------------------------------------------------
TIA_SW_Hlp_Add16
    ;; divide / 4 for a better scaling
    ;clrc
    ;rrf    IRQ_TMP2, F
    ;rrf    IRQ_TMP1, F
    ;clrc
    ;rrf    IRQ_TMP2, F
    ;rrf    IRQ_TMP1, F
        
    BRA_IFCLR IRQ_TMP3, 0, ACCESS, TIA_SW_Hlp_Add16_Pos
TIA_SW_Hlp_Add16_Neg
    ;; TIA_Vx_TARGET_FRQ -= MOD 
    movf    IRQ_TMP1, W
    subwf   POSTINC2, F ; TIA_Vx_TARGET_FRQ_LH+0, F, BANKED
    movf    IRQ_TMP2, W
    subwfb  POSTDEC2, F ; TIA_Vx_TARGET_FRQ_LH+1, F, BANKED
    ;; saturate on overflow
    bc  TIA_SW_Hlp_Add16_Neg_End
    clrf    POSTINC2    ; TIA_Vx_TARGET_FRQ_LH+0, BANKED
    clrf    POSTDEC2    ; TIA_Vx_TARGET_FRQ_LH+1, BANKED
TIA_SW_Hlp_Add16_Neg_End
    return

TIA_SW_Hlp_Add16_Pos
    ;; TIA_Vx_TARGET_FRQ += MOD
    movf    IRQ_TMP1, W
    addwf   POSTINC2, F ; TIA_Vx_TARGET_FRQ_LH+0, F, BANKED
    movf    IRQ_TMP2, W
    addwfc  POSTDEC2, F ; TIA_Vx_TARGET_FRQ_LH+1, F, BANKED
    ;; saturate on overflow (set frequency to zero to avoid unwanted HF beeps)
    bnc TIA_SW_Hlp_Add16_Pos_End
    setf    POSTINC2    ; TIA_Vx_TARGET_FRQ_LH+0, BANKED
    setf    POSTDEC2    ; TIA_Vx_TARGET_FRQ_LH+1, BANKED
TIA_SW_Hlp_Add16_Pos_End
    return


;; --------------------------------------------------------------------------
;; Help Function for TIA_SW_ENV, etc.
;; IN:  7-bit signed value in WREG
;; OUT: absolute value (0x00-0x3f) in WREG
;; --------------------------------------------------------------------------
TIA_SW_Hlp_Abs7
    movf    WREG, W
    skpnz
    addlw   1
    btfss   WREG, 6
    sublw 0x40
    andlw   0x3f
    return

;; --------------------------------------------------------------------------
;; Help Function for TIA_SW_ENV
;; IN:  ENV_x_CTR_H in IRQ_TMP1
;;  ENV_x_CURVE in IRQ_TMP2
;;  ENV_x_ATTACK/ENV_x_DECAY or ENV_x_SUSTAIN in IRQ_TMP3
;;      WREG != 0: use curve, WREG == 0: don't use curve parameter
;; OUT: value which should be added to - or subtracted from - ENV_x_CTR_[LH]
;;      low-byte in WREG and MIOS_PARAMETER1; high-byte in MIOS_PARAMETER2
;; --------------------------------------------------------------------------
TIA_SW_ENV_GetBendedValue
    bnz TIA_SW_ENV_GetBendedValue_Curve

    ;; curve not selected, get value from ENV_TABLE
    movf    IRQ_TMP3, W
    goto    TIA_ENV_TABLE_Get

TIA_SW_ENV_GetBendedValue_Curve
    ;; return ENV_x_DECAY when ENV_x_CURVE == 0x40
    movlw   0x40
    cpfseq  IRQ_TMP2, ACCESS
    rgoto TIA_SW_ENV_GetBendedValue_UD
    comf    IRQ_TMP3, W
    rgoto   TIA_SW_ENV_GetBendedValue_Cont

TIA_SW_ENV_GetBendedValue_UD
    ;; feedback: calculate ABS7(CURVE) * ENV_x_CTR_H
    movf    IRQ_TMP2, W     ; get absolute value of curve parameter
    rcall   TIA_SW_Hlp_Abs7
    mulwf   IRQ_TMP1, ACCESS    ; multiply with current counter value
    
    ;; when CURVE parameter < 0x40: bend down, else up
    BRA_IFCLR IRQ_TMP2, 6, ACCESS, TIA_SW_ENV_GetBendedValue_Down
TIA_SW_ENV_GetBendedValue_Up
    comf    IRQ_TMP3, F
    bcf IRQ_TMP3, 7
    movf    PRODH, W
    subwf   IRQ_TMP3, W
    btfsc   WREG, 7
    movlw 0x00
    rgoto   TIA_SW_ENV_GetBendedValue_Cont

TIA_SW_ENV_GetBendedValue_Down
    comf    IRQ_TMP3, W
    andlw   0x7f
    addwf   PRODH, W
    btfsc   WREG, 7
    movlw 0x7f
    ;;  rgoto   TIA_SW_ENV_GetBendedValue_Cont

TIA_SW_ENV_GetBendedValue_Cont
    andlw   0x7f
    goto    TIA_FRQ_TABLE_Get