I've been trying to write unit tests in JavaScript against AudioParams set using setValueAtTime. My test fails in Firefox due to the following issue:
var context = new AudioContext(),
osc = context.createOscillator(),
gain_node = context.createGain();
osc.connect(gain_node);
osc.start(context.currentTime);
gain_node.connect(context.destination);
gain_node.gain.setValueAtTime(0, context.currentTime + 1);
console.log(gain_node.gain.value); // returns 1 on both Chrome & Firefox
setTimeout(function () {
console.log(gain_node.gain.value); // returns 0 on Chrome, but 1 on Firefox
}, 4000);
So, Firefox isn't retrieving the computed value at that specific time, despite correctly applying the right value. I've seen various discussion of what the correct behaviour should be on the mailing list, but I can't find a definitive answer within the spec, and therefore which browser is correct in its implementation.
As currently spec'ed, the second log should return 0.
I haven't changed Gecko to follow the spec [1] because the group is
planning to change the behavior of AudioParam [2], and, if that
change is effected, it would be better for the value getter to
return the previous value set to keep some desirable existing
behavior [3].
FWIW Chromium also returns 1 if osc is not connect()ed.
[1] https://bugzilla.mozilla.org/show_bug.cgi?id=893020
[2] http://lists.w3.org/Archives/Public/public-audio/2013OctDec/0336.html
[3] See feature 2 at
http://lists.w3.org/Archives/Public/public-audio/2013OctDec/0340.html
Thanks Karl!
From your links I understand that the spec will be changed so that .value is an alias to setValueAtTime, but also:
2. The getter returns the value provided to the setter, if no automation events have been scheduled.
Does this not mean that .value should return its intrinsic value as setValueAtTime is an automation event? I'm not sure what benefit there is to keeping the original value.
(Note that Chromium's behavior on disconnected nodes shouldn't be used as proof/disproof; currently, if they're disconnected, they have no concept of time progressing.)
Stuart - no, Karl meant that returning the value would still be the PREVIOUSLY SET value, not the value you just changed it to. I'll have to think about that.
Maybe we could just add an AudioParam.getValueAtTime(t) ?
I don't understand how that would work if t is in the future and other automations can be added.
And we also need to make the spec clarify that the getter retrieves the current intrinsic value, not computed value
@rtoy sounds as though getting the value at only context.currentTime would resolve this, not allowing computation in the future... needs further discussion
It would be very useful to be able to get the values in the future, considering the _current state_ of the timeline. It has to be async (using a promise), because of the thread hop:
partial interface AudioParam {
Promise<Float32Array> getValuesAtTime(double startTime, double duration);
}
:+1:
@cwilso proposed that having a connect() for AudioParams would be better since one could capture their output much like the output of an AudioNode. Not clear how this handles the "in the future" case though. Waiting to discuss with @padenot and others at F2F
To be clear: it _doesn't_. :) (Handle the "in the future" case.) But it does handle the "including the values from any connected input nodes" bit.
I think it depends on whether we're adding a feature for the unit testing case (in which case the .connect feature is probably more helpful) or for logging/introspection of scheduling (in which case maybe something else would be better).
We've decided that getting future values of an AudioParam is impractical given that one can't know the future of what is connected into it. Will look into connectable AudioParams for v.next.
We still need to resolve the exact definition of the value getter in the spec.
So, while there is good reason to consider a getValueAtTime(t) at some point, back to the main question of what does the value getter do? It seems to me we answered that question above by asserting that it returns the intrinsic value as defined in the spec (basically the last-set value). But developers appear to expect it to return the automation value as of currentTime which is certainly reasonable as well.
Where do people stand on this?
I would prefer the automation value; returning the intrinsic value when there are automations seems to have very little value.
As for the actual value returned, I'm not sure. I believe in Chrome the value is updated at the end of each rendering quantum.
After sleeping on it I agree: returning the automation value as of currentTime (conveniently, the next automation value that will be used by the renderer) makes the most sense. This also possesses a nice symmetry in that the setter acts like setValueAtTime(currentTime) and the getter acts like (once we have this function) getValueAtTime(currentTime).
Yes, I think the most useful thing would be to get the current computed value at a given instant (I am using this to seamlessly interrupt scheduled value changes in an envelope).
For me, being able to get the value at a future (or past) time would be unnecessary.
On Sat, Jan 16, 2016 at 7:35 AM, Joe Berkovitz [email protected]
wrote:
After sleeping on it I agree: returning the automation value as of
currentTime (conveniently, the next automation value that will be used by
the renderer) makes the most sense. This also possesses a nice symmetry in
that the setter acts like setValueAtTime(currentTime) and the getter acts
like (once we have this function) getValueAtTime(currentTime).
​I thought we didn't want a getValueAtTime(time). I think it's pretty much
unknowable. Consider if you have a source graph connected to gain.gain and
you want gain.gain.getValueAtTime(currentTime + 1). You'd have to simulate
the entire graph just to get that one value.
Ray
@rtoy I did not say we should go straight to getValueAtTime(), just that we should be able to easily determine a value equivalent to getValueAtTime(currentTime).
But perhaps a better definition for the .value getter result, given the valid problem you raised, might be getValueAtTime(currentTime - 1/sampleRate). This would give the last-computed value of the parameter at the end of the last-rendered block (or the intrinsic value, if the context had not rendered yet), a reasonable thing to return.
How does that sound?
On Tue, Feb 9, 2016 at 2:36 PM, Joe Berkovitz [email protected]
wrote:
@rtoy https://github.com/rtoy I did not say we should go straight to
getValueAtTime(), just that we should be able to easily determine a value
equivalent to getValueAtTime(currentTime).But perhaps a better definition for the .value getter result, given the
valid problem you raised, might be getValueAtTime(currentTime -
1/sampleRate). This would give the last-computed value of the parameter
at the end of the last-rendered block (or the intrinsic value, if the
context had not rendered yet), a reasonable thing to return.How does that sound?
​Yes, in principle, as long as we don't actually have to provide a value
for an arbitrary time.
In practice, I'm not 100% sure Chrome does this. I think that depending on
exactly when the .value is read, you can get the value before or after the
rendering quantum depending on whether the audio thread has handled the
audioParam for the current quantum or not.
​
—
Reply to this email directly or view it on GitHub
https://github.com/WebAudio/web-audio-api/issues/318#issuecomment-182109908
.
Ray
This is v.next, removing the TPAC 2014 tag.
That said, now that we have the two threads specified, we can properly spec this. We can say this should return the automated value according to all the calls to AudioParams automation functions that have been called before getting this value.
2 issues here
Most helpful comment
After sleeping on it I agree: returning the automation value as of
currentTime(conveniently, the next automation value that will be used by the renderer) makes the most sense. This also possesses a nice symmetry in that the setter acts likesetValueAtTime(currentTime)and the getter acts like (once we have this function)getValueAtTime(currentTime).