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 | |
13 | QT_BEGIN_NAMESPACE |
14 | |
15 | /*! |
16 | \class QSpatialSound |
17 | \inmodule QtSpatialAudio |
18 | \ingroup spatialaudio |
19 | \ingroup multimedia_audio |
20 | |
21 | \brief A sound object in 3D space. |
22 | |
23 | QSpatialSound represents an audible object in 3D space. You can define |
24 | its position and orientation in space, set the sound it is playing and define a |
25 | volume for the object. |
26 | |
27 | The object can have different attenuation behavior, emit sound mainly in one direction |
28 | or spherically, and behave as if occluded by some other object. |
29 | */ |
30 | |
31 | /*! |
32 | Creates a spatial sound source for \a engine. The object can be placed in |
33 | 3D space and will be louder the closer to the listener it is. |
34 | */ |
35 | QSpatialSound::QSpatialSound(QAudioEngine *engine) |
36 | : d(new QSpatialSoundPrivate(this)) |
37 | { |
38 | setEngine(engine); |
39 | } |
40 | |
41 | /*! |
42 | Destroys the sound source. |
43 | */ |
44 | QSpatialSound::~QSpatialSound() |
45 | { |
46 | setEngine(nullptr); |
47 | } |
48 | |
49 | /*! |
50 | \property QSpatialSound::position |
51 | |
52 | Defines the position of the sound source in 3D space. Units are in centimeters |
53 | by default. |
54 | |
55 | \sa QAudioEngine::distanceScale |
56 | */ |
57 | void QSpatialSound::setPosition(QVector3D pos) |
58 | { |
59 | auto *ep = QAudioEnginePrivate::get(engine: d->engine); |
60 | pos *= ep->distanceScale; |
61 | d->pos = pos; |
62 | if (ep) |
63 | ep->resonanceAudio->api->SetSourcePosition(source_id: d->sourceId, x: pos.x(), y: pos.y(), z: pos.z()); |
64 | emit positionChanged(); |
65 | } |
66 | |
67 | QVector3D QSpatialSound::position() const |
68 | { |
69 | auto *ep = QAudioEnginePrivate::get(engine: d->engine); |
70 | return d->pos/ep->distanceScale; |
71 | } |
72 | |
73 | /*! |
74 | \property QSpatialSound::rotation |
75 | |
76 | Defines the orientation of the sound source in 3D space. |
77 | */ |
78 | void QSpatialSound::setRotation(const QQuaternion &q) |
79 | { |
80 | d->rotation = q; |
81 | auto *ep = QAudioEnginePrivate::get(engine: d->engine); |
82 | if (ep) |
83 | ep->resonanceAudio->api->SetSourceRotation(source_id: d->sourceId, x: q.x(), y: q.y(), z: q.z(), w: q.scalar()); |
84 | emit rotationChanged(); |
85 | } |
86 | |
87 | QQuaternion QSpatialSound::rotation() const |
88 | { |
89 | return d->rotation; |
90 | } |
91 | |
92 | /*! |
93 | \property QSpatialSound::volume |
94 | |
95 | Defines the volume of the sound. |
96 | |
97 | Values between 0 and 1 will attenuate the sound, while values above 1 |
98 | provide an additional gain boost. |
99 | */ |
100 | void QSpatialSound::setVolume(float volume) |
101 | { |
102 | if (d->volume == volume) |
103 | return; |
104 | d->volume = volume; |
105 | auto *ep = QAudioEnginePrivate::get(engine: d->engine); |
106 | if (ep) |
107 | ep->resonanceAudio->api->SetSourceVolume(source_id: d->sourceId, volume: d->volume*d->wallDampening); |
108 | emit volumeChanged(); |
109 | } |
110 | |
111 | float QSpatialSound::volume() const |
112 | { |
113 | return d->volume; |
114 | } |
115 | |
116 | /*! |
117 | \enum QSpatialSound::DistanceModel |
118 | |
119 | Defines how the volume of the sound scales with distance to the listener. |
120 | |
121 | \value Logarithmic Volume decreases logarithmically with distance. |
122 | \value Linear Volume decreases linearly with distance. |
123 | \value ManualAttenuation Attenuation is defined manually using the |
124 | \l manualAttenuation property. |
125 | */ |
126 | |
127 | /*! |
128 | \property QSpatialSound::distanceModel |
129 | |
130 | Defines distance model for this sound source. The volume starts scaling down |
131 | from \l size to \l distanceCutoff. The volume is constant for distances smaller |
132 | than size and zero for distances larger than the cutoff distance. |
133 | |
134 | \sa QSpatialSound::DistanceModel |
135 | */ |
136 | void QSpatialSound::setDistanceModel(DistanceModel model) |
137 | { |
138 | if (d->distanceModel == model) |
139 | return; |
140 | d->distanceModel = model; |
141 | |
142 | d->updateDistanceModel(); |
143 | emit distanceModelChanged(); |
144 | } |
145 | |
146 | void QSpatialSoundPrivate::updateDistanceModel() |
147 | { |
148 | if (!engine || sourceId < 0) |
149 | return; |
150 | auto *ep = QAudioEnginePrivate::get(engine); |
151 | |
152 | vraudio::DistanceRolloffModel dm = vraudio::kLogarithmic; |
153 | switch (distanceModel) { |
154 | case QSpatialSound::DistanceModel::Linear: |
155 | dm = vraudio::kLinear; |
156 | break; |
157 | case QSpatialSound::DistanceModel::ManualAttenuation: |
158 | dm = vraudio::kNone; |
159 | break; |
160 | default: |
161 | break; |
162 | } |
163 | |
164 | ep->resonanceAudio->api->SetSourceDistanceModel(source_id: sourceId, rolloff: dm, min_distance: size, max_distance: distanceCutoff); |
165 | } |
166 | |
167 | void QSpatialSoundPrivate::updateRoomEffects() |
168 | { |
169 | if (!engine || sourceId < 0) |
170 | return; |
171 | auto *ep = QAudioEnginePrivate::get(engine); |
172 | if (!ep->currentRoom) |
173 | return; |
174 | auto *rp = QAudioRoomPrivate::get(r: ep->currentRoom); |
175 | if (!rp) |
176 | return; |
177 | |
178 | QVector3D roomDim2 = ep->currentRoom->dimensions()/2.; |
179 | QVector3D roomPos = ep->currentRoom->position(); |
180 | QQuaternion roomRot = ep->currentRoom->rotation(); |
181 | QVector3D dist = pos - roomPos; |
182 | // transform into room coordinates |
183 | dist = roomRot.rotatedVector(vector: dist); |
184 | if (qAbs(t: dist.x()) <= roomDim2.x() && |
185 | qAbs(t: dist.y()) <= roomDim2.y() && |
186 | qAbs(t: dist.z()) <= roomDim2.z()) { |
187 | // Source is inside room, apply |
188 | ep->resonanceAudio->api->SetSourceRoomEffectsGain(source_id: sourceId, room_effects_gain: 1); |
189 | wallDampening = 1.; |
190 | wallOcclusion = 0.; |
191 | } else { |
192 | // ### calculate room occlusion and dampening |
193 | // This is a bit of heuristics on top of the heuristic dampening/occlusion numbers for walls |
194 | // |
195 | // We basically cast a ray from the listener through the walls. If walls have different characteristics |
196 | // and we get close to a corner, we try to use some averaging to avoid abrupt changes |
197 | auto relativeListenerPos = ep->listenerPosition() - roomPos; |
198 | relativeListenerPos = roomRot.rotatedVector(vector: relativeListenerPos); |
199 | |
200 | auto direction = dist.normalized(); |
201 | enum { |
202 | X, Y, Z |
203 | }; |
204 | // Very rough approximation, use the size of the source plus twice the size of our head. |
205 | // One could probably improve upon this. |
206 | const float transitionDistance = size + 0.4; |
207 | QAudioRoom::Wall walls[3]; |
208 | walls[X] = direction.x() > 0 ? QAudioRoom::RightWall : QAudioRoom::LeftWall; |
209 | walls[Y] = direction.y() > 0 ? QAudioRoom::FrontWall : QAudioRoom::BackWall; |
210 | walls[Z] = direction.z() > 0 ? QAudioRoom::Ceiling : QAudioRoom::Floor; |
211 | float factors[3] = { 0., 0., 0. }; |
212 | bool foundWall = false; |
213 | if (direction.x() != 0) { |
214 | float sign = direction.x() > 0 ? 1.f : -1.f; |
215 | float dx = sign * roomDim2.x() - relativeListenerPos.x(); |
216 | QVector3D intersection = relativeListenerPos + direction*dx/direction.x(); |
217 | float dy = roomDim2.y() - qAbs(t: intersection.y()); |
218 | float dz = roomDim2.z() - qAbs(t: intersection.z()); |
219 | if (dy > 0 && dz > 0) { |
220 | // qDebug() << "Hit with wall X" << walls[0] << dy << dz; |
221 | // Ray is hitting this wall |
222 | factors[Y] = qMax(a: 0.f, b: 1.f/3.f - dy/transitionDistance); |
223 | factors[Z] = qMax(a: 0.f, b: 1.f/3.f - dz/transitionDistance); |
224 | factors[X] = 1.f - factors[Y] - factors[Z]; |
225 | foundWall = true; |
226 | } |
227 | } |
228 | if (!foundWall && direction.y() != 0) { |
229 | float sign = direction.y() > 0 ? 1.f : -1.f; |
230 | float dy = sign * roomDim2.y() - relativeListenerPos.y(); |
231 | QVector3D intersection = relativeListenerPos + direction*dy/direction.y(); |
232 | float dx = roomDim2.x() - qAbs(t: intersection.x()); |
233 | float dz = roomDim2.z() - qAbs(t: intersection.z()); |
234 | if (dx > 0 && dz > 0) { |
235 | // Ray is hitting this wall |
236 | // qDebug() << "Hit with wall Y" << walls[1] << dx << dy; |
237 | factors[X] = qMax(a: 0.f, b: 1.f/3.f - dx/transitionDistance); |
238 | factors[Z] = qMax(a: 0.f, b: 1.f/3.f - dz/transitionDistance); |
239 | factors[Y] = 1.f - factors[X] - factors[Z]; |
240 | foundWall = true; |
241 | } |
242 | } |
243 | if (!foundWall) { |
244 | Q_ASSERT(direction.z() != 0); |
245 | float sign = direction.z() > 0 ? 1.f : -1.f; |
246 | float dz = sign * roomDim2.z() - relativeListenerPos.z(); |
247 | QVector3D intersection = relativeListenerPos + direction*dz/direction.z(); |
248 | float dx = roomDim2.x() - qAbs(t: intersection.x()); |
249 | float dy = roomDim2.y() - qAbs(t: intersection.y()); |
250 | if (dx > 0 && dy > 0) { |
251 | // Ray is hitting this wall |
252 | // qDebug() << "Hit with wall Z" << walls[2]; |
253 | factors[X] = qMax(a: 0.f, b: 1.f/3.f - dx/transitionDistance); |
254 | factors[Y] = qMax(a: 0.f, b: 1.f/3.f - dy/transitionDistance); |
255 | factors[Z] = 1.f - factors[X] - factors[Y]; |
256 | foundWall = true; |
257 | } |
258 | } |
259 | wallDampening = 0; |
260 | wallOcclusion = 0; |
261 | for (int i = 0; i < 3; ++i) { |
262 | wallDampening += factors[i]*rp->wallDampening(wall: walls[i]); |
263 | wallOcclusion += factors[i]*rp->wallOcclusion(wall: walls[i]); |
264 | } |
265 | |
266 | // qDebug() << "intersection with wall" << walls[0] << walls[1] << walls[2] << factors[0] << factors[1] << factors[2] << wallDampening << wallOcclusion; |
267 | ep->resonanceAudio->api->SetSourceRoomEffectsGain(source_id: sourceId, room_effects_gain: 0); |
268 | } |
269 | ep->resonanceAudio->api->SetSoundObjectOcclusionIntensity(sound_object_source_id: sourceId, intensity: occlusionIntensity + wallOcclusion); |
270 | ep->resonanceAudio->api->SetSourceVolume(source_id: sourceId, volume: volume*wallDampening); |
271 | } |
272 | |
273 | QSpatialSound::DistanceModel QSpatialSound::distanceModel() const |
274 | { |
275 | return d->distanceModel; |
276 | } |
277 | |
278 | /*! |
279 | \property QSpatialSound::size |
280 | |
281 | Defines the size of the sound source. If the listener is closer to the sound |
282 | object than the size, volume will stay constant. The size is also used to for |
283 | occlusion calculations, where large sources can be partially occluded by a wall. |
284 | */ |
285 | void QSpatialSound::setSize(float size) |
286 | { |
287 | auto *ep = QAudioEnginePrivate::get(engine: d->engine); |
288 | size *= ep->distanceScale; |
289 | if (d->size == size) |
290 | return; |
291 | d->size = size; |
292 | |
293 | d->updateDistanceModel(); |
294 | emit sizeChanged(); |
295 | } |
296 | |
297 | float QSpatialSound::size() const |
298 | { |
299 | auto *ep = QAudioEnginePrivate::get(engine: d->engine); |
300 | return d->size/ep->distanceScale; |
301 | } |
302 | |
303 | /*! |
304 | \property QSpatialSound::distanceCutoff |
305 | |
306 | Defines a distance beyond which sound coming from the source will cutoff. |
307 | If the listener is further away from the sound object than the cutoff |
308 | distance it won't be audible anymore. |
309 | */ |
310 | void QSpatialSound::setDistanceCutoff(float cutoff) |
311 | { |
312 | auto *ep = QAudioEnginePrivate::get(engine: d->engine); |
313 | cutoff *= ep->distanceScale; |
314 | if (d->distanceCutoff == cutoff) |
315 | return; |
316 | d->distanceCutoff = cutoff; |
317 | |
318 | d->updateDistanceModel(); |
319 | emit distanceCutoffChanged(); |
320 | } |
321 | |
322 | float QSpatialSound::distanceCutoff() const |
323 | { |
324 | auto *ep = QAudioEnginePrivate::get(engine: d->engine); |
325 | return d->distanceCutoff/ep->distanceScale; |
326 | } |
327 | |
328 | /*! |
329 | \property QSpatialSound::manualAttenuation |
330 | |
331 | Defines a manual attenuation factor if \l distanceModel is set to |
332 | QSpatialSound::DistanceModel::ManualAttenuation. |
333 | */ |
334 | void QSpatialSound::setManualAttenuation(float attenuation) |
335 | { |
336 | if (d->manualAttenuation == attenuation) |
337 | return; |
338 | d->manualAttenuation = attenuation; |
339 | auto *ep = QAudioEnginePrivate::get(engine: d->engine); |
340 | if (ep) |
341 | ep->resonanceAudio->api->SetSourceDistanceAttenuation(source_id: d->sourceId, distance_attenuation: d->manualAttenuation); |
342 | emit manualAttenuationChanged(); |
343 | } |
344 | |
345 | float QSpatialSound::manualAttenuation() const |
346 | { |
347 | return d->manualAttenuation; |
348 | } |
349 | |
350 | /*! |
351 | \property QSpatialSound::occlusionIntensity |
352 | |
353 | Defines how much the object is occluded. 0 implies the object is |
354 | not occluded at all, 1 implies the sound source is fully occluded by |
355 | another object. |
356 | |
357 | A fully occluded object will still be audible, but especially higher |
358 | frequencies will be dampened. In addition, the object will still |
359 | participate in generating reverb and reflections in the room. |
360 | |
361 | Values larger than 1 are possible to further dampen the direct |
362 | sound coming from the source. |
363 | |
364 | The default is 0. |
365 | */ |
366 | void QSpatialSound::setOcclusionIntensity(float occlusion) |
367 | { |
368 | if (d->occlusionIntensity == occlusion) |
369 | return; |
370 | d->occlusionIntensity = occlusion; |
371 | auto *ep = QAudioEnginePrivate::get(engine: d->engine); |
372 | if (ep) |
373 | ep->resonanceAudio->api->SetSoundObjectOcclusionIntensity(sound_object_source_id: d->sourceId, intensity: d->occlusionIntensity + d->wallOcclusion); |
374 | emit occlusionIntensityChanged(); |
375 | } |
376 | |
377 | float QSpatialSound::occlusionIntensity() const |
378 | { |
379 | return d->occlusionIntensity; |
380 | } |
381 | |
382 | /*! |
383 | \property QSpatialSound::directivity |
384 | |
385 | Defines the directivity of the sound source. A value of 0 implies that the sound is |
386 | emitted equally in all directions, while a value of 1 implies that the source mainly |
387 | emits sound in the forward direction. |
388 | |
389 | Valid values are between 0 and 1, the default is 0. |
390 | */ |
391 | void QSpatialSound::setDirectivity(float alpha) |
392 | { |
393 | alpha = qBound(min: 0., val: alpha, max: 1.); |
394 | if (alpha == d->directivity) |
395 | return; |
396 | d->directivity = alpha; |
397 | |
398 | auto *ep = QAudioEnginePrivate::get(engine: d->engine); |
399 | if (ep) |
400 | ep->resonanceAudio->api->SetSoundObjectDirectivity(sound_object_source_id: d->sourceId, alpha: d->directivity, order: d->directivityOrder); |
401 | |
402 | emit directivityChanged(); |
403 | } |
404 | |
405 | float QSpatialSound::directivity() const |
406 | { |
407 | return d->directivity; |
408 | } |
409 | |
410 | /*! |
411 | \property QSpatialSound::directivityOrder |
412 | |
413 | Defines the order of the directivity of the sound source. A higher order |
414 | implies a sharper localization of the sound cone. |
415 | |
416 | The minimum value and default for this property is 1. |
417 | */ |
418 | void QSpatialSound::setDirectivityOrder(float order) |
419 | { |
420 | order = qMax(a: order, b: 1.); |
421 | if (order == d->directivityOrder) |
422 | return; |
423 | d->directivityOrder = order; |
424 | |
425 | auto *ep = QAudioEnginePrivate::get(engine: d->engine); |
426 | if (ep) |
427 | ep->resonanceAudio->api->SetSoundObjectDirectivity(sound_object_source_id: d->sourceId, alpha: d->directivity, order: d->directivityOrder); |
428 | |
429 | emit directivityChanged(); |
430 | } |
431 | |
432 | float QSpatialSound::directivityOrder() const |
433 | { |
434 | return d->directivityOrder; |
435 | } |
436 | |
437 | /*! |
438 | \property QSpatialSound::nearFieldGain |
439 | |
440 | Defines the near field gain for the sound source. Valid values are between 0 and 1. |
441 | A near field gain of 1 will raise the volume of the sound signal by approx 20 dB for |
442 | distances very close to the listener. |
443 | */ |
444 | void QSpatialSound::setNearFieldGain(float gain) |
445 | { |
446 | gain = qBound(min: 0., val: gain, max: 1.); |
447 | if (gain == d->nearFieldGain) |
448 | return; |
449 | d->nearFieldGain = gain; |
450 | |
451 | auto *ep = QAudioEnginePrivate::get(engine: d->engine); |
452 | if (ep) |
453 | ep->resonanceAudio->api->SetSoundObjectNearFieldEffectGain(sound_object_source_id: d->sourceId, gain: d->nearFieldGain*9.f); |
454 | |
455 | emit nearFieldGainChanged(); |
456 | |
457 | } |
458 | |
459 | float QSpatialSound::nearFieldGain() const |
460 | { |
461 | return d->nearFieldGain; |
462 | } |
463 | |
464 | /*! |
465 | \property QSpatialSound::source |
466 | |
467 | The source file for the sound to be played. |
468 | */ |
469 | void QSpatialSound::setSource(const QUrl &url) |
470 | { |
471 | if (d->url == url) |
472 | return; |
473 | d->url = url; |
474 | |
475 | d->load(); |
476 | emit sourceChanged(); |
477 | } |
478 | |
479 | QUrl QSpatialSound::source() const |
480 | { |
481 | return d->url; |
482 | } |
483 | |
484 | /*! |
485 | \enum QSpatialSound::Loops |
486 | |
487 | Lets you control the sound playback loop using the following values: |
488 | |
489 | \value Infinite Playback infinitely |
490 | \value Once Playback once |
491 | */ |
492 | /*! |
493 | \property QSpatialSound::loops |
494 | |
495 | Determines how many times the sound is played before the player stops. |
496 | Set to QSpatialSound::Infinite to play the current sound in a loop forever. |
497 | |
498 | The default value is \c 1. |
499 | */ |
500 | int QSpatialSound::loops() const |
501 | { |
502 | return d->m_loops.loadRelaxed(); |
503 | } |
504 | |
505 | void QSpatialSound::setLoops(int loops) |
506 | { |
507 | int oldLoops = d->m_loops.fetchAndStoreRelaxed(newValue: loops); |
508 | if (oldLoops != loops) |
509 | emit loopsChanged(); |
510 | } |
511 | |
512 | /*! |
513 | \property QSpatialSound::autoPlay |
514 | |
515 | Determines whether the sound should automatically start playing when a source |
516 | gets specified. |
517 | |
518 | The default value is \c true. |
519 | */ |
520 | bool QSpatialSound::autoPlay() const |
521 | { |
522 | return d->m_autoPlay.loadRelaxed(); |
523 | } |
524 | |
525 | void QSpatialSound::setAutoPlay(bool autoPlay) |
526 | { |
527 | bool old = d->m_autoPlay.fetchAndStoreRelaxed(newValue: autoPlay); |
528 | if (old != autoPlay) |
529 | emit autoPlayChanged(); |
530 | } |
531 | |
532 | /*! |
533 | Starts playing back the sound. Does nothing if the sound is already playing. |
534 | */ |
535 | void QSpatialSound::play() |
536 | { |
537 | d->play(); |
538 | } |
539 | |
540 | /*! |
541 | Pauses sound playback. Calling play() will continue playback. |
542 | */ |
543 | void QSpatialSound::pause() |
544 | { |
545 | d->pause(); |
546 | } |
547 | |
548 | /*! |
549 | Stops sound playback and resets the current position and current loop count to 0. |
550 | Calling play() will start playback at the beginning of the sound file. |
551 | */ |
552 | void QSpatialSound::stop() |
553 | { |
554 | d->stop(); |
555 | } |
556 | |
557 | /*! |
558 | \internal |
559 | */ |
560 | void QSpatialSound::setEngine(QAudioEngine *engine) |
561 | { |
562 | if (d->engine == engine) |
563 | return; |
564 | |
565 | // Remove self from old engine (if necessary) |
566 | auto *ep = QAudioEnginePrivate::get(engine: d->engine); |
567 | if (ep) |
568 | ep->removeSpatialSound(sound: this); |
569 | |
570 | d->engine = engine; |
571 | |
572 | // Add self to new engine if necessary |
573 | ep = QAudioEnginePrivate::get(engine: d->engine); |
574 | if (ep) { |
575 | ep->addSpatialSound(sound: this); |
576 | ep->resonanceAudio->api->SetSourcePosition(source_id: d->sourceId, x: d->pos.x(), y: d->pos.y(), z: d->pos.z()); |
577 | ep->resonanceAudio->api->SetSourceRotation(source_id: d->sourceId, x: d->rotation.x(), y: d->rotation.y(), z: d->rotation.z(), w: d->rotation.scalar()); |
578 | ep->resonanceAudio->api->SetSourceVolume(source_id: d->sourceId, volume: d->volume); |
579 | ep->resonanceAudio->api->SetSoundObjectDirectivity(sound_object_source_id: d->sourceId, alpha: d->directivity, order: d->directivityOrder); |
580 | ep->resonanceAudio->api->SetSoundObjectNearFieldEffectGain(sound_object_source_id: d->sourceId, gain: d->nearFieldGain); |
581 | d->updateDistanceModel(); |
582 | } |
583 | } |
584 | |
585 | /*! |
586 | Returns the engine associated with this listener. |
587 | */ |
588 | QAudioEngine *QSpatialSound::engine() const |
589 | { |
590 | return d->engine; |
591 | } |
592 | |
593 | QT_END_NAMESPACE |
594 | |
595 | #include "moc_qspatialsound.cpp" |
596 |
Definitions
- QSpatialSound
- ~QSpatialSound
- setPosition
- position
- setRotation
- rotation
- setVolume
- volume
- setDistanceModel
- updateDistanceModel
- updateRoomEffects
- distanceModel
- setSize
- size
- setDistanceCutoff
- distanceCutoff
- setManualAttenuation
- manualAttenuation
- setOcclusionIntensity
- occlusionIntensity
- setDirectivity
- directivity
- setDirectivityOrder
- directivityOrder
- setNearFieldGain
- nearFieldGain
- setSource
- source
- loops
- setLoops
- autoPlay
- setAutoPlay
- play
- pause
- stop
- setEngine
Start learning QML with our Intro Training
Find out more