#include "soundboard.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include std::ostream& operator<<(std::ostream& os, const QString& string) { os << string.toStdString(); return os; } Soundboard::Soundboard(QWidget* parent/* = nullptr */) : QWidget(parent) { setWindowTitle("Soundboard"); resize(500, 700); QVBoxLayout* main_layout = new QVBoxLayout(this); // Top QHBoxLayout* top_layout = new QHBoxLayout(); m_combo_box = new QComboBox(); m_combo_box->setToolTip("Select the destination application for the audio stream"); top_layout->addWidget(m_combo_box, 1); connect(m_combo_box, &QComboBox::currentIndexChanged, this, &Soundboard::on_app_selected); m_refresh_button = new QPushButton(); m_refresh_button->setToolTip("Refresh the application list"); m_refresh_button->setIcon(style()->standardIcon(QStyle::SP_BrowserReload)); top_layout->addWidget(m_refresh_button); connect(m_refresh_button, &QPushButton::pressed, this, &Soundboard::refresh_apps); m_folder_button = new QPushButton(); m_folder_button->setToolTip("Select a folder containing audio files"); m_folder_button->setIcon(style()->standardIcon(QStyle::SP_DirIcon)); top_layout->addWidget(m_folder_button); connect(m_folder_button, &QPushButton::pressed, this, &Soundboard::load_directory); m_label = new QLabel("Select a folder"); top_layout->addWidget(m_label); main_layout->addLayout(top_layout); // Volume slider QHBoxLayout* volume_slider_layout = new QHBoxLayout(); m_volume_label = new QLabel("Volume: 80%"); m_volume_label->setFixedWidth(100); volume_slider_layout->addWidget(m_volume_label); m_volume_slider = new QSlider(Qt::Horizontal); m_volume_slider->setToolTip("Adjust the output volume"); m_volume_slider->setRange(0, 100); m_volume_slider->setValue(80); m_volume_slider->setSingleStep(5); volume_slider_layout->addWidget(m_volume_slider); connect(m_volume_slider, &QSlider::valueChanged, this, &Soundboard::set_volume); main_layout->addLayout(volume_slider_layout); // Speed slider QHBoxLayout* speed_slider_layout = new QHBoxLayout(); m_speed_label = new QLabel("Speed: 100%"); m_speed_label->setFixedWidth(100); speed_slider_layout->addWidget(m_speed_label); m_speed_slider = new QSlider(Qt::Horizontal); m_speed_slider->setToolTip("Adjust the audio speed"); m_speed_slider->setRange(0, 100); m_speed_slider->setValue(100); m_speed_slider->setSingleStep(5); speed_slider_layout->addWidget(m_speed_slider); connect(m_speed_slider, &QSlider::valueChanged, this, &Soundboard::set_speed); main_layout->addLayout(speed_slider_layout); // Middle m_sound_list = new QListWidget(); m_sound_list->setSelectionMode(QAbstractItemView::SingleSelection); main_layout->addWidget(m_sound_list); connect(m_sound_list, &QListWidget::itemClicked, this, &Soundboard::play_sound); // Bottom QHBoxLayout* bottom_layout = new QHBoxLayout(); m_stop_button = new QPushButton("Stop"); m_stop_button->setToolTip("Stop all audio streams"); m_stop_button->setIcon(style()->standardIcon(QStyle::SP_MediaStop)); bottom_layout->addWidget(m_stop_button, 1); connect(m_stop_button, &QPushButton::pressed, this, &Soundboard::stop_sounds); m_monitor_toggle = new QCheckBox("Monitor"); m_monitor_toggle->setToolTip("Toggle audio monitoring on your system sink"); bottom_layout->addWidget(m_monitor_toggle); connect(m_monitor_toggle, &QCheckBox::checkStateChanged, this, &Soundboard::toggle_monitor); main_layout->addLayout(bottom_layout); // Setup Pipewire Components m_graph.refresh(); m_sink = Pipewire::Sink::create("soundboard", m_graph); if (!m_sink) { std::cerr << "[ERROR] Failed to initialize virtual sink for soundboard" << std::endl; } refresh_apps(); } Soundboard::~Soundboard() { stop_sounds(); } void Soundboard::on_app_selected(int index) { if (!m_sink) { return; } m_graph.refresh(); // Destroy old output links from our sink auto existing_links = m_graph.get_links_from_node(m_sink->get_id()); for (const auto& link : existing_links) { Pipewire::destroy_link(link.id); } // Recover monitor state if (m_monitor_toggle->isChecked()) { int sink_id = m_sink->get_id(); auto sinks = m_graph.get_all_sinks(); for (const auto& system_sink : sinks) { // Skip our own virtual sink if (system_sink.id == sink_id) { continue; } Pipewire::link_nodes(sink_id, system_sink.id, m_graph); } } std::cout << "[INFO] Monitor: " << m_monitor_toggle->isChecked() << std::endl; if (index < 0 || index >= m_apps.size()) { return; } // Link sink to target app int target_app_id = m_apps[index].id; Pipewire::link_nodes(m_sink->get_id(), target_app_id, m_graph); std::cout << "[INFO] Updated routing" << std::endl; } void Soundboard::refresh_apps() { m_graph.refresh(); m_apps = m_graph.get_input_apps(); m_combo_box->clear(); for (const auto& app : m_apps) { m_combo_box->addItem(app.get_display_name()); } std::cout << "[INFO] Updated app list" << std::endl; } void Soundboard::load_directory() { m_audio_directory = QFileDialog::getExistingDirectory( nullptr, "Select audio directory", QDir::homePath(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks ); if (m_audio_directory.isEmpty()) { return; } m_label->setText(m_audio_directory); m_sound_list->clear(); QDir dir(m_audio_directory); dir.setSorting(QDir::SortFlag::Name); dir.setNameFilters({ "*.mp3", "*.wav" }); dir.setFilter(QDir::Files | QDir::NoDotAndDotDot); QDirIterator it(dir); while (it.hasNext()) { QString full_path = it.next(); QString basename = QString::fromStdString( std::filesystem::path(full_path.toStdString()).filename().string() ); m_sound_list->addItem(basename); } std::cout << "[INFO] Selected audio directory: " << m_audio_directory << std::endl; } void Soundboard::play_sound() { QListWidgetItem* item = m_sound_list->currentItem(); if (item == nullptr || !m_sink) { return; } QString sound = item->text(); QString full_path = QDir(m_audio_directory).filePath(sound); m_player.play_file(full_path, m_sink->get_name(), m_speed); std::cout << "[INFO] Playing: " << sound << std::endl; } void Soundboard::stop_sounds() { m_player.stop_all(); std::cout << "[INFO] Stopped all playing sounds" << std::endl; } void Soundboard::set_volume(int value) { if (!m_sink) { return; } Pipewire::set_volume(m_sink->get_name(), value); m_volume_label->setText(QString("Volume: ") + QString::number(value) + "%"); } void Soundboard::set_speed(int value) { m_speed = static_cast(value) / 100.0f; m_speed_label->setText(QString("Speed: ") + QString::number(value) + "%"); } void Soundboard::toggle_monitor(Qt::CheckState state) { (void) state; on_app_selected(m_combo_box->currentIndex()); }