1 #include "SoundManager.h"
2
3 #include "../ResourceManager.h"
4 #include "../common/misc.h"
5 #include "../Utils/Logger.h"
6 #include "../game.h"
7 #include "../settings.h"
8 #include "Ogg.h"
9 #include "Organya.h"
10 #include "Pixtone.h"
11
12 #include <json.hpp>
13 #include <fstream>
14 #include <iostream>
15
16 #define MUSIC_OFF 0
17 #define MUSIC_ON 1
18 #define MUSIC_BOSS_ONLY 2
19
20 namespace NXE
21 {
22 namespace Sound
23 {
24
SoundManager()25 SoundManager::SoundManager() {}
~SoundManager()26 SoundManager::~SoundManager() {}
27
getInstance()28 SoundManager *SoundManager::getInstance()
29 {
30 return Singleton<SoundManager>::get();
31 }
32
init()33 bool SoundManager::init()
34 {
35 LOG_INFO("Sound system init");
36 if (Mix_Init(MIX_INIT_OGG) == -1)
37 {
38 LOG_ERROR("Unable to init mixer.");
39 return false;
40 }
41
42 #if SDL_MIXER_PATCHLEVEL >= 2
43 if (Mix_OpenAudioDevice(SAMPLE_RATE, AUDIO_S16, 2, 2048, NULL, 0) == -1)
44 {
45 LOG_ERROR("Unable to open audio device.");
46 return false;
47 }
48 #else
49 if (Mix_OpenAudio(SAMPLE_RATE, AUDIO_S16, 2, 2048) == -1)
50 {
51 LOG_ERROR("Unable to open audio device.");
52 return false;
53 }
54 #endif
55 Mix_AllocateChannels(64);
56
57 std::string path = ResourceManager::getInstance()->getPath("music_dirs.json", false);
58
59 _music_dirs.clear();
60 _music_dir_names.clear();
61 _music_playlists.clear();
62 _music_dirs.push_back("org/");
63 _music_dir_names.push_back("Original");
64 _music_playlists.push_back("music.json");
65
66 std::ifstream fl;
67
68 fl.open(widen(path), std::ifstream::in | std::ifstream::binary);
69 if (fl.is_open())
70 {
71 nlohmann::json dirlist = nlohmann::json::parse(fl);
72
73 for (auto it = dirlist.begin(); it != dirlist.end(); ++it)
74 {
75 std::string dir = it.value().at("dir");
76 if (
77 ResourceManager::getInstance()->fileExists(
78 ResourceManager::getInstance()->getPathForDir(dir)
79 )
80 )
81 {
82 auto it_playlist = it.value().find("playlist");
83 if (it_playlist != it.value().end())
84 {
85 _music_playlists.push_back(*it_playlist);
86 }
87 else
88 {
89 _music_playlists.push_back("music.json");
90 }
91
92 _music_dirs.push_back(dir);
93 _music_dir_names.push_back(it.value().at("name"));
94 }
95 else
96 {
97 LOG_WARN("Music dir {} doesn't exist", dir.c_str());
98 }
99 }
100 fl.close();
101 }
102 else
103 {
104 LOG_ERROR("Failed to load music_dirs.json");
105 }
106
107 _reloadTrackList();
108
109 Pixtone::getInstance()->init();
110 Organya::getInstance()->init();
111
112 // prepare resampled stream sounds (Core battle and <SSS in main artery)
113 Pixtone::getInstance()->prepareResampled((int32_t)SFX::SND_STREAM1, 1000);
114 Pixtone::getInstance()->prepareResampled((int32_t)SFX::SND_STREAM2, 1100);
115 Pixtone::getInstance()->prepareResampled((int32_t)SFX::SND_STREAM1, 400);
116 Pixtone::getInstance()->prepareResampled((int32_t)SFX::SND_STREAM2, 500);
117
118 updateSfxVolume();
119 updateMusicVolume();
120 return true;
121 }
122
shutdown()123 void SoundManager::shutdown()
124 {
125 Organya::getInstance()->shutdown();
126 Pixtone::getInstance()->shutdown();
127
128 Mix_CloseAudio();
129 Mix_Quit();
130 LOG_INFO("Sound system shutdown");
131 }
132
playSfx(NXE::Sound::SFX snd,int32_t loop)133 void SoundManager::playSfx(NXE::Sound::SFX snd, int32_t loop)
134 {
135 if (!settings->sound_enabled)
136 return;
137
138 Pixtone::getInstance()->stop((int32_t)snd);
139 Pixtone::getInstance()->play(-1, (int32_t)snd, loop);
140 }
141
playSfxResampled(NXE::Sound::SFX snd,uint32_t percent)142 void SoundManager::playSfxResampled(NXE::Sound::SFX snd, uint32_t percent)
143 {
144 if (!settings->sound_enabled)
145 return;
146
147 Pixtone::getInstance()->playResampled(-1, (int32_t)snd, -1, percent);
148 }
149
stopSfx(NXE::Sound::SFX snd)150 void SoundManager::stopSfx(NXE::Sound::SFX snd)
151 {
152 Pixtone::getInstance()->stop((int32_t)snd);
153 }
154
startStreamSound(int32_t freq)155 void SoundManager::startStreamSound(int32_t freq)
156 {
157 playSfxResampled(SFX::SND_STREAM1, freq);
158 playSfxResampled(SFX::SND_STREAM2, freq + 100);
159 }
160
startPropSound()161 void SoundManager::startPropSound()
162 {
163 playSfx(SFX::SND_PROPELLOR, -1);
164 }
165
stopLoopSfx()166 void SoundManager::stopLoopSfx()
167 {
168 stopSfx(SFX::SND_STREAM1);
169 stopSfx(SFX::SND_STREAM2);
170 stopSfx(SFX::SND_PROPELLOR);
171 }
172
music(uint32_t songno,bool resume)173 void SoundManager::music(uint32_t songno, bool resume)
174 {
175 if (songno == _currentSong)
176 return;
177
178 _lastSong = _currentSong;
179 _currentSong = songno;
180
181 LOG_DEBUG(" >> music({})", songno);
182
183 if (songno != 0 && !_shouldMusicPlay(songno, settings->music_enabled))
184 {
185 LOG_INFO("Not playing track {} because music_enabled is {}", songno, settings->music_enabled);
186 switch (settings->new_music)
187 {
188 case 0:
189 _lastSongPos = Organya::getInstance()->stop();
190 break;
191 case 1:
192 case 2:
193 _songlooped = Ogg::getInstance()->looped();
194 _lastSongPos = Ogg::getInstance()->stop();
195 break;
196 }
197 return;
198 }
199
200 switch (settings->new_music)
201 {
202 case 0:
203 _start_org_track(songno, resume);
204 break;
205 default:
206 _start_ogg_track(songno, resume, _music_dirs.at(settings->new_music));
207 break;
208 }
209 }
210
enableMusic(int newstate)211 void SoundManager::enableMusic(int newstate)
212 {
213 if (newstate != settings->music_enabled)
214 {
215 LOG_DEBUG("enableMusic({})", newstate);
216
217 settings->music_enabled = newstate;
218 bool play = _shouldMusicPlay(_currentSong, newstate);
219
220 switch (settings->new_music)
221 {
222 case 0:
223 if (play != Organya::getInstance()->isPlaying())
224 {
225 if (play)
226 _start_org_track(_currentSong, 0);
227 else
228 _lastSongPos = Organya::getInstance()->stop();
229 }
230 break;
231 default:
232 if (play != Ogg::getInstance()->isPlaying())
233 {
234 if (play)
235 _start_ogg_track(_currentSong, 0, _music_dirs.at(settings->new_music));
236 else
237 _lastSongPos = Ogg::getInstance()->stop();
238 }
239 break;
240 }
241 }
242 }
243
setNewmusic(int newstate)244 void SoundManager::setNewmusic(int newstate)
245 {
246 if (newstate != settings->new_music)
247 {
248 LOG_DEBUG("setNewMusic({})", newstate);
249
250 settings->new_music = newstate;
251
252 Organya::getInstance()->stop();
253 Ogg::getInstance()->stop();
254
255 _reloadTrackList();
256
257 switch (newstate)
258 {
259 case 0:
260 _start_org_track(_currentSong, 0);
261 break;
262 default:
263 _start_ogg_track(_currentSong, 0, _music_dirs.at(newstate));
264 break;
265 }
266 }
267 }
268
currentSong()269 uint32_t SoundManager::currentSong()
270 {
271 return _currentSong;
272 }
273
lastSong()274 uint32_t SoundManager::lastSong()
275 {
276 return _lastSong;
277 }
278
fadeMusic()279 void SoundManager::fadeMusic()
280 {
281 switch (settings->new_music)
282 {
283 case 0:
284 Organya::getInstance()->fade();
285 break;
286 case 1:
287 case 2:
288 Ogg::getInstance()->fade();
289 break;
290 }
291 }
292
runFade()293 void SoundManager::runFade()
294 {
295 switch (settings->new_music)
296 {
297 case 0:
298 Organya::getInstance()->runFade();
299 break;
300 case 1:
301 case 2:
302 Ogg::getInstance()->runFade();
303 break;
304 }
305 }
306
pause()307 void SoundManager::pause()
308 {
309 Mix_Pause(-1);
310 switch (settings->new_music)
311 {
312 case 0:
313 Organya::getInstance()->pause();
314 break;
315 case 1:
316 case 2:
317 Ogg::getInstance()->pause();
318 break;
319 }
320 }
321
resume()322 void SoundManager::resume()
323 {
324 Mix_Resume(-1);
325 switch (settings->new_music)
326 {
327 case 0:
328 Organya::getInstance()->resume();
329 break;
330 case 1:
331 case 2:
332 Ogg::getInstance()->resume();
333 break;
334 }
335 }
336
updateSfxVolume()337 void SoundManager::updateSfxVolume()
338 {
339 Mix_Volume(-1, (int)(128. / 100. * (double)settings->sfx_volume));
340 }
341
updateMusicVolume()342 void SoundManager::updateMusicVolume()
343 {
344 Ogg::getInstance()->updateVolume();
345 }
346
_shouldMusicPlay(uint32_t songno,uint32_t musicmode)347 bool SoundManager::_shouldMusicPlay(uint32_t songno, uint32_t musicmode)
348 {
349 if (game.mode == GM_TITLE || game.mode == GM_CREDITS)
350 return true;
351
352 switch (musicmode)
353 {
354 case MUSIC_OFF:
355 return false;
356 case MUSIC_ON:
357 return true;
358 case MUSIC_BOSS_ONLY:
359 return _musicIsBoss(songno);
360 }
361
362 return false;
363 }
364
_musicIsBoss(uint32_t songno)365 bool SoundManager::_musicIsBoss(uint32_t songno)
366 {
367 if (strchr(_bossmusic, songno))
368 return true;
369 else
370 return false;
371 }
372
_start_org_track(int songno,bool resume)373 void SoundManager::_start_org_track(int songno, bool resume)
374 {
375 if (_music_names.size() < 2) return;
376
377 _lastSongPos = Organya::getInstance()->stop();
378 if (songno == 0)
379 {
380 return;
381 }
382
383 if (Organya::getInstance()->load(
384 ResourceManager::getInstance()->getPath(_music_dirs.at(0) + _music_names[songno] + ".org", false)))
385 {
386 Organya::getInstance()->start(resume ? _lastSongPos : 0);
387 }
388 }
389
_start_ogg_track(int songno,bool resume,std::string dir)390 void SoundManager::_start_ogg_track(int songno, bool resume, std::string dir)
391 {
392 if (_music_names.size() < 2) return;
393
394 if (songno == 0)
395 {
396 _songlooped = Ogg::getInstance()->looped();
397 _lastSongPos = Ogg::getInstance()->stop();
398 return;
399 }
400 Ogg::getInstance()->start(_music_names[songno], dir, resume ? _lastSongPos : 0, resume ? _songlooped : false, _music_loop[songno]);
401 }
402
music_dir_names()403 std::vector<std::string> &SoundManager::music_dir_names()
404 {
405 return _music_dir_names;
406 }
407
_reloadTrackList()408 void SoundManager::_reloadTrackList()
409 {
410 std::string path = ResourceManager::getInstance()->getPath(_music_playlists.at(settings->new_music), false);
411
412 std::ifstream fl;
413
414 _music_names.clear();
415 _music_names.push_back("");
416 _music_loop.clear();
417 _music_loop.push_back(false);
418
419 fl.open(widen(path), std::ifstream::in | std::ifstream::binary);
420 if (fl.is_open())
421 {
422 nlohmann::json tracklist = nlohmann::json::parse(fl);
423
424 for (auto it = tracklist.begin(); it != tracklist.end(); ++it)
425 {
426 auto it_loop = it.value().find("loop");
427 if (it_loop != it.value().end())
428 {
429 _music_loop.push_back(*it_loop);
430 }
431 else
432 {
433 _music_loop.push_back(true);
434 }
435 _music_names.push_back(it.value().at("name"));
436 }
437 fl.close();
438 }
439 else
440 {
441 LOG_ERROR("Failed to load music.json");
442 }
443 }
444
445 } // namespace Sound
446 } // namespace NXE
447