From 8f592c196fb334e7fa05d7f71c0fa64caada7bee Mon Sep 17 00:00:00 2001 From: toaster Date: Sat, 25 Mar 2023 23:37:07 +0000 Subject: [PATCH] Stereo mode - Minimum viable implementation - New horizontal menu - FUTURE WORK: visuals are extremely basic - Accessible from both Extras and in-game Pause if SECRET_SOUNDTEST is unlocked - Rather than the Shadow-the-Hedgehog style free select of SRB2's Sound Test, it's a Best Of The Hacks And Fan Music Sega CD player. - Back - Exit menu - Stop - Stops Stereo music entirely - Pause - Pauses Stereo music without losing place in sequence - FUTURE WORK: This should probably just pause the actual player ala minimised viewport - Play - Begins Stereo music on non-NULL musicdef entry - Track - For NULL soundtest entry: - Switches between sfx - For musicdefs with multiple tracks: - Switches between them - Prev and Next - Changes musicdef entry - FUTURE WORK: This is extremely naive and doesn't respect the following - Cup order - Unlocks - Overrides all game-requested music changes when in Play or Pause mode - This makes it an actual fun in-game feature as a menuification of the `tunes` command, not just a pure novelty. --- src/k_menu.h | 7 ++ src/k_menudraw.c | 51 ++++++++++ src/k_menufunc.c | 2 +- src/menus/extras-1.c | 26 +++++- src/menus/transient/CMakeLists.txt | 1 + src/menus/transient/pause-game.c | 9 ++ src/menus/transient/sound-test.c | 145 +++++++++++++++++++++++++++++ src/s_sound.c | 101 +++++++++++++++++++- src/s_sound.h | 12 +++ 9 files changed, 351 insertions(+), 3 deletions(-) create mode 100644 src/menus/transient/sound-test.c diff --git a/src/k_menu.h b/src/k_menu.h index bbde63198..55394c5d3 100644 --- a/src/k_menu.h +++ b/src/k_menu.h @@ -426,10 +426,14 @@ extern menuitem_t MISC_ChallengesStatsDummyMenu[]; extern menu_t MISC_ChallengesDef; extern menu_t MISC_StatisticsDef; +extern menuitem_t MISC_SoundTest[]; +extern menu_t MISC_SoundTestDef; + // We'll need this since we're gonna have to dynamically enable and disable options depending on which state we're in. typedef enum { mpause_addons = 0, + mpause_stereo, mpause_changegametype, mpause_switchmap, mpause_restartmap, @@ -1203,6 +1207,9 @@ void M_Statistics(INT32 choice); void M_DrawStatistics(void); boolean M_StatisticsInputs(INT32 ch); +void M_SoundTest(INT32 choice); +void M_DrawSoundTest(void); + // These defines make it a little easier to make menus #define DEFAULTMENUSTYLE(source, prev, x, y)\ {\ diff --git a/src/k_menudraw.c b/src/k_menudraw.c index 39144a6d8..2be547a62 100644 --- a/src/k_menudraw.c +++ b/src/k_menudraw.c @@ -5925,3 +5925,54 @@ void M_DrawStatistics(void) } #undef STATSSTEP + +void M_DrawSoundTest(void) +{ + INT32 x = currentMenu->x - menutransition.tics*48, y, i, w, cursorx = 0; + + if (gamestate == GS_MENU) + { + patch_t *bg = W_CachePatchName("M_XTRABG", PU_CACHE); + V_DrawFixedPatch(0, 0, FRACUNIT, 0, bg, NULL); + } + + y = 50; + + if (soundtest.current != NULL) + { + V_DrawThinString(x, y, (soundtest.playing ? highlightflags : 0)|V_ALLOWLOWERCASE|V_6WIDTHSPACE, soundtest.current->title); + V_DrawThinString(x, (y += 10), V_ALLOWLOWERCASE|V_6WIDTHSPACE, va("%d", soundtest.currenttrack)); + if (soundtest.current->author) + V_DrawThinString(x, (y += 10), V_ALLOWLOWERCASE|V_6WIDTHSPACE, soundtest.current->author); + if (soundtest.current->source) + V_DrawThinString(x, (y += 10), V_ALLOWLOWERCASE|V_6WIDTHSPACE, soundtest.current->source); + if (soundtest.current->composers) + V_DrawThinString(x, (y += 10), V_ALLOWLOWERCASE|V_6WIDTHSPACE, soundtest.current->composers); + } + else + { + const char *sfxstr = (cv_soundtest.value) ? S_sfx[cv_soundtest.value].name : "N/A"; + V_DrawThinString(x, y, V_ALLOWLOWERCASE|V_6WIDTHSPACE, sfxstr); + V_DrawThinString(x, (y += 10), V_ALLOWLOWERCASE|V_6WIDTHSPACE, va("%d", cv_soundtest.value)); + } + + y = currentMenu->y; + + for (i = 0; i < currentMenu->numitems; i++) + { + w = V_ThinStringWidth(currentMenu->menuitems[i].text, V_6WIDTHSPACE); + if (i == itemOn) + { + cursorx = x + w/2; + V_DrawThinString(x, y, V_6WIDTHSPACE|highlightflags, currentMenu->menuitems[i].text); + } + else + { + V_DrawThinString(x, y, V_6WIDTHSPACE, currentMenu->menuitems[i].text); + } + x += w + 8; + } + + V_DrawCharacter(cursorx - 4, currentMenu->y - 8 - (skullAnimCounter/5), + '\x1B' | V_SNAPTOTOP|highlightflags, false); // up arrow +} diff --git a/src/k_menufunc.c b/src/k_menufunc.c index 608cf8a08..1915dfa5b 100644 --- a/src/k_menufunc.c +++ b/src/k_menufunc.c @@ -382,7 +382,7 @@ void M_PlayMenuJam(void) return; } - if (Playing()) + if (Playing() || soundtest.playing) return; if (refMenu != NULL && refMenu->music != NULL) diff --git a/src/menus/extras-1.c b/src/menus/extras-1.c index fe6f9e750..e8ef11896 100644 --- a/src/menus/extras-1.c +++ b/src/menus/extras-1.c @@ -22,6 +22,9 @@ menuitem_t EXTRAS_Main[] = {IT_STRING | IT_CALL, NULL, NULL, NULL, {.routine = M_ReplayHut}, 0, 0}, + + {IT_STRING | IT_CALL, NULL, NULL, + NULL, {.routine = M_SoundTest}, 0, 0}, }; // the extras menu essentially reuses the options menu stuff @@ -49,6 +52,8 @@ struct extrasmenu_s extrasmenu; void M_InitExtras(INT32 choice) { + UINT32 maxvalid = 2; + (void)choice; extrasmenu.ticker = 0; @@ -82,6 +87,7 @@ void M_InitExtras(INT32 choice) EXTRAS_Main[3].status = IT_STRING | IT_CALL; EXTRAS_Main[3].text = "Egg TV"; EXTRAS_Main[3].tooltip = "Watch the replays you've saved throughout your many races & battles!"; + maxvalid = 3; } else { @@ -89,7 +95,25 @@ void M_InitExtras(INT32 choice) EXTRAS_Main[3].text = EXTRAS_Main[3].tooltip = "???"; if (EXTRAS_MainDef.lastOn == 3) { - EXTRAS_MainDef.lastOn = 2; + EXTRAS_MainDef.lastOn = maxvalid; + } + } + + // Stereo Mode + if (M_SecretUnlocked(SECRET_SOUNDTEST, true)) + { + EXTRAS_Main[4].status = IT_STRING | IT_CALL; + EXTRAS_Main[4].text = "Stereo Mode"; + EXTRAS_Main[4].tooltip = "You can listen to your favourite tunes here!"; + maxvalid = 4; + } + else + { + EXTRAS_Main[4].status = IT_STRING | IT_TRANSTEXT; + EXTRAS_Main[4].text = EXTRAS_Main[4].tooltip = "???"; + if (EXTRAS_MainDef.lastOn == 4) + { + EXTRAS_MainDef.lastOn = maxvalid; } } diff --git a/src/menus/transient/CMakeLists.txt b/src/menus/transient/CMakeLists.txt index bea478d5f..a1d0cd1c6 100644 --- a/src/menus/transient/CMakeLists.txt +++ b/src/menus/transient/CMakeLists.txt @@ -4,6 +4,7 @@ target_sources(SRB2SDL2 PRIVATE level-select.c gametype.c manual.c + sound-test.c message-box.c pause-game.c pause-replay.c diff --git a/src/menus/transient/pause-game.c b/src/menus/transient/pause-game.c index 941673ad4..f6ad377e0 100644 --- a/src/menus/transient/pause-game.c +++ b/src/menus/transient/pause-game.c @@ -15,6 +15,9 @@ menuitem_t PAUSE_Main[] = {IT_STRING | IT_CALL, "ADDONS", "M_ICOADD", NULL, {.routine = M_Addons}, 0, 0}, + {IT_STRING | IT_CALL, "STEREO MODE", "M_ICOSTM", + NULL, {.routine = M_SoundTest}, 0, 0}, + {IT_STRING | IT_KEYHANDLER, "GAMETYPE", "M_ICOGAM", NULL, {.routine = M_HandlePauseMenuGametype}, 0, 0}, @@ -111,6 +114,7 @@ void M_OpenPauseMenu(void) // By default, disable anything sensitive: PAUSE_Main[mpause_addons].status = IT_DISABLED; + PAUSE_Main[mpause_stereo].status = IT_DISABLED; PAUSE_Main[mpause_changegametype].status = IT_DISABLED; PAUSE_Main[mpause_switchmap].status = IT_DISABLED; PAUSE_Main[mpause_restartmap].status = IT_DISABLED; @@ -127,6 +131,11 @@ void M_OpenPauseMenu(void) Dummymenuplayer_OnChange(); // Make sure the consvar is within bounds of the amount of splitscreen players we have. + if (M_SecretUnlocked(SECRET_SOUNDTEST, true)) + { + PAUSE_Main[mpause_stereo].status = IT_STRING | IT_CALL; + } + if (K_CanChangeRules(false)) { PAUSE_Main[mpause_psetup].status = IT_STRING | IT_CALL; diff --git a/src/menus/transient/sound-test.c b/src/menus/transient/sound-test.c new file mode 100644 index 000000000..946ac6aaf --- /dev/null +++ b/src/menus/transient/sound-test.c @@ -0,0 +1,145 @@ +/// \file menus/transient/sound-test.c +/// \brief Stereo Mode menu + +#include "../../k_menu.h" +#include "../../s_sound.h" + +static void M_SoundTestMainControl(INT32 choice) +{ + (void)choice; + + // Sound test exception + if (soundtest.current == NULL || soundtest.current->numtracks == 0) + { + if (currentMenu->menuitems[itemOn].mvar1 == 1) // Play + { + soundtest.playing = true; + //soundtest.sequence = true; + S_UpdateSoundTestDef(false, false); + } + else if (cv_soundtest.value != 0) + { + S_StopSounds(); + + if (currentMenu->menuitems[itemOn].mvar1 == 0) // Stop + { + CV_SetValue(&cv_soundtest, 0); + } + } + + return; + } + + if (currentMenu->menuitems[itemOn].mvar1 == 1) // Play + { + S_SoundTestPlay(); + } + else if (soundtest.playing == true) + { + S_SoundTestStop((currentMenu->menuitems[itemOn].mvar1 == 2)); + } +} + +static void M_SoundTestNextPrev(INT32 choice) +{ + (void)choice; + + S_UpdateSoundTestDef((currentMenu->menuitems[itemOn].mvar1 < 0), false); +} + +static void M_SoundTestTrack(INT32 choice) +{ + const UINT8 numtracks = (soundtest.current != NULL) ? soundtest.current->numtracks : 0; + + if (numtracks == 1) + { + return; + } + + // Soundtest exception + if (numtracks == 0) + { + S_StopSounds(); + + if (choice == -1) // Extra + { + if (cv_soundtest.value != 0) + CV_SetValue(&cv_soundtest, 0); + } + else if (choice == 2) // Confirm + { + if (cv_soundtest.value != 0) + S_StartSound(NULL, cv_soundtest.value); + } + else // Up or Down + { + CV_AddValue(&cv_soundtest, ((choice == 0) ? -1 : 1)); + } + + return; + } + + if (choice == -1) // Extra + { + soundtest.currenttrack = 0; + } + else + { + // Confirm resets the current instance + if (choice == 0) // Down + { + soundtest.currenttrack--; + if (soundtest.currenttrack < 0) + soundtest.currenttrack = numtracks-1; + } + else if (choice == 1) // Up + { + soundtest.currenttrack++; + if (soundtest.currenttrack >= numtracks) + soundtest.currenttrack = 0; + } + } + + if (soundtest.playing) + { + S_SoundTestPlay(); + } +} + +menuitem_t MISC_SoundTest[] = +{ + {IT_STRING | IT_CALL, "Back", NULL, NULL, {.routine = M_GoBack}, 0, 0}, + {IT_STRING | IT_CALL, "Stop", NULL, NULL, {.routine = M_SoundTestMainControl}, 0, 0}, + {IT_STRING | IT_CALL, "Pause", NULL, NULL, {.routine = M_SoundTestMainControl}, 2, 0}, + {IT_STRING | IT_CALL, "Play", NULL, NULL, {.routine = M_SoundTestMainControl}, 1, 0}, + {IT_STRING | IT_ARROWS, "Track", NULL, NULL, {.routine = M_SoundTestTrack}, 0, 0}, + {IT_STRING | IT_CALL, "Prev", NULL, NULL, {.routine = M_SoundTestNextPrev}, -1, 0}, + {IT_STRING | IT_CALL, "Next", NULL, NULL, {.routine = M_SoundTestNextPrev}, 1, 0}, +}; + +menu_t MISC_SoundTestDef = { + sizeof (MISC_SoundTest)/sizeof (menuitem_t), + &MainDef, + 0, + MISC_SoundTest, + 42, BASEVIDHEIGHT/2, + 0, 0, + MBF_UD_LR_FLIPPED|MBF_SOUNDLESS, + ".", + 98, 0, + M_DrawSoundTest, + NULL, + NULL, + NULL, + NULL, +}; + +void M_SoundTest(INT32 choice) +{ + (void)choice; + + // I reserve the right to add some sort of setup here -- toast 250323 + + MISC_SoundTestDef.prevMenu = currentMenu; + M_SetupNextMenu(&MISC_SoundTestDef, false); +} diff --git a/src/s_sound.c b/src/s_sound.c index 91718ad91..5c5483b01 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -1360,9 +1360,108 @@ static tic_t pause_starttic; musicdef_t *musicdefstart = NULL; struct cursongcredit cursongcredit; // Currently displayed song credit info +struct soundtest soundtest; // Sound Test (sound test) + +void S_UpdateSoundTestDef(boolean reverse, boolean skipnull) +{ + musicdef_t *newdef; + + // Naive implementation for now. + + newdef = (soundtest.current != NULL + && (skipnull == false || soundtest.current->next != NULL)) + ? soundtest.current->next + : musicdefstart; + + if (reverse == false) + { + // Due to how musicdefs are populated, we have to traverse backwards when attempting forwards. + musicdef_t *def; + + if (soundtest.current == musicdefstart) + { + newdef = NULL; + if (skipnull == false) + { + goto conclusion; + } + } + + for (def = musicdefstart; def; def = def->next) + { + if (def->next != soundtest.current) + continue; + + newdef = def; + break; + } + } + +conclusion: + soundtest.current = newdef; + soundtest.currenttrack = 0; + + if (soundtest.playing == true) + { + if (newdef == NULL) + { + S_SoundTestStop(false); + } + else + { + S_SoundTestPlay(); + } + } +} + +void S_SoundTestPlay(void) +{ + soundtest.privilegedrequest = true; + + S_StopMusic(); + + soundtest.playing = true; + + S_ChangeMusicInternal(soundtest.current->name[soundtest.currenttrack], true); + S_ShowMusicCredit(); + + soundtest.privilegedrequest = false; +} + +void S_SoundTestStop(boolean pause) +{ + if (soundtest.playing == false) + { + return; + } + + soundtest.privilegedrequest = true; + + S_StopMusic(); + cursongcredit.def = NULL; + + if (pause == false) + { + soundtest.playing = false; + soundtest.current = NULL; + soundtest.currenttrack = 0; + + if (gamestate == GS_LEVEL) + { + P_RestoreMusic(&players[consoleplayer]); + } + } + + soundtest.privilegedrequest = false; +} + boolean S_PlaysimMusicDisabled(void) { - return (demo.rewinding // Don't mess with music while rewinding! + if (soundtest.privilegedrequest) + return false; + + return (soundtest.playing // Ring Racers: Stereo Mode + || demo.rewinding // Don't mess with music while rewinding! || demo.title); // SRB2Kart: Demos don't interrupt title screen music } diff --git a/src/s_sound.h b/src/s_sound.h index 6a02053f4..3936e83ae 100644 --- a/src/s_sound.h +++ b/src/s_sound.h @@ -200,6 +200,18 @@ extern struct cursongcredit fixed_t old_x; } cursongcredit; +extern struct soundtest +{ + boolean playing; // Music is playing? + boolean privilegedrequest; // Overrides S_PlaysimMusicDisabled w/o changing every function signature + musicdef_t *current; // Current selected music definition + SINT8 currenttrack; // Current selected music track for definition +} soundtest; + +void S_UpdateSoundTestDef(boolean reverse, boolean skipnull); +void S_SoundTestPlay(void); +void S_SoundTestStop(boolean pause); + boolean S_PlaysimMusicDisabled(void); extern musicdef_t *musicdefstart;