diff --git a/src/dehacked.c b/src/dehacked.c index 8057f26d8..0d2315c16 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -8658,6 +8658,23 @@ struct { {"GT_HIDEANDSEEK",GT_HIDEANDSEEK}, {"GT_CTF",GT_CTF}, + // Jingles (jingletype_t) + {"JT_NONE",JT_NONE}, + {"JT_OTHER",JT_OTHER}, + {"JT_MASTER",JT_MASTER}, + {"JT_1UP",JT_1UP}, + {"JT_SHOES",JT_SHOES}, + {"JT_INV",JT_INV}, + {"JT_MINV",JT_MINV}, + {"JT_DROWN",JT_DROWN}, + {"JT_SUPER",JT_SUPER}, + {"JT_GOVER",JT_GOVER}, + {"JT_NIGHTSTIMEOUT",JT_NIGHTSTIMEOUT}, + {"JT_SSTIMEOUT",JT_SSTIMEOUT}, + // {"JT_LCLEAR",JT_LCLEAR}, + // {"JT_RACENT",JT_RACENT}, + // {"JT_CONTSC",JT_CONTSC}, + // Player state (playerstate_t) {"PST_LIVE",PST_LIVE}, // Playing or camping. {"PST_DEAD",PST_DEAD}, // Dead on the ground, view follows killer. diff --git a/src/p_enemy.c b/src/p_enemy.c index 2861f4480..0050e7bb7 100644 --- a/src/p_enemy.c +++ b/src/p_enemy.c @@ -4074,12 +4074,11 @@ void A_Invincibility(mobj_t *actor) if (P_IsLocalPlayer(player) && !player->powers[pw_super]) { - S_StopMusic(); if (mariomode) G_GhostAddColor(GHC_INVINCIBLE); strlcpy(S_sfx[sfx_None].caption, "Invincibility", 14); S_StartCaption(sfx_None, -1, player->powers[pw_invulnerability]); - S_ChangeMusicInternal((mariomode) ? "_minv" : "_inv", false); + P_PlayJingle(player, (mariomode) ? JT_MINV : JT_INV); } } @@ -4113,10 +4112,7 @@ void A_SuperSneakers(mobj_t *actor) if (S_SpeedMusic(0.0f) && (mapheaderinfo[gamemap-1]->levelflags & LF_SPEEDMUSIC)) S_SpeedMusic(1.4f); else - { - S_StopMusic(); - S_ChangeMusicInternal("_shoes", false); - } + P_PlayJingle(player, JT_SHOES); strlcpy(S_sfx[sfx_None].caption, "Speed shoes", 12); S_StartCaption(sfx_None, -1, player->powers[pw_sneakers]); } diff --git a/src/p_inter.c b/src/p_inter.c index cd1e80de2..e42e3798d 100644 --- a/src/p_inter.c +++ b/src/p_inter.c @@ -1669,7 +1669,10 @@ void P_TouchSpecialThing(mobj_t *special, mobj_t *toucher, boolean heightcheck) // Eaten by player! if ((!player->bot) && (player->powers[pw_underwater] && player->powers[pw_underwater] <= 12*TICRATE + 1)) + { + player->powers[pw_underwater] = underwatertics + 1; P_RestoreMusic(player); + } if (player->powers[pw_underwater] < underwatertics + 1) player->powers[pw_underwater] = underwatertics + 1; @@ -2436,10 +2439,7 @@ void P_KillMobj(mobj_t *target, mobj_t *inflictor, mobj_t *source, UINT8 damaget gameovermus = true; if (gameovermus) - { - S_StopMusic(); // Stop the Music! Tails 03-14-2000 - S_ChangeMusicInternal("_gover", false); // Yousa dead now, Okieday? Tails 03-14-2000 - } + P_PlayJingle(target->player, JT_GOVER); // Yousa dead now, Okieday? Tails 03-14-2000 if (!(netgame || multiplayer || demoplayback || demorecording || metalrecording || modeattacking) && numgameovers < maxgameovers) { @@ -2881,7 +2881,7 @@ static inline void P_NiGHTSDamage(mobj_t *target, mobj_t *source) S_StartSound(NULL, sfx_timeup); // that creepy "out of time" music from NiGHTS. } else - S_ChangeMusicInternal((((maptol & TOL_NIGHTS) && !G_IsSpecialStage(gamemap)) ? "_ntime" : "_drown"), false); + P_PlayJingle(player, ((maptol & TOL_NIGHTS) && !G_IsSpecialStage(gamemap)) ? JT_NIGHTSTIMEOUT : JT_SSTIMEOUT); } } } diff --git a/src/p_local.h b/src/p_local.h index 2149a95bf..b885ae948 100644 --- a/src/p_local.h +++ b/src/p_local.h @@ -200,6 +200,47 @@ boolean P_GetLives(player_t *player); boolean P_SpectatorJoinGame(player_t *player); void P_RestoreMultiMusic(player_t *player); +/// ------------------------ +/// Jingle stuff +/// ------------------------ + +typedef enum +{ + JT_NONE, // Null state + JT_OTHER, // Other state + JT_MASTER, // Main level music + JT_1UP, // Extra life + JT_SHOES, // Speed shoes + JT_INV, // Invincibility + JT_MINV, // Mario Invincibility + JT_DROWN, // Drowning + JT_SUPER, // Super Sonic + JT_GOVER, // Game Over + JT_NIGHTSTIMEOUT, // NiGHTS Time Out (10 seconds) + JT_SSTIMEOUT, // NiGHTS Special Stage Time Out (10 seconds) + + // these are not jingles + // JT_LCLEAR, // Level Clear + // JT_RACENT, // Multiplayer Intermission + // JT_CONTSC, // Continue + + NUMJINGLES +} jingletype_t; + +typedef struct +{ + char musname[7]; + boolean looping; +} jingle_t; + +extern jingle_t jingleinfo[NUMJINGLES]; + +#define JINGLEPOSTFADE 1000 + +void P_PlayJingle(player_t *player, jingletype_t jingletype); +boolean P_EvaluateMusicStatus(UINT16 status); +void P_PlayJingleMusic(player_t *player, const char *musname, UINT16 musflags, boolean looping, UINT16 status); + // // P_MOBJ // diff --git a/src/p_tick.c b/src/p_tick.c index 6f7c96ead..ba56639b2 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -573,13 +573,20 @@ void P_Ticker(boolean run) OP_ObjectplaceMovement(&players[0]); P_MoveChaseCamera(&players[0], &camera, false); P_MapEnd(); + S_SetStackAdjustmentStart(); return; } } // Check for pause or menu up in single player if (paused || P_AutoPause()) + { + S_SetStackAdjustmentStart(); return; + } + + if (!S_MusicPaused()) + S_AdjustMusicStackTics(); postimgtype = postimgtype2 = postimg_none; diff --git a/src/p_user.c b/src/p_user.c index ce2d90455..b3336344a 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -56,6 +56,29 @@ static void P_NukeAllPlayers(player_t *player); #endif +// +// Jingle stuff. +// + +jingle_t jingleinfo[NUMJINGLES] = { + // {musname, looping, reset, nest} + {"" , false}, // JT_NONE + {"" , false}, // JT_OTHER + {"" , false}, // JT_MASTER + {"xtlife" , false}, + {"shoes" , true}, + {"invinc" , false}, + {"minvnc" , false}, + {"drown" , false}, + {"supers" , true}, + {"gmover" , false}, + {"drown" , false}, // JT_NIGHTSTIMEOUT + {"drown" , false} // JT_SSTIMEOUT + // {"lclear" , false}, + // {"racent" , true}, + // {"contsc" , true} +}; + // // Movement. // @@ -664,7 +687,10 @@ static void P_DeNightserizePlayer(player_t *player) S_SetMusicPosition(0); } else + { + music_stack_fadein = 0; // HACK: Change fade-in for restore music P_RestoreMusic(player); + } P_RunDeNightserizeExecutors(player->mo); } @@ -720,7 +746,10 @@ void P_NightserizePlayer(player_t *player, INT32 nighttime) S_SetInternalMusicVolume(100); } else + { + music_stack_fadein = 0; // HACK: Change fade-in for restore music P_RestoreMusic(player); + } P_SetPlayerMobjState(player->mo, S_PLAY_NIGHTS_TRANS1); @@ -1121,10 +1150,7 @@ void P_DoSuperTransformation(player_t *player, boolean giverings) { player->powers[pw_super] = 1; if (!(mapheaderinfo[gamemap-1]->levelflags & LF_NOSSMUSIC) && P_IsLocalPlayer(player)) - { - S_StopMusic(); - S_ChangeMusicInternal("_super", true); - } + P_PlayJingle(player, JT_SUPER); S_StartSound(NULL, sfx_supert); //let all players hear it -mattw_cfi @@ -1290,14 +1316,114 @@ void P_PlayLivesJingle(player_t *player) else { if (player) - player->powers[pw_extralife] = extralifetics+1; - S_StopMusic(); // otherwise it won't restart if this is done twice in a row + player->powers[pw_extralife] = extralifetics + 1; strlcpy(S_sfx[sfx_None].caption, "One-up", 7); S_StartCaption(sfx_None, -1, extralifetics+1); - S_ChangeMusicInternal("_1up", false); + P_PlayJingle(player, JT_1UP); } } +void P_PlayJingle(player_t *player, jingletype_t jingletype) +{ + const char *musname = jingleinfo[jingletype].musname; + UINT16 musflags = 0; + boolean looping = jingleinfo[jingletype].looping; + + char newmusic[7]; + strncpy(newmusic, musname, 7); +#if defined(HAVE_BLUA) && defined(HAVE_LUA_MUSICPLUS) + if(LUAh_MusicJingle(jingletype, newmusic, &musflags, &looping)) + return; +#endif + newmusic[6] = 0; + + P_PlayJingleMusic(player, newmusic, musflags, looping, jingletype); +} + +// +// P_PlayJingleMusic +// +void P_PlayJingleMusic(player_t *player, const char *musname, UINT16 musflags, boolean looping, UINT16 status) +{ + if (!P_IsLocalPlayer(player)) + return; + + S_RetainMusic(musname, musflags, looping, 0, status); + S_StopMusic(); + S_ChangeMusicInternal(musname, looping); +} + +boolean P_EvaluateMusicStatus(UINT16 status) +{ + // \todo lua hook + int i; + boolean result = false; + + for (i = 0; i < MAXPLAYERS; i++) + { + if (!P_IsLocalPlayer(&players[i])) + continue; + + switch(status) + { + case JT_1UP: // Extra life + result = (players[i].powers[pw_extralife] > 1); + break; + + case JT_SHOES: // Speed shoes + if (players[i].powers[pw_sneakers] > 1 && !players[i].powers[pw_super]) + { + //strlcpy(S_sfx[sfx_None].caption, "Speed shoes", 12); + //S_StartCaption(sfx_None, -1, players[i].powers[pw_sneakers]); + result = true; + } + else + result = false; + break; + + case JT_INV: // Invincibility + case JT_MINV: // Mario Invincibility + if (players[i].powers[pw_invulnerability] > 1) + { + //strlcpy(S_sfx[sfx_None].caption, "Invincibility", 14); + //S_StartCaption(sfx_None, -1, players[i].powers[pw_invulnerability]); + result = true; + } + else + result = false; + break; + + case JT_DROWN: // Drowning + result = (players[i].powers[pw_underwater] && players[i].powers[pw_underwater] <= 11*TICRATE + 1); + break; + + case JT_SUPER: // Super Sonic + result = (players[i].powers[pw_super] && !(mapheaderinfo[gamemap-1]->levelflags & LF_NOSSMUSIC)); + break; + + case JT_GOVER: // Game Over + result = (players[i].lives <= 0); + break; + + case JT_NIGHTSTIMEOUT: // NiGHTS Time Out (10 seconds) + case JT_SSTIMEOUT: + result = (players[i].nightstime && players[i].nightstime <= 10*TICRATE); + break; + + case JT_NONE: // Null state + case JT_OTHER: // Other state + case JT_MASTER: // Main level music + default: + result = true; + } + + if (result) + break; + } + + return result; + } + // // P_RestoreMusic // @@ -1308,17 +1434,30 @@ void P_RestoreMusic(player_t *player) if (!P_IsLocalPlayer(player)) // Only applies to a local player return; + S_SpeedMusic(1.0f); + + // Jingles have a priority in this order, so follow it + // and as a default case, go down the music stack. + + // Extra life if (player->powers[pw_extralife] > 1) return; - S_SpeedMusic(1.0f); - if (player->powers[pw_super] && !(mapheaderinfo[gamemap-1]->levelflags & LF_NOSSMUSIC)) - S_ChangeMusicInternal("_super", true); + + // Super + else if (player->powers[pw_super] && !(mapheaderinfo[gamemap-1]->levelflags & LF_NOSSMUSIC) + && !S_RecallMusic(JT_SUPER, false)) + P_PlayJingle(player, JT_SUPER); + + // Invulnerability else if (player->powers[pw_invulnerability] > 1) { strlcpy(S_sfx[sfx_None].caption, "Invincibility", 14); S_StartCaption(sfx_None, -1, player->powers[pw_invulnerability]); - S_ChangeMusicInternal((mariomode) ? "_minv" : "_inv", false); + if (!S_RecallMusic(JT_INV, false) && !S_RecallMusic(JT_MINV, false)) + P_PlayJingle(player, (mariomode) ? JT_MINV : JT_INV); } + + // Shoes else if (player->powers[pw_sneakers] > 1 && !player->powers[pw_super]) { strlcpy(S_sfx[sfx_None].caption, "Speed shoes", 12); @@ -1326,13 +1465,19 @@ void P_RestoreMusic(player_t *player) if (mapheaderinfo[gamemap-1]->levelflags & LF_SPEEDMUSIC) { S_SpeedMusic(1.4f); - S_ChangeMusicEx(mapmusname, mapmusflags, true, mapmusposition, 0, 0); + if (!S_RecallMusic(JT_MASTER, true)) + S_ChangeMusicEx(mapmusname, mapmusflags, true, mapmusposition, 0, 0); } - else - S_ChangeMusicInternal("_shoes", true); + else if (!S_RecallMusic(JT_SHOES, false)) + P_PlayJingle(player, JT_SHOES); } - else - S_ChangeMusicEx(mapmusname, mapmusflags, true, mapmusposition, 0, 0); + + // Default + else if (!S_RecallMusic(JT_NONE, false)) // go down the stack + { + CONS_Debug(DBG_BASIC, "Cannot find any music in resume stack!\n"); + S_ChangeMusicEx(mapmusname, mapmusflags, true, mapmusposition, 0, 0); + } } // @@ -1655,9 +1800,12 @@ void P_SwitchShield(player_t *player, UINT16 shieldtype) if (shieldtype & SH_PROTECTWATER) { if (player->powers[pw_underwater] && player->powers[pw_underwater] <= 12*TICRATE + 1) + { + player->powers[pw_underwater] = 0; P_RestoreMusic(player); - - player->powers[pw_underwater] = 0; + } + else + player->powers[pw_underwater] = 0; if (player->powers[pw_spacetime] > 1) { @@ -2444,10 +2592,6 @@ static void P_CheckQuicksand(player_t *player) // static void P_CheckSneakerAndLivesTimer(player_t *player) { - if ((player->powers[pw_underwater] <= 11*TICRATE + 1) - && (player->powers[pw_underwater] > 1)) - return; // don't restore music if drowning music is playing - if (player->powers[pw_extralife] == 1) // Extra Life! P_RestoreMusic(player); @@ -2515,16 +2659,16 @@ static void P_CheckUnderwaterAndSpaceTimer(player_t *player) if (!(player->mo->eflags & MFE_UNDERWATER) && player->powers[pw_underwater]) { if (player->powers[pw_underwater] <= 12*TICRATE + 1) + { + player->powers[pw_underwater] = 0; P_RestoreMusic(player); - - player->powers[pw_underwater] = 0; + } + else + player->powers[pw_underwater] = 0; } if (player->powers[pw_spacetime] > 1 && !P_InSpaceSector(player->mo)) - { - P_RestoreMusic(player); player->powers[pw_spacetime] = 0; - } // Underwater audio cues if (P_IsLocalPlayer(player) && !player->bot) @@ -2537,8 +2681,7 @@ static void P_CheckUnderwaterAndSpaceTimer(player_t *player) if (player->powers[pw_underwater] == 11*TICRATE + 1 && player == &players[consoleplayer]) { - S_StopMusic(); - S_ChangeMusicInternal("_drown", false); + P_PlayJingle(player, JT_DROWN); } } } @@ -2585,10 +2728,6 @@ static void P_CheckInvincibilityTimer(player_t *player) P_SpawnShieldOrb(player); } - if ((player->powers[pw_underwater] <= 11*TICRATE + 1) - && (player->powers[pw_underwater] > 1)) - return; // don't restore music if drowning music is playing - if (!player->powers[pw_super] || (mapheaderinfo[gamemap-1]->levelflags & LF_NOSSMUSIC)) P_RestoreMusic(player); } @@ -3801,6 +3940,8 @@ static void P_DoSuperStuff(player_t *player) { player->powers[pw_super] = 0; P_SetPlayerMobjState(player->mo, S_PLAY_STND); + music_stack_noposition = true; // HACK: Do not reposition next music + music_stack_fadeout = MUSICRATE/2; // HACK: Fade out current music P_RestoreMusic(player); P_SpawnShieldOrb(player); @@ -3882,6 +4023,8 @@ static void P_DoSuperStuff(player_t *player) } // Resume normal music if you're the console player + music_stack_noposition = true; // HACK: Do not reposition next music + music_stack_fadeout = MUSICRATE/2; // HACK: Fade out current music P_RestoreMusic(player); // If you had a shield, restore its visual significance. @@ -6388,7 +6531,7 @@ static void P_NiGHTSMovement(player_t *player) S_StartSound(NULL, sfx_timeup); // that creepy "out of time" music from NiGHTS. } else - S_ChangeMusicInternal((((maptol & TOL_NIGHTS) && !G_IsSpecialStage(gamemap)) ? "_ntime" : "_drown"), false); + P_PlayJingle(player, ((maptol & TOL_NIGHTS) && !G_IsSpecialStage(gamemap)) ? JT_NIGHTSTIMEOUT : JT_SSTIMEOUT); } if (player->mo->z < player->mo->floorz) @@ -10272,7 +10415,7 @@ void P_PlayerThink(player_t *player) // If 11 seconds are left on the timer, // begin the drown music for countdown! if (countdown == 11*TICRATE - 1 && P_IsLocalPlayer(player)) - S_ChangeMusicInternal("_drown", false); + P_PlayJingle(player, JT_DROWN); // If you've hit the countdown and you haven't made // it to the exit, you're a goner! @@ -10761,9 +10904,12 @@ void P_PlayerThink(player_t *player) if (player->powers[pw_underwater] && (player->pflags & PF_GODMODE || (player->powers[pw_shield] & SH_PROTECTWATER))) { if (player->powers[pw_underwater] <= 12*TICRATE+1) + { + player->powers[pw_underwater] = 0; P_RestoreMusic(player); //incase they were about to drown - - player->powers[pw_underwater] = 0; + } + else + player->powers[pw_underwater] = 0; } else if (player->powers[pw_underwater] && !(maptol & TOL_NIGHTS) && !((netgame || multiplayer) && player->spectator)) // underwater timer player->powers[pw_underwater]--; diff --git a/src/s_sound.c b/src/s_sound.c index 2db8392d7..3ef32513b 100644 --- a/src/s_sound.c +++ b/src/s_sound.c @@ -1392,6 +1392,8 @@ static boolean queue_looping; static UINT32 queue_position; static UINT32 queue_fadeinms; +static tic_t pause_starttic; + /// ------------------------ /// Music Status /// ------------------------ @@ -1490,6 +1492,260 @@ UINT32 S_GetMusicPosition(void) return I_GetSongPosition(); } +/// ------------------------ +/// Music Stacking (Jingles) +/// In this section: mazmazz doesn't know how to do dynamic arrays or struct pointers! +/// ------------------------ + +static musicstack_t *music_stacks = NULL; +static musicstack_t *last_music_stack = NULL; + +void S_SetStackAdjustmentStart(void) +{ + if (!pause_starttic) + pause_starttic = gametic; +} + +void S_AdjustMusicStackTics(void) +{ + if (pause_starttic) + { + musicstack_t *mst; + for (mst = music_stacks; mst; mst = mst->next) + mst->tic += gametic - pause_starttic; + pause_starttic = 0; + } +} + +static void S_ResetMusicStack(void) +{ + musicstack_t *mst, *mst_next; + for (mst = music_stacks; mst; mst = mst_next) + { + mst_next = mst->next; + Z_Free(mst); + } + music_stacks = last_music_stack = NULL; +} + +static void S_RemoveMusicStackEntry(musicstack_t *entry) +{ + musicstack_t *mst; + for (mst = music_stacks; mst; mst = mst->next) + { + if (mst == entry) + { + // Remove ourselves from the chain and link + // prev and next together + + if (mst->prev) + mst->prev->next = mst->next; + else + music_stacks = mst->next; + + if (mst->next) + mst->next->prev = mst->prev; + else + last_music_stack = mst->prev; + + break; + } + } + Z_Free(entry); +} + +static void S_RemoveMusicStackEntryByStatus(UINT16 status) +{ + musicstack_t *mst, *mst_next; + + if (!status) + return; + + for (mst = music_stacks; mst; mst = mst_next) + { + mst_next = mst->next; + if (mst->status == status) + S_RemoveMusicStackEntry(mst); + } +} + +static void S_AddMusicStackEntry(const char *mname, UINT16 mflags, boolean looping, UINT32 position, UINT16 status) +{ + musicstack_t *mst, *new_mst; + + // if the first entry is empty, force master onto it + if (!music_stacks) + { + music_stacks = Z_Calloc(sizeof (*mst), PU_MUSIC, NULL); + strncpy(music_stacks->musname, (status == JT_MASTER ? mname : mapmusname), 7); + music_stacks->musflags = (status == JT_MASTER ? mflags : mapmusflags); + music_stacks->looping = (status == JT_MASTER ? looping : true); + music_stacks->position = (status == JT_MASTER ? position : S_GetMusicPosition()); + music_stacks->tic = gametic; + music_stacks->status = JT_MASTER; + + if (status == JT_MASTER) + return; // we just added the user's entry here + } + + // look for an empty slot to park ourselves + for (mst = music_stacks; mst->next; mst = mst->next); + + // create our new entry + new_mst = Z_Calloc(sizeof (*new_mst), PU_MUSIC, NULL); + strncpy(new_mst->musname, mname, 7); + new_mst->musname[6] = 0; + new_mst->musflags = mflags; + new_mst->looping = looping; + new_mst->position = position; + new_mst->tic = gametic; + new_mst->status = status; + + mst->next = new_mst; + new_mst->prev = mst; + new_mst->next = NULL; + last_music_stack = new_mst; +} + +static musicstack_t *S_GetMusicStackEntry(UINT16 status, boolean fromfirst, INT16 startindex) +{ + musicstack_t *mst, *start_mst = NULL, *mst_next; + + // if the first entry is empty, force master onto it + // fixes a memory corruption bug + if (!music_stacks && status != JT_MASTER) + S_AddMusicStackEntry(mapmusname, mapmusflags, true, S_GetMusicPosition(), JT_MASTER); + + if (startindex >= 0) + { + INT16 i = 0; + for (mst = music_stacks; mst && i <= startindex; mst = mst->next, i++) + start_mst = mst; + } + else + start_mst = (fromfirst ? music_stacks : last_music_stack); + + for (mst = start_mst; mst; mst = mst_next) + { + mst_next = (fromfirst ? mst->next : mst->prev); + + if (!status || mst->status == status) + { + if (P_EvaluateMusicStatus(mst->status)) + { + if (!S_MusicExists(mst->musname, !midi_disabled, !digital_disabled)) // paranoia + S_RemoveMusicStackEntry(mst); // then continue + else + return mst; + } + else + S_RemoveMusicStackEntry(mst); // then continue + } + } + + return NULL; +} + +void S_RetainMusic(const char *mname, UINT16 mflags, boolean looping, UINT32 position, UINT16 status) +{ + musicstack_t *mst; + + if (!status) // we use this as a null indicator, don't push + { + CONS_Alert(CONS_ERROR, "Music stack entry must have a nonzero status.\n"); + return; + } + else if (status == JT_MASTER) // enforce only one JT_MASTER + { + for (mst = music_stacks; mst; mst = mst->next) + { + if (mst->status == JT_MASTER) + { + CONS_Alert(CONS_ERROR, "Music stack can only have one JT_MASTER entry.\n"); + return; + } + } + } + else // remove any existing status + S_RemoveMusicStackEntryByStatus(status); + + S_AddMusicStackEntry(mname, mflags, looping, position, status); +} + +boolean S_RecallMusic(UINT16 status, boolean fromfirst) +{ + UINT32 newpos = 0; + boolean mapmuschanged = false; + musicstack_t *result; + musicstack_t *entry = Z_Calloc(sizeof (*result), PU_MUSIC, NULL); + + if (status) + result = S_GetMusicStackEntry(status, fromfirst, -1); + else + result = S_GetMusicStackEntry(JT_NONE, false, -1); + + if (result && !S_MusicExists(result->musname, !midi_disabled, !digital_disabled)) + { + Z_Free(entry); + return false; // music doesn't exist, so don't do anything + } + + // make a copy of result, since we make modifications to our copy + if (result) + { + *entry = *result; + strncpy(entry->musname, result->musname, 7); + } + + // no result, just grab mapmusname + if (!result || !entry->musname[0] || ((status == JT_MASTER || (music_stacks ? !music_stacks->status : false)) && !entry->status)) + { + strncpy(entry->musname, mapmusname, 7); + entry->musflags = mapmusflags; + entry->looping = true; + entry->position = mapmusposition; + entry->tic = gametic; + entry->status = JT_MASTER; + } + + if (entry->status == JT_MASTER) + { + mapmuschanged = strnicmp(entry->musname, mapmusname, 7); + S_ResetMusicStack(); + } + else if (!entry->status) + { + Z_Free(entry); + return false; + } + + if (!mapmuschanged && strncmp(entry->musname, S_MusicName(), 7)) // don't restart music if we're already playing it + { + if (music_stack_fadeout) + S_ChangeMusicEx(entry->musname, entry->musflags, entry->looping, 0, music_stack_fadeout, 0); + else + { + S_ChangeMusicEx(entry->musname, entry->musflags, entry->looping, 0, 0, music_stack_fadein); + if (!music_stack_noposition) // HACK: Global boolean to toggle position resuming, e.g., de-superize + newpos = entry->position + (S_GetMusicLength() ? (UINT32)((float)(gametic - entry->tic)/(float)TICRATE*(float)MUSICRATE) : 0); + + if (newpos > 0 && S_MusicPlaying()) + S_SetMusicPosition(newpos); + else + { + S_StopFadingMusic(); + S_SetInternalMusicVolume(100); + } + } + music_stack_noposition = false; + music_stack_fadeout = 0; + music_stack_fadein = JINGLEPOSTFADE; + } + + Z_Free(entry); + return true; +} + /// ------------------------ /// Music Playback /// ------------------------ @@ -1700,6 +1956,8 @@ void S_PauseAudio(void) #else I_StopCD(); #endif + + S_SetStackAdjustmentStart(); } void S_ResumeAudio(void) @@ -1709,6 +1967,8 @@ void S_ResumeAudio(void) // resume cd music I_ResumeCD(); + + S_AdjustMusicStackTics(); } void S_SetMusicVolume(INT32 digvolume, INT32 seqvolume) @@ -1794,6 +2054,11 @@ void S_Start(void) if (cv_resetmusic.value) S_StopMusic(); S_ChangeMusicEx(mapmusname, mapmusflags, true, mapmusposition, 0, 0); + + S_ResetMusicStack(); + music_stack_noposition = false; + music_stack_fadeout = 0; + music_stack_fadein = JINGLEPOSTFADE; } static void Command_Tunes_f(void) diff --git a/src/s_sound.h b/src/s_sound.h index c17a4cf48..2d05a055d 100644 --- a/src/s_sound.h +++ b/src/s_sound.h @@ -181,6 +181,33 @@ boolean S_SetMusicPosition(UINT32 position); // Get Position of Music UINT32 S_GetMusicPosition(void); +// +// Music Stacking (Jingles) +// + +typedef struct musicstack_s +{ + char musname[7]; + UINT16 musflags; + boolean looping; + UINT32 position; + tic_t tic; + UINT16 status; + + struct musicstack_s *prev; + struct musicstack_s *next; +} musicstack_t; + +char music_stack_nextmusname[7]; +boolean music_stack_noposition; +UINT32 music_stack_fadeout; +UINT32 music_stack_fadein; + +void S_SetStackAdjustmentStart(void); +void S_AdjustMusicStackTics(void); +void S_RetainMusic(const char *mname, UINT16 mflags, boolean looping, UINT32 position, UINT16 status); +boolean S_RecallMusic(UINT16 status, boolean fromfirst); + // // Music Playback // diff --git a/src/sdl/i_video.c b/src/sdl/i_video.c index b4f1a2a9d..5a4fd7a02 100644 --- a/src/sdl/i_video.c +++ b/src/sdl/i_video.c @@ -64,7 +64,6 @@ #include "../m_menu.h" #include "../d_main.h" #include "../s_sound.h" -#include "../i_sound.h" // midi pause/unpause #include "../i_joy.h" #include "../st_stuff.h" #include "../g_game.h" @@ -573,7 +572,7 @@ static void Impl_HandleWindowEvent(SDL_WindowEvent evt) // Tell game we got focus back, resume music if necessary window_notinfocus = false; if (!paused) - I_ResumeSong(); //resume it + S_ResumeAudio(); //resume it if (!firsttimeonmouse) { @@ -585,7 +584,7 @@ static void Impl_HandleWindowEvent(SDL_WindowEvent evt) { // Tell game we lost focus, pause music window_notinfocus = true; - I_PauseSong(); + S_PauseAudio(); if (!disable_mouse) { diff --git a/src/sdl/mixer_sound.c b/src/sdl/mixer_sound.c index 3932d23d3..4d15230a3 100644 --- a/src/sdl/mixer_sound.c +++ b/src/sdl/mixer_sound.c @@ -101,6 +101,7 @@ static UINT32 fading_timer; static UINT32 fading_duration; static INT32 fading_id; static void (*fading_callback)(void); +static boolean fading_nocleanup; #ifdef HAVE_LIBGME static Music_Emu *gme; @@ -122,7 +123,12 @@ static void var_cleanup(void) songpaused = is_looping =\ is_fading = false; - fading_callback = NULL; + // HACK: See music_loop, where we want the fade timing to proceed after a non-looping + // song has stopped playing + if (!fading_nocleanup) + fading_callback = NULL; + else + fading_nocleanup = false; // use it once, set it back immediately internal_volume = 100; } @@ -154,6 +160,8 @@ void I_StartupSound(void) return; } + fading_nocleanup = false; + var_cleanup(); music = NULL; @@ -598,7 +606,15 @@ static void music_loop(void) music_bytes = (UINT32)(loop_point*44100.0L*4); //assume 44.1khz, 4-byte length (see I_GetSongPosition) } else + { + // HACK: Let fade timing proceed beyond the end of a + // non-looping song. This is a specific case where the timing + // should persist after stopping a song, so I don't believe + // this should apply every time the user stops a song. + // This is auto-unset in var_cleanup, called by I_StopSong + fading_nocleanup = true; I_StopSong(); + } } static UINT32 music_fade(UINT32 interval, void *param) @@ -1273,7 +1289,10 @@ boolean I_PlaySong(boolean looping) void I_StopSong(void) { - I_StopFadingSong(); + // HACK: See music_loop on why we want fade timing to proceed + // after end of song + if (!fading_nocleanup) + I_StopFadingSong(); #ifdef HAVE_LIBGME if (gme) @@ -1412,6 +1431,8 @@ void I_StopFadingSong(void) SDL_RemoveTimer(fading_id); is_fading = false; fading_source = fading_target = fading_timer = fading_duration = fading_id = 0; + // don't unset fading_nocleanup here just yet; fading_callback is cleaned up + // in var_cleanup() } boolean I_FadeSongFromVolume(UINT8 target_volume, UINT8 source_volume, UINT32 ms, void (*callback)(void)) diff --git a/src/win32/win_main.c b/src/win32/win_main.c index bfe620a43..8a29f7e18 100644 --- a/src/win32/win_main.c +++ b/src/win32/win_main.c @@ -42,7 +42,7 @@ #include "fabdxlib.h" #include "win_main.h" #include "win_dbg.h" -#include "../i_sound.h" // midi pause/unpause +#include "../s_sound.h" // pause sound with handling #include "../g_input.h" // KEY_MOUSEWHEELxxx #include "../screen.h" // for BASEVID* @@ -110,9 +110,9 @@ static LRESULT CALLBACK MainWndproc(HWND hWnd, UINT message, WPARAM wParam, LPAR // pause music when alt-tab if (appActive && !paused) - I_ResumeSong(); + S_ResumeAudio(); else if (!paused) - I_PauseSong(); + S_PauseAudio(); { HANDLE ci = GetStdHandle(STD_INPUT_HANDLE); DWORD mode;