Skip to content
15 changes: 0 additions & 15 deletions include/MixHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 );

Expand All @@ -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 );

Expand Down
22 changes: 16 additions & 6 deletions include/Mixer.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,16 @@
#ifndef LMMS_MIXER_H
#define LMMS_MIXER_H

#include "Model.h"
#include <QColor>
#include <atomic>
#include <optional>

#include "ConfigManager.h"
#include "EffectChain.h"
#include "JournallingObject.h"
#include "Model.h"
#include "ThreadableJob.h"

#include <atomic>
#include <optional>
#include <QColor>

namespace lmms
{

Expand Down Expand Up @@ -88,11 +89,20 @@ class MixerChannel : public ThreadableJob
std::atomic_size_t m_dependenciesMet;
void incrementDeps();
void processed();


auto silenced() -> bool { return m_silenced.load(std::memory_order_relaxed); }

static auto silenceInvalidOutput() -> bool
{
static auto s_value = ConfigManager::inst()->value("audioengine", "silenceinvalidmixeroutput", "0").toInt();
return s_value;
}

private:
void doProcessing() override;
int m_channelIndex;
std::optional<QColor> m_color;
std::atomic<bool> m_silenced = false;
};

class MixerRoute : public QObject
Expand Down
2 changes: 1 addition & 1 deletion include/SetupDialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -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_silenceInvalidMixerOutput;

// MIDI settings widgets.
QComboBox * m_midiInterfaces;
Expand Down
3 changes: 0 additions & 3 deletions src/core/EffectChain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,15 +191,12 @@ 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);
}
}

Expand Down
114 changes: 0 additions & 114 deletions src/core/MixHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,6 @@
#include "ValueBuffer.h"
#include "SampleFrame.h"



static bool s_NaNHandler;


namespace lmms::MixHelpers
{

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 )
Expand Down
25 changes: 20 additions & 5 deletions src/core/Mixer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
*
*/

#include "Mixer.h"

#include <QDomElement>

#include "AudioEngine.h"
Expand All @@ -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
Expand Down Expand Up @@ -161,77 +165,88 @@



void MixerChannel::doProcessing()
{
const fpp_t fpp = Engine::audioEngine()->framesPerPeriod();

if( m_muted == false )
{
for( MixerRoute * senderRoute : m_receives )
{
MixerChannel * sender = senderRoute->sender();
FloatModel * sendModel = senderRoute->amount();
if( ! sendModel ) qFatal( "Error: no send model found from %d to %d", senderRoute->senderIndex(), m_channelIndex );

if( sender->m_hasInput || sender->m_stillRunning )
{
// figure out if we're getting sample-exact input
ValueBuffer * sendBuf = sendModel->valueBuffer();
ValueBuffer * volBuf = sender->m_volumeModel.valueBuffer();

// mix it's output with this one's output
SampleFrame* ch_buf = sender->m_buffer;

// use sample-exact mixing if sample-exact values are available
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;
}
}


const float v = m_volumeModel.value();

if( m_hasInput )
{
// only start fxchain when we have input...
m_fxChain.startRunning();
}

m_stillRunning = m_fxChain.processAudioBuffer( m_buffer, fpp, m_hasInput );

if (silenceInvalidOutput())
{
const auto begin = m_buffer->data();
const auto end = begin + fpp * DEFAULT_CHANNELS;
const auto fn = [](const auto& sample) { return !std::isfinite(sample); };
const auto hasInvalidOutput = std::any_of(begin, end, fn);

m_silenced.store(hasInvalidOutput, std::memory_order_relaxed);
if (hasInvalidOutput) { std::fill(begin, end, 0.f); }
}

SampleFrame peakSamples = getAbsPeakValues(m_buffer, fpp);
m_peakLeft = std::max(m_peakLeft, peakSamples[0] * v);
m_peakRight = std::max(m_peakRight, peakSamples[1] * v);
}
else
{
m_peakLeft = m_peakRight = 0.0f;
}

// increment dependency counter of all receivers
processed();
}



Check notice on line 249 in src/core/Mixer.cpp

View check run for this annotation

codefactor.io / CodeFactor

src/core/Mixer.cpp#L168-L249

Complex Method
Mixer::Mixer() :
Model( nullptr ),
JournallingObject(),
Expand Down Expand Up @@ -724,7 +739,7 @@
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
Expand Down
4 changes: 0 additions & 4 deletions src/core/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -709,10 +709,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() )
Expand Down
19 changes: 19 additions & 0 deletions src/gui/MixerChannelView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
#include <QMenu>
#include <QMessageBox>
#include <QPainter>
#include <QTimer>
#include <cassert>
#include <iostream>

#include "CaptionMenu.h"
#include "ColorChooser.h"
Expand Down Expand Up @@ -148,6 +150,23 @@ MixerChannelView::MixerChannelView(QWidget* parent, MixerView* mixerView, int ch
mainLayout->addWidget(m_peakIndicator);
mainLayout->addWidget(m_fader, 1, Qt::AlignHCenter);

if (MixerChannel::silenceInvalidOutput())
{
const auto silenceUpdateTimer = new QTimer{this};
connect(silenceUpdateTimer, &QTimer::timeout, [this]
{
const auto channel = m_mixerView->getMixer()->mixerChannel(m_channelIndex);
if (channel->silenced())
{
m_peakIndicator->resetPeakToMinusInf();
std::cerr << "Mixer channel " << m_channelIndex << " has been silenced, invalid output detected (inf, nan, etc)\n";
}
});

constexpr auto updateIntervalMs = 5000;
silenceUpdateTimer->start(updateIntervalMs);
}

connect(m_renameLineEdit, &QLineEdit::editingFinished, this, &MixerChannelView::renameFinished);
}

Expand Down
Loading
Loading