Audio update
This commit is contained in:
@ -57,6 +57,7 @@ set(TETRIS_SOURCES
|
||||
src/graphics/renderers/SyncLineRenderer.cpp
|
||||
src/graphics/renderers/UIRenderer.cpp
|
||||
src/audio/Audio.cpp
|
||||
src/audio/AudioManager.cpp
|
||||
src/renderer/SDLRenderer.cpp
|
||||
src/gameplay/effects/LineEffect.cpp
|
||||
src/audio/SoundEffect.cpp
|
||||
|
||||
@ -118,6 +118,7 @@ static bool decodeMP3(const std::string& path, std::vector<int16_t>& outPCM, int
|
||||
outCh = static_cast<int>(clientFormat.mChannelsPerFrame);
|
||||
return !outPCM.empty();
|
||||
}
|
||||
|
||||
#else
|
||||
static bool decodeMP3(const std::string& path, std::vector<int16_t>& outPCM, int& outRate, int& outCh){
|
||||
(void)outPCM; (void)outRate; (void)outCh; (void)path;
|
||||
@ -184,6 +185,8 @@ void Audio::skipToNextTrack(){
|
||||
void Audio::toggleMute(){ muted=!muted; }
|
||||
void Audio::setMuted(bool m){ muted=m; }
|
||||
|
||||
bool Audio::isMuted() const { return muted; }
|
||||
|
||||
void Audio::nextTrack(){
|
||||
if(tracks.empty()) { current = -1; return; }
|
||||
// Try every track once to find a decodable one
|
||||
|
||||
@ -32,29 +32,27 @@ public:
|
||||
void setSoundVolume(float volume) override;
|
||||
bool isMusicPlaying() const override;
|
||||
|
||||
// Existing Audio class methods
|
||||
bool init(); // initialize backend (MF on Windows)
|
||||
void addTrack(const std::string& path); // decode MP3 -> PCM16 stereo 44100
|
||||
void addTrackAsync(const std::string& path); // add track for background loading
|
||||
void startBackgroundLoading(); // start background thread for loading
|
||||
void waitForLoadingComplete(); // wait for all tracks to finish loading
|
||||
bool isLoadingComplete() const; // check if background loading is done
|
||||
int getLoadedTrackCount() const; // get number of tracks loaded so far
|
||||
void shuffle(); // randomize order
|
||||
void start(); // begin playback
|
||||
void skipToNextTrack(); // advance to the next music track
|
||||
void toggleMute();
|
||||
// Additional IAudioSystem methods (forwarded to concrete implementation)
|
||||
bool init() override;
|
||||
void shutdown() override;
|
||||
void addTrack(const std::string& path) override;
|
||||
void addTrackAsync(const std::string& path) override;
|
||||
void startBackgroundLoading() override;
|
||||
bool isLoadingComplete() const override;
|
||||
int getLoadedTrackCount() const override;
|
||||
void start() override;
|
||||
void skipToNextTrack() override;
|
||||
void shuffle() override;
|
||||
void toggleMute() override;
|
||||
bool isMuted() const override;
|
||||
void setMuted(bool m);
|
||||
bool isMuted() const { return muted; }
|
||||
void setMenuTrack(const std::string& path) override;
|
||||
void playMenuMusic() override;
|
||||
void playGameMusic() override;
|
||||
void playSfx(const std::vector<int16_t>& pcm, int channels, int rate, float volume) override;
|
||||
|
||||
// Menu music support
|
||||
void setMenuTrack(const std::string& path);
|
||||
void playMenuMusic();
|
||||
void playGameMusic();
|
||||
|
||||
// Queue a sound effect to mix over the music (pcm can be mono/stereo, any rate; will be converted)
|
||||
void playSfx(const std::vector<int16_t>& pcm, int channels, int rate, float volume);
|
||||
void shutdown();
|
||||
// Existing Audio class helper methods
|
||||
void waitForLoadingComplete(); // wait for all tracks to finish loading
|
||||
private:
|
||||
Audio()=default; ~Audio()=default; Audio(const Audio&)=delete; Audio& operator=(const Audio&)=delete;
|
||||
static void SDLCALL streamCallback(void* userdata, SDL_AudioStream* stream, int additional, int total);
|
||||
|
||||
15
src/audio/AudioManager.cpp
Normal file
15
src/audio/AudioManager.cpp
Normal file
@ -0,0 +1,15 @@
|
||||
#include "AudioManager.h"
|
||||
#include "Audio.h"
|
||||
|
||||
static IAudioSystem* g_audioSystem = nullptr;
|
||||
|
||||
IAudioSystem* AudioManager::get() {
|
||||
if (!g_audioSystem) {
|
||||
g_audioSystem = &Audio::instance();
|
||||
}
|
||||
return g_audioSystem;
|
||||
}
|
||||
|
||||
void AudioManager::set(IAudioSystem* sys) {
|
||||
g_audioSystem = sys;
|
||||
}
|
||||
11
src/audio/AudioManager.h
Normal file
11
src/audio/AudioManager.h
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "../core/interfaces/IAudioSystem.h"
|
||||
|
||||
class AudioManager {
|
||||
public:
|
||||
// Get the currently registered audio system (may return Audio::instance())
|
||||
static IAudioSystem* get();
|
||||
// Replace the audio system (for tests or different backends)
|
||||
static void set(IAudioSystem* sys);
|
||||
};
|
||||
@ -2,6 +2,7 @@
|
||||
#include "SoundEffect.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include "audio/Audio.h"
|
||||
#include "audio/AudioManager.h"
|
||||
#include <cstdio>
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
@ -93,7 +94,9 @@ void SimpleAudioPlayer::playSound(const std::vector<int16_t>& pcmData, int chann
|
||||
return;
|
||||
}
|
||||
// Route through shared Audio mixer so SFX always play over music
|
||||
Audio::instance().playSfx(pcmData, channels, sampleRate, volume);
|
||||
if (auto sys = AudioManager::get()) {
|
||||
sys->playSfx(pcmData, channels, sampleRate, volume);
|
||||
}
|
||||
}
|
||||
|
||||
bool SoundEffect::loadWAV(const std::string& filePath) {
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include "../interfaces/IInputHandler.h"
|
||||
#include <filesystem>
|
||||
#include "../../audio/Audio.h"
|
||||
#include "../../audio/AudioManager.h"
|
||||
#include "../../audio/SoundEffect.h"
|
||||
#include "../../persistence/Scores.h"
|
||||
#include "../../states/State.h"
|
||||
@ -267,7 +268,7 @@ void ApplicationManager::shutdown() {
|
||||
m_running = false;
|
||||
|
||||
// Stop audio systems before tearing down SDL to avoid aborts/asserts
|
||||
Audio::instance().shutdown();
|
||||
if (auto sys = ::AudioManager::get()) sys->shutdown();
|
||||
SoundEffectManager::instance().shutdown();
|
||||
|
||||
// Cleanup in reverse order of initialization
|
||||
@ -381,11 +382,11 @@ bool ApplicationManager::initializeManagers() {
|
||||
|
||||
// M: Toggle/mute music; start playback if unmuting and not started yet
|
||||
if (!consume && sc == SDL_SCANCODE_M) {
|
||||
Audio::instance().toggleMute();
|
||||
if (auto sys = ::AudioManager::get()) sys->toggleMute();
|
||||
m_musicEnabled = !m_musicEnabled;
|
||||
if (m_musicEnabled && !m_musicStarted && Audio::instance().getLoadedTrackCount() > 0) {
|
||||
Audio::instance().shuffle();
|
||||
Audio::instance().start();
|
||||
if (m_musicEnabled && !m_musicStarted && ::AudioManager::get() && ::AudioManager::get()->getLoadedTrackCount() > 0) {
|
||||
::AudioManager::get()->shuffle();
|
||||
::AudioManager::get()->start();
|
||||
m_musicStarted = true;
|
||||
}
|
||||
consume = true;
|
||||
@ -393,11 +394,7 @@ bool ApplicationManager::initializeManagers() {
|
||||
|
||||
// N: Skip to next song in the playlist (or restart menu track)
|
||||
if (!consume && sc == SDL_SCANCODE_N) {
|
||||
Audio::instance().skipToNextTrack();
|
||||
if (!m_musicStarted && Audio::instance().getLoadedTrackCount() > 0) {
|
||||
m_musicStarted = true;
|
||||
m_musicEnabled = true;
|
||||
}
|
||||
if (auto sys = ::AudioManager::get()) { sys->skipToNextTrack(); if (!m_musicStarted && sys->getLoadedTrackCount() > 0) { m_musicStarted = true; m_musicEnabled = true; } }
|
||||
consume = true;
|
||||
}
|
||||
|
||||
@ -515,13 +512,13 @@ void ApplicationManager::registerServices() {
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Registered IInputHandler service");
|
||||
}
|
||||
|
||||
// Register Audio system singleton
|
||||
auto& audioInstance = Audio::instance();
|
||||
auto audioPtr = std::shared_ptr<Audio>(&audioInstance, [](Audio*) {
|
||||
// Custom deleter that does nothing since Audio is a singleton
|
||||
});
|
||||
m_serviceContainer.registerSingleton<IAudioSystem>(audioPtr);
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Registered IAudioSystem service");
|
||||
// Register Audio system singleton (via AudioManager)
|
||||
IAudioSystem* audioInstance = AudioManager::get();
|
||||
if (audioInstance) {
|
||||
std::shared_ptr<IAudioSystem> audioPtr(audioInstance, [](IAudioSystem*){});
|
||||
m_serviceContainer.registerSingleton<IAudioSystem>(audioPtr);
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Registered IAudioSystem service");
|
||||
}
|
||||
|
||||
SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Service registration completed successfully");
|
||||
}
|
||||
@ -618,7 +615,7 @@ bool ApplicationManager::initializeGame() {
|
||||
// as lambdas that reference members here.
|
||||
|
||||
// Start background music loading similar to main.cpp: Audio init + file discovery
|
||||
Audio::instance().init();
|
||||
if (auto sys = ::AudioManager::get()) sys->init();
|
||||
// Discover available tracks (up to 100) and queue for background loading
|
||||
m_totalTracks = 0;
|
||||
std::vector<std::string> trackPaths;
|
||||
@ -634,15 +631,15 @@ bool ApplicationManager::initializeGame() {
|
||||
}
|
||||
m_totalTracks = static_cast<int>(trackPaths.size());
|
||||
for (const auto& path : trackPaths) {
|
||||
Audio::instance().addTrackAsync(path);
|
||||
if (auto sys = ::AudioManager::get()) sys->addTrackAsync(path);
|
||||
}
|
||||
if (m_totalTracks > 0) {
|
||||
Audio::instance().startBackgroundLoading();
|
||||
// Kick off playback now; Audio will pick a track once decoded.
|
||||
// Do not mark as started yet; we'll flip the flag once a track is actually loaded.
|
||||
if (m_musicEnabled) {
|
||||
Audio::instance().shuffle();
|
||||
Audio::instance().start();
|
||||
if (auto sys = ::AudioManager::get()) sys->startBackgroundLoading();
|
||||
// Kick off playback now; Audio will pick a track once decoded.
|
||||
// Do not mark as started yet; we'll flip the flag once a track is actually loaded.
|
||||
if (m_musicEnabled) {
|
||||
if (auto sys = ::AudioManager::get()) { sys->shuffle(); sys->start(); }
|
||||
m_musicStarted = true;
|
||||
}
|
||||
m_currentTrackLoading = 1; // mark started
|
||||
}
|
||||
@ -941,15 +938,15 @@ void ApplicationManager::setupStateHandlers() {
|
||||
// Start music as soon as at least one track has decoded (don’t wait for all)
|
||||
// Start music as soon as at least one track has decoded (don't wait for all)
|
||||
if (m_musicEnabled && !m_musicStarted) {
|
||||
if (Audio::instance().getLoadedTrackCount() > 0) {
|
||||
Audio::instance().shuffle();
|
||||
Audio::instance().start();
|
||||
m_musicStarted = true;
|
||||
if (auto sys = ::AudioManager::get()) {
|
||||
if (sys->getLoadedTrackCount() > 0) { sys->shuffle(); sys->start(); m_musicStarted = true; }
|
||||
}
|
||||
}
|
||||
// Track completion status for UI
|
||||
if (!m_musicLoaded && Audio::instance().isLoadingComplete()) {
|
||||
m_musicLoaded = true;
|
||||
if (!m_musicLoaded) {
|
||||
if (auto sys = ::AudioManager::get()) {
|
||||
if (sys->isLoadingComplete()) m_musicLoaded = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#include "AssetManager.h"
|
||||
#include "../../graphics/ui/Font.h"
|
||||
#include "../../audio/Audio.h"
|
||||
#include "../../audio/AudioManager.h"
|
||||
#include "../../audio/SoundEffect.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3_image/SDL_image.h>
|
||||
@ -40,7 +41,7 @@ bool AssetManager::initialize(SDL_Renderer* renderer) {
|
||||
m_renderer = renderer;
|
||||
|
||||
// Get references to singleton systems
|
||||
m_audioSystem = &Audio::instance();
|
||||
m_audioSystem = ::AudioManager::get();
|
||||
m_soundSystem = &SoundEffectManager::instance();
|
||||
|
||||
m_initialized = true;
|
||||
|
||||
@ -7,12 +7,12 @@
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include "../interfaces/IAssetLoader.h"
|
||||
#include "../interfaces/IAssetLoader.h"
|
||||
|
||||
// Forward declarations
|
||||
class FontAtlas;
|
||||
class Audio;
|
||||
class SoundEffectManager;
|
||||
class IAudioSystem;
|
||||
|
||||
/**
|
||||
* AssetManager - Centralized resource management following SOLID principles
|
||||
@ -121,7 +121,7 @@ private:
|
||||
|
||||
// System references
|
||||
SDL_Renderer* m_renderer;
|
||||
Audio* m_audioSystem; // Pointer to singleton
|
||||
IAudioSystem* m_audioSystem; // Pointer to audio system (IAudioSystem)
|
||||
SoundEffectManager* m_soundSystem; // Pointer to singleton
|
||||
|
||||
// Configuration
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
/**
|
||||
* @brief Abstract interface for audio system operations
|
||||
@ -52,4 +54,28 @@ public:
|
||||
* @return true if music is playing, false otherwise
|
||||
*/
|
||||
virtual bool isMusicPlaying() const = 0;
|
||||
|
||||
// Extended control methods used by the application
|
||||
virtual bool init() = 0;
|
||||
virtual void shutdown() = 0;
|
||||
|
||||
virtual void addTrack(const std::string& path) = 0;
|
||||
virtual void addTrackAsync(const std::string& path) = 0;
|
||||
virtual void startBackgroundLoading() = 0;
|
||||
virtual bool isLoadingComplete() const = 0;
|
||||
virtual int getLoadedTrackCount() const = 0;
|
||||
|
||||
virtual void start() = 0;
|
||||
virtual void skipToNextTrack() = 0;
|
||||
virtual void shuffle() = 0;
|
||||
|
||||
virtual void toggleMute() = 0;
|
||||
virtual bool isMuted() const = 0;
|
||||
|
||||
virtual void setMenuTrack(const std::string& path) = 0;
|
||||
virtual void playMenuMusic() = 0;
|
||||
virtual void playGameMusic() = 0;
|
||||
|
||||
// Low-level SFX path (raw PCM) used by internal SFX mixer
|
||||
virtual void playSfx(const std::vector<int16_t>& pcm, int channels, int rate, float volume) = 0;
|
||||
};
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include "audio/Audio.h"
|
||||
#include "audio/AudioManager.h"
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.14159265358979323846
|
||||
@ -266,6 +267,6 @@ void LineEffect::playLineClearSound(int lineCount) {
|
||||
const std::vector<int16_t>* sample = (lineCount == 4) ? &tetrisSample : &lineClearSample;
|
||||
if (sample && !sample->empty()) {
|
||||
// Mix via shared Audio device so it layers with music
|
||||
Audio::instance().playSfx(*sample, 2, 44100, (lineCount == 4) ? 0.9f : 0.7f);
|
||||
if (auto sys = ::AudioManager::get()) sys->playSfx(*sample, 2, 44100, (lineCount == 4) ? 0.9f : 0.7f);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include "audio/Audio.h"
|
||||
#include "audio/AudioManager.h"
|
||||
#include "gameplay/core/Game.h"
|
||||
|
||||
#ifndef M_PI
|
||||
@ -461,7 +462,7 @@ void LineEffect::playLineClearSound(int lineCount) {
|
||||
const std::vector<int16_t>* sample = (lineCount == 4) ? &tetrisSample : &lineClearSample;
|
||||
if (sample && !sample->empty()) {
|
||||
// Mix via shared Audio device so it layers with music
|
||||
Audio::instance().playSfx(*sample, 2, 44100, (lineCount == 4) ? 0.9f : 0.7f);
|
||||
if (auto sys = ::AudioManager::get()) sys->playSfx(*sample, 2, 44100, (lineCount == 4) ? 0.9f : 0.7f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
#include "../core/Settings.h"
|
||||
#include "../core/state/StateManager.h"
|
||||
#include "../audio/Audio.h"
|
||||
#include "../audio/AudioManager.h"
|
||||
#include "../audio/SoundEffect.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <SDL3/SDL_render.h>
|
||||
@ -180,7 +181,7 @@ void MenuState::showCoopSetupPanel(bool show, bool resumeMusic) {
|
||||
coopSetupStep = CoopSetupStep::ChoosePartner;
|
||||
// Resume menu music only when requested (ESC should pass resumeMusic=false)
|
||||
if (resumeMusic && ctx.musicEnabled && *ctx.musicEnabled) {
|
||||
Audio::instance().playMenuMusic();
|
||||
if (auto sys = ::AudioManager::get()) sys->playMenuMusic();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
#include "../core/state/StateManager.h"
|
||||
#include "../graphics/ui/Font.h"
|
||||
#include "../audio/Audio.h"
|
||||
#include "../audio/AudioManager.h"
|
||||
#include "../audio/SoundEffect.h"
|
||||
#include <SDL3/SDL.h>
|
||||
#include <algorithm>
|
||||
@ -220,7 +221,7 @@ void OptionsState::toggleFullscreen() {
|
||||
}
|
||||
|
||||
void OptionsState::toggleMusic() {
|
||||
Audio::instance().toggleMute();
|
||||
if (auto sys = ::AudioManager::get()) sys->toggleMute();
|
||||
// If muted, music is disabled. If not muted, music is enabled.
|
||||
// Note: Audio::instance().isMuted() returns true if muted.
|
||||
// But Audio class doesn't expose isMuted directly in header usually?
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
#include "../video/VideoPlayer.h"
|
||||
#include "../audio/Audio.h"
|
||||
#include "../audio/AudioManager.h"
|
||||
#include "../core/state/StateManager.h"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
@ -104,7 +105,7 @@ void VideoState::startAudioIfReady() {
|
||||
if (m_audioPcm.empty()) return;
|
||||
|
||||
// Use the existing audio output path (same device as music/SFX).
|
||||
Audio::instance().playSfx(m_audioPcm, m_audioChannels, m_audioRate, 1.0f);
|
||||
if (auto sys = ::AudioManager::get()) sys->playSfx(m_audioPcm, m_audioChannels, m_audioRate, 1.0f);
|
||||
m_audioStarted = true;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user