/*
 * Fastmix.cpp
 * -----------
 * Purpose: Mixer core for rendering samples, mixing plugins, etc...
 * Notes  : If this is Fastmix.cpp, where is Slowmix.cpp? :)
 * Authors: Olivier Lapicque
 *          OpenMPT Devs
 * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
 */


// FIXME:
// - Playing samples backwards should reverse interpolation LUTs for interpolation modes
//   with more than two taps since they're not symmetric. We might need separate LUTs
//   because otherwise we will add tons of branches.
// - Loop wraparound works pretty well in general, but not at the start of bidi samples.
// - The loop lookahead stuff might still fail for samples with backward loops.

#include "stdafx.h"
#include "Sndfile.h"
#include "MixerLoops.h"
#include "MixFuncTable.h"
#include "plugins/PlugInterface.h"
#include <cfloat>  // For FLT_EPSILON
#include <algorithm>


OPENMPT_NAMESPACE_BEGIN


/////////////////////////////////////////////////////////////////////////

struct MixLoopState
{
	const int8 * samplePointer = nullptr;
	const int8 * lookaheadPointer = nullptr;
	SmpLength lookaheadStart = 0;
	uint32 maxSamples = 0;
	const uint8 ITPingPongDiff;
	const bool precisePingPongLoops;

	MixLoopState(const CSoundFile &sndFile, const ModChannel &chn)
		: ITPingPongDiff{sndFile.m_playBehaviour[kITPingPongMode] ? uint8(1) : uint8(0)}
		, precisePingPongLoops{!sndFile.m_playBehaviour[kImprecisePingPongLoops]}
	{
		if(chn.pCurrentSample == nullptr)
			return;

		UpdateLookaheadPointers(chn);

		// For platforms that have no fast 64-bit division, precompute this constant
		// as it won't change during the invocation of CreateStereoMix.
		SamplePosition increment = chn.increment;
		if(increment.IsNegative())
			increment.Negate();
		maxSamples = 16384u / (increment.GetUInt() + 1u);
		if(maxSamples < 2)
			maxSamples = 2;
	}

	// Calculate offset of loop wrap-around buffer for this sample.
	void UpdateLookaheadPointers(const ModChannel &chn)
	{
		samplePointer = static_cast<const int8 *>(chn.pCurrentSample);
		lookaheadPointer = nullptr;
		if(!samplePointer)
			return;
		if(chn.nLoopEnd < InterpolationLookaheadBufferSize)
			lookaheadStart = chn.nLoopStart;
		else
			lookaheadStart = std::max(chn.nLoopStart, chn.nLoopEnd - InterpolationLookaheadBufferSize);
		// We only need to apply the loop wrap-around logic if the sample is actually looping and if interpolation is applied.
		// If there is no interpolation happening, there is no lookahead happening the sample read-out is exact.
		if(chn.dwFlags[CHN_LOOP] && chn.resamplingMode != SRCMODE_NEAREST)
		{
			const bool inSustainLoop = chn.InSustainLoop() && chn.nLoopStart == chn.pModSample->nSustainStart && chn.nLoopEnd == chn.pModSample->nSustainEnd;

			// Do not enable wraparound magic if we're previewing a custom loop!
			if(inSustainLoop || chn.nLoopEnd == chn.pModSample->nLoopEnd)
			{
				SmpLength lookaheadOffset = 3 * InterpolationLookaheadBufferSize + chn.pModSample->nLength - chn.nLoopEnd;
				if(inSustainLoop)
				{
					lookaheadOffset += 4 * InterpolationLookaheadBufferSize;
				}
				lookaheadPointer = samplePointer + lookaheadOffset * chn.pModSample->GetBytesPerSample();
			}
		}
	}

	// Returns the buffer length required to render a certain amount of samples, based on the channel's playback speed.
	static MPT_FORCEINLINE uint32 DistanceToBufferLength(SamplePosition from, SamplePosition to, SamplePosition inc)
	{
		return static_cast<uint32>((to - from - SamplePosition(1)) / inc) + 1;
	}

	// Check how many samples can be rendered without encountering loop or sample end, and also update loop position / direction
	MPT_FORCEINLINE uint32 GetSampleCount(ModChannel &chn, uint32 nSamples) const
	{
		int32 nLoopStart = chn.dwFlags[CHN_LOOP] ? chn.nLoopStart : 0;
		SamplePosition nInc = chn.increment;

		if(nSamples <= 0 || nInc.IsZero() || !chn.nLength || !samplePointer)
			return 0;

		// Part 1: Making sure the play position is valid, and if necessary, invert the play direction in case we reached a loop boundary of a ping-pong loop.
		chn.pCurrentSample = samplePointer;

		// Under zero ?
		if (chn.position.GetInt() < nLoopStart)
		{
			if (nInc.IsNegative())
			{
				// Invert loop direction for bidi loops
				chn.position = SamplePosition(nLoopStart + nLoopStart, 0) - chn.position;
				if ((chn.position.GetInt() < nLoopStart) || (chn.position.GetUInt() >= (nLoopStart + chn.nLength) / 2))
				{
					chn.position.Set(nLoopStart, 0);
				}
				if(chn.dwFlags[CHN_PINGPONGLOOP])
				{
					chn.dwFlags.reset(CHN_PINGPONGFLAG); // go forward
					nInc.Negate();
					chn.increment = nInc;
				} else
				{
					chn.position.SetInt(chn.nLength - 1);
				}
				if(!chn.dwFlags[CHN_LOOP] || chn.position.GetUInt() >= chn.nLength)
				{
					chn.position.Set(chn.nLength);
					return 0;
				}
			} else
			{
				// We probably didn't hit the loop end yet (first loop), so we do nothing
				if (chn.position.GetInt() < 0) chn.position.SetInt(0);
			}
		} else if (chn.position.GetUInt() >= chn.nLength)
		{
			// Past the end
			if(!chn.dwFlags[CHN_LOOP])
				return 0; // not looping -> stop this channel
			if(chn.dwFlags[CHN_PINGPONGLOOP])
			{
				// Invert loop
				if (nInc.IsPositive())
				{
					nInc.Negate();
					chn.increment = nInc;
				}
				chn.dwFlags.set(CHN_PINGPONGFLAG);

				// Adjust loop position
				if(precisePingPongLoops)
				{
					// More accurate loop end overshoot calculation.
					// Test cases: BidiPrecision.it, BidiPrecision.xm
					const auto overshoot = chn.position - SamplePosition(chn.nLength, 0);
					const auto loopLength = chn.nLoopEnd - chn.nLoopStart - ITPingPongDiff;
					if(overshoot.GetUInt() < loopLength)
						chn.position = SamplePosition(chn.nLength - ITPingPongDiff, 0) - overshoot;
					else
						chn.position = SamplePosition(chn.nLoopStart, 0);
				} else
				{
					SamplePosition invFract = chn.position.GetInvertedFract();
					chn.position = SamplePosition(chn.nLength - (chn.position.GetInt() - chn.nLength) - invFract.GetInt(), invFract.GetFract());
					if(chn.position.GetUInt() <= chn.nLoopStart || chn.position.GetUInt() >= chn.nLength)
					{
						// Impulse Tracker's software mixer would put a -2 (instead of -1) in the following line (doesn't happen on a GUS)
						chn.position.SetInt(chn.nLength - std::min(chn.nLength, static_cast<SmpLength>(ITPingPongDiff + 1)));
					}
				}
			} else
			{
				if (nInc.IsNegative()) // This is a bug
				{
					nInc.Negate();
					chn.increment = nInc;
				}
				// Restart at loop start
				chn.position += SamplePosition(nLoopStart - chn.nLength, 0);
				MPT_ASSERT(chn.position.GetInt() >= nLoopStart);
				// Interpolate correctly after wrapping around
				chn.dwFlags.set(CHN_WRAPPED_LOOP);
			}
		}

		// Part 2: Compute how many samples we can render until we reach the end of sample / loop boundary / etc.

		SamplePosition nPos = chn.position;
		// too big increment, and/or too small loop length
		if (nPos.GetInt() < nLoopStart)
		{
			if (nPos.IsNegative() || nInc.IsNegative()) return 0;
		}
		if (nPos.IsNegative() || nPos.GetUInt() >= chn.nLength) return 0;
		uint32 nSmpCount = nSamples;
		SamplePosition nInv = nInc;
		if (nInc.IsNegative())
		{
			nInv.Negate();
		}
		LimitMax(nSamples, maxSamples);
		SamplePosition incSamples = nInc * (nSamples - 1);
		int32 nPosDest = (nPos + incSamples).GetInt();

		const SmpLength nPosInt = nPos.GetUInt();
		const bool isAtLoopStart = (nPosInt >= chn.nLoopStart && nPosInt < chn.nLoopStart + InterpolationLookaheadBufferSize);
		if(!isAtLoopStart)
		{
			chn.dwFlags.reset(CHN_WRAPPED_LOOP);
		}

		// Loop wrap-around magic.
		bool checkDest = true;
		if(lookaheadPointer != nullptr)
		{
			if(nPos.GetUInt() >= lookaheadStart)
			{
#if 0
				const uint32 oldCount = nSmpCount;

				// When going backwards - we can only go back up to lookaheadStart.
				// When going forwards - read through the whole pre-computed wrap-around buffer if possible.
				// TODO: ProTracker sample swapping needs hard cut at sample end.
				int32 samplesToRead = nInc.IsNegative()
					? (nPosInt - lookaheadStart)
					//: 2 * InterpolationMaxLookahead - (nPosInt - mixLoopState.lookaheadStart);
					: (chn.nLoopEnd - nPosInt);
				//LimitMax(samplesToRead, chn.nLoopEnd - chn.nLoopStart);
				nSmpCount = SamplesToBufferLength(samplesToRead, chn);
				Limit(nSmpCount, 1u, oldCount);
#else
				if (nInc.IsNegative())
				{
					nSmpCount = DistanceToBufferLength(SamplePosition(lookaheadStart, 0), nPos, nInv);
				} else
				{
					nSmpCount = DistanceToBufferLength(nPos, SamplePosition(chn.nLoopEnd, 0), nInv);
				}
#endif
				chn.pCurrentSample = lookaheadPointer;
				checkDest = false;
			} else if(chn.dwFlags[CHN_WRAPPED_LOOP] && isAtLoopStart)
			{
				// We just restarted the loop, so interpolate correctly after wrapping around
				nSmpCount = DistanceToBufferLength(nPos, SamplePosition(nLoopStart + InterpolationLookaheadBufferSize, 0), nInv);
				chn.pCurrentSample = lookaheadPointer + (chn.nLoopEnd - nLoopStart) * chn.pModSample->GetBytesPerSample();
				checkDest = false;
			} else if(nInc.IsPositive() && static_cast<SmpLength>(nPosDest) >= lookaheadStart && nSmpCount > 1)
			{
				// We shouldn't read that far if we're not using the pre-computed wrap-around buffer.
				nSmpCount = DistanceToBufferLength(nPos, SamplePosition(lookaheadStart, 0), nInv);
				checkDest = false;
			}
		}

		if(checkDest)
		{
			// Fix up sample count if target position is invalid
			if (nInc.IsNegative())
			{
				if (nPosDest < nLoopStart)
				{
					nSmpCount = DistanceToBufferLength(SamplePosition(nLoopStart, 0), nPos, nInv);
				}
			} else
			{
				if (nPosDest >= (int32)chn.nLength)
				{
					nSmpCount = DistanceToBufferLength(nPos, SamplePosition(chn.nLength, 0), nInv);
				}
			}
		}

		Limit(nSmpCount, uint32(1u), nSamples);

#ifdef MPT_BUILD_DEBUG
		{
			SmpLength posDest = (nPos + nInc * (nSmpCount - 1)).GetUInt();
			if (posDest < 0 || posDest > chn.nLength)
			{
				// We computed an invalid delta!
				MPT_ASSERT_NOTREACHED();
				return 0;
			}
		}
#endif

		return nSmpCount;
	}
};


// Render count * number of channels samples
void CSoundFile::CreateStereoMix(int count)
{
	mixsample_t *pOfsL, *pOfsR;

	if(!count)
		return;

	// Resetting sound buffer
	StereoFill(MixSoundBuffer, count, m_dryROfsVol, m_dryLOfsVol);
	if(m_MixerSettings.gnChannels > 2)
		StereoFill(MixRearBuffer, count, m_surroundROfsVol, m_surroundLOfsVol);

	CHANNELINDEX nchmixed = 0;

	for(uint32 nChn = 0; nChn < m_nMixChannels; nChn++)
	{
		ModChannel &chn = m_PlayState.Chn[m_PlayState.ChnMix[nChn]];

		if(!chn.pCurrentSample && !chn.nLOfs && !chn.nROfs)
			continue;

		pOfsR = &m_dryROfsVol;
		pOfsL = &m_dryLOfsVol;

		uint32 functionNdx = MixFuncTable::ResamplingModeToMixFlags(static_cast<ResamplingMode>(chn.resamplingMode));
		if(chn.dwFlags[CHN_16BIT]) functionNdx |= MixFuncTable::ndx16Bit;
		if(chn.dwFlags[CHN_STEREO]) functionNdx |= MixFuncTable::ndxStereo;
#ifndef NO_FILTER
		if(chn.dwFlags[CHN_FILTER]) functionNdx |= MixFuncTable::ndxFilter;
#endif

		mixsample_t *pbuffer = MixSoundBuffer;
#ifndef NO_REVERB
		if(((m_MixerSettings.DSPMask & SNDDSP_REVERB) && !chn.dwFlags[CHN_NOREVERB]) || chn.dwFlags[CHN_REVERB])
		{
			m_Reverb.TouchReverbSendBuffer(ReverbSendBuffer, m_RvbROfsVol, m_RvbLOfsVol, count);
			pbuffer = ReverbSendBuffer;
			pOfsR = &m_RvbROfsVol;
			pOfsL = &m_RvbLOfsVol;
		}
#endif
		if(chn.dwFlags[CHN_SURROUND] && m_MixerSettings.gnChannels > 2)
		{
			pbuffer = MixRearBuffer;
			pOfsR = &m_surroundROfsVol;
			pOfsL = &m_surroundLOfsVol;
		}

		//Look for plugins associated with this implicit tracker channel.
#ifndef NO_PLUGINS
		PLUGINDEX nMixPlugin = GetBestPlugin(m_PlayState, m_PlayState.ChnMix[nChn], PrioritiseInstrument, RespectMutes);

		if ((nMixPlugin > 0) && (nMixPlugin <= MAX_MIXPLUGINS) && m_MixPlugins[nMixPlugin - 1].pMixPlugin != nullptr)
		{
			// Render into plugin buffer instead of global buffer
			SNDMIXPLUGINSTATE &mixState = m_MixPlugins[nMixPlugin - 1].pMixPlugin->m_MixState;
			if (mixState.pMixBuffer)
			{
				pbuffer = mixState.pMixBuffer;
				pOfsR = &mixState.nVolDecayR;
				pOfsL = &mixState.nVolDecayL;
				if (!(mixState.dwFlags & SNDMIXPLUGINSTATE::psfMixReady))
				{
					StereoFill(pbuffer, count, *pOfsR, *pOfsL);
					mixState.dwFlags |= SNDMIXPLUGINSTATE::psfMixReady;
				}
			}
		}
#endif // NO_PLUGINS

		if(chn.isPaused)
		{
			EndChannelOfs(chn, pbuffer, count);
			*pOfsR += chn.nROfs;
			*pOfsL += chn.nLOfs;
			chn.nROfs = chn.nLOfs = 0;
			continue;
		}

		MixLoopState mixLoopState(*this, chn);

		////////////////////////////////////////////////////
		CHANNELINDEX naddmix = 0;
		int nsamples = count;
		// Keep mixing this sample until the buffer is filled.
		do
		{
			uint32 nrampsamples = nsamples;
			int32 nSmpCount;
			if(chn.nRampLength > 0)
			{
				if (nrampsamples > chn.nRampLength) nrampsamples = chn.nRampLength;
			}

			if((nSmpCount = mixLoopState.GetSampleCount(chn, nrampsamples)) <= 0)
			{
				// Stopping the channel
				chn.pCurrentSample = nullptr;
				chn.nLength = 0;
				chn.position.Set(0);
				chn.nRampLength = 0;
				EndChannelOfs(chn, pbuffer, nsamples);
				*pOfsR += chn.nROfs;
				*pOfsL += chn.nLOfs;
				chn.nROfs = chn.nLOfs = 0;
				chn.dwFlags.reset(CHN_PINGPONGFLAG);
				break;
			}

			// Should we mix this channel ?
			if((nchmixed >= m_MixerSettings.m_nMaxMixChannels)				// Too many channels
				|| (!chn.nRampLength && !(chn.leftVol | chn.rightVol)))		// Channel is completely silent
			{
				chn.position += chn.increment * nSmpCount;
				chn.nROfs = chn.nLOfs = 0;
				pbuffer += nSmpCount * 2;
				naddmix = 0;
			}
#ifdef MODPLUG_TRACKER
			else if(m_SamplePlayLengths != nullptr)
			{
				// Detecting the longest play time for each sample for optimization
				SmpLength pos = chn.position.GetUInt();
				chn.position += chn.increment * nSmpCount;
				if(!chn.increment.IsNegative())
				{
					pos = chn.position.GetUInt();
				}
				size_t smp = std::distance(static_cast<const ModSample*>(static_cast<std::decay<decltype(Samples)>::type>(Samples)), chn.pModSample);
				if(smp < m_SamplePlayLengths->size())
				{
					(*m_SamplePlayLengths)[smp] = std::max((*m_SamplePlayLengths)[smp], pos);
				}
			}
#endif
			else
			{
				// Do mixing
				mixsample_t *pbufmax = pbuffer + (nSmpCount * 2);
				chn.nROfs = -*(pbufmax - 2);
				chn.nLOfs = -*(pbufmax - 1);

#ifdef MPT_BUILD_DEBUG
				SamplePosition targetpos = chn.position + chn.increment * nSmpCount;
#endif
				MixFuncTable::Functions[functionNdx | (chn.nRampLength ? MixFuncTable::ndxRamp : 0)](chn, m_Resampler, pbuffer, nSmpCount);
#ifdef MPT_BUILD_DEBUG
				MPT_ASSERT(chn.position.GetUInt() == targetpos.GetUInt());
#endif

				chn.nROfs += *(pbufmax - 2);
				chn.nLOfs += *(pbufmax - 1);
				pbuffer = pbufmax;
				naddmix = 1;
			}

			nsamples -= nSmpCount;
			if (chn.nRampLength)
			{
				if (chn.nRampLength <= static_cast<uint32>(nSmpCount))
				{
					// Ramping is done
					chn.nRampLength = 0;
					chn.leftVol = chn.newLeftVol;
					chn.rightVol = chn.newRightVol;
					chn.rightRamp = chn.leftRamp = 0;
					if(chn.dwFlags[CHN_NOTEFADE] && !chn.nFadeOutVol)
					{
						chn.nLength = 0;
						chn.pCurrentSample = nullptr;
					}
				} else
				{
					chn.nRampLength -= nSmpCount;
				}
			}

			const bool pastLoopEnd = chn.position.GetUInt() >= chn.nLoopEnd && chn.dwFlags[CHN_LOOP];
			const bool pastSampleEnd = chn.position.GetUInt() >= chn.nLength && !chn.dwFlags[CHN_LOOP] && chn.nLength && !chn.nMasterChn;
			const bool doSampleSwap = m_playBehaviour[kMODSampleSwap] && chn.nNewIns && chn.nNewIns <= GetNumSamples() && chn.pModSample != &Samples[chn.nNewIns];
			if((pastLoopEnd || pastSampleEnd) && doSampleSwap)
			{
				// ProTracker compatibility: Instrument changes without a note do not happen instantly, but rather when the sample loop has finished playing.
				// Test case: PTInstrSwap.mod, PTSwapNoLoop.mod
				const ModSample &smp = Samples[chn.nNewIns];
				chn.pModSample = &smp;
				chn.pCurrentSample = smp.samplev();
				chn.dwFlags = (chn.dwFlags & CHN_CHANNELFLAGS) | smp.uFlags;
				chn.nLength = smp.uFlags[CHN_LOOP] ? smp.nLoopEnd : 0; // non-looping sample continue in oneshot mode (i.e. they will most probably just play silence)
				chn.nLoopStart = smp.nLoopStart;
				chn.nLoopEnd = smp.nLoopEnd;
				chn.position.SetInt(chn.nLoopStart);
				mixLoopState.UpdateLookaheadPointers(chn);
				if(!chn.pCurrentSample)
				{
					break;
				}
			} else if(pastLoopEnd && !doSampleSwap && m_playBehaviour[kMODOneShotLoops] && chn.nLoopStart == 0)
			{
				// ProTracker "oneshot" loops (if loop start is 0, play the whole sample once and then repeat until loop end)
				chn.position.SetInt(0);
				chn.nLoopEnd = chn.nLength = chn.pModSample->nLoopEnd;
			}
		} while(nsamples > 0);

		// Restore sample pointer in case it got changed through loop wrap-around
		chn.pCurrentSample = mixLoopState.samplePointer;
		nchmixed += naddmix;
	
#ifndef NO_PLUGINS
		if(naddmix && nMixPlugin > 0 && nMixPlugin <= MAX_MIXPLUGINS && m_MixPlugins[nMixPlugin - 1].pMixPlugin)
		{
			m_MixPlugins[nMixPlugin - 1].pMixPlugin->ResetSilence();
		}
#endif // NO_PLUGINS
	}
	m_nMixStat = std::max(m_nMixStat, nchmixed);
}


void CSoundFile::ProcessPlugins(uint32 nCount)
{
#ifndef NO_PLUGINS
	// If any sample channels are active or any plugin has some input, possibly suspended master plugins need to be woken up.
	bool masterHasInput = (m_nMixStat > 0);

#ifdef MPT_INTMIXER
	const float IntToFloat = m_PlayConfig.getIntToFloat();
	const float FloatToInt = m_PlayConfig.getFloatToInt();
#endif // MPT_INTMIXER

	// Setup float inputs from samples
	for(PLUGINDEX plug = 0; plug < MAX_MIXPLUGINS; plug++)
	{
		SNDMIXPLUGIN &plugin = m_MixPlugins[plug];
		if(plugin.pMixPlugin != nullptr
			&& plugin.pMixPlugin->m_MixState.pMixBuffer != nullptr
			&& plugin.pMixPlugin->m_mixBuffer.Ok())
		{
			IMixPlugin *mixPlug = plugin.pMixPlugin;
			SNDMIXPLUGINSTATE &state = mixPlug->m_MixState;

			//We should only ever reach this point if the song is playing.
			if (!mixPlug->IsSongPlaying())
			{
				//Plugin doesn't know it is in a song that is playing;
				//we must have added it during playback. Initialise it!
				mixPlug->NotifySongPlaying(true);
				mixPlug->Resume();
			}


			// Setup float input
			float *plugInputL = mixPlug->m_mixBuffer.GetInputBuffer(0);
			float *plugInputR = mixPlug->m_mixBuffer.GetInputBuffer(1);
			if (state.dwFlags & SNDMIXPLUGINSTATE::psfMixReady)
			{
#ifdef MPT_INTMIXER
				StereoMixToFloat(state.pMixBuffer, plugInputL, plugInputR, nCount, IntToFloat);
#else
				DeinterleaveStereo(state.pMixBuffer, plugInputL, plugInputR, nCount);
#endif // MPT_INTMIXER
			} else if (state.nVolDecayR || state.nVolDecayL)
			{
				StereoFill(state.pMixBuffer, nCount, state.nVolDecayR, state.nVolDecayL);
#ifdef MPT_INTMIXER
				StereoMixToFloat(state.pMixBuffer, plugInputL, plugInputR, nCount, IntToFloat);
#else
				DeinterleaveStereo(state.pMixBuffer, plugInputL, plugInputR, nCount);
#endif // MPT_INTMIXER
			} else
			{
				memset(plugInputL, 0, nCount * sizeof(plugInputL[0]));
				memset(plugInputR, 0, nCount * sizeof(plugInputR[0]));
			}
			state.dwFlags &= ~SNDMIXPLUGINSTATE::psfMixReady;
			
			if(!plugin.IsMasterEffect() && !(state.dwFlags & SNDMIXPLUGINSTATE::psfSilenceBypass))
			{
				masterHasInput = true;
			}
		}
	}
	// Convert mix buffer
#ifdef MPT_INTMIXER
	StereoMixToFloat(MixSoundBuffer, MixFloatBuffer[0], MixFloatBuffer[1], nCount, IntToFloat);
#else
	DeinterleaveStereo(MixSoundBuffer, MixFloatBuffer[0], MixFloatBuffer[1], nCount);
#endif // MPT_INTMIXER
	float *pMixL = MixFloatBuffer[0];
	float *pMixR = MixFloatBuffer[1];

	const bool positionChanged = HasPositionChanged();

	// Process Plugins
	for(PLUGINDEX plug = 0; plug < MAX_MIXPLUGINS; plug++)
	{
		SNDMIXPLUGIN &plugin = m_MixPlugins[plug];
		if (plugin.pMixPlugin != nullptr
			&& plugin.pMixPlugin->m_MixState.pMixBuffer != nullptr
			&& plugin.pMixPlugin->m_mixBuffer.Ok())
		{
			IMixPlugin *pObject = plugin.pMixPlugin;
			if(!plugin.IsMasterEffect() && !plugin.pMixPlugin->ShouldProcessSilence() && !(plugin.pMixPlugin->m_MixState.dwFlags & SNDMIXPLUGINSTATE::psfHasInput))
			{
				// If plugin has no inputs and isn't a master plugin, we shouldn't let it process silence if possible.
				// I have yet to encounter a VST plugin which actually sets this flag.
				bool hasInput = false;
				for(PLUGINDEX inPlug = 0; inPlug < plug; inPlug++)
				{
					if(m_MixPlugins[inPlug].GetOutputPlugin() == plug)
					{
						hasInput = true;
						break;
					}
				}
				if(!hasInput)
				{
					continue;
				}
			}

			bool isMasterMix = false;
			float *plugInputL = pObject->m_mixBuffer.GetInputBuffer(0);
			float *plugInputR = pObject->m_mixBuffer.GetInputBuffer(1);

			if (pMixL == plugInputL)
			{
				isMasterMix = true;
				pMixL = MixFloatBuffer[0];
				pMixR = MixFloatBuffer[1];
			}
			SNDMIXPLUGINSTATE &state = plugin.pMixPlugin->m_MixState;
			float *pOutL = pMixL;
			float *pOutR = pMixR;

			if (!plugin.IsOutputToMaster())
			{
				PLUGINDEX nOutput = plugin.GetOutputPlugin();
				if(nOutput > plug && nOutput < MAX_MIXPLUGINS
					&& m_MixPlugins[nOutput].pMixPlugin != nullptr)
				{
					IMixPlugin *outPlugin = m_MixPlugins[nOutput].pMixPlugin;
					if(!(state.dwFlags & SNDMIXPLUGINSTATE::psfSilenceBypass)) outPlugin->ResetSilence();

					if(outPlugin->m_mixBuffer.Ok())
					{
						pOutL = outPlugin->m_mixBuffer.GetInputBuffer(0);
						pOutR = outPlugin->m_mixBuffer.GetInputBuffer(1);
					}
				}
			}

			/*
			if (plugin.multiRouting) {
				int nOutput=0;
				for (int nOutput=0; nOutput < plugin.nOutputs / 2; nOutput++) {
					destinationPlug = plugin.multiRoutingDestinations[nOutput];
					pOutState = m_MixPlugins[destinationPlug].pMixState;
					pOutputs[2 * nOutput] = plugInputL;
					pOutputs[2 * (nOutput + 1)] = plugInputR;
				}

			}*/

			if (plugin.IsMasterEffect())
			{
				if (!isMasterMix)
				{
					float *pInL = plugInputL;
					float *pInR = plugInputR;
					for (uint32 i=0; i<nCount; i++)
					{
						pInL[i] += pMixL[i];
						pInR[i] += pMixR[i];
						pMixL[i] = 0;
						pMixR[i] = 0;
					}
				}
				pMixL = pOutL;
				pMixR = pOutR;

				if(masterHasInput)
				{
					// Samples or plugins are being rendered, so turn off auto-bypass for this master effect.
					if(plugin.pMixPlugin != nullptr) plugin.pMixPlugin->ResetSilence();
					SNDMIXPLUGIN *chain = &plugin;
					PLUGINDEX out = chain->GetOutputPlugin(), prevOut = plug;
					while(out > prevOut && out < MAX_MIXPLUGINS)
					{
						chain = &m_MixPlugins[out];
						prevOut = out;
						out = chain->GetOutputPlugin();
						if(chain->pMixPlugin)
						{
							chain->pMixPlugin->ResetSilence();
						}
					}
				}
			}

			if(plugin.IsBypassed() || (plugin.IsAutoSuspendable() && (state.dwFlags & SNDMIXPLUGINSTATE::psfSilenceBypass)))
			{
				const float * const pInL = plugInputL;
				const float * const pInR = plugInputR;
				for (uint32 i=0; i<nCount; i++)
				{
					pOutL[i] += pInL[i];
					pOutR[i] += pInR[i];
				}
			} else
			{
				if(positionChanged)
					pObject->PositionChanged();
				pObject->Process(pOutL, pOutR, nCount);

				state.inputSilenceCount += nCount;
				if(plugin.IsAutoSuspendable() && pObject->GetNumOutputChannels() > 0 && state.inputSilenceCount >= m_MixerSettings.gdwMixingFreq * 4)
				{
					bool isSilent = true;
					for(uint32 i = 0; i < nCount; i++)
					{
						if(pOutL[i] >= FLT_EPSILON || pOutL[i] <= -FLT_EPSILON
							|| pOutR[i] >= FLT_EPSILON || pOutR[i] <= -FLT_EPSILON)
						{
							isSilent = false;
							break;
						}
					}
					if(isSilent)
					{
						state.dwFlags |= SNDMIXPLUGINSTATE::psfSilenceBypass;
					} else
					{
						state.inputSilenceCount = 0;
					}
				}
			}
			state.dwFlags &= ~SNDMIXPLUGINSTATE::psfHasInput;
		}
	}
#ifdef MPT_INTMIXER
	FloatToStereoMix(pMixL, pMixR, MixSoundBuffer, nCount, FloatToInt);
#else
	InterleaveStereo(pMixL, pMixR, MixSoundBuffer, nCount);
#endif // MPT_INTMIXER

#else
	MPT_UNREFERENCED_PARAMETER(nCount);
#endif // NO_PLUGINS
}


OPENMPT_NAMESPACE_END
