diff --git a/include/EffectChain.h b/include/EffectChain.h index 0af23c4b82b..0cc192298e4 100644 --- a/include/EffectChain.h +++ b/include/EffectChain.h @@ -26,6 +26,7 @@ #ifndef LMMS_EFFECT_CHAIN_H #define LMMS_EFFECT_CHAIN_H +#include "ConfigManager.h" #include "Model.h" #include "SerializingObject.h" #include "AutomatableModel.h" @@ -70,6 +71,8 @@ class LMMS_EXPORT EffectChain : public Model, public SerializingObject private: + static auto sanitizeEffectOutput() -> bool; + using EffectList = std::vector; EffectList m_effects; diff --git a/include/MixHelpers.h b/include/MixHelpers.h index 3b0ecf968f5..29f71cf5afd 100644 --- a/include/MixHelpers.h +++ b/include/MixHelpers.h @@ -38,12 +38,6 @@ namespace MixHelpers bool isSilent( const SampleFrame* src, int frames ); -bool useNaNHandler(); - -void setNaNHandler( bool use ); - -bool sanitize( SampleFrame* src, int frames ); - /*! \brief Add samples from src to dst */ void add( SampleFrame* dst, const SampleFrame* src, int frames ); @@ -62,15 +56,6 @@ void addMultipliedByBuffer( SampleFrame* dst, const SampleFrame* src, float coef /*! \brief Add samples from src multiplied by coeffSrc and coeffSrcBuf to dst */ void addMultipliedByBuffers( SampleFrame* dst, const SampleFrame* src, ValueBuffer * coeffSrcBuf1, ValueBuffer * coeffSrcBuf2, int frames ); -/*! \brief Same as addMultiplied, but sanitize output (strip out infs/nans) */ -void addSanitizedMultiplied( SampleFrame* dst, const SampleFrame* src, float coeffSrc, int frames ); - -/*! \brief Add samples from src multiplied by coeffSrc and coeffSrcBuf to dst - sanitized version */ -void addSanitizedMultipliedByBuffer( SampleFrame* dst, const SampleFrame* src, float coeffSrc, ValueBuffer * coeffSrcBuf, int frames ); - -/*! \brief Add samples from src multiplied by coeffSrc and coeffSrcBuf to dst - sanitized version */ -void addSanitizedMultipliedByBuffers( SampleFrame* dst, const SampleFrame* src, ValueBuffer * coeffSrcBuf1, ValueBuffer * coeffSrcBuf2, int frames ); - /*! \brief Add samples from src multiplied by coeffSrcLeft/coeffSrcRight to dst */ void addMultipliedStereo( SampleFrame* dst, const SampleFrame* src, float coeffSrcLeft, float coeffSrcRight, int frames ); diff --git a/include/Mixer.h b/include/Mixer.h index 6e3c86565ce..d8fee7775f1 100644 --- a/include/Mixer.h +++ b/include/Mixer.h @@ -25,15 +25,16 @@ #ifndef LMMS_MIXER_H #define LMMS_MIXER_H -#include "Model.h" +#include +#include +#include + +#include "ConfigManager.h" #include "EffectChain.h" #include "JournallingObject.h" +#include "Model.h" #include "ThreadableJob.h" -#include -#include -#include - namespace lmms { @@ -88,7 +89,7 @@ class MixerChannel : public ThreadableJob std::atomic_size_t m_dependenciesMet; void incrementDeps(); void processed(); - + private: void doProcessing() override; int m_channelIndex; diff --git a/include/SetupDialog.h b/include/SetupDialog.h index c314ff42d81..cfc53e2b176 100644 --- a/include/SetupDialog.h +++ b/include/SetupDialog.h @@ -177,11 +177,11 @@ private slots: QComboBox * m_audioInterfaces; AswMap m_audioIfaceSetupWidgets; trMap m_audioIfaceNames; - bool m_NaNHandler; int m_bufferSize; QSlider * m_bufferSizeSlider; QLabel * m_bufferSizeLbl; QLabel * m_bufferSizeWarnLbl; + bool m_sanitizeEffectOutput; int m_sampleRate; QSlider* m_sampleRateSlider; diff --git a/src/core/EffectChain.cpp b/src/core/EffectChain.cpp index 0a6a496f355..5cc1ffd8699 100644 --- a/src/core/EffectChain.cpp +++ b/src/core/EffectChain.cpp @@ -191,15 +191,19 @@ bool EffectChain::processAudioBuffer( SampleFrame* _buf, const fpp_t _frames, bo return false; } - MixHelpers::sanitize( _buf, _frames ); - bool moreEffects = false; for (const auto& effect : m_effects) { if (hasInputNoise || effect->isRunning()) { moreEffects |= effect->processAudioBuffer(_buf, _frames); - MixHelpers::sanitize(_buf, _frames); + + if (sanitizeEffectOutput()) + { + std::replace_if( + &_buf[0][0], &_buf[0][0] + _frames * DEFAULT_CHANNELS, [](auto x) { return !std::isfinite(x); }, + 0.f); + } } } @@ -243,5 +247,10 @@ void EffectChain::clear() m_enabledModel.setValue( false ); } +bool EffectChain::sanitizeEffectOutput() +{ + static auto value = ConfigManager::inst()->value("audioengine", "sanitizeeffectoutput", "1").toInt(); + return value; +} } // namespace lmms diff --git a/src/core/MixHelpers.cpp b/src/core/MixHelpers.cpp index c10e4c50c7c..67467eaffdc 100644 --- a/src/core/MixHelpers.cpp +++ b/src/core/MixHelpers.cpp @@ -34,11 +34,6 @@ #include "ValueBuffer.h" #include "SampleFrame.h" - - -static bool s_NaNHandler; - - namespace lmms::MixHelpers { @@ -80,51 +75,6 @@ bool isSilent( const SampleFrame* src, int frames ) return true; } -bool useNaNHandler() -{ - return s_NaNHandler; -} - -void setNaNHandler( bool use ) -{ - s_NaNHandler = use; -} - -/*! \brief Function for sanitizing a buffer of infs/nans - returns true if those are found */ -bool sanitize( SampleFrame* src, int frames ) -{ - if( !useNaNHandler() ) - { - return false; - } - - for (int f = 0; f < frames; ++f) - { - auto& currentFrame = src[f]; - - if (currentFrame.containsInf() || currentFrame.containsNaN()) - { - #ifdef LMMS_DEBUG - // TODO don't use printf here - printf("Bad data, clearing buffer. frame: "); - printf("%d: value %f, %f\n", f, currentFrame.left(), currentFrame.right()); - #endif - - // Clear the whole buffer if a problem is found - zeroSampleFrames(src, frames); - - return true; - } - else - { - currentFrame.clamp(sample_t(-1000.0), sample_t(1000.0)); - } - }; - - return false; -} - - struct AddOp { void operator()( SampleFrame& dst, const SampleFrame& src ) const @@ -205,70 +155,6 @@ void addMultipliedByBuffers( SampleFrame* dst, const SampleFrame* src, ValueBuff } -void addSanitizedMultipliedByBuffer( SampleFrame* dst, const SampleFrame* src, float coeffSrc, ValueBuffer * coeffSrcBuf, int frames ) -{ - if ( !useNaNHandler() ) - { - addMultipliedByBuffer( dst, src, coeffSrc, coeffSrcBuf, - frames ); - return; - } - - for( int f = 0; f < frames; ++f ) - { - dst[f][0] += ( std::isinf( src[f][0] ) || std::isnan( src[f][0] ) ) ? 0.0f : src[f][0] * coeffSrc * coeffSrcBuf->values()[f]; - dst[f][1] += ( std::isinf( src[f][1] ) || std::isnan( src[f][1] ) ) ? 0.0f : src[f][1] * coeffSrc * coeffSrcBuf->values()[f]; - } -} - -void addSanitizedMultipliedByBuffers( SampleFrame* dst, const SampleFrame* src, ValueBuffer * coeffSrcBuf1, ValueBuffer * coeffSrcBuf2, int frames ) -{ - if ( !useNaNHandler() ) - { - addMultipliedByBuffers( dst, src, coeffSrcBuf1, coeffSrcBuf2, - frames ); - return; - } - - for( int f = 0; f < frames; ++f ) - { - dst[f][0] += ( std::isinf( src[f][0] ) || std::isnan( src[f][0] ) ) - ? 0.0f - : src[f][0] * coeffSrcBuf1->values()[f] * coeffSrcBuf2->values()[f]; - dst[f][1] += ( std::isinf( src[f][1] ) || std::isnan( src[f][1] ) ) - ? 0.0f - : src[f][1] * coeffSrcBuf1->values()[f] * coeffSrcBuf2->values()[f]; - } - -} - - -struct AddSanitizedMultipliedOp -{ - AddSanitizedMultipliedOp( float coeff ) : m_coeff( coeff ) { } - - void operator()( SampleFrame& dst, const SampleFrame& src ) const - { - dst[0] += ( std::isinf( src[0] ) || std::isnan( src[0] ) ) ? 0.0f : src[0] * m_coeff; - dst[1] += ( std::isinf( src[1] ) || std::isnan( src[1] ) ) ? 0.0f : src[1] * m_coeff; - } - - const float m_coeff; -}; - -void addSanitizedMultiplied( SampleFrame* dst, const SampleFrame* src, float coeffSrc, int frames ) -{ - if ( !useNaNHandler() ) - { - addMultiplied( dst, src, coeffSrc, frames ); - return; - } - - run<>( dst, src, frames, AddSanitizedMultipliedOp(coeffSrc) ); -} - - - struct AddMultipliedStereoOp { AddMultipliedStereoOp( float coeffLeft, float coeffRight ) diff --git a/src/core/Mixer.cpp b/src/core/Mixer.cpp index 6007be466db..c7b235104bc 100644 --- a/src/core/Mixer.cpp +++ b/src/core/Mixer.cpp @@ -22,6 +22,8 @@ * */ +#include "Mixer.h" + #include #include "AudioEngine.h" @@ -31,8 +33,10 @@ #include "Song.h" #include "InstrumentTrack.h" +#include "MixHelpers.h" #include "PatternStore.h" #include "SampleTrack.h" +#include "Song.h" #include "TrackContainer.h" // For TrackContainer::TrackList typedef namespace lmms @@ -186,21 +190,21 @@ void MixerChannel::doProcessing() if( ! volBuf && ! sendBuf ) // neither volume nor send has sample-exact data... { const float v = sender->m_volumeModel.value() * sendModel->value(); - MixHelpers::addSanitizedMultiplied( m_buffer, ch_buf, v, fpp ); + MixHelpers::addMultiplied(m_buffer, ch_buf, v, fpp); } else if( volBuf && sendBuf ) // both volume and send have sample-exact data { - MixHelpers::addSanitizedMultipliedByBuffers( m_buffer, ch_buf, volBuf, sendBuf, fpp ); + MixHelpers::addMultipliedByBuffers(m_buffer, ch_buf, volBuf, sendBuf, fpp); } else if( volBuf ) // volume has sample-exact data but send does not { const float v = sendModel->value(); - MixHelpers::addSanitizedMultipliedByBuffer( m_buffer, ch_buf, v, volBuf, fpp ); + MixHelpers::addMultipliedByBuffer(m_buffer, ch_buf, v, volBuf, fpp); } else // vice versa { const float v = sender->m_volumeModel.value(); - MixHelpers::addSanitizedMultipliedByBuffer( m_buffer, ch_buf, v, sendBuf, fpp ); + MixHelpers::addMultipliedByBuffer(m_buffer, ch_buf, v, sendBuf, fpp); } m_hasInput = true; } @@ -724,7 +728,7 @@ void Mixer::masterMix( SampleFrame* _buf ) const float v = volBuf ? 1.0f : m_mixerChannels[0]->m_volumeModel.value(); - MixHelpers::addSanitizedMultiplied( _buf, m_mixerChannels[0]->m_buffer, v, fpp ); + MixHelpers::addMultiplied(_buf, m_mixerChannels[0]->m_buffer, v, fpp); // clear all channel buffers and // reset channel process state diff --git a/src/core/main.cpp b/src/core/main.cpp index fb54feeab0b..2781f864d26 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -711,10 +711,6 @@ int main( int argc, char * * argv ) ConfigManager::inst()->loadConfigFile(configFile); - // Hidden settings - MixHelpers::setNaNHandler( ConfigManager::inst()->value( "app", - "nanhandler", "1" ).toInt() ); - // set language QString pos = ConfigManager::inst()->value( "app", "language" ); if( pos.isEmpty() ) diff --git a/src/gui/MixerChannelView.cpp b/src/gui/MixerChannelView.cpp index 3391affceed..410ae65fe9c 100644 --- a/src/gui/MixerChannelView.cpp +++ b/src/gui/MixerChannelView.cpp @@ -31,7 +31,9 @@ #include #include #include +#include #include +#include #include "CaptionMenu.h" #include "ColorChooser.h" diff --git a/src/gui/modals/SetupDialog.cpp b/src/gui/modals/SetupDialog.cpp index 4ea0d4dddf9..7d6a5fd867d 100644 --- a/src/gui/modals/SetupDialog.cpp +++ b/src/gui/modals/SetupDialog.cpp @@ -139,10 +139,10 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : "ui", "vstalwaysontop").toInt()), m_disableAutoQuit(ConfigManager::inst()->value( "ui", "disableautoquit", "1").toInt()), - m_NaNHandler(ConfigManager::inst()->value( - "app", "nanhandler", "1").toInt()), m_bufferSize(ConfigManager::inst()->value( "audioengine", "framesperaudiobuffer").toInt()), + m_sanitizeEffectOutput(ConfigManager::inst()->value( + "audioengine", "sanitizeffectoutput", "1").toInt()), m_sampleRate(ConfigManager::inst()->value( "audioengine", "samplerate").toInt()), m_midiAutoQuantize(ConfigManager::inst()->value( @@ -553,12 +553,6 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : connect(m_audioInterfaces, SIGNAL(activated(const QString&)), this, SLOT(audioInterfaceChanged(const QString&))); - // Advanced setting, hidden for now - // // TODO Handle or remove. - // auto useNaNHandler = new LedCheckBox(tr("Use built-in NaN handler"), audio_w); - // audio_layout->addWidget(useNaNHandler); - // useNaNHandler->setChecked(m_NaNHandler); - auto sampleRateBox = new QGroupBox{tr("Sample rate"), audio_w}; m_sampleRateSlider = new QSlider{Qt::Horizontal}; @@ -634,12 +628,25 @@ SetupDialog::SetupDialog(ConfigTab tab_to_open) : setBufferSize(m_bufferSizeSlider->value()); + const auto otherBox = new QGroupBox(tr("Other"), audio_w); + const auto otherBoxLayout = new QVBoxLayout{otherBox}; + + const auto sanitizeEffectOutputCheckbox = new QCheckBox{}; + sanitizeEffectOutputCheckbox->setText(tr("Sanitize effect output")); + sanitizeEffectOutputCheckbox->setChecked(m_sanitizeEffectOutput); + otherBoxLayout->addWidget(sanitizeEffectOutputCheckbox); + + connect(sanitizeEffectOutputCheckbox, &QCheckBox::stateChanged, [sanitizeEffectOutputCheckbox, this] { + m_sanitizeEffectOutput = sanitizeEffectOutputCheckbox->isChecked(); + showRestartWarning(); + }); // Audio layout ordering. audio_layout->addWidget(audioInterfaceBox); audio_layout->addWidget(as_w); audio_layout->addWidget(sampleRateBox); audio_layout->addWidget(bufferSizeBox); + audio_layout->addWidget(otherBox); audio_layout->addStretch(); @@ -1001,12 +1008,12 @@ void SetupDialog::accept() QString::number(m_disableAutoQuit)); ConfigManager::inst()->setValue("audioengine", "audiodev", m_audioIfaceNames[m_audioInterfaces->currentText()]); - ConfigManager::inst()->setValue("app", "nanhandler", - QString::number(m_NaNHandler)); ConfigManager::inst()->setValue("audioengine", "samplerate", QString::number(m_sampleRate)); ConfigManager::inst()->setValue("audioengine", "framesperaudiobuffer", QString::number(m_bufferSize)); + ConfigManager::inst()->setValue( + "audioengine", "sanitizeeffectoutput", QString::number(m_sanitizeEffectOutput)); ConfigManager::inst()->setValue("audioengine", "mididev", m_midiIfaceNames[m_midiInterfaces->currentText()]); ConfigManager::inst()->setValue("midi", "midiautoassign",