Files
GTASource/game/audio/userradiotrackmanager.cpp
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

1013 lines
28 KiB
C++

#if __WIN32PC
#pragma warning(disable: 4668)
#include <windows.h>
#include <shobjidl.h> // IShellLink
#include <shlguid.h> // CLSID_ShellLink
#pragma warning(error: 4668)
#include "userradiotrackmanager.h"
#include "system/FileMgr.h"
#include "frontend/PauseMenu.h"
#include "text/messages.h"
#include "text/textfile.h"
#include "atl/map.h"
#include "atl/bitset.h"
#include "file/stream.h"
#include "system/endian.h"
#include "system/magicnumber.h"
//#include "frontend/Frontend.h"
#include "audiohardware/driver.h"
#include "audiohardware/driverutil.h"
#include "xmmintrin.h"
#include "emmintrin.h"
#if RSG_PC && !__NO_OUTPUT
namespace rage
{
XPARAM(processinstance);
}
#endif
const sysIpcPriority UserRadioTrackScanThreadPriority = PRIO_NORMAL;
const int UserMusicScanThreadCpu = 0;
// needs to align to 16
#define SecondsToStereoBytes(s) ((s&~15) * 44100 * 2 * 2)
u32 g_TrackAnalysisBufferSize = SecondsToStereoBytes(64);
s16 *g_TrackAnalysisBuffer = NULL;
bool audUserRadioTrackDatabase::Save(const WCHAR *filename) const
{
WCHAR indexFile[RAGE_MAX_PATH];
WCHAR stringsFile[RAGE_MAX_PATH];
wcscpy_s(indexFile, filename);
wcscat_s(indexFile, L".db");
wcscpy_s(stringsFile, filename);
wcscat_s(stringsFile, L".dbs");
HANDLE hIndexFile = CreateFileW(indexFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_HIDDEN, NULL);
HANDLE hStringsFile = CreateFileW(stringsFile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_HIDDEN, NULL);
#if __BANK
if((hIndexFile == INVALID_HANDLE_VALUE || hStringsFile == INVALID_HANDLE_VALUE) && PARAM_processinstance.Get())
{
Warningf("Multiple copies of game running. Unable to save usermusic database on second instance.");
return false;
}
#endif
if(audVerifyf(hIndexFile != INVALID_HANDLE_VALUE && hStringsFile != INVALID_HANDLE_VALUE, "Failed to create user track database"))
{
for(s32 i = 0; i < m_Data.GetCount(); i++)
{
DWORD bytesWritten = 0;
WriteFile(hIndexFile, &m_Data[i], sizeof(TrackData), &bytesWritten, NULL);
WriteFile(hStringsFile, m_Data[i].FileName, m_Data[i].FileNameLength * sizeof(WCHAR), &bytesWritten, NULL);
}
CloseHandle(hIndexFile);
CloseHandle(hStringsFile);
return true;
}
return false;
}
bool audUserRadioTrackDatabase::Load(const WCHAR *filename)
{
WCHAR indexFile[RAGE_MAX_PATH];
WCHAR stringsFile[RAGE_MAX_PATH];
wcscpy_s(indexFile, filename);
wcscat_s(indexFile, L".db");
wcscpy_s(stringsFile, filename);
wcscat_s(stringsFile, L".dbs");
HANDLE hIndexFile = CreateFileW(indexFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_HIDDEN, NULL);
HANDLE hStringsFile = CreateFileW(stringsFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_HIDDEN, NULL);
if(hIndexFile != INVALID_HANDLE_VALUE && hStringsFile != INVALID_HANDLE_VALUE)
{
const u32 indexSize = static_cast<u32>(GetFileSize(hIndexFile, NULL));
const u32 numEntries = indexSize / sizeof(TrackData);
if(numEntries > 0 && audVerifyf(numEntries * sizeof(TrackData) == indexSize, "Invalid user track database size; expected %u found %u", numEntries * sizeof(TrackData), indexSize))
{
m_Data.Resize(numEntries);
for(u32 i = 0; i < numEntries; i++)
{
DWORD bytesRead = 0;
ReadFile(hIndexFile, &m_Data[i], sizeof(TrackData), &bytesRead, NULL);
WCHAR *inputFileName = rage_new WCHAR[m_Data[i].FileNameLength + 1];
ReadFile(hStringsFile, inputFileName, m_Data[i].FileNameLength * sizeof(WCHAR), &bytesRead, NULL);
inputFileName[m_Data[i].FileNameLength] = L'\0';
m_Data[i].FileName = inputFileName;
}
}
CloseHandle(hIndexFile);
CloseHandle(hStringsFile);
return true;
}
return false;
}
s32 audUserRadioTrackDatabase::AddTrack(const TrackData &track)
{
m_Data.Grow() = track;
const s32 index = static_cast<s32>(m_Data.size()) - 1;
// Ensure fileNameLength is computed to simplify serialisation
if(track.FileName)
{
m_Data[index].FileNameLength = static_cast<u32>(wcslen(track.FileName));
}
else
{
m_Data[index].FileNameLength = 0;
}
return index;
}
s32 audUserRadioTrackDatabase::FindTrackIndex(const WCHAR *fileName)
{
#if 1
for(s32 i = 0; i < m_Data.GetCount(); i++)
{
if(_wcsicmp(m_Data[i].FileName, fileName) == 0)
{
return i;
}
}
return -1;
#else
if(m_SearchCache.GetNumUsed() == 0 && m_Data.GetCount() > 0)
{
for(s32 i = 0; i < m_Data.GetCount(); i++)
{
m_SearchCache.Insert(m_Data[i].FileName, i);
}
}
const s32 *res = m_SearchCache.Access(fileName);
if(res)
{
return *res;
}
return -1;
#endif
}
audUserRadioTrackManager::audUserRadioTrackManager() :
m_bInitialised(false),
m_CurrentTrack(0),
m_ForceNextTrack(-1),
m_CompleteScan(false),
m_ScanStatus(SCAN_STATE_IDLE),
m_ScanCount(0),
m_IsCompleteScanning(false),
m_nTracksToScan(0)
{
m_CurrentDatabase = NULL;
m_NewDatabase = NULL;
}
audUserRadioTrackManager::~audUserRadioTrackManager()
{
}
void audUserRadioTrackManager::Shutdown()
{
if(m_CurrentDatabase)
{
delete m_CurrentDatabase;
m_CurrentDatabase = NULL;
}
}
bool audUserRadioTrackManager::Initialise()
{
audMediaReader::InitClass();
CFileMgr::SetUserDataDir("User Music\\", true);
m_bInitialised = LoadFileLookup();
for(s32 i = 0; i < TRACK_FORMAT_COUNT; i++)
{
m_bIsDecoderAvailable[i] = audMediaReader::IsFormatAvailable((USER_TRACK_FORMAT)i);
}
m_ScanStatus = SCAN_STATE_UNSCANNED;
return m_bInitialised;
}
void audUserRadioTrackManager::PostLoad()
{
if(GetRadioMode() == USERRADIO_PLAY_MODE_SEQUENTIAL && GetNumTracks() > 0)
{
const s32 numTracks = GetNumTracks();
// The track stored in history is always ahead of the audible track when the save is made, so compensate
m_CurrentTrack = (static_cast<s32>(m_History[0]) + numTracks - 1) % numTracks;
}
}
const WCHAR *audUserRadioTrackManager::GetDataFileLocation() const
{
const char *appDataDirectory = CFileMgr::GetAppDataRootDirectory();
const int nChars = MultiByteToWideChar(CP_UTF8, 0, appDataDirectory, -1, NULL, 0);
size_t newLength = nChars + 2 + wcslen(USER_TRACK_LOOKUP_FILENAME);
WCHAR *pwcsUserDir = rage_new WCHAR[newLength];
MultiByteToWideChar(CP_UTF8, 0, appDataDirectory, -1, (LPWSTR)pwcsUserDir, nChars);
if(appDataDirectory[strlen(appDataDirectory) - 1] != '\\')
{
wcscat_s(pwcsUserDir, newLength, L"\\");
}
wcscat_s(pwcsUserDir, newLength, USER_TRACK_LOOKUP_FILENAME);
return pwcsUserDir;
}
void audUserRadioTrackManager::UpdateScanningUI(const bool showMessageOnCompletion /* = true */)
{
if(m_NewDatabase)
{
if(m_CurrentDatabase && m_CurrentDatabase != m_NewDatabase)
{
delete m_CurrentDatabase;
}
m_CurrentDatabase = m_NewDatabase;
m_NewDatabase = NULL;
m_ScanStatus = SCAN_STATE_IDLE;
if(showMessageOnCompletion && !CPauseMenu::IsActive())
{
char *string = TheText.Get("MO_UR_COMPLETEDSCAN");
CNumberWithinMessage numberArray[1];
numberArray[0].Set(GetNumTracks());
CHelpMessage::SetMessageText(HELP_TEXT_SLOT_STANDARD, string, numberArray, 1);
audDisplayf("Submitting scan complete message");
}
}
// If we succeeded without building a database then there were no tracks to find, so go back to idle.
if(m_ScanStatus == SCAN_STATE_SUCCEEDED)
{
m_ScanStatus = SCAN_STATE_IDLE;
}
}
bool audUserRadioTrackManager::LoadFileLookup()
{
if(m_CurrentDatabase)
{
delete m_CurrentDatabase;
}
m_CurrentDatabase = rage_new audUserRadioTrackDatabase();
const WCHAR *dataFileLocation = GetDataFileLocation();
const bool ret = m_CurrentDatabase->Load(dataFileLocation);
delete[] dataFileLocation;
CFileMgr::SetDir("");
return ret;
}
u32 audUserRadioTrackManager::GetTrackDuration(const s32 trackId) const
{
if(!m_CurrentDatabase || trackId >= m_CurrentDatabase->GetNumTracks())
return ~0U;
return m_CurrentDatabase->GetTrack(trackId).DurationMs;
}
const char *audUserRadioTrackManager::GetTrackTitle(const s32 trackId) const
{
if(!m_CurrentDatabase || trackId >= m_CurrentDatabase->GetNumTracks())
return NULL;
return m_CurrentDatabase->GetTrack(trackId).Title;
}
const char *audUserRadioTrackManager::GetTrackArtist(const s32 trackId) const
{
if(!m_CurrentDatabase || trackId >= m_CurrentDatabase->GetNumTracks())
return NULL;
return m_CurrentDatabase->GetTrack(trackId).Artist;
}
u32 audUserRadioTrackManager::GetStartOffsetMs(const s32 trackId) const
{
if(!m_CurrentDatabase || trackId >= m_CurrentDatabase->GetNumTracks())
return ~0U;
u32 startOffset = m_CurrentDatabase->GetTrack(trackId).SkipStartMs;
if(startOffset < 100 || GetRadioMode() != USERRADIO_PLAY_MODE_RADIO)
{
return 0;
}
return startOffset - 100;
}
u32 audUserRadioTrackManager::GetTrackPostRoll(const s32 trackId) const
{
if(!m_CurrentDatabase || trackId >= m_CurrentDatabase->GetNumTracks())
return 0;
u32 skipEnd = m_CurrentDatabase->GetTrack(trackId).SkipEndMs;
if(skipEnd < 100 || GetRadioMode() != USERRADIO_PLAY_MODE_RADIO)
{
return 0;
}
return skipEnd - 100;
}
f32 audUserRadioTrackManager::ComputeTrackMakeUpGain(const s32 trackId) const
{
if(!m_CurrentDatabase || trackId >= m_CurrentDatabase->GetNumTracks())
{
return 0.f;
}
const u32 loudness = m_CurrentDatabase->GetTrack(trackId).Loudness;
if(loudness == 0 || GetRadioMode() != USERRADIO_PLAY_MODE_RADIO)
{
return 0.f;
}
// we have valid peak/rms info
const f32 peakVolumeDb = audDriverUtil::ComputeDbVolumeFromLinear((loudness &0xffff)/65536.f);
const f32 rmsVolumeDb = audDriverUtil::ComputeDbVolumeFromLinear((loudness >>16)/65536.f);
// aim for -9dB RMS but don't go allow peak volume to go above 4dB and don't boost more than +8dB
return Clamp(Min(4.f - peakVolumeDb, -9.f - rmsVolumeDb), -3.f, 8.f);
}
const WCHAR *audUserRadioTrackManager::GetFileName(const s32 trackId) const
{
if(!m_CurrentDatabase || trackId >= m_CurrentDatabase->GetNumTracks())
return NULL;
return m_CurrentDatabase->GetTrack(trackId).FileName;
}
const WCHAR *audUserRadioTrackManager::GetLnkTarget(const WCHAR *lnk)
{
HRESULT hr;
IShellLinkW *pLink;
IPersistFile *ppf;
WIN32_FIND_DATAW wfd;
WCHAR *pPath;
if(FAILED(hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkW, (LPVOID*)&pLink)))
{
return NULL;
}
else
{
// now need to get the IPersistFile interface
if (FAILED(pLink->QueryInterface(IID_IPersistFile, (void**)&ppf)))
{
pLink->Release();
return NULL;
}
else
{
if(FAILED(ppf->Load(lnk, STGM_READ)))
{
ppf->Release();
pLink->Release();
return NULL;
}
else
{
pPath = rage_new WCHAR[MAX_PATH];
HRESULT hr = pLink->GetPath(pPath, MAX_PATH, (WIN32_FIND_DATAW*)&wfd, SLGP_SHORTPATH);
ppf->Release();
pLink->Release();
if(SUCCEEDED(hr))
return pPath;
else
return NULL;
}
}
}
}
USER_TRACK_FORMAT audUserRadioTrackManager::GetFileFormat(const WCHAR *fileName)
{
return audMediaReader::GetFileFormat(fileName);
}
s32 audUserRadioTrackManager::GetNextTrack()
{
s32 track = -1;
if(!GetNumTracks())
return -1;
switch(GetRadioMode())
{
case USERRADIO_PLAY_MODE_RANDOM:
case USERRADIO_PLAY_MODE_RADIO:
{
if(m_ForceNextTrack != -1) // this means the user selected to play the previous track
{
track = m_ForceNextTrack;
m_ForceNextTrack = -1;
return track;
}
for(s32 i = 0; i < 50; i++) // try 50 times, then just go sequential to avoid risk of inf loop if something goes wrong
{
track = audEngineUtil::GetRandomNumberInRange(0, GetNumTracks()-1);
if(!IsTrackInHistory(track))
{
m_CurrentTrack = track;
return track;
}
}
// just do sequential
track = m_CurrentTrack = (m_CurrentTrack + 1) % GetNumTracks();
return track;
}
case USERRADIO_PLAY_MODE_SEQUENTIAL:
{
if(m_ForceNextTrack != -1) // this means the user selected to play the previous track
{
m_CurrentTrack = track = m_ForceNextTrack;
m_ForceNextTrack = -1;
return track;
}
m_CurrentTrack = (m_CurrentTrack + 1) % GetNumTracks();
// Preserve current track in history for save/load
m_History[0] = static_cast<u32>(m_CurrentTrack);
return m_CurrentTrack;
}
}
return -1;
}
#pragma warning(disable: 4748)
DECLARE_THREAD_FUNC(audUserRadioTrackManager::RebuildFileDatabaseCB)
{
audUserRadioTrackManager *_this = (audUserRadioTrackManager*)ptr;
_this->RebuildFileDatabase();
}
void audUserRadioTrackManager::RebuildFileDatabase()
{
char *userDir;
u32 count;
sysPerformanceTimer timer("MediaScan Timer");
timer.Start();
if(m_CompleteScan)
{
m_IsCompleteScanning = true;
}
// catalogue all user files from the specified source directory
Displayf("audUserRadioTrackManager: rebuilding user music database ... (source dir: %s)", USER_TRACK_PATH);
m_AbortRequested = false;
// initialise COM for this thread (required to resolve shortcuts)
CoInitialize(NULL);
audUserRadioTrackDatabase *database = rage_new audUserRadioTrackDatabase();
CFileMgr::SetAppDataDir("User Music\\", true);
CFileMgr::SetUserDataDir("User Music\\", true);
// grab the my documents directory
char *curDir = CFileMgr::GetCurrDirectory();
userDir = rage_new char[lstrlen(curDir)+1];
lstrcpy(userDir, curDir);
const int nChars = MultiByteToWideChar(CP_UTF8, 0, userDir, -1, NULL, 0);
const WCHAR *pwcsUserDir = rage_new WCHAR[nChars];
MultiByteToWideChar(CP_UTF8, 0, userDir, -1, (LPWSTR)pwcsUserDir, nChars);
delete[] userDir;
// pass in full path to CatalogueDirectory() to ensure we can correctly open files
// regardless of current directory
count = CatalogueDirectory(pwcsUserDir, database, 0);
delete [] pwcsUserDir;
Displayf("Finished - found %d files\n", count);
CFileMgr::SetAppDataDir("User Music\\", true);
// we need access to the file name db
CFileMgr::SetAppDataDir("User Music\\", true);
m_ScanCount = 0;
m_nTracksToScan = database->GetNumTracks();
for(s32 i=0; i < database->GetNumTracks(); i++)
{
audUserRadioTrackDatabase::TrackData &trackData = database->GetTrack(i);
// If we already have a populated database, check to see if we can skip the complete scan and re-use the data for this track.
if(m_CurrentDatabase != NULL && m_CurrentDatabase != database)
{
const s32 oldIndex = m_CurrentDatabase->FindTrackIndex(trackData.FileName);
if(oldIndex != -1)
{
const audUserRadioTrackDatabase::TrackData &oldTrackData = m_CurrentDatabase->GetTrack(oldIndex);
trackData.Loudness = oldTrackData.Loudness;
trackData.SkipStartMs = oldTrackData.SkipStartMs;
trackData.SkipEndMs = oldTrackData.SkipEndMs;
safecpy(trackData.Artist, oldTrackData.Artist);
safecpy(trackData.Title, oldTrackData.Title);
if(trackData.Loudness != 0 || trackData.SkipEndMs != 0 || trackData.SkipStartMs != 0)
{
naDisplayf("Reusing data for %S", trackData.FileName);
}
}
}
if(!m_AbortRequested && trackData.Loudness == 0 && trackData.SkipStartMs == 0 && trackData.SkipEndMs == 0)
{
audMediaReader* pAudioReader = rage_new audMediaReader();
Assert(pAudioReader != NULL);
if(pAudioReader)
{
if(pAudioReader->Open(trackData.FileName, 0, true))
{
if(pAudioReader->IsFileOpen())
{
if(m_CompleteScan)
{
AnalyzeTrack(trackData.FileName, pAudioReader, trackData.DurationMs, trackData.SkipStartMs, trackData.SkipEndMs, trackData.Loudness);
}
safecpy(trackData.Artist, pAudioReader->GetArtist());
safecpy(trackData.Title, pAudioReader->GetTitle());
pAudioReader->Close();
}
else
{
naErrorf("Failed to analyze: %S", trackData.FileName);
trackData.DurationMs = ~0U;
}
}
else
{
naErrorf("Failed to analyze: %S", trackData.FileName);
trackData.DurationMs = ~0U;
}
}
delete pAudioReader;
}
m_ScanCount++;
}
m_IsCompleteScanning = false;
if(g_TrackAnalysisBuffer)
{
audDriver::FreeVirtual(g_TrackAnalysisBuffer);
g_TrackAnalysisBuffer = NULL;
}
CoUninitialize();
if(m_AbortRequested)
{
Displayf("Aborted media scan");
}
else
{
const WCHAR *dataFileLocation = GetDataFileLocation();
database->Save(dataFileLocation);
delete [] dataFileLocation;
}
timer.Stop();
#if !__NO_OUTPUT
timer.Print();
#endif
// A scan that finishes immediately leads to what looks like a UI bug, as we get a frame or two of 'Scanning'. Since the scan time is totally unpredictable (until we've followed
// the shortcuts we don't know how many files we'll be looking through) the most robust fix is to apply a minimum time that we'll remain in a 'Scanning' state.
u32 elapsedTimeMs = (u32)timer.GetElapsedTimeMS();
enum { minScanTime = 1500 };
if(elapsedTimeMs < minScanTime)
{
sysIpcSleep(minScanTime - elapsedTimeMs);
}
if(!m_AbortRequested)
{
m_NewDatabase = database;
}
SetStatus(SCAN_STATE_SUCCEEDED);
return;
}
bool audUserRadioTrackManager::IsFileLink(const WCHAR *pDir)
{
const WCHAR *ext = wcsrchr(pDir, L'.');
if(!ext || _wcsicmp(ext, L".lnk"))
{
return false;
}
return true;
}
bool audUserRadioTrackManager::PossiblyAddTrack(const WCHAR *target, audUserRadioTrackDatabase *database)
{
USER_TRACK_FORMAT format = GetFileFormat(target);
s32 duration = 0;
if(format != TRACK_FORMAT_UNKNOWN && audMediaReader::IsValidFile(target, duration))
{
audUserRadioTrackDatabase::TrackData track;
track.DurationMs = static_cast<u32>(duration);
track.format = format;
track.FileName = rage_new WCHAR[wcslen(target) + 1];
lstrcpyW((LPWSTR)track.FileName, target);
database->AddTrack(track);
return true;
}
return false;
}
void audUserRadioTrackManager::AnalyzeBuffer(const s32 samplesRead, const u32 rmsWindowSize, s32 &firstNonZeroSample, s32 &lastNonZeroSample, f32 &maxPeak, f32 &maxRms)
{
const s32 silenceThresholdStart = 413; // around -38dB
const s32 silenceThresholdEnd = 1038; // around -30dB
bool foundContent = false;
lastNonZeroSample = 0;
firstNonZeroSample = 0;
for(s32 sampleIndex = 0; sampleIndex < samplesRead; sampleIndex += rmsWindowSize)
{
// compute a sample accurate start offset - detect the first sample louder than our threshold
for(u32 sampleSubIndex = 0; sampleSubIndex < rmsWindowSize && sampleIndex + sampleSubIndex < samplesRead; sampleSubIndex += 2)
{
s32 maxSample = Max(Abs(g_TrackAnalysisBuffer[sampleIndex+sampleSubIndex]),Abs(g_TrackAnalysisBuffer[sampleIndex+sampleSubIndex+1]));
if(maxSample > silenceThresholdEnd)
{
// divide by two since we're dealing with stereo data
lastNonZeroSample = (sampleIndex+sampleSubIndex);
}
if(!foundContent && maxSample > silenceThresholdStart)
{
foundContent = true;
}
else if(!foundContent)
{
// increment only once per two samples since we're dealing with stereo data
firstNonZeroSample++;
}
}
if(sampleIndex + rmsWindowSize < samplesRead)
{
// compute RMS and Peak volume - window size defined by rmsWindowSize above
__m128 runningTotal = _mm_setzero_ps();
const __m128 oneOverDivisor = _mm_set_ps1(1.f / 32767.f);
const __m128 oneOverMeanDivisor = _mm_set_ps1(1.f / (f32)rmsWindowSize);
__m128 maxPeakV = _mm_setzero_ps();
for(u32 sampleSubIndex = 0; sampleSubIndex < rmsWindowSize; sampleSubIndex += 8)
{
const __m128i inputInts = _mm_load_si128((__m128i*)&g_TrackAnalysisBuffer[sampleSubIndex + sampleIndex]);
// unpack 8 16 bit ints into 32bit ints (splitting into two vectors)
const __m128i inputInts1 = _mm_srai_epi32(_mm_unpacklo_epi16(inputInts,inputInts), 16);
const __m128i inputInts2 = _mm_srai_epi32(_mm_unpackhi_epi16(inputInts,inputInts), 16);
// convert to float, square and sum
__m128 samples1 = _mm_mul_ps(oneOverDivisor, _mm_cvtepi32_ps(inputInts1));
__m128 samples2 = _mm_mul_ps(oneOverDivisor, _mm_cvtepi32_ps(inputInts2));
samples1 = _mm_mul_ps(samples1,samples1);
samples2 = _mm_mul_ps(samples2,samples2);
const __m128 maxMask1 = _mm_cmpgt_ps(samples1, samples2);
const __m128 maxMask2 = _mm_cmpge_ps(samples2,samples1);
const __m128 maxSamples12 = _mm_or_ps(_mm_and_ps(samples1, maxMask1),_mm_and_ps(samples2,maxMask2));
const __m128 maxMask3 = _mm_cmpgt_ps(maxSamples12, maxPeakV);
const __m128 maxMask4 = _mm_cmpge_ps(maxPeakV,maxSamples12);
maxPeakV = _mm_or_ps(_mm_and_ps(maxSamples12, maxMask3),_mm_and_ps(maxPeakV,maxMask4));
runningTotal = _mm_add_ps(runningTotal,
_mm_add_ps(_mm_mul_ps(oneOverMeanDivisor,samples1),_mm_mul_ps(oneOverMeanDivisor,samples2)));
}
// perform horizontal add
__m128 meanSquared = _mm_add_ss(
_mm_add_ss(_mm_shuffle_ps(runningTotal, runningTotal, _MM_SHUFFLE(0,0,0,0)), _mm_shuffle_ps(runningTotal, runningTotal, _MM_SHUFFLE(1,1,1,1))),
_mm_add_ss(_mm_shuffle_ps(runningTotal, runningTotal, _MM_SHUFFLE(2,2,2,2)), _mm_shuffle_ps(runningTotal, runningTotal, _MM_SHUFFLE(3,3,3,3)))
);
// and horizontal max
const __m128 x = _mm_shuffle_ps(maxPeakV, maxPeakV, _MM_SHUFFLE(0,0,0,0));
const __m128 y = _mm_shuffle_ps(maxPeakV, maxPeakV, _MM_SHUFFLE(1,1,1,1));
const __m128 z = _mm_shuffle_ps(maxPeakV, maxPeakV, _MM_SHUFFLE(2,2,2,2));
const __m128 w = _mm_shuffle_ps(maxPeakV, maxPeakV, _MM_SHUFFLE(3,3,3,3));
const __m128 xmask = _mm_cmpgt_ss(x,y);
const __m128 ymask = _mm_cmpge_ss(y,x);
const __m128 zmask = _mm_cmpgt_ss(z,w);
const __m128 wmask = _mm_cmpge_ss(w,z);
const __m128 xyMax = _mm_or_ps(_mm_and_ps(xmask,x),_mm_and_ps(ymask,y));
const __m128 zwMax = _mm_or_ps(_mm_and_ps(zmask,z),_mm_and_ps(wmask,w));
const __m128 xyMask = _mm_cmpgt_ss(xyMax,zwMax);
const __m128 zwMask = _mm_cmpge_ss(zwMax,xyMax);
const __m128 maxVal = _mm_or_ps(_mm_and_ps(xyMask,xyMax),_mm_and_ps(zwMask,zwMax));
f32 maxSampleValue;
f32 rms;
_mm_store_ss(&maxSampleValue,maxVal);
_mm_store_ss(&rms,meanSquared);
maxPeak = Max(maxPeak,maxSampleValue);
maxRms = Max(maxRms,rms);
}
}
}
void audUserRadioTrackManager::AnalyzeTrack(const WCHAR *trackPath, audMediaReader *pTrack, u32 &duration, u32 &skipStart, u32 &skipEnd, u32 &gain)
{
Assert(pTrack);
sysPerformanceTimer analysisTimer("Analysis");
analysisTimer.Start();
duration = pTrack->GetStreamLengthMs();
if(!g_TrackAnalysisBuffer)
{
g_TrackAnalysisBuffer = (s16*)audDriver::AllocateVirtual(g_TrackAnalysisBufferSize);
if(!g_TrackAnalysisBuffer)
{
return;
}
}
// decode the start and look for silence
s32 bytesRead = pTrack->FillBuffer(g_TrackAnalysisBuffer,g_TrackAnalysisBufferSize);
const u32 rmsWindowSize = 65536;
s32 samplesRead = (bytesRead>>1) & (~(rmsWindowSize-1));
s32 startSamplesToSkip = 0, endSamplesToSkip = 0;
f32 maxPeak = 0.f;
f32 maxRms = 0.f;
AnalyzeBuffer(samplesRead,rmsWindowSize,startSamplesToSkip,endSamplesToSkip,maxPeak,maxRms);
const f32 invSamplesPerMs = 1.f / (f32)(pTrack->GetSampleRate()/1000.f);
if(duration > 22000)
{
audMediaReader *pTrackForEnd;
// windows media doesnt seem to like seeking an already open file, open a new reader
pTrackForEnd = rage_new audMediaReader();
if(pTrackForEnd->Open(trackPath, duration - 20000, true))
{
pTrackForEnd->SetMsStartOffset(duration - 20000);
s32 dummyStartSamplesToSkip=0;
// decode the last 20 seconds
bytesRead = pTrackForEnd->FillBuffer(g_TrackAnalysisBuffer, g_TrackAnalysisBufferSize);
s32 samplesRead = (bytesRead>>1);// & (~(rmsWindowSize-1));
s32 silenceStartPoint = 0;
AnalyzeBuffer(samplesRead, rmsWindowSize, dummyStartSamplesToSkip, silenceStartPoint, maxPeak, maxRms);
// silence start point is already samples/2, divide samplesRead by 2 to convert to stereo samples
endSamplesToSkip = (samplesRead>>1) - (silenceStartPoint>>1);
pTrackForEnd->Close();
}
delete pTrackForEnd;
}
else
{
endSamplesToSkip = 0;
}
skipStart = (u32)(startSamplesToSkip * invSamplesPerMs);
skipEnd = (u32)(endSamplesToSkip * invSamplesPerMs);
//
maxPeak = Min(1.f,sqrtf(maxPeak));
maxRms = Min(1.f,sqrtf(maxRms));
const u32 packedPeak = static_cast<u32>(65535U * maxPeak);
const u32 packedRms = static_cast<u32>(65535U * maxRms);
gain = (packedPeak | (packedRms<<16));
analysisTimer.Stop();
Displayf("Analyzed %s - %s in %f: %.2fs pre-roll, %.2fs post-roll, Maximum RMS: %.2fdB Peak: %.2fdB", pTrack->GetArtist(), pTrack->GetTitle(), analysisTimer.GetTimeMS()/1000.f, skipStart / 1000.f, skipEnd / 1000.f, audDriverUtil::ComputeDbVolumeFromLinear(maxRms),audDriverUtil::ComputeDbVolumeFromLinear(maxPeak));
}
u32 audUserRadioTrackManager::CatalogueDirectory(const WCHAR *srcDir, audUserRadioTrackDatabase *database, s32 level)
{
if(level >= MAX_SEARCH_LEVELS)
{
Errorf("audUserRadioTrackManager::CatalogueDirectory: skipping \"%S\" - recursion level too deep! (%d)", srcDir, level);
return 0;
}
size_t bufLen = wcslen(srcDir) + 6;
WCHAR *searchPath = rage_new WCHAR[bufLen];
lstrcpyW(searchPath, srcDir);
lstrcatW(searchPath, L"\\*.*");
WIN32_FIND_DATAW findData;
unsigned int count = 0;
HANDLE hFind = FindFirstFileW(searchPath, &findData);
delete[] searchPath;
if(hFind != INVALID_HANDLE_VALUE)
{
BOOL bActive = true;
while(bActive && !m_AbortRequested)
{
if(findData.cFileName[0] == L'.')
{
;// skip '.' and '..'
}
else if(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
WCHAR *newPath = rage_new WCHAR[wcslen(srcDir) + wcslen(findData.cFileName) + 2];
lstrcpyW(newPath, srcDir);
lstrcatW(newPath, L"\\");
lstrcatW(newPath, findData.cFileName);
count += CatalogueDirectory(newPath, database, level+1);
delete[] newPath;
}
else
{
if(!level && IsFileLink(findData.cFileName))
{
// it's a file link in the top level directory so we'll try to resolve it
Displayf("%S appears to be a shortcut...", findData.cFileName);
WCHAR *lnk = rage_new WCHAR[wcslen(findData.cFileName) + wcslen(srcDir) + 2];
lstrcpyW(lnk, srcDir);
lstrcatW(lnk, L"\\");
lstrcatW(lnk, findData.cFileName);
const WCHAR *target = GetLnkTarget(lnk);
delete[] lnk;
if(target)
{
Displayf("Resolved %S to %S", findData.cFileName, target);
DWORD dwAttribs = GetFileAttributesW(target);
if(dwAttribs == INVALID_FILE_ATTRIBUTES)
{
Warningf("invalid shortcut.");
}
else
{
if(dwAttribs & FILE_ATTRIBUTE_DIRECTORY)
{
count += CatalogueDirectory(target, database, level+1);
}
else
{
if(PossiblyAddTrack(target, database))
{
count++;
}
}
}
delete[] target;
} // if(target)
}
else if(GetFileFormat(findData.cFileName) != TRACK_FORMAT_UNKNOWN)
{
WCHAR *fileName = rage_new WCHAR[wcslen(findData.cFileName) + wcslen(srcDir) + 2];
lstrcpyW(fileName, srcDir);
lstrcatW(fileName, L"\\");
lstrcatW(fileName,findData.cFileName);
if(PossiblyAddTrack(fileName, database))
{
count++;
}
delete[] fileName;
}
}
bActive = FindNextFileW(hFind, &findData);
} // while(bActive)
FindClose(hFind);
} // if(hFind != INVALID_HANDLE_VALUE)
return count;
}
// if idle then kicks off a new scan and returns false,
// otherwise returns false when busy and true when scan is finished. use GetScanStatus()
// to check if the scan was successful or not.
bool audUserRadioTrackManager::StartMediaScan(bool completeScan)
{
bool ret = false;
switch(m_ScanStatus)
{
case SCAN_STATE_FAILED:
case SCAN_STATE_UNSCANNED:
case SCAN_STATE_IDLE:
// kick off new scan
m_ScanStatus = SCAN_STATE_SCANNING;
m_CompleteScan = completeScan;
m_Thread = sysIpcCreateThread(audUserRadioTrackManager::RebuildFileDatabaseCB, this, 65536, UserRadioTrackScanThreadPriority, "[GTA Audio] User Music Scan Thread", false, UserMusicScanThreadCpu);
Assert(m_Thread != sysIpcThreadIdInvalid && "Couldn't create User Music Scan thread!");
if(m_Thread == sysIpcThreadIdInvalid)
{
m_ScanStatus = SCAN_STATE_FAILED;
}
else
{
//sysIpcResumeThread(m_Thread);
ret = true;
}
break;
case SCAN_STATE_SCANNING:
case SCAN_STATE_SUCCEEDED:
break;
}
return ret;
}
void audUserRadioTrackManager::AbortMediaScan()
{
m_AbortRequested = true;
}
USERRADIO_PLAYMODE audUserRadioTrackManager::GetRadioMode() const
{
if(!HasTracks() || audRadioStation::IsInRecoveryMode())
return USERRADIO_PLAY_MODE_RADIO;
return (USERRADIO_PLAYMODE)CPauseMenu::GetMenuPreference(PREF_UR_PLAYMODE);
}
void audUserRadioTrackManager::AddTrackToHistory(s32 track)
{
m_History.AddToHistory(static_cast<u32>(track));
}
bool audUserRadioTrackManager::IsTrackInHistory(s32 track)
{
if(GetNumTracks() < 3)
{
return false;
}
// Limit history length to keep two tracks available
return m_History.IsInHistory(static_cast<u32>(track), GetNumTracks() - 2);
}
#endif