Skip to content
This repository has been archived by the owner on Jul 27, 2021. It is now read-only.

Implement proper ADSR envelope. #7

Open
mitchmindtree opened this issue Mar 4, 2015 · 4 comments
Open

Implement proper ADSR envelope. #7

mitchmindtree opened this issue Mar 4, 2015 · 4 comments

Comments

@mitchmindtree
Copy link
Member

http://en.wikipedia.org/wiki/Synthesizer#ADSR_envelope

Might be best to create an adsr_envelope abstraction in a separate crate.

@smosher
Copy link

smosher commented Mar 5, 2015

Just thought I'd share the technique I used to handle releases in my ADSRs.

When I evaluate the ADSR at time t(in samples), if Some(tr) <= t (that is time of release <= t_now), then I calculate the curve¹ from level to 0.0 where level is established with a recursive call into the ADSR function with the same arguments, but with tr=None and t=t_release for the release phase. This snapshots the amplitude from t=t_release without using state. This way the release always descends from the correct level.

(Since I'm using floats, I could have used infinity instead of Option, still considering it to avoid the match.)

¹ using the same curves you do, I cribbed off this project to implement them because mine were klunky. ;)

I switched my project to OCaml because DSP is all math and OCaml makes that easy (and super composable.) I may port it back to Rust when I have it working. Anyway, hopefully you can get the gist of it. This code isn't quite finished, but it does the job:

let rec adsr ?(curves=(0.,0.,0.)) at dt sl rt =
    let (ac, dc, rc) = curves in
    fun tr t ->
        let mul = match tr with
        | Some tr ->                                            (* release case *)
                let self = adsr ~curves:curves at dt sl rt in   (* get a copy of this ADSR *)
                let delt = t -. tr in                           (* delt = t_now - t_release *)
                if delt < 0. then self None t                   (* relase is in the future; evaluate up to t_now without release *)
                else if delt > rt then 0.                       (* release is over, output 0.0 *)
                else 
                    let level = self None tr in
                    curve rc level 0. (delt /. rt)              (* compute the curve *)
        | None ->
                if t < at then curve ac 0. 1. (t /. at)
                else if t < dt +. at
                then
                    let delt = t -. at in
                    curve dc 1. sl (delt /. dt)
                else sl
        in mul

Some plots with curves=(1.0,1.0,1.0) and varying t_release to test the different phases:

Release during sustain:
adsr_1

Release during decay:
adsr_2

Release during attack:
adsr_3

Looking at that last plot I just realized there's a bug. The falloff isn't immediately exponential. (Frustratingly, the previous version I wrote accounted for this and did it right.) Anyway, I updated the code above and here's a new plot:

adsr_4

I hope this helps if you need any help for the design. You might want to consider state instead for performance. I didn't bother because I would have to recurse the first time anyway to create the state and this thing is aready running quite fast even when supersampled at 16x and with a dumb plotting frontend attached to it.

There is a foible. The function blindly returns a value when the release is over so there's no indication that the note is finished. I thought about making it an Option, but instead I plan to track the detla in the voice logic and let it work out the logic. It probably makes more sense to blindly mix the voice output into the stream even after release for the remainder of the chunk and then deallocate it after the chunk is processed instead of checking at each sample.

Peace.

@mitchmindtree
Copy link
Member Author

Thanks a lot for this! Will have a proper read through it soon 👍

@smosher
Copy link

smosher commented Mar 5, 2015

Np. And yeah... sorry about the length. When I started I didn't think I'd go on so much.

@bvssvni
Copy link

bvssvni commented Mar 5, 2015

@smosher Impressive!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants