Something went wrong. Try again.
Mixxx DJ software with MCP, Subsonic/Navidrome streaming library support — browse and load tracks from your self-hosted music server mixxx.org
subsonic navidrome streaming audio dj mac linux midi
Something went wrong. Try again.
C++
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228#include "engine/effects/engineeffect.h"
#include "effects/backends/effectsbackendmanager.h"#include "engine/effects/engineeffectparameter.h"#include "engine/engine.h"#include "util/defs.h"#include "util/sample.h"
namespace {
// Used during initialization where the SoundSevice is not set upconstexpr auto kInitalSampleRate = mixxx::audio::SampleRate(96000);
} // namespace
EngineEffect::EngineEffect(EffectManifestPointer pManifest, EffectsBackendManagerPointer pBackendManager, const QSet<ChannelHandleAndGroup>& activeInputChannels, const QSet<ChannelHandleAndGroup>& registeredInputChannels, const QSet<ChannelHandleAndGroup>& registeredOutputChannels) : m_pManifest(pManifest), m_pProcessor(pBackendManager->createProcessor(pManifest)), m_parameters(pManifest->parameters().size()) { const QList<EffectManifestParameterPointer>& parameters = m_pManifest->parameters(); for (int i = 0; i < parameters.size(); ++i) { EffectManifestParameterPointer param = parameters.at(i); EngineEffectParameterPointer pParameter(new EngineEffectParameter(param)); m_parameters[i] = pParameter; m_parametersById[param->id()] = pParameter; }
for (const ChannelHandleAndGroup& inputChannel : registeredInputChannels) { ChannelHandleMap<EffectEnableState> outputChannelMap; for (const ChannelHandleAndGroup& outputChannel : registeredOutputChannels) { outputChannelMap.insert(outputChannel.handle(), EffectEnableState::Disabled); } m_effectEnableStateForChannelMatrix.insert(inputChannel.handle(), outputChannelMap); }
m_pProcessor->loadEngineEffectParameters(m_parametersById);
// At this point the SoundDevice is not set up so we use the kInitalSampleRate. const mixxx::EngineParameters engineParameters( kInitalSampleRate, kMaxEngineFrames); m_pProcessor->initialize(activeInputChannels, registeredOutputChannels, engineParameters); m_effectRampsFromDry = pManifest->effectRampsFromDry();}
EngineEffect::~EngineEffect() { if constexpr (kEffectDebugOutput) { qDebug() << debugString() << "destroyed"; }}
void EngineEffect::initializeInputChannel(ChannelHandle inputChannel) { if (m_pProcessor->hasStatesForInputChannel(inputChannel)) { // already initialized for this input channel return; }
// At this point the SoundDevice is not set up so we use the kInitalSampleRate. const mixxx::EngineParameters engineParameters( kInitalSampleRate, kMaxEngineFrames); m_pProcessor->initializeInputChannel(inputChannel, engineParameters);}
bool EngineEffect::processEffectsRequest(const EffectsRequest& message, EffectsResponsePipe* pResponsePipe) { EngineEffectParameterPointer pParameter; EffectsResponse response(message);
switch (message.type) { case EffectsRequest::SET_EFFECT_PARAMETERS: if (kEffectDebugOutput) { qDebug() << debugString() << "SET_EFFECT_PARAMETERS" << "enabled" << message.SetEffectParameters.enabled; }
for (auto& outputMap : m_effectEnableStateForChannelMatrix) { for (auto& enableState : outputMap) { if (enableState != EffectEnableState::Disabled && !message.SetEffectParameters.enabled) { enableState = EffectEnableState::Disabling; // If an input is not routed to the chain, and the effect gets // a message to disable, then the effect gets the message to enable, // process() will not have executed, so the enableState will still be // DISABLING instead of DISABLED. } else if ((enableState == EffectEnableState::Disabled || enableState == EffectEnableState::Disabling) && message.SetEffectParameters.enabled) { enableState = EffectEnableState::Enabling; } } }
response.success = true; pResponsePipe->writeMessage(response); return true; break; case EffectsRequest::SET_PARAMETER_PARAMETERS: if (kEffectDebugOutput) { qDebug() << debugString() << "SET_PARAMETER_PARAMETERS" << "parameter" << message.SetParameterParameters.iParameter << "value" << message.value; } pParameter = m_parameters.value( message.SetParameterParameters.iParameter, EngineEffectParameterPointer()); if (pParameter) { pParameter->setValue(message.value); response.success = true; } else { response.success = false; response.status = EffectsResponse::NO_SUCH_PARAMETER; } pResponsePipe->writeMessage(response); return true; default: break; } return false;}
bool EngineEffect::process(const ChannelHandle& inputHandle, const ChannelHandle& outputHandle, const CSAMPLE* pInput, CSAMPLE* pOutput, const std::size_t numSamples, const mixxx::audio::SampleRate sampleRate, const EffectEnableState chainEnableState, const GroupFeatureState& groupFeatures) { // Compute the effective enable state from the combination of the effect's state // for the channel and the state passed from the EngineEffectChain.
// When the chain's input routing switch or chain enable switches are changed, // the chain sends an intermediate enabling/disabling signal. The chain also sends // intermediate enabling/disabling signals when its dry/wet knob is turned down to // fully dry then turned back up to let some wet signal through.
// Analagously, when the Effect is switched on/off, it sends this EngineEffect an // intermediate enabling/disabling signal.
// The effective enable state is then passed down to the EffectProcessor, which is // responsible for taking appropriate action when it gets an intermediate // enabling/disabling signal. For example, the Echo effect clears its // internal buffer for the channel when it gets the intermediate disabling signal.
EffectEnableState effectiveEffectEnableState = m_effectEnableStateForChannelMatrix[inputHandle][outputHandle];
// If the EngineEffect is fully disabled, do not let // intermediate enabling/disabling signals from the chain override // the EngineEffect's state. if (effectiveEffectEnableState != EffectEnableState::Disabled) { if (chainEnableState == EffectEnableState::Disabled) { // If the chain is fully disabled, skip calling the EffectProcessor. effectiveEffectEnableState = EffectEnableState::Disabled; } else if (chainEnableState == EffectEnableState::Disabling) { // If the chain happens to be in the intermediate disabling state // in the same callback as the effect is in the intermediate enabling // state, the EffectProcessor should get the disabling signal, not the // enabling signal. effectiveEffectEnableState = EffectEnableState::Disabling; } else if (chainEnableState == EffectEnableState::Enabling) { // If the chain happens to be in the intermediate enabling state // in the same callback as the effect is in the intermediate disabling // state, the EffectProcessor should get the disabling signal, not the // enabling signal. if (effectiveEffectEnableState != EffectEnableState::Disabling) { effectiveEffectEnableState = EffectEnableState::Enabling; } } }
bool processingOccured = false;
if (effectiveEffectEnableState != EffectEnableState::Disabled) { //TODO: refactor rest of audio engine to use mixxx::AudioParameters const mixxx::EngineParameters engineParameters( sampleRate, numSamples / mixxx::kEngineChannelOutputCount);
m_pProcessor->process(inputHandle, outputHandle, pInput, pOutput, engineParameters, effectiveEffectEnableState, groupFeatures);
processingOccured = true;
if (!m_effectRampsFromDry) { // the effect does not fade, so we care for it if (effectiveEffectEnableState == EffectEnableState::Disabling) { DEBUG_ASSERT(pInput != pOutput); // Fade to dry only works if pInput is not touched by pOutput // Fade out (fade to dry signal) SampleUtil::linearCrossfadeBuffersOut( pOutput, pInput, numSamples, mixxx::kEngineChannelOutputCount); } else if (effectiveEffectEnableState == EffectEnableState::Enabling) { DEBUG_ASSERT(pInput != pOutput); // Fade to dry only works if pInput is not touched by pOutput // Fade in (fade to wet signal) SampleUtil::linearCrossfadeBuffersOut( pOutput, pInput, numSamples, mixxx::kEngineChannelOutputCount); } } }
// Now that the EffectProcessor has been sent the intermediate enabling/disabling // signal, set the channel state to fully enabled/disabled for the next engine callback. EffectEnableState& effectOnChannelState = m_effectEnableStateForChannelMatrix[inputHandle][outputHandle]; if (effectOnChannelState == EffectEnableState::Disabling) { effectOnChannelState = EffectEnableState::Disabled; } else if (effectOnChannelState == EffectEnableState::Enabling) { effectOnChannelState = EffectEnableState::Enabled; }
return processingOccured;}