123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581 |
- /*
- * SamplEdit.cpp
- * -------------
- * Purpose: Basic sample editing code (resizing, adding silence, normalizing, ...).
- * Notes : (currently none)
- * Authors: OpenMPT Devs
- * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
- */
- #include "stdafx.h"
- #include "SampleEdit.h"
- #include "../soundlib/AudioCriticalSection.h"
- #include "../soundlib/Sndfile.h"
- #include "../soundlib/modsmp_ctrl.h"
- #include "openmpt/soundbase/SampleConvert.hpp"
- #include "openmpt/soundbase/SampleDecode.hpp"
- #include "../soundlib/SampleCopy.h"
- OPENMPT_NAMESPACE_BEGIN
- namespace SampleEdit
- {
- std::vector<std::reference_wrapper<SmpLength>> GetCuesAndLoops(ModSample &smp)
- {
- std::vector<std::reference_wrapper<SmpLength>> loopPoints = {smp.nLoopStart, smp.nLoopEnd, smp.nSustainStart, smp.nSustainEnd};
- loopPoints.insert(loopPoints.end(), std::begin(smp.cues), std::end(smp.cues));
- return loopPoints;
- }
- SmpLength InsertSilence(ModSample &smp, const SmpLength silenceLength, const SmpLength startFrom, CSoundFile &sndFile)
- {
- if(silenceLength == 0 || silenceLength > MAX_SAMPLE_LENGTH || smp.nLength > MAX_SAMPLE_LENGTH - silenceLength || startFrom > smp.nLength)
- return smp.nLength;
- const bool wasEmpty = !smp.HasSampleData();
- const SmpLength newLength = smp.nLength + silenceLength;
- char *pNewSmp = static_cast<char *>(ModSample::AllocateSample(newLength, smp.GetBytesPerSample()));
- if(pNewSmp == nullptr)
- return smp.nLength; //Sample allocation failed.
- if(!wasEmpty)
- {
- // Copy over old sample
- const SmpLength silenceOffset = startFrom * smp.GetBytesPerSample();
- const SmpLength silenceBytes = silenceLength * smp.GetBytesPerSample();
- if(startFrom > 0)
- {
- memcpy(pNewSmp, smp.samplev(), silenceOffset);
- }
- if(startFrom < smp.nLength)
- {
- memcpy(pNewSmp + silenceOffset + silenceBytes, smp.sampleb() + silenceOffset, smp.GetSampleSizeInBytes() - silenceOffset);
- }
- // Update loop points if necessary.
- for(SmpLength &point : GetCuesAndLoops(smp))
- {
- if(point >= startFrom) point += silenceLength;
- }
- } else
- {
- // Set loop points automatically
- smp.nLoopStart = 0;
- smp.nLoopEnd = newLength;
- smp.uFlags.set(CHN_LOOP);
- }
- ctrlSmp::ReplaceSample(smp, pNewSmp, newLength, sndFile);
- smp.PrecomputeLoops(sndFile, true);
- return smp.nLength;
- }
- namespace
- {
- // Update loop points and cues after deleting a sample selection
- void AdjustLoopPoints(SmpLength selStart, SmpLength selEnd, SmpLength &loopStart, SmpLength &loopEnd, SmpLength length)
- {
- Util::DeleteRange(selStart, selEnd - 1, loopStart, loopEnd);
- LimitMax(loopEnd, length);
- if(loopStart + 2 >= loopEnd)
- {
- loopStart = loopEnd = 0;
- }
- }
- }
- SmpLength RemoveRange(ModSample &smp, SmpLength selStart, SmpLength selEnd, CSoundFile &sndFile)
- {
- LimitMax(selEnd, smp.nLength);
- if(selEnd <= selStart)
- {
- return smp.nLength;
- }
- const uint8 bps = smp.GetBytesPerSample();
- memmove(smp.sampleb() + selStart * bps, smp.sampleb() + selEnd * bps, (smp.nLength - selEnd) * bps);
- smp.nLength -= (selEnd - selStart);
- // Did loops or cue points cover the deleted selection?
- AdjustLoopPoints(selStart, selEnd, smp.nLoopStart, smp.nLoopEnd, smp.nLength);
- AdjustLoopPoints(selStart, selEnd, smp.nSustainStart, smp.nSustainEnd, smp.nLength);
- if(smp.nLoopEnd == 0) smp.uFlags.reset(CHN_LOOP | CHN_PINGPONGLOOP);
- if(smp.nSustainEnd == 0) smp.uFlags.reset(CHN_SUSTAINLOOP | CHN_PINGPONGSUSTAIN);
- for(auto &cue : smp.cues)
- {
- if(cue >= selEnd)
- cue -= (selEnd - selStart);
- else if(cue >= selStart && selStart == 0)
- cue = smp.nLength;
- else if(cue >= selStart)
- cue = selStart;
- }
- smp.PrecomputeLoops(sndFile);
- return smp.nLength;
- }
- SmpLength ResizeSample(ModSample &smp, const SmpLength newLength, CSoundFile &sndFile)
- {
- // Invalid sample size
- if(newLength > MAX_SAMPLE_LENGTH || newLength == smp.nLength)
- return smp.nLength;
- // New sample will be bigger so we'll just use "InsertSilence" as it's already there.
- if(newLength > smp.nLength)
- return InsertSilence(smp, newLength - smp.nLength, smp.nLength, sndFile);
- // Else: Shrink sample
- const SmpLength newSmpBytes = newLength * smp.GetBytesPerSample();
- void *newData = ModSample::AllocateSample(newLength, smp.GetBytesPerSample());
- if(newData == nullptr && newLength > 0)
- return smp.nLength; //Sample allocation failed.
- // Copy over old data and replace sample by the new one
- if(newData != nullptr)
- memcpy(newData, smp.sampleb(), newSmpBytes);
- ctrlSmp::ReplaceSample(smp, newData, newLength, sndFile);
- // Sanitize loops and update loop wrap-around buffers
- smp.PrecomputeLoops(sndFile);
- return smp.nLength;
- }
- void ResetSamples(CSoundFile &sndFile, ResetFlag resetflag, SAMPLEINDEX minSample, SAMPLEINDEX maxSample)
- {
- if(minSample == SAMPLEINDEX_INVALID)
- minSample = 1;
- if(maxSample == SAMPLEINDEX_INVALID)
- maxSample = sndFile.GetNumSamples();
- Limit(minSample, SAMPLEINDEX(1), SAMPLEINDEX(MAX_SAMPLES - 1));
- Limit(maxSample, SAMPLEINDEX(1), SAMPLEINDEX(MAX_SAMPLES - 1));
- if(minSample > maxSample)
- std::swap(minSample, maxSample);
- for(SAMPLEINDEX i = minSample; i <= maxSample; i++)
- {
- ModSample &sample = sndFile.GetSample(i);
- switch(resetflag)
- {
- case SmpResetInit:
- sndFile.m_szNames[i] = "";
- sample.filename = "";
- sample.nC5Speed = 8363;
- [[fallthrough]];
- case SmpResetCompo:
- sample.nPan = 128;
- sample.nGlobalVol = 64;
- sample.nVolume = 256;
- sample.nVibDepth = 0;
- sample.nVibRate = 0;
- sample.nVibSweep = 0;
- sample.nVibType = VIB_SINE;
- sample.uFlags.reset(CHN_PANNING | SMP_NODEFAULTVOLUME);
- break;
- case SmpResetVibrato:
- sample.nVibDepth = 0;
- sample.nVibRate = 0;
- sample.nVibSweep = 0;
- sample.nVibType = VIB_SINE;
- break;
- default:
- break;
- }
- }
- }
- namespace
- {
- struct OffsetData
- {
- double max = 0.0, min = 0.0, offset = 0.0;
- };
- // Returns maximum sample amplitude for given sample type (int8/int16).
- template <class T>
- constexpr double GetMaxAmplitude() {return 1.0 + (std::numeric_limits<T>::max)();}
- // Calculates DC offset and returns struct with DC offset, max and min values.
- // DC offset value is average of [-1.0, 1.0[-normalized offset values.
- template<class T>
- OffsetData CalculateOffset(const T *pStart, const SmpLength length)
- {
- OffsetData offsetVals;
- if(length < 1)
- return offsetVals;
- const double intToFloatScale = 1.0 / GetMaxAmplitude<T>();
- double max = -1, min = 1, sum = 0;
- const T *p = pStart;
- for(SmpLength i = 0; i < length; i++, p++)
- {
- const double val = static_cast<double>(*p) * intToFloatScale;
- sum += val;
- if(val > max) max = val;
- if(val < min) min = val;
- }
- offsetVals.max = max;
- offsetVals.min = min;
- offsetVals.offset = (-sum / (double)(length));
- return offsetVals;
- }
- template <class T>
- void RemoveOffsetAndNormalize(T *pStart, const SmpLength length, const double offset, const double amplify)
- {
- T *p = pStart;
- for(SmpLength i = 0; i < length; i++, p++)
- {
- double var = (*p) * amplify + offset;
- *p = mpt::saturate_round<T>(var);
- }
- }
- }
- // Remove DC offset
- double RemoveDCOffset(ModSample &smp, SmpLength start, SmpLength end, CSoundFile &sndFile)
- {
- if(!smp.HasSampleData())
- return 0;
- if(end > smp.nLength) end = smp.nLength;
- if(start > end) start = end;
- if(start == end)
- {
- start = 0;
- end = smp.nLength;
- }
- start *= smp.GetNumChannels();
- end *= smp.GetNumChannels();
- const double maxAmplitude = (smp.GetElementarySampleSize() == 2) ? GetMaxAmplitude<int16>() : GetMaxAmplitude<int8>();
- // step 1: Calculate offset.
- OffsetData oData;
- if(smp.GetElementarySampleSize() == 2)
- oData = CalculateOffset(smp.sample16() + start, end - start);
- else if(smp.GetElementarySampleSize() == 1)
- oData = CalculateOffset(smp.sample8() + start, end - start);
- else
- return 0;
- double offset = oData.offset;
- if((int)(offset * maxAmplitude) == 0)
- return 0;
- // those will be changed...
- oData.max += offset;
- oData.min += offset;
- // ... and that might cause distortion, so we will normalize this.
- const double amplify = 1 / std::max(oData.max, -oData.min);
- // step 2: centralize + normalize sample
- offset *= maxAmplitude * amplify;
- if(smp.GetElementarySampleSize() == 2)
- RemoveOffsetAndNormalize(smp.sample16() + start, end - start, offset, amplify);
- else if(smp.GetElementarySampleSize() == 1)
- RemoveOffsetAndNormalize(smp.sample8() + start, end - start, offset, amplify);
- // step 3: adjust global vol (if available)
- if((sndFile.GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) && (start == 0) && (end == smp.nLength * smp.GetNumChannels()))
- {
- CriticalSection cs;
- smp.nGlobalVol = std::min(mpt::saturate_round<uint16>(smp.nGlobalVol / amplify), uint16(64));
- for(auto &chn : sndFile.m_PlayState.Chn)
- {
- if(chn.pModSample == &smp)
- {
- chn.UpdateInstrumentVolume(&smp, chn.pModInstrument);
- }
- }
- }
- smp.PrecomputeLoops(sndFile, false);
- return oData.offset;
- }
- template<typename T>
- static void ApplyAmplifyImpl(T * MPT_RESTRICT pSample, const SmpLength length, const double amplifyStart, const double amplifyEnd, const bool isFadeIn, const Fade::Law fadeLaw)
- {
- Fade::Func fadeFunc = Fade::GetFadeFunc(fadeLaw);
- if(amplifyStart != amplifyEnd)
- {
- const double fadeOffset = isFadeIn ? amplifyStart : amplifyEnd;
- const double fadeDiff = isFadeIn ? (amplifyEnd - amplifyStart) : (amplifyStart - amplifyEnd);
- const double lengthInv = 1.0 / length;
- for(SmpLength i = 0; i < length; i++)
- {
- const double amp = fadeOffset + fadeFunc(static_cast<double>(isFadeIn ? i : (length - i)) * lengthInv) * fadeDiff;
- pSample[i] = mpt::saturate_round<T>(amp * pSample[i]);
- }
- } else
- {
- const double amp = fadeFunc(amplifyStart);
- for(SmpLength i = 0; i < length; i++)
- {
- pSample[i] = mpt::saturate_round<T>(amp * pSample[i]);
- }
- }
- }
- bool AmplifySample(ModSample &smp, SmpLength start, SmpLength end, double amplifyStart, double amplifyEnd, bool isFadeIn, Fade::Law fadeLaw, CSoundFile &sndFile)
- {
- if(!smp.HasSampleData()) return false;
- if(end == 0 || start >= end || end > smp.nLength)
- {
- start = 0;
- end = smp.nLength;
- }
- if(end - start < 2) return false;
- start *= smp.GetNumChannels();
- end *= smp.GetNumChannels();
- if (smp.GetElementarySampleSize() == 2)
- ApplyAmplifyImpl(smp.sample16() + start, end - start, amplifyStart, amplifyEnd, isFadeIn, fadeLaw);
- else if (smp.GetElementarySampleSize() == 1)
- ApplyAmplifyImpl(smp.sample8() + start, end - start, amplifyStart, amplifyEnd, isFadeIn, fadeLaw);
- else
- return false;
- smp.PrecomputeLoops(sndFile, false);
- return true;
- }
- // Reverse sample data
- bool ReverseSample(ModSample &smp, SmpLength start, SmpLength end, CSoundFile &sndFile)
- {
- return ctrlSmp::ReverseSample(smp, start, end, sndFile);
- }
- template <class T>
- static void UnsignSampleImpl(T *pStart, const SmpLength length)
- {
- const T offset = (std::numeric_limits<T>::min)();
- for(SmpLength i = 0; i < length; i++)
- {
- pStart[i] += offset;
- }
- }
- // Virtually unsign sample data
- bool UnsignSample(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)
- UnsignSampleImpl(smp.sample16() + start, end - start);
- else if(smp.GetElementarySampleSize() == 1)
- UnsignSampleImpl(smp.sample8() + start, end - start);
- else
- return false;
- smp.PrecomputeLoops(sndFile, false);
- return true;
- }
- // Invert sample data (flip by 180 degrees)
- bool InvertSample(ModSample &smp, SmpLength start, SmpLength end, CSoundFile &sndFile)
- {
- return ctrlSmp::InvertSample(smp, start, end, sndFile);
- }
- // Crossfade sample data to create smooth loops
- bool XFadeSample(ModSample &smp, SmpLength fadeLength, int fadeLaw, bool afterloopFade, bool useSustainLoop, CSoundFile &sndFile)
- {
- return ctrlSmp::XFadeSample(smp, fadeLength, fadeLaw, afterloopFade, useSustainLoop, sndFile);
- }
- template <class T>
- static void SilenceSampleImpl(T *p, SmpLength length, SmpLength inc, bool fromStart, bool toEnd)
- {
- const int dest = toEnd ? 0 : p[(length - 1) * inc];
- const int base = fromStart ? 0 :p[0];
- const int delta = dest - base;
- const int64 len_m1 = length - 1;
- for(SmpLength i = 0; i < length; i++)
- {
- int n = base + static_cast<int>((static_cast<int64>(delta) * static_cast<int64>(i)) / len_m1);
- *p = static_cast<T>(n);
- p += inc;
- }
- }
- // Silence parts of the sample data
- bool SilenceSample(ModSample &smp, SmpLength start, SmpLength end, CSoundFile &sndFile)
- {
- LimitMax(end, smp.nLength);
- if(!smp.HasSampleData() || start >= end) return false;
- const SmpLength length = end - start;
- const bool fromStart = start == 0;
- const bool toEnd = end == smp.nLength;
- const uint8 numChn = smp.GetNumChannels();
- for(uint8 chn = 0; chn < numChn; chn++)
- {
- if(smp.GetElementarySampleSize() == 2)
- SilenceSampleImpl(smp.sample16() + start * numChn + chn, length, numChn, fromStart, toEnd);
- else if(smp.GetElementarySampleSize() == 1)
- SilenceSampleImpl(smp.sample8() + start * numChn + chn, length, numChn, fromStart, toEnd);
- else
- return false;
- }
- smp.PrecomputeLoops(sndFile, false);
- return true;
- }
- template <class T>
- static void StereoSepSampleImpl(T *p, SmpLength length, int32 separation)
- {
- const int32 fac1 = static_cast<int32>(32768 + separation / 2), fac2 = static_cast<int32>(32768 - separation / 2);
- while(length--)
- {
- const int32 l = p[0], r = p[1];
- p[0] = mpt::saturate_cast<T>((Util::mul32to64(l, fac1) + Util::mul32to64(r, fac2)) >> 16);
- p[1] = mpt::saturate_cast<T>((Util::mul32to64(l, fac2) + Util::mul32to64(r, fac1)) >> 16);
- p += 2;
- }
- }
- // Change stereo separation
- bool StereoSepSample(ModSample &smp, SmpLength start, SmpLength end, double separation, CSoundFile &sndFile)
- {
- LimitMax(end, smp.nLength);
- if(!smp.HasSampleData() || start >= end || smp.GetNumChannels() != 2) return false;
- const SmpLength length = end - start;
- const uint8 numChn = smp.GetNumChannels();
- const int32 sep32 = mpt::saturate_round<int32>(separation * (65536.0 / 100.0));
- if(smp.GetElementarySampleSize() == 2)
- StereoSepSampleImpl(smp.sample16() + start * numChn, length, sep32);
- else if(smp.GetElementarySampleSize() == 1)
- StereoSepSampleImpl(smp.sample8() + start * numChn, length, sep32);
- else
- return false;
- smp.PrecomputeLoops(sndFile, false);
- return true;
- }
- // Convert 16-bit sample to 8-bit
- bool ConvertTo8Bit(ModSample &smp, CSoundFile &sndFile)
- {
- if(!smp.HasSampleData() || smp.GetElementarySampleSize() != 2)
- return false;
- CopySample<SC::ConversionChain<SC::Convert<int8, int16>, SC::DecodeIdentity<int16>>>(static_cast<int8 *>(smp.samplev()), smp.nLength * smp.GetNumChannels(), 1, smp.sample16(), smp.GetSampleSizeInBytes(), 1);
- smp.uFlags.reset(CHN_16BIT);
- for(auto &chn : sndFile.m_PlayState.Chn)
- {
- if(chn.pModSample == &smp)
- chn.dwFlags.reset(CHN_16BIT);
- }
- smp.PrecomputeLoops(sndFile, false);
- return true;
- }
- // Convert 8-bit sample to 16-bit
- bool ConvertTo16Bit(ModSample &smp, CSoundFile &sndFile)
- {
- if(!smp.HasSampleData() || smp.GetElementarySampleSize() != 1)
- return false;
- int16 *newSample = static_cast<int16 *>(ModSample::AllocateSample(smp.nLength, 2 * smp.GetNumChannels()));
- if(newSample == nullptr)
- return false;
- CopySample<SC::ConversionChain<SC::Convert<int16, int8>, SC::DecodeIdentity<int8>>>(newSample, smp.nLength * smp.GetNumChannels(), 1, smp.sample8(), smp.GetSampleSizeInBytes(), 1);
- smp.uFlags.set(CHN_16BIT);
- ctrlSmp::ReplaceSample(smp, newSample, smp.nLength, sndFile);
- smp.PrecomputeLoops(sndFile, false);
- return true;
- }
- template <class T>
- static void ConvertPingPongLoopImpl(T *pStart, SmpLength length)
- {
- auto *out = pStart, *in = pStart;
- while(length--)
- {
- *(out++) = *(--in);
- }
- }
- // Convert ping-pong loops to regular loops
- bool ConvertPingPongLoop(ModSample &smp, CSoundFile &sndFile, bool sustainLoop)
- {
- if(!smp.HasSampleData()
- || (!smp.HasPingPongLoop() && !sustainLoop)
- || (!smp.HasPingPongSustainLoop() && sustainLoop))
- return false;
- const SmpLength loopStart = sustainLoop ? smp.nSustainStart : smp.nLoopStart;
- const SmpLength loopEnd = sustainLoop ? smp.nSustainEnd : smp.nLoopEnd;
- const SmpLength oldLoopLength = loopEnd - loopStart;
- const SmpLength oldLength = smp.nLength;
- if(InsertSilence(smp, oldLoopLength, loopEnd, sndFile) <= oldLength)
- return false;
- static_assert(MaxSamplingPointSize <= 4);
- if(smp.GetBytesPerSample() == 4) // 16 bit stereo
- ConvertPingPongLoopImpl(static_cast<int32 *>(smp.samplev()) + loopEnd, oldLoopLength);
- else if(smp.GetBytesPerSample() == 2) // 16 bit mono / 8 bit stereo
- ConvertPingPongLoopImpl(static_cast<int16 *>(smp.samplev()) + loopEnd, oldLoopLength);
- else if(smp.GetBytesPerSample() == 1) // 8 bit mono
- ConvertPingPongLoopImpl(static_cast<int8 *>(smp.samplev()) + loopEnd, oldLoopLength);
- else
- return false;
- smp.uFlags.reset(sustainLoop ? CHN_PINGPONGSUSTAIN : CHN_PINGPONGLOOP);
- smp.PrecomputeLoops(sndFile, true);
- return true;
- }
- } // namespace SampleEdit
- OPENMPT_NAMESPACE_END
|