Re: Attack, Hold, Decay using linearRampToValueAtTime

Hey Lonce-

I ran into the same challenge when I was first implementing the "Analog"
Synth (http://webaudiodemos.appspot.com/midi-synth/index.html), and went
through much the same reasoning, and had several in-depth conversations
with Chris.  In short:  the scheduling system works from schedule points,
and NOT from when the scheduling happens.  In other words, you need to very
carefully think about when the start time and value and the end time and
value is for each of your ramps.  In your cases above:

1) if the attack has already completed, there is no "future schedule" to be
cancelled, so the cancelScheduleValues is effectively not important - and
the last known point was the end of the attack, so when you call linearRamp
it sets a linear ramp from the last known point.

2) if the attack has not completed, you just cancelled the attack ramp, and
the last known point was probably a setValueAtTime(0,0).  Obviously, a
linearRamp from zero to zero is going to be, well, zero.  :)

You're sort of on the right track with your solution, but you should just
setValueAtTime the value - you don't need a ramp.  The way that I do this
in the synth is to explicitly capture the value and checkpoint it after
cancelling any current schedule.  Here's the code snippet from my synth:

this.envelope.gain.cancelScheduledValues(now);
this.envelope.gain.setValueAtTime( this.envelope.gain.value, now );  //
this is necessary because of the linear ramp
this.envelope.gain.setTargetValueAtTime(0.0, now, (currentEnvR/100));

(I'm using an exponential release, which is more common in ADSR envelopes.)

In short, once you start using the scheduling system but also want to
insert your own events, you'll want to use setValueAtTime(
thisNode.param.value, now ) to explicitly set the current value and
interrupt ramps.  The big reason to not use "now" as a potential checkpoint
is that

1) it actually wouldn't fix your problem - if the attack was still not
completed when you inserted the release, you'd still get that zero-to-zero
ramp, at least if the attack end was after the release end - and then it
would ramp back up!
2) it's much, much more consistent if "now" is not taken into account as an
explicit schedule point; otherwise, it confuses some sets of scheduling.
 In particular, I think you're expecting any new schedule point to cause an
explicit schedule point of "now", with the current value, to be set.  As
attractive as it might sound at first, it actually is even more complex to
map out in the end.

It's on my list to write an article on building envelopes.

-Chris


On Wed, Jan 2, 2013 at 5:56 PM, Lonce Wyse <lonce.wyse@zwhome.org> wrote:

>
> Hello,
>
>     I have encountered some unexpected behavior in creating Attack, Hold,
> Release envelopes.
>
>  Standard scenario:
>
> Let's say you want
>     a 1 second attack to start on some "play" event, and
>     a 1 second decay to start on some "release" event.
> You want the decay to start at whatever the *current* amplitude is
> (whether yourattack is still in progress or not).
>
>
> One might expect to be able to respond to the "release" event with the
> following code:
>
>     now = config.audioContext.**currentTime;
>     stopTime = now + m_releaseTime;
>
>     gainEnvNode.gain.**cancelScheduledValues(now);
>     gainEnvNode.gain.**linearRampToValueAtTime(0, stopTime);
>
>
> However, the actual behavior of this code appears to be:
>    a) if the attack has completed, the decay ramp is computed from the
> time the attack completed, rather than "now". Thus there is a sudden jump
> to the middle of the intended decay ramp values.
>    b) if the attach has not completed, the sound stops suddenly with no
> decay (not sure how to explain that).
>
>
> So my comment/suggestion is :
>    While it makes sense to schedule a ramp to begin at the last scheduled
> value, the *time* used to begin the new events should perhaps be the time
> of the last scheduled value, or "now" (whichever is greater).
>
>
> The only solution that I have found that works in each case is to insert a
> flat ramp to update the time and value of the envelope when the "release"
> event occurs:
>
>     now = config.audioContext.**currentTime;
>     stopTime = now + m_releaseTime;
>
>     gainEnvNode.gain.**cancelScheduledValues(now);
>     gainEnvNode.gain.**linearRampToValueAtTime(**gainEnvNode.gain.value,
> now);   // THIS IS THE CHANGE FROM PREVIOUS CODE EXAMPLE
>     gainEnvNode.gain.**linearRampToValueAtTime(0, stopTime);
>
>
> This makes some sense when you get it, but is not very intuitive since (if
> I understand it correctly) since
>     a) if the attack has not completed, the inserted ramp has 0 duration,
> and
>     b) if the attack has completed, I am creating a ramp starting at a
> time long since passed.
>
> (Inserting gain.setValueAtTime(**gainEnvNode.gain.value, now) instead
> would seem more intuitive, but it does not work either.) Of course, I may
> have missed a better way to do the AHD thing completely....
>
> Congratulations on all the great work so far,
>
> - lonce
>
>
>
>
>
>
>

Received on Thursday, 3 January 2013 19:14:32 UTC