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..f1dd7e7 100644 --- a/include/SoundManager.h +++ b/include/SoundManager.h @@ -6,14 +6,23 @@ #include "ECS.h" #include "TextureManager.h" + +// enum SoundTypes +// { +// STEPS, +// THROW_EGG, +// }; -enum SoundTypes -{ - STEPS, - THROW_EGG, -}; +// class Game; 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 +31,40 @@ 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; - Mix_Chunk* loadSound(const char* fileName); - static void playSound(GameInternal* game, SoundTypes sound); + Mix_Music* loadMusic(const char* fileName); //!< Loads music from a file (mp3) + //! \returns a pointer to Mix_Music, which is added to a map in the AssetManager + //! \sa AssetManager::AddMusic(std::string id, const char* path) + Mix_Chunk* loadSound(const char* fileName); //!< Loads sound effects from a file (wav) + //! \returns a pointer to Mix_Chunk, which is added to a map in the AssetManager + //! \sa AssetManager::AddSound(std::string id, const char* path) + + static void playSound(GameInternal* game, std::string sound, bool canOverlap, int loops, int volume, int channel); //!< Plays 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 + static void playMusic(GameInternal* game, std::string sound, int loops, int volume, int ms); //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 5a490a8..5e23c78 100644 --- a/src/GameInternal.cpp +++ b/src/GameInternal.cpp @@ -155,6 +155,9 @@ void GameInternal::init(const char* title, int xpos, int ypos, int width, int he 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 // player1.setTeam(Entity::TeamLabel::BLUE); 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..3219c30 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) + throw std::runtime_error(std::string("Couldn't load music '") + fileName + "'"); + + this->music_cache.emplace(fileName, music); + + printf("Loaded music at '%s'\n", fileName); + + return music; +} + Mix_Chunk* SoundManager::loadSound(const char* fileName) { auto it = this->sound_cache.find(fileName); @@ -27,24 +47,82 @@ Mix_Chunk* SoundManager::loadSound(const char* fileName) return sound; } -void SoundManager::playSound(GameInternal* game, SoundTypes sound) +// TODO: using a string here is probably... a less than stellar method, figure out how to change this +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; + if (Mix_Playing(channel) != 0) + return; + } - 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); +}