Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
UDSoundRuntime.cpp
Go to the documentation of this file.
1#include "UDSoundRuntime.hpp"
2
3#include <algorithm>
4
5namespace monopoly::udsound
6{
7 namespace
8 {
9 [[nodiscard]] constexpr data::DataId mainData(data::DataTag tag) noexcept
11 [[nodiscard]] constexpr data::DataId threeDData(data::DataTag tag) noexcept
13 [[nodiscard]] constexpr audio::PlaybackKey tokenVoiceKey(std::uint8_t token) noexcept
14 { return {audio::PlaybackDomain::Voice, static_cast<std::uint64_t>(token) + 1u}; }
15
16 inline constexpr audio::PlaybackKey PennybagsKey{
18 inline constexpr audio::PlaybackKey WarningKey{audio::PlaybackDomain::Interface, 1};
19 inline constexpr audio::PlaybackKey ClickKey{audio::PlaybackDomain::Interface, 2};
20 inline constexpr audio::PlaybackKey CashUpKey{audio::PlaybackDomain::Interface, 3};
21 inline constexpr audio::PlaybackKey CashDownKey{audio::PlaybackDomain::Interface, 4};
22 inline constexpr audio::PlaybackKey BuildKey{audio::PlaybackDomain::Interface, 5};
23 inline constexpr audio::PlaybackKey UnbuildKey{audio::PlaybackDomain::Interface, 6};
24 inline constexpr audio::PlaybackKey SirenKey{audio::PlaybackDomain::Interface, 7};
25 inline constexpr audio::PlaybackKey SaveFailureKey{audio::PlaybackDomain::Interface, 8};
26 inline constexpr audio::PlaybackKey MusicKey{audio::PlaybackDomain::Music, 1};
27 // C_ArtLib.h:525 initializes sounds without SetVolume at 32 percent.
28 // These direct audio helpers bypass that sequencer initialization, so
29 // carry the same per-sound gain into SDL (no global volume adjustment).
30 inline constexpr float DefaultEffectGain = 0.32F;
31 inline constexpr float PennybagsGain = 0.70F;
32 }
33 std::expected<void, std::string> Runtime::warning(audio::Runtime& audio)
35 std::expected<void, std::string> Runtime::click(audio::Runtime& audio)
36 { return audio.play(ClickKey, mainData(ClickTag), 0.25F); }
37 std::expected<void, std::string> Runtime::build(audio::Runtime& audio)
38 { return audio.play(BuildKey, mainData(BuildTag), DefaultEffectGain); }
39 std::expected<void, std::string> Runtime::unbuild(audio::Runtime& audio)
41 std::expected<void, std::string> Runtime::saveFailure(audio::Runtime& audio)
43 std::expected<void, std::string> Runtime::siren(
44 audio::Runtime& audio, std::uint8_t variant)
45 {
46 if (variant >= SirenCount)
47 return std::unexpected("Paddywagon siren variant is outside retail 0..1 range");
48 return audio.play(SirenKey, threeDData(static_cast<data::DataTag>(
49 SirenBaseTag + variant)), 0.74F);
50 }
51 std::expected<void, std::string> Runtime::cashUp(audio::Runtime& audio)
53 std::expected<void, std::string> Runtime::cashDown(audio::Runtime& audio)
55
56 std::expected<void, std::string> Runtime::syncCash(
58 {
59 if (change == ibar::ScoreCashChange::Up) return cashUp(audio);
60 if (change == ibar::ScoreCashChange::Down) return cashDown(audio);
61 return {};
62 }
63
64 std::expected<void, std::string> Runtime::syncMusic(
65 audio::Runtime& audio, bool gameInProgress,
66 bool musicOn, std::uint8_t tuneIndex, bool creditsActive)
67 {
69 bool loop = true;
70
71 if (creditsActive)
72 {
73 const auto credits = mainData(CreditsTag);
74 const auto creditsLoop = mainData(MusicBaseTag);
75 if (creditsMusicStatus_ == 0)
76 creditsMusicStatus_ = 1;
77 else if (creditsMusicStatus_ == 1 &&
78 currentMusic_ == credits && !audio.active(MusicKey))
79 creditsMusicStatus_ = 2;
80
81 desired = creditsMusicStatus_ == 2 ? creditsLoop : credits;
82 loop = creditsMusicStatus_ == 2;
83 }
84 else
85 {
86 creditsMusicStatus_ = 0;
87 if (!gameInProgress || !musicOn)
88 {
89 if (currentMusic_) audio.stop(MusicKey);
90 currentMusic_.reset();
91 return {};
92 }
93 const auto safeTune = std::min<std::uint8_t>(
95 desired = mainData(static_cast<data::DataTag>(
97 }
98
99 if (currentMusic_ == desired && audio.active(MusicKey)) return {};
100 if (currentMusic_ && *currentMusic_ != desired)
101 audio.stop(MusicKey);
102 const auto played = audio.play(MusicKey, desired, 0.20F, loop);
103 if (!played) return played;
104 currentMusic_ = desired;
105 return {};
106 }
107
109 const audio::Runtime& audio, std::uint8_t token) const noexcept
110 {
111 return token < TokenVoiceTokenCount && audio.active(tokenVoiceKey(token));
112 }
113
114 bool Runtime::anyTalking(const audio::Runtime& audio) const noexcept
115 {
116 if (audio.active(PennybagsKey)) return true;
117 for (std::uint8_t token = 0; token < TokenVoiceTokenCount; ++token)
118 if (tokenVoiceActive(audio, token)) return true;
119 return false;
120 }
121
122 void Runtime::stopTalking(audio::Runtime& audio) noexcept
123 {
124 audio.stop(PennybagsKey);
125 for (std::uint8_t token = 0; token < TokenVoiceTokenCount; ++token)
126 audio.stop(tokenVoiceKey(token));
127 watchedTokenVoices_.fill(false);
128 watchedPennybags_ = false;
129 pendingTalkingVoice_.reset();
130 }
131
132 void Runtime::watchTalking(const audio::Runtime& audio) noexcept
133 {
134 if (audio.active(PennybagsKey)) watchedPennybags_ = true;
135 for (std::uint8_t token = 0; token < TokenVoiceTokenCount; ++token)
136 if (tokenVoiceActive(audio, token)) watchedTokenVoices_[token] = true;
137 }
138
139 std::expected<void, std::string> Runtime::startTalkingVoice(
140 audio::Runtime& audio, const PendingTalkingVoice& voice)
141 {
142 if (voice.token)
143 {
144 if (*voice.token >= TokenVoiceTokenCount)
145 return std::unexpected("token voice token is out of range");
146 return audio.play(tokenVoiceKey(*voice.token), voice.wave, voice.gain, false);
147 }
148 return audio.play(PennybagsKey, voice.wave, voice.gain, false);
149 }
150
151 std::expected<TokenVoicePlayResult, std::string> Runtime::playTalkingVoice(
152 audio::Runtime& audio, PendingTalkingVoice voice,
154 {
155 TokenVoicePlayResult result{};
156 const bool talking = anyTalking(audio);
157 switch (policy)
158 {
161 stopTalking(audio);
162 break;
164 if (talking || pendingTalkingVoice_)
165 {
166 result.skipped = true;
167 return result;
168 }
169 break;
172 if (talking || pendingTalkingVoice_)
173 {
174 watchTalking(audio);
175 pendingTalkingVoice_ = voice;
176 result.queued = true;
177 return result;
178 }
179 break;
180 }
181
182 const auto started = startTalkingVoice(audio, voice);
183 if (!started) return std::unexpected(started.error());
184 result.started = true;
185 const bool lockNew =
188 if (lockNew)
189 {
190 if (voice.token) watchedTokenVoices_[*voice.token] = true;
191 else watchedPennybags_ = true;
192 }
193 return result;
194 }
195
196 std::expected<TokenVoicePlayResult, std::string> Runtime::tokenVoice(
197 audio::Runtime& audio, bool voicesOn, std::uint8_t token,
199 std::uint32_t randomValue)
200 {
201 TokenVoicePlayResult result{};
202 if (!voicesOn) return result;
203 const auto wave = chooseTokenVoice(audio.boardEdition(), line, token, randomValue);
204 if (!wave)
205 {
206 result.skipped = true;
207 return result;
208 }
209 return playTalkingVoice(audio, PendingTalkingVoice{token, *wave, 1.0F}, policy);
210 }
211 std::expected<TokenVoicePlayResult, std::string> Runtime::pennybagsVoice(
212 audio::Runtime& audio, bool hostCommentsOn, PennybagsVoice voice,
213 TokenVoiceClipPolicy policy, std::uint32_t randomValue)
214 {
215 TokenVoicePlayResult result{};
216 if (!hostCommentsOn) return result;
217 const auto wave = choosePennybagsVoice(audio.boardEdition(), voice, randomValue);
218 if (!wave)
219 {
220 result.skipped = true;
221 return result;
222 }
223 return playTalkingVoice(audio,
224 PendingTalkingVoice{std::nullopt, *wave, PennybagsGain}, policy);
225 }
226
227 std::expected<TokenVoicePlayResult, std::string> Runtime::pennybagsSpecific(
228 audio::Runtime& audio, bool hostCommentsOn, data::DataId wave,
230 {
231 TokenVoicePlayResult result{};
232 if (!hostCommentsOn || wave == data::EmptyDataId) return result;
233 return playTalkingVoice(audio,
234 PendingTalkingVoice{std::nullopt, wave, PennybagsGain}, policy);
235 }
236
237 std::expected<bool, std::string> Runtime::syncTokenVoices(audio::Runtime& audio)
238 {
239 for (std::uint8_t token = 0; token < TokenVoiceTokenCount; ++token)
240 if (watchedTokenVoices_[token] && !tokenVoiceActive(audio, token))
241 watchedTokenVoices_[token] = false;
242 if (watchedPennybags_ && !audio.active(PennybagsKey))
243 watchedPennybags_ = false;
244
245 if (pendingTalkingVoice_ && !anyTalking(audio))
246 {
247 const auto pending = *pendingTalkingVoice_;
248 pendingTalkingVoice_.reset();
249 const auto started = startTalkingVoice(audio, pending);
250 if (!started) return std::unexpected(started.error());
251 // Retail releases the previous lock before starting SoundToPlayPostLock;
252 // the posted voice is therefore not automatically watched again.
253 }
254
255 const bool watchedToken = std::any_of(watchedTokenVoices_.begin(),
256 watchedTokenVoices_.end(), [](bool value) { return value; });
257 return watchedToken || watchedPennybags_;
258 }
259
260 bool Runtime::talkingQueueIdle(const audio::Runtime& audio) const noexcept
261 {
262 return !anyTalking(audio) && !pendingTalkingVoice_;
263 }
264
265 void Runtime::watchTokenVoice(audio::Runtime& audio, std::uint8_t token) noexcept
266 {
267 if (token < TokenVoiceTokenCount && tokenVoiceActive(audio, token))
268 watchedTokenVoices_[token] = true;
269 }
270
272 {
273 if (audio.active(PennybagsKey)) watchedPennybags_ = true;
274 }
275 void Runtime::reset(audio::Runtime* audio) noexcept
276 {
277 if (audio != nullptr)
278 {
279 audio->stop(WarningKey); audio->stop(ClickKey);
280 audio->stop(CashUpKey); audio->stop(CashDownKey);
281 audio->stop(MusicKey); audio->stop(PennybagsKey);
282 for (std::uint8_t token = 0; token < TokenVoiceTokenCount; ++token)
283 audio->stop(tokenVoiceKey(token));
284 }
285 currentMusic_.reset();
286 creditsMusicStatus_ = 0;
287 pendingTalkingVoice_.reset();
288 watchedTokenVoices_.fill(false);
289 watchedPennybags_ = false;
290 }
291}
unsigned index
int token
std::uint16_t value
Definition Timers.cpp:16
data::BoardEdition boardEdition() const noexcept
void stop(PlaybackKey key) noexcept
bool active(PlaybackKey key) const noexcept
std::expected< void, std::string > play(PlaybackKey key, data::DataId waveDataId, float gain=1.0F, bool loop=false, std::uint32_t pitchHertz=0U, std::int32_t panPercentage=0)
std::expected< void, std::string > saveFailure(audio::Runtime &audio)
std::expected< void, std::string > click(audio::Runtime &audio)
std::expected< void, std::string > syncCash(audio::Runtime &audio, ibar::ScoreCashChange change)
std::expected< void, std::string > syncMusic(audio::Runtime &audio, bool gameInProgress, bool musicOn, std::uint8_t tuneIndex, bool creditsActive=false)
std::expected< void, std::string > cashDown(audio::Runtime &audio)
void watchTokenVoice(audio::Runtime &audio, std::uint8_t token) noexcept
std::expected< void, std::string > build(audio::Runtime &audio)
void watchPennybags(audio::Runtime &audio) noexcept
bool talkingQueueIdle(const audio::Runtime &audio) const noexcept
std::expected< void, std::string > siren(audio::Runtime &audio, std::uint8_t variant)
std::expected< bool, std::string > syncTokenVoices(audio::Runtime &audio)
std::expected< TokenVoicePlayResult, std::string > pennybagsSpecific(audio::Runtime &audio, bool hostCommentsOn, data::DataId wave, TokenVoiceClipPolicy policy)
std::expected< void, std::string > unbuild(audio::Runtime &audio)
std::expected< TokenVoicePlayResult, std::string > pennybagsVoice(audio::Runtime &audio, bool hostCommentsOn, PennybagsVoice voice, TokenVoiceClipPolicy policy, std::uint32_t randomValue)
void reset(audio::Runtime *audio=nullptr) noexcept
std::expected< TokenVoicePlayResult, std::string > tokenVoice(audio::Runtime &audio, bool voicesOn, std::uint8_t token, TokenVoiceLine line, TokenVoiceClipPolicy policy, std::uint32_t randomValue)
bool tokenVoiceActive(const audio::Runtime &audio, std::uint8_t token) const noexcept
std::expected< void, std::string > cashUp(audio::Runtime &audio)
std::expected< void, std::string > warning(audio::Runtime &audio)
std::uint32_t DataId
Definition DataBanks.hpp:10
constexpr DataId EmptyDataId
Definition DataBanks.hpp:13
std::uint16_t DataTag
Definition DataBanks.hpp:11
constexpr DataId packDataId(std::uint16_t group, DataTag tag) noexcept
Definition DataBanks.hpp:39
std::optional< data::DataId > choosePennybagsVoice(data::BoardEdition edition, PennybagsVoice voice, std::uint32_t randomValue) noexcept
constexpr data::DataTag SaveFailureTag
constexpr data::DataTag BuildTag
constexpr data::DataTag SirenBaseTag
constexpr std::uint8_t MusicTuneCount
constexpr data::DataTag UnbuildTag
constexpr std::uint8_t SirenCount
constexpr data::DataTag CreditsTag
constexpr data::DataTag WarningTag
constexpr data::DataTag MusicBaseTag
constexpr data::DataTag CashUpTag
std::optional< data::DataId > chooseTokenVoice(data::BoardEdition edition, TokenVoiceLine line, std::uint8_t token, std::uint32_t randomValue) noexcept
constexpr std::uint8_t TokenVoiceTokenCount
constexpr data::DataTag CashDownTag
constexpr data::DataTag ClickTag