From ba618d308c859f423cf67c8cb368e4b65c15c7bd Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Mon, 8 Mar 2021 13:56:57 +0100 Subject: [PATCH 1/4] - bumped CMake version to 3.1.0 in all projects to reduce warning spam in recent versions. 3.1.0 is the highest minimum set in the existing subprojects so this will not exclude anything that hadn't been already. --- libraries/asmjit/CMakeLists.txt | 2 +- libraries/bzip2/CMakeLists.txt | 2 +- libraries/gdtoa/CMakeLists.txt | 2 +- libraries/glslang/OGLCompilersDLL/CMakeLists.txt | 2 +- libraries/glslang/glslang/CMakeLists.txt | 2 +- libraries/glslang/spirv/CMakeLists.txt | 2 +- libraries/jpeg/CMakeLists.txt | 2 +- libraries/lzma/CMakeLists.txt | 2 +- libraries/zlib/CMakeLists.txt | 2 +- src/CMakeLists.txt | 3 ++- tools/CMakeLists.txt | 2 +- tools/lemon/CMakeLists.txt | 2 +- tools/re2c/CMakeLists.txt | 2 +- tools/zipdir/CMakeLists.txt | 2 +- wadsrc/CMakeLists.txt | 2 +- wadsrc_bm/CMakeLists.txt | 2 +- wadsrc_extra/CMakeLists.txt | 2 +- wadsrc_lights/CMakeLists.txt | 2 +- wadsrc_widescreen/CMakeLists.txt | 2 +- 19 files changed, 20 insertions(+), 19 deletions(-) diff --git a/libraries/asmjit/CMakeLists.txt b/libraries/asmjit/CMakeLists.txt index 6b7636ebe98..283b54fbc7c 100644 --- a/libraries/asmjit/CMakeLists.txt +++ b/libraries/asmjit/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8.7) +cmake_minimum_required( VERSION 3.1.0 ) #make_release_only() diff --git a/libraries/bzip2/CMakeLists.txt b/libraries/bzip2/CMakeLists.txt index e01d5c09e3f..6ca7a4e6d9a 100644 --- a/libraries/bzip2/CMakeLists.txt +++ b/libraries/bzip2/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required( VERSION 2.8.7 ) +cmake_minimum_required( VERSION 3.1.0 ) make_release_only() diff --git a/libraries/gdtoa/CMakeLists.txt b/libraries/gdtoa/CMakeLists.txt index 485f3778bf7..834340909d6 100644 --- a/libraries/gdtoa/CMakeLists.txt +++ b/libraries/gdtoa/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required( VERSION 2.8.7 ) +cmake_minimum_required( VERSION 3.1.0 ) set( CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -D_DEBUG" ) diff --git a/libraries/glslang/OGLCompilersDLL/CMakeLists.txt b/libraries/glslang/OGLCompilersDLL/CMakeLists.txt index c6e6c8fc260..392fa79005b 100644 --- a/libraries/glslang/OGLCompilersDLL/CMakeLists.txt +++ b/libraries/glslang/OGLCompilersDLL/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8.12) +cmake_minimum_required( VERSION 3.1.0 ) make_release_only() use_fast_math() diff --git a/libraries/glslang/glslang/CMakeLists.txt b/libraries/glslang/glslang/CMakeLists.txt index 3cd287b39ff..30b57c5889b 100644 --- a/libraries/glslang/glslang/CMakeLists.txt +++ b/libraries/glslang/glslang/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8.12) +cmake_minimum_required( VERSION 3.1.0 ) make_release_only() use_fast_math() diff --git a/libraries/glslang/spirv/CMakeLists.txt b/libraries/glslang/spirv/CMakeLists.txt index d39a6e3009d..16df27d7b81 100644 --- a/libraries/glslang/spirv/CMakeLists.txt +++ b/libraries/glslang/spirv/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8.12) +cmake_minimum_required( VERSION 3.1.0 ) make_release_only() use_fast_math() diff --git a/libraries/jpeg/CMakeLists.txt b/libraries/jpeg/CMakeLists.txt index 33a3938cf07..d57779495e4 100644 --- a/libraries/jpeg/CMakeLists.txt +++ b/libraries/jpeg/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required( VERSION 2.8.7 ) +cmake_minimum_required( VERSION 3.1.0 ) make_release_only() diff --git a/libraries/lzma/CMakeLists.txt b/libraries/lzma/CMakeLists.txt index b0d7565cd15..570265049cf 100644 --- a/libraries/lzma/CMakeLists.txt +++ b/libraries/lzma/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required( VERSION 2.8.7 ) +cmake_minimum_required( VERSION 3.1.0 ) make_release_only() diff --git a/libraries/zlib/CMakeLists.txt b/libraries/zlib/CMakeLists.txt index a1d6637a989..14f0175f656 100644 --- a/libraries/zlib/CMakeLists.txt +++ b/libraries/zlib/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8.7) +cmake_minimum_required( VERSION 3.1.0 ) set(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS ON) make_release_only() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d44bf47163e..b67730a4c6c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required( VERSION 2.8.7 ) +cmake_minimum_required( VERSION 3.1.0 ) include(precompiled_headers) @@ -1190,6 +1190,7 @@ add_executable( zdoom WIN32 MACOSX_BUNDLE ${PCH_SOURCES} common/utility/x86.cpp common/thirdparty/strnatcmp.c + common/thirdparty/gain_analysis.c common/utility/zstring.cpp common/utility/findfile.cpp common/thirdparty/math/asin.c diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index b3fed70ff39..8725c56e81b 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required( VERSION 2.8.7 ) +cmake_minimum_required( VERSION 3.1.0 ) add_subdirectory( re2c ) add_subdirectory( lemon ) diff --git a/tools/lemon/CMakeLists.txt b/tools/lemon/CMakeLists.txt index e092cf6e92c..dd1d41bf188 100644 --- a/tools/lemon/CMakeLists.txt +++ b/tools/lemon/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required( VERSION 2.8.7 ) +cmake_minimum_required( VERSION 3.1.0 ) if( NOT CMAKE_CROSSCOMPILING ) set( CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -D_DEBUG" ) diff --git a/tools/re2c/CMakeLists.txt b/tools/re2c/CMakeLists.txt index b362a3b843b..9fcfef5a24c 100644 --- a/tools/re2c/CMakeLists.txt +++ b/tools/re2c/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required( VERSION 2.8.7 ) +cmake_minimum_required( VERSION 3.1.0 ) if( NOT CMAKE_CROSSCOMPILING ) diff --git a/tools/zipdir/CMakeLists.txt b/tools/zipdir/CMakeLists.txt index 6a36b2cb5dd..65eb2fb72a3 100644 --- a/tools/zipdir/CMakeLists.txt +++ b/tools/zipdir/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required( VERSION 2.8.7 ) +cmake_minimum_required( VERSION 3.1.0 ) if( NOT CMAKE_CROSSCOMPILING ) include_directories( "${ZLIB_INCLUDE_DIR}" "${BZIP2_INCLUDE_DIR}" "${LZMA_INCLUDE_DIR}" ) diff --git a/wadsrc/CMakeLists.txt b/wadsrc/CMakeLists.txt index 80189a328cd..9e439386479 100644 --- a/wadsrc/CMakeLists.txt +++ b/wadsrc/CMakeLists.txt @@ -1,3 +1,3 @@ -cmake_minimum_required( VERSION 2.8.7 ) +cmake_minimum_required( VERSION 3.1.0 ) add_pk3(gzdoom.pk3 ${CMAKE_CURRENT_SOURCE_DIR}/static) diff --git a/wadsrc_bm/CMakeLists.txt b/wadsrc_bm/CMakeLists.txt index a76c5dc36b2..e0268d1a36d 100644 --- a/wadsrc_bm/CMakeLists.txt +++ b/wadsrc_bm/CMakeLists.txt @@ -1,3 +1,3 @@ -cmake_minimum_required( VERSION 2.4 ) +cmake_minimum_required( VERSION 3.1.0 ) add_pk3(brightmaps.pk3 ${CMAKE_CURRENT_SOURCE_DIR}/static) diff --git a/wadsrc_extra/CMakeLists.txt b/wadsrc_extra/CMakeLists.txt index 0eacb8a5323..d9a1b272fe2 100644 --- a/wadsrc_extra/CMakeLists.txt +++ b/wadsrc_extra/CMakeLists.txt @@ -1,3 +1,3 @@ -cmake_minimum_required( VERSION 2.8.7 ) +cmake_minimum_required( VERSION 3.1.0 ) add_pk3(game_support.pk3 ${CMAKE_CURRENT_SOURCE_DIR}/static) diff --git a/wadsrc_lights/CMakeLists.txt b/wadsrc_lights/CMakeLists.txt index 92f89314a74..4f9d87d7cd0 100644 --- a/wadsrc_lights/CMakeLists.txt +++ b/wadsrc_lights/CMakeLists.txt @@ -1,3 +1,3 @@ -cmake_minimum_required( VERSION 2.4 ) +cmake_minimum_required( VERSION 3.1.0 ) add_pk3(lights.pk3 ${CMAKE_CURRENT_SOURCE_DIR}/static) diff --git a/wadsrc_widescreen/CMakeLists.txt b/wadsrc_widescreen/CMakeLists.txt index 14126355a72..83ed458e3ff 100644 --- a/wadsrc_widescreen/CMakeLists.txt +++ b/wadsrc_widescreen/CMakeLists.txt @@ -1,3 +1,3 @@ -cmake_minimum_required( VERSION 2.4 ) +cmake_minimum_required( VERSION 3.1.0 ) add_pk3(game_widescreen_gfx.pk3 ${CMAKE_CURRENT_SOURCE_DIR}/static) From f11780600f0e6e2c80fedfdc4b3eb7e85e09efc5 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Wed, 10 Mar 2021 23:06:21 +0100 Subject: [PATCH 2/4] - implemented replay gain calculation and management. This is done entirely on the streamed sound data, unlike the old relative volume which uses the backend's volume setting. --- bin/windows/zmusic/include/zmusic.h | 4 +- src/CMakeLists.txt | 2 +- src/common/audio/music/i_music.cpp | 16 - src/common/audio/music/i_music.h | 10 + src/common/audio/music/music.cpp | 252 +++++++++++- src/common/audio/music/music_config.cpp | 5 + src/common/audio/music/s_music.h | 4 + src/common/audio/sound/i_sound.cpp | 15 +- src/common/thirdparty/gain_analysis.cpp | 486 ++++++++++++++++++++++++ src/common/thirdparty/gain_analysis.h | 92 +++++ wadsrc/static/menudef.txt | 1 - 11 files changed, 861 insertions(+), 26 deletions(-) create mode 100644 src/common/thirdparty/gain_analysis.cpp create mode 100644 src/common/thirdparty/gain_analysis.h diff --git a/bin/windows/zmusic/include/zmusic.h b/bin/windows/zmusic/include/zmusic.h index 0dd186c15f6..7de05c5fa3b 100644 --- a/bin/windows/zmusic/include/zmusic.h +++ b/bin/windows/zmusic/include/zmusic.h @@ -29,7 +29,8 @@ typedef enum EMIDIType_ MIDI_MIDI, MIDI_HMI, MIDI_XMI, - MIDI_MUS + MIDI_MUS, + MIDI_MIDS } EMIDIType; typedef enum EMidiDevice_ @@ -313,6 +314,7 @@ extern "C" DLL_IMPORT void ZMusic_Close(ZMusic_MusicStream song); DLL_IMPORT zmusic_bool ZMusic_SetSubsong(ZMusic_MusicStream song, int subsong); DLL_IMPORT zmusic_bool ZMusic_IsLooping(ZMusic_MusicStream song); + DLL_IMPORT int ZMusic_GetDeviceType(ZMusic_MusicStream song); DLL_IMPORT zmusic_bool ZMusic_IsMIDI(ZMusic_MusicStream song); DLL_IMPORT void ZMusic_VolumeChanged(ZMusic_MusicStream song); DLL_IMPORT zmusic_bool ZMusic_WriteSMF(ZMusic_MidiSource source, const char* fn, int looplimit); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b67730a4c6c..2f197aa3b49 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -992,6 +992,7 @@ set (PCH_SOURCES common/2d/v_2ddrawer.cpp common/2d/v_drawtext.cpp common/2d/v_draw.cpp + common/thirdparty/gain_analysis.cpp common/thirdparty/sfmt/SFMT.cpp common/fonts/singlelumpfont.cpp common/fonts/singlepicfont.cpp @@ -1190,7 +1191,6 @@ add_executable( zdoom WIN32 MACOSX_BUNDLE ${PCH_SOURCES} common/utility/x86.cpp common/thirdparty/strnatcmp.c - common/thirdparty/gain_analysis.c common/utility/zstring.cpp common/utility/findfile.cpp common/thirdparty/math/asin.c diff --git a/src/common/audio/music/i_music.cpp b/src/common/audio/music/i_music.cpp index b73c630ca6e..08b8fcfd7da 100644 --- a/src/common/audio/music/i_music.cpp +++ b/src/common/audio/music/i_music.cpp @@ -270,22 +270,6 @@ void I_SetMusicVolume (double factor) I_SetRelativeVolume((float)factor); } -//========================================================================== -// -// test a relative music volume -// -//========================================================================== - -CCMD(testmusicvol) -{ - if (argv.argc() > 1) - { - I_SetRelativeVolume((float)strtod(argv[1], nullptr)); - } - else - Printf("Current relative volume is %1.2f\n", relative_volume); -} - //========================================================================== // // STAT music diff --git a/src/common/audio/music/i_music.h b/src/common/audio/music/i_music.h index b552f5c239e..85be1dea24b 100644 --- a/src/common/audio/music/i_music.h +++ b/src/common/audio/music/i_music.h @@ -56,4 +56,14 @@ EXTERN_CVAR(Bool, mus_enabled) EXTERN_CVAR(Float, snd_musicvolume) +inline float AmplitudeTodB(float amplitude) +{ + return 20.0f * log10(amplitude); +} + +inline float dBToAmplitude(float dB) +{ + return pow(10.0f, dB / 20.0f); +} + #endif //__I_MUSIC_H__ diff --git a/src/common/audio/music/music.cpp b/src/common/audio/music/music.cpp index c10474d9023..77efb971534 100644 --- a/src/common/audio/music/music.cpp +++ b/src/common/audio/music/music.cpp @@ -49,6 +49,10 @@ #include "s_music.h" #include "filereadermusicinterface.h" #include +#include "md5.h" +#include "gain_analysis.h" +#include "gameconfigfile.h" +#include "i_specialpaths.h" // EXTERNAL FUNCTION PROTOTYPES -------------------------------------------- @@ -78,6 +82,14 @@ static MusicCallbacks mus_cb = { nullptr, DefaultOpenMusic }; // PUBLIC DATA DEFINITIONS ------------------------------------------------- +CVAR(Bool, mus_calcgain, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) // changing this will only take effect for the next song. +CVAR(Bool, mus_usereplaygain, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) // changing this will only take effect for the next song. +CUSTOM_CVAR(Float, mus_gainoffset, 0.f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) // for customizing the base volume +{ + if (self > 10.f) self = 10.f; + mus_playing.replayGainFactor = dBToAmplitude(mus_playing.replayGain + mus_gainoffset); +} + // CODE -------------------------------------------------------------------- void S_SetMusicCallbacks(MusicCallbacks* cb) @@ -115,9 +127,33 @@ void S_StopCustomStream(SoundStream *stream) } +static TArray convert; static bool FillStream(SoundStream* stream, void* buff, int len, void* userdata) { - bool written = ZMusic_FillStream(mus_playing.handle, buff, len); + bool written; + if (mus_playing.isfloat) + { + written = ZMusic_FillStream(mus_playing.handle, buff, len); + if (mus_playing.replayGainFactor != 1.f) + { + float* fbuf = (float*)buff; + for (int i = 0; i < len / 4; i++) + { + fbuf[i] *= mus_playing.replayGainFactor; + } + } + } + else + { + // To apply replay gain we need floating point streaming data, so 16 bit input needs to be converted here. + convert.Resize(len / 2); + written = ZMusic_FillStream(mus_playing.handle, convert.Data(), len/2); + float* fbuf = (float*)buff; + for (int i = 0; i < len / 4; i++) + { + fbuf[i] = convert[i] * mus_playing.replayGainFactor * (1.f/32768.f); + } + } if (!written) { @@ -133,9 +169,12 @@ void S_CreateStream() if (!mus_playing.handle) return; SoundStreamInfo fmt; ZMusic_GetStreamInfo(mus_playing.handle, &fmt); + // always create a floating point streaming buffer so we can apply replay gain without risk of integer overflows. + mus_playing.isfloat = fmt.mNumChannels > 0; + if (!mus_playing.isfloat) fmt.mBufferSize *= 2; if (fmt.mBufferSize > 0) // if buffer size is 0 the library will play the song itself (e.g. Windows system synth.) { - int flags = fmt.mNumChannels < 0 ? 0 : SoundStream::Float; + int flags = SoundStream::Float; if (abs(fmt.mNumChannels) < 2) flags |= SoundStream::Mono; musicStream.reset(GSnd->CreateStream(FillStream, fmt.mBufferSize, flags, fmt.mSampleRate, nullptr)); @@ -167,7 +206,7 @@ void S_StopStream() static bool S_StartMusicPlaying(ZMusic_MusicStream song, bool loop, float rel_vol, int subsong) { - if (rel_vol > 0.f) + if (rel_vol > 0.f && !mus_usereplaygain) { float factor = relative_volume / saved_relative_volume; saved_relative_volume = rel_vol; @@ -312,6 +351,211 @@ bool S_StartMusic (const char *m_id) // initiates playback of a song // //========================================================================== +static TMap gainMap; + +static FString ReplayGainHash(ZMusicCustomReader* reader, int flength, int playertype, const char* playparam) +{ + uint8_t buffer[50000]; // for performance reasons only hash the start of the file. If we wanted to do this to large waveform songs it'd cause noticable lag. + uint8_t digest[16]; + char digestout[33]; + auto length = reader->read(reader, buffer, 50000); + reader->seek(reader, 0, SEEK_SET); + MD5Context md5; + md5.Init(); + md5.Update(buffer, (int)length); + md5.Final(digest); + + for (size_t j = 0; j < sizeof(digest); ++j) + { + sprintf(digestout + (j * 2), "%02X", digest[j]); + } + digestout[32] = 0; + + auto type = ZMusic_IdentifyMIDIType((uint32_t*)buffer, 32); + if (type == MIDI_NOTMIDI) return FStringf("%d:%s", flength, digestout); + if (playertype == -1) + { + // todo: get the defaults for MIDI synth and used sound font. + } + return FStringf("%d:%s:%d:%s", flength, digestout, playertype, playparam); +} + +static void SaveGains() +{ + auto path = M_GetAppDataPath(true); + path << "/replaygain.ini"; + FConfigFile gains(path); + TMap::Iterator it(gainMap); + TMap::Pair* pair; + + if (gains.SetSection("Gains", true)) + { + while (it.NextPair(pair)) + { + gains.SetValueForKey(pair->Key, std::to_string(pair->Value).c_str()); + } + } + gains.WriteConfigFile(); +} + +static void ReadGains() +{ + static bool done = false; + if (done) return; + done = true; + auto path = M_GetAppDataPath(true); + path << "/replaygain.ini"; + FConfigFile gains(path); + if (gains.SetSection("Gains")) + { + const char* key; + const char* value; + + while (gains.NextInSection(key, value)) + { + gainMap.Insert(key, (float)strtod(value, nullptr)); + } + } +} + +CCMD(setreplaygain) +{ + // sets replay gain for current song to a fixed value + if (!mus_playing.handle || mus_playing.hash.IsEmpty()) + { + Printf("setreplaygain needs some music playing\n"); + return; + } + if (argv.argc() < 2) + { + Printf("Usage: setreplaygain {dB}\n"); + Printf("Current replay gain is %f dB\n", mus_playing.replayGain); + return; + } + float dB = (float)strtod(argv[1], nullptr); + if (dB > 10) dB = 10; // don't blast the speakers. Values above 2 or 3 are very rare. + gainMap.Insert(mus_playing.hash, dB); + SaveGains(); + mus_playing.replayGain = dB; + mus_playing.replayGainFactor = (float)dBToAmplitude(mus_playing.replayGain + mus_gainoffset); +} + +static void CheckReplayGain(const char *musicname, EMidiDevice playertype, const char *playparam) +{ + mus_playing.replayGain = 0.f; + mus_playing.replayGainFactor = dBToAmplitude(mus_gainoffset); + if (!mus_usereplaygain) return; + + FileReader reader = mus_cb.OpenMusic(musicname); + if (!reader.isOpen()) return; + int flength = (int)reader.GetLength(); + auto mreader = GetMusicReader(reader); // this passes the file reader to the newly created wrapper. + + ReadGains(); + auto hash = ReplayGainHash(mreader, flength, playertype, playparam); + mus_playing.hash = hash; + auto entry = gainMap.CheckKey(hash); + if (entry) + { + mus_playing.replayGain = *entry; + mus_playing.replayGainFactor = dBToAmplitude(mus_playing.replayGain + mus_gainoffset); + return; + } + if (!mus_calcgain) return; + + auto handle = ZMusic_OpenSong(mreader, playertype, playparam); + if (handle == nullptr) return; // not a music file + + if (!ZMusic_Start(handle, 0, false)) + { + ZMusic_Close(handle); + return; // unable to open + } + + SoundStreamInfo fmt; + ZMusic_GetStreamInfo(handle, &fmt); + if (fmt.mBufferSize == 0) + { + ZMusic_Close(handle); + return; // external player. + } + + int flags = SoundStream::Float; + if (abs(fmt.mNumChannels) < 2) flags |= SoundStream::Mono; + + TArray readbuffer(fmt.mBufferSize, true); + TArray lbuffer; + TArray rbuffer; + while (ZMusic_FillStream(handle, readbuffer.Data(), fmt.mBufferSize)) + { + unsigned index; + // 4 cases, all with different preparation needs. + if (fmt.mNumChannels == -2) // 16 bit stereo + { + int16_t* sbuf = (int16_t*)readbuffer.Data(); + int numsamples = fmt.mBufferSize / 4; + index = lbuffer.Reserve(numsamples); + rbuffer.Reserve(numsamples); + + for (int i = 0; i < numsamples; i++) + { + lbuffer[index + i] = sbuf[i * 2]; + rbuffer[index + i] = sbuf[i * 2 + 1]; + } + } + else if (fmt.mNumChannels == -1) // 16 bit mono + { + int16_t* sbuf = (int16_t*)readbuffer.Data(); + int numsamples = fmt.mBufferSize / 2; + index = lbuffer.Reserve(numsamples); + + for (int i = 0; i < numsamples; i++) + { + lbuffer[index + i] = sbuf[i]; + } + } + else if (fmt.mNumChannels == 1) // float mono + { + float* sbuf = (float*)readbuffer.Data(); + int numsamples = fmt.mBufferSize / 4; + index = lbuffer.Reserve(numsamples); + for (int i = 0; i < numsamples; i++) + { + lbuffer[index + i] = sbuf[i] * 32768.f; + } + } + else if (fmt.mNumChannels == 2) // float stereo + { + float* sbuf = (float*)readbuffer.Data(); + int numsamples = fmt.mBufferSize / 8; + auto index = lbuffer.Reserve(numsamples); + rbuffer.Reserve(numsamples); + + for (int i = 0; i < numsamples; i++) + { + lbuffer[index + i] = sbuf[i * 2] * 32768.f; + rbuffer[index + i] = sbuf[i * 2 + 1] * 32768.f; + } + } + float accTime = lbuffer.Size() / (float)fmt.mSampleRate; + if (accTime > 8 * 60) break; // do at most 8 minutes, if the song forces a loop. + } + ZMusic_Close(handle); + + GainAnalyzer analyzer; + analyzer.InitGainAnalysis(fmt.mSampleRate); + int result = analyzer.AnalyzeSamples(lbuffer.Data(), rbuffer.Size() == 0 ? nullptr : rbuffer.Data(), lbuffer.Size(), rbuffer.Size() == 0? 1: 2); + if (result == GAIN_ANALYSIS_OK) + { + auto gain = analyzer.GetTitleGain(); + Printf("Calculated replay gain for %s at %f dB\n", hash.GetChars(), gain); + + gainMap.Insert(hash, gain); + mus_playing.replayGain = gain; + mus_playing.replayGainFactor = dBToAmplitude(mus_playing.replayGain + mus_gainoffset); + SaveGains(); + } +} bool S_ChangeMusic(const char* musicname, int order, bool looping, bool force) { @@ -397,6 +641,8 @@ bool S_ChangeMusic(const char* musicname, int order, bool looping, bool force) } else { + + CheckReplayGain(musicname, devp ? (EMidiDevice)devp->device : MDEV_DEFAULT, devp ? devp->args.GetChars() : ""); auto mreader = GetMusicReader(reader); // this passes the file reader to the newly created wrapper. mus_playing.handle = ZMusic_OpenSong(mreader, devp ? (EMidiDevice)devp->device : MDEV_DEFAULT, devp ? devp->args.GetChars() : ""); if (mus_playing.handle == nullptr) diff --git a/src/common/audio/music/music_config.cpp b/src/common/audio/music/music_config.cpp index e0b341b0a95..935d93d8070 100644 --- a/src/common/audio/music/music_config.cpp +++ b/src/common/audio/music/music_config.cpp @@ -453,6 +453,11 @@ CUSTOM_CVAR(Int, snd_streambuffersize, 64, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CV CUSTOM_CVAR(Int, mod_samplerate, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG | CVAR_VIRTUAL) { + if (self != 0 && self != 11025 && self != 22050 && self != 44100 && self != 48000) + { + self = 0; + return; + } FORWARD_CVAR(mod_samplerate); } diff --git a/src/common/audio/music/s_music.h b/src/common/audio/music/s_music.h index 8b6e3318861..ef852273c45 100644 --- a/src/common/audio/music/s_music.h +++ b/src/common/audio/music/s_music.h @@ -73,8 +73,12 @@ struct MusPlayingInfo FString name; ZMusic_MusicStream handle; int baseorder; + float replayGain; + float replayGainFactor; bool loop; + bool isfloat; FString LastSong; // last music that was played + FString hash; // for setting replay gain while playing. }; extern MusPlayingInfo mus_playing; diff --git a/src/common/audio/sound/i_sound.cpp b/src/common/audio/sound/i_sound.cpp index 209f58a4758..6855ce51b55 100644 --- a/src/common/audio/sound/i_sound.cpp +++ b/src/common/audio/sound/i_sound.cpp @@ -51,11 +51,18 @@ EXTERN_CVAR (Float, snd_sfxvolume) EXTERN_CVAR(Float, snd_musicvolume) -CVAR (Int, snd_samplerate, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) -CVAR (Int, snd_buffersize, 0, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) -CVAR (Int, snd_hrtf, -1, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) +CUSTOM_CVAR(Int, snd_samplerate, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +{ + if (self != 0 && self != 8000 && self != 11025 && self != 22050 && self != 32000 && self != 44100 && self != 48000) + { + self = 0; + return; + } +} +CVAR(Int, snd_buffersize, 0, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) +CVAR(Int, snd_hrtf, -1, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) -#if !defined(NO_OPENAL) +#if !defined(NO_OPENAL) #define DEF_BACKEND "openal" #else #define DEF_BACKEND "null" diff --git a/src/common/thirdparty/gain_analysis.cpp b/src/common/thirdparty/gain_analysis.cpp new file mode 100644 index 00000000000..30ebccdef73 --- /dev/null +++ b/src/common/thirdparty/gain_analysis.cpp @@ -0,0 +1,486 @@ +/* + * ReplayGainAnalysis - analyzes input samples and give the recommended dB change + * Copyright (C) 2001-2009 David Robinson and Glen Sawyer + * Improvements and optimizations added by Frank Klemm, and by Marcel M�ller + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * concept and filter values by David Robinson (David@Robinson.org) + * -- blame him if you think the idea is flawed + * original coding by Glen Sawyer (mp3gain@hotmail.com) + * -- blame him if you think this runs too slowly, or the coding is otherwise flawed + * + * lots of code improvements by Frank Klemm ( http://www.uni-jena.de/~pfk/mpp/ ) + * -- credit him for all the _good_ programming ;) + * + * + * For an explanation of the concepts and the basic algorithms involved, go to: + * http://www.replaygain.org/ + */ + +/* + * Here's the deal. Call + * + * InitGainAnalysis ( long samplefreq ); + * + * to initialize everything. Call + * + * AnalyzeSamples ( const Float_t* left_samples, + * const Float_t* right_samples, + * size_t num_samples, + * int num_channels ); + * + * as many times as you want, with as many or as few samples as you want. + * If mono, pass the sample buffer in through left_samples, leave + * right_samples NULL, and make sure num_channels = 1. + * + * GetTitleGain() + * + * will return the recommended dB level change for all samples analyzed + * SINCE THE LAST TIME you called GetTitleGain() OR InitGainAnalysis(). + * + * GetAlbumGain() + * + * will return the recommended dB level change for all samples analyzed + * since InitGainAnalysis() was called and finalized with GetTitleGain(). + * + * Pseudo-code to process an album: + * + * Float_t l_samples [4096]; + * Float_t r_samples [4096]; + * size_t num_samples; + * unsigned int num_songs; + * unsigned int i; + * + * InitGainAnalysis ( 44100 ); + * for ( i = 1; i <= num_songs; i++ ) { + * while ( ( num_samples = getSongSamples ( song[i], left_samples, right_samples ) ) > 0 ) + * AnalyzeSamples ( left_samples, right_samples, num_samples, 2 ); + * fprintf ("Recommended dB change for song %2d: %+6.2f dB\n", i, GetTitleGain() ); + * } + * fprintf ("Recommended dB change for whole album: %+6.2f dB\n", GetAlbumGain() ); + */ + +/* + * So here's the main source of potential code confusion: + * + * The filters applied to the incoming samples are IIR filters, + * meaning they rely on up to number of previous samples + * AND up to number of previous filtered samples. + * + * I set up the AnalyzeSamples routine to minimize memory usage and interface + * complexity. The speed isn't compromised too much (I don't think), but the + * internal complexity is higher than it should be for such a relatively + * simple routine. + * + * Optimization/clarity suggestions are welcome. + */ + +#include +#include +#include +#include + +#include "gain_analysis.h" + +#define RMS_PERCENTILE 0.95 // percentile which is louder than the proposed level + +#define PINK_REF 64.82 //298640883795 // calibration value + + +// for each filter: +// [0] 48 kHz, [1] 44.1 kHz, [2] 32 kHz, [3] 24 kHz, [4] 22050 Hz, [5] 16 kHz, [6] 12 kHz, [7] is 11025 Hz, [8] 8 kHz + +#ifdef WIN32 +#ifndef __GNUC__ +#pragma warning ( disable : 4305 ) +#pragma warning ( disable : 4244 ) +#endif +#endif + +static const Float_t ABYule[][2 * YULE_ORDER + 1] = { + {(const Float_t) 0.006471345933032, (const Float_t) -7.22103125152679, (const Float_t) -0.02567678242161, (const Float_t) 24.7034187975904, (const Float_t) 0.049805860704367, (const Float_t) -52.6825833623896, (const Float_t) -0.05823001743528, (const Float_t) 77.4825736677539, (const Float_t) 0.040611847441914, (const Float_t) -82.0074753444205, (const Float_t) -0.010912036887501, (const Float_t) 63.1566097101925, (const Float_t) -0.00901635868667, (const Float_t) -34.889569769245, (const Float_t) 0.012448886238123, (const Float_t) 13.2126852760198, (const Float_t) -0.007206683749426, (const Float_t) -3.09445623301669, (const Float_t) 0.002167156433951, (const Float_t) 0.340344741393305, (const Float_t) -0.000261819276949}, + {(const Float_t) 0.015415414474287, (const Float_t) -7.19001570087017, (const Float_t) -0.07691359399407, (const Float_t) 24.4109412087159, (const Float_t) 0.196677418516518, (const Float_t) -51.6306373580801, (const Float_t) -0.338855114128061, (const Float_t) 75.3978476863163, (const Float_t) 0.430094579594561, (const Float_t) -79.4164552507386, (const Float_t) -0.415015413747894, (const Float_t) 61.0373661948115, (const Float_t) 0.304942508151101, (const Float_t) -33.7446462547014, (const Float_t) -0.166191795926663, (const Float_t) 12.8168791146274, (const Float_t) 0.063198189938739, (const Float_t) -3.01332198541437, (const Float_t) -0.015003978694525, (const Float_t) 0.223619893831468, (const Float_t) 0.001748085184539}, + {(const Float_t) 0.021776466467053, (const Float_t) -5.74819833657784, (const Float_t) -0.062376961003801, (const Float_t) 16.246507961894, (const Float_t) 0.107731165328514, (const Float_t) -29.9691822642542, (const Float_t) -0.150994515142316, (const Float_t) 40.027597579378, (const Float_t) 0.170334807313632, (const Float_t) -40.3209196052655, (const Float_t) -0.157984942890531, (const Float_t) 30.8542077487718, (const Float_t) 0.121639833268721, (const Float_t) -17.5965138737281, (const Float_t) -0.074094040816409, (const Float_t) 7.10690214103873, (const Float_t) 0.031282852041061, (const Float_t) -1.82175564515191, (const Float_t) -0.00755421235941, (const Float_t) 0.223619893831468, (const Float_t) 0.00117925454213}, + {(const Float_t) 0.03857599435200, (const Float_t) -3.84664617118067, (const Float_t) -0.02160367184185, (const Float_t) 7.81501653005538, (const Float_t) -0.00123395316851, (const Float_t) -11.34170355132042, (const Float_t) -0.00009291677959, (const Float_t) 13.05504219327545, (const Float_t) -0.01655260341619, (const Float_t) -12.28759895145294, (const Float_t) 0.02161526843274, (const Float_t) 9.48293806319790, (const Float_t) -0.02074045215285, (const Float_t) -5.87257861775999, (const Float_t) 0.00594298065125, (const Float_t) 2.75465861874613, (const Float_t) 0.00306428023191, (const Float_t) -0.86984376593551, (const Float_t) 0.00012025322027, (const Float_t) 0.13919314567432, (const Float_t) 0.00288463683916}, + {(const Float_t) 0.05418656406430, (const Float_t) -3.47845948550071, (const Float_t) -0.02911007808948, (const Float_t) 6.36317777566148, (const Float_t) -0.00848709379851, (const Float_t) -8.54751527471874, (const Float_t) -0.00851165645469, (const Float_t) 9.47693607801280, (const Float_t) -0.00834990904936, (const Float_t) -8.81498681370155, (const Float_t) 0.02245293253339, (const Float_t) 6.85401540936998, (const Float_t) -0.02596338512915, (const Float_t) -4.39470996079559, (const Float_t) 0.01624864962975, (const Float_t) 2.19611684890774, (const Float_t) -0.00240879051584, (const Float_t) -0.75104302451432, (const Float_t) 0.00674613682247, (const Float_t) 0.13149317958808, (const Float_t) -0.00187763777362}, + {(const Float_t) 0.15457299681924, (const Float_t) -2.37898834973084, (const Float_t) -0.09331049056315, (const Float_t) 2.84868151156327, (const Float_t) -0.06247880153653, (const Float_t) -2.64577170229825, (const Float_t) 0.02163541888798, (const Float_t) 2.23697657451713, (const Float_t) -0.05588393329856, (const Float_t) -1.67148153367602, (const Float_t) 0.04781476674921, (const Float_t) 1.00595954808547, (const Float_t) 0.00222312597743, (const Float_t) -0.45953458054983, (const Float_t) 0.03174092540049, (const Float_t) 0.16378164858596, (const Float_t) -0.01390589421898, (const Float_t) -0.05032077717131, (const Float_t) 0.00651420667831, (const Float_t) 0.02347897407020, (const Float_t) -0.00881362733839}, + {(const Float_t) 0.30296907319327, (const Float_t) -1.61273165137247, (const Float_t) -0.22613988682123, (const Float_t) 1.07977492259970, (const Float_t) -0.08587323730772, (const Float_t) -0.25656257754070, (const Float_t) 0.03282930172664, (const Float_t) -0.16276719120440, (const Float_t) -0.00915702933434, (const Float_t) -0.22638893773906, (const Float_t) -0.02364141202522, (const Float_t) 0.39120800788284, (const Float_t) -0.00584456039913, (const Float_t) -0.22138138954925, (const Float_t) 0.06276101321749, (const Float_t) 0.04500235387352, (const Float_t) -0.00000828086748, (const Float_t) 0.02005851806501, (const Float_t) 0.00205861885564, (const Float_t) 0.00302439095741, (const Float_t) -0.02950134983287}, + {(const Float_t) 0.33642304856132, (const Float_t) -1.49858979367799, (const Float_t) -0.25572241425570, (const Float_t) 0.87350271418188, (const Float_t) -0.11828570177555, (const Float_t) 0.12205022308084, (const Float_t) 0.11921148675203, (const Float_t) -0.80774944671438, (const Float_t) -0.07834489609479, (const Float_t) 0.47854794562326, (const Float_t) -0.00469977914380, (const Float_t) -0.12453458140019, (const Float_t) -0.00589500224440, (const Float_t) -0.04067510197014, (const Float_t) 0.05724228140351, (const Float_t) 0.08333755284107, (const Float_t) 0.00832043980773, (const Float_t) -0.04237348025746, (const Float_t) -0.01635381384540, (const Float_t) 0.02977207319925, (const Float_t) -0.01760176568150}, + {(const Float_t) 0.44915256608450, (const Float_t) -0.62820619233671, (const Float_t) -0.14351757464547, (const Float_t) 0.29661783706366, (const Float_t) -0.22784394429749, (const Float_t) -0.37256372942400, (const Float_t) -0.01419140100551, (const Float_t) 0.00213767857124, (const Float_t) 0.04078262797139, (const Float_t) -0.42029820170918, (const Float_t) -0.12398163381748, (const Float_t) 0.22199650564824, (const Float_t) 0.04097565135648, (const Float_t) 0.00613424350682, (const Float_t) 0.10478503600251, (const Float_t) 0.06747620744683, (const Float_t) -0.01863887810927, (const Float_t) 0.05784820375801, (const Float_t) -0.03193428438915, (const Float_t) 0.03222754072173, (const Float_t) 0.00541907748707}, + {(const Float_t) 0.56619470757641, (const Float_t) -1.04800335126349, (const Float_t) -0.75464456939302, (const Float_t) 0.29156311971249, (const Float_t) 0.16242137742230, (const Float_t) -0.26806001042947, (const Float_t) 0.16744243493672, (const Float_t) 0.00819999645858, (const Float_t) -0.18901604199609, (const Float_t) 0.45054734505008, (const Float_t) 0.30931782841830, (const Float_t) -0.33032403314006, (const Float_t) -0.27562961986224, (const Float_t) 0.06739368333110, (const Float_t) 0.00647310677246, (const Float_t) -0.04784254229033, (const Float_t) 0.08647503780351, (const Float_t) 0.01639907836189, (const Float_t) -0.03788984554840, (const Float_t) 0.01807364323573, (const Float_t) -0.00588215443421}, + {(const Float_t) 0.58100494960553, (const Float_t) -0.51035327095184, (const Float_t) -0.53174909058578, (const Float_t) -0.31863563325245, (const Float_t) -0.14289799034253, (const Float_t) -0.20256413484477, (const Float_t) 0.17520704835522, (const Float_t) 0.14728154134330, (const Float_t) 0.02377945217615, (const Float_t) 0.38952639978999, (const Float_t) 0.15558449135573, (const Float_t) -0.23313271880868, (const Float_t) -0.25344790059353, (const Float_t) -0.05246019024463, (const Float_t) 0.01628462406333, (const Float_t) -0.02505961724053, (const Float_t) 0.06920467763959, (const Float_t) 0.02442357316099, (const Float_t) -0.03721611395801, (const Float_t) 0.01818801111503, (const Float_t) -0.00749618797172}, + {(const Float_t) 0.53648789255105, (const Float_t) -0.25049871956020, (const Float_t) -0.42163034350696, (const Float_t) -0.43193942311114, (const Float_t) -0.00275953611929, (const Float_t) -0.03424681017675, (const Float_t) 0.04267842219415, (const Float_t) -0.04678328784242, (const Float_t) -0.10214864179676, (const Float_t) 0.26408300200955, (const Float_t) 0.14590772289388, (const Float_t) 0.15113130533216, (const Float_t) -0.02459864859345, (const Float_t) -0.17556493366449, (const Float_t) -0.11202315195388, (const Float_t) -0.18823009262115, (const Float_t) -0.04060034127000, (const Float_t) 0.05477720428674, (const Float_t) 0.04788665548180, (const Float_t) 0.04704409688120, (const Float_t) -0.02217936801134}, + + {(const Float_t) 0.38524531015142, (const Float_t) -1.29708918404534, (const Float_t) -0.27682212062067, (const Float_t) 0.90399339674203, (const Float_t)-0.09980181488805, (const Float_t) -0.29613799017877, (const Float_t) 0.09951486755646, (const Float_t)-0.42326645916207, (const Float_t) -0.08934020156622, (const Float_t) 0.37934887402200, (const Float_t) -0.00322369330199, (const Float_t) -0.37919795944938, (const Float_t) -0.00110329090689, (const Float_t) 0.23410283284785, (const Float_t) 0.03784509844682, (const Float_t) -0.03892971758879, (const Float_t) 0.01683906213303, (const Float_t) 0.00403009552351, (const Float_t) -0.01147039862572, (const Float_t) 0.03640166626278, (const Float_t) -0.01941767987192 }, + {(const Float_t)0.08717879977844, (const Float_t)-2.62816311472146, (const Float_t)-0.01000374016172, (const Float_t)3.53734535817992, (const Float_t)-0.06265852122368, (const Float_t)-3.81003448678921, (const Float_t)-0.01119328800950, (const Float_t)3.91291636730132, (const Float_t)-0.00114279372960, (const Float_t)-3.53518605896288, (const Float_t)0.02081333954769, (const Float_t)2.71356866157873, (const Float_t)-0.01603261863207, (const Float_t)-1.86723311846592, (const Float_t)0.01936763028546, (const Float_t)1.12075382367659, (const Float_t)0.00760044736442, (const Float_t)-0.48574086886890, (const Float_t)-0.00303979112271, (const Float_t)0.11330544663849, (const Float_t)-0.00075088605788 }, + +}; + + +static const Float_t ABButter[][2 * BUTTER_ORDER + 1] = { + {(const Float_t) 0.99308203517541, (const Float_t) -1.98611621154089, (const Float_t) -1.98616407035082, (const Float_t) 0.986211929160751, (const Float_t) 0.99308203517541}, + {(const Float_t) 0.992472550461293, (const Float_t) -1.98488843762334, (const Float_t) -1.98494510092258, (const Float_t) 0.979389350028798, (const Float_t) 0.992472550461293}, + {(const Float_t) 0.989641019334721, (const Float_t) -1.97917472731008, (const Float_t) -1.97928203866944, (const Float_t) 0.979389350028798, (const Float_t) 0.989641019334721}, + {(const Float_t) 0.98621192462708, (const Float_t) -1.97223372919527, (const Float_t) -1.97242384925416, (const Float_t) 0.97261396931306, (const Float_t) 0.98621192462708}, + {(const Float_t) 0.98500175787242, (const Float_t) -1.96977855582618, (const Float_t) -1.97000351574484, (const Float_t) 0.97022847566350, (const Float_t) 0.98500175787242}, + {(const Float_t) 0.97938932735214, (const Float_t) -1.95835380975398, (const Float_t) -1.95877865470428, (const Float_t) 0.95920349965459, (const Float_t) 0.97938932735214}, + {(const Float_t) 0.97531843204928, (const Float_t) -1.95002759149878, (const Float_t) -1.95063686409857, (const Float_t) 0.95124613669835, (const Float_t) 0.97531843204928}, + {(const Float_t) 0.97316523498161, (const Float_t) -1.94561023566527, (const Float_t) -1.94633046996323, (const Float_t) 0.94705070426118, (const Float_t) 0.97316523498161}, + {(const Float_t) 0.96454515552826, (const Float_t) -1.92783286977036, (const Float_t) -1.92909031105652, (const Float_t) 0.93034775234268, (const Float_t) 0.96454515552826}, + {(const Float_t) 0.96009142950541, (const Float_t) -1.91858953033784, (const Float_t) -1.92018285901082, (const Float_t) 0.92177618768381, (const Float_t) 0.96009142950541}, + {(const Float_t) 0.95856916599601, (const Float_t) -1.91542108074780, (const Float_t) -1.91713833199203, (const Float_t) 0.91885558323625, (const Float_t) 0.95856916599601}, + {(const Float_t) 0.94597685600279, (const Float_t) -1.88903307939452, (const Float_t) -1.89195371200558, (const Float_t) 0.89487434461664, (const Float_t) 0.94597685600279}, + + {(const Float_t)0.96535326815829, (const Float_t)-1.92950577983524, (const Float_t)-1.93070653631658, (const Float_t)0.93190729279793, (const Float_t)0.96535326815829 }, + {(const Float_t)0.98252400815195, (const Float_t)-1.96474258269041, (const Float_t)-1.96504801630391, (const Float_t)0.96535344991740, (const Float_t)0.98252400815195 }, + +}; + +#ifdef WIN32 +#ifndef __GNUC__ +#pragma warning ( default : 4305 ) +#endif +#endif + +// When calling these filter procedures, make sure that ip[-order] and op[-order] point to real data! + +// If your compiler complains that "'operation on 'output' may be undefined", you can +// either ignore the warnings or uncomment the three "y" lines (and comment out the indicated line) + +void +GainAnalyzer::filterYule(const Float_t *input, Float_t *output, size_t nSamples, const Float_t *kernel) +{ + + while (nSamples--) { + *output = 1e-10f /* 1e-10 is a hack to avoid slowdown because of denormals */ + + input[0] * kernel[0] + - output[-1] * kernel[1] + + input[-1] * kernel[2] + - output[-2] * kernel[3] + + input[-2] * kernel[4] + - output[-3] * kernel[5] + + input[-3] * kernel[6] + - output[-4] * kernel[7] + + input[-4] * kernel[8] + - output[-5] * kernel[9] + + input[-5] * kernel[10] + - output[-6] * kernel[11] + + input[-6] * kernel[12] + - output[-7] * kernel[13] + + input[-7] * kernel[14] + - output[-8] * kernel[15] + + input[-8] * kernel[16] + - output[-9] * kernel[17] + + input[-9] * kernel[18] + - output[-10] * kernel[19] + + input[-10] * kernel[20]; + ++output; + ++input; + } +} + +void +GainAnalyzer::filterButter(const Float_t *input, Float_t *output, size_t nSamples, const Float_t *kernel) { + + while (nSamples--) { + *output = + input[0] * kernel[0] + - output[-1] * kernel[1] + + input[-1] * kernel[2] + - output[-2] * kernel[3] + + input[-2] * kernel[4]; + ++output; + ++input; + } +} + + +// returns a INIT_GAIN_ANALYSIS_OK if successful, INIT_GAIN_ANALYSIS_ERROR if not + +int +GainAnalyzer::ResetSampleFrequency(int samplefreq) { + int i; + + // zero out initial values + for (i = 0; i < MAX_ORDER; i++) + linprebuf[i] = lstepbuf[i] = loutbuf[i] = rinprebuf[i] = rstepbuf[i] = routbuf[i] = (Float_t) 0.; + + switch ((int) (samplefreq)) { + case 96000: + freqindex = 0; + break; + case 88200: + freqindex = 1; + break; + case 64000: + freqindex = 2; + break; + case 49716: // I could not find a table for this but we need to be able to handle this frequency for OPL, even if this means not getting the proper level. + case 48000: + freqindex = 3; + break; + case 44100: + freqindex = 4; + break; + case 32000: + freqindex = 5; + break; + case 24000: + freqindex = 6; + break; + case 22050: + freqindex = 7; + break; + case 16000: + freqindex = 8; + break; + case 12000: + freqindex = 9; + break; + case 11025: + freqindex = 10; + break; + case 8000: + freqindex = 11; + break; + + // These two were added for XA support. + case 18900: + freqindex = 12; + break; + case 37800: + freqindex = 13; + break; + default: + return INIT_GAIN_ANALYSIS_ERROR; + } + + sampleWindow = (int) ceil(samplefreq * RMS_WINDOW_TIME); + + lsum = 0.; + rsum = 0.; + totsamp = 0; + + memset(A, 0, sizeof(A)); + + return INIT_GAIN_ANALYSIS_OK; +} + +int +GainAnalyzer::InitGainAnalysis(int samplefreq) { + *this = {}; + if (ResetSampleFrequency(samplefreq) != INIT_GAIN_ANALYSIS_OK) { + return INIT_GAIN_ANALYSIS_ERROR; + } + + linpre = linprebuf + MAX_ORDER; + rinpre = rinprebuf + MAX_ORDER; + lstep = lstepbuf + MAX_ORDER; + rstep = rstepbuf + MAX_ORDER; + lout = loutbuf + MAX_ORDER; + rout = routbuf + MAX_ORDER; + + memset(B, 0, sizeof(B)); + + return INIT_GAIN_ANALYSIS_OK; +} + +// returns GAIN_ANALYSIS_OK if successful, GAIN_ANALYSIS_ERROR if not + +static __inline double fsqr(const double d) { + return d * d; +} + +int +GainAnalyzer::AnalyzeSamples(const Float_t *left_samples, const Float_t *right_samples, size_t num_samples, int num_channels) { + const Float_t *curleft; + const Float_t *curright; + int64_t batchsamples; + int64_t cursamples; + int64_t cursamplepos; + int i; + + if (num_samples == 0) + return GAIN_ANALYSIS_OK; + + cursamplepos = 0; + batchsamples = (int64_t) num_samples; + + switch (num_channels) { + case 1: + right_samples = left_samples; + case 2: + break; + default: + return GAIN_ANALYSIS_ERROR; + } + + if (num_samples < MAX_ORDER) { + memcpy(linprebuf + MAX_ORDER, left_samples, num_samples * sizeof(Float_t)); + memcpy(rinprebuf + MAX_ORDER, right_samples, num_samples * sizeof(Float_t)); + } else { + memcpy(linprebuf + MAX_ORDER, left_samples, MAX_ORDER * sizeof(Float_t)); + memcpy(rinprebuf + MAX_ORDER, right_samples, MAX_ORDER * sizeof(Float_t)); + } + + while (batchsamples > 0) { + cursamples = batchsamples > sampleWindow - totsamp ? sampleWindow - totsamp : batchsamples; + if (cursamplepos < MAX_ORDER) { + curleft = linpre + cursamplepos; + curright = rinpre + cursamplepos; + if (cursamples > MAX_ORDER - cursamplepos) + cursamples = MAX_ORDER - cursamplepos; + } else { + curleft = left_samples + cursamplepos; + curright = right_samples + cursamplepos; + } + + filterYule(curleft, lstep + totsamp, cursamples, ABYule[freqindex]); + filterYule(curright, rstep + totsamp, cursamples, ABYule[freqindex]); + + filterButter(lstep + totsamp, lout + totsamp, cursamples, ABButter[freqindex]); + filterButter(rstep + totsamp, rout + totsamp, cursamples, ABButter[freqindex]); + + curleft = lout + totsamp; // Get the squared values + curright = rout + totsamp; + + i = cursamples % 16; + while (i--) { + lsum += fsqr(*curleft++); + rsum += fsqr(*curright++); + } + i = cursamples / 16; + while (i--) { + lsum += fsqr(curleft[0]) + + fsqr(curleft[1]) + + fsqr(curleft[2]) + + fsqr(curleft[3]) + + fsqr(curleft[4]) + + fsqr(curleft[5]) + + fsqr(curleft[6]) + + fsqr(curleft[7]) + + fsqr(curleft[8]) + + fsqr(curleft[9]) + + fsqr(curleft[10]) + + fsqr(curleft[11]) + + fsqr(curleft[12]) + + fsqr(curleft[13]) + + fsqr(curleft[14]) + + fsqr(curleft[15]); + curleft += 16; + rsum += fsqr(curright[0]) + + fsqr(curright[1]) + + fsqr(curright[2]) + + fsqr(curright[3]) + + fsqr(curright[4]) + + fsqr(curright[5]) + + fsqr(curright[6]) + + fsqr(curright[7]) + + fsqr(curright[8]) + + fsqr(curright[9]) + + fsqr(curright[10]) + + fsqr(curright[11]) + + fsqr(curright[12]) + + fsqr(curright[13]) + + fsqr(curright[14]) + + fsqr(curright[15]); + curright += 16; + } + + batchsamples -= cursamples; + cursamplepos += cursamples; + totsamp += cursamples; + if (totsamp == sampleWindow) { // Get the Root Mean Square (RMS) for this set of samples + double val = STEPS_per_dB * 10. * log10((lsum + rsum) / totsamp * 0.5 + 1.e-37); + int ival = (int) val; + if (ival < 0) ival = 0; + if (ival >= (int) (sizeof(A) / sizeof(*A))) ival = sizeof(A) / sizeof(*A) - 1; + A[ival]++; + lsum = rsum = 0.; + memmove(loutbuf, loutbuf + totsamp, MAX_ORDER * sizeof(Float_t)); + memmove(routbuf, routbuf + totsamp, MAX_ORDER * sizeof(Float_t)); + memmove(lstepbuf, lstepbuf + totsamp, MAX_ORDER * sizeof(Float_t)); + memmove(rstepbuf, rstepbuf + totsamp, MAX_ORDER * sizeof(Float_t)); + totsamp = 0; + } + if (totsamp > + sampleWindow) // somehow I really screwed up: Error in programming! Contact author about totsamp > sampleWindow + return GAIN_ANALYSIS_ERROR; + } + if (num_samples < MAX_ORDER) { + memmove(linprebuf, linprebuf + num_samples, (MAX_ORDER - num_samples) * sizeof(Float_t)); + memmove(rinprebuf, rinprebuf + num_samples, (MAX_ORDER - num_samples) * sizeof(Float_t)); + memcpy(linprebuf + MAX_ORDER - num_samples, left_samples, num_samples * sizeof(Float_t)); + memcpy(rinprebuf + MAX_ORDER - num_samples, right_samples, num_samples * sizeof(Float_t)); + } else { + memcpy(linprebuf, left_samples + num_samples - MAX_ORDER, MAX_ORDER * sizeof(Float_t)); + memcpy(rinprebuf, right_samples + num_samples - MAX_ORDER, MAX_ORDER * sizeof(Float_t)); + } + + return GAIN_ANALYSIS_OK; +} + + +Float_t +GainAnalyzer::analyzeResult(const unsigned int *Array, size_t len) { + unsigned int elems; + signed int upper; + size_t i; + + elems = 0; + for (i = 0; i < len; i++) + elems += Array[i]; + if (elems == 0) + return GAIN_NOT_ENOUGH_SAMPLES; + + upper = (signed int) ceil(elems * (1. - RMS_PERCENTILE)); + for (i = len; i-- > 0;) { + if ((upper -= Array[i]) <= 0) + break; + } + + return (Float_t) ((Float_t) PINK_REF - (Float_t) i / (Float_t) STEPS_per_dB); +} + + +Float_t +GainAnalyzer::GetTitleGain(void) { + Float_t retval; + int i; + + retval = analyzeResult(A, sizeof(A) / sizeof(*A)); + + for (i = 0; i < (int) (sizeof(A) / sizeof(*A)); i++) { + B[i] += A[i]; + A[i] = 0; + } + + for (i = 0; i < MAX_ORDER; i++) + linprebuf[i] = lstepbuf[i] = loutbuf[i] = rinprebuf[i] = rstepbuf[i] = routbuf[i] = 0.f; + + totsamp = 0; + lsum = rsum = 0.; + return retval; +} + + +Float_t +GainAnalyzer::GetAlbumGain(void) { + return analyzeResult(B, sizeof(B) / sizeof(*B)); +} + + +/* end of gain_analysis.c */ diff --git a/src/common/thirdparty/gain_analysis.h b/src/common/thirdparty/gain_analysis.h new file mode 100644 index 00000000000..076da30d4ef --- /dev/null +++ b/src/common/thirdparty/gain_analysis.h @@ -0,0 +1,92 @@ +/* + * ReplayGainAnalysis - analyzes input samples and give the recommended dB change + * Copyright (C) 2001-2009 David Robinson and Glen Sawyer + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * concept and filter values by David Robinson (David@Robinson.org) + * -- blame him if you think the idea is flawed + * coding by Glen Sawyer (mp3gain@hotmail.com) 735 W 255 N, Orem, UT 84057-4505 USA + * -- blame him if you think this runs too slowly, or the coding is otherwise flawed + * + * For an explanation of the concepts and the basic algorithms involved, go to: + * http://www.replaygain.org/ + */ + +#ifndef GAIN_ANALYSIS_H +#define GAIN_ANALYSIS_H + +#include +#include + +#define GAIN_NOT_ENOUGH_SAMPLES (-24601) +#define GAIN_ANALYSIS_ERROR 0 +#define GAIN_ANALYSIS_OK 1 + +#define INIT_GAIN_ANALYSIS_ERROR 0 +#define INIT_GAIN_ANALYSIS_OK 1 + +#define STEPS_per_dB 100. // Table entries per dB +#define MAX_dB 120. // Table entries for 0...MAX_dB (normal max. values are 70...80 dB) +#define MAX_SAMP_FREQ 96000. // maximum allowed sample frequency [Hz] +#define RMS_WINDOW_TIME 0.050 // Time slice size [s] + +#define YULE_ORDER 10 +#define BUTTER_ORDER 2 +#define MAX_ORDER (BUTTER_ORDER > YULE_ORDER ? BUTTER_ORDER : YULE_ORDER) +#define MAX_SAMPLES_PER_WINDOW (size_t) (MAX_SAMP_FREQ * RMS_WINDOW_TIME + 1) // max. Samples per Time slice + +typedef float Float_t; // Type used for filtering + +struct GainAnalyzer +{ + int InitGainAnalysis(int samplefreq); + + int AnalyzeSamples(const Float_t *left_samples, const Float_t *right_samples, size_t num_samples, int num_channels); + + int ResetSampleFrequency(int samplefreq); + + Float_t GetTitleGain(void); + + Float_t GetAlbumGain(void); + +private: + Float_t linprebuf[MAX_ORDER * 2]; + Float_t *linpre; // left input samples, with pre-buffer + Float_t lstepbuf[MAX_SAMPLES_PER_WINDOW + MAX_ORDER]; + Float_t *lstep; // left "first step" (i.e. post first filter) samples + Float_t loutbuf[MAX_SAMPLES_PER_WINDOW + MAX_ORDER]; + Float_t *lout; // left "out" (i.e. post second filter) samples + Float_t rinprebuf[MAX_ORDER * 2]; + Float_t *rinpre; // right input samples ... + Float_t rstepbuf[MAX_SAMPLES_PER_WINDOW + MAX_ORDER]; + Float_t *rstep; + Float_t routbuf[MAX_SAMPLES_PER_WINDOW + MAX_ORDER]; + Float_t *rout; + long sampleWindow; // number of samples required to reach number of milliseconds required for RMS window + long totsamp; + double lsum; + double rsum; + int freqindex; + unsigned int A[(size_t) (STEPS_per_dB * MAX_dB)]; + unsigned int B[(size_t) (STEPS_per_dB * MAX_dB)]; + + void filterYule(const Float_t* input, Float_t* output, size_t nSamples, const Float_t* kernel); + void filterButter(const Float_t* input, Float_t* output, size_t nSamples, const Float_t* kernel); + Float_t analyzeResult(const unsigned int* Array, size_t len); + +}; + +#endif /* GAIN_ANALYSIS_H */ diff --git a/wadsrc/static/menudef.txt b/wadsrc/static/menudef.txt index 0fca5e63025..49fd1e6b5f4 100644 --- a/wadsrc/static/menudef.txt +++ b/wadsrc/static/menudef.txt @@ -1833,7 +1833,6 @@ OptionMenu "CompatSoundMenu" protected OptionValue SampleRates { 0, "$OPTVAL_DEFAULT" - 4000, "$OPTVAL_4000HZ" 8000, "$OPTVAL_8000HZ" 11025, "$OPTVAL_11025HZ" 22050, "$OPTVAL_22050HZ" From b9dafaaa6b9615fe133380dc3d63f8d9a76d9825 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Thu, 11 Mar 2021 00:20:19 +0100 Subject: [PATCH 3/4] - fill in the MIDI defaults for generating the lookup string for replay gain. Both synth and sound font can be very relevant here for the final volume so using the same song with different settings needs to create different strings. --- src/common/audio/music/music.cpp | 51 ++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/src/common/audio/music/music.cpp b/src/common/audio/music/music.cpp index 77efb971534..86a27622802 100644 --- a/src/common/audio/music/music.cpp +++ b/src/common/audio/music/music.cpp @@ -81,6 +81,7 @@ static MusicCallbacks mus_cb = { nullptr, DefaultOpenMusic }; // PUBLIC DATA DEFINITIONS ------------------------------------------------- +EXTERN_CVAR(Int, snd_mididevice) CVAR(Bool, mus_calcgain, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) // changing this will only take effect for the next song. CVAR(Bool, mus_usereplaygain, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) // changing this will only take effect for the next song. @@ -353,8 +354,21 @@ bool S_StartMusic (const char *m_id) //========================================================================== static TMap gainMap; -static FString ReplayGainHash(ZMusicCustomReader* reader, int flength, int playertype, const char* playparam) +EXTERN_CVAR(String, fluid_patchset) +EXTERN_CVAR(String, timidity_config) +EXTERN_CVAR(String, midi_config) +EXTERN_CVAR(String, wildmidi_config) +EXTERN_CVAR(String, adl_custom_bank) +EXTERN_CVAR(Int, adl_bank) +EXTERN_CVAR(Bool, adl_use_custom_bank) +EXTERN_CVAR(String, opn_custom_bank) +EXTERN_CVAR(Bool, opn_use_custom_bank) +EXTERN_CVAR(Int, opl_core) + +static FString ReplayGainHash(ZMusicCustomReader* reader, int flength, int playertype, const char* _playparam) { + std::string playparam = _playparam; + uint8_t buffer[50000]; // for performance reasons only hash the start of the file. If we wanted to do this to large waveform songs it'd cause noticable lag. uint8_t digest[16]; char digestout[33]; @@ -373,11 +387,41 @@ static FString ReplayGainHash(ZMusicCustomReader* reader, int flength, int playe auto type = ZMusic_IdentifyMIDIType((uint32_t*)buffer, 32); if (type == MIDI_NOTMIDI) return FStringf("%d:%s", flength, digestout); + + // get the default for MIDI synth if (playertype == -1) { - // todo: get the defaults for MIDI synth and used sound font. + switch (snd_mididevice) + { + case -1: playertype = MDEV_FLUIDSYNTH; break; + case -2: playertype = MDEV_TIMIDITY; break; + case -3: playertype = MDEV_OPL; break; + case -4: playertype = MDEV_GUS; break; + case -5: playertype = MDEV_FLUIDSYNTH; break; + case -6: playertype = MDEV_WILDMIDI; break; + case -7: playertype = MDEV_ADL; break; + case -8: playertype = MDEV_OPN; break; + default: return ""; + } + } + else if (playertype == MDEV_SNDSYS) return ""; + + // get the default for used sound font. + if (playparam.empty()) + { + switch (playertype) + { + case MDEV_FLUIDSYNTH: playparam = fluid_patchset; break; + case MDEV_TIMIDITY: playparam = timidity_config; break; + case MDEV_GUS: playparam = midi_config; break; + case MDEV_WILDMIDI: playparam = wildmidi_config; break; + case MDEV_ADL: playparam = adl_use_custom_bank ? *adl_custom_bank : std::to_string(adl_bank); break; + case MDEV_OPN: playparam = opn_use_custom_bank ? *opn_custom_bank : ""; break; + case MDEV_OPL: playparam = std::to_string(opl_core); break; + + } } - return FStringf("%d:%s:%d:%s", flength, digestout, playertype, playparam); + return FStringf("%d:%s:%d:%s", flength, digestout, playertype, playparam.c_str()).MakeUpper(); } static void SaveGains() @@ -453,6 +497,7 @@ static void CheckReplayGain(const char *musicname, EMidiDevice playertype, const ReadGains(); auto hash = ReplayGainHash(mreader, flength, playertype, playparam); + if (hash.IsEmpty()) return; // got nothing to measure. mus_playing.hash = hash; auto entry = gainMap.CheckKey(hash); if (entry) From d1023046ba0203ced97def1c534473daa37baab0 Mon Sep 17 00:00:00 2001 From: Christoph Oelckers Date: Thu, 11 Mar 2021 15:47:05 +0100 Subject: [PATCH 4/4] - default replay gain to 'off'. --- src/common/audio/music/music.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/audio/music/music.cpp b/src/common/audio/music/music.cpp index 86a27622802..1437ea83f57 100644 --- a/src/common/audio/music/music.cpp +++ b/src/common/audio/music/music.cpp @@ -84,7 +84,7 @@ static MusicCallbacks mus_cb = { nullptr, DefaultOpenMusic }; EXTERN_CVAR(Int, snd_mididevice) CVAR(Bool, mus_calcgain, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) // changing this will only take effect for the next song. -CVAR(Bool, mus_usereplaygain, true, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) // changing this will only take effect for the next song. +CVAR(Bool, mus_usereplaygain, false, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) // changing this will only take effect for the next song. CUSTOM_CVAR(Float, mus_gainoffset, 0.f, CVAR_ARCHIVE | CVAR_GLOBALCONFIG) // for customizing the base volume { if (self > 10.f) self = 10.f;