#include "stdafx.h" #include "..\..\..\Minecraft.World\net.minecraft.world.level.h" #include "..\..\DLCTexturePack.h" #include "..\..\Minecraft.World\leveldata.h" #include "..\..\Minecraft.World\mth.h" #include "..\..\MultiplayerLocalPlayer.h" #include "..\..\TexturePackRepository.h" #include "..\Consoles_App.h" #include "Common\DLC\DLCAudioFile.h" #include "SoundEngine.h" #ifdef __PSVITA__ #include #endif #ifdef _WINDOWS64 #include "..\..\Minecraft.Client\Windows64\Windows64_App.h" #include "third_party\openAL\include\AL\al.h" #include "third_party\openAL\include\AL\alc.h" #include "third_party\openAL\include\AL\alext.h" #include "third_party/stb_vorbis.c" #include #include #include #include #include #endif #ifdef __ORBIS__ #include // #define __DISABLE_MILES__ // MGH disabled for now as it // crashes if we call sceNpMatching2Initialize #endif // take out Orbis until they are done #if defined _XBOX SoundEngine::SoundEngine() {} void SoundEngine::init(Options *pOptions) {} void SoundEngine::tick(shared_ptr *players, float a) {} void SoundEngine::destroy() {} void SoundEngine::play(int iSound, float x, float y, float z, float volume, float pitch) { app.DebugPrintf("PlaySound - %d\n", iSound); } void SoundEngine::playStreaming(const wstring &name, float x, float y, float z, float volume, float pitch, bool bMusicDelay) {} void SoundEngine::playUI(int iSound, float volume, float pitch) {} void SoundEngine::updateMusicVolume(float fVal) {} void SoundEngine::updateSoundEffectVolume(float fVal) {} void SoundEngine::add(const wstring &name, File *file) {} void SoundEngine::addMusic(const wstring &name, File *file) {} void SoundEngine::addStreaming(const wstring &name, File *file) {} char *SoundEngine::ConvertSoundPathToName(const wstring &name, bool bConvertSpaces) { return NULL; } bool SoundEngine::isStreamingWavebankReady() { return true; } void SoundEngine::playMusicTick() {}; #else #ifdef _WINDOWS64 char SoundEngine::m_szSoundPath[] = {"sources\\sounds\\Durango\\Sound\\"}; char SoundEngine::m_szMusicPath[] = {"sources\\sounds\\music\\"}; char SoundEngine::m_szRedistName[] = {"redist64"}; #elif defined _DURANGO char SoundEngine::m_szSoundPath[] = {"Sound\\"}; char SoundEngine::m_szMusicPath[] = {"music\\"}; char SoundEngine::m_szRedistName[] = {"redist64"}; #elif defined __ORBIS__ #ifdef _CONTENT_PACKAGE char SoundEngine::m_szSoundPath[] = {"Sound/"}; #elif defined _ART_BUILD char SoundEngine::m_szSoundPath[] = {"Sound/"}; #else // just use the host Durango folder for the sound. In the content package, we'll // have moved this in the .gp4 file char SoundEngine::m_szSoundPath[] = {"Durango/Sound/"}; #endif char SoundEngine::m_szMusicPath[] = {"music/"}; char SoundEngine::m_szRedistName[] = {"redist64"}; #elif defined __PSVITA__ char SoundEngine::m_szSoundPath[] = {"PSVita/Sound/"}; char SoundEngine::m_szMusicPath[] = {"music/"}; char SoundEngine::m_szRedistName[] = {"redist"}; #elif defined __PS3__ // extern const char* getPS3HomePath(); char SoundEngine::m_szSoundPath[] = {"PS3/Sound/"}; char SoundEngine::m_szMusicPath[] = {"music/"}; char SoundEngine::m_szRedistName[] = {"redist"}; #define USE_SPURS #ifdef USE_SPURS #include #else #include #endif #endif #ifndef _WINDOWS64 F32 AILCALLBACK custom_falloff_function(HSAMPLE S, F32 distance, F32 rolloff_factor, F32 min_dist, F32 max_dist); #endif #ifdef _WINDOWS64 struct OpenALApi { LPALGENBUFFERS alGenBuffers; LPALDELETEBUFFERS alDeleteBuffers; LPALBUFFERDATA alBufferData; LPALGENSOURCES alGenSources; LPALDELETESOURCES alDeleteSources; LPALSOURCEI alSourcei; LPALSOURCEF alSourcef; LPALSOURCE3F alSource3f; LPALSOURCEPLAY alSourcePlay; LPALSOURCEPAUSE alSourcePause; LPALSOURCESTOP alSourceStop; LPALGETSOURCEI alGetSourcei; LPALLISTENER3F alListener3f; LPALLISTENERFV alListenerfv; LPALDISTANCEMODEL alDistanceModel; LPALGETERROR alGetError; LPALCOPENDEVICE alcOpenDevice; LPALCCLOSEDEVICE alcCloseDevice; LPALCCREATECONTEXT alcCreateContext; LPALCDESTROYCONTEXT alcDestroyContext; LPALCMAKECONTEXTCURRENT alcMakeContextCurrent; LPALCGETERROR alcGetError; LPALCGETSTRING alcGetString; }; struct OpenALState { bool loaded; bool ready; bool moduleOwned; HMODULE module; ALCdevice *device; ALCcontext *context; ALuint source; ALuint buffer; bool streamLoaded; int streamChannels; int streamSampleRate; std::vector streamPCM; std::vector activeSources; std::map cachedBuffers; }; static OpenALApi g_OpenALApi; static OpenALState g_OpenALState; template static bool resolveProc(HMODULE module, const char *name, T &proc) { proc = reinterpret_cast(GetProcAddress(module, name)); return proc != NULL; } static void resetStreamData() { g_OpenALState.streamLoaded = false; g_OpenALState.streamChannels = 0; g_OpenALState.streamSampleRate = 0; g_OpenALState.streamPCM.clear(); } static void cleanupSources() { for (size_t i = 0; i < g_OpenALState.activeSources.size();) { ALuint source = g_OpenALState.activeSources[i]; ALint state = AL_STOPPED; g_OpenALApi.alGetSourcei(source, AL_SOURCE_STATE, &state); if (state == AL_STOPPED || state == AL_INITIAL) { g_OpenALApi.alDeleteSources(1, &source); g_OpenALState.activeSources.erase(g_OpenALState.activeSources.begin() + i); } else { ++i; } } } // terrible static bool loadApi() { if (g_OpenALState.loaded) { return g_OpenALState.module != NULL; } memset(&g_OpenALApi, 0, sizeof(g_OpenALApi)); g_OpenALState.loaded = true; g_OpenALState.ready = false; g_OpenALState.moduleOwned = false; g_OpenALState.module = NULL; g_OpenALState.device = NULL; g_OpenALState.context = NULL; g_OpenALState.source = 0; g_OpenALState.buffer = 0; resetStreamData(); g_OpenALState.activeSources.clear(); g_OpenALState.cachedBuffers.clear(); HMODULE module = GetModuleHandleA("soft_oal.dll"); if (module == NULL) { module = LoadLibraryA("soft_oal.dll"); if (module) { g_OpenALState.moduleOwned = true; } } if (module == NULL) { return false; } g_OpenALState.module = module; bool resolved = resolveProc(module, "alGenBuffers", g_OpenALApi.alGenBuffers) && resolveProc(module, "alDeleteBuffers", g_OpenALApi.alDeleteBuffers) && resolveProc(module, "alBufferData", g_OpenALApi.alBufferData) && resolveProc(module, "alGenSources", g_OpenALApi.alGenSources) && resolveProc(module, "alDeleteSources", g_OpenALApi.alDeleteSources) && resolveProc(module, "alSourcei", g_OpenALApi.alSourcei) && resolveProc(module, "alSourcef", g_OpenALApi.alSourcef) && resolveProc(module, "alSource3f", g_OpenALApi.alSource3f) && resolveProc(module, "alSourcePlay", g_OpenALApi.alSourcePlay) && resolveProc(module, "alSourcePause", g_OpenALApi.alSourcePause) && resolveProc(module, "alSourceStop", g_OpenALApi.alSourceStop) && resolveProc(module, "alGetSourcei", g_OpenALApi.alGetSourcei) && resolveProc(module, "alListener3f", g_OpenALApi.alListener3f) && resolveProc(module, "alListenerfv", g_OpenALApi.alListenerfv) && resolveProc(module, "alDistanceModel", g_OpenALApi.alDistanceModel) && resolveProc(module, "alGetError", g_OpenALApi.alGetError) && resolveProc(module, "alcOpenDevice", g_OpenALApi.alcOpenDevice) && resolveProc(module, "alcCloseDevice", g_OpenALApi.alcCloseDevice) && resolveProc(module, "alcCreateContext", g_OpenALApi.alcCreateContext) && resolveProc(module, "alcDestroyContext", g_OpenALApi.alcDestroyContext) && resolveProc(module, "alcMakeContextCurrent", g_OpenALApi.alcMakeContextCurrent) && resolveProc(module, "alcGetError", g_OpenALApi.alcGetError) && resolveProc(module, "alcGetString", g_OpenALApi.alcGetString); if (!resolved) { return false; } return true; } static ALCdevice *tryDevice(const ALCchar *deviceName) { ALCdevice *device = g_OpenALApi.alcOpenDevice(deviceName); return device; } static bool initAudio() { if (g_OpenALState.ready) { return true; } if (!loadApi()) { return false; } g_OpenALState.device = tryDevice(NULL); if (g_OpenALState.device == NULL) { const char *fallbackDevices[] = {"OpenAL Soft", "Generic Software", "Generic Hardware"}; for (unsigned int i = 0; i < sizeof(fallbackDevices) / sizeof(fallbackDevices[0]); ++i) { g_OpenALState.device = tryDevice(fallbackDevices[i]); if (g_OpenALState.device) { break; } } } if (g_OpenALState.device == NULL) { return false; } g_OpenALState.context = g_OpenALApi.alcCreateContext(g_OpenALState.device, NULL); if (g_OpenALState.context == NULL) { g_OpenALApi.alcCloseDevice(g_OpenALState.device); g_OpenALState.device = NULL; return false; } if (!g_OpenALApi.alcMakeContextCurrent(g_OpenALState.context)) { g_OpenALApi.alcDestroyContext(g_OpenALState.context); g_OpenALState.context = NULL; g_OpenALApi.alcCloseDevice(g_OpenALState.device); g_OpenALState.device = NULL; return false; } g_OpenALApi.alGenSources(1, &g_OpenALState.source); if (g_OpenALApi.alGetError() != AL_NO_ERROR || g_OpenALState.source == 0) { g_OpenALApi.alcMakeContextCurrent(NULL); g_OpenALApi.alcDestroyContext(g_OpenALState.context); g_OpenALState.context = NULL; g_OpenALApi.alcCloseDevice(g_OpenALState.device); g_OpenALState.device = NULL; return false; } g_OpenALApi.alGenBuffers(1, &g_OpenALState.buffer); if (g_OpenALApi.alGetError() != AL_NO_ERROR || g_OpenALState.buffer == 0) { g_OpenALApi.alDeleteSources(1, &g_OpenALState.source); g_OpenALState.source = 0; g_OpenALApi.alcMakeContextCurrent(NULL); g_OpenALApi.alcDestroyContext(g_OpenALState.context); g_OpenALState.context = NULL; g_OpenALApi.alcCloseDevice(g_OpenALState.device); g_OpenALState.device = NULL; return false; } g_OpenALApi.alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); g_OpenALState.ready = true; return true; } static void shutdownAudio() { if (!g_OpenALState.loaded) { return; } if (g_OpenALState.ready) { for (size_t i = 0; i < g_OpenALState.activeSources.size(); ++i) { ALuint source = g_OpenALState.activeSources[i]; g_OpenALApi.alSourceStop(source); g_OpenALApi.alDeleteSources(1, &source); } g_OpenALState.activeSources.clear(); for (std::map::iterator it = g_OpenALState.cachedBuffers.begin(); it != g_OpenALState.cachedBuffers.end(); ++it) { ALuint buffer = it->second; g_OpenALApi.alDeleteBuffers(1, &buffer); } g_OpenALState.cachedBuffers.clear(); if (g_OpenALState.source) { g_OpenALApi.alSourceStop(g_OpenALState.source); g_OpenALApi.alSourcei(g_OpenALState.source, AL_BUFFER, 0); } if (g_OpenALState.buffer) { g_OpenALApi.alDeleteBuffers(1, &g_OpenALState.buffer); g_OpenALState.buffer = 0; } if (g_OpenALState.source) { g_OpenALApi.alDeleteSources(1, &g_OpenALState.source); g_OpenALState.source = 0; } if (g_OpenALState.context) { g_OpenALApi.alcMakeContextCurrent(NULL); g_OpenALApi.alcDestroyContext(g_OpenALState.context); g_OpenALState.context = NULL; } if (g_OpenALState.device) { g_OpenALApi.alcCloseDevice(g_OpenALState.device); g_OpenALState.device = NULL; } } if (g_OpenALState.moduleOwned && g_OpenALState.module) { FreeLibrary(g_OpenALState.module); } g_OpenALState.loaded = false; g_OpenALState.ready = false; g_OpenALState.moduleOwned = false; g_OpenALState.module = NULL; resetStreamData(); memset(&g_OpenALApi, 0, sizeof(g_OpenALApi)); } static void normalizeSlashes(char *path) { for (char *p = path; *p; ++p) { if (*p == '\\') { *p = '/'; } } } static void addUniquePathCandidate(std::vector &candidates, const std::string &path) { if (path.empty()) { return; } for (size_t i = 0; i < candidates.size(); ++i) { if (candidates[i] == path) { return; } } candidates.push_back(path); } static void addSourcesSoundPathCandidates(std::vector &candidates, const std::string &relativePath) { addUniquePathCandidate(candidates, "sources/sounds/" + relativePath); addUniquePathCandidate(candidates, "../sources/sounds/" + relativePath); addUniquePathCandidate(candidates, "../../sources/sounds/" + relativePath); addUniquePathCandidate(candidates, "../../../sources/sounds/" + relativePath); } static void addSourcesSoundRootCandidates(std::vector &roots, const std::string &basePath) { addUniquePathCandidate(roots, basePath + "sources/sounds/"); addUniquePathCandidate(roots, basePath + "../sources/sounds/"); addUniquePathCandidate(roots, basePath + "../../sources/sounds/"); addUniquePathCandidate(roots, basePath + "../../../sources/sounds/"); } static float g_lateralScale = 0.10f; static void transformWorldPos(float x, float y, float z, float &xOut, float &yOut, float &zOut) { xOut = x * g_lateralScale; yOut = y; zOut = z; } static void buildOrientation(float frontX, float frontY, float frontZ, float bOrientation[6]) { float ox = -frontX * g_lateralScale; float oy = frontY; float oz = frontZ; float lenSq = ox * ox + oy * oy + oz * oz; if (lenSq > 0.000001f) { float invLen = 1.0f / sqrtf(lenSq); ox *= invLen; oy *= invLen; oz *= invLen; } else { ox = 0.0f; oy = 0.0f; oz = 1.0f; } bOrientation[0] = ox; bOrientation[1] = oy; bOrientation[2] = oz; bOrientation[3] = 0.0f; bOrientation[4] = 1.0f; bOrientation[5] = 0.0f; } static bool tryDecodeVorbis(const char *fileName, std::vector &pcm, int &channels, int &sampleRate) { if (_access(fileName, 0) != 0) { return false; } short *output = NULL; int sampleCount = stb_vorbis_decode_filename(fileName, &channels, &sampleRate, &output); if (sampleCount <= 0 || output == NULL) { return false; } size_t totalSamples = static_cast(sampleCount) * static_cast(channels); pcm.assign(output, output + totalSamples); free(output); return true; } static bool decodeStreamVorbis(const char *requestedPath) { std::vector candidates; char normalized[512]; strncpy(normalized, requestedPath, sizeof(normalized) - 1); normalized[sizeof(normalized) - 1] = 0; normalizeSlashes(normalized); std::string normalizedPath = normalized; std::string maybeOgg = normalizedPath; size_t extPos = maybeOgg.rfind('.'); if (extPos != std::string::npos) { maybeOgg = maybeOgg.substr(0, extPos) + ".ogg"; } addSourcesSoundPathCandidates(candidates, normalizedPath); if (maybeOgg != normalizedPath) { addSourcesSoundPathCandidates(candidates, maybeOgg); } addUniquePathCandidate(candidates, requestedPath); addUniquePathCandidate(candidates, normalizedPath); addUniquePathCandidate(candidates, maybeOgg); char cwd[MAX_PATH]; cwd[0] = 0; if (GetCurrentDirectoryA(sizeof(cwd), cwd) > 0) { std::string cwdPath = cwd; for (size_t i = 0; i < cwdPath.size(); ++i) { if (cwdPath[i] == '\\') { cwdPath[i] = '/'; } } if (!cwdPath.empty() && cwdPath[cwdPath.size() - 1] != '/') { cwdPath += "/"; } addSourcesSoundPathCandidates(candidates, cwdPath + normalizedPath); if (maybeOgg != normalizedPath) { addSourcesSoundPathCandidates(candidates, cwdPath + maybeOgg); } addUniquePathCandidate(candidates, cwdPath + normalizedPath); addUniquePathCandidate(candidates, cwdPath + maybeOgg); addUniquePathCandidate(candidates, cwdPath + "Minecraft.Client/" + normalizedPath); addUniquePathCandidate(candidates, cwdPath + "x64/Release/" + normalizedPath); addUniquePathCandidate(candidates, cwdPath + "../x64/Release/" + normalizedPath); addUniquePathCandidate(candidates, cwdPath + "../../x64/Release/" + normalizedPath); } char modulePath[MAX_PATH]; modulePath[0] = 0; if (GetModuleFileNameA(NULL, modulePath, sizeof(modulePath)) > 0) { std::string moduleDir = modulePath; for (size_t i = 0; i < moduleDir.size(); ++i) { if (moduleDir[i] == '\\') { moduleDir[i] = '/'; } } size_t slashPos = moduleDir.find_last_of('/'); if (slashPos != std::string::npos) { moduleDir = moduleDir.substr(0, slashPos + 1); addSourcesSoundPathCandidates(candidates, moduleDir + normalizedPath); if (maybeOgg != normalizedPath) { addSourcesSoundPathCandidates(candidates, moduleDir + maybeOgg); } addUniquePathCandidate(candidates, moduleDir + normalizedPath); addUniquePathCandidate(candidates, moduleDir + maybeOgg); addUniquePathCandidate(candidates, moduleDir + "Minecraft.Client/" + normalizedPath); addUniquePathCandidate(candidates, moduleDir + "x64/Release/" + normalizedPath); addUniquePathCandidate(candidates, moduleDir + "../x64/Release/" + normalizedPath); addUniquePathCandidate(candidates, moduleDir + "../../x64/Release/" + normalizedPath); } } for (unsigned int i = 0; i < candidates.size(); ++i) { int channels = 0; int sampleRate = 0; std::vector pcm; if (tryDecodeVorbis(candidates[i].c_str(), pcm, channels, sampleRate)) { g_OpenALState.streamPCM.swap(pcm); g_OpenALState.streamChannels = channels; g_OpenALState.streamSampleRate = sampleRate; g_OpenALState.streamLoaded = true; return true; } } return false; } static bool resolveFilePath(const char *relativeNoExt, std::string &resolvedPath) { std::vector candidates; std::vector roots; std::string base = relativeNoExt ? relativeNoExt : ""; if (base.empty()) { return false; } for (size_t i = 0; i < base.size(); ++i) { if (base[i] == '\\') { base[i] = '/'; } } candidates.push_back(base + ".ogg"); if (base.find("Minecraft/UI/") == 0) { std::string uiName = base.substr(13); if (uiName == "back") { candidates.push_back("Minecraft/note/btn_Back.ogg"); } else if (uiName == "craft") { candidates.push_back("Minecraft/random/click.ogg"); } else if (uiName == "craftfail") { candidates.push_back("Minecraft/random/drr.ogg"); } else if (uiName == "focus" || uiName == "press" || uiName == "scroll") { candidates.push_back("Minecraft/random/click.ogg"); } } char cwd[MAX_PATH]; cwd[0] = 0; bool hasCwd = GetCurrentDirectoryA(sizeof(cwd), cwd) > 0; std::string cwdPath = hasCwd ? std::string(cwd) : std::string(); for (size_t i = 0; i < cwdPath.size(); ++i) { if (cwdPath[i] == '\\') { cwdPath[i] = '/'; } } if (!cwdPath.empty() && cwdPath[cwdPath.size() - 1] != '/') { cwdPath += "/"; } addSourcesSoundRootCandidates(roots, ""); addUniquePathCandidate(roots, ""); if (hasCwd) { addSourcesSoundRootCandidates(roots, cwdPath); addUniquePathCandidate(roots, cwdPath); addUniquePathCandidate(roots, cwdPath + "x64/Release/"); addUniquePathCandidate(roots, cwdPath + "../x64/Release/"); addUniquePathCandidate(roots, cwdPath + "../../x64/Release/"); addUniquePathCandidate(roots, cwdPath + "Minecraft.Client/"); } char modulePath[MAX_PATH]; modulePath[0] = 0; if (GetModuleFileNameA(NULL, modulePath, sizeof(modulePath)) > 0) { std::string moduleDir = modulePath; for (size_t i = 0; i < moduleDir.size(); ++i) { if (moduleDir[i] == '\\') { moduleDir[i] = '/'; } } size_t slashPos = moduleDir.find_last_of('/'); if (slashPos != std::string::npos) { moduleDir = moduleDir.substr(0, slashPos + 1); addSourcesSoundRootCandidates(roots, moduleDir); addUniquePathCandidate(roots, moduleDir); addUniquePathCandidate(roots, moduleDir + "x64/Release/"); addUniquePathCandidate(roots, moduleDir + "../x64/Release/"); addUniquePathCandidate(roots, moduleDir + "../../x64/Release/"); addUniquePathCandidate(roots, moduleDir + "Minecraft.Client/"); } } for (size_t i = 0; i < candidates.size(); ++i) { for (size_t j = 0; j < roots.size(); ++j) { std::string exactPath = roots[j] + candidates[i]; if (_access(exactPath.c_str(), 0) == 0) { resolvedPath = exactPath; return true; } std::string wildcard = exactPath; if (wildcard.size() >= 4 && wildcard.substr(wildcard.size() - 4) == ".ogg") { wildcard.erase(wildcard.size() - 4); wildcard += "*.ogg"; _finddata_t findData; intptr_t findHandle = _findfirst(wildcard.c_str(), &findData); if (findHandle != -1) { std::vector variantFiles; size_t slashPos = exactPath.find_last_of("/\\"); std::string baseFileName = slashPos == std::string::npos ? exactPath : exactPath.substr(slashPos + 1); if (baseFileName.size() >= 4 && baseFileName.substr(baseFileName.size() - 4) == ".ogg") { baseFileName.erase(baseFileName.size() - 4); } do { if (!(findData.attrib & _A_SUBDIR)) { std::string fileName = findData.name; if (fileName.size() >= 4 && fileName.substr(fileName.size() - 4) == ".ogg") { std::string stem = fileName.substr(0, fileName.size() - 4); if (stem == baseFileName) { variantFiles.push_back(fileName); } else if (stem.size() > baseFileName.size() && stem.compare(0, baseFileName.size(), baseFileName) == 0) { char suffixLead = stem[baseFileName.size()]; if ((suffixLead >= '0' && suffixLead <= '9') || suffixLead == '_') { variantFiles.push_back(fileName); } } } } } while (_findnext(findHandle, &findData) == 0); _findclose(findHandle); if (!variantFiles.empty()) { size_t slashPos = wildcard.find_last_of("/\\"); std::string dirPrefix = slashPos == std::string::npos ? "" : wildcard.substr(0, slashPos + 1); DWORD tick = GetTickCount(); size_t selected = (size_t)(tick % (DWORD)variantFiles.size()); resolvedPath = dirPrefix + variantFiles[selected]; return true; } } } } } return false; } // also we use ogg now static bool loadBuffer(const std::string &filePath, ALuint &bufferOut) { std::map::iterator existing = g_OpenALState.cachedBuffers.find(filePath); if (existing != g_OpenALState.cachedBuffers.end()) { bufferOut = existing->second; return true; } int channels = 0; int sampleRate = 0; std::vector pcm; if (!tryDecodeVorbis(filePath.c_str(), pcm, channels, sampleRate)) { return false; } ALenum format = 0; if (channels == 1) { format = AL_FORMAT_MONO16; } else if (channels == 2) { format = AL_FORMAT_STEREO16; } if (format == 0 || pcm.empty()) { return false; } ALuint buffer = 0; g_OpenALApi.alGenBuffers(1, &buffer); if (g_OpenALApi.alGetError() != AL_NO_ERROR || buffer == 0) { return false; } g_OpenALApi.alBufferData(buffer, format, &pcm[0], (ALsizei)(pcm.size() * sizeof(short)), sampleRate); if (g_OpenALApi.alGetError() != AL_NO_ERROR) { g_OpenALApi.alDeleteBuffers(1, &buffer); return false; } g_OpenALState.cachedBuffers[filePath] = buffer; bufferOut = buffer; return true; } static bool playOneShot(const char *relativeNoExt, bool is3D, float x, float y, float z, float volume, float pitch) { if (!initAudio()) { return false; } cleanupSources(); std::string filePath; if (!resolveFilePath(relativeNoExt, filePath)) { return false; } ALuint buffer = 0; if (!loadBuffer(filePath, buffer)) { return false; } ALuint source = 0; g_OpenALApi.alGenSources(1, &source); if (g_OpenALApi.alGetError() != AL_NO_ERROR || source == 0) { return false; } g_OpenALApi.alSourcei(source, AL_BUFFER, buffer); g_OpenALApi.alSourcef(source, AL_PITCH, pitch); g_OpenALApi.alSourcef(source, AL_GAIN, volume); if (is3D) { float sourceX, sourceY, sourceZ; transformWorldPos(x, y, z, sourceX, sourceY, sourceZ); g_OpenALApi.alSource3f(source, AL_POSITION, sourceX, sourceY, sourceZ); g_OpenALApi.alSourcef(source, AL_REFERENCE_DISTANCE, 16.0f); } else { g_OpenALApi.alSource3f(source, AL_POSITION, 0.0f, 0.0f, 0.0f); g_OpenALApi.alSourcef(source, AL_REFERENCE_DISTANCE, 1.0f); } g_OpenALApi.alSourcePlay(source); if (g_OpenALApi.alGetError() != AL_NO_ERROR) { g_OpenALApi.alDeleteSources(1, &source); return false; } g_OpenALState.activeSources.push_back(source); return true; } #endif char *SoundEngine::m_szStreamFileA[eStream_Max] = { "calm1", "calm2", "calm3", "hal1", "hal2", "hal3", "hal4", "nuance1", "nuance2", #ifndef _XBOX // add the new music tracks "creative1", "creative2", "creative3", "creative4", "creative5", "creative6", "menu1", "menu2", "menu3", "menu4", #endif "piano1", "piano2", "piano3", // Nether "nether1", "nether2", "nether3", "nether4", // The End "the_end_dragon_alive", "the_end_end", // CDs "11", "13", "blocks", "cat", "chirp", "far", "mall", "mellohi", "stal", "strad", "ward", "where_are_we_now"}; ///////////////////////////////////////////// // // ErrorCallback // ///////////////////////////////////////////// #ifndef _WINDOWS64 void AILCALL ErrorCallback(S64 i_Id, char const *i_Details) { char *pchLastError = AIL_last_error(); if (pchLastError[0] != 0) { app.DebugPrintf("\rErrorCallback Error Category: %s\n", pchLastError); } if (i_Details) { app.DebugPrintf("ErrorCallback - Details: %s\n", i_Details); } } #endif #ifdef __PSVITA__ // AP - this is the callback when the driver is about to mix. At this point the // mutex is locked by Miles so we can now call all Miles functions without the // possibility of incurring a stall. static bool SoundEngine_Change = false; // has tick been called? static CRITICAL_SECTION SoundEngine_MixerMutex; void AILCALL MilesMixerCB(HDIGDRIVER dig) { // has the tick function been called since the last callback if (SoundEngine_Change) { SoundEngine_Change = false; EnterCriticalSection(&SoundEngine_MixerMutex); Minecraft *pMinecraft = Minecraft::GetInstance(); pMinecraft->soundEngine->updateMiles(); pMinecraft->soundEngine->playMusicUpdate(); LeaveCriticalSection(&SoundEngine_MixerMutex); } } #endif ///////////////////////////////////////////// // // init // ///////////////////////////////////////////// void SoundEngine::init(Options *pOptions) { app.DebugPrintf("---SoundEngine::init\n"); #ifdef _WINDOWS64 initAudio(); return; #else #ifdef __DISABLE_MILES__ return; #endif #ifdef __ORBIS__ C4JThread::PushAffinityAllCores(); #endif char *redistpath; #if (defined _WINDOWS64 || \ defined __PSVITA__) // || defined _DURANGO || defined __ORBIS__ ) redistpath = AIL_set_redist_directory(m_szRedistName); #endif app.DebugPrintf("---SoundEngine::init - AIL_startup\n"); S32 ret = AIL_startup(); int iNumberOfChannels = initAudioHardware(8); // Create a driver to render our audio - 44khz, 16 bit, #ifdef __PS3__ // On the Sony PS3, the driver is always opened in 48 kHz, 32-bit floating // point. The only meaningful configurations are MSS_MC_STEREO, // MSS_MC_51_DISCRETE, and MSS_MC_71_DISCRETE. m_hDriver = AIL_open_digital_driver(48000, 16, iNumberOfChannels, AIL_OPEN_DIGITAL_USE_SPU0); #elif defined __PSVITA__ // maximum of 16 samples AIL_set_preference(DIG_MIXER_CHANNELS, 16); m_hDriver = AIL_open_digital_driver(48000, 16, MSS_MC_STEREO, 0); // AP - For some reason the submit thread defaults to a priority of zero // (invalid). Make sure it has the highest priority to avoid audio breakup. SceUID threadID; AIL_platform_property(m_hDriver, PSP2_SUBMIT_THREAD, &threadID, 0, 0); S32 g_DefaultCPU = sceKernelGetThreadCpuAffinityMask(threadID); S32 Old = sceKernelChangeThreadPriority(threadID, 64); // AP - register a callback when the mixer starts AILMIXERCB temp = AIL_register_mix_callback(m_hDriver, MilesMixerCB); InitializeCriticalSection(&SoundEngine_MixerMutex); #elif defined(__ORBIS__) m_hDriver = AIL_open_digital_driver(48000, 16, 2, 0); app.DebugPrintf("---SoundEngine::init - AIL_open_digital_driver\n"); #else m_hDriver = AIL_open_digital_driver(44100, 16, MSS_MC_USE_SYSTEM_CONFIG, 0); #endif if (m_hDriver == 0) { app.DebugPrintf("Couldn't open digital sound driver. (%s)\n", AIL_last_error()); AIL_shutdown(); #ifdef __ORBIS__ C4JThread::PopAffinity(); #endif return; } app.DebugPrintf("---SoundEngine::init - driver opened\n"); #ifdef __PSVITA__ // set high falloff power for maximum spatial effect in software mode AIL_set_speaker_configuration(m_hDriver, 0, 0, 4.0F); #endif AIL_set_event_error_callback(ErrorCallback); AIL_set_3D_rolloff_factor(m_hDriver, 1.0); // Create an event system tied to that driver - let Miles choose memory // defaults. // if (AIL_startup_event_system(m_hDriver, 0, 0, 0) == 0) // 4J-PB - Durango complains that the default memory (64k)isn't enough // Error: MilesEvent: Out of event system memory (pool passed to event system // startup exhausted). AP - increased command buffer from the default 5K to // 20K for Vita if (AIL_startup_event_system(m_hDriver, 1024 * 20, 0, 1024 * 128) == 0) { app.DebugPrintf("Couldn't init event system (%s).\n", AIL_last_error()); AIL_close_digital_driver(m_hDriver); AIL_shutdown(); #ifdef __ORBIS__ C4JThread::PopAffinity(); #endif app.DebugPrintf("---SoundEngine::init - AIL_startup_event_system failed\n"); return; } char szBankName[255]; #if defined __PS3__ if (app.GetBootedFromDiscPatch()) { char szTempSoundFilename[255]; sprintf(szTempSoundFilename, "%s%s", m_szSoundPath, "Minecraft.msscmp"); app.DebugPrintf("SoundEngine::playMusicUpdate - (booted from disc patch) " "looking for %s\n", szTempSoundFilename); sprintf(szBankName, "%s/%s", app.GetBDUsrDirPath(szTempSoundFilename), m_szSoundPath); app.DebugPrintf("SoundEngine::playMusicUpdate - (booted from disc patch) " "music path - %s\n", szBankName); } else { sprintf(szBankName, "%s/%s", getUsrDirPath(), m_szSoundPath); } #elif defined __PSVITA__ sprintf(szBankName, "%s/%s", getUsrDirPath(), m_szSoundPath); #elif defined __ORBIS__ sprintf(szBankName, "%s/%s", getUsrDirPath(), m_szSoundPath); #else strcpy((char *)szBankName, m_szSoundPath); #endif strcat((char *)szBankName, "Minecraft.msscmp"); m_hBank = AIL_add_soundbank(szBankName, 0); if (m_hBank == NULL) { char *Error = AIL_last_error(); app.DebugPrintf("Couldn't open soundbank: %s (%s)\n", szBankName, Error); AIL_close_digital_driver(m_hDriver); AIL_shutdown(); #ifdef __ORBIS__ C4JThread::PopAffinity(); #endif return; } // #ifdef _DEBUG HMSSENUM token = MSS_FIRST; char const *Events[1] = {0}; S32 EventCount = 0; while (AIL_enumerate_events(m_hBank, &token, 0, &Events[0])) { app.DebugPrintf(4, "%d - %s\n", EventCount, Events[0]); EventCount++; } // #endif U64 u64Result; u64Result = AIL_enqueue_event_by_name("Minecraft/CacheSounds"); m_MasterMusicVolume = 1.0f; m_MasterEffectsVolume = 1.0f; // AIL_set_variable_float(0,"UserEffectVol",1); m_bSystemMusicPlaying = false; m_openStreamThread = NULL; #ifdef __ORBIS__ C4JThread::PopAffinity(); #endif #ifdef __PSVITA__ // AP - By default the mixer won't start up and nothing will process. Kick off // a blank sample to force the mixer to start up. HSAMPLE Sample = AIL_allocate_sample_handle(m_hDriver); AIL_init_sample(Sample, DIG_F_STEREO_16); static U64 silence = 0; AIL_set_sample_address(Sample, &silence, sizeof(U64)); AIL_start_sample(Sample); // wait for 1 mix... AIL_release_sample_handle(Sample); #endif #endif } #ifdef __ORBIS__ // void SoundEngine::SetHandle(int32_t hAudio) // { // //m_hAudio=hAudio; // } #endif void SoundEngine::SetStreamingSounds(int iOverworldMin, int iOverWorldMax, 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); } // AP - moved to a separate function so it can be called from the mixer callback // on Vita void SoundEngine::updateMiles() { #ifdef __PSVITA__ // CD - We must check for Background Music [BGM] at any point // If it's playing disable our audio, otherwise enable int NoBGMPlaying = sceAudioOutGetAdopt(SCE_AUDIO_OUT_PORT_TYPE_BGM); updateSystemMusicPlaying(!NoBGMPlaying); #elif defined __ORBIS__ // is the system playing background music? SceAudioOutPortState outPortState; sceAudioOutGetPortState(m_hBGMAudio, &outPortState); updateSystemMusicPlaying(outPortState.output == SCE_AUDIO_OUT_STATE_OUTPUT_UNKNOWN); #endif #ifdef _WINDOWS64 if (initAudio()) { cleanupSources(); if (m_validListenerCount == 1) { for (int i = 0; i < MAX_LOCAL_PLAYERS; i++) { if (m_ListenerA[i].bValid) { float listenerX, listenerY, listenerZ; transformWorldPos( m_ListenerA[i].vPosition.x, m_ListenerA[i].vPosition.y, m_ListenerA[i].vPosition.z, listenerX, listenerY, listenerZ); g_OpenALApi.alListener3f(AL_POSITION, listenerX, listenerY, listenerZ); float bOrientation[6]; buildOrientation(m_ListenerA[i].vOrientFront.x, m_ListenerA[i].vOrientFront.y, m_ListenerA[i].vOrientFront.z, bOrientation); g_OpenALApi.alListenerfv(AL_ORIENTATION, bOrientation); break; } } } else { g_OpenALApi.alListener3f(AL_POSITION, 0.0f, 0.0f, 0.0f); float bOrientation[6] = {0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f}; g_OpenALApi.alListenerfv(AL_ORIENTATION, bOrientation); } } #else if (m_hDriver == 0 || m_hBank == NULL) { return; } if (m_validListenerCount == 1) { for (int i = 0; i < MAX_LOCAL_PLAYERS; i++) { // set the listener as the first player we find if (m_ListenerA[i].bValid) { AIL_set_listener_3D_position( m_hDriver, m_ListenerA[i].vPosition.x, m_ListenerA[i].vPosition.y, -m_ListenerA[i].vPosition.z); // Flipped sign of z as Miles is // expecting left handed coord system AIL_set_listener_3D_orientation( m_hDriver, -m_ListenerA[i].vOrientFront.x, m_ListenerA[i].vOrientFront.y, m_ListenerA[i].vOrientFront.z, 0, 1, 0); // Flipped sign of z as Miles is expecting left handed coord // system break; } } } else { // 4J-PB - special case for splitscreen // the shortest distance between any listener and a sound will be used to // play a sound a set distance away down the z axis. The listener position // will be set to 0,0,0, and the orientation will be facing down the z axis AIL_set_listener_3D_position(m_hDriver, 0, 0, 0); AIL_set_listener_3D_orientation(m_hDriver, 0, 0, 1, 0, 1, 0); } AIL_begin_event_queue_processing(); // Iterate over the sounds S32 StartedCount = 0, CompletedCount = 0, TotalCount = 0; HMSSENUM token = MSS_FIRST; MILESEVENTSOUNDINFO SoundInfo; int Playing = 0; while (AIL_enumerate_sound_instances(0, &token, 0, 0, 0, &SoundInfo)) { AUDIO_INFO *game_data = (AUDIO_INFO *)(SoundInfo.UserBuffer); if (SoundInfo.Status == MILESEVENT_SOUND_STATUS_PLAYING) { Playing += 1; } if (SoundInfo.Status != MILESEVENT_SOUND_STATUS_COMPLETE) { // apply the master volume // watch for the 'special' volume levels bool isThunder = false; if (game_data->volume == 10000.0f) { isThunder = true; } if (game_data->volume > 1) { game_data->volume = 1; } AIL_set_sample_volume_levels(SoundInfo.Sample, game_data->volume * m_MasterEffectsVolume, game_data->volume * m_MasterEffectsVolume); float distanceScaler = 16.0f; switch (SoundInfo.Status) { case MILESEVENT_SOUND_STATUS_PENDING: // 4J-PB - causes the falloff to be calculated on the PPU instead of the // SPU, and seems to resolve our distorted sound issue AIL_register_falloff_function_callback(SoundInfo.Sample, &custom_falloff_function); if (game_data->bIs3D) { AIL_set_sample_is_3D(SoundInfo.Sample, 1); int iSound = game_data->iSound - eSFX_MAX; switch (iSound) { // Is this the Dragon? case eSoundType_MOB_ENDERDRAGON_GROWL: case eSoundType_MOB_ENDERDRAGON_MOVE: case eSoundType_MOB_ENDERDRAGON_END: case eSoundType_MOB_ENDERDRAGON_HIT: distanceScaler = 100.0f; break; case eSoundType_FIREWORKS_BLAST: case eSoundType_FIREWORKS_BLAST_FAR: case eSoundType_FIREWORKS_LARGE_BLAST: case eSoundType_FIREWORKS_LARGE_BLAST_FAR: distanceScaler = 100.0f; break; case eSoundType_MOB_GHAST_MOAN: case eSoundType_MOB_GHAST_SCREAM: case eSoundType_MOB_GHAST_DEATH: case eSoundType_MOB_GHAST_CHARGE: case eSoundType_MOB_GHAST_FIREBALL: distanceScaler = 30.0f; break; } // Set a special distance scaler for thunder, which we respond to by // having no attenutation if (isThunder) { distanceScaler = 10000.0f; } } else { AIL_set_sample_is_3D(SoundInfo.Sample, 0); } AIL_set_sample_3D_distances(SoundInfo.Sample, distanceScaler, 1, 0); // set the pitch if (!game_data->bUseSoundsPitchVal) { AIL_set_sample_playback_rate_factor(SoundInfo.Sample, game_data->pitch); } if (game_data->bIs3D) { if (m_validListenerCount > 1) { float fClosest = 10000.0f; int iClosestListener = 0; float fClosestX = 0.0f, fClosestY = 0.0f, fClosestZ = 0.0f, fDist; // need to calculate the distance from the sound to the nearest // listener - use Manhattan Distance as the decision for (int i = 0; i < MAX_LOCAL_PLAYERS; i++) { if (m_ListenerA[i].bValid) { float x, y, z; x = fabs(m_ListenerA[i].vPosition.x - game_data->x); y = fabs(m_ListenerA[i].vPosition.y - game_data->y); z = fabs(m_ListenerA[i].vPosition.z - game_data->z); fDist = x + y + z; if (fDist < fClosest) { fClosest = fDist; fClosestX = x; fClosestY = y; fClosestZ = z; iClosestListener = i; } } } // our distances in the world aren't very big, so floats rather than // casts to doubles should be fine fDist = sqrtf((fClosestX * fClosestX) + (fClosestY * fClosestY) + (fClosestZ * fClosestZ)); AIL_set_sample_3D_position(SoundInfo.Sample, 0, 0, fDist); // app.DebugPrintf("Playing sound %d %f from nearest listener // [%d]\n",SoundInfo.EventID,fDist,iClosestListener); } else { AIL_set_sample_3D_position( SoundInfo.Sample, game_data->x, game_data->y, -game_data->z); // Flipped sign of z as Miles is expecting left // handed coord system } } break; default: if (game_data->bIs3D) { if (m_validListenerCount > 1) { float fClosest = 10000.0f; int iClosestListener = 0; float fClosestX = 0.0f, fClosestY = 0.0f, fClosestZ = 0.0f, fDist; // need to calculate the distance from the sound to the nearest // listener - use Manhattan Distance as the decision for (int i = 0; i < MAX_LOCAL_PLAYERS; i++) { if (m_ListenerA[i].bValid) { float x, y, z; x = fabs(m_ListenerA[i].vPosition.x - game_data->x); y = fabs(m_ListenerA[i].vPosition.y - game_data->y); z = fabs(m_ListenerA[i].vPosition.z - game_data->z); fDist = x + y + z; if (fDist < fClosest) { fClosest = fDist; fClosestX = x; fClosestY = y; fClosestZ = z; iClosestListener = i; } } } // our distances in the world aren't very big, so floats rather than // casts to doubles should be fine fDist = sqrtf((fClosestX * fClosestX) + (fClosestY * fClosestY) + (fClosestZ * fClosestZ)); AIL_set_sample_3D_position(SoundInfo.Sample, 0, 0, fDist); // app.DebugPrintf("Playing sound %d %f from nearest listener // [%d]\n",SoundInfo.EventID,fDist,iClosestListener); } else { AIL_set_sample_3D_position( SoundInfo.Sample, game_data->x, game_data->y, -game_data->z); // Flipped sign of z as Miles is expecting left // handed coord system } } break; } } } AIL_complete_event_queue_processing(); #endif } // #define DISTORTION_TEST #ifdef DISTORTION_TEST static float fVal = 0.0f; #endif ///////////////////////////////////////////// // // tick // ///////////////////////////////////////////// #ifdef __PSVITA__ static S32 running = AIL_ms_count(); #endif void SoundEngine::tick(shared_ptr *players, float a) { ConsoleSoundEngine::tick(); #ifdef __DISABLE_MILES__ return; #endif #ifdef __PSVITA__ EnterCriticalSection(&SoundEngine_MixerMutex); #endif // update the listener positions int listenerCount = 0; #ifdef DISTORTION_TEST float fX, fY, fZ; #endif if (players) { bool bListenerPostionSet = false; for (int i = 0; i < MAX_LOCAL_PLAYERS; i++) { if (players[i] != NULL) { m_ListenerA[i].bValid = true; F32 x, y, z; x = players[i]->xo + (players[i]->x - players[i]->xo) * a; y = players[i]->yo + (players[i]->y - players[i]->yo) * a; z = players[i]->zo + (players[i]->z - players[i]->zo) * a; float yRot = players[i]->yRotO + (players[i]->yRot - players[i]->yRotO) * a; float yCos = (float)cos(-yRot * Mth::RAD_TO_GRAD - PI); float ySin = (float)sin(-yRot * Mth::RAD_TO_GRAD - PI); // store the listener positions for splitscreen m_ListenerA[i].vPosition.x = x; m_ListenerA[i].vPosition.y = y; m_ListenerA[i].vPosition.z = z; m_ListenerA[i].vOrientFront.x = ySin; m_ListenerA[i].vOrientFront.y = 0; m_ListenerA[i].vOrientFront.z = yCos; listenerCount++; } else { m_ListenerA[i].bValid = false; } } } // If there were no valid players set, make up a default listener if (listenerCount == 0) { m_ListenerA[0].vPosition.x = 0; m_ListenerA[0].vPosition.y = 0; m_ListenerA[0].vPosition.z = 0; m_ListenerA[0].vOrientFront.x = 0; m_ListenerA[0].vOrientFront.y = 0; m_ListenerA[0].vOrientFront.z = 1.0f; listenerCount++; } m_validListenerCount = listenerCount; #ifdef __PSVITA__ // AP - Show that a change has occurred so we know to update the values at the // next Mixer callback SoundEngine_Change = true; LeaveCriticalSection(&SoundEngine_MixerMutex); #else updateMiles(); #endif } ///////////////////////////////////////////// // // SoundEngine // ///////////////////////////////////////////// SoundEngine::SoundEngine() { random = new Random(); m_hBank = NULL; m_hDriver = 0; m_hStream = 0; m_openStreamThread = NULL; m_StreamState = eMusicStreamState_Idle; m_iMusicDelay = 0; m_validListenerCount = 0; m_MasterMusicVolume = 1.0f; m_MasterEffectsVolume = 1.0f; m_bSystemMusicPlaying = false; m_bHeardTrackA = NULL; // 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_musicID = getMusicID(LevelData::DIMENSION_OVERWORLD); m_StreamingAudioInfo.bIs3D = false; m_StreamingAudioInfo.x = 0; m_StreamingAudioInfo.y = 0; m_StreamingAudioInfo.z = 0; m_StreamingAudioInfo.volume = 1; m_StreamingAudioInfo.pitch = 1; memset(CurrentSoundsPlaying, 0, sizeof(int) * (eSoundType_MAX + eSFX_MAX)); memset(m_ListenerA, 0, sizeof(AUDIO_LISTENER) * XUSER_MAX_COUNT); #ifdef __ORBIS__ m_hBGMAudio = GetAudioBGMHandle(); #endif } void SoundEngine::destroy() { #ifdef _WINDOWS64 shutdownAudio(); #endif } #ifdef _DEBUG void SoundEngine::GetSoundName(char *szSoundName, int iSound) { strcpy((char *)szSoundName, "Minecraft/"); wstring name = wchSoundNames[iSound]; char *SoundName = (char *)ConvertSoundPathToName(name); strcat((char *)szSoundName, SoundName); } #endif ///////////////////////////////////////////// // // play // ///////////////////////////////////////////// void SoundEngine::play(int iSound, float x, float y, float z, float volume, float pitch) { U8 szSoundName[256]; if (iSound == -1) { app.DebugPrintf(6, "PlaySound with sound of -1 !!!!!!!!!!!!!!!\n"); return; } // AP removed old counting system. Now relying on Miles' Play Count Limit /* // if we are already playing loads of this sounds ignore this one if(CurrentSoundsPlaying[iSound+eSFX_MAX]>MAX_SAME_SOUNDS_PLAYING) { // wstring name = wchSoundNames[iSound]; // char *SoundName = (char *)ConvertSoundPathToName(name); // app.DebugPrintf("Too many %s sounds playing!\n",SoundName); return; }*/ // if (iSound != eSoundType_MOB_IRONGOLEM_WALK) return; // build the name strcpy((char *)szSoundName, "Minecraft/"); #ifdef DISTORTION_TEST wstring name = wchSoundNames[eSoundType_MOB_ENDERDRAGON_GROWL]; #else wstring name = wchSoundNames[iSound]; #endif char *SoundName = (char *)ConvertSoundPathToName(name); strcat((char *)szSoundName, SoundName); #ifdef _WINDOWS64 char szOpenALName[256]; strcpy(szOpenALName, "Minecraft/"); strcat(szOpenALName, SoundName); if (playOneShot(szOpenALName, true, x, y, z, volume * m_MasterEffectsVolume, pitch)) { return; } #endif #ifndef _WINDOWS64 if (m_hDriver == 0 || m_hBank == NULL) { return; } // app.DebugPrintf(6,"PlaySound - %d - %s - %s (%f %f %f, vol %f, pitch //%f)\n",iSound, SoundName, szSoundName,x,y,z,volume,pitch); AUDIO_INFO AudioInfo; AudioInfo.x = x; AudioInfo.y = y; AudioInfo.z = z; AudioInfo.volume = volume; AudioInfo.pitch = pitch; AudioInfo.bIs3D = true; AudioInfo.bUseSoundsPitchVal = false; AudioInfo.iSound = iSound + eSFX_MAX; #ifdef _DEBUG strncpy(AudioInfo.chName, (char *)szSoundName, 64); #endif S32 token = AIL_enqueue_event_start(); AIL_enqueue_event_buffer(&token, &AudioInfo, sizeof(AUDIO_INFO), 0); AIL_enqueue_event_end_named(token, (char *)szSoundName); #endif } ///////////////////////////////////////////// // // playUI // ///////////////////////////////////////////// void SoundEngine::playUI(int iSound, float volume, float pitch) { U8 szSoundName[256]; wstring name; // we have some game sounds played as UI sounds... // Not the best way to do this, but it seems to only be the portal sounds if (iSound >= eSFX_MAX) { // AP removed old counting system. Now relying on Miles' Play Count Limit /* // if we are already playing loads of this sounds ignore this one if(CurrentSoundsPlaying[iSound+eSFX_MAX]>MAX_SAME_SOUNDS_PLAYING) return;*/ // build the name strcpy((char *)szSoundName, "Minecraft/"); name = wchSoundNames[iSound]; } else { // AP removed old counting system. Now relying on Miles' Play Count Limit /* // if we are already playing loads of this sounds ignore this one if(CurrentSoundsPlaying[iSound]>MAX_SAME_SOUNDS_PLAYING) return;*/ // build the name strcpy((char *)szSoundName, "Minecraft/UI/"); name = wchUISoundNames[iSound]; } char *SoundName = (char *)ConvertSoundPathToName(name); strcat((char *)szSoundName, SoundName); #ifdef _WINDOWS64 char szOpenALName[256]; if (iSound >= eSFX_MAX) { strcpy(szOpenALName, "Minecraft/"); strcat(szOpenALName, SoundName); } else { strcpy(szOpenALName, "Minecraft/UI/"); strcat(szOpenALName, SoundName); } if (playOneShot(szOpenALName, false, 0.0f, 0.0f, 0.0f, volume * m_MasterEffectsVolume, pitch)) { return; } #endif #ifndef _WINDOWS64 if (m_hDriver == 0 || m_hBank == NULL) { return; } // app.DebugPrintf("UI: Playing %s, volume %f, pitch //%f\n",SoundName,volume,pitch); // app.DebugPrintf("PlaySound - %d - %s\n",iSound, SoundName); AUDIO_INFO AudioInfo; memset(&AudioInfo, 0, sizeof(AUDIO_INFO)); AudioInfo.volume = volume; // will be multiplied by the master volume AudioInfo.pitch = pitch; AudioInfo.bUseSoundsPitchVal = true; if (iSound >= eSFX_MAX) { AudioInfo.iSound = iSound + eSFX_MAX; } else { AudioInfo.iSound = iSound; } #ifdef _DEBUG strncpy(AudioInfo.chName, (char *)szSoundName, 64); #endif // 4J-PB - not going to stop UI events happening based on the number of // currently playing sounds S32 token = AIL_enqueue_event_start(); AIL_enqueue_event_buffer(&token, &AudioInfo, sizeof(AUDIO_INFO), 0); AIL_enqueue_event_end_named(token, (char *)szSoundName); #endif } ///////////////////////////////////////////// // // playStreaming // ///////////////////////////////////////////// void SoundEngine::playStreaming(const wstring &name, float x, float y, float z, float volume, float pitch, bool bMusicDelay) { // 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; if (m_StreamState == eMusicStreamState_Playing) { m_StreamState = eMusicStreamState_Stop; } else if (m_StreamState == eMusicStreamState_Opening) { m_StreamState = eMusicStreamState_OpeningCancel; } if (name.empty()) { // music, or stop CD m_StreamingAudioInfo.bIs3D = false; // 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; #ifdef _DEBUG m_iMusicDelay = 0; #endif Minecraft *pMinecraft = Minecraft::GetInstance(); bool playerInEnd = false; bool playerInNether = false; for (unsigned int i = 0; i < MAX_LOCAL_PLAYERS; i++) { if (pMinecraft->localplayers[i] != NULL) { 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); } } else { // jukebox m_StreamingAudioInfo.bIs3D = true; m_musicID = getMusicID(name); m_iMusicDelay = 0; } } int SoundEngine::GetRandomishTrack(int iStart, int iEnd) { // 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 (int i = iStart; i <= iEnd; i++) { if (m_bHeardTrackA[i] == false) { bAllTracksHeard = false; app.DebugPrintf("Not heard all tracks yet\n"); break; } } if (bAllTracksHeard) { app.DebugPrintf("Heard all tracks - resetting the tracking array\n"); for (int i = iStart; i <= iEnd; i++) { m_bHeardTrackA[i] = false; } } // trying to get a track we haven't heard, but not too hard for (int i = 0; i <= ((iEnd - iStart) / 2); i++) { // 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); } } app.DebugPrintf("Select track %d\n", iVal); return iVal; } ///////////////////////////////////////////// // // getMusicID // ///////////////////////////////////////////// int SoundEngine::getMusicID(int iDomain) { int iRandomVal = 0; Minecraft *pMinecraft = Minecraft::GetInstance(); // Before the game has started? if (pMinecraft == NULL) { // 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); } } } ///////////////////////////////////////////// // // getMusicID // ///////////////////////////////////////////// // check what the CD is int SoundEngine::getMusicID(const wstring &name) { int iCD = 0; char *SoundName = (char *)ConvertSoundPathToName(name, true); // 4J-PB - these will always be the game cds, so use the m_szStreamFileA for // this for (int i = 0; i < 12; i++) { if (strcmp(SoundName, m_szStreamFileA[i + eStream_CD_1]) == 0) { iCD = i; break; } } // adjust for cd start position on normal or mash-up pack return iCD + m_iStream_CD_1; } ///////////////////////////////////////////// // // getMasterMusicVolume // ///////////////////////////////////////////// float SoundEngine::getMasterMusicVolume() { if (m_bSystemMusicPlaying) { return 0.0f; } else { return m_MasterMusicVolume; } } ///////////////////////////////////////////// // // updateMusicVolume // ///////////////////////////////////////////// void SoundEngine::updateMusicVolume(float fVal) { m_MasterMusicVolume = fVal; } ///////////////////////////////////////////// // // updateSystemMusicPlaying // ///////////////////////////////////////////// void SoundEngine::updateSystemMusicPlaying(bool isPlaying) { m_bSystemMusicPlaying = isPlaying; } ///////////////////////////////////////////// // // updateSoundEffectVolume // ///////////////////////////////////////////// void SoundEngine::updateSoundEffectVolume(float fVal) { m_MasterEffectsVolume = fVal; // AIL_set_variable_float(0,"UserEffectVol",fVal); } void SoundEngine::add(const wstring &name, File *file) {} void SoundEngine::addMusic(const wstring &name, File *file) {} void SoundEngine::addStreaming(const wstring &name, File *file) {} bool SoundEngine::isStreamingWavebankReady() { return true; } int SoundEngine::OpenStreamThreadProc(void *lpParameter) { #ifdef __DISABLE_MILES__ return 0; #endif SoundEngine *soundEngine = (SoundEngine *)lpParameter; #ifdef _WINDOWS64 resetStreamData(); if (!initAudio()) { return 0; } decodeStreamVorbis(soundEngine->m_szStreamName); return 0; #endif #ifndef _WINDOWS64 soundEngine->m_hStream = AIL_open_stream(soundEngine->m_hDriver, soundEngine->m_szStreamName, 0); if (soundEngine->m_hStream == 0) { app.DebugPrintf("SoundEngine::OpenStreamThreadProc - Could not open - %s\n", soundEngine->m_szStreamName); } #endif return 0; } ///////////////////////////////////////////// // // playMusicTick // ///////////////////////////////////////////// void SoundEngine::playMusicTick() { // AP - vita will update the music during the mixer callback #ifndef __PSVITA__ playMusicUpdate(); #endif } // AP - moved to a separate function so it can be called from the mixer callback // on Vita void SoundEngine::playMusicUpdate() { // return; static bool firstCall = true; static float fMusicVol = 0.0f; if (firstCall) { fMusicVol = getMasterMusicVolume(); firstCall = false; } switch (m_StreamState) { case eMusicStreamState_Idle: // start a stream playing if (m_iMusicDelay > 0) { m_iMusicDelay--; return; } if (m_musicID != -1) { // start playing it #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); } #else sprintf(m_szStreamName, "%s/%s", getUsrDirPath(), m_szMusicPath); #endif #else 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 // if the music ID is beyond the end of the texture pack music files, // then it's a CD if (m_musicID < m_iStream_CD_1) { SetIsPlayingStreamingGameMusic(true); SetIsPlayingStreamingCDMusic(false); m_MusicType = eMusicType_Game; m_StreamingAudioInfo.bIs3D = false; #ifdef _XBOX_ONE wstring &wstrSoundName = dlcAudioFile->GetSoundName(m_musicID); wstring wstrFile = L"TPACK:\\Data\\" + wstrSoundName + L".ogg"; 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); #if defined __PS3__ || defined __ORBIS__ || defined __PSVITA__ string strFile = "TPACK:/Data/" + string(szName) + ".ogg"; #else string strFile = "TPACK:\\Data\\" + string(szName) + ".ogg"; #endif 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, ".ogg"); } } else { // 4J-PB - if this is a PS3 disc patch, we have to check if the music // file is in the patch data #ifdef __PS3__ if (app.GetBootedFromDiscPatch() && (m_musicID < m_iStream_CD_1)) { // rebuild the path for the music strcpy((char *)m_szStreamName, m_szMusicPath); strcat((char *)m_szStreamName, "music/"); strcat((char *)m_szStreamName, m_szStreamFileA[m_musicID]); strcat((char *)m_szStreamName, ".ogg"); // check if this is in the patch data sprintf(m_szStreamName, "%s/%s", app.GetBDUsrDirPath(m_szStreamName), m_szMusicPath); strcat((char *)m_szStreamName, "music/"); strcat((char *)m_szStreamName, m_szStreamFileA[m_musicID]); strcat((char *)m_szStreamName, ".ogg"); SetIsPlayingStreamingGameMusic(true); SetIsPlayingStreamingCDMusic(false); m_MusicType = eMusicType_Game; m_StreamingAudioInfo.bIs3D = false; } else if (m_musicID < m_iStream_CD_1) { SetIsPlayingStreamingGameMusic(true); SetIsPlayingStreamingCDMusic(false); m_MusicType = eMusicType_Game; m_StreamingAudioInfo.bIs3D = false; // build the name strcat((char *)m_szStreamName, "music/"); strcat((char *)m_szStreamName, m_szStreamFileA[m_musicID]); strcat((char *)m_szStreamName, ".ogg"); } else { SetIsPlayingStreamingGameMusic(false); SetIsPlayingStreamingCDMusic(true); m_MusicType = eMusicType_CD; m_StreamingAudioInfo.bIs3D = true; // build the name strcat((char *)m_szStreamName, "cds/"); strcat((char *)m_szStreamName, m_szStreamFileA[m_musicID]); strcat((char *)m_szStreamName, ".ogg"); } #else if (m_musicID < m_iStream_CD_1) { SetIsPlayingStreamingGameMusic(true); SetIsPlayingStreamingCDMusic(false); m_MusicType = eMusicType_Game; m_StreamingAudioInfo.bIs3D = false; // build the name strcat((char *)m_szStreamName, "music/"); } else { SetIsPlayingStreamingGameMusic(false); SetIsPlayingStreamingCDMusic(true); m_MusicType = eMusicType_CD; m_StreamingAudioInfo.bIs3D = true; // build the name strcat((char *)m_szStreamName, "cds/"); } strcat((char *)m_szStreamName, m_szStreamFileA[m_musicID]); strcat((char *)m_szStreamName, ".ogg"); #endif } // wstring name = m_szStreamFileA[m_musicID]; // char *SoundName = (char // *)ConvertSoundPathToName(name); strcat((char // *)szStreamName,SoundName); #ifdef _WINDOWS64 normalizeSlashes(m_szStreamName); #endif app.DebugPrintf("Starting streaming - %s\n", m_szStreamName); // Don't actually open in this thread, as it can block for ~300ms. m_openStreamThread = new C4JThread(OpenStreamThreadProc, this, "OpenStreamThreadProc"); m_openStreamThread->Run(); m_StreamState = eMusicStreamState_Opening; } break; case eMusicStreamState_Opening: // If the open stream thread is complete, then we are ready to proceed to // actually playing if (!m_openStreamThread->isRunning()) { delete m_openStreamThread; m_openStreamThread = NULL; #ifdef _WINDOWS64 bool started = false; if (initAudio() && g_OpenALState.streamLoaded && g_OpenALState.source && g_OpenALState.buffer && !g_OpenALState.streamPCM.empty()) { ALenum format = 0; if (g_OpenALState.streamChannels == 1) { format = AL_FORMAT_MONO16; } else if (g_OpenALState.streamChannels == 2) { format = AL_FORMAT_STEREO16; } if (format != 0) { g_OpenALApi.alSourceStop(g_OpenALState.source); g_OpenALApi.alSourcei(g_OpenALState.source, AL_BUFFER, 0); g_OpenALApi.alBufferData( g_OpenALState.buffer, format, &g_OpenALState.streamPCM[0], (ALsizei)(g_OpenALState.streamPCM.size() * sizeof(short)), g_OpenALState.streamSampleRate); if (g_OpenALApi.alGetError() == AL_NO_ERROR) { g_OpenALApi.alSourcei(g_OpenALState.source, AL_BUFFER, g_OpenALState.buffer); g_OpenALApi.alSourcef(g_OpenALState.source, AL_PITCH, m_StreamingAudioInfo.pitch); g_OpenALApi.alSourcef(g_OpenALState.source, AL_GAIN, m_StreamingAudioInfo.volume * getMasterMusicVolume()); if (m_StreamingAudioInfo.bIs3D) { if (m_validListenerCount > 1) { float fClosest = 10000.0f; float fClosestX = 0.0f, fClosestY = 0.0f, fClosestZ = 0.0f, fDist; for (int i = 0; i < MAX_LOCAL_PLAYERS; i++) { if (m_ListenerA[i].bValid) { float x, y, z; x = fabs(m_ListenerA[i].vPosition.x - m_StreamingAudioInfo.x); y = fabs(m_ListenerA[i].vPosition.y - m_StreamingAudioInfo.y); z = fabs(m_ListenerA[i].vPosition.z - m_StreamingAudioInfo.z); fDist = x + y + z; if (fDist < fClosest) { fClosest = fDist; fClosestX = x; fClosestY = y; fClosestZ = z; } } } fDist = sqrtf((fClosestX * fClosestX) + (fClosestY * fClosestY) + (fClosestZ * fClosestZ)); g_OpenALApi.alSource3f(g_OpenALState.source, AL_POSITION, 0.0f, 0.0f, fDist); } else { float sourceX, sourceY, sourceZ; transformWorldPos( m_StreamingAudioInfo.x, m_StreamingAudioInfo.y, m_StreamingAudioInfo.z, sourceX, sourceY, sourceZ); g_OpenALApi.alSource3f(g_OpenALState.source, AL_POSITION, sourceX, sourceY, sourceZ); } g_OpenALApi.alSourcef(g_OpenALState.source, AL_REFERENCE_DISTANCE, 64.0f); } else { g_OpenALApi.alSource3f(g_OpenALState.source, AL_POSITION, 0.0f, 0.0f, 0.0f); g_OpenALApi.alSourcef(g_OpenALState.source, AL_REFERENCE_DISTANCE, 1.0f); } g_OpenALApi.alSourcePlay(g_OpenALState.source); started = true; } } } if (started) { m_StreamState = eMusicStreamState_Playing; } else { SetIsPlayingStreamingCDMusic(false); SetIsPlayingStreamingGameMusic(false); m_StreamState = eMusicStreamState_Idle; } resetStreamData(); #else HSAMPLE hSample = AIL_stream_sample_handle(m_hStream); // 4J-PB - causes the falloff to be calculated on the PPU instead of the // SPU, and seems to resolve our distorted sound issue AIL_register_falloff_function_callback(hSample, &custom_falloff_function); if (m_StreamingAudioInfo.bIs3D) { AIL_set_sample_3D_distances( hSample, 64.0f, 1, 0); // Larger distance scaler for music discs if (m_validListenerCount > 1) { float fClosest = 10000.0f; int iClosestListener = 0; float fClosestX = 0.0f, fClosestY = 0.0f, fClosestZ = 0.0f, fDist; // need to calculate the distance from the sound to the nearest // listener - use Manhattan Distance as the decision for (int i = 0; i < MAX_LOCAL_PLAYERS; i++) { if (m_ListenerA[i].bValid) { float x, y, z; x = fabs(m_ListenerA[i].vPosition.x - m_StreamingAudioInfo.x); y = fabs(m_ListenerA[i].vPosition.y - m_StreamingAudioInfo.y); z = fabs(m_ListenerA[i].vPosition.z - m_StreamingAudioInfo.z); fDist = x + y + z; if (fDist < fClosest) { fClosest = fDist; fClosestX = x; fClosestY = y; fClosestZ = z; iClosestListener = i; } } } // our distances in the world aren't very big, so floats rather than // casts to doubles should be fine fDist = sqrtf((fClosestX * fClosestX) + (fClosestY * fClosestY) + (fClosestZ * fClosestZ)); AIL_set_sample_3D_position(hSample, 0, 0, fDist); } else { AIL_set_sample_3D_position( hSample, m_StreamingAudioInfo.x, m_StreamingAudioInfo.y, -m_StreamingAudioInfo.z); // Flipped sign of z as Miles is // expecting left handed coord system } } else { // clear the 3d flag on the stream after a jukebox finishes and // streaming music starts AIL_set_sample_is_3D(hSample, 0); } // set the pitch app.DebugPrintf("Sample rate:%d\n", AIL_sample_playback_rate(hSample)); AIL_set_sample_playback_rate_factor(hSample, m_StreamingAudioInfo.pitch); // set the volume AIL_set_sample_volume_levels( hSample, m_StreamingAudioInfo.volume * getMasterMusicVolume(), m_StreamingAudioInfo.volume * getMasterMusicVolume()); AIL_start_stream(m_hStream); m_StreamState = eMusicStreamState_Playing; #endif } break; case eMusicStreamState_OpeningCancel: if (!m_openStreamThread->isRunning()) { delete m_openStreamThread; m_openStreamThread = NULL; m_StreamState = eMusicStreamState_Stop; } break; case eMusicStreamState_Stop: // should gradually take the volume down in steps #ifdef _WINDOWS64 if (initAudio() && g_OpenALState.source) { g_OpenALApi.alSourceStop(g_OpenALState.source); g_OpenALApi.alSourcei(g_OpenALState.source, AL_BUFFER, 0); } resetStreamData(); #else AIL_pause_stream(m_hStream, 1); AIL_close_stream(m_hStream); m_hStream = 0; #endif SetIsPlayingStreamingCDMusic(false); SetIsPlayingStreamingGameMusic(false); m_StreamState = eMusicStreamState_Idle; break; case eMusicStreamState_Stopping: break; case eMusicStreamState_Play: break; case eMusicStreamState_Playing: if (GetIsPlayingStreamingGameMusic()) { // if(m_MusicInfo.pCue!=NULL) { bool playerInEnd = false; bool playerInNether = false; Minecraft *pMinecraft = Minecraft::GetInstance(); for (unsigned int i = 0; i < MAX_LOCAL_PLAYERS; ++i) { if (pMinecraft->localplayers[i] != NULL) { if (pMinecraft->localplayers[i]->dimension == LevelData::DIMENSION_END) { playerInEnd = true; } else if (pMinecraft->localplayers[i]->dimension == LevelData::DIMENSION_NETHER) { playerInNether = true; } } } if (playerInEnd && !GetIsPlayingEndMusic()) { m_StreamState = eMusicStreamState_Stop; // Set the end track m_musicID = getMusicID(LevelData::DIMENSION_END); SetIsPlayingEndMusic(true); SetIsPlayingNetherMusic(false); } else if (!playerInEnd && GetIsPlayingEndMusic()) { if (playerInNether) { m_StreamState = eMusicStreamState_Stop; // Set the end track m_musicID = getMusicID(LevelData::DIMENSION_NETHER); SetIsPlayingEndMusic(false); SetIsPlayingNetherMusic(true); } else { m_StreamState = eMusicStreamState_Stop; // 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); } } // volume change required? if (fMusicVol != getMasterMusicVolume()) { fMusicVol = getMasterMusicVolume(); #ifdef _WINDOWS64 if (initAudio() && g_OpenALState.source) { g_OpenALApi.alSourcef(g_OpenALState.source, AL_GAIN, fMusicVol); } #else HSAMPLE hSample = AIL_stream_sample_handle(m_hStream); // AIL_set_sample_3D_position( hSample, m_StreamingAudioInfo.x, // m_StreamingAudioInfo.y, m_StreamingAudioInfo.z ); AIL_set_sample_volume_levels(hSample, fMusicVol, fMusicVol); #endif } } } 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) { if (m_validListenerCount > 1) { float fClosest = 10000.0f; int iClosestListener = 0; float fClosestX = 0.0f, fClosestY = 0.0f, fClosestZ = 0.0f, fDist; // need to calculate the distance from the sound to the nearest // listener - use Manhattan Distance as the decision for (int i = 0; i < MAX_LOCAL_PLAYERS; i++) { if (m_ListenerA[i].bValid) { float x, y, z; x = fabs(m_ListenerA[i].vPosition.x - m_StreamingAudioInfo.x); y = fabs(m_ListenerA[i].vPosition.y - m_StreamingAudioInfo.y); z = fabs(m_ListenerA[i].vPosition.z - m_StreamingAudioInfo.z); fDist = x + y + z; if (fDist < fClosest) { fClosest = fDist; fClosestX = x; fClosestY = y; fClosestZ = z; iClosestListener = i; } } } // our distances in the world aren't very big, so floats rather than // casts to doubles should be fine #ifdef _WINDOWS64 if (initAudio() && g_OpenALState.source) { fDist = sqrtf((fClosestX * fClosestX) + (fClosestY * fClosestY) + (fClosestZ * fClosestZ)); g_OpenALApi.alSource3f(g_OpenALState.source, AL_POSITION, 0.0f, 0.0f, fDist); } #else HSAMPLE hSample = AIL_stream_sample_handle(m_hStream); fDist = sqrtf((fClosestX * fClosestX) + (fClosestY * fClosestY) + (fClosestZ * fClosestZ)); AIL_set_sample_3D_position(hSample, 0, 0, fDist); #endif } #ifdef _WINDOWS64 else { if (initAudio() && g_OpenALState.source) { float sourceX, sourceY, sourceZ; transformWorldPos(m_StreamingAudioInfo.x, m_StreamingAudioInfo.y, m_StreamingAudioInfo.z, sourceX, sourceY, sourceZ); g_OpenALApi.alSource3f(g_OpenALState.source, AL_POSITION, sourceX, sourceY, sourceZ); } } #endif } } break; 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; for (unsigned int i = 0; i < MAX_LOCAL_PLAYERS; i++) { if (pMinecraft->localplayers[i] != NULL) { 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); } 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 #ifdef _WINDOWS64 if (m_StreamState == eMusicStreamState_Playing && initAudio() && g_OpenALState.source) { ALint sourceState = AL_STOPPED; g_OpenALApi.alGetSourcei(g_OpenALState.source, AL_SOURCE_STATE, &sourceState); if (sourceState == AL_STOPPED) { g_OpenALApi.alSourcei(g_OpenALState.source, AL_BUFFER, 0); SetIsPlayingStreamingCDMusic(false); SetIsPlayingStreamingGameMusic(false); m_StreamState = eMusicStreamState_Completed; } } #else if (m_hStream != 0) { if (AIL_stream_status(m_hStream) == SMP_DONE) // SMP_DONE { AIL_close_stream(m_hStream); m_hStream = 0; SetIsPlayingStreamingCDMusic(false); SetIsPlayingStreamingGameMusic(false); m_StreamState = eMusicStreamState_Completed; } } #endif } ///////////////////////////////////////////// // // ConvertSoundPathToName // ///////////////////////////////////////////// char *SoundEngine::ConvertSoundPathToName(const wstring &name, bool bConvertSpaces) { static char buf[256]; assert(name.length() < 256); for (unsigned int i = 0; i < name.length(); i++) { wchar_t c = name[i]; if (c == '.') c = '/'; if (bConvertSpaces) { if (c == ' ') c = '_'; } buf[i] = (char)c; } buf[name.length()] = 0; return buf; } #endif #ifndef _WINDOWS64 F32 AILCALLBACK custom_falloff_function(HSAMPLE S, F32 distance, F32 rolloff_factor, F32 min_dist, F32 max_dist) { F32 result; // This is now emulating the linear fall-off function that we used on the Xbox // 360. The parameter which is passed as "max_dist" is the only one actually // used, and is generally used as CurveDistanceScaler is used on XACT on the // Xbox. A special value of 10000.0f is passed for thunder, which has no // attenuation if (max_dist == 10000.0f) { return 1.0f; } result = 1.0f - (distance / max_dist); if (result < 0.0f) result = 0.0f; if (result > 1.0f) result = 1.0f; return result; } #endif