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