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