diff --git a/src/effects/backends/effectprocessor.h b/src/effects/backends/effectprocessor.h index 837da2b91043..80fd6a0904cf 100644 --- a/src/effects/backends/effectprocessor.h +++ b/src/effects/backends/effectprocessor.h @@ -71,14 +71,12 @@ class EffectProcessor { const QSet& activeInputChannels, const QSet& registeredOutputChannels, const mixxx::EngineParameters& engineParameters) = 0; + virtual void initializeInputChannel( + ChannelHandle inputChannel, + const mixxx::EngineParameters& engineParameters) = 0; virtual void loadEngineEffectParameters( const QMap& 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 @@ -178,110 +176,43 @@ class EffectProcessorImpl : public EffectProcessor { m_registeredOutputChannels = registeredOutputChannels; for (const ChannelHandleAndGroup& inputChannel : activeInputChannels) { - if (kEffectDebugOutput) { - qDebug() << this << "EffectProcessorImpl::initialize allocating " - "EffectStates for input" - << inputChannel; - } - ChannelHandleMap 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; - } - - // 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& 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; - } + qDebug() << this << "EffectProcessorImpl::initialize allocating " + "EffectStates for input" + << inputChannel; } - - QSet receivedOutputChannels = m_registeredOutputChannels; + ChannelHandleMap outputChannelMap; for (const ChannelHandleAndGroup& outputChannel : std::as_const(m_registeredOutputChannels)) { + outputChannelMap.insert(outputChannel.handle(), + createSpecificState(engineParameters)); if (kEffectDebugOutput) { - qDebug() << "EffectProcessorImpl::loadStatesForInputChannel" - << this << "output" << outputChannel; + qDebug() << this + << "EffectProcessorImpl::initialize " + "registering output" + << outputChannel << outputChannel.handle() + << outputChannelMap[outputChannel.handle()]; } - - auto pState = dynamic_cast( - 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; + m_channelStateMatrix.insert(inputChannel, outputChannelMap); }; - /// 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& 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 (EffectSpecificState* 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 diff --git a/src/effects/chains/equalizereffectchain.cpp b/src/effects/chains/equalizereffectchain.cpp index 4b5e7ce34768..320406a89350 100644 --- a/src/effects/chains/equalizereffectchain.cpp +++ b/src/effects/chains/equalizereffectchain.cpp @@ -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) { diff --git a/src/effects/chains/equalizereffectchain.h b/src/effects/chains/equalizereffectchain.h index 98a159738380..706c0e6f7e80 100644 --- a/src/effects/chains/equalizereffectchain.h +++ b/src/effects/chains/equalizereffectchain.h @@ -11,7 +11,8 @@ class EqualizerEffectChain : public PerGroupEffectChain { Q_OBJECT public: - EqualizerEffectChain(const QString& group, + EqualizerEffectChain( + const ChannelHandleAndGroup& handleAndGroup, EffectsManager* pEffectsManager, EffectsMessengerPointer pEffectsMessenger); diff --git a/src/effects/chains/pergroupeffectchain.cpp b/src/effects/chains/pergroupeffectchain.cpp index 2ec903bbb4cc..745a3920794a 100644 --- a/src/effects/chains/pergroupeffectchain.cpp +++ b/src/effects/chains/pergroupeffectchain.cpp @@ -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, @@ -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); } diff --git a/src/effects/chains/pergroupeffectchain.h b/src/effects/chains/pergroupeffectchain.h index acf10c3743cd..dc510a930bb8 100644 --- a/src/effects/chains/pergroupeffectchain.h +++ b/src/effects/chains/pergroupeffectchain.h @@ -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, diff --git a/src/effects/chains/quickeffectchain.cpp b/src/effects/chains/quickeffectchain.cpp index 617080f968aa..7df93c380ba1 100644 --- a/src/effects/chains/quickeffectchain.cpp +++ b/src/effects/chains/quickeffectchain.cpp @@ -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(), diff --git a/src/effects/chains/quickeffectchain.h b/src/effects/chains/quickeffectchain.h index 34c747281f7c..eecd76cc7762 100644 --- a/src/effects/chains/quickeffectchain.h +++ b/src/effects/chains/quickeffectchain.h @@ -10,7 +10,8 @@ class QuickEffectChain : public PerGroupEffectChain { Q_OBJECT public: - QuickEffectChain(const QString& group, + QuickEffectChain( + const ChannelHandleAndGroup& handleAndGroup, EffectsManager* pEffectsManager, EffectsMessengerPointer pEffectsMessenger); diff --git a/src/effects/effectchain.cpp b/src/effects/effectchain.cpp index 14e052d1ab54..16edcaaf1ce7 100644 --- a/src/effects/effectchain.cpp +++ b/src/effects/effectchain.cpp @@ -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); diff --git a/src/effects/effectslot.cpp b/src/effects/effectslot.cpp index 8d6083d9d558..d25f4a20027d 100644 --- a/src/effects/effectslot.cpp +++ b/src/effects/effectslot.cpp @@ -192,26 +192,11 @@ void EffectSlot::updateEngineState() { } } -void EffectSlot::fillEffectStatesMap(EffectStatesMap* pStatesMap) const { - //TODO: get actual configuration of engine - const mixxx::EngineParameters engineParameters( - mixxx::audio::SampleRate(96000), - MAX_BUFFER_LEN / mixxx::kEngineChannelCount); - - if (isLoaded()) { - for (const auto& outputChannel : - m_pEffectsManager->registeredOutputChannels()) { - pStatesMap->insert(outputChannel.handle(), - m_pEngineEffect->createState(engineParameters)); - } - } else { - for (EffectState* pState : *pStatesMap) { - if (pState) { - delete pState; - } - } - pStatesMap->clear(); +void EffectSlot::initalizeInputChannel(ChannelHandle inputChannel) { + if (!m_pEngineEffect) { + return; } + m_pEngineEffect->initalizeInputChannel(inputChannel); }; EffectManifestPointer EffectSlot::getManifest() const { diff --git a/src/effects/effectslot.h b/src/effects/effectslot.h index 9fc0a5a6bce5..ce2f3175eab9 100644 --- a/src/effects/effectslot.h +++ b/src/effects/effectslot.h @@ -123,7 +123,7 @@ class EffectSlot : public QObject { return m_group; } - void fillEffectStatesMap(EffectStatesMap* pStatesMap) const; + void initalizeInputChannel(ChannelHandle inputChannel); EffectManifestPointer getManifest() const; diff --git a/src/effects/effectsmanager.cpp b/src/effects/effectsmanager.cpp index 36c107e344db..23c20cf83be9 100644 --- a/src/effects/effectsmanager.cpp +++ b/src/effects/effectsmanager.cpp @@ -127,34 +127,34 @@ EffectChainPointer EffectsManager::getStandardEffectChain(int unitNumber) const return m_standardEffectChains.at(unitNumber); } -void EffectsManager::addDeck(const QString& deckGroupName) { - addEqualizerEffectChain(deckGroupName); - addQuickEffectChain(deckGroupName); +void EffectsManager::addDeck(const ChannelHandleAndGroup& deckHandleGroup) { + addEqualizerEffectChain(deckHandleGroup); + addQuickEffectChain(deckHandleGroup); } -void EffectsManager::addEqualizerEffectChain(const QString& deckGroupName) { +void EffectsManager::addEqualizerEffectChain(const ChannelHandleAndGroup& deckHandleGroup) { VERIFY_OR_DEBUG_ASSERT(!m_equalizerEffectChains.contains( - EqualizerEffectChain::formatEffectChainGroup(deckGroupName))) { + EqualizerEffectChain::formatEffectChainGroup(deckHandleGroup.name()))) { return; } auto pChainSlot = EqualizerEffectChainPointer( - new EqualizerEffectChain(deckGroupName, this, m_pMessenger)); + new EqualizerEffectChain(deckHandleGroup, this, m_pMessenger)); - m_equalizerEffectChains.insert(deckGroupName, pChainSlot); + m_equalizerEffectChains.insert(deckHandleGroup.name(), pChainSlot); m_effectChainSlotsByGroup.insert(pChainSlot->group(), pChainSlot); } -void EffectsManager::addQuickEffectChain(const QString& deckGroupName) { +void EffectsManager::addQuickEffectChain(const ChannelHandleAndGroup& deckHandleGroup) { VERIFY_OR_DEBUG_ASSERT(!m_quickEffectChains.contains( - QuickEffectChain::formatEffectChainGroup(deckGroupName))) { + QuickEffectChain::formatEffectChainGroup(deckHandleGroup.name()))) { return; } auto pChainSlot = QuickEffectChainPointer( - new QuickEffectChain(deckGroupName, this, m_pMessenger)); + new QuickEffectChain(deckHandleGroup, this, m_pMessenger)); - m_quickEffectChains.insert(deckGroupName, pChainSlot); + m_quickEffectChains.insert(deckHandleGroup.name(), pChainSlot); m_effectChainSlotsByGroup.insert(pChainSlot->group(), pChainSlot); } diff --git a/src/effects/effectsmanager.h b/src/effects/effectsmanager.h index 2e45b2564055..d6efe32f8e47 100644 --- a/src/effects/effectsmanager.h +++ b/src/effects/effectsmanager.h @@ -26,7 +26,7 @@ class EffectsManager { virtual ~EffectsManager(); void setup(); - void addDeck(const QString& deckGroupName); + void addDeck(const ChannelHandleAndGroup& deckHandleGroup); EffectChainPointer getEffectChain(const QString& group) const; EqualizerEffectChainPointer getEqualizerEffectChain( @@ -75,8 +75,8 @@ class EffectsManager { void addStandardEffectChains(); void addOutputEffectChain(); - void addEqualizerEffectChain(const QString& deckGroupName); - void addQuickEffectChain(const QString& deckGroupName); + void addEqualizerEffectChain(const ChannelHandleAndGroup& deckHandleGroup); + void addQuickEffectChain(const ChannelHandleAndGroup& deckHandleGroup); void readEffectsXml(); void saveEffectsXml(); diff --git a/src/effects/effectsmessenger.cpp b/src/effects/effectsmessenger.cpp index 2eec71ef6151..200278be7542 100644 --- a/src/effects/effectsmessenger.cpp +++ b/src/effects/effectsmessenger.cpp @@ -90,13 +90,5 @@ void EffectsMessenger::collectGarbage(const EffectsRequest* pRequest) { qDebug() << debugString() << "delete" << pRequest->RemoveEffectChain.pChain; } delete pRequest->RemoveEffectChain.pChain; - } else if (pRequest->type == EffectsRequest::DISABLE_EFFECT_CHAIN_FOR_INPUT_CHANNEL) { - if (kEffectDebugOutput) { - qDebug() << debugString() << "deleting states for input channel" - << pRequest->DisableInputChannelForChain.channelHandle - << "for EngineEffectChain" << pRequest->pTargetChain; - } - pRequest->pTargetChain->deleteStatesForInputChannel( - pRequest->DisableInputChannelForChain.channelHandle); } } diff --git a/src/engine/cachingreader/cachingreader.cpp b/src/engine/cachingreader/cachingreader.cpp index 2402a2263764..85415a0ec07b 100644 --- a/src/engine/cachingreader/cachingreader.cpp +++ b/src/engine/cachingreader/cachingreader.cpp @@ -282,7 +282,9 @@ void CachingReader::process() { // track is already loading! In this case the TRACK_LOADED will // be the very next status update. if (!m_state.testAndSetRelease(STATE_TRACK_UNLOADING, STATE_IDLE)) { - DEBUG_ASSERT(atomicLoadRelaxed(m_state) == STATE_TRACK_LOADING); + DEBUG_ASSERT( + atomicLoadRelaxed(m_state) == STATE_TRACK_LOADING || + atomicLoadRelaxed(m_state) == STATE_IDLE); } } } diff --git a/src/engine/channelhandle.h b/src/engine/channelhandle.h index e20a9e1583e2..766622596357 100644 --- a/src/engine/channelhandle.h +++ b/src/engine/channelhandle.h @@ -207,6 +207,14 @@ class ChannelHandleMap { m_data.clear(); } + int size() const { + return m_data.size(); + } + + bool isEmpty() { + return m_data.isEmpty(); + } + typename container_type::iterator begin() { return m_data.begin(); } diff --git a/src/engine/channelmixer.cpp b/src/engine/channelmixer.cpp index 9a76a2c5a487..664e87e24e7c 100644 --- a/src/engine/channelmixer.cpp +++ b/src/engine/channelmixer.cpp @@ -27,7 +27,10 @@ void ChannelMixer::applyEffectsAndMixChannels(const EngineMaster::GainCalculator EngineMaster::GainCache& gainCache = (*channelGainCache)[pChannelInfo->m_index]; CSAMPLE_GAIN oldGain = gainCache.m_gain; CSAMPLE_GAIN newGain; - if (gainCache.m_fadeout) { + bool fadeout = gainCache.m_fadeout || + (pChannelInfo->m_pChannel && + !pChannelInfo->m_pChannel->isActive()); + if (fadeout) { newGain = 0; gainCache.m_fadeout = false; } else { @@ -42,7 +45,8 @@ void ChannelMixer::applyEffectsAndMixChannels(const EngineMaster::GainCalculator iSampleRate, pChannelInfo->m_features, oldGain, - newGain); + newGain, + fadeout); } } @@ -69,7 +73,10 @@ void ChannelMixer::applyEffectsInPlaceAndMixChannels( EngineMaster::GainCache& gainCache = (*channelGainCache)[pChannelInfo->m_index]; CSAMPLE_GAIN oldGain = gainCache.m_gain; CSAMPLE_GAIN newGain; - if (gainCache.m_fadeout) { + bool fadeout = gainCache.m_fadeout || + (pChannelInfo->m_pChannel && + !pChannelInfo->m_pChannel->isActive()); + if (fadeout) { newGain = 0; gainCache.m_fadeout = false; } else { @@ -83,7 +90,8 @@ void ChannelMixer::applyEffectsInPlaceAndMixChannels( iSampleRate, pChannelInfo->m_features, oldGain, - newGain); + newGain, + fadeout); SampleUtil::add(pOutput, pChannelInfo->m_pBuffer, iBufferSize); } } diff --git a/src/engine/channels/engineaux.cpp b/src/engine/channels/engineaux.cpp index 99ff35631e47..b555ed770f34 100644 --- a/src/engine/channels/engineaux.cpp +++ b/src/engine/channels/engineaux.cpp @@ -15,8 +15,7 @@ EngineAux::EngineAux(const ChannelHandleAndGroup& handleGroup, EffectsManager* p /*isTalkoverChannel*/ false, /*isPrimaryDeck*/ false), m_pInputConfigured(new ControlObject(ConfigKey(getGroup(), "input_configured"))), - m_pPregain(new ControlAudioTaperPot(ConfigKey(getGroup(), "pregain"), -12, 12, 0.5)), - m_wasActive(false) { + m_pPregain(new ControlAudioTaperPot(ConfigKey(getGroup(), "pregain"), -12, 12, 0.5)) { // Make input_configured read-only. m_pInputConfigured->setReadOnly(); ControlDoublePrivate::insertAlias(ConfigKey(getGroup(), "enabled"), @@ -32,15 +31,18 @@ EngineAux::~EngineAux() { delete m_pPregain; } -bool EngineAux::isActive() { +EngineChannel::ActiveState EngineAux::updateActiveState() { bool enabled = m_pInputConfigured->toBool(); if (enabled && m_sampleBuffer) { - m_wasActive = true; - } else if (m_wasActive) { + m_active = true; + return ActiveState::Active; + } + if (m_active) { m_vuMeter.reset(); - m_wasActive = false; + m_active = false; + return ActiveState::WasActive; } - return m_wasActive; + return ActiveState::Inactive; } void EngineAux::onInputConfigured(const AudioInput& input) { diff --git a/src/engine/channels/engineaux.h b/src/engine/channels/engineaux.h index aa6c8fe72e76..5934ca5fec02 100644 --- a/src/engine/channels/engineaux.h +++ b/src/engine/channels/engineaux.h @@ -18,7 +18,7 @@ class EngineAux : public EngineChannel, public AudioDestination { EngineAux(const ChannelHandleAndGroup& handleGroup, EffectsManager* pEffectsManager); virtual ~EngineAux(); - bool isActive(); + ActiveState updateActiveState() override; /// Called by EngineMaster whenever is requesting a new buffer of audio. virtual void process(CSAMPLE* pOutput, const int iBufferSize); @@ -45,5 +45,4 @@ class EngineAux : public EngineChannel, public AudioDestination { private: QScopedPointer m_pInputConfigured; ControlAudioTaperPot* m_pPregain; - bool m_wasActive; }; diff --git a/src/engine/channels/enginechannel.cpp b/src/engine/channels/enginechannel.cpp index 3756f341c440..941a0c7921ce 100644 --- a/src/engine/channels/enginechannel.cpp +++ b/src/engine/channels/enginechannel.cpp @@ -15,6 +15,7 @@ EngineChannel::EngineChannel(const ChannelHandleAndGroup& handleGroup, m_pSampleRate(new ControlProxy("[Master]", "samplerate")), m_sampleBuffer(nullptr), m_bIsPrimaryDeck(isPrimaryDeck), + m_active(false), m_bIsTalkoverChannel(isTalkoverChannel), m_channelIndex(-1) { m_pPFL = new ControlPushButton(ConfigKey(getGroup(), "pfl")); diff --git a/src/engine/channels/enginechannel.h b/src/engine/channels/enginechannel.h index 6771447c1581..6f17da014c13 100644 --- a/src/engine/channels/enginechannel.h +++ b/src/engine/channels/enginechannel.h @@ -21,6 +21,12 @@ class EngineChannel : public EngineObject { RIGHT, }; + enum class ActiveState { + Inactive = 0, + Active, + WasActive + }; + EngineChannel(const ChannelHandleAndGroup& handleGroup, ChannelOrientation defaultOrientation, EffectsManager* pEffectsManager, @@ -38,7 +44,11 @@ class EngineChannel : public EngineObject { return m_group.name(); } - virtual bool isActive() = 0; + virtual ActiveState updateActiveState() = 0; + bool isActive() { + return m_active; + } + void setPfl(bool enabled); virtual bool isPflEnabled() const; void setMaster(bool enabled); @@ -76,6 +86,7 @@ class EngineChannel : public EngineObject { // If set to true, this engine channel represents one of the primary playback decks. // It is used to check for valid bpm targets by the sync code. const bool m_bIsPrimaryDeck; + bool m_active; private slots: void slotOrientationLeft(double v); diff --git a/src/engine/channels/enginedeck.cpp b/src/engine/channels/enginedeck.cpp index de05794bca51..67a3e8f3894c 100644 --- a/src/engine/channels/enginedeck.cpp +++ b/src/engine/channels/enginedeck.cpp @@ -22,10 +22,7 @@ EngineDeck::EngineDeck( primaryDeck), m_pConfig(pConfig), m_pInputConfigured(new ControlObject(ConfigKey(getGroup(), "input_configured"))), - m_pPassing(new ControlPushButton(ConfigKey(getGroup(), "passthrough"))), - // Need a +1 here because the CircularBuffer only allows its size-1 - // items to be held at once (it keeps a blank spot open persistently) - m_wasActive(false) { + m_pPassing(new ControlPushButton(ConfigKey(getGroup(), "passthrough"))) { m_pInputConfigured->setReadOnly(); // Set up passthrough utilities and fields m_pPassing->setButtonMode(ControlPushButton::POWERWINDOW); @@ -101,7 +98,7 @@ EngineBuffer* EngineDeck::getEngineBuffer() { return m_pBuffer; } -bool EngineDeck::isActive() { +EngineChannel::ActiveState EngineDeck::updateActiveState() { bool active = false; if (m_bPassthroughWasActive && !m_bPassthroughIsActive) { active = true; @@ -109,11 +106,16 @@ bool EngineDeck::isActive() { active = m_pBuffer->isTrackLoaded() || isPassthroughActive(); } - if (!active && m_wasActive) { + if (active) { + m_active = true; + return ActiveState::Active; + } + if (m_active) { m_vuMeter.reset(); + m_active = false; + return ActiveState::WasActive; } - m_wasActive = active; - return active; + return ActiveState::Inactive; } void EngineDeck::receiveBuffer( diff --git a/src/engine/channels/enginedeck.h b/src/engine/channels/enginedeck.h index a6bb982144c5..50f61ec86227 100644 --- a/src/engine/channels/enginedeck.h +++ b/src/engine/channels/enginedeck.h @@ -38,7 +38,7 @@ class EngineDeck : public EngineChannel, public AudioDestination { // TODO(XXX) This hack needs to be removed. virtual EngineBuffer* getEngineBuffer(); - virtual bool isActive(); + EngineChannel::ActiveState updateActiveState() override; // This is called by SoundManager whenever there are new samples from the // configured input to be processed. This is run in the callback thread of @@ -77,5 +77,4 @@ class EngineDeck : public EngineChannel, public AudioDestination { ControlPushButton* m_pPassing; bool m_bPassthroughIsActive; bool m_bPassthroughWasActive; - bool m_wasActive; }; diff --git a/src/engine/channels/enginemicrophone.cpp b/src/engine/channels/enginemicrophone.cpp index c7d41ac97507..943eb7b8eb34 100644 --- a/src/engine/channels/enginemicrophone.cpp +++ b/src/engine/channels/enginemicrophone.cpp @@ -16,8 +16,7 @@ EngineMicrophone::EngineMicrophone(const ChannelHandleAndGroup& handleGroup, /*isTalkoverChannel*/ true, /*isPrimaryDeck*/ false), m_pInputConfigured(new ControlObject(ConfigKey(getGroup(), "input_configured"))), - m_pPregain(new ControlAudioTaperPot(ConfigKey(getGroup(), "pregain"), -12, 12, 0.5)), - m_wasActive(false) { + m_pPregain(new ControlAudioTaperPot(ConfigKey(getGroup(), "pregain"), -12, 12, 0.5)) { // Make input_configured read-only. m_pInputConfigured->setReadOnly(); ControlDoublePrivate::insertAlias(ConfigKey(getGroup(), "enabled"), @@ -30,15 +29,18 @@ EngineMicrophone::~EngineMicrophone() { delete m_pPregain; } -bool EngineMicrophone::isActive() { +EngineChannel::ActiveState EngineMicrophone::updateActiveState() { bool enabled = m_pInputConfigured->toBool(); if (enabled && m_sampleBuffer) { - m_wasActive = true; - } else if (m_wasActive) { + m_active = true; + return ActiveState::Active; + } + if (m_active) { m_vuMeter.reset(); - m_wasActive = false; + m_active = false; + return ActiveState::WasActive; } - return m_wasActive; + return ActiveState::Inactive; } void EngineMicrophone::onInputConfigured(const AudioInput& input) { diff --git a/src/engine/channels/enginemicrophone.h b/src/engine/channels/enginemicrophone.h index 282407d35752..789be15cf136 100644 --- a/src/engine/channels/enginemicrophone.h +++ b/src/engine/channels/enginemicrophone.h @@ -22,7 +22,7 @@ class EngineMicrophone : public EngineChannel, public AudioDestination { EffectsManager* pEffectsManager); virtual ~EngineMicrophone(); - bool isActive(); + ActiveState updateActiveState(); // Called by EngineMaster whenever is requesting a new buffer of audio. virtual void process(CSAMPLE* pOutput, const int iBufferSize); @@ -52,6 +52,4 @@ class EngineMicrophone : public EngineChannel, public AudioDestination { private: QScopedPointer m_pInputConfigured; ControlAudioTaperPot* m_pPregain; - - bool m_wasActive; }; diff --git a/src/engine/effects/engineeffect.cpp b/src/engine/effects/engineeffect.cpp index 20e9b30e3709..2f9d2190b359 100644 --- a/src/engine/effects/engineeffect.cpp +++ b/src/engine/effects/engineeffect.cpp @@ -46,24 +46,16 @@ EngineEffect::~EngineEffect() { m_parameters.clear(); } -EffectState* EngineEffect::createState(const mixxx::EngineParameters& engineParameters) { - VERIFY_OR_DEBUG_ASSERT(m_pProcessor) { - return new EffectState(engineParameters); +void EngineEffect::initalizeInputChannel(ChannelHandle inputChannel) { + if (m_pProcessor->hasStatesForInputChannel(inputChannel)) { + // already initialized for this input channel } - return m_pProcessor->createState(engineParameters); -} - -void EngineEffect::loadStatesForInputChannel(ChannelHandle inputChannel, - EffectStatesMap* pStatesMap) { - if (kEffectDebugOutput) { - qDebug() << "EngineEffect::loadStatesForInputChannel" << this - << "loading states for input" << inputChannel; - } - m_pProcessor->loadStatesForInputChannel(inputChannel, pStatesMap); -} -void EngineEffect::deleteStatesForInputChannel(ChannelHandle inputChannel) { - m_pProcessor->deleteStatesForInputChannel(inputChannel); + //TODO: get actual configuration of engine + const mixxx::EngineParameters engineParameters( + mixxx::audio::SampleRate(96000), + MAX_BUFFER_LEN / mixxx::kEngineChannelCount); + m_pProcessor->initializeInputChannel(inputChannel, engineParameters); } bool EngineEffect::processEffectsRequest(EffectsRequest& message, diff --git a/src/engine/effects/engineeffect.h b/src/engine/effects/engineeffect.h index ea83a6fbc158..7c46105e852f 100644 --- a/src/engine/effects/engineeffect.h +++ b/src/engine/effects/engineeffect.h @@ -31,14 +31,8 @@ class EngineEffect final : public EffectsRequestHandler { /// Called in main thread by EffectSlot ~EngineEffect(); - /// Called in main thread to allocate an EffectState - EffectState* createState(const mixxx::EngineParameters& engineParameters); - - /// Called in audio thread to load EffectStates received from the main thread - void loadStatesForInputChannel(ChannelHandle inputChannel, - EffectStatesMap* pStatesMap); - /// Called from the main thread for garbage collection after an input channel is disabled - void deleteStatesForInputChannel(ChannelHandle inputChannel); + /// Called from the main thread to make sure that the channel already has states + void initalizeInputChannel(ChannelHandle inputChannel); /// Called in audio thread bool processEffectsRequest( diff --git a/src/engine/effects/engineeffectchain.cpp b/src/engine/effects/engineeffectchain.cpp index c3101ecb5ab3..d4f868ef1d63 100644 --- a/src/engine/effects/engineeffectchain.cpp +++ b/src/engine/effects/engineeffectchain.cpp @@ -126,8 +126,7 @@ bool EngineEffectChain::processEffectsRequest(EffectsRequest& message, << message.EnableInputChannelForChain.channelHandle; } response.success = enableForInputChannel( - message.EnableInputChannelForChain.channelHandle, - message.EnableInputChannelForChain.pEffectStatesMapArray); + message.EnableInputChannelForChain.channelHandle); break; case EffectsRequest::DISABLE_EFFECT_CHAIN_FOR_INPUT_CHANNEL: if (kEffectDebugOutput) { @@ -146,94 +145,40 @@ bool EngineEffectChain::processEffectsRequest(EffectsRequest& message, return true; } -bool EngineEffectChain::enableForInputChannel(ChannelHandle inputHandle, - EffectStatesMapArray* statesForEffectsInChain) { +bool EngineEffectChain::enableForInputChannel(ChannelHandle inputHandle) { if (kEffectDebugOutput) { qDebug() << "EngineEffectChain::enableForInputChannel" << this << inputHandle; } auto& outputMap = m_chainStatusForChannelMatrix[inputHandle]; for (auto&& outputChannelStatus : outputMap) { - VERIFY_OR_DEBUG_ASSERT(outputChannelStatus.enableState != - EffectEnableState::Enabled) { - for (auto&& pStatesMap : *statesForEffectsInChain) { - for (auto&& pState : pStatesMap) { - delete pState; - } - } - return false; - } + DEBUG_ASSERT(outputChannelStatus.enableState != EffectEnableState::Enabled); outputChannelStatus.enableState = EffectEnableState::Enabling; } - for (int i = 0; i < m_effects.size(); ++i) { - if (m_effects[i] != nullptr) { - if (kEffectDebugOutput) { - qDebug() << "EngineEffectChain::enableForInputChannel" << this - << "loading states for effect" << i; - } - EffectStatesMap* pStatesMap = &(*statesForEffectsInChain)[i]; - VERIFY_OR_DEBUG_ASSERT(pStatesMap) { - return false; - } - m_effects[i]->loadStatesForInputChannel(inputHandle, pStatesMap); - } - } return true; } bool EngineEffectChain::disableForInputChannel(ChannelHandle inputHandle) { auto& outputMap = m_chainStatusForChannelMatrix[inputHandle]; for (auto&& outputChannelStatus : outputMap) { - if (outputChannelStatus.enableState != EffectEnableState::Disabled) { + if (outputChannelStatus.enableState == EffectEnableState::Enabling) { + // Channel has never been processed and can be disabled immediately + outputChannelStatus.enableState = EffectEnableState::Disabled; + } else if (outputChannelStatus.enableState == EffectEnableState::Enabled) { + // Channel was enabled, fade effect out via Disabling state outputChannelStatus.enableState = EffectEnableState::Disabling; } } - // Do not call deleteStatesForInputChannel here because the EngineEffects' - // process() method needs to run one last time before deleting the states. - // deleteStatesForInputChannel needs to be called from the main thread after - // the successful EffectsResponse is returned by the MessagePipe FIFO. return true; } -// Called from the main thread for garbage collection after an input channel is disabled -void EngineEffectChain::deleteStatesForInputChannel(const ChannelHandle inputChannel) { - // If an output channel is not presently being processed, for example when - // PFL is not active, then process() cannot be relied upon to set this - // chain's EffectEnableState from Disabling to Disabled. This must be done - // before the next time process() is called for that output channel, - // otherwise, if any EngineEffects are Enabled, - // EffectProcessorImpl::processChannel will try to run - // with an EffectState that has already been deleted and cause a crash. - // Refer to https://bugs.launchpad.net/mixxx/+bug/1741213 - // NOTE: ChannelHandleMap is like a map in that it associates an object with - // a ChannelHandle key, but it actually backed by a QVarLengthArray, not a - // QMap. So it is okay that m_chainStatusForChannelMatrix may be - // accessed concurrently in the audio engine thread in process(), - // enableForInputChannel(), or disableForInputChannel(). - auto& outputMap = m_chainStatusForChannelMatrix[inputChannel]; - for (auto&& outputChannelStatus : outputMap) { - outputChannelStatus.enableState = EffectEnableState::Disabled; - } - for (EngineEffect* pEffect : qAsConst(m_effects)) { - if (pEffect != nullptr) { - pEffect->deleteStatesForInputChannel(inputChannel); - } - } -} - -EngineEffectChain::ChannelStatus& EngineEffectChain::getChannelStatus( - const ChannelHandle& inputHandle, - const ChannelHandle& outputHandle) { - ChannelStatus& status = m_chainStatusForChannelMatrix[inputHandle][outputHandle]; - return status; -} - bool EngineEffectChain::process(const ChannelHandle& inputHandle, const ChannelHandle& outputHandle, CSAMPLE* pIn, CSAMPLE* pOut, const unsigned int numSamples, const unsigned int sampleRate, - const GroupFeatureState& groupFeatures) { + const GroupFeatureState& groupFeatures, + bool fadeout) { // Compute the effective enable state from the channel input routing switch and // the chain's enable state. When either of these are turned on/off, send the // effects the intermediate enabling/disabling signal. @@ -245,6 +190,13 @@ bool EngineEffectChain::process(const ChannelHandle& inputHandle, ChannelStatus& channelStatus = m_chainStatusForChannelMatrix[inputHandle][outputHandle]; EffectEnableState effectiveChainEnableState = channelStatus.enableState; + if (fadeout && channelStatus.enableState == EffectEnableState::Enabled) { + // This is the last callback before pause + // It can start again without further notice + // make use the effect is paused + effectiveChainEnableState = EffectEnableState::Disabling; + } + // If the channel is fully disabled, do not let intermediate // enabling/disabing signals from the chain's enable switch override // the channel's state. @@ -349,11 +301,15 @@ bool EngineEffectChain::process(const ChannelHandle& inputHandle, // enabling/disabling state, set the channel state or chain state // to the fully enabled/disabled state for the next engine callback. - EffectEnableState& chainOnChannelEnableState = channelStatus.enableState; - if (chainOnChannelEnableState == EffectEnableState::Disabling) { - chainOnChannelEnableState = EffectEnableState::Disabled; - } else if (chainOnChannelEnableState == EffectEnableState::Enabling) { - chainOnChannelEnableState = EffectEnableState::Enabled; + if (channelStatus.enableState == EffectEnableState::Disabling) { + channelStatus.enableState = EffectEnableState::Disabled; + } else if (channelStatus.enableState == EffectEnableState::Enabling) { + channelStatus.enableState = EffectEnableState::Enabled; + } + + if (fadeout && channelStatus.enableState == EffectEnableState::Enabled) { + // Effect is paused now, ramp up next callback which may happen later + channelStatus.enableState = EffectEnableState::Enabling; } if (m_enableState == EffectEnableState::Disabling) { diff --git a/src/engine/effects/engineeffectchain.h b/src/engine/effects/engineeffectchain.h index 9bb5aa6ac355..36007a0918bb 100644 --- a/src/engine/effects/engineeffectchain.h +++ b/src/engine/effects/engineeffectchain.h @@ -42,10 +42,8 @@ class EngineEffectChain final : public EffectsRequestHandler { CSAMPLE* pOut, const unsigned int numSamples, const unsigned int sampleRate, - const GroupFeatureState& groupFeatures); - - /// called from main thread - void deleteStatesForInputChannel(const ChannelHandle channel); + const GroupFeatureState& groupFeatures, + bool fadeout); private: struct ChannelStatus { @@ -64,15 +62,9 @@ class EngineEffectChain final : public EffectsRequestHandler { bool updateParameters(const EffectsRequest& message); bool addEffect(EngineEffect* pEffect, int iIndex); bool removeEffect(EngineEffect* pEffect, int iIndex); - bool enableForInputChannel(ChannelHandle inputHandle, - EffectStatesMapArray* statesForEffectsInChain); + bool enableForInputChannel(ChannelHandle inputHandle); bool disableForInputChannel(ChannelHandle inputHandle); - // Gets or creates a ChannelStatus entry in m_channelStatus for the provided - // handle. - ChannelStatus& getChannelStatus(const ChannelHandle& inputHandle, - const ChannelHandle& outputHandle); - QString m_group; EffectEnableState m_enableState; EffectChainMixMode::Type m_mixMode; diff --git a/src/engine/effects/engineeffectsmanager.cpp b/src/engine/effects/engineeffectsmanager.cpp index 5dae585c537e..8f65bc228f3a 100644 --- a/src/engine/effects/engineeffectsmanager.cpp +++ b/src/engine/effects/engineeffectsmanager.cpp @@ -45,7 +45,6 @@ void EngineEffectsManager::onCallbackStart() { response.status = EffectsResponse::NO_SUCH_CHAIN; break; } - } processed = request->pTargetChain->processEffectsRequest( *request, m_pResponsePipe.data()); if (processed) { @@ -64,6 +63,7 @@ void EngineEffectsManager::onCallbackStart() { response.status = EffectsResponse::INVALID_REQUEST; } break; + } case EffectsRequest::SET_EFFECT_PARAMETERS: case EffectsRequest::SET_PARAMETER_PARAMETERS: VERIFY_OR_DEBUG_ASSERT(m_effects.contains(request->pTargetEffect)) { @@ -97,8 +97,8 @@ void EngineEffectsManager::onCallbackStart() { void EngineEffectsManager::processPreFaderInPlace(const ChannelHandle& inputHandle, const ChannelHandle& outputHandle, CSAMPLE* pInOut, - const unsigned int numSamples, - const unsigned int sampleRate) { + unsigned int numSamples, + unsigned int sampleRate) { // Feature state is gathered after prefader effects processing. // This is okay because the equalizer effects do not make use of it. GroupFeatureState featureState; @@ -116,11 +116,12 @@ void EngineEffectsManager::processPostFaderInPlace( const ChannelHandle& inputHandle, const ChannelHandle& outputHandle, CSAMPLE* pInOut, - const unsigned int numSamples, - const unsigned int sampleRate, + unsigned int numSamples, + unsigned int sampleRate, const GroupFeatureState& groupFeatures, - const CSAMPLE_GAIN oldGain, - const CSAMPLE_GAIN newGain) { + CSAMPLE_GAIN oldGain, + CSAMPLE_GAIN newGain, + bool fadeout) { processInner(SignalProcessingStage::Postfader, inputHandle, outputHandle, @@ -130,7 +131,8 @@ void EngineEffectsManager::processPostFaderInPlace( sampleRate, groupFeatures, oldGain, - newGain); + newGain, + fadeout); } void EngineEffectsManager::processPostFaderAndMix( @@ -138,11 +140,12 @@ void EngineEffectsManager::processPostFaderAndMix( const ChannelHandle& outputHandle, CSAMPLE* pIn, CSAMPLE* pOut, - const unsigned int numSamples, - const unsigned int sampleRate, + unsigned int numSamples, + unsigned int sampleRate, const GroupFeatureState& groupFeatures, - const CSAMPLE_GAIN oldGain, - const CSAMPLE_GAIN newGain) { + CSAMPLE_GAIN oldGain, + CSAMPLE_GAIN newGain, + bool fadeout) { processInner(SignalProcessingStage::Postfader, inputHandle, outputHandle, @@ -152,7 +155,8 @@ void EngineEffectsManager::processPostFaderAndMix( sampleRate, groupFeatures, oldGain, - newGain); + newGain, + fadeout); } void EngineEffectsManager::processInner( @@ -161,11 +165,12 @@ void EngineEffectsManager::processInner( const ChannelHandle& outputHandle, CSAMPLE* pIn, CSAMPLE* pOut, - const unsigned int numSamples, - const unsigned int sampleRate, + unsigned int numSamples, + unsigned int sampleRate, const GroupFeatureState& groupFeatures, - const CSAMPLE_GAIN oldGain, - const CSAMPLE_GAIN newGain) { + CSAMPLE_GAIN oldGain, + CSAMPLE_GAIN newGain, + bool fadeout) { const QList& chains = m_chainsByStage.value(stage); if (pIn == pOut) { @@ -180,7 +185,8 @@ void EngineEffectsManager::processInner( pOut, numSamples, sampleRate, - groupFeatures)) { + groupFeatures, + fadeout)) { } } } @@ -217,7 +223,8 @@ void EngineEffectsManager::processInner( pIntermediateOutput, numSamples, sampleRate, - groupFeatures)) { + groupFeatures, + fadeout)) { // Output of this chain becomes the input of the next chain. pIntermediateInput = pIntermediateOutput; } diff --git a/src/engine/effects/engineeffectsmanager.h b/src/engine/effects/engineeffectsmanager.h index 9eacda043c6b..287c691b7175 100644 --- a/src/engine/effects/engineeffectsmanager.h +++ b/src/engine/effects/engineeffectsmanager.h @@ -33,8 +33,8 @@ class EngineEffectsManager final : public EffectsRequestHandler { const ChannelHandle& inputHandle, const ChannelHandle& outputHandle, CSAMPLE* pInOut, - const unsigned int numSamples, - const unsigned int sampleRate); + unsigned int numSamples, + unsigned int sampleRate); /// Process the postfader EngineEffectChains on the pInOut buffer, modifying /// the contents of the input buffer. @@ -42,11 +42,12 @@ class EngineEffectsManager final : public EffectsRequestHandler { const ChannelHandle& inputHandle, const ChannelHandle& outputHandle, CSAMPLE* pInOut, - const unsigned int numSamples, - const unsigned int sampleRate, + unsigned int numSamples, + unsigned int sampleRate, const GroupFeatureState& groupFeatures, - const CSAMPLE_GAIN oldGain = CSAMPLE_GAIN_ONE, - const CSAMPLE_GAIN newGain = CSAMPLE_GAIN_ONE); + CSAMPLE_GAIN oldGain = CSAMPLE_GAIN_ONE, + CSAMPLE_GAIN newGain = CSAMPLE_GAIN_ONE, + bool fadeout = false); /// Process the postfader EngineEffectChains, leaving the pIn buffer unmodified /// and mixing the output into the pOut buffer. Using EngineEffectsManager's @@ -58,11 +59,12 @@ class EngineEffectsManager final : public EffectsRequestHandler { const ChannelHandle& outputHandle, CSAMPLE* pIn, CSAMPLE* pOut, - const unsigned int numSamples, - const unsigned int sampleRate, + unsigned int numSamples, + unsigned int sampleRate, const GroupFeatureState& groupFeatures, - const CSAMPLE_GAIN oldGain = CSAMPLE_GAIN_ONE, - const CSAMPLE_GAIN newGain = CSAMPLE_GAIN_ONE); + CSAMPLE_GAIN oldGain = CSAMPLE_GAIN_ONE, + CSAMPLE_GAIN newGain = CSAMPLE_GAIN_ONE, + bool fadeout = false); bool processEffectsRequest( EffectsRequest& message, @@ -88,11 +90,12 @@ class EngineEffectsManager final : public EffectsRequestHandler { const ChannelHandle& outputHandle, CSAMPLE* pIn, CSAMPLE* pOut, - const unsigned int numSamples, - const unsigned int sampleRate, + unsigned int numSamples, + unsigned int sampleRate, const GroupFeatureState& groupFeatures, - const CSAMPLE_GAIN oldGain = CSAMPLE_GAIN_ONE, - const CSAMPLE_GAIN newGain = CSAMPLE_GAIN_ONE); + CSAMPLE_GAIN oldGain = CSAMPLE_GAIN_ONE, + CSAMPLE_GAIN newGain = CSAMPLE_GAIN_ONE, + bool fadeout = false); QScopedPointer m_pResponsePipe; QHash> m_chainsByStage; diff --git a/src/engine/effects/message.h b/src/engine/effects/message.h index 880ea48f623b..b78549567aa8 100644 --- a/src/engine/effects/message.h +++ b/src/engine/effects/message.h @@ -44,20 +44,6 @@ struct EffectsRequest { pTargetEffect = nullptr; } - // This is called from the main thread by EffectsManager after receiving a - // response from EngineEffectsManager in the audio engine thread. - ~EffectsRequest() { - if (type == ENABLE_EFFECT_CHAIN_FOR_INPUT_CHANNEL) { - VERIFY_OR_DEBUG_ASSERT(EnableInputChannelForChain.pEffectStatesMapArray != nullptr) { - return; - } - // This only deletes the container used to passed the EffectStates - // to EffectProcessorImpl. The EffectStates are managed by - // EffectProcessorImpl. - delete EnableInputChannelForChain.pEffectStatesMapArray; - } - } - MessageType type; qint64 request_id; @@ -86,7 +72,6 @@ struct EffectsRequest { SignalProcessingStage signalProcessingStage; } RemoveEffectChain; struct { - EffectStatesMapArray* pEffectStatesMapArray; ChannelHandle channelHandle; } EnableInputChannelForChain; struct { diff --git a/src/engine/enginebuffer.cpp b/src/engine/enginebuffer.cpp index 34bbf41ee61e..0b2255f7597d 100644 --- a/src/engine/enginebuffer.cpp +++ b/src/engine/enginebuffer.cpp @@ -1000,18 +1000,23 @@ void EngineBuffer::processTrackLocked( rate = m_rate_old; } - const mixxx::audio::FramePos trackEndPosition = getTrackEndPosition(); - bool atEnd = m_playPosition >= trackEndPosition; - bool backwards = rate < 0; - bool bCurBufferPaused = false; - if (atEnd && !backwards) { - // do not play past end - bCurBufferPaused = true; - } else if (rate == 0 && !is_scratching) { - // do not process samples if have no transport - // the linear scaler supports ramping down to 0 - // this is used for pause by scratching only + bool atEnd = false; + bool backwards = rate < 0; + const mixxx::audio::FramePos trackEndPosition = getTrackEndPosition(); + if (trackEndPosition.isValid()) { + atEnd = m_playPosition >= trackEndPosition; + if (atEnd && !backwards) { + // do not play past end + bCurBufferPaused = true; + } else if (rate == 0 && !is_scratching) { + // do not process samples if have no transport + // the linear scaler supports ramping down to 0 + // this is used for pause by scratching only + bCurBufferPaused = true; + } + } else { + // Track has already been ejected. bCurBufferPaused = true; } @@ -1072,12 +1077,10 @@ void EngineBuffer::processTrackLocked( // Handle repeat mode const bool atStart = m_playPosition <= mixxx::audio::kStartFramePos; - atEnd = m_playPosition >= trackEndPosition; bool repeat_enabled = m_pRepeat->toBool(); - bool end_of_track = //(at_start && backwards) || - (atEnd && !backwards); + bool end_of_track = atEnd && !backwards; // If playbutton is pressed, check if we are at start or end of track if ((m_playButton->toBool() || (m_fwdButton->toBool() || m_backButton->toBool())) diff --git a/src/engine/enginemaster.cpp b/src/engine/enginemaster.cpp index 31a9dc4c054d..acfdce15f726 100644 --- a/src/engine/enginemaster.cpp +++ b/src/engine/enginemaster.cpp @@ -291,7 +291,12 @@ void EngineMaster::processChannels(int iBufferSize) { EngineChannel* pChannel = pChannelInfo->m_pChannel; // Skip inactive channels. - if (!pChannel || !pChannel->isActive()) { + VERIFY_OR_DEBUG_ASSERT(pChannel) { + continue; + } + + EngineChannel::ActiveState activeState = pChannel->updateActiveState(); + if (activeState == EngineChannel::ActiveState::Inactive) { continue; } @@ -482,7 +487,10 @@ void EngineMaster::process(const int iBufferSize) { m_pTalkover, m_iBufferSize, static_cast(m_sampleRate.value()), - busFeatures); + busFeatures, + CSAMPLE_GAIN_ONE, + CSAMPLE_GAIN_ONE, + false); } switch (m_pTalkoverDucking->getMode()) { @@ -537,21 +545,30 @@ void EngineMaster::process(const int iBufferSize) { m_pOutputBusBuffers[EngineChannel::LEFT], m_iBufferSize, static_cast(m_sampleRate.value()), - busFeatures); + busFeatures, + CSAMPLE_GAIN_ONE, + CSAMPLE_GAIN_ONE, + false); m_pEngineEffectsManager->processPostFaderInPlace( m_busCrossfaderCenterHandle.handle(), m_masterHandle.handle(), m_pOutputBusBuffers[EngineChannel::CENTER], m_iBufferSize, static_cast(m_sampleRate.value()), - busFeatures); + busFeatures, + CSAMPLE_GAIN_ONE, + CSAMPLE_GAIN_ONE, + false); m_pEngineEffectsManager->processPostFaderInPlace( m_busCrossfaderRightHandle.handle(), m_masterHandle.handle(), m_pOutputBusBuffers[EngineChannel::RIGHT], m_iBufferSize, static_cast(m_sampleRate.value()), - busFeatures); + busFeatures, + CSAMPLE_GAIN_ONE, + CSAMPLE_GAIN_ONE, + false); } if (masterEnabled) { @@ -782,7 +799,10 @@ void EngineMaster::applyMasterEffects() { m_pMaster, m_iBufferSize, static_cast(m_sampleRate.value()), - masterFeatures); + masterFeatures, + CSAMPLE_GAIN_ONE, + CSAMPLE_GAIN_ONE, + false); } } diff --git a/src/mixer/playermanager.cpp b/src/mixer/playermanager.cpp index 0f018bd0e4e0..725e7500a5c4 100644 --- a/src/mixer/playermanager.cpp +++ b/src/mixer/playermanager.cpp @@ -436,7 +436,7 @@ void PlayerManager::addDeckInner() { AudioInput(AudioInput::VINYLCONTROL, 0, 2, deckIndex), pEngineDeck); // Setup equalizer and QuickEffect chain for this deck. - m_pEffectsManager->addDeck(handleGroup.m_name); + m_pEffectsManager->addDeck(handleGroup); } void PlayerManager::loadSamplers() { diff --git a/src/test/enginemastertest.cpp b/src/test/enginemastertest.cpp index d43f6f0ebcf3..740ce484e15e 100644 --- a/src/test/enginemastertest.cpp +++ b/src/test/enginemastertest.cpp @@ -30,6 +30,7 @@ class EngineChannelMock : public EngineChannel { Q_UNUSED(iBufferSize); } + MOCK_METHOD0(updateActiveState, ActiveState()); MOCK_METHOD0(isActive, bool()); MOCK_CONST_METHOD0(isMasterEnabled, bool()); MOCK_CONST_METHOD0(isPflEnabled, bool()); @@ -64,9 +65,9 @@ TEST_F(EngineMasterTest, SingleChannelOutputWorks) { SampleUtil::fill(pChannelBuffer, 0.1f, MAX_BUFFER_LEN); // Instruct the mock to claim it is active, master and not PFL. - EXPECT_CALL(*pChannel, isActive()) + EXPECT_CALL(*pChannel, updateActiveState()) .Times(1) - .WillOnce(Return(true)); + .WillOnce(Return(EngineChannel::ActiveState::Active)); EXPECT_CALL(*pChannel, isMasterEnabled()) .Times(1) .WillOnce(Return(true)); @@ -101,9 +102,9 @@ TEST_F(EngineMasterTest, SingleChannelPFLOutputWorks) { SampleUtil::fill(pChannelBuffer, 0.1f, MAX_BUFFER_LEN); // Instruct the mock to claim it is active, not master and PFL - EXPECT_CALL(*pChannel, isActive()) + EXPECT_CALL(*pChannel, updateActiveState()) .Times(1) - .WillOnce(Return(true)); + .WillOnce(Return(EngineChannel::ActiveState::Active)); EXPECT_CALL(*pChannel, isMasterEnabled()) .Times(1) .WillOnce(Return(false)); @@ -144,9 +145,9 @@ TEST_F(EngineMasterTest, TwoChannelOutputWorks) { SampleUtil::fill(pChannel2Buffer, 0.2f, MAX_BUFFER_LEN); // Instruct channel 1 to claim it is active, master and not PFL. - EXPECT_CALL(*pChannel1, isActive()) + EXPECT_CALL(*pChannel1, updateActiveState()) .Times(1) - .WillOnce(Return(true)); + .WillOnce(Return(EngineChannel::ActiveState::Active)); EXPECT_CALL(*pChannel1, isMasterEnabled()) .Times(1) .WillOnce(Return(true)); @@ -155,9 +156,9 @@ TEST_F(EngineMasterTest, TwoChannelOutputWorks) { .WillOnce(Return(false)); // Instruct channel 2 to claim it is active, master and not PFL. - EXPECT_CALL(*pChannel2, isActive()) + EXPECT_CALL(*pChannel2, updateActiveState()) .Times(1) - .WillOnce(Return(true)); + .WillOnce(Return(EngineChannel::ActiveState::Active)); EXPECT_CALL(*pChannel2, isMasterEnabled()) .Times(1) .WillOnce(Return(true)); @@ -201,9 +202,9 @@ TEST_F(EngineMasterTest, TwoChannelPFLOutputWorks) { SampleUtil::fill(pChannel2Buffer, 0.2f, MAX_BUFFER_LEN); // Instruct channel 1 to claim it is active, master and PFL. - EXPECT_CALL(*pChannel1, isActive()) + EXPECT_CALL(*pChannel1, updateActiveState()) .Times(1) - .WillOnce(Return(true)); + .WillOnce(Return(EngineChannel::ActiveState::Active)); EXPECT_CALL(*pChannel1, isMasterEnabled()) .Times(1) .WillOnce(Return(true)); @@ -212,9 +213,9 @@ TEST_F(EngineMasterTest, TwoChannelPFLOutputWorks) { .WillOnce(Return(true)); // Instruct channel 2 to claim it is active, master and PFL. - EXPECT_CALL(*pChannel2, isActive()) + EXPECT_CALL(*pChannel2, updateActiveState()) .Times(1) - .WillOnce(Return(true)); + .WillOnce(Return(EngineChannel::ActiveState::Active)); EXPECT_CALL(*pChannel2, isMasterEnabled()) .Times(1) .WillOnce(Return(true)); @@ -263,9 +264,9 @@ TEST_F(EngineMasterTest, ThreeChannelOutputWorks) { SampleUtil::fill(pChannel3Buffer, 0.3f, MAX_BUFFER_LEN); // Instruct channel 1 to claim it is active, master and not PFL. - EXPECT_CALL(*pChannel1, isActive()) + EXPECT_CALL(*pChannel1, updateActiveState()) .Times(1) - .WillOnce(Return(true)); + .WillOnce(Return(EngineChannel::ActiveState::Active)); EXPECT_CALL(*pChannel1, isMasterEnabled()) .Times(1) .WillOnce(Return(true)); @@ -274,9 +275,9 @@ TEST_F(EngineMasterTest, ThreeChannelOutputWorks) { .WillOnce(Return(false)); // Instruct channel 2 to claim it is active, master and not PFL. - EXPECT_CALL(*pChannel2, isActive()) + EXPECT_CALL(*pChannel2, updateActiveState()) .Times(1) - .WillOnce(Return(true)); + .WillOnce(Return(EngineChannel::ActiveState::Active)); EXPECT_CALL(*pChannel2, isMasterEnabled()) .Times(1) .WillOnce(Return(true)); @@ -285,9 +286,9 @@ TEST_F(EngineMasterTest, ThreeChannelOutputWorks) { .WillOnce(Return(false)); // Instruct channel 3 to claim it is active, master and not PFL. - EXPECT_CALL(*pChannel3, isActive()) + EXPECT_CALL(*pChannel3, updateActiveState()) .Times(1) - .WillOnce(Return(true)); + .WillOnce(Return(EngineChannel::ActiveState::Active)); EXPECT_CALL(*pChannel3, isMasterEnabled()) .Times(1) .WillOnce(Return(true)); @@ -339,9 +340,9 @@ TEST_F(EngineMasterTest, ThreeChannelPFLOutputWorks) { SampleUtil::fill(pChannel3Buffer, 0.3f, MAX_BUFFER_LEN); // Instruct channel 1 to claim it is active, master and not PFL. - EXPECT_CALL(*pChannel1, isActive()) + EXPECT_CALL(*pChannel1, updateActiveState()) .Times(1) - .WillOnce(Return(true)); + .WillOnce(Return(EngineChannel::ActiveState::Active)); EXPECT_CALL(*pChannel1, isMasterEnabled()) .Times(1) .WillOnce(Return(true)); @@ -350,9 +351,9 @@ TEST_F(EngineMasterTest, ThreeChannelPFLOutputWorks) { .WillOnce(Return(true)); // Instruct channel 2 to claim it is active, master and not PFL. - EXPECT_CALL(*pChannel2, isActive()) + EXPECT_CALL(*pChannel2, updateActiveState()) .Times(1) - .WillOnce(Return(true)); + .WillOnce(Return(EngineChannel::ActiveState::Active)); EXPECT_CALL(*pChannel2, isMasterEnabled()) .Times(1) .WillOnce(Return(true)); @@ -361,9 +362,9 @@ TEST_F(EngineMasterTest, ThreeChannelPFLOutputWorks) { .WillOnce(Return(true)); // Instruct channel 3 to claim it is active, master and not PFL. - EXPECT_CALL(*pChannel3, isActive()) + EXPECT_CALL(*pChannel3, updateActiveState()) .Times(1) - .WillOnce(Return(true)); + .WillOnce(Return(EngineChannel::ActiveState::Active)); EXPECT_CALL(*pChannel3, isMasterEnabled()) .Times(1) .WillOnce(Return(true)); diff --git a/src/widget/weffectpushbutton.cpp b/src/widget/weffectpushbutton.cpp index a733e51dabeb..f979962502c6 100644 --- a/src/widget/weffectpushbutton.cpp +++ b/src/widget/weffectpushbutton.cpp @@ -22,6 +22,10 @@ void WEffectPushButton::setup(const QDomNode& node, const SkinContext& context) auto pEffectSlot = EffectWidgetUtils::getEffectSlotFromNode(node, context, pChainSlot); m_pEffectParameterSlot = EffectWidgetUtils::getButtonParameterSlotFromNode( node, context, pEffectSlot); + if (!m_pEffectParameterSlot) { + SKIN_WARNING(node, context) << "Could not find effect parameter slot"; + DEBUG_ASSERT(false); + } connect(m_pEffectParameterSlot.data(), &EffectParameterSlotBase::updated, this, @@ -33,9 +37,6 @@ void WEffectPushButton::setup(const QDomNode& node, const SkinContext& context) this, &WEffectPushButton::slotActionChosen); parameterUpdated(); - VERIFY_OR_DEBUG_ASSERT(m_pEffectParameterSlot) { - SKIN_WARNING(node, context) << "Could not find effect parameter slot"; - } } void WEffectPushButton::onConnectedControlChanged(double dParameter, double dValue) {