-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Effect crasher fix #4707
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 36fbe82
Added a TODO for another probably data race in effectprocessor.
daschuer 027ddee
Remove unused function EnginEffectChain::getChannelStatus()
daschuer fd60cf9
Avoid write access via reference
daschuer e379844
Add ChannelHandleMap::isEmpty()
daschuer b019fa7
Pass ChannelHandleAndGroup instead of just the group name to avoid ha…
daschuer f5bb9a1
Move the skin warning before the assertion
daschuer 5663ae2
Enable the effects AFTER we have added effect slots
daschuer 895a7e8
extend scope for a better readability
daschuer 2d2bb6c
Introduce an updateActiveState() function to be able to clean up buff…
daschuer dff90d2
process one more buffer after eject
daschuer bf1af79
Use the fadeout flag to fade down and initialize the effect buffers w…
daschuer 313d4ab
EffectProcessor: split out initalizeChannelHandle
daschuer c4b081a
Don't delete effect state object when chain is disabled. From the use…
daschuer b4dc449
Use override keyword
daschuer 2250eaa
Manage the lifetime of the effect state by a std::unique_ptr
daschuer 27dd095
Merge remote-tracking branch 'upstream/main' into effects_refactoring_3
daschuer cba84c1
Added missing null check in CueControl::hintReader()
daschuer 5d63ac3
re-enabble debug guard
daschuer b504c61
Introduce kInitalSamplerRate to use before the SoundDevice is set up
daschuer 3528e4c
Avoid confusing handle().handle() code
daschuer 8d133e6
Added missing final keyword
daschuer File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
@@ -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 | ||
|
@@ -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) { | ||
qDebug() << "~EffectProcessorImpl" << this; | ||
//} | ||
}; | ||
|
||
/// NOTE: Subclasses for Built-In effects must implement the following static methods for | ||
|
@@ -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" | ||
|
@@ -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); | ||
} | ||
|
@@ -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( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
@@ -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; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uncomment debug guards