242 lines
7.6 KiB
C++
242 lines
7.6 KiB
C++
// 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();
|
|
}
|