summaryrefslogtreecommitdiffstats
path: root/dom/media/webaudio
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/media/webaudio
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--dom/media/webaudio/AlignedTArray.h115
-rw-r--r--dom/media/webaudio/AlignmentUtils.h29
-rw-r--r--dom/media/webaudio/AnalyserNode.cpp388
-rw-r--r--dom/media/webaudio/AnalyserNode.h82
-rw-r--r--dom/media/webaudio/AudioBlock.cpp166
-rw-r--r--dom/media/webaudio/AudioBlock.h134
-rw-r--r--dom/media/webaudio/AudioBuffer.cpp498
-rw-r--r--dom/media/webaudio/AudioBuffer.h138
-rw-r--r--dom/media/webaudio/AudioBufferSourceNode.cpp853
-rw-r--r--dom/media/webaudio/AudioBufferSourceNode.h129
-rw-r--r--dom/media/webaudio/AudioContext.cpp1405
-rw-r--r--dom/media/webaudio/AudioContext.h479
-rw-r--r--dom/media/webaudio/AudioDestinationNode.cpp675
-rw-r--r--dom/media/webaudio/AudioDestinationNode.h133
-rw-r--r--dom/media/webaudio/AudioEventTimeline.cpp513
-rw-r--r--dom/media/webaudio/AudioEventTimeline.h418
-rw-r--r--dom/media/webaudio/AudioListener.cpp121
-rw-r--r--dom/media/webaudio/AudioListener.h85
-rw-r--r--dom/media/webaudio/AudioNode.cpp608
-rw-r--r--dom/media/webaudio/AudioNode.h290
-rw-r--r--dom/media/webaudio/AudioNodeEngine.cpp437
-rw-r--r--dom/media/webaudio/AudioNodeEngine.h389
-rw-r--r--dom/media/webaudio/AudioNodeEngineGeneric.h58
-rw-r--r--dom/media/webaudio/AudioNodeEngineGenericImpl.h341
-rw-r--r--dom/media/webaudio/AudioNodeEngineNEON.cpp9
-rw-r--r--dom/media/webaudio/AudioNodeEngineSSE2.cpp10
-rw-r--r--dom/media/webaudio/AudioNodeEngineSSE4_2_FMA3.cpp10
-rw-r--r--dom/media/webaudio/AudioNodeExternalInputTrack.cpp225
-rw-r--r--dom/media/webaudio/AudioNodeExternalInputTrack.h46
-rw-r--r--dom/media/webaudio/AudioNodeTrack.cpp594
-rw-r--r--dom/media/webaudio/AudioNodeTrack.h233
-rw-r--r--dom/media/webaudio/AudioParam.cpp170
-rw-r--r--dom/media/webaudio/AudioParam.h239
-rw-r--r--dom/media/webaudio/AudioParamDescriptorMap.h22
-rw-r--r--dom/media/webaudio/AudioParamMap.cpp21
-rw-r--r--dom/media/webaudio/AudioParamMap.h34
-rw-r--r--dom/media/webaudio/AudioParamTimeline.h144
-rw-r--r--dom/media/webaudio/AudioProcessingEvent.cpp44
-rw-r--r--dom/media/webaudio/AudioProcessingEvent.h71
-rw-r--r--dom/media/webaudio/AudioScheduledSourceNode.cpp17
-rw-r--r--dom/media/webaudio/AudioScheduledSourceNode.h33
-rw-r--r--dom/media/webaudio/AudioWorkletGlobalScope.cpp358
-rw-r--r--dom/media/webaudio/AudioWorkletGlobalScope.h84
-rw-r--r--dom/media/webaudio/AudioWorkletImpl.cpp88
-rw-r--r--dom/media/webaudio/AudioWorkletImpl.h63
-rw-r--r--dom/media/webaudio/AudioWorkletNode.cpp899
-rw-r--r--dom/media/webaudio/AudioWorkletNode.h68
-rw-r--r--dom/media/webaudio/AudioWorkletProcessor.cpp49
-rw-r--r--dom/media/webaudio/AudioWorkletProcessor.h50
-rw-r--r--dom/media/webaudio/BiquadFilterNode.cpp358
-rw-r--r--dom/media/webaudio/BiquadFilterNode.h71
-rw-r--r--dom/media/webaudio/ChannelMergerNode.cpp100
-rw-r--r--dom/media/webaudio/ChannelMergerNode.h67
-rw-r--r--dom/media/webaudio/ChannelSplitterNode.cpp105
-rw-r--r--dom/media/webaudio/ChannelSplitterNode.h72
-rw-r--r--dom/media/webaudio/ConstantSourceNode.cpp279
-rw-r--r--dom/media/webaudio/ConstantSourceNode.h60
-rw-r--r--dom/media/webaudio/ConvolverNode.cpp479
-rw-r--r--dom/media/webaudio/ConvolverNode.h77
-rw-r--r--dom/media/webaudio/DelayBuffer.cpp236
-rw-r--r--dom/media/webaudio/DelayBuffer.h105
-rw-r--r--dom/media/webaudio/DelayNode.cpp223
-rw-r--r--dom/media/webaudio/DelayNode.h55
-rw-r--r--dom/media/webaudio/DynamicsCompressorNode.cpp226
-rw-r--r--dom/media/webaudio/DynamicsCompressorNode.h91
-rw-r--r--dom/media/webaudio/FFTBlock.cpp231
-rw-r--r--dom/media/webaudio/FFTBlock.h348
-rw-r--r--dom/media/webaudio/GainNode.cpp149
-rw-r--r--dom/media/webaudio/GainNode.h53
-rw-r--r--dom/media/webaudio/IIRFilterNode.cpp266
-rw-r--r--dom/media/webaudio/IIRFilterNode.h56
-rw-r--r--dom/media/webaudio/MediaBufferDecoder.cpp764
-rw-r--r--dom/media/webaudio/MediaBufferDecoder.h70
-rw-r--r--dom/media/webaudio/MediaElementAudioSourceNode.cpp100
-rw-r--r--dom/media/webaudio/MediaElementAudioSourceNode.h67
-rw-r--r--dom/media/webaudio/MediaStreamAudioDestinationNode.cpp150
-rw-r--r--dom/media/webaudio/MediaStreamAudioDestinationNode.h58
-rw-r--r--dom/media/webaudio/MediaStreamAudioSourceNode.cpp278
-rw-r--r--dom/media/webaudio/MediaStreamAudioSourceNode.h127
-rw-r--r--dom/media/webaudio/MediaStreamTrackAudioSourceNode.cpp200
-rw-r--r--dom/media/webaudio/MediaStreamTrackAudioSourceNode.h113
-rw-r--r--dom/media/webaudio/OscillatorNode.cpp560
-rw-r--r--dom/media/webaudio/OscillatorNode.h92
-rw-r--r--dom/media/webaudio/PannerNode.cpp730
-rw-r--r--dom/media/webaudio/PannerNode.h221
-rw-r--r--dom/media/webaudio/PanningUtils.h62
-rw-r--r--dom/media/webaudio/PeriodicWave.cpp146
-rw-r--r--dom/media/webaudio/PeriodicWave.h59
-rw-r--r--dom/media/webaudio/PlayingRefChangeHandler.h42
-rw-r--r--dom/media/webaudio/ReportDecodeResultTask.h37
-rw-r--r--dom/media/webaudio/ScriptProcessorNode.cpp549
-rw-r--r--dom/media/webaudio/ScriptProcessorNode.h131
-rw-r--r--dom/media/webaudio/StereoPannerNode.cpp197
-rw-r--r--dom/media/webaudio/StereoPannerNode.h74
-rw-r--r--dom/media/webaudio/ThreeDPoint.cpp38
-rw-r--r--dom/media/webaudio/ThreeDPoint.h68
-rw-r--r--dom/media/webaudio/WaveShaperNode.cpp389
-rw-r--r--dom/media/webaudio/WaveShaperNode.h72
-rw-r--r--dom/media/webaudio/WebAudioUtils.cpp114
-rw-r--r--dom/media/webaudio/WebAudioUtils.h184
-rw-r--r--dom/media/webaudio/blink/Biquad.cpp442
-rw-r--r--dom/media/webaudio/blink/Biquad.h108
-rw-r--r--dom/media/webaudio/blink/DenormalDisabler.h170
-rw-r--r--dom/media/webaudio/blink/DynamicsCompressor.cpp315
-rw-r--r--dom/media/webaudio/blink/DynamicsCompressor.h135
-rw-r--r--dom/media/webaudio/blink/DynamicsCompressorKernel.cpp496
-rw-r--r--dom/media/webaudio/blink/DynamicsCompressorKernel.h125
-rw-r--r--dom/media/webaudio/blink/FFTConvolver.cpp118
-rw-r--r--dom/media/webaudio/blink/FFTConvolver.h87
-rw-r--r--dom/media/webaudio/blink/HRTFDatabase.cpp135
-rw-r--r--dom/media/webaudio/blink/HRTFDatabase.h104
-rw-r--r--dom/media/webaudio/blink/HRTFDatabaseLoader.cpp215
-rw-r--r--dom/media/webaudio/blink/HRTFDatabaseLoader.h155
-rw-r--r--dom/media/webaudio/blink/HRTFElevation.cpp317
-rw-r--r--dom/media/webaudio/blink/HRTFElevation.h114
-rw-r--r--dom/media/webaudio/blink/HRTFKernel.cpp109
-rw-r--r--dom/media/webaudio/blink/HRTFKernel.h129
-rw-r--r--dom/media/webaudio/blink/HRTFPanner.cpp328
-rw-r--r--dom/media/webaudio/blink/HRTFPanner.h120
-rw-r--r--dom/media/webaudio/blink/IIRFilter.cpp181
-rw-r--r--dom/media/webaudio/blink/IIRFilter.h62
-rw-r--r--dom/media/webaudio/blink/IRC_Composite_C_R0195-incl.cpp4571
-rw-r--r--dom/media/webaudio/blink/PeriodicWave.cpp355
-rw-r--r--dom/media/webaudio/blink/PeriodicWave.h123
-rw-r--r--dom/media/webaudio/blink/README24
-rw-r--r--dom/media/webaudio/blink/Reverb.cpp277
-rw-r--r--dom/media/webaudio/blink/Reverb.h80
-rw-r--r--dom/media/webaudio/blink/ReverbAccumulationBuffer.cpp114
-rw-r--r--dom/media/webaudio/blink/ReverbAccumulationBuffer.h76
-rw-r--r--dom/media/webaudio/blink/ReverbConvolver.cpp272
-rw-r--r--dom/media/webaudio/blink/ReverbConvolver.h94
-rw-r--r--dom/media/webaudio/blink/ReverbConvolverStage.cpp101
-rw-r--r--dom/media/webaudio/blink/ReverbConvolverStage.h84
-rw-r--r--dom/media/webaudio/blink/ReverbInputBuffer.cpp85
-rw-r--r--dom/media/webaudio/blink/ReverbInputBuffer.h73
-rw-r--r--dom/media/webaudio/blink/ZeroPole.cpp83
-rw-r--r--dom/media/webaudio/blink/ZeroPole.h63
-rw-r--r--dom/media/webaudio/blink/moz.build36
-rw-r--r--dom/media/webaudio/moz.build153
-rw-r--r--dom/media/webaudio/test/1856145.oggbin0 -> 5818 bytes
-rw-r--r--dom/media/webaudio/test/8kHz-320kbps-6ch.aacbin0 -> 22657 bytes
-rw-r--r--dom/media/webaudio/test/audio-expected.wavbin0 -> 190764 bytes
-rw-r--r--dom/media/webaudio/test/audio-mono-expected-2.wavbin0 -> 103788 bytes
-rw-r--r--dom/media/webaudio/test/audio-mono-expected.wavbin0 -> 103788 bytes
-rw-r--r--dom/media/webaudio/test/audio-quad.wavbin0 -> 5128 bytes
-rw-r--r--dom/media/webaudio/test/audio.ogvbin0 -> 16049 bytes
-rw-r--r--dom/media/webaudio/test/audioBufferSourceNodeDetached_worker.js3
-rw-r--r--dom/media/webaudio/test/audiovideo.mp4bin0 -> 139713 bytes
-rw-r--r--dom/media/webaudio/test/blink/README9
-rw-r--r--dom/media/webaudio/test/blink/audio-testing.js192
-rw-r--r--dom/media/webaudio/test/blink/biquad-filters.js368
-rw-r--r--dom/media/webaudio/test/blink/biquad-testing.js153
-rw-r--r--dom/media/webaudio/test/blink/convolution-testing.js182
-rw-r--r--dom/media/webaudio/test/blink/mochitest.toml36
-rw-r--r--dom/media/webaudio/test/blink/panner-model-testing.js210
-rw-r--r--dom/media/webaudio/test/blink/test_biquadFilterNodeAllPass.html32
-rw-r--r--dom/media/webaudio/test/blink/test_biquadFilterNodeAutomation.html351
-rw-r--r--dom/media/webaudio/test/blink/test_biquadFilterNodeBandPass.html34
-rw-r--r--dom/media/webaudio/test/blink/test_biquadFilterNodeGetFrequencyResponse.html261
-rw-r--r--dom/media/webaudio/test/blink/test_biquadFilterNodeHighPass.html33
-rw-r--r--dom/media/webaudio/test/blink/test_biquadFilterNodeHighShelf.html33
-rw-r--r--dom/media/webaudio/test/blink/test_biquadFilterNodeLowPass.html34
-rw-r--r--dom/media/webaudio/test/blink/test_biquadFilterNodeLowShelf.html34
-rw-r--r--dom/media/webaudio/test/blink/test_biquadFilterNodeNotch.html33
-rw-r--r--dom/media/webaudio/test/blink/test_biquadFilterNodePeaking.html34
-rw-r--r--dom/media/webaudio/test/blink/test_biquadFilterNodeTail.html76
-rw-r--r--dom/media/webaudio/test/blink/test_iirFilterNode.html467
-rw-r--r--dom/media/webaudio/test/blink/test_iirFilterNodeGetFrequencyResponse.html97
-rw-r--r--dom/media/webaudio/test/corsServer.sjs26
-rw-r--r--dom/media/webaudio/test/file_nodeCreationDocumentGone.html4
-rwxr-xr-xdom/media/webaudio/test/generate-test-files.py52
-rw-r--r--dom/media/webaudio/test/half-a-second-1ch-44100-aac-afconvert.mp4bin0 -> 6560 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-1ch-44100-aac.aacbin0 -> 4826 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-1ch-44100-aac.mp4bin0 -> 5584 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-1ch-44100-alaw.wavbin0 -> 22142 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-1ch-44100-flac.flacbin0 -> 17320 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-1ch-44100-libmp3lame.mp3bin0 -> 4615 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-1ch-44100-libopus.mp4bin0 -> 7171 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-1ch-44100-libopus.opusbin0 -> 6469 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-1ch-44100-libopus.webmbin0 -> 6991 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-1ch-44100-libvorbis.oggbin0 -> 4320 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-1ch-44100-libvorbis.webmbin0 -> 4878 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-1ch-44100-mulaw.wavbin0 -> 22142 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-1ch-44100.wavbin0 -> 44144 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-1ch-48000-aac.aacbin0 -> 4840 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-1ch-48000-aac.mp4bin0 -> 5592 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-1ch-48000-flac.flacbin0 -> 18577 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-1ch-48000-libmp3lame.mp3bin0 -> 4461 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-1ch-48000-libopus.mp4bin0 -> 6738 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-1ch-48000-libopus.opusbin0 -> 6031 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-1ch-48000-libopus.webmbin0 -> 6558 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-1ch-48000-libvorbis.oggbin0 -> 4559 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-1ch-48000-libvorbis.webmbin0 -> 5142 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-1ch-48000.wavbin0 -> 48044 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-2ch-44100-aac.aacbin0 -> 8755 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-2ch-44100-aac.mp4bin0 -> 9513 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-2ch-44100-flac.flacbin0 -> 23279 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-2ch-44100-libmp3lame.mp3bin0 -> 9030 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-2ch-44100-libopus.mp4bin0 -> 11593 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-2ch-44100-libopus.opusbin0 -> 10905 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-2ch-44100-libopus.webmbin0 -> 11413 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-2ch-44100-libvorbis.oggbin0 -> 5478 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-2ch-44100-libvorbis.webmbin0 -> 6033 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-2ch-44100.wavbin0 -> 88244 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-2ch-48000-aac.aacbin0 -> 8727 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-2ch-48000-aac.mp4bin0 -> 9479 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-2ch-48000-flac.flacbin0 -> 24984 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-2ch-48000-libmp3lame.mp3bin0 -> 8685 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-2ch-48000-libopus.mp4bin0 -> 12247 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-2ch-48000-libopus.opusbin0 -> 11559 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-2ch-48000-libopus.webmbin0 -> 12067 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-2ch-48000-libvorbis.oggbin0 -> 5784 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-2ch-48000-libvorbis.webmbin0 -> 6364 bytes
-rw-r--r--dom/media/webaudio/test/half-a-second-2ch-48000.wavbin0 -> 96044 bytes
-rw-r--r--dom/media/webaudio/test/invalid.txt1
-rw-r--r--dom/media/webaudio/test/invalidContent.flac1
-rw-r--r--dom/media/webaudio/test/layouttest-glue.js18
-rw-r--r--dom/media/webaudio/test/mochitest.toml332
-rw-r--r--dom/media/webaudio/test/mochitest_audio.toml100
-rw-r--r--dom/media/webaudio/test/mochitest_bugs.toml89
-rw-r--r--dom/media/webaudio/test/mochitest_media.toml76
-rw-r--r--dom/media/webaudio/test/nil-packet.oggbin0 -> 9760 bytes
-rw-r--r--dom/media/webaudio/test/noaudio.webmbin0 -> 105755 bytes
-rw-r--r--dom/media/webaudio/test/sine-440-10s.opusbin0 -> 94428 bytes
-rw-r--r--dom/media/webaudio/test/sixteen-frames.mp3bin0 -> 625 bytes
-rw-r--r--dom/media/webaudio/test/small-shot-expected.wavbin0 -> 53036 bytes
-rw-r--r--dom/media/webaudio/test/small-shot-mono-expected.wavbin0 -> 26540 bytes
-rw-r--r--dom/media/webaudio/test/small-shot.mp3bin0 -> 6825 bytes
-rw-r--r--dom/media/webaudio/test/small-shot.oggbin0 -> 6416 bytes
-rw-r--r--dom/media/webaudio/test/sweep-300-330-1sec.opusbin0 -> 8889 bytes
-rw-r--r--dom/media/webaudio/test/test_AudioBuffer.html104
-rw-r--r--dom/media/webaudio/test/test_AudioContext.html23
-rw-r--r--dom/media/webaudio/test/test_AudioContext_disabled.html56
-rw-r--r--dom/media/webaudio/test/test_AudioListener.html26
-rw-r--r--dom/media/webaudio/test/test_AudioNodeDevtoolsAPI.html59
-rw-r--r--dom/media/webaudio/test/test_AudioParamDevtoolsAPI.html49
-rw-r--r--dom/media/webaudio/test/test_OfflineAudioContext.html118
-rw-r--r--dom/media/webaudio/test/test_ScriptProcessorCollected1.html77
-rw-r--r--dom/media/webaudio/test/test_WebAudioMemoryReporting.html54
-rw-r--r--dom/media/webaudio/test/test_analyserNode.html178
-rw-r--r--dom/media/webaudio/test/test_analyserNodeMinimum.html51
-rw-r--r--dom/media/webaudio/test/test_analyserNodeOutput.html43
-rw-r--r--dom/media/webaudio/test/test_analyserNodePassThrough.html47
-rw-r--r--dom/media/webaudio/test/test_analyserNodeWithGain.html47
-rw-r--r--dom/media/webaudio/test/test_analyserScale.html58
-rw-r--r--dom/media/webaudio/test/test_audioBufferSourceNode.html44
-rw-r--r--dom/media/webaudio/test/test_audioBufferSourceNodeDetached.html58
-rw-r--r--dom/media/webaudio/test/test_audioBufferSourceNodeEnded.html36
-rw-r--r--dom/media/webaudio/test/test_audioBufferSourceNodeLazyLoopParam.html47
-rw-r--r--dom/media/webaudio/test/test_audioBufferSourceNodeLoop.html45
-rw-r--r--dom/media/webaudio/test/test_audioBufferSourceNodeLoopStartEnd.html48
-rw-r--r--dom/media/webaudio/test/test_audioBufferSourceNodeLoopStartEndSame.html44
-rw-r--r--dom/media/webaudio/test/test_audioBufferSourceNodeNoStart.html33
-rw-r--r--dom/media/webaudio/test/test_audioBufferSourceNodeNullBuffer.html31
-rw-r--r--dom/media/webaudio/test/test_audioBufferSourceNodeOffset.html55
-rw-r--r--dom/media/webaudio/test/test_audioBufferSourceNodePassThrough.html45
-rw-r--r--dom/media/webaudio/test/test_audioBufferSourceNodeRate.html58
-rw-r--r--dom/media/webaudio/test/test_audioContextGC.html162
-rw-r--r--dom/media/webaudio/test/test_audioContextParams_recordNonDefaultSampleRate.html48
-rw-r--r--dom/media/webaudio/test/test_audioContextParams_sampleRate.html82
-rw-r--r--dom/media/webaudio/test/test_audioContextSuspendResumeClose.html419
-rw-r--r--dom/media/webaudio/test/test_audioDestinationNode.html26
-rw-r--r--dom/media/webaudio/test/test_audioParamChaining.html77
-rw-r--r--dom/media/webaudio/test/test_audioParamExponentialRamp.html58
-rw-r--r--dom/media/webaudio/test/test_audioParamGain.html61
-rw-r--r--dom/media/webaudio/test/test_audioParamLinearRamp.html54
-rw-r--r--dom/media/webaudio/test/test_audioParamSetCurveAtTime.html54
-rw-r--r--dom/media/webaudio/test/test_audioParamSetTargetAtTime.html54
-rw-r--r--dom/media/webaudio/test/test_audioParamSetTargetAtTimeZeroTimeConstant.html57
-rw-r--r--dom/media/webaudio/test/test_audioParamSetValueAtTime.html52
-rw-r--r--dom/media/webaudio/test/test_audioParamTimelineDestinationOffset.html45
-rw-r--r--dom/media/webaudio/test/test_badConnect.html52
-rw-r--r--dom/media/webaudio/test/test_biquadFilterNode.html86
-rw-r--r--dom/media/webaudio/test/test_biquadFilterNodePassThrough.html47
-rw-r--r--dom/media/webaudio/test/test_biquadFilterNodeWithGain.html61
-rw-r--r--dom/media/webaudio/test/test_bug1027864.html74
-rw-r--r--dom/media/webaudio/test/test_bug1056032.html35
-rw-r--r--dom/media/webaudio/test/test_bug1113634.html58
-rw-r--r--dom/media/webaudio/test/test_bug1118372.html46
-rw-r--r--dom/media/webaudio/test/test_bug1255618.html41
-rw-r--r--dom/media/webaudio/test/test_bug1267579.html46
-rw-r--r--dom/media/webaudio/test/test_bug1355798.html30
-rw-r--r--dom/media/webaudio/test/test_bug1447273.html175
-rw-r--r--dom/media/webaudio/test/test_bug808374.html22
-rw-r--r--dom/media/webaudio/test/test_bug827541.html24
-rw-r--r--dom/media/webaudio/test/test_bug839753.html18
-rw-r--r--dom/media/webaudio/test/test_bug845960.html18
-rw-r--r--dom/media/webaudio/test/test_bug856771.html26
-rw-r--r--dom/media/webaudio/test/test_bug866570.html18
-rw-r--r--dom/media/webaudio/test/test_bug866737.html36
-rw-r--r--dom/media/webaudio/test/test_bug867089.html43
-rw-r--r--dom/media/webaudio/test/test_bug867174.html38
-rw-r--r--dom/media/webaudio/test/test_bug873335.html22
-rw-r--r--dom/media/webaudio/test/test_bug875221.html243
-rw-r--r--dom/media/webaudio/test/test_bug875402.html50
-rw-r--r--dom/media/webaudio/test/test_bug894150.html21
-rw-r--r--dom/media/webaudio/test/test_bug956489.html56
-rw-r--r--dom/media/webaudio/test/test_bug964376.html64
-rw-r--r--dom/media/webaudio/test/test_bug966247.html46
-rw-r--r--dom/media/webaudio/test/test_bug972678.html62
-rw-r--r--dom/media/webaudio/test/test_channelMergerNode.html57
-rw-r--r--dom/media/webaudio/test/test_channelMergerNodeWithVolume.html60
-rw-r--r--dom/media/webaudio/test/test_channelSplitterNode.html71
-rw-r--r--dom/media/webaudio/test/test_channelSplitterNodeWithVolume.html76
-rw-r--r--dom/media/webaudio/test/test_convolver-upmixing-1-channel-response.html143
-rw-r--r--dom/media/webaudio/test/test_convolverNode.html31
-rw-r--r--dom/media/webaudio/test/test_convolverNodeChannelCount.html61
-rw-r--r--dom/media/webaudio/test/test_convolverNodeChannelInterpretationChanges.html169
-rw-r--r--dom/media/webaudio/test/test_convolverNodeDelay.html72
-rw-r--r--dom/media/webaudio/test/test_convolverNodeFiniteInfluence.html44
-rw-r--r--dom/media/webaudio/test/test_convolverNodeNormalization.html83
-rw-r--r--dom/media/webaudio/test/test_convolverNodeOOM.html46
-rw-r--r--dom/media/webaudio/test/test_convolverNodePassThrough.html48
-rw-r--r--dom/media/webaudio/test/test_convolverNodeWithGain.html62
-rw-r--r--dom/media/webaudio/test/test_convolverNode_mono_mono.html72
-rw-r--r--dom/media/webaudio/test/test_currentTime.html27
-rw-r--r--dom/media/webaudio/test/test_decodeAudioDataOnDetachedBuffer.html50
-rw-r--r--dom/media/webaudio/test/test_decodeAudioDataPromise.html62
-rw-r--r--dom/media/webaudio/test/test_decodeAudioError.html74
-rw-r--r--dom/media/webaudio/test/test_decodeMultichannel.html75
-rw-r--r--dom/media/webaudio/test/test_decodeOpusTail.html28
-rw-r--r--dom/media/webaudio/test/test_decoderDelay.html154
-rw-r--r--dom/media/webaudio/test/test_delayNode.html101
-rw-r--r--dom/media/webaudio/test/test_delayNodeAtMax.html53
-rw-r--r--dom/media/webaudio/test/test_delayNodeChannelChanges.html98
-rw-r--r--dom/media/webaudio/test/test_delayNodeCycles.html156
-rw-r--r--dom/media/webaudio/test/test_delayNodePassThrough.html53
-rw-r--r--dom/media/webaudio/test/test_delayNodeSmallMaxDelay.html43
-rw-r--r--dom/media/webaudio/test/test_delayNodeTailIncrease.html71
-rw-r--r--dom/media/webaudio/test/test_delayNodeTailWithDisconnect.html95
-rw-r--r--dom/media/webaudio/test/test_delayNodeTailWithGain.html72
-rw-r--r--dom/media/webaudio/test/test_delayNodeTailWithReconnect.html136
-rw-r--r--dom/media/webaudio/test/test_delayNodeWithGain.html54
-rw-r--r--dom/media/webaudio/test/test_delaynode-channel-count-1.html104
-rw-r--r--dom/media/webaudio/test/test_disconnectAll.html51
-rw-r--r--dom/media/webaudio/test/test_disconnectAudioParam.html58
-rw-r--r--dom/media/webaudio/test/test_disconnectAudioParamFromOutput.html67
-rw-r--r--dom/media/webaudio/test/test_disconnectExceptions.html75
-rw-r--r--dom/media/webaudio/test/test_disconnectFromAudioNode.html55
-rw-r--r--dom/media/webaudio/test/test_disconnectFromAudioNodeAndOutput.html59
-rw-r--r--dom/media/webaudio/test/test_disconnectFromAudioNodeAndOutputAndInput.html57
-rw-r--r--dom/media/webaudio/test/test_disconnectFromAudioNodeMultipleConnection.html56
-rw-r--r--dom/media/webaudio/test/test_disconnectFromOutput.html54
-rw-r--r--dom/media/webaudio/test/test_dynamicsCompressorNode.html68
-rw-r--r--dom/media/webaudio/test/test_dynamicsCompressorNodePassThrough.html47
-rw-r--r--dom/media/webaudio/test/test_dynamicsCompressorNodeWithGain.html51
-rw-r--r--dom/media/webaudio/test/test_event_listener_leaks.html47
-rw-r--r--dom/media/webaudio/test/test_gainNode.html72
-rw-r--r--dom/media/webaudio/test/test_gainNodeInLoop.html48
-rw-r--r--dom/media/webaudio/test/test_gainNodePassThrough.html49
-rw-r--r--dom/media/webaudio/test/test_iirFilterNodePassThrough.html47
-rw-r--r--dom/media/webaudio/test/test_maxChannelCount.html38
-rw-r--r--dom/media/webaudio/test/test_mediaDecoding.html436
-rw-r--r--dom/media/webaudio/test/test_mediaElementAudioSourceNode.html74
-rw-r--r--dom/media/webaudio/test/test_mediaElementAudioSourceNodeCrossOrigin.html94
-rw-r--r--dom/media/webaudio/test/test_mediaElementAudioSourceNodeFidelity.html137
-rw-r--r--dom/media/webaudio/test/test_mediaElementAudioSourceNodePassThrough.html66
-rw-r--r--dom/media/webaudio/test/test_mediaElementAudioSourceNodeVideo.html70
-rw-r--r--dom/media/webaudio/test/test_mediaStreamAudioDestinationNode.html50
-rw-r--r--dom/media/webaudio/test/test_mediaStreamAudioSourceNode.html50
-rw-r--r--dom/media/webaudio/test/test_mediaStreamAudioSourceNodeCrossOrigin.html59
-rw-r--r--dom/media/webaudio/test/test_mediaStreamAudioSourceNodeNoGC.html116
-rw-r--r--dom/media/webaudio/test/test_mediaStreamAudioSourceNodePassThrough.html55
-rw-r--r--dom/media/webaudio/test/test_mediaStreamAudioSourceNodeResampling.html74
-rw-r--r--dom/media/webaudio/test/test_mediaStreamTrackAudioSourceNode.html54
-rw-r--r--dom/media/webaudio/test/test_mediaStreamTrackAudioSourceNodeCrossOrigin.html54
-rw-r--r--dom/media/webaudio/test/test_mediaStreamTrackAudioSourceNodeVideo.html27
-rw-r--r--dom/media/webaudio/test/test_mixingRules.html402
-rw-r--r--dom/media/webaudio/test/test_nodeCreationDocumentGone.html34
-rw-r--r--dom/media/webaudio/test/test_nodeToParamConnection.html58
-rw-r--r--dom/media/webaudio/test/test_notAllowedToStartAudioContextGC.html57
-rw-r--r--dom/media/webaudio/test/test_offlineDestinationChannelCountLess.html42
-rw-r--r--dom/media/webaudio/test/test_offlineDestinationChannelCountMore.html46
-rw-r--r--dom/media/webaudio/test/test_oscillatorNode.html60
-rw-r--r--dom/media/webaudio/test/test_oscillatorNode2.html53
-rw-r--r--dom/media/webaudio/test/test_oscillatorNodeNegativeFrequency.html50
-rw-r--r--dom/media/webaudio/test/test_oscillatorNodePassThrough.html43
-rw-r--r--dom/media/webaudio/test/test_oscillatorNodeStart.html38
-rw-r--r--dom/media/webaudio/test/test_oscillatorTypeChange.html58
-rw-r--r--dom/media/webaudio/test/test_pannerNode.html71
-rw-r--r--dom/media/webaudio/test/test_pannerNodeAbove.html50
-rw-r--r--dom/media/webaudio/test/test_pannerNodeAtZeroDistance.html140
-rw-r--r--dom/media/webaudio/test/test_pannerNodeChannelCount.html52
-rw-r--r--dom/media/webaudio/test/test_pannerNodeHRTFSymmetry.html76
-rw-r--r--dom/media/webaudio/test/test_pannerNodePassThrough.html53
-rw-r--r--dom/media/webaudio/test/test_pannerNodeTail.html200
-rw-r--r--dom/media/webaudio/test/test_pannerNode_audioparam_distance.html43
-rw-r--r--dom/media/webaudio/test/test_pannerNode_equalPower.html26
-rw-r--r--dom/media/webaudio/test/test_pannerNode_maxDistance.html61
-rw-r--r--dom/media/webaudio/test/test_periodicWave.html130
-rw-r--r--dom/media/webaudio/test/test_periodicWaveBandLimiting.html86
-rw-r--r--dom/media/webaudio/test/test_periodicWaveDisableNormalization.html98
-rw-r--r--dom/media/webaudio/test/test_retrospective-exponentialRampToValueAtTime.html51
-rw-r--r--dom/media/webaudio/test/test_retrospective-linearRampToValueAtTime.html51
-rw-r--r--dom/media/webaudio/test/test_retrospective-setTargetAtTime.html51
-rw-r--r--dom/media/webaudio/test/test_retrospective-setValueAtTime.html54
-rw-r--r--dom/media/webaudio/test/test_retrospective-setValueCurveAtTime.html49
-rw-r--r--dom/media/webaudio/test/test_scriptProcessorNode.html132
-rw-r--r--dom/media/webaudio/test/test_scriptProcessorNodeChannelCount.html80
-rw-r--r--dom/media/webaudio/test/test_scriptProcessorNodeNotConnected.html34
-rw-r--r--dom/media/webaudio/test/test_scriptProcessorNodePassThrough.html103
-rw-r--r--dom/media/webaudio/test/test_scriptProcessorNodeZeroInputOutput.html39
-rw-r--r--dom/media/webaudio/test/test_scriptProcessorNode_playbackTime1.html52
-rw-r--r--dom/media/webaudio/test/test_sequentialBufferSourceWithResampling.html72
-rw-r--r--dom/media/webaudio/test/test_setValueCurveWithNonFiniteElements.html60
-rw-r--r--dom/media/webaudio/test/test_singleSourceDest.html70
-rw-r--r--dom/media/webaudio/test/test_slowStart.html48
-rw-r--r--dom/media/webaudio/test/test_stereoPannerNode.html295
-rw-r--r--dom/media/webaudio/test/test_stereoPannerNodePassThrough.html47
-rw-r--r--dom/media/webaudio/test/test_stereoPanningWithGain.html49
-rw-r--r--dom/media/webaudio/test/test_waveDecoder.html69
-rw-r--r--dom/media/webaudio/test/test_waveShaper.html60
-rw-r--r--dom/media/webaudio/test/test_waveShaperGain.html73
-rw-r--r--dom/media/webaudio/test/test_waveShaperInvalidLengthCurve.html66
-rw-r--r--dom/media/webaudio/test/test_waveShaperNoCurve.html43
-rw-r--r--dom/media/webaudio/test/test_waveShaperPassThrough.html55
-rw-r--r--dom/media/webaudio/test/test_webAudio_muteTab.html93
-rw-r--r--dom/media/webaudio/test/ting-44.1k-1ch.oggbin0 -> 8566 bytes
-rw-r--r--dom/media/webaudio/test/ting-44.1k-1ch.wavbin0 -> 61228 bytes
-rw-r--r--dom/media/webaudio/test/ting-44.1k-2ch.oggbin0 -> 10422 bytes
-rw-r--r--dom/media/webaudio/test/ting-44.1k-2ch.wavbin0 -> 122412 bytes
-rw-r--r--dom/media/webaudio/test/ting-48k-1ch.oggbin0 -> 8680 bytes
-rw-r--r--dom/media/webaudio/test/ting-48k-1ch.wavbin0 -> 66638 bytes
-rw-r--r--dom/media/webaudio/test/ting-48k-2ch.oggbin0 -> 10701 bytes
-rw-r--r--dom/media/webaudio/test/ting-48k-2ch.wavbin0 -> 133232 bytes
-rw-r--r--dom/media/webaudio/test/ting-dualchannel44.1.wavbin0 -> 122412 bytes
-rw-r--r--dom/media/webaudio/test/ting-dualchannel48.wavbin0 -> 122412 bytes
-rw-r--r--dom/media/webaudio/test/waveformatextensible.wavbin0 -> 1024 bytes
-rw-r--r--dom/media/webaudio/test/waveformatextensiblebadmask.wavbin0 -> 7136 bytes
-rw-r--r--dom/media/webaudio/test/webaudio.js367
430 files changed, 49011 insertions, 0 deletions
diff --git a/dom/media/webaudio/AlignedTArray.h b/dom/media/webaudio/AlignedTArray.h
new file mode 100644
index 0000000000..28c0f094ef
--- /dev/null
+++ b/dom/media/webaudio/AlignedTArray.h
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef AlignedTArray_h__
+#define AlignedTArray_h__
+
+#include "mozilla/Alignment.h"
+#include "nsTArray.h"
+
+/**
+ * E: element type, must be a POD type.
+ * N: N bytes alignment for the first element, defaults to 32
+ * S: S bytes of inline storage
+ */
+template <typename E, int S, int N = 32>
+class AlignedAutoTArray : private AutoTArray<E, S + N> {
+ static_assert((N & (N - 1)) == 0, "N must be power of 2");
+ typedef AutoTArray<E, S + N> base_type;
+
+ public:
+ typedef E value_type;
+ typedef typename base_type::size_type size_type;
+ typedef typename base_type::index_type index_type;
+
+ AlignedAutoTArray() = default;
+ explicit AlignedAutoTArray(size_type capacity)
+ : base_type(capacity + sExtra) {}
+ value_type* Elements() { return getAligned(base_type::Elements()); }
+ const value_type* Elements() const {
+ return getAligned(base_type::Elements());
+ }
+ value_type& operator[](index_type i) { return Elements()[i]; }
+ const value_type& operator[](index_type i) const { return Elements()[i]; }
+
+ void SetLength(size_type newLen) { base_type::SetLength(newLen + sExtra); }
+
+ [[nodiscard]] bool SetLength(size_type newLen, const mozilla::fallible_t&) {
+ return base_type::SetLength(newLen + sExtra, mozilla::fallible);
+ }
+
+ size_type Length() const {
+ return base_type::Length() <= sExtra ? 0 : base_type::Length() - sExtra;
+ }
+
+ using base_type::ShallowSizeOfExcludingThis;
+ using base_type::ShallowSizeOfIncludingThis;
+
+ private:
+ AlignedAutoTArray(const AlignedAutoTArray& other) = delete;
+ void operator=(const AlignedAutoTArray& other) = delete;
+
+ static const size_type sPadding =
+ N <= MOZ_ALIGNOF(E) ? 0 : N - MOZ_ALIGNOF(E);
+ static const size_type sExtra = (sPadding + sizeof(E) - 1) / sizeof(E);
+
+ template <typename U>
+ static U* getAligned(U* p) {
+ return reinterpret_cast<U*>(((uintptr_t)p + N - 1) & ~(N - 1));
+ }
+};
+
+/**
+ * E: element type, must be a POD type.
+ * N: N bytes alignment for the first element, defaults to 32
+ */
+template <typename E, int N = 32>
+class AlignedTArray : private nsTArray_Impl<E, nsTArrayInfallibleAllocator> {
+ static_assert((N & (N - 1)) == 0, "N must be power of 2");
+ typedef nsTArray_Impl<E, nsTArrayInfallibleAllocator> base_type;
+
+ public:
+ typedef E value_type;
+ typedef typename base_type::size_type size_type;
+ typedef typename base_type::index_type index_type;
+
+ AlignedTArray() = default;
+ explicit AlignedTArray(size_type capacity) : base_type(capacity + sExtra) {}
+ value_type* Elements() { return getAligned(base_type::Elements()); }
+ const value_type* Elements() const {
+ return getAligned(base_type::Elements());
+ }
+ value_type& operator[](index_type i) { return Elements()[i]; }
+ const value_type& operator[](index_type i) const { return Elements()[i]; }
+
+ void SetLength(size_type newLen) { base_type::SetLength(newLen + sExtra); }
+
+ [[nodiscard]] bool SetLength(size_type newLen, const mozilla::fallible_t&) {
+ return base_type::SetLength(newLen + sExtra, mozilla::fallible);
+ }
+
+ size_type Length() const {
+ return base_type::Length() <= sExtra ? 0 : base_type::Length() - sExtra;
+ }
+
+ using base_type::ShallowSizeOfExcludingThis;
+ using base_type::ShallowSizeOfIncludingThis;
+
+ private:
+ AlignedTArray(const AlignedTArray& other) = delete;
+ void operator=(const AlignedTArray& other) = delete;
+
+ static const size_type sPadding =
+ N <= MOZ_ALIGNOF(E) ? 0 : N - MOZ_ALIGNOF(E);
+ static const size_type sExtra = (sPadding + sizeof(E) - 1) / sizeof(E);
+
+ template <typename U>
+ static U* getAligned(U* p) {
+ return reinterpret_cast<U*>(((uintptr_t)p + N - 1) & ~(N - 1));
+ }
+};
+
+#endif // AlignedTArray_h__
diff --git a/dom/media/webaudio/AlignmentUtils.h b/dom/media/webaudio/AlignmentUtils.h
new file mode 100644
index 0000000000..c22dd4a58b
--- /dev/null
+++ b/dom/media/webaudio/AlignmentUtils.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef AlignmentUtils_h__
+#define AlignmentUtils_h__
+
+#define IS_ALIGNED16(ptr) ((((uintptr_t)ptr + 15) & ~0x0F) == (uintptr_t)ptr)
+
+#ifdef DEBUG
+# define ASSERT_ALIGNED16(ptr) \
+ MOZ_ASSERT(IS_ALIGNED16(ptr), \
+ #ptr " has to be aligned to a 16 byte boundary");
+#else
+# define ASSERT_ALIGNED16(ptr)
+#endif
+
+#ifdef DEBUG
+# define ASSERT_MULTIPLE16(v) \
+ MOZ_ASSERT(v % 16 == 0, #v " has to be a a multiple of 16");
+#else
+# define ASSERT_MULTIPLE16(v)
+#endif
+
+#define ALIGNED16(ptr) (float*)(((uintptr_t)ptr + 15) & ~0x0F);
+
+#endif
diff --git a/dom/media/webaudio/AnalyserNode.cpp b/dom/media/webaudio/AnalyserNode.cpp
new file mode 100644
index 0000000000..d8b547477e
--- /dev/null
+++ b/dom/media/webaudio/AnalyserNode.cpp
@@ -0,0 +1,388 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/AnalyserNode.h"
+#include "mozilla/dom/AnalyserNodeBinding.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeTrack.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/PodOperations.h"
+#include "nsMathUtils.h"
+#include "Tracing.h"
+
+namespace mozilla {
+
+static const uint32_t MAX_FFT_SIZE = 32768;
+static const size_t CHUNK_COUNT = MAX_FFT_SIZE >> WEBAUDIO_BLOCK_SIZE_BITS;
+static_assert(MAX_FFT_SIZE == CHUNK_COUNT * WEBAUDIO_BLOCK_SIZE,
+ "MAX_FFT_SIZE must be a multiple of WEBAUDIO_BLOCK_SIZE");
+static_assert((CHUNK_COUNT & (CHUNK_COUNT - 1)) == 0,
+ "CHUNK_COUNT must be power of 2 for remainder behavior");
+
+namespace dom {
+
+class AnalyserNodeEngine final : public AudioNodeEngine {
+ class TransferBuffer final : public Runnable {
+ public:
+ TransferBuffer(AudioNodeTrack* aTrack, const AudioChunk& aChunk)
+ : Runnable("dom::AnalyserNodeEngine::TransferBuffer"),
+ mTrack(aTrack),
+ mChunk(aChunk) {}
+
+ NS_IMETHOD Run() override {
+ RefPtr<AnalyserNode> node =
+ static_cast<AnalyserNode*>(mTrack->Engine()->NodeMainThread());
+ if (node) {
+ node->AppendChunk(mChunk);
+ }
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<AudioNodeTrack> mTrack;
+ AudioChunk mChunk;
+ };
+
+ public:
+ explicit AnalyserNodeEngine(AnalyserNode* aNode) : AudioNodeEngine(aNode) {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ virtual void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
+ const AudioBlock& aInput, AudioBlock* aOutput,
+ bool* aFinished) override {
+ TRACE("AnalyserNodeEngine::ProcessBlock");
+ *aOutput = aInput;
+
+ if (aInput.IsNull()) {
+ // If AnalyserNode::mChunks has only null chunks, then there is no need
+ // to send further null chunks.
+ if (mChunksToProcess == 0) {
+ return;
+ }
+
+ --mChunksToProcess;
+ if (mChunksToProcess == 0) {
+ aTrack->ScheduleCheckForInactive();
+ }
+
+ } else {
+ // This many null chunks will be required to empty AnalyserNode::mChunks.
+ mChunksToProcess = CHUNK_COUNT;
+ }
+
+ RefPtr<TransferBuffer> transfer =
+ new TransferBuffer(aTrack, aInput.AsAudioChunk());
+ AbstractThread::MainThread()->Dispatch(transfer.forget());
+ }
+
+ virtual bool IsActive() const override { return mChunksToProcess != 0; }
+
+ virtual size_t SizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const override {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ uint32_t mChunksToProcess = 0;
+};
+
+/* static */
+already_AddRefed<AnalyserNode> AnalyserNode::Create(
+ AudioContext& aAudioContext, const AnalyserOptions& aOptions,
+ ErrorResult& aRv) {
+ RefPtr<AnalyserNode> analyserNode = new AnalyserNode(&aAudioContext);
+
+ analyserNode->Initialize(aOptions, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ analyserNode->SetFftSize(aOptions.mFftSize, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ analyserNode->SetMinAndMaxDecibels(aOptions.mMinDecibels,
+ aOptions.mMaxDecibels, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ analyserNode->SetSmoothingTimeConstant(aOptions.mSmoothingTimeConstant, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return analyserNode.forget();
+}
+
+AnalyserNode::AnalyserNode(AudioContext* aContext)
+ : AudioNode(aContext, 2, ChannelCountMode::Max,
+ ChannelInterpretation::Speakers),
+ mAnalysisBlock(2048),
+ mMinDecibels(-100.),
+ mMaxDecibels(-30.),
+ mSmoothingTimeConstant(.8) {
+ mTrack =
+ AudioNodeTrack::Create(aContext, new AnalyserNodeEngine(this),
+ AudioNodeTrack::NO_TRACK_FLAGS, aContext->Graph());
+
+ // Enough chunks must be recorded to handle the case of fftSize being
+ // increased to maximum immediately before getFloatTimeDomainData() is
+ // called, for example.
+ Unused << mChunks.SetLength(CHUNK_COUNT, fallible);
+
+ AllocateBuffer();
+}
+
+size_t AnalyserNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+ amount += mAnalysisBlock.SizeOfExcludingThis(aMallocSizeOf);
+ amount += mChunks.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ amount += mOutputBuffer.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ return amount;
+}
+
+size_t AnalyserNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+JSObject* AnalyserNode::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return AnalyserNode_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void AnalyserNode::SetFftSize(uint32_t aValue, ErrorResult& aRv) {
+ // Disallow values that are not a power of 2 and outside the [32,32768] range
+ if (aValue < 32 || aValue > MAX_FFT_SIZE || (aValue & (aValue - 1)) != 0) {
+ aRv.ThrowIndexSizeError(nsPrintfCString(
+ "FFT size %u is not a power of two in the range 32 to 32768", aValue));
+ return;
+ }
+ if (FftSize() != aValue) {
+ mAnalysisBlock.SetFFTSize(aValue);
+ AllocateBuffer();
+ }
+}
+
+void AnalyserNode::SetMinDecibels(double aValue, ErrorResult& aRv) {
+ if (aValue >= mMaxDecibels) {
+ aRv.ThrowIndexSizeError(nsPrintfCString(
+ "%g is not strictly smaller than current maxDecibels (%g)", aValue,
+ mMaxDecibels));
+ return;
+ }
+ mMinDecibels = aValue;
+}
+
+void AnalyserNode::SetMaxDecibels(double aValue, ErrorResult& aRv) {
+ if (aValue <= mMinDecibels) {
+ aRv.ThrowIndexSizeError(nsPrintfCString(
+ "%g is not strictly larger than current minDecibels (%g)", aValue,
+ mMinDecibels));
+ return;
+ }
+ mMaxDecibels = aValue;
+}
+
+void AnalyserNode::SetMinAndMaxDecibels(double aMinValue, double aMaxValue,
+ ErrorResult& aRv) {
+ if (aMinValue >= aMaxValue) {
+ aRv.ThrowIndexSizeError(nsPrintfCString(
+ "minDecibels value (%g) must be smaller than maxDecibels value (%g)",
+ aMinValue, aMaxValue));
+ return;
+ }
+ mMinDecibels = aMinValue;
+ mMaxDecibels = aMaxValue;
+}
+
+void AnalyserNode::SetSmoothingTimeConstant(double aValue, ErrorResult& aRv) {
+ if (aValue < 0 || aValue > 1) {
+ aRv.ThrowIndexSizeError(
+ nsPrintfCString("%g is not in the range [0, 1]", aValue));
+ return;
+ }
+ mSmoothingTimeConstant = aValue;
+}
+
+void AnalyserNode::GetFloatFrequencyData(const Float32Array& aArray) {
+ if (!FFTAnalysis()) {
+ // Might fail to allocate memory
+ return;
+ }
+
+ aArray.ProcessData([&](const Span<float>& aData, JS::AutoCheckCannotGC&&) {
+ size_t length = std::min(size_t(aData.Length()), mOutputBuffer.Length());
+
+ for (size_t i = 0; i < length; ++i) {
+ aData[i] = WebAudioUtils::ConvertLinearToDecibels(
+ mOutputBuffer[i], -std::numeric_limits<float>::infinity());
+ }
+ });
+}
+
+void AnalyserNode::GetByteFrequencyData(const Uint8Array& aArray) {
+ if (!FFTAnalysis()) {
+ // Might fail to allocate memory
+ return;
+ }
+
+ const double rangeScaleFactor = 1.0 / (mMaxDecibels - mMinDecibels);
+
+ aArray.ProcessData([&](const Span<uint8_t>& aData, JS::AutoCheckCannotGC&&) {
+ size_t length = std::min(size_t(aData.Length()), mOutputBuffer.Length());
+
+ for (size_t i = 0; i < length; ++i) {
+ const double decibels = WebAudioUtils::ConvertLinearToDecibels(
+ mOutputBuffer[i], mMinDecibels);
+ // scale down the value to the range of [0, UCHAR_MAX]
+ const double scaled = std::max(
+ 0.0,
+ std::min(double(UCHAR_MAX),
+ UCHAR_MAX * (decibels - mMinDecibels) * rangeScaleFactor));
+ aData[i] = static_cast<unsigned char>(scaled);
+ }
+ });
+}
+
+void AnalyserNode::GetFloatTimeDomainData(const Float32Array& aArray) {
+ aArray.ProcessData([&](const Span<float>& aData, JS::AutoCheckCannotGC&&) {
+ size_t length = std::min(aData.Length(), size_t(FftSize()));
+
+ GetTimeDomainData(aData.Elements(), length);
+ });
+}
+
+void AnalyserNode::GetByteTimeDomainData(const Uint8Array& aArray) {
+ aArray.ProcessData([&](const Span<uint8_t>& aData, JS::AutoCheckCannotGC&&) {
+ size_t length = std::min(aData.Length(), size_t(FftSize()));
+
+ AlignedTArray<float> tmpBuffer;
+ if (!tmpBuffer.SetLength(length, fallible)) {
+ return;
+ }
+
+ GetTimeDomainData(tmpBuffer.Elements(), length);
+
+ unsigned char* buffer = aData.Elements();
+ for (size_t i = 0; i < length; ++i) {
+ const float value = tmpBuffer[i];
+ // scale the value to the range of [0, UCHAR_MAX]
+ const float scaled =
+ std::max(0.0f, std::min(float(UCHAR_MAX), 128.0f * (value + 1.0f)));
+ buffer[i] = static_cast<unsigned char>(scaled);
+ }
+ });
+}
+
+bool AnalyserNode::FFTAnalysis() {
+ AlignedTArray<float> tmpBuffer;
+ size_t fftSize = FftSize();
+ if (!tmpBuffer.SetLength(fftSize, fallible)) {
+ return false;
+ }
+
+ float* inputBuffer = tmpBuffer.Elements();
+ GetTimeDomainData(inputBuffer, fftSize);
+ ApplyBlackmanWindow(inputBuffer, fftSize);
+ mAnalysisBlock.PerformFFT(inputBuffer);
+
+ // Normalize so than an input sine wave at 0dBfs registers as 0dBfs (undo FFT
+ // scaling factor).
+ const double magnitudeScale = 1.0 / fftSize;
+
+ for (uint32_t i = 0; i < mOutputBuffer.Length(); ++i) {
+ double scalarMagnitude =
+ fdlibm_hypot(mAnalysisBlock.RealData(i), mAnalysisBlock.ImagData(i)) *
+ magnitudeScale;
+ mOutputBuffer[i] = mSmoothingTimeConstant * mOutputBuffer[i] +
+ (1.0 - mSmoothingTimeConstant) * scalarMagnitude;
+ }
+
+ return true;
+}
+
+void AnalyserNode::ApplyBlackmanWindow(float* aBuffer, uint32_t aSize) {
+ double alpha = 0.16;
+ double a0 = 0.5 * (1.0 - alpha);
+ double a1 = 0.5;
+ double a2 = 0.5 * alpha;
+
+ for (uint32_t i = 0; i < aSize; ++i) {
+ double x = double(i) / aSize;
+ double window =
+ a0 - a1 * fdlibm_cos(2 * M_PI * x) + a2 * fdlibm_cos(4 * M_PI * x);
+ aBuffer[i] *= window;
+ }
+}
+
+bool AnalyserNode::AllocateBuffer() {
+ bool result = true;
+ if (mOutputBuffer.Length() != FrequencyBinCount()) {
+ if (!mOutputBuffer.SetLength(FrequencyBinCount(), fallible)) {
+ return false;
+ }
+ memset(mOutputBuffer.Elements(), 0, sizeof(float) * FrequencyBinCount());
+ }
+ return result;
+}
+
+void AnalyserNode::AppendChunk(const AudioChunk& aChunk) {
+ if (mChunks.Length() == 0) {
+ return;
+ }
+
+ ++mCurrentChunk;
+ mChunks[mCurrentChunk & (CHUNK_COUNT - 1)] = aChunk;
+}
+
+// Reads into aData the oldest aLength samples of the fftSize most recent
+// samples.
+void AnalyserNode::GetTimeDomainData(float* aData, size_t aLength) {
+ size_t fftSize = FftSize();
+ MOZ_ASSERT(aLength <= fftSize);
+
+ if (mChunks.Length() == 0) {
+ PodZero(aData, aLength);
+ return;
+ }
+
+ size_t readChunk =
+ mCurrentChunk - ((fftSize - 1) >> WEBAUDIO_BLOCK_SIZE_BITS);
+ size_t readIndex = (0 - fftSize) & (WEBAUDIO_BLOCK_SIZE - 1);
+ MOZ_ASSERT(readIndex == 0 || readIndex + fftSize == WEBAUDIO_BLOCK_SIZE);
+
+ for (size_t writeIndex = 0; writeIndex < aLength;) {
+ const AudioChunk& chunk = mChunks[readChunk & (CHUNK_COUNT - 1)];
+ const size_t channelCount = chunk.ChannelCount();
+ size_t copyLength =
+ std::min<size_t>(aLength - writeIndex, WEBAUDIO_BLOCK_SIZE);
+ float* dataOut = &aData[writeIndex];
+
+ if (channelCount == 0) {
+ PodZero(dataOut, copyLength);
+ } else {
+ float scale = chunk.mVolume / channelCount;
+ { // channel 0
+ auto channelData =
+ static_cast<const float*>(chunk.mChannelData[0]) + readIndex;
+ AudioBufferCopyWithScale(channelData, scale, dataOut, copyLength);
+ }
+ for (uint32_t i = 1; i < channelCount; ++i) {
+ auto channelData =
+ static_cast<const float*>(chunk.mChannelData[i]) + readIndex;
+ AudioBufferAddWithScale(channelData, scale, dataOut, copyLength);
+ }
+ }
+
+ readChunk++;
+ writeIndex += copyLength;
+ }
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/media/webaudio/AnalyserNode.h b/dom/media/webaudio/AnalyserNode.h
new file mode 100644
index 0000000000..bdec13f6b9
--- /dev/null
+++ b/dom/media/webaudio/AnalyserNode.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef AnalyserNode_h_
+#define AnalyserNode_h_
+
+#include "AudioNode.h"
+#include "FFTBlock.h"
+#include "AlignedTArray.h"
+
+namespace mozilla::dom {
+
+class AudioContext;
+struct AnalyserOptions;
+
+class AnalyserNode final : public AudioNode {
+ public:
+ static already_AddRefed<AnalyserNode> Create(AudioContext& aAudioContext,
+ const AnalyserOptions& aOptions,
+ ErrorResult& aRv);
+
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(AnalyserNode, AudioNode)
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ static already_AddRefed<AnalyserNode> Constructor(
+ const GlobalObject& aGlobal, AudioContext& aAudioContext,
+ const AnalyserOptions& aOptions, ErrorResult& aRv) {
+ return Create(aAudioContext, aOptions, aRv);
+ }
+
+ void GetFloatFrequencyData(const Float32Array& aArray);
+ void GetByteFrequencyData(const Uint8Array& aArray);
+ void GetFloatTimeDomainData(const Float32Array& aArray);
+ void GetByteTimeDomainData(const Uint8Array& aArray);
+ uint32_t FftSize() const { return mAnalysisBlock.FFTSize(); }
+ void SetFftSize(uint32_t aValue, ErrorResult& aRv);
+ uint32_t FrequencyBinCount() const { return FftSize() / 2; }
+ double MinDecibels() const { return mMinDecibels; }
+ void SetMinDecibels(double aValue, ErrorResult& aRv);
+ double MaxDecibels() const { return mMaxDecibels; }
+ void SetMaxDecibels(double aValue, ErrorResult& aRv);
+ double SmoothingTimeConstant() const { return mSmoothingTimeConstant; }
+ void SetSmoothingTimeConstant(double aValue, ErrorResult& aRv);
+
+ virtual const char* NodeType() const override { return "AnalyserNode"; }
+
+ virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
+ virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+ void SetMinAndMaxDecibels(double aMinValue, double aMaxValue,
+ ErrorResult& aRv);
+
+ private:
+ ~AnalyserNode() = default;
+
+ friend class AnalyserNodeEngine;
+ void AppendChunk(const AudioChunk& aChunk);
+ bool AllocateBuffer();
+ bool FFTAnalysis();
+ void ApplyBlackmanWindow(float* aBuffer, uint32_t aSize);
+ void GetTimeDomainData(float* aData, size_t aLength);
+
+ private:
+ explicit AnalyserNode(AudioContext* aContext);
+
+ FFTBlock mAnalysisBlock;
+ nsTArray<AudioChunk> mChunks;
+ double mMinDecibels;
+ double mMaxDecibels;
+ double mSmoothingTimeConstant;
+ size_t mCurrentChunk = 0;
+ AlignedTArray<float> mOutputBuffer;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/media/webaudio/AudioBlock.cpp b/dom/media/webaudio/AudioBlock.cpp
new file mode 100644
index 0000000000..09bbd7f70a
--- /dev/null
+++ b/dom/media/webaudio/AudioBlock.cpp
@@ -0,0 +1,166 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AudioBlock.h"
+#include "AlignmentUtils.h"
+
+namespace mozilla {
+
+/**
+ * Heap-allocated buffer of channels of 128-sample float arrays, with
+ * threadsafe refcounting. Typically you would allocate one of these, fill it
+ * in, and then treat it as immutable while it's shared.
+ *
+ * Downstream references are accounted specially so that the creator of the
+ * buffer can reuse and modify its contents next iteration if other references
+ * are all downstream temporary references held by AudioBlock.
+ *
+ * We guarantee 16 byte alignment of the channel data.
+ */
+class AudioBlockBuffer final : public ThreadSharedObject {
+ public:
+ virtual AudioBlockBuffer* AsAudioBlockBuffer() override { return this; };
+
+ uint32_t ChannelsAllocated() const { return mChannelsAllocated; }
+ float* ChannelData(uint32_t aChannel) const {
+ float* base =
+ reinterpret_cast<float*>(((uintptr_t)(this + 1) + 15) & ~0x0F);
+ ASSERT_ALIGNED16(base);
+ return base + aChannel * WEBAUDIO_BLOCK_SIZE;
+ }
+
+ static already_AddRefed<AudioBlockBuffer> Create(uint32_t aChannelCount) {
+ CheckedInt<size_t> size = WEBAUDIO_BLOCK_SIZE;
+ size *= aChannelCount;
+ size *= sizeof(float);
+ size += sizeof(AudioBlockBuffer);
+ size += 15; // padding for alignment
+ if (!size.isValid()) {
+ MOZ_CRASH();
+ }
+
+ void* m = operator new(size.value());
+ RefPtr<AudioBlockBuffer> p = new (m) AudioBlockBuffer(aChannelCount);
+ NS_ASSERTION((reinterpret_cast<char*>(p.get() + 1) -
+ reinterpret_cast<char*>(p.get())) %
+ 4 ==
+ 0,
+ "AudioBlockBuffers should be at least 4-byte aligned");
+ return p.forget();
+ }
+
+ // Graph thread only.
+ void DownstreamRefAdded() { ++mDownstreamRefCount; }
+ void DownstreamRefRemoved() {
+ MOZ_ASSERT(mDownstreamRefCount > 0);
+ --mDownstreamRefCount;
+ }
+ // Whether this is shared by any owners that are not downstream.
+ // Called only from owners with a reference that is not a downstream
+ // reference. Graph thread only.
+ bool HasLastingShares() const {
+ // mRefCnt is atomic and so reading its value is defined even when
+ // modifications may happen on other threads. mDownstreamRefCount is
+ // not modified on any other thread.
+ //
+ // If all other references are downstream references (managed on this, the
+ // graph thread), then other threads are not using this buffer and cannot
+ // add further references. This method can safely return false. The
+ // buffer contents can be modified.
+ //
+ // If there are other references that are not downstream references, then
+ // this method will return true. The buffer will be assumed to be still
+ // in use and so will not be reused.
+ nsrefcnt count = mRefCnt;
+ // This test is strictly less than because the caller has a reference
+ // that is not a downstream reference.
+ MOZ_ASSERT(mDownstreamRefCount < count);
+ return count != mDownstreamRefCount + 1;
+ }
+
+ virtual size_t SizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const override {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ private:
+ explicit AudioBlockBuffer(uint32_t aChannelsAllocated)
+ : mChannelsAllocated(aChannelsAllocated) {}
+ ~AudioBlockBuffer() override { MOZ_ASSERT(mDownstreamRefCount == 0); }
+
+ nsAutoRefCnt mDownstreamRefCount;
+ const uint32_t mChannelsAllocated;
+};
+
+AudioBlock::~AudioBlock() { ClearDownstreamMark(); }
+
+void AudioBlock::SetBuffer(ThreadSharedObject* aNewBuffer) {
+ if (aNewBuffer == mBuffer) {
+ return;
+ }
+
+ ClearDownstreamMark();
+
+ mBuffer = aNewBuffer;
+
+ if (!aNewBuffer) {
+ return;
+ }
+
+ AudioBlockBuffer* buffer = aNewBuffer->AsAudioBlockBuffer();
+ if (buffer) {
+ buffer->DownstreamRefAdded();
+ mBufferIsDownstreamRef = true;
+ }
+}
+
+void AudioBlock::ClearDownstreamMark() {
+ if (mBufferIsDownstreamRef) {
+ mBuffer->AsAudioBlockBuffer()->DownstreamRefRemoved();
+ mBufferIsDownstreamRef = false;
+ }
+}
+
+bool AudioBlock::CanWrite() {
+ // If mBufferIsDownstreamRef is set then the buffer is not ours to use.
+ // It may be in use by another node which is not downstream.
+ return !mBufferIsDownstreamRef &&
+ !mBuffer->AsAudioBlockBuffer()->HasLastingShares();
+}
+
+void AudioBlock::AllocateChannels(uint32_t aChannelCount) {
+ MOZ_ASSERT(mDuration == WEBAUDIO_BLOCK_SIZE);
+
+ if (mBufferIsDownstreamRef) {
+ // This is not our buffer to re-use.
+ ClearDownstreamMark();
+ } else if (mBuffer) {
+ AudioBlockBuffer* buffer = mBuffer->AsAudioBlockBuffer();
+ if (buffer && !buffer->HasLastingShares() &&
+ buffer->ChannelsAllocated() >= aChannelCount) {
+ MOZ_ASSERT(mBufferFormat == AUDIO_FORMAT_FLOAT32);
+ // No need to allocate again.
+ uint32_t previousChannelCount = ChannelCount();
+ mChannelData.SetLength(aChannelCount);
+ for (uint32_t i = previousChannelCount; i < aChannelCount; ++i) {
+ mChannelData[i] = buffer->ChannelData(i);
+ }
+ mVolume = 1.0f;
+ return;
+ }
+ }
+
+ RefPtr<AudioBlockBuffer> buffer = AudioBlockBuffer::Create(aChannelCount);
+ mChannelData.SetLength(aChannelCount);
+ for (uint32_t i = 0; i < aChannelCount; ++i) {
+ mChannelData[i] = buffer->ChannelData(i);
+ }
+ mBuffer = std::move(buffer);
+ mVolume = 1.0f;
+ mBufferFormat = AUDIO_FORMAT_FLOAT32;
+}
+
+} // namespace mozilla
diff --git a/dom/media/webaudio/AudioBlock.h b/dom/media/webaudio/AudioBlock.h
new file mode 100644
index 0000000000..b5178c9d8a
--- /dev/null
+++ b/dom/media/webaudio/AudioBlock.h
@@ -0,0 +1,134 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef MOZILLA_AUDIOBLOCK_H_
+#define MOZILLA_AUDIOBLOCK_H_
+
+#include "AudioSegment.h"
+
+namespace mozilla {
+
+/**
+ * An AudioChunk whose buffer contents need to be valid only for one
+ * processing block iteration, after which contents can be overwritten if the
+ * buffer has not been passed to longer term storage or to another thread,
+ * which may happen though AsAudioChunk() or AsMutableChunk().
+ *
+ * Use on graph thread only.
+ */
+class AudioBlock : private AudioChunk {
+ public:
+ AudioBlock() {
+ mDuration = WEBAUDIO_BLOCK_SIZE;
+ mBufferFormat = AUDIO_FORMAT_SILENCE;
+ }
+ // No effort is made in constructors to ensure that mBufferIsDownstreamRef
+ // is set because the block is expected to be a temporary and so the
+ // reference will be released before the next iteration.
+ // The custom copy constructor is required so as not to set
+ // mBufferIsDownstreamRef without notifying AudioBlockBuffer.
+ AudioBlock(const AudioBlock& aBlock) : AudioChunk(aBlock.AsAudioChunk()) {}
+ explicit AudioBlock(const AudioChunk& aChunk) : AudioChunk(aChunk) {
+ MOZ_ASSERT(aChunk.mDuration == WEBAUDIO_BLOCK_SIZE);
+ }
+ ~AudioBlock();
+
+ using AudioChunk::ChannelCount;
+ using AudioChunk::ChannelData;
+ using AudioChunk::GetDuration;
+ using AudioChunk::IsNull;
+ using AudioChunk::SizeOfExcludingThis;
+ using AudioChunk::SizeOfExcludingThisIfUnshared;
+ // mDuration is not exposed. Use GetDuration().
+ // mBuffer is not exposed. Use Get/SetBuffer().
+ using AudioChunk::mBufferFormat;
+ using AudioChunk::mChannelData;
+ using AudioChunk::mVolume;
+
+ const AudioChunk& AsAudioChunk() const { return *this; }
+ AudioChunk* AsMutableChunk() {
+ ClearDownstreamMark();
+ return this;
+ }
+
+ /**
+ * Allocates, if necessary, aChannelCount buffers of WEBAUDIO_BLOCK_SIZE float
+ * samples for writing.
+ */
+ void AllocateChannels(uint32_t aChannelCount);
+
+ /**
+ * ChannelFloatsForWrite() should only be used when the buffers have been
+ * created with AllocateChannels().
+ */
+ float* ChannelFloatsForWrite(size_t aChannel) {
+ MOZ_ASSERT(mBufferFormat == AUDIO_FORMAT_FLOAT32);
+ MOZ_ASSERT(CanWrite());
+ return static_cast<float*>(const_cast<void*>(mChannelData[aChannel]));
+ }
+
+ ThreadSharedObject* GetBuffer() const { return mBuffer; }
+ void SetBuffer(ThreadSharedObject* aNewBuffer);
+ void SetNull(TrackTime aDuration) {
+ MOZ_ASSERT(aDuration == WEBAUDIO_BLOCK_SIZE);
+ SetBuffer(nullptr);
+ mChannelData.Clear();
+ mVolume = 1.0f;
+ mBufferFormat = AUDIO_FORMAT_SILENCE;
+ }
+
+ AudioBlock& operator=(const AudioBlock& aBlock) {
+ // Instead of just copying, mBufferIsDownstreamRef must be first cleared
+ // if set. It is set again for the new mBuffer if possible. This happens
+ // in SetBuffer().
+ return *this = aBlock.AsAudioChunk();
+ }
+ AudioBlock& operator=(const AudioChunk& aChunk) {
+ MOZ_ASSERT(aChunk.mDuration == WEBAUDIO_BLOCK_SIZE);
+ SetBuffer(aChunk.mBuffer);
+ mChannelData = aChunk.mChannelData;
+ mVolume = aChunk.mVolume;
+ mBufferFormat = aChunk.mBufferFormat;
+ return *this;
+ }
+
+ bool IsMuted() const { return mVolume == 0.0f; }
+
+ bool IsSilentOrSubnormal() const {
+ if (!mBuffer) {
+ return true;
+ }
+
+ for (uint32_t i = 0, length = mChannelData.Length(); i < length; ++i) {
+ const float* channel = static_cast<const float*>(mChannelData[i]);
+ for (TrackTime frame = 0; frame < mDuration; ++frame) {
+ if (fabs(channel[frame]) >= FLT_MIN) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private:
+ void ClearDownstreamMark();
+ bool CanWrite();
+
+ // mBufferIsDownstreamRef is set only when mBuffer references an
+ // AudioBlockBuffer created in a different AudioBlock. That can happen when
+ // this AudioBlock is on a node downstream from the node which created the
+ // buffer. When this is set, the AudioBlockBuffer is notified that this
+ // reference does not prevent the upstream node from re-using the buffer next
+ // iteration and modifying its contents. The AudioBlockBuffer is also
+ // notified when mBuffer releases this reference.
+ bool mBufferIsDownstreamRef = false;
+};
+
+} // namespace mozilla
+
+MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::AudioBlock)
+
+#endif // MOZILLA_AUDIOBLOCK_H_
diff --git a/dom/media/webaudio/AudioBuffer.cpp b/dom/media/webaudio/AudioBuffer.cpp
new file mode 100644
index 0000000000..8c2a36fe45
--- /dev/null
+++ b/dom/media/webaudio/AudioBuffer.cpp
@@ -0,0 +1,498 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AudioBuffer.h"
+#include "mozilla/dom/AudioBufferBinding.h"
+#include "jsfriendapi.h"
+#include "js/ArrayBuffer.h" // JS::StealArrayBufferContents
+#include "js/experimental/TypedData.h" // JS_NewFloat32Array, JS_GetFloat32ArrayData, JS_GetTypedArrayLength, JS_GetArrayBufferViewBuffer
+#include "mozilla/ErrorResult.h"
+#include "AudioSegment.h"
+#include "AudioChannelFormat.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/HoldDropJSObjects.h"
+#include "mozilla/MemoryReporting.h"
+#include "AudioNodeEngine.h"
+#include "nsPrintfCString.h"
+#include "nsTHashSet.h"
+#include <numeric>
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(AudioBuffer)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AudioBuffer)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mJSChannels)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ tmp->ClearJSChannels();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AudioBuffer)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(AudioBuffer)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+ for (uint32_t i = 0; i < tmp->mJSChannels.Length(); ++i) {
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mJSChannels[i])
+ }
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+/**
+ * AudioBuffers can be shared between AudioContexts, so we need a separate
+ * mechanism to track their memory usage. This thread-safe class keeps track of
+ * all the AudioBuffers, and gets called back by the memory reporting system
+ * when a memory report is needed, reporting how much memory is used by the
+ * buffers backing AudioBuffer objects. */
+class AudioBufferMemoryTracker : public nsIMemoryReporter {
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIMEMORYREPORTER
+
+ private:
+ AudioBufferMemoryTracker();
+ virtual ~AudioBufferMemoryTracker();
+
+ public:
+ /* Those methods can be called on any thread. */
+ static void RegisterAudioBuffer(const AudioBuffer* aAudioBuffer);
+ static void UnregisterAudioBuffer(const AudioBuffer* aAudioBuffer);
+
+ private:
+ static AudioBufferMemoryTracker* GetInstance();
+ /* Those methods must be called with the lock held. */
+ void RegisterAudioBufferInternal(const AudioBuffer* aAudioBuffer);
+ /* Returns the number of buffers still present in the hash table. */
+ uint32_t UnregisterAudioBufferInternal(const AudioBuffer* aAudioBuffer);
+ void Init();
+
+ /* This protects all members of this class. */
+ static StaticMutex sMutex MOZ_UNANNOTATED;
+ static StaticRefPtr<AudioBufferMemoryTracker> sSingleton;
+ nsTHashSet<const AudioBuffer*> mBuffers;
+};
+
+StaticRefPtr<AudioBufferMemoryTracker> AudioBufferMemoryTracker::sSingleton;
+StaticMutex AudioBufferMemoryTracker::sMutex;
+
+NS_IMPL_ISUPPORTS(AudioBufferMemoryTracker, nsIMemoryReporter);
+
+AudioBufferMemoryTracker* AudioBufferMemoryTracker::GetInstance() {
+ sMutex.AssertCurrentThreadOwns();
+ if (!sSingleton) {
+ sSingleton = new AudioBufferMemoryTracker();
+ sSingleton->Init();
+ }
+ return sSingleton;
+}
+
+AudioBufferMemoryTracker::AudioBufferMemoryTracker() = default;
+
+void AudioBufferMemoryTracker::Init() { RegisterWeakMemoryReporter(this); }
+
+AudioBufferMemoryTracker::~AudioBufferMemoryTracker() {
+ UnregisterWeakMemoryReporter(this);
+}
+
+void AudioBufferMemoryTracker::RegisterAudioBuffer(
+ const AudioBuffer* aAudioBuffer) {
+ StaticMutexAutoLock lock(sMutex);
+ AudioBufferMemoryTracker* tracker = AudioBufferMemoryTracker::GetInstance();
+ tracker->RegisterAudioBufferInternal(aAudioBuffer);
+}
+
+void AudioBufferMemoryTracker::UnregisterAudioBuffer(
+ const AudioBuffer* aAudioBuffer) {
+ StaticMutexAutoLock lock(sMutex);
+ AudioBufferMemoryTracker* tracker = AudioBufferMemoryTracker::GetInstance();
+ uint32_t count;
+ count = tracker->UnregisterAudioBufferInternal(aAudioBuffer);
+ if (count == 0) {
+ sSingleton = nullptr;
+ }
+}
+
+void AudioBufferMemoryTracker::RegisterAudioBufferInternal(
+ const AudioBuffer* aAudioBuffer) {
+ sMutex.AssertCurrentThreadOwns();
+ mBuffers.Insert(aAudioBuffer);
+}
+
+uint32_t AudioBufferMemoryTracker::UnregisterAudioBufferInternal(
+ const AudioBuffer* aAudioBuffer) {
+ sMutex.AssertCurrentThreadOwns();
+ mBuffers.Remove(aAudioBuffer);
+ return mBuffers.Count();
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(AudioBufferMemoryTrackerMallocSizeOf)
+
+NS_IMETHODIMP
+AudioBufferMemoryTracker::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool) {
+ StaticMutexAutoLock lock(sMutex);
+ const size_t amount =
+ std::accumulate(mBuffers.cbegin(), mBuffers.cend(), size_t(0),
+ [](size_t val, const AudioBuffer* buffer) {
+ return val + buffer->SizeOfIncludingThis(
+ AudioBufferMemoryTrackerMallocSizeOf);
+ });
+
+ MOZ_COLLECT_REPORT("explicit/webaudio/audiobuffer", KIND_HEAP, UNITS_BYTES,
+ amount, "Memory used by AudioBuffer objects (Web Audio).");
+
+ return NS_OK;
+}
+
+AudioBuffer::AudioBuffer(nsPIDOMWindowInner* aWindow,
+ uint32_t aNumberOfChannels, uint32_t aLength,
+ float aSampleRate, ErrorResult& aRv)
+ : mOwnerWindow(do_GetWeakReference(aWindow)), mSampleRate(aSampleRate) {
+ // Note that a buffer with zero channels is permitted here for the sake of
+ // AudioProcessingEvent, where channel counts must match parameters passed
+ // to createScriptProcessor(), one of which may be zero.
+ if (aSampleRate < WebAudioUtils::MinSampleRate ||
+ aSampleRate > WebAudioUtils::MaxSampleRate) {
+ aRv.ThrowNotSupportedError(
+ nsPrintfCString("Sample rate (%g) is out of range", aSampleRate));
+ return;
+ }
+
+ if (aNumberOfChannels > WebAudioUtils::MaxChannelCount) {
+ aRv.ThrowNotSupportedError(nsPrintfCString(
+ "Number of channels (%u) is out of range", aNumberOfChannels));
+ return;
+ }
+
+ if (!aLength || aLength > INT32_MAX) {
+ aRv.ThrowNotSupportedError(
+ nsPrintfCString("Length (%u) is out of range", aLength));
+ return;
+ }
+
+ mSharedChannels.mDuration = aLength;
+ mJSChannels.SetLength(aNumberOfChannels);
+ mozilla::HoldJSObjects(this);
+ AudioBufferMemoryTracker::RegisterAudioBuffer(this);
+}
+
+AudioBuffer::~AudioBuffer() {
+ AudioBufferMemoryTracker::UnregisterAudioBuffer(this);
+ ClearJSChannels();
+ mozilla::DropJSObjects(this);
+}
+
+/* static */
+already_AddRefed<AudioBuffer> AudioBuffer::Constructor(
+ const GlobalObject& aGlobal, const AudioBufferOptions& aOptions,
+ ErrorResult& aRv) {
+ if (!aOptions.mNumberOfChannels) {
+ aRv.ThrowNotSupportedError("Must have nonzero number of channels");
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+
+ return Create(window, aOptions.mNumberOfChannels, aOptions.mLength,
+ aOptions.mSampleRate, aRv);
+}
+
+void AudioBuffer::ClearJSChannels() { mJSChannels.Clear(); }
+
+void AudioBuffer::SetSharedChannels(
+ already_AddRefed<ThreadSharedFloatArrayBufferList> aBuffer) {
+ RefPtr<ThreadSharedFloatArrayBufferList> buffer = aBuffer;
+ uint32_t channelCount = buffer->GetChannels();
+ mSharedChannels.mChannelData.SetLength(channelCount);
+ for (uint32_t i = 0; i < channelCount; ++i) {
+ mSharedChannels.mChannelData[i] = buffer->GetData(i);
+ }
+ mSharedChannels.mBuffer = std::move(buffer);
+ mSharedChannels.mBufferFormat = AUDIO_FORMAT_FLOAT32;
+}
+
+/* static */
+already_AddRefed<AudioBuffer> AudioBuffer::Create(
+ nsPIDOMWindowInner* aWindow, uint32_t aNumberOfChannels, uint32_t aLength,
+ float aSampleRate,
+ already_AddRefed<ThreadSharedFloatArrayBufferList> aInitialContents,
+ ErrorResult& aRv) {
+ RefPtr<ThreadSharedFloatArrayBufferList> initialContents = aInitialContents;
+ RefPtr<AudioBuffer> buffer =
+ new AudioBuffer(aWindow, aNumberOfChannels, aLength, aSampleRate, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ if (initialContents) {
+ MOZ_ASSERT(initialContents->GetChannels() == aNumberOfChannels);
+ buffer->SetSharedChannels(initialContents.forget());
+ }
+
+ return buffer.forget();
+}
+
+/* static */
+already_AddRefed<AudioBuffer> AudioBuffer::Create(
+ nsPIDOMWindowInner* aWindow, float aSampleRate,
+ AudioChunk&& aInitialContents) {
+ AudioChunk initialContents = aInitialContents;
+ ErrorResult rv;
+ RefPtr<AudioBuffer> buffer =
+ new AudioBuffer(aWindow, initialContents.ChannelCount(),
+ initialContents.mDuration, aSampleRate, rv);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+ buffer->mSharedChannels = std::move(aInitialContents);
+
+ return buffer.forget();
+}
+
+JSObject* AudioBuffer::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return AudioBuffer_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+static void CopyChannelDataToFloat(const AudioChunk& aChunk, uint32_t aChannel,
+ uint32_t aSrcOffset, float* aOutput,
+ uint32_t aLength) {
+ MOZ_ASSERT(aChunk.mVolume == 1.0f);
+ if (aChunk.mBufferFormat == AUDIO_FORMAT_FLOAT32) {
+ mozilla::PodCopy(
+ aOutput, aChunk.ChannelData<float>()[aChannel] + aSrcOffset, aLength);
+ } else {
+ MOZ_ASSERT(aChunk.mBufferFormat == AUDIO_FORMAT_S16);
+ ConvertAudioSamples(aChunk.ChannelData<int16_t>()[aChannel] + aSrcOffset,
+ aOutput, aLength);
+ }
+}
+
+bool AudioBuffer::RestoreJSChannelData(JSContext* aJSContext) {
+ nsPIDOMWindowInner* global = GetParentObject();
+ if (!global || !global->AsGlobal()->HasJSGlobal()) {
+ return false;
+ }
+
+ JSAutoRealm ar(aJSContext, global->AsGlobal()->GetGlobalJSObject());
+
+ for (uint32_t i = 0; i < mJSChannels.Length(); ++i) {
+ if (mJSChannels[i]) {
+ // Already have data in JS array.
+ continue;
+ }
+
+ // The following code first zeroes the array and then copies our data
+ // into it. We could avoid this with additional JS APIs to construct
+ // an array (or ArrayBuffer) containing initial data.
+ JS::Rooted<JSObject*> array(aJSContext,
+ JS_NewFloat32Array(aJSContext, Length()));
+ if (!array) {
+ return false;
+ }
+ if (!mSharedChannels.IsNull()) {
+ // "4. Attach ArrayBuffers containing copies of the data to the
+ // AudioBuffer, to be returned by the next call to getChannelData."
+ JS::AutoCheckCannotGC nogc;
+ bool isShared;
+ float* jsData = JS_GetFloat32ArrayData(array, &isShared, nogc);
+ MOZ_ASSERT(!isShared); // Was created as unshared above
+ CopyChannelDataToFloat(mSharedChannels, i, 0, jsData, Length());
+ }
+ mJSChannels[i] = array;
+ }
+
+ mSharedChannels.SetNull(Length());
+
+ return true;
+}
+
+void AudioBuffer::CopyFromChannel(const Float32Array& aDestination,
+ uint32_t aChannelNumber,
+ uint32_t aBufferOffset, ErrorResult& aRv) {
+ if (aChannelNumber >= NumberOfChannels()) {
+ aRv.ThrowIndexSizeError(
+ nsPrintfCString("Channel number (%u) is out of range", aChannelNumber));
+ return;
+ }
+ uint32_t length = Length();
+ if (aBufferOffset >= length) {
+ return;
+ }
+ JS::AutoCheckCannotGC nogc;
+ MOZ_RELEASE_ASSERT(!JS_GetTypedArraySharedness(aDestination.Obj()));
+ auto calculateCount = [=](uint32_t aLength) -> uint32_t {
+ return std::min(length - aBufferOffset, aLength);
+ };
+
+ JSObject* channelArray = mJSChannels[aChannelNumber];
+ if (channelArray) {
+ if (JS_GetTypedArrayLength(channelArray) != length) {
+ // The array's buffer was detached.
+ return;
+ }
+ bool isShared = false;
+ const float* sourceData =
+ JS_GetFloat32ArrayData(channelArray, &isShared, nogc);
+ // The sourceData arrays should all have originated in
+ // RestoreJSChannelData, where they are created unshared.
+ MOZ_ASSERT(!isShared);
+ aDestination.ProcessData(
+ [&](const Span<float>& aData, JS::AutoCheckCannotGC&&) {
+ PodMove(aData.Elements(), sourceData + aBufferOffset,
+ calculateCount(aData.Length()));
+ });
+ return;
+ }
+
+ if (!mSharedChannels.IsNull()) {
+ aDestination.ProcessData([&](const Span<float>& aData,
+ JS::AutoCheckCannotGC&&) {
+ CopyChannelDataToFloat(mSharedChannels, aChannelNumber, aBufferOffset,
+ aData.Elements(), calculateCount(aData.Length()));
+ });
+ return;
+ }
+
+ aDestination.ProcessData(
+ [&](const Span<float>& aData, JS::AutoCheckCannotGC&&) {
+ PodZero(aData.Elements(), calculateCount(aData.Length()));
+ });
+}
+
+void AudioBuffer::CopyToChannel(JSContext* aJSContext,
+ const Float32Array& aSource,
+ uint32_t aChannelNumber, uint32_t aBufferOffset,
+ ErrorResult& aRv) {
+ if (aChannelNumber >= NumberOfChannels()) {
+ aRv.ThrowIndexSizeError(
+ nsPrintfCString("Channel number (%u) is out of range", aChannelNumber));
+ return;
+ }
+
+ if (!RestoreJSChannelData(aJSContext)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ JS::AutoCheckCannotGC nogc;
+ JSObject* channelArray = mJSChannels[aChannelNumber];
+ // This may differ from Length() if the buffer has been detached.
+ uint32_t length = JS_GetTypedArrayLength(channelArray);
+ if (aBufferOffset >= length) {
+ return;
+ }
+
+ int64_t offset = aBufferOffset;
+ aSource.ProcessData([&](const Span<float>& aData, JS::AutoCheckCannotGC&&) {
+ MOZ_ASSERT_IF(std::numeric_limits<decltype(aData.Length())>::max() >
+ std::numeric_limits<int64_t>::max(),
+ aData.Length() <= std::numeric_limits<int64_t>::max());
+ int64_t srcLength = int64_t(aData.Length());
+ size_t count = std::max(int64_t(0), std::min(length - offset, srcLength));
+ bool isShared = false;
+ float* channelData = JS_GetFloat32ArrayData(channelArray, &isShared, nogc);
+ // The channelData arrays should all have originated in
+ // RestoreJSChannelData, where they are created unshared.
+ MOZ_ASSERT(!isShared);
+ PodMove(channelData + aBufferOffset, aData.Elements(), count);
+ });
+}
+
+void AudioBuffer::GetChannelData(JSContext* aJSContext, uint32_t aChannel,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv) {
+ if (aChannel >= NumberOfChannels()) {
+ aRv.ThrowIndexSizeError(
+ nsPrintfCString("Channel number (%u) is out of range", aChannel));
+ return;
+ }
+
+ if (!RestoreJSChannelData(aJSContext)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ aRetval.set(mJSChannels[aChannel]);
+}
+
+already_AddRefed<ThreadSharedFloatArrayBufferList>
+AudioBuffer::StealJSArrayDataIntoSharedChannels(JSContext* aJSContext) {
+ nsPIDOMWindowInner* global = GetParentObject();
+ if (!global || !global->AsGlobal()->HasJSGlobal()) {
+ return nullptr;
+ }
+
+ JSAutoRealm ar(aJSContext, global->AsGlobal()->GetGlobalJSObject());
+
+ // "1. If any of the AudioBuffer's ArrayBuffer have been detached, abort
+ // these steps, and return a zero-length channel data buffers to the
+ // invoker."
+ for (uint32_t i = 0; i < mJSChannels.Length(); ++i) {
+ JSObject* channelArray = mJSChannels[i];
+ if (!channelArray || Length() != JS_GetTypedArrayLength(channelArray)) {
+ // Either empty buffer or one of the arrays' buffers was detached.
+ return nullptr;
+ }
+ }
+
+ // "2. Detach all ArrayBuffers for arrays previously returned by
+ // getChannelData on this AudioBuffer."
+ // "3. Retain the underlying data buffers from those ArrayBuffers and return
+ // references to them to the invoker."
+ RefPtr<ThreadSharedFloatArrayBufferList> result =
+ new ThreadSharedFloatArrayBufferList(mJSChannels.Length());
+ for (uint32_t i = 0; i < mJSChannels.Length(); ++i) {
+ JS::Rooted<JSObject*> arrayBufferView(aJSContext, mJSChannels[i]);
+ bool isSharedMemory;
+ JS::Rooted<JSObject*> arrayBuffer(
+ aJSContext, JS_GetArrayBufferViewBuffer(aJSContext, arrayBufferView,
+ &isSharedMemory));
+ // The channel data arrays should all have originated in
+ // RestoreJSChannelData, where they are created unshared.
+ MOZ_ASSERT(!isSharedMemory);
+ auto stolenData = arrayBuffer
+ ? static_cast<float*>(JS::StealArrayBufferContents(
+ aJSContext, arrayBuffer))
+ : nullptr;
+ if (stolenData) {
+ result->SetData(i, stolenData, js_free, stolenData);
+ } else {
+ NS_ASSERTION(i == 0, "some channels lost when contents not acquired");
+ return nullptr;
+ }
+ }
+
+ for (uint32_t i = 0; i < mJSChannels.Length(); ++i) {
+ mJSChannels[i] = nullptr;
+ }
+
+ return result.forget();
+}
+
+const AudioChunk& AudioBuffer::GetThreadSharedChannelsForRate(
+ JSContext* aJSContext) {
+ if (mSharedChannels.IsNull()) {
+ // mDuration is set in constructor
+ RefPtr<ThreadSharedFloatArrayBufferList> buffer =
+ StealJSArrayDataIntoSharedChannels(aJSContext);
+
+ if (buffer) {
+ SetSharedChannels(buffer.forget());
+ }
+ }
+
+ return mSharedChannels;
+}
+
+size_t AudioBuffer::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t amount = aMallocSizeOf(this);
+ amount += mJSChannels.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ amount += mSharedChannels.SizeOfExcludingThis(aMallocSizeOf, false);
+ return amount;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/AudioBuffer.h b/dom/media/webaudio/AudioBuffer.h
new file mode 100644
index 0000000000..e313c5c944
--- /dev/null
+++ b/dom/media/webaudio/AudioBuffer.h
@@ -0,0 +1,138 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef AudioBuffer_h_
+#define AudioBuffer_h_
+
+#include "AudioSegment.h"
+#include "nsWrapperCache.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/StaticMutex.h"
+#include "nsTArray.h"
+#include "js/TypeDecls.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/TypedArray.h"
+#include "nsPIDOMWindow.h"
+#include "nsIWeakReferenceUtils.h"
+
+namespace mozilla {
+
+class ErrorResult;
+class ThreadSharedFloatArrayBufferList;
+
+namespace dom {
+
+struct AudioBufferOptions;
+
+/**
+ * An AudioBuffer keeps its data either in the mJSChannels objects, which
+ * are Float32Arrays, or in mSharedChannels if the mJSChannels objects' buffers
+ * are detached.
+ */
+class AudioBuffer final : public nsWrapperCache {
+ public:
+ // If non-null, aInitialContents must have number of channels equal to
+ // aNumberOfChannels and their lengths must be at least aLength.
+ static already_AddRefed<AudioBuffer> Create(
+ nsPIDOMWindowInner* aWindow, uint32_t aNumberOfChannels, uint32_t aLength,
+ float aSampleRate,
+ already_AddRefed<ThreadSharedFloatArrayBufferList> aInitialContents,
+ ErrorResult& aRv);
+
+ static already_AddRefed<AudioBuffer> Create(nsPIDOMWindowInner* aWindow,
+ uint32_t aNumberOfChannels,
+ uint32_t aLength,
+ float aSampleRate,
+ ErrorResult& aRv) {
+ return Create(aWindow, aNumberOfChannels, aLength, aSampleRate, nullptr,
+ aRv);
+ }
+
+ // Non-unit AudioChunk::mVolume is not supported
+ static already_AddRefed<AudioBuffer> Create(nsPIDOMWindowInner* aWindow,
+ float aSampleRate,
+ AudioChunk&& aInitialContents);
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(AudioBuffer)
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(AudioBuffer)
+
+ static already_AddRefed<AudioBuffer> Constructor(
+ const GlobalObject& aGlobal, const AudioBufferOptions& aOptions,
+ ErrorResult& aRv);
+
+ nsPIDOMWindowInner* GetParentObject() const {
+ nsCOMPtr<nsPIDOMWindowInner> parentObject = do_QueryReferent(mOwnerWindow);
+ return parentObject;
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ float SampleRate() const { return mSampleRate; }
+
+ uint32_t Length() const { return mSharedChannels.mDuration; }
+
+ double Duration() const {
+ return Length() / static_cast<double>(mSampleRate);
+ }
+
+ uint32_t NumberOfChannels() const { return mJSChannels.Length(); }
+
+ /**
+ * If mSharedChannels is non-null, copies its contents to
+ * new Float32Arrays in mJSChannels. Returns a Float32Array.
+ */
+ void GetChannelData(JSContext* aJSContext, uint32_t aChannel,
+ JS::MutableHandle<JSObject*> aRetval, ErrorResult& aRv);
+
+ void CopyFromChannel(const Float32Array& aDestination,
+ uint32_t aChannelNumber, uint32_t aBufferOffset,
+ ErrorResult& aRv);
+ void CopyToChannel(JSContext* aJSContext, const Float32Array& aSource,
+ uint32_t aChannelNumber, uint32_t aBufferOffset,
+ ErrorResult& aRv);
+
+ /**
+ * Returns a reference to an AudioChunk containing the sample data.
+ * The AudioChunk can have a null buffer if there is no data.
+ */
+ const AudioChunk& GetThreadSharedChannelsForRate(JSContext* aContext);
+
+ protected:
+ AudioBuffer(nsPIDOMWindowInner* aWindow, uint32_t aNumberOfChannels,
+ uint32_t aLength, float aSampleRate, ErrorResult& aRv);
+ ~AudioBuffer();
+
+ void SetSharedChannels(
+ already_AddRefed<ThreadSharedFloatArrayBufferList> aBuffer);
+
+ bool RestoreJSChannelData(JSContext* aJSContext);
+
+ already_AddRefed<ThreadSharedFloatArrayBufferList>
+ StealJSArrayDataIntoSharedChannels(JSContext* aJSContext);
+
+ void ClearJSChannels();
+
+ // Float32Arrays
+ AutoTArray<JS::Heap<JSObject*>, 2> mJSChannels;
+ // mSharedChannels aggregates the data from mJSChannels. This is non-null
+ // if and only if the mJSChannels' buffers are detached, but its mDuration
+ // member keeps the buffer length regardless of whether the buffer is
+ // provided by mJSChannels or mSharedChannels.
+ AudioChunk mSharedChannels;
+
+ nsWeakPtr mOwnerWindow;
+ float mSampleRate;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webaudio/AudioBufferSourceNode.cpp b/dom/media/webaudio/AudioBufferSourceNode.cpp
new file mode 100644
index 0000000000..db34c49f48
--- /dev/null
+++ b/dom/media/webaudio/AudioBufferSourceNode.cpp
@@ -0,0 +1,853 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AudioBufferSourceNode.h"
+#include "nsDebug.h"
+#include "mozilla/dom/AudioBufferSourceNodeBinding.h"
+#include "mozilla/dom/AudioParam.h"
+#include "mozilla/FloatingPoint.h"
+#include "nsContentUtils.h"
+#include "nsMathUtils.h"
+#include "AlignmentUtils.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeTrack.h"
+#include "AudioDestinationNode.h"
+#include "AudioParamTimeline.h"
+#include <limits>
+#include <algorithm>
+#include "Tracing.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(AudioBufferSourceNode,
+ AudioScheduledSourceNode, mBuffer,
+ mPlaybackRate, mDetune)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AudioBufferSourceNode)
+NS_INTERFACE_MAP_END_INHERITING(AudioScheduledSourceNode)
+
+NS_IMPL_ADDREF_INHERITED(AudioBufferSourceNode, AudioScheduledSourceNode)
+NS_IMPL_RELEASE_INHERITED(AudioBufferSourceNode, AudioScheduledSourceNode)
+
+/**
+ * Media-thread playback engine for AudioBufferSourceNode.
+ * Nothing is played until a non-null buffer has been set (via
+ * AudioNodeTrack::SetBuffer) and a non-zero mBufferSampleRate has been set
+ * (via AudioNodeTrack::SetInt32Parameter)
+ */
+class AudioBufferSourceNodeEngine final : public AudioNodeEngine {
+ public:
+ AudioBufferSourceNodeEngine(AudioNode* aNode,
+ AudioDestinationNode* aDestination)
+ : AudioNodeEngine(aNode),
+ mStart(0.0),
+ mBeginProcessing(0),
+ mStop(TRACK_TIME_MAX),
+ mResampler(nullptr),
+ mRemainingResamplerTail(0),
+ mRemainingFrames(TRACK_TICKS_MAX),
+ mLoopStart(0),
+ mLoopEnd(0),
+ mBufferPosition(0),
+ mBufferSampleRate(0),
+ // mResamplerOutRate is initialized in UpdateResampler().
+ mChannels(0),
+ mDestination(aDestination->Track()),
+ mPlaybackRateTimeline(1.0f),
+ mDetuneTimeline(0.0f),
+ mLoop(false) {}
+
+ ~AudioBufferSourceNodeEngine() {
+ if (mResampler) {
+ speex_resampler_destroy(mResampler);
+ }
+ }
+
+ void SetSourceTrack(AudioNodeTrack* aSource) { mSource = aSource; }
+
+ void RecvTimelineEvent(uint32_t aIndex, AudioParamEvent& aEvent) override {
+ MOZ_ASSERT(mDestination);
+ aEvent.ConvertToTicks(mDestination);
+ mRecomputeOutRate = true;
+
+ switch (aIndex) {
+ case AudioBufferSourceNode::PLAYBACKRATE:
+ mPlaybackRateTimeline.InsertEvent<int64_t>(aEvent);
+ break;
+ case AudioBufferSourceNode::DETUNE:
+ mDetuneTimeline.InsertEvent<int64_t>(aEvent);
+ break;
+ default:
+ NS_ERROR("Bad AudioBufferSourceNodeEngine TimelineParameter");
+ }
+ }
+ void SetTrackTimeParameter(uint32_t aIndex, TrackTime aParam) override {
+ switch (aIndex) {
+ case AudioBufferSourceNode::STOP:
+ mStop = aParam;
+ break;
+ default:
+ NS_ERROR("Bad AudioBufferSourceNodeEngine TrackTimeParameter");
+ }
+ }
+ void SetDoubleParameter(uint32_t aIndex, double aParam) override {
+ switch (aIndex) {
+ case AudioBufferSourceNode::START:
+ MOZ_ASSERT(!mStart, "Another START?");
+ mStart = aParam * mDestination->mSampleRate;
+ // Round to nearest
+ mBeginProcessing = llround(mStart);
+ break;
+ case AudioBufferSourceNode::DURATION:
+ MOZ_ASSERT(aParam >= 0);
+ mRemainingFrames = llround(aParam * mBufferSampleRate);
+ break;
+ default:
+ NS_ERROR("Bad AudioBufferSourceNodeEngine double parameter.");
+ };
+ }
+ void SetInt32Parameter(uint32_t aIndex, int32_t aParam) override {
+ switch (aIndex) {
+ case AudioBufferSourceNode::SAMPLE_RATE:
+ MOZ_ASSERT(aParam > 0);
+ MOZ_ASSERT(mRecomputeOutRate);
+ mBufferSampleRate = aParam;
+ mSource->SetActive();
+ break;
+ case AudioBufferSourceNode::BUFFERSTART:
+ MOZ_ASSERT(aParam >= 0);
+ if (mBufferPosition == 0) {
+ mBufferPosition = aParam;
+ }
+ break;
+ case AudioBufferSourceNode::LOOP:
+ mLoop = !!aParam;
+ break;
+ case AudioBufferSourceNode::LOOPSTART:
+ MOZ_ASSERT(aParam >= 0);
+ mLoopStart = aParam;
+ break;
+ case AudioBufferSourceNode::LOOPEND:
+ MOZ_ASSERT(aParam >= 0);
+ mLoopEnd = aParam;
+ break;
+ default:
+ NS_ERROR("Bad AudioBufferSourceNodeEngine Int32Parameter");
+ }
+ }
+ void SetBuffer(AudioChunk&& aBuffer) override { mBuffer = aBuffer; }
+
+ bool BegunResampling() { return mBeginProcessing == -TRACK_TIME_MAX; }
+
+ void UpdateResampler(int32_t aOutRate, uint32_t aChannels) {
+ if (mResampler &&
+ (aChannels != mChannels ||
+ // If the resampler has begun, then it will have moved
+ // mBufferPosition to after the samples it has read, but it hasn't
+ // output its buffered samples. Keep using the resampler, even if
+ // the rates now match, so that this latent segment is output.
+ (aOutRate == mBufferSampleRate && !BegunResampling()))) {
+ speex_resampler_destroy(mResampler);
+ mResampler = nullptr;
+ mRemainingResamplerTail = 0;
+ mBeginProcessing = llround(mStart);
+ }
+
+ if (aChannels == 0 || (aOutRate == mBufferSampleRate && !mResampler)) {
+ mResamplerOutRate = aOutRate;
+ return;
+ }
+
+ if (!mResampler) {
+ mChannels = aChannels;
+ mResampler = speex_resampler_init(mChannels, mBufferSampleRate, aOutRate,
+ SPEEX_RESAMPLER_QUALITY_MIN, nullptr);
+ } else {
+ if (mResamplerOutRate == aOutRate) {
+ return;
+ }
+ if (speex_resampler_set_rate(mResampler, mBufferSampleRate, aOutRate) !=
+ RESAMPLER_ERR_SUCCESS) {
+ NS_ASSERTION(false, "speex_resampler_set_rate failed");
+ return;
+ }
+ }
+
+ mResamplerOutRate = aOutRate;
+
+ if (!BegunResampling()) {
+ // Low pass filter effects from the resampler mean that samples before
+ // the start time are influenced by resampling the buffer. The input
+ // latency indicates half the filter width.
+ int64_t inputLatency = speex_resampler_get_input_latency(mResampler);
+ uint32_t ratioNum, ratioDen;
+ speex_resampler_get_ratio(mResampler, &ratioNum, &ratioDen);
+ // The output subsample resolution supported in aligning the resampler
+ // is ratioNum. First round the start time to the nearest subsample.
+ int64_t subsample = llround(mStart * ratioNum);
+ // Now include the leading effects of the filter, and round *up* to the
+ // next whole tick, because there is no effect on samples outside the
+ // filter width.
+ mBeginProcessing =
+ (subsample - inputLatency * ratioDen + ratioNum - 1) / ratioNum;
+ }
+ }
+
+ // Borrow a full buffer of size WEBAUDIO_BLOCK_SIZE from the source buffer
+ // at offset aSourceOffset. This avoids copying memory.
+ void BorrowFromInputBuffer(AudioBlock* aOutput, uint32_t aChannels) {
+ aOutput->SetBuffer(mBuffer.mBuffer);
+ aOutput->mChannelData.SetLength(aChannels);
+ for (uint32_t i = 0; i < aChannels; ++i) {
+ aOutput->mChannelData[i] =
+ mBuffer.ChannelData<float>()[i] + mBufferPosition;
+ }
+ aOutput->mVolume = mBuffer.mVolume;
+ aOutput->mBufferFormat = AUDIO_FORMAT_FLOAT32;
+ }
+
+ // Copy aNumberOfFrames frames from the source buffer at offset aSourceOffset
+ // and put it at offset aBufferOffset in the destination buffer.
+ template <typename T>
+ void CopyFromInputBuffer(AudioBlock* aOutput, uint32_t aChannels,
+ uintptr_t aOffsetWithinBlock,
+ uint32_t aNumberOfFrames) {
+ MOZ_ASSERT(mBuffer.mVolume == 1.0f);
+ for (uint32_t i = 0; i < aChannels; ++i) {
+ float* baseChannelData = aOutput->ChannelFloatsForWrite(i);
+ ConvertAudioSamples(mBuffer.ChannelData<T>()[i] + mBufferPosition,
+ baseChannelData + aOffsetWithinBlock,
+ aNumberOfFrames);
+ }
+ }
+
+ // Resamples input data to an output buffer, according to |mBufferSampleRate|
+ // and the playbackRate/detune. The number of frames consumed/produced depends
+ // on the amount of space remaining in both the input and output buffer, and
+ // the playback rate (that is, the ratio between the output samplerate and the
+ // input samplerate).
+ void CopyFromInputBufferWithResampling(AudioBlock* aOutput,
+ uint32_t aChannels,
+ uint32_t* aOffsetWithinBlock,
+ uint32_t aAvailableInOutput,
+ TrackTime* aCurrentPosition,
+ uint32_t aBufferMax) {
+ if (*aOffsetWithinBlock == 0) {
+ aOutput->AllocateChannels(aChannels);
+ }
+ SpeexResamplerState* resampler = mResampler;
+ MOZ_ASSERT(aChannels > 0);
+
+ if (mBufferPosition < aBufferMax) {
+ uint32_t availableInInputBuffer = aBufferMax - mBufferPosition;
+ uint32_t ratioNum, ratioDen;
+ speex_resampler_get_ratio(resampler, &ratioNum, &ratioDen);
+ // Limit the number of input samples copied and possibly
+ // format-converted for resampling by estimating how many will be used.
+ // This may be a little small if still filling the resampler with
+ // initial data, but we'll get called again and it will work out.
+ uint32_t inputLimit = aAvailableInOutput * ratioNum / ratioDen + 10;
+ if (!BegunResampling()) {
+ // First time the resampler is used.
+ uint32_t inputLatency = speex_resampler_get_input_latency(resampler);
+ inputLimit += inputLatency;
+ // If starting after mStart, then play from the beginning of the
+ // buffer, but correct for input latency. If starting before mStart,
+ // then align the resampler so that the time corresponding to the
+ // first input sample is mStart.
+ int64_t skipFracNum = static_cast<int64_t>(inputLatency) * ratioDen;
+ double leadTicks = mStart - *aCurrentPosition;
+ if (leadTicks > 0.0) {
+ // Round to nearest output subsample supported by the resampler at
+ // these rates.
+ int64_t leadSubsamples = llround(leadTicks * ratioNum);
+ MOZ_ASSERT(leadSubsamples <= skipFracNum,
+ "mBeginProcessing is wrong?");
+ skipFracNum -= leadSubsamples;
+ }
+ speex_resampler_set_skip_frac_num(
+ resampler, std::min<int64_t>(skipFracNum, UINT32_MAX));
+
+ mBeginProcessing = -TRACK_TIME_MAX;
+ }
+ inputLimit = std::min(inputLimit, availableInInputBuffer);
+
+ MOZ_ASSERT(mBuffer.mVolume == 1.0f);
+ for (uint32_t i = 0; true;) {
+ uint32_t inSamples = inputLimit;
+
+ uint32_t outSamples = aAvailableInOutput;
+ float* outputData =
+ aOutput->ChannelFloatsForWrite(i) + *aOffsetWithinBlock;
+
+ if (mBuffer.mBufferFormat == AUDIO_FORMAT_FLOAT32) {
+ const float* inputData =
+ mBuffer.ChannelData<float>()[i] + mBufferPosition;
+ WebAudioUtils::SpeexResamplerProcess(
+ resampler, i, inputData, &inSamples, outputData, &outSamples);
+ } else {
+ MOZ_ASSERT(mBuffer.mBufferFormat == AUDIO_FORMAT_S16);
+ const int16_t* inputData =
+ mBuffer.ChannelData<int16_t>()[i] + mBufferPosition;
+ WebAudioUtils::SpeexResamplerProcess(
+ resampler, i, inputData, &inSamples, outputData, &outSamples);
+ }
+ if (++i == aChannels) {
+ mBufferPosition += inSamples;
+ mRemainingFrames -= inSamples;
+ MOZ_ASSERT(mBufferPosition <= mBuffer.GetDuration());
+ MOZ_ASSERT(mRemainingFrames >= 0);
+ *aOffsetWithinBlock += outSamples;
+ *aCurrentPosition += outSamples;
+ if ((!mLoop && inSamples == availableInInputBuffer) ||
+ mRemainingFrames == 0) {
+ // We'll feed in enough zeros to empty out the resampler's memory.
+ // This handles the output latency as well as capturing the low
+ // pass effects of the resample filter.
+ mRemainingResamplerTail =
+ 2 * speex_resampler_get_input_latency(resampler) - 1;
+ }
+ return;
+ }
+ }
+ } else {
+ for (uint32_t i = 0; true;) {
+ uint32_t inSamples = mRemainingResamplerTail;
+ uint32_t outSamples = aAvailableInOutput;
+ float* outputData =
+ aOutput->ChannelFloatsForWrite(i) + *aOffsetWithinBlock;
+
+ // AudioDataValue* for aIn selects the function that does not try to
+ // copy and format-convert input data.
+ WebAudioUtils::SpeexResamplerProcess(
+ resampler, i, static_cast<AudioDataValue*>(nullptr), &inSamples,
+ outputData, &outSamples);
+ if (++i == aChannels) {
+ MOZ_ASSERT(inSamples <= mRemainingResamplerTail);
+ mRemainingResamplerTail -= inSamples;
+ *aOffsetWithinBlock += outSamples;
+ *aCurrentPosition += outSamples;
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Fill aOutput with as many zero frames as we can, and advance
+ * aOffsetWithinBlock and aCurrentPosition based on how many frames we write.
+ * This will never advance aOffsetWithinBlock past WEBAUDIO_BLOCK_SIZE or
+ * aCurrentPosition past aMaxPos. This function knows when it needs to
+ * allocate the output buffer, and also optimizes the case where it can avoid
+ * memory allocations.
+ */
+ void FillWithZeroes(AudioBlock* aOutput, uint32_t aChannels,
+ uint32_t* aOffsetWithinBlock, TrackTime* aCurrentPosition,
+ TrackTime aMaxPos) {
+ MOZ_ASSERT(*aCurrentPosition < aMaxPos);
+ uint32_t numFrames = std::min<TrackTime>(
+ WEBAUDIO_BLOCK_SIZE - *aOffsetWithinBlock, aMaxPos - *aCurrentPosition);
+ if (numFrames == WEBAUDIO_BLOCK_SIZE || !aChannels) {
+ aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
+ } else {
+ if (*aOffsetWithinBlock == 0) {
+ aOutput->AllocateChannels(aChannels);
+ }
+ WriteZeroesToAudioBlock(aOutput, *aOffsetWithinBlock, numFrames);
+ }
+ *aOffsetWithinBlock += numFrames;
+ *aCurrentPosition += numFrames;
+ }
+
+ /**
+ * Copy as many frames as possible from the source buffer to aOutput, and
+ * advance aOffsetWithinBlock and aCurrentPosition based on how many frames
+ * we write. This will never advance aOffsetWithinBlock past
+ * WEBAUDIO_BLOCK_SIZE, or aCurrentPosition past mStop. It takes data from
+ * the buffer at aBufferOffset, and never takes more data than aBufferMax.
+ * This function knows when it needs to allocate the output buffer, and also
+ * optimizes the case where it can avoid memory allocations.
+ */
+ void CopyFromBuffer(AudioBlock* aOutput, uint32_t aChannels,
+ uint32_t* aOffsetWithinBlock, TrackTime* aCurrentPosition,
+ uint32_t aBufferMax) {
+ MOZ_ASSERT(*aCurrentPosition < mStop);
+ uint32_t availableInOutput = std::min<TrackTime>(
+ WEBAUDIO_BLOCK_SIZE - *aOffsetWithinBlock, mStop - *aCurrentPosition);
+ if (mResampler) {
+ CopyFromInputBufferWithResampling(aOutput, aChannels, aOffsetWithinBlock,
+ availableInOutput, aCurrentPosition,
+ aBufferMax);
+ return;
+ }
+
+ if (aChannels == 0) {
+ aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
+ // There is no attempt here to limit advance so that mBufferPosition is
+ // limited to aBufferMax. The only observable affect of skipping the
+ // check would be in the precise timing of the ended event if the loop
+ // attribute is reset after playback has looped.
+ *aOffsetWithinBlock += availableInOutput;
+ *aCurrentPosition += availableInOutput;
+ // Rounding at the start and end of the period means that fractional
+ // increments essentially accumulate if outRate remains constant. If
+ // outRate is varying, then accumulation happens on average but not
+ // precisely.
+ TrackTicks start =
+ *aCurrentPosition * mBufferSampleRate / mResamplerOutRate;
+ TrackTicks end = (*aCurrentPosition + availableInOutput) *
+ mBufferSampleRate / mResamplerOutRate;
+ mBufferPosition += end - start;
+ return;
+ }
+
+ uint32_t numFrames =
+ std::min(aBufferMax - mBufferPosition, availableInOutput);
+
+ bool shouldBorrow = false;
+ if (numFrames == WEBAUDIO_BLOCK_SIZE &&
+ mBuffer.mBufferFormat == AUDIO_FORMAT_FLOAT32) {
+ shouldBorrow = true;
+ for (uint32_t i = 0; i < aChannels; ++i) {
+ if (!IS_ALIGNED16(mBuffer.ChannelData<float>()[i] + mBufferPosition)) {
+ shouldBorrow = false;
+ break;
+ }
+ }
+ }
+ MOZ_ASSERT(mBufferPosition < aBufferMax);
+ if (shouldBorrow) {
+ BorrowFromInputBuffer(aOutput, aChannels);
+ } else {
+ if (*aOffsetWithinBlock == 0) {
+ aOutput->AllocateChannels(aChannels);
+ }
+ if (mBuffer.mBufferFormat == AUDIO_FORMAT_FLOAT32) {
+ CopyFromInputBuffer<float>(aOutput, aChannels, *aOffsetWithinBlock,
+ numFrames);
+ } else {
+ MOZ_ASSERT(mBuffer.mBufferFormat == AUDIO_FORMAT_S16);
+ CopyFromInputBuffer<int16_t>(aOutput, aChannels, *aOffsetWithinBlock,
+ numFrames);
+ }
+ }
+ *aOffsetWithinBlock += numFrames;
+ *aCurrentPosition += numFrames;
+ mBufferPosition += numFrames;
+ mRemainingFrames -= numFrames;
+ }
+
+ int32_t ComputeFinalOutSampleRate(float aPlaybackRate, float aDetune) {
+ float computedPlaybackRate = aPlaybackRate * fdlibm_exp2f(aDetune / 1200.f);
+ // Make sure the playback rate is something our resampler can work with.
+ int32_t rate = WebAudioUtils::TruncateFloatToInt<int32_t>(
+ mSource->mSampleRate / computedPlaybackRate);
+ return rate ? rate : mBufferSampleRate;
+ }
+
+ void UpdateSampleRateIfNeeded(uint32_t aChannels, TrackTime aTrackPosition) {
+ bool simplePlaybackRate = mPlaybackRateTimeline.HasSimpleValue();
+ bool simpleDetune = mDetuneTimeline.HasSimpleValue();
+
+ if (simplePlaybackRate && simpleDetune && !mRecomputeOutRate) {
+ return; // skipping the slow exp2f() for the detune
+ }
+ mRecomputeOutRate = false;
+
+ float playbackRate;
+ float detune;
+ if (simplePlaybackRate) {
+ playbackRate = mPlaybackRateTimeline.GetValue();
+ } else {
+ playbackRate =
+ mPlaybackRateTimeline.GetComplexValueAtTime(aTrackPosition);
+ }
+ if (simpleDetune) {
+ detune = mDetuneTimeline.GetValue();
+ } else {
+ detune = mDetuneTimeline.GetComplexValueAtTime(aTrackPosition);
+ }
+ if (playbackRate <= 0 || std::isnan(playbackRate)) {
+ playbackRate = 1.0f;
+ }
+
+ int32_t outRate = ComputeFinalOutSampleRate(playbackRate, detune);
+ UpdateResampler(outRate, aChannels);
+ }
+
+ void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
+ const AudioBlock& aInput, AudioBlock* aOutput,
+ bool* aFinished) override {
+ TRACE("AudioBufferSourceNodeEngine::ProcessBlock");
+ if (mBufferSampleRate == 0) {
+ // start() has not yet been called or no buffer has yet been set
+ aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
+ return;
+ }
+
+ TrackTime streamPosition = mDestination->GraphTimeToTrackTime(aFrom);
+ uint32_t channels = mBuffer.ChannelCount();
+
+ UpdateSampleRateIfNeeded(channels, streamPosition);
+
+ uint32_t written = 0;
+ while (true) {
+ if ((mStop != TRACK_TIME_MAX && streamPosition >= mStop) ||
+ (!mRemainingResamplerTail &&
+ ((mBufferPosition >= mBuffer.GetDuration() && !mLoop) ||
+ mRemainingFrames <= 0))) {
+ if (written != WEBAUDIO_BLOCK_SIZE) {
+ FillWithZeroes(aOutput, channels, &written, &streamPosition,
+ TRACK_TIME_MAX);
+ }
+ *aFinished = true;
+ break;
+ }
+ if (written == WEBAUDIO_BLOCK_SIZE) {
+ break;
+ }
+ if (streamPosition < mBeginProcessing) {
+ FillWithZeroes(aOutput, channels, &written, &streamPosition,
+ mBeginProcessing);
+ continue;
+ }
+
+ TrackTicks bufferLeft;
+ if (mLoop) {
+ // mLoopEnd can become less than mBufferPosition when a LOOPEND engine
+ // parameter is received after "loopend" is changed on the node or a
+ // new buffer with lower samplerate is set.
+ if (mBufferPosition >= mLoopEnd) {
+ mBufferPosition = mLoopStart;
+ }
+ bufferLeft =
+ std::min<TrackTicks>(mRemainingFrames, mLoopEnd - mBufferPosition);
+ } else {
+ bufferLeft =
+ std::min(mRemainingFrames, mBuffer.GetDuration() - mBufferPosition);
+ }
+
+ CopyFromBuffer(aOutput, channels, &written, &streamPosition,
+ bufferLeft + mBufferPosition);
+ }
+ }
+
+ bool IsActive() const override {
+ // Whether buffer has been set and start() has been called.
+ return mBufferSampleRate != 0;
+ }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override {
+ // Not owned:
+ // - mBuffer - shared w/ AudioNode
+ // - mPlaybackRateTimeline - shared w/ AudioNode
+ // - mDetuneTimeline - shared w/ AudioNode
+
+ size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+
+ // NB: We need to modify speex if we want the full memory picture, internal
+ // fields that need measuring noted below.
+ // - mResampler->mem
+ // - mResampler->sinc_table
+ // - mResampler->last_sample
+ // - mResampler->magic_samples
+ // - mResampler->samp_frac_num
+ amount += aMallocSizeOf(mResampler);
+
+ return amount;
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ double mStart; // including the fractional position between ticks
+ // Low pass filter effects from the resampler mean that samples before the
+ // start time are influenced by resampling the buffer. mBeginProcessing
+ // includes the extent of this filter. The special value of -TRACK_TIME_MAX
+ // indicates that the resampler has begun processing.
+ TrackTime mBeginProcessing;
+ TrackTime mStop;
+ AudioChunk mBuffer;
+ SpeexResamplerState* mResampler;
+ // mRemainingResamplerTail, like mBufferPosition
+ // is measured in input buffer samples.
+ uint32_t mRemainingResamplerTail;
+ TrackTicks mRemainingFrames;
+ uint32_t mLoopStart;
+ uint32_t mLoopEnd;
+ uint32_t mBufferPosition;
+ int32_t mBufferSampleRate;
+ int32_t mResamplerOutRate;
+ uint32_t mChannels;
+ RefPtr<AudioNodeTrack> mDestination;
+
+ // mSource deletes the engine in its destructor.
+ AudioNodeTrack* MOZ_NON_OWNING_REF mSource;
+ AudioParamTimeline mPlaybackRateTimeline;
+ AudioParamTimeline mDetuneTimeline;
+ bool mLoop;
+ bool mRecomputeOutRate = true;
+};
+
+AudioBufferSourceNode::AudioBufferSourceNode(AudioContext* aContext)
+ : AudioScheduledSourceNode(aContext, 2, ChannelCountMode::Max,
+ ChannelInterpretation::Speakers),
+ mLoopStart(0.0),
+ mLoopEnd(0.0),
+ // mOffset and mDuration are initialized in Start().
+ mLoop(false),
+ mStartCalled(false),
+ mBufferSet(false) {
+ mPlaybackRate = CreateAudioParam(PLAYBACKRATE, u"playbackRate"_ns, 1.0f);
+ mDetune = CreateAudioParam(DETUNE, u"detune"_ns, 0.0f);
+ AudioBufferSourceNodeEngine* engine =
+ new AudioBufferSourceNodeEngine(this, aContext->Destination());
+ mTrack = AudioNodeTrack::Create(aContext, engine,
+ AudioNodeTrack::NEED_MAIN_THREAD_ENDED,
+ aContext->Graph());
+ engine->SetSourceTrack(mTrack);
+ mTrack->AddMainThreadListener(this);
+}
+
+/* static */
+already_AddRefed<AudioBufferSourceNode> AudioBufferSourceNode::Create(
+ JSContext* aCx, AudioContext& aAudioContext,
+ const AudioBufferSourceOptions& aOptions) {
+ RefPtr<AudioBufferSourceNode> audioNode =
+ new AudioBufferSourceNode(&aAudioContext);
+
+ if (aOptions.mBuffer.WasPassed()) {
+ ErrorResult ignored;
+ MOZ_ASSERT(aCx);
+ audioNode->SetBuffer(aCx, aOptions.mBuffer.Value(), ignored);
+ }
+
+ audioNode->Detune()->SetInitialValue(aOptions.mDetune);
+ audioNode->SetLoop(aOptions.mLoop);
+ audioNode->SetLoopEnd(aOptions.mLoopEnd);
+ audioNode->SetLoopStart(aOptions.mLoopStart);
+ audioNode->PlaybackRate()->SetInitialValue(aOptions.mPlaybackRate);
+
+ return audioNode.forget();
+}
+void AudioBufferSourceNode::DestroyMediaTrack() {
+ bool hadTrack = mTrack;
+ if (hadTrack) {
+ mTrack->RemoveMainThreadListener(this);
+ }
+ AudioNode::DestroyMediaTrack();
+}
+
+size_t AudioBufferSourceNode::SizeOfExcludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+
+ /* mBuffer can be shared and is accounted for separately. */
+
+ amount += mPlaybackRate->SizeOfIncludingThis(aMallocSizeOf);
+ amount += mDetune->SizeOfIncludingThis(aMallocSizeOf);
+ return amount;
+}
+
+size_t AudioBufferSourceNode::SizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+JSObject* AudioBufferSourceNode::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return AudioBufferSourceNode_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void AudioBufferSourceNode::Start(double aWhen, double aOffset,
+ const Optional<double>& aDuration,
+ ErrorResult& aRv) {
+ if (!WebAudioUtils::IsTimeValid(aWhen)) {
+ aRv.ThrowRangeError<MSG_VALUE_OUT_OF_RANGE>("start time");
+ return;
+ }
+ if (aOffset < 0) {
+ aRv.ThrowRangeError<MSG_VALUE_OUT_OF_RANGE>("offset");
+ return;
+ }
+ if (aDuration.WasPassed() && !WebAudioUtils::IsTimeValid(aDuration.Value())) {
+ aRv.ThrowRangeError<MSG_VALUE_OUT_OF_RANGE>("duration");
+ return;
+ }
+
+ if (mStartCalled) {
+ aRv.ThrowInvalidStateError(
+ "Start has already been called on this AudioBufferSourceNode.");
+ return;
+ }
+ mStartCalled = true;
+
+ AudioNodeTrack* ns = mTrack;
+ if (!ns) {
+ // Nothing to play, or we're already dead for some reason
+ return;
+ }
+
+ // Remember our arguments so that we can use them when we get a new buffer.
+ mOffset = aOffset;
+ mDuration = aDuration.WasPassed() ? aDuration.Value()
+ : std::numeric_limits<double>::min();
+
+ WEB_AUDIO_API_LOG("%f: %s %u Start(%f, %g, %g)", Context()->CurrentTime(),
+ NodeType(), Id(), aWhen, aOffset, mDuration);
+
+ // We can't send these parameters without a buffer because we don't know the
+ // buffer's sample rate or length.
+ if (mBuffer) {
+ SendOffsetAndDurationParametersToTrack(ns);
+ }
+
+ // Don't set parameter unnecessarily
+ if (aWhen > 0.0) {
+ ns->SetDoubleParameter(START, aWhen);
+ }
+
+ Context()->StartBlockedAudioContextIfAllowed();
+}
+
+void AudioBufferSourceNode::Start(double aWhen, ErrorResult& aRv) {
+ Start(aWhen, 0 /* offset */, Optional<double>(), aRv);
+}
+
+void AudioBufferSourceNode::SendBufferParameterToTrack(JSContext* aCx) {
+ AudioNodeTrack* ns = mTrack;
+ if (!ns) {
+ return;
+ }
+
+ if (mBuffer) {
+ AudioChunk data = mBuffer->GetThreadSharedChannelsForRate(aCx);
+ ns->SetBuffer(std::move(data));
+
+ if (mStartCalled) {
+ SendOffsetAndDurationParametersToTrack(ns);
+ }
+ } else {
+ ns->SetBuffer(AudioChunk());
+
+ MarkInactive();
+ }
+}
+
+void AudioBufferSourceNode::SendOffsetAndDurationParametersToTrack(
+ AudioNodeTrack* aTrack) {
+ NS_ASSERTION(
+ mBuffer && mStartCalled,
+ "Only call this when we have a buffer and start() has been called");
+
+ float rate = mBuffer->SampleRate();
+ aTrack->SetInt32Parameter(SAMPLE_RATE, rate);
+
+ int32_t offsetSamples = std::max(0, NS_lround(mOffset * rate));
+
+ // Don't set parameter unnecessarily
+ if (offsetSamples > 0) {
+ aTrack->SetInt32Parameter(BUFFERSTART, offsetSamples);
+ }
+
+ if (mDuration != std::numeric_limits<double>::min()) {
+ MOZ_ASSERT(mDuration >= 0.0); // provided by Start()
+ MOZ_ASSERT(rate >= 0.0f); // provided by AudioBuffer::Create()
+ aTrack->SetDoubleParameter(DURATION, mDuration);
+ }
+ MarkActive();
+}
+
+void AudioBufferSourceNode::Stop(double aWhen, ErrorResult& aRv) {
+ if (!WebAudioUtils::IsTimeValid(aWhen)) {
+ aRv.ThrowRangeError<MSG_VALUE_OUT_OF_RANGE>("stop time");
+ return;
+ }
+
+ if (!mStartCalled) {
+ aRv.ThrowInvalidStateError(
+ "Start has not been called on this AudioBufferSourceNode.");
+ return;
+ }
+
+ WEB_AUDIO_API_LOG("%f: %s %u Stop(%f)", Context()->CurrentTime(), NodeType(),
+ Id(), aWhen);
+
+ AudioNodeTrack* ns = mTrack;
+ if (!ns || !Context()) {
+ // We've already stopped and had our track shut down
+ return;
+ }
+
+ ns->SetTrackTimeParameter(STOP, Context(), std::max(0.0, aWhen));
+}
+
+void AudioBufferSourceNode::NotifyMainThreadTrackEnded() {
+ MOZ_ASSERT(mTrack->IsEnded());
+
+ class EndedEventDispatcher final : public Runnable {
+ public:
+ explicit EndedEventDispatcher(AudioBufferSourceNode* aNode)
+ : mozilla::Runnable("EndedEventDispatcher"), mNode(aNode) {}
+ NS_IMETHOD Run() override {
+ // If it's not safe to run scripts right now, schedule this to run later
+ if (!nsContentUtils::IsSafeToRunScript()) {
+ nsContentUtils::AddScriptRunner(this);
+ return NS_OK;
+ }
+
+ mNode->DispatchTrustedEvent(u"ended"_ns);
+ // Release track resources.
+ mNode->DestroyMediaTrack();
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<AudioBufferSourceNode> mNode;
+ };
+
+ Context()->Dispatch(do_AddRef(new EndedEventDispatcher(this)));
+
+ // Drop the playing reference
+ // Warning: The below line might delete this.
+ MarkInactive();
+}
+
+void AudioBufferSourceNode::SendLoopParametersToTrack() {
+ if (!mTrack) {
+ return;
+ }
+ // Don't compute and set the loop parameters unnecessarily
+ if (mLoop && mBuffer) {
+ float rate = mBuffer->SampleRate();
+ double length = (double(mBuffer->Length()) / mBuffer->SampleRate());
+ double actualLoopStart, actualLoopEnd;
+ if (mLoopStart >= 0.0 && mLoopEnd > 0.0 && mLoopStart < mLoopEnd) {
+ MOZ_ASSERT(mLoopStart != 0.0 || mLoopEnd != 0.0);
+ actualLoopStart = (mLoopStart > length) ? 0.0 : mLoopStart;
+ actualLoopEnd = std::min(mLoopEnd, length);
+ } else {
+ actualLoopStart = 0.0;
+ actualLoopEnd = length;
+ }
+ int32_t loopStartTicks = NS_lround(actualLoopStart * rate);
+ int32_t loopEndTicks = NS_lround(actualLoopEnd * rate);
+ if (loopStartTicks < loopEndTicks) {
+ SendInt32ParameterToTrack(LOOPSTART, loopStartTicks);
+ SendInt32ParameterToTrack(LOOPEND, loopEndTicks);
+ SendInt32ParameterToTrack(LOOP, 1);
+ } else {
+ // Be explicit about looping not happening if the offsets make
+ // looping impossible.
+ SendInt32ParameterToTrack(LOOP, 0);
+ }
+ } else {
+ SendInt32ParameterToTrack(LOOP, 0);
+ }
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/AudioBufferSourceNode.h b/dom/media/webaudio/AudioBufferSourceNode.h
new file mode 100644
index 0000000000..9a3861555c
--- /dev/null
+++ b/dom/media/webaudio/AudioBufferSourceNode.h
@@ -0,0 +1,129 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef AudioBufferSourceNode_h_
+#define AudioBufferSourceNode_h_
+
+#include "AudioScheduledSourceNode.h"
+#include "AudioBuffer.h"
+
+namespace mozilla::dom {
+
+struct AudioBufferSourceOptions;
+class AudioParam;
+
+class AudioBufferSourceNode final : public AudioScheduledSourceNode,
+ public MainThreadMediaTrackListener {
+ public:
+ static already_AddRefed<AudioBufferSourceNode> Create(
+ JSContext* aCx, AudioContext& aAudioContext,
+ const AudioBufferSourceOptions& aOptions);
+
+ void DestroyMediaTrack() override;
+
+ uint16_t NumberOfInputs() const final { return 0; }
+ AudioBufferSourceNode* AsAudioBufferSourceNode() override { return this; }
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AudioBufferSourceNode,
+ AudioScheduledSourceNode)
+
+ static already_AddRefed<AudioBufferSourceNode> Constructor(
+ const GlobalObject& aGlobal, AudioContext& aAudioContext,
+ const AudioBufferSourceOptions& aOptions) {
+ return Create(aGlobal.Context(), aAudioContext, aOptions);
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void Start(double aWhen, double aOffset, const Optional<double>& aDuration,
+ ErrorResult& aRv);
+
+ void Start(double aWhen, ErrorResult& aRv) override;
+ void Stop(double aWhen, ErrorResult& aRv) override;
+
+ AudioBuffer* GetBuffer(JSContext* aCx) const { return mBuffer; }
+ void SetBuffer(JSContext* aCx, AudioBuffer* aBuffer, ErrorResult& aRv) {
+ if (aBuffer && mBufferSet) {
+ aRv.ThrowInvalidStateError(
+ "Cannot set the buffer attribute of an AudioBufferSourceNode "
+ "with an AudioBuffer more than once");
+ return;
+ }
+ if (aBuffer) {
+ mBufferSet = true;
+ }
+ mBuffer = aBuffer;
+ SendBufferParameterToTrack(aCx);
+ SendLoopParametersToTrack();
+ }
+ AudioParam* PlaybackRate() const { return mPlaybackRate; }
+ AudioParam* Detune() const { return mDetune; }
+ bool Loop() const { return mLoop; }
+ void SetLoop(bool aLoop) {
+ mLoop = aLoop;
+ SendLoopParametersToTrack();
+ }
+ double LoopStart() const { return mLoopStart; }
+ void SetLoopStart(double aStart) {
+ mLoopStart = aStart;
+ SendLoopParametersToTrack();
+ }
+ double LoopEnd() const { return mLoopEnd; }
+ void SetLoopEnd(double aEnd) {
+ mLoopEnd = aEnd;
+ SendLoopParametersToTrack();
+ }
+ void NotifyMainThreadTrackEnded() override;
+
+ const char* NodeType() const override { return "AudioBufferSourceNode"; }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+ private:
+ explicit AudioBufferSourceNode(AudioContext* aContext);
+ ~AudioBufferSourceNode() = default;
+
+ friend class AudioBufferSourceNodeEngine;
+ // START is sent during Start().
+ // STOP is sent during Stop().
+ // BUFFERSTART and DURATION are sent when SetBuffer() and Start() have
+ // been called (along with sending the buffer).
+ enum EngineParameters {
+ SAMPLE_RATE,
+ START,
+ STOP,
+ // BUFFERSTART is the "offset" passed to start(), multiplied by
+ // buffer.sampleRate.
+ BUFFERSTART,
+ DURATION,
+ LOOP,
+ LOOPSTART,
+ LOOPEND,
+ PLAYBACKRATE,
+ DETUNE
+ };
+
+ void SendLoopParametersToTrack();
+ void SendBufferParameterToTrack(JSContext* aCx);
+ void SendOffsetAndDurationParametersToTrack(AudioNodeTrack* aTrack);
+
+ double mLoopStart;
+ double mLoopEnd;
+ double mOffset;
+ double mDuration;
+ RefPtr<AudioBuffer> mBuffer;
+ RefPtr<AudioParam> mPlaybackRate;
+ RefPtr<AudioParam> mDetune;
+ bool mLoop;
+ bool mStartCalled;
+ bool mBufferSet;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/media/webaudio/AudioContext.cpp b/dom/media/webaudio/AudioContext.cpp
new file mode 100644
index 0000000000..2ef5fdd65b
--- /dev/null
+++ b/dom/media/webaudio/AudioContext.cpp
@@ -0,0 +1,1405 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AudioContext.h"
+
+#include "blink/PeriodicWave.h"
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/OwningNonNull.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_media.h"
+
+#include "mozilla/dom/AnalyserNode.h"
+#include "mozilla/dom/AnalyserNodeBinding.h"
+#include "mozilla/dom/AudioBufferSourceNodeBinding.h"
+#include "mozilla/dom/AudioContextBinding.h"
+#include "mozilla/dom/BaseAudioContextBinding.h"
+#include "mozilla/dom/BiquadFilterNodeBinding.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/ChannelMergerNodeBinding.h"
+#include "mozilla/dom/ChannelSplitterNodeBinding.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ConvolverNodeBinding.h"
+#include "mozilla/dom/DelayNodeBinding.h"
+#include "mozilla/dom/DynamicsCompressorNodeBinding.h"
+#include "mozilla/dom/GainNodeBinding.h"
+#include "mozilla/dom/IIRFilterNodeBinding.h"
+#include "mozilla/dom/HTMLMediaElement.h"
+#include "mozilla/dom/MediaElementAudioSourceNodeBinding.h"
+#include "mozilla/dom/MediaStreamAudioSourceNodeBinding.h"
+#include "mozilla/dom/MediaStreamTrackAudioSourceNodeBinding.h"
+#include "mozilla/dom/OfflineAudioContextBinding.h"
+#include "mozilla/dom/OscillatorNodeBinding.h"
+#include "mozilla/dom/PannerNodeBinding.h"
+#include "mozilla/dom/PeriodicWaveBinding.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/StereoPannerNodeBinding.h"
+#include "mozilla/dom/WaveShaperNodeBinding.h"
+#include "mozilla/dom/Worklet.h"
+
+#include "AudioBuffer.h"
+#include "AudioBufferSourceNode.h"
+#include "AudioChannelService.h"
+#include "AudioDestinationNode.h"
+#include "AudioListener.h"
+#include "AudioNodeTrack.h"
+#include "AudioStream.h"
+#include "AudioWorkletImpl.h"
+#include "AutoplayPolicy.h"
+#include "BiquadFilterNode.h"
+#include "ChannelMergerNode.h"
+#include "ChannelSplitterNode.h"
+#include "ConstantSourceNode.h"
+#include "ConvolverNode.h"
+#include "DelayNode.h"
+#include "DynamicsCompressorNode.h"
+#include "GainNode.h"
+#include "IIRFilterNode.h"
+#include "js/ArrayBuffer.h" // JS::StealArrayBufferContents
+#include "MediaElementAudioSourceNode.h"
+#include "MediaStreamAudioDestinationNode.h"
+#include "MediaStreamAudioSourceNode.h"
+#include "MediaTrackGraph.h"
+#include "MediaStreamTrackAudioSourceNode.h"
+#include "nsContentUtils.h"
+#include "nsIScriptError.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsPIDOMWindow.h"
+#include "nsPrintfCString.h"
+#include "nsRFPService.h"
+#include "OscillatorNode.h"
+#include "PannerNode.h"
+#include "PeriodicWave.h"
+#include "ScriptProcessorNode.h"
+#include "StereoPannerNode.h"
+#include "WaveShaperNode.h"
+#include "Tracing.h"
+
+extern mozilla::LazyLogModule gAutoplayPermissionLog;
+
+#define AUTOPLAY_LOG(msg, ...) \
+ MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
+
+namespace mozilla::dom {
+
+// 0 is a special value that MediaTracks use to denote they are not part of a
+// AudioContext.
+static dom::AudioContext::AudioContextId gAudioContextId = 1;
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(AudioContext)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AudioContext)
+ // The destination node and AudioContext form a cycle and so the destination
+ // track will be destroyed. mWorklet must be shut down before the track
+ // is destroyed. Do this before clearing mWorklet.
+ tmp->ShutdownWorklet();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDestination)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mListener)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mWorklet)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromiseGripArray)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingResumePromises)
+ if (tmp->mTracksAreSuspended || !tmp->mIsStarted) {
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mActiveNodes)
+ }
+ // mDecodeJobs owns the WebAudioDecodeJob objects whose lifetime is managed
+ // explicitly. mAllNodes is an array of weak pointers, ignore it here.
+ // mBasicWaveFormCache cannot participate in cycles, ignore it here.
+
+ // Remove weak reference on the global window as the context is not usable
+ // without mDestination.
+ tmp->DisconnectFromWindow();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(DOMEventTargetHelper)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(AudioContext,
+ DOMEventTargetHelper)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDestination)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListener)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWorklet)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromiseGripArray)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingResumePromises)
+ if (tmp->mTracksAreSuspended || !tmp->mIsStarted) {
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mActiveNodes)
+ }
+ // mDecodeJobs owns the WebAudioDecodeJob objects whose lifetime is managed
+ // explicitly. mAllNodes is an array of weak pointers, ignore it here.
+ // mBasicWaveFormCache cannot participate in cycles, ignore it here.
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_ADDREF_INHERITED(AudioContext, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(AudioContext, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AudioContext)
+ NS_INTERFACE_MAP_ENTRY(nsIMemoryReporter)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+static float GetSampleRateForAudioContext(bool aIsOffline, float aSampleRate,
+ bool aShouldResistFingerprinting) {
+ if (aIsOffline || aSampleRate != 0.0) {
+ return aSampleRate;
+ } else {
+ return static_cast<float>(
+ CubebUtils::PreferredSampleRate(aShouldResistFingerprinting));
+ }
+}
+
+AudioContext::AudioContext(nsPIDOMWindowInner* aWindow, bool aIsOffline,
+ uint32_t aNumberOfChannels, uint32_t aLength,
+ float aSampleRate)
+ : DOMEventTargetHelper(aWindow),
+ mId(gAudioContextId++),
+ mSampleRate(GetSampleRateForAudioContext(
+ aIsOffline, aSampleRate,
+ aWindow->AsGlobal()->ShouldResistFingerprinting(
+ RFPTarget::AudioSampleRate))),
+ mAudioContextState(AudioContextState::Suspended),
+ mNumberOfChannels(aNumberOfChannels),
+ mRTPCallerType(aWindow->AsGlobal()->GetRTPCallerType()),
+ mShouldResistFingerprinting(
+ aWindow->AsGlobal()->ShouldResistFingerprinting(
+ RFPTarget::AudioContext)),
+ mIsOffline(aIsOffline),
+ mIsStarted(!aIsOffline),
+ mIsShutDown(false),
+ mIsDisconnecting(false),
+ mCloseCalled(false),
+ // Realtime contexts start with suspended tracks until an
+ // AudioCallbackDriver is running.
+ mTracksAreSuspended(!aIsOffline),
+ mWasAllowedToStart(true),
+ mSuspendedByContent(false),
+ mSuspendedByChrome(aWindow->IsSuspended()),
+ mWasEverAllowedToStart(false),
+ mWasEverBlockedToStart(false),
+ mWouldBeAllowedToStart(true) {
+ bool mute = aWindow->AddAudioContext(this);
+
+ // Note: AudioDestinationNode needs an AudioContext that must already be
+ // bound to the window.
+ const bool allowedToStart = media::AutoplayPolicy::IsAllowedToPlay(*this);
+ mDestination =
+ new AudioDestinationNode(this, aIsOffline, aNumberOfChannels, aLength);
+ mDestination->Init();
+ // If an AudioContext is not allowed to start, we would postpone its state
+ // transition from `suspended` to `running` until sites explicitly call
+ // AudioContext.resume() or AudioScheduledSourceNode.start().
+ if (!allowedToStart) {
+ MOZ_ASSERT(!mIsOffline);
+ AUTOPLAY_LOG("AudioContext %p is not allowed to start", this);
+ ReportBlocked();
+ } else if (!mIsOffline) {
+ ResumeInternal();
+ }
+
+ // The context can't be muted until it has a destination.
+ if (mute) {
+ Mute();
+ }
+
+ UpdateAutoplayAssumptionStatus();
+
+ FFTBlock::MainThreadInit();
+}
+
+void AudioContext::StartBlockedAudioContextIfAllowed() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MaybeUpdateAutoplayTelemetry();
+ // Only try to start AudioContext when AudioContext was not allowed to start.
+ if (mWasAllowedToStart) {
+ return;
+ }
+
+ const bool isAllowedToPlay = media::AutoplayPolicy::IsAllowedToPlay(*this);
+ AUTOPLAY_LOG("Trying to start AudioContext %p, IsAllowedToPlay=%d", this,
+ isAllowedToPlay);
+
+ // Only start the AudioContext if this resume() call was initiated by content,
+ // not if it was a result of the AudioContext starting after having been
+ // blocked because of the auto-play policy.
+ if (isAllowedToPlay && !mSuspendedByContent) {
+ ResumeInternal();
+ } else {
+ ReportBlocked();
+ }
+}
+
+void AudioContext::DisconnectFromWindow() {
+ MaybeClearPageAwakeRequest();
+ nsPIDOMWindowInner* window = GetOwner();
+ if (window) {
+ window->RemoveAudioContext(this);
+ }
+}
+
+AudioContext::~AudioContext() {
+ DisconnectFromWindow();
+ UnregisterWeakMemoryReporter(this);
+ MOZ_ASSERT(!mSetPageAwakeRequest, "forgot to revoke for page awake?");
+}
+
+JSObject* AudioContext::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ if (mIsOffline) {
+ return OfflineAudioContext_Binding::Wrap(aCx, this, aGivenProto);
+ } else {
+ return AudioContext_Binding::Wrap(aCx, this, aGivenProto);
+ }
+}
+
+static bool CheckFullyActive(nsPIDOMWindowInner* aWindow, ErrorResult& aRv) {
+ if (!aWindow->IsFullyActive()) {
+ aRv.ThrowInvalidStateError("The document is not fully active.");
+ return false;
+ }
+ return true;
+}
+
+/* static */
+already_AddRefed<AudioContext> AudioContext::Constructor(
+ const GlobalObject& aGlobal, const AudioContextOptions& aOptions,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!window) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ /**
+ * If the current settings object’s responsible document is NOT fully
+ * active, throw an InvalidStateError and abort these steps.
+ */
+ if (!CheckFullyActive(window, aRv)) {
+ return nullptr;
+ }
+
+ if (aOptions.mSampleRate.WasPassed() &&
+ (aOptions.mSampleRate.Value() < WebAudioUtils::MinSampleRate ||
+ aOptions.mSampleRate.Value() > WebAudioUtils::MaxSampleRate)) {
+ aRv.ThrowNotSupportedError(nsPrintfCString(
+ "Sample rate %g is not in the range [%u, %u]",
+ aOptions.mSampleRate.Value(), WebAudioUtils::MinSampleRate,
+ WebAudioUtils::MaxSampleRate));
+ return nullptr;
+ }
+ float sampleRate = aOptions.mSampleRate.WasPassed()
+ ? aOptions.mSampleRate.Value()
+ : MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE;
+
+ RefPtr<AudioContext> object =
+ new AudioContext(window, false, 2, 0, sampleRate);
+
+ RegisterWeakMemoryReporter(object);
+
+ return object.forget();
+}
+
+/* static */
+already_AddRefed<AudioContext> AudioContext::Constructor(
+ const GlobalObject& aGlobal, const OfflineAudioContextOptions& aOptions,
+ ErrorResult& aRv) {
+ return Constructor(aGlobal, aOptions.mNumberOfChannels, aOptions.mLength,
+ aOptions.mSampleRate, aRv);
+}
+
+/* static */
+already_AddRefed<AudioContext> AudioContext::Constructor(
+ const GlobalObject& aGlobal, uint32_t aNumberOfChannels, uint32_t aLength,
+ float aSampleRate, ErrorResult& aRv) {
+ nsCOMPtr<nsPIDOMWindowInner> window =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ if (!window) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ /**
+ * If the current settings object’s responsible document is NOT fully
+ * active, throw an InvalidStateError and abort these steps.
+ */
+ if (!CheckFullyActive(window, aRv)) {
+ return nullptr;
+ }
+
+ if (aNumberOfChannels == 0 ||
+ aNumberOfChannels > WebAudioUtils::MaxChannelCount) {
+ aRv.ThrowNotSupportedError(
+ nsPrintfCString("%u is not a valid channel count", aNumberOfChannels));
+ return nullptr;
+ }
+
+ if (aLength == 0) {
+ aRv.ThrowNotSupportedError("Length must be nonzero");
+ return nullptr;
+ }
+
+ if (aSampleRate < WebAudioUtils::MinSampleRate ||
+ aSampleRate > WebAudioUtils::MaxSampleRate) {
+ // The DOM binding protects us against infinity and NaN
+ aRv.ThrowNotSupportedError(nsPrintfCString(
+ "Sample rate %g is not in the range [%u, %u]", aSampleRate,
+ WebAudioUtils::MinSampleRate, WebAudioUtils::MaxSampleRate));
+ return nullptr;
+ }
+
+ RefPtr<AudioContext> object =
+ new AudioContext(window, true, aNumberOfChannels, aLength, aSampleRate);
+
+ RegisterWeakMemoryReporter(object);
+
+ return object.forget();
+}
+
+already_AddRefed<AudioBufferSourceNode> AudioContext::CreateBufferSource() {
+ return AudioBufferSourceNode::Create(nullptr, *this,
+ AudioBufferSourceOptions());
+}
+
+already_AddRefed<ConstantSourceNode> AudioContext::CreateConstantSource() {
+ RefPtr<ConstantSourceNode> constantSourceNode = new ConstantSourceNode(this);
+ return constantSourceNode.forget();
+}
+
+already_AddRefed<AudioBuffer> AudioContext::CreateBuffer(
+ uint32_t aNumberOfChannels, uint32_t aLength, float aSampleRate,
+ ErrorResult& aRv) {
+ if (!aNumberOfChannels) {
+ aRv.ThrowNotSupportedError("Number of channels must be nonzero");
+ return nullptr;
+ }
+
+ return AudioBuffer::Create(GetOwner(), aNumberOfChannels, aLength,
+ aSampleRate, aRv);
+}
+
+namespace {
+
+bool IsValidBufferSize(uint32_t aBufferSize) {
+ switch (aBufferSize) {
+ case 0: // let the implementation choose the buffer size
+ case 256:
+ case 512:
+ case 1024:
+ case 2048:
+ case 4096:
+ case 8192:
+ case 16384:
+ return true;
+ default:
+ return false;
+ }
+}
+
+} // namespace
+
+already_AddRefed<MediaStreamAudioDestinationNode>
+AudioContext::CreateMediaStreamDestination(ErrorResult& aRv) {
+ return MediaStreamAudioDestinationNode::Create(*this, AudioNodeOptions(),
+ aRv);
+}
+
+already_AddRefed<ScriptProcessorNode> AudioContext::CreateScriptProcessor(
+ uint32_t aBufferSize, uint32_t aNumberOfInputChannels,
+ uint32_t aNumberOfOutputChannels, ErrorResult& aRv) {
+ if (aNumberOfInputChannels == 0 && aNumberOfOutputChannels == 0) {
+ aRv.ThrowIndexSizeError(
+ "At least one of numberOfInputChannels and numberOfOutputChannels must "
+ "be nonzero");
+ return nullptr;
+ }
+
+ if (aNumberOfInputChannels > WebAudioUtils::MaxChannelCount) {
+ aRv.ThrowIndexSizeError(nsPrintfCString(
+ "%u is not a valid number of input channels", aNumberOfInputChannels));
+ return nullptr;
+ }
+
+ if (aNumberOfOutputChannels > WebAudioUtils::MaxChannelCount) {
+ aRv.ThrowIndexSizeError(
+ nsPrintfCString("%u is not a valid number of output channels",
+ aNumberOfOutputChannels));
+ return nullptr;
+ }
+
+ if (!IsValidBufferSize(aBufferSize)) {
+ aRv.ThrowIndexSizeError(
+ nsPrintfCString("%u is not a valid bufferSize", aBufferSize));
+ return nullptr;
+ }
+
+ RefPtr<ScriptProcessorNode> scriptProcessor = new ScriptProcessorNode(
+ this, aBufferSize, aNumberOfInputChannels, aNumberOfOutputChannels);
+ return scriptProcessor.forget();
+}
+
+already_AddRefed<AnalyserNode> AudioContext::CreateAnalyser(ErrorResult& aRv) {
+ return AnalyserNode::Create(*this, AnalyserOptions(), aRv);
+}
+
+already_AddRefed<StereoPannerNode> AudioContext::CreateStereoPanner(
+ ErrorResult& aRv) {
+ return StereoPannerNode::Create(*this, StereoPannerOptions(), aRv);
+}
+
+already_AddRefed<MediaElementAudioSourceNode>
+AudioContext::CreateMediaElementSource(HTMLMediaElement& aMediaElement,
+ ErrorResult& aRv) {
+ MediaElementAudioSourceOptions options;
+ options.mMediaElement = aMediaElement;
+
+ return MediaElementAudioSourceNode::Create(*this, options, aRv);
+}
+
+already_AddRefed<MediaStreamAudioSourceNode>
+AudioContext::CreateMediaStreamSource(DOMMediaStream& aMediaStream,
+ ErrorResult& aRv) {
+ MediaStreamAudioSourceOptions options;
+ options.mMediaStream = aMediaStream;
+
+ return MediaStreamAudioSourceNode::Create(*this, options, aRv);
+}
+
+already_AddRefed<MediaStreamTrackAudioSourceNode>
+AudioContext::CreateMediaStreamTrackSource(MediaStreamTrack& aMediaStreamTrack,
+ ErrorResult& aRv) {
+ MediaStreamTrackAudioSourceOptions options;
+ options.mMediaStreamTrack = aMediaStreamTrack;
+
+ return MediaStreamTrackAudioSourceNode::Create(*this, options, aRv);
+}
+
+already_AddRefed<GainNode> AudioContext::CreateGain(ErrorResult& aRv) {
+ return GainNode::Create(*this, GainOptions(), aRv);
+}
+
+already_AddRefed<WaveShaperNode> AudioContext::CreateWaveShaper(
+ ErrorResult& aRv) {
+ return WaveShaperNode::Create(*this, WaveShaperOptions(), aRv);
+}
+
+already_AddRefed<DelayNode> AudioContext::CreateDelay(double aMaxDelayTime,
+ ErrorResult& aRv) {
+ DelayOptions options;
+ options.mMaxDelayTime = aMaxDelayTime;
+ return DelayNode::Create(*this, options, aRv);
+}
+
+already_AddRefed<PannerNode> AudioContext::CreatePanner(ErrorResult& aRv) {
+ return PannerNode::Create(*this, PannerOptions(), aRv);
+}
+
+already_AddRefed<ConvolverNode> AudioContext::CreateConvolver(
+ ErrorResult& aRv) {
+ return ConvolverNode::Create(nullptr, *this, ConvolverOptions(), aRv);
+}
+
+already_AddRefed<ChannelSplitterNode> AudioContext::CreateChannelSplitter(
+ uint32_t aNumberOfOutputs, ErrorResult& aRv) {
+ ChannelSplitterOptions options;
+ options.mNumberOfOutputs = aNumberOfOutputs;
+ return ChannelSplitterNode::Create(*this, options, aRv);
+}
+
+already_AddRefed<ChannelMergerNode> AudioContext::CreateChannelMerger(
+ uint32_t aNumberOfInputs, ErrorResult& aRv) {
+ ChannelMergerOptions options;
+ options.mNumberOfInputs = aNumberOfInputs;
+ return ChannelMergerNode::Create(*this, options, aRv);
+}
+
+already_AddRefed<DynamicsCompressorNode> AudioContext::CreateDynamicsCompressor(
+ ErrorResult& aRv) {
+ return DynamicsCompressorNode::Create(*this, DynamicsCompressorOptions(),
+ aRv);
+}
+
+already_AddRefed<BiquadFilterNode> AudioContext::CreateBiquadFilter(
+ ErrorResult& aRv) {
+ return BiquadFilterNode::Create(*this, BiquadFilterOptions(), aRv);
+}
+
+already_AddRefed<IIRFilterNode> AudioContext::CreateIIRFilter(
+ const Sequence<double>& aFeedforward, const Sequence<double>& aFeedback,
+ mozilla::ErrorResult& aRv) {
+ IIRFilterOptions options;
+ options.mFeedforward = aFeedforward;
+ options.mFeedback = aFeedback;
+ return IIRFilterNode::Create(*this, options, aRv);
+}
+
+already_AddRefed<OscillatorNode> AudioContext::CreateOscillator(
+ ErrorResult& aRv) {
+ return OscillatorNode::Create(*this, OscillatorOptions(), aRv);
+}
+
+already_AddRefed<PeriodicWave> AudioContext::CreatePeriodicWave(
+ const Sequence<float>& aRealData, const Sequence<float>& aImagData,
+ const PeriodicWaveConstraints& aConstraints, ErrorResult& aRv) {
+ RefPtr<PeriodicWave> periodicWave = new PeriodicWave(
+ this, aRealData.Elements(), aRealData.Length(), aImagData.Elements(),
+ aImagData.Length(), aConstraints.mDisableNormalization, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ return periodicWave.forget();
+}
+
+AudioListener* AudioContext::Listener() {
+ if (!mListener) {
+ mListener = new AudioListener(this);
+ }
+ return mListener;
+}
+
+double AudioContext::OutputLatency() {
+ if (mIsShutDown) {
+ return 0.0;
+ }
+ // When reduceFingerprinting is enabled, return a latency figure that is
+ // fixed, but plausible for the platform.
+ double latency_s = 0.0;
+ if (mShouldResistFingerprinting) {
+#ifdef XP_MACOSX
+ latency_s = 512. / mSampleRate;
+#elif MOZ_WIDGET_ANDROID
+ latency_s = 0.020;
+#elif XP_WIN
+ latency_s = 0.04;
+#else // Catchall for other OSes, including Linux.
+ latency_s = 0.025;
+#endif
+ } else {
+ return Graph()->AudioOutputLatency();
+ }
+ return latency_s;
+}
+
+void AudioContext::GetOutputTimestamp(AudioTimestamp& aTimeStamp) {
+ if (!Destination()) {
+ aTimeStamp.mContextTime.Construct(0.0);
+ aTimeStamp.mPerformanceTime.Construct(0.0);
+ return;
+ }
+
+ // The currentTime currently being output is the currentTime minus the audio
+ // output latency. The resolution of CurrentTime() is already reduced.
+ aTimeStamp.mContextTime.Construct(
+ std::max(0.0, CurrentTime() - OutputLatency()));
+ nsPIDOMWindowInner* parent = GetParentObject();
+ Performance* perf = parent ? parent->GetPerformance() : nullptr;
+ if (perf) {
+ // perf->Now() already has reduced resolution here, no need to do it again.
+ aTimeStamp.mPerformanceTime.Construct(
+ std::max(0., perf->Now() - (OutputLatency() * 1000.)));
+ } else {
+ aTimeStamp.mPerformanceTime.Construct(0.0);
+ }
+}
+
+Worklet* AudioContext::GetAudioWorklet(ErrorResult& aRv) {
+ if (!mWorklet) {
+ mWorklet = AudioWorkletImpl::CreateWorklet(this, aRv);
+ }
+
+ return mWorklet;
+}
+bool AudioContext::IsRunning() const {
+ return mAudioContextState == AudioContextState::Running;
+}
+
+already_AddRefed<Promise> AudioContext::CreatePromise(ErrorResult& aRv) {
+ // Get the relevant global for the promise from the wrapper cache because
+ // DOMEventTargetHelper::GetOwner() returns null if the document is unloaded.
+ // We know the wrapper exists because it is being used for |this| from JS.
+ // See https://github.com/heycam/webidl/issues/932 for why the relevant
+ // global is used instead of the current global.
+ nsCOMPtr<nsIGlobalObject> global = xpc::NativeGlobal(GetWrapper());
+ RefPtr<Promise> promise = Promise::Create(global, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ /**
+ * If this's relevant global object's associated Document is not fully
+ * active then return a promise rejected with "InvalidStateError"
+ * DOMException.
+ */
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(global);
+ if (!window->IsFullyActive()) {
+ promise->MaybeRejectWithInvalidStateError(
+ "The document is not fully active.");
+ }
+ return promise.forget();
+}
+
+already_AddRefed<Promise> AudioContext::DecodeAudioData(
+ const ArrayBuffer& aBuffer,
+ const Optional<OwningNonNull<DecodeSuccessCallback>>& aSuccessCallback,
+ const Optional<OwningNonNull<DecodeErrorCallback>>& aFailureCallback,
+ ErrorResult& aRv) {
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+
+ // CheckedUnwrapStatic is OK, since we know we have an ArrayBuffer.
+ JS::Rooted<JSObject*> obj(cx, js::CheckedUnwrapStatic(aBuffer.Obj()));
+ if (!obj) {
+ aRv.ThrowSecurityError("Can't get audio data from cross-origin object");
+ return nullptr;
+ }
+
+ RefPtr<Promise> promise = CreatePromise(aRv);
+ if (aRv.Failed() || promise->State() == Promise::PromiseState::Rejected) {
+ return promise.forget();
+ }
+
+ JSAutoRealm ar(cx, obj);
+
+ // Detach the array buffer
+ size_t length = JS::GetArrayBufferByteLength(obj);
+ uint8_t* data = static_cast<uint8_t*>(JS::StealArrayBufferContents(cx, obj));
+ if (!data) {
+ JS_ClearPendingException(cx);
+
+ // Throw if the buffer is detached
+ aRv.ThrowTypeError("Buffer argument can't be a detached buffer");
+ return nullptr;
+ }
+
+ // Sniff the content of the media.
+ // Failed type sniffing will be handled by AsyncDecodeWebAudio.
+ nsAutoCString contentType;
+ NS_SniffContent(NS_DATA_SNIFFER_CATEGORY, nullptr, data, length, contentType);
+
+ RefPtr<DecodeErrorCallback> failureCallback;
+ RefPtr<DecodeSuccessCallback> successCallback;
+ if (aFailureCallback.WasPassed()) {
+ failureCallback = &aFailureCallback.Value();
+ }
+ if (aSuccessCallback.WasPassed()) {
+ successCallback = &aSuccessCallback.Value();
+ }
+ UniquePtr<WebAudioDecodeJob> job(
+ new WebAudioDecodeJob(this, promise, successCallback, failureCallback));
+ AsyncDecodeWebAudio(contentType.get(), data, length, *job);
+ // Transfer the ownership to mDecodeJobs
+ mDecodeJobs.AppendElement(std::move(job));
+
+ return promise.forget();
+}
+
+void AudioContext::RemoveFromDecodeQueue(WebAudioDecodeJob* aDecodeJob) {
+ // Since UniquePtr doesn't provide an operator== which allows you to compare
+ // against raw pointers, we need to iterate manually.
+ for (uint32_t i = 0; i < mDecodeJobs.Length(); ++i) {
+ if (mDecodeJobs[i].get() == aDecodeJob) {
+ mDecodeJobs.RemoveElementAt(i);
+ break;
+ }
+ }
+}
+
+void AudioContext::RegisterActiveNode(AudioNode* aNode) {
+ if (!mCloseCalled) {
+ mActiveNodes.Insert(aNode);
+ }
+}
+
+void AudioContext::UnregisterActiveNode(AudioNode* aNode) {
+ mActiveNodes.Remove(aNode);
+}
+
+uint32_t AudioContext::MaxChannelCount() const {
+ if (mShouldResistFingerprinting) {
+ return 2;
+ }
+ return std::min<uint32_t>(
+ WebAudioUtils::MaxChannelCount,
+ mIsOffline ? mNumberOfChannels : CubebUtils::MaxNumberOfChannels());
+}
+
+uint32_t AudioContext::ActiveNodeCount() const { return mActiveNodes.Count(); }
+
+MediaTrackGraph* AudioContext::Graph() const {
+ return Destination()->Track()->Graph();
+}
+
+AudioNodeTrack* AudioContext::DestinationTrack() const {
+ if (Destination()) {
+ return Destination()->Track();
+ }
+ return nullptr;
+}
+
+void AudioContext::ShutdownWorklet() {
+ if (mWorklet) {
+ mWorklet->Impl()->NotifyWorkletFinished();
+ }
+}
+
+double AudioContext::CurrentTime() {
+ mozilla::MediaTrack* track = Destination()->Track();
+
+ double rawTime = track->TrackTimeToSeconds(track->GetCurrentTime());
+
+ // CurrentTime increments in intervals of 128/sampleRate. If the Timer
+ // Precision Reduction is smaller than this interval, the jittered time
+ // can always be reversed to the raw step of the interval. In that case
+ // we can simply return the un-reduced time; and avoid breaking tests.
+ // We have to convert each variable into a common magnitude, we choose ms.
+ if ((128 / mSampleRate) * 1000.0 >
+ nsRFPService::TimerResolution(mRTPCallerType) / 1000.0) {
+ return rawTime;
+ }
+
+ // The value of a MediaTrack's CurrentTime will always advance forward; it
+ // will never reset (even if one rewinds a video.) Therefore we can use a
+ // single Random Seed initialized at the same time as the object.
+ return nsRFPService::ReduceTimePrecisionAsSecs(
+ rawTime, GetRandomTimelineSeed(), mRTPCallerType);
+}
+
+nsISerialEventTarget* AudioContext::GetMainThread() const {
+ if (nsPIDOMWindowInner* window = GetParentObject()) {
+ return window->AsGlobal()->SerialEventTarget();
+ }
+ return GetCurrentSerialEventTarget();
+}
+
+void AudioContext::DisconnectFromOwner() {
+ mIsDisconnecting = true;
+ MaybeClearPageAwakeRequest();
+ OnWindowDestroy();
+ DOMEventTargetHelper::DisconnectFromOwner();
+}
+
+void AudioContext::OnWindowDestroy() {
+ // Avoid resend the Telemetry data.
+ if (!mIsShutDown) {
+ MaybeUpdateAutoplayTelemetryWhenShutdown();
+ }
+ mIsShutDown = true;
+
+ CloseInternal(nullptr, AudioContextOperationFlags::None);
+
+ // We don't want to touch promises if the global is going away soon.
+ if (!mIsDisconnecting) {
+ for (auto p : mPromiseGripArray) {
+ p->MaybeRejectWithInvalidStateError("Navigated away from page");
+ }
+
+ mPromiseGripArray.Clear();
+
+ for (const auto& p : mPendingResumePromises) {
+ p->MaybeRejectWithInvalidStateError("Navigated away from page");
+ }
+ mPendingResumePromises.Clear();
+ }
+
+ // On process shutdown, the MTG thread shuts down before the destination
+ // track is destroyed, but AudioWorklet needs to release objects on the MTG
+ // thread. AudioContext::Shutdown() is invoked on processing the
+ // PBrowser::Destroy() message before xpcom shutdown begins.
+ ShutdownWorklet();
+
+ if (mDestination) {
+ // We can destroy the MediaTrackGraph at this point.
+ // Although there may be other clients using the graph, this graph is used
+ // only for clients in the same window and this window is going away.
+ // This will also interrupt any worklet script still running on the graph
+ // thread.
+ Graph()->ForceShutDown();
+ // AudioDestinationNodes on rendering offline contexts have a
+ // self-reference which needs removal.
+ if (mIsOffline) {
+ mDestination->OfflineShutdown();
+ }
+ }
+}
+
+/* This runnable allows to fire the "statechange" event */
+class OnStateChangeTask final : public Runnable {
+ public:
+ explicit OnStateChangeTask(AudioContext* aAudioContext)
+ : Runnable("dom::OnStateChangeTask"), mAudioContext(aAudioContext) {}
+
+ NS_IMETHODIMP
+ Run() override {
+ nsPIDOMWindowInner* parent = mAudioContext->GetParentObject();
+ if (!parent) {
+ return NS_ERROR_FAILURE;
+ }
+
+ Document* doc = parent->GetExtantDoc();
+ if (!doc) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return nsContentUtils::DispatchTrustedEvent(
+ doc, mAudioContext, u"statechange"_ns, CanBubble::eNo, Cancelable::eNo);
+ }
+
+ private:
+ RefPtr<AudioContext> mAudioContext;
+};
+
+void AudioContext::Dispatch(already_AddRefed<nsIRunnable>&& aRunnable) {
+ MOZ_ASSERT(NS_IsMainThread());
+ // It can happen that this runnable took a long time to reach the main thread,
+ // and the global is not valid anymore.
+ if (GetParentObject()) {
+ AbstractThread::MainThread()->Dispatch(std::move(aRunnable));
+ } else {
+ RefPtr<nsIRunnable> runnable(aRunnable);
+ runnable = nullptr;
+ }
+}
+
+void AudioContext::OnStateChanged(void* aPromise, AudioContextState aNewState) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mAudioContextState == AudioContextState::Closed) {
+ fprintf(stderr,
+ "Invalid transition: mAudioContextState: %d -> aNewState %d\n",
+ static_cast<int>(mAudioContextState), static_cast<int>(aNewState));
+ MOZ_ASSERT(false);
+ }
+
+ if (aPromise) {
+ Promise* promise = reinterpret_cast<Promise*>(aPromise);
+ // It is possible for the promise to have been removed from
+ // mPromiseGripArray if the cycle collector has severed our connections. DO
+ // NOT dereference the promise pointer in that case since it may point to
+ // already freed memory.
+ if (mPromiseGripArray.Contains(promise)) {
+ promise->MaybeResolveWithUndefined();
+ DebugOnly<bool> rv = mPromiseGripArray.RemoveElement(promise);
+ MOZ_ASSERT(rv, "Promise wasn't in the grip array?");
+ }
+ }
+
+ // Resolve all pending promises once the audio context has been allowed to
+ // start.
+ if (aNewState == AudioContextState::Running) {
+ for (const auto& p : mPendingResumePromises) {
+ p->MaybeResolveWithUndefined();
+ }
+ mPendingResumePromises.Clear();
+ }
+
+ if (mAudioContextState != aNewState) {
+ RefPtr<OnStateChangeTask> task = new OnStateChangeTask(this);
+ Dispatch(task.forget());
+ }
+
+ mAudioContextState = aNewState;
+ Destination()->NotifyAudioContextStateChanged();
+ MaybeUpdatePageAwakeRequest();
+}
+
+BrowsingContext* AudioContext::GetTopLevelBrowsingContext() {
+ nsCOMPtr<nsPIDOMWindowInner> window = GetParentObject();
+ if (!window) {
+ return nullptr;
+ }
+ BrowsingContext* bc = window->GetBrowsingContext();
+ if (!bc || bc->IsDiscarded()) {
+ return nullptr;
+ }
+ return bc->Top();
+}
+
+void AudioContext::MaybeUpdatePageAwakeRequest() {
+ // No need to keep page awake for offline context.
+ if (IsOffline()) {
+ return;
+ }
+
+ if (mIsShutDown) {
+ return;
+ }
+
+ if (IsRunning() && !mSetPageAwakeRequest) {
+ SetPageAwakeRequest(true);
+ } else if (!IsRunning() && mSetPageAwakeRequest) {
+ SetPageAwakeRequest(false);
+ }
+}
+
+void AudioContext::SetPageAwakeRequest(bool aShouldSet) {
+ mSetPageAwakeRequest = aShouldSet;
+ BrowsingContext* bc = GetTopLevelBrowsingContext();
+ if (!bc) {
+ return;
+ }
+ if (XRE_IsContentProcess()) {
+ ContentChild* contentChild = ContentChild::GetSingleton();
+ Unused << contentChild->SendAddOrRemovePageAwakeRequest(bc, aShouldSet);
+ return;
+ }
+ if (aShouldSet) {
+ bc->Canonical()->AddPageAwakeRequest();
+ } else {
+ bc->Canonical()->RemovePageAwakeRequest();
+ }
+}
+
+void AudioContext::MaybeClearPageAwakeRequest() {
+ if (mSetPageAwakeRequest) {
+ SetPageAwakeRequest(false);
+ }
+}
+
+nsTArray<RefPtr<mozilla::MediaTrack>> AudioContext::GetAllTracks() const {
+ nsTArray<RefPtr<mozilla::MediaTrack>> tracks;
+ for (AudioNode* node : mAllNodes) {
+ mozilla::MediaTrack* t = node->GetTrack();
+ if (t) {
+ tracks.AppendElement(t);
+ }
+ // Add the tracks of AudioParam.
+ const nsTArray<RefPtr<AudioParam>>& audioParams = node->GetAudioParams();
+ if (!audioParams.IsEmpty()) {
+ for (auto& param : audioParams) {
+ t = param->GetTrack();
+ if (t && !tracks.Contains(t)) {
+ tracks.AppendElement(t);
+ }
+ }
+ }
+ }
+ return tracks;
+}
+
+already_AddRefed<Promise> AudioContext::Suspend(ErrorResult& aRv) {
+ TRACE("AudioContext::Suspend");
+ RefPtr<Promise> promise = CreatePromise(aRv);
+ if (aRv.Failed() || promise->State() == Promise::PromiseState::Rejected) {
+ return promise.forget();
+ }
+ if (mIsOffline) {
+ // XXXbz This is not reachable, since we don't implement this
+ // method on OfflineAudioContext at all!
+ promise->MaybeRejectWithNotSupportedError(
+ "Can't suspend OfflineAudioContext yet");
+ return promise.forget();
+ }
+
+ if (mCloseCalled) {
+ promise->MaybeRejectWithInvalidStateError(
+ "Can't suspend if the control thread state is \"closed\"");
+ return promise.forget();
+ }
+
+ mSuspendedByContent = true;
+ mPromiseGripArray.AppendElement(promise);
+ SuspendInternal(promise, AudioContextOperationFlags::SendStateChange);
+ return promise.forget();
+}
+
+void AudioContext::SuspendFromChrome() {
+ if (mIsOffline || mIsShutDown) {
+ return;
+ }
+ MOZ_ASSERT(!mSuspendedByChrome);
+ mSuspendedByChrome = true;
+ SuspendInternal(nullptr, Preferences::GetBool("dom.audiocontext.testing")
+ ? AudioContextOperationFlags::SendStateChange
+ : AudioContextOperationFlags::None);
+}
+
+void AudioContext::SuspendInternal(void* aPromise,
+ AudioContextOperationFlags aFlags) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mIsOffline);
+ Destination()->Suspend();
+
+ nsTArray<RefPtr<mozilla::MediaTrack>> tracks;
+ // If mTracksAreSuspended is true then we already suspended all our tracks,
+ // so don't suspend them again (since suspend(); suspend(); resume(); should
+ // cancel both suspends). But we still need to do ApplyAudioContextOperation
+ // to ensure our new promise is resolved.
+ if (!mTracksAreSuspended) {
+ mTracksAreSuspended = true;
+ tracks = GetAllTracks();
+ }
+ auto promise = Graph()->ApplyAudioContextOperation(
+ DestinationTrack(), std::move(tracks), AudioContextOperation::Suspend);
+ if ((aFlags & AudioContextOperationFlags::SendStateChange)) {
+ promise->Then(
+ GetMainThread(), "AudioContext::OnStateChanged",
+ [self = RefPtr<AudioContext>(this),
+ aPromise](AudioContextState aNewState) {
+ self->OnStateChanged(aPromise, aNewState);
+ },
+ [] { MOZ_CRASH("Unexpected rejection"); });
+ }
+}
+
+void AudioContext::ResumeFromChrome() {
+ if (mIsOffline || mIsShutDown) {
+ return;
+ }
+ MOZ_ASSERT(mSuspendedByChrome);
+ mSuspendedByChrome = false;
+ if (!mWasAllowedToStart) {
+ return;
+ }
+ ResumeInternal();
+}
+
+already_AddRefed<Promise> AudioContext::Resume(ErrorResult& aRv) {
+ TRACE("AudioContext::Resume");
+ RefPtr<Promise> promise = CreatePromise(aRv);
+ if (aRv.Failed() || promise->State() == Promise::PromiseState::Rejected) {
+ return promise.forget();
+ }
+
+ if (mIsOffline) {
+ promise->MaybeRejectWithNotSupportedError(
+ "Can't resume OfflineAudioContext");
+ return promise.forget();
+ }
+
+ if (mCloseCalled) {
+ promise->MaybeRejectWithInvalidStateError(
+ "Can't resume if the control thread state is \"closed\"");
+ return promise.forget();
+ }
+
+ mSuspendedByContent = false;
+ mPendingResumePromises.AppendElement(promise);
+
+ const bool isAllowedToPlay = media::AutoplayPolicy::IsAllowedToPlay(*this);
+ AUTOPLAY_LOG("Trying to resume AudioContext %p, IsAllowedToPlay=%d", this,
+ isAllowedToPlay);
+ if (isAllowedToPlay) {
+ ResumeInternal();
+ } else {
+ ReportBlocked();
+ }
+
+ MaybeUpdateAutoplayTelemetry();
+
+ return promise.forget();
+}
+
+void AudioContext::ResumeInternal() {
+ MOZ_ASSERT(!mIsOffline);
+ AUTOPLAY_LOG("Allow to resume AudioContext %p", this);
+ mWasAllowedToStart = true;
+
+ if (mSuspendedByChrome || mSuspendedByContent || mCloseCalled) {
+ MOZ_ASSERT(mTracksAreSuspended);
+ return;
+ }
+
+ Destination()->Resume();
+
+ nsTArray<RefPtr<mozilla::MediaTrack>> tracks;
+ // If mTracksAreSuspended is false then we already resumed all our tracks,
+ // so don't resume them again (since suspend(); resume(); resume(); should
+ // be OK). But we still need to do ApplyAudioContextOperation
+ // to ensure our new promise is resolved.
+ if (mTracksAreSuspended) {
+ mTracksAreSuspended = false;
+ tracks = GetAllTracks();
+ }
+ // Check for statechange even when resumed from chrome because content may
+ // have called Resume() before chrome resumed the window.
+ Graph()
+ ->ApplyAudioContextOperation(DestinationTrack(), std::move(tracks),
+ AudioContextOperation::Resume)
+ ->Then(
+ GetMainThread(), "AudioContext::OnStateChanged",
+ [self = RefPtr<AudioContext>(this)](AudioContextState aNewState) {
+ self->OnStateChanged(nullptr, aNewState);
+ },
+ [] {}); // Promise may be rejected after graph shutdown.
+}
+
+void AudioContext::UpdateAutoplayAssumptionStatus() {
+ if (media::AutoplayPolicyTelemetryUtils::
+ WouldBeAllowedToPlayIfAutoplayDisabled(*this)) {
+ mWasEverAllowedToStart |= true;
+ mWouldBeAllowedToStart = true;
+ } else {
+ mWasEverBlockedToStart |= true;
+ mWouldBeAllowedToStart = false;
+ }
+}
+
+void AudioContext::MaybeUpdateAutoplayTelemetry() {
+ // Exclude offline AudioContext because it's always allowed to start.
+ if (mIsOffline) {
+ return;
+ }
+
+ if (media::AutoplayPolicyTelemetryUtils::
+ WouldBeAllowedToPlayIfAutoplayDisabled(*this) &&
+ !mWouldBeAllowedToStart) {
+ AccumulateCategorical(
+ mozilla::Telemetry::LABELS_WEB_AUDIO_AUTOPLAY::AllowedAfterBlocked);
+ }
+ UpdateAutoplayAssumptionStatus();
+}
+
+void AudioContext::MaybeUpdateAutoplayTelemetryWhenShutdown() {
+ // Exclude offline AudioContext because it's always allowed to start.
+ if (mIsOffline) {
+ return;
+ }
+
+ if (mWasEverAllowedToStart && !mWasEverBlockedToStart) {
+ AccumulateCategorical(
+ mozilla::Telemetry::LABELS_WEB_AUDIO_AUTOPLAY::NeverBlocked);
+ } else if (!mWasEverAllowedToStart && mWasEverBlockedToStart) {
+ AccumulateCategorical(
+ mozilla::Telemetry::LABELS_WEB_AUDIO_AUTOPLAY::NeverAllowed);
+ }
+}
+
+void AudioContext::ReportBlocked() {
+ ReportToConsole(nsIScriptError::warningFlag,
+ "BlockAutoplayWebAudioStartError");
+ mWasAllowedToStart = false;
+
+ if (!StaticPrefs::media_autoplay_block_event_enabled()) {
+ return;
+ }
+
+ RefPtr<AudioContext> self = this;
+ RefPtr<nsIRunnable> r =
+ NS_NewRunnableFunction("AudioContext::AutoplayBlocked", [self]() {
+ nsPIDOMWindowInner* parent = self->GetParentObject();
+ if (!parent) {
+ return;
+ }
+
+ Document* doc = parent->GetExtantDoc();
+ if (!doc) {
+ return;
+ }
+
+ AUTOPLAY_LOG("Dispatch `blocked` event for AudioContext %p",
+ self.get());
+ nsContentUtils::DispatchTrustedEvent(doc, self, u"blocked"_ns,
+ CanBubble::eNo, Cancelable::eNo);
+ });
+ Dispatch(r.forget());
+}
+
+already_AddRefed<Promise> AudioContext::Close(ErrorResult& aRv) {
+ TRACE("AudioContext::Close");
+ RefPtr<Promise> promise = CreatePromise(aRv);
+ if (aRv.Failed() || promise->State() == Promise::PromiseState::Rejected) {
+ return promise.forget();
+ }
+
+ if (mIsOffline) {
+ // XXXbz This is not reachable, since we don't implement this
+ // method on OfflineAudioContext at all!
+ promise->MaybeRejectWithNotSupportedError(
+ "Can't close OfflineAudioContext yet");
+ return promise.forget();
+ }
+
+ if (mCloseCalled) {
+ promise->MaybeRejectWithInvalidStateError(
+ "Can't close an AudioContext twice");
+ return promise.forget();
+ }
+
+ mPromiseGripArray.AppendElement(promise);
+
+ CloseInternal(promise, AudioContextOperationFlags::SendStateChange);
+
+ return promise.forget();
+}
+
+void AudioContext::OfflineClose() {
+ CloseInternal(nullptr, AudioContextOperationFlags::None);
+}
+
+void AudioContext::CloseInternal(void* aPromise,
+ AudioContextOperationFlags aFlags) {
+ // This can be called when freeing a document, and the tracks are dead at
+ // this point, so we need extra null-checks.
+ AudioNodeTrack* ds = DestinationTrack();
+ if (ds && !mIsOffline) {
+ Destination()->Close();
+
+ nsTArray<RefPtr<mozilla::MediaTrack>> tracks;
+ // If mTracksAreSuspended or mCloseCalled are true then we already suspended
+ // all our tracks, so don't suspend them again. But we still need to do
+ // ApplyAudioContextOperation to ensure our new promise is resolved.
+ if (!mTracksAreSuspended && !mCloseCalled) {
+ tracks = GetAllTracks();
+ }
+ auto promise = Graph()->ApplyAudioContextOperation(
+ ds, std::move(tracks), AudioContextOperation::Close);
+ if ((aFlags & AudioContextOperationFlags::SendStateChange)) {
+ promise->Then(
+ GetMainThread(), "AudioContext::OnStateChanged",
+ [self = RefPtr<AudioContext>(this),
+ aPromise](AudioContextState aNewState) {
+ self->OnStateChanged(aPromise, aNewState);
+ },
+ [] {}); // Promise may be rejected after graph shutdown.
+ }
+ }
+ mCloseCalled = true;
+ // Release references to active nodes.
+ // Active AudioNodes don't unregister in destructors, at which point the
+ // Node is already unregistered.
+ mActiveNodes.Clear();
+}
+
+void AudioContext::RegisterNode(AudioNode* aNode) {
+ MOZ_ASSERT(!mAllNodes.Contains(aNode));
+ mAllNodes.Insert(aNode);
+}
+
+void AudioContext::UnregisterNode(AudioNode* aNode) {
+ MOZ_ASSERT(mAllNodes.Contains(aNode));
+ mAllNodes.Remove(aNode);
+}
+
+already_AddRefed<Promise> AudioContext::StartRendering(ErrorResult& aRv) {
+ MOZ_ASSERT(mIsOffline, "This should only be called on OfflineAudioContext");
+ RefPtr<Promise> promise = CreatePromise(aRv);
+ if (aRv.Failed() || promise->State() == Promise::PromiseState::Rejected) {
+ return promise.forget();
+ }
+ if (mIsStarted) {
+ aRv.ThrowInvalidStateError("Rendering already started");
+ return nullptr;
+ }
+
+ mIsStarted = true;
+ mDestination->StartRendering(promise);
+
+ OnStateChanged(nullptr, AudioContextState::Running);
+
+ return promise.forget();
+}
+
+unsigned long AudioContext::Length() {
+ MOZ_ASSERT(mIsOffline);
+ return mDestination->Length();
+}
+
+void AudioContext::Mute() const {
+ MOZ_ASSERT(!mIsOffline);
+ if (mDestination) {
+ mDestination->Mute();
+ }
+}
+
+void AudioContext::Unmute() const {
+ MOZ_ASSERT(!mIsOffline);
+ if (mDestination) {
+ mDestination->Unmute();
+ }
+}
+
+void AudioContext::SetParamMapForWorkletName(
+ const nsAString& aName, AudioParamDescriptorMap* aParamMap) {
+ MOZ_ASSERT(!mWorkletParamDescriptors.Contains(aName));
+ Unused << mWorkletParamDescriptors.InsertOrUpdate(
+ aName, std::move(*aParamMap), fallible);
+}
+
+size_t AudioContext::SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ // AudioNodes are tracked separately because we do not want the AudioContext
+ // to track all of the AudioNodes it creates, so we wouldn't be able to
+ // traverse them from here.
+
+ size_t amount = aMallocSizeOf(this);
+ if (mListener) {
+ amount += mListener->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ amount += mDecodeJobs.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (uint32_t i = 0; i < mDecodeJobs.Length(); ++i) {
+ amount += mDecodeJobs[i]->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ amount += mActiveNodes.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ return amount;
+}
+
+NS_IMETHODIMP
+AudioContext::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) {
+ const nsLiteralCString nodeDescription(
+ "Memory used by AudioNode DOM objects (Web Audio).");
+ for (AudioNode* node : mAllNodes) {
+ int64_t amount = node->SizeOfIncludingThis(MallocSizeOf);
+ nsPrintfCString domNodePath("explicit/webaudio/audio-node/%s/dom-nodes",
+ node->NodeType());
+ aHandleReport->Callback(""_ns, domNodePath, KIND_HEAP, UNITS_BYTES, amount,
+ nodeDescription, aData);
+ }
+
+ int64_t amount = SizeOfIncludingThis(MallocSizeOf);
+ MOZ_COLLECT_REPORT("explicit/webaudio/audiocontext", KIND_HEAP, UNITS_BYTES,
+ amount,
+ "Memory used by AudioContext objects (Web Audio).");
+
+ return NS_OK;
+}
+
+BasicWaveFormCache* AudioContext::GetBasicWaveFormCache() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mBasicWaveFormCache) {
+ mBasicWaveFormCache = new BasicWaveFormCache(SampleRate());
+ }
+ return mBasicWaveFormCache;
+}
+
+void AudioContext::ReportToConsole(uint32_t aErrorFlags,
+ const char* aMsg) const {
+ MOZ_ASSERT(aMsg);
+ Document* doc =
+ GetParentObject() ? GetParentObject()->GetExtantDoc() : nullptr;
+ nsContentUtils::ReportToConsole(aErrorFlags, "Media"_ns, doc,
+ nsContentUtils::eDOM_PROPERTIES, aMsg);
+}
+
+BasicWaveFormCache::BasicWaveFormCache(uint32_t aSampleRate)
+ : mSampleRate(aSampleRate) {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+BasicWaveFormCache::~BasicWaveFormCache() = default;
+
+WebCore::PeriodicWave* BasicWaveFormCache::GetBasicWaveForm(
+ OscillatorType aType) {
+ MOZ_ASSERT(!NS_IsMainThread());
+ if (aType == OscillatorType::Sawtooth) {
+ if (!mSawtooth) {
+ mSawtooth = WebCore::PeriodicWave::createSawtooth(mSampleRate);
+ }
+ return mSawtooth;
+ }
+ if (aType == OscillatorType::Square) {
+ if (!mSquare) {
+ mSquare = WebCore::PeriodicWave::createSquare(mSampleRate);
+ }
+ return mSquare;
+ }
+ if (aType == OscillatorType::Triangle) {
+ if (!mTriangle) {
+ mTriangle = WebCore::PeriodicWave::createTriangle(mSampleRate);
+ }
+ return mTriangle;
+ }
+ MOZ_ASSERT(false, "Not reached");
+ return nullptr;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/AudioContext.h b/dom/media/webaudio/AudioContext.h
new file mode 100644
index 0000000000..81a64a18bf
--- /dev/null
+++ b/dom/media/webaudio/AudioContext.h
@@ -0,0 +1,479 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef AudioContext_h_
+#define AudioContext_h_
+
+#include "X11UndefineNone.h"
+#include "AudioParamDescriptorMap.h"
+#include "mozilla/dom/OfflineAudioContextBinding.h"
+#include "mozilla/dom/AudioContextBinding.h"
+#include "MediaBufferDecoder.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/RelativeTimeline.h"
+#include "mozilla/TypedEnumBits.h"
+#include "mozilla/UniquePtr.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsTHashMap.h"
+#include "nsHashKeys.h"
+#include "nsTHashSet.h"
+#include "js/TypeDecls.h"
+#include "nsIMemoryReporter.h"
+
+namespace WebCore {
+class PeriodicWave;
+} // namespace WebCore
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+
+class DOMMediaStream;
+class ErrorResult;
+class MediaTrack;
+class MediaTrackGraph;
+class AudioNodeTrack;
+
+namespace dom {
+
+enum class AudioContextState : uint8_t;
+class AnalyserNode;
+class AudioBuffer;
+class AudioBufferSourceNode;
+class AudioDestinationNode;
+class AudioListener;
+class AudioNode;
+class BiquadFilterNode;
+class BrowsingContext;
+class ChannelMergerNode;
+class ChannelSplitterNode;
+class ConstantSourceNode;
+class ConvolverNode;
+class DelayNode;
+class DynamicsCompressorNode;
+class GainNode;
+class GlobalObject;
+class HTMLMediaElement;
+class IIRFilterNode;
+class MediaElementAudioSourceNode;
+class MediaStreamAudioDestinationNode;
+class MediaStreamAudioSourceNode;
+class MediaStreamTrack;
+class MediaStreamTrackAudioSourceNode;
+class OscillatorNode;
+class PannerNode;
+class ScriptProcessorNode;
+class StereoPannerNode;
+class WaveShaperNode;
+class Worklet;
+class PeriodicWave;
+struct PeriodicWaveConstraints;
+class Promise;
+enum class OscillatorType : uint8_t;
+
+// This is addrefed by the OscillatorNodeEngine on the main thread
+// and then used from the MTG thread.
+// It can be released either from the graph thread or the main thread.
+class BasicWaveFormCache {
+ public:
+ explicit BasicWaveFormCache(uint32_t aSampleRate);
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(BasicWaveFormCache)
+ WebCore::PeriodicWave* GetBasicWaveForm(OscillatorType aType);
+
+ private:
+ ~BasicWaveFormCache();
+ RefPtr<WebCore::PeriodicWave> mSawtooth;
+ RefPtr<WebCore::PeriodicWave> mSquare;
+ RefPtr<WebCore::PeriodicWave> mTriangle;
+ uint32_t mSampleRate;
+};
+
+/* This runnable allows the MTG to notify the main thread when audio is actually
+ * flowing */
+class StateChangeTask final : public Runnable {
+ public:
+ /* This constructor should be used when this event is sent from the main
+ * thread. */
+ StateChangeTask(AudioContext* aAudioContext, void* aPromise,
+ AudioContextState aNewState);
+
+ /* This constructor should be used when this event is sent from the audio
+ * thread. */
+ StateChangeTask(AudioNodeTrack* aTrack, void* aPromise,
+ AudioContextState aNewState);
+
+ NS_IMETHOD Run() override;
+
+ private:
+ RefPtr<AudioContext> mAudioContext;
+ void* mPromise;
+ RefPtr<AudioNodeTrack> mAudioNodeTrack;
+ AudioContextState mNewState;
+};
+
+enum class AudioContextOperation : uint8_t { Suspend, Resume, Close };
+static const char* const kAudioContextOptionsStrings[] = {"Suspend", "Resume",
+ "Close"};
+// When suspending or resuming an AudioContext, some operations have to notify
+// the main thread, so that the Promise is resolved, the state is modified, and
+// the statechanged event is sent. Some other operations don't go back to the
+// main thread, for example when the AudioContext is paused by something that is
+// not caused by the page itself: opening a debugger, breaking on a breakpoint,
+// reloading a document.
+enum class AudioContextOperationFlags { None, SendStateChange };
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(AudioContextOperationFlags);
+
+struct AudioContextOptions;
+
+class AudioContext final : public DOMEventTargetHelper,
+ public nsIMemoryReporter,
+ public RelativeTimeline {
+ AudioContext(nsPIDOMWindowInner* aParentWindow, bool aIsOffline,
+ uint32_t aNumberOfChannels = 0, uint32_t aLength = 0,
+ float aSampleRate = 0.0f);
+ ~AudioContext();
+
+ public:
+ typedef uint64_t AudioContextId;
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AudioContext, DOMEventTargetHelper)
+ MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
+
+ nsPIDOMWindowInner* GetParentObject() const { return GetOwner(); }
+
+ nsISerialEventTarget* GetMainThread() const;
+
+ virtual void DisconnectFromOwner() override;
+
+ void OnWindowDestroy(); // idempotent
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ using DOMEventTargetHelper::DispatchTrustedEvent;
+
+ // Constructor for regular AudioContext
+ static already_AddRefed<AudioContext> Constructor(
+ const GlobalObject& aGlobal, const AudioContextOptions& aOptions,
+ ErrorResult& aRv);
+
+ // Constructor for offline AudioContext with options object
+ static already_AddRefed<AudioContext> Constructor(
+ const GlobalObject& aGlobal, const OfflineAudioContextOptions& aOptions,
+ ErrorResult& aRv);
+
+ // Constructor for offline AudioContext
+ static already_AddRefed<AudioContext> Constructor(const GlobalObject& aGlobal,
+ uint32_t aNumberOfChannels,
+ uint32_t aLength,
+ float aSampleRate,
+ ErrorResult& aRv);
+
+ // AudioContext methods
+
+ AudioDestinationNode* Destination() const { return mDestination; }
+
+ float SampleRate() const { return mSampleRate; }
+
+ bool ShouldSuspendNewTrack() const {
+ return mTracksAreSuspended || mCloseCalled;
+ }
+ double CurrentTime();
+
+ AudioListener* Listener();
+
+ AudioContextState State() const { return mAudioContextState; }
+
+ double BaseLatency() const {
+ // Gecko does not do any buffering between rendering the audio and sending
+ // it to the audio subsystem.
+ return 0.0;
+ }
+
+ double OutputLatency();
+
+ void GetOutputTimestamp(AudioTimestamp& aTimeStamp);
+
+ Worklet* GetAudioWorklet(ErrorResult& aRv);
+
+ bool IsRunning() const;
+
+ // Called when an AudioScheduledSourceNode started or the source node starts,
+ // this method might resume the AudioContext if it was not allowed to start.
+ void StartBlockedAudioContextIfAllowed();
+
+ // Those three methods return a promise to content, that is resolved when an
+ // (possibly long) operation is completed on the MTG (and possibly other)
+ // thread(s). To avoid having to match the calls and asychronous result when
+ // the operation is completed, we keep a reference to the promises on the main
+ // thread, and then send the promises pointers down the MTG thread, as a void*
+ // (to make it very clear that the pointer is to merely be treated as an ID).
+ // When back on the main thread, we can resolve or reject the promise, by
+ // casting it back to a `Promise*` while asserting we're back on the main
+ // thread and removing the reference we added.
+ already_AddRefed<Promise> Suspend(ErrorResult& aRv);
+ already_AddRefed<Promise> Resume(ErrorResult& aRv);
+ already_AddRefed<Promise> Close(ErrorResult& aRv);
+ IMPL_EVENT_HANDLER(statechange)
+
+ // These two functions are similar with Suspend() and Resume(), the difference
+ // is they are designed for calling from chrome side, not content side. eg.
+ // calling from inner window, so we won't need to return promise for caller.
+ void SuspendFromChrome();
+ void ResumeFromChrome();
+ // Called on completion of offline rendering:
+ void OfflineClose();
+
+ already_AddRefed<AudioBufferSourceNode> CreateBufferSource();
+
+ already_AddRefed<ConstantSourceNode> CreateConstantSource();
+
+ already_AddRefed<AudioBuffer> CreateBuffer(uint32_t aNumberOfChannels,
+ uint32_t aLength,
+ float aSampleRate,
+ ErrorResult& aRv);
+
+ already_AddRefed<MediaStreamAudioDestinationNode>
+ CreateMediaStreamDestination(ErrorResult& aRv);
+
+ already_AddRefed<ScriptProcessorNode> CreateScriptProcessor(
+ uint32_t aBufferSize, uint32_t aNumberOfInputChannels,
+ uint32_t aNumberOfOutputChannels, ErrorResult& aRv);
+
+ already_AddRefed<StereoPannerNode> CreateStereoPanner(ErrorResult& aRv);
+
+ already_AddRefed<AnalyserNode> CreateAnalyser(ErrorResult& aRv);
+
+ already_AddRefed<GainNode> CreateGain(ErrorResult& aRv);
+
+ already_AddRefed<WaveShaperNode> CreateWaveShaper(ErrorResult& aRv);
+
+ already_AddRefed<MediaElementAudioSourceNode> CreateMediaElementSource(
+ HTMLMediaElement& aMediaElement, ErrorResult& aRv);
+ already_AddRefed<MediaStreamAudioSourceNode> CreateMediaStreamSource(
+ DOMMediaStream& aMediaStream, ErrorResult& aRv);
+ already_AddRefed<MediaStreamTrackAudioSourceNode>
+ CreateMediaStreamTrackSource(MediaStreamTrack& aMediaStreamTrack,
+ ErrorResult& aRv);
+
+ already_AddRefed<DelayNode> CreateDelay(double aMaxDelayTime,
+ ErrorResult& aRv);
+
+ already_AddRefed<PannerNode> CreatePanner(ErrorResult& aRv);
+
+ already_AddRefed<ConvolverNode> CreateConvolver(ErrorResult& aRv);
+
+ already_AddRefed<ChannelSplitterNode> CreateChannelSplitter(
+ uint32_t aNumberOfOutputs, ErrorResult& aRv);
+
+ already_AddRefed<ChannelMergerNode> CreateChannelMerger(
+ uint32_t aNumberOfInputs, ErrorResult& aRv);
+
+ already_AddRefed<DynamicsCompressorNode> CreateDynamicsCompressor(
+ ErrorResult& aRv);
+
+ already_AddRefed<BiquadFilterNode> CreateBiquadFilter(ErrorResult& aRv);
+
+ already_AddRefed<IIRFilterNode> CreateIIRFilter(
+ const Sequence<double>& aFeedforward, const Sequence<double>& aFeedback,
+ mozilla::ErrorResult& aRv);
+
+ already_AddRefed<OscillatorNode> CreateOscillator(ErrorResult& aRv);
+
+ already_AddRefed<PeriodicWave> CreatePeriodicWave(
+ const Sequence<float>& aRealData, const Sequence<float>& aImagData,
+ const PeriodicWaveConstraints& aConstraints, ErrorResult& aRv);
+
+ already_AddRefed<Promise> DecodeAudioData(
+ const ArrayBuffer& aBuffer,
+ const Optional<OwningNonNull<DecodeSuccessCallback>>& aSuccessCallback,
+ const Optional<OwningNonNull<DecodeErrorCallback>>& aFailureCallback,
+ ErrorResult& aRv);
+
+ // OfflineAudioContext methods
+ already_AddRefed<Promise> StartRendering(ErrorResult& aRv);
+ IMPL_EVENT_HANDLER(complete)
+ unsigned long Length();
+
+ bool IsOffline() const { return mIsOffline; }
+
+ bool ShouldResistFingerprinting() const {
+ return mShouldResistFingerprinting;
+ }
+
+ MediaTrackGraph* Graph() const;
+ AudioNodeTrack* DestinationTrack() const;
+
+ // Nodes register here if they will produce sound even if they have silent
+ // or no input connections. The AudioContext will keep registered nodes
+ // alive until the context is collected. This takes care of "playing"
+ // references and "tail-time" references.
+ void RegisterActiveNode(AudioNode* aNode);
+ // Nodes unregister when they have finished producing sound for the
+ // foreseeable future.
+ // Do NOT call UnregisterActiveNode from an AudioNode destructor.
+ // If the destructor is called, then the Node has already been unregistered.
+ // The destructor may be called during hashtable enumeration, during which
+ // unregistering would not be safe.
+ void UnregisterActiveNode(AudioNode* aNode);
+
+ uint32_t MaxChannelCount() const;
+
+ uint32_t ActiveNodeCount() const;
+
+ void Mute() const;
+ void Unmute() const;
+
+ void RegisterNode(AudioNode* aNode);
+ void UnregisterNode(AudioNode* aNode);
+
+ void OnStateChanged(void* aPromise, AudioContextState aNewState);
+
+ BasicWaveFormCache* GetBasicWaveFormCache();
+
+ void ShutdownWorklet();
+ // Steals from |aParamMap|
+ void SetParamMapForWorkletName(const nsAString& aName,
+ AudioParamDescriptorMap* aParamMap);
+ const AudioParamDescriptorMap* GetParamMapForWorkletName(
+ const nsAString& aName) {
+ return mWorkletParamDescriptors.Lookup(aName).DataPtrOrNull();
+ }
+
+ void Dispatch(already_AddRefed<nsIRunnable>&& aRunnable);
+
+ private:
+ void DisconnectFromWindow();
+ already_AddRefed<Promise> CreatePromise(ErrorResult& aRv);
+ void RemoveFromDecodeQueue(WebAudioDecodeJob* aDecodeJob);
+ void ShutdownDecoder();
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+ NS_DECL_NSIMEMORYREPORTER
+
+ friend struct ::mozilla::WebAudioDecodeJob;
+
+ nsTArray<RefPtr<mozilla::MediaTrack>> GetAllTracks() const;
+
+ void ResumeInternal();
+ void SuspendInternal(void* aPromise, AudioContextOperationFlags aFlags);
+ void CloseInternal(void* aPromise, AudioContextOperationFlags aFlags);
+
+ // Will report error message to console and dispatch testing event if needed
+ // when AudioContext is blocked by autoplay policy.
+ void ReportBlocked();
+
+ void ReportToConsole(uint32_t aErrorFlags, const char* aMsg) const;
+
+ // This function should be called everytime we decide whether allow to start
+ // audio context, it's used to update Telemetry related variables.
+ void UpdateAutoplayAssumptionStatus();
+
+ // These functions are used for updating Telemetry.
+ // - MaybeUpdateAutoplayTelemetry: update category 'AllowedAfterBlocked'
+ // - MaybeUpdateAutoplayTelemetryWhenShutdown: update category 'NeverBlocked'
+ // and 'NeverAllowed', so we need to call it when shutdown AudioContext
+ void MaybeUpdateAutoplayTelemetry();
+ void MaybeUpdateAutoplayTelemetryWhenShutdown();
+
+ // If the pref `dom.suspend_inactive.enabled` is enabled, the dom window will
+ // be suspended when the window becomes inactive. In order to keep audio
+ // context running still, we will ask pages to keep awake in that situation.
+ void MaybeUpdatePageAwakeRequest();
+ void MaybeClearPageAwakeRequest();
+ void SetPageAwakeRequest(bool aShouldSet);
+
+ BrowsingContext* GetTopLevelBrowsingContext();
+
+ private:
+ // Each AudioContext has an id, that is passed down the MediaTracks that
+ // back the AudioNodes, so we can easily compute the set of all the
+ // MediaTracks for a given context, on the MediasTrackGraph side.
+ const AudioContextId mId;
+ // Note that it's important for mSampleRate to be initialized before
+ // mDestination, as mDestination's constructor needs to access it!
+ const float mSampleRate;
+ AudioContextState mAudioContextState;
+ RefPtr<AudioDestinationNode> mDestination;
+ RefPtr<AudioListener> mListener;
+ RefPtr<Worklet> mWorklet;
+ nsTArray<UniquePtr<WebAudioDecodeJob>> mDecodeJobs;
+ // This array is used to keep the suspend/close promises alive until
+ // they are resolved, so we can safely pass them accross threads.
+ nsTArray<RefPtr<Promise>> mPromiseGripArray;
+ // This array is used to onlly keep the resume promises alive until they are
+ // resolved, so we can safely pass them accross threads. If the audio context
+ // is not allowed to play, the promise would be pending in this array and be
+ // resolved until audio context has been allowed and user call resume() again.
+ nsTArray<RefPtr<Promise>> mPendingResumePromises;
+ // See RegisterActiveNode. These will keep the AudioContext alive while it
+ // is rendering and the window remains alive.
+ nsTHashSet<RefPtr<AudioNode>> mActiveNodes;
+ // Raw (non-owning) references to all AudioNodes for this AudioContext.
+ nsTHashSet<AudioNode*> mAllNodes;
+ nsTHashMap<nsStringHashKey, AudioParamDescriptorMap> mWorkletParamDescriptors;
+ // Cache to avoid recomputing basic waveforms all the time.
+ RefPtr<BasicWaveFormCache> mBasicWaveFormCache;
+ // Number of channels passed in the OfflineAudioContext ctor.
+ uint32_t mNumberOfChannels;
+ const RTPCallerType mRTPCallerType;
+ const bool mShouldResistFingerprinting;
+ const bool mIsOffline;
+ // true iff realtime or startRendering() has been called.
+ bool mIsStarted;
+ bool mIsShutDown;
+ bool mIsDisconnecting;
+ // Close has been called; reject suspend and resume calls.
+ bool mCloseCalled;
+ // Whether the MediaTracks are suspended, due to one or more of
+ // !mWasAllowedToStart, mSuspendedByContent, or mSuspendedByChrome.
+ // false if offline.
+ bool mTracksAreSuspended;
+ // This flag stores the value of previous status of `allowed-to-start`.
+ // true if offline.
+ bool mWasAllowedToStart;
+ // Whether this AudioContext is suspended because the page called suspend().
+ // Unused if offline.
+ bool mSuspendedByContent;
+ // Whether this AudioContext is suspended because the Window is suspended.
+ // Unused if offline.
+ bool mSuspendedByChrome;
+
+ // These variables are used for telemetry, they're not reflect the actual
+ // status of AudioContext, they are based on the "assumption" of enabling
+ // blocking web audio. Because we want to record Telemetry no matter user
+ // enable blocking autoplay or not.
+ // - 'mWasEverAllowedToStart' would be true when AudioContext had ever been
+ // allowed to start if we enable blocking web audio.
+ // - 'mWasEverBlockedToStart' would be true when AudioContext had ever been
+ // blocked to start if we enable blocking web audio.
+ // - 'mWouldBeAllowedToStart' stores the value of previous status of
+ // `allowed-to-start` if we enable blocking web audio.
+ bool mWasEverAllowedToStart;
+ bool mWasEverBlockedToStart;
+ bool mWouldBeAllowedToStart;
+
+ // Whether we have set the page awake reqeust when non-offline audio context
+ // is running. That will keep the audio context being able to continue running
+ // even if the window is inactive.
+ bool mSetPageAwakeRequest = false;
+};
+
+static const dom::AudioContext::AudioContextId NO_AUDIO_CONTEXT = 0;
+
+} // namespace dom
+} // namespace mozilla
+
+inline nsISupports* ToSupports(mozilla::dom::AudioContext* p) {
+ return NS_CYCLE_COLLECTION_CLASSNAME(mozilla::dom::AudioContext)::Upcast(p);
+}
+
+#endif
diff --git a/dom/media/webaudio/AudioDestinationNode.cpp b/dom/media/webaudio/AudioDestinationNode.cpp
new file mode 100644
index 0000000000..1a9c3c0f7b
--- /dev/null
+++ b/dom/media/webaudio/AudioDestinationNode.cpp
@@ -0,0 +1,675 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AudioDestinationNode.h"
+
+#include "AlignmentUtils.h"
+#include "AudibilityMonitor.h"
+#include "AudioChannelService.h"
+#include "AudioContext.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeTrack.h"
+#include "CubebUtils.h"
+#include "MediaTrackGraph.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/dom/AudioDestinationNodeBinding.h"
+#include "mozilla/dom/BaseAudioContextBinding.h"
+#include "mozilla/dom/OfflineAudioCompletionEvent.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/WakeLock.h"
+#include "mozilla/dom/power/PowerManagerService.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TelemetryHistogramEnums.h"
+#include "nsContentUtils.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsServiceManagerUtils.h"
+#include "Tracing.h"
+
+extern mozilla::LazyLogModule gAudioChannelLog;
+
+#define AUDIO_CHANNEL_LOG(msg, ...) \
+ MOZ_LOG(gAudioChannelLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
+
+namespace mozilla::dom {
+
+namespace {
+class OnCompleteTask final : public Runnable {
+ public:
+ OnCompleteTask(AudioContext* aAudioContext, AudioBuffer* aRenderedBuffer)
+ : Runnable("dom::OfflineDestinationNodeEngine::OnCompleteTask"),
+ mAudioContext(aAudioContext),
+ mRenderedBuffer(aRenderedBuffer) {}
+
+ NS_IMETHOD Run() override {
+ OfflineAudioCompletionEventInit param;
+ param.mRenderedBuffer = mRenderedBuffer;
+
+ RefPtr<OfflineAudioCompletionEvent> event =
+ OfflineAudioCompletionEvent::Constructor(mAudioContext, u"complete"_ns,
+ param);
+ mAudioContext->DispatchTrustedEvent(event);
+
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<AudioContext> mAudioContext;
+ RefPtr<AudioBuffer> mRenderedBuffer;
+};
+} // anonymous namespace
+
+class OfflineDestinationNodeEngine final : public AudioNodeEngine {
+ public:
+ explicit OfflineDestinationNodeEngine(AudioDestinationNode* aNode)
+ : AudioNodeEngine(aNode),
+ mWriteIndex(0),
+ mNumberOfChannels(aNode->ChannelCount()),
+ mLength(aNode->Length()),
+ mSampleRate(aNode->Context()->SampleRate()),
+ mBufferAllocated(false) {}
+
+ void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
+ const AudioBlock& aInput, AudioBlock* aOutput,
+ bool* aFinished) override {
+ TRACE("OfflineDestinationNodeEngine::ProcessBlock");
+ // Do this just for the sake of political correctness; this output
+ // will not go anywhere.
+ *aOutput = aInput;
+
+ // The output buffer is allocated lazily, on the rendering thread, when
+ // non-null input is received.
+ if (!mBufferAllocated && !aInput.IsNull()) {
+ // These allocations might fail if content provides a huge number of
+ // channels or size, but it's OK since we'll deal with the failure
+ // gracefully.
+ mBuffer = ThreadSharedFloatArrayBufferList::Create(mNumberOfChannels,
+ mLength, fallible);
+ if (mBuffer && mWriteIndex) {
+ // Zero leading for any null chunks that were skipped.
+ for (uint32_t i = 0; i < mNumberOfChannels; ++i) {
+ float* channelData = mBuffer->GetDataForWrite(i);
+ PodZero(channelData, mWriteIndex);
+ }
+ }
+
+ mBufferAllocated = true;
+ }
+
+ // Skip copying if there is no buffer.
+ uint32_t outputChannelCount = mBuffer ? mNumberOfChannels : 0;
+
+ // Record our input buffer
+ MOZ_ASSERT(mWriteIndex < mLength, "How did this happen?");
+ const uint32_t duration =
+ std::min(WEBAUDIO_BLOCK_SIZE, mLength - mWriteIndex);
+ const uint32_t inputChannelCount = aInput.ChannelCount();
+ for (uint32_t i = 0; i < outputChannelCount; ++i) {
+ float* outputData = mBuffer->GetDataForWrite(i) + mWriteIndex;
+ if (aInput.IsNull() || i >= inputChannelCount) {
+ PodZero(outputData, duration);
+ } else {
+ const float* inputBuffer =
+ static_cast<const float*>(aInput.mChannelData[i]);
+ if (duration == WEBAUDIO_BLOCK_SIZE && IS_ALIGNED16(inputBuffer)) {
+ // Use the optimized version of the copy with scale operation
+ AudioBlockCopyChannelWithScale(inputBuffer, aInput.mVolume,
+ outputData);
+ } else {
+ if (aInput.mVolume == 1.0f) {
+ PodCopy(outputData, inputBuffer, duration);
+ } else {
+ for (uint32_t j = 0; j < duration; ++j) {
+ outputData[j] = aInput.mVolume * inputBuffer[j];
+ }
+ }
+ }
+ }
+ }
+ mWriteIndex += duration;
+
+ if (mWriteIndex >= mLength) {
+ NS_ASSERTION(mWriteIndex == mLength, "Overshot length");
+ // Go to finished state. When the graph's current time eventually reaches
+ // the end of the track, then the main thread will be notified and we'll
+ // shut down the AudioContext.
+ *aFinished = true;
+ }
+ }
+
+ bool IsActive() const override {
+ // Keep processing to track track time, which is used for all timelines
+ // associated with the same AudioContext.
+ return true;
+ }
+
+ already_AddRefed<AudioBuffer> CreateAudioBuffer(AudioContext* aContext) {
+ MOZ_ASSERT(NS_IsMainThread());
+ // Create the input buffer
+ ErrorResult rv;
+ RefPtr<AudioBuffer> renderedBuffer =
+ AudioBuffer::Create(aContext->GetOwner(), mNumberOfChannels, mLength,
+ mSampleRate, mBuffer.forget(), rv);
+ if (rv.Failed()) {
+ rv.SuppressException();
+ return nullptr;
+ }
+
+ return renderedBuffer.forget();
+ }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override {
+ size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+ if (mBuffer) {
+ amount += mBuffer->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ return amount;
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ private:
+ // The input to the destination node is recorded in mBuffer.
+ // When this buffer fills up with mLength frames, the buffered input is sent
+ // to the main thread in order to dispatch OfflineAudioCompletionEvent.
+ RefPtr<ThreadSharedFloatArrayBufferList> mBuffer;
+ // An index representing the next offset in mBuffer to be written to.
+ uint32_t mWriteIndex;
+ uint32_t mNumberOfChannels;
+ // How many frames the OfflineAudioContext intends to produce.
+ uint32_t mLength;
+ float mSampleRate;
+ bool mBufferAllocated;
+};
+
+class DestinationNodeEngine final : public AudioNodeEngine {
+ public:
+ explicit DestinationNodeEngine(AudioDestinationNode* aNode)
+ : AudioNodeEngine(aNode),
+ mSampleRate(CubebUtils::PreferredSampleRate(
+ aNode->Context()->ShouldResistFingerprinting())),
+ mVolume(1.0f),
+ mAudibilityMonitor(
+ mSampleRate,
+ StaticPrefs::dom_media_silence_duration_for_audibility()),
+ mSuspended(false),
+ mIsAudible(false) {
+ MOZ_ASSERT(aNode);
+ }
+
+ void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
+ const AudioBlock& aInput, AudioBlock* aOutput,
+ bool* aFinished) override {
+ TRACE("DestinationNodeEngine::ProcessBlock");
+ *aOutput = aInput;
+ aOutput->mVolume *= mVolume;
+
+ if (mSuspended) {
+ return;
+ }
+
+ mAudibilityMonitor.Process(aInput);
+ bool isAudible =
+ mAudibilityMonitor.RecentlyAudible() && aOutput->mVolume > 0.0;
+ if (isAudible != mIsAudible) {
+ mIsAudible = isAudible;
+ RefPtr<AudioNodeTrack> track = aTrack;
+ auto r = [track, isAudible]() -> void {
+ MOZ_ASSERT(NS_IsMainThread());
+ RefPtr<AudioNode> node = track->Engine()->NodeMainThread();
+ if (node) {
+ RefPtr<AudioDestinationNode> destinationNode =
+ static_cast<AudioDestinationNode*>(node.get());
+ destinationNode->NotifyDataAudibleStateChanged(isAudible);
+ }
+ };
+
+ aTrack->Graph()->DispatchToMainThreadStableState(NS_NewRunnableFunction(
+ "dom::WebAudioAudibleStateChangedRunnable", r));
+ }
+ }
+
+ bool IsActive() const override {
+ // Keep processing to track track time, which is used for all timelines
+ // associated with the same AudioContext. If there are no other engines
+ // for the AudioContext, then this could return false to suspend the
+ // track, but the track is blocked anyway through
+ // AudioDestinationNode::SetIsOnlyNodeForContext().
+ return true;
+ }
+
+ void SetDoubleParameter(uint32_t aIndex, double aParam) override {
+ if (aIndex == VOLUME) {
+ mVolume = static_cast<float>(aParam);
+ }
+ }
+
+ void SetInt32Parameter(uint32_t aIndex, int32_t aParam) override {
+ if (aIndex == SUSPENDED) {
+ mSuspended = !!aParam;
+ if (mSuspended) {
+ mIsAudible = false;
+ }
+ }
+ }
+
+ enum Parameters {
+ VOLUME,
+ SUSPENDED,
+ };
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ private:
+ int mSampleRate;
+ float mVolume;
+ AudibilityMonitor mAudibilityMonitor;
+ bool mSuspended;
+ bool mIsAudible;
+};
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(AudioDestinationNode, AudioNode,
+ mAudioChannelAgent, mOfflineRenderingPromise)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AudioDestinationNode)
+ NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback)
+NS_INTERFACE_MAP_END_INHERITING(AudioNode)
+
+NS_IMPL_ADDREF_INHERITED(AudioDestinationNode, AudioNode)
+NS_IMPL_RELEASE_INHERITED(AudioDestinationNode, AudioNode)
+
+const AudioNodeTrack::Flags kTrackFlags =
+ AudioNodeTrack::NEED_MAIN_THREAD_CURRENT_TIME |
+ AudioNodeTrack::NEED_MAIN_THREAD_ENDED | AudioNodeTrack::EXTERNAL_OUTPUT;
+
+AudioDestinationNode::AudioDestinationNode(AudioContext* aContext,
+ bool aIsOffline,
+ uint32_t aNumberOfChannels,
+ uint32_t aLength)
+ : AudioNode(aContext, aNumberOfChannels, ChannelCountMode::Explicit,
+ ChannelInterpretation::Speakers),
+ mFramesToProduce(aLength),
+ mIsOffline(aIsOffline),
+ mCreatedTime(TimeStamp::Now()) {
+ if (aIsOffline) {
+ // The track is created on demand to avoid creating a graph thread that
+ // may not be used.
+ return;
+ }
+
+ // GetParentObject can return nullptr here. This will end up creating another
+ // MediaTrackGraph
+ MediaTrackGraph* graph = MediaTrackGraph::GetInstance(
+ MediaTrackGraph::AUDIO_THREAD_DRIVER, aContext->GetParentObject(),
+ aContext->SampleRate(), MediaTrackGraph::DEFAULT_OUTPUT_DEVICE);
+ AudioNodeEngine* engine = new DestinationNodeEngine(this);
+
+ mTrack = AudioNodeTrack::Create(aContext, engine, kTrackFlags, graph);
+ mTrack->AddMainThreadListener(this);
+ // null key is fine: only one output per mTrack
+ mTrack->AddAudioOutput(nullptr, nullptr);
+}
+
+void AudioDestinationNode::Init() {
+ // The reason we don't do that in ctor is because we have to keep AudioContext
+ // holding a strong reference to the destination node first. If we don't do
+ // that, initializing the agent would cause an unexpected destroy of the
+ // destination node when destroying the local weak reference inside
+ // `InitWithWeakCallback()`.
+ if (!mIsOffline) {
+ CreateAndStartAudioChannelAgent();
+ }
+}
+
+void AudioDestinationNode::Close() {
+ DestroyAudioChannelAgentIfExists();
+ ReleaseAudioWakeLockIfExists();
+}
+
+void AudioDestinationNode::CreateAndStartAudioChannelAgent() {
+ MOZ_ASSERT(!mIsOffline);
+ MOZ_ASSERT(!mAudioChannelAgent);
+
+ AudioChannelAgent* agent = new AudioChannelAgent();
+ nsresult rv = agent->InitWithWeakCallback(GetOwner(), this);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ AUDIO_CHANNEL_LOG("Failed to init audio channel agent");
+ return;
+ }
+
+ AudibleState state =
+ IsAudible() ? AudibleState::eAudible : AudibleState::eNotAudible;
+ rv = agent->NotifyStartedPlaying(state);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ AUDIO_CHANNEL_LOG("Failed to start audio channel agent");
+ return;
+ }
+
+ mAudioChannelAgent = agent;
+ mAudioChannelAgent->PullInitialUpdate();
+}
+
+AudioDestinationNode::~AudioDestinationNode() {
+ MOZ_ASSERT(!mAudioChannelAgent);
+ MOZ_ASSERT(!mWakeLock);
+ MOZ_ASSERT(!mCaptureTrackPort);
+}
+
+size_t AudioDestinationNode::SizeOfExcludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+ // Might be useful in the future:
+ // - mAudioChannelAgent
+ return amount;
+}
+
+size_t AudioDestinationNode::SizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+AudioNodeTrack* AudioDestinationNode::Track() {
+ if (mTrack) {
+ return mTrack;
+ }
+
+ AudioContext* context = Context();
+ if (!context) { // This node has been unlinked.
+ return nullptr;
+ }
+
+ MOZ_ASSERT(mIsOffline, "Realtime tracks are created in constructor");
+
+ // GetParentObject can return nullptr here when the document has been
+ // unlinked.
+ MediaTrackGraph* graph =
+ MediaTrackGraph::CreateNonRealtimeInstance(context->SampleRate());
+ AudioNodeEngine* engine = new OfflineDestinationNodeEngine(this);
+
+ mTrack = AudioNodeTrack::Create(context, engine, kTrackFlags, graph);
+ mTrack->AddMainThreadListener(this);
+
+ return mTrack;
+}
+
+void AudioDestinationNode::DestroyAudioChannelAgentIfExists() {
+ if (mAudioChannelAgent) {
+ mAudioChannelAgent->NotifyStoppedPlaying();
+ mAudioChannelAgent = nullptr;
+ if (IsCapturingAudio()) {
+ StopAudioCapturingTrack();
+ }
+ }
+}
+
+void AudioDestinationNode::DestroyMediaTrack() {
+ Close();
+ if (!mTrack) {
+ return;
+ }
+
+ Context()->ShutdownWorklet();
+
+ mTrack->RemoveMainThreadListener(this);
+ AudioNode::DestroyMediaTrack();
+}
+
+void AudioDestinationNode::NotifyMainThreadTrackEnded() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mTrack->IsEnded());
+
+ if (mIsOffline) {
+ AbstractThread::MainThread()->Dispatch(NewRunnableMethod(
+ "dom::AudioDestinationNode::FireOfflineCompletionEvent", this,
+ &AudioDestinationNode::FireOfflineCompletionEvent));
+ }
+}
+
+void AudioDestinationNode::FireOfflineCompletionEvent() {
+ AudioContext* context = Context();
+ context->OfflineClose();
+
+ OfflineDestinationNodeEngine* engine =
+ static_cast<OfflineDestinationNodeEngine*>(Track()->Engine());
+ RefPtr<AudioBuffer> renderedBuffer = engine->CreateAudioBuffer(context);
+ if (!renderedBuffer) {
+ return;
+ }
+ ResolvePromise(renderedBuffer);
+
+ context->Dispatch(do_AddRef(new OnCompleteTask(context, renderedBuffer)));
+
+ context->OnStateChanged(nullptr, AudioContextState::Closed);
+
+ mOfflineRenderingRef.Drop(this);
+}
+
+void AudioDestinationNode::ResolvePromise(AudioBuffer* aRenderedBuffer) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mIsOffline);
+ mOfflineRenderingPromise->MaybeResolve(aRenderedBuffer);
+}
+
+uint32_t AudioDestinationNode::MaxChannelCount() const {
+ return Context()->MaxChannelCount();
+}
+
+void AudioDestinationNode::SetChannelCount(uint32_t aChannelCount,
+ ErrorResult& aRv) {
+ if (aChannelCount > MaxChannelCount()) {
+ aRv.ThrowIndexSizeError(
+ nsPrintfCString("%u is larger than maxChannelCount", aChannelCount));
+ return;
+ }
+
+ if (aChannelCount == ChannelCount()) {
+ return;
+ }
+
+ AudioNode::SetChannelCount(aChannelCount, aRv);
+}
+
+void AudioDestinationNode::Mute() {
+ MOZ_ASSERT(Context() && !Context()->IsOffline());
+ SendDoubleParameterToTrack(DestinationNodeEngine::VOLUME, 0.0f);
+}
+
+void AudioDestinationNode::Unmute() {
+ MOZ_ASSERT(Context() && !Context()->IsOffline());
+ SendDoubleParameterToTrack(DestinationNodeEngine::VOLUME, 1.0f);
+}
+
+void AudioDestinationNode::Suspend() {
+ SendInt32ParameterToTrack(DestinationNodeEngine::SUSPENDED, 1);
+}
+
+void AudioDestinationNode::Resume() {
+ SendInt32ParameterToTrack(DestinationNodeEngine::SUSPENDED, 0);
+}
+
+void AudioDestinationNode::NotifyAudioContextStateChanged() {
+ UpdateFinalAudibleStateIfNeeded(AudibleChangedReasons::ePauseStateChanged);
+}
+
+void AudioDestinationNode::OfflineShutdown() {
+ MOZ_ASSERT(Context() && Context()->IsOffline(),
+ "Should only be called on a valid OfflineAudioContext");
+
+ mOfflineRenderingRef.Drop(this);
+}
+
+JSObject* AudioDestinationNode::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return AudioDestinationNode_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void AudioDestinationNode::StartRendering(Promise* aPromise) {
+ mOfflineRenderingPromise = aPromise;
+ mOfflineRenderingRef.Take(this);
+ Track()->Graph()->StartNonRealtimeProcessing(mFramesToProduce);
+}
+
+NS_IMETHODIMP
+AudioDestinationNode::WindowVolumeChanged(float aVolume, bool aMuted) {
+ MOZ_ASSERT(mAudioChannelAgent);
+ if (!mTrack) {
+ return NS_OK;
+ }
+
+ AUDIO_CHANNEL_LOG(
+ "AudioDestinationNode %p WindowVolumeChanged, "
+ "aVolume = %f, aMuted = %s\n",
+ this, aVolume, aMuted ? "true" : "false");
+
+ mAudioChannelVolume = aMuted ? 0.0f : aVolume;
+ mTrack->SetAudioOutputVolume(nullptr, mAudioChannelVolume);
+ UpdateFinalAudibleStateIfNeeded(AudibleChangedReasons::eVolumeChanged);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AudioDestinationNode::WindowSuspendChanged(nsSuspendedTypes aSuspend) {
+ MOZ_ASSERT(mAudioChannelAgent);
+ if (!mTrack) {
+ return NS_OK;
+ }
+
+ const bool shouldDisable = aSuspend == nsISuspendedTypes::SUSPENDED_BLOCK;
+ if (mAudioChannelDisabled == shouldDisable) {
+ return NS_OK;
+ }
+ mAudioChannelDisabled = shouldDisable;
+
+ AUDIO_CHANNEL_LOG(
+ "AudioDestinationNode %p WindowSuspendChanged, shouldDisable = %d\n",
+ this, mAudioChannelDisabled);
+
+ DisabledTrackMode disabledMode = mAudioChannelDisabled
+ ? DisabledTrackMode::SILENCE_BLACK
+ : DisabledTrackMode::ENABLED;
+ mTrack->SetDisabledTrackMode(disabledMode);
+ UpdateFinalAudibleStateIfNeeded(AudibleChangedReasons::ePauseStateChanged);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AudioDestinationNode::WindowAudioCaptureChanged(bool aCapture) {
+ MOZ_ASSERT(mAudioChannelAgent);
+ if (!mTrack) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> ownerWindow = GetOwner();
+ if (!ownerWindow) {
+ return NS_OK;
+ }
+
+ if (aCapture == IsCapturingAudio()) {
+ return NS_OK;
+ }
+
+ if (aCapture) {
+ StartAudioCapturingTrack();
+ } else {
+ StopAudioCapturingTrack();
+ }
+
+ return NS_OK;
+}
+
+bool AudioDestinationNode::IsCapturingAudio() const {
+ return mCaptureTrackPort != nullptr;
+}
+
+void AudioDestinationNode::StartAudioCapturingTrack() {
+ MOZ_ASSERT(!IsCapturingAudio());
+ nsCOMPtr<nsPIDOMWindowInner> window = Context()->GetParentObject();
+ uint64_t id = window->WindowID();
+ mCaptureTrackPort = mTrack->Graph()->ConnectToCaptureTrack(id, mTrack);
+}
+
+void AudioDestinationNode::StopAudioCapturingTrack() {
+ MOZ_ASSERT(IsCapturingAudio());
+ mCaptureTrackPort->Destroy();
+ mCaptureTrackPort = nullptr;
+}
+
+void AudioDestinationNode::CreateAudioWakeLockIfNeeded() {
+ if (!mWakeLock && IsAudible()) {
+ RefPtr<power::PowerManagerService> pmService =
+ power::PowerManagerService::GetInstance();
+ NS_ENSURE_TRUE_VOID(pmService);
+
+ ErrorResult rv;
+ mWakeLock = pmService->NewWakeLock(u"audio-playing"_ns, GetOwner(), rv);
+ }
+}
+
+void AudioDestinationNode::ReleaseAudioWakeLockIfExists() {
+ if (mWakeLock) {
+ IgnoredErrorResult rv;
+ mWakeLock->Unlock(rv);
+ mWakeLock = nullptr;
+ }
+}
+
+void AudioDestinationNode::NotifyDataAudibleStateChanged(bool aAudible) {
+ MOZ_ASSERT(!mIsOffline);
+
+ AUDIO_CHANNEL_LOG(
+ "AudioDestinationNode %p NotifyDataAudibleStateChanged, audible=%d", this,
+ aAudible);
+
+ if (mDurationBeforeFirstTimeAudible.IsZero()) {
+ MOZ_ASSERT(aAudible);
+ mDurationBeforeFirstTimeAudible = TimeStamp::Now() - mCreatedTime;
+ Telemetry::Accumulate(Telemetry::WEB_AUDIO_BECOMES_AUDIBLE_TIME,
+ mDurationBeforeFirstTimeAudible.ToSeconds());
+ }
+
+ mIsDataAudible = aAudible;
+ UpdateFinalAudibleStateIfNeeded(AudibleChangedReasons::eDataAudibleChanged);
+}
+
+void AudioDestinationNode::UpdateFinalAudibleStateIfNeeded(
+ AudibleChangedReasons aReason) {
+ // The audio context has been closed and we've destroyed the agent.
+ if (!mAudioChannelAgent) {
+ return;
+ }
+ const bool newAudibleState = IsAudible();
+ if (mFinalAudibleState == newAudibleState) {
+ return;
+ }
+ AUDIO_CHANNEL_LOG("AudioDestinationNode %p Final audible state=%d", this,
+ newAudibleState);
+ mFinalAudibleState = newAudibleState;
+ AudibleState state =
+ mFinalAudibleState ? AudibleState::eAudible : AudibleState::eNotAudible;
+ mAudioChannelAgent->NotifyStartedAudible(state, aReason);
+ if (mFinalAudibleState) {
+ CreateAudioWakeLockIfNeeded();
+ } else {
+ ReleaseAudioWakeLockIfExists();
+ }
+}
+
+bool AudioDestinationNode::IsAudible() const {
+ // The desitionation node will be regarded as audible if all following
+ // conditions are true.
+ // (1) data audible state : both audio input and output are audible
+ // (2) window audible state : the tab isn't muted by tab sound indicator
+ // (3) audio context state : audio context should be running
+ return Context()->State() == AudioContextState::Running && mIsDataAudible &&
+ mAudioChannelVolume != 0.0;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/AudioDestinationNode.h b/dom/media/webaudio/AudioDestinationNode.h
new file mode 100644
index 0000000000..df4a9b16ec
--- /dev/null
+++ b/dom/media/webaudio/AudioDestinationNode.h
@@ -0,0 +1,133 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef AudioDestinationNode_h_
+#define AudioDestinationNode_h_
+
+#include "AudioChannelService.h"
+#include "AudioNode.h"
+#include "AudioChannelAgent.h"
+#include "mozilla/TimeStamp.h"
+
+namespace mozilla::dom {
+
+class AudioContext;
+class WakeLock;
+
+class AudioDestinationNode final : public AudioNode,
+ public nsIAudioChannelAgentCallback,
+ public MainThreadMediaTrackListener {
+ public:
+ // This node type knows what MediaTrackGraph to use based on
+ // whether it's in offline mode.
+ AudioDestinationNode(AudioContext* aContext, bool aIsOffline,
+ uint32_t aNumberOfChannels, uint32_t aLength);
+
+ void DestroyMediaTrack() override;
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AudioDestinationNode, AudioNode)
+ NS_DECL_NSIAUDIOCHANNELAGENTCALLBACK
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ uint16_t NumberOfOutputs() const final { return 0; }
+
+ uint32_t MaxChannelCount() const;
+ void SetChannelCount(uint32_t aChannelCount, ErrorResult& aRv) override;
+
+ void Init();
+ void Close();
+
+ // Returns the track or null after unlink.
+ AudioNodeTrack* Track();
+
+ void Mute();
+ void Unmute();
+
+ void Suspend();
+ void Resume();
+
+ void StartRendering(Promise* aPromise);
+
+ void OfflineShutdown();
+
+ void NotifyMainThreadTrackEnded() override;
+ void FireOfflineCompletionEvent();
+
+ const char* NodeType() const override { return "AudioDestinationNode"; }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+ void NotifyDataAudibleStateChanged(bool aAudible);
+ void ResolvePromise(AudioBuffer* aRenderedBuffer);
+
+ unsigned long Length() {
+ MOZ_ASSERT(mIsOffline);
+ return mFramesToProduce;
+ }
+
+ void NotifyAudioContextStateChanged();
+
+ protected:
+ virtual ~AudioDestinationNode();
+
+ private:
+ // This would be created for non-offline audio context in order to receive
+ // tab's mute/suspend/audio capture state change and update the audible state
+ // to the tab.
+ void CreateAndStartAudioChannelAgent();
+ void DestroyAudioChannelAgentIfExists();
+ RefPtr<AudioChannelAgent> mAudioChannelAgent;
+
+ // These members are related to audio capturing. We would start capturing
+ // audio if we're starting capturing audio from whole window, and MUST stop
+ // capturing explicitly when we don't need to capture audio any more, because
+ // we have to release the resource we allocated before.
+ bool IsCapturingAudio() const;
+ void StartAudioCapturingTrack();
+ void StopAudioCapturingTrack();
+ RefPtr<MediaInputPort> mCaptureTrackPort;
+
+ // These members are used to determine if the destination node is actual
+ // audible and `mFinalAudibleState` represents the final result.
+ using AudibleChangedReasons = AudioChannelService::AudibleChangedReasons;
+ using AudibleState = AudioChannelService::AudibleState;
+ void UpdateFinalAudibleStateIfNeeded(AudibleChangedReasons aReason);
+ bool IsAudible() const;
+ bool mFinalAudibleState = false;
+ bool mIsDataAudible = false;
+ float mAudioChannelVolume = 1.0;
+
+ // True if the audio channel disables the track for unvisited tab, and the
+ // track will be enabled again when the tab gets first visited, or a user
+ // presses the tab play icon.
+ bool mAudioChannelDisabled = false;
+
+ // When the destination node is audible, we would request a wakelock to
+ // prevent computer from sleeping in order to keep audio playing.
+ void CreateAudioWakeLockIfNeeded();
+ void ReleaseAudioWakeLockIfExists();
+ RefPtr<WakeLock> mWakeLock;
+
+ SelfReference<AudioDestinationNode> mOfflineRenderingRef;
+ uint32_t mFramesToProduce;
+
+ RefPtr<Promise> mOfflineRenderingPromise;
+
+ bool mIsOffline;
+
+ // These varaibles are used to know how long AudioContext would become audible
+ // since it was created.
+ TimeStamp mCreatedTime;
+ TimeDuration mDurationBeforeFirstTimeAudible;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/media/webaudio/AudioEventTimeline.cpp b/dom/media/webaudio/AudioEventTimeline.cpp
new file mode 100644
index 0000000000..f960cadd62
--- /dev/null
+++ b/dom/media/webaudio/AudioEventTimeline.cpp
@@ -0,0 +1,513 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AudioEventTimeline.h"
+#include "AudioNodeTrack.h"
+
+#include "mozilla/ErrorResult.h"
+
+using mozilla::Span;
+
+// v1 and v0 are passed from float variables but converted to double for
+// double precision interpolation.
+static void FillLinearRamp(double aBufferStartTime, Span<float> aBuffer,
+ double t0, double v0, double t1, double v1) {
+ double bufferStartDelta = aBufferStartTime - t0;
+ double gradient = (v1 - v0) / (t1 - t0);
+ for (size_t i = 0; i < aBuffer.Length(); ++i) {
+ double v = v0 + (bufferStartDelta + static_cast<double>(i)) * gradient;
+ aBuffer[i] = static_cast<float>(v);
+ }
+}
+
+static void FillExponentialRamp(double aBufferStartTime, Span<float> aBuffer,
+ double t0, float v0, double t1, float v1) {
+ MOZ_ASSERT(aBuffer.Length() >= 1);
+ double fullRatio = static_cast<double>(v1) / v0;
+ if (v0 == 0.f || fullRatio < 0.0) {
+ std::fill_n(aBuffer.Elements(), aBuffer.Length(), v0);
+ return;
+ }
+
+ double tDelta = t1 - t0;
+ // Calculate the value for the first tick from the curve initial value.
+ // v(t) = v0 * (v1/v0)^((t-t0)/(t1-t0))
+ double exponent = (aBufferStartTime - t0) / tDelta;
+ // The power function can amplify rounding error in the exponent by
+ // ((t−t0)/(t1−t0)) ln (v1/v0). The single precision exponent argument for
+ // powf() would be sufficient when max(v1/v0,v0/v1) <= e, where e is Euler's
+ // number, but fdlibm's single precision powf() is not expected to provide
+ // speed advantages over double precision pow().
+ double v = v0 * fdlibm_pow(fullRatio, exponent);
+ aBuffer[0] = static_cast<float>(v);
+ if (aBuffer.Length() == 1) {
+ return;
+ }
+
+ // Use the inter-tick ratio to calculate values at other ticks.
+ // v(t+1) = (v1/v0)^(1/(t1-t0)) * v(t)
+ // Double precision is used so that accumulation of rounding error is not
+ // significant.
+ double tickRatio = fdlibm_pow(fullRatio, 1.0 / tDelta);
+ for (size_t i = 1; i < aBuffer.Length(); ++i) {
+ v *= tickRatio;
+ aBuffer[i] = static_cast<float>(v);
+ }
+}
+
+template <typename TimeType, typename DurationType>
+static size_t LimitedCountForDuration(size_t aMax, DurationType aDuration);
+
+template <>
+size_t LimitedCountForDuration<double>(size_t aMax, double aDuration) {
+ // aDuration is in seconds, so tick arithmetic is inappropriate,
+ // and unnecessary.
+ // GetValuesAtTime() is not available, so at most one value is fetched.
+ MOZ_ASSERT(aMax <= 1);
+ return aMax;
+}
+template <>
+size_t LimitedCountForDuration<int64_t>(size_t aMax, int64_t aDuration) {
+ MOZ_ASSERT(aDuration >= 0);
+ // int64_t aDuration is in ticks.
+ // On 32-bit systems, aDuration may be larger than SIZE_MAX.
+ // Determine the larger with int64_t to avoid truncating before the
+ // comparison.
+ return static_cast<int64_t>(aMax) <= aDuration
+ ? aMax
+ : static_cast<size_t>(aDuration);
+}
+template <>
+size_t LimitedCountForDuration<int64_t>(size_t aMax, double aDuration) {
+ MOZ_ASSERT(aDuration >= 0);
+ // double aDuration is in ticks.
+ // AudioTimelineEvent::mDuration may be larger than INT64_MAX.
+ // On 32-bit systems, mDuration may be larger than SIZE_MAX.
+ // Determine the larger with double to avoid truncating before the
+ // comparison.
+ return static_cast<double>(aMax) <= aDuration
+ ? aMax
+ : static_cast<size_t>(aDuration);
+}
+
+static float* NewCurveCopy(Span<const float> aCurve) {
+ if (aCurve.Length() == 0) {
+ return nullptr;
+ }
+
+ float* curve = new float[aCurve.Length()];
+ mozilla::PodCopy(curve, aCurve.Elements(), aCurve.Length());
+ return curve;
+}
+
+namespace mozilla::dom {
+
+AudioTimelineEvent::AudioTimelineEvent(Type aType, double aTime, float aValue,
+ double aTimeConstant)
+ : mType(aType),
+ mValue(aValue),
+ mTimeConstant(aTimeConstant),
+ mPerTickRatio(std::numeric_limits<double>::quiet_NaN()),
+ mTime(aTime) {}
+
+AudioTimelineEvent::AudioTimelineEvent(Type aType,
+ const nsTArray<float>& aValues,
+ double aStartTime, double aDuration)
+ : mType(aType),
+ mCurveLength(aValues.Length()),
+ mCurve(NewCurveCopy(aValues)),
+ mDuration(aDuration),
+ mTime(aStartTime) {
+ MOZ_ASSERT(aType == AudioTimelineEvent::SetValueCurve);
+}
+
+// cppcoreguidelines-pro-type-member-init does not know PodCopy().
+// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init)
+AudioTimelineEvent::AudioTimelineEvent(const AudioTimelineEvent& rhs)
+ : mType(rhs.mType) {
+ PodCopy(this, &rhs, 1);
+
+ if (rhs.mType == AudioTimelineEvent::SetValueCurve) {
+ mCurve = NewCurveCopy(Span(rhs.mCurve, rhs.mCurveLength));
+ }
+}
+
+AudioTimelineEvent::~AudioTimelineEvent() {
+ if (mType == AudioTimelineEvent::SetValueCurve) {
+ delete[] mCurve;
+ }
+}
+
+template <class TimeType>
+double AudioTimelineEvent::EndTime() const {
+ MOZ_ASSERT(mType != AudioTimelineEvent::SetTarget);
+ if (mType == AudioTimelineEvent::SetValueCurve) {
+ return Time<TimeType>() + mDuration;
+ }
+ return Time<TimeType>();
+};
+
+float AudioTimelineEvent::EndValue() const {
+ if (mType == AudioTimelineEvent::SetValueCurve) {
+ return mCurve[mCurveLength - 1];
+ }
+ return mValue;
+};
+
+void AudioTimelineEvent::ConvertToTicks(AudioNodeTrack* aDestination) {
+ mTime = aDestination->SecondsToNearestTrackTime(mTime.Get<double>());
+ switch (mType) {
+ case SetTarget:
+ mTimeConstant *= aDestination->mSampleRate;
+ // exp(-1/timeConstant) is usually very close to 1, but its effect
+ // depends on the difference from 1 and rounding errors would
+ // accumulate, so use double precision to retain precision in the
+ // difference. Single precision expm1f() would be sufficient, but the
+ // arithmetic in AudioTimelineEvent::FillTargetApproach() is simpler
+ // with exp().
+ mPerTickRatio =
+ mTimeConstant == 0.0 ? 0.0 : fdlibm_exp(-1.0 / mTimeConstant);
+
+ break;
+ case SetValueCurve:
+ mDuration *= aDestination->mSampleRate;
+ break;
+ default:
+ break;
+ }
+}
+
+template <class TimeType>
+void AudioTimelineEvent::FillTargetApproach(TimeType aBufferStartTime,
+ Span<float> aBuffer,
+ double v0) const {
+ MOZ_ASSERT(mType == SetTarget);
+ MOZ_ASSERT(aBuffer.Length() >= 1);
+ double v1 = mValue;
+ double vDelta = v0 - v1;
+ if (vDelta == 0.0 || mTimeConstant == 0.0) {
+ std::fill_n(aBuffer.Elements(), aBuffer.Length(), mValue);
+ return;
+ }
+
+ // v(t) = v1 + vDelta(t) where vDelta(t) = (v0-v1) * e^(-(t-t0)/timeConstant).
+ // Calculate the value for the first element in the buffer using this
+ // formulation.
+ vDelta *= fdlibm_expf(-(aBufferStartTime - Time<TimeType>()) / mTimeConstant);
+ for (size_t i = 0; true;) {
+ aBuffer[i] = static_cast<float>(v1 + vDelta);
+ ++i;
+ if (i == aBuffer.Length()) {
+ return;
+ }
+ // For other buffer elements, use the pre-computed exp(-1/timeConstant)
+ // for the inter-tick ratio of the difference from the target.
+ // vDelta(t+1) = vDelta(t) * e^(-1/timeConstant)
+ vDelta *= mPerTickRatio;
+ }
+}
+
+static_assert(TRACK_TIME_MAX >> FloatingPoint<double>::kSignificandWidth == 0,
+ "double precision must be exact for integer tick counts");
+template <class TimeType>
+void AudioTimelineEvent::FillFromValueCurve(TimeType aBufferStartTime,
+ Span<float> aBuffer) const {
+ MOZ_ASSERT(mType == SetValueCurve);
+ double curveStartTime = Time<TimeType>();
+ MOZ_ASSERT(aBufferStartTime >= curveStartTime);
+ MOZ_ASSERT(aBufferStartTime - curveStartTime <= mDuration);
+ MOZ_ASSERT((std::is_same<TimeType, int64_t>::value) || aBuffer.Length() == 1);
+ MOZ_ASSERT((!std::is_same<TimeType, int64_t>::value) ||
+ aBufferStartTime - curveStartTime + aBuffer.Length() - 1 <=
+ mDuration);
+ uint32_t stepCount = mCurveLength - 1;
+ double timeStep = mDuration / stepCount;
+
+ for (size_t fillStart = 0; fillStart < aBuffer.Length();) {
+ // Find the curve sample index, spec'd as `k`, corresponding to a time less
+ // than or equal to the first buffer element to be filled.
+ double stepPos =
+ (aBufferStartTime + fillStart - curveStartTime) / mDuration * stepCount;
+ // GetValuesAtTimeHelperInternal() calls this only when
+ // aBufferStartTime + fillStart - curveStartTime <= mDuration.
+ MOZ_ASSERT(stepPos >= 0 && stepPos <= UINT32_MAX - 1);
+ uint32_t currentNode = floor(stepPos);
+ if (currentNode >= stepCount) {
+ auto remaining = aBuffer.From(fillStart);
+ std::fill_n(remaining.Elements(), remaining.Length(), mCurve[stepCount]);
+ return;
+ }
+
+ // Linearly interpolate to fill the buffer elements for any ticks between
+ // curve samples k and k + 1 inclusive.
+ double tCurrent = curveStartTime + currentNode * timeStep;
+ uint32_t nextNode = currentNode + 1;
+ double tNext = curveStartTime + nextNode * timeStep;
+ // The first buffer index that cannot be filled with these curve samples
+ size_t fillEnd = LimitedCountForDuration<TimeType>(
+ aBuffer.Length(),
+ // This parameter is used only when time is in ticks:
+ // If tNext aligns exactly with a tick then fill to tNext, thus
+ // ensuring that fillStart is advanced even when timeStep is so small
+ // that tNext == tCurrent.
+ floor(tNext - aBufferStartTime) + 1.0);
+ TimeType fillStartTime =
+ aBufferStartTime + static_cast<TimeType>(fillStart);
+ FillLinearRamp(fillStartTime, aBuffer.FromTo(fillStart, fillEnd), tCurrent,
+ mCurve[currentNode], tNext, mCurve[nextNode]);
+ fillStart = fillEnd;
+ }
+}
+
+template <class TimeType>
+float AudioEventTimeline::ComputeSetTargetStartValue(
+ const AudioTimelineEvent* aPreviousEvent, TimeType aTime) {
+ mSetTargetStartTime = aTime;
+ GetValuesAtTimeHelperInternal(aTime, Span(&mSetTargetStartValue, 1),
+ aPreviousEvent, nullptr);
+ return mSetTargetStartValue;
+}
+
+template void AudioEventTimeline::CleanupEventsOlderThan(double);
+template void AudioEventTimeline::CleanupEventsOlderThan(int64_t);
+template <class TimeType>
+void AudioEventTimeline::CleanupEventsOlderThan(TimeType aTime) {
+ auto TimeOf =
+ [](const decltype(mEvents)::const_iterator& aEvent) -> TimeType {
+ return aEvent->Time<TimeType>();
+ };
+
+ if (mSimpleValue.isSome()) {
+ return; // already only a single event
+ }
+
+ // Find first event to keep. Keep one event prior to aTime.
+ auto begin = mEvents.cbegin();
+ auto end = mEvents.cend();
+ auto event = begin + 1;
+ for (; event < end && aTime > TimeOf(event); ++event) {
+ }
+ auto firstToKeep = event - 1;
+
+ if (firstToKeep->mType != AudioTimelineEvent::SetTarget) {
+ // The value is constant if there is a single remaining non-SetTarget event
+ // that has already passed.
+ if (end - firstToKeep == 1 && aTime >= firstToKeep->EndTime<TimeType>()) {
+ mSimpleValue.emplace(firstToKeep->EndValue());
+ }
+ } else {
+ // The firstToKeep event is a SetTarget. Set its initial value if
+ // not already set. First find the most recent event where the value at
+ // the end time of the event is known, either from the event or for
+ // SetTarget events because it has already been calculated. This may not
+ // have been calculated if GetValuesAtTime() was not called for the start
+ // time of the SetTarget event.
+ for (event = firstToKeep;
+ event > begin && event->mType == AudioTimelineEvent::SetTarget &&
+ TimeOf(event) > mSetTargetStartTime.Get<TimeType>();
+ --event) {
+ }
+ // Compute SetTarget start times.
+ for (; event < firstToKeep; ++event) {
+ MOZ_ASSERT((event + 1)->mType == AudioTimelineEvent::SetTarget);
+ ComputeSetTargetStartValue(&*event, TimeOf(event + 1));
+ }
+ }
+ if (firstToKeep == begin) {
+ return;
+ }
+
+ mEvents.RemoveElementsRange(begin, firstToKeep);
+}
+
+// This method computes the AudioParam value at a given time based on the event
+// timeline
+template <class TimeType>
+void AudioEventTimeline::GetValuesAtTimeHelper(TimeType aTime, float* aBuffer,
+ const size_t aSize) {
+ MOZ_ASSERT(aBuffer);
+ MOZ_ASSERT(aSize);
+
+ auto TimeOf = [](const AudioTimelineEvent& aEvent) -> TimeType {
+ return aEvent.Time<TimeType>();
+ };
+
+ size_t eventIndex = 0;
+ const AudioTimelineEvent* previous = nullptr;
+
+ // Let's remove old events except the last one: we need it to calculate some
+ // curves.
+ CleanupEventsOlderThan(aTime);
+
+ for (size_t bufferIndex = 0; bufferIndex < aSize;) {
+ bool timeMatchesEventIndex = false;
+ const AudioTimelineEvent* next;
+ for (;; ++eventIndex) {
+ if (eventIndex >= mEvents.Length()) {
+ next = nullptr;
+ break;
+ }
+
+ next = &mEvents[eventIndex];
+ if (aTime < TimeOf(*next)) {
+ break;
+ }
+
+#ifdef DEBUG
+ MOZ_ASSERT(next->mType == AudioTimelineEvent::SetValueAtTime ||
+ next->mType == AudioTimelineEvent::SetTarget ||
+ next->mType == AudioTimelineEvent::LinearRamp ||
+ next->mType == AudioTimelineEvent::ExponentialRamp ||
+ next->mType == AudioTimelineEvent::SetValueCurve);
+#endif
+
+ if (TimesEqual(aTime, TimeOf(*next))) {
+ timeMatchesEventIndex = true;
+ aBuffer[bufferIndex] = GetValueAtTimeOfEvent<TimeType>(next, previous);
+ // Advance to next event, which may or may not have the same time.
+ }
+ previous = next;
+ }
+
+ if (timeMatchesEventIndex) {
+ // The time matches one of the events exactly.
+ MOZ_ASSERT(TimesEqual(aTime, TimeOf(mEvents[eventIndex - 1])));
+ ++bufferIndex;
+ ++aTime;
+ } else {
+ size_t count = aSize - bufferIndex;
+ if (next) {
+ count = LimitedCountForDuration<TimeType>(count, TimeOf(*next) - aTime);
+ }
+ GetValuesAtTimeHelperInternal(aTime, Span(aBuffer + bufferIndex, count),
+ previous, next);
+ bufferIndex += count;
+ aTime += static_cast<TimeType>(count);
+ }
+ }
+}
+template void AudioEventTimeline::GetValuesAtTimeHelper(double aTime,
+ float* aBuffer,
+ const size_t aSize);
+template void AudioEventTimeline::GetValuesAtTimeHelper(int64_t aTime,
+ float* aBuffer,
+ const size_t aSize);
+
+template <class TimeType>
+float AudioEventTimeline::GetValueAtTimeOfEvent(
+ const AudioTimelineEvent* aEvent, const AudioTimelineEvent* aPrevious) {
+ TimeType time = aEvent->Time<TimeType>();
+ switch (aEvent->mType) {
+ case AudioTimelineEvent::SetTarget:
+ // Start the curve, from the last value of the previous event.
+ return ComputeSetTargetStartValue(aPrevious, time);
+ case AudioTimelineEvent::SetValueCurve:
+ return aEvent->StartValue();
+ default:
+ // For other event types
+ return aEvent->NominalValue();
+ }
+}
+
+template <class TimeType>
+void AudioEventTimeline::GetValuesAtTimeHelperInternal(
+ TimeType aStartTime, Span<float> aBuffer,
+ const AudioTimelineEvent* aPrevious, const AudioTimelineEvent* aNext) {
+ MOZ_ASSERT(aBuffer.Length() >= 1);
+ MOZ_ASSERT((std::is_same<TimeType, int64_t>::value) || aBuffer.Length() == 1);
+
+ // If the requested time is before all of the existing events
+ if (!aPrevious) {
+ std::fill_n(aBuffer.Elements(), aBuffer.Length(), mDefaultValue);
+ return;
+ }
+
+ auto TimeOf = [](const AudioTimelineEvent* aEvent) -> TimeType {
+ return aEvent->Time<TimeType>();
+ };
+ auto EndTimeOf = [](const AudioTimelineEvent* aEvent) -> double {
+ return aEvent->EndTime<TimeType>();
+ };
+
+ // SetTarget nodes can be handled no matter what their next node is (if
+ // they have one)
+ if (aPrevious->mType == AudioTimelineEvent::SetTarget) {
+ aPrevious->FillTargetApproach(aStartTime, aBuffer, mSetTargetStartValue);
+ return;
+ }
+
+ // SetValueCurve events can be handled no matter what their next node is
+ // (if they have one), when aStartTime is in the curve region.
+ if (aPrevious->mType == AudioTimelineEvent::SetValueCurve) {
+ double remainingDuration =
+ TimeOf(aPrevious) - aStartTime + aPrevious->Duration();
+ if (remainingDuration >= 0.0) {
+ // aBuffer.Length() is 1 if remainingDuration is not in ticks.
+ size_t count = LimitedCountForDuration<TimeType>(
+ aBuffer.Length(),
+ // This parameter is used only when time is in ticks:
+ // Fill the last tick in the curve before possible ramps below.
+ floor(remainingDuration) + 1.0);
+ // GetValueAtTimeOfEvent() will set the value at the end of the curve if
+ // another event immediately follows.
+ MOZ_ASSERT(!aNext ||
+ aStartTime + static_cast<TimeType>(count - 1) < TimeOf(aNext));
+ aPrevious->FillFromValueCurve(aStartTime,
+ Span(aBuffer.Elements(), count));
+ aBuffer = aBuffer.From(count);
+ if (aBuffer.Length() == 0) {
+ return;
+ }
+ aStartTime += static_cast<TimeType>(count);
+ }
+ }
+
+ // Handle the cases where our range ends up in a ramp event
+ if (aNext) {
+ switch (aNext->mType) {
+ case AudioTimelineEvent::LinearRamp:
+ FillLinearRamp(aStartTime, aBuffer, EndTimeOf(aPrevious),
+ aPrevious->EndValue(), TimeOf(aNext),
+ aNext->NominalValue());
+ return;
+ case AudioTimelineEvent::ExponentialRamp:
+ FillExponentialRamp(aStartTime, aBuffer, EndTimeOf(aPrevious),
+ aPrevious->EndValue(), TimeOf(aNext),
+ aNext->NominalValue());
+ return;
+ case AudioTimelineEvent::SetValueAtTime:
+ case AudioTimelineEvent::SetTarget:
+ case AudioTimelineEvent::SetValueCurve:
+ break;
+ case AudioTimelineEvent::SetValue:
+ case AudioTimelineEvent::Cancel:
+ case AudioTimelineEvent::Track:
+ MOZ_ASSERT(false, "Should have been handled earlier.");
+ }
+ }
+
+ // Now handle all other cases
+ switch (aPrevious->mType) {
+ case AudioTimelineEvent::SetValueAtTime:
+ case AudioTimelineEvent::LinearRamp:
+ case AudioTimelineEvent::ExponentialRamp:
+ break;
+ case AudioTimelineEvent::SetValueCurve:
+ MOZ_ASSERT(aStartTime - TimeOf(aPrevious) >= aPrevious->Duration());
+ break;
+ case AudioTimelineEvent::SetTarget:
+ MOZ_FALLTHROUGH_ASSERT("AudioTimelineEvent::SetTarget");
+ case AudioTimelineEvent::SetValue:
+ case AudioTimelineEvent::Cancel:
+ case AudioTimelineEvent::Track:
+ MOZ_ASSERT(false, "Should have been handled earlier.");
+ }
+ // If the next event type is neither linear or exponential ramp, the
+ // value is constant.
+ std::fill_n(aBuffer.Elements(), aBuffer.Length(), aPrevious->EndValue());
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/AudioEventTimeline.h b/dom/media/webaudio/AudioEventTimeline.h
new file mode 100644
index 0000000000..9284fc5b6d
--- /dev/null
+++ b/dom/media/webaudio/AudioEventTimeline.h
@@ -0,0 +1,418 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef AudioEventTimeline_h_
+#define AudioEventTimeline_h_
+
+#include <algorithm>
+#include "mozilla/Assertions.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/ErrorResult.h"
+
+#include "MainThreadUtils.h"
+#include "nsTArray.h"
+#include "math.h"
+#include "WebAudioUtils.h"
+
+// XXX Avoid including this here by moving function bodies to the cpp file
+#include "js/GCAPI.h"
+
+namespace mozilla {
+
+class AudioNodeTrack;
+
+namespace dom {
+
+struct AudioTimelineEvent {
+ enum Type : uint32_t {
+ SetValue,
+ SetValueAtTime,
+ LinearRamp,
+ ExponentialRamp,
+ SetTarget,
+ SetValueCurve,
+ Track,
+ Cancel
+ };
+
+ class TimeUnion {
+ public:
+ // double 0.0 is bit-identical to int64_t 0.
+ TimeUnion()
+ : mSeconds()
+#if DEBUG
+ ,
+ mIsInSeconds(true),
+ mIsInTicks(true)
+#endif
+ {
+ }
+ explicit TimeUnion(double aTime)
+ : mSeconds(aTime)
+#if DEBUG
+ ,
+ mIsInSeconds(true),
+ mIsInTicks(false)
+#endif
+ {
+ }
+ explicit TimeUnion(int64_t aTime)
+ : mTicks(aTime)
+#if DEBUG
+ ,
+ mIsInSeconds(false),
+ mIsInTicks(true)
+#endif
+ {
+ }
+
+ double operator=(double aTime) {
+#if DEBUG
+ mIsInSeconds = true;
+ mIsInTicks = true;
+#endif
+ return mSeconds = aTime;
+ }
+ int64_t operator=(int64_t aTime) {
+#if DEBUG
+ mIsInSeconds = true;
+ mIsInTicks = true;
+#endif
+ return mTicks = aTime;
+ }
+
+ template <class TimeType>
+ TimeType Get() const;
+
+ private:
+ union {
+ double mSeconds;
+ int64_t mTicks;
+ };
+#ifdef DEBUG
+ bool mIsInSeconds;
+ bool mIsInTicks;
+
+ public:
+ bool IsInTicks() const { return mIsInTicks; };
+#endif
+ };
+
+ AudioTimelineEvent(Type aType, double aTime, float aValue,
+ double aTimeConstant = 0.0);
+ // For SetValueCurve
+ AudioTimelineEvent(Type aType, const nsTArray<float>& aValues,
+ double aStartTime, double aDuration);
+ AudioTimelineEvent(const AudioTimelineEvent& rhs);
+ ~AudioTimelineEvent();
+
+ template <class TimeType>
+ TimeType Time() const {
+ return mTime.Get<TimeType>();
+ }
+ // If this event is a curve event, this returns the end time of the curve.
+ // Otherwise, this returns the time of the event.
+ template <class TimeType>
+ double EndTime() const;
+
+ float NominalValue() const {
+ MOZ_ASSERT(mType != SetValueCurve);
+ return mValue;
+ }
+ float StartValue() const {
+ MOZ_ASSERT(mType == SetValueCurve);
+ return mCurve[0];
+ }
+ // Value for an event, or for a ValueCurve event, this is the value of the
+ // last element of the curve.
+ float EndValue() const;
+
+ double TimeConstant() const {
+ MOZ_ASSERT(mType == SetTarget);
+ return mTimeConstant;
+ }
+ uint32_t CurveLength() const {
+ MOZ_ASSERT(mType == SetValueCurve);
+ return mCurveLength;
+ }
+ double Duration() const {
+ MOZ_ASSERT(mType == SetValueCurve);
+ return mDuration;
+ }
+ /**
+ * Converts an AudioTimelineEvent's floating point time members to tick
+ * values with respect to a destination AudioNodeTrack.
+ *
+ * This needs to be called for each AudioTimelineEvent that gets sent to an
+ * AudioNodeEngine, on the engine side where the AudioTimlineEvent is
+ * received. This means that such engines need to be aware of their
+ * destination tracks as well.
+ */
+ void ConvertToTicks(AudioNodeTrack* aDestination);
+
+ template <class TimeType>
+ void FillTargetApproach(TimeType aBufferStartTime, Span<float> aBuffer,
+ double v0) const;
+ template <class TimeType>
+ void FillFromValueCurve(TimeType aBufferStartTime, Span<float> aBuffer) const;
+
+ const Type mType;
+
+ private:
+ union {
+ float mValue;
+ uint32_t mCurveLength; // for SetValueCurve
+ };
+ union {
+ double mTimeConstant;
+ // mCurve contains a buffer of SetValueCurve samples. We sample the
+ // values in the buffer depending on how far along we are in time.
+ // If we're at time T and the event has started as time T0 and has a
+ // duration of D, we sample the buffer at floor(mCurveLength*(T-T0)/D)
+ // if T<T0+D, and just take the last sample in the buffer otherwise.
+ float* mCurve;
+ };
+ union {
+ // mPerTickRatio is used only with SetTarget and int64_t TimeType.
+ double mPerTickRatio;
+ double mDuration; // for SetValueCurve
+ };
+
+ // This member is accessed using the `Time` method.
+ //
+ // The time for an event can either be in seconds or in ticks.
+ // Initially the time of the event is always in seconds.
+ // In order to convert it to ticks, call SetTimeInTicks. Once this
+ // method has been called for an event, the time cannot be converted
+ // back to seconds.
+ TimeUnion mTime;
+};
+
+template <>
+inline double AudioTimelineEvent::TimeUnion::Get<double>() const {
+ MOZ_ASSERT(mIsInSeconds);
+ return mSeconds;
+}
+template <>
+inline int64_t AudioTimelineEvent::TimeUnion::Get<int64_t>() const {
+ MOZ_ASSERT(mIsInTicks);
+ return mTicks;
+}
+
+class AudioEventTimeline {
+ public:
+ explicit AudioEventTimeline(float aDefaultValue)
+ : mDefaultValue(aDefaultValue),
+ mSetTargetStartValue(aDefaultValue),
+ mSimpleValue(Some(aDefaultValue)) {}
+
+ bool ValidateEvent(const AudioTimelineEvent& aEvent, ErrorResult& aRv) const {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ auto TimeOf = [](const AudioTimelineEvent& aEvent) -> double {
+ return aEvent.Time<double>();
+ };
+
+ // Validate the event itself
+ if (!WebAudioUtils::IsTimeValid(TimeOf(aEvent))) {
+ aRv.ThrowRangeError<MSG_INVALID_AUDIOPARAM_METHOD_START_TIME_ERROR>();
+ return false;
+ }
+
+ switch (aEvent.mType) {
+ case AudioTimelineEvent::SetValueCurve:
+ if (aEvent.CurveLength() < 2) {
+ aRv.ThrowInvalidStateError("Curve length must be at least 2");
+ return false;
+ }
+ if (aEvent.Duration() <= 0) {
+ aRv.ThrowRangeError(
+ "The curve duration for setValueCurveAtTime must be strictly "
+ "positive.");
+ return false;
+ }
+ MOZ_ASSERT(IsValid(aEvent.Duration()));
+ break;
+ case AudioTimelineEvent::SetTarget:
+ if (!WebAudioUtils::IsTimeValid(aEvent.TimeConstant())) {
+ aRv.ThrowRangeError(
+ "The exponential constant passed to setTargetAtTime must be "
+ "non-negative.");
+ return false;
+ }
+ [[fallthrough]];
+ default:
+ MOZ_ASSERT(IsValid(aEvent.NominalValue()));
+ }
+
+ // Make sure that new events don't fall within the duration of a
+ // curve event.
+ for (unsigned i = 0; i < mEvents.Length(); ++i) {
+ if (mEvents[i].mType == AudioTimelineEvent::SetValueCurve &&
+ TimeOf(mEvents[i]) <= TimeOf(aEvent) &&
+ TimeOf(mEvents[i]) + mEvents[i].Duration() > TimeOf(aEvent)) {
+ aRv.ThrowNotSupportedError("Can't add events during a curve event");
+ return false;
+ }
+ }
+
+ // Make sure that new curve events don't fall in a range which includes
+ // other events.
+ if (aEvent.mType == AudioTimelineEvent::SetValueCurve) {
+ for (unsigned i = 0; i < mEvents.Length(); ++i) {
+ if (TimeOf(aEvent) < TimeOf(mEvents[i]) &&
+ TimeOf(aEvent) + aEvent.Duration() > TimeOf(mEvents[i])) {
+ aRv.ThrowNotSupportedError(
+ "Can't add curve events that overlap other events");
+ return false;
+ }
+ }
+ }
+
+ // Make sure that invalid values are not used for exponential curves
+ if (aEvent.mType == AudioTimelineEvent::ExponentialRamp) {
+ if (aEvent.NominalValue() == 0.f) {
+ aRv.ThrowRangeError(
+ "The value passed to exponentialRampToValueAtTime must be "
+ "non-zero.");
+ return false;
+ }
+ }
+ return true;
+ }
+
+ template <typename TimeType>
+ void InsertEvent(const AudioTimelineEvent& aEvent) {
+ mSimpleValue.reset();
+ for (unsigned i = 0; i < mEvents.Length(); ++i) {
+ if (aEvent.Time<TimeType>() == mEvents[i].Time<TimeType>()) {
+ // If two events happen at the same time, have them in chronological
+ // order of insertion.
+ do {
+ ++i;
+ } while (i < mEvents.Length() &&
+ aEvent.Time<TimeType>() == mEvents[i].Time<TimeType>());
+ mEvents.InsertElementAt(i, aEvent);
+ return;
+ }
+ // Otherwise, place the event right after the latest existing event
+ if (aEvent.Time<TimeType>() < mEvents[i].Time<TimeType>()) {
+ mEvents.InsertElementAt(i, aEvent);
+ return;
+ }
+ }
+
+ // If we couldn't find a place for the event, just append it to the list
+ mEvents.AppendElement(aEvent);
+ }
+
+ bool HasSimpleValue() const { return mSimpleValue.isSome(); }
+
+ float GetValue() const {
+ // This method should only be called if HasSimpleValue() returns true
+ MOZ_ASSERT(HasSimpleValue());
+ return mSimpleValue.value();
+ }
+
+ void SetValue(float aValue) {
+ // FIXME: bug 1308435
+ // A spec change means this should instead behave like setValueAtTime().
+
+ // Silently don't change anything if there are any events
+ if (mEvents.IsEmpty()) {
+ mSetTargetStartValue = mDefaultValue = aValue;
+ mSimpleValue = Some(aValue);
+ }
+ }
+
+ template <typename TimeType>
+ void CancelScheduledValues(TimeType aStartTime) {
+ for (unsigned i = 0; i < mEvents.Length(); ++i) {
+ if (mEvents[i].Time<TimeType>() >= aStartTime) {
+#ifdef DEBUG
+ // Sanity check: the array should be sorted, so all of the following
+ // events should have a time greater than aStartTime too.
+ for (unsigned j = i + 1; j < mEvents.Length(); ++j) {
+ MOZ_ASSERT(mEvents[j].Time<TimeType>() >= aStartTime);
+ }
+#endif
+ mEvents.TruncateLength(i);
+ break;
+ }
+ }
+ if (mEvents.IsEmpty()) {
+ mSimpleValue = Some(mDefaultValue);
+ }
+ }
+
+ static bool TimesEqual(int64_t aLhs, int64_t aRhs) { return aLhs == aRhs; }
+
+ // Since we are going to accumulate error by adding 0.01 multiple time in a
+ // loop, we want to fuzz the equality check in GetValueAtTime.
+ static bool TimesEqual(double aLhs, double aRhs) {
+ const float kEpsilon = 0.0000000001f;
+ return fabs(aLhs - aRhs) < kEpsilon;
+ }
+
+ template <class TimeType>
+ float GetValueAtTime(TimeType aTime) {
+ float result;
+ GetValuesAtTimeHelper(aTime, &result, 1);
+ return result;
+ }
+
+ void GetValuesAtTime(int64_t aTime, float* aBuffer, const size_t aSize) {
+ MOZ_ASSERT(aBuffer);
+ GetValuesAtTimeHelper(aTime, aBuffer, aSize);
+ }
+ void GetValuesAtTime(double aTime, float* aBuffer,
+ const size_t aSize) = delete;
+
+ // Return the number of events scheduled
+ uint32_t GetEventCount() const { return mEvents.Length(); }
+
+ template <class TimeType>
+ void CleanupEventsOlderThan(TimeType aTime);
+
+ private:
+ template <class TimeType>
+ void GetValuesAtTimeHelper(TimeType aTime, float* aBuffer,
+ const size_t aSize);
+
+ template <class TimeType>
+ float GetValueAtTimeOfEvent(const AudioTimelineEvent* aEvent,
+ const AudioTimelineEvent* aPrevious);
+
+ template <class TimeType>
+ void GetValuesAtTimeHelperInternal(TimeType aStartTime, Span<float> aBuffer,
+ const AudioTimelineEvent* aPrevious,
+ const AudioTimelineEvent* aNext);
+
+ static bool IsValid(double value) { return std::isfinite(value); }
+
+ template <class TimeType>
+ float ComputeSetTargetStartValue(const AudioTimelineEvent* aPreviousEvent,
+ TimeType aTime);
+
+ // This is a sorted array of the events in the timeline. Queries of this
+ // data structure should probably be more frequent than modifications to it,
+ // and that is the reason why we're using a simple array as the data
+ // structure. We can optimize this in the future if the performance of the
+ // array ends up being a bottleneck.
+ nsTArray<AudioTimelineEvent> mEvents;
+ float mDefaultValue;
+ // This is the value of this AudioParam at the end of the previous
+ // event for SetTarget curves.
+ float mSetTargetStartValue;
+ AudioTimelineEvent::TimeUnion mSetTargetStartTime;
+ Maybe<float> mSimpleValue;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webaudio/AudioListener.cpp b/dom/media/webaudio/AudioListener.cpp
new file mode 100644
index 0000000000..b8c4e9bc65
--- /dev/null
+++ b/dom/media/webaudio/AudioListener.cpp
@@ -0,0 +1,121 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AudioContext.h"
+#include "AudioListener.h"
+#include "MediaTrackGraph.h"
+#include "Tracing.h"
+#include "mozilla/dom/AudioListenerBinding.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(AudioListener, mContext)
+
+AudioListenerEngine::AudioListenerEngine()
+ : mFrontVector(0., 0., -1.), mRightVector(1., 0., 0.) {}
+
+void AudioListenerEngine::RecvListenerEngineEvent(
+ AudioListenerEngine::AudioListenerParameter aParameter,
+ const ThreeDPoint& aValue) {
+ switch (aParameter) {
+ case AudioListenerParameter::POSITION:
+ mPosition = aValue;
+ break;
+ case AudioListenerParameter::FRONT:
+ mFrontVector = aValue;
+ break;
+ case AudioListenerParameter::RIGHT:
+ mRightVector = aValue;
+ break;
+ default:
+ MOZ_CRASH("Not handled");
+ }
+}
+
+const ThreeDPoint& AudioListenerEngine::Position() const { return mPosition; }
+const ThreeDPoint& AudioListenerEngine::FrontVector() const {
+ return mFrontVector;
+}
+const ThreeDPoint& AudioListenerEngine::RightVector() const {
+ return mRightVector;
+}
+
+AudioListener::AudioListener(AudioContext* aContext)
+ : mContext(aContext),
+ mEngine(new AudioListenerEngine()),
+
+ mFrontVector(0., 0., -1.),
+ mRightVector(1., 0., 0.) {
+ MOZ_ASSERT(aContext);
+}
+
+JSObject* AudioListener::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return AudioListener_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void AudioListener::SetOrientation(double aX, double aY, double aZ, double aXUp,
+ double aYUp, double aZUp) {
+ ThreeDPoint front(aX, aY, aZ);
+ // The panning effect and the azimuth and elevation calculation in the Web
+ // Audio spec becomes undefined with linearly dependent vectors, so keep
+ // existing state in these situations.
+ if (front.IsZero()) {
+ return;
+ }
+ // Normalize before using CrossProduct() to avoid overflow.
+ front.Normalize();
+ ThreeDPoint up(aXUp, aYUp, aZUp);
+ if (up.IsZero()) {
+ return;
+ }
+ up.Normalize();
+ ThreeDPoint right = front.CrossProduct(up);
+ if (right.IsZero()) {
+ return;
+ }
+ right.Normalize();
+
+ if (!mFrontVector.FuzzyEqual(front)) {
+ mFrontVector = front;
+ SendListenerEngineEvent(AudioListenerEngine::AudioListenerParameter::FRONT,
+ mFrontVector);
+ }
+ if (!mRightVector.FuzzyEqual(right)) {
+ mRightVector = right;
+ SendListenerEngineEvent(AudioListenerEngine::AudioListenerParameter::RIGHT,
+ mRightVector);
+ }
+}
+
+void AudioListener::SetPosition(double aX, double aY, double aZ) {
+ if (WebAudioUtils::FuzzyEqual(mPosition.x, aX) &&
+ WebAudioUtils::FuzzyEqual(mPosition.y, aY) &&
+ WebAudioUtils::FuzzyEqual(mPosition.z, aZ)) {
+ return;
+ }
+ mPosition.x = aX;
+ mPosition.y = aY;
+ mPosition.z = aZ;
+ SendListenerEngineEvent(AudioListenerEngine::AudioListenerParameter::POSITION,
+ mPosition);
+}
+
+void AudioListener::SendListenerEngineEvent(
+ AudioListenerEngine::AudioListenerParameter aParameter,
+ const ThreeDPoint& aValue) {
+ mContext->DestinationTrack()->QueueControlMessageWithNoShutdown(
+ [engine = RefPtr(Engine()), aParameter, aValue] {
+ TRACE("AudioListener::RecvListenerEngineEvent");
+ engine->RecvListenerEngineEvent(aParameter, aValue);
+ });
+}
+
+size_t AudioListener::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/AudioListener.h b/dom/media/webaudio/AudioListener.h
new file mode 100644
index 0000000000..7d7fad3bfb
--- /dev/null
+++ b/dom/media/webaudio/AudioListener.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef AudioListener_h_
+#define AudioListener_h_
+
+#include "nsWrapperCache.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/Attributes.h"
+#include "ThreeDPoint.h"
+#include "AudioContext.h"
+#include "PannerNode.h"
+#include "WebAudioUtils.h"
+#include "js/TypeDecls.h"
+#include "mozilla/MemoryReporting.h"
+
+namespace mozilla::dom {
+
+class AudioListenerEngine final {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AudioListenerEngine)
+
+ AudioListenerEngine();
+
+ enum class AudioListenerParameter {
+ POSITION,
+ FRONT, // unit length
+ RIGHT // unit length, orthogonal to FRONT
+ };
+ void RecvListenerEngineEvent(
+ AudioListenerEngine::AudioListenerParameter aParameter,
+ const ThreeDPoint& aValue);
+ const ThreeDPoint& Position() const;
+ const ThreeDPoint& FrontVector() const;
+ const ThreeDPoint& RightVector() const;
+
+ private:
+ ~AudioListenerEngine() = default;
+
+ ThreeDPoint mPosition;
+ ThreeDPoint mFrontVector;
+ ThreeDPoint mRightVector;
+};
+
+class AudioListener final : public nsWrapperCache {
+ public:
+ explicit AudioListener(AudioContext* aContext);
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(AudioListener)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(AudioListener)
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ AudioContext* GetParentObject() const { return mContext; }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void SetPosition(double aX, double aY, double aZ);
+ void SetOrientation(double aX, double aY, double aZ, double aXUp, double aYUp,
+ double aZUp);
+
+ AudioListenerEngine* Engine() { return mEngine.get(); }
+
+ private:
+ void SendListenerEngineEvent(
+ AudioListenerEngine::AudioListenerParameter aParameter,
+ const ThreeDPoint& aValue);
+
+ ~AudioListener() = default;
+
+ private:
+ RefPtr<AudioContext> mContext;
+ RefPtr<AudioListenerEngine> mEngine;
+ ThreeDPoint mPosition;
+ ThreeDPoint mFrontVector;
+ ThreeDPoint mRightVector;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/media/webaudio/AudioNode.cpp b/dom/media/webaudio/AudioNode.cpp
new file mode 100644
index 0000000000..f5c56d0da6
--- /dev/null
+++ b/dom/media/webaudio/AudioNode.cpp
@@ -0,0 +1,608 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AudioNode.h"
+#include "mozilla/ErrorResult.h"
+#include "AudioNodeTrack.h"
+#include "AudioNodeEngine.h"
+#include "mozilla/dom/AudioParam.h"
+#include "mozilla/Services.h"
+#include "nsIObserverService.h"
+
+namespace mozilla::dom {
+
+static const uint32_t INVALID_PORT = 0xffffffff;
+static uint32_t gId = 0;
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(AudioNode)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(AudioNode, DOMEventTargetHelper)
+ tmp->DisconnectFromGraph();
+ if (tmp->mContext) {
+ tmp->mContext->UnregisterNode(tmp);
+ }
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mParams)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputNodes)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputParams)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(AudioNode,
+ DOMEventTargetHelper)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParams)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputNodes)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputParams)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_ADDREF_INHERITED(AudioNode, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(AudioNode, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AudioNode)
+ NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+AudioNode::AudioNode(AudioContext* aContext, uint32_t aChannelCount,
+ ChannelCountMode aChannelCountMode,
+ ChannelInterpretation aChannelInterpretation)
+ : DOMEventTargetHelper(aContext->GetParentObject()),
+ mContext(aContext),
+ mChannelCount(aChannelCount),
+ mChannelCountMode(aChannelCountMode),
+ mChannelInterpretation(aChannelInterpretation),
+ mId(gId++),
+ mPassThrough(false) {
+ MOZ_ASSERT(aContext);
+ aContext->RegisterNode(this);
+}
+
+AudioNode::~AudioNode() {
+ MOZ_ASSERT(mInputNodes.IsEmpty());
+ MOZ_ASSERT(mOutputNodes.IsEmpty());
+ MOZ_ASSERT(mOutputParams.IsEmpty());
+ MOZ_ASSERT(!mTrack,
+ "The webaudio-node-demise notification must have been sent");
+ if (mContext) {
+ mContext->UnregisterNode(this);
+ }
+}
+
+void AudioNode::Initialize(const AudioNodeOptions& aOptions, ErrorResult& aRv) {
+ if (aOptions.mChannelCount.WasPassed()) {
+ SetChannelCount(aOptions.mChannelCount.Value(), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ }
+
+ if (aOptions.mChannelCountMode.WasPassed()) {
+ SetChannelCountModeValue(aOptions.mChannelCountMode.Value(), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ }
+
+ if (aOptions.mChannelInterpretation.WasPassed()) {
+ SetChannelInterpretationValue(aOptions.mChannelInterpretation.Value(), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return;
+ }
+ }
+}
+
+size_t AudioNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ // Not owned:
+ // - mContext
+ // - mTrack
+ size_t amount = 0;
+
+ amount += mInputNodes.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (size_t i = 0; i < mInputNodes.Length(); i++) {
+ amount += mInputNodes[i].SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ // Just measure the array. The entire audio node graph is measured via the
+ // MediaTrackGraph's tracks, so we don't want to double-count the elements.
+ amount += mOutputNodes.ShallowSizeOfExcludingThis(aMallocSizeOf);
+
+ amount += mOutputParams.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (size_t i = 0; i < mOutputParams.Length(); i++) {
+ amount += mOutputParams[i]->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ return amount;
+}
+
+size_t AudioNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+template <class InputNode>
+static size_t FindIndexOfNode(const nsTArray<InputNode>& aInputNodes,
+ const AudioNode* aNode) {
+ for (size_t i = 0; i < aInputNodes.Length(); ++i) {
+ if (aInputNodes[i].mInputNode == aNode) {
+ return i;
+ }
+ }
+ return nsTArray<InputNode>::NoIndex;
+}
+
+template <class InputNode>
+static size_t FindIndexOfNodeWithPorts(const nsTArray<InputNode>& aInputNodes,
+ const AudioNode* aNode,
+ uint32_t aInputPort,
+ uint32_t aOutputPort) {
+ for (size_t i = 0; i < aInputNodes.Length(); ++i) {
+ if (aInputNodes[i].mInputNode == aNode &&
+ aInputNodes[i].mInputPort == aInputPort &&
+ aInputNodes[i].mOutputPort == aOutputPort) {
+ return i;
+ }
+ }
+ return nsTArray<InputNode>::NoIndex;
+}
+
+void AudioNode::DisconnectFromGraph() {
+ MOZ_ASSERT(mRefCnt.get() > mInputNodes.Length(),
+ "Caller should be holding a reference");
+
+ // The idea here is that we remove connections one by one, and at each step
+ // the graph is in a valid state.
+
+ // Disconnect inputs. We don't need them anymore.
+ while (!mInputNodes.IsEmpty()) {
+ InputNode inputNode = mInputNodes.PopLastElement();
+ inputNode.mInputNode->mOutputNodes.RemoveElement(this);
+ }
+
+ while (!mOutputNodes.IsEmpty()) {
+ RefPtr<AudioNode> output = mOutputNodes.PopLastElement();
+ size_t inputIndex = FindIndexOfNode(output->mInputNodes, this);
+ // It doesn't matter which one we remove, since we're going to remove all
+ // entries for this node anyway.
+ output->mInputNodes.RemoveElementAt(inputIndex);
+ // This effects of this connection will remain.
+ output->NotifyHasPhantomInput();
+ }
+
+ while (!mOutputParams.IsEmpty()) {
+ RefPtr<AudioParam> output = mOutputParams.PopLastElement();
+ size_t inputIndex = FindIndexOfNode(output->InputNodes(), this);
+ // It doesn't matter which one we remove, since we're going to remove all
+ // entries for this node anyway.
+ output->RemoveInputNode(inputIndex);
+ }
+
+ DestroyMediaTrack();
+}
+
+AudioNode* AudioNode::Connect(AudioNode& aDestination, uint32_t aOutput,
+ uint32_t aInput, ErrorResult& aRv) {
+ if (aOutput >= NumberOfOutputs()) {
+ aRv.ThrowIndexSizeError(
+ nsPrintfCString("Output index %u is out of bounds", aOutput));
+ return nullptr;
+ }
+
+ if (aInput >= aDestination.NumberOfInputs()) {
+ aRv.ThrowIndexSizeError(
+ nsPrintfCString("Input index %u is out of bounds", aInput));
+ return nullptr;
+ }
+
+ if (Context() != aDestination.Context()) {
+ aRv.ThrowInvalidAccessError(
+ "Can't connect nodes from different AudioContexts");
+ return nullptr;
+ }
+
+ if (FindIndexOfNodeWithPorts(aDestination.mInputNodes, this, aInput,
+ aOutput) !=
+ nsTArray<AudioNode::InputNode>::NoIndex) {
+ // connection already exists.
+ return &aDestination;
+ }
+
+ WEB_AUDIO_API_LOG("%f: %s %u Connect() to %s %u", Context()->CurrentTime(),
+ NodeType(), Id(), aDestination.NodeType(),
+ aDestination.Id());
+
+ // The MediaTrackGraph will handle cycle detection. We don't need to do it
+ // here.
+
+ mOutputNodes.AppendElement(&aDestination);
+ InputNode* input = aDestination.mInputNodes.AppendElement();
+ input->mInputNode = this;
+ input->mInputPort = aInput;
+ input->mOutputPort = aOutput;
+ AudioNodeTrack* destinationTrack = aDestination.mTrack;
+ if (mTrack && destinationTrack) {
+ // Connect tracks in the MediaTrackGraph
+ MOZ_ASSERT(aInput <= UINT16_MAX, "Unexpected large input port number");
+ MOZ_ASSERT(aOutput <= UINT16_MAX, "Unexpected large output port number");
+ input->mTrackPort = destinationTrack->AllocateInputPort(
+ mTrack, static_cast<uint16_t>(aInput), static_cast<uint16_t>(aOutput));
+ }
+ aDestination.NotifyInputsChanged();
+
+ return &aDestination;
+}
+
+void AudioNode::Connect(AudioParam& aDestination, uint32_t aOutput,
+ ErrorResult& aRv) {
+ if (aOutput >= NumberOfOutputs()) {
+ aRv.ThrowIndexSizeError(
+ nsPrintfCString("Output index %u is out of bounds", aOutput));
+ return;
+ }
+
+ if (Context() != aDestination.GetParentObject()) {
+ aRv.ThrowInvalidAccessError(
+ "Can't connect a node to an AudioParam from a different AudioContext");
+ return;
+ }
+
+ if (FindIndexOfNodeWithPorts(aDestination.InputNodes(), this, INVALID_PORT,
+ aOutput) !=
+ nsTArray<AudioNode::InputNode>::NoIndex) {
+ // connection already exists.
+ return;
+ }
+
+ mOutputParams.AppendElement(&aDestination);
+ InputNode* input = aDestination.AppendInputNode();
+ input->mInputNode = this;
+ input->mInputPort = INVALID_PORT;
+ input->mOutputPort = aOutput;
+
+ mozilla::MediaTrack* track = aDestination.Track();
+ MOZ_ASSERT(track->AsProcessedTrack());
+ ProcessedMediaTrack* ps = static_cast<ProcessedMediaTrack*>(track);
+ if (mTrack) {
+ // Setup our track as an input to the AudioParam's track
+ MOZ_ASSERT(aOutput <= UINT16_MAX, "Unexpected large output port number");
+ input->mTrackPort =
+ ps->AllocateInputPort(mTrack, 0, static_cast<uint16_t>(aOutput));
+ }
+}
+
+void AudioNode::SendDoubleParameterToTrack(uint32_t aIndex, double aValue) {
+ MOZ_ASSERT(mTrack, "How come we don't have a track here?");
+ mTrack->SetDoubleParameter(aIndex, aValue);
+}
+
+void AudioNode::SendInt32ParameterToTrack(uint32_t aIndex, int32_t aValue) {
+ MOZ_ASSERT(mTrack, "How come we don't have a track here?");
+ mTrack->SetInt32Parameter(aIndex, aValue);
+}
+
+void AudioNode::SendChannelMixingParametersToTrack() {
+ if (mTrack) {
+ mTrack->SetChannelMixingParameters(mChannelCount, mChannelCountMode,
+ mChannelInterpretation);
+ }
+}
+
+template <>
+bool AudioNode::DisconnectFromOutputIfConnected<AudioNode>(
+ uint32_t aOutputNodeIndex, uint32_t aInputIndex) {
+ WEB_AUDIO_API_LOG("%f: %s %u Disconnect()", Context()->CurrentTime(),
+ NodeType(), Id());
+
+ AudioNode* destination = mOutputNodes[aOutputNodeIndex];
+
+ MOZ_ASSERT(aOutputNodeIndex < mOutputNodes.Length());
+ MOZ_ASSERT(aInputIndex < destination->InputNodes().Length());
+
+ // An upstream node may be starting to play on the graph thread, and the
+ // engine for a downstream node may be sending a PlayingRefChangeHandler
+ // ADDREF message to this (main) thread. Wait for a round trip before
+ // releasing nodes, to give engines receiving sound now time to keep their
+ // nodes alive.
+ class RunnableRelease final : public Runnable {
+ public:
+ explicit RunnableRelease(already_AddRefed<AudioNode> aNode)
+ : mozilla::Runnable("RunnableRelease"), mNode(aNode) {}
+
+ NS_IMETHOD Run() override {
+ mNode = nullptr;
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<AudioNode> mNode;
+ };
+
+ InputNode& input = destination->mInputNodes[aInputIndex];
+ if (input.mInputNode != this) {
+ return false;
+ }
+
+ // Remove one instance of 'dest' from mOutputNodes. There could be
+ // others, and it's not correct to remove them all since some of them
+ // could be for different output ports.
+ RefPtr<AudioNode> output = std::move(mOutputNodes[aOutputNodeIndex]);
+ mOutputNodes.RemoveElementAt(aOutputNodeIndex);
+ // Destroying the InputNode here sends a message to the graph thread
+ // to disconnect the tracks, which should be sent before the
+ // RunAfterPendingUpdates() call below.
+ destination->mInputNodes.RemoveElementAt(aInputIndex);
+ output->NotifyInputsChanged();
+ if (mTrack) {
+ nsCOMPtr<nsIRunnable> runnable = new RunnableRelease(output.forget());
+ mTrack->RunAfterPendingUpdates(runnable.forget());
+ }
+ return true;
+}
+
+template <>
+bool AudioNode::DisconnectFromOutputIfConnected<AudioParam>(
+ uint32_t aOutputParamIndex, uint32_t aInputIndex) {
+ MOZ_ASSERT(aOutputParamIndex < mOutputParams.Length());
+
+ AudioParam* destination = mOutputParams[aOutputParamIndex];
+
+ MOZ_ASSERT(aInputIndex < destination->InputNodes().Length());
+
+ const InputNode& input = destination->InputNodes()[aInputIndex];
+ if (input.mInputNode != this) {
+ return false;
+ }
+ destination->RemoveInputNode(aInputIndex);
+ // Remove one instance of 'dest' from mOutputParams. There could be
+ // others, and it's not correct to remove them all since some of them
+ // could be for different output ports.
+ mOutputParams.RemoveElementAt(aOutputParamIndex);
+ return true;
+}
+
+template <>
+const nsTArray<AudioNode::InputNode>&
+AudioNode::InputsForDestination<AudioNode>(uint32_t aOutputNodeIndex) const {
+ return mOutputNodes[aOutputNodeIndex]->InputNodes();
+}
+
+template <>
+const nsTArray<AudioNode::InputNode>&
+AudioNode::InputsForDestination<AudioParam>(uint32_t aOutputNodeIndex) const {
+ return mOutputParams[aOutputNodeIndex]->InputNodes();
+}
+
+template <typename DestinationType, typename Predicate>
+bool AudioNode::DisconnectMatchingDestinationInputs(uint32_t aDestinationIndex,
+ Predicate aPredicate) {
+ bool wasConnected = false;
+ uint32_t inputCount =
+ InputsForDestination<DestinationType>(aDestinationIndex).Length();
+
+ for (int32_t inputIndex = inputCount - 1; inputIndex >= 0; --inputIndex) {
+ const InputNode& input =
+ InputsForDestination<DestinationType>(aDestinationIndex)[inputIndex];
+ if (aPredicate(input)) {
+ if (DisconnectFromOutputIfConnected<DestinationType>(aDestinationIndex,
+ inputIndex)) {
+ wasConnected = true;
+ break;
+ }
+ }
+ }
+ return wasConnected;
+}
+
+void AudioNode::Disconnect(ErrorResult& aRv) {
+ for (int32_t outputIndex = mOutputNodes.Length() - 1; outputIndex >= 0;
+ --outputIndex) {
+ DisconnectMatchingDestinationInputs<AudioNode>(
+ outputIndex, [](const InputNode&) { return true; });
+ }
+
+ for (int32_t outputIndex = mOutputParams.Length() - 1; outputIndex >= 0;
+ --outputIndex) {
+ DisconnectMatchingDestinationInputs<AudioParam>(
+ outputIndex, [](const InputNode&) { return true; });
+ }
+}
+
+void AudioNode::Disconnect(uint32_t aOutput, ErrorResult& aRv) {
+ if (aOutput >= NumberOfOutputs()) {
+ aRv.ThrowIndexSizeError(
+ nsPrintfCString("Output index %u is out of bounds", aOutput));
+ return;
+ }
+
+ for (int32_t outputIndex = mOutputNodes.Length() - 1; outputIndex >= 0;
+ --outputIndex) {
+ DisconnectMatchingDestinationInputs<AudioNode>(
+ outputIndex, [aOutput](const InputNode& aInputNode) {
+ return aInputNode.mOutputPort == aOutput;
+ });
+ }
+
+ for (int32_t outputIndex = mOutputParams.Length() - 1; outputIndex >= 0;
+ --outputIndex) {
+ DisconnectMatchingDestinationInputs<AudioParam>(
+ outputIndex, [aOutput](const InputNode& aInputNode) {
+ return aInputNode.mOutputPort == aOutput;
+ });
+ }
+}
+
+void AudioNode::Disconnect(AudioNode& aDestination, ErrorResult& aRv) {
+ bool wasConnected = false;
+
+ for (int32_t outputIndex = mOutputNodes.Length() - 1; outputIndex >= 0;
+ --outputIndex) {
+ if (mOutputNodes[outputIndex] != &aDestination) {
+ continue;
+ }
+ wasConnected |= DisconnectMatchingDestinationInputs<AudioNode>(
+ outputIndex, [](const InputNode&) { return true; });
+ }
+
+ if (!wasConnected) {
+ aRv.ThrowInvalidAccessError(
+ "Trying to disconnect from a node we're not connected to");
+ return;
+ }
+}
+
+void AudioNode::Disconnect(AudioNode& aDestination, uint32_t aOutput,
+ ErrorResult& aRv) {
+ if (aOutput >= NumberOfOutputs()) {
+ aRv.ThrowIndexSizeError(
+ nsPrintfCString("Output index %u is out of bounds", aOutput));
+ return;
+ }
+
+ bool wasConnected = false;
+
+ for (int32_t outputIndex = mOutputNodes.Length() - 1; outputIndex >= 0;
+ --outputIndex) {
+ if (mOutputNodes[outputIndex] != &aDestination) {
+ continue;
+ }
+ wasConnected |= DisconnectMatchingDestinationInputs<AudioNode>(
+ outputIndex, [aOutput](const InputNode& aInputNode) {
+ return aInputNode.mOutputPort == aOutput;
+ });
+ }
+
+ if (!wasConnected) {
+ aRv.ThrowInvalidAccessError(
+ "Trying to disconnect from a node we're not connected to");
+ return;
+ }
+}
+
+void AudioNode::Disconnect(AudioNode& aDestination, uint32_t aOutput,
+ uint32_t aInput, ErrorResult& aRv) {
+ if (aOutput >= NumberOfOutputs()) {
+ aRv.ThrowIndexSizeError(
+ nsPrintfCString("Output index %u is out of bounds", aOutput));
+ return;
+ }
+
+ if (aInput >= aDestination.NumberOfInputs()) {
+ aRv.ThrowIndexSizeError(
+ nsPrintfCString("Input index %u is out of bounds", aInput));
+ return;
+ }
+
+ bool wasConnected = false;
+
+ for (int32_t outputIndex = mOutputNodes.Length() - 1; outputIndex >= 0;
+ --outputIndex) {
+ if (mOutputNodes[outputIndex] != &aDestination) {
+ continue;
+ }
+ wasConnected |= DisconnectMatchingDestinationInputs<AudioNode>(
+ outputIndex, [aOutput, aInput](const InputNode& aInputNode) {
+ return aInputNode.mOutputPort == aOutput &&
+ aInputNode.mInputPort == aInput;
+ });
+ }
+
+ if (!wasConnected) {
+ aRv.ThrowInvalidAccessError(
+ "Trying to disconnect from a node we're not connected to");
+ return;
+ }
+}
+
+void AudioNode::Disconnect(AudioParam& aDestination, ErrorResult& aRv) {
+ bool wasConnected = false;
+
+ for (int32_t outputIndex = mOutputParams.Length() - 1; outputIndex >= 0;
+ --outputIndex) {
+ if (mOutputParams[outputIndex] != &aDestination) {
+ continue;
+ }
+ wasConnected |= DisconnectMatchingDestinationInputs<AudioParam>(
+ outputIndex, [](const InputNode&) { return true; });
+ }
+
+ if (!wasConnected) {
+ aRv.ThrowInvalidAccessError(
+ "Trying to disconnect from an AudioParam we're not connected to");
+ return;
+ }
+}
+
+void AudioNode::Disconnect(AudioParam& aDestination, uint32_t aOutput,
+ ErrorResult& aRv) {
+ if (aOutput >= NumberOfOutputs()) {
+ aRv.ThrowIndexSizeError(
+ nsPrintfCString("Output index %u is out of bounds", aOutput));
+ return;
+ }
+
+ bool wasConnected = false;
+
+ for (int32_t outputIndex = mOutputParams.Length() - 1; outputIndex >= 0;
+ --outputIndex) {
+ if (mOutputParams[outputIndex] != &aDestination) {
+ continue;
+ }
+ wasConnected |= DisconnectMatchingDestinationInputs<AudioParam>(
+ outputIndex, [aOutput](const InputNode& aInputNode) {
+ return aInputNode.mOutputPort == aOutput;
+ });
+ }
+
+ if (!wasConnected) {
+ aRv.ThrowInvalidAccessError(
+ "Trying to disconnect from an AudioParam we're not connected to");
+ return;
+ }
+}
+
+void AudioNode::DestroyMediaTrack() {
+ if (mTrack) {
+ // Remove the node pointer on the engine.
+ AudioNodeTrack* ns = mTrack;
+ MOZ_ASSERT(ns, "How come we don't have a track here?");
+ MOZ_ASSERT(ns->Engine()->NodeMainThread() == this,
+ "Invalid node reference");
+ ns->Engine()->ClearNode();
+
+ mTrack->Destroy();
+ mTrack = nullptr;
+
+ nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+ if (obs) {
+ nsAutoString id;
+ id.AppendPrintf("%u", mId);
+ obs->NotifyObservers(nullptr, "webaudio-node-demise", id.get());
+ }
+ }
+}
+
+void AudioNode::RemoveOutputParam(AudioParam* aParam) {
+ mOutputParams.RemoveElement(aParam);
+}
+
+bool AudioNode::PassThrough() const {
+ MOZ_ASSERT(NumberOfInputs() <= 1 && NumberOfOutputs() == 1);
+ return mPassThrough;
+}
+
+void AudioNode::SetPassThrough(bool aPassThrough) {
+ MOZ_ASSERT(NumberOfInputs() <= 1 && NumberOfOutputs() == 1);
+ mPassThrough = aPassThrough;
+ if (mTrack) {
+ mTrack->SetPassThrough(mPassThrough);
+ }
+}
+
+AudioParam* AudioNode::CreateAudioParam(uint32_t aIndex, const nsAString& aName,
+ float aDefaultValue, float aMinValue,
+ float aMaxValue) {
+ return *mParams.AppendElement(
+ new AudioParam(this, aIndex, aName, aDefaultValue, aMinValue, aMaxValue));
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/AudioNode.h b/dom/media/webaudio/AudioNode.h
new file mode 100644
index 0000000000..561749fd61
--- /dev/null
+++ b/dom/media/webaudio/AudioNode.h
@@ -0,0 +1,290 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef AudioNode_h_
+#define AudioNode_h_
+
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/AudioNodeBinding.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsTArray.h"
+#include "AudioContext.h"
+#include "MediaTrackGraph.h"
+#include "WebAudioUtils.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/MemoryReporting.h"
+#include "nsPrintfCString.h"
+#include "nsWeakReference.h"
+#include "SelfRef.h"
+
+namespace mozilla {
+
+class AbstractThread;
+
+namespace dom {
+
+class AudioContext;
+class AudioBufferSourceNode;
+class AudioParam;
+class AudioParamTimeline;
+struct ThreeDPoint;
+
+/**
+ * The DOM object representing a Web Audio AudioNode.
+ *
+ * Each AudioNode has a MediaTrack representing the actual
+ * real-time processing and output of this AudioNode.
+ *
+ * We track the incoming and outgoing connections to other AudioNodes.
+ * Outgoing connections have strong ownership. Also, AudioNodes that will
+ * produce sound on their output even when they have silent or no input ask
+ * the AudioContext to keep playing or tail-time references to keep them alive
+ * until the context is finished.
+ *
+ * Explicit disconnections will only remove references from output nodes after
+ * the graph is notified and the main thread receives a reply. Similarly,
+ * nodes with playing or tail-time references release these references only
+ * after receiving notification from their engine on the graph thread that
+ * playing has stopped. Engines notifying the main thread that they have
+ * finished do so strictly *after* producing and returning their last block.
+ * In this way, an engine that receives non-null input knows that the input
+ * comes from nodes that are still alive and will keep their output nodes
+ * alive for at least as long as it takes to process messages from the graph
+ * thread. i.e. the engine receiving non-null input knows that its node is
+ * still alive, and will still be alive when it receives a message from the
+ * engine.
+ */
+class AudioNode : public DOMEventTargetHelper, public nsSupportsWeakReference {
+ protected:
+ // You can only use refcounting to delete this object
+ virtual ~AudioNode();
+
+ public:
+ AudioNode(AudioContext* aContext, uint32_t aChannelCount,
+ ChannelCountMode aChannelCountMode,
+ ChannelInterpretation aChannelInterpretation);
+
+ // This should be idempotent (safe to call multiple times).
+ virtual void DestroyMediaTrack();
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AudioNode, DOMEventTargetHelper)
+
+ virtual AudioBufferSourceNode* AsAudioBufferSourceNode() { return nullptr; }
+
+ AudioContext* GetParentObject() const { return mContext; }
+
+ AudioContext* Context() const { return mContext; }
+
+ virtual AudioNode* Connect(AudioNode& aDestination, uint32_t aOutput,
+ uint32_t aInput, ErrorResult& aRv);
+
+ virtual void Connect(AudioParam& aDestination, uint32_t aOutput,
+ ErrorResult& aRv);
+
+ virtual void Disconnect(ErrorResult& aRv);
+ virtual void Disconnect(uint32_t aOutput, ErrorResult& aRv);
+ virtual void Disconnect(AudioNode& aDestination, ErrorResult& aRv);
+ virtual void Disconnect(AudioNode& aDestination, uint32_t aOutput,
+ ErrorResult& aRv);
+ virtual void Disconnect(AudioNode& aDestination, uint32_t aOutput,
+ uint32_t aInput, ErrorResult& aRv);
+ virtual void Disconnect(AudioParam& aDestination, ErrorResult& aRv);
+ virtual void Disconnect(AudioParam& aDestination, uint32_t aOutput,
+ ErrorResult& aRv);
+
+ // Called after input nodes have been explicitly added or removed through
+ // the Connect() or Disconnect() methods.
+ virtual void NotifyInputsChanged() {}
+ // Indicate that the node should continue indefinitely to behave as if an
+ // input is connected, even though there is no longer a corresponding entry
+ // in mInputNodes. Called after an input node has been removed because it
+ // is being garbage collected.
+ virtual void NotifyHasPhantomInput() {}
+
+ // The following two virtual methods must be implemented by each node type
+ // to provide their number of input and output ports. These numbers are
+ // constant for the lifetime of the node. Both default to 1.
+ virtual uint16_t NumberOfInputs() const { return 1; }
+ virtual uint16_t NumberOfOutputs() const { return 1; }
+
+ uint32_t Id() const { return mId; }
+
+ bool PassThrough() const;
+ void SetPassThrough(bool aPassThrough);
+
+ uint32_t ChannelCount() const { return mChannelCount; }
+ virtual void SetChannelCount(uint32_t aChannelCount, ErrorResult& aRv) {
+ if (aChannelCount == 0 || aChannelCount > WebAudioUtils::MaxChannelCount) {
+ aRv.ThrowNotSupportedError(
+ nsPrintfCString("Channel count (%u) must be in the range [1, max "
+ "supported channel count]",
+ aChannelCount));
+ return;
+ }
+ mChannelCount = aChannelCount;
+ SendChannelMixingParametersToTrack();
+ }
+ ChannelCountMode ChannelCountModeValue() const { return mChannelCountMode; }
+ virtual void SetChannelCountModeValue(ChannelCountMode aMode,
+ ErrorResult& aRv) {
+ mChannelCountMode = aMode;
+ SendChannelMixingParametersToTrack();
+ }
+ ChannelInterpretation ChannelInterpretationValue() const {
+ return mChannelInterpretation;
+ }
+ virtual void SetChannelInterpretationValue(ChannelInterpretation aMode,
+ ErrorResult& aRv) {
+ mChannelInterpretation = aMode;
+ SendChannelMixingParametersToTrack();
+ }
+
+ struct InputNode final {
+ InputNode() = default;
+ InputNode(const InputNode&) = delete;
+ InputNode(InputNode&&) = default;
+
+ ~InputNode() {
+ if (mTrackPort) {
+ mTrackPort->Destroy();
+ }
+ }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t amount = 0;
+ if (mTrackPort) {
+ amount += mTrackPort->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ return amount;
+ }
+
+ // The InputNode is destroyed when mInputNode is disconnected.
+ AudioNode* MOZ_NON_OWNING_REF mInputNode;
+ RefPtr<MediaInputPort> mTrackPort;
+ // The index of the input port this node feeds into.
+ // This is not used for connections to AudioParams.
+ uint32_t mInputPort;
+ // The index of the output port this node comes out of.
+ uint32_t mOutputPort;
+ };
+
+ // Returns the track, if any.
+ AudioNodeTrack* GetTrack() const { return mTrack; }
+
+ const nsTArray<InputNode>& InputNodes() const { return mInputNodes; }
+ const nsTArray<RefPtr<AudioNode>>& OutputNodes() const {
+ return mOutputNodes;
+ }
+ const nsTArray<RefPtr<AudioParam>>& OutputParams() const {
+ return mOutputParams;
+ }
+
+ template <typename T>
+ const nsTArray<InputNode>& InputsForDestination(uint32_t aOutputIndex) const;
+
+ void RemoveOutputParam(AudioParam* aParam);
+
+ // MarkActive() asks the context to keep the AudioNode alive until the
+ // context is finished. This takes care of "playing" references and
+ // "tail-time" references.
+ void MarkActive() { Context()->RegisterActiveNode(this); }
+ // Active nodes call MarkInactive() when they have finished producing sound
+ // for the foreseeable future.
+ // Do not call MarkInactive from a node destructor. If the destructor is
+ // called, then the node is already inactive.
+ // MarkInactive() may delete |this|.
+ void MarkInactive() { Context()->UnregisterActiveNode(this); }
+
+ virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
+ virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
+
+ // Returns a string from constant static storage identifying the dom node
+ // type.
+ virtual const char* NodeType() const = 0;
+
+ const nsTArray<RefPtr<AudioParam>>& GetAudioParams() const { return mParams; }
+
+ private:
+ // Given:
+ //
+ // - a DestinationType, that can be an AudioNode or an AudioParam ;
+ // - a Predicate, a function that takes an InputNode& and returns a bool ;
+ //
+ // This method iterates on the InputNodes() of the node at the index
+ // aDestinationIndex, and calls `DisconnectFromOutputIfConnected` with this
+ // input node, if aPredicate returns true.
+ template <typename DestinationType, typename Predicate>
+ bool DisconnectMatchingDestinationInputs(uint32_t aDestinationIndex,
+ Predicate aPredicate);
+
+ virtual void LastRelease() override {
+ // We are about to be deleted, disconnect the object from the graph before
+ // the derived type is destroyed.
+ DisconnectFromGraph();
+ }
+ // Callers must hold a reference to 'this'.
+ void DisconnectFromGraph();
+
+ template <typename DestinationType>
+ bool DisconnectFromOutputIfConnected(uint32_t aOutputIndex,
+ uint32_t aInputIndex);
+
+ protected:
+ // Helper for the Constructors for nodes.
+ void Initialize(const AudioNodeOptions& aOptions, ErrorResult& aRv);
+
+ // Helpers for sending different value types to tracks
+ void SendDoubleParameterToTrack(uint32_t aIndex, double aValue);
+ void SendInt32ParameterToTrack(uint32_t aIndex, int32_t aValue);
+ void SendChannelMixingParametersToTrack();
+
+ private:
+ RefPtr<AudioContext> mContext;
+
+ protected:
+ // Set in the constructor of all nodes except offline AudioDestinationNode.
+ // Must not become null until finished.
+ RefPtr<AudioNodeTrack> mTrack;
+
+ // The reference pointing out all audio params which belong to this node.
+ nsTArray<RefPtr<AudioParam>> mParams;
+ // Use this function to create an AudioParam, so as to automatically add
+ // the new AudioParam to `mParams`.
+ AudioParam* CreateAudioParam(
+ uint32_t aIndex, const nsAString& aName, float aDefaultValue,
+ float aMinValue = std::numeric_limits<float>::lowest(),
+ float aMaxValue = std::numeric_limits<float>::max());
+
+ private:
+ // For every InputNode, there is a corresponding entry in mOutputNodes of the
+ // InputNode's mInputNode.
+ nsTArray<InputNode> mInputNodes;
+ // For every mOutputNode entry, there is a corresponding entry in mInputNodes
+ // of the mOutputNode entry. We won't necessarily be able to identify the
+ // exact matching entry, since mOutputNodes doesn't include the port
+ // identifiers and the same node could be connected on multiple ports.
+ nsTArray<RefPtr<AudioNode>> mOutputNodes;
+ // For every mOutputParams entry, there is a corresponding entry in
+ // AudioParam::mInputNodes of the mOutputParams entry. We won't necessarily be
+ // able to identify the exact matching entry, since mOutputParams doesn't
+ // include the port identifiers and the same node could be connected on
+ // multiple ports.
+ nsTArray<RefPtr<AudioParam>> mOutputParams;
+ uint32_t mChannelCount;
+ ChannelCountMode mChannelCountMode;
+ ChannelInterpretation mChannelInterpretation;
+ const uint32_t mId;
+ // Whether the node just passes through its input. This is a devtools API
+ // that only works for some node types.
+ bool mPassThrough;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webaudio/AudioNodeEngine.cpp b/dom/media/webaudio/AudioNodeEngine.cpp
new file mode 100644
index 0000000000..3fba688b8c
--- /dev/null
+++ b/dom/media/webaudio/AudioNodeEngine.cpp
@@ -0,0 +1,437 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AudioNodeEngine.h"
+
+#include "mozilla/AbstractThread.h"
+#ifdef USE_NEON
+# include "mozilla/arm.h"
+# include "AudioNodeEngineGeneric.h"
+#endif
+#ifdef USE_SSE2
+# include "mozilla/SSE.h"
+# include "AudioNodeEngineGeneric.h"
+#endif
+#if defined(USE_SSE42) && defined(USE_FMA3)
+# include "mozilla/SSE.h"
+# include "AudioNodeEngineGeneric.h"
+#endif
+#include "AudioBlock.h"
+#include "Tracing.h"
+
+namespace mozilla {
+
+already_AddRefed<ThreadSharedFloatArrayBufferList>
+ThreadSharedFloatArrayBufferList::Create(uint32_t aChannelCount, size_t aLength,
+ const mozilla::fallible_t&) {
+ RefPtr<ThreadSharedFloatArrayBufferList> buffer =
+ new ThreadSharedFloatArrayBufferList(aChannelCount);
+
+ for (uint32_t i = 0; i < aChannelCount; ++i) {
+ float* channelData = js_pod_malloc<float>(aLength);
+ if (!channelData) {
+ return nullptr;
+ }
+
+ buffer->SetData(i, channelData, js_free, channelData);
+ }
+
+ return buffer.forget();
+}
+
+void WriteZeroesToAudioBlock(AudioBlock* aChunk, uint32_t aStart,
+ uint32_t aLength) {
+ MOZ_ASSERT(aStart + aLength <= WEBAUDIO_BLOCK_SIZE);
+ MOZ_ASSERT(!aChunk->IsNull(), "You should pass a non-null chunk");
+ if (aLength == 0) {
+ return;
+ }
+
+ for (uint32_t i = 0; i < aChunk->ChannelCount(); ++i) {
+ PodZero(aChunk->ChannelFloatsForWrite(i) + aStart, aLength);
+ }
+}
+
+void AudioBufferCopyWithScale(const float* aInput, float aScale, float* aOutput,
+ uint32_t aSize) {
+ if (aScale == 1.0f) {
+ PodCopy(aOutput, aInput, aSize);
+ } else {
+ for (uint32_t i = 0; i < aSize; ++i) {
+ aOutput[i] = aInput[i] * aScale;
+ }
+ }
+}
+
+void AudioBufferAddWithScale(const float* aInput, float aScale, float* aOutput,
+ uint32_t aSize) {
+#ifdef USE_NEON
+ if (mozilla::supports_neon()) {
+ Engine<xsimd::neon>::AudioBufferAddWithScale(aInput, aScale, aOutput,
+ aSize);
+ return;
+ }
+#endif
+
+#ifdef USE_SSE2
+ if (mozilla::supports_sse2()) {
+# if defined(USE_SSE42) && defined(USE_FMA3)
+ if (mozilla::supports_fma3() && mozilla::supports_sse4_2()) {
+ Engine<xsimd::fma3<xsimd::sse4_2>>::AudioBufferAddWithScale(
+ aInput, aScale, aOutput, aSize);
+ } else
+# endif
+ {
+ Engine<xsimd::sse2>::AudioBufferAddWithScale(aInput, aScale, aOutput,
+ aSize);
+ }
+ return;
+ }
+#endif
+
+ if (aScale == 1.0f) {
+ for (uint32_t i = 0; i < aSize; ++i) {
+ aOutput[i] += aInput[i];
+ }
+ } else {
+ for (uint32_t i = 0; i < aSize; ++i) {
+ aOutput[i] += aInput[i] * aScale;
+ }
+ }
+}
+
+void AudioBlockAddChannelWithScale(const float aInput[WEBAUDIO_BLOCK_SIZE],
+ float aScale,
+ float aOutput[WEBAUDIO_BLOCK_SIZE]) {
+ AudioBufferAddWithScale(aInput, aScale, aOutput, WEBAUDIO_BLOCK_SIZE);
+}
+
+void AudioBlockCopyChannelWithScale(const float* aInput, float aScale,
+ float* aOutput) {
+ if (aScale == 1.0f) {
+ memcpy(aOutput, aInput, WEBAUDIO_BLOCK_SIZE * sizeof(float));
+ } else {
+#ifdef USE_NEON
+ if (mozilla::supports_neon()) {
+ Engine<xsimd::neon>::AudioBlockCopyChannelWithScale(aInput, aScale,
+ aOutput);
+ return;
+ }
+#endif
+
+#ifdef USE_SSE2
+ if (mozilla::supports_sse2()) {
+ Engine<xsimd::sse2>::AudioBlockCopyChannelWithScale(aInput, aScale,
+ aOutput);
+ return;
+ }
+#endif
+
+ for (uint32_t i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) {
+ aOutput[i] = aInput[i] * aScale;
+ }
+ }
+}
+
+void BufferComplexMultiply(const float* aInput, const float* aScale,
+ float* aOutput, uint32_t aSize) {
+#ifdef USE_NEON
+ if (mozilla::supports_neon()) {
+ Engine<xsimd::neon>::BufferComplexMultiply(aInput, aScale, aOutput, aSize);
+ return;
+ }
+#endif
+#ifdef USE_SSE2
+ if (mozilla::supports_sse()) {
+# if defined(USE_SSE42) && defined(USE_FMA3)
+ if (mozilla::supports_fma3() && mozilla::supports_sse4_2()) {
+ Engine<xsimd::fma3<xsimd::sse4_2>>::BufferComplexMultiply(aInput, aScale,
+ aOutput, aSize);
+ } else
+# endif
+ {
+ Engine<xsimd::sse2>::BufferComplexMultiply(aInput, aScale, aOutput,
+ aSize);
+ }
+ return;
+ }
+#endif
+
+ for (uint32_t i = 0; i < aSize * 2; i += 2) {
+ float real1 = aInput[i];
+ float imag1 = aInput[i + 1];
+ float real2 = aScale[i];
+ float imag2 = aScale[i + 1];
+ float realResult = real1 * real2 - imag1 * imag2;
+ float imagResult = real1 * imag2 + imag1 * real2;
+ aOutput[i] = realResult;
+ aOutput[i + 1] = imagResult;
+ }
+}
+
+float AudioBufferPeakValue(const float* aInput, uint32_t aSize) {
+ float max = 0.0f;
+ for (uint32_t i = 0; i < aSize; i++) {
+ float mag = fabs(aInput[i]);
+ if (mag > max) {
+ max = mag;
+ }
+ }
+ return max;
+}
+
+void AudioBlockCopyChannelWithScale(const float aInput[WEBAUDIO_BLOCK_SIZE],
+ const float aScale[WEBAUDIO_BLOCK_SIZE],
+ float aOutput[WEBAUDIO_BLOCK_SIZE]) {
+#ifdef USE_NEON
+ if (mozilla::supports_neon()) {
+ Engine<xsimd::neon>::AudioBlockCopyChannelWithScale(aInput, aScale,
+ aOutput);
+ return;
+ }
+#endif
+
+#ifdef USE_SSE2
+ if (mozilla::supports_sse2()) {
+ Engine<xsimd::sse2>::AudioBlockCopyChannelWithScale(aInput, aScale,
+ aOutput);
+ return;
+ }
+#endif
+
+ for (uint32_t i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) {
+ aOutput[i] = aInput[i] * aScale[i];
+ }
+}
+
+void AudioBlockInPlaceScale(float aBlock[WEBAUDIO_BLOCK_SIZE], float aScale) {
+ AudioBufferInPlaceScale(aBlock, aScale, WEBAUDIO_BLOCK_SIZE);
+}
+
+void AudioBlockInPlaceScale(float aBlock[WEBAUDIO_BLOCK_SIZE],
+ float aScale[WEBAUDIO_BLOCK_SIZE]) {
+ AudioBufferInPlaceScale(aBlock, aScale, WEBAUDIO_BLOCK_SIZE);
+}
+
+void AudioBufferInPlaceScale(float* aBlock, float aScale, uint32_t aSize) {
+ if (aScale == 1.0f) {
+ return;
+ }
+#ifdef USE_NEON
+ if (mozilla::supports_neon()) {
+ Engine<xsimd::neon>::AudioBufferInPlaceScale(aBlock, aScale, aSize);
+ return;
+ }
+#endif
+
+#ifdef USE_SSE2
+ if (mozilla::supports_sse2()) {
+ Engine<xsimd::sse2>::AudioBufferInPlaceScale(aBlock, aScale, aSize);
+ return;
+ }
+#endif
+
+ for (uint32_t i = 0; i < aSize; ++i) {
+ *aBlock++ *= aScale;
+ }
+}
+
+void AudioBufferInPlaceScale(float* aBlock, float* aScale, uint32_t aSize) {
+#ifdef USE_NEON
+ if (mozilla::supports_neon()) {
+ Engine<xsimd::neon>::AudioBufferInPlaceScale(aBlock, aScale, aSize);
+ return;
+ }
+#endif
+
+#ifdef USE_SSE2
+ if (mozilla::supports_sse2()) {
+ Engine<xsimd::sse2>::AudioBufferInPlaceScale(aBlock, aScale, aSize);
+ return;
+ }
+#endif
+
+ for (uint32_t i = 0; i < aSize; ++i) {
+ *aBlock++ *= *aScale++;
+ }
+}
+
+void AudioBlockPanMonoToStereo(const float aInput[WEBAUDIO_BLOCK_SIZE],
+ float aGainL[WEBAUDIO_BLOCK_SIZE],
+ float aGainR[WEBAUDIO_BLOCK_SIZE],
+ float aOutputL[WEBAUDIO_BLOCK_SIZE],
+ float aOutputR[WEBAUDIO_BLOCK_SIZE]) {
+ AudioBlockCopyChannelWithScale(aInput, aGainL, aOutputL);
+ AudioBlockCopyChannelWithScale(aInput, aGainR, aOutputR);
+}
+
+void AudioBlockPanMonoToStereo(const float aInput[WEBAUDIO_BLOCK_SIZE],
+ float aGainL, float aGainR,
+ float aOutputL[WEBAUDIO_BLOCK_SIZE],
+ float aOutputR[WEBAUDIO_BLOCK_SIZE]) {
+ AudioBlockCopyChannelWithScale(aInput, aGainL, aOutputL);
+ AudioBlockCopyChannelWithScale(aInput, aGainR, aOutputR);
+}
+
+void AudioBlockPanStereoToStereo(const float aInputL[WEBAUDIO_BLOCK_SIZE],
+ const float aInputR[WEBAUDIO_BLOCK_SIZE],
+ float aGainL, float aGainR, bool aIsOnTheLeft,
+ float aOutputL[WEBAUDIO_BLOCK_SIZE],
+ float aOutputR[WEBAUDIO_BLOCK_SIZE]) {
+#ifdef USE_NEON
+ if (mozilla::supports_neon()) {
+ Engine<xsimd::neon>::AudioBlockPanStereoToStereo(
+ aInputL, aInputR, aGainL, aGainR, aIsOnTheLeft, aOutputL, aOutputR);
+ return;
+ }
+#endif
+
+#ifdef USE_SSE2
+ if (mozilla::supports_sse2()) {
+# if defined(USE_SSE42) && defined(USE_FMA3)
+ if (mozilla::supports_fma3() && mozilla::supports_sse4_2()) {
+ Engine<xsimd::fma3<xsimd::sse4_2>>::AudioBlockPanStereoToStereo(
+ aInputL, aInputR, aGainL, aGainR, aIsOnTheLeft, aOutputL, aOutputR);
+ } else
+# endif
+ {
+ Engine<xsimd::sse2>::AudioBlockPanStereoToStereo(
+ aInputL, aInputR, aGainL, aGainR, aIsOnTheLeft, aOutputL, aOutputR);
+ }
+ return;
+ }
+#endif
+
+ uint32_t i;
+
+ if (aIsOnTheLeft) {
+ for (i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) {
+ aOutputL[i] = aInputL[i] + aInputR[i] * aGainL;
+ aOutputR[i] = aInputR[i] * aGainR;
+ }
+ } else {
+ for (i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) {
+ aOutputL[i] = aInputL[i] * aGainL;
+ aOutputR[i] = aInputR[i] + aInputL[i] * aGainR;
+ }
+ }
+}
+
+void AudioBlockPanStereoToStereo(const float aInputL[WEBAUDIO_BLOCK_SIZE],
+ const float aInputR[WEBAUDIO_BLOCK_SIZE],
+ const float aGainL[WEBAUDIO_BLOCK_SIZE],
+ const float aGainR[WEBAUDIO_BLOCK_SIZE],
+ const bool aIsOnTheLeft[WEBAUDIO_BLOCK_SIZE],
+ float aOutputL[WEBAUDIO_BLOCK_SIZE],
+ float aOutputR[WEBAUDIO_BLOCK_SIZE]) {
+#ifdef USE_NEON
+ if (mozilla::supports_neon()) {
+ Engine<xsimd::neon>::AudioBlockPanStereoToStereo(
+ aInputL, aInputR, aGainL, aGainR, aIsOnTheLeft, aOutputL, aOutputR);
+ return;
+ }
+#endif
+
+#ifdef USE_SSE2
+ if (mozilla::supports_sse2()) {
+# if defined(USE_SSE42) && defined(USE_FMA3)
+ if (mozilla::supports_fma3() && mozilla::supports_sse4_2()) {
+ Engine<xsimd::fma3<xsimd::sse2>>::AudioBlockPanStereoToStereo(
+ aInputL, aInputR, aGainL, aGainR, aIsOnTheLeft, aOutputL, aOutputR);
+ } else
+# endif
+ {
+ Engine<xsimd::sse2>::AudioBlockPanStereoToStereo(
+ aInputL, aInputR, aGainL, aGainR, aIsOnTheLeft, aOutputL, aOutputR);
+ }
+ return;
+ }
+#endif
+
+ uint32_t i;
+ for (i = 0; i < WEBAUDIO_BLOCK_SIZE; i++) {
+ if (aIsOnTheLeft[i]) {
+ aOutputL[i] = aInputL[i] + aInputR[i] * aGainL[i];
+ aOutputR[i] = aInputR[i] * aGainR[i];
+ } else {
+ aOutputL[i] = aInputL[i] * aGainL[i];
+ aOutputR[i] = aInputR[i] + aInputL[i] * aGainR[i];
+ }
+ }
+}
+
+float AudioBufferSumOfSquares(const float* aInput, uint32_t aLength) {
+#ifdef USE_NEON
+ if (mozilla::supports_neon()) {
+ return Engine<xsimd::neon>::AudioBufferSumOfSquares(aInput, aLength);
+ }
+#endif
+
+#ifdef USE_SSE2
+ if (mozilla::supports_sse()) {
+# if defined(USE_SSE42) && defined(USE_FMA3)
+ if (mozilla::supports_fma3() && mozilla::supports_sse4_2()) {
+ return Engine<xsimd::fma3<xsimd::sse4_2>>::AudioBufferSumOfSquares(
+ aInput, aLength);
+ } else
+# endif
+ {
+ return Engine<xsimd::sse2>::AudioBufferSumOfSquares(aInput, aLength);
+ }
+ }
+#endif
+
+ float sum = 0.f;
+ while (aLength--) {
+ sum += *aInput * *aInput;
+ ++aInput;
+ }
+ return sum;
+}
+
+void NaNToZeroInPlace(float* aSamples, size_t aCount) {
+#ifdef USE_SSE2
+ if (mozilla::supports_sse2()) {
+ Engine<xsimd::sse2>::NaNToZeroInPlace(aSamples, aCount);
+ return;
+ }
+#endif
+ for (size_t i = 0; i < aCount; i++) {
+ if (aSamples[i] != aSamples[i]) {
+ aSamples[i] = 0.0;
+ }
+ }
+}
+
+AudioNodeEngine::AudioNodeEngine(dom::AudioNode* aNode)
+ : mNode(aNode),
+ mNodeType(aNode ? aNode->NodeType() : nullptr),
+ mInputCount(aNode ? aNode->NumberOfInputs() : 1),
+ mOutputCount(aNode ? aNode->NumberOfOutputs() : 0) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_COUNT_CTOR(AudioNodeEngine);
+}
+
+void AudioNodeEngine::ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
+ const AudioBlock& aInput,
+ AudioBlock* aOutput, bool* aFinished) {
+ MOZ_ASSERT(mInputCount <= 1 && mOutputCount <= 1);
+ TRACE("AudioNodeEngine::ProcessBlock");
+ *aOutput = aInput;
+}
+
+void AudioNodeEngine::ProcessBlocksOnPorts(AudioNodeTrack* aTrack,
+ GraphTime aFrom,
+ Span<const AudioBlock> aInput,
+ Span<AudioBlock> aOutput,
+ bool* aFinished) {
+ MOZ_ASSERT(mInputCount > 1 || mOutputCount > 1);
+ TRACE("AudioNodeEngine::ProcessBlocksOnPorts");
+ // Only produce one output port, and drop all other input ports.
+ aOutput[0] = aInput[0];
+}
+
+} // namespace mozilla
diff --git a/dom/media/webaudio/AudioNodeEngine.h b/dom/media/webaudio/AudioNodeEngine.h
new file mode 100644
index 0000000000..7a30e1b4cc
--- /dev/null
+++ b/dom/media/webaudio/AudioNodeEngine.h
@@ -0,0 +1,389 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef MOZILLA_AUDIONODEENGINE_H_
+#define MOZILLA_AUDIONODEENGINE_H_
+
+#include "AudioSegment.h"
+#include "mozilla/dom/AudioNode.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Mutex.h"
+
+namespace WebCore {
+class Reverb;
+} // namespace WebCore
+
+namespace mozilla {
+
+namespace dom {
+struct ThreeDPoint;
+class AudioParamTimeline;
+class DelayNodeEngine;
+struct AudioParamEvent;
+} // namespace dom
+
+class AbstractThread;
+class AudioBlock;
+class AudioNodeTrack;
+
+/**
+ * This class holds onto a set of immutable channel buffers. The storage
+ * for the buffers must be malloced, but the buffer pointers and the malloc
+ * pointers can be different (e.g. if the buffers are contained inside
+ * some malloced object).
+ */
+class ThreadSharedFloatArrayBufferList final : public ThreadSharedObject {
+ public:
+ /**
+ * Construct with null channel data pointers.
+ */
+ explicit ThreadSharedFloatArrayBufferList(uint32_t aCount) {
+ mContents.SetLength(aCount);
+ }
+ /**
+ * Create with buffers suitable for transfer to
+ * JS::NewArrayBufferWithContents(). The buffer contents are uninitialized
+ * and so should be set using GetDataForWrite().
+ */
+ static already_AddRefed<ThreadSharedFloatArrayBufferList> Create(
+ uint32_t aChannelCount, size_t aLength, const mozilla::fallible_t&);
+
+ ThreadSharedFloatArrayBufferList* AsThreadSharedFloatArrayBufferList()
+ override {
+ return this;
+ };
+
+ struct Storage final {
+ Storage() : mDataToFree(nullptr), mFree(nullptr), mSampleData(nullptr) {}
+ ~Storage() {
+ if (mFree) {
+ mFree(mDataToFree);
+ } else {
+ MOZ_ASSERT(!mDataToFree);
+ }
+ }
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ // NB: mSampleData might not be owned, if it is it just points to
+ // mDataToFree.
+ return aMallocSizeOf(mDataToFree);
+ }
+ void* mDataToFree;
+ void (*mFree)(void*);
+ float* mSampleData;
+ };
+
+ /**
+ * This can be called on any thread.
+ */
+ uint32_t GetChannels() const { return mContents.Length(); }
+ /**
+ * This can be called on any thread.
+ */
+ const float* GetData(uint32_t aIndex) const {
+ return mContents[aIndex].mSampleData;
+ }
+ /**
+ * This can be called on any thread, but only when the calling thread is the
+ * only owner.
+ */
+ float* GetDataForWrite(uint32_t aIndex) {
+ MOZ_ASSERT(!IsShared());
+ return mContents[aIndex].mSampleData;
+ }
+
+ /**
+ * Call this only during initialization, before the object is handed to
+ * any other thread.
+ */
+ void SetData(uint32_t aIndex, void* aDataToFree, void (*aFreeFunc)(void*),
+ float* aData) {
+ Storage* s = &mContents[aIndex];
+ if (s->mFree) {
+ s->mFree(s->mDataToFree);
+ } else {
+ MOZ_ASSERT(!s->mDataToFree);
+ }
+
+ s->mDataToFree = aDataToFree;
+ s->mFree = aFreeFunc;
+ s->mSampleData = aData;
+ }
+
+ /**
+ * Put this object into an error state where there are no channels.
+ */
+ void Clear() { mContents.Clear(); }
+
+ size_t SizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const override {
+ size_t amount = ThreadSharedObject::SizeOfExcludingThis(aMallocSizeOf);
+ amount += mContents.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (size_t i = 0; i < mContents.Length(); i++) {
+ amount += mContents[i].SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ return amount;
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ private:
+ AutoTArray<Storage, 2> mContents;
+};
+
+/**
+ * aChunk must have been allocated by AllocateAudioBlock.
+ */
+void WriteZeroesToAudioBlock(AudioBlock* aChunk, uint32_t aStart,
+ uint32_t aLength);
+
+/**
+ * Copy with scale. aScale == 1.0f should be optimized.
+ */
+void AudioBufferCopyWithScale(const float* aInput, float aScale, float* aOutput,
+ uint32_t aSize);
+
+/**
+ * Pointwise multiply-add operation. aScale == 1.0f should be optimized.
+ */
+void AudioBufferAddWithScale(const float* aInput, float aScale, float* aOutput,
+ uint32_t aSize);
+
+/**
+ * Pointwise multiply-add operation. aScale == 1.0f should be optimized.
+ */
+void AudioBlockAddChannelWithScale(const float aInput[WEBAUDIO_BLOCK_SIZE],
+ float aScale,
+ float aOutput[WEBAUDIO_BLOCK_SIZE]);
+
+/**
+ * Pointwise copy-scaled operation. aScale == 1.0f should be optimized.
+ *
+ * Buffer size is implicitly assumed to be WEBAUDIO_BLOCK_SIZE.
+ */
+void AudioBlockCopyChannelWithScale(const float* aInput, float aScale,
+ float* aOutput);
+
+/**
+ * Vector copy-scaled operation.
+ */
+void AudioBlockCopyChannelWithScale(const float aInput[WEBAUDIO_BLOCK_SIZE],
+ const float aScale[WEBAUDIO_BLOCK_SIZE],
+ float aOutput[WEBAUDIO_BLOCK_SIZE]);
+
+/**
+ * Vector complex multiplication on arbitrary sized buffers.
+ */
+void BufferComplexMultiply(const float* aInput, const float* aScale,
+ float* aOutput, uint32_t aSize);
+
+/**
+ * Vector maximum element magnitude ( max(abs(aInput)) ).
+ */
+float AudioBufferPeakValue(const float* aInput, uint32_t aSize);
+
+/**
+ * In place gain. aScale == 1.0f should be optimized.
+ */
+void AudioBlockInPlaceScale(float aBlock[WEBAUDIO_BLOCK_SIZE], float aScale);
+
+/**
+ * In place gain. aScale == 1.0f should be optimized.
+ */
+void AudioBufferInPlaceScale(float* aBlock, float aScale, uint32_t aSize);
+
+/**
+ * a-rate in place gain.
+ */
+void AudioBlockInPlaceScale(float aBlock[WEBAUDIO_BLOCK_SIZE],
+ float aScale[WEBAUDIO_BLOCK_SIZE]);
+/**
+ * a-rate in place gain.
+ */
+void AudioBufferInPlaceScale(float* aBlock, float* aScale, uint32_t aSize);
+
+/**
+ * Upmix a mono input to a stereo output, scaling the two output channels by two
+ * different gain value.
+ * This algorithm is specified in the WebAudio spec.
+ */
+void AudioBlockPanMonoToStereo(const float aInput[WEBAUDIO_BLOCK_SIZE],
+ float aGainL, float aGainR,
+ float aOutputL[WEBAUDIO_BLOCK_SIZE],
+ float aOutputR[WEBAUDIO_BLOCK_SIZE]);
+
+void AudioBlockPanMonoToStereo(const float aInput[WEBAUDIO_BLOCK_SIZE],
+ float aGainL[WEBAUDIO_BLOCK_SIZE],
+ float aGainR[WEBAUDIO_BLOCK_SIZE],
+ float aOutputL[WEBAUDIO_BLOCK_SIZE],
+ float aOutputR[WEBAUDIO_BLOCK_SIZE]);
+/**
+ * Pan a stereo source according to right and left gain, and the position
+ * (whether the listener is on the left of the source or not).
+ * This algorithm is specified in the WebAudio spec.
+ */
+void AudioBlockPanStereoToStereo(const float aInputL[WEBAUDIO_BLOCK_SIZE],
+ const float aInputR[WEBAUDIO_BLOCK_SIZE],
+ float aGainL, float aGainR, bool aIsOnTheLeft,
+ float aOutputL[WEBAUDIO_BLOCK_SIZE],
+ float aOutputR[WEBAUDIO_BLOCK_SIZE]);
+void AudioBlockPanStereoToStereo(const float aInputL[WEBAUDIO_BLOCK_SIZE],
+ const float aInputR[WEBAUDIO_BLOCK_SIZE],
+ const float aGainL[WEBAUDIO_BLOCK_SIZE],
+ const float aGainR[WEBAUDIO_BLOCK_SIZE],
+ const bool aIsOnTheLeft[WEBAUDIO_BLOCK_SIZE],
+ float aOutputL[WEBAUDIO_BLOCK_SIZE],
+ float aOutputR[WEBAUDIO_BLOCK_SIZE]);
+
+/**
+ * Replace NaN by zeros in aSamples.
+ */
+void NaNToZeroInPlace(float* aSamples, size_t aCount);
+
+/**
+ * Return the sum of squares of all of the samples in the input.
+ */
+float AudioBufferSumOfSquares(const float* aInput, uint32_t aLength);
+
+/**
+ * All methods of this class and its subclasses are called on the
+ * MediaTrackGraph thread.
+ */
+class AudioNodeEngine {
+ public:
+ // This should be compatible with AudioNodeTrack::OutputChunks.
+ typedef AutoTArray<AudioBlock, 1> OutputChunks;
+
+ explicit AudioNodeEngine(dom::AudioNode* aNode);
+
+ virtual ~AudioNodeEngine() {
+ MOZ_ASSERT(!mNode, "The node reference must be already cleared");
+ MOZ_COUNT_DTOR(AudioNodeEngine);
+ }
+
+ virtual dom::DelayNodeEngine* AsDelayNodeEngine() { return nullptr; }
+
+ virtual void SetTrackTimeParameter(uint32_t aIndex, TrackTime aParam) {
+ NS_ERROR("Invalid SetTrackTimeParameter index");
+ }
+ virtual void SetDoubleParameter(uint32_t aIndex, double aParam) {
+ NS_ERROR("Invalid SetDoubleParameter index");
+ }
+ virtual void SetInt32Parameter(uint32_t aIndex, int32_t aParam) {
+ NS_ERROR("Invalid SetInt32Parameter index");
+ }
+ virtual void RecvTimelineEvent(uint32_t aIndex,
+ dom::AudioParamEvent& aValue) {
+ NS_ERROR("Invalid RecvTimelineEvent index");
+ }
+ virtual void SetBuffer(AudioChunk&& aBuffer) {
+ NS_ERROR("SetBuffer called on engine that doesn't support it");
+ }
+ // This consumes the contents of aData. aData will be emptied after this
+ // returns.
+ virtual void SetRawArrayData(nsTArray<float>&& aData) {
+ NS_ERROR("SetRawArrayData called on an engine that doesn't support it");
+ }
+
+ virtual void SetReverb(WebCore::Reverb* aBuffer,
+ uint32_t aImpulseChannelCount) {
+ NS_ERROR("SetReverb called on engine that doesn't support it");
+ }
+
+ /**
+ * Produce the next block of audio samples, given input samples aInput
+ * (the mixed data for input 0).
+ * aInput is guaranteed to have float sample format (if it has samples at all)
+ * and to have been resampled to the sampling rate for the track, and to have
+ * exactly WEBAUDIO_BLOCK_SIZE samples.
+ * *aFinished is set to false by the caller. The callee must not set this to
+ * true unless silent output is produced. If set to true, we'll finish the
+ * track, consider this input inactive on any downstream nodes, and not
+ * call this again.
+ */
+ virtual void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
+ const AudioBlock& aInput, AudioBlock* aOutput,
+ bool* aFinished);
+ /**
+ * Produce the next block of audio samples, before input is provided.
+ * ProcessBlock() will be called later, and it then should not change
+ * aOutput. This is used only for DelayNodeEngine in a feedback loop.
+ */
+ virtual void ProduceBlockBeforeInput(AudioNodeTrack* aTrack, GraphTime aFrom,
+ AudioBlock* aOutput) {
+ MOZ_ASSERT_UNREACHABLE("ProduceBlockBeforeInput called on wrong engine");
+ }
+
+ /**
+ * Produce the next block of audio samples, given input samples in the aInput
+ * array. There is one input sample per port in aInput, in order.
+ * This is the multi-input/output version of ProcessBlock. Only one kind
+ * of ProcessBlock is called on each node. ProcessBlocksOnPorts() is called
+ * instead of ProcessBlock() if either the number of inputs or the number of
+ * outputs is greater than 1.
+ *
+ * The numbers of AudioBlocks in aInput and aOutput are always guaranteed to
+ * match the numbers of inputs and outputs for the node.
+ */
+ virtual void ProcessBlocksOnPorts(AudioNodeTrack* aTrack, GraphTime aFrom,
+ Span<const AudioBlock> aInput,
+ Span<AudioBlock> aOutput, bool* aFinished);
+
+ // IsActive() returns true if the engine needs to continue processing an
+ // unfinished track even when it has silent or no input connections. This
+ // includes tail-times and when sources have been scheduled to start. If
+ // returning false, then the track can be suspended.
+ virtual bool IsActive() const { return false; }
+
+ // Called on graph thread when the engine will not be used again.
+ virtual void OnGraphThreadDone() {}
+
+ bool HasNode() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ return !!mNode;
+ }
+
+ dom::AudioNode* NodeMainThread() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ return mNode;
+ }
+
+ void ClearNode() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mNode != nullptr);
+ mNode = nullptr;
+ }
+
+ uint16_t InputCount() const { return mInputCount; }
+ uint16_t OutputCount() const { return mOutputCount; }
+
+ virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ // NB: |mNode| is tracked separately so it is excluded here.
+ return 0;
+ }
+
+ virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ void SizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+ AudioNodeSizes& aUsage) const {
+ aUsage.mEngine = SizeOfIncludingThis(aMallocSizeOf);
+ aUsage.mNodeType = mNodeType;
+ }
+
+ private:
+ // This is cleared from AudioNode::DestroyMediaTrack()
+ dom::AudioNode* MOZ_NON_OWNING_REF mNode; // main thread only
+ const char* const mNodeType;
+ const uint16_t mInputCount;
+ const uint16_t mOutputCount;
+};
+
+} // namespace mozilla
+
+#endif /* MOZILLA_AUDIONODEENGINE_H_ */
diff --git a/dom/media/webaudio/AudioNodeEngineGeneric.h b/dom/media/webaudio/AudioNodeEngineGeneric.h
new file mode 100644
index 0000000000..de436ebc8d
--- /dev/null
+++ b/dom/media/webaudio/AudioNodeEngineGeneric.h
@@ -0,0 +1,58 @@
+/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* this source code form is subject to the terms of the mozilla public
+ * license, v. 2.0. if a copy of the mpl was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_AUDIONODEENGINEGENERIC_H_
+#define MOZILLA_AUDIONODEENGINEGENERIC_H_
+
+#include "AudioNodeEngine.h"
+
+#include "xsimd/xsimd.hpp"
+
+namespace mozilla {
+
+template <class Arch>
+struct Engine {
+ static void AudioBufferAddWithScale(const float* aInput, float aScale,
+ float* aOutput, uint32_t aSize);
+
+ static void AudioBlockCopyChannelWithScale(const float* aInput, float aScale,
+ float* aOutput);
+
+ static void AudioBlockCopyChannelWithScale(
+ const float aInput[WEBAUDIO_BLOCK_SIZE],
+ const float aScale[WEBAUDIO_BLOCK_SIZE],
+ float aOutput[WEBAUDIO_BLOCK_SIZE]);
+
+ static void AudioBufferInPlaceScale(float* aBlock, float aScale,
+ uint32_t aSize);
+
+ static void AudioBufferInPlaceScale(float* aBlock, float* aScale,
+ uint32_t aSize);
+
+ static void AudioBlockPanStereoToStereo(
+ const float aInputL[WEBAUDIO_BLOCK_SIZE],
+ const float aInputR[WEBAUDIO_BLOCK_SIZE], float aGainL, float aGainR,
+ bool aIsOnTheLeft, float aOutputL[WEBAUDIO_BLOCK_SIZE],
+ float aOutputR[WEBAUDIO_BLOCK_SIZE]);
+
+ static void BufferComplexMultiply(const float* aInput, const float* aScale,
+ float* aOutput, uint32_t aSize);
+
+ static float AudioBufferSumOfSquares(const float* aInput, uint32_t aLength);
+
+ static void NaNToZeroInPlace(float* aSamples, size_t aCount);
+
+ static void AudioBlockPanStereoToStereo(
+ const float aInputL[WEBAUDIO_BLOCK_SIZE],
+ const float aInputR[WEBAUDIO_BLOCK_SIZE],
+ const float aGainL[WEBAUDIO_BLOCK_SIZE],
+ const float aGainR[WEBAUDIO_BLOCK_SIZE],
+ const bool aIsOnTheLeft[WEBAUDIO_BLOCK_SIZE],
+ float aOutputL[WEBAUDIO_BLOCK_SIZE], float aOutputR[WEBAUDIO_BLOCK_SIZE]);
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webaudio/AudioNodeEngineGenericImpl.h b/dom/media/webaudio/AudioNodeEngineGenericImpl.h
new file mode 100644
index 0000000000..b7fabc7a0b
--- /dev/null
+++ b/dom/media/webaudio/AudioNodeEngineGenericImpl.h
@@ -0,0 +1,341 @@
+/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* this source code form is subject to the terms of the mozilla public
+ * license, v. 2.0. if a copy of the mpl was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_AUDIONODEENGINEGENERICIMPL_H_
+#define MOZILLA_AUDIONODEENGINEGENERICIMPL_H_
+
+#include "AudioNodeEngineGeneric.h"
+#include "AlignmentUtils.h"
+
+#if defined(__GNUC__) && __GNUC__ > 7
+# define MOZ_PRAGMA(tokens) _Pragma(#tokens)
+# define MOZ_UNROLL(factor) MOZ_PRAGMA(GCC unroll factor)
+#elif defined(__INTEL_COMPILER) || (defined(__clang__) && __clang_major__ > 3)
+# define MOZ_PRAGMA(tokens) _Pragma(#tokens)
+# define MOZ_UNROLL(factor) MOZ_PRAGMA(unroll factor)
+#else
+# define MOZ_UNROLL(_)
+#endif
+
+namespace mozilla {
+
+template <class Arch>
+static bool is_aligned(const void* ptr) {
+ return (reinterpret_cast<uintptr_t>(ptr) &
+ ~(static_cast<uintptr_t>(Arch::alignment()) - 1)) ==
+ reinterpret_cast<uintptr_t>(ptr);
+};
+
+template <class Arch>
+void Engine<Arch>::AudioBufferAddWithScale(const float* aInput, float aScale,
+ float* aOutput, uint32_t aSize) {
+ if constexpr (Arch::requires_alignment()) {
+ if (aScale == 1.0f) {
+ while (!is_aligned<Arch>(aInput) || !is_aligned<Arch>(aOutput)) {
+ if (!aSize) return;
+ *aOutput += *aInput;
+ ++aOutput;
+ ++aInput;
+ --aSize;
+ }
+ } else {
+ while (!is_aligned<Arch>(aInput) || !is_aligned<Arch>(aOutput)) {
+ if (!aSize) return;
+ *aOutput += *aInput * aScale;
+ ++aOutput;
+ ++aInput;
+ --aSize;
+ }
+ }
+ }
+ MOZ_ASSERT(is_aligned<Arch>(aInput), "aInput is aligned");
+ MOZ_ASSERT(is_aligned<Arch>(aOutput), "aOutput is aligned");
+
+ xsimd::batch<float, Arch> vgain(aScale);
+
+ uint32_t aVSize = aSize & ~(xsimd::batch<float, Arch>::size - 1);
+ MOZ_UNROLL(4)
+ for (unsigned i = 0; i < aVSize; i += xsimd::batch<float, Arch>::size) {
+ auto vin1 = xsimd::batch<float, Arch>::load_aligned(&aInput[i]);
+ auto vin2 = xsimd::batch<float, Arch>::load_aligned(&aOutput[i]);
+ auto vout = xsimd::fma(vin1, vgain, vin2);
+ vout.store_aligned(&aOutput[i]);
+ }
+
+ for (unsigned i = aVSize; i < aSize; ++i) {
+ aOutput[i] += aInput[i] * aScale;
+ }
+}
+
+template <class Arch>
+void Engine<Arch>::AudioBlockCopyChannelWithScale(const float* aInput,
+ float aScale,
+ float* aOutput) {
+ MOZ_ASSERT(is_aligned<Arch>(aInput), "aInput is aligned");
+ MOZ_ASSERT(is_aligned<Arch>(aOutput), "aOutput is aligned");
+
+ MOZ_ASSERT((WEBAUDIO_BLOCK_SIZE % xsimd::batch<float, Arch>::size == 0),
+ "requires tail processing");
+
+ xsimd::batch<float, Arch> vgain = (aScale);
+
+ MOZ_UNROLL(4)
+ for (unsigned i = 0; i < WEBAUDIO_BLOCK_SIZE;
+ i += xsimd::batch<float, Arch>::size) {
+ auto vin = xsimd::batch<float, Arch>::load_aligned(&aInput[i]);
+ auto vout = vin * vgain;
+ vout.store_aligned(&aOutput[i]);
+ }
+};
+
+template <class Arch>
+void Engine<Arch>::AudioBlockCopyChannelWithScale(
+ const float aInput[WEBAUDIO_BLOCK_SIZE],
+ const float aScale[WEBAUDIO_BLOCK_SIZE],
+ float aOutput[WEBAUDIO_BLOCK_SIZE]) {
+ MOZ_ASSERT(is_aligned<Arch>(aInput), "aInput is aligned");
+ MOZ_ASSERT(is_aligned<Arch>(aOutput), "aOutput is aligned");
+ MOZ_ASSERT(is_aligned<Arch>(aScale), "aScale is aligned");
+
+ MOZ_ASSERT((WEBAUDIO_BLOCK_SIZE % xsimd::batch<float, Arch>::size == 0),
+ "requires tail processing");
+
+ MOZ_UNROLL(4)
+ for (unsigned i = 0; i < WEBAUDIO_BLOCK_SIZE;
+ i += xsimd::batch<float, Arch>::size) {
+ auto vscaled = xsimd::batch<float, Arch>::load_aligned(&aScale[i]);
+ auto vin = xsimd::batch<float, Arch>::load_aligned(&aInput[i]);
+ auto vout = vin * vscaled;
+ vout.store_aligned(&aOutput[i]);
+ }
+};
+
+template <class Arch>
+void Engine<Arch>::AudioBufferInPlaceScale(float* aBlock, float aScale,
+ uint32_t aSize) {
+ MOZ_ASSERT(is_aligned<Arch>(aBlock), "aBlock is aligned");
+
+ xsimd::batch<float, Arch> vgain(aScale);
+
+ uint32_t aVSize = aSize & ~(xsimd::batch<float, Arch>::size - 1);
+ MOZ_UNROLL(4)
+ for (unsigned i = 0; i < aVSize; i += xsimd::batch<float, Arch>::size) {
+ auto vin = xsimd::batch<float, Arch>::load_aligned(&aBlock[i]);
+ auto vout = vin * vgain;
+ vout.store_aligned(&aBlock[i]);
+ }
+ for (unsigned i = aVSize; i < aSize; ++i) aBlock[i] *= aScale;
+};
+
+template <class Arch>
+void Engine<Arch>::AudioBufferInPlaceScale(float* aBlock, float* aScale,
+ uint32_t aSize) {
+ MOZ_ASSERT(is_aligned<Arch>(aBlock), "aBlock is aligned");
+ MOZ_ASSERT(is_aligned<Arch>(aScale), "aScale is aligned");
+
+ uint32_t aVSize = aSize & ~(xsimd::batch<float, Arch>::size - 1);
+ MOZ_UNROLL(4)
+ for (unsigned i = 0; i < aVSize; i += xsimd::batch<float, Arch>::size) {
+ auto vin = xsimd::batch<float, Arch>::load_aligned(&aBlock[i]);
+ auto vgain = xsimd::batch<float, Arch>::load_aligned(&aScale[i]);
+ auto vout = vin * vgain;
+ vout.store_aligned(&aBlock[i]);
+ }
+ for (uint32_t i = aVSize; i < aSize; ++i) {
+ *aBlock++ *= *aScale++;
+ }
+};
+
+template <class Arch>
+void Engine<Arch>::AudioBlockPanStereoToStereo(
+ const float aInputL[WEBAUDIO_BLOCK_SIZE],
+ const float aInputR[WEBAUDIO_BLOCK_SIZE], float aGainL, float aGainR,
+ bool aIsOnTheLeft, float aOutputL[WEBAUDIO_BLOCK_SIZE],
+ float aOutputR[WEBAUDIO_BLOCK_SIZE]) {
+ MOZ_ASSERT(is_aligned<Arch>(aInputL), "aInputL is aligned");
+ MOZ_ASSERT(is_aligned<Arch>(aInputR), "aInputR is aligned");
+ MOZ_ASSERT(is_aligned<Arch>(aOutputL), "aOutputL is aligned");
+ MOZ_ASSERT(is_aligned<Arch>(aOutputR), "aOutputR is aligned");
+
+ MOZ_ASSERT((WEBAUDIO_BLOCK_SIZE % xsimd::batch<float, Arch>::size == 0),
+ "requires tail processing");
+
+ xsimd::batch<float, Arch> vgainl(aGainL);
+ xsimd::batch<float, Arch> vgainr(aGainR);
+
+ if (aIsOnTheLeft) {
+ MOZ_UNROLL(2)
+ for (unsigned i = 0; i < WEBAUDIO_BLOCK_SIZE;
+ i += xsimd::batch<float, Arch>::size) {
+ auto vinl = xsimd::batch<float, Arch>::load_aligned(&aInputL[i]);
+ auto vinr = xsimd::batch<float, Arch>::load_aligned(&aInputR[i]);
+
+ /* left channel : aOutputL = aInputL + aInputR * gainL */
+ auto vout = xsimd::fma(vinr, vgainl, vinl);
+ vout.store_aligned(&aOutputL[i]);
+
+ /* right channel : aOutputR = aInputR * gainR */
+ auto vscaled = vinr * vgainr;
+ vscaled.store_aligned(&aOutputR[i]);
+ }
+ } else {
+ MOZ_UNROLL(2)
+ for (unsigned i = 0; i < WEBAUDIO_BLOCK_SIZE;
+ i += xsimd::batch<float, Arch>::size) {
+ auto vinl = xsimd::batch<float, Arch>::load_aligned(&aInputL[i]);
+ auto vinr = xsimd::batch<float, Arch>::load_aligned(&aInputR[i]);
+
+ /* left channel : aInputL * gainL */
+ auto vscaled = vinl * vgainl;
+ vscaled.store_aligned(&aOutputL[i]);
+
+ /* right channel: aOutputR = aInputR + aInputL * gainR */
+ auto vout = xsimd::fma(vinl, vgainr, vinr);
+ vout.store_aligned(&aOutputR[i]);
+ }
+ }
+};
+
+template <class Arch>
+void Engine<Arch>::BufferComplexMultiply(const float* aInput,
+ const float* aScale, float* aOutput,
+ uint32_t aSize) {
+ MOZ_ASSERT(is_aligned<Arch>(aInput), "aInput is aligned");
+ MOZ_ASSERT(is_aligned<Arch>(aOutput), "aOutput is aligned");
+ MOZ_ASSERT(is_aligned<Arch>(aScale), "aScale is aligned");
+ MOZ_ASSERT((aSize % xsimd::batch<float, Arch>::size == 0),
+ "requires tail processing");
+
+ MOZ_UNROLL(2)
+ for (unsigned i = 0; i < aSize * 2;
+ i += 2 * xsimd::batch<std::complex<float>, Arch>::size) {
+ auto in1 = xsimd::batch<std::complex<float>, Arch>::load_aligned(
+ reinterpret_cast<const std::complex<float>*>(&aInput[i]));
+ auto in2 = xsimd::batch<std::complex<float>, Arch>::load_aligned(
+ reinterpret_cast<const std::complex<float>*>(&aScale[i]));
+ auto out = in1 * in2;
+ out.store_aligned(reinterpret_cast<std::complex<float>*>(&aOutput[i]));
+ }
+};
+
+template <class Arch>
+float Engine<Arch>::AudioBufferSumOfSquares(const float* aInput,
+ uint32_t aLength) {
+ float sum = 0.f;
+
+ if constexpr (Arch::requires_alignment()) {
+ while (!is_aligned<Arch>(aInput)) {
+ if (!aLength) {
+ return sum;
+ }
+ sum += *aInput * *aInput;
+ ++aInput;
+ --aLength;
+ }
+ }
+
+ MOZ_ASSERT(is_aligned<Arch>(aInput), "aInput is aligned");
+
+ constexpr uint32_t unroll_factor = 4;
+ xsimd::batch<float, Arch> accs[unroll_factor] = {0.f, 0.f, 0.f, 0.f};
+
+ uint32_t vLength =
+ aLength & ~(unroll_factor * xsimd::batch<float, Arch>::size - 1);
+
+ for (uint32_t i = 0; i < vLength;
+ i += unroll_factor * xsimd::batch<float, Arch>::size) {
+ MOZ_UNROLL(4)
+ for (uint32_t j = 0; j < unroll_factor; ++j) {
+ auto in = xsimd::batch<float, Arch>::load_aligned(
+ &aInput[i + xsimd::batch<float, Arch>::size * j]);
+ accs[j] = xsimd::fma(in, in, accs[j]);
+ }
+ }
+
+ sum += reduce_add((accs[0] + accs[1]) + (accs[2] + accs[3]));
+ for (uint32_t i = vLength; i < aLength; ++i) sum += aInput[i] * aInput[i];
+ return sum;
+};
+
+template <class Arch>
+void Engine<Arch>::NaNToZeroInPlace(float* aSamples, size_t aCount) {
+ if constexpr (Arch::requires_alignment()) {
+ while (!is_aligned<Arch>(aSamples)) {
+ if (!aCount) {
+ return;
+ }
+ if (*aSamples != *aSamples) {
+ *aSamples = 0.0;
+ }
+ ++aSamples;
+ --aCount;
+ }
+ }
+
+ MOZ_ASSERT(is_aligned<Arch>(aSamples), "aSamples is aligned");
+
+ uint32_t vCount = aCount & ~(xsimd::batch<float, Arch>::size - 1);
+
+ MOZ_UNROLL(4)
+ for (uint32_t i = 0; i < vCount; i += xsimd::batch<float, Arch>::size) {
+ auto vin = xsimd::batch<float, Arch>::load_aligned(&aSamples[i]);
+ auto vout =
+ xsimd::select(xsimd::isnan(vin), xsimd::batch<float, Arch>(0.f), vin);
+ vout.store_aligned(&aSamples[i]);
+ }
+
+ for (uint32_t i = vCount; i < aCount; i++) {
+ if (aSamples[i] != aSamples[i]) {
+ aSamples[i] = 0.0;
+ }
+ }
+};
+
+template <class Arch>
+void Engine<Arch>::AudioBlockPanStereoToStereo(
+ const float aInputL[WEBAUDIO_BLOCK_SIZE],
+ const float aInputR[WEBAUDIO_BLOCK_SIZE],
+ const float aGainL[WEBAUDIO_BLOCK_SIZE],
+ const float aGainR[WEBAUDIO_BLOCK_SIZE],
+ const bool aIsOnTheLeft[WEBAUDIO_BLOCK_SIZE],
+ float aOutputL[WEBAUDIO_BLOCK_SIZE], float aOutputR[WEBAUDIO_BLOCK_SIZE]) {
+ MOZ_ASSERT(is_aligned<Arch>(aInputL), "aInputL is aligned");
+ MOZ_ASSERT(is_aligned<Arch>(aInputR), "aInputR is aligned");
+ MOZ_ASSERT(is_aligned<Arch>(aGainL), "aGainL is aligned");
+ MOZ_ASSERT(is_aligned<Arch>(aGainR), "aGainR is aligned");
+ MOZ_ASSERT(is_aligned<Arch>(aIsOnTheLeft), "aIsOnTheLeft is aligned");
+ MOZ_ASSERT(is_aligned<Arch>(aOutputL), "aOutputL is aligned");
+ MOZ_ASSERT(is_aligned<Arch>(aOutputR), "aOutputR is aligned");
+
+ MOZ_ASSERT((WEBAUDIO_BLOCK_SIZE % xsimd::batch<float, Arch>::size == 0),
+ "requires tail processing");
+
+ MOZ_UNROLL(2)
+ for (uint32_t i = 0; i < WEBAUDIO_BLOCK_SIZE;
+ i += xsimd::batch<float, Arch>::size) {
+ auto mask = xsimd::batch_bool<float, Arch>::load_aligned(&aIsOnTheLeft[i]);
+
+ auto inputL = xsimd::batch<float, Arch>::load_aligned(&aInputL[i]);
+ auto inputR = xsimd::batch<float, Arch>::load_aligned(&aInputR[i]);
+ auto gainL = xsimd::batch<float, Arch>::load_aligned(&aGainL[i]);
+ auto gainR = xsimd::batch<float, Arch>::load_aligned(&aGainR[i]);
+
+ auto outL_true = xsimd::fma(inputR, gainL, inputL);
+ auto outR_true = inputR * gainR;
+
+ auto outL_false = inputL * gainL;
+ auto outR_false = xsimd::fma(inputL, gainR, inputR);
+
+ auto outL = xsimd::select(mask, outL_true, outL_false);
+ auto outR = xsimd::select(mask, outR_true, outR_false);
+
+ outL.store_aligned(&aOutputL[i]);
+ outR.store_aligned(&aOutputR[i]);
+ }
+}
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webaudio/AudioNodeEngineNEON.cpp b/dom/media/webaudio/AudioNodeEngineNEON.cpp
new file mode 100644
index 0000000000..93f5c85285
--- /dev/null
+++ b/dom/media/webaudio/AudioNodeEngineNEON.cpp
@@ -0,0 +1,9 @@
+/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* this source code form is subject to the terms of the mozilla public
+ * license, v. 2.0. if a copy of the mpl was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AudioNodeEngineGenericImpl.h"
+namespace mozilla {
+template struct Engine<xsimd::neon>;
+} // namespace mozilla
diff --git a/dom/media/webaudio/AudioNodeEngineSSE2.cpp b/dom/media/webaudio/AudioNodeEngineSSE2.cpp
new file mode 100644
index 0000000000..558cd866fd
--- /dev/null
+++ b/dom/media/webaudio/AudioNodeEngineSSE2.cpp
@@ -0,0 +1,10 @@
+/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* this source code form is subject to the terms of the mozilla public
+ * license, v. 2.0. if a copy of the mpl was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AudioNodeEngineGenericImpl.h"
+
+namespace mozilla {
+template struct Engine<xsimd::sse2>;
+} // namespace mozilla
diff --git a/dom/media/webaudio/AudioNodeEngineSSE4_2_FMA3.cpp b/dom/media/webaudio/AudioNodeEngineSSE4_2_FMA3.cpp
new file mode 100644
index 0000000000..ce0134ee4b
--- /dev/null
+++ b/dom/media/webaudio/AudioNodeEngineSSE4_2_FMA3.cpp
@@ -0,0 +1,10 @@
+/* -*- mode: c++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* this source code form is subject to the terms of the mozilla public
+ * license, v. 2.0. if a copy of the mpl was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AudioNodeEngineGenericImpl.h"
+
+namespace mozilla {
+template struct Engine<xsimd::fma3<xsimd::sse4_2>>;
+} // namespace mozilla
diff --git a/dom/media/webaudio/AudioNodeExternalInputTrack.cpp b/dom/media/webaudio/AudioNodeExternalInputTrack.cpp
new file mode 100644
index 0000000000..8867a2dbf2
--- /dev/null
+++ b/dom/media/webaudio/AudioNodeExternalInputTrack.cpp
@@ -0,0 +1,225 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AlignedTArray.h"
+#include "AlignmentUtils.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeExternalInputTrack.h"
+#include "AudioChannelFormat.h"
+#include "mozilla/dom/MediaStreamAudioSourceNode.h"
+
+using namespace mozilla::dom;
+
+namespace mozilla {
+
+AudioNodeExternalInputTrack::AudioNodeExternalInputTrack(
+ AudioNodeEngine* aEngine, TrackRate aSampleRate)
+ : AudioNodeTrack(aEngine, NO_TRACK_FLAGS, aSampleRate) {
+ MOZ_COUNT_CTOR(AudioNodeExternalInputTrack);
+}
+
+AudioNodeExternalInputTrack::~AudioNodeExternalInputTrack() {
+ MOZ_COUNT_DTOR(AudioNodeExternalInputTrack);
+}
+
+/* static */
+already_AddRefed<AudioNodeExternalInputTrack>
+AudioNodeExternalInputTrack::Create(MediaTrackGraph* aGraph,
+ AudioNodeEngine* aEngine) {
+ AudioContext* ctx = aEngine->NodeMainThread()->Context();
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aGraph == ctx->Graph());
+
+ RefPtr<AudioNodeExternalInputTrack> track =
+ new AudioNodeExternalInputTrack(aEngine, aGraph->GraphRate());
+ track->mSuspendedCount += ctx->ShouldSuspendNewTrack();
+ aGraph->AddTrack(track);
+ return track.forget();
+}
+
+/**
+ * Copies the data in aInput to aOffsetInBlock within aBlock.
+ * aBlock must have been allocated with AllocateInputBlock and have a channel
+ * count that's a superset of the channels in aInput.
+ */
+template <typename T>
+static void CopyChunkToBlock(AudioChunk& aInput, AudioBlock* aBlock,
+ uint32_t aOffsetInBlock) {
+ uint32_t blockChannels = aBlock->ChannelCount();
+ AutoTArray<const T*, 2> channels;
+ if (aInput.IsNull()) {
+ channels.SetLength(blockChannels);
+ PodZero(channels.Elements(), blockChannels);
+ } else {
+ Span inputChannels = aInput.ChannelData<T>();
+ channels.SetLength(inputChannels.Length());
+ PodCopy(channels.Elements(), inputChannels.Elements(), channels.Length());
+ if (channels.Length() != blockChannels) {
+ // We only need to upmix here because aBlock's channel count has been
+ // chosen to be a superset of the channel count of every chunk.
+ AudioChannelsUpMix(&channels, blockChannels, static_cast<T*>(nullptr));
+ }
+ }
+
+ for (uint32_t c = 0; c < blockChannels; ++c) {
+ float* outputData = aBlock->ChannelFloatsForWrite(c) + aOffsetInBlock;
+ if (channels[c]) {
+ ConvertAudioSamplesWithScale(channels[c], outputData,
+ aInput.GetDuration(), aInput.mVolume);
+ } else {
+ PodZero(outputData, aInput.GetDuration());
+ }
+ }
+}
+
+/**
+ * Converts the data in aSegment to a single chunk aBlock. aSegment must have
+ * duration WEBAUDIO_BLOCK_SIZE. aFallbackChannelCount is a superset of the
+ * channels in every chunk of aSegment. aBlock must be float format or null.
+ */
+static void ConvertSegmentToAudioBlock(AudioSegment* aSegment,
+ AudioBlock* aBlock,
+ int32_t aFallbackChannelCount) {
+ NS_ASSERTION(aSegment->GetDuration() == WEBAUDIO_BLOCK_SIZE,
+ "Bad segment duration");
+
+ {
+ AudioSegment::ChunkIterator ci(*aSegment);
+ NS_ASSERTION(!ci.IsEnded(), "Should be at least one chunk!");
+ if (ci->GetDuration() == WEBAUDIO_BLOCK_SIZE &&
+ (ci->IsNull() || ci->mBufferFormat == AUDIO_FORMAT_FLOAT32)) {
+ bool aligned = true;
+ for (size_t i = 0; i < ci->mChannelData.Length(); ++i) {
+ if (!IS_ALIGNED16(ci->mChannelData[i])) {
+ aligned = false;
+ break;
+ }
+ }
+
+ // Return this chunk directly to avoid copying data.
+ if (aligned) {
+ *aBlock = *ci;
+ return;
+ }
+ }
+ }
+
+ aBlock->AllocateChannels(aFallbackChannelCount);
+
+ uint32_t duration = 0;
+ for (AudioSegment::ChunkIterator ci(*aSegment); !ci.IsEnded(); ci.Next()) {
+ switch (ci->mBufferFormat) {
+ case AUDIO_FORMAT_S16: {
+ CopyChunkToBlock<int16_t>(*ci, aBlock, duration);
+ break;
+ }
+ case AUDIO_FORMAT_FLOAT32: {
+ CopyChunkToBlock<float>(*ci, aBlock, duration);
+ break;
+ }
+ case AUDIO_FORMAT_SILENCE: {
+ // The actual type of the sample does not matter here, but we still need
+ // to send some audio to the graph.
+ CopyChunkToBlock<float>(*ci, aBlock, duration);
+ break;
+ }
+ }
+ duration += ci->GetDuration();
+ }
+}
+
+void AudioNodeExternalInputTrack::ProcessInput(GraphTime aFrom, GraphTime aTo,
+ uint32_t aFlags) {
+ // According to spec, number of outputs is always 1.
+ MOZ_ASSERT(mLastChunks.Length() == 1);
+
+ // GC stuff can result in our input track being destroyed before this track.
+ // Handle that.
+ if (!IsEnabled() || mInputs.IsEmpty() || mPassThrough) {
+ mLastChunks[0].SetNull(WEBAUDIO_BLOCK_SIZE);
+ return;
+ }
+
+ MOZ_ASSERT(mInputs.Length() == 1);
+
+ MediaTrack* source = mInputs[0]->GetSource();
+ AutoTArray<AudioSegment, 1> audioSegments;
+ uint32_t inputChannels = 0;
+
+ MOZ_ASSERT(source->GetData()->GetType() == MediaSegment::AUDIO,
+ "AudioNodeExternalInputTrack shouldn't have a video input");
+
+ const AudioSegment& inputSegment =
+ *mInputs[0]->GetSource()->GetData<AudioSegment>();
+ if (!inputSegment.IsNull()) {
+ AudioSegment& segment = *audioSegments.AppendElement();
+ GraphTime next;
+ for (GraphTime t = aFrom; t < aTo; t = next) {
+ MediaInputPort::InputInterval interval =
+ MediaInputPort::GetNextInputInterval(mInputs[0], t);
+ interval.mEnd = std::min(interval.mEnd, aTo);
+ if (interval.mStart >= interval.mEnd) {
+ break;
+ }
+ next = interval.mEnd;
+
+ // We know this track does not block during the processing interval ---
+ // we're not finished, we don't underrun, and we're not suspended.
+ TrackTime outputStart = GraphTimeToTrackTime(interval.mStart);
+ TrackTime outputEnd = GraphTimeToTrackTime(interval.mEnd);
+ TrackTime ticks = outputEnd - outputStart;
+
+ if (interval.mInputIsBlocked) {
+ segment.AppendNullData(ticks);
+ } else {
+ // The input track is not blocked in this interval, so no need to call
+ // GraphTimeToTrackTimeWithBlocking.
+ TrackTime inputStart =
+ std::min(inputSegment.GetDuration(),
+ source->GraphTimeToTrackTime(interval.mStart));
+ TrackTime inputEnd =
+ std::min(inputSegment.GetDuration(),
+ source->GraphTimeToTrackTime(interval.mEnd));
+
+ segment.AppendSlice(inputSegment, inputStart, inputEnd);
+ // Pad if we're looking past the end of the track
+ segment.AppendNullData(ticks - (inputEnd - inputStart));
+ }
+ }
+
+ for (AudioSegment::ChunkIterator iter(segment); !iter.IsEnded();
+ iter.Next()) {
+ inputChannels =
+ GetAudioChannelsSuperset(inputChannels, iter->ChannelCount());
+ }
+ }
+
+ uint32_t accumulateIndex = 0;
+ if (inputChannels) {
+ DownmixBufferType downmixBuffer;
+ ASSERT_ALIGNED16(downmixBuffer.Elements());
+ for (auto& audioSegment : audioSegments) {
+ AudioBlock tmpChunk;
+ ConvertSegmentToAudioBlock(&audioSegment, &tmpChunk, inputChannels);
+ if (!tmpChunk.IsNull()) {
+ if (accumulateIndex == 0) {
+ mLastChunks[0].AllocateChannels(inputChannels);
+ }
+ AccumulateInputChunk(accumulateIndex, tmpChunk, &mLastChunks[0],
+ &downmixBuffer);
+ accumulateIndex++;
+ }
+ }
+ }
+ if (accumulateIndex == 0) {
+ mLastChunks[0].SetNull(WEBAUDIO_BLOCK_SIZE);
+ }
+}
+
+bool AudioNodeExternalInputTrack::IsEnabled() {
+ return ((MediaStreamAudioSourceNodeEngine*)Engine())->IsEnabled();
+}
+
+} // namespace mozilla
diff --git a/dom/media/webaudio/AudioNodeExternalInputTrack.h b/dom/media/webaudio/AudioNodeExternalInputTrack.h
new file mode 100644
index 0000000000..f273fee696
--- /dev/null
+++ b/dom/media/webaudio/AudioNodeExternalInputTrack.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_AUDIONODEEXTERNALINPUTTRACK_H_
+#define MOZILLA_AUDIONODEEXTERNALINPUTTRACK_H_
+
+#include "MediaTrackGraph.h"
+#include "AudioNodeTrack.h"
+#include "mozilla/Atomics.h"
+
+namespace mozilla {
+
+class AbstractThread;
+
+/**
+ * This is a MediaTrack implementation that acts for a Web Audio node but
+ * unlike other AudioNodeTracks, supports any kind of MediaTrack as an
+ * input --- handling any number of audio tracks and handling blocking of
+ * the input MediaTrack.
+ */
+class AudioNodeExternalInputTrack final : public AudioNodeTrack {
+ public:
+ static already_AddRefed<AudioNodeExternalInputTrack> Create(
+ MediaTrackGraph* aGraph, AudioNodeEngine* aEngine);
+
+ protected:
+ AudioNodeExternalInputTrack(AudioNodeEngine* aEngine, TrackRate aSampleRate);
+ ~AudioNodeExternalInputTrack();
+
+ public:
+ void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) override;
+
+ private:
+ /**
+ * Determines if this is enabled or not. Disabled nodes produce silence.
+ * This node becomes disabled if the document principal does not subsume the
+ * DOMMediaStream principal.
+ */
+ bool IsEnabled();
+};
+
+} // namespace mozilla
+
+#endif /* MOZILLA_AUDIONODEEXTERNALINPUTTRACK_H_ */
diff --git a/dom/media/webaudio/AudioNodeTrack.cpp b/dom/media/webaudio/AudioNodeTrack.cpp
new file mode 100644
index 0000000000..ea411a16ac
--- /dev/null
+++ b/dom/media/webaudio/AudioNodeTrack.cpp
@@ -0,0 +1,594 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AudioNodeTrack.h"
+
+#include "MediaTrackGraph.h"
+#include "MediaTrackListener.h"
+#include "AudioNodeEngine.h"
+#include "ThreeDPoint.h"
+#include "Tracing.h"
+#include "AudioChannelFormat.h"
+#include "AudioParamTimeline.h"
+#include "AudioContext.h"
+#include "nsMathUtils.h"
+#include "AlignmentUtils.h"
+#include "blink/Reverb.h"
+
+using namespace mozilla::dom;
+
+namespace mozilla {
+
+/**
+ * An AudioNodeTrack produces a single audio track with ID
+ * AUDIO_TRACK. This track has rate AudioContext::sIdealAudioRate
+ * for regular audio contexts, and the rate requested by the web content
+ * for offline audio contexts.
+ * Each chunk in the track is a single block of WEBAUDIO_BLOCK_SIZE samples.
+ * Note: This must be a different value than MEDIA_STREAM_DEST_TRACK_ID
+ */
+
+AudioNodeTrack::AudioNodeTrack(AudioNodeEngine* aEngine, Flags aFlags,
+ TrackRate aSampleRate)
+ : ProcessedMediaTrack(
+ aSampleRate, MediaSegment::AUDIO,
+ (aFlags & EXTERNAL_OUTPUT) ? new AudioSegment() : nullptr),
+ mEngine(aEngine),
+ mFlags(aFlags),
+ mNumberOfInputChannels(2),
+ mIsActive(aEngine->IsActive()),
+ mMarkAsEndedAfterThisBlock(false),
+ mAudioParamTrack(false),
+ mPassThrough(false) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mSuspendedCount = !(mIsActive || mFlags & EXTERNAL_OUTPUT);
+ mChannelCountMode = ChannelCountMode::Max;
+ mChannelInterpretation = ChannelInterpretation::Speakers;
+ mLastChunks.SetLength(std::max(uint16_t(1), mEngine->OutputCount()));
+ MOZ_COUNT_CTOR(AudioNodeTrack);
+}
+
+AudioNodeTrack::~AudioNodeTrack() {
+ MOZ_ASSERT(mActiveInputCount == 0);
+ MOZ_COUNT_DTOR(AudioNodeTrack);
+}
+
+void AudioNodeTrack::OnGraphThreadDone() { mEngine->OnGraphThreadDone(); }
+
+void AudioNodeTrack::DestroyImpl() {
+ // These are graph thread objects, so clean up on graph thread.
+ mInputChunks.Clear();
+ mLastChunks.Clear();
+
+ ProcessedMediaTrack::DestroyImpl();
+}
+
+/* static */
+already_AddRefed<AudioNodeTrack> AudioNodeTrack::Create(
+ AudioContext* aCtx, AudioNodeEngine* aEngine, Flags aFlags,
+ MediaTrackGraph* aGraph) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_RELEASE_ASSERT(aGraph);
+
+ // MediaRecorders use an AudioNodeTrack, but no AudioNode
+ AudioNode* node = aEngine->NodeMainThread();
+
+ RefPtr<AudioNodeTrack> track =
+ new AudioNodeTrack(aEngine, aFlags, aGraph->GraphRate());
+ if (node) {
+ track->SetChannelMixingParametersImpl(node->ChannelCount(),
+ node->ChannelCountModeValue(),
+ node->ChannelInterpretationValue());
+ }
+ // All realtime tracks are initially suspended.
+ // ApplyAudioContextOperation() is used to start tracks so that a new track
+ // will not be started before the existing tracks, which may be awaiting an
+ // AudioCallbackDriver to resume.
+ bool isRealtime = !aCtx->IsOffline();
+ track->mSuspendedCount += isRealtime;
+ aGraph->AddTrack(track);
+ if (isRealtime && !aCtx->ShouldSuspendNewTrack()) {
+ nsTArray<RefPtr<mozilla::MediaTrack>> tracks;
+ tracks.AppendElement(track);
+ aGraph->ApplyAudioContextOperation(aCtx->DestinationTrack(),
+ std::move(tracks),
+ AudioContextOperation::Resume);
+ }
+ return track.forget();
+}
+
+size_t AudioNodeTrack::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t amount = 0;
+
+ // Not reported:
+ // - mEngine
+
+ amount += ProcessedMediaTrack::SizeOfExcludingThis(aMallocSizeOf);
+ amount += mLastChunks.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (size_t i = 0; i < mLastChunks.Length(); i++) {
+ // NB: This is currently unshared only as there are instances of
+ // double reporting in DMD otherwise.
+ amount += mLastChunks[i].SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+ }
+
+ return amount;
+}
+
+size_t AudioNodeTrack::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+void AudioNodeTrack::SizeOfAudioNodesIncludingThis(
+ MallocSizeOf aMallocSizeOf, AudioNodeSizes& aUsage) const {
+ // Explicitly separate out the track memory.
+ aUsage.mTrack = SizeOfIncludingThis(aMallocSizeOf);
+
+ if (mEngine) {
+ // This will fill out the rest of |aUsage|.
+ mEngine->SizeOfIncludingThis(aMallocSizeOf, aUsage);
+ }
+}
+
+void AudioNodeTrack::SetTrackTimeParameter(uint32_t aIndex,
+ AudioContext* aContext,
+ double aTrackTime) {
+ QueueControlMessageWithNoShutdown(
+ [self = RefPtr{this}, this, aIndex,
+ relativeToTrack = RefPtr{aContext->DestinationTrack()}, aTrackTime] {
+ TRACE("AudioNodeTrack::SetTrackTimeParameterImpl");
+ SetTrackTimeParameterImpl(aIndex, relativeToTrack, aTrackTime);
+ });
+}
+
+void AudioNodeTrack::SetTrackTimeParameterImpl(uint32_t aIndex,
+ MediaTrack* aRelativeToTrack,
+ double aTrackTime) {
+ TrackTime ticks = aRelativeToTrack->SecondsToNearestTrackTime(aTrackTime);
+ mEngine->SetTrackTimeParameter(aIndex, ticks);
+}
+
+void AudioNodeTrack::SetDoubleParameter(uint32_t aIndex, double aValue) {
+ QueueControlMessageWithNoShutdown(
+ [self = RefPtr{this}, this, aIndex, aValue] {
+ TRACE("AudioNodeTrack::SetDoubleParameter");
+ Engine()->SetDoubleParameter(aIndex, aValue);
+ });
+}
+
+void AudioNodeTrack::SetInt32Parameter(uint32_t aIndex, int32_t aValue) {
+ QueueControlMessageWithNoShutdown(
+ [self = RefPtr{this}, this, aIndex, aValue] {
+ TRACE("AudioNodeTrack::SetInt32Parameter");
+ Engine()->SetInt32Parameter(aIndex, aValue);
+ });
+}
+
+void AudioNodeTrack::SendTimelineEvent(uint32_t aIndex,
+ const AudioParamEvent& aEvent) {
+ QueueControlMessageWithNoShutdown(
+ [self = RefPtr{this}, this, aIndex, event = aEvent]() mutable {
+ TRACE("AudioNodeTrack::RecvTimelineEvent");
+ Engine()->RecvTimelineEvent(aIndex, event);
+ });
+}
+
+void AudioNodeTrack::SetBuffer(AudioChunk&& aBuffer) {
+ QueueControlMessageWithNoShutdown(
+ [self = RefPtr{this}, this, buffer = std::move(aBuffer)]() mutable {
+ TRACE("AudioNodeTrack::SetBuffer");
+ Engine()->SetBuffer(std::move(buffer));
+ });
+}
+
+void AudioNodeTrack::SetReverb(WebCore::Reverb* aReverb,
+ uint32_t aImpulseChannelCount) {
+ QueueControlMessageWithNoShutdown([self = RefPtr{this}, this,
+ reverb = WrapUnique(aReverb),
+ aImpulseChannelCount]() mutable {
+ TRACE("AudioNodeTrack::SetReverb");
+ Engine()->SetReverb(reverb.release(), aImpulseChannelCount);
+ });
+}
+
+void AudioNodeTrack::SetRawArrayData(nsTArray<float>&& aData) {
+ QueueControlMessageWithNoShutdown(
+ [self = RefPtr{this}, this, data = std::move(aData)]() mutable {
+ TRACE("AudioNodeTrack::SetRawArrayData");
+ Engine()->SetRawArrayData(std::move(data));
+ });
+}
+
+void AudioNodeTrack::SetChannelMixingParameters(
+ uint32_t aNumberOfChannels, ChannelCountMode aChannelCountMode,
+ ChannelInterpretation aChannelInterpretation) {
+ QueueControlMessageWithNoShutdown([self = RefPtr{this}, this,
+ aNumberOfChannels, aChannelCountMode,
+ aChannelInterpretation] {
+ TRACE("AudioNodeTrack::SetChannelMixingParameters");
+ SetChannelMixingParametersImpl(aNumberOfChannels, aChannelCountMode,
+ aChannelInterpretation);
+ });
+}
+
+void AudioNodeTrack::SetPassThrough(bool aPassThrough) {
+ QueueControlMessageWithNoShutdown([self = RefPtr{this}, this, aPassThrough] {
+ TRACE("AudioNodeTrack::SetPassThrough");
+ mPassThrough = aPassThrough;
+ });
+}
+
+void AudioNodeTrack::SendRunnable(already_AddRefed<nsIRunnable> aRunnable) {
+ QueueControlMessageWithNoShutdown([runnable = nsCOMPtr{aRunnable}] {
+ TRACE("AudioNodeTrack::SendRunnable");
+ runnable->Run();
+ });
+}
+
+void AudioNodeTrack::SetChannelMixingParametersImpl(
+ uint32_t aNumberOfChannels, ChannelCountMode aChannelCountMode,
+ ChannelInterpretation aChannelInterpretation) {
+ mNumberOfInputChannels = aNumberOfChannels;
+ mChannelCountMode = aChannelCountMode;
+ mChannelInterpretation = aChannelInterpretation;
+}
+
+uint32_t AudioNodeTrack::ComputedNumberOfChannels(uint32_t aInputChannelCount) {
+ switch (mChannelCountMode) {
+ case ChannelCountMode::Explicit:
+ // Disregard the channel count we've calculated from inputs, and just use
+ // mNumberOfInputChannels.
+ return mNumberOfInputChannels;
+ case ChannelCountMode::Clamped_max:
+ // Clamp the computed output channel count to mNumberOfInputChannels.
+ return std::min(aInputChannelCount, mNumberOfInputChannels);
+ default:
+ case ChannelCountMode::Max:
+ // Nothing to do here, just shut up the compiler warning.
+ return aInputChannelCount;
+ }
+}
+
+uint32_t AudioNodeTrack::NumberOfChannels() const {
+ AssertOnGraphThread();
+
+ return mNumberOfInputChannels;
+}
+
+void AudioNodeTrack::AdvanceAndResume(TrackTime aAdvance) {
+ mMainThreadCurrentTime += aAdvance;
+ QueueControlMessageWithNoShutdown([self = RefPtr{this}, this, aAdvance] {
+ TRACE("AudioNodeTrack::AdvanceAndResumeMessage");
+ mStartTime -= aAdvance;
+ mSegment->AppendNullData(aAdvance);
+ DecrementSuspendCount();
+ });
+}
+
+void AudioNodeTrack::ObtainInputBlock(AudioBlock& aTmpChunk,
+ uint32_t aPortIndex) {
+ uint32_t inputCount = mInputs.Length();
+ uint32_t outputChannelCount = 1;
+ AutoTArray<const AudioBlock*, 250> inputChunks;
+ for (uint32_t i = 0; i < inputCount; ++i) {
+ if (aPortIndex != mInputs[i]->InputNumber()) {
+ // This input is connected to a different port
+ continue;
+ }
+ MediaTrack* t = mInputs[i]->GetSource();
+ AudioNodeTrack* a = static_cast<AudioNodeTrack*>(t);
+ MOZ_ASSERT(a == t->AsAudioNodeTrack());
+ if (a->IsAudioParamTrack()) {
+ continue;
+ }
+
+ const AudioBlock* chunk = &a->mLastChunks[mInputs[i]->OutputNumber()];
+ MOZ_ASSERT(chunk);
+ if (chunk->IsNull() || chunk->mChannelData.IsEmpty()) {
+ continue;
+ }
+
+ inputChunks.AppendElement(chunk);
+ outputChannelCount =
+ GetAudioChannelsSuperset(outputChannelCount, chunk->ChannelCount());
+ }
+
+ outputChannelCount = ComputedNumberOfChannels(outputChannelCount);
+
+ uint32_t inputChunkCount = inputChunks.Length();
+ if (inputChunkCount == 0 ||
+ (inputChunkCount == 1 && inputChunks[0]->ChannelCount() == 0)) {
+ aTmpChunk.SetNull(WEBAUDIO_BLOCK_SIZE);
+ return;
+ }
+
+ if (inputChunkCount == 1 &&
+ inputChunks[0]->ChannelCount() == outputChannelCount) {
+ aTmpChunk = *inputChunks[0];
+ return;
+ }
+
+ if (outputChannelCount == 0) {
+ aTmpChunk.SetNull(WEBAUDIO_BLOCK_SIZE);
+ return;
+ }
+
+ aTmpChunk.AllocateChannels(outputChannelCount);
+ DownmixBufferType downmixBuffer;
+ ASSERT_ALIGNED16(downmixBuffer.Elements());
+
+ for (uint32_t i = 0; i < inputChunkCount; ++i) {
+ AccumulateInputChunk(i, *inputChunks[i], &aTmpChunk, &downmixBuffer);
+ }
+}
+
+void AudioNodeTrack::AccumulateInputChunk(uint32_t aInputIndex,
+ const AudioBlock& aChunk,
+ AudioBlock* aBlock,
+ DownmixBufferType* aDownmixBuffer) {
+ AutoTArray<const float*, GUESS_AUDIO_CHANNELS> channels;
+ UpMixDownMixChunk(&aChunk, aBlock->ChannelCount(), channels, *aDownmixBuffer);
+
+ for (uint32_t c = 0; c < channels.Length(); ++c) {
+ const float* inputData = static_cast<const float*>(channels[c]);
+ float* outputData = aBlock->ChannelFloatsForWrite(c);
+ if (inputData) {
+ if (aInputIndex == 0) {
+ AudioBlockCopyChannelWithScale(inputData, aChunk.mVolume, outputData);
+ } else {
+ AudioBlockAddChannelWithScale(inputData, aChunk.mVolume, outputData);
+ }
+ } else {
+ if (aInputIndex == 0) {
+ PodZero(outputData, WEBAUDIO_BLOCK_SIZE);
+ }
+ }
+ }
+}
+
+void AudioNodeTrack::UpMixDownMixChunk(const AudioBlock* aChunk,
+ uint32_t aOutputChannelCount,
+ nsTArray<const float*>& aOutputChannels,
+ DownmixBufferType& aDownmixBuffer) {
+ for (uint32_t i = 0; i < aChunk->ChannelCount(); i++) {
+ aOutputChannels.AppendElement(
+ static_cast<const float*>(aChunk->mChannelData[i]));
+ }
+ if (aOutputChannels.Length() < aOutputChannelCount) {
+ if (mChannelInterpretation == ChannelInterpretation::Speakers) {
+ AudioChannelsUpMix<float>(&aOutputChannels, aOutputChannelCount, nullptr);
+ NS_ASSERTION(aOutputChannelCount == aOutputChannels.Length(),
+ "We called GetAudioChannelsSuperset to avoid this");
+ } else {
+ // Fill up the remaining aOutputChannels by zeros
+ for (uint32_t j = aOutputChannels.Length(); j < aOutputChannelCount;
+ ++j) {
+ aOutputChannels.AppendElement(nullptr);
+ }
+ }
+ } else if (aOutputChannels.Length() > aOutputChannelCount) {
+ if (mChannelInterpretation == ChannelInterpretation::Speakers) {
+ AutoTArray<float*, GUESS_AUDIO_CHANNELS> outputChannels;
+ outputChannels.SetLength(aOutputChannelCount);
+ aDownmixBuffer.SetLength(aOutputChannelCount * WEBAUDIO_BLOCK_SIZE);
+ for (uint32_t j = 0; j < aOutputChannelCount; ++j) {
+ outputChannels[j] = &aDownmixBuffer[j * WEBAUDIO_BLOCK_SIZE];
+ }
+
+ AudioChannelsDownMix<float, float>(aOutputChannels, outputChannels,
+ WEBAUDIO_BLOCK_SIZE);
+
+ aOutputChannels.SetLength(aOutputChannelCount);
+ for (uint32_t j = 0; j < aOutputChannels.Length(); ++j) {
+ aOutputChannels[j] = outputChannels[j];
+ }
+ } else {
+ // Drop the remaining aOutputChannels
+ aOutputChannels.RemoveLastElements(aOutputChannels.Length() -
+ aOutputChannelCount);
+ }
+ }
+}
+
+// The MediaTrackGraph guarantees that this is actually one block, for
+// AudioNodeTracks.
+void AudioNodeTrack::ProcessInput(GraphTime aFrom, GraphTime aTo,
+ uint32_t aFlags) {
+ uint16_t outputCount = mLastChunks.Length();
+ MOZ_ASSERT(outputCount == std::max(uint16_t(1), mEngine->OutputCount()));
+
+ if (!mIsActive) {
+ // mLastChunks are already null.
+#ifdef DEBUG
+ for (const auto& chunk : mLastChunks) {
+ MOZ_ASSERT(chunk.IsNull());
+ }
+#endif
+ } else if (InMutedCycle()) {
+ mInputChunks.Clear();
+ for (uint16_t i = 0; i < outputCount; ++i) {
+ mLastChunks[i].SetNull(WEBAUDIO_BLOCK_SIZE);
+ }
+ } else {
+ // We need to generate at least one input
+ uint16_t maxInputs = std::max(uint16_t(1), mEngine->InputCount());
+ mInputChunks.SetLength(maxInputs);
+ for (uint16_t i = 0; i < maxInputs; ++i) {
+ ObtainInputBlock(mInputChunks[i], i);
+ }
+ bool finished = false;
+ if (mPassThrough) {
+ MOZ_ASSERT(outputCount == 1,
+ "For now, we only support nodes that have one output port");
+ mLastChunks[0] = mInputChunks[0];
+ } else {
+ if (maxInputs <= 1 && outputCount <= 1) {
+ mEngine->ProcessBlock(this, aFrom, mInputChunks[0], &mLastChunks[0],
+ &finished);
+ } else {
+ mEngine->ProcessBlocksOnPorts(
+ this, aFrom, Span(mInputChunks.Elements(), mEngine->InputCount()),
+ Span(mLastChunks.Elements(), mEngine->OutputCount()), &finished);
+ }
+ }
+ for (uint16_t i = 0; i < outputCount; ++i) {
+ NS_ASSERTION(mLastChunks[i].GetDuration() == WEBAUDIO_BLOCK_SIZE,
+ "Invalid WebAudio chunk size");
+ }
+ if (finished) {
+ mMarkAsEndedAfterThisBlock = true;
+ if (mIsActive) {
+ ScheduleCheckForInactive();
+ }
+ }
+
+ if (mDisabledMode != DisabledTrackMode::ENABLED) {
+ for (uint32_t i = 0; i < outputCount; ++i) {
+ mLastChunks[i].SetNull(WEBAUDIO_BLOCK_SIZE);
+ }
+ }
+ }
+
+ if (!mEnded) {
+ // Don't output anything while finished
+ if (mFlags & EXTERNAL_OUTPUT) {
+ AdvanceOutputSegment();
+ }
+ if (mMarkAsEndedAfterThisBlock && (aFlags & ALLOW_END)) {
+ // This track was ended the last time that we looked at it, and all
+ // of the depending tracks have ended their output as well, so now
+ // it's time to mark this track as ended.
+ mEnded = true;
+ }
+ }
+}
+
+void AudioNodeTrack::ProduceOutputBeforeInput(GraphTime aFrom) {
+ MOZ_ASSERT(mEngine->AsDelayNodeEngine());
+ MOZ_ASSERT(mEngine->OutputCount() == 1,
+ "DelayNodeEngine output count should be 1");
+ MOZ_ASSERT(!InMutedCycle(), "DelayNodes should break cycles");
+ MOZ_ASSERT(mLastChunks.Length() == 1);
+
+ if (!mIsActive) {
+ mLastChunks[0].SetNull(WEBAUDIO_BLOCK_SIZE);
+ } else {
+ mEngine->ProduceBlockBeforeInput(this, aFrom, &mLastChunks[0]);
+ NS_ASSERTION(mLastChunks[0].GetDuration() == WEBAUDIO_BLOCK_SIZE,
+ "Invalid WebAudio chunk size");
+ if (mDisabledMode != DisabledTrackMode::ENABLED) {
+ mLastChunks[0].SetNull(WEBAUDIO_BLOCK_SIZE);
+ }
+ }
+}
+
+void AudioNodeTrack::AdvanceOutputSegment() {
+ AudioSegment* segment = GetData<AudioSegment>();
+
+ AudioChunk copyChunk = *mLastChunks[0].AsMutableChunk();
+ AudioSegment tmpSegment;
+ tmpSegment.AppendAndConsumeChunk(std::move(copyChunk));
+
+ for (const auto& l : mTrackListeners) {
+ // Notify MediaTrackListeners.
+ l->NotifyQueuedChanges(Graph(), segment->GetDuration(), tmpSegment);
+ }
+
+ if (mLastChunks[0].IsNull()) {
+ segment->AppendNullData(tmpSegment.GetDuration());
+ } else {
+ segment->AppendFrom(&tmpSegment);
+ }
+}
+
+void AudioNodeTrack::AddInput(MediaInputPort* aPort) {
+ ProcessedMediaTrack::AddInput(aPort);
+ AudioNodeTrack* ns = aPort->GetSource()->AsAudioNodeTrack();
+ // Tracks that are not AudioNodeTracks are considered active.
+ if (!ns || (ns->mIsActive && !ns->IsAudioParamTrack())) {
+ IncrementActiveInputCount();
+ }
+}
+void AudioNodeTrack::RemoveInput(MediaInputPort* aPort) {
+ ProcessedMediaTrack::RemoveInput(aPort);
+ AudioNodeTrack* ns = aPort->GetSource()->AsAudioNodeTrack();
+ // Tracks that are not AudioNodeTracks are considered active.
+ if (!ns || (ns->mIsActive && !ns->IsAudioParamTrack())) {
+ DecrementActiveInputCount();
+ }
+}
+
+void AudioNodeTrack::SetActive() {
+ if (mIsActive || mMarkAsEndedAfterThisBlock) {
+ return;
+ }
+
+ mIsActive = true;
+ if (!(mFlags & EXTERNAL_OUTPUT)) {
+ DecrementSuspendCount();
+ }
+ if (IsAudioParamTrack()) {
+ // Consumers merely influence track order.
+ // They do not read from the track.
+ return;
+ }
+
+ for (const auto& consumer : mConsumers) {
+ AudioNodeTrack* ns = consumer->GetDestination()->AsAudioNodeTrack();
+ if (ns) {
+ ns->IncrementActiveInputCount();
+ }
+ }
+}
+
+void AudioNodeTrack::ScheduleCheckForInactive() {
+ if (mActiveInputCount > 0 && !mMarkAsEndedAfterThisBlock) {
+ return;
+ }
+
+ RunAfterProcessing([self = RefPtr{this}, this] {
+ TRACE("AudioNodeTrack::CheckForInactive");
+ CheckForInactive();
+ });
+}
+
+void AudioNodeTrack::CheckForInactive() {
+ if (((mActiveInputCount > 0 || mEngine->IsActive()) &&
+ !mMarkAsEndedAfterThisBlock) ||
+ !mIsActive) {
+ return;
+ }
+
+ mIsActive = false;
+ mInputChunks.Clear(); // not required for foreseeable future
+ for (auto& chunk : mLastChunks) {
+ chunk.SetNull(WEBAUDIO_BLOCK_SIZE);
+ }
+ if (!(mFlags & EXTERNAL_OUTPUT)) {
+ IncrementSuspendCount();
+ }
+ if (IsAudioParamTrack()) {
+ return;
+ }
+
+ for (const auto& consumer : mConsumers) {
+ AudioNodeTrack* ns = consumer->GetDestination()->AsAudioNodeTrack();
+ if (ns) {
+ ns->DecrementActiveInputCount();
+ }
+ }
+}
+
+void AudioNodeTrack::IncrementActiveInputCount() {
+ ++mActiveInputCount;
+ SetActive();
+}
+
+void AudioNodeTrack::DecrementActiveInputCount() {
+ MOZ_ASSERT(mActiveInputCount > 0);
+ --mActiveInputCount;
+ CheckForInactive();
+}
+
+} // namespace mozilla
diff --git a/dom/media/webaudio/AudioNodeTrack.h b/dom/media/webaudio/AudioNodeTrack.h
new file mode 100644
index 0000000000..505ea96718
--- /dev/null
+++ b/dom/media/webaudio/AudioNodeTrack.h
@@ -0,0 +1,233 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_AUDIONODETRACK_H_
+#define MOZILLA_AUDIONODETRACK_H_
+
+#include "MediaTrackGraph.h"
+#include "mozilla/dom/AudioNodeBinding.h"
+#include "AlignedTArray.h"
+#include "AudioBlock.h"
+#include "AudioSegment.h"
+
+namespace WebCore {
+class Reverb;
+} // namespace WebCore
+
+namespace mozilla {
+
+namespace dom {
+struct ThreeDPoint;
+struct AudioParamEvent;
+class AudioContext;
+} // namespace dom
+
+class AbstractThread;
+class ThreadSharedFloatArrayBufferList;
+class AudioNodeEngine;
+
+typedef AlignedAutoTArray<float, GUESS_AUDIO_CHANNELS * WEBAUDIO_BLOCK_SIZE, 16>
+ DownmixBufferType;
+
+/**
+ * An AudioNodeTrack produces one audio track with ID AUDIO_TRACK.
+ * The start time of the AudioTrack is aligned to the start time of the
+ * AudioContext's destination node track, plus some multiple of BLOCK_SIZE
+ * samples.
+ *
+ * An AudioNodeTrack has an AudioNodeEngine plugged into it that does the
+ * actual audio processing. AudioNodeTrack contains the glue code that
+ * integrates audio processing with the MediaTrackGraph.
+ */
+class AudioNodeTrack : public ProcessedMediaTrack {
+ typedef dom::ChannelCountMode ChannelCountMode;
+ typedef dom::ChannelInterpretation ChannelInterpretation;
+
+ public:
+ typedef mozilla::dom::AudioContext AudioContext;
+
+ enum { AUDIO_TRACK = 1 };
+
+ typedef AutoTArray<AudioBlock, 1> OutputChunks;
+
+ // Flags re main thread updates and track output.
+ typedef unsigned Flags;
+ enum : Flags {
+ NO_TRACK_FLAGS = 0U,
+ NEED_MAIN_THREAD_ENDED = 1U << 0,
+ NEED_MAIN_THREAD_CURRENT_TIME = 1U << 1,
+ // Internal AudioNodeTracks can only pass their output to another
+ // AudioNode, whereas external AudioNodeTracks can pass their output
+ // to other ProcessedMediaTracks or hardware audio output.
+ EXTERNAL_OUTPUT = 1U << 2,
+ };
+ /**
+ * Create a track that will process audio for an AudioNode.
+ * Takes ownership of aEngine.
+ * aGraph is required and equals the graph of aCtx in most cases. An exception
+ * is AudioDestinationNode where the context's graph hasn't been set up yet.
+ */
+ static already_AddRefed<AudioNodeTrack> Create(AudioContext* aCtx,
+ AudioNodeEngine* aEngine,
+ Flags aKind,
+ MediaTrackGraph* aGraph);
+
+ protected:
+ /**
+ * Transfers ownership of aEngine to the new AudioNodeTrack.
+ */
+ AudioNodeTrack(AudioNodeEngine* aEngine, Flags aFlags, TrackRate aSampleRate);
+
+ ~AudioNodeTrack();
+
+ public:
+ // Control API
+ /**
+ * Sets a parameter that's a time relative to some track's played time.
+ * This time is converted to a time relative to this track when it's set.
+ */
+ void SetTrackTimeParameter(uint32_t aIndex, AudioContext* aContext,
+ double aTrackTime);
+ void SetDoubleParameter(uint32_t aIndex, double aValue);
+ void SetInt32Parameter(uint32_t aIndex, int32_t aValue);
+ void SetThreeDPointParameter(uint32_t aIndex, const dom::ThreeDPoint& aValue);
+ void SetBuffer(AudioChunk&& aBuffer);
+ void SetReverb(WebCore::Reverb* aReverb, uint32_t aImpulseChannelCount);
+ // This sends a single event to the timeline on the MTG thread side.
+ void SendTimelineEvent(uint32_t aIndex, const dom::AudioParamEvent& aEvent);
+ // This consumes the contents of aData. aData will be emptied after this
+ // returns.
+ void SetRawArrayData(nsTArray<float>&& aData);
+ void SetChannelMixingParameters(uint32_t aNumberOfChannels,
+ ChannelCountMode aChannelCountMoe,
+ ChannelInterpretation aChannelInterpretation);
+ void SetPassThrough(bool aPassThrough);
+ void SendRunnable(already_AddRefed<nsIRunnable> aRunnable);
+ ChannelInterpretation GetChannelInterpretation() {
+ return mChannelInterpretation;
+ }
+
+ void SetAudioParamHelperTrack() {
+ MOZ_ASSERT(!mAudioParamTrack, "Can only do this once");
+ mAudioParamTrack = true;
+ }
+ // The value for channelCount on an AudioNode, but on the audio thread side.
+ uint32_t NumberOfChannels() const override;
+
+ /*
+ * Resume track after updating its concept of current time by aAdvance.
+ * Main thread. Used only from AudioDestinationNode when resuming a track
+ * suspended to save running the MediaTrackGraph when there are no other
+ * nodes in the AudioContext.
+ */
+ void AdvanceAndResume(TrackTime aAdvance);
+
+ AudioNodeTrack* AsAudioNodeTrack() override { return this; }
+ void AddInput(MediaInputPort* aPort) override;
+ void RemoveInput(MediaInputPort* aPort) override;
+
+ // Graph thread only
+ void SetTrackTimeParameterImpl(uint32_t aIndex, MediaTrack* aRelativeToTrack,
+ double aTrackTime);
+ void SetChannelMixingParametersImpl(
+ uint32_t aNumberOfChannels, ChannelCountMode aChannelCountMoe,
+ ChannelInterpretation aChannelInterpretation);
+ void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) override;
+ /**
+ * Produce the next block of output, before input is provided.
+ * ProcessInput() will be called later, and it then should not change
+ * the output. This is used only for DelayNodeEngine in a feedback loop.
+ */
+ void ProduceOutputBeforeInput(GraphTime aFrom);
+ bool IsAudioParamTrack() const { return mAudioParamTrack; }
+
+ const OutputChunks& LastChunks() const { return mLastChunks; }
+ bool MainThreadNeedsUpdates() const override {
+ return ((mFlags & NEED_MAIN_THREAD_ENDED) && mEnded) ||
+ (mFlags & NEED_MAIN_THREAD_CURRENT_TIME);
+ }
+
+ // Any thread
+ AudioNodeEngine* Engine() { return mEngine.get(); }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+ void SizeOfAudioNodesIncludingThis(MallocSizeOf aMallocSizeOf,
+ AudioNodeSizes& aUsage) const;
+
+ /*
+ * SetActive() is called when either an active input is added or the engine
+ * for a source node transitions from inactive to active. This is not
+ * called from engines for processing nodes because they only become active
+ * when there are active input tracks, in which case this track is already
+ * active.
+ */
+ void SetActive();
+ /*
+ * ScheduleCheckForInactive() is called during track processing when the
+ * engine transitions from active to inactive, or the track finishes. It
+ * schedules a call to CheckForInactive() after track processing.
+ */
+ void ScheduleCheckForInactive();
+
+ protected:
+ void OnGraphThreadDone() override;
+ void DestroyImpl() override;
+
+ /*
+ * CheckForInactive() is called when the engine transitions from active to
+ * inactive, or an active input is removed, or the track finishes. If the
+ * track is now inactive, then mInputChunks will be cleared and mLastChunks
+ * will be set to null. ProcessBlock() will not be called on the engine
+ * again until SetActive() is called.
+ */
+ void CheckForInactive();
+
+ void AdvanceOutputSegment();
+ void FinishOutput();
+ void AccumulateInputChunk(uint32_t aInputIndex, const AudioBlock& aChunk,
+ AudioBlock* aBlock,
+ DownmixBufferType* aDownmixBuffer);
+ void UpMixDownMixChunk(const AudioBlock* aChunk, uint32_t aOutputChannelCount,
+ nsTArray<const float*>& aOutputChannels,
+ DownmixBufferType& aDownmixBuffer);
+
+ uint32_t ComputedNumberOfChannels(uint32_t aInputChannelCount);
+ void ObtainInputBlock(AudioBlock& aTmpChunk, uint32_t aPortIndex);
+ void IncrementActiveInputCount();
+ void DecrementActiveInputCount();
+
+ // The engine that will generate output for this node.
+ const UniquePtr<AudioNodeEngine> mEngine;
+ // The mixed input blocks are kept from iteration to iteration to avoid
+ // reallocating channel data arrays and any buffers for mixing.
+ OutputChunks mInputChunks;
+ // The last block produced by this node.
+ OutputChunks mLastChunks;
+ // Whether this is an internal or external track
+ const Flags mFlags;
+ // The number of input tracks that may provide non-silent input.
+ uint32_t mActiveInputCount = 0;
+ // The number of input channels that this track requires. 0 means don't care.
+ uint32_t mNumberOfInputChannels;
+ // The mixing modes
+ ChannelCountMode mChannelCountMode;
+ ChannelInterpretation mChannelInterpretation;
+ // Tracks are considered active if the track has not finished and either
+ // the engine is active or there are active input tracks.
+ bool mIsActive;
+ // Whether the track should be marked as ended as soon
+ // as the current time range has been computed block by block.
+ bool mMarkAsEndedAfterThisBlock;
+ // Whether the track is an AudioParamHelper track.
+ bool mAudioParamTrack;
+ // Whether the track just passes its input through.
+ bool mPassThrough;
+};
+
+} // namespace mozilla
+
+#endif /* MOZILLA_AUDIONODETRACK_H_ */
diff --git a/dom/media/webaudio/AudioParam.cpp b/dom/media/webaudio/AudioParam.cpp
new file mode 100644
index 0000000000..58d3b225c5
--- /dev/null
+++ b/dom/media/webaudio/AudioParam.cpp
@@ -0,0 +1,170 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AudioParam.h"
+#include "mozilla/dom/AudioParamBinding.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeTrack.h"
+#include "AudioContext.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(AudioParam)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AudioParam)
+ tmp->DisconnectFromGraphAndDestroyTrack();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mNode)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AudioParam)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNode)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTING_NATIVE_ADDREF(AudioParam)
+NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE(AudioParam)
+
+AudioParam::AudioParam(AudioNode* aNode, uint32_t aIndex,
+ const nsAString& aName, float aDefaultValue,
+ float aMinValue, float aMaxValue)
+ : AudioParamTimeline(aDefaultValue),
+ mNode(aNode),
+ mName(aName),
+ mIndex(aIndex),
+ mDefaultValue(aDefaultValue),
+ mMinValue(aMinValue),
+ mMaxValue(aMaxValue) {}
+
+AudioParam::~AudioParam() { DisconnectFromGraphAndDestroyTrack(); }
+
+JSObject* AudioParam::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return AudioParam_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void AudioParam::DisconnectFromGraphAndDestroyTrack() {
+ MOZ_ASSERT(mRefCnt.get() > mInputNodes.Length(),
+ "Caller should be holding a reference or have called "
+ "mRefCnt.stabilizeForDeletion()");
+
+ while (!mInputNodes.IsEmpty()) {
+ RefPtr<AudioNode> input = mInputNodes.PopLastElement().mInputNode;
+ input->RemoveOutputParam(this);
+ }
+
+ if (mNodeTrackPort) {
+ mNodeTrackPort->Destroy();
+ mNodeTrackPort = nullptr;
+ }
+
+ if (mTrack) {
+ mTrack->Destroy();
+ mTrack = nullptr;
+ }
+}
+
+mozilla::MediaTrack* AudioParam::GetTrack() const { return mTrack; }
+
+mozilla::MediaTrack* AudioParam::Track() {
+ if (mTrack) {
+ return mTrack;
+ }
+
+ AudioNodeEngine* engine = new AudioNodeEngine(nullptr);
+ mTrack = AudioNodeTrack::Create(mNode->Context(), engine,
+ AudioNodeTrack::NO_TRACK_FLAGS,
+ mNode->Context()->Graph());
+
+ // Force the input to have only one channel, and make it down-mix using
+ // the speaker rules if needed.
+ mTrack->SetChannelMixingParametersImpl(1, ChannelCountMode::Explicit,
+ ChannelInterpretation::Speakers);
+ // Mark as an AudioParam helper track
+ mTrack->SetAudioParamHelperTrack();
+
+ // Setup the AudioParam's track as an input to the owner AudioNode's track
+ AudioNodeTrack* nodeTrack = mNode->GetTrack();
+ if (nodeTrack) {
+ mNodeTrackPort = nodeTrack->AllocateInputPort(mTrack);
+ }
+
+ // Send the track to the timeline on the MTG side.
+ AudioParamEvent event(mTrack);
+ SendEventToEngine(event);
+
+ return mTrack;
+}
+
+static const char* ToString(AudioTimelineEvent::Type aType) {
+ switch (aType) {
+ case AudioTimelineEvent::SetValue:
+ return "SetValue";
+ case AudioTimelineEvent::SetValueAtTime:
+ return "SetValueAtTime";
+ case AudioTimelineEvent::LinearRamp:
+ return "LinearRamp";
+ case AudioTimelineEvent::ExponentialRamp:
+ return "ExponentialRamp";
+ case AudioTimelineEvent::SetTarget:
+ return "SetTarget";
+ case AudioTimelineEvent::SetValueCurve:
+ return "SetValueCurve";
+ case AudioTimelineEvent::Track:
+ return "Track";
+ case AudioTimelineEvent::Cancel:
+ return "Cancel";
+ default:
+ return "unknown AudioTimelineEvent";
+ }
+}
+
+void AudioParam::SendEventToEngine(const AudioParamEvent& aEvent) {
+ WEB_AUDIO_API_LOG(
+ "%f: %s for %u %s %s=%g time=%f %s=%g", GetParentObject()->CurrentTime(),
+ NS_ConvertUTF16toUTF8(mName).get(), ParentNodeId(),
+ ToString(aEvent.mType),
+ aEvent.mType == AudioTimelineEvent::SetValueCurve ? "length" : "value",
+ aEvent.mType == AudioTimelineEvent::SetValueCurve
+ ? static_cast<double>(aEvent.CurveLength())
+ : static_cast<double>(aEvent.NominalValue()),
+ aEvent.Time<double>(),
+ aEvent.mType == AudioTimelineEvent::SetValueCurve ? "duration"
+ : "constant",
+ aEvent.mType == AudioTimelineEvent::SetValueCurve
+ ? aEvent.Duration()
+ : aEvent.TimeConstant());
+
+ AudioNodeTrack* track = mNode->GetTrack();
+ if (track) {
+ track->SendTimelineEvent(mIndex, aEvent);
+ }
+}
+
+void AudioParam::CleanupOldEvents() {
+ MOZ_ASSERT(NS_IsMainThread());
+ double currentTime = mNode->Context()->CurrentTime();
+
+ CleanupEventsOlderThan(currentTime);
+}
+
+float AudioParamTimeline::AudioNodeInputValue(size_t aCounter) const {
+ MOZ_ASSERT(mTrack);
+
+ // If we have a chunk produced by the AudioNode inputs to the AudioParam,
+ // get its value now. We use aCounter to tell us which frame of the last
+ // AudioChunk to look at.
+ float audioNodeInputValue = 0.0f;
+ const AudioBlock& lastAudioNodeChunk = mTrack->LastChunks()[0];
+ if (!lastAudioNodeChunk.IsNull()) {
+ MOZ_ASSERT(lastAudioNodeChunk.GetDuration() == WEBAUDIO_BLOCK_SIZE);
+ audioNodeInputValue =
+ static_cast<const float*>(lastAudioNodeChunk.mChannelData[0])[aCounter];
+ audioNodeInputValue *= lastAudioNodeChunk.mVolume;
+ }
+
+ return audioNodeInputValue;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/AudioParam.h b/dom/media/webaudio/AudioParam.h
new file mode 100644
index 0000000000..2365857a5b
--- /dev/null
+++ b/dom/media/webaudio/AudioParam.h
@@ -0,0 +1,239 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef AudioParam_h_
+#define AudioParam_h_
+
+#include "AudioParamTimeline.h"
+#include "nsWrapperCache.h"
+#include "nsCycleCollectionParticipant.h"
+#include "AudioNode.h"
+#include "mozilla/dom/TypedArray.h"
+#include "WebAudioUtils.h"
+#include "js/TypeDecls.h"
+
+namespace mozilla::dom {
+
+class AudioParam final : public nsWrapperCache, public AudioParamTimeline {
+ virtual ~AudioParam();
+
+ public:
+ AudioParam(AudioNode* aNode, uint32_t aIndex, const nsAString& aName,
+ float aDefaultValue,
+ float aMinValue = std::numeric_limits<float>::lowest(),
+ float aMaxValue = std::numeric_limits<float>::max());
+
+ NS_IMETHOD_(MozExternalRefCountType) AddRef(void);
+ NS_IMETHOD_(MozExternalRefCountType) Release(void);
+ NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(AudioParam)
+
+ AudioContext* GetParentObject() const { return mNode->Context(); }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ float Value() {
+ return AudioParamTimeline::GetValueAtTime<double>(
+ GetParentObject()->CurrentTime());
+ }
+
+ // We override SetValueCurveAtTime to convert the Float32Array to the wrapper
+ // object.
+ AudioParam* SetValueCurveAtTime(const nsTArray<float>& aValues,
+ double aStartTime, double aDuration,
+ ErrorResult& aRv) {
+ if (!WebAudioUtils::IsTimeValid(aStartTime)) {
+ aRv.ThrowRangeError<MSG_INVALID_AUDIOPARAM_METHOD_START_TIME_ERROR>();
+ return this;
+ }
+ aStartTime = std::max(aStartTime, GetParentObject()->CurrentTime());
+ AudioParamEvent event(AudioTimelineEvent::SetValueCurve, aValues,
+ aStartTime, aDuration);
+ ValidateAndInsertEvent(event, aRv);
+ return this;
+ }
+
+ // Intended for use in AudioNode creation, when the setter should not throw.
+ void SetInitialValue(float aValue) {
+ MOZ_ASSERT(HasSimpleValue(), "Existing events unexpected");
+ AudioParamEvent event(AudioTimelineEvent::SetValue, 0.0f, aValue);
+
+ DebugOnly<ErrorResult> rv;
+ MOZ_ASSERT(ValidateEvent(event, rv), "This event should be valid");
+
+ AudioParamTimeline::SetValue(aValue);
+
+ SendEventToEngine(event);
+ }
+
+ void SetValue(float aValue, ErrorResult& aRv) {
+ AudioParamEvent event(AudioTimelineEvent::SetValue, 0.0f, aValue);
+
+ if (!ValidateEvent(event, aRv)) {
+ return;
+ }
+
+ AudioParamTimeline::SetValue(aValue);
+
+ SendEventToEngine(event);
+ }
+
+ AudioParam* SetValueAtTime(float aValue, double aStartTime,
+ ErrorResult& aRv) {
+ if (!WebAudioUtils::IsTimeValid(aStartTime)) {
+ aRv.ThrowRangeError<MSG_INVALID_AUDIOPARAM_METHOD_START_TIME_ERROR>();
+ return this;
+ }
+ aStartTime = std::max(aStartTime, GetParentObject()->CurrentTime());
+ AudioParamEvent event(AudioTimelineEvent::SetValueAtTime, aStartTime,
+ aValue);
+ ValidateAndInsertEvent(event, aRv);
+ return this;
+ }
+
+ AudioParam* LinearRampToValueAtTime(float aValue, double aEndTime,
+ ErrorResult& aRv) {
+ if (!WebAudioUtils::IsTimeValid(aEndTime)) {
+ aRv.ThrowRangeError<MSG_INVALID_AUDIOPARAM_METHOD_END_TIME_ERROR>();
+ return this;
+ }
+ aEndTime = std::max(aEndTime, GetParentObject()->CurrentTime());
+ AudioParamEvent event(AudioTimelineEvent::LinearRamp, aEndTime, aValue);
+ ValidateAndInsertEvent(event, aRv);
+ return this;
+ }
+
+ AudioParam* ExponentialRampToValueAtTime(float aValue, double aEndTime,
+ ErrorResult& aRv) {
+ if (!WebAudioUtils::IsTimeValid(aEndTime)) {
+ aRv.ThrowRangeError<MSG_INVALID_AUDIOPARAM_METHOD_END_TIME_ERROR>();
+ return this;
+ }
+ aEndTime = std::max(aEndTime, GetParentObject()->CurrentTime());
+ AudioParamEvent event(AudioTimelineEvent::ExponentialRamp, aEndTime,
+ aValue);
+ ValidateAndInsertEvent(event, aRv);
+ return this;
+ }
+
+ AudioParam* SetTargetAtTime(float aTarget, double aStartTime,
+ double aTimeConstant, ErrorResult& aRv) {
+ if (!WebAudioUtils::IsTimeValid(aStartTime) ||
+ !WebAudioUtils::IsTimeValid(aTimeConstant)) {
+ aRv.ThrowRangeError<MSG_INVALID_AUDIOPARAM_METHOD_START_TIME_ERROR>();
+ return this;
+ }
+ aStartTime = std::max(aStartTime, GetParentObject()->CurrentTime());
+ AudioParamEvent event(AudioTimelineEvent::SetTarget, aStartTime, aTarget,
+ aTimeConstant);
+ ValidateAndInsertEvent(event, aRv);
+ return this;
+ }
+
+ AudioParam* CancelScheduledValues(double aStartTime, ErrorResult& aRv) {
+ if (!WebAudioUtils::IsTimeValid(aStartTime)) {
+ aRv.ThrowRangeError<MSG_INVALID_AUDIOPARAM_METHOD_START_TIME_ERROR>();
+ return this;
+ }
+
+ aStartTime = std::max(aStartTime, GetParentObject()->CurrentTime());
+
+ // Remove some events on the main thread copy.
+ AudioEventTimeline::CancelScheduledValues(aStartTime);
+
+ AudioParamEvent event(AudioTimelineEvent::Cancel, aStartTime, 0.0f);
+
+ SendEventToEngine(event);
+
+ return this;
+ }
+
+ uint32_t ParentNodeId() { return mNode->Id(); }
+
+ void GetName(nsAString& aName) { aName.Assign(mName); }
+
+ float DefaultValue() const { return mDefaultValue; }
+
+ float MinValue() const { return mMinValue; }
+
+ float MaxValue() const { return mMaxValue; }
+
+ bool IsTrackSuspended() const {
+ return mTrack ? mTrack->IsSuspended() : false;
+ }
+
+ const nsTArray<AudioNode::InputNode>& InputNodes() const {
+ return mInputNodes;
+ }
+
+ void RemoveInputNode(uint32_t aIndex) { mInputNodes.RemoveElementAt(aIndex); }
+
+ AudioNode::InputNode* AppendInputNode() {
+ return mInputNodes.AppendElement();
+ }
+
+ // May create the track if it doesn't exist
+ mozilla::MediaTrack* Track();
+
+ // Return nullptr if track doesn't exist.
+ mozilla::MediaTrack* GetTrack() const;
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override {
+ size_t amount = AudioParamTimeline::SizeOfExcludingThis(aMallocSizeOf);
+ // Not owned:
+ // - mNode
+
+ // Just count the array, actual nodes are counted in mNode.
+ amount += mInputNodes.ShallowSizeOfExcludingThis(aMallocSizeOf);
+
+ if (mNodeTrackPort) {
+ amount += mNodeTrackPort->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ return amount;
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ private:
+ void ValidateAndInsertEvent(const AudioParamEvent& aEvent, ErrorResult& aRv) {
+ if (!ValidateEvent(aEvent, aRv)) {
+ return;
+ }
+
+ AudioEventTimeline::InsertEvent<double>(aEvent);
+
+ SendEventToEngine(aEvent);
+
+ CleanupOldEvents();
+ }
+
+ void CleanupOldEvents();
+
+ void SendEventToEngine(const AudioParamEvent& aEvent);
+
+ void DisconnectFromGraphAndDestroyTrack();
+
+ nsCycleCollectingAutoRefCnt mRefCnt;
+ NS_DECL_OWNINGTHREAD
+ RefPtr<AudioNode> mNode;
+ // For every InputNode, there is a corresponding entry in mOutputParams of the
+ // InputNode's mInputNode.
+ nsTArray<AudioNode::InputNode> mInputNodes;
+ const nsString mName;
+ // The input port used to connect the AudioParam's track to its node's track
+ RefPtr<MediaInputPort> mNodeTrackPort;
+ const uint32_t mIndex;
+ const float mDefaultValue;
+ const float mMinValue;
+ const float mMaxValue;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/media/webaudio/AudioParamDescriptorMap.h b/dom/media/webaudio/AudioParamDescriptorMap.h
new file mode 100644
index 0000000000..978e8bc90c
--- /dev/null
+++ b/dom/media/webaudio/AudioParamDescriptorMap.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_AudioParamDescriptor_h
+#define mozilla_dom_AudioParamDescriptor_h
+
+#include "mozilla/dom/AudioParamDescriptorBinding.h"
+#include "nsTArray.h"
+
+namespace mozilla::dom {
+
+// Note: we call this "map" to match the spec, but we store audio param
+// descriptors in an array, because we need ordered access, and don't need the
+// map functionalities.
+typedef nsTArray<AudioParamDescriptor> AudioParamDescriptorMap;
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_AudioParamDescriptor_h
diff --git a/dom/media/webaudio/AudioParamMap.cpp b/dom/media/webaudio/AudioParamMap.cpp
new file mode 100644
index 0000000000..2d4f936361
--- /dev/null
+++ b/dom/media/webaudio/AudioParamMap.cpp
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "AudioParamMap.h"
+#include "mozilla/dom/AudioParamMapBinding.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(AudioParamMap, mParent)
+
+AudioParamMap::AudioParamMap(AudioWorkletNode* aParent) : mParent(aParent) {}
+
+JSObject* AudioParamMap::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return AudioParamMap_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/AudioParamMap.h b/dom/media/webaudio/AudioParamMap.h
new file mode 100644
index 0000000000..c7bb0bdbea
--- /dev/null
+++ b/dom/media/webaudio/AudioParamMap.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef AudioParamMap_h_
+#define AudioParamMap_h_
+
+#include "nsWrapperCache.h"
+#include "AudioWorkletNode.h"
+
+namespace mozilla::dom {
+
+class AudioParamMap final : public nsWrapperCache {
+ public:
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(AudioParamMap)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(AudioParamMap)
+
+ explicit AudioParamMap(AudioWorkletNode* aParent);
+
+ AudioWorkletNode* GetParentObject() const { return mParent; }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ private:
+ ~AudioParamMap() = default;
+ RefPtr<AudioWorkletNode> mParent;
+};
+
+} // namespace mozilla::dom
+
+#endif // AudioParamMap_h_
diff --git a/dom/media/webaudio/AudioParamTimeline.h b/dom/media/webaudio/AudioParamTimeline.h
new file mode 100644
index 0000000000..bd0ba99ffe
--- /dev/null
+++ b/dom/media/webaudio/AudioParamTimeline.h
@@ -0,0 +1,144 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef AudioParamTimeline_h_
+#define AudioParamTimeline_h_
+
+#include "AudioEventTimeline.h"
+#include "AudioNodeTrack.h"
+#include "AudioSegment.h"
+
+namespace mozilla::dom {
+
+struct AudioParamEvent final : public AudioTimelineEvent {
+ AudioParamEvent(Type aType, double aTime, float aValue,
+ double aTimeConstant = 0.0)
+ : AudioTimelineEvent(aType, aTime, aValue, aTimeConstant) {}
+ AudioParamEvent(Type aType, const nsTArray<float>& aValues, double aStartTime,
+ double aDuration)
+ : AudioTimelineEvent(aType, aValues, aStartTime, aDuration) {}
+ explicit AudioParamEvent(AudioNodeTrack* aTrack)
+ : AudioTimelineEvent(Track, 0.0, 0.f), mTrack(aTrack) {}
+
+ RefPtr<AudioNodeTrack> mTrack;
+};
+
+// This helper class is used to represent the part of the AudioParam
+// class that gets sent to AudioNodeEngine instances. In addition to
+// AudioEventTimeline methods, it holds a pointer to an optional
+// AudioNodeTrack which represents the AudioNode inputs to the AudioParam.
+// This AudioNodeTrack is managed by the AudioParam subclass on the main
+// thread, and can only be obtained from the AudioNodeEngine instances
+// consuming this class.
+class AudioParamTimeline : public AudioEventTimeline {
+ typedef AudioEventTimeline BaseClass;
+
+ public:
+ explicit AudioParamTimeline(float aDefaultValue) : BaseClass(aDefaultValue) {}
+
+ AudioNodeTrack* Track() const { return mTrack; }
+
+ bool HasSimpleValue() const {
+ return BaseClass::HasSimpleValue() &&
+ (!mTrack || mTrack->LastChunks()[0].IsNull());
+ }
+
+ template <class TimeType>
+ float GetValueAtTime(TimeType aTime);
+
+ // Prefer this method over GetValueAtTime() only if HasSimpleValue() is
+ // known false.
+ float GetComplexValueAtTime(int64_t aTime);
+ float GetComplexValueAtTime(double aTime) = delete;
+
+ template <typename TimeType>
+ void InsertEvent(const AudioParamEvent& aEvent) {
+ if (aEvent.mType == AudioTimelineEvent::Cancel) {
+ CancelScheduledValues(aEvent.Time<TimeType>());
+ return;
+ }
+ if (aEvent.mType == AudioTimelineEvent::Track) {
+ mTrack = aEvent.mTrack;
+ return;
+ }
+ if (aEvent.mType == AudioTimelineEvent::SetValue) {
+ AudioEventTimeline::SetValue(aEvent.NominalValue());
+ return;
+ }
+ AudioEventTimeline::InsertEvent<TimeType>(aEvent);
+ }
+
+ // Get the values of the AudioParam at time aTime + (0 to aSize).
+ // aBuffer must have the correct aSize.
+ // aSize here is an offset to aTime if we try to get the value in ticks,
+ // otherwise it should always be zero. aSize is meant to be used when
+ // getting the value of an a-rate AudioParam for each tick inside an
+ // AudioNodeEngine implementation.
+ void GetValuesAtTime(int64_t aTime, float* aBuffer, const size_t aSize);
+ void GetValuesAtTime(double aTime, float* aBuffer,
+ const size_t aSize) = delete;
+
+ virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ return mTrack ? mTrack->SizeOfIncludingThis(aMallocSizeOf) : 0;
+ }
+
+ virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ private:
+ float AudioNodeInputValue(size_t aCounter) const;
+
+ protected:
+ // This is created lazily when needed.
+ RefPtr<AudioNodeTrack> mTrack;
+};
+
+template <>
+inline float AudioParamTimeline::GetValueAtTime(double aTime) {
+ // Getting an AudioParam value on an AudioNode does not consider input from
+ // other AudioNodes, which is managed only on the graph thread.
+ return BaseClass::GetValueAtTime(aTime);
+}
+
+template <>
+inline float AudioParamTimeline::GetValueAtTime(int64_t aTime) {
+ // Use GetValuesAtTime() for a-rate parameters.
+ MOZ_ASSERT(aTime % WEBAUDIO_BLOCK_SIZE == 0);
+ if (HasSimpleValue()) {
+ return GetValue();
+ }
+ return GetComplexValueAtTime(aTime);
+}
+
+inline float AudioParamTimeline::GetComplexValueAtTime(int64_t aTime) {
+ MOZ_ASSERT(aTime % WEBAUDIO_BLOCK_SIZE == 0);
+
+ // Mix the value of the AudioParam itself with that of the AudioNode inputs.
+ return BaseClass::GetValueAtTime(aTime) +
+ (mTrack ? AudioNodeInputValue(0) : 0.0f);
+}
+
+inline void AudioParamTimeline::GetValuesAtTime(int64_t aTime, float* aBuffer,
+ const size_t aSize) {
+ MOZ_ASSERT(aBuffer);
+ MOZ_ASSERT(aSize <= WEBAUDIO_BLOCK_SIZE);
+ MOZ_ASSERT(aSize == 1 || !HasSimpleValue());
+
+ // Mix the value of the AudioParam itself with that of the AudioNode inputs.
+ BaseClass::GetValuesAtTime(aTime, aBuffer, aSize);
+ if (mTrack) {
+ uint32_t blockOffset = aTime % WEBAUDIO_BLOCK_SIZE;
+ MOZ_ASSERT(blockOffset + aSize <= WEBAUDIO_BLOCK_SIZE);
+ for (size_t i = 0; i < aSize; ++i) {
+ aBuffer[i] += AudioNodeInputValue(blockOffset + i);
+ }
+ }
+}
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/media/webaudio/AudioProcessingEvent.cpp b/dom/media/webaudio/AudioProcessingEvent.cpp
new file mode 100644
index 0000000000..7d9987db07
--- /dev/null
+++ b/dom/media/webaudio/AudioProcessingEvent.cpp
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AudioProcessingEvent.h"
+#include "mozilla/dom/AudioProcessingEventBinding.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "AudioContext.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(AudioProcessingEvent, Event, mInputBuffer,
+ mOutputBuffer, mNode)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AudioProcessingEvent)
+NS_INTERFACE_MAP_END_INHERITING(Event)
+
+NS_IMPL_ADDREF_INHERITED(AudioProcessingEvent, Event)
+NS_IMPL_RELEASE_INHERITED(AudioProcessingEvent, Event)
+
+AudioProcessingEvent::AudioProcessingEvent(ScriptProcessorNode* aOwner,
+ nsPresContext* aPresContext,
+ WidgetEvent* aEvent)
+ : Event(aOwner, aPresContext, aEvent), mPlaybackTime(0.0), mNode(aOwner) {}
+
+AudioProcessingEvent::~AudioProcessingEvent() = default;
+
+JSObject* AudioProcessingEvent::WrapObjectInternal(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return AudioProcessingEvent_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<AudioBuffer> AudioProcessingEvent::LazilyCreateBuffer(
+ uint32_t aNumberOfChannels, ErrorResult& aRv) {
+ RefPtr<AudioBuffer> buffer = AudioBuffer::Create(
+ mNode->Context()->GetOwner(), aNumberOfChannels, mNode->BufferSize(),
+ mNode->Context()->SampleRate(), aRv);
+ MOZ_ASSERT(buffer || aRv.ErrorCodeIs(NS_ERROR_OUT_OF_MEMORY));
+ return buffer.forget();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/AudioProcessingEvent.h b/dom/media/webaudio/AudioProcessingEvent.h
new file mode 100644
index 0000000000..3b8b9cf008
--- /dev/null
+++ b/dom/media/webaudio/AudioProcessingEvent.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef AudioProcessingEvent_h_
+#define AudioProcessingEvent_h_
+
+#include "AudioBuffer.h"
+#include "ScriptProcessorNode.h"
+#include "mozilla/dom/Event.h"
+
+namespace mozilla::dom {
+
+class AudioProcessingEvent final : public Event {
+ public:
+ AudioProcessingEvent(ScriptProcessorNode* aOwner, nsPresContext* aPresContext,
+ WidgetEvent* aEvent);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AudioProcessingEvent, Event)
+
+ JSObject* WrapObjectInternal(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ using Event::InitEvent;
+ void InitEvent(AudioBuffer* aInputBuffer, uint32_t aNumberOfInputChannels,
+ double aPlaybackTime) {
+ InitEvent(u"audioprocess"_ns, CanBubble::eNo, Cancelable::eNo);
+ mInputBuffer = aInputBuffer;
+ mNumberOfInputChannels = aNumberOfInputChannels;
+ mPlaybackTime = aPlaybackTime;
+ }
+
+ double PlaybackTime() const { return mPlaybackTime; }
+
+ AudioBuffer* GetInputBuffer(ErrorResult& aRv) {
+ if (!mInputBuffer) {
+ mInputBuffer = LazilyCreateBuffer(mNumberOfInputChannels, aRv);
+ }
+ return mInputBuffer;
+ }
+
+ AudioBuffer* GetOutputBuffer(ErrorResult& aRv) {
+ if (!mOutputBuffer) {
+ mOutputBuffer = LazilyCreateBuffer(mNode->NumberOfOutputChannels(), aRv);
+ }
+ return mOutputBuffer;
+ }
+
+ bool HasOutputBuffer() const { return !!mOutputBuffer; }
+
+ protected:
+ virtual ~AudioProcessingEvent();
+
+ private:
+ already_AddRefed<AudioBuffer> LazilyCreateBuffer(uint32_t aNumberOfChannels,
+ ErrorResult& rv);
+
+ private:
+ double mPlaybackTime;
+ RefPtr<AudioBuffer> mInputBuffer;
+ RefPtr<AudioBuffer> mOutputBuffer;
+ RefPtr<ScriptProcessorNode> mNode;
+ uint32_t mNumberOfInputChannels;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/media/webaudio/AudioScheduledSourceNode.cpp b/dom/media/webaudio/AudioScheduledSourceNode.cpp
new file mode 100644
index 0000000000..c6609def51
--- /dev/null
+++ b/dom/media/webaudio/AudioScheduledSourceNode.cpp
@@ -0,0 +1,17 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AudioScheduledSourceNode.h"
+
+namespace mozilla::dom {
+
+AudioScheduledSourceNode::AudioScheduledSourceNode(
+ AudioContext* aContext, uint32_t aChannelCount,
+ ChannelCountMode aChannelCountMode,
+ ChannelInterpretation aChannelInterpretation)
+ : AudioNode(aContext, aChannelCount, aChannelCountMode,
+ aChannelInterpretation) {}
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/AudioScheduledSourceNode.h b/dom/media/webaudio/AudioScheduledSourceNode.h
new file mode 100644
index 0000000000..0102d4a6e8
--- /dev/null
+++ b/dom/media/webaudio/AudioScheduledSourceNode.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef AudioScheduledSourceNode_h_
+#define AudioScheduledSourceNode_h_
+
+#include "AudioNode.h"
+#include "mozilla/dom/AudioScheduledSourceNodeBinding.h"
+
+namespace mozilla::dom {
+
+class AudioContext;
+
+class AudioScheduledSourceNode : public AudioNode {
+ public:
+ virtual void Start(double aWhen, ErrorResult& aRv) = 0;
+ virtual void Stop(double aWhen, ErrorResult& aRv) = 0;
+
+ IMPL_EVENT_HANDLER(ended)
+
+ protected:
+ AudioScheduledSourceNode(AudioContext* aContext, uint32_t aChannelCount,
+ ChannelCountMode aChannelCountMode,
+ ChannelInterpretation aChannelInterpretation);
+ virtual ~AudioScheduledSourceNode() = default;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/media/webaudio/AudioWorkletGlobalScope.cpp b/dom/media/webaudio/AudioWorkletGlobalScope.cpp
new file mode 100644
index 0000000000..18b0b4fa75
--- /dev/null
+++ b/dom/media/webaudio/AudioWorkletGlobalScope.cpp
@@ -0,0 +1,358 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AudioWorkletGlobalScope.h"
+
+#include "AudioNodeEngine.h"
+#include "AudioNodeTrack.h"
+#include "AudioWorkletImpl.h"
+#include "jsapi.h"
+#include "js/ForOfIterator.h"
+#include "js/PropertyAndElement.h" // JS_GetProperty
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/dom/AudioWorkletGlobalScopeBinding.h"
+#include "mozilla/dom/AudioWorkletProcessor.h"
+#include "mozilla/dom/BindingCallContext.h"
+#include "mozilla/dom/MessagePort.h"
+#include "mozilla/dom/StructuredCloneHolder.h"
+#include "mozilla/dom/AudioParamDescriptorBinding.h"
+#include "nsPrintfCString.h"
+#include "nsTHashSet.h"
+#include "Tracing.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(AudioWorkletGlobalScope, WorkletGlobalScope,
+ mNameToProcessorMap);
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AudioWorkletGlobalScope)
+NS_INTERFACE_MAP_END_INHERITING(WorkletGlobalScope)
+
+NS_IMPL_ADDREF_INHERITED(AudioWorkletGlobalScope, WorkletGlobalScope)
+NS_IMPL_RELEASE_INHERITED(AudioWorkletGlobalScope, WorkletGlobalScope)
+
+AudioWorkletGlobalScope::AudioWorkletGlobalScope(AudioWorkletImpl* aImpl)
+ : WorkletGlobalScope(aImpl) {}
+
+AudioWorkletImpl* AudioWorkletGlobalScope::Impl() const {
+ return static_cast<AudioWorkletImpl*>(mImpl.get());
+}
+
+bool AudioWorkletGlobalScope::WrapGlobalObject(
+ JSContext* aCx, JS::MutableHandle<JSObject*> aReflector) {
+ // |this| is being exposed to JS and content script will soon be running.
+ // The graph needs a handle on the JSContext so it can interrupt JS.
+ Impl()->DestinationTrack()->Graph()->NotifyJSContext(aCx);
+
+ JS::RealmOptions options = CreateRealmOptions();
+ return AudioWorkletGlobalScope_Binding::Wrap(
+ aCx, this, this, options, BasePrincipal::Cast(mImpl->Principal()),
+ aReflector);
+}
+
+void AudioWorkletGlobalScope::RegisterProcessor(
+ JSContext* aCx, const nsAString& aName,
+ AudioWorkletProcessorConstructor& aProcessorCtor, ErrorResult& aRv) {
+ TRACE_COMMENT("AudioWorkletGlobalScope::RegisterProcessor", "%s",
+ NS_ConvertUTF16toUTF8(aName).get());
+
+ JS::Rooted<JSObject*> processorConstructor(aCx,
+ aProcessorCtor.CallableOrNull());
+
+ /**
+ * 1. If the name is the empty string, throw a NotSupportedError
+ * exception and abort these steps because the empty string is not
+ * a valid key.
+ */
+ if (aName.IsEmpty()) {
+ aRv.ThrowNotSupportedError("Argument 1 should not be an empty string.");
+ return;
+ }
+
+ /**
+ * 2. If the name exists as a key in the node name to processor
+ * definition map, throw a NotSupportedError exception and abort
+ * these steps because registering a definition with a duplicated
+ * key is not allowed.
+ */
+ if (mNameToProcessorMap.GetWeak(aName)) {
+ // Duplicate names are not allowed
+ aRv.ThrowNotSupportedError(
+ "Argument 1 is invalid: a class with the same name is already "
+ "registered.");
+ return;
+ }
+
+ // We know processorConstructor is callable, so not a WindowProxy or Location.
+ JS::Rooted<JSObject*> constructorUnwrapped(
+ aCx, js::CheckedUnwrapStatic(processorConstructor));
+ if (!constructorUnwrapped) {
+ // If the caller's compartment does not have permission to access the
+ // unwrapped constructor then throw.
+ aRv.ThrowSecurityError("Constructor cannot be called");
+ return;
+ }
+
+ /**
+ * 3. If the result of IsConstructor(argument=processorCtor) is false,
+ * throw a TypeError and abort these steps.
+ */
+ if (!JS::IsConstructor(constructorUnwrapped)) {
+ aRv.ThrowTypeError<MSG_NOT_CONSTRUCTOR>("Argument 2");
+ return;
+ }
+
+ /**
+ * 4. Let prototype be the result of Get(O=processorCtor, P="prototype").
+ */
+ // The .prototype on the constructor passed could be an "expando" of a
+ // wrapper. So we should get it from wrapper instead of the underlying
+ // object.
+ JS::Rooted<JS::Value> prototype(aCx);
+ if (!JS_GetProperty(aCx, processorConstructor, "prototype", &prototype)) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+
+ /**
+ * 5. If the result of Type(argument=prototype) is not Object, throw a
+ * TypeError and abort all these steps.
+ */
+ if (!prototype.isObject()) {
+ aRv.ThrowTypeError<MSG_NOT_OBJECT>("processorCtor.prototype");
+ return;
+ }
+ /**
+ * 6. Let parameterDescriptorsValue be the result of Get(O=processorCtor,
+ * P="parameterDescriptors").
+ */
+ JS::Rooted<JS::Value> descriptors(aCx);
+ if (!JS_GetProperty(aCx, processorConstructor, "parameterDescriptors",
+ &descriptors)) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+
+ AudioParamDescriptorMap map;
+ /*
+ * 7. If parameterDescriptorsValue is not undefined
+ */
+ if (!descriptors.isUndefined()) {
+ /*
+ * 7.1. Let parameterDescriptorSequence be the result of the conversion
+ * from parameterDescriptorsValue to an IDL value of type
+ * sequence<AudioParamDescriptor>.
+ */
+ JS::Rooted<JS::Value> objectValue(aCx, descriptors);
+ JS::ForOfIterator iter(aCx);
+ if (!iter.init(objectValue, JS::ForOfIterator::AllowNonIterable)) {
+ aRv.NoteJSContextException(aCx);
+ return;
+ }
+ if (!iter.valueIsIterable()) {
+ aRv.ThrowTypeError<MSG_CONVERSION_ERROR>(
+ "AudioWorkletProcessor.parameterDescriptors", "sequence");
+ return;
+ }
+ /*
+ * 7.2 and 7.3 (and substeps)
+ */
+ map = DescriptorsFromJS(aCx, &iter, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+ }
+
+ /**
+ * 8. Append the key-value pair name → processorCtor to node name to processor
+ * constructor map of the associated AudioWorkletGlobalScope.
+ */
+ if (!mNameToProcessorMap.InsertOrUpdate(aName, RefPtr{&aProcessorCtor},
+ fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ /**
+ * 9. Queue a task to the control thread to add the key-value pair
+ * (name - descriptors) to the node name to parameter descriptor
+ * map of the associated BaseAudioContext.
+ */
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "AudioWorkletGlobalScope: parameter descriptors",
+ [impl = RefPtr{Impl()}, name = nsString(aName),
+ map = std::move(map)]() mutable {
+ AudioNode* destinationNode =
+ impl->DestinationTrack()->Engine()->NodeMainThread();
+ if (!destinationNode) {
+ return;
+ }
+ destinationNode->Context()->SetParamMapForWorkletName(name, &map);
+ }));
+}
+
+uint64_t AudioWorkletGlobalScope::CurrentFrame() const {
+ AudioNodeTrack* destinationTrack = Impl()->DestinationTrack();
+ GraphTime processedTime = destinationTrack->Graph()->ProcessedTime();
+ return destinationTrack->GraphTimeToTrackTime(processedTime);
+}
+
+double AudioWorkletGlobalScope::CurrentTime() const {
+ return static_cast<double>(CurrentFrame()) / SampleRate();
+}
+
+float AudioWorkletGlobalScope::SampleRate() const {
+ return static_cast<float>(Impl()->DestinationTrack()->mSampleRate);
+}
+
+AudioParamDescriptorMap AudioWorkletGlobalScope::DescriptorsFromJS(
+ JSContext* aCx, JS::ForOfIterator* aIter, ErrorResult& aRv) {
+ AudioParamDescriptorMap res;
+ // To check for duplicates
+ nsTHashSet<nsString> namesSet;
+
+ JS::Rooted<JS::Value> nextValue(aCx);
+ bool done = false;
+ size_t i = 0;
+ while (true) {
+ if (!aIter->next(&nextValue, &done)) {
+ aRv.NoteJSContextException(aCx);
+ return AudioParamDescriptorMap();
+ }
+ if (done) {
+ break;
+ }
+
+ BindingCallContext callCx(aCx, "AudioWorkletGlobalScope.registerProcessor");
+ nsPrintfCString sourceDescription("Element %zu in parameterDescriptors", i);
+ i++;
+ AudioParamDescriptor* descriptor = res.AppendElement(fallible);
+ if (!descriptor) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return AudioParamDescriptorMap();
+ }
+ if (!descriptor->Init(callCx, nextValue, sourceDescription.get())) {
+ aRv.NoteJSContextException(aCx);
+ return AudioParamDescriptorMap();
+ }
+ }
+
+ for (const auto& descriptor : res) {
+ if (namesSet.Contains(descriptor.mName)) {
+ aRv.ThrowNotSupportedError("Duplicated name \""_ns +
+ NS_ConvertUTF16toUTF8(descriptor.mName) +
+ "\" in parameterDescriptors."_ns);
+ return AudioParamDescriptorMap();
+ }
+
+ if (!namesSet.Insert(descriptor.mName, fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return AudioParamDescriptorMap();
+ }
+
+ if (descriptor.mMinValue > descriptor.mMaxValue) {
+ aRv.ThrowInvalidStateError(
+ "In parameterDescriptors, "_ns +
+ NS_ConvertUTF16toUTF8(descriptor.mName) +
+ " minValue should be smaller than maxValue."_ns);
+ return AudioParamDescriptorMap();
+ }
+
+ if (descriptor.mDefaultValue < descriptor.mMinValue ||
+ descriptor.mDefaultValue > descriptor.mMaxValue) {
+ aRv.ThrowInvalidStateError(
+ "In parameterDescriptors, "_ns +
+ NS_ConvertUTF16toUTF8(descriptor.mName) +
+ nsLiteralCString(" defaultValue is out of the range defined by "
+ "minValue and maxValue."));
+ return AudioParamDescriptorMap();
+ }
+ }
+
+ return res;
+}
+
+bool AudioWorkletGlobalScope::ConstructProcessor(
+ JSContext* aCx, const nsAString& aName,
+ NotNull<StructuredCloneHolder*> aSerializedOptions,
+ UniqueMessagePortId& aPortIdentifier,
+ JS::MutableHandle<JSObject*> aRetProcessor) {
+ TRACE_COMMENT("AudioWorkletProcessor::ConstructProcessor", "%s",
+ NS_ConvertUTF16toUTF8(aName).get());
+ /**
+ * See
+ * https://webaudio.github.io/web-audio-api/#AudioWorkletProcessor-instantiation
+ */
+ ErrorResult rv;
+ /**
+ * 4. Let deserializedPort be the result of
+ * StructuredDeserialize(serializedPort, the current Realm).
+ */
+ RefPtr<MessagePort> deserializedPort =
+ MessagePort::Create(this, aPortIdentifier, rv);
+ if (NS_WARN_IF(rv.MaybeSetPendingException(aCx))) {
+ return false;
+ }
+ /**
+ * 5. Let deserializedOptions be the result of
+ * StructuredDeserialize(serializedOptions, the current Realm).
+ */
+ JS::CloneDataPolicy cloneDataPolicy;
+ cloneDataPolicy.allowIntraClusterClonableSharedObjects();
+ cloneDataPolicy.allowSharedMemoryObjects();
+
+ JS::Rooted<JS::Value> deserializedOptions(aCx);
+ aSerializedOptions->Read(this, aCx, &deserializedOptions, cloneDataPolicy,
+ rv);
+ if (rv.MaybeSetPendingException(aCx)) {
+ return false;
+ }
+ /**
+ * 6. Let processorCtor be the result of looking up processorName on the
+ * AudioWorkletGlobalScope's node name to processor definition map.
+ */
+ RefPtr<AudioWorkletProcessorConstructor> processorCtor =
+ mNameToProcessorMap.Get(aName);
+ // AudioWorkletNode has already checked the definition exists.
+ // See also https://github.com/WebAudio/web-audio-api/issues/1854
+ MOZ_ASSERT(processorCtor);
+ /**
+ * 7. Store nodeReference and deserializedPort to node reference and
+ * transferred port of this AudioWorkletGlobalScope's pending processor
+ * construction data respectively.
+ */
+ // |nodeReference| is not required here because the "processorerror" event
+ // is thrown by WorkletNodeEngine::ConstructProcessor().
+ mPortForProcessor = std::move(deserializedPort);
+ /**
+ * 8. Construct a callback function from processorCtor with the argument
+ * of deserializedOptions.
+ */
+ // The options were an object before serialization and so will be an object
+ // if deserialization succeeded above. toObject() asserts.
+ JS::Rooted<JSObject*> options(aCx, &deserializedOptions.toObject());
+ RefPtr<AudioWorkletProcessor> processor = processorCtor->Construct(
+ options, rv, "AudioWorkletProcessor construction",
+ CallbackFunction::eRethrowExceptions);
+ // https://github.com/WebAudio/web-audio-api/issues/2096
+ mPortForProcessor = nullptr;
+ if (rv.MaybeSetPendingException(aCx)) {
+ return false;
+ }
+ JS::Rooted<JS::Value> processorVal(aCx);
+ if (NS_WARN_IF(!ToJSValue(aCx, processor, &processorVal))) {
+ return false;
+ }
+ MOZ_ASSERT(processorVal.isObject());
+ aRetProcessor.set(&processorVal.toObject());
+ return true;
+}
+
+RefPtr<MessagePort> AudioWorkletGlobalScope::TakePortForProcessorCtor() {
+ return std::move(mPortForProcessor);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/AudioWorkletGlobalScope.h b/dom/media/webaudio/AudioWorkletGlobalScope.h
new file mode 100644
index 0000000000..faaa731fa6
--- /dev/null
+++ b/dom/media/webaudio/AudioWorkletGlobalScope.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_AudioWorkletGlobalScope_h
+#define mozilla_dom_AudioWorkletGlobalScope_h
+
+#include "mozilla/dom/AudioParamDescriptorMap.h"
+#include "mozilla/dom/FunctionBinding.h"
+#include "mozilla/dom/WorkletGlobalScope.h"
+#include "js/ForOfIterator.h"
+#include "nsRefPtrHashtable.h"
+
+namespace mozilla {
+
+class AudioWorkletImpl;
+
+namespace dom {
+
+class AudioWorkletProcessorConstructor;
+class MessagePort;
+class StructuredCloneHolder;
+class UniqueMessagePortId;
+
+class AudioWorkletGlobalScope final : public WorkletGlobalScope {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AudioWorkletGlobalScope,
+ WorkletGlobalScope);
+
+ explicit AudioWorkletGlobalScope(AudioWorkletImpl* aImpl);
+
+ bool WrapGlobalObject(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aReflector) override;
+
+ void RegisterProcessor(JSContext* aCx, const nsAString& aName,
+ AudioWorkletProcessorConstructor& aProcessorCtor,
+ ErrorResult& aRv);
+
+ AudioWorkletImpl* Impl() const;
+
+ uint64_t CurrentFrame() const;
+
+ double CurrentTime() const;
+
+ float SampleRate() const;
+
+ // If successful, returns true and sets aRetProcessor, which will be in the
+ // compartment for the realm of this global. Returns false on failure.
+ MOZ_CAN_RUN_SCRIPT
+ bool ConstructProcessor(JSContext* aCx, const nsAString& aName,
+ NotNull<StructuredCloneHolder*> aSerializedOptions,
+ UniqueMessagePortId& aPortIdentifier,
+ JS::MutableHandle<JSObject*> aRetProcessor);
+
+ // Returns null if not called during ConstructProcessor() or if the port has
+ // already been taken.
+ RefPtr<MessagePort> TakePortForProcessorCtor();
+
+ private:
+ ~AudioWorkletGlobalScope() = default;
+
+ // Returns an AudioParamDescriptorMap filled with AudioParamDescriptor
+ // objects, extracted from JS. Returns an empty map in case of error and set
+ // aRv accordingly.
+ AudioParamDescriptorMap DescriptorsFromJS(JSContext* aCx,
+ JS::ForOfIterator* aIter,
+ ErrorResult& aRv);
+
+ typedef nsRefPtrHashtable<nsStringHashKey, AudioWorkletProcessorConstructor>
+ NodeNameToProcessorDefinitionMap;
+ NodeNameToProcessorDefinitionMap mNameToProcessorMap;
+ // https://webaudio.github.io/web-audio-api/#pending-processor-construction-data-transferred-port
+ // This does not need to be traversed during cycle-collection because it is
+ // only set while this AudioWorkletGlobalScope is on the stack.
+ RefPtr<MessagePort> mPortForProcessor;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_AudioWorkletGlobalScope_h
diff --git a/dom/media/webaudio/AudioWorkletImpl.cpp b/dom/media/webaudio/AudioWorkletImpl.cpp
new file mode 100644
index 0000000000..5eaa128c8a
--- /dev/null
+++ b/dom/media/webaudio/AudioWorkletImpl.cpp
@@ -0,0 +1,88 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "AudioWorkletImpl.h"
+
+#include "AudioContext.h"
+#include "AudioNodeTrack.h"
+#include "GeckoProfiler.h"
+#include "mozilla/dom/AudioWorkletBinding.h"
+#include "mozilla/dom/AudioWorkletGlobalScope.h"
+#include "mozilla/dom/Worklet.h"
+#include "mozilla/dom/WorkletThread.h"
+#include "nsGlobalWindowInner.h"
+
+namespace mozilla {
+
+/* static */ already_AddRefed<dom::Worklet> AudioWorkletImpl::CreateWorklet(
+ dom::AudioContext* aContext, ErrorResult& aRv) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ nsCOMPtr<nsPIDOMWindowInner> window = aContext->GetOwner();
+ if (NS_WARN_IF(!window)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+ nsCOMPtr<nsIPrincipal> principal =
+ nsGlobalWindowInner::Cast(window)->GetPrincipal();
+ if (NS_WARN_IF(!principal)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ RefPtr<AudioWorkletImpl> impl =
+ new AudioWorkletImpl(window, principal, aContext->DestinationTrack());
+
+ // The Worklet owns a reference to the AudioContext so as to keep the graph
+ // thread running as long as the Worklet is alive by keeping the
+ // AudioDestinationNode alive.
+ return MakeAndAddRef<dom::Worklet>(window, std::move(impl),
+ ToSupports(aContext));
+}
+
+AudioWorkletImpl::AudioWorkletImpl(nsPIDOMWindowInner* aWindow,
+ nsIPrincipal* aPrincipal,
+ AudioNodeTrack* aDestinationTrack)
+ : WorkletImpl(aWindow, aPrincipal), mDestinationTrack(aDestinationTrack) {}
+
+AudioWorkletImpl::~AudioWorkletImpl() = default;
+
+JSObject* AudioWorkletImpl::WrapWorklet(JSContext* aCx, dom::Worklet* aWorklet,
+ JS::Handle<JSObject*> aGivenProto) {
+ MOZ_ASSERT(NS_IsMainThread());
+ return dom::AudioWorklet_Binding::Wrap(aCx, aWorklet, aGivenProto);
+}
+
+nsresult AudioWorkletImpl::SendControlMessage(
+ already_AddRefed<nsIRunnable> aRunnable) {
+ mDestinationTrack->SendRunnable(std::move(aRunnable));
+ return NS_OK;
+}
+
+void AudioWorkletImpl::OnAddModuleStarted() const {
+#ifdef MOZ_GECKO_PROFILER
+ profiler_add_marker(ProfilerStringView("AudioWorklet.addModule"),
+ geckoprofiler::category::MEDIA_RT,
+ {MarkerTiming::IntervalStart()});
+#endif
+}
+
+void AudioWorkletImpl::OnAddModulePromiseSettled() const {
+#ifdef MOZ_GECKO_PROFILER
+ profiler_add_marker(ProfilerStringView("AudioWorklet.addModule"),
+ geckoprofiler::category::MEDIA_RT,
+ {MarkerTiming::IntervalEnd()});
+#endif
+}
+
+already_AddRefed<dom::WorkletGlobalScope>
+AudioWorkletImpl::ConstructGlobalScope() {
+ dom::WorkletThread::AssertIsOnWorkletThread();
+
+ return MakeAndAddRef<dom::AudioWorkletGlobalScope>(this);
+}
+
+} // namespace mozilla
diff --git a/dom/media/webaudio/AudioWorkletImpl.h b/dom/media/webaudio/AudioWorkletImpl.h
new file mode 100644
index 0000000000..39f347fdb2
--- /dev/null
+++ b/dom/media/webaudio/AudioWorkletImpl.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef AudioWorkletImpl_h
+#define AudioWorkletImpl_h
+
+#include "mozilla/dom/WorkletImpl.h"
+#include "mozilla/dom/AudioWorkletGlobalScope.h"
+
+namespace mozilla {
+
+class AudioNodeTrack;
+
+namespace dom {
+class AudioContext;
+}
+
+class AudioWorkletImpl final : public WorkletImpl {
+ public:
+ // Methods for parent thread only:
+
+ static already_AddRefed<dom::Worklet> CreateWorklet(
+ dom::AudioContext* aContext, ErrorResult& aRv);
+
+ JSObject* WrapWorklet(JSContext* aCx, dom::Worklet* aWorklet,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ nsresult SendControlMessage(already_AddRefed<nsIRunnable> aRunnable) override;
+
+ nsContentPolicyType ContentPolicyType() const override {
+ return nsIContentPolicy::TYPE_INTERNAL_AUDIOWORKLET;
+ }
+
+ // Execution thread only.
+ dom::AudioWorkletGlobalScope* GetGlobalScope() {
+ return static_cast<dom::AudioWorkletGlobalScope*>(
+ WorkletImpl::GetGlobalScope());
+ }
+
+ // Any thread:
+ AudioNodeTrack* DestinationTrack() const { return mDestinationTrack; }
+
+ void OnAddModuleStarted() const override;
+ void OnAddModulePromiseSettled() const override;
+
+ protected:
+ // Execution thread only.
+ already_AddRefed<dom::WorkletGlobalScope> ConstructGlobalScope() override;
+
+ private:
+ AudioWorkletImpl(nsPIDOMWindowInner* aWindow, nsIPrincipal* aPrincipal,
+ AudioNodeTrack* aDestinationTrack);
+ ~AudioWorkletImpl();
+
+ const RefPtr<AudioNodeTrack> mDestinationTrack;
+};
+
+} // namespace mozilla
+
+#endif // AudioWorkletImpl_h
diff --git a/dom/media/webaudio/AudioWorkletNode.cpp b/dom/media/webaudio/AudioWorkletNode.cpp
new file mode 100644
index 0000000000..76a3185f2c
--- /dev/null
+++ b/dom/media/webaudio/AudioWorkletNode.cpp
@@ -0,0 +1,899 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "AudioWorkletNode.h"
+
+#include "AudioNodeEngine.h"
+#include "AudioParamMap.h"
+#include "AudioWorkletImpl.h"
+#include "js/Array.h" // JS::{Get,Set}ArrayLength, JS::NewArrayLength
+#include "js/CallAndConstruct.h" // JS::Call, JS::IsCallable
+#include "js/Exception.h"
+#include "js/experimental/TypedData.h" // JS_NewFloat32Array, JS_GetFloat32ArrayData, JS_GetTypedArrayLength, JS_GetArrayBufferViewBuffer
+#include "js/PropertyAndElement.h" // JS_DefineElement, JS_DefineUCProperty, JS_GetProperty
+#include "mozilla/dom/AudioWorkletNodeBinding.h"
+#include "mozilla/dom/AudioParamMapBinding.h"
+#include "mozilla/dom/AutoEntryScript.h"
+#include "mozilla/dom/RootedDictionary.h"
+#include "mozilla/dom/ErrorEvent.h"
+#include "mozilla/dom/Worklet.h"
+#include "nsIScriptGlobalObject.h"
+#include "AudioParam.h"
+#include "AudioDestinationNode.h"
+#include "mozilla/dom/MessageChannel.h"
+#include "mozilla/dom/MessagePort.h"
+#include "mozilla/ScopeExit.h"
+#include "nsReadableUtils.h"
+#include "mozilla/Span.h"
+#include "PlayingRefChangeHandler.h"
+#include "nsPrintfCString.h"
+#include "Tracing.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(AudioWorkletNode, AudioNode)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(AudioWorkletNode, AudioNode, mPort,
+ mParameters)
+
+struct NamedAudioParamTimeline {
+ explicit NamedAudioParamTimeline(const AudioParamDescriptor& aParamDescriptor)
+ : mName(aParamDescriptor.mName),
+ mTimeline(aParamDescriptor.mDefaultValue) {}
+ const nsString mName;
+ AudioParamTimeline mTimeline;
+};
+
+struct ProcessorErrorDetails {
+ ProcessorErrorDetails() : mLineno(0), mColno(0) {}
+ // Line number (1-origin).
+ unsigned mLineno;
+ // Column number in UTF-16 code units (1-origin).
+ unsigned mColno;
+ nsString mFilename;
+ nsString mMessage;
+};
+
+class WorkletNodeEngine final : public AudioNodeEngine {
+ public:
+ WorkletNodeEngine(AudioWorkletNode* aNode,
+ AudioDestinationNode* aDestinationNode,
+ nsTArray<NamedAudioParamTimeline>&& aParamTimelines,
+ const Optional<Sequence<uint32_t>>& aOutputChannelCount)
+ : AudioNodeEngine(aNode),
+ mDestination(aDestinationNode->Track()),
+ mParamTimelines(std::move(aParamTimelines)) {
+ if (aOutputChannelCount.WasPassed()) {
+ mOutputChannelCount = aOutputChannelCount.Value();
+ }
+ }
+
+ MOZ_CAN_RUN_SCRIPT
+ void ConstructProcessor(AudioWorkletImpl* aWorkletImpl,
+ const nsAString& aName,
+ NotNull<StructuredCloneHolder*> aSerializedOptions,
+ UniqueMessagePortId& aPortIdentifier,
+ AudioNodeTrack* aTrack);
+
+ void RecvTimelineEvent(uint32_t aIndex, AudioParamEvent& aEvent) override {
+ MOZ_ASSERT(mDestination);
+ aEvent.ConvertToTicks(mDestination);
+
+ if (aIndex < mParamTimelines.Length()) {
+ mParamTimelines[aIndex].mTimeline.InsertEvent<int64_t>(aEvent);
+ } else {
+ NS_ERROR("Bad WorkletNodeEngine timeline event index");
+ }
+ }
+
+ void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
+ const AudioBlock& aInput, AudioBlock* aOutput,
+ bool* aFinished) override {
+ MOZ_ASSERT(InputCount() <= 1);
+ MOZ_ASSERT(OutputCount() <= 1);
+ TRACE("WorkletNodeEngine::ProcessBlock");
+ ProcessBlocksOnPorts(aTrack, aFrom, Span(&aInput, InputCount()),
+ Span(aOutput, OutputCount()), aFinished);
+ }
+
+ void ProcessBlocksOnPorts(AudioNodeTrack* aTrack, GraphTime aFrom,
+ Span<const AudioBlock> aInput,
+ Span<AudioBlock> aOutput, bool* aFinished) override;
+
+ void OnGraphThreadDone() override { ReleaseJSResources(); }
+
+ bool IsActive() const override { return mKeepEngineActive; }
+
+ // Vector<T> supports non-memmovable types such as PersistentRooted
+ // (without any need to jump through hoops like
+ // MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR_FOR_TEMPLATE for nsTArray).
+ // PersistentRooted is used because these AudioWorkletGlobalScope scope
+ // objects may be kept alive as long as the AudioWorkletNode in the
+ // main-thread global.
+ struct Channels {
+ Vector<JS::PersistentRooted<JSObject*>, GUESS_AUDIO_CHANNELS>
+ mFloat32Arrays;
+ JS::PersistentRooted<JSObject*> mJSArray;
+ // For SetArrayElements():
+ operator JS::Handle<JSObject*>() const { return mJSArray; }
+ };
+ struct Ports {
+ Vector<Channels, 1> mPorts;
+ JS::PersistentRooted<JSObject*> mJSArray;
+ };
+ struct ParameterValues {
+ Vector<JS::PersistentRooted<JSObject*>> mFloat32Arrays;
+ JS::PersistentRooted<JSObject*> mJSObject;
+ };
+
+ private:
+ size_t ParameterCount() { return mParamTimelines.Length(); }
+ void SendProcessorError(AudioNodeTrack* aTrack, JSContext* aCx);
+ bool CallProcess(AudioNodeTrack* aTrack, JSContext* aCx,
+ JS::Handle<JS::Value> aCallable);
+ void ProduceSilence(AudioNodeTrack* aTrack, Span<AudioBlock> aOutput);
+ void SendErrorToMainThread(AudioNodeTrack* aTrack,
+ const ProcessorErrorDetails& aDetails);
+
+ void ReleaseJSResources() {
+ mInputs.mPorts.clearAndFree();
+ mOutputs.mPorts.clearAndFree();
+ mParameters.mFloat32Arrays.clearAndFree();
+ mInputs.mJSArray.reset();
+ mOutputs.mJSArray.reset();
+ mParameters.mJSObject.reset();
+ mGlobal = nullptr;
+ // This is equivalent to setting [[callable process]] to false.
+ mProcessor.reset();
+ }
+
+ nsCString mProcessorName;
+ RefPtr<AudioNodeTrack> mDestination;
+ nsTArray<uint32_t> mOutputChannelCount;
+ nsTArray<NamedAudioParamTimeline> mParamTimelines;
+ // The AudioWorkletGlobalScope-associated objects referenced from
+ // WorkletNodeEngine are typically kept alive as long as the
+ // AudioWorkletNode in the main-thread global. The objects must be released
+ // on the rendering thread, which usually happens simply because
+ // AudioWorkletNode is such that the last AudioNodeTrack reference is
+ // released by the MTG. That occurs on the rendering thread except during
+ // process shutdown, in which case NotifyForcedShutdown() is called on the
+ // rendering thread.
+ //
+ // mInputs, mOutputs and mParameters keep references to all objects passed to
+ // process(), for reuse of the same objects. The JS objects are all in the
+ // compartment of the realm of mGlobal. Properties on the objects may be
+ // replaced by script, so don't assume that getting indexed properties on the
+ // JS arrays will return the same objects. Only objects and buffers created
+ // by the implementation are modified or read by the implementation.
+ Ports mInputs;
+ Ports mOutputs;
+ ParameterValues mParameters;
+
+ RefPtr<AudioWorkletGlobalScope> mGlobal;
+ JS::PersistentRooted<JSObject*> mProcessor;
+
+ // mProcessorIsActive is named [[active source]] in the spec.
+ // It is initially true and so at least the first process()
+ // call will not be skipped when there are no active inputs.
+ bool mProcessorIsActive = true;
+ // mKeepEngineActive ensures another call to ProcessBlocksOnPorts(), even if
+ // there are no active inputs. Its transitions to false lag those of
+ // mProcessorIsActive by one call to ProcessBlocksOnPorts() so that
+ // downstream engines can addref their nodes before this engine's node is
+ // released.
+ bool mKeepEngineActive = true;
+};
+
+void WorkletNodeEngine::SendErrorToMainThread(
+ AudioNodeTrack* aTrack, const ProcessorErrorDetails& aDetails) {
+ RefPtr<AudioNodeTrack> track = aTrack;
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "WorkletNodeEngine::SendProcessorError",
+ [track = std::move(track), aDetails]() mutable {
+ AudioWorkletNode* node =
+ static_cast<AudioWorkletNode*>(track->Engine()->NodeMainThread());
+ if (!node) {
+ return;
+ }
+ node->DispatchProcessorErrorEvent(aDetails);
+ }));
+}
+
+void WorkletNodeEngine::SendProcessorError(AudioNodeTrack* aTrack,
+ JSContext* aCx) {
+ // Note that once an exception is thrown, the processor will output silence
+ // throughout its lifetime.
+ ReleaseJSResources();
+ // The processor errored out while getting a context, try to tell the node
+ // anyways.
+ if (!aCx || !JS_IsExceptionPending(aCx)) {
+ ProcessorErrorDetails details;
+ details.mMessage.Assign(u"Unknown processor error");
+ SendErrorToMainThread(aTrack, details);
+ return;
+ }
+
+ JS::ExceptionStack exnStack(aCx);
+ if (JS::StealPendingExceptionStack(aCx, &exnStack)) {
+ JS::ErrorReportBuilder jsReport(aCx);
+ if (!jsReport.init(aCx, exnStack,
+ JS::ErrorReportBuilder::WithSideEffects)) {
+ ProcessorErrorDetails details;
+ details.mMessage.Assign(u"Unknown processor error");
+ SendErrorToMainThread(aTrack, details);
+ // Set the exception and stack back to have it in the console with a stack
+ // trace.
+ JS::SetPendingExceptionStack(aCx, exnStack);
+ return;
+ }
+
+ ProcessorErrorDetails details;
+
+ CopyUTF8toUTF16(
+ mozilla::MakeStringSpan(jsReport.report()->filename.c_str()),
+ details.mFilename);
+
+ xpc::ErrorReport::ErrorReportToMessageString(jsReport.report(),
+ details.mMessage);
+ details.mLineno = jsReport.report()->lineno;
+ details.mColno = jsReport.report()->column.oneOriginValue();
+ MOZ_ASSERT(!jsReport.report()->isMuted);
+
+ SendErrorToMainThread(aTrack, details);
+
+ // Set the exception and stack back to have it in the console with a stack
+ // trace.
+ JS::SetPendingExceptionStack(aCx, exnStack);
+ } else {
+ NS_WARNING("No exception, but processor errored out?");
+ }
+}
+
+void WorkletNodeEngine::ConstructProcessor(
+ AudioWorkletImpl* aWorkletImpl, const nsAString& aName,
+ NotNull<StructuredCloneHolder*> aSerializedOptions,
+ UniqueMessagePortId& aPortIdentifier, AudioNodeTrack* aTrack) {
+ MOZ_ASSERT(mInputs.mPorts.empty() && mOutputs.mPorts.empty());
+ RefPtr<AudioWorkletGlobalScope> global = aWorkletImpl->GetGlobalScope();
+ if (!global) {
+ // A global was previously used to register this kind of processor. If it
+ // no longer exists now, that is because the document is going away and so
+ // there is no need to send an error.
+ return;
+ }
+ AutoJSAPI api;
+ if (NS_WARN_IF(!api.Init(global))) {
+ SendProcessorError(aTrack, nullptr);
+ return;
+ }
+ mProcessorName = NS_ConvertUTF16toUTF8(aName);
+ JSContext* cx = api.cx();
+ mProcessor.init(cx);
+ if (!global->ConstructProcessor(cx, aName, aSerializedOptions,
+ aPortIdentifier, &mProcessor) ||
+ // mInputs and mOutputs outer arrays are fixed length and so much of the
+ // initialization need only be performed once (i.e. here).
+ NS_WARN_IF(!mInputs.mPorts.growBy(InputCount())) ||
+ NS_WARN_IF(!mOutputs.mPorts.growBy(OutputCount()))) {
+ SendProcessorError(aTrack, cx);
+ return;
+ }
+ mGlobal = std::move(global);
+ mInputs.mJSArray.init(cx);
+ mOutputs.mJSArray.init(cx);
+ for (auto& port : mInputs.mPorts) {
+ port.mJSArray.init(cx);
+ }
+ for (auto& port : mOutputs.mPorts) {
+ port.mJSArray.init(cx);
+ }
+ JSObject* object = JS_NewPlainObject(cx);
+ if (NS_WARN_IF(!object)) {
+ SendProcessorError(aTrack, cx);
+ return;
+ }
+
+ mParameters.mJSObject.init(cx, object);
+ if (NS_WARN_IF(!mParameters.mFloat32Arrays.growBy(ParameterCount()))) {
+ SendProcessorError(aTrack, cx);
+ return;
+ }
+ for (size_t i = 0; i < mParamTimelines.Length(); i++) {
+ auto& float32ArraysRef = mParameters.mFloat32Arrays;
+ float32ArraysRef[i].init(cx);
+ JSObject* array = JS_NewFloat32Array(cx, WEBAUDIO_BLOCK_SIZE);
+ if (NS_WARN_IF(!array)) {
+ SendProcessorError(aTrack, cx);
+ return;
+ }
+
+ float32ArraysRef[i] = array;
+ if (NS_WARN_IF(!JS_DefineUCProperty(
+ cx, mParameters.mJSObject, mParamTimelines[i].mName.get(),
+ mParamTimelines[i].mName.Length(), float32ArraysRef[i],
+ JSPROP_ENUMERATE))) {
+ SendProcessorError(aTrack, cx);
+ return;
+ }
+ }
+ if (NS_WARN_IF(!JS_FreezeObject(cx, mParameters.mJSObject))) {
+ SendProcessorError(aTrack, cx);
+ return;
+ }
+}
+
+// Type T should support the length() and operator[]() methods and the return
+// type of |operator[]() const| should support conversion to Handle<JSObject*>.
+template <typename T>
+static bool SetArrayElements(JSContext* aCx, const T& aElements,
+ JS::Handle<JSObject*> aArray) {
+ for (size_t i = 0; i < aElements.length(); ++i) {
+ if (!JS_DefineElement(aCx, aArray, i, aElements[i], JSPROP_ENUMERATE)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+template <typename T>
+static bool PrepareArray(JSContext* aCx, const T& aElements,
+ JS::MutableHandle<JSObject*> aArray) {
+ size_t length = aElements.length();
+ if (aArray) {
+ // Attempt to reuse.
+ uint32_t oldLength;
+ if (JS::GetArrayLength(aCx, aArray, &oldLength) &&
+ (oldLength == length || JS::SetArrayLength(aCx, aArray, length)) &&
+ SetArrayElements(aCx, aElements, aArray)) {
+ return true;
+ }
+ // Script may have frozen the array. Try again with a new Array.
+ JS_ClearPendingException(aCx);
+ }
+ JSObject* array = JS::NewArrayObject(aCx, length);
+ if (NS_WARN_IF(!array)) {
+ return false;
+ }
+ aArray.set(array);
+ return SetArrayElements(aCx, aElements, aArray);
+}
+
+enum class ArrayElementInit { None, Zero };
+
+// Exactly when to create new Float32Array and Array objects is not specified.
+// This approach aims to minimize garbage creation, while continuing to
+// function after objects are modified by content.
+// See https://github.com/WebAudio/web-audio-api/issues/1934 and
+// https://github.com/WebAudio/web-audio-api/issues/1933
+static bool PrepareBufferArrays(JSContext* aCx, Span<const AudioBlock> aBlocks,
+ WorkletNodeEngine::Ports* aPorts,
+ ArrayElementInit aInit) {
+ MOZ_ASSERT(aBlocks.Length() == aPorts->mPorts.length());
+ for (size_t i = 0; i < aBlocks.Length(); ++i) {
+ size_t channelCount = aBlocks[i].ChannelCount();
+ WorkletNodeEngine::Channels& portRef = aPorts->mPorts[i];
+
+ auto& float32ArraysRef = portRef.mFloat32Arrays;
+ for (auto& channelRef : float32ArraysRef) {
+ size_t length = JS_GetTypedArrayLength(channelRef);
+ if (length != WEBAUDIO_BLOCK_SIZE) {
+ // Script has detached array buffers. Create new objects.
+ JSObject* array = JS_NewFloat32Array(aCx, WEBAUDIO_BLOCK_SIZE);
+ if (NS_WARN_IF(!array)) {
+ return false;
+ }
+ channelRef = array;
+ } else if (aInit == ArrayElementInit::Zero) {
+ // Need only zero existing arrays as new arrays are already zeroed.
+ JS::AutoCheckCannotGC nogc;
+ bool isShared;
+ float* elementData =
+ JS_GetFloat32ArrayData(channelRef, &isShared, nogc);
+ MOZ_ASSERT(!isShared); // Was created as unshared
+ std::fill_n(elementData, WEBAUDIO_BLOCK_SIZE, 0.0f);
+ }
+ }
+ // Enlarge if necessary...
+ if (NS_WARN_IF(!float32ArraysRef.reserve(channelCount))) {
+ return false;
+ }
+ while (float32ArraysRef.length() < channelCount) {
+ JSObject* array = JS_NewFloat32Array(aCx, WEBAUDIO_BLOCK_SIZE);
+ if (NS_WARN_IF(!array)) {
+ return false;
+ }
+ float32ArraysRef.infallibleEmplaceBack(aCx, array);
+ }
+ // ... or shrink if necessary.
+ float32ArraysRef.shrinkTo(channelCount);
+
+ if (NS_WARN_IF(!PrepareArray(aCx, float32ArraysRef, &portRef.mJSArray))) {
+ return false;
+ }
+ }
+
+ return !(NS_WARN_IF(!PrepareArray(aCx, aPorts->mPorts, &aPorts->mJSArray)));
+}
+
+// This runs JS script. MediaTrackGraph control messages, which would
+// potentially destroy the WorkletNodeEngine and its AudioNodeTrack, cannot
+// be triggered by script. They are not run from an nsIThread event loop and
+// do not run until after ProcessBlocksOnPorts() has returned.
+bool WorkletNodeEngine::CallProcess(AudioNodeTrack* aTrack, JSContext* aCx,
+ JS::Handle<JS::Value> aCallable) {
+ TRACE_COMMENT("AudioWorkletNodeEngine::CallProcess", mProcessorName.get());
+
+ JS::RootedVector<JS::Value> argv(aCx);
+ if (NS_WARN_IF(!argv.resize(3))) {
+ return false;
+ }
+ argv[0].setObject(*mInputs.mJSArray);
+ argv[1].setObject(*mOutputs.mJSArray);
+ argv[2].setObject(*mParameters.mJSObject);
+ JS::Rooted<JS::Value> rval(aCx);
+ if (!JS::Call(aCx, mProcessor, aCallable, argv, &rval)) {
+ return false;
+ }
+
+ mProcessorIsActive = JS::ToBoolean(rval);
+ // Transitions of mProcessorIsActive to false do not trigger
+ // PlayingRefChangeHandler::RELEASE until silence is produced in the next
+ // block. This allows downstream engines receiving this non-silence block
+ // to take a reference to their nodes before this engine's node releases its
+ // down node references.
+ if (mProcessorIsActive && !mKeepEngineActive) {
+ mKeepEngineActive = true;
+ RefPtr<PlayingRefChangeHandler> refchanged =
+ new PlayingRefChangeHandler(aTrack, PlayingRefChangeHandler::ADDREF);
+ aTrack->Graph()->DispatchToMainThreadStableState(refchanged.forget());
+ }
+ return true;
+}
+
+void WorkletNodeEngine::ProduceSilence(AudioNodeTrack* aTrack,
+ Span<AudioBlock> aOutput) {
+ if (mKeepEngineActive) {
+ mKeepEngineActive = false;
+ aTrack->ScheduleCheckForInactive();
+ RefPtr<PlayingRefChangeHandler> refchanged =
+ new PlayingRefChangeHandler(aTrack, PlayingRefChangeHandler::RELEASE);
+ aTrack->Graph()->DispatchToMainThreadStableState(refchanged.forget());
+ }
+ for (AudioBlock& output : aOutput) {
+ output.SetNull(WEBAUDIO_BLOCK_SIZE);
+ }
+}
+
+void WorkletNodeEngine::ProcessBlocksOnPorts(AudioNodeTrack* aTrack,
+ GraphTime aFrom,
+ Span<const AudioBlock> aInput,
+ Span<AudioBlock> aOutput,
+ bool* aFinished) {
+ MOZ_ASSERT(aInput.Length() == InputCount());
+ MOZ_ASSERT(aOutput.Length() == OutputCount());
+ TRACE("WorkletNodeEngine::ProcessBlocksOnPorts");
+
+ bool isSilent = true;
+ if (mProcessor) {
+ if (mProcessorIsActive) {
+ isSilent = false; // call process()
+ } else { // [[active source]] is false.
+ // Call process() only if an input is actively processing.
+ for (const AudioBlock& input : aInput) {
+ if (!input.IsNull()) {
+ isSilent = false;
+ break;
+ }
+ }
+ }
+ }
+ if (isSilent) {
+ ProduceSilence(aTrack, aOutput);
+ return;
+ }
+
+ if (!mOutputChannelCount.IsEmpty()) {
+ MOZ_ASSERT(mOutputChannelCount.Length() == aOutput.Length());
+ for (size_t o = 0; o < aOutput.Length(); ++o) {
+ aOutput[o].AllocateChannels(mOutputChannelCount[o]);
+ }
+ } else if (aInput.Length() == 1 && aOutput.Length() == 1) {
+ uint32_t channelCount = std::max(aInput[0].ChannelCount(), 1U);
+ aOutput[0].AllocateChannels(channelCount);
+ } else {
+ for (AudioBlock& output : aOutput) {
+ output.AllocateChannels(1);
+ }
+ }
+
+ AutoEntryScript aes(mGlobal, "Worklet Process");
+ JSContext* cx = aes.cx();
+ auto produceSilenceWithError = MakeScopeExit([this, aTrack, cx, &aOutput] {
+ SendProcessorError(aTrack, cx);
+ ProduceSilence(aTrack, aOutput);
+ });
+
+ JS::Rooted<JS::Value> process(cx);
+ if (!JS_GetProperty(cx, mProcessor, "process", &process) ||
+ !process.isObject() || !JS::IsCallable(&process.toObject()) ||
+ !PrepareBufferArrays(cx, aInput, &mInputs, ArrayElementInit::None) ||
+ !PrepareBufferArrays(cx, aOutput, &mOutputs, ArrayElementInit::Zero)) {
+ // process() not callable or OOM.
+ return;
+ }
+
+ // Copy input values to JS objects.
+ for (size_t i = 0; i < aInput.Length(); ++i) {
+ const AudioBlock& input = aInput[i];
+ size_t channelCount = input.ChannelCount();
+ if (channelCount == 0) {
+ // Null blocks have AUDIO_FORMAT_SILENCE.
+ // Don't call ChannelData<float>().
+ continue;
+ }
+ float volume = input.mVolume;
+ const auto& channelData = input.ChannelData<float>();
+ const auto& float32Arrays = mInputs.mPorts[i].mFloat32Arrays;
+ JS::AutoCheckCannotGC nogc;
+ for (size_t c = 0; c < channelCount; ++c) {
+ bool isShared;
+ float* dest = JS_GetFloat32ArrayData(float32Arrays[c], &isShared, nogc);
+ MOZ_ASSERT(!isShared); // Was created as unshared
+ AudioBlockCopyChannelWithScale(channelData[c], volume, dest);
+ }
+ }
+
+ TrackTime tick = mDestination->GraphTimeToTrackTime(aFrom);
+ // Compute and copy parameter values to JS objects.
+ for (size_t i = 0; i < mParamTimelines.Length(); ++i) {
+ const auto& float32Arrays = mParameters.mFloat32Arrays[i];
+ size_t length = JS_GetTypedArrayLength(float32Arrays);
+
+ // If the Float32Array that is supposed to hold the values for a particular
+ // AudioParam has been detached, error out. This is being worked on in
+ // https://github.com/WebAudio/web-audio-api/issues/1933 and
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1619486
+ if (length != WEBAUDIO_BLOCK_SIZE) {
+ return;
+ }
+ JS::AutoCheckCannotGC nogc;
+ bool isShared;
+ float* dest = JS_GetFloat32ArrayData(float32Arrays, &isShared, nogc);
+ MOZ_ASSERT(!isShared); // Was created as unshared
+
+ size_t frames =
+ mParamTimelines[i].mTimeline.HasSimpleValue() ? 1 : WEBAUDIO_BLOCK_SIZE;
+ mParamTimelines[i].mTimeline.GetValuesAtTime(tick, dest, frames);
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1616599
+ if (frames == 1) {
+ std::fill_n(dest + 1, WEBAUDIO_BLOCK_SIZE - 1, dest[0]);
+ }
+ }
+
+ if (!CallProcess(aTrack, cx, process)) {
+ // An exception occurred.
+ /**
+ * https://webaudio.github.io/web-audio-api/#dom-audioworkletnode-onprocessorerror
+ * Note that once an exception is thrown, the processor will output silence
+ * throughout its lifetime.
+ */
+ return;
+ }
+
+ // Copy output values from JS objects.
+ for (size_t o = 0; o < aOutput.Length(); ++o) {
+ AudioBlock* output = &aOutput[o];
+ size_t channelCount = output->ChannelCount();
+ const auto& float32Arrays = mOutputs.mPorts[o].mFloat32Arrays;
+ for (size_t c = 0; c < channelCount; ++c) {
+ size_t length = JS_GetTypedArrayLength(float32Arrays[c]);
+ if (length != WEBAUDIO_BLOCK_SIZE) {
+ // ArrayBuffer has been detached. Behavior is unspecified.
+ // https://github.com/WebAudio/web-audio-api/issues/1933 and
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1619486
+ return;
+ }
+ JS::AutoCheckCannotGC nogc;
+ bool isShared;
+ const float* src =
+ JS_GetFloat32ArrayData(float32Arrays[c], &isShared, nogc);
+ MOZ_ASSERT(!isShared); // Was created as unshared
+ PodCopy(output->ChannelFloatsForWrite(c), src, WEBAUDIO_BLOCK_SIZE);
+ }
+ }
+
+ produceSilenceWithError.release(); // have output and no error
+}
+
+AudioWorkletNode::AudioWorkletNode(AudioContext* aAudioContext,
+ const nsAString& aName,
+ const AudioWorkletNodeOptions& aOptions)
+ : AudioNode(aAudioContext, 2, ChannelCountMode::Max,
+ ChannelInterpretation::Speakers),
+ mNodeName(aName),
+ mInputCount(aOptions.mNumberOfInputs),
+ mOutputCount(aOptions.mNumberOfOutputs) {}
+
+void AudioWorkletNode::InitializeParameters(
+ nsTArray<NamedAudioParamTimeline>* aParamTimelines, ErrorResult& aRv) {
+ MOZ_ASSERT(!mParameters, "Only initialize the `parameters` attribute once.");
+ MOZ_ASSERT(aParamTimelines);
+
+ AudioContext* context = Context();
+ const AudioParamDescriptorMap* parameterDescriptors =
+ context->GetParamMapForWorkletName(mNodeName);
+ MOZ_ASSERT(parameterDescriptors);
+
+ size_t audioParamIndex = 0;
+ aParamTimelines->SetCapacity(parameterDescriptors->Length());
+
+ for (size_t i = 0; i < parameterDescriptors->Length(); i++) {
+ auto& paramEntry = (*parameterDescriptors)[i];
+ CreateAudioParam(audioParamIndex++, paramEntry.mName,
+ paramEntry.mDefaultValue, paramEntry.mMinValue,
+ paramEntry.mMaxValue);
+ aParamTimelines->AppendElement(paramEntry);
+ }
+}
+
+void AudioWorkletNode::SendParameterData(
+ const Optional<Record<nsString, double>>& aParameterData) {
+ MOZ_ASSERT(mTrack, "This method only works if the track has been created.");
+ nsAutoString name;
+ if (aParameterData.WasPassed()) {
+ const auto& paramData = aParameterData.Value();
+ for (const auto& paramDataEntry : paramData.Entries()) {
+ for (auto& audioParam : mParams) {
+ audioParam->GetName(name);
+ if (paramDataEntry.mKey.Equals(name)) {
+ audioParam->SetInitialValue(paramDataEntry.mValue);
+ }
+ }
+ }
+ }
+}
+
+/* static */
+already_AddRefed<AudioWorkletNode> AudioWorkletNode::Constructor(
+ const GlobalObject& aGlobal, AudioContext& aAudioContext,
+ const nsAString& aName, const AudioWorkletNodeOptions& aOptions,
+ ErrorResult& aRv) {
+ TRACE_COMMENT("AudioWorkletNode::Constructor", "%s",
+ NS_ConvertUTF16toUTF8(aName).get());
+ /**
+ * 1. If nodeName does not exist as a key in the BaseAudioContext’s node
+ * name to parameter descriptor map, throw a InvalidStateError exception
+ * and abort these steps.
+ */
+ const AudioParamDescriptorMap* parameterDescriptors =
+ aAudioContext.GetParamMapForWorkletName(aName);
+ if (!parameterDescriptors) {
+ // Not using nsPrintfCString in case aName has embedded nulls.
+ aRv.ThrowInvalidStateError("Unknown AudioWorklet name '"_ns +
+ NS_ConvertUTF16toUTF8(aName) + "'"_ns);
+ return nullptr;
+ }
+
+ // See https://github.com/WebAudio/web-audio-api/issues/2074 for ordering.
+ RefPtr<AudioWorkletNode> audioWorkletNode =
+ new AudioWorkletNode(&aAudioContext, aName, aOptions);
+ audioWorkletNode->Initialize(aOptions, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ /**
+ * 3. Configure input, output and output channels of node with options.
+ */
+ if (aOptions.mNumberOfInputs == 0 && aOptions.mNumberOfOutputs == 0) {
+ aRv.ThrowNotSupportedError(
+ "Must have nonzero numbers of inputs or outputs");
+ return nullptr;
+ }
+
+ if (aOptions.mOutputChannelCount.WasPassed()) {
+ /**
+ * 1. If any value in outputChannelCount is zero or greater than the
+ * implementation’s maximum number of channels, throw a
+ * NotSupportedError and abort the remaining steps.
+ */
+ for (uint32_t channelCount : aOptions.mOutputChannelCount.Value()) {
+ if (channelCount == 0 || channelCount > WebAudioUtils::MaxChannelCount) {
+ aRv.ThrowNotSupportedError(
+ nsPrintfCString("Channel count (%u) must be in the range [1, max "
+ "supported channel count]",
+ channelCount));
+ return nullptr;
+ }
+ }
+ /**
+ * 2. If the length of outputChannelCount does not equal numberOfOutputs,
+ * throw an IndexSizeError and abort the remaining steps.
+ */
+ if (aOptions.mOutputChannelCount.Value().Length() !=
+ aOptions.mNumberOfOutputs) {
+ aRv.ThrowIndexSizeError(
+ nsPrintfCString("Length of outputChannelCount (%zu) does not match "
+ "numberOfOutputs (%u)",
+ aOptions.mOutputChannelCount.Value().Length(),
+ aOptions.mNumberOfOutputs));
+ return nullptr;
+ }
+ }
+ // MTG does not support more than UINT16_MAX inputs or outputs.
+ if (aOptions.mNumberOfInputs > UINT16_MAX) {
+ aRv.ThrowRangeError<MSG_VALUE_OUT_OF_RANGE>("numberOfInputs");
+ return nullptr;
+ }
+ if (aOptions.mNumberOfOutputs > UINT16_MAX) {
+ aRv.ThrowRangeError<MSG_VALUE_OUT_OF_RANGE>("numberOfOutputs");
+ return nullptr;
+ }
+
+ /**
+ * 4. Let messageChannel be a new MessageChannel.
+ */
+ RefPtr<MessageChannel> messageChannel =
+ MessageChannel::Constructor(aGlobal, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ /* 5. Let nodePort be the value of messageChannel’s port1 attribute.
+ * 6. Let processorPortOnThisSide be the value of messageChannel’s port2
+ * attribute.
+ * 7. Let serializedProcessorPort be the result of
+ * StructuredSerializeWithTransfer(processorPortOnThisSide,
+ * « processorPortOnThisSide »).
+ */
+ UniqueMessagePortId processorPortId;
+ messageChannel->Port2()->CloneAndDisentangle(processorPortId);
+ /**
+ * 8. Convert options dictionary to optionsObject.
+ */
+ JSContext* cx = aGlobal.Context();
+ JS::Rooted<JS::Value> optionsVal(cx);
+ if (NS_WARN_IF(!ToJSValue(cx, aOptions, &optionsVal))) {
+ aRv.NoteJSContextException(cx);
+ return nullptr;
+ }
+
+ /**
+ * 9. Let serializedOptions be the result of
+ * StructuredSerialize(optionsObject).
+ */
+
+ // This context and the worklet are part of the same agent cluster and they
+ // can share memory.
+ JS::CloneDataPolicy cloneDataPolicy;
+ cloneDataPolicy.allowIntraClusterClonableSharedObjects();
+ cloneDataPolicy.allowSharedMemoryObjects();
+
+ // StructuredCloneHolder does not have a move constructor. Instead allocate
+ // memory so that the pointer can be passed to the rendering thread.
+ UniquePtr<StructuredCloneHolder> serializedOptions =
+ MakeUnique<StructuredCloneHolder>(
+ StructuredCloneHolder::CloningSupported,
+ StructuredCloneHolder::TransferringNotSupported,
+ JS::StructuredCloneScope::SameProcess);
+ serializedOptions->Write(cx, optionsVal, JS::UndefinedHandleValue,
+ cloneDataPolicy, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ /**
+ * 10. Set node’s port to nodePort.
+ */
+ audioWorkletNode->mPort = messageChannel->Port1();
+
+ /**
+ * 11. Let parameterDescriptors be the result of retrieval of nodeName from
+ * node name to parameter descriptor map.
+ */
+ nsTArray<NamedAudioParamTimeline> paramTimelines;
+ audioWorkletNode->InitializeParameters(&paramTimelines, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ auto engine = new WorkletNodeEngine(
+ audioWorkletNode, aAudioContext.Destination(), std::move(paramTimelines),
+ aOptions.mOutputChannelCount);
+ audioWorkletNode->mTrack = AudioNodeTrack::Create(
+ &aAudioContext, engine, AudioNodeTrack::NO_TRACK_FLAGS,
+ aAudioContext.Graph());
+
+ audioWorkletNode->SendParameterData(aOptions.mParameterData);
+
+ /**
+ * 12. Queue a control message to invoke the constructor of the
+ * corresponding AudioWorkletProcessor with the processor construction
+ * data that consists of: nodeName, node, serializedOptions, and
+ * serializedProcessorPort.
+ */
+ Worklet* worklet = aAudioContext.GetAudioWorklet(aRv);
+ MOZ_ASSERT(worklet, "Worklet already existed and so getter shouldn't fail.");
+ auto workletImpl = static_cast<AudioWorkletImpl*>(worklet->Impl());
+ audioWorkletNode->mTrack->SendRunnable(NS_NewRunnableFunction(
+ "WorkletNodeEngine::ConstructProcessor",
+ // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT.
+ // See bug 1535398.
+ //
+ // Note that clang and gcc have mutually incompatible rules about whether
+ // attributes should come before or after the `mutable` keyword here, so
+ // use a compatibility hack until we can switch to the standardized
+ // [[attr]] syntax (bug 1627007).
+#ifdef __clang__
+# define AND_MUTABLE(macro) macro mutable
+#else
+# define AND_MUTABLE(macro) mutable macro
+#endif
+ [track = audioWorkletNode->mTrack,
+ workletImpl = RefPtr<AudioWorkletImpl>(workletImpl),
+ name = nsString(aName), options = std::move(serializedOptions),
+ portId = std::move(processorPortId)]()
+ AND_MUTABLE(MOZ_CAN_RUN_SCRIPT_BOUNDARY) {
+ auto engine = static_cast<WorkletNodeEngine*>(track->Engine());
+ engine->ConstructProcessor(
+ workletImpl, name, WrapNotNull(options.get()), portId, track);
+ }));
+#undef AND_MUTABLE
+
+ // [[active source]] is initially true and so at least the first process()
+ // call will not be skipped when there are no active inputs.
+ audioWorkletNode->MarkActive();
+
+ return audioWorkletNode.forget();
+}
+
+AudioParamMap* AudioWorkletNode::GetParameters(ErrorResult& aRv) {
+ if (!mParameters) {
+ RefPtr<AudioParamMap> parameters = new AudioParamMap(this);
+ nsAutoString name;
+ for (const auto& audioParam : mParams) {
+ audioParam->GetName(name);
+ AudioParamMap_Binding::MaplikeHelpers::Set(parameters, name, *audioParam,
+ aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ }
+ mParameters = std::move(parameters);
+ }
+ return mParameters.get();
+}
+
+void AudioWorkletNode::DispatchProcessorErrorEvent(
+ const ProcessorErrorDetails& aDetails) {
+ TRACE("AudioWorkletNode::DispatchProcessorErrorEvent");
+ if (HasListenersFor(nsGkAtoms::onprocessorerror)) {
+ RootedDictionary<ErrorEventInit> init(RootingCx());
+ init.mMessage = aDetails.mMessage;
+ init.mFilename = aDetails.mFilename;
+ init.mLineno = aDetails.mLineno;
+ init.mColno = aDetails.mColno;
+ RefPtr<ErrorEvent> errorEvent =
+ ErrorEvent::Constructor(this, u"processorerror"_ns, init);
+ MOZ_ASSERT(errorEvent);
+ DispatchTrustedEvent(errorEvent);
+ }
+}
+
+JSObject* AudioWorkletNode::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return AudioWorkletNode_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+size_t AudioWorkletNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+ return amount;
+}
+
+size_t AudioWorkletNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/AudioWorkletNode.h b/dom/media/webaudio/AudioWorkletNode.h
new file mode 100644
index 0000000000..22e089f8cd
--- /dev/null
+++ b/dom/media/webaudio/AudioWorkletNode.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef AudioWorkletNode_h_
+#define AudioWorkletNode_h_
+
+#include "AudioNode.h"
+
+namespace mozilla::dom {
+
+class AudioParamMap;
+struct AudioWorkletNodeOptions;
+class MessagePort;
+struct NamedAudioParamTimeline;
+struct ProcessorErrorDetails;
+template <typename KeyType, typename ValueType>
+class Record;
+
+class AudioWorkletNode : public AudioNode {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AudioWorkletNode, AudioNode)
+
+ IMPL_EVENT_HANDLER(processorerror)
+
+ static already_AddRefed<AudioWorkletNode> Constructor(
+ const GlobalObject& aGlobal, AudioContext& aAudioContext,
+ const nsAString& aName, const AudioWorkletNodeOptions& aOptions,
+ ErrorResult& aRv);
+
+ AudioParamMap* GetParameters(ErrorResult& aRv);
+
+ MessagePort* Port() const { return mPort; };
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // AudioNode methods
+ uint16_t NumberOfInputs() const override { return mInputCount; }
+ uint16_t NumberOfOutputs() const override { return mOutputCount; }
+ const char* NodeType() const override { return "AudioWorkletNode"; }
+ void DispatchProcessorErrorEvent(const ProcessorErrorDetails& aDetails);
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+ private:
+ AudioWorkletNode(AudioContext* aAudioContext, const nsAString& aName,
+ const AudioWorkletNodeOptions& aOptions);
+ ~AudioWorkletNode() = default;
+ void InitializeParameters(nsTArray<NamedAudioParamTimeline>* aParamTimelines,
+ ErrorResult& aRv);
+ void SendParameterData(
+ const Optional<Record<nsString, double>>& aParameterData);
+
+ nsString mNodeName;
+ RefPtr<MessagePort> mPort;
+ RefPtr<AudioParamMap> mParameters;
+ uint16_t mInputCount;
+ uint16_t mOutputCount;
+};
+
+} // namespace mozilla::dom
+
+#endif // AudioWorkletNode_h_
diff --git a/dom/media/webaudio/AudioWorkletProcessor.cpp b/dom/media/webaudio/AudioWorkletProcessor.cpp
new file mode 100644
index 0000000000..194cc5bf97
--- /dev/null
+++ b/dom/media/webaudio/AudioWorkletProcessor.cpp
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "AudioWorkletProcessor.h"
+
+#include "mozilla/dom/AudioWorkletNodeBinding.h"
+#include "mozilla/dom/AudioWorkletProcessorBinding.h"
+#include "mozilla/dom/AudioWorkletGlobalScope.h"
+#include "mozilla/dom/MessagePort.h"
+#include "mozilla/dom/WorkletGlobalScope.h"
+#include "nsIGlobalObject.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(AudioWorkletProcessor, mParent, mPort)
+
+AudioWorkletProcessor::AudioWorkletProcessor(nsIGlobalObject* aParent,
+ MessagePort* aPort)
+ : mParent(aParent), mPort(aPort) {}
+
+AudioWorkletProcessor::~AudioWorkletProcessor() = default;
+
+/* static */
+already_AddRefed<AudioWorkletProcessor> AudioWorkletProcessor::Constructor(
+ const GlobalObject& aGlobal, ErrorResult& aRv) {
+ nsCOMPtr<WorkletGlobalScope> global =
+ do_QueryInterface(aGlobal.GetAsSupports());
+ MOZ_ASSERT(global);
+ RefPtr<MessagePort> port = static_cast<AudioWorkletGlobalScope*>(global.get())
+ ->TakePortForProcessorCtor();
+ if (!port) {
+ aRv.ThrowTypeError<MSG_ILLEGAL_CONSTRUCTOR>();
+ return nullptr;
+ }
+ RefPtr<AudioWorkletProcessor> audioWorkletProcessor =
+ new AudioWorkletProcessor(global, port);
+
+ return audioWorkletProcessor.forget();
+}
+
+JSObject* AudioWorkletProcessor::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return AudioWorkletProcessor_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/AudioWorkletProcessor.h b/dom/media/webaudio/AudioWorkletProcessor.h
new file mode 100644
index 0000000000..3d37d61498
--- /dev/null
+++ b/dom/media/webaudio/AudioWorkletProcessor.h
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef AudioWorkletProcessor_h_
+#define AudioWorkletProcessor_h_
+
+#include "nsCOMPtr.h"
+#include "nsWrapperCache.h"
+
+class nsIGlobalObject;
+
+namespace mozilla {
+
+class ErrorResult;
+
+namespace dom {
+
+struct AudioWorkletNodeOptions;
+class GlobalObject;
+class MessagePort;
+
+class AudioWorkletProcessor final : public nsWrapperCache {
+ public:
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(AudioWorkletProcessor)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(AudioWorkletProcessor)
+
+ static already_AddRefed<AudioWorkletProcessor> Constructor(
+ const GlobalObject& aGlobal, ErrorResult& aRv);
+
+ nsIGlobalObject* GetParentObject() const { return mParent; }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ MessagePort* Port() const { return mPort; };
+
+ private:
+ explicit AudioWorkletProcessor(nsIGlobalObject* aParent, MessagePort* aPort);
+ ~AudioWorkletProcessor();
+ nsCOMPtr<nsIGlobalObject> mParent;
+ RefPtr<MessagePort> mPort;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // AudioWorkletProcessor_h_
diff --git a/dom/media/webaudio/BiquadFilterNode.cpp b/dom/media/webaudio/BiquadFilterNode.cpp
new file mode 100644
index 0000000000..602dbf9cc1
--- /dev/null
+++ b/dom/media/webaudio/BiquadFilterNode.cpp
@@ -0,0 +1,358 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "BiquadFilterNode.h"
+#include <algorithm>
+#include "AlignmentUtils.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeTrack.h"
+#include "AudioDestinationNode.h"
+#include "PlayingRefChangeHandler.h"
+#include "WebAudioUtils.h"
+#include "blink/Biquad.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/ErrorResult.h"
+#include "AudioParamTimeline.h"
+#include "Tracing.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(BiquadFilterNode, AudioNode, mFrequency,
+ mDetune, mQ, mGain)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BiquadFilterNode)
+NS_INTERFACE_MAP_END_INHERITING(AudioNode)
+
+NS_IMPL_ADDREF_INHERITED(BiquadFilterNode, AudioNode)
+NS_IMPL_RELEASE_INHERITED(BiquadFilterNode, AudioNode)
+
+static void SetParamsOnBiquad(WebCore::Biquad& aBiquad, float aSampleRate,
+ BiquadFilterType aType, double aFrequency,
+ double aQ, double aGain, double aDetune) {
+ const double nyquist = aSampleRate * 0.5;
+ double normalizedFrequency = aFrequency / nyquist;
+
+ if (aDetune) {
+ normalizedFrequency *= fdlibm_exp2(aDetune / 1200);
+ }
+
+ switch (aType) {
+ case BiquadFilterType::Lowpass:
+ aBiquad.setLowpassParams(normalizedFrequency, aQ);
+ break;
+ case BiquadFilterType::Highpass:
+ aBiquad.setHighpassParams(normalizedFrequency, aQ);
+ break;
+ case BiquadFilterType::Bandpass:
+ aBiquad.setBandpassParams(normalizedFrequency, aQ);
+ break;
+ case BiquadFilterType::Lowshelf:
+ aBiquad.setLowShelfParams(normalizedFrequency, aGain);
+ break;
+ case BiquadFilterType::Highshelf:
+ aBiquad.setHighShelfParams(normalizedFrequency, aGain);
+ break;
+ case BiquadFilterType::Peaking:
+ aBiquad.setPeakingParams(normalizedFrequency, aQ, aGain);
+ break;
+ case BiquadFilterType::Notch:
+ aBiquad.setNotchParams(normalizedFrequency, aQ);
+ break;
+ case BiquadFilterType::Allpass:
+ aBiquad.setAllpassParams(normalizedFrequency, aQ);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("We should never see the alternate names here");
+ break;
+ }
+}
+
+class BiquadFilterNodeEngine final : public AudioNodeEngine {
+ public:
+ BiquadFilterNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination,
+ uint64_t aWindowID)
+ : AudioNodeEngine(aNode),
+ mDestination(aDestination->Track())
+ // Keep the default values in sync with the default values in
+ // BiquadFilterNode::BiquadFilterNode
+ ,
+ mType(BiquadFilterType::Lowpass),
+ mFrequency(350.f),
+ mDetune(0.f),
+ mQ(1.f),
+ mGain(0.f),
+ mWindowID(aWindowID) {}
+
+ enum Parameters { TYPE, FREQUENCY, DETUNE, Q, GAIN };
+ void SetInt32Parameter(uint32_t aIndex, int32_t aValue) override {
+ switch (aIndex) {
+ case TYPE:
+ mType = static_cast<BiquadFilterType>(aValue);
+ break;
+ default:
+ NS_ERROR("Bad BiquadFilterNode Int32Parameter");
+ }
+ }
+ void RecvTimelineEvent(uint32_t aIndex, AudioParamEvent& aEvent) override {
+ MOZ_ASSERT(mDestination);
+
+ aEvent.ConvertToTicks(mDestination);
+
+ switch (aIndex) {
+ case FREQUENCY:
+ mFrequency.InsertEvent<int64_t>(aEvent);
+ break;
+ case DETUNE:
+ mDetune.InsertEvent<int64_t>(aEvent);
+ break;
+ case Q:
+ mQ.InsertEvent<int64_t>(aEvent);
+ break;
+ case GAIN:
+ mGain.InsertEvent<int64_t>(aEvent);
+ break;
+ default:
+ NS_ERROR("Bad BiquadFilterNodeEngine TimelineParameter");
+ }
+ }
+
+ void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
+ const AudioBlock& aInput, AudioBlock* aOutput,
+ bool* aFinished) override {
+ TRACE("BiquadFilterNode::ProcessBlock");
+ float inputBuffer[WEBAUDIO_BLOCK_SIZE + 4];
+ float* alignedInputBuffer = ALIGNED16(inputBuffer);
+ ASSERT_ALIGNED16(alignedInputBuffer);
+
+ if (aInput.IsNull()) {
+ bool hasTail = false;
+ for (uint32_t i = 0; i < mBiquads.Length(); ++i) {
+ if (mBiquads[i].hasTail()) {
+ hasTail = true;
+ break;
+ }
+ }
+ if (!hasTail) {
+ if (!mBiquads.IsEmpty()) {
+ mBiquads.Clear();
+ aTrack->ScheduleCheckForInactive();
+
+ RefPtr<PlayingRefChangeHandler> refchanged =
+ new PlayingRefChangeHandler(aTrack,
+ PlayingRefChangeHandler::RELEASE);
+ aTrack->Graph()->DispatchToMainThreadStableState(refchanged.forget());
+ }
+
+ aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
+ return;
+ }
+
+ PodArrayZero(inputBuffer);
+
+ } else if (mBiquads.Length() != aInput.ChannelCount()) {
+ if (mBiquads.IsEmpty()) {
+ RefPtr<PlayingRefChangeHandler> refchanged =
+ new PlayingRefChangeHandler(aTrack,
+ PlayingRefChangeHandler::ADDREF);
+ aTrack->Graph()->DispatchToMainThreadStableState(refchanged.forget());
+ } else { // Help people diagnose bug 924718
+ WebAudioUtils::LogToDeveloperConsole(
+ mWindowID, "BiquadFilterChannelCountChangeWarning");
+ }
+
+ // Adjust the number of biquads based on the number of channels
+ mBiquads.SetLength(aInput.ChannelCount());
+ }
+
+ uint32_t numberOfChannels = mBiquads.Length();
+ aOutput->AllocateChannels(numberOfChannels);
+
+ TrackTime pos = mDestination->GraphTimeToTrackTime(aFrom);
+
+ double freq = mFrequency.GetValueAtTime(pos);
+ double q = mQ.GetValueAtTime(pos);
+ double gain = mGain.GetValueAtTime(pos);
+ double detune = mDetune.GetValueAtTime(pos);
+
+ for (uint32_t i = 0; i < numberOfChannels; ++i) {
+ const float* input;
+ if (aInput.IsNull()) {
+ input = alignedInputBuffer;
+ } else {
+ input = static_cast<const float*>(aInput.mChannelData[i]);
+ if (aInput.mVolume != 1.0) {
+ AudioBlockCopyChannelWithScale(input, aInput.mVolume,
+ alignedInputBuffer);
+ input = alignedInputBuffer;
+ }
+ }
+ SetParamsOnBiquad(mBiquads[i], aTrack->mSampleRate, mType, freq, q, gain,
+ detune);
+
+ mBiquads[i].process(input, aOutput->ChannelFloatsForWrite(i),
+ aInput.GetDuration());
+ }
+ }
+
+ bool IsActive() const override { return !mBiquads.IsEmpty(); }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override {
+ // Not owned:
+ // - mDestination - probably not owned
+ // - AudioParamTimelines - counted in the AudioNode
+ size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+ amount += mBiquads.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ return amount;
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ private:
+ RefPtr<AudioNodeTrack> mDestination;
+ BiquadFilterType mType;
+ AudioParamTimeline mFrequency;
+ AudioParamTimeline mDetune;
+ AudioParamTimeline mQ;
+ AudioParamTimeline mGain;
+ nsTArray<WebCore::Biquad> mBiquads;
+ uint64_t mWindowID;
+};
+
+BiquadFilterNode::BiquadFilterNode(AudioContext* aContext)
+ : AudioNode(aContext, 2, ChannelCountMode::Max,
+ ChannelInterpretation::Speakers),
+ mType(BiquadFilterType::Lowpass) {
+ mFrequency = CreateAudioParam(
+ BiquadFilterNodeEngine::FREQUENCY, u"frequency"_ns, 350.f,
+ -(aContext->SampleRate() / 2), aContext->SampleRate() / 2);
+ mDetune = CreateAudioParam(BiquadFilterNodeEngine::DETUNE, u"detune"_ns, 0.f);
+ mQ = CreateAudioParam(BiquadFilterNodeEngine::Q, u"Q"_ns, 1.f);
+ mGain = CreateAudioParam(BiquadFilterNodeEngine::GAIN, u"gain"_ns, 0.f);
+
+ uint64_t windowID = 0;
+ if (aContext->GetParentObject()) {
+ windowID = aContext->GetParentObject()->WindowID();
+ }
+ BiquadFilterNodeEngine* engine =
+ new BiquadFilterNodeEngine(this, aContext->Destination(), windowID);
+ mTrack = AudioNodeTrack::Create(
+ aContext, engine, AudioNodeTrack::NO_TRACK_FLAGS, aContext->Graph());
+}
+
+/* static */
+already_AddRefed<BiquadFilterNode> BiquadFilterNode::Create(
+ AudioContext& aAudioContext, const BiquadFilterOptions& aOptions,
+ ErrorResult& aRv) {
+ RefPtr<BiquadFilterNode> audioNode = new BiquadFilterNode(&aAudioContext);
+
+ audioNode->Initialize(aOptions, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ audioNode->SetType(aOptions.mType);
+ audioNode->Q()->SetInitialValue(aOptions.mQ);
+ audioNode->Detune()->SetInitialValue(aOptions.mDetune);
+ audioNode->Frequency()->SetInitialValue(aOptions.mFrequency);
+ audioNode->Gain()->SetInitialValue(aOptions.mGain);
+
+ return audioNode.forget();
+}
+
+size_t BiquadFilterNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+
+ if (mFrequency) {
+ amount += mFrequency->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ if (mDetune) {
+ amount += mDetune->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ if (mQ) {
+ amount += mQ->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ if (mGain) {
+ amount += mGain->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ return amount;
+}
+
+size_t BiquadFilterNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+JSObject* BiquadFilterNode::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return BiquadFilterNode_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void BiquadFilterNode::SetType(BiquadFilterType aType) {
+ mType = aType;
+ SendInt32ParameterToTrack(BiquadFilterNodeEngine::TYPE,
+ static_cast<int32_t>(aType));
+}
+
+void BiquadFilterNode::GetFrequencyResponse(const Float32Array& aFrequencyHz,
+ const Float32Array& aMagResponse,
+ const Float32Array& aPhaseResponse,
+ ErrorResult& aRv) {
+ UniquePtr<float[]> frequencies;
+ size_t length;
+ const double currentTime = Context()->CurrentTime();
+ aFrequencyHz.ProcessData([&](const Span<float>& aFrequencyData,
+ JS::AutoCheckCannotGC&&) {
+ aMagResponse.ProcessData([&](const Span<float>& aMagData,
+ JS::AutoCheckCannotGC&&) {
+ aPhaseResponse.ProcessData([&](const Span<float>& aPhaseData,
+ JS::AutoCheckCannotGC&&) {
+ length = aFrequencyData.Length();
+ if (length != aMagData.Length() || length != aPhaseData.Length()) {
+ aRv.ThrowInvalidAccessError("Parameter lengths must match");
+ return;
+ }
+
+ if (length == 0) {
+ return;
+ }
+
+ frequencies = MakeUniqueForOverwriteFallible<float[]>(length);
+ if (!frequencies) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ const double nyquist = Context()->SampleRate() * 0.5;
+ std::transform(aFrequencyData.begin(), aFrequencyData.end(),
+ frequencies.get(), [&](float aFrequency) {
+ if (aFrequency >= 0 && aFrequency <= nyquist) {
+ return static_cast<float>(aFrequency / nyquist);
+ }
+
+ return std::numeric_limits<float>::quiet_NaN();
+ });
+
+ double freq = mFrequency->GetValueAtTime(currentTime);
+ double q = mQ->GetValueAtTime(currentTime);
+ double gain = mGain->GetValueAtTime(currentTime);
+ double detune = mDetune->GetValueAtTime(currentTime);
+
+ WebCore::Biquad biquad;
+ SetParamsOnBiquad(biquad, Context()->SampleRate(), mType, freq, q, gain,
+ detune);
+ biquad.getFrequencyResponse(int(length), frequencies.get(),
+ aMagData.Elements(), aPhaseData.Elements());
+ });
+ });
+ });
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/BiquadFilterNode.h b/dom/media/webaudio/BiquadFilterNode.h
new file mode 100644
index 0000000000..31b08c00c8
--- /dev/null
+++ b/dom/media/webaudio/BiquadFilterNode.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef BiquadFilterNode_h_
+#define BiquadFilterNode_h_
+
+#include "AudioNode.h"
+#include "AudioParam.h"
+#include "mozilla/dom/BiquadFilterNodeBinding.h"
+
+namespace mozilla::dom {
+
+class AudioContext;
+struct BiquadFilterOptions;
+
+class BiquadFilterNode final : public AudioNode {
+ public:
+ static already_AddRefed<BiquadFilterNode> Create(
+ AudioContext& aAudioContext, const BiquadFilterOptions& aOptions,
+ ErrorResult& aRv);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BiquadFilterNode, AudioNode)
+
+ static already_AddRefed<BiquadFilterNode> Constructor(
+ const GlobalObject& aGlobal, AudioContext& aAudioContext,
+ const BiquadFilterOptions& aOptions, ErrorResult& aRv) {
+ return Create(aAudioContext, aOptions, aRv);
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ BiquadFilterType Type() const { return mType; }
+ void SetType(BiquadFilterType aType);
+
+ AudioParam* Frequency() const { return mFrequency; }
+
+ AudioParam* Detune() const { return mDetune; }
+
+ AudioParam* Q() const { return mQ; }
+
+ AudioParam* Gain() const { return mGain; }
+
+ void GetFrequencyResponse(const Float32Array& aFrequencyHz,
+ const Float32Array& aMagResponse,
+ const Float32Array& aPhaseResponse,
+ ErrorResult& aRv);
+
+ const char* NodeType() const override { return "BiquadFilterNode"; }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+ private:
+ explicit BiquadFilterNode(AudioContext* aContext);
+ ~BiquadFilterNode() = default;
+
+ BiquadFilterType mType;
+ RefPtr<AudioParam> mFrequency;
+ RefPtr<AudioParam> mDetune;
+ RefPtr<AudioParam> mQ;
+ RefPtr<AudioParam> mGain;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/media/webaudio/ChannelMergerNode.cpp b/dom/media/webaudio/ChannelMergerNode.cpp
new file mode 100644
index 0000000000..4ffae02591
--- /dev/null
+++ b/dom/media/webaudio/ChannelMergerNode.cpp
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ChannelMergerNode.h"
+#include "mozilla/dom/ChannelMergerNodeBinding.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeTrack.h"
+#include "nsPrintfCString.h"
+#include "Tracing.h"
+
+namespace mozilla::dom {
+
+class ChannelMergerNodeEngine final : public AudioNodeEngine {
+ public:
+ explicit ChannelMergerNodeEngine(ChannelMergerNode* aNode)
+ : AudioNodeEngine(aNode) {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ void ProcessBlocksOnPorts(AudioNodeTrack* aTrack, GraphTime aFrom,
+ Span<const AudioBlock> aInput,
+ Span<AudioBlock> aOutput,
+ bool* aFinished) override {
+ MOZ_ASSERT(aInput.Length() == InputCount());
+ MOZ_ASSERT(aOutput.Length() == 1, "Should have only one output port");
+ TRACE("ChannelMergerNodeEngine::ProcessBlocksOnPorts");
+
+ // Get the number of output channels, and allocate it
+ size_t channelCount = InputCount();
+ bool allNull = true;
+ for (size_t i = 0; i < channelCount; ++i) {
+ allNull &= aInput[i].IsNull();
+ }
+ if (allNull) {
+ aOutput[0].SetNull(WEBAUDIO_BLOCK_SIZE);
+ return;
+ }
+
+ aOutput[0].AllocateChannels(channelCount);
+
+ for (size_t i = 0; i < channelCount; ++i) {
+ float* output = aOutput[0].ChannelFloatsForWrite(i);
+ if (aInput[i].IsNull()) {
+ PodZero(output, WEBAUDIO_BLOCK_SIZE);
+ } else {
+ AudioBlockCopyChannelWithScale(
+ static_cast<const float*>(aInput[i].mChannelData[0]),
+ aInput[i].mVolume, output);
+ }
+ }
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+};
+
+ChannelMergerNode::ChannelMergerNode(AudioContext* aContext,
+ uint16_t aInputCount)
+ : AudioNode(aContext, 1, ChannelCountMode::Explicit,
+ ChannelInterpretation::Speakers),
+ mInputCount(aInputCount) {
+ mTrack =
+ AudioNodeTrack::Create(aContext, new ChannelMergerNodeEngine(this),
+ AudioNodeTrack::NO_TRACK_FLAGS, aContext->Graph());
+}
+
+/* static */
+already_AddRefed<ChannelMergerNode> ChannelMergerNode::Create(
+ AudioContext& aAudioContext, const ChannelMergerOptions& aOptions,
+ ErrorResult& aRv) {
+ if (aOptions.mNumberOfInputs == 0 ||
+ aOptions.mNumberOfInputs > WebAudioUtils::MaxChannelCount) {
+ aRv.ThrowIndexSizeError(
+ nsPrintfCString("Number of inputs (%u) must be in the range [1, number "
+ "of supported channels]",
+ aOptions.mNumberOfInputs));
+ return nullptr;
+ }
+
+ RefPtr<ChannelMergerNode> audioNode =
+ new ChannelMergerNode(&aAudioContext, aOptions.mNumberOfInputs);
+
+ audioNode->Initialize(aOptions, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return audioNode.forget();
+}
+
+JSObject* ChannelMergerNode::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return ChannelMergerNode_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/ChannelMergerNode.h b/dom/media/webaudio/ChannelMergerNode.h
new file mode 100644
index 0000000000..92327c8b1e
--- /dev/null
+++ b/dom/media/webaudio/ChannelMergerNode.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ChannelMergerNode_h_
+#define ChannelMergerNode_h_
+
+#include "AudioNode.h"
+
+namespace mozilla::dom {
+
+class AudioContext;
+struct ChannelMergerOptions;
+
+class ChannelMergerNode final : public AudioNode {
+ public:
+ static already_AddRefed<ChannelMergerNode> Create(
+ AudioContext& aAudioContext, const ChannelMergerOptions& aOptions,
+ ErrorResult& aRv);
+
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(ChannelMergerNode, AudioNode)
+
+ static already_AddRefed<ChannelMergerNode> Constructor(
+ const GlobalObject& aGlobal, AudioContext& aAudioContext,
+ const ChannelMergerOptions& aOptions, ErrorResult& aRv) {
+ return Create(aAudioContext, aOptions, aRv);
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ uint16_t NumberOfInputs() const override { return mInputCount; }
+
+ const char* NodeType() const override { return "ChannelMergerNode"; }
+
+ virtual void SetChannelCount(uint32_t aChannelCount,
+ ErrorResult& aRv) override {
+ if (aChannelCount != 1) {
+ aRv.ThrowInvalidStateError(
+ "Cannot change channel count of ChannelMergerNode");
+ }
+ }
+
+ virtual void SetChannelCountModeValue(ChannelCountMode aMode,
+ ErrorResult& aRv) override {
+ if (aMode != ChannelCountMode::Explicit) {
+ aRv.ThrowInvalidStateError(
+ "Cannot change channel count mode of ChannelMergerNode");
+ }
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ private:
+ ChannelMergerNode(AudioContext* aContext, uint16_t aInputCount);
+ ~ChannelMergerNode() = default;
+
+ const uint16_t mInputCount;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/media/webaudio/ChannelSplitterNode.cpp b/dom/media/webaudio/ChannelSplitterNode.cpp
new file mode 100644
index 0000000000..52d3ae948f
--- /dev/null
+++ b/dom/media/webaudio/ChannelSplitterNode.cpp
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/ChannelSplitterNode.h"
+#include "mozilla/dom/ChannelSplitterNodeBinding.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeTrack.h"
+#include "Tracing.h"
+
+namespace mozilla::dom {
+
+class ChannelSplitterNodeEngine final : public AudioNodeEngine {
+ public:
+ explicit ChannelSplitterNodeEngine(ChannelSplitterNode* aNode)
+ : AudioNodeEngine(aNode) {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ void ProcessBlocksOnPorts(AudioNodeTrack* aTrack, GraphTime aFrom,
+ Span<const AudioBlock> aInput,
+ Span<AudioBlock> aOutput,
+ bool* aFinished) override {
+ MOZ_ASSERT(aInput.Length() == 1, "Should only have one input port");
+ MOZ_ASSERT(aOutput.Length() == OutputCount());
+ TRACE("ChannelSplitterNodeEngine::ProcessBlocksOnPorts");
+
+ for (uint16_t i = 0; i < OutputCount(); ++i) {
+ if (i < aInput[0].ChannelCount()) {
+ // Split out existing channels
+ aOutput[i].AllocateChannels(1);
+ AudioBlockCopyChannelWithScale(
+ static_cast<const float*>(aInput[0].mChannelData[i]),
+ aInput[0].mVolume, aOutput[i].ChannelFloatsForWrite(0));
+ } else {
+ // Pad with silent channels if needed
+ aOutput[i].SetNull(WEBAUDIO_BLOCK_SIZE);
+ }
+ }
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+};
+
+ChannelSplitterNode::ChannelSplitterNode(AudioContext* aContext,
+ uint16_t aOutputCount)
+ : AudioNode(aContext, aOutputCount, ChannelCountMode::Explicit,
+ ChannelInterpretation::Discrete),
+ mOutputCount(aOutputCount) {
+ mTrack =
+ AudioNodeTrack::Create(aContext, new ChannelSplitterNodeEngine(this),
+ AudioNodeTrack::NO_TRACK_FLAGS, aContext->Graph());
+}
+
+/* static */
+already_AddRefed<ChannelSplitterNode> ChannelSplitterNode::Create(
+ AudioContext& aAudioContext, const ChannelSplitterOptions& aOptions,
+ ErrorResult& aRv) {
+ if (aOptions.mNumberOfOutputs == 0 ||
+ aOptions.mNumberOfOutputs > WebAudioUtils::MaxChannelCount) {
+ aRv.ThrowIndexSizeError(nsPrintfCString(
+ "%u is not a valid number of outputs", aOptions.mNumberOfOutputs));
+ return nullptr;
+ }
+
+ RefPtr<ChannelSplitterNode> audioNode =
+ new ChannelSplitterNode(&aAudioContext, aOptions.mNumberOfOutputs);
+
+ // Manually check that the other options are valid, this node has
+ // channelCount, channelCountMode and channelInterpretation constraints: they
+ // cannot be changed from the default.
+ if (aOptions.mChannelCount.WasPassed()) {
+ audioNode->SetChannelCount(aOptions.mChannelCount.Value(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ }
+ if (aOptions.mChannelInterpretation.WasPassed()) {
+ audioNode->SetChannelInterpretationValue(
+ aOptions.mChannelInterpretation.Value(), aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ }
+ if (aOptions.mChannelCountMode.WasPassed()) {
+ audioNode->SetChannelCountModeValue(aOptions.mChannelCountMode.Value(),
+ aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ }
+
+ return audioNode.forget();
+}
+
+JSObject* ChannelSplitterNode::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return ChannelSplitterNode_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/ChannelSplitterNode.h b/dom/media/webaudio/ChannelSplitterNode.h
new file mode 100644
index 0000000000..c13eaa8f22
--- /dev/null
+++ b/dom/media/webaudio/ChannelSplitterNode.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ChannelSplitterNode_h_
+#define ChannelSplitterNode_h_
+
+#include "AudioNode.h"
+
+namespace mozilla::dom {
+
+class AudioContext;
+struct ChannelSplitterOptions;
+
+class ChannelSplitterNode final : public AudioNode {
+ public:
+ static already_AddRefed<ChannelSplitterNode> Create(
+ AudioContext& aAudioContext, const ChannelSplitterOptions& aOptions,
+ ErrorResult& aRv);
+
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(ChannelSplitterNode, AudioNode)
+
+ static already_AddRefed<ChannelSplitterNode> Constructor(
+ const GlobalObject& aGlobal, AudioContext& aAudioContext,
+ const ChannelSplitterOptions& aOptions, ErrorResult& aRv) {
+ return Create(aAudioContext, aOptions, aRv);
+ }
+
+ void SetChannelCount(uint32_t aChannelCount, ErrorResult& aRv) override {
+ if (aChannelCount != ChannelCount()) {
+ aRv.ThrowInvalidStateError(
+ "Cannot change channel count of ChannelSplitterNode");
+ }
+ }
+ void SetChannelCountModeValue(ChannelCountMode aMode,
+ ErrorResult& aRv) override {
+ if (aMode != ChannelCountModeValue()) {
+ aRv.ThrowInvalidStateError(
+ "Cannot change channel count mode of ChannelSplitterNode");
+ }
+ }
+ void SetChannelInterpretationValue(ChannelInterpretation aMode,
+ ErrorResult& aRv) override {
+ if (aMode != ChannelInterpretationValue()) {
+ aRv.ThrowInvalidStateError(
+ "Cannot change channel interpretation of ChannelSplitterNode");
+ }
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ uint16_t NumberOfOutputs() const override { return mOutputCount; }
+
+ const char* NodeType() const override { return "ChannelSplitterNode"; }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ private:
+ ChannelSplitterNode(AudioContext* aContext, uint16_t aOutputCount);
+ ~ChannelSplitterNode() = default;
+
+ const uint16_t mOutputCount;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/media/webaudio/ConstantSourceNode.cpp b/dom/media/webaudio/ConstantSourceNode.cpp
new file mode 100644
index 0000000000..6e74c3e219
--- /dev/null
+++ b/dom/media/webaudio/ConstantSourceNode.cpp
@@ -0,0 +1,279 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ConstantSourceNode.h"
+
+#include "AudioDestinationNode.h"
+#include "nsContentUtils.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeTrack.h"
+#include "Tracing.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(ConstantSourceNode, AudioScheduledSourceNode,
+ mOffset)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ConstantSourceNode)
+NS_INTERFACE_MAP_END_INHERITING(AudioScheduledSourceNode)
+
+NS_IMPL_ADDREF_INHERITED(ConstantSourceNode, AudioScheduledSourceNode)
+NS_IMPL_RELEASE_INHERITED(ConstantSourceNode, AudioScheduledSourceNode)
+
+class ConstantSourceNodeEngine final : public AudioNodeEngine {
+ public:
+ ConstantSourceNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination)
+ : AudioNodeEngine(aNode),
+ mSource(nullptr),
+ mDestination(aDestination->Track()),
+ mStart(-1),
+ mStop(TRACK_TIME_MAX)
+ // Keep the default values in sync with
+ // ConstantSourceNode::ConstantSourceNode.
+ ,
+ mOffset(1.0f) {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ void SetSourceTrack(AudioNodeTrack* aSource) { mSource = aSource; }
+
+ enum Parameters {
+ OFFSET,
+ START,
+ STOP,
+ };
+ void RecvTimelineEvent(uint32_t aIndex, AudioParamEvent& aEvent) override {
+ MOZ_ASSERT(mDestination);
+
+ aEvent.ConvertToTicks(mDestination);
+
+ switch (aIndex) {
+ case OFFSET:
+ mOffset.InsertEvent<int64_t>(aEvent);
+ break;
+ default:
+ NS_ERROR("Bad ConstantSourceNodeEngine TimelineParameter");
+ }
+ }
+
+ void SetTrackTimeParameter(uint32_t aIndex, TrackTime aParam) override {
+ switch (aIndex) {
+ case START:
+ mStart = aParam;
+ mSource->SetActive();
+ break;
+ case STOP:
+ mStop = aParam;
+ break;
+ default:
+ NS_ERROR("Bad ConstantSourceNodeEngine TrackTimeParameter");
+ }
+ }
+
+ void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
+ const AudioBlock& aInput, AudioBlock* aOutput,
+ bool* aFinished) override {
+ MOZ_ASSERT(mSource == aTrack, "Invalid source track");
+ TRACE("ConstantSourceNodeEngine::ProcessBlock");
+
+ TrackTime ticks = mDestination->GraphTimeToTrackTime(aFrom);
+ if (mStart == -1) {
+ aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
+ return;
+ }
+
+ if (ticks + WEBAUDIO_BLOCK_SIZE <= mStart || ticks >= mStop ||
+ mStop <= mStart) {
+ aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
+ } else {
+ aOutput->AllocateChannels(1);
+ float* output = aOutput->ChannelFloatsForWrite(0);
+ uint32_t writeOffset = 0;
+
+ if (ticks < mStart) {
+ MOZ_ASSERT(mStart - ticks <= WEBAUDIO_BLOCK_SIZE);
+ uint32_t count = mStart - ticks;
+ std::fill_n(output, count, 0.0f);
+ writeOffset += count;
+ }
+
+ MOZ_ASSERT(ticks + writeOffset >= mStart);
+ MOZ_ASSERT(mStop - ticks >= writeOffset);
+ uint32_t count =
+ std::min<TrackTime>(WEBAUDIO_BLOCK_SIZE, mStop - ticks) - writeOffset;
+
+ if (mOffset.HasSimpleValue()) {
+ float value = mOffset.GetValue();
+ std::fill_n(output + writeOffset, count, value);
+ } else {
+ mOffset.GetValuesAtTime(ticks + writeOffset, output + writeOffset,
+ count);
+ }
+
+ writeOffset += count;
+
+ std::fill_n(output + writeOffset, WEBAUDIO_BLOCK_SIZE - writeOffset,
+ 0.0f);
+ }
+
+ if (ticks + WEBAUDIO_BLOCK_SIZE >= mStop) {
+ // We've finished playing.
+ *aFinished = true;
+ }
+ }
+
+ bool IsActive() const override {
+ // start() has been called.
+ return mStart != -1;
+ }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override {
+ size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+
+ // Not owned:
+ // - mSource
+ // - mDestination
+ // - mOffset (internal ref owned by node)
+
+ return amount;
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ // mSource deletes the engine in its destructor.
+ AudioNodeTrack* MOZ_NON_OWNING_REF mSource;
+ RefPtr<AudioNodeTrack> mDestination;
+ TrackTime mStart;
+ TrackTime mStop;
+ AudioParamTimeline mOffset;
+};
+
+ConstantSourceNode::ConstantSourceNode(AudioContext* aContext)
+ : AudioScheduledSourceNode(aContext, 2, ChannelCountMode::Max,
+ ChannelInterpretation::Speakers),
+ mStartCalled(false) {
+ mOffset =
+ CreateAudioParam(ConstantSourceNodeEngine::OFFSET, u"offset"_ns, 1.0f);
+ ConstantSourceNodeEngine* engine =
+ new ConstantSourceNodeEngine(this, aContext->Destination());
+ mTrack = AudioNodeTrack::Create(aContext, engine,
+ AudioNodeTrack::NEED_MAIN_THREAD_ENDED,
+ aContext->Graph());
+ engine->SetSourceTrack(mTrack);
+ mTrack->AddMainThreadListener(this);
+}
+
+ConstantSourceNode::~ConstantSourceNode() = default;
+
+size_t ConstantSourceNode::SizeOfExcludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+
+ amount += mOffset->SizeOfIncludingThis(aMallocSizeOf);
+ return amount;
+}
+
+size_t ConstantSourceNode::SizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+JSObject* ConstantSourceNode::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return ConstantSourceNode_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+already_AddRefed<ConstantSourceNode> ConstantSourceNode::Constructor(
+ const GlobalObject& aGlobal, AudioContext& aContext,
+ const ConstantSourceOptions& aOptions) {
+ RefPtr<ConstantSourceNode> object = new ConstantSourceNode(&aContext);
+ object->mOffset->SetInitialValue(aOptions.mOffset);
+ return object.forget();
+}
+
+void ConstantSourceNode::DestroyMediaTrack() {
+ if (mTrack) {
+ mTrack->RemoveMainThreadListener(this);
+ }
+ AudioNode::DestroyMediaTrack();
+}
+
+void ConstantSourceNode::Start(double aWhen, ErrorResult& aRv) {
+ if (!WebAudioUtils::IsTimeValid(aWhen)) {
+ aRv.ThrowRangeError<MSG_VALUE_OUT_OF_RANGE>("start time");
+ return;
+ }
+
+ if (mStartCalled) {
+ aRv.ThrowInvalidStateError("Can't call start() more than once");
+ return;
+ }
+ mStartCalled = true;
+
+ if (!mTrack) {
+ return;
+ }
+
+ mTrack->SetTrackTimeParameter(ConstantSourceNodeEngine::START, Context(),
+ aWhen);
+
+ MarkActive();
+ Context()->StartBlockedAudioContextIfAllowed();
+}
+
+void ConstantSourceNode::Stop(double aWhen, ErrorResult& aRv) {
+ if (!WebAudioUtils::IsTimeValid(aWhen)) {
+ aRv.ThrowRangeError<MSG_VALUE_OUT_OF_RANGE>("stop time");
+ return;
+ }
+
+ if (!mStartCalled) {
+ aRv.ThrowInvalidStateError("Can't call stop() without calling start()");
+ return;
+ }
+
+ if (!mTrack || !Context()) {
+ return;
+ }
+
+ mTrack->SetTrackTimeParameter(ConstantSourceNodeEngine::STOP, Context(),
+ std::max(0.0, aWhen));
+}
+
+void ConstantSourceNode::NotifyMainThreadTrackEnded() {
+ MOZ_ASSERT(mTrack->IsEnded());
+
+ class EndedEventDispatcher final : public Runnable {
+ public:
+ explicit EndedEventDispatcher(ConstantSourceNode* aNode)
+ : mozilla::Runnable("EndedEventDispatcher"), mNode(aNode) {}
+ NS_IMETHOD Run() override {
+ // If it's not safe to run scripts right now, schedule this to run later
+ if (!nsContentUtils::IsSafeToRunScript()) {
+ nsContentUtils::AddScriptRunner(this);
+ return NS_OK;
+ }
+
+ mNode->DispatchTrustedEvent(u"ended"_ns);
+ // Release track resources.
+ mNode->DestroyMediaTrack();
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<ConstantSourceNode> mNode;
+ };
+
+ Context()->Dispatch(do_AddRef(new EndedEventDispatcher(this)));
+
+ // Drop the playing reference
+ // Warning: The below line might delete this.
+ MarkInactive();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/ConstantSourceNode.h b/dom/media/webaudio/ConstantSourceNode.h
new file mode 100644
index 0000000000..61f939a7e6
--- /dev/null
+++ b/dom/media/webaudio/ConstantSourceNode.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ConstantSourceNode_h_
+#define ConstantSourceNode_h_
+
+#include "AudioScheduledSourceNode.h"
+#include "AudioParam.h"
+#include "mozilla/dom/ConstantSourceNodeBinding.h"
+
+namespace mozilla::dom {
+
+class AudioContext;
+
+class ConstantSourceNode final : public AudioScheduledSourceNode,
+ public MainThreadMediaTrackListener {
+ public:
+ explicit ConstantSourceNode(AudioContext* aContext);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ConstantSourceNode,
+ AudioScheduledSourceNode)
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ static already_AddRefed<ConstantSourceNode> Constructor(
+ const GlobalObject& aGlobal, AudioContext& aContext,
+ const ConstantSourceOptions& aOptions);
+
+ void DestroyMediaTrack() override;
+
+ uint16_t NumberOfInputs() const final { return 0; }
+
+ AudioParam* Offset() const { return mOffset; }
+
+ void Start(double aWhen, ErrorResult& rv) override;
+ void Stop(double aWhen, ErrorResult& rv) override;
+
+ void NotifyMainThreadTrackEnded() override;
+
+ const char* NodeType() const override { return "ConstantSourceNode"; }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+ protected:
+ virtual ~ConstantSourceNode();
+
+ private:
+ RefPtr<AudioParam> mOffset;
+ bool mStartCalled;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/media/webaudio/ConvolverNode.cpp b/dom/media/webaudio/ConvolverNode.cpp
new file mode 100644
index 0000000000..65562ae6d0
--- /dev/null
+++ b/dom/media/webaudio/ConvolverNode.cpp
@@ -0,0 +1,479 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ConvolverNode.h"
+#include "mozilla/dom/ConvolverNodeBinding.h"
+#include "AlignmentUtils.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeTrack.h"
+#include "blink/Reverb.h"
+#include "PlayingRefChangeHandler.h"
+#include "Tracing.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(ConvolverNode, AudioNode, mBuffer)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ConvolverNode)
+NS_INTERFACE_MAP_END_INHERITING(AudioNode)
+
+NS_IMPL_ADDREF_INHERITED(ConvolverNode, AudioNode)
+NS_IMPL_RELEASE_INHERITED(ConvolverNode, AudioNode)
+
+class ConvolverNodeEngine final : public AudioNodeEngine {
+ typedef PlayingRefChangeHandler PlayingRefChanged;
+
+ public:
+ ConvolverNodeEngine(AudioNode* aNode, bool aNormalize)
+ : AudioNodeEngine(aNode) {}
+
+ // Indicates how the right output channel is generated.
+ enum class RightConvolverMode {
+ // A right convolver is always used when there is more than one impulse
+ // response channel.
+ Always,
+ // With a single response channel, the mode may be either Direct or
+ // Difference. The decision on which to use is made when stereo input is
+ // received. Once the right convolver is in use, convolver state is
+ // suitable only for the selected mode, and so the mode cannot change
+ // until the right convolver contains only silent history.
+ //
+ // With Direct mode, each convolver processes a corresponding channel.
+ // This mode is selected when input is initially stereo or
+ // channelInterpretation is "discrete" at the time or starting the right
+ // convolver when input changes from non-silent mono to stereo.
+ Direct,
+ // Difference mode is selected if channelInterpretation is "speakers" at
+ // the time starting the right convolver when the input changes from mono
+ // to stereo.
+ //
+ // When non-silent input is initially mono, with a single response
+ // channel, the right output channel is not produced until input becomes
+ // stereo. Only a single convolver is used for mono processing. When
+ // stereo input arrives after mono input, output must be as if the mono
+ // signal remaining in the left convolver is up-mixed, but the right
+ // convolver has not been initialized with the history of the mono input.
+ // Copying the state of the left convolver into the right convolver is not
+ // desirable, because there is considerable state to copy, and the
+ // different convolvers are intended to process out of phase, which means
+ // that state from one convolver would not directly map to state in
+ // another convolver.
+ //
+ // Instead the distributive property of convolution is used to generate
+ // the right output channel using information in the left output channel.
+ // Using l and r to denote the left and right channel input signals, g the
+ // impulse response, and * convolution, the convolution of the right
+ // channel can be given by
+ //
+ // r * g = (l + (r - l)) * g
+ // = l * g + (r - l) * g
+ //
+ // The left convolver continues to process the left channel l to produce
+ // l * g. The right convolver processes the difference of input channel
+ // signals r - l to produce (r - l) * g. The outputs of the two
+ // convolvers are added to generate the right channel output r * g.
+ //
+ // The benefit of doing this is that the history of the r - l input for a
+ // "speakers" up-mixed mono signal is zero, and so an empty convolver
+ // already has exactly the right history for mixing the previous mono
+ // signal with the new stereo signal.
+ Difference
+ };
+
+ void SetReverb(WebCore::Reverb* aReverb,
+ uint32_t aImpulseChannelCount) override {
+ mRemainingLeftOutput = INT32_MIN;
+ mRemainingRightOutput = 0;
+ mRemainingRightHistory = 0;
+
+ // Assume for now that convolution of channel difference is not required.
+ // Direct may change to Difference during processing.
+ if (aReverb) {
+ mRightConvolverMode = aImpulseChannelCount == 1
+ ? RightConvolverMode::Direct
+ : RightConvolverMode::Always;
+ } else {
+ mRightConvolverMode = RightConvolverMode::Always;
+ }
+
+ mReverb.reset(aReverb);
+ }
+
+ void AllocateReverbInput(const AudioBlock& aInput,
+ uint32_t aTotalChannelCount) {
+ uint32_t inputChannelCount = aInput.ChannelCount();
+ MOZ_ASSERT(inputChannelCount <= aTotalChannelCount);
+ mReverbInput.AllocateChannels(aTotalChannelCount);
+ // Pre-multiply the input's volume
+ for (uint32_t i = 0; i < inputChannelCount; ++i) {
+ const float* src = static_cast<const float*>(aInput.mChannelData[i]);
+ float* dest = mReverbInput.ChannelFloatsForWrite(i);
+ AudioBlockCopyChannelWithScale(src, aInput.mVolume, dest);
+ }
+ // Fill remaining channels with silence
+ for (uint32_t i = inputChannelCount; i < aTotalChannelCount; ++i) {
+ float* dest = mReverbInput.ChannelFloatsForWrite(i);
+ std::fill_n(dest, WEBAUDIO_BLOCK_SIZE, 0.0f);
+ }
+ }
+
+ void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
+ const AudioBlock& aInput, AudioBlock* aOutput,
+ bool* aFinished) override;
+
+ bool IsActive() const override { return mRemainingLeftOutput != INT32_MIN; }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override {
+ size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+
+ amount += mReverbInput.SizeOfExcludingThis(aMallocSizeOf, false);
+
+ if (mReverb) {
+ amount += mReverb->sizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ return amount;
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ private:
+ // Keeping mReverbInput across process calls avoids unnecessary reallocation.
+ AudioBlock mReverbInput;
+ UniquePtr<WebCore::Reverb> mReverb;
+ // Tracks samples of the tail remaining to be output. INT32_MIN is a
+ // special value to indicate that the end of any previous tail has been
+ // handled.
+ int32_t mRemainingLeftOutput = INT32_MIN;
+ // mRemainingRightOutput and mRemainingRightHistory are only used when
+ // mRightOutputMode != Always. There is no special handling required at the
+ // end of tail times and so INT32_MIN is not used.
+ // mRemainingRightOutput tracks how much longer this node needs to continue
+ // to produce a right output channel.
+ int32_t mRemainingRightOutput = 0;
+ // mRemainingRightHistory tracks how much silent input would be required to
+ // drain the right convolver, which may sometimes be longer than the period
+ // a right output channel is required.
+ int32_t mRemainingRightHistory = 0;
+ RightConvolverMode mRightConvolverMode = RightConvolverMode::Always;
+};
+
+static void AddScaledLeftToRight(AudioBlock* aBlock, float aScale) {
+ const float* left = static_cast<const float*>(aBlock->mChannelData[0]);
+ float* right = aBlock->ChannelFloatsForWrite(1);
+ AudioBlockAddChannelWithScale(left, aScale, right);
+}
+
+void ConvolverNodeEngine::ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
+ const AudioBlock& aInput,
+ AudioBlock* aOutput, bool* aFinished) {
+ TRACE("ConvolverNodeEngine::ProcessBlock");
+ if (!mReverb) {
+ aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
+ return;
+ }
+
+ uint32_t inputChannelCount = aInput.ChannelCount();
+ if (aInput.IsNull()) {
+ if (mRemainingLeftOutput > 0) {
+ mRemainingLeftOutput -= WEBAUDIO_BLOCK_SIZE;
+ AllocateReverbInput(aInput, 1); // floats for silence
+ } else {
+ if (mRemainingLeftOutput != INT32_MIN) {
+ mRemainingLeftOutput = INT32_MIN;
+ MOZ_ASSERT(mRemainingRightOutput <= 0);
+ MOZ_ASSERT(mRemainingRightHistory <= 0);
+ aTrack->ScheduleCheckForInactive();
+ RefPtr<PlayingRefChanged> refchanged =
+ new PlayingRefChanged(aTrack, PlayingRefChanged::RELEASE);
+ aTrack->Graph()->DispatchToMainThreadStableState(refchanged.forget());
+ }
+ aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
+ return;
+ }
+ } else {
+ if (mRemainingLeftOutput <= 0) {
+ RefPtr<PlayingRefChanged> refchanged =
+ new PlayingRefChanged(aTrack, PlayingRefChanged::ADDREF);
+ aTrack->Graph()->DispatchToMainThreadStableState(refchanged.forget());
+ }
+
+ // Use mVolume as a flag to detect whether AllocateReverbInput() gets
+ // called.
+ mReverbInput.mVolume = 0.0f;
+
+ // Special handling of input channel count changes is used when there is
+ // only a single impulse response channel. See RightConvolverMode.
+ if (mRightConvolverMode != RightConvolverMode::Always) {
+ ChannelInterpretation channelInterpretation =
+ aTrack->GetChannelInterpretation();
+ if (inputChannelCount == 2) {
+ if (mRemainingRightHistory <= 0) {
+ // Will start the second convolver. Choose to convolve the right
+ // channel directly if there is no left tail to up-mix or up-mixing
+ // is "discrete".
+ mRightConvolverMode =
+ (mRemainingLeftOutput <= 0 ||
+ channelInterpretation == ChannelInterpretation::Discrete)
+ ? RightConvolverMode::Direct
+ : RightConvolverMode::Difference;
+ }
+ // The extra WEBAUDIO_BLOCK_SIZE is subtracted below.
+ mRemainingRightOutput =
+ mReverb->impulseResponseLength() + WEBAUDIO_BLOCK_SIZE;
+ mRemainingRightHistory = mRemainingRightOutput;
+ if (mRightConvolverMode == RightConvolverMode::Difference) {
+ AllocateReverbInput(aInput, 2);
+ // Subtract left from right.
+ AddScaledLeftToRight(&mReverbInput, -1.0f);
+ }
+ } else if (mRemainingRightHistory > 0) {
+ // There is one channel of input, but a second convolver also
+ // requires input. Up-mix appropriately for the second convolver.
+ if ((mRightConvolverMode == RightConvolverMode::Difference) ^
+ (channelInterpretation == ChannelInterpretation::Discrete)) {
+ MOZ_ASSERT(
+ (mRightConvolverMode == RightConvolverMode::Difference &&
+ channelInterpretation == ChannelInterpretation::Speakers) ||
+ (mRightConvolverMode == RightConvolverMode::Direct &&
+ channelInterpretation == ChannelInterpretation::Discrete));
+ // The state is one of the following combinations:
+ // 1) Difference and speakers.
+ // Up-mixing gives r = l.
+ // The input to the second convolver is r - l.
+ // 2) Direct and discrete.
+ // Up-mixing gives r = 0.
+ // The input to the second convolver is r.
+ //
+ // In each case the input for the second convolver is silence, which
+ // will drain the convolver.
+ AllocateReverbInput(aInput, 2);
+ } else {
+ if (channelInterpretation == ChannelInterpretation::Discrete) {
+ MOZ_ASSERT(mRightConvolverMode == RightConvolverMode::Difference);
+ // channelInterpretation has changed since the second convolver
+ // was added. "discrete" up-mixing of input would produce a
+ // silent right channel r = 0, but the second convolver needs
+ // r - l for RightConvolverMode::Difference.
+ AllocateReverbInput(aInput, 2);
+ AddScaledLeftToRight(&mReverbInput, -1.0f);
+ } else {
+ MOZ_ASSERT(channelInterpretation ==
+ ChannelInterpretation::Speakers);
+ MOZ_ASSERT(mRightConvolverMode == RightConvolverMode::Direct);
+ // The Reverb will essentially up-mix the single input channel by
+ // feeding it into both convolvers.
+ }
+ // The second convolver does not have silent input, and so it will
+ // not drain. It will need to continue processing up-mixed input
+ // because the next input block may be stereo, which would be mixed
+ // with the signal remaining in the convolvers.
+ // The extra WEBAUDIO_BLOCK_SIZE is subtracted below.
+ mRemainingRightHistory =
+ mReverb->impulseResponseLength() + WEBAUDIO_BLOCK_SIZE;
+ }
+ }
+ }
+
+ if (mReverbInput.mVolume == 0.0f) { // not yet set
+ if (aInput.mVolume != 1.0f) {
+ AllocateReverbInput(aInput, inputChannelCount); // pre-multiply
+ } else {
+ mReverbInput = aInput;
+ }
+ }
+
+ mRemainingLeftOutput = mReverb->impulseResponseLength();
+ MOZ_ASSERT(mRemainingLeftOutput > 0);
+ }
+
+ // "The ConvolverNode produces a mono output only in the single case where
+ // there is a single input channel and a single-channel buffer."
+ uint32_t outputChannelCount = 2;
+ uint32_t reverbOutputChannelCount = 2;
+ if (mRightConvolverMode != RightConvolverMode::Always) {
+ // When the input changes from stereo to mono, the output continues to be
+ // stereo for the length of the tail time, during which the two channels
+ // may differ.
+ if (mRemainingRightOutput > 0) {
+ MOZ_ASSERT(mRemainingRightHistory > 0);
+ mRemainingRightOutput -= WEBAUDIO_BLOCK_SIZE;
+ } else {
+ outputChannelCount = 1;
+ }
+ // The second convolver keeps processing until it drains.
+ if (mRemainingRightHistory > 0) {
+ mRemainingRightHistory -= WEBAUDIO_BLOCK_SIZE;
+ } else {
+ reverbOutputChannelCount = 1;
+ }
+ }
+
+ // If there are two convolvers, then they each need an output buffer, even
+ // if the second convolver is only processing to keep history of up-mixed
+ // input.
+ aOutput->AllocateChannels(reverbOutputChannelCount);
+
+ mReverb->process(&mReverbInput, aOutput);
+
+ if (mRightConvolverMode == RightConvolverMode::Difference &&
+ outputChannelCount == 2) {
+ // Add left to right.
+ AddScaledLeftToRight(aOutput, 1.0f);
+ } else {
+ // Trim if outputChannelCount < reverbOutputChannelCount
+ aOutput->mChannelData.TruncateLength(outputChannelCount);
+ }
+}
+
+ConvolverNode::ConvolverNode(AudioContext* aContext)
+ : AudioNode(aContext, 2, ChannelCountMode::Clamped_max,
+ ChannelInterpretation::Speakers),
+ mNormalize(true) {
+ ConvolverNodeEngine* engine = new ConvolverNodeEngine(this, mNormalize);
+ mTrack = AudioNodeTrack::Create(
+ aContext, engine, AudioNodeTrack::NO_TRACK_FLAGS, aContext->Graph());
+}
+
+/* static */
+already_AddRefed<ConvolverNode> ConvolverNode::Create(
+ JSContext* aCx, AudioContext& aAudioContext,
+ const ConvolverOptions& aOptions, ErrorResult& aRv) {
+ RefPtr<ConvolverNode> audioNode = new ConvolverNode(&aAudioContext);
+
+ audioNode->Initialize(aOptions, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ // This must be done before setting the buffer.
+ audioNode->SetNormalize(!aOptions.mDisableNormalization);
+
+ if (aOptions.mBuffer.WasPassed()) {
+ MOZ_ASSERT(aCx);
+ audioNode->SetBuffer(aCx, aOptions.mBuffer.Value(), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ }
+
+ return audioNode.forget();
+}
+
+size_t ConvolverNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+ if (mBuffer) {
+ // NB: mBuffer might be shared with the associated engine, by convention
+ // the AudioNode will report.
+ amount += mBuffer->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ return amount;
+}
+
+size_t ConvolverNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+JSObject* ConvolverNode::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return ConvolverNode_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void ConvolverNode::SetBuffer(JSContext* aCx, AudioBuffer* aBuffer,
+ ErrorResult& aRv) {
+ if (aBuffer) {
+ switch (aBuffer->NumberOfChannels()) {
+ case 1:
+ case 2:
+ case 4:
+ // Supported number of channels
+ break;
+ default:
+ aRv.ThrowNotSupportedError(
+ nsPrintfCString("%u is not a supported number of channels",
+ aBuffer->NumberOfChannels()));
+ return;
+ }
+ }
+
+ if (aBuffer && (aBuffer->SampleRate() != Context()->SampleRate())) {
+ aRv.ThrowNotSupportedError(nsPrintfCString(
+ "Buffer sample rate (%g) does not match AudioContext sample rate (%g)",
+ aBuffer->SampleRate(), Context()->SampleRate()));
+ return;
+ }
+
+ // Send the buffer to the track
+ AudioNodeTrack* ns = mTrack;
+ MOZ_ASSERT(ns, "Why don't we have a track here?");
+ if (aBuffer) {
+ AudioChunk data = aBuffer->GetThreadSharedChannelsForRate(aCx);
+ if (data.mBufferFormat == AUDIO_FORMAT_S16) {
+ // Reverb expects data in float format.
+ // Convert on the main thread so as to minimize allocations on the audio
+ // thread.
+ // Reverb will dispose of the buffer once initialized, so convert here
+ // and leave the smaller arrays in the AudioBuffer.
+ // There is currently no value in providing 16/32-byte aligned data
+ // because PadAndMakeScaledDFT() will copy the data (without SIMD
+ // instructions) to aligned arrays for the FFT.
+ CheckedInt<size_t> bufferSize(sizeof(float));
+ bufferSize *= data.mDuration;
+ bufferSize *= data.ChannelCount();
+ RefPtr<SharedBuffer> floatBuffer =
+ SharedBuffer::Create(bufferSize, fallible);
+ if (!floatBuffer) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ auto floatData = static_cast<float*>(floatBuffer->Data());
+ for (size_t i = 0; i < data.ChannelCount(); ++i) {
+ ConvertAudioSamples(data.ChannelData<int16_t>()[i], floatData,
+ data.mDuration);
+ data.mChannelData[i] = floatData;
+ floatData += data.mDuration;
+ }
+ data.mBuffer = std::move(floatBuffer);
+ data.mBufferFormat = AUDIO_FORMAT_FLOAT32;
+ } else if (data.mBufferFormat == AUDIO_FORMAT_SILENCE) {
+ // This is valid, but a signal convolved by a silent signal is silent, set
+ // the reverb to nullptr and return.
+ ns->SetReverb(nullptr, 0);
+ mBuffer = aBuffer;
+ return;
+ }
+
+ // Note about empirical tuning (this is copied from Blink)
+ // The maximum FFT size affects reverb performance and accuracy.
+ // If the reverb is single-threaded and processes entirely in the real-time
+ // audio thread, it's important not to make this too high. In this case
+ // 8192 is a good value. But, the Reverb object is multi-threaded, so we
+ // want this as high as possible without losing too much accuracy. Very
+ // large FFTs will have worse phase errors. Given these constraints 32768 is
+ // a good compromise.
+ const size_t MaxFFTSize = 32768;
+
+ bool allocationFailure = false;
+ UniquePtr<WebCore::Reverb> reverb(new WebCore::Reverb(
+ data, MaxFFTSize, !Context()->IsOffline(), mNormalize,
+ aBuffer->SampleRate(), &allocationFailure));
+ if (!allocationFailure) {
+ ns->SetReverb(reverb.release(), data.ChannelCount());
+ } else {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ } else {
+ ns->SetReverb(nullptr, 0);
+ }
+ mBuffer = aBuffer;
+}
+
+void ConvolverNode::SetNormalize(bool aNormalize) { mNormalize = aNormalize; }
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/ConvolverNode.h b/dom/media/webaudio/ConvolverNode.h
new file mode 100644
index 0000000000..897f8d6547
--- /dev/null
+++ b/dom/media/webaudio/ConvolverNode.h
@@ -0,0 +1,77 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ConvolverNode_h_
+#define ConvolverNode_h_
+
+#include "AudioNode.h"
+#include "AudioBuffer.h"
+#include "nsPrintfCString.h"
+
+namespace mozilla::dom {
+
+class AudioContext;
+struct ConvolverOptions;
+
+class ConvolverNode final : public AudioNode {
+ public:
+ static already_AddRefed<ConvolverNode> Create(
+ JSContext* aCx, AudioContext& aAudioContext,
+ const ConvolverOptions& aOptions, ErrorResult& aRv);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ConvolverNode, AudioNode);
+
+ static already_AddRefed<ConvolverNode> Constructor(
+ const GlobalObject& aGlobal, AudioContext& aAudioContext,
+ const ConvolverOptions& aOptions, ErrorResult& aRv) {
+ return Create(aGlobal.Context(), aAudioContext, aOptions, aRv);
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ AudioBuffer* GetBuffer(JSContext* aCx) const { return mBuffer; }
+
+ void SetBuffer(JSContext* aCx, AudioBuffer* aBuffer, ErrorResult& aRv);
+
+ bool Normalize() const { return mNormalize; }
+
+ void SetNormalize(bool aNormal);
+
+ void SetChannelCount(uint32_t aChannelCount, ErrorResult& aRv) override {
+ if (aChannelCount > 2) {
+ aRv.ThrowNotSupportedError(
+ nsPrintfCString("%u is greater than 2", aChannelCount));
+ return;
+ }
+ AudioNode::SetChannelCount(aChannelCount, aRv);
+ }
+ void SetChannelCountModeValue(ChannelCountMode aMode,
+ ErrorResult& aRv) override {
+ if (aMode == ChannelCountMode::Max) {
+ aRv.ThrowNotSupportedError("Cannot set channel count mode to \"max\"");
+ return;
+ }
+ AudioNode::SetChannelCountModeValue(aMode, aRv);
+ }
+
+ const char* NodeType() const override { return "ConvolverNode"; }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+ private:
+ explicit ConvolverNode(AudioContext* aContext);
+ ~ConvolverNode() = default;
+
+ RefPtr<AudioBuffer> mBuffer;
+ bool mNormalize;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/media/webaudio/DelayBuffer.cpp b/dom/media/webaudio/DelayBuffer.cpp
new file mode 100644
index 0000000000..52c8d4184b
--- /dev/null
+++ b/dom/media/webaudio/DelayBuffer.cpp
@@ -0,0 +1,236 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DelayBuffer.h"
+
+#include "mozilla/PodOperations.h"
+#include "AudioChannelFormat.h"
+#include "AudioNodeEngine.h"
+
+namespace mozilla {
+
+size_t DelayBuffer::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t amount = 0;
+ amount += mChunks.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (size_t i = 0; i < mChunks.Length(); i++) {
+ amount += mChunks[i].SizeOfExcludingThis(aMallocSizeOf, false);
+ }
+
+ amount += mUpmixChannels.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ return amount;
+}
+
+void DelayBuffer::Write(const AudioBlock& aInputChunk) {
+ // We must have a reference to the buffer if there are channels
+ MOZ_ASSERT(aInputChunk.IsNull() == !aInputChunk.ChannelCount());
+#ifdef DEBUG
+ MOZ_ASSERT(!mHaveWrittenBlock);
+ mHaveWrittenBlock = true;
+#endif
+
+ if (!EnsureBuffer()) {
+ return;
+ }
+
+ if (mCurrentChunk == mLastReadChunk) {
+ mLastReadChunk = -1; // invalidate cache
+ }
+ mChunks[mCurrentChunk] = aInputChunk.AsAudioChunk();
+}
+
+void DelayBuffer::Read(const float aPerFrameDelays[WEBAUDIO_BLOCK_SIZE],
+ AudioBlock* aOutputChunk,
+ ChannelInterpretation aChannelInterpretation) {
+ int chunkCount = mChunks.Length();
+ if (!chunkCount) {
+ aOutputChunk->SetNull(WEBAUDIO_BLOCK_SIZE);
+ return;
+ }
+
+ // Find the maximum number of contributing channels to determine the output
+ // channel count that retains all signal information. Buffered blocks will
+ // be upmixed if necessary.
+ //
+ // First find the range of "delay" offsets backwards from the current
+ // position. Note that these may be negative for frames that are after the
+ // current position (including i).
+ float minDelay = aPerFrameDelays[0];
+ float maxDelay = minDelay;
+ for (unsigned i = 1; i < WEBAUDIO_BLOCK_SIZE; ++i) {
+ minDelay = std::min(minDelay, aPerFrameDelays[i] - i);
+ maxDelay = std::max(maxDelay, aPerFrameDelays[i] - i);
+ }
+
+ // Now find the chunks touched by this range and check their channel counts.
+ int oldestChunk = ChunkForDelay(std::ceil(maxDelay));
+ int youngestChunk = ChunkForDelay(std::floor(minDelay));
+
+ uint32_t channelCount = 0;
+ for (int i = oldestChunk; true; i = (i + 1) % chunkCount) {
+ channelCount =
+ GetAudioChannelsSuperset(channelCount, mChunks[i].ChannelCount());
+ if (i == youngestChunk) {
+ break;
+ }
+ }
+
+ if (channelCount) {
+ aOutputChunk->AllocateChannels(channelCount);
+ ReadChannels(aPerFrameDelays, aOutputChunk, 0, channelCount,
+ aChannelInterpretation);
+ } else {
+ aOutputChunk->SetNull(WEBAUDIO_BLOCK_SIZE);
+ }
+}
+
+void DelayBuffer::ReadChannel(const float aPerFrameDelays[WEBAUDIO_BLOCK_SIZE],
+ AudioBlock* aOutputChunk, uint32_t aChannel,
+ ChannelInterpretation aChannelInterpretation) {
+ if (!mChunks.Length()) {
+ float* outputChannel = aOutputChunk->ChannelFloatsForWrite(aChannel);
+ PodZero(outputChannel, WEBAUDIO_BLOCK_SIZE);
+ return;
+ }
+
+ ReadChannels(aPerFrameDelays, aOutputChunk, aChannel, 1,
+ aChannelInterpretation);
+}
+
+void DelayBuffer::ReadChannels(const float aPerFrameDelays[WEBAUDIO_BLOCK_SIZE],
+ AudioBlock* aOutputChunk, uint32_t aFirstChannel,
+ uint32_t aNumChannelsToRead,
+ ChannelInterpretation aChannelInterpretation) {
+ uint32_t totalChannelCount = aOutputChunk->ChannelCount();
+ uint32_t readChannelsEnd = aFirstChannel + aNumChannelsToRead;
+ MOZ_ASSERT(readChannelsEnd <= totalChannelCount);
+
+ if (mUpmixChannels.Length() != totalChannelCount) {
+ mLastReadChunk = -1; // invalidate cache
+ }
+
+ for (uint32_t channel = aFirstChannel; channel < readChannelsEnd; ++channel) {
+ PodZero(aOutputChunk->ChannelFloatsForWrite(channel), WEBAUDIO_BLOCK_SIZE);
+ }
+
+ for (unsigned i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) {
+ float currentDelay = aPerFrameDelays[i];
+ MOZ_ASSERT(currentDelay >= 0.0f);
+ MOZ_ASSERT(currentDelay <= (mChunks.Length() - 1) * WEBAUDIO_BLOCK_SIZE);
+
+ // Interpolate two input frames in case the read position does not match
+ // an integer index.
+ // Use the larger delay, for the older frame, first, as this is more
+ // likely to use the cached upmixed channel arrays.
+ int floorDelay = int(currentDelay);
+ float interpolationFactor = currentDelay - floorDelay;
+ int positions[2];
+ positions[1] = PositionForDelay(floorDelay) + i;
+ positions[0] = positions[1] - 1;
+
+ for (unsigned tick = 0; tick < ArrayLength(positions); ++tick) {
+ int readChunk = ChunkForPosition(positions[tick]);
+ // The zero check on interpolationFactor is important because, when
+ // currentDelay is integer, positions[0] may be outside the range
+ // considered for determining totalChannelCount.
+ // mVolume is not set on default initialized chunks so also handle null
+ // chunks specially.
+ if (interpolationFactor != 0.0f && !mChunks[readChunk].IsNull()) {
+ int readOffset = OffsetForPosition(positions[tick]);
+ UpdateUpmixChannels(readChunk, totalChannelCount,
+ aChannelInterpretation);
+ float multiplier = interpolationFactor * mChunks[readChunk].mVolume;
+ for (uint32_t channel = aFirstChannel; channel < readChannelsEnd;
+ ++channel) {
+ aOutputChunk->ChannelFloatsForWrite(channel)[i] +=
+ multiplier * mUpmixChannels[channel][readOffset];
+ }
+ }
+
+ interpolationFactor = 1.0f - interpolationFactor;
+ }
+ }
+}
+
+void DelayBuffer::Read(float aDelayTicks, AudioBlock* aOutputChunk,
+ ChannelInterpretation aChannelInterpretation) {
+ float computedDelay[WEBAUDIO_BLOCK_SIZE];
+
+ for (unsigned i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) {
+ computedDelay[i] = aDelayTicks;
+ }
+
+ Read(computedDelay, aOutputChunk, aChannelInterpretation);
+}
+
+bool DelayBuffer::EnsureBuffer() {
+ if (mChunks.Length() == 0) {
+ // The length of the buffer is at least one block greater than the maximum
+ // delay so that writing an input block does not overwrite the block that
+ // would subsequently be read at maximum delay. Also round up to the next
+ // block size, so that no block of writes will need to wrap.
+ const int chunkCount = (mMaxDelayTicks + 2 * WEBAUDIO_BLOCK_SIZE - 1) >>
+ WEBAUDIO_BLOCK_SIZE_BITS;
+ if (!mChunks.SetLength(chunkCount, fallible)) {
+ return false;
+ }
+
+ mLastReadChunk = -1;
+ }
+ return true;
+}
+
+int DelayBuffer::PositionForDelay(int aDelay) {
+ // Adding mChunks.Length() keeps integers positive for defined and
+ // appropriate bitshift, remainder, and bitwise operations.
+ return ((mCurrentChunk + mChunks.Length()) * WEBAUDIO_BLOCK_SIZE) - aDelay;
+}
+
+int DelayBuffer::ChunkForPosition(int aPosition) {
+ MOZ_ASSERT(aPosition >= 0);
+ return (aPosition >> WEBAUDIO_BLOCK_SIZE_BITS) % mChunks.Length();
+}
+
+int DelayBuffer::OffsetForPosition(int aPosition) {
+ MOZ_ASSERT(aPosition >= 0);
+ return aPosition & (WEBAUDIO_BLOCK_SIZE - 1);
+}
+
+int DelayBuffer::ChunkForDelay(int aDelay) {
+ return ChunkForPosition(PositionForDelay(aDelay));
+}
+
+void DelayBuffer::UpdateUpmixChannels(
+ int aNewReadChunk, uint32_t aChannelCount,
+ ChannelInterpretation aChannelInterpretation) {
+ if (aNewReadChunk == mLastReadChunk) {
+ MOZ_ASSERT(mUpmixChannels.Length() == aChannelCount);
+ return;
+ }
+
+ NS_WARNING_ASSERTION(mHaveWrittenBlock || aNewReadChunk != mCurrentChunk,
+ "Smoothing is making feedback delay too small.");
+
+ mLastReadChunk = aNewReadChunk;
+ mUpmixChannels.ClearAndRetainStorage();
+ mUpmixChannels.AppendElements(mChunks[aNewReadChunk].ChannelData<float>());
+ MOZ_ASSERT(mUpmixChannels.Length() <= aChannelCount);
+ if (mUpmixChannels.Length() < aChannelCount) {
+ if (aChannelInterpretation == ChannelInterpretation::Speakers) {
+ AudioChannelsUpMix(&mUpmixChannels, aChannelCount,
+ SilentChannel::ZeroChannel<float>());
+ MOZ_ASSERT(mUpmixChannels.Length() == aChannelCount,
+ "We called GetAudioChannelsSuperset to avoid this");
+ } else {
+ // Fill up the remaining channels with zeros
+ for (uint32_t channel = mUpmixChannels.Length(); channel < aChannelCount;
+ ++channel) {
+ mUpmixChannels.AppendElement(SilentChannel::ZeroChannel<float>());
+ }
+ }
+ }
+}
+
+} // namespace mozilla
diff --git a/dom/media/webaudio/DelayBuffer.h b/dom/media/webaudio/DelayBuffer.h
new file mode 100644
index 0000000000..4b1135fa56
--- /dev/null
+++ b/dom/media/webaudio/DelayBuffer.h
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DelayBuffer_h_
+#define DelayBuffer_h_
+
+#include "nsTArray.h"
+#include "AudioBlock.h"
+#include "AudioSegment.h"
+#include "mozilla/dom/AudioNodeBinding.h" // for ChannelInterpretation
+
+namespace mozilla {
+
+class DelayBuffer final {
+ typedef dom::ChannelInterpretation ChannelInterpretation;
+
+ public:
+ explicit DelayBuffer(float aMaxDelayTicks)
+ // Round the maximum delay up to the next tick.
+ : mMaxDelayTicks(std::ceil(aMaxDelayTicks)),
+ mCurrentChunk(0)
+ // mLastReadChunk is initialized in EnsureBuffer
+#ifdef DEBUG
+ ,
+ mHaveWrittenBlock(false)
+#endif
+ {
+ // The 180 second limit in AudioContext::CreateDelay() and the
+ // 1 << MEDIA_TIME_FRAC_BITS limit on sample rate provide a limit on the
+ // maximum delay.
+ MOZ_ASSERT(aMaxDelayTicks <=
+ float(std::numeric_limits<decltype(mMaxDelayTicks)>::max()));
+ }
+
+ // Write a WEBAUDIO_BLOCK_SIZE block for aChannelCount channels.
+ void Write(const AudioBlock& aInputChunk);
+
+ // Read a block with an array of delays, in ticks, for each sample frame.
+ // Each delay should be >= 0 and <= MaxDelayTicks().
+ void Read(const float aPerFrameDelays[WEBAUDIO_BLOCK_SIZE],
+ AudioBlock* aOutputChunk,
+ ChannelInterpretation aChannelInterpretation);
+ // Read a block with a constant delay. The delay should be >= 0 and
+ // <= MaxDelayTicks().
+ void Read(float aDelayTicks, AudioBlock* aOutputChunk,
+ ChannelInterpretation aChannelInterpretation);
+
+ // Read into one of the channels of aOutputChunk, given an array of
+ // delays in ticks. This is useful when delays are different on different
+ // channels. aOutputChunk must have already been allocated with at least as
+ // many channels as were in any of the blocks passed to Write().
+ void ReadChannel(const float aPerFrameDelays[WEBAUDIO_BLOCK_SIZE],
+ AudioBlock* aOutputChunk, uint32_t aChannel,
+ ChannelInterpretation aChannelInterpretation);
+
+ // Advance the buffer pointer
+ void NextBlock() {
+ mCurrentChunk = (mCurrentChunk + 1) % mChunks.Length();
+#ifdef DEBUG
+ MOZ_ASSERT(mHaveWrittenBlock);
+ mHaveWrittenBlock = false;
+#endif
+ }
+
+ void Reset() { mChunks.Clear(); };
+
+ int MaxDelayTicks() const { return mMaxDelayTicks; }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
+
+ private:
+ void ReadChannels(const float aPerFrameDelays[WEBAUDIO_BLOCK_SIZE],
+ AudioBlock* aOutputChunk, uint32_t aFirstChannel,
+ uint32_t aNumChannelsToRead,
+ ChannelInterpretation aChannelInterpretation);
+ bool EnsureBuffer();
+ int PositionForDelay(int aDelay);
+ int ChunkForPosition(int aPosition);
+ int OffsetForPosition(int aPosition);
+ int ChunkForDelay(int aDelay);
+ void UpdateUpmixChannels(int aNewReadChunk, uint32_t channelCount,
+ ChannelInterpretation aChannelInterpretation);
+
+ // Circular buffer for capturing delayed samples.
+ FallibleTArray<AudioChunk> mChunks;
+ // Cached upmixed channel arrays, to avoid repeated allocations.
+ CopyableAutoTArray<const float*, GUESS_AUDIO_CHANNELS> mUpmixChannels;
+ // Maximum delay, in ticks
+ int mMaxDelayTicks;
+ // The current position in the circular buffer. The next write will be to
+ // this chunk, and the next read may begin before this chunk.
+ int mCurrentChunk;
+ // The chunk owning the pointers in mUpmixChannels
+ int mLastReadChunk;
+#ifdef DEBUG
+ bool mHaveWrittenBlock;
+#endif
+};
+
+} // namespace mozilla
+
+#endif // DelayBuffer_h_
diff --git a/dom/media/webaudio/DelayNode.cpp b/dom/media/webaudio/DelayNode.cpp
new file mode 100644
index 0000000000..c531756794
--- /dev/null
+++ b/dom/media/webaudio/DelayNode.cpp
@@ -0,0 +1,223 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DelayNode.h"
+#include "mozilla/dom/DelayNodeBinding.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeTrack.h"
+#include "AudioDestinationNode.h"
+#include "WebAudioUtils.h"
+#include "DelayBuffer.h"
+#include "PlayingRefChangeHandler.h"
+#include "Tracing.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(DelayNode, AudioNode, mDelay)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DelayNode)
+NS_INTERFACE_MAP_END_INHERITING(AudioNode)
+
+NS_IMPL_ADDREF_INHERITED(DelayNode, AudioNode)
+NS_IMPL_RELEASE_INHERITED(DelayNode, AudioNode)
+
+class DelayNodeEngine final : public AudioNodeEngine {
+ typedef PlayingRefChangeHandler PlayingRefChanged;
+
+ public:
+ DelayNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination,
+ float aMaxDelayTicks)
+ : AudioNodeEngine(aNode),
+ mDestination(aDestination->Track())
+ // Keep the default value in sync with the default value in
+ // DelayNode::DelayNode.
+ ,
+ mDelay(0.f)
+ // Use a smoothing range of 20ms
+ ,
+ mBuffer(
+ std::max(aMaxDelayTicks, static_cast<float>(WEBAUDIO_BLOCK_SIZE))),
+ mMaxDelay(aMaxDelayTicks),
+ mHaveProducedBeforeInput(false),
+ mLeftOverData(INT32_MIN) {}
+
+ DelayNodeEngine* AsDelayNodeEngine() override { return this; }
+
+ enum Parameters {
+ DELAY,
+ };
+ void RecvTimelineEvent(uint32_t aIndex, AudioParamEvent& aEvent) override {
+ MOZ_ASSERT(mDestination);
+ aEvent.ConvertToTicks(mDestination);
+
+ switch (aIndex) {
+ case DELAY:
+ mDelay.InsertEvent<int64_t>(aEvent);
+ break;
+ default:
+ NS_ERROR("Bad DelayNodeEngine TimelineParameter");
+ }
+ }
+
+ void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
+ const AudioBlock& aInput, AudioBlock* aOutput,
+ bool* aFinished) override {
+ MOZ_ASSERT(aTrack->mSampleRate == mDestination->mSampleRate);
+ TRACE("DelayNodeEngine::ProcessBlock");
+
+ if (!aInput.IsSilentOrSubnormal()) {
+ if (mLeftOverData <= 0) {
+ RefPtr<PlayingRefChanged> refchanged =
+ new PlayingRefChanged(aTrack, PlayingRefChanged::ADDREF);
+ aTrack->Graph()->DispatchToMainThreadStableState(refchanged.forget());
+ }
+ mLeftOverData = mBuffer.MaxDelayTicks();
+ } else if (mLeftOverData > 0) {
+ mLeftOverData -= WEBAUDIO_BLOCK_SIZE;
+ } else {
+ if (mLeftOverData != INT32_MIN) {
+ mLeftOverData = INT32_MIN;
+ aTrack->ScheduleCheckForInactive();
+
+ // Delete our buffered data now we no longer need it
+ mBuffer.Reset();
+
+ RefPtr<PlayingRefChanged> refchanged =
+ new PlayingRefChanged(aTrack, PlayingRefChanged::RELEASE);
+ aTrack->Graph()->DispatchToMainThreadStableState(refchanged.forget());
+ }
+ aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
+ return;
+ }
+
+ mBuffer.Write(aInput);
+
+ // Skip output update if mLastChunks has already been set by
+ // ProduceBlockBeforeInput() when in a cycle.
+ if (!mHaveProducedBeforeInput) {
+ UpdateOutputBlock(aTrack, aFrom, aOutput, 0.0);
+ }
+ mHaveProducedBeforeInput = false;
+ mBuffer.NextBlock();
+ }
+
+ void UpdateOutputBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
+ AudioBlock* aOutput, float minDelay) {
+ float maxDelay = mMaxDelay;
+ float sampleRate = aTrack->mSampleRate;
+ ChannelInterpretation channelInterpretation =
+ aTrack->GetChannelInterpretation();
+ if (mDelay.HasSimpleValue()) {
+ // If this DelayNode is in a cycle, make sure the delay value is at least
+ // one block, even if that is greater than maxDelay.
+ float delayFrames = mDelay.GetValue() * sampleRate;
+ float delayFramesClamped =
+ std::max(minDelay, std::min(delayFrames, maxDelay));
+ mBuffer.Read(delayFramesClamped, aOutput, channelInterpretation);
+ } else {
+ // Compute the delay values for the duration of the input AudioChunk
+ // If this DelayNode is in a cycle, make sure the delay value is at least
+ // one block.
+ TrackTime tick = mDestination->GraphTimeToTrackTime(aFrom);
+ float values[WEBAUDIO_BLOCK_SIZE];
+ mDelay.GetValuesAtTime(tick, values, WEBAUDIO_BLOCK_SIZE);
+
+ float computedDelay[WEBAUDIO_BLOCK_SIZE];
+ for (size_t counter = 0; counter < WEBAUDIO_BLOCK_SIZE; ++counter) {
+ float delayAtTick = values[counter] * sampleRate;
+ float delayAtTickClamped =
+ std::max(minDelay, std::min(delayAtTick, maxDelay));
+ computedDelay[counter] = delayAtTickClamped;
+ }
+ mBuffer.Read(computedDelay, aOutput, channelInterpretation);
+ }
+ }
+
+ void ProduceBlockBeforeInput(AudioNodeTrack* aTrack, GraphTime aFrom,
+ AudioBlock* aOutput) override {
+ if (mLeftOverData <= 0) {
+ aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
+ } else {
+ UpdateOutputBlock(aTrack, aFrom, aOutput, WEBAUDIO_BLOCK_SIZE);
+ }
+ mHaveProducedBeforeInput = true;
+ }
+
+ bool IsActive() const override { return mLeftOverData != INT32_MIN; }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override {
+ size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+ // Not owned:
+ // - mDestination - probably not owned
+ // - mDelay - shares ref with AudioNode, don't count
+ amount += mBuffer.SizeOfExcludingThis(aMallocSizeOf);
+ return amount;
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ RefPtr<AudioNodeTrack> mDestination;
+ AudioParamTimeline mDelay;
+ DelayBuffer mBuffer;
+ float mMaxDelay;
+ bool mHaveProducedBeforeInput;
+ // How much data we have in our buffer which needs to be flushed out when our
+ // inputs finish.
+ int32_t mLeftOverData;
+};
+
+DelayNode::DelayNode(AudioContext* aContext, double aMaxDelay)
+ : AudioNode(aContext, 2, ChannelCountMode::Max,
+ ChannelInterpretation::Speakers) {
+ mDelay = CreateAudioParam(DelayNodeEngine::DELAY, u"delayTime"_ns, 0.0f, 0.f,
+ aMaxDelay);
+ DelayNodeEngine* engine = new DelayNodeEngine(
+ this, aContext->Destination(), aContext->SampleRate() * aMaxDelay);
+ mTrack = AudioNodeTrack::Create(
+ aContext, engine, AudioNodeTrack::NO_TRACK_FLAGS, aContext->Graph());
+}
+
+/* static */
+already_AddRefed<DelayNode> DelayNode::Create(AudioContext& aAudioContext,
+ const DelayOptions& aOptions,
+ ErrorResult& aRv) {
+ if (aOptions.mMaxDelayTime <= 0. || aOptions.mMaxDelayTime >= 180.) {
+ aRv.ThrowNotSupportedError(
+ nsPrintfCString("\"maxDelayTime\" (%g) is not in the range (0,180)",
+ aOptions.mMaxDelayTime));
+ return nullptr;
+ }
+
+ RefPtr<DelayNode> audioNode =
+ new DelayNode(&aAudioContext, aOptions.mMaxDelayTime);
+
+ audioNode->Initialize(aOptions, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ audioNode->DelayTime()->SetInitialValue(aOptions.mDelayTime);
+ return audioNode.forget();
+}
+
+size_t DelayNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+ amount += mDelay->SizeOfIncludingThis(aMallocSizeOf);
+ return amount;
+}
+
+size_t DelayNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+JSObject* DelayNode::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return DelayNode_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/DelayNode.h b/dom/media/webaudio/DelayNode.h
new file mode 100644
index 0000000000..bdfe9eba68
--- /dev/null
+++ b/dom/media/webaudio/DelayNode.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DelayNode_h_
+#define DelayNode_h_
+
+#include "AudioNode.h"
+#include "AudioParam.h"
+
+namespace mozilla::dom {
+
+class AudioContext;
+struct DelayOptions;
+
+class DelayNode final : public AudioNode {
+ public:
+ static already_AddRefed<DelayNode> Create(AudioContext& aAudioContext,
+ const DelayOptions& aOptions,
+ ErrorResult& aRv);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DelayNode, AudioNode)
+
+ static already_AddRefed<DelayNode> Constructor(const GlobalObject& aGlobal,
+ AudioContext& aAudioContext,
+ const DelayOptions& aOptions,
+ ErrorResult& aRv) {
+ return Create(aAudioContext, aOptions, aRv);
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ AudioParam* DelayTime() const { return mDelay; }
+
+ const char* NodeType() const override { return "DelayNode"; }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+ private:
+ DelayNode(AudioContext* aContext, double aMaxDelay);
+ ~DelayNode() = default;
+
+ friend class DelayNodeEngine;
+
+ RefPtr<AudioParam> mDelay;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/media/webaudio/DynamicsCompressorNode.cpp b/dom/media/webaudio/DynamicsCompressorNode.cpp
new file mode 100644
index 0000000000..7ec27d9495
--- /dev/null
+++ b/dom/media/webaudio/DynamicsCompressorNode.cpp
@@ -0,0 +1,226 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DynamicsCompressorNode.h"
+#include "mozilla/dom/DynamicsCompressorNodeBinding.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeTrack.h"
+#include "AudioDestinationNode.h"
+#include "WebAudioUtils.h"
+#include "blink/DynamicsCompressor.h"
+#include "Tracing.h"
+
+using WebCore::DynamicsCompressor;
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(DynamicsCompressorNode, AudioNode,
+ mThreshold, mKnee, mRatio, mAttack, mRelease)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DynamicsCompressorNode)
+NS_INTERFACE_MAP_END_INHERITING(AudioNode)
+
+NS_IMPL_ADDREF_INHERITED(DynamicsCompressorNode, AudioNode)
+NS_IMPL_RELEASE_INHERITED(DynamicsCompressorNode, AudioNode)
+
+class DynamicsCompressorNodeEngine final : public AudioNodeEngine {
+ public:
+ explicit DynamicsCompressorNodeEngine(AudioNode* aNode,
+ AudioDestinationNode* aDestination)
+ : AudioNodeEngine(aNode),
+ mDestination(aDestination->Track())
+ // Keep the default value in sync with the default value in
+ // DynamicsCompressorNode::DynamicsCompressorNode.
+ ,
+ mThreshold(-24.f),
+ mKnee(30.f),
+ mRatio(12.f),
+ mAttack(0.003f),
+ mRelease(0.25f),
+ mCompressor(new DynamicsCompressor(mDestination->mSampleRate, 2)) {}
+
+ enum Parameters { THRESHOLD, KNEE, RATIO, ATTACK, RELEASE };
+ void RecvTimelineEvent(uint32_t aIndex, AudioParamEvent& aEvent) override {
+ MOZ_ASSERT(mDestination);
+
+ aEvent.ConvertToTicks(mDestination);
+
+ switch (aIndex) {
+ case THRESHOLD:
+ mThreshold.InsertEvent<int64_t>(aEvent);
+ break;
+ case KNEE:
+ mKnee.InsertEvent<int64_t>(aEvent);
+ break;
+ case RATIO:
+ mRatio.InsertEvent<int64_t>(aEvent);
+ break;
+ case ATTACK:
+ mAttack.InsertEvent<int64_t>(aEvent);
+ break;
+ case RELEASE:
+ mRelease.InsertEvent<int64_t>(aEvent);
+ break;
+ default:
+ NS_ERROR("Bad DynamicsCompresssorNodeEngine TimelineParameter");
+ }
+ }
+
+ void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
+ const AudioBlock& aInput, AudioBlock* aOutput,
+ bool* aFinished) override {
+ TRACE("DynamicsCompressorNodeEngine::ProcessBlock");
+ if (aInput.IsNull()) {
+ // Just output silence
+ *aOutput = aInput;
+ return;
+ }
+
+ const uint32_t channelCount = aInput.ChannelCount();
+ if (mCompressor->numberOfChannels() != channelCount) {
+ // Create a new compressor object with a new channel count
+ mCompressor = MakeUnique<WebCore::DynamicsCompressor>(
+ aTrack->mSampleRate, aInput.ChannelCount());
+ }
+
+ TrackTime pos = mDestination->GraphTimeToTrackTime(aFrom);
+ mCompressor->setParameterValue(DynamicsCompressor::ParamThreshold,
+ mThreshold.GetValueAtTime(pos));
+ mCompressor->setParameterValue(DynamicsCompressor::ParamKnee,
+ mKnee.GetValueAtTime(pos));
+ mCompressor->setParameterValue(DynamicsCompressor::ParamRatio,
+ mRatio.GetValueAtTime(pos));
+ mCompressor->setParameterValue(DynamicsCompressor::ParamAttack,
+ mAttack.GetValueAtTime(pos));
+ mCompressor->setParameterValue(DynamicsCompressor::ParamRelease,
+ mRelease.GetValueAtTime(pos));
+
+ aOutput->AllocateChannels(channelCount);
+ mCompressor->process(&aInput, aOutput, aInput.GetDuration());
+
+ SendReductionParamToMainThread(
+ aTrack,
+ mCompressor->parameterValue(DynamicsCompressor::ParamReduction));
+ }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override {
+ // Not owned:
+ // - mDestination (probably)
+ // - Don't count the AudioParamTimelines, their inner refs are owned by the
+ // AudioNode.
+ size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+ amount += mCompressor->sizeOfIncludingThis(aMallocSizeOf);
+ return amount;
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ private:
+ void SendReductionParamToMainThread(AudioNodeTrack* aTrack,
+ float aReduction) {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ class Command final : public Runnable {
+ public:
+ Command(AudioNodeTrack* aTrack, float aReduction)
+ : mozilla::Runnable("Command"),
+ mTrack(aTrack),
+ mReduction(aReduction) {}
+
+ NS_IMETHOD Run() override {
+ RefPtr<DynamicsCompressorNode> node =
+ static_cast<DynamicsCompressorNode*>(
+ mTrack->Engine()->NodeMainThread());
+ if (node) {
+ node->SetReduction(mReduction);
+ }
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<AudioNodeTrack> mTrack;
+ float mReduction;
+ };
+
+ AbstractThread::MainThread()->Dispatch(
+ do_AddRef(new Command(aTrack, aReduction)));
+ }
+
+ private:
+ RefPtr<AudioNodeTrack> mDestination;
+ AudioParamTimeline mThreshold;
+ AudioParamTimeline mKnee;
+ AudioParamTimeline mRatio;
+ AudioParamTimeline mAttack;
+ AudioParamTimeline mRelease;
+ UniquePtr<DynamicsCompressor> mCompressor;
+};
+
+DynamicsCompressorNode::DynamicsCompressorNode(AudioContext* aContext)
+ : AudioNode(aContext, 2, ChannelCountMode::Clamped_max,
+ ChannelInterpretation::Speakers),
+ mReduction(0) {
+ mThreshold = CreateAudioParam(DynamicsCompressorNodeEngine::THRESHOLD,
+ u"threshold"_ns, -24.f, -100.f, 0.f);
+ mKnee = CreateAudioParam(DynamicsCompressorNodeEngine::KNEE, u"knee"_ns, 30.f,
+ 0.f, 40.f);
+ mRatio = CreateAudioParam(DynamicsCompressorNodeEngine::RATIO, u"ratio"_ns,
+ 12.f, 1.f, 20.f);
+ mAttack = CreateAudioParam(DynamicsCompressorNodeEngine::ATTACK, u"attack"_ns,
+ 0.003f, 0.f, 1.f);
+ mRelease = CreateAudioParam(DynamicsCompressorNodeEngine::RELEASE,
+ u"release"_ns, 0.25f, 0.f, 1.f);
+ DynamicsCompressorNodeEngine* engine =
+ new DynamicsCompressorNodeEngine(this, aContext->Destination());
+ mTrack = AudioNodeTrack::Create(
+ aContext, engine, AudioNodeTrack::NO_TRACK_FLAGS, aContext->Graph());
+}
+
+/* static */
+already_AddRefed<DynamicsCompressorNode> DynamicsCompressorNode::Create(
+ AudioContext& aAudioContext, const DynamicsCompressorOptions& aOptions,
+ ErrorResult& aRv) {
+ RefPtr<DynamicsCompressorNode> audioNode =
+ new DynamicsCompressorNode(&aAudioContext);
+
+ audioNode->Initialize(aOptions, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ audioNode->Attack()->SetInitialValue(aOptions.mAttack);
+ audioNode->Knee()->SetInitialValue(aOptions.mKnee);
+ audioNode->Ratio()->SetInitialValue(aOptions.mRatio);
+ audioNode->GetRelease()->SetInitialValue(aOptions.mRelease);
+ audioNode->Threshold()->SetInitialValue(aOptions.mThreshold);
+
+ return audioNode.forget();
+}
+
+size_t DynamicsCompressorNode::SizeOfExcludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+ amount += mThreshold->SizeOfIncludingThis(aMallocSizeOf);
+ amount += mKnee->SizeOfIncludingThis(aMallocSizeOf);
+ amount += mRatio->SizeOfIncludingThis(aMallocSizeOf);
+ amount += mAttack->SizeOfIncludingThis(aMallocSizeOf);
+ amount += mRelease->SizeOfIncludingThis(aMallocSizeOf);
+ return amount;
+}
+
+size_t DynamicsCompressorNode::SizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+JSObject* DynamicsCompressorNode::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return DynamicsCompressorNode_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/DynamicsCompressorNode.h b/dom/media/webaudio/DynamicsCompressorNode.h
new file mode 100644
index 0000000000..1e0f9cda62
--- /dev/null
+++ b/dom/media/webaudio/DynamicsCompressorNode.h
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DynamicsCompressorNode_h_
+#define DynamicsCompressorNode_h_
+
+#include "AudioNode.h"
+#include "AudioParam.h"
+
+namespace mozilla::dom {
+
+class AudioContext;
+struct DynamicsCompressorOptions;
+
+class DynamicsCompressorNode final : public AudioNode {
+ public:
+ static already_AddRefed<DynamicsCompressorNode> Create(
+ AudioContext& aAudioContext, const DynamicsCompressorOptions& aOptions,
+ ErrorResult& aRv);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DynamicsCompressorNode, AudioNode)
+
+ static already_AddRefed<DynamicsCompressorNode> Constructor(
+ const GlobalObject& aGlobal, AudioContext& aAudioContext,
+ const DynamicsCompressorOptions& aOptions, ErrorResult& aRv) {
+ return Create(aAudioContext, aOptions, aRv);
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ AudioParam* Threshold() const { return mThreshold; }
+
+ AudioParam* Knee() const { return mKnee; }
+
+ AudioParam* Ratio() const { return mRatio; }
+
+ AudioParam* Attack() const { return mAttack; }
+
+ // Called GetRelease to prevent clashing with the nsISupports::Release name
+ AudioParam* GetRelease() const { return mRelease; }
+
+ float Reduction() const { return mReduction; }
+
+ const char* NodeType() const override { return "DynamicsCompressorNode"; }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+ void SetReduction(float aReduction) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mReduction = aReduction;
+ }
+
+ void SetChannelCountModeValue(ChannelCountMode aMode,
+ ErrorResult& aRv) override {
+ if (aMode == ChannelCountMode::Max) {
+ aRv.ThrowNotSupportedError("Cannot set channel count mode to \"max\"");
+ return;
+ }
+ AudioNode::SetChannelCountModeValue(aMode, aRv);
+ }
+
+ void SetChannelCount(uint32_t aChannelCount, ErrorResult& aRv) override {
+ if (aChannelCount > 2) {
+ aRv.ThrowNotSupportedError(
+ nsPrintfCString("%u is greater than 2", aChannelCount));
+ return;
+ }
+ AudioNode::SetChannelCount(aChannelCount, aRv);
+ }
+
+ private:
+ explicit DynamicsCompressorNode(AudioContext* aContext);
+ ~DynamicsCompressorNode() = default;
+
+ RefPtr<AudioParam> mThreshold;
+ RefPtr<AudioParam> mKnee;
+ RefPtr<AudioParam> mRatio;
+ float mReduction;
+ RefPtr<AudioParam> mAttack;
+ RefPtr<AudioParam> mRelease;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/media/webaudio/FFTBlock.cpp b/dom/media/webaudio/FFTBlock.cpp
new file mode 100644
index 0000000000..6789ca0264
--- /dev/null
+++ b/dom/media/webaudio/FFTBlock.cpp
@@ -0,0 +1,231 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=4 sw=2 sts=2 et cindent: */
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "FFTBlock.h"
+
+#include <complex>
+
+namespace mozilla {
+
+typedef std::complex<double> Complex;
+
+#ifdef MOZ_LIBAV_FFT
+FFmpegRDFTFuncs FFTBlock::sRDFTFuncs;
+#endif
+
+static double fdlibm_cabs(const Complex& z) {
+ return fdlibm_hypot(real(z), imag(z));
+}
+
+static double fdlibm_carg(const Complex& z) {
+ return fdlibm_atan2(imag(z), real(z));
+}
+
+FFTBlock* FFTBlock::CreateInterpolatedBlock(const FFTBlock& block0,
+ const FFTBlock& block1,
+ double interp) {
+ FFTBlock* newBlock = new FFTBlock(block0.FFTSize());
+
+ newBlock->InterpolateFrequencyComponents(block0, block1, interp);
+
+ // In the time-domain, the 2nd half of the response must be zero, to avoid
+ // circular convolution aliasing...
+ int fftSize = newBlock->FFTSize();
+ AlignedTArray<float> buffer(fftSize);
+ newBlock->GetInverseWithoutScaling(buffer.Elements());
+ AudioBufferInPlaceScale(buffer.Elements(), 1.0f / fftSize, fftSize / 2);
+ PodZero(buffer.Elements() + fftSize / 2, fftSize / 2);
+
+ // Put back into frequency domain.
+ newBlock->PerformFFT(buffer.Elements());
+
+ return newBlock;
+}
+
+void FFTBlock::InterpolateFrequencyComponents(const FFTBlock& block0,
+ const FFTBlock& block1,
+ double interp) {
+ // FIXME : with some work, this method could be optimized
+
+ ComplexU* dft = mOutputBuffer.Elements();
+
+ const ComplexU* dft1 = block0.mOutputBuffer.Elements();
+ const ComplexU* dft2 = block1.mOutputBuffer.Elements();
+
+ MOZ_ASSERT(mFFTSize == block0.FFTSize());
+ MOZ_ASSERT(mFFTSize == block1.FFTSize());
+ double s1base = (1.0 - interp);
+ double s2base = interp;
+
+ double phaseAccum = 0.0;
+ double lastPhase1 = 0.0;
+ double lastPhase2 = 0.0;
+
+ int n = mFFTSize / 2;
+
+ dft[0].r = static_cast<float>(s1base * dft1[0].r + s2base * dft2[0].r);
+ dft[n].r = static_cast<float>(s1base * dft1[n].r + s2base * dft2[n].r);
+
+ for (int i = 1; i < n; ++i) {
+ Complex c1(dft1[i].r, dft1[i].i);
+ Complex c2(dft2[i].r, dft2[i].i);
+
+ double mag1 = fdlibm_cabs(c1);
+ double mag2 = fdlibm_cabs(c2);
+
+ // Interpolate magnitudes in decibels
+ double mag1db = 20.0 * fdlibm_log10(mag1);
+ double mag2db = 20.0 * fdlibm_log10(mag2);
+
+ double s1 = s1base;
+ double s2 = s2base;
+
+ double magdbdiff = mag1db - mag2db;
+
+ // Empirical tweak to retain higher-frequency zeroes
+ double threshold = (i > 16) ? 5.0 : 2.0;
+
+ if (magdbdiff < -threshold && mag1db < 0.0) {
+ s1 = fdlibm_pow(s1, 0.75);
+ s2 = 1.0 - s1;
+ } else if (magdbdiff > threshold && mag2db < 0.0) {
+ s2 = fdlibm_pow(s2, 0.75);
+ s1 = 1.0 - s2;
+ }
+
+ // Average magnitude by decibels instead of linearly
+ double magdb = s1 * mag1db + s2 * mag2db;
+ double mag = fdlibm_pow(10.0, 0.05 * magdb);
+
+ // Now, deal with phase
+ double phase1 = fdlibm_carg(c1);
+ double phase2 = fdlibm_carg(c2);
+
+ double deltaPhase1 = phase1 - lastPhase1;
+ double deltaPhase2 = phase2 - lastPhase2;
+ lastPhase1 = phase1;
+ lastPhase2 = phase2;
+
+ // Unwrap phase deltas
+ if (deltaPhase1 > M_PI) deltaPhase1 -= 2.0 * M_PI;
+ if (deltaPhase1 < -M_PI) deltaPhase1 += 2.0 * M_PI;
+ if (deltaPhase2 > M_PI) deltaPhase2 -= 2.0 * M_PI;
+ if (deltaPhase2 < -M_PI) deltaPhase2 += 2.0 * M_PI;
+
+ // Blend group-delays
+ double deltaPhaseBlend;
+
+ if (deltaPhase1 - deltaPhase2 > M_PI)
+ deltaPhaseBlend = s1 * deltaPhase1 + s2 * (2.0 * M_PI + deltaPhase2);
+ else if (deltaPhase2 - deltaPhase1 > M_PI)
+ deltaPhaseBlend = s1 * (2.0 * M_PI + deltaPhase1) + s2 * deltaPhase2;
+ else
+ deltaPhaseBlend = s1 * deltaPhase1 + s2 * deltaPhase2;
+
+ phaseAccum += deltaPhaseBlend;
+
+ // Unwrap
+ if (phaseAccum > M_PI) phaseAccum -= 2.0 * M_PI;
+ if (phaseAccum < -M_PI) phaseAccum += 2.0 * M_PI;
+
+ dft[i].r = static_cast<float>(mag * fdlibm_cos(phaseAccum));
+ dft[i].i = static_cast<float>(mag * fdlibm_sin(phaseAccum));
+ }
+}
+
+double FFTBlock::ExtractAverageGroupDelay() {
+ ComplexU* dft = mOutputBuffer.Elements();
+
+ double aveSum = 0.0;
+ double weightSum = 0.0;
+ double lastPhase = 0.0;
+
+ int halfSize = FFTSize() / 2;
+
+ const double kSamplePhaseDelay = (2.0 * M_PI) / double(FFTSize());
+
+ // Remove DC offset
+ dft[0].r = 0.0f;
+
+ // Calculate weighted average group delay
+ for (int i = 1; i < halfSize; i++) {
+ Complex c(dft[i].r, dft[i].i);
+ double mag = fdlibm_cabs(c);
+ double phase = fdlibm_carg(c);
+
+ double deltaPhase = phase - lastPhase;
+ lastPhase = phase;
+
+ // Unwrap
+ if (deltaPhase < -M_PI) deltaPhase += 2.0 * M_PI;
+ if (deltaPhase > M_PI) deltaPhase -= 2.0 * M_PI;
+
+ aveSum += mag * deltaPhase;
+ weightSum += mag;
+ }
+
+ // Note how we invert the phase delta wrt frequency since this is how group
+ // delay is defined
+ double ave = aveSum / weightSum;
+ double aveSampleDelay = -ave / kSamplePhaseDelay;
+
+ // Leave 20 sample headroom (for leading edge of impulse)
+ aveSampleDelay -= 20.0;
+ if (aveSampleDelay <= 0.0) return 0.0;
+
+ // Remove average group delay (minus 20 samples for headroom)
+ AddConstantGroupDelay(-aveSampleDelay);
+
+ return aveSampleDelay;
+}
+
+void FFTBlock::AddConstantGroupDelay(double sampleFrameDelay) {
+ int halfSize = FFTSize() / 2;
+
+ ComplexU* dft = mOutputBuffer.Elements();
+
+ const double kSamplePhaseDelay = (2.0 * M_PI) / double(FFTSize());
+
+ double phaseAdj = -sampleFrameDelay * kSamplePhaseDelay;
+
+ // Add constant group delay
+ for (int i = 1; i < halfSize; i++) {
+ Complex c(dft[i].r, dft[i].i);
+ double mag = fdlibm_cabs(c);
+ double phase = fdlibm_carg(c);
+
+ phase += i * phaseAdj;
+
+ dft[i].r = static_cast<float>(mag * fdlibm_cos(phase));
+ dft[i].i = static_cast<float>(mag * fdlibm_sin(phase));
+ }
+}
+
+} // namespace mozilla
diff --git a/dom/media/webaudio/FFTBlock.h b/dom/media/webaudio/FFTBlock.h
new file mode 100644
index 0000000000..6af882f9af
--- /dev/null
+++ b/dom/media/webaudio/FFTBlock.h
@@ -0,0 +1,348 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef FFTBlock_h_
+#define FFTBlock_h_
+
+#ifdef BUILD_ARM_NEON
+# include <cmath>
+# include "mozilla/arm.h"
+# include "dl/sp/api/omxSP.h"
+#endif
+
+#include "AlignedTArray.h"
+#include "AudioNodeEngine.h"
+#if defined(MOZ_LIBAV_FFT)
+# include "FFmpegRDFTTypes.h"
+# include "FFVPXRuntimeLinker.h"
+#else
+# include "kiss_fft/kiss_fftr.h"
+#endif
+
+namespace mozilla {
+
+// This class defines an FFT block, loosely modeled after Blink's FFTFrame
+// class to make sharing code with Blink easy.
+// Currently it's implemented on top of KissFFT on all platforms.
+class FFTBlock final {
+ union ComplexU {
+#if !defined(MOZ_LIBAV_FFT)
+ kiss_fft_cpx c;
+#endif
+ float f[2];
+ struct {
+ float r;
+ float i;
+ };
+ };
+
+ public:
+ static void MainThreadInit() {
+#ifdef MOZ_LIBAV_FFT
+ FFVPXRuntimeLinker::Init();
+ if (!sRDFTFuncs.init) {
+ FFVPXRuntimeLinker::GetRDFTFuncs(&sRDFTFuncs);
+ }
+#endif
+ }
+
+ explicit FFTBlock(uint32_t aFFTSize)
+#if defined(MOZ_LIBAV_FFT)
+ : mAvRDFT(nullptr),
+ mAvIRDFT(nullptr)
+#else
+ : mKissFFT(nullptr),
+ mKissIFFT(nullptr)
+# ifdef BUILD_ARM_NEON
+ ,
+ mOmxFFT(nullptr),
+ mOmxIFFT(nullptr)
+# endif
+#endif
+ {
+ MOZ_COUNT_CTOR(FFTBlock);
+ SetFFTSize(aFFTSize);
+ }
+ ~FFTBlock() {
+ MOZ_COUNT_DTOR(FFTBlock);
+ Clear();
+ }
+
+ // Return a new FFTBlock with frequency components interpolated between
+ // |block0| and |block1| with |interp| between 0.0 and 1.0.
+ static FFTBlock* CreateInterpolatedBlock(const FFTBlock& block0,
+ const FFTBlock& block1,
+ double interp);
+
+ // Transform FFTSize() points of aData and store the result internally.
+ void PerformFFT(const float* aData) {
+ if (!EnsureFFT()) {
+ return;
+ }
+
+#if defined(MOZ_LIBAV_FFT)
+ PodCopy(mOutputBuffer.Elements()->f, aData, mFFTSize);
+ sRDFTFuncs.calc(mAvRDFT, mOutputBuffer.Elements()->f);
+ // Recover packed Nyquist.
+ mOutputBuffer[mFFTSize / 2].r = mOutputBuffer[0].i;
+ mOutputBuffer[0].i = 0.0f;
+#else
+# ifdef BUILD_ARM_NEON
+ if (mozilla::supports_neon()) {
+ omxSP_FFTFwd_RToCCS_F32_Sfs(aData, mOutputBuffer.Elements()->f, mOmxFFT);
+ } else
+# endif
+ {
+ kiss_fftr(mKissFFT, aData, &(mOutputBuffer.Elements()->c));
+ }
+#endif
+ }
+ // Inverse-transform internal data and store the resulting FFTSize()
+ // points in aDataOut.
+ void GetInverse(float* aDataOut) {
+ GetInverseWithoutScaling(aDataOut);
+ AudioBufferInPlaceScale(aDataOut, 1.0f / mFFTSize, mFFTSize);
+ }
+
+ // Inverse-transform internal frequency data and store the resulting
+ // FFTSize() points in |aDataOut|. If frequency data has not already been
+ // scaled, then the output will need scaling by 1/FFTSize().
+ void GetInverseWithoutScaling(float* aDataOut) {
+ if (!EnsureIFFT()) {
+ std::fill_n(aDataOut, mFFTSize, 0.0f);
+ return;
+ };
+
+#if defined(MOZ_LIBAV_FFT)
+ {
+ // Even though this function doesn't scale, the libav forward transform
+ // gives a value that needs scaling by 2 in order for things to turn out
+ // similar to how we expect from kissfft/openmax.
+ AudioBufferCopyWithScale(mOutputBuffer.Elements()->f, 2.0f, aDataOut,
+ mFFTSize);
+ aDataOut[1] = 2.0f * mOutputBuffer[mFFTSize / 2].r; // Packed Nyquist
+ sRDFTFuncs.calc(mAvIRDFT, aDataOut);
+ }
+#else
+# ifdef BUILD_ARM_NEON
+ if (mozilla::supports_neon()) {
+ omxSP_FFTInv_CCSToR_F32_Sfs_unscaled(mOutputBuffer.Elements()->f,
+ aDataOut, mOmxIFFT);
+ } else
+# endif
+ {
+ kiss_fftri(mKissIFFT, &(mOutputBuffer.Elements()->c), aDataOut);
+ }
+#endif
+ }
+
+ void Multiply(const FFTBlock& aFrame) {
+ uint32_t halfSize = mFFTSize / 2;
+ // DFTs are not packed.
+ MOZ_ASSERT(mOutputBuffer[0].i == 0);
+ MOZ_ASSERT(aFrame.mOutputBuffer[0].i == 0);
+
+ BufferComplexMultiply(mOutputBuffer.Elements()->f,
+ aFrame.mOutputBuffer.Elements()->f,
+ mOutputBuffer.Elements()->f, halfSize);
+ mOutputBuffer[halfSize].r *= aFrame.mOutputBuffer[halfSize].r;
+ // This would have been set to NaN if either real component was NaN.
+ mOutputBuffer[0].i = 0.0f;
+ }
+
+ // Perform a forward FFT on |aData|, assuming zeros after dataSize samples,
+ // and pre-scale the generated internal frequency domain coefficients so
+ // that GetInverseWithoutScaling() can be used to transform to the time
+ // domain. This is useful for convolution kernels.
+ void PadAndMakeScaledDFT(const float* aData, size_t dataSize) {
+ MOZ_ASSERT(dataSize <= FFTSize());
+ AlignedTArray<float> paddedData;
+ paddedData.SetLength(FFTSize());
+ AudioBufferCopyWithScale(aData, 1.0f / FFTSize(), paddedData.Elements(),
+ dataSize);
+ PodZero(paddedData.Elements() + dataSize, mFFTSize - dataSize);
+ PerformFFT(paddedData.Elements());
+ }
+
+ // aSize must be a power of 2
+ void SetFFTSize(uint32_t aSize) {
+ MOZ_ASSERT(CountPopulation32(aSize) == 1);
+ mFFTSize = aSize;
+ mOutputBuffer.SetLength(aSize / 2 + 1);
+ PodZero(mOutputBuffer.Elements(), aSize / 2 + 1);
+ Clear();
+ }
+
+ // Return the average group delay and removes this from the frequency data.
+ double ExtractAverageGroupDelay();
+
+ uint32_t FFTSize() const { return mFFTSize; }
+ float RealData(uint32_t aIndex) const { return mOutputBuffer[aIndex].r; }
+ float& RealData(uint32_t aIndex) { return mOutputBuffer[aIndex].r; }
+ float ImagData(uint32_t aIndex) const { return mOutputBuffer[aIndex].i; }
+ float& ImagData(uint32_t aIndex) { return mOutputBuffer[aIndex].i; }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t amount = 0;
+
+#if defined(MOZ_LIBAV_FFT)
+ auto ComputedSizeOfContextIfSet = [this](void* aContext) -> size_t {
+ if (!aContext) {
+ return 0;
+ }
+ // RDFTContext is only forward declared in public headers, but this is
+ // an estimate based on a value of 231 seen requested from
+ // _aligned_alloc on Win64. Don't use malloc_usable_size() because the
+ // context pointer is not necessarily from malloc.
+ size_t amount = 232;
+ // Add size of allocations performed in ff_fft_init().
+ // The maximum FFT size used is 32768 = 2^15 and so revtab32 is not
+ // allocated.
+ MOZ_ASSERT(mFFTSize <= 32768);
+ amount += mFFTSize * (sizeof(uint16_t) + 2 * sizeof(float));
+
+ return amount;
+ };
+
+ amount += ComputedSizeOfContextIfSet(mAvRDFT);
+ amount += ComputedSizeOfContextIfSet(mAvIRDFT);
+#else
+# ifdef BUILD_ARM_NEON
+ amount += aMallocSizeOf(mOmxFFT);
+ amount += aMallocSizeOf(mOmxIFFT);
+# endif
+# ifdef USE_SIMD
+# error kiss fft uses malloc only when USE_SIMD is not defined
+# endif
+ amount += aMallocSizeOf(mKissFFT);
+ amount += aMallocSizeOf(mKissIFFT);
+#endif
+ amount += mOutputBuffer.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ return amount;
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ private:
+ FFTBlock(const FFTBlock& other) = delete;
+ void operator=(const FFTBlock& other) = delete;
+
+ bool EnsureFFT() {
+#if defined(MOZ_LIBAV_FFT)
+ if (!mAvRDFT) {
+ if (!sRDFTFuncs.init) {
+ return false;
+ }
+
+ mAvRDFT = sRDFTFuncs.init(FloorLog2(mFFTSize), DFT_R2C);
+ }
+#else
+# ifdef BUILD_ARM_NEON
+ if (mozilla::supports_neon()) {
+ if (!mOmxFFT) {
+ mOmxFFT = createOmxFFT(mFFTSize);
+ }
+ } else
+# endif
+ {
+ if (!mKissFFT) {
+ mKissFFT = kiss_fftr_alloc(mFFTSize, 0, nullptr, nullptr);
+ }
+ }
+#endif
+ return true;
+ }
+
+ bool EnsureIFFT() {
+#if defined(MOZ_LIBAV_FFT)
+ if (!mAvIRDFT) {
+ if (!sRDFTFuncs.init) {
+ return false;
+ }
+
+ mAvIRDFT = sRDFTFuncs.init(FloorLog2(mFFTSize), IDFT_C2R);
+ }
+#else
+# ifdef BUILD_ARM_NEON
+ if (mozilla::supports_neon()) {
+ if (!mOmxIFFT) {
+ mOmxIFFT = createOmxFFT(mFFTSize);
+ }
+ } else
+# endif
+ {
+ if (!mKissIFFT) {
+ mKissIFFT = kiss_fftr_alloc(mFFTSize, 1, nullptr, nullptr);
+ }
+ }
+#endif
+ return true;
+ }
+
+#ifdef BUILD_ARM_NEON
+ static OMXFFTSpec_R_F32* createOmxFFT(uint32_t aFFTSize) {
+ MOZ_ASSERT((aFFTSize & (aFFTSize - 1)) == 0);
+ OMX_INT bufSize;
+ OMX_INT order = FloorLog2(aFFTSize);
+ MOZ_ASSERT(aFFTSize >> order == 1);
+ OMXResult status = omxSP_FFTGetBufSize_R_F32(order, &bufSize);
+ if (status == OMX_Sts_NoErr) {
+ OMXFFTSpec_R_F32* context =
+ static_cast<OMXFFTSpec_R_F32*>(malloc(bufSize));
+ if (omxSP_FFTInit_R_F32(context, order) != OMX_Sts_NoErr) {
+ return nullptr;
+ }
+ return context;
+ }
+ return nullptr;
+ }
+#endif
+
+ void Clear() {
+#if defined(MOZ_LIBAV_FFT)
+ if (mAvRDFT) {
+ sRDFTFuncs.end(mAvRDFT);
+ mAvRDFT = nullptr;
+ }
+ if (mAvIRDFT) {
+ sRDFTFuncs.end(mAvIRDFT);
+ mAvIRDFT = nullptr;
+ }
+#else
+# ifdef BUILD_ARM_NEON
+ free(mOmxFFT);
+ free(mOmxIFFT);
+ mOmxFFT = mOmxIFFT = nullptr;
+# endif
+ free(mKissFFT);
+ free(mKissIFFT);
+ mKissFFT = mKissIFFT = nullptr;
+#endif
+ }
+ void AddConstantGroupDelay(double sampleFrameDelay);
+ void InterpolateFrequencyComponents(const FFTBlock& block0,
+ const FFTBlock& block1, double interp);
+#if defined(MOZ_LIBAV_FFT)
+ static FFmpegRDFTFuncs sRDFTFuncs;
+ RDFTContext* mAvRDFT;
+ RDFTContext* mAvIRDFT;
+#else
+ kiss_fftr_cfg mKissFFT;
+ kiss_fftr_cfg mKissIFFT;
+# ifdef BUILD_ARM_NEON
+ OMXFFTSpec_R_F32* mOmxFFT;
+ OMXFFTSpec_R_F32* mOmxIFFT;
+# endif
+#endif
+ AlignedTArray<ComplexU> mOutputBuffer;
+ uint32_t mFFTSize;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webaudio/GainNode.cpp b/dom/media/webaudio/GainNode.cpp
new file mode 100644
index 0000000000..ed998ee2a9
--- /dev/null
+++ b/dom/media/webaudio/GainNode.cpp
@@ -0,0 +1,149 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GainNode.h"
+#include "mozilla/dom/GainNodeBinding.h"
+#include "AlignmentUtils.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeTrack.h"
+#include "AudioDestinationNode.h"
+#include "WebAudioUtils.h"
+#include "Tracing.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(GainNode, AudioNode, mGain)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(GainNode)
+NS_INTERFACE_MAP_END_INHERITING(AudioNode)
+
+NS_IMPL_ADDREF_INHERITED(GainNode, AudioNode)
+NS_IMPL_RELEASE_INHERITED(GainNode, AudioNode)
+
+class GainNodeEngine final : public AudioNodeEngine {
+ public:
+ GainNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination)
+ : AudioNodeEngine(aNode),
+ mDestination(aDestination->Track())
+ // Keep the default value in sync with the default value in
+ // GainNode::GainNode.
+ ,
+ mGain(1.f) {}
+
+ enum Parameters { GAIN };
+ void RecvTimelineEvent(uint32_t aIndex, AudioParamEvent& aEvent) override {
+ MOZ_ASSERT(mDestination);
+ aEvent.ConvertToTicks(mDestination);
+
+ switch (aIndex) {
+ case GAIN:
+ mGain.InsertEvent<int64_t>(aEvent);
+ break;
+ default:
+ NS_ERROR("Bad GainNodeEngine TimelineParameter");
+ }
+ }
+
+ void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
+ const AudioBlock& aInput, AudioBlock* aOutput,
+ bool* aFinished) override {
+ TRACE("GainNodeEngine::ProcessBlock");
+ if (aInput.IsNull()) {
+ // If input is silent, so is the output
+ aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
+ } else if (mGain.HasSimpleValue()) {
+ // Optimize the case where we only have a single value set as the volume
+ float gain = mGain.GetValue();
+ if (gain == 0.0f) {
+ aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
+ } else {
+ *aOutput = aInput;
+ aOutput->mVolume *= gain;
+ }
+ } else {
+ // First, compute a vector of gains for each track tick based on the
+ // timeline at hand, and then for each channel, multiply the values
+ // in the buffer with the gain vector.
+ aOutput->AllocateChannels(aInput.ChannelCount());
+
+ // Compute the gain values for the duration of the input AudioChunk
+ TrackTime tick = mDestination->GraphTimeToTrackTime(aFrom);
+ float computedGain[WEBAUDIO_BLOCK_SIZE + 4];
+ float* alignedComputedGain = ALIGNED16(computedGain);
+ ASSERT_ALIGNED16(alignedComputedGain);
+ mGain.GetValuesAtTime(tick, alignedComputedGain, WEBAUDIO_BLOCK_SIZE);
+
+ for (size_t counter = 0; counter < WEBAUDIO_BLOCK_SIZE; ++counter) {
+ alignedComputedGain[counter] *= aInput.mVolume;
+ }
+
+ // Apply the gain to the output buffer
+ for (size_t channel = 0; channel < aOutput->ChannelCount(); ++channel) {
+ const float* inputBuffer =
+ static_cast<const float*>(aInput.mChannelData[channel]);
+ float* buffer = aOutput->ChannelFloatsForWrite(channel);
+ AudioBlockCopyChannelWithScale(inputBuffer, alignedComputedGain,
+ buffer);
+ }
+ }
+ }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override {
+ // Not owned:
+ // - mDestination - MediaTrackGraphImpl::CollectSizesForMemoryReport()
+ // accounts for mDestination.
+ // - mGain - Internal ref owned by AudioNode
+ return AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ RefPtr<AudioNodeTrack> mDestination;
+ AudioParamTimeline mGain;
+};
+
+GainNode::GainNode(AudioContext* aContext)
+ : AudioNode(aContext, 2, ChannelCountMode::Max,
+ ChannelInterpretation::Speakers) {
+ mGain = CreateAudioParam(GainNodeEngine::GAIN, u"gain"_ns, 1.0f);
+ GainNodeEngine* engine = new GainNodeEngine(this, aContext->Destination());
+ mTrack = AudioNodeTrack::Create(
+ aContext, engine, AudioNodeTrack::NO_TRACK_FLAGS, aContext->Graph());
+}
+
+/* static */
+already_AddRefed<GainNode> GainNode::Create(AudioContext& aAudioContext,
+ const GainOptions& aOptions,
+ ErrorResult& aRv) {
+ RefPtr<GainNode> audioNode = new GainNode(&aAudioContext);
+
+ audioNode->Initialize(aOptions, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ audioNode->Gain()->SetInitialValue(aOptions.mGain);
+ return audioNode.forget();
+}
+
+size_t GainNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+ amount += mGain->SizeOfIncludingThis(aMallocSizeOf);
+ return amount;
+}
+
+size_t GainNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+JSObject* GainNode::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return GainNode_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/GainNode.h b/dom/media/webaudio/GainNode.h
new file mode 100644
index 0000000000..d2790af746
--- /dev/null
+++ b/dom/media/webaudio/GainNode.h
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GainNode_h_
+#define GainNode_h_
+
+#include "AudioNode.h"
+#include "AudioParam.h"
+
+namespace mozilla::dom {
+
+class AudioContext;
+struct GainOptions;
+
+class GainNode final : public AudioNode {
+ public:
+ static already_AddRefed<GainNode> Create(AudioContext& aAudioContext,
+ const GainOptions& aOptions,
+ ErrorResult& aRv);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(GainNode, AudioNode)
+
+ static already_AddRefed<GainNode> Constructor(const GlobalObject& aGlobal,
+ AudioContext& aAudioContext,
+ const GainOptions& aOptions,
+ ErrorResult& aRv) {
+ return Create(aAudioContext, aOptions, aRv);
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ AudioParam* Gain() const { return mGain; }
+
+ const char* NodeType() const override { return "GainNode"; }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+ private:
+ explicit GainNode(AudioContext* aContext);
+ ~GainNode() = default;
+
+ RefPtr<AudioParam> mGain;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/media/webaudio/IIRFilterNode.cpp b/dom/media/webaudio/IIRFilterNode.cpp
new file mode 100644
index 0000000000..fe87744d4d
--- /dev/null
+++ b/dom/media/webaudio/IIRFilterNode.cpp
@@ -0,0 +1,266 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "IIRFilterNode.h"
+#include "AudioNodeEngine.h"
+#include "AudioDestinationNode.h"
+#include "blink/IIRFilter.h"
+#include "PlayingRefChangeHandler.h"
+#include "AlignmentUtils.h"
+#include "nsPrintfCString.h"
+
+#include "nsGkAtoms.h"
+#include "Tracing.h"
+
+namespace mozilla::dom {
+
+class IIRFilterNodeEngine final : public AudioNodeEngine {
+ public:
+ IIRFilterNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination,
+ const AudioDoubleArray& aFeedforward,
+ const AudioDoubleArray& aFeedback, uint64_t aWindowID)
+ : AudioNodeEngine(aNode),
+ mDestination(aDestination->Track()),
+ mFeedforward(aFeedforward.Clone()),
+ mFeedback(aFeedback.Clone()),
+ mWindowID(aWindowID) {}
+
+ void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
+ const AudioBlock& aInput, AudioBlock* aOutput,
+ bool* aFinished) override {
+ TRACE("IIRFilterNodeEngine::ProcessBlock");
+ float inputBuffer[WEBAUDIO_BLOCK_SIZE + 4];
+ float* alignedInputBuffer = ALIGNED16(inputBuffer);
+ ASSERT_ALIGNED16(alignedInputBuffer);
+
+ if (aInput.IsNull()) {
+ if (!mIIRFilters.IsEmpty()) {
+ bool allZero = true;
+ for (uint32_t i = 0; i < mIIRFilters.Length(); ++i) {
+ allZero &= mIIRFilters[i]->buffersAreZero();
+ }
+
+ // all filter buffer values are zero, so the output will be zero
+ // as well.
+ if (allZero) {
+ mIIRFilters.Clear();
+ aTrack->ScheduleCheckForInactive();
+
+ RefPtr<PlayingRefChangeHandler> refchanged =
+ new PlayingRefChangeHandler(aTrack,
+ PlayingRefChangeHandler::RELEASE);
+ aTrack->Graph()->DispatchToMainThreadStableState(refchanged.forget());
+
+ aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
+ return;
+ }
+
+ PodZero(alignedInputBuffer, WEBAUDIO_BLOCK_SIZE);
+ }
+ } else if (mIIRFilters.Length() != aInput.ChannelCount()) {
+ if (mIIRFilters.IsEmpty()) {
+ RefPtr<PlayingRefChangeHandler> refchanged =
+ new PlayingRefChangeHandler(aTrack,
+ PlayingRefChangeHandler::ADDREF);
+ aTrack->Graph()->DispatchToMainThreadStableState(refchanged.forget());
+ } else {
+ WebAudioUtils::LogToDeveloperConsole(
+ mWindowID, "IIRFilterChannelCountChangeWarning");
+ }
+
+ // Adjust the number of filters based on the number of channels
+ mIIRFilters.SetLength(aInput.ChannelCount());
+ for (size_t i = 0; i < aInput.ChannelCount(); ++i) {
+ mIIRFilters[i] =
+ MakeUnique<blink::IIRFilter>(&mFeedforward, &mFeedback);
+ }
+ }
+
+ uint32_t numberOfChannels = mIIRFilters.Length();
+ aOutput->AllocateChannels(numberOfChannels);
+
+ for (uint32_t i = 0; i < numberOfChannels; ++i) {
+ const float* input;
+ if (aInput.IsNull()) {
+ input = alignedInputBuffer;
+ } else {
+ input = static_cast<const float*>(aInput.mChannelData[i]);
+ if (aInput.mVolume != 1.0) {
+ AudioBlockCopyChannelWithScale(input, aInput.mVolume,
+ alignedInputBuffer);
+ input = alignedInputBuffer;
+ }
+ }
+
+ mIIRFilters[i]->process(input, aOutput->ChannelFloatsForWrite(i),
+ aInput.GetDuration());
+ }
+ }
+
+ bool IsActive() const override { return !mIIRFilters.IsEmpty(); }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override {
+ // Not owned:
+ // - mDestination - probably not owned
+ // - AudioParamTimelines - counted in the AudioNode
+ size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+ amount += mIIRFilters.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ return amount;
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ private:
+ RefPtr<AudioNodeTrack> mDestination;
+ nsTArray<UniquePtr<blink::IIRFilter>> mIIRFilters;
+ AudioDoubleArray mFeedforward;
+ AudioDoubleArray mFeedback;
+ uint64_t mWindowID;
+};
+
+IIRFilterNode::IIRFilterNode(AudioContext* aContext,
+ const Sequence<double>& aFeedforward,
+ const Sequence<double>& aFeedback)
+ : AudioNode(aContext, 2, ChannelCountMode::Max,
+ ChannelInterpretation::Speakers) {
+ mFeedforward.SetLength(aFeedforward.Length());
+ PodCopy(mFeedforward.Elements(), aFeedforward.Elements(),
+ aFeedforward.Length());
+ mFeedback.SetLength(aFeedback.Length());
+ PodCopy(mFeedback.Elements(), aFeedback.Elements(), aFeedback.Length());
+
+ // Scale coefficients -- we guarantee that mFeedback != 0 when creating
+ // the IIRFilterNode.
+ double scale = mFeedback[0];
+ double* elements = mFeedforward.Elements();
+ for (size_t i = 0; i < mFeedforward.Length(); ++i) {
+ elements[i] /= scale;
+ }
+
+ elements = mFeedback.Elements();
+ for (size_t i = 0; i < mFeedback.Length(); ++i) {
+ elements[i] /= scale;
+ }
+
+ // We check that this is exactly equal to one later in blink/IIRFilter.cpp
+ elements[0] = 1.0;
+
+ uint64_t windowID = 0;
+ if (aContext->GetParentObject()) {
+ windowID = aContext->GetParentObject()->WindowID();
+ }
+ IIRFilterNodeEngine* engine = new IIRFilterNodeEngine(
+ this, aContext->Destination(), mFeedforward, mFeedback, windowID);
+ mTrack = AudioNodeTrack::Create(
+ aContext, engine, AudioNodeTrack::NO_TRACK_FLAGS, aContext->Graph());
+}
+
+/* static */
+already_AddRefed<IIRFilterNode> IIRFilterNode::Create(
+ AudioContext& aAudioContext, const IIRFilterOptions& aOptions,
+ ErrorResult& aRv) {
+ if (aOptions.mFeedforward.Length() == 0 ||
+ aOptions.mFeedforward.Length() > 20) {
+ aRv.ThrowNotSupportedError(
+ nsPrintfCString("\"feedforward\" length %zu is not in the range [1,20]",
+ aOptions.mFeedforward.Length()));
+ return nullptr;
+ }
+
+ if (aOptions.mFeedback.Length() == 0 || aOptions.mFeedback.Length() > 20) {
+ aRv.ThrowNotSupportedError(
+ nsPrintfCString("\"feedback\" length %zu is not in the range [1,20]",
+ aOptions.mFeedback.Length()));
+ return nullptr;
+ }
+
+ bool feedforwardAllZeros = true;
+ for (size_t i = 0; i < aOptions.mFeedforward.Length(); ++i) {
+ if (aOptions.mFeedforward.Elements()[i] != 0.0) {
+ feedforwardAllZeros = false;
+ break;
+ }
+ }
+
+ if (feedforwardAllZeros) {
+ aRv.ThrowInvalidStateError(
+ "\"feedforward\" must contain some nonzero values");
+ return nullptr;
+ }
+
+ if (aOptions.mFeedback[0] == 0.0) {
+ aRv.ThrowInvalidStateError("First value in \"feedback\" must be nonzero");
+ return nullptr;
+ }
+
+ RefPtr<IIRFilterNode> audioNode = new IIRFilterNode(
+ &aAudioContext, aOptions.mFeedforward, aOptions.mFeedback);
+
+ audioNode->Initialize(aOptions, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return audioNode.forget();
+}
+
+size_t IIRFilterNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+ return amount;
+}
+
+size_t IIRFilterNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+JSObject* IIRFilterNode::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return IIRFilterNode_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void IIRFilterNode::GetFrequencyResponse(const Float32Array& aFrequencyHz,
+ const Float32Array& aMagResponse,
+ const Float32Array& aPhaseResponse) {
+ aFrequencyHz.ProcessData([&](const Span<float>& aFrequencyData,
+ JS::AutoCheckCannotGC&&) {
+ aMagResponse.ProcessData([&](const Span<float>& aMagData,
+ JS::AutoCheckCannotGC&&) {
+ aPhaseResponse.ProcessData([&](const Span<float>& aPhaseData,
+ JS::AutoCheckCannotGC&&) {
+ uint32_t length = std::min(
+ {aFrequencyData.Length(), aMagData.Length(), aPhaseData.Length()});
+ if (!length) {
+ return;
+ }
+
+ auto frequencies = MakeUniqueForOverwriteFallible<float[]>(length);
+ if (!frequencies) {
+ return;
+ }
+
+ const double nyquist = Context()->SampleRate() * 0.5;
+
+ // Normalize the frequencies
+ std::transform(aFrequencyData.begin(), aFrequencyData.begin() + length,
+ frequencies.get(), [&](float aFrequency) {
+ if (aFrequency >= 0 && aFrequency <= nyquist) {
+ return static_cast<float>(aFrequency / nyquist);
+ }
+
+ return std::numeric_limits<float>::quiet_NaN();
+ });
+
+ blink::IIRFilter filter(&mFeedforward, &mFeedback);
+ filter.getFrequencyResponse(int(length), frequencies.get(),
+ aMagData.Elements(), aPhaseData.Elements());
+ });
+ });
+ });
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/IIRFilterNode.h b/dom/media/webaudio/IIRFilterNode.h
new file mode 100644
index 0000000000..d79f87f632
--- /dev/null
+++ b/dom/media/webaudio/IIRFilterNode.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef IIRFilterNode_h_
+#define IIRFilterNode_h_
+
+#include "AudioNode.h"
+#include "AudioParam.h"
+#include "mozilla/dom/IIRFilterNodeBinding.h"
+
+namespace mozilla::dom {
+
+class AudioContext;
+struct IIRFilterOptions;
+
+class IIRFilterNode final : public AudioNode {
+ public:
+ static already_AddRefed<IIRFilterNode> Create(
+ AudioContext& aAudioContext, const IIRFilterOptions& aOptions,
+ ErrorResult& aRv);
+
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(IIRFilterNode, AudioNode)
+
+ static already_AddRefed<IIRFilterNode> Constructor(
+ const GlobalObject& aGlobal, AudioContext& aAudioContext,
+ const IIRFilterOptions& aOptions, ErrorResult& aRv) {
+ return Create(aAudioContext, aOptions, aRv);
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void GetFrequencyResponse(const Float32Array& aFrequencyHz,
+ const Float32Array& aMagResponse,
+ const Float32Array& aPhaseResponse);
+
+ const char* NodeType() const override { return "IIRFilterNode"; }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+ private:
+ IIRFilterNode(AudioContext* aContext, const Sequence<double>& aFeedforward,
+ const Sequence<double>& aFeedback);
+ ~IIRFilterNode() = default;
+
+ nsTArray<double> mFeedback;
+ nsTArray<double> mFeedforward;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/media/webaudio/MediaBufferDecoder.cpp b/dom/media/webaudio/MediaBufferDecoder.cpp
new file mode 100644
index 0000000000..425c0639b5
--- /dev/null
+++ b/dom/media/webaudio/MediaBufferDecoder.cpp
@@ -0,0 +1,764 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaBufferDecoder.h"
+
+#include <speex/speex_resampler.h>
+
+#include "AudioBuffer.h"
+#include "AudioContext.h"
+#include "AudioNodeEngine.h"
+#include "BufferMediaResource.h"
+#include "DecoderTraits.h"
+#include "MediaContainerType.h"
+#include "MediaDataDecoderProxy.h"
+#include "MediaDataDemuxer.h"
+#include "MediaQueue.h"
+#include "PDMFactory.h"
+#include "VideoUtils.h"
+#include "WebAudioUtils.h"
+#include "js/MemoryFunctions.h"
+#include "mozilla/AbstractThread.h"
+#include "mozilla/Logging.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/dom/AudioContextBinding.h"
+#include "mozilla/dom/BaseAudioContextBinding.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsIScriptError.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsMimeTypes.h"
+#include "nsPrintfCString.h"
+#include "nsXPCOMCIDInternal.h"
+
+namespace mozilla {
+
+extern LazyLogModule gMediaDecoderLog;
+
+#define LOG(x, ...) \
+ MOZ_LOG(gMediaDecoderLog, LogLevel::Debug, (x, ##__VA_ARGS__))
+
+using namespace dom;
+
+class ReportResultTask final : public Runnable {
+ public:
+ ReportResultTask(WebAudioDecodeJob& aDecodeJob,
+ WebAudioDecodeJob::ResultFn aFunction,
+ WebAudioDecodeJob::ErrorCode aErrorCode)
+ : Runnable("ReportResultTask"),
+ mDecodeJob(aDecodeJob),
+ mFunction(aFunction),
+ mErrorCode(aErrorCode) {
+ MOZ_ASSERT(aFunction);
+ }
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ (mDecodeJob.*mFunction)(mErrorCode);
+
+ return NS_OK;
+ }
+
+ private:
+ // Note that the mDecodeJob member will probably die when mFunction is run.
+ // Therefore, it is not safe to do anything fancy with it in this class.
+ // Really, this class is only used because nsRunnableMethod doesn't support
+ // methods accepting arguments.
+ WebAudioDecodeJob& mDecodeJob;
+ WebAudioDecodeJob::ResultFn mFunction;
+ WebAudioDecodeJob::ErrorCode mErrorCode;
+};
+
+enum class PhaseEnum : int { Decode, AllocateBuffer, Done };
+
+class MediaDecodeTask final : public Runnable {
+ public:
+ MediaDecodeTask(const MediaContainerType& aContainerType, uint8_t* aBuffer,
+ uint32_t aLength, WebAudioDecodeJob& aDecodeJob)
+ : Runnable("MediaDecodeTask"),
+ mContainerType(aContainerType),
+ mBuffer(aBuffer),
+ mLength(aLength),
+ mBatchSize(StaticPrefs::media_rdd_webaudio_batch_size()),
+ mDecodeJob(aDecodeJob),
+ mPhase(PhaseEnum::Decode) {
+ MOZ_ASSERT(aBuffer);
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ // MOZ_CAN_RUN_SCRIPT_BOUNDARY until Runnable::Run is MOZ_CAN_RUN_SCRIPT. See
+ // bug 1535398.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ NS_IMETHOD Run() override;
+ bool Init();
+ TaskQueue* PSupervisorTaskQueue() { return mPSupervisorTaskQueue; }
+ bool OnPSupervisorTaskQueue() const {
+ return mPSupervisorTaskQueue->IsCurrentThreadIn();
+ }
+
+ private:
+ MOZ_CAN_RUN_SCRIPT
+ void ReportFailureOnMainThread(WebAudioDecodeJob::ErrorCode aErrorCode) {
+ if (NS_IsMainThread()) {
+ Cleanup();
+ mDecodeJob.OnFailure(aErrorCode);
+ } else {
+ // Take extra care to cleanup on the main thread
+ mMainThread->Dispatch(NewRunnableMethod("MediaDecodeTask::Cleanup", this,
+ &MediaDecodeTask::Cleanup));
+
+ nsCOMPtr<nsIRunnable> event = new ReportResultTask(
+ mDecodeJob, &WebAudioDecodeJob::OnFailure, aErrorCode);
+ mMainThread->Dispatch(event.forget());
+ }
+ }
+
+ void Decode();
+
+ void OnCreateDecoderCompleted(RefPtr<MediaDataDecoder> aDecoder);
+ MOZ_CAN_RUN_SCRIPT void OnCreateDecoderFailed(const MediaResult& aError);
+
+ MOZ_CAN_RUN_SCRIPT void OnInitDemuxerCompleted();
+ MOZ_CAN_RUN_SCRIPT void OnInitDemuxerFailed(const MediaResult& aError);
+
+ void InitDecoder();
+ void OnInitDecoderCompleted();
+ MOZ_CAN_RUN_SCRIPT void OnInitDecoderFailed();
+
+ void DoDemux();
+ void OnAudioDemuxCompleted(RefPtr<MediaTrackDemuxer::SamplesHolder> aSamples);
+ MOZ_CAN_RUN_SCRIPT void OnAudioDemuxFailed(const MediaResult& aError);
+
+ void DoDecode();
+ void OnAudioDecodeCompleted(MediaDataDecoder::DecodedData&& aResults);
+ MOZ_CAN_RUN_SCRIPT void OnAudioDecodeFailed(const MediaResult& aError);
+
+ void DoDrain();
+ MOZ_CAN_RUN_SCRIPT void OnAudioDrainCompleted(
+ MediaDataDecoder::DecodedData&& aResults);
+ MOZ_CAN_RUN_SCRIPT void OnAudioDrainFailed(const MediaResult& aError);
+
+ void ShutdownDecoder();
+
+ MOZ_CAN_RUN_SCRIPT void FinishDecode();
+ MOZ_CAN_RUN_SCRIPT void AllocateBuffer();
+ MOZ_CAN_RUN_SCRIPT void CallbackTheResult();
+
+ void Cleanup() {
+ MOZ_ASSERT(NS_IsMainThread());
+ JS_free(nullptr, mBuffer);
+ if (mTrackDemuxer) {
+ mTrackDemuxer->BreakCycles();
+ }
+ mTrackDemuxer = nullptr;
+ mDemuxer = nullptr;
+ mPSupervisorTaskQueue = nullptr;
+ mPDecoderTaskQueue = nullptr;
+ }
+
+ private:
+ MediaContainerType mContainerType;
+ uint8_t* mBuffer;
+ const uint32_t mLength;
+ const uint32_t mBatchSize;
+ WebAudioDecodeJob& mDecodeJob;
+ PhaseEnum mPhase;
+ RefPtr<TaskQueue> mPSupervisorTaskQueue;
+ RefPtr<TaskQueue> mPDecoderTaskQueue;
+ RefPtr<MediaDataDemuxer> mDemuxer;
+ RefPtr<MediaTrackDemuxer> mTrackDemuxer;
+ RefPtr<MediaDataDecoder> mDecoder;
+ nsTArray<RefPtr<MediaRawData>> mRawSamples;
+ MediaInfo mMediaInfo;
+ MediaQueue<AudioData> mAudioQueue;
+ RefPtr<AbstractThread> mMainThread;
+};
+
+NS_IMETHODIMP
+MediaDecodeTask::Run() {
+ switch (mPhase) {
+ case PhaseEnum::Decode:
+ Decode();
+ break;
+ case PhaseEnum::AllocateBuffer:
+ AllocateBuffer();
+ break;
+ case PhaseEnum::Done:
+ break;
+ }
+
+ return NS_OK;
+}
+
+bool MediaDecodeTask::Init() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<BufferMediaResource> resource =
+ new BufferMediaResource(static_cast<uint8_t*>(mBuffer), mLength);
+
+ mMainThread = AbstractThread::MainThread();
+
+ mPSupervisorTaskQueue =
+ TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR),
+ "MediaBufferDecoder::mPSupervisorTaskQueue");
+ mPDecoderTaskQueue =
+ TaskQueue::Create(GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER),
+ "MediaBufferDecoder::mPDecoderTaskQueue");
+
+ // If you change this list to add support for new decoders, please consider
+ // updating HTMLMediaElement::CreateDecoder as well.
+ mDemuxer = DecoderTraits::CreateDemuxer(mContainerType, resource);
+ if (!mDemuxer) {
+ return false;
+ }
+
+ return true;
+}
+
+class AutoResampler final {
+ public:
+ AutoResampler() : mResampler(nullptr) {}
+ ~AutoResampler() {
+ if (mResampler) {
+ speex_resampler_destroy(mResampler);
+ }
+ }
+ operator SpeexResamplerState*() const {
+ MOZ_ASSERT(mResampler);
+ return mResampler;
+ }
+ void operator=(SpeexResamplerState* aResampler) { mResampler = aResampler; }
+
+ private:
+ SpeexResamplerState* mResampler;
+};
+
+void MediaDecodeTask::Decode() {
+ MOZ_ASSERT(OnPSupervisorTaskQueue());
+
+ mDemuxer->Init()->Then(PSupervisorTaskQueue(), __func__, this,
+ &MediaDecodeTask::OnInitDemuxerCompleted,
+ &MediaDecodeTask::OnInitDemuxerFailed);
+}
+
+void MediaDecodeTask::OnInitDemuxerCompleted() {
+ MOZ_ASSERT(OnPSupervisorTaskQueue());
+
+ if (!!mDemuxer->GetNumberTracks(TrackInfo::kAudioTrack)) {
+ mTrackDemuxer = mDemuxer->GetTrackDemuxer(TrackInfo::kAudioTrack, 0);
+ if (!mTrackDemuxer) {
+ LOG("MediaDecodeTask: Could not get a track demuxer.");
+ ReportFailureOnMainThread(WebAudioDecodeJob::UnknownContent);
+ return;
+ }
+
+ RefPtr<PDMFactory> platform = new PDMFactory();
+ UniquePtr<TrackInfo> audioInfo = mTrackDemuxer->GetInfo();
+ // We actively ignore audio tracks that we know we can't play.
+ if (audioInfo && audioInfo->IsValid() &&
+ !platform->SupportsMimeType(audioInfo->mMimeType).isEmpty()) {
+ mMediaInfo.mAudio = *audioInfo->GetAsAudioInfo();
+ }
+ }
+
+ RefPtr<PDMFactory> pdm = new PDMFactory();
+ pdm->CreateDecoder(
+ {*mMediaInfo.mAudio.GetAsAudioInfo(), TrackInfo::kAudioTrack})
+ ->Then(PSupervisorTaskQueue(), __func__, this,
+ &MediaDecodeTask::OnCreateDecoderCompleted,
+ &MediaDecodeTask::OnCreateDecoderFailed);
+}
+
+void MediaDecodeTask::OnCreateDecoderCompleted(
+ RefPtr<MediaDataDecoder> aDecoder) {
+ MOZ_ASSERT(OnPSupervisorTaskQueue());
+
+ mDecoder = new MediaDataDecoderProxy(aDecoder.forget(),
+ do_AddRef(mPDecoderTaskQueue.get()));
+ InitDecoder();
+}
+
+void MediaDecodeTask::OnCreateDecoderFailed(const MediaResult& aError) {
+ MOZ_ASSERT(OnPSupervisorTaskQueue());
+
+ LOG("MediaDecodeTask: Could not create a decoder.");
+ ReportFailureOnMainThread(WebAudioDecodeJob::UnknownContent);
+}
+
+void MediaDecodeTask::OnInitDemuxerFailed(const MediaResult& aError) {
+ MOZ_ASSERT(OnPSupervisorTaskQueue());
+
+ LOG("MediaDecodeTask: Could not initialize the demuxer.");
+ ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent);
+}
+
+void MediaDecodeTask::InitDecoder() {
+ MOZ_ASSERT(OnPSupervisorTaskQueue());
+
+ mDecoder->Init()->Then(PSupervisorTaskQueue(), __func__, this,
+ &MediaDecodeTask::OnInitDecoderCompleted,
+ &MediaDecodeTask::OnInitDecoderFailed);
+}
+
+void MediaDecodeTask::OnInitDecoderCompleted() {
+ MOZ_ASSERT(OnPSupervisorTaskQueue());
+
+ DoDemux();
+}
+
+void MediaDecodeTask::OnInitDecoderFailed() {
+ MOZ_ASSERT(OnPSupervisorTaskQueue());
+
+ ShutdownDecoder();
+ LOG("MediaDecodeTask: Could not initialize the decoder");
+ ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent);
+}
+
+void MediaDecodeTask::DoDemux() {
+ MOZ_ASSERT(OnPSupervisorTaskQueue());
+
+ mTrackDemuxer->GetSamples(mBatchSize)
+ ->Then(PSupervisorTaskQueue(), __func__, this,
+ &MediaDecodeTask::OnAudioDemuxCompleted,
+ &MediaDecodeTask::OnAudioDemuxFailed);
+}
+
+void MediaDecodeTask::OnAudioDemuxCompleted(
+ RefPtr<MediaTrackDemuxer::SamplesHolder> aSamples) {
+ MOZ_ASSERT(OnPSupervisorTaskQueue());
+
+ mRawSamples.AppendElements(aSamples->GetSamples());
+
+ DoDemux();
+}
+
+void MediaDecodeTask::OnAudioDemuxFailed(const MediaResult& aError) {
+ MOZ_ASSERT(OnPSupervisorTaskQueue());
+
+ if (aError.Code() == NS_ERROR_DOM_MEDIA_END_OF_STREAM) {
+ DoDecode();
+ } else {
+ ShutdownDecoder();
+ LOG("MediaDecodeTask: Audio demux failed");
+ ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent);
+ }
+}
+
+void MediaDecodeTask::DoDecode() {
+ MOZ_ASSERT(OnPSupervisorTaskQueue());
+
+ if (mRawSamples.IsEmpty()) {
+ DoDrain();
+ return;
+ }
+
+ if (mBatchSize > 1 && mDecoder->CanDecodeBatch()) {
+ nsTArray<RefPtr<MediaRawData>> rawSampleBatch;
+ const int batchSize = std::min((unsigned long)mBatchSize,
+ (unsigned long)mRawSamples.Length());
+ for (int i = 0; i < batchSize; ++i) {
+ rawSampleBatch.AppendElement(std::move(mRawSamples[i]));
+ }
+
+ mDecoder->DecodeBatch(std::move(rawSampleBatch))
+ ->Then(PSupervisorTaskQueue(), __func__, this,
+ &MediaDecodeTask::OnAudioDecodeCompleted,
+ &MediaDecodeTask::OnAudioDecodeFailed);
+
+ mRawSamples.RemoveElementsAt(0, batchSize);
+ } else {
+ RefPtr<MediaRawData> sample = std::move(mRawSamples[0]);
+
+ mDecoder->Decode(sample)->Then(PSupervisorTaskQueue(), __func__, this,
+ &MediaDecodeTask::OnAudioDecodeCompleted,
+ &MediaDecodeTask::OnAudioDecodeFailed);
+
+ mRawSamples.RemoveElementAt(0);
+ }
+}
+
+void MediaDecodeTask::OnAudioDecodeCompleted(
+ MediaDataDecoder::DecodedData&& aResults) {
+ MOZ_ASSERT(OnPSupervisorTaskQueue());
+
+ for (auto&& sample : aResults) {
+ MOZ_ASSERT(sample->mType == MediaData::Type::AUDIO_DATA);
+ RefPtr<AudioData> audioData = sample->As<AudioData>();
+
+ mMediaInfo.mAudio.mRate = audioData->mRate;
+ mMediaInfo.mAudio.mChannels = audioData->mChannels;
+
+ mAudioQueue.Push(audioData.forget());
+ }
+
+ DoDecode();
+}
+
+void MediaDecodeTask::OnAudioDecodeFailed(const MediaResult& aError) {
+ MOZ_ASSERT(OnPSupervisorTaskQueue());
+
+ ShutdownDecoder();
+ LOG("MediaDecodeTask: decode audio failed.");
+ ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent);
+}
+
+void MediaDecodeTask::DoDrain() {
+ MOZ_ASSERT(OnPSupervisorTaskQueue());
+
+ mDecoder->Drain()->Then(PSupervisorTaskQueue(), __func__, this,
+ &MediaDecodeTask::OnAudioDrainCompleted,
+ &MediaDecodeTask::OnAudioDrainFailed);
+}
+
+void MediaDecodeTask::OnAudioDrainCompleted(
+ MediaDataDecoder::DecodedData&& aResults) {
+ MOZ_ASSERT(OnPSupervisorTaskQueue());
+
+ if (aResults.IsEmpty()) {
+ FinishDecode();
+ return;
+ }
+
+ for (auto&& sample : aResults) {
+ MOZ_ASSERT(sample->mType == MediaData::Type::AUDIO_DATA);
+ RefPtr<AudioData> audioData = sample->As<AudioData>();
+
+ mAudioQueue.Push(audioData.forget());
+ }
+ DoDrain();
+}
+
+void MediaDecodeTask::OnAudioDrainFailed(const MediaResult& aError) {
+ MOZ_ASSERT(OnPSupervisorTaskQueue());
+
+ ShutdownDecoder();
+ LOG("MediaDecodeTask: Drain audio failed");
+ ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent);
+}
+
+void MediaDecodeTask::ShutdownDecoder() {
+ MOZ_ASSERT(OnPSupervisorTaskQueue());
+
+ if (!mDecoder) {
+ return;
+ }
+
+ RefPtr<MediaDecodeTask> self = this;
+ mDecoder->Shutdown();
+ mDecoder = nullptr;
+}
+
+void MediaDecodeTask::FinishDecode() {
+ MOZ_ASSERT(OnPSupervisorTaskQueue());
+
+ ShutdownDecoder();
+
+ uint32_t frameCount = mAudioQueue.AudioFramesCount();
+ uint32_t channelCount = mMediaInfo.mAudio.mChannels;
+ uint32_t sampleRate = mMediaInfo.mAudio.mRate;
+
+ if (!frameCount || !channelCount || !sampleRate) {
+ LOG("MediaDecodeTask: invalid content frame count, channel count or "
+ "sample-rate");
+ ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent);
+ return;
+ }
+
+ const uint32_t destSampleRate = mDecodeJob.mContext->SampleRate();
+ AutoResampler resampler;
+
+ uint32_t resampledFrames = frameCount;
+ if (sampleRate != destSampleRate) {
+ resampledFrames = static_cast<uint32_t>(
+ static_cast<uint64_t>(destSampleRate) *
+ static_cast<uint64_t>(frameCount) / static_cast<uint64_t>(sampleRate));
+
+ resampler = speex_resampler_init(channelCount, sampleRate, destSampleRate,
+ SPEEX_RESAMPLER_QUALITY_DEFAULT, nullptr);
+ speex_resampler_skip_zeros(resampler);
+ resampledFrames += speex_resampler_get_output_latency(resampler);
+ }
+
+ // Allocate contiguous channel buffers. Note that if we end up resampling,
+ // we may write fewer bytes than mResampledFrames to the output buffer, in
+ // which case writeIndex will tell us how many valid samples we have.
+ mDecodeJob.mBuffer.mChannelData.SetLength(channelCount);
+#if AUDIO_OUTPUT_FORMAT == AUDIO_FORMAT_FLOAT32
+ // This buffer has separate channel arrays that could be transferred to
+ // JS::NewArrayBufferWithContents(), but AudioBuffer::RestoreJSChannelData()
+ // does not yet take advantage of this.
+ RefPtr<ThreadSharedFloatArrayBufferList> buffer =
+ ThreadSharedFloatArrayBufferList::Create(channelCount, resampledFrames,
+ fallible);
+ if (!buffer) {
+ LOG("MediaDecodeTask: Could not create final buffer (f32)");
+ ReportFailureOnMainThread(WebAudioDecodeJob::UnknownError);
+ return;
+ }
+ for (uint32_t i = 0; i < channelCount; ++i) {
+ mDecodeJob.mBuffer.mChannelData[i] = buffer->GetData(i);
+ }
+#else
+ CheckedInt<size_t> bufferSize(sizeof(AudioDataValue));
+ bufferSize *= resampledFrames;
+ bufferSize *= channelCount;
+ RefPtr<SharedBuffer> buffer = SharedBuffer::Create(bufferSize);
+ if (!buffer) {
+ LOG("MediaDecodeTask: Could not create final buffer (i16)");
+ ReportFailureOnMainThread(WebAudioDecodeJob::UnknownError);
+ return;
+ }
+ auto data = static_cast<AudioDataValue*>(floatBuffer->Data());
+ for (uint32_t i = 0; i < channelCount; ++i) {
+ mDecodeJob.mBuffer.mChannelData[i] = data;
+ data += resampledFrames;
+ }
+#endif
+ mDecodeJob.mBuffer.mBuffer = std::move(buffer);
+ mDecodeJob.mBuffer.mVolume = 1.0f;
+ mDecodeJob.mBuffer.mBufferFormat = AUDIO_OUTPUT_FORMAT;
+
+ uint32_t writeIndex = 0;
+ RefPtr<AudioData> audioData;
+ while ((audioData = mAudioQueue.PopFront())) {
+ if (!audioData->Frames()) {
+ // The packet contains no audio frames, skip it.
+ continue;
+ }
+ audioData->EnsureAudioBuffer(); // could lead to a copy :(
+ const AudioDataValue* bufferData =
+ static_cast<AudioDataValue*>(audioData->mAudioBuffer->Data());
+
+ if (sampleRate != destSampleRate) {
+ const uint32_t maxOutSamples = resampledFrames - writeIndex;
+
+ for (uint32_t i = 0; i < audioData->mChannels; ++i) {
+ uint32_t inSamples = audioData->Frames();
+ uint32_t outSamples = maxOutSamples;
+ AudioDataValue* outData =
+ mDecodeJob.mBuffer.ChannelDataForWrite<AudioDataValue>(i) +
+ writeIndex;
+
+ WebAudioUtils::SpeexResamplerProcess(
+ resampler, i, &bufferData[i * audioData->Frames()], &inSamples,
+ outData, &outSamples);
+
+ if (i == audioData->mChannels - 1) {
+ writeIndex += outSamples;
+ MOZ_ASSERT(writeIndex <= resampledFrames);
+ MOZ_ASSERT(inSamples == audioData->Frames());
+ }
+ }
+ } else {
+ for (uint32_t i = 0; i < audioData->mChannels; ++i) {
+ AudioDataValue* outData =
+ mDecodeJob.mBuffer.ChannelDataForWrite<AudioDataValue>(i) +
+ writeIndex;
+ PodCopy(outData, &bufferData[i * audioData->Frames()],
+ audioData->Frames());
+
+ if (i == audioData->mChannels - 1) {
+ writeIndex += audioData->Frames();
+ }
+ }
+ }
+ }
+
+ if (sampleRate != destSampleRate) {
+ uint32_t inputLatency = speex_resampler_get_input_latency(resampler);
+ const uint32_t maxOutSamples = resampledFrames - writeIndex;
+ for (uint32_t i = 0; i < channelCount; ++i) {
+ uint32_t inSamples = inputLatency;
+ uint32_t outSamples = maxOutSamples;
+ AudioDataValue* outData =
+ mDecodeJob.mBuffer.ChannelDataForWrite<AudioDataValue>(i) +
+ writeIndex;
+
+ WebAudioUtils::SpeexResamplerProcess(resampler, i,
+ (AudioDataValue*)nullptr, &inSamples,
+ outData, &outSamples);
+
+ if (i == channelCount - 1) {
+ writeIndex += outSamples;
+ MOZ_ASSERT(writeIndex <= resampledFrames);
+ MOZ_ASSERT(inSamples == inputLatency);
+ }
+ }
+ }
+
+ mDecodeJob.mBuffer.mDuration = writeIndex;
+ mPhase = PhaseEnum::AllocateBuffer;
+ mMainThread->Dispatch(do_AddRef(this));
+}
+
+void MediaDecodeTask::AllocateBuffer() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mDecodeJob.AllocateBuffer()) {
+ LOG("MediaDecodeTask: Could not allocate final buffer");
+ ReportFailureOnMainThread(WebAudioDecodeJob::UnknownError);
+ return;
+ }
+
+ mPhase = PhaseEnum::Done;
+ CallbackTheResult();
+}
+
+void MediaDecodeTask::CallbackTheResult() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ Cleanup();
+
+ // Now, we're ready to call the script back with the resulting buffer
+ mDecodeJob.OnSuccess(WebAudioDecodeJob::NoError);
+}
+
+bool WebAudioDecodeJob::AllocateBuffer() {
+ MOZ_ASSERT(!mOutput);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Now create the AudioBuffer
+ mOutput = AudioBuffer::Create(mContext->GetOwner(), mContext->SampleRate(),
+ std::move(mBuffer));
+ return mOutput != nullptr;
+}
+
+void AsyncDecodeWebAudio(const char* aContentType, uint8_t* aBuffer,
+ uint32_t aLength, WebAudioDecodeJob& aDecodeJob) {
+ Maybe<MediaContainerType> containerType =
+ MakeMediaContainerType(aContentType);
+ // Do not attempt to decode the media if we were not successful at sniffing
+ // the container type.
+ if (!*aContentType || strcmp(aContentType, APPLICATION_OCTET_STREAM) == 0 ||
+ !containerType) {
+ nsCOMPtr<nsIRunnable> event =
+ new ReportResultTask(aDecodeJob, &WebAudioDecodeJob::OnFailure,
+ WebAudioDecodeJob::UnknownContent);
+ JS_free(nullptr, aBuffer);
+ aDecodeJob.mContext->Dispatch(event.forget());
+ return;
+ }
+
+ RefPtr<MediaDecodeTask> task =
+ new MediaDecodeTask(*containerType, aBuffer, aLength, aDecodeJob);
+ if (!task->Init()) {
+ nsCOMPtr<nsIRunnable> event =
+ new ReportResultTask(aDecodeJob, &WebAudioDecodeJob::OnFailure,
+ WebAudioDecodeJob::UnknownError);
+ aDecodeJob.mContext->Dispatch(event.forget());
+ } else {
+ nsresult rv = task->PSupervisorTaskQueue()->Dispatch(task.forget());
+ MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
+ Unused << rv;
+ }
+}
+
+WebAudioDecodeJob::WebAudioDecodeJob(AudioContext* aContext, Promise* aPromise,
+ DecodeSuccessCallback* aSuccessCallback,
+ DecodeErrorCallback* aFailureCallback)
+ : mContext(aContext),
+ mPromise(aPromise),
+ mSuccessCallback(aSuccessCallback),
+ mFailureCallback(aFailureCallback) {
+ MOZ_ASSERT(aContext);
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_COUNT_CTOR(WebAudioDecodeJob);
+}
+
+WebAudioDecodeJob::~WebAudioDecodeJob() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_COUNT_DTOR(WebAudioDecodeJob);
+}
+
+void WebAudioDecodeJob::OnSuccess(ErrorCode aErrorCode) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aErrorCode == NoError);
+
+ RefPtr<AudioBuffer> output(mOutput);
+ if (mSuccessCallback) {
+ RefPtr<DecodeSuccessCallback> callback(mSuccessCallback);
+ // Ignore errors in calling the callback, since there is not much that we
+ // can do about it here.
+ callback->Call(*output);
+ }
+ mPromise->MaybeResolve(output);
+
+ mContext->RemoveFromDecodeQueue(this);
+}
+
+void WebAudioDecodeJob::OnFailure(ErrorCode aErrorCode) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ const char* errorMessage;
+ switch (aErrorCode) {
+ case UnknownContent:
+ errorMessage =
+ "The buffer passed to decodeAudioData contains an unknown content "
+ "type.";
+ break;
+ case InvalidContent:
+ errorMessage =
+ "The buffer passed to decodeAudioData contains invalid content which "
+ "cannot be decoded successfully.";
+ break;
+ case NoAudio:
+ errorMessage =
+ "The buffer passed to decodeAudioData does not contain any audio.";
+ break;
+ case NoError:
+ MOZ_FALLTHROUGH_ASSERT("Who passed NoError to OnFailure?");
+ // Fall through to get some sort of a sane error message if this actually
+ // happens at runtime.
+ case UnknownError:
+ [[fallthrough]];
+ default:
+ errorMessage =
+ "An unknown error occurred while processing decodeAudioData.";
+ break;
+ }
+
+ // Ignore errors in calling the callback, since there is not much that we can
+ // do about it here.
+ nsAutoCString errorString(errorMessage);
+ if (mFailureCallback) {
+ RefPtr<DOMException> exception = DOMException::Create(
+ NS_ERROR_DOM_ENCODING_NOT_SUPPORTED_ERR, errorString);
+ RefPtr<DecodeErrorCallback> callback(mFailureCallback);
+ callback->Call(*exception);
+ }
+
+ mPromise->MaybeRejectWithEncodingError(errorString);
+
+ mContext->RemoveFromDecodeQueue(this);
+}
+
+size_t WebAudioDecodeJob::SizeOfExcludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ size_t amount = 0;
+ if (mSuccessCallback) {
+ amount += mSuccessCallback->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ if (mFailureCallback) {
+ amount += mFailureCallback->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ if (mOutput) {
+ amount += mOutput->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ amount += mBuffer.SizeOfExcludingThis(aMallocSizeOf, false);
+ return amount;
+}
+
+size_t WebAudioDecodeJob::SizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+} // namespace mozilla
diff --git a/dom/media/webaudio/MediaBufferDecoder.h b/dom/media/webaudio/MediaBufferDecoder.h
new file mode 100644
index 0000000000..7af1acfd06
--- /dev/null
+++ b/dom/media/webaudio/MediaBufferDecoder.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MediaBufferDecoder_h_
+#define MediaBufferDecoder_h_
+
+#include "AudioSegment.h"
+#include "nsWrapperCache.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/TypedArray.h"
+#include "mozilla/MemoryReporting.h"
+
+namespace mozilla {
+
+class ThreadSharedFloatArrayBufferList;
+
+namespace dom {
+class AudioBuffer;
+class AudioContext;
+class DecodeErrorCallback;
+class DecodeSuccessCallback;
+class Promise;
+} // namespace dom
+
+struct WebAudioDecodeJob final {
+ // You may omit both the success and failure callback, or you must pass both.
+ // The callbacks are only necessary for asynchronous operation.
+ WebAudioDecodeJob(dom::AudioContext* aContext, dom::Promise* aPromise,
+ dom::DecodeSuccessCallback* aSuccessCallback = nullptr,
+ dom::DecodeErrorCallback* aFailureCallback = nullptr);
+ ~WebAudioDecodeJob();
+
+ enum ErrorCode {
+ NoError,
+ UnknownContent,
+ UnknownError,
+ InvalidContent,
+ NoAudio
+ };
+
+ typedef void (WebAudioDecodeJob::*ResultFn)(ErrorCode);
+
+ MOZ_CAN_RUN_SCRIPT void OnSuccess(ErrorCode /* ignored */);
+ MOZ_CAN_RUN_SCRIPT void OnFailure(ErrorCode aErrorCode);
+
+ bool AllocateBuffer();
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ AudioChunk mBuffer;
+ RefPtr<dom::AudioContext> mContext;
+ RefPtr<dom::Promise> mPromise;
+ RefPtr<dom::DecodeSuccessCallback> mSuccessCallback;
+ RefPtr<dom::DecodeErrorCallback> mFailureCallback; // can be null
+ RefPtr<dom::AudioBuffer> mOutput;
+};
+
+void AsyncDecodeWebAudio(const char* aContentType, uint8_t* aBuffer,
+ uint32_t aLength, WebAudioDecodeJob& aDecodeJob);
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webaudio/MediaElementAudioSourceNode.cpp b/dom/media/webaudio/MediaElementAudioSourceNode.cpp
new file mode 100644
index 0000000000..953fee66d9
--- /dev/null
+++ b/dom/media/webaudio/MediaElementAudioSourceNode.cpp
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaElementAudioSourceNode.h"
+#include "mozilla/dom/MediaElementAudioSourceNodeBinding.h"
+#include "AudioDestinationNode.h"
+#include "AudioNodeTrack.h"
+#include "MediaStreamTrack.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(MediaElementAudioSourceNode)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaElementAudioSourceNode)
+ tmp->Destroy();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mElement)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(AudioNode)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaElementAudioSourceNode,
+ MediaStreamAudioSourceNode)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mElement)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaElementAudioSourceNode)
+NS_INTERFACE_MAP_END_INHERITING(MediaStreamAudioSourceNode)
+
+NS_IMPL_ADDREF_INHERITED(MediaElementAudioSourceNode,
+ MediaStreamAudioSourceNode)
+NS_IMPL_RELEASE_INHERITED(MediaElementAudioSourceNode,
+ MediaStreamAudioSourceNode)
+
+MediaElementAudioSourceNode::MediaElementAudioSourceNode(
+ AudioContext* aContext, HTMLMediaElement* aElement)
+ : MediaStreamAudioSourceNode(aContext, TrackChangeBehavior::FollowChanges),
+ mElement(aElement) {
+ MOZ_ASSERT(aElement);
+}
+
+/* static */
+already_AddRefed<MediaElementAudioSourceNode>
+MediaElementAudioSourceNode::Create(
+ AudioContext& aAudioContext, const MediaElementAudioSourceOptions& aOptions,
+ ErrorResult& aRv) {
+ // The spec has a pointless check here. See
+ // https://github.com/WebAudio/web-audio-api/issues/2149
+ MOZ_RELEASE_ASSERT(!aAudioContext.IsOffline(), "Bindings messed up?");
+
+ RefPtr<MediaElementAudioSourceNode> node =
+ new MediaElementAudioSourceNode(&aAudioContext, aOptions.mMediaElement);
+
+ RefPtr<DOMMediaStream> stream = aOptions.mMediaElement->CaptureAudio(
+ aRv, aAudioContext.Destination()->Track()->Graph());
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ MOZ_ASSERT(stream, "CaptureAudio should report failure via aRv!");
+
+ node->Init(*stream, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ node->ListenForAllowedToPlay(aOptions);
+ return node.forget();
+}
+
+JSObject* MediaElementAudioSourceNode::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return MediaElementAudioSourceNode_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void MediaElementAudioSourceNode::ListenForAllowedToPlay(
+ const MediaElementAudioSourceOptions& aOptions) {
+ aOptions.mMediaElement->GetAllowedToPlayPromise()
+ ->Then(
+ AbstractThread::MainThread(), __func__,
+ // Capture by reference to bypass the mozilla-refcounted-inside-lambda
+ // static analysis. We capture a non-owning reference so as to allow
+ // cycle collection of the node. The reference is cleared via
+ // DisconnectIfExists() from Destroy() when the node is collected.
+ [&self = *this]() {
+ self.Context()->StartBlockedAudioContextIfAllowed();
+ self.mAllowedToPlayRequest.Complete();
+ })
+ ->Track(mAllowedToPlayRequest);
+}
+
+void MediaElementAudioSourceNode::Destroy() {
+ mAllowedToPlayRequest.DisconnectIfExists();
+ MediaStreamAudioSourceNode::Destroy();
+}
+
+HTMLMediaElement* MediaElementAudioSourceNode::MediaElement() {
+ return mElement;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/MediaElementAudioSourceNode.h b/dom/media/webaudio/MediaElementAudioSourceNode.h
new file mode 100644
index 0000000000..6ba21279fe
--- /dev/null
+++ b/dom/media/webaudio/MediaElementAudioSourceNode.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MediaElementAudioSourceNode_h_
+#define MediaElementAudioSourceNode_h_
+
+#include "MediaStreamAudioSourceNode.h"
+
+namespace mozilla::dom {
+
+class AudioContext;
+struct MediaElementAudioSourceOptions;
+
+class MediaElementAudioSourceNode final : public MediaStreamAudioSourceNode {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaElementAudioSourceNode,
+ MediaStreamAudioSourceNode)
+ static already_AddRefed<MediaElementAudioSourceNode> Create(
+ AudioContext& aAudioContext,
+ const MediaElementAudioSourceOptions& aOptions, ErrorResult& aRv);
+
+ static already_AddRefed<MediaElementAudioSourceNode> Constructor(
+ const GlobalObject& aGlobal, AudioContext& aAudioContext,
+ const MediaElementAudioSourceOptions& aOptions, ErrorResult& aRv) {
+ return Create(aAudioContext, aOptions, aRv);
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ const char* NodeType() const override {
+ return "MediaElementAudioSourceNode";
+ }
+
+ const char* CrossOriginErrorString() const override {
+ return "MediaElementAudioSourceNodeCrossOrigin";
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ HTMLMediaElement* MediaElement();
+
+ private:
+ explicit MediaElementAudioSourceNode(AudioContext* aContext,
+ HTMLMediaElement* aElement);
+ ~MediaElementAudioSourceNode() = default;
+
+ void Destroy() override;
+
+ // If AudioContext was not allowed to start, we would try to start it when
+ // source starts.
+ void ListenForAllowedToPlay(const MediaElementAudioSourceOptions& aOptions);
+
+ MozPromiseRequestHolder<GenericNonExclusivePromise> mAllowedToPlayRequest;
+
+ RefPtr<HTMLMediaElement> mElement;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/media/webaudio/MediaStreamAudioDestinationNode.cpp b/dom/media/webaudio/MediaStreamAudioDestinationNode.cpp
new file mode 100644
index 0000000000..704adaa304
--- /dev/null
+++ b/dom/media/webaudio/MediaStreamAudioDestinationNode.cpp
@@ -0,0 +1,150 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaStreamAudioDestinationNode.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/MediaStreamAudioDestinationNodeBinding.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeTrack.h"
+#include "AudioStreamTrack.h"
+#include "DOMMediaStream.h"
+#include "ForwardedInputTrack.h"
+
+namespace mozilla::dom {
+
+class AudioDestinationTrackSource final : public MediaStreamTrackSource {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AudioDestinationTrackSource,
+ MediaStreamTrackSource)
+
+ AudioDestinationTrackSource(MediaStreamAudioDestinationNode* aNode,
+ mozilla::MediaTrack* aInputTrack,
+ ProcessedMediaTrack* aTrack,
+ nsIPrincipal* aPrincipal)
+ : MediaStreamTrackSource(
+ aPrincipal, nsString(),
+ // We pass 0 here because tracking ids are video only for now.
+ TrackingId(TrackingId::Source::AudioDestinationNode, 0)),
+ mTrack(aTrack),
+ mPort(mTrack->AllocateInputPort(aInputTrack)),
+ mNode(aNode) {}
+
+ void Destroy() override {
+ if (!mTrack->IsDestroyed()) {
+ mTrack->Destroy();
+ mPort->Destroy();
+ }
+ if (mNode) {
+ mNode->DestroyMediaTrack();
+ mNode = nullptr;
+ }
+ }
+
+ MediaSourceEnum GetMediaSource() const override {
+ return MediaSourceEnum::AudioCapture;
+ }
+
+ void Stop() override { Destroy(); }
+
+ void Disable() override {}
+
+ void Enable() override {}
+
+ const RefPtr<ProcessedMediaTrack> mTrack;
+ const RefPtr<MediaInputPort> mPort;
+
+ private:
+ ~AudioDestinationTrackSource() = default;
+
+ RefPtr<MediaStreamAudioDestinationNode> mNode;
+};
+
+NS_IMPL_ADDREF_INHERITED(AudioDestinationTrackSource, MediaStreamTrackSource)
+NS_IMPL_RELEASE_INHERITED(AudioDestinationTrackSource, MediaStreamTrackSource)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AudioDestinationTrackSource)
+NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSource)
+NS_IMPL_CYCLE_COLLECTION_INHERITED(AudioDestinationTrackSource,
+ MediaStreamTrackSource, mNode)
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(MediaStreamAudioDestinationNode, AudioNode,
+ mDOMStream)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamAudioDestinationNode)
+NS_INTERFACE_MAP_END_INHERITING(AudioNode)
+
+NS_IMPL_ADDREF_INHERITED(MediaStreamAudioDestinationNode, AudioNode)
+NS_IMPL_RELEASE_INHERITED(MediaStreamAudioDestinationNode, AudioNode)
+
+MediaStreamAudioDestinationNode::MediaStreamAudioDestinationNode(
+ AudioContext* aContext)
+ : AudioNode(aContext, 2, ChannelCountMode::Explicit,
+ ChannelInterpretation::Speakers),
+ mDOMStream(MakeAndAddRef<DOMMediaStream>(GetOwner())) {
+ // Ensure an audio track with the correct ID is exposed to JS. If we can't get
+ // a principal here because the document is not available, pass in a null
+ // principal. This happens in edge cases when the document is being unloaded
+ // and it does not matter too much to have something working as long as it's
+ // not dangerous.
+ nsCOMPtr<nsIPrincipal> principal = nullptr;
+ if (aContext->GetParentObject()) {
+ Document* doc = aContext->GetParentObject()->GetExtantDoc();
+ principal = doc->NodePrincipal();
+ }
+ mTrack = AudioNodeTrack::Create(aContext, new AudioNodeEngine(this),
+ AudioNodeTrack::EXTERNAL_OUTPUT,
+ aContext->Graph());
+ auto source = MakeRefPtr<AudioDestinationTrackSource>(
+ this, mTrack,
+ aContext->Graph()->CreateForwardedInputTrack(MediaSegment::AUDIO),
+ principal);
+ auto track = MakeRefPtr<AudioStreamTrack>(GetOwner(), source->mTrack, source);
+ mDOMStream->AddTrackInternal(track);
+}
+
+/* static */
+already_AddRefed<MediaStreamAudioDestinationNode>
+MediaStreamAudioDestinationNode::Create(AudioContext& aAudioContext,
+ const AudioNodeOptions& aOptions,
+ ErrorResult& aRv) {
+ // The spec has a pointless check here. See
+ // https://github.com/WebAudio/web-audio-api/issues/2149
+ MOZ_RELEASE_ASSERT(!aAudioContext.IsOffline(), "Bindings messed up?");
+
+ RefPtr<MediaStreamAudioDestinationNode> audioNode =
+ new MediaStreamAudioDestinationNode(&aAudioContext);
+
+ audioNode->Initialize(aOptions, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return audioNode.forget();
+}
+
+size_t MediaStreamAudioDestinationNode::SizeOfExcludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ // Future:
+ // - mDOMStream
+ size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+ return amount;
+}
+
+size_t MediaStreamAudioDestinationNode::SizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+void MediaStreamAudioDestinationNode::DestroyMediaTrack() {
+ AudioNode::DestroyMediaTrack();
+}
+
+JSObject* MediaStreamAudioDestinationNode::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return MediaStreamAudioDestinationNode_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/MediaStreamAudioDestinationNode.h b/dom/media/webaudio/MediaStreamAudioDestinationNode.h
new file mode 100644
index 0000000000..24923955df
--- /dev/null
+++ b/dom/media/webaudio/MediaStreamAudioDestinationNode.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MediaStreamAudioDestinationNode_h_
+#define MediaStreamAudioDestinationNode_h_
+
+#include "AudioNode.h"
+
+namespace mozilla::dom {
+
+class AudioContext;
+struct AudioNodeOptions;
+
+class MediaStreamAudioDestinationNode final : public AudioNode {
+ public:
+ static already_AddRefed<MediaStreamAudioDestinationNode> Create(
+ AudioContext& aAudioContext, const AudioNodeOptions& aOptions,
+ ErrorResult& aRv);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaStreamAudioDestinationNode,
+ AudioNode)
+
+ static already_AddRefed<MediaStreamAudioDestinationNode> Constructor(
+ const GlobalObject& aGlobal, AudioContext& aAudioContext,
+ const AudioNodeOptions& aOptions, ErrorResult& aRv) {
+ return Create(aAudioContext, aOptions, aRv);
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ uint16_t NumberOfOutputs() const final { return 0; }
+
+ void DestroyMediaTrack() override;
+
+ DOMMediaStream* DOMStream() const { return mDOMStream; }
+
+ const char* NodeType() const override {
+ return "MediaStreamAudioDestinationNode";
+ }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+ private:
+ explicit MediaStreamAudioDestinationNode(AudioContext* aContext);
+ ~MediaStreamAudioDestinationNode() = default;
+
+ RefPtr<DOMMediaStream> mDOMStream;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/media/webaudio/MediaStreamAudioSourceNode.cpp b/dom/media/webaudio/MediaStreamAudioSourceNode.cpp
new file mode 100644
index 0000000000..a915e78859
--- /dev/null
+++ b/dom/media/webaudio/MediaStreamAudioSourceNode.cpp
@@ -0,0 +1,278 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaStreamAudioSourceNode.h"
+#include "mozilla/dom/MediaStreamAudioSourceNodeBinding.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeExternalInputTrack.h"
+#include "AudioStreamTrack.h"
+#include "mozilla/dom/Document.h"
+#include "nsContentUtils.h"
+#include "nsIScriptError.h"
+#include "nsID.h"
+#include "Tracing.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(MediaStreamAudioSourceNode)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaStreamAudioSourceNode)
+ tmp->Destroy();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mInputStream)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mInputTrack)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(AudioNode)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(MediaStreamAudioSourceNode,
+ AudioNode)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInputStream)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInputTrack)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamAudioSourceNode)
+NS_INTERFACE_MAP_END_INHERITING(AudioNode)
+
+NS_IMPL_ADDREF_INHERITED(MediaStreamAudioSourceNode, AudioNode)
+NS_IMPL_RELEASE_INHERITED(MediaStreamAudioSourceNode, AudioNode)
+
+MediaStreamAudioSourceNode::MediaStreamAudioSourceNode(
+ AudioContext* aContext, TrackChangeBehavior aBehavior)
+ : AudioNode(aContext, 2, ChannelCountMode::Max,
+ ChannelInterpretation::Speakers),
+ mBehavior(aBehavior) {}
+
+/* static */
+already_AddRefed<MediaStreamAudioSourceNode> MediaStreamAudioSourceNode::Create(
+ AudioContext& aAudioContext, const MediaStreamAudioSourceOptions& aOptions,
+ ErrorResult& aRv) {
+ // The spec has a pointless check here. See
+ // https://github.com/WebAudio/web-audio-api/issues/2149
+ MOZ_RELEASE_ASSERT(!aAudioContext.IsOffline(), "Bindings messed up?");
+
+ RefPtr<MediaStreamAudioSourceNode> node =
+ new MediaStreamAudioSourceNode(&aAudioContext, LockOnTrackPicked);
+
+ // aOptions.mMediaStream is not nullable.
+ node->Init(*aOptions.mMediaStream, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ return node.forget();
+}
+
+void MediaStreamAudioSourceNode::Init(DOMMediaStream& aMediaStream,
+ ErrorResult& aRv) {
+ mInputStream = &aMediaStream;
+ AudioNodeEngine* engine = new MediaStreamAudioSourceNodeEngine(this);
+ mTrack = AudioNodeExternalInputTrack::Create(Context()->Graph(), engine);
+ mInputStream->AddConsumerToKeepAlive(ToSupports(this));
+
+ mInputStream->RegisterTrackListener(this);
+ if (mInputStream->Audible()) {
+ NotifyAudible();
+ }
+ AttachToRightTrack(mInputStream, aRv);
+}
+
+void MediaStreamAudioSourceNode::Destroy() {
+ if (mInputStream) {
+ mInputStream->UnregisterTrackListener(this);
+ mInputStream = nullptr;
+ }
+ DetachFromTrack();
+}
+
+MediaStreamAudioSourceNode::~MediaStreamAudioSourceNode() { Destroy(); }
+
+void MediaStreamAudioSourceNode::AttachToTrack(
+ const RefPtr<MediaStreamTrack>& aTrack, ErrorResult& aRv) {
+ MOZ_ASSERT(!mInputTrack);
+ MOZ_ASSERT(aTrack->AsAudioStreamTrack());
+ MOZ_DIAGNOSTIC_ASSERT(!aTrack->Ended());
+
+ if (!mTrack) {
+ return;
+ }
+
+ if (NS_WARN_IF(Context()->Graph() != aTrack->Graph())) {
+ nsCOMPtr<nsPIDOMWindowInner> pWindow = Context()->GetParentObject();
+ Document* document = pWindow ? pWindow->GetExtantDoc() : nullptr;
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Web Audio"_ns,
+ document, nsContentUtils::eDOM_PROPERTIES,
+ "MediaStreamAudioSourceNodeDifferentRate");
+ // This is not a spec-required exception, just a limitation of our
+ // implementation.
+ aRv.ThrowNotSupportedError(
+ "Connecting AudioNodes from AudioContexts with different sample-rate "
+ "is currently not supported.");
+ return;
+ }
+
+ mInputTrack = aTrack;
+ ProcessedMediaTrack* outputTrack =
+ static_cast<ProcessedMediaTrack*>(mTrack.get());
+ mInputPort = mInputTrack->ForwardTrackContentsTo(outputTrack);
+ PrincipalChanged(mInputTrack); // trigger enabling/disabling of the connector
+ mInputTrack->AddPrincipalChangeObserver(this);
+ MarkActive();
+}
+
+void MediaStreamAudioSourceNode::DetachFromTrack() {
+ if (mInputTrack) {
+ mInputTrack->RemovePrincipalChangeObserver(this);
+ mInputTrack = nullptr;
+ }
+ if (mInputPort) {
+ mInputPort->Destroy();
+ mInputPort = nullptr;
+ }
+}
+
+static int AudioTrackCompare(const RefPtr<AudioStreamTrack>& aLhs,
+ const RefPtr<AudioStreamTrack>& aRhs) {
+ nsAutoStringN<NSID_LENGTH> IDLhs;
+ nsAutoStringN<NSID_LENGTH> IDRhs;
+ aLhs->GetId(IDLhs);
+ aRhs->GetId(IDRhs);
+ return Compare(NS_ConvertUTF16toUTF8(IDLhs), NS_ConvertUTF16toUTF8(IDRhs));
+}
+
+void MediaStreamAudioSourceNode::AttachToRightTrack(
+ const RefPtr<DOMMediaStream>& aMediaStream, ErrorResult& aRv) {
+ nsTArray<RefPtr<AudioStreamTrack>> tracks;
+ aMediaStream->GetAudioTracks(tracks);
+
+ if (tracks.IsEmpty() && mBehavior == LockOnTrackPicked) {
+ aRv.ThrowInvalidStateError("No audio tracks in MediaStream");
+ return;
+ }
+
+ // Sort the track to have a stable order, on their ID by lexicographic
+ // ordering on sequences of code unit values.
+ tracks.Sort(AudioTrackCompare);
+
+ for (const RefPtr<AudioStreamTrack>& track : tracks) {
+ if (mBehavior == FollowChanges) {
+ if (track->Ended()) {
+ continue;
+ }
+ }
+
+ if (!track->Ended()) {
+ AttachToTrack(track, aRv);
+ }
+ return;
+ }
+
+ // There was no track available. We'll allow the node to be garbage collected.
+ MarkInactive();
+}
+
+void MediaStreamAudioSourceNode::NotifyTrackAdded(
+ const RefPtr<MediaStreamTrack>& aTrack) {
+ if (mBehavior != FollowChanges) {
+ return;
+ }
+ if (mInputTrack) {
+ return;
+ }
+
+ if (!aTrack->AsAudioStreamTrack()) {
+ return;
+ }
+
+ AttachToTrack(aTrack, IgnoreErrors());
+}
+
+void MediaStreamAudioSourceNode::NotifyTrackRemoved(
+ const RefPtr<MediaStreamTrack>& aTrack) {
+ if (mBehavior == FollowChanges) {
+ if (aTrack != mInputTrack) {
+ return;
+ }
+
+ DetachFromTrack();
+ AttachToRightTrack(mInputStream, IgnoreErrors());
+ }
+}
+
+void MediaStreamAudioSourceNode::NotifyAudible() {
+ MOZ_ASSERT(mInputStream);
+ Context()->StartBlockedAudioContextIfAllowed();
+}
+
+/**
+ * Changes the principal. Note that this will be called on the main thread, but
+ * changes will be enacted on the MediaTrackGraph thread. If the principal
+ * change results in the document principal losing access to the stream, then
+ * there needs to be other measures in place to ensure that any media that is
+ * governed by the new stream principal is not available to the MediaTrackGraph
+ * before this change completes. Otherwise, a site could get access to
+ * media that they are not authorized to receive.
+ *
+ * One solution is to block the altered content, call this method, then dispatch
+ * another change request to the MediaTrackGraph thread that allows the content
+ * under the new principal to flow. This might be unnecessary if the principal
+ * change is changing to be the document principal.
+ */
+void MediaStreamAudioSourceNode::PrincipalChanged(
+ MediaStreamTrack* aMediaStreamTrack) {
+ MOZ_ASSERT(aMediaStreamTrack == mInputTrack);
+
+ bool subsumes = false;
+ Document* doc = nullptr;
+ if (nsPIDOMWindowInner* parent = Context()->GetParentObject()) {
+ doc = parent->GetExtantDoc();
+ if (doc) {
+ nsIPrincipal* docPrincipal = doc->NodePrincipal();
+ nsIPrincipal* trackPrincipal = aMediaStreamTrack->GetPrincipal();
+ if (!trackPrincipal ||
+ NS_FAILED(docPrincipal->Subsumes(trackPrincipal, &subsumes))) {
+ subsumes = false;
+ }
+ }
+ }
+ auto track = static_cast<AudioNodeExternalInputTrack*>(mTrack.get());
+ bool enabled = subsumes;
+ track->SetInt32Parameter(MediaStreamAudioSourceNodeEngine::ENABLE, enabled);
+
+ if (!enabled && doc) {
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Web Audio"_ns,
+ doc, nsContentUtils::eDOM_PROPERTIES,
+ CrossOriginErrorString());
+ }
+}
+
+size_t MediaStreamAudioSourceNode::SizeOfExcludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ // Future:
+ // - mInputStream
+ size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+ if (mInputPort) {
+ amount += mInputPort->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ return amount;
+}
+
+size_t MediaStreamAudioSourceNode::SizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+void MediaStreamAudioSourceNode::DestroyMediaTrack() {
+ if (mInputPort) {
+ mInputPort->Destroy();
+ mInputPort = nullptr;
+ }
+ AudioNode::DestroyMediaTrack();
+}
+
+JSObject* MediaStreamAudioSourceNode::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return MediaStreamAudioSourceNode_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/MediaStreamAudioSourceNode.h b/dom/media/webaudio/MediaStreamAudioSourceNode.h
new file mode 100644
index 0000000000..1875fc2e83
--- /dev/null
+++ b/dom/media/webaudio/MediaStreamAudioSourceNode.h
@@ -0,0 +1,127 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MediaStreamAudioSourceNode_h_
+#define MediaStreamAudioSourceNode_h_
+
+#include "AudioNode.h"
+#include "AudioNodeEngine.h"
+#include "DOMMediaStream.h"
+#include "PrincipalChangeObserver.h"
+
+namespace mozilla::dom {
+
+class AudioContext;
+struct MediaStreamAudioSourceOptions;
+
+class MediaStreamAudioSourceNodeEngine final : public AudioNodeEngine {
+ public:
+ explicit MediaStreamAudioSourceNodeEngine(AudioNode* aNode)
+ : AudioNodeEngine(aNode), mEnabled(false) {}
+
+ bool IsEnabled() const { return mEnabled; }
+ enum Parameters { ENABLE };
+ void SetInt32Parameter(uint32_t aIndex, int32_t aValue) override {
+ switch (aIndex) {
+ case ENABLE:
+ mEnabled = !!aValue;
+ break;
+ default:
+ NS_ERROR("MediaStreamAudioSourceNodeEngine bad parameter index");
+ }
+ }
+
+ private:
+ bool mEnabled;
+};
+
+class MediaStreamAudioSourceNode
+ : public AudioNode,
+ public DOMMediaStream::TrackListener,
+ public PrincipalChangeObserver<MediaStreamTrack> {
+ public:
+ static already_AddRefed<MediaStreamAudioSourceNode> Create(
+ AudioContext& aContext, const MediaStreamAudioSourceOptions& aOptions,
+ ErrorResult& aRv);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaStreamAudioSourceNode,
+ AudioNode)
+
+ static already_AddRefed<MediaStreamAudioSourceNode> Constructor(
+ const GlobalObject& aGlobal, AudioContext& aAudioContext,
+ const MediaStreamAudioSourceOptions& aOptions, ErrorResult& aRv) {
+ return Create(aAudioContext, aOptions, aRv);
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void DestroyMediaTrack() override;
+
+ uint16_t NumberOfInputs() const override { return 0; }
+
+ DOMMediaStream* GetMediaStream() { return mInputStream; }
+
+ const char* NodeType() const override { return "MediaStreamAudioSourceNode"; }
+
+ virtual const char* CrossOriginErrorString() const {
+ return "MediaStreamAudioSourceNodeCrossOrigin";
+ }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+ // Attaches to aTrack so that its audio content will be used as input.
+ void AttachToTrack(const RefPtr<MediaStreamTrack>& aTrack, ErrorResult& aRv);
+
+ // Detaches from the currently attached track if there is one.
+ void DetachFromTrack();
+
+ // Attaches to the first audio track in the MediaStream, when the tracks are
+ // ordered by id.
+ void AttachToRightTrack(const RefPtr<DOMMediaStream>& aMediaStream,
+ ErrorResult& aRv);
+
+ // From DOMMediaStream::TrackListener.
+ void NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack) override;
+ void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) override;
+ void NotifyAudible() override;
+
+ // From PrincipalChangeObserver<MediaStreamTrack>.
+ void PrincipalChanged(MediaStreamTrack* aMediaStreamTrack) override;
+
+ // This allows implementing the correct behaviour for both
+ // MediaElementAudioSourceNode and MediaStreamAudioSourceNode, that have most
+ // of their behaviour shared.
+ enum TrackChangeBehavior {
+ // MediaStreamAudioSourceNode locks on the track it picked, and never
+ // changes.
+ LockOnTrackPicked,
+ // MediaElementAudioSourceNode can change track, depending on what the
+ // HTMLMediaElement does.
+ FollowChanges
+ };
+
+ protected:
+ MediaStreamAudioSourceNode(AudioContext* aContext,
+ TrackChangeBehavior aBehavior);
+ void Init(DOMMediaStream& aMediaStream, ErrorResult& aRv);
+ virtual void Destroy();
+ virtual ~MediaStreamAudioSourceNode();
+
+ private:
+ const TrackChangeBehavior mBehavior;
+ RefPtr<MediaInputPort> mInputPort;
+ RefPtr<DOMMediaStream> mInputStream;
+
+ // On construction we set this to the first audio track of mInputStream.
+ RefPtr<MediaStreamTrack> mInputTrack;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/media/webaudio/MediaStreamTrackAudioSourceNode.cpp b/dom/media/webaudio/MediaStreamTrackAudioSourceNode.cpp
new file mode 100644
index 0000000000..f3948a33ad
--- /dev/null
+++ b/dom/media/webaudio/MediaStreamTrackAudioSourceNode.cpp
@@ -0,0 +1,200 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaStreamTrackAudioSourceNode.h"
+#include "mozilla/dom/MediaStreamTrackAudioSourceNodeBinding.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeExternalInputTrack.h"
+#include "AudioStreamTrack.h"
+#include "mozilla/dom/Document.h"
+#include "nsContentUtils.h"
+#include "nsIScriptError.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(MediaStreamTrackAudioSourceNode)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(MediaStreamTrackAudioSourceNode)
+ tmp->Destroy();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mInputTrack)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(AudioNode)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(
+ MediaStreamTrackAudioSourceNode, AudioNode)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInputTrack)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaStreamTrackAudioSourceNode)
+NS_INTERFACE_MAP_END_INHERITING(AudioNode)
+
+NS_IMPL_ADDREF_INHERITED(MediaStreamTrackAudioSourceNode, AudioNode)
+NS_IMPL_RELEASE_INHERITED(MediaStreamTrackAudioSourceNode, AudioNode)
+
+MediaStreamTrackAudioSourceNode::MediaStreamTrackAudioSourceNode(
+ AudioContext* aContext)
+ : AudioNode(aContext, 2, ChannelCountMode::Max,
+ ChannelInterpretation::Speakers),
+ mTrackListener(this) {}
+
+/* static */ already_AddRefed<MediaStreamTrackAudioSourceNode>
+MediaStreamTrackAudioSourceNode::Create(
+ AudioContext& aAudioContext,
+ const MediaStreamTrackAudioSourceOptions& aOptions, ErrorResult& aRv) {
+ // The spec has a pointless check here. See
+ // https://github.com/WebAudio/web-audio-api/issues/2149
+ MOZ_RELEASE_ASSERT(!aAudioContext.IsOffline(), "Bindings messed up?");
+
+ if (!aOptions.mMediaStreamTrack->Ended() &&
+ aAudioContext.Graph() != aOptions.mMediaStreamTrack->Graph()) {
+ nsCOMPtr<nsPIDOMWindowInner> pWindow = aAudioContext.GetParentObject();
+ Document* document = pWindow ? pWindow->GetExtantDoc() : nullptr;
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Web Audio"_ns,
+ document, nsContentUtils::eDOM_PROPERTIES,
+ "MediaStreamAudioSourceNodeDifferentRate");
+ // This is not a spec-required exception, just a limitation of our
+ // implementation.
+ aRv.ThrowNotSupportedError(
+ "Connecting AudioNodes from AudioContexts with different sample-rate "
+ "is currently not supported.");
+ return nullptr;
+ }
+
+ RefPtr<MediaStreamTrackAudioSourceNode> node =
+ new MediaStreamTrackAudioSourceNode(&aAudioContext);
+
+ node->Init(aOptions.mMediaStreamTrack, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ return node.forget();
+}
+
+void MediaStreamTrackAudioSourceNode::Init(MediaStreamTrack* aMediaStreamTrack,
+ ErrorResult& aRv) {
+ MOZ_ASSERT(aMediaStreamTrack);
+
+ if (!aMediaStreamTrack->AsAudioStreamTrack()) {
+ aRv.ThrowInvalidStateError("\"mediaStreamTrack\" must be an audio track");
+ return;
+ }
+
+ if (aMediaStreamTrack->Ended()) {
+ // The track is ended and will never produce any data. Pretend like this is
+ // fine.
+ return;
+ }
+
+ MarkActive();
+
+ MediaTrackGraph* graph = Context()->Graph();
+
+ AudioNodeEngine* engine = new MediaStreamTrackAudioSourceNodeEngine(this);
+ mTrack = AudioNodeExternalInputTrack::Create(graph, engine);
+
+ MOZ_ASSERT(mTrack);
+
+ mInputTrack = aMediaStreamTrack;
+ ProcessedMediaTrack* outputTrack =
+ static_cast<ProcessedMediaTrack*>(mTrack.get());
+ mInputPort = mInputTrack->ForwardTrackContentsTo(outputTrack);
+ PrincipalChanged(mInputTrack); // trigger enabling/disabling of the connector
+ mInputTrack->AddPrincipalChangeObserver(this);
+
+ mInputTrack->AddConsumer(&mTrackListener);
+}
+
+void MediaStreamTrackAudioSourceNode::Destroy() {
+ if (mInputTrack) {
+ mTrackListener.NotifyEnded(mInputTrack);
+ mInputTrack->RemovePrincipalChangeObserver(this);
+ mInputTrack->RemoveConsumer(&mTrackListener);
+ mInputTrack = nullptr;
+ }
+
+ if (mInputPort) {
+ mInputPort->Destroy();
+ mInputPort = nullptr;
+ }
+}
+
+MediaStreamTrackAudioSourceNode::~MediaStreamTrackAudioSourceNode() {
+ Destroy();
+}
+
+/**
+ * Changes the principal. Note that this will be called on the main thread, but
+ * changes will be enacted on the MediaTrackGraph thread. If the principal
+ * change results in the document principal losing access to the track, then
+ * there needs to be other measures in place to ensure that any media that is
+ * governed by the new track principal is not available to the MediaTrackGraph
+ * before this change completes. Otherwise, a site could get access to
+ * media that they are not authorized to receive.
+ *
+ * One solution is to block the altered content, call this method, then dispatch
+ * another change request to the MediaTrackGraph thread that allows the content
+ * under the new principal to flow. This might be unnecessary if the principal
+ * change is changing to be the document principal.
+ */
+void MediaStreamTrackAudioSourceNode::PrincipalChanged(
+ MediaStreamTrack* aMediaStreamTrack) {
+ MOZ_ASSERT(aMediaStreamTrack == mInputTrack);
+
+ bool subsumes = false;
+ Document* doc = nullptr;
+ if (nsPIDOMWindowInner* parent = Context()->GetParentObject()) {
+ doc = parent->GetExtantDoc();
+ if (doc) {
+ nsIPrincipal* docPrincipal = doc->NodePrincipal();
+ nsIPrincipal* trackPrincipal = aMediaStreamTrack->GetPrincipal();
+ if (!trackPrincipal ||
+ NS_FAILED(docPrincipal->Subsumes(trackPrincipal, &subsumes))) {
+ subsumes = false;
+ }
+ }
+ }
+ auto track = static_cast<AudioNodeExternalInputTrack*>(mTrack.get());
+ bool enabled = subsumes;
+ track->SetInt32Parameter(MediaStreamTrackAudioSourceNodeEngine::ENABLE,
+ enabled);
+ fprintf(stderr, "NOW: %s", enabled ? "enabled" : "disabled");
+
+ if (!enabled && doc) {
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Web Audio"_ns,
+ doc, nsContentUtils::eDOM_PROPERTIES,
+ CrossOriginErrorString());
+ }
+}
+
+size_t MediaStreamTrackAudioSourceNode::SizeOfExcludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+ if (mInputPort) {
+ amount += mInputPort->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ return amount;
+}
+
+size_t MediaStreamTrackAudioSourceNode::SizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+void MediaStreamTrackAudioSourceNode::DestroyMediaTrack() {
+ if (mInputPort) {
+ mInputPort->Destroy();
+ mInputPort = nullptr;
+ }
+ AudioNode::DestroyMediaTrack();
+}
+
+JSObject* MediaStreamTrackAudioSourceNode::WrapObject(
+ JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return MediaStreamTrackAudioSourceNode_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/MediaStreamTrackAudioSourceNode.h b/dom/media/webaudio/MediaStreamTrackAudioSourceNode.h
new file mode 100644
index 0000000000..bb3d5d6c94
--- /dev/null
+++ b/dom/media/webaudio/MediaStreamTrackAudioSourceNode.h
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MediaStreamTrackAudioSourceNode_h_
+#define MediaStreamTrackAudioSourceNode_h_
+
+#include "AudioNode.h"
+#include "AudioNodeEngine.h"
+#include "mozilla/dom/MediaStreamTrack.h"
+#include "mozilla/WeakPtr.h"
+#include "PrincipalChangeObserver.h"
+
+namespace mozilla::dom {
+
+class AudioContext;
+struct MediaStreamTrackAudioSourceOptions;
+
+class MediaStreamTrackAudioSourceNodeEngine final : public AudioNodeEngine {
+ public:
+ explicit MediaStreamTrackAudioSourceNodeEngine(AudioNode* aNode)
+ : AudioNodeEngine(aNode), mEnabled(false) {}
+
+ bool IsEnabled() const { return mEnabled; }
+ enum Parameters { ENABLE };
+ void SetInt32Parameter(uint32_t aIndex, int32_t aValue) override {
+ switch (aIndex) {
+ case ENABLE:
+ mEnabled = !!aValue;
+ break;
+ default:
+ NS_ERROR("MediaStreamTrackAudioSourceNodeEngine bad parameter index");
+ }
+ }
+
+ private:
+ bool mEnabled;
+};
+
+class MediaStreamTrackAudioSourceNode
+ : public AudioNode,
+ public PrincipalChangeObserver<MediaStreamTrack>,
+ public SupportsWeakPtr {
+ public:
+ static already_AddRefed<MediaStreamTrackAudioSourceNode> Create(
+ AudioContext& aContext,
+ const MediaStreamTrackAudioSourceOptions& aOptions, ErrorResult& aRv);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaStreamTrackAudioSourceNode,
+ AudioNode)
+
+ static already_AddRefed<MediaStreamTrackAudioSourceNode> Constructor(
+ const GlobalObject& aGlobal, AudioContext& aAudioContext,
+ const MediaStreamTrackAudioSourceOptions& aOptions, ErrorResult& aRv) {
+ return Create(aAudioContext, aOptions, aRv);
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void DestroyMediaTrack() override;
+
+ uint16_t NumberOfInputs() const override { return 0; }
+
+ const char* NodeType() const override {
+ return "MediaStreamTrackAudioSourceNode";
+ }
+
+ virtual const char* CrossOriginErrorString() const {
+ return "MediaStreamTrackAudioSourceNodeCrossOrigin";
+ }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+ // From PrincipalChangeObserver<MediaStreamTrack>.
+ void PrincipalChanged(MediaStreamTrack* aMediaStreamTrack) override;
+
+ protected:
+ explicit MediaStreamTrackAudioSourceNode(AudioContext* aContext);
+ void Init(MediaStreamTrack* aMediaStreamTrack, ErrorResult& aRv);
+ void Destroy();
+ virtual ~MediaStreamTrackAudioSourceNode();
+
+ class TrackListener : public MediaStreamTrackConsumer {
+ public:
+ explicit TrackListener(MediaStreamTrackAudioSourceNode* aNode)
+ : mNode(aNode) {}
+
+ void NotifyEnded(MediaStreamTrack* aTrack) override {
+ if (mNode) {
+ mNode->MarkInactive();
+ mNode->DestroyMediaTrack();
+ mNode = nullptr;
+ }
+ }
+
+ private:
+ WeakPtr<MediaStreamTrackAudioSourceNode> mNode;
+ };
+
+ private:
+ RefPtr<MediaInputPort> mInputPort;
+ RefPtr<MediaStreamTrack> mInputTrack;
+ TrackListener mTrackListener;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/media/webaudio/OscillatorNode.cpp b/dom/media/webaudio/OscillatorNode.cpp
new file mode 100644
index 0000000000..70592f6e96
--- /dev/null
+++ b/dom/media/webaudio/OscillatorNode.cpp
@@ -0,0 +1,560 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "OscillatorNode.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeTrack.h"
+#include "AudioDestinationNode.h"
+#include "nsContentUtils.h"
+#include "WebAudioUtils.h"
+#include "blink/PeriodicWave.h"
+#include "Tracing.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(OscillatorNode, AudioScheduledSourceNode,
+ mPeriodicWave, mFrequency, mDetune)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(OscillatorNode)
+NS_INTERFACE_MAP_END_INHERITING(AudioScheduledSourceNode)
+
+NS_IMPL_ADDREF_INHERITED(OscillatorNode, AudioScheduledSourceNode)
+NS_IMPL_RELEASE_INHERITED(OscillatorNode, AudioScheduledSourceNode)
+
+class OscillatorNodeEngine final : public AudioNodeEngine {
+ public:
+ OscillatorNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination)
+ : AudioNodeEngine(aNode),
+ mSource(nullptr),
+ mDestination(aDestination->Track()),
+ mStart(-1),
+ mStop(TRACK_TIME_MAX)
+ // Keep the default values in sync with OscillatorNode::OscillatorNode.
+ ,
+ mFrequency(440.f),
+ mDetune(0.f),
+ mType(OscillatorType::Sine),
+ mPhase(0.),
+ mFinalFrequency(0.),
+ mPhaseIncrement(0.),
+ mRecomputeParameters(true),
+ mCustomDisableNormalization(false) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mBasicWaveFormCache = aDestination->Context()->GetBasicWaveFormCache();
+ }
+
+ void SetSourceTrack(AudioNodeTrack* aSource) { mSource = aSource; }
+
+ enum Parameters {
+ FREQUENCY,
+ DETUNE,
+ TYPE,
+ DISABLE_NORMALIZATION,
+ START,
+ STOP,
+ };
+ void RecvTimelineEvent(uint32_t aIndex, AudioParamEvent& aEvent) override {
+ mRecomputeParameters = true;
+
+ MOZ_ASSERT(mDestination);
+
+ aEvent.ConvertToTicks(mDestination);
+
+ switch (aIndex) {
+ case FREQUENCY:
+ mFrequency.InsertEvent<int64_t>(aEvent);
+ break;
+ case DETUNE:
+ mDetune.InsertEvent<int64_t>(aEvent);
+ break;
+ default:
+ NS_ERROR("Bad OscillatorNodeEngine TimelineParameter");
+ }
+ }
+
+ void SetTrackTimeParameter(uint32_t aIndex, TrackTime aParam) override {
+ switch (aIndex) {
+ case START:
+ mStart = aParam;
+ mSource->SetActive();
+ break;
+ case STOP:
+ mStop = aParam;
+ break;
+ default:
+ NS_ERROR("Bad OscillatorNodeEngine TrackTimeParameter");
+ }
+ }
+
+ void SetInt32Parameter(uint32_t aIndex, int32_t aParam) override {
+ switch (aIndex) {
+ case TYPE:
+ // Set the new type.
+ mType = static_cast<OscillatorType>(aParam);
+ if (mType == OscillatorType::Sine) {
+ // Forget any previous custom data.
+ mCustomDisableNormalization = false;
+ mPeriodicWave = nullptr;
+ mRecomputeParameters = true;
+ }
+ switch (mType) {
+ case OscillatorType::Sine:
+ mPhase = 0.0;
+ break;
+ case OscillatorType::Square:
+ case OscillatorType::Triangle:
+ case OscillatorType::Sawtooth:
+ mPeriodicWave = mBasicWaveFormCache->GetBasicWaveForm(mType);
+ break;
+ case OscillatorType::Custom:
+ break;
+ default:
+ NS_ERROR("Bad OscillatorNodeEngine type parameter.");
+ }
+ // End type switch.
+ break;
+ case DISABLE_NORMALIZATION:
+ MOZ_ASSERT(aParam >= 0, "negative custom array length");
+ mCustomDisableNormalization = static_cast<uint32_t>(aParam);
+ break;
+ default:
+ NS_ERROR("Bad OscillatorNodeEngine Int32Parameter.");
+ }
+ // End index switch.
+ }
+
+ void SetBuffer(AudioChunk&& aBuffer) override {
+ MOZ_ASSERT(aBuffer.ChannelCount() == 2,
+ "PeriodicWave should have sent two channels");
+ MOZ_ASSERT(aBuffer.mVolume == 1.0f);
+ mPeriodicWave = WebCore::PeriodicWave::create(
+ mSource->mSampleRate, aBuffer.ChannelData<float>()[0],
+ aBuffer.ChannelData<float>()[1], aBuffer.mDuration,
+ mCustomDisableNormalization);
+ }
+
+ void IncrementPhase() {
+ const float twoPiFloat = float(2 * M_PI);
+ mPhase += mPhaseIncrement;
+ if (mPhase > twoPiFloat) {
+ mPhase -= twoPiFloat;
+ } else if (mPhase < -twoPiFloat) {
+ mPhase += twoPiFloat;
+ }
+ }
+
+ // Returns true if the final frequency (and thus the phase increment) changed,
+ // false otherwise. This allow some optimizations at callsite.
+ bool UpdateParametersIfNeeded(size_t aIndexInBlock,
+ const float aFrequency[WEBAUDIO_BLOCK_SIZE],
+ const float aDetune[WEBAUDIO_BLOCK_SIZE]) {
+ // Shortcut if frequency-related AudioParam are not automated, and we
+ // already have computed the frequency information and related parameters.
+ if (!ParametersMayNeedUpdate()) {
+ return false;
+ }
+
+ float detune = aDetune[aIndexInBlock];
+ if (detune != mLastDetune) {
+ mLastDetune = detune;
+ // Single-precision fdlibm_exp2f() would sometimes amplify rounding
+ // error in the division for large detune.
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1849806#c4
+ mDetuneRatio = fdlibm_exp2(detune / 1200.);
+ }
+ float finalFrequency = aFrequency[aIndexInBlock] * mDetuneRatio;
+ mRecomputeParameters = false;
+
+ if (finalFrequency == mFinalFrequency) {
+ return false;
+ }
+
+ mFinalFrequency = finalFrequency;
+ mPhaseIncrement = 2 * M_PI * finalFrequency / mSource->mSampleRate;
+ return true;
+ }
+
+ void FillBounds(float* output, TrackTime ticks, uint32_t& start,
+ uint32_t& end) {
+ MOZ_ASSERT(output);
+ static_assert(TrackTime(WEBAUDIO_BLOCK_SIZE) < UINT_MAX,
+ "WEBAUDIO_BLOCK_SIZE overflows interator bounds.");
+ start = 0;
+ if (ticks < mStart) {
+ start = mStart - ticks;
+ for (uint32_t i = 0; i < start; ++i) {
+ output[i] = 0.0;
+ }
+ }
+ end = WEBAUDIO_BLOCK_SIZE;
+ if (ticks + end > mStop) {
+ end = mStop - ticks;
+ for (uint32_t i = end; i < WEBAUDIO_BLOCK_SIZE; ++i) {
+ output[i] = 0.0;
+ }
+ }
+ }
+
+ void ComputeSine(float* aOutput, uint32_t aStart, uint32_t aEnd,
+ const float aFrequency[WEBAUDIO_BLOCK_SIZE],
+ const float aDetune[WEBAUDIO_BLOCK_SIZE]) {
+ for (uint32_t i = aStart; i < aEnd; ++i) {
+ // We ignore the return value, changing the frequency has no impact on
+ // performances here.
+ UpdateParametersIfNeeded(i, aFrequency, aDetune);
+
+ aOutput[i] = fdlibm_sinf(mPhase);
+
+ IncrementPhase();
+ }
+ }
+
+ bool ParametersMayNeedUpdate() {
+ return !mDetune.HasSimpleValue() || !mFrequency.HasSimpleValue() ||
+ mRecomputeParameters;
+ }
+
+ void ComputeCustom(float* aOutput, uint32_t aStart, uint32_t aEnd,
+ const float aFrequency[WEBAUDIO_BLOCK_SIZE],
+ const float aDetune[WEBAUDIO_BLOCK_SIZE]) {
+ MOZ_ASSERT(mPeriodicWave, "No custom waveform data");
+
+ uint32_t periodicWaveSize = mPeriodicWave->periodicWaveSize();
+ // Mask to wrap wave data indices into the range [0,periodicWaveSize).
+ uint32_t indexMask = periodicWaveSize - 1;
+ MOZ_ASSERT(periodicWaveSize && (periodicWaveSize & indexMask) == 0,
+ "periodicWaveSize must be power of 2");
+ float* higherWaveData = nullptr;
+ float* lowerWaveData = nullptr;
+ float tableInterpolationFactor;
+ // Phase increment at frequency of 1 Hz.
+ // mPhase runs [0,periodicWaveSize) here instead of [0,2*M_PI).
+ float basePhaseIncrement = mPeriodicWave->rateScale();
+
+ bool needToFetchWaveData =
+ UpdateParametersIfNeeded(aStart, aFrequency, aDetune);
+
+ bool parametersMayNeedUpdate = ParametersMayNeedUpdate();
+ mPeriodicWave->waveDataForFundamentalFrequency(
+ mFinalFrequency, lowerWaveData, higherWaveData,
+ tableInterpolationFactor);
+
+ for (uint32_t i = aStart; i < aEnd; ++i) {
+ if (parametersMayNeedUpdate) {
+ if (needToFetchWaveData) {
+ mPeriodicWave->waveDataForFundamentalFrequency(
+ mFinalFrequency, lowerWaveData, higherWaveData,
+ tableInterpolationFactor);
+ }
+ needToFetchWaveData = UpdateParametersIfNeeded(i, aFrequency, aDetune);
+ }
+ // Bilinear interpolation between adjacent samples in each table.
+ float floorPhase = floorf(mPhase);
+ int j1Signed = static_cast<int>(floorPhase);
+ uint32_t j1 = j1Signed & indexMask;
+ uint32_t j2 = j1 + 1;
+ j2 &= indexMask;
+
+ float sampleInterpolationFactor = mPhase - floorPhase;
+
+ float lower = (1.0f - sampleInterpolationFactor) * lowerWaveData[j1] +
+ sampleInterpolationFactor * lowerWaveData[j2];
+ float higher = (1.0f - sampleInterpolationFactor) * higherWaveData[j1] +
+ sampleInterpolationFactor * higherWaveData[j2];
+ aOutput[i] = (1.0f - tableInterpolationFactor) * lower +
+ tableInterpolationFactor * higher;
+
+ // Calculate next phase position from wrapped value j1 to avoid loss of
+ // precision at large values.
+ mPhase =
+ j1 + sampleInterpolationFactor + basePhaseIncrement * mFinalFrequency;
+ }
+ }
+
+ void ComputeSilence(AudioBlock* aOutput) {
+ aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
+ }
+
+ void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
+ const AudioBlock& aInput, AudioBlock* aOutput,
+ bool* aFinished) override {
+ MOZ_ASSERT(mSource == aTrack, "Invalid source track");
+ TRACE("OscillatorNodeEngine::ProcessBlock");
+
+ TrackTime ticks = mDestination->GraphTimeToTrackTime(aFrom);
+ if (mStart == -1) {
+ ComputeSilence(aOutput);
+ return;
+ }
+
+ if (ticks + WEBAUDIO_BLOCK_SIZE <= mStart || ticks >= mStop ||
+ mStop <= mStart) {
+ ComputeSilence(aOutput);
+
+ } else {
+ aOutput->AllocateChannels(1);
+ float* output = aOutput->ChannelFloatsForWrite(0);
+
+ uint32_t start, end;
+ FillBounds(output, ticks, start, end);
+ MOZ_ASSERT(start < end);
+
+ float frequency[WEBAUDIO_BLOCK_SIZE];
+ float detune[WEBAUDIO_BLOCK_SIZE];
+ if (ParametersMayNeedUpdate()) {
+ if (mFrequency.HasSimpleValue()) {
+ std::fill_n(frequency, WEBAUDIO_BLOCK_SIZE, mFrequency.GetValue());
+ } else {
+ mFrequency.GetValuesAtTime(ticks + start, frequency + start,
+ end - start);
+ }
+ if (mDetune.HasSimpleValue()) {
+ std::fill_n(detune, WEBAUDIO_BLOCK_SIZE, mDetune.GetValue());
+ } else {
+ mDetune.GetValuesAtTime(ticks + start, detune + start, end - start);
+ }
+ }
+
+ // Synthesize the correct waveform.
+ switch (mType) {
+ case OscillatorType::Sine:
+ ComputeSine(output, start, end, frequency, detune);
+ break;
+ case OscillatorType::Square:
+ case OscillatorType::Triangle:
+ case OscillatorType::Sawtooth:
+ case OscillatorType::Custom:
+ ComputeCustom(output, start, end, frequency, detune);
+ break;
+ case OscillatorType::EndGuard_:
+ MOZ_ASSERT_UNREACHABLE("end guard");
+ // Avoid `default:` so that `-Wswitch` catches missing enumerators
+ // at compile time.
+ };
+ }
+
+ if (ticks + WEBAUDIO_BLOCK_SIZE >= mStop) {
+ // We've finished playing.
+ *aFinished = true;
+ }
+ }
+
+ bool IsActive() const override {
+ // start() has been called.
+ return mStart != -1;
+ }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override {
+ size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+
+ // Not owned:
+ // - mSource
+ // - mDestination
+ // - mFrequency (internal ref owned by node)
+ // - mDetune (internal ref owned by node)
+
+ if (mPeriodicWave) {
+ amount += mPeriodicWave->sizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ return amount;
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ // mSource deletes this engine in its destructor
+ AudioNodeTrack* MOZ_NON_OWNING_REF mSource;
+ RefPtr<AudioNodeTrack> mDestination;
+ TrackTime mStart;
+ TrackTime mStop;
+ AudioParamTimeline mFrequency;
+ AudioParamTimeline mDetune;
+ OscillatorType mType;
+ float mPhase;
+ float mFinalFrequency;
+ float mPhaseIncrement;
+ float mLastDetune = 0.f;
+ float mDetuneRatio = 1.f; // 2^(mLastDetune/1200)
+ bool mRecomputeParameters;
+ RefPtr<BasicWaveFormCache> mBasicWaveFormCache;
+ bool mCustomDisableNormalization;
+ RefPtr<WebCore::PeriodicWave> mPeriodicWave;
+};
+
+OscillatorNode::OscillatorNode(AudioContext* aContext)
+ : AudioScheduledSourceNode(aContext, 2, ChannelCountMode::Max,
+ ChannelInterpretation::Speakers),
+ mType(OscillatorType::Sine),
+ mStartCalled(false) {
+ mFrequency = CreateAudioParam(
+ OscillatorNodeEngine::FREQUENCY, u"frequency"_ns, 440.0f,
+ -(aContext->SampleRate() / 2), aContext->SampleRate() / 2);
+ mDetune = CreateAudioParam(OscillatorNodeEngine::DETUNE, u"detune"_ns, 0.0f);
+ OscillatorNodeEngine* engine =
+ new OscillatorNodeEngine(this, aContext->Destination());
+ mTrack = AudioNodeTrack::Create(aContext, engine,
+ AudioNodeTrack::NEED_MAIN_THREAD_ENDED,
+ aContext->Graph());
+ engine->SetSourceTrack(mTrack);
+ mTrack->AddMainThreadListener(this);
+}
+
+/* static */
+already_AddRefed<OscillatorNode> OscillatorNode::Create(
+ AudioContext& aAudioContext, const OscillatorOptions& aOptions,
+ ErrorResult& aRv) {
+ RefPtr<OscillatorNode> audioNode = new OscillatorNode(&aAudioContext);
+
+ audioNode->Initialize(aOptions, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ audioNode->Frequency()->SetInitialValue(aOptions.mFrequency);
+ audioNode->Detune()->SetInitialValue(aOptions.mDetune);
+
+ if (aOptions.mPeriodicWave.WasPassed()) {
+ audioNode->SetPeriodicWave(aOptions.mPeriodicWave.Value());
+ } else {
+ audioNode->SetType(aOptions.mType, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ }
+
+ return audioNode.forget();
+}
+
+size_t OscillatorNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+
+ // For now only report if we know for sure that it's not shared.
+ if (mPeriodicWave) {
+ amount += mPeriodicWave->SizeOfIncludingThisIfNotShared(aMallocSizeOf);
+ }
+ amount += mFrequency->SizeOfIncludingThis(aMallocSizeOf);
+ amount += mDetune->SizeOfIncludingThis(aMallocSizeOf);
+ return amount;
+}
+
+size_t OscillatorNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+JSObject* OscillatorNode::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return OscillatorNode_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void OscillatorNode::DestroyMediaTrack() {
+ if (mTrack) {
+ mTrack->RemoveMainThreadListener(this);
+ }
+ AudioNode::DestroyMediaTrack();
+}
+
+void OscillatorNode::SendTypeToTrack() {
+ if (!mTrack) {
+ return;
+ }
+ if (mType == OscillatorType::Custom) {
+ // The engine assumes we'll send the custom data before updating the type.
+ SendPeriodicWaveToTrack();
+ }
+ SendInt32ParameterToTrack(OscillatorNodeEngine::TYPE,
+ static_cast<int32_t>(mType));
+}
+
+void OscillatorNode::SendPeriodicWaveToTrack() {
+ NS_ASSERTION(mType == OscillatorType::Custom,
+ "Sending custom waveform to engine thread with non-custom type");
+ MOZ_ASSERT(mTrack, "Missing node track.");
+ MOZ_ASSERT(mPeriodicWave, "Send called without PeriodicWave object.");
+ SendInt32ParameterToTrack(OscillatorNodeEngine::DISABLE_NORMALIZATION,
+ mPeriodicWave->DisableNormalization());
+ AudioChunk data = mPeriodicWave->GetThreadSharedBuffer();
+ mTrack->SetBuffer(std::move(data));
+}
+
+void OscillatorNode::Start(double aWhen, ErrorResult& aRv) {
+ if (!WebAudioUtils::IsTimeValid(aWhen)) {
+ aRv.ThrowRangeError<MSG_VALUE_OUT_OF_RANGE>("start time");
+ return;
+ }
+
+ if (mStartCalled) {
+ aRv.ThrowInvalidStateError("Can't call start() more than once");
+ return;
+ }
+ mStartCalled = true;
+
+ if (!mTrack) {
+ // Nothing to play, or we're already dead for some reason
+ return;
+ }
+
+ // TODO: Perhaps we need to do more here.
+ mTrack->SetTrackTimeParameter(OscillatorNodeEngine::START, Context(), aWhen);
+
+ MarkActive();
+ Context()->StartBlockedAudioContextIfAllowed();
+}
+
+void OscillatorNode::Stop(double aWhen, ErrorResult& aRv) {
+ if (!WebAudioUtils::IsTimeValid(aWhen)) {
+ aRv.ThrowRangeError<MSG_VALUE_OUT_OF_RANGE>("stop time");
+ return;
+ }
+
+ if (!mStartCalled) {
+ aRv.ThrowInvalidStateError("Can't call stop() without calling start()");
+ return;
+ }
+
+ if (!mTrack || !Context()) {
+ // We've already stopped and had our track shut down
+ return;
+ }
+
+ // TODO: Perhaps we need to do more here.
+ mTrack->SetTrackTimeParameter(OscillatorNodeEngine::STOP, Context(),
+ std::max(0.0, aWhen));
+}
+
+void OscillatorNode::NotifyMainThreadTrackEnded() {
+ MOZ_ASSERT(mTrack->IsEnded());
+
+ class EndedEventDispatcher final : public Runnable {
+ public:
+ explicit EndedEventDispatcher(OscillatorNode* aNode)
+ : mozilla::Runnable("EndedEventDispatcher"), mNode(aNode) {}
+ NS_IMETHOD Run() override {
+ // If it's not safe to run scripts right now, schedule this to run later
+ if (!nsContentUtils::IsSafeToRunScript()) {
+ nsContentUtils::AddScriptRunner(this);
+ return NS_OK;
+ }
+
+ mNode->DispatchTrustedEvent(u"ended"_ns);
+ // Release track resources.
+ mNode->DestroyMediaTrack();
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<OscillatorNode> mNode;
+ };
+
+ Context()->Dispatch(do_AddRef(new EndedEventDispatcher(this)));
+
+ // Drop the playing reference
+ // Warning: The below line might delete this.
+ MarkInactive();
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/OscillatorNode.h b/dom/media/webaudio/OscillatorNode.h
new file mode 100644
index 0000000000..1e207b9d67
--- /dev/null
+++ b/dom/media/webaudio/OscillatorNode.h
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef OscillatorNode_h_
+#define OscillatorNode_h_
+
+#include "AudioScheduledSourceNode.h"
+#include "AudioParam.h"
+#include "PeriodicWave.h"
+#include "mozilla/dom/OscillatorNodeBinding.h"
+
+namespace mozilla::dom {
+
+class AudioContext;
+struct OscillatorOptions;
+
+class OscillatorNode final : public AudioScheduledSourceNode,
+ public MainThreadMediaTrackListener {
+ public:
+ static already_AddRefed<OscillatorNode> Create(
+ AudioContext& aAudioContext, const OscillatorOptions& aOptions,
+ ErrorResult& aRv);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(OscillatorNode,
+ AudioScheduledSourceNode)
+
+ static already_AddRefed<OscillatorNode> Constructor(
+ const GlobalObject& aGlobal, AudioContext& aAudioContext,
+ const OscillatorOptions& aOptions, ErrorResult& aRv) {
+ return Create(aAudioContext, aOptions, aRv);
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void DestroyMediaTrack() override;
+
+ uint16_t NumberOfInputs() const final { return 0; }
+
+ OscillatorType Type() const { return mType; }
+ void SetType(OscillatorType aType, ErrorResult& aRv) {
+ if (aType == OscillatorType::Custom) {
+ // ::Custom can only be set by setPeriodicWave().
+ // https://github.com/WebAudio/web-audio-api/issues/105 for exception.
+ aRv.ThrowInvalidStateError("Can't set type to \"custom\"");
+ return;
+ }
+ mType = aType;
+ SendTypeToTrack();
+ }
+
+ AudioParam* Frequency() const { return mFrequency; }
+ AudioParam* Detune() const { return mDetune; }
+
+ void Start(double aWhen, ErrorResult& aRv) override;
+ void Stop(double aWhen, ErrorResult& aRv) override;
+
+ void SetPeriodicWave(PeriodicWave& aPeriodicWave) {
+ mPeriodicWave = &aPeriodicWave;
+ // SendTypeToTrack will call SendPeriodicWaveToTrack for us.
+ mType = OscillatorType::Custom;
+ SendTypeToTrack();
+ }
+
+ void NotifyMainThreadTrackEnded() override;
+
+ const char* NodeType() const override { return "OscillatorNode"; }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+ private:
+ explicit OscillatorNode(AudioContext* aContext);
+ ~OscillatorNode() = default;
+
+ void SendTypeToTrack();
+ void SendPeriodicWaveToTrack();
+
+ OscillatorType mType;
+ RefPtr<PeriodicWave> mPeriodicWave;
+ RefPtr<AudioParam> mFrequency;
+ RefPtr<AudioParam> mDetune;
+ bool mStartCalled;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/media/webaudio/PannerNode.cpp b/dom/media/webaudio/PannerNode.cpp
new file mode 100644
index 0000000000..c6a17e5943
--- /dev/null
+++ b/dom/media/webaudio/PannerNode.cpp
@@ -0,0 +1,730 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PannerNode.h"
+#include "AlignmentUtils.h"
+#include "AudioDestinationNode.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeTrack.h"
+#include "AudioListener.h"
+#include "PanningUtils.h"
+#include "AudioBufferSourceNode.h"
+#include "PlayingRefChangeHandler.h"
+#include "blink/HRTFPanner.h"
+#include "blink/HRTFDatabaseLoader.h"
+#include "Tracing.h"
+
+using WebCore::HRTFDatabaseLoader;
+using WebCore::HRTFPanner;
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(PannerNode)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PannerNode, AudioNode)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPositionX, mPositionY, mPositionZ,
+ mOrientationX, mOrientationY, mOrientationZ)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PannerNode, AudioNode)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPositionX, mPositionY, mPositionZ,
+ mOrientationX, mOrientationY, mOrientationZ)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PannerNode)
+NS_INTERFACE_MAP_END_INHERITING(AudioNode)
+
+NS_IMPL_ADDREF_INHERITED(PannerNode, AudioNode)
+NS_IMPL_RELEASE_INHERITED(PannerNode, AudioNode)
+
+class PannerNodeEngine final : public AudioNodeEngine {
+ public:
+ explicit PannerNodeEngine(AudioNode* aNode,
+ AudioDestinationNode* aDestination,
+ AudioListenerEngine* aListenerEngine)
+ : AudioNodeEngine(aNode),
+ mDestination(aDestination->Track()),
+ mListenerEngine(aListenerEngine)
+ // Please keep these default values consistent with
+ // PannerNode::PannerNode below.
+ ,
+ mPanningModelFunction(&PannerNodeEngine::EqualPowerPanningFunction),
+ mDistanceModelFunction(&PannerNodeEngine::InverseGainFunction),
+ mPositionX(0.),
+ mPositionY(0.),
+ mPositionZ(0.),
+ mOrientationX(1.),
+ mOrientationY(0.),
+ mOrientationZ(0.),
+ mRefDistance(1.),
+ mMaxDistance(10000.),
+ mRolloffFactor(1.),
+ mConeInnerAngle(360.),
+ mConeOuterAngle(360.),
+ mConeOuterGain(0.),
+ mLeftOverData(INT_MIN) {}
+
+ void RecvTimelineEvent(uint32_t aIndex, AudioParamEvent& aEvent) override {
+ MOZ_ASSERT(mDestination);
+ aEvent.ConvertToTicks(mDestination);
+ switch (aIndex) {
+ case PannerNode::POSITIONX:
+ mPositionX.InsertEvent<int64_t>(aEvent);
+ break;
+ case PannerNode::POSITIONY:
+ mPositionY.InsertEvent<int64_t>(aEvent);
+ break;
+ case PannerNode::POSITIONZ:
+ mPositionZ.InsertEvent<int64_t>(aEvent);
+ break;
+ case PannerNode::ORIENTATIONX:
+ mOrientationX.InsertEvent<int64_t>(aEvent);
+ break;
+ case PannerNode::ORIENTATIONY:
+ mOrientationY.InsertEvent<int64_t>(aEvent);
+ break;
+ case PannerNode::ORIENTATIONZ:
+ mOrientationZ.InsertEvent<int64_t>(aEvent);
+ break;
+ default:
+ NS_ERROR("Bad PannerNode TimelineParameter");
+ }
+ }
+
+ void CreateHRTFPanner() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mHRTFPanner) {
+ return;
+ }
+ // HRTFDatabaseLoader needs to be fetched on the main thread.
+ RefPtr<HRTFDatabaseLoader> loader =
+ HRTFDatabaseLoader::createAndLoadAsynchronouslyIfNecessary(
+ NodeMainThread()->Context()->SampleRate());
+ mHRTFPanner = MakeUnique<HRTFPanner>(
+ NodeMainThread()->Context()->SampleRate(), loader.forget());
+ }
+
+ void SetInt32Parameter(uint32_t aIndex, int32_t aParam) override {
+ switch (aIndex) {
+ case PannerNode::PANNING_MODEL:
+ switch (PanningModelType(aParam)) {
+ case PanningModelType::Equalpower:
+ mPanningModelFunction =
+ &PannerNodeEngine::EqualPowerPanningFunction;
+ break;
+ case PanningModelType::HRTF:
+ mPanningModelFunction = &PannerNodeEngine::HRTFPanningFunction;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("We should never see alternate names here");
+ break;
+ }
+ break;
+ case PannerNode::DISTANCE_MODEL:
+ switch (DistanceModelType(aParam)) {
+ case DistanceModelType::Inverse:
+ mDistanceModelFunction = &PannerNodeEngine::InverseGainFunction;
+ break;
+ case DistanceModelType::Linear:
+ mDistanceModelFunction = &PannerNodeEngine::LinearGainFunction;
+ break;
+ case DistanceModelType::Exponential:
+ mDistanceModelFunction = &PannerNodeEngine::ExponentialGainFunction;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("We should never see alternate names here");
+ break;
+ }
+ break;
+ default:
+ NS_ERROR("Bad PannerNodeEngine Int32Parameter");
+ }
+ }
+ void SetDoubleParameter(uint32_t aIndex, double aParam) override {
+ switch (aIndex) {
+ case PannerNode::REF_DISTANCE:
+ mRefDistance = aParam;
+ break;
+ case PannerNode::MAX_DISTANCE:
+ mMaxDistance = aParam;
+ break;
+ case PannerNode::ROLLOFF_FACTOR:
+ mRolloffFactor = aParam;
+ break;
+ case PannerNode::CONE_INNER_ANGLE:
+ mConeInnerAngle = aParam;
+ break;
+ case PannerNode::CONE_OUTER_ANGLE:
+ mConeOuterAngle = aParam;
+ break;
+ case PannerNode::CONE_OUTER_GAIN:
+ mConeOuterGain = aParam;
+ break;
+ default:
+ NS_ERROR("Bad PannerNodeEngine DoubleParameter");
+ }
+ }
+
+ void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
+ const AudioBlock& aInput, AudioBlock* aOutput,
+ bool* aFinished) override {
+ TRACE("PannerNodeEngine::ProcessBlock");
+
+ if (aInput.IsNull()) {
+ // mLeftOverData != INT_MIN means that the panning model was HRTF and a
+ // tail-time reference was added. Even if the model is now equalpower,
+ // the reference will need to be removed.
+ if (mLeftOverData > 0 &&
+ mPanningModelFunction == &PannerNodeEngine::HRTFPanningFunction) {
+ mLeftOverData -= WEBAUDIO_BLOCK_SIZE;
+ } else {
+ if (mLeftOverData != INT_MIN) {
+ mLeftOverData = INT_MIN;
+ aTrack->ScheduleCheckForInactive();
+ mHRTFPanner->reset();
+
+ RefPtr<PlayingRefChangeHandler> refchanged =
+ new PlayingRefChangeHandler(aTrack,
+ PlayingRefChangeHandler::RELEASE);
+ aTrack->Graph()->DispatchToMainThreadStableState(refchanged.forget());
+ }
+ aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
+ return;
+ }
+ } else if (mPanningModelFunction ==
+ &PannerNodeEngine::HRTFPanningFunction) {
+ if (mLeftOverData == INT_MIN) {
+ RefPtr<PlayingRefChangeHandler> refchanged =
+ new PlayingRefChangeHandler(aTrack,
+ PlayingRefChangeHandler::ADDREF);
+ aTrack->Graph()->DispatchToMainThreadStableState(refchanged.forget());
+ }
+ mLeftOverData = mHRTFPanner->maxTailFrames();
+ }
+
+ TrackTime tick = mDestination->GraphTimeToTrackTime(aFrom);
+ (this->*mPanningModelFunction)(aInput, aOutput, tick);
+ }
+
+ bool IsActive() const override { return mLeftOverData != INT_MIN; }
+
+ void ComputeAzimuthAndElevation(const ThreeDPoint& position, float& aAzimuth,
+ float& aElevation);
+ float ComputeConeGain(const ThreeDPoint& position,
+ const ThreeDPoint& orientation);
+ // Compute how much the distance contributes to the gain reduction.
+ double ComputeDistanceGain(const ThreeDPoint& position);
+
+ void EqualPowerPanningFunction(const AudioBlock& aInput, AudioBlock* aOutput,
+ TrackTime tick);
+ void HRTFPanningFunction(const AudioBlock& aInput, AudioBlock* aOutput,
+ TrackTime tick);
+
+ float LinearGainFunction(double aDistance);
+ float InverseGainFunction(double aDistance);
+ float ExponentialGainFunction(double aDistance);
+
+ ThreeDPoint ConvertAudioParamTimelineTo3DP(AudioParamTimeline& aX,
+ AudioParamTimeline& aY,
+ AudioParamTimeline& aZ,
+ TrackTime& tick);
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override {
+ size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+ if (mHRTFPanner) {
+ amount += mHRTFPanner->sizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ return amount;
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ RefPtr<AudioNodeTrack> mDestination;
+ // This member is set on the main thread, but is not accessed on the rendering
+ // thread untile mPanningModelFunction has changed, and this happens strictly
+ // later, via a MediaTrackGraph ControlMessage.
+ UniquePtr<HRTFPanner> mHRTFPanner;
+ RefPtr<AudioListenerEngine> mListenerEngine;
+ using PanningModelFunction = void (PannerNodeEngine::*)(const AudioBlock&,
+ AudioBlock*,
+ TrackTime);
+ PanningModelFunction mPanningModelFunction;
+ using DistanceModelFunction = float (PannerNodeEngine::*)(double);
+ DistanceModelFunction mDistanceModelFunction;
+ AudioParamTimeline mPositionX;
+ AudioParamTimeline mPositionY;
+ AudioParamTimeline mPositionZ;
+ AudioParamTimeline mOrientationX;
+ AudioParamTimeline mOrientationY;
+ AudioParamTimeline mOrientationZ;
+ double mRefDistance;
+ double mMaxDistance;
+ double mRolloffFactor;
+ double mConeInnerAngle;
+ double mConeOuterAngle;
+ double mConeOuterGain;
+ int mLeftOverData;
+};
+
+PannerNode::PannerNode(AudioContext* aContext)
+ : AudioNode(aContext, 2, ChannelCountMode::Clamped_max,
+ ChannelInterpretation::Speakers)
+ // Please keep these default values consistent with
+ // PannerNodeEngine::PannerNodeEngine above.
+ ,
+ mPanningModel(PanningModelType::Equalpower),
+ mDistanceModel(DistanceModelType::Inverse),
+ mRefDistance(1.),
+ mMaxDistance(10000.),
+ mRolloffFactor(1.),
+ mConeInnerAngle(360.),
+ mConeOuterAngle(360.),
+ mConeOuterGain(0.) {
+ mPositionX = CreateAudioParam(PannerNode::POSITIONX, u"PositionX"_ns, 0.f);
+ mPositionY = CreateAudioParam(PannerNode::POSITIONY, u"PositionY"_ns, 0.f);
+ mPositionZ = CreateAudioParam(PannerNode::POSITIONZ, u"PositionZ"_ns, 0.f);
+ mOrientationX =
+ CreateAudioParam(PannerNode::ORIENTATIONX, u"OrientationX"_ns, 1.0f);
+ mOrientationY =
+ CreateAudioParam(PannerNode::ORIENTATIONY, u"OrientationY"_ns, 0.f);
+ mOrientationZ =
+ CreateAudioParam(PannerNode::ORIENTATIONZ, u"OrientationZ"_ns, 0.f);
+ mTrack = AudioNodeTrack::Create(
+ aContext,
+ new PannerNodeEngine(this, aContext->Destination(),
+ aContext->Listener()->Engine()),
+ AudioNodeTrack::NO_TRACK_FLAGS, aContext->Graph());
+}
+
+/* static */
+already_AddRefed<PannerNode> PannerNode::Create(AudioContext& aAudioContext,
+ const PannerOptions& aOptions,
+ ErrorResult& aRv) {
+ RefPtr<PannerNode> audioNode = new PannerNode(&aAudioContext);
+
+ audioNode->Initialize(aOptions, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ audioNode->SetPanningModel(aOptions.mPanningModel);
+ audioNode->SetDistanceModel(aOptions.mDistanceModel);
+ audioNode->mPositionX->SetInitialValue(aOptions.mPositionX);
+ audioNode->mPositionY->SetInitialValue(aOptions.mPositionY);
+ audioNode->mPositionZ->SetInitialValue(aOptions.mPositionZ);
+ audioNode->mOrientationX->SetInitialValue(aOptions.mOrientationX);
+ audioNode->mOrientationY->SetInitialValue(aOptions.mOrientationY);
+ audioNode->mOrientationZ->SetInitialValue(aOptions.mOrientationZ);
+ audioNode->SetRefDistance(aOptions.mRefDistance, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ audioNode->SetMaxDistance(aOptions.mMaxDistance, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ audioNode->SetRolloffFactor(aOptions.mRolloffFactor, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ audioNode->SetConeInnerAngle(aOptions.mConeInnerAngle);
+ audioNode->SetConeOuterAngle(aOptions.mConeOuterAngle);
+ audioNode->SetConeOuterGain(aOptions.mConeOuterGain, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ return audioNode.forget();
+}
+
+void PannerNode::SetPanningModel(PanningModelType aPanningModel) {
+ mPanningModel = aPanningModel;
+ if (mPanningModel == PanningModelType::HRTF) {
+ // We can set the engine's `mHRTFPanner` member here from the main thread,
+ // because the engine will not touch it from the MediaTrackGraph
+ // thread until the PANNING_MODEL message sent below is received.
+ static_cast<PannerNodeEngine*>(mTrack->Engine())->CreateHRTFPanner();
+ }
+ SendInt32ParameterToTrack(PANNING_MODEL, int32_t(mPanningModel));
+}
+
+static bool SetParamFromDouble(AudioParam* aParam, double aValue,
+ const char (&aParamName)[2], ErrorResult& aRv) {
+ float value = static_cast<float>(aValue);
+ if (!std::isfinite(value)) {
+ aRv.ThrowTypeError<MSG_NOT_FINITE>(aParamName);
+ return false;
+ }
+ aParam->SetValue(value, aRv);
+ return !aRv.Failed();
+}
+
+void PannerNode::SetPosition(double aX, double aY, double aZ,
+ ErrorResult& aRv) {
+ if (!SetParamFromDouble(mPositionX, aX, "x", aRv)) {
+ return;
+ }
+ if (!SetParamFromDouble(mPositionY, aY, "y", aRv)) {
+ return;
+ }
+ SetParamFromDouble(mPositionZ, aZ, "z", aRv);
+}
+
+void PannerNode::SetOrientation(double aX, double aY, double aZ,
+ ErrorResult& aRv) {
+ if (!SetParamFromDouble(mOrientationX, aX, "x", aRv)) {
+ return;
+ }
+ if (!SetParamFromDouble(mOrientationY, aY, "y", aRv)) {
+ return;
+ }
+ SetParamFromDouble(mOrientationZ, aZ, "z", aRv);
+}
+
+size_t PannerNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ return AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+}
+
+size_t PannerNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+JSObject* PannerNode::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return PannerNode_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+// Those three functions are described in the spec.
+float PannerNodeEngine::LinearGainFunction(double aDistance) {
+ double clampedRollof = std::clamp(mRolloffFactor, 0.0, 1.0);
+ return AssertedCast<float>(
+ 1.0 - clampedRollof *
+ (std::max(std::min(aDistance, mMaxDistance), mRefDistance) -
+ mRefDistance) /
+ (mMaxDistance - mRefDistance));
+}
+
+float PannerNodeEngine::InverseGainFunction(double aDistance) {
+ return mRefDistance /
+ (mRefDistance +
+ mRolloffFactor * (std::max(aDistance, mRefDistance) - mRefDistance));
+}
+
+float PannerNodeEngine::ExponentialGainFunction(double aDistance) {
+ return fdlibm_pow(std::max(aDistance, mRefDistance) / mRefDistance,
+ -mRolloffFactor);
+}
+
+void PannerNodeEngine::HRTFPanningFunction(const AudioBlock& aInput,
+ AudioBlock* aOutput,
+ TrackTime tick) {
+ // The output of this node is always stereo, no matter what the inputs are.
+ aOutput->AllocateChannels(2);
+
+ float azimuth, elevation;
+
+ ThreeDPoint position =
+ ConvertAudioParamTimelineTo3DP(mPositionX, mPositionY, mPositionZ, tick);
+ ThreeDPoint orientation = ConvertAudioParamTimelineTo3DP(
+ mOrientationX, mOrientationY, mOrientationZ, tick);
+ if (!orientation.IsZero()) {
+ orientation.Normalize();
+ }
+ ComputeAzimuthAndElevation(position, azimuth, elevation);
+
+ AudioBlock input = aInput;
+ // Gain is applied before the delay and convolution of the HRTF.
+ input.mVolume *=
+ ComputeConeGain(position, orientation) * ComputeDistanceGain(position);
+
+ mHRTFPanner->pan(azimuth, elevation, &input, aOutput);
+}
+
+ThreeDPoint PannerNodeEngine::ConvertAudioParamTimelineTo3DP(
+ AudioParamTimeline& aX, AudioParamTimeline& aY, AudioParamTimeline& aZ,
+ TrackTime& tick) {
+ return ThreeDPoint(aX.GetValueAtTime(tick), aY.GetValueAtTime(tick),
+ aZ.GetValueAtTime(tick));
+}
+
+void PannerNodeEngine::EqualPowerPanningFunction(const AudioBlock& aInput,
+ AudioBlock* aOutput,
+ TrackTime tick) {
+ float azimuth, elevation, gainL, gainR, normalizedAzimuth, distanceGain,
+ coneGain;
+ int inputChannels = aInput.ChannelCount();
+
+ // Optimize the case where the position and orientation is constant for this
+ // processing block: we can just apply a constant gain on the left and right
+ // channel
+ if (mPositionX.HasSimpleValue() && mPositionY.HasSimpleValue() &&
+ mPositionZ.HasSimpleValue() && mOrientationX.HasSimpleValue() &&
+ mOrientationY.HasSimpleValue() && mOrientationZ.HasSimpleValue()) {
+ ThreeDPoint position(mPositionX.GetValue(), mPositionY.GetValue(),
+ mPositionZ.GetValue());
+ ThreeDPoint orientation(mOrientationX.GetValue(), mOrientationY.GetValue(),
+ mOrientationZ.GetValue());
+ if (!orientation.IsZero()) {
+ orientation.Normalize();
+ }
+
+ // For a stereo source, when both the listener and the panner are in
+ // the same spot, and no cone gain is specified, this node is noop.
+ if (inputChannels == 2 && mListenerEngine->Position() == position &&
+ mConeInnerAngle == 360 && mConeOuterAngle == 360) {
+ *aOutput = aInput;
+ return;
+ }
+
+ ComputeAzimuthAndElevation(position, azimuth, elevation);
+ coneGain = ComputeConeGain(position, orientation);
+
+ // The following algorithm is described in the spec.
+ // Clamp azimuth in the [-90, 90] range.
+ azimuth = std::min(180.f, std::max(-180.f, azimuth));
+
+ // Wrap around
+ if (azimuth < -90.f) {
+ azimuth = -180.f - azimuth;
+ } else if (azimuth > 90) {
+ azimuth = 180.f - azimuth;
+ }
+
+ // Normalize the value in the [0, 1] range.
+ if (inputChannels == 1) {
+ normalizedAzimuth = (azimuth + 90.f) / 180.f;
+ } else {
+ if (azimuth <= 0) {
+ normalizedAzimuth = (azimuth + 90.f) / 90.f;
+ } else {
+ normalizedAzimuth = azimuth / 90.f;
+ }
+ }
+
+ distanceGain = ComputeDistanceGain(position);
+
+ // Actually compute the left and right gain.
+ gainL = fdlibm_cos(0.5 * M_PI * normalizedAzimuth);
+ gainR = fdlibm_sin(0.5 * M_PI * normalizedAzimuth);
+
+ // Compute the output.
+ ApplyStereoPanning(aInput, aOutput, gainL, gainR, azimuth <= 0);
+
+ aOutput->mVolume *= distanceGain * coneGain;
+ } else {
+ float positionX[WEBAUDIO_BLOCK_SIZE];
+ float positionY[WEBAUDIO_BLOCK_SIZE];
+ float positionZ[WEBAUDIO_BLOCK_SIZE];
+ float orientationX[WEBAUDIO_BLOCK_SIZE];
+ float orientationY[WEBAUDIO_BLOCK_SIZE];
+ float orientationZ[WEBAUDIO_BLOCK_SIZE];
+
+ if (!mPositionX.HasSimpleValue()) {
+ mPositionX.GetValuesAtTime(tick, positionX, WEBAUDIO_BLOCK_SIZE);
+ } else {
+ positionX[0] = mPositionX.GetValue();
+ }
+ if (!mPositionY.HasSimpleValue()) {
+ mPositionY.GetValuesAtTime(tick, positionY, WEBAUDIO_BLOCK_SIZE);
+ } else {
+ positionY[0] = mPositionY.GetValue();
+ }
+ if (!mPositionZ.HasSimpleValue()) {
+ mPositionZ.GetValuesAtTime(tick, positionZ, WEBAUDIO_BLOCK_SIZE);
+ } else {
+ positionZ[0] = mPositionZ.GetValue();
+ }
+ if (!mOrientationX.HasSimpleValue()) {
+ mOrientationX.GetValuesAtTime(tick, orientationX, WEBAUDIO_BLOCK_SIZE);
+ } else {
+ orientationX[0] = mOrientationX.GetValue();
+ }
+ if (!mOrientationY.HasSimpleValue()) {
+ mOrientationY.GetValuesAtTime(tick, orientationY, WEBAUDIO_BLOCK_SIZE);
+ } else {
+ orientationY[0] = mOrientationY.GetValue();
+ }
+ if (!mOrientationZ.HasSimpleValue()) {
+ mOrientationZ.GetValuesAtTime(tick, orientationZ, WEBAUDIO_BLOCK_SIZE);
+ } else {
+ orientationZ[0] = mOrientationZ.GetValue();
+ }
+
+ float buffer[3 * WEBAUDIO_BLOCK_SIZE + 4];
+ bool onLeft[WEBAUDIO_BLOCK_SIZE];
+
+ float* alignedPanningL = ALIGNED16(buffer);
+ float* alignedPanningR = alignedPanningL + WEBAUDIO_BLOCK_SIZE;
+ float* alignedGain = alignedPanningR + WEBAUDIO_BLOCK_SIZE;
+ ASSERT_ALIGNED16(alignedPanningL);
+ ASSERT_ALIGNED16(alignedPanningR);
+ ASSERT_ALIGNED16(alignedGain);
+
+ for (size_t counter = 0; counter < WEBAUDIO_BLOCK_SIZE; ++counter) {
+ ThreeDPoint position(
+ mPositionX.HasSimpleValue() ? positionX[0] : positionX[counter],
+ mPositionY.HasSimpleValue() ? positionY[0] : positionY[counter],
+ mPositionZ.HasSimpleValue() ? positionZ[0] : positionZ[counter]);
+ ThreeDPoint orientation(
+ mOrientationX.HasSimpleValue() ? orientationX[0]
+ : orientationX[counter],
+ mOrientationY.HasSimpleValue() ? orientationY[0]
+ : orientationY[counter],
+ mOrientationZ.HasSimpleValue() ? orientationZ[0]
+ : orientationZ[counter]);
+ if (!orientation.IsZero()) {
+ orientation.Normalize();
+ }
+
+ ComputeAzimuthAndElevation(position, azimuth, elevation);
+ coneGain = ComputeConeGain(position, orientation);
+
+ // The following algorithm is described in the spec.
+ // Clamp azimuth in the [-90, 90] range.
+ azimuth = std::min(180.f, std::max(-180.f, azimuth));
+
+ // Wrap around
+ if (azimuth < -90.f) {
+ azimuth = -180.f - azimuth;
+ } else if (azimuth > 90) {
+ azimuth = 180.f - azimuth;
+ }
+
+ // Normalize the value in the [0, 1] range.
+ if (inputChannels == 1) {
+ normalizedAzimuth = (azimuth + 90.f) / 180.f;
+ } else {
+ if (azimuth <= 0) {
+ normalizedAzimuth = (azimuth + 90.f) / 90.f;
+ } else {
+ normalizedAzimuth = azimuth / 90.f;
+ }
+ }
+
+ distanceGain = ComputeDistanceGain(position);
+
+ // Actually compute the left and right gain.
+ float gainL = fdlibm_cos(0.5 * M_PI * normalizedAzimuth);
+ float gainR = fdlibm_sin(0.5 * M_PI * normalizedAzimuth);
+
+ alignedPanningL[counter] = gainL;
+ alignedPanningR[counter] = gainR;
+ alignedGain[counter] = distanceGain * coneGain;
+ onLeft[counter] = azimuth <= 0;
+ }
+
+ // Apply the panning to the output buffer
+ ApplyStereoPanning(aInput, aOutput, alignedPanningL, alignedPanningR,
+ onLeft);
+
+ // Apply the input volume, cone and distance gain to the output buffer.
+ float* outputL = aOutput->ChannelFloatsForWrite(0);
+ float* outputR = aOutput->ChannelFloatsForWrite(1);
+ AudioBlockInPlaceScale(outputL, alignedGain);
+ AudioBlockInPlaceScale(outputR, alignedGain);
+ }
+}
+
+// This algorithm is specified in the webaudio spec.
+void PannerNodeEngine::ComputeAzimuthAndElevation(const ThreeDPoint& position,
+ float& aAzimuth,
+ float& aElevation) {
+ ThreeDPoint sourceListener = position - mListenerEngine->Position();
+ if (sourceListener.IsZero()) {
+ aAzimuth = 0.0;
+ aElevation = 0.0;
+ return;
+ }
+
+ sourceListener.Normalize();
+
+ // Project the source-listener vector on the x-z plane.
+ const ThreeDPoint& listenerFront = mListenerEngine->FrontVector();
+ const ThreeDPoint& listenerRight = mListenerEngine->RightVector();
+ ThreeDPoint up = listenerRight.CrossProduct(listenerFront);
+
+ double upProjection = sourceListener.DotProduct(up);
+ aElevation = 90 - 180 * fdlibm_acos(upProjection) / M_PI;
+
+ if (aElevation > 90) {
+ aElevation = 180 - aElevation;
+ } else if (aElevation < -90) {
+ aElevation = -180 - aElevation;
+ }
+
+ ThreeDPoint projectedSource = sourceListener - up * upProjection;
+ if (projectedSource.IsZero()) {
+ // source - listener direction is up or down.
+ aAzimuth = 0.0;
+ return;
+ }
+ projectedSource.Normalize();
+
+ // Actually compute the angle, and convert to degrees
+ double projection = projectedSource.DotProduct(listenerRight);
+ aAzimuth = 180 * fdlibm_acos(projection) / M_PI;
+
+ // Compute whether the source is in front or behind the listener.
+ double frontBack = projectedSource.DotProduct(listenerFront);
+ if (frontBack < 0) {
+ aAzimuth = 360 - aAzimuth;
+ }
+ // Rotate the azimuth so it is relative to the listener front vector instead
+ // of the right vector.
+ if ((aAzimuth >= 0) && (aAzimuth <= 270)) {
+ aAzimuth = 90 - aAzimuth;
+ } else {
+ aAzimuth = 450 - aAzimuth;
+ }
+}
+
+// This algorithm is described in the WebAudio spec.
+float PannerNodeEngine::ComputeConeGain(const ThreeDPoint& position,
+ const ThreeDPoint& orientation) {
+ // Omnidirectional source
+ if (orientation.IsZero() ||
+ ((mConeInnerAngle == 360) && (mConeOuterAngle == 360))) {
+ return 1;
+ }
+
+ // Normalized source-listener vector
+ ThreeDPoint sourceToListener = mListenerEngine->Position() - position;
+ sourceToListener.Normalize();
+
+ // Angle between the source orientation vector and the source-listener vector
+ double dotProduct = sourceToListener.DotProduct(orientation);
+ double angle = 180 * fdlibm_acos(dotProduct) / M_PI;
+ double absAngle = fabs(angle);
+
+ // Divide by 2 here since API is entire angle (not half-angle)
+ double absInnerAngle = fabs(mConeInnerAngle) / 2;
+ double absOuterAngle = fabs(mConeOuterAngle) / 2;
+ double gain = 1;
+
+ if (absAngle <= absInnerAngle) {
+ // No attenuation
+ gain = 1;
+ } else if (absAngle >= absOuterAngle) {
+ // Max attenuation
+ gain = mConeOuterGain;
+ } else {
+ // Between inner and outer cones
+ // inner -> outer, x goes from 0 -> 1
+ double x = (absAngle - absInnerAngle) / (absOuterAngle - absInnerAngle);
+ gain = (1 - x) + mConeOuterGain * x;
+ }
+
+ return gain;
+}
+
+double PannerNodeEngine::ComputeDistanceGain(const ThreeDPoint& position) {
+ ThreeDPoint distanceVec = position - mListenerEngine->Position();
+ float distance = sqrt(distanceVec.DotProduct(distanceVec));
+ return std::max(0.0f, (this->*mDistanceModelFunction)(distance));
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/PannerNode.h b/dom/media/webaudio/PannerNode.h
new file mode 100644
index 0000000000..94876c371c
--- /dev/null
+++ b/dom/media/webaudio/PannerNode.h
@@ -0,0 +1,221 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef PannerNode_h_
+#define PannerNode_h_
+
+#include "AudioNode.h"
+#include "AudioParam.h"
+#include "nsPrintfCString.h"
+#include "mozilla/dom/PannerNodeBinding.h"
+#include "ThreeDPoint.h"
+#include <limits>
+#include <set>
+
+namespace mozilla::dom {
+
+class AudioContext;
+class AudioBufferSourceNode;
+struct PannerOptions;
+
+class PannerNode final : public AudioNode {
+ public:
+ static already_AddRefed<PannerNode> Create(AudioContext& aAudioContext,
+ const PannerOptions& aOptions,
+ ErrorResult& aRv);
+
+ static already_AddRefed<PannerNode> Constructor(const GlobalObject& aGlobal,
+ AudioContext& aAudioContext,
+ const PannerOptions& aOptions,
+ ErrorResult& aRv) {
+ return Create(aAudioContext, aOptions, aRv);
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void SetChannelCount(uint32_t aChannelCount, ErrorResult& aRv) override {
+ if (aChannelCount > 2) {
+ aRv.ThrowNotSupportedError(
+ nsPrintfCString("%u is greater than 2", aChannelCount));
+ return;
+ }
+ AudioNode::SetChannelCount(aChannelCount, aRv);
+ }
+ void SetChannelCountModeValue(ChannelCountMode aMode,
+ ErrorResult& aRv) override {
+ if (aMode == ChannelCountMode::Max) {
+ aRv.ThrowNotSupportedError("Cannot set channel count mode to \"max\"");
+ return;
+ }
+ AudioNode::SetChannelCountModeValue(aMode, aRv);
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PannerNode, AudioNode)
+
+ PanningModelType PanningModel() const { return mPanningModel; }
+ void SetPanningModel(PanningModelType aPanningModel);
+
+ DistanceModelType DistanceModel() const { return mDistanceModel; }
+ void SetDistanceModel(DistanceModelType aDistanceModel) {
+ mDistanceModel = aDistanceModel;
+ SendInt32ParameterToTrack(DISTANCE_MODEL, int32_t(mDistanceModel));
+ }
+
+ void SetPosition(double aX, double aY, double aZ, ErrorResult& aRv);
+ void SetOrientation(double aX, double aY, double aZ, ErrorResult& aRv);
+
+ double RefDistance() const { return mRefDistance; }
+ void SetRefDistance(double aRefDistance, ErrorResult& aRv) {
+ if (WebAudioUtils::FuzzyEqual(mRefDistance, aRefDistance)) {
+ return;
+ }
+
+ if (aRefDistance < 0) {
+ aRv.ThrowRangeError(
+ "The refDistance value passed to PannerNode must not be negative.");
+ return;
+ }
+
+ mRefDistance = aRefDistance;
+ SendDoubleParameterToTrack(REF_DISTANCE, mRefDistance);
+ }
+
+ double MaxDistance() const { return mMaxDistance; }
+ void SetMaxDistance(double aMaxDistance, ErrorResult& aRv) {
+ if (WebAudioUtils::FuzzyEqual(mMaxDistance, aMaxDistance)) {
+ return;
+ }
+
+ if (aMaxDistance <= 0) {
+ aRv.ThrowRangeError(
+ "The maxDistance value passed to PannerNode must be positive.");
+ return;
+ }
+
+ mMaxDistance = aMaxDistance;
+ SendDoubleParameterToTrack(MAX_DISTANCE, mMaxDistance);
+ }
+
+ double RolloffFactor() const { return mRolloffFactor; }
+ void SetRolloffFactor(double aRolloffFactor, ErrorResult& aRv) {
+ if (WebAudioUtils::FuzzyEqual(mRolloffFactor, aRolloffFactor)) {
+ return;
+ }
+
+ if (aRolloffFactor < 0) {
+ aRv.ThrowRangeError(
+ "The rolloffFactor value passed to PannerNode must not be negative.");
+ return;
+ }
+
+ mRolloffFactor = aRolloffFactor;
+ SendDoubleParameterToTrack(ROLLOFF_FACTOR, mRolloffFactor);
+ }
+
+ double ConeInnerAngle() const { return mConeInnerAngle; }
+ void SetConeInnerAngle(double aConeInnerAngle) {
+ if (WebAudioUtils::FuzzyEqual(mConeInnerAngle, aConeInnerAngle)) {
+ return;
+ }
+ mConeInnerAngle = aConeInnerAngle;
+ SendDoubleParameterToTrack(CONE_INNER_ANGLE, mConeInnerAngle);
+ }
+
+ double ConeOuterAngle() const { return mConeOuterAngle; }
+ void SetConeOuterAngle(double aConeOuterAngle) {
+ if (WebAudioUtils::FuzzyEqual(mConeOuterAngle, aConeOuterAngle)) {
+ return;
+ }
+ mConeOuterAngle = aConeOuterAngle;
+ SendDoubleParameterToTrack(CONE_OUTER_ANGLE, mConeOuterAngle);
+ }
+
+ double ConeOuterGain() const { return mConeOuterGain; }
+ void SetConeOuterGain(double aConeOuterGain, ErrorResult& aRv) {
+ if (WebAudioUtils::FuzzyEqual(mConeOuterGain, aConeOuterGain)) {
+ return;
+ }
+
+ if (aConeOuterGain < 0 || aConeOuterGain > 1) {
+ aRv.ThrowInvalidStateError(
+ nsPrintfCString("%g is not in the range [0, 1]", aConeOuterGain));
+ return;
+ }
+
+ mConeOuterGain = aConeOuterGain;
+ SendDoubleParameterToTrack(CONE_OUTER_GAIN, mConeOuterGain);
+ }
+
+ AudioParam* PositionX() { return mPositionX; }
+
+ AudioParam* PositionY() { return mPositionY; }
+
+ AudioParam* PositionZ() { return mPositionZ; }
+
+ AudioParam* OrientationX() { return mOrientationX; }
+
+ AudioParam* OrientationY() { return mOrientationY; }
+
+ AudioParam* OrientationZ() { return mOrientationZ; }
+
+ const char* NodeType() const override { return "PannerNode"; }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+ private:
+ explicit PannerNode(AudioContext* aContext);
+ ~PannerNode() = default;
+
+ friend class AudioListener;
+ friend class PannerNodeEngine;
+ enum EngineParameters {
+ PANNING_MODEL,
+ DISTANCE_MODEL,
+ POSITION,
+ POSITIONX,
+ POSITIONY,
+ POSITIONZ,
+ ORIENTATION, // unit length or zero
+ ORIENTATIONX,
+ ORIENTATIONY,
+ ORIENTATIONZ,
+ REF_DISTANCE,
+ MAX_DISTANCE,
+ ROLLOFF_FACTOR,
+ CONE_INNER_ANGLE,
+ CONE_OUTER_ANGLE,
+ CONE_OUTER_GAIN
+ };
+
+ ThreeDPoint ConvertAudioParamTo3DP(RefPtr<AudioParam> aX,
+ RefPtr<AudioParam> aY,
+ RefPtr<AudioParam> aZ) {
+ return ThreeDPoint(aX->GetValue(), aY->GetValue(), aZ->GetValue());
+ }
+
+ PanningModelType mPanningModel;
+ DistanceModelType mDistanceModel;
+ RefPtr<AudioParam> mPositionX;
+ RefPtr<AudioParam> mPositionY;
+ RefPtr<AudioParam> mPositionZ;
+ RefPtr<AudioParam> mOrientationX;
+ RefPtr<AudioParam> mOrientationY;
+ RefPtr<AudioParam> mOrientationZ;
+
+ double mRefDistance;
+ double mMaxDistance;
+ double mRolloffFactor;
+ double mConeInnerAngle;
+ double mConeOuterAngle;
+ double mConeOuterGain;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/media/webaudio/PanningUtils.h b/dom/media/webaudio/PanningUtils.h
new file mode 100644
index 0000000000..eef8ae78c9
--- /dev/null
+++ b/dom/media/webaudio/PanningUtils.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef PANNING_UTILS_H
+#define PANNING_UTILS_H
+
+#include "AudioSegment.h"
+#include "AudioNodeEngine.h"
+
+namespace mozilla::dom {
+
+template <typename T>
+void GainMonoToStereo(const AudioBlock& aInput, AudioBlock* aOutput, T aGainL,
+ T aGainR) {
+ float* outputL = aOutput->ChannelFloatsForWrite(0);
+ float* outputR = aOutput->ChannelFloatsForWrite(1);
+ const float* input = static_cast<const float*>(aInput.mChannelData[0]);
+
+ MOZ_ASSERT(aInput.ChannelCount() == 1);
+ MOZ_ASSERT(aOutput->ChannelCount() == 2);
+
+ AudioBlockPanMonoToStereo(input, aGainL, aGainR, outputL, outputR);
+}
+
+// T can be float or an array of float, and U can be bool or an array of bool,
+// depending if the value of the parameters are constant for this block.
+template <typename T, typename U>
+void GainStereoToStereo(const AudioBlock& aInput, AudioBlock* aOutput, T aGainL,
+ T aGainR, U aOnLeft) {
+ float* outputL = aOutput->ChannelFloatsForWrite(0);
+ float* outputR = aOutput->ChannelFloatsForWrite(1);
+ const float* inputL = static_cast<const float*>(aInput.mChannelData[0]);
+ const float* inputR = static_cast<const float*>(aInput.mChannelData[1]);
+
+ MOZ_ASSERT(aInput.ChannelCount() == 2);
+ MOZ_ASSERT(aOutput->ChannelCount() == 2);
+
+ AudioBlockPanStereoToStereo(inputL, inputR, aGainL, aGainR, aOnLeft, outputL,
+ outputR);
+}
+
+// T can be float or an array of float, and U can be bool or an array of bool,
+// depending if the value of the parameters are constant for this block.
+template <typename T, typename U>
+void ApplyStereoPanning(const AudioBlock& aInput, AudioBlock* aOutput, T aGainL,
+ T aGainR, U aOnLeft) {
+ aOutput->AllocateChannels(2);
+
+ if (aInput.ChannelCount() == 1) {
+ GainMonoToStereo(aInput, aOutput, aGainL, aGainR);
+ } else {
+ GainStereoToStereo(aInput, aOutput, aGainL, aGainR, aOnLeft);
+ }
+ aOutput->mVolume = aInput.mVolume;
+}
+
+} // namespace mozilla::dom
+
+#endif // PANNING_UTILS_H
diff --git a/dom/media/webaudio/PeriodicWave.cpp b/dom/media/webaudio/PeriodicWave.cpp
new file mode 100644
index 0000000000..e036161724
--- /dev/null
+++ b/dom/media/webaudio/PeriodicWave.cpp
@@ -0,0 +1,146 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PeriodicWave.h"
+#include "AudioContext.h"
+#include "mozilla/dom/PeriodicWaveBinding.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PeriodicWave, mContext)
+
+PeriodicWave::PeriodicWave(AudioContext* aContext, const float* aRealData,
+ const uint32_t aRealSize, const float* aImagData,
+ const uint32_t aImagSize,
+ const bool aDisableNormalization, ErrorResult& aRv)
+ : mContext(aContext), mDisableNormalization(aDisableNormalization) {
+ if (aRealData && aImagData && aRealSize != aImagSize) {
+ aRv.ThrowIndexSizeError("\"real\" and \"imag\" are different in length");
+ return;
+ }
+
+ uint32_t length = 0;
+ if (aRealData) {
+ length = aRealSize;
+ } else if (aImagData) {
+ length = aImagSize;
+ } else {
+ // If nothing has been passed, this PeriodicWave will be a sine wave: 2
+ // elements for each array, the second imaginary component set to 1.0.
+ length = 2;
+ }
+
+ if (length < 2) {
+ aRv.ThrowIndexSizeError(
+ "\"real\" and \"imag\" must have a length of "
+ "at least 2");
+ return;
+ }
+
+ MOZ_ASSERT(aContext);
+ MOZ_ASSERT((aRealData || aImagData) || length == 2);
+
+ // Caller should have checked this and thrown.
+ MOZ_ASSERT(length >= 2);
+ mCoefficients.mDuration = length;
+
+ // Copy coefficient data.
+ // The SharedBuffer and two arrays share a single allocation.
+ CheckedInt<size_t> bufferSize(sizeof(float));
+ bufferSize *= length;
+ bufferSize *= 2;
+ RefPtr<SharedBuffer> buffer = SharedBuffer::Create(bufferSize, fallible);
+ if (!buffer) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ auto data = static_cast<float*>(buffer->Data());
+ mCoefficients.mBuffer = std::move(buffer);
+
+ if (!aRealData && !aImagData) {
+ PodZero(data, length);
+ mCoefficients.mChannelData.AppendElement(data);
+ data += length;
+ data[0] = 0.0f;
+ data[1] = 1.0f;
+ mCoefficients.mChannelData.AppendElement(data);
+ } else {
+ if (aRealData) {
+ PodCopy(data, aRealData, length);
+ } else {
+ PodZero(data, length);
+ }
+ mCoefficients.mChannelData.AppendElement(data);
+
+ data += length;
+ if (aImagData) {
+ PodCopy(data, aImagData, length);
+ } else {
+ PodZero(data, length);
+ }
+ mCoefficients.mChannelData.AppendElement(data);
+ }
+ mCoefficients.mVolume = 1.0f;
+ mCoefficients.mBufferFormat = AUDIO_FORMAT_FLOAT32;
+}
+
+/* static */
+already_AddRefed<PeriodicWave> PeriodicWave::Constructor(
+ const GlobalObject& aGlobal, AudioContext& aAudioContext,
+ const PeriodicWaveOptions& aOptions, ErrorResult& aRv) {
+ const float* realData;
+ const float* imagData;
+ uint32_t realSize;
+ uint32_t imagSize;
+
+ if (aOptions.mReal.WasPassed()) {
+ realData = aOptions.mReal.Value().Elements();
+ realSize = aOptions.mReal.Value().Length();
+ } else {
+ realData = nullptr;
+ realSize = 0;
+ }
+
+ if (aOptions.mImag.WasPassed()) {
+ imagData = aOptions.mImag.Value().Elements();
+ imagSize = aOptions.mImag.Value().Length();
+ } else {
+ imagData = nullptr;
+ imagSize = 0;
+ }
+
+ RefPtr<PeriodicWave> wave =
+ new PeriodicWave(&aAudioContext, realData, realSize, imagData, imagSize,
+ aOptions.mDisableNormalization, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ return wave.forget();
+}
+
+size_t PeriodicWave::SizeOfExcludingThisIfNotShared(
+ MallocSizeOf aMallocSizeOf) const {
+ // Not owned:
+ // - mContext
+ size_t amount = 0;
+ amount += mCoefficients.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+
+ return amount;
+}
+
+size_t PeriodicWave::SizeOfIncludingThisIfNotShared(
+ MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThisIfNotShared(aMallocSizeOf);
+}
+
+JSObject* PeriodicWave::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return PeriodicWave_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/PeriodicWave.h b/dom/media/webaudio/PeriodicWave.h
new file mode 100644
index 0000000000..18408789a9
--- /dev/null
+++ b/dom/media/webaudio/PeriodicWave.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef PeriodicWave_h_
+#define PeriodicWave_h_
+
+#include "nsWrapperCache.h"
+#include "nsCycleCollectionParticipant.h"
+#include "mozilla/Attributes.h"
+#include "AudioContext.h"
+#include "AudioNodeEngine.h"
+
+namespace mozilla::dom {
+
+class AudioContext;
+struct PeriodicWaveOptions;
+
+class PeriodicWave final : public nsWrapperCache {
+ public:
+ PeriodicWave(AudioContext* aContext, const float* aRealData,
+ const uint32_t aRealSize, const float* aImagData,
+ const uint32_t aImagSize, const bool aDisableNormalization,
+ ErrorResult& aRv);
+
+ NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(PeriodicWave)
+ NS_DECL_CYCLE_COLLECTION_NATIVE_WRAPPERCACHE_CLASS(PeriodicWave)
+
+ static already_AddRefed<PeriodicWave> Constructor(
+ const GlobalObject& aGlobal, AudioContext& aAudioContext,
+ const PeriodicWaveOptions& aOptions, ErrorResult& aRv);
+
+ AudioContext* GetParentObject() const { return mContext; }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ uint32_t DataLength() const { return mCoefficients.mDuration; }
+
+ bool DisableNormalization() const { return mDisableNormalization; }
+
+ const AudioChunk& GetThreadSharedBuffer() const { return mCoefficients; }
+
+ size_t SizeOfExcludingThisIfNotShared(MallocSizeOf aMallocSizeOf) const;
+ size_t SizeOfIncludingThisIfNotShared(MallocSizeOf aMallocSizeOf) const;
+
+ private:
+ ~PeriodicWave() = default;
+
+ AudioChunk mCoefficients;
+ RefPtr<AudioContext> mContext;
+ bool mDisableNormalization;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/media/webaudio/PlayingRefChangeHandler.h b/dom/media/webaudio/PlayingRefChangeHandler.h
new file mode 100644
index 0000000000..cda01fdcd3
--- /dev/null
+++ b/dom/media/webaudio/PlayingRefChangeHandler.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef PlayingRefChangeHandler_h__
+#define PlayingRefChangeHandler_h__
+
+#include "nsThreadUtils.h"
+#include "AudioNodeTrack.h"
+
+namespace mozilla::dom {
+
+class PlayingRefChangeHandler final : public Runnable {
+ public:
+ enum ChangeType { ADDREF, RELEASE };
+ PlayingRefChangeHandler(AudioNodeTrack* aTrack, ChangeType aChange)
+ : Runnable("dom::PlayingRefChangeHandler"),
+ mTrack(aTrack),
+ mChange(aChange) {}
+
+ NS_IMETHOD Run() override {
+ RefPtr<AudioNode> node = mTrack->Engine()->NodeMainThread();
+ if (node) {
+ if (mChange == ADDREF) {
+ node->MarkActive();
+ } else if (mChange == RELEASE) {
+ node->MarkInactive();
+ }
+ }
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<AudioNodeTrack> mTrack;
+ ChangeType mChange;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/media/webaudio/ReportDecodeResultTask.h b/dom/media/webaudio/ReportDecodeResultTask.h
new file mode 100644
index 0000000000..49b7e39e04
--- /dev/null
+++ b/dom/media/webaudio/ReportDecodeResultTask.h
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ReportDecodeResultTask_h_
+#define ReportDecodeResultTask_h_
+
+#include "mozilla/Attributes.h"
+#include "MediaBufferDecoder.h"
+
+namespace mozilla {
+
+class ReportDecodeResultTask final : public Runnable {
+ public:
+ ReportDecodeResultTask(DecodeJob& aDecodeJob, DecodeJob::ResultFn aFunction)
+ : mDecodeJob(aDecodeJob), mFunction(aFunction) {
+ MOZ_ASSERT(aFunction);
+ }
+
+ NS_IMETHOD Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ (mDecodeJob.*mFunction)();
+
+ return NS_OK;
+ }
+
+ private:
+ DecodeJob& mDecodeJob;
+ DecodeJob::ResultFn mFunction;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webaudio/ScriptProcessorNode.cpp b/dom/media/webaudio/ScriptProcessorNode.cpp
new file mode 100644
index 0000000000..1e645a2b42
--- /dev/null
+++ b/dom/media/webaudio/ScriptProcessorNode.cpp
@@ -0,0 +1,549 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ScriptProcessorNode.h"
+#include "mozilla/dom/ScriptProcessorNodeBinding.h"
+#include "AudioBuffer.h"
+#include "AudioDestinationNode.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeTrack.h"
+#include "AudioProcessingEvent.h"
+#include "WebAudioUtils.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/PodOperations.h"
+#include <deque>
+#include "Tracing.h"
+
+namespace mozilla::dom {
+
+// The maximum latency, in seconds, that we can live with before dropping
+// buffers.
+static const float MAX_LATENCY_S = 0.5;
+
+// This class manages a queue of output buffers shared between
+// the main thread and the Media Track Graph thread.
+class SharedBuffers final {
+ private:
+ class OutputQueue final {
+ public:
+ explicit OutputQueue(const char* aName) : mMutex(aName) {}
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+ MOZ_REQUIRES(mMutex) {
+ mMutex.AssertCurrentThreadOwns();
+
+ size_t amount = 0;
+ for (size_t i = 0; i < mBufferList.size(); i++) {
+ amount += mBufferList[i].SizeOfExcludingThis(aMallocSizeOf, false);
+ }
+
+ return amount;
+ }
+
+ Mutex& Lock() const MOZ_RETURN_CAPABILITY(mMutex) {
+ return const_cast<OutputQueue*>(this)->mMutex;
+ }
+
+ size_t ReadyToConsume() const MOZ_REQUIRES(mMutex) {
+ // Accessed on both main thread and media graph thread.
+ mMutex.AssertCurrentThreadOwns();
+ return mBufferList.size();
+ }
+
+ // Produce one buffer
+ AudioChunk& Produce() MOZ_REQUIRES(mMutex) {
+ mMutex.AssertCurrentThreadOwns();
+ MOZ_ASSERT(NS_IsMainThread());
+ mBufferList.push_back(AudioChunk());
+ return mBufferList.back();
+ }
+
+ // Consumes one buffer.
+ AudioChunk Consume() MOZ_REQUIRES(mMutex) {
+ mMutex.AssertCurrentThreadOwns();
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(ReadyToConsume() > 0);
+ AudioChunk front = mBufferList.front();
+ mBufferList.pop_front();
+ return front;
+ }
+
+ // Empties the buffer queue.
+ void Clear() MOZ_REQUIRES(mMutex) {
+ mMutex.AssertCurrentThreadOwns();
+ mBufferList.clear();
+ }
+
+ private:
+ typedef std::deque<AudioChunk> BufferList;
+
+ // Synchronizes access to mBufferList. Note that it's the responsibility
+ // of the callers to perform the required locking, and we assert that every
+ // time we access mBufferList.
+ Mutex mMutex MOZ_UNANNOTATED;
+ // The list representing the queue.
+ BufferList mBufferList;
+ };
+
+ public:
+ explicit SharedBuffers(float aSampleRate)
+ : mOutputQueue("SharedBuffers::outputQueue"),
+ mDelaySoFar(TRACK_TIME_MAX),
+ mSampleRate(aSampleRate),
+ mLatency(0.0),
+ mDroppingBuffers(false) {}
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t amount = aMallocSizeOf(this);
+
+ {
+ MutexAutoLock lock(mOutputQueue.Lock());
+ amount += mOutputQueue.SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ return amount;
+ }
+
+ // main thread
+
+ // NotifyNodeIsConnected() may be called even when the state has not
+ // changed.
+ void NotifyNodeIsConnected(bool aIsConnected) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!aIsConnected) {
+ // Reset main thread state for FinishProducingOutputBuffer().
+ mLatency = 0.0f;
+ mLastEventTime = TimeStamp();
+ mDroppingBuffers = false;
+ // Don't flush the output buffer here because the graph thread may be
+ // using it now. The graph thread will flush when it knows it is
+ // disconnected.
+ }
+ mNodeIsConnected = aIsConnected;
+ }
+
+ void FinishProducingOutputBuffer(const AudioChunk& aBuffer) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mNodeIsConnected) {
+ // The output buffer is not used, and mLastEventTime will not be
+ // initialized until the node is re-connected.
+ return;
+ }
+
+ TimeStamp now = TimeStamp::Now();
+
+ if (mLastEventTime.IsNull()) {
+ mLastEventTime = now;
+ } else {
+ // When main thread blocking has built up enough so
+ // |mLatency > MAX_LATENCY_S|, frame dropping starts. It continues until
+ // the output buffer is completely empty, at which point the accumulated
+ // latency is also reset to 0.
+ // It could happen that the output queue becomes empty before the input
+ // node has fully caught up. In this case there will be events where
+ // |(now - mLastEventTime)| is very short, making mLatency negative.
+ // As this happens and the size of |mLatency| becomes greater than
+ // MAX_LATENCY_S, frame dropping starts again to maintain an as short
+ // output queue as possible.
+ float latency = (now - mLastEventTime).ToSeconds();
+ float bufferDuration = aBuffer.mDuration / mSampleRate;
+ mLatency += latency - bufferDuration;
+ mLastEventTime = now;
+ if (fabs(mLatency) > MAX_LATENCY_S) {
+ mDroppingBuffers = true;
+ }
+ }
+
+ MutexAutoLock lock(mOutputQueue.Lock());
+ if (mDroppingBuffers) {
+ if (mOutputQueue.ReadyToConsume()) {
+ return;
+ }
+ mDroppingBuffers = false;
+ mLatency = 0;
+ }
+
+ for (uint32_t offset = 0; offset < aBuffer.mDuration;
+ offset += WEBAUDIO_BLOCK_SIZE) {
+ AudioChunk& chunk = mOutputQueue.Produce();
+ chunk = aBuffer;
+ chunk.SliceTo(offset, offset + WEBAUDIO_BLOCK_SIZE);
+ }
+ }
+
+ // graph thread
+
+ AudioChunk GetOutputBuffer() {
+ MOZ_ASSERT(!NS_IsMainThread());
+ AudioChunk buffer;
+
+ {
+ MutexAutoLock lock(mOutputQueue.Lock());
+ if (mOutputQueue.ReadyToConsume() > 0) {
+ if (mDelaySoFar == TRACK_TIME_MAX) {
+ mDelaySoFar = 0;
+ }
+ buffer = mOutputQueue.Consume();
+ } else {
+ // If we're out of buffers to consume, just output silence
+ buffer.SetNull(WEBAUDIO_BLOCK_SIZE);
+ if (mDelaySoFar != TRACK_TIME_MAX) {
+ // Remember the delay that we just hit
+ mDelaySoFar += WEBAUDIO_BLOCK_SIZE;
+ }
+ }
+ }
+
+ return buffer;
+ }
+
+ TrackTime DelaySoFar() const {
+ MOZ_ASSERT(!NS_IsMainThread());
+ return mDelaySoFar == TRACK_TIME_MAX ? 0 : mDelaySoFar;
+ }
+
+ void Flush() {
+ MOZ_ASSERT(!NS_IsMainThread());
+ mDelaySoFar = TRACK_TIME_MAX;
+ {
+ MutexAutoLock lock(mOutputQueue.Lock());
+ mOutputQueue.Clear();
+ }
+ }
+
+ private:
+ OutputQueue mOutputQueue;
+ // How much delay we've seen so far. This measures the amount of delay
+ // caused by the main thread lagging behind in producing output buffers.
+ // TRACK_TIME_MAX means that we have not received our first buffer yet.
+ // Graph thread only.
+ TrackTime mDelaySoFar;
+ // The samplerate of the context.
+ const float mSampleRate;
+ // The remaining members are main thread only.
+ // This is the latency caused by the buffering. If this grows too high, we
+ // will drop buffers until it is acceptable.
+ float mLatency;
+ // This is the time at which we last produced a buffer, to detect if the main
+ // thread has been blocked.
+ TimeStamp mLastEventTime;
+ // True if we should be dropping buffers.
+ bool mDroppingBuffers;
+ // True iff the AudioNode has at least one input or output connected.
+ bool mNodeIsConnected;
+};
+
+class ScriptProcessorNodeEngine final : public AudioNodeEngine {
+ public:
+ ScriptProcessorNodeEngine(ScriptProcessorNode* aNode,
+ AudioDestinationNode* aDestination,
+ uint32_t aBufferSize,
+ uint32_t aNumberOfInputChannels)
+ : AudioNodeEngine(aNode),
+ mDestination(aDestination->Track()),
+ mSharedBuffers(new SharedBuffers(mDestination->mSampleRate)),
+ mBufferSize(aBufferSize),
+ mInputChannelCount(aNumberOfInputChannels),
+ mInputWriteIndex(0) {}
+
+ SharedBuffers* GetSharedBuffers() const { return mSharedBuffers.get(); }
+
+ enum {
+ IS_CONNECTED,
+ };
+
+ void SetInt32Parameter(uint32_t aIndex, int32_t aParam) override {
+ switch (aIndex) {
+ case IS_CONNECTED:
+ mIsConnected = aParam;
+ break;
+ default:
+ NS_ERROR("Bad Int32Parameter");
+ } // End index switch.
+ }
+
+ void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
+ const AudioBlock& aInput, AudioBlock* aOutput,
+ bool* aFinished) override {
+ TRACE("ScriptProcessorNodeEngine::ProcessBlock");
+
+ // This node is not connected to anything. Per spec, we don't fire the
+ // onaudioprocess event. We also want to clear out the input and output
+ // buffer queue, and output a null buffer.
+ if (!mIsConnected) {
+ aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
+ mSharedBuffers->Flush();
+ mInputWriteIndex = 0;
+ return;
+ }
+
+ // The input buffer is allocated lazily when non-null input is received.
+ if (!aInput.IsNull() && !mInputBuffer) {
+ mInputBuffer = ThreadSharedFloatArrayBufferList::Create(
+ mInputChannelCount, mBufferSize, fallible);
+ if (mInputBuffer && mInputWriteIndex) {
+ // Zero leading for null chunks that were skipped.
+ for (uint32_t i = 0; i < mInputChannelCount; ++i) {
+ float* channelData = mInputBuffer->GetDataForWrite(i);
+ PodZero(channelData, mInputWriteIndex);
+ }
+ }
+ }
+
+ // First, record our input buffer, if its allocation succeeded.
+ uint32_t inputChannelCount = mInputBuffer ? mInputBuffer->GetChannels() : 0;
+ for (uint32_t i = 0; i < inputChannelCount; ++i) {
+ float* writeData = mInputBuffer->GetDataForWrite(i) + mInputWriteIndex;
+ if (aInput.IsNull()) {
+ PodZero(writeData, aInput.GetDuration());
+ } else {
+ MOZ_ASSERT(aInput.GetDuration() == WEBAUDIO_BLOCK_SIZE, "sanity check");
+ MOZ_ASSERT(aInput.ChannelCount() == inputChannelCount);
+ AudioBlockCopyChannelWithScale(
+ static_cast<const float*>(aInput.mChannelData[i]), aInput.mVolume,
+ writeData);
+ }
+ }
+ mInputWriteIndex += aInput.GetDuration();
+
+ // Now, see if we have data to output
+ // Note that we need to do this before sending the buffer to the main
+ // thread so that our delay time is updated.
+ *aOutput = mSharedBuffers->GetOutputBuffer();
+
+ if (mInputWriteIndex >= mBufferSize) {
+ SendBuffersToMainThread(aTrack, aFrom);
+ mInputWriteIndex -= mBufferSize;
+ }
+ }
+
+ bool IsActive() const override {
+ // Could return false when !mIsConnected after all output chunks produced
+ // by main thread events calling
+ // SharedBuffers::FinishProducingOutputBuffer() have been processed.
+ return true;
+ }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override {
+ // Not owned:
+ // - mDestination (probably)
+ size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+ amount += mSharedBuffers->SizeOfIncludingThis(aMallocSizeOf);
+ if (mInputBuffer) {
+ amount += mInputBuffer->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ return amount;
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ private:
+ void SendBuffersToMainThread(AudioNodeTrack* aTrack, GraphTime aFrom) {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ // we now have a full input buffer ready to be sent to the main thread.
+ TrackTime playbackTick = mDestination->GraphTimeToTrackTime(aFrom);
+ // Add the duration of the current sample
+ playbackTick += WEBAUDIO_BLOCK_SIZE;
+ // Add the delay caused by the main thread
+ playbackTick += mSharedBuffers->DelaySoFar();
+ // Compute the playback time in the coordinate system of the destination
+ double playbackTime = mDestination->TrackTimeToSeconds(playbackTick);
+
+ class Command final : public Runnable {
+ public:
+ Command(AudioNodeTrack* aTrack,
+ already_AddRefed<ThreadSharedFloatArrayBufferList> aInputBuffer,
+ double aPlaybackTime)
+ : mozilla::Runnable("Command"),
+ mTrack(aTrack),
+ mInputBuffer(aInputBuffer),
+ mPlaybackTime(aPlaybackTime) {}
+
+ NS_IMETHOD Run() override {
+ auto engine = static_cast<ScriptProcessorNodeEngine*>(mTrack->Engine());
+ AudioChunk output;
+ output.SetNull(engine->mBufferSize);
+ {
+ auto node =
+ static_cast<ScriptProcessorNode*>(engine->NodeMainThread());
+ if (!node) {
+ return NS_OK;
+ }
+
+ if (node->HasListenersFor(nsGkAtoms::onaudioprocess)) {
+ DispatchAudioProcessEvent(node, &output);
+ }
+ // The node may have been destroyed during event dispatch.
+ }
+
+ // Append it to our output buffer queue
+ engine->GetSharedBuffers()->FinishProducingOutputBuffer(output);
+
+ return NS_OK;
+ }
+
+ // Sets up |output| iff buffers are set in event handlers.
+ void DispatchAudioProcessEvent(ScriptProcessorNode* aNode,
+ AudioChunk* aOutput) {
+ AudioContext* context = aNode->Context();
+ if (!context) {
+ return;
+ }
+
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(!jsapi.Init(aNode->GetOwner()))) {
+ return;
+ }
+ JSContext* cx = jsapi.cx();
+ uint32_t inputChannelCount = aNode->ChannelCount();
+
+ // Create the input buffer
+ RefPtr<AudioBuffer> inputBuffer;
+ if (mInputBuffer) {
+ ErrorResult rv;
+ inputBuffer = AudioBuffer::Create(
+ context->GetOwner(), inputChannelCount, aNode->BufferSize(),
+ context->SampleRate(), mInputBuffer.forget(), rv);
+ if (rv.Failed()) {
+ rv.SuppressException();
+ return;
+ }
+ }
+
+ // Ask content to produce data in the output buffer
+ // Note that we always avoid creating the output buffer here, and we try
+ // to avoid creating the input buffer as well. The AudioProcessingEvent
+ // class knows how to lazily create them if needed once the script tries
+ // to access them. Otherwise, we may be able to get away without
+ // creating them!
+ RefPtr<AudioProcessingEvent> event =
+ new AudioProcessingEvent(aNode, nullptr, nullptr);
+ event->InitEvent(inputBuffer, inputChannelCount, mPlaybackTime);
+ aNode->DispatchTrustedEvent(event);
+
+ // Steal the output buffers if they have been set.
+ // Don't create a buffer if it hasn't been used to return output;
+ // FinishProducingOutputBuffer() will optimize output = null.
+ // GetThreadSharedChannelsForRate() may also return null after OOM.
+ if (event->HasOutputBuffer()) {
+ ErrorResult rv;
+ AudioBuffer* buffer = event->GetOutputBuffer(rv);
+ // HasOutputBuffer() returning true means that GetOutputBuffer()
+ // will not fail.
+ MOZ_ASSERT(!rv.Failed());
+ *aOutput = buffer->GetThreadSharedChannelsForRate(cx);
+ MOZ_ASSERT(aOutput->IsNull() ||
+ aOutput->mBufferFormat == AUDIO_FORMAT_FLOAT32,
+ "AudioBuffers initialized from JS have float data");
+ }
+ }
+
+ private:
+ RefPtr<AudioNodeTrack> mTrack;
+ RefPtr<ThreadSharedFloatArrayBufferList> mInputBuffer;
+ double mPlaybackTime;
+ };
+
+ RefPtr<Command> command =
+ new Command(aTrack, mInputBuffer.forget(), playbackTime);
+ AbstractThread::MainThread()->Dispatch(command.forget());
+ }
+
+ friend class ScriptProcessorNode;
+
+ RefPtr<AudioNodeTrack> mDestination;
+ UniquePtr<SharedBuffers> mSharedBuffers;
+ RefPtr<ThreadSharedFloatArrayBufferList> mInputBuffer;
+ const uint32_t mBufferSize;
+ const uint32_t mInputChannelCount;
+ // The write index into the current input buffer
+ uint32_t mInputWriteIndex;
+ bool mIsConnected = false;
+};
+
+ScriptProcessorNode::ScriptProcessorNode(AudioContext* aContext,
+ uint32_t aBufferSize,
+ uint32_t aNumberOfInputChannels,
+ uint32_t aNumberOfOutputChannels)
+ : AudioNode(aContext, aNumberOfInputChannels,
+ mozilla::dom::ChannelCountMode::Explicit,
+ mozilla::dom::ChannelInterpretation::Speakers),
+ mBufferSize(aBufferSize ? aBufferSize
+ : // respect what the web developer requested
+ 4096) // choose our own buffer size -- 4KB for now
+ ,
+ mNumberOfOutputChannels(aNumberOfOutputChannels) {
+ MOZ_ASSERT(BufferSize() % WEBAUDIO_BLOCK_SIZE == 0, "Invalid buffer size");
+ ScriptProcessorNodeEngine* engine = new ScriptProcessorNodeEngine(
+ this, aContext->Destination(), BufferSize(), aNumberOfInputChannels);
+ mTrack = AudioNodeTrack::Create(
+ aContext, engine, AudioNodeTrack::NO_TRACK_FLAGS, aContext->Graph());
+}
+
+ScriptProcessorNode::~ScriptProcessorNode() = default;
+
+size_t ScriptProcessorNode::SizeOfExcludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+ return amount;
+}
+
+size_t ScriptProcessorNode::SizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+void ScriptProcessorNode::EventListenerAdded(nsAtom* aType) {
+ AudioNode::EventListenerAdded(aType);
+ if (aType == nsGkAtoms::onaudioprocess) {
+ UpdateConnectedStatus();
+ }
+}
+
+void ScriptProcessorNode::EventListenerRemoved(nsAtom* aType) {
+ AudioNode::EventListenerRemoved(aType);
+ if (aType == nsGkAtoms::onaudioprocess && mTrack) {
+ UpdateConnectedStatus();
+ }
+}
+
+JSObject* ScriptProcessorNode::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return ScriptProcessorNode_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void ScriptProcessorNode::UpdateConnectedStatus() {
+ bool isConnected =
+ mHasPhantomInput || !(OutputNodes().IsEmpty() &&
+ OutputParams().IsEmpty() && InputNodes().IsEmpty());
+
+ // Events are queued even when there is no listener because a listener
+ // may be added while events are in the queue.
+ SendInt32ParameterToTrack(ScriptProcessorNodeEngine::IS_CONNECTED,
+ isConnected);
+
+ if (isConnected && HasListenersFor(nsGkAtoms::onaudioprocess)) {
+ MarkActive();
+ } else {
+ MarkInactive();
+ }
+
+ // MarkInactive above might have released this node, check if it has a track.
+ if (!mTrack) {
+ return;
+ }
+
+ auto engine = static_cast<ScriptProcessorNodeEngine*>(mTrack->Engine());
+ engine->GetSharedBuffers()->NotifyNodeIsConnected(isConnected);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/ScriptProcessorNode.h b/dom/media/webaudio/ScriptProcessorNode.h
new file mode 100644
index 0000000000..c8f023cb17
--- /dev/null
+++ b/dom/media/webaudio/ScriptProcessorNode.h
@@ -0,0 +1,131 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ScriptProcessorNode_h_
+#define ScriptProcessorNode_h_
+
+#include "AudioNode.h"
+#include "mozilla/ErrorResult.h"
+
+namespace mozilla::dom {
+
+class AudioContext;
+class SharedBuffers;
+
+class ScriptProcessorNode final : public AudioNode {
+ public:
+ ScriptProcessorNode(AudioContext* aContext, uint32_t aBufferSize,
+ uint32_t aNumberOfInputChannels,
+ uint32_t aNumberOfOutputChannels);
+
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(ScriptProcessorNode, AudioNode)
+
+ IMPL_EVENT_HANDLER(audioprocess)
+
+ using EventTarget::EventListenerAdded;
+ void EventListenerAdded(nsAtom* aType) override;
+
+ using EventTarget::EventListenerRemoved;
+ void EventListenerRemoved(nsAtom* aType) override;
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ AudioNode* Connect(AudioNode& aDestination, uint32_t aOutput, uint32_t aInput,
+ ErrorResult& aRv) override {
+ AudioNode* node = AudioNode::Connect(aDestination, aOutput, aInput, aRv);
+ if (!aRv.Failed()) {
+ UpdateConnectedStatus();
+ }
+ return node;
+ }
+
+ void Connect(AudioParam& aDestination, uint32_t aOutput,
+ ErrorResult& aRv) override {
+ AudioNode::Connect(aDestination, aOutput, aRv);
+ if (!aRv.Failed()) {
+ UpdateConnectedStatus();
+ }
+ }
+ void Disconnect(ErrorResult& aRv) override {
+ AudioNode::Disconnect(aRv);
+ UpdateConnectedStatus();
+ }
+ void Disconnect(uint32_t aOutput, ErrorResult& aRv) override {
+ AudioNode::Disconnect(aOutput, aRv);
+ UpdateConnectedStatus();
+ }
+ void NotifyInputsChanged() override { UpdateConnectedStatus(); }
+ void NotifyHasPhantomInput() override {
+ mHasPhantomInput = true;
+ // No need to UpdateConnectedStatus() because there was previously an
+ // input in InputNodes().
+ }
+ void Disconnect(AudioNode& aDestination, ErrorResult& aRv) override {
+ AudioNode::Disconnect(aDestination, aRv);
+ UpdateConnectedStatus();
+ }
+ void Disconnect(AudioNode& aDestination, uint32_t aOutput,
+ ErrorResult& aRv) override {
+ AudioNode::Disconnect(aDestination, aOutput, aRv);
+ UpdateConnectedStatus();
+ }
+ void Disconnect(AudioNode& aDestination, uint32_t aOutput, uint32_t aInput,
+ ErrorResult& aRv) override {
+ AudioNode::Disconnect(aDestination, aOutput, aInput, aRv);
+ UpdateConnectedStatus();
+ }
+ void Disconnect(AudioParam& aDestination, ErrorResult& aRv) override {
+ AudioNode::Disconnect(aDestination, aRv);
+ UpdateConnectedStatus();
+ }
+ void Disconnect(AudioParam& aDestination, uint32_t aOutput,
+ ErrorResult& aRv) override {
+ AudioNode::Disconnect(aDestination, aOutput, aRv);
+ UpdateConnectedStatus();
+ }
+ void SetChannelCount(uint32_t aChannelCount, ErrorResult& aRv) override {
+ if (aChannelCount != ChannelCount()) {
+ // Spec says to throw InvalidStateError, but see
+ // https://github.com/WebAudio/web-audio-api/issues/2153
+ aRv.ThrowNotSupportedError(
+ "Cannot change channel count of ScriptProcessorNode");
+ }
+ }
+ void SetChannelCountModeValue(ChannelCountMode aMode,
+ ErrorResult& aRv) override {
+ if (aMode != ChannelCountMode::Explicit) {
+ // Spec says to throw InvalidStateError, but see
+ // https://github.com/WebAudio/web-audio-api/issues/2154
+ aRv.ThrowNotSupportedError(
+ "Cannot change channel count mode of ScriptProcessorNode");
+ }
+ }
+
+ uint32_t BufferSize() const { return mBufferSize; }
+
+ uint32_t NumberOfOutputChannels() const { return mNumberOfOutputChannels; }
+
+ using DOMEventTargetHelper::DispatchTrustedEvent;
+
+ const char* NodeType() const override { return "ScriptProcessorNode"; }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+ private:
+ virtual ~ScriptProcessorNode();
+
+ void UpdateConnectedStatus();
+
+ const uint32_t mBufferSize;
+ const uint32_t mNumberOfOutputChannels;
+ bool mHasPhantomInput = false;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/media/webaudio/StereoPannerNode.cpp b/dom/media/webaudio/StereoPannerNode.cpp
new file mode 100644
index 0000000000..758a92511a
--- /dev/null
+++ b/dom/media/webaudio/StereoPannerNode.cpp
@@ -0,0 +1,197 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "StereoPannerNode.h"
+#include "mozilla/dom/StereoPannerNodeBinding.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeTrack.h"
+#include "AudioDestinationNode.h"
+#include "AlignmentUtils.h"
+#include "WebAudioUtils.h"
+#include "PanningUtils.h"
+#include "AudioParamTimeline.h"
+#include "AudioParam.h"
+#include "Tracing.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(StereoPannerNode, AudioNode, mPan)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(StereoPannerNode)
+NS_INTERFACE_MAP_END_INHERITING(AudioNode)
+
+NS_IMPL_ADDREF_INHERITED(StereoPannerNode, AudioNode)
+NS_IMPL_RELEASE_INHERITED(StereoPannerNode, AudioNode)
+
+class StereoPannerNodeEngine final : public AudioNodeEngine {
+ public:
+ StereoPannerNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination)
+ : AudioNodeEngine(aNode),
+ mDestination(aDestination->Track())
+ // Keep the default value in sync with the default value in
+ // StereoPannerNode::StereoPannerNode.
+ ,
+ mPan(0.f) {}
+
+ enum Parameters { PAN };
+ void RecvTimelineEvent(uint32_t aIndex, AudioParamEvent& aEvent) override {
+ MOZ_ASSERT(mDestination);
+ aEvent.ConvertToTicks(mDestination);
+
+ switch (aIndex) {
+ case PAN:
+ mPan.InsertEvent<int64_t>(aEvent);
+ break;
+ default:
+ NS_ERROR("Bad StereoPannerNode TimelineParameter");
+ }
+ }
+
+ void GetGainValuesForPanning(float aPanning, bool aMonoToStereo,
+ float& aLeftGain, float& aRightGain) {
+ // Clamp and normalize the panning in [0; 1]
+ aPanning = std::min(std::max(aPanning, -1.f), 1.f);
+
+ if (aMonoToStereo) {
+ aPanning += 1;
+ aPanning /= 2;
+ } else if (aPanning <= 0) {
+ aPanning += 1;
+ }
+
+ aLeftGain = fdlibm_cos(0.5 * M_PI * aPanning);
+ aRightGain = fdlibm_sin(0.5 * M_PI * aPanning);
+ }
+
+ void SetToSilentStereoBlock(AudioBlock* aChunk) {
+ for (uint32_t channel = 0; channel < 2; channel++) {
+ float* samples = aChunk->ChannelFloatsForWrite(channel);
+ for (uint32_t i = 0; i < WEBAUDIO_BLOCK_SIZE; i++) {
+ samples[i] = 0.f;
+ }
+ }
+ }
+
+ void UpmixToStereoIfNeeded(const AudioBlock& aInput, AudioBlock* aOutput) {
+ if (aInput.ChannelCount() == 2) {
+ *aOutput = aInput;
+ } else {
+ MOZ_ASSERT(aInput.ChannelCount() == 1);
+ aOutput->SetBuffer(aInput.GetBuffer());
+ aOutput->mChannelData.SetLength(2);
+ for (uint32_t i = 0; i < 2; ++i) {
+ aOutput->mChannelData[i] = aInput.ChannelData<float>()[0];
+ }
+ // 1/sqrt(2) multiplier is because StereoPanner up-mixing differs from
+ // input up-mixing.
+ aOutput->mVolume = M_SQRT1_2 * aInput.mVolume;
+ aOutput->mBufferFormat = AUDIO_FORMAT_FLOAT32;
+ }
+ }
+
+ virtual void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
+ const AudioBlock& aInput, AudioBlock* aOutput,
+ bool* aFinished) override {
+ // The output of this node is always stereo, no matter what the inputs are.
+ MOZ_ASSERT(aInput.ChannelCount() <= 2);
+ TRACE("StereoPannerNodeEngine::ProcessBlock");
+
+ bool monoToStereo = aInput.ChannelCount() == 1;
+
+ if (aInput.IsNull()) {
+ // If input is silent, so is the output
+ aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
+ } else if (mPan.HasSimpleValue()) {
+ float panning = mPan.GetValue();
+ // If the panning is 0.0, we can simply copy the input to the
+ // output with gain applied, up-mixing to stereo if needed.
+ if (panning == 0.0f) {
+ UpmixToStereoIfNeeded(aInput, aOutput);
+ } else {
+ // Optimize the case where the panning is constant for this processing
+ // block: we can just apply a constant gain on the left and right
+ // channel
+ float gainL, gainR;
+
+ GetGainValuesForPanning(panning, monoToStereo, gainL, gainR);
+ ApplyStereoPanning(aInput, aOutput, gainL, gainR, panning <= 0);
+ }
+ } else {
+ float computedGain[2 * WEBAUDIO_BLOCK_SIZE + 4];
+ bool onLeft[WEBAUDIO_BLOCK_SIZE];
+
+ float values[WEBAUDIO_BLOCK_SIZE];
+ TrackTime tick = mDestination->GraphTimeToTrackTime(aFrom);
+ mPan.GetValuesAtTime(tick, values, WEBAUDIO_BLOCK_SIZE);
+
+ float* alignedComputedGain = ALIGNED16(computedGain);
+ ASSERT_ALIGNED16(alignedComputedGain);
+ for (size_t counter = 0; counter < WEBAUDIO_BLOCK_SIZE; ++counter) {
+ float left, right;
+ GetGainValuesForPanning(values[counter], monoToStereo, left, right);
+
+ alignedComputedGain[counter] = left;
+ alignedComputedGain[WEBAUDIO_BLOCK_SIZE + counter] = right;
+ onLeft[counter] = values[counter] <= 0;
+ }
+
+ // Apply the gain to the output buffer
+ ApplyStereoPanning(aInput, aOutput, alignedComputedGain,
+ &alignedComputedGain[WEBAUDIO_BLOCK_SIZE], onLeft);
+ }
+ }
+
+ virtual size_t SizeOfIncludingThis(
+ MallocSizeOf aMallocSizeOf) const override {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ RefPtr<AudioNodeTrack> mDestination;
+ AudioParamTimeline mPan;
+};
+
+StereoPannerNode::StereoPannerNode(AudioContext* aContext)
+ : AudioNode(aContext, 2, ChannelCountMode::Clamped_max,
+ ChannelInterpretation::Speakers) {
+ mPan =
+ CreateAudioParam(StereoPannerNodeEngine::PAN, u"pan"_ns, 0.f, -1.f, 1.f);
+ StereoPannerNodeEngine* engine =
+ new StereoPannerNodeEngine(this, aContext->Destination());
+ mTrack = AudioNodeTrack::Create(
+ aContext, engine, AudioNodeTrack::NO_TRACK_FLAGS, aContext->Graph());
+}
+
+/* static */
+already_AddRefed<StereoPannerNode> StereoPannerNode::Create(
+ AudioContext& aAudioContext, const StereoPannerOptions& aOptions,
+ ErrorResult& aRv) {
+ RefPtr<StereoPannerNode> audioNode = new StereoPannerNode(&aAudioContext);
+
+ audioNode->Initialize(aOptions, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ audioNode->Pan()->SetInitialValue(aOptions.mPan);
+ return audioNode.forget();
+}
+
+size_t StereoPannerNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+ amount += mPan->SizeOfIncludingThis(aMallocSizeOf);
+ return amount;
+}
+
+size_t StereoPannerNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+JSObject* StereoPannerNode::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return StereoPannerNode_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/StereoPannerNode.h b/dom/media/webaudio/StereoPannerNode.h
new file mode 100644
index 0000000000..9dc7370fd1
--- /dev/null
+++ b/dom/media/webaudio/StereoPannerNode.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef StereoPannerNode_h_
+#define StereoPannerNode_h_
+
+#include "AudioNode.h"
+#include "nsPrintfCString.h"
+#include "mozilla/RefCounted.h"
+#include "mozilla/dom/StereoPannerNodeBinding.h"
+
+namespace mozilla::dom {
+
+class AudioContext;
+struct StereoPannerOptions;
+
+class StereoPannerNode final : public AudioNode {
+ public:
+ static already_AddRefed<StereoPannerNode> Create(
+ AudioContext& aAudioContext, const StereoPannerOptions& aOptions,
+ ErrorResult& aRv);
+
+ MOZ_DECLARE_REFCOUNTED_TYPENAME(StereoPannerNode)
+
+ static already_AddRefed<StereoPannerNode> Constructor(
+ const GlobalObject& aGlobal, AudioContext& aAudioContext,
+ const StereoPannerOptions& aOptions, ErrorResult& aRv) {
+ return Create(aAudioContext, aOptions, aRv);
+ }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ virtual void SetChannelCount(uint32_t aChannelCount,
+ ErrorResult& aRv) override {
+ if (aChannelCount > 2) {
+ aRv.ThrowNotSupportedError(
+ nsPrintfCString("%u is greater than 2", aChannelCount));
+ return;
+ }
+ AudioNode::SetChannelCount(aChannelCount, aRv);
+ }
+ virtual void SetChannelCountModeValue(ChannelCountMode aMode,
+ ErrorResult& aRv) override {
+ if (aMode == ChannelCountMode::Max) {
+ aRv.ThrowNotSupportedError("Cannot set channel count mode to \"max\"");
+ return;
+ }
+ AudioNode::SetChannelCountModeValue(aMode, aRv);
+ }
+
+ AudioParam* Pan() const { return mPan; }
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(StereoPannerNode, AudioNode)
+
+ virtual const char* NodeType() const override { return "StereoPannerNode"; }
+
+ virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
+ virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
+
+ private:
+ explicit StereoPannerNode(AudioContext* aContext);
+ ~StereoPannerNode() = default;
+
+ RefPtr<AudioParam> mPan;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/media/webaudio/ThreeDPoint.cpp b/dom/media/webaudio/ThreeDPoint.cpp
new file mode 100644
index 0000000000..d54d03e24c
--- /dev/null
+++ b/dom/media/webaudio/ThreeDPoint.cpp
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Other similar methods can be added if needed.
+ */
+
+#include "ThreeDPoint.h"
+#include "WebAudioUtils.h"
+
+namespace mozilla::dom {
+
+bool ThreeDPoint::FuzzyEqual(const ThreeDPoint& other) {
+ return WebAudioUtils::FuzzyEqual(x, other.x) &&
+ WebAudioUtils::FuzzyEqual(y, other.y) &&
+ WebAudioUtils::FuzzyEqual(z, other.z);
+}
+
+ThreeDPoint operator-(const ThreeDPoint& lhs, const ThreeDPoint& rhs) {
+ return ThreeDPoint(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z);
+}
+
+ThreeDPoint operator*(const ThreeDPoint& lhs, const ThreeDPoint& rhs) {
+ return ThreeDPoint(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z);
+}
+
+ThreeDPoint operator*(const ThreeDPoint& lhs, const double rhs) {
+ return ThreeDPoint(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs);
+}
+
+bool operator==(const ThreeDPoint& lhs, const ThreeDPoint& rhs) {
+ return lhs.x == rhs.x && lhs.y == rhs.y && lhs.z == rhs.z;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/ThreeDPoint.h b/dom/media/webaudio/ThreeDPoint.h
new file mode 100644
index 0000000000..4e2c7358f8
--- /dev/null
+++ b/dom/media/webaudio/ThreeDPoint.h
@@ -0,0 +1,68 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ThreeDPoint_h_
+#define ThreeDPoint_h_
+
+#include <cmath>
+#include <algorithm>
+
+namespace mozilla::dom {
+
+struct ThreeDPoint final {
+ ThreeDPoint() : x(0.), y(0.), z(0.) {}
+ ThreeDPoint(double aX, double aY, double aZ) : x(aX), y(aY), z(aZ) {}
+
+ double Magnitude() const { return sqrt(x * x + y * y + z * z); }
+
+ void Normalize() {
+ // Zero vectors cannot be normalized. For our purpose, normalizing a zero
+ // vector results in a zero vector.
+ if (IsZero()) {
+ return;
+ }
+ // Normalize with the maximum norm first to avoid overflow and underflow.
+ double invMax = 1 / MaxNorm();
+ x *= invMax;
+ y *= invMax;
+ z *= invMax;
+
+ double invDistance = 1 / Magnitude();
+ x *= invDistance;
+ y *= invDistance;
+ z *= invDistance;
+ }
+
+ ThreeDPoint CrossProduct(const ThreeDPoint& rhs) const {
+ return ThreeDPoint(y * rhs.z - z * rhs.y, z * rhs.x - x * rhs.z,
+ x * rhs.y - y * rhs.x);
+ }
+
+ double DotProduct(const ThreeDPoint& rhs) {
+ return x * rhs.x + y * rhs.y + z * rhs.z;
+ }
+
+ bool IsZero() const { return x == 0 && y == 0 && z == 0; }
+
+ // For comparing two vectors of close to unit magnitude.
+ bool FuzzyEqual(const ThreeDPoint& other);
+
+ double x, y, z;
+
+ private:
+ double MaxNorm() const {
+ return std::max(fabs(x), std::max(fabs(y), fabs(z)));
+ }
+};
+
+ThreeDPoint operator-(const ThreeDPoint& lhs, const ThreeDPoint& rhs);
+ThreeDPoint operator*(const ThreeDPoint& lhs, const ThreeDPoint& rhs);
+ThreeDPoint operator*(const ThreeDPoint& lhs, const double rhs);
+bool operator==(const ThreeDPoint& lhs, const ThreeDPoint& rhs);
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/media/webaudio/WaveShaperNode.cpp b/dom/media/webaudio/WaveShaperNode.cpp
new file mode 100644
index 0000000000..f01b613039
--- /dev/null
+++ b/dom/media/webaudio/WaveShaperNode.cpp
@@ -0,0 +1,389 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WaveShaperNode.h"
+#include "mozilla/dom/WaveShaperNodeBinding.h"
+#include "AlignmentUtils.h"
+#include "AudioNode.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeTrack.h"
+#include "mozilla/PodOperations.h"
+#include "Tracing.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(WaveShaperNode)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WaveShaperNode, AudioNode)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WaveShaperNode, AudioNode)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(WaveShaperNode)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WaveShaperNode)
+NS_INTERFACE_MAP_END_INHERITING(AudioNode)
+
+NS_IMPL_ADDREF_INHERITED(WaveShaperNode, AudioNode)
+NS_IMPL_RELEASE_INHERITED(WaveShaperNode, AudioNode)
+
+static uint32_t ValueOf(OverSampleType aType) {
+ switch (aType) {
+ case OverSampleType::None:
+ return 1;
+ case OverSampleType::_2x:
+ return 2;
+ case OverSampleType::_4x:
+ return 4;
+ default:
+ MOZ_ASSERT_UNREACHABLE("We should never reach here");
+ return 1;
+ }
+}
+
+class Resampler final {
+ public:
+ Resampler()
+ : mType(OverSampleType::None),
+ mUpSampler(nullptr),
+ mDownSampler(nullptr),
+ mChannels(0),
+ mSampleRate(0) {}
+
+ ~Resampler() { Destroy(); }
+
+ void Reset(uint32_t aChannels, TrackRate aSampleRate, OverSampleType aType) {
+ if (aChannels == mChannels && aSampleRate == mSampleRate &&
+ aType == mType) {
+ return;
+ }
+
+ mChannels = aChannels;
+ mSampleRate = aSampleRate;
+ mType = aType;
+
+ Destroy();
+
+ if (aType == OverSampleType::None) {
+ mBuffer.Clear();
+ return;
+ }
+
+ mUpSampler = speex_resampler_init(aChannels, aSampleRate,
+ aSampleRate * ValueOf(aType),
+ SPEEX_RESAMPLER_QUALITY_MIN, nullptr);
+ mDownSampler =
+ speex_resampler_init(aChannels, aSampleRate * ValueOf(aType),
+ aSampleRate, SPEEX_RESAMPLER_QUALITY_MIN, nullptr);
+ mBuffer.SetLength(WEBAUDIO_BLOCK_SIZE * ValueOf(aType));
+ }
+
+ float* UpSample(uint32_t aChannel, const float* aInputData,
+ uint32_t aBlocks) {
+ uint32_t inSamples = WEBAUDIO_BLOCK_SIZE;
+ uint32_t outSamples = WEBAUDIO_BLOCK_SIZE * aBlocks;
+ float* outputData = mBuffer.Elements();
+
+ MOZ_ASSERT(mBuffer.Length() == outSamples);
+
+ WebAudioUtils::SpeexResamplerProcess(mUpSampler, aChannel, aInputData,
+ &inSamples, outputData, &outSamples);
+
+ MOZ_ASSERT(inSamples == WEBAUDIO_BLOCK_SIZE &&
+ outSamples == WEBAUDIO_BLOCK_SIZE * aBlocks);
+
+ return outputData;
+ }
+
+ void DownSample(uint32_t aChannel, float* aOutputData, uint32_t aBlocks) {
+ uint32_t inSamples = WEBAUDIO_BLOCK_SIZE * aBlocks;
+ uint32_t outSamples = WEBAUDIO_BLOCK_SIZE;
+ const float* inputData = mBuffer.Elements();
+
+ MOZ_ASSERT(mBuffer.Length() == inSamples);
+
+ WebAudioUtils::SpeexResamplerProcess(mDownSampler, aChannel, inputData,
+ &inSamples, aOutputData, &outSamples);
+
+ MOZ_ASSERT(inSamples == WEBAUDIO_BLOCK_SIZE * aBlocks &&
+ outSamples == WEBAUDIO_BLOCK_SIZE);
+ }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ size_t amount = 0;
+ // Future: properly measure speex memory
+ amount += aMallocSizeOf(mUpSampler);
+ amount += aMallocSizeOf(mDownSampler);
+ amount += mBuffer.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ return amount;
+ }
+
+ private:
+ void Destroy() {
+ if (mUpSampler) {
+ speex_resampler_destroy(mUpSampler);
+ mUpSampler = nullptr;
+ }
+ if (mDownSampler) {
+ speex_resampler_destroy(mDownSampler);
+ mDownSampler = nullptr;
+ }
+ }
+
+ private:
+ OverSampleType mType;
+ SpeexResamplerState* mUpSampler;
+ SpeexResamplerState* mDownSampler;
+ uint32_t mChannels;
+ TrackRate mSampleRate;
+ nsTArray<float> mBuffer;
+};
+
+class WaveShaperNodeEngine final : public AudioNodeEngine {
+ public:
+ explicit WaveShaperNodeEngine(AudioNode* aNode)
+ : AudioNodeEngine(aNode), mType(OverSampleType::None) {}
+
+ enum Parameters { TYPE };
+
+ void SetRawArrayData(nsTArray<float>&& aCurve) override {
+ mCurve = std::move(aCurve);
+ }
+
+ void SetInt32Parameter(uint32_t aIndex, int32_t aValue) override {
+ switch (aIndex) {
+ case TYPE:
+ mType = static_cast<OverSampleType>(aValue);
+ break;
+ default:
+ NS_ERROR("Bad WaveShaperNode Int32Parameter");
+ }
+ }
+
+ template <uint32_t blocks>
+ void ProcessCurve(const float* aInputBuffer, float* aOutputBuffer) {
+ for (uint32_t j = 0; j < WEBAUDIO_BLOCK_SIZE * blocks; ++j) {
+ // Index into the curve array based on the amplitude of the
+ // incoming signal by using an amplitude range of [-1, 1] and
+ // performing a linear interpolation of the neighbor values.
+ float index = (mCurve.Length() - 1) * (aInputBuffer[j] + 1.0f) / 2.0f;
+ if (index < 0.0f) {
+ aOutputBuffer[j] = mCurve[0];
+ } else {
+ int32_t indexLower = index;
+ if (static_cast<uint32_t>(indexLower) >= mCurve.Length() - 1) {
+ aOutputBuffer[j] = mCurve[mCurve.Length() - 1];
+ } else {
+ uint32_t indexHigher = indexLower + 1;
+ float interpolationFactor = index - indexLower;
+ aOutputBuffer[j] = (1.0f - interpolationFactor) * mCurve[indexLower] +
+ interpolationFactor * mCurve[indexHigher];
+ }
+ }
+ }
+ }
+
+ void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
+ const AudioBlock& aInput, AudioBlock* aOutput,
+ bool* aFinished) override {
+ TRACE("WaveShaperNodeEngine::ProcessBlock");
+
+ uint32_t channelCount = aInput.ChannelCount();
+ if (!mCurve.Length()) {
+ // Optimize the case where we don't have a curve buffer
+ *aOutput = aInput;
+ return;
+ }
+
+ // If the input is null, check to see if non-null output will be produced
+ bool nullInput = false;
+ if (channelCount == 0) {
+ float index = (mCurve.Length() - 1) * 0.5;
+ uint32_t indexLower = index;
+ uint32_t indexHigher = indexLower + 1;
+ float interpolationFactor = index - indexLower;
+ if ((1.0f - interpolationFactor) * mCurve[indexLower] +
+ interpolationFactor * mCurve[indexHigher] ==
+ 0.0) {
+ *aOutput = aInput;
+ return;
+ }
+ nullInput = true;
+ channelCount = 1;
+ }
+
+ aOutput->AllocateChannels(channelCount);
+ for (uint32_t i = 0; i < channelCount; ++i) {
+ const float* inputSamples;
+ float scaledInput[WEBAUDIO_BLOCK_SIZE + 4];
+ float* alignedScaledInput = ALIGNED16(scaledInput);
+ ASSERT_ALIGNED16(alignedScaledInput);
+ if (!nullInput) {
+ if (aInput.mVolume != 1.0f) {
+ AudioBlockCopyChannelWithScale(
+ static_cast<const float*>(aInput.mChannelData[i]), aInput.mVolume,
+ alignedScaledInput);
+ inputSamples = alignedScaledInput;
+ } else {
+ inputSamples = static_cast<const float*>(aInput.mChannelData[i]);
+ }
+ } else {
+ PodZero(alignedScaledInput, WEBAUDIO_BLOCK_SIZE);
+ inputSamples = alignedScaledInput;
+ }
+ float* outputBuffer = aOutput->ChannelFloatsForWrite(i);
+ float* sampleBuffer;
+
+ switch (mType) {
+ case OverSampleType::None:
+ mResampler.Reset(channelCount, aTrack->mSampleRate,
+ OverSampleType::None);
+ ProcessCurve<1>(inputSamples, outputBuffer);
+ break;
+ case OverSampleType::_2x:
+ mResampler.Reset(channelCount, aTrack->mSampleRate,
+ OverSampleType::_2x);
+ sampleBuffer = mResampler.UpSample(i, inputSamples, 2);
+ ProcessCurve<2>(sampleBuffer, sampleBuffer);
+ mResampler.DownSample(i, outputBuffer, 2);
+ break;
+ case OverSampleType::_4x:
+ mResampler.Reset(channelCount, aTrack->mSampleRate,
+ OverSampleType::_4x);
+ sampleBuffer = mResampler.UpSample(i, inputSamples, 4);
+ ProcessCurve<4>(sampleBuffer, sampleBuffer);
+ mResampler.DownSample(i, outputBuffer, 4);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("We should never reach here");
+ }
+ }
+ }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override {
+ size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+ amount += mCurve.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ amount += mResampler.SizeOfExcludingThis(aMallocSizeOf);
+ return amount;
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ private:
+ nsTArray<float> mCurve;
+ OverSampleType mType;
+ Resampler mResampler;
+};
+
+WaveShaperNode::WaveShaperNode(AudioContext* aContext)
+ : AudioNode(aContext, 2, ChannelCountMode::Max,
+ ChannelInterpretation::Speakers),
+ mType(OverSampleType::None) {
+ WaveShaperNodeEngine* engine = new WaveShaperNodeEngine(this);
+ mTrack = AudioNodeTrack::Create(
+ aContext, engine, AudioNodeTrack::NO_TRACK_FLAGS, aContext->Graph());
+}
+
+/* static */
+already_AddRefed<WaveShaperNode> WaveShaperNode::Create(
+ AudioContext& aAudioContext, const WaveShaperOptions& aOptions,
+ ErrorResult& aRv) {
+ RefPtr<WaveShaperNode> audioNode = new WaveShaperNode(&aAudioContext);
+
+ audioNode->Initialize(aOptions, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ if (aOptions.mCurve.WasPassed()) {
+ audioNode->SetCurveInternal(aOptions.mCurve.Value(), aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+ }
+
+ audioNode->SetOversample(aOptions.mOversample);
+ return audioNode.forget();
+}
+
+JSObject* WaveShaperNode::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return WaveShaperNode_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void WaveShaperNode::SetCurve(const Nullable<Float32Array>& aCurve,
+ ErrorResult& aRv) {
+ // Let's purge the cached value for the curve attribute.
+ WaveShaperNode_Binding::ClearCachedCurveValue(this);
+
+ if (aCurve.IsNull()) {
+ CleanCurveInternal();
+ return;
+ }
+
+ nsTArray<float> curve;
+ if (!aCurve.Value().AppendDataTo(curve)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ SetCurveInternal(curve, aRv);
+}
+
+void WaveShaperNode::SetCurveInternal(const nsTArray<float>& aCurve,
+ ErrorResult& aRv) {
+ if (aCurve.Length() < 2) {
+ aRv.ThrowInvalidStateError("Must have at least two entries");
+ return;
+ }
+
+ mCurve = aCurve.Clone();
+ SendCurveToTrack();
+}
+
+void WaveShaperNode::CleanCurveInternal() {
+ mCurve.Clear();
+ SendCurveToTrack();
+}
+
+void WaveShaperNode::SendCurveToTrack() {
+ AudioNodeTrack* ns = mTrack;
+ MOZ_ASSERT(ns, "Why don't we have a track here?");
+
+ nsTArray<float> copyCurve(mCurve.Clone());
+ ns->SetRawArrayData(std::move(copyCurve));
+}
+
+void WaveShaperNode::GetCurve(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aError) {
+ // Let's return a null value if the list is empty.
+ if (mCurve.IsEmpty()) {
+ aRetval.set(nullptr);
+ return;
+ }
+
+ MOZ_ASSERT(mCurve.Length() >= 2);
+ JSObject* curve = Float32Array::Create(aCx, this, mCurve, aError);
+ if (aError.Failed()) {
+ return;
+ }
+ aRetval.set(curve);
+}
+
+void WaveShaperNode::SetOversample(OverSampleType aType) {
+ mType = aType;
+ SendInt32ParameterToTrack(WaveShaperNodeEngine::TYPE,
+ static_cast<int32_t>(aType));
+}
+
+} // namespace mozilla::dom
diff --git a/dom/media/webaudio/WaveShaperNode.h b/dom/media/webaudio/WaveShaperNode.h
new file mode 100644
index 0000000000..5c91960af9
--- /dev/null
+++ b/dom/media/webaudio/WaveShaperNode.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WaveShaperNode_h_
+#define WaveShaperNode_h_
+
+#include "AudioNode.h"
+#include "mozilla/dom/WaveShaperNodeBinding.h"
+#include "mozilla/dom/TypedArray.h"
+
+namespace mozilla::dom {
+
+class AudioContext;
+struct WaveShaperOptions;
+
+class WaveShaperNode final : public AudioNode {
+ public:
+ static already_AddRefed<WaveShaperNode> Create(
+ AudioContext& aAudioContext, const WaveShaperOptions& aOptions,
+ ErrorResult& aRv);
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(WaveShaperNode,
+ AudioNode)
+
+ static already_AddRefed<WaveShaperNode> Constructor(
+ const GlobalObject& aGlobal, AudioContext& aAudioContext,
+ const WaveShaperOptions& aOptions, ErrorResult& aRv) {
+ return Create(aAudioContext, aOptions, aRv);
+ }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void GetCurve(JSContext* aCx, JS::MutableHandle<JSObject*> aRetval,
+ ErrorResult& aRv);
+ void SetCurve(const Nullable<Float32Array>& aData, ErrorResult& aRv);
+
+ OverSampleType Oversample() const { return mType; }
+ void SetOversample(OverSampleType aType);
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override {
+ // Possibly track in the future:
+ // - mCurve
+ return AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ const char* NodeType() const override { return "WaveShaperNode"; }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ private:
+ explicit WaveShaperNode(AudioContext* aContext);
+ ~WaveShaperNode() = default;
+
+ void SetCurveInternal(const nsTArray<float>& aCurve, ErrorResult& aRv);
+ void CleanCurveInternal();
+
+ void SendCurveToTrack();
+
+ nsTArray<float> mCurve;
+ OverSampleType mType;
+};
+
+} // namespace mozilla::dom
+
+#endif
diff --git a/dom/media/webaudio/WebAudioUtils.cpp b/dom/media/webaudio/WebAudioUtils.cpp
new file mode 100644
index 0000000000..a1338650e0
--- /dev/null
+++ b/dom/media/webaudio/WebAudioUtils.cpp
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebAudioUtils.h"
+#include "blink/HRTFDatabaseLoader.h"
+
+#include "nsComponentManagerUtils.h"
+#include "nsContentUtils.h"
+#include "nsIConsoleService.h"
+#include "nsIScriptError.h"
+#include "nsJSUtils.h"
+#include "nsServiceManagerUtils.h"
+
+#include "mozilla/SchedulerGroup.h"
+
+namespace mozilla {
+
+LazyLogModule gWebAudioAPILog("WebAudioAPI");
+
+namespace dom {
+
+void WebAudioUtils::Shutdown() { WebCore::HRTFDatabaseLoader::shutdown(); }
+
+int WebAudioUtils::SpeexResamplerProcess(SpeexResamplerState* aResampler,
+ uint32_t aChannel, const float* aIn,
+ uint32_t* aInLen, float* aOut,
+ uint32_t* aOutLen) {
+ return speex_resampler_process_float(aResampler, aChannel, aIn, aInLen, aOut,
+ aOutLen);
+}
+
+int WebAudioUtils::SpeexResamplerProcess(SpeexResamplerState* aResampler,
+ uint32_t aChannel, const int16_t* aIn,
+ uint32_t* aInLen, float* aOut,
+ uint32_t* aOutLen) {
+ AutoTArray<AudioDataValue, WEBAUDIO_BLOCK_SIZE * 4> tmp;
+ tmp.SetLength(*aInLen);
+ ConvertAudioSamples(aIn, tmp.Elements(), *aInLen);
+ int result = speex_resampler_process_float(
+ aResampler, aChannel, tmp.Elements(), aInLen, aOut, aOutLen);
+ return result;
+}
+
+int WebAudioUtils::SpeexResamplerProcess(SpeexResamplerState* aResampler,
+ uint32_t aChannel, const int16_t* aIn,
+ uint32_t* aInLen, int16_t* aOut,
+ uint32_t* aOutLen) {
+ AutoTArray<AudioDataValue, WEBAUDIO_BLOCK_SIZE * 4> tmp1;
+ AutoTArray<AudioDataValue, WEBAUDIO_BLOCK_SIZE * 4> tmp2;
+ tmp1.SetLength(*aInLen);
+ tmp2.SetLength(*aOutLen);
+ ConvertAudioSamples(aIn, tmp1.Elements(), *aInLen);
+ int result = speex_resampler_process_float(
+ aResampler, aChannel, tmp1.Elements(), aInLen, tmp2.Elements(), aOutLen);
+ ConvertAudioSamples(tmp2.Elements(), aOut, *aOutLen);
+ return result;
+}
+
+void WebAudioUtils::LogToDeveloperConsole(uint64_t aWindowID,
+ const char* aKey) {
+ // This implementation is derived from dom/media/VideoUtils.cpp, but we
+ // use a windowID so that the message is delivered to the developer console.
+ // It is similar to ContentUtils::ReportToConsole, but also works off main
+ // thread.
+ if (!NS_IsMainThread()) {
+ nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction(
+ "dom::WebAudioUtils::LogToDeveloperConsole",
+ [aWindowID, aKey] { LogToDeveloperConsole(aWindowID, aKey); });
+ SchedulerGroup::Dispatch(task.forget());
+ return;
+ }
+
+ nsCOMPtr<nsIConsoleService> console(
+ do_GetService("@mozilla.org/consoleservice;1"));
+ if (!console) {
+ NS_WARNING("Failed to log message to console.");
+ return;
+ }
+
+ nsAutoString spec;
+ uint32_t aLineNumber = 0, aColumnNumber = 1;
+ JSContext* cx = nsContentUtils::GetCurrentJSContext();
+ if (cx) {
+ nsJSUtils::GetCallingLocation(cx, spec, &aLineNumber, &aColumnNumber);
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIScriptError> errorObject =
+ do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
+ if (!errorObject) {
+ NS_WARNING("Failed to log message to console.");
+ return;
+ }
+
+ nsAutoString result;
+ rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES, aKey,
+ result);
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Failed to log message to console.");
+ return;
+ }
+
+ errorObject->InitWithWindowID(result, spec, u""_ns, aLineNumber,
+ aColumnNumber, nsIScriptError::warningFlag,
+ "Web Audio", aWindowID);
+ console->LogMessage(errorObject);
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/media/webaudio/WebAudioUtils.h b/dom/media/webaudio/WebAudioUtils.h
new file mode 100644
index 0000000000..08e4974771
--- /dev/null
+++ b/dom/media/webaudio/WebAudioUtils.h
@@ -0,0 +1,184 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WebAudioUtils_h_
+#define WebAudioUtils_h_
+
+#include <cmath>
+#include <limits>
+#include <type_traits>
+#include "fdlibm.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/Logging.h"
+#include "MediaSegment.h"
+
+// Forward declaration
+typedef struct SpeexResamplerState_ SpeexResamplerState;
+
+namespace mozilla {
+
+extern LazyLogModule gWebAudioAPILog;
+#define WEB_AUDIO_API_LOG(...) \
+ MOZ_LOG(gWebAudioAPILog, LogLevel::Debug, (__VA_ARGS__))
+
+namespace dom::WebAudioUtils {
+
+// 32 is the minimum required by the spec for createBuffer() and
+// createScriptProcessor() and matches what is used by Blink. The limit
+// protects against large memory allocations.
+const size_t MaxChannelCount = 32;
+// AudioContext::CreateBuffer() "must support sample-rates in at least the
+// range 22050 to 96000."
+const uint32_t MinSampleRate = 8000;
+const uint32_t MaxSampleRate = 192000;
+
+inline bool FuzzyEqual(float v1, float v2) { return fabsf(v1 - v2) < 1e-7f; }
+inline bool FuzzyEqual(double v1, double v2) { return fabs(v1 - v2) < 1e-7; }
+
+/**
+ * Converts a linear value to decibels. Returns aMinDecibels if the linear
+ * value is 0.
+ */
+inline float ConvertLinearToDecibels(float aLinearValue, float aMinDecibels) {
+ return aLinearValue ? 20.0f * fdlibm_log10f(aLinearValue) : aMinDecibels;
+}
+
+/**
+ * Converts a decibel value to a linear value.
+ */
+inline float ConvertDecibelsToLinear(float aDecibels) {
+ return fdlibm_powf(10.0f, 0.05f * aDecibels);
+}
+
+inline void FixNaN(double& aDouble) {
+ if (std::isnan(aDouble) || std::isinf(aDouble)) {
+ aDouble = 0.0;
+ }
+}
+
+inline double DiscreteTimeConstantForSampleRate(double timeConstant,
+ double sampleRate) {
+ return 1.0 - fdlibm_exp(-1.0 / (sampleRate * timeConstant));
+}
+
+inline bool IsTimeValid(double aTime) {
+ return aTime >= 0 && aTime <= (MEDIA_TIME_MAX >> TRACK_RATE_MAX_BITS);
+}
+
+/**
+ * Converts a floating point value to an integral type in a safe and
+ * platform agnostic way. The following program demonstrates the kinds
+ * of ways things can go wrong depending on the CPU architecture you're
+ * compiling for:
+ *
+ * #include <stdio.h>
+ * volatile float r;
+ * int main()
+ * {
+ * unsigned int q;
+ * r = 1e100;
+ * q = r;
+ * printf("%f %d\n", r, q);
+ * r = -1e100;
+ * q = r;
+ * printf("%f %d\n", r, q);
+ * r = 1e15;
+ * q = r;
+ * printf("%f %x\n", r, q);
+ * r = 0/0.;
+ * q = r;
+ * printf("%f %d\n", r, q);
+ * }
+ *
+ * This program, when compiled for unsigned int, generates the following
+ * results depending on the architecture:
+ *
+ * x86 and x86-64
+ * ---
+ * inf 0
+ * -inf 0
+ * 999999995904.000000 -727384064 d4a50000
+ * nan 0
+ *
+ * ARM
+ * ---
+ * inf -1
+ * -inf 0
+ * 999999995904.000000 -1
+ * nan 0
+ *
+ * When compiled for int, this program generates the following results:
+ *
+ * x86 and x86-64
+ * ---
+ * inf -2147483648
+ * -inf -2147483648
+ * 999999995904.000000 -2147483648
+ * nan -2147483648
+ *
+ * ARM
+ * ---
+ * inf 2147483647
+ * -inf -2147483648
+ * 999999995904.000000 2147483647
+ * nan 0
+ *
+ * Note that the caller is responsible to make sure that the value
+ * passed to this function is not a NaN. This function will abort if
+ * it sees a NaN.
+ */
+template <typename IntType, typename FloatType>
+IntType TruncateFloatToInt(FloatType f) {
+ using std::numeric_limits;
+ static_assert(std::is_integral_v<IntType> == true,
+ "IntType must be an integral type");
+ static_assert(std::is_floating_point_v<FloatType> == true,
+ "FloatType must be a floating point type");
+
+ if (std::isnan(f)) {
+ // It is the responsibility of the caller to deal with NaN values.
+ // If we ever get to this point, we have a serious bug to fix.
+ MOZ_CRASH("We should never see a NaN here");
+ }
+
+ // If the floating point value is outside of the range of maximum
+ // integral value for this type, just clamp to the maximum value.
+ // The equality case must also return max() due to loss of precision when
+ // converting max() to float.
+ if (f >= FloatType(numeric_limits<IntType>::max())) {
+ return numeric_limits<IntType>::max();
+ }
+
+ if (f <= FloatType(numeric_limits<IntType>::min())) {
+ // If the floating point value is outside of the range of minimum
+ // integral value for this type, just clamp to the minimum value.
+ return numeric_limits<IntType>::min();
+ }
+
+ // Otherwise, this conversion must be well defined.
+ return IntType(f);
+}
+
+void Shutdown();
+
+int SpeexResamplerProcess(SpeexResamplerState* aResampler, uint32_t aChannel,
+ const float* aIn, uint32_t* aInLen, float* aOut,
+ uint32_t* aOutLen);
+
+int SpeexResamplerProcess(SpeexResamplerState* aResampler, uint32_t aChannel,
+ const int16_t* aIn, uint32_t* aInLen, float* aOut,
+ uint32_t* aOutLen);
+
+int SpeexResamplerProcess(SpeexResamplerState* aResampler, uint32_t aChannel,
+ const int16_t* aIn, uint32_t* aInLen, int16_t* aOut,
+ uint32_t* aOutLen);
+
+void LogToDeveloperConsole(uint64_t aWindowID, const char* aKey);
+
+} // namespace dom::WebAudioUtils
+} // namespace mozilla
+
+#endif
diff --git a/dom/media/webaudio/blink/Biquad.cpp b/dom/media/webaudio/blink/Biquad.cpp
new file mode 100644
index 0000000000..f8ce08f9bb
--- /dev/null
+++ b/dom/media/webaudio/blink/Biquad.cpp
@@ -0,0 +1,442 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Biquad.h"
+
+#include "DenormalDisabler.h"
+
+#include <float.h>
+#include <algorithm>
+#include <math.h>
+
+#include "fdlibm.h"
+
+namespace WebCore {
+
+Biquad::Biquad() {
+ // Initialize as pass-thru (straight-wire, no filter effect)
+ setNormalizedCoefficients(1, 0, 0, 1, 0, 0);
+
+ reset(); // clear filter memory
+}
+
+Biquad::~Biquad() = default;
+
+void Biquad::process(const float* sourceP, float* destP,
+ size_t framesToProcess) {
+ // Create local copies of member variables
+ double x1 = m_x1;
+ double x2 = m_x2;
+ double y1 = m_y1;
+ double y2 = m_y2;
+
+ double b0 = m_b0;
+ double b1 = m_b1;
+ double b2 = m_b2;
+ double a1 = m_a1;
+ double a2 = m_a2;
+
+ for (size_t i = 0; i < framesToProcess; ++i) {
+ // FIXME: this can be optimized by pipelining the multiply adds...
+ double x = sourceP[i];
+ double y = b0 * x + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2;
+
+ destP[i] = y;
+
+ // Update state variables
+ x2 = x1;
+ x1 = x;
+ y2 = y1;
+ y1 = y;
+ }
+
+ // Avoid introducing a stream of subnormals when input is silent and the
+ // tail approaches zero.
+ if (x1 == 0.0 && x2 == 0.0 && (y1 != 0.0 || y2 != 0.0) &&
+ fabs(y1) < FLT_MIN && fabs(y2) < FLT_MIN) {
+ // Flush future values to zero (until there is new input).
+ y1 = y2 = 0.0;
+// Flush calculated values.
+#ifndef HAVE_DENORMAL
+ for (int i = framesToProcess; i-- && fabsf(destP[i]) < FLT_MIN;) {
+ destP[i] = 0.0f;
+ }
+#endif
+ }
+ // Local variables back to member.
+ m_x1 = x1;
+ m_x2 = x2;
+ m_y1 = y1;
+ m_y2 = y2;
+}
+
+void Biquad::reset() { m_x1 = m_x2 = m_y1 = m_y2 = 0; }
+
+void Biquad::setLowpassParams(double cutoff, double resonance) {
+ // Limit cutoff to 0 to 1.
+ cutoff = std::max(0.0, std::min(cutoff, 1.0));
+
+ if (cutoff == 1) {
+ // When cutoff is 1, the z-transform is 1.
+ setNormalizedCoefficients(1, 0, 0, 1, 0, 0);
+ } else if (cutoff > 0) {
+ // Compute biquad coefficients for lowpass filter
+ double g = fdlibm_pow(10.0, -0.05 * resonance);
+ double w0 = M_PI * cutoff;
+ double cos_w0 = fdlibm_cos(w0);
+ double alpha = 0.5 * fdlibm_sin(w0) * g;
+
+ double b1 = 1.0 - cos_w0;
+ double b0 = 0.5 * b1;
+ double b2 = b0;
+ double a0 = 1.0 + alpha;
+ double a1 = -2.0 * cos_w0;
+ double a2 = 1.0 - alpha;
+
+ setNormalizedCoefficients(b0, b1, b2, a0, a1, a2);
+ } else {
+ // When cutoff is zero, nothing gets through the filter, so set
+ // coefficients up correctly.
+ setNormalizedCoefficients(0, 0, 0, 1, 0, 0);
+ }
+}
+
+void Biquad::setHighpassParams(double cutoff, double resonance) {
+ // Limit cutoff to 0 to 1.
+ cutoff = std::max(0.0, std::min(cutoff, 1.0));
+
+ if (cutoff == 1) {
+ // The z-transform is 0.
+ setNormalizedCoefficients(0, 0, 0, 1, 0, 0);
+ } else if (cutoff > 0) {
+ // Compute biquad coefficients for highpass filter
+ double g = fdlibm_pow(10.0, -0.05 * resonance);
+ double w0 = M_PI * cutoff;
+ double cos_w0 = fdlibm_cos(w0);
+ double alpha = 0.5 * fdlibm_sin(w0) * g;
+
+ double b1 = -1.0 - cos_w0;
+ double b0 = -0.5 * b1;
+ double b2 = b0;
+ double a0 = 1.0 + alpha;
+ double a1 = -2.0 * cos_w0;
+ double a2 = 1.0 - alpha;
+
+ setNormalizedCoefficients(b0, b1, b2, a0, a1, a2);
+ } else {
+ // When cutoff is zero, we need to be careful because the above
+ // gives a quadratic divided by the same quadratic, with poles
+ // and zeros on the unit circle in the same place. When cutoff
+ // is zero, the z-transform is 1.
+ setNormalizedCoefficients(1, 0, 0, 1, 0, 0);
+ }
+}
+
+void Biquad::setNormalizedCoefficients(double b0, double b1, double b2,
+ double a0, double a1, double a2) {
+ double a0Inverse = 1 / a0;
+
+ m_b0 = b0 * a0Inverse;
+ m_b1 = b1 * a0Inverse;
+ m_b2 = b2 * a0Inverse;
+ m_a1 = a1 * a0Inverse;
+ m_a2 = a2 * a0Inverse;
+}
+
+void Biquad::setLowShelfParams(double frequency, double dbGain) {
+ // Clip frequencies to between 0 and 1, inclusive.
+ frequency = std::max(0.0, std::min(frequency, 1.0));
+
+ double A = fdlibm_pow(10.0, dbGain / 40);
+
+ if (frequency == 1) {
+ // The z-transform is a constant gain.
+ setNormalizedCoefficients(A * A, 0, 0, 1, 0, 0);
+ } else if (frequency > 0) {
+ double w0 = M_PI * frequency;
+ double S = 1; // filter slope (1 is max value)
+ double alpha = 0.5 * fdlibm_sin(w0) * sqrt((A + 1 / A) * (1 / S - 1) + 2);
+ double k = fdlibm_cos(w0);
+ double k2 = 2 * sqrt(A) * alpha;
+ double aPlusOne = A + 1;
+ double aMinusOne = A - 1;
+
+ double b0 = A * (aPlusOne - aMinusOne * k + k2);
+ double b1 = 2 * A * (aMinusOne - aPlusOne * k);
+ double b2 = A * (aPlusOne - aMinusOne * k - k2);
+ double a0 = aPlusOne + aMinusOne * k + k2;
+ double a1 = -2 * (aMinusOne + aPlusOne * k);
+ double a2 = aPlusOne + aMinusOne * k - k2;
+
+ setNormalizedCoefficients(b0, b1, b2, a0, a1, a2);
+ } else {
+ // When frequency is 0, the z-transform is 1.
+ setNormalizedCoefficients(1, 0, 0, 1, 0, 0);
+ }
+}
+
+void Biquad::setHighShelfParams(double frequency, double dbGain) {
+ // Clip frequencies to between 0 and 1, inclusive.
+ frequency = std::max(0.0, std::min(frequency, 1.0));
+
+ double A = fdlibm_pow(10.0, dbGain / 40);
+
+ if (frequency == 1) {
+ // The z-transform is 1.
+ setNormalizedCoefficients(1, 0, 0, 1, 0, 0);
+ } else if (frequency > 0) {
+ double w0 = M_PI * frequency;
+ double S = 1; // filter slope (1 is max value)
+ double alpha = 0.5 * fdlibm_sin(w0) * sqrt((A + 1 / A) * (1 / S - 1) + 2);
+ double k = fdlibm_cos(w0);
+ double k2 = 2 * sqrt(A) * alpha;
+ double aPlusOne = A + 1;
+ double aMinusOne = A - 1;
+
+ double b0 = A * (aPlusOne + aMinusOne * k + k2);
+ double b1 = -2 * A * (aMinusOne + aPlusOne * k);
+ double b2 = A * (aPlusOne + aMinusOne * k - k2);
+ double a0 = aPlusOne - aMinusOne * k + k2;
+ double a1 = 2 * (aMinusOne - aPlusOne * k);
+ double a2 = aPlusOne - aMinusOne * k - k2;
+
+ setNormalizedCoefficients(b0, b1, b2, a0, a1, a2);
+ } else {
+ // When frequency = 0, the filter is just a gain, A^2.
+ setNormalizedCoefficients(A * A, 0, 0, 1, 0, 0);
+ }
+}
+
+void Biquad::setPeakingParams(double frequency, double Q, double dbGain) {
+ // Clip frequencies to between 0 and 1, inclusive.
+ frequency = std::max(0.0, std::min(frequency, 1.0));
+
+ // Don't let Q go negative, which causes an unstable filter.
+ Q = std::max(0.0, Q);
+
+ double A = fdlibm_pow(10.0, dbGain / 40);
+
+ if (frequency > 0 && frequency < 1) {
+ if (Q > 0) {
+ double w0 = M_PI * frequency;
+ double alpha = fdlibm_sin(w0) / (2 * Q);
+ double k = fdlibm_cos(w0);
+
+ double b0 = 1 + alpha * A;
+ double b1 = -2 * k;
+ double b2 = 1 - alpha * A;
+ double a0 = 1 + alpha / A;
+ double a1 = -2 * k;
+ double a2 = 1 - alpha / A;
+
+ setNormalizedCoefficients(b0, b1, b2, a0, a1, a2);
+ } else {
+ // When Q = 0, the above formulas have problems. If we look at
+ // the z-transform, we can see that the limit as Q->0 is A^2, so
+ // set the filter that way.
+ setNormalizedCoefficients(A * A, 0, 0, 1, 0, 0);
+ }
+ } else {
+ // When frequency is 0 or 1, the z-transform is 1.
+ setNormalizedCoefficients(1, 0, 0, 1, 0, 0);
+ }
+}
+
+void Biquad::setAllpassParams(double frequency, double Q) {
+ // Clip frequencies to between 0 and 1, inclusive.
+ frequency = std::max(0.0, std::min(frequency, 1.0));
+
+ // Don't let Q go negative, which causes an unstable filter.
+ Q = std::max(0.0, Q);
+
+ if (frequency > 0 && frequency < 1) {
+ if (Q > 0) {
+ double w0 = M_PI * frequency;
+ double alpha = fdlibm_sin(w0) / (2 * Q);
+ double k = fdlibm_cos(w0);
+
+ double b0 = 1 - alpha;
+ double b1 = -2 * k;
+ double b2 = 1 + alpha;
+ double a0 = 1 + alpha;
+ double a1 = -2 * k;
+ double a2 = 1 - alpha;
+
+ setNormalizedCoefficients(b0, b1, b2, a0, a1, a2);
+ } else {
+ // When Q = 0, the above formulas have problems. If we look at
+ // the z-transform, we can see that the limit as Q->0 is -1, so
+ // set the filter that way.
+ setNormalizedCoefficients(-1, 0, 0, 1, 0, 0);
+ }
+ } else {
+ // When frequency is 0 or 1, the z-transform is 1.
+ setNormalizedCoefficients(1, 0, 0, 1, 0, 0);
+ }
+}
+
+void Biquad::setNotchParams(double frequency, double Q) {
+ // Clip frequencies to between 0 and 1, inclusive.
+ frequency = std::max(0.0, std::min(frequency, 1.0));
+
+ // Don't let Q go negative, which causes an unstable filter.
+ Q = std::max(0.0, Q);
+
+ if (frequency > 0 && frequency < 1) {
+ if (Q > 0) {
+ double w0 = M_PI * frequency;
+ double alpha = fdlibm_sin(w0) / (2 * Q);
+ double k = fdlibm_cos(w0);
+
+ double b0 = 1;
+ double b1 = -2 * k;
+ double b2 = 1;
+ double a0 = 1 + alpha;
+ double a1 = -2 * k;
+ double a2 = 1 - alpha;
+
+ setNormalizedCoefficients(b0, b1, b2, a0, a1, a2);
+ } else {
+ // When Q = 0, the above formulas have problems. If we look at
+ // the z-transform, we can see that the limit as Q->0 is 0, so
+ // set the filter that way.
+ setNormalizedCoefficients(0, 0, 0, 1, 0, 0);
+ }
+ } else {
+ // When frequency is 0 or 1, the z-transform is 1.
+ setNormalizedCoefficients(1, 0, 0, 1, 0, 0);
+ }
+}
+
+void Biquad::setBandpassParams(double frequency, double Q) {
+ // No negative frequencies allowed.
+ frequency = std::max(0.0, frequency);
+
+ // Don't let Q go negative, which causes an unstable filter.
+ Q = std::max(0.0, Q);
+
+ if (frequency > 0 && frequency < 1) {
+ double w0 = M_PI * frequency;
+ if (Q > 0) {
+ double alpha = fdlibm_sin(w0) / (2 * Q);
+ double k = fdlibm_cos(w0);
+
+ double b0 = alpha;
+ double b1 = 0;
+ double b2 = -alpha;
+ double a0 = 1 + alpha;
+ double a1 = -2 * k;
+ double a2 = 1 - alpha;
+
+ setNormalizedCoefficients(b0, b1, b2, a0, a1, a2);
+ } else {
+ // When Q = 0, the above formulas have problems. If we look at
+ // the z-transform, we can see that the limit as Q->0 is 1, so
+ // set the filter that way.
+ setNormalizedCoefficients(1, 0, 0, 1, 0, 0);
+ }
+ } else {
+ // When the cutoff is zero, the z-transform approaches 0, if Q
+ // > 0. When both Q and cutoff are zero, the z-transform is
+ // pretty much undefined. What should we do in this case?
+ // For now, just make the filter 0. When the cutoff is 1, the
+ // z-transform also approaches 0.
+ setNormalizedCoefficients(0, 0, 0, 1, 0, 0);
+ }
+}
+
+void Biquad::setZeroPolePairs(const Complex& zero, const Complex& pole) {
+ double b0 = 1;
+ double b1 = -2 * zero.real();
+
+ double zeroMag = abs(zero);
+ double b2 = zeroMag * zeroMag;
+
+ double a1 = -2 * pole.real();
+
+ double poleMag = abs(pole);
+ double a2 = poleMag * poleMag;
+ setNormalizedCoefficients(b0, b1, b2, 1, a1, a2);
+}
+
+void Biquad::setAllpassPole(const Complex& pole) {
+ Complex zero = Complex(1, 0) / pole;
+ setZeroPolePairs(zero, pole);
+}
+
+void Biquad::getFrequencyResponse(int nFrequencies, const float* frequency,
+ float* magResponse, float* phaseResponse) {
+ // Evaluate the Z-transform of the filter at given normalized
+ // frequency from 0 to 1. (1 corresponds to the Nyquist
+ // frequency.)
+ //
+ // The z-transform of the filter is
+ //
+ // H(z) = (b0 + b1*z^(-1) + b2*z^(-2))/(1 + a1*z^(-1) + a2*z^(-2))
+ //
+ // Evaluate as
+ //
+ // b0 + (b1 + b2*z1)*z1
+ // --------------------
+ // 1 + (a1 + a2*z1)*z1
+ //
+ // with z1 = 1/z and z = exp(j*pi*frequency). Hence z1 = exp(-j*pi*frequency)
+
+ // Make local copies of the coefficients as a micro-optimization.
+ double b0 = m_b0;
+ double b1 = m_b1;
+ double b2 = m_b2;
+ double a1 = m_a1;
+ double a2 = m_a2;
+
+ for (int k = 0; k < nFrequencies; ++k) {
+ double omega = -M_PI * frequency[k];
+ Complex z = Complex(fdlibm_cos(omega), fdlibm_sin(omega));
+ Complex numerator = b0 + (b1 + b2 * z) * z;
+ Complex denominator = Complex(1, 0) + (a1 + a2 * z) * z;
+ // Strangely enough, using complex division:
+ // e.g. Complex response = numerator / denominator;
+ // fails on our test machines, yielding infinities and NaNs, so we do
+ // things the long way here.
+ double n = norm(denominator);
+ double r = (real(numerator) * real(denominator) +
+ imag(numerator) * imag(denominator)) /
+ n;
+ double i = (imag(numerator) * real(denominator) -
+ real(numerator) * imag(denominator)) /
+ n;
+ std::complex<double> response = std::complex<double>(r, i);
+
+ magResponse[k] =
+ static_cast<float>(fdlibm_hypot(real(response), imag(response)));
+ phaseResponse[k] =
+ static_cast<float>(fdlibm_atan2(imag(response), real(response)));
+ }
+}
+
+} // namespace WebCore
diff --git a/dom/media/webaudio/blink/Biquad.h b/dom/media/webaudio/blink/Biquad.h
new file mode 100644
index 0000000000..d64ed72b50
--- /dev/null
+++ b/dom/media/webaudio/blink/Biquad.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef Biquad_h
+#define Biquad_h
+
+#include <complex>
+
+namespace WebCore {
+
+typedef std::complex<double> Complex;
+
+// A basic biquad (two-zero / two-pole digital filter)
+//
+// It can be configured to a number of common and very useful filters:
+// lowpass, highpass, shelving, parameteric, notch, allpass, ...
+
+class Biquad {
+ public:
+ Biquad();
+ ~Biquad();
+
+ void process(const float* sourceP, float* destP, size_t framesToProcess);
+
+ // frequency is 0 - 1 normalized, resonance and dbGain are in decibels.
+ // Q is a unitless quality factor.
+ void setLowpassParams(double frequency, double resonance);
+ void setHighpassParams(double frequency, double resonance);
+ void setBandpassParams(double frequency, double Q);
+ void setLowShelfParams(double frequency, double dbGain);
+ void setHighShelfParams(double frequency, double dbGain);
+ void setPeakingParams(double frequency, double Q, double dbGain);
+ void setAllpassParams(double frequency, double Q);
+ void setNotchParams(double frequency, double Q);
+
+ // Set the biquad coefficients given a single zero (other zero will be
+ // conjugate) and a single pole (other pole will be conjugate)
+ void setZeroPolePairs(const Complex& zero, const Complex& pole);
+
+ // Set the biquad coefficients given a single pole (other pole will be
+ // conjugate) (The zeroes will be the inverse of the poles)
+ void setAllpassPole(const Complex& pole);
+
+ // Return true iff the next output block will contain sound even with
+ // silent input.
+ bool hasTail() const { return m_y1 || m_y2 || m_x1 || m_x2; }
+
+ // Resets filter state
+ void reset();
+
+ // Filter response at a set of n frequencies. The magnitude and
+ // phase response are returned in magResponse and phaseResponse.
+ // The phase response is in radians.
+ void getFrequencyResponse(int nFrequencies, const float* frequency,
+ float* magResponse, float* phaseResponse);
+
+ private:
+ void setNormalizedCoefficients(double b0, double b1, double b2, double a0,
+ double a1, double a2);
+
+ // Filter coefficients. The filter is defined as
+ //
+ // y[n] + m_a1*y[n-1] + m_a2*y[n-2] = m_b0*x[n] + m_b1*x[n-1] + m_b2*x[n-2].
+ double m_b0;
+ double m_b1;
+ double m_b2;
+ double m_a1;
+ double m_a2;
+
+ // Filter memory
+ //
+ // Double precision for the output values is valuable because errors can
+ // accumulate. Input values are also stored as double so they need not be
+ // converted again for computation.
+ double m_x1; // input delayed by 1 sample
+ double m_x2; // input delayed by 2 samples
+ double m_y1; // output delayed by 1 sample
+ double m_y2; // output delayed by 2 samples
+};
+
+} // namespace WebCore
+
+#endif // Biquad_h
diff --git a/dom/media/webaudio/blink/DenormalDisabler.h b/dom/media/webaudio/blink/DenormalDisabler.h
new file mode 100644
index 0000000000..646482b74f
--- /dev/null
+++ b/dom/media/webaudio/blink/DenormalDisabler.h
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2011, Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DenormalDisabler_h
+#define DenormalDisabler_h
+
+#include <cmath>
+#include <cinttypes>
+#include <cstring>
+#include <float.h>
+
+namespace WebCore {
+
+// Deal with denormals. They can very seriously impact performance on x86.
+
+// Define HAVE_DENORMAL if we support flushing denormals to zero.
+
+#if defined(XP_WIN) && defined(_MSC_VER)
+// Windows compiled using MSVC with SSE2
+# define HAVE_DENORMAL 1
+#endif
+
+#if defined(__GNUC__) && defined(__SSE__)
+# define HAVE_DENORMAL 1
+#endif
+
+#if defined(__arm__) || defined(__aarch64__)
+# define HAVE_DENORMAL 1
+#endif
+
+#ifdef HAVE_DENORMAL
+class DenormalDisabler {
+ public:
+ DenormalDisabler() : m_savedCSR(0) { disableDenormals(); }
+
+ ~DenormalDisabler() { restoreState(); }
+
+ // This is a nop if we can flush denormals to zero in hardware.
+ static inline float flushDenormalFloatToZero(float f) { return f; }
+
+ private:
+ unsigned m_savedCSR;
+
+# if defined(__GNUC__) && defined(__SSE__)
+ static inline bool isDAZSupported() {
+# if defined(__x86_64__)
+ return true;
+# else
+ static bool s_isInited = false;
+ static bool s_isSupported = false;
+ if (s_isInited) {
+ return s_isSupported;
+ }
+
+ struct fxsaveResult {
+ uint8_t before[28];
+ uint32_t CSRMask;
+ uint8_t after[480];
+ } __attribute__((aligned(16)));
+
+ fxsaveResult registerData;
+ memset(&registerData, 0, sizeof(fxsaveResult));
+ asm volatile("fxsave %0" : "=m"(registerData));
+ s_isSupported = registerData.CSRMask & 0x0040;
+ s_isInited = true;
+ return s_isSupported;
+# endif
+ }
+
+ inline void disableDenormals() {
+ m_savedCSR = getCSR();
+ setCSR(m_savedCSR | (isDAZSupported() ? 0x8040 : 0x8000));
+ }
+
+ inline void restoreState() { setCSR(m_savedCSR); }
+
+ inline int getCSR() {
+ int result;
+ asm volatile("stmxcsr %0" : "=m"(result));
+ return result;
+ }
+
+ inline void setCSR(int a) {
+ int temp = a;
+ asm volatile("ldmxcsr %0" : : "m"(temp));
+ }
+
+# elif defined(XP_WIN) && defined(_MSC_VER)
+ inline void disableDenormals() {
+ // Save the current state, and set mode to flush denormals.
+ //
+ // http://stackoverflow.com/questions/637175/possible-bug-in-controlfp-s-may-not-restore-control-word-correctly
+ _controlfp_s(&m_savedCSR, 0, 0);
+ unsigned unused;
+ _controlfp_s(&unused, _DN_FLUSH, _MCW_DN);
+ }
+
+ inline void restoreState() {
+ unsigned unused;
+ _controlfp_s(&unused, m_savedCSR, _MCW_DN);
+ }
+# elif defined(__arm__) || defined(__aarch64__)
+ inline void disableDenormals() {
+ m_savedCSR = getStatusWord();
+ // Bit 24 is the flush-to-zero mode control bit. Setting it to 1 flushes
+ // denormals to 0.
+ setStatusWord(m_savedCSR | (1 << 24));
+ }
+
+ inline void restoreState() { setStatusWord(m_savedCSR); }
+
+ inline int getStatusWord() {
+ int result;
+# if defined(__aarch64__)
+ asm volatile("mrs %x[result], FPCR" : [result] "=r"(result));
+# else
+ asm volatile("vmrs %[result], FPSCR" : [result] "=r"(result));
+# endif
+ return result;
+ }
+
+ inline void setStatusWord(int a) {
+# if defined(__aarch64__)
+ asm volatile("msr FPCR, %x[src]" : : [src] "r"(a));
+# else
+ asm volatile("vmsr FPSCR, %[src]" : : [src] "r"(a));
+# endif
+ }
+
+# endif
+};
+
+#else
+// FIXME: add implementations for other architectures and compilers
+class DenormalDisabler {
+ public:
+ DenormalDisabler() {}
+
+ // Assume the worst case that other architectures and compilers
+ // need to flush denormals to zero manually.
+ static inline float flushDenormalFloatToZero(float f) {
+ return (fabs(f) < FLT_MIN) ? 0.0f : f;
+ }
+};
+
+#endif
+
+} // namespace WebCore
+#endif // DenormalDisabler_h
diff --git a/dom/media/webaudio/blink/DynamicsCompressor.cpp b/dom/media/webaudio/blink/DynamicsCompressor.cpp
new file mode 100644
index 0000000000..0ee9426692
--- /dev/null
+++ b/dom/media/webaudio/blink/DynamicsCompressor.cpp
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2011 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "DynamicsCompressor.h"
+#include "AlignmentUtils.h"
+#include "AudioBlock.h"
+
+#include <cmath>
+#include "AudioNodeEngine.h"
+#include "nsDebug.h"
+
+using mozilla::AudioBlockCopyChannelWithScale;
+using mozilla::WEBAUDIO_BLOCK_SIZE;
+
+namespace WebCore {
+
+DynamicsCompressor::DynamicsCompressor(float sampleRate,
+ unsigned numberOfChannels)
+ : m_numberOfChannels(numberOfChannels),
+ m_sampleRate(sampleRate),
+ m_compressor(sampleRate, numberOfChannels) {
+ // Uninitialized state - for parameter recalculation.
+ m_lastFilterStageRatio = -1;
+ m_lastAnchor = -1;
+ m_lastFilterStageGain = -1;
+
+ setNumberOfChannels(numberOfChannels);
+ initializeParameters();
+}
+
+size_t DynamicsCompressor::sizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t amount = aMallocSizeOf(this);
+ amount += m_preFilterPacks.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (size_t i = 0; i < m_preFilterPacks.Length(); i++) {
+ if (m_preFilterPacks[i]) {
+ amount += m_preFilterPacks[i]->sizeOfIncludingThis(aMallocSizeOf);
+ }
+ }
+
+ amount += m_postFilterPacks.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (size_t i = 0; i < m_postFilterPacks.Length(); i++) {
+ if (m_postFilterPacks[i]) {
+ amount += m_postFilterPacks[i]->sizeOfIncludingThis(aMallocSizeOf);
+ }
+ }
+
+ amount += aMallocSizeOf(m_sourceChannels.get());
+ amount += aMallocSizeOf(m_destinationChannels.get());
+ amount += m_compressor.sizeOfExcludingThis(aMallocSizeOf);
+ return amount;
+}
+
+void DynamicsCompressor::setParameterValue(unsigned parameterID, float value) {
+ MOZ_ASSERT(parameterID < ParamLast);
+ if (parameterID < ParamLast) m_parameters[parameterID] = value;
+}
+
+void DynamicsCompressor::initializeParameters() {
+ // Initializes compressor to default values.
+
+ m_parameters[ParamThreshold] = -24; // dB
+ m_parameters[ParamKnee] = 30; // dB
+ m_parameters[ParamRatio] = 12; // unit-less
+ m_parameters[ParamAttack] = 0.003f; // seconds
+ m_parameters[ParamRelease] = 0.250f; // seconds
+ m_parameters[ParamPreDelay] = 0.006f; // seconds
+
+ // Release zone values 0 -> 1.
+ m_parameters[ParamReleaseZone1] = 0.09f;
+ m_parameters[ParamReleaseZone2] = 0.16f;
+ m_parameters[ParamReleaseZone3] = 0.42f;
+ m_parameters[ParamReleaseZone4] = 0.98f;
+
+ m_parameters[ParamFilterStageGain] = 4.4f; // dB
+ m_parameters[ParamFilterStageRatio] = 2;
+ m_parameters[ParamFilterAnchor] = 15000 / nyquist();
+
+ m_parameters[ParamPostGain] = 0; // dB
+ m_parameters[ParamReduction] = 0; // dB
+
+ // Linear crossfade (0 -> 1).
+ m_parameters[ParamEffectBlend] = 1;
+}
+
+float DynamicsCompressor::parameterValue(unsigned parameterID) {
+ MOZ_ASSERT(parameterID < ParamLast);
+ return m_parameters[parameterID];
+}
+
+void DynamicsCompressor::setEmphasisStageParameters(
+ unsigned stageIndex, float gain, float normalizedFrequency /* 0 -> 1 */) {
+ float gk = 1 - gain / 20;
+ float f1 = normalizedFrequency * gk;
+ float f2 = normalizedFrequency / gk;
+ float r1 = fdlibm_expf(-f1 * M_PI);
+ float r2 = fdlibm_expf(-f2 * M_PI);
+
+ MOZ_ASSERT(m_numberOfChannels == m_preFilterPacks.Length());
+
+ for (unsigned i = 0; i < m_numberOfChannels; ++i) {
+ // Set pre-filter zero and pole to create an emphasis filter.
+ ZeroPole& preFilter = m_preFilterPacks[i]->filters[stageIndex];
+ preFilter.setZero(r1);
+ preFilter.setPole(r2);
+
+ // Set post-filter with zero and pole reversed to create the de-emphasis
+ // filter. If there were no compressor kernel in between, they would cancel
+ // each other out (allpass filter).
+ ZeroPole& postFilter = m_postFilterPacks[i]->filters[stageIndex];
+ postFilter.setZero(r2);
+ postFilter.setPole(r1);
+ }
+}
+
+void DynamicsCompressor::setEmphasisParameters(float gain, float anchorFreq,
+ float filterStageRatio) {
+ setEmphasisStageParameters(0, gain, anchorFreq);
+ setEmphasisStageParameters(1, gain, anchorFreq / filterStageRatio);
+ setEmphasisStageParameters(
+ 2, gain, anchorFreq / (filterStageRatio * filterStageRatio));
+ setEmphasisStageParameters(
+ 3, gain,
+ anchorFreq / (filterStageRatio * filterStageRatio * filterStageRatio));
+}
+
+void DynamicsCompressor::process(const AudioBlock* sourceChunk,
+ AudioBlock* destinationChunk,
+ unsigned framesToProcess) {
+ // Though numberOfChannels is retrived from destinationBus, we still name it
+ // numberOfChannels instead of numberOfDestinationChannels. It's because we
+ // internally match sourceChannels's size to destinationBus by channel up/down
+ // mix. Thus we need numberOfChannels to do the loop work for both
+ // m_sourceChannels and m_destinationChannels.
+
+ unsigned numberOfChannels = destinationChunk->ChannelCount();
+ unsigned numberOfSourceChannels = sourceChunk->ChannelCount();
+
+ MOZ_ASSERT(numberOfChannels == m_numberOfChannels && numberOfSourceChannels);
+
+ if (numberOfChannels != m_numberOfChannels || !numberOfSourceChannels) {
+ destinationChunk->SetNull(WEBAUDIO_BLOCK_SIZE);
+ return;
+ }
+
+ switch (numberOfChannels) {
+ case 2: // stereo
+ m_sourceChannels[0] =
+ static_cast<const float*>(sourceChunk->mChannelData[0]);
+
+ if (numberOfSourceChannels > 1)
+ m_sourceChannels[1] =
+ static_cast<const float*>(sourceChunk->mChannelData[1]);
+ else
+ // Simply duplicate mono channel input data to right channel for stereo
+ // processing.
+ m_sourceChannels[1] = m_sourceChannels[0];
+
+ break;
+ case 1:
+ m_sourceChannels[0] =
+ static_cast<const float*>(sourceChunk->mChannelData[0]);
+ break;
+ default:
+ MOZ_CRASH("not supported.");
+ }
+
+ for (unsigned i = 0; i < numberOfChannels; ++i)
+ m_destinationChannels[i] = const_cast<float*>(
+ static_cast<const float*>(destinationChunk->mChannelData[i]));
+
+ float filterStageGain = parameterValue(ParamFilterStageGain);
+ float filterStageRatio = parameterValue(ParamFilterStageRatio);
+ float anchor = parameterValue(ParamFilterAnchor);
+
+ if (filterStageGain != m_lastFilterStageGain ||
+ filterStageRatio != m_lastFilterStageRatio || anchor != m_lastAnchor) {
+ m_lastFilterStageGain = filterStageGain;
+ m_lastFilterStageRatio = filterStageRatio;
+ m_lastAnchor = anchor;
+
+ setEmphasisParameters(filterStageGain, anchor, filterStageRatio);
+ }
+
+ float sourceWithVolume[WEBAUDIO_BLOCK_SIZE + 4];
+ float* alignedSourceWithVolume = ALIGNED16(sourceWithVolume);
+ ASSERT_ALIGNED16(alignedSourceWithVolume);
+
+ // Apply pre-emphasis filter.
+ // Note that the final three stages are computed in-place in the destination
+ // buffer.
+ for (unsigned i = 0; i < numberOfChannels; ++i) {
+ const float* sourceData;
+ if (sourceChunk->mVolume == 1.0f) {
+ // Fast path, the volume scale doesn't need to get taken into account
+ sourceData = m_sourceChannels[i];
+ } else {
+ AudioBlockCopyChannelWithScale(m_sourceChannels[i], sourceChunk->mVolume,
+ alignedSourceWithVolume);
+ sourceData = alignedSourceWithVolume;
+ }
+
+ float* destinationData = m_destinationChannels[i];
+ ZeroPole* preFilters = m_preFilterPacks[i]->filters;
+
+ preFilters[0].process(sourceData, destinationData, framesToProcess);
+ preFilters[1].process(destinationData, destinationData, framesToProcess);
+ preFilters[2].process(destinationData, destinationData, framesToProcess);
+ preFilters[3].process(destinationData, destinationData, framesToProcess);
+ }
+
+ float dbThreshold = parameterValue(ParamThreshold);
+ float dbKnee = parameterValue(ParamKnee);
+ float ratio = parameterValue(ParamRatio);
+ float attackTime = parameterValue(ParamAttack);
+ float releaseTime = parameterValue(ParamRelease);
+ float preDelayTime = parameterValue(ParamPreDelay);
+
+ // This is effectively a master volume on the compressed signal
+ // (pre-blending).
+ float dbPostGain = parameterValue(ParamPostGain);
+
+ // Linear blending value from dry to completely processed (0 -> 1)
+ // 0 means the signal is completely unprocessed.
+ // 1 mixes in only the compressed signal.
+ float effectBlend = parameterValue(ParamEffectBlend);
+
+ float releaseZone1 = parameterValue(ParamReleaseZone1);
+ float releaseZone2 = parameterValue(ParamReleaseZone2);
+ float releaseZone3 = parameterValue(ParamReleaseZone3);
+ float releaseZone4 = parameterValue(ParamReleaseZone4);
+
+ // Apply compression to the pre-filtered signal.
+ // The processing is performed in place.
+ m_compressor.process(m_destinationChannels.get(), m_destinationChannels.get(),
+ numberOfChannels, framesToProcess,
+
+ dbThreshold, dbKnee, ratio, attackTime, releaseTime,
+ preDelayTime, dbPostGain, effectBlend,
+
+ releaseZone1, releaseZone2, releaseZone3, releaseZone4);
+
+ // Update the compression amount.
+ setParameterValue(ParamReduction, m_compressor.meteringGain());
+
+ // Apply de-emphasis filter.
+ for (unsigned i = 0; i < numberOfChannels; ++i) {
+ float* destinationData = m_destinationChannels[i];
+ ZeroPole* postFilters = m_postFilterPacks[i]->filters;
+
+ postFilters[0].process(destinationData, destinationData, framesToProcess);
+ postFilters[1].process(destinationData, destinationData, framesToProcess);
+ postFilters[2].process(destinationData, destinationData, framesToProcess);
+ postFilters[3].process(destinationData, destinationData, framesToProcess);
+ }
+}
+
+void DynamicsCompressor::reset() {
+ m_lastFilterStageRatio = -1; // for recalc
+ m_lastAnchor = -1;
+ m_lastFilterStageGain = -1;
+
+ for (unsigned channel = 0; channel < m_numberOfChannels; ++channel) {
+ for (unsigned stageIndex = 0; stageIndex < 4; ++stageIndex) {
+ m_preFilterPacks[channel]->filters[stageIndex].reset();
+ m_postFilterPacks[channel]->filters[stageIndex].reset();
+ }
+ }
+
+ m_compressor.reset();
+}
+
+void DynamicsCompressor::setNumberOfChannels(unsigned numberOfChannels) {
+ if (m_preFilterPacks.Length() == numberOfChannels) return;
+
+ m_preFilterPacks.Clear();
+ m_postFilterPacks.Clear();
+ for (unsigned i = 0; i < numberOfChannels; ++i) {
+ m_preFilterPacks.AppendElement(new ZeroPoleFilterPack4());
+ m_postFilterPacks.AppendElement(new ZeroPoleFilterPack4());
+ }
+
+ m_sourceChannels = mozilla::MakeUnique<const float*[]>(numberOfChannels);
+ m_destinationChannels = mozilla::MakeUnique<float*[]>(numberOfChannels);
+
+ m_compressor.setNumberOfChannels(numberOfChannels);
+ m_numberOfChannels = numberOfChannels;
+}
+
+} // namespace WebCore
diff --git a/dom/media/webaudio/blink/DynamicsCompressor.h b/dom/media/webaudio/blink/DynamicsCompressor.h
new file mode 100644
index 0000000000..1838e268fe
--- /dev/null
+++ b/dom/media/webaudio/blink/DynamicsCompressor.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2011 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DynamicsCompressor_h
+#define DynamicsCompressor_h
+
+#include "DynamicsCompressorKernel.h"
+#include "ZeroPole.h"
+
+#include "nsTArray.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+class AudioBlock;
+} // namespace mozilla
+
+namespace WebCore {
+
+using mozilla::AudioBlock;
+using mozilla::UniquePtr;
+
+// DynamicsCompressor implements a flexible audio dynamics compression effect
+// such as is commonly used in musical production and game audio. It lowers the
+// volume of the loudest parts of the signal and raises the volume of the
+// softest parts, making the sound richer, fuller, and more controlled.
+
+class DynamicsCompressor {
+ public:
+ enum {
+ ParamThreshold,
+ ParamKnee,
+ ParamRatio,
+ ParamAttack,
+ ParamRelease,
+ ParamPreDelay,
+ ParamReleaseZone1,
+ ParamReleaseZone2,
+ ParamReleaseZone3,
+ ParamReleaseZone4,
+ ParamPostGain,
+ ParamFilterStageGain,
+ ParamFilterStageRatio,
+ ParamFilterAnchor,
+ ParamEffectBlend,
+ ParamReduction,
+ ParamLast
+ };
+
+ DynamicsCompressor(float sampleRate, unsigned numberOfChannels);
+
+ void process(const AudioBlock* sourceChunk, AudioBlock* destinationChunk,
+ unsigned framesToProcess);
+ void reset();
+ void setNumberOfChannels(unsigned);
+ unsigned numberOfChannels() const { return m_numberOfChannels; }
+
+ void setParameterValue(unsigned parameterID, float value);
+ float parameterValue(unsigned parameterID);
+
+ float sampleRate() const { return m_sampleRate; }
+ float nyquist() const { return m_sampleRate / 2; }
+
+ double tailTime() const { return 0; }
+ double latencyTime() const {
+ return m_compressor.latencyFrames() / static_cast<double>(sampleRate());
+ }
+
+ size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ protected:
+ unsigned m_numberOfChannels;
+
+ // m_parameters holds the tweakable compressor parameters.
+ float m_parameters[ParamLast];
+ void initializeParameters();
+
+ float m_sampleRate;
+
+ // Emphasis filter controls.
+ float m_lastFilterStageRatio;
+ float m_lastAnchor;
+ float m_lastFilterStageGain;
+
+ struct ZeroPoleFilterPack4 {
+ ZeroPole filters[4];
+ size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this);
+ }
+ };
+
+ // Per-channel emphasis filters.
+ nsTArray<UniquePtr<ZeroPoleFilterPack4> > m_preFilterPacks;
+ nsTArray<UniquePtr<ZeroPoleFilterPack4> > m_postFilterPacks;
+
+ mozilla::UniquePtr<const float*[]> m_sourceChannels;
+ mozilla::UniquePtr<float*[]> m_destinationChannels;
+
+ void setEmphasisStageParameters(unsigned stageIndex, float gain,
+ float normalizedFrequency /* 0 -> 1 */);
+ void setEmphasisParameters(float gain, float anchorFreq,
+ float filterStageRatio);
+
+ // The core compressor.
+ DynamicsCompressorKernel m_compressor;
+};
+
+} // namespace WebCore
+
+#endif // DynamicsCompressor_h
diff --git a/dom/media/webaudio/blink/DynamicsCompressorKernel.cpp b/dom/media/webaudio/blink/DynamicsCompressorKernel.cpp
new file mode 100644
index 0000000000..7fac8b78a3
--- /dev/null
+++ b/dom/media/webaudio/blink/DynamicsCompressorKernel.cpp
@@ -0,0 +1,496 @@
+/*
+ * Copyright (C) 2011 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "DynamicsCompressorKernel.h"
+
+#include "DenormalDisabler.h"
+#include <algorithm>
+#include <cmath>
+
+#include "mozilla/FloatingPoint.h"
+#include "WebAudioUtils.h"
+
+using namespace mozilla::dom; // for WebAudioUtils
+using mozilla::MakeUnique;
+using mozilla::PositiveInfinity;
+
+namespace WebCore {
+
+// Metering hits peaks instantly, but releases this fast (in seconds).
+const float meteringReleaseTimeConstant = 0.325f;
+
+const float uninitializedValue = -1;
+
+DynamicsCompressorKernel::DynamicsCompressorKernel(float sampleRate,
+ unsigned numberOfChannels)
+ : m_sampleRate(sampleRate),
+ m_lastPreDelayFrames(DefaultPreDelayFrames),
+ m_preDelayReadIndex(0),
+ m_preDelayWriteIndex(DefaultPreDelayFrames),
+ m_ratio(uninitializedValue),
+ m_slope(uninitializedValue),
+ m_linearThreshold(uninitializedValue),
+ m_dbThreshold(uninitializedValue),
+ m_dbKnee(uninitializedValue),
+ m_kneeThreshold(uninitializedValue),
+ m_kneeThresholdDb(uninitializedValue),
+ m_ykneeThresholdDb(uninitializedValue),
+ m_K(uninitializedValue) {
+ setNumberOfChannels(numberOfChannels);
+
+ // Initializes most member variables
+ reset();
+
+ m_meteringReleaseK =
+ static_cast<float>(WebAudioUtils::DiscreteTimeConstantForSampleRate(
+ meteringReleaseTimeConstant, sampleRate));
+}
+
+size_t DynamicsCompressorKernel::sizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t amount = 0;
+ amount += m_preDelayBuffers.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (size_t i = 0; i < m_preDelayBuffers.Length(); i++) {
+ amount += aMallocSizeOf(m_preDelayBuffers[i].get());
+ }
+
+ return amount;
+}
+
+void DynamicsCompressorKernel::setNumberOfChannels(unsigned numberOfChannels) {
+ if (m_preDelayBuffers.Length() == numberOfChannels) return;
+
+ m_preDelayBuffers.Clear();
+ for (unsigned i = 0; i < numberOfChannels; ++i)
+ m_preDelayBuffers.AppendElement(MakeUnique<float[]>(MaxPreDelayFrames));
+}
+
+void DynamicsCompressorKernel::setPreDelayTime(float preDelayTime) {
+ // Re-configure look-ahead section pre-delay if delay time has changed.
+ unsigned preDelayFrames = preDelayTime * sampleRate();
+ if (preDelayFrames > MaxPreDelayFrames - 1)
+ preDelayFrames = MaxPreDelayFrames - 1;
+
+ if (m_lastPreDelayFrames != preDelayFrames) {
+ m_lastPreDelayFrames = preDelayFrames;
+ for (unsigned i = 0; i < m_preDelayBuffers.Length(); ++i)
+ memset(m_preDelayBuffers[i].get(), 0, sizeof(float) * MaxPreDelayFrames);
+
+ m_preDelayReadIndex = 0;
+ m_preDelayWriteIndex = preDelayFrames;
+ }
+}
+
+// Exponential curve for the knee.
+// It is 1st derivative matched at m_linearThreshold and asymptotically
+// approaches the value m_linearThreshold + 1 / k.
+float DynamicsCompressorKernel::kneeCurve(float x, float k) {
+ // Linear up to threshold.
+ if (x < m_linearThreshold) return x;
+
+ return m_linearThreshold +
+ (1 - fdlibm_expf(-k * (x - m_linearThreshold))) / k;
+}
+
+// Full compression curve with constant ratio after knee.
+float DynamicsCompressorKernel::saturate(float x, float k) {
+ float y;
+
+ if (x < m_kneeThreshold)
+ y = kneeCurve(x, k);
+ else {
+ // Constant ratio after knee.
+ float xDb = WebAudioUtils::ConvertLinearToDecibels(x, -1000.0f);
+ float yDb = m_ykneeThresholdDb + m_slope * (xDb - m_kneeThresholdDb);
+
+ y = WebAudioUtils::ConvertDecibelsToLinear(yDb);
+ }
+
+ return y;
+}
+
+// Approximate 1st derivative with input and output expressed in dB.
+// This slope is equal to the inverse of the compression "ratio".
+// In other words, a compression ratio of 20 would be a slope of 1/20.
+float DynamicsCompressorKernel::slopeAt(float x, float k) {
+ if (x < m_linearThreshold) return 1;
+
+ float x2 = x * 1.001;
+
+ float xDb = WebAudioUtils::ConvertLinearToDecibels(x, -1000.0f);
+ float x2Db = WebAudioUtils::ConvertLinearToDecibels(x2, -1000.0f);
+
+ float yDb = WebAudioUtils::ConvertLinearToDecibels(kneeCurve(x, k), -1000.0f);
+ float y2Db =
+ WebAudioUtils::ConvertLinearToDecibels(kneeCurve(x2, k), -1000.0f);
+
+ float m = (y2Db - yDb) / (x2Db - xDb);
+
+ return m;
+}
+
+float DynamicsCompressorKernel::kAtSlope(float desiredSlope) {
+ float xDb = m_dbThreshold + m_dbKnee;
+ float x = WebAudioUtils::ConvertDecibelsToLinear(xDb);
+
+ // Approximate k given initial values.
+ float minK = 0.1f;
+ float maxK = 10000;
+ float k = 5;
+
+ for (int i = 0; i < 15; ++i) {
+ // A high value for k will more quickly asymptotically approach a slope of
+ // 0.
+ float slope = slopeAt(x, k);
+
+ if (slope < desiredSlope) {
+ // k is too high.
+ maxK = k;
+ } else {
+ // k is too low.
+ minK = k;
+ }
+
+ // Re-calculate based on geometric mean.
+ k = sqrtf(minK * maxK);
+ }
+
+ return k;
+}
+
+float DynamicsCompressorKernel::updateStaticCurveParameters(float dbThreshold,
+ float dbKnee,
+ float ratio) {
+ if (dbThreshold != m_dbThreshold || dbKnee != m_dbKnee || ratio != m_ratio) {
+ // Threshold and knee.
+ m_dbThreshold = dbThreshold;
+ m_linearThreshold = WebAudioUtils::ConvertDecibelsToLinear(dbThreshold);
+ m_dbKnee = dbKnee;
+
+ // Compute knee parameters.
+ m_ratio = ratio;
+ m_slope = 1 / m_ratio;
+
+ float k = kAtSlope(1 / m_ratio);
+
+ m_kneeThresholdDb = dbThreshold + dbKnee;
+ m_kneeThreshold = WebAudioUtils::ConvertDecibelsToLinear(m_kneeThresholdDb);
+
+ m_ykneeThresholdDb = WebAudioUtils::ConvertLinearToDecibels(
+ kneeCurve(m_kneeThreshold, k), -1000.0f);
+
+ m_K = k;
+ }
+ return m_K;
+}
+
+void DynamicsCompressorKernel::process(
+ float* sourceChannels[], float* destinationChannels[],
+ unsigned numberOfChannels, unsigned framesToProcess,
+
+ float dbThreshold, float dbKnee, float ratio, float attackTime,
+ float releaseTime, float preDelayTime, float dbPostGain,
+ float effectBlend, /* equal power crossfade */
+
+ float releaseZone1, float releaseZone2, float releaseZone3,
+ float releaseZone4) {
+ MOZ_ASSERT(m_preDelayBuffers.Length() == numberOfChannels);
+
+ float sampleRate = this->sampleRate();
+
+ float dryMix = 1 - effectBlend;
+ float wetMix = effectBlend;
+
+ float k = updateStaticCurveParameters(dbThreshold, dbKnee, ratio);
+
+ // Makeup gain.
+ float fullRangeGain = saturate(1, k);
+ float fullRangeMakeupGain = 1 / fullRangeGain;
+
+ // Empirical/perceptual tuning.
+ fullRangeMakeupGain = fdlibm_powf(fullRangeMakeupGain, 0.6f);
+
+ float masterLinearGain =
+ WebAudioUtils::ConvertDecibelsToLinear(dbPostGain) * fullRangeMakeupGain;
+
+ // Attack parameters.
+ attackTime = std::max(0.001f, attackTime);
+ float attackFrames = attackTime * sampleRate;
+
+ // Release parameters.
+ float releaseFrames = sampleRate * releaseTime;
+
+ // Detector release time.
+ float satReleaseTime = 0.0025f;
+ float satReleaseFrames = satReleaseTime * sampleRate;
+
+ // Create a smooth function which passes through four points.
+
+ // Polynomial of the form
+ // y = a + b*x + c*x^2 + d*x^3 + e*x^4;
+
+ float y1 = releaseFrames * releaseZone1;
+ float y2 = releaseFrames * releaseZone2;
+ float y3 = releaseFrames * releaseZone3;
+ float y4 = releaseFrames * releaseZone4;
+
+ // All of these coefficients were derived for 4th order polynomial curve
+ // fitting where the y values match the evenly spaced x values as follows:
+ // (y1 : x == 0, y2 : x == 1, y3 : x == 2, y4 : x == 3)
+ float kA = 0.9999999999999998f * y1 + 1.8432219684323923e-16f * y2 -
+ 1.9373394351676423e-16f * y3 + 8.824516011816245e-18f * y4;
+ float kB = -1.5788320352845888f * y1 + 2.3305837032074286f * y2 -
+ 0.9141194204840429f * y3 + 0.1623677525612032f * y4;
+ float kC = 0.5334142869106424f * y1 - 1.272736789213631f * y2 +
+ 0.9258856042207512f * y3 - 0.18656310191776226f * y4;
+ float kD = 0.08783463138207234f * y1 - 0.1694162967925622f * y2 +
+ 0.08588057951595272f * y3 - 0.00429891410546283f * y4;
+ float kE = -0.042416883008123074f * y1 + 0.1115693827987602f * y2 -
+ 0.09764676325265872f * y3 + 0.028494263462021576f * y4;
+
+ // x ranges from 0 -> 3 0 1 2 3
+ // -15 -10 -5 0db
+
+ // y calculates adaptive release frames depending on the amount of
+ // compression.
+
+ setPreDelayTime(preDelayTime);
+
+ const int nDivisionFrames = 32;
+
+ const int nDivisions = framesToProcess / nDivisionFrames;
+
+ unsigned frameIndex = 0;
+ for (int i = 0; i < nDivisions; ++i) {
+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ // Calculate desired gain
+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ // Fix gremlins.
+ if (std::isnan(m_detectorAverage)) m_detectorAverage = 1;
+ if (std::isinf(m_detectorAverage)) m_detectorAverage = 1;
+
+ float desiredGain = m_detectorAverage;
+
+ // Pre-warp so we get desiredGain after sin() warp below.
+ float scaledDesiredGain = fdlibm_asinf(desiredGain) / (0.5f * M_PI);
+
+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ // Deal with envelopes
+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ // envelopeRate is the rate we slew from current compressor level to the
+ // desired level. The exact rate depends on if we're attacking or releasing
+ // and by how much.
+ float envelopeRate;
+
+ bool isReleasing = scaledDesiredGain > m_compressorGain;
+
+ // compressionDiffDb is the difference between current compression level and
+ // the desired level.
+ float compressionDiffDb;
+ if (scaledDesiredGain == 0.0) {
+ compressionDiffDb = PositiveInfinity<float>();
+ } else {
+ compressionDiffDb = WebAudioUtils::ConvertLinearToDecibels(
+ m_compressorGain / scaledDesiredGain, -1000.0f);
+ }
+
+ if (isReleasing) {
+ // Release mode - compressionDiffDb should be negative dB
+ m_maxAttackCompressionDiffDb = -1;
+
+ // Fix gremlins.
+ if (std::isnan(compressionDiffDb)) compressionDiffDb = -1;
+ if (std::isinf(compressionDiffDb)) compressionDiffDb = -1;
+
+ // Adaptive release - higher compression (lower compressionDiffDb)
+ // releases faster.
+
+ // Contain within range: -12 -> 0 then scale to go from 0 -> 3
+ float x = compressionDiffDb;
+ x = std::max(-12.0f, x);
+ x = std::min(0.0f, x);
+ x = 0.25f * (x + 12);
+
+ // Compute adaptive release curve using 4th order polynomial.
+ // Normal values for the polynomial coefficients would create a
+ // monotonically increasing function.
+ float x2 = x * x;
+ float x3 = x2 * x;
+ float x4 = x2 * x2;
+ float releaseFrames = kA + kB * x + kC * x2 + kD * x3 + kE * x4;
+
+#define kSpacingDb 5
+ float dbPerFrame = kSpacingDb / releaseFrames;
+
+ envelopeRate = WebAudioUtils::ConvertDecibelsToLinear(dbPerFrame);
+ } else {
+ // Attack mode - compressionDiffDb should be positive dB
+
+ // Fix gremlins.
+ if (std::isnan(compressionDiffDb)) compressionDiffDb = 1;
+ if (std::isinf(compressionDiffDb)) compressionDiffDb = 1;
+
+ // As long as we're still in attack mode, use a rate based off
+ // the largest compressionDiffDb we've encountered so far.
+ if (m_maxAttackCompressionDiffDb == -1 ||
+ m_maxAttackCompressionDiffDb < compressionDiffDb)
+ m_maxAttackCompressionDiffDb = compressionDiffDb;
+
+ float effAttenDiffDb = std::max(0.5f, m_maxAttackCompressionDiffDb);
+
+ float x = 0.25f / effAttenDiffDb;
+ envelopeRate = 1 - fdlibm_powf(x, 1 / attackFrames);
+ }
+
+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ // Inner loop - calculate shaped power average - apply compression.
+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+ {
+ int preDelayReadIndex = m_preDelayReadIndex;
+ int preDelayWriteIndex = m_preDelayWriteIndex;
+ float detectorAverage = m_detectorAverage;
+ float compressorGain = m_compressorGain;
+
+ int loopFrames = nDivisionFrames;
+ while (loopFrames--) {
+ float compressorInput = 0;
+
+ // Predelay signal, computing compression amount from un-delayed
+ // version.
+ for (unsigned i = 0; i < numberOfChannels; ++i) {
+ float* delayBuffer = m_preDelayBuffers[i].get();
+ float undelayedSource = sourceChannels[i][frameIndex];
+ delayBuffer[preDelayWriteIndex] = undelayedSource;
+
+ float absUndelayedSource =
+ undelayedSource > 0 ? undelayedSource : -undelayedSource;
+ if (compressorInput < absUndelayedSource)
+ compressorInput = absUndelayedSource;
+ }
+
+ // Calculate shaped power on undelayed input.
+
+ float scaledInput = compressorInput;
+ float absInput = scaledInput > 0 ? scaledInput : -scaledInput;
+
+ // Put through shaping curve.
+ // This is linear up to the threshold, then enters a "knee" portion
+ // followed by the "ratio" portion. The transition from the threshold to
+ // the knee is smooth (1st derivative matched). The transition from the
+ // knee to the ratio portion is smooth (1st derivative matched).
+ float shapedInput = saturate(absInput, k);
+
+ float attenuation = absInput <= 0.0001f ? 1 : shapedInput / absInput;
+
+ float attenuationDb =
+ -WebAudioUtils::ConvertLinearToDecibels(attenuation, -1000.0f);
+ attenuationDb = std::max(2.0f, attenuationDb);
+
+ float dbPerFrame = attenuationDb / satReleaseFrames;
+
+ float satReleaseRate =
+ WebAudioUtils::ConvertDecibelsToLinear(dbPerFrame) - 1;
+
+ bool isRelease = (attenuation > detectorAverage);
+ float rate = isRelease ? satReleaseRate : 1;
+
+ detectorAverage += (attenuation - detectorAverage) * rate;
+ detectorAverage = std::min(1.0f, detectorAverage);
+
+ // Fix gremlins.
+ if (std::isnan(detectorAverage)) detectorAverage = 1;
+ if (std::isinf(detectorAverage)) detectorAverage = 1;
+
+ // Exponential approach to desired gain.
+ if (envelopeRate < 1) {
+ // Attack - reduce gain to desired.
+ compressorGain += (scaledDesiredGain - compressorGain) * envelopeRate;
+ } else {
+ // Release - exponentially increase gain to 1.0
+ compressorGain *= envelopeRate;
+ compressorGain = std::min(1.0f, compressorGain);
+ }
+
+ // Warp pre-compression gain to smooth out sharp exponential transition
+ // points.
+ float postWarpCompressorGain =
+ fdlibm_sinf(0.5f * M_PI * compressorGain);
+
+ // Calculate total gain using master gain and effect blend.
+ float totalGain =
+ dryMix + wetMix * masterLinearGain * postWarpCompressorGain;
+
+ // Calculate metering.
+ float dbRealGain = 20 * fdlibm_log10f(postWarpCompressorGain);
+ if (dbRealGain < m_meteringGain)
+ m_meteringGain = dbRealGain;
+ else
+ m_meteringGain += (dbRealGain - m_meteringGain) * m_meteringReleaseK;
+
+ // Apply final gain.
+ for (unsigned i = 0; i < numberOfChannels; ++i) {
+ float* delayBuffer = m_preDelayBuffers[i].get();
+ destinationChannels[i][frameIndex] =
+ delayBuffer[preDelayReadIndex] * totalGain;
+ }
+
+ frameIndex++;
+ preDelayReadIndex = (preDelayReadIndex + 1) & MaxPreDelayFramesMask;
+ preDelayWriteIndex = (preDelayWriteIndex + 1) & MaxPreDelayFramesMask;
+ }
+
+ // Locals back to member variables.
+ m_preDelayReadIndex = preDelayReadIndex;
+ m_preDelayWriteIndex = preDelayWriteIndex;
+ m_detectorAverage =
+ DenormalDisabler::flushDenormalFloatToZero(detectorAverage);
+ m_compressorGain =
+ DenormalDisabler::flushDenormalFloatToZero(compressorGain);
+ }
+ }
+}
+
+void DynamicsCompressorKernel::reset() {
+ m_detectorAverage = 0;
+ m_compressorGain = 1;
+ m_meteringGain = 1;
+
+ // Predelay section.
+ for (unsigned i = 0; i < m_preDelayBuffers.Length(); ++i)
+ memset(m_preDelayBuffers[i].get(), 0, sizeof(float) * MaxPreDelayFrames);
+
+ m_preDelayReadIndex = 0;
+ m_preDelayWriteIndex = DefaultPreDelayFrames;
+
+ m_maxAttackCompressionDiffDb = -1; // uninitialized state
+}
+
+} // namespace WebCore
diff --git a/dom/media/webaudio/blink/DynamicsCompressorKernel.h b/dom/media/webaudio/blink/DynamicsCompressorKernel.h
new file mode 100644
index 0000000000..bb7defbd7e
--- /dev/null
+++ b/dom/media/webaudio/blink/DynamicsCompressorKernel.h
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2011 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef DynamicsCompressorKernel_h
+#define DynamicsCompressorKernel_h
+
+#include "nsTArray.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/UniquePtr.h"
+
+namespace WebCore {
+
+class DynamicsCompressorKernel {
+ public:
+ DynamicsCompressorKernel(float sampleRate, unsigned numberOfChannels);
+
+ void setNumberOfChannels(unsigned);
+
+ // Performs stereo-linked compression.
+ void process(float* sourceChannels[], float* destinationChannels[],
+ unsigned numberOfChannels, unsigned framesToProcess,
+
+ float dbThreshold, float dbKnee, float ratio, float attackTime,
+ float releaseTime, float preDelayTime, float dbPostGain,
+ float effectBlend,
+
+ float releaseZone1, float releaseZone2, float releaseZone3,
+ float releaseZone4);
+
+ void reset();
+
+ unsigned latencyFrames() const { return m_lastPreDelayFrames; }
+
+ float sampleRate() const { return m_sampleRate; }
+
+ float meteringGain() const { return m_meteringGain; }
+
+ size_t sizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ protected:
+ float m_sampleRate;
+
+ float m_detectorAverage;
+ float m_compressorGain;
+
+ // Metering
+ float m_meteringReleaseK;
+ float m_meteringGain;
+
+ // Lookahead section.
+ enum { MaxPreDelayFrames = 1024 };
+ enum { MaxPreDelayFramesMask = MaxPreDelayFrames - 1 };
+ enum {
+ DefaultPreDelayFrames = 256
+ }; // setPreDelayTime() will override this initial value
+ unsigned m_lastPreDelayFrames;
+ void setPreDelayTime(float);
+
+ nsTArray<mozilla::UniquePtr<float[]>> m_preDelayBuffers;
+ int m_preDelayReadIndex;
+ int m_preDelayWriteIndex;
+
+ float m_maxAttackCompressionDiffDb;
+
+ // Static compression curve.
+ float kneeCurve(float x, float k);
+ float saturate(float x, float k);
+ float slopeAt(float x, float k);
+ float kAtSlope(float desiredSlope);
+
+ float updateStaticCurveParameters(float dbThreshold, float dbKnee,
+ float ratio);
+
+ // Amount of input change in dB required for 1 dB of output change.
+ // This applies to the portion of the curve above m_kneeThresholdDb (see
+ // below).
+ float m_ratio;
+ float m_slope; // Inverse ratio.
+
+ // The input to output change below the threshold is linear 1:1.
+ float m_linearThreshold;
+ float m_dbThreshold;
+
+ // m_dbKnee is the number of dB above the threshold before we enter the
+ // "ratio" portion of the curve. m_kneeThresholdDb = m_dbThreshold + m_dbKnee
+ // The portion between m_dbThreshold and m_kneeThresholdDb is the "soft knee"
+ // portion of the curve which transitions smoothly from the linear portion to
+ // the ratio portion.
+ float m_dbKnee;
+ float m_kneeThreshold;
+ float m_kneeThresholdDb;
+ float m_ykneeThresholdDb;
+
+ // Internal parameter for the knee portion of the curve.
+ float m_K;
+};
+
+} // namespace WebCore
+
+#endif // DynamicsCompressorKernel_h
diff --git a/dom/media/webaudio/blink/FFTConvolver.cpp b/dom/media/webaudio/blink/FFTConvolver.cpp
new file mode 100644
index 0000000000..2ade9031ce
--- /dev/null
+++ b/dom/media/webaudio/blink/FFTConvolver.cpp
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "FFTConvolver.h"
+#include "mozilla/PodOperations.h"
+
+using namespace mozilla;
+
+namespace WebCore {
+
+FFTConvolver::FFTConvolver(size_t fftSize, size_t renderPhase)
+ : m_frame(fftSize), m_readWriteIndex(renderPhase % (fftSize / 2)) {
+ MOZ_ASSERT(fftSize >= 2 * WEBAUDIO_BLOCK_SIZE);
+ m_inputBuffer.SetLength(fftSize);
+ PodZero(m_inputBuffer.Elements(), fftSize);
+ m_outputBuffer.SetLength(fftSize);
+ PodZero(m_outputBuffer.Elements(), fftSize);
+ m_lastOverlapBuffer.SetLength(fftSize / 2);
+ PodZero(m_lastOverlapBuffer.Elements(), fftSize / 2);
+}
+
+size_t FFTConvolver::sizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t amount = 0;
+ amount += m_frame.SizeOfExcludingThis(aMallocSizeOf);
+ amount += m_inputBuffer.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ amount += m_outputBuffer.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ amount += m_lastOverlapBuffer.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ return amount;
+}
+
+size_t FFTConvolver::sizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + sizeOfExcludingThis(aMallocSizeOf);
+}
+
+const float* FFTConvolver::process(FFTBlock* fftKernel, const float* sourceP) {
+ size_t halfSize = fftSize() / 2;
+
+ // WEBAUDIO_BLOCK_SIZE must be an exact multiple of halfSize,
+ // halfSize must be a multiple of WEBAUDIO_BLOCK_SIZE
+ // and > WEBAUDIO_BLOCK_SIZE.
+ MOZ_ASSERT(halfSize % WEBAUDIO_BLOCK_SIZE == 0 &&
+ WEBAUDIO_BLOCK_SIZE <= halfSize);
+
+ // Copy samples to input buffer (note contraint above!)
+ float* inputP = m_inputBuffer.Elements();
+
+ MOZ_ASSERT(sourceP && inputP &&
+ m_readWriteIndex + WEBAUDIO_BLOCK_SIZE <= m_inputBuffer.Length());
+
+ memcpy(inputP + m_readWriteIndex, sourceP,
+ sizeof(float) * WEBAUDIO_BLOCK_SIZE);
+
+ float* outputP = m_outputBuffer.Elements();
+ m_readWriteIndex += WEBAUDIO_BLOCK_SIZE;
+
+ // Check if it's time to perform the next FFT
+ if (m_readWriteIndex == halfSize) {
+ // The input buffer is now filled (get frequency-domain version)
+ m_frame.PerformFFT(m_inputBuffer.Elements());
+ m_frame.Multiply(*fftKernel);
+ m_frame.GetInverseWithoutScaling(m_outputBuffer.Elements());
+
+ // Overlap-add 1st half from previous time
+ AudioBufferAddWithScale(m_lastOverlapBuffer.Elements(), 1.0f,
+ m_outputBuffer.Elements(), halfSize);
+
+ // Finally, save 2nd half of result
+ MOZ_ASSERT(m_outputBuffer.Length() == 2 * halfSize &&
+ m_lastOverlapBuffer.Length() == halfSize);
+
+ memcpy(m_lastOverlapBuffer.Elements(), m_outputBuffer.Elements() + halfSize,
+ sizeof(float) * halfSize);
+
+ // Reset index back to start for next time
+ m_readWriteIndex = 0;
+ }
+
+ return outputP + m_readWriteIndex;
+}
+
+void FFTConvolver::reset() {
+ PodZero(m_lastOverlapBuffer.Elements(), m_lastOverlapBuffer.Length());
+ m_readWriteIndex = 0;
+}
+
+size_t FFTConvolver::latencyFrames() const {
+ return std::max<size_t>(fftSize() / 2, WEBAUDIO_BLOCK_SIZE) -
+ WEBAUDIO_BLOCK_SIZE;
+}
+
+} // namespace WebCore
diff --git a/dom/media/webaudio/blink/FFTConvolver.h b/dom/media/webaudio/blink/FFTConvolver.h
new file mode 100644
index 0000000000..d54b07f7a4
--- /dev/null
+++ b/dom/media/webaudio/blink/FFTConvolver.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef FFTConvolver_h
+#define FFTConvolver_h
+
+#include "nsTArray.h"
+#include "mozilla/FFTBlock.h"
+#include "mozilla/MemoryReporting.h"
+
+namespace WebCore {
+
+typedef AlignedTArray<float> AlignedAudioFloatArray;
+using mozilla::FFTBlock;
+
+class FFTConvolver {
+ public:
+ // |fftSize| must be a power of two.
+ //
+ // |renderPhase| is the initial offset in the initially zero input buffer.
+ // It is coordinated with the other stages, so they don't all do their
+ // FFTs at the same time.
+ explicit FFTConvolver(size_t fftSize, size_t renderPhase = 0);
+
+ // Process WEBAUDIO_BLOCK_SIZE elements of array |sourceP| and return a
+ // pointer to an output array of the same size.
+ //
+ // |fftKernel| must be pre-scaled for FFTBlock::GetInverseWithoutScaling().
+ //
+ // FIXME: Later, we can do more sophisticated buffering to relax this
+ // requirement...
+ const float* process(FFTBlock* fftKernel, const float* sourceP);
+
+ void reset();
+
+ size_t fftSize() const { return m_frame.FFTSize(); }
+
+ // The input to output latency is up to fftSize / 2, but alignment of the
+ // FFTs with the blocks reduces this by one block.
+ size_t latencyFrames() const;
+
+ size_t sizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+ size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ private:
+ FFTBlock m_frame;
+
+ // Buffer input until we get fftSize / 2 samples then do an FFT
+ size_t m_readWriteIndex;
+ AlignedAudioFloatArray m_inputBuffer;
+
+ // Stores output which we read a little at a time
+ AlignedAudioFloatArray m_outputBuffer;
+
+ // Saves the 2nd half of the FFT buffer, so we can do an overlap-add with the
+ // 1st half of the next one
+ AlignedAudioFloatArray m_lastOverlapBuffer;
+};
+
+} // namespace WebCore
+
+#endif // FFTConvolver_h
diff --git a/dom/media/webaudio/blink/HRTFDatabase.cpp b/dom/media/webaudio/blink/HRTFDatabase.cpp
new file mode 100644
index 0000000000..54be5ea958
--- /dev/null
+++ b/dom/media/webaudio/blink/HRTFDatabase.cpp
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "HRTFDatabase.h"
+
+#include "HRTFElevation.h"
+
+namespace WebCore {
+
+const int HRTFDatabase::MinElevation = -45;
+const int HRTFDatabase::MaxElevation = 90;
+const unsigned HRTFDatabase::RawElevationAngleSpacing = 15;
+const unsigned HRTFDatabase::NumberOfRawElevations =
+ 10; // -45 -> +90 (each 15 degrees)
+const unsigned HRTFDatabase::InterpolationFactor = 1;
+const unsigned HRTFDatabase::NumberOfTotalElevations =
+ NumberOfRawElevations * InterpolationFactor;
+
+nsReturnRef<HRTFDatabase> HRTFDatabase::create(float sampleRate) {
+ return nsReturnRef<HRTFDatabase>(new HRTFDatabase(sampleRate));
+}
+
+HRTFDatabase::HRTFDatabase(float sampleRate) : m_sampleRate(sampleRate) {
+ m_elevations.SetLength(NumberOfTotalElevations);
+
+ unsigned elevationIndex = 0;
+ for (int elevation = MinElevation; elevation <= MaxElevation;
+ elevation += RawElevationAngleSpacing) {
+ nsAutoRef<HRTFElevation> hrtfElevation(
+ HRTFElevation::createBuiltin(elevation, sampleRate));
+ MOZ_ASSERT(hrtfElevation.get());
+ if (!hrtfElevation.get()) return;
+
+ m_elevations[elevationIndex] = hrtfElevation.out();
+ elevationIndex += InterpolationFactor;
+ }
+
+ // Now, go back and interpolate elevations.
+ if (InterpolationFactor > 1) {
+ for (unsigned i = 0; i < NumberOfTotalElevations;
+ i += InterpolationFactor) {
+ unsigned j = (i + InterpolationFactor);
+ if (j >= NumberOfTotalElevations)
+ j = i; // for last elevation interpolate with itself
+
+ // Create the interpolated convolution kernels and delays.
+ for (unsigned jj = 1; jj < InterpolationFactor; ++jj) {
+ float x =
+ static_cast<float>(jj) / static_cast<float>(InterpolationFactor);
+ m_elevations[i + jj] = HRTFElevation::createByInterpolatingSlices(
+ m_elevations[i].get(), m_elevations[j].get(), x, sampleRate);
+ MOZ_ASSERT(m_elevations[i + jj].get());
+ }
+ }
+ }
+}
+
+size_t HRTFDatabase::sizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t amount = aMallocSizeOf(this);
+ amount += m_elevations.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (size_t i = 0; i < m_elevations.Length(); i++) {
+ amount += m_elevations[i]->sizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ return amount;
+}
+
+void HRTFDatabase::getKernelsFromAzimuthElevation(
+ double azimuthBlend, unsigned azimuthIndex, double elevationAngle,
+ HRTFKernel*& kernelL, HRTFKernel*& kernelR, double& frameDelayL,
+ double& frameDelayR) {
+ unsigned elevationIndex = indexFromElevationAngle(elevationAngle);
+ MOZ_ASSERT(elevationIndex < m_elevations.Length() &&
+ m_elevations.Length() > 0);
+
+ if (!m_elevations.Length()) {
+ kernelL = 0;
+ kernelR = 0;
+ return;
+ }
+
+ if (elevationIndex > m_elevations.Length() - 1)
+ elevationIndex = m_elevations.Length() - 1;
+
+ HRTFElevation* hrtfElevation = m_elevations[elevationIndex].get();
+ MOZ_ASSERT(hrtfElevation);
+ if (!hrtfElevation) {
+ kernelL = 0;
+ kernelR = 0;
+ return;
+ }
+
+ hrtfElevation->getKernelsFromAzimuth(azimuthBlend, azimuthIndex, kernelL,
+ kernelR, frameDelayL, frameDelayR);
+}
+
+unsigned HRTFDatabase::indexFromElevationAngle(double elevationAngle) {
+ // Clamp to allowed range.
+ elevationAngle =
+ mozilla::clamped(elevationAngle, static_cast<double>(MinElevation),
+ static_cast<double>(MaxElevation));
+
+ unsigned elevationIndex =
+ static_cast<int>(InterpolationFactor * (elevationAngle - MinElevation) /
+ RawElevationAngleSpacing);
+ return elevationIndex;
+}
+
+} // namespace WebCore
diff --git a/dom/media/webaudio/blink/HRTFDatabase.h b/dom/media/webaudio/blink/HRTFDatabase.h
new file mode 100644
index 0000000000..50cf2958bd
--- /dev/null
+++ b/dom/media/webaudio/blink/HRTFDatabase.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef HRTFDatabase_h
+#define HRTFDatabase_h
+
+#include "HRTFElevation.h"
+#include "nsAutoRef.h"
+#include "nsTArray.h"
+#include "mozilla/MemoryReporting.h"
+
+namespace WebCore {
+
+class HRTFKernel;
+
+class HRTFDatabase {
+ public:
+ static nsReturnRef<HRTFDatabase> create(float sampleRate);
+
+ // clang-format off
+ // getKernelsFromAzimuthElevation() returns a left and right ear kernel, and an interpolated left and right frame delay for the given azimuth and elevation.
+ // azimuthBlend must be in the range 0 -> 1.
+ // Valid values for azimuthIndex are 0 -> HRTFElevation::NumberOfTotalAzimuths - 1 (corresponding to angles of 0 -> 360).
+ // Valid values for elevationAngle are MinElevation -> MaxElevation.
+ // clang-format on
+ void getKernelsFromAzimuthElevation(double azimuthBlend,
+ unsigned azimuthIndex,
+ double elevationAngle,
+ HRTFKernel*& kernelL,
+ HRTFKernel*& kernelR, double& frameDelayL,
+ double& frameDelayR);
+
+ // Returns the number of different azimuth angles.
+ static unsigned numberOfAzimuths() {
+ return HRTFElevation::NumberOfTotalAzimuths;
+ }
+
+ float sampleRate() const { return m_sampleRate; }
+
+ // Number of elevations loaded from resource.
+ static const unsigned NumberOfRawElevations;
+
+ size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ private:
+ HRTFDatabase(const HRTFDatabase& other) = delete;
+ void operator=(const HRTFDatabase& other) = delete;
+
+ explicit HRTFDatabase(float sampleRate);
+
+ // Minimum and maximum elevation angles (inclusive) for a HRTFDatabase.
+ static const int MinElevation;
+ static const int MaxElevation;
+ static const unsigned RawElevationAngleSpacing;
+
+ // Interpolates by this factor to get the total number of elevations from
+ // every elevation loaded from resource.
+ static const unsigned InterpolationFactor;
+
+ // Total number of elevations after interpolation.
+ static const unsigned NumberOfTotalElevations;
+
+ // Returns the index for the correct HRTFElevation given the elevation angle.
+ static unsigned indexFromElevationAngle(double);
+
+ nsTArray<nsAutoRef<HRTFElevation> > m_elevations;
+ float m_sampleRate;
+};
+
+} // namespace WebCore
+
+template <>
+class nsAutoRefTraits<WebCore::HRTFDatabase>
+ : public nsPointerRefTraits<WebCore::HRTFDatabase> {
+ public:
+ static void Release(WebCore::HRTFDatabase* ptr) { delete (ptr); }
+};
+
+#endif // HRTFDatabase_h
diff --git a/dom/media/webaudio/blink/HRTFDatabaseLoader.cpp b/dom/media/webaudio/blink/HRTFDatabaseLoader.cpp
new file mode 100644
index 0000000000..3db455545e
--- /dev/null
+++ b/dom/media/webaudio/blink/HRTFDatabaseLoader.cpp
@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "HRTFDatabaseLoader.h"
+#include "HRTFDatabase.h"
+#include "GeckoProfiler.h"
+#include "nsThreadUtils.h"
+
+using namespace mozilla;
+
+namespace WebCore {
+
+// Singleton
+nsTHashtable<HRTFDatabaseLoader::LoaderByRateEntry>*
+ HRTFDatabaseLoader::s_loaderMap = nullptr;
+
+size_t HRTFDatabaseLoader::sizeOfLoaders(mozilla::MallocSizeOf aMallocSizeOf) {
+ return s_loaderMap ? s_loaderMap->SizeOfIncludingThis(aMallocSizeOf) : 0;
+}
+
+already_AddRefed<HRTFDatabaseLoader>
+HRTFDatabaseLoader::createAndLoadAsynchronouslyIfNecessary(float sampleRate) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RefPtr<HRTFDatabaseLoader> loader;
+
+ if (!s_loaderMap) {
+ s_loaderMap = new nsTHashtable<LoaderByRateEntry>();
+ }
+
+ LoaderByRateEntry* entry = s_loaderMap->PutEntry(sampleRate);
+ loader = entry->mLoader;
+ if (loader) { // existing entry
+ MOZ_ASSERT(sampleRate == loader->databaseSampleRate());
+ return loader.forget();
+ }
+
+ loader = new HRTFDatabaseLoader(sampleRate);
+ entry->mLoader = loader;
+
+ loader->loadAsynchronously();
+
+ return loader.forget();
+}
+
+HRTFDatabaseLoader::HRTFDatabaseLoader(float sampleRate)
+ : m_refCnt(0),
+ m_threadLock("HRTFDatabaseLoader"),
+ m_databaseLoaderThread(nullptr),
+ m_databaseSampleRate(sampleRate),
+ m_databaseLoaded(false) {
+ MOZ_ASSERT(NS_IsMainThread());
+}
+
+HRTFDatabaseLoader::~HRTFDatabaseLoader() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ waitForLoaderThreadCompletion();
+ m_hrtfDatabase.reset();
+
+ if (s_loaderMap) {
+ // Remove ourself from the map.
+ s_loaderMap->RemoveEntry(m_databaseSampleRate);
+ if (s_loaderMap->Count() == 0) {
+ delete s_loaderMap;
+ s_loaderMap = nullptr;
+ }
+ }
+}
+
+size_t HRTFDatabaseLoader::sizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t amount = aMallocSizeOf(this);
+
+ // NB: Need to make sure we're not competing with the loader thread.
+ const_cast<HRTFDatabaseLoader*>(this)->waitForLoaderThreadCompletion();
+
+ if (m_hrtfDatabase) {
+ amount += m_hrtfDatabase->sizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ return amount;
+}
+
+class HRTFDatabaseLoader::ProxyReleaseEvent final : public Runnable {
+ public:
+ explicit ProxyReleaseEvent(HRTFDatabaseLoader* loader)
+ : mozilla::Runnable("WebCore::HRTFDatabaseLoader::ProxyReleaseEvent"),
+ mLoader(loader) {}
+ NS_IMETHOD Run() override {
+ mLoader->MainThreadRelease();
+ return NS_OK;
+ }
+
+ private:
+ // Ownership transferred by ProxyRelease
+ HRTFDatabaseLoader* MOZ_OWNING_REF mLoader;
+};
+
+void HRTFDatabaseLoader::ProxyRelease() {
+ nsCOMPtr<nsIEventTarget> mainTarget = GetMainThreadSerialEventTarget();
+ if (MOZ_LIKELY(mainTarget)) {
+ RefPtr<ProxyReleaseEvent> event = new ProxyReleaseEvent(this);
+ DebugOnly<nsresult> rv = mainTarget->Dispatch(event, NS_DISPATCH_NORMAL);
+ MOZ_ASSERT(NS_SUCCEEDED(rv), "Failed to dispatch release event");
+ } else {
+ // Should be in XPCOM shutdown.
+ MOZ_ASSERT(NS_IsMainThread(), "Main thread is not available for dispatch.");
+ MainThreadRelease();
+ }
+}
+
+void HRTFDatabaseLoader::MainThreadRelease() {
+ MOZ_ASSERT(NS_IsMainThread());
+ int count = --m_refCnt;
+ MOZ_ASSERT(count >= 0, "extra release");
+ NS_LOG_RELEASE(this, count, "HRTFDatabaseLoader");
+ if (count == 0) {
+ // It is safe to delete here as the first reference can only be added
+ // on this (main) thread.
+ delete this;
+ }
+}
+
+// Asynchronously load the database in this thread.
+static void databaseLoaderEntry(void* threadData) {
+ AUTO_PROFILER_REGISTER_THREAD("HRTFDatabaseLdr");
+ NS_SetCurrentThreadName("HRTFDatabaseLdr");
+
+ HRTFDatabaseLoader* loader =
+ reinterpret_cast<HRTFDatabaseLoader*>(threadData);
+ MOZ_ASSERT(loader);
+ loader->load();
+}
+
+void HRTFDatabaseLoader::load() {
+ MOZ_ASSERT(!NS_IsMainThread());
+ MOZ_ASSERT(!m_hrtfDatabase.get(), "Called twice");
+ // Load the default HRTF database.
+ m_hrtfDatabase = HRTFDatabase::create(m_databaseSampleRate);
+ m_databaseLoaded = true;
+ // Notifies the main thread of completion. See loadAsynchronously().
+ Release();
+}
+
+void HRTFDatabaseLoader::loadAsynchronously() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(m_refCnt, "Must not be called before a reference is added");
+
+ // Add a reference so that the destructor won't run and wait for the
+ // loader thread, until load() has completed.
+ AddRef();
+
+ MutexAutoLock locker(m_threadLock);
+
+ MOZ_ASSERT(!m_hrtfDatabase.get() && !m_databaseLoaderThread, "Called twice");
+ // Start the asynchronous database loading process.
+ m_databaseLoaderThread = PR_CreateThread(
+ PR_USER_THREAD, databaseLoaderEntry, this, PR_PRIORITY_NORMAL,
+ PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0);
+}
+
+bool HRTFDatabaseLoader::isLoaded() const { return m_hrtfDatabase.get(); }
+
+void HRTFDatabaseLoader::waitForLoaderThreadCompletion() {
+ MutexAutoLock locker(m_threadLock);
+
+ // waitForThreadCompletion() should not be called twice for the same thread.
+ if (m_databaseLoaderThread) {
+ DebugOnly<PRStatus> status = PR_JoinThread(m_databaseLoaderThread);
+ MOZ_ASSERT(status == PR_SUCCESS, "PR_JoinThread failed");
+ }
+ m_databaseLoaderThread = nullptr;
+}
+
+void HRTFDatabaseLoader::shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (s_loaderMap) {
+ // Set s_loaderMap to nullptr so that the hashtable is not modified on
+ // reference release during enumeration.
+ nsTHashtable<LoaderByRateEntry>* loaderMap = s_loaderMap;
+ s_loaderMap = nullptr;
+ for (auto iter = loaderMap->Iter(); !iter.Done(); iter.Next()) {
+ iter.Get()->mLoader->waitForLoaderThreadCompletion();
+ }
+ delete loaderMap;
+ }
+}
+
+} // namespace WebCore
diff --git a/dom/media/webaudio/blink/HRTFDatabaseLoader.h b/dom/media/webaudio/blink/HRTFDatabaseLoader.h
new file mode 100644
index 0000000000..5d4548d247
--- /dev/null
+++ b/dom/media/webaudio/blink/HRTFDatabaseLoader.h
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef HRTFDatabaseLoader_h
+#define HRTFDatabaseLoader_h
+
+#include "nsHashKeys.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Mutex.h"
+#include "HRTFDatabase.h"
+
+template <class EntryType>
+class nsTHashtable;
+template <class T>
+class nsAutoRef;
+
+namespace WebCore {
+
+// HRTFDatabaseLoader will asynchronously load the default HRTFDatabase in a new
+// thread.
+
+class HRTFDatabaseLoader {
+ public:
+ // Lazily creates a HRTFDatabaseLoader (if not already created) for the given
+ // sample-rate and starts loading asynchronously (when created the first
+ // time). Returns the HRTFDatabaseLoader. Must be called from the main thread.
+ static already_AddRefed<HRTFDatabaseLoader>
+ createAndLoadAsynchronouslyIfNecessary(float sampleRate);
+
+ // AddRef and Release may be called from any thread.
+ void AddRef() {
+#if defined(DEBUG) || defined(NS_BUILD_REFCNT_LOGGING)
+ int count =
+#endif
+ ++m_refCnt;
+ MOZ_ASSERT(count > 0, "invalid ref count");
+ NS_LOG_ADDREF(this, count, "HRTFDatabaseLoader", sizeof(*this));
+ }
+
+ void Release() {
+ // The last reference can't be removed on a non-main thread because
+ // the object can be accessed on the main thread from the hash
+ // table via createAndLoadAsynchronouslyIfNecessary().
+ int count = m_refCnt;
+ MOZ_ASSERT(count > 0, "extra release");
+ // Optimization attempt to possibly skip proxying the release to the
+ // main thread.
+ if (count != 1 && m_refCnt.compareExchange(count, count - 1)) {
+ NS_LOG_RELEASE(this, count - 1, "HRTFDatabaseLoader");
+ return;
+ }
+
+ ProxyRelease();
+ }
+
+ // Returns true once the default database has been completely loaded.
+ bool isLoaded() const;
+
+ // waitForLoaderThreadCompletion() may be called more than once,
+ // on any thread except m_databaseLoaderThread.
+ void waitForLoaderThreadCompletion();
+
+ HRTFDatabase* database() {
+ if (!m_databaseLoaded) {
+ return nullptr;
+ }
+ return m_hrtfDatabase.get();
+ }
+
+ float databaseSampleRate() const { return m_databaseSampleRate; }
+
+ static void shutdown();
+
+ // Called in asynchronous loading thread.
+ void load();
+
+ // Sums the size of all cached database loaders.
+ static size_t sizeOfLoaders(mozilla::MallocSizeOf aMallocSizeOf);
+
+ private:
+ // Both constructor and destructor must be called from the main thread.
+ explicit HRTFDatabaseLoader(float sampleRate);
+ ~HRTFDatabaseLoader();
+
+ size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ void ProxyRelease(); // any thread
+ void MainThreadRelease(); // main thread only
+ class ProxyReleaseEvent;
+
+ // If it hasn't already been loaded, creates a new thread and initiates
+ // asynchronous loading of the default database. This must be called from the
+ // main thread.
+ void loadAsynchronously();
+
+ // Map from sample-rate to loader.
+ class LoaderByRateEntry : public nsFloatHashKey {
+ public:
+ explicit LoaderByRateEntry(KeyTypePointer aKey)
+ : nsFloatHashKey(aKey),
+ mLoader() // so PutEntry() will zero-initialize
+ {}
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return mLoader ? mLoader->sizeOfIncludingThis(aMallocSizeOf) : 0;
+ }
+
+ // The HRTFDatabaseLoader removes itself from s_loaderMap on destruction.
+ HRTFDatabaseLoader* MOZ_NON_OWNING_REF mLoader;
+ };
+
+ // Keeps track of loaders on a per-sample-rate basis.
+ static nsTHashtable<LoaderByRateEntry>* s_loaderMap; // singleton
+
+ mozilla::Atomic<int> m_refCnt;
+
+ nsAutoRef<HRTFDatabase> m_hrtfDatabase;
+
+ // Holding a m_threadLock is required when accessing m_databaseLoaderThread.
+ mozilla::Mutex m_threadLock;
+ PRThread* m_databaseLoaderThread MOZ_GUARDED_BY(m_threadLock);
+
+ float m_databaseSampleRate;
+ mozilla::Atomic<bool> m_databaseLoaded;
+};
+
+} // namespace WebCore
+
+#endif // HRTFDatabaseLoader_h
diff --git a/dom/media/webaudio/blink/HRTFElevation.cpp b/dom/media/webaudio/blink/HRTFElevation.cpp
new file mode 100644
index 0000000000..38ac0f8614
--- /dev/null
+++ b/dom/media/webaudio/blink/HRTFElevation.cpp
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "HRTFElevation.h"
+
+#include <speex/speex_resampler.h>
+#include "mozilla/PodOperations.h"
+#include "AudioSampleFormat.h"
+
+#include "IRC_Composite_C_R0195-incl.cpp"
+
+using namespace mozilla;
+
+namespace WebCore {
+
+const int elevationSpacing = irc_composite_c_r0195_elevation_interval;
+const int firstElevation = irc_composite_c_r0195_first_elevation;
+const int numberOfElevations = MOZ_ARRAY_LENGTH(irc_composite_c_r0195);
+
+const unsigned HRTFElevation::NumberOfTotalAzimuths = 360 / 15 * 8;
+
+const int rawSampleRate = irc_composite_c_r0195_sample_rate;
+
+// Number of frames in an individual impulse response.
+const size_t ResponseFrameSize = 256;
+
+size_t HRTFElevation::sizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t amount = aMallocSizeOf(this);
+
+ amount += m_kernelListL.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (size_t i = 0; i < m_kernelListL.Length(); i++) {
+ amount += m_kernelListL[i]->sizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ return amount;
+}
+
+size_t HRTFElevation::fftSizeForSampleRate(float sampleRate) {
+ // The IRCAM HRTF impulse responses were 512 sample-frames @44.1KHz,
+ // but these have been truncated to 256 samples.
+ // An FFT-size of twice impulse response size is used (for convolution).
+ // So for sample rates of 44.1KHz an FFT size of 512 is good.
+ // We double the FFT-size only for sample rates at least double this.
+ // If the FFT size is too large then the impulse response will be padded
+ // with zeros without the fade-out provided by HRTFKernel.
+ MOZ_ASSERT(sampleRate > 1.0 && sampleRate < 1048576.0);
+
+ // This is the size if we were to use all raw response samples.
+ unsigned resampledLength =
+ floorf(ResponseFrameSize * sampleRate / rawSampleRate);
+ // Keep things semi-sane, with max FFT size of 1024.
+ unsigned size = std::min(resampledLength, 1023U);
+ // Ensure a minimum of 2 * WEBAUDIO_BLOCK_SIZE (with the size++ below) for
+ // FFTConvolver and set the 8 least significant bits for rounding up to
+ // the next power of 2 below.
+ size |= 2 * WEBAUDIO_BLOCK_SIZE - 1;
+ // Round up to the next power of 2, making the FFT size no more than twice
+ // the impulse response length. This doubles size for values that are
+ // already powers of 2. This works by filling in alls bit to right of the
+ // most significant bit. The most significant bit is no greater than
+ // 1 << 9, and the least significant 8 bits were already set above, so
+ // there is at most one bit to add.
+ size |= (size >> 1);
+ size++;
+ MOZ_ASSERT((size & (size - 1)) == 0);
+
+ return size;
+}
+
+nsReturnRef<HRTFKernel> HRTFElevation::calculateKernelForAzimuthElevation(
+ int azimuth, int elevation, SpeexResamplerState* resampler,
+ float sampleRate) {
+ int elevationIndex = (elevation - firstElevation) / elevationSpacing;
+ MOZ_ASSERT(elevationIndex >= 0 && elevationIndex <= numberOfElevations);
+
+ int numberOfAzimuths = irc_composite_c_r0195[elevationIndex].count;
+ int azimuthSpacing = 360 / numberOfAzimuths;
+ MOZ_ASSERT(numberOfAzimuths * azimuthSpacing == 360);
+
+ int azimuthIndex = azimuth / azimuthSpacing;
+ MOZ_ASSERT(azimuthIndex * azimuthSpacing == azimuth);
+
+ const int16_t(&impulse_response_data)[ResponseFrameSize] =
+ irc_composite_c_r0195[elevationIndex].azimuths[azimuthIndex];
+
+ float response[ResponseFrameSize];
+ ConvertAudioSamples(impulse_response_data, response, ResponseFrameSize);
+ float* resampledResponse;
+
+ // Note that depending on the fftSize returned by the panner, we may be
+ // truncating the impulse response.
+ const size_t resampledResponseLength = fftSizeForSampleRate(sampleRate) / 2;
+
+ AutoTArray<AudioDataValue, 2 * ResponseFrameSize> resampled;
+ if (sampleRate == rawSampleRate) {
+ resampledResponse = response;
+ MOZ_ASSERT(resampledResponseLength == ResponseFrameSize);
+ } else {
+ resampled.SetLength(resampledResponseLength);
+ resampledResponse = resampled.Elements();
+ speex_resampler_skip_zeros(resampler);
+
+ // Feed the input buffer into the resampler.
+ spx_uint32_t in_len = ResponseFrameSize;
+ spx_uint32_t out_len = resampled.Length();
+ speex_resampler_process_float(resampler, 0, response, &in_len,
+ resampled.Elements(), &out_len);
+
+ if (out_len < resampled.Length()) {
+ // The input should have all been processed.
+ MOZ_ASSERT(in_len == ResponseFrameSize);
+ // Feed in zeros get the data remaining in the resampler.
+ spx_uint32_t out_index = out_len;
+ in_len = speex_resampler_get_input_latency(resampler);
+ out_len = resampled.Length() - out_index;
+ speex_resampler_process_float(resampler, 0, nullptr, &in_len,
+ resampled.Elements() + out_index, &out_len);
+ out_index += out_len;
+ // There may be some uninitialized samples remaining for very low
+ // sample rates.
+ PodZero(resampled.Elements() + out_index, resampled.Length() - out_index);
+ }
+
+ speex_resampler_reset_mem(resampler);
+ }
+
+ return HRTFKernel::create(resampledResponse, resampledResponseLength,
+ sampleRate);
+}
+
+// The range of elevations for the IRCAM impulse responses varies depending on
+// azimuth, but the minimum elevation appears to always be -45.
+//
+// Here's how it goes:
+static int maxElevations[] = {
+ // Azimuth
+ //
+ 90, // 0
+ 45, // 15
+ 60, // 30
+ 45, // 45
+ 75, // 60
+ 45, // 75
+ 60, // 90
+ 45, // 105
+ 75, // 120
+ 45, // 135
+ 60, // 150
+ 45, // 165
+ 75, // 180
+ 45, // 195
+ 60, // 210
+ 45, // 225
+ 75, // 240
+ 45, // 255
+ 60, // 270
+ 45, // 285
+ 75, // 300
+ 45, // 315
+ 60, // 330
+ 45 // 345
+};
+
+nsReturnRef<HRTFElevation> HRTFElevation::createBuiltin(int elevation,
+ float sampleRate) {
+ if (elevation < firstElevation ||
+ elevation > firstElevation + numberOfElevations * elevationSpacing ||
+ (elevation / elevationSpacing) * elevationSpacing != elevation)
+ return nsReturnRef<HRTFElevation>();
+
+ // Spacing, in degrees, between every azimuth loaded from resource.
+ // Some elevations do not have data for all these intervals.
+ // See maxElevations.
+ static const unsigned AzimuthSpacing = 15;
+ static const unsigned NumberOfRawAzimuths = 360 / AzimuthSpacing;
+ static_assert(AzimuthSpacing * NumberOfRawAzimuths == 360, "Not a multiple");
+ static const unsigned InterpolationFactor =
+ NumberOfTotalAzimuths / NumberOfRawAzimuths;
+ static_assert(
+ NumberOfTotalAzimuths == NumberOfRawAzimuths * InterpolationFactor,
+ "Not a multiple");
+
+ HRTFKernelList kernelListL;
+ kernelListL.SetLength(NumberOfTotalAzimuths);
+
+ SpeexResamplerState* resampler =
+ sampleRate == rawSampleRate
+ ? nullptr
+ : speex_resampler_init(1, rawSampleRate, sampleRate,
+ SPEEX_RESAMPLER_QUALITY_MIN, nullptr);
+
+ // Load convolution kernels from HRTF files.
+ int interpolatedIndex = 0;
+ for (unsigned rawIndex = 0; rawIndex < NumberOfRawAzimuths; ++rawIndex) {
+ // Don't let elevation exceed maximum for this azimuth.
+ int maxElevation = maxElevations[rawIndex];
+ int actualElevation = std::min(elevation, maxElevation);
+
+ kernelListL[interpolatedIndex] = calculateKernelForAzimuthElevation(
+ rawIndex * AzimuthSpacing, actualElevation, resampler, sampleRate);
+
+ interpolatedIndex += InterpolationFactor;
+ }
+
+ if (resampler) speex_resampler_destroy(resampler);
+
+ // Now go back and interpolate intermediate azimuth values.
+ for (unsigned i = 0; i < NumberOfTotalAzimuths; i += InterpolationFactor) {
+ int j = (i + InterpolationFactor) % NumberOfTotalAzimuths;
+
+ // Create the interpolated convolution kernels and delays.
+ for (unsigned jj = 1; jj < InterpolationFactor; ++jj) {
+ float x =
+ float(jj) / float(InterpolationFactor); // interpolate from 0 -> 1
+
+ kernelListL[i + jj] = HRTFKernel::createInterpolatedKernel(
+ kernelListL[i], kernelListL[j], x);
+ }
+ }
+
+ return nsReturnRef<HRTFElevation>(
+ new HRTFElevation(std::move(kernelListL), elevation, sampleRate));
+}
+
+nsReturnRef<HRTFElevation> HRTFElevation::createByInterpolatingSlices(
+ HRTFElevation* hrtfElevation1, HRTFElevation* hrtfElevation2, float x,
+ float sampleRate) {
+ MOZ_ASSERT(hrtfElevation1 && hrtfElevation2);
+ if (!hrtfElevation1 || !hrtfElevation2) return nsReturnRef<HRTFElevation>();
+
+ MOZ_ASSERT(x >= 0.0 && x < 1.0);
+
+ HRTFKernelList kernelListL;
+ kernelListL.SetLength(NumberOfTotalAzimuths);
+
+ const HRTFKernelList& kernelListL1 = hrtfElevation1->kernelListL();
+ const HRTFKernelList& kernelListL2 = hrtfElevation2->kernelListL();
+
+ // Interpolate kernels of corresponding azimuths of the two elevations.
+ for (unsigned i = 0; i < NumberOfTotalAzimuths; ++i) {
+ kernelListL[i] = HRTFKernel::createInterpolatedKernel(kernelListL1[i],
+ kernelListL2[i], x);
+ }
+
+ // Interpolate elevation angle.
+ double angle = (1.0 - x) * hrtfElevation1->elevationAngle() +
+ x * hrtfElevation2->elevationAngle();
+
+ return nsReturnRef<HRTFElevation>(new HRTFElevation(
+ std::move(kernelListL), static_cast<int>(angle), sampleRate));
+}
+
+void HRTFElevation::getKernelsFromAzimuth(
+ double azimuthBlend, unsigned azimuthIndex, HRTFKernel*& kernelL,
+ HRTFKernel*& kernelR, double& frameDelayL, double& frameDelayR) {
+ bool checkAzimuthBlend = azimuthBlend >= 0.0 && azimuthBlend < 1.0;
+ MOZ_ASSERT(checkAzimuthBlend);
+ if (!checkAzimuthBlend) azimuthBlend = 0.0;
+
+ unsigned numKernels = m_kernelListL.Length();
+
+ bool isIndexGood = azimuthIndex < numKernels;
+ MOZ_ASSERT(isIndexGood);
+ if (!isIndexGood) {
+ kernelL = 0;
+ kernelR = 0;
+ return;
+ }
+
+ // Return the left and right kernels,
+ // using symmetry to produce the right kernel.
+ kernelL = m_kernelListL[azimuthIndex];
+ int azimuthIndexR = (numKernels - azimuthIndex) % numKernels;
+ kernelR = m_kernelListL[azimuthIndexR];
+
+ frameDelayL = kernelL->frameDelay();
+ frameDelayR = kernelR->frameDelay();
+
+ int azimuthIndex2L = (azimuthIndex + 1) % numKernels;
+ double frameDelay2L = m_kernelListL[azimuthIndex2L]->frameDelay();
+ int azimuthIndex2R = (numKernels - azimuthIndex2L) % numKernels;
+ double frameDelay2R = m_kernelListL[azimuthIndex2R]->frameDelay();
+
+ // Linearly interpolate delays.
+ frameDelayL =
+ (1.0 - azimuthBlend) * frameDelayL + azimuthBlend * frameDelay2L;
+ frameDelayR =
+ (1.0 - azimuthBlend) * frameDelayR + azimuthBlend * frameDelay2R;
+}
+
+} // namespace WebCore
diff --git a/dom/media/webaudio/blink/HRTFElevation.h b/dom/media/webaudio/blink/HRTFElevation.h
new file mode 100644
index 0000000000..cd54bbc04a
--- /dev/null
+++ b/dom/media/webaudio/blink/HRTFElevation.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef HRTFElevation_h
+#define HRTFElevation_h
+
+#include "HRTFKernel.h"
+#include "nsAutoRef.h"
+#include "mozilla/MemoryReporting.h"
+
+struct SpeexResamplerState_;
+typedef struct SpeexResamplerState_ SpeexResamplerState;
+
+namespace WebCore {
+
+// HRTFElevation contains all of the HRTFKernels (one left ear and one right ear
+// per azimuth angle) for a particular elevation.
+
+class HRTFElevation {
+ public:
+ // Loads and returns an HRTFElevation with the given HRTF database subject
+ // name and elevation from browser (or WebKit.framework) resources. Normally,
+ // there will only be a single HRTF database set, but this API supports the
+ // possibility of multiple ones with different names. Interpolated azimuths
+ // will be generated based on InterpolationFactor. Valid values for elevation
+ // are -45 -> +90 in 15 degree increments.
+ static nsReturnRef<HRTFElevation> createBuiltin(int elevation,
+ float sampleRate);
+
+ // Given two HRTFElevations, and an interpolation factor x: 0 -> 1, returns an
+ // interpolated HRTFElevation.
+ static nsReturnRef<HRTFElevation> createByInterpolatingSlices(
+ HRTFElevation* hrtfElevation1, HRTFElevation* hrtfElevation2, float x,
+ float sampleRate);
+
+ double elevationAngle() const { return m_elevationAngle; }
+ unsigned numberOfAzimuths() const { return NumberOfTotalAzimuths; }
+ float sampleRate() const { return m_sampleRate; }
+
+ // Returns the left and right kernels for the given azimuth index.
+ // The interpolated delays based on azimuthBlend: 0 -> 1 are returned in
+ // frameDelayL and frameDelayR.
+ void getKernelsFromAzimuth(double azimuthBlend, unsigned azimuthIndex,
+ HRTFKernel*& kernelL, HRTFKernel*& kernelR,
+ double& frameDelayL, double& frameDelayR);
+
+ // Total number of azimuths after interpolation.
+ static const unsigned NumberOfTotalAzimuths;
+
+ static size_t fftSizeForSampleRate(float sampleRate);
+
+ size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ private:
+ HRTFElevation(const HRTFElevation& other) = delete;
+ void operator=(const HRTFElevation& other) = delete;
+
+ HRTFElevation(HRTFKernelList&& kernelListL, int elevation, float sampleRate)
+ : m_kernelListL(std::move(kernelListL)),
+ m_elevationAngle(elevation),
+ m_sampleRate(sampleRate) {}
+
+ // Returns the list of left ear HRTFKernels for all the azimuths going from 0
+ // to 360 degrees.
+ const HRTFKernelList& kernelListL() { return m_kernelListL; }
+
+ // Given a specific azimuth and elevation angle, returns the left HRTFKernel.
+ // Values for azimuth must be multiples of 15 in 0 -> 345,
+ // but not all azimuths are available for elevations > +45.
+ // Valid values for elevation are -45 -> +90 in 15 degree increments.
+ static nsReturnRef<HRTFKernel> calculateKernelForAzimuthElevation(
+ int azimuth, int elevation, SpeexResamplerState* resampler,
+ float sampleRate);
+
+ HRTFKernelList m_kernelListL;
+ double m_elevationAngle;
+ float m_sampleRate;
+};
+
+} // namespace WebCore
+
+template <>
+class nsAutoRefTraits<WebCore::HRTFElevation>
+ : public nsPointerRefTraits<WebCore::HRTFElevation> {
+ public:
+ static void Release(WebCore::HRTFElevation* ptr) { delete (ptr); }
+};
+
+#endif // HRTFElevation_h
diff --git a/dom/media/webaudio/blink/HRTFKernel.cpp b/dom/media/webaudio/blink/HRTFKernel.cpp
new file mode 100644
index 0000000000..ecaa846a66
--- /dev/null
+++ b/dom/media/webaudio/blink/HRTFKernel.cpp
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "HRTFKernel.h"
+namespace WebCore {
+
+// Takes the input audio channel |impulseP| as an input impulse response and
+// calculates the average group delay. This represents the initial delay before
+// the most energetic part of the impulse response. The sample-frame delay is
+// removed from the |impulseP| impulse response, and this value is returned.
+// The |length| of the passed in |impulseP| must be must be a power of 2.
+static float extractAverageGroupDelay(float* impulseP, size_t length) {
+ // Check for power-of-2.
+ MOZ_ASSERT(length && (length & (length - 1)) == 0);
+
+ FFTBlock estimationFrame(length);
+ estimationFrame.PerformFFT(impulseP);
+
+ float frameDelay =
+ static_cast<float>(estimationFrame.ExtractAverageGroupDelay());
+ estimationFrame.GetInverse(impulseP);
+
+ return frameDelay;
+}
+
+HRTFKernel::HRTFKernel(float* impulseResponse, size_t length, float sampleRate)
+ : m_frameDelay(0), m_sampleRate(sampleRate) {
+ AlignedTArray<float> buffer;
+ // copy to a 32-byte aligned buffer
+ if (((uintptr_t)impulseResponse & 31) != 0) {
+ buffer.SetLength(length);
+ mozilla::PodCopy(buffer.Elements(), impulseResponse, length);
+ impulseResponse = buffer.Elements();
+ }
+
+ // Determine the leading delay (average group delay) for the response.
+ m_frameDelay = extractAverageGroupDelay(impulseResponse, length);
+
+ // The FFT size (with zero padding) needs to be twice the response length
+ // in order to do proper convolution.
+ unsigned fftSize = 2 * length;
+
+ // Quick fade-out (apply window) at truncation point
+ // because the impulse response has been truncated.
+ unsigned numberOfFadeOutFrames = static_cast<unsigned>(
+ sampleRate / 4410); // 10 sample-frames @44.1KHz sample-rate
+ MOZ_ASSERT(numberOfFadeOutFrames < length);
+ if (numberOfFadeOutFrames < length) {
+ for (unsigned i = length - numberOfFadeOutFrames; i < length; ++i) {
+ float x =
+ 1.0f - static_cast<float>(i - (length - numberOfFadeOutFrames)) /
+ numberOfFadeOutFrames;
+ impulseResponse[i] *= x;
+ }
+ }
+
+ m_fftFrame = mozilla::MakeUnique<FFTBlock>(fftSize);
+ m_fftFrame->PadAndMakeScaledDFT(impulseResponse, length);
+}
+
+// Interpolates two kernels with x: 0 -> 1 and returns the result.
+nsReturnRef<HRTFKernel> HRTFKernel::createInterpolatedKernel(
+ HRTFKernel* kernel1, HRTFKernel* kernel2, float x) {
+ MOZ_ASSERT(kernel1 && kernel2);
+ if (!kernel1 || !kernel2) return nsReturnRef<HRTFKernel>();
+
+ MOZ_ASSERT(x >= 0.0 && x < 1.0);
+ x = mozilla::clamped(x, 0.0f, 1.0f);
+
+ float sampleRate1 = kernel1->sampleRate();
+ float sampleRate2 = kernel2->sampleRate();
+ MOZ_ASSERT(sampleRate1 == sampleRate2);
+ if (sampleRate1 != sampleRate2) return nsReturnRef<HRTFKernel>();
+
+ float frameDelay =
+ (1 - x) * kernel1->frameDelay() + x * kernel2->frameDelay();
+
+ UniquePtr<FFTBlock> interpolatedFrame(FFTBlock::CreateInterpolatedBlock(
+ *kernel1->fftFrame(), *kernel2->fftFrame(), x));
+ return HRTFKernel::create(std::move(interpolatedFrame), frameDelay,
+ sampleRate1);
+}
+
+} // namespace WebCore
diff --git a/dom/media/webaudio/blink/HRTFKernel.h b/dom/media/webaudio/blink/HRTFKernel.h
new file mode 100644
index 0000000000..34479f6301
--- /dev/null
+++ b/dom/media/webaudio/blink/HRTFKernel.h
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef HRTFKernel_h
+#define HRTFKernel_h
+
+#include "nsAutoRef.h"
+#include "nsTArray.h"
+#include "mozilla/FFTBlock.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/UniquePtr.h"
+
+namespace WebCore {
+
+using mozilla::FFTBlock;
+using mozilla::UniquePtr;
+
+// HRTF stands for Head-Related Transfer Function.
+// HRTFKernel is a frequency-domain representation of an impulse-response used
+// as part of the spatialized panning system. For a given azimuth / elevation
+// angle there will be one HRTFKernel for the left ear transfer function, and
+// one for the right ear. The leading delay (average group delay) for each
+// impulse response is extracted:
+// m_fftFrame is the frequency-domain representation of the impulse
+// response with the delay removed
+// m_frameDelay is the leading delay of the original impulse response.
+class HRTFKernel {
+ public:
+ // Note: this is destructive on the passed in |impulseResponse|.
+ // The |length| of |impulseResponse| must be a power of two.
+ // The size of the DFT will be |2 * length|.
+ static nsReturnRef<HRTFKernel> create(float* impulseResponse, size_t length,
+ float sampleRate);
+
+ static nsReturnRef<HRTFKernel> create(UniquePtr<FFTBlock> fftFrame,
+ float frameDelay, float sampleRate);
+
+ // Given two HRTFKernels, and an interpolation factor x: 0 -> 1, returns an
+ // interpolated HRTFKernel.
+ static nsReturnRef<HRTFKernel> createInterpolatedKernel(HRTFKernel* kernel1,
+ HRTFKernel* kernel2,
+ float x);
+
+ FFTBlock* fftFrame() { return m_fftFrame.get(); }
+
+ size_t fftSize() const { return m_fftFrame->FFTSize(); }
+ float frameDelay() const { return m_frameDelay; }
+
+ float sampleRate() const { return m_sampleRate; }
+ double nyquist() const { return 0.5 * sampleRate(); }
+
+ size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t amount = aMallocSizeOf(this);
+ amount += m_fftFrame->SizeOfIncludingThis(aMallocSizeOf);
+ return amount;
+ }
+
+ private:
+ HRTFKernel(const HRTFKernel& other) = delete;
+ void operator=(const HRTFKernel& other) = delete;
+
+ // Note: this is destructive on the passed in |impulseResponse|.
+ HRTFKernel(float* impulseResponse, size_t fftSize, float sampleRate);
+
+ HRTFKernel(UniquePtr<FFTBlock> fftFrame, float frameDelay, float sampleRate)
+ : m_fftFrame(std::move(fftFrame)),
+ m_frameDelay(frameDelay),
+ m_sampleRate(sampleRate) {}
+
+ UniquePtr<FFTBlock> m_fftFrame;
+ float m_frameDelay;
+ float m_sampleRate;
+};
+
+typedef nsTArray<nsAutoRef<HRTFKernel> > HRTFKernelList;
+
+} // namespace WebCore
+
+template <>
+class nsAutoRefTraits<WebCore::HRTFKernel>
+ : public nsPointerRefTraits<WebCore::HRTFKernel> {
+ public:
+ static void Release(WebCore::HRTFKernel* ptr) { delete (ptr); }
+};
+
+namespace WebCore {
+
+inline nsReturnRef<HRTFKernel> HRTFKernel::create(float* impulseResponse,
+ size_t length,
+ float sampleRate) {
+ return nsReturnRef<HRTFKernel>(
+ new HRTFKernel(impulseResponse, length, sampleRate));
+}
+
+inline nsReturnRef<HRTFKernel> HRTFKernel::create(UniquePtr<FFTBlock> fftFrame,
+ float frameDelay,
+ float sampleRate) {
+ return nsReturnRef<HRTFKernel>(
+ new HRTFKernel(std::move(fftFrame), frameDelay, sampleRate));
+}
+
+} // namespace WebCore
+
+#endif // HRTFKernel_h
diff --git a/dom/media/webaudio/blink/HRTFPanner.cpp b/dom/media/webaudio/blink/HRTFPanner.cpp
new file mode 100644
index 0000000000..3d01fb5b39
--- /dev/null
+++ b/dom/media/webaudio/blink/HRTFPanner.cpp
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2010, Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "HRTFPanner.h"
+#include "HRTFDatabaseLoader.h"
+
+#include "FFTConvolver.h"
+#include "HRTFDatabase.h"
+#include "AudioBlock.h"
+
+using namespace mozilla;
+using dom::ChannelInterpretation;
+
+namespace WebCore {
+
+// The value of 2 milliseconds is larger than the largest delay which exists in
+// any HRTFKernel from the default HRTFDatabase (0.0136 seconds). We ASSERT the
+// delay values used in process() with this value.
+const float MaxDelayTimeSeconds = 0.002f;
+
+const int UninitializedAzimuth = -1;
+
+HRTFPanner::HRTFPanner(float sampleRate,
+ already_AddRefed<HRTFDatabaseLoader> databaseLoader)
+ : m_databaseLoader(databaseLoader),
+ m_sampleRate(sampleRate),
+ m_crossfadeSelection(CrossfadeSelection1),
+ m_azimuthIndex1(UninitializedAzimuth),
+ m_azimuthIndex2(UninitializedAzimuth)
+ // m_elevation1 and m_elevation2 are initialized in pan()
+ ,
+ m_crossfadeX(0),
+ m_crossfadeIncr(0),
+ m_convolverL1(HRTFElevation::fftSizeForSampleRate(sampleRate)),
+ m_convolverR1(m_convolverL1.fftSize()),
+ m_convolverL2(m_convolverL1.fftSize()),
+ m_convolverR2(m_convolverL1.fftSize()),
+ m_delayLine(MaxDelayTimeSeconds * sampleRate) {
+ MOZ_ASSERT(m_databaseLoader);
+ MOZ_COUNT_CTOR(HRTFPanner);
+}
+
+HRTFPanner::~HRTFPanner() { MOZ_COUNT_DTOR(HRTFPanner); }
+
+size_t HRTFPanner::sizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t amount = aMallocSizeOf(this);
+
+ // NB: m_databaseLoader can be shared, so it is not measured here
+ amount += m_convolverL1.sizeOfExcludingThis(aMallocSizeOf);
+ amount += m_convolverR1.sizeOfExcludingThis(aMallocSizeOf);
+ amount += m_convolverL2.sizeOfExcludingThis(aMallocSizeOf);
+ amount += m_convolverR2.sizeOfExcludingThis(aMallocSizeOf);
+ amount += m_delayLine.SizeOfExcludingThis(aMallocSizeOf);
+
+ return amount;
+}
+
+void HRTFPanner::reset() {
+ m_azimuthIndex1 = UninitializedAzimuth;
+ m_azimuthIndex2 = UninitializedAzimuth;
+ // m_elevation1 and m_elevation2 are initialized in pan()
+ m_crossfadeSelection = CrossfadeSelection1;
+ m_crossfadeX = 0.0f;
+ m_crossfadeIncr = 0.0f;
+ m_convolverL1.reset();
+ m_convolverR1.reset();
+ m_convolverL2.reset();
+ m_convolverR2.reset();
+ m_delayLine.Reset();
+}
+
+int HRTFPanner::calculateDesiredAzimuthIndexAndBlend(double azimuth,
+ double& azimuthBlend) {
+ // Convert the azimuth angle from the range -180 -> +180 into the range 0 ->
+ // 360. The azimuth index may then be calculated from this positive value.
+ if (azimuth < 0) azimuth += 360.0;
+
+ int numberOfAzimuths = HRTFDatabase::numberOfAzimuths();
+ const double angleBetweenAzimuths = 360.0 / numberOfAzimuths;
+
+ // Calculate the azimuth index and the blend (0 -> 1) for interpolation.
+ double desiredAzimuthIndexFloat = azimuth / angleBetweenAzimuths;
+ int desiredAzimuthIndex = static_cast<int>(desiredAzimuthIndexFloat);
+ azimuthBlend =
+ desiredAzimuthIndexFloat - static_cast<double>(desiredAzimuthIndex);
+
+ // We don't immediately start using this azimuth index, but instead approach
+ // this index from the last index we rendered at. This minimizes the clicks
+ // and graininess for moving sources which occur otherwise.
+ desiredAzimuthIndex = std::max(0, desiredAzimuthIndex);
+ desiredAzimuthIndex = std::min(numberOfAzimuths - 1, desiredAzimuthIndex);
+ return desiredAzimuthIndex;
+}
+
+void HRTFPanner::pan(double desiredAzimuth, double elevation,
+ const AudioBlock* inputBus, AudioBlock* outputBus) {
+#ifdef DEBUG
+ unsigned numInputChannels = inputBus->IsNull() ? 0 : inputBus->ChannelCount();
+
+ MOZ_ASSERT(numInputChannels <= 2);
+ MOZ_ASSERT(inputBus->GetDuration() == WEBAUDIO_BLOCK_SIZE);
+#endif
+
+ bool isOutputGood = outputBus && outputBus->ChannelCount() == 2 &&
+ outputBus->GetDuration() == WEBAUDIO_BLOCK_SIZE;
+ MOZ_ASSERT(isOutputGood);
+
+ if (!isOutputGood) {
+ if (outputBus) outputBus->SetNull(outputBus->GetDuration());
+ return;
+ }
+
+ HRTFDatabase* database = m_databaseLoader->database();
+ if (!database) { // not yet loaded
+ outputBus->SetNull(outputBus->GetDuration());
+ return;
+ }
+
+ // IRCAM HRTF azimuths values from the loaded database is reversed from the
+ // panner's notion of azimuth.
+ double azimuth = -desiredAzimuth;
+
+ bool isAzimuthGood = azimuth >= -180.0 && azimuth <= 180.0;
+ MOZ_ASSERT(isAzimuthGood);
+ if (!isAzimuthGood) {
+ outputBus->SetNull(outputBus->GetDuration());
+ return;
+ }
+
+ // Normally, we'll just be dealing with mono sources.
+ // If we have a stereo input, implement stereo panning with left source
+ // processed by left HRTF, and right source by right HRTF.
+
+ // Get destination pointers.
+ float* destinationL =
+ static_cast<float*>(const_cast<void*>(outputBus->mChannelData[0]));
+ float* destinationR =
+ static_cast<float*>(const_cast<void*>(outputBus->mChannelData[1]));
+
+ double azimuthBlend;
+ int desiredAzimuthIndex =
+ calculateDesiredAzimuthIndexAndBlend(azimuth, azimuthBlend);
+
+ // Initially snap azimuth and elevation values to first values encountered.
+ if (m_azimuthIndex1 == UninitializedAzimuth) {
+ m_azimuthIndex1 = desiredAzimuthIndex;
+ m_elevation1 = elevation;
+ }
+ if (m_azimuthIndex2 == UninitializedAzimuth) {
+ m_azimuthIndex2 = desiredAzimuthIndex;
+ m_elevation2 = elevation;
+ }
+
+ // Cross-fade / transition over a period of around 45 milliseconds.
+ // This is an empirical value tuned to be a reasonable trade-off between
+ // smoothness and speed.
+ const double fadeFrames = sampleRate() <= 48000 ? 2048 : 4096;
+
+ // Check for azimuth and elevation changes, initiating a cross-fade if needed.
+ if (!m_crossfadeX && m_crossfadeSelection == CrossfadeSelection1) {
+ if (desiredAzimuthIndex != m_azimuthIndex1 || elevation != m_elevation1) {
+ // Cross-fade from 1 -> 2
+ m_crossfadeIncr = 1 / fadeFrames;
+ m_azimuthIndex2 = desiredAzimuthIndex;
+ m_elevation2 = elevation;
+ }
+ }
+ if (m_crossfadeX == 1 && m_crossfadeSelection == CrossfadeSelection2) {
+ if (desiredAzimuthIndex != m_azimuthIndex2 || elevation != m_elevation2) {
+ // Cross-fade from 2 -> 1
+ m_crossfadeIncr = -1 / fadeFrames;
+ m_azimuthIndex1 = desiredAzimuthIndex;
+ m_elevation1 = elevation;
+ }
+ }
+
+ // Get the HRTFKernels and interpolated delays.
+ HRTFKernel* kernelL1;
+ HRTFKernel* kernelR1;
+ HRTFKernel* kernelL2;
+ HRTFKernel* kernelR2;
+ double frameDelayL1;
+ double frameDelayR1;
+ double frameDelayL2;
+ double frameDelayR2;
+ database->getKernelsFromAzimuthElevation(azimuthBlend, m_azimuthIndex1,
+ m_elevation1, kernelL1, kernelR1,
+ frameDelayL1, frameDelayR1);
+ database->getKernelsFromAzimuthElevation(azimuthBlend, m_azimuthIndex2,
+ m_elevation2, kernelL2, kernelR2,
+ frameDelayL2, frameDelayR2);
+
+ bool areKernelsGood = kernelL1 && kernelR1 && kernelL2 && kernelR2;
+ MOZ_ASSERT(areKernelsGood);
+ if (!areKernelsGood) {
+ outputBus->SetNull(outputBus->GetDuration());
+ return;
+ }
+
+ MOZ_ASSERT(frameDelayL1 / sampleRate() < MaxDelayTimeSeconds &&
+ frameDelayR1 / sampleRate() < MaxDelayTimeSeconds);
+ MOZ_ASSERT(frameDelayL2 / sampleRate() < MaxDelayTimeSeconds &&
+ frameDelayR2 / sampleRate() < MaxDelayTimeSeconds);
+
+ // Crossfade inter-aural delays based on transitions.
+ float frameDelaysL[WEBAUDIO_BLOCK_SIZE];
+ float frameDelaysR[WEBAUDIO_BLOCK_SIZE];
+ {
+ float x = m_crossfadeX;
+ float incr = m_crossfadeIncr;
+ for (unsigned i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) {
+ frameDelaysL[i] = (1 - x) * frameDelayL1 + x * frameDelayL2;
+ frameDelaysR[i] = (1 - x) * frameDelayR1 + x * frameDelayR2;
+ x += incr;
+ }
+ }
+
+ // First run through delay lines for inter-aural time difference.
+ m_delayLine.Write(*inputBus);
+ // "Speakers" means a mono input is read into both outputs (with possibly
+ // different delays).
+ m_delayLine.ReadChannel(frameDelaysL, outputBus, 0,
+ ChannelInterpretation::Speakers);
+ m_delayLine.ReadChannel(frameDelaysR, outputBus, 1,
+ ChannelInterpretation::Speakers);
+ m_delayLine.NextBlock();
+
+ bool needsCrossfading = m_crossfadeIncr;
+
+ const float* convolutionDestinationL1;
+ const float* convolutionDestinationR1;
+ const float* convolutionDestinationL2;
+ const float* convolutionDestinationR2;
+
+ // Now do the convolutions.
+ // Note that we avoid doing convolutions on both sets of convolvers if we're
+ // not currently cross-fading.
+
+ if (m_crossfadeSelection == CrossfadeSelection1 || needsCrossfading) {
+ convolutionDestinationL1 =
+ m_convolverL1.process(kernelL1->fftFrame(), destinationL);
+ convolutionDestinationR1 =
+ m_convolverR1.process(kernelR1->fftFrame(), destinationR);
+ }
+
+ if (m_crossfadeSelection == CrossfadeSelection2 || needsCrossfading) {
+ convolutionDestinationL2 =
+ m_convolverL2.process(kernelL2->fftFrame(), destinationL);
+ convolutionDestinationR2 =
+ m_convolverR2.process(kernelR2->fftFrame(), destinationR);
+ }
+
+ if (needsCrossfading) {
+ // Apply linear cross-fade.
+ float x = m_crossfadeX;
+ float incr = m_crossfadeIncr;
+ for (unsigned i = 0; i < WEBAUDIO_BLOCK_SIZE; ++i) {
+ destinationL[i] = (1 - x) * convolutionDestinationL1[i] +
+ x * convolutionDestinationL2[i];
+ destinationR[i] = (1 - x) * convolutionDestinationR1[i] +
+ x * convolutionDestinationR2[i];
+ x += incr;
+ }
+ // Update cross-fade value from local.
+ m_crossfadeX = x;
+
+ if (m_crossfadeIncr > 0 && fabs(m_crossfadeX - 1) < m_crossfadeIncr) {
+ // We've fully made the crossfade transition from 1 -> 2.
+ m_crossfadeSelection = CrossfadeSelection2;
+ m_crossfadeX = 1;
+ m_crossfadeIncr = 0;
+ } else if (m_crossfadeIncr < 0 && fabs(m_crossfadeX) < -m_crossfadeIncr) {
+ // We've fully made the crossfade transition from 2 -> 1.
+ m_crossfadeSelection = CrossfadeSelection1;
+ m_crossfadeX = 0;
+ m_crossfadeIncr = 0;
+ }
+ } else {
+ const float* sourceL;
+ const float* sourceR;
+ if (m_crossfadeSelection == CrossfadeSelection1) {
+ sourceL = convolutionDestinationL1;
+ sourceR = convolutionDestinationR1;
+ } else {
+ sourceL = convolutionDestinationL2;
+ sourceR = convolutionDestinationR2;
+ }
+ PodCopy(destinationL, sourceL, WEBAUDIO_BLOCK_SIZE);
+ PodCopy(destinationR, sourceR, WEBAUDIO_BLOCK_SIZE);
+ }
+}
+
+int HRTFPanner::maxTailFrames() const {
+ // Although the ideal tail time would be the length of the impulse
+ // response, there is additional tail time from the approximations in the
+ // implementation. Because HRTFPanner is implemented with a DelayKernel
+ // and a FFTConvolver, the tailTime of the HRTFPanner is the sum of the
+ // tailTime of the DelayKernel and the tailTime of the FFTConvolver. The
+ // FFTs of the convolver are fftSize(), half of which is latency, but this
+ // is aligned with blocks and so is reduced by the one block which is
+ // processed immediately.
+ return m_delayLine.MaxDelayTicks() + m_convolverL1.fftSize() / 2 +
+ m_convolverL1.latencyFrames();
+}
+
+} // namespace WebCore
diff --git a/dom/media/webaudio/blink/HRTFPanner.h b/dom/media/webaudio/blink/HRTFPanner.h
new file mode 100644
index 0000000000..b30689557b
--- /dev/null
+++ b/dom/media/webaudio/blink/HRTFPanner.h
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2010, Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef HRTFPanner_h
+#define HRTFPanner_h
+
+#include "FFTConvolver.h"
+#include "DelayBuffer.h"
+#include "mozilla/MemoryReporting.h"
+
+namespace mozilla {
+class AudioBlock;
+} // namespace mozilla
+
+namespace WebCore {
+
+typedef nsTArray<float> AudioFloatArray;
+
+class HRTFDatabaseLoader;
+
+using mozilla::AudioBlock;
+
+class HRTFPanner {
+ public:
+ HRTFPanner(float sampleRate,
+ already_AddRefed<HRTFDatabaseLoader> databaseLoader);
+ ~HRTFPanner();
+
+ // chunk durations must be 128
+ void pan(double azimuth, double elevation, const AudioBlock* inputBus,
+ AudioBlock* outputBus);
+ void reset();
+
+ size_t fftSize() const { return m_convolverL1.fftSize(); }
+
+ float sampleRate() const { return m_sampleRate; }
+
+ int maxTailFrames() const;
+
+ size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ private:
+ // Given an azimuth angle in the range -180 -> +180, returns the corresponding
+ // azimuth index for the database, and azimuthBlend which is an interpolation
+ // value from 0 -> 1.
+ int calculateDesiredAzimuthIndexAndBlend(double azimuth,
+ double& azimuthBlend);
+
+ RefPtr<HRTFDatabaseLoader> m_databaseLoader;
+
+ float m_sampleRate;
+
+ // We maintain two sets of convolvers for smooth cross-faded interpolations
+ // when then azimuth and elevation are dynamically changing. When the azimuth
+ // and elevation are not changing, we simply process with one of the two sets.
+ // Initially we use CrossfadeSelection1 corresponding to m_convolverL1 and
+ // m_convolverR1. Whenever the azimuth or elevation changes, a crossfade is
+ // initiated to transition to the new position. So if we're currently
+ // processing with CrossfadeSelection1, then we transition to
+ // CrossfadeSelection2 (and vice versa). If we're in the middle of a
+ // transition, then we wait until it is complete before initiating a new
+ // transition.
+
+ // Selects either the convolver set (m_convolverL1, m_convolverR1) or
+ // (m_convolverL2, m_convolverR2).
+ enum CrossfadeSelection { CrossfadeSelection1, CrossfadeSelection2 };
+
+ CrossfadeSelection m_crossfadeSelection;
+
+ // azimuth/elevation for CrossfadeSelection1.
+ int m_azimuthIndex1;
+ double m_elevation1;
+
+ // azimuth/elevation for CrossfadeSelection2.
+ int m_azimuthIndex2;
+ double m_elevation2;
+
+ // A crossfade value 0 <= m_crossfadeX <= 1.
+ float m_crossfadeX;
+
+ // Per-sample-frame crossfade value increment.
+ float m_crossfadeIncr;
+
+ FFTConvolver m_convolverL1;
+ FFTConvolver m_convolverR1;
+ FFTConvolver m_convolverL2;
+ FFTConvolver m_convolverR2;
+
+ mozilla::DelayBuffer m_delayLine;
+
+ AudioFloatArray m_tempL1;
+ AudioFloatArray m_tempR1;
+ AudioFloatArray m_tempL2;
+ AudioFloatArray m_tempR2;
+};
+
+} // namespace WebCore
+
+#endif // HRTFPanner_h
diff --git a/dom/media/webaudio/blink/IIRFilter.cpp b/dom/media/webaudio/blink/IIRFilter.cpp
new file mode 100644
index 0000000000..5ea027e83a
--- /dev/null
+++ b/dom/media/webaudio/blink/IIRFilter.cpp
@@ -0,0 +1,181 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "IIRFilter.h"
+
+#include "DenormalDisabler.h"
+#include "fdlibm.h"
+#include "mozilla/FloatingPoint.h"
+
+#include <mozilla/Assertions.h>
+
+#include <complex>
+
+namespace blink {
+
+// The length of the memory buffers for the IIR filter. This MUST be a power of
+// two and must be greater than the possible length of the filter coefficients.
+const int kBufferLength = 32;
+static_assert(kBufferLength >= IIRFilter::kMaxOrder + 1,
+ "Internal IIR buffer length must be greater than maximum IIR "
+ "Filter order.");
+
+IIRFilter::IIRFilter(const AudioDoubleArray* feedforwardCoef,
+ const AudioDoubleArray* feedbackCoef)
+ : m_bufferIndex(0),
+ m_feedback(feedbackCoef),
+ m_feedforward(feedforwardCoef) {
+ m_xBuffer.SetLength(kBufferLength);
+ m_yBuffer.SetLength(kBufferLength);
+ reset();
+}
+
+IIRFilter::~IIRFilter() = default;
+
+void IIRFilter::reset() {
+ memset(m_xBuffer.Elements(), 0, m_xBuffer.Length() * sizeof(double));
+ memset(m_yBuffer.Elements(), 0, m_yBuffer.Length() * sizeof(double));
+}
+
+static std::complex<double> evaluatePolynomial(const double* coef,
+ std::complex<double> z,
+ int order) {
+ // Use Horner's method to evaluate the polynomial P(z) = sum(coef[k]*z^k, k,
+ // 0, order);
+ std::complex<double> result = 0;
+
+ for (int k = order; k >= 0; --k)
+ result = result * z + std::complex<double>(coef[k]);
+
+ return result;
+}
+
+void IIRFilter::process(const float* sourceP, float* destP,
+ size_t framesToProcess) {
+ // Compute
+ //
+ // y[n] = sum(b[k] * x[n - k], k = 0, M) - sum(a[k] * y[n - k], k = 1, N)
+ //
+ // where b[k] are the feedforward coefficients and a[k] are the feedback
+ // coefficients of the filter.
+
+ // This is a Direct Form I implementation of an IIR Filter. Should we
+ // consider doing a different implementation such as Transposed Direct Form
+ // II?
+ const double* feedback = m_feedback->Elements();
+ const double* feedforward = m_feedforward->Elements();
+
+ MOZ_ASSERT(feedback);
+ MOZ_ASSERT(feedforward);
+
+ // Sanity check to see if the feedback coefficients have been scaled
+ // appropriately. It must be EXACTLY 1!
+ MOZ_ASSERT(feedback[0] == 1);
+
+ int feedbackLength = m_feedback->Length();
+ int feedforwardLength = m_feedforward->Length();
+ int minLength = std::min(feedbackLength, feedforwardLength);
+
+ double* xBuffer = m_xBuffer.Elements();
+ double* yBuffer = m_yBuffer.Elements();
+
+ for (size_t n = 0; n < framesToProcess; ++n) {
+ // To help minimize roundoff, we compute using double's, even though the
+ // filter coefficients only have single precision values.
+ double yn = feedforward[0] * sourceP[n];
+
+ // Run both the feedforward and feedback terms together, when possible.
+ for (int k = 1; k < minLength; ++k) {
+ int n = (m_bufferIndex - k) & (kBufferLength - 1);
+ yn += feedforward[k] * xBuffer[n];
+ yn -= feedback[k] * yBuffer[n];
+ }
+
+ // Handle any remaining feedforward or feedback terms.
+ for (int k = minLength; k < feedforwardLength; ++k)
+ yn += feedforward[k] * xBuffer[(m_bufferIndex - k) & (kBufferLength - 1)];
+
+ for (int k = minLength; k < feedbackLength; ++k)
+ yn -= feedback[k] * yBuffer[(m_bufferIndex - k) & (kBufferLength - 1)];
+
+ // Save the current input and output values in the memory buffers for the
+ // next output.
+ m_xBuffer[m_bufferIndex] = sourceP[n];
+ m_yBuffer[m_bufferIndex] = yn;
+
+ m_bufferIndex = (m_bufferIndex + 1) & (kBufferLength - 1);
+
+ // Avoid introducing a stream of subnormals
+ destP[n] = WebCore::DenormalDisabler::flushDenormalFloatToZero(yn);
+ MOZ_ASSERT(destP[n] == 0.0 || std::fabs(destP[n]) > FLT_MIN ||
+ std::isnan(destP[n]),
+ "output should not be subnormal, but can be NaN");
+ }
+}
+
+void IIRFilter::getFrequencyResponse(int nFrequencies, const float* frequency,
+ float* magResponse, float* phaseResponse) {
+ // Evaluate the z-transform of the filter at the given normalized frequencies
+ // from 0 to 1. (One corresponds to the Nyquist frequency.)
+ //
+ // The z-tranform of the filter is
+ //
+ // H(z) = sum(b[k]*z^(-k), k, 0, M) / sum(a[k]*z^(-k), k, 0, N);
+ //
+ // The desired frequency response is H(exp(j*omega)), where omega is in
+ // [0, 1).
+ //
+ // Let P(x) = sum(c[k]*x^k, k, 0, P) be a polynomial of order P. Then each of
+ // the sums in H(z) is equivalent to evaluating a polynomial at the point 1/z.
+
+ for (int k = 0; k < nFrequencies; ++k) {
+ // zRecip = 1/z = exp(-j*frequency)
+ double omega = -M_PI * frequency[k];
+ std::complex<double> zRecip =
+ std::complex<double>(fdlibm_cos(omega), fdlibm_sin(omega));
+
+ std::complex<double> numerator = evaluatePolynomial(
+ m_feedforward->Elements(), zRecip, m_feedforward->Length() - 1);
+ std::complex<double> denominator = evaluatePolynomial(
+ m_feedback->Elements(), zRecip, m_feedback->Length() - 1);
+ // Strangely enough, using complex division:
+ // e.g. Complex response = numerator / denominator;
+ // fails on our test machines, yielding infinities and NaNs, so we do
+ // things the long way here.
+ double n = norm(denominator);
+ double r = (real(numerator) * real(denominator) +
+ imag(numerator) * imag(denominator)) /
+ n;
+ double i = (imag(numerator) * real(denominator) -
+ real(numerator) * imag(denominator)) /
+ n;
+ std::complex<double> response = std::complex<double>(r, i);
+
+ magResponse[k] =
+ static_cast<float>(fdlibm_hypot(real(response), imag(response)));
+ phaseResponse[k] =
+ static_cast<float>(fdlibm_atan2(imag(response), real(response)));
+ }
+}
+
+bool IIRFilter::buffersAreZero() {
+ double* xBuffer = m_xBuffer.Elements();
+ double* yBuffer = m_yBuffer.Elements();
+
+ for (size_t k = 0; k < m_feedforward->Length(); ++k) {
+ if (xBuffer[(m_bufferIndex - k) & (kBufferLength - 1)] != 0.0) {
+ return false;
+ }
+ }
+
+ for (size_t k = 0; k < m_feedback->Length(); ++k) {
+ if (fabs(yBuffer[(m_bufferIndex - k) & (kBufferLength - 1)]) >= FLT_MIN) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // namespace blink
diff --git a/dom/media/webaudio/blink/IIRFilter.h b/dom/media/webaudio/blink/IIRFilter.h
new file mode 100644
index 0000000000..87522add06
--- /dev/null
+++ b/dom/media/webaudio/blink/IIRFilter.h
@@ -0,0 +1,62 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef IIRFilter_h
+#define IIRFilter_h
+
+#include "nsTArray.h"
+
+typedef nsTArray<double> AudioDoubleArray;
+
+namespace blink {
+
+class IIRFilter final {
+ public:
+ // The maximum IIR filter order. This also limits the number of feedforward
+ // coefficients. The maximum number of coefficients is 20 according to the
+ // spec.
+ const static size_t kMaxOrder = 19;
+ IIRFilter(const AudioDoubleArray* feedforwardCoef,
+ const AudioDoubleArray* feedbackCoef);
+ ~IIRFilter();
+
+ void process(const float* sourceP, float* destP, size_t framesToProcess);
+
+ void reset();
+
+ void getFrequencyResponse(int nFrequencies, const float* frequency,
+ float* magResponse, float* phaseResponse);
+
+ bool buffersAreZero();
+
+ private:
+ // Filter memory
+ //
+ // For simplicity, we assume |m_xBuffer| and |m_yBuffer| have the same length,
+ // and the length is a power of two. Since the number of coefficients has a
+ // fixed upper length, the size of xBuffer and yBuffer is fixed. |m_xBuffer|
+ // holds the old input values and |m_yBuffer| holds the old output values
+ // needed to compute the new output value.
+ //
+ // m_yBuffer[m_bufferIndex] holds the most recent output value, say, y[n].
+ // Then m_yBuffer[m_bufferIndex - k] is y[n - k]. Similarly for m_xBuffer.
+ //
+ // To minimize roundoff, these arrays are double's instead of floats.
+ AudioDoubleArray m_xBuffer;
+ AudioDoubleArray m_yBuffer;
+
+ // Index into the xBuffer and yBuffer arrays where the most current x and y
+ // values should be stored. xBuffer[bufferIndex] corresponds to x[n], the
+ // current x input value and yBuffer[bufferIndex] is where y[n], the current
+ // output value.
+ int m_bufferIndex;
+
+ // Coefficients of the IIR filter.
+ const AudioDoubleArray* m_feedback;
+ const AudioDoubleArray* m_feedforward;
+};
+
+} // namespace blink
+
+#endif // IIRFilter_h
diff --git a/dom/media/webaudio/blink/IRC_Composite_C_R0195-incl.cpp b/dom/media/webaudio/blink/IRC_Composite_C_R0195-incl.cpp
new file mode 100644
index 0000000000..37d731d3a2
--- /dev/null
+++ b/dom/media/webaudio/blink/IRC_Composite_C_R0195-incl.cpp
@@ -0,0 +1,4571 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * The sample data in the arrays here was derived for Webkit by Chris Rogers
+ * through averaging of impulse responses from the IRCAM Listen HRTF Database.
+ * The responses here are half the length of the Listen responses. This
+ * sample data has been granted to the Public Domain.
+ *
+ * This file is intended to be included in compilation of a single
+ * implementation file.
+ *
+ * Each elevation (p) contains impulse responses at a varying number of
+ * equally spaced azimuths for the left ear, ordered clockwise from in front
+ * the listener.
+ */
+
+#include "mozilla/ArrayUtils.h"
+
+using mozilla::ArrayLength;
+
+const int16_t irc_composite_c_r0195_p315[][256] =
+ {/* IRC_Composite_C_R0195_T000_P315.wav */
+ {-37, 37, -38, 39, -39, 40, -41, 42, -42, 43, -43,
+ 44, -44, 44, -44, 44, -43, 42, -39, 36, -31, 23,
+ -10, -10, 5, -655, -410, -552, -353, -474, -1525, -758, 656,
+ -263, 70, 1414, -1528, -731, -1811, 1646, 1312, -1501, -407, 8893,
+ -1543, 7696, 8084, 2629, -2452, -234, 3799, 1676, 177, -1077, -474,
+ -1325, 3527, 985, 265, -884, 373, 971, 1024, -412, 507, -173,
+ 259, -799, 635, -628, 507, -344, 394, -359, 178, -276, 349,
+ -201, 137, -249, 377, -311, 263, -404, 284, -244, 173, -243,
+ 330, -320, 112, -150, 164, -142, 174, -300, 158, -197, 13,
+ -141, 85, -190, 64, -122, 41, -122, 60, -195, 125, -163,
+ 10, -67, -6, -122, 77, -133, 26, -71, -42, -156, 48,
+ -152, -12, -89, -120, -104, -37, -154, -57, -139, -80, -165,
+ -95, -242, -81, -146, -111, -178, -109, -208, -48, -178, -131,
+ -163, -68, -169, -94, -190, -139, -190, -118, -204, -160, -220,
+ -140, -204, -171, -238, -126, -203, -114, -209, -138, -177, -124,
+ -184, -130, -175, -170, -185, -180, -231, -189, -233, -210, -236,
+ -245, -288, -208, -329, -246, -274, -199, -273, -189, -267, -208,
+ -215, -199, -187, -209, -206, -210, -123, -197, -156, -173, -142,
+ -97, -123, -97, -107, -73, -84, -39, -50, -66, -11, -50,
+ -12, -51, 8, -27, 19, -48, -9, -18, 5, -42, -15,
+ -35, -31, -27, -27, -64, -33, -54, -1, -98, -47, -56,
+ -7, -76, -47, -70, -42, -54, -65, -76, -43, -57, -9,
+ -61, -39, -58, 33, -39, 3, -34, 20, -19, 4, -71,
+ 61, -22, 10},
+ /* IRC_Composite_C_R0195_T015_P315.wav */
+ {81, -82, 83, -84, 84, -85, 86, -87, 87, -87, 87,
+ -87, 87, -86, 84, -82, 78, -72, 63, -49, 23, 33,
+ -344, -481, -563, -443, -265, -1527, -713, 251, -1453, 939, 2510,
+ -1221, -1282, -1307, 806, 585, -990, -82, 9029, -4621, 9096, 11230,
+ 4611, -3051, 400, 2105, 749, 1644, 165, -1556, -1521, 3009, 1430,
+ 723, -902, 933, 187, 28, 480, 951, -214, 122, -730, -95,
+ -137, 573, -593, 558, -692, -62, 28, 16, -505, 426, -283,
+ 227, -320, 210, -374, 303, -435, 127, -128, 76, -349, 106,
+ -364, 139, -348, 184, -425, -36, -441, 91, -413, 93, -444,
+ 33, -257, -74, -414, 93, -379, 19, -327, -43, -366, 79,
+ -293, 20, -199, -76, -207, 149, -239, 68, -247, 66, -219,
+ 61, -232, 38, -212, 36, -209, -27, -209, -58, -227, -6,
+ -309, -12, -225, -60, -199, -10, -277, 19, -207, -69, -211,
+ -30, -261, -24, -233, -102, -217, -131, -247, -144, -229, -111,
+ -256, -104, -254, -130, -241, -66, -249, -88, -223, -144, -199,
+ -148, -229, -101, -242, -189, -240, -163, -308, -147, -285, -217,
+ -239, -224, -291, -173, -269, -220, -209, -208, -240, -180, -212,
+ -201, -217, -169, -184, -174, -178, -146, -209, -108, -186, -96,
+ -108, -90, -120, -44, -101, -49, -39, -22, -44, 1, -58,
+ 3, -19, -21, -8, -6, -22, -9, 18, -15, -7, -23,
+ -12, -24, -11, -43, -33, -33, -31, -57, -5, -64, -22,
+ -45, -16, -87, -8, -40, -45, -57, -14, -53, -5, -47,
+ -21, -45, -8, -33, 13, -16, 3, -29, 6, -18, 23,
+ -31, 24, -41},
+ /* IRC_Composite_C_R0195_T030_P315.wav */
+ {9, -9, 8, -8, 8, -8, 8, -7, 7, -6, 5,
+ -4, 3, -2, 0, 2, -5, 9, -14, 23, -127, -380,
+ -922, -742, -548, 257, -2362, 972, -1653, 1079, 1193, 1855, -423,
+ -3573, -1945, 3974, 274, -2796, 11656, -988, 5085, 9362, 10293, -750,
+ -3105, -1864, 2111, 2283, -318, -221, -1158, -110, 2923, 410, -981,
+ 900, 122, -303, 833, 532, -152, 20, -462, 17, -48, 336,
+ 235, 63, -647, 577, -362, -19, -259, 102, 168, -103, -338,
+ -333, 24, -9, -458, 287, -470, -273, -216, 46, -484, 13,
+ -220, -143, -392, -290, -340, -198, -301, -237, -241, -176, -489,
+ -201, -271, -252, -335, -60, -395, -225, -412, -191, -198, -164,
+ -259, -64, -380, -75, -287, -9, -159, -126, -100, -116, -155,
+ -96, -113, -23, -100, -117, -115, -164, -180, 8, -173, -57,
+ -208, -89, -129, -73, -188, -102, -184, -93, -204, -81, -217,
+ -128, -180, -66, -279, -127, -244, -206, -146, -178, -190, -135,
+ -228, -154, -205, -100, -196, -143, -185, -140, -235, -135, -209,
+ -144, -158, -250, -202, -234, -227, -157, -214, -201, -260, -180,
+ -244, -184, -234, -151, -192, -198, -223, -199, -199, -202, -189,
+ -164, -233, -143, -220, -175, -186, -107, -156, -84, -181, -106,
+ -116, -72, -63, -29, -76, -11, -83, 15, -65, 30, -2,
+ -4, -41, 4, -49, 38, -44, 57, -2, 9, -61, 11,
+ -29, 35, -27, 2, -50, -23, -17, 1, -57, 2, -26,
+ -14, -67, -39, -9, -30, -30, -21, -44, -39, -75, -12,
+ -27, -13, -32, 13, -22, -16, 5, -2, 23, -25, 48,
+ -24, -32, -30},
+ /* IRC_Composite_C_R0195_T045_P315.wav */
+ {52, -54, 57, -59, 62, -64, 67, -71, 74, -78, 82,
+ -86, 91, -95, 100, -104, 107, -105, 80, -502, -332, -993,
+ -162, -1907, 1505, -2021, -314, 756, -842, 5057, -1148, -2263, -3462,
+ 3639, -1435, 4503, -272, 9367, 494, 8006, 14353, 933, -5033, -2453,
+ 2315, 1073, 2918, -1236, -3010, 2416, 371, 1956, -531, -1294, -177,
+ 691, 1104, -312, 402, -1300, 693, -586, 315, -26, 538, -594,
+ 294, -267, -119, -580, 378, 28, 261, -1010, 385, -342, 8,
+ -224, 217, -399, 43, -603, -1, -295, -252, 167, -340, -419,
+ -328, -348, -86, -497, -57, -294, -381, -499, -201, -454, -232,
+ -518, -17, -717, -310, -542, -84, -458, -96, -313, -154, -496,
+ -112, -226, -165, -242, -163, -234, -306, -270, -70, -238, -108,
+ -139, -101, -251, -218, -175, -17, -215, -23, -205, -109, -221,
+ -12, -131, -27, -205, -45, -264, -158, -151, -4, -169, -113,
+ -251, -102, -247, -100, -151, -158, -212, -170, -190, -217, -220,
+ -105, -182, -191, -216, -210, -208, -188, -144, -93, -288, -132,
+ -248, -124, -281, -112, -217, -92, -321, -80, -343, -94, -222,
+ -100, -231, -229, -216, -139, -209, -164, -138, -158, -204, -180,
+ -171, -145, -125, -68, -150, -139, -133, -97, -43, -133, -83,
+ -49, -109, -53, -101, -20, -84, 23, -64, -10, -116, 38,
+ -50, 15, -23, -2, -3, -27, -28, 3, 14, 36, -13,
+ -21, -18, -38, 28, -26, 9, -12, -28, -46, -29, -8,
+ 8, 18, -21, -12, -47, 16, -23, -21, -22, -28, -21,
+ -46, -32, 37, -14, -1, -31, 11, -4, -17, 51, -42,
+ 23, -30, 55},
+ /* IRC_Composite_C_R0195_T060_P315.wav */
+ {-9, 10, -10, 10, -11, 11, -11, 12, -12, 13, -13,
+ 14, -14, 14, -15, 15, -14, 14, -237, -853, -211, -839,
+ -537, -1171, 1035, -1099, 1039, 294, -1596, 6549, -2739, -2660, -4050,
+ 4749, 2134, 7195, 4024, 6882, -1377, 13010, 8996, -6905, -3319, -3088,
+ 4606, 2892, 2461, -4423, -1310, 1787, 1273, 1672, -1868, -79, 1190,
+ 70, -141, -131, 222, -677, 570, -820, 675, -811, 128, -382,
+ 165, -353, -183, -560, 68, -440, -382, -163, -67, -467, -152,
+ -570, 177, -472, -46, -374, -58, -324, -179, -380, -114, -308,
+ -223, -231, -266, -228, -188, -382, -93, -468, -172, -439, -277,
+ -467, -233, -484, -158, -431, -177, -375, -153, -360, -208, -377,
+ -90, -359, -252, -321, -261, -288, -236, -352, -117, -397, -126,
+ -318, -103, -229, -203, -302, -132, -152, -113, -159, -188, -103,
+ -160, -141, -121, -237, -158, -186, -215, -126, -180, -203, -129,
+ -258, -131, -232, -90, -192, -139, -222, -58, -238, -81, -214,
+ -111, -210, -119, -232, -97, -257, -79, -254, -124, -244, -169,
+ -224, -170, -218, -187, -194, -208, -142, -212, -136, -216, -224,
+ -142, -210, -181, -238, -144, -243, -81, -233, -107, -193, -82,
+ -124, -113, -114, -86, -61, -72, -88, -74, -42, -62, -68,
+ -57, -28, -20, -32, -42, -4, -55, -25, 11, -34, -17,
+ -21, 12, -38, -17, -4, -21, -19, -51, -19, -40, -1,
+ -58, -23, -65, -46, -5, -23, -26, -24, -22, 36, -42,
+ 4, -20, 73, -50, 19, -61, 15, -46, 1, -36, -37,
+ -6, -9, 13, -34, 18, -10, 58, -13, 38, 32, 29,
+ 4, 16, -6},
+ /* IRC_Composite_C_R0195_T075_P315.wav */
+ {-13, 13, -14, 14, -14, 14, -14, 14, -14, 13, -13,
+ 11, -9, 5, 2, -16, 50, -741, 214, -932, 242, -1432,
+ -236, -662, 1347, -571, -1196, 4105, -1805, 3633, -3512, -1059, -3340,
+ 6144, 8904, 2949, -3056, 13761, 4639, 6581, 1016, -7994, -537, 2069,
+ 9498, -3772, -2314, -3272, 3945, 434, 437, -1200, -83, 923, 138,
+ 258, 258, -7, 455, -141, 284, -478, 398, -1090, 528, -789,
+ -29, -665, -287, -554, -73, -808, -317, -229, -409, -754, -201,
+ -562, 103, -767, -233, -393, 90, -725, -54, -341, -112, -375,
+ -320, -304, -39, -501, -232, -150, -220, -432, -164, -401, -81,
+ -462, -293, -278, -139, -468, -172, -335, -180, -318, -233, -226,
+ -103, -361, -214, -194, -168, -399, -129, -254, -92, -285, -156,
+ -134, -28, -353, -136, -218, -68, -245, -164, -257, -128, -294,
+ -188, -259, -264, -283, -216, -266, -192, -249, -243, -221, -193,
+ -297, -159, -234, -247, -176, -224, -169, -180, -199, -220, -117,
+ -192, -112, -197, -146, -176, -92, -190, -108, -218, -108, -210,
+ -116, -237, -105, -215, -104, -222, -112, -224, -93, -273, -105,
+ -266, -128, -293, -88, -283, -37, -279, -49, -211, -28, -227,
+ 20, -197, 2, -189, 32, -205, 58, -176, 59, -144, 57,
+ -167, 132, -119, 93, -104, 104, -78, 87, -109, 101, -75,
+ 67, -98, 89, -87, 61, -85, 56, -78, 56, -103, 66,
+ -107, 43, -101, 53, -122, 26, -89, 31, -77, 5, -71,
+ 25, -79, 9, -93, 21, -74, 23, -83, 51, -92, 20,
+ -72, 56, -59, 56, -36, 38, 19, 39, 4, 62, -17,
+ 56, -23, 68},
+ /* IRC_Composite_C_R0195_T090_P315.wav */
+ {87, -92, 97, -103, 109, -116, 123, -131, 140, -150, 162,
+ -174, 189, -205, 224, -244, 266, -286, 51, -859, 535, -1364,
+ 139, -1195, 1589, -390, 911, -1048, 4305, 473, -5108, 558, 766,
+ 2725, 10227, -95, 2100, 7357, 10939, 4034, -9033, 1273, -876, 7923,
+ 448, -413, -5710, 1509, 967, 2067, -1395, -1318, 853, 624, -242,
+ 51, 284, 401, 299, 341, -114, 602, -538, 101, -460, -168,
+ -515, -276, -591, -395, -537, -566, -206, -633, -470, -595, -421,
+ -387, -497, -528, -554, -277, -415, -451, -399, -376, -198, -441,
+ -287, -406, -190, -420, -196, -316, -218, -371, -230, -316, -251,
+ -337, -304, -180, -217, -314, -203, -175, -197, -285, -220, -238,
+ -109, -305, -167, -234, -146, -316, -92, -192, -70, -236, -136,
+ -80, -80, -227, -175, -115, -77, -157, -162, -216, -61, -249,
+ -168, -273, -182, -316, -207, -288, -178, -282, -287, -286, -206,
+ -333, -257, -356, -226, -357, -253, -372, -144, -341, -201, -306,
+ -120, -225, -145, -256, -69, -151, -103, -191, -78, -159, -72,
+ -231, -86, -209, -102, -254, -75, -225, -131, -246, -122, -189,
+ -129, -238, -123, -178, -85, -185, -80, -172, -78, -165, -42,
+ -131, -62, -158, -35, -137, -15, -123, -23, -84, 14, -68,
+ 23, -29, 8, 12, -7, 21, 17, -7, 6, -12, 18,
+ -60, 33, -67, 51, -69, 41, -34, 41, -45, 33, -37,
+ 31, -56, 11, -29, -2, -76, 4, -43, 17, -73, -1,
+ -63, 19, -62, -23, -71, -23, -50, -30, -62, 24, -59,
+ 23, -75, 63, -53, 35, -32, 38, -9, 16, 4, 53,
+ -2, 34, -15},
+ /* IRC_Composite_C_R0195_T105_P315.wav */
+ {-4, 4, -4, 4, -4, 4, -4, 4, -5, 5, -5,
+ 6, -7, 9, -11, 17, -28, 67, 21, -444, -141, -224,
+ -503, -492, 56, 581, 659, -254, 970, 3243, -1085, -4435, 2584,
+ 328, 6386, 5564, -809, 5990, 6762, 10583, -5626, -2592, 332, 5216,
+ 4379, -2326, -2369, -2841, 2364, 794, 414, -2336, 841, 434, 185,
+ -245, 467, 54, 226, 66, 291, 109, -14, -250, -37, -434,
+ -55, -468, -301, -501, -528, -390, -260, -361, -561, -460, -370,
+ -387, -354, -670, -414, -508, -325, -671, -240, -470, -287, -539,
+ -267, -403, -309, -523, -407, -423, -444, -437, -347, -265, -394,
+ -345, -296, -231, -221, -203, -113, -208, -173, -209, -71, -127,
+ -120, -168, -119, -150, -164, -160, -171, -155, -129, -132, -133,
+ -114, -84, -152, -145, -150, -50, -140, -100, -199, -121, -188,
+ -145, -209, -221, -240, -266, -216, -273, -254, -311, -241, -309,
+ -303, -306, -288, -304, -324, -325, -280, -286, -254, -285, -213,
+ -282, -199, -235, -168, -198, -151, -152, -157, -138, -157, -155,
+ -189, -176, -212, -179, -192, -145, -184, -171, -174, -110, -150,
+ -113, -170, -84, -115, -76, -135, -71, -161, -62, -153, -49,
+ -144, -73, -126, -80, -100, -82, -63, -82, -57, -74, 14,
+ -40, -10, -30, 25, -21, 31, -22, 45, -26, 43, -15,
+ 48, -18, 20, -19, 34, -32, 26, -30, 33, -39, 33,
+ -38, 25, -62, 21, -45, -12, -65, -6, -49, -13, -61,
+ 8, -71, 9, -80, 15, -87, 37, -62, 49, -58, 33,
+ -30, 20, -31, -30, -19, -18, -22, 12, -19, 18, -18,
+ 62, -6, 19},
+ /* IRC_Composite_C_R0195_T120_P315.wav */
+ {-8, 8, -9, 10, -10, 11, -12, 13, -14, 15, -17,
+ 18, -20, 22, -24, 27, -30, 33, -36, -6, -353, -241,
+ 59, -307, -656, 81, 435, 931, -462, 1098, 2228, 1185, -5297,
+ 2389, 1039, 4366, 5054, 563, 5052, 5065, 10308, -3912, -1912, -658,
+ 6384, 3010, -2420, -2503, -2288, 1665, 1300, -465, -1526, 865, 689,
+ -244, 192, 664, -256, -73, 265, -57, -312, -7, -322, -127,
+ -354, -230, -494, -275, -378, -331, -253, -267, -433, -173, -259,
+ -320, -294, -344, -567, -293, -505, -543, -515, -293, -398, -306,
+ -402, -421, -360, -360, -466, -547, -514, -519, -327, -326, -417,
+ -471, -219, -314, -177, -232, -161, -243, -135, -249, -154, -136,
+ -89, -210, -64, -203, -123, -169, -90, -247, -100, -141, -68,
+ -108, -41, -101, -97, -95, -145, -67, -56, -122, -193, -178,
+ -180, -188, -181, -281, -271, -252, -234, -263, -302, -242, -343,
+ -214, -391, -204, -407, -189, -457, -208, -383, -206, -352, -186,
+ -336, -183, -268, -158, -243, -145, -229, -99, -205, -110, -223,
+ -83, -256, -120, -241, -124, -258, -103, -238, -73, -205, -48,
+ -194, -12, -168, -14, -161, -15, -200, -2, -184, -29, -168,
+ -39, -203, -35, -143, -59, -138, -57, -120, -39, -78, -38,
+ -71, 4, -85, 25, -41, 42, -65, 60, -35, 71, -28,
+ 56, -41, 66, -43, 40, -26, -6, -23, -11, -1, -28,
+ 3, -16, -8, -25, -4, -23, -19, -21, -43, 6, -38,
+ -12, -49, 4, -21, -1, -32, 9, -8, 22, -23, -1,
+ -11, -14, -32, -12, -40, -9, -31, 23, -39, 25, -13,
+ 20, -8, 9},
+ /* IRC_Composite_C_R0195_T135_P315.wav */
+ {-23, 24, -24, 24, -24, 24, -24, 24, -24, 24, -24,
+ 24, -24, 23, -23, 22, -21, 19, -17, 12, 2, -321,
+ 142, -352, -116, -71, -511, 212, 1241, -690, 1132, 1447, 1818,
+ -2325, -1459, 2842, 2322, 3950, -401, 7294, 1799, 9821, 384, -3014,
+ -458, 4980, 4067, -3265, -1817, -2557, 1536, 1050, 141, -1408, 467,
+ 945, 477, -318, 841, -225, -162, -191, 333, -541, -289, -847,
+ 48, -224, -228, -453, -42, -332, -101, -393, -171, -405, -103,
+ -444, -70, -591, -149, -459, -69, -570, -117, -584, -95, -396,
+ -42, -496, -210, -651, -204, -618, -333, -581, -255, -540, -313,
+ -471, -202, -369, -230, -385, -202, -278, -178, -259, -149, -205,
+ -123, -195, -100, -150, -73, -188, -126, -225, -124, -193, -92,
+ -182, -65, -172, -74, -168, -103, -186, -57, -178, -89, -280,
+ -140, -220, -97, -251, -226, -268, -204, -268, -271, -286, -293,
+ -290, -360, -269, -322, -274, -307, -261, -331, -275, -290, -250,
+ -261, -307, -242, -239, -199, -225, -211, -141, -180, -148, -189,
+ -147, -163, -178, -155, -195, -114, -193, -96, -201, -69, -171,
+ -54, -158, -66, -130, -90, -112, -83, -108, -98, -108, -90,
+ -89, -94, -103, -90, -117, -87, -105, -51, -97, -64, -70,
+ -56, -23, -79, 7, -88, 34, -80, 43, -77, 63, -50,
+ 59, -68, 43, -51, 41, -54, 60, -92, 29, -102, 48,
+ -85, 28, -79, 9, -67, 13, -66, 37, -57, 38, -64,
+ 47, -47, 53, -38, 54, -42, 32, -33, 27, -15, 13,
+ -24, -26, -34, -6, -41, 23, -23, 1, -31, 23, -1,
+ 22, -48, -14},
+ /* IRC_Composite_C_R0195_T150_P315.wav */
+ {-14, 14, -14, 14, -14, 14, -14, 14, -14, 14, -15,
+ 15, -15, 15, -15, 15, -15, 15, -15, 15, -14, 13,
+ -9, -299, 105, -109, -149, -174, -101, 123, 928, 331, 409,
+ 1091, 1957, -1219, -1800, 2020, 2641, 4225, -365, 6765, 1276, 7468,
+ 2095, -1421, -1941, 2847, 4183, -2838, -2013, -2585, 1301, 1369, 1048,
+ -1018, 299, 712, 620, -358, 419, -160, -391, -365, 85, -244,
+ -278, -735, 101, -159, -46, -323, -112, -300, -73, -375, -177,
+ -408, -241, -349, -232, -359, -171, -320, -131, -293, -2, -318,
+ -62, -331, -78, -385, -166, -449, -212, -441, -266, -408, -251,
+ -382, -246, -414, -254, -434, -274, -407, -251, -444, -252, -399,
+ -189, -349, -176, -289, -151, -238, -106, -181, -45, -201, -17,
+ -170, -8, -151, 12, -150, -68, -196, -148, -187, -145, -232,
+ -247, -286, -284, -266, -250, -295, -275, -287, -281, -302, -308,
+ -288, -299, -267, -330, -280, -301, -267, -269, -255, -300, -276,
+ -267, -240, -267, -229, -279, -186, -258, -159, -229, -176, -170,
+ -148, -137, -189, -140, -182, -128, -193, -123, -144, -95, -99,
+ -87, -75, -90, -47, -89, -71, -115, -103, -102, -112, -110,
+ -131, -87, -120, -99, -104, -130, -68, -115, -50, -116, -33,
+ -115, -23, -91, -20, -77, -32, -46, -36, -20, -19, -1,
+ -1, -16, 2, -30, -4, -10, 2, -12, -25, -22, -46,
+ -39, -40, -29, -51, -40, -76, -30, -47, -17, -48, -11,
+ -52, 8, -41, 31, -26, 41, 1, 54, -33, 53, -2,
+ 35, -28, 34, -23, 25, -31, 33, -25, -2, -7, 10,
+ -26, -60, -40},
+ /* IRC_Composite_C_R0195_T165_P315.wav */
+ {-17, 17, -18, 19, -19, 20, -21, 21, -22, 23, -24,
+ 25, -26, 27, -29, 30, -32, 33, -35, 37, -39, 42,
+ -45, 48, -52, 67, -40, 152, 75, -119, -31, 837, 302,
+ 348, 1063, 1535, 471, -104, -785, 1551, 1039, 5803, 545, 3679,
+ 3043, 5986, 50, -1878, 1830, -1451, 2642, -2294, -1114, -2432, 2146,
+ 1209, -135, -693, 285, 141, 308, -50, -77, -237, 196, -409,
+ 80, -447, -119, -467, -15, -415, -52, -429, -63, -216, -215,
+ -423, -211, -250, -99, -343, -232, -231, -78, -380, -66, -225,
+ 10, -172, -61, -149, -128, -331, -281, -369, -348, -420, -336,
+ -400, -303, -402, -320, -360, -377, -402, -338, -363, -356, -382,
+ -392, -353, -371, -352, -344, -280, -280, -250, -233, -166, -154,
+ -160, -136, -102, -71, -93, -69, -118, -111, -164, -179, -223,
+ -230, -272, -255, -285, -312, -330, -270, -347, -306, -400, -338,
+ -356, -324, -353, -315, -327, -313, -301, -321, -289, -309, -258,
+ -317, -227, -285, -196, -252, -189, -237, -155, -182, -95, -165,
+ -128, -123, -87, -127, -113, -150, -118, -122, -92, -114, -79,
+ -90, -44, -70, -19, -82, -37, -76, -88, -84, -86, -94,
+ -117, -109, -92, -108, -75, -136, -74, -119, -62, -120, -51,
+ -126, -38, -97, -20, -110, -3, -105, 29, -72, 41, -66,
+ 43, -69, 58, -54, 55, -37, 38, -45, 18, -47, 10,
+ -73, -1, -104, 15, -120, 0, -103, 12, -106, 19, -73,
+ 20, -41, 43, -9, 42, -5, 48, -4, 46, 3, 29,
+ -8, 24, 11, 13, -8, 11, 55, 13, 32, 16, 31,
+ 9, -51, -42},
+ /* IRC_Composite_C_R0195_T180_P315.wav */
+ {24, -25, 26, -27, 28, -30, 31, -33, 34, -36, 38, -40,
+ 42, -45, 47, -50, 53, -57, 61, -65, 70, -76, 82, -90,
+ 100, -116, 158, -120, 110, -47, 143, -200, 288, -83, 509, 566,
+ 678, 584, 1717, -752, -218, -645, 3620, 1547, 2553, 2618, 2850, 3932,
+ 1154, 2478, -3553, 2655, -1012, 521, -1943, -220, 418, 465, 358, -445,
+ 32, 572, 410, 189, 111, 242, -109, 240, 99, 120, -178, -44,
+ -146, -12, -251, -169, -110, -64, -56, -37, -65, 38, 137, -75,
+ -42, -159, -127, -127, -11, -39, -7, -121, -97, -173, -207, -322,
+ -357, -395, -311, -334, -329, -369, -303, -285, -259, -281, -323, -274,
+ -364, -264, -415, -314, -459, -317, -463, -346, -399, -298, -323, -239,
+ -217, -185, -167, -162, -146, -115, -142, -125, -194, -139, -196, -150,
+ -224, -208, -224, -201, -206, -219, -254, -241, -239, -289, -306, -358,
+ -320, -362, -312, -398, -353, -429, -362, -421, -352, -421, -332, -364,
+ -272, -351, -188, -256, -150, -222, -109, -165, -57, -142, -72, -111,
+ -54, -116, -66, -134, -87, -132, -39, -149, -55, -144, -3, -111,
+ -37, -108, -57, -98, -89, -106, -109, -135, -117, -147, -114, -137,
+ -99, -140, -85, -140, -56, -119, -68, -119, -57, -94, -53, -95,
+ -61, -85, -42, -76, -22, -61, -39, -37, -44, -27, -31, -14,
+ -37, -23, -16, -39, 0, -62, -11, -92, 2, -64, -27, -61,
+ -33, -39, -34, -35, -26, -27, 5, -20, 14, -33, 30, -28,
+ 12, -27, 25, -7, 7, 29, -15, 24, -9, 67, 32, 52,
+ -7, 19, -10, -34},
+ /* IRC_Composite_C_R0195_T195_P315.wav */
+ {-3, 3, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4,
+ -5, 5, -5, 5, -5, 5, -6, 6, -6, 7, -7, 8,
+ -10, 16, -53, -21, 71, -52, 123, 66, -67, 138, 16, 149,
+ 471, 552, 491, 879, 834, 185, -864, 886, 1624, 2747, 1377, 2335,
+ 2675, 2476, 2941, -204, -424, -902, 1080, -594, -652, -972, -15, 737,
+ 158, 15, -103, 673, 669, 117, 134, 496, 369, 6, 174, 211,
+ 204, 39, 24, 7, -16, 8, -20, 185, 63, 80, 52, 87,
+ -18, -50, -107, -83, -58, -90, -149, -191, -215, -179, -280, -228,
+ -426, -325, -403, -260, -377, -309, -454, -294, -358, -263, -350, -288,
+ -312, -298, -376, -336, -392, -351, -440, -368, -423, -300, -356, -221,
+ -309, -156, -237, -133, -233, -159, -209, -147, -233, -211, -247, -210,
+ -236, -217, -261, -238, -270, -208, -309, -227, -327, -269, -364, -336,
+ -391, -375, -368, -412, -407, -388, -417, -354, -411, -328, -386, -273,
+ -319, -198, -244, -162, -205, -101, -178, -129, -177, -104, -161, -77,
+ -156, -48, -139, -49, -168, -54, -132, -40, -129, -49, -113, -43,
+ -97, -66, -144, -76, -174, -72, -177, -115, -186, -100, -175, -109,
+ -149, -106, -124, -101, -86, -76, -56, -81, -27, -107, 3, -87,
+ -27, -99, -52, -77, -57, -80, -68, -65, -62, -77, -17, -71,
+ -16, -67, -12, -47, -11, -33, -14, -33, -31, -4, -26, -4,
+ -79, 6, -73, -6, -74, -4, -61, -7, -62, 10, -50, 33,
+ -27, 0, -4, 51, 21, 29, 16, 43, 17, 21, 38, 16,
+ 70, -26, 47, -23},
+ /* IRC_Composite_C_R0195_T210_P315.wav */
+ {6, -6, 6, -6, 6, -6, 6, -6, 6, -6, 6, -7,
+ 7, -7, 7, -7, 7, -7, 8, -8, 8, -8, 9, -9,
+ 9, -9, 9, -9, 9, -6, -16, -81, -27, -64, -11, -116,
+ 37, -7, 308, 224, 339, 624, 595, 47, -450, 456, 1108, 1630,
+ 1528, 1730, 2147, 2381, 2098, 1287, -731, 283, 215, 213, -551, -518,
+ 308, 203, 729, -194, 652, 579, 759, 441, 763, 571, 528, 360,
+ 442, 343, 364, 229, 431, 125, 293, 81, 275, 88, 238, 45,
+ 215, 155, 198, 82, 61, 12, 5, -51, -58, -119, -156, -200,
+ -208, -255, -244, -261, -244, -232, -236, -252, -249, -226, -258, -256,
+ -283, -248, -281, -296, -308, -266, -304, -256, -333, -220, -281, -198,
+ -262, -186, -220, -164, -184, -148, -198, -168, -234, -150, -270, -190,
+ -317, -221, -358, -246, -358, -268, -356, -277, -357, -276, -375, -272,
+ -357, -257, -384, -239, -370, -230, -374, -258, -374, -262, -363, -281,
+ -344, -263, -317, -224, -292, -194, -243, -172, -216, -149, -196, -138,
+ -151, -112, -138, -121, -140, -121, -158, -136, -149, -135, -124, -140,
+ -124, -136, -125, -160, -126, -165, -139, -151, -144, -151, -125, -132,
+ -116, -99, -80, -92, -84, -64, -97, -68, -84, -76, -83, -93,
+ -75, -65, -93, -87, -89, -70, -103, -55, -102, -68, -112, -68,
+ -70, -61, -55, -62, -34, -41, -24, -58, -48, -63, -63, -52,
+ -80, -56, -81, -54, -68, -55, -48, -48, -24, -69, -21, -60,
+ -6, -21, -10, -12, -6, 14, -8, 19, 1, 57, 22, 38,
+ 15, 27, 32, 33},
+ /* IRC_Composite_C_R0195_T225_P315.wav */
+ {-7, 7, -8, 8, -8, 8, -8, 8, -8, 8, -8, 8,
+ -8, 8, -8, 8, -8, 8, -8, 9, -9, 9, -9, 9,
+ -9, 9, -9, 9, -6, 33, 28, 58, 51, 52, 71, 102,
+ 56, 94, 137, 218, 326, 455, 483, 632, 249, 92, 428, 904,
+ 1132, 1053, 1398, 1725, 1892, 1761, 996, 419, -35, 393, 230, -426,
+ 83, 187, 561, 443, 405, 624, 567, 778, 322, 714, 220, 555,
+ 133, 272, 57, 175, 33, -14, 10, 34, 86, 73, 113, 24,
+ -32, -47, -92, -107, -159, -198, -236, -286, -255, -307, -290, -357,
+ -323, -402, -335, -423, -329, -409, -335, -383, -288, -312, -274, -328,
+ -286, -276, -227, -303, -252, -323, -243, -382, -269, -374, -281, -400,
+ -300, -362, -289, -363, -292, -337, -287, -341, -275, -329, -275, -353,
+ -270, -350, -272, -365, -283, -346, -284, -356, -310, -344, -298, -351,
+ -295, -331, -289, -350, -307, -364, -293, -334, -305, -333, -280, -301,
+ -259, -287, -211, -253, -202, -233, -185, -196, -178, -176, -188, -164,
+ -153, -127, -135, -117, -145, -120, -134, -127, -143, -116, -126, -80,
+ -113, -89, -119, -102, -89, -86, -92, -78, -108, -66, -94, -63,
+ -86, -58, -73, -44, -52, -66, -55, -76, -50, -92, -33, -91,
+ -19, -106, -44, -97, -52, -76, -62, -65, -52, -53, -56, -64,
+ -38, -57, -27, -67, -32, -61, -26, -56, -44, -40, -36, -27,
+ -22, -5, -13, -6, 12, -15, 8, -1, 18, 1, -3, 6,
+ -13, 24, 6, 33, 17, 44, 11, 63, 31, 57, 47, 73,
+ 42, 34, 14, 5},
+ /* IRC_Composite_C_R0195_T240_P315.wav */
+ {0, 0, 0, -1, 1, -1, 1, -1, 1, -1, 1, -1,
+ 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1,
+ 1, -1, 2, 58, 78, 16, 22, 51, 114, 84, 41, 96,
+ 70, 113, 111, 119, 154, 190, 332, 340, 437, 381, 393, 351,
+ 291, 690, 869, 1203, 1249, 1386, 1308, 1507, 1378, 1185, 595, 424,
+ 365, 626, 420, 458, 252, 382, 637, 446, 207, 255, 488, 305,
+ 321, 49, 221, 53, 63, 79, -40, 108, -82, 101, -101, -13,
+ -203, -152, -221, -220, -235, -289, -282, -281, -296, -224, -344, -241,
+ -368, -252, -366, -268, -359, -308, -318, -288, -298, -317, -264, -285,
+ -261, -303, -269, -298, -270, -320, -331, -336, -351, -327, -351, -348,
+ -331, -347, -276, -351, -246, -372, -243, -370, -280, -365, -300, -410,
+ -295, -413, -330, -447, -345, -440, -333, -430, -333, -415, -326, -403,
+ -318, -379, -309, -372, -303, -357, -286, -300, -274, -299, -260, -274,
+ -222, -258, -207, -249, -178, -228, -179, -202, -181, -164, -194, -132,
+ -181, -100, -149, -105, -124, -85, -117, -92, -115, -104, -129, -96,
+ -119, -72, -137, -65, -104, -51, -87, -75, -80, -56, -63, -66,
+ -73, -81, -74, -88, -75, -87, -74, -68, -80, -63, -77, -50,
+ -74, -36, -76, -42, -77, -46, -59, -47, -48, -54, -32, -62,
+ -21, -50, -24, -43, -33, -23, -40, -10, -7, -13, -16, -4,
+ 2, 7, 17, -10, 37, -7, 35, 11, 27, -4, 32, -1,
+ 15, -21, -1, -28, 7, -9, 16, 21, 30, 38, 52, 37,
+ 48, 38, 34, 3},
+ /* IRC_Composite_C_R0195_T255_P315.wav */
+ {-2, 2, -2, 2, -2, 2, -2, 2, -2, 2, -2, 2,
+ -2, 2, -2, 2, -2, 2, -2, 2, -2, 2, -2, 2,
+ -3, 3, -3, 3, -3, 4, -6, 74, 75, 79, 61, 128,
+ 72, 79, 70, 94, 189, 182, 258, 204, 285, 366, 406, 480,
+ 409, 503, 603, 776, 875, 1073, 1366, 1703, 1601, 1585, 1344, 1253,
+ 1054, 708, 209, 184, 245, 471, 220, 82, 328, 234, 143, -43,
+ 229, 63, 146, 52, 46, 143, -26, 108, -162, -81, -337, -290,
+ -357, -257, -274, -266, -282, -295, -253, -242, -177, -217, -241, -242,
+ -180, -187, -261, -282, -286, -285, -278, -318, -290, -350, -241, -329,
+ -216, -356, -250, -344, -253, -301, -333, -307, -381, -310, -406, -310,
+ -398, -327, -389, -328, -383, -321, -397, -364, -416, -386, -447, -400,
+ -469, -437, -477, -423, -470, -435, -472, -439, -441, -374, -396, -361,
+ -359, -340, -300, -318, -278, -293, -236, -257, -203, -214, -171, -197,
+ -164, -178, -159, -160, -150, -162, -167, -187, -166, -177, -176, -193,
+ -156, -159, -125, -174, -110, -158, -92, -129, -96, -113, -107, -94,
+ -103, -101, -73, -98, -45, -93, -27, -108, -14, -122, -36, -134,
+ -52, -131, -66, -121, -83, -114, -73, -107, -51, -91, -32, -79,
+ 2, -74, 2, -68, 4, -68, 20, -45, 9, -63, -6, -50,
+ 8, -15, -2, -22, -9, 6, 4, 25, -11, 21, -12, 18,
+ -6, -1, -16, -16, -15, -8, 7, 8, -10, 14, -14, 21,
+ -9, 18, -34, -11, 0, 0, 38, 9, 28, 24, 33, 41,
+ 32, 56, 10, 45},
+ /* IRC_Composite_C_R0195_T270_P315.wav */
+ {5, -5, 5, -5, 5, -5, 5, -6, 6, -6, 6, -6,
+ 6, -6, 7, -7, 7, -7, 8, -8, 8, -9, 9, -10,
+ 10, -11, 12, -13, 14, -16, 19, -24, 35, -112, -87, -172,
+ -147, -119, -127, -152, -113, -103, -89, -113, -68, -116, -46, 22,
+ 42, 151, 321, 392, 548, 700, 898, 1192, 1367, 1289, 1342, 1432,
+ 1348, 1134, 1055, 823, 716, 745, 988, 819, 628, 418, 468, 438,
+ 382, 415, 357, 462, 457, 454, 331, 457, 286, 349, 199, 216,
+ 110, 272, 103, 141, 96, 107, 157, 39, 151, 90, 143, 187,
+ 135, 145, 132, 119, 90, 29, 67, 9, 33, -99, -4, -78,
+ -31, -119, -118, -125, -133, -39, -116, 6, -53, -9, -2, -28,
+ -8, -68, -109, -175, -231, -243, -320, -296, -388, -312, -439, -323,
+ -431, -308, -403, -341, -379, -320, -333, -304, -317, -322, -321, -302,
+ -340, -277, -367, -233, -348, -216, -344, -197, -304, -195, -255, -199,
+ -226, -173, -179, -167, -168, -175, -177, -169, -197, -168, -238, -156,
+ -231, -167, -240, -168, -219, -192, -218, -202, -205, -205, -186, -197,
+ -157, -172, -143, -163, -152, -143, -150, -149, -177, -148, -159, -149,
+ -150, -163, -154, -183, -160, -189, -132, -190, -124, -156, -96, -125,
+ -92, -99, -99, -74, -85, -61, -83, -67, -68, -74, -62, -91,
+ -54, -91, -46, -108, -22, -86, -15, -70, -6, -63, -3, -43,
+ -35, -76, -52, -91, -99, -89, -83, -72, -67, -70, -42, -53,
+ -31, -59, -32, -49, -39, -55, -65, -43, -43, -36, -50, -25,
+ -39, -34, -26, -31},
+ /* IRC_Composite_C_R0195_T285_P315.wav */
+ {0, 0, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1,
+ -1, 1, -1, 1, -1, 1, -1, 1, -1, 2, -2, 2,
+ -2, 3, -3, 0, -53, -54, -56, -85, -47, -114, -135, -181,
+ -153, -193, -267, -244, -55, -96, 1, 65, -104, -149, -27, 77,
+ 318, 544, 620, 776, 1096, 1388, 1457, 1232, 925, 941, 734, 587,
+ 918, 901, 616, 762, 669, 803, 637, 587, 517, 641, 585, 459,
+ 366, 454, 473, 472, 377, 381, 369, 477, 434, 303, 311, 258,
+ 424, 217, 330, 203, 348, 239, 206, 163, 147, 190, 93, 131,
+ 3, 88, 29, 61, 25, -4, 30, -29, -32, -79, -68, -48,
+ -73, -75, -119, -13, -30, 17, -48, 5, -35, 5, -39, -80,
+ -142, -105, -167, -166, -226, -200, -281, -233, -317, -276, -334, -290,
+ -353, -338, -366, -316, -342, -297, -343, -306, -273, -252, -266, -278,
+ -281, -276, -239, -265, -258, -270, -226, -250, -191, -215, -181, -217,
+ -149, -202, -131, -195, -119, -225, -148, -263, -146, -220, -148, -221,
+ -170, -215, -156, -215, -188, -262, -202, -269, -194, -235, -202, -241,
+ -188, -209, -153, -204, -154, -203, -167, -217, -151, -214, -172, -225,
+ -158, -182, -145, -170, -150, -164, -121, -136, -87, -134, -90, -102,
+ -41, -87, -63, -73, -53, -73, -60, -73, -67, -76, -98, -68,
+ -64, -72, -69, -72, -35, -71, -32, -55, -37, -56, -47, -39,
+ -69, -58, -90, -53, -99, -57, -83, -34, -50, -55, -58, -67,
+ -57, -63, -70, -62, -74, -53, -70, -30, -49, -56, -37, -35,
+ -21, -10, -19, -29},
+ /* IRC_Composite_C_R0195_T300_P315.wav */
+ {2, -2, 2, -2, 2, -2, 2, -2, 2, -2, 2, -2,
+ 2, -2, 2, -2, 2, -2, 2, -2, 2, -2, 2, -2,
+ 2, -2, 2, -3, 4, -10, -133, -133, -170, -242, -249, -433,
+ -299, -139, -159, 192, 133, -70, 22, -303, 18, -19, 635, 218,
+ 706, 1507, 1430, 1383, 2387, 1796, 1075, 392, 368, 909, 611, 309,
+ -168, 353, 2, 566, 420, 570, 464, 600, 533, 643, 640, 611,
+ 592, 536, 352, 641, 435, 676, 299, 466, 377, 487, 301, 349,
+ 330, 262, 326, 234, 228, 152, 89, 163, 56, 54, -10, 7,
+ -78, -45, -66, -85, -68, -104, -18, 4, -26, 41, 2, 31,
+ 0, 79, 5, 36, 26, 46, 69, 42, -19, -67, -82, -80,
+ -133, -166, -222, -198, -222, -228, -274, -302, -279, -261, -261, -282,
+ -253, -280, -237, -241, -240, -230, -249, -229, -231, -221, -230, -219,
+ -245, -228, -234, -188, -202, -201, -222, -167, -182, -137, -203, -165,
+ -172, -150, -180, -163, -208, -169, -194, -164, -229, -193, -245, -204,
+ -252, -223, -246, -236, -257, -266, -272, -274, -271, -280, -267, -251,
+ -243, -225, -207, -200, -174, -184, -165, -180, -160, -164, -160, -158,
+ -167, -156, -169, -151, -138, -125, -125, -108, -104, -80, -94, -71,
+ -82, -75, -85, -57, -68, -57, -49, -58, -52, -65, -51, -59,
+ -53, -74, -44, -57, -44, -43, -49, -55, -42, -57, -55, -65,
+ -60, -62, -53, -54, -78, -70, -64, -55, -66, -73, -66, -48,
+ -68, -87, -49, -57, -32, -77, -35, -66, -33, -71, -35, -70,
+ 15, -17, -11, -33},
+ /* IRC_Composite_C_R0195_T315_P315.wav */
+ {-3, 3, -4, 4, -4, 4, -4, 4, -4, 4, -4, 5,
+ -5, 5, -5, 5, -5, 5, -6, 6, -6, 6, -6, 6,
+ -6, 6, -5, 3, 2, -152, -177, -483, -235, -110, -653, -348,
+ -150, -38, 208, 331, 33, -385, -315, -27, 364, 811, -94, 2504,
+ 888, 2086, 3574, 3328, 531, 314, 789, 729, 404, -337, 56, 36,
+ 119, 405, 418, 656, 613, 503, 327, 557, 477, 459, 703, 343,
+ 212, 405, 546, 432, 317, 280, 459, 304, 364, 271, 334, 99,
+ 204, 199, 155, 38, 34, 17, 94, 27, 75, -28, 51, -48,
+ 69, -29, 44, -56, 45, -30, 20, 5, 41, -28, 41, 31,
+ 56, 13, 68, 19, 90, -37, 31, -65, 21, -87, -51, -142,
+ -103, -155, -98, -191, -137, -221, -116, -207, -139, -209, -136, -203,
+ -147, -207, -168, -209, -164, -226, -176, -243, -198, -258, -194, -267,
+ -197, -246, -196, -229, -191, -208, -190, -182, -197, -201, -171, -193,
+ -170, -221, -151, -220, -141, -245, -167, -251, -184, -255, -210, -246,
+ -238, -245, -223, -258, -247, -284, -254, -278, -249, -311, -255, -297,
+ -217, -263, -193, -236, -176, -198, -176, -178, -155, -172, -140, -181,
+ -132, -195, -91, -184, -92, -165, -90, -121, -68, -117, -88, -97,
+ -47, -73, -55, -70, -16, -46, -13, -61, -26, -38, -50, -48,
+ -41, -43, -40, -60, -49, -55, -58, -57, -47, -68, -57, -81,
+ -49, -74, -61, -74, -29, -70, -35, -79, -39, -87, -35, -94,
+ -49, -86, -55, -65, -49, -58, -43, -30, -32, -23, -33, -5,
+ -23, 19, -13, -17},
+ /* IRC_Composite_C_R0195_T330_P315.wav */
+ {-7, 7, -7, 7, -7, 7, -7, 7, -7, 7, -7, 7,
+ -7, 7, -7, 7, -7, 8, -8, 8, -8, 9, -9, 9,
+ -10, 11, -11, -55, -296, -386, -360, -162, -486, -689, -594, 323,
+ -279, 489, 253, -225, -799, -208, -290, 1653, -875, 1421, 3185, 831,
+ 3972, 4758, 3089, -835, 674, 981, 1685, -351, -114, -17, 248, 390,
+ 777, 359, 517, 535, 503, 290, 712, 58, 681, 346, 142, -203,
+ 198, 199, 331, -307, 34, 132, 139, 70, 114, 91, 13, 123,
+ 184, 191, 4, 23, 205, 163, 125, 72, 141, -40, 14, 124,
+ -28, 33, -51, 40, -5, 35, -58, 73, -52, -16, 56, 78,
+ -40, -37, 45, 32, 27, 55, -7, -15, -18, 29, -29, 22,
+ -117, 6, -57, -69, -79, -66, -162, -105, -133, -121, -164, -165,
+ -210, -152, -236, -167, -217, -215, -247, -204, -244, -201, -239, -194,
+ -273, -180, -249, -181, -223, -192, -224, -139, -212, -188, -198, -169,
+ -226, -134, -219, -153, -208, -148, -204, -149, -219, -168, -214, -162,
+ -245, -160, -257, -219, -229, -208, -279, -218, -292, -266, -282, -268,
+ -275, -227, -298, -216, -236, -164, -238, -151, -226, -142, -217, -147,
+ -203, -145, -177, -146, -138, -122, -131, -73, -151, -51, -110, -31,
+ -107, -6, -86, -9, -40, -10, -36, -1, -49, -9, -42, -8,
+ -74, 2, -73, 3, -80, -26, -79, 6, -86, -19, -76, -29,
+ -75, -32, -78, -38, -65, -46, -77, -46, -88, -55, -75, -74,
+ -82, -34, -77, -58, -74, -44, -61, -16, -21, -21, -1, -32,
+ 25, -5, -9, -4},
+ /* IRC_Composite_C_R0195_T345_P315.wav */
+ {-3, 3, -3, 3, -3, 3, -3, 3, -3, 3, -2, 2,
+ -2, 2, -1, 1, 0, 0, 1, -2, 3, -4, 6, -9,
+ 13, -23, -205, -456, -570, -178, -609, -516, -1049, -349, 515, 157,
+ -280, 1141, -1675, -135, -1096, 1502, 703, -567, 2409, 4580, 1615, 5569,
+ 6660, -925, 461, 21, 3064, -94, 275, -939, 176, 137, 1125, 718,
+ -145, 338, 911, 169, 330, 365, 552, 148, 369, -393, 179, -127,
+ 204, 80, -84, -339, 8, 81, -65, -288, 145, 50, -22, -24,
+ 61, 62, 45, -71, 194, 159, 54, -19, 226, -56, 24, 48,
+ 99, -31, -91, -8, 53, 24, -79, 12, 113, -69, -3, 43,
+ 52, -53, 25, 22, 42, -13, -5, -1, 64, -132, -15, -42,
+ -12, -176, -19, -103, -113, -146, -76, -99, -119, -177, -139, -96,
+ -193, -229, -107, -210, -181, -214, -106, -219, -211, -156, -157, -151,
+ -246, -153, -144, -151, -254, -173, -147, -195, -202, -155, -196, -154,
+ -199, -151, -187, -147, -192, -144, -157, -216, -170, -139, -200, -196,
+ -193, -158, -269, -172, -256, -190, -293, -226, -252, -229, -318, -238,
+ -250, -265, -306, -166, -254, -191, -300, -137, -229, -129, -286, -136,
+ -186, -156, -221, -108, -162, -109, -143, -76, -137, -51, -129, -13,
+ -99, -24, -56, 17, -62, -20, -41, 40, -86, 44, -52, 39,
+ -123, 42, -65, 39, -115, -16, -46, 0, -101, -28, -69, -10,
+ -115, -27, -101, 20, -128, 3, -110, 13, -133, -3, -101, -28,
+ -103, -3, -79, -10, -82, 10, -56, 27, -77, 25, -50, 57,
+ -33, 20, -49, 39}};
+
+const int16_t irc_composite_c_r0195_p330[][256] =
+ {/* IRC_Composite_C_R0195_T000_P330.wav */
+ {-59, 61, -63, 65, -67, 69, -71, 73, -76, 78, -81,
+ 83, -86, 89, -92, 95, -98, 100, -102, 103, -103, 99,
+ -90, 66, 27, -206, -447, -812, -1040, -532, -532, -221, -2282,
+ 2737, -1254, 634, -40, 577, -3131, -820, -289, 5598, -4456, 4514,
+ 11627, -957, 5804, 7393, 108, -5260, 1410, 3318, 1621, -1983, 2287,
+ 284, 1686, 1238, 2060, -1196, 399, 489, 1551, 51, 439, -136,
+ 620, 290, -51, 26, 548, -418, 535, -429, 119, -693, 256,
+ -317, 191, -412, 18, -258, -48, -331, 42, -245, -151, -84,
+ -244, -168, -112, -248, 56, -243, -130, -346, 3, -311, -140,
+ -201, -205, -208, -164, -257, -153, -216, -142, -212, -32, -240,
+ -169, -200, -80, -259, -56, -266, -49, -259, -153, -247, -131,
+ -186, -180, -157, -174, -170, -168, -133, -130, -144, -118, -156,
+ -112, -157, -115, -125, -52, -108, -95, -100, -113, -69, -143,
+ -89, -121, -105, -124, -163, -161, -185, -102, -218, -93, -213,
+ -136, -158, -144, -182, -122, -122, -161, -147, -153, -151, -116,
+ -162, -100, -155, -94, -176, -98, -138, -94, -127, -119, -122,
+ -158, -125, -151, -130, -178, -168, -144, -200, -186, -199, -181,
+ -157, -192, -201, -216, -176, -230, -170, -197, -219, -207, -194,
+ -203, -187, -195, -174, -204, -175, -205, -170, -207, -182, -206,
+ -156, -195, -149, -193, -165, -200, -125, -166, -120, -182, -142,
+ -113, -73, -137, -86, -97, -75, -88, -72, -83, -37, -82,
+ -24, -72, -40, -53, 24, -38, -23, -48, 32, 10, 7,
+ -19, 18, 7, 43, 7, 34, 32, 65, 53, 56, 21,
+ 94, 55, 42},
+ /* IRC_Composite_C_R0195_T015_P330.wav */
+ {-33, 32, -32, 31, -31, 30, -28, 27, -25, 22, -19,
+ 15, -10, 4, 5, -16, 31, -53, 84, -136, 240, -673,
+ 177, -174, -1030, -1321, -76, -1250, -372, 289, -2348, 2418, 1442,
+ -314, -2260, 351, -1076, 96, -4201, 12653, -2793, 350, 16253, 7416,
+ -1734, 373, 1945, -1722, 32, 2367, 1990, -2906, 2200, 2573, 955,
+ -397, 1580, -424, 684, 371, 1473, 401, 8, -1088, 1156, -110,
+ 164, -526, 361, -233, -337, -706, 160, -330, -24, -130, 132,
+ -810, 170, -491, 242, -434, 33, -622, 355, -505, -308, -68,
+ -61, -264, -71, -244, -294, -284, -179, -295, 41, -462, -220,
+ -369, -36, -507, -206, -424, -65, -416, -180, -392, -246, -316,
+ -59, -245, -204, -339, -224, -205, -78, -425, -107, -254, -66,
+ -399, -77, -350, 46, -239, -96, -256, -101, -268, -138, -142,
+ -190, -179, -136, -172, -84, -261, -40, -173, 25, -283, -32,
+ -282, 9, -234, -87, -198, -101, -238, -154, -207, -159, -166,
+ -106, -164, -136, -199, -109, -121, -73, -175, -65, -149, -73,
+ -162, -108, -135, -78, -99, -124, -126, -179, -91, -133, -84,
+ -188, -122, -167, -148, -155, -182, -183, -160, -158, -219, -207,
+ -224, -181, -161, -192, -206, -207, -174, -243, -140, -189, -173,
+ -194, -138, -203, -173, -192, -143, -155, -154, -216, -158, -170,
+ -138, -202, -110, -182, -132, -154, -83, -201, -104, -130, -44,
+ -129, -80, -139, -19, -72, -80, -88, -16, -110, -20, -46,
+ -35, -88, 15, -32, 6, -67, -9, -16, 52, -18, 27,
+ 27, 26, -2, 60, 51, 76, 28, 98, 40, 77, 33,
+ 94, 11, 62},
+ /* IRC_Composite_C_R0195_T030_P330.wav */
+ {15, -16, 18, -19, 21, -23, 26, -29, 32, -36,
+ 41, -47, 54, -62, 73, -86, 104, -126, 155, -102,
+ 601, -1085, -197, -1634, 218, -1892, 836, -1472, 627, 332,
+ 2248, 1679, -4388, -1076, 2778, -5228, 3611, 10385, -5976, 10952,
+ 11266, 9110, -4453, 651, -313, -354, 33, 2898, -224, -1072,
+ 2571, 1824, -115, 539, 940, -84, 401, 1263, 337, 3,
+ -171, -426, 685, -69, -27, -94, -916, -1001, 131, -752,
+ -458, -101, -74, -99, -305, -309, -166, -102, 238, -512,
+ 98, -429, -43, -482, 424, -183, -138, -249, -301, -240,
+ -13, -418, -203, -184, -296, -341, -269, -474, -99, -447,
+ -210, -601, -403, -560, -148, -418, -397, -430, -241, -495,
+ -140, -391, -94, -358, -170, -431, -28, -368, -72, -337,
+ -69, -376, -11, -267, -144, -348, -177, -200, -156, -317,
+ -161, -307, -97, -238, -69, -237, -166, -271, -82, -187,
+ -69, -237, -35, -248, -105, -213, -38, -207, -108, -284,
+ -91, -256, -64, -223, -92, -227, -52, -205, -48, -178,
+ -94, -174, -26, -226, -71, -202, -71, -184, -34, -207,
+ -88, -154, -79, -180, -41, -222, -49, -215, -56, -245,
+ -90, -251, -51, -260, -109, -246, -150, -229, -109, -239,
+ -125, -253, -146, -228, -85, -219, -106, -252, -82, -250,
+ -58, -194, -116, -232, -74, -193, -81, -184, -110, -195,
+ -65, -162, -92, -193, -65, -199, -29, -203, 0, -193,
+ 28, -151, -16, -159, -4, -109, -9, -90, -21, -99,
+ 36, -92, 27, -82, 30, -74, 71, -74, 64, -43,
+ 102, -28, 93, 18, 88, 22, 98, 29, 116, 21,
+ 126, -20, 133, -40, 143, -66},
+ /* IRC_Composite_C_R0195_T045_P330.wav */
+ {60, -60, 60, -60, 60, -60, 59, -57, 55, -51, 47,
+ -40, 30, -15, -9, 50, -138, 582, -538, -783, -136, 149,
+ -1756, -848, -253, -449, 1379, -797, 1611, 1075, 2352, -3846, -4473,
+ 1829, 1877, 4778, 13625, -4798, 10293, 11126, 4895, -5087, -2315, -1117,
+ -112, 3354, 2968, -606, -2642, 2599, 370, 2356, -155, 634, -118,
+ 50, -430, 371, -366, 83, 83, 846, -691, 615, -552, -124,
+ -1033, -101, -1482, 300, -868, 142, -849, -166, -356, 234, -620,
+ -26, -586, 234, -687, 38, -602, 184, -537, -8, -502, 109,
+ -408, 41, -516, 144, -611, 54, -513, -33, -440, -242, -381,
+ -176, -498, -276, -575, -140, -578, -79, -706, -148, -475, -195,
+ -498, -214, -378, -197, -385, -257, -315, -80, -443, -59, -376,
+ -102, -363, -123, -440, -39, -442, -136, -304, -144, -309, -84,
+ -271, -165, -242, -238, -200, -117, -273, -150, -258, -130, -283,
+ -92, -309, -42, -296, -47, -253, -76, -218, 8, -252, -78,
+ -168, -84, -181, -86, -207, -54, -197, -48, -239, -19, -220,
+ -19, -245, -20, -221, -34, -247, -31, -246, -93, -181, -96,
+ -211, -94, -210, -85, -235, -118, -230, -101, -229, -107, -245,
+ -109, -215, -112, -220, -113, -204, -100, -188, -108, -185, -89,
+ -182, -62, -197, -34, -195, -28, -194, -18, -182, -55, -157,
+ -81, -141, -90, -141, -91, -129, -74, -112, -85, -107, -64,
+ -103, -64, -86, -50, -93, -43, -62, -35, -57, -57, -38,
+ -24, -28, -39, -24, -20, -16, 22, 2, 35, 17, 56,
+ 35, 75, 47, 56, 59, 88, 45, 61, 91, 80, 67,
+ 62, 68, 59},
+ /* IRC_Composite_C_R0195_T060_P330.wav */
+ {-16, 18, -21, 24, -27, 30, -34, 39, -45, 51, -59,
+ 68, -79, 93, -109, 120, -488, -209, -843, -446, 581, -2538,
+ 150, -1048, 1674, -298, 921, -422, 4633, -1379, -3456, -5235, 5897,
+ -232, 13802, 4771, -1871, 13790, 9150, 509, -7321, -2062, 426, 4376,
+ 2825, -66, -4120, 2160, 1014, 1965, -182, 10, -66, 1012, -1092,
+ 605, -694, -103, -630, 876, -1446, 848, -616, 147, -237, 208,
+ -605, 430, -846, 156, -639, -33, -577, -169, -800, -107, -681,
+ -261, -491, -122, -613, -72, -646, -89, -523, -258, -835, -111,
+ -630, -192, -684, -120, -546, -114, -589, -198, -484, -123, -592,
+ -31, -441, -137, -356, -160, -262, -88, -337, -230, -206, -197,
+ -286, -158, -371, -90, -386, -164, -357, -120, -396, -123, -371,
+ -159, -394, -268, -385, -156, -411, -140, -387, -213, -351, -179,
+ -344, -165, -326, -182, -213, -154, -216, -130, -224, -133, -226,
+ -137, -212, -88, -294, -83, -283, -112, -241, -166, -252, -129,
+ -207, -171, -148, -189, -146, -134, -197, -75, -135, -114, -111,
+ -109, -99, -40, -137, -112, -65, -123, -140, -104, -208, -112,
+ -191, -189, -188, -164, -205, -119, -255, -149, -161, -150, -183,
+ -173, -162, -174, -126, -195, -97, -145, -155, -118, -129, -86,
+ -145, -98, -141, -61, -119, -118, -63, -133, -28, -155, 2,
+ -161, -3, -128, -39, -100, -78, -65, -82, -72, -98, -47,
+ -85, -70, -46, -75, -12, -101, 21, -108, 22, -100, 40,
+ -92, 45, -75, 34, -63, 31, -40, 44, -41, 58, -23,
+ 74, -14, 74, -19, 131, -21, 113, 30, 101, 49, 110,
+ 57, 108, 51},
+ /* IRC_Composite_C_R0195_T075_P330.wav */
+ {19, -20, 21, -23, 25, -26, 28, -31, 33, -36, 39,
+ -42, 45, -49, 49, -225, -118, -411, -201, -767, -125, -1198,
+ -1086, 1587, 699, -114, 366, 4108, -2207, 1045, -7292, 822, 4055,
+ 16836, -2188, 1091, 15052, 6204, 1571, -5363, -5126, 3112, 6714, 709,
+ -1466, -3132, 1975, 682, 2154, -1147, 580, -537, 152, -77, -200,
+ -771, 391, -354, -354, -457, 389, -921, 324, -902, 497, -455,
+ -10, -125, 532, -709, 91, -249, -171, -384, -149, -591, -267,
+ -649, -63, -714, -477, -400, -204, -742, -437, -554, -445, -510,
+ -517, -561, -379, -611, -347, -592, -417, -611, -301, -482, -385,
+ -503, -245, -302, -310, -258, -221, -225, -194, -239, -127, -248,
+ -118, -185, -116, -277, -78, -132, -151, -167, -94, -212, -171,
+ -287, -162, -301, -224, -259, -202, -317, -238, -263, -235, -226,
+ -328, -285, -227, -274, -225, -257, -270, -272, -203, -280, -198,
+ -229, -168, -195, -197, -135, -134, -188, -159, -143, -143, -184,
+ -122, -175, -115, -188, -85, -144, -140, -133, -147, -139, -167,
+ -127, -183, -140, -187, -127, -176, -160, -172, -135, -190, -179,
+ -159, -144, -201, -142, -175, -140, -206, -117, -148, -146, -179,
+ -121, -175, -129, -135, -92, -156, -126, -159, -68, -176, -108,
+ -129, -91, -133, -75, -91, -67, -116, -63, -52, -31, -122,
+ -11, -115, 3, -129, 15, -110, -22, -119, -11, -83, -62,
+ -34, -39, -48, -27, -43, -19, -79, 23, -48, -1, -86,
+ 40, -64, 16, -37, 23, -6, -6, 9, 18, 18, 23,
+ 24, 45, 17, 82, 40, 87, 28, 105, 40, 116, 77,
+ 81, 80, 85},
+ /* IRC_Composite_C_R0195_T090_P330.wav */
+ {6, -4, 3, -1, 0, 3, -5, 8, -11, 16,
+ -21, 29, -39, 57, -101, -332, 89, -835, -16, 8,
+ -1022, -629, -474, 1050, 924, -984, 2683, 3640, -2207, -5863,
+ 2261, -3476, 15168, 2953, -3266, 10727, 14390, 3174, -8455, -1966,
+ 1449, 7129, 3075, -3700, -2692, 1997, 423, 1234, -1087, 216,
+ -471, 1061, -1107, 458, -1402, 142, 319, -88, -425, 76,
+ -1043, 226, -230, 12, -536, -207, -663, 129, -455, 253,
+ -222, -284, -472, 86, -636, 265, -368, -261, -539, -160,
+ -407, 43, -778, -256, -683, -475, -586, -306, -628, -455,
+ -647, -525, -513, -416, -500, -430, -812, -419, -489, -274,
+ -452, -386, -597, -258, -363, -159, -236, -327, -241, -259,
+ -214, -139, -145, -206, -216, -123, -79, -29, -96, -65,
+ -217, -263, -177, -100, -202, -186, -272, -223, -240, -138,
+ -180, -221, -352, -224, -206, -195, -213, -254, -233, -244,
+ -227, -156, -224, -178, -249, -190, -199, -133, -154, -164,
+ -183, -191, -143, -185, -101, -190, -166, -201, -98, -150,
+ -133, -119, -165, -142, -178, -120, -115, -180, -149, -197,
+ -152, -187, -75, -218, -143, -264, -128, -205, -139, -221,
+ -148, -269, -159, -215, -126, -214, -123, -256, -105, -233,
+ -71, -203, -97, -215, -79, -182, -53, -120, -82, -122,
+ -49, -119, 20, -120, 24, -141, 6, -133, 24, -115,
+ 14, -116, -39, -71, -8, -71, 6, -93, -11, -108,
+ 40, -59, 43, -147, 22, -98, 28, -64, 17, -87,
+ -13, -67, 63, -36, 44, -53, 62, -77, 123, -52,
+ 145, -81, 82, -33, 141, -4, 116, 8, 98, 14,
+ 137, 56, 139, -7, 138, 4},
+ /* IRC_Composite_C_R0195_T105_P330.wav */
+ {61, -61, 61, -61, 62, -62, 62, -62, 63, -63, 63,
+ -64, 64, -65, 67, -301, 294, -741, -246, -201, 457, -1576,
+ -144, -73, 2001, -1275, 1763, 1153, 6179, -10039, 184, 1999, 2771,
+ 10917, -378, 522, 13686, 11704, -7376, -5609, 1748, 7382, 4547, -1426,
+ -5565, -24, 2068, 630, -441, -1191, 186, 450, -243, 220, -909,
+ -111, -78, 770, -727, 124, -899, -156, -79, 202, -685, -336,
+ -650, -314, -474, -53, -350, -261, -756, 191, -463, 128, -417,
+ -139, -484, -53, -226, -113, -375, -280, -491, -342, -457, -195,
+ -589, -428, -623, -328, -572, -393, -551, -458, -577, -399, -453,
+ -401, -526, -392, -496, -278, -442, -319, -518, -259, -377, -158,
+ -346, -171, -366, -187, -308, -53, -156, -44, -233, -111, -203,
+ -9, -156, -124, -286, -249, -248, -172, -147, -210, -273, -296,
+ -215, -151, -154, -166, -209, -170, -207, -76, -118, -174, -253,
+ -208, -154, -172, -145, -172, -242, -211, -169, -70, -176, -103,
+ -198, -93, -133, -36, -100, -118, -191, -149, -134, -127, -138,
+ -220, -229, -254, -179, -191, -192, -230, -211, -235, -161, -158,
+ -161, -230, -195, -206, -174, -165, -144, -216, -190, -173, -127,
+ -136, -156, -157, -179, -150, -128, -122, -138, -124, -152, -83,
+ -109, -30, -73, -66, -103, -55, -77, -32, -75, -113, -125,
+ -86, -47, -60, -39, -89, -29, -60, 20, -18, -21, -32,
+ -8, -50, 24, -43, -1, -69, -6, -57, 11, -53, 21,
+ -58, -2, -13, 35, -10, 30, 5, 44, 12, 60, 31,
+ 79, -4, 79, 46, 97, 47, 97, 36, 128, 43, 132,
+ 30, 81, 38},
+ /* IRC_Composite_C_R0195_T120_P330.wav */
+ {1, 4, -8, 14, -20, 27, -35, 44, -54, 66, -79,
+ 95, -114, 135, -159, 177, -87, 615, -830, 163, -909, 1304,
+ -1626, -325, 297, 1206, 69, -817, 4405, 1017, -2560, -616, -3770,
+ 4387, 14092, -5772, 3858, 10124, 9882, -2074, -2959, -3337, 7410, 7437,
+ -1780, -6636, -2212, 2639, 1700, -1078, -1371, -262, 1153, -566, 417,
+ -320, 27, -39, -111, 485, -441, -792, -288, -146, -108, -616,
+ -333, -464, -419, -367, -303, -262, -308, -516, -133, -270, -200,
+ -382, -324, -225, -347, -205, -250, -254, -201, -400, -249, -330,
+ -242, -364, -366, -273, -444, -377, -352, -336, -480, -405, -375,
+ -496, -436, -362, -461, -436, -395, -354, -445, -362, -274, -397,
+ -353, -222, -328, -290, -351, -239, -329, -117, -69, -109, -182,
+ -35, -73, -135, -239, -228, -363, -234, -203, -243, -333, -218,
+ -233, -231, -229, -116, -190, -172, -177, -158, -144, -144, -172,
+ -173, -215, -124, -180, -106, -203, -150, -185, -121, -123, -149,
+ -106, -93, -132, -84, -116, -33, -138, -72, -186, -81, -187,
+ -113, -205, -194, -255, -235, -238, -195, -263, -202, -264, -198,
+ -268, -158, -241, -175, -267, -183, -249, -160, -199, -167, -203,
+ -153, -184, -118, -137, -101, -164, -93, -143, -66, -128, -30,
+ -127, -42, -113, -31, -78, -23, -59, -78, -78, -48, -79,
+ -53, -70, -40, -106, -22, -46, -9, -51, -12, -37, -31,
+ -54, -23, -49, -43, -68, -43, -55, -39, -14, -29, -16,
+ -13, -8, 32, 0, 28, 5, 31, 33, 68, 33, 69,
+ 42, 71, 38, 89, 64, 82, 64, 81, 94, 57, 103,
+ 62, 51, 87},
+ /* IRC_Composite_C_R0195_T135_P330.wav */
+ {13, -11, 8, -5, 2, 2, -7, 12, -18, 26, -34,
+ 45, -58, 75, -96, 126, -173, 263, -756, 239, -382, 327,
+ -504, 374, -988, 304, -762, 1963, 32, 1243, 612, 772, 3408,
+ -6680, -2100, 10509, 4890, -1044, 6633, 2426, 10000, 1888, -3554, -3952,
+ 7154, 5846, -3202, -5792, -2133, 1379, 2382, -1044, -683, -846, 1056,
+ -24, 511, 18, 303, -710, -25, -39, -85, -861, -206, -321,
+ -34, -428, -245, -428, -30, -639, -166, -459, -198, -463, -354,
+ -339, -255, -329, -348, -224, -286, -255, -287, -204, -181, -232,
+ -210, -296, -199, -313, -262, -347, -268, -316, -307, -365, -310,
+ -360, -308, -316, -324, -335, -371, -355, -377, -361, -310, -401,
+ -283, -402, -244, -368, -238, -331, -274, -247, -165, -162, -178,
+ -185, -197, -205, -193, -250, -203, -288, -187, -284, -187, -262,
+ -183, -219, -206, -232, -196, -178, -177, -216, -242, -166, -214,
+ -147, -219, -161, -195, -148, -178, -168, -190, -170, -141, -148,
+ -164, -119, -116, -35, -121, -54, -95, -56, -120, -107, -112,
+ -149, -152, -172, -185, -195, -227, -175, -253, -211, -280, -223,
+ -250, -252, -243, -251, -235, -239, -213, -184, -218, -176, -225,
+ -139, -205, -123, -184, -114, -154, -113, -98, -100, -89, -89,
+ -91, -67, -61, -32, -65, -41, -68, -21, -70, -18, -87,
+ -13, -97, -10, -79, -5, -41, -42, -23, -67, 8, -74,
+ 15, -75, -43, -63, -41, -18, -81, -7, -69, 6, -49,
+ 2, -9, -7, 21, -8, 24, -11, 44, 32, 44, 38,
+ 36, 66, 51, 91, 52, 103, 44, 90, 55, 87, 41,
+ 60, 28, 49},
+ /* IRC_Composite_C_R0195_T150_P330.wav */
+ {15, -18, 20, -22, 25, -28, 31, -35, 38, -42, 47,
+ -52, 57, -63, 70, -76, 84, -91, 97, -95, 51, -395,
+ 348, -546, 384, -515, 230, -347, 78, 797, 314, 1274, 475,
+ 2177, 1227, -4216, -2348, 10076, 258, 3084, 3609, 6149, 5472, 3566,
+ -4049, -1019, 5212, 3739, -5321, -3637, -1580, 2270, 1066, -633, -1395,
+ -136, 1340, -32, 75, -243, 507, -628, 158, 108, -226, -702,
+ -291, -112, -126, -404, -330, -254, -212, -362, -151, -436, -14,
+ -424, -215, -583, -44, -508, -144, -379, -62, -360, -158, -295,
+ -27, -305, -69, -339, -66, -367, -167, -326, -112, -361, -177,
+ -468, -151, -402, -103, -407, -145, -397, -208, -387, -142, -366,
+ -207, -368, -232, -411, -224, -349, -159, -358, -152, -366, -101,
+ -313, -105, -307, -196, -327, -268, -341, -250, -319, -245, -350,
+ -228, -325, -151, -267, -108, -294, -109, -251, -84, -251, -104,
+ -207, -91, -253, -103, -244, -91, -252, -85, -272, -130, -311,
+ -129, -241, -94, -173, -90, -147, -80, -140, -35, -126, -79,
+ -186, -118, -197, -120, -210, -155, -272, -176, -269, -156, -268,
+ -151, -277, -170, -275, -153, -283, -181, -265, -186, -257, -194,
+ -217, -154, -209, -128, -189, -106, -201, -80, -156, -34, -154,
+ -17, -151, 12, -97, 27, -70, 9, -39, -39, -3, -46,
+ -29, -50, -61, -28, -93, -9, -89, -8, -103, 1, -74,
+ -1, -70, -9, -35, -44, -51, -51, -29, -31, -30, -13,
+ -35, 10, 8, 7, -4, 11, 13, 35, 12, 38, 37,
+ 49, 36, 56, 47, 69, 81, 52, 69, 15, 90, 11,
+ 52, -17, 56},
+ /* IRC_Composite_C_R0195_T165_P330.wav */
+ {-38, 38, -39, 39, -39, 40, -40, 41, -42, 43, -44,
+ 46, -47, 49, -52, 55, -59, 64, -70, 79, -90, 108,
+ -143, 381, -197, 374, -312, 378, -424, 566, -428, 1134, 331,
+ 1309, 501, 1274, 2077, -920, -3821, 4175, 5452, 953, 5098, 2533,
+ 5399, 3026, 943, -4554, 1848, 1742, -912, -4176, -875, -267, 1882,
+ -418, -1283, -863, 1021, 217, -28, -198, 445, -494, -277, -183,
+ 13, -551, -347, -206, -115, -218, -241, -334, -209, -134, -196,
+ -318, -365, -189, -242, -376, -315, -159, -285, -247, -217, -119,
+ -295, -192, -233, -63, -298, -72, -221, -78, -309, -142, -336,
+ -207, -384, -220, -389, -223, -371, -217, -392, -165, -378, -175,
+ -375, -156, -391, -200, -346, -196, -386, -169, -293, -171, -325,
+ -127, -312, -180, -413, -244, -413, -275, -437, -268, -434, -258,
+ -381, -194, -377, -183, -352, -126, -339, -126, -286, -92, -262,
+ -50, -229, -59, -226, -70, -231, -92, -226, -106, -251, -108,
+ -219, -79, -217, -73, -199, -48, -168, -54, -223, -113, -224,
+ -121, -228, -134, -243, -180, -267, -148, -265, -176, -290, -166,
+ -274, -160, -279, -151, -277, -159, -262, -146, -233, -125, -202,
+ -129, -198, -90, -162, -74, -165, -69, -153, -33, -141, -13,
+ -136, 2, -114, 40, -123, 47, -102, 43, -95, 48, -108,
+ 50, -112, 44, -100, 32, -92, 18, -95, 22, -82, 17,
+ -82, 23, -102, 19, -84, 22, -128, 24, -87, 44, -69,
+ 35, -66, 63, -29, 79, -26, 54, -19, 109, -9, 110,
+ -5, 137, -8, 111, 4, 120, 34, 92, -13, 96, -16,
+ 93, -54, 92},
+ /* IRC_Composite_C_R0195_T180_P330.wav */
+ {-26, 26, -26, 25, -25, 25, -24, 23, -23, 22, -21,
+ 20, -19, 17, -16, 14, -11, 8, -4, -1, 7, -15,
+ 27, -46, 89, 61, -145, 147, -71, 284, -270, 217, -379,
+ 526, 177, 1579, 111, 1304, -624, 3003, -1570, -1854, 2371, 5215,
+ 2766, 3717, 2594, 2071, 4289, 53, -1269, -2397, 2282, -1207, -426,
+ -1536, -96, 41, 749, -948, -416, 181, 838, -208, 144, -15,
+ -109, -323, 76, 15, 16, -276, 4, -8, -1, -123, -108,
+ -209, -91, -130, 71, -58, -162, -230, -119, -177, -166, -207,
+ -161, -106, -120, -36, -71, -88, -115, -141, -23, -102, -161,
+ -225, -242, -194, -306, -250, -347, -260, -382, -276, -340, -235,
+ -319, -231, -283, -196, -267, -199, -293, -242, -296, -261, -227,
+ -164, -163, -202, -268, -256, -325, -304, -440, -389, -451, -306,
+ -367, -292, -374, -282, -281, -248, -233, -251, -211, -225, -190,
+ -161, -223, -149, -241, -144, -229, -124, -173, -155, -158, -194,
+ -95, -175, -83, -151, -123, -82, -138, -35, -195, -70, -203,
+ -140, -178, -222, -181, -287, -200, -285, -259, -281, -289, -246,
+ -302, -246, -260, -227, -227, -242, -189, -224, -180, -220, -169,
+ -184, -146, -121, -121, -86, -121, -59, -86, -47, -78, -63,
+ -71, -90, -66, -82, -53, -82, -82, -54, -72, -16, -90,
+ -24, -88, -44, -31, -45, -32, -70, -43, -25, -59, -33,
+ -76, -24, -59, -46, -55, -45, -56, -56, -64, -32, -54,
+ -6, -57, -1, -22, 23, 2, 9, -2, 35, 54, 34,
+ 72, 22, 84, 15, 94, 10, 75, 34, 67, 47, 21,
+ 57, 14, 36},
+ /* IRC_Composite_C_R0195_T195_P330.wav */
+ {19, -19, 19, -20, 20, -21, 21, -22, 23, -23, 24, -24,
+ 25, -26, 26, -27, 27, -28, 29, -29, 30, -30, 30, -29,
+ 28, -25, 18, 14, 179, 64, -45, 11, 282, 14, -136, -276,
+ 1166, 711, 738, -60, 1508, 162, 166, 165, 449, 1532, 5355, 3392,
+ 931, 2171, 2302, 2446, -1456, -644, -2020, 1267, 216, -603, -1662, -126,
+ 427, -150, -158, 310, 15, 459, -99, 240, 14, 137, -140, 123,
+ 54, 92, -77, 39, 37, 33, -11, 20, 88, 136, -9, 51,
+ -50, -40, -148, -156, -244, -108, -193, -138, -117, -78, -113, -124,
+ -103, -88, -193, -190, -214, -191, -267, -312, -314, -321, -363, -318,
+ -371, -313, -329, -304, -279, -293, -244, -274, -270, -284, -268, -270,
+ -226, -229, -218, -220, -244, -283, -323, -321, -343, -387, -412, -395,
+ -369, -338, -385, -339, -361, -243, -279, -233, -263, -196, -226, -166,
+ -214, -179, -210, -188, -205, -188, -194, -186, -176, -189, -187, -188,
+ -151, -148, -132, -148, -138, -144, -161, -155, -165, -177, -165, -222,
+ -217, -269, -237, -294, -251, -318, -235, -297, -234, -275, -203, -229,
+ -195, -221, -176, -219, -151, -204, -120, -189, -136, -166, -97, -148,
+ -79, -121, -68, -94, -95, -78, -72, -67, -80, -75, -79, -69,
+ -70, -61, -76, -83, -71, -84, -52, -102, -40, -87, -65, -74,
+ -59, -59, -44, -53, -35, -32, -38, -24, -30, -33, -41, -31,
+ -32, -32, -53, -10, -53, 7, -39, 3, 11, -3, 30, 29,
+ 44, 32, 63, 46, 79, 39, 81, 48, 51, 38, 73, 36,
+ 50, 22, 77, -11},
+ /* IRC_Composite_C_R0195_T210_P330.wav */
+ {4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4, -4,
+ 4, -4, 4, -4, 4, -4, 4, -3, 3, -3, 2, -2,
+ 1, 0, -1, 4, -7, 14, -37, -66, 67, -14, -33, -186,
+ 91, -96, 90, 124, 958, 676, 187, 107, -283, 310, 1299, 1265,
+ 1017, 1358, 3762, 3924, 898, 885, 85, 1188, 235, 180, -1266, -91,
+ 662, -183, -222, -323, 535, 278, 681, 120, 382, 366, 409, 299,
+ 413, 216, 314, 49, 299, 205, 244, 15, 349, 134, 369, -25,
+ 355, 37, 155, -122, 149, -90, 17, -126, 0, -119, -101, -98,
+ -45, -101, -108, -137, -41, -165, -128, -190, -90, -220, -248, -213,
+ -218, -183, -289, -234, -287, -213, -287, -181, -291, -252, -334, -216,
+ -249, -215, -305, -231, -259, -234, -279, -267, -321, -303, -381, -272,
+ -334, -278, -372, -241, -327, -217, -356, -191, -320, -190, -311, -163,
+ -271, -146, -273, -141, -249, -181, -245, -176, -249, -177, -262, -171,
+ -258, -201, -269, -174, -249, -165, -249, -157, -236, -157, -222, -150,
+ -201, -185, -232, -186, -231, -212, -272, -199, -268, -197, -269, -185,
+ -248, -203, -236, -188, -226, -202, -206, -202, -200, -198, -175, -174,
+ -139, -154, -129, -144, -119, -116, -115, -116, -101, -118, -88, -110,
+ -115, -124, -128, -104, -139, -117, -161, -110, -128, -96, -116, -100,
+ -91, -78, -46, -54, -38, -54, -28, -28, -31, -22, -47, -22,
+ -45, -45, -34, -47, -26, -63, -19, -69, 15, -46, 17, -34,
+ 36, -7, 28, 11, 12, 21, 18, 27, 33, 12, 47, 14,
+ 27, 2, 38, -2},
+ /* IRC_Composite_C_R0195_T225_P330.wav */
+ {-1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1,
+ -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1,
+ -1, 1, -1, 1, -1, 1, 2, -68, -59, -5, -96, 48,
+ -93, -175, -114, 61, 113, 32, 173, 565, 369, -5, 6, 42,
+ 629, 908, 1232, 1079, 1509, 2842, 2279, 709, 505, 658, 652, 456,
+ 31, -132, 16, 714, 22, 392, 243, 724, 581, 610, 769, 414,
+ 780, 295, 589, 257, 440, 139, 342, 210, 326, 138, 213, 205,
+ 269, 65, 206, 39, 192, -75, 77, -72, 119, -93, 32, -98,
+ -25, -124, -61, -115, -124, -100, -152, -32, -169, -66, -171, -60,
+ -174, -142, -204, -153, -209, -250, -258, -282, -284, -268, -319, -270,
+ -333, -243, -293, -243, -293, -258, -255, -244, -253, -268, -246, -247,
+ -251, -255, -254, -239, -252, -237, -244, -241, -235, -232, -243, -208,
+ -257, -193, -268, -204, -268, -227, -248, -235, -265, -257, -230, -233,
+ -218, -240, -222, -215, -193, -210, -173, -175, -183, -176, -191, -180,
+ -202, -196, -223, -223, -252, -229, -258, -254, -257, -250, -252, -220,
+ -262, -227, -250, -215, -243, -223, -230, -208, -202, -158, -170, -134,
+ -160, -123, -166, -123, -164, -100, -174, -109, -180, -112, -164, -109,
+ -168, -131, -171, -106, -158, -104, -162, -121, -153, -114, -132, -112,
+ -125, -95, -82, -74, -52, -73, -50, -54, -44, -32, -38, -20,
+ -57, -17, -36, -14, -46, -36, -11, -10, -23, -34, -7, -16,
+ 7, -20, -30, -21, -13, -19, -3, -32, -10, -25, 9, -16,
+ 17, 18, -2, 23},
+ /* IRC_Composite_C_R0195_T240_P330.wav */
+ {6, -6, 6, -6, 6, -6, 6, -6, 7, -7, 7, -7,
+ 7, -7, 7, -7, 7, -7, 7, -7, 8, -8, 8, -8,
+ 8, -8, 8, -8, 1, -47, -37, -124, -93, -95, -58, -38,
+ -70, -58, -105, -121, -151, -76, 14, 175, 104, 51, 147, 291,
+ 191, -40, 158, 429, 1090, 1311, 1373, 1241, 1326, 1526, 1181, 419,
+ 583, 828, 657, 621, 656, 466, 551, 572, 739, 747, 685, 612,
+ 423, 667, 583, 601, 619, 480, 483, 323, 428, 299, 276, 192,
+ 162, 105, 83, 89, 89, 51, 98, -46, 97, -2, 117, -65,
+ 34, -69, 1, -69, -69, -127, -170, -131, -173, -121, -145, -147,
+ -134, -204, -102, -232, -83, -273, -123, -325, -159, -309, -183, -275,
+ -249, -249, -252, -185, -266, -178, -245, -201, -259, -208, -248, -243,
+ -270, -238, -253, -227, -254, -209, -249, -203, -269, -184, -271, -141,
+ -258, -121, -230, -113, -188, -120, -198, -167, -178, -200, -214, -242,
+ -233, -264, -244, -276, -244, -267, -267, -261, -267, -239, -274, -233,
+ -272, -226, -277, -260, -268, -247, -282, -253, -289, -226, -270, -213,
+ -255, -189, -247, -158, -192, -146, -173, -170, -171, -177, -143, -184,
+ -151, -187, -146, -147, -141, -154, -121, -150, -139, -166, -139, -155,
+ -135, -176, -141, -174, -144, -174, -160, -177, -162, -156, -152, -157,
+ -114, -126, -89, -103, -86, -98, -78, -68, -62, -53, -84, -35,
+ -53, -7, -51, -15, -38, -6, -34, -13, -34, -15, -32, -28,
+ -35, -19, -32, 1, -19, 7, -27, 9, 3, 4, -14, 3,
+ -5, 36, 2, 41},
+ /* IRC_Composite_C_R0195_T255_P330.wav */
+ {-1, 1, -1, 1, -1, 1, -2, 2, -2, 2, -2, 2,
+ -2, 2, -2, 2, -2, 3, -3, 3, -3, 3, -3, 4,
+ -4, 4, -5, 5, -6, 7, -8, 10, -13, 22, -83, -97,
+ -106, -152, -99, -59, -90, -92, -128, -74, -117, -122, -12, 68,
+ 86, 137, 156, 199, 190, 329, 533, 767, 1036, 1233, 1574, 1651,
+ 1491, 1169, 954, 781, 906, 1168, 950, 370, 704, 786, 629, 605,
+ 494, 653, 561, 698, 550, 738, 501, 619, 385, 441, 218, 218,
+ 189, 132, 100, 39, 147, 27, 91, -38, 77, -24, 27, -36,
+ -30, -17, -41, -95, -49, -122, -68, -169, -78, -186, -103, -225,
+ -135, -197, -120, -153, -145, -183, -142, -167, -150, -231, -159, -244,
+ -153, -214, -165, -219, -193, -224, -173, -215, -157, -238, -162, -231,
+ -149, -238, -155, -243, -185, -203, -182, -175, -206, -184, -214, -165,
+ -184, -162, -168, -165, -138, -160, -133, -192, -158, -236, -216, -272,
+ -273, -290, -295, -306, -316, -279, -301, -261, -303, -259, -299, -287,
+ -289, -292, -273, -302, -247, -283, -207, -253, -202, -230, -201, -189,
+ -205, -172, -192, -162, -193, -165, -169, -166, -191, -174, -179, -167,
+ -155, -172, -157, -151, -142, -148, -140, -156, -161, -162, -168, -167,
+ -172, -156, -177, -164, -174, -152, -168, -156, -171, -153, -173, -152,
+ -171, -139, -162, -124, -144, -85, -111, -53, -108, -40, -71, -33,
+ -75, -21, -62, -20, -68, 0, -50, 5, -37, -3, -30, 28,
+ -23, 3, -23, 12, -19, 28, -12, 24, -5, 41, 17, 24,
+ -7, 17, -24, 23},
+ /* IRC_Composite_C_R0195_T270_P330.wav */
+ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 1, -1, 1, -1, 1, -1, 1, -2, 3,
+ -127, -152, -188, -165, -160, -178, -195, -131, -72, -37, -53, -57,
+ -34, 48, 106, 135, 127, 188, 385, 765, 884, 1201, 1389, 1575,
+ 1756, 1541, 1264, 1282, 908, 615, 982, 1053, 498, 403, 667, 706,
+ 647, 681, 663, 732, 712, 700, 422, 527, 505, 426, 401, 192,
+ 156, 184, 190, 100, 26, -34, 55, -50, 19, -119, 28, -59,
+ -54, -94, -103, -24, -139, -24, -165, -13, -154, -5, -159, -81,
+ -104, -118, -99, -136, -98, -118, -106, -89, -142, -101, -168, -112,
+ -178, -178, -148, -180, -114, -217, -134, -255, -104, -252, -140, -238,
+ -148, -235, -187, -236, -202, -242, -197, -183, -140, -137, -108, -143,
+ -129, -142, -128, -211, -166, -246, -182, -269, -212, -271, -233, -292,
+ -267, -287, -263, -282, -269, -312, -249, -304, -257, -293, -240, -287,
+ -230, -257, -242, -248, -234, -224, -236, -219, -241, -188, -227, -176,
+ -221, -206, -210, -185, -185, -166, -179, -163, -176, -164, -168, -164,
+ -186, -168, -180, -154, -159, -141, -142, -150, -142, -147, -152, -145,
+ -159, -150, -168, -167, -187, -177, -186, -176, -192, -163, -197, -167,
+ -186, -164, -170, -159, -148, -137, -112, -102, -104, -88, -70, -56,
+ -50, -28, -30, -11, -12, -16, -10, -21, -31, -4, -26, 2,
+ 15, 8, 10, -3, -2, 5, 10, 12, -9, 0, -5, 9,
+ -13, -19, -29, -16},
+ /* IRC_Composite_C_R0195_T285_P330.wav */
+ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, -1, 1, -1, 1,
+ -1, 1, -1, 2, -2, 3, -6, 13, -111, -184, -215, -184,
+ -140, -139, -179, -220, -168, -247, -124, 1, -91, -84, 42, -80,
+ 17, -54, 56, 218, 634, 881, 967, 1181, 1587, 1636, 1273, 1643,
+ 1650, 702, 357, 915, 823, 759, 711, 555, 754, 615, 798, 685,
+ 720, 707, 793, 638, 630, 510, 595, 460, 429, 211, 188, 178,
+ 148, 102, 16, 162, 0, 142, -40, 140, -32, 103, 24, -7,
+ -30, 11, -24, -1, -87, 12, -168, 51, -103, 64, -105, 39,
+ -118, 10, -91, -91, -119, -125, -90, -149, -75, -130, -138, -153,
+ -195, -185, -254, -205, -313, -223, -303, -243, -266, -224, -223, -230,
+ -188, -216, -155, -167, -129, -140, -137, -102, -110, -74, -146, -82,
+ -177, -89, -192, -110, -215, -160, -231, -232, -220, -242, -237, -262,
+ -238, -271, -237, -259, -263, -234, -261, -228, -278, -222, -281, -252,
+ -268, -270, -265, -263, -228, -244, -187, -230, -176, -214, -164, -194,
+ -141, -175, -152, -173, -143, -161, -171, -179, -174, -184, -157, -194,
+ -163, -211, -148, -196, -144, -205, -158, -219, -155, -190, -162, -178,
+ -166, -174, -156, -186, -169, -200, -161, -200, -167, -208, -167, -218,
+ -158, -186, -143, -155, -119, -123, -104, -117, -86, -101, -54, -82,
+ -39, -73, -25, -68, -7, -55, -18, -42, 23, -14, 19, -21,
+ 7, -24, 19, -4, 2, -14, 7, -10, 12, -14, 13, -7,
+ 26, -23, 8, -14},
+ /* IRC_Composite_C_R0195_T300_P330.wav */
+ {-3, 3, -3, 3, -3, 3, -3, 3, -3, 3, -3, 3,
+ -3, 3, -3, 3, -3, 3, -3, 3, -3, 3, -3, 4,
+ -4, 4, -4, 4, -4, 4, -5, -62, -157, -83, -247, -284,
+ -202, -303, -294, -235, -54, -305, 15, 156, 241, -112, -479, -226,
+ 774, -232, 495, 1765, 1236, 1265, 2221, 2705, 1763, 850, 505, 963,
+ 731, 757, 546, 519, 206, 795, 441, 708, 577, 742, 596, 545,
+ 491, 526, 666, 413, 377, 307, 300, 457, 188, 260, 34, 250,
+ 133, 251, 58, 87, 249, 132, 194, -1, 200, 55, 156, 95,
+ 139, 122, 50, 62, 57, 84, -3, -71, -62, -99, -113, -176,
+ -122, -254, -180, -227, -166, -230, -202, -293, -196, -279, -248, -326,
+ -256, -320, -217, -260, -211, -244, -154, -191, -125, -171, -113, -160,
+ -81, -135, -55, -102, -70, -95, -24, -42, -53, -78, -73, -93,
+ -89, -149, -139, -195, -180, -235, -192, -245, -233, -268, -234, -245,
+ -224, -256, -221, -249, -225, -220, -233, -215, -250, -226, -218, -236,
+ -221, -224, -181, -198, -176, -191, -173, -185, -156, -184, -162, -178,
+ -171, -172, -156, -184, -183, -174, -192, -186, -179, -189, -180, -212,
+ -184, -191, -167, -199, -217, -182, -203, -172, -210, -178, -224, -155,
+ -220, -185, -224, -199, -194, -203, -201, -206, -192, -196, -188, -165,
+ -158, -177, -147, -145, -112, -128, -98, -113, -74, -77, -54, -65,
+ -67, -32, -44, -10, -54, -13, -29, -7, -15, -1, -23, 19,
+ -18, 10, -27, 11, -16, 8, 2, 12, -12, 4, -1, 37,
+ 18, 19, 17, 36},
+ /* IRC_Composite_C_R0195_T315_P330.wav */
+ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, -1, 1, -1, 1, -1, 1, -1, 1, -1,
+ 1, -1, 1, -1, 2, -3, -227, -204, -310, -344, -179, -311,
+ -505, -298, -88, -402, 115, 712, -76, -577, -211, 9, 558, -404,
+ 914, 2627, 1047, 2118, 4245, 2542, 908, 788, 1382, 865, 422, 674,
+ -11, 248, 192, 1314, 191, 817, 366, 966, 306, 467, 192, 289,
+ 334, 100, 222, 7, 227, 236, 315, 254, 209, 305, 479, 300,
+ 240, 335, 445, 260, 199, 112, 202, 84, 42, 63, 85, -45,
+ -100, -80, -73, -157, -181, -120, -209, -167, -259, -161, -262, -234,
+ -281, -209, -243, -208, -221, -245, -173, -195, -197, -241, -151, -201,
+ -166, -240, -132, -182, -104, -150, -85, -104, -75, -148, -59, -141,
+ -36, -144, -33, -133, -71, -89, -7, -47, -109, -70, -108, -44,
+ -152, -113, -164, -122, -182, -216, -170, -212, -172, -263, -155, -228,
+ -178, -252, -176, -207, -200, -242, -206, -220, -204, -208, -195, -218,
+ -188, -193, -156, -217, -159, -200, -119, -211, -133, -181, -131, -177,
+ -169, -158, -166, -171, -195, -175, -181, -214, -193, -224, -184, -218,
+ -204, -219, -183, -222, -197, -222, -197, -226, -215, -204, -205, -207,
+ -216, -212, -210, -224, -222, -213, -210, -205, -202, -194, -174, -155,
+ -148, -134, -144, -126, -89, -101, -75, -94, -84, -69, -47, -37,
+ -69, -67, -26, -35, -13, -29, -17, -23, -3, 13, -18, -8,
+ -26, 22, -30, 20, -15, 30, -13, 8, -11, 23, 49, 30,
+ 39, 22, 36, 16},
+ /* IRC_Composite_C_R0195_T330_P330.wav */
+ {9, -9, 9, -10, 10, -10, 10, -11, 11, -12, 12, -12,
+ 13, -13, 14, -15, 15, -16, 17, -17, 18, -19, 20, -22,
+ 23, -25, 30, -50, -332, -388, -131, -617, -214, -313, -733, -659,
+ 255, -155, -66, 699, -77, -372, -1255, -242, 2041, -707, -6, 4736,
+ 2036, 2809, 3545, 4509, 22, -72, 1207, 1973, -173, 508, 959, 302,
+ 542, 540, 656, 123, 461, 665, 400, 314, 251, 504, 190, 265,
+ 35, 373, 467, 91, 154, 128, 447, 203, 170, 90, 232, 80,
+ 149, 193, 21, -6, -90, -101, -59, -80, -255, -144, -154, -358,
+ -29, -238, -96, -237, -126, -195, 15, -288, -107, -147, -89, -276,
+ -120, -223, -134, -174, -181, -152, -158, -179, -71, -138, -116, -177,
+ -93, -189, -60, -206, -162, -159, -113, -111, -150, -123, -93, -106,
+ -83, -101, -52, -113, -43, -99, -10, -99, -66, -88, -58, -127,
+ -64, -152, -99, -152, -144, -200, -127, -259, -116, -227, -129, -245,
+ -98, -258, -94, -239, -152, -227, -154, -225, -147, -242, -169, -202,
+ -172, -212, -140, -232, -141, -182, -139, -168, -130, -205, -127, -137,
+ -140, -176, -175, -160, -166, -171, -205, -188, -193, -212, -199, -238,
+ -185, -248, -173, -246, -186, -227, -206, -245, -184, -218, -210, -235,
+ -210, -228, -175, -238, -206, -213, -189, -196, -171, -200, -159, -123,
+ -146, -114, -128, -128, -81, -88, -83, -80, -52, -124, -70, -58,
+ -40, -45, -69, -50, -10, -13, -16, -42, -4, -25, 50, -37,
+ 11, -37, 37, -4, 24, -11, 12, -7, 33, 10, 69, 29,
+ 60, 20, 87, -1},
+ /* IRC_Composite_C_R0195_T345_P330.wav */
+ {1, 0, 0, 0, -1, 1, -2, 2, -3, 4, -5, 6,
+ -7, 8, -9, 11, -13, 15, -17, 20, -24, 28, -33, 40,
+ -47, 52, -211, -235, -688, -550, -297, -690, -704, -444, 713, -1568,
+ 989, 470, -39, -1205, 323, -714, 276, -2380, 5748, 2929, 602, 8080,
+ 5302, -453, -722, 3812, 715, -74, -365, 1626, 369, 1163, 1544, 781,
+ -4, 229, 668, 441, 364, 676, 214, 441, 36, 409, 174, 565,
+ -210, 423, -90, 172, -132, 46, -99, 78, -186, 159, -181, 96,
+ -244, 13, -280, -20, -156, -41, -291, -109, -365, -55, -322, -117,
+ -274, -136, -169, -159, -189, -140, -191, 12, -178, 0, -295, -70,
+ -206, -88, -236, -75, -169, -78, -191, -95, -204, -87, -146, -66,
+ -185, -107, -238, -113, -249, -119, -185, -98, -187, -131, -176, -95,
+ -137, -31, -142, -17, -163, -50, -120, -40, -114, -19, -100, -43,
+ -142, -64, -163, -81, -167, -107, -184, -140, -220, -122, -200, -146,
+ -203, -155, -165, -178, -177, -198, -166, -175, -153, -160, -161, -178,
+ -163, -171, -143, -138, -136, -139, -146, -131, -151, -132, -167, -143,
+ -153, -146, -165, -157, -219, -159, -215, -151, -222, -190, -225, -204,
+ -218, -208, -214, -189, -218, -191, -221, -190, -214, -185, -216, -196,
+ -215, -178, -200, -217, -218, -194, -162, -182, -170, -189, -185, -152,
+ -157, -123, -144, -122, -111, -99, -99, -89, -76, -63, -81, -56,
+ -61, -31, -49, -85, -23, -30, -14, -9, -21, -45, -18, -1,
+ 16, 3, 5, -5, 5, 8, -5, 64, 39, 55, 37, 73,
+ 42, 56, 31, 84}};
+
+const int16_t irc_composite_c_r0195_p345[][256] =
+ {/* IRC_Composite_C_R0195_T000_P345.wav */
+ {2, -1, 1, -1, 1, -1, 1, -1, 0, 0, 0,
+ 0, -1, 1, -2, 2, -3, 4, -5, 7, -9, 11,
+ -15, 21, -203, -88, -927, -196, -604, -22, -2141, -496, -650,
+ 1585, -548, 803, -263, 412, -5131, 5346, -2103, -7267, 12288, 1672,
+ 1403, 10338, 7456, -3182, -874, 2680, 1965, -5296, 1356, 1865, 68,
+ -578, 2523, 1092, 1554, 2056, 2530, 1058, 1432, 379, 1062, -82,
+ -380, -971, 369, -376, 62, -56, 669, -390, 190, 379, 537,
+ 98, -46, -35, 315, 348, 110, 183, 491, -38, 19, 211,
+ 170, -22, -231, -125, -221, 138, -512, -336, -126, 10, -387,
+ -311, -283, -176, -241, -345, -432, -207, -330, -489, -386, -392,
+ -351, -423, -274, -457, -352, -361, -311, -213, -391, -323, -302,
+ -143, -325, -378, -206, -229, -190, -319, -334, -196, -236, -126,
+ -387, -117, -339, -105, -264, -135, -207, -172, -176, -254, -160,
+ -183, -177, -200, -218, -201, -189, -116, -205, -120, -224, -113,
+ -159, -119, -241, -118, -127, -119, -188, -91, -119, -15, -186,
+ -30, -132, 28, -213, -19, -166, 21, -190, -46, -185, -49,
+ -159, -65, -168, -65, -190, -59, -218, -46, -217, -24, -231,
+ -74, -204, -52, -181, -89, -211, -94, -196, -70, -219, -104,
+ -200, -55, -181, -86, -186, -39, -155, -66, -178, -1, -187,
+ -47, -188, 6, -142, -11, -161, -5, -114, -9, -113, 6,
+ -145, -7, -123, -21, -161, -34, -116, -47, -173, -82, -145,
+ -76, -157, -72, -137, -75, -186, -79, -163, -31, -131, -17,
+ -156, -51, -99, 26, -74, -5, -101, 37, -82, 48, -83,
+ 35, -130, 20},
+ /* IRC_Composite_C_R0195_T015_P345.wav */
+ {-8, 8, -9, 9, -9, 10, -10, 11, -11, 12, -13,
+ 14, -15, 17, -19, 21, -23, 26, -30, 34, -31, -453,
+ -1036, -33, -665, 328, -1202, -749, -1695, -16, 154, 1160, 807,
+ 430, -250, 202, -9659, 9217, 1210, -9352, 17225, 6331, 1506, 7124,
+ 7939, -4308, -4730, 1188, 3176, -2475, 1424, -48, -469, 790, 3944,
+ 1309, 680, 1752, 2484, 1048, 341, -282, 654, -429, -824, -317,
+ 110, -114, -77, 514, 87, -336, 314, 152, 794, -136, 169,
+ -632, 188, -172, 280, -21, -59, -628, -366, -701, 125, -342,
+ 195, -484, 14, -385, 142, -203, -25, -63, -111, -438, -237,
+ -144, -19, -603, -408, -283, -225, -622, -635, -157, -385, -331,
+ -577, -30, -389, -404, -427, -106, -226, -443, -405, -162, -392,
+ -396, -469, 17, -462, -322, -529, -39, -465, -308, -396, -19,
+ -390, -307, -364, 11, -326, -213, -317, -16, -354, -213, -313,
+ -60, -343, -187, -297, -56, -344, -144, -249, -9, -231, -112,
+ -162, -24, -173, -102, -136, 27, -150, -25, -210, 56, -219,
+ 64, -254, 58, -220, 23, -244, -12, -203, -8, -244, -39,
+ -226, -35, -259, -51, -231, -34, -263, -76, -247, -60, -275,
+ -44, -227, -27, -295, -46, -242, -22, -251, -17, -192, -18,
+ -252, -5, -165, 49, -233, 62, -170, 73, -217, 68, -124,
+ 45, -174, 23, -102, 4, -167, 36, -98, 28, -187, 19,
+ -138, 2, -165, -15, -133, -54, -155, -42, -139, -76, -178,
+ -15, -149, -36, -176, 14, -154, -30, -174, 22, -136, -10,
+ -157, 29, -146, 8, -148, 67, -147, 48, -150, 87, -144,
+ 95, -120, 68},
+ /* IRC_Composite_C_R0195_T030_P345.wav */
+ {-5, 4, -2, 1, 1, -3, 5, -8, 11, -14,
+ 18, -22, 28, -33, 40, -47, 52, -40, -408, -1246,
+ -239, -636, 412, -1166, -641, -1047, -627, -510, 85, 1903,
+ 1495, 1980, -2101, -8149, 8335, -7570, 2561, 15248, -2261, 7441,
+ 12350, 8152, -6960, -897, -1312, -80, 35, 3139, -1223, -111,
+ 782, 2694, 1229, 2248, 830, 1733, -78, 616, 211, 239,
+ -1106, 180, -577, 174, -80, 366, -414, -235, 48, 899,
+ -101, -343, 251, 6, -139, -259, 84, -149, -439, -392,
+ -377, -353, -641, -180, -347, -645, -512, 18, -444, -412,
+ -353, -372, 29, -540, -286, -340, 91, -270, 81, -387,
+ -257, -287, -105, -191, -258, -436, 11, -476, -269, -411,
+ -52, -449, -147, -462, -317, -370, -394, -207, -298, -370,
+ -445, -321, -432, -282, -273, -429, -392, -579, -276, -340,
+ -267, -422, -237, -397, -288, -363, -183, -311, -252, -339,
+ -167, -369, -181, -301, -85, -308, -243, -297, -206, -198,
+ -141, -225, -133, -188, -51, -81, -5, -156, 39, -131,
+ -42, -137, -12, -105, -59, -165, -33, -91, -103, -131,
+ -61, -132, -15, -166, -70, -196, -74, -136, -77, -240,
+ -119, -181, -167, -207, -153, -210, -116, -267, -128, -236,
+ -136, -218, -68, -191, -86, -215, -66, -87, -41, -115,
+ -57, -149, 39, -73, 32, -134, 21, -60, 58, -96,
+ 16, -33, 13, -37, -10, -59, 0, -62, -11, -108,
+ -26, -126, 5, -173, -25, -133, -22, -188, -62, -152,
+ -27, -144, -75, -133, -61, -105, -49, -114, -43, -128,
+ -43, -94, -30, -105, -18, -125, -14, -97, 30, -123,
+ 23, -65, 42, -66, 35, -56},
+ /* IRC_Composite_C_R0195_T045_P345.wav */
+ {-8, 9, -9, 10, -11, 12, -14, 15, -17, 18, -20,
+ 23, -26, 29, -32, 27, -914, -992, 248, -1147, 746, -600,
+ -1411, -744, -565, -282, 187, 2409, 2683, -221, -1093, -6934, 4033,
+ -4276, 11276, -297, 4328, 20553, 5592, -1738, -1835, 48, -2700, 3273,
+ 333, -1462, -612, 3497, 663, 1061, 1222, 2492, 754, 1168, -36,
+ 273, -1174, -272, -1054, 891, -844, 132, -415, 315, -117, 257,
+ -335, 440, -662, 37, -141, 164, -657, 106, -514, -245, -612,
+ -181, -587, -119, -354, -272, -370, 70, -460, -182, -631, -316,
+ -708, -110, -803, -99, -670, -498, -427, -54, -498, -454, -369,
+ -295, -361, -311, -286, -279, -189, -173, -337, -51, -132, -118,
+ -321, -115, -360, -197, -296, -134, -359, -223, -370, -154, -278,
+ -322, -354, -297, -436, -355, -372, -287, -465, -352, -358, -269,
+ -478, -297, -336, -244, -461, -251, -336, -244, -377, -295, -277,
+ -241, -357, -246, -212, -210, -283, -149, -224, -96, -233, -70,
+ -163, -39, -261, -32, -98, -58, -219, -32, -96, -67, -134,
+ 7, -101, -50, -139, 18, -115, -50, -164, -70, -125, -101,
+ -200, -71, -185, -130, -181, -136, -176, -132, -203, -147, -173,
+ -138, -199, -159, -176, -127, -141, -96, -163, -71, -143, -38,
+ -126, -27, -77, -59, -103, 5, -43, -32, -25, -36, -5,
+ -42, 40, -34, 18, -45, 1, -59, 7, -47, -28, -49,
+ -55, -63, -33, -55, -79, -59, -82, -109, -94, -40, -81,
+ -89, -121, -82, -83, -49, -111, -62, -79, -97, -59, -52,
+ -74, -97, -50, -98, -31, -116, 11, -85, 32, -106, 43,
+ -88, 79, -70},
+ /* IRC_Composite_C_R0195_T060_P345.wav */
+ {2, 0, -1, 2, -4, 7, -10, 13, -18, 24, -33,
+ 46, -69, 124, -1066, 18, -1103, -58, -112, -921, -201, 357,
+ -2572, -292, 84, 3712, 705, 1846, -3182, 733, -1997, -2694, 2428,
+ -595, 23701, 7754, -2253, 5078, 3945, -1381, 793, -3491, -1163, 3714,
+ 3613, -2506, 296, 680, 1730, 1310, 1740, 569, 508, 99, -722,
+ -392, -453, -871, 66, -328, -669, -71, -484, -147, -35, 146,
+ 49, -532, -397, -139, -139, -522, -373, -116, -571, -415, -445,
+ -69, -274, -275, -429, -422, -271, -360, -287, -382, -22, -407,
+ -385, -428, -388, -334, -422, -213, -696, -500, -450, -310, -488,
+ -360, -442, -369, -341, -387, -436, -315, -312, -227, -524, -284,
+ -360, -145, -448, -103, -329, -255, -335, -170, -226, -169, -275,
+ -186, -277, -175, -313, -153, -328, -106, -340, -187, -359, -196,
+ -395, -186, -410, -204, -401, -242, -445, -212, -399, -269, -333,
+ -214, -369, -275, -249, -157, -224, -209, -240, -93, -196, -87,
+ -134, -136, -170, -118, -141, -152, -108, -143, -171, -122, -121,
+ -75, -167, -118, -171, -34, -161, -63, -166, -73, -182, -54,
+ -115, -102, -170, -130, -176, -78, -151, -106, -187, -132, -212,
+ -115, -143, -110, -184, -158, -155, -100, -103, -72, -126, -49,
+ -120, -26, -63, 20, -83, 6, -50, 25, -17, 26, -29,
+ 39, -31, 8, -37, 34, -77, 20, -77, 4, -80, -26,
+ -93, -19, -70, -86, -71, -70, -82, -94, -51, -114, -61,
+ -80, -78, -73, -90, -68, -70, -42, -80, -51, -78, -111,
+ -16, -79, -18, -107, -18, -114, -3, -27, -1, -36, -19,
+ -21, -10, 39},
+ /* IRC_Composite_C_R0195_T075_P345.wav */
+ {13, -13, 13, -13, 13, -13, 13, -14, 14, -14, 14,
+ -14, 13, -149, -486, -602, -335, -455, -212, -518, 0, -1171,
+ -540, -311, 1579, 3683, 3360, -5772, -426, -1232, -715, 5798, -5995,
+ 21017, 15656, -2523, -3222, 5350, 1893, 1313, -2953, -885, 4326, 1209,
+ -279, -1161, 869, 861, 1767, 953, -85, 210, -139, -663, -17,
+ -553, 268, -673, -215, -760, -236, -646, -139, -393, -427, -462,
+ -55, -562, -131, -287, -193, -567, -273, -625, -345, -491, -150,
+ -487, -111, -512, -283, -603, -50, -418, -328, -443, -111, -346,
+ -355, -349, -35, -305, -248, -516, -491, -547, -144, -443, -470,
+ -467, -394, -484, -216, -365, -370, -505, -259, -440, -318, -386,
+ -196, -476, -376, -474, -304, -362, -195, -334, -380, -413, -292,
+ -310, -213, -289, -263, -333, -155, -255, -179, -252, -180, -265,
+ -153, -285, -190, -264, -147, -236, -237, -304, -248, -194, -182,
+ -204, -186, -250, -173, -172, -144, -229, -114, -172, -167, -183,
+ -136, -161, -106, -122, -161, -186, -139, -156, -91, -182, -139,
+ -216, -134, -193, -139, -180, -115, -198, -155, -201, -90, -175,
+ -115, -207, -160, -200, -119, -149, -119, -195, -93, -130, -96,
+ -135, -84, -130, -121, -106, -85, -107, -61, -110, -53, -77,
+ 2, -45, -15, -51, -20, 4, 33, -23, -40, -36, 1,
+ -53, 16, -46, -46, -69, 1, -46, -34, -47, -35, -46,
+ -45, -73, -11, -76, -35, -107, -21, -105, -57, -70, -68,
+ -101, -73, -74, -59, -95, -48, -78, -69, -89, -82, -50,
+ -61, -44, -75, -23, -52, -44, 10, -43, -23, -47, 63,
+ -13, 31, 19},
+ /* IRC_Composite_C_R0195_T090_P345.wav */
+ {-40, 45, -52, 60, -69, 79, -92, 108, -129, 156, -197,
+ 267, -462, -149, 208, -635, -826, 268, -390, -920, 244, -62,
+ -1260, -320, 2937, 4521, -564, -4829, 2706, -6796, 9962, -6783, 6356,
+ 23745, 4526, -4606, -155, 5413, 3814, -1343, -3207, 4343, 2939, -733,
+ -2665, 998, 738, 1892, 332, -416, -415, 112, -618, 265, -680,
+ -48, -750, 405, -729, 216, -673, -99, -730, -121, -855, -310,
+ -693, -146, -680, -198, -695, -260, -522, -283, -523, -143, -597,
+ -333, -173, -342, -516, -211, -519, -364, -344, -136, -671, -213,
+ -411, -202, -398, -103, -578, -290, -294, -138, -306, -230, -402,
+ -283, -321, -289, -337, -289, -473, -414, -279, -358, -413, -275,
+ -431, -453, -313, -307, -448, -301, -341, -461, -354, -322, -417,
+ -358, -269, -383, -284, -242, -302, -305, -257, -285, -308, -182,
+ -268, -314, -270, -212, -228, -190, -249, -276, -158, -168, -143,
+ -161, -146, -207, -91, -103, -158, -110, -126, -88, -73, -13,
+ -110, -35, -46, -106, -57, -116, -140, -215, -98, -197, -197,
+ -174, -246, -265, -202, -185, -227, -181, -218, -214, -125, -146,
+ -175, -178, -153, -217, -80, -87, -137, -151, -98, -122, -94,
+ -88, -136, -141, -168, -115, -121, -70, -143, -102, -56, -76,
+ -36, 6, -27, -49, 15, 48, -26, 23, -19, 19, -21,
+ 34, -59, 8, -22, -47, 2, -38, -69, -64, -5, -74,
+ -105, -59, -66, -97, -94, -96, -78, -97, -52, -113, -64,
+ -142, -56, -66, -39, -129, -40, -66, -56, -70, 5, -96,
+ -30, -55, 4, -41, 13, -55, 20, -26, 32, -22, 71,
+ -18, 17, 30},
+ /* IRC_Composite_C_R0195_T105_P345.wav */
+ {-67, 70, -74, 77, -81, 86, -90, 96, -101, 108,
+ -115, 124, -133, 69, -792, -387, 271, -596, -717, 859,
+ -864, -953, -174, 2178, -1005, 1282, 2249, 6443, -10856, 1162,
+ -3444, 7735, 12493, -6470, 12789, 13090, -1110, -7098, 1720, 6128,
+ 6522, -3157, -2418, 823, 2649, -1545, -82, 75, 412, 176,
+ -227, -1102, -27, 27, -108, -705, 430, -492, -144, -623,
+ 458, -659, -400, -488, -145, -709, -561, -722, -446, -592,
+ -392, -773, -320, -376, -219, -435, -230, -433, -344, -257,
+ -282, -422, -405, -416, -298, -443, -368, -500, -217, -519,
+ -166, -433, -279, -366, -130, -440, -244, -224, -238, -388,
+ -222, -308, -238, -178, -169, -397, -297, -264, -256, -355,
+ -298, -417, -426, -330, -305, -333, -364, -344, -434, -356,
+ -283, -355, -382, -364, -318, -364, -236, -292, -356, -334,
+ -239, -297, -263, -218, -274, -335, -226, -222, -269, -272,
+ -264, -249, -207, -170, -177, -194, -173, -129, -105, -104,
+ -107, -136, -46, -4, 64, 55, 23, -30, -30, -40,
+ -113, -234, -240, -226, -142, -197, -242, -304, -222, -141,
+ -127, -177, -202, -148, -92, -95, -154, -222, -191, -141,
+ -114, -142, -174, -129, -161, -67, -128, -34, -186, -89,
+ -116, -46, -141, -77, -118, -122, -94, -41, -91, -117,
+ -59, -17, -48, -43, -49, -43, -26, 26, 1, -33,
+ -44, -8, 1, 24, -53, -52, -67, -16, -53, -12,
+ -77, -55, -119, -25, -52, -50, -119, -50, -99, -48,
+ -93, -38, -125, -90, -97, -23, -111, -77, -96, -23,
+ -117, -28, -57, -25, -82, -14, -37, -3, -37, 48,
+ -10, 30, -29, 94, 17, 67},
+ /* IRC_Composite_C_R0195_T120_P345.wav */
+ {-3, 4, -6, 7, -9, 12, -14, 18, -21, 26, -32,
+ 40, -51, 68, -105, -237, -115, -196, -240, -710, 503, -404,
+ -299, -610, 1573, -1052, 1538, 3156, 4734, -10274, 2428, 47, 3883,
+ 7208, -4454, 16464, 10228, -4264, -5316, 5989, 6995, 2607, -3203, -668,
+ 1640, 904, -2642, -469, 203, 1050, -729, -1586, -33, 324, -227,
+ 138, -165, -76, 50, -374, -903, 385, -637, -751, -371, 7,
+ -958, -172, -379, -347, -638, -287, -600, -251, -397, -596, -514,
+ -222, -363, -323, -357, -509, -371, -223, -433, -356, -378, -472,
+ -223, -149, -381, -362, -256, -360, -249, -245, -414, -343, -215,
+ -318, -296, -253, -281, -260, -186, -267, -334, -184, -218, -221,
+ -298, -323, -280, -235, -266, -346, -291, -364, -250, -269, -330,
+ -407, -337, -288, -297, -291, -367, -331, -313, -227, -277, -310,
+ -324, -354, -249, -294, -307, -326, -246, -243, -266, -239, -219,
+ -134, -170, -168, -181, -178, -179, -104, -105, -182, -117, -16,
+ 21, -1, -16, -123, -97, -70, -125, -190, -191, -224, -174,
+ -191, -187, -240, -111, -186, -142, -146, -92, -134, -84, -176,
+ -166, -110, -109, -146, -139, -231, -119, -73, -70, -193, -83,
+ -143, -72, -63, -63, -163, -69, -90, -96, -97, -84, -157,
+ -37, -91, -88, -116, -29, -99, -31, -73, -68, -76, 24,
+ -65, -8, -90, -71, -32, 33, -119, -46, -74, -20, -92,
+ 37, -144, -45, -100, 37, -86, 3, -132, 2, -42, -29,
+ -104, -4, -152, -16, -85, -19, -175, 14, -94, -9, -106,
+ -24, -72, 42, -77, -27, -22, 2, -33, 78, -29, -21,
+ 21, 111, -5},
+ /* IRC_Composite_C_R0195_T135_P345.wav */
+ {-8, 8, -9, 9, -9, 10, -10, 11, -11, 11, -12,
+ 12, -12, 13, -12, 12, -14, -199, -345, 194, -292, -20,
+ -363, 219, -924, 441, 900, 798, -558, 3720, 1651, -3208, -514,
+ -4991, 13814, 1350, -1342, 10873, 8019, 1098, -3160, 1031, 6880, 5706,
+ -3917, -4208, 1661, 708, -1088, -1854, -201, 229, -177, -273, -648,
+ -82, 475, 5, -734, 169, -151, -581, -653, 471, -491, -236,
+ -587, -109, -475, -248, -581, -317, -480, -514, -555, -293, -524,
+ -197, -571, -212, -393, -202, -577, -116, -375, -344, -418, -218,
+ -355, -263, -348, -305, -348, -210, -371, -214, -394, -179, -361,
+ -184, -347, -189, -348, -251, -234, -293, -267, -213, -252, -242,
+ -269, -147, -370, -183, -325, -201, -333, -191, -269, -230, -243,
+ -283, -228, -273, -293, -252, -342, -267, -359, -215, -428, -224,
+ -365, -239, -330, -269, -348, -257, -251, -286, -250, -246, -272,
+ -177, -242, -155, -277, -116, -236, -94, -218, -96, -109, -104,
+ -71, -63, -76, -143, -84, -111, -151, -139, -218, -149, -229,
+ -159, -185, -165, -192, -165, -156, -158, -137, -140, -159, -114,
+ -167, -123, -164, -97, -148, -122, -112, -140, -90, -115, -78,
+ -111, -104, -86, -114, -49, -142, 12, -166, -28, -168, -17,
+ -113, -58, -89, -81, -116, -67, -58, -65, -96, -68, -92,
+ -33, -99, -37, -87, -67, -90, -37, -68, -114, -65, -87,
+ -50, -124, -30, -83, -1, -96, -4, -55, -19, -87, 13,
+ -80, -10, -90, -5, -84, -42, -79, 4, -67, -63, -38,
+ -27, -36, -23, -7, 11, -34, 14, -18, 51, -74, 41,
+ 9, 19, -7},
+ /* IRC_Composite_C_R0195_T150_P345.wav */
+ {9, -10, 11, -12, 13, -14, 16, -18, 20, -22, 25,
+ -28, 32, -36, 41, -48, 57, -69, 92, -187, -93, -65,
+ 148, -471, 61, 62, -108, -513, 1146, 660, 197, 907, 4643,
+ -2481, -3069, 2384, -436, 10331, -1444, 3529, 10790, 4133, -3114, 1101,
+ 5334, 2231, 175, -4639, -668, 1364, -942, -1832, -179, 106, -201,
+ -621, -288, 256, -14, 7, -70, 10, -62, -163, -538, -397,
+ 250, -662, -393, -549, -98, -597, -308, -388, -216, -452, -393,
+ -291, -335, -411, -150, -445, -160, -290, -89, -416, -142, -299,
+ -403, -360, -239, -271, -373, -246, -311, -338, -233, -221, -259,
+ -317, -192, -311, -255, -207, -237, -276, -272, -275, -277, -233,
+ -252, -250, -181, -293, -170, -245, -207, -274, -163, -258, -249,
+ -201, -205, -239, -243, -229, -292, -303, -216, -288, -278, -312,
+ -241, -345, -256, -284, -302, -346, -258, -265, -246, -271, -236,
+ -265, -215, -277, -207, -240, -196, -255, -168, -178, -155, -144,
+ -107, -152, -110, -104, -74, -180, -121, -180, -122, -196, -144,
+ -245, -129, -207, -127, -182, -107, -225, -82, -192, -124, -217,
+ -97, -227, -156, -196, -108, -174, -119, -141, -77, -130, -30,
+ -126, -49, -162, -24, -161, -3, -167, -53, -117, -53, -120,
+ -68, -81, -70, -85, -45, -100, -52, -108, -40, -95, -39,
+ -123, -50, -123, -64, -117, -58, -114, -101, -106, -61, -84,
+ -90, -69, -81, -96, -40, -52, -52, -64, -50, -43, -17,
+ -37, -37, -34, -28, -67, -18, -49, -26, -48, -2, -23,
+ -34, -24, 7, 4, -14, 13, 9, -1, -21, 7, 5,
+ 3, -11, -9},
+ /* IRC_Composite_C_R0195_T165_P345.wav */
+ {-16, 16, -16, 16, -16, 17, -17, 17, -17, 18, -18,
+ 18, -18, 19, -19, 19, -19, 19, -19, 19, -18, 15,
+ -25, -180, 39, 144, -199, 84, -114, -19, -188, 1254, 741,
+ 544, 587, 3703, -1657, -2617, 2350, 59, 8499, 1392, 3068, 7765,
+ 4121, -1476, -112, 3753, -281, 908, -3882, -912, -13, 232, -1645,
+ 225, -231, 30, -284, 242, 364, 101, -416, -192, -215, -224,
+ -211, -588, -413, 94, -532, -410, -203, -224, -476, -149, -162,
+ -206, -299, -157, -323, -203, -258, -155, -285, -195, -259, -160,
+ -310, -161, -188, -219, -325, -228, -252, -329, -249, -318, -288,
+ -289, -215, -279, -273, -237, -265, -253, -240, -166, -308, -223,
+ -268, -204, -257, -185, -200, -214, -215, -169, -225, -233, -270,
+ -224, -251, -229, -254, -245, -245, -239, -242, -271, -250, -255,
+ -257, -319, -229, -320, -280, -334, -233, -335, -241, -307, -225,
+ -319, -212, -265, -239, -293, -245, -235, -213, -218, -190, -158,
+ -146, -114, -164, -138, -176, -126, -194, -163, -222, -185, -202,
+ -181, -221, -210, -185, -167, -169, -169, -149, -149, -121, -122,
+ -91, -166, -97, -138, -90, -161, -94, -134, -93, -167, -101,
+ -146, -89, -171, -96, -130, -90, -122, -75, -116, -77, -102,
+ -73, -86, -102, -91, -92, -79, -122, -82, -99, -73, -113,
+ -89, -86, -78, -89, -91, -110, -85, -90, -61, -78, -110,
+ -68, -88, -12, -123, -18, -120, 12, -117, 12, -119, 3,
+ -74, 28, -74, -4, -63, 7, -46, 0, -67, 7, -52,
+ 21, -52, 5, -33, 17, 5, -5, -18, -6, 3, -14,
+ -17, -13, -5},
+ /* IRC_Composite_C_R0195_T180_P345.wav */
+ {14, -14, 15, -15, 15, -16, 16, -17, 18, -18, 19, -20,
+ 20, -21, 22, -23, 25, -26, 28, -31, 33, -37, 43, -53,
+ 84, -38, 163, -71, -158, 103, 194, -92, 53, 482, 91, 607,
+ 1972, 2332, -1572, -66, -260, 1180, 4049, -804, 6508, 7047, 3077, -817,
+ 1693, 1744, 560, 329, -3303, 313, -242, -704, -831, 249, -87, -229,
+ 183, 93, 110, 39, -28, -382, -360, -250, -254, -387, -156, -146,
+ -568, -151, -222, 7, -192, -113, -168, -1, -224, -13, -332, -98,
+ -241, -68, -295, -42, -210, -57, -298, -57, -251, -128, -317, -192,
+ -299, -254, -305, -255, -341, -262, -251, -237, -260, -298, -266, -251,
+ -226, -202, -261, -186, -262, -164, -227, -165, -247, -142, -218, -142,
+ -250, -192, -303, -264, -330, -282, -325, -281, -325, -258, -295, -215,
+ -312, -214, -308, -229, -274, -251, -309, -306, -294, -311, -271, -299,
+ -256, -284, -250, -220, -205, -223, -287, -266, -239, -195, -135, -147,
+ -134, -149, -99, -176, -199, -275, -263, -263, -286, -264, -295, -223,
+ -247, -187, -176, -146, -128, -120, -103, -115, -67, -85, -89, -116,
+ -143, -100, -117, -73, -127, -58, -97, -32, -121, -65, -147, -77,
+ -157, -103, -172, -126, -140, -135, -140, -152, -146, -132, -154, -119,
+ -152, -105, -161, -118, -130, -105, -117, -95, -88, -108, -66, -84,
+ -63, -104, -55, -48, -36, -55, -20, -46, -8, -40, 5, -55,
+ -25, -61, -13, -59, -44, -51, -47, -33, -43, -29, -37, -41,
+ -40, -27, -26, -28, -32, -23, -2, -7, 24, -14, -21, -16,
+ -7, -26, -52, -44},
+ /* IRC_Composite_C_R0195_T195_P345.wav */
+ {6, -6, 6, -7, 7, -7, 7, -7, 7, -7, 8, -8,
+ 8, -8, 9, -9, 9, -10, 10, -11, 11, -12, 14, -15,
+ 17, -21, 38, -16, 75, -220, -59, 106, 197, -177, 5, -30,
+ -104, 721, 1540, 1146, -1007, 1149, 558, -528, 530, 189, 5406, 5771,
+ 1722, 1227, 2989, 2251, -549, 199, -1093, 649, 351, -1192, -797, 86,
+ 474, -276, 161, -98, 513, -55, 324, -259, 35, -250, 203, -294,
+ -48, -254, -20, -107, 43, -102, 74, 7, 58, 5, 31, 16,
+ -81, -113, -13, -32, -61, -116, -90, -161, -57, -141, -149, -200,
+ -221, -127, -211, -175, -301, -256, -303, -211, -249, -283, -281, -255,
+ -186, -218, -164, -228, -189, -224, -141, -185, -133, -235, -194, -211,
+ -160, -216, -234, -256, -252, -243, -263, -310, -294, -327, -270, -308,
+ -298, -307, -298, -251, -283, -236, -306, -260, -308, -239, -300, -271,
+ -297, -271, -265, -262, -234, -272, -265, -257, -272, -222, -275, -166,
+ -216, -116, -201, -132, -209, -165, -253, -247, -300, -289, -266, -286,
+ -274, -274, -215, -180, -166, -157, -142, -89, -78, -66, -105, -65,
+ -95, -58, -92, -79, -99, -64, -63, -57, -120, -89, -112, -118,
+ -168, -135, -190, -134, -199, -134, -197, -155, -189, -166, -196, -156,
+ -193, -139, -163, -148, -164, -142, -130, -130, -110, -101, -105, -93,
+ -75, -95, -30, -101, -23, -80, -12, -54, -3, -54, -21, -40,
+ -15, -43, -40, -44, -53, -61, -41, -51, -45, -65, -44, -54,
+ -43, -56, -70, -49, -64, -4, -42, 4, -66, 5, -19, 28,
+ -23, -34, -32, -54},
+ /* IRC_Composite_C_R0195_T210_P345.wav */
+ {-2, 2, -2, 2, -2, 2, -2, 1, -1, 1, -1, 1,
+ -1, 0, 0, 0, 1, -1, 1, -2, 3, -3, 4, -6,
+ 7, -9, 13, -20, 43, -89, -222, 10, 103, -107, -34, -36,
+ 142, -400, -30, 464, 582, 998, 189, -102, 357, 928, -496, 3,
+ 2407, 3014, 4127, 2156, 97, 2572, 2367, 192, -1077, 553, 225, 383,
+ -471, -511, -191, 662, 222, 22, 339, 294, 362, 131, 306, 286,
+ 107, 75, 262, 201, 100, 126, 189, 137, 172, 10, 156, 70,
+ 103, -4, -19, -25, -76, -86, -32, -105, -27, -153, -34, -185,
+ -5, -211, -79, -248, -114, -227, -127, -231, -155, -259, -155, -256,
+ -173, -269, -198, -265, -178, -210, -163, -209, -150, -214, -137, -236,
+ -119, -268, -155, -295, -185, -296, -225, -287, -248, -275, -270, -274,
+ -255, -294, -292, -305, -257, -323, -287, -335, -263, -321, -272, -309,
+ -253, -288, -250, -283, -256, -298, -279, -264, -275, -235, -286, -206,
+ -259, -185, -242, -173, -250, -179, -242, -182, -246, -189, -235, -187,
+ -222, -180, -217, -154, -198, -159, -175, -140, -165, -133, -123, -114,
+ -152, -125, -126, -116, -132, -139, -111, -130, -90, -128, -110, -154,
+ -128, -148, -134, -149, -163, -162, -154, -157, -161, -181, -160, -162,
+ -162, -166, -161, -175, -145, -160, -146, -183, -159, -151, -121, -142,
+ -111, -108, -118, -82, -70, -76, -70, -81, -45, -58, -32, -55,
+ -61, -50, -56, -13, -58, -19, -65, -23, -55, -34, -79, -47,
+ -53, -75, -77, -59, -49, -49, -45, -23, -65, -15, -50, -1,
+ -35, -44, -51, -18},
+ /* IRC_Composite_C_R0195_T225_P345.wav */
+ {4, -4, 4, -4, 5, -5, 5, -5, 5, -5, 5, -5,
+ 5, -5, 5, -6, 6, -6, 6, -6, 6, -6, 7, -7,
+ 7, -7, 8, -8, 8, -8, 9, -22, -68, -102, -39, -33,
+ -2, -125, -43, -65, -81, -88, 504, 491, 271, 136, 50, 242,
+ 413, 575, 246, 1154, 3270, 2343, 1173, 1360, 1386, 1335, 693, 41,
+ -16, 626, 497, -208, 385, -141, 534, 407, 815, 196, 540, 538,
+ 608, 534, 497, 553, 348, 395, 314, 320, 191, 167, 89, 75,
+ -22, 80, -26, 77, -33, 113, -103, 86, -85, 66, -164, -34,
+ -138, -32, -150, -66, -143, -81, -162, -134, -151, -126, -180, -176,
+ -206, -176, -215, -184, -242, -174, -257, -171, -240, -144, -234, -156,
+ -238, -145, -213, -186, -241, -198, -236, -200, -304, -237, -309, -223,
+ -315, -256, -320, -273, -306, -276, -282, -301, -281, -293, -281, -297,
+ -281, -281, -278, -300, -296, -308, -282, -258, -291, -257, -276, -228,
+ -231, -194, -203, -185, -194, -181, -173, -165, -183, -152, -208, -152,
+ -227, -158, -213, -156, -222, -165, -206, -169, -192, -153, -170, -166,
+ -179, -180, -153, -149, -139, -152, -153, -126, -111, -90, -107, -114,
+ -128, -119, -123, -144, -150, -167, -156, -182, -180, -204, -160, -191,
+ -173, -198, -185, -178, -167, -163, -174, -180, -175, -186, -163, -152,
+ -140, -147, -132, -122, -102, -97, -72, -79, -75, -66, -54, -46,
+ -62, -28, -64, -26, -69, -23, -69, -28, -68, -48, -80, -55,
+ -84, -64, -71, -62, -66, -82, -71, -59, -36, -53, -39, -61,
+ -45, -48, -32, -64},
+ /* IRC_Composite_C_R0195_T240_P345.wav */
+ {1, -1, 1, -1, 1, -1, 1, -2, 2, -2, 2, -2,
+ 2, -2, 2, -2, 2, -2, 2, -2, 2, -2, 3, -3,
+ 3, -3, 4, -4, 5, -7, 11, -87, -91, -69, -34, -17,
+ -83, -130, -72, -103, -104, -134, 114, -4, -17, 85, 373, 202,
+ -49, -101, 219, 365, 876, 782, 785, 1628, 2056, 1505, 913, 728,
+ 668, 1183, 655, 491, 465, 893, 831, 430, 584, 712, 1020, 649,
+ 708, 723, 730, 613, 504, 566, 414, 315, 187, 278, 133, 226,
+ 116, 259, 129, 186, 116, 185, 45, 88, -32, 30, -121, -4,
+ -138, -59, -242, -77, -196, -88, -164, -95, -154, -158, -144, -123,
+ -166, -155, -131, -163, -193, -126, -158, -197, -221, -177, -191, -199,
+ -207, -207, -232, -259, -213, -257, -223, -245, -210, -232, -214, -199,
+ -250, -237, -259, -286, -308, -300, -276, -316, -306, -323, -315, -283,
+ -322, -259, -334, -262, -308, -246, -279, -237, -252, -210, -235, -204,
+ -211, -204, -216, -219, -187, -208, -184, -188, -172, -182, -147, -178,
+ -171, -209, -148, -207, -140, -214, -153, -202, -132, -178, -141, -158,
+ -129, -140, -132, -148, -104, -170, -94, -160, -103, -179, -127, -186,
+ -148, -195, -164, -196, -178, -176, -178, -191, -206, -200, -201, -208,
+ -201, -208, -200, -195, -166, -167, -160, -164, -138, -176, -171, -170,
+ -145, -157, -125, -127, -91, -104, -77, -96, -65, -75, -71, -96,
+ -65, -84, -60, -84, -57, -72, -49, -85, -51, -85, -52, -82,
+ -51, -93, -48, -69, -42, -81, -54, -69, -69, -69, -62, -61,
+ -61, -54, -82, -67},
+ /* IRC_Composite_C_R0195_T255_P345.wav */
+ {1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1,
+ 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1,
+ 1, -1, 2, -2, 2, -2, 2, -2, 3, -3, 5, -29,
+ -81, -145, -126, -102, -79, -99, -83, -32, 26, -25, -104, -85,
+ -9, -133, -67, -57, 165, 465, 613, 590, 447, 814, 1074, 1050,
+ 1231, 1480, 1384, 1691, 1545, 721, 693, 1027, 967, 911, 897, 961,
+ 823, 595, 850, 817, 872, 522, 568, 476, 335, 222, 147, 215,
+ 189, 255, 142, 248, 142, 175, 30, 4, 8, -59, -17, -77,
+ -20, -108, -60, -97, -148, -132, -187, -115, -138, -127, -111, -127,
+ -113, -156, -101, -162, -102, -180, -108, -179, -120, -179, -195, -199,
+ -229, -185, -270, -215, -278, -237, -258, -241, -261, -210, -244, -198,
+ -260, -213, -287, -247, -340, -255, -344, -240, -343, -221, -330, -201,
+ -299, -199, -288, -250, -262, -234, -258, -219, -245, -213, -243, -209,
+ -226, -210, -206, -229, -185, -199, -171, -204, -168, -186, -151, -175,
+ -154, -197, -147, -168, -130, -180, -149, -171, -122, -158, -129, -152,
+ -132, -156, -139, -160, -164, -170, -174, -154, -162, -126, -151, -134,
+ -162, -148, -177, -191, -199, -212, -200, -209, -220, -215, -220, -217,
+ -209, -214, -187, -188, -163, -169, -145, -176, -134, -160, -141, -157,
+ -132, -139, -118, -136, -115, -131, -104, -132, -107, -134, -96, -102,
+ -76, -89, -80, -83, -80, -72, -65, -58, -64, -74, -61, -66,
+ -46, -79, -66, -94, -77, -85, -74, -86, -77, -80, -67, -80,
+ -78, -81, -61, -58},
+ /* IRC_Composite_C_R0195_T270_P345.wav */
+ {1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1,
+ 1, -1, 1, -1, 2, -2, 2, -2, 2, -2, 2, -2,
+ 2, -3, 3, -3, 3, -3, 4, -4, 5, -5, 7, -13,
+ -9, 25, 107, 159, 235, 223, 228, 197, 135, 148, 180, 104,
+ 249, 332, 345, 533, 679, 812, 953, 1029, 929, 1077, 1230, 1424,
+ 1718, 1696, 1725, 1631, 997, 986, 1012, 146, -166, 111, 353, 347,
+ -6, -43, 73, 193, 90, 62, -93, -92, -338, -310, -405, -336,
+ -407, -441, -401, -511, -347, -531, -317, -432, -349, -454, -400, -327,
+ -440, -322, -442, -318, -433, -382, -424, -384, -375, -393, -396, -458,
+ -428, -480, -383, -434, -382, -464, -371, -432, -337, -394, -307, -399,
+ -335, -417, -322, -442, -324, -436, -303, -449, -304, -409, -298, -363,
+ -284, -317, -307, -308, -323, -288, -319, -293, -322, -285, -281, -248,
+ -239, -242, -269, -244, -286, -239, -275, -228, -281, -236, -270, -215,
+ -230, -183, -189, -147, -168, -142, -140, -138, -148, -145, -122, -112,
+ -122, -100, -101, -92, -130, -111, -155, -125, -154, -111, -187, -117,
+ -190, -95, -171, -106, -163, -109, -148, -117, -153, -132, -173, -135,
+ -163, -114, -185, -122, -191, -116, -168, -92, -150, -108, -141, -94,
+ -122, -95, -150, -97, -145, -88, -132, -65, -125, -53, -97, -33,
+ -83, -12, -48, -5, -27, 7, -11, 35, -15, 26, -18, 26,
+ -29, 3, -32, -9, -39, 15, 11, 7, 13, 12, 6, 12,
+ 11, 20, 5, 2, 4, 3, -12, -24, -23, -5, -3, -22,
+ 4, 5, 1, 4},
+ /* IRC_Composite_C_R0195_T285_P345.wav */
+ {0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 1, -1,
+ 1, -1, 1, -1, 1, -1, 1, -1, 1, -2, 2, -2,
+ 2, -2, 3, -3, 3, -4, 5, -6, 8, -11, 22, -196,
+ -173, -113, -87, -175, -179, -137, -214, -236, -209, -197, -203, -89,
+ 115, 94, 63, 76, -21, -123, -58, 550, 837, 1139, 1631, 2133,
+ 1768, 1607, 1992, 1501, 200, 482, 1172, 950, 840, 796, 772, 845,
+ 1106, 940, 1271, 764, 758, 561, 601, 592, 456, 196, 274, 22,
+ 269, 50, 133, 94, 119, 18, 4, 153, 30, 97, 24, 85,
+ 20, 33, 14, -38, -26, -152, -46, -172, -60, -155, -115, -148,
+ -198, -89, -195, -112, -261, -100, -214, -212, -183, -197, -150, -219,
+ -140, -179, -164, -230, -178, -187, -230, -226, -257, -158, -281, -201,
+ -228, -189, -261, -210, -220, -259, -267, -256, -233, -276, -267, -249,
+ -227, -294, -254, -243, -276, -267, -297, -257, -292, -260, -305, -245,
+ -279, -264, -243, -230, -212, -214, -151, -148, -130, -127, -88, -74,
+ -103, -75, -88, -67, -122, -69, -96, -106, -123, -96, -146, -128,
+ -172, -140, -152, -152, -190, -154, -179, -153, -191, -155, -209, -127,
+ -197, -114, -197, -134, -193, -131, -195, -130, -172, -126, -176, -158,
+ -191, -169, -206, -154, -203, -183, -230, -172, -205, -185, -204, -179,
+ -196, -157, -166, -116, -150, -115, -126, -82, -123, -71, -85, -83,
+ -83, -68, -72, -63, -78, -79, -49, -75, -79, -92, -70, -98,
+ -66, -95, -75, -105, -104, -85, -82, -92, -105, -73, -107, -87,
+ -82, -75, -79, -91},
+ /* IRC_Composite_C_R0195_T300_P345.wav */
+ {-1, 1, -1, 1, -1, 1, -1, 1, -1, 2, -2, 2,
+ -2, 2, -2, 2, -2, 2, -2, 2, -2, 3, -3, 3,
+ -3, 4, -4, 5, -6, 8, -10, 16, -33, 4, 109, 115,
+ 258, 119, 96, 128, 286, 309, 258, 544, 795, 778, 461, 339,
+ 836, 601, 406, 1682, 1975, 1154, 2276, 2834, 1223, 701, 931, 69,
+ -668, -254, 489, 635, -539, 173, 339, 531, 396, 94, 191, -102,
+ 150, -127, -75, -251, -176, -409, -489, -562, -686, -623, -666, -571,
+ -644, -520, -569, -434, -437, -442, -489, -355, -376, -438, -545, -399,
+ -497, -467, -538, -479, -549, -412, -479, -420, -458, -466, -435, -392,
+ -372, -360, -342, -413, -353, -363, -348, -419, -344, -451, -370, -415,
+ -367, -468, -389, -500, -406, -491, -367, -474, -360, -481, -350, -435,
+ -296, -421, -285, -402, -267, -363, -222, -350, -221, -334, -215, -270,
+ -177, -235, -146, -214, -118, -175, -89, -140, -81, -133, -96, -92,
+ -54, -91, -56, -86, -58, -64, -32, -51, -40, -76, -57, -55,
+ -36, -113, -96, -83, -105, -91, -129, -152, -109, -131, -124, -142,
+ -150, -156, -119, -136, -143, -152, -125, -140, -130, -126, -129, -99,
+ -125, -119, -110, -121, -109, -118, -102, -117, -98, -117, -101, -93,
+ -114, -105, -94, -69, -112, -75, -86, -60, -49, -25, -59, 6,
+ -33, -1, -8, 8, -2, 17, 23, 0, 29, 13, 17, 24,
+ 18, 21, 18, 23, -13, 11, -8, 15, 5, 10, -10, 8,
+ -24, 7, 1, -11, 1, 27, -4, 48, 5, 53, 24, 86,
+ 43, 68, 50, 99},
+ /* IRC_Composite_C_R0195_T315_P345.wav */
+ {1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -2,
+ 2, -2, 2, -2, 2, -2, 2, -2, 2, -2, 2, -2,
+ 2, -2, 2, -2, 2, -1, 0, 6, -137, -110, -314, -483,
+ -264, -184, -467, -482, 89, -336, 204, 277, 141, -93, -770, -71,
+ 819, -837, 1577, 3127, 1312, 2383, 3512, 2495, -80, 350, 890, 699,
+ 71, 561, 954, 294, 1113, 1117, 1259, 495, 984, 849, 877, 553,
+ 746, 524, 537, 393, 441, 428, 542, 429, 296, 210, 204, 231,
+ 133, -55, 33, -7, -111, -11, -91, 34, -190, -56, -131, 39,
+ -14, 66, -79, 58, -96, 105, -60, 169, -64, 84, -143, 118,
+ -100, -84, -157, -91, -304, -233, -224, -201, -251, -259, -309, -306,
+ -307, -286, -299, -265, -407, -357, -301, -345, -348, -313, -294, -342,
+ -334, -338, -298, -266, -288, -261, -298, -254, -268, -227, -236, -260,
+ -244, -223, -227, -260, -233, -195, -202, -171, -228, -156, -170, -114,
+ -170, -107, -146, -105, -89, -96, -83, -40, -73, -59, -66, -65,
+ -71, -53, -107, -74, -75, -92, -122, -102, -105, -87, -130, -86,
+ -139, -115, -142, -131, -148, -147, -160, -144, -151, -139, -173, -139,
+ -186, -126, -170, -147, -194, -137, -189, -141, -184, -162, -186, -151,
+ -208, -180, -195, -172, -186, -165, -204, -165, -157, -155, -139, -132,
+ -141, -108, -126, -89, -110, -98, -101, -113, -94, -111, -133, -99,
+ -126, -121, -126, -97, -115, -103, -122, -84, -114, -58, -120, -106,
+ -109, -97, -103, -75, -128, -91, -104, -73, -91, -53, -59, -46,
+ -63, -33, -22, -14},
+ /* IRC_Composite_C_R0195_T330_P345.wav */
+ {-9, 9, -9, 9, -9, 9, -9, 9, -9, 9, -9, 9,
+ -9, 9, -9, 9, -9, 9, -9, 10, -10, 10, -10, 10,
+ -10, 10, -10, 10, -7, -378, -458, 118, -356, -539, -320, -251,
+ -977, -150, 272, 171, 262, 290, -552, -354, -2031, 2393, -54, -667,
+ 4888, 3245, 1575, 3792, 4173, -210, -907, 432, 1658, -334, 338, 623,
+ 940, 723, 1598, 1314, 1216, 764, 1038, 976, 401, 116, 250, 473,
+ 239, 334, 528, 294, 340, 65, 340, 194, 348, 152, 304, 92,
+ 279, 123, 93, -24, 89, -192, -125, -56, -130, 68, 194, -86,
+ 77, 174, 220, 128, 98, -234, -216, -168, -160, -251, -283, -410,
+ -399, -240, -227, -363, -327, -361, -302, -370, -271, -312, -309, -428,
+ -402, -318, -223, -266, -304, -253, -264, -272, -246, -203, -278, -299,
+ -319, -254, -236, -260, -281, -188, -203, -212, -220, -185, -220, -178,
+ -230, -207, -215, -227, -248, -182, -197, -163, -179, -101, -143, -112,
+ -138, -84, -115, -82, -111, -83, -102, -83, -51, -69, -49, -89,
+ -18, -67, -73, -108, -72, -63, -79, -111, -167, -94, -130, -99,
+ -166, -109, -181, -121, -156, -124, -154, -131, -162, -169, -165, -170,
+ -136, -151, -159, -185, -163, -122, -139, -141, -175, -141, -148, -148,
+ -172, -157, -130, -154, -150, -148, -142, -153, -141, -116, -159, -97,
+ -155, -91, -162, -92, -141, -53, -134, -97, -132, -68, -111, -97,
+ -127, -98, -128, -105, -137, -81, -141, -76, -120, -73, -136, -97,
+ -105, -48, -102, -73, -108, -56, -97, -41, -75, -56, -70, -20,
+ -33, -9, -51, -32},
+ /* IRC_Composite_C_R0195_T345_P345.wav */
+ {1, -2, 2, -3, 4, -4, 5, -6, 7, -8, 9,
+ -10, 11, -13, 14, -16, 17, -19, 21, -22, 24, -25,
+ 25, -22, 13, 15, -152, -764, 140, -602, 18, -900, -519,
+ -1092, -545, 489, 261, 706, -1198, 1895, -1912, -2689, 2900, 30,
+ -3177, 8188, 3071, 2405, 4243, 6073, 615, -3233, 507, 1858, -510,
+ -537, 1688, 110, 230, 1724, 2185, 1547, 1263, 1871, 1145, 573,
+ 342, -23, 261, -614, -9, -121, 805, -137, 38, 285, 272,
+ -29, 312, 280, 184, 234, 346, -32, 603, 80, 194, 153,
+ 323, -216, 458, 198, 10, -275, 28, -275, -40, -248, -266,
+ -416, -305, -349, -213, -336, -338, -460, -267, -400, -247, -403,
+ -303, -379, -264, -460, -298, -347, -227, -420, -309, -405, -246,
+ -238, -250, -319, -267, -250, -266, -221, -240, -318, -184, -236,
+ -175, -305, -161, -225, -161, -213, -190, -219, -158, -206, -155,
+ -198, -165, -211, -169, -178, -141, -180, -148, -191, -154, -191,
+ -120, -222, -141, -171, -68, -131, -104, -142, -95, -72, -93,
+ -71, -91, -94, -90, -92, -83, -97, -91, -117, -108, -109,
+ -102, -101, -101, -116, -95, -82, -119, -115, -158, -119, -137,
+ -108, -151, -151, -163, -123, -125, -148, -167, -118, -168, -149,
+ -196, -146, -169, -130, -179, -147, -164, -153, -114, -109, -119,
+ -128, -121, -82, -111, -89, -98, -81, -102, -105, -71, -85,
+ -85, -116, -85, -98, -76, -124, -102, -118, -107, -84, -126,
+ -141, -114, -78, -80, -110, -127, -121, -93, -112, -96, -109,
+ -98, -90, -55, -53, -66, -54, -41, -26, -18, -19, -5,
+ -33, -57, -33}};
+
+const int16_t irc_composite_c_r0195_p000[][256] =
+ {/* IRC_Composite_C_R0195_T000_P000.wav */
+ {1, -1, 1, -1, 0, 0, 0, 0, 0, 1, -1,
+ 2, -2, 2, -3, 4, -5, 6, -7, 9, -11, 14,
+ -20, 33, -756, -534, -348, 102, -1032, -582, -1506, 65, -959,
+ 133, -210, 1928, 576, -291, -5368, 5774, -6962, -2265, 14308, -3483,
+ 7090, 10599, 2076, -2731, 3149, -192, -3412, -1652, 3866, 548, -1226,
+ -1088, 2012, 2077, 1960, -209, 1087, 1404, 1871, 1150, 1665, 536,
+ 626, 1200, 1268, 592, -114, -160, -95, 133, -37, -246, 158,
+ -144, -190, -548, 421, 96, 105, -192, 181, 129, 158, 143,
+ 73, -164, -261, -64, -147, -128, -55, -166, -60, 98, 349,
+ -253, 126, -172, 110, -391, -118, 265, -317, 256, -97, 416,
+ -62, 274, -244, -355, -104, -427, -357, -741, -368, -489, -365,
+ -418, -498, -150, -384, -361, -530, -257, -297, -381, -491, -511,
+ -393, -429, -294, -395, -373, -408, -292, -342, -260, -297, -286,
+ -427, -324, -291, -291, -276, -319, -244, -386, -221, -241, -141,
+ -260, -273, -231, -153, -86, -203, -202, -152, -95, -140, -176,
+ -127, -110, -156, -237, -148, -139, -94, -248, -190, -202, -85,
+ -155, -205, -223, -169, -158, -183, -216, -167, -164, -146, -219,
+ -103, -162, -71, -220, -10, -169, 7, -134, 32, -76, -42,
+ -100, -66, -50, -44, -71, -88, -122, -40, -75, 21, -78,
+ -18, -125, -33, -69, 53, -77, -22, -138, -41, -62, -55,
+ -92, -83, -99, -115, -150, -17, -53, -12, -184, -50, -96,
+ 28, -81, -1, -78, -41, -118, -14, -66, 38, -82, -29,
+ -104, 29, 5, 34, -62, 14, 7, 47, -61, 67, -43,
+ 51, -145, 61},
+ /* IRC_Composite_C_R0195_T015_P000.wav */
+ {123, -125, 127, -129, 131, -133, 135, -137, 138, -140,
+ 141, -141, 141, -140, 137, -131, 122, -106, 76, -10,
+ -238, -830, -319, -1069, 17, -859, 165, -1424, -309, -2002,
+ 526, 695, 3378, -155, -423, -6705, 9134, -12025, 1719, 14823,
+ -7846, 16119, 11510, -1243, -4202, 5454, -1293, -2996, -1156, 2894,
+ -853, -440, -59, 2319, 814, 827, 428, 2620, 1829, 1181,
+ 817, 1748, 469, 1133, 599, 1385, -490, 316, -1523, 369,
+ -621, 391, -835, 160, -740, 77, -149, 751, -195, -103,
+ -622, 191, -100, 342, -303, -90, -465, 82, 62, 259,
+ -409, -385, 44, 87, -50, 21, 54, -467, -207, -79,
+ -324, -108, -90, -152, -620, 71, -110, 47, -749, -161,
+ -523, -153, -446, -157, -336, 40, -137, -86, -507, -177,
+ -322, -14, -430, -277, -557, -360, -326, -296, -274, -463,
+ -359, -502, -263, -388, -316, -369, -353, -459, -464, -335,
+ -293, -324, -333, -369, -324, -380, -288, -268, -287, -294,
+ -317, -270, -395, -181, -204, -149, -383, -214, -212, -113,
+ -188, -174, -211, -188, -99, -166, -173, -203, -111, -134,
+ -148, -226, -166, -126, -86, -216, -199, -234, -119, -193,
+ -118, -250, -172, -245, -101, -156, -114, -172, -153, -162,
+ -98, -70, -71, -106, -70, -81, -14, -14, 37, -38,
+ -36, -107, -3, -9, 52, -91, -63, -116, 20, -50,
+ 18, -151, -37, -125, 57, -97, 31, -149, 5, -133,
+ 4, -141, 9, -132, -7, -168, 9, -124, 33, -144,
+ 22, -166, 15, -117, 46, -156, 11, -127, 36, -124,
+ 23, -58, 69, -65, 71, -34, 110, -36, 130, -45,
+ 96, -68, 96, -59, 38, -71},
+ /* IRC_Composite_C_R0195_T030_P000.wav */
+ {-63, 66, -69, 73, -76, 80, -84, 89, -93, 98,
+ -102, 107, -110, 111, -108, 95, -54, -154, -785, -302,
+ -1153, 268, -1139, 240, -1422, 320, -2283, 272, -629, 4046,
+ 237, 3112, -7137, 2285, 231, -13166, 20036, -5068, 11914, 16533,
+ 2581, -5753, 2916, 1267, -3077, -1961, 1642, -123, 210, 665,
+ 1080, 1069, 485, 1122, 1846, 1948, 901, 1486, 921, 1132,
+ 470, 865, -186, -57, -358, -153, -489, -118, -724, -365,
+ -517, -156, -335, 155, -255, 96, -315, 376, -169, 22,
+ -435, 5, -544, -35, -484, -12, -457, 280, -453, -177,
+ -399, 435, -423, -188, -484, 161, -217, -38, -732, -416,
+ -157, 73, -480, -440, -302, -140, -260, -335, -490, -420,
+ -470, -291, -617, -112, -315, 20, -559, -210, -412, 154,
+ -162, -102, -453, -225, -205, -23, -247, -203, -409, -147,
+ -366, -147, -367, -150, -477, -326, -502, -222, -390, -294,
+ -498, -249, -423, -313, -510, -239, -382, -176, -516, -351,
+ -496, -92, -337, -150, -462, -212, -366, -74, -288, -94,
+ -322, -110, -334, -53, -240, 11, -306, -108, -332, -45,
+ -271, -52, -295, -76, -308, -99, -319, -57, -226, -50,
+ -303, -130, -254, -54, -183, -89, -261, -98, -193, -6,
+ -184, -12, -164, 45, -175, 27, -120, 129, -69, 82,
+ -89, 120, -26, 113, -48, 59, -77, 18, -85, -3,
+ -107, 0, -78, -29, -159, 0, -62, 67, -139, -9,
+ -138, 19, -136, -3, -124, -27, -149, -30, -136, -11,
+ -97, -7, -137, -19, -96, 20, -84, 15, -64, 28,
+ -92, 9, 15, 121, -12, 24, -32, 74, 36, 98,
+ -9, 3, -30, 36, -42, 4},
+ /* IRC_Composite_C_R0195_T045_P000.wav */
+ {72, -74, 77, -79, 82, -86, 91, -96, 102, -111, 121,
+ -134, 152, -179, 228, -635, 490, -1252, -114, -1202, 742, -1779,
+ 495, -1563, 532, -2895, 2675, 1167, 4718, -4709, 4646, -5559, -6673,
+ 7608, -8298, 21201, 11442, 4596, 920, 5308, -1818, -1353, -1082, -283,
+ -725, 2349, -984, -140, 614, 1696, 1859, 2003, 557, 2080, 1067,
+ 416, 1112, 525, 38, -87, -851, -336, 38, -375, -495, -291,
+ -403, -90, -299, -393, -130, -647, -37, -270, -227, -182, -2,
+ -485, -21, -380, -221, -179, -364, -244, -343, -247, -161, -213,
+ -443, -201, -386, -349, -198, -453, -401, -479, -187, -349, -331,
+ -486, -40, -343, -132, -248, -346, -150, -185, -498, -200, -280,
+ -512, -271, -152, -472, -405, -421, -235, -444, -272, -369, -299,
+ -285, -209, -483, -192, -140, -287, -241, -120, -328, 36, -232,
+ -275, -345, -26, -488, -78, -319, -205, -488, 22, -390, -294,
+ -391, -131, -450, -198, -416, -347, -391, -95, -530, -266, -359,
+ -162, -403, -129, -438, -172, -313, -124, -327, -146, -338, -102,
+ -245, -121, -328, -124, -244, -79, -277, -128, -320, -47, -241,
+ -144, -306, -70, -274, -85, -274, -147, -219, -71, -212, -78,
+ -214, -60, -127, 0, -182, -21, -106, 67, -97, 35, -113,
+ 84, 16, 65, -67, 99, -24, 79, 7, 29, -85, 99,
+ -35, 17, -91, 57, -100, 58, -112, -18, -130, 73, -160,
+ -25, -148, 6, -136, 45, -217, 2, -142, 79, -180, 64,
+ -198, 81, -156, 114, -200, 98, -162, 123, -120, 152, -170,
+ 176, -85, 130, -107, 189, -99, 158, -113, 138, -127, 161,
+ -140, 111, -221},
+ /* IRC_Composite_C_R0195_T060_P000.wav */
+ {123, -132, 142, -153, 166, -180, 196, -215, 238, -264, 296,
+ -338, 397, -545, -26, -1070, 548, -1189, 312, -1132, -540, -593,
+ 703, -1932, 23, 1834, 2821, 3650, -4896, 2244, -8990, 3547, -3667,
+ 11688, 19991, 4002, -67, 5265, -1066, -618, 257, -237, -93, 2490,
+ -1554, -1096, 1601, 944, 1501, 1048, 1374, 1754, 1792, 566, 335,
+ -350, 932, -271, -1525, -771, -646, -466, 146, -610, 113, -1035,
+ 510, -222, -23, -608, -443, -725, 203, -887, -434, -589, -19,
+ -438, -40, -610, 14, -467, -137, -372, -214, -629, -55, -580,
+ -263, -517, -350, -563, -317, -499, -281, -660, -233, -369, 6,
+ -533, -180, -457, -90, -331, -140, -395, -150, -246, -124, -225,
+ -134, -388, -151, -322, -330, -363, -280, -394, -241, -503, -138,
+ -532, -169, -402, -263, -433, -255, -363, -244, -372, -271, -364,
+ -268, -337, -126, -472, -58, -441, 6, -373, -75, -345, -91,
+ -290, -171, -369, -128, -311, -189, -312, -183, -241, -230, -309,
+ -78, -303, -168, -337, -178, -236, -164, -270, -238, -313, -113,
+ -261, -195, -270, -208, -224, -136, -312, -153, -332, -79, -287,
+ -193, -257, -149, -227, -124, -297, -82, -209, -107, -181, -87,
+ -173, -18, -146, 9, -126, 28, -74, -22, -25, 22, -50,
+ 40, -41, 25, -17, -14, 5, 9, -67, 23, -34, 57,
+ -46, 5, 3, 37, -39, 6, -36, 11, -54, -39, -69,
+ -61, -79, -35, -99, -38, -98, -29, -88, -12, -86, -19,
+ -71, -19, -37, -12, -58, 7, -41, 34, -37, 47, -22,
+ 42, -26, 97, 2, 68, -13, 74, 7, 35, -6, 34,
+ -1, 0, -38},
+ /* IRC_Composite_C_R0195_T075_P000.wav */
+ {30, -35, 41, -47, 55, -63, 72, -84, 97, -113, 134,
+ -162, 202, -472, -304, -372, 274, -1176, -371, -433, -535, -54,
+ 364, -1802, 1909, 3073, 5773, -6217, -2323, -1631, -4047, 10163, -533,
+ 23315, 9574, -7064, 764, 4451, 279, -686, 938, 2910, -1594, -781,
+ 31, 2302, -241, 1013, 1336, 1127, 1162, 699, 28, 761, 380,
+ -426, -314, -917, -750, -202, -732, -632, -472, -517, -73, -213,
+ -19, -699, -215, -440, -368, -595, -354, -716, -262, -604, -448,
+ -306, -492, -226, -481, -404, -393, -315, -472, -290, -442, -401,
+ -227, -459, -350, -606, -194, -594, -308, -438, -436, -423, -217,
+ -461, -220, -298, -300, -185, -218, -342, -120, -282, -142, -260,
+ -82, -276, -46, -282, -219, -258, -93, -447, -193, -418, -296,
+ -377, -249, -479, -177, -358, -304, -416, -185, -366, -179, -486,
+ -287, -406, -134, -512, -167, -454, -201, -352, -195, -386, -169,
+ -289, -223, -334, -186, -280, -123, -302, -167, -268, -36, -301,
+ -139, -264, -133, -195, -111, -276, -131, -175, -123, -195, -148,
+ -226, -77, -211, -165, -231, -123, -257, -132, -285, -135, -289,
+ -147, -278, -141, -274, -166, -238, -154, -189, -134, -212, -101,
+ -127, -6, -142, -21, -106, 41, -81, 41, -105, 54, -54,
+ 6, -41, -3, -62, 27, -57, -25, -42, 29, -39, 10,
+ -90, 56, -38, 43, -106, 38, -63, 27, -58, -21, -110,
+ -19, -82, -36, -124, -57, -83, 23, -113, 9, -73, 55,
+ -109, 34, -55, 45, -13, 62, -54, 27, -33, 52, 12,
+ 35, -34, 72, -19, 33, -32, 74, -59, 39, -25, 53,
+ 1, 10, -3},
+ /* IRC_Composite_C_R0195_T090_P000.wav */
+ {-3, 3, -4, 5, -6, 7, -9, 11, -13, 17,
+ -23, 36, -967, 38, -432, 430, -1179, 327, -1275, 279,
+ -1297, 1913, -1800, 360, 826, 8131, -4106, 182, -6660, -1308,
+ 8763, -6147, 21004, 13661, -1493, -3341, 815, 6070, 1330, -1696,
+ 3689, 487, -871, -2812, 2923, 1220, 319, -441, 2019, 161,
+ 1147, -214, 819, -1063, 179, -1133, 125, -1120, 701, -1109,
+ -441, -308, -3, -1227, 135, -661, -529, -162, -375, -951,
+ -262, -371, -533, -415, -491, -644, -459, -451, -508, -551,
+ -360, -465, -434, -399, -416, -458, -204, -423, -358, -363,
+ -290, -434, -346, -447, -174, -475, -424, -400, -239, -439,
+ -175, -468, -418, -188, -90, -409, -214, -233, -225, -147,
+ -140, -439, -299, -104, -157, -229, -145, -201, -232, -109,
+ -193, -310, -284, -271, -306, -318, -342, -330, -320, -362,
+ -208, -295, -346, -295, -212, -394, -231, -297, -357, -348,
+ -177, -356, -301, -275, -291, -319, -184, -255, -307, -178,
+ -245, -228, -206, -194, -280, -137, -218, -191, -183, -203,
+ -186, -105, -193, -151, -164, -132, -164, -77, -203, -113,
+ -167, -192, -164, -75, -239, -194, -160, -166, -186, -77,
+ -232, -182, -148, -63, -204, -92, -164, -69, -54, 4,
+ -140, -18, -43, 36, -61, 41, -116, -2, -92, 42,
+ -126, -61, -150, -8, -117, -15, -62, -69, -114, 61,
+ -14, 10, -74, 8, -25, 119, -37, 0, -69, 10,
+ -56, 37, -94, -111, -86, 30, -107, -71, -119, 4,
+ -92, 38, -125, 29, -50, 58, -57, 64, -84, 92,
+ -4, 67, -73, 54, -8, 88, -67, 71, 21, 64,
+ -36, 123, -7, 23, -36, 115},
+ /* IRC_Composite_C_R0195_T105_P000.wav */
+ {67, -63, 58, -51, 43, -33, 19, -1, -24, 61, -121,
+ 244, -927, 183, -686, 327, -496, -330, -486, -340, -314, 396,
+ 182, -1212, 1259, 6063, -556, -2378, -2582, -3838, 9614, -8512, 18557,
+ 14219, -782, -2750, 529, 5714, 4180, -1314, 2811, 874, -1413, -2611,
+ 2334, 2590, -537, -811, 436, 1284, 515, -112, -879, -106, -221,
+ -558, -955, -184, -84, -312, -553, -219, -379, -172, -456, -790,
+ -576, -350, -708, -606, -553, -357, -404, -422, -537, -414, -291,
+ -515, -518, -625, -263, -417, -492, -719, -360, -360, -368, -573,
+ -481, -304, -142, -306, -536, -356, -164, -154, -398, -429, -388,
+ -147, -288, -315, -532, -144, -267, -143, -527, -186, -329, 6,
+ -447, -226, -375, 20, -329, -159, -444, -77, -329, -143, -450,
+ -93, -327, 2, -390, -87, -374, 55, -418, -96, -445, -37,
+ -436, -129, -505, -118, -444, -152, -487, -172, -395, -135, -432,
+ -170, -368, -96, -414, -145, -394, -32, -326, -123, -434, -83,
+ -276, -94, -355, -149, -242, -56, -256, -166, -269, -75, -228,
+ -119, -313, -87, -207, -58, -304, -97, -234, -23, -285, -126,
+ -259, -11, -242, -91, -247, -1, -204, -10, -242, -5, -194,
+ 74, -169, 102, -92, 170, -62, 135, -93, 108, -134, 5,
+ -190, -8, -203, -47, -223, 16, -187, 1, -174, 58, -131,
+ 78, -113, 75, -130, 98, -75, 44, -121, 101, -58, 53,
+ -146, 47, -70, 70, -128, -19, -108, 21, -89, -23, -119,
+ -27, -39, 31, -124, -51, -37, 65, -45, 0, 4, 66,
+ 6, 17, 11, 14, 30, 64, 44, 27, -16, 79, -9,
+ -11, -60, 55},
+ /* IRC_Composite_C_R0195_T120_P000.wav */
+ {-76, 75, -75, 74, -72, 69, -65, 59, -49, 35,
+ -14, -21, 85, -263, 347, 27, -533, -67, -385, -42,
+ -335, 645, -2065, 1305, 1033, 1922, -2255, 6706, -3555, 1838,
+ -9048, 1510, 15767, 522, 6485, 7416, 2582, -1635, -1123, 7704,
+ 6890, -3746, -2349, -815, 4058, -1305, 633, -1081, 2060, -1086,
+ 490, -1435, 1038, -465, -103, -1558, 594, -576, -246, -797,
+ -411, -729, 216, -627, -444, -642, 122, -670, -147, -796,
+ -255, -906, 76, -717, -449, -584, -119, -685, -337, -606,
+ -352, -326, -478, -502, -420, -468, -445, -428, -466, -351,
+ -293, -513, -155, -443, -243, -371, -82, -573, -58, -388,
+ -209, -303, -195, -394, -165, -220, -376, -192, -262, -255,
+ -289, -234, -278, -284, -271, -328, -227, -325, -185, -358,
+ -162, -289, -168, -301, -148, -278, -221, -237, -227, -212,
+ -236, -164, -236, -223, -147, -209, -256, -222, -209, -360,
+ -125, -328, -206, -254, -219, -307, -162, -296, -276, -174,
+ -324, -187, -268, -155, -286, -159, -317, -134, -252, -195,
+ -181, -236, -204, -162, -148, -226, -141, -227, -132, -141,
+ -218, -189, -165, -114, -288, -111, -256, -76, -252, -50,
+ -308, -80, -158, -63, -217, -126, -102, 12, -10, -77,
+ 13, 74, 33, 49, -22, 50, -56, 57, -177, -13,
+ -201, 19, -144, -101, -181, 59, -32, -49, -45, -72,
+ -16, 32, -40, -48, -33, -42, -45, 64, -145, 8,
+ -69, 33, -144, 44, -92, 56, -118, -2, -60, 8,
+ -77, -3, -91, -37, -73, -11, -53, 0, -49, 56,
+ -45, 17, -73, 100, 6, 64, -7, 125, -47, 94,
+ 22, 33, -55, 12, -30, 7},
+ /* IRC_Composite_C_R0195_T135_P000.wav */
+ {-47, 46, -46, 44, -43, 40, -37, 33, -28, 21, -12,
+ -1, 20, -48, 94, -206, 95, -290, -258, 153, -592, 203,
+ -293, 297, -1153, 1348, 644, 1791, -1433, 6094, -3856, -260, -5383,
+ 6042, 11744, -1699, 5991, 8208, 1865, -2201, 2510, 6226, 4590, -5126,
+ -1361, 782, 2433, -2030, 854, -833, 853, -1117, 568, -1205, 680,
+ -1070, -374, -797, 196, -738, -381, -460, 54, -509, 80, -809,
+ -117, -538, -17, -916, 203, -876, -170, -815, -257, -782, -178,
+ -520, -324, -433, -344, -353, -383, -382, -403, -522, -240, -512,
+ -312, -523, -189, -533, -114, -477, -247, -366, -173, -363, -247,
+ -204, -351, -273, -220, -239, -329, -247, -257, -330, -207, -331,
+ -212, -342, -200, -298, -246, -261, -277, -228, -264, -213, -328,
+ -212, -273, -219, -256, -244, -234, -265, -159, -264, -191, -244,
+ -157, -229, -190, -221, -203, -203, -265, -198, -238, -189, -235,
+ -164, -189, -220, -183, -216, -189, -257, -194, -227, -199, -234,
+ -209, -214, -214, -195, -212, -179, -255, -200, -207, -151, -257,
+ -182, -194, -172, -200, -169, -219, -215, -208, -180, -207, -192,
+ -215, -173, -203, -137, -219, -101, -232, -121, -175, -4, -148,
+ -45, -92, 5, -35, 0, -5, -45, 0, 8, 10, -53,
+ -107, -5, -81, 6, -157, 3, -130, 19, -128, 39, -138,
+ 5, -65, 57, -90, 7, -52, 45, -79, 13, -78, 29,
+ -109, 14, -79, 25, -126, 7, -61, -11, -75, 1, -51,
+ -46, -64, -35, -74, -40, -48, 36, -46, 3, -64, 53,
+ -59, 33, -26, 105, 4, 63, 13, 64, -28, 24, -1,
+ 3, -80, -21},
+ /* IRC_Composite_C_R0195_T150_P000.wav */
+ {-5, 6, -7, 7, -8, 9, -10, 11, -12, 13, -15,
+ 16, -17, 19, -20, 20, -17, 1, -234, 227, -421, 117,
+ -50, -175, -40, 33, -222, 178, 1388, 586, 910, 1027, 4228,
+ -7050, 1223, 2017, 6153, 4669, 1121, 8628, 6102, -3597, 1339, 6488,
+ 2001, -1618, -2670, 715, 665, -9, -1202, 163, -501, 22, -977,
+ 268, -483, 330, -922, 182, -469, 62, -791, -223, -334, -215,
+ -485, -347, -491, -176, -429, -178, -696, -63, -705, -191, -727,
+ -100, -715, -18, -605, -161, -531, -137, -475, -197, -485, -201,
+ -433, -135, -408, -232, -404, -136, -441, -131, -469, -151, -475,
+ -144, -469, -155, -431, -165, -416, -207, -381, -199, -342, -208,
+ -344, -172, -301, -189, -339, -193, -297, -159, -303, -169, -283,
+ -144, -295, -190, -323, -198, -312, -172, -324, -214, -279, -169,
+ -264, -159, -230, -157, -232, -160, -206, -167, -240, -200, -232,
+ -191, -234, -175, -238, -189, -225, -142, -206, -214, -206, -200,
+ -152, -232, -176, -232, -184, -217, -152, -205, -194, -188, -192,
+ -160, -199, -163, -211, -186, -234, -194, -218, -212, -270, -194,
+ -202, -181, -219, -182, -202, -169, -194, -142, -192, -142, -138,
+ -58, -121, -63, -104, -7, -62, -12, -40, 0, -55, -16,
+ -33, -41, -87, -38, -71, -48, -89, -7, -81, -22, -59,
+ 5, -41, -28, -47, -20, -14, -23, -20, -10, -31, -22,
+ -25, -11, -55, -34, -32, -33, -44, -53, 1, -30, -56,
+ -58, -29, -61, -47, -27, -44, -54, -52, -32, -37, -63,
+ -16, 18, 1, -3, 18, 72, 0, 21, 9, 42, -23,
+ -7, -40, -42},
+ /* IRC_Composite_C_R0195_T165_P000.wav */
+ {-31, 31, -31, 31, -30, 30, -30, 29, -28, 27, -25, 23,
+ -20, 16, -12, 5, 5, -18, 40, -78, 187, -74, -49, 55,
+ -110, 112, -199, 218, -425, 530, 199, 1304, 62, 2352, 168, 2823,
+ -5315, 837, 4953, 2252, 4624, 4623, 6361, 3213, -1540, 938, 5698, -601,
+ -1840, -1321, 415, -514, -55, -892, 3, -75, 186, -753, 471, -260,
+ 192, -752, 21, -345, -220, -374, -258, -349, -282, -436, -427, -398,
+ -253, -570, -291, -449, -37, -524, -88, -564, -148, -493, -85, -435,
+ -73, -317, -105, -359, -181, -335, -176, -321, -195, -365, -206, -391,
+ -180, -383, -187, -437, -200, -456, -199, -434, -221, -404, -185, -335,
+ -239, -304, -221, -288, -247, -317, -222, -256, -192, -283, -227, -290,
+ -193, -268, -185, -298, -185, -261, -149, -305, -194, -283, -162, -272,
+ -207, -271, -209, -214, -185, -206, -201, -195, -156, -200, -178, -267,
+ -179, -273, -180, -271, -149, -245, -175, -193, -143, -182, -204, -213,
+ -175, -213, -191, -252, -204, -249, -193, -210, -188, -202, -194, -140,
+ -186, -146, -188, -134, -188, -186, -187, -199, -210, -191, -202, -211,
+ -250, -183, -225, -176, -254, -154, -188, -96, -150, -85, -102, -69,
+ -80, -63, -71, -81, -74, -55, -81, -81, -69, -57, -79, -51,
+ -37, -34, -57, -26, -18, 6, -12, 5, -5, 22, -15, 19,
+ -13, 6, -49, 9, -38, -32, -76, -25, -65, -43, -75, -1,
+ -82, -14, -96, -21, -95, -25, -47, -4, -67, -76, -41, -67,
+ -39, -53, -5, -25, -37, 30, 14, 61, -21, 72, -39, 30,
+ -59, 25, -48, -21},
+ /* IRC_Composite_C_R0195_T180_P000.wav */
+ {9, -9, 9, -10, 10, -11, 11, -12, 13, -13, 14, -15,
+ 16, -17, 18, -19, 20, -21, 22, -24, 25, -26, 25, -17,
+ 104, -114, 196, -98, 54, -126, 232, -41, 337, 459, 418, 1258,
+ 2024, 1306, -3081, 2067, -322, 1935, 2599, 4829, 7530, 3384, -1993, 2649,
+ 3619, -256, -653, -863, -84, -381, -1124, -459, 826, -56, -554, -375,
+ 247, 362, 403, -767, -54, -307, 253, -528, -72, -499, -251, -538,
+ -224, -365, -429, -387, -270, -98, -240, -189, -323, -221, -114, -200,
+ -172, -265, -34, -233, -42, -325, -228, -392, -171, -360, -298, -341,
+ -265, -231, -220, -299, -332, -289, -282, -307, -286, -295, -293, -265,
+ -194, -288, -280, -341, -219, -319, -258, -367, -240, -308, -190, -294,
+ -215, -308, -212, -271, -223, -283, -231, -230, -192, -212, -231, -187,
+ -195, -184, -197, -212, -220, -239, -198, -224, -201, -254, -209, -229,
+ -205, -256, -239, -265, -231, -229, -194, -202, -206, -179, -140, -169,
+ -205, -189, -208, -194, -220, -219, -227, -219, -190, -210, -179, -230,
+ -143, -194, -136, -216, -131, -181, -147, -210, -176, -226, -209, -222,
+ -193, -214, -206, -190, -190, -205, -213, -200, -179, -148, -109, -85,
+ -53, -73, -40, -100, -69, -150, -91, -144, -106, -152, -99, -85,
+ -54, -38, -16, 3, 2, 19, 74, 35, 28, 11, 42, 13,
+ 8, -18, -32, -36, -29, -19, -47, -86, -72, -66, -42, -83,
+ -71, -83, -83, -72, -54, -20, -67, -68, -58, -74, -64, -79,
+ -16, -60, -46, -42, -7, 11, 22, 53, 51, 32, -13, 33,
+ 28, 3, -22, -46},
+ /* IRC_Composite_C_R0195_T195_P000.wav */
+ {12, -12, 12, -12, 12, -12, 12, -12, 12, -12, 12, -12,
+ 12, -12, 12, -12, 11, -11, 10, -9, 8, -6, 4, -1,
+ -5, 16, -58, -48, -46, 77, 37, -65, -49, -7, -27, 197,
+ 332, 255, 848, 1561, 1608, -2167, 729, -55, 1458, 1975, 2964, 6210,
+ 4289, 99, 1163, 3083, 300, -110, -1053, 97, -72, -273, -635, 580,
+ 371, -146, -5, 150, 225, 128, 22, 74, -75, 62, -60, -62,
+ -162, -184, -359, -146, -99, 33, -264, -53, -91, -68, -97, -68,
+ -107, -168, -116, -102, -205, -156, -272, -180, -287, -225, -284, -228,
+ -198, -203, -177, -240, -212, -239, -265, -245, -245, -239, -269, -232,
+ -261, -248, -259, -262, -292, -271, -282, -251, -272, -265, -298, -265,
+ -285, -267, -290, -263, -263, -239, -279, -219, -264, -196, -232, -191,
+ -206, -170, -181, -179, -220, -200, -244, -184, -248, -231, -259, -234,
+ -254, -249, -268, -279, -250, -255, -219, -236, -226, -171, -197, -163,
+ -222, -176, -205, -187, -189, -200, -188, -195, -184, -172, -221, -174,
+ -245, -154, -259, -156, -238, -169, -237, -202, -213, -230, -237, -242,
+ -223, -236, -210, -202, -201, -217, -219, -206, -204, -158, -180, -128,
+ -112, -64, -38, -58, -71, -95, -94, -116, -117, -160, -118, -117,
+ -105, -102, -88, -47, -31, -21, -9, 0, 24, -11, 6, -19,
+ 6, 1, -16, -22, -27, -22, -10, -73, -58, -79, -51, -76,
+ -65, -82, -99, -73, -70, -36, -82, -58, -110, -76, -105, -83,
+ -46, -122, -54, -86, 17, -33, 15, 14, -1, 0, 13, 44,
+ 23, -6, 2, -42},
+ /* IRC_Composite_C_R0195_T210_P000.wav */
+ {6, -6, 6, -6, 7, -7, 7, -7, 7, -7, 7, -7,
+ 8, -8, 8, -8, 8, -9, 9, -9, 10, -10, 10, -11,
+ 12, -12, 13, -13, -3, -140, -62, 16, 74, -75, -81, -9,
+ -154, 52, 75, 214, 374, 933, 1063, -50, -424, 1215, -28, 199,
+ 1671, 2854, 5292, 2238, 1086, 1897, 2061, -301, -116, -17, 89, 220,
+ -368, -4, 403, 517, 47, 242, 331, 363, 284, 327, 289, 188,
+ 152, 291, 134, 170, 136, 286, 56, 186, 15, 53, -103, 44,
+ -142, -179, -241, -86, -171, -94, -229, -133, -165, -58, -222, -146,
+ -225, -113, -173, -105, -202, -191, -235, -184, -216, -202, -258, -257,
+ -241, -211, -214, -256, -271, -278, -250, -239, -276, -254, -285, -245,
+ -277, -265, -277, -285, -295, -284, -275, -264, -248, -242, -249, -215,
+ -225, -190, -221, -208, -231, -195, -210, -191, -211, -203, -222, -199,
+ -222, -228, -234, -247, -238, -233, -235, -232, -214, -216, -211, -201,
+ -215, -188, -250, -191, -224, -183, -218, -210, -195, -212, -196, -217,
+ -202, -221, -224, -225, -263, -219, -268, -220, -277, -225, -254, -223,
+ -243, -232, -228, -229, -221, -217, -194, -205, -169, -170, -122, -124,
+ -95, -89, -83, -76, -91, -74, -103, -96, -96, -80, -91, -101,
+ -107, -123, -107, -115, -88, -113, -70, -93, -25, -70, -18, -56,
+ -11, -38, -15, -23, -20, -23, -29, -31, -55, -70, -58, -57,
+ -52, -93, -74, -95, -46, -80, -63, -97, -83, -108, -85, -90,
+ -88, -102, -101, -88, -79, -54, -29, -30, -33, -9, -5, -3,
+ 24, -9, 7, -23},
+ /* IRC_Composite_C_R0195_T225_P000.wav */
+ {-1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1,
+ -1, 1, -1, 1, -1, 1, -1, 1, -2, 2, -2, 2,
+ -3, 4, -4, 6, -9, 17, -16, -98, -60, -77, 48, 3,
+ -81, -121, -43, -55, 1, 19, 93, 305, 537, 631, 131, -260,
+ 545, 567, 27, 1207, 2128, 2827, 2558, 1188, 1325, 1915, 744, 102,
+ 304, 255, 211, 393, 327, 58, 643, 377, 590, 456, 611, 727,
+ 425, 704, 395, 620, 277, 569, 148, 252, 218, 128, 12, -134,
+ -32, -106, -54, -94, -7, 3, -19, -31, -59, -78, -49, -114,
+ -79, -187, -72, -177, -103, -203, -112, -200, -151, -256, -183, -276,
+ -182, -258, -181, -256, -202, -239, -241, -240, -281, -205, -284, -252,
+ -257, -288, -259, -319, -259, -309, -239, -302, -232, -305, -231, -282,
+ -235, -251, -227, -244, -183, -227, -177, -216, -169, -213, -156, -216,
+ -165, -230, -183, -214, -199, -211, -207, -215, -203, -236, -236, -213,
+ -269, -197, -284, -219, -271, -227, -256, -210, -245, -209, -247, -203,
+ -231, -195, -235, -194, -248, -194, -245, -212, -237, -203, -241, -198,
+ -226, -200, -216, -221, -205, -209, -210, -188, -216, -146, -202, -108,
+ -186, -110, -157, -108, -129, -121, -125, -129, -118, -125, -104, -120,
+ -104, -95, -101, -78, -84, -57, -71, -28, -65, -16, -83, -25,
+ -68, -41, -63, -42, -59, -29, -46, -33, -54, -48, -58, -50,
+ -85, -80, -93, -89, -117, -98, -103, -82, -104, -94, -118, -105,
+ -87, -86, -85, -96, -100, -64, -69, -48, -63, -65, -45, -15,
+ -39, -27, -55, -31},
+ /* IRC_Composite_C_R0195_T240_P000.wav */
+ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 1, -1,
+ 1, -1, 1, -1, 2, -2, 2, -3, 5, -9, -97, -133,
+ -196, -147, -157, -47, -93, 37, -29, 1, 0, 59, -116, -55,
+ 150, 274, 295, 91, 361, 1004, 1112, 1011, 1324, 1700, 2156, 1559,
+ 331, 574, 1032, 523, 614, 629, 882, 990, 1087, 1066, 943, 977,
+ 621, 691, 470, 536, 382, 281, 280, 209, 295, 117, 203, 196,
+ 393, 300, 250, 185, 141, 127, -15, -42, -136, -73, -208, -111,
+ -261, -80, -157, -42, -115, -38, -121, -105, -155, -167, -199, -209,
+ -235, -206, -227, -259, -253, -267, -251, -281, -214, -260, -213, -271,
+ -234, -259, -192, -277, -204, -294, -201, -341, -226, -327, -238, -309,
+ -206, -263, -189, -240, -185, -208, -186, -211, -192, -209, -158, -223,
+ -155, -231, -165, -251, -159, -233, -168, -259, -189, -266, -206, -255,
+ -200, -268, -211, -290, -218, -286, -233, -302, -227, -283, -216, -289,
+ -207, -262, -193, -248, -196, -236, -182, -219, -173, -221, -200, -230,
+ -207, -207, -205, -216, -209, -217, -217, -212, -207, -184, -207, -178,
+ -189, -131, -147, -115, -129, -101, -99, -95, -93, -92, -108, -91,
+ -105, -77, -114, -73, -104, -68, -90, -52, -74, -36, -70, -50,
+ -80, -45, -81, -54, -78, -26, -78, -29, -94, -58, -114, -94,
+ -144, -115, -134, -101, -99, -82, -78, -81, -65, -58, -71, -110,
+ -125, -130, -133, -109, -102, -88, -107, -78, -90, -39, -58, -31,
+ -64, -17, -39, -13},
+ /* IRC_Composite_C_R0195_T255_P000.wav */
+ {2, -2, 2, -2, 2, -2, 2, -2, 2, -2, 2, -2,
+ 2, -2, 2, -2, 2, -2, 2, -2, 2, -3, 3, -3,
+ 3, -3, 3, -3, 3, -3, 4, -4, 4, -5, 5, -6,
+ 9, -19, -71, -126, -111, -131, -223, -213, -179, -73, -136, -101,
+ -19, 30, 101, 69, 89, 95, 323, 406, 515, 721, 768, 1140,
+ 1389, 925, 919, 1504, 1350, 1027, 1508, 1800, 2083, 1870, 1354, 950,
+ 539, 490, 494, 392, 378, 293, 344, 461, 473, 522, 375, 344,
+ 271, 237, 162, 56, 42, 11, 83, -5, 55, -30, 81, -3,
+ 71, -18, -50, 0, -151, -66, -240, -143, -251, -198, -256, -250,
+ -192, -239, -205, -271, -195, -240, -195, -267, -238, -257, -227, -242,
+ -241, -251, -226, -256, -185, -246, -212, -252, -224, -227, -235, -216,
+ -249, -192, -222, -170, -209, -167, -231, -184, -255, -188, -250, -181,
+ -283, -185, -279, -146, -264, -164, -273, -161, -261, -181, -298, -216,
+ -272, -226, -251, -181, -242, -222, -248, -216, -260, -249, -302, -271,
+ -289, -255, -276, -256, -246, -225, -218, -198, -202, -222, -210, -223,
+ -219, -210, -230, -209, -218, -149, -175, -141, -182, -152, -139, -146,
+ -160, -169, -152, -140, -120, -129, -130, -102, -120, -59, -92, -67,
+ -121, -84, -93, -62, -69, -69, -85, -101, -60, -94, -80, -123,
+ -92, -126, -73, -83, -80, -90, -79, -71, -42, -42, -54, -59,
+ -77, -88, -83, -86, -100, -126, -130, -119, -130, -124, -145, -127,
+ -148, -111, -124, -92, -85, -75, -85, -70, -68, -66, -66, -59,
+ -78, -67, -59, -65},
+ /* IRC_Composite_C_R0195_T270_P000.wav */
+ {-1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1,
+ -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1,
+ -1, 1, -1, 1, -1, 1, -1, 2, -2, 2, -2, 3,
+ -4, 9, -91, -159, -163, -111, -130, -163, -90, -102, -89, -163,
+ -184, -188, -171, -83, 30, 36, 169, 298, 65, 214, 405, 559,
+ 898, 896, 1271, 2043, 2157, 2404, 2912, 2261, 1344, 1103, 1268, 1220,
+ 756, 484, 263, 283, 379, 505, 398, 413, 455, 527, 338, 482,
+ 334, 406, 255, 187, 211, 119, 189, 4, 182, -16, 145, -56,
+ 26, -74, -83, -100, -148, -133, -197, -139, -191, -198, -217, -194,
+ -192, -215, -224, -197, -182, -180, -242, -199, -243, -153, -261, -180,
+ -264, -151, -250, -164, -239, -176, -235, -178, -231, -191, -258, -227,
+ -259, -218, -280, -188, -268, -159, -259, -154, -260, -137, -244, -185,
+ -245, -228, -251, -260, -250, -247, -239, -233, -209, -205, -224, -262,
+ -274, -268, -248, -262, -263, -277, -234, -255, -222, -233, -226, -262,
+ -221, -257, -211, -267, -218, -251, -186, -221, -179, -224, -195, -213,
+ -161, -226, -193, -212, -178, -219, -191, -204, -183, -190, -175, -182,
+ -165, -164, -140, -131, -97, -107, -80, -102, -45, -85, -60, -81,
+ -66, -74, -59, -82, -82, -91, -88, -102, -100, -120, -85, -122,
+ -72, -129, -98, -137, -55, -101, -62, -69, -41, -72, -56, -83,
+ -69, -90, -53, -98, -81, -105, -80, -107, -83, -112, -102, -115,
+ -97, -125, -111, -136, -123, -145, -118, -131, -95, -114, -62, -70,
+ -59, -73, -48, -57},
+ /* IRC_Composite_C_R0195_T285_P000.wav */
+ {-1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1,
+ -1, 1, -1, 1, -1, 1, -1, 2, -2, 2, -2, 2,
+ -2, 2, -2, 2, -2, 2, -2, 2, -3, 3, -4, 6,
+ -9, 19, -100, -184, -163, -233, -161, -194, -211, -190, -252, -368,
+ -121, 58, 37, -12, 35, 111, -96, -223, -19, 464, 559, 1425,
+ 2382, 1927, 1709, 2204, 1586, 984, 1062, 1267, 1442, 1113, 1078, 1182,
+ 1130, 1148, 848, 575, 534, 584, 541, 405, 456, 300, 346, 294,
+ 405, 279, 183, 240, 258, 176, 160, 100, -27, 100, -24, 93,
+ -139, 0, -270, 60, -268, 20, -249, -123, -250, -178, -127, -185,
+ -84, -288, -109, -237, -116, -161, -179, -137, -193, -134, -189, -164,
+ -211, -174, -211, -244, -187, -259, -227, -263, -160, -220, -206, -234,
+ -213, -214, -205, -225, -212, -266, -182, -254, -229, -238, -166, -272,
+ -223, -282, -263, -305, -287, -272, -261, -288, -239, -247, -227, -247,
+ -224, -236, -226, -259, -230, -256, -220, -217, -226, -224, -232, -214,
+ -225, -205, -261, -251, -252, -219, -235, -230, -230, -236, -236, -209,
+ -203, -226, -221, -222, -180, -160, -190, -161, -159, -154, -156, -97,
+ -111, -129, -127, -107, -66, -95, -69, -92, -64, -55, -64, -43,
+ -78, -65, -103, -71, -87, -110, -101, -114, -93, -117, -90, -112,
+ -88, -109, -72, -82, -69, -68, -63, -65, -90, -69, -67, -77,
+ -62, -56, -47, -91, -59, -73, -34, -101, -70, -91, -61, -105,
+ -91, -128, -154, -149, -128, -150, -140, -142, -93, -111, -108, -78,
+ -62, -77, -59, -62},
+ /* IRC_Composite_C_R0195_T300_P000.wav */
+ {4, -4, 4, -4, 4, -4, 4, -4, 4, -5, 5, -5,
+ 5, -5, 5, -5, 5, -6, 6, -6, 6, -6, 6, -7,
+ 7, -7, 8, -8, 8, -9, 10, -11, 13, -16, 24, -75,
+ -192, -243, -206, -121, -200, -413, -335, -275, -203, -85, -38, 193,
+ -95, -382, 283, -596, -531, 1085, 630, 1024, 2620, 2194, 1518, 1499,
+ 1392, 607, 112, 846, 1164, 950, 675, 1005, 1305, 1174, 1521, 1142,
+ 1208, 859, 1074, 771, 735, 552, 498, 679, 274, 454, 160, 457,
+ 127, 176, 172, 280, 101, 64, 67, -27, 98, 11, 3, -150,
+ -68, -72, -105, -163, -160, -139, -135, -141, -133, -136, -55, -184,
+ -109, -200, -81, -104, -78, -213, -147, -100, -188, -136, -214, -162,
+ -281, -139, -265, -190, -228, -184, -320, -138, -205, -130, -238, -158,
+ -209, -100, -297, -175, -328, -233, -318, -199, -385, -256, -355, -254,
+ -336, -269, -310, -262, -286, -237, -272, -206, -279, -217, -262, -212,
+ -280, -260, -296, -269, -321, -249, -293, -297, -294, -263, -252, -259,
+ -248, -231, -189, -223, -204, -205, -214, -202, -172, -186, -170, -216,
+ -162, -160, -149, -167, -137, -119, -139, -138, -127, -91, -90, -111,
+ -123, -105, -94, -88, -92, -101, -104, -94, -87, -104, -82, -106,
+ -68, -106, -75, -113, -53, -76, -65, -86, -59, -75, -81, -73,
+ -84, -59, -75, -52, -59, -73, -55, -69, -45, -87, -49, -62,
+ -47, -76, -53, -57, -57, -74, -83, -72, -109, -94, -135, -114,
+ -127, -112, -128, -125, -121, -104, -114, -94, -97, -102, -114, -98,
+ -73, -61, -46, -72},
+ /* IRC_Composite_C_R0195_T315_P000.wav */
+ {-3, 3, -3, 3, -3, 3, -3, 3, -3, 3, -3, 3,
+ -3, 3, -3, 3, -3, 3, -4, 4, -4, 4, -4, 4,
+ -5, 5, -5, 6, -7, 8, -10, 14, -29, -234, -230, -266,
+ -204, -305, -247, -524, -359, -150, 55, 142, 71, 572, -570, -1004,
+ 861, -803, -374, 3189, 1120, 2120, 3144, 2428, 636, 1026, 1269, 59,
+ -187, 571, 1369, 181, 332, 532, 1375, 1240, 1554, 1033, 1045, 866,
+ 1172, 1026, 1022, 606, 720, 597, 671, 349, 376, 201, 259, 47,
+ 222, 146, 93, -124, 20, -35, -174, -23, 6, -91, -108, -18,
+ 6, 38, -2, -118, 67, -77, -79, -121, -96, -206, -171, -74,
+ -193, -141, -167, -54, -168, -65, -180, -137, -281, -209, -164, -172,
+ -178, -158, -127, -188, -174, -11, -152, -142, -222, -182, -348, -203,
+ -338, -287, -380, -316, -332, -319, -347, -297, -281, -364, -362, -314,
+ -304, -376, -347, -321, -343, -330, -367, -276, -306, -278, -290, -268,
+ -295, -267, -229, -264, -248, -290, -244, -263, -201, -223, -197, -193,
+ -189, -154, -159, -137, -178, -126, -217, -115, -163, -129, -166, -130,
+ -150, -147, -107, -147, -129, -143, -117, -141, -126, -115, -135, -99,
+ -146, -90, -112, -81, -119, -52, -80, -79, -84, -83, -64, -59,
+ -53, -89, -91, -74, -101, -29, -121, -49, -139, -64, -94, -23,
+ -57, -36, -29, -38, -50, -50, -47, -30, -53, -56, -81, -58,
+ -97, -62, -69, -56, -82, -74, -103, -94, -111, -122, -150, -117,
+ -148, -135, -117, -104, -87, -95, -77, -107, -55, -89, -56, -82,
+ -65, -70, -36, -13},
+ /* IRC_Composite_C_R0195_T330_P000.wav */
+ {-15, 15, -15, 15, -15, 15, -15, 16, -16, 16, -16, 16,
+ -16, 16, -16, 16, -16, 16, -16, 16, -16, 15, -15, 14,
+ -13, 11, -8, 5, 0, -7, 12, -293, -526, -71, -505, -401,
+ -1003, -83, -138, 277, -121, 450, 638, -529, -2538, 1974, -1021, -928,
+ 6018, 1862, 2117, 4170, 3177, -540, 80, 773, 465, -382, 760, 707,
+ 287, -96, 895, 1260, 1490, 749, 709, 1138, 1758, 1405, 855, 490,
+ 661, 833, 888, 382, 366, 310, 491, 203, 204, 155, 246, 122,
+ -32, -122, -37, 50, 94, -114, -242, -173, 94, -97, -233, -239,
+ 44, 3, 91, -64, -78, -19, 148, 9, 2, -196, 62, -119,
+ -154, -277, 128, -102, -103, -182, -189, -136, -49, 51, -260, -285,
+ -17, -46, -344, -260, -264, -310, -481, -356, -385, -299, -442, -338,
+ -388, -433, -413, -352, -281, -444, -379, -419, -361, -361, -316, -320,
+ -407, -371, -354, -283, -325, -263, -281, -264, -300, -242, -200, -218,
+ -231, -208, -283, -185, -218, -137, -254, -173, -210, -124, -178, -189,
+ -155, -197, -150, -148, -83, -183, -134, -157, -104, -154, -134, -133,
+ -118, -172, -161, -120, -119, -156, -145, -96, -143, -172, -117, -71,
+ -107, -120, -113, -53, -111, -85, -65, -51, -97, -63, -15, -77,
+ -94, -67, -18, -45, -126, -60, -101, -21, -115, 3, -114, -45,
+ -90, -5, -69, -55, -89, -42, -99, -38, -100, -24, -95, -21,
+ -116, -37, -113, -13, -85, -64, -143, -57, -122, -61, -126, -85,
+ -123, -78, -94, -71, -113, -55, -72, -37, -93, -41, -42, -10,
+ -34, -37, -13, -34},
+ /* IRC_Composite_C_R0195_T345_P000.wav */
+ {-1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1,
+ 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1,
+ -1, 1, -2, 2, -3, -196, -727, -30, -506, -136, -1125,
+ -612, -692, -135, 283, 619, 802, -51, -2221, 1398, -964, -5308,
+ 8059, 961, 2013, 8066, 4451, -858, 1438, 1808, -955, -2183, 475,
+ 1387, 742, 458, 140, 184, 1030, 1768, 1316, 965, 1001, 1155,
+ 1147, 1222, 1301, 1124, 461, 289, 297, 592, 383, 273, -220,
+ -259, -53, 323, 264, -32, -200, 42, 191, 421, 77, -195,
+ -257, 18, -154, -38, -140, -135, -290, -47, 107, 49, -29,
+ -98, -94, -50, 49, 264, 36, 153, -148, 86, -24, 287,
+ -156, 101, -86, 125, -186, -182, -278, -236, -501, -636, -484,
+ -401, -330, -428, -463, -554, -283, -366, -318, -494, -441, -375,
+ -318, -358, -465, -416, -438, -337, -400, -344, -393, -247, -308,
+ -313, -356, -181, -229, -204, -374, -214, -320, -200, -307, -179,
+ -339, -189, -252, -152, -246, -143, -210, -166, -196, -69, -139,
+ -172, -216, -103, -73, -127, -214, -116, -118, -96, -247, -135,
+ -187, -78, -264, -166, -235, -95, -173, -142, -167, -169, -129,
+ -117, -114, -125, -125, -103, -137, -47, -110, -26, -121, -21,
+ -72, -15, -71, -68, -34, -53, -38, -72, -96, -96, -90,
+ -11, -126, -33, -166, -22, -92, 18, -75, -35, -69, -60,
+ -67, -82, -72, -61, -96, -68, -103, -26, -100, -19, -108,
+ 0, -110, -34, -106, -39, -54, -75, -76, -83, -35, -42,
+ -69, -51, -59, -38, -55, 19, -49, -3, -43, 27, -14,
+ -5, -46, -24}};
+
+const int16_t irc_composite_c_r0195_p015[][256] =
+ {/* IRC_Composite_C_R0195_T000_P015.wav */
+ {4, -4, 4, -4, 4, -4, 4, -4, 4, -4, 4,
+ -5, 5, -5, 4, -4, 4, -4, 3, -2, 1, 2,
+ -5, -101, -665, -424, -897, -170, -507, -460, -1001, -828, -356,
+ 47, 474, 980, 404, 2103, -3700, -5968, 7765, -9133, 9741, 10867,
+ 71, 6925, 3456, -1455, -2458, 2718, -170, -1201, -1751, 1809, 507,
+ 632, -1165, 1945, 1684, 1705, -450, 1152, 360, 1527, 269, 952,
+ 644, 931, 727, 1576, 725, 835, 741, 915, 186, 162, 327,
+ 384, 20, 24, -311, 60, 225, 484, -123, -28, -210, 118,
+ 15, 76, -335, -153, -249, 49, -120, 24, -200, -169, -213,
+ -175, -235, -158, -280, -399, -410, -304, -104, -355, -319, -311,
+ -79, -366, -75, -79, -120, -487, 147, -42, -198, -175, 135,
+ -242, 55, 233, 123, -314, -58, -179, -96, -352, -26, 7,
+ 11, -105, -129, 125, -110, -293, -276, -461, -507, -406, -422,
+ -497, -481, -446, -459, -325, -336, -443, -420, -320, -391, -360,
+ -309, -407, -375, -288, -289, -385, -315, -272, -292, -271, -256,
+ -318, -173, -220, -185, -329, -142, -239, -76, -219, -117, -277,
+ -107, -204, -117, -233, -199, -220, -177, -238, -171, -170, -187,
+ -285, -154, -168, -145, -192, -131, -239, -194, -170, -109, -205,
+ -155, -164, -163, -187, -126, -85, -138, -195, -140, -189, -84,
+ -134, -22, -177, -51, -128, -26, -105, 26, -144, -65, -123,
+ -28, -136, 19, -69, -43, -112, 40, -43, 59, -67, 32,
+ -35, 97, -21, 99, -27, 90, 23, 141, 21, 38, 39,
+ 61, 12, 10, -1, 37, -3, 7, 2, 41, -19, -12,
+ -12, 16, -53},
+ /* IRC_Composite_C_R0195_T015_P015.wav */
+ {100, -101, 102, -102, 102, -102, 102, -102, 101, -99, 96,
+ -93, 88, -81, 71, -58, 37, -6, -46, 154, -587, -280,
+ -52, -712, -530, -842, -228, -637, -217, -1535, -463, 274, 2688,
+ 1298, -390, -2760, 4349, -16532, 13572, -2117, 2580, 22789, -850, 559,
+ 1346, 2618, -4202, 397, 316, 686, -1654, 1356, -848, 1484, 164,
+ 1873, 376, 1720, 39, 1069, 309, 1623, 748, 807, 488, 1192,
+ 547, 1364, 679, 594, 48, 334, -179, 362, 98, -149, -587,
+ -275, -81, 371, -430, -372, -369, 412, -126, -39, -294, 169,
+ -117, 61, -209, -100, -271, -40, -262, -244, -406, 12, -243,
+ -253, -484, 54, -243, -283, -575, -138, -358, -262, -307, -61,
+ -567, -206, 25, 51, -696, -10, -132, -245, -388, 187, -132,
+ -194, -316, -92, -71, -291, -374, 27, -295, -84, -4, 145,
+ -576, 122, -217, -153, -724, 53, -337, -126, -502, -305, -401,
+ -138, -372, -296, -502, -318, -375, -140, -645, -283, -375, -217,
+ -529, -172, -316, -324, -287, -239, -309, -305, -230, -228, -310,
+ -332, -289, -168, -302, -286, -241, -197, -266, -181, -156, -260,
+ -272, -185, -148, -225, -221, -239, -178, -223, -142, -203, -226,
+ -260, -124, -148, -200, -259, -124, -203, -182, -207, -78, -186,
+ -163, -136, -74, -179, -123, -45, -87, -180, -103, -28, -117,
+ -135, -64, -29, -148, -90, -13, -28, -111, -40, 7, -42,
+ -98, -26, -4, -57, -22, 22, -38, -72, 76, 67, 1,
+ -21, 80, 49, 39, 63, 66, 26, 23, 62, 97, 29,
+ 39, 49, 28, -1, 52, 74, -6, -4, 19, 15, -15,
+ -12, 17, -50},
+ /* IRC_Composite_C_R0195_T030_P015.wav */
+ {-101, 102, -103, 105, -105, 106, -106, 106, -104, 102, -97,
+ 90, -79, 62, -35, -12, 109, -431, -649, 311, -951, 117,
+ -1193, 10, -1439, 943, -1996, 57, -1866, 3892, -160, 3817, -4940,
+ 7695, -14389, 320, 6811, -6312, 29122, 1548, 2820, 2754, 2735, -5703,
+ -20, 838, 313, -2387, 2394, -1371, 1731, 262, 1937, 34, 1456,
+ -294, 1713, 221, 997, 660, 1535, 549, 1179, 928, 928, 293,
+ 321, -76, 273, -164, -356, -622, 24, -278, -26, -520, -116,
+ -436, -61, -412, -21, -436, -263, -534, -25, -24, 127, -328,
+ -77, -189, 107, -200, -313, -487, -278, -245, -413, -269, -216,
+ -100, -434, -149, -238, -270, -527, -94, -144, -244, -529, -94,
+ -120, -125, -544, -205, -477, -214, -375, -52, -404, -184, -361,
+ -14, -189, -53, -346, -89, -276, -127, -233, -126, -417, -169,
+ -225, -263, -470, -227, -238, -276, -329, -339, -304, -226, -209,
+ -212, -255, -261, -275, -96, -168, -289, -298, -134, -296, -334,
+ -291, -248, -272, -268, -362, -278, -286, -108, -360, -215, -330,
+ -76, -394, -179, -365, -104, -449, -206, -420, -128, -409, -167,
+ -434, -175, -416, -105, -318, -125, -408, -157, -270, -45, -277,
+ -160, -326, -56, -220, -114, -280, -69, -199, -100, -242, -24,
+ -143, -39, -214, 3, -104, 20, -184, 20, -140, -7, -183,
+ 31, -135, -21, -164, 13, -128, 42, -188, 49, -113, 71,
+ -189, 90, -97, 102, -87, 84, -61, 147, -63, 149, -94,
+ 170, -108, 183, -108, 208, -150, 179, -163, 250, -98, 215,
+ -137, 235, -107, 214, -135, 246, -126, 201, -171, 186, -149,
+ 135, -194, 121},
+ /* IRC_Composite_C_R0195_T045_P015.wav */
+ {-146, 153, -161, 170, -179, 189, -200, 212, -224, 238, -252,
+ 266, -278, 280, -241, -262, -1208, 369, -877, 618, -1865, 344,
+ -1624, 1098, -2418, 1166, -2204, 3338, -287, 6351, -5670, 4490, -9471,
+ -5595, 10415, -3658, 26422, 6163, 571, 640, 3020, -3458, 345, 262,
+ -17, -2379, 1504, -915, 2155, 96, 1778, -36, 1408, 800, 1533,
+ 117, 618, 355, 1940, 474, 1443, 116, 436, -13, 320, -248,
+ -589, -590, -422, -164, -365, -343, -251, -474, 20, -343, -59,
+ -619, -421, -460, -72, -443, -399, -435, -238, -311, -325, -256,
+ -210, -285, -282, -222, -283, -212, -374, -178, -386, -213, -343,
+ -35, -317, -276, -390, -79, -244, -300, -445, -222, -296, -233,
+ -458, -263, -403, -259, -462, -188, -423, -283, -371, -78, -225,
+ -208, -380, -203, -156, -140, -307, -202, -208, -86, -293, -179,
+ -233, -185, -390, -305, -381, -170, -416, -344, -426, -229, -331,
+ -201, -370, -183, -273, -88, -257, -116, -206, -60, -298, -72,
+ -233, -115, -304, -131, -336, -187, -271, -44, -375, -190, -355,
+ -35, -321, -189, -400, -136, -365, -198, -383, -200, -395, -244,
+ -401, -188, -398, -184, -393, -179, -365, -150, -314, -118, -327,
+ -118, -291, -73, -272, -87, -247, -52, -258, -27, -226, 25,
+ -210, 8, -202, 51, -136, 62, -162, 35, -135, 61, -156,
+ -12, -167, 55, -165, 15, -174, 47, -131, 28, -125, 76,
+ -91, 69, -71, 126, -60, 104, -40, 141, -51, 117, -33,
+ 141, -58, 103, -38, 158, -48, 95, -52, 149, -45, 86,
+ -47, 120, -68, 88, -39, 116, -45, 74, -35, 89, -68,
+ 62, -90, 53},
+ /* IRC_Composite_C_R0195_T060_P015.wav */
+ {29, -34, 39, -45, 52, -59, 69, -80, 93, -110, 131,
+ -160, 205, -308, -459, -920, -171, 388, -674, -719, -1064, 350,
+ -1149, 552, -1891, 2109, -28, 5773, -3032, 5413, -11840, 194, -1388,
+ 1646, 23561, 8535, 2547, 1107, -66, 257, 913, 517, -131, -1162,
+ -856, 344, 2136, -879, 1332, -592, 2048, 385, 1977, 174, 2217,
+ -131, 2091, -69, 1390, -1343, 963, -905, 515, -1176, -81, -1557,
+ 250, -301, -119, -467, -479, -337, 138, -523, -373, -525, -22,
+ -558, -74, -857, -193, -765, -208, -1034, -15, -947, -96, -803,
+ 45, -665, 15, -523, -1, -312, 49, -446, -141, -311, -126,
+ -246, -344, -467, -27, -388, -328, -427, -198, -495, -118, -356,
+ -277, -493, -134, -525, -71, -411, -262, -445, -175, -367, -96,
+ -400, -267, -383, -49, -313, -260, -205, -179, -166, -56, -272,
+ -194, -146, -166, -310, -204, -405, -207, -321, -211, -468, -223,
+ -383, -150, -326, -281, -312, -146, -220, -208, -241, -286, -189,
+ -157, -134, -243, -213, -136, -145, -92, -188, -205, -244, -73,
+ -253, -131, -321, -224, -277, -194, -364, -238, -342, -254, -284,
+ -301, -272, -246, -239, -245, -226, -236, -207, -220, -191, -251,
+ -189, -226, -188, -154, -195, -171, -163, -122, -121, -97, -111,
+ -91, -108, -20, -120, -59, -33, -136, -42, -55, -62, -49,
+ -97, -100, 16, -97, -13, -104, 7, -74, -20, -54, 27,
+ -6, 45, -12, 56, 35, 24, 86, 33, 48, 24, 68,
+ 72, -3, 50, 22, 52, 12, 43, -32, 109, -68, 106,
+ -66, 49, -61, 102, -74, 44, -72, 28, -60, 21, -71,
+ -4, -76, 16},
+ /* IRC_Composite_C_R0195_T075_P015.wav */
+ {19, -16, 12, -7, 2, 4, -11, 18, -27, 36, -47,
+ 59, -68, 37, -553, 36, -916, -89, -13, -916, -389, -1224,
+ 572, -284, 1453, 853, 1354, 3254, 1573, -10498, -1005, -5881, 20609,
+ 10333, 4130, 9878, -3085, -1702, 1001, 5191, 773, -2104, -1269, -41,
+ 1645, -349, 309, 387, 447, 1304, 620, 1533, 360, 1697, 867,
+ 1545, -94, 149, -359, 473, -1422, -432, -1225, -246, -886, 186,
+ -914, -144, -892, 309, -602, 60, -603, -112, -520, -150, -750,
+ -486, -681, -262, -736, -448, -1017, -103, -734, -305, -903, -61,
+ -752, 39, -644, -19, -565, 33, -324, -70, -458, -205, -229,
+ -182, -403, -322, -245, -211, -302, -350, -295, -222, -298, -293,
+ -385, -263, -357, -235, -379, -252, -470, -139, -429, -84, -457,
+ -178, -385, -120, -380, -232, -339, -192, -273, -205, -196, -146,
+ -164, -174, -129, -218, -203, -145, -229, -290, -273, -262, -283,
+ -222, -302, -215, -314, -150, -291, -151, -311, -154, -281, -177,
+ -260, -179, -190, -177, -222, -159, -209, -215, -215, -185, -191,
+ -224, -214, -221, -182, -209, -269, -260, -254, -234, -284, -241,
+ -293, -201, -269, -218, -264, -188, -203, -199, -218, -207, -118,
+ -151, -146, -184, -126, -92, -92, -129, -108, -109, -42, -120,
+ -57, -144, -45, -102, -23, -164, -63, -98, -36, -106, -81,
+ -3, -74, -55, -79, -49, -60, -59, -60, -31, -36, -35,
+ 57, -37, 99, -7, 135, -26, 115, 14, 154, -6, 78,
+ 28, 119, -3, 53, -48, 80, -38, 55, -56, 13, -65,
+ 74, -45, 19, -79, 50, -39, 24, -95, 40, -41, 15,
+ -61, 55, -129},
+ /* IRC_Composite_C_R0195_T090_P015.wav */
+ {106, -108, 111, -113, 116, -119, 121, -123, 125, -125, 121,
+ -102, -150, -386, -21, -846, -117, 203, -762, -759, -425, -20,
+ 26, 535, 183, 3678, 866, 180, 877, -12284, 4742, -888, 21444,
+ 10158, -2546, 4612, -145, -630, 3778, 3819, 1940, -4361, -1372, 2229,
+ 1116, -1596, -423, 1713, 840, 536, 1040, 2123, 618, 888, -302,
+ 898, -618, 52, -845, 240, -1279, -435, -1143, 105, -929, -50,
+ -1077, -180, -541, 98, -930, -161, -671, -158, -656, -338, -774,
+ -263, -620, -260, -792, -238, -787, -172, -874, -235, -797, -199,
+ -767, -178, -702, -101, -607, -63, -479, -72, -504, -49, -400,
+ -118, -387, -113, -404, -73, -442, -100, -429, -15, -496, -79,
+ -480, -58, -494, -80, -498, -112, -411, -138, -447, -136, -406,
+ -145, -485, -166, -440, -131, -485, -87, -417, -104, -413, -102,
+ -426, -31, -350, -62, -299, 27, -272, -36, -302, -22, -320,
+ -120, -364, -124, -307, -111, -382, -185, -231, -108, -275, -163,
+ -225, -123, -157, -158, -254, -183, -208, -146, -274, -183, -229,
+ -148, -301, -148, -275, -212, -388, -177, -304, -193, -358, -180,
+ -265, -195, -243, -171, -184, -212, -200, -212, -156, -173, -149,
+ -232, -145, -162, -100, -147, -119, -102, -57, -53, -87, -18,
+ -36, -11, -87, -46, -46, 17, -89, -82, -57, 6, -100,
+ -61, -101, -38, -60, -46, -108, -40, -41, 19, -104, -21,
+ -14, 77, -24, 51, 22, 136, 25, 103, 19, 102, 3,
+ 92, -42, 26, -51, 25, -38, -15, -65, -12, -35, -8,
+ 28, 24, -31, -1, 36, 92, 2, 6, 8, 52, -33,
+ -7, -25, -27},
+ /* IRC_Composite_C_R0195_T105_P015.wav */
+ {16, -19, 23, -28, 34, -40, 48, -57, 68, -82, 100,
+ -121, 33, -823, 199, -561, -96, -481, 741, -1752, -67, -480,
+ 1840, -966, 591, 1601, 5280, -3652, 770, -7901, 214, 10553, 2446,
+ 18816, 1377, -3359, -1411, 6633, 5595, -115, -232, 1823, -2575, -717,
+ -242, 2719, -2255, 509, 511, 2747, 225, 1585, 549, 951, -478,
+ -34, -731, -13, -1246, -512, -881, -341, -1127, 207, -442, -495,
+ -702, -517, -566, 35, -620, -983, -450, -179, -571, -441, -658,
+ -297, -211, -340, -730, -514, -334, -479, -562, -662, -463, -524,
+ -336, -537, -522, -336, -226, -483, -343, -332, -364, -181, -306,
+ -365, -271, -175, -316, -213, -283, -326, -115, -221, -216, -290,
+ -146, -290, -98, -334, -301, -279, -138, -349, -278, -318, -250,
+ -285, -237, -388, -326, -257, -206, -416, -256, -293, -207, -350,
+ -156, -397, -149, -314, -125, -323, -153, -327, -64, -258, -145,
+ -241, -10, -249, -51, -197, -108, -234, 33, -286, -138, -267,
+ -26, -273, -117, -347, -77, -229, -117, -301, -119, -229, -104,
+ -247, -175, -311, -162, -227, -248, -287, -181, -293, -170, -318,
+ -206, -285, -159, -334, -167, -232, -161, -235, -76, -246, -154,
+ -204, -10, -221, -106, -229, 14, -182, -12, -251, 30, -154,
+ 32, -190, 61, -152, 90, -145, 48, -122, 71, -96, 29,
+ -121, 6, -57, 66, -80, 40, -49, 59, -112, -8, -129,
+ 54, -18, 76, -21, 97, 37, 171, 23, 59, -61, 75,
+ -37, 21, -127, 1, -154, 68, -112, -12, -180, 86, -58,
+ 91, -120, 128, -28, 129, -100, 167, -17, 106, -84, 94,
+ -49, 76, -37},
+ /* IRC_Composite_C_R0195_T120_P015.wav */
+ {39, -39, 39, -39, 39, -38, 38, -37, 35, -32, 27,
+ -19, 0, -223, 40, -675, 329, -841, 457, -348, -274, -1647,
+ 1770, 432, -494, 3, 4260, 289, 887, -4454, -3947, 7446, -2378,
+ 15614, 7773, 95, -2028, 2409, 7171, 2953, 950, 1439, -1677, -1118,
+ -1770, 1575, 491, 137, -1226, 1181, 2451, 1325, -149, 862, 100,
+ -196, -613, -302, -1148, -1325, -632, -731, -1011, -688, 174, -67,
+ -430, -378, -168, -383, -445, -570, -934, -576, -71, -333, -939,
+ -576, -180, -299, -551, -542, -536, -312, -421, -506, -398, -463,
+ -360, -423, -342, -463, -390, -485, -299, -416, -291, -519, -227,
+ -313, -272, -376, -199, -241, -176, -354, -259, -245, -94, -289,
+ -288, -266, -79, -203, -210, -332, -200, -224, -227, -331, -258,
+ -315, -219, -293, -232, -358, -231, -306, -201, -298, -234, -413,
+ -216, -295, -220, -417, -228, -272, -126, -326, -197, -254, -104,
+ -275, -161, -280, -75, -215, -110, -276, -33, -130, -19, -261,
+ -8, -171, 30, -242, -55, -229, -65, -254, -74, -218, -134,
+ -301, -99, -211, -155, -300, -156, -234, -131, -322, -164, -311,
+ -142, -349, -229, -334, -148, -297, -165, -289, -133, -215, -100,
+ -254, -101, -185, -51, -234, -38, -186, -6, -168, 0, -111,
+ -29, -125, 29, -96, -23, -153, 22, -59, -32, -103, 28,
+ -118, 11, -64, 83, -103, 41, -112, 28, -109, 99, -41,
+ 39, -86, 157, 43, 121, -15, 157, -11, 121, -30, 90,
+ -113, 25, -109, 38, -172, -47, -151, 15, -102, 21, -97,
+ 16, -33, 104, 12, 59, -55, 121, -5, 63, -116, 114,
+ -30, 115, -85},
+ /* IRC_Composite_C_R0195_T135_P015.wav */
+ {15, -17, 19, -21, 24, -28, 32, -37, 43, -50, 60,
+ -73, 90, -115, 157, -297, 361, -673, 287, -116, -598, -544,
+ 1021, -902, -263, 1113, 1563, -848, 3395, 810, -1816, -1445, -4755,
+ 10984, 3295, 5362, 6262, 2589, -529, 1444, 7698, 4758, -2233, -2138,
+ -1622, 980, -226, 100, -517, 871, 952, 682, 983, 1138, -542,
+ -341, -164, -377, -526, -992, -910, -828, -413, -830, -380, -392,
+ -124, -316, -225, -315, -25, -678, -691, -468, -412, -545, -395,
+ -676, -301, -467, -356, -548, -300, -516, -260, -553, -295, -485,
+ -232, -525, -220, -421, -217, -488, -227, -455, -268, -426, -269,
+ -474, -232, -395, -199, -378, -219, -372, -176, -401, -202, -354,
+ -114, -340, -124, -267, -86, -322, -166, -298, -181, -316, -179,
+ -307, -163, -290, -175, -321, -186, -325, -234, -372, -200, -335,
+ -202, -352, -199, -268, -180, -319, -236, -296, -200, -331, -209,
+ -300, -142, -273, -118, -226, -91, -203, -97, -250, -91, -176,
+ -72, -201, -65, -156, -22, -171, -59, -200, -36, -210, -93,
+ -201, -48, -197, -73, -185, -73, -195, -95, -266, -166, -294,
+ -169, -299, -216, -315, -187, -265, -167, -287, -146, -275, -175,
+ -265, -144, -282, -127, -249, -66, -222, -39, -146, -32, -126,
+ -20, -76, 5, -92, 18, -56, 21, -91, 13, -119, -23,
+ -127, 37, -71, 16, -116, 10, -105, 12, -96, 55, -69,
+ 43, -76, 137, -20, 86, -3, 123, 28, 100, 8, 49,
+ -58, 60, -31, -10, -117, -15, -44, -19, -89, 2, -51,
+ -18, -51, 62, -39, 31, -46, 59, -59, 67, -57, 112,
+ -72, 62, -22},
+ /* IRC_Composite_C_R0195_T150_P015.wav */
+ {22, -23, 24, -24, 25, -26, 27, -27, 28, -28, 28,
+ -28, 26, -24, 19, -9, -13, 134, -205, 5, 10, -146,
+ -122, -75, -78, -152, 345, -100, 1711, 198, 1882, 367, 2730,
+ -6333, 870, 5933, 2367, 6468, 4276, 4587, 970, 719, 5822, 3692,
+ -1207, -2971, -1058, 797, 482, -202, -740, 507, 536, 875, 480,
+ 668, -204, -211, -690, 500, -1107, -874, -841, -223, -910, -122,
+ -465, -407, -494, -192, -244, -202, -337, -294, -451, -519, -512,
+ -353, -580, -345, -407, -307, -608, -176, -450, -303, -475, -205,
+ -453, -277, -342, -237, -284, -212, -314, -272, -380, -283, -368,
+ -307, -417, -297, -337, -321, -374, -329, -283, -379, -283, -310,
+ -317, -249, -232, -217, -269, -190, -252, -183, -270, -238, -227,
+ -187, -286, -191, -260, -230, -245, -233, -289, -230, -246, -218,
+ -241, -208, -260, -204, -276, -211, -286, -243, -284, -211, -281,
+ -227, -258, -199, -238, -185, -241, -133, -220, -133, -189, -80,
+ -190, -65, -146, -88, -167, -80, -129, -110, -166, -124, -142,
+ -139, -178, -111, -146, -116, -143, -102, -118, -138, -137, -152,
+ -173, -190, -174, -190, -193, -230, -208, -208, -216, -246, -203,
+ -231, -209, -216, -167, -210, -170, -158, -128, -148, -142, -127,
+ -104, -88, -98, -82, -75, -58, -53, -46, -85, -53, -56,
+ -1, -14, 17, -80, -58, -41, -57, -6, -70, 6, -78,
+ 37, -14, 58, 10, 69, 35, 20, 45, 42, 27, 3,
+ 3, -29, -5, -10, 26, -54, -21, -21, 39, -58, -58,
+ -23, 10, -16, -35, 6, -22, 2, -8, 54, -13, 7,
+ -2, 51, 6},
+ /* IRC_Composite_C_R0195_T165_P015.wav */
+ {20, -21, 21, -22, 23, -23, 24, -25, 26, -26, 27, -28,
+ 29, -29, 30, -30, 29, -28, 22, -5, 163, -209, 156, -276,
+ 329, -325, 157, -151, 120, 93, 1138, 522, 872, 2060, 255, 503,
+ -4749, 3937, 3841, 3248, 5654, 4851, 3110, 1024, 1230, 4380, 981, -2433,
+ -2102, 345, 461, 292, -696, 50, 937, 951, -195, 227, 100, -111,
+ -682, -242, -295, -695, -542, -371, -167, -471, -237, -441, -313, -257,
+ -355, -428, -338, -330, -347, -256, -340, -455, -298, -395, -419, -365,
+ -319, -336, -313, -224, -306, -223, -245, -277, -258, -289, -268, -318,
+ -289, -331, -299, -363, -319, -390, -363, -384, -327, -342, -339, -372,
+ -297, -284, -259, -319, -264, -296, -233, -297, -258, -289, -255, -235,
+ -239, -231, -238, -221, -219, -255, -237, -261, -205, -265, -219, -253,
+ -180, -250, -196, -250, -200, -248, -207, -278, -194, -276, -219, -261,
+ -199, -283, -219, -241, -187, -217, -180, -196, -141, -180, -121, -175,
+ -110, -181, -79, -171, -84, -195, -62, -164, -99, -185, -115, -180,
+ -164, -155, -140, -152, -147, -164, -113, -154, -117, -178, -132, -201,
+ -137, -183, -162, -227, -171, -200, -151, -199, -165, -161, -150, -178,
+ -144, -157, -158, -179, -144, -176, -163, -168, -98, -168, -122, -152,
+ -52, -148, -86, -125, -9, -56, -37, -107, -79, -44, -26, -30,
+ -92, -55, -14, 29, 14, 24, 62, 67, 48, 30, 48, 10,
+ 57, -9, 40, -55, 25, -77, 17, -96, 25, -64, 21, -101,
+ -1, 4, 58, -21, 25, 24, 70, 3, 30, 9, 36, -16,
+ 6, -21, -39, -15},
+ /* IRC_Composite_C_R0195_T180_P015.wav */
+ {-9, 9, -9, 9, -10, 10, -10, 10, -11, 11, -11, 11,
+ -12, 12, -13, 13, -14, 15, -15, 16, -17, 19, -21, 50,
+ -66, 25, 121, 28, -120, 155, -168, 335, 416, 671, 222, 2210,
+ 806, 1236, -2882, 612, 2593, 898, 4860, 6139, 4465, 870, -196, 3886,
+ 2840, -2197, -1283, -67, 115, -435, 2, 221, 731, -37, -52, 179,
+ 744, -513, -450, -510, 328, -437, -225, -503, 1, -204, -76, -530,
+ -331, -379, -166, -435, -279, -284, -151, -266, -225, -384, -293, -305,
+ -154, -293, -219, -273, -134, -244, -264, -371, -311, -317, -245, -345,
+ -336, -343, -237, -329, -370, -441, -342, -336, -321, -387, -336, -319,
+ -260, -281, -304, -299, -268, -249, -331, -318, -306, -253, -254, -267,
+ -280, -263, -244, -262, -278, -297, -251, -213, -235, -246, -260, -197,
+ -223, -206, -272, -188, -228, -176, -268, -213, -279, -202, -229, -233,
+ -258, -216, -166, -186, -223, -208, -202, -136, -198, -161, -225, -147,
+ -174, -123, -192, -165, -169, -119, -164, -157, -169, -110, -156, -134,
+ -190, -131, -196, -117, -175, -127, -176, -95, -111, -96, -149, -112,
+ -153, -144, -212, -161, -218, -171, -217, -170, -191, -155, -154, -114,
+ -156, -141, -134, -77, -145, -152, -168, -124, -167, -164, -158, -156,
+ -173, -143, -139, -75, -112, -48, -151, -85, -139, -30, -81, -70,
+ -106, -105, -40, -41, 29, 15, 57, 54, 103, 128, 65, 65,
+ 35, 45, -4, -23, -56, -53, -39, -44, -11, -66, 11, -23,
+ 53, -3, 44, 26, 45, 23, 36, 66, 32, 13, 3, 39,
+ 11, -14, 8, -12},
+ /* IRC_Composite_C_R0195_T195_P015.wav */
+ {3, -3, 4, -4, 4, -5, 5, -5, 6, -6, 7, -7,
+ 8, -9, 9, -10, 11, -13, 14, -16, 19, -22, 27, -33,
+ 44, -87, 114, -132, 102, -154, 155, -97, -20, -37, -29, 363,
+ 412, 474, 853, 1336, 1092, -788, -1724, 2098, 1099, 2640, 4844, 4905,
+ 2895, -39, 1681, 2632, 175, -1400, -248, 325, 22, -206, 196, 419,
+ 561, 41, 192, 277, 401, -173, 2, -114, 106, -178, -24, -176,
+ 30, -91, 54, -173, -183, -124, -65, -136, -127, -106, -163, -59,
+ -115, -115, -267, -191, -291, -145, -314, -229, -397, -205, -300, -229,
+ -373, -215, -360, -218, -346, -234, -397, -220, -360, -257, -382, -254,
+ -389, -213, -322, -234, -324, -214, -345, -227, -381, -255, -363, -174,
+ -354, -207, -318, -230, -299, -263, -316, -271, -243, -219, -239, -195,
+ -263, -183, -262, -181, -310, -210, -258, -227, -229, -243, -215, -263,
+ -194, -269, -182, -235, -184, -209, -147, -196, -148, -196, -147, -207,
+ -155, -221, -187, -223, -184, -221, -208, -205, -186, -173, -165, -148,
+ -132, -141, -127, -151, -120, -154, -131, -136, -104, -119, -115, -117,
+ -108, -145, -140, -188, -146, -212, -174, -199, -183, -203, -176, -190,
+ -174, -178, -155, -183, -156, -192, -139, -178, -158, -185, -162, -172,
+ -198, -158, -136, -126, -116, -127, -125, -157, -109, -154, -85, -152,
+ -60, -135, -26, -117, 10, -26, 16, -15, 36, 69, 64, 91,
+ 41, 52, -5, 31, -40, -16, -60, -3, -25, -20, -13, 18,
+ 23, 6, 15, 15, 42, 21, 45, 37, 32, 17, 30, 46,
+ 26, 13, 23, -19},
+ /* IRC_Composite_C_R0195_T210_P015.wav */
+ {5, -5, 6, -6, 6, -6, 6, -6, 6, -6, 7, -7,
+ 7, -7, 7, -8, 8, -8, 8, -9, 9, -9, 9, -9,
+ 9, -9, 7, -1, -45, -76, 21, -111, 31, -35, -9, -141,
+ 17, -81, 104, 351, 414, 314, 1167, 1284, -698, -334, -392, 1578,
+ 2039, 2332, 5286, 3230, 840, 735, 2013, 886, -250, -668, 388, 293,
+ -154, 194, 485, 539, 373, 247, 362, 554, 379, 14, 200, 206,
+ 243, 111, 161, 197, 127, 193, 183, 265, -4, 123, -60, 64,
+ -188, -26, -316, -89, -293, -150, -287, -153, -256, -136, -244, -221,
+ -246, -144, -274, -195, -276, -224, -307, -223, -340, -289, -302, -287,
+ -323, -301, -317, -303, -243, -292, -298, -294, -312, -280, -306, -243,
+ -309, -226, -276, -214, -262, -251, -287, -252, -261, -239, -268, -241,
+ -251, -220, -263, -226, -289, -222, -266, -235, -299, -243, -268, -249,
+ -232, -227, -224, -212, -188, -201, -205, -192, -215, -147, -197, -162,
+ -188, -158, -182, -181, -170, -210, -185, -204, -179, -201, -174, -187,
+ -176, -157, -173, -157, -158, -147, -138, -131, -123, -128, -126, -106,
+ -147, -126, -173, -126, -184, -147, -199, -168, -203, -173, -188, -192,
+ -200, -201, -192, -187, -199, -212, -196, -185, -179, -192, -178, -197,
+ -173, -188, -148, -169, -132, -157, -149, -165, -140, -138, -136, -123,
+ -135, -83, -122, -54, -79, -22, -60, -30, -15, -6, 29, -8,
+ 28, -22, 45, -13, 39, -33, 30, 5, 20, -21, 0, 10,
+ 30, -5, 10, -9, 35, 17, 55, 16, 50, 34, 50, 43,
+ 21, 55, 18, 41},
+ /* IRC_Composite_C_R0195_T225_P015.wav */
+ {3, -3, 3, -3, 3, -3, 3, -3, 3, -3, 3, -3,
+ 3, -3, 3, -3, 3, -3, 3, -3, 3, -3, 3, -2,
+ 2, -2, 2, -1, 1, 0, -5, -60, -132, -64, -20, -85,
+ -7, -176, -5, -52, -74, -72, 84, 430, 320, 474, 759, 338,
+ -353, -244, 418, 1855, 1977, 2351, 3573, 2234, 1049, 638, 1145, 830,
+ 98, -47, 162, 541, 334, 496, 426, 623, 595, 701, 586, 611,
+ 652, 468, 517, 289, 478, 320, 343, 117, 277, 121, 176, -24,
+ 22, -90, -30, -74, -66, -98, -115, -117, -110, -81, -130, -60,
+ -133, -100, -189, -113, -212, -176, -266, -224, -323, -264, -344, -286,
+ -346, -280, -345, -296, -322, -294, -296, -283, -287, -282, -269, -247,
+ -271, -247, -275, -227, -292, -244, -283, -265, -275, -259, -269, -255,
+ -263, -256, -260, -233, -264, -234, -273, -263, -267, -260, -261, -252,
+ -268, -235, -241, -202, -245, -183, -233, -168, -240, -165, -221, -162,
+ -203, -155, -192, -162, -177, -157, -183, -162, -190, -153, -205, -157,
+ -215, -152, -221, -147, -199, -142, -182, -144, -171, -142, -156, -148,
+ -173, -154, -163, -157, -167, -170, -162, -167, -179, -174, -184, -178,
+ -180, -174, -206, -178, -198, -188, -207, -204, -201, -199, -190, -214,
+ -206, -229, -200, -212, -173, -192, -170, -215, -166, -189, -131, -174,
+ -137, -168, -105, -105, -64, -83, -43, -48, 2, -25, -6, -28,
+ 14, -21, 8, -16, 16, 3, 33, 17, 37, 28, 49, 34,
+ 62, 28, 60, 13, 34, 6, 35, -8, 13, -2, 6, 11,
+ 8, 6, -3, 12},
+ /* IRC_Composite_C_R0195_T240_P015.wav */
+ {0, 0, 0, 0, 1, -1, 1, -1, 1, -1, 1, -1,
+ 1, -1, 1, -1, 2, -2, 2, -2, 2, -2, 3, -3,
+ 3, -4, 4, -5, 6, -7, 9, -12, 19, -47, -65, -42,
+ -103, -56, -86, -61, -84, -136, -84, -125, -76, 46, 139, 205,
+ 243, 514, 311, -296, -329, 431, 1034, 1553, 1739, 2266, 2575, 1097,
+ 709, 1340, 1199, 680, 407, 592, 719, 747, 950, 891, 924, 905,
+ 835, 742, 679, 646, 246, 443, 284, 396, 76, 270, 79, 360,
+ 141, 234, 132, 176, 91, 44, 66, -33, 37, -24, -26, -52,
+ -140, -134, -171, -129, -209, -205, -236, -225, -189, -236, -238, -284,
+ -228, -319, -232, -313, -237, -314, -261, -304, -270, -301, -338, -291,
+ -284, -273, -275, -283, -246, -253, -222, -299, -253, -305, -231, -286,
+ -251, -299, -224, -259, -244, -287, -247, -273, -229, -277, -233, -278,
+ -222, -256, -210, -244, -213, -226, -197, -200, -180, -187, -177, -191,
+ -166, -184, -157, -198, -181, -196, -166, -179, -163, -184, -173, -192,
+ -155, -187, -160, -198, -161, -183, -151, -173, -150, -191, -183, -210,
+ -175, -205, -187, -208, -163, -185, -168, -170, -162, -180, -159, -180,
+ -156, -158, -160, -186, -181, -225, -214, -217, -218, -234, -238, -231,
+ -223, -221, -238, -230, -217, -206, -178, -170, -207, -185, -183, -154,
+ -152, -154, -148, -132, -105, -76, -64, -31, -49, -9, -20, 6,
+ -12, 13, 19, 25, 27, 32, 34, 31, 31, 36, 25, 9,
+ 26, 16, 20, -7, 0, -5, 22, 12, 4, 3, 18, 14,
+ 9, -1, -12, -43},
+ /* IRC_Composite_C_R0195_T255_P015.wav */
+ {1, -1, 2, -2, 2, -2, 2, -2, 2, -2, 2, -2,
+ 2, -2, 2, -2, 2, -2, 2, -2, 2, -2, 2, -2,
+ 2, -2, 3, -3, 3, -3, 3, -4, 5, -6, 10, -30,
+ -120, -103, -93, -24, -84, -108, -106, -140, -67, -76, -73, -121,
+ -133, -119, 68, 29, 241, 313, 294, 61, 72, 517, 913, 1238,
+ 1676, 2099, 2348, 2019, 1198, 861, 1666, 1655, 972, 1040, 933, 753,
+ 835, 866, 579, 456, 471, 608, 472, 520, 250, 352, 304, 298,
+ 249, 242, 223, 199, 145, 120, 235, 93, 122, 49, 3, -39,
+ -15, -81, -71, -213, -81, -243, -130, -266, -181, -248, -213, -210,
+ -258, -180, -292, -166, -311, -258, -301, -258, -288, -304, -246, -283,
+ -255, -296, -284, -260, -336, -252, -351, -234, -370, -214, -328, -219,
+ -303, -234, -296, -242, -261, -235, -272, -252, -263, -194, -230, -174,
+ -222, -179, -253, -165, -264, -178, -236, -165, -217, -158, -210, -155,
+ -187, -164, -190, -169, -218, -178, -196, -162, -199, -188, -233, -170,
+ -224, -161, -189, -146, -207, -166, -198, -168, -188, -182, -169, -203,
+ -189, -199, -180, -192, -203, -183, -200, -212, -208, -215, -216, -240,
+ -188, -209, -178, -223, -162, -198, -167, -209, -211, -239, -196, -203,
+ -168, -214, -197, -239, -145, -209, -149, -231, -167, -214, -131, -185,
+ -129, -161, -129, -137, -105, -119, -97, -124, -84, -109, -56, -30,
+ 29, 13, 51, 35, 66, 39, 42, 23, 8, -3, 12, 8,
+ 29, 13, 32, 3, 26, 10, 21, -16, -7, -1, 7, 2,
+ -5, -3, -3, -1},
+ /* IRC_Composite_C_R0195_T270_P015.wav */
+ {-1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1,
+ -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1,
+ -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -2, 2,
+ -2, 3, -5, 38, 41, 81, 155, 185, 109, 43, 168, 195,
+ 276, 233, 321, 381, 601, 818, 813, 1005, 694, 582, 706, 813,
+ 1380, 2408, 2859, 3417, 3192, 1234, 164, 601, 379, 145, -151, -609,
+ -543, -195, 49, -120, -111, 136, 61, -67, -105, -152, -114, -346,
+ -267, -397, -310, -240, -454, -334, -493, -338, -595, -418, -579, -472,
+ -506, -514, -489, -592, -416, -592, -471, -574, -491, -531, -515, -529,
+ -526, -514, -446, -512, -519, -497, -434, -454, -430, -467, -415, -434,
+ -400, -422, -405, -387, -422, -391, -428, -343, -360, -326, -389, -310,
+ -335, -241, -318, -246, -341, -202, -264, -207, -278, -221, -234, -239,
+ -210, -224, -186, -240, -226, -220, -207, -184, -222, -220, -245, -206,
+ -189, -231, -234, -238, -195, -237, -129, -227, -188, -216, -145, -224,
+ -225, -241, -252, -274, -214, -216, -202, -212, -164, -190, -171, -187,
+ -134, -175, -141, -168, -164, -167, -150, -174, -138, -128, -101, -135,
+ -76, -112, -70, -116, -70, -102, -92, -120, -98, -123, -112, -146,
+ -92, -145, -88, -135, -96, -115, -84, -87, -67, -61, -82, -42,
+ -48, 11, -13, 5, 25, 35, 52, 70, 95, 98, 101, 116,
+ 108, 116, 94, 137, 109, 102, 83, 89, 88, 89, 64, 59,
+ 40, 43, 14, 53, 3, 16, -3, 7, 12, -13, 16, -2,
+ 28, 6, 37, 18},
+ /* IRC_Composite_C_R0195_T285_P015.wav */
+ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, -1, 1, -1, 1, -1,
+ 2, -3, 6, -109, -159, -182, -252, -225, -252, -209, -220, -323,
+ -249, -123, 27, 108, 237, 39, -227, -176, -141, 26, 983, 1563,
+ 1989, 2256, 2042, 1443, 1643, 1525, 1088, 1602, 1474, 1170, 1150, 824,
+ 708, 924, 1176, 1036, 269, 443, 781, 769, 425, 308, 405, 367,
+ 433, 461, 448, 234, 194, 163, 218, 246, 100, -62, 56, -52,
+ 109, -114, 68, -271, 33, -231, -10, -251, -107, -211, -189, -193,
+ -271, -173, -258, -220, -344, -246, -222, -245, -241, -310, -276, -282,
+ -207, -275, -244, -325, -317, -257, -224, -252, -269, -249, -255, -262,
+ -227, -233, -233, -271, -256, -189, -188, -164, -249, -173, -198, -150,
+ -210, -124, -172, -127, -154, -156, -239, -193, -248, -200, -220, -169,
+ -212, -123, -176, -157, -249, -249, -297, -272, -295, -303, -316, -296,
+ -263, -235, -217, -173, -234, -208, -250, -182, -255, -227, -234, -247,
+ -244, -205, -187, -219, -199, -160, -156, -146, -178, -163, -199, -166,
+ -183, -164, -214, -208, -196, -160, -168, -196, -193, -160, -204, -162,
+ -188, -175, -206, -218, -198, -177, -203, -194, -169, -169, -172, -168,
+ -160, -149, -149, -113, -128, -93, -92, -23, -69, -13, -56, -10,
+ -44, 8, -28, 9, -21, 0, 13, 41, 16, 24, 3, 43,
+ 28, 54, 11, 12, -13, 29, -17, 4, -27, 25, -15, 29,
+ -5, 31, 21, 23},
+ /* IRC_Composite_C_R0195_T300_P015.wav */
+ {3, -3, 3, -3, 3, -3, 3, -3, 4, -4, 4, -4,
+ 4, -4, 4, -4, 4, -4, 5, -5, 5, -5, 5, -6,
+ 6, -6, 6, -7, 7, -8, 9, -10, 13, -17, 28, -93,
+ -234, -180, -207, -251, -293, -262, -250, -346, -376, -130, 67, -6,
+ 108, -151, -710, -124, 223, 55, 1941, 2161, 1520, 2079, 1404, 552,
+ 750, 680, 497, 384, 1277, 1177, 733, 896, 1394, 1657, 1268, 1283,
+ 1309, 1208, 892, 805, 954, 655, 670, 296, 482, 320, 570, 596,
+ 507, 445, 118, 348, 106, 469, 16, 25, -157, 191, 5, 47,
+ -157, -78, -270, -124, -129, -61, -219, -206, -188, -118, -157, -193,
+ -216, -244, -222, -155, -190, -248, -263, -189, -260, -255, -305, -285,
+ -272, -179, -250, -320, -353, -177, -207, -158, -311, -279, -294, -136,
+ -181, -176, -231, -183, -184, -86, -72, -138, -172, -154, -185, -208,
+ -132, -194, -245, -197, -155, -169, -170, -257, -250, -273, -291, -308,
+ -267, -292, -322, -261, -262, -223, -282, -280, -302, -284, -249, -260,
+ -293, -276, -203, -199, -209, -219, -251, -230, -225, -236, -239, -254,
+ -234, -253, -179, -216, -170, -211, -150, -212, -180, -190, -146, -198,
+ -192, -173, -159, -174, -166, -187, -188, -211, -146, -166, -163, -201,
+ -184, -171, -129, -130, -154, -144, -145, -167, -132, -161, -149, -160,
+ -132, -127, -113, -104, -89, -73, -95, -76, -83, -55, -53, -22,
+ -5, -13, -3, 5, 22, 0, 15, 5, -11, -15, 15, -13,
+ -5, -8, 7, -15, 26, 37, 33, 12, 52, 50, 45, 34,
+ 80, 35, 79, 31},
+ /* IRC_Composite_C_R0195_T315_P015.wav */
+ {-3, 3, -3, 3, -3, 3, -3, 3, -3, 3, -3, 3,
+ -3, 3, -3, 3, -3, 3, -3, 3, -3, 3, -3, 3,
+ -3, 3, -3, 3, -3, 2, -2, 2, -3, -145, -419, -273,
+ -234, -320, -494, -368, -332, -137, 23, 435, 141, -274, 189, -707,
+ -1152, 1545, 415, 1656, 4094, 1604, 1580, 2053, 473, -525, 351, 1046,
+ 431, 4, 613, 555, 843, 494, 1071, 1048, 1375, 1139, 994, 1431,
+ 1283, 1617, 838, 1011, 491, 1146, 716, 701, 358, 388, 443, 310,
+ 457, 32, -5, -93, 169, 269, -27, 91, -165, 86, -189, 135,
+ -120, -103, -270, -74, -56, -217, -55, -238, -119, -288, -120, -263,
+ -264, -223, -279, -179, -302, -173, -211, -262, -189, -255, -177, -382,
+ -172, -263, -206, -302, -155, -222, -165, -156, -91, -226, -129, -208,
+ -104, -116, -7, -182, -57, -43, -59, -165, -192, -254, -334, -329,
+ -222, -307, -218, -299, -183, -230, -153, -307, -262, -341, -352, -360,
+ -306, -320, -349, -329, -284, -313, -262, -287, -276, -348, -332, -282,
+ -281, -235, -311, -213, -232, -205, -232, -209, -189, -252, -237, -224,
+ -214, -199, -229, -157, -205, -196, -185, -173, -156, -212, -157, -188,
+ -156, -172, -131, -151, -175, -157, -139, -172, -126, -151, -157, -177,
+ -123, -115, -119, -116, -103, -151, -157, -157, -103, -111, -83, -143,
+ -134, -106, -78, -57, -83, -76, -143, -96, -110, -30, -81, -37,
+ -72, -10, -21, -9, 18, 1, -1, -42, 21, 2, 14, 26,
+ 52, 25, 26, 34, 53, 32, 44, 40, 74, 42, 66, 49,
+ 90, 62, 69, 50},
+ /* IRC_Composite_C_R0195_T330_P015.wav */
+ {1, -1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ -1, 1, -2, 2, -2, 3, -3, 4, -5, 6, -7, 9,
+ -11, 14, -18, 25, -38, 72, -494, -377, -163, -458, -490, -5,
+ -649, -505, -448, 737, 42, 720, -5, -1466, 655, -1957, -272, 3638,
+ 1401, 3790, 5414, 320, 1005, 1351, 716, -1658, 736, 755, 629, -241,
+ 367, 775, 655, 1313, 522, 711, 607, 1017, 814, 1153, 1014, 727,
+ 1072, 1193, 1369, 911, 877, 665, 747, 788, 602, 389, -2, 301,
+ 79, 299, 235, 78, -171, -171, -12, -204, -96, -124, -285, -390,
+ -109, 136, -127, -79, -244, -125, -236, -21, -257, -313, -264, -266,
+ -248, -188, -112, -345, -304, -50, -265, -235, -235, -38, -345, -176,
+ -119, -52, -156, -49, -98, -184, -269, -46, -139, -85, -206, -15,
+ -64, -75, -25, 28, -69, -306, -216, -145, -321, -127, -396, -359,
+ -364, -161, -402, -370, -313, -329, -419, -337, -409, -317, -386, -319,
+ -505, -396, -337, -244, -368, -365, -380, -286, -289, -230, -330, -297,
+ -331, -175, -233, -184, -243, -195, -204, -144, -176, -187, -238, -158,
+ -239, -170, -186, -124, -214, -122, -133, -166, -196, -122, -151, -204,
+ -200, -147, -209, -159, -148, -140, -151, -108, -106, -148, -137, -107,
+ -109, -157, -138, -152, -159, -152, -143, -132, -169, -84, -121, -99,
+ -110, -53, -57, -49, -65, -56, -59, -72, -39, -80, -76, -50,
+ -24, -78, -31, -4, 10, -54, 43, 12, 18, 43, 54, 13,
+ -6, 86, 38, 22, 50, 62, 50, 38, 99, 31, 62, 60,
+ 58, 29, 11, 38},
+ /* IRC_Composite_C_R0195_T345_P015.wav */
+ {-11, 11, -12, 12, -13, 13, -14, 15, -16, 16, -17,
+ 18, -19, 20, -22, 23, -24, 26, -27, 29, -32, 34,
+ -38, 43, -52, 76, -328, -363, -214, -611, -509, -335, -643,
+ -447, -869, 31, 57, 1040, 355, -374, 1188, -2297, -4332, 5821,
+ -1811, 4636, 8422, 1800, 2573, 2446, 333, -2543, 348, 441, 1038,
+ -1221, -120, 503, 1705, 557, 994, 621, 1108, 529, 1387, 553,
+ 713, 268, 987, 1168, 1373, 742, 1063, 633, 1079, 800, 733,
+ 478, 430, -8, 448, 243, 389, -34, 240, -156, 40, -68,
+ 208, -262, -163, -284, -85, -119, 4, -292, -169, -173, -9,
+ -177, -185, -284, -261, -239, -151, -333, -444, -278, -212, -269,
+ -320, -207, -353, -167, -122, 5, -101, -146, -236, 26, 158,
+ -193, -6, 31, -110, -13, 194, -336, -291, 166, -187, -339,
+ 71, 83, -256, -36, 64, -291, -253, -258, -423, -483, -404,
+ -542, -450, -398, -404, -517, -415, -395, -300, -370, -455, -406,
+ -370, -334, -365, -318, -452, -367, -267, -228, -288, -345, -283,
+ -288, -195, -237, -211, -198, -242, -243, -148, -120, -215, -182,
+ -175, -171, -178, -118, -143, -239, -153, -169, -160, -247, -180,
+ -208, -203, -145, -188, -199, -188, -106, -162, -137, -157, -131,
+ -109, -183, -154, -130, -109, -197, -150, -64, -168, -155, -207,
+ -78, -176, 15, -176, -113, -192, -34, -58, -13, -144, -90,
+ -50, -81, -89, 24, -35, -100, -56, -23, -9, 29, 37,
+ -35, -51, 58, 55, 55, -8, 24, 51, 71, 28, 39,
+ 46, 49, 51, 27, -11, 48, 41, 22, -3, 23, -14,
+ 20, -19, -13}};
+
+const int16_t irc_composite_c_r0195_p030[][256] =
+ {/* IRC_Composite_C_R0195_T000_P030.wav */
+ {-54, 55, -55, 56, -57, 57, -58, 58, -58, 59, -58,
+ 58, -58, 56, -55, 52, -48, 42, -34, 21, 1, -38,
+ 103, -854, -293, -223, -413, -662, -699, -525, -649, -153, -821,
+ -339, 218, 2022, 2054, -2330, -2792, 1747, -9777, 10764, 1443, 5425,
+ 12121, 258, -1458, 190, 2417, 73, -781, 460, -747, -560, 1297,
+ 150, 875, 435, 1456, 292, 1300, -209, 948, 616, 962, -100,
+ 649, 571, 794, 577, 979, 597, 904, 611, 978, 804, 976,
+ 113, 890, 241, 591, 176, 615, -381, 92, -14, 208, -196,
+ 144, -25, -87, -290, -51, -201, -11, -376, 13, -401, 6,
+ -326, 16, -348, -73, -269, -196, -276, -77, -266, -298, -332,
+ -42, -326, -208, -519, -147, -423, -93, -432, -257, -451, -109,
+ -270, -192, -375, -151, -315, -188, -206, -37, -459, -168, -241,
+ -10, -278, -73, -257, 87, -155, -18, -234, -126, -305, -82,
+ -294, -405, -294, -172, -409, -47, -89, -22, -89, 128, -65,
+ 30, 48, -38, -108, -174, -371, -254, -219, -255, -366, -375,
+ -485, -192, -333, -204, -374, -171, -415, -254, -437, -213, -353,
+ -257, -320, -265, -229, -230, -199, -344, -342, -208, -291, -328,
+ -415, -224, -366, -172, -311, -156, -289, -178, -259, -84, -212,
+ -136, -195, -136, -204, -23, -192, -135, -219, -38, -190, -41,
+ -200, -34, -195, -41, -117, -47, -160, -96, -111, -97, -131,
+ -73, -186, -78, -137, -70, -202, -93, -119, -55, -149, -67,
+ -139, -48, -119, -34, -122, -39, -119, -31, -62, -1, -61,
+ -28, -48, 31, 6, 17, -8, 75, 29, 40, 45, 65,
+ 21, 45, 14},
+ /* IRC_Composite_C_R0195_T015_P030.wav */
+ {45, -49, 52, -57, 61, -66, 71, -77, 83, -90,
+ 98, -107, 116, -126, 138, -150, 164, -177, 188, -179,
+ -73, -966, -102, -989, 560, -1173, -327, -1684, 597, -1115,
+ 259, -1628, 2183, 1183, 3440, -5302, 3131, -10502, -529, 12738,
+ -6827, 21598, 5258, -7, -3187, 1963, 495, 327, 502, -665,
+ -1769, 1964, -371, 1009, 596, 896, 151, 1819, -137, 401,
+ 915, 974, 249, 114, 559, 962, 751, 584, 371, 576,
+ 838, 1123, 906, 351, -23, 332, 521, 427, -138, -265,
+ -100, 55, -54, 59, -205, -261, -77, 2, -107, -229,
+ -219, -403, -235, -134, -143, -470, -234, -177, -61, -236,
+ -83, -258, -197, -192, -101, -300, -331, -254, -216, -242,
+ -271, -289, -310, -261, -262, -333, -321, -293, -270, -333,
+ -255, -243, -275, -316, -256, -252, -176, -243, -182, -212,
+ -172, -289, -118, -228, -303, -307, -77, -225, -298, -230,
+ -122, -403, -282, -152, -387, -451, 8, 13, -160, -49,
+ 124, -112, -104, 87, -45, -358, -217, -135, -206, -167,
+ -172, -229, -317, -197, -162, -307, -272, -363, -290, -325,
+ -168, -360, -289, -359, -212, -286, -239, -278, -241, -270,
+ -234, -333, -266, -308, -234, -344, -218, -357, -274, -331,
+ -208, -244, -205, -284, -219, -226, -149, -177, -181, -201,
+ -159, -198, -109, -172, -113, -218, -95, -145, -67, -151,
+ -73, -148, -84, -129, -52, -118, -51, -142, -54, -113,
+ -29, -150, -66, -118, -44, -150, -8, -105, -59, -133,
+ -10, -103, -51, -102, -26, -114, 2, -62, -14, -78,
+ 24, -62, 35, -36, 28, -33, 74, -22, 57, 13,
+ 101, -23, 94, 36, 50, 32},
+ /* IRC_Composite_C_R0195_T030_P030.wav */
+ {22, -22, 22, -22, 22, -22, 21, -21, 20, -19, 18,
+ -16, 13, -9, 3, 6, -23, 62, -927, -68, -994, 540,
+ -922, -246, -1369, 328, -1205, -194, -1442, 1439, 570, 2758, -885,
+ 3393, -4925, -8851, 7444, -11725, 26992, 7407, 2376, 4575, -342, -2312,
+ -724, 2866, -938, -1366, 86, -585, 1150, 1637, 627, 500, 364,
+ 713, 726, 1042, -231, 616, 243, 1337, 315, 722, 212, 833,
+ 509, 1128, 741, 763, -23, 706, -315, 392, -241, 100, -771,
+ -188, -212, 298, -422, -413, -205, 37, -281, -130, -523, -88,
+ -503, -112, -573, -211, -454, -80, -504, -132, -294, -80, -239,
+ -22, -204, -163, -320, -82, -296, -297, -476, -149, -313, -195,
+ -414, -189, -328, -173, -333, -197, -351, -236, -353, -212, -335,
+ -289, -358, -212, -232, -235, -269, -127, -247, -255, -385, -167,
+ -387, -267, -392, -201, -373, -245, -334, -180, -331, -245, -322,
+ -210, -238, -125, -198, -59, -138, -44, -137, -3, -58, 9,
+ -199, -115, -324, -112, -304, -122, -334, -195, -342, -88, -186,
+ -146, -296, -157, -238, -181, -256, -164, -321, -198, -320, -193,
+ -346, -189, -318, -169, -297, -240, -369, -178, -273, -261, -370,
+ -222, -299, -226, -327, -186, -311, -192, -283, -157, -277, -144,
+ -267, -140, -249, -109, -220, -95, -203, -109, -172, -51, -152,
+ -88, -149, -49, -133, -83, -123, -30, -107, -62, -145, -55,
+ -94, -23, -81, -49, -86, -1, -42, -24, -119, -38, -58,
+ -5, -105, -50, -71, 1, -68, -36, -53, -6, -19, 20,
+ -14, 18, -29, 55, -17, 56, -9, 73, 3, 81, 35,
+ 97, 32, 73},
+ /* IRC_Composite_C_R0195_T045_P030.wav */
+ {9, -7, 5, -3, 0, 3, -7, 12, -18, 25, -34,
+ 45, -60, 79, -107, 151, -363, -677, -240, -871, 518, -941,
+ -269, -1276, 265, -1859, 1187, -1350, 2149, 1, 5698, -4523, 4866,
+ -12427, -2128, 5167, 3666, 25146, 2435, 401, 417, 1825, -3164, 2605,
+ 613, -1851, -1719, 979, -329, 2301, -426, 1606, -126, 1675, 119,
+ 1149, -57, 595, -80, 1644, 19, 793, -129, 1326, 678, 1045,
+ -17, 856, -22, 267, -380, 190, -844, -146, -664, 199, -735,
+ -167, -428, 32, -402, -217, -411, -308, -427, -284, -543, -438,
+ -454, -307, -561, -259, -430, -199, -381, -181, -382, -148, -228,
+ -87, -405, -169, -303, -91, -361, -175, -378, -291, -318, -127,
+ -301, -269, -260, -171, -316, -164, -268, -241, -361, -178, -341,
+ -229, -322, -201, -335, -221, -314, -343, -328, -221, -321, -372,
+ -308, -210, -400, -380, -322, -269, -386, -304, -356, -223, -292,
+ -166, -317, -192, -91, -100, -158, -51, -73, -116, -23, -56,
+ -150, -243, -92, -175, -169, -181, -177, -294, -169, -187, -210,
+ -286, -130, -227, -213, -253, -159, -238, -150, -211, -249, -260,
+ -144, -232, -247, -283, -227, -289, -178, -293, -252, -299, -148,
+ -285, -211, -271, -197, -276, -200, -222, -204, -252, -176, -235,
+ -205, -223, -107, -218, -119, -198, -109, -182, -35, -149, -101,
+ -171, -62, -123, -44, -128, -75, -141, -37, -172, -54, -143,
+ -11, -144, -27, -104, 26, -103, 23, -105, 37, -73, 18,
+ -74, 15, -63, 4, -83, 16, -68, -5, -50, 47, -28,
+ 24, -35, 41, -27, 78, -21, 77, -17, 105, 16, 96,
+ 7, 122, 24},
+ /* IRC_Composite_C_R0195_T060_P030.wav */
+ {8, -7, 7, -6, 5, -5, 4, -3, 3, -3, 3,
+ -3, 5, -9, 20, 86, -790, 103, -872, 55, -678, -196,
+ -962, -387, -893, 162, 722, 1609, 608, 2929, 1713, -6837, -667,
+ -12691, 15713, 13906, 9242, 5982, 83, -1809, 1076, 1596, 1326, -1529,
+ -1392, -366, 731, 78, 1368, -246, 850, 592, 521, 699, 1115,
+ 310, 930, 973, 592, 664, 159, 708, 710, 489, -74, 485,
+ -697, 374, -695, 249, -1252, 204, -915, 137, -777, -91, -241,
+ -403, -350, -466, -269, -521, -499, -540, -380, -320, -709, -233,
+ -517, -228, -634, -244, -652, -290, -447, -147, -545, -241, -352,
+ 5, -488, -69, -437, 53, -465, -6, -353, -78, -525, -24,
+ -335, -157, -418, -27, -367, -103, -287, -241, -397, -158, -276,
+ -304, -351, -181, -369, -225, -401, -209, -512, -184, -447, -153,
+ -476, -152, -484, -76, -492, -243, -498, -127, -515, -186, -392,
+ -135, -408, -53, -239, -82, -191, 13, -138, -32, -118, -6,
+ -177, -44, -187, -143, -246, -50, -262, -108, -264, -71, -256,
+ -48, -325, -90, -349, -63, -352, -110, -280, -159, -311, -153,
+ -350, -187, -295, -152, -318, -137, -192, -178, -252, -142, -284,
+ -157, -277, -190, -286, -151, -282, -119, -257, -91, -310, -54,
+ -224, -93, -207, -75, -245, -9, -203, -48, -237, 12, -182,
+ -40, -201, 33, -146, -26, -200, -37, -156, -43, -159, -28,
+ -130, -32, -114, 20, -143, 6, -113, 12, -126, 58, -103,
+ 21, -121, 59, -91, 44, -108, 59, -43, 47, -58, 60,
+ -38, 52, -61, 83, -36, 62, 3, 66, 27, 84, 84,
+ 65, 66, 103},
+ /* IRC_Composite_C_R0195_T075_P030.wav */
+ {100, -103, 106, -109, 112, -115, 118, -120, 122, -122, 119,
+ -110, 88, -25, 53, -874, 378, -897, 369, -1226, 537, -1263,
+ 224, -1798, 1274, 98, 2195, -462, 4170, 361, -3989, -3149, -11328,
+ 17722, 9812, 11683, 3944, -511, -808, 1931, 2611, 1751, -2704, -605,
+ -430, 728, -349, 908, 612, 941, -687, 931, 759, -66, 894,
+ 2123, 310, 1489, 449, 746, 475, 494, -493, -725, 213, -1074,
+ 381, -1100, -205, -670, -115, -645, -170, -469, -431, 31, -824,
+ -248, -707, -458, -476, -267, -901, -228, -228, -601, -420, -335,
+ -514, -545, -367, -632, -446, -566, -351, -391, -306, -444, -134,
+ -388, -144, -333, -243, -194, -98, -434, -105, -173, -206, -249,
+ -121, -377, -102, -345, -159, -256, -146, -470, -106, -327, -152,
+ -330, -205, -368, -192, -409, -197, -444, -310, -359, -229, -461,
+ -161, -393, -318, -351, -227, -387, -261, -314, -291, -270, -249,
+ -242, -213, -299, -192, -269, -108, -253, -73, -278, 80, -171,
+ -11, -111, 7, -198, -30, -155, -148, -117, -164, -168, -181,
+ -155, -172, -221, -156, -207, -176, -229, -130, -292, -171, -280,
+ -233, -288, -132, -237, -242, -209, -166, -245, -147, -263, -160,
+ -240, -126, -261, -109, -276, -150, -274, -92, -247, -135, -225,
+ -43, -207, -57, -174, -33, -157, -10, -172, -20, -116, -56,
+ -146, 22, -132, -61, -119, -53, -166, -22, -107, -81, -99,
+ -40, -65, -89, -70, -32, -77, -31, -75, -25, -75, 36,
+ -146, 3, -52, 3, -100, 13, -55, -5, -17, 13, -41,
+ 5, -12, 59, -13, 32, 3, 61, 44, 107, 53, 129,
+ 85, 118, 95},
+ /* IRC_Composite_C_R0195_T090_P030.wav */
+ {-85, 89, -94, 98, -102, 107, -112, 116, -119, 121,
+ -119, 106, -58, -455, 60, 99, -1054, -36, -88, 242,
+ -1635, 433, -1255, 1196, -1210, 2540, -840, 5344, -2798, 4670,
+ -10472, -874, 1281, 9198, 19443, 266, 2404, -1705, 3816, 1662,
+ 3038, 83, -1499, -2490, 1368, -37, 500, -334, 1490, -626,
+ 909, -15, 1075, 678, 1886, 658, 852, 386, 1418, 146,
+ 400, -1073, -338, -633, -165, -1024, -383, -1251, -264, -407,
+ -25, -1062, -140, -559, 70, -716, -459, -642, -96, -725,
+ -472, -412, -384, -647, -281, -605, -395, -572, -399, -706,
+ -351, -583, -297, -495, -434, -423, -201, -406, -407, -323,
+ -268, -341, -186, -360, -201, -241, -141, -313, -94, -257,
+ -169, -208, -194, -224, -147, -275, -276, -255, -215, -224,
+ -195, -256, -265, -271, -252, -352, -290, -430, -350, -309,
+ -268, -387, -267, -289, -271, -262, -303, -332, -237, -287,
+ -328, -292, -220, -307, -211, -297, -184, -216, -183, -248,
+ -116, -180, -163, -187, -112, -160, -80, -117, -46, -81,
+ -25, -91, -53, -114, -120, -176, -181, -173, -184, -205,
+ -224, -154, -233, -229, -178, -204, -243, -197, -188, -194,
+ -206, -154, -204, -205, -213, -116, -191, -127, -210, -145,
+ -216, -173, -183, -177, -263, -209, -141, -116, -148, -107,
+ -124, -68, -87, -45, -121, -68, -134, -77, -87, -29,
+ -115, -67, -117, -46, -114, -27, -115, -45, -100, -16,
+ -82, -32, -85, -17, -74, -28, -41, -27, -85, -23,
+ -68, -40, -47, -15, -96, -13, -38, 11, -39, -38,
+ -19, 1, -19, -3, -8, -15, -22, 28, 17, 70,
+ 76, 128, 80, 124, 112, 151},
+ /* IRC_Composite_C_R0195_T105_P030.wav */
+ {104, -111, 117, -125, 133, -143, 153, -166, 180, -197, 218,
+ -245, 288, -401, 132, -429, -287, -538, 503, -701, -185, -1083,
+ 867, -1151, 1236, -192, 2589, 704, 2171, -548, -4634, -1245, -3979,
+ 18483, 6281, 7009, -196, 501, 1762, 4063, 3838, -1, -2732, -1085,
+ -409, 884, 444, -393, -9, 733, 362, 138, 1179, 1594, 1300,
+ 947, 317, 734, 700, 55, -900, -943, -310, -712, -521, -933,
+ -377, -1554, -143, -410, 48, -924, -152, -440, -212, -554, -286,
+ -601, -527, -371, -454, -653, -533, -426, -450, -579, -486, -483,
+ -369, -609, -354, -543, -201, -457, -293, -586, -146, -430, -339,
+ -522, -196, -439, -324, -340, -141, -423, -157, -344, -39, -390,
+ -88, -363, -66, -244, -152, -284, -176, -153, -268, -264, -245,
+ -148, -256, -290, -221, -325, -190, -340, -211, -490, -165, -333,
+ -238, -471, -203, -235, -228, -397, -218, -264, -147, -365, -267,
+ -367, -142, -319, -211, -356, -158, -290, -141, -243, -127, -217,
+ -86, -135, -64, -128, -75, -153, -67, -155, -25, -190, -45,
+ -193, -18, -259, 1, -235, -77, -284, -88, -227, -151, -250,
+ -186, -245, -111, -256, -126, -274, -46, -272, -88, -254, -32,
+ -214, -71, -201, -144, -187, -98, -154, -182, -196, -78, -154,
+ -85, -159, -36, -221, -26, -121, -4, -188, -47, -127, -22,
+ -132, -24, -158, -49, -155, -2, -158, -21, -135, -30, -121,
+ 10, -87, -16, -118, 10, -48, -22, -86, -12, -80, 14,
+ -80, 13, -86, 50, -103, 29, -96, 64, -85, 45, -104,
+ 50, -45, 64, -67, -11, -39, 117, -14, 158, 27, 234,
+ 13, 225, 68},
+ /* IRC_Composite_C_R0195_T120_P030.wav */
+ {28, -28, 29, -29, 29, -29, 29, -27, 25, -21, 15,
+ -5, -12, 43, -140, 88, -559, -189, 139, -567, 198, -314,
+ -739, -383, 1538, -450, 794, 642, 4608, -2302, 1277, -7331, 2563,
+ 5148, 5379, 12280, 2713, -323, -1693, 7682, 3305, 1850, -1530, -2098,
+ -1411, 1431, -392, 275, -715, 741, -246, 1255, 618, 1870, 987,
+ 1146, 368, 730, -169, 18, -891, -805, -1522, -439, -421, -616,
+ -1006, -319, -441, -263, -511, -164, -630, -92, -636, -184, -746,
+ -202, -725, -323, -658, -347, -529, -471, -673, -383, -415, -310,
+ -627, -358, -521, -225, -567, -258, -424, -264, -432, -273, -312,
+ -333, -413, -308, -330, -340, -367, -280, -273, -329, -276, -223,
+ -220, -254, -266, -200, -177, -199, -276, -232, -217, -244, -204,
+ -238, -182, -299, -214, -297, -195, -311, -235, -299, -177, -299,
+ -241, -285, -222, -309, -277, -284, -244, -293, -235, -236, -241,
+ -258, -206, -250, -223, -292, -255, -245, -224, -254, -179, -205,
+ -152, -237, -116, -146, -78, -191, -75, -140, -27, -176, -63,
+ -134, -82, -199, -79, -164, -84, -202, -123, -164, -105, -201,
+ -110, -207, -105, -211, -126, -202, -134, -203, -129, -204, -131,
+ -154, -100, -194, -94, -163, -74, -166, -82, -216, -107, -149,
+ -27, -161, -56, -129, -40, -130, -55, -114, -53, -102, -63,
+ -77, -31, -89, -106, -127, -83, -123, -74, -94, -24, -173,
+ -31, -94, 11, -133, -8, -83, -23, -88, 12, -65, -46,
+ -111, 25, -94, 13, -117, 28, -88, 37, -79, 24, -35,
+ 51, -27, 22, 7, 50, -3, 53, 7, 103, 86, 163,
+ 91, 162, 75},
+ /* IRC_Composite_C_R0195_T135_P030.wav */
+ {6, -6, 7, -7, 8, -8, 9, -9, 10, -10, 11, -12,
+ 13, -14, 16, -18, -114, -234, -98, -110, -115, -173, -170, -232,
+ -274, 363, 317, 1353, 133, 2229, 1323, 703, -7423, 4287, 1521, 5913,
+ 8831, 4880, 804, -872, 5713, 3941, 3130, -2817, -2290, -315, 304, -50,
+ -26, -209, 494, 295, 1100, 751, 1288, 533, 716, 964, 54, -449,
+ -399, -528, -808, -934, -947, -951, -457, -538, -157, -625, -308, -385,
+ -95, -571, -310, -374, -278, -494, -514, -429, -461, -556, -433, -529,
+ -432, -535, -327, -580, -236, -492, -287, -555, -216, -430, -280, -451,
+ -246, -351, -270, -341, -290, -325, -254, -404, -265, -383, -232, -420,
+ -232, -398, -198, -399, -199, -300, -210, -291, -213, -230, -263, -273,
+ -261, -215, -220, -262, -203, -257, -189, -275, -199, -298, -243, -292,
+ -198, -258, -246, -263, -220, -238, -243, -271, -259, -242, -227, -263,
+ -220, -247, -166, -281, -180, -274, -165, -251, -184, -245, -176, -216,
+ -217, -207, -158, -156, -155, -166, -106, -127, -108, -139, -81, -152,
+ -98, -167, -88, -170, -109, -150, -125, -150, -119, -119, -124, -135,
+ -111, -142, -113, -144, -112, -168, -76, -193, -154, -199, -103, -124,
+ -149, -137, -125, -90, -105, -96, -120, -90, -84, -94, -99, -96,
+ -69, -90, -69, -82, -94, -80, -67, -60, -94, -70, -110, -76,
+ -102, -51, -103, -55, -74, -38, -105, -57, -91, -76, -96, -51,
+ -36, -59, -52, -57, -56, -61, -46, -70, -31, -32, -25, -47,
+ 6, -6, 17, -33, -1, 20, 49, 32, 43, 24, 63, 112,
+ 138, 114, 43, 112},
+ /* IRC_Composite_C_R0195_T150_P030.wav */
+ {40, -42, 44, -47, 49, -53, 56, -60, 64, -69, 75, -82,
+ 90, -99, 110, -125, 148, -207, 85, -316, 199, -301, 148, -341,
+ 137, -377, 122, 76, 967, 463, 1542, 593, 2935, -2335, -3746, 3892,
+ 1126, 7018, 5853, 6116, -1287, 2364, 4148, 3574, 181, -2393, -1125, -178,
+ 512, -398, -17, -462, 310, 1083, 929, 1228, 538, 479, 431, 450,
+ -124, -446, -926, -681, -484, -854, -858, -886, -425, -380, -163, -187,
+ -475, -333, -267, -193, -398, -286, -470, -331, -472, -511, -390, -535,
+ -398, -443, -447, -458, -316, -355, -323, -348, -312, -366, -331, -282,
+ -304, -410, -276, -324, -270, -334, -288, -347, -263, -345, -283, -348,
+ -294, -324, -322, -384, -264, -346, -287, -352, -258, -326, -246, -345,
+ -247, -332, -220, -276, -243, -276, -171, -257, -208, -277, -184, -237,
+ -184, -265, -186, -245, -165, -259, -201, -253, -180, -238, -198, -272,
+ -176, -246, -187, -277, -161, -252, -150, -275, -137, -267, -126, -254,
+ -141, -241, -149, -227, -151, -206, -151, -203, -126, -181, -101, -170,
+ -85, -185, -87, -146, -87, -170, -106, -170, -90, -160, -111, -150,
+ -107, -140, -90, -144, -63, -132, -90, -238, -87, -160, -61, -203,
+ -110, -165, -57, -102, -36, -116, -53, -98, -41, -96, -62, -105,
+ -61, -102, -70, -120, -86, -109, -103, -103, -70, -67, -72, -85,
+ -74, -124, -64, -94, -56, -139, -102, -99, -75, -97, -58, -61,
+ -30, -94, -25, -74, -43, -104, -48, -76, -56, -75, -27, -39,
+ -24, -3, 21, -10, -1, 6, 97, 22, 44, 1, 101, 93,
+ 134, 63, 82, 45},
+ /* IRC_Composite_C_R0195_T165_P030.wav */
+ {-5, 5, -6, 6, -6, 6, -6, 7, -7, 7, -8, 8,
+ -9, 9, -10, 11, -12, 14, -17, 25, -163, 17, -80, 12,
+ 2, -73, 14, -200, 92, 163, 755, 471, 1371, 723, 2348, -678,
+ -3403, 2150, 1314, 5172, 5430, 6608, 508, 1066, 3048, 2691, 1177, -1954,
+ -1312, -864, 1114, -99, -395, -170, 755, 800, 840, 925, 689, -7,
+ -2, -92, -86, -565, -703, -538, -415, -580, -306, -607, -337, -594,
+ -235, -197, -71, -393, -276, -461, -131, -367, -319, -372, -331, -590,
+ -309, -448, -316, -391, -370, -330, -202, -345, -331, -285, -252, -333,
+ -332, -343, -285, -341, -299, -355, -341, -319, -335, -324, -306, -321,
+ -304, -329, -296, -343, -321, -372, -317, -381, -294, -371, -299, -341,
+ -289, -334, -273, -311, -239, -276, -234, -285, -217, -256, -202, -250,
+ -169, -221, -150, -205, -167, -217, -204, -247, -225, -236, -214, -247,
+ -214, -207, -155, -197, -183, -192, -155, -160, -191, -236, -192, -219,
+ -188, -240, -195, -229, -141, -216, -140, -210, -108, -198, -110, -186,
+ -131, -164, -118, -161, -128, -132, -117, -113, -132, -130, -137, -165,
+ -120, -157, -110, -125, -107, -149, -159, -131, -114, -123, -178, -132,
+ -136, -75, -82, -32, -23, -71, -33, -49, -17, -94, -64, -87,
+ -53, -94, -80, -79, -100, -87, -113, -66, -94, -109, -102, -113,
+ -90, -94, -92, -105, -128, -124, -129, -89, -94, -67, -93, -83,
+ -95, -80, -94, -87, -89, -91, -53, -81, -31, -57, 18, -49,
+ -13, -37, 25, -7, 9, -13, -1, 53, 58, 59, 79, 109,
+ 133, 124, 84, 60},
+ /* IRC_Composite_C_R0195_T180_P030.wav */
+ {11, -12, 12, -12, 13, -13, 14, -14, 15, -15, 16, -17,
+ 18, -19, 20, -21, 23, -25, 28, -33, 39, -51, 85, 16,
+ -87, 129, -81, 94, 33, -96, -40, 382, 84, 940, 857, 674,
+ 1733, 496, -329, -2997, 2450, 4178, 3824, 5230, 3832, 1334, 27, 2852,
+ 1755, 841, -2811, -750, 709, 798, -492, -489, 295, 1105, 873, 238,
+ 383, 361, 52, -483, -293, -277, -430, -468, -128, -283, -308, -272,
+ -363, -267, -297, -189, -289, -143, -258, -242, -373, -291, -355, -331,
+ -315, -246, -385, -325, -276, -197, -314, -270, -313, -267, -313, -307,
+ -364, -269, -366, -341, -361, -328, -371, -363, -339, -357, -363, -329,
+ -335, -334, -353, -302, -358, -291, -419, -330, -381, -332, -384, -319,
+ -331, -309, -321, -276, -296, -256, -286, -256, -306, -268, -277, -214,
+ -200, -211, -199, -179, -152, -213, -196, -243, -196, -273, -196, -241,
+ -183, -226, -151, -195, -173, -198, -169, -171, -172, -166, -179, -179,
+ -198, -192, -224, -204, -227, -203, -207, -198, -181, -177, -142, -148,
+ -122, -136, -110, -124, -113, -120, -142, -140, -137, -157, -161, -213,
+ -116, -179, -81, -164, -112, -182, -121, -113, -114, -132, -189, -130,
+ -138, -73, -121, -47, -93, -7, -49, 37, -29, -7, -77, -47,
+ -80, -70, -85, -99, -107, -91, -49, -61, -81, -124, -69, -122,
+ -37, -160, -57, -192, -64, -193, -77, -181, -87, -158, -111, -159,
+ -105, -113, -89, -109, -88, -83, -83, -88, -75, -44, -73, -39,
+ -53, -1, -6, 52, 20, 54, 20, 68, 72, 99, 107, 107,
+ 128, 102, 141, 66},
+ /* IRC_Composite_C_R0195_T195_P030.wav */
+ {-2, 2, -2, 2, -2, 2, -2, 2, -2, 2, -2, 2,
+ -2, 2, -2, 1, -1, 1, -1, 0, 0, -1, 2, -3,
+ 4, -1, 20, -103, 88, -97, 77, -98, 0, -92, 123, 108,
+ 628, 541, 868, 946, 950, -323, -2426, 1960, 1868, 3655, 4432, 4145,
+ 1520, 425, 2257, 2243, 324, -1505, -101, 360, 566, -340, -97, 521,
+ 852, 598, 427, 177, 417, 31, 149, -102, -24, -395, -11, -98,
+ -149, -157, -72, -31, -41, -10, -27, -82, -183, -287, -120, -245,
+ -255, -309, -131, -235, -200, -246, -222, -253, -210, -246, -249, -280,
+ -281, -300, -278, -328, -316, -325, -352, -332, -302, -334, -406, -331,
+ -345, -314, -353, -298, -355, -316, -331, -341, -377, -390, -356, -375,
+ -325, -347, -301, -303, -263, -297, -274, -288, -262, -288, -280, -284,
+ -231, -214, -205, -233, -199, -221, -205, -231, -218, -253, -209, -218,
+ -203, -211, -199, -179, -191, -186, -199, -201, -193, -181, -187, -195,
+ -182, -193, -188, -207, -197, -194, -197, -186, -192, -163, -178, -160,
+ -165, -142, -165, -124, -155, -125, -159, -112, -160, -116, -190, -111,
+ -165, -107, -172, -122, -190, -155, -169, -127, -164, -144, -157, -122,
+ -135, -85, -113, -68, -116, -46, -67, -29, -54, -47, -65, -70,
+ -92, -108, -92, -118, -99, -92, -87, -77, -99, -78, -106, -86,
+ -118, -92, -123, -105, -136, -119, -126, -128, -143, -162, -171, -163,
+ -154, -125, -147, -120, -134, -91, -125, -91, -103, -88, -80, -77,
+ -45, -46, -3, 16, 24, 24, 27, 58, 42, 84, 85, 129,
+ 127, 141, 127, 125},
+ /* IRC_Composite_C_R0195_T210_P030.wav */
+ {-3, 3, -3, 3, -3, 3, -3, 3, -3, 3, -3, 3,
+ -3, 3, -3, 4, -4, 4, -4, 4, -4, 5, -5, 6,
+ -6, 7, -9, 12, -23, -83, -41, -51, -44, -54, -36, -72,
+ -62, 13, 62, 362, 515, 745, 618, 964, 123, -1474, 242, 1605,
+ 2935, 3779, 3927, 2051, 644, 1570, 1770, 938, -679, -243, 210, 595,
+ 104, 204, 284, 657, 698, 453, 338, 507, 437, 72, 227, 224,
+ 63, -55, 88, 141, 215, 131, 225, 97, 158, 60, 38, -69,
+ -107, -265, -85, -124, -202, -245, -131, -257, -191, -192, -178, -241,
+ -145, -269, -233, -270, -235, -338, -254, -338, -292, -359, -271, -388,
+ -290, -377, -344, -357, -293, -419, -370, -380, -349, -397, -346, -388,
+ -357, -344, -302, -335, -306, -279, -276, -246, -273, -284, -282, -237,
+ -264, -250, -226, -225, -248, -218, -230, -247, -240, -246, -259, -217,
+ -216, -201, -233, -193, -207, -183, -205, -202, -228, -190, -207, -213,
+ -222, -215, -237, -186, -220, -176, -235, -136, -217, -116, -201, -133,
+ -200, -133, -159, -165, -150, -164, -152, -134, -165, -144, -175, -119,
+ -164, -116, -139, -114, -152, -126, -132, -128, -147, -152, -173, -154,
+ -145, -152, -115, -135, -100, -99, -65, -75, -79, -71, -101, -104,
+ -126, -96, -135, -132, -139, -125, -80, -131, -108, -168, -90, -142,
+ -81, -142, -146, -133, -121, -109, -142, -113, -121, -117, -148, -144,
+ -150, -144, -150, -171, -140, -133, -106, -126, -118, -107, -114, -61,
+ -79, -59, -59, -5, -31, 13, -2, 37, 21, 52, 67, 76,
+ 116, 109, 131, 125},
+ /* IRC_Composite_C_R0195_T225_P030.wav */
+ {-1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1,
+ -1, 1, -1, 2, -2, 2, -2, 2, -2, 2, -2, 3,
+ -3, 3, -4, 5, -7, 14, -133, -89, -52, -68, -90, -53,
+ -98, -77, -65, -141, -33, -38, 144, 467, 407, 586, 730, 265,
+ -702, -561, 1147, 2053, 3022, 3625, 2128, 1090, 1205, 1750, 1123, 130,
+ -65, 370, 545, 406, 83, 446, 636, 787, 529, 661, 549, 526,
+ 605, 368, 384, 101, 455, 156, 304, 149, 290, 238, 156, 207,
+ 48, 113, -86, -57, -36, -176, -93, -129, -107, -209, -107, -181,
+ -195, -158, -169, -157, -239, -205, -240, -206, -253, -259, -282, -324,
+ -296, -377, -347, -379, -340, -400, -365, -377, -398, -363, -379, -335,
+ -398, -330, -337, -283, -328, -250, -335, -246, -279, -228, -301, -284,
+ -299, -253, -272, -251, -277, -231, -269, -213, -274, -207, -256, -219,
+ -248, -212, -205, -234, -191, -244, -197, -238, -194, -226, -213, -226,
+ -214, -216, -219, -201, -233, -193, -216, -184, -183, -195, -171, -198,
+ -136, -183, -121, -177, -126, -151, -131, -156, -138, -160, -140, -152,
+ -130, -143, -109, -146, -113, -168, -114, -165, -123, -191, -139, -186,
+ -143, -163, -101, -126, -91, -147, -80, -140, -85, -158, -107, -181,
+ -100, -149, -100, -152, -116, -152, -120, -144, -111, -159, -127, -155,
+ -129, -148, -146, -173, -157, -145, -134, -136, -128, -146, -120, -128,
+ -115, -125, -133, -149, -144, -153, -151, -147, -139, -128, -113, -110,
+ -89, -78, -59, -67, -27, -39, -8, -19, 9, 9, 34, 44,
+ 63, 65, 98, 113},
+ /* IRC_Composite_C_R0195_T240_P030.wav */
+ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 1, -1, 1, -1, 1, -1,
+ 1, -1, 1, -1, 1, -1, 1, -1, 1, 1, -97, -166,
+ -68, -82, -98, -145, 6, -178, -148, -79, -61, -4, 226, 289,
+ 466, 695, 150, -363, -564, 127, 1778, 2371, 2730, 2418, 1314, 1333,
+ 1958, 1573, 362, 208, 618, 691, 672, 358, 520, 767, 841, 691,
+ 635, 738, 675, 472, 476, 447, 387, 278, 358, 303, 322, 169,
+ 236, 146, 167, 153, 93, 161, -50, 82, -137, 22, -215, -48,
+ -220, -102, -183, -156, -150, -189, -134, -246, -183, -293, -242, -320,
+ -276, -333, -344, -324, -341, -336, -359, -336, -345, -340, -353, -365,
+ -331, -325, -327, -353, -302, -293, -288, -330, -303, -271, -296, -272,
+ -313, -253, -304, -231, -298, -228, -279, -211, -267, -230, -276, -219,
+ -263, -229, -267, -208, -254, -187, -265, -150, -261, -152, -276, -148,
+ -255, -165, -256, -184, -237, -198, -227, -197, -220, -180, -224, -157,
+ -210, -127, -200, -118, -181, -83, -171, -100, -162, -99, -139, -124,
+ -137, -122, -137, -107, -131, -113, -152, -146, -173, -133, -190, -141,
+ -182, -105, -142, -132, -151, -164, -146, -160, -134, -193, -153, -203,
+ -128, -188, -144, -178, -164, -173, -166, -129, -130, -161, -160, -179,
+ -136, -174, -125, -201, -162, -176, -100, -152, -132, -174, -152, -170,
+ -137, -167, -127, -157, -111, -151, -85, -120, -73, -114, -88, -97,
+ -72, -75, -82, -80, -55, -47, -53, -51, -32, -31, 18, -8,
+ 35, 22, 73, 65},
+ /* IRC_Composite_C_R0195_T255_P030.wav */
+ {1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1,
+ 1, -1, 1, -1, 1, -1, 1, -1, 2, -2, 2, -2,
+ 2, -2, 3, -3, 4, -4, 5, -6, 8, -12, 21, -67,
+ -186, -86, -78, -102, -151, -102, -59, -120, -188, -201, -77, -93,
+ -133, 190, 315, 388, 642, 206, -429, -627, -41, 1512, 2532, 2996,
+ 2313, 1394, 1764, 1723, 1247, 1241, 976, 437, 321, 813, 834, 517,
+ 405, 589, 1007, 744, 553, 490, 641, 609, 429, 381, 224, 460,
+ 347, 308, 191, 277, 332, 175, 143, 27, 143, -12, 2, -104,
+ -86, -53, -114, -79, -254, -137, -211, -109, -349, -252, -297, -195,
+ -348, -341, -330, -262, -295, -325, -316, -289, -220, -305, -292, -333,
+ -256, -294, -316, -394, -312, -314, -286, -387, -317, -323, -233, -326,
+ -302, -311, -250, -238, -292, -290, -296, -199, -259, -241, -292, -230,
+ -245, -240, -270, -264, -223, -199, -230, -230, -203, -176, -179, -217,
+ -226, -180, -203, -190, -239, -188, -230, -173, -226, -190, -237, -149,
+ -184, -187, -190, -158, -126, -139, -130, -127, -103, -111, -83, -113,
+ -124, -148, -136, -140, -136, -138, -118, -132, -127, -165, -140, -179,
+ -161, -224, -175, -209, -159, -182, -149, -200, -170, -196, -150, -218,
+ -152, -222, -177, -254, -168, -210, -186, -215, -137, -166, -128, -166,
+ -128, -184, -106, -195, -174, -233, -137, -162, -153, -164, -155, -113,
+ -146, -99, -141, -92, -113, -93, -86, -120, -80, -115, -69, -118,
+ -75, -88, -73, -78, -77, -48, -78, -41, -54, -26, -50, -11,
+ -21, 13, 14, 31},
+ /* IRC_Composite_C_R0195_T270_P030.wav */
+ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 1, -1, 2, -2, 4,
+ -6, 13, -118, -205, -234, -125, -147, -202, -210, -154, -149, -302,
+ -174, -138, 74, 139, 366, 494, 501, -30, -788, -660, 656, 2336,
+ 3548, 2933, 1466, 1698, 2087, 1596, 1331, 660, 745, 348, 609, 649,
+ 510, 592, 471, 837, 634, 881, 692, 658, 657, 499, 658, 390,
+ 588, 291, 406, 283, 323, 211, 84, 205, 87, 193, 12, 79,
+ -62, 28, -174, -101, -266, -102, -288, -156, -317, -186, -313, -245,
+ -324, -271, -272, -274, -260, -294, -238, -276, -248, -278, -258, -319,
+ -271, -317, -281, -337, -312, -324, -337, -313, -331, -296, -324, -287,
+ -323, -272, -287, -250, -300, -247, -285, -236, -292, -228, -244, -210,
+ -272, -214, -253, -227, -239, -213, -239, -229, -229, -214, -226, -214,
+ -215, -199, -227, -177, -203, -180, -193, -178, -164, -152, -146, -149,
+ -125, -151, -141, -143, -150, -155, -139, -130, -168, -120, -114, -108,
+ -126, -125, -154, -199, -182, -165, -181, -236, -178, -180, -150, -191,
+ -167, -205, -200, -201, -200, -203, -212, -213, -204, -217, -180, -215,
+ -180, -171, -178, -161, -169, -169, -194, -215, -198, -197, -181, -198,
+ -150, -180, -147, -175, -139, -160, -138, -133, -143, -113, -144, -115,
+ -133, -126, -110, -112, -106, -129, -97, -110, -96, -107, -98, -88,
+ -83, -69, -93, -79, -84, -51, -65, -56, -47, -41, -18, -38,
+ 4, -20, 19, 6},
+ /* IRC_Composite_C_R0195_T285_P030.wav */
+ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1,
+ 1, -1, 1, -1, 1, -1, 2, -2, 3, -3, 4, -7,
+ 13, -190, -183, -235, -156, -218, -208, -304, -188, -211, -199, -139,
+ -161, 153, 336, 233, -123, -164, -557, -685, 1147, 1908, 2035, 3160,
+ 1492, 785, 1242, 1667, 1506, 818, 1143, 817, 1178, 780, 1057, 657,
+ 807, 713, 683, 790, 563, 729, 595, 855, 599, 704, 615, 614,
+ 540, 520, 362, 304, 245, 337, 219, 250, 133, 197, 59, 126,
+ -65, -70, -160, -89, -159, -231, -254, -291, -203, -274, -185, -304,
+ -208, -285, -189, -264, -256, -331, -243, -311, -262, -308, -280, -304,
+ -246, -335, -250, -318, -269, -309, -282, -300, -260, -340, -268, -307,
+ -236, -284, -269, -325, -251, -300, -247, -310, -244, -277, -183, -239,
+ -198, -269, -229, -228, -237, -229, -245, -249, -264, -220, -228, -201,
+ -208, -179, -162, -96, -70, -76, -134, -147, -121, -145, -142, -175,
+ -192, -136, -108, -69, -153, -156, -210, -206, -236, -198, -320, -233,
+ -263, -173, -277, -125, -224, -144, -166, -131, -224, -164, -197, -216,
+ -260, -240, -244, -177, -196, -187, -157, -155, -171, -202, -195, -206,
+ -203, -187, -195, -236, -200, -188, -186, -176, -232, -177, -163, -133,
+ -189, -164, -179, -140, -153, -146, -152, -146, -135, -108, -114, -110,
+ -119, -88, -135, -96, -118, -89, -127, -122, -110, -95, -72, -108,
+ -98, -91, -59, -48, -71, -44, -52, -26, -30, -20, -36, -34,
+ -3, -13, 9, 16},
+ /* IRC_Composite_C_R0195_T300_P030.wav */
+ {1, -1, 2, -2, 2, -2, 2, -2, 2, -2, 2, -2,
+ 2, -2, 2, -3, 3, -3, 3, -3, 3, -4, 4, -4,
+ 5, -5, 5, -6, 7, -8, 9, -10, 12, -16, 27, -117,
+ -227, -314, -248, -219, -228, -232, -272, -389, -204, -111, 300, 56,
+ 366, 86, -1011, -298, 44, -215, 2477, 2714, 2032, 2711, 645, 499,
+ 536, 1654, 449, 374, 611, 764, 837, 821, 1350, 980, 1002, 808,
+ 1149, 967, 761, 841, 707, 633, 745, 777, 758, 579, 642, 512,
+ 570, 444, 429, 371, 457, 296, 276, 181, 191, 36, 17, 10,
+ -72, -108, -107, -232, -151, -205, -165, -300, -163, -265, -253, -269,
+ -203, -258, -301, -275, -269, -292, -272, -260, -302, -297, -289, -229,
+ -300, -264, -305, -243, -283, -264, -269, -279, -280, -287, -259, -269,
+ -276, -247, -263, -262, -247, -231, -256, -218, -280, -241, -295, -239,
+ -252, -218, -256, -230, -258, -241, -251, -148, -189, -159, -149, -45,
+ -91, -37, -119, -83, -194, -47, -124, -58, -134, -130, -231, -259,
+ -239, -276, -269, -285, -258, -248, -198, -188, -240, -206, -250, -202,
+ -249, -205, -247, -163, -226, -240, -217, -219, -189, -209, -212, -237,
+ -257, -142, -184, -180, -176, -167, -213, -202, -187, -188, -191, -235,
+ -229, -241, -179, -197, -185, -210, -199, -189, -160, -163, -174, -186,
+ -151, -189, -138, -133, -147, -130, -137, -107, -118, -101, -102, -120,
+ -113, -110, -78, -102, -64, -110, -74, -76, -59, -92, -76, -85,
+ -71, -79, -43, -88, -39, -85, -19, -81, 13, -60, 8, -26,
+ 31, 8, 16, 23},
+ /* IRC_Composite_C_R0195_T315_P030.wav */
+ {-3, 3, -3, 3, -3, 3, -3, 3, -3, 3, -3, 3,
+ -3, 3, -3, 3, -3, 3, -3, 3, -4, 4, -4, 4,
+ -4, 5, -5, 6, -7, 10, -15, 27, -80, -353, -110, -370,
+ -400, -195, -345, -309, -394, -97, -188, 193, 754, 56, -290, -194,
+ -1922, 214, 1653, 1159, 4327, 2754, 1527, 1212, 743, 317, 539, 1293,
+ -56, -21, 321, 1262, 295, 442, 708, 1213, 856, 813, 872, 1000,
+ 917, 1073, 730, 897, 944, 834, 864, 775, 610, 537, 692, 383,
+ 605, 370, 536, 245, 348, 155, 280, 101, 83, 31, 90, -127,
+ -63, -179, -119, -272, -116, -304, -232, -233, -212, -345, -176, -295,
+ -247, -334, -204, -342, -233, -195, -284, -324, -229, -236, -255, -304,
+ -258, -323, -258, -297, -224, -323, -252, -263, -224, -317, -252, -224,
+ -255, -281, -221, -218, -205, -288, -209, -253, -193, -244, -249, -201,
+ -218, -210, -200, -172, -180, -181, -84, -203, -75, -54, 19, -108,
+ -2, -46, -171, -209, -203, -294, -294, -286, -264, -295, -154, -187,
+ -222, -182, -252, -197, -249, -211, -305, -232, -289, -224, -284, -267,
+ -256, -184, -221, -260, -295, -281, -237, -233, -228, -269, -251, -203,
+ -162, -193, -192, -189, -166, -237, -188, -201, -208, -209, -181, -224,
+ -228, -151, -192, -200, -209, -131, -196, -159, -162, -159, -166, -116,
+ -160, -151, -131, -47, -142, -119, -122, -73, -93, -108, -96, -133,
+ -90, -84, -86, -83, -90, -56, -96, -65, -65, -48, -80, -67,
+ -79, -50, -60, -46, -63, -39, -44, -12, -27, 9, -35, 23,
+ -3, 24, 6, 41},
+ /* IRC_Composite_C_R0195_T330_P030.wav */
+ {-5, 5, -5, 5, -5, 5, -5, 5, -5, 6, -6, 6,
+ -6, 6, -6, 6, -6, 6, -7, 7, -7, 7, -8, 8,
+ -9, 9, -11, 14, -26, -447, -195, -107, -505, -457, -480, -118,
+ -514, -260, -345, 82, 416, 511, 1319, -1763, -994, 103, -1768, 3014,
+ 4385, 2231, 5234, 1274, 290, -49, 1679, 111, -604, 148, 524, 573,
+ -122, 973, 360, 1000, 586, 1035, 552, 578, 788, 885, 854, 618,
+ 749, 894, 854, 1021, 930, 968, 502, 1006, 765, 768, 438, 460,
+ 339, 388, 236, 299, 159, 177, -65, 162, -6, -74, -134, -51,
+ -230, -132, -193, -105, -327, -152, -257, -191, -291, -227, -315, -280,
+ -252, -192, -306, -253, -281, -182, -326, -252, -283, -225, -353, -255,
+ -282, -258, -322, -236, -271, -288, -246, -248, -299, -265, -254, -312,
+ -330, -129, -295, -141, -204, -129, -190, -22, -154, -203, -125, -172,
+ -156, -188, -111, -223, -112, -106, -241, -86, -37, -262, -238, -193,
+ -103, -105, -7, -250, -241, -39, -63, -184, -238, -261, -364, -243,
+ -263, -310, -361, -286, -407, -227, -301, -289, -311, -319, -263, -215,
+ -201, -304, -287, -339, -226, -246, -239, -335, -276, -276, -181, -234,
+ -181, -257, -180, -234, -151, -165, -203, -213, -194, -170, -205, -157,
+ -164, -191, -168, -154, -86, -133, -128, -162, -96, -147, -81, -165,
+ -68, -173, -90, -164, -64, -127, -68, -162, -97, -99, -50, -115,
+ -84, -111, -102, -87, -74, -80, -123, -76, -57, -95, -59, -42,
+ -45, -74, -38, -27, -9, -30, 0, -55, 36, -23, 58, -21,
+ 61, -7, 59, 29},
+ /* IRC_Composite_C_R0195_T345_P030.wav */
+ {0, 0, 0, 0, 0, 0, 0, 1, -1, 1, -1,
+ 2, -2, 2, -3, 3, -4, 5, -5, 6, -7, 8,
+ -9, 10, -10, -1, -563, -328, -175, -595, -424, -853, 130,
+ -981, 36, -843, 608, 167, 1104, 1422, -2010, -2282, 1073, -3818,
+ 6278, 4848, 2400, 7303, 1229, -443, -1130, 2494, 95, -616, -743,
+ 335, 108, 968, 417, 599, 684, 1073, 630, 810, 221, 839,
+ 469, 1136, 218, 739, 427, 945, 692, 942, 955, 757, 852,
+ 1008, 762, 614, 598, 745, 200, 403, 51, 357, 29, 333,
+ -147, -65, 9, 35, -114, -161, -231, -52, -197, -139, -202,
+ -61, -310, -211, -214, -165, -307, -207, -293, -261, -259, -204,
+ -201, -310, -323, -244, -250, -260, -335, -226, -421, -206, -333,
+ -191, -401, -188, -244, -264, -283, -225, -208, -338, -233, -69,
+ -304, -181, -213, -18, -265, 39, -68, -82, -113, -107, 16,
+ -329, -80, -324, -205, -399, -184, -328, -391, -87, -9, 36,
+ 1, 59, -85, 124, -60, -143, -307, -231, -406, -296, -456,
+ -311, -297, -269, -433, -350, -189, -283, -277, -303, -343, -325,
+ -287, -213, -328, -275, -279, -293, -296, -268, -217, -284, -286,
+ -294, -277, -236, -247, -201, -305, -195, -224, -135, -244, -185,
+ -128, -225, -126, -186, -74, -235, -93, -209, -95, -133, -90,
+ -131, -165, -80, -89, -69, -148, -104, -72, -157, -77, -134,
+ -88, -171, -67, -113, -124, -110, -76, -110, -130, -59, -96,
+ -88, -119, -61, -109, -81, -81, -67, -82, -54, -22, -49,
+ -19, -18, 2, -15, 25, 13, 24, 8, 45, 57, 4,
+ 20, 23, 60}};
+
+const int16_t irc_composite_c_r0195_p045[][256] =
+ {/* IRC_Composite_C_R0195_T000_P045.wav */
+ {-30, 30, -31, 32, -33, 34, -35, 36, -37, 38, -39,
+ 40, -41, 42, -43, 43, -44, 44, -42, 38, -28, -9,
+ -568, -169, -659, -127, -629, -131, -879, -529, -702, -207, -686,
+ -146, 757, 1500, 46, 1169, -4599, -1790, 163, -4363, 15423, 4884,
+ 3940, 951, 722, -172, 1158, 1933, -227, -750, 303, -613, 1137,
+ 856, 493, 193, 1077, 347, 1002, 300, 420, 882, 521, 444,
+ 512, 395, 442, 484, 453, 404, 610, 620, 839, 826, 758,
+ 649, 736, 767, 608, 484, 493, 147, 169, 427, 10, 204,
+ -311, 19, -245, 98, -241, 0, -167, 16, -127, -26, -105,
+ -158, -191, -79, -246, -206, -268, -177, -349, -216, -328, -216,
+ -366, -178, -332, -214, -308, -168, -352, -204, -312, -132, -301,
+ -215, -309, -152, -304, -202, -336, -200, -308, -229, -340, -205,
+ -331, -196, -326, -240, -274, -198, -310, -200, -321, -159, -282,
+ -190, -264, -176, -207, -74, -246, -157, -167, -77, -245, 18,
+ -155, -133, -274, -40, -289, -273, -267, -139, -520, -214, -265,
+ -252, -317, -46, -133, -17, 38, 121, -153, 151, 27, -13,
+ -114, 13, -297, -74, -188, -147, -332, -197, -268, -297, -199,
+ -221, -310, -309, -263, -206, -258, -237, -414, -155, -291, -200,
+ -367, -189, -287, -139, -241, -261, -247, -142, -190, -233, -239,
+ -158, -249, -110, -242, -173, -207, -82, -217, -136, -180, -136,
+ -164, -112, -177, -97, -134, -40, -164, -32, -115, -16, -91,
+ -12, -112, 4, -81, -19, -100, -20, -70, -21, -87, -6,
+ -59, 22, -68, -7, -66, 36, -20, 13, -47, -2, -27,
+ 28, -4, 13},
+ /* IRC_Composite_C_R0195_T015_P045.wav */
+ {16, -14, 12, -9, 7, -4, 1, 3, -7, 12, -17,
+ 22, -29, 36, -45, 56, -69, 85, -107, 143, -247, -403,
+ -616, -230, -517, -270, -663, -670, -583, -556, -570, -300, 850,
+ 1495, 298, 1490, -1579, -5393, 1137, -9580, 17573, 8715, 3526, 4288,
+ -490, -1083, 280, 3534, -555, -1021, -341, -114, 804, 1521, 139,
+ 355, 844, 673, 325, 735, -35, 1245, 91, 909, -18, 912,
+ 280, 465, 82, 792, 554, 703, 257, 849, 594, 952, 381,
+ 794, 169, 438, 96, 219, 18, 183, -220, -11, -303, 284,
+ -345, -44, -429, -44, -355, -58, -224, -155, -342, -30, -264,
+ -140, -360, -95, -355, -133, -396, -161, -354, -174, -312, -65,
+ -314, -194, -357, -142, -289, -170, -390, -213, -337, -152, -369,
+ -215, -364, -191, -332, -194, -330, -206, -298, -234, -318, -198,
+ -344, -225, -368, -198, -333, -240, -308, -215, -230, -223, -291,
+ -224, -258, -133, -220, -212, -260, -84, -207, -164, -294, -157,
+ -248, -263, -285, -134, -218, -267, -295, -223, -237, -192, -163,
+ -155, -68, 86, -74, -67, -51, -46, -49, -45, -144, -181,
+ -126, -51, -106, -136, -213, -189, -163, -189, -238, -230, -238,
+ -276, -288, -300, -274, -264, -268, -228, -288, -202, -201, -180,
+ -232, -217, -238, -174, -222, -220, -242, -187, -203, -190, -211,
+ -143, -223, -148, -187, -110, -202, -179, -175, -124, -140, -207,
+ -165, -148, -117, -138, -129, -91, -111, -52, -96, -31, -72,
+ -57, -38, -41, -39, -63, -12, -65, -2, -67, 12, -64,
+ 37, -48, 34, -49, 20, -4, 26, -16, 4, -9, 34,
+ -12, 34, -23},
+ /* IRC_Composite_C_R0195_T030_P045.wav */
+ {53, -54, 56, -58, 59, -62, 64, -67, 70, -73, 77,
+ -81, 87, -93, 100, -110, 122, -140, 176, -592, -266, -620,
+ -93, -807, 111, -1033, -244, -1451, 661, -1378, 1038, 309, 3279,
+ -1940, 4376, -7187, -1452, -4658, 38, 22380, 4913, 3629, 1106, -370,
+ -1337, 2958, 848, -390, -2691, 1295, -1155, 2599, -36, 956, 635,
+ 787, 53, 821, 200, 581, 67, 983, 274, 565, 247, 646,
+ 211, 751, 653, 273, 546, 759, 707, 776, 230, 704, 98,
+ 258, -440, 41, -44, -99, -570, -276, 125, -89, -316, -287,
+ -67, -256, -409, -175, -243, -300, -369, -371, -102, -291, -350,
+ -382, -109, -199, -343, -360, -210, -168, -316, -283, -194, -249,
+ -97, -252, -166, -374, -161, -245, -317, -297, -364, -202, -337,
+ -267, -324, -305, -165, -347, -151, -302, -171, -371, -213, -207,
+ -334, -383, -313, -191, -297, -398, -235, -273, -140, -391, -214,
+ -292, -119, -316, -254, -221, -194, -227, -232, -269, -229, -362,
+ -187, -364, -206, -335, -144, -247, -146, -237, -158, -174, -97,
+ -117, 50, -80, 66, -91, 178, -80, -68, -202, -96, -198,
+ -132, -223, -177, -186, -153, -217, -140, -249, -182, -298, -91,
+ -285, -230, -334, -153, -250, -218, -295, -154, -260, -140, -294,
+ -141, -233, -87, -273, -133, -222, -68, -214, -169, -225, -107,
+ -209, -114, -264, -92, -242, -49, -295, -99, -226, -66, -252,
+ -85, -193, -65, -189, -57, -171, -14, -172, -4, -179, 85,
+ -157, 3, -134, 92, -113, -4, -109, 69, -51, 13, -79,
+ 68, -27, 61, -58, 66, -43, 79, -23, 54, -24, 30,
+ -27, 66, -41},
+ /* IRC_Composite_C_R0195_T045_P045.wav */
+ {-71, 74, -76, 79, -82, 84, -87, 90, -93, 96, -99, 101,
+ -102, 102, -97, 85, -52, -97, -607, -227, -812, 438, -1024, -204,
+ -760, 95, -1636, 140, -766, 2279, -763, 3056, 150, 2120, -5760, -6480,
+ 1711, -14, 24602, 5276, 1036, -813, 1031, 1166, 848, 674, -478, -1487,
+ -945, 687, 1855, -292, 799, 1002, 786, 207, 559, 505, 363, 364,
+ 343, 530, 502, 11, 500, 785, 837, 217, 463, 907, 599, 564,
+ 357, 380, -197, 84, -367, 169, -530, -810, -282, -180, -171, -519,
+ -179, -442, -72, -160, -239, -494, -294, -88, -553, -268, -512, -278,
+ -503, -198, -473, -229, -317, -213, -370, -187, -331, -249, -344, -154,
+ -381, -135, -317, -147, -279, -176, -355, -133, -274, -305, -330, -235,
+ -314, -312, -303, -290, -257, -159, -269, -234, -243, -173, -350, -204,
+ -383, -239, -309, -233, -404, -183, -289, -321, -344, -212, -319, -251,
+ -253, -168, -303, -204, -277, -189, -284, -216, -333, -204, -348, -208,
+ -298, -166, -339, -189, -186, -273, -152, -87, -184, -166, 10, 48,
+ -40, 2, 0, -18, -97, -3, -134, -124, -166, -216, -161, -149,
+ -234, -162, -175, -239, -198, -150, -294, -241, -224, -190, -303, -189,
+ -150, -236, -232, -162, -159, -199, -136, -231, -166, -169, -136, -155,
+ -152, -125, -146, -133, -100, -123, -156, -121, -135, -145, -164, -174,
+ -157, -151, -189, -118, -198, -112, -121, -108, -138, -101, -93, -63,
+ -79, -82, -83, -26, -44, -58, -96, 20, -91, -15, -43, 22,
+ -69, 29, 5, 6, -19, 24, 3, 1, 25, 26, 26, 17,
+ 31, 13, 22, 22},
+ /* IRC_Composite_C_R0195_T060_P045.wav */
+ {59, -58, 56, -55, 52, -49, 45, -40, 34, -25, 14,
+ 1, -23, 55, -108, 214, -619, -300, -123, -537, -172, -393,
+ -415, -586, -104, -1306, 69, -335, 1375, 535, 2761, -686, 3824,
+ -8072, -1237, -7735, 11382, 19138, 4865, 2658, -2047, 1916, 571, 2806,
+ -624, -694, -1625, 70, 232, 950, -64, 1404, -414, 1196, 34,
+ 1403, -261, 1089, 343, 834, -188, 587, -251, 1086, 518, 827,
+ 135, 641, 494, 842, -31, 161, -360, -291, -317, 171, -783,
+ -241, -1031, 130, -617, -201, -704, -152, -418, -199, -454, -247,
+ -285, -342, -410, -378, -429, -306, -467, -327, -419, -184, -471,
+ -188, -474, -157, -446, -174, -469, -146, -371, -239, -326, -267,
+ -268, -265, -279, -218, -230, -216, -236, -148, -289, -212, -358,
+ -216, -330, -124, -259, -173, -298, -166, -287, -241, -319, -291,
+ -323, -261, -320, -293, -286, -298, -318, -332, -305, -269, -323,
+ -212, -266, -206, -301, -143, -304, -236, -268, -212, -344, -239,
+ -265, -225, -364, -157, -292, -187, -206, -129, -241, -186, -152,
+ -141, -106, -103, -15, -115, 56, -68, 26, -47, -92, -125,
+ -141, -154, -176, -196, -181, -250, -197, -136, -112, -240, -89,
+ -180, -145, -235, -146, -223, -248, -178, -229, -152, -219, -112,
+ -250, -98, -141, -152, -155, -116, -137, -150, -110, -137, -128,
+ -94, -113, -72, -189, -29, -176, -93, -125, -129, -123, -190,
+ -65, -169, -92, -169, -68, -159, -48, -109, -46, -109, -25,
+ -81, -31, -72, -27, -71, 7, -65, -16, -30, -30, -44,
+ 30, -39, 8, -56, 53, -36, 81, -51, 97, -64, 95,
+ -35, 60, -30},
+ /* IRC_Composite_C_R0195_T075_P045.wav */
+ {7, -7, 6, -6, 5, -5, 4, -3, 2, 0, -2,
+ 5, -9, 14, -23, 45, -729, -86, -463, -18, -765, 398,
+ -1336, 518, -1262, -130, -544, 1878, -781, 3861, -694, 2694, -2479,
+ -6040, -3168, 1816, 21907, 5475, 5383, -2274, 1082, 1645, 3196, -180,
+ -862, -1506, -288, -182, 870, -91, 1493, -388, 779, 238, 870,
+ 64, 614, 1007, 850, 122, 95, 413, 1397, 214, 480, 297,
+ 844, -36, 272, 27, -433, -791, -467, 60, -441, -446, -697,
+ -441, -417, -402, -239, -587, -268, -547, -343, -464, -296, -435,
+ -388, -347, -416, -430, -420, -403, -296, -406, -376, -408, -228,
+ -422, -330, -353, -273, -416, -325, -234, -301, -372, -327, -219,
+ -336, -296, -307, -215, -240, -202, -250, -161, -199, -207, -315,
+ -154, -247, -142, -267, -158, -266, -221, -313, -201, -303, -302,
+ -339, -264, -278, -318, -314, -301, -319, -312, -315, -204, -334,
+ -223, -340, -127, -300, -165, -343, -198, -307, -246, -285, -207,
+ -293, -254, -293, -183, -246, -202, -252, -100, -213, -78, -138,
+ -60, -197, -164, -160, -109, -141, -50, -142, 2, -155, 50,
+ -176, 0, -217, -67, -265, -39, -241, -180, -198, -196, -186,
+ -182, -111, -135, -134, -125, -176, -98, -184, -100, -212, -114,
+ -228, -113, -95, -140, -143, -141, -78, -133, -100, -67, -178,
+ -75, -161, -2, -319, 35, -236, -7, -269, 53, -223, -47,
+ -143, -20, -108, -80, -80, -54, -96, -67, -96, -24, -164,
+ -15, -108, -24, -125, -8, -47, -26, -57, 4, -8, -39,
+ -35, 32, -69, 30, -49, 58, -82, 89, -80, 107, -80,
+ 104, -56, 112},
+ /* IRC_Composite_C_R0195_T090_P045.wav */
+ {82, -84, 86, -88, 90, -91, 93, -94, 95, -95, 94,
+ -92, 87, -78, 61, -33, -240, -426, 56, -683, 113, -744,
+ 373, -1306, 284, -1194, 1644, -667, 2652, -291, 4245, -2423, -2014,
+ -4070, -4168, 13696, 10251, 10475, -3031, 1940, 668, 3850, 1809, -625,
+ -1492, -994, -456, 184, 563, 568, -96, 1027, 330, 539, 32,
+ 648, 1068, 571, 102, 392, 437, 1555, 535, 636, 312, 223,
+ -470, 382, -426, -676, -1277, -176, -342, -251, -706, -452, -477,
+ -459, -100, -511, -346, -481, -454, -509, -470, -397, -448, -337,
+ -587, -233, -611, -143, -563, -280, -521, -364, -455, -244, -466,
+ -275, -326, -321, -388, -219, -441, -210, -415, -297, -415, -244,
+ -390, -213, -373, -184, -325, -193, -152, -257, -243, -230, -170,
+ -254, -69, -214, -166, -228, -242, -227, -305, -281, -292, -232,
+ -343, -218, -305, -280, -254, -331, -243, -363, -249, -287, -245,
+ -320, -255, -262, -272, -215, -275, -191, -269, -200, -292, -200,
+ -287, -217, -267, -214, -217, -218, -189, -189, -148, -203, -120,
+ -154, -118, -165, -102, -132, -161, -134, -144, -132, -168, -94,
+ -134, -87, -100, -96, -67, -104, -58, -178, -114, -154, -134,
+ -226, -145, -224, -215, -130, -174, -117, -174, -71, -88, -63,
+ -109, -58, -78, -120, -50, -84, -79, -101, -63, -97, -110,
+ -65, -129, -123, -160, -83, -162, -109, -152, -92, -143, -76,
+ -108, -73, -110, -42, -74, -64, -99, -35, -69, -11, -79,
+ -57, -78, -51, -41, -69, -60, -68, -23, -64, 48, -114,
+ 27, -96, 75, -109, 79, -84, 99, -121, 109, -64, 69,
+ -52, 115, -98},
+ /* IRC_Composite_C_R0195_T105_P045.wav */
+ {28, -30, 31, -33, 35, -38, 40, -43, 47, -51, 55,
+ -60, 67, -75, 85, -99, 111, -72, -376, -88, -540, 307,
+ -524, -278, -931, 669, 115, 481, 924, 2601, 65, 2397, -5614,
+ -1324, -2647, 10799, 8614, 10099, 118, -2294, 4895, 2386, 2569, -875,
+ -924, -2723, 797, 264, 147, -195, 968, 521, 465, -376, 1032,
+ 858, 1253, -135, 495, 389, 792, 755, 975, 0, 305, -239,
+ -39, -629, -469, -1320, -587, -582, -54, -751, -376, -721, -115,
+ -425, -260, -335, -443, -566, -430, -612, -353, -563, -240, -666,
+ -197, -569, -261, -523, -199, -719, -213, -574, -181, -663, -112,
+ -511, -96, -492, -191, -414, -208, -419, -314, -403, -269, -455,
+ -207, -442, -146, -453, -78, -424, -85, -318, -146, -327, -204,
+ -254, -94, -214, -119, -280, -105, -318, -172, -355, -253, -350,
+ -232, -337, -213, -346, -174, -337, -196, -365, -187, -307, -214,
+ -347, -231, -304, -233, -293, -202, -273, -150, -270, -125, -278,
+ -116, -278, -165, -297, -205, -241, -198, -221, -169, -202, -83,
+ -240, -100, -225, -103, -190, -146, -171, -133, -169, -147, -152,
+ -62, -157, -83, -150, -63, -116, -82, -115, -86, -88, -91,
+ -126, -129, -161, -111, -196, -127, -178, -79, -197, -117, -103,
+ -73, -136, -26, -65, -11, -114, 16, -69, 41, -165, 27,
+ -83, -32, -83, -83, -89, -127, -138, -85, -115, -105, -151,
+ -76, -90, -77, -130, -45, -81, -85, -105, -60, -35, -80,
+ -62, -86, -27, -81, -18, -56, -46, -45, -2, 5, -61,
+ -31, -63, -14, -64, -36, 8, -66, 36, -43, 73, -62,
+ 61, -61, 80},
+ /* IRC_Composite_C_R0195_T120_P045.wav */
+ {-12, 12, -13, 13, -14, 14, -15, 16, -17, 18, -20,
+ 22, -25, 29, -34, 41, -54, 124, -315, 231, -915, 409,
+ -381, 76, -1011, 336, -106, 1161, -312, 2462, 834, 1649, -538,
+ -4939, -381, 2968, 10089, 6155, 8410, -2705, 920, 4167, 3787, 745,
+ -2352, -1802, -28, 292, -651, 645, 868, -434, 709, 424, 388,
+ 1070, 1285, -64, 322, 849, 520, 234, 673, 260, -252, -169,
+ -219, -404, -898, -522, -1000, -760, -659, -37, -489, -625, -480,
+ -117, -279, -318, -554, -444, -479, -476, -447, -397, -506, -426,
+ -374, -367, -517, -341, -428, -392, -509, -321, -429, -316, -424,
+ -291, -372, -230, -336, -266, -337, -314, -357, -245, -417, -326,
+ -393, -223, -400, -276, -346, -207, -347, -196, -289, -214, -324,
+ -195, -253, -149, -240, -179, -253, -165, -279, -187, -327, -224,
+ -367, -235, -338, -247, -316, -185, -311, -218, -272, -162, -301,
+ -187, -304, -196, -322, -148, -322, -157, -312, -104, -274, -142,
+ -240, -141, -246, -193, -235, -171, -192, -186, -214, -189, -212,
+ -181, -191, -123, -229, -156, -208, -159, -193, -117, -178, -146,
+ -179, -127, -164, -97, -166, -64, -139, -51, -149, -26, -150,
+ -13, -111, -43, -117, -85, -121, -102, -128, -113, -107, -143,
+ -80, -75, -82, -86, -55, -52, -89, -61, -20, -13, -63,
+ 41, -66, -14, -65, -25, -99, -66, -90, -83, -119, -73,
+ -60, -96, -94, -16, -118, -43, -110, -43, -94, -27, -153,
+ -76, -126, -21, -94, -45, -92, -51, -33, 3, -63, -11,
+ -52, -9, -73, 9, -41, -20, -40, 3, -44, -34, 0,
+ 0, -32, 16},
+ /* IRC_Composite_C_R0195_T135_P045.wav */
+ {15, -16, 17, -18, 19, -20, 21, -22, 23, -24, 25,
+ -27, 28, -29, 29, -28, 24, -11, -117, -145, -90, -121,
+ -109, -173, -39, -435, 83, -128, 1107, 344, 1609, 548, 3339,
+ -3005, -2346, -79, 2825, 6596, 9304, 4876, -1449, 2789, 1884, 4671,
+ 84, -2193, -1608, -211, -70, 580, 333, 233, 35, 298, -56,
+ 1401, 612, 725, 299, 829, 803, 425, -150, 290, -261, -139,
+ -438, -491, -740, -322, -692, -659, -1013, -400, -489, -130, -431,
+ -461, -332, -161, -302, -454, -443, -489, -403, -443, -472, -442,
+ -454, -357, -427, -305, -456, -382, -411, -333, -418, -365, -303,
+ -376, -334, -316, -292, -311, -265, -339, -352, -281, -323, -329,
+ -379, -310, -346, -316, -320, -334, -291, -274, -280, -297, -290,
+ -272, -270, -230, -233, -229, -239, -263, -224, -254, -270, -293,
+ -279, -264, -289, -263, -287, -260, -261, -232, -231, -207, -225,
+ -198, -204, -183, -223, -189, -221, -183, -247, -188, -250, -189,
+ -248, -190, -209, -183, -176, -163, -144, -170, -178, -173, -185,
+ -158, -195, -164, -234, -222, -222, -206, -165, -198, -160, -175,
+ -170, -167, -137, -130, -154, -126, -141, -104, -142, -91, -116,
+ -80, -71, -71, -88, -42, -93, -59, -74, -48, -70, -66,
+ -85, -79, -76, -90, -60, -94, -51, -65, -11, -46, -25,
+ -16, -13, -19, -24, -51, -74, -67, -91, -21, -96, -77,
+ -66, 2, -73, -36, -56, -44, -134, -42, -35, -70, -118,
+ -47, -68, -116, -72, -82, -75, -136, -24, -85, -36, -90,
+ 1, -62, -4, -20, -33, -14, 1, -17, -33, -28, -35,
+ -37, -9, -16},
+ /* IRC_Composite_C_R0195_T150_P045.wav */
+ {13, -14, 16, -17, 19, -20, 22, -25, 27, -30, 34, -38,
+ 42, -48, 55, -63, 75, -91, 119, -208, 14, -179, 43, -143,
+ 14, -237, 127, -445, 347, 318, 882, 1092, 671, 2337, -2, -2406,
+ -2033, 3403, 2686, 9080, 6065, 1654, -385, 2378, 3537, 1656, -964, -2514,
+ -65, -61, 604, 313, -548, 8, 506, 751, 592, 750, 799, 256,
+ 788, 738, 691, -486, -503, -169, -52, -560, -771, -513, -275, -418,
+ -623, -527, -620, -550, -389, -73, -175, -357, -452, -335, -134, -523,
+ -381, -574, -280, -554, -346, -501, -295, -395, -265, -376, -302, -406,
+ -287, -380, -314, -400, -273, -369, -283, -407, -233, -368, -270, -408,
+ -280, -353, -282, -379, -324, -362, -292, -349, -288, -317, -293, -299,
+ -273, -329, -303, -342, -223, -328, -247, -359, -217, -332, -232, -362,
+ -243, -293, -224, -266, -223, -255, -188, -248, -204, -263, -170, -234,
+ -148, -228, -150, -221, -123, -200, -152, -223, -169, -222, -177, -244,
+ -174, -210, -124, -206, -129, -206, -127, -177, -96, -151, -166, -237,
+ -192, -200, -169, -204, -186, -233, -213, -217, -169, -178, -153, -156,
+ -130, -156, -129, -136, -88, -131, -105, -128, -101, -129, -90, -83,
+ -84, -106, -52, -43, -49, -65, -44, -66, -38, -70, 26, -76,
+ 6, -88, -13, -82, -34, -41, -4, -34, -1, -14, 9, -50,
+ -23, -69, -16, -101, -10, -110, -17, -119, -37, -74, -19, -102,
+ -24, -41, -56, -111, -39, -111, -124, -147, -53, -65, -109, -46,
+ -41, -68, -52, -7, -29, -64, -43, -18, -36, -49, -47, 1,
+ -26, -14, -36, -17},
+ /* IRC_Composite_C_R0195_T165_P045.wav */
+ {-5, 6, -6, 7, -7, 8, -9, 9, -10, 11, -13, 14,
+ -16, 18, -20, 23, -27, 31, -37, 45, -57, 83, -131, 98,
+ -140, 102, -154, 88, -172, 82, 24, 745, 704, 1159, 942, 1377,
+ 685, -3350, -75, 2310, 5071, 6483, 6504, -342, 276, 2310, 3436, 1307,
+ -1968, -1879, -504, 1149, 580, -184, -486, 250, 728, 683, 585, 651,
+ 265, 901, 267, 694, -240, -105, -607, -269, -676, -500, -479, -378,
+ -278, -303, -345, -367, -327, -471, -422, -217, -111, -233, -480, -413,
+ -399, -332, -479, -354, -471, -387, -344, -260, -374, -267, -230, -298,
+ -347, -353, -301, -344, -328, -357, -343, -372, -361, -346, -368, -366,
+ -338, -358, -344, -365, -318, -353, -318, -336, -351, -319, -335, -289,
+ -334, -309, -321, -326, -336, -348, -335, -354, -321, -327, -304, -329,
+ -269, -270, -235, -245, -249, -208, -236, -185, -237, -214, -241, -196,
+ -206, -169, -200, -166, -161, -150, -185, -187, -205, -193, -210, -166,
+ -170, -138, -171, -155, -201, -170, -152, -109, -129, -189, -178, -185,
+ -130, -167, -150, -206, -233, -222, -196, -166, -206, -190, -201, -159,
+ -173, -138, -175, -147, -160, -114, -124, -105, -119, -77, -100, -117,
+ -136, -90, -111, -91, -103, -87, -79, -69, -28, -22, -44, -41,
+ -25, -6, -27, -13, -21, -10, -27, 9, 1, -13, 14, -2,
+ 20, 6, -17, -7, -23, -45, -82, -88, -89, -86, -91, -67,
+ -101, -82, -96, -74, -71, -81, -64, -98, -60, -94, -66, -105,
+ -103, -119, -65, -33, -28, -21, -24, -24, -66, -22, -25, -29,
+ -63, -45, -61, -77},
+ /* IRC_Composite_C_R0195_T180_P045.wav */
+ {1, -1, 1, -1, 2, -2, 2, -2, 2, -2, 2, -2,
+ 3, -3, 3, -3, 3, -4, 4, -4, 4, -4, 3, -2,
+ 9, -9, 70, -59, 121, -126, 127, -45, 222, 619, 653, 1289,
+ 587, 1432, 300, -1888, -1240, 3381, 3947, 6141, 5035, 479, -148, 2113,
+ 2998, 773, -1353, -1664, 690, 1041, -58, -664, -159, 711, 746, 445,
+ 284, 522, 787, 289, 291, 216, 96, -622, -296, -616, -684, -562,
+ -153, -193, -156, -160, -230, -111, -119, -291, -584, -298, -144, -240,
+ -398, -579, -379, -509, -208, -402, -311, -292, -227, -267, -259, -266,
+ -213, -263, -327, -375, -294, -371, -362, -434, -364, -436, -381, -431,
+ -375, -404, -373, -398, -329, -365, -326, -409, -345, -385, -333, -367,
+ -288, -357, -297, -388, -309, -398, -314, -430, -330, -408, -271, -349,
+ -243, -310, -230, -234, -197, -214, -201, -187, -203, -238, -232, -238,
+ -212, -225, -205, -211, -176, -179, -201, -211, -228, -202, -224, -150,
+ -202, -129, -214, -123, -184, -94, -133, -120, -193, -166, -163, -113,
+ -144, -135, -197, -168, -225, -183, -213, -177, -199, -157, -207, -155,
+ -195, -92, -173, -121, -200, -130, -155, -103, -148, -111, -166, -85,
+ -143, -56, -147, -56, -154, -59, -141, -36, -120, -42, -95, -13,
+ -67, 16, -28, 44, -44, 24, -33, 43, -27, 82, -21, 103,
+ -17, 122, -2, 81, -69, 27, -35, -19, -94, -112, -111, -109,
+ -91, -94, -119, -134, -85, -80, -83, -129, -79, -97, -58, -112,
+ -41, -119, -24, -104, -33, -99, -41, -79, -41, -68, -44, -36,
+ -44, -71, -84, -70},
+ /* IRC_Composite_C_R0195_T195_P045.wav */
+ {-10, 11, -11, 11, -12, 12, -12, 13, -13, 14, -14, 15,
+ -15, 16, -17, 17, -18, 19, -20, 21, -22, 23, -24, 26,
+ -28, 29, -70, 1, 21, -50, -12, 7, -98, 118, -221, 432,
+ 295, 945, 530, 962, 1220, -497, -1827, 293, 2669, 3717, 6133, 2421,
+ 667, 647, 2517, 2286, 876, -1019, -811, 715, 567, 146, -552, 68,
+ 761, 748, 406, 192, 794, 584, 428, 293, 114, -104, -113, -153,
+ -376, -408, -234, -64, -66, -15, 75, -43, -32, -84, -58, -289,
+ -255, -381, -227, -301, -267, -362, -351, -257, -250, -265, -197, -240,
+ -267, -205, -195, -288, -234, -313, -331, -391, -314, -376, -403, -414,
+ -376, -388, -386, -415, -381, -386, -367, -392, -359, -382, -358, -395,
+ -338, -337, -339, -329, -314, -348, -381, -383, -383, -385, -396, -353,
+ -328, -277, -291, -244, -249, -220, -237, -207, -212, -209, -222, -227,
+ -219, -186, -196, -201, -196, -190, -194, -210, -219, -243, -209, -201,
+ -163, -195, -186, -186, -145, -123, -130, -154, -175, -167, -170, -122,
+ -126, -124, -185, -179, -196, -160, -179, -161, -199, -183, -176, -134,
+ -131, -147, -153, -166, -151, -161, -167, -166, -166, -160, -162, -139,
+ -166, -135, -153, -143, -142, -125, -105, -102, -94, -90, -78, -72,
+ -57, -50, -51, -21, -44, -9, -31, -16, -46, -3, -13, 24,
+ 10, 27, 32, 25, 66, -13, -17, -41, -15, -51, -100, -128,
+ -126, -123, -107, -116, -127, -132, -96, -99, -88, -127, -87, -121,
+ -70, -130, -87, -96, -70, -95, -94, -64, -80, -73, -64, -54,
+ -55, -52, -44, -57},
+ /* IRC_Composite_C_R0195_T210_P045.wav */
+ {-1, 1, -1, 1, -1, 1, -1, 1, -2, 2, -2, 2,
+ -2, 2, -2, 3, -3, 3, -3, 4, -4, 4, -5, 5,
+ -6, 6, -7, 9, -41, -44, -65, -30, -65, -8, -83, -60,
+ -92, 30, 110, 507, 445, 762, 568, 1174, -1167, -1019, 628, 2221,
+ 3797, 4340, 2039, 369, 1655, 2128, 2575, -74, -532, -44, 828, 327,
+ -261, -60, 394, 940, 522, 463, 465, 701, 615, 239, 186, 177,
+ 226, 24, 55, -107, -121, -144, 122, 80, 101, 60, 168, 75,
+ 69, -42, -103, -208, -202, -275, -257, -213, -175, -274, -214, -201,
+ -239, -203, -186, -264, -269, -249, -233, -294, -292, -329, -314, -341,
+ -364, -402, -341, -398, -395, -448, -334, -429, -332, -431, -329, -423,
+ -331, -395, -337, -340, -332, -347, -337, -368, -357, -389, -353, -376,
+ -312, -331, -265, -307, -238, -251, -202, -250, -204, -228, -195, -243,
+ -216, -235, -186, -234, -184, -216, -198, -218, -214, -243, -226, -238,
+ -184, -210, -192, -215, -154, -146, -144, -168, -181, -177, -171, -140,
+ -114, -169, -137, -193, -142, -178, -151, -186, -152, -183, -144, -144,
+ -116, -143, -141, -149, -130, -174, -129, -185, -122, -193, -143, -180,
+ -134, -169, -148, -166, -136, -141, -122, -130, -103, -114, -106, -102,
+ -72, -91, -60, -67, -53, -70, -51, -40, -61, -20, -57, 16,
+ -56, 22, -58, 21, -49, -10, -48, 3, -48, -36, -55, -46,
+ -67, -83, -96, -101, -127, -82, -139, -122, -145, -101, -121, -109,
+ -139, -130, -124, -118, -116, -120, -95, -97, -71, -73, -66, -58,
+ -48, -24, -31, -11},
+ /* IRC_Composite_C_R0195_T225_P045.wav */
+ {-3, 3, -3, 3, -3, 3, -3, 4, -4, 4, -4, 4,
+ -4, 4, -4, 5, -5, 5, -5, 5, -6, 6, -6, 7,
+ -7, 8, -9, 10, -13, 20, -75, -97, -66, -47, -85, -74,
+ -101, -34, -123, -132, -22, 222, 292, 490, 466, 903, 275, -1049,
+ -389, 584, 2358, 3669, 3152, 1258, 1261, 1738, 2331, 1624, 37, -28,
+ 186, 674, 40, 219, 313, 646, 713, 656, 528, 603, 598, 473,
+ 348, 444, 144, 373, 151, 223, 11, 95, 112, 186, 158, 153,
+ 282, 115, 221, 30, 18, -91, -32, -200, -106, -202, -164, -212,
+ -141, -234, -274, -248, -205, -234, -252, -279, -220, -289, -259, -337,
+ -286, -358, -337, -398, -361, -383, -377, -397, -403, -387, -416, -383,
+ -391, -375, -383, -384, -351, -350, -337, -371, -325, -339, -314, -362,
+ -307, -327, -273, -302, -260, -293, -265, -270, -253, -272, -249, -242,
+ -222, -243, -203, -221, -193, -215, -188, -249, -219, -244, -222, -251,
+ -227, -225, -219, -214, -206, -186, -149, -158, -170, -204, -178, -182,
+ -146, -152, -156, -169, -175, -163, -157, -142, -159, -167, -156, -155,
+ -165, -170, -148, -148, -131, -118, -114, -117, -133, -119, -145, -148,
+ -151, -147, -145, -167, -157, -166, -139, -171, -158, -145, -135, -121,
+ -133, -97, -119, -63, -84, -67, -101, -69, -67, -57, -75, -59,
+ -62, -49, -63, -41, -77, -33, -68, -14, -83, -22, -64, -11,
+ -77, -48, -83, -73, -129, -98, -117, -128, -152, -116, -144, -123,
+ -154, -107, -170, -138, -138, -97, -115, -107, -82, -89, -65, -72,
+ -83, -58, -72, -30},
+ /* IRC_Composite_C_R0195_T240_P045.wav */
+ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ -1, 1, -1, 1, -1, 2, -5, -83, -84, -72, -135, -73,
+ -91, -109, -87, -133, -84, -173, -91, -36, 199, 307, 354, 685,
+ 469, -284, -594, -516, 1131, 3046, 2851, 2215, 1571, 1578, 1884, 1712,
+ 836, 241, 420, 708, 320, 266, 499, 693, 588, 631, 732, 645,
+ 553, 384, 505, 486, 415, 335, 428, 309, 250, 327, 166, 248,
+ 139, 364, 147, 249, 184, 201, 90, 89, -4, -84, -57, -140,
+ -149, -268, -170, -229, -181, -241, -235, -260, -225, -272, -259, -355,
+ -270, -344, -221, -429, -254, -394, -305, -443, -348, -406, -390, -405,
+ -383, -420, -382, -385, -345, -366, -308, -315, -295, -317, -301, -307,
+ -307, -319, -316, -337, -273, -310, -260, -332, -260, -287, -238, -267,
+ -235, -236, -259, -223, -218, -231, -226, -221, -205, -245, -216, -236,
+ -203, -244, -208, -224, -221, -211, -210, -180, -186, -192, -188, -204,
+ -183, -150, -161, -144, -182, -151, -199, -152, -176, -159, -170, -168,
+ -142, -147, -118, -110, -117, -97, -125, -84, -116, -88, -125, -124,
+ -122, -160, -117, -177, -157, -199, -179, -171, -156, -148, -174, -137,
+ -139, -117, -130, -135, -112, -124, -104, -131, -80, -123, -82, -99,
+ -80, -101, -46, -83, -75, -90, -80, -63, -59, -51, -122, -65,
+ -82, -66, -92, -66, -83, -92, -88, -117, -122, -158, -126, -150,
+ -152, -142, -139, -83, -139, -101, -146, -84, -121, -95, -110, -91,
+ -88, -79, -72, -47},
+ /* IRC_Composite_C_R0195_T255_P045.wav */
+ {-1, 1, -1, 1, -1, 1, -1, 1, -2, 2, -2, 2,
+ -2, 2, -2, 2, -3, 3, -3, 3, -4, 4, -4, 5,
+ -5, 6, -6, 7, -8, 10, -12, 15, -21, 35, -138, -127,
+ -91, -119, -131, -100, -136, -154, -123, -162, -225, -17, 208, 157,
+ 422, 539, 315, -186, -874, -513, 1317, 2834, 3212, 2035, 1165, 1380,
+ 1985, 1825, 760, 470, 698, 708, 299, 840, 390, 551, 566, 771,
+ 555, 435, 745, 529, 606, 365, 682, 399, 459, 406, 443, 291,
+ 358, 302, 320, 305, 286, 249, 233, 146, 69, 90, 0, -26,
+ -85, -106, -54, -197, -179, -210, -226, -306, -252, -328, -293, -273,
+ -273, -342, -265, -302, -315, -353, -309, -387, -325, -457, -297, -442,
+ -355, -408, -318, -407, -303, -346, -296, -308, -269, -329, -219, -337,
+ -263, -338, -282, -364, -295, -326, -281, -306, -274, -302, -265, -294,
+ -241, -286, -262, -250, -230, -264, -201, -225, -215, -219, -198, -213,
+ -212, -205, -209, -201, -251, -202, -242, -212, -196, -186, -200, -172,
+ -211, -195, -206, -149, -217, -164, -212, -147, -189, -117, -143, -88,
+ -129, -55, -121, -125, -117, -90, -114, -92, -138, -75, -136, -123,
+ -181, -119, -197, -132, -134, -131, -172, -162, -136, -187, -159, -156,
+ -181, -172, -159, -163, -167, -116, -199, -172, -135, -102, -126, -131,
+ -78, -101, -129, -76, -94, -120, -143, -86, -127, -74, -98, -46,
+ -113, -79, -97, -44, -125, -131, -113, -86, -115, -124, -120, -129,
+ -130, -91, -134, -118, -138, -78, -131, -110, -118, -103, -103, -97,
+ -94, -71, -62, -45},
+ /* IRC_Composite_C_R0195_T270_P045.wav */
+ {-2, 2, -2, 2, -2, 2, -2, 2, -2, 2, -2, 2,
+ -2, 2, -2, 2, -2, 2, -2, 2, -2, 2, -2, 2,
+ -2, 2, -2, 3, -3, 4, -4, 6, -9, 16, -64, -161,
+ -143, -126, -145, -185, -145, -161, -193, -254, -120, -230, -52, 146,
+ 316, 329, 329, 253, -502, -967, -275, 2022, 3347, 2710, 947, 1504,
+ 1884, 1375, 1901, 809, 618, 609, 671, 737, 376, 610, 640, 652,
+ 766, 573, 669, 526, 783, 441, 547, 504, 550, 598, 531, 667,
+ 314, 551, 275, 554, 115, 507, 96, 221, 103, 170, 93, 44,
+ 90, -71, 5, -109, -137, -246, -250, -241, -295, -289, -315, -295,
+ -325, -258, -316, -295, -327, -300, -348, -332, -372, -354, -355, -374,
+ -324, -355, -324, -341, -316, -283, -312, -268, -304, -257, -336, -239,
+ -313, -303, -322, -321, -295, -351, -234, -324, -242, -342, -262, -303,
+ -265, -288, -272, -254, -289, -193, -250, -195, -234, -188, -219, -212,
+ -194, -217, -186, -231, -179, -233, -211, -209, -215, -227, -234, -228,
+ -223, -193, -202, -196, -175, -156, -144, -145, -108, -127, -113, -111,
+ -50, -105, -58, -119, -95, -117, -140, -166, -165, -130, -150, -114,
+ -120, -117, -146, -129, -172, -175, -166, -189, -168, -160, -197, -193,
+ -208, -220, -186, -186, -158, -220, -160, -145, -123, -177, -155, -188,
+ -132, -141, -109, -129, -71, -127, -88, -107, -92, -141, -124, -132,
+ -136, -104, -99, -93, -109, -90, -81, -97, -101, -102, -111, -126,
+ -107, -115, -121, -123, -88, -123, -117, -104, -75, -79, -90, -92,
+ -70, -77, -68, -59},
+ /* IRC_Composite_C_R0195_T285_P045.wav */
+ {5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5,
+ 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5,
+ 5, -5, 6, -6, 6, -7, 8, -12, 29, 101, 50, 50,
+ 117, 188, 58, 104, 258, 239, 166, 306, 686, 455, 833, 834,
+ 700, 494, 424, 49, 743, 2627, 3265, 3156, 1306, -112, 1114, 559,
+ 356, 246, 113, -51, -113, 695, 8, 344, 126, 384, -46, 304,
+ 134, 58, 125, -42, 60, -117, 150, -115, 60, -66, -74, -290,
+ -174, -222, -336, -410, -393, -454, -500, -436, -457, -579, -566, -638,
+ -618, -691, -652, -777, -695, -736, -701, -743, -633, -655, -650, -600,
+ -573, -602, -585, -524, -544, -563, -496, -527, -483, -513, -414, -513,
+ -387, -442, -351, -441, -335, -395, -360, -389, -346, -380, -350, -337,
+ -339, -350, -286, -303, -303, -289, -278, -317, -289, -279, -256, -274,
+ -224, -223, -201, -206, -165, -204, -151, -205, -147, -195, -148, -200,
+ -177, -178, -170, -179, -162, -156, -149, -146, -135, -109, -105, -142,
+ -94, -76, -93, -61, -56, -26, -52, -21, -71, -105, -113, -130,
+ -140, -154, -74, -110, -107, -129, -114, -156, -108, -147, -143, -197,
+ -121, -103, -164, -177, -160, -133, -135, -137, -138, -104, -105, -75,
+ -105, -83, -126, -87, -67, -44, -97, -18, -59, -13, -45, -23,
+ -53, -56, -43, -12, -14, -6, -40, 1, -18, 27, -19, 35,
+ -53, 6, -9, 7, 11, 2, -8, -3, 22, 16, -2, 32,
+ 44, 18, -5, 28, 17, 14, 19, 9, 27, 26, 56, 44,
+ 54, 64, 65, 88},
+ /* IRC_Composite_C_R0195_T300_P045.wav */
+ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 1, -2, 6, -170, -102, -259, -234,
+ -199, -268, -51, -528, -137, -354, -180, 92, -180, 340, 604, 11,
+ -371, -583, -984, 10, 2699, 3142, 3080, 1412, 327, 1987, 956, 830,
+ 764, 347, 196, 382, 1091, 294, 859, 889, 945, 523, 1199, 597,
+ 582, 854, 625, 694, 469, 918, 446, 675, 763, 646, 482, 552,
+ 534, 540, 563, 394, 300, 434, 396, 321, 144, 359, -94, 142,
+ -43, 165, -138, 6, -183, -179, -221, -161, -328, -285, -267, -402,
+ -333, -327, -294, -327, -363, -296, -325, -259, -291, -320, -304, -304,
+ -298, -291, -297, -314, -280, -329, -297, -314, -281, -234, -352, -241,
+ -283, -217, -315, -280, -263, -279, -310, -268, -299, -296, -261, -277,
+ -280, -277, -225, -216, -261, -203, -214, -192, -239, -227, -233, -213,
+ -266, -208, -195, -200, -197, -191, -188, -186, -205, -150, -205, -153,
+ -112, -151, -87, -35, -120, -124, -179, -137, -157, -174, -117, -144,
+ -171, -153, -152, -138, -131, -174, -135, -128, -150, -114, -214, -174,
+ -206, -228, -215, -210, -194, -196, -208, -219, -189, -187, -156, -164,
+ -222, -168, -262, -148, -197, -176, -237, -214, -212, -100, -129, -146,
+ -213, -165, -123, -124, -133, -162, -159, -151, -116, -118, -122, -155,
+ -138, -137, -123, -99, -108, -104, -101, -84, -78, -75, -64, -83,
+ -84, -92, -41, -94, -60, -107, -53, -86, -49, -66, -60, -68,
+ -31, -65, -31, -29},
+ /* IRC_Composite_C_R0195_T315_P045.wav */
+ {-1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1,
+ -1, 1, -1, 1, -1, 1, -1, 1, -1, 1, -1, 1,
+ -1, 2, -2, 4, -7, 17, -100, -204, -340, -371, -160, -344,
+ -275, -415, -419, -249, 41, -182, 77, 891, 131, 2, -1052, -1535,
+ 924, 162, 4003, 4715, 1544, 1179, 621, 1424, 674, 1203, -197, 55,
+ 273, 545, 402, 710, 753, 673, 897, 715, 647, 832, 758, 686,
+ 732, 814, 620, 688, 779, 690, 604, 649, 586, 620, 586, 617,
+ 478, 512, 430, 467, 385, 389, 275, 162, 250, 131, 159, 97,
+ 100, -42, -92, -107, -156, -193, -270, -280, -274, -295, -312, -336,
+ -301, -367, -310, -306, -332, -327, -275, -299, -308, -302, -264, -320,
+ -240, -345, -250, -325, -245, -353, -231, -309, -234, -308, -240, -335,
+ -236, -284, -266, -330, -242, -316, -259, -281, -275, -309, -222, -254,
+ -279, -277, -236, -254, -242, -235, -247, -199, -188, -240, -203, -141,
+ -139, -214, -131, -200, -170, -111, -70, -193, -145, -148, -101, -173,
+ -117, -218, -163, -214, -197, -208, -111, -206, -113, -191, -104, -100,
+ -70, -142, -89, -169, -106, -184, -156, -187, -185, -255, -195, -151,
+ -192, -223, -180, -149, -215, -245, -216, -248, -232, -230, -274, -292,
+ -203, -223, -171, -218, -208, -215, -141, -139, -194, -204, -163, -184,
+ -155, -159, -154, -164, -134, -151, -130, -134, -130, -152, -122, -129,
+ -133, -120, -106, -118, -102, -72, -84, -69, -84, -65, -68, -55,
+ -55, -56, -72, -45, -57, -43, -50, -64, -34, -44, -40, -36,
+ -22, -14, -32, 2},
+ /* IRC_Composite_C_R0195_T330_P045.wav */
+ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 1, -1, 1, -1, 1, -1, 1, -2, 2, -2,
+ 3, -4, 5, -8, -246, -348, -392, -294, -392, -112, -819, -204,
+ -558, 205, -381, 275, 768, 476, 319, -1186, -2693, 1397, 173, 2904,
+ 7126, 2191, 1840, -141, 1440, 643, 1148, 280, -721, 280, 227, 474,
+ 413, 704, 690, 753, 889, 309, 920, 476, 802, 503, 682, 628,
+ 623, 869, 602, 783, 626, 695, 707, 691, 850, 635, 757, 447,
+ 558, 418, 394, 227, 133, 146, 217, 152, 239, 143, 20, 23,
+ 13, -113, -130, -110, -235, -231, -231, -223, -290, -311, -279, -339,
+ -297, -335, -258, -358, -326, -332, -275, -283, -293, -287, -240, -279,
+ -296, -244, -248, -261, -257, -280, -251, -271, -271, -291, -272, -276,
+ -292, -293, -287, -273, -297, -261, -259, -291, -282, -238, -254, -285,
+ -269, -242, -239, -234, -237, -211, -232, -160, -167, -153, -54, -121,
+ -133, -68, -127, -55, -93, -170, -253, -307, -209, -255, -315, -307,
+ -329, -320, -102, -172, -106, -41, -45, 45, -125, 12, -25, -98,
+ -177, -185, -220, -166, -179, -213, -164, -210, -152, -240, -217, -183,
+ -300, -182, -230, -224, -316, -238, -276, -217, -287, -209, -290, -207,
+ -191, -176, -237, -198, -251, -191, -184, -201, -245, -147, -177, -164,
+ -190, -112, -183, -108, -171, -100, -163, -85, -153, -101, -137, -74,
+ -146, -57, -120, -58, -110, -43, -90, -50, -94, -38, -79, -64,
+ -64, -54, -43, -59, -47, -30, -23, -30, -38, -10, -18, 12,
+ -13, 10, -24, 5},
+ /* IRC_Composite_C_R0195_T345_P045.wav */
+ {16, -16, 16, -16, 16, -16, 16, -16, 16, -15, 15, -14,
+ 14, -13, 11, -9, 7, -4, 0, 5, -13, 25, -45, 84,
+ -233, -338, -363, -485, -200, -447, -145, -898, -330, -612, 95, -690,
+ 294, 625, 1287, -151, 69, -4722, 1521, -1345, 1556, 10396, 3052, 2742,
+ 68, 1282, 287, 1583, 813, -614, -309, 335, 28, 768, 552, 558,
+ 584, 902, 480, 734, 438, 738, 500, 576, 491, 546, 493, 681,
+ 435, 611, 561, 752, 683, 864, 812, 738, 819, 537, 703, 581,
+ 367, 218, 0, 254, 20, 344, -127, 37, -166, 211, -28, -62,
+ -54, -160, -27, -117, -166, -287, -249, -103, -237, -262, -426, -232,
+ -222, -305, -355, -320, -241, -298, -264, -311, -247, -264, -248, -253,
+ -288, -211, -240, -212, -321, -248, -261, -248, -306, -335, -270, -295,
+ -235, -360, -279, -274, -198, -287, -320, -253, -257, -194, -270, -236,
+ -243, -201, -255, -169, -230, -203, -38, -204, -189, 64, -47, -32,
+ -96, -24, -149, -249, -211, -363, -445, -322, -199, -451, -374, -212,
+ -206, -56, -101, 52, -41, 90, 144, 4, -84, 38, -111, -234,
+ -118, -217, -227, -246, -157, -274, -235, -253, -192, -341, -200, -287,
+ -253, -258, -299, -242, -317, -247, -273, -208, -264, -246, -273, -176,
+ -209, -205, -204, -238, -186, -184, -158, -218, -216, -193, -143, -174,
+ -141, -179, -144, -152, -78, -161, -124, -122, -54, -126, -102, -95,
+ -62, -64, -102, -54, -75, -51, -32, -78, -64, -53, -39, -81,
+ -33, -75, -14, -32, -27, -68, 2, -35, 2, -22, 5, -24,
+ 24, -12, -6, 0}};
+
+const int16_t irc_composite_c_r0195_p060[][256] =
+ {/* IRC_Composite_C_R0195_T000_P060.wav */
+ {6, -6, 6, -6, 6, -6, 6, -6, 6, -6, 6,
+ -6, 6, -6, 5, -5, 5, -6, 7, -9, 18, -62,
+ -477, -304, -336, -458, -335, -491, -402, -829, -409, -707, -293,
+ -66, 983, 571, 504, 539, -3665, -1218, -4247, 2405, 12687, 3792,
+ 3387, 222, 562, 745, 2239, 1323, -741, -286, -83, 813, 783,
+ 665, -6, 1300, 432, 669, 373, 824, 513, 741, 332, 740,
+ 326, 679, 126, 625, 66, 777, 213, 737, 590, 515, 621,
+ 819, 662, 597, 612, 571, 221, 378, 180, 456, -17, 193,
+ -19, 201, -103, 188, -114, -50, -223, 41, -272, -7, -204,
+ -73, -274, -27, -179, -118, -266, -52, -236, -150, -298, -185,
+ -328, -211, -330, -359, -321, -288, -288, -390, -233, -301, -238,
+ -287, -186, -257, -179, -231, -185, -249, -207, -244, -242, -257,
+ -261, -239, -326, -213, -312, -252, -311, -214, -308, -230, -270,
+ -210, -269, -227, -231, -256, -281, -231, -249, -231, -210, -238,
+ -174, -214, -204, -218, -156, -225, -179, -158, -114, -151, -171,
+ -128, -150, -184, -87, -97, -145, -171, -201, -199, -179, -259,
+ -239, -309, -310, -314, -227, -196, -170, -192, -26, -115, 43,
+ -61, 74, -180, -52, -139, -83, -192, -18, -187, -104, -111,
+ -67, -175, -118, -149, -164, -160, -118, -201, -219, -161, -187,
+ -241, -189, -199, -195, -205, -163, -179, -154, -122, -150, -171,
+ -167, -152, -190, -183, -192, -193, -159, -180, -150, -160, -87,
+ -151, -131, -124, -50, -117, -92, -81, -58, -70, -16, -38,
+ -34, -12, 39, -10, 47, 38, 87, 17, 83, 31, 82,
+ 15, 52, 1},
+ /* IRC_Composite_C_R0195_T030_P060.wav */
+ {20, -21, 22, -23, 24, -25, 26, -28, 29, -31, 33,
+ -35, 37, -40, 44, -49, 57, -69, 99, -268, -386, -296,
+ -341, -306, -439, -461, -219, -926, -450, -515, -185, -151, 2064,
+ -248, 2208, -557, -2551, -2664, -6482, 6173, 13499, 9000, -215, 137,
+ 1397, 897, 1847, 1331, -989, -1152, -31, 458, 894, 968, 314,
+ 922, 758, 306, 312, 862, 265, 458, 604, 466, 114, 555,
+ 424, 349, 321, 475, 497, 583, 497, 442, 464, 481, 505,
+ 592, 441, 239, 76, 20, 163, 45, -273, -262, -336, -166,
+ -64, -152, -189, -166, -140, -223, -230, -243, -348, -277, -328,
+ -243, -252, -223, -247, -223, -225, -225, -211, -310, -229, -259,
+ -253, -320, -259, -356, -330, -309, -289, -302, -271, -256, -257,
+ -245, -269, -281, -283, -267, -256, -268, -267, -280, -263, -261,
+ -261, -284, -289, -275, -272, -247, -274, -279, -271, -276, -305,
+ -292, -268, -282, -266, -248, -248, -229, -247, -221, -202, -217,
+ -233, -213, -204, -239, -209, -216, -194, -227, -155, -201, -185,
+ -243, -159, -264, -210, -189, -201, -282, -175, -151, -178, -186,
+ -73, -151, -201, -182, -71, -254, -132, -94, -52, -235, -22,
+ -27, -101, -159, -17, -130, -166, -55, -23, -206, -132, -126,
+ -159, -211, -63, -187, -189, -148, -84, -174, -142, -124, -126,
+ -185, -149, -155, -156, -187, -149, -205, -136, -153, -105, -164,
+ -138, -137, -75, -158, -128, -135, -115, -148, -94, -125, -133,
+ -123, -51, -120, -102, -85, -18, -84, -52, -42, 8, -51,
+ 10, 0, 9, -20, 40, 16, 14, 24, 69, 21, 25,
+ 23, 33, 2},
+ /* IRC_Composite_C_R0195_T060_P060.wav */
+ {49, -49, 50, -50, 49, -49, 48, -47, 45, -43, 39,
+ -34, 27, -17, 1, 24, -67, 158, -573, 27, -273, -533,
+ -199, -358, -57, -1237, 277, -801, -585, 81, 1678, -52, 2584,
+ -635, 2281, -6550, -1907, -4733, 12287, 16138, 2134, 560, -1141, 3390,
+ 1047, 2071, -850, -288, -1730, 541, 499, 654, 282, 1081, 13,
+ 1053, -59, 982, 296, 639, 439, 387, 97, 735, -102, 457,
+ 298, 541, 229, 951, 144, 642, -123, 725, 239, 371, -407,
+ 173, -217, 5, -418, 64, -709, -238, -708, -195, -402, -221,
+ -414, -199, -238, -175, -279, -243, -447, -227, -531, -270, -429,
+ -178, -455, -173, -350, -170, -312, -238, -284, -268, -355, -248,
+ -402, -300, -358, -251, -472, -244, -350, -274, -353, -195, -309,
+ -279, -276, -243, -232, -248, -185, -198, -344, -177, -263, -274,
+ -355, -198, -341, -222, -358, -178, -344, -238, -289, -271, -338,
+ -286, -218, -316, -206, -256, -230, -264, -214, -239, -277, -261,
+ -259, -225, -268, -221, -134, -246, -182, -192, -154, -260, -116,
+ -218, -234, -234, -227, -254, -254, -224, -164, -220, -284, -56,
+ -257, -45, -289, -17, -299, 22, -225, 103, -264, -36, -33,
+ -158, -37, -147, 39, -238, -62, -117, -69, -262, 42, -153,
+ -162, -86, -22, -214, -31, -193, -19, -225, -87, -128, -143,
+ -84, -102, -114, -95, -106, -146, -143, -5, -308, -86, -140,
+ -102, -254, 41, -68, -208, 37, -26, -47, -124, 31, -99,
+ -131, -35, -104, -59, -141, 32, -115, 3, -48, 1, -54,
+ 32, -67, -4, -31, -40, -50, 45, -30, -17, 43, 3,
+ 8, 10, 37},
+ /* IRC_Composite_C_R0195_T090_P060.wav */
+ {20, -22, 23, -25, 26, -28, 30, -33, 35, -38, 41,
+ -45, 49, -54, 60, -66, 75, -87, -10, -413, -67, -233,
+ -277, -289, -247, -282, -617, -150, -183, 967, 613, 1964, 132,
+ 3132, -4890, -30, -7492, 6867, 13211, 9263, 1898, -2424, 3417, 1010,
+ 3400, -250, -877, -1856, 320, -121, 560, 520, 754, 353, 491,
+ -65, 931, 362, 494, 135, 792, 604, 460, -39, 664, 258,
+ 476, 82, 671, 151, 141, 64, 562, -256, -248, -550, -170,
+ -357, -396, -602, -158, -390, -415, -610, -298, -495, -302, -712,
+ -205, -411, -237, -411, -197, -452, -250, -501, -239, -421, -284,
+ -481, -170, -438, -181, -420, -184, -419, -262, -417, -218, -376,
+ -336, -394, -333, -356, -335, -390, -304, -355, -247, -352, -237,
+ -395, -250, -384, -156, -227, -147, -266, -148, -197, -207, -288,
+ -280, -286, -312, -317, -266, -316, -238, -306, -223, -320, -187,
+ -342, -177, -339, -198, -318, -212, -319, -219, -304, -241, -273,
+ -207, -279, -190, -248, -107, -255, -126, -251, -112, -272, -95,
+ -249, -163, -250, -133, -247, -177, -254, -198, -235, -194, -229,
+ -212, -194, -191, -142, -191, -92, -153, -45, -145, -65, -138,
+ -64, -93, -82, -65, -113, -49, -140, -78, -118, -32, -137,
+ -37, -107, -30, -59, -142, -106, -132, -11, -240, 49, -159,
+ 20, -179, 55, -117, -17, -81, -27, -68, -101, -72, -98,
+ -48, -107, -136, -19, -93, -74, -119, 94, -211, 28, -129,
+ 49, -157, 78, -96, 21, -143, 105, -58, -15, -28, 13,
+ -42, 14, -40, -16, -41, -38, -39, 21, -40, -15, -19,
+ -23, -62, 3},
+ /* IRC_Composite_C_R0195_T120_P060.wav */
+ {-11, 11, -11, 11, -11, 10, -10, 10, -9, 9, -8,
+ 7, -6, 4, -1, -4, 11, -23, 54, -588, 228, -347,
+ 10, -393, 78, -279, -133, -619, 373, 107, 880, 1033, 1066,
+ 2024, -241, -1833, -4036, 1812, 3742, 13338, 5090, 1325, -1146, 2733,
+ 2814, 1516, -118, -2246, -488, 96, 262, 192, 815, 13, 476,
+ 529, 495, 181, 475, 265, 780, 210, 341, 81, 498, 784,
+ 541, 47, -30, -8, 36, -29, -17, -592, -609, -210, -251,
+ -604, -616, -485, -190, -407, -234, -662, -290, -550, -301, -639,
+ -361, -458, -250, -462, -279, -431, -205, -390, -287, -423, -271,
+ -396, -265, -443, -263, -432, -176, -477, -278, -447, -188, -475,
+ -218, -453, -216, -462, -223, -420, -282, -431, -248, -385, -293,
+ -403, -276, -425, -204, -367, -74, -400, -116, -375, -65, -372,
+ -120, -338, -171, -350, -192, -321, -226, -351, -196, -350, -234,
+ -344, -164, -341, -158, -306, -138, -338, -156, -268, -116, -265,
+ -176, -243, -204, -212, -198, -229, -226, -192, -144, -137, -153,
+ -181, -152, -194, -135, -178, -195, -187, -227, -175, -208, -170,
+ -244, -159, -254, -137, -245, -163, -226, -152, -162, -107, -156,
+ -107, -123, -75, -95, -2, -107, -23, -62, -12, -74, -60,
+ -54, -88, -41, -86, -11, -107, -13, -74, -29, -98, -23,
+ -52, -89, -64, -61, -71, -89, -73, 19, -96, 13, -86,
+ 41, -106, 92, -5, 1, -25, -7, -37, -56, 34, -91,
+ -24, -86, 10, -49, -33, -31, -56, 10, -83, 56, -62,
+ 45, -72, 47, -53, 7, -38, 12, -40, -11, -4, -41,
+ -34, -33, -33},
+ /* IRC_Composite_C_R0195_T150_P060.wav */
+ {-16, 16, -17, 17, -18, 18, -19, 20, -20, 21, -21, 22,
+ -22, 23, -23, 23, -23, 22, -20, 15, -1, -129, 61, -83,
+ -118, -17, -51, -30, -188, -95, 109, 510, 730, 1005, 940, 2137,
+ -1039, -410, -2971, 1165, 5406, 9365, 3937, 2137, -649, 991, 4216, 933,
+ -931, -1885, 270, 39, 471, -2, 92, 175, 614, 97, 357, 338,
+ 829, 636, 315, -32, 510, 435, 434, 61, 257, -175, -293, -260,
+ -101, -474, -492, -360, -265, -315, -367, -291, -300, -567, -400, -366,
+ -215, -495, -451, -522, -369, -523, -275, -402, -310, -403, -263, -346,
+ -267, -319, -256, -370, -300, -367, -298, -401, -295, -443, -304, -417,
+ -322, -444, -298, -381, -312, -439, -329, -390, -317, -384, -301, -362,
+ -302, -383, -295, -368, -322, -392, -272, -324, -246, -326, -211, -303,
+ -225, -323, -240, -326, -214, -295, -235, -325, -259, -307, -231, -265,
+ -204, -266, -221, -283, -193, -226, -150, -215, -156, -235, -140, -226,
+ -173, -226, -138, -181, -139, -211, -149, -228, -146, -230, -122, -202,
+ -104, -157, -89, -169, -122, -180, -141, -172, -119, -242, -182, -247,
+ -146, -221, -160, -209, -160, -222, -134, -194, -158, -172, -105, -115,
+ -90, -104, -68, -78, -70, -76, -8, -32, 19, -27, 16, -5,
+ -14, -32, 0, -60, -11, -71, 10, -79, -60, -81, -34, -99,
+ -28, -10, -16, 0, 23, 9, -66, -19, -7, -4, -45, 41,
+ 7, -5, -25, -4, 7, -31, -3, 22, -19, -42, 6, -62,
+ -7, -36, 56, -32, 47, -21, 39, -84, -6, -47, -14, -45,
+ 33, -19, -3, -57},
+ /* IRC_Composite_C_R0195_T180_P060.wav */
+ {10, -10, 10, -11, 11, -10, 10, -10, 10, -10, 10, -10,
+ 9, -9, 8, -7, 6, -5, 3, -1, -2, 6, -12, 19,
+ -22, 153, -20, -54, 110, 44, 10, -80, 172, 36, 605, 639,
+ 998, 719, 1758, -623, -249, -1810, 1268, 4590, 6942, 3540, 1131, 148,
+ 816, 4013, 533, -755, -1438, 271, -13, 605, 142, 55, 202, 291,
+ -131, 465, 665, 844, 217, 120, 213, 663, 92, -57, -100, 67,
+ -191, -270, -528, -381, -572, -177, -319, -6, -278, -198, -211, 58,
+ -313, -463, -592, -248, -465, -368, -496, -344, -514, -270, -361, -242,
+ -341, -263, -349, -193, -335, -278, -416, -272, -416, -335, -439, -313,
+ -451, -348, -453, -349, -443, -392, -472, -363, -430, -386, -408, -312,
+ -392, -320, -379, -310, -370, -331, -403, -335, -385, -348, -366, -269,
+ -311, -276, -302, -234, -290, -316, -341, -275, -241, -217, -208, -227,
+ -228, -238, -222, -184, -224, -208, -241, -186, -232, -205, -245, -203,
+ -214, -163, -151, -128, -135, -145, -180, -187, -204, -171, -188, -145,
+ -158, -112, -157, -75, -116, -69, -149, -129, -160, -136, -115, -133,
+ -152, -184, -177, -167, -165, -197, -218, -169, -194, -138, -205, -133,
+ -162, -112, -132, -87, -117, -57, -79, -38, -55, -39, -50, -29,
+ -58, -18, 2, 13, -3, 31, 17, 9, 8, 29, -11, -10,
+ -3, 19, -49, -35, -14, -39, -21, -64, -32, -53, -24, -15,
+ -14, -23, 27, 17, 51, -9, -15, -18, 18, -37, 5, -74,
+ -13, -18, 63, 0, 46, 7, 63, -15, 19, -10, 7, -30,
+ -32, -44, -79, -107},
+ /* IRC_Composite_C_R0195_T210_P060.wav */
+ {-5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -5, 5,
+ -5, 5, -5, 5, -5, 5, -5, 5, -5, 5, -4, 4,
+ -4, 4, -4, -36, -70, 2, -43, -45, -73, 17, -83, -76,
+ -100, 274, 192, 709, 499, 1049, 474, 192, -1587, -214, 1449, 4357,
+ 5043, 2092, 784, 976, 2790, 1402, 1131, -633, 70, 310, 385, 217,
+ 300, 87, 393, 309, 640, 644, 573, 357, 469, 403, 414, 393,
+ 291, 95, 9, 177, 41, -38, -147, -145, -178, -91, 51, -23,
+ 82, 6, 81, -66, -34, -205, -207, -298, -223, -348, -275, -305,
+ -257, -377, -308, -322, -230, -324, -269, -300, -305, -334, -286, -346,
+ -334, -384, -351, -411, -405, -389, -413, -415, -387, -382, -401, -384,
+ -393, -405, -388, -417, -364, -399, -357, -398, -374, -392, -376, -343,
+ -359, -316, -314, -243, -324, -280, -326, -283, -293, -262, -262, -275,
+ -249, -231, -206, -195, -215, -224, -241, -210, -220, -242, -276, -255,
+ -264, -198, -192, -190, -189, -210, -164, -172, -169, -172, -189, -153,
+ -193, -127, -166, -117, -158, -112, -128, -133, -107, -136, -117, -161,
+ -108, -110, -142, -127, -184, -138, -172, -147, -192, -188, -193, -158,
+ -153, -183, -168, -189, -142, -164, -152, -151, -135, -105, -91, -78,
+ -46, -70, -9, -22, -10, 6, -3, 11, 9, 0, -18, -34,
+ -49, -47, -78, -56, -80, -88, -84, -41, -76, -89, -89, -34,
+ -62, 21, -11, 29, -63, 38, 13, 32, -26, -6, -18, -15,
+ -14, -11, -43, -37, -18, 40, -10, 3, -52, 3, -65, -18,
+ -66, -33, -83, -53},
+ /* IRC_Composite_C_R0195_T240_P060.wav */
+ {-5, 5, -5, 5, -5, 6, -6, 6, -6, 6, -6, 6,
+ -6, 6, -6, 6, -6, 6, -6, 6, -6, 7, -7, 7,
+ -6, 6, -6, 4, 1, -68, -141, -43, -120, -98, -71, -122,
+ -111, -111, -115, -151, -61, 253, 299, 522, 567, 502, 242, -1198,
+ -647, 857, 3187, 4265, 2111, 1152, 1068, 2588, 1288, 1124, 171, 189,
+ 245, 613, 495, 306, 631, 410, 664, 513, 726, 614, 455, 361,
+ 493, 538, 415, 305, 397, 398, 328, 162, 355, 127, 167, 95,
+ 91, 92, 127, 159, 137, 126, 136, 112, 42, -54, -59, -214,
+ -99, -334, -210, -340, -229, -357, -264, -345, -232, -312, -253, -294,
+ -304, -352, -288, -347, -349, -364, -352, -373, -394, -376, -368, -434,
+ -376, -402, -393, -434, -406, -412, -357, -430, -351, -354, -374, -337,
+ -289, -323, -276, -287, -233, -304, -270, -311, -283, -321, -267, -321,
+ -256, -305, -231, -272, -202, -256, -217, -272, -229, -238, -234, -247,
+ -261, -261, -253, -218, -198, -218, -190, -226, -148, -209, -150, -201,
+ -174, -181, -174, -172, -158, -155, -141, -131, -122, -138, -117, -118,
+ -119, -130, -144, -143, -130, -146, -135, -155, -149, -172, -147, -158,
+ -167, -175, -169, -144, -176, -150, -108, -129, -129, -136, -93, -143,
+ -97, -99, -87, -71, -74, -15, -38, -48, -75, -66, -95, -101,
+ -91, -93, -103, -154, -122, -128, -116, -84, -70, -79, -65, -51,
+ -28, -23, -20, -21, -53, -15, -9, 15, -49, 5, -40, -18,
+ -42, -33, -30, -86, -81, -68, -62, -85, -44, -26, -89, -49,
+ -55, -24, -61, -16},
+ /* IRC_Composite_C_R0195_T270_P060.wav */
+ {5, -5, 6, -6, 6, -6, 6, -6, 7, -7, 7, -7,
+ 8, -8, 8, -9, 9, -10, 10, -11, 12, -12, 13, -15,
+ 16, -19, 22, -27, 38, -79, -217, -116, -99, -179, -211, -36,
+ -231, -204, -130, -219, -231, -133, 80, 277, 396, 430, 429, -88,
+ -768, -1080, 327, 3345, 4354, 1751, 849, 1821, 1217, 1828, 885, 517,
+ 269, 311, 1017, 365, 820, 357, 716, 549, 590, 621, 516, 736,
+ 326, 780, 263, 770, 281, 705, 378, 636, 371, 475, 311, 429,
+ 255, 409, 238, 273, 140, 314, 213, 208, 138, 105, 115, -23,
+ 74, -205, -25, -248, -155, -287, -280, -252, -297, -307, -288, -293,
+ -267, -333, -245, -352, -304, -348, -348, -351, -433, -337, -451, -334,
+ -472, -324, -423, -375, -381, -372, -314, -375, -276, -267, -321, -301,
+ -279, -273, -291, -287, -302, -304, -304, -290, -236, -296, -289, -263,
+ -294, -284, -324, -265, -281, -292, -245, -245, -266, -260, -214, -224,
+ -254, -200, -260, -209, -252, -220, -200, -254, -196, -233, -158, -228,
+ -136, -185, -148, -205, -143, -175, -148, -149, -166, -120, -148, -104,
+ -128, -140, -125, -152, -101, -130, -77, -199, -88, -178, -108, -197,
+ -126, -198, -117, -194, -113, -172, -133, -128, -87, -131, -57, -112,
+ -60, -136, -71, -150, -125, -162, -106, -205, -139, -165, -138, -149,
+ -118, -134, -84, -134, -83, -120, -64, -109, -79, -116, -53, -90,
+ -64, -49, -97, -69, -54, -86, -38, -63, -120, -64, -43, -89,
+ -78, -45, -32, -110, -9, -49, -30, -91, -36, -87, -34, -90,
+ -12, -62, -20, -67},
+ /* IRC_Composite_C_R0195_T300_P060.wav */
+ {5, -5, 5, -5, 6, -6, 6, -6, 6, -6, 6, -6,
+ 6, -7, 7, -7, 7, -7, 7, -8, 8, -8, 8, -9,
+ 9, -9, 10, -11, 16, -103, -316, -221, -172, -255, -229, -364,
+ -48, -533, -236, -67, -300, 259, 642, 185, 100, 427, -1196, -1745,
+ 476, 2467, 5309, 2589, 680, 1682, 925, 1569, 1041, 949, -237, 234,
+ 798, 421, 371, 569, 750, 604, 969, 544, 826, 529, 786, 297,
+ 628, 582, 490, 514, 782, 585, 536, 518, 533, 446, 484, 585,
+ 264, 478, 477, 367, 328, 417, 65, 219, 65, 246, -91, 156,
+ -64, 72, -138, 50, -297, -48, -254, -210, -356, -206, -256, -287,
+ -317, -214, -367, -254, -373, -339, -401, -317, -430, -376, -368, -350,
+ -418, -316, -298, -353, -273, -305, -281, -287, -227, -371, -293, -252,
+ -310, -323, -260, -272, -324, -243, -257, -310, -290, -276, -297, -255,
+ -201, -324, -280, -270, -230, -282, -221, -248, -298, -222, -268, -241,
+ -260, -255, -277, -235, -187, -236, -210, -172, -224, -173, -145, -191,
+ -157, -155, -157, -126, -186, -84, -150, -177, -153, -72, -128, -133,
+ -127, -100, -229, -197, -160, -241, -236, -171, -176, -120, -75, -47,
+ -120, -105, -128, -76, -160, -120, -127, -109, -139, -135, -144, -192,
+ -179, -152, -203, -173, -143, -161, -61, -179, -143, -104, -126, -147,
+ -78, -176, -138, -177, -87, -160, -110, -137, -186, -155, -86, -135,
+ -127, -105, -97, -124, -34, -111, -50, -117, -72, -123, -75, -65,
+ -94, -51, -83, -75, -56, -47, -45, -13, -39, -55, 20, -56,
+ 36, -41, 12, -24},
+ /* IRC_Composite_C_R0195_T330_P060.wav */
+ {3, -3, 4, -4, 4, -4, 5, -5, 5, -6, 6, -6,
+ 7, -7, 8, -9, 10, -11, 13, -15, 18, -22, 28, -41,
+ 71, -233, -373, -96, -345, -318, -371, -265, -252, -594, -439, -312,
+ -197, 197, 323, 301, 1318, -757, -1616, -868, -2081, 2422, 6337, 5378,
+ 1528, 381, 1195, 1007, 1545, 810, 158, -165, 224, 315, 652, 555,
+ 370, 728, 945, 600, 540, 730, 606, 560, 567, 567, 493, 662,
+ 473, 574, 547, 639, 468, 638, 527, 625, 475, 584, 476, 643,
+ 403, 465, 284, 372, 243, 235, 43, 108, 49, 102, 20, -28,
+ -86, -48, -49, -120, -149, -133, -123, -173, -198, -229, -246, -291,
+ -242, -300, -328, -373, -296, -357, -299, -377, -351, -356, -276, -311,
+ -324, -289, -282, -268, -271, -255, -256, -273, -249, -285, -230, -270,
+ -219, -299, -282, -269, -275, -287, -307, -267, -290, -268, -297, -284,
+ -263, -248, -257, -244, -243, -257, -290, -273, -259, -265, -237, -261,
+ -238, -273, -209, -234, -171, -171, -157, -118, -120, -137, -80, -115,
+ -96, -166, -45, -140, -79, -126, -114, -259, -243, -265, -228, -274,
+ -267, -264, -220, -203, -117, -137, -118, -112, -33, -82, -41, -93,
+ -102, -121, -120, -145, -175, -177, -147, -143, -65, -127, -138, -136,
+ -94, -127, -130, -217, -183, -159, -168, -202, -184, -203, -240, -202,
+ -146, -195, -160, -147, -169, -143, -72, -128, -191, -139, -128, -170,
+ -166, -129, -160, -152, -97, -112, -123, -68, -72, -87, -76, -27,
+ -63, -48, -50, -16, -43, 16, -15, -10, -1, 57, 47, 30,
+ 49, 50, 35, 14}};
+
+const int16_t irc_composite_c_r0195_p075[][256] =
+ {/* IRC_Composite_C_R0195_T000_P075.wav */
+ {12, -12, 13, -13, 13, -13, 13, -13, 14, -14, 14, -14,
+ 14, -14, 15, -15, 14, -14, 13, -12, 7, 8, -224, -367,
+ -189, -595, -344, -165, -801, -581, -302, -836, -618, 407, 530, 134,
+ 1455, -966, -265, -4329, -4113, 4525, 6992, 8542, 304, -199, 2762, 1323,
+ 1544, 1359, 195, -1272, 815, 548, 454, 489, 1188, 303, 1217, 433,
+ 606, 486, 940, 200, 865, 394, 415, 452, 439, 167, 687, 262,
+ 388, 668, 618, 250, 776, 505, 518, 525, 594, 287, 552, 461,
+ 358, 276, 304, 160, 239, 30, 35, -21, -62, -125, -17, -31,
+ -371, 39, -211, -122, -197, 40, -391, 72, -166, -93, -262, -84,
+ -337, -76, -325, -174, -301, -196, -378, -120, -367, -324, -293, -268,
+ -402, -208, -271, -377, -322, -212, -314, -262, -200, -245, -294, -186,
+ -222, -275, -226, -280, -246, -300, -262, -182, -254, -272, -206, -217,
+ -307, -182, -214, -248, -250, -184, -258, -303, -196, -229, -312, -240,
+ -213, -256, -268, -167, -202, -256, -145, -174, -141, -306, -39, -235,
+ -192, -132, -161, -142, -205, -78, -192, -93, -268, -65, -239, -166,
+ -229, -88, -215, -227, -134, -262, -99, -230, -94, -297, -147, -247,
+ -15, -245, -197, -51, -171, -94, -46, -60, -97, -14, -100, 14,
+ -99, -80, -92, -24, -178, 55, -112, -23, -104, 47, -121, -73,
+ -80, -27, -196, -98, -74, -188, -162, -54, -110, -182, -133, -30,
+ -121, -83, -72, -116, -145, -93, -89, -136, -159, -163, -75, -145,
+ -145, -85, -61, -103, -107, 5, -74, -43, -3, -22, -72, -26,
+ -24, 6, -85, 5},
+ /* IRC_Composite_C_R0195_T060_P075.wav */
+ {-14, 15, -15, 16, -17, 18, -19, 20, -21, 22, -23,
+ 24, -25, 27, -28, 30, -32, 35, -42, 65, -281, -389,
+ 123, -583, -268, -119, -384, -436, -660, 282, -1424, 931, -191,
+ 1870, -113, 1526, 243, -2974, -2922, -3038, 6766, 14120, 3000, -177,
+ 427, 2482, 2472, 607, 1128, -2092, 683, -982, 2038, -839, 1456,
+ -30, 1050, 188, 720, 669, 391, 864, 257, 483, 214, 493,
+ 238, 321, 378, 114, 592, 45, 886, -9, 646, 103, 701,
+ 13, 345, 57, 116, 121, -88, 171, -386, 97, -335, -262,
+ -369, -373, -308, -409, 33, -533, 5, -484, 24, -435, -237,
+ -319, -208, -233, -416, -146, -317, -166, -309, -232, -304, -340,
+ -158, -439, -169, -482, -113, -521, -200, -450, -258, -409, -296,
+ -298, -425, -296, -402, -280, -434, -185, -317, -227, -279, -194,
+ -241, -311, -232, -320, -314, -306, -233, -329, -276, -236, -245,
+ -297, -256, -187, -267, -262, -210, -246, -287, -248, -205, -331,
+ -278, -229, -232, -260, -191, -174, -217, -208, -141, -202, -243,
+ -151, -110, -284, -132, -132, -134, -277, -115, -207, -229, -254,
+ -153, -213, -329, -111, -236, -248, -245, -82, -281, -102, -144,
+ -93, -133, -52, -82, -139, -23, -121, -40, -135, 7, -127,
+ -34, -127, -9, -67, -119, -37, -34, -134, 4, -70, -77,
+ -81, -62, -64, -127, -18, -109, 11, -156, 60, -224, 27,
+ -77, -130, 6, -101, -38, -151, 93, -175, -123, 0, -32,
+ -129, -111, 115, -229, -48, 12, -46, -196, -37, -14, -163,
+ -83, -1, -137, -48, -101, 91, -141, 15, -60, 28, -35,
+ -33, -10, -28},
+ /* IRC_Composite_C_R0195_T120_P075.wav */
+ {-13, 13, -14, 14, -15, 15, -16, 16, -17, 17, -18,
+ 18, -19, 19, -20, 20, -19, 19, -16, 12, -1, -57,
+ -48, -189, -132, -75, -227, -56, -343, 28, -501, 199, 201,
+ 917, 964, 1130, 545, 1226, -3782, -981, -1166, 8252, 10379, 3341,
+ -521, -669, 4572, 428, 2096, -1386, -405, -1098, 1383, -2, 236,
+ 525, 510, 124, 361, 340, 604, 403, 459, 20, 703, 550,
+ 361, 4, 507, 76, 379, 200, 87, 66, -36, 226, 133,
+ 30, -418, -183, -144, -119, -262, -333, -294, -550, -263, -353,
+ -297, -518, -525, -481, -405, -172, -382, -282, -383, -195, -329,
+ -205, -358, -340, -304, -296, -417, -305, -321, -319, -400, -254,
+ -454, -283, -418, -294, -482, -262, -427, -329, -482, -271, -419,
+ -319, -403, -345, -351, -373, -409, -381, -310, -297, -225, -271,
+ -290, -302, -223, -252, -250, -249, -220, -293, -267, -259, -270,
+ -267, -281, -238, -321, -208, -300, -203, -357, -197, -295, -169,
+ -277, -177, -273, -210, -234, -196, -252, -188, -188, -144, -221,
+ -121, -160, -150, -180, -111, -124, -171, -110, -188, -133, -213,
+ -124, -220, -168, -203, -176, -182, -229, -156, -255, -117, -221,
+ -99, -240, -61, -158, -122, -129, -39, -92, -133, 4, -74,
+ -53, -77, 35, -129, 8, -50, 5, -61, -24, -33, -46,
+ 31, -56, 58, -35, 39, -37, 57, -74, 2, -108, -10,
+ -136, 18, -141, 8, -114, 4, -132, -22, -37, -31, -20,
+ -71, 79, -149, 14, -32, 50, -85, -47, 94, -59, 21,
+ -39, 82, -145, 47, -47, -14, -82, -16, -3, -58, -48,
+ -44, -22, -40},
+ /* IRC_Composite_C_R0195_T180_P075.wav */
+ {-17, 17, -17, 17, -16, 16, -16, 16, -15, 15, -15, 14,
+ -14, 14, -13, 12, -12, 11, -10, 9, -7, 5, -2, -2,
+ 11, 164, -118, 156, -74, 171, -166, 281, -259, 331, -163, 647,
+ 483, 1342, 96, 2125, -652, 369, -2506, 1012, 3788, 6928, 5436, -854,
+ 927, 1011, 2973, 419, 642, -1377, -224, 371, 309, -128, 78, 268,
+ 228, 233, 434, 427, 397, -91, 692, 541, 379, -60, 222, -23,
+ 131, 178, 60, -172, -56, -82, -145, -311, -297, -431, -248, -412,
+ -107, -371, -72, -353, -277, -448, -332, -408, -362, -461, -379, -530,
+ -388, -407, -290, -364, -334, -409, -288, -419, -285, -384, -338, -447,
+ -343, -386, -344, -453, -378, -390, -377, -502, -386, -429, -361, -457,
+ -343, -463, -441, -462, -348, -398, -369, -416, -375, -435, -312, -336,
+ -244, -309, -307, -321, -263, -272, -272, -262, -236, -196, -217, -232,
+ -215, -261, -261, -304, -256, -296, -220, -241, -218, -248, -199, -190,
+ -206, -213, -223, -195, -209, -182, -150, -161, -155, -165, -145, -155,
+ -129, -127, -102, -102, -126, -107, -145, -133, -143, -141, -131, -140,
+ -117, -119, -86, -121, -100, -131, -130, -130, -139, -142, -162, -155,
+ -148, -128, -171, -136, -140, -117, -101, -76, -112, -44, -78, -20,
+ -37, -38, -18, 15, -6, 2, 39, 44, 55, -20, 46, 41,
+ 30, 8, 37, 20, -14, 0, -10, 0, -57, -38, -55, -62,
+ -113, -27, -88, -33, -69, -21, -80, 1, -86, 62, -11, 16,
+ 23, 128, 6, 102, -12, 152, 3, 46, 29, 36, -97, 2,
+ -15, 22, -109, -54},
+ /* IRC_Composite_C_R0195_T240_P075.wav */
+ {3, -3, 3, -3, 3, -3, 3, -2, 2, -2, 2, -2,
+ 1, -1, 1, 0, 0, 1, -2, 3, -4, 5, -7, 10,
+ -13, 20, -42, -111, -129, -71, -115, -137, -61, -159, -123, -192,
+ -19, -219, 406, 390, 680, 549, 886, -353, -736, -1471, 543, 4694,
+ 6064, 1476, 785, 1081, 2341, 1848, 567, 793, -1054, 878, -19, 694,
+ -27, 624, 453, 574, 713, 561, 524, 289, 447, 341, 517, 436,
+ 227, 504, 311, 411, 259, 396, 204, 319, 114, 96, 38, -78,
+ 39, -23, 9, 123, -104, 210, -61, 44, -245, -60, -225, -163,
+ -284, -292, -366, -364, -318, -284, -326, -284, -257, -269, -279, -348,
+ -246, -376, -367, -329, -388, -355, -428, -316, -418, -375, -426, -318,
+ -396, -436, -393, -407, -383, -405, -383, -442, -422, -393, -344, -375,
+ -309, -340, -270, -311, -270, -269, -256, -268, -233, -283, -233, -230,
+ -236, -283, -256, -265, -301, -274, -317, -261, -298, -265, -239, -262,
+ -221, -248, -188, -287, -192, -249, -152, -246, -140, -231, -148, -218,
+ -130, -170, -165, -154, -171, -127, -181, -99, -138, -109, -120, -99,
+ -88, -129, -61, -150, -63, -199, -44, -206, -106, -201, -113, -189,
+ -182, -146, -180, -174, -177, -146, -150, -167, -103, -131, -76, -138,
+ -14, -119, -32, -66, -17, -34, -48, 8, -80, -30, -22, -34,
+ -12, -52, 1, -29, -7, 12, -34, -30, -36, -43, -95, -29,
+ -80, -102, -17, -119, -24, -119, 10, -78, -16, -105, -38, -66,
+ -45, -15, 9, -36, -32, 33, -12, -9, -66, -12, -59, 8,
+ -23, -10, -6, -22},
+ /* IRC_Composite_C_R0195_T300_P075.wav */
+ {5, -5, 5, -6, 6, -6, 6, -7, 7, -7, 8, -8,
+ 9, -9, 10, -10, 11, -12, 13, -14, 16, -19, 23, -30,
+ 49, -174, -216, -220, -266, -146, -276, -310, -167, -398, -257, -446,
+ -119, 2, 150, 904, 282, 399, -249, -994, -2605, 51, 5569, 5353,
+ 3304, -294, 1237, 2394, 748, 1912, 84, -494, 511, 465, 372, 641,
+ 761, 120, 1277, 373, 764, 283, 1087, -27, 731, 558, 213, 625,
+ 490, 578, 331, 755, 261, 576, 308, 533, 270, 531, 241, 337,
+ 345, 292, 249, 254, 130, 252, 95, 124, 0, -94, 22, -124,
+ -166, -120, -222, -195, -188, -102, -288, -138, -247, -177, -285, -135,
+ -368, -230, -298, -370, -301, -355, -365, -394, -255, -460, -300, -338,
+ -376, -356, -289, -388, -412, -303, -449, -224, -403, -214, -327, -319,
+ -233, -280, -268, -283, -281, -308, -227, -300, -251, -210, -330, -195,
+ -309, -196, -294, -197, -342, -211, -310, -249, -268, -272, -295, -243,
+ -275, -237, -249, -206, -274, -172, -248, -191, -211, -154, -258, -145,
+ -194, -183, -134, -143, -154, -131, -111, -163, -86, -129, -136, -111,
+ -159, -135, -153, -128, -194, -126, -204, -111, -239, -106, -205, -215,
+ -163, -175, -185, -155, -144, -130, -163, -92, -105, -142, -4, -111,
+ -109, -19, -118, 13, -189, 9, -76, -118, -8, -6, -126, -54,
+ 46, -138, -64, 43, -92, -25, -132, 48, -164, -57, -43, -117,
+ -199, -11, -164, -58, -105, -75, -100, -150, -59, -62, -137, -80,
+ -148, -65, -126, -49, -62, -33, -77, -10, -18, -11, -21, -15,
+ -36, -38, -38, 9}};
+
+const int16_t irc_composite_c_r0195_p090[][256] =
+ {/* IRC_Composite_C_R0195_T000_P090.wav */
+ {-1, 1, -1, 1, -1, 1, -1, 2, -2, 2, -3, 3,
+ -3, 4, -5, 5, -7, 8, -10, 14, -25, -385, -133, -356,
+ -344, -390, -312, -438, -400, -852, -107, -995, -245, -186, 1358, -771,
+ 1473, 28, -1710, -3762, -3025, 4119, 8200, 5927, -785, 1558, 2250, 1837,
+ 2098, 631, -318, 150, -77, 1215, -23, 792, 865, 600, 981, 659,
+ 366, 950, 511, 766, 502, 516, 589, 299, 331, 571, 105, 609,
+ 462, 485, 538, 594, 470, 640, 393, 432, 325, 604, 259, 355,
+ 336, 454, 33, 263, 182, -24, -124, 121, -231, -50, -93, 49,
+ -230, -101, 25, -196, -123, -21, -160, -161, -132, -136, -155, -268,
+ -121, -146, -311, -112, -328, -201, -220, -280, -214, -304, -233, -227,
+ -250, -242, -334, -256, -283, -393, -267, -285, -332, -331, -224, -295,
+ -279, -171, -151, -361, -199, -239, -227, -317, -168, -211, -370, -182,
+ -150, -343, -204, -192, -224, -319, -203, -137, -357, -146, -223, -237,
+ -276, -162, -201, -262, -186, -209, -147, -267, -46, -252, -162, -146,
+ -182, -199, -143, -218, -204, -108, -250, -163, -90, -241, -137, -125,
+ -122, -272, -111, -188, -211, -160, -160, -164, -201, -231, -114, -175,
+ -180, -212, -172, -181, -129, -130, -77, -150, -114, -39, 12, -122,
+ -9, -11, -46, -21, -4, 7, -73, -20, -50, -21, -80, -102,
+ 2, -103, -66, -67, -8, -41, -119, -26, 29, -208, 3, -108,
+ -34, -228, 114, -196, -84, -28, 1, -109, -92, 77, -169, -27,
+ 48, -12, -79, 3, -100, -44, -53, 13, -103, -90, -14, -91,
+ -23, -64, -21, -116}};
+
+struct Elevation {
+ /**
+ * An array of |count| impulse responses of 256 samples for the left ear.
+ * The impulse responses in each elevation are at equally spaced azimuths
+ * for a full 360 degree revolution, ordered clockwise from in front the
+ * listener.
+ */
+ const int16_t (*azimuths)[256];
+ int count;
+};
+
+/**
+ * irc_composite_c_r0195 is an array with each element containing data for one
+ * elevation.
+ */
+const Elevation irc_composite_c_r0195[] = {
+ {irc_composite_c_r0195_p315, MOZ_ARRAY_LENGTH(irc_composite_c_r0195_p315)},
+ {irc_composite_c_r0195_p330, MOZ_ARRAY_LENGTH(irc_composite_c_r0195_p330)},
+ {irc_composite_c_r0195_p345, MOZ_ARRAY_LENGTH(irc_composite_c_r0195_p345)},
+ {irc_composite_c_r0195_p000, MOZ_ARRAY_LENGTH(irc_composite_c_r0195_p000)},
+ {irc_composite_c_r0195_p015, MOZ_ARRAY_LENGTH(irc_composite_c_r0195_p015)},
+ {irc_composite_c_r0195_p030, MOZ_ARRAY_LENGTH(irc_composite_c_r0195_p030)},
+ {irc_composite_c_r0195_p045, MOZ_ARRAY_LENGTH(irc_composite_c_r0195_p045)},
+ {irc_composite_c_r0195_p060, MOZ_ARRAY_LENGTH(irc_composite_c_r0195_p060)},
+ {irc_composite_c_r0195_p075, MOZ_ARRAY_LENGTH(irc_composite_c_r0195_p075)},
+ {irc_composite_c_r0195_p090, MOZ_ARRAY_LENGTH(irc_composite_c_r0195_p090)}};
+
+const int irc_composite_c_r0195_first_elevation = -45; /* degrees */
+const int irc_composite_c_r0195_elevation_interval = 15; /* degrees */
+const int irc_composite_c_r0195_sample_rate = 44100; /* Hz */
diff --git a/dom/media/webaudio/blink/PeriodicWave.cpp b/dom/media/webaudio/blink/PeriodicWave.cpp
new file mode 100644
index 0000000000..6b1d173008
--- /dev/null
+++ b/dom/media/webaudio/blink/PeriodicWave.cpp
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "PeriodicWave.h"
+#include <algorithm>
+#include <cmath>
+#include <limits>
+#include "mozilla/FFTBlock.h"
+
+const unsigned MinPeriodicWaveSize = 4096; // This must be a power of two.
+const unsigned MaxPeriodicWaveSize = 8192; // This must be a power of two.
+const float CentsPerRange = 1200 / 3; // 1/3 Octave.
+
+using namespace mozilla;
+using mozilla::dom::OscillatorType;
+
+namespace WebCore {
+
+already_AddRefed<PeriodicWave> PeriodicWave::create(float sampleRate,
+ const float* real,
+ const float* imag,
+ size_t numberOfComponents,
+ bool disableNormalization) {
+ bool isGood = real && imag && numberOfComponents > 0;
+ MOZ_ASSERT(isGood);
+ if (isGood) {
+ RefPtr<PeriodicWave> periodicWave =
+ new PeriodicWave(sampleRate, numberOfComponents, disableNormalization);
+
+ // Limit the number of components used to those for frequencies below the
+ // Nyquist of the fixed length inverse FFT.
+ size_t halfSize = periodicWave->m_periodicWaveSize / 2;
+ numberOfComponents = std::min(numberOfComponents, halfSize);
+ periodicWave->m_numberOfComponents = numberOfComponents;
+ periodicWave->m_realComponents =
+ MakeUnique<AudioFloatArray>(numberOfComponents);
+ periodicWave->m_imagComponents =
+ MakeUnique<AudioFloatArray>(numberOfComponents);
+ memcpy(periodicWave->m_realComponents->Elements(), real,
+ numberOfComponents * sizeof(float));
+ memcpy(periodicWave->m_imagComponents->Elements(), imag,
+ numberOfComponents * sizeof(float));
+
+ return periodicWave.forget();
+ }
+ return nullptr;
+}
+
+already_AddRefed<PeriodicWave> PeriodicWave::createSine(float sampleRate) {
+ RefPtr<PeriodicWave> periodicWave =
+ new PeriodicWave(sampleRate, MinPeriodicWaveSize, false);
+ periodicWave->generateBasicWaveform(OscillatorType::Sine);
+ return periodicWave.forget();
+}
+
+already_AddRefed<PeriodicWave> PeriodicWave::createSquare(float sampleRate) {
+ RefPtr<PeriodicWave> periodicWave =
+ new PeriodicWave(sampleRate, MinPeriodicWaveSize, false);
+ periodicWave->generateBasicWaveform(OscillatorType::Square);
+ return periodicWave.forget();
+}
+
+already_AddRefed<PeriodicWave> PeriodicWave::createSawtooth(float sampleRate) {
+ RefPtr<PeriodicWave> periodicWave =
+ new PeriodicWave(sampleRate, MinPeriodicWaveSize, false);
+ periodicWave->generateBasicWaveform(OscillatorType::Sawtooth);
+ return periodicWave.forget();
+}
+
+already_AddRefed<PeriodicWave> PeriodicWave::createTriangle(float sampleRate) {
+ RefPtr<PeriodicWave> periodicWave =
+ new PeriodicWave(sampleRate, MinPeriodicWaveSize, false);
+ periodicWave->generateBasicWaveform(OscillatorType::Triangle);
+ return periodicWave.forget();
+}
+
+PeriodicWave::PeriodicWave(float sampleRate, size_t numberOfComponents,
+ bool disableNormalization)
+ : m_sampleRate(sampleRate),
+ m_centsPerRange(CentsPerRange),
+ m_maxPartialsInBandLimitedTable(0),
+ m_normalizationScale(1.0f),
+ m_disableNormalization(disableNormalization) {
+ float nyquist = 0.5 * m_sampleRate;
+
+ if (numberOfComponents <= MinPeriodicWaveSize) {
+ m_periodicWaveSize = MinPeriodicWaveSize;
+ } else {
+ unsigned npow2 = fdlibm_exp2f(floorf(
+ fdlibm_logf(numberOfComponents - 1.0) / fdlibm_logf(2.0f) + 1.0f));
+ m_periodicWaveSize = std::min(MaxPeriodicWaveSize, npow2);
+ }
+
+ m_numberOfRanges =
+ (unsigned)(3.0f * fdlibm_logf(m_periodicWaveSize) / fdlibm_logf(2.0f));
+ m_bandLimitedTables.SetLength(m_numberOfRanges);
+ m_lowestFundamentalFrequency = nyquist / maxNumberOfPartials();
+ m_rateScale = m_periodicWaveSize / m_sampleRate;
+}
+
+size_t PeriodicWave::sizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t amount = aMallocSizeOf(this);
+
+ amount += m_bandLimitedTables.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (size_t i = 0; i < m_bandLimitedTables.Length(); i++) {
+ if (m_bandLimitedTables[i]) {
+ amount +=
+ m_bandLimitedTables[i]->ShallowSizeOfIncludingThis(aMallocSizeOf);
+ }
+ }
+
+ return amount;
+}
+
+void PeriodicWave::waveDataForFundamentalFrequency(
+ float fundamentalFrequency, float*& lowerWaveData, float*& higherWaveData,
+ float& tableInterpolationFactor) {
+ // Negative frequencies are allowed, in which case we alias
+ // to the positive frequency.
+ fundamentalFrequency = fabsf(fundamentalFrequency);
+
+ // We only need to rebuild to the tables if the new fundamental
+ // frequency is low enough to allow for more partials below the
+ // Nyquist frequency.
+ unsigned numberOfPartials = numberOfPartialsForRange(0);
+ float nyquist = 0.5 * m_sampleRate;
+ if (fundamentalFrequency != 0.0) {
+ numberOfPartials =
+ std::min(numberOfPartials, (unsigned)(nyquist / fundamentalFrequency));
+ }
+ if (numberOfPartials > m_maxPartialsInBandLimitedTable) {
+ for (unsigned rangeIndex = 0; rangeIndex < m_numberOfRanges; ++rangeIndex) {
+ m_bandLimitedTables[rangeIndex] = 0;
+ }
+
+ // We need to create the first table to determine the normalization
+ // constant.
+ createBandLimitedTables(fundamentalFrequency, 0);
+ m_maxPartialsInBandLimitedTable = numberOfPartials;
+ }
+
+ // Calculate the pitch range.
+ float ratio = fundamentalFrequency > 0
+ ? fundamentalFrequency / m_lowestFundamentalFrequency
+ : 0.5;
+ float centsAboveLowestFrequency =
+ fdlibm_logf(ratio) / fdlibm_logf(2.0f) * 1200;
+
+ // Add one to round-up to the next range just in time to truncate
+ // partials before aliasing occurs.
+ float pitchRange = 1 + centsAboveLowestFrequency / m_centsPerRange;
+
+ pitchRange = std::max(pitchRange, 0.0f);
+ pitchRange = std::min(pitchRange, static_cast<float>(m_numberOfRanges - 1));
+
+ // The words "lower" and "higher" refer to the table data having
+ // the lower and higher numbers of partials. It's a little confusing
+ // since the range index gets larger the more partials we cull out.
+ // So the lower table data will have a larger range index.
+ unsigned rangeIndex1 = static_cast<unsigned>(pitchRange);
+ unsigned rangeIndex2 =
+ rangeIndex1 < m_numberOfRanges - 1 ? rangeIndex1 + 1 : rangeIndex1;
+
+ if (!m_bandLimitedTables[rangeIndex1].get())
+ createBandLimitedTables(fundamentalFrequency, rangeIndex1);
+
+ if (!m_bandLimitedTables[rangeIndex2].get())
+ createBandLimitedTables(fundamentalFrequency, rangeIndex2);
+
+ lowerWaveData = m_bandLimitedTables[rangeIndex2]->Elements();
+ higherWaveData = m_bandLimitedTables[rangeIndex1]->Elements();
+
+ // Ranges from 0 -> 1 to interpolate between lower -> higher.
+ tableInterpolationFactor = rangeIndex2 - pitchRange;
+}
+
+unsigned PeriodicWave::maxNumberOfPartials() const {
+ return m_periodicWaveSize / 2;
+}
+
+unsigned PeriodicWave::numberOfPartialsForRange(unsigned rangeIndex) const {
+ // Number of cents below nyquist where we cull partials.
+ float centsToCull = rangeIndex * m_centsPerRange;
+
+ // A value from 0 -> 1 representing what fraction of the partials to keep.
+ float cullingScale = fdlibm_exp2f(-centsToCull / 1200);
+
+ // The very top range will have all the partials culled.
+ unsigned numberOfPartials = cullingScale * maxNumberOfPartials();
+
+ return numberOfPartials;
+}
+
+// Convert into time-domain wave buffers.
+// One table is created for each range for non-aliasing playback
+// at different playback rates. Thus, higher ranges have more
+// high-frequency partials culled out.
+void PeriodicWave::createBandLimitedTables(float fundamentalFrequency,
+ unsigned rangeIndex) {
+ unsigned fftSize = m_periodicWaveSize;
+ unsigned i;
+
+ const float* realData = m_realComponents->Elements();
+ const float* imagData = m_imagComponents->Elements();
+
+ // This FFTBlock is used to cull partials (represented by frequency bins).
+ FFTBlock frame(fftSize);
+
+ // Find the starting bin where we should start culling the aliasing
+ // partials for this pitch range. We need to clear out the highest
+ // frequencies to band-limit the waveform.
+ unsigned numberOfPartials = numberOfPartialsForRange(rangeIndex);
+ // Also limit to the number of components that are provided.
+ numberOfPartials = std::min(numberOfPartials, m_numberOfComponents - 1);
+
+ // Limit number of partials to those below Nyquist frequency
+ float nyquist = 0.5 * m_sampleRate;
+ if (fundamentalFrequency != 0.0) {
+ numberOfPartials =
+ std::min(numberOfPartials, (unsigned)(nyquist / fundamentalFrequency));
+ }
+
+ // Copy from loaded frequency data and generate complex conjugate
+ // because of the way the inverse FFT is defined.
+ // The coefficients of higher partials remain zero, as initialized in
+ // the FFTBlock constructor.
+ for (i = 0; i < numberOfPartials + 1; ++i) {
+ frame.RealData(i) = realData[i];
+ frame.ImagData(i) = -imagData[i];
+ }
+
+ // Clear any DC-offset.
+ frame.RealData(0) = 0;
+ // Clear value which has no effect.
+ frame.ImagData(0) = 0;
+
+ // Create the band-limited table.
+ m_bandLimitedTables[rangeIndex] =
+ MakeUnique<AlignedAudioFloatArray>(m_periodicWaveSize);
+
+ // Apply an inverse FFT to generate the time-domain table data.
+ float* data = m_bandLimitedTables[rangeIndex]->Elements();
+ frame.GetInverseWithoutScaling(data);
+
+ // For the first range (which has the highest power), calculate
+ // its peak value then compute normalization scale.
+ if (m_disableNormalization) {
+ // See Bug 1424906, results need to be scaled by 0.5 even
+ // when normalization is disabled.
+ m_normalizationScale = 0.5;
+ } else if (!rangeIndex) {
+ float maxValue;
+ maxValue = AudioBufferPeakValue(data, m_periodicWaveSize);
+
+ if (maxValue) m_normalizationScale = 1.0f / maxValue;
+ }
+
+ // Apply normalization scale.
+ AudioBufferInPlaceScale(data, m_normalizationScale, m_periodicWaveSize);
+}
+
+void PeriodicWave::generateBasicWaveform(OscillatorType shape) {
+ const float piFloat = float(M_PI);
+ unsigned fftSize = periodicWaveSize();
+ unsigned halfSize = fftSize / 2;
+
+ m_numberOfComponents = halfSize;
+ m_realComponents = MakeUnique<AudioFloatArray>(halfSize);
+ m_imagComponents = MakeUnique<AudioFloatArray>(halfSize);
+ float* realP = m_realComponents->Elements();
+ float* imagP = m_imagComponents->Elements();
+
+ // Clear DC and imag value which is ignored.
+ realP[0] = 0;
+ imagP[0] = 0;
+
+ for (unsigned n = 1; n < halfSize; ++n) {
+ float omega = 2 * piFloat * n;
+ float invOmega = 1 / omega;
+
+ // Fourier coefficients according to standard definition.
+ float a; // Coefficient for cos().
+ float b; // Coefficient for sin().
+
+ // Calculate Fourier coefficients depending on the shape.
+ // Note that the overall scaling (magnitude) of the waveforms
+ // is normalized in createBandLimitedTables().
+ switch (shape) {
+ case OscillatorType::Sine:
+ // Standard sine wave function.
+ a = 0;
+ b = (n == 1) ? 1 : 0;
+ break;
+ case OscillatorType::Square:
+ // Square-shaped waveform with the first half its maximum value
+ // and the second half its minimum value.
+ a = 0;
+ b = invOmega * ((n & 1) ? 2 : 0);
+ break;
+ case OscillatorType::Sawtooth:
+ // Sawtooth-shaped waveform with the first half ramping from
+ // zero to maximum and the second half from minimum to zero.
+ a = 0;
+ b = -invOmega * fdlibm_cos(0.5 * omega);
+ break;
+ case OscillatorType::Triangle:
+ // Triangle-shaped waveform going from its maximum value to
+ // its minimum value then back to the maximum value.
+ a = 0;
+ if (n & 1) {
+ b = 2 * (2 / (n * piFloat) * 2 / (n * piFloat)) *
+ ((((n - 1) >> 1) & 1) ? -1 : 1);
+ } else {
+ b = 0;
+ }
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("invalid oscillator type");
+ a = 0;
+ b = 0;
+ break;
+ }
+
+ realP[n] = a;
+ imagP[n] = b;
+ }
+}
+
+} // namespace WebCore
diff --git a/dom/media/webaudio/blink/PeriodicWave.h b/dom/media/webaudio/blink/PeriodicWave.h
new file mode 100644
index 0000000000..fd4e50287c
--- /dev/null
+++ b/dom/media/webaudio/blink/PeriodicWave.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef PeriodicWave_h
+#define PeriodicWave_h
+
+#include "mozilla/dom/OscillatorNodeBinding.h"
+#include "mozilla/UniquePtr.h"
+#include <nsTArray.h>
+#include "AlignedTArray.h"
+#include "mozilla/MemoryReporting.h"
+
+namespace WebCore {
+
+typedef AlignedTArray<float> AlignedAudioFloatArray;
+typedef nsTArray<float> AudioFloatArray;
+
+using mozilla::UniquePtr;
+
+class PeriodicWave {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebCore::PeriodicWave);
+
+ static already_AddRefed<PeriodicWave> createSine(float sampleRate);
+ static already_AddRefed<PeriodicWave> createSquare(float sampleRate);
+ static already_AddRefed<PeriodicWave> createSawtooth(float sampleRate);
+ static already_AddRefed<PeriodicWave> createTriangle(float sampleRate);
+
+ // Creates an arbitrary periodic wave given the frequency components
+ // (Fourier coefficients).
+ static already_AddRefed<PeriodicWave> create(float sampleRate,
+ const float* real,
+ const float* imag,
+ size_t numberOfComponents,
+ bool disableNormalization);
+
+ // Returns pointers to the lower and higher wave data for the pitch range
+ // containing the given fundamental frequency. These two tables are in
+ // adjacent "pitch" ranges where the higher table will have the maximum
+ // number of partials which won't alias when played back at this
+ // fundamental frequency. The lower wave is the next range containing fewer
+ // partials than the higher wave. Interpolation between these two tables
+ // can be made according to tableInterpolationFactor. Where values
+ // from 0 -> 1 interpolate between lower -> higher.
+ void waveDataForFundamentalFrequency(float, float*& lowerWaveData,
+ float*& higherWaveData,
+ float& tableInterpolationFactor);
+
+ // Returns the scalar multiplier to the oscillator frequency to calculate
+ // wave buffer phase increment.
+ float rateScale() const { return m_rateScale; }
+
+ unsigned periodicWaveSize() const { return m_periodicWaveSize; }
+ float sampleRate() const { return m_sampleRate; }
+
+ size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ private:
+ explicit PeriodicWave(float sampleRate, size_t numberOfComponents,
+ bool disableNormalization);
+ ~PeriodicWave() = default;
+
+ void generateBasicWaveform(mozilla::dom::OscillatorType);
+
+ float m_sampleRate;
+ unsigned m_periodicWaveSize;
+ unsigned m_numberOfRanges;
+ float m_centsPerRange;
+ unsigned m_numberOfComponents;
+ UniquePtr<AudioFloatArray> m_realComponents;
+ UniquePtr<AudioFloatArray> m_imagComponents;
+
+ // The lowest frequency (in Hertz) where playback will include all of the
+ // partials. Playing back lower than this frequency will gradually lose
+ // more high-frequency information.
+ // This frequency is quite low (~10Hz @ // 44.1KHz)
+ float m_lowestFundamentalFrequency;
+
+ float m_rateScale;
+
+ unsigned numberOfRanges() const { return m_numberOfRanges; }
+
+ // Maximum possible number of partials (before culling).
+ unsigned maxNumberOfPartials() const;
+
+ unsigned numberOfPartialsForRange(unsigned rangeIndex) const;
+
+ // Creates table for specified index based on fundamental frequency.
+ void createBandLimitedTables(float fundamentalFrequency, unsigned rangeIndex);
+ unsigned m_maxPartialsInBandLimitedTable;
+ float m_normalizationScale;
+ bool m_disableNormalization;
+ nsTArray<UniquePtr<AlignedAudioFloatArray> > m_bandLimitedTables;
+};
+
+} // namespace WebCore
+
+#endif // PeriodicWave_h
diff --git a/dom/media/webaudio/blink/README b/dom/media/webaudio/blink/README
new file mode 100644
index 0000000000..96d209dfc8
--- /dev/null
+++ b/dom/media/webaudio/blink/README
@@ -0,0 +1,24 @@
+This directory contains the code originally borrowed from the Blink Web Audio
+implementation. We are forking the code here because in many cases the burden
+of adopting Blink specific utilities is too large compared to the prospect of
+importing upstream fixes by just copying newer versions of the code in the
+future.
+
+The process of borrowing code from Blink is as follows:
+
+* Try to borrow utility classes only, and avoid borrowing code which depends
+ too much on the Blink specific utilities.
+* First, import the pristine files from the Blink repository before adding
+ them to the build system, noting the SVN revision of Blink from which the
+ original files were copied in the commit message.
+* In a separate commit, add the imported source files to the build system,
+ and apply the necessary changes to make it build successfully.
+* Use the code in a separate commit.
+* Never add headers as exported headers. All headers should be included
+ using the following convention: #include "blink/Header.h".
+* Leave the imported code in the WebCore namespace, and import the needed
+ names into the Mozilla code via `using'.
+* Cherry-pick upsteam fixes manually when needed. In case you fix a problem
+ that is not Mozilla specific locally, try to upstream your changes into
+ Blink.
+* Ping ehsan for any questions.
diff --git a/dom/media/webaudio/blink/Reverb.cpp b/dom/media/webaudio/blink/Reverb.cpp
new file mode 100644
index 0000000000..dfce966ca9
--- /dev/null
+++ b/dom/media/webaudio/blink/Reverb.cpp
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "Reverb.h"
+#include "ReverbConvolverStage.h"
+
+#include <math.h>
+#include "ReverbConvolver.h"
+#include "mozilla/FloatingPoint.h"
+
+using namespace mozilla;
+
+namespace WebCore {
+
+// Empirical gain calibration tested across many impulse responses to ensure
+// perceived volume is same as dry (unprocessed) signal
+const float GainCalibration = 0.00125f;
+const float GainCalibrationSampleRate = 44100;
+
+// A minimum power value to when normalizing a silent (or very quiet) impulse
+// response
+const float MinPower = 0.000125f;
+
+static float calculateNormalizationScale(const nsTArray<const float*>& response,
+ size_t aLength, float sampleRate) {
+ // Normalize by RMS power
+ size_t numberOfChannels = response.Length();
+
+ float power = 0;
+
+ for (size_t i = 0; i < numberOfChannels; ++i) {
+ float channelPower = AudioBufferSumOfSquares(response[i], aLength);
+ power += channelPower;
+ }
+
+ power = sqrt(power / (numberOfChannels * aLength));
+
+ // Protect against accidental overload
+ if (!std::isfinite(power) || std::isnan(power) || power < MinPower)
+ power = MinPower;
+
+ float scale = 1 / power;
+
+ scale *= GainCalibration; // calibrate to make perceived volume same as
+ // unprocessed
+
+ // Scale depends on sample-rate.
+ if (sampleRate) scale *= GainCalibrationSampleRate / sampleRate;
+
+ // True-stereo compensation
+ if (numberOfChannels == 4) scale *= 0.5f;
+
+ return scale;
+}
+
+Reverb::Reverb(const AudioChunk& impulseResponse, size_t maxFFTSize,
+ bool useBackgroundThreads, bool normalize, float sampleRate,
+ bool* aAllocationFailure) {
+ MOZ_ASSERT(aAllocationFailure);
+ size_t impulseResponseBufferLength = impulseResponse.mDuration;
+ float scale = impulseResponse.mVolume;
+
+ CopyableAutoTArray<const float*, 4> irChannels;
+ irChannels.AppendElements(impulseResponse.ChannelData<float>());
+ AutoTArray<float, 1024> tempBuf;
+
+ if (normalize) {
+ scale = calculateNormalizationScale(irChannels, impulseResponseBufferLength,
+ sampleRate);
+ }
+
+ if (scale != 1.0f) {
+ bool rv = tempBuf.SetLength(
+ irChannels.Length() * impulseResponseBufferLength, mozilla::fallible);
+ *aAllocationFailure = !rv;
+ if (*aAllocationFailure) {
+ return;
+ }
+
+ for (uint32_t i = 0; i < irChannels.Length(); ++i) {
+ float* buf = &tempBuf[i * impulseResponseBufferLength];
+ AudioBufferCopyWithScale(irChannels[i], scale, buf,
+ impulseResponseBufferLength);
+ irChannels[i] = buf;
+ }
+ }
+
+ *aAllocationFailure = !initialize(irChannels, impulseResponseBufferLength,
+ maxFFTSize, useBackgroundThreads);
+}
+
+size_t Reverb::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t amount = aMallocSizeOf(this);
+ amount += m_convolvers.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (size_t i = 0; i < m_convolvers.Length(); i++) {
+ if (m_convolvers[i]) {
+ amount += m_convolvers[i]->sizeOfIncludingThis(aMallocSizeOf);
+ }
+ }
+
+ amount += m_tempBuffer.SizeOfExcludingThis(aMallocSizeOf, false);
+ return amount;
+}
+
+bool Reverb::initialize(const nsTArray<const float*>& impulseResponseBuffer,
+ size_t impulseResponseBufferLength, size_t maxFFTSize,
+ bool useBackgroundThreads) {
+ m_impulseResponseLength = impulseResponseBufferLength;
+
+ // The reverb can handle a mono impulse response and still do stereo
+ // processing
+ size_t numResponseChannels = impulseResponseBuffer.Length();
+ MOZ_ASSERT(numResponseChannels > 0);
+ // The number of convolvers required is at least the number of audio
+ // channels. Even if there is initially only one audio channel, another
+ // may be added later, and so a second convolver is created now while the
+ // impulse response is available.
+ size_t numConvolvers = std::max<size_t>(numResponseChannels, 2);
+ m_convolvers.SetCapacity(numConvolvers);
+
+ int convolverRenderPhase = 0;
+ for (size_t i = 0; i < numConvolvers; ++i) {
+ size_t channelIndex = i < numResponseChannels ? i : 0;
+ const float* channel = impulseResponseBuffer[channelIndex];
+ size_t length = impulseResponseBufferLength;
+
+ bool allocationFailure;
+ UniquePtr<ReverbConvolver> convolver(
+ new ReverbConvolver(channel, length, maxFFTSize, convolverRenderPhase,
+ useBackgroundThreads, &allocationFailure));
+ if (allocationFailure) {
+ return false;
+ }
+ m_convolvers.AppendElement(std::move(convolver));
+
+ convolverRenderPhase += WEBAUDIO_BLOCK_SIZE;
+ }
+
+ // For "True" stereo processing we allocate a temporary buffer to avoid
+ // repeatedly allocating it in the process() method. It can be bad to allocate
+ // memory in a real-time thread.
+ if (numResponseChannels == 4) {
+ m_tempBuffer.AllocateChannels(2);
+ WriteZeroesToAudioBlock(&m_tempBuffer, 0, WEBAUDIO_BLOCK_SIZE);
+ }
+ return true;
+}
+
+void Reverb::process(const AudioBlock* sourceBus, AudioBlock* destinationBus) {
+ // Do a fairly comprehensive sanity check.
+ // If these conditions are satisfied, all of the source and destination
+ // pointers will be valid for the various matrixing cases.
+ bool isSafeToProcess =
+ sourceBus && destinationBus && sourceBus->ChannelCount() > 0 &&
+ destinationBus->mChannelData.Length() > 0 &&
+ WEBAUDIO_BLOCK_SIZE <= MaxFrameSize &&
+ WEBAUDIO_BLOCK_SIZE <= size_t(sourceBus->GetDuration()) &&
+ WEBAUDIO_BLOCK_SIZE <= size_t(destinationBus->GetDuration());
+
+ MOZ_ASSERT(isSafeToProcess);
+ if (!isSafeToProcess) return;
+
+ // For now only handle mono or stereo output
+ MOZ_ASSERT(destinationBus->ChannelCount() <= 2);
+
+ float* destinationChannelL =
+ static_cast<float*>(const_cast<void*>(destinationBus->mChannelData[0]));
+ const float* sourceBusL =
+ static_cast<const float*>(sourceBus->mChannelData[0]);
+
+ // Handle input -> output matrixing...
+ size_t numInputChannels = sourceBus->ChannelCount();
+ size_t numOutputChannels = destinationBus->ChannelCount();
+ size_t numReverbChannels = m_convolvers.Length();
+
+ if (numInputChannels == 2 && numReverbChannels == 2 &&
+ numOutputChannels == 2) {
+ // 2 -> 2 -> 2
+ const float* sourceBusR =
+ static_cast<const float*>(sourceBus->mChannelData[1]);
+ float* destinationChannelR =
+ static_cast<float*>(const_cast<void*>(destinationBus->mChannelData[1]));
+ m_convolvers[0]->process(sourceBusL, destinationChannelL);
+ m_convolvers[1]->process(sourceBusR, destinationChannelR);
+ } else if (numInputChannels == 1 && numOutputChannels == 2 &&
+ numReverbChannels == 2) {
+ // 1 -> 2 -> 2
+ for (int i = 0; i < 2; ++i) {
+ float* destinationChannel = static_cast<float*>(
+ const_cast<void*>(destinationBus->mChannelData[i]));
+ m_convolvers[i]->process(sourceBusL, destinationChannel);
+ }
+ } else if (numInputChannels == 1 && numOutputChannels == 1) {
+ // 1 -> 1 -> 1 (Only one of the convolvers is used.)
+ m_convolvers[0]->process(sourceBusL, destinationChannelL);
+ } else if (numInputChannels == 2 && numReverbChannels == 4 &&
+ numOutputChannels == 2) {
+ // 2 -> 4 -> 2 ("True" stereo)
+ const float* sourceBusR =
+ static_cast<const float*>(sourceBus->mChannelData[1]);
+ float* destinationChannelR =
+ static_cast<float*>(const_cast<void*>(destinationBus->mChannelData[1]));
+
+ float* tempChannelL =
+ static_cast<float*>(const_cast<void*>(m_tempBuffer.mChannelData[0]));
+ float* tempChannelR =
+ static_cast<float*>(const_cast<void*>(m_tempBuffer.mChannelData[1]));
+
+ // Process left virtual source
+ m_convolvers[0]->process(sourceBusL, destinationChannelL);
+ m_convolvers[1]->process(sourceBusL, destinationChannelR);
+
+ // Process right virtual source
+ m_convolvers[2]->process(sourceBusR, tempChannelL);
+ m_convolvers[3]->process(sourceBusR, tempChannelR);
+
+ AudioBufferAddWithScale(tempChannelL, 1.0f, destinationChannelL,
+ sourceBus->GetDuration());
+ AudioBufferAddWithScale(tempChannelR, 1.0f, destinationChannelR,
+ sourceBus->GetDuration());
+ } else if (numInputChannels == 1 && numReverbChannels == 4 &&
+ numOutputChannels == 2) {
+ // 1 -> 4 -> 2 (Processing mono with "True" stereo impulse response)
+ // This is an inefficient use of a four-channel impulse response, but we
+ // should handle the case.
+ float* destinationChannelR =
+ static_cast<float*>(const_cast<void*>(destinationBus->mChannelData[1]));
+
+ float* tempChannelL =
+ static_cast<float*>(const_cast<void*>(m_tempBuffer.mChannelData[0]));
+ float* tempChannelR =
+ static_cast<float*>(const_cast<void*>(m_tempBuffer.mChannelData[1]));
+
+ // Process left virtual source
+ m_convolvers[0]->process(sourceBusL, destinationChannelL);
+ m_convolvers[1]->process(sourceBusL, destinationChannelR);
+
+ // Process right virtual source
+ m_convolvers[2]->process(sourceBusL, tempChannelL);
+ m_convolvers[3]->process(sourceBusL, tempChannelR);
+
+ AudioBufferAddWithScale(tempChannelL, 1.0f, destinationChannelL,
+ sourceBus->GetDuration());
+ AudioBufferAddWithScale(tempChannelR, 1.0f, destinationChannelR,
+ sourceBus->GetDuration());
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unexpected Reverb configuration");
+ destinationBus->SetNull(destinationBus->GetDuration());
+ }
+}
+
+} // namespace WebCore
diff --git a/dom/media/webaudio/blink/Reverb.h b/dom/media/webaudio/blink/Reverb.h
new file mode 100644
index 0000000000..16459532a9
--- /dev/null
+++ b/dom/media/webaudio/blink/Reverb.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef Reverb_h
+#define Reverb_h
+
+#include "ReverbConvolver.h"
+#include "nsTArray.h"
+#include "AudioBlock.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/UniquePtr.h"
+
+namespace WebCore {
+
+using mozilla::UniquePtr;
+
+// Multi-channel convolution reverb with channel matrixing - one or more
+// ReverbConvolver objects are used internally.
+
+class Reverb {
+ public:
+ enum { MaxFrameSize = 256 };
+
+ // renderSliceSize is a rendering hint, so the FFTs can be optimized to not
+ // all occur at the same time (very bad when rendering on a real-time thread).
+ // aAllocation failure is to be checked by the caller. If false, internal
+ // memory could not be allocated, and this Reverb instance is not to be
+ // used.
+ Reverb(const mozilla::AudioChunk& impulseResponseBuffer, size_t maxFFTSize,
+ bool useBackgroundThreads, bool normalize, float sampleRate,
+ bool* aAllocationFailure);
+
+ void process(const mozilla::AudioBlock* sourceBus,
+ mozilla::AudioBlock* destinationBus);
+
+ size_t impulseResponseLength() const { return m_impulseResponseLength; }
+
+ size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ private:
+ bool initialize(const nsTArray<const float*>& impulseResponseBuffer,
+ size_t impulseResponseBufferLength, size_t maxFFTSize,
+ bool useBackgroundThreads);
+
+ size_t m_impulseResponseLength;
+
+ nsTArray<UniquePtr<ReverbConvolver> > m_convolvers;
+
+ // For "True" stereo processing
+ mozilla::AudioBlock m_tempBuffer;
+};
+
+} // namespace WebCore
+
+#endif // Reverb_h
diff --git a/dom/media/webaudio/blink/ReverbAccumulationBuffer.cpp b/dom/media/webaudio/blink/ReverbAccumulationBuffer.cpp
new file mode 100644
index 0000000000..ec745c294f
--- /dev/null
+++ b/dom/media/webaudio/blink/ReverbAccumulationBuffer.cpp
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ReverbAccumulationBuffer.h"
+#include "AudioNodeEngine.h"
+#include "mozilla/PodOperations.h"
+#include <algorithm>
+
+using namespace mozilla;
+
+namespace WebCore {
+
+ReverbAccumulationBuffer::ReverbAccumulationBuffer()
+ : m_readIndex(0), m_readTimeFrame(0) {}
+
+bool ReverbAccumulationBuffer::allocate(size_t length) {
+ if (!m_buffer.SetLength(length, fallible)) {
+ return false;
+ }
+ PodZero(m_buffer.Elements(), length);
+ return true;
+}
+
+void ReverbAccumulationBuffer::readAndClear(float* destination,
+ size_t numberOfFrames) {
+ size_t bufferLength = m_buffer.Length();
+ bool isCopySafe =
+ m_readIndex <= bufferLength && numberOfFrames <= bufferLength;
+
+ MOZ_ASSERT(isCopySafe);
+ if (!isCopySafe) return;
+
+ size_t framesAvailable = bufferLength - m_readIndex;
+ size_t numberOfFrames1 = std::min(numberOfFrames, framesAvailable);
+ size_t numberOfFrames2 = numberOfFrames - numberOfFrames1;
+
+ float* source = m_buffer.Elements();
+ memcpy(destination, source + m_readIndex, sizeof(float) * numberOfFrames1);
+ memset(source + m_readIndex, 0, sizeof(float) * numberOfFrames1);
+
+ // Handle wrap-around if necessary
+ if (numberOfFrames2 > 0) {
+ memcpy(destination + numberOfFrames1, source,
+ sizeof(float) * numberOfFrames2);
+ memset(source, 0, sizeof(float) * numberOfFrames2);
+ }
+
+ m_readIndex = (m_readIndex + numberOfFrames) % bufferLength;
+ m_readTimeFrame += numberOfFrames;
+}
+
+void ReverbAccumulationBuffer::accumulate(const float* source,
+ size_t numberOfFrames,
+ size_t* readIndex,
+ size_t delayFrames) {
+ size_t bufferLength = m_buffer.Length();
+
+ size_t writeIndex = (*readIndex + delayFrames) % bufferLength;
+
+ // Update caller's readIndex
+ *readIndex = (*readIndex + numberOfFrames) % bufferLength;
+
+ size_t framesAvailable = bufferLength - writeIndex;
+ size_t numberOfFrames1 = std::min(numberOfFrames, framesAvailable);
+ size_t numberOfFrames2 = numberOfFrames - numberOfFrames1;
+
+ float* destination = m_buffer.Elements();
+
+ bool isSafe = writeIndex <= bufferLength &&
+ numberOfFrames1 + writeIndex <= bufferLength &&
+ numberOfFrames2 <= bufferLength;
+ MOZ_ASSERT(isSafe);
+ if (!isSafe) return;
+
+ AudioBufferAddWithScale(source, 1.0f, destination + writeIndex,
+ numberOfFrames1);
+ if (numberOfFrames2 > 0) {
+ AudioBufferAddWithScale(source + numberOfFrames1, 1.0f, destination,
+ numberOfFrames2);
+ }
+}
+
+void ReverbAccumulationBuffer::reset() {
+ PodZero(m_buffer.Elements(), m_buffer.Length());
+ m_readIndex = 0;
+ m_readTimeFrame = 0;
+}
+
+} // namespace WebCore
diff --git a/dom/media/webaudio/blink/ReverbAccumulationBuffer.h b/dom/media/webaudio/blink/ReverbAccumulationBuffer.h
new file mode 100644
index 0000000000..8883a56a73
--- /dev/null
+++ b/dom/media/webaudio/blink/ReverbAccumulationBuffer.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef ReverbAccumulationBuffer_h
+#define ReverbAccumulationBuffer_h
+
+#include "AlignedTArray.h"
+#include "mozilla/MemoryReporting.h"
+
+namespace WebCore {
+
+// ReverbAccumulationBuffer is a circular delay buffer with one client reading
+// from it and multiple clients writing/accumulating to it at different delay
+// offsets from the read position. The read operation will zero the memory just
+// read from the buffer, so it will be ready for accumulation the next time
+// around.
+class ReverbAccumulationBuffer {
+ public:
+ ReverbAccumulationBuffer();
+ // Returns false on failure.
+ bool allocate(size_t length);
+
+ // This will read from, then clear-out numberOfFrames
+ void readAndClear(float* destination, size_t numberOfFrames);
+
+ // Each ReverbConvolverStage will accumulate its output at the appropriate
+ // delay from the read position. We need to pass in and update readIndex here,
+ // since each ReverbConvolverStage may be running in a different thread than
+ // the realtime thread calling ReadAndClear() and maintaining m_readIndex
+ void accumulate(const float* source, size_t numberOfFrames, size_t* readIndex,
+ size_t delayFrames);
+
+ size_t readIndex() const { return m_readIndex; }
+
+ size_t readTimeFrame() const { return m_readTimeFrame; }
+
+ void reset();
+
+ size_t sizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return m_buffer.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ private:
+ AlignedTArray<float, 16> m_buffer;
+ size_t m_readIndex;
+ size_t m_readTimeFrame; // for debugging (frame on continuous timeline)
+};
+
+} // namespace WebCore
+
+#endif // ReverbAccumulationBuffer_h
diff --git a/dom/media/webaudio/blink/ReverbConvolver.cpp b/dom/media/webaudio/blink/ReverbConvolver.cpp
new file mode 100644
index 0000000000..dcaf6e7467
--- /dev/null
+++ b/dom/media/webaudio/blink/ReverbConvolver.cpp
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ReverbConvolver.h"
+#include "ReverbConvolverStage.h"
+
+using namespace mozilla;
+
+namespace WebCore {
+
+const int InputBufferSize = 8 * 16384;
+
+// We only process the leading portion of the impulse response in the real-time
+// thread. We don't exceed this length. It turns out then, that the background
+// thread has about 278msec of scheduling slop. Empirically, this has been found
+// to be a good compromise between giving enough time for scheduling slop, while
+// still minimizing the amount of processing done in the primary (high-priority)
+// thread. This was found to be a good value on Mac OS X, and may work well on
+// other platforms as well, assuming the very rough scheduling latencies are
+// similar on these time-scales. Of course, this code may need to be tuned for
+// individual platforms if this assumption is found to be incorrect.
+const size_t RealtimeFrameLimit = 8192 + 4096 // ~278msec @ 44.1KHz
+ - WEBAUDIO_BLOCK_SIZE;
+// First stage will have size MinFFTSize - successive stages will double in
+// size each time until we hit the maximum size.
+const size_t MinFFTSize = 256;
+// If we are using background threads then don't exceed this FFT size for the
+// stages which run in the real-time thread. This avoids having only one or
+// two large stages (size 16384 or so) at the end which take a lot of time
+// every several processing slices. This way we amortize the cost over more
+// processing slices.
+const size_t MaxRealtimeFFTSize = 4096;
+
+ReverbConvolver::ReverbConvolver(const float* impulseResponseData,
+ size_t impulseResponseLength,
+ size_t maxFFTSize, size_t convolverRenderPhase,
+ bool useBackgroundThreads,
+ bool* aAllocationFailure)
+ : m_impulseResponseLength(impulseResponseLength),
+ m_inputBuffer(InputBufferSize),
+ m_backgroundThread("ConvolverWorker"),
+ m_backgroundThreadMonitor("ConvolverMonitor"),
+ m_useBackgroundThreads(useBackgroundThreads),
+ m_wantsToExit(false),
+ m_moreInputBuffered(false) {
+ *aAllocationFailure = !m_accumulationBuffer.allocate(impulseResponseLength +
+ WEBAUDIO_BLOCK_SIZE);
+ if (*aAllocationFailure) {
+ return;
+ }
+ // For the moment, a good way to know if we have real-time constraint is to
+ // check if we're using background threads. Otherwise, assume we're being run
+ // from a command-line tool.
+ bool hasRealtimeConstraint = useBackgroundThreads;
+
+ const float* response = impulseResponseData;
+ size_t totalResponseLength = impulseResponseLength;
+
+ // The total latency is zero because the first FFT stage is small enough
+ // to return output in the first block.
+ size_t reverbTotalLatency = 0;
+
+ size_t stageOffset = 0;
+ size_t stagePhase = 0;
+ size_t fftSize = MinFFTSize;
+ while (stageOffset < totalResponseLength) {
+ size_t stageSize = fftSize / 2;
+
+ // For the last stage, it's possible that stageOffset is such that we're
+ // straddling the end of the impulse response buffer (if we use stageSize),
+ // so reduce the last stage's length...
+ if (stageSize + stageOffset > totalResponseLength) {
+ stageSize = totalResponseLength - stageOffset;
+ // Use smallest FFT that is large enough to cover the last stage.
+ fftSize = MinFFTSize;
+ while (stageSize * 2 > fftSize) {
+ fftSize *= 2;
+ }
+ }
+
+ // This "staggers" the time when each FFT happens so they don't all happen
+ // at the same time
+ int renderPhase = convolverRenderPhase + stagePhase;
+
+ UniquePtr<ReverbConvolverStage> stage(new ReverbConvolverStage(
+ response, totalResponseLength, reverbTotalLatency, stageOffset,
+ stageSize, fftSize, renderPhase, &m_accumulationBuffer));
+
+ bool isBackgroundStage = false;
+
+ if (this->useBackgroundThreads() && stageOffset > RealtimeFrameLimit) {
+ m_backgroundStages.AppendElement(std::move(stage));
+ isBackgroundStage = true;
+ } else
+ m_stages.AppendElement(std::move(stage));
+
+ // Figure out next FFT size
+ fftSize *= 2;
+
+ stageOffset += stageSize;
+
+ if (hasRealtimeConstraint && !isBackgroundStage &&
+ fftSize > MaxRealtimeFFTSize) {
+ fftSize = MaxRealtimeFFTSize;
+ // Custom phase positions for all but the first of the realtime
+ // stages of largest size. These spread out the work of the
+ // larger realtime stages. None of the FFTs of size 1024, 2048 or
+ // 4096 are performed when processing the same block. The first
+ // MaxRealtimeFFTSize = 4096 stage, at the end of the doubling,
+ // performs its FFT at block 7. The FFTs of size 2048 are
+ // performed in blocks 3 + 8 * n and size 1024 at 1 + 4 * n.
+ const uint32_t phaseLookup[] = {14, 0, 10, 4};
+ stagePhase = WEBAUDIO_BLOCK_SIZE *
+ phaseLookup[m_stages.Length() % ArrayLength(phaseLookup)];
+ } else if (fftSize > maxFFTSize) {
+ fftSize = maxFFTSize;
+ // A prime offset spreads out FFTs in a way that all
+ // available phase positions will be used if there are sufficient
+ // stages.
+ stagePhase += 5 * WEBAUDIO_BLOCK_SIZE;
+ } else if (stageSize > WEBAUDIO_BLOCK_SIZE) {
+ // As the stages are doubling in size, the next FFT will occur
+ // mid-way between FFTs for this stage.
+ stagePhase = stageSize - WEBAUDIO_BLOCK_SIZE;
+ }
+ }
+
+ // Start up background thread
+ // FIXME: would be better to up the thread priority here. It doesn't need to
+ // be real-time, but higher than the default...
+ if (this->useBackgroundThreads() && m_backgroundStages.Length() > 0) {
+ if (!m_backgroundThread.Start()) {
+ NS_WARNING("Cannot start convolver thread.");
+ return;
+ }
+ m_backgroundThread.message_loop()->PostTask(NewNonOwningRunnableMethod(
+ "WebCore::ReverbConvolver::backgroundThreadEntry", this,
+ &ReverbConvolver::backgroundThreadEntry));
+ }
+}
+
+ReverbConvolver::~ReverbConvolver() {
+ // Wait for background thread to stop
+ if (useBackgroundThreads() && m_backgroundThread.IsRunning()) {
+ m_wantsToExit = true;
+
+ // Wake up thread so it can return
+ {
+ MonitorAutoLock locker(m_backgroundThreadMonitor);
+ m_moreInputBuffered = true;
+ m_backgroundThreadMonitor.Notify();
+ }
+
+ m_backgroundThread.Stop();
+ }
+}
+
+size_t ReverbConvolver::sizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t amount = aMallocSizeOf(this);
+ amount += m_stages.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (size_t i = 0; i < m_stages.Length(); i++) {
+ if (m_stages[i]) {
+ amount += m_stages[i]->sizeOfIncludingThis(aMallocSizeOf);
+ }
+ }
+
+ amount += m_backgroundStages.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (size_t i = 0; i < m_backgroundStages.Length(); i++) {
+ if (m_backgroundStages[i]) {
+ amount += m_backgroundStages[i]->sizeOfIncludingThis(aMallocSizeOf);
+ }
+ }
+
+ // NB: The buffer sizes are static, so even though they might be accessed
+ // in another thread it's safe to measure them.
+ amount += m_accumulationBuffer.sizeOfExcludingThis(aMallocSizeOf);
+ amount += m_inputBuffer.sizeOfExcludingThis(aMallocSizeOf);
+
+ // Possible future measurements:
+ // - m_backgroundThread
+ // - m_backgroundThreadMonitor
+ return amount;
+}
+
+void ReverbConvolver::backgroundThreadEntry() {
+ while (!m_wantsToExit) {
+ // Wait for realtime thread to give us more input
+ m_moreInputBuffered = false;
+ {
+ MonitorAutoLock locker(m_backgroundThreadMonitor);
+ while (!m_moreInputBuffered && !m_wantsToExit)
+ m_backgroundThreadMonitor.Wait();
+ }
+
+ // Process all of the stages until their read indices reach the input
+ // buffer's write index
+ int writeIndex = m_inputBuffer.writeIndex();
+
+ // Even though it doesn't seem like every stage needs to maintain its own
+ // version of readIndex we do this in case we want to run in more than one
+ // background thread.
+ int readIndex;
+
+ while ((readIndex = m_backgroundStages[0]->inputReadIndex()) !=
+ writeIndex) { // FIXME: do better to detect buffer overrun...
+ // Accumulate contributions from each stage
+ for (size_t i = 0; i < m_backgroundStages.Length(); ++i)
+ m_backgroundStages[i]->processInBackground(this);
+ }
+ }
+}
+
+void ReverbConvolver::process(const float* sourceChannelData,
+ float* destinationChannelData) {
+ const float* source = sourceChannelData;
+ float* destination = destinationChannelData;
+ bool isDataSafe = source && destination;
+ MOZ_ASSERT(isDataSafe);
+ if (!isDataSafe) return;
+
+ // Feed input buffer (read by all threads)
+ m_inputBuffer.write(source, WEBAUDIO_BLOCK_SIZE);
+
+ // Accumulate contributions from each stage
+ for (size_t i = 0; i < m_stages.Length(); ++i) m_stages[i]->process(source);
+
+ // Finally read from accumulation buffer
+ m_accumulationBuffer.readAndClear(destination, WEBAUDIO_BLOCK_SIZE);
+
+ // Now that we've buffered more input, wake up our background thread.
+
+ // Not using a MonitorAutoLock looks strange, but we use a TryLock() instead
+ // because this is run on the real-time thread where it is a disaster for the
+ // lock to be contended (causes audio glitching). It's OK if we fail to
+ // signal from time to time, since we'll get to it the next time we're called.
+ // We're called repeatedly and frequently (around every 3ms). The background
+ // thread is processing well into the future and has a considerable amount of
+ // leeway here...
+ if (m_backgroundThreadMonitor.TryLock()) {
+ m_moreInputBuffered = true;
+ m_backgroundThreadMonitor.Notify();
+ m_backgroundThreadMonitor.Unlock();
+ }
+}
+
+} // namespace WebCore
diff --git a/dom/media/webaudio/blink/ReverbConvolver.h b/dom/media/webaudio/blink/ReverbConvolver.h
new file mode 100644
index 0000000000..8000ba811e
--- /dev/null
+++ b/dom/media/webaudio/blink/ReverbConvolver.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef ReverbConvolver_h
+#define ReverbConvolver_h
+
+#include "ReverbAccumulationBuffer.h"
+#include "ReverbInputBuffer.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/UniquePtr.h"
+#ifdef LOG
+# undef LOG
+#endif
+#include "base/thread.h"
+#include <atomic>
+
+namespace WebCore {
+
+using mozilla::UniquePtr;
+
+class ReverbConvolverStage;
+
+class ReverbConvolver {
+ public:
+ // maxFFTSize can be adjusted (from say 2048 to 32768) depending on how much
+ // precision is necessary. For certain tweaky de-convolving applications the
+ // phase errors add up quickly and lead to non-sensical results with larger
+ // FFT sizes and single-precision floats. In these cases 2048 is a good size.
+ // If not doing multi-threaded convolution, then should not go > 8192.
+ ReverbConvolver(const float* impulseResponseData,
+ size_t impulseResponseLength, size_t maxFFTSize,
+ size_t convolverRenderPhase, bool useBackgroundThreads,
+ bool* aAllocationFailure);
+ ~ReverbConvolver();
+
+ void process(const float* sourceChannelData, float* destinationChannelData);
+
+ size_t impulseResponseLength() const { return m_impulseResponseLength; }
+
+ ReverbInputBuffer* inputBuffer() { return &m_inputBuffer; }
+
+ bool useBackgroundThreads() const { return m_useBackgroundThreads; }
+ void backgroundThreadEntry();
+
+ size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ private:
+ nsTArray<UniquePtr<ReverbConvolverStage> > m_stages;
+ nsTArray<UniquePtr<ReverbConvolverStage> > m_backgroundStages;
+ size_t m_impulseResponseLength;
+
+ ReverbAccumulationBuffer m_accumulationBuffer;
+
+ // One or more background threads read from this input buffer which is fed
+ // from the realtime thread.
+ ReverbInputBuffer m_inputBuffer;
+
+ // Background thread and synchronization
+ base::Thread m_backgroundThread;
+ mozilla::Monitor m_backgroundThreadMonitor MOZ_UNANNOTATED;
+ bool m_useBackgroundThreads;
+ std::atomic<bool> m_wantsToExit;
+ std::atomic<bool> m_moreInputBuffered;
+};
+
+} // namespace WebCore
+
+#endif // ReverbConvolver_h
diff --git a/dom/media/webaudio/blink/ReverbConvolverStage.cpp b/dom/media/webaudio/blink/ReverbConvolverStage.cpp
new file mode 100644
index 0000000000..56354f0271
--- /dev/null
+++ b/dom/media/webaudio/blink/ReverbConvolverStage.cpp
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ReverbConvolverStage.h"
+
+#include "ReverbAccumulationBuffer.h"
+#include "ReverbConvolver.h"
+#include "ReverbInputBuffer.h"
+#include "mozilla/PodOperations.h"
+
+using namespace mozilla;
+
+namespace WebCore {
+
+ReverbConvolverStage::ReverbConvolverStage(
+ const float* impulseResponse, size_t, size_t reverbTotalLatency,
+ size_t stageOffset, size_t stageLength, size_t fftSize, size_t renderPhase,
+ ReverbAccumulationBuffer* accumulationBuffer)
+ : m_accumulationBuffer(accumulationBuffer),
+ m_accumulationReadIndex(0),
+ m_inputReadIndex(0) {
+ MOZ_ASSERT(impulseResponse);
+ MOZ_ASSERT(accumulationBuffer);
+
+ m_fftKernel = MakeUnique<FFTBlock>(fftSize);
+ m_fftKernel->PadAndMakeScaledDFT(impulseResponse + stageOffset, stageLength);
+ m_fftConvolver = MakeUnique<FFTConvolver>(fftSize, renderPhase);
+
+ // The convolution stage at offset stageOffset needs to have a corresponding
+ // delay to cancel out the offset.
+ size_t totalDelay = stageOffset + reverbTotalLatency;
+
+ // But, the FFT convolution itself incurs latency, so subtract this out...
+ size_t fftLatency = m_fftConvolver->latencyFrames();
+ MOZ_ASSERT(totalDelay >= fftLatency);
+ totalDelay -= fftLatency;
+
+ m_postDelayLength = totalDelay;
+}
+
+size_t ReverbConvolverStage::sizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t amount = aMallocSizeOf(this);
+
+ if (m_fftKernel) {
+ amount += m_fftKernel->SizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ if (m_fftConvolver) {
+ amount += m_fftConvolver->sizeOfIncludingThis(aMallocSizeOf);
+ }
+
+ return amount;
+}
+
+void ReverbConvolverStage::processInBackground(ReverbConvolver* convolver) {
+ ReverbInputBuffer* inputBuffer = convolver->inputBuffer();
+ float* source =
+ inputBuffer->directReadFrom(&m_inputReadIndex, WEBAUDIO_BLOCK_SIZE);
+ process(source);
+}
+
+void ReverbConvolverStage::process(const float* source) {
+ MOZ_ASSERT(source);
+ if (!source) return;
+
+ // Now, run the convolution (into the delay buffer).
+ // An expensive FFT will happen every fftSize / 2 frames.
+ const float* output = m_fftConvolver->process(m_fftKernel.get(), source);
+
+ // Now accumulate into reverb's accumulation buffer.
+ m_accumulationBuffer->accumulate(output, WEBAUDIO_BLOCK_SIZE,
+ &m_accumulationReadIndex, m_postDelayLength);
+}
+
+} // namespace WebCore
diff --git a/dom/media/webaudio/blink/ReverbConvolverStage.h b/dom/media/webaudio/blink/ReverbConvolverStage.h
new file mode 100644
index 0000000000..f770e76d45
--- /dev/null
+++ b/dom/media/webaudio/blink/ReverbConvolverStage.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef ReverbConvolverStage_h
+#define ReverbConvolverStage_h
+
+#include "FFTConvolver.h"
+
+#include "nsTArray.h"
+#include "mozilla/FFTBlock.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/UniquePtr.h"
+
+namespace WebCore {
+
+using mozilla::FFTBlock;
+
+class ReverbAccumulationBuffer;
+class ReverbConvolver;
+
+// A ReverbConvolverStage represents the convolution associated with a
+// sub-section of a large impulse response. It incorporates a delay line to
+// account for the offset of the sub-section within the larger impulse response.
+class ReverbConvolverStage {
+ public:
+ // renderPhase is useful to know so that we can manipulate the pre versus post
+ // delay so that stages will perform their heavy work (FFT processing) on
+ // different slices to balance the load in a real-time thread.
+ ReverbConvolverStage(const float* impulseResponse, size_t responseLength,
+ size_t reverbTotalLatency, size_t stageOffset,
+ size_t stageLength, size_t fftSize, size_t renderPhase,
+ ReverbAccumulationBuffer*);
+
+ // |source| must point to an array of WEBAUDIO_BLOCK_SIZE elements.
+ void process(const float* source);
+
+ void processInBackground(ReverbConvolver* convolver);
+
+ // Useful for background processing
+ int inputReadIndex() const { return m_inputReadIndex; }
+
+ size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ private:
+ mozilla::UniquePtr<FFTBlock> m_fftKernel;
+ mozilla::UniquePtr<FFTConvolver> m_fftConvolver;
+
+ ReverbAccumulationBuffer* m_accumulationBuffer;
+ size_t m_accumulationReadIndex;
+ int m_inputReadIndex;
+
+ size_t m_postDelayLength;
+
+ nsTArray<float> m_temporaryBuffer;
+};
+
+} // namespace WebCore
+
+#endif // ReverbConvolverStage_h
diff --git a/dom/media/webaudio/blink/ReverbInputBuffer.cpp b/dom/media/webaudio/blink/ReverbInputBuffer.cpp
new file mode 100644
index 0000000000..d6a69263c9
--- /dev/null
+++ b/dom/media/webaudio/blink/ReverbInputBuffer.cpp
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ReverbInputBuffer.h"
+#include "mozilla/PodOperations.h"
+
+using namespace mozilla;
+
+namespace WebCore {
+
+ReverbInputBuffer::ReverbInputBuffer(size_t length) : m_writeIndex(0) {
+ m_buffer.SetLength(length);
+ PodZero(m_buffer.Elements(), length);
+}
+
+void ReverbInputBuffer::write(const float* sourceP, size_t numberOfFrames) {
+ // m_writeIndex is atomic and checked by other threads, so only touch
+ // it at the start and end.
+ size_t bufferLength = m_buffer.Length();
+ size_t index = m_writeIndex;
+ size_t newIndex = index + numberOfFrames;
+
+ MOZ_RELEASE_ASSERT(newIndex <= bufferLength);
+
+ memcpy(m_buffer.Elements() + index, sourceP, sizeof(float) * numberOfFrames);
+
+ if (newIndex >= bufferLength) {
+ newIndex = 0;
+ }
+ m_writeIndex = newIndex;
+}
+
+float* ReverbInputBuffer::directReadFrom(int* readIndex,
+ size_t numberOfFrames) {
+ size_t bufferLength = m_buffer.Length();
+ bool isPointerGood = readIndex && *readIndex >= 0 &&
+ *readIndex + numberOfFrames <= bufferLength;
+ MOZ_ASSERT(isPointerGood);
+ if (!isPointerGood) {
+ // Should never happen in practice but return pointer to start of buffer
+ // (avoid crash)
+ if (readIndex) *readIndex = 0;
+ return m_buffer.Elements();
+ }
+
+ float* sourceP = m_buffer.Elements();
+ float* p = sourceP + *readIndex;
+
+ // Update readIndex
+ *readIndex = (*readIndex + numberOfFrames) % bufferLength;
+
+ return p;
+}
+
+void ReverbInputBuffer::reset() {
+ PodZero(m_buffer.Elements(), m_buffer.Length());
+ m_writeIndex = 0;
+}
+
+} // namespace WebCore
diff --git a/dom/media/webaudio/blink/ReverbInputBuffer.h b/dom/media/webaudio/blink/ReverbInputBuffer.h
new file mode 100644
index 0000000000..93ef97a010
--- /dev/null
+++ b/dom/media/webaudio/blink/ReverbInputBuffer.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef ReverbInputBuffer_h
+#define ReverbInputBuffer_h
+
+#include "nsTArray.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/MemoryReporting.h"
+
+namespace WebCore {
+
+// ReverbInputBuffer is used to buffer input samples for deferred processing by
+// the background threads.
+class ReverbInputBuffer {
+ public:
+ explicit ReverbInputBuffer(size_t length);
+
+ // The realtime audio thread keeps writing samples here.
+ // The assumption is that the buffer's length is evenly divisible by
+ // numberOfFrames (for nearly all cases this will be fine).
+ // FIXME: remove numberOfFrames restriction...
+ void write(const float* sourceP, size_t numberOfFrames);
+
+ // Background threads can call this to check if there's anything to read...
+ size_t writeIndex() const { return m_writeIndex; }
+
+ // The individual background threads read here (and hope that they can keep up
+ // with the buffer writing). readIndex is updated with the next readIndex to
+ // read from... The assumption is that the buffer's length is evenly divisible
+ // by numberOfFrames.
+ // FIXME: remove numberOfFrames restriction...
+ float* directReadFrom(int* readIndex, size_t numberOfFrames);
+
+ void reset();
+
+ size_t sizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return m_buffer.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ private:
+ nsTArray<float> m_buffer;
+ mozilla::Atomic<size_t, mozilla::ReleaseAcquire> m_writeIndex;
+};
+
+} // namespace WebCore
+
+#endif // ReverbInputBuffer_h
diff --git a/dom/media/webaudio/blink/ZeroPole.cpp b/dom/media/webaudio/blink/ZeroPole.cpp
new file mode 100644
index 0000000000..4d0888d758
--- /dev/null
+++ b/dom/media/webaudio/blink/ZeroPole.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2011 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "ZeroPole.h"
+
+#include "DenormalDisabler.h"
+
+#include <cmath>
+#include <float.h>
+
+namespace WebCore {
+
+void ZeroPole::process(const float* source, float* destination,
+ int framesToProcess) {
+ float zero = m_zero;
+ float pole = m_pole;
+
+ // Gain compensation to make 0dB @ 0Hz
+ const float k1 = 1 / (1 - zero);
+ const float k2 = 1 - pole;
+
+ // Member variables to locals.
+ float lastX = m_lastX;
+ float lastY = m_lastY;
+
+ for (int i = 0; i < framesToProcess; ++i) {
+ float input = source[i];
+
+ // Zero
+ float output1 = k1 * (input - zero * lastX);
+ lastX = input;
+
+ // Pole
+ float output2 = k2 * output1 + pole * lastY;
+ lastY = output2;
+
+ destination[i] = output2;
+ }
+
+// Locals to member variables. Flush denormals here so we don't
+// slow down the inner loop above.
+#ifndef HAVE_DENORMAL
+ if (lastX == 0.0f && lastY != 0.0f && fabsf(lastY) < FLT_MIN) {
+ // Flush future values to zero (until there is new input).
+ lastY = 0.0;
+
+ // Flush calculated values.
+ for (int i = framesToProcess; i-- && fabsf(destination[i]) < FLT_MIN;) {
+ destination[i] = 0.0f;
+ }
+ }
+#endif
+
+ m_lastX = lastX;
+ m_lastY = lastY;
+}
+
+} // namespace WebCore
diff --git a/dom/media/webaudio/blink/ZeroPole.h b/dom/media/webaudio/blink/ZeroPole.h
new file mode 100644
index 0000000000..07f3f04f53
--- /dev/null
+++ b/dom/media/webaudio/blink/ZeroPole.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2011 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
+ * its contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef ZeroPole_h
+#define ZeroPole_h
+
+namespace WebCore {
+
+// ZeroPole is a simple filter with one zero and one pole.
+
+class ZeroPole {
+ public:
+ ZeroPole() : m_zero(0), m_pole(0), m_lastX(0), m_lastY(0) {}
+
+ void process(const float* source, float* destination, int framesToProcess);
+
+ // Reset filter state.
+ void reset() {
+ m_lastX = 0;
+ m_lastY = 0;
+ }
+
+ void setZero(float zero) { m_zero = zero; }
+ void setPole(float pole) { m_pole = pole; }
+
+ float zero() const { return m_zero; }
+ float pole() const { return m_pole; }
+
+ private:
+ float m_zero;
+ float m_pole;
+ float m_lastX;
+ float m_lastY;
+};
+
+} // namespace WebCore
+
+#endif // ZeroPole_h
diff --git a/dom/media/webaudio/blink/moz.build b/dom/media/webaudio/blink/moz.build
new file mode 100644
index 0000000000..5f00d01ed3
--- /dev/null
+++ b/dom/media/webaudio/blink/moz.build
@@ -0,0 +1,36 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+UNIFIED_SOURCES += [
+ "Biquad.cpp",
+ "DynamicsCompressor.cpp",
+ "DynamicsCompressorKernel.cpp",
+ "FFTConvolver.cpp",
+ "HRTFDatabase.cpp",
+ "HRTFDatabaseLoader.cpp",
+ "HRTFElevation.cpp",
+ "HRTFKernel.cpp",
+ "HRTFPanner.cpp",
+ "IIRFilter.cpp",
+ "PeriodicWave.cpp",
+ "Reverb.cpp",
+ "ReverbAccumulationBuffer.cpp",
+ "ReverbConvolver.cpp",
+ "ReverbConvolverStage.cpp",
+ "ReverbInputBuffer.cpp",
+ "ZeroPole.cpp",
+]
+
+# Are we targeting x86 or x64? If so, build SSE2 files.
+if CONFIG["INTEL_ARCHITECTURE"]:
+ DEFINES["USE_SSE2"] = True
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+LOCAL_INCLUDES += [
+ "/dom/media/webaudio",
+]
diff --git a/dom/media/webaudio/moz.build b/dom/media/webaudio/moz.build
new file mode 100644
index 0000000000..2e82d3daa8
--- /dev/null
+++ b/dom/media/webaudio/moz.build
@@ -0,0 +1,153 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("*"):
+ BUG_COMPONENT = ("Core", "Web Audio")
+
+DIRS += ["blink"]
+
+MOCHITEST_MANIFESTS += [
+ "test/blink/mochitest.toml",
+ "test/mochitest.toml",
+ "test/mochitest_audio.toml",
+ "test/mochitest_bugs.toml",
+ "test/mochitest_media.toml",
+]
+
+TEST_HARNESS_FILES.testing.mochitest.tests.dom.media.webaudio.test.blink += [
+ "test/blink/audio-testing.js",
+ "test/blink/convolution-testing.js",
+ "test/blink/panner-model-testing.js",
+]
+
+EXPORTS += [
+ "AlignedTArray.h",
+ "AudioBlock.h",
+ "AudioEventTimeline.h",
+ "AudioNodeEngine.h",
+ "AudioNodeExternalInputTrack.h",
+ "AudioNodeTrack.h",
+ "AudioParamTimeline.h",
+ "MediaBufferDecoder.h",
+ "ThreeDPoint.h",
+ "WebAudioUtils.h",
+]
+
+EXPORTS.mozilla += [
+ "FFTBlock.h",
+ "MediaStreamAudioDestinationNode.h",
+]
+
+EXPORTS.mozilla.dom += [
+ "AnalyserNode.h",
+ "AudioBuffer.h",
+ "AudioBufferSourceNode.h",
+ "AudioContext.h",
+ "AudioDestinationNode.h",
+ "AudioListener.h",
+ "AudioNode.h",
+ "AudioParam.h",
+ "AudioParamDescriptorMap.h",
+ "AudioParamMap.h",
+ "AudioProcessingEvent.h",
+ "AudioScheduledSourceNode.h",
+ "AudioWorkletGlobalScope.h",
+ "AudioWorkletNode.h",
+ "AudioWorkletProcessor.h",
+ "BiquadFilterNode.h",
+ "ChannelMergerNode.h",
+ "ChannelSplitterNode.h",
+ "ConstantSourceNode.h",
+ "ConvolverNode.h",
+ "DelayNode.h",
+ "DynamicsCompressorNode.h",
+ "GainNode.h",
+ "IIRFilterNode.h",
+ "MediaElementAudioSourceNode.h",
+ "MediaStreamAudioDestinationNode.h",
+ "MediaStreamAudioSourceNode.h",
+ "MediaStreamTrackAudioSourceNode.h",
+ "OscillatorNode.h",
+ "PannerNode.h",
+ "PeriodicWave.h",
+ "ScriptProcessorNode.h",
+ "StereoPannerNode.h",
+ "WaveShaperNode.h",
+]
+
+UNIFIED_SOURCES += [
+ "AnalyserNode.cpp",
+ "AudioBlock.cpp",
+ "AudioBuffer.cpp",
+ "AudioBufferSourceNode.cpp",
+ "AudioContext.cpp",
+ "AudioDestinationNode.cpp",
+ "AudioEventTimeline.cpp",
+ "AudioListener.cpp",
+ "AudioNode.cpp",
+ "AudioNodeEngine.cpp",
+ "AudioNodeExternalInputTrack.cpp",
+ "AudioNodeTrack.cpp",
+ "AudioParam.cpp",
+ "AudioParamMap.cpp",
+ "AudioProcessingEvent.cpp",
+ "AudioScheduledSourceNode.cpp",
+ "AudioWorkletGlobalScope.cpp",
+ "AudioWorkletImpl.cpp",
+ "AudioWorkletNode.cpp",
+ "AudioWorkletProcessor.cpp",
+ "BiquadFilterNode.cpp",
+ "ChannelMergerNode.cpp",
+ "ChannelSplitterNode.cpp",
+ "ConstantSourceNode.cpp",
+ "ConvolverNode.cpp",
+ "DelayBuffer.cpp",
+ "DelayNode.cpp",
+ "DynamicsCompressorNode.cpp",
+ "FFTBlock.cpp",
+ "GainNode.cpp",
+ "IIRFilterNode.cpp",
+ "MediaBufferDecoder.cpp",
+ "MediaElementAudioSourceNode.cpp",
+ "MediaStreamAudioDestinationNode.cpp",
+ "MediaStreamAudioSourceNode.cpp",
+ "MediaStreamTrackAudioSourceNode.cpp",
+ "OscillatorNode.cpp",
+ "PannerNode.cpp",
+ "PeriodicWave.cpp",
+ "ScriptProcessorNode.cpp",
+ "StereoPannerNode.cpp",
+ "ThreeDPoint.cpp",
+ "WaveShaperNode.cpp",
+ "WebAudioUtils.cpp",
+]
+
+if CONFIG["TARGET_CPU"] == "aarch64" or CONFIG["BUILD_ARM_NEON"]:
+ DEFINES["USE_NEON"] = True
+ LOCAL_INCLUDES += ["/third_party/xsimd/include"]
+ SOURCES += ["AudioNodeEngineNEON.cpp"]
+ SOURCES["AudioNodeEngineNEON.cpp"].flags += CONFIG["NEON_FLAGS"]
+ if CONFIG["BUILD_ARM_NEON"]:
+ LOCAL_INCLUDES += ["/media/openmax_dl/dl/api/"]
+
+# Are we targeting x86 or x64? If so, build SSEX files.
+if CONFIG["INTEL_ARCHITECTURE"]:
+ DEFINES["USE_SSE2"] = True
+ SOURCES += ["AudioNodeEngineSSE2.cpp"]
+ LOCAL_INCLUDES += ["/third_party/xsimd/include"]
+ SOURCES["AudioNodeEngineSSE2.cpp"].flags += CONFIG["SSE2_FLAGS"]
+ if CONFIG["SSE4_2_FLAGS"] and CONFIG["FMA_FLAGS"]:
+ DEFINES["USE_SSE4_2"] = True
+ DEFINES["USE_FMA3"] = True
+ SOURCES += ["AudioNodeEngineSSE4_2_FMA3.cpp"]
+ SOURCES["AudioNodeEngineSSE4_2_FMA3.cpp"].flags += (
+ CONFIG["SSE4_2_FLAGS"] + CONFIG["FMA_FLAGS"]
+ )
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+LOCAL_INCLUDES += [".."]
diff --git a/dom/media/webaudio/test/1856145.ogg b/dom/media/webaudio/test/1856145.ogg
new file mode 100644
index 0000000000..771fad7899
--- /dev/null
+++ b/dom/media/webaudio/test/1856145.ogg
Binary files differ
diff --git a/dom/media/webaudio/test/8kHz-320kbps-6ch.aac b/dom/media/webaudio/test/8kHz-320kbps-6ch.aac
new file mode 100644
index 0000000000..8981d40dfd
--- /dev/null
+++ b/dom/media/webaudio/test/8kHz-320kbps-6ch.aac
Binary files differ
diff --git a/dom/media/webaudio/test/audio-expected.wav b/dom/media/webaudio/test/audio-expected.wav
new file mode 100644
index 0000000000..384f76318a
--- /dev/null
+++ b/dom/media/webaudio/test/audio-expected.wav
Binary files differ
diff --git a/dom/media/webaudio/test/audio-mono-expected-2.wav b/dom/media/webaudio/test/audio-mono-expected-2.wav
new file mode 100644
index 0000000000..68c90dfa1e
--- /dev/null
+++ b/dom/media/webaudio/test/audio-mono-expected-2.wav
Binary files differ
diff --git a/dom/media/webaudio/test/audio-mono-expected.wav b/dom/media/webaudio/test/audio-mono-expected.wav
new file mode 100644
index 0000000000..bf00e5cdf2
--- /dev/null
+++ b/dom/media/webaudio/test/audio-mono-expected.wav
Binary files differ
diff --git a/dom/media/webaudio/test/audio-quad.wav b/dom/media/webaudio/test/audio-quad.wav
new file mode 100644
index 0000000000..093f0197ae
--- /dev/null
+++ b/dom/media/webaudio/test/audio-quad.wav
Binary files differ
diff --git a/dom/media/webaudio/test/audio.ogv b/dom/media/webaudio/test/audio.ogv
new file mode 100644
index 0000000000..68dee3cf2b
--- /dev/null
+++ b/dom/media/webaudio/test/audio.ogv
Binary files differ
diff --git a/dom/media/webaudio/test/audioBufferSourceNodeDetached_worker.js b/dom/media/webaudio/test/audioBufferSourceNodeDetached_worker.js
new file mode 100644
index 0000000000..2a5a4bff89
--- /dev/null
+++ b/dom/media/webaudio/test/audioBufferSourceNodeDetached_worker.js
@@ -0,0 +1,3 @@
+onmessage = function (event) {
+ postMessage("Pong");
+};
diff --git a/dom/media/webaudio/test/audiovideo.mp4 b/dom/media/webaudio/test/audiovideo.mp4
new file mode 100644
index 0000000000..fe93122d29
--- /dev/null
+++ b/dom/media/webaudio/test/audiovideo.mp4
Binary files differ
diff --git a/dom/media/webaudio/test/blink/README b/dom/media/webaudio/test/blink/README
new file mode 100644
index 0000000000..1d819221fd
--- /dev/null
+++ b/dom/media/webaudio/test/blink/README
@@ -0,0 +1,9 @@
+This directory contains tests originally borrowed from the Blink Web Audio test
+suite.
+
+The process of borrowing tests from Blink is as follows:
+
+* Import the pristine file from the Blink repo, noting the revision in the
+ commit message.
+* Modify the test files to turn the LayoutTest into a mochitest-plain and add
+* them to the test suite in a separate commit.
diff --git a/dom/media/webaudio/test/blink/audio-testing.js b/dom/media/webaudio/test/blink/audio-testing.js
new file mode 100644
index 0000000000..c66d32c7f2
--- /dev/null
+++ b/dom/media/webaudio/test/blink/audio-testing.js
@@ -0,0 +1,192 @@
+if (window.testRunner)
+ testRunner.overridePreference("WebKitWebAudioEnabled", "1");
+
+function writeString(s, a, offset) {
+ for (var i = 0; i < s.length; ++i) {
+ a[offset + i] = s.charCodeAt(i);
+ }
+}
+
+function writeInt16(n, a, offset) {
+ n = Math.floor(n);
+
+ var b1 = n & 255;
+ var b2 = (n >> 8) & 255;
+
+ a[offset + 0] = b1;
+ a[offset + 1] = b2;
+}
+
+function writeInt32(n, a, offset) {
+ n = Math.floor(n);
+ var b1 = n & 255;
+ var b2 = (n >> 8) & 255;
+ var b3 = (n >> 16) & 255;
+ var b4 = (n >> 24) & 255;
+
+ a[offset + 0] = b1;
+ a[offset + 1] = b2;
+ a[offset + 2] = b3;
+ a[offset + 3] = b4;
+}
+
+function writeAudioBuffer(audioBuffer, a, offset) {
+ var n = audioBuffer.length;
+ var channels = audioBuffer.numberOfChannels;
+
+ for (var i = 0; i < n; ++i) {
+ for (var k = 0; k < channels; ++k) {
+ var buffer = audioBuffer.getChannelData(k);
+ var sample = buffer[i] * 32768.0;
+
+ // Clip samples to the limitations of 16-bit.
+ // If we don't do this then we'll get nasty wrap-around distortion.
+ if (sample < -32768)
+ sample = -32768;
+ if (sample > 32767)
+ sample = 32767;
+
+ writeInt16(sample, a, offset);
+ offset += 2;
+ }
+ }
+}
+
+function createWaveFileData(audioBuffer) {
+ var frameLength = audioBuffer.length;
+ var numberOfChannels = audioBuffer.numberOfChannels;
+ var sampleRate = audioBuffer.sampleRate;
+ var bitsPerSample = 16;
+ var byteRate = sampleRate * numberOfChannels * bitsPerSample/8;
+ var blockAlign = numberOfChannels * bitsPerSample/8;
+ var wavDataByteLength = frameLength * numberOfChannels * 2; // 16-bit audio
+ var headerByteLength = 44;
+ var totalLength = headerByteLength + wavDataByteLength;
+
+ var waveFileData = new Uint8Array(totalLength);
+
+ var subChunk1Size = 16; // for linear PCM
+ var subChunk2Size = wavDataByteLength;
+ var chunkSize = 4 + (8 + subChunk1Size) + (8 + subChunk2Size);
+
+ writeString("RIFF", waveFileData, 0);
+ writeInt32(chunkSize, waveFileData, 4);
+ writeString("WAVE", waveFileData, 8);
+ writeString("fmt ", waveFileData, 12);
+
+ writeInt32(subChunk1Size, waveFileData, 16); // SubChunk1Size (4)
+ writeInt16(1, waveFileData, 20); // AudioFormat (2)
+ writeInt16(numberOfChannels, waveFileData, 22); // NumChannels (2)
+ writeInt32(sampleRate, waveFileData, 24); // SampleRate (4)
+ writeInt32(byteRate, waveFileData, 28); // ByteRate (4)
+ writeInt16(blockAlign, waveFileData, 32); // BlockAlign (2)
+ writeInt32(bitsPerSample, waveFileData, 34); // BitsPerSample (4)
+
+ writeString("data", waveFileData, 36);
+ writeInt32(subChunk2Size, waveFileData, 40); // SubChunk2Size (4)
+
+ // Write actual audio data starting at offset 44.
+ writeAudioBuffer(audioBuffer, waveFileData, 44);
+
+ return waveFileData;
+}
+
+function createAudioData(audioBuffer) {
+ return createWaveFileData(audioBuffer);
+}
+
+function finishAudioTest(event) {
+ var audioData = createAudioData(event.renderedBuffer);
+ testRunner.setAudioData(audioData);
+ testRunner.notifyDone();
+}
+
+// Create an impulse in a buffer of length sampleFrameLength
+function createImpulseBuffer(context, sampleFrameLength) {
+ var audioBuffer = context.createBuffer(1, sampleFrameLength, context.sampleRate);
+ var n = audioBuffer.length;
+ var dataL = audioBuffer.getChannelData(0);
+
+ for (var k = 0; k < n; ++k) {
+ dataL[k] = 0;
+ }
+ dataL[0] = 1;
+
+ return audioBuffer;
+}
+
+// Create a buffer of the given length with a linear ramp having values 0 <= x < 1.
+function createLinearRampBuffer(context, sampleFrameLength) {
+ var audioBuffer = context.createBuffer(1, sampleFrameLength, context.sampleRate);
+ var n = audioBuffer.length;
+ var dataL = audioBuffer.getChannelData(0);
+
+ for (var i = 0; i < n; ++i)
+ dataL[i] = i / n;
+
+ return audioBuffer;
+}
+
+// Create a buffer of the given length having a constant value.
+function createConstantBuffer(context, sampleFrameLength, constantValue) {
+ var audioBuffer = context.createBuffer(1, sampleFrameLength, context.sampleRate);
+ var n = audioBuffer.length;
+ var dataL = audioBuffer.getChannelData(0);
+
+ for (var i = 0; i < n; ++i)
+ dataL[i] = constantValue;
+
+ return audioBuffer;
+}
+
+// Create a stereo impulse in a buffer of length sampleFrameLength
+function createStereoImpulseBuffer(context, sampleFrameLength) {
+ var audioBuffer = context.createBuffer(2, sampleFrameLength, context.sampleRate);
+ var n = audioBuffer.length;
+ var dataL = audioBuffer.getChannelData(0);
+ var dataR = audioBuffer.getChannelData(1);
+
+ for (var k = 0; k < n; ++k) {
+ dataL[k] = 0;
+ dataR[k] = 0;
+ }
+ dataL[0] = 1;
+ dataR[0] = 1;
+
+ return audioBuffer;
+}
+
+// Convert time (in seconds) to sample frames.
+function timeToSampleFrame(time, sampleRate) {
+ return Math.floor(0.5 + time * sampleRate);
+}
+
+// Compute the number of sample frames consumed by start with
+// the specified |grainOffset|, |duration|, and |sampleRate|.
+function grainLengthInSampleFrames(grainOffset, duration, sampleRate) {
+ var startFrame = timeToSampleFrame(grainOffset, sampleRate);
+ var endFrame = timeToSampleFrame(grainOffset + duration, sampleRate);
+
+ return endFrame - startFrame;
+}
+
+// True if the number is not an infinity or NaN
+function isValidNumber(x) {
+ return !isNaN(x) && (x != Infinity) && (x != -Infinity);
+}
+
+function shouldThrowTypeError(func, text) {
+ var ok = false;
+ try {
+ func();
+ } catch (e) {
+ if (e instanceof TypeError) {
+ ok = true;
+ }
+ }
+ if (ok) {
+ testPassed(text + " threw TypeError.");
+ } else {
+ testFailed(text + " should throw TypeError.");
+ }
+}
diff --git a/dom/media/webaudio/test/blink/biquad-filters.js b/dom/media/webaudio/test/blink/biquad-filters.js
new file mode 100644
index 0000000000..06fff98b18
--- /dev/null
+++ b/dom/media/webaudio/test/blink/biquad-filters.js
@@ -0,0 +1,368 @@
+// Taken from WebKit/LayoutTests/webaudio/resources/biquad-filters.js
+
+// A biquad filter has a z-transform of
+// H(z) = (b0 + b1 / z + b2 / z^2) / (1 + a1 / z + a2 / z^2)
+//
+// The formulas for the various filters were taken from
+// http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt.
+
+
+// Lowpass filter.
+function createLowpassFilter(freq, q, gain) {
+ var b0;
+ var b1;
+ var b2;
+ var a0;
+ var a1;
+ var a2;
+
+ if (freq == 1) {
+ // The formula below works, except for roundoff. When freq = 1,
+ // the filter is just a wire, so hardwire the coefficients.
+ b0 = 1;
+ b1 = 0;
+ b2 = 0;
+ a0 = 1;
+ a1 = 0;
+ a2 = 0;
+ } else {
+ var w0 = Math.PI * freq;
+ var alpha = 0.5 * Math.sin(w0) / Math.pow(10, q / 20);
+ var cos_w0 = Math.cos(w0);
+
+ b0 = 0.5 * (1 - cos_w0);
+ b1 = 1 - cos_w0;
+ b2 = b0;
+ a0 = 1 + alpha;
+ a1 = -2.0 * cos_w0;
+ a2 = 1 - alpha;
+ }
+
+ return normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2);
+}
+
+function createHighpassFilter(freq, q, gain) {
+ var b0;
+ var b1;
+ var b2;
+ var a1;
+ var a2;
+
+ if (freq == 1) {
+ // The filter is 0
+ b0 = 0;
+ b1 = 0;
+ b2 = 0;
+ a0 = 1;
+ a1 = 0;
+ a2 = 0;
+ } else if (freq == 0) {
+ // The filter is 1. Computation of coefficients below is ok, but
+ // there's a pole at 1 and a zero at 1, so round-off could make
+ // the filter unstable.
+ b0 = 1;
+ b1 = 0;
+ b2 = 0;
+ a0 = 1;
+ a1 = 0;
+ a2 = 0;
+ } else {
+ var w0 = Math.PI * freq;
+ var alpha = 0.5 * Math.sin(w0) / Math.pow(10, q / 20);
+ var cos_w0 = Math.cos(w0);
+
+ b0 = 0.5 * (1 + cos_w0);
+ b1 = -1 - cos_w0;
+ b2 = b0;
+ a0 = 1 + alpha;
+ a1 = -2.0 * cos_w0;
+ a2 = 1 - alpha;
+ }
+
+ return normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2);
+}
+
+function normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2) {
+ var scale = 1 / a0;
+
+ return {b0 : b0 * scale,
+ b1 : b1 * scale,
+ b2 : b2 * scale,
+ a1 : a1 * scale,
+ a2 : a2 * scale};
+}
+
+function createBandpassFilter(freq, q, gain) {
+ var b0;
+ var b1;
+ var b2;
+ var a0;
+ var a1;
+ var a2;
+ var coef;
+
+ if (freq > 0 && freq < 1) {
+ var w0 = Math.PI * freq;
+ if (q > 0) {
+ var alpha = Math.sin(w0) / (2 * q);
+ var k = Math.cos(w0);
+
+ b0 = alpha;
+ b1 = 0;
+ b2 = -alpha;
+ a0 = 1 + alpha;
+ a1 = -2 * k;
+ a2 = 1 - alpha;
+
+ coef = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2);
+ } else {
+ // q = 0, and frequency is not 0 or 1. The above formula has a
+ // divide by zero problem. The limit of the z-transform as q
+ // approaches 0 is 1, so set the filter that way.
+ coef = {b0 : 1, b1 : 0, b2 : 0, a1 : 0, a2 : 0};
+ }
+ } else {
+ // When freq = 0 or 1, the z-transform is identically 0,
+ // independent of q.
+ coef = {b0 : 0, b1 : 0, b2 : 0, a1 : 0, a2 : 0}
+ }
+
+ return coef;
+}
+
+function createLowShelfFilter(freq, q, gain) {
+ // q not used
+ var b0;
+ var b1;
+ var b2;
+ var a0;
+ var a1;
+ var a2;
+ var coef;
+
+ var S = 1;
+ var A = Math.pow(10, gain / 40);
+
+ if (freq == 1) {
+ // The filter is just a constant gain
+ coef = {b0 : A * A, b1 : 0, b2 : 0, a1 : 0, a2 : 0};
+ } else if (freq == 0) {
+ // The filter is 1
+ coef = {b0 : 1, b1 : 0, b2 : 0, a1 : 0, a2 : 0};
+ } else {
+ var w0 = Math.PI * freq;
+ var alpha = 1 / 2 * Math.sin(w0) * Math.sqrt((A + 1 / A) * (1 / S - 1) + 2);
+ var k = Math.cos(w0);
+ var k2 = 2 * Math.sqrt(A) * alpha;
+ var Ap1 = A + 1;
+ var Am1 = A - 1;
+
+ b0 = A * (Ap1 - Am1 * k + k2);
+ b1 = 2 * A * (Am1 - Ap1 * k);
+ b2 = A * (Ap1 - Am1 * k - k2);
+ a0 = Ap1 + Am1 * k + k2;
+ a1 = -2 * (Am1 + Ap1 * k);
+ a2 = Ap1 + Am1 * k - k2;
+ coef = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2);
+ }
+
+ return coef;
+}
+
+function createHighShelfFilter(freq, q, gain) {
+ // q not used
+ var b0;
+ var b1;
+ var b2;
+ var a0;
+ var a1;
+ var a2;
+ var coef;
+
+ var A = Math.pow(10, gain / 40);
+
+ if (freq == 1) {
+ // When freq = 1, the z-transform is 1
+ coef = {b0 : 1, b1 : 0, b2 : 0, a1 : 0, a2 : 0};
+ } else if (freq > 0) {
+ var w0 = Math.PI * freq;
+ var S = 1;
+ var alpha = 0.5 * Math.sin(w0) * Math.sqrt((A + 1 / A) * (1 / S - 1) + 2);
+ var k = Math.cos(w0);
+ var k2 = 2 * Math.sqrt(A) * alpha;
+ var Ap1 = A + 1;
+ var Am1 = A - 1;
+
+ b0 = A * (Ap1 + Am1 * k + k2);
+ b1 = -2 * A * (Am1 + Ap1 * k);
+ b2 = A * (Ap1 + Am1 * k - k2);
+ a0 = Ap1 - Am1 * k + k2;
+ a1 = 2 * (Am1 - Ap1*k);
+ a2 = Ap1 - Am1 * k-k2;
+
+ coef = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2);
+ } else {
+ // When freq = 0, the filter is just a gain
+ coef = {b0 : A * A, b1 : 0, b2 : 0, a1 : 0, a2 : 0};
+ }
+
+ return coef;
+}
+
+function createPeakingFilter(freq, q, gain) {
+ var b0;
+ var b1;
+ var b2;
+ var a0;
+ var a1;
+ var a2;
+ var coef;
+
+ var A = Math.pow(10, gain / 40);
+
+ if (freq > 0 && freq < 1) {
+ if (q > 0) {
+ var w0 = Math.PI * freq;
+ var alpha = Math.sin(w0) / (2 * q);
+ var k = Math.cos(w0);
+
+ b0 = 1 + alpha * A;
+ b1 = -2 * k;
+ b2 = 1 - alpha * A;
+ a0 = 1 + alpha / A;
+ a1 = -2 * k;
+ a2 = 1 - alpha / A;
+
+ coef = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2);
+ } else {
+ // q = 0, we have a divide by zero problem in the formulas
+ // above. But if we look at the z-transform, we see that the
+ // limit as q approaches 0 is A^2.
+ coef = {b0 : A * A, b1 : 0, b2 : 0, a1 : 0, a2 : 0};
+ }
+ } else {
+ // freq = 0 or 1, the z-transform is 1
+ coef = {b0 : 1, b1 : 0, b2 : 0, a1 : 0, a2 : 0};
+ }
+
+ return coef;
+}
+
+function createNotchFilter(freq, q, gain) {
+ var b0;
+ var b1;
+ var b2;
+ var a0;
+ var a1;
+ var a2;
+ var coef;
+
+ if (freq > 0 && freq < 1) {
+ if (q > 0) {
+ var w0 = Math.PI * freq;
+ var alpha = Math.sin(w0) / (2 * q);
+ var k = Math.cos(w0);
+
+ b0 = 1;
+ b1 = -2 * k;
+ b2 = 1;
+ a0 = 1 + alpha;
+ a1 = -2 * k;
+ a2 = 1 - alpha;
+ coef = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2);
+ } else {
+ // When q = 0, we get a divide by zero above. The limit of the
+ // z-transform as q approaches 0 is 0, so set the coefficients
+ // appropriately.
+ coef = {b0 : 0, b1 : 0, b2 : 0, a1 : 0, a2 : 0};
+ }
+ } else {
+ // When freq = 0 or 1, the z-transform is 1
+ coef = {b0 : 1, b1 : 0, b2 : 0, a1 : 0, a2 : 0};
+ }
+
+ return coef;
+}
+
+function createAllpassFilter(freq, q, gain) {
+ var b0;
+ var b1;
+ var b2;
+ var a0;
+ var a1;
+ var a2;
+ var coef;
+
+ if (freq > 0 && freq < 1) {
+ if (q > 0) {
+ var w0 = Math.PI * freq;
+ var alpha = Math.sin(w0) / (2 * q);
+ var k = Math.cos(w0);
+
+ b0 = 1 - alpha;
+ b1 = -2 * k;
+ b2 = 1 + alpha;
+ a0 = 1 + alpha;
+ a1 = -2 * k;
+ a2 = 1 - alpha;
+ coef = normalizeFilterCoefficients(b0, b1, b2, a0, a1, a2);
+ } else {
+ // q = 0
+ coef = {b0 : -1, b1 : 0, b2 : 0, a1 : 0, a2 : 0};
+ }
+ } else {
+ coef = {b0 : 1, b1 : 0, b2 : 0, a1 : 0, a2 : 0};
+ }
+
+ return coef;
+}
+
+function filterData(filterCoef, signal, len) {
+ var y = new Array(len);
+ var b0 = filterCoef.b0;
+ var b1 = filterCoef.b1;
+ var b2 = filterCoef.b2;
+ var a1 = filterCoef.a1;
+ var a2 = filterCoef.a2;
+
+ // Prime the pump. (Assumes the signal has length >= 2!)
+ y[0] = b0 * signal[0];
+ y[1] = b0 * signal[1] + b1 * signal[0] - a1 * y[0];
+
+ // Filter all of the signal that we have.
+ for (var k = 2; k < Math.min(signal.length, len); ++k) {
+ y[k] = b0 * signal[k] + b1 * signal[k-1] + b2 * signal[k-2] - a1 * y[k-1] - a2 * y[k-2];
+ }
+
+ // If we need to filter more, but don't have any signal left,
+ // assume the signal is zero.
+ for (var k = signal.length; k < len; ++k) {
+ y[k] = - a1 * y[k-1] - a2 * y[k-2];
+ }
+
+ return y;
+}
+
+// Map the filter type name to a function that computes the filter coefficents for the given filter
+// type.
+var filterCreatorFunction = {"lowpass": createLowpassFilter,
+ "highpass": createHighpassFilter,
+ "bandpass": createBandpassFilter,
+ "lowshelf": createLowShelfFilter,
+ "highshelf": createHighShelfFilter,
+ "peaking": createPeakingFilter,
+ "notch": createNotchFilter,
+ "allpass": createAllpassFilter};
+
+var filterTypeName = {"lowpass": "Lowpass filter",
+ "highpass": "Highpass filter",
+ "bandpass": "Bandpass filter",
+ "lowshelf": "Lowshelf filter",
+ "highshelf": "Highshelf filter",
+ "peaking": "Peaking filter",
+ "notch": "Notch filter",
+ "allpass": "Allpass filter"};
+
+function createFilter(filterType, freq, q, gain) {
+ return filterCreatorFunction[filterType](freq, q, gain);
+}
diff --git a/dom/media/webaudio/test/blink/biquad-testing.js b/dom/media/webaudio/test/blink/biquad-testing.js
new file mode 100644
index 0000000000..795adf6012
--- /dev/null
+++ b/dom/media/webaudio/test/blink/biquad-testing.js
@@ -0,0 +1,153 @@
+// Globals, to make testing and debugging easier.
+var context;
+var filter;
+var signal;
+var renderedBuffer;
+var renderedData;
+
+var sampleRate = 44100.0;
+var pulseLengthFrames = .1 * sampleRate;
+
+// Maximum allowed error for the test to succeed. Experimentally determined.
+var maxAllowedError = 5.9e-8;
+
+// This must be large enough so that the filtered result is
+// essentially zero. See comments for createTestAndRun.
+var timeStep = .1;
+
+// Maximum number of filters we can process (mostly for setting the
+// render length correctly.)
+var maxFilters = 5;
+
+// How long to render. Must be long enough for all of the filters we
+// want to test.
+var renderLengthSeconds = timeStep * (maxFilters + 1) ;
+
+var renderLengthSamples = Math.round(renderLengthSeconds * sampleRate);
+
+// Number of filters that will be processed.
+var nFilters;
+
+function createImpulseBuffer(context, length) {
+ var impulse = context.createBuffer(1, length, context.sampleRate);
+ var data = impulse.getChannelData(0);
+ for (var k = 1; k < data.length; ++k) {
+ data[k] = 0;
+ }
+ data[0] = 1;
+
+ return impulse;
+}
+
+
+function createTestAndRun(context, filterType, filterParameters) {
+ // To test the filters, we apply a signal (an impulse) to each of
+ // the specified filters, with each signal starting at a different
+ // time. The output of the filters is summed together at the
+ // output. Thus for filter k, the signal input to the filter
+ // starts at time k * timeStep. For this to work well, timeStep
+ // must be large enough for the output of each filter to have
+ // decayed to zero with timeStep seconds. That way the filter
+ // outputs don't interfere with each other.
+
+ nFilters = Math.min(filterParameters.length, maxFilters);
+
+ signal = new Array(nFilters);
+ filter = new Array(nFilters);
+
+ impulse = createImpulseBuffer(context, pulseLengthFrames);
+
+ // Create all of the signal sources and filters that we need.
+ for (var k = 0; k < nFilters; ++k) {
+ signal[k] = context.createBufferSource();
+ signal[k].buffer = impulse;
+
+ filter[k] = context.createBiquadFilter();
+ filter[k].type = filterType;
+ filter[k].frequency.value = context.sampleRate / 2 * filterParameters[k].cutoff;
+ filter[k].detune.value = (filterParameters[k].detune === undefined) ? 0 : filterParameters[k].detune;
+ filter[k].Q.value = filterParameters[k].q;
+ filter[k].gain.value = filterParameters[k].gain;
+
+ signal[k].connect(filter[k]);
+ filter[k].connect(context.destination);
+
+ signal[k].start(timeStep * k);
+ }
+
+ context.oncomplete = checkFilterResponse(filterType, filterParameters);
+ context.startRendering();
+}
+
+function addSignal(dest, src, destOffset) {
+ // Add src to dest at the given dest offset.
+ for (var k = destOffset, j = 0; k < dest.length, j < src.length; ++k, ++j) {
+ dest[k] += src[j];
+ }
+}
+
+function generateReference(filterType, filterParameters) {
+ var result = new Array(renderLengthSamples);
+ var data = new Array(renderLengthSamples);
+ // Initialize the result array and data.
+ for (var k = 0; k < result.length; ++k) {
+ result[k] = 0;
+ data[k] = 0;
+ }
+ // Make data an impulse.
+ data[0] = 1;
+
+ for (var k = 0; k < nFilters; ++k) {
+ // Filter an impulse
+ var detune = (filterParameters[k].detune === undefined) ? 0 : filterParameters[k].detune;
+ var frequency = filterParameters[k].cutoff * Math.pow(2, detune / 1200); // Apply detune, converting from Cents.
+
+ var filterCoef = createFilter(filterType,
+ frequency,
+ filterParameters[k].q,
+ filterParameters[k].gain);
+ var y = filterData(filterCoef, data, renderLengthSamples);
+
+ // Accumulate this filtered data into the final output at the desired offset.
+ addSignal(result, y, timeToSampleFrame(timeStep * k, sampleRate));
+ }
+
+ return result;
+}
+
+function checkFilterResponse(filterType, filterParameters) {
+ return function(event) {
+ renderedBuffer = event.renderedBuffer;
+ renderedData = renderedBuffer.getChannelData(0);
+
+ reference = generateReference(filterType, filterParameters);
+
+ var len = Math.min(renderedData.length, reference.length);
+
+ var success = true;
+
+ // Maximum error between rendered data and expected data
+ var maxError = 0;
+
+ // Sample offset where the maximum error occurred.
+ var maxPosition = 0;
+
+ // Number of infinities or NaNs that occurred in the rendered data.
+ var invalidNumberCount = 0;
+
+ ok(nFilters == filterParameters.length, "Test wanted " + filterParameters.length + " filters but only " + maxFilters + " allowed.");
+
+ compareChannels(renderedData, reference, len, 0, 0, true);
+
+ // Check for bad numbers in the rendered output too.
+ // There shouldn't be any.
+ for (var k = 0; k < len; ++k) {
+ if (!isValidNumber(renderedData[k])) {
+ ++invalidNumberCount;
+ }
+ }
+
+ ok(invalidNumberCount == 0, "Rendered output has " + invalidNumberCount + " infinities or NaNs.");
+ SimpleTest.finish();
+ }
+}
diff --git a/dom/media/webaudio/test/blink/convolution-testing.js b/dom/media/webaudio/test/blink/convolution-testing.js
new file mode 100644
index 0000000000..98ff0c7756
--- /dev/null
+++ b/dom/media/webaudio/test/blink/convolution-testing.js
@@ -0,0 +1,182 @@
+var sampleRate = 44100.0;
+
+var renderLengthSeconds = 8;
+var pulseLengthSeconds = 1;
+var pulseLengthFrames = pulseLengthSeconds * sampleRate;
+
+function createSquarePulseBuffer(context, sampleFrameLength) {
+ var audioBuffer = context.createBuffer(1, sampleFrameLength, context.sampleRate);
+
+ var n = audioBuffer.length;
+ var data = audioBuffer.getChannelData(0);
+
+ for (var i = 0; i < n; ++i)
+ data[i] = 1;
+
+ return audioBuffer;
+}
+
+// The triangle buffer holds the expected result of the convolution.
+// It linearly ramps up from 0 to its maximum value (at the center)
+// then linearly ramps down to 0. The center value corresponds to the
+// point where the two square pulses overlap the most.
+function createTrianglePulseBuffer(context, sampleFrameLength) {
+ var audioBuffer = context.createBuffer(1, sampleFrameLength, context.sampleRate);
+
+ var n = audioBuffer.length;
+ var halfLength = n / 2;
+ var data = audioBuffer.getChannelData(0);
+
+ for (var i = 0; i < halfLength; ++i)
+ data[i] = i + 1;
+
+ for (var i = halfLength; i < n; ++i)
+ data[i] = n - i - 1;
+
+ return audioBuffer;
+}
+
+function log10(x) {
+ return Math.log(x)/Math.LN10;
+}
+
+function linearToDecibel(x) {
+ return 20*log10(x);
+}
+
+// Verify that the rendered result is very close to the reference
+// triangular pulse.
+function checkTriangularPulse(rendered, reference) {
+ var match = true;
+ var maxDelta = 0;
+ var valueAtMaxDelta = 0;
+ var maxDeltaIndex = 0;
+
+ for (var i = 0; i < reference.length; ++i) {
+ var diff = rendered[i] - reference[i];
+ var x = Math.abs(diff);
+ if (x > maxDelta) {
+ maxDelta = x;
+ valueAtMaxDelta = reference[i];
+ maxDeltaIndex = i;
+ }
+ }
+
+ // allowedDeviationFraction was determined experimentally. It
+ // is the threshold of the relative error at the maximum
+ // difference between the true triangular pulse and the
+ // rendered pulse.
+ var allowedDeviationDecibels = -129.4;
+ var maxDeviationDecibels = linearToDecibel(maxDelta / valueAtMaxDelta);
+
+ if (maxDeviationDecibels <= allowedDeviationDecibels) {
+ testPassed("Triangular portion of convolution is correct.");
+ } else {
+ testFailed("Triangular portion of convolution is not correct. Max deviation = " + maxDeviationDecibels + " dB at " + maxDeltaIndex);
+ match = false;
+ }
+
+ return match;
+}
+
+// Verify that the rendered data is close to zero for the first part
+// of the tail.
+function checkTail1(data, reference, breakpoint) {
+ var isZero = true;
+ var tail1Max = 0;
+
+ for (var i = reference.length; i < reference.length + breakpoint; ++i) {
+ var mag = Math.abs(data[i]);
+ if (mag > tail1Max) {
+ tail1Max = mag;
+ }
+ }
+
+ // Let's find the peak of the reference (even though we know a
+ // priori what it is).
+ var refMax = 0;
+ for (var i = 0; i < reference.length; ++i) {
+ refMax = Math.max(refMax, Math.abs(reference[i]));
+ }
+
+ // This threshold is experimentally determined by examining the
+ // value of tail1MaxDecibels.
+ var threshold1 = -129.7;
+
+ var tail1MaxDecibels = linearToDecibel(tail1Max/refMax);
+ if (tail1MaxDecibels <= threshold1) {
+ testPassed("First part of tail of convolution is sufficiently small.");
+ } else {
+ testFailed("First part of tail of convolution is not sufficiently small: " + tail1MaxDecibels + " dB");
+ isZero = false;
+ }
+
+ return isZero;
+}
+
+// Verify that the second part of the tail of the convolution is
+// exactly zero.
+function checkTail2(data, reference, breakpoint) {
+ var isZero = true;
+ var tail2Max = 0;
+ // For the second part of the tail, the maximum value should be
+ // exactly zero.
+ var threshold2 = 0;
+ for (var i = reference.length + breakpoint; i < data.length; ++i) {
+ if (Math.abs(data[i]) > 0) {
+ isZero = false;
+ break;
+ }
+ }
+
+ if (isZero) {
+ testPassed("Rendered signal after tail of convolution is silent.");
+ } else {
+ testFailed("Rendered signal after tail of convolution should be silent.");
+ }
+
+ return isZero;
+}
+
+function checkConvolvedResult(trianglePulse) {
+ return function(event) {
+ var renderedBuffer = event.renderedBuffer;
+
+ var referenceData = trianglePulse.getChannelData(0);
+ var renderedData = renderedBuffer.getChannelData(0);
+
+ var success = true;
+
+ // Verify the triangular pulse is actually triangular.
+
+ success = success && checkTriangularPulse(renderedData, referenceData);
+
+ // Make sure that portion after convolved portion is totally
+ // silent. But round-off prevents this from being completely
+ // true. At the end of the triangle, it should be close to
+ // zero. If we go farther out, it should be even closer and
+ // eventually zero.
+
+ // For the tail of the convolution (where the result would be
+ // theoretically zero), we partition the tail into two
+ // parts. The first is the at the beginning of the tail,
+ // where we tolerate a small but non-zero value. The second part is
+ // farther along the tail where the result should be zero.
+
+ // breakpoint is the point dividing the first two tail parts
+ // we're looking at. Experimentally determined.
+ var breakpoint = 12800;
+
+ success = success && checkTail1(renderedData, referenceData, breakpoint);
+
+ success = success && checkTail2(renderedData, referenceData, breakpoint);
+
+ if (success) {
+ testPassed("Test signal was correctly convolved.");
+ } else {
+ testFailed("Test signal was not correctly convolved.");
+ }
+
+ finishJSTest();
+ }
+}
diff --git a/dom/media/webaudio/test/blink/mochitest.toml b/dom/media/webaudio/test/blink/mochitest.toml
new file mode 100644
index 0000000000..2b176d1225
--- /dev/null
+++ b/dom/media/webaudio/test/blink/mochitest.toml
@@ -0,0 +1,36 @@
+[DEFAULT]
+tags = "mtg webaudio"
+subsuite = "media"
+support-files = [
+ "biquad-filters.js",
+ "biquad-testing.js",
+ "../webaudio.js",
+]
+
+["test_biquadFilterNodeAllPass.html"]
+
+["test_biquadFilterNodeAutomation.html"]
+skip-if = ["true"] # Known problems with Biquad automation, e.g. Bug 1155709
+
+["test_biquadFilterNodeBandPass.html"]
+
+["test_biquadFilterNodeGetFrequencyResponse.html"]
+
+["test_biquadFilterNodeHighPass.html"]
+
+["test_biquadFilterNodeHighShelf.html"]
+
+["test_biquadFilterNodeLowPass.html"]
+
+["test_biquadFilterNodeLowShelf.html"]
+
+["test_biquadFilterNodeNotch.html"]
+
+["test_biquadFilterNodePeaking.html"]
+
+["test_biquadFilterNodeTail.html"]
+
+["test_iirFilterNode.html"]
+
+["test_iirFilterNodeGetFrequencyResponse.html"]
+
diff --git a/dom/media/webaudio/test/blink/panner-model-testing.js b/dom/media/webaudio/test/blink/panner-model-testing.js
new file mode 100644
index 0000000000..45460e2768
--- /dev/null
+++ b/dom/media/webaudio/test/blink/panner-model-testing.js
@@ -0,0 +1,210 @@
+var sampleRate = 48000.0;
+
+var numberOfChannels = 1;
+
+// Time step when each panner node starts.
+var timeStep = 0.001;
+
+// Length of the impulse signal.
+var pulseLengthFrames = Math.round(timeStep * sampleRate);
+
+// How many panner nodes to create for the test
+var nodesToCreate = 100;
+
+// Be sure we render long enough for all of our nodes.
+var renderLengthSeconds = timeStep * (nodesToCreate + 1);
+
+// These are global mostly for debugging.
+var context;
+var impulse;
+var bufferSource;
+var panner;
+var position;
+var time;
+
+var renderedBuffer;
+var renderedLeft;
+var renderedRight;
+
+function createGraph(context, nodeCount) {
+ bufferSource = new Array(nodeCount);
+ panner = new Array(nodeCount);
+ position = new Array(nodeCount);
+ time = new Array(nodeCount);
+ // Angle between panner locations. (nodeCount - 1 because we want
+ // to include both 0 and 180 deg.
+ var angleStep = Math.PI / (nodeCount - 1);
+
+ if (numberOfChannels == 2) {
+ impulse = createStereoImpulseBuffer(context, pulseLengthFrames);
+ }
+ else
+ impulse = createImpulseBuffer(context, pulseLengthFrames);
+
+ for (var k = 0; k < nodeCount; ++k) {
+ bufferSource[k] = context.createBufferSource();
+ bufferSource[k].buffer = impulse;
+
+ panner[k] = context.createPanner();
+ panner[k].panningModel = "equalpower";
+ panner[k].distanceModel = "linear";
+
+ var angle = angleStep * k;
+ position[k] = {angle : angle, x : Math.cos(angle), z : Math.sin(angle)};
+ panner[k].positionX.value = position[k].x;
+ panner[k].positionZ.value = position[k].z;
+
+ bufferSource[k].connect(panner[k]);
+ panner[k].connect(context.destination);
+
+ // Start the source
+ time[k] = k * timeStep;
+ bufferSource[k].start(time[k]);
+ }
+}
+
+function createTestAndRun(context, nodeCount, numberOfSourceChannels) {
+ numberOfChannels = numberOfSourceChannels;
+
+ createGraph(context, nodeCount);
+
+ context.oncomplete = checkResult;
+ context.startRendering();
+}
+
+// Map our position angle to the azimuth angle (in degrees).
+//
+// An angle of 0 corresponds to an azimuth of 90 deg; pi, to -90 deg.
+function angleToAzimuth(angle) {
+ return 90 - angle * 180 / Math.PI;
+}
+
+// The gain caused by the EQUALPOWER panning model
+function equalPowerGain(angle) {
+ var azimuth = angleToAzimuth(angle);
+
+ if (numberOfChannels == 1) {
+ var panPosition = (azimuth + 90) / 180;
+
+ var gainL = Math.cos(0.5 * Math.PI * panPosition);
+ var gainR = Math.sin(0.5 * Math.PI * panPosition);
+
+ return { left : gainL, right : gainR };
+ } else {
+ if (azimuth <= 0) {
+ var panPosition = (azimuth + 90) / 90;
+
+ var gainL = 1 + Math.cos(0.5 * Math.PI * panPosition);
+ var gainR = Math.sin(0.5 * Math.PI * panPosition);
+
+ return { left : gainL, right : gainR };
+ } else {
+ var panPosition = azimuth / 90;
+
+ var gainL = Math.cos(0.5 * Math.PI * panPosition);
+ var gainR = 1 + Math.sin(0.5 * Math.PI * panPosition);
+
+ return { left : gainL, right : gainR };
+ }
+ }
+}
+
+function checkResult(event) {
+ renderedBuffer = event.renderedBuffer;
+ renderedLeft = renderedBuffer.getChannelData(0);
+ renderedRight = renderedBuffer.getChannelData(1);
+
+ // The max error we allow between the rendered impulse and the
+ // expected value. This value is experimentally determined. Set
+ // to 0 to make the test fail to see what the actual error is.
+ var maxAllowedError = 1.3e-6;
+
+ var success = true;
+
+ // Number of impulses found in the rendered result.
+ var impulseCount = 0;
+
+ // Max (relative) error and the index of the maxima for the left
+ // and right channels.
+ var maxErrorL = 0;
+ var maxErrorIndexL = 0;
+ var maxErrorR = 0;
+ var maxErrorIndexR = 0;
+
+ // Number of impulses that don't match our expected locations.
+ var timeCount = 0;
+
+ // Locations of where the impulses aren't at the expected locations.
+ var timeErrors = new Array();
+
+ for (var k = 0; k < renderedLeft.length; ++k) {
+ // We assume that the left and right channels start at the same instant.
+ if (renderedLeft[k] != 0 || renderedRight[k] != 0) {
+ // The expected gain for the left and right channels.
+ var pannerGain = equalPowerGain(position[impulseCount].angle);
+ var expectedL = pannerGain.left;
+ var expectedR = pannerGain.right;
+
+ // Absolute error in the gain.
+ var errorL = Math.abs(renderedLeft[k] - expectedL);
+ var errorR = Math.abs(renderedRight[k] - expectedR);
+
+ if (Math.abs(errorL) > maxErrorL) {
+ maxErrorL = Math.abs(errorL);
+ maxErrorIndexL = impulseCount;
+ }
+ if (Math.abs(errorR) > maxErrorR) {
+ maxErrorR = Math.abs(errorR);
+ maxErrorIndexR = impulseCount;
+ }
+
+ // Keep track of the impulses that didn't show up where we
+ // expected them to be.
+ var expectedOffset = timeToSampleFrame(time[impulseCount], sampleRate);
+ if (k != expectedOffset) {
+ timeErrors[timeCount] = { actual : k, expected : expectedOffset};
+ ++timeCount;
+ }
+ ++impulseCount;
+ }
+ }
+
+ if (impulseCount == nodesToCreate) {
+ testPassed("Number of impulses matches the number of panner nodes.");
+ } else {
+ testFailed("Number of impulses is incorrect. (Found " + impulseCount + " but expected " + nodesToCreate + ")");
+ success = false;
+ }
+
+ if (timeErrors.length > 0) {
+ success = false;
+ testFailed(timeErrors.length + " timing errors found in " + nodesToCreate + " panner nodes.");
+ for (var k = 0; k < timeErrors.length; ++k) {
+ testFailed("Impulse at sample " + timeErrors[k].actual + " but expected " + timeErrors[k].expected);
+ }
+ } else {
+ testPassed("All impulses at expected offsets.");
+ }
+
+ if (maxErrorL <= maxAllowedError) {
+ testPassed("Left channel gain values are correct.");
+ } else {
+ testFailed("Left channel gain values are incorrect. Max error = " + maxErrorL + " at time " + time[maxErrorIndexL] + " (threshold = " + maxAllowedError + ")");
+ success = false;
+ }
+
+ if (maxErrorR <= maxAllowedError) {
+ testPassed("Right channel gain values are correct.");
+ } else {
+ testFailed("Right channel gain values are incorrect. Max error = " + maxErrorR + " at time " + time[maxErrorIndexR] + " (threshold = " + maxAllowedError + ")");
+ success = false;
+ }
+
+ if (success) {
+ testPassed("EqualPower panner test passed");
+ } else {
+ testFailed("EqualPower panner test failed");
+ }
+
+ finishJSTest();
+}
diff --git a/dom/media/webaudio/test/blink/test_biquadFilterNodeAllPass.html b/dom/media/webaudio/test/blink/test_biquadFilterNodeAllPass.html
new file mode 100644
index 0000000000..024c2f50df
--- /dev/null
+++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeAllPass.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test BiquadFilterNode All Pass Filter</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script src="audio-testing.js"></script>
+<script src="biquad-filters.js"></script>
+<script src="biquad-testing.js"></script>
+<script src="webaudio.js" type="text/javascript"></script>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ // Create offline audio context.
+ var context = new OfflineAudioContext(2, sampleRate * renderLengthSeconds, sampleRate);
+
+ var filterParameters = [{cutoff : 0, q : 10, gain : 1 },
+ {cutoff : 1, q : 10, gain : 1 },
+ {cutoff : .5, q : 0, gain : 1 },
+ {cutoff : 0.25, q : 10, gain : 1 },
+ ];
+ createTestAndRun(context, "allpass", filterParameters);
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/blink/test_biquadFilterNodeAutomation.html b/dom/media/webaudio/test/blink/test_biquadFilterNodeAutomation.html
new file mode 100644
index 0000000000..5a71ce46e5
--- /dev/null
+++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeAutomation.html
@@ -0,0 +1,351 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test BiquadFilterNode All Pass Filter</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script src="audio-testing.js"></script>
+<script src="biquad-filters.js"></script>
+<script src="biquad-testing.js"></script>
+<script src="webaudio.js" type="text/javascript"></script>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ // Don't need to run these tests at high sampling rate, so just use a low one to reduce memory
+ // usage and complexity.
+ var sampleRate = 16000;
+
+ // How long to render for each test.
+ var renderDuration = 1;
+
+ // The definition of the linear ramp automation function.
+ function linearRamp(t, v0, v1, t0, t1) {
+ return v0 + (v1 - v0) * (t - t0) / (t1 - t0);
+ }
+
+ // Generate the filter coefficients for the specified filter using the given parameters for
+ // the given duration. |filterTypeFunction| is a function that returns the filter
+ // coefficients for one set of parameters. |parameters| is a property bag that contains the
+ // start and end values (as an array) for each of the biquad attributes. The properties are
+ // |freq|, |Q|, |gain|, and |detune|. |duration| is the number of seconds for which the
+ // coefficients are generated.
+ //
+ // A property bag with properties |b0|, |b1|, |b2|, |a1|, |a2|. Each propery is an array
+ // consisting of the coefficients for the time-varying biquad filter.
+ function generateFilterCoefficients(filterTypeFunction, parameters, duration) {
+ var endFrame = Math.ceil(duration * sampleRate);
+ var nCoef = endFrame;
+ var b0 = new Float64Array(nCoef);
+ var b1 = new Float64Array(nCoef);
+ var b2 = new Float64Array(nCoef);
+ var a1 = new Float64Array(nCoef);
+ var a2 = new Float64Array(nCoef);
+
+ var k = 0;
+ // If the property is not given, use the defaults.
+ var freqs = parameters.freq || [350, 350];
+ var qs = parameters.Q || [1, 1];
+ var gains = parameters.gain || [0, 0];
+ var detunes = parameters.detune || [0, 0];
+
+ for (var frame = 0; frame < endFrame; ++frame) {
+ // Apply linear ramp at frame |frame|.
+ var f = linearRamp(frame / sampleRate, freqs[0], freqs[1], 0, duration);
+ var q = linearRamp(frame / sampleRate, qs[0], qs[1], 0, duration);
+ var g = linearRamp(frame / sampleRate, gains[0], gains[1], 0, duration);
+ var d = linearRamp(frame / sampleRate, detunes[0], detunes[1], 0, duration);
+
+ // Compute actual frequency parameter
+ f = f * Math.pow(2, d / 1200);
+
+ // Compute filter coefficients
+ var coef = filterTypeFunction(f / (sampleRate / 2), q, g);
+ b0[k] = coef.b0;
+ b1[k] = coef.b1;
+ b2[k] = coef.b2;
+ a1[k] = coef.a1;
+ a2[k] = coef.a2;
+ ++k;
+ }
+
+ return {b0: b0, b1: b1, b2: b2, a1: a1, a2: a2};
+ }
+
+ // Apply the given time-varying biquad filter to the given signal, |signal|. |coef| should be
+ // the time-varying coefficients of the filter, as returned by |generateFilterCoefficients|.
+ function timeVaryingFilter(signal, coef) {
+ var length = signal.length;
+ // Use double precision for the internal computations.
+ var y = new Float64Array(length);
+
+ // Prime the pump. (Assumes the signal has length >= 2!)
+ y[0] = coef.b0[0] * signal[0];
+ y[1] = coef.b0[1] * signal[1] + coef.b1[1] * signal[0] - coef.a1[1] * y[0];
+
+ for (var n = 2; n < length; ++n) {
+ y[n] = coef.b0[n] * signal[n] + coef.b1[n] * signal[n-1] + coef.b2[n] * signal[n-2];
+ y[n] -= coef.a1[n] * y[n-1] + coef.a2[n] * y[n-2];
+ }
+
+ // But convert the result to single precision for comparison.
+ return y.map(Math.fround);
+ }
+
+ // Configure the audio graph using |context|. Returns the biquad filter node and the
+ // AudioBuffer used for the source.
+ function configureGraph(context, toneFrequency) {
+ // The source is just a simple sine wave.
+ var src = context.createBufferSource();
+ var b = context.createBuffer(1, renderDuration * sampleRate, sampleRate);
+ var data = b.getChannelData(0);
+ var omega = 2 * Math.PI * toneFrequency / sampleRate;
+ for (var k = 0; k < data.length; ++k) {
+ data[k] = Math.sin(omega * k);
+ }
+ src.buffer = b;
+ var f = context.createBiquadFilter();
+ src.connect(f);
+ f.connect(context.destination);
+
+ src.start();
+
+ return {filter: f, source: b};
+ }
+
+ function createFilterVerifier(filterCreator, threshold, parameters, input, message) {
+ return function (resultBuffer) {
+ var actual = resultBuffer.getChannelData(0);
+ var coefs = generateFilterCoefficients(filterCreator, parameters, renderDuration);
+
+ reference = timeVaryingFilter(input, coefs);
+
+ compareChannels(actual, reference);
+ };
+ }
+
+ var testPromises = [];
+
+ // Automate just the frequency parameter. A bandpass filter is used where the center
+ // frequency is swept across the source (which is a simple tone).
+ testPromises.push(function () {
+ var context = new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate);
+
+ // Center frequency of bandpass filter and also the frequency of the test tone.
+ var centerFreq = 10*440;
+
+ // Sweep the frequency +/- 9*440 Hz from the center. This should cause the output to low at
+ // the beginning and end of the test where the done is outside the pass band of the filter,
+ // but high in the center where the tone is near the center of the pass band.
+ var parameters = {
+ freq: [centerFreq - 9*440, centerFreq + 9*440]
+ }
+ var graph = configureGraph(context, centerFreq);
+ var f = graph.filter;
+ var b = graph.source;
+
+ f.type = "bandpass";
+ f.frequency.setValueAtTime(parameters.freq[0], 0);
+ f.frequency.linearRampToValueAtTime(parameters.freq[1], renderDuration);
+
+ return context.startRendering()
+ .then(createFilterVerifier(createBandpassFilter, 5e-5, parameters, b.getChannelData(0),
+ "Output of bandpass filter with frequency automation"));
+ }());
+
+ // Automate just the Q parameter. A bandpass filter is used where the Q of the filter is
+ // swept.
+ testPromises.push(function() {
+ var context = new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate);
+
+ // The frequency of the test tone.
+ var centerFreq = 440;
+
+ // Sweep the Q paramter between 1 and 200. This will cause the output of the filter to pass
+ // most of the tone at the beginning to passing less of the tone at the end. This is
+ // because we set center frequency of the bandpass filter to be slightly off from the actual
+ // tone.
+ var parameters = {
+ Q: [1, 200],
+ // Center frequency of the bandpass filter is just 25 Hz above the tone frequency.
+ freq: [centerFreq + 25, centerFreq + 25]
+ };
+ var graph = configureGraph(context, centerFreq);
+ var f = graph.filter;
+ var b = graph.source;
+
+ f.type = "bandpass";
+ f.frequency.value = parameters.freq[0];
+ f.Q.setValueAtTime(parameters.Q[0], 0);
+ f.Q.linearRampToValueAtTime(parameters.Q[1], renderDuration);
+
+ return context.startRendering()
+ .then(createFilterVerifier(createBandpassFilter, 1.4e-6, parameters, b.getChannelData(0),
+ "Output of bandpass filter with Q automation"));
+ }());
+
+ // Automate just the gain of the lowshelf filter. A test tone will be in the lowshelf part of
+ // the filter. The output will vary as the gain of the lowshelf is changed.
+ testPromises.push(function() {
+ var context = new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate);
+
+ // Frequency of the test tone.
+ var centerFreq = 440;
+
+ // Set the cutoff frequency of the lowshelf to be significantly higher than the test tone.
+ // Sweep the gain from 20 dB to -20 dB. (We go from 20 to -20 to easily verify that the
+ // filter didn't go unstable.)
+ var parameters = {
+ freq: [3500, 3500],
+ gain: [20, -20]
+ }
+ var graph = configureGraph(context, centerFreq);
+ var f = graph.filter;
+ var b = graph.source;
+
+ f.type = "lowshelf";
+ f.frequency.value = parameters.freq[0];
+ f.gain.setValueAtTime(parameters.gain[0], 0);
+ f.gain.linearRampToValueAtTime(parameters.gain[1], renderDuration);
+
+ context.startRendering()
+ .then(createFilterVerifier(createLowShelfFilter, 8e-6, parameters, b.getChannelData(0),
+ "Output of lowshelf filter with gain automation"));
+ }());
+
+ // Automate just the detune parameter. Basically the same test as for the frequncy parameter
+ // but we just use the detune parameter to modulate the frequency parameter.
+ testPromises.push(function() {
+ var context = new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate);
+ var centerFreq = 10*440;
+ var parameters = {
+ freq: [centerFreq, centerFreq],
+ detune: [-10*1200, 10*1200]
+ };
+ var graph = configureGraph(context, centerFreq);
+ var f = graph.filter;
+ var b = graph.source;
+
+ f.type = "bandpass";
+ f.frequency.value = parameters.freq[0];
+ f.detune.setValueAtTime(parameters.detune[0], 0);
+ f.detune.linearRampToValueAtTime(parameters.detune[1], renderDuration);
+
+ context.startRendering()
+ .then(createFilterVerifier(createBandpassFilter, 5e-6, parameters, b.getChannelData(0),
+ "Output of bandpass filter with detune automation"));
+ }());
+
+ // Automate all of the filter parameters at once. This is a basic check that everything is
+ // working. A peaking filter is used because it uses all of the parameters.
+ testPromises.push(function() {
+ var context = new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate);
+ var graph = configureGraph(context, 10*440);
+ var f = graph.filter;
+ var b = graph.source;
+
+ // Sweep all of the filter parameters. These are pretty much arbitrary.
+ var parameters = {
+ freq: [10000, 100],
+ Q: [f.Q.value, .0001],
+ gain: [f.gain.value, 20],
+ detune: [2400, -2400]
+ };
+
+ f.type = "peaking";
+ // Set starting points for all parameters of the filter. Start at 10 kHz for the center
+ // frequency, and the defaults for Q and gain.
+ f.frequency.setValueAtTime(parameters.freq[0], 0);
+ f.Q.setValueAtTime(parameters.Q[0], 0);
+ f.gain.setValueAtTime(parameters.gain[0], 0);
+ f.detune.setValueAtTime(parameters.detune[0], 0);
+
+ // Linear ramp each parameter
+ f.frequency.linearRampToValueAtTime(parameters.freq[1], renderDuration);
+ f.Q.linearRampToValueAtTime(parameters.Q[1], renderDuration);
+ f.gain.linearRampToValueAtTime(parameters.gain[1], renderDuration);
+ f.detune.linearRampToValueAtTime(parameters.detune[1], renderDuration);
+
+ context.startRendering()
+ .then(createFilterVerifier(createPeakingFilter, 3.3e-4, parameters, b.getChannelData(0),
+ "Output of peaking filter with automation of all parameters"));
+ }());
+
+ // Test that modulation of the frequency parameter of the filter works. A sinusoid of 440 Hz
+ // is the test signal that is applied to a bandpass biquad filter. The frequency parameter of
+ // the filter is modulated by a sinusoid at 103 Hz, and the frequency modulation varies from
+ // 116 to 412 Hz. (This test was taken from the description in
+ // https://github.com/WebAudio/web-audio-api/issues/509#issuecomment-94731355)
+ testPromises.push(function() {
+ var context = new OfflineAudioContext(1, renderDuration * sampleRate, sampleRate);
+
+ // Create a graph with the sinusoidal source at 440 Hz as the input to a biquad filter.
+ var graph = configureGraph(context, 440);
+ var f = graph.filter;
+ var b = graph.source;
+
+ f.type = "bandpass";
+ f.Q.value = 5;
+ f.frequency.value = 264;
+
+ // Create the modulation source, a sinusoid with frequency 103 Hz and amplitude 148. (The
+ // amplitude of 148 is added to the filter's frequency value of 264 to produce a sinusoidal
+ // modulation of the frequency parameter from 116 to 412 Hz.)
+ var mod = context.createBufferSource();
+ var mbuffer = context.createBuffer(1, renderDuration * sampleRate, sampleRate);
+ var d = mbuffer.getChannelData(0);
+ var omega = 2 * Math.PI * 103 / sampleRate;
+ for (var k = 0; k < d.length; ++k) {
+ d[k] = 148 * Math.sin(omega * k);
+ }
+ mod.buffer = mbuffer;
+
+ mod.connect(f.frequency);
+
+ mod.start();
+ return context.startRendering()
+ .then(function (resultBuffer) {
+ var actual = resultBuffer.getChannelData(0);
+ // Compute the filter coefficients using the mod sine wave
+
+ var endFrame = Math.ceil(renderDuration * sampleRate);
+ var nCoef = endFrame;
+ var b0 = new Float64Array(nCoef);
+ var b1 = new Float64Array(nCoef);
+ var b2 = new Float64Array(nCoef);
+ var a1 = new Float64Array(nCoef);
+ var a2 = new Float64Array(nCoef);
+
+ // Generate the filter coefficients when the frequency varies from 116 to 248 Hz using
+ // the 103 Hz sinusoid.
+ for (var k = 0; k < nCoef; ++k) {
+ var freq = f.frequency.value + d[k];
+ var c = createBandpassFilter(freq / (sampleRate / 2), f.Q.value, f.gain.value);
+ b0[k] = c.b0;
+ b1[k] = c.b1;
+ b2[k] = c.b2;
+ a1[k] = c.a1;
+ a2[k] = c.a2;
+ }
+ reference = timeVaryingFilter(b.getChannelData(0),
+ {b0: b0, b1: b1, b2: b2, a1: a1, a2: a2});
+
+ compareChannels(actual, reference);
+ });
+ }());
+
+ // Wait for all tests
+ Promise.all(testPromises).then(function () {
+ SimpleTest.finish();
+ }, function () {
+ SimpleTest.finish();
+ });
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/blink/test_biquadFilterNodeBandPass.html b/dom/media/webaudio/test/blink/test_biquadFilterNodeBandPass.html
new file mode 100644
index 0000000000..05c4c3a265
--- /dev/null
+++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeBandPass.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test BiquadFilterNode Band Pass Filter</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script src="audio-testing.js"></script>
+<script src="biquad-filters.js"></script>
+<script src="biquad-testing.js"></script>
+<script src="webaudio.js" type="text/javascript"></script>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ // Create offline audio context.
+ var context = new OfflineAudioContext(2, sampleRate * renderLengthSeconds, sampleRate);
+
+ // The filters we want to test.
+ var filterParameters = [{cutoff : 0, q : 0, gain : 1 },
+ {cutoff : 1, q : 0, gain : 1 },
+ {cutoff : 0.5, q : 0, gain : 1 },
+ {cutoff : 0.25, q : 1, gain : 1 },
+ ];
+
+ createTestAndRun(context, "bandpass", filterParameters);
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/blink/test_biquadFilterNodeGetFrequencyResponse.html b/dom/media/webaudio/test/blink/test_biquadFilterNodeGetFrequencyResponse.html
new file mode 100644
index 0000000000..c2b6612034
--- /dev/null
+++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeGetFrequencyResponse.html
@@ -0,0 +1,261 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test BiquadFilterNode All Pass Filter</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script src="audio-testing.js"></script>
+<script src="biquad-filters.js"></script>
+<script src="biquad-testing.js"></script>
+<script src="webaudio.js" type="text/javascript"></script>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+// Test the frequency response of a biquad filter. We compute the frequency response for a simple
+// peaking biquad filter and compare it with the expected frequency response. The actual filter
+// used doesn't matter since we're testing getFrequencyResponse and not the actual filter output.
+// The filters are extensively tested in other biquad tests.
+
+var context;
+
+// The biquad filter node.
+var filter;
+
+// The magnitude response of the biquad filter.
+var magResponse;
+
+// The phase response of the biquad filter.
+var phaseResponse;
+
+// Number of frequency samples to take.
+var numberOfFrequencies = 1000;
+
+// The filter parameters.
+var filterCutoff = 1000; // Hz.
+var filterQ = 1;
+var filterGain = 5; // Decibels.
+
+// The maximum allowed error in the magnitude response.
+var maxAllowedMagError = 5.7e-7;
+
+// The maximum allowed error in the phase response.
+var maxAllowedPhaseError = 4.7e-8;
+
+// The magnitudes and phases of the reference frequency response.
+var magResponse;
+var phaseResponse;
+
+// The magnitudes and phases of the reference frequency response.
+var expectedMagnitudes;
+var expectedPhases;
+
+// Convert frequency in Hz to a normalized frequency between 0 to 1 with 1 corresponding to the
+// Nyquist frequency.
+function normalizedFrequency(freqHz, sampleRate)
+{
+ var nyquist = sampleRate / 2;
+ return freqHz / nyquist;
+}
+
+// Get the filter response at a (normalized) frequency |f| for the filter with coefficients |coef|.
+function getResponseAt(coef, f)
+{
+ var b0 = coef.b0;
+ var b1 = coef.b1;
+ var b2 = coef.b2;
+ var a1 = coef.a1;
+ var a2 = coef.a2;
+
+ // H(z) = (b0 + b1 / z + b2 / z^2) / (1 + a1 / z + a2 / z^2)
+ //
+ // Compute H(exp(i * pi * f)). No native complex numbers in javascript, so break H(exp(i * pi * // f))
+ // in to the real and imaginary parts of the numerator and denominator. Let omega = pi * f.
+ // Then the numerator is
+ //
+ // b0 + b1 * cos(omega) + b2 * cos(2 * omega) - i * (b1 * sin(omega) + b2 * sin(2 * omega))
+ //
+ // and the denominator is
+ //
+ // 1 + a1 * cos(omega) + a2 * cos(2 * omega) - i * (a1 * sin(omega) + a2 * sin(2 * omega))
+ //
+ // Compute the magnitude and phase from the real and imaginary parts.
+
+ var omega = Math.PI * f;
+ var numeratorReal = b0 + b1 * Math.cos(omega) + b2 * Math.cos(2 * omega);
+ var numeratorImag = -(b1 * Math.sin(omega) + b2 * Math.sin(2 * omega));
+ var denominatorReal = 1 + a1 * Math.cos(omega) + a2 * Math.cos(2 * omega);
+ var denominatorImag = -(a1 * Math.sin(omega) + a2 * Math.sin(2 * omega));
+
+ var magnitude = Math.sqrt((numeratorReal * numeratorReal + numeratorImag * numeratorImag)
+ / (denominatorReal * denominatorReal + denominatorImag * denominatorImag));
+ var phase = Math.atan2(numeratorImag, numeratorReal) - Math.atan2(denominatorImag, denominatorReal);
+
+ if (phase >= Math.PI) {
+ phase -= 2 * Math.PI;
+ } else if (phase <= -Math.PI) {
+ phase += 2 * Math.PI;
+ }
+
+ return {magnitude : magnitude, phase : phase};
+}
+
+// Compute the reference frequency response for the biquad filter |filter| at the frequency samples
+// given by |frequencies|.
+function frequencyResponseReference(filter, frequencies)
+{
+ var sampleRate = filter.context.sampleRate;
+ var normalizedFreq = normalizedFrequency(filter.frequency.value, sampleRate);
+ var filterCoefficients = createFilter(filter.type, normalizedFreq, filter.Q.value, filter.gain.value);
+
+ var magnitudes = [];
+ var phases = [];
+
+ for (var k = 0; k < frequencies.length; ++k) {
+ var response = getResponseAt(filterCoefficients, normalizedFrequency(frequencies[k], sampleRate));
+ magnitudes.push(response.magnitude);
+ phases.push(response.phase);
+ }
+
+ return {magnitudes : magnitudes, phases : phases};
+}
+
+// Compute a set of linearly spaced frequencies.
+function createFrequencies(nFrequencies, sampleRate)
+{
+ var frequencies = new Float32Array(nFrequencies);
+ var nyquist = sampleRate / 2;
+ var freqDelta = nyquist / nFrequencies;
+
+ for (var k = 0; k < nFrequencies; ++k) {
+ frequencies[k] = k * freqDelta;
+ }
+
+ return frequencies;
+}
+
+function linearToDecibels(x)
+{
+ if (x) {
+ return 20 * Math.log(x) / Math.LN10;
+ } else {
+ return -1000;
+ }
+}
+
+// Look through the array and find any NaN or infinity. Returns the index of the first occurence or
+// -1 if none.
+function findBadNumber(signal)
+{
+ for (var k = 0; k < signal.length; ++k) {
+ if (!isValidNumber(signal[k])) {
+ return k;
+ }
+ }
+ return -1;
+}
+
+// Compute absolute value of the difference between phase angles, taking into account the wrapping
+// of phases.
+function absolutePhaseDifference(x, y)
+{
+ var diff = Math.abs(x - y);
+
+ if (diff > Math.PI) {
+ diff = 2 * Math.PI - diff;
+ }
+ return diff;
+}
+
+// Compare the frequency response with our expected response.
+function compareResponses(filter, frequencies, magResponse, phaseResponse)
+{
+ var expectedResponse = frequencyResponseReference(filter, frequencies);
+
+ expectedMagnitudes = expectedResponse.magnitudes;
+ expectedPhases = expectedResponse.phases;
+
+ var n = magResponse.length;
+ var success = true;
+ var badResponse = false;
+
+ var maxMagError = -1;
+ var maxMagErrorIndex = -1;
+
+ var k;
+ var hasBadNumber;
+
+ hasBadNumber = findBadNumber(magResponse);
+ ok (hasBadNumber < 0, "Magnitude response has NaN or infinity at " + hasBadNumber);
+
+ hasBadNumber = findBadNumber(phaseResponse);
+ ok (hasBadNumber < 0, "Phase response has NaN or infinity at " + hasBadNumber);
+
+ // These aren't testing the implementation itself. Instead, these are sanity checks on the
+ // reference. Failure here does not imply an error in the implementation.
+ hasBadNumber = findBadNumber(expectedMagnitudes);
+ ok (hasBadNumber < 0, "Expected magnitude response has NaN or infinity at " + hasBadNumber);
+
+ hasBadNumber = findBadNumber(expectedPhases);
+ ok (hasBadNumber < 0, "Expected phase response has NaN or infinity at " + hasBadNumber);
+
+ for (k = 0; k < n; ++k) {
+ var error = Math.abs(linearToDecibels(magResponse[k]) - linearToDecibels(expectedMagnitudes[k]));
+ if (error > maxMagError) {
+ maxMagError = error;
+ maxMagErrorIndex = k;
+ }
+ }
+
+ var message = "Magnitude error (" + maxMagError + " dB)";
+ message += " exceeded threshold at " + frequencies[maxMagErrorIndex];
+ message += " Hz. Actual: " + linearToDecibels(magResponse[maxMagErrorIndex]);
+ message += " dB, expected: " + linearToDecibels(expectedMagnitudes[maxMagErrorIndex]) + " dB.";
+ ok(maxMagError < maxAllowedMagError, message);
+
+ var maxPhaseError = -1;
+ var maxPhaseErrorIndex = -1;
+
+ for (k = 0; k < n; ++k) {
+ var error = absolutePhaseDifference(phaseResponse[k], expectedPhases[k]);
+ if (error > maxPhaseError) {
+ maxPhaseError = error;
+ maxPhaseErrorIndex = k;
+ }
+ }
+
+ message = "Phase error (radians) (" + maxPhaseError;
+ message += ") exceeded threshold at " + frequencies[maxPhaseErrorIndex];
+ message += " Hz. Actual: " + phaseResponse[maxPhaseErrorIndex];
+ message += " expected: " + expectedPhases[maxPhaseErrorIndex];
+
+ ok(maxPhaseError < maxAllowedPhaseError, message);
+}
+
+context = new AudioContext();
+
+filter = context.createBiquadFilter();
+
+// Arbitrarily test a peaking filter, but any kind of filter can be tested.
+filter.type = "peaking";
+filter.frequency.value = filterCutoff;
+filter.Q.value = filterQ;
+filter.gain.value = filterGain;
+
+var frequencies = createFrequencies(numberOfFrequencies, context.sampleRate);
+magResponse = new Float32Array(numberOfFrequencies);
+phaseResponse = new Float32Array(numberOfFrequencies);
+
+filter.getFrequencyResponse(frequencies, magResponse, phaseResponse);
+compareResponses(filter, frequencies, magResponse, phaseResponse);
+
+SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/blink/test_biquadFilterNodeHighPass.html b/dom/media/webaudio/test/blink/test_biquadFilterNodeHighPass.html
new file mode 100644
index 0000000000..a615574c2d
--- /dev/null
+++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeHighPass.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test BiquadFilterNode High Pass Filter</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script src="audio-testing.js"></script>
+<script src="biquad-filters.js"></script>
+<script src="biquad-testing.js"></script>
+<script src="webaudio.js" type="text/javascript"></script>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ // Create offline audio context.
+ var context = new OfflineAudioContext(2, sampleRate * renderLengthSeconds, sampleRate);
+
+ // The filters we want to test.
+ var filterParameters = [{cutoff : 0, q : 1, gain : 1 },
+ {cutoff : 1, q : 1, gain : 1 },
+ {cutoff : 0.25, q : 1, gain : 1 },
+ ];
+
+ createTestAndRun(context, "highpass", filterParameters);
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/blink/test_biquadFilterNodeHighShelf.html b/dom/media/webaudio/test/blink/test_biquadFilterNodeHighShelf.html
new file mode 100644
index 0000000000..c8f6815930
--- /dev/null
+++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeHighShelf.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test BiquadFilterNode High Shelf Filter</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script src="audio-testing.js"></script>
+<script src="biquad-filters.js"></script>
+<script src="biquad-testing.js"></script>
+<script src="webaudio.js" type="text/javascript"></script>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ // Create offline audio context.
+ var context = new OfflineAudioContext(2, sampleRate * renderLengthSeconds, sampleRate);
+
+ // The filters we want to test.
+ var filterParameters = [{cutoff : 0, q : 10, gain : 10 },
+ {cutoff : 1, q : 10, gain : 10 },
+ {cutoff : 0.25, q : 10, gain : 10 },
+ ];
+
+ createTestAndRun(context, "highshelf", filterParameters);
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/blink/test_biquadFilterNodeLowPass.html b/dom/media/webaudio/test/blink/test_biquadFilterNodeLowPass.html
new file mode 100644
index 0000000000..dcea18551a
--- /dev/null
+++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeLowPass.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test BiquadFilterNode Low Pass Filter</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script src="audio-testing.js"></script>
+<script src="biquad-filters.js"></script>
+<script src="biquad-testing.js"></script>
+<script src="webaudio.js" type="text/javascript"></script>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ // Create offline audio context.
+ var context = new OfflineAudioContext(2, sampleRate * renderLengthSeconds, sampleRate);
+
+ // The filters we want to test.
+ var filterParameters = [{cutoff : 0, q : 1, gain : 1 },
+ {cutoff : 1, q : 1, gain : 1 },
+ {cutoff : 0.25, q : 1, gain : 1 },
+ {cutoff : 0.25, q : 1, gain : 1, detune : 100 },
+ {cutoff : 0.01, q : 1, gain : 1, detune : -200 },
+ ];
+ createTestAndRun(context, "lowpass", filterParameters);
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/blink/test_biquadFilterNodeLowShelf.html b/dom/media/webaudio/test/blink/test_biquadFilterNodeLowShelf.html
new file mode 100644
index 0000000000..c1349f8e7e
--- /dev/null
+++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeLowShelf.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test BiquadFilterNode Low Shelf Filter</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script src="audio-testing.js"></script>
+<script src="biquad-filters.js"></script>
+<script src="biquad-testing.js"></script>
+<script src="webaudio.js" type="text/javascript"></script>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+
+ // Create offline audio context.
+ var context = new OfflineAudioContext(2, sampleRate * renderLengthSeconds, sampleRate);
+
+ // The filters we want to test.
+ var filterParameters = [{cutoff : 0, q : 10, gain : 10 },
+ {cutoff : 1, q : 10, gain : 10 },
+ {cutoff : 0.25, q : 10, gain : 10 },
+ ];
+
+ createTestAndRun(context, "lowshelf", filterParameters);
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/blink/test_biquadFilterNodeNotch.html b/dom/media/webaudio/test/blink/test_biquadFilterNodeNotch.html
new file mode 100644
index 0000000000..0fcbc5546e
--- /dev/null
+++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeNotch.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test BiquadFilterNode Notch Filter</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script src="audio-testing.js"></script>
+<script src="biquad-filters.js"></script>
+<script src="biquad-testing.js"></script>
+<script src="webaudio.js" type="text/javascript"></script>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ // Create offline audio context.
+ var context = new OfflineAudioContext(2, sampleRate * renderLengthSeconds, sampleRate);
+
+ var filterParameters = [{cutoff : 0, q : 10, gain : 1 },
+ {cutoff : 1, q : 10, gain : 1 },
+ {cutoff : .5, q : 0, gain : 1 },
+ {cutoff : 0.25, q : 10, gain : 1 },
+ ];
+
+ createTestAndRun(context, "notch", filterParameters);
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/blink/test_biquadFilterNodePeaking.html b/dom/media/webaudio/test/blink/test_biquadFilterNodePeaking.html
new file mode 100644
index 0000000000..8e4727a37e
--- /dev/null
+++ b/dom/media/webaudio/test/blink/test_biquadFilterNodePeaking.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test BiquadFilterNode Low Pass Filter</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script src="audio-testing.js"></script>
+<script src="biquad-filters.js"></script>
+<script src="biquad-testing.js"></script>
+<script src="webaudio.js" type="text/javascript"></script>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ // Create offline audio context.
+ var context = new OfflineAudioContext(2, sampleRate * renderLengthSeconds, sampleRate);
+
+ // The filters we want to test.
+ var filterParameters = [{cutoff : 0, q : 10, gain : 10 },
+ {cutoff : 1, q : 10, gain : 10 },
+ {cutoff : .5, q : 0, gain : 10 },
+ {cutoff : 0.25, q : 10, gain : 10 },
+ ];
+
+ createTestAndRun(context, "peaking", filterParameters);
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/blink/test_biquadFilterNodeTail.html b/dom/media/webaudio/test/blink/test_biquadFilterNodeTail.html
new file mode 100644
index 0000000000..cc170df2a5
--- /dev/null
+++ b/dom/media/webaudio/test/blink/test_biquadFilterNodeTail.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test BiquadFilterNode All Pass Filter</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script src="audio-testing.js"></script>
+<script src="biquad-filters.js"></script>
+<script src="biquad-testing.js"></script>
+<script src="webaudio.js" type="text/javascript"></script>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ // A high sample rate shows the issue more clearly.
+ var sampleRate = 192000;
+ // Some short duration because we don't need to run the test for very long.
+ var testDurationSec = 0.5;
+ var testDurationFrames = testDurationSec * sampleRate;
+
+ // Amplitude experimentally determined to give a biquad output close to 1. (No attempt was
+ // made to produce exactly 1; it's not needed.)
+ var sourceAmplitude = 100;
+
+ // The output of the biquad filter should not change by more than this much between output
+ // samples. Threshold was determined experimentally.
+ var glitchThreshold = 0.01292;
+
+ // Test that a Biquad filter doesn't have it's output terminated because the input has gone
+ // away. Generally, when a source node is finished, it disconnects itself from any downstream
+ // nodes. This is the correct behavior. Nodes that have no inputs (disconnected) are
+ // generally assumed to output zeroes. This is also desired behavior. However, biquad
+ // filters have memory so they should not suddenly output zeroes when the input is
+ // disconnected. This test checks to see if the output doesn't suddenly change to zero.
+ var context = new OfflineAudioContext(1, testDurationFrames, sampleRate);
+
+ // Create an impulse source.
+ var buffer = context.createBuffer(1, 1, context.sampleRate);
+ buffer.getChannelData(0)[0] = sourceAmplitude;
+ var source = context.createBufferSource();
+ source.buffer = buffer;
+
+ // Create the biquad filter. It doesn't really matter what kind, so the default filter type
+ // and parameters is fine. Connect the source to it.
+ var biquad = context.createBiquadFilter();
+ source.connect(biquad);
+ biquad.connect(context.destination);
+
+ source.start();
+
+ context.startRendering().then(function(result) {
+ // There should be no large discontinuities in the output
+ var buffer = result.getChannelData(0);
+ var maxGlitchIndex = 0;
+ var maxGlitchValue = 0.0;
+ for (var i = 1; i < buffer.length; i++) {
+ var diff = Math.abs(buffer[i-1] - buffer[i]);
+ if (diff >= glitchThreshold) {
+ if (diff > maxGlitchValue) {
+ maxGlitchIndex = i;
+ maxGlitchValue = diff;
+ }
+ }
+ }
+ ok(maxGlitchIndex == 0, 'glitches detected in biquad output: maximum glitch at ' + maxGlitchIndex + ' with diff of ' + maxGlitchValue);
+ SimpleTest.finish();
+ })
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/blink/test_iirFilterNode.html b/dom/media/webaudio/test/blink/test_iirFilterNode.html
new file mode 100644
index 0000000000..0ef7a37e3b
--- /dev/null
+++ b/dom/media/webaudio/test/blink/test_iirFilterNode.html
@@ -0,0 +1,467 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test IIRFilterNode GetFrequencyResponse</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <script type="text/javascript" src="biquad-filters.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ var sampleRate = 48000;
+ var testDurationSec = 1;
+ var testFrames = testDurationSec * sampleRate;
+
+ var testPromises = []
+ testPromises.push(function () {
+ // Test that the feedback coefficients are normalized. Do this be creating two
+ // IIRFilterNodes. One has normalized coefficients, and one doesn't. Compute the
+ // difference and make sure they're the same.
+ var context = new OfflineAudioContext(2, testFrames, sampleRate);
+
+ // Use a simple impulse as the source.
+ var buffer = context.createBuffer(1, 1, sampleRate);
+ buffer.getChannelData(0)[0] = 1;
+ var source = context.createBufferSource();
+ source.buffer = buffer;
+
+ // Gain node for computing the difference between the filters.
+ var gain = context.createGain();
+ gain.gain.value = -1;
+
+ // The IIR filters. Use a common feedforward array.
+ var ff = [1];
+
+ var fb1 = [1, .9];
+
+ var fb2 = new Float64Array(2);
+ // Scale the feedback coefficients by an arbitrary factor.
+ var coefScaleFactor = 2;
+ for (var k = 0; k < fb2.length; ++k) {
+ fb2[k] = coefScaleFactor * fb1[k];
+ }
+
+ var iir1 = context.createIIRFilter(ff, fb1);
+ var iir2 = context.createIIRFilter(ff, fb2);
+
+ // Create the graph. The output of iir1 (normalized coefficients) is channel 0, and the
+ // output of iir2 (unnormalized coefficients), with appropriate scaling, is channel 1.
+ var merger = context.createChannelMerger(2);
+ source.connect(iir1);
+ source.connect(iir2);
+ iir1.connect(merger, 0, 0);
+ iir2.connect(gain);
+
+ // The gain for the gain node should be set to compensate for the scaling of the
+ // coefficients. Since iir2 has scaled the coefficients by coefScaleFactor, the output is
+ // reduced by the same factor, so adjust the gain to scale the output of iir2 back up.
+ gain.gain.value = coefScaleFactor;
+ gain.connect(merger, 0, 1);
+
+ merger.connect(context.destination);
+
+ source.start();
+
+ // Rock and roll!
+
+ return context.startRendering().then(function (result) {
+ // Find the max amplitude of the result, which should be near zero.
+ var iir1Data = result.getChannelData(0);
+ var iir2Data = result.getChannelData(1);
+
+ // Threshold isn't exactly zero because the arithmetic is done differently between the
+ // IIRFilterNode and the BiquadFilterNode.
+ compareChannels(iir1Data, iir2Data);
+ });
+ }());
+
+ testPromises.push(function () {
+ // Create a simple 1-zero filter and compare with the expected output.
+ var context = new OfflineAudioContext(1, testFrames, sampleRate);
+
+ // Use a simple impulse as the source
+ var buffer = context.createBuffer(1, 1, sampleRate);
+ buffer.getChannelData(0)[0] = 1;
+ var source = context.createBufferSource();
+ source.buffer = buffer;
+
+ // The filter is y(n) = 0.5*(x(n) + x(n-1)), a simple 2-point moving average. This is
+ // rather arbitrary; keep it simple.
+
+ var iir = context.createIIRFilter([0.5, 0.5], [1]);
+
+ // Create the graph
+ source.connect(iir);
+ iir.connect(context.destination);
+
+ // Rock and roll!
+ source.start();
+
+ return context.startRendering().then(function (result) {
+ var actual = result.getChannelData(0);
+ var expected = new Float64Array(testFrames);
+ // The filter is a simple 2-point moving average of an impulse, so the first two values
+ // are non-zero and the rest are zero.
+ expected[0] = 0.5;
+ expected[1] = 0.5;
+ compareChannels(actual, expected);
+ });
+ }());
+
+ testPromises.push(function () {
+ // Create a simple 1-pole filter and compare with the expected output.
+
+ // The filter is y(n) + c*y(n-1)= x(n). The analytical response is (-c)^n, so choose a
+ // suitable number of frames to run the test for where the output isn't flushed to zero.
+ var c = 0.9;
+ var eps = 1e-20;
+ var duration = Math.floor(Math.log(eps) / Math.log(Math.abs(c)));
+ var context = new OfflineAudioContext(1, duration, sampleRate);
+
+ // Use a simple impulse as the source
+ var buffer = context.createBuffer(1, 1, sampleRate);
+ buffer.getChannelData(0)[0] = 1;
+ var source = context.createBufferSource();
+ source.buffer = buffer;
+
+ var iir = context.createIIRFilter([1], [1, c]);
+
+ // Create the graph
+ source.connect(iir);
+ iir.connect(context.destination);
+
+ // Rock and roll!
+ source.start();
+
+ return context.startRendering().then(function (result) {
+ var actual = result.getChannelData(0);
+ var expected = new Float64Array(actual.length);
+
+ // The filter is a simple 1-pole filter: y(n) = -c*y(n-k)+x(n), with an impulse as the
+ // input.
+ expected[0] = 1;
+ for (k = 1; k < testFrames; ++k) {
+ expected[k] = -c * expected[k-1];
+ }
+
+ compareChannels(actual, expected);
+ });
+ }());
+
+ // This function creates an IIRFilterNode equivalent to the specified
+ // BiquadFilterNode and compares the outputs. The
+ // outputs from the two filters should be virtually identical.
+ function testWithBiquadFilter(filterType) {
+ var context = new OfflineAudioContext(2, testFrames, sampleRate);
+
+ // Use a constant (step function) as the source
+ var buffer = context.createBuffer(1, testFrames, context.sampleRate);
+ for (var i = 0; i < testFrames; ++i) {
+ buffer.getChannelData(0)[i] = 1;
+ }
+ var source = context.createBufferSource();
+ source.buffer = buffer;
+
+ // Create the biquad. Choose some rather arbitrary values for Q and gain for the biquad
+ // so that the shelf filters aren't identical.
+ var biquad = context.createBiquadFilter();
+ biquad.type = filterType;
+ biquad.Q.value = 10;
+ biquad.gain.value = 10;
+
+ // Create the equivalent IIR Filter node by computing the coefficients of the given biquad
+ // filter type.
+ var nyquist = sampleRate / 2;
+ var coef = createFilter(filterType,
+ biquad.frequency.value / nyquist,
+ biquad.Q.value,
+ biquad.gain.value);
+
+ var iir = context.createIIRFilter([coef.b0, coef.b1, coef.b2], [1, coef.a1, coef.a2]);
+
+ var merger = context.createChannelMerger(2);
+ // Create the graph
+ source.connect(biquad);
+ source.connect(iir);
+
+ biquad.connect(merger, 0, 0);
+ iir.connect(merger, 0, 1);
+
+ merger.connect(context.destination);
+
+ // Rock and roll!
+ source.start();
+
+ return context.startRendering().then(function (result) {
+ // Find the max amplitude of the result, which should be near zero.
+ var expected = result.getChannelData(0);
+ var actual = result.getChannelData(1);
+ compareChannels(actual, expected);
+ });
+ }
+
+ biquadFilterTypes = ["lowpass", "highpass", "bandpass", "notch",
+ "allpass", "lowshelf", "highshelf", "peaking"];
+
+ // Create a set of tasks based on biquadTestConfigs.
+ for (var i = 0; i < biquadFilterTypes.length; ++i) {
+ testPromises.push(testWithBiquadFilter(biquadFilterTypes[i]));
+ }
+
+ testPromises.push(function () {
+ // Multi-channel test. Create a biquad filter and the equivalent IIR filter. Filter the
+ // same multichannel signal and compare the results.
+ var nChannels = 3;
+ var context = new OfflineAudioContext(nChannels, testFrames, sampleRate);
+
+ // Create a set of oscillators as the multi-channel source.
+ var source = [];
+
+ for (k = 0; k < nChannels; ++k) {
+ source[k] = context.createOscillator();
+ source[k].type = "sawtooth";
+ // The frequency of the oscillator is pretty arbitrary, but each oscillator should have a
+ // different frequency.
+ source[k].frequency.value = 100 + k * 100;
+ }
+
+ var merger = context.createChannelMerger(3);
+
+ var biquad = context.createBiquadFilter();
+
+ // Create the equivalent IIR Filter node.
+ var nyquist = sampleRate / 2;
+ var coef = createFilter(biquad.type,
+ biquad.frequency.value / nyquist,
+ biquad.Q.value,
+ biquad.gain.value);
+ var fb = [1, coef.a1, coef.a2];
+ var ff = [coef.b0, coef.b1, coef.b2];
+
+ var iir = context.createIIRFilter(ff, fb);
+ // Gain node to compute the difference between the IIR and biquad filter.
+ var gain = context.createGain();
+ gain.gain.value = -1;
+
+ // Create the graph.
+ for (k = 0; k < nChannels; ++k)
+ source[k].connect(merger, 0, k);
+
+ merger.connect(biquad);
+ merger.connect(iir);
+ iir.connect(gain);
+ biquad.connect(context.destination);
+ gain.connect(context.destination);
+
+ for (k = 0; k < nChannels; ++k)
+ source[k].start();
+
+ return context.startRendering().then(function (result) {
+ var errorThresholds = [3.7671e-5, 3.0071e-5, 2.6241e-5];
+
+ // Check the difference signal on each channel
+ for (channel = 0; channel < result.numberOfChannels; ++channel) {
+ // Find the max amplitude of the result, which should be near zero.
+ var data = result.getChannelData(channel);
+ var maxError = data.reduce(function(reducedValue, currentValue) {
+ return Math.max(reducedValue, Math.abs(currentValue));
+ });
+
+ ok(maxError <= errorThresholds[channel], "Max difference between IIR and Biquad on channel " + channel);
+ }
+ });
+ }());
+
+ testPromises.push(function () {
+ // Apply an IIRFilter to the given input signal.
+ //
+ // IIR filter in the time domain is
+ //
+ // y[n] = sum(ff[k]*x[n-k], k, 0, M) - sum(fb[k]*y[n-k], k, 1, N)
+ //
+ function iirFilter(input, feedforward, feedback) {
+ // For simplicity, create an x buffer that contains the input, and a y buffer that contains
+ // the output. Both of these buffers have an initial work space to implement the initial
+ // memory of the filter.
+ var workSize = Math.max(feedforward.length, feedback.length);
+ var x = new Float32Array(input.length + workSize);
+
+ // Float64 because we want to match the implementation that uses doubles to minimize
+ // roundoff.
+ var y = new Float64Array(input.length + workSize);
+
+ // Copy the input over.
+ for (var k = 0; k < input.length; ++k)
+ x[k + feedforward.length] = input[k];
+
+ // Run the filter
+ for (var n = 0; n < input.length; ++n) {
+ var index = n + workSize;
+ var yn = 0;
+ for (var k = 0; k < feedforward.length; ++k)
+ yn += feedforward[k] * x[index - k];
+ for (var k = 0; k < feedback.length; ++k)
+ yn -= feedback[k] * y[index - k];
+
+ y[index] = yn;
+ }
+
+ return y.slice(workSize).map(Math.fround);
+ }
+
+ // Cascade the two given biquad filters to create one IIR filter.
+ function cascadeBiquads(f1Coef, f2Coef) {
+ // The biquad filters are:
+ //
+ // f1 = (b10 + b11/z + b12/z^2)/(1 + a11/z + a12/z^2);
+ // f2 = (b20 + b21/z + b22/z^2)/(1 + a21/z + a22/z^2);
+ //
+ // To cascade them, multiply the two transforms together to get a fourth order IIR filter.
+
+ var numProduct = [f1Coef.b0 * f2Coef.b0,
+ f1Coef.b0 * f2Coef.b1 + f1Coef.b1 * f2Coef.b0,
+ f1Coef.b0 * f2Coef.b2 + f1Coef.b1 * f2Coef.b1 + f1Coef.b2 * f2Coef.b0,
+ f1Coef.b1 * f2Coef.b2 + f1Coef.b2 * f2Coef.b1,
+ f1Coef.b2 * f2Coef.b2
+ ];
+
+ var denProduct = [1,
+ f2Coef.a1 + f1Coef.a1,
+ f2Coef.a2 + f1Coef.a1 * f2Coef.a1 + f1Coef.a2,
+ f1Coef.a1 * f2Coef.a2 + f1Coef.a2 * f2Coef.a1,
+ f1Coef.a2 * f2Coef.a2
+ ];
+
+ return {
+ ff: numProduct,
+ fb: denProduct
+ }
+ }
+
+ // Find the magnitude of the root of the quadratic that has the maximum magnitude.
+ //
+ // The quadratic is z^2 + a1 * z + a2 and we want the root z that has the largest magnitude.
+ function largestRootMagnitude(a1, a2) {
+ var discriminant = a1 * a1 - 4 * a2;
+ if (discriminant < 0) {
+ // Complex roots: -a1/2 +/- i*sqrt(-d)/2. Thus the magnitude of each root is the same
+ // and is sqrt(a1^2/4 + |d|/4)
+ var d = Math.sqrt(-discriminant);
+ return Math.hypot(a1 / 2, d / 2);
+ } else {
+ // Real roots
+ var d = Math.sqrt(discriminant);
+ return Math.max(Math.abs((-a1 + d) / 2), Math.abs((-a1 - d) / 2));
+ }
+ }
+
+ // Cascade 2 lowpass biquad filters and compare that with the equivalent 4th order IIR
+ // filter.
+
+ var nyquist = sampleRate / 2;
+ // Compute the coefficients of a lowpass filter.
+
+ // First some preliminary stuff. Compute the coefficients of the biquad. This is used to
+ // figure out how frames to use in the test.
+ var biquadType = "lowpass";
+ var biquadCutoff = 350;
+ var biquadQ = 5;
+ var biquadGain = 1;
+
+ var coef = createFilter(biquadType,
+ biquadCutoff / nyquist,
+ biquadQ,
+ biquadGain);
+
+ // Cascade the biquads together to create an equivalent IIR filter.
+ var cascade = cascadeBiquads(coef, coef);
+
+ // Since we're cascading two identical biquads, the root of denominator of the IIR filter is
+ // repeated, so the root of the denominator with the largest magnitude occurs twice. The
+ // impulse response of the IIR filter will be roughly c*(r*r)^n at time n, where r is the
+ // root of largest magnitude. This approximation gets better as n increases. We can use
+ // this to get a rough idea of when the response has died down to a small value.
+
+ // This is the value we will use to determine how many frames to render. Rendering too many
+ // is a waste of time and also makes it hard to compare the actual result to the expected
+ // because the magnitudes are so small that they could be mostly round-off noise.
+ //
+ // Find magnitude of the root with largest magnitude
+ var rootMagnitude = largestRootMagnitude(coef.a1, coef.a2);
+
+ // Find n such that |r|^(2*n) <= eps. That is, n = log(eps)/(2*log(r)). Somewhat
+ // arbitrarily choose eps = 1e-20;
+ var eps = 1e-20;
+ var framesForTest = Math.floor(Math.log(eps) / (2 * Math.log(rootMagnitude)));
+
+ // We're ready to create the graph for the test. The offline context has two channels:
+ // channel 0 is the expected (cascaded biquad) result and channel 1 is the actual IIR filter
+ // result.
+ var context = new OfflineAudioContext(2, framesForTest, sampleRate);
+
+ // Use a simple impulse with a large (arbitrary) amplitude as the source
+ var amplitude = 1;
+ var buffer = context.createBuffer(1, testFrames, sampleRate);
+ buffer.getChannelData(0)[0] = amplitude;
+ var source = context.createBufferSource();
+ source.buffer = buffer;
+
+ // Create the two biquad filters. Doesn't really matter what, but for simplicity we choose
+ // identical lowpass filters with the same parameters.
+ var biquad1 = context.createBiquadFilter();
+ biquad1.type = biquadType;
+ biquad1.frequency.value = biquadCutoff;
+ biquad1.Q.value = biquadQ;
+
+ var biquad2 = context.createBiquadFilter();
+ biquad2.type = biquadType;
+ biquad2.frequency.value = biquadCutoff;
+ biquad2.Q.value = biquadQ;
+
+ var iir = context.createIIRFilter(cascade.ff, cascade.fb);
+
+ // Create the merger to get the signals into multiple channels
+ var merger = context.createChannelMerger(2);
+
+ // Create the graph, filtering the source through two biquads.
+ source.connect(biquad1);
+ biquad1.connect(biquad2);
+ biquad2.connect(merger, 0, 0);
+
+ source.connect(iir);
+ iir.connect(merger, 0, 1);
+
+ merger.connect(context.destination);
+
+ // Now filter the source through the IIR filter.
+ var y = iirFilter(buffer.getChannelData(0), cascade.ff, cascade.fb);
+
+ // Rock and roll!
+ source.start();
+
+ return context.startRendering().then(function(result) {
+ var expected = result.getChannelData(0);
+ var actual = result.getChannelData(1);
+
+ compareChannels(actual, expected);
+
+ });
+ }());
+
+ // Wait for all tests
+ Promise.all(testPromises).then(function () {
+ SimpleTest.finish();
+ }, function () {
+ SimpleTest.finish();
+ });
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/blink/test_iirFilterNodeGetFrequencyResponse.html b/dom/media/webaudio/test/blink/test_iirFilterNodeGetFrequencyResponse.html
new file mode 100644
index 0000000000..09782f73be
--- /dev/null
+++ b/dom/media/webaudio/test/blink/test_iirFilterNodeGetFrequencyResponse.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test IIRFilterNode GetFrequencyResponse</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <script type="text/javascript" src="biquad-filters.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ // Modified from WebKit/LayoutTests/webaudio/iirfilter-getFrequencyResponse.html
+ var sampleRate = 48000;
+ var testDurationSec = 0.01;
+
+ // Compute a set of linearly spaced frequencies.
+ function createFrequencies(nFrequencies, sampleRate)
+ {
+ var frequencies = new Float32Array(nFrequencies);
+ var nyquist = sampleRate / 2;
+ var freqDelta = nyquist / nFrequencies;
+
+ for (var k = 0; k < nFrequencies; ++k) {
+ frequencies[k] = k * freqDelta;
+ }
+
+ return frequencies;
+ }
+
+ // Number of frequency samples to take.
+ var numberOfFrequencies = 1000;
+
+ var context = new OfflineAudioContext(1, testDurationSec * sampleRate, sampleRate);
+
+ var frequencies = createFrequencies(numberOfFrequencies, context.sampleRate);
+
+ // 1-Pole IIR Filter
+ var iir = context.createIIRFilter([1], [1, -0.9]);
+
+ var iirMag = new Float32Array(numberOfFrequencies);
+ var iirPhase = new Float32Array(numberOfFrequencies);
+ var trueMag = new Float32Array(numberOfFrequencies);
+ var truePhase = new Float32Array(numberOfFrequencies);
+
+ // The IIR filter is
+ // H(z) = 1/(1 - 0.9*z^(-1)).
+ //
+ // The frequency response is
+ // H(exp(j*w)) = 1/(1 - 0.9*exp(-j*w)).
+ //
+ // Thus, the magnitude is
+ // |H(exp(j*w))| = 1/sqrt(1.81-1.8*cos(w)).
+ //
+ // The phase is
+ // arg(H(exp(j*w)) = atan(0.9*sin(w)/(.9*cos(w)-1))
+
+ var frequencyScale = Math.PI / (sampleRate / 2);
+
+ for (var k = 0; k < frequencies.length; ++k) {
+ var omega = frequencyScale * frequencies[k];
+ trueMag[k] = 1/Math.sqrt(1.81-1.8*Math.cos(omega));
+ truePhase[k] = Math.atan(0.9 * Math.sin(omega) / (0.9 * Math.cos(omega) - 1));
+ }
+
+ iir.getFrequencyResponse(frequencies, iirMag, iirPhase);
+ compareChannels(iirMag, trueMag);
+ compareChannels(iirPhase, truePhase);
+
+ // Compare IIR and Biquad Filter
+ // Create an IIR filter equivalent to the biquad filter. Compute the frequency response for both and verify that they are the same.
+ var biquad = context.createBiquadFilter();
+ var coef = createFilter(biquad.type,
+ biquad.frequency.value / (context.sampleRate / 2),
+ biquad.Q.value,
+ biquad.gain.value);
+
+ var iir = context.createIIRFilter([coef.b0, coef.b1, coef.b2], [1, coef.a1, coef.a2]);
+
+ var biquadMag = new Float32Array(numberOfFrequencies);
+ var biquadPhase = new Float32Array(numberOfFrequencies);
+ var iirMag = new Float32Array(numberOfFrequencies);
+ var iirPhase = new Float32Array(numberOfFrequencies);
+
+ biquad.getFrequencyResponse(frequencies, biquadMag, biquadPhase);
+ iir.getFrequencyResponse(frequencies, iirMag, iirPhase);
+ compareChannels(iirMag, biquadMag);
+ compareChannels(iirPhase, biquadPhase);
+
+ SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/corsServer.sjs b/dom/media/webaudio/test/corsServer.sjs
new file mode 100644
index 0000000000..de77ef71ed
--- /dev/null
+++ b/dom/media/webaudio/test/corsServer.sjs
@@ -0,0 +1,26 @@
+function handleRequest(request, response) {
+ var file = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("CurWorkD", Ci.nsIFile);
+ var fis = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ var bis = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
+ Ci.nsIBinaryInputStream
+ );
+ var paths = "tests/dom/media/webaudio/test/small-shot.ogg";
+ var split = paths.split("/");
+ for (var i = 0; i < split.length; ++i) {
+ file.append(split[i]);
+ }
+ fis.init(file, -1, -1, false);
+ bis.setInputStream(fis);
+ var bytes = bis.readBytes(bis.available());
+ response.setHeader("Content-Type", "video/ogg", false);
+ response.setHeader("Content-Length", "" + bytes.length, false);
+ response.setHeader("Access-Control-Allow-Origin", "*", false);
+ response.write(bytes, bytes.length);
+ response.processAsync();
+ response.finish();
+ bis.close();
+}
diff --git a/dom/media/webaudio/test/file_nodeCreationDocumentGone.html b/dom/media/webaudio/test/file_nodeCreationDocumentGone.html
new file mode 100644
index 0000000000..aedf16702f
--- /dev/null
+++ b/dom/media/webaudio/test/file_nodeCreationDocumentGone.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<html><script>
+var context = new AudioContext();
+setTimeout(function(){window.close();},1000);</script></html>
diff --git a/dom/media/webaudio/test/generate-test-files.py b/dom/media/webaudio/test/generate-test-files.py
new file mode 100755
index 0000000000..af4bc3bb49
--- /dev/null
+++ b/dom/media/webaudio/test/generate-test-files.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python3
+
+import os
+
+rates = [44100, 48000]
+channels = [1, 2]
+duration = "0.5"
+frequency = "1000"
+volume = "-3dB"
+name = "half-a-second"
+formats = {
+ "aac-in-adts": [{"codec": "aac", "extension": "aac"}],
+ "mp3": [{"codec": "libmp3lame", "extension": "mp3"}],
+ "mp4": [
+ {
+ "codec": "libopus",
+ "extension": "mp4",
+ },
+ {"codec": "aac", "extension": "mp4"},
+ ],
+ "ogg": [
+ {"codec": "libvorbis", "extension": "ogg"},
+ {"codec": "libopus", "extension": "opus"},
+ ],
+ "flac": [
+ {"codec": "flac", "extension": "flac"},
+ ],
+ "webm": [
+ {"codec": "libopus", "extension": "webm"},
+ {"codec": "libvorbis", "extension": "webm"},
+ ],
+}
+
+for rate in rates:
+ for channel_count in channels:
+ wav_filename = "{}-{}ch-{}.wav".format(name, channel_count, rate)
+ wav_command = "sox -V -r {} -n -b 16 -c {} {} synth {} sin {} vol {}".format(
+ rate, channel_count, wav_filename, duration, frequency, volume
+ )
+ print(wav_command)
+ os.system(wav_command)
+ for container, codecs in formats.items():
+ for codec in codecs:
+ encoded_filename = "{}-{}ch-{}-{}.{}".format(
+ name, channel_count, rate, codec["codec"], codec["extension"]
+ )
+ print(encoded_filename)
+ encoded_command = "ffmpeg -y -i {} -acodec {} {}".format(
+ wav_filename, codec["codec"], encoded_filename
+ )
+ print(encoded_command)
+ os.system(encoded_command)
diff --git a/dom/media/webaudio/test/half-a-second-1ch-44100-aac-afconvert.mp4 b/dom/media/webaudio/test/half-a-second-1ch-44100-aac-afconvert.mp4
new file mode 100644
index 0000000000..7e3b008376
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-1ch-44100-aac-afconvert.mp4
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-1ch-44100-aac.aac b/dom/media/webaudio/test/half-a-second-1ch-44100-aac.aac
new file mode 100644
index 0000000000..28435589ae
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-1ch-44100-aac.aac
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-1ch-44100-aac.mp4 b/dom/media/webaudio/test/half-a-second-1ch-44100-aac.mp4
new file mode 100644
index 0000000000..c603635f6e
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-1ch-44100-aac.mp4
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-1ch-44100-alaw.wav b/dom/media/webaudio/test/half-a-second-1ch-44100-alaw.wav
new file mode 100644
index 0000000000..4e8b6e7d45
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-1ch-44100-alaw.wav
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-1ch-44100-flac.flac b/dom/media/webaudio/test/half-a-second-1ch-44100-flac.flac
new file mode 100644
index 0000000000..49f71674f5
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-1ch-44100-flac.flac
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-1ch-44100-libmp3lame.mp3 b/dom/media/webaudio/test/half-a-second-1ch-44100-libmp3lame.mp3
new file mode 100644
index 0000000000..fb1ec45af2
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-1ch-44100-libmp3lame.mp3
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-1ch-44100-libopus.mp4 b/dom/media/webaudio/test/half-a-second-1ch-44100-libopus.mp4
new file mode 100644
index 0000000000..3a7df17582
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-1ch-44100-libopus.mp4
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-1ch-44100-libopus.opus b/dom/media/webaudio/test/half-a-second-1ch-44100-libopus.opus
new file mode 100644
index 0000000000..304b9f9d1d
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-1ch-44100-libopus.opus
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-1ch-44100-libopus.webm b/dom/media/webaudio/test/half-a-second-1ch-44100-libopus.webm
new file mode 100644
index 0000000000..71be30de9c
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-1ch-44100-libopus.webm
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-1ch-44100-libvorbis.ogg b/dom/media/webaudio/test/half-a-second-1ch-44100-libvorbis.ogg
new file mode 100644
index 0000000000..ab5ec06e50
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-1ch-44100-libvorbis.ogg
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-1ch-44100-libvorbis.webm b/dom/media/webaudio/test/half-a-second-1ch-44100-libvorbis.webm
new file mode 100644
index 0000000000..b5142703ba
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-1ch-44100-libvorbis.webm
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-1ch-44100-mulaw.wav b/dom/media/webaudio/test/half-a-second-1ch-44100-mulaw.wav
new file mode 100644
index 0000000000..5b6c1edcb4
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-1ch-44100-mulaw.wav
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-1ch-44100.wav b/dom/media/webaudio/test/half-a-second-1ch-44100.wav
new file mode 100644
index 0000000000..0028a66007
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-1ch-44100.wav
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-1ch-48000-aac.aac b/dom/media/webaudio/test/half-a-second-1ch-48000-aac.aac
new file mode 100644
index 0000000000..e1c5ba4631
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-1ch-48000-aac.aac
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-1ch-48000-aac.mp4 b/dom/media/webaudio/test/half-a-second-1ch-48000-aac.mp4
new file mode 100644
index 0000000000..089d2a93e1
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-1ch-48000-aac.mp4
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-1ch-48000-flac.flac b/dom/media/webaudio/test/half-a-second-1ch-48000-flac.flac
new file mode 100644
index 0000000000..783bbf2c97
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-1ch-48000-flac.flac
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-1ch-48000-libmp3lame.mp3 b/dom/media/webaudio/test/half-a-second-1ch-48000-libmp3lame.mp3
new file mode 100644
index 0000000000..f9dfe29a89
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-1ch-48000-libmp3lame.mp3
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-1ch-48000-libopus.mp4 b/dom/media/webaudio/test/half-a-second-1ch-48000-libopus.mp4
new file mode 100644
index 0000000000..eb48fdac1b
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-1ch-48000-libopus.mp4
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-1ch-48000-libopus.opus b/dom/media/webaudio/test/half-a-second-1ch-48000-libopus.opus
new file mode 100644
index 0000000000..1b7cefcb3f
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-1ch-48000-libopus.opus
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-1ch-48000-libopus.webm b/dom/media/webaudio/test/half-a-second-1ch-48000-libopus.webm
new file mode 100644
index 0000000000..c06e5d7583
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-1ch-48000-libopus.webm
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-1ch-48000-libvorbis.ogg b/dom/media/webaudio/test/half-a-second-1ch-48000-libvorbis.ogg
new file mode 100644
index 0000000000..ad88da968c
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-1ch-48000-libvorbis.ogg
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-1ch-48000-libvorbis.webm b/dom/media/webaudio/test/half-a-second-1ch-48000-libvorbis.webm
new file mode 100644
index 0000000000..d63e2c31d3
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-1ch-48000-libvorbis.webm
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-1ch-48000.wav b/dom/media/webaudio/test/half-a-second-1ch-48000.wav
new file mode 100644
index 0000000000..d1fcb21134
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-1ch-48000.wav
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-2ch-44100-aac.aac b/dom/media/webaudio/test/half-a-second-2ch-44100-aac.aac
new file mode 100644
index 0000000000..d2255e982b
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-2ch-44100-aac.aac
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-2ch-44100-aac.mp4 b/dom/media/webaudio/test/half-a-second-2ch-44100-aac.mp4
new file mode 100644
index 0000000000..fbdd17e416
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-2ch-44100-aac.mp4
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-2ch-44100-flac.flac b/dom/media/webaudio/test/half-a-second-2ch-44100-flac.flac
new file mode 100644
index 0000000000..9cc57c24bd
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-2ch-44100-flac.flac
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-2ch-44100-libmp3lame.mp3 b/dom/media/webaudio/test/half-a-second-2ch-44100-libmp3lame.mp3
new file mode 100644
index 0000000000..399df50839
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-2ch-44100-libmp3lame.mp3
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-2ch-44100-libopus.mp4 b/dom/media/webaudio/test/half-a-second-2ch-44100-libopus.mp4
new file mode 100644
index 0000000000..242fb3e12e
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-2ch-44100-libopus.mp4
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-2ch-44100-libopus.opus b/dom/media/webaudio/test/half-a-second-2ch-44100-libopus.opus
new file mode 100644
index 0000000000..a9311b5f9c
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-2ch-44100-libopus.opus
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-2ch-44100-libopus.webm b/dom/media/webaudio/test/half-a-second-2ch-44100-libopus.webm
new file mode 100644
index 0000000000..ca8876d5e1
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-2ch-44100-libopus.webm
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-2ch-44100-libvorbis.ogg b/dom/media/webaudio/test/half-a-second-2ch-44100-libvorbis.ogg
new file mode 100644
index 0000000000..edf76edf89
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-2ch-44100-libvorbis.ogg
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-2ch-44100-libvorbis.webm b/dom/media/webaudio/test/half-a-second-2ch-44100-libvorbis.webm
new file mode 100644
index 0000000000..b01575c526
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-2ch-44100-libvorbis.webm
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-2ch-44100.wav b/dom/media/webaudio/test/half-a-second-2ch-44100.wav
new file mode 100644
index 0000000000..ae37e12813
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-2ch-44100.wav
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-2ch-48000-aac.aac b/dom/media/webaudio/test/half-a-second-2ch-48000-aac.aac
new file mode 100644
index 0000000000..d26803d76f
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-2ch-48000-aac.aac
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-2ch-48000-aac.mp4 b/dom/media/webaudio/test/half-a-second-2ch-48000-aac.mp4
new file mode 100644
index 0000000000..d7e3140580
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-2ch-48000-aac.mp4
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-2ch-48000-flac.flac b/dom/media/webaudio/test/half-a-second-2ch-48000-flac.flac
new file mode 100644
index 0000000000..624e5280ff
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-2ch-48000-flac.flac
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-2ch-48000-libmp3lame.mp3 b/dom/media/webaudio/test/half-a-second-2ch-48000-libmp3lame.mp3
new file mode 100644
index 0000000000..bd009ebfb4
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-2ch-48000-libmp3lame.mp3
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-2ch-48000-libopus.mp4 b/dom/media/webaudio/test/half-a-second-2ch-48000-libopus.mp4
new file mode 100644
index 0000000000..89e2b19256
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-2ch-48000-libopus.mp4
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-2ch-48000-libopus.opus b/dom/media/webaudio/test/half-a-second-2ch-48000-libopus.opus
new file mode 100644
index 0000000000..1e3c72b7b2
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-2ch-48000-libopus.opus
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-2ch-48000-libopus.webm b/dom/media/webaudio/test/half-a-second-2ch-48000-libopus.webm
new file mode 100644
index 0000000000..c7306df2ef
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-2ch-48000-libopus.webm
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-2ch-48000-libvorbis.ogg b/dom/media/webaudio/test/half-a-second-2ch-48000-libvorbis.ogg
new file mode 100644
index 0000000000..63e6d2bb87
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-2ch-48000-libvorbis.ogg
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-2ch-48000-libvorbis.webm b/dom/media/webaudio/test/half-a-second-2ch-48000-libvorbis.webm
new file mode 100644
index 0000000000..589370d6e2
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-2ch-48000-libvorbis.webm
Binary files differ
diff --git a/dom/media/webaudio/test/half-a-second-2ch-48000.wav b/dom/media/webaudio/test/half-a-second-2ch-48000.wav
new file mode 100644
index 0000000000..e9fc21e30b
--- /dev/null
+++ b/dom/media/webaudio/test/half-a-second-2ch-48000.wav
Binary files differ
diff --git a/dom/media/webaudio/test/invalid.txt b/dom/media/webaudio/test/invalid.txt
new file mode 100644
index 0000000000..c44840faf1
--- /dev/null
+++ b/dom/media/webaudio/test/invalid.txt
@@ -0,0 +1 @@
+Surely this is not an audio file!
diff --git a/dom/media/webaudio/test/invalidContent.flac b/dom/media/webaudio/test/invalidContent.flac
new file mode 100644
index 0000000000..b2f4e1ff7a
--- /dev/null
+++ b/dom/media/webaudio/test/invalidContent.flac
@@ -0,0 +1 @@
+fLaC
diff --git a/dom/media/webaudio/test/layouttest-glue.js b/dom/media/webaudio/test/layouttest-glue.js
new file mode 100644
index 0000000000..0ed0b9dc90
--- /dev/null
+++ b/dom/media/webaudio/test/layouttest-glue.js
@@ -0,0 +1,18 @@
+// Reimplementation of the LayoutTest API from Blink so we can easily port
+// WebAudio tests to Simpletest, without touching the internals of the test.
+
+function testFailed(msg) {
+ ok(false, msg);
+}
+
+function testPassed(msg) {
+ ok(true, msg);
+}
+
+function finishJSTest() {
+ SimpleTest.finish();
+}
+
+function description(str) {
+ info(str);
+}
diff --git a/dom/media/webaudio/test/mochitest.toml b/dom/media/webaudio/test/mochitest.toml
new file mode 100644
index 0000000000..74d17d4fc9
--- /dev/null
+++ b/dom/media/webaudio/test/mochitest.toml
@@ -0,0 +1,332 @@
+[DEFAULT]
+tags = "mtg webaudio"
+subsuite = "media"
+support-files = [
+ "8kHz-320kbps-6ch.aac",
+ "audio-expected.wav",
+ "audio-mono-expected-2.wav",
+ "audio-mono-expected.wav",
+ "audio-quad.wav",
+ "audio.ogv",
+ "audiovideo.mp4",
+ "audioBufferSourceNodeDetached_worker.js",
+ "corsServer.sjs",
+ "!/dom/events/test/event_leak_utils.js",
+ "file_nodeCreationDocumentGone.html",
+ "invalid.txt",
+ "invalidContent.flac",
+ "layouttest-glue.js",
+ "nil-packet.ogg",
+ "noaudio.webm",
+ "small-shot-expected.wav",
+ "small-shot-mono-expected.wav",
+ "small-shot.ogg",
+ "small-shot.mp3",
+ "sweep-300-330-1sec.opus",
+ "ting-44.1k-1ch.ogg",
+ "ting-44.1k-2ch.ogg",
+ "ting-48k-1ch.ogg",
+ "ting-48k-2ch.ogg",
+ "ting-44.1k-1ch.wav",
+ "ting-44.1k-2ch.wav",
+ "ting-48k-1ch.wav",
+ "ting-48k-2ch.wav",
+ "sine-440-10s.opus",
+ "webaudio.js",
+ # See ./generate-test-files.py
+ "half-a-second-1ch-44100-aac.aac",
+ "half-a-second-1ch-44100-aac.mp4",
+ "half-a-second-1ch-44100-alaw.wav",
+ "half-a-second-1ch-44100-flac.flac",
+ "half-a-second-1ch-44100-libmp3lame.mp3",
+ "half-a-second-1ch-44100-libopus.mp4",
+ "half-a-second-1ch-44100-libopus.opus",
+ "half-a-second-1ch-44100-libopus.webm",
+ "half-a-second-1ch-44100-libvorbis.ogg",
+ "half-a-second-1ch-44100-libvorbis.webm",
+ "half-a-second-1ch-44100-mulaw.wav",
+ "half-a-second-1ch-44100.wav",
+ "half-a-second-1ch-48000-aac.aac",
+ "half-a-second-1ch-48000-aac.mp4",
+ "half-a-second-1ch-48000-flac.flac",
+ "half-a-second-1ch-48000-libmp3lame.mp3",
+ "half-a-second-1ch-48000-libopus.mp4",
+ "half-a-second-1ch-48000-libopus.opus",
+ "half-a-second-1ch-48000-libopus.webm",
+ "half-a-second-1ch-48000-libvorbis.ogg",
+ "half-a-second-1ch-48000-libvorbis.webm",
+ "half-a-second-1ch-48000.wav",
+ "half-a-second-2ch-44100-aac.aac",
+ "half-a-second-2ch-44100-aac.mp4",
+ "half-a-second-2ch-44100-flac.flac",
+ "half-a-second-2ch-44100-libmp3lame.mp3",
+ "half-a-second-2ch-44100-libopus.mp4",
+ "half-a-second-2ch-44100-libopus.opus",
+ "half-a-second-2ch-44100-libopus.webm",
+ "half-a-second-2ch-44100-libvorbis.ogg",
+ "half-a-second-2ch-44100-libvorbis.webm",
+ "half-a-second-2ch-44100.wav",
+ "half-a-second-2ch-48000-aac.aac",
+ "half-a-second-2ch-48000-aac.mp4",
+ "half-a-second-2ch-48000-flac.flac",
+ "half-a-second-2ch-48000-libmp3lame.mp3",
+ "half-a-second-2ch-48000-libopus.mp4",
+ "half-a-second-2ch-48000-libopus.opus",
+ "half-a-second-2ch-48000-libopus.webm",
+ "half-a-second-2ch-48000-libvorbis.ogg",
+ "half-a-second-2ch-48000-libvorbis.webm",
+ "half-a-second-2ch-48000.wav",
+ "half-a-second-1ch-44100-aac-afconvert.mp4",
+ "waveformatextensible.wav",
+ "waveformatextensiblebadmask.wav",
+ "sixteen-frames.mp3", # only 16 frames of valid audio
+ "../../webrtc/tests/mochitests/mediaStreamPlayback.js",
+ "../../webrtc/tests/mochitests/head.js",
+]
+
+["test_OfflineAudioContext.html"]
+
+["test_ScriptProcessorCollected1.html"]
+
+["test_WebAudioMemoryReporting.html"]
+
+["test_analyserNode.html"]
+skip-if = ["!asan && os != android"] # These are tested in web-platform-tests, except on ASan and Android which don't run WPT.
+
+["test_analyserNodeMinimum.html"]
+skip-if = ["!asan && os != android"] # These are tested in web-platform-tests, except on ASan and Android which don't run WPT.
+
+["test_analyserNodeOutput.html"]
+skip-if = ["!asan && os != android"] # These are tested in web-platform-tests, except on ASan and Android which don't run WPT.
+
+["test_analyserNodePassThrough.html"]
+
+["test_analyserNodeWithGain.html"]
+skip-if = ["!asan && os != android"] # These are tested in web-platform-tests, except on ASan and Android which don't run WPT.
+
+["test_analyserScale.html"]
+skip-if = ["!asan && os != android"] # These are tested in web-platform-tests, except on ASan and Android which don't run WPT.
+
+["test_audioContextParams_recordNonDefaultSampleRate.html"]
+
+["test_audioContextParams_sampleRate.html"]
+
+["test_channelMergerNode.html"]
+
+["test_channelMergerNodeWithVolume.html"]
+
+["test_channelSplitterNode.html"]
+
+["test_channelSplitterNodeWithVolume.html"]
+
+["test_convolver-upmixing-1-channel-response.html"]
+# This is a copy of
+# testing/web-platform/tests/webaudio/the-audio-api/the-convolvernode-interface/convolver-upmixing-1-channel-response.html,
+# but WPT are not run with ASan or Android builds.
+skip-if = ["!asan && os != android"]
+
+["test_convolverNode.html"]
+
+["test_convolverNodeChannelCount.html"]
+
+["test_convolverNodeChannelInterpretationChanges.html"]
+
+["test_convolverNodeDelay.html"]
+
+["test_convolverNodeFiniteInfluence.html"]
+
+["test_convolverNodeNormalization.html"]
+
+["test_convolverNodeOOM.html"]
+skip-if = [
+ "asan",
+ "tsan", # 1672869
+]
+
+["test_convolverNodePassThrough.html"]
+
+["test_convolverNodeWithGain.html"]
+
+["test_convolverNode_mono_mono.html"]
+
+["test_currentTime.html"]
+
+["test_decodeAudioDataOnDetachedBuffer.html"]
+
+["test_decodeAudioDataPromise.html"]
+
+["test_decodeAudioError.html"]
+
+["test_decodeMultichannel.html"]
+
+["test_decodeOpusTail.html"]
+
+["test_decoderDelay.html"]
+
+["test_delayNode.html"]
+
+["test_delayNodeAtMax.html"]
+
+["test_delayNodeChannelChanges.html"]
+
+["test_delayNodeCycles.html"]
+
+["test_delayNodePassThrough.html"]
+
+["test_delayNodeSmallMaxDelay.html"]
+
+["test_delayNodeTailIncrease.html"]
+
+["test_delayNodeTailWithDisconnect.html"]
+
+["test_delayNodeTailWithGain.html"]
+
+["test_delayNodeTailWithReconnect.html"]
+
+["test_delayNodeWithGain.html"]
+
+["test_delaynode-channel-count-1.html"]
+# This is a copy of
+# testing/web-platform/tests/webaudio/the-audio-api/the-delaynode-interface/delaynode-channel-count-1.html
+# but WPT are not run with ASan or Android builds.
+skip-if = ["!asan && os != android"]
+
+["test_disconnectAll.html"]
+
+["test_disconnectAudioParam.html"]
+
+["test_disconnectAudioParamFromOutput.html"]
+
+["test_disconnectExceptions.html"]
+
+["test_disconnectFromAudioNode.html"]
+
+["test_disconnectFromAudioNodeAndOutput.html"]
+
+["test_disconnectFromAudioNodeAndOutputAndInput.html"]
+
+["test_disconnectFromAudioNodeMultipleConnection.html"]
+
+["test_disconnectFromOutput.html"]
+
+["test_dynamicsCompressorNode.html"]
+
+["test_dynamicsCompressorNodePassThrough.html"]
+
+["test_dynamicsCompressorNodeWithGain.html"]
+
+["test_event_listener_leaks.html"]
+
+["test_gainNode.html"]
+
+["test_gainNodeInLoop.html"]
+
+["test_gainNodePassThrough.html"]
+
+["test_iirFilterNodePassThrough.html"]
+
+["test_maxChannelCount.html"]
+
+["test_mixingRules.html"]
+
+["test_nodeCreationDocumentGone.html"]
+
+["test_nodeToParamConnection.html"]
+
+["test_notAllowedToStartAudioContextGC.html"]
+
+["test_offlineDestinationChannelCountLess.html"]
+
+["test_offlineDestinationChannelCountMore.html"]
+
+["test_oscillatorNode.html"]
+
+["test_oscillatorNode2.html"]
+
+["test_oscillatorNodeNegativeFrequency.html"]
+
+["test_oscillatorNodePassThrough.html"]
+
+["test_oscillatorNodeStart.html"]
+
+["test_oscillatorTypeChange.html"]
+
+["test_pannerNode.html"]
+
+["test_pannerNodeAbove.html"]
+
+["test_pannerNodeAtZeroDistance.html"]
+
+["test_pannerNodeChannelCount.html"]
+
+["test_pannerNodeHRTFSymmetry.html"]
+
+["test_pannerNodeTail.html"]
+
+["test_pannerNode_audioparam_distance.html"]
+
+["test_pannerNode_equalPower.html"]
+
+["test_pannerNode_maxDistance.html"]
+
+["test_periodicWave.html"]
+
+["test_periodicWaveBandLimiting.html"]
+
+["test_periodicWaveDisableNormalization.html"]
+
+["test_retrospective-exponentialRampToValueAtTime.html"]
+
+["test_retrospective-linearRampToValueAtTime.html"]
+
+["test_retrospective-setTargetAtTime.html"]
+
+["test_retrospective-setValueAtTime.html"]
+
+["test_retrospective-setValueCurveAtTime.html"]
+
+["test_scriptProcessorNode.html"]
+
+["test_scriptProcessorNodeChannelCount.html"]
+
+["test_scriptProcessorNodeNotConnected.html"]
+
+["test_scriptProcessorNodePassThrough.html"]
+
+["test_scriptProcessorNodeZeroInputOutput.html"]
+
+["test_scriptProcessorNode_playbackTime1.html"]
+
+["test_sequentialBufferSourceWithResampling.html"]
+
+["test_setValueCurveWithNonFiniteElements.html"]
+
+["test_singleSourceDest.html"]
+
+["test_slowStart.html"]
+
+["test_stereoPannerNode.html"]
+
+["test_stereoPannerNodePassThrough.html"]
+
+["test_stereoPanningWithGain.html"]
+
+["test_waveDecoder.html"]
+
+["test_waveShaper.html"]
+
+["test_waveShaperGain.html"]
+
+["test_waveShaperInvalidLengthCurve.html"]
+
+["test_waveShaperNoCurve.html"]
+
+["test_waveShaperPassThrough.html"]
+
+["test_webAudio_muteTab.html"]
+scheme = "https"
+skip-if = [
+ "os == 'mac'",
+ "os == 'win'",
+ "os == 'android'", # Bug 1404995, no loopback devices on some platforms
+]
diff --git a/dom/media/webaudio/test/mochitest_audio.toml b/dom/media/webaudio/test/mochitest_audio.toml
new file mode 100644
index 0000000000..56e612b102
--- /dev/null
+++ b/dom/media/webaudio/test/mochitest_audio.toml
@@ -0,0 +1,100 @@
+[DEFAULT]
+tags = "mtg webaudio"
+subsuite = "media"
+support-files = [
+ "audio-expected.wav",
+ "audio-mono-expected-2.wav",
+ "audio-mono-expected.wav",
+ "audio-quad.wav",
+ "audio.ogv",
+ "audiovideo.mp4",
+ "audioBufferSourceNodeDetached_worker.js",
+ "corsServer.sjs",
+ "!/dom/events/test/event_leak_utils.js",
+ "file_nodeCreationDocumentGone.html",
+ "invalid.txt",
+ "invalidContent.flac",
+ "layouttest-glue.js",
+ "nil-packet.ogg",
+ "noaudio.webm",
+ "small-shot-expected.wav",
+ "small-shot-mono-expected.wav",
+ "small-shot.ogg",
+ "small-shot.mp3",
+ "sweep-300-330-1sec.opus",
+ "ting-44.1k-1ch.ogg",
+ "ting-44.1k-2ch.ogg",
+ "ting-48k-1ch.ogg",
+ "ting-48k-2ch.ogg",
+ "ting-44.1k-1ch.wav",
+ "ting-44.1k-2ch.wav",
+ "ting-48k-1ch.wav",
+ "ting-48k-2ch.wav",
+ "sine-440-10s.opus",
+ "1856145.ogg",
+ "webaudio.js",
+ "../../webrtc/tests/mochitests/mediaStreamPlayback.js",
+ "../../webrtc/tests/mochitests/head.js",
+]
+
+["test_AudioBuffer.html"]
+
+["test_AudioContext.html"]
+
+["test_AudioContext_disabled.html"]
+
+["test_AudioListener.html"]
+
+["test_AudioNodeDevtoolsAPI.html"]
+
+["test_AudioParamDevtoolsAPI.html"]
+
+["test_audioBufferSourceNode.html"]
+
+["test_audioBufferSourceNodeDetached.html"]
+
+["test_audioBufferSourceNodeEnded.html"]
+
+["test_audioBufferSourceNodeLazyLoopParam.html"]
+
+["test_audioBufferSourceNodeLoop.html"]
+
+["test_audioBufferSourceNodeLoopStartEnd.html"]
+
+["test_audioBufferSourceNodeLoopStartEndSame.html"]
+
+["test_audioBufferSourceNodeNoStart.html"]
+
+["test_audioBufferSourceNodeNullBuffer.html"]
+
+["test_audioBufferSourceNodeOffset.html"]
+
+["test_audioBufferSourceNodePassThrough.html"]
+
+["test_audioBufferSourceNodeRate.html"]
+
+["test_audioContextGC.html"]
+
+["test_audioContextSuspendResumeClose.html"]
+tags = "capturestream"
+
+["test_audioDestinationNode.html"]
+
+["test_audioParamChaining.html"]
+
+["test_audioParamExponentialRamp.html"]
+
+["test_audioParamGain.html"]
+
+["test_audioParamLinearRamp.html"]
+
+["test_audioParamSetCurveAtTime.html"]
+
+["test_audioParamSetTargetAtTime.html"]
+
+["test_audioParamSetTargetAtTimeZeroTimeConstant.html"]
+
+["test_audioParamSetValueAtTime.html"]
+
+["test_audioParamTimelineDestinationOffset.html"]
+
diff --git a/dom/media/webaudio/test/mochitest_bugs.toml b/dom/media/webaudio/test/mochitest_bugs.toml
new file mode 100644
index 0000000000..8e987d6065
--- /dev/null
+++ b/dom/media/webaudio/test/mochitest_bugs.toml
@@ -0,0 +1,89 @@
+[DEFAULT]
+tags = "mtg webaudio"
+subsuite = "media"
+support-files = [
+ "audio-expected.wav",
+ "audio-mono-expected-2.wav",
+ "audio-mono-expected.wav",
+ "audio-quad.wav",
+ "audio.ogv",
+ "audiovideo.mp4",
+ "audioBufferSourceNodeDetached_worker.js",
+ "corsServer.sjs",
+ "!/dom/events/test/event_leak_utils.js",
+ "file_nodeCreationDocumentGone.html",
+ "invalid.txt",
+ "invalidContent.flac",
+ "layouttest-glue.js",
+ "nil-packet.ogg",
+ "noaudio.webm",
+ "small-shot-expected.wav",
+ "small-shot-mono-expected.wav",
+ "small-shot.ogg",
+ "small-shot.mp3",
+ "sweep-300-330-1sec.opus",
+ "ting-44.1k-1ch.ogg",
+ "ting-44.1k-2ch.ogg",
+ "ting-48k-1ch.ogg",
+ "ting-48k-2ch.ogg",
+ "ting-44.1k-1ch.wav",
+ "ting-44.1k-2ch.wav",
+ "ting-48k-1ch.wav",
+ "ting-48k-2ch.wav",
+ "sine-440-10s.opus",
+ "webaudio.js",
+ "../../webrtc/tests/mochitests/mediaStreamPlayback.js",
+ "../../webrtc/tests/mochitests/head.js",
+]
+
+["test_bug808374.html"]
+
+["test_bug827541.html"]
+
+["test_bug839753.html"]
+
+["test_bug845960.html"]
+
+["test_bug856771.html"]
+
+["test_bug866570.html"]
+
+["test_bug866737.html"]
+
+["test_bug867089.html"]
+
+["test_bug867174.html"]
+
+["test_bug873335.html"]
+
+["test_bug875221.html"]
+
+["test_bug875402.html"]
+
+["test_bug894150.html"]
+
+["test_bug956489.html"]
+
+["test_bug964376.html"]
+
+["test_bug966247.html"]
+tags = "capturestream"
+
+["test_bug972678.html"]
+
+["test_bug1027864.html"]
+skip-if = ["true"] #Bug 1650930
+
+["test_bug1056032.html"]
+
+["test_bug1113634.html"]
+
+["test_bug1118372.html"]
+
+["test_bug1255618.html"]
+
+["test_bug1267579.html"]
+
+["test_bug1355798.html"]
+
+["test_bug1447273.html"]
diff --git a/dom/media/webaudio/test/mochitest_media.toml b/dom/media/webaudio/test/mochitest_media.toml
new file mode 100644
index 0000000000..c369afd942
--- /dev/null
+++ b/dom/media/webaudio/test/mochitest_media.toml
@@ -0,0 +1,76 @@
+[DEFAULT]
+tags = "mtg webaudio"
+subsuite = "media"
+support-files = [
+ "audio-expected.wav",
+ "audio-mono-expected-2.wav",
+ "audio-mono-expected.wav",
+ "audio-quad.wav",
+ "audio.ogv",
+ "audiovideo.mp4",
+ "audioBufferSourceNodeDetached_worker.js",
+ "corsServer.sjs",
+ "!/dom/events/test/event_leak_utils.js",
+ "file_nodeCreationDocumentGone.html",
+ "invalid.txt",
+ "invalidContent.flac",
+ "layouttest-glue.js",
+ "nil-packet.ogg",
+ "noaudio.webm",
+ "small-shot-expected.wav",
+ "small-shot-mono-expected.wav",
+ "small-shot.ogg",
+ "small-shot.mp3",
+ "sweep-300-330-1sec.opus",
+ "ting-44.1k-1ch.ogg",
+ "ting-44.1k-2ch.ogg",
+ "ting-48k-1ch.ogg",
+ "ting-48k-2ch.ogg",
+ "ting-44.1k-1ch.wav",
+ "ting-44.1k-2ch.wav",
+ "ting-48k-1ch.wav",
+ "ting-48k-2ch.wav",
+ "sine-440-10s.opus",
+ "webaudio.js",
+ "../../webrtc/tests/mochitests/mediaStreamPlayback.js",
+ "../../webrtc/tests/mochitests/head.js",
+]
+
+["test_mediaDecoding.html"]
+
+["test_mediaElementAudioSourceNode.html"]
+tags = "capturestream"
+
+["test_mediaElementAudioSourceNodeCrossOrigin.html"]
+tags = "capturestream"
+
+["test_mediaElementAudioSourceNodeFidelity.html"]
+tags = "capturestream"
+
+["test_mediaElementAudioSourceNodePassThrough.html"]
+tags = "capturestream"
+
+["test_mediaElementAudioSourceNodeVideo.html"]
+tags = "capturestream"
+
+["test_mediaStreamAudioDestinationNode.html"]
+
+["test_mediaStreamAudioSourceNode.html"]
+
+["test_mediaStreamAudioSourceNodeCrossOrigin.html"]
+tags = "capturestream"
+
+["test_mediaStreamAudioSourceNodeNoGC.html"]
+skip-if = ["os == 'mac' && debug"] # Bug 1756880 - lower frequency shutdown hangs
+scheme = "https"
+
+["test_mediaStreamAudioSourceNodePassThrough.html"]
+
+["test_mediaStreamAudioSourceNodeResampling.html"]
+tags = "capturestream"
+
+["test_mediaStreamTrackAudioSourceNode.html"]
+
+["test_mediaStreamTrackAudioSourceNodeCrossOrigin.html"]
+
+["test_mediaStreamTrackAudioSourceNodeVideo.html"]
diff --git a/dom/media/webaudio/test/nil-packet.ogg b/dom/media/webaudio/test/nil-packet.ogg
new file mode 100644
index 0000000000..7b00b5a63e
--- /dev/null
+++ b/dom/media/webaudio/test/nil-packet.ogg
Binary files differ
diff --git a/dom/media/webaudio/test/noaudio.webm b/dom/media/webaudio/test/noaudio.webm
new file mode 100644
index 0000000000..9207017fb6
--- /dev/null
+++ b/dom/media/webaudio/test/noaudio.webm
Binary files differ
diff --git a/dom/media/webaudio/test/sine-440-10s.opus b/dom/media/webaudio/test/sine-440-10s.opus
new file mode 100644
index 0000000000..eb91020168
--- /dev/null
+++ b/dom/media/webaudio/test/sine-440-10s.opus
Binary files differ
diff --git a/dom/media/webaudio/test/sixteen-frames.mp3 b/dom/media/webaudio/test/sixteen-frames.mp3
new file mode 100644
index 0000000000..1d15dcad59
--- /dev/null
+++ b/dom/media/webaudio/test/sixteen-frames.mp3
Binary files differ
diff --git a/dom/media/webaudio/test/small-shot-expected.wav b/dom/media/webaudio/test/small-shot-expected.wav
new file mode 100644
index 0000000000..2faaa8258b
--- /dev/null
+++ b/dom/media/webaudio/test/small-shot-expected.wav
Binary files differ
diff --git a/dom/media/webaudio/test/small-shot-mono-expected.wav b/dom/media/webaudio/test/small-shot-mono-expected.wav
new file mode 100644
index 0000000000..d4e2647e42
--- /dev/null
+++ b/dom/media/webaudio/test/small-shot-mono-expected.wav
Binary files differ
diff --git a/dom/media/webaudio/test/small-shot.mp3 b/dom/media/webaudio/test/small-shot.mp3
new file mode 100644
index 0000000000..f9397a5106
--- /dev/null
+++ b/dom/media/webaudio/test/small-shot.mp3
Binary files differ
diff --git a/dom/media/webaudio/test/small-shot.ogg b/dom/media/webaudio/test/small-shot.ogg
new file mode 100644
index 0000000000..1a41623f81
--- /dev/null
+++ b/dom/media/webaudio/test/small-shot.ogg
Binary files differ
diff --git a/dom/media/webaudio/test/sweep-300-330-1sec.opus b/dom/media/webaudio/test/sweep-300-330-1sec.opus
new file mode 100644
index 0000000000..619d1e0844
--- /dev/null
+++ b/dom/media/webaudio/test/sweep-300-330-1sec.opus
Binary files differ
diff --git a/dom/media/webaudio/test/test_AudioBuffer.html b/dom/media/webaudio/test/test_AudioBuffer.html
new file mode 100644
index 0000000000..05957f679e
--- /dev/null
+++ b/dom/media/webaudio/test/test_AudioBuffer.html
@@ -0,0 +1,104 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test whether we can create an AudioContext interface</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script src="webaudio.js" type="text/javascript"></script>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var context = new AudioContext();
+ var buffer = context.createBuffer(2, 2048, context.sampleRate);
+ SpecialPowers.gc(); // Make sure that our channels are accessible after GC
+ ok(buffer, "Buffer was allocated successfully");
+ is(buffer.sampleRate, context.sampleRate, "Correct sample rate");
+ is(buffer.length, 2048, "Correct length");
+ ok(Math.abs(buffer.duration - 2048 / context.sampleRate) < 0.0001, "Correct duration");
+ is(buffer.numberOfChannels, 2, "Correct number of channels");
+ for (var i = 0; i < buffer.numberOfChannels; ++i) {
+ var buf = buffer.getChannelData(i);
+ ok(buf, "Buffer index " + i + " exists");
+ ok(buf instanceof Float32Array, "Result is a typed array");
+ is(buf.length, buffer.length, "Correct length");
+ var foundNonZero = false;
+ for (var j = 0; j < buf.length; ++j) {
+ if (buf[j] != 0) {
+ foundNonZero = true;
+ break;
+ }
+ buf[j] = j;
+ }
+ ok(!foundNonZero, "Buffer " + i + " should be initialized to 0");
+ }
+
+ // Now test copying the channel data out of a normal buffer
+ var copy = new Float32Array(100);
+ buffer.copyFromChannel(copy, 0, 1024);
+ for (var i = 0; i < copy.length; ++i) {
+ is(copy[i], 1024 + i, "Correct sample");
+ }
+ // Test copying the channel data out of a playing buffer
+ var srcNode = context.createBufferSource();
+ srcNode.buffer = buffer;
+ srcNode.start(0);
+ copy = new Float32Array(100);
+ buffer.copyFromChannel(copy, 0, 1024);
+ for (var i = 0; i < copy.length; ++i) {
+ is(copy[i], 1024 + i, "Correct sample");
+ }
+
+ // Test copying to the channel data
+ var newData = new Float32Array(200);
+ buffer.copyToChannel(newData, 0, 100);
+ var changedData = buffer.getChannelData(0);
+ for (var i = 0; i < changedData.length; ++i) {
+ if (i < 100 || i >= 300) {
+ is(changedData[i], i, "Untouched sample");
+ } else {
+ is(changedData[i], 0, "Correct sample");
+ }
+ }
+
+ // Now, detach the array buffer
+ var worker = new Worker("audioBufferSourceNodeDetached_worker.js");
+ var data = buffer.getChannelData(0).buffer;
+ worker.postMessage(data, [data]);
+ SpecialPowers.gc();
+
+ expectNoException(function() {
+ buffer.copyFromChannel(copy, 0, 1024);
+ });
+
+ expectNoException(function() {
+ buffer.copyToChannel(newData, 0, 100);
+ });
+
+ expectException(function() {
+ context.createBuffer(2, 2048, 7999);
+ }, DOMException.NOT_SUPPORTED_ERR);
+ expectException(function() {
+ context.createBuffer(2, 2048, 192001);
+ }, DOMException.NOT_SUPPORTED_ERR);
+ context.createBuffer(2, 2048, 8000); // no exception
+ context.createBuffer(2, 2048, 192000); // no exception
+ context.createBuffer(32, 2048, 48000); // no exception
+ // Null length
+ expectException(function() {
+ context.createBuffer(2, 0, 48000);
+ }, DOMException.NOT_SUPPORTED_ERR);
+ // Null number of channels
+ expectException(function() {
+ context.createBuffer(0, 2048, 48000);
+ }, DOMException.NOT_SUPPORTED_ERR);
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_AudioContext.html b/dom/media/webaudio/test/test_AudioContext.html
new file mode 100644
index 0000000000..50aeee489e
--- /dev/null
+++ b/dom/media/webaudio/test/test_AudioContext.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test whether we can create an AudioContext interface</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var ac = new AudioContext();
+ ok(ac, "Create a AudioContext object");
+ ok(ac instanceof EventTarget, "AudioContexts must be EventTargets");
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_AudioContext_disabled.html b/dom/media/webaudio/test/test_AudioContext_disabled.html
new file mode 100644
index 0000000000..9c3651a883
--- /dev/null
+++ b/dom/media/webaudio/test/test_AudioContext_disabled.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test whether we can disable the AudioContext interface</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+const webaudio_interfaces = [
+ "AudioContext",
+ "OfflineAudioContext",
+ "AudioContext",
+ "OfflineAudioCompletionEvent",
+ "AudioNode",
+ "AudioDestinationNode",
+ "AudioParam",
+ "GainNode",
+ "DelayNode",
+ "AudioBuffer",
+ "AudioBufferSourceNode",
+ "MediaElementAudioSourceNode",
+ "ScriptProcessorNode",
+ "AudioProcessingEvent",
+ "PannerNode",
+ "AudioListener",
+ "StereoPannerNode",
+ "ConvolverNode",
+ "AnalyserNode",
+ "ChannelSplitterNode",
+ "ChannelMergerNode",
+ "DynamicsCompressorNode",
+ "BiquadFilterNode",
+ "IIRFilterNode",
+ "WaveShaperNode",
+ "OscillatorNode",
+ "PeriodicWave",
+ "MediaStreamAudioSourceNode",
+ "MediaStreamAudioDestinationNode"
+];
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ SpecialPowers.pushPrefEnv({"set": [["dom.webaudio.enabled", false]]}, function() {
+ webaudio_interfaces.forEach((e) => ok(!window[e], e + " must be disabled when the Web Audio API is disabled"));
+ SimpleTest.finish();
+ });
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_AudioListener.html b/dom/media/webaudio/test/test_AudioListener.html
new file mode 100644
index 0000000000..e3d605cbcc
--- /dev/null
+++ b/dom/media/webaudio/test/test_AudioListener.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AudioContext.listener and the AudioListener interface</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var context = new AudioContext();
+ ok("listener" in context, "AudioContext.listener should exist");
+ // The values set by the following cannot be read from script, but the
+ // implementation is simple enough, so we just make sure that nothing throws.
+ context.listener.setPosition(1.0, 1.0, 1.0);
+ context.listener.setOrientation(1.0, 1.0, 1.0, 1.0, 1.0, 1.0);
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_AudioNodeDevtoolsAPI.html b/dom/media/webaudio/test/test_AudioNodeDevtoolsAPI.html
new file mode 100644
index 0000000000..f032ed88f0
--- /dev/null
+++ b/dom/media/webaudio/test/test_AudioNodeDevtoolsAPI.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test the devtool AudioNode API</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ SimpleTest.waitForExplicitFinish();
+
+ function id(node) {
+ return SpecialPowers.getPrivilegedProps(node, "id");
+ }
+
+ var ac = new AudioContext();
+ var ids;
+ var weak;
+ (function() {
+ var src1 = ac.createBufferSource();
+ var src2 = ac.createBufferSource();
+ ok(id(src2) > id(src1), "The ID should be monotonic");
+ ok(id(src1) > id(ac.destination), "The ID of the destination node should be the lowest");
+ ids = [id(src1), id(src2)];
+ weak = SpecialPowers.Cu.getWeakReference(src1);
+ is(SpecialPowers.unwrap(weak.get()), src1, "The node should support a weak reference");
+ })();
+ function observer(subject, topic, data) {
+ var id = parseInt(data);
+ var index = ids.indexOf(id);
+ if (index != -1) {
+ info("Dropping id " + id + " at index " + index);
+ ids.splice(index, 1);
+ if (!ids.length) {
+ SimpleTest.executeSoon(function() {
+ is(weak.get(), null, "The weak reference must be dropped now");
+ SpecialPowers.removeObserver(observer, "webaudio-node-demise");
+ SimpleTest.finish();
+ });
+ }
+ }
+ }
+ SpecialPowers.addObserver(observer, "webaudio-node-demise");
+
+ forceCC();
+ forceCC();
+
+ function forceCC() {
+ SpecialPowers.DOMWindowUtils.cycleCollect();
+ SpecialPowers.DOMWindowUtils.garbageCollect();
+ SpecialPowers.DOMWindowUtils.garbageCollect();
+ }
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_AudioParamDevtoolsAPI.html b/dom/media/webaudio/test/test_AudioParamDevtoolsAPI.html
new file mode 100644
index 0000000000..81fdd357c9
--- /dev/null
+++ b/dom/media/webaudio/test/test_AudioParamDevtoolsAPI.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test the devtool AudioParam API</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+ function checkIdAndName(node, name) {
+ is(SpecialPowers.getPrivilegedProps(node, "id"),
+ SpecialPowers.getPrivilegedProps(node[name], "parentNodeId"),
+ "The parent id should be correct");
+ is(SpecialPowers.getPrivilegedProps(node[name], "name"), name,
+ "The name of the AudioParam should be correct.");
+ }
+
+ var ac = new AudioContext(),
+ gain = ac.createGain(),
+ osc = ac.createOscillator(),
+ del = ac.createDelay(),
+ source = ac.createBufferSource(),
+ stereoPanner = ac.createStereoPanner(),
+ comp = ac.createDynamicsCompressor(),
+ biquad = ac.createBiquadFilter();
+
+ checkIdAndName(gain, "gain");
+ checkIdAndName(osc, "frequency");
+ checkIdAndName(osc, "detune");
+ checkIdAndName(del, "delayTime");
+ checkIdAndName(source, "playbackRate");
+ checkIdAndName(source, "detune");
+ checkIdAndName(stereoPanner, "pan");
+ checkIdAndName(comp, "threshold");
+ checkIdAndName(comp, "knee");
+ checkIdAndName(comp, "ratio");
+ checkIdAndName(comp, "attack");
+ checkIdAndName(comp, "release");
+ checkIdAndName(biquad, "frequency");
+ checkIdAndName(biquad, "detune");
+ checkIdAndName(biquad, "Q");
+ checkIdAndName(biquad, "gain");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_OfflineAudioContext.html b/dom/media/webaudio/test/test_OfflineAudioContext.html
new file mode 100644
index 0000000000..d9403566ae
--- /dev/null
+++ b/dom/media/webaudio/test/test_OfflineAudioContext.html
@@ -0,0 +1,118 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test OfflineAudioContext</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var renderedBuffer = null;
+var finished = 0;
+
+function finish() {
+ finished++;
+ if (finished == 2) {
+ SimpleTest.finish();
+ }
+}
+
+function setOrCompareRenderedBuffer(aRenderedBuffer) {
+ if (renderedBuffer) {
+ is(renderedBuffer, aRenderedBuffer, "Rendered buffers from the event and the promise should be the same");
+ finish();
+ } else {
+ renderedBuffer = aRenderedBuffer;
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ let ctxs = [
+ new OfflineAudioContext(2, 100, 22050),
+ new OfflineAudioContext({length: 100, sampleRate: 22050}),
+ new OfflineAudioContext({channels: 2, length: 100, sampleRate: 22050}),
+ ];
+
+ for (let ctx of ctxs) {
+ ok(ctx instanceof EventTarget, "OfflineAudioContexts must be EventTargets");
+ is(ctx.length, 100, "OfflineAudioContext.length is equal to the value passed to the ctor.");
+
+ var buf = ctx.createBuffer(2, 100, ctx.sampleRate);
+ for (var i = 0; i < 2; ++i) {
+ for (var j = 0; j < 100; ++j) {
+ buf.getChannelData(i)[j] = Math.sin(2 * Math.PI * 200 * j / ctx.sampleRate);
+ }
+ }
+ }
+
+ is(ctxs[1].destination.channelCount, 1, "OfflineAudioContext defaults to to correct channelCount.");
+
+ let ctx = ctxs[0];
+
+ expectException(function() {
+ new OfflineAudioContext(2, 100, 0);
+ }, DOMException.NOT_SUPPORTED_ERR);
+ expectException(function() {
+ new OfflineAudioContext(2, 100, -1);
+ }, DOMException.NOT_SUPPORTED_ERR);
+ expectException(function() {
+ new OfflineAudioContext(0, 100, 44100);
+ }, DOMException.NOT_SUPPORTED_ERR);
+ new OfflineAudioContext(32, 100, 44100);
+ expectException(function() {
+ new OfflineAudioContext(33, 100, 44100);
+ }, DOMException.NOT_SUPPORTED_ERR);
+ expectException(function() {
+ new OfflineAudioContext(2, 0, 44100);
+ }, DOMException.NOT_SUPPORTED_ERR);
+ expectTypeError(function() {
+ new OfflineAudioContext({});
+ });
+ expectTypeError(function() {
+ new OfflineAudioContext({sampleRate: 44100});
+ });
+ expectTypeError(function() {
+ new OfflineAudioContext({length: 44100*40});
+ });
+
+ var src = ctx.createBufferSource();
+ src.buffer = buf;
+ src.start(0);
+ src.connect(ctx.destination);
+
+ ctx.addEventListener("complete", function(e) {
+ ok(e instanceof OfflineAudioCompletionEvent, "Correct event received");
+ is(e.renderedBuffer.numberOfChannels, 2, "Correct expected number of buffers");
+ ok(renderedBuffer != null, "The event should be fired after the promise callback.");
+ expectNoException(function() {
+ ctx.startRendering().then(function() {
+ ok(false, "Promise should not resolve when startRendering is called a second time on an OfflineAudioContext")
+ finish();
+ }).catch(function(err) {
+ ok(true, "Promise should reject when startRendering is called a second time on an OfflineAudioContext")
+ finish();
+ });
+ });
+ compareBuffers(e.renderedBuffer, buf);
+ setOrCompareRenderedBuffer(e.renderedBuffer);
+
+ });
+
+ expectNoException(function() {
+ ctx.startRendering().then(function(b) {
+ is(renderedBuffer, null, "The promise callback should be called first.");
+ setOrCompareRenderedBuffer(b);
+ }).catch(function (error) {
+ ok(false, "The promise from OfflineAudioContext.startRendering should never be rejected");
+ });
+ });
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_ScriptProcessorCollected1.html b/dom/media/webaudio/test/test_ScriptProcessorCollected1.html
new file mode 100644
index 0000000000..8f05889d26
--- /dev/null
+++ b/dom/media/webaudio/test/test_ScriptProcessorCollected1.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test ScriptProcessorNode in cycle with no listener is collected</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var observer = function(subject, topic, data) {
+ var id = parseInt(data);
+ var index = ids.indexOf(id);
+ if (index != -1) {
+ ok(true, "Collected AudioNode id " + id + " at index " + index);
+ ids.splice(index, 1);
+ }
+}
+
+SpecialPowers.addObserver(observer, "webaudio-node-demise");
+
+SimpleTest.registerCleanupFunction(function() {
+ if (observer) {
+ SpecialPowers.removeObserver(observer, "webaudio-node-demise");
+ }
+});
+
+var ac = new AudioContext();
+
+var testProcessor = ac.createScriptProcessor(256, 1, 0);
+var delay = ac.createDelay();
+testProcessor.connect(delay);
+delay.connect(testProcessor);
+
+var referenceProcessor = ac.createScriptProcessor(256, 1, 0);
+var gain = ac.createGain();
+gain.connect(referenceProcessor);
+
+var processCount = 0;
+testProcessor.onaudioprocess = function(event) {
+ ++processCount;
+ switch (processCount) {
+ case 1:
+ // Switch to listening to referenceProcessor;
+ referenceProcessor.onaudioprocess = event.target.onaudioprocess;
+ referenceProcessor = null;
+ event.target.onaudioprocess = null;
+ break;
+ case 2:
+ // There are no references to testProcessor and so GC can begin.
+ SpecialPowers.exactGC(function() {
+ SpecialPowers.removeObserver(observer, "webaudio-node-demise");
+ observer = null;
+ event.target.onaudioprocess = null;
+ ok(!ids.length, "All expected nodes should be collected");
+ SimpleTest.finish();
+ });
+ break;
+ }
+};
+
+function id(node) {
+ return SpecialPowers.getPrivilegedProps(node, "id");
+}
+
+// Nodes with these ids should be collected.
+var ids = [ id(testProcessor), id(delay), id(gain) ];
+testProcessor = null;
+delay = null;
+gain = null;
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_WebAudioMemoryReporting.html b/dom/media/webaudio/test/test_WebAudioMemoryReporting.html
new file mode 100644
index 0000000000..693e558304
--- /dev/null
+++ b/dom/media/webaudio/test/test_WebAudioMemoryReporting.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Web Audio memory reporting</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var ac = new AudioContext();
+var sp = ac.createScriptProcessor(4096, 1, 1);
+sp.connect(ac.destination);
+
+// Not started so as to test
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1225003#c2
+var oac = new OfflineAudioContext(1, 1, 48000);
+
+var nodeTypes = ["ScriptProcessorNode", "AudioDestinationNode"];
+var objectTypes = ["dom-nodes", "engine-objects", "track-objects"];
+
+var usages = { "explicit/webaudio/audiocontext": 0 };
+
+for (var i = 0; i < nodeTypes.length; ++i) {
+ for (var j = 0; j < objectTypes.length; ++j) {
+ usages["explicit/webaudio/audio-node/" +
+ nodeTypes[i] + "/" + objectTypes[j]] = 0;
+ }
+}
+
+var handleReport = function(aProcess, aPath, aKind, aUnits, aAmount, aDesc) {
+ if (aPath in usages) {
+ usages[aPath] += aAmount;
+ }
+}
+
+var finished = function () {
+ ok(true, "Yay didn't crash!");
+ for (var resource in usages) {
+ ok(usages[resource] > 0, "Non-zero usage for " + resource);
+ };
+ SimpleTest.finish();
+}
+
+SpecialPowers.Cc["@mozilla.org/memory-reporter-manager;1"].
+ getService(SpecialPowers.Ci.nsIMemoryReporterManager).
+ getReports(handleReport, null, finished, null, /* anonymized = */ false);
+
+// To test bug 1225003, run a failing decodeAudioData() job over a time when
+// the tasks from getReports() are expected to run.
+ac.decodeAudioData(new ArrayBuffer(4), function(){}, function(){});
+</script>
+</html>
diff --git a/dom/media/webaudio/test/test_analyserNode.html b/dom/media/webaudio/test/test_analyserNode.html
new file mode 100644
index 0000000000..0793eeb2cb
--- /dev/null
+++ b/dom/media/webaudio/test/test_analyserNode.html
@@ -0,0 +1,178 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AnalyserNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function testNode() {
+ var context = new AudioContext();
+ var buffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+
+ var destination = context.destination;
+
+ var source = context.createBufferSource();
+
+ var analyser = context.createAnalyser();
+
+ source.buffer = buffer;
+
+ source.connect(analyser);
+ analyser.connect(destination);
+
+ is(analyser.channelCount, 2, "analyser node has 2 input channels by default");
+ is(analyser.channelCountMode, "max", "Correct channelCountMode for the analyser node");
+ is(analyser.channelInterpretation, "speakers", "Correct channelCountInterpretation for the analyser node");
+
+ is(analyser.fftSize, 2048, "Correct default value for fftSize");
+ is(analyser.frequencyBinCount, 1024, "Correct default value for frequencyBinCount");
+ expectException(function() {
+ analyser.fftSize = 0;
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ analyser.fftSize = 1;
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ analyser.fftSize = 8;
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ analyser.fftSize = 100; // non-power of two
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ analyser.fftSize = 2049;
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ analyser.fftSize = 4097;
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ analyser.fftSize = 8193;
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ analyser.fftSize = 16385;
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ analyser.fftSize = 32769;
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ analyser.fftSize = 65536;
+ }, DOMException.INDEX_SIZE_ERR);
+ analyser.fftSize = 1024;
+ is(analyser.frequencyBinCount, 512, "Correct new value for frequencyBinCount");
+
+ is(analyser.minDecibels, -100, "Correct default value for minDecibels");
+ is(analyser.maxDecibels, -30, "Correct default value for maxDecibels");
+ expectException(function() {
+ analyser.minDecibels = -30;
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ analyser.minDecibels = -29;
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ analyser.maxDecibels = -100;
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ analyser.maxDecibels = -101;
+ }, DOMException.INDEX_SIZE_ERR);
+
+ ok(Math.abs(analyser.smoothingTimeConstant - 0.8) < 0.001, "Correct default value for smoothingTimeConstant");
+ expectException(function() {
+ analyser.smoothingTimeConstant = -0.1;
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ analyser.smoothingTimeConstant = 1.1;
+ }, DOMException.INDEX_SIZE_ERR);
+ analyser.smoothingTimeConstant = 0;
+ analyser.smoothingTimeConstant = 1;
+}
+
+function testConstructor() {
+ var context = new AudioContext();
+
+ var analyser = new AnalyserNode(context);
+ is(analyser.channelCount, 2, "analyser node has 2 input channels by default");
+ is(analyser.channelCountMode, "max", "Correct channelCountMode for the analyser node");
+ is(analyser.channelInterpretation, "speakers", "Correct channelCountInterpretation for the analyser node");
+
+ is(analyser.fftSize, 2048, "Correct default value for fftSize");
+ is(analyser.frequencyBinCount, 1024, "Correct default value for frequencyBinCount");
+ is(analyser.minDecibels, -100, "Correct default value for minDecibels");
+ is(analyser.maxDecibels, -30, "Correct default value for maxDecibels");
+ ok(Math.abs(analyser.smoothingTimeConstant - 0.8) < 0.001, "Correct default value for smoothingTimeConstant");
+
+ expectException(function() {
+ analyser = new AnalyserNode(context, { fftSize: 0 });
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ analyser = new AnalyserNode(context, { fftSize: 1 });
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ analyser = new AnalyserNode(context, { fftSize: 8 });
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ analyser = new AnalyserNode(context, { fftSize: 100 }); // non-power of two
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ analyser = new AnalyserNode(context, { fftSize: 2049 });
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ analyser = new AnalyserNode(context, { fftSize: 4097 });
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ analyser = new AnalyserNode(context, { fftSize: 8193 });
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ analyser = new AnalyserNode(context, { fftSize: 16385 });
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ analyser = new AnalyserNode(context, { fftSize: 32769 });
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ analyser = new AnalyserNode(context, { fftSize: 65536 });
+ }, DOMException.INDEX_SIZE_ERR);
+ analyser = new AnalyserNode(context, { fftSize: 1024 });
+ is(analyser.frequencyBinCount, 512, "Correct new value for frequencyBinCount");
+
+ expectException(function() {
+ analyser = new AnalyserNode(context, { minDecibels: -30 });
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ analyser = new AnalyserNode(context, { minDecibels: -29 });
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ analyser = new AnalyserNode(context, { maxDecibels: -100 });
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ analyser = new AnalyserNode(context, { maxDecibels: -101 });
+ }, DOMException.INDEX_SIZE_ERR);
+
+ expectException(function() {
+ analyser = new AnalyserNode(context, { smoothingTimeConstant: -0.1 });
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ analyser = new AnalyserNode(context, { smoothingTimeConstant: -1.1 });
+ }, DOMException.INDEX_SIZE_ERR);
+ analyser = new AnalyserNode(context, { smoothingTimeConstant: 0 });
+ analyser = new AnalyserNode(context, { smoothingTimeConstant: 1 });
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+
+ testNode();
+ testConstructor();
+
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_analyserNodeMinimum.html b/dom/media/webaudio/test/test_analyserNodeMinimum.html
new file mode 100644
index 0000000000..950fd1812b
--- /dev/null
+++ b/dom/media/webaudio/test/test_analyserNodeMinimum.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AnalyserNode when the input is silent</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+
+ var ac = new AudioContext();
+ var analyser = ac.createAnalyser();
+ var constant = ac.createConstantSource();
+ var sp = ac.createScriptProcessor(2048, 1, 0);
+
+ constant.offset.value = 0.0;
+
+ constant.connect(analyser)
+ .connect(ac.destination);
+
+ constant.connect(sp);
+
+ var buf = new Float32Array(analyser.frequencyBinCount);
+ var iteration_count = 10;
+ sp.onaudioprocess = function() {
+ analyser.getFloatFrequencyData(buf);
+ var correct = true;
+ for (var i = 0; i < buf.length; i++) {
+ correct &= buf[i] == -Infinity;
+ }
+ ok(correct, "silent input process -Infinity in decibel bins");
+ if(!(iteration_count--)) {
+ sp.onaudioprocess = null;
+ constant.stop();
+ ac.close();
+ SimpleTest.finish();
+ }
+ }
+
+ constant.start();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_analyserNodeOutput.html b/dom/media/webaudio/test/test_analyserNodeOutput.html
new file mode 100644
index 0000000000..27b354e92d
--- /dev/null
+++ b/dom/media/webaudio/test/test_analyserNodeOutput.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AnalyserNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var source = context.createBufferSource();
+
+ var analyser = context.createAnalyser();
+
+ source.buffer = this.buffer;
+
+ source.connect(analyser);
+
+ source.start(0);
+ return analyser;
+ },
+ createExpectedBuffers(context) {
+ this.buffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ this.buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+
+ return [this.buffer];
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_analyserNodePassThrough.html b/dom/media/webaudio/test/test_analyserNodePassThrough.html
new file mode 100644
index 0000000000..50ff94a8c7
--- /dev/null
+++ b/dom/media/webaudio/test/test_analyserNodePassThrough.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AnalyserNode with passthrough</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var source = context.createBufferSource();
+
+ var analyser = context.createAnalyser();
+
+ source.buffer = this.buffer;
+
+ source.connect(analyser);
+
+ var analyserWrapped = SpecialPowers.wrap(analyser);
+ ok("passThrough" in analyserWrapped, "AnalyserNode should support the passThrough API");
+ analyserWrapped.passThrough = true;
+
+ source.start(0);
+ return analyser;
+ },
+ createExpectedBuffers(context) {
+ this.buffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ this.buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+
+ return [this.buffer];
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_analyserNodeWithGain.html b/dom/media/webaudio/test/test_analyserNodeWithGain.html
new file mode 100644
index 0000000000..fa0a2caa75
--- /dev/null
+++ b/dom/media/webaudio/test/test_analyserNodeWithGain.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<title>Test effect of AnalyserNode on GainNode output</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+promise_test(function() {
+ // fftSize <= bufferSize so that the time domain data is full of input after
+ // processing the buffer.
+ const fftSize = 32;
+ const bufferSize = 128;
+
+ var context = new OfflineAudioContext(1, bufferSize, 48000);
+
+ var analyser1 = context.createAnalyser();
+ analyser1.fftSize = fftSize;
+ analyser1.connect(context.destination);
+ var analyser2 = context.createAnalyser();
+ analyser2.fftSize = fftSize;
+
+ var gain = context.createGain();
+ gain.gain.value = 2.0;
+ gain.connect(analyser1);
+ gain.connect(analyser2);
+
+ // Create a DC input to make getFloatTimeDomainData() output consistent at
+ // any time.
+ var buffer = context.createBuffer(1, 1, context.sampleRate);
+ buffer.getChannelData(0)[0] = 1.0 / gain.gain.value;
+ var source = context.createBufferSource();
+ source.buffer = buffer;
+ source.loop = true;
+ source.connect(gain);
+ source.start();
+
+ return context.startRendering().
+ then(function(buffer) {
+ assert_equals(buffer.getChannelData(0)[0], 1.0,
+ "analyser1 output");
+
+ var data = new Float32Array(1);
+ analyser1.getFloatTimeDomainData(data);
+ assert_equals(data[0], 1.0, "analyser1 time domain data");
+ analyser2.getFloatTimeDomainData(data);
+ assert_equals(data[0], 1.0, "analyser2 time domain data");
+ });
+});
+</script>
diff --git a/dom/media/webaudio/test/test_analyserScale.html b/dom/media/webaudio/test/test_analyserScale.html
new file mode 100644
index 0000000000..dc4260c47a
--- /dev/null
+++ b/dom/media/webaudio/test/test_analyserScale.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AnalyserNode when the input is scaled </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+
+ var context = new AudioContext();
+
+ var gain = context.createGain();
+ var analyser = context.createAnalyser();
+ var osc = context.createOscillator();
+
+
+ osc.connect(gain);
+ gain.connect(analyser);
+
+ osc.start();
+
+ var array = new Uint8Array(analyser.frequencyBinCount);
+
+ function getAnalyserData() {
+ gain.gain.setValueAtTime(currentGain, context.currentTime);
+ analyser.getByteTimeDomainData(array);
+ var max = -1;
+ for (var i = 0; i < array.length; i++) {
+ if (array[i] > max) {
+ max = Math.abs(array[i] - 128);
+ }
+ }
+ if (max <= currentGain * 128) {
+ ok(true, "Analyser got scaled data for " + currentGain);
+ currentGain = tests.shift();
+ if (currentGain == undefined) {
+ SimpleTest.finish();
+ return;
+ }
+ }
+ requestAnimationFrame(getAnalyserData);
+ }
+
+ var tests = [1.0, 0.5, 0.0];
+ var currentGain = tests.shift();
+ requestAnimationFrame(getAnalyserData);
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_audioBufferSourceNode.html b/dom/media/webaudio/test/test_audioBufferSourceNode.html
new file mode 100644
index 0000000000..fc7c0b48d1
--- /dev/null
+++ b/dom/media/webaudio/test/test_audioBufferSourceNode.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AudioBufferSourceNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 4096,
+ createGraph(context) {
+ var buffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+
+ var source = context.createBufferSource();
+ source.start(0);
+ source.buffer = buffer;
+ return source;
+ },
+ createExpectedBuffers(context) {
+ var buffers = [];
+ var buffer = context.createBuffer(2, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ buffer.getChannelData(1)[i] = buffer.getChannelData(0)[i];
+ }
+ buffers.push(buffer);
+ buffers.push(getEmptyBuffer(context, 2048));
+ return buffers;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_audioBufferSourceNodeDetached.html b/dom/media/webaudio/test/test_audioBufferSourceNodeDetached.html
new file mode 100644
index 0000000000..e84c33e585
--- /dev/null
+++ b/dom/media/webaudio/test/test_audioBufferSourceNodeDetached.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AudioBufferSourceNode when an AudioBuffer's getChanneData buffer is detached</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function createGarbage() {
+ var s = [];
+ for (var i = 0; i < 10000000; ++i) {
+ s.push(i);
+ }
+ var sum = 0;
+ for (var i = 0; i < s.length; ++i) {
+ sum += s[i];
+ }
+ return sum;
+}
+
+var worker = new Worker("audioBufferSourceNodeDetached_worker.js");
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var buffer = context.createBuffer(1, 10000000, context.sampleRate);
+ var data = buffer.getChannelData(0);
+ for (var i = 0; i < data.length; ++i) {
+ data[i] = (i%100)/100 - 0.5;
+ }
+
+ // Detach the buffer now
+ var data = buffer.getChannelData(0).buffer;
+ worker.postMessage(data, [data]);
+ // Create garbage and GC to replace the buffer data with garbage
+ SpecialPowers.gc();
+ createGarbage();
+ SpecialPowers.gc();
+
+ var source = context.createBufferSource();
+ source.buffer = buffer;
+ source.start();
+ // This should play silence
+ return source;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_audioBufferSourceNodeEnded.html b/dom/media/webaudio/test/test_audioBufferSourceNodeEnded.html
new file mode 100644
index 0000000000..a11bb880a2
--- /dev/null
+++ b/dom/media/webaudio/test/test_audioBufferSourceNodeEnded.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test ended event on AudioBufferSourceNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var context = new AudioContext();
+ var buffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+
+ var source = context.createBufferSource();
+
+ source.onended = function(e) {
+ is(e.target, source, "Correct target for the ended event");
+ SimpleTest.finish();
+ };
+
+ source.start(0);
+ source.buffer = buffer;
+ source.connect(context.destination);
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_audioBufferSourceNodeLazyLoopParam.html b/dom/media/webaudio/test/test_audioBufferSourceNodeLazyLoopParam.html
new file mode 100644
index 0000000000..757d4487c4
--- /dev/null
+++ b/dom/media/webaudio/test/test_audioBufferSourceNodeLazyLoopParam.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AudioBufferSourceNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 4096,
+ numberOfChannels: 1,
+ createGraph(context) {
+ // silence for half of the buffer, ones after that.
+ var buffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 1024; i < 2048; i++) {
+ buffer.getChannelData(0)[i] = 1;
+ }
+
+ var source = context.createBufferSource();
+
+ // we start at the 1024 frames, we should only have ones.
+ source.loop = true;
+ source.loopStart = 1024 / context.sampleRate;
+ source.loopEnd = 2048 / context.sampleRate;
+ source.buffer = buffer;
+ source.start(0, 1024 / context.sampleRate, 2048 / context.sampleRate);
+ return source;
+ },
+ createExpectedBuffers(context) {
+ var expectedBuffer = context.createBuffer(1, 4096, context.sampleRate);
+ for (var i = 0; i < 2048; i++) {
+ expectedBuffer.getChannelData(0)[i] = 1;
+ }
+ return expectedBuffer;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_audioBufferSourceNodeLoop.html b/dom/media/webaudio/test/test_audioBufferSourceNodeLoop.html
new file mode 100644
index 0000000000..10d5d99108
--- /dev/null
+++ b/dom/media/webaudio/test/test_audioBufferSourceNodeLoop.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AudioBufferSourceNode looping</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 2048 * 4,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var buffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+
+ var source = context.createBufferSource();
+ source.buffer = buffer;
+
+ source.start(0);
+ source.loop = true;
+ return source;
+ },
+ createExpectedBuffers(context) {
+ var expectedBuffer = context.createBuffer(1, 2048 * 4, context.sampleRate);
+ for (var i = 0; i < 4; ++i) {
+ for (var j = 0; j < 2048; ++j) {
+ expectedBuffer.getChannelData(0)[i * 2048 + j] = Math.sin(440 * 2 * Math.PI * j / context.sampleRate);
+ }
+ }
+ return expectedBuffer;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_audioBufferSourceNodeLoopStartEnd.html b/dom/media/webaudio/test/test_audioBufferSourceNodeLoopStartEnd.html
new file mode 100644
index 0000000000..1ef08e0b83
--- /dev/null
+++ b/dom/media/webaudio/test/test_audioBufferSourceNodeLoopStartEnd.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AudioBufferSourceNode looping</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 2048 * 4,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var buffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+
+ var source = new AudioBufferSourceNode(context, {buffer, loop: true, loopStart: buffer.duration * 0.25, loopEnd: buffer.duration * 0.75 });
+ source.start(0);
+ return source;
+ },
+ createExpectedBuffers(context) {
+ var expectedBuffer = context.createBuffer(1, 2048 * 4, context.sampleRate);
+ for (var i = 0; i < 1536; ++i) {
+ expectedBuffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+ for (var i = 0; i < 6; ++i) {
+ for (var j = 512; j < 1536; ++j) {
+ expectedBuffer.getChannelData(0)[1536 + i * 1024 + j - 512] = Math.sin(440 * 2 * Math.PI * j / context.sampleRate);
+ }
+ }
+ for (var j = 7680; j < 2048 * 4; ++j) {
+ expectedBuffer.getChannelData(0)[j] = Math.sin(440 * 2 * Math.PI * (j - 7168) / context.sampleRate);
+ }
+ return expectedBuffer;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_audioBufferSourceNodeLoopStartEndSame.html b/dom/media/webaudio/test/test_audioBufferSourceNodeLoopStartEndSame.html
new file mode 100644
index 0000000000..cfe054f838
--- /dev/null
+++ b/dom/media/webaudio/test/test_audioBufferSourceNodeLoopStartEndSame.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AudioBufferSourceNode looping</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var buffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+
+ var source = context.createBufferSource();
+ source.buffer = buffer;
+
+ source.loop = true;
+ source.loopStart = source.loopEnd = 1 / context.sampleRate;
+ source.start(0);
+ return source;
+ },
+ createExpectedBuffers(context) {
+ var buffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+ return buffer;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_audioBufferSourceNodeNoStart.html b/dom/media/webaudio/test/test_audioBufferSourceNodeNoStart.html
new file mode 100644
index 0000000000..e9a0472e2a
--- /dev/null
+++ b/dom/media/webaudio/test/test_audioBufferSourceNodeNoStart.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AudioBufferSourceNode when start() is not called</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var buffer = context.createBuffer(1, 2048, context.sampleRate);
+ var data = buffer.getChannelData(0);
+ for (var i = 0; i < data.length; ++i) {
+ data[i] = (i%100)/100 - 0.5;
+ }
+ var source = context.createBufferSource();
+ source.buffer = buffer;
+ return source;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_audioBufferSourceNodeNullBuffer.html b/dom/media/webaudio/test/test_audioBufferSourceNodeNullBuffer.html
new file mode 100644
index 0000000000..b0b405b366
--- /dev/null
+++ b/dom/media/webaudio/test/test_audioBufferSourceNodeNullBuffer.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AudioBufferSourceNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var source = context.createBufferSource();
+
+ source.start(0);
+ source.buffer = null;
+ is(source.buffer, null, "Try playing back a null buffer");
+ return source;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_audioBufferSourceNodeOffset.html b/dom/media/webaudio/test/test_audioBufferSourceNodeOffset.html
new file mode 100644
index 0000000000..0411b74ce5
--- /dev/null
+++ b/dom/media/webaudio/test/test_audioBufferSourceNodeOffset.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test the offset property on AudioBufferSourceNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var fuzz = 0.3;
+
+if (navigator.platform.startsWith("Mac")) {
+ // bug 895720
+ fuzz = 0.6;
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var samplesFromSource = 0;
+ var context = new AudioContext();
+ var sp = context.createScriptProcessor(256);
+
+ sp.onaudioprocess = function(e) {
+ samplesFromSource += e.inputBuffer.length;
+ }
+
+ var buffer = context.createBuffer(1, context.sampleRate, context.sampleRate);
+ for (var i = 0; i < context.sampleRate; ++i) {
+ buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+
+ var source = context.createBufferSource();
+
+ source.onended = function(e) {
+ // The timing at which the audioprocess and ended listeners are called can
+ // change, hence the fuzzy equal here.
+ var errorRatio = samplesFromSource / (0.5 * context.sampleRate);
+ ok(errorRatio > (1.0 - fuzz) && errorRatio < (1.0 + fuzz),
+ "Correct number of samples received (expected: " +
+ (0.5 * context.sampleRate) + ", actual: " + samplesFromSource + ").");
+ SimpleTest.finish();
+ };
+
+ source.buffer = buffer;
+ source.connect(sp);
+ source.start(0, 0.5);
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_audioBufferSourceNodePassThrough.html b/dom/media/webaudio/test/test_audioBufferSourceNodePassThrough.html
new file mode 100644
index 0000000000..6cb0cccf99
--- /dev/null
+++ b/dom/media/webaudio/test/test_audioBufferSourceNodePassThrough.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AudioBufferSourceNode with passthrough</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var buffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+
+ var source = context.createBufferSource();
+
+ source.buffer = buffer;
+
+ var srcWrapped = SpecialPowers.wrap(source);
+ ok("passThrough" in srcWrapped, "AudioBufferSourceNode should support the passThrough API");
+ srcWrapped.passThrough = true;
+
+ source.start(0);
+ return source;
+ },
+ createExpectedBuffers(context) {
+ var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate);
+
+ return [expectedBuffer];
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_audioBufferSourceNodeRate.html b/dom/media/webaudio/test/test_audioBufferSourceNodeRate.html
new file mode 100644
index 0000000000..60a065c2a9
--- /dev/null
+++ b/dom/media/webaudio/test/test_audioBufferSourceNodeRate.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AudioBufferSourceNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var rate = 44100;
+var off = new OfflineAudioContext(1, rate, rate);
+var off2 = new OfflineAudioContext(1, rate, rate);
+
+var source = off.createBufferSource();
+var source2 = off2.createBufferSource();
+
+// a buffer of a 440Hz at half the length. If we detune by -1200 or set the
+// playbackRate to 0.5, we should get 44100 samples back with a sine at 220Hz.
+var buf = off.createBuffer(1, rate / 2, rate);
+var bufarray = buf.getChannelData(0);
+for (var i = 0; i < bufarray.length; i++) {
+ bufarray[i] = Math.sin(i * 440 * 2 * Math.PI / rate);
+}
+
+source.buffer = buf;
+source.playbackRate.value = 0.25; // slowdown to 25% speed
+source.connect(off.destination);
+source.start(0);
+
+source2.buffer = buf;
+source2.detune.value = -2400; // two octaves -> slowdown to 25% speed
+source2.connect(off2.destination);
+source2.start(0);
+
+off.startRendering().then((renderedPlaybackRate) => {
+ // we don't care about comparing the value here, we just want to know whether
+ // the second part is noisy.
+ var rmsValue = rms(renderedPlaybackRate, 0, 22050);
+ ok(rmsValue != 0, "Resampling happened (rms of the second part " + rmsValue + ")");
+
+ off2.startRendering().then((renderedDetune) => {
+ var rmsValue = rms(renderedDetune, 0, 22050);
+ ok(rmsValue != 0, "Resampling happened (rms of the second part " + rmsValue + ")");
+ // The two buffers should be the same: detune of -2400 is a slowdown to 25% speed
+ compareBuffers(renderedPlaybackRate, renderedDetune);
+ SimpleTest.finish();
+ });
+});
+
+</script>
+</pre>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/media/webaudio/test/test_audioContextGC.html b/dom/media/webaudio/test/test_audioContextGC.html
new file mode 100644
index 0000000000..be9990cfad
--- /dev/null
+++ b/dom/media/webaudio/test/test_audioContextGC.html
@@ -0,0 +1,162 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test inactive AudioContext is garbage collected</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+let ids;
+
+const observer = (subject, topic, data) => {
+ const id = parseInt(data);
+ if (ids) {
+ ok(ids.delete(id), "Collected AudioNode id " + id);
+ }
+}
+SpecialPowers.addObserver(observer, "webaudio-node-demise");
+
+SimpleTest.registerCleanupFunction(function() {
+ if (observer) {
+ SpecialPowers.removeObserver(observer, "webaudio-node-demise");
+ }
+});
+
+function id(node) {
+ return SpecialPowers.getPrivilegedProps(node, "id");
+}
+
+let tests = [{
+ name: "Bare running AudioContext", setup: () => {
+ const ac = new AudioContext();
+ ids.add(id(ac.destination));
+ // Await state change notification before collection.
+ return new Promise((resolve) => {
+ ac.onstatechange = () => {
+ is(ac.state, "running", "ac.state");
+ resolve();
+ };
+ });
+ }
+}, {
+ name: "Stopped source", setup: () => {
+ const ac = new AudioContext();
+ ids.add(id(ac.destination));
+ const source = new ConstantSourceNode(ac);
+ ids.add(id(source));
+ source.start();
+ source.stop();
+ // Await ended notification before collection.
+ return new Promise((resolve) => {
+ source.onended = () => {
+ is(ac.state, "running", "ac.state");
+ resolve();
+ };
+ });
+ }
+}, {
+ name: "OfflineAudioContext not started", setup: () => {
+ const ac = new OfflineAudioContext({
+ numberOfChannels: 1, length: 1, sampleRate: 48000
+ });
+ ids.add(id(ac.destination));
+ const source = new ConstantSourceNode(ac);
+ ids.add(id(source));
+ source.start();
+ }
+}, {
+ name: "Completed OfflineAudioContext", setup: async () => {
+ const ac = new OfflineAudioContext({
+ numberOfChannels: 1, length: 1, sampleRate: 48000
+ });
+ ids.add(id(ac.destination));
+ const sourceBeforeStart = new ConstantSourceNode(ac);
+ ids.add(id(sourceBeforeStart));
+ sourceBeforeStart.start();
+ ac.startRendering();
+ await new Promise((resolve) => {
+ ac.oncomplete = () => {
+ resolve();
+ };
+ });
+ const sourceAfterComplete = new ConstantSourceNode(ac);
+ ids.add(id(sourceAfterComplete));
+ sourceAfterComplete.start();
+ }
+}, {
+ name: "suspended AudioContext", setup: async () => {
+ const ac = new AudioContext();
+ ids.add(id(ac.destination));
+ const sourceBeforeSuspend = new ConstantSourceNode(ac);
+ ids.add(id(sourceBeforeSuspend));
+ sourceBeforeSuspend.start();
+ ac.suspend();
+ const sourceAfterSuspend = new ConstantSourceNode(ac);
+ ids.add(id(sourceAfterSuspend));
+ sourceAfterSuspend.start();
+ await new Promise((resolve) => {
+ ac.onstatechange = () => {
+ if (ac.state == "suspended") {
+ resolve();
+ }
+ };
+ });
+ const sourceAfterSuspended = new ConstantSourceNode(ac);
+ ids.add(id(sourceAfterSuspended));
+ sourceAfterSuspended.start();
+ }
+}, {
+ name: "closed AudioContext", setup: async () => {
+ const ac = new AudioContext();
+ ids.add(id(ac.destination));
+ const sourceBeforeClose = new ConstantSourceNode(ac);
+ ids.add(id(sourceBeforeClose));
+ sourceBeforeClose.start();
+ ac.close();
+ const sourceAfterClose = new ConstantSourceNode(ac);
+ ids.add(id(sourceAfterClose));
+ sourceAfterClose.start();
+ await new Promise((resolve) => {
+ ac.onstatechange = () => {
+ if (ac.state == "closed") {
+ resolve();
+ }
+ };
+ });
+ const sourceAfterClosed = new ConstantSourceNode(ac);
+ ids.add(id(sourceAfterClosed));
+ sourceAfterClosed.start();
+ }
+}];
+
+const start_next_test = async () => {
+ const test = tests.shift();
+ if (!test) {
+ SimpleTest.finish();
+ return;
+ }
+ // Collect all audio nodes from previous tests.
+ if (!ids) {
+ await new Promise(resolve => {
+ SpecialPowers.exactGC(resolve);
+ });
+ }
+ ids = new Set();
+ await test.setup();
+ SpecialPowers.exactGC(() => {
+ is(ids.size, 0,
+ `All expected nodes for "${test.name}" should be collected`);
+ start_next_test();
+ });
+}
+
+start_next_test();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_audioContextParams_recordNonDefaultSampleRate.html b/dom/media/webaudio/test/test_audioContextParams_recordNonDefaultSampleRate.html
new file mode 100644
index 0000000000..9f2e6c9116
--- /dev/null
+++ b/dom/media/webaudio/test/test_audioContextParams_recordNonDefaultSampleRate.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body>
+<pre id="test">
+
+<script class="testbody" type="text/javascript">
+function startTest() {
+ let ctx = new AudioContext({sampleRate: 32000});
+ let oscillator = ctx.createOscillator();
+ let dest = ctx.createMediaStreamDestination();
+ oscillator.connect(dest);
+ oscillator.start();
+ let stream = dest.stream;
+
+ let recorder = new MediaRecorder(stream);
+ recorder.ondataavailable = (e) => {
+ ok(true, 'recorder ondataavailable event');
+ if (recorder.state == 'recording') {
+ ok(e.data.size > 0, 'check blob has data');
+ recorder.stop();
+ }
+ }
+
+ recorder.onstop = () => {
+ ok(true, 'recorder stop event');
+ SimpleTest.finish();
+ }
+
+ try {
+ recorder.start(1000);
+ ok(true, 'recorder started');
+ is('recording', recorder.state, 'check record state recording');
+ } catch (e) {
+ ok(false, 'Can t record audio context');
+ }
+}
+
+startTest();
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_audioContextParams_sampleRate.html b/dom/media/webaudio/test/test_audioContextParams_sampleRate.html
new file mode 100644
index 0000000000..65cee61243
--- /dev/null
+++ b/dom/media/webaudio/test/test_audioContextParams_sampleRate.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+
+<script>
+/* import-globals-from ../../webrtc/tests/mochitests/mediaStreamPlayback.js */
+createHTML({
+ title: "Parallel MTG by setting AudioContextParam sample rate",
+ bug: "1387454",
+ visible: true
+});
+
+runTest(async () => {
+ // Test an AudioContext of specific sample rate.
+ // Verify that the oscillator produces a tone.
+ const rate1 = 500;
+ const ac1 = new AudioContext({sampleRate: 44100});
+ const dest_ac1 = ac1.createMediaStreamDestination();
+ const osc_ac1 = ac1.createOscillator();
+ osc_ac1.frequency.value = rate1;
+ osc_ac1.connect(dest_ac1);
+ osc_ac1.start(0);
+
+ const analyser = new AudioStreamAnalyser(ac1, dest_ac1.stream);
+ analyser.enableDebugCanvas();
+ await analyser.waitForAnalysisSuccess( array => {
+ const freg_50Hz = array[analyser.binIndexForFrequency(50)];
+ const freq_rate1 = array[analyser.binIndexForFrequency(rate1)];
+ const freq_4000Hz = array[analyser.binIndexForFrequency(4000)];
+
+ info("Analysing audio frequency - low:target1:high = "
+ + freg_50Hz + ':' + freq_rate1 + ':' + freq_4000Hz);
+ return freg_50Hz < 50 && freq_rate1 > 200 && freq_4000Hz < 50;
+ })
+ osc_ac1.stop();
+
+ // Same test using a new AudioContext of different sample rate.
+ const rate2 = 1500;
+ const ac2 = new AudioContext({sampleRate: 48000});
+ const dest_ac2 = ac2.createMediaStreamDestination();
+ const osc_ac2 = ac2.createOscillator();
+ osc_ac2.frequency.value = rate2;
+ osc_ac2.connect(dest_ac2);
+ osc_ac2.start(0);
+
+ const analyser2 = new AudioStreamAnalyser(ac2, dest_ac2.stream);
+ analyser2.enableDebugCanvas();
+ await analyser2.waitForAnalysisSuccess( array => {
+ const freg_50Hz = array[analyser2.binIndexForFrequency(50)];
+ const freq_rate2 = array[analyser2.binIndexForFrequency(rate2)];
+ const freq_4000Hz = array[analyser2.binIndexForFrequency(4000)];
+
+ info("Analysing audio frequency - low:target2:high = "
+ + freg_50Hz + ':' + freq_rate2 + ':' + freq_4000Hz);
+ return freg_50Hz < 50 && freq_rate2 > 200 && freq_4000Hz < 50;
+ })
+ osc_ac2.stop();
+
+ // Two AudioContexts with different sample rate cannot communicate.
+ mustThrowWith("Connect nodes with different sample rate", "NotSupportedError",
+ () => ac2.createMediaStreamSource(dest_ac1.stream));
+
+ // Two AudioContext with the same sample rate can communicate.
+ const ac3 = new AudioContext({sampleRate: 48000});
+ const dest_ac3 = ac3.createMediaStreamDestination();
+ ac2.createMediaStreamSource(dest_ac3.stream);
+ ok(true, "Connect nodes with the same sample rate is ok");
+
+ mustThrowWith("Invalid zero samplerate", "NotSupportedError",
+ () => new AudioContext({sampleRate: 0}));
+
+ mustThrowWith("Invalid negative samplerate", "NotSupportedError",
+ () => new AudioContext({sampleRate: -1}));
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_audioContextSuspendResumeClose.html b/dom/media/webaudio/test/test_audioContextSuspendResumeClose.html
new file mode 100644
index 0000000000..36cf8f720c
--- /dev/null
+++ b/dom/media/webaudio/test/test_audioContextSuspendResumeClose.html
@@ -0,0 +1,419 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test suspend, resume and close method of the AudioContext</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function tryToCreateNodeOnClosedContext(ctx) {
+ is(ctx.state, "closed", "The context is in closed state");
+
+ [ { name: "createBufferSource" },
+ { name: "createMediaStreamDestination",
+ onOfflineAudioContext: false},
+ { name: "createScriptProcessor" },
+ { name: "createStereoPanner" },
+ { name: "createAnalyser" },
+ { name: "createGain" },
+ { name: "createDelay" },
+ { name: "createBiquadFilter" },
+ { name: "createWaveShaper" },
+ { name: "createPanner" },
+ { name: "createConvolver" },
+ { name: "createChannelSplitter" },
+ { name: "createChannelMerger" },
+ { name: "createDynamicsCompressor" },
+ { name: "createOscillator" },
+ { name: "createMediaElementSource",
+ args: [new Audio()],
+ onOfflineAudioContext: false },
+ { name: "createMediaStreamSource",
+ args: [(new AudioContext()).createMediaStreamDestination().stream],
+ onOfflineAudioContext: false } ].forEach(function(e) {
+
+ if (e.onOfflineAudioContext == false &&
+ ctx instanceof OfflineAudioContext) {
+ return;
+ }
+
+ expectNoException(function() {
+ ctx[e.name].apply(ctx, e.args);
+ }, DOMException.INVALID_STATE_ERR);
+ });
+}
+
+function loadFile(url, callback) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", url, true);
+ xhr.responseType = "arraybuffer";
+ xhr.onload = function() {
+ callback(xhr.response);
+ };
+ xhr.send();
+}
+
+// createBuffer, createPeriodicWave and decodeAudioData should work on a context
+// that has `state` == "closed"
+function tryLegalOpeerationsOnClosedContext(ctx) {
+ is(ctx.state, "closed", "The context is in closed state");
+
+ [ { name: "createBuffer",
+ args: [1, 44100, 44100] },
+ { name: "createPeriodicWave",
+ args: [new Float32Array(10), new Float32Array(10)] }
+ ].forEach(function(e) {
+ expectNoException(function() {
+ ctx[e.name].apply(ctx, e.args);
+ });
+ });
+ loadFile("ting-44.1k-1ch.ogg", function(buf) {
+ ctx.decodeAudioData(buf).then(function(decodedBuf) {
+ ok(true, "decodeAudioData on a closed context should work, it did.")
+ finish();
+ }).catch(function(e){
+ ok(false, "decodeAudioData on a closed context should work, it did not");
+ finish();
+ });
+ });
+}
+
+// Test that MediaStreams that are the output of a suspended AudioContext are
+// producing silence
+// ac1 produce a sine fed to a MediaStreamAudioDestinationNode
+// ac2 is connected to ac1 with a MediaStreamAudioSourceNode, and check that
+// there is silence when ac1 is suspended
+function testMultiContextOutput() {
+ var ac1 = new AudioContext(),
+ ac2 = new AudioContext();
+
+ ac1.onstatechange = function() {
+ ac1.onstatechange = null;
+
+ var osc1 = ac1.createOscillator(),
+ mediaStreamDestination1 = ac1.createMediaStreamDestination();
+
+ var mediaStreamAudioSourceNode2 =
+ ac2.createMediaStreamSource(mediaStreamDestination1.stream),
+ sp2 = ac2.createScriptProcessor(),
+ silentBuffersInARow = 0;
+
+
+ sp2.onaudioprocess = function(e) {
+ ac1.suspend().then(function() {
+ is(ac1.state, "suspended", "ac1 is suspended");
+ sp2.onaudioprocess = checkSilence;
+ });
+ sp2.onaudioprocess = null;
+ }
+
+ function checkSilence(e) {
+ var input = e.inputBuffer.getChannelData(0);
+ var silent = true;
+ for (var i = 0; i < input.length; i++) {
+ if (input[i] != 0.0) {
+ silent = false;
+ }
+ }
+
+ if (silent) {
+ silentBuffersInARow++;
+ if (silentBuffersInARow == 10) {
+ ok(true,
+ "MediaStreams produce silence when their input is blocked.");
+ sp2.onaudioprocess = null;
+ ac1.close();
+ ac2.close();
+ finish();
+ }
+ } else {
+ is(silentBuffersInARow, 0,
+ "No non silent buffer inbetween silent buffers.");
+ }
+ }
+
+ osc1.connect(mediaStreamDestination1);
+
+ mediaStreamAudioSourceNode2.connect(sp2);
+ osc1.start();
+ }
+}
+
+
+// Test that there is no buffering between contexts when connecting a running
+// AudioContext to a suspended AudioContext. Our ScriptProcessorNode does some
+// buffering internally, so we ensure this by using a very very low frequency
+// on a sine, and oberve that the phase has changed by a big enough margin.
+function testMultiContextInput() {
+ var ac1 = new AudioContext(),
+ ac2 = new AudioContext();
+
+ ac1.onstatechange = function() {
+ ac1.onstatechange = null;
+
+ var osc1 = ac1.createOscillator(),
+ mediaStreamDestination1 = ac1.createMediaStreamDestination(),
+ sp1 = ac1.createScriptProcessor();
+
+ var mediaStreamAudioSourceNode2 =
+ ac2.createMediaStreamSource(mediaStreamDestination1.stream),
+ sp2 = ac2.createScriptProcessor(),
+ eventReceived = 0;
+
+
+ osc1.frequency.value = 0.0001;
+
+ function checkDiscontinuity(e) {
+ var inputBuffer = e.inputBuffer.getChannelData(0);
+ if (eventReceived++ == 3) {
+ var delta = Math.abs(inputBuffer[1] - sp2.value),
+ theoreticalIncrement = 2048 * 3 * Math.PI * 2 * osc1.frequency.value / ac1.sampleRate;
+ ok(delta >= theoreticalIncrement,
+ "Buffering did not occur when the context was suspended (delta:" + delta + " increment: " + theoreticalIncrement+")");
+ ac1.close();
+ ac2.close();
+ sp1.onaudioprocess = null;
+ sp2.onaudioprocess = null;
+ finish();
+ }
+ }
+
+ sp2.onaudioprocess = function(e) {
+ var inputBuffer = e.inputBuffer.getChannelData(0);
+ sp2.value = inputBuffer[inputBuffer.length - 1];
+ ac2.suspend().then(function() {
+ ac2.resume().then(function() {
+ sp2.onaudioprocess = checkDiscontinuity;
+ });
+ });
+ }
+
+ osc1.connect(mediaStreamDestination1);
+ osc1.connect(sp1);
+
+ mediaStreamAudioSourceNode2.connect(sp2);
+ osc1.start();
+ }
+}
+
+// Test that ScriptProcessorNode's onaudioprocess don't get called while the
+// context is suspended/closed. It is possible that we get the handler called
+// exactly once after suspend, because the event has already been sent to the
+// event loop.
+function testScriptProcessNodeSuspended() {
+ var ac = new AudioContext();
+ var sp = ac.createScriptProcessor();
+ var remainingIterations = 30;
+ var afterResume = false;
+ ac.onstatechange = function() {
+ ac.onstatechange = null;
+ sp.onaudioprocess = function() {
+ ok(ac.state == "running", "If onaudioprocess is called, the context" +
+ " must be running (was " + ac.state + ", remainingIterations:" + remainingIterations +")");
+ remainingIterations--;
+ if (!afterResume) {
+ if (remainingIterations == 0) {
+ ac.suspend().then(function() {
+ ac.resume().then(function() {
+ remainingIterations = 30;
+ afterResume = true;
+ });
+ });
+ }
+ } else {
+ sp.onaudioprocess = null;
+ finish();
+ }
+ }
+ }
+ sp.connect(ac.destination);
+}
+
+// Take an AudioContext, make sure it switches to running when the audio starts
+// flowing, and then, call suspend, resume and close on it, tracking its state.
+function testAudioContext() {
+ var ac = new AudioContext();
+ is(ac.state, "suspended", "AudioContext should start in suspended state.");
+ var stateTracker = {
+ previous: ac.state,
+ // no promise for the initial suspended -> running
+ initial: { handler: false },
+ suspend: { promise: false, handler: false },
+ resume: { promise: false, handler: false },
+ close: { promise: false, handler: false }
+ };
+
+ function initialSuspendToRunning() {
+ ok(stateTracker.previous == "suspended" &&
+ ac.state == "running",
+ "AudioContext should switch to \"running\" when the audio hardware is" +
+ " ready.");
+
+ stateTracker.previous = ac.state;
+ ac.onstatechange = afterSuspend;
+ stateTracker.initial.handler = true;
+
+ ac.suspend().then(function() {
+ ok(!stateTracker.suspend.promise && !stateTracker.suspend.handler,
+ "Promise should be resolved before the callback, and only once.")
+ stateTracker.suspend.promise = true;
+ });
+ }
+
+ function afterSuspend() {
+ ok(stateTracker.previous == "running" &&
+ ac.state == "suspended",
+ "AudioContext should switch to \"suspend\" when the audio stream is" +
+ "suspended.");
+ ok(stateTracker.suspend.promise && !stateTracker.suspend.handler,
+ "Handler should be called after the callback, and only once");
+
+ stateTracker.suspend.handler = true;
+ stateTracker.previous = ac.state;
+ ac.onstatechange = afterResume;
+
+ ac.resume().then(function() {
+ ok(!stateTracker.resume.promise && !stateTracker.resume.handler,
+ "Promise should be called before the callback, and only once");
+ stateTracker.resume.promise = true;
+ });
+ }
+
+ function afterResume() {
+ ok(stateTracker.previous == "suspended" &&
+ ac.state == "running",
+ "AudioContext should switch to \"running\" when the audio stream resumes.");
+
+ ok(stateTracker.resume.promise && !stateTracker.resume.handler,
+ "Handler should be called after the callback, and only once");
+
+ stateTracker.resume.handler = true;
+ stateTracker.previous = ac.state;
+ ac.onstatechange = afterClose;
+
+ ac.close().then(function() {
+ ok(!stateTracker.close.promise && !stateTracker.close.handler,
+ "Promise should be called before the callback, and only once");
+ stateTracker.close.promise = true;
+ tryToCreateNodeOnClosedContext(ac);
+ tryLegalOpeerationsOnClosedContext(ac);
+ });
+ }
+
+ function afterClose() {
+ ok(stateTracker.previous == "running" &&
+ ac.state == "closed",
+ "AudioContext should switch to \"closed\" when the audio stream is" +
+ " closed.");
+ ok(stateTracker.close.promise && !stateTracker.close.handler,
+ "Handler should be called after the callback, and only once");
+ }
+
+ ac.onstatechange = initialSuspendToRunning;
+}
+
+function testOfflineAudioContext() {
+ var o = new OfflineAudioContext(1, 44100, 44100);
+ is(o.state, "suspended", "OfflineAudioContext should start in suspended state.");
+
+ expectRejectedPromise(o, "resume", "NotSupportedError");
+
+ var previousState = o.state,
+ finishedRendering = false;
+ function beforeStartRendering() {
+ ok(previousState == "suspended" && o.state == "running", "onstatechanged" +
+ "handler is called on state changed, and the new state is running");
+ previousState = o.state;
+ o.onstatechange = onRenderingFinished;
+ }
+
+ function onRenderingFinished() {
+ ok(previousState == "running" && o.state == "closed",
+ "onstatechanged handler is called when rendering finishes, " +
+ "and the new state is closed");
+ ok(finishedRendering, "The Promise that is resolved when the rendering is" +
+ "done should be resolved earlier than the state change.");
+ previousState = o.state;
+ o.onstatechange = afterRenderingFinished;
+
+ tryToCreateNodeOnClosedContext(o);
+ tryLegalOpeerationsOnClosedContext(o);
+ }
+
+ function afterRenderingFinished() {
+ ok(false, "There should be no transition out of the closed state.");
+ }
+
+ o.onstatechange = beforeStartRendering;
+
+ o.startRendering().then(function(buffer) {
+ finishedRendering = true;
+ });
+}
+
+function testSuspendResumeEventLoop() {
+ var ac = new AudioContext();
+ var source = ac.createBufferSource();
+ source.buffer = ac.createBuffer(1, 44100, 44100);
+ source.onended = function() {
+ ok(true, "The AudioContext did resume.");
+ finish();
+ }
+ ac.onstatechange = function() {
+ ac.onstatechange = null;
+
+ ok(ac.state == "running", "initial state is running");
+ ac.suspend();
+ source.start();
+ ac.resume();
+ }
+}
+
+function testResumeInStateChangeForResumeCallback() {
+ // Regression test for bug 1468085.
+ var ac = new AudioContext;
+ ac.onstatechange = function() {
+ ac.resume().then(() => {
+ ok(true, "resume promise resolved as expected.");
+ finish();
+ });
+ }
+}
+
+var remaining = 0;
+function finish() {
+ remaining--;
+ if (remaining == 0) {
+ SimpleTest.finish();
+ }
+}
+
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var tests = [
+ testOfflineAudioContext,
+ testScriptProcessNodeSuspended,
+ testMultiContextOutput,
+ testMultiContextInput,
+ testSuspendResumeEventLoop,
+ testResumeInStateChangeForResumeCallback
+ ];
+
+ // See Bug 1305136, many intermittent failures on Linux
+ if (!navigator.platform.startsWith("Linux")) {
+ tests.push(testAudioContext);
+ }
+
+ remaining = tests.length;
+ tests.forEach(function(f) { f() });
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_audioDestinationNode.html b/dom/media/webaudio/test/test_audioDestinationNode.html
new file mode 100644
index 0000000000..e7a8b091ba
--- /dev/null
+++ b/dom/media/webaudio/test/test_audioDestinationNode.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AudioDestinationNode as EventTarget</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var ac = new AudioContext()
+ac.destination.addEventListener("foo", function() {
+ ok(true, "Event received!");
+ SimpleTest.finish();
+});
+ac.destination.dispatchEvent(new CustomEvent("foo"));
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/media/webaudio/test/test_audioParamChaining.html b/dom/media/webaudio/test/test_audioParamChaining.html
new file mode 100644
index 0000000000..85b8099e2e
--- /dev/null
+++ b/dom/media/webaudio/test/test_audioParamChaining.html
@@ -0,0 +1,77 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test whether we can create an AudioContext interface</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish()
+
+function frameToTime(frame, rate)
+{
+ return frame / rate;
+}
+
+const RATE = 44100;
+
+var oc = new OfflineAudioContext(1, 44100, RATE);
+// This allows us to have a source that is simply a DC offset.
+var source = oc.createBufferSource();
+var buf = oc.createBuffer(1, 1, RATE);
+buf.getChannelData(0)[0] = 1;
+source.loop = true;
+source.buffer = buf;
+
+source.start(0);
+
+var gain = oc.createGain();
+
+source.connect(gain).connect(oc.destination);
+
+var gain2 = oc.createGain();
+var rv2 = gain2.gain.linearRampToValueAtTime(0.1, 0.5);
+ok(rv2 instanceof AudioParam, "linearRampToValueAtTime returns an AudioParam.");
+ok(rv2 == gain2.gain, "linearRampToValueAtTime returns the right AudioParam.");
+
+rv2 = gain2.gain.exponentialRampToValueAtTime(0.01, 1.0);
+ok(rv2 instanceof AudioParam,
+ "exponentialRampToValueAtTime returns an AudioParam.");
+ok(rv2 == gain2.gain,
+ "exponentialRampToValueAtTime returns the right AudioParam.");
+
+rv2 = gain2.gain.setTargetAtTime(1.0, 2.0, 0.1);
+ok(rv2 instanceof AudioParam, "setTargetAtTime returns an AudioParam.");
+ok(rv2 == gain2.gain, "setTargetAtTime returns the right AudioParam.");
+
+var array = new Float32Array(10);
+rv2 = gain2.gain.setValueCurveAtTime(array, 10, 11);
+ok(rv2 instanceof AudioParam, "setValueCurveAtTime returns an AudioParam.");
+ok(rv2 == gain2.gain, "setValueCurveAtTime returns the right AudioParam.");
+
+// We chain three automation methods, making a gain step.
+var rv = gain.gain.setValueAtTime(0, frameToTime(0, RATE))
+ .setValueAtTime(0.5, frameToTime(22000, RATE))
+ .setValueAtTime(1, frameToTime(44000, RATE));
+
+ok(rv instanceof AudioParam, "setValueAtTime returns an AudioParam.");
+ok(rv == gain.gain, "setValueAtTime returns the right AudioParam.");
+
+oc.startRendering().then(function(rendered) {
+ console.log(rendered.getChannelData(0));
+ is(rendered.getChannelData(0)[0], 0,
+ "The value of the first step is correct.");
+ is(rendered.getChannelData(0)[22050], 0.5,
+ "The value of the second step is correct");
+ is(rendered.getChannelData(0)[44099], 1,
+ "The value of the third step is correct.");
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_audioParamExponentialRamp.html b/dom/media/webaudio/test/test_audioParamExponentialRamp.html
new file mode 100644
index 0000000000..2416b5de14
--- /dev/null
+++ b/dom/media/webaudio/test/test_audioParamExponentialRamp.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AudioParam.exponentialRampToValue</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var V0 = 0.1;
+var V1 = 0.9;
+var T0 = 0;
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var sourceBuffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ sourceBuffer.getChannelData(0)[i] = 1;
+ }
+
+ var source = context.createBufferSource();
+ source.buffer = sourceBuffer;
+
+ var gain = context.createGain();
+ gain.gain.setValueAtTime(V0, 0);
+ gain.gain.exponentialRampToValueAtTime(V1, 2048/context.sampleRate);
+
+ source.connect(gain);
+
+ source.start(0);
+ return gain;
+ },
+ createExpectedBuffers(context) {
+ var T1 = 2048 / context.sampleRate;
+ var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ var t = i / context.sampleRate;
+ expectedBuffer.getChannelData(0)[i] = V0 * Math.pow(V1 / V0, (t - T0) / (T1 - T0));
+ }
+ return expectedBuffer;
+ },
+};
+
+
+SimpleTest.waitForExplicitFinish();
+// Comparing different AudioContexts may result in different timing reated information being reported
+// when we jitter time, as they are on different Relative Timelines.
+SpecialPowers.pushPrefEnv({"set": [["privacy.resistFingerprinting.reduceTimerPrecision.jitter", false]]}, runTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_audioParamGain.html b/dom/media/webaudio/test/test_audioParamGain.html
new file mode 100644
index 0000000000..3977b94703
--- /dev/null
+++ b/dom/media/webaudio/test/test_audioParamGain.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AudioParam with pre-gain </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var ctx = new AudioContext();
+var source = ctx.createOscillator();
+var lfo = ctx.createOscillator();
+var lfoIntensity = ctx.createGain();
+var effect = ctx.createGain();
+var sp = ctx.createScriptProcessor(2048, 1);
+
+source.frequency.value = 440;
+lfo.frequency.value = 2;
+// Very low gain, so the LFO should have very little influence
+// on the source, its RMS value should be close to the nominal value
+// for a sine wave.
+lfoIntensity.gain.value = 0.0001;
+
+lfo.connect(lfoIntensity);
+lfoIntensity.connect(effect.gain);
+source.connect(effect);
+effect.connect(sp);
+
+sp.onaudioprocess = function(e) {
+ var buffer = e.inputBuffer.getChannelData(0);
+ var rms = 0;
+ for (var i = 0; i < buffer.length; i++) {
+ rms += buffer[i] * buffer[i];
+ }
+
+ rms /= buffer.length;
+ rms = Math.sqrt(rms);
+
+ // 1 / Math.sqrt(2) is the theoretical RMS value for a sine wave.
+ ok(fuzzyCompare(rms, 1 / Math.sqrt(2)),
+ "Gain correctly applied to the AudioParam.");
+
+ ctx = null;
+ sp.onaudioprocess = null;
+ lfo.stop(0);
+ source.stop(0);
+
+ SimpleTest.finish();
+}
+
+lfo.start(0);
+source.start(0);
+
+</script>
+</pre>
+</body>
diff --git a/dom/media/webaudio/test/test_audioParamLinearRamp.html b/dom/media/webaudio/test/test_audioParamLinearRamp.html
new file mode 100644
index 0000000000..5ec26467e8
--- /dev/null
+++ b/dom/media/webaudio/test/test_audioParamLinearRamp.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AudioParam.linearRampToValue</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var V0 = 0.1;
+var V1 = 0.9;
+var T0 = 0;
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var sourceBuffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ sourceBuffer.getChannelData(0)[i] = 1;
+ }
+
+ var source = context.createBufferSource();
+ source.buffer = sourceBuffer;
+
+ var gain = context.createGain();
+ gain.gain.setValueAtTime(V0, 0);
+ gain.gain.linearRampToValueAtTime(V1, 2048/context.sampleRate);
+
+ source.connect(gain);
+
+ source.start(0);
+ return gain;
+ },
+ createExpectedBuffers(context) {
+ var T1 = 2048 / context.sampleRate;
+ var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ var t = i / context.sampleRate;
+ expectedBuffer.getChannelData(0)[i] = V0 + (V1 - V0) * ((t - T0) / (T1 - T0));
+ }
+ return expectedBuffer;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_audioParamSetCurveAtTime.html b/dom/media/webaudio/test/test_audioParamSetCurveAtTime.html
new file mode 100644
index 0000000000..e21b58bb19
--- /dev/null
+++ b/dom/media/webaudio/test/test_audioParamSetCurveAtTime.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AudioParam.linearRampToValue</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var T0 = 0;
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var source = context.createConstantSource();
+
+ var gain = context.createGain();
+ gain.gain.setValueCurveAtTime(this.curve, T0, this.duration);
+ source.connect(gain);
+
+ source.start(0);
+ return gain;
+ },
+ createExpectedBuffers(context) {
+ this.duration = 1024 / context.sampleRate;
+ this.curve = new Float32Array([1.0, 0.5, 0.75, 0.25]);
+ var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate);
+ var data = expectedBuffer.getChannelData(0);
+ var step = 1024 / 3;
+ for (var i = 0; i < 2048; ++i) {
+ if (i < step) {
+ data[i] = 1.0 - 0.5*i/step;
+ } else if (i < 2*step) {
+ data[i] = 0.5 + 0.25*(i - step)/step;
+ } else if (i < 3*step) {
+ data[i] = 0.75 - 0.5*(i - 2*step)/step;
+ } else {
+ data[i] = 0.25;
+ }
+ }
+ return expectedBuffer;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_audioParamSetTargetAtTime.html b/dom/media/webaudio/test/test_audioParamSetTargetAtTime.html
new file mode 100644
index 0000000000..442e209be6
--- /dev/null
+++ b/dom/media/webaudio/test/test_audioParamSetTargetAtTime.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AudioParam.setTargetAtTime</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var V0 = 0.9;
+var V1 = 0.1;
+var T0 = 0;
+var TimeConstant = 10;
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var sourceBuffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ sourceBuffer.getChannelData(0)[i] = 1;
+ }
+
+ var source = context.createBufferSource();
+ source.buffer = sourceBuffer;
+
+ var gain = context.createGain();
+ gain.gain.value = V0;
+ gain.gain.setTargetAtTime(V1, T0, TimeConstant);
+
+ source.connect(gain);
+
+ source.start(0);
+ return gain;
+ },
+ createExpectedBuffers(context) {
+ var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ var t = i / context.sampleRate;
+ expectedBuffer.getChannelData(0)[i] = V1 + (V0 - V1) * Math.exp(-(t - T0) / TimeConstant);
+ }
+ return expectedBuffer;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_audioParamSetTargetAtTimeZeroTimeConstant.html b/dom/media/webaudio/test/test_audioParamSetTargetAtTimeZeroTimeConstant.html
new file mode 100644
index 0000000000..2d18447a0d
--- /dev/null
+++ b/dom/media/webaudio/test/test_audioParamSetTargetAtTimeZeroTimeConstant.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AudioParam.setTargetAtTime with zero time constant</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var V0 = 0.9;
+var V1 = 0.1;
+var T0 = 0;
+var TimeConstant = 0;
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var sourceBuffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ sourceBuffer.getChannelData(0)[i] = 1;
+ }
+
+ var source = context.createBufferSource();
+ source.buffer = sourceBuffer;
+
+ var gain = context.createGain();
+ gain.gain.value = V0;
+ gain.gain.setTargetAtTime(V1, T0, TimeConstant);
+
+ source.connect(gain);
+
+ source.start(0);
+ return gain;
+ },
+ createExpectedBuffers(context) {
+ var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate);
+ expectedBuffer.getChannelData(0)[0] = V0;
+ for (var i = 1; i < 2048; ++i) {
+ expectedBuffer.getChannelData(0)[i] = V1;
+ }
+ return expectedBuffer;
+ },
+};
+
+SimpleTest.waitForExplicitFinish();
+// Comparing different AudioContexts may result in different timing reated information being reported
+// when we jitter time, as they are on different Relative Timelines.
+SpecialPowers.pushPrefEnv({"set": [["privacy.resistFingerprinting.reduceTimerPrecision.jitter", false]]}, runTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_audioParamSetValueAtTime.html b/dom/media/webaudio/test/test_audioParamSetValueAtTime.html
new file mode 100644
index 0000000000..18c02837e6
--- /dev/null
+++ b/dom/media/webaudio/test/test_audioParamSetValueAtTime.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AudioParam.linearRampToValue</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var V0 = 0.1;
+var V1 = 0.9;
+var T0 = 0;
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var sourceBuffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ sourceBuffer.getChannelData(0)[i] = 1;
+ }
+
+ var source = context.createBufferSource();
+ source.buffer = sourceBuffer;
+
+ var gain = context.createGain();
+ gain.gain.value = 0;
+ gain.gain.setValueAtTime(V0, 1024/context.sampleRate);
+
+ source.connect(gain);
+
+ source.start(0);
+ return gain;
+ },
+ createExpectedBuffers(context) {
+ var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 1024; i < 2048; ++i) {
+ expectedBuffer.getChannelData(0)[i] = 0.1;
+ }
+ return expectedBuffer;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_audioParamTimelineDestinationOffset.html b/dom/media/webaudio/test/test_audioParamTimelineDestinationOffset.html
new file mode 100644
index 0000000000..76db12f88a
--- /dev/null
+++ b/dom/media/webaudio/test/test_audioParamTimelineDestinationOffset.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AudioParam timeline events scheduled after the destination stream has started playback</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.requestFlakyTimeout("This test needs to wait until the AudioDestinationNode's stream's timer starts.");
+
+var gTest = {
+ length: 16384,
+ numberOfChannels: 1,
+ createGraphAsync(context, callback) {
+ var sourceBuffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ sourceBuffer.getChannelData(0)[i] = 1;
+ }
+
+ setTimeout(function() {
+ var source = context.createBufferSource();
+ source.buffer = sourceBuffer;
+ source.start(context.currentTime);
+ source.stop(context.currentTime + sourceBuffer.duration);
+
+ var gain = context.createGain();
+ gain.gain.setValueAtTime(0, context.currentTime);
+ gain.gain.setTargetAtTime(0, context.currentTime + sourceBuffer.duration, 1);
+ source.connect(gain);
+
+ callback(gain);
+ }, 100);
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_badConnect.html b/dom/media/webaudio/test/test_badConnect.html
new file mode 100644
index 0000000000..51e83f6088
--- /dev/null
+++ b/dom/media/webaudio/test/test_badConnect.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test whether we can create an AudioContext interface</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script src="webaudio.js" type="text/javascript"></script>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var context1 = new OfflineAudioContext(1, 128, 44100);
+ var context2 = new OfflineAudioContext(1, 128, 44100);
+
+ var destination1 = context1.destination;
+ var destination2 = context2.destination;
+ var gain1 = new GainNode(context2);
+
+ isnot(destination1, destination2, "Destination nodes should not be the same");
+ isnot(destination1.context, destination2.context, "Destination nodes should not have the same context");
+
+ var source1 = context1.createBufferSource();
+
+ expectException(function() {
+ source1.connect(destination1, 1);
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ source1.connect(destination1, 0, 1);
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ source1.connect(destination2);
+ }, DOMException.INVALID_ACCESS_ERR);
+ expectException(function() {
+ source1.connect(gain1.gain);
+ }, DOMException.INVALID_ACCESS_ERR);
+
+ source1.connect(destination1);
+
+ expectException(function() {
+ source1.disconnect(1);
+ }, DOMException.INDEX_SIZE_ERR);
+
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_biquadFilterNode.html b/dom/media/webaudio/test/test_biquadFilterNode.html
new file mode 100644
index 0000000000..561198c491
--- /dev/null
+++ b/dom/media/webaudio/test/test_biquadFilterNode.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test BiquadFilterNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script src="webaudio.js" type="text/javascript"></script>
+<script class="testbody" type="text/javascript">
+
+function near(a, b, msg) {
+ ok(Math.abs(a - b) < 1e-3, msg);
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var context = new AudioContext();
+ var buffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+
+ var destination = context.destination;
+
+ var source = context.createBufferSource();
+
+ var filter = context.createBiquadFilter();
+
+ source.buffer = buffer;
+
+ source.connect(filter);
+ filter.connect(destination);
+
+ // Verify default values
+ is(filter.type, "lowpass", "Correct default value for type");
+ near(filter.frequency.defaultValue, 350, "Correct default value for filter frequency");
+ near(filter.detune.defaultValue, 0, "Correct default value for filter detune");
+ near(filter.Q.defaultValue, 1, "Correct default value for filter Q");
+ near(filter.gain.defaultValue, 0, "Correct default value for filter gain");
+ is(filter.channelCount, 2, "Biquad filter node has 2 input channels by default");
+ is(filter.channelCountMode, "max", "Correct channelCountMode for the biquad filter node");
+ is(filter.channelInterpretation, "speakers", "Correct channelCountInterpretation for the biquad filter node");
+
+ // Make sure that we can set all of the valid type values
+ var types = [
+ "lowpass",
+ "highpass",
+ "bandpass",
+ "lowshelf",
+ "highshelf",
+ "peaking",
+ "notch",
+ "allpass",
+ ];
+ for (var i = 0; i < types.length; ++i) {
+ filter.type = types[i];
+ }
+
+ // Make sure getFrequencyResponse handles invalid frequencies properly
+ var frequencies = new Float32Array([-1.0, context.sampleRate*0.5 - 1.0, context.sampleRate]);
+ var magResults = new Float32Array(3);
+ var phaseResults = new Float32Array(3);
+ filter.getFrequencyResponse(frequencies, magResults, phaseResults);
+ ok(isNaN(magResults[0]), "Invalid input frequency should give NaN magnitude response");
+ ok(!isNaN(magResults[1]), "Valid input frequency should not give NaN magnitude response");
+ ok(isNaN(magResults[2]), "Invalid input frequency should give NaN magnitude response");
+ ok(isNaN(phaseResults[0]), "Invalid input frquency should give NaN phase response");
+ ok(!isNaN(phaseResults[1]), "Valid input frquency should not give NaN phase response");
+ ok(isNaN(phaseResults[2]), "Invalid input frquency should give NaN phase response");
+
+ source.start(0);
+ SimpleTest.executeSoon(function() {
+ source.stop(0);
+ source.disconnect();
+ filter.disconnect();
+
+ SimpleTest.finish();
+ });
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_biquadFilterNodePassThrough.html b/dom/media/webaudio/test/test_biquadFilterNodePassThrough.html
new file mode 100644
index 0000000000..4db01b0b14
--- /dev/null
+++ b/dom/media/webaudio/test/test_biquadFilterNodePassThrough.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test BiquadFilterNode with passthrough</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var source = context.createBufferSource();
+
+ var filter = context.createBiquadFilter();
+
+ source.buffer = this.buffer;
+
+ source.connect(filter);
+
+ var filterWrapped = SpecialPowers.wrap(filter);
+ ok("passThrough" in filterWrapped, "BiquadFilterNode should support the passThrough API");
+ filterWrapped.passThrough = true;
+
+ source.start(0);
+ return filter;
+ },
+ createExpectedBuffers(context) {
+ this.buffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ this.buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+
+ return [this.buffer];
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_biquadFilterNodeWithGain.html b/dom/media/webaudio/test/test_biquadFilterNodeWithGain.html
new file mode 100644
index 0000000000..d97a753de9
--- /dev/null
+++ b/dom/media/webaudio/test/test_biquadFilterNodeWithGain.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test BiquadFilterNode after a GainNode and tail - Bugs 924286 and 924288</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script src="webaudio.js" type="text/javascript"></script>
+<script class="testbody" type="text/javascript">
+
+const signalLength = 2048;
+
+var gTest = {
+ length: signalLength,
+ numberOfChannels: 1,
+ createGraph(context) {
+ // Two oscillators scheduled sequentially
+ var signalDuration = signalLength / context.sampleRate;
+ var osc1 = context.createOscillator();
+ osc1.type = "square";
+ osc1.start(0);
+ osc1.stop(signalDuration / 2);
+ var osc2 = context.createOscillator();
+ osc2.start(signalDuration / 2);
+ osc2.stop(signalDuration);
+
+ // Comparing a biquad on each source with one on both sources checks that
+ // the biquad on the first source doesn't shut down early.
+ var biquad1 = context.createBiquadFilter();
+ osc1.connect(biquad1);
+ var biquad2 = context.createBiquadFilter();
+ osc2.connect(biquad2);
+
+ var gain = context.createGain();
+ gain.gain.value = -1;
+ osc1.connect(gain);
+ osc2.connect(gain);
+
+ var biquadWithGain = context.createBiquadFilter();
+ gain.connect(biquadWithGain);
+
+ // The output of biquadWithGain should be the inverse of the sum of the
+ // outputs of biquad1 and biquad2, so blend them together and expect
+ // silence.
+ var blend = context.createGain();
+ biquad1.connect(blend);
+ biquad2.connect(blend);
+ biquadWithGain.connect(blend);
+
+ return blend;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_bug1027864.html b/dom/media/webaudio/test/test_bug1027864.html
new file mode 100644
index 0000000000..847485ff88
--- /dev/null
+++ b/dom/media/webaudio/test/test_bug1027864.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test bug 1027864</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+function observer(subject, topic, data) {
+ var id = parseInt(data);
+ var index = ids.indexOf(id);
+ if (index != -1) {
+ ok(true, "Dropping id " + id + " at index " + index);
+ ids.splice(index, 1);
+ if (!ids.length) {
+ SimpleTest.executeSoon(function() {
+ SimpleTest.finish();
+ });
+ }
+ }
+}
+
+function id(node) {
+ return SpecialPowers.getPrivilegedProps(node, "id");
+}
+
+SpecialPowers.addAsyncObserver(observer, "webaudio-node-demise", false);
+
+SimpleTest.registerCleanupFunction(function() {
+ SpecialPowers.removeAsyncObserver(observer, "webaudio-node-demise");
+});
+
+var ac = new AudioContext();
+var ids;
+
+(function() {
+ var delay = ac.createDelay();
+ delay.delayTime.value = 0.03;
+
+ var gain = ac.createGain();
+ gain.gain.value = 0.6;
+
+ delay.connect(gain);
+ gain.connect(delay);
+
+ gain.connect(ac.destination);
+
+ var source = ac.createOscillator();
+
+ source.connect(gain);
+ source.start(ac.currentTime);
+ source.stop(ac.currentTime + 0.1);
+
+ ids = [ id(delay), id(gain), id(source) ];
+})();
+
+setInterval(function() {
+ forceCC();
+}, 200);
+
+function forceCC() {
+ SpecialPowers.DOMWindowUtils.cycleCollect();
+ SpecialPowers.DOMWindowUtils.garbageCollect();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_bug1056032.html b/dom/media/webaudio/test/test_bug1056032.html
new file mode 100644
index 0000000000..ba38267e19
--- /dev/null
+++ b/dom/media/webaudio/test/test_bug1056032.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<meta charset=utf-8>
+<head>
+ <title>Test that we can decode an mp3 (bug 1056032)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var filename = "small-shot.mp3";
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", filename);
+ xhr.responseType = "arraybuffer";
+ xhr.onload = function() {
+ var context = new AudioContext();
+ context.decodeAudioData(xhr.response, function(b) {
+ ok(true, "We can decode an mp3 using decodeAudioData");
+ SimpleTest.finish();
+ }, function() {
+ ok(false, "We should be able to decode an mp3 using decodeAudioData but couldn't");
+ SimpleTest.finish();
+ });
+ };
+ xhr.send(null);
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_bug1113634.html b/dom/media/webaudio/test/test_bug1113634.html
new file mode 100644
index 0000000000..acdcba7c25
--- /dev/null
+++ b/dom/media/webaudio/test/test_bug1113634.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AudioParam.setTargetAtTime where the target time is the same as the time of a previous event</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var V0 = 0.9;
+var V1 = 0.1;
+var T0 = 0;
+var TimeConstant = 0.1;
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var sourceBuffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ sourceBuffer.getChannelData(0)[i] = 1;
+ }
+
+ var source = context.createBufferSource();
+ source.buffer = sourceBuffer;
+
+ var gain = context.createGain();
+ gain.gain.setValueAtTime(V0, T0);
+ gain.gain.setTargetAtTime(V1, T0, TimeConstant);
+
+ source.connect(gain);
+
+ source.start(0);
+ return gain;
+ },
+ createExpectedBuffers(context) {
+ var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ var t = i / context.sampleRate;
+ expectedBuffer.getChannelData(0)[i] = V1 + (V0 - V1) * Math.exp(-(t - T0) / TimeConstant);
+ }
+ return expectedBuffer;
+ },
+};
+
+SimpleTest.waitForExplicitFinish();
+// Comparing different AudioContexts may result in different timing reated information being reported
+// when we jitter time, as they are on different Relative Timelines.
+SpecialPowers.pushPrefEnv({"set": [["privacy.resistFingerprinting.reduceTimerPrecision.jitter", false]]}, runTest);
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_bug1118372.html b/dom/media/webaudio/test/test_bug1118372.html
new file mode 100644
index 0000000000..f049b221e8
--- /dev/null
+++ b/dom/media/webaudio/test/test_bug1118372.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test WaveShaperNode with no curve</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ var context = new OfflineAudioContext(1, 2048, 44100);
+
+ var osc=context.createOscillator();
+ var gain=context.createGain();
+ var shaper=context.createWaveShaper();
+ gain.gain.value=0.1;
+ shaper.curve=new Float32Array([-0.5,-0.5,1,1]);
+
+ osc.connect(gain);
+ gain.connect(shaper);
+ shaper.connect(context.destination);
+ osc.start(0);
+
+ context.startRendering().then(function(buffer) {
+ var samples = buffer.getChannelData(0);
+ // the signal should be scaled
+ var failures = 0;
+ for (var i = 0; i < 2048; ++i) {
+ if (samples[i] > 0.5) {
+ failures = failures + 1;
+ }
+ }
+ ok(failures == 0, "signal should have been rescaled by gain: found " + failures + " points too loud.");
+ SimpleTest.finish();
+ });
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_bug1255618.html b/dom/media/webaudio/test/test_bug1255618.html
new file mode 100644
index 0000000000..15e7351995
--- /dev/null
+++ b/dom/media/webaudio/test/test_bug1255618.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test sync XHR does not crash unlinked AudioContext</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+const filename = "test_bug1255618.html";
+
+function collect_and_fetch() {
+ SpecialPowers.forceGC();
+ SpecialPowers.forceCC();
+
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", filename, false);
+ var ended = false;
+ xhr.onloadend = function() { ended = true; }
+ // Sync XHR will suspend timeouts, which involves any AudioContexts still
+ // registered with the window.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1255618#c0
+ xhr.send(null);
+
+ ok(ended, "No crash during fetch");
+ SimpleTest.finish();
+}
+
+var ac = new AudioContext();
+
+ac.onstatechange = function () {
+ ac.onstatechange = null;
+ is(ac.state, "running", "statechange to running");
+ ac = null;
+ SimpleTest.executeSoon(collect_and_fetch);
+}
+
+</script>
+</body>
diff --git a/dom/media/webaudio/test/test_bug1267579.html b/dom/media/webaudio/test/test_bug1267579.html
new file mode 100644
index 0000000000..7003b345f5
--- /dev/null
+++ b/dom/media/webaudio/test/test_bug1267579.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that PeriodicWave handles fundamental fequency of zero</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+// This is the smallest value that the test framework will accept
+const testLength = 256;
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ runTest();
+});
+
+var gTest = {
+ numberOfChannels: 1,
+ createGraph(context) {
+ var osc = context.createOscillator();
+ osc.setPeriodicWave(context.
+ createPeriodicWave(new Float32Array([0.0, 1.0]),
+ new Float32Array(2)));
+ osc.frequency.value = 0.0;
+ osc.start();
+ return osc;
+ },
+ createExpectedBuffers(context) {
+ var buffer = context.createBuffer(1, testLength, context.sampleRate);
+
+ for (var i = 0; i < buffer.length; ++i) {
+ buffer.getChannelData(0)[i] = 1.0;
+ }
+ return buffer;
+ },
+};
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_bug1355798.html b/dom/media/webaudio/test/test_bug1355798.html
new file mode 100644
index 0000000000..9b46322bbc
--- /dev/null
+++ b/dom/media/webaudio/test/test_bug1355798.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test PannerNode produces output even when the even when the distance is
+ from the listener is zero, and the cone gain is present, regression test for
+ bug 1355798.</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+var off = new OfflineAudioContext(1, 128, 44100);
+var panner = off.createPanner();
+var osc = off.createOscillator();
+panner.setPosition(1, 1, 1);
+off.listener.setPosition(1, 1, 1);
+osc.connect(panner).connect(off.destination);
+panner.coneOuterAngle = 359;
+osc.start();
+off.startRendering().then(function(b) {
+ is(b.getChannelData(0).filter(x => isNaN(x)).length, 0);
+ SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_bug1447273.html b/dom/media/webaudio/test/test_bug1447273.html
new file mode 100644
index 0000000000..f4b473d8ab
--- /dev/null
+++ b/dom/media/webaudio/test/test_bug1447273.html
@@ -0,0 +1,175 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test bug 1447273 - GainNode with a stereo input and changing volume</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <script type="text/javascript" src="head.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/**
+ * Sets up a stereo BufferSource and plumbs it through different gain node
+ * configurations. A control gain path with no changes to gain is used along
+ * with 2 other paths which should increase their gain. The result should be
+ * that audio travelling along the increased gain paths is louder than the
+ * control path.
+ */
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout(
+ "This test uses a live audio context and uses a setTimeout to schedule a " +
+ "change to the graph.");
+addLoadEvent(function() {
+ let context = new AudioContext();
+
+ let numChannels = 2;
+ let sampleRate = context.sampleRate;
+ // 60 seconds to mitigate timing issues on slow test machines
+ let recordingLength = 60;
+ let bufferLength = sampleRate * recordingLength;
+ let gainExplicitlyIncreased = false;
+ let sourceFinished = false;
+
+ // Create source buffer
+ let sourceBuffer = context.createBuffer(numChannels, bufferLength, sampleRate);
+ for (let i = 0; i < bufferLength; ++i) {
+ sourceBuffer.getChannelData(0)[i] = 1;
+ sourceBuffer.getChannelData(1)[i] = 1;
+ }
+ let source = context.createBufferSource();
+ source.buffer = sourceBuffer;
+
+ let gainNoChange = context.createGain();
+ let gainExplicitAssignment = context.createGain();
+ let gainSetValueAtTime = context.createGain();
+
+ // All gain nodes start of with the same gain
+ gainNoChange.gain.value = 0.25;
+ gainExplicitAssignment.gain.value = 0.25;
+ gainSetValueAtTime.gain.value = 0.25;
+
+ // Connect source to gain nodes:
+ // source--> gainNoChange
+ // |-> gainExplicitAssignment
+ // \-> gainSetValueAtTime
+ source.connect(gainNoChange);
+ source.connect(gainExplicitAssignment);
+ source.connect(gainSetValueAtTime);
+
+ // Create intermediate media streams (required to repro bug 1447273)
+ let destNoChange = context.createMediaStreamDestination();
+ let destExplicitAssignement = context.createMediaStreamDestination();
+ let destSetValueAtTime = context.createMediaStreamDestination();
+
+ let sourceNoChange = context.createMediaStreamSource(destNoChange.stream);
+ let sourceExplicitAssignement = context.createMediaStreamSource(destExplicitAssignement.stream);
+ let sourceSetValueAtTime = context.createMediaStreamSource(destSetValueAtTime.stream);
+
+ // Connect gain nodes to our intermediate streams:
+ // source--> gainNoChange -> destNoChange -> sourceNoChange
+ // |-> gainExplicitAssignment -> destExplicitAssignement -> sourceExplicitAssignement
+ // \-> gainSetValueAtTime -> destSetValueAtTime -> sourceSetValueAtTime
+ gainNoChange.connect(destNoChange);
+ gainExplicitAssignment.connect(destExplicitAssignement);
+ gainSetValueAtTime.connect(destSetValueAtTime);
+
+ // Create analysers for each path
+ let analyserNoChange = context.createAnalyser();
+ let analyserExplicitAssignment = context.createAnalyser();
+ let analyserSetValueAtTime = context.createAnalyser();
+
+ // Connect our intermediate media streams to analysers:
+ // source--> gainNoChange -> destNoChange -> sourceNoChange -> analyserNoChange
+ // |-> gainExplicitAssignment -> destExplicitAssignement -> sourceExplicitAssignement -> analyserExplicitAssignment
+ // \-> gainSetValueAtTime -> destSetValueAtTime -> sourceSetValueAtTime -> analyserSetValueAtTime
+ sourceNoChange.connect(analyserNoChange);
+ sourceExplicitAssignement.connect(analyserExplicitAssignment);
+ sourceSetValueAtTime.connect(analyserSetValueAtTime);
+
+ // Two seconds in, increase gain for setValueAt path
+ gainSetValueAtTime.gain.setValueAtTime(0.5, 2);
+
+ // Maximum values seen at each analyser node, will be updated by
+ // checkAnalysersForMaxValues() during test.
+ let maxNoGainChange = 0;
+ let maxExplicitAssignment = 0;
+ let maxSetValueAtTime = 0;
+
+ // Poll analysers and check for max values
+ function checkAnalysersForMaxValues() {
+ let findMaxValue =
+ (array) => array.reduce((a, b) => Math.max(Math.abs(a), Math.abs(b)));
+
+ let dataArray = new Float32Array(analyserNoChange.fftSize);
+ analyserNoChange.getFloatTimeDomainData(dataArray);
+ maxNoGainChange = Math.max(maxNoGainChange, findMaxValue(dataArray));
+
+ analyserExplicitAssignment.getFloatTimeDomainData(dataArray);
+ maxExplicitAssignment = Math.max(maxExplicitAssignment, findMaxValue(dataArray));
+
+ analyserSetValueAtTime.getFloatTimeDomainData(dataArray);
+ maxSetValueAtTime = Math.max(maxSetValueAtTime, findMaxValue(dataArray));
+
+ // End test if we've met our conditions
+ // Add a small amount to initial gain to make sure we're not getting
+ // passes due to rounding errors.
+ let epsilon = 0.01;
+ if (maxExplicitAssignment > (maxNoGainChange + epsilon) &&
+ maxSetValueAtTime > (maxNoGainChange + epsilon)) {
+ source.stop();
+ }
+ }
+
+ source.onended = () => {
+ sourceFinished = true;
+ info(`maxNoGainChange: ${maxNoGainChange}`);
+ info(`maxExplicitAssignment: ${maxExplicitAssignment}`);
+ info(`maxSetValueAtTime: ${maxSetValueAtTime}`);
+ ok(gainExplicitlyIncreased,
+ "Gain should be explicitly assinged during test!");
+ // Add a small amount to initial gain to make sure we're not getting
+ // passes due to rounding errors.
+ let epsilon = 0.01;
+ ok(maxExplicitAssignment > (maxNoGainChange + epsilon),
+ "Volume should increase due to explicit assignment to gain.value");
+ ok(maxSetValueAtTime > (maxNoGainChange + epsilon),
+ "Volume should increase due to setValueAtTime on gain.value");
+ SimpleTest.finish();
+ };
+
+ // Start the graph
+ source.start(0);
+
+ // We'll use this callback to check our analysers for gain
+ function animationFrameCb() {
+ if (sourceFinished) {
+ return;
+ }
+ requestAnimationFrame(animationFrameCb);
+ checkAnalysersForMaxValues();
+ }
+
+ // Using timers is gross, but as of writing there doesn't appear to be a
+ // nicer way to perform gain.value = 0.5 on our node. When/if we support
+ // suspend(time) on offline contexts we could potentially use that instead.
+
+ // Roughly 2 seconds through our source buffer (setTimeout flakiness) increase
+ // our gain on gainExplicitAssignment path.
+ window.setTimeout(() => {
+ gainExplicitAssignment.gain.value = 0.5;
+ // Make debugging flaky timeouts in test easier
+ info("Gain explicitly set!")
+ gainExplicitlyIncreased = true;
+ // Start checking analysers, we do this only after changing volume to avoid
+ // possible starvation of this timeout from requestAnimationFrame calls.
+ animationFrameCb();
+ }, 2000);
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_bug808374.html b/dom/media/webaudio/test/test_bug808374.html
new file mode 100644
index 0000000000..b255c0b9c3
--- /dev/null
+++ b/dom/media/webaudio/test/test_bug808374.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Crashtest for bug 808374</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+try {
+ var ctx = new AudioContext();
+ ctx.createBuffer(0, 1, ctx.sampleRate);
+} catch (e) {
+ ok(true, "The test should not crash during CC");
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_bug827541.html b/dom/media/webaudio/test/test_bug827541.html
new file mode 100644
index 0000000000..f205c7edf9
--- /dev/null
+++ b/dom/media/webaudio/test/test_bug827541.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Tell the cycle collector about the audio contexts owned by nsGlobalWindow</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+ var iframe = document.createElementNS("http://www.w3.org/1999/xhtml", "iframe");
+ document.body.appendChild(iframe);
+ var frameWin = iframe.contentWindow;
+ new frameWin.AudioContext();
+ document.body.removeChild(iframe);
+ expectException(() => new frameWin.AudioContext(),
+ DOMException.INVALID_STATE_ERR);
+
+ // This test should not leak.
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_bug839753.html b/dom/media/webaudio/test/test_bug839753.html
new file mode 100644
index 0000000000..f3e6598116
--- /dev/null
+++ b/dom/media/webaudio/test/test_bug839753.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Crashtest for bug 839753</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+(new AudioContext()).destination.expando = null;
+ok(true, "The test should not trigger wrapper cache assertions");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_bug845960.html b/dom/media/webaudio/test/test_bug845960.html
new file mode 100644
index 0000000000..17bf9a5700
--- /dev/null
+++ b/dom/media/webaudio/test/test_bug845960.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Crashtest for bug 845960</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+(new AudioContext()).decodeAudioData(new ArrayBuffer(0), function() {});
+ok(true, "Should not crash when the optional failure callback is not specified");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_bug856771.html b/dom/media/webaudio/test/test_bug856771.html
new file mode 100644
index 0000000000..24a527ccd5
--- /dev/null
+++ b/dom/media/webaudio/test/test_bug856771.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for bug 856771</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var context = new AudioContext();
+
+ var source = context.createBufferSource();
+ source.connect(context.destination);
+ ok(true, "Nothing should leak");
+
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_bug866570.html b/dom/media/webaudio/test/test_bug866570.html
new file mode 100644
index 0000000000..90bd8f6985
--- /dev/null
+++ b/dom/media/webaudio/test/test_bug866570.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Crashtest for bug 859600</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+(new AudioContext()).foo = null;
+ok(true, "The test should not fatally assert");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_bug866737.html b/dom/media/webaudio/test/test_bug866737.html
new file mode 100644
index 0000000000..e8db6b76e8
--- /dev/null
+++ b/dom/media/webaudio/test/test_bug866737.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for bug 866737</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var context = new AudioContext();
+
+(function() {
+ var d = context.createDelay();
+ var panner = context.createPanner();
+ d.connect(panner);
+ var gain = context.createGain();
+ panner.connect(gain);
+ gain.connect(context.destination);
+ gain.disconnect(0);
+})();
+
+SpecialPowers.forceGC();
+SpecialPowers.forceCC();
+
+var gain = context.createGain();
+gain.connect(context.destination);
+gain.disconnect(0);
+
+ok(true, "No crashes should happen!");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_bug867089.html b/dom/media/webaudio/test/test_bug867089.html
new file mode 100644
index 0000000000..e5a5179530
--- /dev/null
+++ b/dom/media/webaudio/test/test_bug867089.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Crashtest for bug 867089</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var ctx = new AudioContext();
+
+ // Test invalid playbackRate values for AudioBufferSourceNode.
+ var source = ctx.createBufferSource();
+ var buffer = ctx.createBuffer(2, 2048, 8000);
+ source.buffer = buffer;
+ source.playbackRate.value = 0.0;
+ source.connect(ctx.destination);
+ source.start(0);
+
+ var source2 = ctx.createBufferSource();
+ source2.buffer = buffer;
+ source2.playbackRate.value = -1.0;
+ source2.connect(ctx.destination);
+ source2.start(0);
+
+ var source3 = ctx.createBufferSource();
+ source3.buffer = buffer;
+ source3.playbackRate.value = 3000000.0;
+ source3.connect(ctx.destination);
+ source3.start(0);
+ ok(true, "We did not crash.");
+ SimpleTest.finish();
+});
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_bug867174.html b/dom/media/webaudio/test/test_bug867174.html
new file mode 100644
index 0000000000..e949bcec41
--- /dev/null
+++ b/dom/media/webaudio/test/test_bug867174.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Crashtest for bug 867174</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var ctx = new AudioContext();
+
+ var source = ctx.createBufferSource();
+ var buffer = ctx.createBuffer(2, 2048, 8000);
+ source.playbackRate.setTargetAtTime(0, 2, 3);
+ var sp = ctx.createScriptProcessor();
+ source.connect(sp);
+ sp.connect(ctx.destination);
+ source.start(0);
+
+ sp.onaudioprocess = function(e) {
+ // Now set the buffer
+ source.buffer = buffer;
+
+ ok(true, "We did not crash.");
+ sp.onaudioprocess = null;
+ SimpleTest.finish();
+ };
+});
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_bug873335.html b/dom/media/webaudio/test/test_bug873335.html
new file mode 100644
index 0000000000..19fd6d4dae
--- /dev/null
+++ b/dom/media/webaudio/test/test_bug873335.html
@@ -0,0 +1,22 @@
+<html>
+<head>
+<meta charset="UTF-8">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+
+function boom()
+{
+ (new AudioContext()).createScriptProcessor().hamster = {};
+ SpecialPowers.forceCC();
+ SpecialPowers.forceGC();
+ ok(true, "test finished");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/dom/media/webaudio/test/test_bug875221.html b/dom/media/webaudio/test/test_bug875221.html
new file mode 100644
index 0000000000..134316f9ae
--- /dev/null
+++ b/dom/media/webaudio/test/test_bug875221.html
@@ -0,0 +1,243 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Crashtest for bug 875221</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/* eslint-disable no-implied-eval */
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("This test is generated by a fuzzer, so we leave these setTimeouts untouched.");
+
+let o0, o1, o2, o3, o4, o5, o6, o7, o8, o9, o10;
+let o11, o12, o13, o14, o15, o16, o17, o18, o19;
+
+try { o0 = document.createElement('audio'); } catch(e) { }
+try { (document.body || document.documentElement).appendChild(o0); } catch(e) { }
+try { o1 = new OfflineAudioContext(1, 10, (new AudioContext()).sampleRate); } catch(e) { }
+try { o1.startRendering(); } catch(e) { }
+try { o1.listener.dopplerFactor = 1; } catch(e) { }
+try { o2 = o1.createScriptProcessor(); } catch(e) { }
+try { o3 = o1.createChannelMerger(4); } catch(e) { }
+try { o1.listener.dopplerFactor = 3; } catch(e) { }
+try { o1.listener.setPosition(0, 134217728, 64) } catch(e) { }
+try { o1.listener.dopplerFactor = 15; } catch(e) { }
+try { o1.startRendering(); } catch(e) { }
+try { o4 = new OfflineAudioContext(1, 10, (new AudioContext()).sampleRate); } catch(e) { }
+try { o4.listener.speedOfSound = 2048; } catch(e) { }
+try { o4.listener.setPosition(32768, 1, 1) } catch(e) { }
+try { o5 = o1.createChannelSplitter(4); } catch(e) { }
+try { o4.listener.setVelocity(4, 1, 0) } catch(e) { }
+try { o4.startRendering(); } catch(e) { }
+try { o4.startRendering(); } catch(e) { }
+try { o4.listener.setPosition(64, 1, 0) } catch(e) { }
+try { o1.listener.setOrientation(4194304, 15, 8388608, 15, 1, 1) } catch(e) { }
+try { o1.listener.dopplerFactor = 256; } catch(e) { }
+try { o6 = o4.createDelay(16); } catch(e) { }
+try { o4.startRendering(); } catch(e) { }
+try { o4.listener.setOrientation(0, 1, 0, 0, 31, 1073741824) } catch(e) { }
+try { o4.listener.speedOfSound = 1; } catch(e) { }
+try { o1.listener.speedOfSound = 0; } catch(e) { }
+try { o1.startRendering(); } catch(e) { }
+try { o6.connect(o3, 1, 0) } catch(e) { }
+try { o1.listener.setPosition(4294967296, 32, 1) } catch(e) { }
+try { o1.listener.speedOfSound = 0; } catch(e) { }
+try { o1.listener.speedOfSound = 0; } catch(e) { }
+try { o1.listener.setVelocity(1, 256, 0) } catch(e) { }
+try { o4.startRendering(); } catch(e) { }
+try { o3.disconnect() } catch(e) { }
+setTimeout("try { o4.startRendering(); } catch(e) { }",50)
+try { o4.listener.setOrientation(0, 0, 2048, 128, 16384, 127) } catch(e) { }
+try { o4.listener.setVelocity(0, 4, 1) } catch(e) { }
+try { o7 = o4.createScriptProcessor(1024, 4, 1); } catch(e) { }
+try { o8 = o4.createDynamicsCompressor(); } catch(e) { }
+try { o1.startRendering(); } catch(e) { }
+try { o2.connect(o7); } catch(e) { }
+try { o7.connect(o4); } catch(e) { }
+try { o2.connect(o7); } catch(e) { }
+try { o7.connect(o4); } catch(e) { }
+SpecialPowers.forceCC();
+SpecialPowers.forceGC();
+try { o4.listener.setOrientation(8192, 1, 1, 512, 0, 15) } catch(e) { }
+setTimeout("try { o7.onaudioprocess = function() {}; } catch(e) { }",50)
+try { o1.startRendering(); } catch(e) { }
+try { o1.listener.speedOfSound = 1073741824; } catch(e) { }
+try { o2.connect(o7); } catch(e) { }
+try { o7.connect(o4); } catch(e) { }
+try { o9 = o4.createScriptProcessor(1024, 1, 4); } catch(e) { }
+try { o10 = o4.createAnalyser(); } catch(e) { }
+try { o4.listener.speedOfSound = 0; } catch(e) { }
+try { o2.connect(o7); } catch(e) { }
+try { o9.connect(o1); } catch(e) { }
+try { o4.listener.setVelocity(524288, 1, 65536) } catch(e) { }
+setTimeout("try { o2.connect(o9); } catch(e) { } setTimeout(done, 0);",1000)
+try { o7.connect(o4); } catch(e) { }
+try { o1.listener.setVelocity(1, 127, 31) } catch(e) { }
+try { o2.connect(o7); } catch(e) { }
+try { o7.connect(o1); } catch(e) { }
+setTimeout("try { o5.disconnect() } catch(e) { }",100)
+try { o2.connect(o9); } catch(e) { }
+try { o7.connect(o4); } catch(e) { }
+try { o4.startRendering(); } catch(e) { }
+setTimeout("try { o1.listener.dopplerFactor = 1; } catch(e) { }",100)
+try { o5.disconnect() } catch(e) { }
+try { o1.startRendering(); } catch(e) { }
+try { o1.startRendering(); } catch(e) { }
+try { o10.disconnect() } catch(e) { }
+try { o1.startRendering(); } catch(e) { }
+try { o11 = o1.createGain(); } catch(e) { }
+try { o2.connect(o7); } catch(e) { }
+try { o9.connect(o4); } catch(e) { }
+try { o4.listener.setOrientation(31, 0, 15, 0, 33554432, 1) } catch(e) { }
+try { o4.listener.dopplerFactor = 1; } catch(e) { }
+try { o2.connect(o7); } catch(e) { }
+try { o7.connect(o4); } catch(e) { }
+try { o2.connect(o7); } catch(e) { }
+setTimeout("try { o9.connect(o4); } catch(e) { }",50)
+try { o2.connect(o9); } catch(e) { }
+setTimeout("try { o9.connect(o1); } catch(e) { }",200)
+try { o2.connect(o7); } catch(e) { }
+try { o7.connect(o1); } catch(e) { }
+try { o12 = o4.createDynamicsCompressor(); } catch(e) { }
+try { o2.connect(o7); } catch(e) { }
+try { o9.connect(o1); } catch(e) { }
+try { o9.onaudioprocess = function() {}; } catch(e) { }
+try { o1.listener.speedOfSound = 262144; } catch(e) { }
+try { o2.connect(o7); } catch(e) { }
+try { o7.connect(o4); } catch(e) { }
+try { o2.connect(o9); } catch(e) { }
+setTimeout("try { o7.connect(o4); } catch(e) { }",50)
+try { o2.connect(o9); } catch(e) { }
+try { o7.connect(o4); } catch(e) { }
+try { o13 = o4.createGain(); } catch(e) { }
+try { o4.listener.dopplerFactor = 31; } catch(e) { }
+try { o11.gain.value = 268435456; } catch(e) { }
+try { o1.listener.setOrientation(63, 3, 1, 63, 1, 2147483648) } catch(e) { }
+try { o2.connect(o9); } catch(e) { }
+try { o7.connect(o4); } catch(e) { }
+try { o4.listener.setVelocity(1, 0, 1) } catch(e) { }
+try { o11.gain.value = 65536; } catch(e) { }
+try { o2.connect(o9); } catch(e) { }
+setTimeout("try { o7.connect(o4); } catch(e) { }",200)
+try { o14 = o4.createDynamicsCompressor(); } catch(e) { }
+setTimeout("try { o2.connect(o9); } catch(e) { }",50)
+try { o7.connect(o1); } catch(e) { }
+try { o15 = o1.createWaveShaper(); } catch(e) { }
+try { o2.connect(o7); } catch(e) { }
+try { o7.connect(o1); } catch(e) { }
+try { o16 = o1.createWaveShaper(); } catch(e) { }
+try { o11.gain.value = 1; } catch(e) { }
+try { o1.listener.speedOfSound = 16; } catch(e) { }
+try { o4.listener.setVelocity(0, 127, 15) } catch(e) { }
+try { o1.listener.setVelocity(0, 2048, 16777216) } catch(e) { }
+try { o13.gain.value = 0; } catch(e) { }
+try { o2.connect(o9); } catch(e) { }
+try { o7.connect(o4); } catch(e) { }
+try { o2.connect(o9); } catch(e) { }
+try { o9.connect(o1); } catch(e) { }
+try { o17 = document.createElement('audio'); } catch(e) { }
+try { (document.body || document.documentElement).appendChild(o0); } catch(e) { }
+try { o4.listener.setVelocity(3, 1, 256) } catch(e) { }
+try { o11.gain.cancelScheduledValues(1) } catch(e) { }
+try { o1.listener.dopplerFactor = 524288; } catch(e) { }
+try { o9.onaudioprocess = function() {}; } catch(e) { }
+setTimeout("try { o7.connect(o13, 0, 0) } catch(e) { }",50)
+try { o1.listener.speedOfSound = 0; } catch(e) { }
+try { o10.disconnect() } catch(e) { }
+try { o2.connect(o9); } catch(e) { }
+try { o9.connect(o4); } catch(e) { }
+try { o2.connect(o7); } catch(e) { }
+try { o9.connect(o4); } catch(e) { }
+try { o1.listener.speedOfSound = 1; } catch(e) { }
+try { o15.disconnect() } catch(e) { }
+try { o11.gain.exponentialRampToValueAtTime(0, 15) } catch(e) { }
+try { o15.curve = new Float32Array(15); } catch(e) { }
+try { o4.listener.setVelocity(1, 1, 1) } catch(e) { }
+try { o14.connect(o6, 0, 0) } catch(e) { }
+try { o2.connect(o9); } catch(e) { }
+try { o9.connect(o1); } catch(e) { }
+try { o2.connect(o9); } catch(e) { }
+try { o7.connect(o4); } catch(e) { }
+try { o2.connect(o7); } catch(e) { }
+setTimeout("try { o7.connect(o1); } catch(e) { }",100)
+try { o4.listener.setVelocity(1, 7, 1) } catch(e) { }
+try { o18 = document.createElement('audio'); } catch(e) { }
+try { (document.body || document.documentElement).appendChild(o18); } catch(e) { }
+try { o19 = o4.createGain(); } catch(e) { }
+try { o2.connect(o7); } catch(e) { }
+try { o7.connect(o1); } catch(e) { }
+try { o4.listener.dopplerFactor = 0; } catch(e) { }
+try { o1.listener.setPosition(256, 16, 1) } catch(e) { }
+setTimeout("try { o2.connect(o9); } catch(e) { }",50)
+try { o7.connect(o1); } catch(e) { }
+try { o4.listener.speedOfSound = 31; } catch(e) { }
+try { o2.connect(o7); } catch(e) { }
+setTimeout("try { o9.connect(o4); } catch(e) { }",1000)
+try { o11.gain.value = 127; } catch(e) { }
+try { o7.connect(o7, 0, 0) } catch(e) { }
+try { o4.listener.speedOfSound = 63; } catch(e) { }
+try { o11.gain.value = 33554432; } catch(e) { }
+try { o2.connect(o7); } catch(e) { }
+try { o9.connect(o4); } catch(e) { }
+try { o4.listener.speedOfSound = 16; } catch(e) { }
+try { o4.listener.setVelocity(1048576, 0, 127) } catch(e) { }
+try { o1.listener.dopplerFactor = 0; } catch(e) { }
+try { o6.connect(o2, 0, 1) } catch(e) { }
+try { o5.disconnect() } catch(e) { }
+try { o3.disconnect() } catch(e) { }
+try { o2.connect(o9); } catch(e) { }
+try { o7.connect(o1); } catch(e) { }
+try { o16.disconnect() } catch(e) { }
+try { o2.connect(o9); } catch(e) { }
+try { o7.connect(o1); } catch(e) { }
+try { o9.disconnect() } catch(e) { }
+try { o4.listener.speedOfSound = 1; } catch(e) { }
+try { o2.connect(o7); } catch(e) { }
+try { o7.connect(o4); } catch(e) { }
+try { o11.gain.setValueCurveAtTime(new Float32Array(3), 2048, 3) } catch(e) { }
+try { o13.gain.value = 8; } catch(e) { }
+try { o2.connect(o7); } catch(e) { }
+try { o9.connect(o4); } catch(e) { }
+try { o4.listener.setOrientation(1, 2048, 1, 1, 0, 31) } catch(e) { }
+try { o2.connect(o9); } catch(e) { }
+try { o7.connect(o1); } catch(e) { }
+try { o1.listener.speedOfSound = 256; } catch(e) { }
+try { o2.connect(o7); } catch(e) { }
+try { o9.connect(o4); } catch(e) { }
+try { o4.listener.setVelocity(1, 67108864, 128) } catch(e) { }
+setTimeout("try { o1.listener.setVelocity(0, 1, 1) } catch(e) { }",100)
+try { o2.connect(o9); } catch(e) { }
+try { o9.connect(o1); } catch(e) { }
+setTimeout("try { o20 = o1.createBiquadFilter(); } catch(e) { }",200)
+try { o13.gain.value = 4096; } catch(e) { }
+try { o1.listener.dopplerFactor = 0; } catch(e) { }
+try { o2.connect(o7); } catch(e) { }
+try { o9.connect(o1); } catch(e) { }
+try { o2.connect(o9); } catch(e) { }
+try { o7.connect(o4); } catch(e) { }
+setTimeout("try { o2.connect(o9); } catch(e) { }",200)
+try { o7.connect(o1); } catch(e) { }
+try { o3.connect(o15, 1, 1) } catch(e) { }
+try { o2.connect(o12, 0, 0) } catch(e) { }
+try { o19.gain.exponentialRampToValueAtTime(1, 0) } catch(e) { }
+try { o2.connect(o7); } catch(e) { }
+try { o7.connect(o4); } catch(e) { }
+try { o2.connect(o7); } catch(e) { }
+try { o9.connect(o1); } catch(e) { }
+try { o2.connect(o7); } catch(e) { }
+try { o7.connect(o4); } catch(e) { }
+
+function done() {
+ ok(true, "We did not crash.");
+ SimpleTest.finish();
+}
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_bug875402.html b/dom/media/webaudio/test/test_bug875402.html
new file mode 100644
index 0000000000..bb14c82037
--- /dev/null
+++ b/dom/media/webaudio/test/test_bug875402.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Crashtest for bug 875402</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/* eslint-disable no-implied-eval */
+
+SimpleTest.waitForExplicitFinish();
+
+SimpleTest.requestFlakyTimeout("This test is generated by a fuzzer, so we leave these setTimeouts untouched.");
+
+let o1, o2, o4, o5, o7, o9, o13;
+
+try { o1 = new OfflineAudioContext(1, 10, (new AudioContext()).sampleRate); } catch(e) { }
+try { o2 = o1.createScriptProcessor(); } catch(e) { }
+try { o4 = new OfflineAudioContext(1, 10, (new AudioContext()).sampleRate); } catch(e) { }
+try { o5 = o1.createChannelSplitter(4); } catch(e) { }
+try { o7 = o4.createScriptProcessor(1024, 4, 1); } catch(e) { }
+SpecialPowers.forceCC();
+SpecialPowers.forceGC();
+try { o1.startRendering(); } catch(e) { }
+try { o2.connect(o7); } catch(e) { }
+try { o7.connect(o4); } catch(e) { }
+try { o9 = o4.createScriptProcessor(1024, 1, 4); } catch(e) { }
+try { o2.connect(o7); } catch(e) { }
+try { o9.connect(o1); } catch(e) { }
+setTimeout("try { o2.connect(o9); } catch(e) { } done();",1000)
+try { o7.connect(o4); } catch(e) { }
+setTimeout("try { o5.disconnect() } catch(e) { }",100)
+try { o2.connect(o9); } catch(e) { }
+try { o4.startRendering(); } catch(e) { }
+try { o2.connect(o9); } catch(e) { }
+setTimeout("try { o7.connect(o4); } catch(e) { }",50)
+try { o13 = o4.createGain(); } catch(e) { }
+setTimeout("try { o7.connect(o13, 0, 0) } catch(e) { }",50)
+
+function done() {
+ ok(true, "We did not crash.");
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_bug894150.html b/dom/media/webaudio/test/test_bug894150.html
new file mode 100644
index 0000000000..4577232d71
--- /dev/null
+++ b/dom/media/webaudio/test/test_bug894150.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test whether we can create an AudioContext interface</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script>
+
+var ac = new AudioContext();
+ac.createPanner();
+var listener = ac.listener;
+SpecialPowers.forceGC();
+SpecialPowers.forceCC();
+listener.setOrientation(0, 0, -1, 0, 0, 0);
+
+ok(true, "No crashes should happen!");
+
+</script>
+</body>
diff --git a/dom/media/webaudio/test/test_bug956489.html b/dom/media/webaudio/test/test_bug956489.html
new file mode 100644
index 0000000000..f0ae559b05
--- /dev/null
+++ b/dom/media/webaudio/test/test_bug956489.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test when and currentTime are in the same coordinate system</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("This test needs to wait a while for the AudioContext's timer to start.");
+addLoadEvent(function() {
+ var freq = 330;
+
+ var context = new AudioContext();
+
+ var buffer = context.createBuffer(1, context.sampleRate / freq, context.sampleRate);
+ for (var i = 0; i < buffer.length; ++i) {
+ buffer.getChannelData(0)[i] = Math.sin(2 * Math.PI * i / buffer.length);
+ }
+
+ var source = context.createBufferSource();
+ source.loop = true;
+ source.buffer = buffer;
+
+ setTimeout(function () {
+ var finished = false;
+
+ source.start(context.currentTime);
+ var processor = context.createScriptProcessor(256, 1, 1);
+ processor.onaudioprocess = function (e) {
+ if (finished) return;
+ var c = e.inputBuffer.getChannelData(0);
+ var result = true;
+
+ for (var i = 0; i < buffer.length; ++i) {
+ if (Math.abs(c[i] - buffer.getChannelData(0)[i]) > 1e-9) {
+ result = false;
+ break;
+ }
+ }
+ finished = true;
+ ok(result, "when and currentTime are in same time coordinate system");
+ SimpleTest.finish();
+ }
+ processor.connect(context.destination);
+ source.connect(processor);
+ }, 500);
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_bug964376.html b/dom/media/webaudio/test/test_bug964376.html
new file mode 100644
index 0000000000..bc9d167dcd
--- /dev/null
+++ b/dom/media/webaudio/test/test_bug964376.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test repeating audio is not distorted</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function gcd(a, b) {
+ if (b === 0) {
+ return a;
+ }
+ return gcd(b, a % b);
+}
+
+var SAMPLE_PLACEMENT = 128;
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 1,
+
+ createGraph(context) {
+ var freq = Math.round(context.sampleRate / SAMPLE_PLACEMENT);
+ var dur = context.sampleRate / gcd(freq, context.sampleRate);
+ var buffer = context.createBuffer(1, dur, context.sampleRate);
+
+ for (var i = 0; i < context.sampleRate; ++i) {
+ buffer.getChannelData(0)[i] = Math.sin(freq * 2 * Math.PI * i / context.sampleRate);
+ }
+
+ var source = context.createBufferSource();
+ source.buffer = buffer;
+ source.loop = true;
+ source.playbackRate.setValueAtTime(0.5, SAMPLE_PLACEMENT / context.sampleRate);
+ source.start(0);
+
+ return source;
+ },
+
+ createExpectedBuffers(context) {
+ var freq = Math.round(context.sampleRate / SAMPLE_PLACEMENT);
+ var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate);
+ var c = expectedBuffer.getChannelData(0);
+ for (var i = 0; i < c.length; ++i) {
+ if (i < SAMPLE_PLACEMENT) {
+ c[i] = Math.sin(freq * 2 * Math.PI * i / context.sampleRate);
+ } else {
+ c[i] = Math.sin(freq / 2 * 2 * Math.PI * (i + SAMPLE_PLACEMENT) / context.sampleRate);
+ }
+ }
+
+ return expectedBuffer;
+ },
+};
+
+runTest();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_bug966247.html b/dom/media/webaudio/test/test_bug966247.html
new file mode 100644
index 0000000000..69831c33b6
--- /dev/null
+++ b/dom/media/webaudio/test/test_bug966247.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test whether an audio file played with a volume set to 0 plays silence</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<audio preload=none src="ting-48k-1ch.ogg" controls> </audio>
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ var count = 20;
+
+ function isSilent(b) {
+ for (var i = 0; i < b.length; i++) {
+ if (b[i] != 0.0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ var a = document.getElementsByTagName("audio")[0];
+ a.volume = 0.0;
+ var ac = new AudioContext();
+ var measn = ac.createMediaElementSource(a);
+ var sp = ac.createScriptProcessor();
+
+ sp.onaudioprocess = function(e) {
+ var inputBuffer = e.inputBuffer.getChannelData(0);
+ ok(isSilent(inputBuffer), "The volume is set to 0, so all the elements of the buffer are supposed to be equal to 0.0");
+ }
+ // Connect the MediaElementAudioSourceNode to the ScriptProcessorNode to check
+ // the audio volume.
+ measn.connect(sp);
+ a.play();
+
+ a.addEventListener("ended", function() {
+ sp.onaudioprocess = null;
+ SimpleTest.finish();
+ });
+
+</script>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_bug972678.html b/dom/media/webaudio/test/test_bug972678.html
new file mode 100644
index 0000000000..ac96b4ac3b
--- /dev/null
+++ b/dom/media/webaudio/test/test_bug972678.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test buffers do not interfere when scheduled in sequence</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var OFFSETS = [0.005, 0.01, 0.02, 0.03];
+var LENGTH = 128;
+
+var gTest = {
+ length: 128 * OFFSETS.length,
+ numberOfChannels: 1,
+
+ createGraph(context) {
+ var gain = context.createGain();
+
+ // create a repeating sample
+ var repeatingSample = context.createBuffer(1, 2, context.sampleRate);
+ var c = repeatingSample.getChannelData(0);
+ for (var i = 0; i < repeatingSample.length; ++i) {
+ c[i] = i % 2 == 0 ? 1 : -1;
+ }
+
+ OFFSETS.forEach(function (offset, offsetIdx) {
+ // Schedule a set of nodes to repeat the sample.
+ for (var i = 0; i < LENGTH; i += repeatingSample.length) {
+ var source = context.createBufferSource();
+ source.buffer = repeatingSample;
+ source.connect(gain);
+ source.start((offsetIdx * LENGTH + i + offset) / context.sampleRate);
+ }
+
+ let buffer = context.createBuffer(1, LENGTH, context.sampleRate);
+ c = buffer.getChannelData(0);
+ for (var i = 0; i < buffer.length; ++i) {
+ c[i] = i % 2 == 0 ? -1 : 1;
+ }
+
+ var source = context.createBufferSource();
+ source.buffer = buffer;
+ source.connect(gain);
+ source.start((offsetIdx * LENGTH + offset) / context.sampleRate);
+ });
+
+ return gain;
+ },
+
+ createExpectedBuffers(context) {
+ return context.createBuffer(1, gTest.length, context.sampleRate);
+ },
+};
+
+runTest();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_channelMergerNode.html b/dom/media/webaudio/test/test_channelMergerNode.html
new file mode 100644
index 0000000000..b62d34d6ba
--- /dev/null
+++ b/dom/media/webaudio/test/test_channelMergerNode.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test ChannelMergerNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 6,
+ createGraph(context) {
+ var buffers = [];
+ for (var j = 0; j < 6; ++j) {
+ var buffer = context.createBuffer(2, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ buffer.getChannelData(0)[i] = Math.sin(440 * 2 * (j + 1) * Math.PI * i / context.sampleRate);
+ // Second channel is silent
+ }
+ buffers.push(buffer);
+ }
+
+ var merger = new ChannelMergerNode(context);
+ is(merger.channelCount, 1, "merger node has 1 input channels");
+ is(merger.channelCountMode, "explicit", "Correct channelCountMode for the merger node");
+ is(merger.channelInterpretation, "speakers", "Correct channelCountInterpretation for the merger node");
+
+ for (var i = 0; i < 6; ++i) {
+ var source = context.createBufferSource();
+ source.buffer = buffers[i];
+ source.connect(merger, 0, i);
+ source.start(0);
+ }
+
+ return merger;
+ },
+ createExpectedBuffers(context) {
+ var expectedBuffer = context.createBuffer(6, 2048, context.sampleRate);
+ for (var i = 0; i < 6; ++i) {
+ for (var j = 0; j < 2048; ++j) {
+ expectedBuffer.getChannelData(i)[j] = 0.5 * Math.sin(440 * 2 * (i + 1) * Math.PI * j / context.sampleRate);
+ }
+ }
+ return expectedBuffer;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_channelMergerNodeWithVolume.html b/dom/media/webaudio/test/test_channelMergerNodeWithVolume.html
new file mode 100644
index 0000000000..55b9ec0c0b
--- /dev/null
+++ b/dom/media/webaudio/test/test_channelMergerNodeWithVolume.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test ChannelMergerNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 6,
+ createGraph(context) {
+ var buffers = [];
+ for (var j = 0; j < 6; ++j) {
+ var buffer = context.createBuffer(2, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ buffer.getChannelData(0)[i] = Math.sin(440 * 2 * (j + 1) * Math.PI * i / context.sampleRate);
+ // Second channel is silent
+ }
+ buffers.push(buffer);
+ }
+
+ var merger = context.createChannelMerger();
+ is(merger.channelCount, 1, "merger node has 1 input channels");
+ is(merger.channelCountMode, "explicit", "Correct channelCountMode for the merger node");
+ is(merger.channelInterpretation, "speakers", "Correct channelCountInterpretation for the merger node");
+
+ for (var i = 0; i < 6; ++i) {
+ var source = context.createBufferSource();
+ source.buffer = buffers[i];
+ var gain = context.createGain();
+ gain.gain.value = 0.5;
+ source.connect(gain);
+ gain.connect(merger, 0, i);
+ source.start(0);
+ }
+
+ return merger;
+ },
+ createExpectedBuffers(context) {
+ var expectedBuffer = context.createBuffer(6, 2048, context.sampleRate);
+ for (var i = 0; i < 6; ++i) {
+ for (var j = 0; j < 2048; ++j) {
+ expectedBuffer.getChannelData(i)[j] = 0.5 * 0.5 * Math.sin(440 * 2 * (i + 1) * Math.PI * j / context.sampleRate);
+ }
+ }
+ return expectedBuffer;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_channelSplitterNode.html b/dom/media/webaudio/test/test_channelSplitterNode.html
new file mode 100644
index 0000000000..d74845f821
--- /dev/null
+++ b/dom/media/webaudio/test/test_channelSplitterNode.html
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test ChannelSplitterNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+// We do not use our generic graph test framework here because
+// the splitter node is special in that it creates multiple
+// output ports.
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var context = new AudioContext();
+ var buffer = context.createBuffer(4, 2048, context.sampleRate);
+ for (var j = 0; j < 4; ++j) {
+ for (var i = 0; i < 2048; ++i) {
+ buffer.getChannelData(j)[i] = Math.sin(440 * 2 * (j + 1) * Math.PI * i / context.sampleRate);
+ }
+ }
+ var emptyBuffer = context.createBuffer(1, 2048, context.sampleRate);
+
+ var destination = context.destination;
+
+ var source = context.createBufferSource();
+
+ var splitter = new ChannelSplitterNode(context);
+ is(splitter.channelCount, 6, "splitter node has 2 input channels by default");
+ is(splitter.channelCountMode, "explicit", "Correct channelCountMode for the splitter node");
+ is(splitter.channelInterpretation, "discrete", "Correct channelCountInterpretation for the splitter node");
+
+ source.buffer = buffer;
+ source.connect(splitter);
+
+ var channelsSeen = 0;
+ function createHandler(i) {
+ return function(e) {
+ is(e.inputBuffer.numberOfChannels, 1, "Correct input channel count");
+ if (i < 4) {
+ compareChannels(e.inputBuffer.getChannelData(0), buffer.getChannelData(i));
+ } else {
+ compareChannels(e.inputBuffer.getChannelData(0), emptyBuffer.getChannelData(0));
+ }
+ e.target.onaudioprocess = null;
+ ++channelsSeen;
+
+ if (channelsSeen == 6) {
+ SimpleTest.finish();
+ }
+ };
+ }
+
+ for (var i = 0; i < 6; ++i) {
+ var sp = context.createScriptProcessor(2048, 1);
+ splitter.connect(sp, i);
+ sp.onaudioprocess = createHandler(i);
+ sp.connect(destination);
+ }
+
+ source.start(0);
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_channelSplitterNodeWithVolume.html b/dom/media/webaudio/test/test_channelSplitterNodeWithVolume.html
new file mode 100644
index 0000000000..c03f6deeaf
--- /dev/null
+++ b/dom/media/webaudio/test/test_channelSplitterNodeWithVolume.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test ChannelSplitterNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+// We do not use our generic graph test framework here because
+// the splitter node is special in that it creates multiple
+// output ports.
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var context = new AudioContext();
+ var buffer = context.createBuffer(4, 2048, context.sampleRate);
+ var expectedBuffer = context.createBuffer(4, 2048, context.sampleRate);
+ for (var j = 0; j < 4; ++j) {
+ for (var i = 0; i < 2048; ++i) {
+ buffer.getChannelData(j)[i] = Math.sin(440 * 2 * (j + 1) * Math.PI * i / context.sampleRate);
+ expectedBuffer.getChannelData(j)[i] = buffer.getChannelData(j)[i] / 2;
+ }
+ }
+ var emptyBuffer = context.createBuffer(1, 2048, context.sampleRate);
+
+ var destination = context.destination;
+
+ var source = context.createBufferSource();
+
+ var splitter = context.createChannelSplitter();
+ is(splitter.channelCount, 6, "splitter node has 2 input channels by default");
+ is(splitter.channelCountMode, "explicit", "Correct channelCountMode for the splitter node");
+ is(splitter.channelInterpretation, "discrete", "Correct channelCountInterpretation for the splitter node");
+
+ source.buffer = buffer;
+ var gain = context.createGain();
+ gain.gain.value = 0.5;
+ source.connect(gain);
+ gain.connect(splitter);
+
+ var channelsSeen = 0;
+ function createHandler(i) {
+ return function(e) {
+ is(e.inputBuffer.numberOfChannels, 1, "Correct input channel count");
+ if (i < 4) {
+ compareBuffers(e.inputBuffer.getChannelData(0), expectedBuffer.getChannelData(i));
+ } else {
+ compareBuffers(e.inputBuffer.getChannelData(0), emptyBuffer.getChannelData(0));
+ }
+ e.target.onaudioprocess = null;
+ ++channelsSeen;
+
+ if (channelsSeen == 6) {
+ SimpleTest.finish();
+ }
+ };
+ }
+
+ for (var i = 0; i < 6; ++i) {
+ var sp = context.createScriptProcessor(2048, 1);
+ splitter.connect(sp, i);
+ sp.onaudioprocess = createHandler(i);
+ sp.connect(destination);
+ }
+
+ source.start(0);
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_convolver-upmixing-1-channel-response.html b/dom/media/webaudio/test/test_convolver-upmixing-1-channel-response.html
new file mode 100644
index 0000000000..50bd594821
--- /dev/null
+++ b/dom/media/webaudio/test/test_convolver-upmixing-1-channel-response.html
@@ -0,0 +1,143 @@
+<!DOCTYPE html>
+<title>Test that up-mixing signals in ConvolverNode processing is linear</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+const EPSILON = 3.0 * Math.pow(2, -22);
+// sampleRate is a power of two so that delay times are exact in base-2
+// floating point arithmetic.
+const SAMPLE_RATE = 32768;
+// Length of stereo convolver input in frames (arbitrary):
+const STEREO_FRAMES = 256;
+// Length of mono signal in frames. This is more than two blocks to ensure
+// that at least one block will be mono, even if interpolation in the
+// DelayNode means that stereo is output one block earlier and later than
+// if frames are delayed without interpolation.
+const MONO_FRAMES = 384;
+// Length of response buffer:
+const RESPONSE_FRAMES = 256;
+
+function test_linear_upmixing(channelInterpretation, initial_mono_frames)
+{
+ let stereo_input_end = initial_mono_frames + STEREO_FRAMES;
+ // Total length:
+ let length = stereo_input_end + RESPONSE_FRAMES + MONO_FRAMES + STEREO_FRAMES;
+ // The first two channels contain signal where some up-mixing occurs
+ // internally to a ConvolverNode when a stereo signal is added and removed.
+ // The last two channels are expected to contain the same signal, but mono
+ // and stereo signals are convolved independently before up-mixing the mono
+ // output to mix with the stereo output.
+ let context = new OfflineAudioContext({numberOfChannels: 4,
+ length,
+ sampleRate: SAMPLE_RATE});
+
+ let response = new AudioBuffer({numberOfChannels: 1,
+ length: RESPONSE_FRAMES,
+ sampleRate: context.sampleRate});
+
+ // Two stereo channel splitters will collect test and reference outputs.
+ let destinationMerger = new ChannelMergerNode(context, {numberOfInputs: 4});
+ destinationMerger.connect(context.destination);
+ let testSplitter =
+ new ChannelSplitterNode(context, {numberOfOutputs: 2});
+ let referenceSplitter =
+ new ChannelSplitterNode(context, {numberOfOutputs: 2});
+ testSplitter.connect(destinationMerger, 0, 0);
+ testSplitter.connect(destinationMerger, 1, 1);
+ referenceSplitter.connect(destinationMerger, 0, 2);
+ referenceSplitter.connect(destinationMerger, 1, 3);
+
+ // A GainNode mixes reference stereo and mono signals because up-mixing
+ // cannot be performed at a channel splitter.
+ let referenceGain = new GainNode(context);
+ referenceGain.connect(referenceSplitter);
+ referenceGain.channelInterpretation = channelInterpretation;
+
+ // The impulse response for convolution contains two impulses so as to test
+ // effects in at least two processing blocks.
+ response.getChannelData(0)[0] = 0.5;
+ response.getChannelData(0)[response.length - 1] = 0.5;
+
+ let testConvolver = new ConvolverNode(context, {disableNormalization: true,
+ buffer: response});
+ testConvolver.channelInterpretation = channelInterpretation;
+ let referenceMonoConvolver = new ConvolverNode(context,
+ {disableNormalization: true,
+ buffer: response});
+ let referenceStereoConvolver = new ConvolverNode(context,
+ {disableNormalization: true,
+ buffer: response});
+ // No need to set referenceStereoConvolver.channelInterpretation because
+ // input is either silent or stereo.
+ testConvolver.connect(testSplitter);
+ // Mix reference convolver output.
+ referenceMonoConvolver.connect(referenceGain);
+ referenceStereoConvolver.connect(referenceGain);
+
+ // The DelayNode initially has a single channel of silence, which is used to
+ // switch the stereo signal in and out. The output of the delay node is
+ // first mono silence (if there is a non-zero initial_mono_frames), then
+ // stereo, then mono silence, and finally stereo again. maxDelayTime is
+ // used to generate the middle mono silence period from the initial silence
+ // in the DelayNode and then generate the final period of stereo from its
+ // initial input.
+ let maxDelayTime = (length - STEREO_FRAMES) / context.sampleRate;
+ let delay =
+ new DelayNode(context,
+ {maxDelayTime,
+ delayTime: initial_mono_frames / context.sampleRate});
+ // Schedule an increase in the delay to return to mono silence.
+ delay.delayTime.setValueAtTime(maxDelayTime,
+ stereo_input_end / context.sampleRate);
+ delay.connect(testConvolver);
+ delay.connect(referenceStereoConvolver);
+
+ let stereoMerger = new ChannelMergerNode(context, {numberOfInputs: 2});
+ stereoMerger.connect(delay);
+
+ // Three independent signals
+ let monoSignal = new OscillatorNode(context, {frequency: 440});
+ let leftSignal = new OscillatorNode(context, {frequency: 450});
+ let rightSignal = new OscillatorNode(context, {frequency: 460});
+ monoSignal.connect(testConvolver);
+ monoSignal.connect(referenceMonoConvolver);
+ leftSignal.connect(stereoMerger, 0, 0);
+ rightSignal.connect(stereoMerger, 0, 1);
+ monoSignal.start();
+ leftSignal.start();
+ rightSignal.start();
+
+ return context.startRendering().
+ then((buffer) => {
+ let maxDiff = -1.0;
+ let frameIndex = 0;
+ let channelIndex = 0;
+ for (let c = 0; c < 2; ++c) {
+ let testOutput = buffer.getChannelData(0 + c);
+ let referenceOutput = buffer.getChannelData(2 + c);
+ for (var i = 0; i < buffer.length; ++i) {
+ var diff = Math.abs(testOutput[i] - referenceOutput[i]);
+ if (diff > maxDiff) {
+ maxDiff = diff;
+ frameIndex = i;
+ channelIndex = c;
+ }
+ }
+ }
+ assert_approx_equals(buffer.getChannelData(0 + channelIndex)[frameIndex],
+ buffer.getChannelData(2 + channelIndex)[frameIndex],
+ EPSILON,
+ `output at ${frameIndex} ` +
+ `in channel ${channelIndex}` );
+ });
+}
+
+promise_test(() => test_linear_upmixing("speakers", MONO_FRAMES),
+ "speakers, initially mono");
+promise_test(() => test_linear_upmixing("discrete", MONO_FRAMES),
+ "discrete");
+// Gecko uses a separate path for "speakers" up-mixing when the convolver's
+// first input is stereo, so test that separately.
+promise_test(() => test_linear_upmixing("speakers", 0),
+ "speakers, initially stereo");
+</script>
diff --git a/dom/media/webaudio/test/test_convolverNode.html b/dom/media/webaudio/test/test_convolverNode.html
new file mode 100644
index 0000000000..c1677aafab
--- /dev/null
+++ b/dom/media/webaudio/test/test_convolverNode.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test the ConvolverNode interface</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var context = new AudioContext();
+ var conv = new ConvolverNode(context);
+
+ is(conv.channelCount, 2, "Convolver node has 2 input channels by default");
+ is(conv.channelCountMode, "clamped-max", "Correct channelCountMode for the Convolver node");
+ is(conv.channelInterpretation, "speakers", "Correct channelCountInterpretation for the Convolver node");
+
+ is(conv.buffer, null, "Default buffer value");
+ is(conv.normalize, true, "Default normalize value");
+
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_convolverNodeChannelCount.html b/dom/media/webaudio/test/test_convolverNodeChannelCount.html
new file mode 100644
index 0000000000..03824578ea
--- /dev/null
+++ b/dom/media/webaudio/test/test_convolverNodeChannelCount.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test ConvolverNode channel count</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script src="webaudio.js" type="text/javascript"></script>
+<script class="testbody" type="text/javascript">
+
+const signalLength = 2048;
+const responseLength = 1000;
+const outputLength = 2048; // < signalLength + responseLength to test bug 910171
+
+var gTest = {
+ length: outputLength,
+ numberOfChannels: 2,
+ createGraph(context) {
+ var buffer = context.createBuffer(2, signalLength, context.sampleRate);
+ for (var i = 0; i < signalLength; ++i) {
+ var sample = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ // When mixed into a single channel, this produces silence
+ buffer.getChannelData(0)[i] = sample;
+ buffer.getChannelData(1)[i] = -sample;
+ }
+
+ var response = context.createBuffer(2, responseLength, context.sampleRate);
+ for (var i = 0; i < responseLength; ++i) {
+ response.getChannelData(0)[i] = i / responseLength;
+ response.getChannelData(1)[i] = 1 - (i / responseLength);
+ }
+
+ var convolver = context.createConvolver();
+ convolver.buffer = response;
+ convolver.channelCount = 1;
+
+ expectException(function() { convolver.channelCount = 3; },
+ DOMException.NOT_SUPPORTED_ERR);
+ convolver.channelCountMode = "explicit";
+ expectException(function() { convolver.channelCountMode = "max"; },
+ DOMException.NOT_SUPPORTED_ERR);
+ convolver.channelInterpretation = "discrete";
+ convolver.channelInterpretation = "speakers";
+
+ var source = context.createBufferSource();
+ source.buffer = buffer;
+ source.connect(convolver);
+ source.start(0);
+
+ return convolver;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_convolverNodeChannelInterpretationChanges.html b/dom/media/webaudio/test/test_convolverNodeChannelInterpretationChanges.html
new file mode 100644
index 0000000000..bede517b2e
--- /dev/null
+++ b/dom/media/webaudio/test/test_convolverNodeChannelInterpretationChanges.html
@@ -0,0 +1,169 @@
+<!DOCTYPE html>
+<title>Test up-mixing in ConvolverNode after ChannelInterpretation change</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// This test is not in wpt because it requires that multiple changes to the
+// nodes in an AudioContext during a single event will be processed by the
+// audio thread in a single transaction. Gecko provides that, but this is not
+// currently required by the Web Audio API.
+
+const EPSILON = Math.pow(2, -23);
+// sampleRate is a power of two so that delay times are exact in base-2
+// floating point arithmetic.
+const SAMPLE_RATE = 32768;
+// Length of initial mono signal in frames, if the test has an initial mono
+// signal. This is more than one block to ensure that at least one block
+// will be mono, even if interpolation in the DelayNode means that stereo is
+// output one block earlier than if frames are delayed without interpolation.
+const MONO_FRAMES = 256;
+// Length of response buffer. This is greater than 1 to ensure that the
+// convolver has stereo output at least one block after stereo input is
+// disconnected.
+const RESPONSE_FRAMES = 2;
+
+function test_interpretation_change(t, initialInterpretation, initialMonoFrames)
+{
+ let context = new AudioContext({sampleRate: SAMPLE_RATE});
+
+ // Three independent signals. These are constant so that results are
+ // independent of the timing of the `ended` event.
+ let monoOffset = 0.25
+ let monoSource = new ConstantSourceNode(context, {offset: monoOffset});
+ let leftOffset = 0.125;
+ let rightOffset = 0.5;
+ let leftSource = new ConstantSourceNode(context, {offset: leftOffset});
+ let rightSource = new ConstantSourceNode(context, {offset: rightOffset});
+ monoSource.start();
+ leftSource.start();
+ rightSource.start();
+
+ let stereoMerger = new ChannelMergerNode(context, {numberOfInputs: 2});
+ leftSource.connect(stereoMerger, 0, 0);
+ rightSource.connect(stereoMerger, 0, 1);
+
+ // The DelayNode initially has a single channel of silence, and so the
+ // output of the delay node is first mono silence (if there is a non-zero
+ // initialMonoFrames), then stereo. In Gecko, this triggers a convolver
+ // configuration that is different for different channelInterpretations.
+ let delay =
+ new DelayNode(context,
+ {maxDelayTime: MONO_FRAMES / context.sampleRate,
+ delayTime: initialMonoFrames / context.sampleRate});
+ stereoMerger.connect(delay);
+
+ // Two convolvers with the same impulse response. The test convolver will
+ // process a mix of stereo and mono signals. The reference convolver will
+ // always process stereo, including the up-mixed mono signal.
+ let response = new AudioBuffer({numberOfChannels: 1,
+ length: RESPONSE_FRAMES,
+ sampleRate: context.sampleRate});
+ response.getChannelData(0)[response.length - 1] = 1;
+
+ let testConvolver = new ConvolverNode(context,
+ {disableNormalization: true,
+ buffer: response});
+ testConvolver.channelInterpretation = initialInterpretation;
+ let referenceConvolver = new ConvolverNode(context,
+ {disableNormalization: true,
+ buffer: response});
+ // No need to set referenceConvolver.channelInterpretation because
+ // input is always stereo, due to up-mixing at gain node.
+ let referenceMixer = new GainNode(context);
+ referenceMixer.channelCount = 2;
+ referenceMixer.channelCountMode = "explicit";
+ referenceMixer.channelInterpretation = initialInterpretation;
+ referenceMixer.connect(referenceConvolver);
+
+ delay.connect(testConvolver);
+ delay.connect(referenceMixer);
+
+ monoSource.connect(testConvolver);
+ monoSource.connect(referenceMixer);
+
+ // A timer sends 'ended' when the convolvers are known to be processing
+ // stereo.
+ let timer = new ConstantSourceNode(context);
+ timer.start();
+ timer.stop((initialMonoFrames + 1) / context.sampleRate);
+
+ timer.onended = t.step_func(() => {
+ let changedInterpretation =
+ initialInterpretation == "speakers" ? "discrete" : "speakers";
+
+ // Switch channelInterpretation in test and reference paths.
+ testConvolver.channelInterpretation = changedInterpretation;
+ referenceMixer.channelInterpretation = changedInterpretation;
+
+ // Disconnect the stereo input from both test and reference convolvers.
+ // The disconnected convolvers will continue to output stereo for at least
+ // one frame. The test convolver will up-mix its mono input into its two
+ // buffers.
+ delay.disconnect();
+
+ // Capture the outputs in a script processor.
+ //
+ // The first two channels contain signal where some up-mixing occurs
+ // internally to the test convolver.
+ //
+ // The last two channels are expected to contain the same signal, but
+ // up-mixing was performed at a GainNode prior to convolution.
+ //
+ // Two stereo splitters will collect test and reference outputs.
+ let testSplitter =
+ new ChannelSplitterNode(context, {numberOfOutputs: 2});
+ let referenceSplitter =
+ new ChannelSplitterNode(context, {numberOfOutputs: 2});
+ testConvolver.connect(testSplitter);
+ referenceConvolver.connect(referenceSplitter);
+
+ let outputMerger = new ChannelMergerNode(context, {numberOfInputs: 4});
+ testSplitter.connect(outputMerger, 0, 0);
+ testSplitter.connect(outputMerger, 1, 1);
+ referenceSplitter.connect(outputMerger, 0, 2);
+ referenceSplitter.connect(outputMerger, 1, 3);
+
+ let processor = context.createScriptProcessor(256, 4, 0);
+ outputMerger.connect(processor);
+
+ processor.onaudioprocess = t.step_func_done((e) => {
+ e.target.onaudioprocess = null;
+ outputMerger.disconnect();
+
+ // The test convolver output is stereo for the first block.
+ let length = 128;
+
+ let buffer = e.inputBuffer;
+ let maxDiff = -1.0;
+ let frameIndex = 0;
+ let channelIndex = 0;
+ for (let c = 0; c < 2; ++c) {
+ let testOutput = buffer.getChannelData(0 + c);
+ let referenceOutput = buffer.getChannelData(2 + c);
+ for (var i = 0; i < length; ++i) {
+ var diff = Math.abs(testOutput[i] - referenceOutput[i]);
+ if (diff > maxDiff) {
+ maxDiff = diff;
+ frameIndex = i;
+ channelIndex = c;
+ }
+ }
+ }
+ assert_approx_equals(buffer.getChannelData(0 + channelIndex)[frameIndex],
+ buffer.getChannelData(2 + channelIndex)[frameIndex],
+ EPSILON,
+ `output at ${frameIndex} ` +
+ `in channel ${channelIndex}` );
+ });
+ });
+}
+
+async_test((t) => test_interpretation_change(t, "speakers", MONO_FRAMES),
+ "speakers to discrete, initially mono");
+async_test((t) => test_interpretation_change(t, "discrete", MONO_FRAMES),
+ "discrete to speakers");
+// Gecko uses a separate path for "speakers" initial up-mixing when the
+// convolver's first input is stereo, so test that separately.
+async_test((t) => test_interpretation_change(t, "speakers", 0),
+ "speakers to discrete, initially stereo");
+</script>
diff --git a/dom/media/webaudio/test/test_convolverNodeDelay.html b/dom/media/webaudio/test/test_convolverNodeDelay.html
new file mode 100644
index 0000000000..2e8caf8027
--- /dev/null
+++ b/dom/media/webaudio/test/test_convolverNodeDelay.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<title>Test convolution to delay a triangle pulse</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+const sampleRate = 48000;
+const LENGTH = 12800;
+// tolerate 16-bit math.
+const EPSILON = 1.0 / Math.pow(2, 15);
+
+// Triangle pulse
+var sourceBuffer = new OfflineAudioContext(1, 1, sampleRate).
+ createBuffer(1, 2 * 128, sampleRate);
+var channelData = sourceBuffer.getChannelData(0);
+for (var i = 0; i < 128; ++i) {
+ channelData[i] = i/128;
+ channelData[128 + i] = 1.0 - i/128;
+}
+
+function test_delay_index(delayIndex) {
+
+ var context = new OfflineAudioContext(2, LENGTH, sampleRate);
+
+ var merger = context.createChannelMerger(2);
+ merger.connect(context.destination);
+
+ var impulse = context.createBuffer(1, delayIndex + 1, sampleRate);
+ impulse.getChannelData(0)[delayIndex] = 1.0;
+ var convolver = context.createConvolver();
+ convolver.normalize = false;
+ convolver.buffer = impulse;
+ convolver.connect(merger, 0, 0);
+
+ var delayTime = delayIndex/sampleRate;
+ var delay = context.createDelay(delayTime || 1/sampleRate);
+ delay.delayTime.value = delayTime;
+ delay.connect(merger, 0, 1);
+
+ var source = context.createBufferSource();
+ source.buffer = sourceBuffer;
+ source.connect(convolver);
+ source.connect(delay);
+ source.start(0);
+
+ return context.startRendering().
+ then((buffer) => {
+ var convolverOutput = buffer.getChannelData(0);
+ var delayOutput = buffer.getChannelData(1);
+ var maxDiff = 0.0;
+ var maxIndex = 0;
+ for (var i = 0; i < buffer.length; ++i) {
+ var diff = Math.abs(convolverOutput[i] - delayOutput[i]);
+ if (diff > maxDiff) {
+ maxDiff = diff;
+ maxIndex = i;
+ }
+ }
+ // The convolver should produce similar output to the delay.
+ assert_approx_equals(convolverOutput[maxIndex], delayOutput[maxIndex],
+ EPSILON, "output at " + maxIndex);
+ });
+}
+
+// The 5/4 ratio provides sampling across a range of delays and offsets within
+// blocks.
+for (var delayIndex = 0;
+ delayIndex < LENGTH;
+ delayIndex = Math.floor((5 * (delayIndex + 1)) / 4)) {
+ promise_test(test_delay_index.bind(null, delayIndex),
+ "Delay " + delayIndex);
+}
+</script>
diff --git a/dom/media/webaudio/test/test_convolverNodeFiniteInfluence.html b/dom/media/webaudio/test/test_convolverNodeFiniteInfluence.html
new file mode 100644
index 0000000000..1cfb51ce8a
--- /dev/null
+++ b/dom/media/webaudio/test/test_convolverNodeFiniteInfluence.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<title>Test convolution effect has finite duration</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+promise_test(function() {
+
+ const responseLength = 256;
+ // Accept an influence period of twice the responseLength to accept FFT
+ // implementations.
+ const tolerancePeriod = 2 * responseLength;
+ const totalSize = tolerancePeriod + responseLength;
+
+ var context = new OfflineAudioContext(1, totalSize, 48000);
+
+ var responseBuffer =
+ context.createBuffer(1, responseLength, context.sampleRate);
+ var responseChannelData = responseBuffer.getChannelData(0);
+ responseChannelData[0] = 1;
+ responseChannelData[responseLength - 1] = 1;
+ var convolver = context.createConvolver();
+ convolver.buffer = responseBuffer;
+ convolver.connect(context.destination);
+
+ var sourceBuffer = context.createBuffer(1, totalSize, context.sampleRate);
+ sourceBuffer.getChannelData(0)[0] = NaN;
+ var source = context.createBufferSource();
+ source.buffer = sourceBuffer;
+ source.connect(convolver);
+ source.start();
+
+ return context.startRendering().
+ then((buffer) => {
+ var convolverOutput = buffer.getChannelData(0);
+ // There should be no non-zeros after the tolerance period.
+ var testIndex = tolerancePeriod;
+ for (;
+ testIndex < buffer.length - 1 && convolverOutput[testIndex] == 0;
+ ++testIndex) {
+ }
+ assert_equals(convolverOutput[testIndex], 0, "output at " + testIndex);
+ });
+});
+</script>
diff --git a/dom/media/webaudio/test/test_convolverNodeNormalization.html b/dom/media/webaudio/test/test_convolverNodeNormalization.html
new file mode 100644
index 0000000000..24cb7d1670
--- /dev/null
+++ b/dom/media/webaudio/test/test_convolverNodeNormalization.html
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<title>Test normalization of convolution buffers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// Constants from
+// https://www.w3.org/TR/2015/WD-webaudio-20151208/#widl-ConvolverNode-normalize
+const GainCalibration = 0.00125;
+const GainCalibrationSampleRate = 44100;
+
+const sampleRate = GainCalibrationSampleRate;
+const LENGTH = 12800;
+// tolerate 16-bit math.
+const EPSILON = 1.0 / Math.pow(2, 15);
+
+function test_normalization_via_response_concat(delayIndex)
+{
+ var context = new OfflineAudioContext(1, LENGTH, sampleRate);
+
+ var impulse = context.createBuffer(1, 1, sampleRate);
+ impulse.getChannelData(0)[0] = 1.0;
+ var source = context.createBufferSource();
+ source.buffer = impulse;
+ source.start(0);
+
+ // Construct a set of adjacent responses in such a way that, when each is
+ // convolved with the impulse, they can be merged to produce a constant.
+
+ // The 5/4 ratio provides a range of lengths with different offsets within
+ // blocks.
+ var i = 0;
+ for (var responseEnd = 1;
+ i < LENGTH;
+ responseEnd = Math.floor((5 * responseEnd) / 4) + 1) {
+ var responseBuffer = context.createBuffer(1, responseEnd, sampleRate);
+ var response = responseBuffer.getChannelData(0);
+ var responseStart = i;
+ // The values in the response should be normalized, and so the output
+ // should not be dependent on the value. Pick a changing value to test
+ // this.
+ var value = responseStart + 1;
+ for (; i < responseEnd; ++i) {
+ response[i] = value;
+ }
+ var convolver = context.createConvolver();
+ convolver.normalize = true;
+ convolver.buffer = responseBuffer;
+ convolver.connect(context.destination);
+ // Undo the normalization calibration by scaling the impulse so as to
+ // expect unit pulse output from the convolver.
+ var gain = context.createGain();
+ gain.gain.value =
+ Math.sqrt((responseEnd - responseStart) / responseEnd) / GainCalibration;
+ gain.connect(convolver);
+ source.connect(gain);
+ }
+
+ return context.startRendering().
+ then((buffer) => {
+ var output = buffer.getChannelData(0);
+ var max = output[0];
+ var maxIndex = 0;
+ var min = max;
+ var minIndex = 0;
+ for (var i = 1; i < buffer.length; ++i) {
+ if (output[i] > max) {
+ max = output[i];
+ maxIndex = i;
+ } else if (output[i] < min) {
+ min = output[i];
+ minIndex = i;
+ }
+ }
+ assert_approx_equals(output[maxIndex], 1.0, EPSILON,
+ "max output at " + maxIndex);
+ assert_approx_equals(output[minIndex], 1.0, EPSILON,
+ "min output at " + minIndex);
+ });
+}
+
+promise_test(test_normalization_via_response_concat,
+ "via response concatenation");
+</script>
diff --git a/dom/media/webaudio/test/test_convolverNodeOOM.html b/dom/media/webaudio/test/test_convolverNodeOOM.html
new file mode 100644
index 0000000000..80260267a8
--- /dev/null
+++ b/dom/media/webaudio/test/test_convolverNodeOOM.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test ConvolverNode with very large buffer that triggers an OOM</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 1,
+ skipOfflineContextTests: true,
+ createGraph(context) {
+ var source = context.createOscillator();
+ var convolver = context.createConvolver();
+ // Very big buffer that results in an OOM
+ try {
+ var buffer = context.createBuffer(2, 300000000, context.sampleRate)
+ buffer.getChannelData(0);
+ } catch(e) {
+ // OOM when attempting to create the buffer, this can happen on 32bits
+ // OSes. Simply return here.
+ return convolver;
+ }
+ source.connect(convolver);
+ try {
+ convolver.buffer = buffer;
+ } catch (e) {
+ // This can also OOM.
+ return convolver;
+ }
+ source.start();
+ return convolver;
+ }
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_convolverNodePassThrough.html b/dom/media/webaudio/test/test_convolverNodePassThrough.html
new file mode 100644
index 0000000000..54682aee0c
--- /dev/null
+++ b/dom/media/webaudio/test/test_convolverNodePassThrough.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test ConvolverNode with passthrough</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var source = context.createBufferSource();
+
+ var convolver = context.createConvolver();
+
+ source.buffer = this.buffer;
+ convolver.buffer = this.buffer;
+
+ source.connect(convolver);
+
+ var convolverWrapped = SpecialPowers.wrap(convolver);
+ ok("passThrough" in convolverWrapped, "ConvolverNode should support the passThrough API");
+ convolverWrapped.passThrough = true;
+
+ source.start(0);
+ return convolver;
+ },
+ createExpectedBuffers(context) {
+ this.buffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ this.buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+
+ return [this.buffer];
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_convolverNodeWithGain.html b/dom/media/webaudio/test/test_convolverNodeWithGain.html
new file mode 100644
index 0000000000..0762f16329
--- /dev/null
+++ b/dom/media/webaudio/test/test_convolverNodeWithGain.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test ConvolverNode after a GainNode - Bug 891254 </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script src="webaudio.js" type="text/javascript"></script>
+<script class="testbody" type="text/javascript">
+
+const signalLength = 2048;
+const responseLength = 100;
+const outputLength = 4096; // > signalLength + responseLength
+
+var gTest = {
+ length: outputLength,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var buffer = context.createBuffer(1, signalLength, context.sampleRate);
+ for (var i = 0; i < signalLength; ++i) {
+ buffer.getChannelData(0)[i] = Math.sin(2 * Math.PI * i / signalLength);
+ }
+
+ var source = context.createBufferSource();
+ source.buffer = buffer;
+ source.start(0);
+
+ var response = context.createBuffer(1, responseLength, context.sampleRate);
+ for (var i = 0; i < responseLength; ++i) {
+ response.getChannelData(0)[i] = i / responseLength;
+ }
+
+ var gain = context.createGain();
+ gain.gain.value = -1;
+ source.connect(gain);
+
+ var convolver1 = context.createConvolver();
+ convolver1.buffer = response;
+ gain.connect(convolver1);
+
+ var convolver2 = context.createConvolver();
+ convolver2.buffer = response;
+ source.connect(convolver2);
+
+ // The output of convolver1 should be the inverse of convolver2, so blend
+ // them together and expect silence.
+ var blend = context.createGain();
+ convolver1.connect(blend);
+ convolver2.connect(blend);
+
+ return blend;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_convolverNode_mono_mono.html b/dom/media/webaudio/test/test_convolverNode_mono_mono.html
new file mode 100644
index 0000000000..f3bac8d5c3
--- /dev/null
+++ b/dom/media/webaudio/test/test_convolverNode_mono_mono.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+
+<html>
+<head>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script type="text/javascript" src="webaudio.js"></script>
+<script type="text/javascript" src="layouttest-glue.js"></script>
+<script type="text/javascript" src="blink/audio-testing.js"></script>
+<script type="text/javascript" src="blink/convolution-testing.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+
+<div id="description"></div>
+<div id="console"></div>
+
+<script>
+description("Tests ConvolverNode processing a mono channel with mono impulse response.");
+SimpleTest.waitForExplicitFinish();
+
+// To test the convolver, we convolve two square pulses together to
+// produce a triangular pulse. To verify the result is correct we
+// check several parts of the result. First, we make sure the initial
+// part of the result is zero (due to the latency in the convolver).
+// Next, the triangular pulse should match the theoretical result to
+// within some roundoff. After the triangular pulse, the result
+// should be exactly zero, but round-off prevents that. We make sure
+// the part after the pulse is sufficiently close to zero. Finally,
+// the result should be exactly zero because the inputs are exactly
+// zero.
+function runTest() {
+ if (window.testRunner) {
+ window.testRunner.dumpAsText();
+ window.testRunner.waitUntilDone();
+ }
+
+ window.jsTestIsAsync = true;
+
+ // Create offline audio context.
+ var context = new OfflineAudioContext(2, sampleRate * renderLengthSeconds, sampleRate);
+
+ var squarePulse = createSquarePulseBuffer(context, pulseLengthFrames);
+ var trianglePulse = createTrianglePulseBuffer(context, 2 * pulseLengthFrames);
+
+ var bufferSource = context.createBufferSource();
+ bufferSource.buffer = squarePulse;
+
+ var convolver = context.createConvolver();
+ convolver.normalize = false;
+ convolver.buffer = squarePulse;
+
+ bufferSource.connect(convolver);
+ convolver.connect(context.destination);
+
+ bufferSource.start(0);
+
+ context.oncomplete = checkConvolvedResult(trianglePulse);
+ context.startRendering();
+}
+
+function finishJSTest() {
+ SimpleTest.finish();
+}
+
+runTest();
+
+</script>
+
+<script src="../fast/js/resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_currentTime.html b/dom/media/webaudio/test/test_currentTime.html
new file mode 100644
index 0000000000..66fdf42653
--- /dev/null
+++ b/dom/media/webaudio/test/test_currentTime.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AudioContext.currentTime</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("This test needs to wait a while for the AudioContext's timer to start.");
+addLoadEvent(function() {
+ var ac = new AudioContext();
+ is(ac.currentTime, 0, "AudioContext.currentTime should be 0 initially");
+ ac.onstatechange = function () {
+ ok(ac.state == "running", "AudioContext.currentTime should eventually start");
+ ok(ac.currentTime > 0, "AudioContext.currentTime should have increased by now");
+ SimpleTest.finish();
+ }
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_decodeAudioDataOnDetachedBuffer.html b/dom/media/webaudio/test/test_decodeAudioDataOnDetachedBuffer.html
new file mode 100644
index 0000000000..e7c6d2db0c
--- /dev/null
+++ b/dom/media/webaudio/test/test_decodeAudioDataOnDetachedBuffer.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+ <meta charset=utf-8>
+<head>
+ <title>Bug 1308434 - Test DecodeAudioData on detached buffer</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script type="text/javascript">
+var testDecodeAudioDataOnDetachedBuffer = function(buffer) {
+ var context = new AudioContext();
+
+ // make the buffer detached
+ context.decodeAudioData(buffer);
+
+ // check that the buffer is detached
+ is(buffer.byteLength, 0, "Buffer should be detached");
+
+ // call decodeAudioData on detached buffer
+ context.decodeAudioData(buffer).then(function(b) {
+ ok(false, "We should not be able to decode the detached buffer but we do");
+ SimpleTest.finish();
+ }, function(r) {
+ SimpleTest.isa(r, TypeError);
+ is(r.message, "BaseAudioContext.decodeAudioData: Buffer argument can't be a detached buffer", "Incorrect message");
+ SimpleTest.finish();
+ });
+};
+
+var filename = "small-shot.mp3";
+
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", filename);
+ xhr.responseType = "arraybuffer";
+
+ xhr.onload = function() {
+ testDecodeAudioDataOnDetachedBuffer(xhr.response);
+ };
+
+ xhr.send();
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_decodeAudioDataPromise.html b/dom/media/webaudio/test/test_decodeAudioDataPromise.html
new file mode 100644
index 0000000000..139a686db1
--- /dev/null
+++ b/dom/media/webaudio/test/test_decodeAudioDataPromise.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test the decodeAudioData API with Promise</title>
+
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script src="webaudio.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+
+var finished = 0;
+
+function finish() {
+ if (++finished == 2) {
+ SimpleTest.finish();
+ }
+}
+
+var ac = new AudioContext();
+// Test that a the promise is rejected with an invalid source buffer.
+expectNoException(function() {
+ var p = ac.decodeAudioData(" ");
+ ok(p instanceof Promise, "AudioContext.decodeAudioData should return a Promise");
+ p.then(function(data) {
+ ok(false, "Promise should not resolve with an invalid source buffer.");
+ finish();
+ }).catch(function(e) {
+ ok(true, "Promise should be rejected with an invalid source buffer.");
+ ok(e.name == "TypeError", "The error should be TypeError");
+ finish();
+ })
+});
+
+// Test that a the promise is resolved with a valid source buffer.
+var xhr = new XMLHttpRequest();
+xhr.open("GET", "ting-44.1k-1ch.ogg", true);
+xhr.responseType = "arraybuffer";
+xhr.onload = function() {
+ var p = ac.decodeAudioData(xhr.response);
+ ok(p instanceof Promise, "AudioContext.decodeAudioData should return a Promise");
+ p.then(function(data) {
+ ok(data instanceof AudioBuffer, "Promise should resolve, passing an AudioBuffer");
+ ok(true, "Promise should resolve with a valid source buffer.");
+ finish();
+ }).catch(function() {
+ ok(false, "Promise should not be rejected with a valid source buffer.");
+ finish();
+ });
+};
+xhr.send();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_decodeAudioError.html b/dom/media/webaudio/test/test_decodeAudioError.html
new file mode 100644
index 0000000000..f18b971ac4
--- /dev/null
+++ b/dom/media/webaudio/test/test_decodeAudioError.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test the decodeAudioData Errors</title>
+
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <script src="webaudio.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+
+var finished = 0;
+
+var ctx = new AudioContext();
+
+function errorExpectedWithFile(file, errorMsg) {
+ var xhr = new XMLHttpRequest();
+ function test(e) {
+ ok(e instanceof DOMException,
+ "The exception should be an instance of DOMException");
+ ok(e.name == "EncodingError",
+ "The exception name should be EncodingError");
+ ok(e.message == errorMsg,
+ "The exception message is not the one intended.\n" +
+ "\tExpected : " + errorMsg + "\n" +
+ "\tGot : " + e.message );
+ finish();
+ }
+ xhr.open("GET", file, true);
+ xhr.responseType = "arraybuffer";
+ xhr.onload = function() {
+ ctx.decodeAudioData(xhr.response, buffer => {
+ ok(false, "You should not be able to decode that");
+ finish();
+ }, e => test(e))
+ .then(buffer => {
+ ok(false, "You should not be able to decode that");
+ finish();
+ })
+ .catch(e => test(e));
+ };
+ xhr.send();
+}
+
+function finish() {
+ if (++finished == 4) {
+ SimpleTest.finish();
+ }
+}
+
+// Unknown Content
+errorExpectedWithFile("404", "The buffer passed to decodeAudioData contains an unknown content type.");
+
+// Invalid Content
+errorExpectedWithFile("invalidContent.flac", "The buffer passed to decodeAudioData contains invalid content which cannot be decoded successfully.");
+
+// No Audio
+// # Bug 1656032
+// Think about increasing the finish counter to 6 when activating this line
+// errorExpectedWithFile("noaudio.webm", "The buffer passed to decodeAudioData does not contain any audio.");
+
+// Unknown Error
+// errorExpectedWithFile("There is no file we can't handle", "An unknown error occurred while processing decodeAudioData.");
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_decodeMultichannel.html b/dom/media/webaudio/test/test_decodeMultichannel.html
new file mode 100644
index 0000000000..a799c641ee
--- /dev/null
+++ b/dom/media/webaudio/test/test_decodeMultichannel.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<meta charset=utf-8>
+<head>
+ <title>Test that we can decode multichannel file with webaudio and &lt;audio&gt;</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var testcases = [
+ {
+ filename: "audio-quad.wav",
+ channels: 4
+ },
+ {
+ filename: "8kHz-320kbps-6ch.aac",
+ channels: 6
+ }
+];
+
+SimpleTest.waitForExplicitFinish();
+
+function decodeUsingAudioElement(filename, resolve) {
+ var a = new Audio();
+ a.addEventListener("error", function() {
+ ok(false, "Error loading metadata");
+ resolve();
+ });
+ a.addEventListener("loadedmetadata", function() {
+ ok(true, "Metadata Loaded");
+ resolve();
+ });
+
+ a.src = filename;
+ a.load();
+}
+
+function testOne({filename, channels}) {
+ return new Promise(resolve => {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", filename);
+ xhr.responseType = "arraybuffer";
+ xhr.onload = function() {
+ var context = new AudioContext();
+ context.decodeAudioData(xhr.response, function(b) {
+ ok(true, "Decoding of a wave file with four channels succeded.");
+ is(b.numberOfChannels,
+ channels,
+ `The AudioBuffer decoded from ${filename} should have ${channels} channels.`);
+ decodeUsingAudioElement(filename, resolve);
+ }, function() {
+ ok(false, `Decoding ${filename} failed)`);
+ decodeUsingAudioElement(filename, resolve);
+ });
+ };
+ xhr.send(null);
+ });
+}
+
+async function runTest() {
+ for (var testcase of testcases) {
+ await testOne(testcase);
+ }
+
+ SimpleTest.finish();
+}
+
+addLoadEvent(runTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_decodeOpusTail.html b/dom/media/webaudio/test/test_decodeOpusTail.html
new file mode 100644
index 0000000000..451b2b6a23
--- /dev/null
+++ b/dom/media/webaudio/test/test_decodeOpusTail.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<meta charset="utf-8">
+<head>
+ <title>Regression test to check that opus files don't have a tail at the end.</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+// This gets a 1 second Opus file and decodes it to a buffer. The opus file is
+// decoded at 48kHz, and the OfflineAudioContext is also at 48kHz, no resampling
+// is taking place.
+fetch('sweep-300-330-1sec.opus')
+.then(function(response) { return response.arrayBuffer(); })
+.then(function(buffer) {
+ var off = new OfflineAudioContext(1, 128, 48000);
+ off.decodeAudioData(buffer, function(decoded) {
+ var pcm = decoded.getChannelData(0);
+ is(pcm.length, 48000, "The length of the decoded file is correct.");
+ SimpleTest.finish();
+ });
+});
+
+</script>
diff --git a/dom/media/webaudio/test/test_decoderDelay.html b/dom/media/webaudio/test/test_decoderDelay.html
new file mode 100644
index 0000000000..4ed379dec5
--- /dev/null
+++ b/dom/media/webaudio/test/test_decoderDelay.html
@@ -0,0 +1,154 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8" />
+ <title>Test that decoder delay is handled</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+ <script class="testbody" type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+ const {AppConstants} = SpecialPowers.ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+ );
+
+ var tests_half_a_second = [
+ "half-a-second-1ch-44100-aac.mp4",
+ "half-a-second-1ch-44100-flac.flac",
+ "half-a-second-1ch-44100-libmp3lame.mp3",
+ "half-a-second-1ch-44100-libopus.opus",
+ "half-a-second-1ch-44100-libopus.webm",
+ "half-a-second-1ch-44100-libvorbis.ogg",
+ "half-a-second-1ch-44100.wav",
+ "half-a-second-1ch-48000-aac.mp4",
+ "half-a-second-1ch-48000-flac.flac",
+ "half-a-second-1ch-48000-libmp3lame.mp3",
+ "half-a-second-1ch-48000-libopus.opus",
+ "half-a-second-1ch-48000-libopus.webm",
+ "half-a-second-1ch-48000-libvorbis.ogg",
+ "half-a-second-1ch-48000.wav",
+ "half-a-second-2ch-44100-aac.mp4",
+ "half-a-second-2ch-44100-flac.flac",
+ "half-a-second-2ch-44100-libmp3lame.mp3",
+ "half-a-second-2ch-44100-libopus.opus",
+ "half-a-second-2ch-44100-libopus.webm",
+ "half-a-second-2ch-44100-libvorbis.ogg",
+ "half-a-second-2ch-44100.wav",
+ "half-a-second-2ch-48000-aac.mp4",
+ "half-a-second-2ch-48000-flac.flac",
+ "half-a-second-2ch-48000-libmp3lame.mp3",
+ "half-a-second-2ch-48000-libopus.opus",
+ "half-a-second-2ch-48000-libopus.webm",
+ "half-a-second-2ch-48000-libvorbis.ogg",
+ "half-a-second-2ch-48000.wav",
+ ];
+
+ // Those files are almost exactly half a second, but don't have enough pre-roll/padding
+ // information in the container, or the container isn't parsed properly, so
+ // aren't trimmed appropriately.
+ // vorbis webm, opus mp4, aac adts
+ var tests_adts = [
+ "half-a-second-1ch-44100-aac.aac",
+ "half-a-second-1ch-44100-libopus.mp4",
+ "half-a-second-1ch-44100-libvorbis.webm",
+ "half-a-second-1ch-48000-aac.aac",
+ "half-a-second-1ch-48000-libopus.mp4",
+ "half-a-second-1ch-48000-libvorbis.webm",
+ "half-a-second-2ch-44100-aac.aac",
+ "half-a-second-2ch-44100-libopus.mp4",
+ "half-a-second-2ch-44100-libvorbis.webm",
+ "half-a-second-2ch-48000-aac.aac",
+ "half-a-second-2ch-48000-libopus.mp4",
+ "half-a-second-2ch-48000-libvorbis.webm",
+ ];
+
+ // Other files that have interesting characteristics.
+ var tests_others = [
+ {
+ // Very short VBR file, 16 frames of audio at 44100. Padding spanning two
+ // packets.
+ "path": "sixteen-frames.mp3",
+ "frameCount": 16,
+ "samplerate": 44100,
+ "fuzz": {}
+ },
+ {
+ // This is incorrect (the duration should be 0.5s exactly)
+ // This is tracked in https://github.com/mozilla/mp4parse-rust/issues/404
+ "path":"half-a-second-1ch-44100-aac-afconvert.mp4",
+ "frameCount": 22464,
+ "samplerate": 44100,
+ "fuzz": {
+ "android": 2
+ }
+ },
+ {
+ // Bug 1856145 - Invalid OGG file with busted granulepos in the
+ // bytestrem, but we should be able to recover and play it properly.
+ // This triggers a bug in the decoder delay trimming logic.
+ "path": "1856145.ogg",
+ "samplerate": 8000,
+ "frameCount": 8192,
+ "fuzz" : {}
+ }
+ ];
+
+ var all_tests = [tests_half_a_second, tests_adts, tests_others].flat();
+
+ var count = 0;
+ function checkDone() {
+ if (++count == all_tests.length) {
+ SimpleTest.finish();
+ }
+ }
+
+ async function doit() {
+ var context = new OfflineAudioContext(1, 128, 48000);
+ tests_half_a_second.forEach(async testfile => {
+ var response = await fetch(testfile);
+ var buffer = await response.arrayBuffer();
+ var decoded = await context.decodeAudioData(buffer);
+ is(
+ decoded.duration,
+ 0.5,
+ "The file " + testfile + " is half a second."
+ );
+ // Value found empirically after looking at the files. The initial
+ // amplitude should be 0 at phase 0 because those files are sine wave.
+ // The compression is sometimes lossy and the first sample is not always
+ // exactly 0.0.
+ ok(
+ Math.abs(decoded.getChannelData(0)[0]) <= 0.022,
+ `The start point for ${testfile} is correct ${ decoded.getChannelData(0)[0] }`
+ );
+ checkDone();
+ });
+ tests_adts.forEach(async testfile => {
+ var response = await fetch(testfile);
+ var buffer = await response.arrayBuffer();
+ var decoded = await context.decodeAudioData(buffer);
+ // Value found empirically after looking at the files. ADTS containers
+ // don't have encoder delay / padding info so we can't trim correctly.
+ ok(
+ Math.abs(decoded.duration - 0.5) < 0.02,
+ `The ADTS file ${testfile} is about half a second (${decoded.duration}, error: ${Math.abs(decoded.duration-0.5)}).`
+ );
+ checkDone();
+ });
+ tests_others.forEach(async test => {
+ // Get an context at a specific rate to avoid duration changes due to resampling.
+ var contextAtRate = new OfflineAudioContext(1, 128, test.samplerate);
+ var response = await fetch(test.path);
+ var buffer = await response.arrayBuffer();
+ var decoded = await contextAtRate.decodeAudioData(buffer);
+ const fuzz = test.fuzz[AppConstants.platform] ?? 0;
+ ok(Math.abs(decoded.length - test.frameCount) <= fuzz, `${test.path} is ${decoded.length} frames long`);
+ checkDone();
+ });
+ }
+
+ doit();
+ </script>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_delayNode.html b/dom/media/webaudio/test/test_delayNode.html
new file mode 100644
index 0000000000..89172aa86f
--- /dev/null
+++ b/dom/media/webaudio/test/test_delayNode.html
@@ -0,0 +1,101 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test DelayNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script src="webaudio.js" type="text/javascript"></script>
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 4096,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var delay = new DelayNode(context);
+ ok(delay.delayTime, "The audioparam member must exist");
+ is(delay.delayTime.value, 0, "Correct initial value");
+ is(delay.delayTime.defaultValue, 0, "Correct default value");
+ delay.delayTime.value = 0.5;
+ is(delay.delayTime.value, 0.5, "Correct initial value");
+ is(delay.delayTime.defaultValue, 0, "Correct default value");
+
+ delay = new DelayNode(context, { delayTime: 0.5 });
+ ok(delay.delayTime, "The audioparam member must exist");
+ is(delay.delayTime.value, 0.5, "Correct initial value");
+ is(delay.delayTime.defaultValue, 0, "Correct default value");
+
+ var buffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+
+ var source = context.createBufferSource();
+
+ delay = context.createDelay();
+
+ source.buffer = buffer;
+
+ source.connect(delay);
+
+ ok(delay.delayTime, "The audioparam member must exist");
+ is(delay.delayTime.value, 0, "Correct initial value");
+ is(delay.delayTime.defaultValue, 0, "Correct default value");
+ delay.delayTime.value = 0.5;
+ is(delay.delayTime.value, 0.5, "Correct initial value");
+ is(delay.delayTime.defaultValue, 0, "Correct default value");
+ is(delay.channelCount, 2, "delay node has 2 input channels by default");
+ is(delay.channelCountMode, "max", "Correct channelCountMode for the delay node");
+ is(delay.channelInterpretation, "speakers", "Correct channelCountInterpretation for the delay node");
+
+ expectException(function() {
+ context.createDelay(0);
+ }, DOMException.NOT_SUPPORTED_ERR);
+ expectException(function() {
+ context.createDelay(180);
+ }, DOMException.NOT_SUPPORTED_ERR);
+ expectTypeError(function() {
+ context.createDelay(NaN);
+ }, DOMException.NOT_SUPPORTED_ERR);
+ expectException(function() {
+ context.createDelay(-1);
+ }, DOMException.NOT_SUPPORTED_ERR);
+
+ expectException(function() {
+ new DelayNode(context, { maxDelayTime: 0 });
+ }, DOMException.NOT_SUPPORTED_ERR);
+ expectException(function() {
+ new DelayNode(context, { maxDelayTime: 180 });
+ }, DOMException.NOT_SUPPORTED_ERR);
+ expectTypeError(function() {
+ new DelayNode(context, { maxDelayTime: NaN });
+ }, DOMException.NOT_SUPPORTED_ERR);
+ expectException(function() {
+ new DelayNode(context, { maxDelayTime: -1 });
+ }, DOMException.NOT_SUPPORTED_ERR);
+
+ context.createDelay(1); // should not throw
+
+ // Delay the source stream by 2048 frames
+ delay.delayTime.value = 2048 / context.sampleRate;
+
+ source.start(0);
+ return delay;
+ },
+ createExpectedBuffers(context) {
+ var expectedBuffer = context.createBuffer(1, 2048 * 2, context.sampleRate);
+ for (var i = 2048; i < 2048 * 2; ++i) {
+ expectedBuffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * (i - 2048) / context.sampleRate);
+ }
+ return expectedBuffer;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_delayNodeAtMax.html b/dom/media/webaudio/test/test_delayNodeAtMax.html
new file mode 100644
index 0000000000..3d0afba0ac
--- /dev/null
+++ b/dom/media/webaudio/test/test_delayNodeAtMax.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test DelayNode with maxDelayTime delay - bug 890528</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script src="webaudio.js" type="text/javascript"></script>
+<script class="testbody" type="text/javascript">
+
+const signalLength = 2048;
+const delayLength = 1000; // Not on a block boundary
+const outputLength = 4096 // > signalLength + 2 * delayLength;
+
+function applySignal(buffer, offset) {
+ for (var i = 0; i < signalLength; ++i) {
+ buffer.getChannelData(0)[offset + i] = Math.cos(Math.PI * i / signalLength);
+ }
+}
+
+var gTest = {
+ numberOfChannels: 1,
+ createGraph(context) {
+ var buffer = context.createBuffer(1, signalLength, context.sampleRate);
+ applySignal(buffer, 0);
+
+ var source = context.createBufferSource();
+ source.buffer = buffer;
+
+ const delayTime = delayLength / context.sampleRate;
+ var delay = context.createDelay(delayTime);
+ delay.delayTime.value = delayTime;
+
+ source.connect(delay);
+
+ source.start(0);
+ return delay;
+ },
+ createExpectedBuffers(context) {
+ var expectedBuffer = context.createBuffer(1, outputLength, context.sampleRate);
+ applySignal(expectedBuffer, delayLength);
+ return expectedBuffer;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_delayNodeChannelChanges.html b/dom/media/webaudio/test/test_delayNodeChannelChanges.html
new file mode 100644
index 0000000000..c58d81f467
--- /dev/null
+++ b/dom/media/webaudio/test/test_delayNodeChannelChanges.html
@@ -0,0 +1,98 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>test DelayNode channel count changes</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestCompleteLog();
+
+const bufferSize = 4096;
+
+var ctx;
+var testDelay;
+var stereoDelay;
+var invertor;
+
+function compareOutputs(callback) {
+ var processor = ctx.createScriptProcessor(bufferSize, 2, 0);
+ testDelay.connect(processor);
+ invertor.connect(processor);
+ processor.onaudioprocess =
+ function(e) {
+ compareBuffers(e.inputBuffer,
+ ctx.createBuffer(2, bufferSize, ctx.sampleRate));
+ e.target.onaudioprocess = null;
+ callback();
+ }
+}
+
+function startTest() {
+ // And a two-channel signal
+ var merger = ctx.createChannelMerger();
+ merger.connect(testDelay);
+ merger.connect(stereoDelay);
+ var oscL = ctx.createOscillator();
+ oscL.connect(merger, 0, 0);
+ oscL.start(0);
+ var oscR = ctx.createOscillator();
+ oscR.type = "sawtooth";
+ oscR.connect(merger, 0, 1);
+ oscR.start(0);
+
+ compareOutputs(
+ function () {
+ // Disconnect the two-channel signal and test again
+ merger.disconnect();
+ compareOutputs(SimpleTest.finish);
+ });
+}
+
+function prepareTest() {
+ ctx = new AudioContext();
+
+ // The output of a test delay node with mono and stereo input will be
+ // compared with that of separate mono and stereo delay nodes.
+ const delayTime = 0.3 * bufferSize / ctx.sampleRate;
+ testDelay = ctx.createDelay(delayTime);
+ testDelay.delayTime.value = delayTime;
+ let monoDelay = ctx.createDelay(delayTime);
+ monoDelay.delayTime.value = delayTime;
+ stereoDelay = ctx.createDelay(delayTime);
+ stereoDelay.delayTime.value = delayTime;
+
+ // Create a one-channel signal and connect to the delay nodes
+ var monoOsc = ctx.createOscillator();
+ monoOsc.frequency.value = 110;
+ monoOsc.connect(testDelay);
+ monoOsc.connect(monoDelay);
+ monoOsc.start(0);
+
+ // Invert the expected so that mixing with the test will find the difference.
+ invertor = ctx.createGain();
+ invertor.gain.value = -1.0;
+ monoDelay.connect(invertor);
+ stereoDelay.connect(invertor);
+
+ // Start the test after the delay nodes have begun processing.
+ var processor = ctx.createScriptProcessor(bufferSize, 1, 0);
+ processor.connect(ctx.destination);
+
+ processor.onaudioprocess =
+ function(e) {
+ e.target.onaudioprocess = null;
+ processor.disconnect();
+ startTest();
+ };
+}
+prepareTest();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_delayNodeCycles.html b/dom/media/webaudio/test/test_delayNodeCycles.html
new file mode 100644
index 0000000000..fb4b6c5202
--- /dev/null
+++ b/dom/media/webaudio/test/test_delayNodeCycles.html
@@ -0,0 +1,156 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test the support of cycles.</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+const sampleRate = 48000;
+const inputLength = 2048;
+
+addLoadEvent(function() {
+ function addSine(b) {
+ for (var i = 0; i < b.length; i++) {
+ b[i] += Math.sin(440 * 2 * Math.PI * i / sampleRate);
+ }
+ }
+
+ function getSineBuffer(ctx) {
+ var buffer = ctx.createBuffer(1, inputLength, ctx.sampleRate);
+ addSine(buffer.getChannelData(0));
+ return buffer;
+ }
+
+ function createAndPlayWithCycleAndDelayNode(ctx, delayFrames) {
+ var source = ctx.createBufferSource();
+ source.buffer = getSineBuffer(ctx);
+
+ var gain = ctx.createGain();
+ var delay = ctx.createDelay();
+ delay.delayTime.value = delayFrames/ctx.sampleRate;
+
+ source.connect(gain);
+ gain.connect(delay);
+ delay.connect(ctx.destination);
+ // cycle
+ delay.connect(gain);
+
+ source.start(0);
+ }
+
+ function createAndPlayWithCycleAndNoDelayNode(ctx) {
+ var source = ctx.createBufferSource();
+ source.loop = true;
+ source.buffer = getSineBuffer(ctx);
+
+ var gain = ctx.createGain();
+ var gain2 = ctx.createGain();
+
+ source.connect(gain);
+ gain.connect(gain2);
+ // cycle
+ gain2.connect(gain);
+ gain2.connect(ctx.destination);
+
+ source.start(0);
+ }
+
+ function createAndPlayWithCycleAndNoDelayNodeInCycle(ctx) {
+ var source = ctx.createBufferSource();
+ source.loop = true;
+ source.buffer = getSineBuffer(ctx);
+
+ var delay = ctx.createDelay();
+ var gain = ctx.createGain();
+ var gain2 = ctx.createGain();
+
+ // Their is a cycle, a delay, but the delay is not in the cycle.
+ source.connect(delay);
+ delay.connect(gain);
+ gain.connect(gain2);
+ // cycle
+ gain2.connect(gain);
+ gain2.connect(ctx.destination);
+
+ source.start(0);
+ }
+
+ var remainingTests = 0;
+ function finish() {
+ if (--remainingTests == 0) {
+ SimpleTest.finish();
+ }
+ }
+
+ function getOfflineContext(oncomplete) {
+ var ctx = new OfflineAudioContext(1, sampleRate, sampleRate);
+ ctx.oncomplete = oncomplete;
+ return ctx;
+ }
+
+ function checkSilentBuffer(e) {
+ var buffer = e.renderedBuffer.getChannelData(0);
+ for (var i = 0; i < buffer.length; i++) {
+ if (buffer[i] != 0.0) {
+ ok(false, "buffer should be silent.");
+ finish();
+ return;
+ }
+ }
+ ok(true, "buffer should be silent.");
+ finish();
+ }
+
+ function checkNoisyBuffer(e, aDelayFrames) {
+ let delayFrames = Math.max(128, aDelayFrames);
+
+ var expected = new Float32Array(e.renderedBuffer.length);
+ for (var i = delayFrames; i < expected.length; i += delayFrames) {
+ addSine(expected.subarray(i, i + inputLength));
+ }
+
+ compareChannels(e.renderedBuffer.getChannelData(0), expected);
+ finish();
+ }
+
+ function expectSilentOutput(f) {
+ remainingTests++;
+ var ctx = getOfflineContext(checkSilentBuffer);
+ f(ctx);
+ ctx.startRendering();
+ }
+
+ function expectNoisyOutput(delayFrames) {
+ remainingTests++;
+ var ctx = getOfflineContext();
+ ctx.oncomplete = function(e) { checkNoisyBuffer(e, delayFrames); };
+ createAndPlayWithCycleAndDelayNode(ctx, delayFrames);
+ ctx.startRendering();
+ }
+
+ // This is trying to make a graph with a cycle and no DelayNode in the graph.
+ // The cycle subgraph should be muted, in this graph the output should be silent.
+ expectSilentOutput(createAndPlayWithCycleAndNoDelayNode);
+ // This is trying to make a graph with a cycle and a DelayNode in the graph, but
+ // not part of the cycle.
+ // The cycle subgraph should be muted, in this graph the output should be silent.
+ expectSilentOutput(createAndPlayWithCycleAndNoDelayNodeInCycle);
+ // Those are making legal graphs, with at least one DelayNode in the cycle.
+ // There should be some non-silent output.
+ expectNoisyOutput(sampleRate/4);
+ // DelayNode.delayTime will be clamped to 128/ctx.sampleRate.
+ // There should be some non-silent output.
+ expectNoisyOutput(0);
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_delayNodePassThrough.html b/dom/media/webaudio/test/test_delayNodePassThrough.html
new file mode 100644
index 0000000000..0c2d1db30a
--- /dev/null
+++ b/dom/media/webaudio/test/test_delayNodePassThrough.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test DelayNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script src="webaudio.js" type="text/javascript"></script>
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 4096,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var source = context.createBufferSource();
+
+ var delay = context.createDelay();
+
+ source.buffer = this.buffer;
+
+ source.connect(delay);
+
+ delay.delayTime.value = 0.5;
+
+ // Delay the source stream by 2048 frames
+ delay.delayTime.value = 2048 / context.sampleRate;
+
+ var delayWrapped = SpecialPowers.wrap(delay);
+ ok("passThrough" in delayWrapped, "DelayNode should support the passThrough API");
+ delayWrapped.passThrough = true;
+
+ source.start(0);
+ return delay;
+ },
+ createExpectedBuffers(context) {
+ this.buffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ this.buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+ var silence = context.createBuffer(1, 2048, context.sampleRate);
+
+ return [this.buffer, silence];
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_delayNodeSmallMaxDelay.html b/dom/media/webaudio/test/test_delayNodeSmallMaxDelay.html
new file mode 100644
index 0000000000..235a55a4c8
--- /dev/null
+++ b/dom/media/webaudio/test/test_delayNodeSmallMaxDelay.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test DelayNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script src="webaudio.js" type="text/javascript"></script>
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var source = context.createBufferSource();
+
+ var delay = context.createDelay(0.02);
+
+ source.buffer = this.buffer;
+
+ source.connect(delay);
+
+ source.start(0);
+ return delay;
+ },
+ createExpectedBuffers(context) {
+ var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ expectedBuffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+ this.buffer = expectedBuffer;
+ return expectedBuffer;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_delayNodeTailIncrease.html b/dom/media/webaudio/test/test_delayNodeTailIncrease.html
new file mode 100644
index 0000000000..a511a4a3ff
--- /dev/null
+++ b/dom/media/webaudio/test/test_delayNodeTailIncrease.html
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test increasing delay of DelayNode after input finishes</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+const signalLength = 100;
+const bufferSize = 1024;
+// Delay should be long enough to allow CC to run
+const delayBufferCount = 50;
+const delayLength = delayBufferCount * bufferSize + 700;
+
+var count = 0;
+
+function applySignal(buffer, offset) {
+ for (var i = 0; i < signalLength; ++i) {
+ buffer.getChannelData(0)[offset + i] = Math.cos(Math.PI * i / signalLength);
+ }
+}
+
+function onAudioProcess(e) {
+ switch(count) {
+ case 5:
+ SpecialPowers.forceGC();
+ SpecialPowers.forceCC();
+ break;
+ case delayBufferCount:
+ var offset = delayLength - count * bufferSize;
+ var ctx = e.target.context;
+ var expected = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
+ applySignal(expected, offset);
+ compareBuffers(e.inputBuffer, expected);
+ SimpleTest.finish();
+ }
+ count++;
+}
+
+function startTest() {
+ var ctx = new AudioContext();
+ var processor = ctx.createScriptProcessor(bufferSize, 1, 0);
+ processor.onaudioprocess = onAudioProcess;
+
+ // Switch on delay at a time in the future.
+ var delayDuration = delayLength / ctx.sampleRate;
+ var delayStartTime = (delayLength - bufferSize) / ctx.sampleRate;
+ var delay = ctx.createDelay(delayDuration);
+ delay.delayTime.setValueAtTime(delayDuration, delayStartTime);
+ delay.connect(processor);
+
+ // Short signal that finishes before switching to long delay
+ var buffer = ctx.createBuffer(1, signalLength, ctx.sampleRate);
+ applySignal(buffer, 0);
+ var source = ctx.createBufferSource();
+ source.buffer = buffer;
+ source.start();
+ source.connect(delay);
+};
+
+startTest();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_delayNodeTailWithDisconnect.html b/dom/media/webaudio/test/test_delayNodeTailWithDisconnect.html
new file mode 100644
index 0000000000..c6723f643d
--- /dev/null
+++ b/dom/media/webaudio/test/test_delayNodeTailWithDisconnect.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test tail time lifetime of DelayNode after input is disconnected</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+// Web Audio doesn't provide a means to precisely time disconnect()s but we
+// can test that the output of delay nodes matches the output from their
+// sources before they are disconnected.
+
+SimpleTest.waitForExplicitFinish();
+
+const signalLength = 128;
+const bufferSize = 4096;
+const sourceCount = bufferSize / signalLength;
+// Delay should be long enough to allow CC to run
+var delayBufferCount = 20;
+const delayLength = delayBufferCount * bufferSize;
+
+var sourceOutput = new Float32Array(bufferSize);
+var delayOutputCount = 0;
+var sources = [];
+
+function onDelayOutput(e) {
+ if (delayOutputCount < delayBufferCount) {
+ delayOutputCount++;
+ return;
+ }
+
+ compareChannels(e.inputBuffer.getChannelData(0), sourceOutput);
+ e.target.onaudioprocess = null;
+ SimpleTest.finish();
+}
+
+function onSourceOutput(e) {
+ // Record the first buffer
+ e.inputBuffer.copyFromChannel(sourceOutput, 0);
+ e.target.onaudioprocess = null;
+}
+
+function disconnectSources() {
+ for (var i = 0; i < sourceCount; ++i) {
+ sources[i].disconnect();
+ }
+
+ SpecialPowers.forceGC();
+ SpecialPowers.forceCC();
+}
+
+function startTest() {
+ var ctx = new AudioContext();
+
+ var sourceProcessor = ctx.createScriptProcessor(bufferSize, 1, 0);
+ sourceProcessor.onaudioprocess = onSourceOutput;
+ // Keep audioprocess events going after source disconnect.
+ sourceProcessor.connect(ctx.destination);
+
+ var delayProcessor = ctx.createScriptProcessor(bufferSize, 1, 0);
+ delayProcessor.onaudioprocess = onDelayOutput;
+
+ var delayDuration = delayLength / ctx.sampleRate;
+ for (var i = 0; i < sourceCount; ++i) {
+ var delay = ctx.createDelay(delayDuration);
+ delay.delayTime.value = delayDuration;
+ delay.connect(delayProcessor);
+
+ var source = ctx.createOscillator();
+ source.frequency.value = 440 + 10 * i
+ source.start(i * signalLength / ctx.sampleRate);
+ source.stop((i + 1) * signalLength / ctx.sampleRate);
+ source.connect(delay);
+ source.connect(sourceProcessor);
+
+ sources[i] = source;
+ }
+
+ // Assuming the above Web Audio operations have already scheduled an event
+ // to run in stable state and start the graph thread, schedule a subsequent
+ // event to disconnect the sources, which will remove main thread connection
+ // references before it knows the graph thread has started using the source
+ // streams.
+ SimpleTest.executeSoon(disconnectSources);
+};
+
+startTest();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_delayNodeTailWithGain.html b/dom/media/webaudio/test/test_delayNodeTailWithGain.html
new file mode 100644
index 0000000000..60cca276c0
--- /dev/null
+++ b/dom/media/webaudio/test/test_delayNodeTailWithGain.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test tail time lifetime of DelayNode indirectly connected to source</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+const signalLength = 130;
+const bufferSize = 1024;
+// Delay should be long enough to allow CC to run
+const delayBufferCount = 50;
+const delayLength = delayBufferCount * bufferSize + 700;
+
+var count = 0;
+
+function applySignal(buffer, offset) {
+ for (var i = 0; i < signalLength; ++i) {
+ buffer.getChannelData(0)[offset + i] = Math.cos(Math.PI * i / signalLength);
+ }
+}
+
+function onAudioProcess(e) {
+ switch(count) {
+ case 5:
+ SpecialPowers.forceGC();
+ SpecialPowers.forceCC();
+ break;
+ case delayBufferCount:
+ var offset = delayLength - count * bufferSize;
+ var ctx = e.target.context;
+ var expected = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
+ applySignal(expected, offset);
+ compareBuffers(e.inputBuffer, expected);
+ SimpleTest.finish();
+ }
+ count++;
+}
+
+function startTest() {
+ var ctx = new AudioContext();
+ var processor = ctx.createScriptProcessor(bufferSize, 1, 0);
+ processor.onaudioprocess = onAudioProcess;
+
+ var delayDuration = delayLength / ctx.sampleRate;
+ var delay = ctx.createDelay(delayDuration);
+ delay.delayTime.value = delayDuration;
+ delay.connect(processor);
+
+ var gain = ctx.createGain();
+ gain.connect(delay);
+
+ // Short signal that finishes before garbage collection
+ var buffer = ctx.createBuffer(1, signalLength, ctx.sampleRate);
+ applySignal(buffer, 0);
+ var source = ctx.createBufferSource();
+ source.buffer = buffer;
+ source.start();
+ source.connect(gain);
+};
+
+startTest();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_delayNodeTailWithReconnect.html b/dom/media/webaudio/test/test_delayNodeTailWithReconnect.html
new file mode 100644
index 0000000000..da5f02b052
--- /dev/null
+++ b/dom/media/webaudio/test/test_delayNodeTailWithReconnect.html
@@ -0,0 +1,136 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test tail time lifetime of DelayNode after input finishes and new input added</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+// The buffer source will start on a block boundary, so keeping the signal
+// within one block ensures that it will not cross AudioProcessingEvent buffer
+// boundaries.
+const signalLength = 128;
+const bufferSize = 1024;
+// Delay should be long enough to allow CC to run
+var delayBufferCount = 50;
+var delayBufferOffset;
+const delayLength = delayBufferCount * bufferSize;
+
+var phase = "initial";
+var sourceCount = 0;
+var delayCount = 0;
+var oscillator;
+var delay;
+var source;
+
+function applySignal(buffer, offset) {
+ for (var i = 0; i < signalLength; ++i) {
+ buffer.getChannelData(0)[offset + i] = Math.cos(Math.PI * i / signalLength);
+ }
+}
+
+function bufferIsSilent(buffer, out) {
+ for (var i = 0; i < buffer.length; ++i) {
+ if (buffer.getChannelData(0)[i] != 0) {
+ if (out) {
+ out.soundOffset = i;
+ }
+ return false;
+ }
+ }
+ return true;
+}
+
+function onDelayOutput(e) {
+ switch(phase) {
+
+ case "initial":
+ // Wait for oscillator sound to exit delay
+ if (bufferIsSilent(e.inputBuffer))
+ break;
+
+ phase = "played oscillator";
+ break;
+
+ case "played oscillator":
+ // First tail time has expired. Start second source and remove references
+ // to the delay and connected second source.
+ oscillator.disconnect();
+ source.connect(delay);
+ source.start();
+ source = null;
+ delay = null;
+ phase = "started second source";
+ break;
+
+ case "second tail time":
+ if (delayCount == delayBufferCount) {
+ var ctx = e.target.context;
+ var expected = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
+ applySignal(expected, delayBufferOffset);
+ compareBuffers(e.inputBuffer, expected);
+ e.target.onaudioprocess = null;
+ SimpleTest.finish();
+ }
+ }
+
+ delayCount++;
+}
+
+function onSourceOutput(e) {
+ switch(phase) {
+ case "started second source":
+ var out = {};
+ if (!bufferIsSilent(e.inputBuffer, out)) {
+ delayBufferCount += sourceCount;
+ delayBufferOffset = out.soundOffset;
+ phase = "played second source";
+ }
+ break;
+ case "played second source":
+ SpecialPowers.forceGC();
+ SpecialPowers.forceCC();
+ phase = "second tail time";
+ e.target.onaudioprocess = null;
+ }
+
+ sourceCount++;
+}
+
+function startTest() {
+ var ctx = new AudioContext();
+ var delayDuration = delayLength / ctx.sampleRate;
+ delay = ctx.createDelay(delayDuration);
+ delay.delayTime.value = delayDuration;
+ var processor1 = ctx.createScriptProcessor(bufferSize, 1, 0);
+ delay.connect(processor1);
+ processor1.onaudioprocess = onDelayOutput;
+
+ // Signal to trigger initial tail time reference
+ oscillator = ctx.createOscillator();
+ oscillator.start(0);
+ oscillator.stop(100/ctx.sampleRate);
+ oscillator.connect(delay);
+
+ // Short signal, not started yet, with a ScriptProcessor to detect when it
+ // starts. It should finish before garbage collection.
+ var buffer = ctx.createBuffer(1, signalLength, ctx.sampleRate);
+ applySignal(buffer, 0);
+ source = ctx.createBufferSource();
+ source.buffer = buffer;
+ var processor2 = ctx.createScriptProcessor(bufferSize, 1, 0);
+ source.connect(processor2);
+ processor2.onaudioprocess = onSourceOutput;
+};
+
+startTest();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_delayNodeWithGain.html b/dom/media/webaudio/test/test_delayNodeWithGain.html
new file mode 100644
index 0000000000..af075c7439
--- /dev/null
+++ b/dom/media/webaudio/test/test_delayNodeWithGain.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test DelayNode with a GainNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script src="webaudio.js" type="text/javascript"></script>
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 4096,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var buffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+
+ var source = context.createBufferSource();
+
+ var delay = context.createDelay();
+
+ source.buffer = buffer;
+
+ var gain = context.createGain();
+ gain.gain.value = 0.5;
+
+ source.connect(gain);
+ gain.connect(delay);
+
+ // Delay the source stream by 2048 frames
+ delay.delayTime.value = 2048 / context.sampleRate;
+
+ source.start(0);
+ return delay;
+ },
+ createExpectedBuffers(context) {
+ var expectedBuffer = context.createBuffer(1, 2048 * 2, context.sampleRate);
+ for (var i = 2048; i < 2048 * 2; ++i) {
+ expectedBuffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * (i - 2048) / context.sampleRate) / 2;
+ }
+ return expectedBuffer;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_delaynode-channel-count-1.html b/dom/media/webaudio/test/test_delaynode-channel-count-1.html
new file mode 100644
index 0000000000..dd964ef9e3
--- /dev/null
+++ b/dom/media/webaudio/test/test_delaynode-channel-count-1.html
@@ -0,0 +1,104 @@
+<!DOCTYPE html>
+<title>Test that DelayNode output channelCount matches that of the delayed input</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// See https://github.com/WebAudio/web-audio-api/issues/25
+
+// sampleRate is a power of two so that delay times are exact in base-2
+// floating point arithmetic.
+const SAMPLE_RATE = 32768;
+// Arbitrary delay time in frames (but this is assumed a multiple of block
+// size below):
+const DELAY_FRAMES = 3 * 128;
+// Implementations may apply interpolation to input samples, which can spread
+// the effect of input with larger channel counts over neighbouring blocks.
+// This test ignores enough neighbouring blocks to ignore the effects of
+// filter radius of up to this number of frames:
+const INTERPOLATION_GRACE = 128;
+// Number of frames of DelayNode output that are known to be stereo:
+const STEREO_FRAMES = 128;
+// The delay will be increased at this frame to switch DelayNode output back
+// to mono.
+const MONO_OUTPUT_START_FRAME =
+ DELAY_FRAMES + INTERPOLATION_GRACE + STEREO_FRAMES;
+// Number of frames of output that are known to be mono after the known stereo
+// and interpolation grace.
+const MONO_FRAMES = 128;
+// Total length allows for interpolation after effects of stereo input are
+// finished and one block to test return to mono output:
+const TOTAL_LENGTH =
+ MONO_OUTPUT_START_FRAME + INTERPOLATION_GRACE + MONO_FRAMES;
+// maxDelayTime, is a multiple of block size, because the Gecko implementation
+// once had a bug with delayTime = maxDelayTime in this situation:
+const MAX_DELAY_FRAMES = TOTAL_LENGTH + INTERPOLATION_GRACE;
+
+promise_test(() => {
+ let context = new OfflineAudioContext({numberOfChannels: 1,
+ length: TOTAL_LENGTH,
+ sampleRate: SAMPLE_RATE});
+
+ // Only channel 1 of the splitter is connected to the destination.
+ let splitter = new ChannelSplitterNode(context, {numberOfOutputs: 2});
+ splitter.connect(context.destination, 1);
+
+ // A gain node has channelCountMode "max" and channelInterpretation
+ // "speakers", and so will up-mix a mono input when there is stereo input.
+ let gain = new GainNode(context);
+ gain.connect(splitter);
+
+ // The delay node initially outputs a single channel of silence, when it
+ // does not have enough signal in its history to output what it has
+ // previously received. After the delay period, it will then output the
+ // stereo signal it received.
+ let delay =
+ new DelayNode(context,
+ {maxDelayTime: MAX_DELAY_FRAMES / context.sampleRate,
+ delayTime: DELAY_FRAMES / context.sampleRate});
+ // Schedule an increase in the delay to return to mono silent output from
+ // the unfilled portion of the DelayNode's buffer.
+ delay.delayTime.setValueAtTime(MAX_DELAY_FRAMES / context.sampleRate,
+ MONO_OUTPUT_START_FRAME / context.sampleRate);
+ delay.connect(gain);
+
+ let stereoMerger = new ChannelMergerNode(context, {numberOfInputs: 2});
+ stereoMerger.connect(delay);
+
+ let leftOffset = 0.125;
+ let rightOffset = 0.5;
+ let leftSource = new ConstantSourceNode(context, {offset: leftOffset});
+ let rightSource = new ConstantSourceNode(context, {offset: rightOffset});
+ leftSource.start();
+ rightSource.start();
+ leftSource.connect(stereoMerger, 0, 0);
+ rightSource.connect(stereoMerger, 0, 1);
+ // Connect a mono source directly to the gain, so that even stereo silence
+ // will be detected in channel 1 of the gain output because it will cause
+ // the mono source to be up-mixed.
+ let monoOffset = 0.25
+ let monoSource = new ConstantSourceNode(context, {offset: monoOffset});
+ monoSource.start();
+ monoSource.connect(gain);
+
+ return context.startRendering().
+ then((buffer) => {
+ let output = buffer.getChannelData(0);
+
+ function assert_samples_equal(startIndex, length, expected, description)
+ {
+ for (let i = startIndex; i < startIndex + length; ++i) {
+ assert_equals(output[i], expected, description + ` at ${i}`);
+ }
+ }
+
+ assert_samples_equal(0, DELAY_FRAMES - INTERPOLATION_GRACE,
+ 0, "Initial mono");
+ assert_samples_equal(DELAY_FRAMES + INTERPOLATION_GRACE, STEREO_FRAMES,
+ monoOffset + rightOffset, "Stereo");
+ assert_samples_equal(MONO_OUTPUT_START_FRAME + INTERPOLATION_GRACE,
+ MONO_FRAMES,
+ 0, "Final mono");
+ });
+});
+
+</script>
diff --git a/dom/media/webaudio/test/test_disconnectAll.html b/dom/media/webaudio/test/test_disconnectAll.html
new file mode 100644
index 0000000000..f67b969949
--- /dev/null
+++ b/dom/media/webaudio/test/test_disconnectAll.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test whether we can disconnect an AudioNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ </head>
+ <body>
+ <pre id="test">
+ <script class="testbody" type="text/javascript">
+ var gTest = {
+ length: 256,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var sourceBuffer = context.createBuffer(1, 256, context.sampleRate);
+ var data = sourceBuffer.getChannelData(0);
+ for (var j = 0; j < data.length; j++) {
+ data[j] = 1;
+ }
+
+ var source = context.createBufferSource();
+ source.buffer = sourceBuffer;
+
+ var source = context.createBufferSource();
+ source.buffer = sourceBuffer;
+
+ var gain1 = context.createGain();
+ var gain2 = context.createGain();
+ var gain3 = context.createGain();
+ var merger = context.createChannelMerger(3);
+
+ source.connect(gain1);
+ source.connect(gain2);
+ source.connect(gain3);
+ gain1.connect(merger);
+ gain2.connect(merger);
+ gain3.connect(merger);
+ source.start();
+
+ source.disconnect();
+
+ return merger;
+ }
+ };
+
+ runTest();
+ </script>
+ </pre>
+ </body>
+</html> \ No newline at end of file
diff --git a/dom/media/webaudio/test/test_disconnectAudioParam.html b/dom/media/webaudio/test/test_disconnectAudioParam.html
new file mode 100644
index 0000000000..87df902b8e
--- /dev/null
+++ b/dom/media/webaudio/test/test_disconnectAudioParam.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test whether we can disconnect an AudioParam</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ </head>
+ <body>
+ <pre id="test">
+ <script class="testbody" type="text/javascript">
+ var gTest = {
+ length: 256,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var sourceBuffer = context.createBuffer(1, 256, context.sampleRate);
+ var data = sourceBuffer.getChannelData(0);
+ for (var j = 0; j < data.length; j++) {
+ data[j] = 1;
+ }
+
+ var source = context.createBufferSource();
+ source.buffer = sourceBuffer;
+
+ var half = context.createGain();
+ var gain1 = context.createGain();
+ var gain2 = context.createGain();
+
+ half.gain.value = 0.5;
+
+ source.connect(gain1);
+ gain1.connect(gain2);
+ source.connect(half);
+
+ half.connect(gain1.gain);
+ half.connect(gain2.gain);
+
+ half.disconnect(gain2.gain);
+
+ source.start();
+
+ return gain2;
+ },
+ createExpectedBuffers(context) {
+ let expectedBuffer = context.createBuffer(1, 256, context.sampleRate);
+ for (var i = 0; i < 256; ++i) {
+ expectedBuffer.getChannelData(0)[i] = 1.5;
+ }
+
+ return expectedBuffer;
+ }
+ };
+
+ runTest();
+ </script>
+ </pre>
+ </body>
+</html>
diff --git a/dom/media/webaudio/test/test_disconnectAudioParamFromOutput.html b/dom/media/webaudio/test/test_disconnectAudioParamFromOutput.html
new file mode 100644
index 0000000000..533091f920
--- /dev/null
+++ b/dom/media/webaudio/test/test_disconnectAudioParamFromOutput.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test whether we can disconnect an AudioParam</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ </head>
+ <body>
+ <pre id="test">
+ <script class="testbody" type="text/javascript">
+ var gTest = {
+ length: 256,
+ numberOfChannels: 2,
+ createGraph(context) {
+ var sourceBuffer = context.createBuffer(2, 256, context.sampleRate);
+ for (var i = 1; i <= 2; i++) {
+ var data = sourceBuffer.getChannelData(i-1);
+ for (var j = 0; j < data.length; j++) {
+ data[j] = i;
+ }
+ }
+
+ var source = context.createBufferSource();
+ source.buffer = sourceBuffer;
+
+ var source = context.createBufferSource();
+ source.buffer = sourceBuffer;
+
+ var half = context.createGain();
+ var gain1 = context.createGain();
+ var gain2 = context.createGain();
+ var splitter = context.createChannelSplitter(2);
+
+ half.gain.value = 0.5;
+
+ source.connect(gain1);
+ gain1.connect(gain2);
+ source.connect(half);
+ half.connect(splitter);
+ splitter.connect(gain1.gain, 0);
+ splitter.connect(gain2.gain, 1);
+
+ splitter.disconnect(gain2.gain, 1);
+
+ source.start();
+
+ return gain2;
+ },
+ createExpectedBuffers(context) {
+ var expectedBuffer = context.createBuffer(2, 256, context.sampleRate);
+ for (var i = 1; i <= 2; i++) {
+ var data = expectedBuffer.getChannelData(i-1);
+ for (var j = 0; j < data.length; j++) {
+ data[j] = (i == 1) ? 1.5 : 3.0;
+ }
+ }
+
+ return expectedBuffer;
+ }
+ };
+
+ runTest();
+ </script>
+ </pre>
+ </body>
+</html>
diff --git a/dom/media/webaudio/test/test_disconnectExceptions.html b/dom/media/webaudio/test/test_disconnectExceptions.html
new file mode 100644
index 0000000000..54fde4df8d
--- /dev/null
+++ b/dom/media/webaudio/test/test_disconnectExceptions.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test whether we can disconnect an AudioNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ </head>
+ <body>
+ <pre id="test">
+ <script class="testbody" type="text/javascript">
+ var ctx = new AudioContext();
+ var sourceBuffer = ctx.createBuffer(2, 256, ctx.sampleRate);
+ for (var i = 1; i <= 2; i++) {
+ var data = sourceBuffer.getChannelData(i-1);
+ for (var j = 0; j < data.length; j++) {
+ data[j] = i;
+ }
+ }
+
+ var source = ctx.createBufferSource();
+ source.buffer = sourceBuffer;
+
+ var gain1 = ctx.createGain();
+ var splitter = ctx.createChannelSplitter(2);
+ var merger = ctx.createChannelMerger(2);
+ var gain2 = ctx.createGain();
+ var gain3 = ctx.createGain();
+
+ gain1.connect(splitter);
+ splitter.connect(gain2, 0);
+ splitter.connect(gain3, 1);
+ splitter.connect(merger, 0, 0);
+ splitter.connect(merger, 1, 1);
+ gain2.connect(gain3);
+ gain3.connect(ctx.destination);
+ merger.connect(ctx.destination);
+
+ expectException(function() {
+ splitter.disconnect(2);
+ }, DOMException.INDEX_SIZE_ERR);
+
+ expectNoException(function() {
+ splitter.disconnect(1);
+ splitter.disconnect(1);
+ });
+
+ expectException(function() {
+ gain1.disconnect(gain2);
+ }, DOMException.INVALID_ACCESS_ERR);
+
+ expectException(function() {
+ gain1.disconnect(gain3);
+ ok(false, 'Should get InvalidAccessError exception');
+ }, DOMException.INVALID_ACCESS_ERR);
+
+ expectException(function() {
+ splitter.disconnect(gain2, 2);
+ }, DOMException.INDEX_SIZE_ERR);
+
+ expectException(function() {
+ splitter.disconnect(gain1, 0);
+ }, DOMException.INVALID_ACCESS_ERR);
+
+ expectException(function() {
+ splitter.disconnect(gain3, 0, 0);
+ }, DOMException.INVALID_ACCESS_ERR);
+
+ expectException(function() {
+ splitter.disconnect(merger, 3, 0);
+ }, DOMException.INDEX_SIZE_ERR);
+ </script>
+ </pre>
+ </body>
+</html>
diff --git a/dom/media/webaudio/test/test_disconnectFromAudioNode.html b/dom/media/webaudio/test/test_disconnectFromAudioNode.html
new file mode 100644
index 0000000000..5f80bbfbe8
--- /dev/null
+++ b/dom/media/webaudio/test/test_disconnectFromAudioNode.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test whether we can disconnect an AudioNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ </head>
+ <body>
+ <pre id="test">
+ <script class="testbody" type="text/javascript">
+ var gTest = {
+ length: 256,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var sourceBuffer = context.createBuffer(1, 256, context.sampleRate);
+ var data = sourceBuffer.getChannelData(0);
+ for (var j = 0; j < data.length; j++) {
+ data[j] = 1;
+ }
+
+ var source = context.createBufferSource();
+ source.buffer = sourceBuffer;
+
+ var gain1 = context.createGain();
+ var gain2 = context.createGain();
+ var gain3 = context.createGain();
+
+ source.connect(gain1);
+ source.connect(gain2);
+
+ gain1.connect(gain3);
+ gain2.connect(gain3);
+
+ source.start();
+
+ source.disconnect(gain2);
+
+ return gain3;
+ },
+ createExpectedBuffers(context) {
+ let expectedBuffer = context.createBuffer(1, 256, context.sampleRate);
+ for (var i = 0; i < 256; ++i) {
+ expectedBuffer.getChannelData(0)[i] = 1.0;
+ }
+
+ return expectedBuffer;
+ }
+ };
+
+ runTest();
+ </script>
+ </pre>
+ </body>
+</html>
diff --git a/dom/media/webaudio/test/test_disconnectFromAudioNodeAndOutput.html b/dom/media/webaudio/test/test_disconnectFromAudioNodeAndOutput.html
new file mode 100644
index 0000000000..a0812bdb09
--- /dev/null
+++ b/dom/media/webaudio/test/test_disconnectFromAudioNodeAndOutput.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test whether we can disconnect an AudioNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ </head>
+ <body>
+ <pre id="test">
+ <script class="testbody" type="text/javascript">
+ var gTest = {
+ length: 256,
+ numberOfChannels: 2,
+ createGraph(context) {
+ var sourceBuffer = context.createBuffer(2, 256, context.sampleRate);
+ for (var i = 1; i <= 2; i++) {
+ var data = sourceBuffer.getChannelData(i-1);
+ for (var j = 0; j < data.length; j++) {
+ data[j] = i;
+ }
+ }
+
+ var source = context.createBufferSource();
+ source.buffer = sourceBuffer;
+
+ var splitter = context.createChannelSplitter(2);
+ var gain1 = context.createGain();
+ var gain2 = context.createGain();
+ var merger = context.createChannelMerger(2);
+
+ source.connect(splitter);
+ splitter.connect(gain1, 0);
+ splitter.connect(gain2, 0);
+ splitter.connect(gain2, 1);
+ gain1.connect(merger, 0, 1);
+ gain2.connect(merger, 0, 1);
+ source.start();
+
+ splitter.disconnect(gain2, 0);
+
+ return merger;
+ },
+ createExpectedBuffers(context) {
+ let expectedBuffer = context.createBuffer(2, 256, context.sampleRate);
+ for (var i = 0; i < 256; ++i) {
+ expectedBuffer.getChannelData(0)[i] = 0;
+ expectedBuffer.getChannelData(1)[i] = 3;
+ }
+
+ return expectedBuffer;
+ }
+ };
+
+ runTest();
+ </script>
+ </pre>
+ </body>
+</html>
diff --git a/dom/media/webaudio/test/test_disconnectFromAudioNodeAndOutputAndInput.html b/dom/media/webaudio/test/test_disconnectFromAudioNodeAndOutputAndInput.html
new file mode 100644
index 0000000000..478768c62d
--- /dev/null
+++ b/dom/media/webaudio/test/test_disconnectFromAudioNodeAndOutputAndInput.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test whether we can disconnect an AudioNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ </head>
+ <body>
+ <pre id="test">
+ <script class="testbody" type="text/javascript">
+ var gTest = {
+ length: 256,
+ numberOfChannels: 3,
+ createGraph(context) {
+ var sourceBuffer = context.createBuffer(3, 256, context.sampleRate);
+ for (var i = 1; i <= 3; i++) {
+ var data = sourceBuffer.getChannelData(i-1);
+ for (var j = 0; j < data.length; j++) {
+ data[j] = i;
+ }
+ }
+
+ var source = context.createBufferSource();
+ source.buffer = sourceBuffer;
+
+ var splitter = context.createChannelSplitter(3);
+ var merger = context.createChannelMerger(3);
+
+ source.connect(splitter);
+ splitter.connect(merger, 0, 0);
+ splitter.connect(merger, 1, 1);
+ splitter.connect(merger, 2, 2);
+ source.start();
+
+ splitter.disconnect(merger, 2, 2);
+
+ return merger;
+ },
+ createExpectedBuffers(context) {
+ var expectedBuffer = context.createBuffer(3, 256, context.sampleRate);
+ for (var i = 1; i <= 3; i++) {
+ var data = expectedBuffer.getChannelData(i-1);
+ for (var j = 0; j < data.length; j++) {
+ data[j] = (i == 3) ? 0 : i;
+ }
+ }
+
+ return expectedBuffer;
+ }
+ };
+
+ runTest();
+ </script>
+ </pre>
+ </body>
+</html> \ No newline at end of file
diff --git a/dom/media/webaudio/test/test_disconnectFromAudioNodeMultipleConnection.html b/dom/media/webaudio/test/test_disconnectFromAudioNodeMultipleConnection.html
new file mode 100644
index 0000000000..9bd8e4d6f6
--- /dev/null
+++ b/dom/media/webaudio/test/test_disconnectFromAudioNodeMultipleConnection.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>
+ Test whether we can disconnect all outbound connection of an AudioNode
+ </title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ </head>
+ <body>
+ <pre id="test">
+ <script class="testbody" type="text/javascript">
+ var gTest = {
+ length: 256,
+ numberOfChannels: 2,
+ createGraph(context) {
+ var sourceBuffer = context.createBuffer(1, 256, context.sampleRate);
+ var data = sourceBuffer.getChannelData(0);
+ for (var j = 0; j < data.length; j++) {
+ data[j] = 1;
+ }
+
+ var source = context.createBufferSource();
+ source.buffer = sourceBuffer;
+
+ var merger = context.createChannelMerger(2);
+ var gain = context.createGain();
+
+ source.connect(merger, 0, 0);
+ source.connect(gain);
+ source.connect(merger, 0, 1);
+
+ source.disconnect(merger);
+
+ source.start();
+
+ return merger;
+ },
+ createExpectedBuffers(context) {
+ let expectedBuffer = context.createBuffer(2, 256, context.sampleRate);
+ for (var channel = 0; channel < 2; channel++) {
+ for (var i = 0; i < 256; ++i) {
+ expectedBuffer.getChannelData(0)[i] = 0;
+ }
+ }
+
+ return expectedBuffer;
+ }
+ };
+
+ runTest();
+ </script>
+ </pre>
+ </body>
+</html>
diff --git a/dom/media/webaudio/test/test_disconnectFromOutput.html b/dom/media/webaudio/test/test_disconnectFromOutput.html
new file mode 100644
index 0000000000..35080bf609
--- /dev/null
+++ b/dom/media/webaudio/test/test_disconnectFromOutput.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <title>Test whether we can disconnect an AudioNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ </head>
+ <body>
+ <pre id="test">
+ <script class="testbody" type="text/javascript">
+ var gTest = {
+ length: 256,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var sourceBuffer = context.createBuffer(3, 256, context.sampleRate);
+ for (var i = 1; i <= 3; i++) {
+ var data = sourceBuffer.getChannelData(i-1);
+ for (var j = 0; j < data.length; j++) {
+ data[j] = i;
+ }
+ }
+
+ var source = context.createBufferSource();
+ source.buffer = sourceBuffer;
+
+ var splitter = context.createChannelSplitter(3);
+ var sum = context.createGain();
+
+ source.connect(splitter);
+ splitter.connect(sum, 0);
+ splitter.connect(sum, 1);
+ splitter.connect(sum, 2);
+ source.start();
+
+ splitter.disconnect(1);
+
+ return sum;
+ },
+ createExpectedBuffers(context) {
+ let expectedBuffer = context.createBuffer(1, 256, context.sampleRate);
+ for (var i = 0; i < 256; ++i) {
+ expectedBuffer.getChannelData(0)[i] = 4;
+ }
+
+ return expectedBuffer;
+ },
+ };
+
+ runTest();
+ </script>
+ </pre>
+ </body>
+</html>
diff --git a/dom/media/webaudio/test/test_dynamicsCompressorNode.html b/dom/media/webaudio/test/test_dynamicsCompressorNode.html
new file mode 100644
index 0000000000..05b6887a53
--- /dev/null
+++ b/dom/media/webaudio/test/test_dynamicsCompressorNode.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test DynamicsCompressorNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function near(a, b, msg) {
+ ok(Math.abs(a - b) < 1e-4, msg);
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var context = new AudioContext();
+
+ var osc = context.createOscillator();
+ var sp = context.createScriptProcessor();
+
+ var compressor = new DynamicsCompressorNode(context);
+
+ osc.connect(compressor);
+ osc.connect(sp);
+ compressor.connect(context.destination);
+
+ is(compressor.channelCount, 2, "compressor node has 2 input channels by default");
+ is(compressor.channelCountMode, "clamped-max", "Correct channelCountMode for the compressor node");
+ is(compressor.channelInterpretation, "speakers", "Correct channelCountInterpretation for the compressor node");
+
+ // Verify default values
+ ok(compressor.threshold instanceof AudioParam, "treshold is an AudioParam");
+ near(compressor.threshold.defaultValue, -24, "Correct default value for threshold");
+ ok(compressor.knee instanceof AudioParam, "knee is an AudioParam");
+ near(compressor.knee.defaultValue, 30, "Correct default value for knee");
+ ok(compressor.ratio instanceof AudioParam, "knee is an AudioParam");
+ near(compressor.ratio.defaultValue, 12, "Correct default value for ratio");
+ is(typeof compressor.reduction, "number", "reduction is a number");
+ near(compressor.reduction, 0, "Correct default value for reduction");
+ ok(compressor.attack instanceof AudioParam, "attack is an AudioParam");
+ near(compressor.attack.defaultValue, 0.003, "Correct default value for attack");
+ ok(compressor.release instanceof AudioParam, "release is an AudioParam");
+ near(compressor.release.defaultValue, 0.25, "Correct default value for release");
+
+ compressor.threshold.value = -80;
+
+ osc.start();
+ var iteration = 0;
+ sp.onaudioprocess = function(e) {
+ if (iteration > 10) {
+ ok(compressor.reduction < 0,
+ "Feeding a full-scale sine to a compressor should result in an db" +
+ "reduction.");
+ sp.onaudioprocess = null;
+ osc.stop(0);
+
+ SimpleTest.finish();
+ }
+ iteration++;
+ }
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_dynamicsCompressorNodePassThrough.html b/dom/media/webaudio/test/test_dynamicsCompressorNodePassThrough.html
new file mode 100644
index 0000000000..9e8d794547
--- /dev/null
+++ b/dom/media/webaudio/test/test_dynamicsCompressorNodePassThrough.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test DynamicsCompressorNode with passthrough</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var source = context.createBufferSource();
+
+ var compressor = context.createDynamicsCompressor();
+
+ source.buffer = this.buffer;
+
+ source.connect(compressor);
+
+ var compressorWrapped = SpecialPowers.wrap(compressor);
+ ok("passThrough" in compressorWrapped, "DynamicsCompressorNode should support the passThrough API");
+ compressorWrapped.passThrough = true;
+
+ source.start(0);
+ return compressor;
+ },
+ createExpectedBuffers(context) {
+ this.buffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ this.buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+
+ return [this.buffer];
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_dynamicsCompressorNodeWithGain.html b/dom/media/webaudio/test/test_dynamicsCompressorNodeWithGain.html
new file mode 100644
index 0000000000..7e6487e3f8
--- /dev/null
+++ b/dom/media/webaudio/test/test_dynamicsCompressorNodeWithGain.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+ <title>Test DynamicsCompressor with Gain</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ var samplerate = 44100;
+ var context = new OfflineAudioContext(1, samplerate/100, samplerate);
+
+ var osc = context.createOscillator();
+ osc.frequency.value = 2400;
+
+ var gain = context.createGain();
+ gain.gain.value = 1.5;
+
+ // These numbers are borrowed from the example code on MDN
+ // https://developer.mozilla.org/en-US/docs/Web/API/DynamicsCompressorNode
+ var compressor = context.createDynamicsCompressor();
+ compressor.threshold.value = -50;
+ compressor.knee.value = 40;
+ compressor.ratio.value = 12;
+ compressor.reduction.value = -20;
+ compressor.attack.value = 0;
+ compressor.release.value = 0.25;
+
+ osc.connect(gain);
+ gain.connect(compressor);
+ compressor.connect(context.destination);
+ osc.start();
+
+ context.startRendering().then(buffer => {
+ var peak = Math.max(...buffer.getChannelData(0));
+ console.log(peak);
+ // These values are experimentally determined. Without dynamics compression
+ // the peak should be just under 1.5. We also check for a minimum value
+ // to make sure we are not getting all zeros.
+ ok(peak >= 0.2 && peak < 1.0, "Peak value should be greater than 0.25 and less than 1.0");
+ SimpleTest.finish();
+ });
+});
+</script>
+<pre>
+</pre>
+</body>
diff --git a/dom/media/webaudio/test/test_event_listener_leaks.html b/dom/media/webaudio/test/test_event_listener_leaks.html
new file mode 100644
index 0000000000..a3bcc9259e
--- /dev/null
+++ b/dom/media/webaudio/test/test_event_listener_leaks.html
@@ -0,0 +1,47 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Bug 1450358 - Test AudioContext event listener leak conditions</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/dom/events/test/event_leak_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+// Manipulate AudioContext objects in the frame's context.
+// Its important here that we create a listener callback from
+// the DOM objects back to the frame's global in order to
+// exercise the leak condition.
+async function useAudioContext(contentWindow) {
+ let ctx = new contentWindow.AudioContext();
+ ctx.onstatechange = e => {
+ contentWindow.stateChangeCount += 1;
+ };
+
+ let osc = ctx.createOscillator();
+ osc.type = "sine";
+ osc.frequency.value = 440;
+ osc.start();
+}
+
+async function runTest() {
+ try {
+ await checkForEventListenerLeaks("AudioContext", useAudioContext);
+ } catch (e) {
+ ok(false, e);
+ } finally {
+ SimpleTest.finish();
+ }
+}
+
+SimpleTest.waitForExplicitFinish();
+addEventListener("load", runTest, { once: true });
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/dom/media/webaudio/test/test_gainNode.html b/dom/media/webaudio/test/test_gainNode.html
new file mode 100644
index 0000000000..77b0ae88b0
--- /dev/null
+++ b/dom/media/webaudio/test/test_gainNode.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test GainNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 1,
+ createGraph(context) {
+
+ var gain = new GainNode(context);
+ ok(gain.gain, "The audioparam member must exist");
+ is(gain.gain.value, 1.0, "Correct initial value");
+ is(gain.gain.defaultValue, 1.0, "Correct default value");
+ gain.gain.value = 0.5;
+ is(gain.gain.value, 0.5, "Correct initial value");
+ is(gain.gain.defaultValue, 1.0, "Correct default value");
+
+ gain = new GainNode(context, { gain: 0.5 });
+ ok(gain.gain, "The audioparam member must exist");
+ is(gain.gain.value, 0.5, "Correct initial value");
+ is(gain.gain.defaultValue, 1.0, "Correct default value");
+ gain.gain.value = 0.5;
+ is(gain.gain.value, 0.5, "Correct initial value");
+ is(gain.gain.defaultValue, 1.0, "Correct default value");
+
+ var buffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+
+ var source = context.createBufferSource();
+ source.buffer = buffer;
+
+ gain = context.createGain();
+ source.connect(gain);
+
+ ok(gain.gain, "The audioparam member must exist");
+ is(gain.gain.value, 1.0, "Correct initial value");
+ is(gain.gain.defaultValue, 1.0, "Correct default value");
+ gain.gain.value = 0.5;
+ is(gain.gain.value, 0.5, "Correct initial value");
+ is(gain.gain.defaultValue, 1.0, "Correct default value");
+ is(gain.channelCount, 2, "gain node has 2 input channels by default");
+ is(gain.channelCountMode, "max", "Correct channelCountMode for the gain node");
+ is(gain.channelInterpretation, "speakers", "Correct channelCountInterpretation for the gain node");
+
+ source.start(0);
+ return gain;
+ },
+ createExpectedBuffers(context) {
+ var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ expectedBuffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate) / 2;
+ }
+ return expectedBuffer;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_gainNodeInLoop.html b/dom/media/webaudio/test/test_gainNodeInLoop.html
new file mode 100644
index 0000000000..90dedc0ef4
--- /dev/null
+++ b/dom/media/webaudio/test/test_gainNodeInLoop.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test GainNode in presence of loops</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 4096,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var sourceBuffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ sourceBuffer.getChannelData(0)[i] = 1;
+ }
+
+ var source = context.createBufferSource();
+ source.buffer = sourceBuffer;
+ source.loop = true;
+ source.start(0);
+ source.stop(sourceBuffer.duration * 2);
+
+ var gain = context.createGain();
+ // Adjust the gain in a way that we don't just end up modifying AudioChunk::mVolume
+ gain.gain.setValueAtTime(0.5, 0);
+ source.connect(gain);
+ return gain;
+ },
+ createExpectedBuffers(context) {
+ var expectedBuffer = context.createBuffer(1, 4096, context.sampleRate);
+ for (var i = 0; i < 4096; ++i) {
+ expectedBuffer.getChannelData(0)[i] = 0.5;
+ }
+ return expectedBuffer;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_gainNodePassThrough.html b/dom/media/webaudio/test/test_gainNodePassThrough.html
new file mode 100644
index 0000000000..5a973ccaa2
--- /dev/null
+++ b/dom/media/webaudio/test/test_gainNodePassThrough.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test GainNode with passthrough</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var source = context.createBufferSource();
+
+ var gain = context.createGain();
+
+ source.buffer = this.buffer;
+
+ source.connect(gain);
+
+ gain.gain.value = 0.5;
+
+ var gainWrapped = SpecialPowers.wrap(gain);
+ ok("passThrough" in gainWrapped, "GainNode should support the passThrough API");
+ gainWrapped.passThrough = true;
+
+ source.start(0);
+ return gain;
+ },
+ createExpectedBuffers(context) {
+ this.buffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ this.buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+
+ return [this.buffer];
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_iirFilterNodePassThrough.html b/dom/media/webaudio/test/test_iirFilterNodePassThrough.html
new file mode 100644
index 0000000000..ab54499e04
--- /dev/null
+++ b/dom/media/webaudio/test/test_iirFilterNodePassThrough.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test IIRFilterNode with passthrough</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var source = context.createBufferSource();
+
+ var filter = context.createIIRFilter([0.5, 0.5], [1.0]);
+
+ source.buffer = this.buffer;
+
+ source.connect(filter);
+
+ var filterWrapped = SpecialPowers.wrap(filter);
+ ok("passThrough" in filterWrapped, "BiquadFilterNode should support the passThrough API");
+ filterWrapped.passThrough = true;
+
+ source.start(0);
+ return filter;
+ },
+ createExpectedBuffers(context) {
+ this.buffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ this.buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+
+ return [this.buffer];
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_maxChannelCount.html b/dom/media/webaudio/test/test_maxChannelCount.html
new file mode 100644
index 0000000000..1a4e5c856e
--- /dev/null
+++ b/dom/media/webaudio/test/test_maxChannelCount.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test the AudioContext.destination interface</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+// Work around bug 911777
+SpecialPowers.forceGC();
+SpecialPowers.forceCC();
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var ac = new AudioContext();
+ ok(ac.destination.maxChannelCount > 0, "We can query the maximum number of channels");
+
+ var oac = new OfflineAudioContext(2, 1024, 48000);
+ is(oac.destination.maxChannelCount, 2, "This OfflineAudioContext should have 2 max channels.");
+
+ oac = new OfflineAudioContext(6, 1024, 48000);
+ is(oac.destination.maxChannelCount, 6, "This OfflineAudioContext should have 6 max channels.");
+
+ expectException(function() {
+ oac.destination.channelCount = oac.destination.channelCount + 1;
+ }, DOMException.INDEX_SIZE_ERR);
+
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_mediaDecoding.html b/dom/media/webaudio/test/test_mediaDecoding.html
new file mode 100644
index 0000000000..e76a533e4a
--- /dev/null
+++ b/dom/media/webaudio/test/test_mediaDecoding.html
@@ -0,0 +1,436 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Test the decodeAudioData API and Resampling</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ </head>
+
+ <body>
+ <pre id="test">
+<script src="webaudio.js" type="text/javascript"></script>
+<script type="text/javascript">
+
+ // These routines have been copied verbatim from WebKit, and are used in order
+ // to convert a memory buffer into a wave buffer.
+ function writeString(s, a, offset) {
+ for (var i = 0; i < s.length; ++i) {
+ a[offset + i] = s.charCodeAt(i);
+ }
+ }
+
+ function writeInt16(n, a, offset) {
+ n = Math.floor(n);
+
+ var b1 = n & 255;
+ var b2 = (n >> 8) & 255;
+
+ a[offset + 0] = b1;
+ a[offset + 1] = b2;
+ }
+
+ function writeInt32(n, a, offset) {
+ n = Math.floor(n);
+ var b1 = n & 255;
+ var b2 = (n >> 8) & 255;
+ var b3 = (n >> 16) & 255;
+ var b4 = (n >> 24) & 255;
+
+ a[offset + 0] = b1;
+ a[offset + 1] = b2;
+ a[offset + 2] = b3;
+ a[offset + 3] = b4;
+ }
+
+ function writeAudioBuffer(audioBuffer, a, offset) {
+ var n = audioBuffer.length;
+ var channels = audioBuffer.numberOfChannels;
+
+ for (var i = 0; i < n; ++i) {
+ for (var k = 0; k < channels; ++k) {
+ var buffer = audioBuffer.getChannelData(k);
+ var sample = buffer[i] * 32768.0;
+
+ // Clip samples to the limitations of 16-bit.
+ // If we don't do this then we'll get nasty wrap-around distortion.
+ if (sample < -32768)
+ sample = -32768;
+ if (sample > 32767)
+ sample = 32767;
+
+ writeInt16(sample, a, offset);
+ offset += 2;
+ }
+ }
+ }
+
+ function createWaveFileData(audioBuffer) {
+ var frameLength = audioBuffer.length;
+ var numberOfChannels = audioBuffer.numberOfChannels;
+ var sampleRate = audioBuffer.sampleRate;
+ var bitsPerSample = 16;
+ var byteRate = sampleRate * numberOfChannels * bitsPerSample / 8;
+ var blockAlign = numberOfChannels * bitsPerSample / 8;
+ var wavDataByteLength = frameLength * numberOfChannels * 2; // 16-bit audio
+ var headerByteLength = 44;
+ var totalLength = headerByteLength + wavDataByteLength;
+
+ var waveFileData = new Uint8Array(totalLength);
+
+ var subChunk1Size = 16; // for linear PCM
+ var subChunk2Size = wavDataByteLength;
+ var chunkSize = 4 + (8 + subChunk1Size) + (8 + subChunk2Size);
+
+ writeString("RIFF", waveFileData, 0);
+ writeInt32(chunkSize, waveFileData, 4);
+ writeString("WAVE", waveFileData, 8);
+ writeString("fmt ", waveFileData, 12);
+
+ writeInt32(subChunk1Size, waveFileData, 16); // SubChunk1Size (4)
+ writeInt16(1, waveFileData, 20); // AudioFormat (2)
+ writeInt16(numberOfChannels, waveFileData, 22); // NumChannels (2)
+ writeInt32(sampleRate, waveFileData, 24); // SampleRate (4)
+ writeInt32(byteRate, waveFileData, 28); // ByteRate (4)
+ writeInt16(blockAlign, waveFileData, 32); // BlockAlign (2)
+ writeInt32(bitsPerSample, waveFileData, 34); // BitsPerSample (4)
+
+ writeString("data", waveFileData, 36);
+ writeInt32(subChunk2Size, waveFileData, 40); // SubChunk2Size (4)
+
+ // Write actual audio data starting at offset 44.
+ writeAudioBuffer(audioBuffer, waveFileData, 44);
+
+ return waveFileData;
+ }
+
+</script>
+<script class="testbody" type="text/javascript">
+
+ SimpleTest.waitForExplicitFinish();
+
+ // fuzzTolerance and fuzzToleranceMobile are used to determine fuzziness
+ // thresholds. They're needed to make sure that we can deal with neglibible
+ // differences in the binary buffer caused as a result of resampling the
+ // audio. fuzzToleranceMobile is typically larger on mobile platforms since
+ // we do fixed-point resampling as opposed to floating-point resampling on
+ // those platforms.
+ // If fuzzMagnitude, is present, is the maximum magnitude difference, per
+ // sample, to consider two samples are identical. It is multiplied by the
+ // maximum value a sample, in our case INT16_MAX. This allows checking files
+ // that should be identical except one has e.g. a higher quantization noise.
+ var files = [
+ // An ogg file, 44.1khz, mono
+ {
+ url: "ting-44.1k-1ch.ogg",
+ valid: true,
+ expectedUrl: "ting-44.1k-1ch.wav",
+ numberOfChannels: 1,
+ frames: 30592,
+ sampleRate: 44100,
+ duration: 0.693,
+ fuzzTolerance: 5,
+ fuzzToleranceMobile: 1284
+ },
+ // An ogg file, 44.1khz, stereo
+ {
+ url: "ting-44.1k-2ch.ogg",
+ valid: true,
+ expectedUrl: "ting-44.1k-2ch.wav",
+ numberOfChannels: 2,
+ frames: 30592,
+ sampleRate: 44100,
+ duration: 0.693,
+ fuzzTolerance: 6,
+ fuzzToleranceMobile: 2544
+ },
+ // An ogg file, 48khz, mono
+ {
+ url: "ting-48k-1ch.ogg",
+ valid: true,
+ expectedUrl: "ting-48k-1ch.wav",
+ numberOfChannels: 1,
+ frames: 33297,
+ sampleRate: 48000,
+ duration: 0.693,
+ fuzzTolerance: 5,
+ fuzzToleranceMobile: 1388
+ },
+ // An ogg file, 48khz, stereo
+ {
+ url: "ting-48k-2ch.ogg",
+ valid: true,
+ expectedUrl: "ting-48k-2ch.wav",
+ numberOfChannels: 2,
+ frames: 33297,
+ sampleRate: 48000,
+ duration: 0.693,
+ fuzzTolerance: 14,
+ fuzzToleranceMobile: 2752
+ },
+ // Make sure decoding a wave file results in the same buffer (for both the
+ // resampling and non-resampling cases)
+ {
+ url: "ting-44.1k-1ch.wav",
+ valid: true,
+ expectedUrl: "ting-44.1k-1ch.wav",
+ numberOfChannels: 1,
+ frames: 30592,
+ sampleRate: 44100,
+ duration: 0.693,
+ fuzzTolerance: 0,
+ fuzzToleranceMobile: 0
+ },
+ {
+ url: "ting-48k-1ch.wav",
+ valid: true,
+ expectedUrl: "ting-48k-1ch.wav",
+ numberOfChannels: 1,
+ frames: 33297,
+ sampleRate: 48000,
+ duration: 0.693,
+ fuzzTolerance: 0,
+ fuzzToleranceMobile: 0
+ },
+ // // A wave file
+ // //{ url: "24bit-44khz.wav", valid: true, expectedUrl: "24bit-44khz-expected.wav" },
+ // A non-audio file
+ { url: "invalid.txt", valid: false, sampleRate: 44100 },
+ // A webm file with no audio
+ { url: "noaudio.webm", valid: false, sampleRate: 48000 },
+ // A video ogg file with audio
+ {
+ url: "audio.ogv",
+ valid: true,
+ expectedUrl: "audio-expected.wav",
+ numberOfChannels: 2,
+ sampleRate: 44100,
+ frames: 47680,
+ duration: 1.0807,
+ fuzzTolerance: 106,
+ fuzzToleranceMobile: 3482
+ },
+ {
+ url: "nil-packet.ogg",
+ expectedUrl: null,
+ valid: true,
+ numberOfChannels: 2,
+ sampleRate: 48000,
+ frames: 18600,
+ duration: 0.3874,
+ },
+ {
+ url: "half-a-second-1ch-44100-mulaw.wav",
+ // It is expected that mulaw and linear are similar enough at 16-bits
+ expectedUrl: "half-a-second-1ch-44100.wav",
+ valid: true,
+ numberOfChannels: 1,
+ sampleRate: 44100,
+ frames: 22050,
+ duration: 0.5,
+ fuzzMagnitude: 0.04,
+ },
+ {
+ url: "half-a-second-1ch-44100-alaw.wav",
+ // It is expected that alaw and linear are similar enough at 16-bits
+ expectedUrl: "half-a-second-1ch-44100.wav",
+ valid: true,
+ numberOfChannels: 1,
+ sampleRate: 44100,
+ frames: 22050,
+ duration: 0.5,
+ fuzzMagnitude: 0.04,
+ },
+ {
+ url: "waveformatextensible.wav",
+ valid: true,
+ numberOfChannels: 1,
+ sampleRate: 44100,
+ frames: 472,
+ duration: 0.01
+ },
+ {
+ // A wav file that has 8 channel, but has a channel mask that doesn't
+ // match the channel count.
+ url: "waveformatextensiblebadmask.wav",
+ valid: true,
+ numberOfChannels: 8,
+ sampleRate: 8000,
+ frames: 80,
+ duration: 0.01
+ }
+ ];
+
+ // Returns true if the memory buffers are less different that |fuzz| bytes
+ function fuzzyMemcmp(buf1, buf2, fuzz) {
+ var difference = 0;
+ is(buf1.length, buf2.length, "same length");
+ for (var i = 0; i < buf1.length; ++i) {
+ if (Math.abs(buf1[i] - buf2[i]) > fuzz.magnitude * (2 << 15)) {
+ ++difference;
+ }
+ }
+ if (difference > fuzz.count) {
+ ok(false, "Expected at most " + fuzz + " bytes difference, found " + difference + " bytes");
+ }
+ console.log(difference, fuzz.count);
+ return difference <= fuzz.count;
+ }
+
+ function getFuzzTolerance(test) {
+ var kIsMobile =
+ navigator.userAgent.includes("Mobile") || // b2g
+ navigator.userAgent.includes("Android"); // android
+ return {
+ magnitude: test.fuzzMagnitude ?? 0,
+ count: kIsMobile ? test.fuzzToleranceMobile ?? 0 : test.fuzzTolerance ?? 0
+ };
+ }
+
+ function bufferIsSilent(buffer) {
+ for (var i = 0; i < buffer.length; ++i) {
+ if (buffer.getChannelData(0)[i] != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ function checkAudioBuffer(buffer, test) {
+ if (buffer.numberOfChannels != test.numberOfChannels) {
+ is(buffer.numberOfChannels, test.numberOfChannels, "Correct number of channels");
+ return;
+ }
+ ok(Math.abs(buffer.duration - test.duration) < 1e-3, `Correct duration expected ${test.duration} got ${buffer.duration}`);
+ if (Math.abs(buffer.duration - test.duration) >= 1e-3) {
+ ok(false, "got: " + buffer.duration + ", expected: " + test.duration);
+ }
+ is(buffer.sampleRate, test.sampleRate, "Correct sample rate");
+ is(buffer.length, test.frames, "Correct length");
+
+ var wave = createWaveFileData(buffer);
+ if (test.expectedWaveData) {
+ ok(fuzzyMemcmp(wave, test.expectedWaveData, getFuzzTolerance(test)), "Received expected decoded data for " + test.url);
+ }
+ }
+
+ function checkResampledBuffer(buffer, test, callback) {
+ if (buffer.numberOfChannels != test.numberOfChannels) {
+ is(buffer.numberOfChannels, test.numberOfChannels, "Correct number of channels");
+ return;
+ }
+ ok(Math.abs(buffer.duration - test.duration) < 1e-3, "Correct duration");
+ if (Math.abs(buffer.duration - test.duration) >= 1e-3) {
+ ok(false, "got: " + buffer.duration + ", expected: " + test.duration);
+ }
+ // Take into account the resampling when checking the size
+ var expectedLength = test.frames * buffer.sampleRate / test.sampleRate;
+ SimpleTest.ok(
+ Math.abs(buffer.length - expectedLength) < 1.0,
+ "Correct length - got " + buffer.length +
+ ", expected about " + expectedLength
+ );
+
+ // Playback the buffer in the original context, to resample back to the
+ // original rate and compare with the decoded buffer without resampling.
+ let cx = test.nativeContext;
+ var expected = cx.createBufferSource();
+ expected.buffer = test.expectedBuffer;
+ expected.start();
+ var inverse = cx.createGain();
+ inverse.gain.value = -1;
+ expected.connect(inverse);
+ inverse.connect(cx.destination);
+ var resampled = cx.createBufferSource();
+ resampled.buffer = buffer;
+ resampled.start();
+ // This stop should do nothing, but it tests for bug 937475
+ resampled.stop(test.frames / cx.sampleRate);
+ resampled.connect(cx.destination);
+ cx.oncomplete = function (e) {
+ ok(!bufferIsSilent(e.renderedBuffer), "Expect buffer not silent");
+ // Resampling will lose the highest frequency components, so we should
+ // pass the difference through a low pass filter. However, either the
+ // input files don't have significant high frequency components or the
+ // tolerance in compareBuffers() is too high to detect them.
+ compareBuffers(e.renderedBuffer,
+ cx.createBuffer(test.numberOfChannels,
+ test.frames, test.sampleRate));
+ callback();
+ }
+ cx.startRendering();
+ }
+
+ function runResampling(test, response, callback) {
+ var sampleRate = test.sampleRate == 44100 ? 48000 : 44100;
+ var cx = new OfflineAudioContext(1, 1, sampleRate);
+ cx.decodeAudioData(response, function onSuccess(asyncResult) {
+ is(asyncResult.sampleRate, sampleRate, "Correct sample rate");
+
+ checkResampledBuffer(asyncResult, test, callback);
+ }, function onFailure() {
+ ok(false, "Expected successful decode with resample");
+ callback();
+ });
+ }
+
+ function runTest(test, response, callback) {
+ // We need to copy the array here, because decodeAudioData will detach the
+ // array's buffer.
+ var compressedAudio = response.slice(0);
+ var expectCallback = false;
+ var cx = new OfflineAudioContext(test.numberOfChannels || 1,
+ test.frames || 1, test.sampleRate);
+ cx.decodeAudioData(response, function onSuccess(asyncResult) {
+ ok(expectCallback, "Success callback should fire asynchronously");
+ ok(test.valid, "Did expect success for test " + test.url);
+
+ checkAudioBuffer(asyncResult, test);
+
+ test.expectedBuffer = asyncResult;
+ test.nativeContext = cx;
+ runResampling(test, compressedAudio, callback);
+ }, function onFailure(e) {
+ ok(e instanceof DOMException, "We want to see an exception here");
+ is(e.name, "EncodingError", "Exception name matches");
+ ok(expectCallback, "Failure callback should fire asynchronously");
+ ok(!test.valid, "Did expect failure for test " + test.url);
+ callback();
+ });
+ expectCallback = true;
+ }
+
+ function loadTest(test, callback) {
+ var xhr = new XMLHttpRequest();
+ xhr.open("GET", test.url, true);
+ xhr.responseType = "arraybuffer";
+ xhr.onload = function () {
+ if (!test.expectedUrl) {
+ runTest(test, xhr.response, callback);
+ return;
+ }
+ var getExpected = new XMLHttpRequest();
+ getExpected.open("GET", test.expectedUrl, true);
+ getExpected.responseType = "arraybuffer";
+ getExpected.onload = function () {
+ test.expectedWaveData = new Uint8Array(getExpected.response);
+ runTest(test, xhr.response, callback);
+ };
+ getExpected.send();
+ };
+ xhr.send();
+ }
+
+ function loadNextTest() {
+ if (files.length) {
+ loadTest(files.shift(), loadNextTest);
+ } else {
+ SimpleTest.finish();
+ }
+ }
+
+ loadNextTest();
+
+</script>
+</pre>
+ </body>
+</html>
diff --git a/dom/media/webaudio/test/test_mediaElementAudioSourceNode.html b/dom/media/webaudio/test/test_mediaElementAudioSourceNode.html
new file mode 100644
index 0000000000..36e1e9f2cc
--- /dev/null
+++ b/dom/media/webaudio/test/test_mediaElementAudioSourceNode.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<meta charset="utf-8">
+<head>
+ <title>Test MediaElementAudioSourceNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+function test() {
+ var audio = new Audio("small-shot.ogg");
+ var context = new AudioContext();
+ var expectedMinNonzeroSampleCount;
+ var expectedMaxNonzeroSampleCount;
+ var nonzeroSampleCount = 0;
+ var complete = false;
+ var iterationCount = 0;
+
+ // This test ensures we receive at least expectedSampleCount nonzero samples
+ function processSamples(e) {
+ if (complete) {
+ return;
+ }
+
+ if (iterationCount == 0) {
+ // Don't start playing the audio until the AudioContext stuff is connected
+ // and running.
+ audio.play();
+ }
+ ++iterationCount;
+
+ var buf = e.inputBuffer.getChannelData(0);
+ var nonzeroSamplesThisBuffer = 0;
+ for (var i = 0; i < buf.length; ++i) {
+ if (buf[i] != 0) {
+ ++nonzeroSamplesThisBuffer;
+ }
+ }
+ nonzeroSampleCount += nonzeroSamplesThisBuffer;
+ is(e.inputBuffer.numberOfChannels, 1,
+ "Checking data channel count (nonzeroSamplesThisBuffer=" +
+ nonzeroSamplesThisBuffer + ")");
+ ok(nonzeroSampleCount <= expectedMaxNonzeroSampleCount,
+ "Too many nonzero samples (got " + nonzeroSampleCount + ", expected max " + expectedMaxNonzeroSampleCount + ")");
+ if (nonzeroSampleCount >= expectedMinNonzeroSampleCount &&
+ nonzeroSamplesThisBuffer == 0) {
+ ok(true,
+ "Check received enough nonzero samples (got " + nonzeroSampleCount + ", expected min " + expectedMinNonzeroSampleCount + ")");
+ SimpleTest.finish();
+ complete = true;
+ }
+ }
+
+ audio.onloadedmetadata = function() {
+ var node = new MediaElementAudioSourceNode(context, { mediaElement: audio });
+ var sp = context.createScriptProcessor(2048, 1);
+ node.connect(sp);
+ // Use a fuzz factor of 100 to account for samples that just happen to be zero
+ expectedMinNonzeroSampleCount = Math.floor(audio.duration*context.sampleRate) - 100;
+ expectedMaxNonzeroSampleCount = Math.floor(audio.duration*context.sampleRate) + 500;
+ sp.onaudioprocess = processSamples;
+ };
+}
+
+SpecialPowers.pushPrefEnv({"set": [["media.preload.default", 2], ["media.preload.auto", 3]]}, test);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_mediaElementAudioSourceNodeCrossOrigin.html b/dom/media/webaudio/test/test_mediaElementAudioSourceNodeCrossOrigin.html
new file mode 100644
index 0000000000..778658b332
--- /dev/null
+++ b/dom/media/webaudio/test/test_mediaElementAudioSourceNodeCrossOrigin.html
@@ -0,0 +1,94 @@
+<!DOCTYPE HTML>
+<html>
+<meta charset="utf-8">
+<head>
+ <title>Test MediaStreamAudioSourceNode doesn't get data from cross-origin media resources</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+// Turn off the authentication dialog blocking for this test.
+SpecialPowers.setIntPref("network.auth.subresource-http-auth-allow", 2)
+
+var tests = [
+ // Not the same origin no CORS asked for, should have silence
+ { url: "http://example.org:80/tests/dom/media/webaudio/test/small-shot.ogg",
+ cors: null,
+ expectSilence: true },
+ // Same origin, should have sound
+ { url: "small-shot.ogg",
+ cors: null,
+ expectSilence: false },
+ // Cross-origin but we asked for CORS and the server answered with the right
+ // header, should have
+ { url: "http://example.org:80/tests/dom/media/webaudio/test/corsServer.sjs",
+ cors: "anonymous",
+ expectSilence: false }
+];
+
+var testsRemaining = tests.length;
+
+tests.forEach(function(e) {
+ e.ac = new AudioContext();
+ var a = new Audio();
+ if (e.cors) {
+ a.crossOrigin = e.cors;
+ }
+ a.src = e.url;
+ document.body.appendChild(a);
+
+ a.onloadedmetadata = () => {
+ // Wait for "loadedmetadata" before capturing since tracks are then known
+ // directly. If we set up the capture before "loadedmetadata" we
+ // (internally) have to wait an extra async jump for tracks to become known
+ // to main thread, before setting up audio data forwarding to the node.
+ // As that happens, the audio resource may have already ended on slow test
+ // machines, causing failures.
+ a.onloadedmetadata = null;
+ var measn = e.ac.createMediaElementSource(a);
+ var sp = e.ac.createScriptProcessor(2048, 1);
+ sp.seenSound = false;
+ sp.onaudioprocess = checkBufferSilent;
+
+ measn.connect(sp);
+ a.play();
+ };
+
+ function checkFinished(sp) {
+ if (a.ended) {
+ sp.onaudioprocess = null;
+ var not = e.expectSilence ? "" : "not";
+ is(e.expectSilence, !sp.seenSound,
+ "Buffer is " + not + " silent as expected, for " +
+ e.url + " (cors: " + e.cors + ")");
+ if (--testsRemaining == 0) {
+ SimpleTest.finish();
+ }
+ }
+ }
+
+ function checkBufferSilent(e) {
+ var inputArrayBuffer = e.inputBuffer.getChannelData(0);
+ var silent = true;
+ for (var i = 0; i < inputArrayBuffer.length; i++) {
+ if (inputArrayBuffer[i] != 0.0) {
+ silent = false;
+ break;
+ }
+ }
+ // It is acceptable to find a full buffer of silence here, even if we expect
+ // sound, because Gecko's looping on media elements is not seamless and we
+ // can underrun. We are looking for at least one buffer of non-silent data.
+ e.target.seenSound = !silent || e.target.seenSound;
+ checkFinished(e.target);
+ return silent;
+ }
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_mediaElementAudioSourceNodeFidelity.html b/dom/media/webaudio/test/test_mediaElementAudioSourceNodeFidelity.html
new file mode 100644
index 0000000000..4714172607
--- /dev/null
+++ b/dom/media/webaudio/test/test_mediaElementAudioSourceNodeFidelity.html
@@ -0,0 +1,137 @@
+<!DOCTYPE HTML>
+<html>
+<meta charset="utf-8">
+<head>
+ <title>Test MediaStreamAudioSourceNode doesn't get data from cross-origin media resources</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+function binIndexForFrequency(frequency, analyser) {
+ return 1 + Math.round(frequency *
+ analyser.fftSize /
+ analyser.context.sampleRate);
+}
+
+function debugCanvas(analyser) {
+ var cvs = document.createElement("canvas");
+ document.body.appendChild(cvs);
+
+ // Easy: 1px per bin
+ cvs.width = analyser.frequencyBinCount;
+ cvs.height = 256;
+ cvs.style.border = "1px solid red";
+
+ var c = cvs.getContext('2d');
+ var buf = new Uint8Array(analyser.frequencyBinCount);
+
+ function render() {
+ c.clearRect(0, 0, cvs.width, cvs.height);
+ analyser.getByteFrequencyData(buf);
+ for (var i = 0; i < buf.length; i++) {
+ c.fillRect(i, (256 - (buf[i])), 1, 256);
+ }
+ requestAnimationFrame(render);
+ }
+ requestAnimationFrame(render);
+}
+
+
+function checkFrequency(an) {
+ an.getFloatFrequencyData(frequencyArray);
+ // We should have no energy when checking the data largely outside the index
+ // for 440Hz (the frequency of the sine wave), start checking an octave above,
+ // the Opus compression can add some harmonics to the pure since wave.
+ var maxNoiseIndex = binIndexForFrequency(880, an);
+ for (var i = maxNoiseIndex + 1; i < frequencyArray.length; i++) {
+ if (frequencyArray[i] > frequencyArray[maxNoiseIndex]) {
+ maxNoiseIndex = i;
+ }
+ }
+
+ // On the other hand, we should find a peak at 440Hz. Our sine wave is not
+ // attenuated, we're expecting the peak to reach 0dBFs.
+ var index = binIndexForFrequency(440, an);
+ info("energy at 440: " + frequencyArray[index] +
+ ", threshold " + (an.maxDecibels - 10) +
+ "; max noise at index " + maxNoiseIndex +
+ ": " + frequencyArray[maxNoiseIndex] );
+ if (frequencyArray[index] < (an.maxDecibels - 10)) {
+ return false;
+ }
+ // Let some slack, there might be some noise here because of int -> float
+ // conversion or the Opus encoding.
+ if (frequencyArray[maxNoiseIndex] > an.minDecibels + 40) {
+ return false;
+ }
+
+ return true;
+}
+
+var audioElement = new Audio();
+audioElement.src = 'sine-440-10s.opus'
+audioElement.loop = true;
+var ac = new AudioContext();
+var mediaElementSource = ac.createMediaElementSource(audioElement);
+var an = ac.createAnalyser();
+// Use no smoothing as this would just average with previous
+// getFloatFrequencyData() calls. Non-seamless looping would introduce noise,
+// and smoothing would spread this into calls after the loop point.
+an.smoothingTimeConstant = 0;
+let frequencyArray = new Float32Array(an.frequencyBinCount);
+
+// Uncomment this to check what the analyser is doing.
+// debugCanvas(an);
+
+mediaElementSource.connect(an)
+
+audioElement.play();
+// We want to check the we have the expected audio for at least two loop of
+// the HTMLMediaElement, piped into an AudioContext. The file is ten seconds,
+// and we use the default FFT size.
+var lastCurrentTime = 0;
+var loopCount = 0;
+audioElement.onplaying = function() {
+ audioElement.ontimeupdate = function() {
+ // We don't run the analysis when close to loop point or at the
+ // beginning, since looping is not seamless, there could be an
+ // unpredictable amount of silence
+ var rv = checkFrequency(an);
+ info("currentTime: " + audioElement.currentTime);
+ if (audioElement.currentTime < 4 ||
+ audioElement.currentTime > 8){
+ return;
+ }
+ if (!rv) {
+ ok(false, "Found unexpected noise during analysis.");
+ audioElement.ontimeupdate = null;
+ audioElement.onplaying = null;
+ ac.close();
+ audioElement.src = '';
+ SimpleTest.finish()
+ return;
+ }
+ ok(true, "Found correct audio signal during analysis");
+ info(lastCurrentTime + " " + audioElement.currentTime);
+ if (lastCurrentTime > audioElement.currentTime) {
+ info("loopCount: " + loopCount);
+ if (loopCount > 1) {
+ audioElement.ontimeupdate = null;
+ audioElement.onplaying = null;
+ ac.close();
+ audioElement.src = '';
+ SimpleTest.finish();
+ }
+ lastCurrentTime = audioElement.currentTime;
+ loopCount++;
+ } else {
+ lastCurrentTime = audioElement.currentTime;
+ }
+ }
+}
+
+</script>
diff --git a/dom/media/webaudio/test/test_mediaElementAudioSourceNodePassThrough.html b/dom/media/webaudio/test/test_mediaElementAudioSourceNodePassThrough.html
new file mode 100644
index 0000000000..5ee12b16ad
--- /dev/null
+++ b/dom/media/webaudio/test/test_mediaElementAudioSourceNodePassThrough.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+<meta charset="utf-8">
+<head>
+ <title>Test MediaElementAudioSourceNode with passthrough</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+function test() {
+ var audio = new Audio("small-shot.ogg");
+ var context = new AudioContext();
+ var node = context.createMediaElementSource(audio);
+ var sp = context.createScriptProcessor(2048, 1);
+ node.connect(sp);
+ var nonzeroSampleCount = 0;
+ var complete = false;
+ var iterationCount = 0;
+
+ var srcWrapped = SpecialPowers.wrap(node);
+ ok("passThrough" in srcWrapped, "MediaElementAudioSourceNode should support the passThrough API");
+ srcWrapped.passThrough = true;
+
+ // This test ensures we receive at least expectedSampleCount nonzero samples
+ function processSamples(e) {
+ if (complete) {
+ return;
+ }
+
+ if (iterationCount == 0) {
+ // Don't start playing the audio until the AudioContext stuff is connected
+ // and running.
+ audio.play();
+ }
+ ++iterationCount;
+
+ var buf = e.inputBuffer.getChannelData(0);
+ var nonzeroSamplesThisBuffer = 0;
+ for (var i = 0; i < buf.length; ++i) {
+ if (buf[i] != 0) {
+ ++nonzeroSamplesThisBuffer;
+ }
+ }
+ nonzeroSampleCount += nonzeroSamplesThisBuffer;
+ if (iterationCount == 10) {
+ is(nonzeroSampleCount, 0, "The input must be silence");
+ SimpleTest.finish();
+ complete = true;
+ }
+ }
+
+ audio.oncanplaythrough = function() {
+ sp.onaudioprocess = processSamples;
+ };
+}
+
+SpecialPowers.pushPrefEnv({"set": [["media.preload.default", 2], ["media.preload.auto", 3]]}, test);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_mediaElementAudioSourceNodeVideo.html b/dom/media/webaudio/test/test_mediaElementAudioSourceNodeVideo.html
new file mode 100644
index 0000000000..dcb85f12cb
--- /dev/null
+++ b/dom/media/webaudio/test/test_mediaElementAudioSourceNodeVideo.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+<meta charset="utf-8">
+<head>
+ <title>Test MediaElementAudioSourceNode before "loadedmetadata"</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var video = document.createElement("video");
+function test() {
+ video.src = "audiovideo.mp4";
+
+ var context = new AudioContext();
+ var complete = false;
+
+ video.onended = () => {
+ if (complete) {
+ return;
+ }
+
+ complete = true;
+ ok(false, "Video ended without any samples seen");
+ SimpleTest.finish();
+ };
+
+ video.ontimeupdate = () => {
+ info("Timeupdate: " + video.currentTime);
+ };
+
+ var node = context.createMediaElementSource(video);
+ var sp = context.createScriptProcessor(2048, 1);
+ node.connect(sp);
+
+ // This test ensures we receive some nonzero samples when we capture to
+ // WebAudio before "loadedmetadata".
+ sp.onaudioprocess = e => {
+ if (complete) {
+ return;
+ }
+
+ var buf = e.inputBuffer.getChannelData(0);
+ for (var i = 0; i < buf.length; ++i) {
+ if (buf[i] != 0) {
+ complete = true;
+ ok(true, "Got non-zero samples");
+ SimpleTest.finish();
+ return;
+ }
+ }
+ };
+
+ video.play();
+}
+
+if (video.canPlayType("video/mp4")) {
+ test();
+} else {
+ ok(true, "MP4 not supported. Skipping.");
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_mediaStreamAudioDestinationNode.html b/dom/media/webaudio/test/test_mediaStreamAudioDestinationNode.html
new file mode 100644
index 0000000000..fd0ce8141b
--- /dev/null
+++ b/dom/media/webaudio/test/test_mediaStreamAudioDestinationNode.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test MediaStreamAudioDestinationNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<audio id="audioelem"></audio>
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("This test uses a live media element so it needs to wait for the media stack to do some work.");
+addLoadEvent(function() {
+ var context = new AudioContext();
+ var buffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+
+ var source = context.createBufferSource();
+ source.buffer = buffer;
+
+ var dest = new MediaStreamAudioDestinationNode(context);
+ source.connect(dest);
+
+ var elem = document.getElementById('audioelem');
+ elem.srcObject = dest.stream;
+ elem.onloadedmetadata = function() {
+ ok(true, "got metadata event");
+ setTimeout(function() {
+ is(elem.played.length, 1, "should have a played interval");
+ is(elem.played.start(0), 0, "should have played immediately");
+ isnot(elem.played.end(0), 0, "should have played for a non-zero interval");
+
+ // This will end the media element.
+ dest.stream.getTracks()[0].stop();
+ }, 2000);
+ };
+ elem.onended = function() {
+ ok(true, "media element ended after destination track.stop()");
+ SimpleTest.finish();
+ };
+
+ source.start(0);
+ elem.play();
+});
+</script>
diff --git a/dom/media/webaudio/test/test_mediaStreamAudioSourceNode.html b/dom/media/webaudio/test/test_mediaStreamAudioSourceNode.html
new file mode 100644
index 0000000000..eaa8a564b9
--- /dev/null
+++ b/dom/media/webaudio/test/test_mediaStreamAudioSourceNode.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<meta charset="utf-8">
+<head>
+ <title>Test MediaStreamAudioSourceNode processing is correct</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function createBuffer(context) {
+ var buffer = context.createBuffer(2, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ buffer.getChannelData(1)[i] = -buffer.getChannelData(0)[i];
+ }
+ return buffer;
+}
+
+var gTest = {
+ length: 2048,
+ skipOfflineContextTests: true,
+ createGraph(context) {
+ var sourceGraph = new AudioContext();
+ var source = sourceGraph.createBufferSource();
+ source.buffer = createBuffer(context);
+ var dest = sourceGraph.createMediaStreamDestination();
+ source.connect(dest);
+ source.start(0);
+
+ var mediaStreamSource = new MediaStreamAudioSourceNode(context, { mediaStream: dest.stream });
+ // channelCount and channelCountMode should have no effect
+ mediaStreamSource.channelCount = 1;
+ mediaStreamSource.channelCountMode = "explicit";
+ return mediaStreamSource;
+ },
+ createExpectedBuffers(context) {
+ return createBuffer(context);
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_mediaStreamAudioSourceNodeCrossOrigin.html b/dom/media/webaudio/test/test_mediaStreamAudioSourceNodeCrossOrigin.html
new file mode 100644
index 0000000000..aaeaab3159
--- /dev/null
+++ b/dom/media/webaudio/test/test_mediaStreamAudioSourceNodeCrossOrigin.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<meta charset="utf-8">
+<head>
+ <title>Test MediaStreamAudioSourceNode doesn't get data from cross-origin media resources</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var audio = new Audio("http://example.org:80/tests/dom/media/webaudio/test/small-shot.ogg");
+audio.load();
+var context = new AudioContext();
+audio.onloadedmetadata = function() {
+ var node = context.createMediaStreamSource(audio.mozCaptureStreamUntilEnded());
+ var sp = context.createScriptProcessor(2048, 1);
+ node.connect(sp);
+ var complete = false;
+ var iterationCount = 0;
+
+ // This test ensures we receive at least expectedSampleCount nonzero samples
+ function processSamples(e) {
+ if (complete) {
+ return;
+ }
+
+ if (iterationCount == 0) {
+ // Don't start playing the audio until the AudioContext stuff is connected
+ // and running.
+ audio.play();
+ }
+ ++iterationCount;
+
+ var buf = e.inputBuffer.getChannelData(0);
+ var nonzeroSamplesThisBuffer = 0;
+ for (var i = 0; i < buf.length; ++i) {
+ if (buf[i] != 0) {
+ ++nonzeroSamplesThisBuffer;
+ }
+ }
+ is(nonzeroSamplesThisBuffer, 0,
+ "Checking all samples are zero");
+ if (iterationCount >= 20) {
+ SimpleTest.finish();
+ complete = true;
+ }
+ }
+
+ audio.oncanplaythrough = function() {
+ sp.onaudioprocess = processSamples;
+ };
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_mediaStreamAudioSourceNodeNoGC.html b/dom/media/webaudio/test/test_mediaStreamAudioSourceNodeNoGC.html
new file mode 100644
index 0000000000..7920af9f7b
--- /dev/null
+++ b/dom/media/webaudio/test/test_mediaStreamAudioSourceNodeNoGC.html
@@ -0,0 +1,116 @@
+<!DOCTYPE HTML>
+<html>
+<meta charset="utf-8">
+<head>
+ <title>Test that MediaStreamAudioSourceNode and its input MediaStream stays alive while there are active tracks</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("gUM and WebAudio data is async to main thread. " +
+ "We need a timeout to see that something does " +
+ "NOT happen to data.");
+
+let context = new AudioContext();
+let analyser = context.createAnalyser();
+
+function wait(millis, resolveWithThis) {
+ return new Promise(resolve => setTimeout(() => resolve(resolveWithThis), millis));
+}
+
+function binIndexForFrequency(frequency) {
+ return 1 + Math.round(frequency * analyser.fftSize / context.sampleRate);
+}
+
+function waitForAudio(analysisFunction, cancelPromise) {
+ let data = new Uint8Array(analyser.frequencyBinCount);
+ let cancelled = false;
+ let cancelledMsg = "";
+ cancelPromise.then(msg => {
+ cancelled = true;
+ cancelledMsg = msg;
+ });
+ return new Promise((resolve, reject) => {
+ let loop = () => {
+ analyser.getByteFrequencyData(data);
+ if (cancelled) {
+ reject(new Error("waitForAudio cancelled: " + cancelledMsg));
+ return;
+ }
+ if (analysisFunction(data)) {
+ resolve();
+ return;
+ }
+ requestAnimationFrame(loop);
+ };
+ loop();
+ });
+}
+
+async function test(sourceNode) {
+ try {
+ await analyser.connect(context.destination);
+
+ ok(true, "Waiting for audio to pass through the analyser")
+ await waitForAudio(arr => arr[binIndexForFrequency(1000)] > 200,
+ wait(60000, "Timeout waiting for audio"));
+
+ ok(true, "Audio was detected by the analyser. Forcing CC.");
+ SpecialPowers.forceCC();
+ SpecialPowers.forceGC();
+ SpecialPowers.forceCC();
+ SpecialPowers.forceGC();
+
+ info("Checking that GC didn't destroy the stream or source node");
+ await waitForAudio(arr => arr[binIndexForFrequency(1000)] < 50,
+ wait(5000, "Timeout waiting for GC (timeout OK)"))
+ .then(() => Promise.reject("Audio stopped unexpectedly"),
+ () => Promise.resolve());
+
+ ok(true, "Audio is still flowing");
+ } catch(e) {
+ ok(false, "Error executing test: " + e + (e.stack ? "\n" + e.stack : ""));
+ SimpleTest.finish();
+ }
+}
+
+(async function() {
+ try {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // This test expects the fake audio device, specifically for the tones
+ // it outputs. Explicitly disable the audio loopback device and enable
+ // fake streams.
+ ['media.audio_loopback_dev', ''],
+ ['media.navigator.streams.fake', true],
+ ['media.navigator.permission.disabled', true]
+ ]
+ });
+
+ // Test stream source GC
+ let stream = await navigator.mediaDevices.getUserMedia({audio: true});
+ let source = context.createMediaStreamSource(stream);
+ stream = null;
+ source.connect(analyser);
+ await test(source);
+
+ // Test track source GC
+ stream = await navigator.mediaDevices.getUserMedia({audio: true});
+ source = context.createMediaStreamTrackSource(stream.getAudioTracks()[0]);
+ stream = null;
+ source.connect(analyser);
+ await test(source);
+ } catch(e) {
+ ok(false, `Error executing test: ${e}${e.stack ? "\n" + e.stack : ""}`);
+ } finally {
+ context.close();
+ SimpleTest.finish();
+ }
+})();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_mediaStreamAudioSourceNodePassThrough.html b/dom/media/webaudio/test/test_mediaStreamAudioSourceNodePassThrough.html
new file mode 100644
index 0000000000..379bfdbc6a
--- /dev/null
+++ b/dom/media/webaudio/test/test_mediaStreamAudioSourceNodePassThrough.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<meta charset="utf-8">
+<head>
+ <title>Test MediaStreamAudioSourceNode passthrough</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function createBuffer(context, delay) {
+ var buffer = context.createBuffer(2, 2048, context.sampleRate);
+ for (var i = 0; i < 2048 - delay; ++i) {
+ buffer.getChannelData(0)[i + delay] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ buffer.getChannelData(1)[i + delay] = -buffer.getChannelData(0)[i + delay];
+ }
+ return buffer;
+}
+
+var gTest = {
+ length: 2048,
+ skipOfflineContextTests: true,
+ createGraph(context) {
+ var sourceGraph = new AudioContext();
+ var source = sourceGraph.createBufferSource();
+ source.buffer = createBuffer(context, 0);
+ var dest = sourceGraph.createMediaStreamDestination();
+ source.connect(dest);
+ source.start(0);
+
+ var mediaStreamSource = context.createMediaStreamSource(dest.stream);
+ // channelCount and channelCountMode should have no effect
+ mediaStreamSource.channelCount = 1;
+ mediaStreamSource.channelCountMode = "explicit";
+
+ var srcWrapped = SpecialPowers.wrap(mediaStreamSource);
+ ok("passThrough" in srcWrapped, "MediaStreamAudioSourceNode should support the passThrough API");
+ srcWrapped.passThrough = true;
+
+ return mediaStreamSource;
+ },
+ createExpectedBuffers(context) {
+ return context.createBuffer(2, 2048, context.sampleRate);
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_mediaStreamAudioSourceNodeResampling.html b/dom/media/webaudio/test/test_mediaStreamAudioSourceNodeResampling.html
new file mode 100644
index 0000000000..efacf1ecc5
--- /dev/null
+++ b/dom/media/webaudio/test/test_mediaStreamAudioSourceNodeResampling.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<meta charset="utf-8">
+<head>
+ <title>Test MediaStreamAudioSourceNode processing is correct</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+function test() {
+ var audio = new Audio("small-shot.ogg");
+ var context = new AudioContext();
+ var expectedMinNonzeroSampleCount;
+ var expectedMaxNonzeroSampleCount;
+ var nonzeroSampleCount = 0;
+ var complete = false;
+ var iterationCount = 0;
+
+ // This test ensures we receive at least expectedSampleCount nonzero samples
+ function processSamples(e) {
+ if (complete) {
+ return;
+ }
+
+ if (iterationCount == 0) {
+ // Don't start playing the audio until the AudioContext stuff is connected
+ // and running.
+ audio.play();
+ }
+ ++iterationCount;
+
+ var buf = e.inputBuffer.getChannelData(0);
+ var nonzeroSamplesThisBuffer = 0;
+ for (var i = 0; i < buf.length; ++i) {
+ if (buf[i] != 0) {
+ ++nonzeroSamplesThisBuffer;
+ }
+ }
+ nonzeroSampleCount += nonzeroSamplesThisBuffer;
+ is(e.inputBuffer.numberOfChannels, 1,
+ "Checking data channel count (nonzeroSamplesThisBuffer=" +
+ nonzeroSamplesThisBuffer + ")");
+ ok(nonzeroSampleCount <= expectedMaxNonzeroSampleCount,
+ "Too many nonzero samples (got " + nonzeroSampleCount + ", expected max " + expectedMaxNonzeroSampleCount + ")");
+ if (nonzeroSampleCount >= expectedMinNonzeroSampleCount &&
+ nonzeroSamplesThisBuffer == 0) {
+ ok(true,
+ "Check received enough nonzero samples (got " + nonzeroSampleCount + ", expected min " + expectedMinNonzeroSampleCount + ")");
+ SimpleTest.finish();
+ complete = true;
+ }
+ }
+
+ audio.onloadedmetadata = function() {
+ var node = context.createMediaStreamSource(audio.mozCaptureStreamUntilEnded());
+ var sp = context.createScriptProcessor(2048, 1, 0);
+ node.connect(sp);
+ // Use a fuzz factor of 100 to account for samples that just happen to be zero
+ expectedMinNonzeroSampleCount = Math.floor(audio.duration*context.sampleRate) - 100;
+ expectedMaxNonzeroSampleCount = Math.floor(audio.duration*context.sampleRate) + 500;
+ sp.onaudioprocess = processSamples;
+ };
+}
+
+SpecialPowers.pushPrefEnv({"set": [["media.preload.default", 2], ["media.preload.auto", 3]]}, test);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_mediaStreamTrackAudioSourceNode.html b/dom/media/webaudio/test/test_mediaStreamTrackAudioSourceNode.html
new file mode 100644
index 0000000000..350e9e0fab
--- /dev/null
+++ b/dom/media/webaudio/test/test_mediaStreamTrackAudioSourceNode.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<meta charset="utf-8">
+<head>
+ <title>Test MediaStreamTrackAudioSourceNode processing is correct</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function createBuffer(context) {
+ let buffer = context.createBuffer(2, 2048, context.sampleRate);
+ for (let i = 0; i < 2048; ++i) {
+ buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ buffer.getChannelData(1)[i] = -buffer.getChannelData(0)[i];
+ }
+ return buffer;
+}
+
+let gTest = {
+ length: 2048,
+ skipOfflineContextTests: true,
+ createGraph(context) {
+ let sourceGraph = new AudioContext();
+ let source = sourceGraph.createBufferSource();
+ source.buffer = createBuffer(context);
+ let dest = sourceGraph.createMediaStreamDestination();
+ source.connect(dest);
+
+ // Extract first audio track from dest.stream
+ let track = dest.stream.getAudioTracks()[0];
+
+ source.start(0);
+
+ let mediaStreamTrackSource = new MediaStreamTrackAudioSourceNode(context, { mediaStreamTrack: track });
+ // channelCount and channelCountMode should have no effect
+ mediaStreamTrackSource.channelCount = 1;
+ mediaStreamTrackSource.channelCountMode = "explicit";
+ return mediaStreamTrackSource;
+ },
+ createExpectedBuffers(context) {
+ return createBuffer(context);
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_mediaStreamTrackAudioSourceNodeCrossOrigin.html b/dom/media/webaudio/test/test_mediaStreamTrackAudioSourceNodeCrossOrigin.html
new file mode 100644
index 0000000000..243534de32
--- /dev/null
+++ b/dom/media/webaudio/test/test_mediaStreamTrackAudioSourceNodeCrossOrigin.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<meta charset="utf-8">
+<head>
+<title>Test MediaStreamTrackAudioSourceNode doesn't get data from cross-origin media resources</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+const CROSS_ORIGIN_URL = "http://example.org:80/tests/dom/media/webaudio/test/sine-440-10s.opus"
+let iterationCount = 0;
+let context = null;
+let sp;
+
+function processSamples(e) {
+ ++iterationCount;
+
+ let buf = e.inputBuffer.getChannelData(0);
+ let nonzeroSamplesThisBuffer = 0;
+ for (let i = 0; i < buf.length; ++i) {
+ if (buf[i] != 0) {
+ ++nonzeroSamplesThisBuffer;
+ }
+ }
+ is(nonzeroSamplesThisBuffer, 0,
+ "a source that is cross origin cannot be inspected by Web Audio");
+
+ if (iterationCount == 40) {
+ sp.onaudioprocess = null;
+ context.close();
+ SimpleTest.finish();
+ }
+}
+
+let audio = new Audio();
+audio.src = CROSS_ORIGIN_URL;
+audio.onloadedmetadata = function () {
+ context = new AudioContext();
+ let stream = audio.mozCaptureStream();
+ let track = stream.getAudioTracks()[0];
+ let node = context.createMediaStreamTrackSource(track);
+ node.connect(context.destination);
+ sp = context.createScriptProcessor(2048, 1);
+ sp.onaudioprocess = processSamples;
+ node.connect(sp);
+}
+
+</script>
+</pre>
+</body>
diff --git a/dom/media/webaudio/test/test_mediaStreamTrackAudioSourceNodeVideo.html b/dom/media/webaudio/test/test_mediaStreamTrackAudioSourceNodeVideo.html
new file mode 100644
index 0000000000..772b7f8d5f
--- /dev/null
+++ b/dom/media/webaudio/test/test_mediaStreamTrackAudioSourceNodeVideo.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html>
+<meta charset="utf-8">
+<head>
+ <title>Test MediaStreamTrackAudioSourceNode throw video track</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/dom/media/webaudio/test/webaudio.js"></script>
+ <script type="text/javascript" src="/tests/dom/media/webrtc/tests/mochitests/head.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+ let context = new AudioContext();
+ let canvas = document.createElement("canvas");
+ canvas.getContext("2d");
+ let track = canvas.captureStream().getTracks()[0];
+
+ expectException(() => {
+ new MediaStreamTrackAudioSourceNode(
+ context,
+ { mediaStreamTrack: track });
+ }, DOMException.INVALID_STATE_ERR);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_mixingRules.html b/dom/media/webaudio/test/test_mixingRules.html
new file mode 100644
index 0000000000..719175fbfb
--- /dev/null
+++ b/dom/media/webaudio/test/test_mixingRules.html
@@ -0,0 +1,402 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Testcase for AudioNode channel up-mix/down-mix rules</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+
+<body>
+
+<script>
+
+// This test is based on http://src.chromium.org/viewvc/blink/trunk/LayoutTests/webaudio/audionode-channel-rules.html
+
+var context = null;
+var sp = null;
+var renderNumberOfChannels = 8;
+var singleTestFrameLength = 8;
+var testBuffers;
+
+// A list of connections to an AudioNode input, each of which is to be used in one or more specific test cases.
+// Each element in the list is a string, with the number of connections corresponding to the length of the string,
+// and each character in the string is from '1' to '8' representing a 1 to 8 channel connection (from an AudioNode output).
+// For example, the string "128" means 3 connections, having 1, 2, and 8 channels respectively.
+var connectionsList = [];
+for (var i = 1; i <= 8; ++i) {
+ connectionsList.push(i.toString());
+ for (var j = 1; j <= 8; ++j) {
+ connectionsList.push(i.toString() + j.toString());
+ }
+}
+
+// A list of mixing rules, each of which will be tested against all of the connections in connectionsList.
+var mixingRulesList = [
+ {channelCount: 1, channelCountMode: "max", channelInterpretation: "speakers"},
+ {channelCount: 2, channelCountMode: "clamped-max", channelInterpretation: "speakers"},
+ {channelCount: 3, channelCountMode: "clamped-max", channelInterpretation: "speakers"},
+ {channelCount: 4, channelCountMode: "clamped-max", channelInterpretation: "speakers"},
+ {channelCount: 5, channelCountMode: "clamped-max", channelInterpretation: "speakers"},
+ {channelCount: 6, channelCountMode: "clamped-max", channelInterpretation: "speakers"},
+ {channelCount: 7, channelCountMode: "clamped-max", channelInterpretation: "speakers"},
+ {channelCount: 2, channelCountMode: "explicit", channelInterpretation: "speakers"},
+ {channelCount: 3, channelCountMode: "explicit", channelInterpretation: "speakers"},
+ {channelCount: 4, channelCountMode: "explicit", channelInterpretation: "speakers"},
+ {channelCount: 5, channelCountMode: "explicit", channelInterpretation: "speakers"},
+ {channelCount: 6, channelCountMode: "explicit", channelInterpretation: "speakers"},
+ {channelCount: 7, channelCountMode: "explicit", channelInterpretation: "speakers"},
+ {channelCount: 8, channelCountMode: "explicit", channelInterpretation: "speakers"},
+ {channelCount: 1, channelCountMode: "max", channelInterpretation: "discrete"},
+ {channelCount: 2, channelCountMode: "clamped-max", channelInterpretation: "discrete"},
+ {channelCount: 3, channelCountMode: "clamped-max", channelInterpretation: "discrete"},
+ {channelCount: 4, channelCountMode: "clamped-max", channelInterpretation: "discrete"},
+ {channelCount: 5, channelCountMode: "clamped-max", channelInterpretation: "discrete"},
+ {channelCount: 6, channelCountMode: "clamped-max", channelInterpretation: "discrete"},
+ {channelCount: 3, channelCountMode: "explicit", channelInterpretation: "discrete"},
+ {channelCount: 4, channelCountMode: "explicit", channelInterpretation: "discrete"},
+ {channelCount: 5, channelCountMode: "explicit", channelInterpretation: "discrete"},
+ {channelCount: 6, channelCountMode: "explicit", channelInterpretation: "discrete"},
+ {channelCount: 7, channelCountMode: "explicit", channelInterpretation: "discrete"},
+ {channelCount: 8, channelCountMode: "explicit", channelInterpretation: "discrete"},
+];
+
+var numberOfTests = mixingRulesList.length * connectionsList.length;
+
+// Create an n-channel buffer, with all sample data zero except for a shifted impulse.
+// The impulse position depends on the channel index.
+// For example, for a 4-channel buffer:
+// channel0: 1 0 0 0 0 0 0 0
+// channel1: 0 1 0 0 0 0 0 0
+// channel2: 0 0 1 0 0 0 0 0
+// channel3: 0 0 0 1 0 0 0 0
+function createTestBuffer(numberOfChannels) {
+ var buffer = context.createBuffer(numberOfChannels, singleTestFrameLength, context.sampleRate);
+ for (var i = 0; i < numberOfChannels; ++i) {
+ var data = buffer.getChannelData(i);
+ data[i] = 1;
+ }
+ return buffer;
+}
+
+// Discrete channel interpretation mixing:
+// https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#UpMix
+// up-mix by filling channels until they run out then ignore remaining dest channels.
+// down-mix by filling as many channels as possible, then dropping remaining source channels.
+function discreteSum(sourceBuffer, destBuffer) {
+ if (sourceBuffer.length != destBuffer.length) {
+ is(sourceBuffer.length, destBuffer.length, "source and destination buffers should have the same length");
+ }
+
+ var numberOfChannels = Math.min(sourceBuffer.numberOfChannels, destBuffer.numberOfChannels);
+ var length = sourceBuffer.length;
+
+ for (var c = 0; c < numberOfChannels; ++c) {
+ var source = sourceBuffer.getChannelData(c);
+ var dest = destBuffer.getChannelData(c);
+ for (var i = 0; i < length; ++i) {
+ dest[i] += source[i];
+ }
+ }
+}
+
+// Speaker channel interpretation mixing:
+// https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html#UpMix
+// eslint-disable-next-line complexity
+function speakersSum(sourceBuffer, destBuffer)
+{
+ var numberOfSourceChannels = sourceBuffer.numberOfChannels;
+ var numberOfDestinationChannels = destBuffer.numberOfChannels;
+ var length = destBuffer.length;
+
+ if ((numberOfDestinationChannels == 2 && numberOfSourceChannels == 1) ||
+ (numberOfDestinationChannels == 4 && numberOfSourceChannels == 1)) {
+ // Handle mono -> stereo/Quad case (summing mono channel into both left and right).
+ var source = sourceBuffer.getChannelData(0);
+ var destL = destBuffer.getChannelData(0);
+ var destR = destBuffer.getChannelData(1);
+
+ for (var i = 0; i < length; ++i) {
+ destL[i] += source[i];
+ destR[i] += source[i];
+ }
+ } else if ((numberOfDestinationChannels == 4 && numberOfSourceChannels == 2) ||
+ (numberOfDestinationChannels == 6 && numberOfSourceChannels == 2)) {
+ // Handle stereo -> Quad/5.1 case (summing left and right channels into the output's left and right).
+ var sourceL = sourceBuffer.getChannelData(0);
+ var sourceR = sourceBuffer.getChannelData(1);
+ var destL = destBuffer.getChannelData(0);
+ var destR = destBuffer.getChannelData(1);
+
+ for (var i = 0; i < length; ++i) {
+ destL[i] += sourceL[i];
+ destR[i] += sourceR[i];
+ }
+ } else if (numberOfDestinationChannels == 1 && numberOfSourceChannels == 2) {
+ // Handle stereo -> mono case. output += 0.5 * (input.L + input.R).
+ var sourceL = sourceBuffer.getChannelData(0);
+ var sourceR = sourceBuffer.getChannelData(1);
+ var dest = destBuffer.getChannelData(0);
+
+ for (var i = 0; i < length; ++i) {
+ dest[i] += 0.5 * (sourceL[i] + sourceR[i]);
+ }
+ } else if (numberOfDestinationChannels == 1 && numberOfSourceChannels == 4) {
+ // Handle Quad -> mono case. output += 0.25 * (input.L + input.R + input.SL + input.SR).
+ var sourceL = sourceBuffer.getChannelData(0);
+ var sourceR = sourceBuffer.getChannelData(1);
+ var sourceSL = sourceBuffer.getChannelData(2);
+ var sourceSR = sourceBuffer.getChannelData(3);
+ var dest = destBuffer.getChannelData(0);
+
+ for (var i = 0; i < length; ++i) {
+ dest[i] += 0.25 * (sourceL[i] + sourceR[i] + sourceSL[i] + sourceSR[i]);
+ }
+ } else if (numberOfDestinationChannels == 2 && numberOfSourceChannels == 4) {
+ // Handle Quad -> stereo case. outputLeft += 0.5 * (input.L + input.SL),
+ // outputRight += 0.5 * (input.R + input.SR).
+ var sourceL = sourceBuffer.getChannelData(0);
+ var sourceR = sourceBuffer.getChannelData(1);
+ var sourceSL = sourceBuffer.getChannelData(2);
+ var sourceSR = sourceBuffer.getChannelData(3);
+ var destL = destBuffer.getChannelData(0);
+ var destR = destBuffer.getChannelData(1);
+
+ for (var i = 0; i < length; ++i) {
+ destL[i] += 0.5 * (sourceL[i] + sourceSL[i]);
+ destR[i] += 0.5 * (sourceR[i] + sourceSR[i]);
+ }
+ } else if (numberOfDestinationChannels == 6 && numberOfSourceChannels == 4) {
+ // Handle Quad -> 5.1 case. outputLeft += (inputL, inputR, 0, 0, inputSL, inputSR)
+ var sourceL = sourceBuffer.getChannelData(0);
+ var sourceR = sourceBuffer.getChannelData(1);
+ var sourceSL = sourceBuffer.getChannelData(2);
+ var sourceSR = sourceBuffer.getChannelData(3);
+ var destL = destBuffer.getChannelData(0);
+ var destR = destBuffer.getChannelData(1);
+ var destSL = destBuffer.getChannelData(4);
+ var destSR = destBuffer.getChannelData(5);
+
+ for (var i = 0; i < length; ++i) {
+ destL[i] += sourceL[i];
+ destR[i] += sourceR[i];
+ destSL[i] += sourceSL[i];
+ destSR[i] += sourceSR[i];
+ }
+ } else if (numberOfDestinationChannels == 6 && numberOfSourceChannels == 1) {
+ // Handle mono -> 5.1 case, sum mono channel into center.
+ var source = sourceBuffer.getChannelData(0);
+ var dest = destBuffer.getChannelData(2);
+
+ for (var i = 0; i < length; ++i) {
+ dest[i] += source[i];
+ }
+ } else if (numberOfDestinationChannels == 1 && numberOfSourceChannels == 6) {
+ // Handle 5.1 -> mono.
+ var sourceL = sourceBuffer.getChannelData(0);
+ var sourceR = sourceBuffer.getChannelData(1);
+ var sourceC = sourceBuffer.getChannelData(2);
+ // skip LFE for now, according to current spec.
+ var sourceSL = sourceBuffer.getChannelData(4);
+ var sourceSR = sourceBuffer.getChannelData(5);
+ var dest = destBuffer.getChannelData(0);
+
+ for (var i = 0; i < length; ++i) {
+ dest[i] += 0.7071 * (sourceL[i] + sourceR[i]) + sourceC[i] + 0.5 * (sourceSL[i] + sourceSR[i]);
+ }
+ } else if (numberOfDestinationChannels == 2 && numberOfSourceChannels == 6) {
+ // Handle 5.1 -> stereo.
+ var sourceL = sourceBuffer.getChannelData(0);
+ var sourceR = sourceBuffer.getChannelData(1);
+ var sourceC = sourceBuffer.getChannelData(2);
+ // skip LFE for now, according to current spec.
+ var sourceSL = sourceBuffer.getChannelData(4);
+ var sourceSR = sourceBuffer.getChannelData(5);
+ var destL = destBuffer.getChannelData(0);
+ var destR = destBuffer.getChannelData(1);
+
+ for (var i = 0; i < length; ++i) {
+ destL[i] += sourceL[i] + 0.7071 * (sourceC[i] + sourceSL[i]);
+ destR[i] += sourceR[i] + 0.7071 * (sourceC[i] + sourceSR[i]);
+ }
+ } else if (numberOfDestinationChannels == 4 && numberOfSourceChannels == 6) {
+ // Handle 5.1 -> Quad.
+ var sourceL = sourceBuffer.getChannelData(0);
+ var sourceR = sourceBuffer.getChannelData(1);
+ var sourceC = sourceBuffer.getChannelData(2);
+ // skip LFE for now, according to current spec.
+ var sourceSL = sourceBuffer.getChannelData(4);
+ var sourceSR = sourceBuffer.getChannelData(5);
+ var destL = destBuffer.getChannelData(0);
+ var destR = destBuffer.getChannelData(1);
+ var destSL = destBuffer.getChannelData(2);
+ var destSR = destBuffer.getChannelData(3);
+
+ for (var i = 0; i < length; ++i) {
+ destL[i] += sourceL[i] + 0.7071 * sourceC[i];
+ destR[i] += sourceR[i] + 0.7071 * sourceC[i];
+ destSL[i] += sourceSL[i];
+ destSR[i] += sourceSR[i];
+ }
+ } else {
+ // Fallback for unknown combinations.
+ discreteSum(sourceBuffer, destBuffer);
+ }
+}
+
+function scheduleTest(testNumber, connections, channelCount, channelCountMode, channelInterpretation) {
+ var mixNode = context.createGain();
+ mixNode.channelCount = channelCount;
+ mixNode.channelCountMode = channelCountMode;
+ mixNode.channelInterpretation = channelInterpretation;
+ mixNode.connect(sp);
+
+ for (var i = 0; i < connections.length; ++i) {
+ var connectionNumberOfChannels = connections.charCodeAt(i) - "0".charCodeAt(0);
+
+ var source = context.createBufferSource();
+ // Get a buffer with the right number of channels, converting from 1-based to 0-based index.
+ var buffer = testBuffers[connectionNumberOfChannels - 1];
+ source.buffer = buffer;
+ source.connect(mixNode);
+
+ // Start at the right offset.
+ var sampleFrameOffset = testNumber * singleTestFrameLength;
+ var time = sampleFrameOffset / context.sampleRate;
+ source.start(time);
+ }
+}
+
+function computeNumberOfChannels(connections, channelCount, channelCountMode) {
+ if (channelCountMode == "explicit")
+ return channelCount;
+
+ var computedNumberOfChannels = 1; // Must have at least one channel.
+
+ // Compute "computedNumberOfChannels" based on all the connections.
+ for (var i = 0; i < connections.length; ++i) {
+ var connectionNumberOfChannels = connections.charCodeAt(i) - "0".charCodeAt(0);
+ computedNumberOfChannels = Math.max(computedNumberOfChannels, connectionNumberOfChannels);
+ }
+
+ if (channelCountMode == "clamped-max")
+ computedNumberOfChannels = Math.min(computedNumberOfChannels, channelCount);
+
+ return computedNumberOfChannels;
+}
+
+function checkTestResult(renderedBuffer, testNumber, connections, channelCount, channelCountMode, channelInterpretation) {
+ var computedNumberOfChannels = computeNumberOfChannels(connections, channelCount, channelCountMode);
+
+ // Create a zero-initialized silent AudioBuffer with computedNumberOfChannels.
+ var destBuffer = context.createBuffer(computedNumberOfChannels, singleTestFrameLength, context.sampleRate);
+
+ // Mix all of the connections into the destination buffer.
+ for (var i = 0; i < connections.length; ++i) {
+ var connectionNumberOfChannels = connections.charCodeAt(i) - "0".charCodeAt(0);
+ var sourceBuffer = testBuffers[connectionNumberOfChannels - 1]; // convert from 1-based to 0-based index
+
+ if (channelInterpretation == "speakers") {
+ speakersSum(sourceBuffer, destBuffer);
+ } else if (channelInterpretation == "discrete") {
+ discreteSum(sourceBuffer, destBuffer);
+ } else {
+ ok(false, "Invalid channel interpretation!");
+ }
+ }
+
+ // Validate that destBuffer matches the rendered output.
+ // We need to check the rendered output at a specific sample-frame-offset corresponding
+ // to the specific test case we're checking for based on testNumber.
+
+ var sampleFrameOffset = testNumber * singleTestFrameLength;
+ for (var c = 0; c < renderNumberOfChannels; ++c) {
+ var renderedData = renderedBuffer.getChannelData(c);
+ for (var frame = 0; frame < singleTestFrameLength; ++frame) {
+ var renderedValue = renderedData[frame + sampleFrameOffset];
+
+ var expectedValue = 0;
+ if (c < destBuffer.numberOfChannels) {
+ var expectedData = destBuffer.getChannelData(c);
+ expectedValue = expectedData[frame];
+ }
+
+ if (Math.abs(renderedValue - expectedValue) > 1e-4) {
+ var s = "connections: " + connections + ", " + channelCountMode;
+
+ // channelCount is ignored in "max" mode.
+ if (channelCountMode == "clamped-max" || channelCountMode == "explicit") {
+ s += "(" + channelCount + ")";
+ }
+
+ s += ", " + channelInterpretation + ". ";
+
+ var message = s + "rendered: " + renderedValue + " expected: " + expectedValue + " channel: " + c + " frame: " + frame;
+ is(renderedValue, expectedValue, message);
+ }
+ }
+ }
+}
+
+function checkResult(event) {
+ var buffer = event.inputBuffer;
+
+ // Sanity check result.
+ ok(buffer.length != numberOfTests * singleTestFrameLength ||
+ buffer.numberOfChannels != renderNumberOfChannels, "Sanity check");
+
+ // Check all the tests.
+ var testNumber = 0;
+ for (var m = 0; m < mixingRulesList.length; ++m) {
+ var mixingRules = mixingRulesList[m];
+ for (var i = 0; i < connectionsList.length; ++i, ++testNumber) {
+ checkTestResult(buffer, testNumber, connectionsList[i], mixingRules.channelCount, mixingRules.channelCountMode, mixingRules.channelInterpretation);
+ }
+ }
+
+ sp.onaudioprocess = null;
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+function runTest() {
+ // Create 8-channel offline audio context.
+ // Each test will render 8 sample-frames starting at sample-frame position testNumber * 8.
+ var totalFrameLength = numberOfTests * singleTestFrameLength;
+ context = new AudioContext();
+ var nextPowerOfTwo = 256;
+ while (nextPowerOfTwo < totalFrameLength) {
+ nextPowerOfTwo *= 2;
+ }
+ sp = context.createScriptProcessor(nextPowerOfTwo, renderNumberOfChannels);
+
+ // Set destination to discrete mixing.
+ sp.channelCount = renderNumberOfChannels;
+ sp.channelCountMode = "explicit";
+ sp.channelInterpretation = "discrete";
+
+ // Create test buffers from 1 to 8 channels.
+ testBuffers = new Array();
+ for (var i = 0; i < renderNumberOfChannels; ++i) {
+ testBuffers[i] = createTestBuffer(i + 1);
+ }
+
+ // Schedule all the tests.
+ var testNumber = 0;
+ for (var m = 0; m < mixingRulesList.length; ++m) {
+ var mixingRules = mixingRulesList[m];
+ for (var i = 0; i < connectionsList.length; ++i, ++testNumber) {
+ scheduleTest(testNumber, connectionsList[i], mixingRules.channelCount, mixingRules.channelCountMode, mixingRules.channelInterpretation);
+ }
+ }
+
+ // Render then check results.
+ sp.onaudioprocess = checkResult;
+}
+
+runTest();
+
+</script>
+
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_nodeCreationDocumentGone.html b/dom/media/webaudio/test/test_nodeCreationDocumentGone.html
new file mode 100644
index 0000000000..07a4f7a97d
--- /dev/null
+++ b/dom/media/webaudio/test/test_nodeCreationDocumentGone.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test whether we can create an AudioContext interface</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.requestCompleteLog();
+SimpleTest.waitForExplicitFinish();
+
+var a = window.open("file_nodeCreationDocumentGone.html");
+a.onbeforeunload = function() {
+ setTimeout(function(){
+ try {
+ a.context.createScriptProcessor(512, 1, 1);
+ } catch(e) {
+ ok (true,"got exception");
+ }
+ setTimeout(function() {
+ ok (true,"no crash");
+ SimpleTest.finish();
+ }, 0);
+ }, 0);
+}
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_nodeToParamConnection.html b/dom/media/webaudio/test/test_nodeToParamConnection.html
new file mode 100644
index 0000000000..49098eaffe
--- /dev/null
+++ b/dom/media/webaudio/test/test_nodeToParamConnection.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test connecting an AudioNode to an AudioParam</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 2048,
+ createGraph(context) {
+ var sourceBuffer = context.createBuffer(2, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ sourceBuffer.getChannelData(0)[i] = 1;
+ sourceBuffer.getChannelData(1)[i] = -1;
+ }
+
+ var paramSource = context.createBufferSource();
+ paramSource.buffer = this.buffer;
+
+ var source = context.createBufferSource();
+ source.buffer = sourceBuffer;
+
+ var gain = context.createGain();
+
+ paramSource.connect(gain.gain);
+ source.connect(gain);
+
+ paramSource.start(0);
+ source.start(0);
+ return gain;
+ },
+ createExpectedBuffers(context) {
+ this.buffer = context.createBuffer(2, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ for (var j = 0; j < 2; ++j) {
+ this.buffer.getChannelData(j)[i] = Math.sin(440 * 2 * (j + 1) * Math.PI * i / context.sampleRate);
+ }
+ }
+ var expectedBuffer = context.createBuffer(2, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ expectedBuffer.getChannelData(0)[i] = 1 + (this.buffer.getChannelData(0)[i] + this.buffer.getChannelData(1)[i]) / 2;
+ expectedBuffer.getChannelData(1)[i] = -(1 + (this.buffer.getChannelData(0)[i] + this.buffer.getChannelData(1)[i]) / 2);
+ }
+ return expectedBuffer;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_notAllowedToStartAudioContextGC.html b/dom/media/webaudio/test/test_notAllowedToStartAudioContextGC.html
new file mode 100644
index 0000000000..b8715c1644
--- /dev/null
+++ b/dom/media/webaudio/test/test_notAllowedToStartAudioContextGC.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test GC for not-allow-to-start audio context</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.requestFlakyTimeout(`Checking that something does not happen`);
+
+SimpleTest.waitForExplicitFinish();
+
+var destId;
+
+function observer(subject, topic, data) {
+ let id = parseInt(data);
+ ok(id != destId, "dropping another node, not the context's destination");
+}
+
+SpecialPowers.addAsyncObserver(observer, "webaudio-node-demise", false);
+SimpleTest.registerCleanupFunction(function() {
+ SpecialPowers.removeAsyncObserver(observer, "webaudio-node-demise");
+});
+
+SpecialPowers.pushPrefEnv({"set": [["media.autoplay.default", SpecialPowers.Ci.nsIAutoplay.BLOCKED],
+ ["media.autoplay.blocking_policy", 0]]},
+ startTest);
+
+function startTest() {
+ info("- create audio context -");
+ let ac = new AudioContext();
+
+ info("- get node Id -");
+ destId = SpecialPowers.getPrivilegedProps(ac.destination, "id");
+
+ info("- trigger GCs -");
+ SpecialPowers.forceGC();
+ SpecialPowers.forceCC();
+ SpecialPowers.forceGC();
+
+ info("- after three GCs -");
+
+ // We're doing this async so that we can receive observerservice messages.
+ setTimeout(function() {
+ ok(true, `AudioContext that has been prevented
+ from starting has correctly survived GC`)
+ SimpleTest.finish();
+ }, 1);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_offlineDestinationChannelCountLess.html b/dom/media/webaudio/test/test_offlineDestinationChannelCountLess.html
new file mode 100644
index 0000000000..8ff1deac4b
--- /dev/null
+++ b/dom/media/webaudio/test/test_offlineDestinationChannelCountLess.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test OfflineAudioContext with a channel count less than the specified number</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var ctx = new OfflineAudioContext(2, 100, 22050);
+
+ var buf = ctx.createBuffer(6, 100, ctx.sampleRate);
+ for (var i = 0; i < 6; ++i) {
+ for (var j = 0; j < 100; ++j) {
+ buf.getChannelData(i)[j] = Math.sin(2 * Math.PI * 200 * j / ctx.sampleRate);
+ }
+ }
+
+ var src = ctx.createBufferSource();
+ src.buffer = buf;
+ src.start(0);
+ src.connect(ctx.destination);
+ ctx.destination.channelCountMode = "max";
+ ctx.startRendering();
+ ctx.oncomplete = function(e) {
+ is(e.renderedBuffer.numberOfChannels, 2, "Correct expected number of buffers");
+ compareChannels(e.renderedBuffer.getChannelData(0), buf.getChannelData(0));
+ compareChannels(e.renderedBuffer.getChannelData(1), buf.getChannelData(1));
+
+ SimpleTest.finish();
+ };
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_offlineDestinationChannelCountMore.html b/dom/media/webaudio/test/test_offlineDestinationChannelCountMore.html
new file mode 100644
index 0000000000..fa38114e2b
--- /dev/null
+++ b/dom/media/webaudio/test/test_offlineDestinationChannelCountMore.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test OfflineAudioContext with a channel count less than the specified number</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var ctx = new OfflineAudioContext(6, 100, 22050);
+
+ var buf = ctx.createBuffer(2, 100, ctx.sampleRate);
+ for (var i = 0; i < 2; ++i) {
+ for (var j = 0; j < 100; ++j) {
+ buf.getChannelData(i)[j] = Math.sin(2 * Math.PI * 200 * j / ctx.sampleRate);
+ }
+ }
+ var emptyBuffer = ctx.createBuffer(1, 100, ctx.sampleRate);
+
+ var src = ctx.createBufferSource();
+ src.buffer = buf;
+ src.start(0);
+ src.connect(ctx.destination);
+ ctx.destination.channelCountMode = "max";
+ ctx.startRendering();
+ ctx.oncomplete = function(e) {
+ is(e.renderedBuffer.numberOfChannels, 6, "Correct expected number of buffers");
+ compareChannels(e.renderedBuffer.getChannelData(0), buf.getChannelData(0));
+ compareChannels(e.renderedBuffer.getChannelData(1), buf.getChannelData(1));
+ for (var i = 2; i < 6; ++i) {
+ compareChannels(e.renderedBuffer.getChannelData(i), emptyBuffer.getChannelData(0));
+ }
+
+ SimpleTest.finish();
+ };
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_oscillatorNode.html b/dom/media/webaudio/test/test_oscillatorNode.html
new file mode 100644
index 0000000000..e2a47a4e1e
--- /dev/null
+++ b/dom/media/webaudio/test/test_oscillatorNode.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test the OscillatorNode interface</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+
+ var context = new AudioContext();
+ var osc = new OscillatorNode(context);
+
+ is(osc.channelCount, 2, "Oscillator node has 2 input channels by default");
+ is(osc.channelCountMode, "max", "Correct channelCountMode for the Oscillator node");
+ is(osc.channelInterpretation, "speakers", "Correct channelCountInterpretation for the Oscillator node");
+ is(osc.type, "sine", "Correct default type");
+ expectException(function() {
+ osc.type = "custom";
+ }, DOMException.INVALID_STATE_ERR);
+ is(osc.type, "sine", "Cannot set the type to custom");
+ is(osc.frequency.value, 440, "Correct default frequency value");
+ is(osc.detune.value, 0, "Correct default detine value");
+
+ // Make sure that we can set all of the valid type values
+ var types = [
+ "sine",
+ "square",
+ "sawtooth",
+ "triangle",
+ ];
+ for (var i = 0; i < types.length; ++i) {
+ osc.type = types[i];
+ }
+
+ // Verify setPeriodicWave()
+ var real = new Float32Array([1.0, 0.5, 0.25, 0.125]);
+ var imag = new Float32Array([1.0, 0.7, -1.0, 0.5]);
+ osc.setPeriodicWave(context.createPeriodicWave(real, imag));
+ is(osc.type, "custom", "Failed to set custom waveform");
+
+ expectNoException(function() {
+ osc.start();
+ });
+ expectNoException(function() {
+ osc.stop();
+ });
+
+ SimpleTest.finish();
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_oscillatorNode2.html b/dom/media/webaudio/test/test_oscillatorNode2.html
new file mode 100644
index 0000000000..69a6655ff1
--- /dev/null
+++ b/dom/media/webaudio/test/test_oscillatorNode2.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test OscillatorNode lifetime and sine phase</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script src="webaudio.js" type="text/javascript"></script>
+<script class="testbody" type="text/javascript">
+
+const signalLength = 2048;
+
+function createOscillator(context) {
+ var osc = context.createOscillator();
+ osc.start(0);
+ osc.stop(signalLength/context.sampleRate);
+ return osc;
+}
+
+function connectUnreferencedOscillator(context, destination) {
+ var osc = createOscillator(context);
+ osc.connect(destination);
+}
+
+var gTest = {
+ length: signalLength,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var blend = context.createGain();
+
+ connectUnreferencedOscillator(context, blend);
+ // Test that the unreferenced oscillator remains alive until it has finished.
+ SpecialPowers.forceGC();
+ SpecialPowers.forceCC();
+
+ // Create another sine wave oscillator in negative time, which should
+ // cancel when mixed with the unreferenced oscillator.
+ var oscillator = createOscillator(context);
+ oscillator.frequency.value = -440;
+ oscillator.connect(blend);
+
+ return blend;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_oscillatorNodeNegativeFrequency.html b/dom/media/webaudio/test/test_oscillatorNodeNegativeFrequency.html
new file mode 100644
index 0000000000..c46c0fea13
--- /dev/null
+++ b/dom/media/webaudio/test/test_oscillatorNodeNegativeFrequency.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test the OscillatorNode when the frequency is negative</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+
+ var types = ["sine",
+ "square",
+ "sawtooth",
+ "triangle"];
+
+ var finished = 0;
+ function finish() {
+ if (++finished == types.length) {
+ SimpleTest.finish();
+ }
+ }
+
+ types.forEach(function(t) {
+ var context = new OfflineAudioContext(1, 256, 44100);
+ var osc = context.createOscillator();
+
+ osc.frequency.value = -440;
+ osc.type = t;
+
+ osc.connect(context.destination);
+ osc.start();
+ context.startRendering().then(function(buffer) {
+ var samples = buffer.getChannelData(0);
+ // This samples the wave form in the middle of the first period, the value
+ // should be negative.
+ ok(samples[Math.floor(44100 / 440 / 4)] < 0., "Phase should be inverted when using a " + t + " waveform");
+ finish();
+ });
+ });
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_oscillatorNodePassThrough.html b/dom/media/webaudio/test/test_oscillatorNodePassThrough.html
new file mode 100644
index 0000000000..63c0848d06
--- /dev/null
+++ b/dom/media/webaudio/test/test_oscillatorNodePassThrough.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test Oscillator with passthrough</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var buffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+
+ var source = context.createOscillator();
+
+ var srcWrapped = SpecialPowers.wrap(source);
+ ok("passThrough" in srcWrapped, "OscillatorNode should support the passThrough API");
+ srcWrapped.passThrough = true;
+
+ source.start(0);
+ return source;
+ },
+ createExpectedBuffers(context) {
+ var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate);
+
+ return [expectedBuffer];
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_oscillatorNodeStart.html b/dom/media/webaudio/test/test_oscillatorNodeStart.html
new file mode 100644
index 0000000000..4df129170f
--- /dev/null
+++ b/dom/media/webaudio/test/test_oscillatorNodeStart.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test the OscillatorNode interface</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+
+ var context = new AudioContext();
+ var osc = context.createOscillator();
+ var sp = context.createScriptProcessor(0, 1, 0);
+
+ osc.connect(sp);
+
+ sp.onaudioprocess = function (e) {
+ var input = e.inputBuffer.getChannelData(0);
+ var isSilent = true;
+ for (var i = 0; i < input.length; i++) {
+ if (input[i] != 0.0) {
+ isSilent = false;
+ }
+ }
+ sp.onaudioprocess = null;
+ ok(isSilent, "OscillatorNode should be silent before calling start.");
+ SimpleTest.finish();
+ }
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_oscillatorTypeChange.html b/dom/media/webaudio/test/test_oscillatorTypeChange.html
new file mode 100644
index 0000000000..e4b4944703
--- /dev/null
+++ b/dom/media/webaudio/test/test_oscillatorTypeChange.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test OscillatorNode type change after it has started and triangle phase</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+const bufferSize = 1024;
+
+function startTest() {
+ var ctx = new AudioContext();
+
+ var oscillator1 = ctx.createOscillator();
+ oscillator1.connect(ctx.destination);
+ oscillator1.start(0);
+
+ // Assuming the above Web Audio operations have already scheduled an event
+ // to run in stable state and start the graph thread, schedule a subsequent
+ // event to change the type of oscillator1.
+ SimpleTest.executeSoon(function() {
+ oscillator1.type = "triangle";
+
+ // Another triangle wave with -1 gain should cancel the first. This is
+ // starting at the same time as the type change, assuming that the phase
+ // is reset on type change. A negative frequency should achieve the same
+ // as the -1 gain but for bug 916285.
+ var oscillator2 = ctx.createOscillator();
+ oscillator2.type = "triangle";
+ oscillator2.start(0);
+
+ var processor = ctx.createScriptProcessor(bufferSize, 1, 0);
+ oscillator1.connect(processor);
+ var gain = ctx.createGain();
+ gain.gain.value = -1;
+ gain.connect(processor);
+ oscillator2.connect(gain);
+
+ processor.onaudioprocess = function(e) {
+ compareChannels(e.inputBuffer.getChannelData(0),
+ new Float32Array(bufferSize));
+ e.target.onaudioprocess = null;
+ SimpleTest.finish();
+ }
+ });
+};
+
+startTest();
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_pannerNode.html b/dom/media/webaudio/test/test_pannerNode.html
new file mode 100644
index 0000000000..7f4d3ea915
--- /dev/null
+++ b/dom/media/webaudio/test/test_pannerNode.html
@@ -0,0 +1,71 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test PannerNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+function near(a, b, msg) {
+ ok(Math.abs(a - b) < 1e-4, msg);
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var context = new AudioContext();
+ var buffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+
+ var destination = context.destination;
+
+ var source = context.createBufferSource();
+
+ var panner = new PannerNode(context);
+
+ source.buffer = buffer;
+
+ source.connect(panner);
+ panner.connect(destination);
+
+ // Verify default values
+ is(panner.panningModel, "equalpower", "Correct default value for panning model");
+ is(panner.distanceModel, "inverse", "Correct default value for distance model");
+ near(panner.refDistance, 1, "Correct default value for ref distance");
+ near(panner.maxDistance, 10000, "Correct default value for max distance");
+ near(panner.rolloffFactor, 1, "Correct default value for rolloff factor");
+ near(panner.coneInnerAngle, 360, "Correct default value for cone inner angle");
+ near(panner.coneOuterAngle, 360, "Correct default value for cone outer angle");
+ near(panner.coneOuterGain, 0, "Correct default value for cone outer gain");
+ is(panner.channelCount, 2, "panner node has 2 input channels by default");
+ is(panner.channelCountMode, "clamped-max", "Correct channelCountMode for the panner node");
+ is(panner.channelInterpretation, "speakers", "Correct channelCountInterpretation for the panner node");
+
+ panner.setPosition(1, 1, 1);
+ near(panner.positionX.value, 1, "setPosition sets AudioParam properly");
+ near(panner.positionY.value, 1, "setPosition sets AudioParam properly");
+ near(panner.positionZ.value, 1, "setPosition sets AudioParam properly");
+
+ panner.setOrientation(0, 1, 0);
+ near(panner.orientationX.value, 0, "setOrientation sets AudioParam properly");
+ near(panner.orientationY.value, 1, "setOrientation sets AudioParam properly");
+ near(panner.orientationZ.value, 0, "setOrientation sets AudioParam properly");
+
+ source.start(0);
+ SimpleTest.executeSoon(function() {
+ source.stop(0);
+ source.disconnect();
+ panner.disconnect();
+
+ SimpleTest.finish();
+ });
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_pannerNodeAbove.html b/dom/media/webaudio/test/test_pannerNodeAbove.html
new file mode 100644
index 0000000000..5931fa04de
--- /dev/null
+++ b/dom/media/webaudio/test/test_pannerNodeAbove.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test PannerNode directly above</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script src="webaudio.js" type="text/javascript"></script>
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ numberOfChannels: 2,
+ createGraph(context) {
+ // An up vector will be made perpendicular to the front vector, in the
+ // front-up plane.
+ context.listener.setOrientation(0, 6.311749985202524e+307, 0, 0.1, 1000, 0);
+ // Linearly dependent vectors are ignored.
+ context.listener.setOrientation(0, 0, -6.311749985202524e+307, 0, 0, 6.311749985202524e+307);
+ var panner = context.createPanner();
+ panner.positionX.value = 2; // directly above
+ panner.rolloffFactor = 0; // no distance gain
+ panner.panningModel = "equalpower"; // no effect when directly above
+
+ var source = context.createBufferSource();
+ source.buffer = this.buffer;
+ source.connect(panner);
+ source.start(0);
+
+ return panner;
+ },
+ createExpectedBuffers(context) {
+ var expectedBuffer = context.createBuffer(2, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ // Different signals in left and right buffers
+ expectedBuffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ expectedBuffer.getChannelData(1)[i] = Math.sin(220 * 2 * Math.PI * i / context.sampleRate);
+ }
+ this.buffer = expectedBuffer;
+ return expectedBuffer;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_pannerNodeAtZeroDistance.html b/dom/media/webaudio/test/test_pannerNodeAtZeroDistance.html
new file mode 100644
index 0000000000..b5aff589ee
--- /dev/null
+++ b/dom/media/webaudio/test/test_pannerNodeAtZeroDistance.html
@@ -0,0 +1,140 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test PannerNode produces output even when the even when the distance is from the listener is zero</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+const BUF_SIZE = 1024;
+const sampleRate = 44100;
+var types = [
+ "equalpower",
+ "HRTF"
+]
+
+async function testMono(panningModel) {
+ var ac = new OfflineAudioContext(2, BUF_SIZE, sampleRate);
+
+ // A sine to be used to fill the buffers
+ function sine(t) {
+ return Math.sin(440 * 2 * Math.PI * t / ac.sampleRate);
+ }
+
+ var monoBuffer = ac.createBuffer(1, BUF_SIZE, ac.sampleRate);
+ for (var i = 0; i < BUF_SIZE; ++i) {
+ monoBuffer.getChannelData(0)[i] = sine(i);
+ }
+
+ var monoSource = ac.createBufferSource();
+ monoSource.buffer = monoBuffer;
+ monoSource.start(0);
+
+ var panner = new PannerNode(ac, {
+ panningModel,
+ distanceModel: "linear",
+ });
+ monoSource.connect(panner);
+
+ var panner2 = new PannerNode(ac, {
+ panningModel,
+ distanceModel: "inverse",
+ });
+ panner.connect(panner2);
+
+ var panner3 = new PannerNode(ac, {
+ panningModel,
+ distanceModel: "exponential"
+ });
+ panner2.connect(panner3);
+
+ panner3.connect(ac.destination);
+
+ const buffer = await ac.startRendering();
+
+ if (panningModel == "equalpower") {
+ // Use the input buffer to compare the output. According to the spec,
+ // mono input at zero distance will apply gain = cos(0.5 * Math.PI / 2)
+ // https://webaudio.github.io/web-audio-api/#Spatialzation-equal-power-panning
+ const gain = Math.cos(0.5 * Math.PI / 2);
+ for (var i = 0; i < BUF_SIZE; ++i) {
+ monoBuffer.getChannelData(0)[i] = gain * monoBuffer.getChannelData(0)[i];
+ }
+ compareChannels(buffer.getChannelData(0), monoBuffer.getChannelData(0));
+ } else {
+ ok(!isChannelSilent(buffer.getChannelData(0)),
+ "mono panning: expect non-zero left channel");
+ }
+ // Check symmetry
+ compareChannels(buffer.getChannelData(0), buffer.getChannelData(1));
+}
+
+async function testStereo(panningModel) {
+ var ac = new OfflineAudioContext(2, BUF_SIZE, sampleRate);
+
+ // A sine to be used to fill the buffers
+ function sine(t) {
+ return Math.sin(440 * 2 * Math.PI * t / ac.sampleRate);
+ }
+
+ var stereoBuffer = ac.createBuffer(2, BUF_SIZE, ac.sampleRate);
+ for (var i = 0; i < BUF_SIZE; ++i) {
+ stereoBuffer.getChannelData(0)[i] = sine(i);
+ stereoBuffer.getChannelData(1)[i] = sine(i);
+ }
+
+ var stereoSource = ac.createBufferSource();
+ stereoSource.buffer = stereoBuffer;
+ stereoSource.start(0);
+
+ var panner = new PannerNode(ac, {
+ panningModel,
+ distanceModel: "linear",
+ });
+ stereoSource.connect(panner);
+
+ var panner2 = new PannerNode(ac, {
+ panningModel,
+ distanceModel: "inverse",
+ });
+ panner.connect(panner2);
+
+ var panner3 = new PannerNode(ac, {
+ panningModel,
+ distanceModel: "exponential",
+ });
+ panner2.connect(panner3);
+
+ panner3.connect(ac.destination);
+
+ const buffer = await ac.startRendering();
+ if (panningModel == "equalpower") {
+ compareBuffers(buffer, stereoBuffer);
+ } else {
+ ok(!isChannelSilent(buffer.getChannelData(0)),
+ "stereo panning: expect non-zero left channel");
+ // Check symmetry
+ compareChannels(buffer.getChannelData(0), buffer.getChannelData(1));
+ }
+}
+
+async function test(type) {
+ await promiseHRTFReady(sampleRate);
+ await testMono(type)
+ await testStereo(type);
+}
+
+add_task(async function() {
+ for (const panningModel of types) {
+ await test(panningModel);
+ }
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_pannerNodeChannelCount.html b/dom/media/webaudio/test/test_pannerNodeChannelCount.html
new file mode 100644
index 0000000000..9cb90f32da
--- /dev/null
+++ b/dom/media/webaudio/test/test_pannerNodeChannelCount.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test PannerNode directly above</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script src="webaudio.js" type="text/javascript"></script>
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 2,
+ createGraph(context) {
+ var buffer = context.createBuffer(2, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ var sample = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ // When mixed into a single channel, this produces silence
+ buffer.getChannelData(0)[i] = sample;
+ buffer.getChannelData(1)[i] = -sample;
+ }
+
+ var panner = context.createPanner();
+ panner.positionX.value = 1;
+ panner.positionY.value = 2;
+ panner.positionZ.value = 3;
+ panner.channelCount = 1;
+ expectException(function() { panner.channelCount = 3; },
+ DOMException.NOT_SUPPORTED_ERR);
+ panner.channelCountMode = "explicit";
+ expectException(function() { panner.channelCountMode = "max"; },
+ DOMException.NOT_SUPPORTED_ERR);
+ panner.channelInterpretation = "discrete";
+ panner.channelInterpretation = "speakers";
+
+ var source = context.createBufferSource();
+ source.buffer = buffer;
+ source.connect(panner);
+ source.start(0);
+
+ return panner;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_pannerNodeHRTFSymmetry.html b/dom/media/webaudio/test/test_pannerNodeHRTFSymmetry.html
new file mode 100644
index 0000000000..cd7b623e41
--- /dev/null
+++ b/dom/media/webaudio/test/test_pannerNodeHRTFSymmetry.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test left/right symmetry and block-offset invariance of HRTF panner</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+const blockSize = 128;
+const bufferSize = 4096; // > HRTF panner latency
+
+var ctx = new AudioContext();
+
+function startTest() {
+ var leftPanner = ctx.createPanner();
+ var rightPanner = ctx.createPanner();
+ leftPanner.panningModel = "HRTF";
+ rightPanner.panningModel = "HRTF";
+ leftPanner.positionX.value = -1;
+ rightPanner.positionX.value = 1;
+
+ // Test that PannerNode processes the signal consistently irrespective of
+ // the offset in the processing block. This is done by inserting a delay of
+ // less than a block size before one panner.
+ const delayTime = 0.7 * blockSize / ctx.sampleRate;
+ var leftDelay = ctx.createDelay(delayTime);
+ leftDelay.delayTime.value = delayTime;
+ leftDelay.connect(leftPanner);
+ // and compensating for the delay after the other.
+ var rightDelay = ctx.createDelay(delayTime);
+ rightDelay.delayTime.value = delayTime;
+ rightPanner.connect(rightDelay);
+
+ // Feed the panners with a signal having some harmonics to fill the spectrum.
+ var oscillator = ctx.createOscillator();
+ oscillator.frequency.value = 110;
+ oscillator.type = "sawtooth";
+ oscillator.connect(leftDelay);
+ oscillator.connect(rightPanner);
+ oscillator.start(0);
+
+ // Switch the channels on one panner output, and it should match the other.
+ var splitter = ctx.createChannelSplitter();
+ leftPanner.connect(splitter);
+ var merger = ctx.createChannelMerger();
+ splitter.connect(merger, 0, 1);
+ splitter.connect(merger, 1, 0);
+
+ // Invert one signal so that mixing with the other will find the difference.
+ var gain = ctx.createGain();
+ gain.gain.value = -1.0;
+ merger.connect(gain);
+
+ var processor = ctx.createScriptProcessor(bufferSize, 2, 0);
+ gain.connect(processor);
+ rightDelay.connect(processor);
+ processor.onaudioprocess =
+ function(e) {
+ compareBuffers(e.inputBuffer,
+ ctx.createBuffer(2, bufferSize, ctx.sampleRate));
+ e.target.onaudioprocess = null;
+ SimpleTest.finish();
+ }
+}
+
+promiseHRTFReady(ctx.sampleRate).then(startTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_pannerNodePassThrough.html b/dom/media/webaudio/test/test_pannerNodePassThrough.html
new file mode 100644
index 0000000000..d8c809a2e2
--- /dev/null
+++ b/dom/media/webaudio/test/test_pannerNodePassThrough.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test PannerNode with passthrough</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var source = context.createBufferSource();
+
+ var panner = context.createPanner();
+
+ source.buffer = this.buffer;
+
+ source.connect(panner);
+
+ context.listener.setOrientation(0, 6.311749985202524e+307, 0, 0.1, 1000, 0);
+ context.listener.setOrientation(0, 0, -6.311749985202524e+307, 0, 0, 6.311749985202524e+307);
+ panner.positionX = 2;
+ panner.rolloffFactor = 0;
+ panner.panningModel = "equalpower";
+
+ var pannerWrapped = SpecialPowers.wrap(panner);
+ ok("passThrough" in pannerWrapped, "PannerNode should support the passThrough API");
+ pannerWrapped.passThrough = true;
+
+ source.start(0);
+ return panner;
+ },
+ createExpectedBuffers(context) {
+ this.buffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ this.buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+
+ return [this.buffer];
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_pannerNodeTail.html b/dom/media/webaudio/test/test_pannerNodeTail.html
new file mode 100644
index 0000000000..7035780bf2
--- /dev/null
+++ b/dom/media/webaudio/test/test_pannerNodeTail.html
@@ -0,0 +1,200 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test tail time lifetime of PannerNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+// This tests that a PannerNode does not release its reference before
+// it finishes emitting sound.
+//
+// The PannerNode tail time is short, so, when a PannerNode is destroyed on
+// the main thread, it is unlikely to notify the graph thread before the tail
+// time expires. However, by adding DelayNodes downstream from the
+// PannerNodes, the graph thread can have enough time to notice that a
+// DelayNode has been destroyed.
+//
+// In the current implementation, DelayNodes will take a tail-time reference
+// immediately when they receive the first block of sound from an upstream
+// node, so this test connects the downstream DelayNodes while the upstream
+// nodes are finishing, and then runs GC (on the main thread) before the
+// DelayNodes receive any input (on the graph thread).
+//
+// Web Audio doesn't provide a means to precisely time connect()s but we can
+// test that the output of delay nodes matches the output from a reference
+// PannerNode that we know will not be GCed.
+//
+// Another set of delay nodes is added upstream to ensure that the source node
+// has removed its self-reference after dispatching its "ended" event.
+
+SimpleTest.waitForExplicitFinish();
+
+const blockSize = 128;
+// bufferSize should be long enough that to allow an audioprocess event to be
+// sent to the main thread and a connect message to return to the graph
+// thread.
+const bufferSize = 4096;
+const pannerCount = bufferSize / blockSize;
+// sourceDelayBufferCount should be long enough to allow the source node
+// onended to finish and remove the source self-reference.
+const sourceDelayBufferCount = 3;
+var gotEnded = false;
+// ccDelayLength should be long enough to allow CC to run
+var ccDelayBufferCount = 20;
+const ccDelayLength = ccDelayBufferCount * bufferSize;
+
+var ctx = new AudioContext();
+var testPanners = [];
+var referencePanner = new PannerNode(ctx, {panningModel: "HRTF"});
+var referenceProcessCount = 0;
+var referenceOutput = [new Float32Array(bufferSize),
+ new Float32Array(bufferSize)];
+var testProcessor;
+var testProcessCount = 0;
+
+function onReferenceOutput(e) {
+ switch(referenceProcessCount) {
+
+ case sourceDelayBufferCount - 1:
+ // The panners are about to finish.
+ if (!gotEnded) {
+ todo(false, "Source hasn't ended. Increase sourceDelayBufferCount?");
+ }
+
+ // Connect each PannerNode output to a downstream DelayNode,
+ // and connect ScriptProcessors to compare test and reference panners.
+ var delayDuration = ccDelayLength / ctx.sampleRate;
+ for (var i = 0; i < pannerCount; ++i) {
+ var delay = ctx.createDelay(delayDuration);
+ delay.delayTime.value = delayDuration;
+ delay.connect(testProcessor);
+ testPanners[i].connect(delay);
+ }
+ testProcessor = null;
+ testPanners = null;
+
+ // The panning effect is linear so only one reference panner is required.
+ // This also checks that the individual panners don't chop their output
+ // too soon.
+ referencePanner.connect(e.target);
+
+ // Assuming the above operations have already scheduled an event to run in
+ // stable state and ask the graph thread to make connections, schedule a
+ // subsequent event to run cycle collection, which should not collect
+ // panners that are still producing sound.
+ SimpleTest.executeSoon(function() {
+ SpecialPowers.forceGC();
+ SpecialPowers.forceCC();
+ });
+
+ break;
+
+ case sourceDelayBufferCount:
+ // Record this buffer during which PannerNode outputs were connected.
+ for (var i = 0; i < 2; ++i) {
+ e.inputBuffer.copyFromChannel(referenceOutput[i], i);
+ }
+ e.target.onaudioprocess = null;
+ e.target.disconnect();
+
+ // If the buffer is silent, there is probably not much point just
+ // increasing the buffer size, because, with the buffer size already
+ // significantly larger than panner tail time, it demonstrates that the
+ // lag between threads is much greater than the tail time.
+ if (isChannelSilent(referenceOutput[0])) {
+ todo(false, "Connections not detected.");
+ }
+ }
+
+ referenceProcessCount++;
+}
+
+function onTestOutput(e) {
+ if (testProcessCount < sourceDelayBufferCount + ccDelayBufferCount) {
+ testProcessCount++;
+ return;
+ }
+
+ for (var i = 0; i < 2; ++i) {
+ compareChannels(e.inputBuffer.getChannelData(i), referenceOutput[i]);
+ }
+ e.target.onaudioprocess = null;
+ e.target.disconnect();
+ SimpleTest.finish();
+}
+
+function startTest() {
+ // 0.002 is MaxDelayTimeSeconds in HRTFpanner.cpp
+ // and 512 is fftSize() at 48 kHz.
+ const expectedPannerTailTime = 0.002 * ctx.sampleRate + 512;
+
+ // Place the listener to the side of the origin, where the panners are
+ // positioned, to maximize delay in one ear.
+ ctx.listener.setPosition(1,0,0);
+
+ // Create some PannerNodes downstream from DelayNodes with delays long
+ // enough for their source to finish, dispatch its "ended" event
+ // and release its playing reference. The DelayNodes should expire their
+ // tail-time references before the PannerNodes and so only the PannerNode
+ // lifetimes depends on their tail-time references. Many DelayNodes are
+ // created and timed to finish at different times so that one PannerNode
+ // will be finishing the block processed immediately after the connect is
+ // received.
+ var source = ctx.createBufferSource();
+ // Just short of blockSize here to avoid rounding into the next block
+ var buffer = ctx.createBuffer(1, blockSize - 1, ctx.sampleRate);
+ for (var i = 0; i < buffer.length; ++i) {
+ buffer.getChannelData(0)[i] = Math.cos(Math.PI * i / buffer.length);
+ }
+ source.buffer = buffer;
+ source.start(0);
+ source.onended = function(e) {
+ gotEnded = true;
+ };
+
+ // Time the first test panner to finish just before downstream DelayNodes
+ // are about the be connected. Note that DelayNode lifetime depends on
+ // maxDelayTime so set that equal to the delay.
+ var delayDuration =
+ (sourceDelayBufferCount * bufferSize
+ - expectedPannerTailTime - 2 * blockSize) / ctx.sampleRate;
+
+ for (var i = 0; i < pannerCount; ++i) {
+ var delay = ctx.createDelay(delayDuration);
+ delay.delayTime.value = delayDuration;
+ source.connect(delay);
+ delay.connect(referencePanner)
+
+ var panner = ctx.createPanner();
+ panner.panningModel = "HRTF";
+ delay.connect(panner);
+ testPanners[i] = panner;
+
+ delayDuration += blockSize / ctx.sampleRate;
+ }
+
+ // Create a ScriptProcessor now to use as a timer to trigger connection of
+ // downstream nodes. It will also be used to record reference output.
+ var referenceProcessor = ctx.createScriptProcessor(bufferSize, 2, 0);
+ referenceProcessor.onaudioprocess = onReferenceOutput;
+ // Start audioprocess events before source delays are connected.
+ referenceProcessor.connect(ctx.destination);
+
+ // The test ScriptProcessor will record output of testPanners.
+ // Create it now so that it is synchronized with the referenceProcessor.
+ testProcessor = ctx.createScriptProcessor(bufferSize, 2, 0);
+ testProcessor.onaudioprocess = onTestOutput;
+ // Start audioprocess events before source delays are connected.
+ testProcessor.connect(ctx.destination);
+}
+
+promiseHRTFReady(ctx.sampleRate).then(startTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_pannerNode_audioparam_distance.html b/dom/media/webaudio/test/test_pannerNode_audioparam_distance.html
new file mode 100644
index 0000000000..2d955de19d
--- /dev/null
+++ b/dom/media/webaudio/test/test_pannerNode_audioparam_distance.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Distance effect of a PannerNode with the position set via AudioParams (Bug 1472550)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+ SimpleTest.waitForExplicitFinish();
+ var o = new OfflineAudioContext(2, 256, 44100);
+
+ // We want a stereo constant source.
+ var b = o.createBuffer(2, 1, 44100);
+ b.getChannelData(0)[0] = 1;
+ b.getChannelData(1)[0] = 1;
+ var c = o.createBufferSource();
+ c.buffer = b;
+ c.loop = true;
+
+ var p = o.createPanner();
+ p.positionY.setValueAtTime(1, 0);
+ p.positionX.setValueAtTime(1, 0);
+ p.positionZ.setValueAtTime(1, 0);
+
+ // Set the listener somewhere far
+ o.listener.setPosition(20, 2, 20);
+
+ c.start();
+ c.connect(p).connect(o.destination);
+
+ o.startRendering().then((ab) => {
+ // Check that the distance attenuates the sound.
+ ok(ab.getChannelData(0)[0] < 0.1, "left channel must be very quiet");
+ ok(ab.getChannelData(1)[0] < 0.1, "right channel must be very quiet");
+ SimpleTest.finish();
+ });
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_pannerNode_equalPower.html b/dom/media/webaudio/test/test_pannerNode_equalPower.html
new file mode 100644
index 0000000000..127a87b254
--- /dev/null
+++ b/dom/media/webaudio/test/test_pannerNode_equalPower.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test PannerNode</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script type="text/javascript" src="webaudio.js"></script>
+<script type="text/javascript" src="layouttest-glue.js"></script>
+<script type="text/javascript" src="blink/audio-testing.js"></script>
+<script type="text/javascript" src="blink/panner-model-testing.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ function checkFinished() {
+ SimpleTest.finish();
+ }
+ var ctx = new OfflineAudioContext(2, sampleRate * renderLengthSeconds, sampleRate);
+ createTestAndRun(ctx, nodesToCreate, 2, checkFinished);
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_pannerNode_maxDistance.html b/dom/media/webaudio/test/test_pannerNode_maxDistance.html
new file mode 100644
index 0000000000..bb70b0a0ec
--- /dev/null
+++ b/dom/media/webaudio/test/test_pannerNode_maxDistance.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test PannerNode outputs silence when the distance is greater than maxDist</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+// BUF_SIZE must be greater than HRTF delay.
+const BUF_SIZE = 1024;
+const sampleRate = 44100;
+var types = [
+ "equalpower",
+ "HRTF"
+]
+
+async function test(panningModel) {
+ var ac = new OfflineAudioContext(1, 1024, sampleRate);
+ var osc = ac.createOscillator();
+ var panner = new PannerNode(ac, {
+ panningModel,
+ distanceModel: "linear",
+ maxDistance: 100,
+ positionY: 200,
+ });
+
+ ac.listener.setPosition(0, 0, 0);
+
+ osc.connect(panner);
+ panner.connect(ac.destination);
+
+ osc.start();
+
+ const buffer = await ac.startRendering()
+
+ var silence = true;
+ var array = buffer.getChannelData(0);
+ for (var i = 0; i < buffer.length; i++) {
+ if (array[i] != 0) {
+ ok(false, "Found noise in the buffer.");
+ silence = false;
+ }
+ }
+ ok(silence, "The buffer is silent.");
+}
+
+add_task(async function() {
+ await promiseHRTFReady(sampleRate);
+ for (const panningModel of types) {
+ await test(panningModel);
+ }
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_periodicWave.html b/dom/media/webaudio/test/test_periodicWave.html
new file mode 100644
index 0000000000..7b8a6ab12c
--- /dev/null
+++ b/dom/media/webaudio/test/test_periodicWave.html
@@ -0,0 +1,130 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test the PeriodicWave interface</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+// real and imag are used in separate PeriodicWaves to make their peak values
+// easy to determine.
+const realMax = 99;
+var real = new Float32Array(realMax + 1);
+real[1] = 2.0; // fundamental
+real[realMax] = 3.0;
+const realPeak = real[1] + real[realMax];
+const realFundamental = 19.0;
+var imag = new Float32Array(4);
+imag[0] = 6.0; // should be ignored.
+imag[3] = 0.5;
+const imagPeak = imag[3];
+const imagFundamental = 551.0;
+
+const testLength = 4096;
+
+addLoadEvent(function() {
+ var ac = new AudioContext();
+ ac.createPeriodicWave(new Float32Array(4096), new Float32Array(4096));
+ expectException(function() {
+ ac.createPeriodicWave(new Float32Array(512), imag);
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ ac.createPeriodicWave(new Float32Array(0), new Float32Array(0));
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ ac.createPeriodicWave(new Float32Array(1), new Float32Array(1));
+ }, DOMException.INDEX_SIZE_ERR);
+ expectNoException(function() {
+ ac.createPeriodicWave(new Float32Array(4097), new Float32Array(4097));
+ });
+
+ expectNoException(function() {
+ new PeriodicWave(ac, {});
+ });
+
+ // real.size == imag.size
+ expectException(function() {
+ new PeriodicWave(ac, {real: new Float32Array(10), imag: new Float32Array(9)});
+ }, DOMException.INDEX_SIZE_ERR);
+
+ // size lower than 2 is not allowed
+ expectException(function() {
+ new PeriodicWave(ac, {real: new Float32Array(0)});
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ new PeriodicWave(ac, {imag: new Float32Array(0)});
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ new PeriodicWave(ac, {real: new Float32Array(1)});
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ new PeriodicWave(ac, {imag: new Float32Array(1)});
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ new PeriodicWave(ac, {real: new Float32Array(0), imag: new Float32Array(0)});
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ new PeriodicWave(ac, {real: new Float32Array(1), imag: new Float32Array(1)});
+ }, DOMException.INDEX_SIZE_ERR);
+
+ new PeriodicWave(ac, {real: new Float32Array(4096), imag: new Float32Array(4096)});
+ new PeriodicWave(ac, {real: new Float32Array(4096) });
+ new PeriodicWave(ac, {imag: new Float32Array(4096) });
+
+ runTest();
+});
+
+var gTest = {
+ createGraph(context) {
+ var merger = context.createChannelMerger();
+
+ var osc0 = context.createOscillator();
+ var osc1 = context.createOscillator();
+
+ osc0.setPeriodicWave(context.
+ createPeriodicWave(real,
+ new Float32Array(real.length)));
+ osc1.setPeriodicWave(context.
+ createPeriodicWave(new Float32Array(imag.length),
+ imag));
+
+ osc0.frequency.value = realFundamental;
+ osc1.frequency.value = imagFundamental;
+
+ osc0.start();
+ osc1.start();
+
+ osc0.connect(merger, 0, 0);
+ osc1.connect(merger, 0, 1);
+
+ return merger;
+ },
+ createExpectedBuffers(context) {
+ var buffer = context.createBuffer(2, testLength, context.sampleRate);
+
+ for (var i = 0; i < buffer.length; ++i) {
+
+ buffer.getChannelData(0)[i] = 1.0 / realPeak *
+ (real[1] * Math.cos(2 * Math.PI * realFundamental * i /
+ context.sampleRate) +
+ real[realMax] * Math.cos(2 * Math.PI * realMax * realFundamental * i /
+ context.sampleRate));
+
+ buffer.getChannelData(1)[i] = 1.0 / imagPeak *
+ imag[3] * Math.sin(2 * Math.PI * 3 * imagFundamental * i /
+ context.sampleRate);
+ }
+ return buffer;
+ },
+};
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_periodicWaveBandLimiting.html b/dom/media/webaudio/test/test_periodicWaveBandLimiting.html
new file mode 100644
index 0000000000..a17328b965
--- /dev/null
+++ b/dom/media/webaudio/test/test_periodicWaveBandLimiting.html
@@ -0,0 +1,86 @@
+<!DOCTYPE html>
+<title>Test effect of band limiting on PeriodicWave signals</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+const sampleRate = 48000;
+const bufferSize = 12800;
+const epsilon = 0.01;
+
+// "All implementations must support arrays up to at least 8192", but the
+// linear interpolation of the current implementation distorts the higher
+// frequency components too much to pass this test.
+const frequencyIndexMax = 200;
+
+// A set of oscillators are created near the Nyquist frequency.
+// These are factors giving each oscillator frequency relative to the Nyquist.
+// The first is an octave below Nyquist and the last is just above.
+const OCTAVE_BELOW = 0;
+const HALF_BELOW = 1;
+const NEAR_BELOW = 2;
+const ABOVE = 3;
+const oscillatorFactors = [0.5, Math.sqrt(0.5), 0.99, 1.01];
+const oscillatorCount = oscillatorFactors.length;
+
+// Return magnitude relative to unit sine wave
+function magnitude(array) {
+ var mag = 0
+ for (var i = 0; i < array.length; ++i) {
+ let sample = array[i];
+ mag += sample * sample;
+ }
+ return Math.sqrt(2 * mag / array.length);
+}
+
+function test_frequency_index(frequencyIndex) {
+
+ var context =
+ new OfflineAudioContext(oscillatorCount, bufferSize, sampleRate);
+
+ var merger = context.createChannelMerger(oscillatorCount);
+ merger.connect(context.destination);
+
+ var real = new Float32Array(frequencyIndex + 1);
+ real[frequencyIndex] = 1;
+ var image = new Float32Array(real.length);
+ var wave = context.createPeriodicWave(real, image);
+
+ for (var i = 0; i < oscillatorCount; ++i) {
+ var oscillator = context.createOscillator();
+ oscillator.frequency.value =
+ oscillatorFactors[i] * sampleRate / (2 * frequencyIndex);
+ oscillator.connect(merger, 0, i);
+ oscillator.setPeriodicWave(wave);
+ oscillator.start(0);
+ }
+
+ return context.startRendering().
+ then((buffer) => {
+ assert_equals(buffer.numberOfChannels, oscillatorCount);
+ var magnitudes = [];
+ for (var i = 0; i < oscillatorCount; ++i) {
+ magnitudes[i] = magnitude(buffer.getChannelData(i));
+ }
+ // Unaffected by band-limiting one octave below Nyquist.
+ assert_approx_equals(magnitudes[OCTAVE_BELOW], 1, epsilon,
+ "magnitude with frequency octave below Nyquist");
+ // Still at least half the amplitude at half octave below Nyquist.
+ assert_greater_than(magnitudes[HALF_BELOW], 0.5 * (1 - epsilon),
+ "magnitude with frequency half octave below Nyquist");
+ // Approaching zero or zero near Nyquist.
+ assert_less_than(magnitudes[NEAR_BELOW], 0.1,
+ "magnitude with frequency near Nyquist");
+ assert_equals(magnitudes[ABOVE], 0,
+ "magnitude with frequency above Nyquist");
+ });
+}
+
+// The 5/4 ratio with rounding up provides sampling across a range of
+// octaves and offsets within octaves.
+for (var frequencyIndex = 1;
+ frequencyIndex < frequencyIndexMax;
+ frequencyIndex = Math.floor((5 * frequencyIndex + 3) / 4)) {
+ promise_test(test_frequency_index.bind(null, frequencyIndex),
+ "Frequency " + frequencyIndex);
+}
+</script>
diff --git a/dom/media/webaudio/test/test_periodicWaveDisableNormalization.html b/dom/media/webaudio/test/test_periodicWaveDisableNormalization.html
new file mode 100644
index 0000000000..229d48282e
--- /dev/null
+++ b/dom/media/webaudio/test/test_periodicWaveDisableNormalization.html
@@ -0,0 +1,98 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test PeriodicWave disableNormalization Parameter</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+// We create PerodicWave instances containing two tones and compare it to
+// buffers created directly in JavaScript by adding the two waves together.
+// Two of the PeriodicWaves are normalized, the other is not. This test is
+// a modification of test_periodicWave.html.
+//
+// These constants are borrowed from test_periodicWave.html and modified
+// so that the realPeak (which is the normalization factor) will be small
+// enough that the errors are within the bounds for the test.
+const realMax = 99;
+var real = new Float32Array(realMax + 1);
+real[1] = 2.0; // fundamental
+real[realMax] = 0.25;
+
+const realPeak = real[1] + real[realMax];
+const realFundamental = 19.0;
+
+const testLength = 4096;
+
+addLoadEvent(function() {
+ runTest();
+});
+
+var gTest = {
+ createGraph(context) {
+ var merger = context.createChannelMerger();
+
+ var osc0 = context.createOscillator();
+ var osc1 = context.createOscillator();
+ var osc2 = context.createOscillator();
+
+ osc0.setPeriodicWave(context.
+ createPeriodicWave(real,
+ new Float32Array(real.length),
+ {disableNormalization: false}));
+ osc1.setPeriodicWave(context.
+ createPeriodicWave(real,
+ new Float32Array(real.length)));
+ osc2.setPeriodicWave(context.
+ createPeriodicWave(real,
+ new Float32Array(real.length),
+ {disableNormalization: true}));
+
+ osc0.frequency.value = realFundamental;
+ osc1.frequency.value = realFundamental;
+ osc2.frequency.value = realFundamental;
+
+ osc0.start();
+ osc1.start();
+ osc2.start();
+
+ osc0.connect(merger, 0, 0);
+ osc1.connect(merger, 0, 1);
+ osc2.connect(merger, 0, 2);
+
+ return merger;
+ },
+ createExpectedBuffers(context) {
+ var buffer = context.createBuffer(3, testLength, context.sampleRate);
+
+ for (var i = 0; i < buffer.length; ++i) {
+
+ buffer.getChannelData(0)[i] = 1.0 / realPeak *
+ (real[1] * Math.cos(2 * Math.PI * realFundamental * i /
+ context.sampleRate) +
+ real[realMax] * Math.cos(2 * Math.PI * realMax * realFundamental * i /
+ context.sampleRate));
+
+ buffer.getChannelData(1)[i] = buffer.getChannelData(0)[i];
+
+ buffer.getChannelData(2)[i] =
+ (real[1] * Math.cos(2 * Math.PI * realFundamental * i /
+ context.sampleRate) +
+ real[realMax] * Math.cos(2 * Math.PI * realMax * realFundamental * i /
+ context.sampleRate));
+ }
+ return buffer;
+ },
+ 'numberOfChannels': 3,
+};
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_retrospective-exponentialRampToValueAtTime.html b/dom/media/webaudio/test/test_retrospective-exponentialRampToValueAtTime.html
new file mode 100644
index 0000000000..20d3d59faf
--- /dev/null
+++ b/dom/media/webaudio/test/test_retrospective-exponentialRampToValueAtTime.html
@@ -0,0 +1,51 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test exponentialRampToValue with end time in the past</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+function do_test(t, context) {
+ var source = context.createConstantSource();
+ source.start();
+
+ var test = context.createGain();
+ test.gain.exponentialRampToValueAtTime(0.1, 0.5*context.currentTime);
+ test.gain.exponentialRampToValueAtTime(0.9, 2.0);
+
+ var reference = context.createGain();
+ reference.gain.exponentialRampToValueAtTime(0.1, context.currentTime);
+ reference.gain.exponentialRampToValueAtTime(0.9, 2.0);
+
+ source.connect(test);
+ source.connect(reference);
+
+ var merger = context.createChannelMerger();
+ test.connect(merger, 0, 0);
+ reference.connect(merger, 0, 1);
+
+ var processor = context.createScriptProcessor(0, 2, 0);
+ merger.connect(processor);
+ processor.onaudioprocess =
+ t.step_func_done((e) => {
+ source.stop();
+ processor.onaudioprocess = null;
+
+ var testValue = e.inputBuffer.getChannelData(0)[0];
+ var referenceValue = e.inputBuffer.getChannelData(1)[0];
+
+ assert_equals(testValue, referenceValue,
+ "value matches expected");
+ });
+}
+
+async_test(function(t) {
+ var context = new AudioContext;
+ (function waitForTimeAdvance() {
+ if (context.currentTime == 0) {
+ t.step_timeout(waitForTimeAdvance, 0);
+ } else {
+ do_test(t, context);
+ }
+ })();
+});
+</script>
diff --git a/dom/media/webaudio/test/test_retrospective-linearRampToValueAtTime.html b/dom/media/webaudio/test/test_retrospective-linearRampToValueAtTime.html
new file mode 100644
index 0000000000..1594a30bd1
--- /dev/null
+++ b/dom/media/webaudio/test/test_retrospective-linearRampToValueAtTime.html
@@ -0,0 +1,51 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test linearRampToValue with end time in the past</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+function do_test(t, context) {
+ var source = context.createConstantSource();
+ source.start();
+
+ var test = context.createGain();
+ test.gain.linearRampToValueAtTime(0.1, 0.5*context.currentTime);
+ test.gain.linearRampToValueAtTime(0.9, 2.0);
+
+ var reference = context.createGain();
+ reference.gain.linearRampToValueAtTime(0.1, context.currentTime);
+ reference.gain.linearRampToValueAtTime(0.9, 2.0);
+
+ source.connect(test);
+ source.connect(reference);
+
+ var merger = context.createChannelMerger();
+ test.connect(merger, 0, 0);
+ reference.connect(merger, 0, 1);
+
+ var processor = context.createScriptProcessor(0, 2, 0);
+ merger.connect(processor);
+ processor.onaudioprocess =
+ t.step_func_done((e) => {
+ source.stop();
+ processor.onaudioprocess = null;
+
+ var testValue = e.inputBuffer.getChannelData(0)[0];
+ var referenceValue = e.inputBuffer.getChannelData(1)[0];
+
+ assert_equals(testValue, referenceValue,
+ "value matches expected");
+ });
+}
+
+async_test(function(t) {
+ var context = new AudioContext;
+ (function waitForTimeAdvance() {
+ if (context.currentTime == 0) {
+ t.step_timeout(waitForTimeAdvance, 0);
+ } else {
+ do_test(t, context);
+ }
+ })();
+});
+</script>
diff --git a/dom/media/webaudio/test/test_retrospective-setTargetAtTime.html b/dom/media/webaudio/test/test_retrospective-setTargetAtTime.html
new file mode 100644
index 0000000000..9b04fe22bb
--- /dev/null
+++ b/dom/media/webaudio/test/test_retrospective-setTargetAtTime.html
@@ -0,0 +1,51 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test setTargetAtTime with start time in the past</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+function do_test(t, context) {
+ var source = context.createConstantSource();
+ source.start();
+
+ var test = context.createGain();
+ test.gain.setTargetAtTime(0.1, 0.5*context.currentTime, 0.1);
+ test.gain.linearRampToValueAtTime(0.9, 2.0);
+
+ var reference = context.createGain();
+ reference.gain.setTargetAtTime(0.1, context.currentTime, 0.1);
+ reference.gain.linearRampToValueAtTime(0.9, 2.0);
+
+ source.connect(test);
+ source.connect(reference);
+
+ var merger = context.createChannelMerger();
+ test.connect(merger, 0, 0);
+ reference.connect(merger, 0, 1);
+
+ var processor = context.createScriptProcessor(0, 2, 0);
+ merger.connect(processor);
+ processor.onaudioprocess =
+ t.step_func_done((e) => {
+ source.stop();
+ processor.onaudioprocess = null;
+
+ var testValue = e.inputBuffer.getChannelData(0)[0];
+ var referenceValue = e.inputBuffer.getChannelData(1)[0];
+
+ assert_equals(testValue, referenceValue,
+ "value matches expected");
+ });
+}
+
+async_test(function(t) {
+ var context = new AudioContext;
+ (function waitForTimeAdvance() {
+ if (context.currentTime == 0) {
+ t.step_timeout(waitForTimeAdvance, 0);
+ } else {
+ do_test(t, context);
+ }
+ })();
+});
+</script>
diff --git a/dom/media/webaudio/test/test_retrospective-setValueAtTime.html b/dom/media/webaudio/test/test_retrospective-setValueAtTime.html
new file mode 100644
index 0000000000..b9657ef211
--- /dev/null
+++ b/dom/media/webaudio/test/test_retrospective-setValueAtTime.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<title>Test setValueAtTime with startTime in the past</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+function do_test(t, context) {
+ var source = context.createConstantSource();
+ source.start();
+
+ // Use a ramp of slope 1/sample to measure time.
+ // The end value is the extent of exact precision in single precision float.
+ const rampEnd = Math.pow(2, 24);
+ const rampEndSeconds = rampEnd / context.sampleRate;
+ var test = context.createGain();
+ test.gain.setValueAtTime(0.0, 0.5*context.currentTime);
+ test.gain.linearRampToValueAtTime(rampEnd, rampEndSeconds);
+
+ var reference = context.createGain();
+ reference.gain.setValueAtTime(0.0, context.currentTime);
+ reference.gain.linearRampToValueAtTime(rampEnd, rampEndSeconds);
+
+ source.connect(test);
+ source.connect(reference);
+
+ var merger = context.createChannelMerger();
+ test.connect(merger, 0, 0);
+ reference.connect(merger, 0, 1);
+
+ var processor = context.createScriptProcessor(0, 2, 0);
+ merger.connect(processor);
+ processor.onaudioprocess =
+ t.step_func_done((e) => {
+ source.stop();
+ processor.onaudioprocess = null;
+
+ var testValue = e.inputBuffer.getChannelData(0)[0];
+ var referenceValue = e.inputBuffer.getChannelData(1)[0];
+
+ assert_equals(testValue, referenceValue,
+ "ramp value matches expected");
+ });
+}
+
+async_test(function(t) {
+ var context = new AudioContext;
+ (function waitForTimeAdvance() {
+ if (context.currentTime == 0) {
+ t.step_timeout(waitForTimeAdvance, 0);
+ } else {
+ do_test(t, context);
+ }
+ })();
+});
+</script>
diff --git a/dom/media/webaudio/test/test_retrospective-setValueCurveAtTime.html b/dom/media/webaudio/test/test_retrospective-setValueCurveAtTime.html
new file mode 100644
index 0000000000..008b240129
--- /dev/null
+++ b/dom/media/webaudio/test/test_retrospective-setValueCurveAtTime.html
@@ -0,0 +1,49 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test SetValueCurve with start time in the past</title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+function do_test(t, context) {
+ var source = context.createConstantSource();
+ source.start();
+
+ var test = context.createGain();
+ test.gain.setValueCurveAtTime(new Float32Array([1.0, 0.1]), 0.0, 1.0);
+
+ var reference = context.createGain();
+ reference.gain.setValueCurveAtTime(new Float32Array([1.0, 0.1]), 0.5*context.currentTime, 1.0);
+
+ source.connect(test);
+ source.connect(reference);
+
+ var merger = context.createChannelMerger();
+ test.connect(merger, 0, 0);
+ reference.connect(merger, 0, 1);
+
+ var processor = context.createScriptProcessor(0, 2, 0);
+ merger.connect(processor);
+ processor.onaudioprocess =
+ t.step_func_done((e) => {
+ source.stop();
+ processor.onaudioprocess = null;
+
+ var testValue = e.inputBuffer.getChannelData(0)[0];
+ var referenceValue = e.inputBuffer.getChannelData(1)[0];
+
+ assert_equals(testValue, referenceValue,
+ "value matches expected");
+ });
+}
+
+async_test(function(t) {
+ var context = new AudioContext;
+ (function waitForTimeAdvance() {
+ if (context.currentTime == 0) {
+ t.step_timeout(waitForTimeAdvance, 0);
+ } else {
+ do_test(t, context);
+ }
+ })();
+});
+</script>
diff --git a/dom/media/webaudio/test/test_scriptProcessorNode.html b/dom/media/webaudio/test/test_scriptProcessorNode.html
new file mode 100644
index 0000000000..ec263755cb
--- /dev/null
+++ b/dom/media/webaudio/test/test_scriptProcessorNode.html
@@ -0,0 +1,132 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test ScriptProcessorNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+// We do not use our generic graph test framework here because
+// the testing logic here is sort of complicated, and would
+// not be easy to map to OfflineAudioContext, as ScriptProcessorNodes
+// can experience delays.
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var context = new AudioContext();
+ var buffer = null;
+
+ var sourceSP = context.createScriptProcessor(2048);
+ sourceSP.addEventListener("audioprocess", function(e) {
+ // generate the audio
+ for (var i = 0; i < 2048; ++i) {
+ // Make sure our first sample won't be zero
+ e.outputBuffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * (i + 1) / context.sampleRate);
+ e.outputBuffer.getChannelData(1)[i] = Math.sin(880 * 2 * Math.PI * (i + 1) / context.sampleRate);
+ }
+ // Remember our generated audio
+ buffer = e.outputBuffer;
+
+ sourceSP.removeEventListener("audioprocess", arguments.callee);
+ });
+
+ expectException(function() {
+ context.createScriptProcessor(1);
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ context.createScriptProcessor(2);
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ context.createScriptProcessor(128);
+ }, DOMException.INDEX_SIZE_ERR);
+ expectException(function() {
+ context.createScriptProcessor(255);
+ }, DOMException.INDEX_SIZE_ERR);
+
+ is(sourceSP.channelCount, 2, "script processor node has 2 input channels by default");
+ is(sourceSP.channelCountMode, "explicit", "Correct channelCountMode for the script processor node");
+ is(sourceSP.channelInterpretation, "speakers", "Correct channelCountInterpretation for the script processor node");
+
+ function findFirstNonZeroSample(buffer) {
+ for (var i = 0; i < buffer.length; ++i) {
+ if (buffer.getChannelData(0)[i] != 0) {
+ return i;
+ }
+ }
+ return buffer.length;
+ }
+
+ var sp = context.createScriptProcessor(2048);
+ sourceSP.connect(sp);
+ sp.connect(context.destination);
+ var lastPlaybackTime = 0;
+
+ var emptyBuffer = context.createBuffer(1, 2048, context.sampleRate);
+
+ function checkAudioProcessingEvent(e) {
+ is(e.target, sp, "Correct event target");
+ ok(e.playbackTime > lastPlaybackTime, "playbackTime correctly set");
+ lastPlaybackTime = e.playbackTime;
+ is(e.inputBuffer.numberOfChannels, 2, "Correct number of channels for the input buffer");
+ is(e.inputBuffer.length, 2048, "Correct length for the input buffer");
+ is(e.inputBuffer.sampleRate, context.sampleRate, "Correct sample rate for the input buffer");
+ is(e.outputBuffer.numberOfChannels, 2, "Correct number of channels for the output buffer");
+ is(e.outputBuffer.length, 2048, "Correct length for the output buffer");
+ is(e.outputBuffer.sampleRate, context.sampleRate, "Correct sample rate for the output buffer");
+
+ compareChannels(e.outputBuffer.getChannelData(0), emptyBuffer.getChannelData(0));
+ compareChannels(e.outputBuffer.getChannelData(1), emptyBuffer.getChannelData(0));
+ }
+
+ sp.onaudioprocess = function(e) {
+ isnot(buffer, null, "The audioprocess handler for sourceSP must be run at this point");
+ checkAudioProcessingEvent(e);
+
+ // Because of the initial latency added by the second script processor node,
+ // we will never see any generated audio frames in the first callback.
+ compareChannels(e.inputBuffer.getChannelData(0), emptyBuffer.getChannelData(0));
+ compareChannels(e.inputBuffer.getChannelData(1), emptyBuffer.getChannelData(0));
+
+ sp.onaudioprocess = function(e) {
+ checkAudioProcessingEvent(e);
+
+ var firstNonZero = findFirstNonZeroSample(e.inputBuffer);
+ ok(firstNonZero <= 2048, "First non-zero sample within range");
+
+ compareChannels(e.inputBuffer.getChannelData(0), emptyBuffer.getChannelData(0), firstNonZero);
+ compareChannels(e.inputBuffer.getChannelData(1), emptyBuffer.getChannelData(0), firstNonZero);
+ compareChannels(e.inputBuffer.getChannelData(0), buffer.getChannelData(0), 2048 - firstNonZero, firstNonZero, 0);
+ compareChannels(e.inputBuffer.getChannelData(1), buffer.getChannelData(1), 2048 - firstNonZero, firstNonZero, 0);
+
+ if (firstNonZero == 0) {
+ // If we did not experience any delays, the test is done!
+ sp.onaudioprocess = null;
+
+ SimpleTest.finish();
+ } else if (firstNonZero != 2048) {
+ // In case we just saw a zero buffer this time, wait one more round
+ sp.onaudioprocess = function(e) {
+ checkAudioProcessingEvent(e);
+
+ compareChannels(e.inputBuffer.getChannelData(0), buffer.getChannelData(0), firstNonZero, 0, 2048 - firstNonZero);
+ compareChannels(e.inputBuffer.getChannelData(1), buffer.getChannelData(1), firstNonZero, 0, 2048 - firstNonZero);
+ compareChannels(e.inputBuffer.getChannelData(0), emptyBuffer.getChannelData(0), undefined, firstNonZero);
+ compareChannels(e.inputBuffer.getChannelData(1), emptyBuffer.getChannelData(0), undefined, firstNonZero);
+
+ sp.onaudioprocess = null;
+
+ SimpleTest.finish();
+ };
+ }
+ };
+ };
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_scriptProcessorNodeChannelCount.html b/dom/media/webaudio/test/test_scriptProcessorNodeChannelCount.html
new file mode 100644
index 0000000000..5e9a9960b7
--- /dev/null
+++ b/dom/media/webaudio/test/test_scriptProcessorNodeChannelCount.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AudioBufferSourceNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+// We do not use our generic graph test framework here because
+// the testing logic here is sort of complicated, and would
+// not be easy to map to OfflineAudioContext, as ScriptProcessorNodes
+// can experience delays.
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var context = new AudioContext();
+ var buffer = context.createBuffer(6, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ for (var j = 0; j < 6; ++j) {
+ buffer.getChannelData(0)[i] = Math.sin(440 * j * Math.PI * i / context.sampleRate);
+ }
+ }
+
+ var monoBuffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ monoBuffer.getChannelData(0)[i] = 1;
+ }
+
+ var source = context.createBufferSource();
+
+ var sp = context.createScriptProcessor(2048, 3);
+ expectException(function() { sp.channelCount = 2; },
+ DOMException.NOT_SUPPORTED_ERR);
+ sp.channelCountMode = "explicit";
+ expectException(function() { sp.channelCountMode = "max"; },
+ DOMException.NOT_SUPPORTED_ERR);
+ expectException(function() { sp.channelCountMode = "clamped-max"; },
+ DOMException.NOT_SUPPORTED_ERR);
+ sp.channelInterpretation = "discrete";
+ source.start(0);
+ source.buffer = buffer;
+ source.connect(sp);
+ sp.connect(context.destination);
+
+ var monoSource = context.createBufferSource();
+ monoSource.buffer = monoBuffer;
+ monoSource.connect(sp);
+ monoSource.start(2048 / context.sampleRate);
+
+ sp.onaudioprocess = function(e) {
+ is(e.inputBuffer.numberOfChannels, 3, "Should be correctly down-mixed to three channels");
+ for (var i = 0; i < 3; ++i) {
+ compareChannels(e.inputBuffer.getChannelData(i), buffer.getChannelData(i));
+ }
+
+ // On the next iteration, we'll get a silence buffer
+ sp.onaudioprocess = function(e) {
+ var emptyBuffer = context.createBuffer(1, 2048, context.sampleRate);
+ is(e.inputBuffer.numberOfChannels, 3, "Should be correctly up-mixed to three channels");
+ compareChannels(e.inputBuffer.getChannelData(0), monoBuffer.getChannelData(0));
+ for (var i = 1; i < 3; ++i) {
+ compareChannels(e.inputBuffer.getChannelData(i), emptyBuffer.getChannelData(0));
+ }
+
+ sp.onaudioprocess = null;
+ sp.disconnect(context.destination);
+
+ SimpleTest.finish();
+ };
+ };
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_scriptProcessorNodeNotConnected.html b/dom/media/webaudio/test/test_scriptProcessorNodeNotConnected.html
new file mode 100644
index 0000000000..fb45895380
--- /dev/null
+++ b/dom/media/webaudio/test/test_scriptProcessorNodeNotConnected.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AudioBufferSourceNode: should not fire audioprocess if not connected.</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("This test needs to wait a while to ensure that a given event does not happen.");
+addLoadEvent(function() {
+ var context = new AudioContext();
+
+ var sp = context.createScriptProcessor(2048, 2, 2);
+ sp.onaudioprocess = function(e) {
+ ok(false, "Should not call onaudioprocess if the node is not connected.");
+ sp.onaudioprocess = null;
+ SimpleTest.finish();
+ };
+ setTimeout(function() {
+ console.log(sp.onaudioprocess);
+ if (sp.onaudioprocess) {
+ ok(true, "onaudioprocess not fired.");
+ SimpleTest.finish();
+ }
+ }, 4000);
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_scriptProcessorNodePassThrough.html b/dom/media/webaudio/test/test_scriptProcessorNodePassThrough.html
new file mode 100644
index 0000000000..5d2d8170e2
--- /dev/null
+++ b/dom/media/webaudio/test/test_scriptProcessorNodePassThrough.html
@@ -0,0 +1,103 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test ScriptProcessorNode with passthrough</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+// We do not use our generic graph test framework here because
+// the testing logic here is sort of complicated, and would
+// not be easy to map to OfflineAudioContext, as ScriptProcessorNodes
+// can experience delays.
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var context = new AudioContext();
+ var buffer = null;
+
+ var sourceSP = context.createScriptProcessor(2048);
+ sourceSP.addEventListener("audioprocess", function(e) {
+ // generate the audio
+ for (var i = 0; i < 2048; ++i) {
+ // Make sure our first sample won't be zero
+ e.outputBuffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * (i + 1) / context.sampleRate);
+ e.outputBuffer.getChannelData(1)[i] = Math.sin(880 * 2 * Math.PI * (i + 1) / context.sampleRate);
+ }
+ // Remember our generated audio
+ buffer = e.outputBuffer;
+
+ sourceSP.removeEventListener("audioprocess", arguments.callee);
+ });
+
+ function findFirstNonZeroSample(buffer) {
+ for (var i = 0; i < buffer.length; ++i) {
+ if (buffer.getChannelData(0)[i] != 0) {
+ return i;
+ }
+ }
+ return buffer.length;
+ }
+
+ var sp = context.createScriptProcessor(2048);
+ sourceSP.connect(sp);
+
+ var spWrapped = SpecialPowers.wrap(sp);
+ ok("passThrough" in spWrapped, "ScriptProcessorNode should support the passThrough API");
+ spWrapped.passThrough = true;
+
+ sp.onaudioprocess = function() {
+ ok(false, "The audioprocess event must never be dispatched on the passthrough ScriptProcessorNode");
+ };
+
+ var sp2 = context.createScriptProcessor(2048);
+ sp.connect(sp2);
+ sp2.connect(context.destination);
+
+ var emptyBuffer = context.createBuffer(1, 2048, context.sampleRate);
+
+ sp2.onaudioprocess = function(e) {
+ // Because of the initial latency added by the second script processor node,
+ // we will never see any generated audio frames in the first callback.
+ compareChannels(e.inputBuffer.getChannelData(0), emptyBuffer.getChannelData(0));
+ compareChannels(e.inputBuffer.getChannelData(1), emptyBuffer.getChannelData(0));
+
+ sp2.onaudioprocess = function(e) {
+ var firstNonZero = findFirstNonZeroSample(e.inputBuffer);
+ ok(firstNonZero <= 2048, "First non-zero sample within range");
+
+ compareChannels(e.inputBuffer.getChannelData(0), emptyBuffer.getChannelData(0), firstNonZero);
+ compareChannels(e.inputBuffer.getChannelData(1), emptyBuffer.getChannelData(0), firstNonZero);
+ compareChannels(e.inputBuffer.getChannelData(0), buffer.getChannelData(0), 2048 - firstNonZero, firstNonZero, 0);
+ compareChannels(e.inputBuffer.getChannelData(1), buffer.getChannelData(1), 2048 - firstNonZero, firstNonZero, 0);
+
+ if (firstNonZero == 0) {
+ // If we did not experience any delays, the test is done!
+ sp2.onaudioprocess = null;
+
+ SimpleTest.finish();
+ } else if (firstNonZero != 2048) {
+ // In case we just saw a zero buffer this time, wait one more round
+ sp2.onaudioprocess = function(e) {
+ compareChannels(e.inputBuffer.getChannelData(0), buffer.getChannelData(0), firstNonZero, 0, 2048 - firstNonZero);
+ compareChannels(e.inputBuffer.getChannelData(1), buffer.getChannelData(1), firstNonZero, 0, 2048 - firstNonZero);
+ compareChannels(e.inputBuffer.getChannelData(0), emptyBuffer.getChannelData(0), undefined, firstNonZero);
+ compareChannels(e.inputBuffer.getChannelData(1), emptyBuffer.getChannelData(0), undefined, firstNonZero);
+
+ sp2.onaudioprocess = null;
+
+ SimpleTest.finish();
+ };
+ }
+ };
+ };
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_scriptProcessorNodeZeroInputOutput.html b/dom/media/webaudio/test/test_scriptProcessorNodeZeroInputOutput.html
new file mode 100644
index 0000000000..f4b25d49dd
--- /dev/null
+++ b/dom/media/webaudio/test/test_scriptProcessorNodeZeroInputOutput.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AudioBufferSourceNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var context = new AudioContext();
+
+ var sp = context.createScriptProcessor(2048, 0, 2);
+ sp.onaudioprocess = function(e) {
+ is(e.inputBuffer.numberOfChannels, 0, "Should have 0 input channels");
+ is(e.outputBuffer.numberOfChannels, 2, "Should have 2 output channels");
+ sp.onaudioprocess = null;
+
+ sp = context.createScriptProcessor(2048, 2, 0);
+ sp.onaudioprocess = function(e) {
+ is(e.inputBuffer.numberOfChannels, 2, "Should have 2 input channels");
+ is(e.outputBuffer.numberOfChannels, 0, "Should have 0 output channels");
+ sp.onaudioprocess = null;
+
+ SimpleTest.finish();
+ };
+ sp.connect(context.destination);
+ };
+ sp.connect(context.destination);
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_scriptProcessorNode_playbackTime1.html b/dom/media/webaudio/test/test_scriptProcessorNode_playbackTime1.html
new file mode 100644
index 0000000000..ec695f952b
--- /dev/null
+++ b/dom/media/webaudio/test/test_scriptProcessorNode_playbackTime1.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test ScriptProcessorNode playbackTime for bug 970773</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var context = new AudioContext();
+const delay = 0.1;
+
+function doTest() {
+ const processorBufferLength = 256;
+ // |currentTime| may include double precision floating point
+ // rounding errors, so round to nearest integer sample to ignore these.
+ var minimumPlaybackSample =
+ Math.round(context.currentTime * context.sampleRate) +
+ processorBufferLength;
+ var sp = context.createScriptProcessor(processorBufferLength);
+ sp.connect(context.destination);
+ sp.onaudioprocess =
+ function(e) {
+ is(e.inputBuffer.length, processorBufferLength,
+ "expected buffer length");
+ var playbackSample = Math.round(e.playbackTime * context.sampleRate)
+ ok(playbackSample >= minimumPlaybackSample,
+ "playbackSample " + playbackSample +
+ " beyond expected minimum " + minimumPlaybackSample);
+ sp.onaudioprocess = null;
+ SimpleTest.finish();
+ };
+}
+
+// Wait until AudioDestinationNode has accumulated enough 'extra' time so that
+// a failure would be easily detected.
+(function waitForExtraTime() {
+ if (context.currentTime < delay) {
+ SimpleTest.executeSoon(waitForExtraTime);
+ } else {
+ doTest();
+ }
+})();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_sequentialBufferSourceWithResampling.html b/dom/media/webaudio/test/test_sequentialBufferSourceWithResampling.html
new file mode 100644
index 0000000000..218bf462c5
--- /dev/null
+++ b/dom/media/webaudio/test/test_sequentialBufferSourceWithResampling.html
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<title>Test seamless playback of a series of resampled buffers</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+// Permitting some accumulation of rounding to int16_t.
+// 64/2^15 would be only just small enough to detect off-by-one-subsample
+// scheduling errors with the frequencies here.
+const EPSILON = 4.0 / Math.pow(2, 15);
+// Offsets test for rounding to nearest rather than up or down.
+const OFFSETS = [EPSILON, 1.0 - EPSILON];
+// The ratio of resampling is 147:160, so 256 start points is enough to cover
+// every fractional offset.
+const LENGTH = 256;
+
+function do_test(context_rate, buffer_rate, start_offset) {
+
+ var context =
+ new OfflineAudioContext(2, LENGTH, context_rate);
+
+ var merger = context.createChannelMerger(context.destination.channelCount);
+ merger.connect(context.destination);
+
+ // Create an audio signal that will be repeated
+ var repeating_signal = context.createBuffer(1, 1, buffer_rate);
+ repeating_signal.getChannelData(0)[0] = 0.5;
+
+ // Schedule a series of nodes to repeat the signal.
+ for (var i = 0; i < LENGTH; ++i) {
+ var source = context.createBufferSource();
+ source.buffer = repeating_signal;
+ source.connect(merger, 0, 0);
+ source.start((i + start_offset) / buffer_rate);
+ }
+
+ // A single long signal should produce the same result.
+ var long_signal = context.createBuffer(1, LENGTH, buffer_rate);
+ var c = long_signal.getChannelData(0);
+ for (var i = 0; i < c.length; ++i) {
+ c[i] = 0.5;
+ }
+
+ var source = context.createBufferSource();
+ source.buffer = long_signal;
+ source.connect(merger, 0, 1);
+ source.start(start_offset / buffer_rate);
+
+ return context.startRendering().
+ then((buffer) => {
+ let series_output = buffer.getChannelData(0);
+ let expected = buffer.getChannelData(1);
+
+ for (var i = 0; i < buffer.length; ++i) {
+ assert_approx_equals(series_output[i], expected[i], EPSILON,
+ "series output at " + i);
+ }
+ });
+}
+
+function start_tests(context_rate, buffer_rate) {
+ OFFSETS.forEach((start_offset) => {
+ promise_test(() => do_test(context_rate, buffer_rate, start_offset),
+ "" + context_rate + " context, "
+ + buffer_rate + " buffer, "
+ + start_offset + " start");
+ });
+}
+
+start_tests(48000, 44100);
+start_tests(44100, 48000);
+
+</script>
diff --git a/dom/media/webaudio/test/test_setValueCurveWithNonFiniteElements.html b/dom/media/webaudio/test/test_setValueCurveWithNonFiniteElements.html
new file mode 100644
index 0000000000..28829e1ec2
--- /dev/null
+++ b/dom/media/webaudio/test/test_setValueCurveWithNonFiniteElements.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<meta charset=utf-8>
+<head>
+ <title>Bug 1308437 - setValueCurve should throw on non-finite elements</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+
+function testInfiniteElement(audioContext, audioParam) {
+ // create value curve with infinite element
+ var arr = new Float32Array(5);
+ arr[0] = 0.5;
+ arr[1] = 1;
+ arr[2] = Infinity;
+ arr[3] = 1;
+ arr[4] = 0.5;
+
+ try {
+ audioParam.setValueCurveAtTime(arr, audioContext.currentTime(), 2);
+ ok(false, "We shouldn't be able to call setValueCurve with Infinity but we can");
+ } catch(e) {
+ ok(e instanceof TypeError, "TypeError is thrown");
+ }
+};
+
+function testNanElement(audioContext, audioParam) {
+ // create value curve with infinite element
+ var arr = new Float32Array(5);
+ arr[0] = 0.5;
+ arr[1] = 1;
+ arr[2] = NaN;
+ arr[3] = 1;
+ arr[4] = 0.5;
+
+ try {
+ audioParam.setValueCurveAtTime(arr, audioContext.currentTime(), 2);
+ ok(false, "We shouldn't be able to call setValueCurve with NaN but we can");
+ } catch(e) {
+ ok(e instanceof TypeError, "TypeError is thrown");
+ }
+};
+
+addLoadEvent(function() {
+ var audioContext = new AudioContext();
+ var gainNode = audioContext.createGain();
+
+ testInfiniteElement(audioContext, gainNode.gain);
+ testNanElement(audioContext, gainNode.gain);
+
+ SimpleTest.finish();
+});
+</script>
+</pre>
+</body>
+</html> \ No newline at end of file
diff --git a/dom/media/webaudio/test/test_singleSourceDest.html b/dom/media/webaudio/test/test_singleSourceDest.html
new file mode 100644
index 0000000000..fd4de50f5d
--- /dev/null
+++ b/dom/media/webaudio/test/test_singleSourceDest.html
@@ -0,0 +1,70 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test whether we can create an AudioContext interface</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ var context = new AudioContext();
+ var buffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+
+ var destination = context.destination;
+ is(destination.context, context, "Destination node has proper context");
+ is(destination.context, context, "Destination node has proper context");
+ is(destination.numberOfInputs, 1, "Destination node has 1 inputs");
+ is(destination.numberOfOutputs, 0, "Destination node has 0 outputs");
+ is(destination.channelCount, 2, "Destination node has 2 input channels by default");
+ is(destination.channelCountMode, "explicit", "Correct channelCountMode for the destination node");
+ is(destination.channelInterpretation, "speakers", "Correct channelCountInterpretation for the destination node");
+ ok(destination instanceof EventTarget, "AudioNodes must be EventTargets");
+
+ var source = context.createBufferSource();
+ is(source.context, context, "Source node has proper context");
+ is(source.numberOfInputs, 0, "Source node has 0 inputs");
+ is(source.numberOfOutputs, 1, "Source node has 1 outputs");
+ is(source.loop, false, "Source node is not looping");
+ is(source.loopStart, 0, "Correct default value for loopStart");
+ is(source.loopEnd, 0, "Correct default value for loopEnd");
+ ok(!source.buffer, "Source node should not have a buffer when it's created");
+ is(source.channelCount, 2, "source node has 2 input channels by default");
+ is(source.channelCountMode, "max", "Correct channelCountMode for the source node");
+ is(source.channelInterpretation, "speakers", "Correct channelCountInterpretation for the source node");
+
+ expectException(function() {
+ source.channelCount = 0;
+ }, DOMException.NOT_SUPPORTED_ERR);
+
+ source.buffer = buffer;
+ ok(source.buffer, "Source node should have a buffer now");
+
+ source.connect(destination);
+
+ is(source.numberOfInputs, 0, "Source node has 0 inputs");
+ is(source.numberOfOutputs, 1, "Source node has 0 outputs");
+ is(destination.numberOfInputs, 1, "Destination node has 0 inputs");
+ is(destination.numberOfOutputs, 0, "Destination node has 0 outputs");
+
+ source.start(0);
+ SimpleTest.executeSoon(function() {
+ source.stop(0);
+ source.disconnect();
+
+ SpecialPowers.clearUserPref("media.webaudio.enabled");
+ SimpleTest.finish();
+ });
+});
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_slowStart.html b/dom/media/webaudio/test/test_slowStart.html
new file mode 100644
index 0000000000..17de7351c1
--- /dev/null
+++ b/dom/media/webaudio/test/test_slowStart.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test AudioContext.currentTime</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("This test needs to periodically query the AudioContext's position.");
+const CUBEB_INIT_DELAY = 5000;
+// Delay audio stream start by a good 5 seconds
+SpecialPowers.pushPrefEnv({"set": [["media.cubeb.slow_stream_init_ms",
+ CUBEB_INIT_DELAY]]}, runTest);
+
+
+function runTest() {
+ let ac = new AudioContext();
+ let notStartedYetCount = 0;
+ let startWallClockTime = performance.now();
+ is(ac.currentTime, 0, "AudioContext.currentTime should be 0 initially");
+ is(ac.state, "suspended", "AudioContext.currentTime is initially suspended");
+ let intervalHandle = setInterval(function() {
+ if (ac.state == "running" || ac.currentTime > 0) {
+ clearInterval(intervalHandle);
+ return;
+ }
+ is(ac.currentTime, 0, "AudioContext.currentTime is still 0");
+ is(ac.state, "suspended", "AudioContext.currentTime is still suspended");
+ notStartedYetCount++;
+ });
+ ac.onstatechange = function() {
+ is(ac.state, "running", "The AudioContext eventually started.");
+ var startDuration = performance.now() - startWallClockTime;
+ info(`AudioContext start time with a delay of ${CUBEB_INIT_DELAY}): ${startDuration}`);
+ ok(notStartedYetCount > 0, "We should have observed the AudioContext in \"suspended\" state");
+ ok(startDuration >= CUBEB_INIT_DELAY, "The AudioContext state transition was correct.");
+ SimpleTest.finish();
+ }
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_stereoPannerNode.html b/dom/media/webaudio/test/test_stereoPannerNode.html
new file mode 100644
index 0000000000..d08e1640b2
--- /dev/null
+++ b/dom/media/webaudio/test/test_stereoPannerNode.html
@@ -0,0 +1,295 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test StereoPannerNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var SR = 44100;
+var BUF_SIZE = 128;
+var PANNING = 0.1;
+var GAIN = 0.5;
+
+// Cheap reimplementation of some bits of the spec
+function gainForPanningMonoToStereo(panning) {
+ panning += 1;
+ panning /= 2;
+ return [ Math.cos(0.5 * Math.PI * panning),
+ Math.sin(0.5 * Math.PI * panning) ];
+}
+
+function gainForPanningStereoToStereo(panning) {
+ if (panning <= 0) {
+ panning += 1.;
+ }
+ return [ Math.cos(0.5 * Math.PI * panning),
+ Math.sin(0.5 * Math.PI * panning) ];
+}
+
+function applyStereoToStereoPanning(l, r, panningValues, panning) {
+ var outL, outR;
+ if (panning <= 0) {
+ outL = l + r * panningValues[0];
+ outR = r * panningValues[1];
+ } else {
+ outL = l * panningValues[0];
+ outR = r + l * panningValues[1];
+ }
+ return [outL,outR];
+}
+
+function applyMonoToStereoPanning(c, panning) {
+ return [c * panning[0], c * panning[1]];
+}
+
+// Test the DOM interface
+var context = new OfflineAudioContext(1, 1, SR);
+var stereoPanner = new StereoPannerNode(context);
+ok(stereoPanner.pan, "The AudioParam member must exist");
+is(stereoPanner.pan.value, 0.0, "Correct initial value");
+is(stereoPanner.pan.defaultValue, 0.0, "Correct default value");
+is(stereoPanner.channelCount, 2, "StereoPannerNode node has 2 input channels by default");
+is(stereoPanner.channelCountMode, "clamped-max", "Correct channelCountMode for the StereoPannerNode");
+is(stereoPanner.channelInterpretation, "speakers", "Correct channelCountInterpretation for the StereoPannerNode");
+expectException(function() {
+ stereoPanner.channelCount = 3;
+}, DOMException.NOT_SUPPORTED_ERR);
+expectException(function() {
+ stereoPanner.channelCountMode = "max";
+}, DOMException.NOT_SUPPORTED_ERR);
+
+// A sine to be used to fill the buffers
+function sine(t) {
+ return Math.sin(440 * 2 * Math.PI * t / context.sampleRate);
+}
+
+// A couple mono and stereo buffers: the StereoPannerNode equation is different
+// if the input is mono or stereo
+var stereoBuffer = new AudioBuffer({ numberOfChannels: 2,
+ length: BUF_SIZE,
+ sampleRate: context.sampleRate });
+var monoBuffer = new AudioBuffer({ numberOfChannels: 1,
+ length: BUF_SIZE,
+ sampleRate: context.sampleRate });
+for (var i = 0; i < BUF_SIZE; ++i) {
+ monoBuffer.getChannelData(0)[i] =
+ stereoBuffer.getChannelData(0)[i] =
+ stereoBuffer.getChannelData(1)[i] = sine(i);
+}
+
+// Expected test vectors
+function expectedBufferNoop(gain) {
+ gain = gain || 1.0;
+ var expectedBuffer = new AudioBuffer({ numberOfChannels: 2,
+ length: BUF_SIZE,
+ sampleRate: SR });
+ for (var i = 0; i < BUF_SIZE; i++) {
+ expectedBuffer.getChannelData(0)[i] = gain * sine(i);
+ expectedBuffer.getChannelData(1)[i] = gain * sine(i);
+ }
+ return expectedBuffer;
+}
+
+function expectedBufferForStereo(panning, gain) {
+ gain = gain || 1.0;
+ var expectedBuffer = new AudioBuffer({ numberOfChannels: 2,
+ length: BUF_SIZE,
+ sampleRate: SR });
+ var gainPanning = gainForPanningStereoToStereo(panning);
+ for (var i = 0; i < BUF_SIZE; i++) {
+ var values = [ gain * sine(i), gain * sine(i) ];
+ var processed = applyStereoToStereoPanning(values[0], values[1], gainPanning, PANNING);
+ expectedBuffer.getChannelData(0)[i] = processed[0];
+ expectedBuffer.getChannelData(1)[i] = processed[1];
+ }
+ return expectedBuffer;
+}
+
+function expectedBufferForMono(panning, gain) {
+ gain = gain || 1.0;
+ var expectedBuffer = new AudioBuffer({ numberOfChannels: 2,
+ length: BUF_SIZE,
+ sampleRate: SR });
+ var gainPanning = gainForPanningMonoToStereo(panning);
+ gainPanning[0] *= gain;
+ gainPanning[1] *= gain;
+ for (var i = 0; i < BUF_SIZE; i++) {
+ var value = sine(i);
+ var processed = applyMonoToStereoPanning(value, gainPanning);
+ expectedBuffer.getChannelData(0)[i] = processed[0];
+ expectedBuffer.getChannelData(1)[i] = processed[1];
+ }
+ return expectedBuffer;
+}
+
+// Actual test cases
+var tests = [
+ function monoPanningNoop(ctx, panner) {
+ var monoSource = ctx.createBufferSource();
+ monoSource.connect(panner);
+ monoSource.buffer = monoBuffer;
+ monoSource.start(0);
+ return expectedBufferForMono(0);
+ },
+ function stereoPanningNoop(ctx, panner) {
+ var stereoSource = ctx.createBufferSource();
+ stereoSource.connect(panner);
+ stereoSource.buffer = stereoBuffer;
+ stereoSource.start(0);
+ return expectedBufferNoop();
+ },
+ function monoPanningNoopWithGain(ctx, panner) {
+ var monoSource = ctx.createBufferSource();
+ var gain = ctx.createGain();
+ gain.gain.value = GAIN;
+ monoSource.connect(gain);
+ gain.connect(panner);
+ monoSource.buffer = monoBuffer;
+ monoSource.start(0);
+ return expectedBufferForMono(0, GAIN);
+ },
+ function stereoPanningNoopWithGain(ctx, panner) {
+ var stereoSource = ctx.createBufferSource();
+ var gain = ctx.createGain();
+ gain.gain.value = GAIN;
+ stereoSource.connect(gain);
+ gain.connect(panner);
+ stereoSource.buffer = stereoBuffer;
+ stereoSource.start(0);
+ return expectedBufferNoop(GAIN);
+ },
+ function stereoPanningAutomation(ctx, panner) {
+ var stereoSource = ctx.createBufferSource();
+ stereoSource.connect(panner);
+ stereoSource.buffer = stereoBuffer;
+ panner.pan.setValueAtTime(0.1, 0.0);
+ stereoSource.start(0);
+ return expectedBufferForStereo(PANNING);
+ },
+ function stereoPanning(ctx, panner) {
+ var stereoSource = ctx.createBufferSource();
+ stereoSource.buffer = stereoBuffer;
+ stereoSource.connect(panner);
+ panner.pan.value = 0.1;
+ stereoSource.start(0);
+ return expectedBufferForStereo(PANNING);
+ },
+ function monoPanningAutomation(ctx, panner) {
+ var monoSource = ctx.createBufferSource();
+ monoSource.connect(panner);
+ monoSource.buffer = monoBuffer;
+ panner.pan.setValueAtTime(PANNING, 0.0);
+ monoSource.start(0);
+ return expectedBufferForMono(PANNING);
+ },
+ function monoPanning(ctx, panner) {
+ var monoSource = ctx.createBufferSource();
+ monoSource.connect(panner);
+ monoSource.buffer = monoBuffer;
+ panner.pan.value = 0.1;
+ monoSource.start(0);
+ return expectedBufferForMono(PANNING);
+ },
+ function monoPanningWithGain(ctx, panner) {
+ var monoSource = ctx.createBufferSource();
+ var gain = ctx.createGain();
+ gain.gain.value = GAIN;
+ monoSource.connect(gain);
+ gain.connect(panner);
+ monoSource.buffer = monoBuffer;
+ panner.pan.value = 0.1;
+ monoSource.start(0);
+ return expectedBufferForMono(PANNING, GAIN);
+ },
+ function stereoPanningWithGain(ctx, panner) {
+ var stereoSource = ctx.createBufferSource();
+ var gain = ctx.createGain();
+ gain.gain.value = GAIN;
+ stereoSource.connect(gain);
+ gain.connect(panner);
+ stereoSource.buffer = stereoBuffer;
+ panner.pan.value = 0.1;
+ stereoSource.start(0);
+ return expectedBufferForStereo(PANNING, GAIN);
+ },
+ function monoPanningWithGainAndAutomation(ctx, panner) {
+ var monoSource = ctx.createBufferSource();
+ var gain = ctx.createGain();
+ gain.gain.value = GAIN;
+ monoSource.connect(gain);
+ gain.connect(panner);
+ monoSource.buffer = monoBuffer;
+ panner.pan.setValueAtTime(PANNING, 0);
+ monoSource.start(0);
+ return expectedBufferForMono(PANNING, GAIN);
+ },
+ function stereoPanningWithGainAndAutomation(ctx, panner) {
+ var stereoSource = ctx.createBufferSource();
+ var gain = ctx.createGain();
+ gain.gain.value = GAIN;
+ stereoSource.connect(gain);
+ gain.connect(panner);
+ stereoSource.buffer = stereoBuffer;
+ panner.pan.setValueAtTime(PANNING, 0);
+ stereoSource.start(0);
+ return expectedBufferForStereo(PANNING, GAIN);
+ },
+ function bug_1783181(ctx, panner) {
+ const length = 128;
+ const buffer = new AudioBuffer({ length, numberOfChannels: 2, sampleRate: ctx.sampleRate });
+
+ buffer.copyToChannel(new Float32Array([1, 0.5, 0, -0.5, -1]), 0);
+ buffer.copyToChannel(new Float32Array([-0.5, -0.25, 0, 0.25, 0.5]), 1);
+
+ const audioBufferSourceNode = new AudioBufferSourceNode(ctx, { buffer });
+
+ audioBufferSourceNode.connect(panner);
+
+ panner.pan.setValueAtTime(0.5, 0);
+ panner.pan.setValueAtTime(0, 2 / ctx.sampleRate);
+ panner.pan.linearRampToValueAtTime(1, 5 / ctx.sampleRate);
+ panner.pan.cancelScheduledValues(3 / ctx.sampleRate);
+
+ audioBufferSourceNode.start(0);
+
+ const expected = new AudioBuffer({ length, numberOfChannels: 2, sampleRate: ctx.sampleRate });
+ expected.copyToChannel(new Float32Array([ 0.7071067690849304, 0.3535533845424652, 0, -0.5, -1 ]), 0);
+ expected.copyToChannel(new Float32Array([ 0.20710676908493042, 0.10355338454246521, 0, 0.25, 0.5 ]), 1);
+
+ return expected;
+ }
+];
+
+var finished = 0;
+function finish() {
+ if (++finished == tests.length) {
+ SimpleTest.finish();
+ }
+}
+
+tests.forEach(function(f) {
+ var ac = new OfflineAudioContext(2, BUF_SIZE, SR);
+ var panner = ac.createStereoPanner();
+ panner.connect(ac.destination);
+ var expected = f(ac, panner);
+ ac.oncomplete = function(e) {
+ info(f.name);
+ compareBuffers(e.renderedBuffer, expected);
+ finish();
+ };
+ ac.startRendering()
+});
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</pre>
+<pre id=dump>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_stereoPannerNodePassThrough.html b/dom/media/webaudio/test/test_stereoPannerNodePassThrough.html
new file mode 100644
index 0000000000..2d774d366b
--- /dev/null
+++ b/dom/media/webaudio/test/test_stereoPannerNodePassThrough.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test StereoPanerNode with passthrough</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var source = context.createBufferSource();
+
+ var stereoPanner = context.createStereoPanner();
+
+ source.buffer = this.buffer;
+
+ source.connect(stereoPanner);
+
+ var stereoPannerWrapped = SpecialPowers.wrap(stereoPanner);
+ ok("passThrough" in stereoPannerWrapped, "StereoPannerNode should support the passThrough API");
+ stereoPannerWrapped.passThrough = true;
+
+ source.start(0);
+ return stereoPanner;
+ },
+ createExpectedBuffers(context) {
+ this.buffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ this.buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+
+ return [this.buffer];
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_stereoPanningWithGain.html b/dom/media/webaudio/test/test_stereoPanningWithGain.html
new file mode 100644
index 0000000000..94dfa3bb92
--- /dev/null
+++ b/dom/media/webaudio/test/test_stereoPanningWithGain.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test stereo equalpower panning with a GainNode</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script src="webaudio.js" type="text/javascript"></script>
+<script class="testbody" type="text/javascript">
+
+const size = 256;
+
+var gTest = {
+ numberOfChannels: 2,
+ createGraph(context) {
+ var panner = context.createPanner();
+ panner.setPosition(1.0, 0.0, 0.0); // reference distance the right
+ panner.panningModel = "equalpower";
+
+ var gain = context.createGain();
+ gain.gain.value = -0.5;
+ gain.connect(panner);
+
+ var buffer = context.createBuffer(2, 2, context.sampleRate);
+ buffer.getChannelData(0)[0] = 1.0;
+ buffer.getChannelData(1)[1] = 1.0;
+ var source = context.createBufferSource();
+ source.buffer = buffer;
+ source.connect(gain);
+ source.start(0);
+
+ return panner;
+ },
+ createExpectedBuffers(context) {
+ var expectedBuffer = context.createBuffer(2, size, context.sampleRate);
+ expectedBuffer.getChannelData(1)[0] = -0.5;
+ expectedBuffer.getChannelData(1)[1] = -0.5;
+ return expectedBuffer;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_waveDecoder.html b/dom/media/webaudio/test/test_waveDecoder.html
new file mode 100644
index 0000000000..65c429f2ba
--- /dev/null
+++ b/dom/media/webaudio/test/test_waveDecoder.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html>
+<meta charset=utf-8>
+<head>
+ <title>Test that we decode uint8 and sint16 wave files with correct conversion to float64</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var testsDone = 0;
+var tests = ["UklGRjUrAABXQVZFZm10IBAAAAABAAEAESsAABErAAABAAgAZGF0YQMAAAD/AIA=",
+ "UklGRkZWAABXQVZFZm10IBAAAAABAAEAESsAACJWAAACABAAZGF0YQYAAAD/fwCAAAA="];
+
+SimpleTest.waitForExplicitFinish();
+
+function base64ToUint8Buffer(b64) {
+ var str = atob(b64)
+ var u8 = new Uint8Array(str.length);
+ for (var i = 0; i < str.length; ++i) {
+ u8[i] = str.charCodeAt(i);
+ }
+ return u8;
+}
+
+function fixupBufferSampleRate(u8, rate) {
+ u8[24] = (rate & 0x000000ff) >> 0;
+ u8[25] = (rate & 0x0000ff00) >> 8;
+ u8[26] = (rate & 0x00ff0000) >> 16;
+ u8[27] = (rate & 0xff000000) >> 24;
+}
+
+function finishTest() {
+ testsDone += 1;
+ if (testsDone == tests.length) {
+ SimpleTest.finish();
+ }
+}
+
+function decodeComplete(b) {
+ ok(true, "Decoding succeeded.");
+ is(b.numberOfChannels, 1, "Should have 1 channel.");
+ is(b.length, 3, "Should have three samples.");
+ var samples = b.getChannelData(0);
+ ok(samples[0] > 0.99 && samples[0] < 1.01, "Check near 1.0. Got " + samples[0]);
+ ok(samples[1] > -1.01 && samples[1] < -0.99, "Check near -1.0. Got " + samples[1]);
+ ok(samples[2] > -0.01 && samples[2] < 0.01, "Check near 0.0. Got " + samples[2]);
+ finishTest();
+}
+
+function decodeFailed() {
+ ok(false, "Decoding failed.");
+ finishTest();
+}
+
+addLoadEvent(function() {
+ var context = new AudioContext();
+
+ for (var i = 0; i < tests.length; ++i) {
+ var u8 = base64ToUint8Buffer(tests[i]);
+ fixupBufferSampleRate(u8, context.sampleRate);
+ context.decodeAudioData(u8.buffer, decodeComplete, decodeFailed);
+ }
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_waveShaper.html b/dom/media/webaudio/test/test_waveShaper.html
new file mode 100644
index 0000000000..9d2f1b3fa2
--- /dev/null
+++ b/dom/media/webaudio/test/test_waveShaper.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test WaveShaperNode with no curve</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 4096,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var source = context.createBufferSource();
+ source.buffer = this.buffer;
+
+ var shaper = new WaveShaperNode(context);
+ shaper.curve = this.curve;
+
+ source.connect(shaper);
+
+ source.start(0);
+ return shaper;
+ },
+ createExpectedBuffers(context) {
+ this.buffer = context.createBuffer(1, 4096, context.sampleRate);
+ for (var i = 1; i < 4095; ++i) {
+ this.buffer.getChannelData(0)[i] = 2 * (i / 4096) - 1;
+ }
+ // Two out of range values
+ this.buffer.getChannelData(0)[0] = -2;
+ this.buffer.getChannelData(0)[4095] = 2;
+
+ this.curve = new Float32Array(2048);
+ for (var i = 0; i < 2048; ++i) {
+ this.curve[i] = Math.sin(100 * Math.PI * (i + 1) / context.sampleRate);
+ }
+
+ var expectedBuffer = context.createBuffer(1, 4096, context.sampleRate);
+ for (var i = 1; i < 4095; ++i) {
+ var input = this.buffer.getChannelData(0)[i];
+ var index = Math.floor(this.curve.length * (input + 1) / 2);
+ index = Math.max(0, Math.min(this.curve.length - 1, index));
+ expectedBuffer.getChannelData(0)[i] = this.curve[index];
+ }
+ expectedBuffer.getChannelData(0)[0] = this.curve[0];
+ expectedBuffer.getChannelData(0)[4095] = this.curve[2047];
+ return expectedBuffer;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_waveShaperGain.html b/dom/media/webaudio/test/test_waveShaperGain.html
new file mode 100644
index 0000000000..45411eca02
--- /dev/null
+++ b/dom/media/webaudio/test/test_waveShaperGain.html
@@ -0,0 +1,73 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+ <title>Test that WaveShaperNode doesn't corrupt its inputs when the gain is !=
+ 1.0 (bug 1203616)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre>
+</pre>
+<script class="testbody" type="text/javascript">
+SimpleTest.waitForExplicitFinish();
+var samplerate = 44100;
+var context = new OfflineAudioContext(1, 44100, samplerate);
+
+var dc = context.createBufferSource();
+
+var buffer = context.createBuffer(1, 1, samplerate);
+buffer.getChannelData(0)[0] = 1.0;
+dc.buffer = buffer;
+
+var gain = context.createGain();
+var ws2 = context.createWaveShaper();
+var ws = [];
+
+// No-op waveshaper curves.
+for (var i = 0; i < 2; i++) {
+ ws[i] = context.createWaveShaper();
+ var curve = new Float32Array(2);
+ curve[0] = -1.0;
+ curve[1] = 1.0;
+ ws[i].curve = curve;
+ ws[i].connect(context.destination);
+ gain.connect(ws[i]);
+}
+
+dc.connect(gain);
+dc.start();
+
+gain.gain.value = 0.5;
+
+context.startRendering().then(buffer => {
+ document.querySelector("pre").innerHTML = buffer.getChannelData(0)[0];
+ ok(buffer.getChannelData(0)[0] == 1.0, "Volume was handled properly");
+
+ context = new OfflineAudioContext(1, 100, samplerate);
+ var oscillator = context.createOscillator();
+ var gain = context.createGain();
+ var waveShaper = context.createWaveShaper();
+
+ oscillator.start(0);
+ oscillator.connect(gain);
+
+ // to silence
+ gain.gain.value = 0;
+ gain.connect(waveShaper);
+
+ // convert all signal into 1.0. The non unity values are to detect the use
+ // of uninitialized buffers (see Bug 1283910).
+ waveShaper.curve = new Float32Array([ 0.5, 0.5, 0.5, 0.5, 0.5, 1, 1, 0.5, 0.5, 0.5, 0.5, 0.5 ]);
+ waveShaper.connect(context.destination);
+
+ context.startRendering().then((buffer) => {
+ var result = buffer.getChannelData(0);
+ ok(result.every(x => x === 1), "WaveShaper handles zero gain properly");
+ SimpleTest.finish();
+ });
+});
+</script>
+</body>
+
diff --git a/dom/media/webaudio/test/test_waveShaperInvalidLengthCurve.html b/dom/media/webaudio/test/test_waveShaperInvalidLengthCurve.html
new file mode 100644
index 0000000000..0901521a7b
--- /dev/null
+++ b/dom/media/webaudio/test/test_waveShaperInvalidLengthCurve.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test WaveShaperNode with an invalid curve</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var source = context.createBufferSource();
+ source.buffer = this.buffer;
+
+ var shaper = context.createWaveShaper();
+
+ expectException(() => {
+ shaper.curve = new Float32Array(0);
+ }, DOMException.INVALID_STATE_ERR);
+
+ is(shaper.curve, null, "The curve mustn't have been set");
+
+ expectException(() => {
+ shaper.curve = new Float32Array(1);
+ }, DOMException.INVALID_STATE_ERR);
+
+ is(shaper.curve, null, "The curve mustn't have been set");
+
+ expectNoException(() => {
+ shaper.curve = new Float32Array(2);
+ });
+
+ isnot(shaper.curve, null, "The curve must have been set");
+
+ expectNoException(() => {
+ shaper.curve = null;
+ });
+
+ is(shaper.curve, null, "The curve must be null by default");
+
+ source.connect(shaper);
+
+ source.start(0);
+ return shaper;
+ },
+ createExpectedBuffers(context) {
+ var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ expectedBuffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+ this.buffer = expectedBuffer;
+ return expectedBuffer;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_waveShaperNoCurve.html b/dom/media/webaudio/test/test_waveShaperNoCurve.html
new file mode 100644
index 0000000000..2da0b511af
--- /dev/null
+++ b/dom/media/webaudio/test/test_waveShaperNoCurve.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test WaveShaperNode with no curve</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 2048,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var source = context.createBufferSource();
+ source.buffer = this.buffer;
+
+ var shaper = context.createWaveShaper();
+ is(shaper.curve, null, "The shaper curve must be null by default");
+
+ source.connect(shaper);
+
+ source.start(0);
+ return shaper;
+ },
+ createExpectedBuffers(context) {
+ var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate);
+ for (var i = 0; i < 2048; ++i) {
+ expectedBuffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+ }
+ this.buffer = expectedBuffer;
+ return expectedBuffer;
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_waveShaperPassThrough.html b/dom/media/webaudio/test/test_waveShaperPassThrough.html
new file mode 100644
index 0000000000..d34add9c90
--- /dev/null
+++ b/dom/media/webaudio/test/test_waveShaperPassThrough.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test WaveShaperNode with passthrough</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="webaudio.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+ length: 4096,
+ numberOfChannels: 1,
+ createGraph(context) {
+ var source = context.createBufferSource();
+ source.buffer = this.buffer;
+
+ var shaper = context.createWaveShaper();
+ shaper.curve = this.curve;
+
+ var shaperWrapped = SpecialPowers.wrap(shaper);
+ ok("passThrough" in shaperWrapped, "WaveShaperNode should support the passThrough API");
+ shaperWrapped.passThrough = true;
+
+ source.connect(shaper);
+
+ source.start(0);
+ return shaper;
+ },
+ createExpectedBuffers(context) {
+ this.buffer = context.createBuffer(1, 4096, context.sampleRate);
+ for (var i = 1; i < 4095; ++i) {
+ this.buffer.getChannelData(0)[i] = 2 * (i / 4096) - 1;
+ }
+ // Two out of range values
+ this.buffer.getChannelData(0)[0] = -2;
+ this.buffer.getChannelData(0)[4095] = 2;
+
+ this.curve = new Float32Array(2048);
+ for (var i = 0; i < 2048; ++i) {
+ this.curve[i] = Math.sin(100 * Math.PI * (i + 1) / context.sampleRate);
+ }
+
+ return [this.buffer];
+ },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/test_webAudio_muteTab.html b/dom/media/webaudio/test/test_webAudio_muteTab.html
new file mode 100644
index 0000000000..3a3d60eea7
--- /dev/null
+++ b/dom/media/webaudio/test/test_webAudio_muteTab.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+
+<script>
+/* import-globals-from ../../webrtc/tests/mochitests/mediaStreamPlayback.js */
+createHTML({
+ title: "Check tab muting when the tab plays audio via the Web Audio API",
+ bug: "1346880",
+ visible: false
+});
+
+/**
+ * Check that muting a tab results in no audible audio: mute a tab, in which
+ * an OscillatorNode is playing. The default audio output device is a
+ * pulseaudio null-sink. Simulateously, record the other side of the null
+ * sink, and check that no audio has been written to the sink, because the tab
+ * was muted. Then, umute the tab and check that audio is being sent to the
+ * null-sink. */
+runTest(async () => {
+ if (!SpecialPowers.getCharPref("media.audio_loopback_dev", "")) {
+ todo(false, "No loopback device set by framework. Try --use-test-media-devices");
+ return;
+ }
+
+ // Mute the tab
+ await SpecialPowers.toggleMuteState(true, window.top);
+
+ const stream = await getUserMedia({audio: {
+ noiseSuppression: false,
+ echoCancellation: false,
+ autoGainControl: false,
+ }});
+ try {
+ const ac = new AudioContext();
+ const osc = new OscillatorNode(ac);
+ osc.connect(ac.destination);
+ osc.start();
+
+ const analyser = new AudioStreamAnalyser(ac, stream);
+ // Wait for some time, checking there is only ever silent audio in the
+ // loopback stream. `waitForAnalysisSuccess` runs off requestAnimationFrame
+ let silenceFor = 3 / (1 / 60);
+ await analyser.waitForAnalysisSuccess(array => {
+ // `array` has values between 0 and 255, 0 being silence.
+ const sum = array.reduce((acc, v) => { return acc + v; });
+ if (sum == 0) {
+ silenceFor--;
+ } else {
+ info(`Sum of the array values ${sum}`);
+ ok(false, `Found non-silent data in the loopback stream while the tab was muted.`);
+ return true;
+ }
+ if (silenceFor == 0) {
+ ok(true, "Muting the tab was effective");
+ }
+ return silenceFor == 0;
+ });
+
+ // Unmute the tab
+ await SpecialPowers.toggleMuteState(false, window.top);
+
+ await analyser.waitForAnalysisSuccess(array => {
+ // `array` has values between 0 and 255, 0 being silence.
+ const sum = array.reduce((acc, v) => { return acc + v; });
+ if (sum != 0) {
+ info(`Sum after unmuting ${sum}`);
+ ok(true, "Unmuting the tab was effective");
+ return true;
+ } else {
+ // Increment again if we find silence.
+ silenceFor++;
+ if (silenceFor > 100) {
+ ok(false, "Unmuting wasn't effective")
+ return true;
+ }
+ return false;
+ }
+ });
+ } finally {
+ for (let t of stream.getTracks()) {
+ t.stop();
+ }
+ }
+});
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/media/webaudio/test/ting-44.1k-1ch.ogg b/dom/media/webaudio/test/ting-44.1k-1ch.ogg
new file mode 100644
index 0000000000..a11aaf1cbf
--- /dev/null
+++ b/dom/media/webaudio/test/ting-44.1k-1ch.ogg
Binary files differ
diff --git a/dom/media/webaudio/test/ting-44.1k-1ch.wav b/dom/media/webaudio/test/ting-44.1k-1ch.wav
new file mode 100644
index 0000000000..dd731e354c
--- /dev/null
+++ b/dom/media/webaudio/test/ting-44.1k-1ch.wav
Binary files differ
diff --git a/dom/media/webaudio/test/ting-44.1k-2ch.ogg b/dom/media/webaudio/test/ting-44.1k-2ch.ogg
new file mode 100644
index 0000000000..94e0014858
--- /dev/null
+++ b/dom/media/webaudio/test/ting-44.1k-2ch.ogg
Binary files differ
diff --git a/dom/media/webaudio/test/ting-44.1k-2ch.wav b/dom/media/webaudio/test/ting-44.1k-2ch.wav
new file mode 100644
index 0000000000..b2ef11ecbe
--- /dev/null
+++ b/dom/media/webaudio/test/ting-44.1k-2ch.wav
Binary files differ
diff --git a/dom/media/webaudio/test/ting-48k-1ch.ogg b/dom/media/webaudio/test/ting-48k-1ch.ogg
new file mode 100644
index 0000000000..f45ce33a58
--- /dev/null
+++ b/dom/media/webaudio/test/ting-48k-1ch.ogg
Binary files differ
diff --git a/dom/media/webaudio/test/ting-48k-1ch.wav b/dom/media/webaudio/test/ting-48k-1ch.wav
new file mode 100644
index 0000000000..91874dfbb2
--- /dev/null
+++ b/dom/media/webaudio/test/ting-48k-1ch.wav
Binary files differ
diff --git a/dom/media/webaudio/test/ting-48k-2ch.ogg b/dom/media/webaudio/test/ting-48k-2ch.ogg
new file mode 100644
index 0000000000..e4c564abbd
--- /dev/null
+++ b/dom/media/webaudio/test/ting-48k-2ch.ogg
Binary files differ
diff --git a/dom/media/webaudio/test/ting-48k-2ch.wav b/dom/media/webaudio/test/ting-48k-2ch.wav
new file mode 100644
index 0000000000..6adb8d3e5d
--- /dev/null
+++ b/dom/media/webaudio/test/ting-48k-2ch.wav
Binary files differ
diff --git a/dom/media/webaudio/test/ting-dualchannel44.1.wav b/dom/media/webaudio/test/ting-dualchannel44.1.wav
new file mode 100644
index 0000000000..62954394d3
--- /dev/null
+++ b/dom/media/webaudio/test/ting-dualchannel44.1.wav
Binary files differ
diff --git a/dom/media/webaudio/test/ting-dualchannel48.wav b/dom/media/webaudio/test/ting-dualchannel48.wav
new file mode 100644
index 0000000000..a0b8247888
--- /dev/null
+++ b/dom/media/webaudio/test/ting-dualchannel48.wav
Binary files differ
diff --git a/dom/media/webaudio/test/waveformatextensible.wav b/dom/media/webaudio/test/waveformatextensible.wav
new file mode 100644
index 0000000000..f16e588021
--- /dev/null
+++ b/dom/media/webaudio/test/waveformatextensible.wav
Binary files differ
diff --git a/dom/media/webaudio/test/waveformatextensiblebadmask.wav b/dom/media/webaudio/test/waveformatextensiblebadmask.wav
new file mode 100644
index 0000000000..3aa55df98e
--- /dev/null
+++ b/dom/media/webaudio/test/waveformatextensiblebadmask.wav
Binary files differ
diff --git a/dom/media/webaudio/test/webaudio.js b/dom/media/webaudio/test/webaudio.js
new file mode 100644
index 0000000000..049e0e5af3
--- /dev/null
+++ b/dom/media/webaudio/test/webaudio.js
@@ -0,0 +1,367 @@
+// Helpers for Web Audio tests
+
+// It is expected that the test defines this.
+/* global gTest */
+
+function expectException(func, exceptionCode) {
+ var threw = false;
+ try {
+ func();
+ } catch (ex) {
+ threw = true;
+ is(ex.constructor.name, "DOMException", "Expect a DOM exception");
+ is(ex.code, exceptionCode, "Expect the correct exception code");
+ }
+ ok(threw, "The exception was thrown");
+}
+
+function expectNoException(func) {
+ var threw = false;
+ try {
+ func();
+ } catch (ex) {
+ threw = true;
+ }
+ ok(!threw, "An exception was not thrown");
+}
+
+function expectTypeError(func) {
+ var threw = false;
+ try {
+ func();
+ } catch (ex) {
+ threw = true;
+ ok(ex instanceof TypeError, "Expect a TypeError");
+ }
+ ok(threw, "The exception was thrown");
+}
+
+function expectRejectedPromise(that, func, exceptionName) {
+ var promise = that[func]();
+
+ ok(promise instanceof Promise, "Expect a Promise");
+
+ promise
+ .then(function (res) {
+ ok(false, "Promise resolved when it should have been rejected.");
+ })
+ .catch(function (err) {
+ is(
+ err.name,
+ exceptionName,
+ "Promise correctly reject with " + exceptionName
+ );
+ });
+}
+
+function fuzzyCompare(a, b) {
+ return Math.abs(a - b) < 9e-3;
+}
+
+function compareChannels(
+ buf1,
+ buf2,
+ /*optional*/ length,
+ /*optional*/ sourceOffset,
+ /*optional*/ destOffset,
+ /*optional*/ skipLengthCheck
+) {
+ if (!skipLengthCheck) {
+ is(buf1.length, buf2.length, "Channels must have the same length");
+ }
+ sourceOffset = sourceOffset || 0;
+ destOffset = destOffset || 0;
+ if (length == undefined) {
+ length = buf1.length - sourceOffset;
+ }
+ var difference = 0;
+ var maxDifference = 0;
+ var firstBadIndex = -1;
+ for (var i = 0; i < length; ++i) {
+ if (!fuzzyCompare(buf1[i + sourceOffset], buf2[i + destOffset])) {
+ difference++;
+ maxDifference = Math.max(
+ maxDifference,
+ Math.abs(buf1[i + sourceOffset] - buf2[i + destOffset])
+ );
+ if (firstBadIndex == -1) {
+ firstBadIndex = i;
+ }
+ }
+ }
+
+ is(
+ difference,
+ 0,
+ "maxDifference: " +
+ maxDifference +
+ ", first bad index: " +
+ firstBadIndex +
+ " with test-data offset " +
+ sourceOffset +
+ " and expected-data offset " +
+ destOffset +
+ "; corresponding values " +
+ buf1[firstBadIndex + sourceOffset] +
+ " and " +
+ buf2[firstBadIndex + destOffset] +
+ " --- differences"
+ );
+}
+
+function compareBuffers(got, expected) {
+ if (got.numberOfChannels != expected.numberOfChannels) {
+ is(
+ got.numberOfChannels,
+ expected.numberOfChannels,
+ "Correct number of buffer channels"
+ );
+ return;
+ }
+ if (got.length != expected.length) {
+ is(got.length, expected.length, "Correct buffer length");
+ return;
+ }
+ if (got.sampleRate != expected.sampleRate) {
+ is(got.sampleRate, expected.sampleRate, "Correct sample rate");
+ return;
+ }
+
+ for (var i = 0; i < got.numberOfChannels; ++i) {
+ compareChannels(
+ got.getChannelData(i),
+ expected.getChannelData(i),
+ got.length,
+ 0,
+ 0,
+ true
+ );
+ }
+}
+
+/**
+ * Compute the root mean square (RMS,
+ * <http://en.wikipedia.org/wiki/Root_mean_square>) of a channel of a slice
+ * (defined by `start` and `end`) of an AudioBuffer.
+ *
+ * This is useful to detect that a buffer is noisy or silent.
+ */
+function rms(audiobuffer, channel = 0, start = 0, end = audiobuffer.length) {
+ var buffer = audiobuffer.getChannelData(channel);
+ var rms = 0;
+ for (var i = start; i < end; i++) {
+ rms += buffer[i] * buffer[i];
+ }
+
+ rms /= buffer.length;
+ rms = Math.sqrt(rms);
+ return rms;
+}
+
+function getEmptyBuffer(context, length) {
+ return context.createBuffer(
+ gTest.numberOfChannels,
+ length,
+ context.sampleRate
+ );
+}
+
+function isChannelSilent(channel) {
+ for (var i = 0; i < channel.length; ++i) {
+ if (channel[i] != 0.0) {
+ return false;
+ }
+ }
+ return true;
+}
+
+const HRTFPannersByRate = new Map();
+/**
+ * Return a promise that resolves when PannerNodes with HRTF panningModel in
+ * an AudioContext of the specified sample rate will be ready to produce
+ * non-zero output. Before the HRIR database is loaded, such PannerNodes
+ * produce zero output.
+ */
+async function promiseHRTFReady(sampleRate) {
+ if (HRTFPannersByRate.has(sampleRate)) {
+ return;
+ }
+
+ const ctx = new AudioContext({ sampleRate });
+ const processor = ctx.createScriptProcessor(4096, 2, 0);
+ const panner = new PannerNode(ctx, { panningModel: "HRTF" });
+ panner.connect(processor);
+ const oscillator = ctx.createOscillator();
+ oscillator.connect(panner);
+ oscillator.start(0);
+
+ await new Promise(r => {
+ processor.onaudioprocess = e => {
+ if (!isChannelSilent(e.inputBuffer.getChannelData(0))) {
+ r();
+ }
+ };
+ });
+
+ ctx.suspend();
+ oscillator.disconnect();
+ panner.disconnect();
+ processor.onaudioprocess = null;
+ // Keep a reference to the panner so that the database is not unloaded.
+ HRTFPannersByRate.set(sampleRate, panner);
+}
+
+/**
+ * This function assumes that the test file defines a single gTest variable with
+ * the following properties and methods:
+ *
+ * + numberOfChannels: optional property which specifies the number of channels
+ * in the output. The default value is 2.
+ * + createGraph: mandatory method which takes a context object and does
+ * everything needed in order to set up the Web Audio graph.
+ * This function returns the node to be inspected.
+ * + createGraphAsync: async version of createGraph. This function takes
+ * a callback which should be called with an argument
+ * set to the node to be inspected when the callee is
+ * ready to proceed with the test. Either this function
+ * or createGraph must be provided.
+ * + createExpectedBuffers: optional method which takes a context object and
+ * returns either one expected buffer or an array of
+ * them, designating what is expected to be observed
+ * in the output. If omitted, the output is expected
+ * to be silence. All buffers must have the same
+ * length, which must be a bufferSize supported by
+ * ScriptProcessorNode. This function is guaranteed
+ * to be called before createGraph.
+ * + length: property equal to the total number of frames which we are waiting
+ * to see in the output, mandatory if createExpectedBuffers is not
+ * provided, in which case it must be a bufferSize supported by
+ * ScriptProcessorNode (256, 512, 1024, 2048, 4096, 8192, or 16384).
+ * If createExpectedBuffers is provided then this must be equal to
+ * the number of expected buffers * the expected buffer length.
+ *
+ * + skipOfflineContextTests: optional. when true, skips running tests on an offline
+ * context by circumventing testOnOfflineContext.
+ */
+function runTest() {
+ function done() {
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ function runTestFunction() {
+ if (!gTest.numberOfChannels) {
+ gTest.numberOfChannels = 2; // default
+ }
+
+ var testLength;
+
+ function runTestOnContext(context, callback, testOutput) {
+ if (!gTest.createExpectedBuffers) {
+ // Assume that the output is silence
+ var expectedBuffers = getEmptyBuffer(context, gTest.length);
+ } else {
+ var expectedBuffers = gTest.createExpectedBuffers(context);
+ }
+ if (!(expectedBuffers instanceof Array)) {
+ expectedBuffers = [expectedBuffers];
+ }
+ var expectedFrames = 0;
+ for (var i = 0; i < expectedBuffers.length; ++i) {
+ is(
+ expectedBuffers[i].numberOfChannels,
+ gTest.numberOfChannels,
+ "Correct number of channels for expected buffer " + i
+ );
+ expectedFrames += expectedBuffers[i].length;
+ }
+ if (gTest.length && gTest.createExpectedBuffers) {
+ is(expectedFrames, gTest.length, "Correct number of expected frames");
+ }
+
+ if (gTest.createGraphAsync) {
+ gTest.createGraphAsync(context, function (nodeToInspect) {
+ testOutput(nodeToInspect, expectedBuffers, callback);
+ });
+ } else {
+ testOutput(gTest.createGraph(context), expectedBuffers, callback);
+ }
+ }
+
+ function testOnNormalContext(callback) {
+ function testOutput(nodeToInspect, expectedBuffers, callback) {
+ testLength = 0;
+ var sp = context.createScriptProcessor(
+ expectedBuffers[0].length,
+ gTest.numberOfChannels,
+ 0
+ );
+ nodeToInspect.connect(sp);
+ sp.onaudioprocess = function (e) {
+ var expectedBuffer = expectedBuffers.shift();
+ testLength += expectedBuffer.length;
+ compareBuffers(e.inputBuffer, expectedBuffer);
+ if (!expectedBuffers.length) {
+ sp.onaudioprocess = null;
+ callback();
+ }
+ };
+ }
+ var context = new AudioContext();
+ runTestOnContext(context, callback, testOutput);
+ }
+
+ function testOnOfflineContext(callback, sampleRate) {
+ function testOutput(nodeToInspect, expectedBuffers, callback) {
+ nodeToInspect.connect(context.destination);
+ context.oncomplete = function (e) {
+ var samplesSeen = 0;
+ while (expectedBuffers.length) {
+ var expectedBuffer = expectedBuffers.shift();
+ is(
+ e.renderedBuffer.numberOfChannels,
+ expectedBuffer.numberOfChannels,
+ "Correct number of input buffer channels"
+ );
+ for (var i = 0; i < e.renderedBuffer.numberOfChannels; ++i) {
+ compareChannels(
+ e.renderedBuffer.getChannelData(i),
+ expectedBuffer.getChannelData(i),
+ expectedBuffer.length,
+ samplesSeen,
+ undefined,
+ true
+ );
+ }
+ samplesSeen += expectedBuffer.length;
+ }
+ callback();
+ };
+ context.startRendering();
+ }
+
+ var context = new OfflineAudioContext(
+ gTest.numberOfChannels,
+ testLength,
+ sampleRate
+ );
+ runTestOnContext(context, callback, testOutput);
+ }
+
+ testOnNormalContext(function () {
+ if (!gTest.skipOfflineContextTests) {
+ testOnOfflineContext(function () {
+ testOnOfflineContext(done, 44100);
+ }, 48000);
+ } else {
+ done();
+ }
+ });
+ }
+
+ if (document.readyState !== "complete") {
+ addLoadEvent(runTestFunction);
+ } else {
+ runTestFunction();
+ }
+}