Page 1 of 1
Question about ADSR envelopes
Posted: Mon Jul 26, 2010 1:00 pm
by tokumaru
How do you guys handle the R(elease) part in engines with dynamic tempo? I mean, I believe it's necessary to know the length of the notes (in frames) to properly apply that last part of the envelope, but I can't think of an efficient way to do it... Any ideas?
Posted: Mon Jul 26, 2010 1:17 pm
by tepples
Release is just a decay state that begins at the note-off command in the sequence. It's not very audible on voice-poor synthesizers like the NES APU because it gets preempted by the next note-on.
Posted: Mon Jul 26, 2010 2:45 pm
by tokumaru
So you only get to hear the decay of notes that are followed by silence?
Posted: Mon Jul 26, 2010 4:21 pm
by tepples
Some playing styles use an effect called "staccato" in which every note is followed by silence. For example, in S3M and IT, the SC4 effect cuts the playing note 4 frames after the start of this row.
Posted: Tue Jul 27, 2010 4:08 am
by neilbaldwin
Personally I don't think the volume envelope should be tied to the note length. If you think about plucking one string on a guitar, the volume of the sound doesn't decay faster/slower depending on how quickly you pluck the string again.
What you should have is a gate on timer which controls when the envelope passes into the release phase for a held note, or use key-off commands to do the same thing if you want to truncate your note early.
Posted: Tue Jul 27, 2010 9:35 am
by tokumaru
Thanks for the insight, Neil. I'm still getting the hand of this music thing!
Posted: Tue Jul 27, 2010 9:50 am
by mic_
One thing you may want to have though is a rate scaling feature (as seen on some FM chips), where the envelope narrows at higher frequencies, according to a scaling value.
Posted: Tue Jul 27, 2010 11:01 am
by Drag
To do ADSR, you need to keep track of something called "key-on"
Key On is when the note starts, and Key Off is when the note ends. In most musical situations, you'll be given the option to either use a Key Off, or just a simple Rest to end your notes (which just cuts the notes to silence).
Anyway, to do ADSR, you basically just do this:
Key On!
Volume = whatever
A Phase: Volume += A
Repeat until Volume = Maximum
D Phase: Volume -= D
Repeat until Volume <= S
Volume = S
Wait here until Key Off
R Phase: Volume -= R
End
Key Off is just a command entered by the composer. It's entirely possible for the composer to just ignore adding the Key Offs to the song. In that case, each note will just go through attack and decay, pausing at sustain until the next note, which will interrupt the previous note, and also go through attack and decay.
As for the timing of the envelopes, I think it's best kept independent of the song tempo, like Neil said.
Posted: Tue Jul 27, 2010 11:47 am
by Bregalad
In the simple music engine, I don't have ADSR really but I have a system that let the volume decrease a couple of ticks before it ends. On a fast tempo, this will last a couple of frames and won't be very audible, but on slower tempos, this will last quite a few frames, and will be audible.
This is a "cheap" system. On a more advanced one you'd want a system with parametrable note % that is held before the release phase, or a system with key-off commands like MIDI.
Posted: Thu Jul 29, 2010 4:47 am
by neilbaldwin
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: Select all
;---------------------------------------------------------------
; 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