| 1 | /* |
| 2 | * LegacyClonk |
| 3 | * |
| 4 | * Copyright (c) 1998-2000, Matthes Bender (RedWolf Design) |
| 5 | * Copyright (c) 2017-2022, The LegacyClonk Team and contributors |
| 6 | * |
| 7 | * Distributed under the terms of the ISC license; see accompanying file |
| 8 | * "COPYING" for details. |
| 9 | * |
| 10 | * "Clonk" is a registered trademark of Matthes Bender, used with permission. |
| 11 | * See accompanying file "TRADEMARK" for details. |
| 12 | * |
| 13 | * To redistribute this file separately, substitute the full license texts |
| 14 | * for the above references. |
| 15 | */ |
| 16 | |
| 17 | // Handles the sound bank and plays effects. |
| 18 | |
| 19 | #pragma once |
| 20 | |
| 21 | #include <C4AudioSystem.h> |
| 22 | #include <C4Object.h> |
| 23 | |
| 24 | #include <chrono> |
| 25 | #include <cstddef> |
| 26 | #include <cstdint> |
| 27 | #include <list> |
| 28 | #include <optional> |
| 29 | #include <string> |
| 30 | #include <variant> |
| 31 | |
| 32 | bool IsSoundPlaying(const char *name, const C4Object *obj); |
| 33 | void SoundLevel(const char *name, C4Object *obj, std::int32_t iLevel); |
| 34 | bool StartSoundEffect(const char *name, bool loop = false, std::int32_t volume = 100, |
| 35 | C4Object *obj = nullptr, std::int32_t falloffDistance = 0); |
| 36 | void StartSoundEffectAt(const char *name, std::int32_t x, std::int32_t y); |
| 37 | void StopSoundEffect(const char *name, const C4Object *obj); |
| 38 | |
| 39 | class C4SoundSystem |
| 40 | { |
| 41 | public: |
| 42 | static constexpr std::int32_t AudibilityRadius = 700; |
| 43 | static constexpr std::int32_t NearSoundRadius = 50; |
| 44 | |
| 45 | C4SoundSystem(); |
| 46 | C4SoundSystem(const C4SoundSystem &) = delete; |
| 47 | C4SoundSystem(C4SoundSystem &&) = delete; |
| 48 | ~C4SoundSystem() = default; |
| 49 | C4SoundSystem &operator=(const C4SoundSystem &) = delete; |
| 50 | C4SoundSystem &operator=(C4SoundSystem &&) = delete; |
| 51 | |
| 52 | // Detaches the specified object from all sound instances. |
| 53 | void ClearPointers(const C4Object *obj); |
| 54 | void Execute(); |
| 55 | // Load sounds from the specified folder |
| 56 | void LoadEffects(C4Group &group); |
| 57 | // Call whenever the user wants to toggle sound playback |
| 58 | bool ToggleOnOff(); |
| 59 | |
| 60 | private: |
| 61 | struct Instance; |
| 62 | |
| 63 | struct Sample |
| 64 | { |
| 65 | const std::string name; |
| 66 | const std::unique_ptr<C4AudioSystem::SoundFile> sample; |
| 67 | const std::uint32_t duration; |
| 68 | std::list<Instance> instances; |
| 69 | |
| 70 | Sample(const char *const name, const void *const buf, const std::size_t size); |
| 71 | Sample(const Sample &) = delete; |
| 72 | Sample(Sample &&) = delete; |
| 73 | ~Sample() = default; |
| 74 | Sample &operator=(const Sample &) = delete; |
| 75 | Sample &operator=(Sample &&) = delete; |
| 76 | |
| 77 | void Execute(); |
| 78 | }; |
| 79 | |
| 80 | struct Instance |
| 81 | { |
| 82 | struct ObjPos |
| 83 | { |
| 84 | const int32_t x; |
| 85 | const int32_t y; |
| 86 | |
| 87 | ObjPos() = delete; |
| 88 | ObjPos(const C4Object &obj) : x{obj.x}, y{obj.y} {} |
| 89 | ObjPos(const ObjPos &) = delete; |
| 90 | ObjPos(ObjPos &&) = delete; |
| 91 | ~ObjPos() = default; |
| 92 | ObjPos &operator=(const ObjPos &) = delete; |
| 93 | ObjPos &operator=(ObjPos &&) = delete; |
| 94 | }; |
| 95 | |
| 96 | Instance(Sample &sample, bool loop, std::int32_t volume, |
| 97 | C4Object *obj, std::int32_t falloffDistance) |
| 98 | : sample{sample}, loop{loop}, volume{volume}, |
| 99 | obj{obj}, falloffDistance{falloffDistance}, |
| 100 | startTime{std::chrono::steady_clock::now()} {} |
| 101 | Instance(const Instance &) = delete; |
| 102 | Instance(Instance &&) = delete; |
| 103 | ~Instance() = default; |
| 104 | Instance &operator=(const Instance &) = delete; |
| 105 | Instance &operator=(Instance &&) = delete; |
| 106 | |
| 107 | Sample &sample; |
| 108 | std::unique_ptr<C4AudioSystem::SoundChannel> channel; |
| 109 | const bool loop; |
| 110 | std::int32_t volume, pan{0}; |
| 111 | std::variant<C4Object *, const ObjPos> obj; |
| 112 | const std::int32_t falloffDistance; |
| 113 | const std::chrono::time_point<std::chrono::steady_clock> startTime; |
| 114 | |
| 115 | // Returns false if this instance should be removed. |
| 116 | bool DetachObj(); |
| 117 | // Returns false if this instance should be removed. |
| 118 | bool Execute(bool justStarted = false); |
| 119 | C4Object *GetObj() const; |
| 120 | // Estimates playback position (milliseconds) |
| 121 | std::uint32_t GetPlaybackPosition() const; |
| 122 | bool IsNear(const C4Object &obj) const; |
| 123 | }; |
| 124 | |
| 125 | static constexpr std::int32_t MaxSoundInstances = 20; |
| 126 | std::list<Sample> samples; |
| 127 | |
| 128 | // Returns a sound instance that matches the specified name and object. |
| 129 | std::optional<decltype(Sample::instances)::iterator> FindInst( |
| 130 | const char *wildcard, const C4Object *obj); |
| 131 | // Returns a reference to the "sound enabled" config entry of the current game mode |
| 132 | static bool &GetCfgSoundEnabled(); |
| 133 | static void GetVolumeByPos(std::int32_t x, std::int32_t y, |
| 134 | std::int32_t &volume, std::int32_t &pan); |
| 135 | Instance *NewInstance(const char *filename, bool loop, |
| 136 | std::int32_t volume, std::int32_t pan, C4Object *obj, std::int32_t falloffDistance); |
| 137 | // Adds default file extension if missing and replaces "*" with "?" |
| 138 | static std::string PrepareFilename(const char *filename); |
| 139 | |
| 140 | friend bool IsSoundPlaying(const char *, const C4Object *); |
| 141 | friend void SoundLevel(const char *, C4Object *, std::int32_t); |
| 142 | friend bool StartSoundEffect(const char *, bool, std::int32_t, C4Object *, std::int32_t); |
| 143 | friend void StartSoundEffectAt(const char *, std::int32_t, std::int32_t); |
| 144 | friend void StopSoundEffect(const char *, const C4Object *); |
| 145 | }; |
| 146 | |