#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); m_volume_slider = new QSlider(Qt::Horizontal); m_volume_slider->setToolTip("Adjust the output volume"); m_volume_slider->setRange(0, 80); m_volume_slider->setValue(80); m_volume_slider->setSingleStep(5); main_layout->addWidget(m_volume_slider); connect(m_volume_slider, &QSlider::valueChanged, this, &Soundboard::set_volume); // Middle m_sound_list = new QListWidget(); m_sound_list->setSelectionMode(QAbstractItemView::SingleSelection); main_layout->addWidget(m_sound_list); connect(m_sound_list, &QListWidget::itemSelectionChanged, this, &Soundboard::play_sound); // Bottom QHBoxLayout* bottom_layout = new QHBoxLayout(); m_stop_button = new QPushButton("Stop"); m_stop_button->setToolTip("Stop all audio streams"); bottom_layout->addWidget(m_stop_button); 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()); 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); std::cout << "[INFO] Set volume to " << value << '%' << std::endl; } void Soundboard::toggle_monitor(Qt::CheckState state) { (void) state; on_app_selected(m_combo_box->currentIndex()); }