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