winamp/Src/Plugins/Library/ml_rg/replaygain.cpp
2024-09-24 14:54:57 +02:00

370 lines
10 KiB
C++

#include "main.h"
#include <math.h>
#include "../ReplayGainAnalysis/gain_analysis.h"
#include "api__ml_rg.h"
#include <shlwapi.h>
#include <strsafe.h>
#include <locale.h>
#pragma intrinsic(fabs)
static inline float fastmax(float x, float a)
{
x -= a;
x += (float)fabs(x);
x *= 0.5f;
x += a;
return (x);
}
static HMODULE rgLib = 0;
typedef int(*INITGAINANALYSIS)(void *context, long samplefreq);
static INITGAINANALYSIS InitGainAnalysis = 0;
typedef int(*ANALYZESAMPLES)(void *context, const float * left_samples, const float * right_samples, size_t num_samples, int num_channels);
static ANALYZESAMPLES AnalyzeSamples = 0;
typedef int(*RESETSAMPLEFREQUENCY)(void *context, long samplefreq);
static RESETSAMPLEFREQUENCY ResetSampleFrequency = 0;
typedef float(*GETTITLEGAIN)(void *context);
static GETTITLEGAIN GetTitleGain = 0;
typedef float(*GETALBUMGAIN)(void *context);
static GETALBUMGAIN GetAlbumGain = 0;
typedef void *(* CREATERGCONTEXT)();
static CREATERGCONTEXT CreateRGContext = 0;
typedef void(*FREERGCONTEXT)(void *context);
static FREERGCONTEXT FreeRGContext = 0;
void LoadRG()
{
if (rgLib)
return ;
wchar_t path[MAX_PATH] = {0};
PathCombineW(path, (wchar_t*)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETSHAREDDLLDIRECTORYW), L"ReplayGainAnalysis.dll");
rgLib = LoadLibraryW(path);
if (rgLib)
{
InitGainAnalysis = (INITGAINANALYSIS)GetProcAddress(rgLib, "WAInitGainAnalysis");
AnalyzeSamples = (ANALYZESAMPLES)GetProcAddress(rgLib, "WAAnalyzeSamples");
GetTitleGain = (GETTITLEGAIN)GetProcAddress(rgLib, "WAGetTitleGain");
ResetSampleFrequency = (RESETSAMPLEFREQUENCY)GetProcAddress(rgLib, "WAResetSampleFrequency");
GetAlbumGain = (GETALBUMGAIN)GetProcAddress(rgLib, "WAGetAlbumGain");
CreateRGContext = (CREATERGCONTEXT)GetProcAddress(rgLib, "WACreateRGContext");
FreeRGContext = (FREERGCONTEXT)GetProcAddress(rgLib, "WAFreeRGContext");
}
}
void *CreateRG()
{
LoadRG();
return CreateRGContext();
}
void DestroyRG(void *context)
{
FreeRGContext(context);
}
#define CHUNKSIZE 16384
static void CalculateRG_float(void *context, ifc_audiostream *decoder, AudioParameters *parameters, wchar_t track_gain[64], wchar_t track_peak[64], ProgressCallback *callback, int *killSwitch, float &albumPeak)
{
float data[2*CHUNKSIZE] = {0};
float right[CHUNKSIZE] = {0};
float peak = 0;
if (parameters->channels > 2)
{
char titleStr[32] = {0};
MessageBoxA(GetDialogBoxParent(),
WASABI_API_LNGSTRING(IDS_TOO_MANY_CHANNELS),
WASABI_API_LNGSTRING_BUF(IDS_REPLAYGAIN,titleStr,32),
MB_OK);
decodeFile->CloseAudio(decoder);
return ;
}
ResetSampleFrequency(context, parameters->sampleRate);
if (callback) callback->InformSize((parameters->sizeBytes == -1) ? 0 : parameters->sizeBytes);
while (1)
{
if (*killSwitch)
{
decodeFile->CloseAudio(decoder);
return ;
}
int error=0;
size_t bytesRead = decoder->ReadAudio((void *)data, sizeof(data), killSwitch, &error);
if (*killSwitch)
{
decodeFile->CloseAudio(decoder);
return ;
}
else if (error)
{
break;
}
if (callback) callback->Progress(bytesRead);
size_t samples = bytesRead / sizeof(*data);
if (!samples)
break;
for (size_t i = 0;i != samples;i++)
{
peak = fastmax(peak, (float)fabs(data[i]));
data[i] *= 32768.0f;
}
albumPeak = fastmax(peak, albumPeak);
if (parameters->channels == 1)
AnalyzeSamples(context, data, NULL, samples, 1);
else
{
size_t samples2 = samples / 2;
for (size_t i = 0;i != samples2;i++)
{
data[i] = data[i * 2];
right[i] = data[i * 2 + 1];
}
AnalyzeSamples(context, data, right, samples2, 2);
}
}
decodeFile->CloseAudio(decoder);
float gain = GetTitleGain(context);
if (gain != GAIN_NOT_ENOUGH_SAMPLES)
{
_locale_t C_locale = WASABI_API_LNG->Get_C_NumericLocale();
_snwprintf_l(track_gain, 64, L"%-+.2f dB", C_locale, gain);
_snwprintf_l(track_peak, 64, L"%-.9f", C_locale, peak);
}
}
static void FillFloat(float *left, float *right, void *samples, size_t bps, size_t numSamples, size_t numChannels, float &peak, float &albumPeak, float gain)
{
switch (bps)
{
case 8:
{
unsigned __int8 *samples8 = (unsigned __int8 *)samples;
size_t t = 0;
for (size_t x = 0; x != numSamples; x ++)
{
left[x] = (float)(samples8[t++] - 128) * 256.0f * gain;
if (numChannels == 2)
{
right[x] = (float)(samples8[t++] - 128) * 256.0f* gain;
}
else
right[x] = left[x];
peak = fastmax(peak, (float)fabs(left[x]));
peak = fastmax(peak, (float)fabs(right[x]));
albumPeak=fastmax(albumPeak, peak);
}
}
break;
case 16:
{
short *samples16 = (short *)samples;
size_t t = 0;
if (numChannels == 1)
{
for (size_t x = 0; x != numSamples; x ++)
{
left[x] = (float)samples16[t++] * gain;
right[x] = left[x];
peak = fastmax(peak, (float)fabs(left[x]));
albumPeak=fastmax(albumPeak, peak);
}
}
else if (numChannels == 2)
{
for (size_t x = 0; x != numSamples; x ++)
{
left[x] = (float)samples16[t++] * gain ;
right[x] = (float)samples16[t++] * gain;
peak = fastmax(peak, (float)fabs(left[x]));
peak = fastmax(peak, (float)fabs(right[x]));
albumPeak=fastmax(albumPeak, peak);
}
}
}
break;
case 24:
{
unsigned __int8 *samples8 = (unsigned __int8 *)samples;
for (size_t x = 0; x != numSamples; x ++)
{
long temp = (((long)samples8[0]) << 8);
temp = temp | (((long)samples8[1]) << 16);
temp = temp | (((long)samples8[2]) << 24);
left[x] = (float)temp* gain / 65536.0f;
samples8 += 3;
if (numChannels == 2)
{
temp = (((long)samples8[0]) << 8);
temp = temp | (((long)samples8[1]) << 16);
temp = temp | (((long)samples8[2]) << 24);
right[x] = (float)temp* gain / 65536.0f;
samples8 += 3;
}
else
right[x] = left[x];
peak = fastmax(peak, (float)fabs(left[x]));
peak = fastmax(peak, (float)fabs(right[x]));
albumPeak=fastmax(albumPeak, peak);
}
}
break;
}
}
#undef CHUNKSIZE
#define CHUNKSIZE 4096
static void CalculateRG_pcm(void *context, ifc_audiostream *decoder, AudioParameters *parameters, wchar_t track_gain[64], wchar_t track_peak[64], ProgressCallback *callback, int *killSwitch, float &albumPeak)
{
char data[4*2*CHUNKSIZE] = {0};
float left[CHUNKSIZE] = {0};
float right[CHUNKSIZE] = {0};
float peak = 0;
if (parameters->channels > 2)
{
char titleStr[32];
MessageBoxA(GetDialogBoxParent(),
WASABI_API_LNGSTRING(IDS_TOO_MANY_CHANNELS),
WASABI_API_LNGSTRING_BUF(IDS_REPLAYGAIN,titleStr,32),
MB_OK);
decodeFile->CloseAudio(decoder);
return ;
}
int padded_bits = (parameters->bitsPerSample + 7) & (~7);
albumPeak *= 32768.0f;
ResetSampleFrequency(context, parameters->sampleRate);
if (callback) callback->InformSize((parameters->sizeBytes == -1) ? 0 : parameters->sizeBytes);
while (1)
{
if (*killSwitch)
{
decodeFile->CloseAudio(decoder);
return ;
}
int error=0;
size_t bytesRead = decoder->ReadAudio((void *)data, 4096 * parameters->channels * (padded_bits / 8), killSwitch, &error);
if (*killSwitch)
{
decodeFile->CloseAudio(decoder);
return ;
}
else if (error)
{
break;
}
if (callback) callback->Progress(bytesRead);
size_t samples = bytesRead / (padded_bits / 8);
if (!samples)
break;
FillFloat(left, right, data, padded_bits, samples / parameters->channels, parameters->channels, peak, albumPeak, (float)pow(2., (double)(padded_bits - parameters->bitsPerSample)));
size_t samples2 = samples / 2;
AnalyzeSamples(context, left, right, samples2, 2);
}
decodeFile->CloseAudio(decoder);
float gain = GetTitleGain(context);
if (gain != GAIN_NOT_ENOUGH_SAMPLES)
{
StringCchPrintfW(track_gain, 64, L"%-+.2f dB", gain);
StringCchPrintfW(track_peak, 64, L"%-.9f", peak / 32768.0f);
}
albumPeak /= 32768.0f;
}
void CalculateRG(void *context, const wchar_t *filename, wchar_t track_gain[64], wchar_t track_peak[64], ProgressCallback *callback, int *killSwitch, float &albumPeak)
{
LoadRG();
if (!rgLib)
{
char titleStr[32] = {0};
MessageBoxA(GetDialogBoxParent(),
WASABI_API_LNGSTRING(IDS_NOT_ABLE_TO_OPEN_RG_DLL),
WASABI_API_LNGSTRING_BUF(IDS_REPLAYGAIN,titleStr,32),
MB_OK);
return ;
}
wchar_t dummy[64] = {0};
if (!GetFileInfo(filename, L"replaygain_track_gain", dummy, 64)) // check if the plugin even supports replaygain
return ;
/*
TODO: want to do something like this, but have to do it on the main thread (ugh)
if (!_wcsnicmp(dummy, "-24601", 6))
{
SetFileInfo(itr->filename, L"replaygain_track_gain", L"");
SetFileInfo(itr->filename, L"replaygain_track_peak", L"");
SetFileInfo(itr->filename, L"replaygain_album_gain", L"");
SetFileInfo(itr->filename, L"replaygain_album_peak", L"");
WriteFileInfo();
}
*/
AudioParameters parameters;
parameters.flags = AUDIOPARAMETERS_FLOAT | AUDIOPARAMETERS_MAXCHANNELS | AUDIOPARAMETERS_MAXSAMPLERATE;
parameters.channels = 2;
parameters.sampleRate = 192000;
ifc_audiostream *decoder = decodeFile->OpenAudioBackground(filename, &parameters);
if (decoder)
CalculateRG_float(context, decoder, &parameters, track_gain, track_peak, callback, killSwitch, albumPeak);
else
{
// try PCM
memset(&parameters, 0, sizeof(AudioParameters));
parameters.flags = AUDIOPARAMETERS_MAXCHANNELS | AUDIOPARAMETERS_MAXSAMPLERATE;
parameters.channels = 2;
parameters.sampleRate = 192000;
ifc_audiostream *decoder = decodeFile->OpenAudioBackground(filename, &parameters);
if (decoder)
CalculateRG_pcm(context, decoder, &parameters, track_gain, track_peak, callback, killSwitch, albumPeak);
}
}
void CalculateAlbumRG(void *context, wchar_t album_gain[64], wchar_t album_peak[64], float &albumPeak)
{
float gain = GetAlbumGain(context);
if (gain != GAIN_NOT_ENOUGH_SAMPLES)
{
/*StringCchPrintfW(album_gain, 64, L"%-+.2f dB", gain);
StringCchPrintfW(album_peak, 64, L"%-.9f", albumPeak);*/
_locale_t C_locale = WASABI_API_LNG->Get_C_NumericLocale();
_snwprintf_l(album_gain, 64, L"%-+.2f dB", C_locale, gain);
_snwprintf_l(album_peak, 64, L"%-.9f", C_locale, albumPeak);
}
}
void StartRG(void *context)
{
LoadRG();
if (!rgLib)
{
char titleStr[32] = {0};
MessageBoxA(GetDialogBoxParent(),
WASABI_API_LNGSTRING(IDS_NOT_ABLE_TO_OPEN_RG_DLL),
WASABI_API_LNGSTRING_BUF(IDS_REPLAYGAIN,titleStr,32),
MB_OK);
return ;
}
InitGainAnalysis(context, 44100); // since this is most common. We'll reset it before doing a real calculation anyway
}