You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
227 lines
7.5 KiB
227 lines
7.5 KiB
#include "miniaudio.h" |
|
|
|
#include "sound_manager.h" |
|
#include <QFile> |
|
#include <QDebug> |
|
#include "../../lib/debug_config.h" |
|
|
|
SoundManager* SoundManager::instance() { |
|
static SoundManager s; |
|
return &s; |
|
} |
|
|
|
SoundManager::~SoundManager() { |
|
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "SoundManager: static destructor entered"); |
|
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_context = ma_device_get_context(ma_engine_get_device(engine)); |
|
m_initialized = true; |
|
qDebug("SoundManager: initialized (engine=%p, context=%p)", (void*)engine, (void*)m_context); |
|
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; } |
|
if (it->decoder) { ma_decoder_uninit(it->decoder); delete it->decoder; } |
|
} |
|
m_sounds.clear(); |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "SoundManager: before ma_engine_uninit engine=%p", (void*)m_engine); |
|
if (m_engine) { ma_engine_uninit(m_engine); delete m_engine; m_engine = nullptr; } |
|
DEBUG_INFO(DEBUG_CATEGORY_DEBUG, "SoundManager: after ma_engine_uninit"); |
|
m_initialized = false; |
|
} |
|
|
|
void SoundManager::setEnabled(bool enabled) { m_enabled = 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; |
|
} |
|
|
|
float SoundManager::volume() const { return m_volume; } |
|
|
|
void SoundManager::setEvent(const QString& name, bool enabled, float volume) { |
|
if (volume < 0.0f) volume = 0.0f; |
|
if (volume > 1.0f) volume = 1.0f; |
|
EventSettings es; |
|
es.enabled = enabled; |
|
es.volume = volume; |
|
m_eventSettings[name] = es; |
|
qDebug("SoundManager: event '%s' enabled=%d vol=%.2f", qPrintable(name), enabled, volume); |
|
} |
|
|
|
SoundManager::EventSettings SoundManager::eventSettings(const QString& name) const { |
|
return m_eventSettings.value(name); |
|
} |
|
|
|
bool SoundManager::loadSound(const QString& name, const QString& filePath) { |
|
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(filePath); |
|
if (!f.open(QIODevice::ReadOnly)) { |
|
qWarning("SoundManager: cannot open %s", qPrintable(filePath)); |
|
return false; |
|
} |
|
|
|
LoadedSound ls; |
|
ls.mp3Data = f.readAll(); |
|
f.close(); |
|
|
|
ls.decoder = new ma_decoder; |
|
ma_decoder_config decConfig = ma_decoder_config_init_default(); |
|
ma_result result = ma_decoder_init_memory(ls.mp3Data.constData(), ls.mp3Data.size(), |
|
&decConfig, ls.decoder); |
|
if (result != MA_SUCCESS) { |
|
qWarning("SoundManager: ma_decoder_init_memory failed for %s (%d)", qPrintable(name), result); |
|
delete ls.decoder; |
|
return false; |
|
} |
|
|
|
ls.sound = new ma_sound; |
|
result = ma_sound_init_from_data_source(m_engine, (ma_data_source*)&ls.decoder->ds, |
|
0, NULL, ls.sound); |
|
if (result != MA_SUCCESS) { |
|
qWarning("SoundManager: sound init failed for %s (%d)", qPrintable(name), result); |
|
ma_decoder_uninit(ls.decoder); delete ls.decoder; |
|
delete ls.sound; |
|
return false; |
|
} |
|
|
|
m_sounds.insert(name, ls); |
|
qDebug("SoundManager: loaded '%s' from %s (%lld bytes)", |
|
qPrintable(name), qPrintable(filePath), (long long)ls.mp3Data.size()); |
|
return true; |
|
} |
|
|
|
void SoundManager::play(const QString& name) { |
|
if (!m_enabled || !m_initialized) return; |
|
|
|
EventSettings es = m_eventSettings.value(name); |
|
if (!es.enabled) return; |
|
|
|
auto it = m_sounds.find(name); |
|
if (it == m_sounds.end()) return; |
|
|
|
ma_sound* s = it->sound; |
|
float effectiveVolume = m_volume * es.volume; |
|
ma_sound_set_volume(s, effectiveVolume); |
|
ma_sound_stop(s); |
|
ma_sound_seek_to_pcm_frame(s, 0); |
|
ma_sound_start(s); |
|
} |
|
|
|
ma_context* SoundManager::context() { |
|
return m_context; |
|
} |
|
|
|
void SoundManager::playRawPcm(const int16_t* pcm, int sampleCount, int sampleRate, int channels) { |
|
if (!m_engine || !m_initialized) { |
|
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "SoundManager::playRawPcm: not initialized"); |
|
return; |
|
} |
|
stopCurrentPcm(); |
|
DEBUG_DEBUG(DEBUG_CATEGORY_DEBUG, "SoundManager::playRawPcm: %d samples %dHz %dch", sampleCount, sampleRate, channels); |
|
|
|
ma_audio_buffer_config bufCfg = ma_audio_buffer_config_init( |
|
ma_format_s16, (ma_uint32)channels, (ma_uint64)sampleCount, pcm, NULL); |
|
ma_audio_buffer* buf = new ma_audio_buffer; |
|
m_currentPcmBuffer = buf; |
|
ma_result r = ma_audio_buffer_init_copy(&bufCfg, buf); |
|
if (r != MA_SUCCESS) { |
|
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "SoundManager: audio_buffer_init_copy failed (%d)", r); |
|
delete buf; m_currentPcmBuffer = nullptr; |
|
return; |
|
} |
|
|
|
m_currentPcmSound = new ma_sound; |
|
r = ma_sound_init_from_data_source(m_engine, (ma_data_source*)buf, 0, nullptr, m_currentPcmSound); |
|
if (r != MA_SUCCESS) { |
|
DEBUG_WARN(DEBUG_CATEGORY_DEBUG, "SoundManager: PCM sound init failed (%d)", r); |
|
ma_audio_buffer_uninit(buf); delete buf; m_currentPcmBuffer = nullptr; |
|
delete m_currentPcmSound; m_currentPcmSound = nullptr; |
|
return; |
|
} |
|
|
|
ma_sound_set_volume(m_currentPcmSound, m_volume); |
|
ma_sound_start(m_currentPcmSound); |
|
} |
|
|
|
void SoundManager::pauseCurrentPcm() { |
|
if (m_currentPcmSound) ma_sound_stop(m_currentPcmSound); |
|
} |
|
|
|
void SoundManager::resumeCurrentPcm() { |
|
if (m_currentPcmSound) ma_sound_start(m_currentPcmSound); |
|
} |
|
|
|
void SoundManager::stopCurrentPcm() { |
|
if (m_currentPcmSound) { |
|
ma_sound_stop(m_currentPcmSound); |
|
delete m_currentPcmSound; m_currentPcmSound = nullptr; |
|
} |
|
if (m_currentPcmBuffer) { |
|
ma_audio_buffer* buf = (ma_audio_buffer*)m_currentPcmBuffer; |
|
ma_audio_buffer_uninit(buf); |
|
delete buf; |
|
m_currentPcmBuffer = nullptr; |
|
} |
|
} |
|
|
|
static QString decodeDeviceName(const char* name) { |
|
QString s = QString::fromUtf8(name); |
|
if (s.contains(QChar(0xFFFD))) |
|
s = QString::fromLocal8Bit(name); |
|
return s; |
|
} |
|
|
|
QVector<SoundManager::DeviceInfo> SoundManager::enumeratePlaybackDevices() { |
|
QVector<DeviceInfo> result; |
|
ma_context* ctx = instance()->m_context; |
|
if (!ctx) return result; |
|
|
|
ma_device_info* pDevices = nullptr; |
|
ma_uint32 count = 0; |
|
if (ma_context_get_devices(ctx, &pDevices, &count, nullptr, nullptr) != MA_SUCCESS) return result; |
|
for (ma_uint32 i = 0; i < count; i++) |
|
result.append({decodeDeviceName(pDevices[i].name), pDevices[i].isDefault != MA_FALSE}); |
|
return result; |
|
} |
|
|
|
QVector<SoundManager::DeviceInfo> SoundManager::enumerateCaptureDevices() { |
|
QVector<DeviceInfo> result; |
|
ma_context* ctx = instance()->m_context; |
|
if (!ctx) return result; |
|
|
|
ma_device_info* pDevices = nullptr; |
|
ma_uint32 count = 0; |
|
if (ma_context_get_devices(ctx, nullptr, nullptr, &pDevices, &count) != MA_SUCCESS) return result; |
|
for (ma_uint32 i = 0; i < count; i++) |
|
result.append({decodeDeviceName(pDevices[i].name), pDevices[i].isDefault != MA_FALSE}); |
|
return result; |
|
}
|
|
|