- Qt 6.4
- Qt Spatial Audio
- Spatial Audio Panning Example
- Technology Preview

# Spatial Audio Panning Example

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

     #include <QtSpatialAudio/QtSpatialAudio>

     #include <QtWidgets/QApplication>
     #include <QtWidgets/QCheckBox>
     #include <QtWidgets/QComboBox>
     #include <QtWidgets/QFileDialog>
     #include <QtWidgets/QFormLayout>
     #include <QtWidgets/QHBoxLayout>
     #include <QtWidgets/QLineEdit>
     #include <QtWidgets/QPushButton>
     #include <QtWidgets/QSlider>

     #include <QtCore/QPropertyAnimation>

     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();
     }