Qt 6.x
The Qt SDK
Loading...
Searching...
No Matches
qspatialsound.cpp
Go to the documentation of this file.
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#include "qspatialsound_p.h"
5#include "qaudiolistener.h"
6#include "qaudioengine_p.h"
7#include "resonance_audio.h"
8#include <qaudiosink.h>
9#include <qurl.h>
10#include <qdebug.h>
11#include <qaudiodecoder.h>
12
14
37{
38 setEngine(engine);
39}
40
45{
46 setEngine(nullptr);
47}
48
58{
59 auto *ep = QAudioEnginePrivate::get(d->engine);
60 pos *= ep->distanceScale;
61 d->pos = pos;
62 if (ep)
63 ep->resonanceAudio->api->SetSourcePosition(d->sourceId, pos.x(), pos.y(), pos.z());
66}
67
69{
70 auto *ep = QAudioEnginePrivate::get(d->engine);
71 return d->pos/ep->distanceScale;
72}
73
80{
81 d->rotation = q;
82 auto *ep = QAudioEnginePrivate::get(d->engine);
83 if (ep)
84 ep->resonanceAudio->api->SetSourceRotation(d->sourceId, q.x(), q.y(), q.z(), q.scalar());
86}
87
89{
90 return d->rotation;
91}
92
101void QSpatialSound::setVolume(float volume)
102{
103 if (d->volume == volume)
104 return;
105 d->volume = volume;
106 auto *ep = QAudioEnginePrivate::get(d->engine);
107 if (ep)
108 ep->resonanceAudio->api->SetSourceVolume(d->sourceId, d->volume*d->wallDampening);
110}
111
113{
114 return d->volume;
115}
116
138{
139 if (d->distanceModel == model)
140 return;
141 d->distanceModel = model;
142
145}
146
148{
149 if (!engine || sourceId < 0)
150 return;
152
153 vraudio::DistanceRolloffModel dm = vraudio::kLogarithmic;
154 switch (distanceModel) {
156 dm = vraudio::kLinear;
157 break;
159 dm = vraudio::kNone;
160 break;
161 default:
162 break;
163 }
164
165 ep->resonanceAudio->api->SetSourceDistanceModel(sourceId, dm, size, distanceCutoff);
166}
167
169{
170 if (!engine || sourceId < 0)
171 return;
173 if (!ep->currentRoom)
174 return;
175 auto *rp = QAudioRoomPrivate::get(ep->currentRoom);
176
177 QVector3D roomDim2 = ep->currentRoom->dimensions()/2.;
178 QVector3D roomPos = ep->currentRoom->position();
179 QQuaternion roomRot = ep->currentRoom->rotation();
180 QVector3D dist = pos - roomPos;
181 // transform into room coordinates
182 dist = roomRot.rotatedVector(dist);
183 if (qAbs(dist.x()) <= roomDim2.x() &&
184 qAbs(dist.y()) <= roomDim2.y() &&
185 qAbs(dist.z()) <= roomDim2.z()) {
186 // Source is inside room, apply
187 ep->resonanceAudio->api->SetSourceRoomEffectsGain(sourceId, 1);
188 wallDampening = 1.;
189 wallOcclusion = 0.;
190 } else {
191 // ### calculate room occlusion and dampening
192 // This is a bit of heuristics on top of the heuristic dampening/occlusion numbers for walls
193 //
194 // We basically cast a ray from the listener through the walls. If walls have different characteristics
195 // and we get close to a corner, we try to use some averaging to avoid abrupt changes
196 auto relativeListenerPos = ep->listenerPosition() - roomPos;
197 relativeListenerPos = roomRot.rotatedVector(relativeListenerPos);
198
199 auto direction = dist.normalized();
200 enum {
201 X, Y, Z
202 };
203 // Very rough approximation, use the size of the source plus twice the size of our head.
204 // One could probably improve upon this.
205 const float transitionDistance = size + 0.4;
206 QAudioRoom::Wall walls[3];
209 walls[Z] = direction.z() > 0 ? QAudioRoom::Ceiling : QAudioRoom::Floor;
210 float factors[3] = { 0., 0., 0. };
211 bool foundWall = false;
212 if (direction.x() != 0) {
213 float sign = direction.x() > 0 ? 1.f : -1.f;
214 float dx = sign * roomDim2.x() - relativeListenerPos.x();
215 QVector3D intersection = relativeListenerPos + direction*dx/direction.x();
216 float dy = roomDim2.y() - qAbs(intersection.y());
217 float dz = roomDim2.z() - qAbs(intersection.z());
218 if (dy > 0 && dz > 0) {
219// qDebug() << "Hit with wall X" << walls[0] << dy << dz;
220 // Ray is hitting this wall
221 factors[Y] = qMax(0.f, 1.f/3.f - dy/transitionDistance);
222 factors[Z] = qMax(0.f, 1.f/3.f - dz/transitionDistance);
223 factors[X] = 1.f - factors[Y] - factors[Z];
224 foundWall = true;
225 }
226 }
227 if (!foundWall && direction.y() != 0) {
228 float sign = direction.y() > 0 ? 1.f : -1.f;
229 float dy = sign * roomDim2.y() - relativeListenerPos.y();
230 QVector3D intersection = relativeListenerPos + direction*dy/direction.y();
231 float dx = roomDim2.x() - qAbs(intersection.x());
232 float dz = roomDim2.z() - qAbs(intersection.z());
233 if (dx > 0 && dz > 0) {
234 // Ray is hitting this wall
235// qDebug() << "Hit with wall Y" << walls[1] << dx << dy;
236 factors[X] = qMax(0.f, 1.f/3.f - dx/transitionDistance);
237 factors[Z] = qMax(0.f, 1.f/3.f - dz/transitionDistance);
238 factors[Y] = 1.f - factors[X] - factors[Z];
239 foundWall = true;
240 }
241 }
242 if (!foundWall) {
243 Q_ASSERT(direction.z() != 0);
244 float sign = direction.z() > 0 ? 1.f : -1.f;
245 float dz = sign * roomDim2.z() - relativeListenerPos.z();
246 QVector3D intersection = relativeListenerPos + direction*dz/direction.z();
247 float dx = roomDim2.x() - qAbs(intersection.x());
248 float dy = roomDim2.y() - qAbs(intersection.y());
249 if (dx > 0 && dy > 0) {
250 // Ray is hitting this wall
251// qDebug() << "Hit with wall Z" << walls[2];
252 factors[X] = qMax(0.f, 1.f/3.f - dx/transitionDistance);
253 factors[Y] = qMax(0.f, 1.f/3.f - dy/transitionDistance);
254 factors[Z] = 1.f - factors[X] - factors[Y];
255 foundWall = true;
256 }
257 }
258 wallDampening = 0;
259 wallOcclusion = 0;
260 for (int i = 0; i < 3; ++i) {
261 wallDampening += factors[i]*rp->wallDampening(walls[i]);
262 wallOcclusion += factors[i]*rp->wallOcclusion(walls[i]);
263 }
264
265// qDebug() << "intersection with wall" << walls[0] << walls[1] << walls[2] << factors[0] << factors[1] << factors[2] << wallDampening << wallOcclusion;
266 ep->resonanceAudio->api->SetSourceRoomEffectsGain(sourceId, 0);
267 }
268 ep->resonanceAudio->api->SetSoundObjectOcclusionIntensity(sourceId, occlusionIntensity + wallOcclusion);
269 ep->resonanceAudio->api->SetSourceVolume(sourceId, volume*wallDampening);
270}
271
273{
274 return d->distanceModel;
275}
276
285{
286 auto *ep = QAudioEnginePrivate::get(d->engine);
287 size *= ep->distanceScale;
288 if (d->size == size)
289 return;
290 d->size = size;
291
294}
295
297{
298 auto *ep = QAudioEnginePrivate::get(d->engine);
299 return d->size/ep->distanceScale;
300}
301
310{
311 auto *ep = QAudioEnginePrivate::get(d->engine);
312 cutoff *= ep->distanceScale;
313 if (d->distanceCutoff == cutoff)
314 return;
315 d->distanceCutoff = cutoff;
316
319}
320
322{
323 auto *ep = QAudioEnginePrivate::get(d->engine);
324 return d->distanceCutoff/ep->distanceScale;
325}
326
334{
335 if (d->manualAttenuation == attenuation)
336 return;
337 d->manualAttenuation = attenuation;
338 auto *ep = QAudioEnginePrivate::get(d->engine);
339 if (ep)
340 ep->resonanceAudio->api->SetSourceDistanceAttenuation(d->sourceId, d->manualAttenuation);
342}
343
345{
346 return d->manualAttenuation;
347}
348
366{
367 if (d->occlusionIntensity == occlusion)
368 return;
369 d->occlusionIntensity = occlusion;
370 auto *ep = QAudioEnginePrivate::get(d->engine);
371 if (ep)
372 ep->resonanceAudio->api->SetSoundObjectOcclusionIntensity(d->sourceId, d->occlusionIntensity + d->wallOcclusion);
374}
375
377{
378 return d->occlusionIntensity;
379}
380
391{
392 alpha = qBound(0., alpha, 1.);
393 if (alpha == d->directivity)
394 return;
395 d->directivity = alpha;
396
397 auto *ep = QAudioEnginePrivate::get(d->engine);
398 if (ep)
399 ep->resonanceAudio->api->SetSoundObjectDirectivity(d->sourceId, d->directivity, d->directivityOrder);
400
402}
403
405{
406 return d->directivity;
407}
408
418{
419 order = qMax(order, 1.);
420 if (order == d->directivityOrder)
421 return;
423
424 auto *ep = QAudioEnginePrivate::get(d->engine);
425 if (ep)
426 ep->resonanceAudio->api->SetSoundObjectDirectivity(d->sourceId, d->directivity, d->directivityOrder);
427
429}
430
432{
433 return d->directivityOrder;
434}
435
444{
445 gain = qBound(0., gain, 1.);
446 if (gain == d->nearFieldGain)
447 return;
448 d->nearFieldGain = gain;
449
450 auto *ep = QAudioEnginePrivate::get(d->engine);
451 if (ep)
452 ep->resonanceAudio->api->SetSoundObjectNearFieldEffectGain(d->sourceId, d->nearFieldGain*9.f);
453
455
456}
457
459{
460 return d->nearFieldGain;
461}
462
469{
470 if (d->url == url)
471 return;
472 d->url = url;
473
474 d->load();
476}
477
479{
480 return d->url;
481}
482
500{
501 return d->m_loops.loadRelaxed();
502}
503
505{
506 int oldLoops = d->m_loops.fetchAndStoreRelaxed(loops);
507 if (oldLoops != loops)
509}
510
520{
521 return d->m_autoPlay.loadRelaxed();
522}
523
524void QSpatialSound::setAutoPlay(bool autoPlay)
525{
527 if (old != autoPlay)
529}
530
535{
536 d->play();
537}
538
543{
544 d->pause();
545}
546
552{
553 d->stop();
554}
555
559void QSpatialSound::setEngine(QAudioEngine *engine)
560{
561 if (d->engine == engine)
562 return;
564
565 if (ep)
566 ep->removeSpatialSound(this);
567 d->engine = engine;
568
570 if (ep) {
571 ep->addSpatialSound(this);
572 ep->resonanceAudio->api->SetSourcePosition(d->sourceId, d->pos.x(), d->pos.y(), d->pos.z());
573 ep->resonanceAudio->api->SetSourceRotation(d->sourceId, d->rotation.x(), d->rotation.y(), d->rotation.z(), d->rotation.scalar());
574 ep->resonanceAudio->api->SetSourceVolume(d->sourceId, d->volume);
575 ep->resonanceAudio->api->SetSoundObjectDirectivity(d->sourceId, d->directivity, d->directivityOrder);
576 ep->resonanceAudio->api->SetSoundObjectNearFieldEffectGain(d->sourceId, d->nearFieldGain);
578 }
579}
580
585{
586 return d->engine;
587}
588
590
591#include "moc_qspatialsound.cpp"
QAudioEngine * engine
QAtomicInteger< bool > m_autoPlay
static QAudioEnginePrivate * get(QAudioEngine *engine)
\inmodule QtSpatialAudio
static QAudioRoomPrivate * get(const QAudioRoom *r)
Wall
An enum defining the 6 walls of the room.
Definition qaudioroom.h:57
T fetchAndStoreRelaxed(T newValue) noexcept
T loadRelaxed() const noexcept
The QQuaternion class represents a quaternion consisting of a vector and scalar.
Definition qquaternion.h:21
float z() const
Returns the z coordinate of this quaternion's vector.
float scalar() const
Returns the scalar component of this quaternion.
float x() const
Returns the x coordinate of this quaternion's vector.
float y() const
Returns the y coordinate of this quaternion's vector.
QVector3D rotatedVector(const QVector3D &vector) const
Rotates vector with this quaternion to produce a new vector in 3D space.
QSpatialSound::DistanceModel distanceModel
void stop()
Stops sound playback and resets the current position and current loop count to 0.
void setVolume(float volume)
void setSize(float size)
void manualAttenuationChanged()
void pause()
Pauses sound playback.
void setPosition(QVector3D pos)
void setNearFieldGain(float gain)
void play()
Starts playing back the sound.
void rotationChanged()
void setDistanceModel(DistanceModel model)
float nearFieldGain
Defines the near field gain for the sound source.
void autoPlayChanged()
float manualAttenuation
Defines a manual attenuation factor if \l distanceModel is set to QSpatialSound::DistanceModel::Manua...
void occlusionIntensityChanged()
DistanceModel distanceModel
Defines distance model for this sound source.
void setManualAttenuation(float attenuation)
void setSource(const QUrl &url)
~QSpatialSound()
Destroys the sound source.
float directivity
Defines the directivity of the sound source.
void setLoops(int loops)
void distanceModelChanged()
void setDirectivityOrder(float alpha)
void positionChanged()
void volumeChanged()
QAudioEngine * engine() const
Returns the engine associated with this listener.
QUrl source
The source file for the sound to be played.
float directivityOrder
Defines the order of the directivity of the sound source.
void setDistanceCutoff(float cutoff)
void setAutoPlay(bool autoPlay)
void setDirectivity(float alpha)
void sourceChanged()
bool autoPlay
Determines whether the sound should automatically start playing when a source gets specified.
void setOcclusionIntensity(float occlusion)
QQuaternion rotation
Defines the orientation of the sound source in 3D space.
void distanceCutoffChanged()
float size
Defines the size of the sound source.
int loops
Determines how many times the sound is played before the player stops.
void nearFieldGainChanged()
void setRotation(const QQuaternion &q)
float volume
Defines the volume of the sound.
float occlusionIntensity
Defines how much the object is occluded.
float distanceCutoff
Defines a distance beyond which sound coming from the source will cutoff.
QSpatialSound(QAudioEngine *engine)
Creates a spatial sound source for engine.
void loopsChanged()
QVector3D position
Defines the position of the sound source in 3D space.
DistanceModel
Defines how the volume of the sound scales with distance to the listener.
void sizeChanged()
void directivityChanged()
\inmodule QtCore
Definition qurl.h:94
The QVector3D class represents a vector or vertex in 3D space.
Definition qvectornd.h:171
constexpr float y() const noexcept
Returns the y coordinate of this point.
Definition qvectornd.h:671
constexpr float x() const noexcept
Returns the x coordinate of this point.
Definition qvectornd.h:670
constexpr float z() const noexcept
Returns the z coordinate of this point.
Definition qvectornd.h:672
#define this
Definition dialogs.cpp:9
direction
Combined button and popup list for selecting options.
constexpr const T & qBound(const T &min, const T &val, const T &max)
Definition qminmax.h:44
constexpr const T & qMax(const T &a, const T &b)
Definition qminmax.h:42
constexpr T qAbs(const T &t)
Definition qnumeric.h:328
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLdouble GLdouble GLdouble GLdouble q
Definition qopenglext.h:259
GLfloat GLfloat GLfloat alpha
Definition qopenglext.h:418
GLfixed GLfixed GLint GLint order
#define X(name)
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define emit
static int sign(int x)
QSqlQueryModel * model
[16]
std::uniform_real_distribution dist(1, 2.5)
[2]
QUrl url("example.com")
[constructor-url-reference]
QJSEngine engine
[0]