1 | // Copyright (C) 2022 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-3.0-only |
3 | #include <qaudioroom_p.h> |
4 | |
5 | QT_BEGIN_NAMESPACE |
6 | |
7 | namespace { |
8 | inline QVector3D toVector(const float *f) |
9 | { |
10 | return QVector3D(f[0], f[1], f[2]); |
11 | } |
12 | |
13 | inline void toFloats(const QVector3D &v, float *f) |
14 | { |
15 | f[0] = v.x(); |
16 | f[1] = v.y(); |
17 | f[2] = v.z(); |
18 | } |
19 | |
20 | inline QQuaternion toQuaternion(const float *f) |
21 | { |
22 | // resonance audio puts the scalar component last |
23 | return QQuaternion(f[3], f[0], f[1], f[2]); |
24 | } |
25 | |
26 | inline void toFloats(const QQuaternion &q, float *f) |
27 | { |
28 | f[0] = q.x(); |
29 | f[1] = q.y(); |
30 | f[2] = q.z(); |
31 | f[3] = q.scalar(); |
32 | } |
33 | |
34 | // Default values for occlusion and dampening of different wall materials. |
35 | // These values are used as defaults if a wall is only defined by a material |
36 | // and define how sound passes through the wall. |
37 | // We define both occlusion and dampening constants to be able to tune the |
38 | // sound. Dampening only reduces the level of the sound without affecting its |
39 | // tone, while occlusion will dampen higher frequencies more than lower ones |
40 | struct { |
41 | float occlusion; |
42 | float dampening; |
43 | } occlusionAndDampening[] = { |
44 | { .occlusion: 0.f, .dampening: 1.f }, // Transparent, |
45 | { .occlusion: 0.f, .dampening: .1f }, // AcousticCeilingTiles, |
46 | { .occlusion: 2.f, .dampening: .4f }, // BrickBare, |
47 | { .occlusion: 2.f, .dampening: .4f }, // BrickPainted, |
48 | { .occlusion: 4.f, .dampening: 1.f }, // ConcreteBlockCoarse, |
49 | { .occlusion: 4.f, .dampening: 1.f }, // ConcreteBlockPainted, |
50 | { .occlusion: .7f, .dampening: .7f }, // CurtainHeavy, |
51 | { .occlusion: .5f, .dampening: .5f }, // FiberGlassInsulation, |
52 | { .occlusion: .2f, .dampening: .3f }, // GlassThin, |
53 | { .occlusion: .5f, .dampening: .2f }, // GlassThick, |
54 | { .occlusion: 7.f, .dampening: 1.f }, // Grass, |
55 | { .occlusion: 4.f, .dampening: 1.f }, // LinoleumOnConcrete, |
56 | { .occlusion: 4.f, .dampening: 1.f }, // Marble, |
57 | { .occlusion: 0.f, .dampening: .2f }, // Metal, |
58 | { .occlusion: 4.f, .dampening: 1.f }, // ParquetOnConcrete, |
59 | { .occlusion: 2.f, .dampening: .4f }, // PlasterRough, |
60 | { .occlusion: 2.f, .dampening: .4f }, // PlasterSmooth, |
61 | { .occlusion: 1.5f, .dampening: .2f }, // PlywoodPanel, |
62 | { .occlusion: 4.f, .dampening: 1.f }, // PolishedConcreteOrTile, |
63 | { .occlusion: 4.f, .dampening: 1.f }, // Sheetrock, |
64 | { .occlusion: 4.f, .dampening: 1.f }, // WaterOrIceSurface, |
65 | { .occlusion: 1.f, .dampening: .3f }, // WoodCeiling, |
66 | { .occlusion: 1.f, .dampening: .3f }, // WoodPanel, |
67 | { .occlusion: 0.f, .dampening: .0f }, // UniformMaterial, |
68 | }; |
69 | |
70 | } |
71 | |
72 | // make sure the wall definitions agree with resonance audio |
73 | |
74 | static_assert(QAudioRoom::LeftWall == 0); |
75 | static_assert(QAudioRoom::RightWall == 1); |
76 | static_assert(QAudioRoom::Floor == 2); |
77 | static_assert(QAudioRoom::Ceiling == 3); |
78 | static_assert(QAudioRoom::FrontWall == 4); |
79 | static_assert(QAudioRoom::BackWall == 5); |
80 | |
81 | float QAudioRoomPrivate::wallOcclusion(QAudioRoom::Wall wall) const |
82 | { |
83 | return m_wallOcclusion[wall] < 0 ? occlusionAndDampening[roomProperties.material_names[wall]].occlusion : m_wallOcclusion[wall]; |
84 | } |
85 | |
86 | float QAudioRoomPrivate::wallDampening(QAudioRoom::Wall wall) const |
87 | { |
88 | return m_wallDampening[wall] < 0 ? occlusionAndDampening[roomProperties.material_names[wall]].dampening : m_wallDampening[wall]; |
89 | } |
90 | |
91 | void QAudioRoomPrivate::update() |
92 | { |
93 | if (!dirty) |
94 | return; |
95 | reflections = vraudio::ComputeReflectionProperties(room_properties: roomProperties); |
96 | reverb = vraudio::ComputeReverbProperties(room_properties: roomProperties); |
97 | dirty = false; |
98 | } |
99 | |
100 | |
101 | /*! |
102 | \class QAudioRoom |
103 | \inmodule QtSpatialAudio |
104 | \ingroup spatialaudio |
105 | \ingroup multimedia_audio |
106 | |
107 | Defines a room for the spatial audio engine. |
108 | |
109 | If the listener is inside a room, first order sound reflections and reverb |
110 | matching the rooms properties will get applied to the sound field. |
111 | |
112 | A room is always square and defined by its center position, its orientation and dimensions. |
113 | Each of the 6 walls of the room can be made of different materials that will contribute |
114 | to the computed reflections and reverb that the listener will experience while being inside |
115 | the room. |
116 | |
117 | If multiple rooms cover the same position, the engine will use the room with the smallest |
118 | volume. |
119 | */ |
120 | |
121 | /*! |
122 | Constructs a QAudioRoom for \a engine. |
123 | */ |
124 | QAudioRoom::QAudioRoom(QAudioEngine *engine) |
125 | : d(new QAudioRoomPrivate) |
126 | { |
127 | Q_ASSERT(engine); |
128 | d->engine = engine; |
129 | auto *ep = QAudioEnginePrivate::get(engine); |
130 | ep->addRoom(room: this); |
131 | } |
132 | |
133 | /*! |
134 | Destroys the room. |
135 | */ |
136 | QAudioRoom::~QAudioRoom() |
137 | { |
138 | auto *ep = QAudioEnginePrivate::get(engine: d->engine); |
139 | if (ep) |
140 | ep->removeRoom(room: this); |
141 | delete d; |
142 | } |
143 | |
144 | /*! |
145 | \enum QAudioRoom::Material |
146 | |
147 | Defines different materials that can be applied to the different walls of the room. |
148 | |
149 | \value Transparent The side of the room is open and won't contribute to reflections or reverb. |
150 | \value AcousticCeilingTiles Acoustic tiles that suppress most reflections and reverb. |
151 | \value BrickBare Bare brick wall. |
152 | \value BrickPainted Painted brick wall. |
153 | \value ConcreteBlockCoarse Raw concrete wall |
154 | \value ConcreteBlockPainted Painted concrete wall |
155 | \value CurtainHeavy Heavy curtain. Will mostly reflect low frequencies |
156 | \value FiberGlassInsulation Fiber glass insulation. Only reflects very low frequencies |
157 | \value GlassThin Thin glass wall |
158 | \value GlassThick Thick glass wall |
159 | \value Grass Grass |
160 | \value LinoleumOnConcrete Linoleum floor |
161 | \value Marble Marble floor |
162 | \value Metal Metal |
163 | \value ParquetOnConcrete Parquet wooden floor on concrete |
164 | \value PlasterRough Rough plaster |
165 | \value PlasterSmooth Smooth plaster |
166 | \value PlywoodPanel Plywodden panel |
167 | \value PolishedConcreteOrTile Polished concrete or tiles |
168 | \value Sheetrock Rock |
169 | \value WaterOrIceSurface Water or ice |
170 | \value WoodCeiling Wooden ceiling |
171 | \value WoodPanel Wooden panel |
172 | \value UniformMaterial Artificial material giving uniform reflections on all frequencies |
173 | */ |
174 | |
175 | /*! |
176 | \enum QAudioRoom::Wall |
177 | |
178 | An enum defining the 6 walls of the room |
179 | |
180 | \value LeftWall Left wall (negative x) |
181 | \value RightWall Right wall (positive x) |
182 | \value Floor Bottom wall (negative y) |
183 | \value Ceiling Top wall (positive y) |
184 | \value FrontWall Front wall (negative z) |
185 | \value BackWall Back wall (positive z) |
186 | */ |
187 | |
188 | |
189 | /*! |
190 | \property QAudioRoom::position |
191 | |
192 | Defines the position of the center of the room in 3D space. Units are in centimeters |
193 | by default. |
194 | |
195 | \sa dimensions, QAudioEngine::distanceScale |
196 | */ |
197 | void QAudioRoom::setPosition(QVector3D pos) |
198 | { |
199 | auto *ep = QAudioEnginePrivate::get(engine: d->engine); |
200 | pos *= ep->distanceScale; |
201 | if (toVector(f: d->roomProperties.position) == pos) |
202 | return; |
203 | toFloats(v: pos, f: d->roomProperties.position); |
204 | d->dirty = true; |
205 | emit positionChanged(); |
206 | } |
207 | |
208 | QVector3D QAudioRoom::position() const |
209 | { |
210 | auto *ep = QAudioEnginePrivate::get(engine: d->engine); |
211 | auto pos = toVector(f: d->roomProperties.position); |
212 | pos /= ep->distanceScale; |
213 | return pos; |
214 | } |
215 | |
216 | /*! |
217 | \property QAudioRoom::dimensions |
218 | |
219 | Defines the dimensions of the room in 3D space. Units are in centimeters |
220 | by default. |
221 | |
222 | \sa position, QAudioEngine::distanceScale |
223 | */ |
224 | void QAudioRoom::setDimensions(QVector3D dim) |
225 | { |
226 | auto *ep = QAudioEnginePrivate::get(engine: d->engine); |
227 | dim *= ep->distanceScale; |
228 | if (toVector(f: d->roomProperties.dimensions) == dim) |
229 | return; |
230 | toFloats(v: dim, f: d->roomProperties.dimensions); |
231 | d->dirty = true; |
232 | emit dimensionsChanged(); |
233 | } |
234 | |
235 | QVector3D QAudioRoom::dimensions() const |
236 | { |
237 | auto *ep = QAudioEnginePrivate::get(engine: d->engine); |
238 | auto dim = toVector(f: d->roomProperties.dimensions); |
239 | dim /= ep->distanceScale; |
240 | return dim; |
241 | } |
242 | |
243 | /*! |
244 | \property QAudioRoom::rotation |
245 | |
246 | Defines the orientation of the room in 3D space. |
247 | */ |
248 | void QAudioRoom::setRotation(const QQuaternion &q) |
249 | { |
250 | if (toQuaternion(f: d->roomProperties.rotation) == q) |
251 | return; |
252 | toFloats(q, f: d->roomProperties.rotation); |
253 | d->dirty = true; |
254 | emit rotationChanged(); |
255 | } |
256 | |
257 | QQuaternion QAudioRoom::rotation() const |
258 | { |
259 | return toQuaternion(f: d->roomProperties.rotation); |
260 | } |
261 | |
262 | /*! |
263 | \fn void QAudioRoom::wallsChanged() |
264 | |
265 | Signals when the wall material changes. |
266 | */ |
267 | /*! |
268 | Sets \a wall to \a material. |
269 | |
270 | Different wall materials have different reflection and reverb properties |
271 | that influence the sound of the room. |
272 | |
273 | \sa wallMaterial(), Material, QAudioRoom::Wall |
274 | */ |
275 | void QAudioRoom::setWallMaterial(Wall wall, Material material) |
276 | { |
277 | static_assert(vraudio::kUniform == int(UniformMaterial)); |
278 | static_assert(vraudio::kTransparent == int(Transparent)); |
279 | |
280 | if (d->roomProperties.material_names[int(wall)] == int(material)) |
281 | return; |
282 | d->roomProperties.material_names[int(wall)] = vraudio::MaterialName(int(material)); |
283 | d->dirty = true; |
284 | emit wallsChanged(); |
285 | } |
286 | |
287 | /*! |
288 | returns the material being used for \a wall. |
289 | |
290 | \sa setWallMaterial(), Material, QAudioRoom::Wall |
291 | */ |
292 | QAudioRoom::Material QAudioRoom::wallMaterial(Wall wall) const |
293 | { |
294 | return Material(d->roomProperties.material_names[int(wall)]); |
295 | } |
296 | |
297 | /*! |
298 | \property QAudioRoom::reflectionGain |
299 | |
300 | A gain factor for reflections generated in this room. A value |
301 | from 0 to 1 will dampen reflections, while a value larger than 1 |
302 | will apply a gain to reflections, making them louder. |
303 | |
304 | The default is 1, a factor of 0 disables reflections. Negative |
305 | values are mapped to 0. |
306 | */ |
307 | void QAudioRoom::setReflectionGain(float factor) |
308 | { |
309 | if (factor < 0.) |
310 | factor = 0.; |
311 | if (d->roomProperties.reflection_scalar == factor) |
312 | return; |
313 | d->roomProperties.reflection_scalar = factor; |
314 | d->dirty = true; |
315 | emit reflectionGainChanged(); |
316 | } |
317 | |
318 | float QAudioRoom::reflectionGain() const |
319 | { |
320 | return d->roomProperties.reflection_scalar; |
321 | } |
322 | |
323 | /*! |
324 | \property QAudioRoom::reverbGain |
325 | |
326 | A gain factor for reverb generated in this room. A value |
327 | from 0 to 1 will dampen reverb, while a value larger than 1 |
328 | will apply a gain to the reverb, making it louder. |
329 | |
330 | The default is 1, a factor of 0 disables reverb. Negative |
331 | values are mapped to 0. |
332 | */ |
333 | void QAudioRoom::setReverbGain(float factor) |
334 | { |
335 | if (factor < 0) |
336 | factor = 0; |
337 | if (d->roomProperties.reverb_gain == factor) |
338 | return; |
339 | d->roomProperties.reverb_gain = factor; |
340 | d->dirty = true; |
341 | emit reverbGainChanged(); |
342 | } |
343 | |
344 | float QAudioRoom::reverbGain() const |
345 | { |
346 | return d->roomProperties.reverb_gain; |
347 | } |
348 | |
349 | /*! |
350 | \property QAudioRoom::reverbTime |
351 | |
352 | A factor to be applies to all reverb timings generated for this room. |
353 | Larger values will lead to longer reverb timings, making the room sound |
354 | larger. |
355 | |
356 | The default is 1. Negative values are mapped to 0. |
357 | */ |
358 | void QAudioRoom::setReverbTime(float factor) |
359 | { |
360 | if (factor < 0) |
361 | factor = 0; |
362 | if (d->roomProperties.reverb_time == factor) |
363 | return; |
364 | d->roomProperties.reverb_time = factor; |
365 | d->dirty = true; |
366 | emit reverbTimeChanged(); |
367 | } |
368 | |
369 | float QAudioRoom::reverbTime() const |
370 | { |
371 | return d->roomProperties.reverb_time; |
372 | } |
373 | |
374 | /*! |
375 | \property QAudioRoom::reverbBrightness |
376 | |
377 | A brightness factor to be applied to the generated reverb. |
378 | A positive value will increase reverb for higher frequencies and |
379 | dampen lower frequencies, a negative value does the reverse. |
380 | |
381 | The default is 0. |
382 | */ |
383 | void QAudioRoom::setReverbBrightness(float factor) |
384 | { |
385 | if (d->roomProperties.reverb_brightness == factor) |
386 | return; |
387 | d->roomProperties.reverb_brightness = factor; |
388 | d->dirty = true; |
389 | emit reverbBrightnessChanged(); |
390 | } |
391 | |
392 | float QAudioRoom::reverbBrightness() const |
393 | { |
394 | return d->roomProperties.reverb_brightness; |
395 | } |
396 | |
397 | QT_END_NAMESPACE |
398 | |
399 | #include "moc_qaudioroom.cpp" |
400 | |