1 //////////////////////////////////////////////////////////////////////////////////
2 // Dynamic Audio Normalizer - Winamp DSP Wrapper
3 // Copyright (c) 2014-2019 LoRd_MuldeR <mulder2@gmx.de>. Some rights reserved.
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
23 // http://opensource.org/licenses/MIT
24 //////////////////////////////////////////////////////////////////////////////////
34 #define WIN32_LEAN_AND_MEAN 1
38 #include <winamp_dsp.h>
39 #include <winamp_ipc.h>
45 //Dynamic Audio Normalizer API
46 #include <DynamicAudioNormalizer.h>
49 #define DLL_EXPORT __declspec(dllexport)
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";
58 static const DWORD FRAMELEN_DEFAULT
= 500;
59 static const DWORD FLTRSIZE_DEFAULT
= 31;
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);
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
);
75 static const size_t MAX_CHANNELS
= 12;
77 ///////////////////////////////////////////////////////////////////////////////
79 ///////////////////////////////////////////////////////////////////////////////
81 static char g_description
[256] = { '\0' };
83 static winampDSPHeader g_header
=
86 "Dynamic Audio Normalizer [" __DATE__
"]",
91 static winampDSPModule g_module
=
105 uint32_t bitsPerSample
;
113 static volatile WNDPROC g_oldWndProc
= NULL
;
114 static volatile LONG g_forceReset
= 0;
115 static volatile DWORD g_lastResetTick
= 0;
117 ///////////////////////////////////////////////////////////////////////////////
119 ///////////////////////////////////////////////////////////////////////////////
121 static void outputMessage(const char *const format
, ...)
123 MY_CRITSEC_ENTER(g_loggingMutex
);
126 va_start(argList
, format
);
127 vsnprintf_s(g_loggingBuffer
, 1024, _TRUNCATE
, format
, argList
);
129 OutputDebugStringA(g_loggingBuffer
);
131 MY_CRITSEC_LEAVE(g_loggingMutex
);
134 static void logFunction(const int logLevel
, const char *const message
)
138 case MDynamicAudioNormalizer::LOG_LEVEL_DBG
:
139 outputMessage("[DynAudNorm_WA5] NFO: %s\n", message
);
141 case MDynamicAudioNormalizer::LOG_LEVEL_WRN
:
142 outputMessage("[DynAudNorm_WA5] WRN: %s\n", message
);
144 case MDynamicAudioNormalizer::LOG_LEVEL_ERR
:
145 outputMessage("[DynAudNorm_WA5] ERR: %s\n", message
);
148 outputMessage("[DynAudNorm_WA5] DBG: %s\n", message
);
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
)))
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
)
190 static double *allocBuffer(size_t size
)
192 double *buffer
= NULL
;
195 buffer
= new double[size
];
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!");
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
);
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
))));
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
))));
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
);
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
)))));
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
)))));
294 showErrorMsg("Unsupported bit-depth detected!");
298 static bool createNewInstance(const uint32_t sampleRate
, const uint32_t channelCount
)
306 const DWORD optionFrameLen
= regValueGet(REGISTRY_FRAMELEN
);
307 const DWORD optionFltrSize
= regValueGet(REGISTRY_FLTRSIZE
);
311 g_instance
= new MDynamicAudioNormalizer
315 optionFrameLen
? optionFrameLen
: FRAMELEN_DEFAULT
,
316 optionFltrSize
? optionFltrSize
: FLTRSIZE_DEFAULT
321 showErrorMsg("Failed to create Dynamic Audio Normalizer instance!");
325 if(!g_instance
->initialize())
327 showErrorMsg("Dynamic Audio Normalizer initialization has failed!");
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);
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!");
361 if(winampVersionMajor
< 5)
363 showErrorMsg("Your Winamp version is too old. Please use Winamp 5.0 or newer!");
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
);
379 return g_oldWndProc(hwnd
, uMsg
, wParam
, lParam
);
385 ///////////////////////////////////////////////////////////////////////////////
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
))
421 if (!(g_oldWndProc
= (WNDPROC
)SetWindowLong(this_mod
->hwndParent
, GWL_WNDPROC
, (LONG_PTR
)winampWindowHook
)))
423 outputMessage("[DynAudNorm_WA5] WARNING: Failed to hook Winamp window!");
429 static void quit(struct winampDSPModule
*this_mod
)
431 outputMessage("[DynAudNorm_WA5] WinampDSP::quit(%p)", this_mod
);
435 if (SetWindowLong(this_mod
->hwndParent
, GWL_WNDPROC
, (LONG_PTR
)g_oldWndProc
))
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
)
456 outputMessage("[DynAudNorm_WA5] Sample count is zero or negative -> nothing to do!");
460 if(nch
> MAX_CHANNELS
)
462 showErrorMsg("Maximum channel count has been exceeded!");
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))
486 outputMessage("[DynAudNorm_WA5] Reset has been triggered!");
489 g_lastResetTick
= tickCount
;
493 deinterleave(samples
, numsamples
, bps
, nch
);
496 g_instance
->processInplace(g_sampleBuffer
, numsamples
, outputSize
);
498 interleave(samples
, static_cast<int>(outputSize
), bps
, nch
);
499 return static_cast<int>(outputSize
);
502 ///////////////////////////////////////////////////////////////////////////////
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
, ...)
513 va_start(argList
, text
);
514 _vsnwprintf_s(temp
, 128, _TRUNCATE
, text
, 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");
536 MessageBoxW(NULL
, text
, L
"Dynamic Audio Normalizer", MB_TOPMOST
| MB_TASKMODAL
| MB_OK
);
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))
567 regValueSet(REGISTRY_VERSION
, version
);
568 regValueSet(REGISTRY_FRAMELEN
, FRAMELEN_DEFAULT
);
569 regValueSet(REGISTRY_FLTRSIZE
, FLTRSIZE_DEFAULT
);
575 static winampDSPModule
*getModule(int which
)
577 outputMessage("[DynAudNorm_WA5] WinampDSP::getModule(%d)", which
);
579 winampDSPModule
*module
= NULL
;
580 MY_CRITSEC_ENTER(g_createEffMutex
);
584 if(g_initialized
< 0)
586 g_initialized
= initializeCoreLibrary() ? 1 : 0;
588 if(g_initialized
> 0)
594 MY_CRITSEC_LEAVE(g_createEffMutex
);
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!*/
601 res
= v
* (unsigned long)1103515245;
602 res
+= (unsigned long)13293;
603 res
&= (unsigned long)0x7FFFFFFF;
608 extern "C" DLL_EXPORT winampDSPHeader
*winampDSPGetHeader2()
610 outputMessage("[DynAudNorm_WA5] WinampDSP::winampDSPGetHeader2()");