v0.18
This commit is contained in:
@@ -0,0 +1,312 @@
|
||||
/********************************************************************************
|
||||
* File: audio.cpp
|
||||
* Author: ppkantorski
|
||||
* Description:
|
||||
* Memory-optimized version with reduced allocation overhead and chunked I/O.
|
||||
* Key changes:
|
||||
* - Eliminated temporary vector allocations (saves 50% memory during load)
|
||||
* - Chunked file reading to reduce peak memory usage
|
||||
* - Reduced alignment padding (saves ~3-4KB per sound)
|
||||
* - Added lazy loading option via unloadAllSounds()
|
||||
*
|
||||
* For the latest updates and contributions, visit the project's GitHub repository.
|
||||
* (GitHub Repository: https://github.com/ppkantorski/Ultrahand-Overlay)
|
||||
*
|
||||
* Note: Please be aware that this notice cannot be altered or removed. It is a part
|
||||
* of the project's documentation and must remain intact.
|
||||
*
|
||||
* Licensed under both GPLv2 and CC-BY-4.0
|
||||
* Copyright (c) 2025 ppkantorski
|
||||
********************************************************************************/
|
||||
|
||||
#include "audio.hpp"
|
||||
|
||||
namespace ult {
|
||||
bool Audio::m_initialized = false;
|
||||
std::atomic<bool> Audio::m_enabled{true};
|
||||
float Audio::m_masterVolume = 0.6f;
|
||||
bool Audio::m_lastDockedState = false;
|
||||
std::vector<Audio::CachedSound> Audio::m_cachedSounds;
|
||||
std::mutex Audio::m_audioMutex;
|
||||
|
||||
bool Audio::initialize() {
|
||||
std::lock_guard<std::mutex> lock(m_audioMutex);
|
||||
|
||||
if (m_initialized) return true;
|
||||
|
||||
if (R_FAILED(audoutInitialize()) || R_FAILED(audoutStartAudioOut())) {
|
||||
audoutExit();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_initialized = true;
|
||||
m_cachedSounds.resize(static_cast<uint32_t>(SoundType::Count));
|
||||
m_lastDockedState = ult::consoleIsDocked();
|
||||
reloadAllSounds();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Audio::exit() {
|
||||
std::lock_guard<std::mutex> lock(m_audioMutex);
|
||||
|
||||
// Free all cached sound buffers
|
||||
for (auto& cached : m_cachedSounds) {
|
||||
if (cached.buffer) {
|
||||
free(cached.buffer);
|
||||
cached.buffer = nullptr;
|
||||
}
|
||||
cached.bufferSize = 0;
|
||||
cached.dataSize = 0;
|
||||
}
|
||||
|
||||
if (m_initialized) {
|
||||
audoutStopAudioOut();
|
||||
audoutExit();
|
||||
m_initialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Audio::reloadAllSounds() {
|
||||
for (uint32_t i = 0; i < static_cast<uint32_t>(SoundType::Count); ++i) {
|
||||
loadSoundFromWav(static_cast<SoundType>(i), m_soundPaths[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void Audio::unloadAllSounds(const std::initializer_list<SoundType>& excludeSounds) {
|
||||
std::lock_guard<std::mutex> lock(m_audioMutex);
|
||||
if (!m_initialized) return;
|
||||
|
||||
for (uint32_t i = 0; i < m_cachedSounds.size(); ++i) {
|
||||
SoundType current = static_cast<SoundType>(i);
|
||||
|
||||
// Skip if this sound is in the exclude list
|
||||
if (std::find(excludeSounds.begin(), excludeSounds.end(), current) != excludeSounds.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& cached = m_cachedSounds[i];
|
||||
if (cached.buffer) {
|
||||
free(cached.buffer);
|
||||
cached.buffer = nullptr;
|
||||
}
|
||||
cached.bufferSize = 0;
|
||||
cached.dataSize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool Audio::reloadIfDockedChanged() {
|
||||
if (!m_initialized) return false;
|
||||
|
||||
const bool currentDocked = ult::consoleIsDocked();
|
||||
if (currentDocked == m_lastDockedState) return false;
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_audioMutex);
|
||||
m_lastDockedState = currentDocked;
|
||||
reloadAllSounds();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Audio::loadSoundFromWav(SoundType type, const char* path) {
|
||||
const uint32_t idx = static_cast<uint32_t>(type);
|
||||
if (!m_initialized || idx >= static_cast<uint32_t>(SoundType::Count)) return false;
|
||||
|
||||
// Free existing buffer
|
||||
free(m_cachedSounds[idx].buffer);
|
||||
m_cachedSounds[idx] = { nullptr, 0, 0 };
|
||||
|
||||
FILE* f = fopen(path, "rb");
|
||||
if (!f) return false;
|
||||
|
||||
// Parse WAV header
|
||||
char hdr[12];
|
||||
if (fread(hdr, 1, 12, f) != 12 || memcmp(hdr, "RIFF", 4) || memcmp(hdr + 8, "WAVE", 4)) {
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
u16 fmt = 0, ch = 0, bits = 0;
|
||||
u32 rate = 0, dSize = 0;
|
||||
long dPos = 0;
|
||||
|
||||
// Find fmt and data chunks
|
||||
while (fread(hdr, 1, 8, f) == 8) {
|
||||
const u32 sz = *(u32*)(hdr + 4);
|
||||
if (!memcmp(hdr, "fmt ", 4)) {
|
||||
fread(&fmt, 2, 1, f);
|
||||
fread(&ch, 2, 1, f);
|
||||
fread(&rate, 4, 1, f);
|
||||
fseek(f, 6, SEEK_CUR);
|
||||
fread(&bits, 2, 1, f);
|
||||
fseek(f, sz - 16, SEEK_CUR);
|
||||
} else if (!memcmp(hdr, "data", 4)) {
|
||||
dSize = sz;
|
||||
dPos = ftell(f);
|
||||
break;
|
||||
} else {
|
||||
fseek(f, sz, SEEK_CUR);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate format
|
||||
if (!dSize || fmt != 1 || ch == 0 || ch > 2 || (bits != 8 && bits != 16)) {
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calculate buffer sizes
|
||||
// Note: audout REQUIRES stereo (2 channels), so we must duplicate mono
|
||||
const bool mono = (ch == 1);
|
||||
const uint32_t inSamples = dSize / (bits / 8);
|
||||
const uint32_t outSamples = mono ? inSamples * 2 : inSamples; // Duplicate mono to stereo
|
||||
const uint32_t outSize = outSamples * 2; // 16-bit samples
|
||||
|
||||
// Use smaller alignment to reduce waste (256 bytes instead of 4KB)
|
||||
const uint32_t align = 0x100;
|
||||
const uint32_t bufSize = (outSize + align - 1) & ~(align - 1);
|
||||
|
||||
// Allocate output buffer
|
||||
void* buf = aligned_alloc(align, bufSize);
|
||||
if (!buf) {
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
fseek(f, dPos, SEEK_SET);
|
||||
s16* out = (s16*)buf;
|
||||
|
||||
// Calculate volume scaling
|
||||
float effectiveVolume = m_masterVolume;
|
||||
if (m_lastDockedState) {
|
||||
effectiveVolume *= 0.5f;
|
||||
}
|
||||
const float scale = std::clamp(effectiveVolume, 0.0f, 1.0f);
|
||||
|
||||
// Process audio in chunks to minimize memory usage
|
||||
// This eliminates the need for temporary vectors
|
||||
constexpr uint32_t CHUNK_SIZE = 512;
|
||||
|
||||
if (bits == 8) {
|
||||
// 8-bit audio: read and convert in chunks
|
||||
const int32_t scaleInt = static_cast<int32_t>(scale * 256.0f);
|
||||
u8 chunk[CHUNK_SIZE];
|
||||
uint32_t remaining = inSamples;
|
||||
uint32_t outIdx = 0;
|
||||
|
||||
while (remaining > 0) {
|
||||
const uint32_t toRead = std::min(remaining, CHUNK_SIZE);
|
||||
if (fread(chunk, 1, toRead, f) != toRead) {
|
||||
free(buf);
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < toRead; ++i) {
|
||||
const s16 sample = static_cast<s16>((chunk[i] - 128) * scaleInt);
|
||||
if (mono) {
|
||||
// Duplicate to both L and R channels for stereo output
|
||||
out[outIdx++] = sample; // Left
|
||||
out[outIdx++] = sample; // Right
|
||||
} else {
|
||||
out[outIdx++] = sample;
|
||||
}
|
||||
}
|
||||
remaining -= toRead;
|
||||
}
|
||||
} else {
|
||||
// 16-bit audio: read and convert in chunks
|
||||
s16 chunk[CHUNK_SIZE];
|
||||
uint32_t remaining = inSamples;
|
||||
uint32_t outIdx = 0;
|
||||
|
||||
while (remaining > 0) {
|
||||
const uint32_t toRead = std::min(remaining, CHUNK_SIZE);
|
||||
if (fread(chunk, sizeof(s16), toRead, f) != toRead) {
|
||||
free(buf);
|
||||
fclose(f);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < toRead; ++i) {
|
||||
const s16 sample = static_cast<s16>(chunk[i] * scale);
|
||||
if (mono) {
|
||||
// Duplicate to both L and R channels for stereo output
|
||||
out[outIdx++] = sample; // Left
|
||||
out[outIdx++] = sample; // Right
|
||||
} else {
|
||||
out[outIdx++] = sample;
|
||||
}
|
||||
}
|
||||
remaining -= toRead;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
|
||||
// Zero-fill any padding
|
||||
if (outSize < bufSize) {
|
||||
memset((u8*)buf + outSize, 0, bufSize - outSize);
|
||||
}
|
||||
|
||||
m_cachedSounds[idx] = { buf, bufSize, outSize };
|
||||
return true;
|
||||
}
|
||||
|
||||
void Audio::playSound(SoundType type) {
|
||||
// Lock-free check - SAFE with atomic
|
||||
if (!m_enabled.load(std::memory_order_relaxed)) return;
|
||||
|
||||
const uint32_t idx = static_cast<uint32_t>(type);
|
||||
if (idx >= static_cast<uint32_t>(SoundType::Count)) return;
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_audioMutex);
|
||||
|
||||
// Check again under lock
|
||||
if (!m_initialized) return;
|
||||
|
||||
auto& cached = m_cachedSounds[idx];
|
||||
if (!cached.buffer) return;
|
||||
|
||||
// Release any finished buffers
|
||||
AudioOutBuffer* releasedBuffers = nullptr;
|
||||
u32 releasedCount = 0;
|
||||
audoutGetReleasedAudioOutBuffer(&releasedBuffers, &releasedCount);
|
||||
|
||||
// Static buffer is safe with mutex protection
|
||||
static AudioOutBuffer audioBuffer = {};
|
||||
audioBuffer = {};
|
||||
audioBuffer.buffer = cached.buffer;
|
||||
audioBuffer.buffer_size = cached.bufferSize;
|
||||
audioBuffer.data_size = cached.dataSize;
|
||||
audioBuffer.data_offset = 0;
|
||||
audioBuffer.next = nullptr;
|
||||
|
||||
AudioOutBuffer* rel = nullptr;
|
||||
audoutPlayBuffer(&audioBuffer, &rel);
|
||||
}
|
||||
|
||||
void Audio::setMasterVolume(float v) {
|
||||
std::lock_guard<std::mutex> lock(m_audioMutex);
|
||||
m_masterVolume = std::clamp(v, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
void Audio::setEnabled(bool e) {
|
||||
m_enabled.store(e, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
bool Audio::isEnabled() {
|
||||
return m_enabled.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
//bool Audio::isDocked() {
|
||||
// Result rc = apmInitialize();
|
||||
// if (R_FAILED(rc)) return false;
|
||||
//
|
||||
// ApmPerformanceMode perfMode = ApmPerformanceMode_Invalid;
|
||||
// rc = apmGetPerformanceMode(&perfMode);
|
||||
// apmExit();
|
||||
//
|
||||
// return R_SUCCEEDED(rc) && (perfMode == ApmPerformanceMode_Boost);
|
||||
//}
|
||||
}
|
||||
Reference in New Issue
Block a user