Subversion Repositories svn.mios

Rev

Rev 556 | Blame | Compare with Previous | Last modification | View Log | RSS feed

; $Id: sid_voice.inc 881 2010-01-09 17:39:42Z tk $
;
; MIDIbox SID
; Voice handling
;
; ==========================================================================
;
;  Copyright 1998-2007 Thorsten Klose (tk@midibox.org)
;  Licensed for personal non-commercial use only.
;  All other rights reserved.
; 
; ==========================================================================


;; --------------------------------------------------------------------------
;;  This function initializes the voice queue and the assigned instruments
;;  The queue contains a number for each voice. So long bit 7 is set, the
;;  voice is assigned to an instrument, if bit 7 is not set, the voice can
;;  be allocated by a new instrument
;; 
;;  The instrument number to which the voice is assigned can be found in
;;  SID_VOICE_INSTR_x, it is especially important for mono voices
;; 
;;  The first voice in the queue is the first which will be taken.
;;  To realize a "drop longest note first" algorithm, the take number should
;;  always be moved to the end of the queue
;; 
;;  IN: -
;;  OUT: -
;;  USES: PRODL, FSR1
;; --------------------------------------------------------------------------
SID_VOICE_Init
    ;; init voice queue
    lfsr    FSR1, SID_VOICE_QUEUE_0
    clrf    PRODL
SID_VOICE_InitQueueLoop1
    movff   PRODL, POSTINC1
    incf    PRODL, F
    movlw   SID_VOICE_QUEUE_LEN - 1
    cpfsgt  PRODL, ACCESS
    rgoto SID_VOICE_InitQueueLoop1

    ;; init INSTR queue
    lfsr    FSR1, SID_VOICE_INSTR_0
    clrf    PRODL
SID_VOICE_InitQueueLoop2
    setf    POSTINC1
    incf    PRODL, F
    movlw   SID_VOICE_QUEUE_LEN - 1
    cpfsgt  PRODL, ACCESS
    rgoto SID_VOICE_InitQueueLoop2

    ;; init exclusive voice allocation flags
    rcall   SID_VOICE_ReInit

    ;; init superpoly note table (4 entries)
    lfsr    FSR1, SID_SUPERPOLY_VOICE_NOTE_TAB
    clrf    POSTINC1
    clrf    POSTINC1
    clrf    POSTINC1
    clrf    POSTINC1

    return

;; --------------------------------------------------------------------------
;;  (Re-)initializes the exclusive voice allocation flags
;; --------------------------------------------------------------------------
SID_VOICE_ReInit
    ;; interrupts must be disabled when the voice queue is modified
    ;; (in drum mode, voice queue is handled via IRQs)
    IRQ_DISABLE

    ;; by default, allow non-exclusive access (INSTR[6] set to 1)
    lfsr    FSR1, SID_VOICE_INSTR_0
    clrf    PRODL
SID_VOICE_ReInit_Loop
    bsf POSTINC1, 6
    incf    PRODL, F
    movlw   SID_VOICE_QUEUE_LEN - 1
    cpfsgt  PRODL, ACCESS
    rgoto SID_VOICE_ReInit_Loop
    
    ;; branch depending on engine
    movff   SID_PATCH_BUFFER_SHADOW + SID_Ix_ENGINE, WREG
    BRA_IFSET WREG, 1, ACCESS, SID_VOICE_ReInit_DM
SID_VOICE_ReInit_LB
    ;; exclusive voice assignments not used by Lead/Bassline engine
    rgoto   SID_VOICE_ReInit_End

SID_VOICE_ReInit_DM
    BRA_IFSET WREG, 0, ACCESS, SID_VOICE_ReInit_M
SID_VOICE_ReInit_D
    ;; go through 16 instruments
    lfsr    FSR1, SID_PATCH_BUFFER_SHADOW + SID_Ix_D_I1_BASE
    clrf    PRODL
SID_VOICE_ReInit_D_Loop
    movlw   SID_Ix_Dx_FLAGS1    ; voice assignment located in [7:4]
    swapf   PLUSW1, W
    andlw   0x0f
    addlw   -3          ; check if voice 1..6 exclusively selected
    movwf   PRODH
    movlw   6
    cpfslt  PRODH, ACCESS
    rgoto SID_VOICE_ReInit_D_Loop_Next
    ;; exclusive voice assignment found -> clear flag #6 of INSTR record
    rcall   SID_VOICE_ReInit_Sub
SID_VOICE_ReInit_D_Loop_Next
    movlw   (SID_Ix_D_I2_BASE-SID_Ix_D_I1_BASE)
    addwf   FSR1L, F
    incf    PRODL, F
    BRA_IFCLR PRODL, 4, ACCESS, SID_VOICE_ReInit_D_Loop
    rgoto   SID_VOICE_ReInit_End
    
SID_VOICE_ReInit_M
    ;; go through 6 instruments
    lfsr    FSR1, SID_PATCH_BUFFER_SHADOW + SID_Ix_M_I1_BASE
    clrf    PRODL
SID_VOICE_ReInit_M_Loop
    movlw   SID_Ix_M_Vx_VOICE_ASSGN ; voice assignment located in [3:0]
    movf    PLUSW1, W
    andlw   0x0f
    addlw   -3          ; check if voice 1..6 exclusively selected
    movwf   PRODH
    movlw   6
    cpfslt  PRODH, ACCESS
    rgoto SID_VOICE_ReInit_M_Loop_Next
    ;; exclusive voice assignment found -> clear flag #6 of INSTR record
    rcall   SID_VOICE_ReInit_Sub
SID_VOICE_ReInit_M_Loop_Next
    movlw   (SID_Ix_M_I2_BASE-SID_Ix_M_I1_BASE)
    addwf   FSR1L, F
    movlw   0
    addwfc  FSR1H, F

    incf    PRODL, F
    movlw   6-1
    cpfsgt  PRODL, ACCESS
    rgoto SID_VOICE_ReInit_M_Loop
    ;;  rgoto   SID_VOICE_ReInit_End

SID_VOICE_ReInit_End
    IRQ_ENABLE
    return

    ;; help routine which searches for INSTR entry of specific voice and
    ;; clears flag #6 of this entry, so that it is exclusively assigned
    ;; IN: voice number in PRODH
SID_VOICE_ReInit_Sub
    lfsr    FSR0, SID_VOICE_QUEUE_0
    clrf    TABLAT
SID_VOICE_ReInit_Sub_Loop
    movf    TABLAT, W
    movf    PLUSW0, W
    andlw   0x7f
    xorwf   PRODH, W
    bz  SID_VOICE_ReInit_Sub_Found
    incf    TABLAT, F
    movlw   SID_VOICE_QUEUE_LEN - 1
    cpfsgt  TABLAT, ACCESS
    rgoto SID_VOICE_ReInit_Sub_Loop
    ;; very strange error which should never happen!!! issue an reset for debugging purposes
    goto    MIOS_Reset
SID_VOICE_ReInit_Sub_Found
    movf    TABLAT, W
    addlw   SID_VOICE_INSTR_OFFSET
    bcf PLUSW0, 6
    return


;; --------------------------------------------------------------------------
;;  (Re-)initializes the superpoly voice allocation
;; --------------------------------------------------------------------------
SID_VOICE_SP_ReInit
    ;; if lead engine selected:
    movff   SID_PATCH_BUFFER_SHADOW + SID_Ix_ENGINE, WREG
    andlw   0x03
    bnz SID_VOICE_SP_ReInit_NoLead
SID_VOICE_SP_ReInit_Lead
    ;; re-initialize voice queues
    rcall   SID_VOICE_Init
SID_VOICE_SP_ReInit_NoLead
    return


;; --------------------------------------------------------------------------
;;  This function searches for a voice which is not allocated, or drops the
;;  voice which played the longest note
;;  IN:  o instrument number in SID_CURRENT_MIDIVOICE
;;       o voice assignment mode in SID_CURRENT_VOICE_ASSG[2:0]
;;       o number of voices (1..6) in SID_CURRENT_VOICE_ASSG[6:4]
;;  OUT: o SID_CURRENT_VOICE contains the index of the found voice
;;       o FSR1 contains the base pointer to the found voice
;;  USES: PROD[LH], TABLAT, FSR1
;; --------------------------------------------------------------------------
SID_VOICE_Get
    SET_BSR SID_BASE

    ;; number of voices -> TABLAT
    swapf   SID_CURRENT_VOICE_ASSG, W, BANKED
    andlw   0x07
    movwf   TABLAT

    ;; branch depending on voice assignment mode
    movf    SID_CURRENT_VOICE_ASSG, W, BANKED
    andlw   0x0f
    bz  SID_VOICE_Get_LR
    addlw   -1
    bz  SID_VOICE_Get_L
    addlw   -1
    bz  SID_VOICE_Get_R
    addlw   -1

SID_VOICE_Get_S
    ;; get single voice, number in SID_CURRENT_VOICE_ASSG[3:0]  (-3)
    movwf   PRODH

    ;; only take one of the first n voices (n = TABLAT)
    decf    TABLAT, W
    cpfsgt  PRODH, ACCESS
    rgoto SID_VOICE_Get_S_Ok
    ;; decrement number of available voices
    ;; e.g.: n=3, PRODH=4 (voice 5) -> PRODH += ((comf n) + 1) -> PRODH += 0xfc+1) -> PRODH=1 (voice 2)
    comf    WREG, W
    addlw   1
    addwf   PRODH, F
    ;; ensure that PRODH is never > n (corner case where SID_CURRENT_VOICE_ASSG[2:0] contains invalid value)
    movf    TABLAT, W
    cpfslt  PRODH, ACCESS
    clrf PRODH
SID_VOICE_Get_S_Ok

    ;; search for the voice and allocate it
    lfsr    FSR1, SID_VOICE_QUEUE_0
    clrf    PRODL
SID_VOICE_Get_S_Loop
    movf    PRODL, W
    movf    PLUSW1, W
    andlw   0x7f
    xorwf   PRODH, W
    bz  SID_VOICE_Get_Found
    incf    PRODL, F
    movlw   SID_VOICE_QUEUE_LEN - 1
    cpfsgt  PRODL, ACCESS
    rgoto SID_VOICE_Get_S_Loop
SID_VOICE_Get_S_NotFound
    ;; very strange error which should never happen!!! issue an reset for debugging purposes
    goto    MIOS_Reset


    ;; ------------------------------------------------------------------
SID_VOICE_Get_L
    ;; only Voice 1..3 allowed!
    movlw   3
    cpfslt  TABLAT, ACCESS
    movwf TABLAT

SID_VOICE_Get_LR ; (all voices available)

    lfsr    FSR1, SID_VOICE_QUEUE_0
    clrf    PRODL
SID_VOICE_Get_L_Loop1
    movf    PRODL, W
    movff   PLUSW1, PRODH
    BRA_IFSET PRODH, 7, ACCESS, SID_VOICE_Get_L_Loop1_Next
    ;; don't take voice if exclusively allocated
    movf    PRODL, W
    addlw   SID_VOICE_INSTR_OFFSET
    BRA_IFCLR PLUSW1, 6, ACCESS, SID_VOICE_Get_L_Loop1_Next
    
    ;; check if voice < TABLAT (number of voices)
    decf    TABLAT, W
    cpfsgt  PRODH, ACCESS
    rgoto SID_VOICE_Get_Found
SID_VOICE_Get_L_Loop1_Next
    incf    PRODL, F
    movlw   SID_VOICE_QUEUE_LEN - 1
    cpfsgt  PRODL, ACCESS
    rgoto SID_VOICE_Get_L_Loop1
SID_VOICE_Get_L_NotFound


    ;; no free voice found, take first note in queue w/ exclusive check
    ;; it must be < TABLAT
    clrf    PRODL
SID_VOICE_Get_L_Loop2
    movf    PRODL, W
    addlw   SID_VOICE_INSTR_OFFSET
    BRA_IFCLR PLUSW1, 6, ACCESS, SID_VOICE_Get_L_Loop2_Next

    movf    PRODL, W
    movf    PLUSW1, W
    andlw   0x7f
    movwf   PRODH

    ;; check if voice < TABLAT (number of voices)
    decf    TABLAT, W
    cpfsgt  PRODH, ACCESS
    rgoto SID_VOICE_Get_Found
SID_VOICE_Get_L_Loop2_Next
    incf    PRODL, F
    movlw   SID_VOICE_QUEUE_LEN - 1
    cpfsgt  PRODL, ACCESS
    rgoto SID_VOICE_Get_L_Loop2

    ;; still no free voice found, take first note in queue w/o exclusive check
    ;; it must be < TABLAT
    clrf    PRODL
SID_VOICE_Get_L_Loop3
    movf    PRODL, W
    movf    PLUSW1, W
    andlw   0x7f
    movwf   PRODH

    ;; check if voice < TABLAT (number of voices)
    decf    TABLAT, W
    cpfsgt  PRODH, ACCESS
    rgoto SID_VOICE_Get_Found
    incf    PRODL, F
    movlw   SID_VOICE_QUEUE_LEN - 1
    cpfsgt  PRODL, ACCESS
    rgoto SID_VOICE_Get_L_Loop3
SID_VOICE_Get_L_NeverFound
    ;; very strange error which should never happen!!! issue an reset for debugging purposes
    goto    MIOS_Reset


    ;; ------------------------------------------------------------------
SID_VOICE_Get_R
    ;; if number of voices <= 3, only take voice of left channel
    movlw   3
    cpfsgt  TABLAT, ACCESS
    rgoto SID_VOICE_Get_LR

    ;; search in voice queue for free voice
    ;; only Voice 4, 5 or 6 allowed!
    lfsr    FSR1, SID_VOICE_QUEUE_0
    clrf    PRODL
SID_VOICE_Get_R_Loop1
    movf    PRODL, W
    movff   PLUSW1, PRODH
    BRA_IFSET PRODH, 7, ACCESS, SID_VOICE_Get_R_Loop1_Next
    ;; don't take voice if exclusively allocated
    movf    PRODL, W
    addlw   SID_VOICE_INSTR_OFFSET
    BRA_IFCLR PLUSW1, 6, ACCESS, SID_VOICE_Get_R_Loop1_Next

    ;; check if voice >= 3
    movlw   3
    cpfslt  PRODH, ACCESS
    rgoto SID_VOICE_Get_Found
SID_VOICE_Get_R_Loop1_Next
    incf    PRODL, F
    movlw   SID_VOICE_QUEUE_LEN - 1
    cpfsgt  PRODL, ACCESS
    rgoto SID_VOICE_Get_R_Loop1
SID_VOICE_Get_R_NotFound


    ;; no free voice found, take first note in queue w exclusive check
    ;; it must be >= 3
    clrf    PRODL
SID_VOICE_Get_R_Loop2
    movf    PRODL, W
    addlw   SID_VOICE_INSTR_OFFSET
    BRA_IFCLR PLUSW1, 6, ACCESS, SID_VOICE_Get_R_Loop2_Next

    movf    PRODL, W
    movf    PLUSW1, W
    andlw   0x7f
    movwf   PRODH
    movlw   3
    cpfslt  PRODH, ACCESS
    rgoto SID_VOICE_Get_Found
SID_VOICE_Get_R_Loop2_Next
    incf    PRODL, F
    movlw   SID_VOICE_QUEUE_LEN - 1
    cpfsgt  PRODL, ACCESS
    rgoto SID_VOICE_Get_R_Loop2


    ;; no free voice found, take first note in queue w/o exclusive check
    ;; it must be >= 3
    clrf    PRODL
SID_VOICE_Get_R_Loop3
    movf    PRODL, W
    movf    PLUSW1, W
    andlw   0x7f
    movwf   PRODH
    movlw   3
    cpfslt  PRODH, ACCESS
    rgoto SID_VOICE_Get_Found
    incf    PRODL, F
    movlw   SID_VOICE_QUEUE_LEN - 1
    cpfsgt  PRODL, ACCESS
    rgoto SID_VOICE_Get_R_Loop3
SID_VOICE_Get_R_NeverFound
    ;; very strange error which should never happen!!! issue an reset for debugging purposes
    goto    MIOS_Reset


    ;; ------------------------------------------------------------------
    ;; ------------------------------------------------------------------
    ;; free voice found - remember index | 0x80
SID_VOICE_Get_Found
    ;; entry point for SID_Voice_GetLast if selected voice has been found and is not allocated
SID_VOICE_GetLast_Cont
    movf    PRODL, W    ; mask as allocated
    bsf PLUSW1, 7
    movff   PLUSW1, PRODH

    ;; if this is not already the last voice in the queue, shift queue and put the number to the end
    movf    PRODL, W
    xorlw   SID_VOICE_QUEUE_LEN - 1
    bz  SID_VOICE_Get_End
    ;; remember instrument setting in SID_CURRENT_VOICE (dirty!!! but we know that this register will be overwritten later)
    ;; this is required to hold the exclusive allocation flag
    movf    PRODL, W
    addlw   SID_VOICE_INSTR_OFFSET
    movff   PLUSW1, SID_CURRENT_VOICE

SID_VOICE_Get_Loop2
    incf    PRODL, W
    movff   PLUSW1, TABLAT
    addlw   -1
    movff   TABLAT, PLUSW1

    incf    PRODL, W
    addlw   SID_VOICE_INSTR_OFFSET
    movff   PLUSW1, TABLAT
    addlw   -1
    movff   TABLAT, PLUSW1

    incf    PRODL, F
    movlw   SID_VOICE_QUEUE_LEN - 2
    cpfsgt  PRODL, ACCESS
    rgoto SID_VOICE_Get_Loop2

    ;; put voice number at the end of the voice queue
    movlw   SID_VOICE_QUEUE_LEN - 1
    movwf   PRODL
    movff   PRODH, PLUSW1

    ;; put instrument number at the end of the instrument queue
    ;; this is required to hold the exclusive allocation flag
    ;; (the usage of SID_CURRENT_VOICE is dirty!!! but we know that this register will be overwritten later)
    movlw   SID_VOICE_INSTR_OFFSET + SID_VOICE_QUEUE_LEN - 1
    movff   SID_CURRENT_VOICE, PLUSW1

SID_VOICE_Get_End
    ;; store the instrument number which allocates the voice
    movf    PRODL, W
    addlw   SID_VOICE_INSTR_OFFSET
    movf    PLUSW1, W
    andlw   0x40        ; (don't touch exclusive allocation flag)
    iorwf   SID_CURRENT_MIDIVOICE, W, BANKED
    movwf   TABLAT
    movf    PRODL, W
    addlw   SID_VOICE_INSTR_OFFSET
    movff   TABLAT, PLUSW1

    ;; voice number -> SID_CURRENT_VOICE
    movf    PRODH, W
    andlw   0x7f
    movff   WREG, SID_CURRENT_VOICE

    ;; create base pointer to voice
    lfsr    FSR1, SIDL_V1_BASE
    mullw   SID_Vx_RECORD_LEN
    movf    PRODL, W
    addwf   FSR1L, F
    movf    PRODH, W
    addwfc  FSR1H, F
    return


;; --------------------------------------------------------------------------
;;  This function searches for a given voice. If it is already allocated, 
;;  it continues at SID_VOICE_Get, otherwise it returns the pointer to 
;;  the selected voice
;;  IN:  o selected voice in WREG
;;       o voice assignment mode in SID_CURRENT_VOICE_ASSG[2:0]
;;       o number of voices (1..6) in SID_CURRENT_VOICE_ASSG[6:4]
;;       o SID_CURRENT_MIDIVOICE: the instrument (criteria for equal voice)
;;  OUT: o SID_CURRENT_VOICE contains the index of the found voice
;;       o FSR1 contains the base pointer to the found voice
;;  USES: PROD[LH], TABLAT, FSR1
;; --------------------------------------------------------------------------
SID_VOICE_GetLast
    movwf   PRODH       ; save voice in PRODH

    SET_BSR SID_BASE

SID_VOICE_GetLast_SearchVoice
    ;; search in voice queue for selected voice
    lfsr    FSR1, SID_VOICE_QUEUE_0
    clrf    PRODL
SID_VOICE_GetLast_Loop1
    movf    PRODL, W
    movf    PLUSW1, W
    andlw   0x7f
    xorwf   PRODH, W
    bz  SID_VOICE_GetLast_Found
    incf    PRODL, F
    movlw   SID_VOICE_QUEUE_LEN - 1
    cpfsgt  PRODL, ACCESS
    rgoto SID_VOICE_GetLast_Loop1
    
SID_VOICE_GetLast_NotFound
    ;; selected voice not found, continue at SID_VOICE_Get
    rgoto   SID_VOICE_Get

SID_VOICE_GetLast_Found
    ;; selected voice found
    ;; if instrument number not equal, we should get a new voice
    movf    PRODL, W
    addlw   SID_VOICE_INSTR_OFFSET
    movf    PLUSW1, W
    andlw   0x3f
    xorwf   SID_CURRENT_MIDIVOICE, W, BANKED
    bnz SID_VOICE_GetLast_NotFound

    ;; if number of available voices has changed meanwhile (e.g. Stereo->Mono switch):
    ;; check that voice number still < n
    swapf   SID_CURRENT_VOICE_ASSG, W, BANKED
    andlw   0x07
    cpfslt  PRODH, ACCESS
    rgoto SID_VOICE_GetLast_NotFound

SID_VOICE_GetLast_FoundGroup
    ;; it's mine!
    ;; continue at queue handling
    rgoto   SID_VOICE_GetLast_Cont


;; --------------------------------------------------------------------------
;;  This function releases a voice, so that it is free for SID_VOICE_Get
;;  IN:  o voice which should be released in SID_CURRENT_VOICE
;;  OUT: o FSR1 contains the base pointer to the voice
;;  USES: BSR, PROD[LH], TABLAT, FSR1
;; --------------------------------------------------------------------------
SID_VOICE_Release
    ;; search in voice queue for the voice
    lfsr    FSR1, SID_VOICE_QUEUE_0
    clrf    PRODL
SID_VOICE_Release_Loop
    movf    PRODL, W
    movf    PLUSW1, W
    andlw   0x7f
    xorwf   SID_CURRENT_VOICE, W, BANKED
    bz  SID_VOICE_Release_Found
    incf    PRODL, F
    movlw   SID_VOICE_QUEUE_LEN - 1
    cpfsgt  PRODL, ACCESS
    rgoto SID_VOICE_Release_Loop

SID_VOICE_Release_NotFound
    ;; very strange error which should never happen!!! issue an reset for debugging purposes
    goto    MIOS_Reset

SID_VOICE_Release_Found
    ;; clear allocate marker
    movf    PRODL, W
    bcf PLUSW1, 7

    ;; create base pointer to voice
    lfsr    FSR1, SIDL_V1_BASE
    movf    SID_CURRENT_VOICE, W, BANKED
    mullw   SID_Vx_RECORD_LEN
    movf    PRODL, W
    addwf   FSR1L, F
    movf    PRODH, W
    addwfc  FSR1H, F
    return