1
0

SampleEdit.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. /*
  2. * SamplEdit.cpp
  3. * -------------
  4. * Purpose: Basic sample editing code (resizing, adding silence, normalizing, ...).
  5. * Notes : (currently none)
  6. * Authors: OpenMPT Devs
  7. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  8. */
  9. #include "stdafx.h"
  10. #include "SampleEdit.h"
  11. #include "../soundlib/AudioCriticalSection.h"
  12. #include "../soundlib/Sndfile.h"
  13. #include "../soundlib/modsmp_ctrl.h"
  14. #include "openmpt/soundbase/SampleConvert.hpp"
  15. #include "openmpt/soundbase/SampleDecode.hpp"
  16. #include "../soundlib/SampleCopy.h"
  17. OPENMPT_NAMESPACE_BEGIN
  18. namespace SampleEdit
  19. {
  20. std::vector<std::reference_wrapper<SmpLength>> GetCuesAndLoops(ModSample &smp)
  21. {
  22. std::vector<std::reference_wrapper<SmpLength>> loopPoints = {smp.nLoopStart, smp.nLoopEnd, smp.nSustainStart, smp.nSustainEnd};
  23. loopPoints.insert(loopPoints.end(), std::begin(smp.cues), std::end(smp.cues));
  24. return loopPoints;
  25. }
  26. SmpLength InsertSilence(ModSample &smp, const SmpLength silenceLength, const SmpLength startFrom, CSoundFile &sndFile)
  27. {
  28. if(silenceLength == 0 || silenceLength > MAX_SAMPLE_LENGTH || smp.nLength > MAX_SAMPLE_LENGTH - silenceLength || startFrom > smp.nLength)
  29. return smp.nLength;
  30. const bool wasEmpty = !smp.HasSampleData();
  31. const SmpLength newLength = smp.nLength + silenceLength;
  32. char *pNewSmp = static_cast<char *>(ModSample::AllocateSample(newLength, smp.GetBytesPerSample()));
  33. if(pNewSmp == nullptr)
  34. return smp.nLength; //Sample allocation failed.
  35. if(!wasEmpty)
  36. {
  37. // Copy over old sample
  38. const SmpLength silenceOffset = startFrom * smp.GetBytesPerSample();
  39. const SmpLength silenceBytes = silenceLength * smp.GetBytesPerSample();
  40. if(startFrom > 0)
  41. {
  42. memcpy(pNewSmp, smp.samplev(), silenceOffset);
  43. }
  44. if(startFrom < smp.nLength)
  45. {
  46. memcpy(pNewSmp + silenceOffset + silenceBytes, smp.sampleb() + silenceOffset, smp.GetSampleSizeInBytes() - silenceOffset);
  47. }
  48. // Update loop points if necessary.
  49. for(SmpLength &point : GetCuesAndLoops(smp))
  50. {
  51. if(point >= startFrom) point += silenceLength;
  52. }
  53. } else
  54. {
  55. // Set loop points automatically
  56. smp.nLoopStart = 0;
  57. smp.nLoopEnd = newLength;
  58. smp.uFlags.set(CHN_LOOP);
  59. }
  60. ctrlSmp::ReplaceSample(smp, pNewSmp, newLength, sndFile);
  61. smp.PrecomputeLoops(sndFile, true);
  62. return smp.nLength;
  63. }
  64. namespace
  65. {
  66. // Update loop points and cues after deleting a sample selection
  67. void AdjustLoopPoints(SmpLength selStart, SmpLength selEnd, SmpLength &loopStart, SmpLength &loopEnd, SmpLength length)
  68. {
  69. Util::DeleteRange(selStart, selEnd - 1, loopStart, loopEnd);
  70. LimitMax(loopEnd, length);
  71. if(loopStart + 2 >= loopEnd)
  72. {
  73. loopStart = loopEnd = 0;
  74. }
  75. }
  76. }
  77. SmpLength RemoveRange(ModSample &smp, SmpLength selStart, SmpLength selEnd, CSoundFile &sndFile)
  78. {
  79. LimitMax(selEnd, smp.nLength);
  80. if(selEnd <= selStart)
  81. {
  82. return smp.nLength;
  83. }
  84. const uint8 bps = smp.GetBytesPerSample();
  85. memmove(smp.sampleb() + selStart * bps, smp.sampleb() + selEnd * bps, (smp.nLength - selEnd) * bps);
  86. smp.nLength -= (selEnd - selStart);
  87. // Did loops or cue points cover the deleted selection?
  88. AdjustLoopPoints(selStart, selEnd, smp.nLoopStart, smp.nLoopEnd, smp.nLength);
  89. AdjustLoopPoints(selStart, selEnd, smp.nSustainStart, smp.nSustainEnd, smp.nLength);
  90. if(smp.nLoopEnd == 0) smp.uFlags.reset(CHN_LOOP | CHN_PINGPONGLOOP);
  91. if(smp.nSustainEnd == 0) smp.uFlags.reset(CHN_SUSTAINLOOP | CHN_PINGPONGSUSTAIN);
  92. for(auto &cue : smp.cues)
  93. {
  94. if(cue >= selEnd)
  95. cue -= (selEnd - selStart);
  96. else if(cue >= selStart && selStart == 0)
  97. cue = smp.nLength;
  98. else if(cue >= selStart)
  99. cue = selStart;
  100. }
  101. smp.PrecomputeLoops(sndFile);
  102. return smp.nLength;
  103. }
  104. SmpLength ResizeSample(ModSample &smp, const SmpLength newLength, CSoundFile &sndFile)
  105. {
  106. // Invalid sample size
  107. if(newLength > MAX_SAMPLE_LENGTH || newLength == smp.nLength)
  108. return smp.nLength;
  109. // New sample will be bigger so we'll just use "InsertSilence" as it's already there.
  110. if(newLength > smp.nLength)
  111. return InsertSilence(smp, newLength - smp.nLength, smp.nLength, sndFile);
  112. // Else: Shrink sample
  113. const SmpLength newSmpBytes = newLength * smp.GetBytesPerSample();
  114. void *newData = ModSample::AllocateSample(newLength, smp.GetBytesPerSample());
  115. if(newData == nullptr && newLength > 0)
  116. return smp.nLength; //Sample allocation failed.
  117. // Copy over old data and replace sample by the new one
  118. if(newData != nullptr)
  119. memcpy(newData, smp.sampleb(), newSmpBytes);
  120. ctrlSmp::ReplaceSample(smp, newData, newLength, sndFile);
  121. // Sanitize loops and update loop wrap-around buffers
  122. smp.PrecomputeLoops(sndFile);
  123. return smp.nLength;
  124. }
  125. void ResetSamples(CSoundFile &sndFile, ResetFlag resetflag, SAMPLEINDEX minSample, SAMPLEINDEX maxSample)
  126. {
  127. if(minSample == SAMPLEINDEX_INVALID)
  128. minSample = 1;
  129. if(maxSample == SAMPLEINDEX_INVALID)
  130. maxSample = sndFile.GetNumSamples();
  131. Limit(minSample, SAMPLEINDEX(1), SAMPLEINDEX(MAX_SAMPLES - 1));
  132. Limit(maxSample, SAMPLEINDEX(1), SAMPLEINDEX(MAX_SAMPLES - 1));
  133. if(minSample > maxSample)
  134. std::swap(minSample, maxSample);
  135. for(SAMPLEINDEX i = minSample; i <= maxSample; i++)
  136. {
  137. ModSample &sample = sndFile.GetSample(i);
  138. switch(resetflag)
  139. {
  140. case SmpResetInit:
  141. sndFile.m_szNames[i] = "";
  142. sample.filename = "";
  143. sample.nC5Speed = 8363;
  144. [[fallthrough]];
  145. case SmpResetCompo:
  146. sample.nPan = 128;
  147. sample.nGlobalVol = 64;
  148. sample.nVolume = 256;
  149. sample.nVibDepth = 0;
  150. sample.nVibRate = 0;
  151. sample.nVibSweep = 0;
  152. sample.nVibType = VIB_SINE;
  153. sample.uFlags.reset(CHN_PANNING | SMP_NODEFAULTVOLUME);
  154. break;
  155. case SmpResetVibrato:
  156. sample.nVibDepth = 0;
  157. sample.nVibRate = 0;
  158. sample.nVibSweep = 0;
  159. sample.nVibType = VIB_SINE;
  160. break;
  161. default:
  162. break;
  163. }
  164. }
  165. }
  166. namespace
  167. {
  168. struct OffsetData
  169. {
  170. double max = 0.0, min = 0.0, offset = 0.0;
  171. };
  172. // Returns maximum sample amplitude for given sample type (int8/int16).
  173. template <class T>
  174. constexpr double GetMaxAmplitude() {return 1.0 + (std::numeric_limits<T>::max)();}
  175. // Calculates DC offset and returns struct with DC offset, max and min values.
  176. // DC offset value is average of [-1.0, 1.0[-normalized offset values.
  177. template<class T>
  178. OffsetData CalculateOffset(const T *pStart, const SmpLength length)
  179. {
  180. OffsetData offsetVals;
  181. if(length < 1)
  182. return offsetVals;
  183. const double intToFloatScale = 1.0 / GetMaxAmplitude<T>();
  184. double max = -1, min = 1, sum = 0;
  185. const T *p = pStart;
  186. for(SmpLength i = 0; i < length; i++, p++)
  187. {
  188. const double val = static_cast<double>(*p) * intToFloatScale;
  189. sum += val;
  190. if(val > max) max = val;
  191. if(val < min) min = val;
  192. }
  193. offsetVals.max = max;
  194. offsetVals.min = min;
  195. offsetVals.offset = (-sum / (double)(length));
  196. return offsetVals;
  197. }
  198. template <class T>
  199. void RemoveOffsetAndNormalize(T *pStart, const SmpLength length, const double offset, const double amplify)
  200. {
  201. T *p = pStart;
  202. for(SmpLength i = 0; i < length; i++, p++)
  203. {
  204. double var = (*p) * amplify + offset;
  205. *p = mpt::saturate_round<T>(var);
  206. }
  207. }
  208. }
  209. // Remove DC offset
  210. double RemoveDCOffset(ModSample &smp, SmpLength start, SmpLength end, CSoundFile &sndFile)
  211. {
  212. if(!smp.HasSampleData())
  213. return 0;
  214. if(end > smp.nLength) end = smp.nLength;
  215. if(start > end) start = end;
  216. if(start == end)
  217. {
  218. start = 0;
  219. end = smp.nLength;
  220. }
  221. start *= smp.GetNumChannels();
  222. end *= smp.GetNumChannels();
  223. const double maxAmplitude = (smp.GetElementarySampleSize() == 2) ? GetMaxAmplitude<int16>() : GetMaxAmplitude<int8>();
  224. // step 1: Calculate offset.
  225. OffsetData oData;
  226. if(smp.GetElementarySampleSize() == 2)
  227. oData = CalculateOffset(smp.sample16() + start, end - start);
  228. else if(smp.GetElementarySampleSize() == 1)
  229. oData = CalculateOffset(smp.sample8() + start, end - start);
  230. else
  231. return 0;
  232. double offset = oData.offset;
  233. if((int)(offset * maxAmplitude) == 0)
  234. return 0;
  235. // those will be changed...
  236. oData.max += offset;
  237. oData.min += offset;
  238. // ... and that might cause distortion, so we will normalize this.
  239. const double amplify = 1 / std::max(oData.max, -oData.min);
  240. // step 2: centralize + normalize sample
  241. offset *= maxAmplitude * amplify;
  242. if(smp.GetElementarySampleSize() == 2)
  243. RemoveOffsetAndNormalize(smp.sample16() + start, end - start, offset, amplify);
  244. else if(smp.GetElementarySampleSize() == 1)
  245. RemoveOffsetAndNormalize(smp.sample8() + start, end - start, offset, amplify);
  246. // step 3: adjust global vol (if available)
  247. if((sndFile.GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) && (start == 0) && (end == smp.nLength * smp.GetNumChannels()))
  248. {
  249. CriticalSection cs;
  250. smp.nGlobalVol = std::min(mpt::saturate_round<uint16>(smp.nGlobalVol / amplify), uint16(64));
  251. for(auto &chn : sndFile.m_PlayState.Chn)
  252. {
  253. if(chn.pModSample == &smp)
  254. {
  255. chn.UpdateInstrumentVolume(&smp, chn.pModInstrument);
  256. }
  257. }
  258. }
  259. smp.PrecomputeLoops(sndFile, false);
  260. return oData.offset;
  261. }
  262. template<typename T>
  263. static void ApplyAmplifyImpl(T * MPT_RESTRICT pSample, const SmpLength length, const double amplifyStart, const double amplifyEnd, const bool isFadeIn, const Fade::Law fadeLaw)
  264. {
  265. Fade::Func fadeFunc = Fade::GetFadeFunc(fadeLaw);
  266. if(amplifyStart != amplifyEnd)
  267. {
  268. const double fadeOffset = isFadeIn ? amplifyStart : amplifyEnd;
  269. const double fadeDiff = isFadeIn ? (amplifyEnd - amplifyStart) : (amplifyStart - amplifyEnd);
  270. const double lengthInv = 1.0 / length;
  271. for(SmpLength i = 0; i < length; i++)
  272. {
  273. const double amp = fadeOffset + fadeFunc(static_cast<double>(isFadeIn ? i : (length - i)) * lengthInv) * fadeDiff;
  274. pSample[i] = mpt::saturate_round<T>(amp * pSample[i]);
  275. }
  276. } else
  277. {
  278. const double amp = fadeFunc(amplifyStart);
  279. for(SmpLength i = 0; i < length; i++)
  280. {
  281. pSample[i] = mpt::saturate_round<T>(amp * pSample[i]);
  282. }
  283. }
  284. }
  285. bool AmplifySample(ModSample &smp, SmpLength start, SmpLength end, double amplifyStart, double amplifyEnd, bool isFadeIn, Fade::Law fadeLaw, CSoundFile &sndFile)
  286. {
  287. if(!smp.HasSampleData()) return false;
  288. if(end == 0 || start >= end || end > smp.nLength)
  289. {
  290. start = 0;
  291. end = smp.nLength;
  292. }
  293. if(end - start < 2) return false;
  294. start *= smp.GetNumChannels();
  295. end *= smp.GetNumChannels();
  296. if (smp.GetElementarySampleSize() == 2)
  297. ApplyAmplifyImpl(smp.sample16() + start, end - start, amplifyStart, amplifyEnd, isFadeIn, fadeLaw);
  298. else if (smp.GetElementarySampleSize() == 1)
  299. ApplyAmplifyImpl(smp.sample8() + start, end - start, amplifyStart, amplifyEnd, isFadeIn, fadeLaw);
  300. else
  301. return false;
  302. smp.PrecomputeLoops(sndFile, false);
  303. return true;
  304. }
  305. // Reverse sample data
  306. bool ReverseSample(ModSample &smp, SmpLength start, SmpLength end, CSoundFile &sndFile)
  307. {
  308. return ctrlSmp::ReverseSample(smp, start, end, sndFile);
  309. }
  310. template <class T>
  311. static void UnsignSampleImpl(T *pStart, const SmpLength length)
  312. {
  313. const T offset = (std::numeric_limits<T>::min)();
  314. for(SmpLength i = 0; i < length; i++)
  315. {
  316. pStart[i] += offset;
  317. }
  318. }
  319. // Virtually unsign sample data
  320. bool UnsignSample(ModSample &smp, SmpLength start, SmpLength end, CSoundFile &sndFile)
  321. {
  322. if(!smp.HasSampleData()) return false;
  323. if(end == 0 || start > smp.nLength || end > smp.nLength)
  324. {
  325. start = 0;
  326. end = smp.nLength;
  327. }
  328. start *= smp.GetNumChannels();
  329. end *= smp.GetNumChannels();
  330. if(smp.GetElementarySampleSize() == 2)
  331. UnsignSampleImpl(smp.sample16() + start, end - start);
  332. else if(smp.GetElementarySampleSize() == 1)
  333. UnsignSampleImpl(smp.sample8() + start, end - start);
  334. else
  335. return false;
  336. smp.PrecomputeLoops(sndFile, false);
  337. return true;
  338. }
  339. // Invert sample data (flip by 180 degrees)
  340. bool InvertSample(ModSample &smp, SmpLength start, SmpLength end, CSoundFile &sndFile)
  341. {
  342. return ctrlSmp::InvertSample(smp, start, end, sndFile);
  343. }
  344. // Crossfade sample data to create smooth loops
  345. bool XFadeSample(ModSample &smp, SmpLength fadeLength, int fadeLaw, bool afterloopFade, bool useSustainLoop, CSoundFile &sndFile)
  346. {
  347. return ctrlSmp::XFadeSample(smp, fadeLength, fadeLaw, afterloopFade, useSustainLoop, sndFile);
  348. }
  349. template <class T>
  350. static void SilenceSampleImpl(T *p, SmpLength length, SmpLength inc, bool fromStart, bool toEnd)
  351. {
  352. const int dest = toEnd ? 0 : p[(length - 1) * inc];
  353. const int base = fromStart ? 0 :p[0];
  354. const int delta = dest - base;
  355. const int64 len_m1 = length - 1;
  356. for(SmpLength i = 0; i < length; i++)
  357. {
  358. int n = base + static_cast<int>((static_cast<int64>(delta) * static_cast<int64>(i)) / len_m1);
  359. *p = static_cast<T>(n);
  360. p += inc;
  361. }
  362. }
  363. // Silence parts of the sample data
  364. bool SilenceSample(ModSample &smp, SmpLength start, SmpLength end, CSoundFile &sndFile)
  365. {
  366. LimitMax(end, smp.nLength);
  367. if(!smp.HasSampleData() || start >= end) return false;
  368. const SmpLength length = end - start;
  369. const bool fromStart = start == 0;
  370. const bool toEnd = end == smp.nLength;
  371. const uint8 numChn = smp.GetNumChannels();
  372. for(uint8 chn = 0; chn < numChn; chn++)
  373. {
  374. if(smp.GetElementarySampleSize() == 2)
  375. SilenceSampleImpl(smp.sample16() + start * numChn + chn, length, numChn, fromStart, toEnd);
  376. else if(smp.GetElementarySampleSize() == 1)
  377. SilenceSampleImpl(smp.sample8() + start * numChn + chn, length, numChn, fromStart, toEnd);
  378. else
  379. return false;
  380. }
  381. smp.PrecomputeLoops(sndFile, false);
  382. return true;
  383. }
  384. template <class T>
  385. static void StereoSepSampleImpl(T *p, SmpLength length, int32 separation)
  386. {
  387. const int32 fac1 = static_cast<int32>(32768 + separation / 2), fac2 = static_cast<int32>(32768 - separation / 2);
  388. while(length--)
  389. {
  390. const int32 l = p[0], r = p[1];
  391. p[0] = mpt::saturate_cast<T>((Util::mul32to64(l, fac1) + Util::mul32to64(r, fac2)) >> 16);
  392. p[1] = mpt::saturate_cast<T>((Util::mul32to64(l, fac2) + Util::mul32to64(r, fac1)) >> 16);
  393. p += 2;
  394. }
  395. }
  396. // Change stereo separation
  397. bool StereoSepSample(ModSample &smp, SmpLength start, SmpLength end, double separation, CSoundFile &sndFile)
  398. {
  399. LimitMax(end, smp.nLength);
  400. if(!smp.HasSampleData() || start >= end || smp.GetNumChannels() != 2) return false;
  401. const SmpLength length = end - start;
  402. const uint8 numChn = smp.GetNumChannels();
  403. const int32 sep32 = mpt::saturate_round<int32>(separation * (65536.0 / 100.0));
  404. if(smp.GetElementarySampleSize() == 2)
  405. StereoSepSampleImpl(smp.sample16() + start * numChn, length, sep32);
  406. else if(smp.GetElementarySampleSize() == 1)
  407. StereoSepSampleImpl(smp.sample8() + start * numChn, length, sep32);
  408. else
  409. return false;
  410. smp.PrecomputeLoops(sndFile, false);
  411. return true;
  412. }
  413. // Convert 16-bit sample to 8-bit
  414. bool ConvertTo8Bit(ModSample &smp, CSoundFile &sndFile)
  415. {
  416. if(!smp.HasSampleData() || smp.GetElementarySampleSize() != 2)
  417. return false;
  418. 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);
  419. smp.uFlags.reset(CHN_16BIT);
  420. for(auto &chn : sndFile.m_PlayState.Chn)
  421. {
  422. if(chn.pModSample == &smp)
  423. chn.dwFlags.reset(CHN_16BIT);
  424. }
  425. smp.PrecomputeLoops(sndFile, false);
  426. return true;
  427. }
  428. // Convert 8-bit sample to 16-bit
  429. bool ConvertTo16Bit(ModSample &smp, CSoundFile &sndFile)
  430. {
  431. if(!smp.HasSampleData() || smp.GetElementarySampleSize() != 1)
  432. return false;
  433. int16 *newSample = static_cast<int16 *>(ModSample::AllocateSample(smp.nLength, 2 * smp.GetNumChannels()));
  434. if(newSample == nullptr)
  435. return false;
  436. CopySample<SC::ConversionChain<SC::Convert<int16, int8>, SC::DecodeIdentity<int8>>>(newSample, smp.nLength * smp.GetNumChannels(), 1, smp.sample8(), smp.GetSampleSizeInBytes(), 1);
  437. smp.uFlags.set(CHN_16BIT);
  438. ctrlSmp::ReplaceSample(smp, newSample, smp.nLength, sndFile);
  439. smp.PrecomputeLoops(sndFile, false);
  440. return true;
  441. }
  442. template <class T>
  443. static void ConvertPingPongLoopImpl(T *pStart, SmpLength length)
  444. {
  445. auto *out = pStart, *in = pStart;
  446. while(length--)
  447. {
  448. *(out++) = *(--in);
  449. }
  450. }
  451. // Convert ping-pong loops to regular loops
  452. bool ConvertPingPongLoop(ModSample &smp, CSoundFile &sndFile, bool sustainLoop)
  453. {
  454. if(!smp.HasSampleData()
  455. || (!smp.HasPingPongLoop() && !sustainLoop)
  456. || (!smp.HasPingPongSustainLoop() && sustainLoop))
  457. return false;
  458. const SmpLength loopStart = sustainLoop ? smp.nSustainStart : smp.nLoopStart;
  459. const SmpLength loopEnd = sustainLoop ? smp.nSustainEnd : smp.nLoopEnd;
  460. const SmpLength oldLoopLength = loopEnd - loopStart;
  461. const SmpLength oldLength = smp.nLength;
  462. if(InsertSilence(smp, oldLoopLength, loopEnd, sndFile) <= oldLength)
  463. return false;
  464. static_assert(MaxSamplingPointSize <= 4);
  465. if(smp.GetBytesPerSample() == 4) // 16 bit stereo
  466. ConvertPingPongLoopImpl(static_cast<int32 *>(smp.samplev()) + loopEnd, oldLoopLength);
  467. else if(smp.GetBytesPerSample() == 2) // 16 bit mono / 8 bit stereo
  468. ConvertPingPongLoopImpl(static_cast<int16 *>(smp.samplev()) + loopEnd, oldLoopLength);
  469. else if(smp.GetBytesPerSample() == 1) // 8 bit mono
  470. ConvertPingPongLoopImpl(static_cast<int8 *>(smp.samplev()) + loopEnd, oldLoopLength);
  471. else
  472. return false;
  473. smp.uFlags.reset(sustainLoop ? CHN_PINGPONGSUSTAIN : CHN_PINGPONGLOOP);
  474. smp.PrecomputeLoops(sndFile, true);
  475. return true;
  476. }
  477. } // namespace SampleEdit
  478. OPENMPT_NAMESPACE_END