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