1 /************************************************************************************
2
3 AstroMenace
4 Hardcore 3D space scroll-shooter with spaceship upgrade possibilities.
5 Copyright (c) 2006-2019 Mikhail Kurinnoi, Viewizard
6
7
8 AstroMenace is free software: you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 AstroMenace is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with AstroMenace. If not, see <https://www.gnu.org/licenses/>.
20
21
22 Website: https://viewizard.com/
23 Project: https://github.com/viewizard/astromenace
24 E-mail: viewizard@viewizard.com
25
26 *************************************************************************************/
27
28 #include "../core/core.h"
29 #include "../config/config.h"
30 #include "audio.h"
31
32 // NOTE switch to nested namespace definition (namespace A::B::C { ... }) (since C++17)
33 namespace viewizard {
34 namespace astromenace {
35
36 namespace {
37
38 // empirical found "everage load value" for one sfx asset
39 // small value mean that asset loads fast, big value - slow
40 constexpr unsigned SFXLoadValue{20};
41
42 eMusicTheme CurrentPlayingMusicTheme{eMusicTheme::NONE};
43 unsigned int CurrentLoadedVoiceAssetsLanguage{0}; // English
44
45 struct sSoundMetadata {
46 std::string FileName{};
47 float VolumeCorrection{1.0f};
48 bool AllowStop{true}; // allow stop this sfx in vw_StopAllSoundsIfAllowed()
49
sSoundMetadataviewizard::astromenace::__anon4d9f68630111::sSoundMetadata50 explicit sSoundMetadata(const std::string &_FileName,
51 float _VolumeCorrection = 1.0f,
52 bool _AllowStop = true) :
53 FileName{_FileName},
54 VolumeCorrection{_VolumeCorrection},
55 AllowStop{_AllowStop}
56 {}
57 };
58
59 struct sMusicMetadata {
60 std::string FileName{};
61 std::string FileNameLoop{};
62 float VolumeCorrection{1.0f};
63 bool NeedRelease{false}; // if this music theme playing now - release it first
64
sMusicMetadataviewizard::astromenace::__anon4d9f68630111::sMusicMetadata65 explicit sMusicMetadata(const std::string &_FileName,
66 const std::string &_FileNameLoop = std::string{},
67 float _VolumeCorrection = 1.0f,
68 bool _NeedRelease = false) :
69 FileName{_FileName},
70 FileNameLoop{_FileNameLoop},
71 VolumeCorrection{_VolumeCorrection},
72 NeedRelease{_NeedRelease}
73 {}
74 };
75
76 const std::unordered_map<eMenuSFX, sSoundMetadata, sEnumHash> MenuSFXMap{
77 // key metadata
78 {eMenuSFX::OverSmallButton, sSoundMetadata{"sfx/menu_onbutton2.wav", 0.15f, false}},
79 {eMenuSFX::OverBigButton, sSoundMetadata{"sfx/menu_onbutton.wav", 0.4f, false}},
80 {eMenuSFX::Click, sSoundMetadata{"sfx/menu_click.wav", 0.6f, false}},
81 {eMenuSFX::SwitchToAnotherMenu, sSoundMetadata{"sfx/menu_new.wav"}},
82 {eMenuSFX::TapingClick, sSoundMetadata{"sfx/menu_taping.wav", 0.8f}},
83 {eMenuSFX::OverLine, sSoundMetadata{"sfx/menu_online.wav", 0.75f}},
84 {eMenuSFX::SelectLine, sSoundMetadata{"sfx/menu_selectline.wav"}},
85 {eMenuSFX::CanNotClick, sSoundMetadata{"sfx/menu_nclick.wav", 1.0f, false}},
86 {eMenuSFX::DragError, sSoundMetadata{"sfx/drag_error.wav"}},
87 {eMenuSFX::DragUninstallFromSlot, sSoundMetadata{"sfx/drag_offslot.wav", 0.65f}},
88 {eMenuSFX::DragInstallToSlot, sSoundMetadata{"sfx/drag_onslot.wav", 0.82f}},
89 {eMenuSFX::DragRelease, sSoundMetadata{"sfx/drag_release.wav", 0.6f}},
90 {eMenuSFX::MissionShowMenu, sSoundMetadata{"sfx/game_showmenu.wav", 1.0f, false}},
91 {eMenuSFX::MissionHideMenu, sSoundMetadata{"sfx/game_hidemenu.wav", 1.0f, false}},
92 {eMenuSFX::WarningLowLife, sSoundMetadata{"sfx/lowlife.wav"}}
93 };
94
95 const std::unordered_map<eGameSFX, sSoundMetadata, sEnumHash> GameSFXMap{
96 // key metadata
97 {eGameSFX::WeaponMalfunction_Kinetic, sSoundMetadata{"sfx/weapon1probl.wav"}},
98 {eGameSFX::WeaponMalfunction_Particle, sSoundMetadata{"sfx/weapon2probl.wav"}},
99 {eGameSFX::WeaponMalfunction_Beam, sSoundMetadata{"sfx/weapon3probl.wav"}},
100 {eGameSFX::WeaponMalfunction_Energy, sSoundMetadata{"sfx/weapon4probl.wav"}},
101 {eGameSFX::WeaponMalfunction_Launcher, sSoundMetadata{"sfx/weapon5probl.wav"}},
102 {eGameSFX::Explosion_Small, sSoundMetadata{"sfx/explosion4.wav"}},
103 {eGameSFX::Explosion_Medium, sSoundMetadata{"sfx/explosion1.wav"}},
104 {eGameSFX::Explosion_Big, sSoundMetadata{"sfx/explosion2.wav"}},
105 {eGameSFX::Explosion_Big_Energy, sSoundMetadata{"sfx/explosion3.wav"}},
106 {eGameSFX::WeaponFire_Kinetic1, sSoundMetadata{"sfx/weaponfire1.wav", 0.7f}},
107 {eGameSFX::WeaponFire_Kinetic2, sSoundMetadata{"sfx/weaponfire2.wav", 0.65f}},
108 {eGameSFX::WeaponFire_Kinetic3, sSoundMetadata{"sfx/weaponfire3.wav", 0.7f}},
109 {eGameSFX::WeaponFire_Kinetic4, sSoundMetadata{"sfx/weaponfire4.wav"}},
110 {eGameSFX::WeaponFire_Ion1, sSoundMetadata{"sfx/weaponfire5.wav"}},
111 {eGameSFX::WeaponFire_Ion2, sSoundMetadata{"sfx/weaponfire6.wav"}},
112 {eGameSFX::WeaponFire_Ion3, sSoundMetadata{"sfx/weaponfire7.wav", 0.7f}},
113 {eGameSFX::WeaponFire_Plasma1, sSoundMetadata{"sfx/weaponfire8.wav", 0.85f}},
114 {eGameSFX::WeaponFire_Plasma2, sSoundMetadata{"sfx/weaponfire9.wav", 0.95f}},
115 {eGameSFX::WeaponFire_Plasma3, sSoundMetadata{"sfx/weaponfire10.wav", 0.9f}},
116 {eGameSFX::WeaponFire_Maser1, sSoundMetadata{"sfx/weaponfire11.wav", 0.6f}},
117 {eGameSFX::WeaponFire_Maser2, sSoundMetadata{"sfx/weaponfire12.wav", 0.55f}},
118 {eGameSFX::WeaponFire_Antimatter, sSoundMetadata{"sfx/weaponfire13.wav", 0.9f}},
119 {eGameSFX::WeaponFire_Laser, sSoundMetadata{"sfx/weaponfire14.wav", 0.8f}},
120 {eGameSFX::WeaponFire_Gauss, sSoundMetadata{"sfx/weaponfire15.wav", 0.8f}},
121 {eGameSFX::WeaponFire_SmallMissile, sSoundMetadata{"sfx/weaponfire16.wav"}},
122 {eGameSFX::WeaponFire_NormalMissile, sSoundMetadata{"sfx/weaponfire17.wav"}},
123 {eGameSFX::WeaponFire_Torpedo, sSoundMetadata{"sfx/weaponfire18.wav"}},
124 {eGameSFX::WeaponFire_Bomb, sSoundMetadata{"sfx/weaponfire19.wav"}},
125 {eGameSFX::Hit_Kinetic, sSoundMetadata{"sfx/kinetichit.wav"}},
126 {eGameSFX::Hit_Ion, sSoundMetadata{"sfx/ionhit.wav"}},
127 {eGameSFX::Hit_Plasma, sSoundMetadata{"sfx/plasmahit.wav"}},
128 {eGameSFX::Hit_Antimatter, sSoundMetadata{"sfx/antimaterhit.wav"}},
129 {eGameSFX::Hit_Gauss, sSoundMetadata{"sfx/gausshit.wav"}}
130 };
131
132 const std::unordered_map<eVoicePhrase, sSoundMetadata, sEnumHash> VoiceMap{
133 // key metadata (note, 'en' here, since we use vw_GetText() for file name)
134 {eVoicePhrase::Attention, sSoundMetadata{"lang/en/voice/Attention.wav"}},
135 {eVoicePhrase::EngineMalfunction, sSoundMetadata{"lang/en/voice/EngineMalfunction.wav"}},
136 {eVoicePhrase::MissileDetected, sSoundMetadata{"lang/en/voice/MissileDetected.wav"}},
137 {eVoicePhrase::PowerSupplyReestablished,sSoundMetadata{"lang/en/voice/PowerSupplyReestablished.wav"}},
138 {eVoicePhrase::PrepareForAction, sSoundMetadata{"lang/en/voice/PrepareForAction.wav"}},
139 {eVoicePhrase::ReactorMalfunction, sSoundMetadata{"lang/en/voice/ReactorMalfunction.wav"}},
140 {eVoicePhrase::Warning, sSoundMetadata{"lang/en/voice/Warning.wav"}},
141 {eVoicePhrase::WeaponDamaged, sSoundMetadata{"lang/en/voice/WeaponDamaged.wav"}},
142 {eVoicePhrase::WeaponDestroyed, sSoundMetadata{"lang/en/voice/WeaponDestroyed.wav"}},
143 {eVoicePhrase::WeaponMalfunction, sSoundMetadata{"lang/en/voice/WeaponMalfunction.wav"}}
144 };
145
146 #pragma GCC diagnostic push
147 #pragma GCC diagnostic ignored "-Winline"
148 const std::unordered_map<eMusicTheme, sMusicMetadata, sEnumHash> MusicMap{
149 // key metadata
150 {eMusicTheme::MENU, sMusicMetadata{"music/menu.ogg"}},
151 {eMusicTheme::GAME, sMusicMetadata{"music/game.ogg", "", 1.0f, true}},
152 {eMusicTheme::BOSS, sMusicMetadata{"music/boss-loop.ogg"}},
153 {eMusicTheme::FAILED, sMusicMetadata{"music/missionfailed.ogg", "", 0.7f}},
154 {eMusicTheme::CREDITS, sMusicMetadata{"music/boss-intro.ogg", "music/boss-loop.ogg"}}
155 };
156 #pragma GCC diagnostic pop
157
158 } // unnamed namespace
159
160
161 /*
162 * Get all audio assets load value.
163 */
GetAudioAssetsLoadValue()164 unsigned GetAudioAssetsLoadValue()
165 {
166 return (MenuSFXMap.size() * SFXLoadValue +
167 GameSFXMap.size() * SFXLoadValue +
168 VoiceMap.size() * SFXLoadValue);
169 }
170
171 /*
172 * Cycle with function callback on each audio asset load.
173 */
ForEachAudioAssetLoad(std::function<void (unsigned AssetValue)> function)174 void ForEachAudioAssetLoad(std::function<void (unsigned AssetValue)> function)
175 {
176 if (!vw_GetAudioStatus())
177 return;
178
179 for (auto &tmpAsset : MenuSFXMap) {
180 vw_LoadSoundBuffer(tmpAsset.second.FileName);
181 function(SFXLoadValue);
182 }
183 for (auto &tmpAsset : GameSFXMap) {
184 vw_LoadSoundBuffer(tmpAsset.second.FileName);
185 function(SFXLoadValue);
186 }
187 for (auto &tmpAsset : VoiceMap) {
188 vw_LoadSoundBuffer(vw_GetText(tmpAsset.second.FileName, GameConfig().VoiceLanguage));
189 function(SFXLoadValue);
190 }
191 CurrentLoadedVoiceAssetsLanguage = GameConfig().VoiceLanguage;
192 }
193
194 /*
195 * Reload voice assets for new language.
196 */
ReloadVoiceAssets()197 void ReloadVoiceAssets()
198 {
199 for (auto &tmpAsset : VoiceMap) {
200 std::string OldFileName{vw_GetText(tmpAsset.second.FileName, CurrentLoadedVoiceAssetsLanguage)};
201 std::string NewFileName{vw_GetText(tmpAsset.second.FileName, GameConfig().VoiceLanguage)};
202 // we may have situation, when language don't provide voice file and
203 // use voice from another language
204 if (OldFileName != NewFileName) {
205 vw_ReleaseSoundBuffer(OldFileName);
206 vw_LoadSoundBuffer(NewFileName);
207 }
208 }
209 CurrentLoadedVoiceAssetsLanguage = GameConfig().VoiceLanguage;
210 }
211
212 /*
213 * Play music theme with fade-in and fade-out previous music theme (if need).
214 */
PlayMusicTheme(eMusicTheme MusicTheme,uint32_t FadeInTicks,uint32_t FadeOutTicks)215 void PlayMusicTheme(eMusicTheme MusicTheme, uint32_t FadeInTicks, uint32_t FadeOutTicks)
216 {
217 auto tmpMusic = MusicMap.find(MusicTheme);
218 if (tmpMusic == MusicMap.end())
219 return;
220
221 if (tmpMusic->second.NeedRelease)
222 vw_ReleaseMusic(tmpMusic->second.FileName);
223
224 CurrentPlayingMusicTheme = MusicTheme;
225
226 if (vw_GetAudioStatus() &&
227 GameConfig().MusicVolume) {
228 vw_FadeOutAllMusicWithException(tmpMusic->second.FileName, FadeOutTicks, 1.0f, FadeInTicks);
229
230 if (vw_IsMusicPlaying(tmpMusic->second.FileName))
231 return;
232
233 if (!vw_PlayMusic(tmpMusic->second.FileName, 0.0f,
234 tmpMusic->second.VolumeCorrection * (GameConfig().MusicVolume / 10.0f),
235 tmpMusic->second.FileNameLoop.empty(), tmpMusic->second.FileNameLoop)) {
236 vw_ReleaseMusic(tmpMusic->second.FileName);
237 CurrentPlayingMusicTheme = eMusicTheme::NONE;
238 } else // we are playing new music theme, fade-in it
239 vw_SetMusicFadeIn(tmpMusic->second.FileName, 1.0f, FadeInTicks);
240 }
241 }
242
243 /*
244 * Change "global" volume for menu sfx (2D).
245 */
ChangeMenuSFXGlobalVolume(float NewGlobalVolume)246 void ChangeMenuSFXGlobalVolume(float NewGlobalVolume)
247 {
248 for (auto &tmpSFX : MenuSFXMap) {
249 vw_SetSoundGlobalVolume(tmpSFX.second.FileName, NewGlobalVolume);
250 }
251 }
252
253 /*
254 * Change "global" volume for voice.
255 */
ChangeVoiceGlobalVolume(float NewGlobalVolume)256 void ChangeVoiceGlobalVolume(float NewGlobalVolume)
257 {
258 for (auto &tmpVoice : VoiceMap) {
259 vw_SetSoundGlobalVolume(vw_GetText(tmpVoice.second.FileName, GameConfig().VoiceLanguage), NewGlobalVolume);
260 }
261 }
262
263 /*
264 * Play menu sfx (2D).
265 */
PlayMenuSFX(eMenuSFX MenuSFX,float LocalVolume)266 unsigned int PlayMenuSFX(eMenuSFX MenuSFX, float LocalVolume)
267 {
268 if (!vw_GetAudioStatus() ||
269 !GameConfig().SoundVolume)
270 return 0;
271
272 auto tmpSFX = MenuSFXMap.find(MenuSFX);
273 if (tmpSFX == MenuSFXMap.end())
274 return 0;
275
276 // restart, if already playing this SFX
277 int ret = vw_ReplayFirstFoundSound(tmpSFX->second.FileName);
278 if (ret)
279 return ret;
280
281 return vw_PlaySound(tmpSFX->second.FileName,
282 LocalVolume * tmpSFX->second.VolumeCorrection,
283 GameConfig().SoundVolume / 10.0f, sVECTOR3D{},
284 true, tmpSFX->second.AllowStop, 1);
285 }
286
287 /*
288 * Play game sfx (3D).
289 */
PlayGameSFX(eGameSFX GameSFX,float LocalVolume,const sVECTOR3D & Location,int AtType)290 unsigned int PlayGameSFX(eGameSFX GameSFX, float LocalVolume, const sVECTOR3D &Location, int AtType)
291 {
292 if (!vw_GetAudioStatus() ||
293 !GameConfig().SoundVolume)
294 return 0;
295
296 auto tmpSFX = GameSFXMap.find(GameSFX);
297 if (tmpSFX == GameSFXMap.end())
298 return 0;
299
300 return vw_PlaySound(tmpSFX->second.FileName,
301 LocalVolume * tmpSFX->second.VolumeCorrection,
302 GameConfig().SoundVolume / 10.0f, Location,
303 false, tmpSFX->second.AllowStop, AtType);
304 }
305
306 /*
307 * Play voice phrase.
308 */
PlayVoicePhrase(eVoicePhrase VoicePhrase,float LocalVolume)309 unsigned int PlayVoicePhrase(eVoicePhrase VoicePhrase, float LocalVolume)
310 {
311 if (!vw_GetAudioStatus() ||
312 !GameConfig().VoiceVolume)
313 return 0;
314
315 auto tmpVoice = VoiceMap.find(VoicePhrase);
316 if (tmpVoice == VoiceMap.end())
317 return 0;
318
319 // FIXME revise Russian voice files for proper volume
320 if (GameConfig().VoiceLanguage == 2)
321 LocalVolume *= 0.6f;
322
323 return vw_PlaySound(vw_GetText(tmpVoice->second.FileName, GameConfig().VoiceLanguage),
324 LocalVolume * tmpVoice->second.VolumeCorrection,
325 GameConfig().VoiceVolume / 10.0f, sVECTOR3D{},
326 true, tmpVoice->second.AllowStop, 1);
327 }
328
329 /*
330 * Main audio loop.
331 */
AudioLoop()332 void AudioLoop()
333 {
334 // update buffers
335 vw_UpdateSound(SDL_GetTicks());
336 vw_UpdateMusic(SDL_GetTicks());
337
338 if (!vw_IsAnyMusicPlaying()) {
339 // start playing music
340 if (vw_GetAudioStatus() &&
341 GameConfig().MusicVolume &&
342 (CurrentPlayingMusicTheme != eMusicTheme::NONE)) {
343 auto tmpMusic = MusicMap.find(CurrentPlayingMusicTheme);
344 if (tmpMusic == MusicMap.end()) {
345 CurrentPlayingMusicTheme = eMusicTheme::NONE;
346 return;
347 }
348
349 if (!vw_PlayMusic(tmpMusic->second.FileName, 0.0f,
350 tmpMusic->second.VolumeCorrection * (GameConfig().MusicVolume / 10.0f),
351 tmpMusic->second.FileNameLoop.empty(), tmpMusic->second.FileNameLoop)) {
352 vw_ReleaseMusic(tmpMusic->second.FileName);
353 CurrentPlayingMusicTheme = eMusicTheme::NONE;
354 } else // we are playing new music theme, FadeIn it
355 vw_SetMusicFadeIn(tmpMusic->second.FileName, 1.0f, 2000);
356 }
357 } else {
358 // turn off music, if music volume set to 0
359 if (vw_GetAudioStatus() &&
360 !GameConfig().MusicVolume) {
361 vw_ReleaseAllMusic();
362 }
363 }
364 }
365
366 } // astromenace namespace
367 } // viewizard namespace
368