/src/mozilla-central/dom/media/webaudio/blink/Reverb.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (C) 2010 Google Inc. All rights reserved. |
3 | | * |
4 | | * Redistribution and use in source and binary forms, with or without |
5 | | * modification, are permitted provided that the following conditions |
6 | | * are met: |
7 | | * |
8 | | * 1. Redistributions of source code must retain the above copyright |
9 | | * notice, this list of conditions and the following disclaimer. |
10 | | * 2. Redistributions in binary form must reproduce the above copyright |
11 | | * notice, this list of conditions and the following disclaimer in the |
12 | | * documentation and/or other materials provided with the distribution. |
13 | | * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of |
14 | | * its contributors may be used to endorse or promote products derived |
15 | | * from this software without specific prior written permission. |
16 | | * |
17 | | * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
18 | | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
19 | | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
20 | | * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
21 | | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
22 | | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
23 | | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
24 | | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
25 | | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
26 | | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
27 | | */ |
28 | | |
29 | | #include "Reverb.h" |
30 | | #include "ReverbConvolverStage.h" |
31 | | |
32 | | #include <math.h> |
33 | | #include "ReverbConvolver.h" |
34 | | #include "mozilla/FloatingPoint.h" |
35 | | |
36 | | using namespace mozilla; |
37 | | |
38 | | namespace WebCore { |
39 | | |
40 | | // Empirical gain calibration tested across many impulse responses to ensure perceived volume is same as dry (unprocessed) signal |
41 | | const float GainCalibration = 0.00125f; |
42 | | const float GainCalibrationSampleRate = 44100; |
43 | | |
44 | | // A minimum power value to when normalizing a silent (or very quiet) impulse response |
45 | | const float MinPower = 0.000125f; |
46 | | |
47 | | static float calculateNormalizationScale(const nsTArray<const float*>& response, size_t aLength, float sampleRate) |
48 | 0 | { |
49 | 0 | // Normalize by RMS power |
50 | 0 | size_t numberOfChannels = response.Length(); |
51 | 0 |
|
52 | 0 | float power = 0; |
53 | 0 |
|
54 | 0 | for (size_t i = 0; i < numberOfChannels; ++i) { |
55 | 0 | float channelPower = AudioBufferSumOfSquares(response[i], aLength); |
56 | 0 | power += channelPower; |
57 | 0 | } |
58 | 0 |
|
59 | 0 | power = sqrt(power / (numberOfChannels * aLength)); |
60 | 0 |
|
61 | 0 | // Protect against accidental overload |
62 | 0 | if (!IsFinite(power) || IsNaN(power) || power < MinPower) |
63 | 0 | power = MinPower; |
64 | 0 |
|
65 | 0 | float scale = 1 / power; |
66 | 0 |
|
67 | 0 | scale *= GainCalibration; // calibrate to make perceived volume same as unprocessed |
68 | 0 |
|
69 | 0 | // Scale depends on sample-rate. |
70 | 0 | if (sampleRate) |
71 | 0 | scale *= GainCalibrationSampleRate / sampleRate; |
72 | 0 |
|
73 | 0 | // True-stereo compensation |
74 | 0 | if (numberOfChannels == 4) |
75 | 0 | scale *= 0.5f; |
76 | 0 |
|
77 | 0 | return scale; |
78 | 0 | } |
79 | | |
80 | | Reverb::Reverb(const AudioChunk& impulseResponse, size_t maxFFTSize, bool useBackgroundThreads, bool normalize, float sampleRate) |
81 | 0 | { |
82 | 0 | size_t impulseResponseBufferLength = impulseResponse.mDuration; |
83 | 0 | float scale = impulseResponse.mVolume; |
84 | 0 |
|
85 | 0 | AutoTArray<const float*,4> irChannels(impulseResponse.ChannelData<float>()); |
86 | 0 | AutoTArray<float,1024> tempBuf; |
87 | 0 |
|
88 | 0 | if (normalize) { |
89 | 0 | scale = calculateNormalizationScale(irChannels, impulseResponseBufferLength, sampleRate); |
90 | 0 | } |
91 | 0 |
|
92 | 0 | if (scale != 1.0f) { |
93 | 0 | tempBuf.SetLength(irChannels.Length()*impulseResponseBufferLength); |
94 | 0 | for (uint32_t i = 0; i < irChannels.Length(); ++i) { |
95 | 0 | float* buf = &tempBuf[i*impulseResponseBufferLength]; |
96 | 0 | AudioBufferCopyWithScale(irChannels[i], scale, buf, |
97 | 0 | impulseResponseBufferLength); |
98 | 0 | irChannels[i] = buf; |
99 | 0 | } |
100 | 0 | } |
101 | 0 |
|
102 | 0 | initialize(irChannels, impulseResponseBufferLength, |
103 | 0 | maxFFTSize, useBackgroundThreads); |
104 | 0 | } |
105 | | |
106 | | size_t Reverb::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const |
107 | 0 | { |
108 | 0 | size_t amount = aMallocSizeOf(this); |
109 | 0 | amount += m_convolvers.ShallowSizeOfExcludingThis(aMallocSizeOf); |
110 | 0 | for (size_t i = 0; i < m_convolvers.Length(); i++) { |
111 | 0 | if (m_convolvers[i]) { |
112 | 0 | amount += m_convolvers[i]->sizeOfIncludingThis(aMallocSizeOf); |
113 | 0 | } |
114 | 0 | } |
115 | 0 |
|
116 | 0 | amount += m_tempBuffer.SizeOfExcludingThis(aMallocSizeOf, false); |
117 | 0 | return amount; |
118 | 0 | } |
119 | | |
120 | | |
121 | | void Reverb::initialize(const nsTArray<const float*>& impulseResponseBuffer, |
122 | | size_t impulseResponseBufferLength, |
123 | | size_t maxFFTSize, bool useBackgroundThreads) |
124 | 0 | { |
125 | 0 | m_impulseResponseLength = impulseResponseBufferLength; |
126 | 0 |
|
127 | 0 | // The reverb can handle a mono impulse response and still do stereo processing |
128 | 0 | size_t numResponseChannels = impulseResponseBuffer.Length(); |
129 | 0 | MOZ_ASSERT(numResponseChannels > 0); |
130 | 0 | // The number of convolvers required is at least the number of audio |
131 | 0 | // channels. Even if there is initially only one audio channel, another |
132 | 0 | // may be added later, and so a second convolver is created now while the |
133 | 0 | // impulse response is available. |
134 | 0 | size_t numConvolvers = std::max<size_t>(numResponseChannels, 2); |
135 | 0 | m_convolvers.SetCapacity(numConvolvers); |
136 | 0 |
|
137 | 0 | int convolverRenderPhase = 0; |
138 | 0 | for (size_t i = 0; i < numConvolvers; ++i) { |
139 | 0 | size_t channelIndex = i < numResponseChannels ? i : 0; |
140 | 0 | const float* channel = impulseResponseBuffer[channelIndex]; |
141 | 0 | size_t length = impulseResponseBufferLength; |
142 | 0 |
|
143 | 0 | nsAutoPtr<ReverbConvolver> convolver(new ReverbConvolver(channel, length, maxFFTSize, convolverRenderPhase, useBackgroundThreads)); |
144 | 0 | m_convolvers.AppendElement(convolver.forget()); |
145 | 0 |
|
146 | 0 | convolverRenderPhase += WEBAUDIO_BLOCK_SIZE; |
147 | 0 | } |
148 | 0 |
|
149 | 0 | // For "True" stereo processing we allocate a temporary buffer to avoid repeatedly allocating it in the process() method. |
150 | 0 | // It can be bad to allocate memory in a real-time thread. |
151 | 0 | if (numResponseChannels == 4) { |
152 | 0 | m_tempBuffer.AllocateChannels(2); |
153 | 0 | WriteZeroesToAudioBlock(&m_tempBuffer, 0, WEBAUDIO_BLOCK_SIZE); |
154 | 0 | } |
155 | 0 | } |
156 | | |
157 | | void Reverb::process(const AudioBlock* sourceBus, AudioBlock* destinationBus) |
158 | 0 | { |
159 | 0 | // Do a fairly comprehensive sanity check. |
160 | 0 | // If these conditions are satisfied, all of the source and destination pointers will be valid for the various matrixing cases. |
161 | 0 | bool isSafeToProcess = |
162 | 0 | sourceBus && destinationBus && sourceBus->ChannelCount() > 0 && |
163 | 0 | destinationBus->mChannelData.Length() > 0 && |
164 | 0 | WEBAUDIO_BLOCK_SIZE <= MaxFrameSize && |
165 | 0 | WEBAUDIO_BLOCK_SIZE <= size_t(sourceBus->GetDuration()) && |
166 | 0 | WEBAUDIO_BLOCK_SIZE <= size_t(destinationBus->GetDuration()); |
167 | 0 |
|
168 | 0 | MOZ_ASSERT(isSafeToProcess); |
169 | 0 | if (!isSafeToProcess) |
170 | 0 | return; |
171 | 0 | |
172 | 0 | // For now only handle mono or stereo output |
173 | 0 | MOZ_ASSERT(destinationBus->ChannelCount() <= 2); |
174 | 0 |
|
175 | 0 | float* destinationChannelL = static_cast<float*>(const_cast<void*>(destinationBus->mChannelData[0])); |
176 | 0 | const float* sourceBusL = static_cast<const float*>(sourceBus->mChannelData[0]); |
177 | 0 |
|
178 | 0 | // Handle input -> output matrixing... |
179 | 0 | size_t numInputChannels = sourceBus->ChannelCount(); |
180 | 0 | size_t numOutputChannels = destinationBus->ChannelCount(); |
181 | 0 | size_t numReverbChannels = m_convolvers.Length(); |
182 | 0 |
|
183 | 0 | if (numInputChannels == 2 && numReverbChannels == 2 && numOutputChannels == 2) { |
184 | 0 | // 2 -> 2 -> 2 |
185 | 0 | const float* sourceBusR = static_cast<const float*>(sourceBus->mChannelData[1]); |
186 | 0 | float* destinationChannelR = static_cast<float*>(const_cast<void*>(destinationBus->mChannelData[1])); |
187 | 0 | m_convolvers[0]->process(sourceBusL, destinationChannelL); |
188 | 0 | m_convolvers[1]->process(sourceBusR, destinationChannelR); |
189 | 0 | } else if (numInputChannels == 1 && numOutputChannels == 2 && numReverbChannels == 2) { |
190 | 0 | // 1 -> 2 -> 2 |
191 | 0 | for (int i = 0; i < 2; ++i) { |
192 | 0 | float* destinationChannel = static_cast<float*>(const_cast<void*>(destinationBus->mChannelData[i])); |
193 | 0 | m_convolvers[i]->process(sourceBusL, destinationChannel); |
194 | 0 | } |
195 | 0 | } else if (numInputChannels == 1 && numOutputChannels == 1) { |
196 | 0 | // 1 -> 1 -> 1 (Only one of the convolvers is used.) |
197 | 0 | m_convolvers[0]->process(sourceBusL, destinationChannelL); |
198 | 0 | } else if (numInputChannels == 2 && numReverbChannels == 4 && numOutputChannels == 2) { |
199 | 0 | // 2 -> 4 -> 2 ("True" stereo) |
200 | 0 | const float* sourceBusR = static_cast<const float*>(sourceBus->mChannelData[1]); |
201 | 0 | float* destinationChannelR = static_cast<float*>(const_cast<void*>(destinationBus->mChannelData[1])); |
202 | 0 |
|
203 | 0 | float* tempChannelL = static_cast<float*>(const_cast<void*>(m_tempBuffer.mChannelData[0])); |
204 | 0 | float* tempChannelR = static_cast<float*>(const_cast<void*>(m_tempBuffer.mChannelData[1])); |
205 | 0 |
|
206 | 0 | // Process left virtual source |
207 | 0 | m_convolvers[0]->process(sourceBusL, destinationChannelL); |
208 | 0 | m_convolvers[1]->process(sourceBusL, destinationChannelR); |
209 | 0 |
|
210 | 0 | // Process right virtual source |
211 | 0 | m_convolvers[2]->process(sourceBusR, tempChannelL); |
212 | 0 | m_convolvers[3]->process(sourceBusR, tempChannelR); |
213 | 0 |
|
214 | 0 | AudioBufferAddWithScale(tempChannelL, 1.0f, destinationChannelL, sourceBus->GetDuration()); |
215 | 0 | AudioBufferAddWithScale(tempChannelR, 1.0f, destinationChannelR, sourceBus->GetDuration()); |
216 | 0 | } else if (numInputChannels == 1 && numReverbChannels == 4 && numOutputChannels == 2) { |
217 | 0 | // 1 -> 4 -> 2 (Processing mono with "True" stereo impulse response) |
218 | 0 | // This is an inefficient use of a four-channel impulse response, but we should handle the case. |
219 | 0 | float* destinationChannelR = static_cast<float*>(const_cast<void*>(destinationBus->mChannelData[1])); |
220 | 0 |
|
221 | 0 | float* tempChannelL = static_cast<float*>(const_cast<void*>(m_tempBuffer.mChannelData[0])); |
222 | 0 | float* tempChannelR = static_cast<float*>(const_cast<void*>(m_tempBuffer.mChannelData[1])); |
223 | 0 |
|
224 | 0 | // Process left virtual source |
225 | 0 | m_convolvers[0]->process(sourceBusL, destinationChannelL); |
226 | 0 | m_convolvers[1]->process(sourceBusL, destinationChannelR); |
227 | 0 |
|
228 | 0 | // Process right virtual source |
229 | 0 | m_convolvers[2]->process(sourceBusL, tempChannelL); |
230 | 0 | m_convolvers[3]->process(sourceBusL, tempChannelR); |
231 | 0 |
|
232 | 0 | AudioBufferAddWithScale(tempChannelL, 1.0f, destinationChannelL, sourceBus->GetDuration()); |
233 | 0 | AudioBufferAddWithScale(tempChannelR, 1.0f, destinationChannelR, sourceBus->GetDuration()); |
234 | 0 | } else { |
235 | 0 | MOZ_ASSERT_UNREACHABLE("Unexpected Reverb configuration"); |
236 | 0 | destinationBus->SetNull(destinationBus->GetDuration()); |
237 | 0 | } |
238 | 0 | } |
239 | | |
240 | | } // namespace WebCore |