diff --git a/Minecraft.Client/Common/Audio/Consoles_SoundEngine.cpp b/Minecraft.Client/Common/Audio/Consoles_SoundEngine.cpp index 54e9a1ef4..c75effc83 100644 --- a/Minecraft.Client/Common/Audio/Consoles_SoundEngine.cpp +++ b/Minecraft.Client/Common/Audio/Consoles_SoundEngine.cpp @@ -18,6 +18,10 @@ void ConsoleSoundEngine::SetIsPlayingStreamingGameMusic(bool bVal) { m_bIsPlayingStreamingGameMusic=bVal; } +bool ConsoleSoundEngine::GetIsPlayingMenuMusic() +{ + return m_bIsPlayingMenuMusic; +} bool ConsoleSoundEngine::GetIsPlayingEndMusic() { return m_bIsPlayingEndMusic; @@ -26,6 +30,10 @@ bool ConsoleSoundEngine::GetIsPlayingNetherMusic() { return m_bIsPlayingNetherMusic; } +void ConsoleSoundEngine::SetIsPlayingMenuMusic(bool bVal) +{ + m_bIsPlayingMenuMusic = bVal; +} void ConsoleSoundEngine::SetIsPlayingEndMusic(bool bVal) { m_bIsPlayingEndMusic=bVal; diff --git a/Minecraft.Client/Common/Audio/Consoles_SoundEngine.h b/Minecraft.Client/Common/Audio/Consoles_SoundEngine.h index 9a1ae3723..5adeafd44 100644 --- a/Minecraft.Client/Common/Audio/Consoles_SoundEngine.h +++ b/Minecraft.Client/Common/Audio/Consoles_SoundEngine.h @@ -61,9 +61,11 @@ class ConsoleSoundEngine virtual bool GetIsPlayingStreamingGameMusic() ; virtual void SetIsPlayingStreamingCDMusic(bool bVal) ; virtual void SetIsPlayingStreamingGameMusic(bool bVal) ; + virtual bool GetIsPlayingMenuMusic(); virtual bool GetIsPlayingEndMusic() ; virtual bool GetIsPlayingNetherMusic() ; virtual void SetIsPlayingEndMusic(bool bVal) ; + virtual void SetIsPlayingMenuMusic(bool bVal); virtual void SetIsPlayingNetherMusic(bool bVal) ; static const WCHAR *wchSoundNames[eSoundType_MAX]; static const WCHAR *wchUISoundNames[eSFX_MAX]; @@ -94,6 +96,7 @@ class ConsoleSoundEngine bool m_bIsPlayingStreamingCDMusic; bool m_bIsPlayingStreamingGameMusic; + bool m_bIsPlayingMenuMusic; bool m_bIsPlayingEndMusic; bool m_bIsPlayingNetherMusic; }; \ No newline at end of file diff --git a/Minecraft.Client/Common/Audio/MusicTrackManager.cpp b/Minecraft.Client/Common/Audio/MusicTrackManager.cpp new file mode 100644 index 000000000..7fbe2a55e --- /dev/null +++ b/Minecraft.Client/Common/Audio/MusicTrackManager.cpp @@ -0,0 +1,97 @@ +#include "stdafx.h" +#include "MusicTrackManager.h" +#include "../Minecraft.World/Random.h" + +MusicTrackManager::MusicTrackManager(Random* rng) + : m_random(rng) +{ + // The domains will be filled later via setDomainRange(). +} + +MusicTrackManager::~MusicTrackManager() +{ + // unordered_map will automatically destroy its DomainInfo objects, + // which in turn delete[] the heard arrays. +} + +void MusicTrackManager::setDomainRange(Domain domain, int minIdx, int maxIdx) +{ + assert(minIdx <= maxIdx); + // Use emplace to construct the DomainInfo in-place. + // If the domain already exists, this will replace it (C++17). + m_domains.erase(domain); // Remove old entry if any + m_domains.emplace(domain, DomainInfo(minIdx, maxIdx)); +} + +int MusicTrackManager::selectTrack(Domain domain) +{ + // Special case: if domain is None, return -1 (no track). + if (domain == Domain::None) + return -1; + + DomainInfo& info = getInfo(domain); + + // If range contains only one track, just return that track. + if (info.trackCount == 1) + return info.minIdx; + + // Check whether all tracks have been heard. + bool allHeard = true; + for (int i = 0; i < info.trackCount; ++i) + { + if (!info.heard[i]) + { + allHeard = false; + break; + } + } + + // If all tracks have been heard, reset the heard flags. + if (allHeard) + { + std::memset(info.heard, 0, sizeof(bool) * info.trackCount); + } + + // Try up to (trackCount/2 + 1) times to pick a track that hasn't been heard. + // This biases toward unplayed tracks but doesn't guarantee it if the random + // keeps hitting played ones. It's a compromise between fairness and performance. + const int maxAttempts = info.trackCount / 2 + 1; + for (int attempt = 0; attempt < maxAttempts; ++attempt) + { + int idx = m_random->nextInt(info.trackCount); // 0 .. trackCount-1 + if (!info.heard[idx]) + { + info.heard[idx] = true; + return info.minIdx + idx; + } + } + + // Fallback: if we couldn't find an unplayed track (should be rare), + // just pick any random track and mark it as heard. + int fallbackIdx = m_random->nextInt(info.trackCount); + info.heard[fallbackIdx] = true; + return info.minIdx + fallbackIdx; +} + +void MusicTrackManager::resetDomain(Domain domain) +{ + DomainInfo& info = getInfo(domain); + std::memset(info.heard, 0, sizeof(bool) * info.trackCount); +} + +// ---------- private helpers ---------- + +MusicTrackManager::DomainInfo& MusicTrackManager::getInfo(Domain domain) +{ + auto it = m_domains.find(domain); + assert(it != m_domains.end() && "Domain not initialized. Call setDomainRange first."); + return it->second; +} + +const MusicTrackManager::DomainInfo& MusicTrackManager::getInfo(Domain domain) const +{ + auto it = m_domains.find(domain); + assert(it != m_domains.end() && "Domain not initialized."); + return it->second; +} + diff --git a/Minecraft.Client/Common/Audio/MusicTrackManager.h b/Minecraft.Client/Common/Audio/MusicTrackManager.h new file mode 100644 index 000000000..a253ded60 --- /dev/null +++ b/Minecraft.Client/Common/Audio/MusicTrackManager.h @@ -0,0 +1,117 @@ +#pragma once + +#include +#include // for memset +#include + +// Forward declaration of the random number generator used by SoundEngine. +// Replace with your actual random class if different. +class Random; + +/** + * Manages selection of music tracks across different gameplay domains + * (Menu, Overworld Survival, Overworld Creative, Nether, End). + * Each domain maintains its own range of track indices and a history + * of recently played tracks to avoid immediate repetition. + */ +class MusicTrackManager +{ +public: + // Enumeration of all music domains. The values match the original + // SoundEngine constants for easy conversion. + enum class Domain + { + Menu = -1, + OverworldSurvival = 0, + OverworldCreative = 1, + Nether = 2, + End = 3, + None = 4 // Special value for when no background music should play (e.g., during a music disc) + }; + + /** + * Constructor. + * @param rng Pointer to a random number generator (must remain valid). + */ + explicit MusicTrackManager(Random* rng); + + /** + * Destructor – frees all dynamically allocated heard-track arrays. + */ + ~MusicTrackManager(); + + // Prevent copying (to avoid double deletion of internal arrays). + MusicTrackManager(const MusicTrackManager&) = delete; + MusicTrackManager& operator=(const MusicTrackManager&) = delete; + + /** + * Sets the inclusive index range for a given domain. + * @param domain The music domain. + * @param minIdx First valid track index. + * @param maxIdx Last valid track index (must be >= minIdx). + */ + void setDomainRange(Domain domain, int minIdx, int maxIdx); + + /** + * Selects a track index for the specified domain. + * Tries to return a track that has not been played recently; + * if all tracks have been played, resets the history and picks one randomly. + * For domains with only one track, that track is always returned. + * @param domain The domain for which to choose a track. + * @return A valid track index within the domain's range. + */ + int selectTrack(Domain domain); + + /** + * Resets the heard history for a domain, marking all tracks as "not heard". + * Useful when switching domains or after a major game state change. + * @param domain The domain to reset. + */ + void resetDomain(Domain domain); + + int getDomainMin(Domain d) const { return getInfo(d).minIdx; } + int getDomainMax(Domain d) const { return getInfo(d).maxIdx; } + +private: + // Internal structure storing range and heard-array for a domain. + struct DomainInfo + { + int minIdx; // First track index (inclusive) + int maxIdx; // Last track index (inclusive) + int trackCount; // Number of tracks in this domain + bool* heard; // Dynamic array: heard[i] == true if track (minIdx + i) has been played recently + + DomainInfo(int minVal = 0, int maxVal = 0) + : minIdx(minVal), maxIdx(maxVal), trackCount(maxVal - minVal + 1) + { + heard = new bool[trackCount](); // value-initialized to false + } + + // Move constructor (optional, but needed if we want to store in unordered_map with emplace) + DomainInfo(DomainInfo&& other) noexcept + : minIdx(other.minIdx), maxIdx(other.maxIdx), trackCount(other.trackCount), heard(other.heard) + { + other.heard = nullptr; // prevent double deletion + } + + // Destructor + ~DomainInfo() + { + delete[] heard; + } + + // No copy + DomainInfo(const DomainInfo&) = delete; + DomainInfo& operator=(const DomainInfo&) = delete; + }; + + // Map from Domain to its info. + std::unordered_map m_domains; + + // Pointer to the random number generator. + Random* m_random; + + // Helper to get the info for a domain (assumes domain exists). + DomainInfo& getInfo(Domain domain); + const DomainInfo& getInfo(Domain domain) const; +}; diff --git a/Minecraft.Client/Common/Audio/SoundEngine.cpp b/Minecraft.Client/Common/Audio/SoundEngine.cpp index 24cb7bf45..989eaf238 100644 --- a/Minecraft.Client/Common/Audio/SoundEngine.cpp +++ b/Minecraft.Client/Common/Audio/SoundEngine.cpp @@ -114,6 +114,9 @@ const char *SoundEngine::m_szStreamFileA[eStream_Max]= "hal4", "nuance1", "nuance2", + "piano1", + "piano2", + "piano3", // 11 #ifndef _XBOX "creative1", @@ -121,26 +124,23 @@ const char *SoundEngine::m_szStreamFileA[eStream_Max]= "creative3", "creative4", "creative5", - "creative6", + "creative6", // 17 + "menu1", "menu2", "menu3", - "menu4", + "menu4", // 21 #endif - "piano1", - "piano2", - "piano3", - // Nether "nether1", "nether2", "nether3", - "nether4", + "nether4", // 25 // The End "the_end_dragon_alive", - "the_end_end", + "the_end_end", // 27 // CDs "11", @@ -191,23 +191,22 @@ void SoundEngine::init(Options* pOptions) return; } -void SoundEngine::SetStreamingSounds(int iOverworldMin, int iOverWorldMax, int iNetherMin, int iNetherMax, int iEndMin, int iEndMax, int iCD1) +void SoundEngine::SetStreamingSounds(int iMenuMin, int iMenuMax, + int iOverworldSurvivalMin, int iOverWorldSurvivalMax, + int iOverworldCreativeMin, int iOverWorldCreativeMax, + int iNetherMin, int iNetherMax, + int iEndMin, int iEndMax, + int iCD1) { - m_iStream_Overworld_Min=iOverworldMin; - m_iStream_Overworld_Max=iOverWorldMax; - m_iStream_Nether_Min=iNetherMin; - m_iStream_Nether_Max=iNetherMax; - m_iStream_End_Min=iEndMin; - m_iStream_End_Max=iEndMax; - m_iStream_CD_1=iCD1; - - // array to monitor recently played tracks - if(m_bHeardTrackA) - { - delete [] m_bHeardTrackA; - } - m_bHeardTrackA = new bool[iEndMax+1]; - memset(m_bHeardTrackA,0,sizeof(bool)*iEndMax+1); + using Domain = MusicTrackManager::Domain; + + m_musicTrackManager.setDomainRange(Domain::Menu, iMenuMin, iMenuMax); + m_musicTrackManager.setDomainRange(Domain::OverworldSurvival, iOverworldSurvivalMin, iOverWorldSurvivalMax); + m_musicTrackManager.setDomainRange(Domain::OverworldCreative, iOverworldCreativeMin, iOverWorldCreativeMax); + m_musicTrackManager.setDomainRange(Domain::Nether, iNetherMin, iNetherMax); + m_musicTrackManager.setDomainRange(Domain::End, iEndMin, iEndMax); + + m_iStream_CD_1 = iCD1; } void SoundEngine::updateMiniAudio() @@ -391,9 +390,8 @@ void SoundEngine::tick(shared_ptr *players, float a) // SoundEngine // ///////////////////////////////////////////// -SoundEngine::SoundEngine() +SoundEngine::SoundEngine(): random(new Random()), m_musicTrackManager(random), m_currentMusicDomain(MusicTrackManager::Domain::Menu) { - random = new Random(); memset(&m_engine, 0, sizeof(ma_engine)); memset(&m_engineConfig, 0, sizeof(ma_engine_config)); m_musicStreamActive = false; @@ -401,15 +399,29 @@ SoundEngine::SoundEngine() m_iMusicDelay=0; m_validListenerCount=0; - m_bHeardTrackA=nullptr; + m_musicTrackManager.setDomainRange(MusicTrackManager::Domain::Menu, + eStream_Overworld_Menu1, + eStream_Overworld_Menu4); - // Start the streaming music playing some music from the overworld - SetStreamingSounds(eStream_Overworld_Calm1,eStream_Overworld_piano3, - eStream_Nether1,eStream_Nether4, - eStream_end_dragon,eStream_end_end, - eStream_CD_1); + m_musicTrackManager.setDomainRange(MusicTrackManager::Domain::OverworldSurvival, + eStream_Overworld_Calm1, + eStream_Overworld_piano3); - m_musicID=getMusicID(LevelData::DIMENSION_OVERWORLD); + m_musicTrackManager.setDomainRange(MusicTrackManager::Domain::OverworldCreative, + eStream_Overworld_Creative1, + eStream_Overworld_Creative6); + + m_musicTrackManager.setDomainRange(MusicTrackManager::Domain::Nether, + eStream_Nether1, + eStream_Nether4); + + m_musicTrackManager.setDomainRange(MusicTrackManager::Domain::End, + eStream_end_dragon, + eStream_end_end); + + m_iStream_CD_1 = eStream_CD_1; + + m_musicID = getTrackForDomain(MusicTrackManager::Domain::Menu); m_StreamingAudioInfo.bIs3D=false; m_StreamingAudioInfo.x=0; @@ -660,176 +672,80 @@ void SoundEngine::playUI(int iSound, float volume, float pitch) // playStreaming // ///////////////////////////////////////////// -void SoundEngine::playStreaming(const wstring& name, float x, float y , float z, float volume, float pitch, bool bMusicDelay) +MusicTrackManager::Domain SoundEngine::determineCurrentMusicDomain() const { - // This function doesn't actually play a streaming sound, just sets states and an id for the music tick to play it - // Level audio will be played when a play with an empty name comes in - // CD audio will be played when a named stream comes in - - m_StreamingAudioInfo.x=x; - m_StreamingAudioInfo.y=y; - m_StreamingAudioInfo.z=z; - m_StreamingAudioInfo.volume=volume; - m_StreamingAudioInfo.pitch=pitch; + Minecraft* mc = Minecraft::GetInstance(); + if (!mc || !mc->level) + return MusicTrackManager::Domain::Menu; - if(m_StreamState==eMusicStreamState_Playing) - { - m_StreamState=eMusicStreamState_Stop; - } - else if(m_StreamState==eMusicStreamState_Opening) - { - m_StreamState=eMusicStreamState_OpeningCancel; - } + bool inEnd = false, inNether = false, creative = false; - if(name.empty()) + for (unsigned int i = 0; i < MAX_LOCAL_PLAYERS; ++i) { - // music, or stop CD - m_StreamingAudioInfo.bIs3D=false; + auto player = mc->localplayers[i]; + if (!player) continue; - // we need a music id - // random delay of up to 3 minutes for music - m_iMusicDelay = random->nextInt(20 * 60 * 3);//random->nextInt(20 * 60 * 10) + 20 * 60 * 10; + if (player->dimension == LevelData::DIMENSION_END) + inEnd = true; + else if (player->dimension == LevelData::DIMENSION_NETHER) + inNether = true; -#ifdef _DEBUG - m_iMusicDelay=0; -#endif - Minecraft *pMinecraft=Minecraft::GetInstance(); - - bool playerInEnd=false; - bool playerInNether=false; - - for(unsigned int i=0;ilocalplayers[i]!=nullptr) - { - if(pMinecraft->localplayers[i]->dimension==LevelData::DIMENSION_END) - { - playerInEnd=true; - } - else if(pMinecraft->localplayers[i]->dimension==LevelData::DIMENSION_NETHER) - { - playerInNether=true; - } - } - } - if(playerInEnd) - { - m_musicID = getMusicID(LevelData::DIMENSION_END); - } - else if(playerInNether) - { - m_musicID = getMusicID(LevelData::DIMENSION_NETHER); - } - else - { - m_musicID = getMusicID(LevelData::DIMENSION_OVERWORLD); - } + if (player->level->getLevelData()->getGameType()->isCreative()) + creative = true; } - else - { - // jukebox - m_StreamingAudioInfo.bIs3D=true; - m_musicID=getMusicID(name); - m_iMusicDelay=0; - } -} + if (inEnd) return MusicTrackManager::Domain::End; + if (inNether) return MusicTrackManager::Domain::Nether; + if (creative) return MusicTrackManager::Domain::OverworldCreative; + return MusicTrackManager::Domain::OverworldSurvival; +} -int SoundEngine::GetRandomishTrack(int iStart,int iEnd) +void SoundEngine::playStreaming(const wstring& name, float x, float y, float z, float volume, float pitch, bool bMusicDelay) { - // 4J-PB - make it more likely that we'll get a track we've not heard for a while, although repeating tracks sometimes is fine - - // if all tracks have been heard, clear the flags - bool bAllTracksHeard=true; - int iVal=iStart; - for(size_t i=iStart;i<=iEnd;i++) + m_StreamingAudioInfo.x = x; + m_StreamingAudioInfo.y = y; + m_StreamingAudioInfo.z = z; + m_StreamingAudioInfo.volume = volume; + m_StreamingAudioInfo.pitch = pitch; + + if (m_StreamState == eMusicStreamState_Playing) + m_StreamState = eMusicStreamState_Stop; + else if (m_StreamState == eMusicStreamState_Opening) + m_StreamState = eMusicStreamState_OpeningCancel; + + if (name.empty()) { - if(m_bHeardTrackA[i]==false) - { - bAllTracksHeard=false; - app.DebugPrintf("Not heard all tracks yet\n"); - break; - } - } + m_StreamingAudioInfo.bIs3D = false; + m_iMusicDelay = bMusicDelay ? random->nextInt(20 * 60 * 3) : 0; - if(bAllTracksHeard) - { - app.DebugPrintf("Heard all tracks - resetting the tracking array\n"); +#ifdef _DEBUG + m_iMusicDelay = 0; +#endif - for(size_t i=iStart;i<=iEnd;i++) - { - m_bHeardTrackA[i]=false; - } + MusicTrackManager::Domain domain = determineCurrentMusicDomain(); + m_currentMusicDomain = domain; + m_musicID = getTrackForDomain(domain); } - - // trying to get a track we haven't heard, but not too hard - for(size_t i=0;i<=((iEnd-iStart)/2);i++) + else { - // random->nextInt(1) will always return 0 - iVal=random->nextInt((iEnd-iStart)+1)+iStart; - if(m_bHeardTrackA[iVal]==false) - { - // not heard this - app.DebugPrintf("(%d) Not heard track %d yet, so playing it now\n",i,iVal); - m_bHeardTrackA[iVal]=true; - break; - } - else - { - app.DebugPrintf("(%d) Skipping track %d already heard it recently\n",i,iVal); - } + // Disk (jukebox) + m_StreamingAudioInfo.bIs3D = true; + m_musicID = getMusicID(name); + m_iMusicDelay = 0; } - - app.DebugPrintf("Select track %d\n",iVal); - return iVal; } + ///////////////////////////////////////////// // -// getMusicID +// getTrackForDomain(MusicTrackManager::Domain domain) // ///////////////////////////////////////////// -int SoundEngine::getMusicID(int iDomain) +int SoundEngine::getTrackForDomain(MusicTrackManager::Domain domain) { - int iRandomVal=0; - Minecraft *pMinecraft=Minecraft::GetInstance(); - - // Before the game has started? - if(pMinecraft==nullptr) - { - // any track from the overworld - return GetRandomishTrack(m_iStream_Overworld_Min,m_iStream_Overworld_Max); - } - - if(pMinecraft->skins->isUsingDefaultSkin()) - { - switch(iDomain) - { - case LevelData::DIMENSION_END: - // the end isn't random - it has different music depending on whether the dragon is alive or not, but we've not added the dead dragon music yet - return m_iStream_End_Min; - case LevelData::DIMENSION_NETHER: - return GetRandomishTrack(m_iStream_Nether_Min,m_iStream_Nether_Max); - //return m_iStream_Nether_Min + random->nextInt(m_iStream_Nether_Max-m_iStream_Nether_Min); - default: //overworld - //return m_iStream_Overworld_Min + random->nextInt(m_iStream_Overworld_Max-m_iStream_Overworld_Min); - return GetRandomishTrack(m_iStream_Overworld_Min,m_iStream_Overworld_Max); - } - } - else - { - // using a texture pack - may have multiple End music tracks - switch(iDomain) - { - case LevelData::DIMENSION_END: - return GetRandomishTrack(m_iStream_End_Min,m_iStream_End_Max); - case LevelData::DIMENSION_NETHER: - //return m_iStream_Nether_Min + random->nextInt(m_iStream_Nether_Max-m_iStream_Nether_Min); - return GetRandomishTrack(m_iStream_Nether_Min,m_iStream_Nether_Max); - default: //overworld - //return m_iStream_Overworld_Min + random->nextInt(m_iStream_Overworld_Max-m_iStream_Overworld_Min); - return GetRandomishTrack(m_iStream_Overworld_Min,m_iStream_Overworld_Max); - } - } + if (domain == MusicTrackManager::Domain::End) + return m_musicTrackManager.getDomainMin(domain); + else + return m_musicTrackManager.selectTrack(domain); } ///////////////////////////////////////////// @@ -958,516 +874,485 @@ void SoundEngine::playMusicTick() #endif } -// AP - moved to a separate function so it can be called from the mixer callback on Vita -void SoundEngine::playMusicUpdate() +//============================================================================= +// playMusicUpdate +// Called every frame (or via mixer callback on Vita) to manage streaming music. +// Handles state machine for background music and music discs (jukebox). +//============================================================================= +void SoundEngine::playMusicUpdate() { - static float fMusicVol = 0.0f; - fMusicVol = getMasterMusicVolume(); - - switch(m_StreamState) - { - case eMusicStreamState_Idle: - - // start a stream playing - if (m_iMusicDelay > 0) - { - m_iMusicDelay--; - return; - } - - if (m_musicStreamActive) - { - app.DebugPrintf("WARNING: m_musicStreamActive already true in Idle state, resetting to Playing\n"); - m_StreamState = eMusicStreamState_Playing; - return; - } + // Cache the current master music volume (may be zero if system music is playing) + float masterVolume = getMasterMusicVolume(); - if(m_musicID!=-1) - { - // start playing it + //------------------------------------------------------------------------- + // State machine for streaming audio (background music or discs) + //------------------------------------------------------------------------- + switch (m_StreamState) + { + //--------------------------------------------------------------------- + // IDLE – no stream is open; waiting for a delay or ready to start + //--------------------------------------------------------------------- + case eMusicStreamState_Idle: + { + // If a delay is active (e.g., between tracks), decrement and wait + if (m_iMusicDelay > 0) + { + m_iMusicDelay--; + return; + } + // Sanity check: if a stream is already active, something went wrong + if (m_musicStreamActive) + { + app.DebugPrintf("WARNING: m_musicStreamActive already true in Idle state, resetting to Playing\n"); + m_StreamState = eMusicStreamState_Playing; + return; + } -#if ( defined __PS3__ || defined __PSVITA__ || defined __ORBIS__ ) + // If no track ID is selected, fallback to a default (should not happen) + if (m_musicID == -1) + { + m_musicID = m_musicTrackManager.selectTrack(MusicTrackManager::Domain::Menu); + } + // Build the full file path for the selected music ID. + // This block handles platform-specific paths, DLC/mash-up packs, and CD music. + // The result is stored in m_szStreamName. + { + // Start with the base music path (platform‑dependent) +#if (defined __PS3__ || defined __PSVITA__ || defined __ORBIS__) #ifdef __PS3__ - // 4J-PB - Need to check if we are a patched BD build - if(app.GetBootedFromDiscPatch()) - { - sprintf(m_szStreamName,"%s/%s",app.GetBDUsrDirPath(m_szMusicPath), m_szMusicPath ); - app.DebugPrintf("SoundEngine::playMusicUpdate - (booted from disc patch) music path - %s",m_szStreamName); - } - else - { - sprintf(m_szStreamName,"%s/%s",getUsrDirPath(), m_szMusicPath ); - } + // PS3 special case: booted from disc patch vs. installed data + if (app.GetBootedFromDiscPatch()) + { + sprintf(m_szStreamName, "%s/%s", app.GetBDUsrDirPath(m_szMusicPath), m_szMusicPath); + app.DebugPrintf("SoundEngine::playMusicUpdate - (booted from disc patch) music path - %s", m_szStreamName); + } + else + { + sprintf(m_szStreamName, "%s/%s", getUsrDirPath(), m_szMusicPath); + } #else - sprintf(m_szStreamName,"%s/%s",getUsrDirPath(), m_szMusicPath ); + // Other consoles (Vita, Orbis) + sprintf(m_szStreamName, "%s/%s", getUsrDirPath(), m_szMusicPath); #endif - #else - strcpy((char *)m_szStreamName,m_szMusicPath); + // Windows / Durango – plain relative path + strcpy((char*)m_szStreamName, m_szMusicPath); #endif - // are we using a mash-up pack? - //if(pMinecraft && !pMinecraft->skins->isUsingDefaultSkin() && pMinecraft->skins->getSelected()->hasAudio()) - if(Minecraft::GetInstance()->skins->getSelected()->hasAudio()) - { - // It's a mash-up - need to use the DLC path for the music - TexturePack *pTexPack=Minecraft::GetInstance()->skins->getSelected(); - DLCTexturePack *pDLCTexPack=(DLCTexturePack *)pTexPack; - DLCPack *pack = pDLCTexPack->getDLCInfoParentPack(); - DLCAudioFile *dlcAudioFile = (DLCAudioFile *) pack->getFile(DLCManager::e_DLCType_Audio, 0); - - app.DebugPrintf("Mashup pack \n"); - // build the name + // Check if a mash‑up pack (DLC) is active and has custom audio + if (Minecraft::GetInstance()->skins->getSelected()->hasAudio()) + { + // Mash‑up pack: use DLC audio files + TexturePack* pTexPack = Minecraft::GetInstance()->skins->getSelected(); + DLCTexturePack* pDLCTexPack = (DLCTexturePack*)pTexPack; + DLCPack* pack = pDLCTexPack->getDLCInfoParentPack(); + DLCAudioFile* dlcAudioFile = (DLCAudioFile*)pack->getFile(DLCManager::e_DLCType_Audio, 0); + + app.DebugPrintf("Mashup pack\n"); + + // Determine whether this is game music (track index < first CD) or a CD + if (m_musicID < m_iStream_CD_1) + { + // Game music from the mash‑up pack + SetIsPlayingStreamingGameMusic(true); + SetIsPlayingStreamingCDMusic(false); + m_MusicType = eMusicType_Game; + m_StreamingAudioInfo.bIs3D = false; - // if the music ID is beyond the end of the texture pack music files, then it's a CD - if(m_musicIDGetSoundName(m_musicID); - wstring wstrFile=L"TPACK:\\Data\\" + wstrSoundName +L".wav"; - std::wstring mountedPath = StorageManager.GetMountedPath(wstrFile); - wcstombs(m_szStreamName,mountedPath.c_str(),255); + // Xbox One: use StorageManager to resolve TPACK path + wstring& wstrSoundName = dlcAudioFile->GetSoundName(m_musicID); + wstring wstrFile = L"TPACK:\\Data\\" + wstrSoundName + L".wav"; + std::wstring mountedPath = StorageManager.GetMountedPath(wstrFile); + wcstombs(m_szStreamName, mountedPath.c_str(), 255); #else - wstring &wstrSoundName=dlcAudioFile->GetSoundName(m_musicID); - char szName[255]; - wcstombs(szName,wstrSoundName.c_str(),255); + // Other platforms: convert wstring to char and build TPACK path + wstring& wstrSoundName = dlcAudioFile->GetSoundName(m_musicID); + char szName[255]; + wcstombs(szName, wstrSoundName.c_str(), 255); #if defined __PS3__ || defined __ORBIS__ || defined __PSVITA__ - string strFile="TPACK:/Data/" + string(szName) + ".wav"; + string strFile = "TPACK:/Data/" + string(szName) + ".wav"; #else - string strFile="TPACK:\\Data\\" + string(szName) + ".wav"; + string strFile = "TPACK:\\Data\\" + string(szName) + ".wav"; #endif - std::string mountedPath = StorageManager.GetMountedPath(strFile); - strcpy(m_szStreamName,mountedPath.c_str()); + std::string mountedPath = StorageManager.GetMountedPath(strFile); + strcpy(m_szStreamName, mountedPath.c_str()); #endif - } - else - { - SetIsPlayingStreamingGameMusic(false); - SetIsPlayingStreamingCDMusic(true); - m_MusicType=eMusicType_CD; - m_StreamingAudioInfo.bIs3D=true; - - // Need to adjust to index into the cds in the game's m_szStreamFileA - strcat((char *)m_szStreamName,"cds/"); - strcat((char *)m_szStreamName,m_szStreamFileA[m_musicID-m_iStream_CD_1+eStream_CD_1]); - strcat((char *)m_szStreamName,".wav"); - } - } - else - { - // 4J-PB - if this is a PS3 disc patch, we have to check if the music file is in the patch data + } + else + { + // CD track from the mash‑up pack + SetIsPlayingStreamingGameMusic(false); + SetIsPlayingStreamingCDMusic(true); + m_MusicType = eMusicType_CD; + m_StreamingAudioInfo.bIs3D = true; + + // Append "cds/" and the base filename (from the global stream file array) + strcat((char*)m_szStreamName, "cds/"); + strcat((char*)m_szStreamName, m_szStreamFileA[m_musicID - m_iStream_CD_1 + eStream_CD_1]); + strcat((char*)m_szStreamName, ".wav"); + } + } + else + { + // No mash‑up pack – use standard Minecraft music files #ifdef __PS3__ - if(app.GetBootedFromDiscPatch() && (m_musicID(m_szStreamName), "rb") == 0 && pFile) - { - fclose(pFile); - } - else - { - const char* extensions[] = { ".ogg", ".mp3", ".wav" }; - size_t extCount = sizeof(extensions) / sizeof(extensions[0]); - bool found = false; - - char* dotPos = strrchr(reinterpret_cast(m_szStreamName), '.'); - if (dotPos != nullptr && (dotPos - reinterpret_cast(m_szStreamName)) < 250) - { - for (size_t i = 0; i < extCount; i++) - { - strcpy_s(dotPos, 5, extensions[i]); - - if (fopen_s(&pFile, reinterpret_cast(m_szStreamName), "rb") == 0 && pFile) - { - fclose(pFile); - found = true; - break; - } - } - } - - if (!found) - { - if (dotPos != nullptr) - { - strcpy_s(dotPos, 5, ".wav"); - } - app.DebugPrintf("WARNING: No audio file found for music ID %d (tried .ogg, .mp3, .wav)\n", m_musicID); - return; - } - } + } - app.DebugPrintf("Starting streaming - %s\n",m_szStreamName); - m_openStreamThread = new C4JThread(OpenStreamThreadProc, this, "OpenStreamThreadProc"); - m_openStreamThread->Run(); - m_StreamState = eMusicStreamState_Opening; - } - break; + // Verify that the file exists; if not, try alternative extensions (.ogg, .mp3) + FILE* pFile = nullptr; + if (fopen_s(&pFile, reinterpret_cast(m_szStreamName), "rb") == 0 && pFile) + { + fclose(pFile); + } + else + { + // File not found – try changing the extension + const char* extensions[] = { ".ogg", ".mp3", ".wav" }; + size_t extCount = sizeof(extensions) / sizeof(extensions[0]); + bool found = false; + + // Find the position of the current extension (assumed to be ".wav") + char* dotPos = strrchr(reinterpret_cast(m_szStreamName), '.'); + if (dotPos != nullptr && (dotPos - reinterpret_cast(m_szStreamName)) < 250) + { + for (size_t i = 0; i < extCount; ++i) + { + strcpy_s(dotPos, 5, extensions[i]); // Replace extension + if (fopen_s(&pFile, reinterpret_cast(m_szStreamName), "rb") == 0 && pFile) + { + fclose(pFile); + found = true; + break; + } + } + } + + if (!found) + { + // Restore original extension for error message + if (dotPos != nullptr) + strcpy_s(dotPos, 5, ".wav"); + app.DebugPrintf("WARNING: No audio file found for music ID %d (tried .ogg, .mp3, .wav)\n", m_musicID); + return; // Abort – stay in Idle + } + } + } // end file path construction - case eMusicStreamState_Opening: - if( !m_openStreamThread->isRunning() ) - { - delete m_openStreamThread; - m_openStreamThread = nullptr; + app.DebugPrintf("Starting streaming - %s\n", m_szStreamName); - app.DebugPrintf("OpenStreamThreadProc finished. m_musicStreamActive=%d\n", m_musicStreamActive); + // Launch a thread to open the audio file (prevents blocking the main thread) + m_openStreamThread = new C4JThread(OpenStreamThreadProc, this, "OpenStreamThreadProc"); + m_openStreamThread->Run(); + m_StreamState = eMusicStreamState_Opening; + break; + } - if (!m_musicStreamActive) - { - const char* currentExt = strrchr(reinterpret_cast(m_szStreamName), '.'); - if (currentExt && _stricmp(currentExt, ".wav") == 0) - { - const bool isCD = (m_musicID >= m_iStream_CD_1); - const char* folder = isCD ? "cds/" : "music/"; - - int n = sprintf_s(reinterpret_cast(m_szStreamName), 512, "%s%s%s.wav", m_szMusicPath, folder, m_szStreamFileA[m_musicID]); - - if (n > 0) - { - FILE* pFile = nullptr; - if (fopen_s(&pFile, reinterpret_cast(m_szStreamName), "rb") == 0 && pFile) - { - fclose(pFile); - - m_openStreamThread = new C4JThread(OpenStreamThreadProc, this, "OpenStreamThreadProc"); - m_openStreamThread->Run(); - break; - } - } - } - - m_StreamState = eMusicStreamState_Idle; - break; - } - - if (m_StreamingAudioInfo.bIs3D) - { - ma_sound_set_spatialization_enabled(&m_musicStream, MA_TRUE); - ma_sound_set_position(&m_musicStream, m_StreamingAudioInfo.x, m_StreamingAudioInfo.y, m_StreamingAudioInfo.z); - } - else - { - ma_sound_set_spatialization_enabled(&m_musicStream, MA_FALSE); - } + //--------------------------------------------------------------------- + // OPENING – waiting for the background thread to finish opening the file + //--------------------------------------------------------------------- + case eMusicStreamState_Opening: + { + if (!m_openStreamThread->isRunning()) + { + // Thread finished + delete m_openStreamThread; + m_openStreamThread = nullptr; - ma_sound_set_pitch(&m_musicStream, m_StreamingAudioInfo.pitch); + app.DebugPrintf("OpenStreamThreadProc finished. m_musicStreamActive=%d\n", m_musicStreamActive); - float finalVolume = m_StreamingAudioInfo.volume * getMasterMusicVolume(); + // If the stream failed to open, try a fallback (e.g., if we used a numbered variant) + if (!m_musicStreamActive) + { + const char* currentExt = strrchr(reinterpret_cast(m_szStreamName), '.'); + if (currentExt && _stricmp(currentExt, ".wav") == 0) + { + // Attempt to rebuild the path using the base filename (without number) + const bool isCD = (m_musicID >= m_iStream_CD_1); + const char* folder = isCD ? "cds/" : "music/"; + int n = sprintf_s(reinterpret_cast(m_szStreamName), 512, "%s%s%s.wav", + m_szMusicPath, folder, m_szStreamFileA[m_musicID]); + if (n > 0) + { + FILE* pFile = nullptr; + if (fopen_s(&pFile, reinterpret_cast(m_szStreamName), "rb") == 0 && pFile) + { + fclose(pFile); + // Retry opening with the new path + m_openStreamThread = new C4JThread(OpenStreamThreadProc, this, "OpenStreamThreadProc"); + m_openStreamThread->Run(); + break; // stay in Opening + } + } + } + // No fallback worked – go back to Idle + m_StreamState = eMusicStreamState_Idle; + break; + } - ma_sound_set_volume(&m_musicStream, finalVolume); - ma_result startResult = ma_sound_start(&m_musicStream); - app.DebugPrintf("ma_sound_start result: %d\n", startResult); + // Stream opened successfully; configure spatialization, pitch, volume + if (m_StreamingAudioInfo.bIs3D) + { + ma_sound_set_spatialization_enabled(&m_musicStream, MA_TRUE); + ma_sound_set_position(&m_musicStream, + m_StreamingAudioInfo.x, + m_StreamingAudioInfo.y, + m_StreamingAudioInfo.z); + } + else + { + ma_sound_set_spatialization_enabled(&m_musicStream, MA_FALSE); + } - m_StreamState=eMusicStreamState_Playing; - } - break; - case eMusicStreamState_OpeningCancel: - if( !m_openStreamThread->isRunning() ) - { - delete m_openStreamThread; - m_openStreamThread = nullptr; - m_StreamState = eMusicStreamState_Stop; - } - break; - case eMusicStreamState_Stop: - if (m_musicStreamActive) - { - ma_sound_stop(&m_musicStream); - ma_sound_uninit(&m_musicStream); - m_musicStreamActive = false; - } + ma_sound_set_pitch(&m_musicStream, m_StreamingAudioInfo.pitch); - SetIsPlayingStreamingCDMusic(false); - SetIsPlayingStreamingGameMusic(false); + float finalVolume = m_StreamingAudioInfo.volume * masterVolume; + ma_sound_set_volume(&m_musicStream, finalVolume); - m_StreamState = eMusicStreamState_Idle; - break; - case eMusicStreamState_Stopping: - break; - case eMusicStreamState_Play: - break; - case eMusicStreamState_Playing: - { - static int frameCount = 0; - if (frameCount++ % 60 == 0) - { - if (m_musicStreamActive) - { - bool isPlaying = ma_sound_is_playing(&m_musicStream); - float vol = ma_sound_get_volume(&m_musicStream); - bool isAtEnd = ma_sound_at_end(&m_musicStream); - } - } - } - if(GetIsPlayingStreamingGameMusic()) - { - //if(m_MusicInfo.pCue!=nullptr) - { - bool playerInEnd = false; - bool playerInNether=false; - Minecraft *pMinecraft = Minecraft::GetInstance(); - for(unsigned int i = 0; i < MAX_LOCAL_PLAYERS; ++i) - { - if(pMinecraft->localplayers[i]!=nullptr) - { - if(pMinecraft->localplayers[i]->dimension==LevelData::DIMENSION_END) - { - playerInEnd=true; - } - else if(pMinecraft->localplayers[i]->dimension==LevelData::DIMENSION_NETHER) - { - playerInNether=true; - } - } - } + // Start playback + ma_result startResult = ma_sound_start(&m_musicStream); + app.DebugPrintf("ma_sound_start result: %d\n", startResult); - if(playerInEnd && !GetIsPlayingEndMusic()) - { - m_StreamState=eMusicStreamState_Stop; + m_StreamState = eMusicStreamState_Playing; + } + break; + } - // Set the end track - m_musicID = getMusicID(LevelData::DIMENSION_END); - SetIsPlayingEndMusic(true); - SetIsPlayingNetherMusic(false); - } - else if(!playerInEnd && GetIsPlayingEndMusic()) - { - if(playerInNether) - { - m_StreamState=eMusicStreamState_Stop; + //--------------------------------------------------------------------- + // OPENINGCANCEL – user requested stop while opening + //--------------------------------------------------------------------- + case eMusicStreamState_OpeningCancel: + { + if (!m_openStreamThread->isRunning()) + { + delete m_openStreamThread; + m_openStreamThread = nullptr; + m_StreamState = eMusicStreamState_Stop; + } + break; + } - // Set the end track - m_musicID = getMusicID(LevelData::DIMENSION_NETHER); - SetIsPlayingEndMusic(false); - SetIsPlayingNetherMusic(true); - } - else - { - m_StreamState=eMusicStreamState_Stop; + //--------------------------------------------------------------------- + // STOP – actively stop the current stream + //--------------------------------------------------------------------- + case eMusicStreamState_Stop: + { + if (m_musicStreamActive) + { + ma_sound_stop(&m_musicStream); + ma_sound_uninit(&m_musicStream); + m_musicStreamActive = false; + } - // Set the end track - m_musicID = getMusicID(LevelData::DIMENSION_OVERWORLD); - SetIsPlayingEndMusic(false); - SetIsPlayingNetherMusic(false); - } - } - else if (playerInNether && !GetIsPlayingNetherMusic()) - { - m_StreamState=eMusicStreamState_Stop; - // set the Nether track - m_musicID = getMusicID(LevelData::DIMENSION_NETHER); - SetIsPlayingNetherMusic(true); - SetIsPlayingEndMusic(false); - } - else if(!playerInNether && GetIsPlayingNetherMusic()) - { - if(playerInEnd) - { - m_StreamState=eMusicStreamState_Stop; - // set the Nether track - m_musicID = getMusicID(LevelData::DIMENSION_END); - SetIsPlayingNetherMusic(false); - SetIsPlayingEndMusic(true); - } - else - { - m_StreamState=eMusicStreamState_Stop; - // set the Nether track - m_musicID = getMusicID(LevelData::DIMENSION_OVERWORLD); - SetIsPlayingNetherMusic(false); - SetIsPlayingEndMusic(false); - } - } + // Clear flags indicating what type of music was playing + SetIsPlayingStreamingCDMusic(false); + SetIsPlayingStreamingGameMusic(false); - // volume change required? - if (m_musicStreamActive) - { - float finalVolume = m_StreamingAudioInfo.volume * fMusicVol; + m_StreamState = eMusicStreamState_Idle; + break; + } - ma_sound_set_volume(&m_musicStream, finalVolume); - } - } - } - else - { - // Music disc playing - if it's a 3D stream, then set the position - we don't have any streaming audio in the world that moves, so this isn't - // required unless we have more than one listener, and are setting the listening position to the origin and setting a fake position - // for the sound down the z axis - if (m_StreamingAudioInfo.bIs3D && m_validListenerCount > 1) - { - int iClosestListener = 0; - float fClosestDist = 1e6f; + //--------------------------------------------------------------------- + // PLAYING – the stream is actively playing + //--------------------------------------------------------------------- + case eMusicStreamState_Playing: + { + // Optional periodic debug logging (once per second at 60 fps) + static int frameCount = 0; + if (frameCount++ % 60 == 0) + { + if (m_musicStreamActive) + { + bool isPlaying = ma_sound_is_playing(&m_musicStream); + float vol = ma_sound_get_volume(&m_musicStream); + bool isAtEnd = ma_sound_at_end(&m_musicStream); + // (debug info could be printed here if needed) + } + } - for (size_t i = 0; i < MAX_LOCAL_PLAYERS; i++) - { - if (m_ListenerA[i].bValid) - { - float dx = m_StreamingAudioInfo.x - m_ListenerA[i].vPosition.x; - float dy = m_StreamingAudioInfo.y - m_ListenerA[i].vPosition.y; - float dz = m_StreamingAudioInfo.z - m_ListenerA[i].vPosition.z; - float dist = sqrtf(dx*dx + dy*dy + dz*dz); + // Separate handling for game music (background) and CD music (jukebox) + if (GetIsPlayingStreamingGameMusic()) + { + // Background music: check if the musical context has changed + MusicTrackManager::Domain currentDomain = determineCurrentMusicDomain(); - if (dist < fClosestDist) - { - fClosestDist = dist; - iClosestListener = i; - } - } - } + // If the domain changed, we need to stop the current track + // so that a new one (appropriate for the new domain) will start. + if (currentDomain != m_currentMusicDomain) + { + m_StreamState = eMusicStreamState_Stop; + // Store the new domain for when we restart + m_currentMusicDomain = currentDomain; + m_musicID = getTrackForDomain(currentDomain); + break; + } - float relX = m_StreamingAudioInfo.x - m_ListenerA[iClosestListener].vPosition.x; - float relY = m_StreamingAudioInfo.y - m_ListenerA[iClosestListener].vPosition.y; - float relZ = m_StreamingAudioInfo.z - m_ListenerA[iClosestListener].vPosition.z; + // Update volume if master volume changed + if (m_musicStreamActive) + { + float finalVolume = m_StreamingAudioInfo.volume * masterVolume; + ma_sound_set_volume(&m_musicStream, finalVolume); + } + } + else + { + // Music disc playing (jukebox) + if (m_StreamingAudioInfo.bIs3D && m_validListenerCount > 1) + { + // For split‑screen, we need to position the sound relative to the closest listener. + // (The engine's listener is set to the origin; we simulate distance by moving the sound.) + int iClosestListener = 0; + float fClosestDist = 1e6f; + + for (size_t i = 0; i < MAX_LOCAL_PLAYERS; ++i) + { + if (m_ListenerA[i].bValid) + { + float dx = m_StreamingAudioInfo.x - m_ListenerA[i].vPosition.x; + float dy = m_StreamingAudioInfo.y - m_ListenerA[i].vPosition.y; + float dz = m_StreamingAudioInfo.z - m_ListenerA[i].vPosition.z; + float dist = sqrtf(dx * dx + dy * dy + dz * dz); + + if (dist < fClosestDist) + { + fClosestDist = dist; + iClosestListener = i; + } + } + } + + // Compute sound position relative to that listener + float relX = m_StreamingAudioInfo.x - m_ListenerA[iClosestListener].vPosition.x; + float relY = m_StreamingAudioInfo.y - m_ListenerA[iClosestListener].vPosition.y; + float relZ = m_StreamingAudioInfo.z - m_ListenerA[iClosestListener].vPosition.z; + + if (m_musicStreamActive) + { + ma_sound_set_position(&m_musicStream, relX, relY, relZ); + } + } + } + break; + } - if (m_musicStreamActive) - { - ma_sound_set_position(&m_musicStream, relX, relY, relZ); - } - } - } + //--------------------------------------------------------------------- + // COMPLETED – the current track reached its end naturally + //--------------------------------------------------------------------- + case eMusicStreamState_Completed: + { + // Set a random delay (0–3 minutes) before playing the next track + m_iMusicDelay = random->nextInt(20 * 60 * 3); - break; + // Determine the current musical context + m_currentMusicDomain = determineCurrentMusicDomain(); - case eMusicStreamState_Completed: - { - // random delay of up to 3 minutes for music - m_iMusicDelay = random->nextInt(20 * 60 * 3);//random->nextInt(20 * 60 * 10) + 20 * 60 * 10; - // Check if we have a local player in The Nether or in The End, and play that music if they are - Minecraft *pMinecraft=Minecraft::GetInstance(); - bool playerInEnd=false; - bool playerInNether=false; + // Select a new track ID appropriate for that domain + m_musicID = getTrackForDomain(m_currentMusicDomain); - for(unsigned int i=0;ilocalplayers[i]!=nullptr) - { - if(pMinecraft->localplayers[i]->dimension==LevelData::DIMENSION_END) - { - playerInEnd=true; - } - else if(pMinecraft->localplayers[i]->dimension==LevelData::DIMENSION_NETHER) - { - playerInNether=true; - } - } - } - if(playerInEnd) - { - m_musicID = getMusicID(LevelData::DIMENSION_END); - SetIsPlayingEndMusic(true); - SetIsPlayingNetherMusic(false); - } - else if(playerInNether) - { - m_musicID = getMusicID(LevelData::DIMENSION_NETHER); - SetIsPlayingNetherMusic(true); - SetIsPlayingEndMusic(false); - } - else - { - m_musicID = getMusicID(LevelData::DIMENSION_OVERWORLD); - SetIsPlayingNetherMusic(false); - SetIsPlayingEndMusic(false); - } + // Update the flags that track which domain is active (for compatibility) + // These are used elsewhere; we keep them in sync. + SetIsPlayingMenuMusic(m_currentMusicDomain == MusicTrackManager::Domain::Menu); + SetIsPlayingEndMusic(m_currentMusicDomain == MusicTrackManager::Domain::End); + SetIsPlayingNetherMusic(m_currentMusicDomain == MusicTrackManager::Domain::Nether); - m_StreamState=eMusicStreamState_Idle; - } - break; - } + m_StreamState = eMusicStreamState_Idle; + break; + } - // check the status of the stream - this is for when a track completes rather than is stopped by the user action + //--------------------------------------------------------------------- + // States that require no action (STOPPING, PLAY – unused) + //--------------------------------------------------------------------- + case eMusicStreamState_Stopping: + case eMusicStreamState_Play: + break; +} - if (m_musicStreamActive) - { - if (!ma_sound_is_playing(&m_musicStream) && ma_sound_at_end(&m_musicStream)) - { - ma_sound_uninit(&m_musicStream); - m_musicStreamActive = false; + //------------------------------------------------------------------------- + // End-of‑stream detection (common to all states) + // If the stream is active but has finished playing, transition to Completed. + //------------------------------------------------------------------------- + if (m_musicStreamActive) + { + if (!ma_sound_is_playing(&m_musicStream) && ma_sound_at_end(&m_musicStream)) + { + ma_sound_uninit(&m_musicStream); + m_musicStreamActive = false; - SetIsPlayingStreamingCDMusic(false); - SetIsPlayingStreamingGameMusic(false); + SetIsPlayingStreamingCDMusic(false); + SetIsPlayingStreamingGameMusic(false); - m_StreamState = eMusicStreamState_Completed; - } - } + m_StreamState = eMusicStreamState_Completed; + } + } } diff --git a/Minecraft.Client/Common/Audio/SoundEngine.h b/Minecraft.Client/Common/Audio/SoundEngine.h index 2134c491c..3d382362e 100644 --- a/Minecraft.Client/Common/Audio/SoundEngine.h +++ b/Minecraft.Client/Common/Audio/SoundEngine.h @@ -3,6 +3,7 @@ class Mob; class Options; using namespace std; #include "..\..\Minecraft.World\SoundTypes.h" +#include "MusicTrackManager.h" #include "miniaudio.h" @@ -17,6 +18,10 @@ enum eMUSICFILES eStream_Overworld_hal4, eStream_Overworld_nuance1, eStream_Overworld_nuance2, + eStream_Overworld_piano1, + eStream_Overworld_piano2, + eStream_Overworld_piano3, // <-- make piano3 the last overworld one + #ifndef _XBOX // Add the new music tracks eStream_Overworld_Creative1, @@ -25,14 +30,12 @@ enum eMUSICFILES eStream_Overworld_Creative4, eStream_Overworld_Creative5, eStream_Overworld_Creative6, + eStream_Overworld_Menu1, eStream_Overworld_Menu2, eStream_Overworld_Menu3, eStream_Overworld_Menu4, #endif - eStream_Overworld_piano1, - eStream_Overworld_piano2, - eStream_Overworld_piano3, // <-- make piano3 the last overworld one // Nether eStream_Nether1, eStream_Nether2, @@ -106,6 +109,7 @@ extern std::vector m_activeSounds; class SoundEngine : public ConsoleSoundEngine { static const int MAX_SAME_SOUNDS_PLAYING = 8; // 4J added + public: SoundEngine(); void destroy() override; @@ -125,10 +129,11 @@ class SoundEngine : public ConsoleSoundEngine void addMusic(const wstring& name, File *file) override; void addStreaming(const wstring& name, File *file) override; char *ConvertSoundPathToName(const wstring& name, bool bConvertSpaces=false) override; - bool isStreamingWavebankReady(); // 4J Added - int getMusicID(int iDomain); + bool isStreamingWavebankReady(); + MusicTrackManager::Domain determineCurrentMusicDomain() const; + int getTrackForDomain(MusicTrackManager::Domain domain); int getMusicID(const wstring& name); - void SetStreamingSounds(int iOverworldMin, int iOverWorldMax, int iNetherMin, int iNetherMax, int iEndMin, int iEndMax, int iCD1); + void SetStreamingSounds(int iMenuMin, int iMenuMax, int iOverworldSurvivalMin, int iOverWorldSurvivalMax, int iOverworldCreativeMin, int iOverWorldCreativeMax, int iNetherMin, int iNetherMax, int iEndMin, int iEndMax, int iCD1); void updateMiniAudio(); void playMusicUpdate(); @@ -141,8 +146,10 @@ class SoundEngine : public ConsoleSoundEngine int initAudioHardware(int iMinSpeakers) override { return iMinSpeakers;} #endif - - int GetRandomishTrack(int iStart,int iEnd); + + Random* random; + MusicTrackManager m_musicTrackManager; + MusicTrackManager::Domain m_currentMusicDomain; ma_engine m_engine; ma_engine_config m_engineConfig; @@ -157,8 +164,6 @@ class SoundEngine : public ConsoleSoundEngine AUDIO_LISTENER m_ListenerA[MAX_LOCAL_PLAYERS]; int m_validListenerCount; - - Random *random; int m_musicID; int m_iMusicDelay; int m_StreamState; @@ -174,12 +179,7 @@ class SoundEngine : public ConsoleSoundEngine char m_szStreamName[255]; int CurrentSoundsPlaying[eSoundType_MAX+eSFX_MAX]; - // streaming music files - will be different for mash-up packs - int m_iStream_Overworld_Min,m_iStream_Overworld_Max; - int m_iStream_Nether_Min,m_iStream_Nether_Max; - int m_iStream_End_Min,m_iStream_End_Max; int m_iStream_CD_1; - bool *m_bHeardTrackA; #ifdef __ORBIS__ int32_t m_hBGMAudio; diff --git a/Minecraft.Client/Common/Consoles_App.cpp b/Minecraft.Client/Common/Consoles_App.cpp index c3a623d5f..204ded30b 100644 --- a/Minecraft.Client/Common/Consoles_App.cpp +++ b/Minecraft.Client/Common/Consoles_App.cpp @@ -3754,10 +3754,12 @@ void CMinecraftApp::HandleXuiActions(void) // need to stop the streaming audio - by playing streaming audio from the default texture pack now // reset the streaming sounds back to the normal ones #ifndef _XBOX - pMinecraft->soundEngine->SetStreamingSounds(eStream_Overworld_Calm1,eStream_Overworld_piano3, - eStream_Nether1,eStream_Nether4, - eStream_end_dragon,eStream_end_end, - eStream_CD_1); + pMinecraft->soundEngine->SetStreamingSounds(eStream_Overworld_Menu1, eStream_Overworld_Menu4, + eStream_Overworld_Calm1, eStream_Overworld_piano3, + eStream_Overworld_Creative1, eStream_Overworld_Creative6, + eStream_Nether1, eStream_Nether4, + eStream_end_dragon, eStream_end_end, + eStream_CD_1); #endif pMinecraft->soundEngine->playStreaming(L"", 0, 0, 0, 1, 1); diff --git a/Minecraft.Client/Common/UI/UIController.cpp b/Minecraft.Client/Common/UI/UIController.cpp index b12ea5e73..4d4242493 100644 --- a/Minecraft.Client/Common/UI/UIController.cpp +++ b/Minecraft.Client/Common/UI/UIController.cpp @@ -2064,10 +2064,12 @@ void UIController::NavigateToHomeMenu() { // need to stop the streaming audio - by playing streaming audio from the default texture pack now // reset the streaming sounds back to the normal ones - pMinecraft->soundEngine->SetStreamingSounds(eStream_Overworld_Calm1,eStream_Overworld_piano3, - eStream_Nether1,eStream_Nether4, - eStream_end_dragon,eStream_end_end, - eStream_CD_1); + pMinecraft->soundEngine->SetStreamingSounds(eStream_Overworld_Menu1, eStream_Overworld_Menu4, + eStream_Overworld_Calm1, eStream_Overworld_piano3, + eStream_Overworld_Creative1, eStream_Overworld_Creative6, + eStream_Nether1, eStream_Nether4, + eStream_end_dragon, eStream_end_end, + eStream_CD_1); pMinecraft->soundEngine->playStreaming(L"", 0, 0, 0, 1, 1); // if(pDLCTexPack->m_pStreamedWaveBank!=nullptr) diff --git a/Minecraft.Client/DLCTexturePack.cpp b/Minecraft.Client/DLCTexturePack.cpp index f1304a9ef..69c75d632 100644 --- a/Minecraft.Client/DLCTexturePack.cpp +++ b/Minecraft.Client/DLCTexturePack.cpp @@ -483,7 +483,7 @@ int DLCTexturePack::packMounted(LPVOID pParam,int iPad,DWORD dwErr,DWORD dwLicen iEndStart=iOverworldC+iNetherC; iEndC=dlcFile->GetCountofType(DLCAudioFile::e_AudioType_End); - Minecraft::GetInstance()->soundEngine->SetStreamingSounds(iOverworldStart,iOverworldStart+iOverworldC-1, + Minecraft::GetInstance()->soundEngine->SetStreamingSounds(iOverworldStart,iOverworldStart+iOverworldC-1, iOverworldStart, iOverworldStart + iOverworldC - 1, iOverworldStart, iOverworldStart + iOverworldC - 1, iNetherStart,iNetherStart+iNetherC-1,iEndStart,iEndStart+iEndC-1,iEndStart+iEndC); // push the CD start to after } #endif diff --git a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp index 70aeb22bf..3ab47affc 100644 --- a/Minecraft.Client/Windows64/Windows64_Minecraft.cpp +++ b/Minecraft.Client/Windows64/Windows64_Minecraft.cpp @@ -1806,23 +1806,16 @@ int APIENTRY _tWinMain(_In_ HINSTANCE hInstance, InputManager.Tick(); // Detect KBM vs controller input mode - if (InputManager.IsPadConnected(0)) - { - const bool controllerUsed = InputManager.ButtonPressed(0) || - InputManager.GetJoypadStick_LX(0, false) != 0.0f || - InputManager.GetJoypadStick_LY(0, false) != 0.0f || - InputManager.GetJoypadStick_RX(0, false) != 0.0f || - InputManager.GetJoypadStick_RY(0, false) != 0.0f; - - if (controllerUsed) - g_KBMInput.SetKBMActive(false); - else if (g_KBMInput.HasAnyInput()) - g_KBMInput.SetKBMActive(true); - } - else - { + const bool controllerUsed = InputManager.ButtonPressed(0) || + InputManager.GetJoypadStick_LX(0, false) != 0.0f || + InputManager.GetJoypadStick_LY(0, false) != 0.0f || + InputManager.GetJoypadStick_RX(0, false) != 0.0f || + InputManager.GetJoypadStick_RY(0, false) != 0.0f; + + if (controllerUsed) + g_KBMInput.SetKBMActive(false); + else if (g_KBMInput.HasAnyInput()) g_KBMInput.SetKBMActive(true); - } if (!g_KBMInput.IsMouseGrabbed()) {