I rewrote my ADSR routine for this new tracker. I'm quite please with it because it only takes two thirds of a scan-line (worst case).
I'd be interested if anyone could optimise this further while retaining the same functionality.
Link here too, I normally don't use forum-friendly formatting
http://dl.dropbox.com/u/5493868/adsr.asm
Code:
;---------------------------------------------------------------
; ADSR Routine
;
; Neil Baldwin, 2010
;
; Taken from my new "Pulsar" project.
; Feel free to use it if it's any use to you
;
;---------------------------------------------------------------
;
; Requires RAM variables
;
;envelopeCounter DB 0
;envelopeSpeed DB 0
;envelopeAmp DB 0
;envelopePhase DB 0
initPhase EQU 5 ;set "envelopePhase" to this value to start it
attackPhase EQU 4
decayPhase EQU 3
sustainPhase EQU 2
releasePhase EQU 1
offPhase EQU 0 ;envelope stops at this stage
;
;Test ADSR settings
;
;For A, D, S & R, the higher the number, the quicker the speed
;For gateTime, the higher the number, the longer the sustain phase
;
attack DB $30 ;speed to go from 0 to F amplitude, if speed set to 0, envelope starts at max (0F) amplitude
decay DB $10 ;speed to go from max (0F) to sustain level
sustain DB $08 ;level at which envelope is held, see gateTime
release DB $10 ;speed to go from sustain level to 0
gateTime DB $40 ;controls the release phase. 0=hold forever, anything else is number of frames
;sustain level held until release phase is triggered
;
;Call this routine every audio engine refresh
;
;Of course, if you want to use it on multiple voices, you'll need to either use indexing on the RAM variables
;or turn it into a macro.
;
doADSR: ldy envelopePhase ;get phase address (-1)
lda envelopePhasesHi,y ;and push onto stack for RTS trick
pha
lda envelopePhasesLo,y
pha
rts
envelopePhasesLo
DL adsrOff-1,adsrRelease-1,adsrSustain-1,adsrDecay-1,adsrAttack-1,adsrInit-1
envelopePhasesHi
DH adsrOff-1,adsrRelease-1,adsrSustain-1,adsrDecay-1,adsrAttack-1,adsrInit-1
adsrInit: lda #$00 ;initialise amplitude, counter
sta envelopeAmp
sta envelopeCounter
dec envelopePhase ;then move to Attack phase
;drop through
adsrAttack: lda attack ;if Attack = 0, set max amp and move to Decay
bne @a
lda #$0F
sta envelopeAmp
dec envelopePhase
rts
@a clc ;otherwise, add Attack rate to counter
adc envelopeCounter
sta envelopeCounter
lda envelopeAmp ;if counter overflows, carry is set and the ADC #$00
adc #$00 ;will increment amplitude
cmp #$10 ;exceeded max (0F)?
bcc @b
dec envelopePhase ;yes, move to Decay
rts
@b sta envelopeAmp ;no, store new amplitude value
rts
adsrDecay: lda decay ;if Decay = 0, move to Sustain phase
bne @a
dec envelopePhase
rts
@a clc ;otherwise, add Decay speed to counter
adc envelopeCounter
sta envelopeCounter ;if counter overflow, carry is set
ror a ;this time we need to subtract from amplitude
eor #$80 ;so use ROR A to push carry into bit 7
asl a ;invert bit 7 and push back into carry
lda envelopeAmp ;so that SBC #$00 will subtract 1 if carry set after overflow
sbc #$00
cmp sustain ;reached sustain level (or 0)?
bmi @b
bcc @b
sta envelopeAmp ;no, store new amplitude
rts
@b dec envelopePhase ;yes, move to Sustain phase
lda #$00 ;and zero counter because it will be indeterminate at this point
sta envelopeCounter
rts
adsrSustain:
lda gateTime ;if Gate Time = 0, sustain forever. In practicality, you'd
;only use 0 gate time if you had a command to force the envelope
;into the release phase, as in a MIDI Key Off command
beq @a
inc envelopeCounter ;otherwise, increment counter until >= Gate Time
cmp envelopeCounter
bcs @a
dec envelopePhase ;the move to Release Phase
@a rts
adsrRelease:
lda decay ;add Release speed to counter
clc
adc envelopeCounter
sta envelopeCounter
ror a ;same trick as Decay, invert the carry and do a SBC #$00
eor #$80
asl a
lda envelopeAmp
sbc #$00
bmi @a ;subtract 1 from amplitude until >=$00
sta envelopeAmp
rts
@a dec envelopePhase ;move to Off phase, envelope done
rts
adsrOff:
lda #$00 ;could replace with just "STY envelopAmp" becase Y=0 at this point
sta envelopeAmp
rts