aboutsummaryrefslogtreecommitdiff
path: root/Src/Plugins/Library/ml_rg
diff options
context:
space:
mode:
Diffstat (limited to 'Src/Plugins/Library/ml_rg')
-rw-r--r--Src/Plugins/Library/ml_rg/Process.cpp53
-rw-r--r--Src/Plugins/Library/ml_rg/Process.h25
-rw-r--r--Src/Plugins/Library/ml_rg/Progress.cpp261
-rw-r--r--Src/Plugins/Library/ml_rg/RGFactory.cpp67
-rw-r--r--Src/Plugins/Library/ml_rg/RGFactory.h24
-rw-r--r--Src/Plugins/Library/ml_rg/Results.cpp172
-rw-r--r--Src/Plugins/Library/ml_rg/api__ml_rg.h24
-rw-r--r--Src/Plugins/Library/ml_rg/config.cpp52
-rw-r--r--Src/Plugins/Library/ml_rg/main.h146
-rw-r--r--Src/Plugins/Library/ml_rg/metadata.cpp28
-rw-r--r--Src/Plugins/Library/ml_rg/ml_rg.cpp409
-rw-r--r--Src/Plugins/Library/ml_rg/ml_rg.rc152
-rw-r--r--Src/Plugins/Library/ml_rg/ml_rg.sln57
-rw-r--r--Src/Plugins/Library/ml_rg/ml_rg.vcxproj310
-rw-r--r--Src/Plugins/Library/ml_rg/ml_rg.vcxproj.filters68
-rw-r--r--Src/Plugins/Library/ml_rg/obj_replaygain.h62
-rw-r--r--Src/Plugins/Library/ml_rg/replaygain.cpp370
-rw-r--r--Src/Plugins/Library/ml_rg/resource.h40
-rw-r--r--Src/Plugins/Library/ml_rg/version.rc239
19 files changed, 2359 insertions, 0 deletions
diff --git a/Src/Plugins/Library/ml_rg/Process.cpp b/Src/Plugins/Library/ml_rg/Process.cpp
new file mode 100644
index 00000000..21cc97e7
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/Process.cpp
@@ -0,0 +1,53 @@
+#include "main.h"
+#include "Process.h"
+
+int ProcessReplayGain::Open(int _mode)
+{
+ mode=_mode;
+ if (mode != RG_INDIVIDUAL_TRACKS
+ && mode != RG_ALBUM)
+ return RG_MODE_NOT_SUPPORTED;
+ context=CreateRG();
+ if (!context)
+ return RG_FAILURE;
+
+ StartRG(context);
+ return RG_SUCCESS;
+}
+
+int ProcessReplayGain::ProcessTrack(const wchar_t *filename)
+{
+ int killSwitch=0;
+ RGWorkFile workFile(filename);
+
+ CalculateRG(context, workFile.filename, workFile.track_gain, workFile.track_peak, 0, &killSwitch, albumPeak);
+ queue.push_back(workFile);
+
+ return RG_SUCCESS;
+}
+
+int ProcessReplayGain::Write()
+{
+ if (mode == RG_ALBUM)
+ {
+ wchar_t album_gain[64]=L"", album_peak[64]=L"";
+ CalculateAlbumRG(context, album_gain, album_peak, albumPeak);
+ CopyAlbumData(queue, album_gain, album_peak);
+ }
+ WriteAlbum(queue);
+
+ return RG_SUCCESS;
+}
+
+void ProcessReplayGain::Close()
+{
+ DestroyRG(context);
+}
+
+#define CBCLASS ProcessReplayGain
+START_DISPATCH;
+CB(OBJ_REPLAYGAIN_OPEN, Open)
+CB(OBJ_REPLAYGAIN_PROCESSTRACK, ProcessTrack)
+CB(OBJ_REPLAYGAIN_WRITE, Write)
+VCB(OBJ_REPLAYGAIN_CLOSE,Close)
+END_DISPATCH;
diff --git a/Src/Plugins/Library/ml_rg/Process.h b/Src/Plugins/Library/ml_rg/Process.h
new file mode 100644
index 00000000..e3f1d195
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/Process.h
@@ -0,0 +1,25 @@
+#ifndef NULLSOFT_ML_RG_PROCESS_H
+#define NULLSOFT_ML_RG_PROCESS_H
+
+#include "obj_replaygain.h"
+
+//this class is meant for use as a service
+
+class ProcessReplayGain : public obj_replaygain
+{
+public:
+ ProcessReplayGain() : context(0), albumPeak(0), mode(RG_INDIVIDUAL_TRACKS) {}
+ int Open(int mode);
+ int ProcessTrack(const wchar_t *filename);
+ int Write();
+ void Close();
+
+protected:
+ RECVS_DISPATCH;
+ void *context;
+ int mode;
+ float albumPeak;
+ WorkQueue::RGWorkQueue queue;
+};
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_rg/Progress.cpp b/Src/Plugins/Library/ml_rg/Progress.cpp
new file mode 100644
index 00000000..18c26031
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/Progress.cpp
@@ -0,0 +1,261 @@
+#include "main.h"
+#include "resource.h"
+#include"api__ml_rg.h"
+#include <strsafe.h>
+
+struct Progress
+{
+ Progress()
+ {
+ processedFiles = 0;
+ currentBytes = 0;
+ totalBytes = 0;
+ activeHWND = 0;
+ threadHandle = 0;
+ openDialogs = 0;
+ done = false;
+ killSwitch = 0;
+ }
+
+ size_t processedFiles;
+ uint64_t currentBytes;
+ uint32_t totalBytes;
+ WorkQueue activeQueue;
+ HWND activeHWND;
+ HANDLE threadHandle;
+ size_t openDialogs;
+ bool done;
+ int killSwitch;
+};
+
+DWORD WINAPI ThreadProc(void *param)
+{
+ Progress *progress = (Progress *)param;
+ ProgressCallback callback(progress->activeHWND);
+ progress->activeQueue.Calculate(&callback, &progress->killSwitch);
+ PostMessage(progress->activeHWND, WM_USER + 2, 0, 0);
+
+ return 0;
+}
+
+void getViewport(RECT *r, HWND wnd, int full, RECT *sr)
+{
+ POINT *p = NULL;
+
+ if (p || sr || wnd)
+ {
+ HMONITOR hm = NULL;
+
+ if (sr)
+ hm = MonitorFromRect(sr, MONITOR_DEFAULTTONEAREST);
+ else if (wnd)
+ hm = MonitorFromWindow(wnd, MONITOR_DEFAULTTONEAREST);
+ else if (p)
+ hm = MonitorFromPoint(*p, MONITOR_DEFAULTTONEAREST);
+
+ if (hm)
+ {
+ MONITORINFOEXW mi;
+ memset(&mi, 0, sizeof(mi));
+ mi.cbSize = sizeof(mi);
+
+ if (GetMonitorInfoW(hm, &mi))
+ {
+ if (!full)
+ *r = mi.rcWork;
+ else
+ *r = mi.rcMonitor;
+
+ return ;
+ }
+ }
+ }
+ if (full)
+ { // this might be borked =)
+ r->top = r->left = 0;
+ r->right = GetSystemMetrics(SM_CXSCREEN);
+ r->bottom = GetSystemMetrics(SM_CYSCREEN);
+ }
+ else
+ {
+ SystemParametersInfoW(SPI_GETWORKAREA, 0, r, 0);
+ }
+}
+
+BOOL windowOffScreen(HWND hwnd, POINT pt)
+{
+ RECT r = {0}, wnd = {0}, sr = {0};
+ GetWindowRect(hwnd, &wnd);
+ sr.left = pt.x;
+ sr.top = pt.y;
+ sr.right = sr.left + (wnd.right - wnd.left);
+ sr.bottom = sr.top + (wnd.bottom - wnd.top);
+ getViewport(&r, hwnd, 0, &sr);
+ return !PtInRect(&r, pt);
+}
+
+void SaveWindowPos(HWND hwnd)
+{
+ RECT rect = {0};
+ GetWindowRect(hwnd, &rect);
+ char buf[16] = {0};
+ StringCchPrintfA(buf, 16, "%d", rect.left);
+ WritePrivateProfileStringA("ml_rg", "prog_x", buf, iniFile);
+ StringCchPrintfA(buf, 16, "%d", rect.top);
+ WritePrivateProfileStringA("ml_rg", "prog_y", buf, iniFile);
+}
+
+INT_PTR WINAPI ReplayGainProgressProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch (msg)
+ {
+ case WM_INITDIALOG:
+ {
+ Progress *progress = (Progress *)lParam;
+ progress->killSwitch = 0;
+ progress->done = false;
+ progress->openDialogs = 0;
+ progress->processedFiles = 0;
+ progress->activeHWND = hwndDlg;
+
+ wchar_t dummy[64] = {0};
+ StringCchPrintfW(dummy, 64, WASABI_API_LNGSTRINGW(IDS_1_OF_X_FILES), progress->activeQueue.totalFiles);
+ SetDlgItemTextW(hwndDlg, IDC_PROGRESS_FILES, dummy);
+
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, (LONG_PTR)progress);
+ DWORD threadId;
+ progress->threadHandle = CreateThread(NULL, 0, ThreadProc, (void *)progress, CREATE_SUSPENDED, &threadId);
+ SetThreadPriority(progress->threadHandle, THREAD_PRIORITY_IDLE);
+ ResumeThread(progress->threadHandle);
+
+ POINT pt = {(LONG)GetPrivateProfileIntA("ml_rg", "prog_x", -1, iniFile),
+ (LONG)GetPrivateProfileIntA("ml_rg", "prog_y", -1, iniFile)};
+ if (!windowOffScreen(hwndDlg, pt))
+ SetWindowPos(hwndDlg, HWND_TOP, pt.x, pt.y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOSENDCHANGING);
+ else
+ ShowWindow(hwndDlg, SW_SHOW);
+ }
+ break;
+ case WM_DESTROY:
+ {
+ SaveWindowPos(hwndDlg);
+ Progress *progress = (Progress *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ CloseHandle(progress->threadHandle);
+ progress->activeHWND = 0;
+ delete progress;
+ }
+ break;
+ case WM_USER: // file finished
+ {
+ Progress *progress = (Progress *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ progress->processedFiles++;
+
+ if (progress->processedFiles + 1 > progress->activeQueue.totalFiles)
+ SetDlgItemTextW(hwndDlg, IDC_PROGRESS_FILES, WASABI_API_LNGSTRINGW(IDS_FINISHED));
+ else
+ {
+ wchar_t dummy[64] = {0};
+ StringCchPrintfW(dummy, 64,
+ WASABI_API_LNGSTRINGW(IDS_X_OF_X_FILES),
+ progress->processedFiles + 1, progress->activeQueue.totalFiles);
+ SetDlgItemTextW(hwndDlg, IDC_PROGRESS_FILES, dummy);
+ }
+ }
+ break;
+ case WM_USER + 1: // album done
+ {
+ Progress *progress = (Progress *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ WorkQueue::RGWorkQueue *queue = (WorkQueue::RGWorkQueue *)lParam;
+ SaveWindowPos(hwndDlg);
+ if (config_ask && config_ask_each_album)
+ {
+ progress->openDialogs++;
+ DoResults(*queue);
+ progress->openDialogs--;
+ if (!progress->openDialogs && progress->done)
+ DestroyWindow(hwndDlg);
+ }
+ else if (config_ask == 0)
+ {
+ WriteAlbum(*queue);
+ }
+ }
+ break;
+ case WM_USER + 2: // all tracks done
+ {
+ Progress *progress = (Progress *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ ShowWindow(hwndDlg, SW_HIDE);
+ SaveWindowPos(hwndDlg);
+ if (config_ask && config_ask_each_album == 0)
+ {
+ DoResults(progress->activeQueue);
+ }
+ progress->killSwitch = 1;
+ WaitForSingleObject(progress->threadHandle, INFINITE);
+ progress->done = true;
+ if (!progress->openDialogs)
+ DestroyWindow(hwndDlg);
+ }
+ break;
+ case WM_USER + 3: // total bytes of current file
+ {
+ Progress *progress = (Progress *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ progress->currentBytes = 0;
+ progress->totalBytes = (uint32_t)lParam;
+ if (progress->totalBytes == 0)
+ {
+ SetDlgItemTextW(hwndDlg, IDC_FILE_PROGRESS, WASABI_API_LNGSTRINGW(IDS_PROCESSING));
+ }
+ else
+ {
+ wchar_t dummy[64] = {0};
+ StringCchPrintfW(dummy, 64, L"%u%%", (progress->currentBytes * 100) / progress->totalBytes);
+ SetDlgItemTextW(hwndDlg, IDC_FILE_PROGRESS, dummy);
+ }
+ }
+ break;
+ case WM_USER + 4: // more bytes read
+ {
+ Progress *progress = (Progress *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ progress->currentBytes += lParam;
+ if (progress->totalBytes == 0)
+ {
+ SetDlgItemTextW(hwndDlg, IDC_FILE_PROGRESS, WASABI_API_LNGSTRINGW(IDS_PROCESSING));
+ }
+ else
+ {
+ wchar_t dummy[64] = {0};
+ StringCchPrintfW(dummy, 64, L"%u%%", (progress->currentBytes * 100) / progress->totalBytes);
+ SetDlgItemTextW(hwndDlg, IDC_FILE_PROGRESS, dummy);
+ }
+ }
+ break;
+ case WM_COMMAND:
+ switch (LOWORD(wParam))
+ {
+ case IDCANCEL:
+ {
+ Progress *progress = (Progress *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ progress->killSwitch = 1;
+ break;
+ }
+ }
+ break;
+ }
+ return 0;
+}
+
+void DoProgress(WorkQueue &workQueue)
+{
+ Progress *progress = new Progress;
+ progress->activeQueue = workQueue; // this is a huge slow copy, but I don't care at the moment
+ WASABI_API_CREATEDIALOGPARAMW(IDD_PROGRESS, GetDialogBoxParent(), ReplayGainProgressProc, (LPARAM)progress);
+}
+
+HWND GetDialogBoxParent()
+{
+ HWND parent = (HWND)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETDIALOGBOXPARENT);
+ if (!parent || parent == (HWND)1)
+ return plugin.hwndWinampParent;
+ return parent;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_rg/RGFactory.cpp b/Src/Plugins/Library/ml_rg/RGFactory.cpp
new file mode 100644
index 00000000..a70daea2
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/RGFactory.cpp
@@ -0,0 +1,67 @@
+#include "main.h"
+#include "RGFactory.h"
+#include "Process.h"
+static const char serviceName[] = "Replay Gain Processor";
+
+FOURCC RGFactory::GetServiceType()
+{
+ return WaSvc::OBJECT;
+}
+
+const char *RGFactory::GetServiceName()
+{
+ return serviceName;
+}
+
+GUID RGFactory::GetGUID()
+{
+ return RGGUID;
+}
+
+void *RGFactory::GetInterface(int global_lock)
+{
+ obj_replaygain *ifc=new ProcessReplayGain;
+// if (global_lock)
+// plugin.service->service_lock(this, (void *)ifc);
+ return ifc;
+}
+
+int RGFactory::SupportNonLockingInterface()
+{
+ return 1;
+}
+
+int RGFactory::ReleaseInterface(void *ifc)
+{
+ //plugin.service->service_unlock(ifc);
+ obj_replaygain *api_ = static_cast<obj_replaygain *>(ifc);
+ ProcessReplayGain *svc_ = static_cast<ProcessReplayGain *>(api_);
+ delete svc_;
+ return 1;
+}
+
+const char *RGFactory::GetTestString()
+{
+ return 0;
+}
+
+int RGFactory::ServiceNotify(int msg, int param1, int param2)
+{
+ return 1;
+}
+
+#ifdef CBCLASS
+#undef CBCLASS
+#endif
+
+#define CBCLASS RGFactory
+START_DISPATCH;
+CB(WASERVICEFACTORY_GETSERVICETYPE, GetServiceType)
+CB(WASERVICEFACTORY_GETSERVICENAME, GetServiceName)
+CB(WASERVICEFACTORY_GETGUID, GetGUID)
+CB(WASERVICEFACTORY_GETINTERFACE, GetInterface)
+CB(WASERVICEFACTORY_SUPPORTNONLOCKINGGETINTERFACE, SupportNonLockingInterface)
+CB(WASERVICEFACTORY_RELEASEINTERFACE, ReleaseInterface)
+CB(WASERVICEFACTORY_GETTESTSTRING, GetTestString)
+CB(WASERVICEFACTORY_SERVICENOTIFY, ServiceNotify)
+END_DISPATCH;
diff --git a/Src/Plugins/Library/ml_rg/RGFactory.h b/Src/Plugins/Library/ml_rg/RGFactory.h
new file mode 100644
index 00000000..5deea3aa
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/RGFactory.h
@@ -0,0 +1,24 @@
+#ifndef NULLSOFT_RG_FACTORY_H
+#define NULLSOFT_RG_FACTORY_H
+
+#include <api/service/waservicefactory.h>
+#include <api/service/services.h>
+
+class RGFactory : public waServiceFactory
+{
+public:
+ FOURCC GetServiceType();
+ const char *GetServiceName();
+ GUID GetGUID();
+ void *GetInterface(int global_lock);
+ int SupportNonLockingInterface();
+ int ReleaseInterface(void *ifc);
+ const char *GetTestString();
+ int ServiceNotify(int msg, int param1, int param2);
+
+protected:
+ RECVS_DISPATCH;
+};
+
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_rg/Results.cpp b/Src/Plugins/Library/ml_rg/Results.cpp
new file mode 100644
index 00000000..592bd47b
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/Results.cpp
@@ -0,0 +1,172 @@
+#include "main.h"
+#include "resource.h"
+#include "../nu/listview.h"
+#include "api__ml_rg.h"
+#include <shlwapi.h>
+#include <strsafe.h>
+
+// this isn't nice but it localises the values for display as they're saved in "C" locale
+enum { GAIN_MODE=0, PEAK_MODE };
+wchar_t* LocaliseNumericText(wchar_t str[64], int mode){
+static wchar_t tmp[64];
+double value;
+ tmp[0]=0;
+ value = _wtof_l(str,WASABI_API_LNG->Get_C_NumericLocale());
+ StringCchPrintfW(tmp,64,(mode==GAIN_MODE?L"%-+.2f dB":L"%-.9f"),value);
+ return tmp;
+}
+
+static void AddQueueToListView(W_ListView *listView, WorkQueue::RGWorkQueue *queue)
+{
+ int i=listView->GetCount();
+ for(WorkQueue::RGWorkQueue::iterator itr = queue->begin(); itr!= queue->end(); itr++)
+ {
+ listView->InsertItem(i, PathFindFileNameW(itr->filename), (int)&*itr);
+ listView->SetItemText(i, 1, LocaliseNumericText(itr->track_gain,GAIN_MODE));
+ listView->SetItemText(i, 2, LocaliseNumericText(itr->track_peak,PEAK_MODE));
+ listView->SetItemText(i, 3, LocaliseNumericText(itr->album_gain,GAIN_MODE));
+ listView->SetItemText(i, 4, LocaliseNumericText(itr->album_peak,PEAK_MODE));
+ i++;
+ }
+}
+
+INT_PTR WINAPI ReplayGainDialogProc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch(msg)
+ {
+ case WM_INITDIALOG:
+ {
+ W_ListView listView(GetDlgItem(hwndDlg, IDC_RGLIST));
+ listView.setwnd(GetDlgItem(hwndDlg, IDC_RGLIST));
+
+ listView.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_FILENAME), 200);
+ listView.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_TRACK_GAIN), 65);
+ listView.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_TRACK_PEAK), 80);
+ listView.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_ALBUM_GAIN), 65);
+ listView.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_ALBUM_PEAK), 80);
+
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+
+ WorkQueue::RGWorkQueue *queue = (WorkQueue::RGWorkQueue *)lParam;
+
+ AddQueueToListView(&listView, queue);
+
+ POINT pt = {(LONG)GetPrivateProfileIntA("ml_rg", "res_x", -1, iniFile),
+ (LONG)GetPrivateProfileIntA("ml_rg", "res_y", -1, iniFile)};
+ if (!windowOffScreen(hwndDlg, pt))
+ SetWindowPos(hwndDlg, HWND_TOP, pt.x, pt.y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOSENDCHANGING);
+ }
+ break;
+ case WM_COMMAND:
+ switch(LOWORD(wParam))
+ {
+ case IDOK:
+ {
+ WorkQueue::RGWorkQueue *queue = (WorkQueue::RGWorkQueue *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ WriteAlbum(*queue);
+ }
+ case IDCANCEL:
+ {
+ RECT rect = {0};
+ GetWindowRect(hwndDlg, &rect);
+ char buf[16] = {0};
+ StringCchPrintfA(buf, 16, "%d", rect.left);
+ WritePrivateProfileStringA("ml_rg", "res_x", buf, iniFile);
+ StringCchPrintfA(buf, 16, "%d", rect.top);
+ WritePrivateProfileStringA("ml_rg", "res_y", buf, iniFile);
+ EndDialog(hwndDlg, 0);
+ }
+ break;
+ case IDC_SAVETRACK:
+ {
+ WorkQueue::RGWorkQueue *queue = (WorkQueue::RGWorkQueue *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ WriteTracks(*queue);
+ EndDialog(hwndDlg, 0);
+ }
+ break;
+ }
+ }
+ return 0;
+}
+
+INT_PTR WINAPI ReplayGainDialogProcAll(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch(msg)
+ {
+ case WM_INITDIALOG:
+ {
+ W_ListView listView(GetDlgItem(hwndDlg, IDC_RGLIST));
+ listView.setwnd(GetDlgItem(hwndDlg, IDC_RGLIST));
+
+ listView.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_FILENAME), 200);
+ listView.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_TRACK_GAIN), 65);
+ listView.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_TRACK_PEAK), 80);
+ listView.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_ALBUM_GAIN), 65);
+ listView.AddCol(WASABI_API_LNGSTRINGW(IDS_COL_ALBUM_PEAK), 80);
+
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+
+ WorkQueue *queue = (WorkQueue *)lParam;
+
+ AddQueueToListView(&listView, &queue->unclassified);
+ for (WorkQueue::AlbumMap::iterator mapItr=queue->albums.begin();mapItr!=queue->albums.end();mapItr++)
+ {
+ AddQueueToListView(&listView, &mapItr->second);
+ }
+
+ POINT pt = {(LONG)GetPrivateProfileIntA("ml_rg", "res_x", -1, iniFile),
+ (LONG)GetPrivateProfileIntA("ml_rg", "res_y", -1, iniFile)};
+ if (!windowOffScreen(hwndDlg, pt))
+ SetWindowPos(hwndDlg, HWND_TOP, pt.x, pt.y, 0, 0, SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOSENDCHANGING);
+ }
+ break;
+ case WM_COMMAND:
+ switch(LOWORD(wParam))
+ {
+ case IDOK:
+ {
+ WorkQueue *queue = (WorkQueue *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ WriteAlbum(queue->unclassified);
+ for (WorkQueue::AlbumMap::iterator mapItr=queue->albums.begin();mapItr!=queue->albums.end();mapItr++)
+ {
+ WriteAlbum(mapItr->second);
+ }
+ }
+ case IDCANCEL:
+ {
+ RECT rect = {0};
+ GetWindowRect(hwndDlg, &rect);
+ char buf[16] = {0};
+ StringCchPrintfA(buf, 16, "%d", rect.left);
+ WritePrivateProfileStringA("ml_rg", "res_x", buf, iniFile);
+ StringCchPrintfA(buf, 16, "%d", rect.top);
+ WritePrivateProfileStringA("ml_rg", "res_y", buf, iniFile);
+ EndDialog(hwndDlg, 0);
+ }
+ break;
+ case IDC_SAVETRACK:
+ {
+ WorkQueue *queue = (WorkQueue *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ WriteTracks(queue->unclassified);
+ for (WorkQueue::AlbumMap::iterator mapItr=queue->albums.begin();mapItr!=queue->albums.end();mapItr++)
+ {
+ WriteTracks(mapItr->second);
+ }
+ EndDialog(hwndDlg, 0);
+ }
+ break;
+ }
+ }
+ return 0;
+}
+
+void DoResults(WorkQueue::RGWorkQueue &queue)
+{
+ if (!queue.empty())
+ WASABI_API_DIALOGBOXPARAM(IDD_RESULTS, GetDialogBoxParent(), ReplayGainDialogProc, (LPARAM)&queue);
+}
+
+void DoResults(WorkQueue &queue)
+{
+ WASABI_API_DIALOGBOXPARAM(IDD_RESULTS, GetDialogBoxParent(), ReplayGainDialogProcAll, (LPARAM)&queue);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_rg/api__ml_rg.h b/Src/Plugins/Library/ml_rg/api__ml_rg.h
new file mode 100644
index 00000000..f46f1047
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/api__ml_rg.h
@@ -0,0 +1,24 @@
+#ifndef NULLSOFT_ML_RG_API_H
+#define NULLSOFT_ML_RG_API_H
+
+#include "../Winamp/api_decodefile.h"
+extern api_decodefile *decodeFile;
+#define AGAVE_API_DECODE decodeFile
+
+#include "api/application/api_application.h"
+extern api_application *applicationApi;
+#define WASABI_API_APP applicationApi
+
+#include "../playlist/api_playlistmanager.h"
+extern api_playlistmanager *playlistManager;
+#define AGAVE_API_PLAYLISTMANAGER playlistManager
+
+#include "../Agave/Language/api_language.h"
+
+#include "../Winamp/api_stats.h"
+extern api_stats *statsApi;
+#define AGAVE_API_STATS statsApi
+
+#include <api/service/waservicefactory.h>
+
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_rg/config.cpp b/Src/Plugins/Library/ml_rg/config.cpp
new file mode 100644
index 00000000..3c5ea665
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/config.cpp
@@ -0,0 +1,52 @@
+#include "main.h"
+#include "resource.h"
+
+int config_ask=1;
+int config_ask_each_album=1;
+int config_ignore_gained_album=0;
+
+void DoButtons(HWND hwndDlg)
+{
+ config_ask = IsDlgButtonChecked(hwndDlg, IDC_ASK);
+ config_ask_each_album = IsDlgButtonChecked(hwndDlg, IDC_ALBUM);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_ALBUM), config_ask);
+ EnableWindow(GetDlgItem(hwndDlg, IDC_ALL), config_ask);
+}
+
+INT_PTR WINAPI RGConfig(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ switch(msg)
+ {
+ case WM_INITDIALOG:
+ CheckDlgButton(hwndDlg, IDC_ASK, config_ask);
+ if (config_ask_each_album)
+ CheckDlgButton(hwndDlg, IDC_ALBUM, BST_CHECKED);
+ else
+ CheckDlgButton(hwndDlg, IDC_ALL, BST_CHECKED);
+ DoButtons(hwndDlg);
+ break;
+ case WM_DESTROY:
+ {
+ config_ask = IsDlgButtonChecked(hwndDlg, IDC_ASK);
+ config_ask_each_album = IsDlgButtonChecked(hwndDlg, IDC_ALBUM);
+ WritePrivateProfileStringA("ml_rg", "config_ask", config_ask ? "1" : "0", iniFile);
+ WritePrivateProfileStringA("ml_rg", "config_ask_each_album", config_ask_each_album ? "1" : "0", iniFile);
+ break;
+ }
+ case WM_COMMAND:
+ switch(LOWORD(wParam))
+ {
+ case IDCANCEL:
+ case IDOK:
+ EndDialog(hwndDlg, 0);
+ break;
+ case IDC_ASK:
+ case IDC_ALBUM:
+ case IDC_ALL:
+ DoButtons(hwndDlg);
+ break;
+ }
+ break;
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_rg/main.h b/Src/Plugins/Library/ml_rg/main.h
new file mode 100644
index 00000000..461b4ae7
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/main.h
@@ -0,0 +1,146 @@
+#ifndef NULLSOFT_ML_RG_MAIN_H
+#define NULLSOFT_ML_RG_MAIN_H
+
+#include <windows.h>
+#include "../../General/gen_ml/ml.h"
+#include <windowsx.h>
+#include "../winamp/wa_ipc.h"
+#include "../../General/gen_ml/ml.h"
+#include "resource.h"
+#include <string>
+#include <vector>
+#include <map>
+
+extern winampMediaLibraryPlugin plugin;
+extern char *iniFile;
+
+LRESULT SetFileInfo(const wchar_t *filename, const wchar_t *metadata, const wchar_t *data);
+int GetFileInfo(const wchar_t *filename, const wchar_t *metadata, wchar_t *dest, int len);
+void WriteFileInfo();
+void TagUpdated(const wchar_t *filename);
+
+struct RGWorkFile
+{
+ RGWorkFile(const wchar_t *_filename=0)
+ {
+ if (_filename)
+ lstrcpynW(filename, _filename, MAX_PATH);
+ else
+ filename[0]=0;
+ track_gain[0]=0;
+ track_peak[0]=0;
+ album_gain[0]=0;
+ album_peak[0]=0;
+ }
+
+ wchar_t filename[MAX_PATH];
+ wchar_t track_gain[64];
+ wchar_t track_peak[64];
+ wchar_t album_gain[64];
+ wchar_t album_peak[64];
+};
+
+class ProgressCallback;
+
+class WorkQueue
+{
+public:
+ WorkQueue() : totalFiles(0){}
+ void Add(const wchar_t *filename);
+ void Calculate(ProgressCallback *callback, int *killSwitch);
+ typedef std::vector<RGWorkFile> RGWorkQueue;
+ typedef std::map<std::wstring, RGWorkQueue> AlbumMap;
+ AlbumMap albums;
+ RGWorkQueue unclassified;
+ size_t totalFiles;
+};
+
+constexpr auto TIME_SPAN_MS = 10;
+
+class ProgressCallback
+{
+public:
+ ProgressCallback(HWND _c)
+ : callback(_c),
+ totalReceived(0)
+ {
+ ticks = GetTickCount64();
+ }
+
+ void InformSize(size_t bytes)
+ {
+ if (!PostMessage(callback, WM_USER + 3, 0, bytes))
+ {
+ // LOG the error
+ DWORD e = GetLastError();
+ }
+ }
+ /// <summary>
+ /// This function may fire an "ERROR_NOT_ENOUGH_QUOTA" 1816 (0x718) error when the limit is hit!
+ /// Put some throttle here, post message every 10 ms, not each time we receive a progress.
+ /// </summary>
+ /// <param name="bytes"></param>
+ void Progress(size_t bytes)
+ {
+ totalReceived += bytes;
+ ULONGLONG currentTicks = GetTickCount64();
+ if (currentTicks - ticks >= TIME_SPAN_MS)
+ {
+ ticks = currentTicks;
+ if (!PostMessage(callback, WM_USER + 4, 0, totalReceived))
+ {
+ // LOG the error
+ DWORD e = GetLastError();
+ }
+
+ totalReceived = 0;
+ }
+ }
+ void FileFinished()
+ {
+ // notify remaining bytes
+ if (totalReceived)
+ {
+ PostMessage(callback, WM_USER + 4, 0, totalReceived);
+ totalReceived = 0;
+ }
+
+ if(!PostMessage(callback, WM_USER, 0, 0))
+ {
+ // LOG the error
+ DWORD e = GetLastError();
+ }
+ }
+ void AlbumFinished(WorkQueue::RGWorkQueue *album)
+ {
+ if(!PostMessage(callback, WM_USER + 1, 0, (LPARAM)album))
+ {
+ // LOG the error
+ DWORD e = GetLastError();
+ }
+ }
+
+ HWND callback;
+ ULONGLONG ticks;
+ size_t totalReceived;
+};
+
+void CopyAlbumData(WorkQueue::RGWorkQueue &workQueue, const wchar_t *album_gain, const wchar_t *album_peak);
+void WriteAlbum(WorkQueue::RGWorkQueue &workQueue);
+void WriteTracks(WorkQueue::RGWorkQueue &workQueue);
+void DoResults(WorkQueue::RGWorkQueue &queue);
+void DoResults(WorkQueue &queue);
+void DoProgress(WorkQueue &workQueue);
+
+void DestroyRG(void *context);
+void *CreateRG();
+void CalculateAlbumRG(void *context, wchar_t album_gain[64], wchar_t album_peak[64], float &albumPeak);
+void StartRG(void *context);
+void CalculateRG(void *context, const wchar_t *filename, wchar_t track_gain[64], wchar_t track_peak[64], ProgressCallback *callback, int *killSwitch, float &albumPeak);
+
+HWND GetDialogBoxParent();
+BOOL windowOffScreen(HWND hwnd, POINT pt);
+
+extern int config_ask, config_ask_each_album, config_ignore_gained_album;
+INT_PTR WINAPI RGConfig(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
+#endif \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_rg/metadata.cpp b/Src/Plugins/Library/ml_rg/metadata.cpp
new file mode 100644
index 00000000..63d4107f
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/metadata.cpp
@@ -0,0 +1,28 @@
+#include "main.h"
+
+LRESULT SetFileInfo(const wchar_t *filename, const wchar_t *metadata, const wchar_t *data)
+{
+ extendedFileInfoStructW efis = {
+ filename,
+ metadata,
+ data ? data : L"",
+ data ? (size_t)lstrlenW(data) : 0,
+ };
+ return SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&efis, IPC_SET_EXTENDED_FILE_INFOW);
+}
+
+void WriteFileInfo()
+{
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_WRITE_EXTENDED_FILE_INFO);
+}
+
+int GetFileInfo(const wchar_t *filename, const wchar_t *metadata, wchar_t *dest, int len)
+{
+ extendedFileInfoStructW efis = { filename, metadata, dest, (size_t)len, };
+ return (int)(INT_PTR)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&efis, IPC_GET_EXTENDED_FILE_INFOW_HOOKABLE); //will return 1 if wa2 supports this IPC call
+}
+
+void TagUpdated(const wchar_t *filename)
+{
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)filename, IPC_FILE_TAG_MAY_HAVE_UPDATEDW);
+} \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_rg/ml_rg.cpp b/Src/Plugins/Library/ml_rg/ml_rg.cpp
new file mode 100644
index 00000000..6d6dcb34
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/ml_rg.cpp
@@ -0,0 +1,409 @@
+#include "Main.h"
+#include "../nu/AutoWideFn.h"
+#include "api__ml_rg.h"
+//#include <api/service/waservicefactory.h>
+#include "RGFactory.h"
+#include "../playlist/ifc_playlistloadercallback.h"
+#include <strsafe.h>
+
+// wasabi based services for localisation support
+api_language *WASABI_API_LNG = 0;
+HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0;
+
+int uninstalling = 0;
+RGFactory rgFactory;
+static DWORD ml_rg_config_ipc;
+static BOOL ml_rg_open_prefs;
+
+template <class api_T>
+void ServiceBuild(api_T *&api_t, GUID factoryGUID_t)
+{
+ if (plugin.service)
+ {
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t);
+ if (factory)
+ api_t = reinterpret_cast<api_T *>( factory->getInterface() );
+ }
+}
+
+template <class api_T>
+void ServiceRelease(api_T *api_t, GUID factoryGUID_t)
+{
+ if (plugin.service && api_t)
+ {
+ waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t);
+ if (factory)
+ factory->releaseInterface(api_t);
+ }
+ api_t = NULL;
+}
+
+static int Init();
+static void Quit();
+static INT_PTR PluginMessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3);
+
+#define PLUG_VER L"1.29"
+extern "C" winampMediaLibraryPlugin plugin =
+{
+ MLHDR_VER,
+ "nullsoft(ml_rg.dll)",
+ Init,
+ Quit,
+ PluginMessageProc,
+ 0,
+ 0,
+ 0,
+};
+
+api_decodefile *AGAVE_API_DECODE = 0;
+api_application *WASABI_API_APP = 0;
+api_playlistmanager *AGAVE_API_PLAYLISTMANAGER = 0;
+api_stats *AGAVE_API_STATS = 0;
+
+char *iniFile = 0;
+int Init()
+{
+ waServiceFactory *sf = 0;
+
+
+ ServiceBuild( AGAVE_API_PLAYLISTMANAGER, api_playlistmanagerGUID );
+ ServiceBuild( AGAVE_API_DECODE, decodeFileGUID );
+ ServiceBuild( WASABI_API_APP, applicationApiServiceGuid );
+ ServiceBuild( AGAVE_API_STATS, AnonymousStatsGUID );
+
+ plugin.service->service_register( &rgFactory );
+
+ // loader so that we can get the localisation service api for use
+ ServiceBuild( WASABI_API_LNG, languageApiGUID );
+
+ // need to have this initialised before we try to do anything with localisation features
+ WASABI_API_START_LANG( plugin.hDllInstance, MlReplayGainLangGUID );
+
+ static wchar_t szDescription[ 256 ];
+ StringCchPrintfW( szDescription, ARRAYSIZE( szDescription ), WASABI_API_LNGSTRINGW( IDS_NULLSOFT_REPLAY_GAIN_ANALYZER ), PLUG_VER );
+
+ plugin.description = (char *)szDescription;
+
+ ml_rg_config_ipc = (DWORD)SendMessageA( plugin.hwndWinampParent, WM_WA_IPC, ( WPARAM ) & "ml_rg_config", IPC_REGISTER_WINAMP_IPCMESSAGE );
+
+ iniFile = (char *)SendMessage( plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETINIFILE );
+ config_ask = GetPrivateProfileIntA( "ml_rg", "config_ask", config_ask, iniFile );
+ config_ask_each_album = GetPrivateProfileIntA( "ml_rg", "config_ask_each_album", config_ask_each_album, iniFile );
+
+
+ return ML_INIT_SUCCESS;
+}
+
+void Quit()
+{
+ ServiceRelease(decodeFile, decodeFileGUID);
+ ServiceRelease(WASABI_API_APP, applicationApiServiceGuid);
+ ServiceRelease(WASABI_API_LNG, languageApiGUID);
+ ServiceRelease(AGAVE_API_PLAYLISTMANAGER, api_playlistmanagerGUID);
+ ServiceRelease(AGAVE_API_STATS, AnonymousStatsGUID);
+ plugin.service->service_deregister(&rgFactory);
+}
+
+void WorkQueue::Add(const wchar_t *filename)
+{
+ wchar_t album[512] = L"";
+
+ // if the user wants to ignore tracks already scanned, check and skip appropriately
+ if (config_ignore_gained_album && GetFileInfo(filename, L"replaygain_album_gain", album, 256) && album[0] != 0)
+ return;
+
+
+ GetFileInfo(filename, L"album", album, 256);
+ if (album[0])
+ {
+ RGWorkFile workFile;
+ lstrcpynW(workFile.filename, filename, MAX_PATH);
+ albums[album].push_back(workFile);
+ }
+ else
+ {
+ RGWorkFile workFile;
+ lstrcpynW(workFile.filename, filename, MAX_PATH);
+ unclassified.push_back(workFile);
+ }
+ totalFiles++;
+}
+
+void WriteAlbum(WorkQueue::RGWorkQueue &workQueue)
+{
+ for (WorkQueue::RGWorkQueue::iterator itr = workQueue.begin();itr != workQueue.end();itr++)
+ {
+ if (itr->track_gain[0])
+ SetFileInfo(itr->filename, L"replaygain_track_gain", itr->track_gain);
+
+ if (itr->track_peak[0])
+ SetFileInfo(itr->filename, L"replaygain_track_peak", itr->track_peak);
+
+ if (itr->album_gain[0])
+ SetFileInfo(itr->filename, L"replaygain_album_gain", itr->album_gain);
+
+ if (itr->album_peak[0])
+ SetFileInfo(itr->filename, L"replaygain_album_peak", itr->album_peak);
+
+ WriteFileInfo();
+ if (AGAVE_API_STATS)
+ AGAVE_API_STATS->IncrementStat(api_stats::REPLAYGAIN_COUNT);
+
+ TagUpdated(itr->filename);
+ }
+}
+
+void WriteTracks(WorkQueue::RGWorkQueue &workQueue)
+{
+ for (WorkQueue::RGWorkQueue::iterator itr = workQueue.begin();itr != workQueue.end();itr++)
+ {
+ if (itr->track_gain[0])
+ SetFileInfo(itr->filename, L"replaygain_track_gain", itr->track_gain);
+
+ if (itr->track_peak[0])
+ SetFileInfo(itr->filename, L"replaygain_track_peak", itr->track_peak);
+
+ WriteFileInfo();
+ if (AGAVE_API_STATS)
+ AGAVE_API_STATS->IncrementStat(api_stats::REPLAYGAIN_COUNT);
+
+ TagUpdated(itr->filename);
+ }
+}
+
+void CopyAlbumData(WorkQueue::RGWorkQueue &workQueue, const wchar_t *album_gain, const wchar_t *album_peak)
+{
+ for (WorkQueue::RGWorkQueue::iterator itr = workQueue.begin();itr != workQueue.end();itr++)
+ {
+ if (itr->track_gain && itr->track_gain[0]) // if there's no track gain, it's because there was an error!
+ {
+ if (album_gain && album_gain[0])
+ lstrcpynW(itr->album_gain, album_gain, 64);
+
+ if (album_peak && album_peak[0])
+ lstrcpynW(itr->album_peak, album_peak, 64);
+ }
+ }
+}
+
+void WorkQueue::Calculate(ProgressCallback *callback, int *killSwitch)
+{
+ void *context = CreateRG();
+ StartRG(context);
+
+ float albumPeak = 0;
+ for (RGWorkQueue::iterator itr = unclassified.begin();itr != unclassified.end();itr++)
+ {
+ if (*killSwitch) {DestroyRG(context); return ;}
+ CalculateRG(context, itr->filename, itr->track_gain, itr->track_peak, callback, killSwitch, albumPeak);
+ callback->FileFinished();
+ }
+ if (*killSwitch) {DestroyRG(context); return ;}
+ callback->AlbumFinished(&unclassified);
+
+ for (AlbumMap::iterator mapItr = albums.begin();mapItr != albums.end();mapItr++)
+ {
+ albumPeak = 0;
+ StartRG(context);
+ for (RGWorkQueue::iterator itr = mapItr->second.begin();itr != mapItr->second.end();itr++)
+ {
+ if (*killSwitch) {DestroyRG(context); return ;}
+ CalculateRG(context, itr->filename, itr->track_gain, itr->track_peak, callback, killSwitch, albumPeak);
+ callback->FileFinished();
+ }
+ wchar_t album_gain[64] = L"", album_peak[64] = L"";
+ CalculateAlbumRG(context, album_gain, album_peak, albumPeak);
+ CopyAlbumData(mapItr->second, album_gain, album_peak);
+ if (*killSwitch) {DestroyRG(context); return ;}
+ callback->AlbumFinished(&(mapItr->second));
+ }
+ DestroyRG(context);
+}
+
+class WorkQueuePlaylistLoader : public ifc_playlistloadercallback
+{
+public:
+ WorkQueuePlaylistLoader(WorkQueue *_queue);
+ void OnFile(const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info);
+
+protected:
+ WorkQueue *queue;
+ RECVS_DISPATCH;
+};
+
+WorkQueuePlaylistLoader::WorkQueuePlaylistLoader(WorkQueue *_queue)
+{
+ queue = _queue;
+}
+
+void WorkQueuePlaylistLoader::OnFile(const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info)
+{
+ queue->Add(filename);
+}
+
+#define CBCLASS WorkQueuePlaylistLoader
+START_DISPATCH;
+VCB(IFC_PLAYLISTLOADERCALLBACK_ONFILE, OnFile)
+END_DISPATCH;
+#undef CBCLASS
+
+INT_PTR PluginMessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3)
+{
+ if (message_type == ML_MSG_ONSENDTOBUILD)
+ {
+ if (param1 == ML_TYPE_ITEMRECORDLISTW || param1 == ML_TYPE_ITEMRECORDLIST ||
+ param1 == ML_TYPE_FILENAMES || param1 == ML_TYPE_STREAMNAMES ||
+ param1 == ML_TYPE_FILENAMESW || param1 == ML_TYPE_STREAMNAMESW ||
+ (AGAVE_API_PLAYLISTMANAGER && (param1 == ML_TYPE_PLAYLIST || param1 == ML_TYPE_PLAYLISTS)))
+ {
+ wchar_t description[512] = {0};
+ WASABI_API_LNGSTRINGW_BUF(IDS_CALCULATE_REPLAY_GAIN, description, 512);
+
+ mlAddToSendToStructW s = {0};
+ s.context = param2;
+ s.desc = description;
+ s.user32 = (INT_PTR)PluginMessageProc;
+ SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&s, ML_IPC_ADDTOSENDTOW);
+ }
+ }
+ else if (message_type == ML_MSG_ONSENDTOSELECT)
+ {
+ if (param3 != (INT_PTR)PluginMessageProc) return 0;
+
+ INT_PTR type = param1;
+ INT_PTR data = param2;
+
+ if (data)
+ {
+ WorkQueue workQueue;
+ if (type == ML_TYPE_ITEMRECORDLIST)
+ {
+ itemRecordList *p = (itemRecordList*)data;
+ for (int x = 0; x < p->Size; x ++)
+ {
+ workQueue.Add(AutoWideFn(p->Items[x].filename));
+ }
+
+ DoProgress(workQueue);
+ return 1;
+ }
+ else if (type == ML_TYPE_ITEMRECORDLISTW)
+ {
+ itemRecordListW *p = (itemRecordListW*)data;
+ for (int x = 0; x < p->Size; x ++)
+ {
+ workQueue.Add(p->Items[x].filename);
+ }
+
+ DoProgress(workQueue);
+ return 1;
+ }
+ else if (type == ML_TYPE_FILENAMES || type == ML_TYPE_STREAMNAMES)
+ {
+ char *p = (char*)data;
+ while (p && *p)
+ {
+ workQueue.Add(AutoWideFn(p));
+ p += strlen(p) + 1;
+ }
+ DoProgress(workQueue);
+ return 1;
+ }
+ else if (type == ML_TYPE_FILENAMESW || type == ML_TYPE_STREAMNAMESW)
+ {
+ wchar_t *p = (wchar_t*)data;
+ while (p && *p)
+ {
+ workQueue.Add(p);
+ p += wcslen(p) + 1;
+ }
+ DoProgress(workQueue);
+ return 1;
+ }
+ else if (type == ML_TYPE_PLAYLIST)
+ {
+ mlPlaylist *playlist = (mlPlaylist *)param2;
+ WorkQueuePlaylistLoader loader(&workQueue);
+ AGAVE_API_PLAYLISTMANAGER->Load(playlist->filename, &loader);
+ DoProgress(workQueue);
+ return 1;
+ }
+ else if (type == ML_TYPE_PLAYLISTS)
+ {
+ mlPlaylist **playlists = (mlPlaylist **)param2;
+ WorkQueuePlaylistLoader loader(&workQueue);
+ while (playlists && *playlists)
+ {
+ mlPlaylist *playlist = *playlists;
+ AGAVE_API_PLAYLISTMANAGER->Load(playlist->filename, &loader);
+ playlists++;
+ }
+ DoProgress(workQueue);
+ return 1;
+ }
+ }
+ }
+ else if (message_type == ML_MSG_CONFIG)
+ {
+ ml_rg_open_prefs = TRUE;
+ SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 42, IPC_OPENPREFSTOPAGE);
+ ml_rg_open_prefs = FALSE;
+ return 1;
+ }
+ // this will be sent if we've opened the playback->replay gain prefs page
+ // so that we can enable the embedded controls and return the RGConfig proc
+ else if (message_type == ml_rg_config_ipc)
+ {
+ // sanity check by winamp.exe to make sure that we're valid
+ if(!param1)
+ {
+ return 1;
+ }
+ // return the config dialog proceedure
+ else if(param1 == 1)
+ {
+ return (INT_PTR)RGConfig;
+ }
+ // queried when the playback prefs page is opened to see if we [ml_rg] caused it
+ else if(param1 == 2)
+ {
+ if(ml_rg_open_prefs)
+ {
+ return TRUE;
+ }
+ }
+ }
+ return 0;
+}
+
+extern "C" {
+ __declspec(dllexport) winampMediaLibraryPlugin *winampGetMediaLibraryPlugin()
+ {
+ return &plugin;
+ }
+
+ __declspec( dllexport ) int winampUninstallPlugin(HINSTANCE hDllInst, HWND hwndDlg, int param) {
+ uninstalling = 1;
+
+ // prompt to remove our settings with default as no (just incase)
+ if(MessageBoxW(hwndDlg,WASABI_API_LNGSTRINGW(IDS_DO_YOU_ALSO_WANT_TO_REMOVE_SETTINGS),
+ (wchar_t*)plugin.description,MB_YESNO|MB_DEFBUTTON2) == IDYES)
+ {
+ WritePrivateProfileStringA("ml_rg", 0, 0, iniFile);
+ }
+
+ // also attempt to remove the ReplayGainAnalysis.dll so everything is kept cleaner
+ wchar_t path[MAX_PATH] = {0};
+ PathCombineW(path, (wchar_t*)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETSHAREDDLLDIRECTORYW), L"ReplayGainAnalysis.dll");
+
+ // if we get a handle then try to lower the handle count so we can delete
+ HINSTANCE rgLib = GetModuleHandleW(path);
+ if(rgLib) {
+ FreeLibrary(rgLib);
+ }
+ DeleteFileW(path);
+
+ // allow an on-the-fly removal (since we've got to be with a compatible client build)
+ return ML_PLUGIN_UNINSTALL_NOW;
+ }
+}; \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_rg/ml_rg.rc b/Src/Plugins/Library/ml_rg/ml_rg.rc
new file mode 100644
index 00000000..fe1ce9c8
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/ml_rg.rc
@@ -0,0 +1,152 @@
+// Microsoft Visual C++ generated resource script.
+//
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "afxres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.S.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+#endif //_WIN32
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""afxres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "#include ""version.rc2""\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Dialog
+//
+
+IDD_RESULTS DIALOGEX 0, 0, 356, 170
+STYLE DS_SYSMODAL | DS_SETFONT | DS_MODALFRAME | DS_SETFOREGROUND | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_CONTROLPARENT | WS_EX_APPWINDOW
+CAPTION "Replay Gain Results"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ CONTROL "",IDC_RGLIST,"SysListView32",LVS_REPORT | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,4,7,348,141
+ DEFPUSHBUTTON "Save as Album",IDOK,116,153,76,13
+ PUSHBUTTON "Save Track data",IDC_SAVETRACK,196,153,76,13
+ PUSHBUTTON "Cancel",IDCANCEL,276,153,76,13
+END
+
+IDD_PROGRESS DIALOGEX 0, 0, 186, 63
+STYLE DS_SETFONT | DS_MODALFRAME | DS_SETFOREGROUND | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
+EXSTYLE WS_EX_APPWINDOW
+CAPTION "Calculating Replay Gain"
+FONT 8, "MS Shell Dlg", 400, 0, 0x1
+BEGIN
+ LTEXT "File Progress:",IDC_STATIC,7,7,44,8
+ LTEXT "Calculating...",IDC_FILE_PROGRESS,63,7,116,8
+ LTEXT "Total Progress:",IDC_STATIC,7,23,50,8
+ LTEXT "Calculating...",IDC_PROGRESS_FILES,63,23,116,8
+ PUSHBUTTON "Abort",IDCANCEL,129,41,50,14
+END
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// DESIGNINFO
+//
+
+#ifdef APSTUDIO_INVOKED
+GUIDELINES DESIGNINFO
+BEGIN
+ IDD_RESULTS, DIALOG
+ BEGIN
+ LEFTMARGIN, 4
+ RIGHTMARGIN, 352
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 166
+ END
+
+ IDD_PROGRESS, DIALOG
+ BEGIN
+ LEFTMARGIN, 7
+ RIGHTMARGIN, 179
+ TOPMARGIN, 7
+ BOTTOMMARGIN, 56
+ END
+END
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// String Table
+//
+
+STRINGTABLE
+BEGIN
+ IDS_NULLSOFT_REPLAY_GAIN_ANALYZER "Nullsoft Replay Gain Analyzer v%s"
+ 65535 "{5F633543-148D-48cc-B683-DA82F592CF28}"
+END
+
+STRINGTABLE
+BEGIN
+ IDS_CALCULATE_REPLAY_GAIN "Calculate Replay Gain"
+ IDS_1_OF_X_FILES "1 of %u files"
+ IDS_FINISHED "Finished."
+ IDS_X_OF_X_FILES "%u of %u files"
+ IDS_PROCESSING "Processing..."
+ IDS_TOO_MANY_CHANNELS "Too many channels, can't deal with this yet"
+ IDS_REPLAYGAIN "ReplayGain"
+ IDS_NOT_ABLE_TO_OPEN_RG_DLL "Not able to open ReplayGainAnalysis.dll"
+ IDS_COL_FILENAME "Filename"
+ IDS_COL_TRACK_GAIN "Track Gain"
+ IDS_COL_TRACK_PEAK "Track Peak"
+ IDS_COL_ALBUM_GAIN "Album Gain"
+ IDS_COL_ALBUM_PEAK "Album Peak"
+ IDS_DO_YOU_ALSO_WANT_TO_REMOVE_SETTINGS
+ "Do you also want to remove the saved settings for this plug-in?"
+END
+
+#endif // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+#include "version.rc2"
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/Src/Plugins/Library/ml_rg/ml_rg.sln b/Src/Plugins/Library/ml_rg/ml_rg.sln
new file mode 100644
index 00000000..5beb288b
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/ml_rg.sln
@@ -0,0 +1,57 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.29424.173
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ml_rg", "ml_rg.vcxproj", "{F42B3AC8-E719-4AA6-B9EC-1F9BFC799861}"
+ ProjectSection(ProjectDependencies) = postProject
+ {8155E1C4-B6F8-4A1D-96A3-E4FF0FE9192D} = {8155E1C4-B6F8-4A1D-96A3-E4FF0FE9192D}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReplayGainAnalysis", "..\ReplayGainAnalysis\ReplayGainAnalysis.vcxproj", "{8155E1C4-B6F8-4A1D-96A3-E4FF0FE9192D}"
+ ProjectSection(ProjectDependencies) = postProject
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F} = {DABE6307-F8DD-416D-9DAC-673E2DECB73F}
+ EndProjectSection
+EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nsutil", "..\nsutil\nsutil.vcxproj", "{DABE6307-F8DD-416D-9DAC-673E2DECB73F}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Win32 = Debug|Win32
+ Debug|x64 = Debug|x64
+ Release|Win32 = Release|Win32
+ Release|x64 = Release|x64
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {F42B3AC8-E719-4AA6-B9EC-1F9BFC799861}.Debug|Win32.ActiveCfg = Debug|Win32
+ {F42B3AC8-E719-4AA6-B9EC-1F9BFC799861}.Debug|Win32.Build.0 = Debug|Win32
+ {F42B3AC8-E719-4AA6-B9EC-1F9BFC799861}.Debug|x64.ActiveCfg = Debug|x64
+ {F42B3AC8-E719-4AA6-B9EC-1F9BFC799861}.Debug|x64.Build.0 = Debug|x64
+ {F42B3AC8-E719-4AA6-B9EC-1F9BFC799861}.Release|Win32.ActiveCfg = Release|Win32
+ {F42B3AC8-E719-4AA6-B9EC-1F9BFC799861}.Release|Win32.Build.0 = Release|Win32
+ {F42B3AC8-E719-4AA6-B9EC-1F9BFC799861}.Release|x64.ActiveCfg = Release|x64
+ {F42B3AC8-E719-4AA6-B9EC-1F9BFC799861}.Release|x64.Build.0 = Release|x64
+ {8155E1C4-B6F8-4A1D-96A3-E4FF0FE9192D}.Debug|Win32.ActiveCfg = Debug|Win32
+ {8155E1C4-B6F8-4A1D-96A3-E4FF0FE9192D}.Debug|Win32.Build.0 = Debug|Win32
+ {8155E1C4-B6F8-4A1D-96A3-E4FF0FE9192D}.Debug|x64.ActiveCfg = Debug|x64
+ {8155E1C4-B6F8-4A1D-96A3-E4FF0FE9192D}.Debug|x64.Build.0 = Debug|x64
+ {8155E1C4-B6F8-4A1D-96A3-E4FF0FE9192D}.Release|Win32.ActiveCfg = Release|Win32
+ {8155E1C4-B6F8-4A1D-96A3-E4FF0FE9192D}.Release|Win32.Build.0 = Release|Win32
+ {8155E1C4-B6F8-4A1D-96A3-E4FF0FE9192D}.Release|x64.ActiveCfg = Release|x64
+ {8155E1C4-B6F8-4A1D-96A3-E4FF0FE9192D}.Release|x64.Build.0 = Release|x64
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|Win32.ActiveCfg = Debug|Win32
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|Win32.Build.0 = Debug|Win32
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|x64.ActiveCfg = Debug|x64
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|x64.Build.0 = Debug|x64
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|Win32.ActiveCfg = Release|Win32
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|Win32.Build.0 = Release|Win32
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|x64.ActiveCfg = Release|x64
+ {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|x64.Build.0 = Release|x64
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {FC646532-2050-40A5-A2AB-F699F1C071C4}
+ EndGlobalSection
+EndGlobal
diff --git a/Src/Plugins/Library/ml_rg/ml_rg.vcxproj b/Src/Plugins/Library/ml_rg/ml_rg.vcxproj
new file mode 100644
index 00000000..309e9194
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/ml_rg.vcxproj
@@ -0,0 +1,310 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{F42B3AC8-E719-4AA6-B9EC-1F9BFC799861}</ProjectGuid>
+ <RootNamespace>ml_rg</RootNamespace>
+ <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>DynamicLibrary</ConfigurationType>
+ <PlatformToolset>v142</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ <IncludePath>$(IncludePath)</IncludePath>
+ <LibraryPath>$(LibraryPath)</LibraryPath>
+ <EmbedManifest>true</EmbedManifest>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ <EmbedManifest>true</EmbedManifest>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ <IncludePath>$(IncludePath)</IncludePath>
+ <LibraryPath>$(LibraryPath)</LibraryPath>
+ <EmbedManifest>true</EmbedManifest>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir>
+ <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir>
+ <EmbedManifest>true</EmbedManifest>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg">
+ <VcpkgEnableManifest>false</VcpkgEnableManifest>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ <VcpkgConfiguration>Debug</VcpkgConfiguration>
+ <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
+ <VcpkgConfiguration>Debug</VcpkgConfiguration>
+ </PropertyGroup>
+ <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <VcpkgInstalledDir>
+ </VcpkgInstalledDir>
+ <VcpkgUseStatic>false</VcpkgUseStatic>
+ <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;_DEBUG;_WINDOWS;_USRDLL;ML_RG_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\
+xcopy /Y /D $(IntDir)$(TargetName).pdb ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\</Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
+ </PostBuildEvent>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ResourceCompile>
+ <Manifest>
+ <OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <Optimization>Disabled</Optimization>
+ <AdditionalIncludeDirectories>..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;_DEBUG;_WINDOWS;_USRDLL;ML_RG_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <MinimalRebuild>false</MinimalRebuild>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
+ <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
+ <DisableSpecificWarnings>4311;4302;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\
+xcopy /Y /D $(IntDir)$(TargetName).pdb ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\</Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
+ </PostBuildEvent>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ResourceCompile>
+ <Manifest>
+ <OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <Optimization>MinSpace</Optimization>
+ <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN32;NDEBUG;_WINDOWS;_USRDLL;ML_RG_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <TargetMachine>MachineX86</TargetMachine>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\
+</Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
+ </PostBuildEvent>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ResourceCompile>
+ <Manifest>
+ <OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <Optimization>MinSpace</Optimization>
+ <InlineFunctionExpansion>AnySuitable</InlineFunctionExpansion>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
+ <AdditionalIncludeDirectories>..\..\..\Wasabi;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;WIN64;NDEBUG;_WINDOWS;_USRDLL;ML_RG_EXPORTS;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ <StringPooling>true</StringPooling>
+ <MultiProcessorCompilation>true</MultiProcessorCompilation>
+ <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
+ <BufferSecurityCheck>true</BufferSecurityCheck>
+ <WarningLevel>Level3</WarningLevel>
+ <DebugInformationFormat>None</DebugInformationFormat>
+ <DisableSpecificWarnings>4311;4302;%(DisableSpecificWarnings)</DisableSpecificWarnings>
+ <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName>
+ </ClCompile>
+ <Link>
+ <AdditionalDependencies>comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies>
+ <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile>
+ <GenerateDebugInformation>false</GenerateDebugInformation>
+ <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile>
+ <SubSystem>Windows</SubSystem>
+ <OptimizeReferences>true</OptimizeReferences>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <RandomizedBaseAddress>false</RandomizedBaseAddress>
+ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary>
+ <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers>
+ <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
+ </Link>
+ <PostBuildEvent>
+ <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\
+</Command>
+ <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message>
+ </PostBuildEvent>
+ <ResourceCompile>
+ <PreprocessorDefinitions>_WIN32_WINNT=0x0601;WINVER=0x0601;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ResourceCompile>
+ <Manifest>
+ <OutputManifestFile>$(IntDir)$(TargetName)$(TargetExt).intermediate.manifest</OutputManifestFile>
+ </Manifest>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClInclude Include="api__ml_rg.h" />
+ <ClInclude Include="main.h" />
+ <ClInclude Include="obj_replaygain.h" />
+ <ClInclude Include="Process.h" />
+ <ClInclude Include="resource.h" />
+ <ClInclude Include="RGFactory.h" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="..\..\..\nu\listview.cpp" />
+ <ClCompile Include="config.cpp" />
+ <ClCompile Include="metadata.cpp" />
+ <ClCompile Include="ml_rg.cpp" />
+ <ClCompile Include="Process.cpp" />
+ <ClCompile Include="Progress.cpp" />
+ <ClCompile Include="replaygain.cpp" />
+ <ClCompile Include="Results.cpp" />
+ <ClCompile Include="RGFactory.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_rg.rc" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj">
+ <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_rg/ml_rg.vcxproj.filters b/Src/Plugins/Library/ml_rg/ml_rg.vcxproj.filters
new file mode 100644
index 00000000..c0ec5bcf
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/ml_rg.vcxproj.filters
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <ClCompile Include="config.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="metadata.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Process.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Progress.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="replaygain.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="Results.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="RGFactory.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ml_rg.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="..\..\..\nu\listview.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="api__ml_rg.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="main.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="obj_replaygain.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="Process.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="resource.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="RGFactory.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+ <ItemGroup>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{20ba2df7-10e3-42e6-896b-4c0a795e542f}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Ressource Files">
+ <UniqueIdentifier>{018c7bf8-fe6c-4705-aa4d-c641ffd71994}</UniqueIdentifier>
+ </Filter>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{916716df-cad8-4fa2-9a6f-2203f1fbade7}</UniqueIdentifier>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ResourceCompile Include="ml_rg.rc">
+ <Filter>Ressource Files</Filter>
+ </ResourceCompile>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/Src/Plugins/Library/ml_rg/obj_replaygain.h b/Src/Plugins/Library/ml_rg/obj_replaygain.h
new file mode 100644
index 00000000..a594c246
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/obj_replaygain.h
@@ -0,0 +1,62 @@
+#ifndef NULLSOFT_ML_RG_OBJ_REPLAYGAIN_H
+#define NULLSOFT_ML_RG_OBJ_REPLAYGAIN_H
+
+#include <bfc/dispatch.h>
+
+enum
+{
+ RG_SUCCESS = 0,
+ RG_FAILURE = 1,
+ RG_MODE_NOT_SUPPORTED=2,
+
+ RG_INDIVIDUAL_TRACKS = 0, // use this mode to calculate each track sent individually
+ RG_ALBUM = 1, // use this mode to treat all tracks sent as belonging to the same album
+ RG_AUTO = 2, // retrieve tags from the files to determine album info
+};
+
+class obj_replaygain : public Dispatchable
+{
+protected:
+ obj_replaygain() {}
+ ~obj_replaygain() {}
+public:
+ int Open(int mode);
+ int ProcessTrack(const wchar_t *filename);
+ int Write();
+ void Close();
+
+ DISPATCH_CODES
+ {
+ OBJ_REPLAYGAIN_OPEN = 10,
+ OBJ_REPLAYGAIN_PROCESSTRACK = 20,
+ OBJ_REPLAYGAIN_WRITE = 30,
+ OBJ_REPLAYGAIN_CLOSE = 40,
+ };
+};
+
+inline int obj_replaygain::Open(int mode)
+{
+ return _call(OBJ_REPLAYGAIN_OPEN, (int)RG_FAILURE, mode);
+}
+
+inline int obj_replaygain::ProcessTrack(const wchar_t *filename)
+{
+ return _call(OBJ_REPLAYGAIN_PROCESSTRACK, (int)RG_FAILURE, filename);
+}
+
+inline int obj_replaygain::Write()
+{
+ return _call(OBJ_REPLAYGAIN_WRITE, (int)RG_FAILURE);
+}
+
+inline void obj_replaygain::Close()
+{
+ _voidcall(OBJ_REPLAYGAIN_CLOSE);
+}
+
+// {3A398A1B-D316-4094-993E-27EAEA553D19}
+static const GUID RGGUID =
+{ 0x3a398a1b, 0xd316, 0x4094, { 0x99, 0x3e, 0x27, 0xea, 0xea, 0x55, 0x3d, 0x19 } };
+
+
+#endif
diff --git a/Src/Plugins/Library/ml_rg/replaygain.cpp b/Src/Plugins/Library/ml_rg/replaygain.cpp
new file mode 100644
index 00000000..beda7f8b
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/replaygain.cpp
@@ -0,0 +1,370 @@
+#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
+}
diff --git a/Src/Plugins/Library/ml_rg/resource.h b/Src/Plugins/Library/ml_rg/resource.h
new file mode 100644
index 00000000..88f4f058
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/resource.h
@@ -0,0 +1,40 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by ml_rg.rc
+//
+#define IDS_CALCULATE_REPLAY_GAIN 1
+#define IDS_1_OF_X_FILES 2
+#define IDS_FINISHED 3
+#define IDS_X_OF_X_FILES 4
+#define IDS_PROCESSING 5
+#define IDS_TOO_MANY_CHANNELS 6
+#define IDS_REPLAYGAIN 7
+#define IDS_NOT_ABLE_TO_OPEN_RG_DLL 8
+#define IDS_COL_FILENAME 9
+#define IDS_COL_TRACK_GAIN 10
+#define IDS_COL_TRACK_PEAK 11
+#define IDS_COL_ALBUM_GAIN 12
+#define IDS_COL_ALBUM_PEAK 13
+#define IDS_STRING14 14
+#define IDS_DO_YOU_ALSO_WANT_TO_REMOVE_SETTINGS 14
+#define IDD_RESULTS 101
+#define IDD_PROGRESS 102
+#define IDC_RGLIST 1001
+#define IDC_SAVETRACK 1002
+#define IDC_PROGRESS_FILES 1003
+#define IDC_FILE_PROGRESS 1004
+#define IDC_ASK 1005
+#define IDC_ALBUM 1006
+#define IDC_ALL 1007
+#define IDS_NULLSOFT_REPLAY_GAIN_ANALYZER 65534
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 107
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1008
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/Src/Plugins/Library/ml_rg/version.rc2 b/Src/Plugins/Library/ml_rg/version.rc2
new file mode 100644
index 00000000..c016ebfe
--- /dev/null
+++ b/Src/Plugins/Library/ml_rg/version.rc2
@@ -0,0 +1,39 @@
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+#include "../../../Winamp/buildType.h"
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 1,29,0,0
+ PRODUCTVERSION WINAMP_PRODUCTVER
+ FILEFLAGSMASK 0x17L
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x4L
+ FILETYPE 0x2L
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "CompanyName", "Winamp SA"
+ VALUE "FileDescription", "Winamp Media Library Plug-in"
+ VALUE "FileVersion", "1,29,0,0"
+ VALUE "InternalName", "Nullsoft Replay Gain Analyzer"
+ VALUE "LegalCopyright", "Copyright © 2006-2023 Winamp SA"
+ VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA"
+ VALUE "OriginalFilename", "ml_rg.dll"
+ VALUE "ProductName", "Winamp"
+ VALUE "ProductVersion", STR_WINAMP_PRODUCTVER
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END