Browse Source
- SoundManager singleton wraps ma_engine + ma_sound (non-blocking, background thread) - MP3 files embedded in QRC, decoded to PCM at startup with MA_SOUND_FLAG_DECODE - miniaudio.h (MIT) in lib/, IMPLEMENTATION in separate miniaudio_impl.c - Sound settings page in SettingsDialog: enable/disable, volume slider (0-100%) - Config keys [gui] sound_enabled, sound_volume - Incoming messages trigger msg_incoming.mp3 soundtopo_upd
25 changed files with 101647 additions and 2 deletions
@ -0,0 +1,4 @@
|
||||
If: |
||||
PathMatch: transport/miniaudio_impl\.c |
||||
Index: |
||||
Background: Skip |
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,124 @@
|
||||
#include "miniaudio.h" |
||||
|
||||
#include "sound_manager.h" |
||||
#include <QFile> |
||||
#include <QDebug> |
||||
|
||||
SoundManager* SoundManager::instance() { |
||||
static SoundManager s; |
||||
return &s; |
||||
} |
||||
|
||||
SoundManager::~SoundManager() { shutdown(); } |
||||
|
||||
bool SoundManager::init() { |
||||
if (m_initialized) return true; |
||||
|
||||
ma_engine* engine = new ma_engine; |
||||
ma_result result = ma_engine_init(NULL, engine); |
||||
if (result != MA_SUCCESS) { |
||||
qWarning("SoundManager: ma_engine_init failed (%d)", result); |
||||
delete engine; |
||||
return false; |
||||
} |
||||
m_engine = engine; |
||||
m_initialized = true; |
||||
qDebug("SoundManager: initialized"); |
||||
return true; |
||||
} |
||||
|
||||
void SoundManager::shutdown() { |
||||
if (!m_initialized) return; |
||||
|
||||
for (auto it = m_sounds.begin(); it != m_sounds.end(); ++it) { |
||||
if (it->sound) { |
||||
ma_sound_uninit(it->sound); |
||||
delete it->sound; |
||||
} |
||||
} |
||||
m_sounds.clear(); |
||||
|
||||
if (m_engine) { |
||||
ma_engine_uninit(m_engine); |
||||
delete m_engine; |
||||
m_engine = nullptr; |
||||
} |
||||
m_initialized = false; |
||||
qDebug("SoundManager: shutdown"); |
||||
} |
||||
|
||||
void SoundManager::setEnabled(bool enabled) { |
||||
m_enabled = enabled; |
||||
qDebug("SoundManager: enabled=%d", enabled); |
||||
} |
||||
|
||||
bool SoundManager::isEnabled() const { return m_enabled; } |
||||
|
||||
void SoundManager::setVolume(float volume) { |
||||
if (volume < 0.0f) volume = 0.0f; |
||||
if (volume > 1.0f) volume = 1.0f; |
||||
m_volume = volume; |
||||
for (auto it = m_sounds.begin(); it != m_sounds.end(); ++it) { |
||||
if (it->sound) |
||||
ma_sound_set_volume(it->sound, m_volume); |
||||
} |
||||
} |
||||
|
||||
float SoundManager::volume() const { return m_volume; } |
||||
|
||||
bool SoundManager::loadSound(const QString& name, const QString& qrcPath) { |
||||
if (!m_initialized) { |
||||
qWarning("SoundManager: not initialized, cannot load %s", qPrintable(name)); |
||||
return false; |
||||
} |
||||
if (m_sounds.contains(name)) { |
||||
qWarning("SoundManager: sound '%s' already loaded", qPrintable(name)); |
||||
return false; |
||||
} |
||||
|
||||
QFile f(qrcPath); |
||||
if (!f.open(QIODevice::ReadOnly)) { |
||||
qWarning("SoundManager: cannot open %s", qPrintable(qrcPath)); |
||||
return false; |
||||
} |
||||
QByteArray mp3Data = f.readAll(); |
||||
f.close(); |
||||
|
||||
ma_decoder_config decConfig = ma_decoder_config_init_default(); |
||||
ma_decoder decoder; |
||||
ma_result result = ma_decoder_init_memory(mp3Data.constData(), mp3Data.size(), &decConfig, &decoder); |
||||
if (result != MA_SUCCESS) { |
||||
qWarning("SoundManager: ma_decoder_init_memory failed for %s (%d)", qPrintable(name), result); |
||||
return false; |
||||
} |
||||
|
||||
LoadedSound ls; |
||||
ls.sound = new ma_sound; |
||||
result = ma_sound_init_from_data_source(m_engine, (ma_data_source*)&decoder.ds, |
||||
MA_SOUND_FLAG_DECODE, |
||||
NULL, ls.sound); |
||||
ma_decoder_uninit(&decoder); |
||||
|
||||
if (result != MA_SUCCESS) { |
||||
qWarning("SoundManager: sound init failed for %s (%d)", qPrintable(name), result); |
||||
delete ls.sound; |
||||
return false; |
||||
} |
||||
|
||||
m_sounds.insert(name, ls); |
||||
qDebug("SoundManager: loaded '%s' from %s (%lld bytes)", |
||||
qPrintable(name), qPrintable(qrcPath), (long long)mp3Data.size()); |
||||
return true; |
||||
} |
||||
|
||||
void SoundManager::play(const QString& name) { |
||||
if (!m_enabled || !m_initialized) return; |
||||
|
||||
auto it = m_sounds.find(name); |
||||
if (it == m_sounds.end()) return; |
||||
|
||||
ma_sound* s = it->sound; |
||||
ma_sound_stop(s); |
||||
ma_sound_seek_to_pcm_frame(s, 0); |
||||
ma_sound_start(s); |
||||
} |
||||
@ -0,0 +1,39 @@
|
||||
#pragma once |
||||
|
||||
#include <QObject> |
||||
#include <QHash> |
||||
#include <QString> |
||||
|
||||
struct ma_engine; |
||||
struct ma_sound; |
||||
|
||||
class SoundManager : public QObject { |
||||
Q_OBJECT |
||||
public: |
||||
static SoundManager* instance(); |
||||
|
||||
bool init(); |
||||
void shutdown(); |
||||
|
||||
void setEnabled(bool enabled); |
||||
bool isEnabled() const; |
||||
void setVolume(float volume); |
||||
float volume() const; |
||||
|
||||
bool loadSound(const QString& name, const QString& qrcPath); |
||||
void play(const QString& name); |
||||
|
||||
private: |
||||
SoundManager() = default; |
||||
~SoundManager(); |
||||
|
||||
struct LoadedSound { |
||||
ma_sound* sound = nullptr; |
||||
}; |
||||
|
||||
ma_engine* m_engine = nullptr; |
||||
bool m_initialized = false; |
||||
bool m_enabled = true; |
||||
float m_volume = 0.8f; |
||||
QHash<QString, LoadedSound> m_sounds; |
||||
}; |
||||
@ -0,0 +1,120 @@
|
||||
#include "soundsettingspage.h" |
||||
#include "sound_manager.h" |
||||
#include <QVBoxLayout> |
||||
#include <QHBoxLayout> |
||||
#include <QCheckBox> |
||||
#include <QSlider> |
||||
#include <QLabel> |
||||
#include <QFile> |
||||
#include <QTextStream> |
||||
#include <QDebug> |
||||
|
||||
SoundSettingsPage::SoundSettingsPage(QWidget* parent) |
||||
: QWidget(parent) |
||||
{ |
||||
auto* layout = new QVBoxLayout(this); |
||||
layout->setContentsMargins(0, 12, 0, 0); |
||||
layout->setSpacing(14); |
||||
|
||||
m_enabledCheck = new QCheckBox("Enable sounds", this); |
||||
m_enabledCheck->setStyleSheet("font-size: 13px;"); |
||||
layout->addWidget(m_enabledCheck); |
||||
|
||||
auto* volLayout = new QHBoxLayout(); |
||||
auto* volLabel = new QLabel("Volume:", this); |
||||
volLabel->setStyleSheet("font-size: 13px;"); |
||||
volLayout->addWidget(volLabel); |
||||
|
||||
m_volumeSlider = new QSlider(Qt::Horizontal, this); |
||||
m_volumeSlider->setRange(0, 100); |
||||
m_volumeSlider->setFixedWidth(200); |
||||
m_volumeSlider->setStyleSheet("QSlider::groove:horizontal { height: 6px; border-radius: 3px; background: palette(mid); }" |
||||
"QSlider::handle:horizontal { width: 14px; height: 14px; margin: -4px 0; border-radius: 7px; background: #3390EC; }"); |
||||
volLayout->addWidget(m_volumeSlider); |
||||
|
||||
m_volumeLabel = new QLabel("80%", this); |
||||
m_volumeLabel->setFixedWidth(36); |
||||
m_volumeLabel->setStyleSheet("font-size: 13px;"); |
||||
volLayout->addWidget(m_volumeLabel); |
||||
volLayout->addStretch(); |
||||
layout->addLayout(volLayout); |
||||
|
||||
layout->addStretch(); |
||||
|
||||
connect(m_enabledCheck, &QCheckBox::stateChanged, this, &SoundSettingsPage::onEnabledChanged); |
||||
connect(m_volumeSlider, &QSlider::valueChanged, this, [this](int val) { |
||||
m_volumeLabel->setText(QString::number(val) + "%"); |
||||
}); |
||||
} |
||||
|
||||
void SoundSettingsPage::onEnabledChanged(int state) { |
||||
m_volumeSlider->setEnabled(state == Qt::Checked); |
||||
m_volumeLabel->setEnabled(state == Qt::Checked); |
||||
} |
||||
|
||||
void SoundSettingsPage::loadFromConfig(const QString& path) { |
||||
QFile f(path); |
||||
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) return; |
||||
|
||||
bool enabled = true; |
||||
int volume = 80; |
||||
bool inGui = false; |
||||
QTextStream in(&f); |
||||
while (!in.atEnd()) { |
||||
QString line = in.readLine().trimmed(); |
||||
if (line == "[gui]") { inGui = true; continue; } |
||||
if (line.startsWith("[") && line.endsWith("]")) inGui = false; |
||||
if (!inGui) continue; |
||||
if (line.startsWith("sound_enabled=")) |
||||
enabled = line.mid(QString("sound_enabled=").size()).trimmed() != "0"; |
||||
else if (line.startsWith("sound_volume=")) |
||||
volume = line.mid(QString("sound_volume=").size()).trimmed().toInt(); |
||||
} |
||||
f.close(); |
||||
|
||||
m_enabledCheck->setChecked(enabled); |
||||
m_volumeSlider->setValue(volume); |
||||
m_volumeSlider->setEnabled(enabled); |
||||
m_volumeLabel->setEnabled(enabled); |
||||
m_volumeLabel->setText(QString::number(volume) + "%"); |
||||
} |
||||
|
||||
void SoundSettingsPage::saveToConfig(const QString& path) const { |
||||
QFile f(path); |
||||
if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) return; |
||||
QString content = f.readAll(); |
||||
f.close(); |
||||
|
||||
QStringList lines = content.split('\n'); |
||||
|
||||
bool inGui = false; |
||||
bool foundEnabled = false, foundVolume = false; |
||||
for (int i = 0; i < lines.size(); i++) { |
||||
QString line = lines[i].trimmed(); |
||||
if (line == "[gui]") { inGui = true; continue; } |
||||
if (line.startsWith("[") && line.endsWith("]")) inGui = false; |
||||
if (!inGui) continue; |
||||
|
||||
if (line.startsWith("sound_enabled=")) { |
||||
lines[i] = QString("sound_enabled=%1").arg(m_enabledCheck->isChecked() ? 1 : 0); |
||||
foundEnabled = true; |
||||
} else if (line.startsWith("sound_volume=")) { |
||||
lines[i] = QString("sound_volume=%1").arg(m_volumeSlider->value()); |
||||
foundVolume = true; |
||||
} |
||||
} |
||||
|
||||
if (!foundEnabled || !foundVolume) { |
||||
for (int i = 0; i < lines.size(); i++) { |
||||
if (lines[i].trimmed() == "[gui]") { |
||||
if (!foundEnabled) lines.insert(i + 1, QString("sound_enabled=%1").arg(m_enabledCheck->isChecked() ? 1 : 0)); |
||||
if (!foundVolume) lines.insert(i + 1, QString("sound_volume=%1").arg(m_volumeSlider->value())); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
|
||||
f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text); |
||||
f.write(lines.join('\n').toUtf8()); |
||||
f.close(); |
||||
} |
||||
@ -0,0 +1,24 @@
|
||||
#pragma once |
||||
|
||||
#include <QWidget> |
||||
|
||||
class QCheckBox; |
||||
class QSlider; |
||||
class QLabel; |
||||
|
||||
class SoundSettingsPage : public QWidget { |
||||
Q_OBJECT |
||||
public: |
||||
explicit SoundSettingsPage(QWidget* parent = nullptr); |
||||
|
||||
void loadFromConfig(const QString& configPath); |
||||
void saveToConfig(const QString& configPath) const; |
||||
|
||||
private slots: |
||||
void onEnabledChanged(int state); |
||||
|
||||
private: |
||||
QCheckBox* m_enabledCheck; |
||||
QSlider* m_volumeSlider; |
||||
QLabel* m_volumeLabel; |
||||
}; |
||||
@ -0,0 +1,2 @@
|
||||
#define MINIAUDIO_IMPLEMENTATION |
||||
#include "../../../lib/miniaudio.h" |
||||
Loading…
Reference in new issue