Revision control

++ // 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()
++ {
++ 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 nameFilters{tr("Audio Files (*.mp3 *.aac *.wma *.flac *.ogg *.wav)")};
++ fileDialog->setNameFilters(nameFilters);
++ }
++
++ 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();
++ }