Diff
Modified: trunk/LayoutTests/ChangeLog (266711 => 266712)
--- trunk/LayoutTests/ChangeLog 2020-09-07 22:35:43 UTC (rev 266711)
+++ trunk/LayoutTests/ChangeLog 2020-09-08 01:35:14 UTC (rev 266712)
@@ -1,3 +1,24 @@
+2020-09-07 Chris Dumez <[email protected]>
+
+ AudioParam.cancelAndHoldAtTime() is missing
+ https://bugs.webkit.org/show_bug.cgi?id=215947
+ <rdar://problem/68362061>
+
+ Reviewed by Darin Adler.
+
+ * webaudio/audioparam-cancel-and-hold-expected.txt: Added.
+ * webaudio/audioparam-cancel-and-hold.html: Added.
+ * webaudio/cancel-values-crash-913217-expected.txt: Added.
+ * webaudio/cancel-values-crash-913217.html: Added.
+ * webaudio/resources/audio-param.js: Added.
+ Import layout test coverage from Blink.
+
+ * webaudio/audioparam-setValueCurveAtTime-expected.txt: Removed.
+ * webaudio/audioparam-setValueCurveAtTime.html: Removed.
+ Remove outdated test. This test is now part of web-platform-tests at
+ webaudio/the-audio-api/the-audioparam-interface/audioparam-setValueCurveAtTime.html
+ and is now passing.
+
2020-09-07 Karl Rackler <[email protected]>
Remove duplicate test expectaions from platform/mac-bigsur
Modified: trunk/LayoutTests/imported/w3c/ChangeLog (266711 => 266712)
--- trunk/LayoutTests/imported/w3c/ChangeLog 2020-09-07 22:35:43 UTC (rev 266711)
+++ trunk/LayoutTests/imported/w3c/ChangeLog 2020-09-08 01:35:14 UTC (rev 266712)
@@ -1,3 +1,17 @@
+2020-09-07 Chris Dumez <[email protected]>
+
+ AudioParam.cancelAndHoldAtTime() is missing
+ https://bugs.webkit.org/show_bug.cgi?id=215947
+ <rdar://problem/68362061>
+
+ Reviewed by Darin Adler.
+
+ Rebaseline WPT tests that are now passing.
+
+ * web-platform-tests/webaudio/idlharness.https.window-expected.txt:
+ * web-platform-tests/webaudio/the-audio-api/the-audioparam-interface/audioparam-setValueCurveAtTime-expected.txt:
+ * web-platform-tests/webaudio/the-audio-api/the-audioparam-interface/retrospective-setValueCurveAtTime-expected.txt:
+
2020-09-06 Darin Adler <[email protected]>
Make TextCodecCJK and TextCodecSingleByte thread-safe and refactor a bit to share code
Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/webaudio/idlharness.https.window-expected.txt (266711 => 266712)
--- trunk/LayoutTests/imported/w3c/web-platform-tests/webaudio/idlharness.https.window-expected.txt 2020-09-07 22:35:43 UTC (rev 266711)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/webaudio/idlharness.https.window-expected.txt 2020-09-08 01:35:14 UTC (rev 266712)
@@ -232,7 +232,7 @@
PASS AudioParam interface: operation setTargetAtTime(float, double, float)
PASS AudioParam interface: operation setValueCurveAtTime(sequence<float>, double, double)
PASS AudioParam interface: operation cancelScheduledValues(double)
-FAIL AudioParam interface: operation cancelAndHoldAtTime(double) assert_own_property: interface prototype object missing non-static operation expected property "cancelAndHoldAtTime" missing
+PASS AudioParam interface: operation cancelAndHoldAtTime(double)
PASS AudioParam must be primary interface of new AudioBufferSourceNode(context).playbackRate
PASS Stringification of new AudioBufferSourceNode(context).playbackRate
PASS AudioParam interface: new AudioBufferSourceNode(context).playbackRate must inherit property "value" with the proper type
@@ -252,8 +252,8 @@
PASS AudioParam interface: calling setValueCurveAtTime(sequence<float>, double, double) on new AudioBufferSourceNode(context).playbackRate with too few arguments must throw TypeError
PASS AudioParam interface: new AudioBufferSourceNode(context).playbackRate must inherit property "cancelScheduledValues(double)" with the proper type
PASS AudioParam interface: calling cancelScheduledValues(double) on new AudioBufferSourceNode(context).playbackRate with too few arguments must throw TypeError
-FAIL AudioParam interface: new AudioBufferSourceNode(context).playbackRate must inherit property "cancelAndHoldAtTime(double)" with the proper type assert_inherits: property "cancelAndHoldAtTime" not found in prototype chain
-FAIL AudioParam interface: calling cancelAndHoldAtTime(double) on new AudioBufferSourceNode(context).playbackRate with too few arguments must throw TypeError assert_inherits: property "cancelAndHoldAtTime" not found in prototype chain
+PASS AudioParam interface: new AudioBufferSourceNode(context).playbackRate must inherit property "cancelAndHoldAtTime(double)" with the proper type
+PASS AudioParam interface: calling cancelAndHoldAtTime(double) on new AudioBufferSourceNode(context).playbackRate with too few arguments must throw TypeError
PASS AudioScheduledSourceNode interface: existence and properties of interface object
PASS AudioScheduledSourceNode interface object length
PASS AudioScheduledSourceNode interface object name
Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/webaudio/the-audio-api/the-audioparam-interface/audioparam-setValueCurveAtTime-expected.txt (266711 => 266712)
--- trunk/LayoutTests/imported/w3c/web-platform-tests/webaudio/the-audio-api/the-audioparam-interface/audioparam-setValueCurveAtTime-expected.txt 2020-09-07 22:35:43 UTC (rev 266711)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/webaudio/the-audio-api/the-audioparam-interface/audioparam-setValueCurveAtTime-expected.txt 2020-09-08 01:35:14 UTC (rev 266712)
@@ -4,27 +4,27 @@
PASS Audit report
PASS > [test] AudioParam setValueCurveAtTime() functionality.
PASS Number of tests started and ended at the correct time is equal to 20.
-FAIL X Max error for test 0 at offset 1304 is not less than or equal to 0.0000037194. Got 0.030871472751557993. assert_true: expected true got false
-FAIL X Max error for test 1 at offset 2627 is not less than or equal to 0.0000037194. Got 0.030871472751557993. assert_true: expected true got false
-FAIL X Max error for test 2 at offset 3950 is not less than or equal to 0.0000037194. Got 0.030871472751543783. assert_true: expected true got false
-FAIL X Max error for test 3 at offset 5273 is not less than or equal to 0.0000037194. Got 0.030871472751543783. assert_true: expected true got false
-FAIL X Max error for test 4 at offset 6596 is not less than or equal to 0.0000037194. Got 0.030871472751543783. assert_true: expected true got false
-FAIL X Max error for test 5 at offset 7919 is not less than or equal to 0.0000037194. Got 0.030871472751543783. assert_true: expected true got false
-FAIL X Max error for test 6 at offset 9242 is not less than or equal to 0.0000037194. Got 0.030871472751543783. assert_true: expected true got false
-FAIL X Max error for test 7 at offset 10565 is not less than or equal to 0.0000037194. Got 0.030871472751543783. assert_true: expected true got false
-FAIL X Max error for test 8 at offset 11888 is not less than or equal to 0.0000037194. Got 0.030871472751586526. assert_true: expected true got false
-FAIL X Max error for test 9 at offset 13211 is not less than or equal to 0.0000037194. Got 0.03087147275151525. assert_true: expected true got false
-FAIL X Max error for test 10 at offset 14534 is not less than or equal to 0.0000037194. Got 0.03087147275151525. assert_true: expected true got false
-FAIL X Max error for test 11 at offset 15857 is not less than or equal to 0.0000037194. Got 0.030871472751508144. assert_true: expected true got false
-FAIL X Max error for test 12 at offset 17180 is not less than or equal to 0.0000037194. Got 0.030871472751586526. assert_true: expected true got false
-FAIL X Max error for test 13 at offset 18503 is not less than or equal to 0.0000037194. Got 0.03087147275151525. assert_true: expected true got false
-FAIL X Max error for test 14 at offset 19826 is not less than or equal to 0.0000037194. Got 0.03087147275151525. assert_true: expected true got false
-FAIL X Max error for test 15 at offset 21149 is not less than or equal to 0.0000037194. Got 0.030871472751508144. assert_true: expected true got false
-FAIL X Max error for test 16 at offset 22472 is not less than or equal to 0.0000037194. Got 0.030871472751586526. assert_true: expected true got false
-FAIL X Max error for test 17 at offset 23795 is not less than or equal to 0.0000037194. Got 0.030871472751586526. assert_true: expected true got false
-FAIL X Max error for test 18 at offset 25118 is not less than or equal to 0.0000037194. Got 0.03087147275143698. assert_true: expected true got false
-FAIL X Max error for test 19 at offset 26441 is not less than or equal to 0.0000037194. Got 0.030871472751586526. assert_true: expected true got false
+PASS Max error for test 0 at offset 903 is less than or equal to 0.0000037194.
+PASS Max error for test 1 at offset 2226 is less than or equal to 0.0000037194.
+PASS Max error for test 2 at offset 3549 is less than or equal to 0.0000037194.
+PASS Max error for test 3 at offset 4872 is less than or equal to 0.0000037194.
+PASS Max error for test 4 at offset 6195 is less than or equal to 0.0000037194.
+PASS Max error for test 5 at offset 7518 is less than or equal to 0.0000037194.
+PASS Max error for test 6 at offset 8841 is less than or equal to 0.0000037194.
+PASS Max error for test 7 at offset 10164 is less than or equal to 0.0000037194.
+PASS Max error for test 8 at offset 11487 is less than or equal to 0.0000037194.
+PASS Max error for test 9 at offset 12810 is less than or equal to 0.0000037194.
+PASS Max error for test 10 at offset 14133 is less than or equal to 0.0000037194.
+PASS Max error for test 11 at offset 15456 is less than or equal to 0.0000037194.
+PASS Max error for test 12 at offset 16779 is less than or equal to 0.0000037194.
+PASS Max error for test 13 at offset 18102 is less than or equal to 0.0000037194.
+PASS Max error for test 14 at offset 19425 is less than or equal to 0.0000037194.
+PASS Max error for test 15 at offset 20748 is less than or equal to 0.0000037194.
+PASS Max error for test 16 at offset 22071 is less than or equal to 0.0000037194.
+PASS Max error for test 17 at offset 23394 is less than or equal to 0.0000037194.
+PASS Max error for test 18 at offset 24717 is less than or equal to 0.0000037194.
+PASS Max error for test 19 at offset 26040 is less than or equal to 0.0000037194.
PASS Number of failed tests with an acceptable relative tolerance of 0.0000037194 is equal to 0.
-FAIL < [test] 20 out of 22 assertions were failed. assert_true: expected true got false
-FAIL # AUDIT TASK RUNNER FINISHED: 1 out of 1 tasks were failed. assert_true: expected true got false
+PASS < [test] All assertions passed. (total 22 assertions)
+PASS # AUDIT TASK RUNNER FINISHED: 1 tasks ran successfully.
Modified: trunk/LayoutTests/imported/w3c/web-platform-tests/webaudio/the-audio-api/the-audioparam-interface/retrospective-setValueCurveAtTime-expected.txt (266711 => 266712)
--- trunk/LayoutTests/imported/w3c/web-platform-tests/webaudio/the-audio-api/the-audioparam-interface/retrospective-setValueCurveAtTime-expected.txt 2020-09-07 22:35:43 UTC (rev 266711)
+++ trunk/LayoutTests/imported/w3c/web-platform-tests/webaudio/the-audio-api/the-audioparam-interface/retrospective-setValueCurveAtTime-expected.txt 2020-09-08 01:35:14 UTC (rev 266712)
@@ -5,7 +5,7 @@
PASS > [test] Test SetValueCurve with start time in the past
PASS Test[0:127] contains only the constant 1.
PASS Reference[0:127] contains only the constant 1.
-PASS Test[128:] is identical to the array [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1...].
+PASS Test[128:] is identical to the array [1,0.9999450445175171,0.999890148639679,0.999835193157196,0.9997802972793579,0.999725341796875,0.9996703863143921,0.999615490436554,0.999560534954071,0.9995056390762329,0.99945068359375,0.9993957281112671,0.999340832233429,0.999285876750946,0.9992309808731079,0.999176025390625...].
PASS < [test] All assertions passed. (total 3 assertions)
PASS # AUDIT TASK RUNNER FINISHED: 1 tasks ran successfully.
Added: trunk/LayoutTests/webaudio/audioparam-cancel-and-hold-expected.txt (0 => 266712)
--- trunk/LayoutTests/webaudio/audioparam-cancel-and-hold-expected.txt (rev 0)
+++ trunk/LayoutTests/webaudio/audioparam-cancel-and-hold-expected.txt 2020-09-08 01:35:14 UTC (rev 266712)
@@ -0,0 +1,113 @@
+
+PASS # AUDIT TASK RUNNER STARTED.
+PASS Executing "linear"
+PASS Executing "exponential"
+PASS Executing "setTarget"
+PASS Executing "setValueCurve"
+PASS Executing "setValueCurve after end"
+PASS Executing "initial setTarget"
+PASS Executing "post cancel: Linear"
+PASS Executing "post cancel: Exponential"
+PASS Executing "post cancel: ValueCurve"
+PASS Executing "post cancel: setTarget"
+PASS Executing "post cancel: setValue"
+PASS Executing "cancel future setTarget"
+PASS Executing "cancel setTarget now"
+PASS Executing "cancel future setValueCurve"
+PASS Executing "cancel setValueCurve now"
+PASS Executing "linear, cancel, linear, cancel, linear"
+PASS Audit report
+PASS > [linear] Cancel linearRampToValueAtTime
+PASS linearRampToValueAtTime: linearRampToValue(0, 0.5) up to time 0.25 equals [0,0.00208333320915699,0.00416666641831398,0.0062500000931322575,0.00833333283662796,0.010416666977107525,0.012500000186264515,0.01458333246409893,0.01666666567325592,0.01875000074505806,0.02083333395421505,0.02291666530072689,0.02500000037252903,0.02708333171904087,0.02916666492819786,0.03125...] with an element-wise tolerance of {"absoluteThreshold":0.000083998,"relativeThreshold":0}.
+PASS Cancelling linearRampToValueAtTime: linearRampToValue(0, 0.5) at time 0.25 contains only the constant 0.5102040767669678.
+PASS Expected value for cancelling linearRampToValueAtTime: linearRampToValue(0, 0.5) at time 0.25 is 0.5102040767669678 within an error of 0.000083998.
+PASS < [linear] All assertions passed. (total 3 assertions)
+PASS > [exponential] Cancel exponentialRampAtTime
+PASS exponentialRampToValue(0.001, 0.5) up to time 0.25 equals [0,0.00208333320915699,0.00416666641831398,0.0062500000931322575,0.00833333283662796,0.010416666977107525,0.012500000186264515,0.01458333246409893,0.01666666567325592,0.01875000074505806,0.02083333395421505,0.02291666530072689,0.02500000037252903,0.02708333171904087,0.02916666492819786,0.03125...] with an element-wise tolerance of {"absoluteThreshold":5.9605e-8,"relativeThreshold":0}.
+PASS Cancelling exponentialRampToValue(0.001, 0.5) at time 0.25 contains only the constant 0.033932220190763474.
+PASS Expected value for cancelling exponentialRampToValue(0.001, 0.5) at time 0.25 is 0.033932216465473175 within an error of 0.0000018664.
+PASS < [exponential] All assertions passed. (total 3 assertions)
+PASS > [setTarget] Cancel setTargetAtTime
+PASS setTargetAtTime(0, 0.01, 0.05) up to time 0.25 equals [0,0.00208333320915699,0.00416666641831398,0.0062500000931322575,0.00833333283662796,0.010416666977107525,0.012500000186264515,0.01458333246409893,0.01666666567325592,0.01875000074505806,0.02083333395421505,0.02291666530072689,0.02500000037252903,0.02708333171904087,0.02916666492819786,0.03125...] with an element-wise tolerance of {"absoluteThreshold":4.5267e-7,"relativeThreshold":0}.
+PASS Cancelling setTargetAtTime(0, 0.01, 0.05) at time 0.25 contains only the constant 0.008229749277234077.
+PASS Expected value for cancelling setTargetAtTime(0, 0.01, 0.05) at time 0.25 is 0.008229747414588928 within an error of 4.5267e-7.
+PASS < [setTarget] All assertions passed. (total 3 assertions)
+PASS > [setValueCurve] Cancel setValueCurveAtTime
+PASS setValueCurveAtTime([1,0], 0.01, 0.49) up to time 0.25 equals [0,0.00208333320915699,0.00416666641831398,0.0062500000931322575,0.00833333283662796,0.010416666977107525,0.012500000186264515,0.01458333246409893,0.01666666567325592,0.01875000074505806,0.02083333395421505,0.02291666530072689,0.02500000037252903,0.02708333171904087,0.02916666492819786,0.03125...] with an element-wise tolerance of {"absoluteThreshold":0,"relativeThreshold":0}.
+PASS Cancelling setValueCurveAtTime([1,0], 0.01, 0.49) at time 0.25 contains only the constant 0.5102040767669678.
+PASS Expected value for cancelling setValueCurveAtTime([1,0], 0.01, 0.49) at time 0.25 is 0.510204081632653 within an error of 9.5368e-9.
+PASS < [setValueCurve] All assertions passed. (total 3 assertions)
+PASS > [setValueCurve after end] Cancel setValueCurveAtTime after the end
+PASS setValueCurveAtTime([1,0], 0.01, 0.11499999999999999) up to time 0.25 equals [0,0.00208333320915699,0.00416666641831398,0.0062500000931322575,0.00833333283662796,0.010416666977107525,0.012500000186264515,0.01458333246409893,0.01666666567325592,0.01875000074505806,0.02083333395421505,0.02291666530072689,0.02500000037252903,0.02708333171904087,0.02916666492819786,0.03125...] with an element-wise tolerance of {"absoluteThreshold":0,"relativeThreshold":0}.
+PASS Cancelling setValueCurveAtTime([1,0], 0.01, 0.11499999999999999) at time 0.25 contains only the constant 0.
+PASS Expected value for cancelling setValueCurveAtTime([1,0], 0.01, 0.11499999999999999) at time 0.25 is 0 within an error of 0.
+PASS < [setValueCurve after end] All assertions passed. (total 3 assertions)
+PASS > [initial setTarget] Cancel with initial setTargetAtTime
+PASS setTargetAtTime(0, 0.01, 0.1) up to time 0.25 equals [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1...] with an element-wise tolerance of {"absoluteThreshold":0.000001232,"relativeThreshold":0}.
+PASS Cancelling setTargetAtTime(0, 0.01, 0.1) at time 0.25 contains only the constant 0.09071767330169678.
+FAIL X Expected value for cancelling setTargetAtTime(0, 0.01, 0.1) at time 0.25 is not close to 0.09071795642375946 within a relative error of 0.000001232 (RelErr=0.000003120904326378261). Got 0.09071767330169678. assert_true: expected true got false
+FAIL < [initial setTarget] 1 out of 3 assertions were failed. assert_true: expected true got false
+PASS > [post cancel: Linear] LinearRamp after cancelling
+PASS Post cancellation linearRampToValueAtTime: linearRampToValue(0, 0.5) up to time 0.25 equals [0,0.00208333320915699,0.00416666641831398,0.0062500000931322575,0.00833333283662796,0.010416666977107525,0.012500000186264515,0.01458333246409893,0.01666666567325592,0.01875000074505806,0.02083333395421505,0.02291666530072689,0.02500000037252903,0.02708333171904087,0.02916666492819786,0.03125...] with an element-wise tolerance of {"absoluteThreshold":0.000083998,"relativeThreshold":0}.
+PASS Cancelling Post cancellation linearRampToValueAtTime: linearRampToValue(0, 0.5) at time 0.25 contains only the constant 0.5102040767669678.
+PASS Expected value for cancelling Post cancellation linearRampToValueAtTime: linearRampToValue(0, 0.5) at time 0.25 is 0.5102040767669678 within an error of 0.000083998.
+PASS Post linearRamp(2, 0.375) equals [0.5102040767669678,0.510452389717102,0.5107007026672363,0.5109489560127258,0.5111972689628601,0.5114455819129944,0.5116938948631287,0.5119421482086182,0.5121904611587524,0.5124387741088867,0.512687087059021,0.5129353404045105,0.5131836533546448,0.513431966304779,0.5136802792549133,0.5139285922050476...] with an element-wise tolerance of {"absoluteThreshold":0,"relativeThreshold":0}.
+PASS < [post cancel: Linear] All assertions passed. (total 4 assertions)
+PASS > [post cancel: Exponential] ExponentialRamp after cancelling
+PASS Post cancel exponentialRampToValueAtTime: linearRampToValue(0, 0.5) up to time 0.25 equals [0,0.00208333320915699,0.00416666641831398,0.0062500000931322575,0.00833333283662796,0.010416666977107525,0.012500000186264515,0.01458333246409893,0.01666666567325592,0.01875000074505806,0.02083333395421505,0.02291666530072689,0.02500000037252903,0.02708333171904087,0.02916666492819786,0.03125...] with an element-wise tolerance of {"absoluteThreshold":0.000083998,"relativeThreshold":0}.
+PASS Cancelling Post cancel exponentialRampToValueAtTime: linearRampToValue(0, 0.5) at time 0.25 contains only the constant 0.5102040767669678.
+PASS Expected value for cancelling Post cancel exponentialRampToValueAtTime: linearRampToValue(0, 0.5) at time 0.25 is 0.5102040767669678 within an error of 0.000083998.
+PASS Post exponentialRamp(2, 0.375) equals [0.5102040767669678,0.510320246219635,0.5104364156723022,0.5105526447296143,0.5106688737869263,0.5107851624488831,0.5109014511108398,0.5110177993774414,0.511134147644043,0.5112505555152893,0.5113669633865356,0.511483371257782,0.5115998387336731,0.5117163062095642,0.5118328332901001,0.511949360370636...] with an element-wise tolerance of {"absoluteThreshold":0,"relativeThreshold":0}.
+PASS < [post cancel: Exponential] All assertions passed. (total 4 assertions)
+PASS > [post cancel: ValueCurve]
+PASS Post cancel setValueCurveAtTime: linearRampToValue(0, 0.5) up to time 0.25 equals [0,0.00208333320915699,0.00416666641831398,0.0062500000931322575,0.00833333283662796,0.010416666977107525,0.012500000186264515,0.01458333246409893,0.01666666567325592,0.01875000074505806,0.02083333395421505,0.02291666530072689,0.02500000037252903,0.02708333171904087,0.02916666492819786,0.03125...] with an element-wise tolerance of {"absoluteThreshold":0.000083998,"relativeThreshold":0}.
+PASS Cancelling Post cancel setValueCurveAtTime: linearRampToValue(0, 0.5) at time 0.25 contains only the constant 0.5102040767669678.
+PASS Expected value for cancelling Post cancel setValueCurveAtTime: linearRampToValue(0, 0.5) at time 0.25 is 0.5102040767669678 within an error of 0.000083998.
+PASS Post setValueCurve([0.125,2], 0.375, 0.125) equals [0.5102040767669678,0.5102040767669678,0.5102040767669678,0.5102040767669678,0.5102040767669678,0.5102040767669678,0.5102040767669678,0.5102040767669678,0.5102040767669678,0.5102040767669678,0.5102040767669678,0.5102040767669678,0.5102040767669678,0.5102040767669678,0.5102040767669678,0.5102040767669678...] with an element-wise tolerance of {"absoluteThreshold":0.000083998,"relativeThreshold":0}.
+PASS < [post cancel: ValueCurve] All assertions passed. (total 4 assertions)
+PASS > [post cancel: setTarget]
+PASS Post cancel setTargetAtTime: linearRampToValue(0, 0.5) up to time 0.25 equals [0,0.00208333320915699,0.00416666641831398,0.0062500000931322575,0.00833333283662796,0.010416666977107525,0.012500000186264515,0.01458333246409893,0.01666666567325592,0.01875000074505806,0.02083333395421505,0.02291666530072689,0.02500000037252903,0.02708333171904087,0.02916666492819786,0.03125...] with an element-wise tolerance of {"absoluteThreshold":0.000083998,"relativeThreshold":0}.
+PASS Cancelling Post cancel setTargetAtTime: linearRampToValue(0, 0.5) at time 0.25 contains only the constant 0.5102040767669678.
+PASS Expected value for cancelling Post cancel setTargetAtTime: linearRampToValue(0, 0.5) at time 0.25 is 0.5102040767669678 within an error of 0.000083998.
+PASS Post setTargetAtTime(0.125, 0.375, 0.1) equals [0.5102040767669678,0.5101238489151001,0.5100436210632324,0.5099633932113647,0.5098832249641418,0.509803056716919,0.509722888469696,0.5096427202224731,0.509562611579895,0.5094825029373169,0.5094023942947388,0.5093223452568054,0.5092422962188721,0.5091622471809387,0.5090821981430054,0.5090022087097168...] with an element-wise tolerance of {"absoluteThreshold":0.000084037,"relativeThreshold":0}.
+PASS < [post cancel: setTarget] All assertions passed. (total 4 assertions)
+PASS > [post cancel: setValue]
+PASS Post cancel setValueAtTime: linearRampToValue(0, 0.5) up to time 0.25 equals [0,0.00208333320915699,0.00416666641831398,0.0062500000931322575,0.00833333283662796,0.010416666977107525,0.012500000186264515,0.01458333246409893,0.01666666567325592,0.01875000074505806,0.02083333395421505,0.02291666530072689,0.02500000037252903,0.02708333171904087,0.02916666492819786,0.03125...] with an element-wise tolerance of {"absoluteThreshold":0.000083998,"relativeThreshold":0}.
+PASS Cancelling Post cancel setValueAtTime: linearRampToValue(0, 0.5) at time 0.25 contains only the constant 0.5102040767669678.
+PASS Expected value for cancelling Post cancel setValueAtTime: linearRampToValue(0, 0.5) at time 0.25 is 0.5102040767669678 within an error of 0.000083998.
+PASS Post setValueAtTime(0.125, 0.375) equals [0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125,0.125...] with an element-wise tolerance of {"absoluteThreshold":0,"relativeThreshold":0}.
+PASS < [post cancel: setValue] All assertions passed. (total 4 assertions)
+PASS > [cancel future setTarget]
+PASS After cancelling future setTarget event, output contains only the constant 0.5.
+PASS < [cancel future setTarget] All assertions passed. (total 1 assertions)
+PASS > [cancel setTarget now]
+PASS After cancelling setTarget event starting now, output contains only the constant 0.5.
+PASS < [cancel setTarget now] All assertions passed. (total 1 assertions)
+PASS > [cancel future setValueCurve]
+PASS After cancelling future setValueCurve event, output contains only the constant 0.5.
+PASS < [cancel future setValueCurve] All assertions passed. (total 1 assertions)
+PASS > [cancel setValueCurve now]
+PASS After cancelling current setValueCurve event starting now, output contains only the constant 0.5.
+PASS < [cancel setValueCurve now] All assertions passed. (total 1 assertions)
+PASS > [linear, cancel, linear, cancel, linear] Schedules 3 linear ramps, cancelling 2 of them, so that we end up with 2 cancel events next to each other
+FAIL X 1st linearRamp: linearRampToValue(0, 0.5) up to time 0.25 does not equal [0,0.00208333320915699,0.00416666641831398,0.0062500000931322575,0.00833333283662796,0.010416666977107525,0.012500000186264515,0.01458333246409893,0.01666666567325592,0.01875000074505806,0.02083333395421505,0.02291666530072689,0.02500000037252903,0.02708333171904087,0.02916666492819786,0.03125...] with an element-wise tolerance of {"absoluteThreshold":0,"relativeThreshold":0}.
+ Index Actual Expected AbsError RelError Test threshold
+ [715] 9.9000853300094604e-1 9.9000847339630127e-1 5.9604644775390625e-8 6.0206196590330427e-8 0.0000000000000000e+0
+ [756] 9.8826533555984497e-1 9.8826527595520020e-1 5.9604644775390625e-8 6.0312394076357907e-8 0.0000000000000000e+0
+ [1100] 9.7363948822021484e-1 9.7363942861557007e-1 5.9604644775390625e-8 6.1218396691415015e-8 0.0000000000000000e+0
+ [1223] 9.6840989589691162e-1 9.6840983629226685e-1 5.9604644775390625e-8 6.1548987362207974e-8 0.0000000000000000e+0
+ [1245] 9.6747452020645142e-1 9.6747446060180664e-1 5.9604644775390625e-8 6.1608494283471034e-8 0.0000000000000000e+0
+ ...and 1125 more errors.
+ Max AbsError of 5.9604644775390625e-8 at index of 715.
+ Max RelError of 1.1680563227414376e-7 at index of 11998.
+ [11998] 5.1028919219970703e-1 5.1028913259506226e-1 5.9604644775390625e-8 1.1680563227414376e-7 0.0000000000000000e+0
+ assert_true: expected true got false
+PASS Cancelling 1st linearRamp: linearRampToValue(0, 0.5) at time 0.25 contains only the constant 0.5102040767669678.
+PASS Expected value for cancelling 1st linearRamp: linearRampToValue(0, 0.5) at time 0.25 is 0.5102040767669678 within an error of 0.
+PASS 2nd linearRamp(2, 0.5) equals [0.5102040767669678,0.5103282332420349,0.510452389717102,0.5105765461921692,0.5107007026672363,0.5108247995376587,0.5109489560127258,0.511073112487793,0.5111972689628601,0.5113214254379272,0.5114455819129944,0.5115697383880615,0.5116938948631287,0.5118180513381958,0.5119421482086182,0.5120663046836853...] with an element-wise tolerance of {"absoluteThreshold":0,"relativeThreshold":0}.
+PASS Cancelling 2nd linearRamp(2, 0.5) at time 0.375 contains only the constant 1.2551020383834839.
+PASS Expected value for cancelling 2nd linearRamp(2, 0.5) at time 0.375 is 1.2551020383834839 within an error of 0.
+PASS 3rd linearRamp(0, 0.5) equals [1.2551020383834839,1.2548928260803223,1.2546836137771606,1.2544745206832886,1.254265308380127,1.2540560960769653,1.2538468837738037,1.2536377906799316,1.25342857837677,1.2532193660736084,1.2530101537704468,1.2528010606765747,1.252591848373413,1.2523826360702515,1.2521734237670898,1.2519643306732178...] with an element-wise tolerance of {"absoluteThreshold":0,"relativeThreshold":0}.
+FAIL < [linear, cancel, linear, cancel, linear] 1 out of 7 assertions were failed. assert_true: expected true got false
+FAIL # AUDIT TASK RUNNER FINISHED: 2 out of 16 tasks were failed. assert_true: expected true got false
+
Added: trunk/LayoutTests/webaudio/audioparam-cancel-and-hold.html (0 => 266712)
--- trunk/LayoutTests/webaudio/audioparam-cancel-and-hold.html (rev 0)
+++ trunk/LayoutTests/webaudio/audioparam-cancel-and-hold.html 2020-09-08 01:35:14 UTC (rev 266712)
@@ -0,0 +1,823 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>
+ Test CancelValuesAndHoldAtTime
+ </title>
+ <script src=""
+ <script src=""
+ <script src=""
+ <script src=""
+ <script src=""
+ </head>
+ <body>
+ <script id="layout-test-code">
+ let sampleRate = 48000;
+ let renderDuration = 0.5;
+
+ let audit = Audit.createTaskRunner();
+
+ // The first few tasks test the cancellation of each relevant automation
+ // function. For the test, a simple linear ramp from 0 to 1 is used to
+ // start things off. Then the automation to be tested is scheduled and
+ // cancelled.
+
+ audit.define(
+ {label: 'linear', description: 'Cancel linearRampToValueAtTime'},
+ function(task, should) {
+ cancelTest(should, linearRampTest('linearRampToValueAtTime'), {
+ valueThreshold: 8.3998e-5,
+ curveThreshold: 8.3998e-5
+ }).then(task.done.bind(task));
+ });
+
+ audit.define(
+ {label: 'exponential', description: 'Cancel exponentialRampAtTime'},
+ function(task, should) {
+ // Cancel an exponential ramp. The thresholds are experimentally
+ // determined.
+ cancelTest(should, function(g, v0, t0, cancelTime) {
+ // Initialize values to 0.
+ g[0].gain.setValueAtTime(0, 0);
+ g[1].gain.setValueAtTime(0, 0);
+ // Schedule a short linear ramp to start things off.
+ g[0].gain.linearRampToValueAtTime(v0, t0);
+ g[1].gain.linearRampToValueAtTime(v0, t0);
+
+ // After the linear ramp, schedule an exponential ramp to the end.
+ // (This is the event that will be be cancelled.)
+ let v1 = 0.001;
+ let t1 = renderDuration;
+
+ g[0].gain.exponentialRampToValueAtTime(v1, t1);
+ g[1].gain.exponentialRampToValueAtTime(v1, t1);
+
+ expectedConstant = Math.fround(
+ v0 * Math.pow(v1 / v0, (cancelTime - t0) / (t1 - t0)));
+ return {
+ expectedConstant: expectedConstant,
+ autoMessage: 'exponentialRampToValue(' + v1 + ', ' + t1 + ')',
+ summary: 'exponentialRampToValueAtTime',
+ };
+ }, {
+ valueThreshold: 1.8664e-6,
+ curveThreshold: 5.9605e-8
+ }).then(task.done.bind(task));
+ });
+
+ audit.define(
+ {label: 'setTarget', description: 'Cancel setTargetAtTime'},
+ function(task, should) {
+ // Cancel a setTarget event.
+ cancelTest(should, function(g, v0, t0, cancelTime) {
+ // Initialize values to 0.
+ g[0].gain.setValueAtTime(0, 0);
+ g[1].gain.setValueAtTime(0, 0);
+ // Schedule a short linear ramp to start things off.
+ g[0].gain.linearRampToValueAtTime(v0, t0);
+ g[1].gain.linearRampToValueAtTime(v0, t0);
+
+ // At the end of the linear ramp, schedule a setTarget. (This is
+ // the event that will be cancelled.)
+ let v1 = 0;
+ let t1 = t0;
+ let timeConstant = 0.05;
+
+ g[0].gain.setTargetAtTime(v1, t1, timeConstant);
+ g[1].gain.setTargetAtTime(v1, t1, timeConstant);
+
+ expectedConstant = Math.fround(
+ v1 + (v0 - v1) * Math.exp(-(cancelTime - t0) / timeConstant));
+ return {
+ expectedConstant: expectedConstant,
+ autoMessage: 'setTargetAtTime(' + v1 + ', ' + t1 + ', ' +
+ timeConstant + ')',
+ summary: 'setTargetAtTime',
+ };
+ }, {
+ valueThreshold: 4.5267e-7, // 1.1317e-7,
+ curveThreshold: 4.5267e-7
+ }).then(task.done.bind(task));
+ });
+
+ audit.define(
+ {label: 'setValueCurve', description: 'Cancel setValueCurveAtTime'},
+ function(task, should) {
+ // Cancel a setValueCurve event.
+ cancelTest(should, function(g, v0, t0, cancelTime) {
+ // Initialize values to 0.
+ g[0].gain.setValueAtTime(0, 0);
+ g[1].gain.setValueAtTime(0, 0);
+ // Schedule a short linear ramp to start things off.
+ g[0].gain.linearRampToValueAtTime(v0, t0);
+ g[1].gain.linearRampToValueAtTime(v0, t0);
+
+ // After the linear ramp, schedule a setValuesCurve. (This is the
+ // event that will be cancelled.)
+ let v1 = 0;
+ let duration = renderDuration - t0;
+
+ // For simplicity, a 2-point curve so we get a linear interpolated
+ // result.
+ let curve = Float32Array.from([v0, 0]);
+
+ g[0].gain.setValueCurveAtTime(curve, t0, duration);
+ g[1].gain.setValueCurveAtTime(curve, t0, duration);
+
+ let index =
+ Math.floor((curve.length - 1) / duration * (cancelTime - t0));
+
+ let curvePointsPerFrame =
+ (curve.length - 1) / duration / sampleRate;
+ let virtualIndex =
+ (cancelTime - t0) * sampleRate * curvePointsPerFrame;
+
+ let delta = virtualIndex - index;
+ expectedConstant = curve[0] + (curve[1] - curve[0]) * delta;
+ return {
+ expectedConstant: expectedConstant,
+ autoMessage: 'setValueCurveAtTime([' + curve + '], ' + t0 +
+ ', ' + duration + ')',
+ summary: 'setValueCurveAtTime',
+ };
+ }, {
+ valueThreshold: 9.5368e-9,
+ curveThreshold: 0
+ }).then(task.done.bind(task));
+ });
+
+ audit.define(
+ {
+ label: 'setValueCurve after end',
+ description: 'Cancel setValueCurveAtTime after the end'
+ },
+ function(task, should) {
+ cancelTest(should, function(g, v0, t0, cancelTime) {
+ // Initialize values to 0.
+ g[0].gain.setValueAtTime(0, 0);
+ g[1].gain.setValueAtTime(0, 0);
+ // Schedule a short linear ramp to start things off.
+ g[0].gain.linearRampToValueAtTime(v0, t0);
+ g[1].gain.linearRampToValueAtTime(v0, t0);
+
+ // After the linear ramp, schedule a setValuesCurve. (This is the
+ // event that will be cancelled.) Make sure the curve ends before
+ // the cancellation time.
+ let v1 = 0;
+ let duration = cancelTime - t0 - 0.125;
+
+ // For simplicity, a 2-point curve so we get a linear interpolated
+ // result.
+ let curve = Float32Array.from([v0, 0]);
+
+ g[0].gain.setValueCurveAtTime(curve, t0, duration);
+ g[1].gain.setValueCurveAtTime(curve, t0, duration);
+
+ expectedConstant = curve[1];
+ return {
+ expectedConstant: expectedConstant,
+ autoMessage: 'setValueCurveAtTime([' + curve + '], ' + t0 +
+ ', ' + duration + ')',
+ summary: 'setValueCurveAtTime',
+ };
+ }, {
+ valueThreshold: 0,
+ curveThreshold: 0
+ }).then(task.done.bind(task));
+ });
+
+ // Special case where we schedule a setTarget and there is no earlier
+ // automation event. This tests that we pick up the starting point
+ // correctly from the last setting of the AudioParam value attribute.
+
+
+ audit.define(
+ {
+ label: 'initial setTarget',
+ description: 'Cancel with initial setTargetAtTime'
+ },
+ function(task, should) {
+ cancelTest(should, function(g, v0, t0, cancelTime) {
+ let v1 = 0;
+ let timeConstant = 0.1;
+ g[0].gain.value = 1;
+ g[0].gain.setTargetAtTime(v1, t0, timeConstant);
+ g[1].gain.value = 1;
+ g[1].gain.setTargetAtTime(v1, t0, timeConstant);
+
+ let expectedConstant = Math.fround(
+ v1 + (v0 - v1) * Math.exp(-(cancelTime - t0) / timeConstant));
+
+ return {
+ expectedConstant: expectedConstant,
+ autoMessage: 'setTargetAtTime(' + v1 + ', ' + t0 + ', ' +
+ timeConstant + ')',
+ summary: 'Initial setTargetAtTime',
+ };
+ }, {
+ valueThreshold: 1.2320e-6,
+ curveThreshold: 1.2320e-6
+ }).then(task.done.bind(task));
+ });
+
+ // Test automations scheduled after the call to cancelAndHoldAtTime.
+ // Very similar to the above tests, but we also schedule an event after
+ // cancelAndHoldAtTime and verify that curve after cancellation has
+ // the correct values.
+
+ audit.define(
+ {
+ label: 'post cancel: Linear',
+ description: 'LinearRamp after cancelling'
+ },
+ function(task, should) {
+ // Run the cancel test using a linearRamp as the event to be
+ // cancelled. Then schedule another linear ramp after the
+ // cancellation.
+ cancelTest(
+ should,
+ linearRampTest('Post cancellation linearRampToValueAtTime'),
+ {valueThreshold: 8.3998e-5, curveThreshold: 8.3998e-5},
+ function(g, cancelTime, expectedConstant) {
+ // Schedule the linear ramp on g[0], and do the same for g[2],
+ // using the starting point given by expectedConstant.
+ let v2 = 2;
+ let t2 = cancelTime + 0.125;
+ g[0].gain.linearRampToValueAtTime(v2, t2);
+ g[2].gain.setValueAtTime(expectedConstant, cancelTime);
+ g[2].gain.linearRampToValueAtTime(v2, t2);
+ return {
+ constantEndTime: cancelTime,
+ message: 'Post linearRamp(' + v2 + ', ' + t2 + ')'
+ };
+ })
+ .then(task.done.bind(task));
+ });
+
+ audit.define(
+ {
+ label: 'post cancel: Exponential',
+ description: 'ExponentialRamp after cancelling'
+ },
+ function(task, should) {
+ // Run the cancel test using a linearRamp as the event to be
+ // cancelled. Then schedule an exponential ramp after the
+ // cancellation.
+ cancelTest(
+ should,
+ linearRampTest('Post cancel exponentialRampToValueAtTime'),
+ {valueThreshold: 8.3998e-5, curveThreshold: 8.3998e-5},
+ function(g, cancelTime, expectedConstant) {
+ // Schedule the exponential ramp on g[0], and do the same for
+ // g[2], using the starting point given by expectedConstant.
+ let v2 = 2;
+ let t2 = cancelTime + 0.125;
+ g[0].gain.exponentialRampToValueAtTime(v2, t2);
+ g[2].gain.setValueAtTime(expectedConstant, cancelTime);
+ g[2].gain.exponentialRampToValueAtTime(v2, t2);
+ return {
+ constantEndTime: cancelTime,
+ message: 'Post exponentialRamp(' + v2 + ', ' + t2 + ')'
+ };
+ })
+ .then(task.done.bind(task));
+ });
+
+ audit.define('post cancel: ValueCurve', function(task, should) {
+ // Run the cancel test using a linearRamp as the event to be cancelled.
+ // Then schedule a setValueCurve after the cancellation.
+ cancelTest(
+ should, linearRampTest('Post cancel setValueCurveAtTime'),
+ {valueThreshold: 8.3998e-5, curveThreshold: 8.3998e-5},
+ function(g, cancelTime, expectedConstant) {
+ // Schedule the exponential ramp on g[0], and do the same for
+ // g[2], using the starting point given by expectedConstant.
+ let t2 = cancelTime + 0.125;
+ let duration = 0.125;
+ let curve = Float32Array.from([.125, 2]);
+ g[0].gain.setValueCurveAtTime(curve, t2, duration);
+ g[2].gain.setValueAtTime(expectedConstant, cancelTime);
+ g[2].gain.setValueCurveAtTime(curve, t2, duration);
+ return {
+ constantEndTime: cancelTime,
+ message: 'Post setValueCurve([' + curve + '], ' + t2 + ', ' +
+ duration + ')',
+ errorThreshold: 8.3998e-5
+ };
+ })
+ .then(task.done.bind(task));
+ });
+
+ audit.define('post cancel: setTarget', function(task, should) {
+ // Run the cancel test using a linearRamp as the event to be cancelled.
+ // Then schedule a setTarget after the cancellation.
+ cancelTest(
+ should, linearRampTest('Post cancel setTargetAtTime'),
+ {valueThreshold: 8.3998e-5, curveThreshold: 8.3998e-5},
+ function(g, cancelTime, expectedConstant) {
+ // Schedule the exponential ramp on g[0], and do the same for
+ // g[2], using the starting point given by expectedConstant.
+ let v2 = 0.125;
+ let t2 = cancelTime + 0.125;
+ let timeConstant = 0.1;
+ g[0].gain.setTargetAtTime(v2, t2, timeConstant);
+ g[2].gain.setValueAtTime(expectedConstant, cancelTime);
+ g[2].gain.setTargetAtTime(v2, t2, timeConstant);
+ return {
+ constantEndTime: cancelTime + 0.125,
+ message: 'Post setTargetAtTime(' + v2 + ', ' + t2 + ', ' +
+ timeConstant + ')',
+ errorThreshold: 8.4037e-5
+ };
+ })
+ .then(task.done.bind(task));
+ });
+
+ audit.define('post cancel: setValue', function(task, should) {
+ // Run the cancel test using a linearRamp as the event to be cancelled.
+ // Then schedule a setTarget after the cancellation.
+ cancelTest(
+ should, linearRampTest('Post cancel setValueAtTime'),
+ {valueThreshold: 8.3998e-5, curveThreshold: 8.3998e-5},
+ function(g, cancelTime, expectedConstant) {
+ // Schedule the exponential ramp on g[0], and do the same for
+ // g[2], using the starting point given by expectedConstant.
+ let v2 = 0.125;
+ let t2 = cancelTime + 0.125;
+ g[0].gain.setValueAtTime(v2, t2);
+ g[2].gain.setValueAtTime(expectedConstant, cancelTime);
+ g[2].gain.setValueAtTime(v2, t2);
+ return {
+ constantEndTime: cancelTime + 0.125,
+ message: 'Post setValueAtTime(' + v2 + ', ' + t2 + ')'
+ };
+ })
+ .then(task.done.bind(task));
+ });
+
+ audit.define('cancel future setTarget', (task, should) => {
+ const context =
+ new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate);
+ const src = "" ConstantSourceNode(context);
+ src.connect(context.destination);
+
+ src.offset.setValueAtTime(0.5, 0);
+ src.offset.setTargetAtTime(0, 0.75 * renderDuration, 0.1);
+ // Now cancel the effect of the setTarget.
+ src.offset.cancelAndHoldAtTime(0.5 * renderDuration);
+
+ src.start();
+ context.startRendering()
+ .then(buffer => {
+ let actual = buffer.getChannelData(0);
+ // Because the setTarget was cancelled, the output should be a
+ // constant.
+ should(actual, 'After cancelling future setTarget event, output')
+ .beConstantValueOf(0.5);
+ })
+ .then(task.done.bind(task));
+ });
+
+ audit.define('cancel setTarget now', (task, should) => {
+ const context =
+ new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate);
+ const src = "" ConstantSourceNode(context);
+ src.connect(context.destination);
+
+ src.offset.setValueAtTime(0.5, 0);
+ src.offset.setTargetAtTime(0, 0.5 * renderDuration, 0.1);
+ // Now cancel the effect of the setTarget.
+ src.offset.cancelAndHoldAtTime(0.5 * renderDuration);
+
+ src.start();
+ context.startRendering()
+ .then(buffer => {
+ let actual = buffer.getChannelData(0);
+ // Because the setTarget was cancelled, the output should be a
+ // constant.
+ should(
+ actual,
+ 'After cancelling setTarget event starting now, output')
+ .beConstantValueOf(0.5);
+ })
+ .then(task.done.bind(task));
+ });
+
+ audit.define('cancel future setValueCurve', (task, should) => {
+ const context =
+ new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate);
+ const src = "" ConstantSourceNode(context);
+ src.connect(context.destination);
+
+ src.offset.setValueAtTime(0.5, 0);
+ src.offset.setValueCurveAtTime([-1, 1], 0.75 * renderDuration, 0.1);
+ // Now cancel the effect of the setTarget.
+ src.offset.cancelAndHoldAtTime(0.5 * renderDuration);
+
+ src.start();
+ context.startRendering()
+ .then(buffer => {
+ let actual = buffer.getChannelData(0);
+ // Because the setTarget was cancelled, the output should be a
+ // constant.
+ should(
+ actual, 'After cancelling future setValueCurve event, output')
+ .beConstantValueOf(0.5);
+ })
+ .then(task.done.bind(task));
+ });
+
+ audit.define('cancel setValueCurve now', (task, should) => {
+ const context =
+ new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate);
+ const src = "" ConstantSourceNode(context);
+ src.connect(context.destination);
+
+ src.offset.setValueAtTime(0.5, 0);
+ src.offset.setValueCurveAtTime([-1, 1], 0.5 * renderDuration, 0.1);
+ // Now cancel the effect of the setTarget.
+ src.offset.cancelAndHoldAtTime(0.5 * renderDuration);
+
+ src.start();
+ context.startRendering()
+ .then(buffer => {
+ let actual = buffer.getChannelData(0);
+ // Because the setTarget was cancelled, the output should be a
+ // constant.
+ should(
+ actual,
+ 'After cancelling current setValueCurve event starting now, output')
+ .beConstantValueOf(0.5);
+ })
+ .then(task.done.bind(task));
+ });
+
+ audit.define(
+ {
+ label: 'linear, cancel, linear, cancel, linear',
+ description: 'Schedules 3 linear ramps, cancelling 2 of them, '
+ + 'so that we end up with 2 cancel events next to each other'
+ },
+ (task, should) => {
+ cancelTest2(
+ should,
+ linearRampTest('1st linearRamp'),
+ {valueThreshold: 0, curveThreshold: 0},
+ (g, cancelTime, expectedConstant, cancelTime2) => {
+ // Ramp from first cancel time to the end will be cancelled at
+ // second cancel time.
+ const v1 = expectedConstant;
+ const t1 = cancelTime;
+ const v2 = 2;
+ const t2 = renderDuration;
+ g[0].gain.linearRampToValueAtTime(v2, t2);
+ g[2].gain.setValueAtTime(v1, t1);
+ g[2].gain.linearRampToValueAtTime(v2, t2);
+
+ const expectedConstant2 =
+ audioParamLinearRamp(cancelTime2, v1, t1, v2, t2);
+
+ return {
+ constantEndTime: cancelTime,
+ message: `2nd linearRamp(${v2}, ${t2})`,
+ expectedConstant2
+ };
+ },
+ (g, cancelTime2, expectedConstant2) => {
+ // Ramp from second cancel time to the end.
+ const v3 = 0;
+ const t3 = renderDuration;
+ g[0].gain.linearRampToValueAtTime(v3, t3);
+ g[3].gain.setValueAtTime(expectedConstant2, cancelTime2);
+ g[3].gain.linearRampToValueAtTime(v3, t3);
+ return {
+ constantEndTime2: cancelTime2,
+ message2: `3rd linearRamp(${v3}, ${t3})`,
+ };
+ })
+ .then(() => task.done());
+ });
+
+ audit.run();
+
+ // Common function for doing a linearRamp test. This just does a linear
+ // ramp from 0 to v0 at from time 0 to t0. Then another linear ramp is
+ // scheduled from v0 to 0 from time t0 to t1. This is the ramp that is to
+ // be cancelled.
+ function linearRampTest(message) {
+ return function(g, v0, t0, cancelTime) {
+ g[0].gain.setValueAtTime(0, 0);
+ g[1].gain.setValueAtTime(0, 0);
+ g[0].gain.linearRampToValueAtTime(v0, t0);
+ g[1].gain.linearRampToValueAtTime(v0, t0);
+
+ let v1 = 0;
+ let t1 = renderDuration;
+ g[0].gain.linearRampToValueAtTime(v1, t1);
+ g[1].gain.linearRampToValueAtTime(v1, t1);
+
+ expectedConstant =
+ Math.fround(v0 + (v1 - v0) * (cancelTime - t0) / (t1 - t0));
+
+ return {
+ expectedConstant: expectedConstant,
+ autoMessage:
+ message + ': linearRampToValue(' + v1 + ', ' + t1 + ')',
+ summary: message,
+ };
+ }
+ }
+
+ // Run the cancellation test. A set of automations is created and
+ // canceled.
+ //
+ // |testerFunction| is a function that generates the automation to be
+ // tested. It is given an array of 3 gain nodes, the value and time of an
+ // initial linear ramp, and the time where the cancellation should occur.
+ // The function must do the automations for the first two gain nodes. It
+ // must return a dictionary with |expectedConstant| being the value at the
+ // cancellation time, |autoMessage| for message to describe the test, and
+ // |summary| for general summary message to be printed at the end of the
+ // test.
+ //
+ // |thresholdOptions| is a property bag that specifies the error threshold
+ // to use. |thresholdOptions.valueThreshold| is the error threshold for
+ // comparing the actual constant output after cancelling to the expected
+ // value. |thresholdOptions.curveThreshold| is the error threshold for
+ // comparing the actual and expected automation curves before the
+ // cancelation point.
+ //
+ // For cancellation tests, |postCancelTest| is a function that schedules
+ // some automation after the cancellation. It takes 3 arguments: an array
+ // of the gain nodes, the cancellation time, and the expected value at the
+ // cancellation time. This function must return a dictionary consisting
+ // of |constantEndtime| indicating when the held constant from
+ // cancellation stops being constant, |message| giving a summary of what
+ // automation is being used, and |errorThreshold| that is the error
+ // threshold between the expected curve and the actual curve.
+ //
+ function cancelTest(
+ should, testerFunction, thresholdOptions, postCancelTest) {
+ // Create a context with three channels. Channel 0 is the test channel
+ // containing the actual output that includes the cancellation of
+ // events. Channel 1 is the expected data upto the cancellation so we
+ // can verify the cancellation produced the correct result. Channel 2
+ // is for verifying events inserted after the cancellation so we can
+ // verify that automations are correctly generated after the
+ // cancellation point.
+ let context =
+ new OfflineAudioContext(3, renderDuration * sampleRate, sampleRate);
+
+ // Test source is a constant signal
+ let src = ""
+ src.buffer = createConstantBuffer(context, 1, 1);
+ src.loop = true;
+
+ // We'll do the automation tests with three gain nodes. One (g0) will
+ // have cancelAndHoldAtTime and the other (g1) will not. g1 is
+ // used as the expected result for that automation up to the
+ // cancellation point. They should be the same. The third node (g2) is
+ // used for testing automations inserted after the cancellation point,
+ // if any. g2 is the expected result from the cancellation point to the
+ // end of the test.
+
+ let g0 = context.createGain();
+ let g1 = context.createGain();
+ let g2 = context.createGain();
+ let v0 = 1;
+ let t0 = 0.01;
+
+ let cancelTime = renderDuration / 2;
+
+ // Test automation here. The tester function is responsible for setting
+ // up the gain nodes with the desired automation for testing.
+ autoResult = testerFunction([g0, g1, g2], v0, t0, cancelTime);
+ let expectedConstant = autoResult.expectedConstant;
+ let autoMessage = autoResult.autoMessage;
+ let summaryMessage = autoResult.summary;
+
+ // Cancel scheduled events somewhere in the middle of the test
+ // automation.
+ g0.gain.cancelAndHoldAtTime(cancelTime);
+
+ let constantEndTime;
+ if (postCancelTest) {
+ postResult =
+ postCancelTest([g0, g1, g2], cancelTime, expectedConstant);
+ constantEndTime = postResult.constantEndTime;
+ }
+
+ // Connect everything together (with a merger to make a two-channel
+ // result). Channel 0 is the test (with cancelAndHoldAtTime) and
+ // channel 1 is the reference (without cancelAndHoldAtTime).
+ // Channel 1 is used to verify that everything up to the cancellation
+ // has the correct values.
+ src.connect(g0);
+ src.connect(g1);
+ src.connect(g2);
+ let merger = context.createChannelMerger(3);
+ g0.connect(merger, 0, 0);
+ g1.connect(merger, 0, 1);
+ g2.connect(merger, 0, 2);
+ merger.connect(context.destination);
+
+ // Go!
+ src.start();
+
+ return context.startRendering().then(function(buffer) {
+ let actual = buffer.getChannelData(0);
+ let expected = buffer.getChannelData(1);
+
+ // The actual output should be a constant from the cancel time to the
+ // end. We use the last value of the actual output as the constant,
+ // but we also want to compare that with what we thought it should
+ // really be.
+
+ let cancelFrame = Math.ceil(cancelTime * sampleRate);
+
+ // Verify that the curves up to the cancel time are "identical". The
+ // should be but round-off may make them differ slightly due to the
+ // way cancelling is done.
+ let endFrame = Math.floor(cancelTime * sampleRate);
+ should(
+ actual.slice(0, endFrame),
+ autoMessage + ' up to time ' + cancelTime)
+ .beCloseToArray(
+ expected.slice(0, endFrame),
+ {absoluteThreshold: thresholdOptions.curveThreshold});
+
+ // Verify the output after the cancellation is a constant.
+ let actualTail;
+ let constantEndFrame;
+
+ if (postCancelTest) {
+ constantEndFrame = Math.ceil(constantEndTime * sampleRate);
+ actualTail = actual.slice(cancelFrame, constantEndFrame);
+ } else {
+ actualTail = actual.slice(cancelFrame);
+ }
+
+ let actualConstant = actual[cancelFrame];
+
+ should(
+ actualTail,
+ 'Cancelling ' + autoMessage + ' at time ' + cancelTime)
+ .beConstantValueOf(actualConstant);
+
+ // Verify that the constant is the value we expect.
+ should(
+ actualConstant,
+ 'Expected value for cancelling ' + autoMessage + ' at time ' +
+ cancelTime)
+ .beCloseTo(
+ expectedConstant,
+ {threshold: thresholdOptions.valueThreshold});
+
+ // Verify the curve after the constantEndTime matches our
+ // expectations.
+ if (postCancelTest) {
+ let c2 = buffer.getChannelData(2);
+ should(actual.slice(constantEndFrame), postResult.message)
+ .beCloseToArray(
+ c2.slice(constantEndFrame),
+ {absoluteThreshold: postResult.errorThreshold || 0});
+ }
+ });
+ }
+
+ // Similar to cancelTest, but does 2 cancels.
+ function cancelTest2(
+ should, testerFunction, thresholdOptions,
+ postCancelTest, postCancelTest2) {
+ // Channel 0: Actual output that includes the cancellation of events.
+ // Channel 1: Expected data up to the first cancellation.
+ // Channel 2: Expected data from 1st cancellation to 2nd cancellation.
+ // Channel 3: Expected data from 2nd cancellation to the end.
+ const context =
+ new OfflineAudioContext(4, renderDuration * sampleRate, sampleRate);
+
+ const src = ""
+
+ // g0: Actual gain which will have cancelAndHoldAtTime called on it
+ // twice.
+ // g1: Expected gain from start to the 1st cancel.
+ // g2: Expected gain from 1st cancel to the 2nd cancel.
+ // g3: Expected gain from the 2nd cancel to the end.
+ const g0 = context.createGain();
+ const g1 = context.createGain();
+ const g2 = context.createGain();
+ const g3 = context.createGain();
+ const v0 = 1;
+ const t0 = 0.01;
+
+ const cancelTime1 = renderDuration * 0.5;
+ const cancelTime2 = renderDuration * 0.75;
+
+ // Run testerFunction to generate the 1st ramp.
+ const {
+ expectedConstant, autoMessage, summaryMessage} =
+ testerFunction([g0, g1, g2], v0, t0, cancelTime1);
+
+ // 1st cancel, cancelling the 1st ramp.
+ g0.gain.cancelAndHoldAtTime(cancelTime1);
+
+ // Run postCancelTest to generate the 2nd ramp.
+ const {
+ constantEndTime, message, errorThreshold = 0, expectedConstant2} =
+ postCancelTest(
+ [g0, g1, g2], cancelTime1, expectedConstant, cancelTime2);
+
+ // 2nd cancel, cancelling the 2nd ramp.
+ g0.gain.cancelAndHoldAtTime(cancelTime2);
+
+ // Run postCancelTest2 to generate the 3rd ramp.
+ const {constantEndTime2, message2} =
+ postCancelTest2([g0, g1, g2, g3], cancelTime2, expectedConstant2);
+
+ // Connect everything together
+ src.connect(g0);
+ src.connect(g1);
+ src.connect(g2);
+ src.connect(g3);
+ const merger = context.createChannelMerger(4);
+ g0.connect(merger, 0, 0);
+ g1.connect(merger, 0, 1);
+ g2.connect(merger, 0, 2);
+ g3.connect(merger, 0, 3);
+ merger.connect(context.destination);
+
+ // Go!
+ src.start();
+
+ return context.startRendering().then(function (buffer) {
+ const actual = buffer.getChannelData(0);
+ const expected1 = buffer.getChannelData(1);
+ const expected2 = buffer.getChannelData(2);
+ const expected3 = buffer.getChannelData(3);
+
+ const cancelFrame1 = Math.ceil(cancelTime1 * sampleRate);
+ const cancelFrame2 = Math.ceil(cancelTime2 * sampleRate);
+
+ const constantEndFrame1 = Math.ceil(constantEndTime * sampleRate);
+ const constantEndFrame2 = Math.ceil(constantEndTime2 * sampleRate);
+
+ const actualTail1 = actual.slice(cancelFrame1, constantEndFrame1);
+ const actualTail2 = actual.slice(cancelFrame2, constantEndFrame2);
+
+ const actualConstant1 = actual[cancelFrame1];
+ const actualConstant2 = actual[cancelFrame2];
+
+ // Verify first section curve
+ should(
+ actual.slice(0, cancelFrame1),
+ autoMessage + ' up to time ' + cancelTime1)
+ .beCloseToArray(
+ expected1.slice(0, cancelFrame1),
+ {absoluteThreshold: thresholdOptions.curveThreshold});
+
+ // Verify that a value was held after 1st cancel
+ should(
+ actualTail1,
+ 'Cancelling ' + autoMessage + ' at time ' + cancelTime1)
+ .beConstantValueOf(actualConstant1);
+
+ // Verify that held value after 1st cancel was correct
+ should(
+ actualConstant1,
+ 'Expected value for cancelling ' + autoMessage + ' at time ' +
+ cancelTime1)
+ .beCloseTo(
+ expectedConstant,
+ {threshold: thresholdOptions.valueThreshold});
+
+ // Verify middle section curve
+ should(actual.slice(constantEndFrame1, cancelFrame2), message)
+ .beCloseToArray(
+ expected2.slice(constantEndFrame1, cancelFrame2),
+ {absoluteThreshold: errorThreshold});
+
+ // Verify that a value was held after 2nd cancel
+ should(
+ actualTail2,
+ 'Cancelling ' + message + ' at time ' + cancelTime2)
+ .beConstantValueOf(actualConstant2);
+
+ // Verify that held value after 2nd cancel was correct
+ should(
+ actualConstant2,
+ 'Expected value for cancelling ' + message + ' at time ' +
+ cancelTime2)
+ .beCloseTo(
+ expectedConstant2,
+ {threshold: thresholdOptions.valueThreshold});
+
+ // Verify end section curve
+ should(actual.slice(constantEndFrame2), message2)
+ .beCloseToArray(
+ expected3.slice(constantEndFrame2),
+ {absoluteThreshold: errorThreshold || 0});
+ });
+ }
+ </script>
+ </body>
+</html>
Deleted: trunk/LayoutTests/webaudio/audioparam-setValueCurveAtTime-expected.txt (266711 => 266712)
--- trunk/LayoutTests/webaudio/audioparam-setValueCurveAtTime-expected.txt 2020-09-07 22:35:43 UTC (rev 266711)
+++ trunk/LayoutTests/webaudio/audioparam-setValueCurveAtTime-expected.txt 2020-09-08 01:35:14 UTC (rev 266712)
@@ -1,11 +0,0 @@
-Test AudioParam setValueCurveAtTime() functionality.
-
-On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
-
-PASS All 20 tests started and ended at the correct time.
-PASS All 20 tests passed within an acceptable tolerance.
-PASS AudioParam setValueCurveAtTime() test passed.
-PASS successfullyParsed is true
-
-TEST COMPLETE
-
Deleted: trunk/LayoutTests/webaudio/audioparam-setValueCurveAtTime.html (266711 => 266712)
--- trunk/LayoutTests/webaudio/audioparam-setValueCurveAtTime.html 2020-09-07 22:35:43 UTC (rev 266711)
+++ trunk/LayoutTests/webaudio/audioparam-setValueCurveAtTime.html 2020-09-08 01:35:14 UTC (rev 266712)
@@ -1,74 +0,0 @@
-<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
-<html>
-<head>
-<script src=""
-<script src=""
-<script src=""
-</head>
-
-<body>
-<div id="description"></div>
-<div id="console"></div>
-
-<script>
-description("Test AudioParam setValueCurveAtTime() functionality.");
-
-// Play a long DC signal out through an AudioGainNode and for each time interval call
-// setValueCurveAtTime() to set the values for the duration of the interval. Each curve is a sine
-// wave, and we assume that the time interval is not an exact multiple of the period. This causes a
-// discontinuity between time intervals which is used to test timing.
-
-// Number of tests to run.
-var numberOfTests = 20;
-
-// Max allowed difference between the rendered data and the expected result. (The error is zero
-// because the rendered curve should really be exactly the same as the reference.)
-var maxAllowedError = 0;
-
-// The amplitude of the sine wave.
-var sineAmplitude = 1;
-
-// Frequency of the sine wave.
-var freqHz = 440;
-
-// Curve to use for setValueCurveAtTime().
-var curve;
-
-// Sets the curve data for the entire time interval.
-function automation(value, startTime, endTime)
-{
- gainNode.gain.setValueCurveAtTime(curve, startTime, endTime - startTime);
-}
-
-// Create a sine wave of the specified duration.
-function createReferenceSineArray(startTime, endTime, startValue, endValue, sampleRate)
-{
- // Ignore |startValue| and |endValue| for the sine wave.
- return createSineWaveArray(endTime - startTime, freqHz, sineAmplitude, sampleRate);
-}
-
-function runTest()
-{
- // The curve of values to use.
- curve = createSineWaveArray(timeInterval, freqHz, sineAmplitude, sampleRate);
-
- createAudioGraphAndTest(numberOfTests,
- sineAmplitude,
- function(k) {
- // Don't need to set the value.
- },
- automation,
- "setValueCurveAtTime()",
- maxAllowedError,
- createReferenceSineArray,
- 2 * Math.PI * sineAmplitude * freqHz / sampleRate);
-}
-
-runTest();
-successfullyParsed = true;
-
-</script>
-
-<script src=""
-</body>
-</html>
Added: trunk/LayoutTests/webaudio/cancel-values-crash-913217-expected.txt (0 => 266712)
--- trunk/LayoutTests/webaudio/cancel-values-crash-913217-expected.txt (rev 0)
+++ trunk/LayoutTests/webaudio/cancel-values-crash-913217-expected.txt 2020-09-08 01:35:14 UTC (rev 266712)
@@ -0,0 +1,8 @@
+
+PASS # AUDIT TASK RUNNER STARTED.
+PASS Executing "crash"
+PASS Audit report
+PASS > [crash]
+PASS < [crash] All assertions passed. (total 0 assertions)
+PASS # AUDIT TASK RUNNER FINISHED: 1 tasks ran successfully.
+
Added: trunk/LayoutTests/webaudio/cancel-values-crash-913217.html (0 => 266712)
--- trunk/LayoutTests/webaudio/cancel-values-crash-913217.html (rev 0)
+++ trunk/LayoutTests/webaudio/cancel-values-crash-913217.html 2020-09-08 01:35:14 UTC (rev 266712)
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>
+ Test if canceling events after setTargetAtTime crashes
+ </title>
+ <script src=""
+ <script src=""
+ <script src=""
+ <script src=""
+ </head>
+ <body>
+ <script id="layout-test-code">
+ // Arbitrary parameters for the offline context.
+ const sampleRate = 48000;
+ const renderDuration = 1;
+ const channels = 2;
+ const renderFrames = renderDuration * sampleRate;
+
+ const audit = Audit.createTaskRunner();
+
+ audit.define('crash', (task, should) => {
+ const context =
+ new OfflineAudioContext(channels, renderFrames, sampleRate);
+ const source = context.createConstantSource();
+ const analyser = context.createAnalyser();
+ source.connect(analyser);
+
+ // Scheduling the following at an earlier time than above causes the
+ // page to crash in 73.0.3638.0. See crbug.com/913217.
+ source.offset.setValueAtTime(1, context.currentTime);
+ source.offset.setTargetAtTime(0, context.currentTime + 2, 0.00001);
+ source.offset.cancelAndHoldAtTime(context.currentTime + 1.99999);
+
+ source.start(context.currentTime);
+
+ context.startRendering().then(() => {
+ // Should finish without a crash.
+ task.done();
+ });
+ });
+
+ audit.run();
+ </script>
+ </body>
+</html>
Added: trunk/LayoutTests/webaudio/resources/audio-param.js (0 => 266712)
--- trunk/LayoutTests/webaudio/resources/audio-param.js (rev 0)
+++ trunk/LayoutTests/webaudio/resources/audio-param.js 2020-09-08 01:35:14 UTC (rev 266712)
@@ -0,0 +1,44 @@
+// Define functions that implement the formulas for AudioParam automations.
+
+// AudioParam linearRamp value at time t for a linear ramp between (t0, v0) and
+// (t1, v1). It is assumed that t0 <= t. Results are undefined otherwise.
+function audioParamLinearRamp(t, v0, t0, v1, t1) {
+ if (t >= t1)
+ return v1;
+ return (v0 + (v1 - v0) * (t - t0) / (t1 - t0))
+}
+
+// AudioParam exponentialRamp value at time t for an exponential ramp between
+// (t0, v0) and (t1, v1). It is assumed that t0 <= t. Results are undefined
+// otherwise.
+function audioParamExponentialRamp(t, v0, t0, v1, t1) {
+ if (t >= t1)
+ return v1;
+ return v0 * Math.pow(v1 / v0, (t - t0) / (t1 - t0));
+}
+
+// AudioParam setTarget value at time t for a setTarget curve starting at (t0,
+// v0) with a final value of vFainal and a time constant of timeConstant. It is
+// assumed that t0 <= t. Results are undefined otherwise.
+function audioParamSetTarget(t, v0, t0, vFinal, timeConstant) {
+ return vFinal + (v0 - vFinal) * Math.exp(-(t - t0) / timeConstant);
+}
+
+// AudioParam setValueCurve value at time t for a setValueCurve starting at time
+// t0 with curve, curve, and duration duration. The sample rate is sampleRate.
+// It is assumed that t0 <= t.
+function audioParamSetValueCurve(t, curve, t0, duration) {
+ if (t > t0 + duration)
+ return curve[curve.length - 1];
+
+ let curvePointsPerSecond = (curve.length - 1) / duration;
+
+ let virtualIndex = (t - t0) * curvePointsPerSecond;
+ let index = Math.floor(virtualIndex);
+
+ let delta = virtualIndex - index;
+
+ let c0 = curve[index];
+ let c1 = curve[Math.min(index + 1, curve.length - 1)];
+ return c0 + (c1 - c0) * delta;
+}
Modified: trunk/Source/WTF/wtf/UniqueRef.h (266711 => 266712)
--- trunk/Source/WTF/wtf/UniqueRef.h 2020-09-07 22:35:43 UTC (rev 266711)
+++ trunk/Source/WTF/wtf/UniqueRef.h 2020-09-08 01:35:14 UTC (rev 266712)
@@ -46,6 +46,12 @@
}
template<typename T>
+UniqueRef<T> makeUniqueRefFromNonNullUniquePtr(std::unique_ptr<T> ptr)
+{
+ return UniqueRef<T>(*ptr.release());
+}
+
+template<typename T>
class UniqueRef {
public:
template <typename U>
@@ -71,6 +77,7 @@
private:
template<class U, class... Args> friend UniqueRef<U> makeUniqueRefWithoutFastMallocCheck(Args&&...);
+ template<class U> friend UniqueRef<U> makeUniqueRefFromNonNullUniquePtr(std::unique_ptr<U>);
template<class U> friend class UniqueRef;
UniqueRef(T& other)
@@ -87,3 +94,4 @@
using WTF::UniqueRef;
using WTF::makeUniqueRef;
using WTF::makeUniqueRefWithoutFastMallocCheck;
+using WTF::makeUniqueRefFromNonNullUniquePtr;
Modified: trunk/Source/WebCore/ChangeLog (266711 => 266712)
--- trunk/Source/WebCore/ChangeLog 2020-09-07 22:35:43 UTC (rev 266711)
+++ trunk/Source/WebCore/ChangeLog 2020-09-08 01:35:14 UTC (rev 266712)
@@ -1,3 +1,53 @@
+2020-09-07 Chris Dumez <[email protected]>
+
+ AudioParam.cancelAndHoldAtTime() is missing
+ https://bugs.webkit.org/show_bug.cgi?id=215947
+ <rdar://problem/68362061>
+
+ Reviewed by Darin Adler.
+
+ Add implementation for AudioParam.cancelAndHoldAtTime():
+ - https://www.w3.org/TR/webaudio/#dom-audioparam-cancelandholdattime
+
+ This patch is based on the following Blink commit by Raymond Toy:
+ - https://codereview.chromium.org/1533103002
+
+ Tests: webaudio/audioparam-cancel-and-hold.html
+ webaudio/cancel-values-crash-913217.html
+
+ * Modules/webaudio/AudioParam.cpp:
+ * Modules/webaudio/AudioParam.h:
+ * Modules/webaudio/AudioParam.idl:
+ * Modules/webaudio/AudioParamTimeline.cpp:
+ (WebCore::AudioParamTimeline::setValueAtTime):
+ (WebCore::AudioParamTimeline::linearRampToValueAtTime):
+ (WebCore::AudioParamTimeline::exponentialRampToValueAtTime):
+ (WebCore::AudioParamTimeline::setTargetAtTime):
+ (WebCore::AudioParamTimeline::setValueCurveAtTime):
+ (WebCore::AudioParamTimeline::insertEvent):
+ (WebCore::AudioParamTimeline::cancelAndHoldAtTime):
+ (WebCore::AudioParamTimeline::removeCancelledEvents):
+ (WebCore::AudioParamTimeline::valueForContextTime):
+ (WebCore::AudioParamTimeline::valuesForTimeRangeImpl):
+ (WebCore::AudioParamTimeline::linearRampAtTime):
+ (WebCore::AudioParamTimeline::exponentialRampAtTime):
+ (WebCore::AudioParamTimeline::valueCurveAtTime):
+ (WebCore::AudioParamTimeline::handleCancelValues):
+ * Modules/webaudio/AudioParamTimeline.h:
+ (WebCore::AudioParamTimeline::ParamEvent::createSetValueEvent):
+ (WebCore::AudioParamTimeline::ParamEvent::createLinearRampEvent):
+ (WebCore::AudioParamTimeline::ParamEvent::createExponentialRampEvent):
+ (WebCore::AudioParamTimeline::ParamEvent::createSetTargetEvent):
+ (WebCore::AudioParamTimeline::ParamEvent::createSetValueCurveEvent):
+ (WebCore::AudioParamTimeline::ParamEvent::createCancelValuesEvent):
+ (WebCore::AudioParamTimeline::ParamEvent::ParamEvent):
+ (WebCore::AudioParamTimeline::ParamEvent::type const):
+ (WebCore::AudioParamTimeline::ParamEvent::savedEvent):
+ (WebCore::AudioParamTimeline::ParamEvent::setCancelledValue):
+ (WebCore::AudioParamTimeline::ParamEvent::hasDefaultCancelledValue const):
+ (WebCore::AudioParamTimeline::ParamEvent::curvePointsPerSecond const):
+ (WebCore::AudioParamTimeline::ParamEvent::curveEndValue const):
+
2020-09-07 Commit Queue <[email protected]>
Unreviewed, reverting r266645.
Modified: trunk/Source/WebCore/Modules/webaudio/AudioParam.cpp (266711 => 266712)
--- trunk/Source/WebCore/Modules/webaudio/AudioParam.cpp 2020-09-07 22:35:43 UTC (rev 266711)
+++ trunk/Source/WebCore/Modules/webaudio/AudioParam.cpp 2020-09-08 01:35:14 UTC (rev 266712)
@@ -215,6 +215,18 @@
return *this;
}
+ExceptionOr<AudioParam&> AudioParam::cancelAndHoldAtTime(double cancelTime)
+{
+ if (cancelTime < 0)
+ return Exception { RangeError, "cancelTime must be a positive value"_s };
+
+ auto result = m_timeline.cancelAndHoldAtTime(Seconds { cancelTime });
+ if (result.hasException())
+ return result.releaseException();
+
+ return *this;
+}
+
float AudioParam::finalValue()
{
float value;
Modified: trunk/Source/WebCore/Modules/webaudio/AudioParam.h (266711 => 266712)
--- trunk/Source/WebCore/Modules/webaudio/AudioParam.h 2020-09-07 22:35:43 UTC (rev 266711)
+++ trunk/Source/WebCore/Modules/webaudio/AudioParam.h 2020-09-08 01:35:14 UTC (rev 266712)
@@ -108,6 +108,7 @@
ExceptionOr<AudioParam&> setTargetAtTime(float target, double startTime, float timeConstant);
ExceptionOr<AudioParam&> setValueCurveAtTime(Vector<float>&& curve, double startTime, double duration);
ExceptionOr<AudioParam&> cancelScheduledValues(double cancelTime);
+ ExceptionOr<AudioParam&> cancelAndHoldAtTime(double cancelTime);
bool hasSampleAccurateValues() const { return m_timeline.hasValues() || numberOfRenderingConnections(); }
Modified: trunk/Source/WebCore/Modules/webaudio/AudioParam.idl (266711 => 266712)
--- trunk/Source/WebCore/Modules/webaudio/AudioParam.idl 2020-09-07 22:35:43 UTC (rev 266711)
+++ trunk/Source/WebCore/Modules/webaudio/AudioParam.idl 2020-09-08 01:35:14 UTC (rev 266712)
@@ -59,10 +59,8 @@
// Cancels all scheduled parameter changes with times greater than or equal to startTime.
[MayThrowException] AudioParam cancelScheduledValues(double cancelTime);
+ [MayThrowException] AudioParam cancelAndHoldAtTime(double cancelTime);
- // FIXME: Add support for this.
- // AudioParam cancelAndHoldAtTime(double cancelTime);
-
// FIXME: These are not standard.
[EnabledBySetting=PrefixedWebAudio] readonly attribute DOMString name;
[EnabledBySetting=PrefixedWebAudio] readonly attribute unsigned short units;
Modified: trunk/Source/WebCore/Modules/webaudio/AudioParamTimeline.cpp (266711 => 266712)
--- trunk/Source/WebCore/Modules/webaudio/AudioParamTimeline.cpp 2020-09-07 22:35:43 UTC (rev 266711)
+++ trunk/Source/WebCore/Modules/webaudio/AudioParamTimeline.cpp 2020-09-08 01:35:14 UTC (rev 266712)
@@ -65,27 +65,32 @@
ExceptionOr<void> AudioParamTimeline::setValueAtTime(float value, Seconds time)
{
- return insertEvent(ParamEvent(ParamEvent::SetValue, value, time, 0, { }, { }));
+ auto locker = holdLock(m_eventsMutex);
+ return insertEvent(ParamEvent::createSetValueEvent(value, time));
}
ExceptionOr<void> AudioParamTimeline::linearRampToValueAtTime(float value, Seconds time)
{
- return insertEvent(ParamEvent(ParamEvent::LinearRampToValue, value, time, 0, { }, { }));
+ auto locker = holdLock(m_eventsMutex);
+ return insertEvent(ParamEvent::createLinearRampEvent(value, time));
}
ExceptionOr<void> AudioParamTimeline::exponentialRampToValueAtTime(float value, Seconds time)
{
- return insertEvent(ParamEvent(ParamEvent::ExponentialRampToValue, value, time, 0, { }, { }));
+ auto locker = holdLock(m_eventsMutex);
+ return insertEvent(ParamEvent::createExponentialRampEvent(value, time));
}
ExceptionOr<void> AudioParamTimeline::setTargetAtTime(float target, Seconds time, float timeConstant)
{
- return insertEvent(ParamEvent(ParamEvent::SetTarget, target, time, timeConstant, { }, { }));
+ auto locker = holdLock(m_eventsMutex);
+ return insertEvent(ParamEvent::createSetTargetEvent(target, time, timeConstant));
}
ExceptionOr<void> AudioParamTimeline::setValueCurveAtTime(Vector<float>&& curve, Seconds time, Seconds duration)
{
- return insertEvent(ParamEvent(ParamEvent::SetValueCurve, 0, time, 0, duration, WTFMove(curve)));
+ auto locker = holdLock(m_eventsMutex);
+ return insertEvent(ParamEvent::createSetValueCurveEvent(WTFMove(curve), time, duration));
}
static bool isValidNumber(float x)
@@ -98,52 +103,55 @@
return !std::isnan(s.value()) && !std::isinf(s.value());
}
-ExceptionOr<void> AudioParamTimeline::insertEvent(const ParamEvent& event)
+ExceptionOr<void> AudioParamTimeline::insertEvent(UniqueRef<ParamEvent> event)
{
// Sanity check the event. Be super careful we're not getting infected with NaN or Inf.
- bool isValid = event.type() < ParamEvent::LastType
- && isValidNumber(event.value())
- && isValidNumber(event.time())
- && isValidNumber(event.timeConstant())
- && isValidNumber(event.duration())
- && event.duration() >= 0_s;
+ bool isValid = event->type() < ParamEvent::LastType
+ && isValidNumber(event->value())
+ && isValidNumber(event->time())
+ && isValidNumber(event->timeConstant())
+ && isValidNumber(event->duration())
+ && event->duration() >= 0_s;
if (!isValid)
return { };
- auto locker = holdLock(m_eventsMutex);
+ ASSERT(m_eventsMutex.isLocked());
unsigned i = 0;
- auto insertTime = event.time();
+ auto insertTime = event->time();
+
for (auto& paramEvent : m_events) {
- if (event.type() == ParamEvent::SetValueCurve) {
- // If this event is a SetValueCurve, make sure it doesn't overlap any existing event.
- // It's ok if the SetValueCurve starts at the same time as the end of some other duration.
- auto endTime = event.time() + event.duration();
- if (paramEvent.type() == ParamEvent::SetValueCurve) {
- auto paramEventEndTime = paramEvent.time() + paramEvent.duration();
- if ((paramEvent.time() >= event.time() && paramEvent.time() < endTime)
- || (paramEventEndTime > event.time() && paramEventEndTime < endTime)
- || (event.time() >= paramEvent.time() && event.time() < paramEventEndTime)
- || (endTime >= paramEvent.time() && endTime < paramEventEndTime)) {
+ if (event->type() == ParamEvent::SetValueCurve) {
+ if (paramEvent->type() != ParamEvent::CancelValues) {
+ // If this event is a SetValueCurve, make sure it doesn't overlap any existing event.
+ // It's ok if the SetValueCurve starts at the same time as the end of some other duration.
+ auto endTime = event->time() + event->duration();
+ if (paramEvent->type() == ParamEvent::SetValueCurve) {
+ auto paramEventEndTime = paramEvent->time() + paramEvent->duration();
+ if ((paramEvent->time() >= event->time() && paramEvent->time() < endTime)
+ || (paramEventEndTime > event->time() && paramEventEndTime < endTime)
+ || (event->time() >= paramEvent->time() && event->time() < paramEventEndTime)
+ || (endTime >= paramEvent->time() && endTime < paramEventEndTime)) {
+ return Exception { NotSupportedError, "Events are overlapping"_s };
+ }
+ } else if (paramEvent->time() > event->time() && paramEvent->time() < endTime)
return Exception { NotSupportedError, "Events are overlapping"_s };
- }
- } else if (paramEvent.time() > event.time() && paramEvent.time() < endTime)
- return Exception { NotSupportedError, "Events are overlapping"_s };
- } else if (paramEvent.type() == ParamEvent::SetValueCurve) {
+ }
+ } else if (paramEvent->type() == ParamEvent::SetValueCurve) {
// Otherwise, make sure this event doesn't overlap any existing SetValueCurve event.
- auto parentEventEndTime = paramEvent.time() + paramEvent.duration();
- if (event.time() >= paramEvent.time() && event.time() < parentEventEndTime)
+ auto parentEventEndTime = paramEvent->time() + paramEvent->duration();
+ if (event->time() >= paramEvent->time() && event->time() < parentEventEndTime)
return Exception { NotSupportedError, "Events are overlapping" };
}
- if (paramEvent.time() > insertTime)
+ if (paramEvent->time() > insertTime)
break;
++i;
}
- m_events.insert(i, event);
+ m_events.insert(i, WTFMove(event));
return { };
}
@@ -176,11 +184,122 @@
}
}
+ExceptionOr<void> AudioParamTimeline::cancelAndHoldAtTime(Seconds cancelTime)
+{
+ auto locker = holdLock(m_eventsMutex);
+
+ // Find the first event at or just past cancelTime.
+ size_t i = m_events.findMatching([&](auto& event) {
+ return event->time() > cancelTime;
+ });
+ i = (i == notFound) ? m_events.size() : i;
+
+ // The event that is being cancelled. This is the event just past cancelTime, if any.
+ size_t cancelledEventIndex = i;
+
+ // If the event just before cancelTime is a SetTarget or SetValueCurve event, we need
+ // to handle that event specially instead of the event after.
+ if (i > 0 && ((m_events[i - 1]->type() == ParamEvent::SetTarget) || (m_events[i - 1]->type() == ParamEvent::SetValueCurve)))
+ cancelledEventIndex = i - 1;
+ else if (i >= m_events.size()) {
+ // If there were no events occurring after |cancelTime| (and the
+ // previous event is not SetTarget or SetValueCurve, we're done.
+ return { };
+ }
+
+ // cancelledEvent is the event that is being cancelled.
+ auto& cancelledEvent = m_events[cancelledEventIndex].get();
+ auto eventType = cancelledEvent.type();
+
+ // New event to be inserted, if any, and a SetValueEvent if needed.
+ std::unique_ptr<ParamEvent> newEvent;
+ std::unique_ptr<ParamEvent> newSetValueEvent;
+
+ switch (eventType) {
+ case ParamEvent::LinearRampToValue:
+ case ParamEvent::ExponentialRampToValue: {
+ // For these events we need to remember the parameters of the event
+ // for a CancelValues event so that we can properly cancel the event
+ // and hold the value.
+ auto savedEvent = makeUniqueRef<ParamEvent>(eventType, cancelledEvent.value(), cancelledEvent.time(), cancelledEvent.timeConstant(), cancelledEvent.duration(), Vector<float> { cancelledEvent.curve() }, cancelledEvent.curvePointsPerSecond(), cancelledEvent.curveEndValue(), nullptr);
+ newEvent = ParamEvent::createCancelValuesEvent(cancelTime, savedEvent.moveToUniquePtr()).moveToUniquePtr();
+ break;
+ }
+ case ParamEvent::SetTarget: {
+ if (cancelledEvent.time() < cancelTime) {
+ // Don't want to remove the SetTarget event if it started before the
+ // cancel time, so bump the index. But we do want to insert a
+ // cancelEvent so that we stop this automation and hold the value when
+ // we get there.
+ ++cancelledEventIndex;
+
+ newEvent = ParamEvent::createCancelValuesEvent(cancelTime, nullptr).moveToUniquePtr();
+ }
+ break;
+ }
+ case ParamEvent::SetValueCurve: {
+ // If the setValueCurve event started strictly before the cancel time,
+ // there might be something to do....
+ if (cancelledEvent.time() < cancelTime) {
+ if (cancelTime > cancelledEvent.time() + cancelledEvent.duration()) {
+ // If the cancellation time is past the end of the curve there's
+ // nothing to do except remove the following events.
+ ++cancelledEventIndex;
+ } else {
+ // Cancellation time is in the middle of the curve. Therefore,
+ // create a new SetValueCurve event with the appropriate new
+ // parameters to cancel this event properly. Since it's illegal
+ // to insert any event within a SetValueCurve event, we can
+ // compute the new end value now instead of doing when running
+ // the timeline.
+ auto newDuration = cancelTime - cancelledEvent.time();
+ float endValue = valueCurveAtTime(cancelTime, cancelledEvent.time(), cancelledEvent.duration(), cancelledEvent.curve().data(), cancelledEvent.curve().size());
+
+ // Replace the existing SetValueCurve with this new one that is identical except for the duration.
+ newEvent = makeUniqueRef<ParamEvent>(eventType, cancelledEvent.value(), cancelledEvent.time(), cancelledEvent.timeConstant(), newDuration, Vector<float> { cancelledEvent.curve() }, cancelledEvent.curvePointsPerSecond(), endValue, nullptr).moveToUniquePtr();
+ newSetValueEvent = ParamEvent::createSetValueEvent(endValue, cancelledEvent.time() + newDuration).moveToUniquePtr();
+ }
+ }
+ break;
+ }
+ case ParamEvent::SetValue:
+ case ParamEvent::CancelValues:
+ // Nothing needs to be done for a SetValue or CancelValues event.
+ break;
+ case ParamEvent::LastType:
+ ASSERT_NOT_REACHED();
+ break;
+ }
+
+ // Now remove all the following events from the timeline.
+ if (cancelledEventIndex < m_events.size())
+ removeCancelledEvents(cancelledEventIndex);
+
+ // Insert the new event, if any.
+ if (newEvent) {
+ auto result = insertEvent(makeUniqueRefFromNonNullUniquePtr(WTFMove(newEvent)));
+ if (result.hasException())
+ return result.releaseException();
+ if (newSetValueEvent) {
+ insertEvent(makeUniqueRefFromNonNullUniquePtr(WTFMove(newSetValueEvent)));
+ if (result.hasException())
+ return result.releaseException();
+ }
+ }
+
+ return { };
+}
+
+void AudioParamTimeline::removeCancelledEvents(size_t firstEventToRemove)
+{
+ m_events.remove(firstEventToRemove, m_events.size() - firstEventToRemove);
+}
+
Optional<float> AudioParamTimeline::valueForContextTime(BaseAudioContext& context, float defaultValue, float minValue, float maxValue)
{
{
std::unique_lock<Lock> lock(m_eventsMutex, std::try_to_lock);
- if (!lock.owns_lock() || !m_events.size() || Seconds { context.currentTime() } < m_events[0].time())
+ if (!lock.owns_lock() || !m_events.size() || Seconds { context.currentTime() } < m_events[0]->time())
return WTF::nullopt;
}
@@ -221,7 +340,7 @@
return defaultValue;
// Return default value if there are no events matching the desired time range.
- if (!m_events.size() || endTime <= m_events[0].time()) {
+ if (!m_events.size() || endTime <= m_events[0]->time()) {
for (unsigned i = 0; i < numberOfValues; ++i)
values[i] = defaultValue;
return defaultValue;
@@ -233,7 +352,7 @@
// If first event is after startTime then fill initial part of values buffer with defaultValue
// until we reach the first event time.
- auto firstEventTime = m_events[0].time();
+ auto firstEventTime = m_events[0]->time();
if (firstEventTime > startTime) {
auto fillToTime = std::min(endTime, firstEventTime);
unsigned fillToFrame = AudioUtilities::timeToSampleFrame((fillToTime - startTime).value(), sampleRate);
@@ -252,18 +371,22 @@
// and keeping track of a "current" event index.
int n = m_events.size();
for (int i = 0; i < n && writeIndex < numberOfValues; ++i) {
- ParamEvent& event = m_events[i];
- ParamEvent* nextEvent = i < n - 1 ? &(m_events[i + 1]) : nullptr;
+ auto& event = m_events[i].get();
+ auto* nextEvent = i < n - 1 ? &m_events[i + 1].get() : nullptr;
// Wait until we get a more recent event.
if (nextEvent && nextEvent->time() < currentTime)
continue;
+ auto nextEventType = nextEvent ? static_cast<ParamEvent::Type>(nextEvent->type()) : ParamEvent::LastType /* unknown */;
+
float value1 = event.value();
auto time1 = event.time();
float value2 = nextEvent ? nextEvent->value() : value1;
auto time2 = nextEvent ? nextEvent->time() : endTime + 1_s;
+ handleCancelValues(event, nextEvent, value2, time2, nextEventType);
+
auto deltaTime = time2 - time1;
auto sampleFrameTimeIncr = Seconds { 1 / sampleRate };
@@ -271,8 +394,6 @@
unsigned fillToFrame = AudioUtilities::timeToSampleFrame((fillToTime - startTime).value(), sampleRate);
fillToFrame = std::min(fillToFrame, numberOfValues);
- ParamEvent::Type nextEventType = nextEvent ? static_cast<ParamEvent::Type>(nextEvent->type()) : ParamEvent::LastType /* unknown */;
-
// First handle linear and exponential ramps which require looking ahead to the next event.
if (nextEventType == ParamEvent::LinearRampToValue) {
for (; writeIndex < fillToFrame; ++writeIndex) {
@@ -314,106 +435,152 @@
case ParamEvent::SetValue:
case ParamEvent::LinearRampToValue:
case ParamEvent::ExponentialRampToValue:
- {
- currentTime = fillToTime;
+ currentTime = fillToTime;
- // Simply stay at a constant value.
+ // Simply stay at a constant value.
+ value = event.value();
+ for (; writeIndex < fillToFrame; ++writeIndex)
+ values[writeIndex] = value;
+
+ break;
+ case ParamEvent::CancelValues:
+ currentTime = fillToTime;
+
+ // If the previous event was a SetTarget or ExponentialRamp
+ // event, the current value is one sample behind. Update
+ // the sample value by one sample, but only at the start of
+ // this CancelValues event.
+ if (event.hasDefaultCancelledValue())
value = event.value();
+ else {
+ auto cancelTime = time1;
+ if (i >= 1 && cancelTime <= currentTime && currentTime < cancelTime + sampleFrameTimeIncr) {
+ auto lastEventType = m_events[i - 1]->type();
+ if (lastEventType == ParamEvent::SetTarget) {
+ float target = m_events[i - 1]->value();
+ float timeConstant = m_events[i - 1]->timeConstant();
+ float discreteTimeConstant = static_cast<float>(AudioUtilities::discreteTimeConstantForSampleRate(timeConstant, controlRate));
+ value += (target - value) * discreteTimeConstant;
+ }
+ }
+ }
+
+ for (; writeIndex < fillToFrame; ++writeIndex)
+ values[writeIndex] = value;
+
+ break;
+ case ParamEvent::SetTarget: {
+ currentTime = fillToTime;
+
+ // Exponential approach to target value with given time constant.
+ float target = event.value();
+ float timeConstant = event.timeConstant();
+ float discreteTimeConstant = static_cast<float>(AudioUtilities::discreteTimeConstantForSampleRate(timeConstant, controlRate));
+
+ // If the value is close enough to the target, just fill in the data
+ // with the target value.
+ if (hasSetTargetConverged(value, target, currentTime, time1, timeConstant)) {
for (; writeIndex < fillToFrame; ++writeIndex)
+ values[writeIndex] = target;
+ value = target;
+ } else {
+ // Serially process remaining values.
+ for (; writeIndex < fillToFrame; ++writeIndex) {
values[writeIndex] = value;
-
- break;
+ value += (target - value) * discreteTimeConstant;
+ }
}
- case ParamEvent::SetTarget:
- {
- currentTime = fillToTime;
+ break;
+ }
+ case ParamEvent::SetValueCurve: {
+ float* curveData = event.curve().data();
+ unsigned numberOfCurvePoints = event.curve().size();
+ float curveEndValue = event.curveEndValue();
- // Exponential approach to target value with given time constant.
- float target = event.value();
- float timeConstant = event.timeConstant();
- float discreteTimeConstant = static_cast<float>(AudioUtilities::discreteTimeConstantForSampleRate(timeConstant, controlRate));
+ // Curve events have duration, so don't just use next event time.
+ auto duration = event.duration();
+ double curvePointsPerFrame = event.curvePointsPerSecond() / sampleRate;
- // If the value is close enough to the target, just fill in the data
- // with the target value.
- if (hasSetTargetConverged(value, target, currentTime, time1, timeConstant)) {
- for (; writeIndex < fillToFrame; ++writeIndex)
- values[writeIndex] = target;
- value = target;
- } else {
- // Serially process remaining values.
- for (; writeIndex < fillToFrame; ++writeIndex) {
- values[writeIndex] = value;
- value += (target - value) * discreteTimeConstant;
- }
- }
-
+ if (!curveData || !numberOfCurvePoints || duration <= 0_s || sampleRate <= 0) {
+ // Error condition - simply propagate previous value.
+ currentTime = fillToTime;
+ for (; writeIndex < fillToFrame; ++writeIndex)
+ values[writeIndex] = value;
break;
}
- case ParamEvent::SetValueCurve:
- {
- float* curveData = event.curve().data();
- unsigned numberOfCurvePoints = event.curve().size();
+ // Save old values and recalculate information based on the curve's duration
+ // instead of the next event time.
+ unsigned nextEventFillToFrame = fillToFrame;
+ auto nextEventFillToTime = fillToTime;
+ fillToTime = std::min(endTime, time1 + duration);
+ fillToFrame = AudioUtilities::timeToSampleFrame((fillToTime - startTime).value(), sampleRate);
+ fillToFrame = std::min(fillToFrame, numberOfValues);
- // Curve events have duration, so don't just use next event time.
- auto duration = event.duration();
- auto durationFrames = duration.value() * sampleRate;
- float curvePointsPerFrame = static_cast<float>(numberOfCurvePoints) / durationFrames;
+ // Index into the curve data using a floating-point value.
+ // We're scaling the number of curve points by the duration (see curvePointsPerFrame).
+ double curveVirtualIndex = 0;
+ if (time1 < currentTime) {
+ // Index somewhere in the middle of the curve data.
+ // Don't use timeToSampleFrame() since we want the exact floating-point frame.
+ double frameOffset = (currentTime - time1).value() * sampleRate;
+ curveVirtualIndex = curvePointsPerFrame * frameOffset;
+ }
- if (!curveData || !numberOfCurvePoints || duration <= 0_s || sampleRate <= 0) {
- // Error condition - simply propagate previous value.
- currentTime = fillToTime;
- for (; writeIndex < fillToFrame; ++writeIndex)
- values[writeIndex] = value;
- break;
- }
+ // Set the default value in case fillToFrame is 0.
+ value = curveEndValue;
- // Save old values and recalculate information based on the curve's duration
- // instead of the next event time.
- unsigned nextEventFillToFrame = fillToFrame;
- auto nextEventFillToTime = fillToTime;
- fillToTime = std::min(endTime, time1 + duration);
- fillToFrame = AudioUtilities::timeToSampleFrame((fillToTime - startTime).value(), sampleRate);
- fillToFrame = std::min(fillToFrame, numberOfValues);
+ // Render the stretched curve data using nearest neighbor sampling.
+ // Oversampled curve data can be provided if smoothness is desired.
+ int k = 0;
+ for (; writeIndex < fillToFrame; ++writeIndex, ++k) {
+ // Compute current index this way to minimize round-off that would
+ // have occurred by incrementing the index by curvePointsPerFrame.
+ double currentVirtualIndex = curveVirtualIndex + k * curvePointsPerFrame;
+ unsigned curveIndex0;
- // Index into the curve data using a floating-point value.
- // We're scaling the number of curve points by the duration (see curvePointsPerFrame).
- float curveVirtualIndex = 0;
- if (time1 < currentTime) {
- // Index somewhere in the middle of the curve data.
- // Don't use timeToSampleFrame() since we want the exact floating-point frame.
- float frameOffset = (currentTime - time1).value() * sampleRate;
- curveVirtualIndex = curvePointsPerFrame * frameOffset;
- }
+ // Clamp index to the last element of the array.
+ if (currentVirtualIndex < numberOfCurvePoints)
+ curveIndex0 = static_cast<unsigned>(currentVirtualIndex);
+ else
+ curveIndex0 = numberOfCurvePoints - 1;
- // Render the stretched curve data using nearest neighbor sampling.
- // Oversampled curve data can be provided if smoothness is desired.
- for (; writeIndex < fillToFrame; ++writeIndex) {
- // Ideally we'd use round() from MathExtras, but we're in a tight loop here
- // and we're trading off precision for extra speed.
- unsigned curveIndex = static_cast<unsigned>(0.5 + curveVirtualIndex);
+ unsigned curveIndex1 = std::min(curveIndex0 + 1, numberOfCurvePoints - 1);
- curveVirtualIndex += curvePointsPerFrame;
+ // Linearly interpolate between the two nearest curve points.
+ // |delta| is clamped to 1 because currentVirtualIndex can exceed
+ // curveIndex0 by more than one. This can happen when we reached
+ // the end of the curve but still need values to fill out the
+ // current rendering quantum.
+ ASSERT(curveIndex0 < numberOfCurvePoints);
+ ASSERT(curveIndex1 < numberOfCurvePoints);
+ float c0 = curveData[curveIndex0];
+ float c1 = curveData[curveIndex1];
+ double delta = std::min(currentVirtualIndex - curveIndex0, 1.0);
- // Bounds check.
- if (curveIndex < numberOfCurvePoints)
- value = curveData[curveIndex];
+ value = c0 + (c1 - c0) * delta;
- values[writeIndex] = value;
- }
+ values[writeIndex] = value;
+ }
- // If there's any time left after the duration of this event and the start
- // of the next, then just propagate the last value.
+ // If there's any time left after the duration of this event and the start
+ // of the next, then just propagate the last value.
+ if (writeIndex < nextEventFillToFrame) {
+ value = curveEndValue;
for (; writeIndex < nextEventFillToFrame; ++writeIndex)
values[writeIndex] = value;
+ }
- // Re-adjust current time
- currentTime = nextEventFillToTime;
+ // Re-adjust current time
+ currentTime = nextEventFillToTime;
- break;
- }
+ break;
}
+ case ParamEvent::LastType:
+ ASSERT_NOT_REACHED();
+ break;
+ }
}
}
@@ -425,6 +592,145 @@
return value;
}
+float AudioParamTimeline::linearRampAtTime(Seconds t, float value1, Seconds time1, float value2, Seconds time2)
+{
+ return value1 + (value2 - value1) * (t - time1).value() / (time2 - time1).value();
+}
+
+float AudioParamTimeline::exponentialRampAtTime(Seconds t, float value1, Seconds time1, float value2, Seconds time2)
+{
+ return value1 * pow(value2 / value1, (t - time1).value() / (time2 - time1).value());
+}
+
+float AudioParamTimeline::valueCurveAtTime(Seconds t, Seconds time1, Seconds duration, const float* curveData, size_t curveLength)
+{
+ double curveIndex = (curveLength - 1) / duration.value() * (t - time1).value();
+ size_t k = std::min(static_cast<size_t>(curveIndex), curveLength - 1);
+ size_t k1 = std::min(k + 1, curveLength - 1);
+ float c0 = curveData[k];
+ float c1 = curveData[k1];
+ float delta = std::min(curveIndex - k, 1.0);
+
+ return c0 + (c1 - c0) * delta;
+}
+
+void AudioParamTimeline::handleCancelValues(ParamEvent& event, ParamEvent* nextEvent, float& value2, Seconds& time2, ParamEvent::Type& nextEventType)
+{
+ if (!nextEvent || nextEvent->type() != ParamEvent::CancelValues || !nextEvent->savedEvent())
+ return;
+
+ float value1 = event.value();
+ auto time1 = event.time();
+
+ switch (event.type()) {
+ case ParamEvent::CancelValues:
+ case ParamEvent::LinearRampToValue:
+ case ParamEvent::ExponentialRampToValue:
+ case ParamEvent::SetValue: {
+ // These three events potentially establish a starting value for
+ // the following event, so we need to examine the cancelled
+ // event to see what to do.
+ auto* savedEvent = nextEvent->savedEvent();
+
+ // Update the end time and type to pretend that we're running
+ // this saved event type.
+ time2 = nextEvent->time();
+ nextEventType = savedEvent->type();
+
+ if (nextEvent->hasDefaultCancelledValue()) {
+ // We've already established a value for the cancelled
+ // event, so just return it.
+ value2 = nextEvent->value();
+ } else {
+ // If the next event would have been a LinearRamp or
+ // ExponentialRamp, we need to compute a new end value for
+ // the event so that the curve works continues as if it were
+ // not cancelled.
+ switch (savedEvent->type()) {
+ case ParamEvent::LinearRampToValue:
+ value2 = linearRampAtTime(nextEvent->time(), value1, time1, savedEvent->value(), savedEvent->time());
+ break;
+ case ParamEvent::ExponentialRampToValue:
+ value2 = exponentialRampAtTime(nextEvent->time(), value1, time1, savedEvent->value(), savedEvent->time());
+ break;
+ case ParamEvent::SetValueCurve:
+ case ParamEvent::SetValue:
+ case ParamEvent::SetTarget:
+ case ParamEvent::CancelValues:
+ // These cannot be possible types for the saved event because they can't be created.
+ // createCancelValuesEvent doesn't allow them (SetValue, SetTarget, CancelValues) or
+ // cancelScheduledValues() doesn't create such an event (SetValueCurve).
+ ASSERT_NOT_REACHED();
+ break;
+ case ParamEvent::LastType:
+ ASSERT_NOT_REACHED();
+ break;
+ }
+
+ // Cache the new value so we don't keep computing it over and over.
+ nextEvent->setCancelledValue(value2);
+ }
+ } break;
+ case ParamEvent::SetValueCurve:
+ // Everything needed for this was handled when cancelling was
+ // done.
+ break;
+ case ParamEvent::SetTarget:
+ // Nothing special needs to be done for SetTarget
+ // followed by CancelValues.
+ break;
+ case ParamEvent::LastType:
+ ASSERT_NOT_REACHED();
+ break;
+ }
+}
+
+auto AudioParamTimeline::ParamEvent::createSetValueEvent(float value, Seconds time) -> UniqueRef<ParamEvent>
+{
+ return makeUniqueRef<ParamEvent>(ParamEvent::SetValue, value, time, 0, Seconds { }, Vector<float> { }, 0, 0, nullptr);
+}
+
+auto AudioParamTimeline::ParamEvent::createLinearRampEvent(float value, Seconds time) -> UniqueRef<ParamEvent>
+{
+ return makeUniqueRef<ParamEvent>(ParamEvent::LinearRampToValue, value, time, 0, Seconds { }, Vector<float> { }, 0, 0, nullptr);
+}
+
+auto AudioParamTimeline::ParamEvent::createExponentialRampEvent(float value, Seconds time) -> UniqueRef<ParamEvent>
+{
+ return makeUniqueRef<ParamEvent>(ParamEvent::ExponentialRampToValue, value, time, 0, Seconds { }, Vector<float> { }, 0, 0, nullptr);
+}
+
+auto AudioParamTimeline::ParamEvent::createSetTargetEvent(float target, Seconds time, float timeConstant) -> UniqueRef<ParamEvent>
+{
+ // The time line code does not expect a timeConstant of 0. (It returns NaN or Infinity due to division by zero. The caller
+ // should have converted this to a SetValueEvent.
+ ASSERT(!!timeConstant);
+ return makeUniqueRef<ParamEvent>(ParamEvent::SetTarget, target, time, timeConstant, Seconds { }, Vector<float> { }, 0, 0, nullptr);
+}
+
+auto AudioParamTimeline::ParamEvent::createSetValueCurveEvent(Vector<float>&& curve, Seconds time, Seconds duration) -> UniqueRef<ParamEvent>
+{
+ double curvePointsPerSecond = (curve.size() - 1) / duration.value();
+ float curveEndValue = curve.last();
+ return makeUniqueRef<ParamEvent>(ParamEvent::SetValueCurve, 0, time, 0, duration, WTFMove(curve), curvePointsPerSecond, curveEndValue, nullptr);
+}
+
+auto AudioParamTimeline::ParamEvent::createCancelValuesEvent(Seconds cancelTime, std::unique_ptr<ParamEvent> savedEvent) -> UniqueRef<ParamEvent>
+{
+#if ASSERT_ENABLED
+ if (savedEvent) {
+ // The savedEvent can only have certain event types. Verify that.
+ auto savedEventType = savedEvent->type();
+
+ ASSERT(savedEventType != ParamEvent::LastType);
+ ASSERT(savedEventType == ParamEvent::LinearRampToValue
+ || savedEventType == ParamEvent::ExponentialRampToValue
+ || savedEventType == ParamEvent::SetValueCurve);
+ }
+#endif
+ return makeUniqueRef<ParamEvent>(ParamEvent::CancelValues, 0, cancelTime, 0, Seconds { }, Vector<float> { }, 0, 0, WTFMove(savedEvent));
+}
+
} // namespace WebCore
#endif // ENABLE(WEB_AUDIO)
Modified: trunk/Source/WebCore/Modules/webaudio/AudioParamTimeline.h (266711 => 266712)
--- trunk/Source/WebCore/Modules/webaudio/AudioParamTimeline.h 2020-09-07 22:35:43 UTC (rev 266711)
+++ trunk/Source/WebCore/Modules/webaudio/AudioParamTimeline.h 2020-09-08 01:35:14 UTC (rev 266712)
@@ -48,7 +48,8 @@
ExceptionOr<void> exponentialRampToValueAtTime(float value, Seconds time);
ExceptionOr<void> setTargetAtTime(float target, Seconds time, float timeConstant);
ExceptionOr<void> setValueCurveAtTime(Vector<float>&& curve, Seconds time, Seconds duration);
- void cancelScheduledValues(Seconds startTime);
+ void cancelScheduledValues(Seconds cancelTime);
+ ExceptionOr<void> cancelAndHoldAtTime(Seconds cancelTime);
// hasValue is set to true if a valid timeline value is returned.
// otherwise defaultValue is returned.
@@ -65,6 +66,7 @@
private:
class ParamEvent {
+ WTF_MAKE_FAST_ALLOCATED;
public:
enum Type {
SetValue,
@@ -72,10 +74,18 @@
ExponentialRampToValue,
SetTarget,
SetValueCurve,
+ CancelValues,
LastType
};
- ParamEvent(Type type, float value, Seconds time, float timeConstant, Seconds duration, Vector<float>&& curve)
+ static UniqueRef<ParamEvent> createSetValueEvent(float value, Seconds time);
+ static UniqueRef<ParamEvent> createLinearRampEvent(float value, Seconds time);
+ static UniqueRef<ParamEvent> createExponentialRampEvent(float value, Seconds time);
+ static UniqueRef<ParamEvent> createSetTargetEvent(float target, Seconds time, float timeConstant);
+ static UniqueRef<ParamEvent> createSetValueCurveEvent(Vector<float>&& curve, Seconds time, Seconds duration);
+ static UniqueRef<ParamEvent> createCancelValuesEvent(Seconds cancelTime, std::unique_ptr<ParamEvent> savedEvent);
+
+ ParamEvent(Type type, float value, Seconds time, float timeConstant, Seconds duration, Vector<float>&& curve, double curvePointsPerSecond, float curveEndValue, std::unique_ptr<ParamEvent> savedEvent)
: m_type(type)
, m_value(value)
, m_time(time)
@@ -82,29 +92,77 @@
, m_timeConstant(timeConstant)
, m_duration(duration)
, m_curve(WTFMove(curve))
+ , m_curvePointsPerSecond(curvePointsPerSecond)
+ , m_curveEndValue(curveEndValue)
+ , m_savedEvent(WTFMove(savedEvent))
{
}
- unsigned type() const { return m_type; }
+ Type type() const { return m_type; }
float value() const { return m_value; }
Seconds time() const { return m_time; }
float timeConstant() const { return m_timeConstant; }
Seconds duration() const { return m_duration; }
Vector<float>& curve() { return m_curve; }
+ ParamEvent* savedEvent() { return m_savedEvent.get(); }
+ void setCancelledValue(float cancelledValue)
+ {
+ ASSERT(m_type == Type::CancelValues);
+ m_value = cancelledValue;
+ m_hasDefaultCancelledValue = true;
+ }
+ bool hasDefaultCancelledValue() const
+ {
+ ASSERT(m_type == Type::CancelValues);
+ return m_hasDefaultCancelledValue;
+ }
+
+ double curvePointsPerSecond() const { return m_curvePointsPerSecond; }
+ float curveEndValue() const { return m_curveEndValue; }
+
private:
- unsigned m_type;
- float m_value;
+ Type m_type;
+ float m_value { 0 };
Seconds m_time;
- float m_timeConstant;
+
+ // Only used for SetTarget events.
+ float m_timeConstant { 0 };
+
+ // The duration of the curve.
Seconds m_duration;
+
+ // The array of curve points.
Vector<float> m_curve;
+
+ // The number of curve points per second. it is used to compute
+ // the curve index step when running the automation.
+ double m_curvePointsPerSecond { 0 };
+
+ // The default value to use at the end of the curve. Normally
+ // it's the last entry in m_curve, but cancelling a SetValueCurve
+ // will set this to a new value.
+ float m_curveEndValue { 0 };
+
+ // True if a default value has been assigned to the CancelValues event.
+ bool m_hasDefaultCancelledValue { false };
+
+ // For CancelValues. If CancelValues is in the middle of an event, this
+ // holds the event that is being cancelled, so that processing can
+ // continue as if the event still existed up until we reach the actual
+ // scheduled cancel time.
+ std::unique_ptr<ParamEvent> m_savedEvent;
};
- ExceptionOr<void> insertEvent(const ParamEvent&);
+ void removeCancelledEvents(size_t firstEventToRemove);
+ ExceptionOr<void> insertEvent(UniqueRef<ParamEvent>);
float valuesForTimeRangeImpl(Seconds startTime, Seconds endTime, float defaultValue, float* values, unsigned numberOfValues, double sampleRate, double controlRate);
+ float linearRampAtTime(Seconds t, float value1, Seconds time1, float value2, Seconds time2);
+ float exponentialRampAtTime(Seconds t, float value1, Seconds time1, float value2, Seconds time2);
+ float valueCurveAtTime(Seconds t, Seconds time1, Seconds duration, const float* curveData, size_t curveLength);
+ void handleCancelValues(ParamEvent&, ParamEvent* nextEvent, float& value2, Seconds& time2, ParamEvent::Type& nextEventType);
- Vector<ParamEvent> m_events;
+ Vector<UniqueRef<ParamEvent>> m_events;
Lock m_eventsMutex;
};