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