Exemplo n.º 1
0
/* Read XBus data from serial port */
void  readSerialPort(void) {
	/* Serial port and XBus message buffers */
	unsigned char buff;
	unsigned int byte = 0;
	unsigned char message[XBUS_CHANNELS*4 + 7];
	
	/* Read XBus input indefinitely */
	while (1) {
		/* Get a character from the serial port */
		int n = read(fd, &buff, 1);
		
		/* If we have new data, process it */
		if (n == 1) {
			/* Check for start character */
			if (byte == 0 && buff == 0xA4) {
				message[byte++] = buff;
				continue;
			}
			/* Assimilate the rest of the message */
			if (byte > 0) {
				message[byte++] = buff;
				
				/* Full message seems to be two bytes (+ checksum) longer than length stated in the 2nd byte */
				if (byte==message[1]+3) {
					// Couldn't figure out which bytes are actually being checksummed, so gave up trying.
					// TODO: Figure this out before doing any interface to flight controls (i.e safety critical stuff!)
					/* uint8_t crcCalc = crc8(message,message[1]);
					if (message[byte-1] == crcCalc) {
						// Process message
					} else {
						#ifdef DEBUG
						printf("XBus CRC Fail!\n");
						#endif
					}
					*/
					// Assume that when we have a full packets worth of bytes (from start char), we can decode channels
					
					/* Assume a maximum of 14 channels (XG14 Tx) */
					double channels[XBUS_CHANNELS];
					for (int i=0; i<XBUS_CHANNELS; i++) {
						/* Conversion from JR Documentation */
						channels[i] = 800.0 + (1400.0*(message[4 + i*4 + 2]*256+message[4 + i*4 + 3]))/(double(0xFFFF));
					}
					
					/* Process the channel data */
					processChannels(channels);
					
					/* Message has been deatl with, reset for next one */
					byte = 0;
				}
			}
		}
	}
}
Exemplo n.º 2
0
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->get();
    bool headphoneEnabled = m_pHeadphoneEnabled->get();

    int iSampleRate = static_cast<int>(m_pMasterSampleRate->get());
    // Update internal master sync.
    m_pMasterSync->onCallbackStart(iSampleRate, iBufferSize);
    if (m_pEngineEffectsManager) {
        m_pEngineEffectsManager->onCallbackStart();
    }

    // Bitvector of enabled channels
    const unsigned int maxChannels = 32;
    unsigned int busChannelConnectionFlags[3] = { 0, 0, 0 };
    unsigned int headphoneOutput = 0;

    // Prepare each channel for output
    processChannels(busChannelConnectionFlags, &headphoneOutput, iBufferSize);

    // Compute headphone mix
    // Head phone left/right mix
    CSAMPLE chead_gain = 1;
    CSAMPLE cmaster_gain = 0;
    if (masterEnabled) {
        CSAMPLE cf_val = m_pHeadMix->get();
        chead_gain = 0.5 * (-cf_val + 1.);
        cmaster_gain = 0.5 * (cf_val + 1.);
        // qDebug() << "head val " << cf_val << ", head " << chead_gain
        //          << ", master " << cmaster_gain;
    }

    // Mix all the enabled headphone channels together.
    m_headphoneGain.setGain(chead_gain);

    if (m_bRampingGain) {
        ChannelMixer::mixChannelsRamping(
            m_channels, m_headphoneGain, headphoneOutput,
            maxChannels, &m_channelHeadphoneGainCache,
            m_pHead, iBufferSize);
    } else {
        ChannelMixer::mixChannels(
            m_channels, m_headphoneGain, headphoneOutput,
            maxChannels, &m_channelHeadphoneGainCache,
            m_pHead, iBufferSize);
    }

    // Calculate the crossfader gains for left and right side of the crossfader
    double c1_gain, c2_gain;
    EngineXfader::getXfadeGains(m_pCrossfader->get(), m_pXFaderCurve->get(),
                                m_pXFaderCalibration->get(),
                                m_pXFaderMode->get() == MIXXX_XFADER_CONSTPWR,
                                m_pXFaderReverse->get() == 1.0,
                                &c1_gain, &c2_gain);

    // Channels with the talkover flag should be mixed with the master signal at
    // full master volume.  All other channels should be adjusted by ducking gain.
    m_masterGain.setGains(m_pTalkoverDucking->getGain(iBufferSize / 2),
                          c1_gain, 1.0, c2_gain, 1.0);

    // Make the mix for each output bus. m_masterGain takes care of applying the
    // master volume, the channel volume, and the orientation gain.
    for (int o = EngineChannel::LEFT; o <= EngineChannel::RIGHT; o++) {
        if (m_bRampingGain) {
            ChannelMixer::mixChannelsRamping(
                m_channels, m_masterGain,
                busChannelConnectionFlags[o], maxChannels,
                &m_channelMasterGainCache,
                m_pOutputBusBuffers[o], iBufferSize);
        } else {
            ChannelMixer::mixChannels(
                m_channels, m_masterGain,
                busChannelConnectionFlags[o], maxChannels,
                &m_channelMasterGainCache,
                m_pOutputBusBuffers[o], iBufferSize);
        }
    }

    // Process master channel effects
    if (m_pEngineEffectsManager) {
        GroupFeatureState busFeatures;
        m_pEngineEffectsManager->process(getBusLeftGroup(), m_pOutputBusBuffers[0],
                                             iBufferSize, busFeatures);
        m_pEngineEffectsManager->process(getBusCenterGroup(), m_pOutputBusBuffers[1],
                                             iBufferSize, busFeatures);
        m_pEngineEffectsManager->process(getBusRightGroup(), m_pOutputBusBuffers[2],
                                             iBufferSize, busFeatures);
    }

    if (masterEnabled) {
        // Mix the three channels together. We already mixed the busses together
        // with the channel gains and overall master gain.
        SampleUtil::copy3WithGain(m_pMaster,
                                  m_pOutputBusBuffers[EngineChannel::LEFT], 1.0,
                                  m_pOutputBusBuffers[EngineChannel::CENTER], 1.0,
                                  m_pOutputBusBuffers[EngineChannel::RIGHT], 1.0,
                                  iBufferSize);

        // Process master channel effects
        if (m_pEngineEffectsManager) {
            GroupFeatureState masterFeatures;
            // Well, this is delayed by one buffer (it's dependent on the
            // output). Oh well.
            if (m_pVumeter != NULL) {
                m_pVumeter->collectFeatures(&masterFeatures);
            }
            m_pEngineEffectsManager->process(getMasterGroup(), m_pMaster,
                                             iBufferSize, masterFeatures);
        }

        // Apply master volume after effects.
        CSAMPLE master_volume = m_pMasterVolume->get();
        if (m_bRampingGain) {
            SampleUtil::applyRampingGain(m_pMaster, m_masterVolumeOld,
                                         master_volume, iBufferSize);
        } else {
            SampleUtil::applyGain(m_pMaster, master_volume, iBufferSize);
        }
        m_masterVolumeOld = master_volume;

        // Balance values
        CSAMPLE balright = 1.;
        CSAMPLE balleft = 1.;
        CSAMPLE bal = m_pBalance->get();
        if (bal > 0.) {
            balleft -= bal;
        } else if (bal < 0.) {
            balright += bal;
        }

        // Perform balancing on main out
        SampleUtil::applyAlternatingGain(m_pMaster, balleft, balright, iBufferSize);

        // Update VU meter (it does not return anything). Needs to be here so that
        // master balance is reflected in the VU meter.
        if (m_pVumeter != NULL) {
            m_pVumeter->process(m_pMaster, iBufferSize);
        }
        // Submit master samples to the side chain to do shoutcasting, recording,
        // etc. (cpu intensive non-realtime tasks)
        if (m_pSideChain != NULL) {
            m_pSideChain->writeSamples(m_pMaster, iBufferSize);
        }

        // Add master to headphone with appropriate gain
        if (headphoneEnabled) {
            if (m_bRampingGain) {
                SampleUtil::addWithRampingGain(m_pHead, m_pMaster,
                                               m_headphoneMasterGainOld,
                                               cmaster_gain, iBufferSize);
            } else {
                SampleUtil::addWithGain(m_pHead, m_pMaster, cmaster_gain, iBufferSize);
            }
            m_headphoneMasterGainOld = cmaster_gain;
        }
    }


    if (headphoneEnabled) {
        // Process headphone channel effects
        if (m_pEngineEffectsManager) {
            GroupFeatureState headphoneFeatures;
            m_pEngineEffectsManager->process(getHeadphoneGroup(), m_pHead,
                                             iBufferSize, headphoneFeatures);
        }
        // Head volume
        CSAMPLE headphoneVolume = m_pHeadVolume->get();
        if (m_bRampingGain) {
            SampleUtil::applyRampingGain(m_pHead, m_headphoneVolumeOld,
                                         headphoneVolume, iBufferSize);
        } else {
            SampleUtil::applyGain(m_pHead, headphoneVolume, iBufferSize);
        }
        m_headphoneVolumeOld = headphoneVolume;
    }

    if (masterEnabled && headphoneEnabled) {
        // 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->get()) {
            for (int i = 0; i + 1 < 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;
            }
        }
    }

    if (masterEnabled) {
        m_pMasterDelay->process(m_pMaster, iBufferSize);
    } else {
        SampleUtil::clear(m_pMaster, iBufferSize);
    }
    if (headphoneEnabled) {
        m_pHeadDelay->process(m_pHead, 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();
}
Exemplo n.º 3
0
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->get();
    bool boothEnabled = m_pBoothEnabled->get();
    bool headphoneEnabled = m_pHeadphoneEnabled->get();

    // TODO: remove assumption of stereo buffer
    const unsigned int kChannels = 2;
    const unsigned int iFrames = iBufferSize / kChannels;
    unsigned int iSampleRate = static_cast<int>(m_pMasterSampleRate->get());
    if (m_pEngineEffectsManager) {
        m_pEngineEffectsManager->onCallbackStart();
    }

    // Update internal master sync rate.
    m_pMasterSync->onCallbackStart(iSampleRate, iBufferSize);
    // Prepare each channel for output
    processChannels(iBufferSize);
    // Do internal master sync post-processing
    m_pMasterSync->onCallbackEnd(iSampleRate, iBufferSize);

    // Compute headphone mix
    // Head phone left/right mix
    CSAMPLE chead_gain = 1;
    CSAMPLE cmaster_gain = 0;
    if (masterEnabled) {
        CSAMPLE cf_val = m_pHeadMix->get();
        chead_gain = 0.5 * (-cf_val + 1.);
        cmaster_gain = 0.5 * (cf_val + 1.);
        // qDebug() << "head val " << cf_val << ", head " << chead_gain
        //          << ", master " << cmaster_gain;
    }

    // Mix all the PFL enabled channels together.
    m_headphoneGain.setGain(chead_gain);

    if (headphoneEnabled) {
        if (m_bRampingGain) {
            ChannelMixer::mixChannelsRamping(
                    m_headphoneGain, &m_activeHeadphoneChannels,
                    &m_channelHeadphoneGainCache,
                    m_pHead, iBufferSize);
        } else {
            ChannelMixer::mixChannels(
                    m_headphoneGain, &m_activeHeadphoneChannels,
                    &m_channelHeadphoneGainCache,
                    m_pHead, iBufferSize);
        }

        // Process headphone channel effects
        if (m_pEngineEffectsManager) {
            GroupFeatureState headphoneFeatures;
            m_pEngineEffectsManager->process(m_headphoneHandle.handle(),
                                             m_pHead,
                                             iBufferSize, iSampleRate,
                                             headphoneFeatures);
        }
    }

    // Mix all the talkover enabled channels together.
    if (m_bRampingGain) {
        ChannelMixer::mixChannelsRamping(
                m_talkoverGain, &m_activeTalkoverChannels,
                &m_channelTalkoverGainCache,
                m_pTalkover, iBufferSize);
    } else {
        ChannelMixer::mixChannels(
                m_talkoverGain, &m_activeTalkoverChannels,
                &m_channelTalkoverGainCache,
                m_pTalkover, iBufferSize);
    }

    // Clear talkover compressor for the next round of gain calculation.
    m_pTalkoverDucking->clearKeys();
    if (m_pTalkoverDucking->getMode() != EngineTalkoverDucking::OFF) {
        m_pTalkoverDucking->processKey(m_pTalkover, iBufferSize);
    }

    // Calculate the crossfader gains for left and right side of the crossfader
    double crossfaderLeftGain, crossfaderRightGain;
    EngineXfader::getXfadeGains(m_pCrossfader->get(), m_pXFaderCurve->get(),
                                m_pXFaderCalibration->get(),
                                m_pXFaderMode->get(),
                                m_pXFaderReverse->toBool(),
                                &crossfaderLeftGain, &crossfaderRightGain);

    m_masterGain.setGains(crossfaderLeftGain, 1.0, crossfaderRightGain,
                            m_pTalkoverDucking->getGain(iBufferSize / 2));

    // 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
    for (int o = EngineChannel::LEFT; o <= EngineChannel::RIGHT; o++) {
        if (m_bRampingGain) {
            ChannelMixer::mixChannelsRamping(
                    m_masterGain,
                    &m_activeBusChannels[o],
                    &m_channelMasterGainCache, // no [o] because the old gain follows an orientation switch
                    m_pOutputBusBuffers[o], iBufferSize);
        } else {
            ChannelMixer::mixChannels(
                    m_masterGain,
                    &m_activeBusChannels[o],
                    &m_channelMasterGainCache,
                    m_pOutputBusBuffers[o], iBufferSize);
        }
    }

    // Process crossfader orientation bus channel effects
    if (m_pEngineEffectsManager) {
        GroupFeatureState busFeatures;
        m_pEngineEffectsManager->process(m_busLeftHandle.handle(),
                                         m_pOutputBusBuffers[EngineChannel::LEFT],
                                         iBufferSize, iSampleRate, busFeatures);
        m_pEngineEffectsManager->process(m_busCenterHandle.handle(),
                                         m_pOutputBusBuffers[EngineChannel::CENTER],
                                         iBufferSize, iSampleRate, busFeatures);
        m_pEngineEffectsManager->process(m_busRightHandle.handle(),
                                         m_pOutputBusBuffers[EngineChannel::RIGHT],
                                         iBufferSize, 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,
            iBufferSize);

        MicMonitorMode configuredMicMonitorMode = static_cast<MicMonitorMode>(
            static_cast<int>(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(iBufferSize, iSampleRate);

            // Copy master mix to booth output with booth gain before mixing
            // talkover with master mix
            if (boothEnabled) {
                CSAMPLE boothGain = m_pBoothGain->get();
                if (m_bRampingGain) {
                    SampleUtil::copy1WithRampingGain(m_pBooth, m_pMaster,
                        m_boothGainOld, boothGain, iBufferSize);
                } else {
                    SampleUtil::copy1WithGain(m_pBooth, m_pMaster,
                        boothGain, iBufferSize);
                }
                m_boothGainOld = boothGain;
            }

            // Mix talkover into master mix
            if (m_pNumMicsConfigured->get() > 0) {
                SampleUtil::copy2WithGain(m_pMaster,
                    m_pMaster, 1.0,
                    m_pTalkover, 1.0,
                    iBufferSize);
            }

            // Apply master gain
            // TODO(Be): make this not affect the headphones. Refer to
            // https://bugs.launchpad.net/mixxx/+bug/1458213
            CSAMPLE master_gain = m_pMasterGain->get();
            if (m_bRampingGain) {
                SampleUtil::applyRampingGain(m_pMaster, m_masterGainOld, master_gain,
                                             iBufferSize);
            } else {
                SampleUtil::applyGain(m_pMaster, master_gain, iBufferSize);
            }
            m_masterGainOld = master_gain;

            // Record/broadcast signal is the same as the master output
            m_ppSidechainOutput = &m_pMaster;
        } 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(iBufferSize, iSampleRate);

            // Mix talkover with master
            if (m_pNumMicsConfigured->get() > 0) {
                SampleUtil::copy2WithGain(m_pMaster,
                    m_pMaster, 1.0,
                    m_pTalkover, 1.0,
                    iBufferSize);
            }

            // Copy master mix (with talkover mixed in) to booth output with booth gain
            if (boothEnabled) {
                CSAMPLE boothGain = m_pBoothGain->get();
                if (m_bRampingGain) {
                    SampleUtil::copy1WithRampingGain(m_pBooth, m_pMaster,
                        m_boothGainOld, boothGain, iBufferSize);
                } else {
                    SampleUtil::copy1WithGain(m_pBooth, m_pMaster,
                        boothGain, iBufferSize);
                }
                m_boothGainOld = boothGain;
            }

            // Apply master gain
            // TODO(Be): make this not affect the headphones. Refer to
            // https://bugs.launchpad.net/mixxx/+bug/1458213
            CSAMPLE master_gain = m_pMasterGain->get();
            if (m_bRampingGain) {
                SampleUtil::applyRampingGain(m_pMaster, m_masterGainOld, master_gain,
                                             iBufferSize);
            } else {
                SampleUtil::applyGain(m_pMaster, master_gain, iBufferSize);
            }
            m_masterGainOld = master_gain;

            // Record/broadcast signal is the same as the master output
            m_ppSidechainOutput = &m_pMaster;
        } 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 boothGain = m_pBoothGain->get();
                if (m_bRampingGain) {
                    SampleUtil::copy1WithRampingGain(m_pBooth, m_pMaster,
                        m_boothGainOld, boothGain, iBufferSize);
                } else {
                    SampleUtil::copy1WithGain(m_pBooth, m_pMaster,
                        boothGain, 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(iBufferSize, iSampleRate);

            // Apply master gain
            // TODO(Be): make this not affect the headphones. Refer to
            // https://bugs.launchpad.net/mixxx/+bug/1458213
            CSAMPLE master_gain = m_pMasterGain->get();
            if (m_bRampingGain) {
                SampleUtil::applyRampingGain(m_pMaster, m_masterGainOld, master_gain,
                                             iBufferSize);
            } else {
                SampleUtil::applyGain(m_pMaster, master_gain, iBufferSize);
            }
            m_masterGainOld = master_gain;

            // 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.
                SampleUtil::copy(m_pSidechainMix, m_pMaster, iBufferSize);
                m_pLatencyCompensationDelay->process(m_pSidechainMix, iBufferSize);
                SampleUtil::copy2WithGain(m_pSidechainMix,
                                          m_pSidechainMix, 1.0,
                                          m_pTalkover, 1.0,
                                          iBufferSize);
                m_ppSidechainOutput = &m_pSidechainMix;
            } else {
                m_ppSidechainOutput = &m_pMaster;
            }
        }

        // 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) {
            m_pEngineSideChain->writeSamples(*m_ppSidechainOutput, iFrames);
        }

        // Balance values
        CSAMPLE balright = 1.;
        CSAMPLE balleft = 1.;
        CSAMPLE bal = m_pBalance->get();
        if (bal > 0.) {
            balleft -= bal;
        } else if (bal < 0.) {
            balright += bal;
        }

        // Perform balancing on main out
        SampleUtil::applyAlternatingGain(m_pMaster, balleft, balright, iBufferSize);

        // 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 != NULL) {
            m_pVumeter->process(m_pMaster, iBufferSize);
        }

        // Add master to headphone with appropriate gain
        if (headphoneEnabled) {
            if (m_bRampingGain) {
                SampleUtil::addWithRampingGain(m_pHead, m_pMaster,
                                               m_headphoneMasterGainOld,
                                               cmaster_gain, iBufferSize);
            } else {
                SampleUtil::addWithGain(m_pHead, m_pMaster, cmaster_gain, iBufferSize);
            }
            m_headphoneMasterGainOld = cmaster_gain;
        }
    }

    if (headphoneEnabled) {
        // Process headphone channel effects
        if (m_pEngineEffectsManager) {
            GroupFeatureState headphoneFeatures;
            m_pEngineEffectsManager->process(m_headphoneHandle.handle(),
                                             m_pHead,
                                             iBufferSize, iSampleRate,
                                             headphoneFeatures);
        }
        // Head volume
        CSAMPLE headphoneGain = m_pHeadGain->get();
        if (m_bRampingGain) {
            SampleUtil::applyRampingGain(m_pHead, m_headphoneGainOld,
                                         headphoneGain, iBufferSize);
        } else {
            SampleUtil::applyGain(m_pHead, headphoneGain, iBufferSize);
        }
        m_headphoneGainOld = headphoneGain;
    }

    if (masterEnabled && headphoneEnabled) {
        // 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->get()) {
            // note: NOT VECTORIZED because of in place copy
            for (int i = 0; i + 1 < 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;
            }
        }
    }

    if (m_pMasterMonoMixdown->get()) {
        SampleUtil::mixStereoToMono(m_pMaster, m_pMaster, iBufferSize);
    }

    if (masterEnabled) {
        m_pMasterDelay->process(m_pMaster, iBufferSize);
    } else {
        SampleUtil::clear(m_pMaster, iBufferSize);
    }
    if (headphoneEnabled) {
        m_pHeadDelay->process(m_pHead, iBufferSize);
    }
    if (boothEnabled) {
        m_pBoothDelay->process(m_pBooth, 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();
}