From a4bd32a63e002fef2f283c2a21130a118057fbba Mon Sep 17 00:00:00 2001 From: Nils Brederlow <62596379+dingodoppelt@users.noreply.github.com> Date: Fri, 17 Jul 2026 15:14:31 +0200 Subject: [PATCH 1/8] Use MVerb by Martin Eastwood for reverberation --- .gitmodules | 3 + Jamulus.pro | 3 +- libs/mverb | 1 + src/client.cpp | 2 +- src/client.h | 6 +- src/plugins/audioreverb.cpp | 221 +++++++----------------------------- src/plugins/audioreverb.h | 43 ++----- 7 files changed, 57 insertions(+), 222 deletions(-) create mode 160000 libs/mverb diff --git a/.gitmodules b/.gitmodules index 819ad73047..f61a791095 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "libs/oboe"] path = libs/oboe url = ../../google/oboe.git +[submodule "libs/mverb"] + path = libs/mverb + url = https://github.com/martineastwood/mverb.git diff --git a/Jamulus.pro b/Jamulus.pro index fa956ef6d5..d5712b297d 100644 --- a/Jamulus.pro +++ b/Jamulus.pro @@ -403,7 +403,8 @@ HEADERS += src/plugins/audioreverb.h \ src/recorder/jamrecorder.h \ src/recorder/creaperproject.h \ src/recorder/cwavestream.h \ - src/signalhandler.h + src/signalhandler.h \ + libs/mverb/MVerb.h !contains(CONFIG, "serveronly") { HEADERS += src/client.h \ diff --git a/libs/mverb b/libs/mverb new file mode 160000 index 0000000000..9f9512e9d9 --- /dev/null +++ b/libs/mverb @@ -0,0 +1 @@ +Subproject commit 9f9512e9d990fcf265e00fc7c2ee47ad20c8019f diff --git a/src/client.cpp b/src/client.cpp index fb9bab5478..cbc71e436b 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -1345,7 +1345,7 @@ void CClient::Init() Channel.SetAudioStreamProperties ( eAudioCompressionType, iCeltNumCodedBytes, iSndCrdFrameSizeFactor, iNumAudioChannels ); // init reverberation - AudioReverb.Init ( eAudioChannelConf, iStereoBlockSizeSam, SYSTEM_SAMPLE_RATE_HZ ); + AudioReverb.Init ( eAudioChannelConf, iStereoBlockSizeSam ); // init the sound card conversion buffers if ( bSndCrdConversionBufferRequired ) diff --git a/src/client.h b/src/client.h index fb77930723..03478385ac 100644 --- a/src/client.h +++ b/src/client.h @@ -192,11 +192,7 @@ class CClient : public QObject void SetReverbLevel ( const int iNL ) { iReverbLevel = iNL; } bool IsReverbOnLeftChan() const { return bReverbOnLeftChan; } - void SetReverbOnLeftChan ( const bool bIL ) - { - bReverbOnLeftChan = bIL; - AudioReverb.Clear(); - } + void SetReverbOnLeftChan ( const bool bIL ) { bReverbOnLeftChan = bIL; } void SetDoAutoSockBufSize ( const bool bValue ); bool GetDoAutoSockBufSize() const { return Channel.GetDoAutoSockBufSize(); } diff --git a/src/plugins/audioreverb.cpp b/src/plugins/audioreverb.cpp index a10c415bb8..a6acc4373b 100644 --- a/src/plugins/audioreverb.cpp +++ b/src/plugins/audioreverb.cpp @@ -2,206 +2,67 @@ * Audio Reverberation * \******************************************************************************/ /* - The following code is based on "JCRev: John Chowning's reverberator class" - by Perry R. Cook and Gary P. Scavone, 1995 - 2004 - which is in "The Synthesis ToolKit in C++ (STK)" - http://ccrma.stanford.edu/software/stk - - Original description: - This class is derived from the CLM JCRev function, which is based on the use - of networks of simple allpass and comb delay filters. This class implements - three series allpass units, followed by four parallel comb filters, and two - decorrelation delay lines in parallel at the output. + The following code calls MVerb for reverberation. + MVerb was written by Martin Eastwood. + https://github.com/martineastwood/mverb */ #include "audioreverb.h" -void CAudioReverb::Init ( const EAudChanConf eNAudioChannelConf, const int iNStereoBlockSizeSam, const int iSampleRate, const float fT60 ) +void CAudioReverb::Init ( const EAudChanConf eNAudioChannelConf, const int iNStereoBlockSizeSam ) { - // store parameters eAudioChannelConf = eNAudioChannelConf; iStereoBlockSizeSam = iNStereoBlockSizeSam; - - // delay lengths for 44100 Hz sample rate - int lengths[9] = { 1116, 1356, 1422, 1617, 225, 341, 441, 211, 179 }; - const float scaler = static_cast ( iSampleRate ) / 44100.0f; - - if ( scaler != 1.0f ) - { - for ( int i = 0; i < 9; i++ ) - { - int delay = static_cast ( floorf ( scaler * lengths[i] ) ); - - if ( ( delay & 1 ) == 0 ) - { - delay++; - } - - while ( !isPrime ( delay ) ) - { - delay += 2; - } - - lengths[i] = delay; - } - } - - for ( int i = 0; i < 3; i++ ) - { - allpassDelays[i].Init ( lengths[i + 4] ); - } - - for ( int i = 0; i < 4; i++ ) - { - combDelays[i].Init ( lengths[i] ); - combFilters[i].setPole ( 0.2f ); - } - - setT60 ( fT60, iSampleRate ); - outLeftDelay.Init ( lengths[7] ); - outRightDelay.Init ( lengths[8] ); - allpassCoefficient = 0.7f; - Clear(); + fMaxShort = static_cast ( _MAXSHORT ); + mverb.setParameter ( MVerb::DAMPINGFREQ, 0.5 ); + mverb.setParameter ( MVerb::DENSITY, 0.5 ); + mverb.setParameter ( MVerb::BANDWIDTHFREQ, 0.5 ); + mverb.setParameter ( MVerb::DECAY, 0.5 ); + mverb.setParameter ( MVerb::PREDELAY, 0.5 ); + mverb.setParameter ( MVerb::GAIN, 1. ); + mverb.setParameter ( MVerb::MIX, 1.0 ); + mverb.setParameter ( MVerb::EARLYMIX, 0.5 ); + mverb.setParameter ( MVerb::SIZE, 0.75 ); } -bool CAudioReverb::isPrime ( const int number ) +void CAudioReverb::Process ( CVector& vecsStereoInOut, const bool bReverbOnLeftChan, const float fReverbGain ) { - /* - Returns true if argument value is prime. Taken from "class Effect" in - "STK abstract effects parent class". - */ - if ( number == 2 ) - { - return true; - } + // Jamulus uses interleaved stereo, mverb operates on a 2-dimensional array instead + // Calculate the number of frames for each channel ( iStereoBlockSizeSam / 2 ) + const int numFrames = iStereoBlockSizeSam >> 1; - if ( number & 1 ) + // These buffers get filled with dry signal and are then passed to mverb + // They need to be vectors as the windows builds fail when arrays are used + std::vector bufL ( numFrames ); + std::vector bufR ( numFrames ); + + // One buffer to pass to mverb's process function + float* fInput[2] = { bufL.data(), bufR.data() }; + + if ( eAudioChannelConf == CC_STEREO ) { - for ( int i = 3; i < static_cast ( sqrtf ( static_cast ( number ) ) ) + 1; i += 2 ) + for ( int i = 0, j = 0; j < numFrames; i += 2, j++ ) { - if ( ( number % i ) == 0 ) - { - return false; - } + // True stereo reverb + bufL[j] = static_cast ( vecsStereoInOut[i] ) / fMaxShort; + bufR[j] = static_cast ( vecsStereoInOut[i + 1] ) / fMaxShort; } - - return true; // prime } else { - return false; // even + for ( int i = 0, j = 0; j < numFrames; i += 2, j++ ) + { + // For Mono and Mono-in/Stereo-out only one channel is selected and copied into both fInput channels + bufR[j] = bufL[j] = static_cast ( vecsStereoInOut[i + !bReverbOnLeftChan] ) / fMaxShort; + } } -} -void CAudioReverb::Clear() -{ - // reset and clear all internal state - allpassDelays[0].Reset ( 0 ); - allpassDelays[1].Reset ( 0 ); - allpassDelays[2].Reset ( 0 ); - combDelays[0].Reset ( 0 ); - combDelays[1].Reset ( 0 ); - combDelays[2].Reset ( 0 ); - combDelays[3].Reset ( 0 ); - combFilters[0].Reset(); - combFilters[1].Reset(); - combFilters[2].Reset(); - combFilters[3].Reset(); - outRightDelay.Reset ( 0 ); - outLeftDelay.Reset ( 0 ); -} + mverb.process ( fInput, fInput, numFrames ); -void CAudioReverb::setT60 ( const float fT60, const int iSampleRate ) -{ - // set the reverberation T60 decay time - for ( int i = 0; i < 4; i++ ) + for ( int i = 0, j = 0; j < numFrames; i += 2, j++ ) { - combCoefficient[i] = powf ( 10.0f, static_cast ( -3.0f * combDelays[i].Size() / ( fT60 * iSampleRate ) ) ); + // Mix wet and dry signal + vecsStereoInOut[i] = Float2Short ( vecsStereoInOut[i] + bufL[j] * fMaxShort * fReverbGain ); + vecsStereoInOut[i + 1] = Float2Short ( vecsStereoInOut[i + 1] + bufR[j] * fMaxShort * fReverbGain ); } } - -void CAudioReverb::COnePole::setPole ( const float fPole ) -{ - // calculate IIR filter coefficients based on the pole value - fA = -fPole; - fB = 1.0f - fPole; -} - -float CAudioReverb::COnePole::Calc ( const float fIn ) -{ - // calculate IIR filter - fLastSample = fB * fIn - fA * fLastSample; - - return fLastSample; -} - -void CAudioReverb::Process ( CVector& vecsStereoInOut, const bool bReverbOnLeftChan, const float fAttenuation ) -{ - float fMixedInput, temp, temp0, temp1, temp2; - - for ( int i = 0; i < iStereoBlockSizeSam; i += 2 ) - { - // we sum up the stereo input channels (in case mono input is used, a zero - // shall be input for the right channel) - if ( eAudioChannelConf == CC_STEREO ) - { - fMixedInput = 0.5f * ( vecsStereoInOut[i] + vecsStereoInOut[i + 1] ); - } - else - { - if ( bReverbOnLeftChan ) - { - fMixedInput = vecsStereoInOut[i]; - } - else - { - fMixedInput = vecsStereoInOut[i + 1]; - } - } - - temp = allpassDelays[0].Get(); - temp0 = allpassCoefficient * temp; - temp0 += fMixedInput; - allpassDelays[0].Add ( temp0 ); - temp0 = -( allpassCoefficient * temp0 ) + temp; - - temp = allpassDelays[1].Get(); - temp1 = allpassCoefficient * temp; - temp1 += temp0; - allpassDelays[1].Add ( temp1 ); - temp1 = -( allpassCoefficient * temp1 ) + temp; - - temp = allpassDelays[2].Get(); - temp2 = allpassCoefficient * temp; - temp2 += temp1; - allpassDelays[2].Add ( temp2 ); - temp2 = -( allpassCoefficient * temp2 ) + temp; - - const float temp3 = temp2 + combFilters[0].Calc ( combCoefficient[0] * combDelays[0].Get() ); - const float temp4 = temp2 + combFilters[1].Calc ( combCoefficient[1] * combDelays[1].Get() ); - const float temp5 = temp2 + combFilters[2].Calc ( combCoefficient[2] * combDelays[2].Get() ); - const float temp6 = temp2 + combFilters[3].Calc ( combCoefficient[3] * combDelays[3].Get() ); - - combDelays[0].Add ( temp3 ); - combDelays[1].Add ( temp4 ); - combDelays[2].Add ( temp5 ); - combDelays[3].Add ( temp6 ); - - const float filtout = temp3 + temp4 + temp5 + temp6; - - outLeftDelay.Add ( filtout ); - outRightDelay.Add ( filtout ); - - // inplace apply the attenuated reverb signal (for stereo always apply - // reverberation effect on both channels) - if ( ( eAudioChannelConf == CC_STEREO ) || bReverbOnLeftChan ) - { - vecsStereoInOut[i] = Float2Short ( ( 1.0f - fAttenuation ) * vecsStereoInOut[i] + 0.5f * fAttenuation * outLeftDelay.Get() ); - } - - if ( ( eAudioChannelConf == CC_STEREO ) || !bReverbOnLeftChan ) - { - vecsStereoInOut[i + 1] = Float2Short ( ( 1.0f - fAttenuation ) * vecsStereoInOut[i + 1] + 0.5f * fAttenuation * outRightDelay.Get() ); - } - } -} \ No newline at end of file diff --git a/src/plugins/audioreverb.h b/src/plugins/audioreverb.h index a5da6fac61..d486326569 100644 --- a/src/plugins/audioreverb.h +++ b/src/plugins/audioreverb.h @@ -2,56 +2,29 @@ * Audio Reverberation * \******************************************************************************/ /* - The following code is based on "JCRev: John Chowning's reverberator class" - by Perry R. Cook and Gary P. Scavone, 1995 - 2004 - which is in "The Synthesis ToolKit in C++ (STK)" - http://ccrma.stanford.edu/software/stk - - Original description: - This class is derived from the CLM JCRev function, which is based on the use - of networks of simple allpass and comb delay filters. This class implements - three series allpass units, followed by four parallel comb filters, and two - decorrelation delay lines in parallel at the output. + The following code calls MVerb for reverberation. + MVerb was written by Martin Eastwood. + https://github.com/martineastwood/mverb */ #pragma once #include "util.h" +#include "libs/mverb/MVerb.h" class CAudioReverb { public: CAudioReverb() {} - void Init ( const EAudChanConf eNAudioChannelConf, const int iNStereoBlockSizeSam, const int iSampleRate, const float fT60 = 1.1f ); + void Init ( const EAudChanConf eNAudioChannelConf, const int iNStereoBlockSizeSam ); void Clear(); - void Process ( CVector& vecsStereoInOut, const bool bReverbOnLeftChan, const float fAttenuation ); + void Process ( CVector& vecsStereoInOut, const bool bReverbOnLeftChan, const float fReverbGain ); protected: - void setT60 ( const float fT60, const int iSampleRate ); - bool isPrime ( const int number ); - - class COnePole - { - public: - COnePole() : fA ( 0 ), fB ( 0 ) { Reset(); } - void setPole ( const float fPole ); - float Calc ( const float fIn ); - void Reset() { fLastSample = 0; } - - protected: - float fA; - float fB; - float fLastSample; - }; + MVerb mverb; EAudChanConf eAudioChannelConf; int iStereoBlockSizeSam; - CFIFO allpassDelays[3]; - CFIFO combDelays[4]; - COnePole combFilters[4]; - CFIFO outLeftDelay; - CFIFO outRightDelay; - float allpassCoefficient; - float combCoefficient[4]; + float fMaxShort; }; From fea4ea01284668436540ba6c094e927bc4bc11b7 Mon Sep 17 00:00:00 2001 From: Nils Brederlow <62596379+dingodoppelt@users.noreply.github.com> Date: Fri, 17 Jul 2026 20:54:12 +0200 Subject: [PATCH 2/8] Refactor autobuild.yml to pull submodules per platform use bash for all submodule updates --- .github/workflows/autobuild.yml | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/.github/workflows/autobuild.yml b/.github/workflows/autobuild.yml index 122150979e..57709282e1 100644 --- a/.github/workflows/autobuild.yml +++ b/.github/workflows/autobuild.yml @@ -287,10 +287,20 @@ jobs: - name: Checkout code uses: actions/checkout@v7 with: - # only Android needs the oboe submodule, so don't fetch it for other builds - submodules: ${{ matrix.config.target_os == 'android' }} + submodules: false fetch-depth: ${{ matrix.config.checkout_fetch_depth || '1' }} + - name: Initialize Submodules + shell: bash + run: | + # clone mverb for all platforms + git submodule update --init libs/mverb + + # clone oboe only for android builds + if [[ "${{ matrix.config.target_os }}" == "android" ]]; then + git submodule update --init libs/oboe + fi + - name: Cache Mac dependencies if: matrix.config.target_os == 'macos' uses: actions/cache@v6 From 9a1a9dc47369c8b1e48761f6f54a43b3f7686327 Mon Sep 17 00:00:00 2001 From: Nils Brederlow <62596379+dingodoppelt@users.noreply.github.com> Date: Sat, 18 Jul 2026 19:23:41 +0200 Subject: [PATCH 3/8] Add five reverb presets, settable in INI file make "Stadium" the default reverb preset --- src/client.h | 2 ++ src/plugins/audioreverb.cpp | 18 ++++++++---------- src/plugins/audioreverb.h | 38 ++++++++++++++++++++++++++++++++++++- src/settings.cpp | 9 +++++++++ 4 files changed, 56 insertions(+), 11 deletions(-) diff --git a/src/client.h b/src/client.h index 03478385ac..fe60510814 100644 --- a/src/client.h +++ b/src/client.h @@ -189,7 +189,9 @@ class CClient : public QObject void SetAudioInFader ( const int iNV ) { iAudioInFader = iNV; } int GetReverbLevel() const { return iReverbLevel; } + int GetReverbPreset() const { return AudioReverb.getPreset(); } void SetReverbLevel ( const int iNL ) { iReverbLevel = iNL; } + void SetReverbPreset ( const int iNP ) { AudioReverb.setPreset ( iNP ); } bool IsReverbOnLeftChan() const { return bReverbOnLeftChan; } void SetReverbOnLeftChan ( const bool bIL ) { bReverbOnLeftChan = bIL; } diff --git a/src/plugins/audioreverb.cpp b/src/plugins/audioreverb.cpp index a6acc4373b..5cd2bb23f0 100644 --- a/src/plugins/audioreverb.cpp +++ b/src/plugins/audioreverb.cpp @@ -13,16 +13,14 @@ void CAudioReverb::Init ( const EAudChanConf eNAudioChannelConf, const int iNSte { eAudioChannelConf = eNAudioChannelConf; iStereoBlockSizeSam = iNStereoBlockSizeSam; - fMaxShort = static_cast ( _MAXSHORT ); - mverb.setParameter ( MVerb::DAMPINGFREQ, 0.5 ); - mverb.setParameter ( MVerb::DENSITY, 0.5 ); - mverb.setParameter ( MVerb::BANDWIDTHFREQ, 0.5 ); - mverb.setParameter ( MVerb::DECAY, 0.5 ); - mverb.setParameter ( MVerb::PREDELAY, 0.5 ); - mverb.setParameter ( MVerb::GAIN, 1. ); - mverb.setParameter ( MVerb::MIX, 1.0 ); - mverb.setParameter ( MVerb::EARLYMIX, 0.5 ); - mverb.setParameter ( MVerb::SIZE, 0.75 ); +} + +void CAudioReverb::loadPreset() +{ + for ( int i = 0; i < MVerb::NUM_PARAMS; i++ ) + { + mverb.setParameter ( i, presets[iPreset][i] ); + } } void CAudioReverb::Process ( CVector& vecsStereoInOut, const bool bReverbOnLeftChan, const float fReverbGain ) diff --git a/src/plugins/audioreverb.h b/src/plugins/audioreverb.h index d486326569..25cd4b1e67 100644 --- a/src/plugins/audioreverb.h +++ b/src/plugins/audioreverb.h @@ -14,17 +14,53 @@ class CAudioReverb { public: - CAudioReverb() {} + CAudioReverb() + { + mverb.setSampleRate ( static_cast ( SYSTEM_SAMPLE_RATE_HZ ) ); + fMaxShort = static_cast ( _MAXSHORT ); + iPreset = STADIUM; + loadPreset(); + } void Init ( const EAudChanConf eNAudioChannelConf, const int iNStereoBlockSizeSam ); void Clear(); void Process ( CVector& vecsStereoInOut, const bool bReverbOnLeftChan, const float fReverbGain ); + void setPreset ( const int iNPreset ) + { + // silently fail if preset doesn't exist + if ( MathUtils::InRange ( iNPreset, SUBTLE, HALVES ) ) + { + iPreset = iNPreset; + loadPreset(); + } + }; + int getPreset() const { return iPreset; }; protected: MVerb mverb; + void loadPreset(); EAudChanConf eAudioChannelConf; int iStereoBlockSizeSam; float fMaxShort; + int iPreset; + + enum + { + SUBTLE = 0, + STADIUM = 1, + CUPBOARD = 2, + DARK = 3, + HALVES = 4, + NUM_REV_PRESETS = 5 + }; + + constexpr static float const presets[NUM_REV_PRESETS][MVerb::NUM_PARAMS] = { { .5, .5, .5, .5, .5, 1., 1., .5, .75 }, + { 0., .5, 1., .5, 0., 1., 1., .35, .75 }, + { 0., .5, 1., .5, 0., 1., 1., 1., .75 }, + { .9, .5, .1, .5, 0., 1., 1., 1., .75 }, + { .5, .5, .5, .5, .5, 1., 1., .5, .75 } + + }; }; diff --git a/src/settings.cpp b/src/settings.cpp index bdc453913e..4d8fec898b 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -508,6 +508,12 @@ void CClientSettings::ReadSettingsFromXML ( const QDomDocument& IniXMLDocument, pClient->SetReverbOnLeftChan ( bValue ); } + // reverb preset + if ( GetNumericIniSet ( IniXMLDocument, "client", "revpreset", 0, 4, iValue ) ) + { + pClient->SetReverbPreset ( iValue ); + } + // sound card selection const QString strError = pClient->SetSndCrdDev ( FromBase64ToString ( GetIniSetting ( IniXMLDocument, "client", "auddev_base64", "" ) ) ); @@ -951,6 +957,9 @@ void CClientSettings::WriteSettingsToXML ( QDomDocument& IniXMLDocument, bool is // reverberation channel assignment SetFlagIniSet ( IniXMLDocument, "client", "reverblchan", pClient->IsReverbOnLeftChan() ); + // reverb preset + SetNumericIniSet ( IniXMLDocument, "client", "revpreset", pClient->GetReverbPreset() ); + // sound card selection PutIniSetting ( IniXMLDocument, "client", "auddev_base64", ToBase64 ( pClient->GetSndCrdDev() ) ); From d27f4f0be89286bc80163b66124379b596532e70 Mon Sep 17 00:00:00 2001 From: Nils Brederlow <62596379+dingodoppelt@users.noreply.github.com> Date: Sat, 18 Jul 2026 19:37:19 +0200 Subject: [PATCH 4/8] Fix autobuilds Use c++17 for windows builds --- Jamulus.pro | 2 ++ src/plugins/audioreverb.h | 10 +++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Jamulus.pro b/Jamulus.pro index d5712b297d..c77568fb58 100644 --- a/Jamulus.pro +++ b/Jamulus.pro @@ -90,6 +90,8 @@ DEFINES += APP_VERSION=\\\"$$VERSION\\\" \ DEFINES += QT_NO_DEPRECATED_WARNINGS win32 { + # fixes error C7525: inline variables require at least '/std:c++17' + CONFIG += c++17 DEFINES -= UNICODE # fixes issue with ASIO SDK (asiolist.cpp is not unicode compatible) DEFINES += NOMINMAX # solves a compiler error in qdatetime.h (Qt5) RC_FILE = src/res/win-mainicon.rc diff --git a/src/plugins/audioreverb.h b/src/plugins/audioreverb.h index 25cd4b1e67..28c820f51c 100644 --- a/src/plugins/audioreverb.h +++ b/src/plugins/audioreverb.h @@ -56,11 +56,11 @@ class CAudioReverb NUM_REV_PRESETS = 5 }; - constexpr static float const presets[NUM_REV_PRESETS][MVerb::NUM_PARAMS] = { { .5, .5, .5, .5, .5, 1., 1., .5, .75 }, - { 0., .5, 1., .5, 0., 1., 1., .35, .75 }, - { 0., .5, 1., .5, 0., 1., 1., 1., .75 }, - { .9, .5, .1, .5, 0., 1., 1., 1., .75 }, - { .5, .5, .5, .5, .5, 1., 1., .5, .75 } + constexpr static inline float const presets[NUM_REV_PRESETS][MVerb::NUM_PARAMS] = { { .5, .5, .5, .5, .5, 1., 1., .5, .75 }, + { 0., .5, 1., .5, 0., 1., 1., .35, .75 }, + { 0., .5, 1., .5, 0., 1., 1., 1., .75 }, + { .9, .5, .1, .5, 0., 1., 1., 1., .75 }, + { .5, .5, .5, .5, .5, 1., 1., .5, .75 } }; }; From 4d6a1fbbcc4ec0c02be68c43b94c0f7246f963a0 Mon Sep 17 00:00:00 2001 From: Nils Brederlow <62596379+dingodoppelt@users.noreply.github.com> Date: Sun, 19 Jul 2026 12:14:48 +0200 Subject: [PATCH 5/8] Add reverb preset selection to advanced audio settings dialog --- src/clientsettingsdlg.cpp | 28 +++++ src/clientsettingsdlg.h | 1 + src/clientsettingsdlgbase.ui | 197 +++++++++++++++++------------------ 3 files changed, 124 insertions(+), 102 deletions(-) diff --git a/src/clientsettingsdlg.cpp b/src/clientsettingsdlg.cpp index ad92a0f8e1..add1a27a18 100644 --- a/src/clientsettingsdlg.cpp +++ b/src/clientsettingsdlg.cpp @@ -380,6 +380,15 @@ CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, CClientSettings* pNSet cbxInputBoost->setWhatsThis ( strInputBoost ); cbxInputBoost->setAccessibleName ( tr ( "Input Boost combo box" ) ); + // reverb preset + QString strReverbPreset = "" + tr ( "Reverb Preset" ) + ": " + + tr ( "Jamulus uses MVerb by Martin Eastwood for reverberation. " + "MVerb comes with a set of presets you can select here. " + "Available Presets: Subtle, Stadium, Cupboard, Dark, Halves " ); + lblInputBoost->setWhatsThis ( strReverbPreset ); + cbxInputBoost->setWhatsThis ( strReverbPreset ); + cbxInputBoost->setAccessibleName ( tr ( "Reverb Preset combo box" ) ); + // custom directories QString strCustomDirectories = "" + tr ( "Custom Directories" ) + ": " + tr ( "If you need to add additional directories to the Connect dialog Directory drop down, " @@ -555,6 +564,15 @@ CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, CClientSettings* pNSet // factor is 1-based while index is 0-based: cbxInputBoost->setCurrentIndex ( pSettings->iInputBoost - 1 ); + // Rever Preset combo box + cbxReverbPreset->clear(); + cbxReverbPreset->addItem ( tr ( "Subtle" ) ); + cbxReverbPreset->addItem ( tr ( "Stadium" ) ); + cbxReverbPreset->addItem ( tr ( "Cupboard" ) ); + cbxReverbPreset->addItem ( tr ( "Dark" ) ); + cbxReverbPreset->addItem ( tr ( "Halves" ) ); + cbxReverbPreset->setCurrentIndex ( pClient->GetReverbPreset() ); + // init number of mixer rows spnMixerRows->setValue ( pSettings->iNumMixerPanelRows ); @@ -786,6 +804,11 @@ CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, CClientSettings* pNSet this, &CClientSettingsDlg::OnInputBoostChanged ); + QObject::connect ( cbxReverbPreset, + static_cast ( &QComboBox::activated ), + this, + &CClientSettingsDlg::OnReverbPresetChanged ); + // buttons #if defined( _WIN32 ) && !defined( WITH_JACK ) // Driver Setup button is only available for Windows when JACK is not used @@ -1389,6 +1412,11 @@ void CClientSettingsDlg::OnInputBoostChanged() pClient->SetInputBoost ( pSettings->iInputBoost ); } +void CClientSettingsDlg::OnReverbPresetChanged() +{ + pClient->SetReverbPreset ( cbxReverbPreset->currentIndex() ); +} + void CClientSettingsDlg::OnAliasTextChanged ( const QString& strNewName ) { // check length diff --git a/src/clientsettingsdlg.h b/src/clientsettingsdlg.h index 7844f64117..6564e21ca9 100644 --- a/src/clientsettingsdlg.h +++ b/src/clientsettingsdlg.h @@ -109,6 +109,7 @@ public slots: void OnCustomDirectoriesChanged ( bool bDelete ); void OnNewClientLevelEditingFinished() { pSettings->iNewClientFaderLevel = edtNewClientLevel->text().toInt(); } void OnInputBoostChanged(); + void OnReverbPresetChanged(); void OnSndCrdBufferDelayButtonGroupClicked ( QAbstractButton* button ); void OnSoundcardActivated ( int iSndDevIdx ); void OnLInChanActivated ( int iChanIdx ); diff --git a/src/clientsettingsdlgbase.ui b/src/clientsettingsdlgbase.ui index 842ab9220c..8e15b59077 100644 --- a/src/clientsettingsdlgbase.ui +++ b/src/clientsettingsdlgbase.ui @@ -7,7 +7,7 @@ 0 0 534 - 591 + 652 @@ -30,7 +30,7 @@ - 3 + 2 true @@ -45,7 +45,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -60,10 +60,10 @@ - Qt::Vertical + Qt::Orientation::Vertical - QSizePolicy::Preferred + QSizePolicy::Policy::Preferred @@ -209,7 +209,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -305,7 +305,7 @@ - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 1 @@ -332,7 +332,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -347,7 +347,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -399,10 +399,10 @@ - Qt::Vertical + Qt::Orientation::Vertical - QSizePolicy::Expanding + QSizePolicy::Policy::Expanding @@ -431,7 +431,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -450,10 +450,10 @@ - QFrame::NoFrame + QFrame::Shape::NoFrame - QFrame::Plain + QFrame::Shadow::Plain @@ -600,10 +600,10 @@ - Qt::Vertical + Qt::Orientation::Vertical - QSizePolicy::Expanding + QSizePolicy::Policy::Expanding @@ -654,7 +654,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -717,10 +717,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QSizePolicy::MinimumExpanding + QSizePolicy::Policy::MinimumExpanding @@ -759,7 +759,7 @@ Local - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter false @@ -772,7 +772,7 @@ Server - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter false @@ -789,7 +789,7 @@ Size - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter false @@ -802,7 +802,7 @@ Size - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter false @@ -816,10 +816,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QSizePolicy::Minimum + QSizePolicy::Policy::Minimum @@ -841,20 +841,20 @@ 1 - Qt::Vertical + Qt::Orientation::Vertical - QSlider::TicksBothSides + QSlider::TickPosition::TicksBothSides - Qt::Horizontal + Qt::Orientation::Horizontal - QSizePolicy::Minimum + QSizePolicy::Policy::Minimum @@ -876,10 +876,10 @@ 1 - Qt::Vertical + Qt::Orientation::Vertical - QSlider::TicksBothSides + QSlider::TickPosition::TicksBothSides @@ -915,7 +915,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -931,7 +931,7 @@ kbps - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter @@ -941,7 +941,7 @@ val - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter 2 @@ -951,7 +951,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -971,10 +971,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QSizePolicy::Minimum + QSizePolicy::Policy::Minimum @@ -1000,7 +1000,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1015,10 +1015,10 @@ - Qt::Vertical + Qt::Orientation::Vertical - QSizePolicy::MinimumExpanding + QSizePolicy::Policy::MinimumExpanding @@ -1055,7 +1055,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -1092,7 +1092,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -1115,10 +1115,33 @@ + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + + + + + Reverb Preset + + + + + + - Qt::Vertical + Qt::Orientation::Vertical @@ -1144,7 +1167,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -1159,7 +1182,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1176,10 +1199,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QSizePolicy::Preferred + QSizePolicy::Policy::Preferred @@ -1206,7 +1229,7 @@ Pan - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter sldAudioPan @@ -1218,10 +1241,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QSizePolicy::Minimum + QSizePolicy::Policy::Minimum @@ -1252,20 +1275,20 @@ 1 - Qt::Horizontal + Qt::Orientation::Horizontal - QSlider::TicksBothSides + QSlider::TickPosition::TicksBothSides - Qt::Horizontal + Qt::Orientation::Horizontal - QSizePolicy::Minimum + QSizePolicy::Policy::Minimum @@ -1289,7 +1312,7 @@ Center - Qt::AlignCenter + Qt::AlignmentFlag::AlignCenter false @@ -1304,10 +1327,10 @@ - Qt::Horizontal + Qt::Orientation::Horizontal - QSizePolicy::Preferred + QSizePolicy::Policy::Preferred @@ -1324,7 +1347,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -1383,7 +1406,6 @@ - 50 false @@ -1408,7 +1430,6 @@ - 50 false @@ -1502,7 +1523,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1522,7 +1543,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1538,7 +1559,6 @@ - 50 false @@ -1561,7 +1581,6 @@ - 50 false @@ -1580,7 +1599,6 @@ - 50 false @@ -1596,7 +1614,6 @@ - 50 false @@ -1611,7 +1628,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1629,7 +1646,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -1642,7 +1659,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -1657,7 +1674,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -1671,7 +1688,6 @@ - 50 false @@ -1694,7 +1710,6 @@ - 50 false @@ -1713,7 +1728,6 @@ - 50 false @@ -1726,7 +1740,6 @@ - 50 false @@ -1741,7 +1754,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1755,7 +1768,6 @@ - 50 false @@ -1774,7 +1786,6 @@ - 50 false @@ -1795,7 +1806,6 @@ - 50 false @@ -1818,7 +1828,6 @@ - 50 false @@ -1837,7 +1846,6 @@ - 50 false @@ -1850,7 +1858,6 @@ - 50 false @@ -1865,7 +1872,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -1879,7 +1886,6 @@ - 50 false @@ -1898,7 +1904,6 @@ - 50 false @@ -1918,7 +1923,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -1932,7 +1937,6 @@ - 50 false @@ -1955,7 +1959,6 @@ - 50 false @@ -1974,7 +1977,6 @@ - 50 false @@ -1987,7 +1989,6 @@ - 50 false @@ -2002,7 +2003,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -2016,7 +2017,6 @@ - 50 false @@ -2035,7 +2035,6 @@ - 50 false @@ -2056,7 +2055,6 @@ - 50 false @@ -2079,7 +2077,6 @@ - 50 false @@ -2098,7 +2095,6 @@ - 50 false @@ -2111,7 +2107,6 @@ - 50 false @@ -2126,7 +2121,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -2140,7 +2135,6 @@ - 50 false @@ -2159,7 +2153,6 @@ - 50 false @@ -2179,7 +2172,7 @@ - Qt::Vertical + Qt::Orientation::Vertical @@ -2213,7 +2206,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -2228,7 +2221,7 @@ - Qt::Vertical + Qt::Orientation::Vertical From 6b3b5e1cd3d6b29eec2e3cd11187bd379f5ebbbd Mon Sep 17 00:00:00 2001 From: Nils Brederlow <62596379+dingodoppelt@users.noreply.github.com> Date: Sun, 19 Jul 2026 15:42:07 +0200 Subject: [PATCH 6/8] Make reverb buffers protected members --- src/clientsettingsdlg.cpp | 11 ++++------- src/plugins/audioreverb.cpp | 24 ++++++++++++++---------- src/plugins/audioreverb.h | 11 +++++++++-- 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/clientsettingsdlg.cpp b/src/clientsettingsdlg.cpp index add1a27a18..80550005a8 100644 --- a/src/clientsettingsdlg.cpp +++ b/src/clientsettingsdlg.cpp @@ -382,9 +382,9 @@ CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, CClientSettings* pNSet // reverb preset QString strReverbPreset = "" + tr ( "Reverb Preset" ) + ": " + - tr ( "Jamulus uses MVerb by Martin Eastwood for reverberation. " - "MVerb comes with a set of presets you can select here. " - "Available Presets: Subtle, Stadium, Cupboard, Dark, Halves " ); + tr ( "Jamulus uses MVerb by Martin Eastwood for reverberation. " + "MVerb comes with a set of presets you can select here. " + "Available Presets: Subtle, Stadium, Cupboard, Dark, Halves " ); lblInputBoost->setWhatsThis ( strReverbPreset ); cbxInputBoost->setWhatsThis ( strReverbPreset ); cbxInputBoost->setAccessibleName ( tr ( "Reverb Preset combo box" ) ); @@ -1412,10 +1412,7 @@ void CClientSettingsDlg::OnInputBoostChanged() pClient->SetInputBoost ( pSettings->iInputBoost ); } -void CClientSettingsDlg::OnReverbPresetChanged() -{ - pClient->SetReverbPreset ( cbxReverbPreset->currentIndex() ); -} +void CClientSettingsDlg::OnReverbPresetChanged() { pClient->SetReverbPreset ( cbxReverbPreset->currentIndex() ); } void CClientSettingsDlg::OnAliasTextChanged ( const QString& strNewName ) { diff --git a/src/plugins/audioreverb.cpp b/src/plugins/audioreverb.cpp index 5cd2bb23f0..2e0a9885bb 100644 --- a/src/plugins/audioreverb.cpp +++ b/src/plugins/audioreverb.cpp @@ -13,26 +13,30 @@ void CAudioReverb::Init ( const EAudChanConf eNAudioChannelConf, const int iNSte { eAudioChannelConf = eNAudioChannelConf; iStereoBlockSizeSam = iNStereoBlockSizeSam; + + // Jamulus uses interleaved stereo, mverb operates on a 2-dimensional array instead + // Calculate the number of frames for each channel ( iStereoBlockSizeSam / 2 ) + numFrames = iStereoBlockSizeSam >> 1; + + // These buffers get filled with dry signal and are then passed to mverb + // They need to be vectors as the windows builds fail when arrays are used + bufL.resize ( numFrames ); + bufR.resize ( numFrames ); + + mverb->setSampleRate ( static_cast ( SYSTEM_SAMPLE_RATE_HZ ) ); + loadPreset(); } void CAudioReverb::loadPreset() { for ( int i = 0; i < MVerb::NUM_PARAMS; i++ ) { - mverb.setParameter ( i, presets[iPreset][i] ); + mverb->setParameter ( i, presets[iPreset][i] ); } } void CAudioReverb::Process ( CVector& vecsStereoInOut, const bool bReverbOnLeftChan, const float fReverbGain ) { - // Jamulus uses interleaved stereo, mverb operates on a 2-dimensional array instead - // Calculate the number of frames for each channel ( iStereoBlockSizeSam / 2 ) - const int numFrames = iStereoBlockSizeSam >> 1; - - // These buffers get filled with dry signal and are then passed to mverb - // They need to be vectors as the windows builds fail when arrays are used - std::vector bufL ( numFrames ); - std::vector bufR ( numFrames ); // One buffer to pass to mverb's process function float* fInput[2] = { bufL.data(), bufR.data() }; @@ -55,7 +59,7 @@ void CAudioReverb::Process ( CVector& vecsStereoInOut, const bool bReve } } - mverb.process ( fInput, fInput, numFrames ); + mverb->process ( fInput, fInput, numFrames ); for ( int i = 0, j = 0; j < numFrames; i += 2, j++ ) { diff --git a/src/plugins/audioreverb.h b/src/plugins/audioreverb.h index 28c820f51c..63bb18d59a 100644 --- a/src/plugins/audioreverb.h +++ b/src/plugins/audioreverb.h @@ -16,9 +16,12 @@ class CAudioReverb public: CAudioReverb() { - mverb.setSampleRate ( static_cast ( SYSTEM_SAMPLE_RATE_HZ ) ); fMaxShort = static_cast ( _MAXSHORT ); iPreset = STADIUM; + + // Create MVerb on the heap + mverb = std::unique_ptr> ( new MVerb() ); + mverb->setSampleRate ( SYSTEM_SAMPLE_RATE_HZ ); loadPreset(); } @@ -38,7 +41,7 @@ class CAudioReverb int getPreset() const { return iPreset; }; protected: - MVerb mverb; + std::unique_ptr> mverb; void loadPreset(); EAudChanConf eAudioChannelConf; @@ -46,6 +49,10 @@ class CAudioReverb float fMaxShort; int iPreset; + int numFrames; + std::vector bufL; + std::vector bufR; + enum { SUBTLE = 0, From 7b3ebd7a99fbb14118edc477a0ac19faa1f5686c Mon Sep 17 00:00:00 2001 From: Nils Brederlow <62596379+dingodoppelt@users.noreply.github.com> Date: Sun, 19 Jul 2026 22:29:30 +0200 Subject: [PATCH 7/8] Add documentation for MVerb in `COMPILING.md` --- COMPILING.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/COMPILING.md b/COMPILING.md index c6ac38b098..3314596e6c 100644 --- a/COMPILING.md +++ b/COMPILING.md @@ -17,7 +17,19 @@ First of all, you need to get the Jamulus source code. You can either download i - For .tar.gz [use this link](https://github.com/jamulussoftware/jamulus/archive/latest.tar.gz) to download the latest release - For .zip [use this link](https://github.com/jamulussoftware/jamulus/archive/latest.zip) - If you use `git`, [set it up](https://docs.github.com/en/get-started/quickstart/set-up-git) – preferably with SSH if you want to contribute. -Then run `git clone git@github.com:jamulussoftware/jamulus` in Terminal to get the bleeding edge version directly from GitHub. +Then run +``` +git clone git@github.com:jamulussoftware/jamulus +``` +in Terminal to get the bleeding edge version directly from GitHub. +- The reverberation is done by MVerb and you need to pull it in as a submodule. +``` +# Change into the jamulus/ directory +cd jamulus +# Initialize submodule MVerb +git submodule update --init libs/mverb +``` + ## Linux From 45f70fad748ef687e5a039f7e214c71835365355 Mon Sep 17 00:00:00 2001 From: Nils Brederlow <62596379+dingodoppelt@users.noreply.github.com> Date: Mon, 20 Jul 2026 15:00:35 +0200 Subject: [PATCH 8/8] Do not translate reverb preset names --- src/clientsettingsdlg.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/clientsettingsdlg.cpp b/src/clientsettingsdlg.cpp index 80550005a8..8b409f668e 100644 --- a/src/clientsettingsdlg.cpp +++ b/src/clientsettingsdlg.cpp @@ -566,11 +566,11 @@ CClientSettingsDlg::CClientSettingsDlg ( CClient* pNCliP, CClientSettings* pNSet // Rever Preset combo box cbxReverbPreset->clear(); - cbxReverbPreset->addItem ( tr ( "Subtle" ) ); - cbxReverbPreset->addItem ( tr ( "Stadium" ) ); - cbxReverbPreset->addItem ( tr ( "Cupboard" ) ); - cbxReverbPreset->addItem ( tr ( "Dark" ) ); - cbxReverbPreset->addItem ( tr ( "Halves" ) ); + cbxReverbPreset->addItem ( "Subtle" ); + cbxReverbPreset->addItem ( "Stadium" ); + cbxReverbPreset->addItem ( "Cupboard" ); + cbxReverbPreset->addItem ( "Dark" ); + cbxReverbPreset->addItem ( "Halves" ); cbxReverbPreset->setCurrentIndex ( pClient->GetReverbPreset() ); // init number of mixer rows