/******************************************************************************** * File: audio.hpp * Author: ppkantorski * Description: * This header defines the Audio class and related structures used for * handling sound playback within the Ultrahand Overlay. It provides interfaces * for loading, caching, and playing WAV audio through libnx's audout service, * along with basic sound type management and synchronization support. * * 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-2026 ppkantorski ********************************************************************************/ #pragma once #include #include #include #include #include #include #include #include "tsl_utils.hpp" namespace ult { class Audio { public: enum class SoundType : uint8_t { Navigate, Enter, Exit, Wall, On, Off, Settings, Move, Notification, Count }; struct CachedSound { // Compact raw PCM — native channel count, 16-bit, no volume applied. // This is the only per-sound allocation. No pre-baked stereo copy is kept. void* rawBuf = nullptr; uint32_t rawSize = 0; // actual data bytes uint32_t rawCap = 0; // allocated (aligned) bytes uint32_t sampleRate = 48000; // native rate read from WAV header bool isMono = false; }; static bool initialize(); static void exit(); static inline bool allSoundsExist() { for (const auto& path : m_soundPaths) if (!isFile(path)) return false; return true; } static void playSound(SoundType type); static inline void playNavigateSound() { playSound(SoundType::Navigate); } static inline void playEnterSound() { playSound(SoundType::Enter); } static inline void playExitSound() { playSound(SoundType::Exit); } static inline void playWallSound() { playSound(SoundType::Wall); } static inline void playOnSound() { playSound(SoundType::On); } static inline void playOffSound() { playSound(SoundType::Off); } static inline void playSettingsSound() { playSound(SoundType::Settings); } static inline void playMoveSound() { playSound(SoundType::Move); } static inline void playNotificationSound() { playSound(SoundType::Notification); } static void setMasterVolume(float volume); static void setEnabled(bool enabled); static bool isEnabled(); static bool reloadIfDockedChanged(); static void reloadAllSounds(); static void unloadAllSounds(const std::initializer_list& excludeSounds = {}); static std::mutex m_audioMutex; static bool m_initialized; private: static std::atomic m_enabled; static std::atomic m_masterVolumeFixed; // volume as 0–256 fixed-point static bool m_lastDockedState; static std::vector m_cachedSounds; // Single shared DMA playback buffer — sized to the largest sound's // 48 kHz stereo output. Reused on every playSound(); safe because audout // is always drained before the buffer is written. static void* m_playBuf; static uint32_t m_playBufCap; static AudioOutBuffer m_audoutBuf; inline static constexpr const char* m_soundPaths[static_cast(SoundType::Count)] = { "sdmc:/config/ultrahand/sounds/tick.wav", "sdmc:/config/ultrahand/sounds/enter.wav", "sdmc:/config/ultrahand/sounds/exit.wav", "sdmc:/config/ultrahand/sounds/wall.wav", "sdmc:/config/ultrahand/sounds/on.wav", "sdmc:/config/ultrahand/sounds/off.wav", "sdmc:/config/ultrahand/sounds/settings.wav", "sdmc:/config/ultrahand/sounds/move.wav", "sdmc:/config/ultrahand/sounds/notification.wav" }; // Loads WAV into rawBuf (16-bit, native channels, no volume applied). // Must be called under m_audioMutex. static bool loadSoundFromWav(SoundType type, const char* path); // Ensures m_playBuf is large enough for the largest loaded sound's // 48 kHz stereo output. Call after any load. Must hold m_audioMutex. static void growPlayBuf(); // Renders rawBuf → m_playBuf on demand: resample to 48 kHz if needed, // expand mono → stereo, apply current volume + dock attenuation. // Returns actual output byte count written, or 0 on error. // Must be called under m_audioMutex. static uint32_t renderToPlayBuf(const CachedSound& s); }; }