Replace music handling

(This commit does not compile. Sound test and tunes
command code needs to be ported after this.)

This is a big one. Here's the rundown:

The old music system was very direct, much of the time
just a proxy to the real sound API in i_sound.h.

You could change the music on command, but there wasn't
a consistent way to prevent some music from playing over
others. P_RestoreMusic is one example of needing to
address this problem. The jingles system was intended as
another solution. Furthermore, sound test (Stereo) has its
own needs.

I am removing all of that. Music handling in general is
now a very deliberate system, kind of similar to jingles.

In the new system, "tunes" are registered. The tune stores
info such as whether it should loop or fade out. Most of
the configuration is intended to be initialized only ONCE.
Tunes can be mapped to an actual music lump. They can be
remapped at any time too.

Tunes are also configured with a priority number. This
determines which tune is heard, if multiple are supposed
to be playing at a time. You can even tell a tune how long
it should play, so it's unnecessary to track this with
bespoke timers.
This commit is contained in:
James R 2023-08-05 16:37:22 -07:00
parent 646701c55c
commit 39f46a0f20
29 changed files with 1210 additions and 1187 deletions

View file

@ -148,6 +148,8 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32
k_mapuser.c
k_powerup.cpp
k_hitlag.c
music.cpp
music_manager.cpp
)
if(SRB2_CONFIG_ENABLE_WEBM_MOVIES)

View file

@ -63,6 +63,7 @@
#include "k_vote.h"
#include "k_serverstats.h"
#include "k_zvote.h"
#include "music.h"
// cl loading screen
#include "v_video.h"
@ -2747,7 +2748,10 @@ static void Command_connect(void)
// Menu restore state.
restoreMenu = &PLAY_MP_OptSelectDef;
S_ChangeMusicInternal("NETMD2", true);
Music_Remap("menu", "NETMD2");
Music_Play("menu");
if (setup_numplayers == 0)
{
setup_numplayers = 1;

View file

@ -83,6 +83,7 @@
#include "k_podium.h"
#include "k_vote.h"
#include "k_serverstats.h"
#include "music.h"
#ifdef HWRENDER
#include "hardware/hw_main.h" // 3D View Rendering
@ -926,6 +927,8 @@ void D_SRB2Loop(void)
}
#endif
Music_Tick();
// Fully completed frame made.
finishprecise = I_GetPreciseTime();
if (!singletics)
@ -1015,8 +1018,7 @@ void D_ClearState(void)
//
void D_StartTitle(void)
{
if (!demo.title)
S_StopMusic();
Music_StopAll();
D_ClearState();
F_StartTitleScreen();
@ -1654,6 +1656,9 @@ void D_SRB2Main(void)
I_InitMusic();
S_InitSfxChannels(cv_soundvolume.value);
}
Music_Init();
CON_SetLoadingProgress(LOADED_SINITSFXCHANNELS);
S_InitMusicDefs();

View file

@ -6609,14 +6609,6 @@ struct int_const_s const INT_CONST[] = {
{"int_score",int_score},
{"int_scoreortimeattack", int_scoreortimeattack},
// Jingles (jingletype_t)
{"JT_NONE",JT_NONE},
{"JT_OTHER",JT_OTHER},
{"JT_MASTER",JT_MASTER},
{"JT_INVINCIBILITY",JT_INVINCIBILITY},
{"JT_GROW",JT_GROW},
// Overlay exception settings
{"OV_DONTSCREENOFFSET", OV_DONTSCREENOFFSET},
{"OV_DONT3DOFFSET", OV_DONT3DOFFSET},

View file

@ -46,6 +46,7 @@
// SRB2Kart
#include "k_menu.h"
#include "k_grandprix.h"
#include "music.h"
// Stage of animation:
// 0 = text, 1 = art screen
@ -298,8 +299,6 @@ void F_StartCustomCutscene(INT32 cutscenenum, boolean precutscene, boolean reset
void F_StartIntro(void)
{
S_StopMusicCredit();
if (gamestate)
{
F_WipeStartScreen();
@ -308,7 +307,7 @@ void F_StartIntro(void)
F_RunWipe(wipe_intro_toblack, wipedefs[wipe_intro_toblack], false, "FADEMAP0", false, false);
}
S_StopMusic();
Music_StopAll();
S_StopSounds();
if (introtoplay)
@ -331,7 +330,8 @@ void F_StartIntro(void)
intro_scenenum = 0;
finalecount = animtimer = skullAnimCounter = stoptimer = 0;
timetonext = introscenetime[intro_scenenum];
S_StopMusic();
Music_StopAll();
}
//
@ -701,11 +701,10 @@ void F_StartCredits(void)
gameaction = ga_nothing;
paused = false;
CON_ToggleOff();
S_StopMusic();
Music_StopAll();
S_StopSounds();
S_ChangeMusicInternal("_creds", false);
S_ShowMusicCredit();
Music_Play("credits");
finalecount = 0;
animtimer = 0;
@ -907,7 +906,9 @@ UINT16 finaleemeralds = 0;
void F_StartGameEvaluation(void)
{
S_FadeMusic(0, MUSICRATE/4);
Music_DelayEnd("credits", TICRATE/4);
Music_Tick(); // it needs to fade out right now
S_StopMusicCredit();
// Credits option in extras menu
@ -1253,7 +1254,8 @@ void F_GameEvaluationTicker(void)
if (finalecount == 1)
{
// sitting on that distant _shore
S_ChangeMusicInternal("_SHORE", false);
Music_Remap("shore", "_SHORE");
Music_Play("shore");
}
}
else
@ -1261,7 +1263,8 @@ void F_GameEvaluationTicker(void)
if (finalecount == 1)
{
// _drift across open waters
S_ChangeMusicInternal("_DRIFT", false);
Music_Remap("shore", "_DRIFT");
Music_Play("shore");
}
}
@ -1766,8 +1769,12 @@ void F_TitleScreenTicker(boolean run)
{
if (finalecount == 0)
{
// Now start the music
S_ChangeMusicInternal("_title", looptitle);
if (!Music_Playing("title"))
{
// Now start the music
Music_Loop("title", looptitle);
Music_Play("title");
}
}
else if (menumessage.active)
{
@ -1956,7 +1963,7 @@ void F_WaitingPlayersTicker(void)
// dumb hack, only start the music on the 1st tick so if you instantly go into the map you aren't hearing a tic of music
if (finalecount == 2)
S_ChangeMusicInternal("WAIT2J", true);
Music_Play("wait");
}
void F_WaitingPlayersDrawer(void)
@ -2002,11 +2009,14 @@ static void F_AdvanceToNextScene(void)
picxpos = cutscenes[cutnum]->scene[scenenum].xcoord[picnum];
picypos = cutscenes[cutnum]->scene[scenenum].ycoord[picnum];
// FIXME - port to new music system
#if 0
if (cutscenes[cutnum]->scene[scenenum].musswitch[0])
S_ChangeMusicEx(cutscenes[cutnum]->scene[scenenum].musswitch,
cutscenes[cutnum]->scene[scenenum].musswitchflags,
cutscenes[cutnum]->scene[scenenum].musicloop,
cutscenes[cutnum]->scene[scenenum].musswitchposition, 0, 0);
#endif
// Fade to the next
F_NewCutscene(cutscenes[cutnum]->scene[scenenum].text);
@ -2075,6 +2085,8 @@ void F_StartCustomCutscene(INT32 cutscenenum, boolean precutscene, boolean reset
animtimer = cutscenes[cutnum]->scene[0].picduration[0]; // Picture duration
stoptimer = 0;
// FIXME - port to new music system
#if 0
if (cutscenes[cutnum]->scene[0].musswitch[0])
S_ChangeMusicEx(cutscenes[cutnum]->scene[0].musswitch,
cutscenes[cutnum]->scene[0].musswitchflags,
@ -2082,6 +2094,7 @@ void F_StartCustomCutscene(INT32 cutscenenum, boolean precutscene, boolean reset
cutscenes[cutnum]->scene[scenenum].musswitchposition, 0, 0);
else
S_StopMusic();
#endif
S_StopSounds();
}
@ -2365,11 +2378,14 @@ static void F_AdvanceToNextPage(void)
picypos = textprompts[cutnum]->page[scenenum].ycoord[picnum];
animtimer = pictime = textprompts[cutnum]->page[scenenum].picduration[picnum];
// FIXME - port to new music system
#if 0
// music change
if (textprompts[cutnum]->page[scenenum].musswitch[0])
S_ChangeMusic(textprompts[cutnum]->page[scenenum].musswitch,
textprompts[cutnum]->page[scenenum].musswitchflags,
textprompts[cutnum]->page[scenenum].musicloop);
#endif
}
}
@ -2453,11 +2469,14 @@ void F_StartTextPrompt(INT32 promptnum, INT32 pagenum, mobj_t *mo, UINT16 postex
picypos = textprompts[cutnum]->page[scenenum].ycoord[picnum];
animtimer = pictime = textprompts[cutnum]->page[scenenum].picduration[picnum];
// FIXME - port to new music system
#if 0
// music change
if (textprompts[cutnum]->page[scenenum].musswitch[0])
S_ChangeMusic(textprompts[cutnum]->page[scenenum].musswitch,
textprompts[cutnum]->page[scenenum].musswitchflags,
textprompts[cutnum]->page[scenenum].musicloop);
#endif
// get the calling player
if (promptblockcontrols && mo && mo->player)

View file

@ -69,6 +69,7 @@
#include "k_vote.h"
#include "k_serverstats.h"
#include "k_zvote.h"
#include "music.h"
#ifdef HAVE_DISCORDRPC
#include "discord.h"
@ -2402,7 +2403,7 @@ void G_Ticker(boolean run)
musiccountdown--;
if (musiccountdown == 1)
{
S_ChangeMusicInternal("racent", true);
Music_Play("intermission");
}
else if (musiccountdown == (MUSICCOUNTDOWNMAX - (3*TICRATE)/2))
{
@ -2410,6 +2411,8 @@ void G_Ticker(boolean run)
}
}
P_InvincGrowMusic();
K_TickMidVote();
}
}
@ -2510,7 +2513,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
INT32 itemtype;
INT32 itemamount;
INT32 growshrinktimer;
boolean songcredit = false;
UINT16 nocontrol;
INT32 khudfault;
INT32 kickstartaccel;
@ -2850,11 +2852,6 @@ void G_PlayerReborn(INT32 player, boolean betweenmaps)
if (exiting)
return;
P_RestoreMusic(p);
if (songcredit)
S_ShowMusicCredit();
}
//
@ -4695,7 +4692,7 @@ void G_EndGame(void)
// In a netgame, don't unwittingly boot everyone.
if (netgame)
{
S_StopMusic();
Music_StopAll();
G_SetGamestate(GS_WAITINGPLAYERS); // hack to prevent a command repeat
if (server)

View file

@ -47,6 +47,7 @@
#include "k_podium.h"
#include "k_powerup.h"
#include "k_hitlag.h"
#include "music.h"
// SOME IMPORTANT VARIABLES DEFINED IN DOOMDEF.H:
// gamespeed is cc (0 for easy, 1 for normal, 2 for hard)
@ -3843,8 +3844,6 @@ void K_RemoveGrowShrink(player_t *player)
}
player->growshrinktimer = 0;
P_RestoreMusic(player);
}
boolean K_IsBigger(mobj_t *compare, mobj_t *other)
@ -4380,7 +4379,7 @@ INT32 K_ExplodePlayer(player_t *player, mobj_t *inflictor, mobj_t *source) // A
{
P_DamageMobj(player->mo, inflictor, source, 1, DMG_INSTAKILL);
player->SPBdistance = 0;
S_StopMusic();
Music_StopAll();
}
spbMultiplier = inflictor->movefactor;
@ -6197,18 +6196,12 @@ void K_DoInvincibility(player_t *player, tic_t time)
P_SetScale(overlay, player->mo->scale);
}
if (P_IsLocalPlayer(player) == true && player->invincibilitytimer == 0)
{
S_ChangeMusicSpecial("kinvnc");
}
else //used to be "if (P_IsDisplayPlayer(player) == false)"
if (P_IsLocalPlayer(player) == false)
{
S_StartSound(player->mo, sfx_alarmi);
}
player->invincibilitytimer += time;
P_RestoreMusic(player);
}
void K_KillBananaChain(mobj_t *banana, mobj_t *inflictor, mobj_t *source)
@ -11281,25 +11274,15 @@ void K_MoveKartPlayer(player_t *player, boolean onground)
player->mo->destscale = FixedMul(player->mo->destscale, SHRINK_SCALE);
}
if (player->invincibilitytimer > 0)
{
; // invincibility has priority in P_RestoreMusic, no point in starting here
}
else if (P_IsLocalPlayer(player) == true)
{
if (player->growshrinktimer < 1)
S_ChangeMusicSpecial("kgrow");
}
else //used to be "if (P_IsDisplayPlayer(player) == false)"
if (P_IsLocalPlayer(player) == false && player->invincibilitytimer == 0)
{
// don't play this if the player has invincibility -- that takes priority
S_StartSound(player->mo, sfx_alarmg);
}
player->growshrinktimer = max(0, player->growshrinktimer);
player->growshrinktimer += ((gametyperules & GTR_CLOSERPLAYERS) ? 8 : 12) * TICRATE;
P_RestoreMusic(player);
S_StartSound(player->mo, sfx_kc5a);
player->itemamount--;
@ -11934,7 +11917,7 @@ void K_CheckSpectateStatus(boolean considermapreset)
// Reset the match when 3P joins 1P and 2P, DUEL mode must be disabled
if (i > 0 && !mapreset && gamestate == GS_LEVEL && (numingame < 3 && numingame+i >= 2))
{
S_ChangeMusicInternal("chalng", false); // COME ON
Music_Play("comeon"); // COME ON
mapreset = 3*TICRATE; // Even though only the server uses this for game logic, set for everyone for HUD
}
}

View file

@ -16,6 +16,7 @@
#include "f_finale.h"
#include "m_misc.h"
#include "m_cond.h"
#include "music.h"
#ifdef PC_DOS
#include <stdio.h> // for snprintf
@ -366,7 +367,7 @@ boolean M_Responder(event_t *ev)
return true;
}
#define NotCurrentlyPlaying(desiredname) (!S_MusicPlaying() || strcmp(desiredname, S_MusicName()))
#define NotCurrentlyPlaying(desiredname) strcmp(desiredname, Music_CurrentSong())
void M_PlayMenuJam(void)
{
@ -375,8 +376,7 @@ void M_PlayMenuJam(void)
if (challengesmenu.pending)
{
S_StopMusic();
S_StopMusicCredit();
Music_StopAll();
musicstatepermitted = true;
return;
@ -391,16 +391,15 @@ void M_PlayMenuJam(void)
{
if (refMenu->music[0] == '.' && refMenu->music[1] == '\0')
{
S_StopMusic();
S_StopMusicCredit();
Music_StopAll();
return;
}
else if (override == 0)
{
if (NotCurrentlyPlaying(refMenu->music))
{
S_ChangeMusicInternal(refMenu->music, true);
S_ShowMusicCredit();
Music_Remap("menu", refMenu->music);
Music_Play("menu");
}
return;
}
@ -416,8 +415,8 @@ void M_PlayMenuJam(void)
if (refMenu != NULL && NotCurrentlyPlaying(overridetotrack[override - 1]))
{
S_ChangeMusicInternal(overridetotrack[override - 1], true);
S_ShowMusicCredit();
Music_Remap("menu", overridetotrack[override - 1]);
Music_Play("menu");
if (override < GDMUSIC_KEEPONMENU)
gamedata->musicstate = GDMUSIC_NONE;
@ -435,8 +434,8 @@ void M_PlayMenuJam(void)
if (!NotCurrentlyPlaying(cv_menujam.string))
return;
S_ChangeMusicInternal(cv_menujam.string, true);
S_ShowMusicCredit();
Music_Remap("menu", cv_menujam.string);
Music_Play("menu");
}
#undef IsCurrentlyPlaying
@ -574,6 +573,8 @@ void M_StartControlPanel(void)
modeattacking = ATTACKING_NONE;
}
Music_Stop("title");
if (cv_currprofile.value == -1) // Only ask once per session.
{
// Make sure the profile data is ready now since we need to select a profile.
@ -596,7 +597,8 @@ void M_StartControlPanel(void)
CV_StealthSetValue(&cv_currprofile, -1); // Make sure to reset that as it is set by PR_ApplyProfile which we kind of hack together to force it.
// Ambient ocean sounds
S_ChangeMusicInternal("_OCEAN", true);
Music_Remap("menu_nocred", "_OCEAN");
Music_Play("menu_nocred");
}
else
{

View file

@ -51,6 +51,7 @@
#include "k_pwrlv.h"
#include "k_grandprix.h"
#include "k_color.h"
#include "music.h"
#ifdef HWRENDER
#include "hardware/hw_main.h"
@ -1174,7 +1175,7 @@ static void Y_TickVoteRoulette(void)
vote.roulette.endOffset++;
}
S_ChangeMusicInternal("voteeb", false);
Music_Play("vote_end");
break;
}
}
@ -1461,8 +1462,7 @@ void Y_VoteTicker(void)
if (vote.tic == 0)
{
S_ChangeMusicInternal("vote", true);
S_ShowMusicCredit();
Music_Play("vote");
}
if (g_pickedVote != VOTE_NOT_PICKED)
@ -1728,13 +1728,13 @@ void Y_SetupVoteFinish(SINT8 pick, SINT8 level)
{
// Only one unique vote, so just end it immediately.
vote.endtic = vote.tic + (5*TICRATE);
S_ChangeMusicInternal("voteeb", false);
Music_Play("vote_end");
Y_VoteStops(pick, level);
break;
}
default:
{
S_ChangeMusicInternal("voteea", true);
Music_Play("vote_suspense");
break;
}
}

View file

@ -1018,7 +1018,6 @@ void Command_ObjectPlace_f(void)
op_oldflags1 = mobjinfo[MT_PLAYER].flags;
++players[0].lives;
players[0].playerstate = PST_LIVE;
P_RestoreMusic(&players[0]);
}
else
op_oldstate = (statenum_t)(players[0].mo->state-states);

View file

@ -4,6 +4,7 @@
#include "../k_menu.h"
#include "../m_cond.h" // Condition Sets
#include "../m_random.h" // And just some randomness for the exits.
#include "../music.h"
#include "../z_zone.h"
#include "../r_skins.h"
#include "../s_sound.h"
@ -220,7 +221,7 @@ menu_t *M_InterruptMenuWithChallenges(menu_t *desiredmenu)
if ((challengesmenu.pending = (newunlock != MAXUNLOCKABLES)))
{
S_StopMusic();
Music_StopAll();
MISC_ChallengesDef.prevMenu = desiredmenu;
}

View file

@ -4,6 +4,7 @@
#include "../k_menu.h"
#include "../s_sound.h"
#include "../m_random.h"
#include "../music.h"
#include "../r_skins.h"
struct wrongwarp_s wrongwarp;
@ -22,7 +23,8 @@ void M_WrongWarp(INT32 choice)
M_SetupNextMenu(&MISC_WrongWarpDef, false);
// Done here to avoid immediate music credit
S_ChangeMusicInternal("YEAWAY", true);
Music_Remap("menu_nocred", "YEAWAY");
Music_Play("menu_nocred");
}
static void M_WrongWarpTick(void)

322
src/music.cpp Normal file
View file

@ -0,0 +1,322 @@
// DR ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2023 by Kart Krew.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include "music_manager.hpp"
#include "music_tune.hpp"
#include "doomtype.h"
#include "music.h"
using namespace srb2::music;
namespace
{
TuneManager g_tunes;
}; // namespace
void Music_Init(void)
{
{
Tune& tune = g_tunes.insert("level");
tune.priority = 1;
tune.resume_fade_in = 750;
tune.sync = true;
tune.credit = true;
}
{
Tune& tune = g_tunes.insert("position");
tune.priority = 10;
tune.fade_out = 3500;
}
{
Tune& tune = g_tunes.insert("grow");
tune.song = "kgrow";
tune.priority = 20;
tune.resume_fade_in = 200;
}
{
Tune& tune = g_tunes.insert("invinc");
tune.song = "kinvnc";
tune.priority = 21;
}
{
Tune& tune = g_tunes.insert("finish");
tune.priority = 30;
tune.loop = false;
}
{
Tune& tune = g_tunes.insert("comeon");
tune.song = "chalng";
tune.priority = 35;
tune.loop = false;
}
{
Tune& tune = g_tunes.insert("intermission");
tune.song = "racent";
tune.priority = 40;
}
{
Tune& tune = g_tunes.insert("vote");
tune.song = "vote";
tune.priority = 50;
tune.credit = true;
}
{
Tune& tune = g_tunes.insert("vote_suspense");
tune.song = "voteea";
tune.priority = 51;
}
{
Tune& tune = g_tunes.insert("vote_end");
tune.song = "voteeb";
tune.priority = 52;
tune.loop = false;
}
{
Tune& tune = g_tunes.insert("wait");
tune.song = "WAIT2J";
tune.priority = 60;
}
{
Tune& tune = g_tunes.insert("title");
tune.song = "_title";
tune.priority = 100;
tune.resist = true;
}
{
Tune& tune = g_tunes.insert("menu");
tune.priority = 100;
tune.credit = true;
}
{
Tune& tune = g_tunes.insert("menu_nocred");
tune.priority = 100;
}
{
Tune& tune = g_tunes.insert("credits");
tune.priority = 100;
tune.song = "_creds";
tune.fade_out = 250;
tune.loop = false;
tune.credit = true;
}
{
Tune& tune = g_tunes.insert("shore");
tune.priority = 100;
tune.loop = false;
}
}
void Music_Tick(void)
{
g_tunes.tick();
}
void Music_Play(const char* id)
{
Tune* tune = g_tunes.find(id);
if (tune)
{
tune->play();
g_tunes.tick(); // play this immediately
}
}
void Music_DelayEnd(const char* id, tic_t duration)
{
Tune* tune = g_tunes.find(id);
if (tune)
{
tune->delay_end(duration);
if (tune->time_remaining() <= detail::msec_to_tics(tune->fade_out))
{
// If this action would cause a fade out, start
// fading immediately.
g_tunes.tick();
}
}
}
void Music_Seek(const char* id, tic_t set)
{
Tune* tune = g_tunes.find(id);
if (tune)
{
tune->seek = set;
tune->needs_seek = true;
}
}
void Music_Stop(const char* id)
{
Tune* tune = g_tunes.find(id);
if (tune)
{
g_tunes.stop(*tune);
g_tunes.tick();
}
}
void Music_Pause(const char* id)
{
Tune* tune = g_tunes.find(id);
if (tune)
{
tune->pause();
g_tunes.pause_unpause();
}
}
void Music_UnPause(const char* id)
{
Tune* tune = g_tunes.find(id);
if (tune)
{
tune->unpause();
g_tunes.pause_unpause();
}
}
void Music_PauseAll(void)
{
g_tunes.for_each([](Tune& tune) { tune.pause(); });
g_tunes.pause_unpause();
}
void Music_UnPauseAll(void)
{
g_tunes.for_each([](Tune& tune) { tune.unpause(); });
g_tunes.pause_unpause();
}
void Music_StopAll(void)
{
g_tunes.stop_all();
g_tunes.tick();
}
void Music_Remap(const char* id, const char* song)
{
Tune* tune = g_tunes.find(id);
if (tune)
{
tune->song = song;
}
}
boolean Music_Playing(const char* id)
{
const Tune* tune = g_tunes.find(id);
return tune && tune->playing();
}
boolean Music_Paused(const char* id)
{
const Tune* tune = g_tunes.find(id);
return tune && tune->paused();
}
tic_t Music_Elapsed(const char* id)
{
const Tune* tune = g_tunes.find(id);
return tune ? tune->elapsed() : 0u;
}
tic_t Music_DurationLeft(const char* id)
{
const Tune* tune = g_tunes.find(id);
return tune ? tune->time_remaining() : 0u;
}
tic_t Music_TotalDuration(const char* id)
{
const Tune* tune = g_tunes.find(id);
return tune ? tune->duration() : 0u;
}
void Music_Loop(const char* id, boolean loop)
{
Tune* tune = g_tunes.find(id);
if (tune)
{
// FIXME: this has no effect if the song is already
// playing
tune->loop = loop;
}
}
boolean Music_CanLoop(const char* id)
{
const Tune* tune = g_tunes.find(id);
return tune && tune->loop;
}
boolean Music_CanEnd(const char* id)
{
const Tune* tune = g_tunes.find(id);
return tune && tune->can_end();
}
const char* Music_Song(const char* id)
{
const Tune* tune = g_tunes.find(id);
return tune ? tune->song.c_str() : "";
}
const char* Music_CurrentSong(void)
{
return g_tunes.current_song().c_str();
}
const char* Music_CurrentId(void)
{
return g_tunes.current_id();
}

148
src/music.h Normal file
View file

@ -0,0 +1,148 @@
// DR ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2023 by Kart Krew.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
//
// A brief description of "tunes".
//
// Tunes are how the game identifies music. A tune may be
// remapped to any music lump. For example, the "level" tune
// represents the level music, but the actual song that plays
// is different between levels.
//
// Tunes store info, such as for how long they will play (in
// the case of "invinc" and "grow"), or whether they fade in
// or out and for how long they fade.
//
// Tunes are given a priority. Only the highest priority tune
// is heard, even if the others haven't ended. Tunes with the
// same priority are sorted by picking the last one that begun
// playing.
//
#ifndef MUSIC_H
#define MUSIC_H
#include "doomtype.h"
#ifdef __cplusplus
extern "C" {
#endif
//
// Get the currently playing tune.
//
// Returns the song name for the currently playing tune.
// Returns empty string if no tune is playing.
const char *Music_CurrentSong(void);
// Returns the id of the currently playing tune. Returns empty
// string if no tune is playing.
const char *Music_CurrentId(void);
//
// Actions that take effect immediately.
//
// Begin playing a tune, duration is infinite. If the tune was
// already playing, this resets its current position (seeks
// back to the start.)
void Music_Play(const char *id);
// Postpone the end of this tune until N tics from now. The
// tune should already be playing before calling this.
void Music_DelayEnd(const char *id, tic_t duration);
// Stop playing a tune.
void Music_Stop(const char *id);
void Music_StopAll(void);
// Pause a tune. This effectively extends its duration,
// relative to game time. Unpausing will resume the tune
// exactly where it left off.
void Music_Pause(const char *id);
void Music_UnPause(const char *id);
void Music_PauseAll(void);
void Music_UnPauseAll(void);
//
// Change properties. May be called before calling Music_Play.
// These take effect on the next tick.
//
// Seek to a specific time in the tune.
void Music_Seek(const char *id, tic_t set);
// Remap a tune to another song. Use the lump name, with the
// 'O_' at the beginning removed. song is case insensitive.
void Music_Remap(const char *id, const char *song);
// Set whether a tune should loop.
void Music_Loop(const char *id, boolean loop);
//
// Query properties.
//
// Returns true if the tune is configured to loop.
boolean Music_CanLoop(const char *id);
// Returns true if the tune does not play indefinitely, i.e.
// has a limited duration.
boolean Music_CanEnd(const char *id);
// Returns true if the tune is playing. This does not
// necessarily mean it is audible, because it has to be at the
// highest priority to be heard.
boolean Music_Playing(const char *id);
// Returns true if the tune is paused.
boolean Music_Paused(const char *id);
// Returns the number of tics elapsed since the start of the
// tune.
tic_t Music_Elapsed(const char *id);
// Returns the number of tics remaining until the tune ends.
tic_t Music_DurationLeft(const char *id);
// Returns the total duration of the tune, in tics.
tic_t Music_TotalDuration(const char *id);
// Returns the song name mapped to a tune.
const char *Music_Song(const char *id);
//
// Low level program state.
//
// Loads certain data structures used for the lifetime of the
// program. Only needs to be called once, before any other
// functions.
void Music_Init(void);
// Call this every tic to update the music.
void Music_Tick(void);
#ifdef __cplusplus
} // extern "C"
#endif
#endif // MUSIC_H

38
src/music_detail.hpp Normal file
View file

@ -0,0 +1,38 @@
// DR ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2023 by Kart Krew.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef MUSIC_DETAIL_HPP
#define MUSIC_DETAIL_HPP
#include "doomdef.h"
#include "doomstat.h"
#include "doomtype.h"
namespace srb2::music::detail
{
inline constexpr tic_t msec_to_tics(int msec)
{
return msec * (TICRATE / 1000.f);
}
inline constexpr int tics_to_msec(tic_t tics)
{
return tics * (1000.f / TICRATE);
}
inline tic_t tic_time()
{
// Use gametic so it is synced with game logic.
return gametic;
}
}; // namespace srb2::music::detail
#endif // MUSIC_DETAIL_HPP

204
src/music_manager.cpp Normal file
View file

@ -0,0 +1,204 @@
// DR ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 1998-2000 by DooM Legacy Team.
// Copyright (C) 1999-2020 by Sonic Team Junior.
// Copyright (C) 2023 by Kart Krew.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <string>
#include <fmt/format.h>
#include "music_manager.hpp"
#include "music_tune.hpp"
#include "doomtype.h"
#include "i_sound.h"
#include "i_time.h"
#include "s_sound.h"
#include "w_wad.h"
#include "z_zone.h"
using namespace srb2::music;
namespace
{
// How many tics music is allowed to drift away from game
// logic.
constexpr int kResyncTics = 2;
}; // namespace
void TuneManager::tick()
{
if (S_MusicDisabled())
{
return;
}
Tune* tune = current_tune();
std::string old_song = current_song_;
current_song_ = tune && tune->playing() ? tune->song : std::string{};
bool changed = current_song_ != old_song;
if (stop_credit_)
{
if (changed)
{
// Only stop the music credit if the song actually
// changed.
S_StopMusicCredit();
}
stop_credit_ = false;
}
if (!tune)
{
if (changed)
{
I_UnloadSong();
}
return;
}
if (changed)
{
if (load())
{
I_FadeInPlaySong(tune->resume ? tune->resume_fade_in : tune->fade_in, tune->loop);
seek(tune);
adjust_volume();
I_SetSongSpeed(tune->speed());
if (tune->credit && !tune->resume)
{
S_ShowMusicCredit();
}
tune->resume = true;
gme_ = !std::strcmp(I_SongType(), "GME");
}
else
{
I_UnloadSong();
}
}
else if (tune->needs_seek || (tune->sync && resync()))
{
seek(tune);
}
else if (tune->time_remaining() <= detail::msec_to_tics(tune->fade_out))
{
if (tune->can_fade_out)
{
I_FadeSong(0, tune->fade_out, nullptr);
tune->can_fade_out = false;
}
if (!tune->keep_open)
{
tune->ending = true;
}
}
}
void TuneManager::pause_unpause() const
{
const Tune* tune = current_tune();
if (tune)
{
if (tune->paused())
{
I_PauseSong();
}
else
{
I_ResumeSong();
}
}
}
bool TuneManager::load() const
{
lumpnum_t lumpnum = W_CheckNumForLongName(fmt::format("O_{}", current_song_).c_str());
if (lumpnum == LUMPERROR)
{
return false;
}
return I_LoadSong(static_cast<char*>(W_CacheLumpNum(lumpnum, PU_MUSIC)), W_LumpLength(lumpnum));
}
void TuneManager::adjust_volume() const
{
UINT8 i;
const musicdef_t* def = S_FindMusicDef(current_song_.c_str(), &i);
if (!def)
{
return;
}
I_SetCurrentSongVolume(def->debug_volume != 0 ? def->debug_volume : def->volume);
}
bool TuneManager::resync()
{
if (gme_)
{
// This is dodging the problem. GME can be very slow
// for seeking, since it (probably) just emulates the
// entire song up to where its seeking.
//
// The time loss is not easily predictable, and it
// causes repeated resyncing, so just don't sync if
// it's GME.
return false;
}
long d_local = I_GetTime() - time_local_;
long d_sync = detail::tic_time() - time_sync_;
if (std::abs(d_local - d_sync) >= kResyncTics)
{
time_sync_ = detail::tic_time();
time_local_ = I_GetTime();
return true;
}
return false;
}
void TuneManager::seek(Tune* tune)
{
uint32_t end = I_GetSongLength();
uint32_t loop = I_GetSongLoopPoint();
uint32_t pos = detail::tics_to_msec(tune->seek + tune->elapsed()) * tune->speed();
if (pos > end && (end - loop) > 0u)
{
pos = loop + ((pos - end) % (end - loop));
}
I_SetSongPosition(pos);
tune->needs_seek = false;
}

113
src/music_manager.hpp Normal file
View file

@ -0,0 +1,113 @@
// DR ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2023 by Kart Krew.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef MUSIC_MANAGER_HPP
#define MUSIC_MANAGER_HPP
#include <algorithm>
#include <cstdlib>
#include <string>
#include <unordered_map>
#include "cxxutil.hpp"
#include "music_tune.hpp"
namespace srb2::music
{
class TuneManager
{
public:
const std::string& current_song() const { return current_song_; }
Tune* current_tune() const
{
auto it = current_iterator();
return it != map_.end() ? const_cast<Tune*>(&it->second) : nullptr;
}
const char* current_id() const
{
auto it = current_iterator();
return it != map_.end() ? it->first.c_str() : "";
}
Tune* find(const char* id) const
{
auto it = map_.find(id);
return it != map_.end() ? const_cast<Tune*>(&it->second) : nullptr;
}
Tune& insert(const char* id)
{
auto [it, inserted] = map_.emplace(id, Tune{});
SRB2_ASSERT(inserted);
return it->second;
}
void tick();
void pause_unpause() const;
void stop(Tune& tune)
{
tune.stop();
stop_credit_ = true;
}
void stop_all()
{
for_each([](Tune& tune) { tune.stop(); });
stop_credit_ = true;
}
template <typename F>
void for_each(F&& f)
{
for (auto& [_, tune] : map_)
{
if (!tune.resist)
{
f(tune);
}
}
}
private:
std::unordered_map<std::string, Tune> map_;
std::string current_song_;
tic_t time_sync_;
tic_t time_local_;
bool stop_credit_ = false;
bool gme_; // hack
decltype(map_)::const_iterator current_iterator() const
{
return std::max_element(
map_.begin(),
map_.end(),
[](const auto& a, const auto& b) { return a.second < b.second; }
);
}
bool load() const;
void adjust_volume() const;
bool resync();
static void seek(Tune* tune);
};
}; // namespace srb2::music
#endif // MUSIC_MANAGER_HPP

194
src/music_tune.hpp Normal file
View file

@ -0,0 +1,194 @@
// DR ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2023 by Kart Krew.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#ifndef MUSIC_TUNE_HPP
#define MUSIC_TUNE_HPP
#include <algorithm>
#include <optional>
#include <string>
#include "doomdef.h"
#include "doomtype.h"
#include "music_detail.hpp"
namespace srb2::music
{
class Tune
{
public:
explicit Tune() {}
std::string song; // looks up the lump
// Higher priority tunes play first.
int priority = 0;
// Fade at the beginning or end of the tune.
int fade_in = 0; // in milliseconds
int fade_out = 0;
// Fade time subtracts from the duration of the song?
int fade_out_inclusive = true;
// Fade in when a higher priority tune ends and this one
// resumes.
int resume_fade_in = 0;
// Sync this tune to game logic.
bool sync = false;
// This tune loops at the end.
bool loop = true;
// When this tune runs out of duration, keep playing
// silence.
bool keep_open = false;
// This tune does not respect mass stop or pause actions
// from TuneManager::stop_all etc. It must be
// stopped/paused individually.
bool resist = false;
// This tune shows a credit when first played (not
// resumed).
bool credit = false;
// Start playing this number of tics into the tune.
tic_t seek = 0;
// these track state
bool can_fade_out = true;
bool needs_seek = false;
bool resume = false;
bool ending = false;
tic_t elapsed() const { return std::max(pause_.value_or(detail::tic_time()), begin_) - begin_; }
tic_t time_remaining() const { return end_ - std::min(pause_.value_or(detail::tic_time()), end_); }
tic_t duration() const { return end_ - begin_; }
bool playing() const { return begin_ <= detail::tic_time() && (!ending || time_remaining()); }
bool paused() const { return pause_.has_value(); }
bool can_end() const { return end_ != INFTICS; }
float speed() const
{
// Slow level music down a bit in Encore. (Values are vibe-based. WE GET IT YOU VAPE)
if (encoremode && gamestate == GS_LEVEL)
{
return 0.86471f;
}
return 1.f;
}
bool operator <(const Tune& b) const
{
// If this song is not playing, it has lowest
// priority.
if (!playing())
{
return true;
}
// If the other song is not playing, we automatically
// have higher priority.
if (!b.playing())
{
return false;
}
// The highest priority song is preferred.
if (priority != b.priority)
{
return priority < b.priority;
}
// If both songs have the same priority, prefer the
// one that begun later.
return begin_ < b.begin_;
}
void play()
{
if (!needs_seek)
{
seek = 0;
}
can_fade_out = true;
needs_seek = true;
resume = false;
ending = false;
begin_ = detail::tic_time();
end_ = INFTICS;
pause_.reset();
}
void delay_end(tic_t duration)
{
end_ = detail::tic_time() + duration;
if (!fade_out_inclusive)
{
end_ += detail::msec_to_tics(fade_out);
}
if (playing())
{
can_fade_out = true;
ending = false;
}
}
void stop()
{
begin_ = INFTICS;
end_ = 0;
pause_.reset();
}
void pause()
{
pause_ = detail::tic_time();
}
void unpause()
{
if (!pause_)
{
return;
}
if (playing())
{
tic_t n = detail::tic_time() - *pause_;
begin_ += n;
if (can_end())
{
end_ += n;
}
}
pause_.reset();
}
private:
tic_t begin_ = INFTICS;
tic_t end_ = 0;
std::optional<tic_t> pause_;
};
}; // namespace srb2::music
#endif // MUSIC_TUNE_HPP

View file

@ -22,6 +22,7 @@
#include "../g_game.h"
#include "../z_zone.h"
#include "../k_waypoint.h"
#include "../music.h"
//
// ███████╗██╗██╗░░██╗███╗░░░███╗███████╗
@ -556,21 +557,11 @@ boolean Obj_ShrinkLaserCollide(mobj_t *gun, mobj_t *victim)
victim->destscale = FixedMul(victim->destscale, SHRINK_SCALE);
}
if (victim->player->invincibilitytimer > 0)
{
; // invincibility has priority in P_RestoreMusic, no point in starting here
}
else if (P_IsLocalPlayer(victim->player) == true)
{
S_ChangeMusicSpecial("kgrow");
}
else //used to be "if (P_IsDisplayPlayer(victim->player) == false)"
if (P_IsLocalPlayer(victim->player) == false && victim->player->invincibilitytimer == 0)
{
// don't play this if the player has invincibility -- that takes priority
S_StartSound(victim, sfx_alarmg);
}
P_RestoreMusic(victim->player);
}
}
else

View file

@ -183,9 +183,9 @@ boolean P_InQuicksand(mobj_t *mo);
boolean P_PlayerHitFloor(player_t *player, boolean fromAir, angle_t oldPitch, angle_t oldRoll);
void P_SetObjectMomZ(mobj_t *mo, fixed_t value, boolean relative);
void P_RestoreMusic(player_t *player);
void P_StartPositionMusic(boolean exact);
void P_EndingMusic(void);
void P_InvincGrowMusic(void);
mobj_t *P_SpawnGhostMobj(mobj_t *mobj);
mobj_t *P_SpawnFakeShadow(mobj_t *mobj, UINT8 offset);
INT32 P_GivePlayerRings(player_t *player, INT32 num_rings);
@ -237,36 +237,6 @@ 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_INVINCIBILITY, // Invincibility
JT_GROW, // Grow
NUMJINGLES
} jingletype_t;
struct jingle_t
{
char musname[7];
boolean looping;
};
extern jingle_t jingleinfo[NUMJINGLES];
#define JINGLEPOSTFADE 1000
void P_PlayJingle(player_t *player, jingletype_t jingletype);
boolean P_EvaluateMusicStatus(UINT16 status, const char *musname);
void P_PlayJingleMusic(player_t *player, const char *musname, UINT16 musflags, boolean looping, UINT16 status);
//
// P_MOBJ
//

View file

@ -104,6 +104,7 @@
#include "k_podium.h"
#include "k_rank.h"
#include "k_mapuser.h"
#include "music.h"
// Replay names have time
#if !defined (UNDER_CE)
@ -7923,6 +7924,23 @@ static void P_InitMinimapInfo(void)
minimapinfo.offs_y = FixedMul((minimapinfo.min_y + minimapinfo.map_h/2) << FRACBITS, minimapinfo.zoom);
}
void P_ResetLevelMusic(void)
{
if (mapheaderinfo[gamemap-1]->musname_size > 1)
mapmusrng = P_RandomKey(PR_MUSICSELECT, mapheaderinfo[gamemap-1]->musname_size);
else
mapmusrng = 0;
}
void P_LoadLevelMusic(void)
{
tic_t level_music_start = starttime + (TICRATE/2);
Music_StopAll();
Music_Remap("level", mapheaderinfo[gamemap-1]->musname[mapmusrng]);
Music_Seek("level", max(leveltime, level_music_start) - level_music_start);
}
/** Loads a level from a lump or external wad.
*
* \param fromnetsave If true, skip some stuff because we're loading a netgame snapshot.
@ -7969,6 +7987,11 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
P_InitLevelSettings();
if (!demo.title)
{
Music_Stop("title");
}
for (i = 0; i <= r_splitscreen; i++)
postimgtype[i] = postimg_none;
@ -7987,7 +8010,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
{
tic_t locstarttime, endtime, nowtime;
S_StopMusic(); // er, about that...
Music_StopAll(); // er, about that...
// Fade to an inverted screen, with a circle fade...
F_WipeStartScreen();
@ -8072,16 +8095,20 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
{
int wipetype = wipe_level_toblack;
// TODO: What is this?? This does nothing because P_LoadLevelMusic is gonna halt music, anyway.
#if 0
// Fade out music here. Deduct 2 tics so the fade volume actually reaches 0.
// But don't halt the music! S_Start will take care of that. This dodges a MIDI crash bug.
if (gamestate == GS_LEVEL)
S_FadeMusic(0, FixedMul(
FixedDiv((F_GetWipeLength(wipedefs[wipe_level_toblack])-2)*NEWTICRATERATIO, NEWTICRATE), MUSICRATE));
#endif
if (K_PodiumSequence())
{
// mapmusrng is set by local player position in K_ResetCeremony
S_InitLevelMusic(true);
P_ResetLevelMusic();
P_LoadLevelMusic();
}
else if (gamestate == GS_LEVEL)
{
@ -8093,7 +8120,12 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
// We should be fine starting music here.
// Don't do this during titlemap, because the menu code handles music by itself.
S_InitLevelMusic(fromnetsave);
// Netsave loading does this itself, because leveltime isn't set yet!
if (!fromnetsave)
{
P_ResetLevelMusic();
P_LoadLevelMusic();
}
}
if (gametyperules & GTR_SPECIALSTART)

View file

@ -103,6 +103,8 @@ extern mapthing_t *mapthings;
void P_SetupLevelSky(const char *skytexname, boolean global);
void P_RespawnThings(void);
void P_ResetLevelMusic(void);
void P_LoadLevelMusic(void);
boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate);
void P_PostLoadLevel(void);
#ifdef HWRENDER

View file

@ -2887,6 +2887,8 @@ boolean P_ProcessSpecial(activator_t *activator, INT16 special, INT32 *args, cha
break;
case 413: // Change music
// FIXME: port to new music system
#if 0
// console player only unless TMM_ALLPLAYERS is set
if ((args[1] & TMM_ALLPLAYERS) || (mo && mo->player && P_IsLocalPlayer(mo->player)) || titlemapinaction)
{
@ -2963,6 +2965,7 @@ boolean P_ProcessSpecial(activator_t *activator, INT16 special, INT32 *args, cha
// Except, you can use the TMM_NORELOAD flag to change this behavior.
// if (mapmusflags & MUSIC_RELOADRESET) then it will reset the music in G_PlayerReborn.
}
#endif
break;
case 414: // Play SFX

View file

@ -42,6 +42,7 @@
#include "k_specialstage.h"
#include "acs/interface.h"
#include "k_objects.h"
#include "music.h"
#ifdef PARANOIA
#include "deh_tables.h" // MOBJTYPE_LIST
@ -714,7 +715,6 @@ void P_Ticker(boolean run)
P_MoveChaseCamera(&players[0], &camera[0], false);
R_UpdateViewInterpolation();
P_MapEnd();
S_SetStackAdjustmentStart();
return;
}
}
@ -731,13 +731,9 @@ void P_Ticker(boolean run)
}
else if (demo.freecam && democam.cam) // special case: allow freecam to MOVE during pause!
P_DemoCameraMovement(democam.cam);
S_SetStackAdjustmentStart();
return;
}
if (!S_MusicPaused())
S_AdjustMusicStackTics();
for (i = 0; i <= r_splitscreen; i++)
postimgtype[i] = postimg_none;
@ -874,8 +870,7 @@ void P_Ticker(boolean run)
// Bosses have a punchy start, so no position.
if (leveltime == 1)
{
S_ChangeMusic(mapmusname, mapmusflags, true);
S_ShowMusicCredit();
Music_Play("level");
}
}
else if (leveltime < starttime + TICRATE)
@ -883,8 +878,7 @@ void P_Ticker(boolean run)
if (leveltime == (starttime + (TICRATE/2)))
{
// Plays the music after the starting countdown.
S_ChangeMusic(mapmusname, mapmusflags, true);
S_ShowMusicCredit();
Music_Play("level");
}
else if (starttime != introtime)
{
@ -892,7 +886,6 @@ void P_Ticker(boolean run)
if (leveltime == starttime-(3*TICRATE))
{
S_StartSound(NULL, sfx_s3ka7); // 3,
S_FadeMusic(0, 3500); //S_FadeOutStopMusic(3500); -- TODO the S_StopMusic callback can halt successor music instead
}
else if ((leveltime == starttime-(2*TICRATE))
|| (leveltime == starttime-TICRATE))

View file

@ -64,6 +64,7 @@
#include "k_director.h"
#include "g_party.h"
#include "k_profiles.h"
#include "music.h"
#ifdef HW3SOUND
#include "hardware/hw3sound.h"
@ -78,20 +79,6 @@
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
{"kinvnc" , true}, // JT_INVINCIBILITY
{"kgrow" , true}, // JT_GROW
};
//
// Movement.
//
@ -597,90 +584,6 @@ void P_AddPlayerScore(player_t *player, UINT32 amount)
player->roundscore = MAXSCORE;
}
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);
#ifdef 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 gamestate != GS_LEVEL, always play the jingle (1-up intermission)
if (gamestate == GS_LEVEL && player && !P_IsLocalPlayer(player))
return;
S_RetainMusic(musname, musflags, looping, 0, status);
//S_StopMusic();
S_ChangeMusicInternal(musname, looping);
}
boolean P_EvaluateMusicStatus(UINT16 status, const char *musname)
{
// \todo lua hook
int i;
boolean result = false;
for (i = 0; i < MAXPLAYERS; i++)
{
if (!P_IsLocalPlayer(&players[i]))
continue;
switch(status)
{
case JT_INVINCIBILITY: // Invincibility
if (players[i].invincibilitytimer > 1)
{
strlcpy(S_sfx[sfx_None].caption, "Invincibility", 14);
S_StartCaption(sfx_None, -1, players[i].invincibilitytimer);
result = true;
}
else
{
result = false;
}
break;
case JT_GROW: // Grow
if (players[i].growshrinktimer > 1)
{
strlcpy(S_sfx[sfx_None].caption, "Grow", 14);
S_StartCaption(sfx_None, -1, players[i].growshrinktimer);
result = true;
}
else
{
result = false;
}
break;
case JT_OTHER: // Other state
case JT_NONE: // Null state
case JT_MASTER: // Main level music
default:
result = true;
}
if (result)
break;
}
return result;
}
void P_PlayRinglossSound(mobj_t *source)
{
if (source->player && K_GetShieldFromItem(source->player->itemtype) != KSHIELD_NONE)
@ -716,7 +619,7 @@ void P_StartPositionMusic(boolean exact)
: (leveltime < 1))
return;
S_ChangeMusicInternal("encore", true);
Music_Remap("position", "encore");
}
else
{
@ -725,12 +628,15 @@ void P_StartPositionMusic(boolean exact)
: (leveltime < introtime))
return;
S_ChangeMusicInternal(
Music_Remap("position",
(mapheaderinfo[gamemap-1]->positionmus[0]
? mapheaderinfo[gamemap-1]->positionmus
: "postn"
), true);
));
}
Music_Play("position");
Music_DelayEnd("position", (starttime + (TICRATE/2)) - leveltime);
}
//
@ -870,110 +776,54 @@ skippingposition:
if (jingle == NULL)
return;
S_ChangeMusicInternal(jingle, false);
Music_Remap("finish", jingle);
Music_Play("finish");
}
//
// P_RestoreMusic
//
// Restores music after some special music change
//
void P_RestoreMusic(player_t *player)
void P_InvincGrowMusic(void)
{
UINT8 overrideLevel = 0;
SINT8 i;
INT32 invinc = 0;
INT32 grow = 0;
if (P_IsLocalPlayer(player) == false)
{
// Only applies to local players
return;
}
UINT8 i;
// Event - HERE COMES A NEW CHALLENGER
if (mapreset)
for (i = 0; i <= r_splitscreen; ++i)
{
S_ChangeMusicInternal("chalng", false);
return;
}
player_t *player = &players[displayplayers[i]];
// Event - Level Ending
if (musiccountdown > 0)
{
return;
}
// Event - Level Start
if ((K_CheckBossIntro() == false)
&& (leveltime < (starttime + (TICRATE/2)))) // see also where time overs are handled
{
P_StartPositionMusic(false); // inexact timing permitted
return;
}
for (i = 0; i <= r_splitscreen; i++)
{
player_t *checkPlayer = &players[displayplayers[i]];
if (!checkPlayer)
if (!P_IsLocalPlayer(player))
{
// Director cam on another player? Don't play
// this.
continue;
}
if (checkPlayer->exiting)
// Find the longest running timer among splitscreen
// players and use that.
if (player->invincibilitytimer > invinc)
{
return;
invinc = player->invincibilitytimer;
}
if (checkPlayer->invincibilitytimer > 1)
if (player->growshrinktimer > grow)
{
overrideLevel = max(overrideLevel, 2);
}
else if (checkPlayer->growshrinktimer > 1)
{
overrideLevel = max(overrideLevel, 1);
grow = player->growshrinktimer;
}
}
if (overrideLevel != 0)
if (invinc && !Music_Playing("invinc"))
{
// Do a jingle override.
jingletype_t jt = JT_NONE;
switch (overrideLevel)
{
// Lowest priority to highest priority.
case 1:
jt = JT_GROW;
break;
case 2:
jt = JT_INVINCIBILITY;
break;
default:
break;
}
if (jt != JT_NONE)
{
//CONS_Printf("JINGLE: %d\n", jt);
//if (S_RecallMusic(jt, false) == false)
//{
P_PlayJingle(player, jt);
//}
return;
}
Music_Play("invinc");
}
#if 0
// Event - Final Lap
// Still works for GME, but disabled for consistency
if ((gametyperules & GTR_CIRCUIT) && player->laps >= numlaps)
S_SpeedMusic(1.2f);
#endif
if (S_RecallMusic(JT_NONE, false) == false) // go down the stack
if (grow && !Music_Playing("grow"))
{
CONS_Debug(DBG_BASIC, "Cannot find any music in resume stack!\n");
S_ChangeMusicEx(mapmusname, mapmusflags, true, mapmusposition, 0, 0);
Music_Play("grow");
}
Music_DelayEnd("invinc", invinc);
Music_DelayEnd("grow", grow);
}
//
@ -1423,7 +1273,7 @@ void P_DoPlayerExit(player_t *player, pflags_t flags)
if (P_IsLocalPlayer(player) && !specialout)
{
S_StopMusic();
Music_StopAll();
musiccountdown = MUSICCOUNTDOWNMAX;
}
@ -1945,8 +1795,6 @@ static void P_CheckInvincibilityTimer(player_t *player)
{
//K_KartResetPlayerColor(player); -- this gets called every tic anyways
G_GhostAddColor((INT32) (player - players), GHC_NORMAL);
P_RestoreMusic(player);
return;
}
}
@ -4036,7 +3884,7 @@ void P_DoTimeOver(player_t *player)
if (P_IsLocalPlayer(player))
{
S_StopMusic();
Music_StopAll();
musiccountdown = MUSICCOUNTDOWNMAX;
}

View file

@ -35,6 +35,7 @@
#include "m_random.h" // P_RandomKey
#include "i_time.h"
#include "v_video.h" // V_ThinStringWidth
#include "music.h"
#ifdef HW3SOUND
// 3D Sound Interface
@ -62,10 +63,6 @@ static void PlaySoundIfUnfocused_OnChange(void);
static void ModFilter_OnChange(void);
#endif
static lumpnum_t S_GetMusicLumpNum(const char *mname);
static boolean S_CheckQueue(void);
consvar_t cv_samplerate = CVAR_INIT ("samplerate", "22050", 0, CV_Unsigned, NULL); //Alam: For easy hacking?
// stereo reverse
@ -1347,21 +1344,6 @@ void S_InitSfxChannels(INT32 sfxVolume)
/// Music
/// ------------------------
static char music_name[7]; // up to 6-character name
static void *music_data;
static UINT16 music_flags;
static boolean music_looping;
static consvar_t *music_refade_cv;
static int music_usage;
static char queue_name[7];
static UINT16 queue_flags;
static boolean queue_looping;
static UINT32 queue_position;
static UINT32 queue_fadeinms;
static tic_t pause_starttic;
void S_AttemptToRestoreMusic(void)
{
switch (gamestate)
@ -1369,18 +1351,20 @@ void S_AttemptToRestoreMusic(void)
case GS_LEVEL:
if (musiccountdown != 1)
{
P_RestoreMusic(&players[consoleplayer]);
P_LoadLevelMusic();
Music_Play("level");
break;
}
// FALLTHRU
case GS_INTERMISSION:
S_ChangeMusicInternal("racent", true);
Music_Play("intermission");
break;
case GS_CEREMONY:
S_ChangeMusicEx(mapmusname, mapmusflags, true, mapmusposition, 0, 0);
Music_Play("level");
break;
case GS_TITLESCREEN:
S_ChangeMusicInternal("_title", looptitle);
Music_Loop("title", looptitle);
Music_Play("title");
break;
case GS_MENU:
M_PlayMenuJam();
@ -1822,16 +1806,6 @@ handlenextsong:
S_UpdateSoundTestDef(false, true, true);
}
boolean S_PlaysimMusicDisabled(void)
{
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
}
//
// S_FindMusicDef
//
@ -2130,14 +2104,14 @@ void S_InitMusicDefs(void)
void S_ShowMusicCredit(void)
{
UINT8 i = 0;
musicdef_t *def = S_FindMusicDef(music_name, &i);
musicdef_t *def = S_FindMusicDef(Music_CurrentSong(), &i);
char credittext[128] = "";
char *work = NULL;
size_t len = 128, worklen;
INT32 widthused = BASEVIDWIDTH, workwidth;
if (!cv_songcredits.value || S_PlaysimMusicDisabled())
if (!cv_songcredits.value)
return;
if (!def) // No definitions
@ -2206,9 +2180,6 @@ void S_ShowMusicCredit(void)
void S_StopMusicCredit(void)
{
if (S_PlaysimMusicDisabled())
return;
cursongcredit.def = NULL;
}
@ -2216,26 +2187,11 @@ void S_StopMusicCredit(void)
/// Music Status
/// ------------------------
boolean S_DigMusicDisabled(void)
{
return digital_disabled;
}
boolean S_MusicDisabled(void)
{
return digital_disabled;
}
boolean S_MusicPlaying(void)
{
return I_SongPlaying();
}
boolean S_MusicPaused(void)
{
return I_SongPaused();
}
boolean S_MusicNotInFocus(void)
{
return (
@ -2243,616 +2199,16 @@ boolean S_MusicNotInFocus(void)
);
}
const char *S_MusicType(void)
{
return I_SongType();
}
const char *S_MusicName(void)
{
return music_name;
}
boolean S_MusicInfo(char *mname, UINT16 *mflags, boolean *looping)
{
if (!I_SongPlaying())
return false;
strncpy(mname, music_name, 7);
mname[6] = 0;
*mflags = music_flags;
*looping = music_looping;
return (boolean)mname[0];
}
boolean S_MusicExists(const char *mname)
{
return W_CheckNumForName(va("O_%s", mname)) != LUMPERROR;
}
/// ------------------------
/// Music Effects
/// ------------------------
boolean S_SpeedMusic(float speed)
{
return I_SetSongSpeed(speed);
}
/// ------------------------
/// Music Seeking
/// ------------------------
UINT32 S_GetMusicLength(void)
{
return I_GetSongLength();
}
boolean S_SetMusicLoopPoint(UINT32 looppoint)
{
return I_SetSongLoopPoint(looppoint);
}
UINT32 S_GetMusicLoopPoint(void)
{
return I_GetSongLoopPoint();
}
boolean S_SetMusicPosition(UINT32 position)
{
if (S_PlaysimMusicDisabled())
return false;
return I_SetSongPosition(position);
}
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!
/// ------------------------
char music_stack_nextmusname[7];
boolean music_stack_noposition = false;
UINT32 music_stack_fadeout = 0;
UINT32 music_stack_fadein = 0;
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 : (S_CheckQueue() ? queue_name : mapmusname)), 7);
music_stacks->musflags = (status == JT_MASTER ? mflags : (S_CheckQueue() ? queue_flags : mapmusflags));
music_stacks->looping = (status == JT_MASTER ? looping : (S_CheckQueue() ? queue_looping : true));
music_stacks->position = (status == JT_MASTER ? position : (S_CheckQueue() ? queue_position : S_GetMusicPosition()));
music_stacks->tic = gametic;
music_stacks->status = JT_MASTER;
music_stacks->mlumpnum = S_GetMusicLumpNum(music_stacks->musname);
music_stacks->noposition = S_CheckQueue();
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;
new_mst->mlumpnum = S_GetMusicLumpNum(new_mst->musname);
new_mst->noposition = false;
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, mst->musname))
{
if (!S_MusicExists(mst->musname)) // 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;
if (S_PlaysimMusicDisabled())
return false;
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))
{
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;
entry->mlumpnum = S_GetMusicLumpNum(entry->musname);
entry->noposition = false; // don't set this until we do the mapmuschanged check, below. Else, this breaks some resumes.
}
if (entry->status == JT_MASTER)
{
mapmuschanged = strnicmp(entry->musname, mapmusname, 7);
if (mapmuschanged)
{
strncpy(entry->musname, mapmusname, 7);
entry->musflags = mapmusflags;
entry->looping = true;
entry->position = mapmusposition;
entry->tic = gametic;
entry->status = JT_MASTER;
entry->mlumpnum = S_GetMusicLumpNum(entry->musname);
entry->noposition = true;
}
S_ResetMusicStack();
}
else if (!entry->status)
{
Z_Free(entry);
return false;
}
if (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 (!entry->noposition && !music_stack_noposition) // HACK: Global boolean to toggle position resuming, e.g., de-superize
{
UINT32 poslapse = 0;
// To prevent the game from jumping past the end of the music, we need
// to check if we can get the song's length. Otherwise, if the lapsed resume time goes
// over a LOOPPOINT, mixer_sound.c will be unable to calculate the new resume position.
if (S_GetMusicLength())
poslapse = (UINT32)((float)(gametic - entry->tic)/(float)TICRATE*(float)MUSICRATE);
newpos = entry->position + poslapse;
}
// If the newly recalled music lumpnum does not match the lumpnum that we stored in stack,
// then discard the new position. That way, we will not recall an invalid position
// when the music is replaced or digital/MIDI is toggled.
if (newpos > 0 && S_MusicPlaying() && S_GetMusicLumpNum(entry->musname) == entry->mlumpnum)
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
/// ------------------------
static lumpnum_t S_GetMusicLumpNum(const char *mname)
{
if (S_MusicExists(mname))
return W_GetNumForName(va("o_%s", mname));
else
return LUMPERROR;
}
static boolean S_LoadMusic(const char *mname)
{
lumpnum_t mlumpnum;
void *mdata;
if (S_MusicDisabled())
return false;
mlumpnum = S_GetMusicLumpNum(mname);
if (mlumpnum == LUMPERROR)
{
CONS_Alert(CONS_ERROR, "Music %.6s could not be loaded: lump not found!\n", mname);
return false;
}
// load & register it
mdata = W_CacheLumpNum(mlumpnum, PU_MUSIC);
if (I_LoadSong(mdata, W_LumpLength(mlumpnum)))
{
strncpy(music_name, mname, 7);
music_name[6] = 0;
music_data = mdata;
return true;
}
else
{
CONS_Alert(CONS_ERROR, "Music %.6s could not be loaded: engine failure!\n", mname);
return false;
}
}
static void S_UnloadMusic(void)
{
I_UnloadSong();
#ifndef HAVE_SDL //SDL uses RWOPS
Z_ChangeTag(music_data, PU_CACHE);
#endif
music_data = NULL;
music_name[0] = 0;
music_flags = 0;
music_looping = false;
music_refade_cv = 0;
music_usage = 0;
}
static boolean S_PlayMusic(boolean looping, UINT32 fadeinms)
{
//musicdef_t *def;
if (S_MusicDisabled())
return false;
I_UpdateSongLagConditions();
if ((!fadeinms && !I_PlaySong(looping)) ||
(fadeinms && !I_FadeInPlaySong(fadeinms, looping)))
{
CONS_Alert(CONS_ERROR, "Music %.6s could not be played: engine failure!\n", music_name);
S_UnloadMusic();
return false;
}
#if 0
/* set loop point from MUSICDEF */
for (def = musicdefstart; def; def = def->next)
{
if (strcasecmp(def->name, music_name) == 0)
{
if (def->loop_ms)
S_SetMusicLoopPoint(def->loop_ms);
break;
}
}
#endif
S_InitMusicVolume(); // switch between digi and sequence volume
if (S_MusicNotInFocus())
I_SetMusicVolume(0);
return true;
}
static void S_QueueMusic(const char *mmusic, UINT16 mflags, boolean looping, UINT32 position, UINT32 fadeinms)
{
strncpy(queue_name, mmusic, 7);
queue_flags = mflags;
queue_looping = looping;
queue_position = position;
queue_fadeinms = fadeinms;
}
static boolean S_CheckQueue(void)
{
return queue_name[0];
}
static void S_ClearQueue(void)
{
queue_name[0] = queue_flags = queue_looping = queue_position = queue_fadeinms = 0;
}
static void S_ChangeMusicToQueue(void)
{
S_ChangeMusicEx(queue_name, queue_flags, queue_looping, queue_position, 0, queue_fadeinms);
S_ClearQueue();
}
void S_ChangeMusicEx(const char *mmusic, UINT16 mflags, boolean looping, UINT32 position, UINT32 prefadems, UINT32 fadeinms)
{
char newmusic[7];
if (S_MusicDisabled() || S_PlaysimMusicDisabled())
return;
strncpy(newmusic, mmusic, 7);
newmusic[6] = 0;
// No Music (empty string)
if (newmusic[0] == 0)
{
if (prefadems)
I_FadeSong(0, prefadems, &S_StopMusic);
else
S_StopMusic();
return;
}
if (prefadems) // queue music change for after fade // allow even if the music is the same
// && S_MusicPlaying() // Let the delay happen even if we're not playing music
{
CONS_Debug(DBG_DETAILED, "Now fading out song %s\n", music_name);
S_QueueMusic(newmusic, mflags, looping, position, fadeinms);
I_FadeSong(0, prefadems, S_ChangeMusicToQueue);
return;
}
else if (strnicmp(music_name, newmusic, 6) || (mflags & MUSIC_FORCERESET))
{
CONS_Debug(DBG_DETAILED, "Now playing song %s\n", newmusic);
S_StopMusic();
if (!S_LoadMusic(newmusic))
return;
music_flags = mflags;
music_looping = looping;
{
UINT8 i = 0;
musicdef_t *def = S_FindMusicDef(music_name, &i);
if (def)
{
I_SetCurrentSongVolume(def->debug_volume != 0 ? def->debug_volume : def->volume);
}
}
if (!S_PlayMusic(looping, fadeinms))
return;
if (position)
I_SetSongPosition(position);
I_SetSongTrack(mflags & MUSIC_TRACKMASK);
// Slow level music down a bit in Encore. (Values are vibe-based. WE GET IT YOU VAPE)
S_SpeedMusic((encoremode && gamestate == GS_LEVEL) ? 0.86471f : 1.f);
}
else if (fadeinms) // let fades happen with same music
{
I_SetSongPosition(position);
I_FadeSong(100, fadeinms, NULL);
}
else // reset volume to 100 with same music
{
I_StopFadingSong();
I_FadeSong(100, 500, NULL);
}
}
void S_ChangeMusicSpecial (const char *mmusic)
{
if (cv_resetspecialmusic.value)
S_ChangeMusic(mmusic, MUSIC_FORCERESET, true);
else
S_ChangeMusicInternal(mmusic, true);
}
void S_StopMusic(void)
{
if (!I_SongPlaying() || S_PlaysimMusicDisabled())
return;
if (strcasecmp(music_name, mapmusname) == 0)
mapmusresume = I_GetSongPosition();
if (I_SongPaused())
I_ResumeSong();
I_StopSong();
S_UnloadMusic(); // for now, stopping also means you unload the song
if (cv_closedcaptioning.value)
{
if (closedcaptions[0].s-S_sfx == sfx_None)
{
if (gamestate != wipegamestate)
{
closedcaptions[0].c = NULL;
closedcaptions[0].s = NULL;
closedcaptions[0].t = 0;
closedcaptions[0].b = 0;
}
else
closedcaptions[0].t = CAPTIONFADETICS;
}
}
}
//
// Stop and resume music, during game PAUSE.
//
void S_PauseAudio(void)
{
if (I_SongPlaying() && !I_SongPaused())
I_PauseSong();
S_SetStackAdjustmentStart();
Music_PauseAll();
}
void S_ResumeAudio(void)
@ -2860,13 +2216,7 @@ void S_ResumeAudio(void)
if (S_MusicNotInFocus())
return;
if (soundtest.paused == true)
return;
if (I_SongPlaying() && I_SongPaused())
I_ResumeSong();
S_AdjustMusicStackTics();
Music_UnPauseAll();
}
void S_SetMusicVolume(INT32 digvolume)
@ -2879,103 +2229,10 @@ void S_SetMusicVolume(INT32 digvolume)
I_SetMusicVolume(digvolume);
}
void
S_SetRestoreMusicFadeInCvar (consvar_t *cv)
{
music_refade_cv = cv;
}
int
S_GetRestoreMusicFadeIn (void)
{
if (music_refade_cv)
return music_refade_cv->value;
else
return 0;
}
void
S_SetMusicUsage (int type)
{
music_usage = type;
I_UpdateSongLagConditions();
}
int
S_MusicUsage (void)
{
return music_usage;
}
/// ------------------------
/// Music Fading
/// ------------------------
void S_SetInternalMusicVolume(INT32 volume)
{
I_SetInternalMusicVolume(min(max(volume, 0), 100));
}
void S_StopFadingMusic(void)
{
I_StopFadingSong();
}
boolean S_FadeMusicFromVolume(UINT8 target_volume, INT16 source_volume, UINT32 ms)
{
if (S_PlaysimMusicDisabled())
return false;
if (source_volume < 0)
return I_FadeSong(target_volume, ms, NULL);
else
return I_FadeSongFromVolume(target_volume, source_volume, ms, NULL);
}
boolean S_FadeOutStopMusic(UINT32 ms)
{
if (S_PlaysimMusicDisabled())
return false;
return I_FadeSong(0, ms, &S_StopMusic);
}
/// ------------------------
/// Init & Others
/// ------------------------
//
// Per level startup code.
// Kills playing sounds at start of level,
// determines music if any, changes music.
//
void S_InitLevelMusic(boolean fromnetsave)
{
if (mapmusflags & MUSIC_RELOADRESET)
{
if (!fromnetsave)
{
if (mapheaderinfo[gamemap-1]->musname_size > 1)
mapmusrng = P_RandomKey(PR_MUSICSELECT, mapheaderinfo[gamemap-1]->musname_size);
else
mapmusrng = 0;
}
strncpy(mapmusname, mapheaderinfo[gamemap-1]->musname[mapmusrng], 7);
mapmusname[6] = 0;
mapmusflags = (mapheaderinfo[gamemap-1]->mustrack & MUSIC_TRACKMASK);
mapmusposition = mapheaderinfo[gamemap-1]->muspos;
mapmusresume = 0;
}
S_StopMusic();
S_ResetMusicStack();
music_stack_noposition = false;
music_stack_fadeout = 0;
music_stack_fadein = JINGLEPOSTFADE;
}
static inline void PrintMusicDefField(const char *label, const char *field)
{
if (field)
@ -3095,7 +2352,7 @@ static void Command_RestartAudio_f(void)
if (dedicated) // No point in doing anything if game is a dedicated server.
return;
S_StopMusic();
Music_StopAll();
S_StopSounds();
I_ShutdownMusic();
I_ShutdownSound();
@ -3223,7 +2480,7 @@ static void Command_MusicDef_f(void)
{
UINT8 i = 0;
def = S_FindMusicDef(music_name, &i);
def = S_FindMusicDef(Music_CurrentSong(), &i);
def->debug_volume = atoi(arg2);
I_SetCurrentSongVolume(def->debug_volume);
@ -3298,13 +2555,11 @@ void GameDigiMusic_OnChange(void)
digital_disabled = false;
I_StartupSound(); // will return early if initialised
I_InitMusic();
S_AttemptToRestoreMusic();
}
else
{
digital_disabled = true;
S_StopMusic();
I_UnloadSong();
}
}

View file

@ -157,22 +157,8 @@ void S_StopSound(void *origin);
boolean S_DigMusicDisabled(void);
boolean S_MusicDisabled(void);
boolean S_MusicPlaying(void);
boolean S_MusicPaused(void);
boolean S_MusicNotInFocus(void);
const char *S_MusicType(void);
const char *S_MusicName(void);
boolean S_MusicExists(const char *mname);
boolean S_MusicInfo(char *mname, UINT16 *mflags, boolean *looping);
//
// Music Effects
//
// Set Speed of Music
boolean S_SpeedMusic(float speed);
#define MAXDEFTRACKS 3
@ -240,8 +226,6 @@ void S_SoundTestTogglePause(void);
void S_TickSoundTest(void);
#define SOUNDTEST_FADEOUTSECONDS 5
boolean S_PlaysimMusicDisabled(void);
extern musicdef_t *musicdefstart;
void S_LoadMusicDefs(UINT16 wadnum);
@ -250,84 +234,10 @@ musicdef_t *S_FindMusicDef(const char *name, UINT8 *i);
void S_ShowMusicCredit(void);
void S_StopMusicCredit(void);
//
// Music Seeking
//
// Get Length of Music
UINT32 S_GetMusicLength(void);
// Set LoopPoint of Music
boolean S_SetMusicLoopPoint(UINT32 looppoint);
// Get LoopPoint of Music
UINT32 S_GetMusicLoopPoint(void);
// Set Position of Music
boolean S_SetMusicPosition(UINT32 position);
// Get Position of Music
UINT32 S_GetMusicPosition(void);
//
// Music Stacking (Jingles)
//
struct musicstack_t
{
char musname[7];
UINT16 musflags;
boolean looping;
UINT32 position;
tic_t tic;
UINT16 status;
lumpnum_t mlumpnum;
boolean noposition; // force music stack resuming from zero (like music_stack_noposition)
musicstack_t *prev;
musicstack_t *next;
};
extern char music_stack_nextmusname[7];
extern boolean music_stack_noposition;
extern UINT32 music_stack_fadeout;
extern 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
//
enum
{
MUS_SPECIAL = 1,/* powerups--invincibility, grow */
};
// Start music track, arbitrary, given its name, and set whether looping
// note: music flags 12 bits for tracknum (gme, other formats with more than one track)
// 13-15 aren't used yet
// and the last bit we ignore (internal game flag for resetting music on reload)
void S_ChangeMusicEx(const char *mmusic, UINT16 mflags, boolean looping, UINT32 position, UINT32 prefadems, UINT32 fadeinms);
#define S_ChangeMusicInternal(a,b) S_ChangeMusicEx(a,0,b,0,0,0)
#define S_ChangeMusic(a,b,c) S_ChangeMusicEx(a,b,c,0,0,0)
void S_ChangeMusicSpecial (const char *mmusic);
void S_SetRestoreMusicFadeInCvar (consvar_t *cvar);
#define S_ClearRestoreMusicFadeInCvar() \
S_SetRestoreMusicFadeInCvar(0)
int S_GetRestoreMusicFadeIn (void);
void S_SetMusicUsage (int type);
int S_MusicUsage (void);
// Stops the music.
void S_StopMusic(void);
// Stop and resume music, during game PAUSE.
void S_PauseAudio(void);
void S_ResumeAudio(void);
@ -339,17 +249,6 @@ void S_DisableSound(void);
// Attempt to restore music based on gamestate.
void S_AttemptToRestoreMusic(void);
//
// Music Fading
//
void S_SetInternalMusicVolume(INT32 volume);
void S_StopFadingMusic(void);
boolean S_FadeMusicFromVolume(UINT8 target_volume, INT16 source_volume, UINT32 ms);
#define S_FadeMusic(a, b) S_FadeMusicFromVolume(a, -1, b)
#define S_FadeInChangeMusic(a,b,c,d) S_ChangeMusicEx(a,b,c,0,0,d)
boolean S_FadeOutStopMusic(UINT32 ms);
//
// Updates music & sounds
//

View file

@ -50,6 +50,8 @@
#include "k_grandprix.h" // we need to know grandprix status for titlecards
#include "k_boss.h"
#include "k_zvote.h"
#include "music.h"
#include "i_sound.h"
UINT16 objectsdrawn = 0;
@ -381,38 +383,40 @@ static void ST_pushDebugTimeMS(INT32 *height, const char *label, UINT32 ms)
static void ST_drawMusicDebug(INT32 *height)
{
char mname[7];
UINT16 mflags; // unused
boolean looping;
const char *mname = Music_CurrentSong();
boolean looping = Music_CanLoop(Music_CurrentId());
UINT8 i = 0;
const musicdef_t *def;
const char *format;
if (!S_MusicInfo(mname, &mflags, &looping))
ST_pushDebugString(height, va(" Tune: %8s", Music_CurrentId()));
ST_pushRow(height);
if (!strcmp(mname, ""))
{
ST_pushDebugString(height, "Song: <NOTHING>");
return;
}
def = S_FindMusicDef(mname, &i);
format = S_MusicType();
format = I_SongType();
ST_pushDebugTimeMS(height, " Elapsed: ", S_GetMusicPosition());
ST_pushDebugTimeMS(height, " Elapsed: ", I_GetSongPosition());
ST_pushDebugTimeMS(height, looping
? " Loop B: "
: "Duration: ", S_GetMusicLength());
: "Duration: ", I_GetSongLength());
if (looping)
{
ST_pushDebugTimeMS(height, " Loop A: ", S_GetMusicLoopPoint());
ST_pushDebugTimeMS(height, " Loop A: ", I_GetSongLoopPoint());
}
ST_pushRow(height);
if (format)
{
ST_pushDebugString(height, va(" Format: %8s", S_MusicType()));
ST_pushDebugString(height, va(" Format: %8s", I_SongType()));
}
ST_pushDebugString(height, va(" Song: %8s", mname));

View file

@ -50,6 +50,7 @@
#include "k_grandprix.h"
#include "k_serverstats.h" // SV_BumpMatchStats
#include "m_easing.h"
#include "music.h"
#ifdef HWRENDER
#include "hardware/hw_main.h"
@ -1823,7 +1824,7 @@ void Y_StartIntermission(void)
I_Error("Y_StartIntermission: Internal map ID %d not found (nummapheaders = %d)", prevmap, nummapheaders);
if (timer > 1 && musiccountdown == 0)
S_ChangeMusicInternal("racent", true); // loop it
Music_Play("intermission");
S_ShowMusicCredit(); // Always call