123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759 |
- /*
- * 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;
- const SmpLength nPosInt = nPos.GetUInt();
- if(nPos.GetInt() < nLoopStart)
- {
- // too big increment, and/or too small loop length
- if(nPos.IsNegative() || nInc.IsNegative())
- return 0;
- } else
- {
- // Not testing for equality since we might be going backwards from the very end of the sample
- if(nPosInt > chn.nLength)
- return 0;
- // If going forwards and we're preceisely at the end, there's no point in going further
- if(nPosInt == chn.nLength && nInc.IsPositive())
- 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 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(nPosInt >= 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
|