From aaba428fd6cbfdd4da9714885659853f2ff634d7 Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Wed, 29 Jun 2022 09:50:09 +0200 Subject: Add the spatialaudio audiopanning example Change-Id: I7ec164baa57da8f846a47daa703eb62f1f0c8be2 Reviewed-by: Christian Tismer --- .../audiopanning/audiopanning.pyproject | 3 + .../spatialaudio/audiopanning/doc/audiopanning.rst | 15 ++ examples/spatialaudio/audiopanning/main.py | 193 +++++++++++++++++++++ 3 files changed, 211 insertions(+) create mode 100644 examples/spatialaudio/audiopanning/audiopanning.pyproject create mode 100644 examples/spatialaudio/audiopanning/doc/audiopanning.rst create mode 100644 examples/spatialaudio/audiopanning/main.py (limited to 'examples') diff --git a/examples/spatialaudio/audiopanning/audiopanning.pyproject b/examples/spatialaudio/audiopanning/audiopanning.pyproject new file mode 100644 index 000000000..cc7a74a34 --- /dev/null +++ b/examples/spatialaudio/audiopanning/audiopanning.pyproject @@ -0,0 +1,3 @@ +{ + "files": ["main.py"] +} diff --git a/examples/spatialaudio/audiopanning/doc/audiopanning.rst b/examples/spatialaudio/audiopanning/doc/audiopanning.rst new file mode 100644 index 000000000..ea1fe4983 --- /dev/null +++ b/examples/spatialaudio/audiopanning/doc/audiopanning.rst @@ -0,0 +1,15 @@ +Spatial Audio Panning Example +============================= + +The Spatial Audio Example demonstrates how you can use Qt Spatial Audio to +place sound sources in 3D space and how positioning of the sound source and +room properties affect the listening experience. + +The example lets you specify a sound file to be played back at a certain +position in 3D space relative to the listener. Using sliders you can change +the distance and the azimuth and elevation angles to the source. You can also +change dimensions of a virtual room, the intensity of the room reflections +and reverb. + +Various other properties can also be modified such as the dimensions of a virtual +room and the intensity of the room reflections and reverb. diff --git a/examples/spatialaudio/audiopanning/main.py b/examples/spatialaudio/audiopanning/main.py new file mode 100644 index 000000000..55770e3b6 --- /dev/null +++ b/examples/spatialaudio/audiopanning/main.py @@ -0,0 +1,193 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +import math +import sys +from argparse import ArgumentParser, RawTextHelpFormatter + +from PySide6.QtSpatialAudio import (QAudioRoom, QAudioEngine, QAudioListener, + QSpatialSound) +from PySide6.QtWidgets import (QApplication, QCheckBox, QComboBox, QDialog, + QFileDialog, QFormLayout, QHBoxLayout, + QLineEdit, QPushButton, QSlider, QWidget) +from PySide6.QtGui import QGuiApplication, QVector3D, QQuaternion +from PySide6.QtCore import (QCoreApplication, QPropertyAnimation, + QStandardPaths, QUrl, Qt, qVersion, Slot) + + +"""PySide6 port of the spatialaudio/audiopanning example from Qt v6.x""" + + +class AudioWidget(QWidget): + + def __init__(self): + super().__init__() + self._file_dialog = None + self.setMinimumSize(400, 300) + form = QFormLayout(self) + + file_layout = QHBoxLayout() + self._file_edit = QLineEdit() + self._file_edit.setPlaceholderText("Audio File") + file_layout.addWidget(self._file_edit) + self._file_dialog_button = QPushButton("Choose...") + file_layout.addWidget(self._file_dialog_button) + form.addRow(file_layout) + + self._azimuth = QSlider(Qt.Horizontal) + self._azimuth.setRange(-180, 180) + form.addRow("Azimuth (-180 - 180 degree):", self._azimuth) + + self._elevation = QSlider(Qt.Horizontal) + self._elevation.setRange(-90, 90) + form.addRow("Elevation (-90 - 90 degree)", self._elevation) + + self._distance = QSlider(Qt.Horizontal) + self._distance.setRange(0, 1000) + self._distance.setValue(100) + form.addRow("Distance (0 - 10 meter):", self._distance) + + self._occlusion = QSlider(Qt.Horizontal) + self._occlusion.setRange(0, 400) + form.addRow("Occlusion (0 - 4):", self._occlusion) + + self._room_dimension = QSlider(Qt.Horizontal) + self._room_dimension.setRange(0, 10000) + self._room_dimension.setValue(1000) + form.addRow("Room dimension (0 - 100 meter):", self._room_dimension) + + self._reverb_gain = QSlider(Qt.Horizontal) + self._reverb_gain.setRange(0, 500) + self._reverb_gain.setValue(0) + form.addRow("Reverb gain (0-5):", self._reverb_gain) + + self._reflection_gain = QSlider(Qt.Horizontal) + self._reflection_gain.setRange(0, 500) + self._reflection_gain.setValue(0) + form.addRow("Reflection gain (0-5):", self._reflection_gain) + + self._mode = QComboBox() + self._mode.addItem("Surround", QAudioEngine.Surround) + self._mode.addItem("Stereo", QAudioEngine.Stereo) + self._mode.addItem("Headphone", QAudioEngine.Headphone) + + form.addRow("Output mode:", self._mode) + + self._animate_button = QCheckBox("Animate sound position") + form.addRow(self._animate_button) + + self._file_edit.textChanged.connect(self.file_changed) + self._file_dialog_button.clicked.connect(self.open_file_dialog) + + self._azimuth.valueChanged.connect(self.update_position) + self._elevation.valueChanged.connect(self.update_position) + self._distance.valueChanged.connect(self.update_position) + self._occlusion.valueChanged.connect(self.new_occlusion) + + self._room_dimension.valueChanged.connect(self.update_room) + self._reverb_gain.valueChanged.connect(self.update_room) + self._reflection_gain.valueChanged.connect(self.update_room) + + self._mode.currentIndexChanged.connect(self.mode_changed) + + self._engine = QAudioEngine() + self._room = QAudioRoom(self._engine) + self._room.setWallMaterial(QAudioRoom.BackWall, QAudioRoom.BrickBare) + self._room.setWallMaterial(QAudioRoom.FrontWall, QAudioRoom.BrickBare) + self._room.setWallMaterial(QAudioRoom.LeftWall, QAudioRoom.BrickBare) + self._room.setWallMaterial(QAudioRoom.RightWall, QAudioRoom.BrickBare) + self._room.setWallMaterial(QAudioRoom.Floor, QAudioRoom.Marble) + self._room.setWallMaterial(QAudioRoom.Ceiling, QAudioRoom.WoodCeiling) + self.update_room() + + self._listener = QAudioListener(self._engine) + self._listener.setPosition(QVector3D()) + self._listener.setRotation(QQuaternion()) + self._engine.start() + + self._sound = QSpatialSound(self._engine) + self.update_position() + + self._animation = QPropertyAnimation(self._azimuth, b"value") + self._animation.setDuration(10000) + self._animation.setStartValue(-180) + self._animation.setEndValue(180) + self._animation.setLoopCount(-1) + self._animate_button.toggled.connect(self.animate_changed) + + def set_file(self, file): + self._file_edit.setText(file) + + def update_position(self): + az = self._azimuth.value() / 180. * math.pi + el = self._elevation.value() / 180. * math.pi + d = self._distance.value() + + x = d * math.sin(az) * math.cos(el) + y = d * math.sin(el) + z = -d * math.cos(az) * math.cos(el) + self._sound.setPosition(QVector3D(x, y, z)) + + @Slot() + def new_occlusion(self): + self._sound.setOcclusionIntensity(self._occlusion.value() / 100.) + + @Slot() + def mode_changed(self): + self._engine.setOutputMode(self._mode.currentData()) + + @Slot(str) + def file_changed(self, file): + self._sound.setSource(QUrl.fromLocalFile(file)) + self._sound.setSize(5) + self._sound.setLoops(QSpatialSound.Infinite) + + @Slot() + def open_file_dialog(self): + if not self._file_dialog: + directory = QStandardPaths.writableLocation(QStandardPaths.MusicLocation) + self._file_dialog = QFileDialog(self, "Open Audio File", directory) + self._file_dialog.setAcceptMode(QFileDialog.AcceptOpen) + mime_types = ["audio/mpeg", "audio/aac", "audio/x-ms-wma", + "audio/x-flac+ogg", "audio/x-wav"] + self._file_dialog.setMimeTypeFilters(mime_types) + self._file_dialog.selectMimeTypeFilter(mime_types[0]) + + if self._file_dialog.exec() == QDialog.Accepted: + self._file_edit.setText(self._file_dialog.selectedFiles()[0]) + + @Slot() + def update_room(self): + d = self._room_dimension.value() + self._room.setDimensions(QVector3D(d, d, 400)) + self._room.setReflectionGain(float(self._reflection_gain.value()) / 100) + self._room.setReverbGain(float(self._reverb_gain.value()) / 100) + + @Slot() + def animate_changed(self): + if self._animate_button.isChecked(): + self._animation.start() + else: + self._animation.stop() + + +if __name__ == '__main__': + app = QApplication(sys.argv) + + name = "Spatial Audio Test Application" + QCoreApplication.setApplicationVersion(qVersion()) + QGuiApplication.setApplicationDisplayName(name) + + argument_parser = ArgumentParser(description=name, + formatter_class=RawTextHelpFormatter) + argument_parser.add_argument("file", help="File", + nargs='?', type=str) + options = argument_parser.parse_args() + + w = AudioWidget() + w.show() + + if options.file: + w.set_file(options.file) + + sys.exit(app.exec()) -- cgit v1.2.3