Drumpad example (Qt Design Studio) - Final project

This example contains the final Qt Design Studio project of the Qt Design Studio integration tutorial. It contains all the necessary files to execute the project, including the Python code developed along the tutorial.

For more details, see the Qt Design Studio integration tutorial.

To download the initial project source code, visit Drumpad example (Qt Design Studio) - Initial project.

Download this example

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

import sys

from PySide6.QtGui import QGuiApplication
from PySide6.QtQml import QQmlApplicationEngine

from autogen.settings import setup_qt_environment
from audio import *  # noqa: F401,F403


def main():
    app = QGuiApplication(sys.argv)
    engine = QQmlApplicationEngine()

    setup_qt_environment(engine)

    if not engine.rootObjects():
        sys.exit(-1)

    ex = app.exec()
    del engine
    return ex


if __name__ == "__main__":
    sys.exit(main())
# Copyright (C) 2025 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

# This file is automatically generated by Qt Design Studio.
import os
import sys
from pathlib import Path

from PySide6.QtQml import QQmlApplicationEngine

project_root = Path(__file__).parent.parent.parent


def setup_qt_environment(qml_engine: QQmlApplicationEngine):
    """
    Load the QML application. Import the compiled resources when the application is deployed.
    """
    qml_app_url = "DrumpadContent/App.qml"

    if "__compiled__" in globals():
        # Application has been deployed using pyside6-deploy
        try:
            import autogen.resources  # noqa: F401
        except ImportError:
            resource_file = Path(__file__).parent / "resources.py"
            print(
                f"Error: No compiled resources found in {resource_file.absolute()}\n"
                f"Please compile the resources using pyside6-rcc or pyside6-project build",
                file=sys.stderr,
            )
            sys.exit(1)

        qml_engine.addImportPath(":/")
        qml_engine.load(f":/{qml_app_url}")
        return

    qml_engine.addImportPath(str(project_root.absolute()))
    os.environ["QT_QUICK_CONTROLS_CONF"] = str(project_root / "qtquickcontrols2.conf")
    qml_engine.load(str(project_root / qml_app_url))
# Copyright (C) 2025 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

from pathlib import Path

from PySide6.QtCore import QObject, Slot, QDirIterator
from PySide6.QtQml import QmlElement

from autogen.settings import project_root


QML_IMPORT_NAME = "Audio"
QML_IMPORT_MAJOR_VERSION = 1


@QmlElement
class AudioFilesModel(QObject):
    @Slot(result=list)
    def getModel(self):
        if "__compiled__" in globals():
            resource_prefix = ":/Sounds/"
            iterator = QDirIterator(resource_prefix, QDirIterator.Subdirectories)
            audio_files = []
            while iterator.hasNext():
                resource = iterator.next()
                audio_files.append(resource.split(resource_prefix)[-1])
            return audio_files

        return list(p.name for p in Path(project_root / "Sounds").glob("*.wav"))
# Copyright (C) 2025 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

from PySide6.QtQml import QmlElement
from PySide6.QtCore import QObject, Slot, Property, Signal, QUrl
from PySide6.QtMultimedia import QSoundEffect

from autogen.settings import project_root

QML_IMPORT_NAME = "Audio"
QML_IMPORT_MAJOR_VERSION = 1


@QmlElement
class AudioEngine(QObject):
    volumeChanged = Signal()
    fileChanged = Signal()
    isPlayingChanged = Signal()
    decodingStatusChanged = Signal(QSoundEffect.Status, str)

    def __init__(self, parent=None):
        super().__init__(parent)
        self._sound_effect = QSoundEffect()
        self._sound_effect.playingChanged.connect(self.isPlayingChanged.emit)  #
        self._sound_effect.statusChanged.connect(self.reportStatus)

    def reportStatus(self):
        if self._sound_effect.status() == QSoundEffect.Status.Error:
            self.decodingStatusChanged.emit(
                QSoundEffect.Status.Error,
                f"Error decoding file: {self._sound_effect.source().path()}",
            )
        else:
            self.decodingStatusChanged.emit(self._sound_effect.status(), "")

    @Slot(result=None)
    def play(self):
        self._sound_effect.play()

    def volume(self):
        return self._sound_effect.volume()

    def setVolume(self, value):
        self._sound_effect.setVolume(value)
        self.volumeChanged.emit()

    def file(self):
        return self._sound_effect.source()

    def setFile(self, value: QUrl):
        if self._sound_effect.source() == value or value.isEmpty():
            return

        if "__compiled__" in globals():
            self._sound_effect.setSource(f"qrc:/{value.toString()}")
        else:
            self._sound_effect.setSource(f"file:{project_root / value.toString()}")
        self.fileChanged.emit()

    def isPlaying(self):
        return self._sound_effect.isPlaying()

    volume = Property(float, volume, setVolume, notify=volumeChanged)
    file = Property(QUrl, file, setFile, notify=fileChanged)
    isPlaying = Property(bool, isPlaying, notify=isPlayingChanged)
# Copyright (C) 2025 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

import struct

from PySide6.QtCore import Qt, Property, QUrl, Signal, QFile, QPointF
from PySide6.QtGui import QPen, QPainter
from PySide6.QtMultimedia import QAudioFormat, QAudioDecoder
from PySide6.QtQml import QmlElement
from PySide6.QtQuick import QQuickPaintedItem

QML_IMPORT_NAME = "Audio"
QML_IMPORT_MAJOR_VERSION = 1


@QmlElement
class WaveformItem(QQuickPaintedItem):

    fileChanged = Signal()

    def __init__(self, parent=None):
        super().__init__(parent)
        self._waveformData = []
        self._background_color = Qt.black

        audio_format = QAudioFormat()
        audio_format.setChannelCount(1)
        audio_format.setSampleRate(44100)
        audio_format.setSampleFormat(QAudioFormat.Float)

        self._file_url: QUrl | None = None
        self._audio_file: QFile | None = None

        self._decoder = QAudioDecoder()
        self._decoder.setAudioFormat(audio_format)

        self._decoder.bufferReady.connect(self.onBufferReady)
        self._decoder.finished.connect(self.decoderFinished)

    def file(self) -> QUrl | None:
        return self._file_url

    def setFile(self, value: QUrl):
        if self._decoder.source() == value:
            return

        if self._audio_file and self._audio_file.isOpen():
            self._audio_file.close()

        self._waveformData = []
        self._decoder.stop()

        self._file_url = value
        if "__compiled__" in globals():
            path = self._file_url.toString().replace("qrc:/", ":/")
        else:
            path = self._file_url.path()
        self._audio_file = QFile(path)
        self._audio_file.open(QFile.ReadOnly)
        self._decoder.setSourceDevice(self._audio_file)
        self._decoder.start()
        self.fileChanged.emit()

    def paint(self, painter):
        # Fill the bounding rectangle with the specified color
        painter.fillRect(self.boundingRect(), self._background_color)

        # If no waveform data is available, draw the text
        if not self._waveformData:
            painter.setPen(Qt.white)
            painter.drawText(self.boundingRect(), Qt.AlignCenter, "Waveform not available")
            return

        painter.setRenderHint(QPainter.Antialiasing)

        # Set the pen for drawing the waveform
        pen = QPen(Qt.blue)
        pen.setWidth(1)
        painter.setPen(pen)

        # Get container dimensions
        rect = self.boundingRect()
        data_size = len(self._waveformData)

        # Calculate step size and center line
        x_step = rect.width() / data_size
        center_y = rect.height() / 2.0

        # Draw the waveform as connected lines
        for i in range(1, data_size):
            x1 = (i - 1) * x_step
            y1 = center_y - self._waveformData[i - 1] * center_y
            x2 = i * x_step
            y2 = center_y - self._waveformData[i] * center_y
            painter.drawLine(QPointF(x1, y1), QPointF(x2, y2))

    @staticmethod
    def float_buffer_to_list(data):
        # Calculate the number of 32-bit floats in the buffer
        float_count = len(data) // 4  # Each float32 is 4 bytes
        # Unpack the binary data into a list of floats
        return list(struct.unpack(f"{float_count}f", data))

    def onBufferReady(self):
        buffer = self._decoder.read()
        data = buffer.constData()
        self._waveformData.extend(self.float_buffer_to_list(data))
        self.update()

    file: QUrl = Property(QUrl, file, setFile, notify=fileChanged)

    def decoderFinished(self):
        self._audio_file.close()
// prop: json-converted
// prop: auto-generated

import QmlProject

Project {
    mainFile: "DrumpadContent/App.qml"
    mainUiFile: "DrumpadContent/MainScreen.qml"
    targetDirectory: "/opt/Drumpad"
    enableCMakeGeneration: false
    enablePythonGeneration: true
    widgetApp: true
    importPaths: [ "." ]
    mockImports: [ "Mocks" ]

    qdsVersion: "4.5"
    quickVersion: "6.7"
    qt6Project: true
    qtForMCUs: false

    multilanguageSupport: true
    primaryLanguage: "en"
    supportedLanguages: [ "en" ]

    Environment {
        QML_COMPAT_RESOLVE_URLS_ON_ASSIGNMENT: "1"
        QT_AUTO_SCREEN_SCALE_FACTOR: "1"
        QT_ENABLE_HIGHDPI_SCALING: "0"
        QT_LOGGING_RULES: "qt.qml.connections=false"
        QT_QUICK_CONTROLS_CONF: "qtquickcontrols2.conf"
    }

    QmlFiles {
        directory: "Drumpad"
    }

    QmlFiles {
        directory: "DrumpadContent"
    }

    QmlFiles {
        directory: "Generated"
    }

    Files {
        directory: "Sounds"
        filter: "*.mp3;*.wav"
    }

    QmlFiles {
        directory: "Mocks/Audio"
    }

    Files {
        files: [
            "qtquickcontrols2.conf"
        ]
    }

    Files {
        directory: "Drumpad"
        filter: "qmldir"
    }

    Files {
        directory: "DrumpadContent"
        filter: "*.ttf;*.otf"
    }
}
<RCC>
    <qresource>
        <file>Drumpad.qmlproject</file>
        <file>Drumpad/AvailableSoundsComboBox.qml</file>
        <file>Drumpad/CenteredFlow.qml</file>
        <file>Drumpad/Constants.qml</file>
        <file>Drumpad/PadButton.qml</file>
        <file>Drumpad/qmldir</file>
        <file>Drumpad/SoundEffectPlayer.qml</file>
        <file>Drumpad/StyledSpinBox.qml</file>
        <file>Drumpad/VolumeSlider.qml</file>
        <file>DrumpadContent/App.qml</file>
        <file>DrumpadContent/MainScreen.qml</file>
        <file>DrumpadContent/qmldir</file>
        <file>qtquickcontrols2.conf</file>
        <file>Sounds/Bongo Loop 125bpm.wav</file>
        <file>Sounds/Clap.wav</file>
        <file>Sounds/Closed Hat.wav</file>
        <file>Sounds/Kick Drum.wav</file>
        <file>Sounds/Open Hat.wav</file>
        <file>Sounds/Sine Bass Ebm.wav</file>
    </qresource>
</RCC>
; This file can be edited to change the style of the application
; Read "Qt Quick Controls 2 Configuration File" for details:
; http://doc.qt.io/qt-5/qtquickcontrols2-configuration.html

[Controls]
Style=Basic
// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls
import Audio

ComboBox {
    id: root

    property string currentFile: currentText ? `Sounds/${currentText}` : ""
    required property int initialIndex

    model: audioFilesModel.getModel()

    background: Rectangle {
        border.color: root.pressed ? Constants.primaryColor : Constants.secondaryColor
        border.width: root.visualFocus ? 3 : 2
        color: root.pressed ? Constants.secondaryColor : "black"
        implicitHeight: 30
        radius: 2
    }
    contentItem: Text {
        color: "white"
        elide: Text.ElideRight
        leftPadding: 10
        rightPadding: root.indicator.width + 10
        text: root.displayText
        verticalAlignment: Text.AlignVCenter
    }
    delegate: ItemDelegate {
        id: delegate

        required property int index

        highlighted: root.highlightedIndex === index

        background: Rectangle {
            color: delegate.highlighted ? Constants.darkGray : "black"
            implicitWidth: delegate.contentItem.implicitWidth
            width: popup.width
        }
        contentItem: Text {
            anchors.fill: parent
            color: delegate.highlighted ? "#ff0000" : "white"
            elide: Text.ElideRight
            leftPadding: 10
            text: root.model[delegate.index]
            verticalAlignment: Text.AlignVCenter
        }
    }
    indicator: Canvas {
        id: canvas

        contextType: "2d"
        height: 8
        width: 12
        x: root.width - canvas.width - root.rightPadding
        y: root.topPadding + (root.availableHeight - canvas.height) / 2

        onPaint: {
            let margin = 2;
            context.reset();
            context.lineWidth = 2;
            context.strokeStyle = "white";
            context.lineCap = "round";
            context.beginPath();
            context.moveTo(margin, margin);
            context.lineTo(width / 2, height - margin);
            context.lineTo(width - margin, margin);
            context.stroke();
        }

        Connections {
            function onPressedChanged() {
                canvas.requestPaint();
            }

            target: root
        }
    }
    popup: Popup {
        id: popup

        implicitHeight: contentItem.implicitHeight
        implicitWidth: 200
        padding: 2
        y: root.height + 2

        background: Rectangle {
            border.color: Constants.primaryColor
            border.width: 2
            color: "black"
        }
        contentItem: ListView {
            clip: true
            currentIndex: root.highlightedIndex
            implicitHeight: Math.min(contentHeight, 200)
            model: popup.visible ? root.delegateModel : null
        }
    }

    Component.onCompleted: {
        currentIndex = root.initialIndex % model.length;
    }

    AudioFilesModel {
        id: audioFilesModel
    }
}
// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

import QtQuick

// A Flow layout that centers its children horizontally
// Note that the implementation adds unnecessary spacing in rows that are not full
Flow {
    property int customMargin: (children.length && (children[0].width + spacing <= parentWidth))
        ? (parentWidth - rowWidth) / 2 + padding
        : padding
    property int parentWidth: parent.width - 2 * padding
    property int rowCount: children.length ? parentWidth / (children[0].width + spacing) : 0
    property int rowWidth: children.length
        ? rowCount * children[0].width + (rowCount - 1) * spacing + 2 * padding
        : 0

    anchors {
        leftMargin: customMargin
        rightMargin: customMargin
    }
}
// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

pragma Singleton
import QtQuick

QtObject {
    readonly property string darkGray: "#333333"
    readonly property string mediumGray: "#9B9B9B"
    readonly property string primaryColor: "#FF0000"
    readonly property string secondaryColor: "#8C0000"
}
// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

import QtQuick
import QtQuick.Shapes

Rectangle {
    id: root

    property bool isPlaying: false
    property bool isError: false
    property bool isLoading: false
    property int cornerRadius: 10
    signal pressed()

    color: "transparent"

    Shape {
        anchors.fill: parent

        ShapePath {
            strokeColor: "black"
            strokeWidth: 2

            fillGradient: RadialGradient {
                centerRadius: root.height
                centerX: root.width / 2
                centerY: root.height / 2
                focalX: centerX
                focalY: centerY

                GradientStop {
                    position: 0
                    color: {
                        if (isError)
                            return "black";
                        if (isLoading)
                            return "yellow";
                        if (isPlaying)
                            return Qt.darker(Constants.primaryColor, 1.25);
                        return Qt.darker(Constants.secondaryColor, 1.25);
                    }
                }
                GradientStop {
                    position: 0.5
                    color: {
                        if (isError)
                            return Constants.darkGray;
                        if (isLoading)
                            return "orange";
                        if (isPlaying)
                            return Constants.primaryColor;
                        return Constants.secondaryColor;
                    }
                }
            }

            // Rounded shape path
            PathMove {
                x: root.cornerRadius
                y: 0
            }
            PathQuad {
                controlX: 0
                controlY: 0
                x: 0
                y: root.cornerRadius
            }
            PathLine {
                x: 0
                y: root.height - root.cornerRadius
            }
            PathQuad {
                controlX: 0
                controlY: root.height
                x: root.cornerRadius
                y: root.height
            }
            PathLine {
                x: root.width - root.cornerRadius
                y: root.height
            }
            PathQuad {
                controlX: root.width
                controlY: root.height
                x: root.width
                y: root.height - root.cornerRadius
            }
            PathLine {
                x: root.width
                y: root.cornerRadius
            }
            PathQuad {
                controlX: root.width
                controlY: 0
                x: root.width - root.cornerRadius
                y: 0
            }
            PathLine {
                x: root.cornerRadius
                y: 0
            }
        }
    }

    MouseArea {
        anchors.fill: parent
        onClicked: root.pressed()
    }
}
module Drumpad

AvailableSoundsComboBox 1.0 AvailableSoundsComboBox.qml
SoundEffectPlayer 1.0 SoundEffectPlayer.qml
CenteredFlow 1.0 CenteredFlow.qml
VolumeSlider 1.0 VolumeSlider.qml
StyledSpinBox 1.0 StyledSpinBox.qml
PadButton 1.0 PadButton.qml

singleton Constants 1.0 Constants.qml
// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

import QtQuick
import QtQuick.Layouts
import QtQuick.Dialogs
import QtMultimedia

import Drumpad
import Audio

Rectangle {
    id: root

    property string decodingError: ""
    required property int index
    property int status: SoundEffect.Null
    property bool isLoading: status == SoundEffect.Loading
    property bool isError: status == SoundEffect.Error || status == SoundEffect.Null
    property bool isReady: status == SoundEffect.Ready

    function play() {
        if (root.status == SoundEffect.Ready) {
            audioEngine.play();
        }
    }

    color: Constants.darkGray
    implicitHeight: layout.implicitHeight + 2 * layout.anchors.margins
    implicitWidth: layout.implicitWidth + 2 * layout.anchors.margins
    radius: 10

    onDecodingErrorChanged: {
        if (status == SoundEffect.Error && root.decodingError) {
            errorMessageDialog.text = root.decodingError;
            errorMessageDialog.open();
        }
    }

    AudioEngine {
        id: audioEngine

        file: availableSoundsComboBox.currentFile
        volume: volumeSlider.value

        onDecodingStatusChanged: (status, error) => {
            root.status = status;
            if (status == SoundEffect.Error && error) {
                root.decodingError = error;
            } else {
                root.decodingError = "";
            }
        }
    }

    MessageDialog {
        id: errorMessageDialog

        buttons: MessageDialog.Ok
        title: "Error decoding file"
    }

    ColumnLayout {
        id: layout

        anchors.fill: parent
        anchors.margins: 10
        spacing: 10

        RowLayout {
            spacing: 10

            Text {
                Layout.alignment: Qt.AlignVCenter
                Layout.fillWidth: true
                color: "white"
                text: `Player ${root.index + 1}`
            }
            AvailableSoundsComboBox {
                id: availableSoundsComboBox

                Layout.alignment: Qt.AlignCenter
                initialIndex: root.index
            }
        }

        WaveformItem {
            id: waveformItem

            file: audioEngine.file
            height: 100
            width: 300
        }

        Row {
            Layout.alignment: Qt.AlignCenter
            spacing: 10

            PadButton {
                id: padRectangle
                height: 100
                width: 100
                isPlaying: audioEngine.isPlaying
                isError: root.isError
                isLoading: root.isLoading
                onPressed: root.play()
            }

            VolumeSlider {
                id: volumeSlider

                height: padRectangle.height
                value: 0.75
                width: 16
            }
        }
    }
}
// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

import QtQuick
import QtQuick.Controls

SpinBox {
    id: root

    property int innerPadding: 10

    height: contentItem.implicitHeight + innerPadding
    width: contentItem.width + up.indicator.implicitWidth + down.indicator.implicitWidth

    background: Rectangle {
        border.color: Constants.secondaryColor
    }

    contentItem: Text {
        color: "black"
        height: parent.height
        horizontalAlignment: Text.AlignHCenter
        text: root.textFromValue(root.value, root.locale)
        verticalAlignment: Text.AlignVCenter
        width: implicitWidth + innerPadding * 2
    }

    down.indicator: Rectangle {
        border.color: Constants.secondaryColor
        color: root.down.pressed ? Constants.mediumGray : enabled ? Constants.darkGray : "black"
        height: parent.height
        implicitWidth: downText.implicitWidth + innerPadding * 2
        x: root.mirrored ? parent.width - width : 0

        Text {
            id: downText

            anchors.fill: parent
            color: "white"
            font.pixelSize: Math.round(root.font.pixelSize * 1.5)
            fontSizeMode: Text.Fit
            horizontalAlignment: Text.AlignHCenter
            text: "-"
            verticalAlignment: Text.AlignVCenter
        }
    }

    up.indicator: Rectangle {
        border.color: Constants.secondaryColor
        color: root.up.pressed ? Constants.mediumGray : enabled ? Constants.darkGray : "black"
        height: parent.height
        implicitWidth: upText.implicitWidth + innerPadding * 2
        x: root.mirrored ? 0 : parent.width - width

        Text {
            id: upText

            anchors.centerIn: parent
            anchors.fill: parent
            color: "white"
            font.pixelSize: Math.round(root.font.pixelSize * 1.5)
            fontSizeMode: Text.Fit
            horizontalAlignment: Text.AlignHCenter
            text: "+"
            verticalAlignment: Text.AlignVCenter
        }
    }
}
// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

import QtQuick
import QtQuick.Controls

Slider {
    id: root

    orientation: Qt.Vertical
    padding: 0

    background: Rectangle {
        color: Constants.mediumGray
        implicitHeight: root.height
        implicitWidth: root.width
        radius: width / 2

        Rectangle {
            anchors.bottom: parent.bottom
            anchors.horizontalCenter: parent.horizontalCenter
            color: Qt.lighter(Constants.primaryColor, 1 - (root.visualPosition * 0.3))
            height: (1 - root.visualPosition) * parent.height + (root.visualPosition * handle.height)
            radius: parent.width / 2
            width: parent.width
        }
    }

    handle: Rectangle {
        border.color: "#b0b0b0"
        border.width: 1
        color: root.pressed ? "#e0e0e0" : "#ffffff"
        height: root.width
        radius: width / 2
        width: root.width
        x: root.availableWidth / 2 - height / 2
        y: root.visualPosition * (root.availableHeight - height)
    }
}
// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

import QtQuick 2.15
import QtQuick.Window 2.15
import Drumpad 1.0

Window {
    id: root

    height: 800
    title: "Drumpad"
    visible: true
    width: 1200

    MainScreen {
        id: mainScreen

        anchors.fill: parent
    }
}
// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Drumpad
import Audio

Rectangle {
    id: root

    property QtObject soundEffectPlayer: Qt.createComponent("../Drumpad/SoundEffectPlayer.qml",
                                                            Component.PreferSynchronous)

    color: "black"
    focus: true

    Component.onCompleted: {
        // Initialize the default sound effect players
        for (var i = 0; i < audioPlayersSpinBox.value; i++) {
            root.soundEffectPlayer.createObject(soundEffectPlayersFlow, {
                index: i
            });
        }
    }
    Keys.onPressed: event => {
        if (event.key < Qt.Key_1 || event.key > Qt.Key_9) {
            // Ignore key out of scope
            return;
        }

        let digit = event.key - Qt.Key_1;
        if (digit < soundEffectPlayersFlow.children.length) {
            soundEffectPlayersFlow.children[digit].play();
        }
    }

    ColumnLayout {
        anchors.fill: parent
        anchors.margins: 10

        Row {
            id: audioPlayersCountRow

            Layout.alignment: Qt.AlignHCenter
            spacing: 5

            Text {
                anchors.verticalCenter: parent.verticalCenter
                color: "white"
                text: "Audio players:"
            }

            StyledSpinBox {
                id: audioPlayersSpinBox

                value: 5

                onValueModified: {
                    let soundPlayersCount = soundEffectPlayersFlow.children.length;
                    if (audioPlayersSpinBox.value < soundPlayersCount) {
                        // Remove extra sound effect players
                        soundEffectPlayersFlow.children.length = audioPlayersSpinBox.value;
                        return;
                    }

                    if (audioPlayersSpinBox.value < soundPlayersCount) {
                        return;
                    }
                    // Create more sound effect players
                    for (var i = soundPlayersCount; i < audioPlayersSpinBox.value; i++) {
                        root.soundEffectPlayer.createObject(soundEffectPlayersFlow, {
                            index: i
                        });
                    }
                }
            }
        }

        ScrollView {
            Layout.fillHeight: true
            Layout.fillWidth: true
            contentWidth: width

            background: Rectangle {
                color: "#232323"
            }

            CenteredFlow {
                id: soundEffectPlayersFlow

                anchors.fill: parent
                padding: 10
                spacing: 10
            }
        }
    }
}
module DrumpadContent

App 1.0 App.qml
MainScreen 1.0 MainScreen.qml
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

import QtQuick
import QtMultimedia

Item {
    id: root

    property double volume
    property url file

    MediaPlayer {
        id: player
        source: file
        audioOutput: AudioOutput {}
    }

    onVolumeChanged : {
        console.log("Mock: VolumeChanaged ", volume )
    }

    function play() {
        console.log("Mock: play()")
        player.play()
    }
}
module Audio
AudioEngine 1.0 AudioEngine.qml
WaveformItem 1.0 WaveformItem.qml
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

import QtQuick
import QtQuick.Controls

Rectangle {
    id: root
    width: 1920
    height: 1080
    color: "blue"
    property url file
}
// Copyright (C) 2026 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

Item {
    getFiles: function() {
        console.log("AudioFilesModel mock: getFiles()")
    }
}
module Components
AudioFilesModel 1.0 AudioFilesModel.qml