#include "engine/enginemaster.h" #include #include #include #include "control/controlaudiotaperpot.h" #include "control/controlpotmeter.h" #include "control/controlpushbutton.h" #include "effects/effectsmanager.h" #include "engine/channelmixer.h" #include "engine/channels/enginechannel.h" #include "engine/channels/enginedeck.h" #include "engine/effects/engineeffectsmanager.h" #include "engine/enginebuffer.h" #include "engine/enginedelay.h" #include "engine/enginetalkoverducking.h" #include "engine/enginevumeter.h" #include "engine/engineworkerscheduler.h" #include "engine/enginexfader.h" #include "engine/sidechain/enginesidechain.h" #include "engine/sync/enginesync.h" #include "mixer/playermanager.h" #include "moc_enginemaster.cpp" #include "preferences/usersettings.h" #include "util/defs.h" #include "util/sample.h" #include "util/timer.h" #include "util/trace.h" EngineMaster::EngineMaster( UserSettingsPointer pConfig, const QString& group, EffectsManager* pEffectsManager, ChannelHandleFactoryPointer pChannelHandleFactory, bool bEnableSidechain) : m_pChannelHandleFactory(pChannelHandleFactory), m_pEngineEffectsManager(pEffectsManager->getEngineEffectsManager()), m_masterGainOld(0.0), m_boothGainOld(0.0), m_headphoneMasterGainOld(0.0), m_headphoneGainOld(1.0), m_balleftOld(1.0), m_balrightOld(1.0), m_masterHandle(registerChannelGroup(group)), m_headphoneHandle(registerChannelGroup("[Headphone]")), m_masterOutputHandle(registerChannelGroup("[MasterOutput]")), m_busTalkoverHandle(registerChannelGroup("[BusTalkover]")), m_busCrossfaderLeftHandle(registerChannelGroup("[BusLeft]")), m_busCrossfaderCenterHandle(registerChannelGroup("[BusCenter]")), m_busCrossfaderRightHandle(registerChannelGroup("[BusRight]")) { pEffectsManager->registerInputChannel(m_masterHandle); pEffectsManager->registerInputChannel(m_headphoneHandle); pEffectsManager->registerOutputChannel(m_masterHandle); pEffectsManager->registerOutputChannel(m_headphoneHandle); pEffectsManager->registerInputChannel(m_masterOutputHandle); pEffectsManager->registerInputChannel(m_busTalkoverHandle); pEffectsManager->registerInputChannel(m_busCrossfaderLeftHandle); pEffectsManager->registerInputChannel(m_busCrossfaderCenterHandle); pEffectsManager->registerInputChannel(m_busCrossfaderRightHandle); m_bBusOutputConnected[EngineChannel::LEFT] = false; m_bBusOutputConnected[EngineChannel::CENTER] = false; m_bBusOutputConnected[EngineChannel::RIGHT] = false; m_bExternalRecordBroadcastInputConnected = false; m_pWorkerScheduler = new EngineWorkerScheduler(this); m_pWorkerScheduler->start(QThread::HighPriority); // Master sample rate m_pMasterSampleRate = new ControlObject(ConfigKey(group, "samplerate"), true, true); m_pMasterSampleRate->set(44100.); // Latency control m_pMasterLatency = new ControlObject(ConfigKey(group, "latency"), true, true); m_pMasterAudioBufferSize = new ControlObject(ConfigKey(group, "audio_buffer_size")); m_pAudioLatencyOverloadCount = new ControlObject(ConfigKey(group, "audio_latency_overload_count"), true, true); m_pAudioLatencyUsage = new ControlPotmeter(ConfigKey(group, "audio_latency_usage"), 0.0, 0.25); m_pAudioLatencyOverload = new ControlPotmeter(ConfigKey(group, "audio_latency_overload"), 0.0, 1.0); // Master sync controller m_pMasterSync = new EngineSync(pConfig); // The last-used bpm value is saved in the destructor of EngineSync. double default_bpm = pConfig->getValue( ConfigKey("[InternalClock]", "bpm"), 124.0); ControlObject::getControl(ConfigKey("[InternalClock]","bpm"))->set(default_bpm); // Crossfader m_pCrossfader = new ControlPotmeter(ConfigKey(group, "crossfader"), -1., 1.); // Balance m_pBalance = new ControlPotmeter(ConfigKey(group, "balance"), -1., 1.); // Master gain m_pMasterGain = new ControlAudioTaperPot(ConfigKey(group, "gain"), -14, 14, 0.5); // Booth gain m_pBoothGain = new ControlAudioTaperPot(ConfigKey(group, "booth_gain"), -14, 14, 0.5); // Legacy: the master "gain" control used to be named "volume" in Mixxx // 1.11.0 and earlier. See Bug #1306253. ControlDoublePrivate::insertAlias(ConfigKey(group, "volume"), ConfigKey(group, "gain")); // VU meter: m_pVumeter = new EngineVuMeter(group); m_pMasterDelay = new EngineDelay(group, ConfigKey(group, "delay")); m_pHeadDelay = new EngineDelay(group, ConfigKey(group, "headDelay")); m_pBoothDelay = new EngineDelay(group, ConfigKey(group, "boothDelay")); m_pLatencyCompensationDelay = new EngineDelay(group, ConfigKey(group, "microphoneLatencyCompensation")); m_pNumMicsConfigured = new ControlObject(ConfigKey(group, "num_mics_configured")); // Headphone volume m_pHeadGain = new ControlAudioTaperPot(ConfigKey(group, "headGain"), -14, 14, 0.5); // Legacy: the headphone "headGain" control used to be named "headVolume" in // Mixxx 1.11.0 and earlier. See Bug #1306253. ControlDoublePrivate::insertAlias(ConfigKey(group, "headVolume"), ConfigKey(group, "headGain")); // Headphone mix (left/right) m_pHeadMix = new ControlPotmeter(ConfigKey(group, "headMix"),-1.,1.); m_pHeadMix->setDefaultValue(-1.); m_pHeadMix->set(-1.); // Master / Headphone split-out mode (for devices with only one output). m_pHeadSplitEnabled = new ControlPushButton(ConfigKey(group, "headSplit")); m_pHeadSplitEnabled->setButtonMode(ControlPushButton::TOGGLE); m_pHeadSplitEnabled->set(0.0); m_pTalkoverDucking = new EngineTalkoverDucking(pConfig, group); // Allocate buffers m_pHead = SampleUtil::alloc(MAX_BUFFER_LEN); m_pMaster = SampleUtil::alloc(MAX_BUFFER_LEN); m_pBooth = SampleUtil::alloc(MAX_BUFFER_LEN); m_pTalkover = SampleUtil::alloc(MAX_BUFFER_LEN); m_pTalkoverHeadphones = SampleUtil::alloc(MAX_BUFFER_LEN); m_pSidechainMix = SampleUtil::alloc(MAX_BUFFER_LEN); SampleUtil::clear(m_pHead, MAX_BUFFER_LEN); SampleUtil::clear(m_pMaster, MAX_BUFFER_LEN); SampleUtil::clear(m_pBooth, MAX_BUFFER_LEN); SampleUtil::clear(m_pTalkover, MAX_BUFFER_LEN); SampleUtil::clear(m_pTalkoverHeadphones, MAX_BUFFER_LEN); SampleUtil::clear(m_pSidechainMix, MAX_BUFFER_LEN); // Setup the output buses for (int o = EngineChannel::LEFT; o <= EngineChannel::RIGHT; ++o) { m_pOutputBusBuffers[o] = SampleUtil::alloc(MAX_BUFFER_LEN); SampleUtil::clear(m_pOutputBusBuffers[o], MAX_BUFFER_LEN); } // Starts a thread for recording and broadcast m_pEngineSideChain = bEnableSidechain ? new EngineSideChain(pConfig, m_pSidechainMix) : nullptr; // X-Fader Setup m_pXFaderMode = new ControlPushButton( ConfigKey(EngineXfader::kXfaderConfigKey, "xFaderMode")); m_pXFaderMode->setButtonMode(ControlPushButton::TOGGLE); m_pXFaderCurve = new ControlPotmeter( ConfigKey(EngineXfader::kXfaderConfigKey, "xFaderCurve"), EngineXfader::kTransformMin, EngineXfader::kTransformMax); m_pXFaderCalibration = new ControlPotmeter( ConfigKey(EngineXfader::kXfaderConfigKey, "xFaderCalibration"), 0.3, 1., true); m_pXFaderReverse = new ControlPushButton( ConfigKey(EngineXfader::kXfaderConfigKey, "xFaderReverse")); m_pXFaderReverse->setButtonMode(ControlPushButton::TOGGLE); m_pKeylockEngine = new ControlObject(ConfigKey(group, "keylock_engine"), true, false, true); m_pKeylockEngine->set(pConfig->getValueString( ConfigKey(group, "keylock_engine")).toDouble()); // TODO: Make this read only and make EngineMaster decide whether // processing the master mix is necessary. m_pMasterEnabled = new ControlObject(ConfigKey(group, "enabled"), true, false, true); // persist = true m_pBoothEnabled = new ControlObject(ConfigKey(group, "booth_enabled")); m_pBoothEnabled->setReadOnly(); m_pMasterMonoMixdown = new ControlObject(ConfigKey(group, "mono_mixdown"), true, false, true); // persist = true m_pMicMonitorMode = new ControlObject(ConfigKey(group, "talkover_mix"), true, false, true); // persist = true m_pHeadphoneEnabled = new ControlObject(ConfigKey(group, "headEnabled")); m_pHeadphoneEnabled->setReadOnly(); // Note: the EQ Rack is set in EffectsManager::setupDefaults(); } EngineMaster::~EngineMaster() { //qDebug() << "in ~EngineMaster()"; delete m_pKeylockEngine; delete m_pCrossfader; delete m_pBalance; delete m_pHeadMix; delete m_pHeadSplitEnabled; delete m_pMasterGain; delete m_pBoothGain; delete m_pHeadGain; delete m_pTalkoverDucking; delete m_pVumeter; delete m_pEngineSideChain; delete m_pMasterDelay; delete m_pHeadDelay; delete m_pBoothDelay; delete m_pLatencyCompensationDelay; delete m_pNumMicsConfigured; delete m_pXFaderReverse; delete m_pXFaderCalibration; delete m_pXFaderCurve; delete m_pXFaderMode; delete m_pMasterSync; delete m_pMasterSampleRate; delete m_pMasterLatency; delete m_pMasterAudioBufferSize; delete m_pAudioLatencyOverloadCount; delete m_pAudioLatencyUsage; delete m_pAudioLatencyOverload; delete m_pMasterEnabled; delete m_pBoothEnabled; delete m_pMasterMonoMixdown; delete m_pMicMonitorMode; delete m_pHeadphoneEnabled; SampleUtil::free(m_pHead); SampleUtil::free(m_pMaster); SampleUtil::free(m_pBooth); SampleUtil::free(m_pTalkover); SampleUtil::free(m_pTalkoverHeadphones); SampleUtil::free(m_pSidechainMix); for (int o = EngineChannel::LEFT; o <= EngineChannel::RIGHT; o++) { SampleUtil::free(m_pOutputBusBuffers[o]); } delete m_pWorkerScheduler; for (int i = 0; i < m_channels.size(); ++i) { ChannelInfo* pChannelInfo = m_channels[i]; SampleUtil::free(pChannelInfo->m_pBuffer); delete pChannelInfo->m_pChannel; delete pChannelInfo->m_pVolumeControl; delete pChannelInfo->m_pMuteControl; delete pChannelInfo; } } const CSAMPLE* EngineMaster::getMasterBuffer() const { return m_pMaster; } const CSAMPLE* EngineMaster::getBoothBuffer() const { return m_pBooth; } const CSAMPLE* EngineMaster::getHeadphoneBuffer() const { return m_pHead; } const CSAMPLE* EngineMaster::getSidechainBuffer() const { return m_pSidechainMix; } void EngineMaster::processChannels(int iBufferSize) { // Update internal master sync rate. m_pMasterSync->onCallbackStart(m_iSampleRate, m_iBufferSize); m_activeBusChannels[EngineChannel::LEFT].clear(); m_activeBusChannels[EngineChannel::CENTER].clear(); m_activeBusChannels[EngineChannel::RIGHT].clear(); m_activeHeadphoneChannels.clear(); m_activeTalkoverChannels.clear(); m_activeChannels.clear(); //ScopedTimer timer("EngineMaster::processChannels"); EngineChannel* pMasterChannel = m_pMasterSync->getMaster(); // Reserve the first place for the master channel which // should be processed first m_activeChannels.append(NULL); int activeChannelsStartIndex = 1; // Nothing at 0 yet for (int i = 0; i < m_channels.size(); ++i) { ChannelInfo* pChannelInfo = m_channels[i]; EngineChannel* pChannel = pChannelInfo->m_pChannel; // Skip inactive channels. if (!pChannel || !pChannel->isActive()) { continue; } if (pChannel->isTalkoverEnabled() && !pChannelInfo->m_pMuteControl->toBool()) { // talkover is an exclusive channel // once talkover is enabled it is not used in // xFader-Mix m_activeTalkoverChannels.append(pChannelInfo); // Check if we need to fade out the master channel GainCache& gainCache = m_channelMasterGainCache[i]; if (gainCache.m_gain != 0) { gainCache.m_fadeout = true; m_activeBusChannels[pChannel->getOrientation()].append(pChannelInfo); } } else { // Check if we need to fade out the channel GainCache& gainCache = m_channelTalkoverGainCache[i]; if (gainCache.m_gain != 0) { gainCache.m_fadeout = true; m_activeTalkoverChannels.append(pChannelInfo); } if (pChannel->isMasterEnabled() && !pChannelInfo->m_pMuteControl->toBool()) { // the xFader-Mix m_activeBusChannels[pChannel->getOrientation()].append(pChannelInfo); } else { // Check if we need to fade out the channel GainCache& gainCache = m_channelMasterGainCache[i]; if (gainCache.m_gain != 0) { gainCache.m_fadeout = true; m_activeBusChannels[pChannel->getOrientation()].append(pChannelInfo); } } } // If the channel is enabled for previewing in headphones, copy it // over to the headphone buffer if (pChannel->isPflEnabled()) { m_activeHeadphoneChannels.append(pChannelInfo); } else { // Check if we need to fade out the channel GainCache& gainCache = m_channelHeadphoneGainCache[i]; if (gainCache.m_gain != 0) { m_channelHeadphoneGainCache[i].m_fadeout = true; m_activeHeadphoneChannels.append(pChannelInfo); } } // If necessary, add the channel to the list of buffers to process. if (pChannel == pMasterChannel) { // If this is the sync master, it should be processed first. m_activeChannels.replace(0, pChannelInfo); activeChannelsStartIndex = 0; } else { m_activeChannels.append(pChannelInfo); } } // Now that the list is built and ordered, do the processing. for (int i = activeChannelsStartIndex; i < m_activeChannels.size(); ++i) { ChannelInfo* pChannelInfo = m_activeChannels[i]; EngineChannel* pChannel = pChannelInfo->m_pChannel; pChannel->process(pChannelInfo->m_pBuffer, iBufferSize); // Collect metadata for effects if (m_pEngineEffectsManager) { GroupFeatureState features; pChannel->collectFeatures(&features); pChannelInfo->m_features = features; } } // Do internal master sync post-processing before the other // channels. m_pMasterSync->onCallbackEnd(m_iSampleRate, m_iBufferSize); // After all the engines have been processed, trigger post-processing // which ensures that all channels are updating certain values at the // same point in time. This prevents sync from failing depending on // if the sync target was processed before or after the sync origin. for (int i = activeChannelsStartIndex; i < m_activeChannels.size(); ++i) { m_activeChannels[i]->m_pChannel->postProcess(iBufferSize); } } void EngineMaster::process(const int iBufferSize) { static bool haveSetName = false; if (!haveSetName) { QThread::currentThread()->setObjectName("Engine"); haveSetName = true; } //Trace t("EngineMaster::process"); bool masterEnabled = m_pMasterEnabled->toBool(); bool boothEnabled = m_pBoothEnabled->toBool(); bool headphoneEnabled = m_pHeadphoneEnabled->toBool(); m_iSampleRate = static_cast(m_pMasterSampleRate->get()); m_iBufferSize = iBufferSize; // TODO: remove assumption of stereo buffer const unsigned int kChannels = 2; const unsigned int iFrames = iBufferSize / kChannels; if (m_pEngineEffectsManager) { m_pEngineEffectsManager->onCallbackStart(); } // Prepare all channels for output processChannels(m_iBufferSize); // Compute headphone mix // Head phone left/right mix CSAMPLE pflMixGainInHeadphones = 1; CSAMPLE masterMixGainInHeadphones = 0; if (masterEnabled) { const auto cf_val = static_cast(m_pHeadMix->get()); pflMixGainInHeadphones = 0.5f * (-cf_val + 1.0f); masterMixGainInHeadphones = 0.5f * (cf_val + 1.0f); // qDebug() << "head val " << cf_val << ", head " << chead_gain // << ", master " << cmaster_gain; } // Mix all the PFL enabled channels together. m_headphoneGain.setGain(pflMixGainInHeadphones); if (headphoneEnabled) { // Process effects and mix PFL channels together for the headphones. // Effects will be reprocessed post-fader for the crossfader busses // and master mix, so the channel input buffers cannot be modified here. ChannelMixer::applyEffectsAndMixChannels( m_headphoneGain, &m_activeHeadphoneChannels, &m_channelHeadphoneGainCache, m_pHead, m_headphoneHandle.handle(), m_iBufferSize, m_iSampleRate, m_pEngineEffectsManager); // Process headphone channel effects if (m_pEngineEffectsManager) { GroupFeatureState headphoneFeatures; // If there is only one channel in the headphone mix, use its features // for effects processing. This allows for previewing how an effect will // sound on a playing deck before turning up the dry/wet knob to make it // audible on the master mix. Without this, the effect would sound different // in headphones than how it would sound if it was enabled on the deck, // for example with tempo synced effects. if (m_activeHeadphoneChannels.size() == 1) { headphoneFeatures = m_activeHeadphoneChannels.at(0)->m_features; } m_pEngineEffectsManager->processPostFaderInPlace( m_headphoneHandle.handle(), m_headphoneHandle.handle(), m_pHead, m_iBufferSize, m_iSampleRate, headphoneFeatures); } } // Mix all the talkover enabled channels together. // Effects processing is done in place to avoid unnecessary buffer copying. ChannelMixer::applyEffectsInPlaceAndMixChannels( m_talkoverGain, &m_activeTalkoverChannels, &m_channelTalkoverGainCache, m_pTalkover, m_masterHandle.handle(), m_iBufferSize, m_iSampleRate, m_pEngineEffectsManager); // Process effects on all microphones mixed together // We have no metadata for mixed effect buses, so use an empty GroupFeatureState. GroupFeatureState busFeatures; if (m_pEngineEffectsManager) { m_pEngineEffectsManager->processPostFaderInPlace( m_busTalkoverHandle.handle(), m_masterHandle.handle(), m_pTalkover, m_iBufferSize, m_iSampleRate, busFeatures); } switch (m_pTalkoverDucking->getMode()) { case EngineTalkoverDucking::OFF: m_pTalkoverDucking->setAboveThreshold(false); break; case EngineTalkoverDucking::AUTO: m_pTalkoverDucking->processKey(m_pTalkover, m_iBufferSize); break; case EngineTalkoverDucking::MANUAL: m_pTalkoverDucking->setAboveThreshold(m_activeTalkoverChannels.size()); break; default: DEBUG_ASSERT("!Unknown Ducking mode"); m_pTalkoverDucking->setAboveThreshold(false); break; } // Calculate the crossfader gains for left and right side of the crossfader CSAMPLE_GAIN crossfaderLeftGain, crossfaderRightGain; EngineXfader::getXfadeGains(m_pCrossfader->get(), m_pXFaderCurve->get(), m_pXFaderCalibration->get(), m_pXFaderMode->get(), m_pXFaderReverse->toBool(), &crossfaderLeftGain, &crossfaderRightGain); // Make the mix for each crossfader orientation output bus. // m_masterGain takes care of applying the attenuation from // channel volume faders, crossfader, and talkover ducking. // Talkover is mixed in later according to the configured MicMonitorMode m_masterGain.setGains(crossfaderLeftGain, 1.0f, crossfaderRightGain, m_pTalkoverDucking->getGain(m_iBufferSize / 2)); for (int o = EngineChannel::LEFT; o <= EngineChannel::RIGHT; o++) { ChannelMixer::applyEffectsInPlaceAndMixChannels( m_masterGain, &m_activeBusChannels[o], &m_channelMasterGainCache, // no [o] because the old gain follows an orientation switch m_pOutputBusBuffers[o], m_masterHandle.handle(), m_iBufferSize, m_iSampleRate, m_pEngineEffectsManager); } // Process crossfader orientation bus channel effects if (m_pEngineEffectsManager) { m_pEngineEffectsManager->processPostFaderInPlace( m_busCrossfaderLeftHandle.handle(), m_masterHandle.handle(), m_pOutputBusBuffers[EngineChannel::LEFT], m_iBufferSize, m_iSampleRate, busFeatures); m_pEngineEffectsManager->processPostFaderInPlace( m_busCrossfaderCenterHandle.handle(), m_masterHandle.handle(), m_pOutputBusBuffers[EngineChannel::CENTER], m_iBufferSize, m_iSampleRate, busFeatures); m_pEngineEffectsManager->processPostFaderInPlace( m_busCrossfaderRightHandle.handle(), m_masterHandle.handle(), m_pOutputBusBuffers[EngineChannel::RIGHT], m_iBufferSize, m_iSampleRate, busFeatures); } if (masterEnabled) { // Mix the crossfader orientation buffers together into the master mix SampleUtil::copy3WithGain(m_pMaster, m_pOutputBusBuffers[EngineChannel::LEFT], 1.0, m_pOutputBusBuffers[EngineChannel::CENTER], 1.0, m_pOutputBusBuffers[EngineChannel::RIGHT], 1.0, m_iBufferSize); MicMonitorMode configuredMicMonitorMode = static_cast( static_cast(m_pMicMonitorMode->get())); // Process master, booth, and record/broadcast buffers according to the // MicMonitorMode configured in DlgPrefSound // TODO(Be): make SampleUtil ramping functions update the old gain variable if (configuredMicMonitorMode == MicMonitorMode::MASTER) { // Process master channel effects // TODO(Be): Move this after mixing in talkover. To apply master effects // to both the master and booth in that case will require refactoring // the effects system to be able to process the same effects on multiple // buffers within the same callback. applyMasterEffects(); if (headphoneEnabled) { processHeadphones(masterMixGainInHeadphones); } // Copy master mix to booth output with booth gain before mixing // talkover with master mix if (boothEnabled) { CSAMPLE_GAIN boothGain = static_cast(m_pBoothGain->get()); SampleUtil::copyWithRampingGain(m_pBooth, m_pMaster, m_boothGainOld, boothGain, m_iBufferSize); m_boothGainOld = boothGain; } // Mix talkover into master mix if (m_pNumMicsConfigured->get() > 0) { SampleUtil::add(m_pMaster, m_pTalkover, m_iBufferSize); } // Apply master gain CSAMPLE_GAIN master_gain = static_cast(m_pMasterGain->get()); SampleUtil::applyRampingGain(m_pMaster, m_masterGainOld, master_gain, m_iBufferSize); m_masterGainOld = master_gain; // Record/broadcast signal is the same as the master output if (sidechainMixRequired()) { SampleUtil::copy(m_pSidechainMix, m_pMaster, m_iBufferSize); } } else if (configuredMicMonitorMode == MicMonitorMode::MASTER_AND_BOOTH) { // Process master channel effects // TODO(Be): Move this after mixing in talkover. For the MASTER only // MicMonitorMode above, that will require refactoring the effects system // to be able to process the same effects on different buffers // within the same callback. For consistency between the MicMonitorModes, // process master effects here before mixing in talkover. applyMasterEffects(); if (headphoneEnabled) { processHeadphones(masterMixGainInHeadphones); } // Mix talkover with master if (m_pNumMicsConfigured->get() > 0) { SampleUtil::add(m_pMaster, m_pTalkover, m_iBufferSize); } // Copy master mix (with talkover mixed in) to booth output with booth gain if (boothEnabled) { CSAMPLE_GAIN boothGain = static_cast(m_pBoothGain->get()); SampleUtil::copyWithRampingGain(m_pBooth, m_pMaster, m_boothGainOld, boothGain, m_iBufferSize); m_boothGainOld = boothGain; } // Apply master gain CSAMPLE_GAIN master_gain = static_cast(m_pMasterGain->get()); SampleUtil::applyRampingGain(m_pMaster, m_masterGainOld, master_gain, m_iBufferSize); m_masterGainOld = master_gain; // Record/broadcast signal is the same as the master output if (sidechainMixRequired()) { SampleUtil::copy(m_pSidechainMix, m_pMaster, m_iBufferSize); } } else if (configuredMicMonitorMode == MicMonitorMode::DIRECT_MONITOR) { // Skip mixing talkover with the master and booth outputs // if using direct monitoring because it is being mixed in hardware // without the latency of sending the signal into Mixxx for processing. // However, include the talkover mix in the record/broadcast signal. // Copy master mix to booth output with booth gain if (boothEnabled) { CSAMPLE_GAIN boothGain = static_cast(m_pBoothGain->get()); SampleUtil::copyWithRampingGain(m_pBooth, m_pMaster, m_boothGainOld, boothGain, m_iBufferSize); m_boothGainOld = boothGain; } // Process master channel effects // NOTE(Be): This should occur before mixing in talkover for the // record/broadcast signal so the record/broadcast signal is the same // as what is heard on the master & booth outputs. applyMasterEffects(); if (headphoneEnabled) { processHeadphones(masterMixGainInHeadphones); } // Apply master gain CSAMPLE_GAIN master_gain = static_cast(m_pMasterGain->get()); SampleUtil::applyRampingGain(m_pMaster, m_masterGainOld, master_gain, m_iBufferSize); m_masterGainOld = master_gain; if (sidechainMixRequired()) { SampleUtil::copy(m_pSidechainMix, m_pMaster, 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 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); } // Process effects that apply to master hardware output only but not // record/broadcast signal if (m_pEngineEffectsManager) { GroupFeatureState masterFeatures; masterFeatures.has_gain = true; masterFeatures.gain = m_pMasterGain->get(); m_pEngineEffectsManager->processPostFaderInPlace( m_masterOutputHandle.handle(), m_masterHandle.handle(), m_pMaster, m_iBufferSize, m_iSampleRate, masterFeatures); } // Balance values CSAMPLE balright = 1.; CSAMPLE balleft = 1.; const auto bal = static_cast(m_pBalance->get()); if (bal > 0.) { balleft -= bal; } else if (bal < 0.) { balright += bal; } // Perform balancing on main out SampleUtil::applyRampingAlternatingGain(m_pMaster, balleft, balright, m_balleftOld, m_balrightOld, iBufferSize); m_balleftOld = balleft; m_balrightOld = balright; // Update VU meter (it does not return anything). Needs to be here so that // master balance and talkover is reflected in the VU meter. if (m_pVumeter != nullptr) { m_pVumeter->process(m_pMaster, m_iBufferSize); } } if (m_pMasterMonoMixdown->toBool()) { SampleUtil::mixStereoToMono(m_pMaster, m_pMaster, m_iBufferSize); } if (masterEnabled) { m_pMasterDelay->process(m_pMaster, m_iBufferSize); } else { SampleUtil::clear(m_pMaster, m_iBufferSize); } if (headphoneEnabled) { m_pHeadDelay->process(m_pHead, m_iBufferSize); } if (boothEnabled) { m_pBoothDelay->process(m_pBooth, m_iBufferSize); } // We're close to the end of the callback. Wake up the engine worker // scheduler so that it runs the workers. m_pWorkerScheduler->runWorkers(); } void EngineMaster::applyMasterEffects() { // Apply master effects if (m_pEngineEffectsManager) { GroupFeatureState masterFeatures; masterFeatures.has_gain = true; masterFeatures.gain = m_pMasterGain->get(); m_pEngineEffectsManager->processPostFaderInPlace(m_masterHandle.handle(), m_masterHandle.handle(), m_pMaster, m_iBufferSize, m_iSampleRate, masterFeatures); } } void EngineMaster::processHeadphones(const CSAMPLE_GAIN masterMixGainInHeadphones) { // Add master mix to headphones SampleUtil::addWithRampingGain(m_pHead, m_pMaster, m_headphoneMasterGainOld, masterMixGainInHeadphones, m_iBufferSize); m_headphoneMasterGainOld = masterMixGainInHeadphones; // If Head Split is enabled, replace the left channel of the pfl buffer // with a mono mix of the headphone buffer, and the right channel of the pfl // buffer with a mono mix of the master output buffer. if (m_pHeadSplitEnabled->toBool()) { // note: NOT VECTORIZED because of in place copy for (unsigned int i = 0; i + 1 < m_iBufferSize; i += 2) { m_pHead[i] = (m_pHead[i] + m_pHead[i + 1]) / 2; m_pHead[i + 1] = (m_pMaster[i] + m_pMaster[i + 1]) / 2; } } // Apply headphone gain CSAMPLE_GAIN headphoneGain = static_cast(m_pHeadGain->get()); SampleUtil::applyRampingGain(m_pHead, m_headphoneGainOld, headphoneGain, m_iBufferSize); m_headphoneGainOld = headphoneGain; } void EngineMaster::addChannel(EngineChannel* pChannel) { ChannelInfo* pChannelInfo = new ChannelInfo(m_channels.size()); pChannelInfo->m_pChannel = pChannel; const QString& group = pChannel->getGroup(); pChannelInfo->m_handle = m_pChannelHandleFactory->getOrCreateHandle(group); pChannelInfo->m_pVolumeControl = new ControlAudioTaperPot( ConfigKey(group, "volume"), -20, 0, 1); pChannelInfo->m_pVolumeControl->setDefaultValue(1.0); pChannelInfo->m_pVolumeControl->set(1.0); pChannelInfo->m_pMuteControl = new ControlPushButton( ConfigKey(group, "mute")); pChannelInfo->m_pMuteControl->setButtonMode(ControlPushButton::POWERWINDOW); pChannelInfo->m_pBuffer = SampleUtil::alloc(MAX_BUFFER_LEN); SampleUtil::clear(pChannelInfo->m_pBuffer, MAX_BUFFER_LEN); m_channels.append(pChannelInfo); const GainCache gainCacheDefault = {0, false}; m_channelHeadphoneGainCache.append(gainCacheDefault); m_channelTalkoverGainCache.append(gainCacheDefault); m_channelMasterGainCache.append(gainCacheDefault); // Pre-allocate scratch buffers to avoid memory allocation in the // callback. QVarLengthArray does nothing if reserve is called with a size // smaller than its pre-allocation. m_activeChannels.reserve(m_channels.size()); m_activeBusChannels[EngineChannel::LEFT].reserve(m_channels.size()); m_activeBusChannels[EngineChannel::CENTER].reserve(m_channels.size()); m_activeBusChannels[EngineChannel::RIGHT].reserve(m_channels.size()); m_activeHeadphoneChannels.reserve(m_channels.size()); m_activeTalkoverChannels.reserve(m_channels.size()); EngineBuffer* pBuffer = pChannelInfo->m_pChannel->getEngineBuffer(); if (pBuffer != nullptr) { pBuffer->bindWorkers(m_pWorkerScheduler); } } EngineChannel* EngineMaster::getChannel(const QString& group) { for (int i = 0; i < m_channels.size(); ++i) { ChannelInfo* pChannelInfo = m_channels[i]; if (pChannelInfo->m_pChannel->getGroup() == group) { return pChannelInfo->m_pChannel; } } return nullptr; } const CSAMPLE* EngineMaster::getDeckBuffer(unsigned int i) const { return getChannelBuffer(PlayerManager::groupForDeck(i)); } const CSAMPLE* EngineMaster::getOutputBusBuffer(unsigned int i) const { if (i <= EngineChannel::RIGHT) { return m_pOutputBusBuffers[i]; } return nullptr; } const CSAMPLE* EngineMaster::getChannelBuffer(const QString& group) const { for (int i = 0; i < m_channels.size(); ++i) { const ChannelInfo* pChannelInfo = m_channels[i]; if (pChannelInfo->m_pChannel->getGroup() == group) { return pChannelInfo->m_pBuffer; } } return nullptr; } const CSAMPLE* EngineMaster::buffer(const AudioOutput& output) const { switch (output.getType()) { case AudioOutput::MASTER: return getMasterBuffer(); break; case AudioOutput::BOOTH: return getBoothBuffer(); break; case AudioOutput::HEADPHONES: return getHeadphoneBuffer(); break; case AudioOutput::BUS: return getOutputBusBuffer(output.getIndex()); break; case AudioOutput::DECK: return getDeckBuffer(output.getIndex()); break; case AudioOutput::RECORD_BROADCAST: return getSidechainBuffer(); break; default: return nullptr; } } void EngineMaster::onOutputConnected(const AudioOutput& output) { switch (output.getType()) { case AudioOutput::MASTER: // overwrite config option if a master output is configured m_pMasterEnabled->forceSet(1.0); break; case AudioOutput::HEADPHONES: m_pMasterEnabled->forceSet(1.0); m_pHeadphoneEnabled->forceSet(1.0); break; case AudioOutput::BOOTH: m_pMasterEnabled->forceSet(1.0); m_pBoothEnabled->forceSet(1.0); break; case AudioOutput::BUS: m_bBusOutputConnected[output.getIndex()] = true; break; case AudioOutput::DECK: // We don't track enabled decks. break; case AudioOutput::RECORD_BROADCAST: // We don't track enabled sidechain. break; default: break; } } void EngineMaster::onOutputDisconnected(const AudioOutput& output) { switch (output.getType()) { case AudioOutput::MASTER: // not used, because we need the master buffer for headphone mix // and recording/broadcasting as well break; case AudioOutput::BOOTH: m_pBoothEnabled->forceSet(0.0); break; case AudioOutput::HEADPHONES: m_pHeadphoneEnabled->forceSet(0.0); break; case AudioOutput::BUS: m_bBusOutputConnected[output.getIndex()] = false; break; case AudioOutput::DECK: // We don't track enabled decks. break; case AudioOutput::RECORD_BROADCAST: // We don't track enabled sidechain. break; default: break; } } void EngineMaster::onInputConnected(const AudioInput& input) { switch (input.getType()) { case AudioInput::MICROPHONE: m_pNumMicsConfigured->set(m_pNumMicsConfigured->get() + 1); break; case AudioInput::AUXILIARY: // We don't track enabled auxiliary inputs. break; case AudioInput::VINYLCONTROL: // We don't track enabled vinyl control inputs. break; case AudioInput::RECORD_BROADCAST: m_bExternalRecordBroadcastInputConnected = true; break; default: break; } } void EngineMaster::onInputDisconnected(const AudioInput& input) { switch (input.getType()) { case AudioInput::MICROPHONE: m_pNumMicsConfigured->set(m_pNumMicsConfigured->get() - 1); break; case AudioInput::AUXILIARY: // We don't track enabled auxiliary inputs. break; case AudioInput::VINYLCONTROL: // We don't track enabled vinyl control inputs. break; case AudioInput::RECORD_BROADCAST: m_bExternalRecordBroadcastInputConnected = false; break; default: break; } } void EngineMaster::registerNonEngineChannelSoundIO(SoundManager* pSoundManager) { pSoundManager->registerInput(AudioInput(AudioPath::RECORD_BROADCAST, 0, 2), m_pEngineSideChain); pSoundManager->registerOutput(AudioOutput(AudioOutput::MASTER, 0, 2), this); pSoundManager->registerOutput(AudioOutput(AudioOutput::HEADPHONES, 0, 2), this); pSoundManager->registerOutput(AudioOutput(AudioOutput::BOOTH, 0, 2), this); for (int o = EngineChannel::LEFT; o <= EngineChannel::RIGHT; o++) { pSoundManager->registerOutput(AudioOutput(AudioOutput::BUS, 0, 2, o), this); } pSoundManager->registerOutput(AudioOutput(AudioOutput::RECORD_BROADCAST, 0, 2), this); } bool EngineMaster::sidechainMixRequired() const { return m_pEngineSideChain && !m_bExternalRecordBroadcastInputConnected; }