diff --git a/assets/sound/background_music.mp3 b/assets/sound/background_music.mp3 new file mode 100644 index 0000000..f79cd6c Binary files /dev/null and b/assets/sound/background_music.mp3 differ diff --git a/include/AssetManager.h b/include/AssetManager.h index 4992dd2..be44516 100644 --- a/include/AssetManager.h +++ b/include/AssetManager.h @@ -36,12 +36,16 @@ public: // sound management void addSoundEffect(std::string id, const char* path); + void addMusic(std::string id, const char* path); + SDL_Texture* getTexture(std::string id); Mix_Chunk* getSound(std::string id); + Mix_Music* getMusic(std::string id); private: Manager* man; std::map textures; std::map soundEffects; + std::map music; }; diff --git a/include/Constants.h b/include/Constants.h index 8237a71..b54b4ce 100644 --- a/include/Constants.h +++ b/include/Constants.h @@ -24,3 +24,8 @@ constexpr int MAP_SIZE_Y = 20; constexpr int SPAWN_ATTEMPTS = 20; +constexpr int PLAY_LOOPED = -1; +constexpr int PLAY_ONCE = 0; + +constexpr int MAX_VOLUME = 128; + diff --git a/include/SoundManager.h b/include/SoundManager.h index bcfc0ac..8663d34 100644 --- a/include/SoundManager.h +++ b/include/SoundManager.h @@ -6,14 +6,15 @@ #include "ECS.h" #include "TextureManager.h" - -enum SoundTypes -{ - STEPS, - THROW_EGG, -}; - + class GameInternal; + +/*! + * + * \brief Handles music and sound. + * \details SoundManager handles loading in music and sound effects from files, playing music and sound effects and toggling the audio volume. + * + */ class SoundManager { public: @@ -22,14 +23,55 @@ class SoundManager for (auto& it : this->sound_cache) { Mix_FreeChunk(it.second); } + + for (auto& it : this->music_cache) { + Mix_FreeMusic(it.second); + } } SoundManager(SoundManager const&) = delete; void operator=(SoundManager const&) = delete; + std::map music_cache; std::map sound_cache; + /*! + * \brief Loads music from a file (mp3) + * \returns a pointer to Mix_Music + * \sa AssetManager::AddMusic(std::string id, const char* path) + */ + Mix_Music* loadMusic(const char* fileName); + /*! + * \brief Loads sound effects from a file (wav) + * \returns a pointer to Mix_Chunk + * \sa AssetManager::AddSound(std::string id, const char* path) + */ Mix_Chunk* loadSound(const char* fileName); - static void playSound(GameInternal* game, SoundTypes sound); + + /*! + * \brief Handles playing of sound effects + * + * Handles if sounds can overlap, how often they can loop, as well as the volume at which the specified sound effect should play + * and on which channel the soundeffect should play. + */ + static void playSound(GameInternal* game, std::string sound, bool canOverlap, int loops, int volume, int channel); + /*! + * \brief Handles playing of music + * + * Handles how often track can loop, as well as the volume at which the specified track should play and if it fades in. + */ + static void playMusic(GameInternal* game, std::string sound, int loops, int volume, int ms); + + static void setSoundVolume(int volume, int channel); //!< Volume handling for sound effects (either all or on a specific channel) + static void setMusicVolume(int volume); //!< Volume handling for music track + + static void pauseSound(int channel); //!< Handles pausing sound effects (either all or on a specific channel) + static void pauseMusic(); //!< Handles pausing music track + + static void restartSound(int channel); //!< Handles resuming sound effects (either all or on a specific channel) + static void restartMusic(); //!< Handles resuming music track + + static void fadeOutMusic(int ms); //!< Handles fading out a music track + private: }; \ No newline at end of file diff --git a/src/AssetManager.cpp b/src/AssetManager.cpp index d737a5d..fb45af7 100644 --- a/src/AssetManager.cpp +++ b/src/AssetManager.cpp @@ -28,6 +28,11 @@ void AssetManager::addSoundEffect(std::string id, const char* path) soundEffects.emplace(id, this->man->getGame()->soundManager->loadSound(path)); } +void AssetManager::addMusic(std::string id, const char* path) +{ + music.emplace(id, this->man->getGame()->soundManager->loadMusic(path)); +} + SDL_Texture* AssetManager::getTexture(std::string id) { return textures.at(id); } @@ -36,6 +41,11 @@ Mix_Chunk* AssetManager::getSound(std::string id) { return soundEffects.at(id); } +Mix_Music* AssetManager::getMusic(std::string id) +{ + return music.at(id); +} + void AssetManager::createProjectile(Vector2D pos, Vector2D velocity, int scale, int range, int speed, const char* texturePath, Entity::TeamLabel teamLabel) { auto& projectile(man->addEntity()); diff --git a/src/GameInternal.cpp b/src/GameInternal.cpp index 53337e5..d590eec 100644 --- a/src/GameInternal.cpp +++ b/src/GameInternal.cpp @@ -152,8 +152,11 @@ void GameInternal::init(const char* title, int xpos, int ypos, int width, int he assets->addTexture("egg", "assets/egg.png"); */ // loading sounds - assets->addSoundEffect("throw_egg", "assets/sound/throw_egg.wav"); - assets->addSoundEffect("steps", "assets/sound/steps.wav"); + // assets->addSoundEffect("throw_egg", "assets/sound/throw_egg.wav"); + // assets->addSoundEffect("steps", "assets/sound/steps.wav"); + + // loading music + // assets->addMusic("background_music", "assets/sound/background_music.mp3"); //ecs implementation diff --git a/src/ProjectileComponent.cpp b/src/ProjectileComponent.cpp index 75c711b..5399b54 100644 --- a/src/ProjectileComponent.cpp +++ b/src/ProjectileComponent.cpp @@ -14,7 +14,7 @@ void ProjectileComponent::init() { transformComponent = &entity->getComponent(); transformComponent->direction = direction; - SoundManager::playSound(this->entity->getManager().getGame(), THROW_EGG); + SoundManager::playSound(this->entity->getManager().getGame(), "throw_egg", true, PLAY_ONCE, MAX_VOLUME, -1); } void ProjectileComponent::update() diff --git a/src/SoundManager.cpp b/src/SoundManager.cpp index b8575c8..2954b9f 100644 --- a/src/SoundManager.cpp +++ b/src/SoundManager.cpp @@ -7,6 +7,26 @@ #include "GameInternal.h" #include "AssetManager.h" +Mix_Music* SoundManager::loadMusic(const char* fileName) +{ + auto it = this->music_cache.find(fileName); + + if (it != this->music_cache.end()) { + return it->second; + } + + auto music = Mix_LoadMUS(fileName); + + if (music == NULL) + std::cerr << "Couldn't load music '" << fileName << "'" << std::endl; + + this->music_cache.emplace(fileName, music); + + std::cout << "Loaded music at " << fileName << std::endl; + + return music; +} + Mix_Chunk* SoundManager::loadSound(const char* fileName) { auto it = this->sound_cache.find(fileName); @@ -18,33 +38,98 @@ Mix_Chunk* SoundManager::loadSound(const char* fileName) auto sound = Mix_LoadWAV(fileName); if (sound == NULL) - throw std::runtime_error(std::string("Couldn't load sound '") + fileName + "'"); + std::cerr << "Couldn't load sound '" << fileName << "'" << std::endl; this->sound_cache.emplace(fileName, sound); - printf("Loaded sound at '%s'\n", fileName); + std::cout << "Loaded sound at " << fileName << std::endl; return sound; } -void SoundManager::playSound(GameInternal* game, SoundTypes sound) +void SoundManager::playSound(GameInternal* game, std::string sound, bool canOverlap, int loops, int volume, int channel) { - switch (sound) + if(!canOverlap) { - case SoundTypes::STEPS: - if (Mix_Playing(-1) != 0) - break; + // dev needs to specify a channel for this check to work, if they set it to -1 and let sdl pick the first available + // channel mix_getchunk() won't work + if (Mix_Playing(channel) != 0 && + Mix_GetChunk(channel) == game->assets->getSound(sound) && + channel != -1) + { + return; + } + + Mix_HaltChannel(channel); + } - if (Mix_PlayChannel(-1, game->assets->getSound("steps"), 0) == -1) { - std::cerr << "Error playing sound 'steps': " << Mix_GetError() << std::endl; - } - - break; + if(Mix_VolumeChunk(game->assets->getSound(sound), volume) == -1) + { + std::cerr << "Error adjusting volume: " << Mix_GetError() << std::endl; + } - case SoundTypes::THROW_EGG: - if (Mix_PlayChannel(-1, game->assets->getSound("throw_egg"), 0) == -1) { - std::cerr << "Error playing sound 'throw_egg': " << Mix_GetError() << std::endl; - } - break; + if (Mix_PlayChannel(channel, game->assets->getSound(sound), loops) == -1) + { + std::cerr << "Error playing sound '" << sound << "': " << Mix_GetError() << std::endl; } } + +void SoundManager::playMusic(GameInternal* game, std::string music, int loops, int volume, int ms) +{ + if (Mix_PlayingMusic() != 0 || Mix_Fading() == Mix_Fading::MIX_FADING_IN) + return; + + if(ms > 0) + { + Mix_FadeInMusic(game->assets->getMusic(music), loops, ms); + return; + } + + if(Mix_VolumeMusic(volume) == -1) + { + std::cerr << "Error adjusting volume: " << Mix_GetError() << std::endl; + } + + if (Mix_PlayMusic(game->assets->getMusic(music), loops) == -1) + { + std::cerr << "Error playing music '" << music << "': " << Mix_GetError() << std::endl; + } +} + +void SoundManager::setSoundVolume(int volume, int channel) +{ + Mix_Volume(channel, volume); +} + +void SoundManager::setMusicVolume(int volume) +{ + Mix_VolumeMusic(volume); +} + +void SoundManager::pauseSound(int channel) +{ + Mix_Pause(channel); +} + +void SoundManager::pauseMusic() +{ + Mix_PauseMusic(); +} + +void SoundManager::restartSound(int channel) +{ + Mix_Resume(channel); +} + +void SoundManager::restartMusic() +{ + Mix_ResumeMusic(); +} + +void SoundManager::fadeOutMusic(int ms) +{ + if(Mix_Fading() == Mix_Fading::MIX_FADING_OUT) + return; + + Mix_FadeOutMusic(ms); +}