1/*
2Copyright 2018 Google Inc. All Rights Reserved.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS-IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17// Prevent Visual Studio from complaining about std::copy_n.
18#if defined(_WIN32)
19#define _SCL_SECURE_NO_WARNINGS
20#endif
21
22#include "platforms/common/room_effects_utils.h"
23
24#include <algorithm>
25#include <cmath>
26#include <numeric>
27
28namespace vraudio {
29
30namespace {
31
32// Air absorption coefficients at 20 degrees Celsius and 50% relative humidity,
33// according to:
34// http://www.music.mcgill.ca/~gary/courses/papers/Moorer-Reverb-CMJ-1979.pdf
35// These coefficients have been extrapolated to other frequencies by fitting
36// an exponential curve:
37//
38// m = a + be^(-cx) where:
39//
40// a = -0.00259118
41// b = 0.003173474
42// c = -0.0002491554
43//
44const float kAirAbsorptionCoefficients[]{0.0006f, 0.0006f, 0.0007f,
45 0.0008f, 0.0010f, 0.0015f,
46 0.0026f, 0.0060f, 0.0207f};
47
48// Room materials with the corresponding absorption coefficients.
49const RoomMaterial kRoomMaterials[static_cast<size_t>(
50 MaterialName::kNumMaterialNames)] = {
51 {.name: MaterialName::kTransparent,
52 // 31.25 62.5 125 250 500 1000 2000 4000 8000 Hz.
53 .absorption_coefficients: {1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f}},
54 {.name: MaterialName::kAcousticCeilingTiles,
55 .absorption_coefficients: {0.672f, 0.675f, 0.700f, 0.660f, 0.720f, 0.920f, 0.880f, 0.750f, 1.000f}},
56 {.name: MaterialName::kBrickBare,
57 .absorption_coefficients: {0.030f, 0.030f, 0.030f, 0.030f, 0.030f, 0.040f, 0.050f, 0.070f, 0.140f}},
58
59 {.name: MaterialName::kBrickPainted,
60 .absorption_coefficients: {0.006f, 0.007f, 0.010f, 0.010f, 0.020f, 0.020f, 0.020f, 0.030f, 0.060f}},
61
62 {.name: MaterialName::kConcreteBlockCoarse,
63 .absorption_coefficients: {0.360f, 0.360f, 0.360f, 0.440f, 0.310f, 0.290f, 0.390f, 0.250f, 0.500f}},
64
65 {.name: MaterialName::kConcreteBlockPainted,
66 .absorption_coefficients: {0.092f, 0.090f, 0.100f, 0.050f, 0.060f, 0.070f, 0.090f, 0.080f, 0.160f}},
67
68 {.name: MaterialName::kCurtainHeavy,
69 .absorption_coefficients: {0.073f, 0.106f, 0.140f, 0.350f, 0.550f, 0.720f, 0.700f, 0.650f, 1.000f}},
70
71 {.name: MaterialName::kFiberGlassInsulation,
72 .absorption_coefficients: {0.193f, 0.220f, 0.220f, 0.820f, 0.990f, 0.990f, 0.990f, 0.990f, 1.000f}},
73
74 {.name: MaterialName::kGlassThin,
75 .absorption_coefficients: {0.180f, 0.169f, 0.180f, 0.060f, 0.040f, 0.030f, 0.020f, 0.020f, 0.040f}},
76
77 {.name: MaterialName::kGlassThick,
78 .absorption_coefficients: {0.350f, 0.350f, 0.350f, 0.250f, 0.180f, 0.120f, 0.070f, 0.040f, 0.080f}},
79
80 {.name: MaterialName::kGrass,
81 .absorption_coefficients: {0.05f, 0.05f, 0.15f, 0.25f, 0.40f, 0.55f, 0.60f, 0.60f, 0.60f}},
82
83 {.name: MaterialName::kLinoleumOnConcrete,
84 .absorption_coefficients: {0.020f, 0.020f, 0.020f, 0.030f, 0.030f, 0.030f, 0.030f, 0.020f, 0.040f}},
85
86 {.name: MaterialName::kMarble,
87 .absorption_coefficients: {0.010f, 0.010f, 0.010f, 0.010f, 0.010f, 0.010f, 0.020f, 0.020f, 0.040f}},
88
89 {.name: MaterialName::kMetal,
90 .absorption_coefficients: {0.030f, 0.035f, 0.04f, 0.04f, 0.05f, 0.05f, 0.05f, 0.07f, 0.09f}},
91
92 {.name: MaterialName::kParquetOnConcrete,
93 .absorption_coefficients: {0.028f, 0.030f, 0.040f, 0.040f, 0.070f, 0.060f, 0.060f, 0.070f, 0.140f}},
94
95 {.name: MaterialName::kPlasterRough,
96 .absorption_coefficients: {0.017f, 0.018f, 0.020f, 0.030f, 0.040f, 0.050f, 0.040f, 0.030f, 0.060f}},
97
98 {.name: MaterialName::kPlasterSmooth,
99 .absorption_coefficients: {0.011f, 0.012f, 0.013f, 0.015f, 0.020f, 0.030f, 0.040f, 0.050f, 0.100f}},
100
101 {.name: MaterialName::kPlywoodPanel,
102 .absorption_coefficients: {0.40f, 0.34f, 0.28f, 0.22f, 0.17f, 0.09f, 0.10f, 0.11f, 0.22f}},
103
104 {.name: MaterialName::kPolishedConcreteOrTile,
105 .absorption_coefficients: {0.008f, 0.008f, 0.010f, 0.010f, 0.015f, 0.020f, 0.020f, 0.020f, 0.040f}},
106
107 {.name: MaterialName::kSheetrock,
108 .absorption_coefficients: {0.290f, 0.279f, 0.290f, 0.100f, 0.050f, 0.040f, 0.070f, 0.090f, 0.180f}},
109
110 {.name: MaterialName::kWaterOrIceSurface,
111 .absorption_coefficients: {0.006f, 0.006f, 0.008f, 0.008f, 0.013f, 0.015f, 0.020f, 0.025f, 0.050f}},
112
113 {.name: MaterialName::kWoodCeiling,
114 .absorption_coefficients: {0.150f, 0.147f, 0.150f, 0.110f, 0.100f, 0.070f, 0.060f, 0.070f, 0.140f}},
115
116 {.name: MaterialName::kWoodPanel,
117 .absorption_coefficients: {0.280f, 0.280f, 0.280f, 0.220f, 0.170f, 0.090f, 0.100f, 0.110f, 0.220f}},
118
119 {.name: MaterialName::kUniform,
120 .absorption_coefficients: {0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f, 0.5f}}};
121
122// Frequency threshold for low pass filtering. This is the -3dB frequency.
123const float kCutoffFrequency = 800.0f;
124
125// Number of averaging bands for computing the average absorption coefficient.
126const int kNumAveragingBands = 3;
127
128// Average absorption coefficients are computed based on 3 octave bands (500Hz,
129// 1kHz, 2kHz) from the user specified materials. The 500Hz band has index 4.
130const int kStartingBand = 4;
131
132// Default scaling factor of the reverberation tail. This value is
133// multiplied by the user-defined factor in order to allow for the change of
134// gain in the reverb tail from the UI. Updates to the gain value are made to
135// match the previous reverb implementation's loudness.
136const float kDefaultReverbGain = 0.045f;
137
138// Constant used in Eyring's equation to compute RT60.
139const float kEyringConstant = 0.161f;
140
141// Computes RT60 time in the given frequency band according to Eyring.
142inline float ComputeRt60Eyring(float total_area, float mean_absorption,
143 size_t band, float volume) {
144 return kEyringConstant * volume /
145 (-total_area * std::log(x: 1.0f - mean_absorption) +
146 4.0f * kAirAbsorptionCoefficients[band] * volume);
147}
148
149inline std::vector<float> ComputeShoeBoxSurfaceAreas(
150 const RoomProperties& room_properties) {
151 const float room_width = room_properties.dimensions[0];
152 const float room_height = room_properties.dimensions[1];
153 const float room_depth = room_properties.dimensions[2];
154 const float left_right_area = room_height * room_depth;
155 const float top_bottom_area = room_width * room_depth;
156 const float front_back_area = room_width * room_height;
157 return std::vector<float>{left_right_area, left_right_area, top_bottom_area,
158 top_bottom_area, front_back_area, front_back_area};
159}
160
161// Generates average reflection coefficients for each room model surface.
162//
163// @param room_properties Struct containing properties of the shoe-box room
164// model.
165// @param coefficients Reflection coefficients for each surface.
166void GenerateReflectionCoefficients(const RoomProperties& room_properties,
167 float* coefficients) {
168 DCHECK(coefficients);
169 // Loop through all the surfaces and compute the average absorption
170 // coefficient for 3 bands (500Hz, 1kHz and 2kHz).
171 for (size_t surface = 0; surface < kNumRoomSurfaces; ++surface) {
172 const size_t material_index =
173 static_cast<size_t>(room_properties.material_names[surface]);
174 // Absorption coefficients in all bands for the current surface.
175 const auto& absorption_coefficients =
176 kRoomMaterials[material_index].absorption_coefficients;
177 // Compute average absorption coefficients for each surface.
178 float average_absorption_coefficient =
179 std::accumulate(first: std::begin(arr: absorption_coefficients) + kStartingBand,
180 last: std::begin(arr: absorption_coefficients) + kStartingBand +
181 kNumAveragingBands,
182 init: 0.0f) /
183 static_cast<float>(kNumAveragingBands);
184 // Compute a reflection coefficient for each surface.
185 coefficients[surface] =
186 std::min(a: 1.0f, b: std::sqrt(x: 1.0f - average_absorption_coefficient));
187 }
188}
189
190// Uses the Eyring's equation to estimate RT60 values (reverb time in seconds)
191// in |kNumReverbOctaveBands| octave bands, including the correction for air
192// absorption. The equation is applied as defined in:
193// https://arauacustica.com/files/publicaciones_relacionados/pdf_esp_26.pdf
194//
195
196void GenerateRt60Values(const RoomProperties& room_properties,
197 float* rt60_values) {
198 DCHECK(rt60_values);
199 // Compute the shoe-box room volume.
200 const float room_volume = room_properties.dimensions[0] *
201 room_properties.dimensions[1] *
202 room_properties.dimensions[2];
203 if (room_volume < std::numeric_limits<float>::epsilon()) {
204 // RT60 values will be all zeros, if the room volume is zero.
205 return;
206 }
207
208 // Compute surface areas of the shoe-box room.
209 const std::vector<float> all_surface_areas =
210 ComputeShoeBoxSurfaceAreas(room_properties);
211 const float total_area =
212 std::accumulate(first: all_surface_areas.begin(), last: all_surface_areas.end(), init: 0.0f);
213 DCHECK_GT(total_area, 0.0f);
214 // Loop through each band and compute the RT60 values.
215 for (size_t band = 0; band < kNumReverbOctaveBands; ++band) {
216 // Initialize the effective absorbing area.
217 float absorbing_area = 0.0f;
218 for (size_t surface = 0; surface < kNumRoomSurfaces; ++surface) {
219 const size_t material_index =
220 static_cast<size_t>(room_properties.material_names[surface]);
221 // Compute the effective absorbing area based on the absorption
222 // coefficients for the current band and all the surfaces.
223 absorbing_area +=
224 kRoomMaterials[material_index].absorption_coefficients[band] *
225 all_surface_areas[surface];
226 }
227 DCHECK_GT(absorbing_area, 0.0f);
228 const float mean_absorption = std::min(a: absorbing_area / total_area, b: 1.0f);
229
230 // Compute RT60 time in this band according to Eyring.
231 rt60_values[band] =
232 ComputeRt60Eyring(total_area, mean_absorption, band, volume: room_volume);
233 }
234}
235
236// Modifies the RT60 values by the given |brightness_modifier| and
237// |time_scaler|.
238void ModifyRT60Values(float brightness_modifier, float time_scaler,
239 float* rt60_values) {
240 DCHECK(rt60_values);
241 for (size_t band = 0; band < kNumReverbOctaveBands; ++band) {
242 // Linearly scale calculated reverb times according to the user specified
243 // |brightness_modifier| and |time_scaler| values.
244 rt60_values[band] *=
245 (1.0f + brightness_modifier * static_cast<float>(band + 1) /
246 static_cast<float>(kNumReverbOctaveBands)) *
247 time_scaler;
248 }
249}
250
251} // namespace
252
253ReflectionProperties ComputeReflectionProperties(
254 const RoomProperties& room_properties) {
255 ReflectionProperties reflection_properties;
256 std::copy(first: std::begin(arr: room_properties.position),
257 last: std::end(arr: room_properties.position),
258 result: std::begin(arr&: reflection_properties.room_position));
259 std::copy(first: std::begin(arr: room_properties.rotation),
260 last: std::end(arr: room_properties.rotation),
261 result: std::begin(arr&: reflection_properties.room_rotation));
262 std::copy(first: std::begin(arr: room_properties.dimensions),
263 last: std::end(arr: room_properties.dimensions),
264 result: std::begin(arr&: reflection_properties.room_dimensions));
265 reflection_properties.cutoff_frequency = kCutoffFrequency;
266 GenerateReflectionCoefficients(room_properties,
267 coefficients: reflection_properties.coefficients);
268 reflection_properties.gain = room_properties.reflection_scalar;
269 return reflection_properties;
270}
271
272ReverbProperties ComputeReverbProperties(
273 const RoomProperties& room_properties) {
274 ReverbProperties reverb_properties;
275 GenerateRt60Values(room_properties, rt60_values: reverb_properties.rt60_values);
276 ModifyRT60Values(brightness_modifier: room_properties.reverb_brightness,
277 time_scaler: room_properties.reverb_time, rt60_values: reverb_properties.rt60_values);
278 reverb_properties.gain = kDefaultReverbGain * room_properties.reverb_gain;
279 return reverb_properties;
280}
281
282ReverbProperties ComputeReverbPropertiesFromRT60s(const float* rt60_values,
283 float brightness_modifier,
284 float time_scalar,
285 float gain_multiplier) {
286 DCHECK(rt60_values);
287
288 ReverbProperties reverb_properties;
289 std::copy_n(first: rt60_values, n: kNumReverbOctaveBands,
290 result: reverb_properties.rt60_values);
291 ModifyRT60Values(brightness_modifier, time_scaler: time_scalar,
292 rt60_values: reverb_properties.rt60_values);
293 reverb_properties.gain = kDefaultReverbGain * gain_multiplier;
294 return reverb_properties;
295}
296
297float ComputeRoomEffectsGain(const WorldPosition& source_position,
298 const WorldPosition& room_position,
299 const WorldRotation& room_rotation,
300 const WorldPosition& room_dimensions) {
301 const float room_volume =
302 room_dimensions[0] * room_dimensions[1] * room_dimensions[2];
303 if (room_volume < std::numeric_limits<float>::epsilon()) {
304 // No room effects should be present when the room volume is zero.
305 return 0.0f;
306 }
307
308 // Compute the relative source position with respect to the room.
309 WorldPosition relative_source_position;
310 GetRelativeDirection(from_position: room_position, from_rotation: room_rotation, to_position: source_position,
311 relative_direction: &relative_source_position);
312 WorldPosition closest_position_in_room;
313 GetClosestPositionInAabb(relative_position: relative_source_position, aabb_dimensions: room_dimensions,
314 closest_position: &closest_position_in_room);
315 // Shift the attenuation curve by 1.0f to avoid zero division.
316 const float distance_to_room =
317 1.0f + (relative_source_position - closest_position_in_room).norm();
318 return 1.0f / (distance_to_room * distance_to_room);
319}
320
321RoomMaterial GetRoomMaterial(size_t material_index) {
322 DCHECK_LT(material_index,
323 static_cast<size_t>(MaterialName::kNumMaterialNames));
324 return kRoomMaterials[material_index];
325}
326
327} // namespace vraudio
328

source code of qtmultimedia/src/3rdparty/resonance-audio/platforms/common/room_effects_utils.cc