winamp/Src/external_dependencies/openmpt-trunk/soundlib/modsmp_ctrl.cpp
2024-09-24 14:54:57 +02:00

431 lines
12 KiB
C++

/*
* modsmp_ctrl.cpp
* ---------------
* Purpose: Basic sample editing code.
* Notes : This is a legacy namespace. Some of this stuff is not required in libopenmpt (but stuff in soundlib/ still depends on it). The rest could be merged into struct ModSample.
* Authors: OpenMPT Devs
* The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
*/
#include "stdafx.h"
#include "modsmp_ctrl.h"
#include "AudioCriticalSection.h"
#include "Sndfile.h"
OPENMPT_NAMESPACE_BEGIN
namespace ctrlSmp
{
void ReplaceSample(ModSample &smp, void *pNewSample, const SmpLength newLength, CSoundFile &sndFile)
{
void * const pOldSmp = smp.samplev();
FlagSet<ChannelFlags> setFlags, resetFlags;
setFlags.set(CHN_16BIT, smp.uFlags[CHN_16BIT]);
resetFlags.set(CHN_16BIT, !smp.uFlags[CHN_16BIT]);
setFlags.set(CHN_STEREO, smp.uFlags[CHN_STEREO]);
resetFlags.set(CHN_STEREO, !smp.uFlags[CHN_STEREO]);
CriticalSection cs;
ctrlChn::ReplaceSample(sndFile, smp, pNewSample, newLength, setFlags, resetFlags);
smp.pData.pSample = pNewSample;
smp.nLength = newLength;
ModSample::FreeSample(pOldSmp);
}
// Propagate loop point changes to player
bool UpdateLoopPoints(const ModSample &smp, CSoundFile &sndFile)
{
if(!smp.HasSampleData())
return false;
CriticalSection cs;
// Update channels with new loop values
for(auto &chn : sndFile.m_PlayState.Chn) if((chn.pModSample == &smp) && chn.nLength != 0)
{
bool looped = false, bidi = false;
if(smp.nSustainStart < smp.nSustainEnd && smp.nSustainEnd <= smp.nLength && smp.uFlags[CHN_SUSTAINLOOP] && !chn.dwFlags[CHN_KEYOFF])
{
// Sustain loop is active
chn.nLoopStart = smp.nSustainStart;
chn.nLoopEnd = smp.nSustainEnd;
chn.nLength = smp.nSustainEnd;
looped = true;
bidi = smp.uFlags[CHN_PINGPONGSUSTAIN];
} else if(smp.nLoopStart < smp.nLoopEnd && smp.nLoopEnd <= smp.nLength && smp.uFlags[CHN_LOOP])
{
// Normal loop is active
chn.nLoopStart = smp.nLoopStart;
chn.nLoopEnd = smp.nLoopEnd;
chn.nLength = smp.nLoopEnd;
looped = true;
bidi = smp.uFlags[CHN_PINGPONGLOOP];
}
chn.dwFlags.set(CHN_LOOP, looped);
chn.dwFlags.set(CHN_PINGPONGLOOP, looped && bidi);
if(chn.position.GetUInt() > chn.nLength)
{
chn.position.Set(chn.nLoopStart);
chn.dwFlags.reset(CHN_PINGPONGFLAG);
}
if(!bidi)
{
chn.dwFlags.reset(CHN_PINGPONGFLAG);
}
if(!looped)
{
chn.nLength = smp.nLength;
}
}
return true;
}
template <class T>
static void ReverseSampleImpl(T *pStart, const SmpLength length)
{
for(SmpLength i = 0; i < length / 2; i++)
{
std::swap(pStart[i], pStart[length - 1 - i]);
}
}
// Reverse sample data
bool ReverseSample(ModSample &smp, SmpLength start, SmpLength end, CSoundFile &sndFile)
{
if(!smp.HasSampleData()) return false;
if(end == 0 || start > smp.nLength || end > smp.nLength)
{
start = 0;
end = smp.nLength;
}
if(end - start < 2) return false;
static_assert(MaxSamplingPointSize <= 4);
if(smp.GetBytesPerSample() == 4) // 16 bit stereo
ReverseSampleImpl(static_cast<int32 *>(smp.samplev()) + start, end - start);
else if(smp.GetBytesPerSample() == 2) // 16 bit mono / 8 bit stereo
ReverseSampleImpl(static_cast<int16 *>(smp.samplev()) + start, end - start);
else if(smp.GetBytesPerSample() == 1) // 8 bit mono
ReverseSampleImpl(static_cast<int8 *>(smp.samplev()) + start, end - start);
else
return false;
smp.PrecomputeLoops(sndFile, false);
return true;
}
template <class T>
static void InvertSampleImpl(T *pStart, const SmpLength length)
{
for(SmpLength i = 0; i < length; i++)
{
pStart[i] = ~pStart[i];
}
}
// Invert sample data (flip by 180 degrees)
bool InvertSample(ModSample &smp, SmpLength start, SmpLength end, CSoundFile &sndFile)
{
if(!smp.HasSampleData()) return false;
if(end == 0 || start > smp.nLength || end > smp.nLength)
{
start = 0;
end = smp.nLength;
}
start *= smp.GetNumChannels();
end *= smp.GetNumChannels();
if(smp.GetElementarySampleSize() == 2)
InvertSampleImpl(smp.sample16() + start, end - start);
else if(smp.GetElementarySampleSize() == 1)
InvertSampleImpl(smp.sample8() + start, end - start);
else
return false;
smp.PrecomputeLoops(sndFile, false);
return true;
}
template <class T>
static void XFadeSampleImpl(const T *srcIn, const T *srcOut, T *output, const SmpLength fadeLength, double e)
{
const double length = 1.0 / static_cast<double>(fadeLength);
for(SmpLength i = 0; i < fadeLength; i++, srcIn++, srcOut++, output++)
{
double fact1 = std::pow(i * length, e);
double fact2 = std::pow((fadeLength - i) * length, e);
int32 val = static_cast<int32>(
static_cast<double>(*srcIn) * fact1 +
static_cast<double>(*srcOut) * fact2);
*output = mpt::saturate_cast<T>(val);
}
}
// X-Fade sample data to create smooth loop transitions
bool XFadeSample(ModSample &smp, SmpLength fadeLength, int fadeLaw, bool afterloopFade, bool useSustainLoop, CSoundFile &sndFile)
{
if(!smp.HasSampleData()) return false;
const SmpLength loopStart = useSustainLoop ? smp.nSustainStart : smp.nLoopStart;
const SmpLength loopEnd = useSustainLoop ? smp.nSustainEnd : smp.nLoopEnd;
if(loopEnd <= loopStart || loopEnd > smp.nLength) return false;
if(loopStart < fadeLength) return false;
const SmpLength start = (loopStart - fadeLength) * smp.GetNumChannels();
const SmpLength end = (loopEnd - fadeLength) * smp.GetNumChannels();
const SmpLength afterloopStart = loopStart * smp.GetNumChannels();
const SmpLength afterloopEnd = loopEnd * smp.GetNumChannels();
const SmpLength afterLoopLength = std::min(smp.nLength - loopEnd, fadeLength) * smp.GetNumChannels();
fadeLength *= smp.GetNumChannels();
// e=0.5: constant power crossfade (for uncorrelated samples), e=1.0: constant volume crossfade (for perfectly correlated samples)
const double e = 1.0 - fadeLaw / 200000.0;
if(smp.GetElementarySampleSize() == 2)
{
XFadeSampleImpl(smp.sample16() + start, smp.sample16() + end, smp.sample16() + end, fadeLength, e);
if(afterloopFade) XFadeSampleImpl(smp.sample16() + afterloopEnd, smp.sample16() + afterloopStart, smp.sample16() + afterloopEnd, afterLoopLength, e);
} else if(smp.GetElementarySampleSize() == 1)
{
XFadeSampleImpl(smp.sample8() + start, smp.sample8() + end, smp.sample8() + end, fadeLength, e);
if(afterloopFade) XFadeSampleImpl(smp.sample8() + afterloopEnd, smp.sample8() + afterloopStart, smp.sample8() + afterloopEnd, afterLoopLength, e);
} else
return false;
smp.PrecomputeLoops(sndFile, true);
return true;
}
template <class T>
static void ConvertStereoToMonoMixImpl(T *pDest, const SmpLength length)
{
const T *pEnd = pDest + length;
for(T *pSource = pDest; pDest != pEnd; pDest++, pSource += 2)
{
*pDest = static_cast<T>(mpt::rshift_signed(pSource[0] + pSource[1] + 1, 1));
}
}
template <class T>
static void ConvertStereoToMonoOneChannelImpl(T *pDest, const T *pSource, const SmpLength length)
{
for(const T *pEnd = pDest + length; pDest != pEnd; pDest++, pSource += 2)
{
*pDest = *pSource;
}
}
// Convert a multichannel sample to mono (currently only implemented for stereo)
bool ConvertToMono(ModSample &smp, CSoundFile &sndFile, StereoToMonoMode conversionMode)
{
if(!smp.HasSampleData() || smp.GetNumChannels() != 2) return false;
// Note: Sample is overwritten in-place! Unused data is not deallocated!
if(conversionMode == mixChannels)
{
if(smp.GetElementarySampleSize() == 2)
ConvertStereoToMonoMixImpl(smp.sample16(), smp.nLength);
else if(smp.GetElementarySampleSize() == 1)
ConvertStereoToMonoMixImpl(smp.sample8(), smp.nLength);
else
return false;
} else
{
if(conversionMode == splitSample)
{
conversionMode = onlyLeft;
}
if(smp.GetElementarySampleSize() == 2)
ConvertStereoToMonoOneChannelImpl(smp.sample16(), smp.sample16() + (conversionMode == onlyLeft ? 0 : 1), smp.nLength);
else if(smp.GetElementarySampleSize() == 1)
ConvertStereoToMonoOneChannelImpl(smp.sample8(), smp.sample8() + (conversionMode == onlyLeft ? 0 : 1), smp.nLength);
else
return false;
}
CriticalSection cs;
smp.uFlags.reset(CHN_STEREO);
for(auto &chn : sndFile.m_PlayState.Chn)
{
if(chn.pModSample == &smp)
{
chn.dwFlags.reset(CHN_STEREO);
}
}
smp.PrecomputeLoops(sndFile, false);
return true;
}
template <class T>
static void SplitStereoImpl(void *destL, void *destR, const T *source, SmpLength length)
{
T *l = static_cast<T *>(destL), *r = static_cast<T*>(destR);
while(length--)
{
*(l++) = source[0];
*(r++) = source[1];
source += 2;
}
}
// Converts a stereo sample into two mono samples. Source sample will not be deleted.
bool SplitStereo(const ModSample &source, ModSample &left, ModSample &right, CSoundFile &sndFile)
{
if(!source.HasSampleData() || source.GetNumChannels() != 2 || &left == &right)
return false;
const bool sourceIsLeft = &left == &source, sourceIsRight = &right == &source;
if(left.HasSampleData() && !sourceIsLeft)
return false;
if(right.HasSampleData() && !sourceIsRight)
return false;
void *leftData = sourceIsLeft ? left.samplev() : ModSample::AllocateSample(source.nLength, source.GetElementarySampleSize());
void *rightData = sourceIsRight ? right.samplev() : ModSample::AllocateSample(source.nLength, source.GetElementarySampleSize());
if(!leftData || !rightData)
{
if(!sourceIsLeft)
ModSample::FreeSample(leftData);
if(!sourceIsRight)
ModSample::FreeSample(rightData);
return false;
}
if(source.GetElementarySampleSize() == 2)
SplitStereoImpl(leftData, rightData, source.sample16(), source.nLength);
else if(source.GetElementarySampleSize() == 1)
SplitStereoImpl(leftData, rightData, source.sample8(), source.nLength);
else
MPT_ASSERT_NOTREACHED();
CriticalSection cs;
left = source;
left.uFlags.reset(CHN_STEREO);
left.pData.pSample = leftData;
right = source;
right.uFlags.reset(CHN_STEREO);
right.pData.pSample = rightData;
for(auto &chn : sndFile.m_PlayState.Chn)
{
if(chn.pModSample == &left || chn.pModSample == &right)
chn.dwFlags.reset(CHN_STEREO);
}
left.PrecomputeLoops(sndFile, false);
right.PrecomputeLoops(sndFile, false);
return true;
}
template <class T>
static void ConvertMonoToStereoImpl(const T *MPT_RESTRICT src, T *MPT_RESTRICT dst, SmpLength length)
{
while(length--)
{
dst[0] = *src;
dst[1] = *src;
dst += 2;
src++;
}
}
// Convert a multichannel sample to mono (currently only implemented for stereo)
bool ConvertToStereo(ModSample &smp, CSoundFile &sndFile)
{
if(!smp.HasSampleData() || smp.GetNumChannels() != 1) return false;
void *newSample = ModSample::AllocateSample(smp.nLength, smp.GetBytesPerSample() * 2);
if(newSample == nullptr)
{
return 0;
}
if(smp.GetElementarySampleSize() == 2)
ConvertMonoToStereoImpl(smp.sample16(), (int16 *)newSample, smp.nLength);
else if(smp.GetElementarySampleSize() == 1)
ConvertMonoToStereoImpl(smp.sample8(), (int8 *)newSample, smp.nLength);
else
return false;
CriticalSection cs;
smp.uFlags.set(CHN_STEREO);
ReplaceSample(smp, newSample, smp.nLength, sndFile);
smp.PrecomputeLoops(sndFile, false);
return true;
}
} // namespace ctrlSmp
namespace ctrlChn
{
void ReplaceSample( CSoundFile &sndFile,
const ModSample &sample,
const void * const pNewSample,
const SmpLength newLength,
FlagSet<ChannelFlags> setFlags,
FlagSet<ChannelFlags> resetFlags)
{
const bool periodIsFreq = sndFile.PeriodsAreFrequencies();
for(auto &chn : sndFile.m_PlayState.Chn)
{
if(chn.pModSample == &sample)
{
if(chn.pCurrentSample != nullptr)
chn.pCurrentSample = pNewSample;
if(chn.position.GetUInt() > newLength)
chn.position.Set(0);
if(chn.nLength > 0)
LimitMax(chn.nLength, newLength);
if(chn.InSustainLoop())
{
chn.nLoopStart = sample.nSustainStart;
chn.nLoopEnd = sample.nSustainEnd;
} else
{
chn.nLoopStart = sample.nLoopStart;
chn.nLoopEnd = sample.nLoopEnd;
}
chn.dwFlags.set(setFlags);
chn.dwFlags.reset(resetFlags);
if(chn.nC5Speed && sample.nC5Speed && !sndFile.UseFinetuneAndTranspose())
{
if(periodIsFreq)
chn.nPeriod = Util::muldivr_unsigned(chn.nPeriod, sample.nC5Speed, chn.nC5Speed);
else
chn.nPeriod = Util::muldivr_unsigned(chn.nPeriod, chn.nC5Speed, sample.nC5Speed);
}
chn.nC5Speed = sample.nC5Speed;
}
}
}
} // namespace ctrlChn
OPENMPT_NAMESPACE_END