diff --git a/CHANGELOG b/CHANGELOG index 7ba7e546e7a..6233568bf63 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,7 @@ * Add controller mapping for Hercules DJControl Inpulse 300 #2465 * Add controller mapping for Denon MC7000 #2546 * Add controller mapping for Stanton DJC.4 #2607 +* Fix broadcasting via broadcast/recording input lp:1876222 #2743 ==== 2.2.3 2019-11-24 ==== * Don't make users reconfigure sound hardware when it has not changed #2253 diff --git a/src/engine/enginemaster.cpp b/src/engine/enginemaster.cpp index 5454723c90c..8312f849793 100644 --- a/src/engine/enginemaster.cpp +++ b/src/engine/enginemaster.cpp @@ -153,7 +153,9 @@ EngineMaster::EngineMaster(UserSettingsPointer pConfig, } // Starts a thread for recording and broadcast - m_pEngineSideChain = bEnableSidechain ? new EngineSideChain(pConfig) : NULL; + m_pEngineSideChain = + bEnableSidechain ? + new EngineSideChain(pConfig, m_pSidechainMix) : nullptr; // X-Fader Setup m_pXFaderMode = new ControlPushButton( @@ -562,7 +564,7 @@ void EngineMaster::process(const int iBufferSize) { m_masterGainOld = master_gain; // Record/broadcast signal is the same as the master output - if (!m_bExternalRecordBroadcastInputConnected) { + if (sidechainMixRequired()) { SampleUtil::copy(m_pSidechainMix, m_pMaster, m_iBufferSize); } } else if (configuredMicMonitorMode == MicMonitorMode::MASTER_AND_BOOTH) { @@ -599,7 +601,7 @@ void EngineMaster::process(const int iBufferSize) { m_masterGainOld = master_gain; // Record/broadcast signal is the same as the master output - if (!m_bExternalRecordBroadcastInputConnected) { + if (sidechainMixRequired()) { SampleUtil::copy(m_pSidechainMix, m_pMaster, m_iBufferSize); } } else if (configuredMicMonitorMode == MicMonitorMode::DIRECT_MONITOR) { @@ -632,41 +634,42 @@ void EngineMaster::process(const int iBufferSize) { SampleUtil::applyRampingGain(m_pMaster, m_masterGainOld, master_gain, m_iBufferSize); m_masterGainOld = master_gain; - if (!m_bExternalRecordBroadcastInputConnected) { + if (sidechainMixRequired()) { SampleUtil::copy(m_pSidechainMix, m_pMaster, m_iBufferSize); - } - // The talkover signal Mixxx receives is delayed by the round trip latency. - // There is an output latency between the time Mixxx processes the audio - // and the user hears it. So if the microphone user plays on beat with - // what they hear, they will be playing out of sync with the engine's - // processing by the output latency. Additionally, Mixxx gets input signals - // delayed by the input latency. By the time Mixxx receives the input signal, - // a full round trip through the signal chain has elapsed since Mixxx - // processed the output signal. - // Although Mixxx receives the input signal delayed, the user hears it mixed - // in hardware with the master & booth outputs without that - // latency, so to record/broadcast the same signal that is heard - // on the master & booth outputs, the master mix must be delayed before - // mixing the talkover signal for the record/broadcast mix. - // If not using microphone inputs or recording/broadcasting from - // a sound card input, skip unnecessary processing here. - if (m_pNumMicsConfigured->get() > 0 - && !m_bExternalRecordBroadcastInputConnected) { - // Copy the master mix to a separate buffer before delaying it - // to avoid delaying the master output. - m_pLatencyCompensationDelay->process(m_pSidechainMix, m_iBufferSize); - SampleUtil::add(m_pSidechainMix, m_pTalkover, m_iBufferSize); + if (m_pNumMicsConfigured->get() > 0) { + // The talkover signal Mixxx receives is delayed by the round trip latency. + // There is an output latency between the time Mixxx processes the audio + // and the user hears it. So if the microphone user plays on beat with + // what they hear, they will be playing out of sync with the engine's + // processing by the output latency. Additionally, Mixxx gets input signals + // delayed by the input latency. By the time Mixxx receives the input signal, + // a full round trip through the signal chain has elapsed since Mixxx + // processed the output signal. + // Although Mixxx receives the input signal delayed, the user hears it mixed + // in hardware with the master & booth outputs without that + // latency, so to record/broadcast the same signal that is heard + // on the master & booth outputs, the master mix must be delayed before + // mixing the talkover signal for the record/broadcast mix. + // If not using microphone inputs or recording/broadcasting from + // a sound card input, skip unnecessary processing here. + + // Copy the master mix to a separate buffer before delaying it + // to avoid delaying the master output. + m_pLatencyCompensationDelay->process(m_pSidechainMix, m_iBufferSize); + SampleUtil::add(m_pSidechainMix, m_pTalkover, m_iBufferSize); + } } } - // Submit buffer to the side chain to do broadcasting, recording, - // etc. (CPU intensive non-realtime tasks) - // If recording/broadcasting from a sound card input, - // SoundManager will send the input buffer from the sound card to m_pSidechain - // so skip sending a buffer to m_pSidechain here. - if (!m_bExternalRecordBroadcastInputConnected - && m_pEngineSideChain != nullptr) { + // Submit buffer to the side chain to do CPU intensive non-realtime + // tasks like recording. The SoundDeviceNetwork, responsible for + // passing samples to the network reads directly from m_pSidechainMix, + // registering it with SoundDevice::addOutput(). + // Note: In case the broadcast/recording input is configured, + // EngineSideChain::receiveBuffer has copied the input buffer to m_pSidechainMix + // via before (called by SoundManager::pushInputBuffers()) + if (m_pEngineSideChain) { m_pEngineSideChain->writeSamples(m_pSidechainMix, iFrames); } @@ -963,3 +966,7 @@ void EngineMaster::registerNonEngineChannelSoundIO(SoundManager* pSoundManager) } pSoundManager->registerOutput(AudioOutput(AudioOutput::RECORD_BROADCAST, 0, 2), this); } + +bool EngineMaster::sidechainMixRequired() const { + return m_pEngineSideChain && !m_bExternalRecordBroadcastInputConnected; +} diff --git a/src/engine/enginemaster.h b/src/engine/enginemaster.h index ea891b5f7f9..3dc2e21518a 100644 --- a/src/engine/enginemaster.h +++ b/src/engine/enginemaster.h @@ -273,6 +273,7 @@ class EngineMaster : public QObject, public AudioSource { ChannelHandleFactory* m_pChannelHandleFactory; void applyMasterEffects(); void processHeadphones(const double masterMixGainInHeadphones); + bool sidechainMixRequired() const; EngineEffectsManager* m_pEngineEffectsManager; diff --git a/src/engine/sidechain/enginesidechain.cpp b/src/engine/sidechain/enginesidechain.cpp index 3dbd3f45903..55808621242 100644 --- a/src/engine/sidechain/enginesidechain.cpp +++ b/src/engine/sidechain/enginesidechain.cpp @@ -28,6 +28,7 @@ #include #include "engine/sidechain/sidechainworker.h" +#include "engine/engine.h" #include "util/counter.h" #include "util/event.h" #include "util/sample.h" @@ -36,11 +37,14 @@ #define SIDECHAIN_BUFFER_SIZE 65536 -EngineSideChain::EngineSideChain(UserSettingsPointer pConfig) +EngineSideChain::EngineSideChain( + UserSettingsPointer pConfig, + CSAMPLE* sidechainMix) : m_pConfig(pConfig), m_bStopThread(false), m_sampleFifo(SIDECHAIN_BUFFER_SIZE), - m_pWorkBuffer(SampleUtil::alloc(SIDECHAIN_BUFFER_SIZE)) { + m_pWorkBuffer(SampleUtil::alloc(SIDECHAIN_BUFFER_SIZE)), + m_pSidechainMix(sidechainMix) { // We use HighPriority to prevent starvation by lower-priority processes (Qt // main thread, analysis, etc.). This used to be LowPriority but that is not // a suitable choice since we do semi-realtime tasks @@ -78,11 +82,13 @@ void EngineSideChain::addSideChainWorker(SideChainWorker* pWorker) { void EngineSideChain::receiveBuffer(AudioInput input, const CSAMPLE* pBuffer, unsigned int iFrames) { - if (input.getType() != AudioInput::RECORD_BROADCAST) { + VERIFY_OR_DEBUG_ASSERT(input.getType() == AudioInput::RECORD_BROADCAST) { qDebug() << "WARNING: AudioInput type is not RECORD_BROADCAST. Ignoring incoming buffer."; return; } - writeSamples(pBuffer, iFrames); + // Just copy the received samples form the sound card input to the + // engine. After processing we get it back via writeSamples() + SampleUtil::copy(m_pSidechainMix, pBuffer, iFrames * mixxx::kEngineChannelCount); } void EngineSideChain::writeSamples(const CSAMPLE* pBuffer, int iFrames) { diff --git a/src/engine/sidechain/enginesidechain.h b/src/engine/sidechain/enginesidechain.h index 8497c07d91b..23c4ae1ad33 100644 --- a/src/engine/sidechain/enginesidechain.h +++ b/src/engine/sidechain/enginesidechain.h @@ -32,7 +32,7 @@ class EngineSideChain : public QThread, public AudioDestination { Q_OBJECT public: - EngineSideChain(UserSettingsPointer pConfig); + EngineSideChain(UserSettingsPointer pConfig, CSAMPLE* sidechainMix); virtual ~EngineSideChain(); // Not thread-safe, wait-free. Submit buffer of samples to the sidechain for @@ -58,6 +58,7 @@ class EngineSideChain : public QThread, public AudioDestination { FIFO m_sampleFifo; CSAMPLE* m_pWorkBuffer; + CSAMPLE* m_pSidechainMix; // Provides thread safety around the wait condition below. QMutex m_waitLock;