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
32bool IsSoundPlaying(const char *name, const C4Object *obj);
33void SoundLevel(const char *name, C4Object *obj, std::int32_t iLevel);
34bool StartSoundEffect(const char *name, bool loop = false, std::int32_t volume = 100,
35 C4Object *obj = nullptr, std::int32_t falloffDistance = 0);
36void StartSoundEffectAt(const char *name, std::int32_t x, std::int32_t y);
37void StopSoundEffect(const char *name, const C4Object *obj);
38
39class C4SoundSystem
40{
41public:
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
60private:
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