Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Effect crasher fix #4707

Merged
merged 22 commits into from
Nov 3, 2022
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
29fa03b
Added a TODO for a probably data race in effectprocessor.
daschuer Oct 19, 2021
36fbe82
Added a TODO for another probably data race in effectprocessor.
daschuer Oct 19, 2021
027ddee
Remove unused function EnginEffectChain::getChannelStatus()
daschuer Mar 16, 2022
fd60cf9
Avoid write access via reference
daschuer Mar 16, 2022
e379844
Add ChannelHandleMap::isEmpty()
daschuer Mar 16, 2022
b019fa7
Pass ChannelHandleAndGroup instead of just the group name to avoid ha…
daschuer Mar 17, 2022
f5bb9a1
Move the skin warning before the assertion
daschuer Mar 19, 2022
5663ae2
Enable the effects AFTER we have added effect slots
daschuer Mar 19, 2022
895a7e8
extend scope for a better readability
daschuer Mar 23, 2022
2d2bb6c
Introduce an updateActiveState() function to be able to clean up buff…
daschuer Mar 26, 2022
dff90d2
process one more buffer after eject
daschuer Mar 26, 2022
bf1af79
Use the fadeout flag to fade down and initialize the effect buffers w…
daschuer Mar 28, 2022
313d4ab
EffectProcessor: split out initalizeChannelHandle
daschuer Mar 29, 2022
c4b081a
Don't delete effect state object when chain is disabled. From the use…
daschuer Mar 29, 2022
b4dc449
Use override keyword
daschuer Mar 29, 2022
2250eaa
Manage the lifetime of the effect state by a std::unique_ptr
daschuer Apr 2, 2022
27dd095
Merge remote-tracking branch 'upstream/main' into effects_refactoring_3
daschuer Jul 19, 2022
cba84c1
Added missing null check in CueControl::hintReader()
daschuer Jul 22, 2022
5d63ac3
re-enabble debug guard
daschuer Oct 28, 2022
b504c61
Introduce kInitalSamplerRate to use before the SoundDevice is set up
daschuer Oct 28, 2022
3528e4c
Avoid confusing handle().handle() code
daschuer Oct 28, 2022
8d133e6
Added missing final keyword
daschuer Oct 28, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 49 additions & 118 deletions src/effects/backends/effectprocessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "engine/effects/groupfeaturestate.h"
#include "engine/effects/message.h"
#include "engine/engine.h"
#include "util/sample.h"
#include "util/types.h"

/// Effects are implemented as two separate classes, an EffectState subclass and
Expand Down Expand Up @@ -71,14 +72,12 @@ class EffectProcessor {
const QSet<ChannelHandleAndGroup>& activeInputChannels,
const QSet<ChannelHandleAndGroup>& registeredOutputChannels,
const mixxx::EngineParameters& engineParameters) = 0;
virtual void initializeInputChannel(
ChannelHandle inputChannel,
const mixxx::EngineParameters& engineParameters) = 0;
virtual void loadEngineEffectParameters(
const QMap<QString, EngineEffectParameterPointer>& parameters) = 0;
virtual EffectState* createState(const mixxx::EngineParameters& engineParameters) = 0;
virtual void deleteStatesForInputChannel(ChannelHandle inputChannel) = 0;

// Called from the audio thread
virtual bool loadStatesForInputChannel(ChannelHandle inputChannel,
const EffectStatesMap* pStatesMap) = 0;
virtual bool hasStatesForInputChannel(ChannelHandle inputChannel) const = 0;

/// Called from the audio thread
/// This method takes a buffer of audio samples as pInput, processes the buffer
Expand Down Expand Up @@ -112,28 +111,9 @@ class EffectProcessorImpl : public EffectProcessor {
/// Subclasses should not implement their own destructor. All state should
/// be stored in the EffectState subclass, not the EffectProcessorImpl subclass.
~EffectProcessorImpl() {
if (kEffectDebugOutput) {
qDebug() << "~EffectProcessorImpl" << this;
}
int inputChannelHandleNumber = 0;
for (ChannelHandleMap<EffectSpecificState*>& outputsMap : m_channelStateMatrix) {
int outputChannelHandleNumber = 0;
for (EffectSpecificState* pState : outputsMap) {
VERIFY_OR_DEBUG_ASSERT(pState != nullptr) {
continue;
}
if (kEffectDebugOutput) {
qDebug() << "~EffectProcessorImpl deleting EffectState" << pState
<< "for input ChannelHandle(" << inputChannelHandleNumber << ")"
<< "and output ChannelHandle(" << outputChannelHandleNumber << ")";
}
delete pState;
outputChannelHandleNumber++;
}
outputsMap.clear();
inputChannelHandleNumber++;
}
m_channelStateMatrix.clear();
//if (kEffectDebugOutput) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uncomment debug guards

qDebug() << "~EffectProcessorImpl" << this;
//}
};

/// NOTE: Subclasses for Built-In effects must implement the following static methods for
Expand All @@ -156,7 +136,8 @@ class EffectProcessorImpl : public EffectProcessor {
const mixxx::EngineParameters& engineParameters,
const EffectEnableState enableState,
const GroupFeatureState& groupFeatures) final {
EffectSpecificState* pState = m_channelStateMatrix[inputHandle][outputHandle];
EffectSpecificState* pState =
m_channelStateMatrix[inputHandle][outputHandle.handle()].get();
VERIFY_OR_DEBUG_ASSERT(pState != nullptr) {
if (kEffectDebugOutput) {
qWarning() << "EffectProcessorImpl::process could not retrieve"
Expand All @@ -166,8 +147,7 @@ class EffectProcessorImpl : public EffectProcessor {
<< "EffectState should have been preallocated in the"
"main thread.";
}
pState = createSpecificState(engineParameters);
m_channelStateMatrix[inputHandle][outputHandle] = pState;
SampleUtil::copy(pOutput, pInput, engineParameters.samplesPerBuffer());
}
processChannel(pState, pInput, pOutput, engineParameters, enableState, groupFeatures);
}
Expand All @@ -178,110 +158,61 @@ class EffectProcessorImpl : public EffectProcessor {
m_registeredOutputChannels = registeredOutputChannels;

for (const ChannelHandleAndGroup& inputChannel : activeInputChannels) {
if (kEffectDebugOutput) {
qDebug() << this << "EffectProcessorImpl::initialize allocating "
"EffectStates for input"
<< inputChannel;
}
ChannelHandleMap<EffectSpecificState*> outputChannelMap;
for (const ChannelHandleAndGroup& outputChannel :
std::as_const(m_registeredOutputChannels)) {
outputChannelMap.insert(outputChannel.handle(),
createSpecificState(engineParameters));
if (kEffectDebugOutput) {
qDebug() << this << "EffectProcessorImpl::initialize "
"registering output"
<< outputChannel << outputChannelMap[outputChannel.handle()];
}
}
m_channelStateMatrix.insert(inputChannel.handle(), outputChannelMap);
initializeInputChannel(inputChannel.handle(), engineParameters);
}
};

EffectState* createState(const mixxx::EngineParameters& engineParameters) final {
return createSpecificState(engineParameters);
};

bool loadStatesForInputChannel(ChannelHandle inputChannel,
const EffectStatesMap* pStatesMap) final {
void initializeInputChannel(ChannelHandle inputChannel,
const mixxx::EngineParameters& engineParameters) final {
if (kEffectDebugOutput) {
qDebug() << "EffectProcessorImpl::loadStatesForInputChannel" << this
<< "input" << inputChannel;
qDebug() << this << "EffectProcessorImpl::initialize allocating "
"EffectStates for input"
<< inputChannel;
}

// NOTE: ChannelHandleMap is like a map in that it associates an
// object with a ChannelHandle key, but it is actually backed by a
// QVarLengthArray, not a QMap. So it is okay that
// m_channelStateMatrix may be accessed concurrently in the main
// thread in deleteStatesForInputChannel.

// Can't directly cast a ChannelHandleMap from containing the base
// EffectState* type to EffectSpecificState* type, so iterate through
// pStatesMap to build a new ChannelHandleMap with
// dynamic_cast'ed states.
ChannelHandleMap<EffectSpecificState*>& effectSpecificStatesMap =
m_channelStateMatrix[inputChannel];

// deleteStatesForInputChannel should have been called before a new
// map of EffectStates was sent to this function, or this is the first
// time states are being loaded for this input channel, so
// effectSpecificStatesMap should be empty and this loop should
// not go through any iterations.
for (EffectSpecificState* pState : effectSpecificStatesMap) {
VERIFY_OR_DEBUG_ASSERT(pState == nullptr) {
delete pState;
int requiredVectorSize = 0;
// For fast lookups we use a vector with index = handle;
// gaps are filled with nullptr
for (const ChannelHandleAndGroup& outputChannel :
std::as_const(m_registeredOutputChannels)) {
int vectorIndex = outputChannel.handle().handle();
if (requiredVectorSize <= vectorIndex) {
requiredVectorSize = vectorIndex + 1;
}
}

QSet<ChannelHandleAndGroup> receivedOutputChannels = m_registeredOutputChannels;
DEBUG_ASSERT(requiredVectorSize > 0);
auto& outputChannelStates = m_channelStateMatrix[inputChannel];
DEBUG_ASSERT(outputChannelStates.size() == 0);
outputChannelStates.reserve(requiredVectorSize);
outputChannelStates.clear();
for (int i = 0; i < requiredVectorSize; ++i) {
outputChannelStates.push_back(std::unique_ptr<EffectSpecificState>());
}
for (const ChannelHandleAndGroup& outputChannel :
std::as_const(m_registeredOutputChannels)) {
outputChannelStates[outputChannel.handle().handle()].reset(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is handle().handle()? Can we make that more meaningful?

createSpecificState(engineParameters));
if (kEffectDebugOutput) {
qDebug() << "EffectProcessorImpl::loadStatesForInputChannel"
<< this << "output" << outputChannel;
qDebug() << this
<< "EffectProcessorImpl::initialize "
"registering output"
<< outputChannel << outputChannel.handle()
<< outputChannelStates[outputChannel.handle().handle()].get();
}

auto pState = dynamic_cast<EffectSpecificState*>(
pStatesMap->at(outputChannel.handle()));
VERIFY_OR_DEBUG_ASSERT(pState != nullptr) {
return false;
}
effectSpecificStatesMap.insert(outputChannel.handle(), pState);
receivedOutputChannels.insert(outputChannel);
}
// Output channels are hardcoded in EngineMaster and should not
// be registered after Mixxx initializes.
DEBUG_ASSERT(receivedOutputChannels == m_registeredOutputChannels);
return true;
};

/// Called from main thread for garbage collection after an input channel is disabled
void deleteStatesForInputChannel(ChannelHandle inputChannel) final {
if (kEffectDebugOutput) {
qDebug() << "EffectProcessorImpl::deleteStatesForInputChannel"
<< this << inputChannel;
}

// NOTE: ChannelHandleMap is like a map in that it associates an
// object with a ChannelHandle key, but it is actually backed by a
// QVarLengthArray, not a QMap. So it is okay that
// m_channelStateMatrix may be accessed concurrently in the audio
// engine thread in loadStatesForInputChannel.

ChannelHandleMap<EffectSpecificState*>& stateMap =
m_channelStateMatrix[inputChannel];
for (EffectSpecificState* pState : stateMap) {
VERIFY_OR_DEBUG_ASSERT(pState != nullptr) {
continue;
}
if (kEffectDebugOutput) {
qDebug() << "EffectProcessorImpl::deleteStatesForInputChannel"
<< this << "deleting state" << pState;
bool hasStatesForInputChannel(ChannelHandle inputChannel) const {
if (inputChannel.handle() < m_channelStateMatrix.size()) {
for (const auto& pState : m_channelStateMatrix.at(inputChannel)) {
if (pState) {
return true;
}
}
delete pState;
}
stateMap.clear();
};
return false;
}

protected:
/// Subclasses for external effects plugins may reimplement this, but
Expand All @@ -297,5 +228,5 @@ class EffectProcessorImpl : public EffectProcessor {

private:
QSet<ChannelHandleAndGroup> m_registeredOutputChannels;
ChannelHandleMap<ChannelHandleMap<EffectSpecificState*>> m_channelStateMatrix;
ChannelHandleMap<std::vector<std::unique_ptr<EffectSpecificState>>> m_channelStateMatrix;
};
15 changes: 9 additions & 6 deletions src/effects/chains/equalizereffectchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,25 @@

#include "effects/effectslot.h"

EqualizerEffectChain::EqualizerEffectChain(const QString& group,
EqualizerEffectChain::EqualizerEffectChain(
const ChannelHandleAndGroup& handleAndGroup,
EffectsManager* pEffectsManager,
EffectsMessengerPointer pEffectsMessenger)
: PerGroupEffectChain(group,
formatEffectChainGroup(group),
: PerGroupEffectChain(
handleAndGroup,
formatEffectChainGroup(handleAndGroup.name()),
SignalProcessingStage::Prefader,
pEffectsManager,
pEffectsMessenger),
m_pCOFilterWaveform(
new ControlObject(ConfigKey(group, "filterWaveformEnable"))) {
new ControlObject(ConfigKey(handleAndGroup.name(), "filterWaveformEnable"))) {
// Add a single effect slot
addEffectSlot(formatEffectSlotGroup(group));
addEffectSlot(formatEffectSlotGroup(handleAndGroup.name()));
enableForInputChannel(handleAndGroup);
m_effectSlots[0]->setEnabled(true);
// DlgPrefEq loads the Effect with loadEffectToGroup

setupLegacyAliasesForGroup(group);
setupLegacyAliasesForGroup(handleAndGroup.name());
}

void EqualizerEffectChain::setFilterWaveform(bool state) {
Expand Down
3 changes: 2 additions & 1 deletion src/effects/chains/equalizereffectchain.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
class EqualizerEffectChain : public PerGroupEffectChain {
Q_OBJECT
public:
EqualizerEffectChain(const QString& group,
EqualizerEffectChain(
const ChannelHandleAndGroup& handleAndGroup,
EffectsManager* pEffectsManager,
EffectsMessengerPointer pEffectsMessenger);

Expand Down
17 changes: 3 additions & 14 deletions src/effects/chains/pergroupeffectchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

#include "effects/effectsmanager.h"

PerGroupEffectChain::PerGroupEffectChain(const QString& group,
PerGroupEffectChain::PerGroupEffectChain(
const ChannelHandleAndGroup& handleAndGroup,
const QString& chainSlotGroup,
SignalProcessingStage stage,
EffectsManager* pEffectsManager,
Expand All @@ -15,18 +16,6 @@ PerGroupEffectChain::PerGroupEffectChain(const QString& group,
m_pControlChainMix->set(1.0);
sendParameterUpdate();

// TODO(rryan): remove.
const ChannelHandleAndGroup* handleAndGroup = nullptr;
for (const ChannelHandleAndGroup& handle_group :
m_pEffectsManager->registeredInputChannels()) {
if (handle_group.name() == group) {
handleAndGroup = &handle_group;
break;
}
}
DEBUG_ASSERT(handleAndGroup != nullptr);

// Register this channel alone with the chain slot.
registerInputChannel(*handleAndGroup);
enableForInputChannel(*handleAndGroup);
registerInputChannel(handleAndGroup);
}
3 changes: 2 additions & 1 deletion src/effects/chains/pergroupeffectchain.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
class PerGroupEffectChain : public EffectChain {
Q_OBJECT
public:
PerGroupEffectChain(const QString& group,
PerGroupEffectChain(
const ChannelHandleAndGroup& handleGroup,
const QString& chainSlotGroup,
SignalProcessingStage stage,
EffectsManager* pEffectsManager,
Expand Down
11 changes: 7 additions & 4 deletions src/effects/chains/quickeffectchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@
#include "effects/effectslot.h"
#include "effects/presets/effectchainpresetmanager.h"

QuickEffectChain::QuickEffectChain(const QString& group,
QuickEffectChain::QuickEffectChain(
const ChannelHandleAndGroup& handleAndGroup,
EffectsManager* pEffectsManager,
EffectsMessengerPointer pEffectsMessenger)
: PerGroupEffectChain(group,
formatEffectChainGroup(group),
: PerGroupEffectChain(
handleAndGroup,
formatEffectChainGroup(handleAndGroup.name()),
SignalProcessingStage::Postfader,
pEffectsManager,
pEffectsMessenger) {
for (int i = 0; i < kNumEffectsPerUnit; ++i) {
addEffectSlot(formatEffectSlotGroup(group, i));
addEffectSlot(formatEffectSlotGroup(handleAndGroup.name(), i));
m_effectSlots.at(i)->setEnabled(true);
}
enableForInputChannel(handleAndGroup);
// The base EffectChain class constructor connects to the signal for the list of StandardEffectChain presets,
// but QuickEffectChain has a separate list, so disconnect the signal which is inappropriate for this subclass.
disconnect(m_pChainPresetManager.data(),
Expand Down
3 changes: 2 additions & 1 deletion src/effects/chains/quickeffectchain.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
class QuickEffectChain : public PerGroupEffectChain {
Q_OBJECT
public:
QuickEffectChain(const QString& group,
QuickEffectChain(
const ChannelHandleAndGroup& handleAndGroup,
EffectsManager* pEffectsManager,
EffectsMessengerPointer pEffectsMessenger);

Expand Down
20 changes: 4 additions & 16 deletions src/effects/effectchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -396,24 +396,12 @@ void EffectChain::enableForInputChannel(const ChannelHandleAndGroup& handleGroup
request->pTargetChain = m_pEngineEffectChain;
request->EnableInputChannelForChain.channelHandle = handleGroup.handle();

// Allocate EffectStates here in the main thread to avoid allocating
// memory in the realtime audio callback thread. Pointers to the
// EffectStates are passed to the EffectRequest and the EffectProcessorImpls
// store the pointers. The containers of EffectState* pointers get deleted
// by ~EffectsRequest, but the EffectStates are managed by EffectProcessorImpl.

// The EffectStates for one EngineEffectChain must be sent all together in
// the same message using an EffectStatesMapArray. If they were separated
// into a message for each effect, there would be a chance that the
// EngineEffectChain could get activated in one cycle of the audio callback
// thread but the EffectStates for an EngineEffect would not be received by
// EngineEffectsManager until the next audio callback cycle.

auto* pEffectStatesMapArray = new EffectStatesMapArray;
// Initialize EffectStates for the input channel here in the main thread to
// avoid allocating memory in the realtime audio callback thread.

for (int i = 0; i < m_effectSlots.size(); ++i) {
m_effectSlots[i]->fillEffectStatesMap(&(*pEffectStatesMapArray)[i]);
m_effectSlots[i]->initalizeInputChannel(handleGroup.handle());
}
request->EnableInputChannelForChain.pEffectStatesMapArray = pEffectStatesMapArray;

m_pMessenger->writeRequest(request);

Expand Down
Loading