123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579 |
- /*
- * ModSample.h
- * -----------
- * Purpose: Module Sample header class and helpers
- * 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 "Sndfile.h"
- #include "ModSample.h"
- #include "modsmp_ctrl.h"
- #include "mpt/base/numbers.hpp"
- #include <cmath>
- OPENMPT_NAMESPACE_BEGIN
- // Translate sample properties between two given formats.
- void ModSample::Convert(MODTYPE fromType, MODTYPE toType)
- {
- // Convert between frequency and transpose values if necessary.
- if((!(toType & (MOD_TYPE_MOD | MOD_TYPE_XM))) && (fromType & (MOD_TYPE_MOD | MOD_TYPE_XM)))
- {
- TransposeToFrequency();
- RelativeTone = 0;
- nFineTune = 0;
- // TransposeToFrequency assumes NTSC middle-C frequency like FT2, but we play MODs with PAL middle-C!
- if(fromType == MOD_TYPE_MOD)
- nC5Speed = Util::muldivr_unsigned(nC5Speed, 8272, 8363);
- } else if((toType & (MOD_TYPE_MOD | MOD_TYPE_XM)) && (!(fromType & (MOD_TYPE_MOD | MOD_TYPE_XM))))
- {
- // FrequencyToTranspose assumes NTSC middle-C frequency like FT2, but we play MODs with PAL middle-C!
- if(toType == MOD_TYPE_MOD)
- nC5Speed = Util::muldivr_unsigned(nC5Speed, 8363, 8272);
- FrequencyToTranspose();
- }
- // No ping-pong loop, panning and auto-vibrato for MOD / S3M samples
- if(toType & (MOD_TYPE_MOD | MOD_TYPE_S3M))
- {
- uFlags.reset(CHN_PINGPONGLOOP | CHN_PANNING);
- nVibDepth = 0;
- nVibRate = 0;
- nVibSweep = 0;
- nVibType = VIB_SINE;
- RelativeTone = 0;
- }
- // No global volume / sustain loops for MOD/S3M/XM
- if(toType & (MOD_TYPE_MOD | MOD_TYPE_XM | MOD_TYPE_S3M))
- {
- nGlobalVol = 64;
- // Sustain loops - convert to normal loops
- if(uFlags[CHN_SUSTAINLOOP])
- {
- // We probably overwrite a normal loop here, but since sustain loops are evaluated before normal loops, this is just correct.
- nLoopStart = nSustainStart;
- nLoopEnd = nSustainEnd;
- uFlags.set(CHN_LOOP);
- uFlags.set(CHN_PINGPONGLOOP, uFlags[CHN_PINGPONGSUSTAIN]);
- }
- nSustainStart = nSustainEnd = 0;
- uFlags.reset(CHN_SUSTAINLOOP | CHN_PINGPONGSUSTAIN);
- }
- // All XM samples have default panning, and XM's autovibrato settings are rather limited.
- if(toType & MOD_TYPE_XM)
- {
- if(!uFlags[CHN_PANNING])
- {
- uFlags.set(CHN_PANNING);
- nPan = 128;
- }
- LimitMax(nVibDepth, uint8(15));
- LimitMax(nVibRate, uint8(63));
- }
- // Autovibrato sweep setting is inverse in XM (0 = "no sweep") and IT (0 = "no vibrato")
- if(((fromType & MOD_TYPE_XM) && (toType & (MOD_TYPE_IT | MOD_TYPE_MPT))) || ((toType & MOD_TYPE_XM) && (fromType & (MOD_TYPE_IT | MOD_TYPE_MPT))))
- {
- if(nVibRate != 0 && nVibDepth != 0)
- {
- if(nVibSweep != 0)
- nVibSweep = mpt::saturate_cast<decltype(nVibSweep)>(Util::muldivr_unsigned(nVibDepth, 256, nVibSweep));
- else
- nVibSweep = 255;
- }
- }
- // Convert incompatible autovibrato types
- if(toType == MOD_TYPE_IT && nVibType == VIB_RAMP_UP)
- {
- nVibType = VIB_RAMP_DOWN;
- } else if(toType == MOD_TYPE_XM && nVibType == VIB_RANDOM)
- {
- nVibType = VIB_SINE;
- }
- // No external samples in formats other than MPTM.
- if(toType != MOD_TYPE_MPT)
- {
- uFlags.reset(SMP_KEEPONDISK);
- }
- // No Adlib instruments in formats that can't handle it.
- if(!CSoundFile::SupportsOPL(toType) && uFlags[CHN_ADLIB])
- {
- SetAdlib(false);
- } else if(toType == MOD_TYPE_S3M && uFlags[CHN_ADLIB])
- {
- // No support for OPL3 waveforms in S3M
- adlib[8] &= 0x03;
- adlib[9] &= 0x03;
- }
- }
- // Initialize sample slot with default values.
- void ModSample::Initialize(MODTYPE type)
- {
- FreeSample();
- nLength = 0;
- nLoopStart = nLoopEnd = 0;
- nSustainStart = nSustainEnd = 0;
- nC5Speed = 8363;
- nPan = 128;
- nVolume = 256;
- nGlobalVol = 64;
- uFlags.reset(CHN_PANNING | CHN_SUSTAINLOOP | CHN_LOOP | CHN_PINGPONGLOOP | CHN_PINGPONGSUSTAIN | CHN_ADLIB | SMP_MODIFIED | SMP_KEEPONDISK);
- if(type == MOD_TYPE_XM)
- {
- uFlags.set(CHN_PANNING);
- }
- RelativeTone = 0;
- nFineTune = 0;
- nVibType = VIB_SINE;
- nVibSweep = 0;
- nVibDepth = 0;
- nVibRate = 0;
- rootNote = 0;
- filename = "";
- RemoveAllCuePoints();
- }
- // Returns sample rate of the sample.
- uint32 ModSample::GetSampleRate(const MODTYPE type) const
- {
- uint32 rate;
- if(CSoundFile::UseFinetuneAndTranspose(type))
- rate = TransposeToFrequency(RelativeTone, nFineTune);
- else
- rate = nC5Speed;
- // TransposeToFrequency assumes NTSC middle-C frequency like FT2, but we play MODs with PAL middle-C!
- if(type == MOD_TYPE_MOD)
- rate = Util::muldivr_unsigned(rate, 8272, 8363);
- return (rate > 0) ? rate : 8363;
- }
- // Copies sample data from another sample slot and ensures that the 16-bit/stereo flags are set accordingly.
- bool ModSample::CopyWaveform(const ModSample &smpFrom)
- {
- if(!smpFrom.HasSampleData())
- return false;
- // If we duplicate a sample slot, avoid deleting the sample we just copy from
- if(smpFrom.sampleb() == sampleb())
- pData.pSample = nullptr;
- LimitMax(nLength, smpFrom.nLength);
- uFlags.set(CHN_16BIT, smpFrom.uFlags[CHN_16BIT]);
- uFlags.set(CHN_STEREO, smpFrom.uFlags[CHN_STEREO]);
- if(AllocateSample())
- {
- memcpy(sampleb(), smpFrom.sampleb(), GetSampleSizeInBytes());
- return true;
- }
- return false;
- }
- // Allocate sample based on a ModSample's properties.
- // Returns number of bytes allocated, 0 on failure.
- size_t ModSample::AllocateSample()
- {
- FreeSample();
- if((pData.pSample = AllocateSample(nLength, GetBytesPerSample())) == nullptr)
- {
- return 0;
- } else
- {
- return GetSampleSizeInBytes();
- }
- }
- // Allocate sample memory. On success, a pointer to the silenced sample buffer is returned. On failure, nullptr is returned.
- // numFrames must contain the sample length, bytesPerSample the size of a sampling point multiplied with the number of channels.
- void *ModSample::AllocateSample(SmpLength numFrames, size_t bytesPerSample)
- {
- const size_t allocSize = GetRealSampleBufferSize(numFrames, bytesPerSample);
- if(allocSize != 0)
- {
- char *p = new(std::nothrow) char[allocSize];
- if(p != nullptr)
- {
- memset(p, 0, allocSize);
- return p + (InterpolationLookaheadBufferSize * MaxSamplingPointSize);
- }
- }
- return nullptr;
- }
- // Compute sample buffer size in bytes, including any overhead introduced by pre-computed loops and such. Returns 0 if sample is too big.
- size_t ModSample::GetRealSampleBufferSize(SmpLength numSamples, size_t bytesPerSample)
- {
- // Number of required lookahead samples:
- // * 1x InterpolationMaxLookahead samples before the actual sample start. This is set to MaxSamplingPointSize due to the way AllocateSample/FreeSample currently work.
- // * 1x InterpolationMaxLookahead samples of silence after the sample end (if normal loop end == sample end, this can be optimized out).
- // * 2x InterpolationMaxLookahead before the loop point (because we start at InterpolationMaxLookahead before the loop point and will look backwards from there as well)
- // * 2x InterpolationMaxLookahead after the loop point (for wrap-around)
- // * 4x InterpolationMaxLookahead for the sustain loop (same as the two points above)
- const SmpLength maxSize = Util::MaxValueOfType(numSamples);
- const SmpLength lookaheadBufferSize = (MaxSamplingPointSize + 1 + 4 + 4) * InterpolationLookaheadBufferSize;
- if(numSamples == 0 || numSamples > MAX_SAMPLE_LENGTH || lookaheadBufferSize > maxSize - numSamples)
- {
- return 0;
- }
- numSamples += lookaheadBufferSize;
- if(maxSize / bytesPerSample < numSamples)
- {
- return 0;
- }
- return numSamples * bytesPerSample;
- }
- void ModSample::FreeSample()
- {
- FreeSample(pData.pSample);
- pData.pSample = nullptr;
- }
- void ModSample::FreeSample(void *samplePtr)
- {
- if(samplePtr)
- {
- delete[](((char *)samplePtr) - (InterpolationLookaheadBufferSize * MaxSamplingPointSize));
- }
- }
- // Set loop points and update loop wrap-around buffer
- void ModSample::SetLoop(SmpLength start, SmpLength end, bool enable, bool pingpong, CSoundFile &sndFile)
- {
- nLoopStart = start;
- nLoopEnd = end;
- LimitMax(nLoopEnd, nLength);
- if(nLoopStart < nLoopEnd)
- {
- uFlags.set(CHN_LOOP, enable);
- uFlags.set(CHN_PINGPONGLOOP, pingpong && enable);
- } else
- {
- nLoopStart = nLoopEnd = 0;
- uFlags.reset(CHN_LOOP | CHN_PINGPONGLOOP);
- }
- PrecomputeLoops(sndFile, true);
- }
- // Set sustain loop points and update loop wrap-around buffer
- void ModSample::SetSustainLoop(SmpLength start, SmpLength end, bool enable, bool pingpong, CSoundFile &sndFile)
- {
- nSustainStart = start;
- nSustainEnd = end;
- LimitMax(nLoopEnd, nLength);
- if(nSustainStart < nSustainEnd)
- {
- uFlags.set(CHN_SUSTAINLOOP, enable);
- uFlags.set(CHN_PINGPONGSUSTAIN, pingpong && enable);
- } else
- {
- nSustainStart = nSustainEnd = 0;
- uFlags.reset(CHN_SUSTAINLOOP | CHN_PINGPONGSUSTAIN);
- }
- PrecomputeLoops(sndFile, true);
- }
- namespace // Unnamed namespace for local implementation functions.
- {
- template <typename T>
- class PrecomputeLoop
- {
- protected:
- T *target;
- const T *sampleData;
- SmpLength loopEnd;
- int numChannels;
- bool pingpong;
- bool ITPingPongMode;
- public:
- PrecomputeLoop(T *target, const T *sampleData, SmpLength loopEnd, int numChannels, bool pingpong, bool ITPingPongMode)
- : target(target), sampleData(sampleData), loopEnd(loopEnd), numChannels(numChannels), pingpong(pingpong), ITPingPongMode(ITPingPongMode)
- {
- if(loopEnd > 0)
- {
- CopyLoop(true);
- CopyLoop(false);
- }
- }
- void CopyLoop(bool direction) const
- {
- // Direction: true = start reading and writing forward, false = start reading and writing backward (write direction never changes)
- const int numSamples = 2 * InterpolationLookaheadBufferSize + (direction ? 1 : 0); // Loop point is included in forward loop expansion
- T *dest = target + numChannels * (2 * InterpolationLookaheadBufferSize - 1); // Write buffer offset
- SmpLength readPosition = loopEnd - 1;
- const int writeIncrement = direction ? 1 : -1;
- int readIncrement = writeIncrement;
- for(int i = 0; i < numSamples; i++)
- {
- // Copy sample over to lookahead buffer
- for(int c = 0; c < numChannels; c++)
- {
- dest[c] = sampleData[readPosition * numChannels + c];
- }
- dest += writeIncrement * numChannels;
- if(readPosition == loopEnd - 1 && readIncrement > 0)
- {
- // Reached end of loop while going forward
- if(pingpong)
- {
- readIncrement = -1;
- if(ITPingPongMode && readPosition > 0)
- {
- readPosition--;
- }
- } else
- {
- readPosition = 0;
- }
- } else if(readPosition == 0 && readIncrement < 0)
- {
- // Reached start of loop while going backward
- if(pingpong)
- {
- readIncrement = 1;
- } else
- {
- readPosition = loopEnd - 1;
- }
- } else
- {
- readPosition += readIncrement;
- }
- }
- }
- };
- template <typename T>
- void PrecomputeLoopsImpl(ModSample &smp, const CSoundFile &sndFile)
- {
- const int numChannels = smp.GetNumChannels();
- const int copySamples = numChannels * InterpolationLookaheadBufferSize;
- T *sampleData = static_cast<T *>(smp.samplev());
- T *afterSampleStart = sampleData + smp.nLength * numChannels;
- T *loopLookAheadStart = afterSampleStart + copySamples;
- T *sustainLookAheadStart = loopLookAheadStart + 4 * copySamples;
- // Hold sample on the same level as the last sampling point at the end to prevent extra pops with interpolation.
- // Do the same at the sample start, too.
- for(int i = 0; i < (int)InterpolationLookaheadBufferSize; i++)
- {
- for(int c = 0; c < numChannels; c++)
- {
- afterSampleStart[i * numChannels + c] = afterSampleStart[-numChannels + c];
- sampleData[-(i + 1) * numChannels + c] = sampleData[c];
- }
- }
- if(smp.uFlags[CHN_LOOP])
- {
- PrecomputeLoop<T>(loopLookAheadStart,
- sampleData + smp.nLoopStart * numChannels,
- smp.nLoopEnd - smp.nLoopStart,
- numChannels,
- smp.uFlags[CHN_PINGPONGLOOP],
- sndFile.m_playBehaviour[kITPingPongMode]);
- }
- if(smp.uFlags[CHN_SUSTAINLOOP])
- {
- PrecomputeLoop<T>(sustainLookAheadStart,
- sampleData + smp.nSustainStart * numChannels,
- smp.nSustainEnd - smp.nSustainStart,
- numChannels,
- smp.uFlags[CHN_PINGPONGSUSTAIN],
- sndFile.m_playBehaviour[kITPingPongMode]);
- }
- }
- } // unnamed namespace
- void ModSample::PrecomputeLoops(CSoundFile &sndFile, bool updateChannels)
- {
- if(!HasSampleData())
- return;
- SanitizeLoops();
- // Update channels with possibly changed loop values
- if(updateChannels)
- {
- ctrlSmp::UpdateLoopPoints(*this, sndFile);
- }
- if(GetElementarySampleSize() == 2)
- PrecomputeLoopsImpl<int16>(*this, sndFile);
- else if(GetElementarySampleSize() == 1)
- PrecomputeLoopsImpl<int8>(*this, sndFile);
- }
- // Remove loop points if they're invalid.
- void ModSample::SanitizeLoops()
- {
- LimitMax(nSustainEnd, nLength);
- LimitMax(nLoopEnd, nLength);
- if(nSustainStart >= nSustainEnd)
- {
- nSustainStart = nSustainEnd = 0;
- uFlags.reset(CHN_SUSTAINLOOP | CHN_PINGPONGSUSTAIN);
- }
- if(nLoopStart >= nLoopEnd)
- {
- nLoopStart = nLoopEnd = 0;
- uFlags.reset(CHN_LOOP | CHN_PINGPONGLOOP);
- }
- }
- /////////////////////////////////////////////////////////////
- // Transpose <-> Frequency conversions
- uint32 ModSample::TransposeToFrequency(int transpose, int finetune)
- {
- return mpt::saturate_round<uint32>(std::pow(2.0, (transpose * 128.0 + finetune) * (1.0 / (12.0 * 128.0))) * 8363.0);
- }
- void ModSample::TransposeToFrequency()
- {
- nC5Speed = TransposeToFrequency(RelativeTone, nFineTune);
- }
- // Return a pair of {transpose, finetune}
- std::pair<int8, int8> ModSample::FrequencyToTranspose(uint32 freq)
- {
- if(!freq)
- return {};
- const auto f2t = mpt::saturate_round<int32>(std::log(freq * (1.0 / 8363.0)) * (12.0 * 128.0 * (1.0 / mpt::numbers::ln2)));
- const auto fine = std::div(Clamp(f2t, -16384, 16383), int32(128));
- return {static_cast<int8>(fine.quot), static_cast<int8>(fine.rem)};
- }
- void ModSample::FrequencyToTranspose()
- {
- std::tie(RelativeTone, nFineTune) = FrequencyToTranspose(nC5Speed);
- }
- // Transpose the sample by amount specified in octaves (i.e. amount=1 transposes one octave up)
- void ModSample::Transpose(double amount)
- {
- nC5Speed = mpt::saturate_round<uint32>(nC5Speed * std::pow(2.0, amount));
- }
- // Check if the sample has any valid cue points
- bool ModSample::HasAnyCuePoints() const
- {
- if(uFlags[CHN_ADLIB])
- return false;
- for(auto pt : cues)
- {
- if(pt < nLength)
- return true;
- }
- return false;
- }
- // Check if the sample's cue points are the default cue point set.
- bool ModSample::HasCustomCuePoints() const
- {
- if(uFlags[CHN_ADLIB])
- return false;
- for(SmpLength i = 0; i < std::size(cues); i++)
- {
- if(cues[i] != (i + 1) << 11)
- return true;
- }
- return false;
- }
- void ModSample::SetDefaultCuePoints()
- {
- // Default cues compatible with old-style volume column offset
- for(int i = 0; i < 9; i++)
- {
- cues[i] = (i + 1) << 11;
- }
- }
- void ModSample::Set16BitCuePoints()
- {
- // Cue points that are useful for extending regular offset command
- for(int i = 0; i < 9; i++)
- {
- cues[i] = (i + 1) << 16;
- }
- }
- void ModSample::RemoveAllCuePoints()
- {
- if(!uFlags[CHN_ADLIB])
- cues.fill(MAX_SAMPLE_LENGTH);
- }
- void ModSample::SetAdlib(bool enable, OPLPatch patch)
- {
- if(!enable && uFlags[CHN_ADLIB])
- {
- SetDefaultCuePoints();
- }
- uFlags.set(CHN_ADLIB, enable);
- if(enable)
- {
- // Bogus sample to make playback work
- uFlags.reset(CHN_16BIT | CHN_STEREO);
- nLength = 4;
- AllocateSample();
- adlib = patch;
- }
- }
- OPENMPT_NAMESPACE_END
|