Added support for new MSVC compiler version.
[DynamicAudioNormalizer.git] / DynamicAudioNormalizerWA5 / src / DynamicAudioNormalizerWA5.cpp
blob641775414b775e1881a3d259192a9d398bab7c8e
1 //////////////////////////////////////////////////////////////////////////////////
2 // Dynamic Audio Normalizer - Winamp DSP Wrapper
3 // Copyright (c) 2014-2019 LoRd_MuldeR <mulder2@gmx.de>. Some rights reserved.
4 //
5 // Permission is hereby granted, free of charge, to any person obtaining a copy
6 // of this software and associated documentation files (the "Software"), to deal
7 // in the Software without restriction, including without limitation the rights
8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 // copies of the Software, and to permit persons to whom the Software is
10 // furnished to do so, subject to the following conditions:
12 // The above copyright notice and this permission notice shall be included in
13 // all copies or substantial portions of the Software.
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 // THE SOFTWARE.
23 // http://opensource.org/licenses/MIT
24 //////////////////////////////////////////////////////////////////////////////////
26 //Stdlib
27 #include <cstdlib>
28 #include <cstdio>
29 #include <stdint.h>
30 #include <algorithm>
32 //Win32
33 #define NOMINMAX 1
34 #define WIN32_LEAN_AND_MEAN 1
35 #include <Windows.h>
37 //Winamp
38 #include <winamp_dsp.h>
39 #include <winamp_ipc.h>
41 //Internal
42 #include <Common.h>
43 #include <Threads.h>
45 //Dynamic Audio Normalizer API
46 #include <DynamicAudioNormalizer.h>
48 //DLL exports
49 #define DLL_EXPORT __declspec(dllexport)
51 //Reg key
52 static const wchar_t *REGISTRY_BASEPATH = L"Software\\MuldeR\\DynAudNorm\\WA5";
53 static const wchar_t *REGISTRY_VERSION = L"Version";
54 static const wchar_t *REGISTRY_FRAMELEN = L"FrameLenMsec";
55 static const wchar_t *REGISTRY_FLTRSIZE = L"FilterSize";
57 //Defaults
58 static const DWORD FRAMELEN_DEFAULT = 500;
59 static const DWORD FLTRSIZE_DEFAULT = 31;
61 //Critical Section
62 static char g_loggingBuffer[1024];
63 static MY_CRITSEC_INIT(g_loggingMutex);
65 //Forward declarations
66 static void config(struct winampDSPModule*);
67 static int init(struct winampDSPModule*);
68 static void quit(struct winampDSPModule*);
69 static int modify_samples(struct winampDSPModule*, short int*, int, int, int, int);
70 static winampDSPModule *getModule(int);
71 static int sf(int);
72 static bool showAboutScreen(const uint32_t&, const uint32_t&, const uint32_t&, const char *const, const char *const, const char *const, const char *const, const bool&, const bool &config);
74 //Const
75 static const size_t MAX_CHANNELS = 12;
77 ///////////////////////////////////////////////////////////////////////////////
78 // Global Data
79 ///////////////////////////////////////////////////////////////////////////////
81 static char g_description[256] = { '\0' };
83 static winampDSPHeader g_header =
85 DSP_HDRVER + 1,
86 "Dynamic Audio Normalizer [" __DATE__ "]",
87 getModule,
91 static winampDSPModule g_module =
93 g_description,
94 NULL, // hwndParent
95 NULL, // hDllInstance
96 config,
97 init,
98 modify_samples,
99 quit
102 static struct
104 uint32_t sampleRate;
105 uint32_t bitsPerSample;
106 uint32_t channels;
108 g_properties =
110 44100, 16, 2
113 static volatile WNDPROC g_oldWndProc = NULL;
114 static volatile LONG g_forceReset = 0;
115 static volatile DWORD g_lastResetTick = 0;
117 ///////////////////////////////////////////////////////////////////////////////
118 // Helper functions
119 ///////////////////////////////////////////////////////////////////////////////
121 static void outputMessage(const char *const format, ...)
123 MY_CRITSEC_ENTER(g_loggingMutex);
125 va_list argList;
126 va_start(argList, format);
127 vsnprintf_s(g_loggingBuffer, 1024, _TRUNCATE, format, argList);
128 va_end(argList);
129 OutputDebugStringA(g_loggingBuffer);
131 MY_CRITSEC_LEAVE(g_loggingMutex);
134 static void logFunction(const int logLevel, const char *const message)
136 switch (logLevel)
138 case MDynamicAudioNormalizer::LOG_LEVEL_DBG:
139 outputMessage("[DynAudNorm_WA5] NFO: %s\n", message);
140 break;
141 case MDynamicAudioNormalizer::LOG_LEVEL_WRN:
142 outputMessage("[DynAudNorm_WA5] WRN: %s\n", message);
143 break;
144 case MDynamicAudioNormalizer::LOG_LEVEL_ERR:
145 outputMessage("[DynAudNorm_WA5] ERR: %s\n", message);
146 break;
147 default:
148 outputMessage("[DynAudNorm_WA5] DBG: %s\n", message);
149 break;
153 static void showErrorMsg(const char *const text)
155 MessageBoxA(NULL, text, "Dynamic Audio Normalizer", MB_ICONSTOP | MB_TOPMOST | MB_TASKMODAL);
158 static DWORD regValueGet(const wchar_t *const name)
160 DWORD result = 0; HKEY hKey = NULL;
161 if(RegCreateKeyExW(HKEY_CURRENT_USER, REGISTRY_BASEPATH, 0, NULL, 0, KEY_READ, NULL, &hKey, NULL) == ERROR_SUCCESS)
163 DWORD type = 0, value = 0, size = sizeof(DWORD);
164 if(RegQueryValueExW(hKey, name, 0, &type, ((LPBYTE) &value), &size) == ERROR_SUCCESS)
166 if((type == REG_DWORD) && (size == sizeof(DWORD)))
168 result = value;
171 RegCloseKey(hKey);
173 return result;
176 static bool regValueSet(const wchar_t *const name, const DWORD &value)
178 bool result = false; HKEY hKey = NULL;
179 if(RegCreateKeyExW(HKEY_CURRENT_USER, REGISTRY_BASEPATH, 0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL) == ERROR_SUCCESS)
181 if(RegSetValueExW(hKey, name, 0, REG_DWORD, ((LPBYTE) &value), sizeof(DWORD)) == ERROR_SUCCESS)
183 result = true;
185 RegCloseKey(hKey);
187 return result;
190 static double *allocBuffer(size_t size)
192 double *buffer = NULL;
195 buffer = new double[size];
197 catch(...)
199 outputMessage("[DynAudNorm_WA5] ERR: Allocation of size %u failed!", static_cast<unsigned int>(sizeof(double) * size));
200 showErrorMsg("Memory allocation has failed: Out of memory!");
201 _exit(0xC0000017);
203 return buffer;
206 ///////////////////////////////////////////////////////////////////////////////
207 // Internal Functions
208 ///////////////////////////////////////////////////////////////////////////////
210 static MDynamicAudioNormalizer *g_instance = NULL;
211 static size_t g_sampleBufferSize = 0;
212 static double *g_sampleBuffer[MAX_CHANNELS];
214 static void updateBufferSize(const int &numsamples)
216 if(size_t(numsamples) > g_sampleBufferSize)
218 if(g_sampleBufferSize > 0)
220 for(int c = 0; c < MAX_CHANNELS; c++)
222 delete [] (g_sampleBuffer[c]);
223 g_sampleBuffer[c] = NULL;
226 for(int c = 0; c < MAX_CHANNELS; c++)
228 g_sampleBuffer[c] = allocBuffer(numsamples);
230 g_sampleBufferSize = numsamples;
234 static void deinterleave(const short int *const input, const int &numsamples, const int &bps, const int &nch)
236 updateBufferSize(numsamples);
238 if(bps == 16)
240 const short int *ptr = input;
241 for(int i = 0; i < numsamples; i++)
243 for(int c = 0; c < nch; c++)
245 g_sampleBuffer[c][i] = std::min(1.0, std::max(-1.0, (double(*(ptr++)) / double(SHRT_MAX))));
249 else if(bps == 8)
251 const int8_t *ptr = reinterpret_cast<const int8_t*>(input);
252 for(int i = 0; i < numsamples; i++)
254 for(int c = 0; c < nch; c++)
256 g_sampleBuffer[c][i] = std::min(1.0, std::max(-1.0, (double(*(ptr++)) / double(INT8_MAX))));
260 else
262 showErrorMsg("Unsupported bit-depth detected!");
266 static void interleave(short int *const output, const int &numsamples, const int &bps, const int &nch)
268 updateBufferSize(numsamples);
270 if(bps == 16)
272 short int *ptr = output;
273 for(int i = 0; i < numsamples; i++)
275 for(int c = 0; c < nch; c++)
277 *(ptr++) = static_cast<short int>(std::max(double(SHRT_MIN), std::min(double(SHRT_MAX), round(g_sampleBuffer[c][i] * double(SHRT_MAX)))));
281 else if(bps == 8)
283 int8_t *ptr = reinterpret_cast<int8_t*>(output);
284 for(int i = 0; i < numsamples; i++)
286 for(int c = 0; c < nch; c++)
288 *(ptr++) = static_cast<int8_t>(std::max(double(INT8_MIN), std::min(double(INT8_MAX), round(g_sampleBuffer[c][i] * double(INT8_MAX)))));
292 else
294 showErrorMsg("Unsupported bit-depth detected!");
298 static bool createNewInstance(const uint32_t sampleRate, const uint32_t channelCount)
300 if(g_instance)
302 delete g_instance;
303 g_instance = NULL;
306 const DWORD optionFrameLen = regValueGet(REGISTRY_FRAMELEN);
307 const DWORD optionFltrSize = regValueGet(REGISTRY_FLTRSIZE);
311 g_instance = new MDynamicAudioNormalizer
313 channelCount,
314 sampleRate,
315 optionFrameLen ? optionFrameLen : FRAMELEN_DEFAULT,
316 optionFltrSize ? optionFltrSize : FLTRSIZE_DEFAULT
319 catch(...)
321 showErrorMsg("Failed to create Dynamic Audio Normalizer instance!");
322 return false;
325 if(!g_instance->initialize())
327 showErrorMsg("Dynamic Audio Normalizer initialization has failed!");
328 return false;
331 return true;
334 static bool detectWinampVersion(const HWND &hwndParent)
336 unsigned long winampVersion = 0;
337 unsigned long winampVersionMajor = 0;
338 unsigned long winampVersionMinor = 0;
340 if(SendMessageTimeout(hwndParent, WM_WA_IPC, 0, IPC_GETVERSION, SMTO_ABORTIFHUNG, 5000, &winampVersion))
342 if((winampVersion >= 0x5000) && (winampVersion <= 0xFFFFF))
344 winampVersionMajor = (winampVersion >> 0xC);
345 winampVersionMinor = (winampVersion & 0xFF);
348 else
350 winampVersion = 0xFFFFFFFF;
353 outputMessage("[DynAudNorm_WA5] Winamp version is: 0x%X (v%X.%02X)", winampVersion, winampVersionMajor, winampVersionMinor);
355 if(winampVersionMajor < 1)
357 showErrorMsg("Failed to detect Winamp version. Please use Winamp 5.0 or newer!");
358 return false;
361 if(winampVersionMajor < 5)
363 showErrorMsg("Your Winamp version is too old. Please use Winamp 5.0 or newer!");
364 return false;
367 return true;
370 static LRESULT CALLBACK winampWindowHook(const HWND hwnd, const UINT uMsg, const WPARAM wParam, const LPARAM lParam)
372 if ((lParam == IPC_CB_MISC) && ((wParam == IPC_CB_MISC_STATUS) || (wParam == IPC_CB_MISC_TITLE)))
374 InterlockedIncrement(&g_forceReset);
377 if (g_oldWndProc)
379 return g_oldWndProc(hwnd, uMsg, wParam, lParam);
382 return 0L;
385 ///////////////////////////////////////////////////////////////////////////////
386 // Public Functions
387 ///////////////////////////////////////////////////////////////////////////////
389 static void config(struct winampDSPModule *this_mod)
391 outputMessage("[DynAudNorm_WA5] WinampDSP::config(%p)", this_mod);
393 uint32_t major, minor, patch;
394 MDynamicAudioNormalizer::getVersionInfo(major, minor, patch);
396 const char *date, *time, *compiler, *arch; bool debug;
397 MDynamicAudioNormalizer::getBuildInfo(&date, &time, &compiler, &arch, debug);
399 showAboutScreen(major, minor, patch, date, time, compiler, arch, debug, true);
402 static int init(struct winampDSPModule *this_mod)
404 outputMessage("[DynAudNorm_WA5] WinampDSP::init(%p)", this_mod);
405 detectWinampVersion(this_mod->hwndParent);
407 if(g_sampleBufferSize == 0)
409 for(int c = 0; c < MAX_CHANNELS; c++)
411 g_sampleBuffer[c] = allocBuffer(1024);
413 g_sampleBufferSize = 1024;
416 if(!createNewInstance(g_properties.sampleRate, g_properties.channels))
418 return 1;
421 if (!(g_oldWndProc = (WNDPROC)SetWindowLong(this_mod->hwndParent, GWL_WNDPROC, (LONG_PTR)winampWindowHook)))
423 outputMessage("[DynAudNorm_WA5] WARNING: Failed to hook Winamp window!");
426 return 0;
429 static void quit(struct winampDSPModule *this_mod)
431 outputMessage("[DynAudNorm_WA5] WinampDSP::quit(%p)", this_mod);
433 if (g_oldWndProc)
435 if (SetWindowLong(this_mod->hwndParent, GWL_WNDPROC, (LONG_PTR)g_oldWndProc))
437 g_oldWndProc = NULL;
441 if(g_sampleBufferSize > 0)
443 for(int c = 0; c < MAX_CHANNELS; c++)
445 delete [] (g_sampleBuffer[c]);
446 g_sampleBuffer[c] = NULL;
448 g_sampleBufferSize = 0;
452 static int modify_samples(struct winampDSPModule *this_mod, short int *samples, int numsamples, int bps, int nch, int srate)
454 if(numsamples < 1)
456 outputMessage("[DynAudNorm_WA5] Sample count is zero or negative -> nothing to do!");
457 return 0;
460 if(nch > MAX_CHANNELS)
462 showErrorMsg("Maximum channel count has been exceeded!");
463 return 0;
466 if((!g_instance) || (g_properties.bitsPerSample != bps) || (g_properties.channels != nch) || (g_properties.sampleRate != srate))
468 outputMessage("[DynAudNorm_WA5] WinampDSP::modify_samples(mod=%p, num=%d, bps=%d, nch=%d, srate=%d)", this_mod, numsamples, bps, nch, srate);
470 g_properties.bitsPerSample = bps;
471 g_properties.channels = nch;
472 g_properties.sampleRate = srate;
474 if(!createNewInstance(g_properties.sampleRate, g_properties.channels))
476 return 0; /*creating instance failed!*/
479 else if (InterlockedExchange(&g_forceReset, 0L) > 0L)
481 const DWORD tickCount = GetTickCount();
482 if ((tickCount < g_lastResetTick) || ((tickCount - g_lastResetTick) > 250L))
484 if (g_lastResetTick)
486 outputMessage("[DynAudNorm_WA5] Reset has been triggered!");
487 g_instance->reset();
489 g_lastResetTick = tickCount;
493 deinterleave(samples, numsamples, bps, nch);
495 int64_t outputSize;
496 g_instance->processInplace(g_sampleBuffer, numsamples, outputSize);
498 interleave(samples, static_cast<int>(outputSize), bps, nch);
499 return static_cast<int>(outputSize);
502 ///////////////////////////////////////////////////////////////////////////////
503 // Winamp DSP API
504 ///////////////////////////////////////////////////////////////////////////////
506 static int8_t g_initialized = -1;
507 static MY_CRITSEC_INIT(g_createEffMutex);
509 static void appendStr(wchar_t *buffer, const size_t &size, const wchar_t *const text, ...)
511 wchar_t temp[128];
512 va_list argList;
513 va_start(argList, text);
514 _vsnwprintf_s(temp, 128, _TRUNCATE, text, argList);
515 va_end(argList);
516 wcsncat_s(buffer, size, temp, _TRUNCATE);
519 static bool showAboutScreen(const uint32_t &major, const uint32_t &minor, const uint32_t &patch, const char *const date, const char *const time, const char *const compiler, const char *const arch, const bool &debug, const bool &config)
521 wchar_t text[1024] = { '\0' };
522 appendStr(text, 1024, L"Dynamic Audio Normalizer, Winamp Wrapper, Version %u.%02u-%u, %s\n", major, minor, patch, (debug ? L"DEBGU" : L"Release"));
523 appendStr(text, 1024, L"Copyright (c) 2014-2019 LoRd_MuldeR <mulder2@gmx.de>. Some rights reserved.\n");
524 appendStr(text, 1024, L"Built on %S at %S with %S for %S.\n\n", date, time, compiler, arch);
525 appendStr(text, 1024, L"This library is free software; you can redistribute it and/or\n");
526 appendStr(text, 1024, L"modify it under the terms of the GNU Lesser General Public\n");
527 appendStr(text, 1024, L"License as published by the Free Software Foundation; either\n");
528 appendStr(text, 1024, L"version 2.1 of the License, or (at your option) any later version.\n\n");
529 appendStr(text, 1024, L"This library is distributed in the hope that it will be useful,\n");
530 appendStr(text, 1024, L"but WITHOUT ANY WARRANTY; without even the implied warranty of\n");
531 appendStr(text, 1024, L"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n");
532 appendStr(text, 1024, L"Lesser General Public License for more details.\n");
534 if(config)
536 MessageBoxW(NULL, text, L"Dynamic Audio Normalizer", MB_TOPMOST | MB_TASKMODAL | MB_OK);
537 return true;
539 else
541 appendStr(text, 1024, L"\nPlease click 'OK' if you agree to the above notice or 'Cancel' otherwise...\n");
542 const int ret = MessageBoxW(NULL, text, L"Dynamic Audio Normalizer", MB_TOPMOST | MB_TASKMODAL | MB_OKCANCEL | MB_DEFBUTTON2);
543 return (ret == IDOK);
547 static bool initializeCoreLibrary(void)
549 MDynamicAudioNormalizer::setLogFunction(logFunction);
551 uint32_t major, minor, patch;
552 MDynamicAudioNormalizer::getVersionInfo(major, minor, patch);
554 outputMessage("[DynAudNorm_WA5] Dynamic Audio Normalizer Winamp-Wrapper (v%u.%02u-%u)", major, minor, patch);
555 _snprintf_s(g_description, 256, _TRUNCATE, "Dynamic Audio Normalizer v%u.%02u-%u [by LoRd_MuldeR]", major, minor, patch);
557 const DWORD version = (1000u * major) + (10u * minor) + patch;
558 if(regValueGet(REGISTRY_VERSION) != version)
560 const char *date, *time, *compiler, *arch; bool debug;
561 MDynamicAudioNormalizer::getBuildInfo(&date, &time, &compiler, &arch, debug);
563 if(!showAboutScreen(major, minor, patch, date, time, compiler, arch, debug, false))
565 return false;
567 regValueSet(REGISTRY_VERSION, version);
568 regValueSet(REGISTRY_FRAMELEN, FRAMELEN_DEFAULT);
569 regValueSet(REGISTRY_FLTRSIZE, FLTRSIZE_DEFAULT);
572 return true;
575 static winampDSPModule *getModule(int which)
577 outputMessage("[DynAudNorm_WA5] WinampDSP::getModule(%d)", which);
579 winampDSPModule *module = NULL;
580 MY_CRITSEC_ENTER(g_createEffMutex);
582 if(which == 0)
584 if(g_initialized < 0)
586 g_initialized = initializeCoreLibrary() ? 1 : 0;
588 if(g_initialized > 0)
590 module = &g_module;
594 MY_CRITSEC_LEAVE(g_createEffMutex);
595 return module;
598 static int sf(int v) /*Note: The "sf" function was copied 1:1 from the example code, I have NO clue what it does!*/
600 int res;
601 res = v * (unsigned long)1103515245;
602 res += (unsigned long)13293;
603 res &= (unsigned long)0x7FFFFFFF;
604 res ^= v;
605 return res;
608 extern "C" DLL_EXPORT winampDSPHeader *winampDSPGetHeader2()
610 outputMessage("[DynAudNorm_WA5] WinampDSP::winampDSPGetHeader2()");
611 return &g_header;