|
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 = -58; |
|
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(ThreadSharedFloatArrayBufferList* response, size_t aLength, float sampleRate) |
|
48 { |
|
49 // Normalize by RMS power |
|
50 size_t numberOfChannels = response->GetChannels(); |
|
51 |
|
52 float power = 0; |
|
53 |
|
54 for (size_t i = 0; i < numberOfChannels; ++i) { |
|
55 float channelPower = AudioBufferSumOfSquares(static_cast<const float*>(response->GetData(i)), aLength); |
|
56 power += channelPower; |
|
57 } |
|
58 |
|
59 power = sqrt(power / (numberOfChannels * aLength)); |
|
60 |
|
61 // Protect against accidental overload |
|
62 if (!IsFinite(power) || IsNaN(power) || power < MinPower) |
|
63 power = MinPower; |
|
64 |
|
65 float scale = 1 / power; |
|
66 |
|
67 scale *= powf(10, GainCalibration * 0.05f); // calibrate to make perceived volume same as unprocessed |
|
68 |
|
69 // Scale depends on sample-rate. |
|
70 if (sampleRate) |
|
71 scale *= GainCalibrationSampleRate / sampleRate; |
|
72 |
|
73 // True-stereo compensation |
|
74 if (response->GetChannels() == 4) |
|
75 scale *= 0.5f; |
|
76 |
|
77 return scale; |
|
78 } |
|
79 |
|
80 Reverb::Reverb(ThreadSharedFloatArrayBufferList* impulseResponse, size_t impulseResponseBufferLength, size_t renderSliceSize, size_t maxFFTSize, size_t numberOfChannels, bool useBackgroundThreads, bool normalize, float sampleRate) |
|
81 { |
|
82 float scale = 1; |
|
83 |
|
84 nsAutoTArray<const float*,4> irChannels; |
|
85 for (size_t i = 0; i < impulseResponse->GetChannels(); ++i) { |
|
86 irChannels.AppendElement(impulseResponse->GetData(i)); |
|
87 } |
|
88 nsAutoTArray<float,1024> tempBuf; |
|
89 |
|
90 if (normalize) { |
|
91 scale = calculateNormalizationScale(impulseResponse, impulseResponseBufferLength, sampleRate); |
|
92 |
|
93 if (scale) { |
|
94 tempBuf.SetLength(irChannels.Length()*impulseResponseBufferLength); |
|
95 for (uint32_t i = 0; i < irChannels.Length(); ++i) { |
|
96 float* buf = &tempBuf[i*impulseResponseBufferLength]; |
|
97 AudioBufferCopyWithScale(irChannels[i], scale, buf, |
|
98 impulseResponseBufferLength); |
|
99 irChannels[i] = buf; |
|
100 } |
|
101 } |
|
102 } |
|
103 |
|
104 initialize(irChannels, impulseResponseBufferLength, renderSliceSize, |
|
105 maxFFTSize, numberOfChannels, useBackgroundThreads); |
|
106 } |
|
107 |
|
108 size_t Reverb::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const |
|
109 { |
|
110 size_t amount = aMallocSizeOf(this); |
|
111 amount += m_convolvers.SizeOfExcludingThis(aMallocSizeOf); |
|
112 for (size_t i = 0; i < m_convolvers.Length(); i++) { |
|
113 if (m_convolvers[i]) { |
|
114 amount += m_convolvers[i]->sizeOfIncludingThis(aMallocSizeOf); |
|
115 } |
|
116 } |
|
117 |
|
118 amount += m_tempBuffer.SizeOfExcludingThis(aMallocSizeOf, false); |
|
119 return amount; |
|
120 } |
|
121 |
|
122 |
|
123 void Reverb::initialize(const nsTArray<const float*>& impulseResponseBuffer, |
|
124 size_t impulseResponseBufferLength, size_t renderSliceSize, |
|
125 size_t maxFFTSize, size_t numberOfChannels, bool useBackgroundThreads) |
|
126 { |
|
127 m_impulseResponseLength = impulseResponseBufferLength; |
|
128 |
|
129 // The reverb can handle a mono impulse response and still do stereo processing |
|
130 size_t numResponseChannels = impulseResponseBuffer.Length(); |
|
131 m_convolvers.SetCapacity(numberOfChannels); |
|
132 |
|
133 int convolverRenderPhase = 0; |
|
134 for (size_t i = 0; i < numResponseChannels; ++i) { |
|
135 const float* channel = impulseResponseBuffer[i]; |
|
136 size_t length = impulseResponseBufferLength; |
|
137 |
|
138 nsAutoPtr<ReverbConvolver> convolver(new ReverbConvolver(channel, length, renderSliceSize, maxFFTSize, convolverRenderPhase, useBackgroundThreads)); |
|
139 m_convolvers.AppendElement(convolver.forget()); |
|
140 |
|
141 convolverRenderPhase += renderSliceSize; |
|
142 } |
|
143 |
|
144 // For "True" stereo processing we allocate a temporary buffer to avoid repeatedly allocating it in the process() method. |
|
145 // It can be bad to allocate memory in a real-time thread. |
|
146 if (numResponseChannels == 4) { |
|
147 AllocateAudioBlock(2, &m_tempBuffer); |
|
148 WriteZeroesToAudioBlock(&m_tempBuffer, 0, WEBAUDIO_BLOCK_SIZE); |
|
149 } |
|
150 } |
|
151 |
|
152 void Reverb::process(const AudioChunk* sourceBus, AudioChunk* destinationBus, size_t framesToProcess) |
|
153 { |
|
154 // Do a fairly comprehensive sanity check. |
|
155 // If these conditions are satisfied, all of the source and destination pointers will be valid for the various matrixing cases. |
|
156 bool isSafeToProcess = sourceBus && destinationBus && sourceBus->mChannelData.Length() > 0 && destinationBus->mChannelData.Length() > 0 |
|
157 && framesToProcess <= MaxFrameSize && framesToProcess <= size_t(sourceBus->mDuration) && framesToProcess <= size_t(destinationBus->mDuration); |
|
158 |
|
159 MOZ_ASSERT(isSafeToProcess); |
|
160 if (!isSafeToProcess) |
|
161 return; |
|
162 |
|
163 // For now only handle mono or stereo output |
|
164 MOZ_ASSERT(destinationBus->mChannelData.Length() <= 2); |
|
165 |
|
166 float* destinationChannelL = static_cast<float*>(const_cast<void*>(destinationBus->mChannelData[0])); |
|
167 const float* sourceBusL = static_cast<const float*>(sourceBus->mChannelData[0]); |
|
168 |
|
169 // Handle input -> output matrixing... |
|
170 size_t numInputChannels = sourceBus->mChannelData.Length(); |
|
171 size_t numOutputChannels = destinationBus->mChannelData.Length(); |
|
172 size_t numReverbChannels = m_convolvers.Length(); |
|
173 |
|
174 if (numInputChannels == 2 && numReverbChannels == 2 && numOutputChannels == 2) { |
|
175 // 2 -> 2 -> 2 |
|
176 const float* sourceBusR = static_cast<const float*>(sourceBus->mChannelData[1]); |
|
177 float* destinationChannelR = static_cast<float*>(const_cast<void*>(destinationBus->mChannelData[1])); |
|
178 m_convolvers[0]->process(sourceBusL, sourceBus->mDuration, destinationChannelL, destinationBus->mDuration, framesToProcess); |
|
179 m_convolvers[1]->process(sourceBusR, sourceBus->mDuration, destinationChannelR, destinationBus->mDuration, framesToProcess); |
|
180 } else if (numInputChannels == 1 && numOutputChannels == 2 && numReverbChannels == 2) { |
|
181 // 1 -> 2 -> 2 |
|
182 for (int i = 0; i < 2; ++i) { |
|
183 float* destinationChannel = static_cast<float*>(const_cast<void*>(destinationBus->mChannelData[i])); |
|
184 m_convolvers[i]->process(sourceBusL, sourceBus->mDuration, destinationChannel, destinationBus->mDuration, framesToProcess); |
|
185 } |
|
186 } else if (numInputChannels == 1 && numReverbChannels == 1 && numOutputChannels == 2) { |
|
187 // 1 -> 1 -> 2 |
|
188 m_convolvers[0]->process(sourceBusL, sourceBus->mDuration, destinationChannelL, destinationBus->mDuration, framesToProcess); |
|
189 |
|
190 // simply copy L -> R |
|
191 float* destinationChannelR = static_cast<float*>(const_cast<void*>(destinationBus->mChannelData[1])); |
|
192 bool isCopySafe = destinationChannelL && destinationChannelR && size_t(destinationBus->mDuration) >= framesToProcess && size_t(destinationBus->mDuration) >= framesToProcess; |
|
193 MOZ_ASSERT(isCopySafe); |
|
194 if (!isCopySafe) |
|
195 return; |
|
196 PodCopy(destinationChannelR, destinationChannelL, framesToProcess); |
|
197 } else if (numInputChannels == 1 && numReverbChannels == 1 && numOutputChannels == 1) { |
|
198 // 1 -> 1 -> 1 |
|
199 m_convolvers[0]->process(sourceBusL, sourceBus->mDuration, destinationChannelL, destinationBus->mDuration, framesToProcess); |
|
200 } else if (numInputChannels == 2 && numReverbChannels == 4 && numOutputChannels == 2) { |
|
201 // 2 -> 4 -> 2 ("True" stereo) |
|
202 const float* sourceBusR = static_cast<const float*>(sourceBus->mChannelData[1]); |
|
203 float* destinationChannelR = static_cast<float*>(const_cast<void*>(destinationBus->mChannelData[1])); |
|
204 |
|
205 float* tempChannelL = static_cast<float*>(const_cast<void*>(m_tempBuffer.mChannelData[0])); |
|
206 float* tempChannelR = static_cast<float*>(const_cast<void*>(m_tempBuffer.mChannelData[1])); |
|
207 |
|
208 // Process left virtual source |
|
209 m_convolvers[0]->process(sourceBusL, sourceBus->mDuration, destinationChannelL, destinationBus->mDuration, framesToProcess); |
|
210 m_convolvers[1]->process(sourceBusL, sourceBus->mDuration, destinationChannelR, destinationBus->mDuration, framesToProcess); |
|
211 |
|
212 // Process right virtual source |
|
213 m_convolvers[2]->process(sourceBusR, sourceBus->mDuration, tempChannelL, m_tempBuffer.mDuration, framesToProcess); |
|
214 m_convolvers[3]->process(sourceBusR, sourceBus->mDuration, tempChannelR, m_tempBuffer.mDuration, framesToProcess); |
|
215 |
|
216 AudioBufferAddWithScale(tempChannelL, 1.0f, destinationChannelL, sourceBus->mDuration); |
|
217 AudioBufferAddWithScale(tempChannelR, 1.0f, destinationChannelR, sourceBus->mDuration); |
|
218 } else if (numInputChannels == 1 && numReverbChannels == 4 && numOutputChannels == 2) { |
|
219 // 1 -> 4 -> 2 (Processing mono with "True" stereo impulse response) |
|
220 // This is an inefficient use of a four-channel impulse response, but we should handle the case. |
|
221 float* destinationChannelR = static_cast<float*>(const_cast<void*>(destinationBus->mChannelData[1])); |
|
222 |
|
223 float* tempChannelL = static_cast<float*>(const_cast<void*>(m_tempBuffer.mChannelData[0])); |
|
224 float* tempChannelR = static_cast<float*>(const_cast<void*>(m_tempBuffer.mChannelData[1])); |
|
225 |
|
226 // Process left virtual source |
|
227 m_convolvers[0]->process(sourceBusL, sourceBus->mDuration, destinationChannelL, destinationBus->mDuration, framesToProcess); |
|
228 m_convolvers[1]->process(sourceBusL, sourceBus->mDuration, destinationChannelR, destinationBus->mDuration, framesToProcess); |
|
229 |
|
230 // Process right virtual source |
|
231 m_convolvers[2]->process(sourceBusL, sourceBus->mDuration, tempChannelL, m_tempBuffer.mDuration, framesToProcess); |
|
232 m_convolvers[3]->process(sourceBusL, sourceBus->mDuration, tempChannelR, m_tempBuffer.mDuration, framesToProcess); |
|
233 |
|
234 AudioBufferAddWithScale(tempChannelL, 1.0f, destinationChannelL, sourceBus->mDuration); |
|
235 AudioBufferAddWithScale(tempChannelR, 1.0f, destinationChannelR, sourceBus->mDuration); |
|
236 } else { |
|
237 // Handle gracefully any unexpected / unsupported matrixing |
|
238 // FIXME: add code for 5.1 support... |
|
239 destinationBus->SetNull(destinationBus->mDuration); |
|
240 } |
|
241 } |
|
242 |
|
243 void Reverb::reset() |
|
244 { |
|
245 for (size_t i = 0; i < m_convolvers.Length(); ++i) |
|
246 m_convolvers[i]->reset(); |
|
247 } |
|
248 |
|
249 size_t Reverb::latencyFrames() const |
|
250 { |
|
251 return !m_convolvers.IsEmpty() ? m_convolvers[0]->latencyFrames() : 0; |
|
252 } |
|
253 |
|
254 } // namespace WebCore |