Spatial Audio Panning Example

 // Copyright (C) 2022 The Qt Company Ltd.
 // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

 #include <QApplication>
 #include <QAudioEngine>
 #include <QAudioListener>
 #include <QAudioRoom>
 #include <QCheckBox>
 #include <QComboBox>
 #include <QCommandLineParser>
 #include <QFileDialog>
 #include <QFormLayout>
 #include <QHBoxLayout>
 #include <QLibraryInfo>
 #include <QLineEdit>
 #include <QPropertyAnimation>
 #include <QPushButton>
 #include <QSlider>
 #include <QSpatialSound>
 #include <QStandardPaths>

 class AudioWidget : public QWidget
 {
 public:
     AudioWidget();
     void setFile(const QString &file);

 private slots:
     void updatePosition();
     void newOcclusion();
     void modeChanged();
     void fileChanged(const QString &file);
     void openFileDialog();
     void updateRoom();
     void animateChanged();

 private:
     QLineEdit *fileEdit = nullptr;
     QPushButton *fileDialogButton = nullptr;
     QSlider *azimuth = nullptr;
     QSlider *elevation = nullptr;
     QSlider *distance = nullptr;
     QSlider *occlusion = nullptr;
     QSlider *roomDimension = nullptr;
     QSlider *reverbGain = nullptr;
     QSlider *reflectionGain = nullptr;
     QComboBox *mode = nullptr;
     QCheckBox *animateButton = nullptr;
     QPropertyAnimation *animation = nullptr;

     QAudioEngine engine;
     QAudioListener *listener = nullptr;
     QSpatialSound *sound = nullptr;
     QAudioRoom *room = nullptr;
     QFileDialog *fileDialog = nullptr;
 };

 AudioWidget::AudioWidget()
     : QWidget()
 {
     setMinimumSize(400, 300);
     auto *form = new QFormLayout(this);

     auto *fileLayout = new QHBoxLayout;
     fileEdit = new QLineEdit;
     fileEdit->setPlaceholderText(tr("Audio File"));
     fileLayout->addWidget(fileEdit);
     fileDialogButton = new QPushButton(tr("Choose..."));
     fileLayout->addWidget(fileDialogButton);
     form->addRow(fileLayout);

     azimuth = new QSlider(Qt::Horizontal);
     azimuth->setRange(-180, 180);
     form->addRow(tr("Azimuth (-180 - 180 degree):"), azimuth);

     elevation = new QSlider(Qt::Horizontal);
     elevation->setRange(-90, 90);
     form->addRow(tr("Elevation (-90 - 90 degree)"), elevation);

     distance = new QSlider(Qt::Horizontal);
     distance->setRange(0, 1000);
     distance->setValue(100);
     form->addRow(tr("Distance (0 - 10 meter):"), distance);

     occlusion = new QSlider(Qt::Horizontal);
     occlusion->setRange(0, 400);
     form->addRow(tr("Occlusion (0 - 4):"), occlusion);

     roomDimension = new QSlider(Qt::Horizontal);
     roomDimension->setRange(0, 10000);
     roomDimension->setValue(1000);
     form->addRow(tr("Room dimension (0 - 100 meter):"), roomDimension);

     reverbGain = new QSlider(Qt::Horizontal);
     reverbGain->setRange(0, 500);
     reverbGain->setValue(0);
     form->addRow(tr("Reverb gain (0-5):"), reverbGain);

     reflectionGain = new QSlider(Qt::Horizontal);
     reflectionGain->setRange(0, 500);
     reflectionGain->setValue(0);
     form->addRow(tr("Reflection gain (0-5):"), reflectionGain);

     mode = new QComboBox;
     mode->addItem(tr("Surround"), QVariant::fromValue(QAudioEngine::Surround));
     mode->addItem(tr("Stereo"), QVariant::fromValue(QAudioEngine::Stereo));
     mode->addItem(tr("Headphone"), QVariant::fromValue(QAudioEngine::Headphone));

     form->addRow(tr("Output mode:"), mode);

     animateButton = new QCheckBox(tr("Animate sound position"));
     form->addRow(animateButton);

     connect(fileEdit, &QLineEdit::textChanged, this, &AudioWidget::fileChanged);
     connect(fileDialogButton, &QPushButton::clicked, this, &AudioWidget::openFileDialog);

     connect(azimuth, &QSlider::valueChanged, this, &AudioWidget::updatePosition);
     connect(elevation, &QSlider::valueChanged, this, &AudioWidget::updatePosition);
     connect(distance, &QSlider::valueChanged, this, &AudioWidget::updatePosition);
     connect(occlusion, &QSlider::valueChanged, this, &AudioWidget::newOcclusion);

     connect(roomDimension, &QSlider::valueChanged, this, &AudioWidget::updateRoom);
     connect(reverbGain, &QSlider::valueChanged, this, &AudioWidget::updateRoom);
     connect(reflectionGain, &QSlider::valueChanged, this, &AudioWidget::updateRoom);

     connect(mode, &QComboBox::currentIndexChanged, this, &AudioWidget::modeChanged);

     room = new QAudioRoom(&engine);
     room->setWallMaterial(QAudioRoom::BackWall, QAudioRoom::BrickBare);
     room->setWallMaterial(QAudioRoom::FrontWall, QAudioRoom::BrickBare);
     room->setWallMaterial(QAudioRoom::LeftWall, QAudioRoom::BrickBare);
     room->setWallMaterial(QAudioRoom::RightWall, QAudioRoom::BrickBare);
     room->setWallMaterial(QAudioRoom::Floor, QAudioRoom::Marble);
     room->setWallMaterial(QAudioRoom::Ceiling, QAudioRoom::WoodCeiling);
     updateRoom();

     listener = new QAudioListener(&engine);
     listener->setPosition({});
     listener->setRotation({});
     engine.start();

     sound = new QSpatialSound(&engine);
     updatePosition();

     animation = new QPropertyAnimation(azimuth, "value");
     animation->setDuration(10000);
     animation->setStartValue(-180);
     animation->setEndValue(180);
     animation->setLoopCount(-1);
     connect(animateButton, &QCheckBox::toggled, this, &AudioWidget::animateChanged);
 }

 void AudioWidget::setFile(const QString &file)
 {
     fileEdit->setText(file);
 }

 void AudioWidget::updatePosition()
 {
     const float az = azimuth->value() / 180. * M_PI;
     const float el = elevation->value() / 180. * M_PI;
     const float d = distance->value();

     const float x = d * sin(az) * cos(el);
     const float y = d * sin(el);
     const float z = -d * cos(az) * cos(el);
     sound->setPosition({x, y, z});
 }

 void AudioWidget::newOcclusion()
 {
     sound->setOcclusionIntensity(occlusion->value() / 100.);
 }

 void AudioWidget::modeChanged()
 {
     engine.setOutputMode(mode->currentData().value<QAudioEngine::OutputMode>());
 }

 void AudioWidget::fileChanged(const QString &file)
 {
     sound->setSource(QUrl::fromLocalFile(file));
     sound->setSize(5);
     sound->setLoops(QSpatialSound::Infinite);
 }

 void AudioWidget::openFileDialog()
 {
     if (fileDialog == nullptr) {
         const QString dir = QStandardPaths::writableLocation(QStandardPaths::MusicLocation);
         fileDialog = new QFileDialog(this, tr("Open Audio File"), dir);
         fileDialog->setAcceptMode(QFileDialog::AcceptOpen);
         const QStringList mimeTypes{"audio/mpeg", "audio/aac", "audio/x-ms-wma",
                                     "audio/x-flac+ogg", "audio/x-wav"};
         fileDialog->setMimeTypeFilters(mimeTypes);
         fileDialog->selectMimeTypeFilter(mimeTypes.constFirst());
     }

     if (fileDialog->exec() == QDialog::Accepted)
         fileEdit->setText(fileDialog->selectedFiles().constFirst());
 }

 void AudioWidget::updateRoom()
 {
     const float d = roomDimension->value();
     room->setDimensions(QVector3D(d, d, 400));
     room->setReflectionGain(float(reflectionGain->value()) / 100);
     room->setReverbGain(float(reverbGain->value()) / 100);
 }

 void AudioWidget::animateChanged()
 {
     if (animateButton->isChecked())
         animation->start();
     else
         animation->stop();
 }

 int main(int argc, char **argv)
 {
     QApplication app(argc, argv);

     QCoreApplication::setApplicationVersion(qVersion());
     QGuiApplication::setApplicationDisplayName(AudioWidget::tr("Spatial Audio test application"));

     QCommandLineParser commandLineParser;
     commandLineParser.addVersionOption();
     commandLineParser.addHelpOption();
     commandLineParser.addPositionalArgument("Audio File",
                                             "Audio File to play");

     commandLineParser.process(app);

     AudioWidget w;
     w.show();

     if (!commandLineParser.positionalArguments().isEmpty())
         w.setFile(commandLineParser.positionalArguments().constFirst());

     return app.exec();
 }