OpenClonk
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros
C4MusicSystem.cpp
Go to the documentation of this file.
1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 1998-2000, Matthes Bender
5  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
6  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
7  *
8  * Distributed under the terms of the ISC license; see accompanying file
9  * "COPYING" for details.
10  *
11  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12  * See accompanying file "TRADEMARK" for details.
13  *
14  * To redistribute this file separately, substitute the full license texts
15  * for the above references.
16  */
17 
18 /* Handles Music.ocg and randomly plays songs */
19 
20 #include "C4Include.h"
21 #include "platform/C4MusicSystem.h"
22 
23 #include "game/C4Application.h"
24 #include "game/C4GraphicsSystem.h"
25 #include "lib/C4Random.h"
26 #include "platform/C4MusicFile.h"
27 #include "platform/C4Window.h"
28 
30  playlist(),
31  music_break_min(DefaultMusicBreak), music_break_max(DefaultMusicBreak),
32  wait_time_end()
33 {
34 }
35 
37 {
38  Clear();
39 }
40 
41 #if AUDIO_TK == AUDIO_TK_OPENAL
43 {
44  alcMakeContextCurrent(alcContext);
45 }
46 #endif
47 
49 {
50 #if AUDIO_TK == AUDIO_TK_SDL_MIXER
51  SDL_version compile_version;
52  const SDL_version * link_version;
53  MIX_VERSION(&compile_version);
54  link_version=Mix_Linked_Version();
55  LogF("SDL_mixer runtime version is %d.%d.%d (compiled with %d.%d.%d)",
56  link_version->major, link_version->minor, link_version->patch,
57  compile_version.major, compile_version.minor, compile_version.patch);
58  if (SDL_InitSubSystem(SDL_INIT_AUDIO) != 0)
59  {
60  LogF("SDL_InitSubSystem(SDL_INIT_AUDIO): %s", SDL_GetError());
61  return false;
62  }
63  //frequency, format, stereo, chunksize
64  if (Mix_OpenAudio(44100, AUDIO_S16SYS, 2, 1024))
65  {
66  LogF("SDL_mixer: %s", SDL_GetError());
67  return false;
68  }
69  MODInitialized = true;
70  return true;
71 #elif AUDIO_TK == AUDIO_TK_OPENAL
72  alcDevice = alcOpenDevice(nullptr);
73  if (!alcDevice)
74  {
75  LogF("Sound system: OpenAL create context error");
76  return false;
77  }
78  alcContext = alcCreateContext(alcDevice, nullptr);
79  if (!alcContext)
80  {
81  LogF("Sound system: OpenAL create context error");
82  return false;
83  }
84 #ifndef __APPLE__
85  if (!alutInitWithoutContext(nullptr, nullptr))
86  {
87  LogF("Sound system: ALUT init error");
88  return false;
89  }
90 #endif
91  MODInitialized = true;
92  return true;
93 #endif
94  return false;
95 }
96 
98 {
99 #if AUDIO_TK == AUDIO_TK_SDL_MIXER
100  Mix_CloseAudio();
101  SDL_QuitSubSystem(SDL_INIT_AUDIO);
102 #elif AUDIO_TK == AUDIO_TK_OPENAL
103 #ifndef __APPLE__
104  alutExit();
105 #endif
106  alcDestroyContext(alcContext);
107  alcCloseDevice(alcDevice);
108  alcContext = nullptr;
109  alcDevice = nullptr;
110 #endif
111  MODInitialized = false;
112 }
113 
114 bool C4MusicSystem::Init(const char * PlayList)
115 {
116  // init mod
117  if (!MODInitialized && !InitializeMOD()) return false;
118  // Might be reinitialisation
119  ClearSongs();
120  // Global music file
122  // User music file
124  // read MoreMusic.txt
125  LoadMoreMusic();
126  // set play list
127  SCounter = 0;
128  if (PlayList) SetPlayList(PlayList); else SetPlayList(nullptr);
129  // set initial volume
130  UpdateVolume();
131  // ok
132  return true;
133 }
134 
136 {
137  // check if the scenario contains music
138  bool fLocalMusic = false;
139  StdStrBuf MusicDir;
140  if (GrpContainsMusic(hGroup))
141  {
142  // clear global songs
143  ClearSongs();
144  fLocalMusic = true;
145  // add songs
146  MusicDir.Take(Game.ScenarioFile.GetFullName());
147  LoadDir(MusicDir.getData());
148  // log
149  LogF(LoadResStr("IDS_PRC_LOCALMUSIC"), MusicDir.getData());
150  }
151  // check for music folders in group set
152  C4Group *pMusicFolder = nullptr;
153  while ((pMusicFolder = Game.GroupSet.FindGroup(C4GSCnt_Music, pMusicFolder)))
154  {
155  if (!fLocalMusic)
156  {
157  // clear global songs
158  ClearSongs();
159  fLocalMusic = true;
160  }
161  // add songs
162  MusicDir.Take(pMusicFolder->GetFullName());
163  MusicDir.AppendChar(DirectorySeparator);
164  MusicDir.Append(C4CFN_Music);
165  LoadDir(MusicDir.getData());
166  // log
167  LogF(LoadResStr("IDS_PRC_LOCALMUSIC"), MusicDir.getData());
168  }
169  // no music?
170  if (!SongCount) return false;
171  // set play list
172  SetPlayList(nullptr);
173  // ok
174  return true;
175 }
176 
177 void C4MusicSystem::Load(const char *szFile)
178 {
179  // safety
180  if (!szFile || !*szFile) return;
181  C4MusicFile *NewSong=nullptr;
182 #if AUDIO_TK == AUDIO_TK_OPENAL
183  // openal: Only ogg supported
184  const char *szExt = GetExtension(szFile);
185  if (SEqualNoCase(szExt, "ogg")) NewSong = new C4MusicFileOgg;
186 #elif AUDIO_TK == AUDIO_TK_SDL_MIXER
188  NewSong = new C4MusicFileSDL;
189 #endif
190  // unrecognized type/mod not initialized?
191  if (!NewSong) return;
192  // init music file
193  NewSong->Init(szFile);
194  // add song to list (push back)
195  C4MusicFile *pCurr = Songs;
196  while (pCurr && pCurr->pNext) pCurr = pCurr->pNext;
197  if (pCurr) pCurr->pNext = NewSong; else Songs = NewSong;
198  NewSong->pNext = nullptr;
199  // count songs
200  SongCount++;
201  playlist_valid = false;
202 }
203 
204 void C4MusicSystem::LoadDir(const char *szPath)
205 {
206  char Path[_MAX_FNAME + 1], File[_MAX_FNAME + 1];
207  C4Group *pDirGroup = nullptr;
208  // split path
209  SCopy(szPath, Path, _MAX_FNAME);
210  char *pFileName = GetFilename(Path);
211  SCopy(pFileName, File);
212  *(pFileName - 1) = 0;
213  // no file name?
214  if (!File[0])
215  // -> add the whole directory
216  SCopy("*", File);
217  // no wildcard match?
218  else if (!SSearch(File, "*?"))
219  {
220  // then it's either a file or a directory - do the test with C4Group
221  pDirGroup = new C4Group();
222  if (!pDirGroup->Open(szPath))
223  {
224  // so it must be a file
225  if (!pDirGroup->Open(Path))
226  {
227  // -> file/dir doesn't exist
228  LogF("Music File not found: %s", szPath);
229  delete pDirGroup;
230  return;
231  }
232  // mother group is open... proceed with normal handling
233  }
234  else
235  {
236  // ok, set wildcard (load the whole directory)
237  SCopy(szPath, Path);
238  SCopy("*", File);
239  }
240  }
241  // open directory group, if not already done so
242  if (!pDirGroup)
243  {
244  pDirGroup = new C4Group();
245  if (!pDirGroup->Open(Path))
246  {
247  LogF("Music File not found: %s", szPath);
248  delete pDirGroup;
249  return;
250  }
251  }
252  // search file(s)
253  char szFile[_MAX_FNAME + 1];
254  pDirGroup->ResetSearch();
255  while (pDirGroup->FindNextEntry(File, szFile))
256  {
257  char strFullPath[_MAX_FNAME + 1];
258  sprintf(strFullPath, "%s%c%s", Path, DirectorySeparator, szFile);
259  Load(strFullPath);
260  }
261  // free it
262  delete pDirGroup;
263 }
264 
266 {
267  StdStrBuf MoreMusicFile;
268  // load MoreMusic.txt
269  if (!MoreMusicFile.LoadFromFile(Config.AtUserDataPath(C4CFN_MoreMusic))) return;
270  // read contents
271  char *pPos = MoreMusicFile.getMData();
272  while (pPos && *pPos)
273  {
274  // get line
275  char szLine[1024 + 1];
276  SCopyUntil(pPos, szLine, '\n', 1024);
277  pPos = strchr(pPos, '\n'); if (pPos) pPos++;
278  // remove leading whitespace
279  char *pLine = szLine;
280  while (*pLine == ' ' || *pLine == '\t' || *pLine == '\r') pLine++;
281  // and whitespace at end
282  char *p = pLine + strlen(pLine) - 1;
283  while (*p == ' ' || *p == '\t' || *p == '\r') { *p = 0; --p; }
284  // comment?
285  if (*pLine == '#')
286  {
287  // might be a "directive"
288  if (SEqual(pLine, "#clear"))
289  ClearSongs();
290  continue;
291  }
292  // try to load file(s)
293  LoadDir(pLine);
294  }
295 }
296 
298 {
299  Stop();
300  while (Songs)
301  {
302  C4MusicFile *pFile = Songs;
303  Songs = pFile->pNext;
304  delete pFile;
305  }
306  SongCount = 0;
308  playlist_valid = false;
309 }
310 
312 {
313 #if AUDIO_TK == AUDIO_TK_SDL_MIXER
314  // Stop a fadeout
315  Mix_HaltMusic();
316 #endif
317  ClearSongs();
318  if (MODInitialized) { DeinitializeMOD(); }
319 }
320 
322 {
323  game_music_level = 100;
324  music_break_min = music_break_max = DefaultMusicBreak;
325  music_break_chance = DefaultMusicBreakChance;
326  music_max_position_memory = DefaultMusicMaxPositionMemory;
327  SetPlayList(nullptr);
328  is_waiting = false;
329  upcoming_music_file = nullptr;
330 }
331 
332 void C4MusicSystem::Execute(bool force_song_execution)
333 {
334  // Execute music fading
335  if (FadeMusicFile)
336  {
338  // Fading done?
339  if (tNow >= FadeTimeEnd)
340  {
341  FadeMusicFile->Stop();
342  FadeMusicFile = nullptr;
343  if (PlayMusicFile)
344  {
346  }
347  else if (upcoming_music_file)
348  {
349  // Fade end -> start desired next immediately
350  force_song_execution = true;
351  }
352  }
353  else
354  {
355  // Fade process
356  int fade_volume = 1000 * (tNow - FadeTimeStart) / (FadeTimeEnd - FadeTimeStart);
357  FadeMusicFile->SetVolume(Volume * (1000 - fade_volume) / 1000);
358  if (PlayMusicFile) PlayMusicFile->SetVolume(Volume * fade_volume / 1000);
359  }
360  }
361  // Ensure a piece is played
362 #if AUDIO_TK != AUDIO_TK_SDL_MIXER
363  if (!::Game.iTick35 || !::Game.IsRunning || force_song_execution || ::Game.IsPaused())
364 #else
365  (void) force_song_execution;
366 #endif
367  {
368  if (!PlayMusicFile)
369  {
371  {
372  // Play a song if no longer in silence mode and nothing is playing right now
373  C4MusicFile *next_file = upcoming_music_file;
374  is_waiting = false;
375  upcoming_music_file = nullptr;
376  if (next_file)
377  Play(next_file, false, 0.0);
378  else
379  Play();
380  }
381  }
382  else
383  {
384  // Calls NotifySuccess if a new piece had been selected.
386  }
387  }
388 }
389 
390 bool C4MusicSystem::Play(const char *szSongname, bool fLoop, int fadetime_ms, double max_resume_time, bool allow_break)
391 {
392  // pause is done
393  is_waiting = false;
394  upcoming_music_file = nullptr;
395 
396  // music off?
398  return false;
399 
400  // info
401  if (::Config.Sound.Verbose)
402  {
403  LogF(R"(MusicSystem: Play("%s", %s, %d, %.3lf, %s))", szSongname ? szSongname : "(null)", fLoop ? "true" : "false", fadetime_ms, max_resume_time, allow_break ? "true" : "false");
404  }
405 
406  C4MusicFile* NewFile = nullptr;
407 
408  // Specified song name
409  if (szSongname && szSongname[0])
410  {
411  // Search in list
412  for (NewFile = Songs; NewFile; NewFile = NewFile->pNext)
413  {
414  char songname[_MAX_FNAME + 1];
415  SCopy(szSongname, songname); DefaultExtension(songname, "mid");
416  if (SEqual(GetFilename(NewFile->FileName), songname))
417  break;
418  SCopy(szSongname, songname); DefaultExtension(songname, "ogg");
419  if (SEqual(GetFilename(NewFile->FileName), songname))
420  break;
421  }
422  }
423  else
424  {
425  // When resuming, prefer songs that were interrupted before
426  if (max_resume_time > 0)
427  {
429  for (C4MusicFile *check_file = Songs; check_file; check_file = check_file->pNext)
430  if (!check_file->NoPlay)
431  {
432  if (check_file->HasResumePos() && check_file->GetRemainingTime() > max_resume_time)
433  if (!music_max_position_memory || (t_now - check_file->GetLastInterruptionTime() <= music_max_position_memory*1000))
434  if (!NewFile || NewFile->LastPlayed < check_file->LastPlayed)
435  NewFile = check_file;
436  }
437  }
438 
439  // Random song
440  if (!NewFile)
441  {
442  // Intead of a new song, is a break also allowed?
443  if (allow_break) ScheduleWaitTime();
444  if (!is_waiting)
445  {
446  if (::Config.Sound.Verbose) LogF(" ASongCount=%d SCounter=%d", ASongCount, SCounter);
447  // try to find random song
448  int32_t new_file_playability = 0, new_file_num_rolls = 0;
449  for (C4MusicFile *check_file = Songs; check_file; check_file = check_file->pNext)
450  {
451  if (!check_file->NoPlay)
452  {
453  // Categorize song playability:
454  // 0 = no song found yet
455  // 1 = song was played recently
456  // 2 = song not played recently
457  // 3 = song was not played yet
458  int32_t check_file_playability = (check_file->LastPlayed < 0) ? 3 : (SCounter - check_file->LastPlayed <= ASongCount / 2) ? 1 : 2;
459  if (::Config.Sound.Verbose) LogF(" Song LastPlayed %d [%d] (%s)", int(check_file->LastPlayed), int(check_file_playability), check_file->GetDebugInfo().getData());
460  if (check_file_playability > new_file_playability)
461  {
462  // Found much better fit. Play this and reset number of songs found in same plyability
463  new_file_num_rolls = 1;
464  NewFile = check_file;
465  new_file_playability = check_file_playability;
466  }
467  else if (check_file_playability == new_file_playability)
468  {
469  // Found a fit in the same playability category: Roll for it
470  if (!UnsyncedRandom(++new_file_num_rolls)) NewFile = check_file;
471  }
472  else
473  {
474  // Worse playability - ignore this song
475  }
476  }
477  }
478  }
479 
480  }
481  }
482 
483  // File (or wait time) found?
484  if (!NewFile && !is_waiting)
485  return false;
486 
487  // Stop/Fade out old music
488  bool is_fading = (fadetime_ms && NewFile != PlayMusicFile && PlayMusicFile);
489  if (!is_fading)
490  {
491  Stop();
492  }
493  else
494  {
496  if (FadeMusicFile)
497  {
498  if (FadeMusicFile == NewFile && FadeMusicFile->IsLooping() == fLoop && tNow < FadeTimeEnd)
499  {
500  // Fading back to a song while it wasn't fully faded out yet. Just swap our pointers and fix timings for that.
502  PlayMusicFile = NewFile;
503  FadeTimeEnd = tNow + fadetime_ms * (tNow - FadeTimeStart) / (FadeTimeEnd - FadeTimeStart);
504  FadeTimeStart = FadeTimeEnd - fadetime_ms;
505  return true;
506  }
507  else
508  {
509  // Fading to a third song while the previous wasn't faded out yet
510  // That's pretty chaotic anyway, so just cancel the last song
511  // Also happens if fading should already be done, in which case it won't harm to stop now
512  // (It would stop on next call to Execute() anyway)
513  // Also happens when fading back to the same song but loop status changes, but that should be really uncommon.
514  FadeMusicFile->Stop();
515  }
516 
517  }
519  PlayMusicFile = nullptr;
520  FadeTimeStart = tNow;
521  FadeTimeEnd = FadeTimeStart + fadetime_ms;
522  }
523 
524  // Waiting?
525  if (!NewFile) return false;
526 
527  // If the old file is being faded out and a new file would just start, start delayed and without fading
528  // so the beginning of a song isn't faded unnecesserily (because our songs often start very abruptly)
529  if (is_fading && (!NewFile->HasResumePos() || NewFile->GetRemainingTime() <= max_resume_time))
530  {
531  upcoming_music_file = NewFile;
532  is_waiting = true;
534  return false;
535  }
536 
537  if (!Play(NewFile, fLoop, max_resume_time)) return false;
538 
539  if (is_fading) PlayMusicFile->SetVolume(0);
540 
541  return true;
542 }
543 
544 bool C4MusicSystem::Play(C4MusicFile *NewFile, bool fLoop, double max_resume_time)
545 {
546  // info
547  if (::Config.Sound.Verbose)
548  {
549  LogF(R"(MusicSystem: PlaySong("%s", %s, %.3lf))", NewFile->GetDebugInfo().getData(), fLoop ? "true" : "false", max_resume_time);
550  }
551  // Play new song directly
552  if (!NewFile->Play(fLoop, max_resume_time)) return false;
553  PlayMusicFile = NewFile;
554  NewFile->LastPlayed = SCounter++;
555  Loop = fLoop;
556 
557  // Set volume
559 
560  // Message first time a piece is played
561  if (!NewFile->HasBeenAnnounced())
562  NewFile->Announce();
563 
564  return true;
565 }
566 
568 {
569  // nothing played?
570  if (!PlayMusicFile) return;
571  // loop?
572  if (Loop)
573  if (PlayMusicFile->Play())
574  return;
575  // clear last played piece
576  Stop();
577  // force a wait time after this song?
579 }
580 
582 {
583  // Roll for scheduling a break after the next piece.
584  if (SCounter < 3) return false; // But not right away.
585  if (int32_t(UnsyncedRandom(100)) >= music_break_chance) return false;
586  if (music_break_max > 0)
587  {
588  int32_t music_break = music_break_min;
589  if (music_break_max > music_break_min) music_break += UnsyncedRandom(music_break_max - music_break_min); // note that UnsyncedRandom has limited range
590  if (music_break > 0)
591  {
592  is_waiting = true;
593  wait_time_end = C4TimeMilliseconds::Now() + music_break;
594  if (::Config.Sound.Verbose)
595  {
596  LogF("MusicSystem: Pause (%d msecs)", (int)music_break);
597  }
598  // After wait, do not resume previously started songs
599  for (C4MusicFile *check_file = Songs; check_file; check_file = check_file->pNext)
600  check_file->ClearResumePos();
601  }
602  }
603  return is_waiting;
604 }
605 
606 void C4MusicSystem::FadeOut(int fadeout_ms)
607 {
608  // Kill any previous fading music and schedule current piece to fade
609  if (PlayMusicFile)
610  {
613  PlayMusicFile = nullptr;
615  FadeTimeEnd = FadeTimeStart + fadeout_ms;
616  }
617 }
618 
620 {
621  if (PlayMusicFile)
622  {
623  PlayMusicFile->Stop();
624  PlayMusicFile=nullptr;
625  }
626  if (FadeMusicFile)
627  {
628  FadeMusicFile->Stop();
629  FadeMusicFile = nullptr;
630  }
631  return true;
632 }
633 
635 {
636  // Save volume for next file
637  int32_t config_volume = Clamp<int32_t>(Config.Sound.MusicVolume, 0, 100);
638  Volume = config_volume * game_music_level / 100;
639  // Tell it to the act file
640  if (PlayMusicFile)
641  PlayMusicFile->SetVolume(Volume);
642 }
643 
645 {
646  if (SEqualNoCase(ext, "mid"))
647  return MUSICTYPE_MID;
648 #if AUDIO_TK == AUDIO_TK_SDL_MIXER
649  else if (SEqualNoCase(ext, "xm") || SEqualNoCase(ext, "it") || SEqualNoCase(ext, "s3m") || SEqualNoCase(ext, "mod"))
650  return MUSICTYPE_MOD;
651 #ifdef USE_MP3
652  else if (SEqualNoCase(ext, "mp3"))
653  return MUSICTYPE_MP3;
654 #endif
655 #endif
656  else if (SEqualNoCase(ext, "ogg"))
657  return MUSICTYPE_OGG;
658  return MUSICTYPE_UNKNOWN;
659 }
660 
662 {
663  // search for known file extensions
664  return rGrp.FindEntry("*.mid")
665 #ifdef USE_MP3
666  || rGrp.FindEntry("*.mp3")
667 #endif
668  || rGrp.FindEntry("*.xm")
669  || rGrp.FindEntry("*.it")
670  || rGrp.FindEntry("*.s3m")
671  || rGrp.FindEntry("*.mod")
672  || rGrp.FindEntry("*.ogg");
673 }
674 
675 int C4MusicSystem::SetPlayList(const char *szPlayList, bool fForceSwitch, int fadetime_ms, double max_resume_time)
676 {
677  // Shortcut if no change
678  if (playlist_valid && playlist == szPlayList) return 0;
679  // info
680  if (::Config.Sound.Verbose)
681  {
682  LogF(R"(MusicSystem: SetPlayList("%s", %s, %d, %.3lf))", szPlayList ? szPlayList : "(null)", fForceSwitch ? "true" : "false", fadetime_ms, max_resume_time);
683  }
684  // reset
685  C4MusicFile *pFile;
686  for (pFile = Songs; pFile; pFile = pFile->pNext)
687  {
688  pFile->NoPlay = true;
689  }
690  ASongCount = 0;
691  if (szPlayList && *szPlayList)
692  {
693  // match
694  char szFileName[_MAX_FNAME + 1];
695  for (int cnt = 0; SGetModule(szPlayList, cnt, szFileName, _MAX_FNAME); cnt++)
696  for (pFile = Songs; pFile; pFile = pFile->pNext) if (pFile->NoPlay)
697  if (WildcardMatch(szFileName, GetFilename(pFile->FileName)) || pFile->HasCategory(szFileName))
698  {
699  ASongCount++;
700  pFile->NoPlay = false;
701  }
702  }
703  else
704  {
705  // default: all files except the ones beginning with an at ('@')
706  // Ignore frontend and credits music
707  for (pFile = Songs; pFile; pFile = pFile->pNext)
708  if (*GetFilename(pFile->FileName) != '@' &&
709  !pFile->HasCategory("frontend") &&
710  !pFile->HasCategory("credits"))
711  {
712  ASongCount++;
713  pFile->NoPlay = false;
714  }
715  }
716  // Force switch of music if currently playing piece is not in list or idle because no music file matched
717  if (fForceSwitch)
718  {
719  if (PlayMusicFile)
720  {
721  fForceSwitch = PlayMusicFile->NoPlay;
722  }
723  else
724  {
725  fForceSwitch = (!is_waiting || C4TimeMilliseconds::Now() >= wait_time_end);
726  }
727  if (fForceSwitch)
728  {
729  // Switch music. Switching to a break is also allowed, but won't be done if there is a piece to resume
730  // Otherwise breaks would never occur if the playlist changes often.
731  Play(nullptr, false, fadetime_ms, max_resume_time, PlayMusicFile != nullptr);
732  }
733  }
734  // Remember setting (e.g. to be saved in savegames)
735  playlist.Copy(szPlayList);
736  playlist_valid = true; // do not re-calculate available song if playlist is reset to same value in the future
737  return ASongCount;
738 }
739 
741 {
742  // // command key for music toggle pressed
743  // use different settings for game/menu (lobby also counts as "menu", so go by Game.IsRunning-flag rather than startup)
744  if (Game.IsRunning)
745  {
746  // game music
748  if (!Config.Sound.RXMusic) Stop(); else Play();
750  }
751  else
752  {
753  // game menu
755  if (!Config.Sound.FEMusic) Stop(); else Play();
756  }
757  // key processed
758  return true;
759 }
760 
762 {
763  comp->Value(mkNamingAdapt(playlist, "PlayList", StdCopyStrBuf()));
764  comp->Value(mkNamingAdapt(game_music_level, "Volume", 100));
765  comp->Value(mkNamingAdapt(music_break_min, "MusicBreakMin", DefaultMusicBreak));
766  comp->Value(mkNamingAdapt(music_break_max, "MusicBreakMax", DefaultMusicBreak));
767  comp->Value(mkNamingAdapt(music_break_chance, "MusicBreakChance", DefaultMusicBreakChance));
768  comp->Value(mkNamingAdapt(music_max_position_memory, "MusicMaxPositionMemory", DefaultMusicMaxPositionMemory));
769  // Wait time is not saved - begin savegame resume with a fresh song!
770  // Reflect loaded values immediately
771  if (comp->isDeserializer())
772  {
773  SetGameMusicLevel(game_music_level);
774  SetPlayList(playlist.getData());
775  }
776 }
777 
778 int32_t C4MusicSystem::SetGameMusicLevel(int32_t volume_percent)
779 {
780  game_music_level = Clamp<int32_t>(volume_percent, 0, 500); // allow max 5x the user setting
781  UpdateVolume();
782  return game_music_level;
783 }
784 
785 const int32_t C4MusicSystem::DefaultMusicBreak = 120000; // two minutes default music break time
786 const int32_t C4MusicSystem::DefaultMusicBreakChance = 50; // ...with a 50% chance
787 const int32_t C4MusicSystem::DefaultMusicMaxPositionMemory = 420; // after this time (in seconds) a piece is no longer continued at the position where it was interrupted
char * GetFilename(char *szPath)
Definition: StdFile.cpp:45
const char * getData() const
Definition: StdBuf.h:442
void Execute(bool force_buffer_checks=false)
bool FindEntry(const char *szWildCard, StdStrBuf *sFileName=nullptr, size_t *iSize=nullptr)
Definition: C4Group.cpp:1774
virtual StdStrBuf GetDebugInfo() const
Definition: C4MusicFile.h:52
bool IsRunning
Definition: C4Game.h:139
bool SGetModule(const char *szList, int iIndex, char *sTarget, int iSize)
Definition: Standard.cpp:511
void Announce()
Definition: C4MusicFile.cpp:40
C4Config Config
Definition: C4Config.cpp:833
void SCopy(const char *szSource, char *sTarget, size_t iMaxL)
Definition: Standard.cpp:130
C4MusicFile * FadeMusicFile
Definition: C4MusicSystem.h:63
C4Game Game
Definition: C4Globals.cpp:52
int32_t iTick35
Definition: C4Game.h:129
uint32_t UnsyncedRandom()
Definition: C4Random.cpp:58
C4GroupSet GroupSet
Definition: C4Game.h:87
#define sprintf
Definition: Standard.h:164
int32_t FEMusic
Definition: C4Config.h:128
const char * SSearch(const char *szString, const char *szIndex)
Definition: Standard.cpp:341
bool SEqualNoCase(const char *szStr1, const char *szStr2, int iLen)
Definition: Standard.cpp:185
C4MusicFile * pNext
Definition: C4MusicFile.h:35
void SelectContext()
bool Init(const char *PlayList=nullptr)
#define C4CFN_MoreMusic
Definition: C4Components.h:114
virtual bool Play(bool loop=false, double max_resume_time=0.0)=0
int SetPlayList(const char *szPlayList, bool fForceSwitch=false, int fadetime_ms=0, double max_resume_time=0.0)
void FlashMessageOnOff(const char *strWhat, bool fOn)
virtual void Stop(int fadeout_ms=0)=0
bool SEqual(const char *szStr1, const char *szStr2)
Definition: Standard.h:93
virtual void CheckIfPlaying()=0
const char * LoadResStr(const char *id)
Definition: C4Language.h:83
void LoadDir(const char *szPath)
MusicType
char * getMData()
Definition: StdBuf.h:443
StdNamingAdapt< T > mkNamingAdapt(T &&rValue, const char *szName)
Definition: StdAdaptors.h:92
void AppendChar(char cChar)
Definition: StdBuf.h:588
bool LoadFromFile(const char *szFile)
Definition: StdBuf.cpp:73
void Load(const char *szFile)
bool ScheduleWaitTime()
bool HasBeenAnnounced() const
Definition: C4MusicFile.h:56
StdStrBuf GetFullName() const
Definition: C4Group.cpp:2078
int32_t Verbose
Definition: C4Config.h:130
C4GraphicsSystem GraphicsSystem
Definition: C4Globals.cpp:51
bool Open(const char *szGroupName, bool fCreate=false)
Definition: C4Group.cpp:514
C4TimeMilliseconds wait_time_end
Definition: C4MusicSystem.h:68
void Take(char *pnData)
Definition: StdBuf.h:457
C4Group ScenarioFile
Definition: C4Game.h:86
void Append(const char *pnData, size_t iChars)
Definition: StdBuf.h:519
#define C4CFN_Music
Definition: C4Components.h:30
C4TimeMilliseconds FadeTimeEnd
Definition: C4MusicSystem.h:64
virtual bool Init(const char *strFile)
Definition: C4MusicFile.cpp:66
const char * AtSystemDataPath(const char *szFilename)
Definition: C4Config.cpp:533
bool IsLooping() const
Definition: C4MusicFile.h:54
#define _MAX_FNAME
char * GetExtension(char *szFilename)
Definition: StdFile.cpp:121
void Value(const T &rStruct)
Definition: StdCompiler.h:161
void DefaultExtension(char *szFilename, const char *szExtension)
Definition: StdFile.cpp:274
bool InitForScenario(C4Group &hGroup)
virtual double GetRemainingTime()
Definition: C4MusicFile.h:47
void FadeOut(int fadeout_ms)
virtual bool isDeserializer()
Definition: StdCompiler.h:53
int32_t SetGameMusicLevel(int32_t val)
bool IsPaused()
Definition: C4Game.cpp:1002
C4TimeMilliseconds FadeTimeStart
Definition: C4MusicSystem.h:64
virtual bool HasCategory(const char *szcat) const
Definition: C4MusicFile.h:46
virtual bool HasResumePos() const
Definition: C4MusicFile.h:48
bool FindNextEntry(const char *szWildCard, StdStrBuf *sFileName=nullptr, size_t *iSize=nullptr, bool fStartAtFilename=false)
Definition: C4Group.cpp:1780
void DeinitializeMOD()
int LastPlayed
Definition: C4MusicFile.h:36
const char * AtUserDataPath(const char *szFilename)
Definition: C4Config.cpp:526
bool WildcardMatch(const char *szWildcard, const char *szString)
Definition: StdFile.cpp:374
void CompileFunc(class StdCompiler *comp)
C4Group * FindGroup(int32_t Contents, C4Group *pAfter=nullptr, bool fSamePrio=false)
Definition: C4GroupSet.cpp:155
void ResetSearch(bool reload_contents=false)
Definition: C4Group.cpp:1013
int32_t MusicVolume
Definition: C4Config.h:131
bool GrpContainsMusic(C4Group &rGrp)
C4MusicFile * Songs
Definition: C4MusicSystem.h:55
C4ConfigSound Sound
Definition: C4Config.h:254
#define DirectorySeparator
bool LogF(const char *strMessage,...)
Definition: C4Log.cpp:250
void Copy()
Definition: StdBuf.h:467
MusicType GetMusicFileTypeByExtension(const char *ext)
C4MusicFile * upcoming_music_file
Definition: C4MusicSystem.h:63
virtual void SetVolume(int)=0
bool InitializeMOD()
static C4TimeMilliseconds Now()
void SCopyUntil(const char *szSource, char *sTarget, char cUntil, int iMaxL, int iIndex)
Definition: Standard.cpp:146
C4MusicFile * PlayMusicFile
Definition: C4MusicSystem.h:59
int32_t RXMusic
Definition: C4Config.h:127
bool Play(const char *szSongname=nullptr, bool fLoop=false, int fadetime_ms=0, double max_resume_time=0.0, bool allow_break=false)
char FileName[_MAX_FNAME+1]
Definition: C4MusicFile.h:34
#define C4GSCnt_Music
Definition: C4GroupSet.h:37