diff options
author | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
---|---|---|
committer | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
commit | 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch) | |
tree | 12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/external_dependencies/openmpt-trunk/soundlib/Sndfile.cpp | |
parent | 537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff) | |
download | winamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz |
Initial community commit
Diffstat (limited to 'Src/external_dependencies/openmpt-trunk/soundlib/Sndfile.cpp')
-rw-r--r-- | Src/external_dependencies/openmpt-trunk/soundlib/Sndfile.cpp | 2083 |
1 files changed, 2083 insertions, 0 deletions
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/Sndfile.cpp b/Src/external_dependencies/openmpt-trunk/soundlib/Sndfile.cpp new file mode 100644 index 00000000..e3c87cd7 --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/soundlib/Sndfile.cpp @@ -0,0 +1,2083 @@ +/* + * Sndfile.cpp + * ----------- + * Purpose: Core class of the playback engine. Every song is represented by a CSoundFile object. + * Notes : (currently none) + * Authors: Olivier Lapicque + * OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#include "stdafx.h" +#ifdef MODPLUG_TRACKER +#include "../mptrack/Mptrack.h" // For CTrackApp::OpenURL +#include "../mptrack/TrackerSettings.h" +#include "../mptrack/Moddoc.h" +#include "../mptrack/Reporting.h" +#include "../mptrack/Mainfrm.h" +#endif // MODPLUG_TRACKER +#ifdef MPT_EXTERNAL_SAMPLES +#include "../common/mptFileIO.h" +#endif // MPT_EXTERNAL_SAMPLES +#include "../common/version.h" +#include "../soundlib/AudioCriticalSection.h" +#include "../common/serialization_utils.h" +#include "Sndfile.h" +#include "Tables.h" +#include "mod_specifications.h" +#include "tuningcollection.h" +#include "plugins/PluginManager.h" +#include "plugins/PlugInterface.h" +#include "../common/mptStringBuffer.h" +#include "../common/FileReader.h" +#include "Container.h" +#include "OPL.h" +#include "mpt/io/io.hpp" +#include "mpt/io/io_stdstream.hpp" + +#ifndef NO_ARCHIVE_SUPPORT +#include "../unarchiver/unarchiver.h" +#endif // NO_ARCHIVE_SUPPORT + + +OPENMPT_NAMESPACE_BEGIN + + +bool SettingCacheCompleteFileBeforeLoading() +{ + #ifdef MODPLUG_TRACKER + return TrackerSettings::Instance().MiscCacheCompleteFileBeforeLoading; + #else + return false; + #endif +} + + +mpt::ustring FileHistory::AsISO8601() const +{ + tm date = loadDate; + if(openTime > 0) + { + // Calculate the date when editing finished. + double openSeconds = static_cast<double>(openTime) / HISTORY_TIMER_PRECISION; + tm tmpLoadDate = loadDate; + int64 loadDateSinceEpoch = mpt::Date::Unix::FromUTC(tmpLoadDate); + int64 saveDateSinceEpoch = loadDateSinceEpoch + mpt::saturate_round<int64>(openSeconds); + date = mpt::Date::Unix(saveDateSinceEpoch).AsUTC(); + } + return mpt::Date::ToShortenedISO8601(date); +} + + +CSoundFile::PlayState::PlayState() +{ + std::fill(std::begin(Chn), std::end(Chn), ModChannel{}); + m_midiMacroScratchSpace.reserve(kMacroLength); // Note: If macros ever become variable-length, the scratch space needs to be at least one byte longer than the longest macro in the file for end-of-SysEx insertion to stay allocation-free in the mixer! +} + + +////////////////////////////////////////////////////////// +// CSoundFile + +#ifdef MODPLUG_TRACKER +const NoteName *CSoundFile::m_NoteNames = NoteNamesFlat; +#endif + +CSoundFile::CSoundFile() : +#ifndef MODPLUG_TRACKER + m_NoteNames(NoteNamesSharp), +#endif + m_pModSpecs(&ModSpecs::itEx), + m_nType(MOD_TYPE_NONE), + Patterns(*this), +#ifdef MODPLUG_TRACKER + m_MIDIMapper(*this), +#endif + Order(*this), + m_PRNG(mpt::make_prng<mpt::fast_prng>(mpt::global_prng())), + m_visitedRows(*this) +{ + MemsetZero(MixSoundBuffer); + MemsetZero(MixRearBuffer); + MemsetZero(MixFloatBuffer); + +#ifdef MODPLUG_TRACKER + m_bChannelMuteTogglePending.reset(); + + m_nDefaultRowsPerBeat = m_PlayState.m_nCurrentRowsPerBeat = (TrackerSettings::Instance().m_nRowHighlightBeats) ? TrackerSettings::Instance().m_nRowHighlightBeats : 4; + m_nDefaultRowsPerMeasure = m_PlayState.m_nCurrentRowsPerMeasure = (TrackerSettings::Instance().m_nRowHighlightMeasures >= m_nDefaultRowsPerBeat) ? TrackerSettings::Instance().m_nRowHighlightMeasures : m_nDefaultRowsPerBeat * 4; +#else + m_nDefaultRowsPerBeat = m_PlayState.m_nCurrentRowsPerBeat = 4; + m_nDefaultRowsPerMeasure = m_PlayState.m_nCurrentRowsPerMeasure = 16; +#endif // MODPLUG_TRACKER + + MemsetZero(Instruments); + Clear(m_szNames); + + m_pTuningsTuneSpecific = new CTuningCollection(); +} + + +CSoundFile::~CSoundFile() +{ + Destroy(); + delete m_pTuningsTuneSpecific; + m_pTuningsTuneSpecific = nullptr; +} + + +void CSoundFile::AddToLog(LogLevel level, const mpt::ustring &text) const +{ + if(m_pCustomLog) + { + m_pCustomLog->AddToLog(level, text); + } else + { + #ifdef MODPLUG_TRACKER + if(GetpModDoc()) GetpModDoc()->AddToLog(level, text); + #else + MPT_LOG_GLOBAL(level, "soundlib", text); + #endif + } +} + + +// Global variable initializer for loader functions +void CSoundFile::InitializeGlobals(MODTYPE type) +{ + // Do not add or change any of these values! And if you do, review each and every loader to check if they require these defaults! + m_nType = type; + + MODTYPE bestType = GetBestSaveFormat(); + m_playBehaviour = GetDefaultPlaybackBehaviour(bestType); + SetModSpecsPointer(m_pModSpecs, bestType); + + // Delete instruments in case some previously called loader already created them. + for(INSTRUMENTINDEX i = 1; i <= m_nInstruments; i++) + { + delete Instruments[i]; + Instruments[i] = nullptr; + } + + m_ContainerType = MOD_CONTAINERTYPE_NONE; + m_nChannels = 0; + m_nInstruments = 0; + m_nSamples = 0; + m_nSamplePreAmp = 48; + m_nVSTiVolume = 48; + m_OPLVolumeFactor = m_OPLVolumeFactorScale; + m_nDefaultSpeed = 6; + m_nDefaultTempo.Set(125); + m_nDefaultGlobalVolume = MAX_GLOBAL_VOLUME; + m_SongFlags.reset(); + m_nMinPeriod = 16; + m_nMaxPeriod = 32767; + m_nResampling = SRCMODE_DEFAULT; + m_dwLastSavedWithVersion = Version(0); + m_dwCreatedWithVersion = Version(0); + + SetMixLevels(MixLevels::Compatible); + + Patterns.ClearPatterns(); + Order.Initialize(); + + m_songName.clear(); + m_songArtist.clear(); + m_songMessage.clear(); + m_modFormat = ModFormatDetails(); + m_FileHistory.clear(); + m_tempoSwing.clear(); +#ifdef MPT_EXTERNAL_SAMPLES + m_samplePaths.clear(); +#endif // MPT_EXTERNAL_SAMPLES + + // Note: we do not use the Amiga resampler for DBM as it's a multichannel format and can make use of higher-quality Amiga soundcards instead of Paula. + if(GetType() & (/*MOD_TYPE_DBM | */MOD_TYPE_DIGI | MOD_TYPE_MED | MOD_TYPE_MOD | MOD_TYPE_OKT | MOD_TYPE_SFX | MOD_TYPE_STP)) + { + m_SongFlags.set(SONG_ISAMIGA); + } +} + + +void CSoundFile::InitializeChannels() +{ + for(CHANNELINDEX nChn = 0; nChn < MAX_BASECHANNELS; nChn++) + { + InitChannel(nChn); + } +} + + +struct FileFormatLoader +{ + decltype(CSoundFile::ProbeFileHeaderXM) *prober; + decltype(&CSoundFile::ReadXM) loader; +}; + +#ifdef MODPLUG_TRACKER +#define MPT_DECLARE_FORMAT(format) { nullptr, &CSoundFile::Read ## format } +#else +#define MPT_DECLARE_FORMAT(format) { CSoundFile::ProbeFileHeader ## format, &CSoundFile::Read ## format } +#endif + +// All module format loaders, in the order they should be executed. +// This order matters, depending on the format, due to some unfortunate +// clashes or lack of magic bytes that can lead to mis-detection of some formats. +// Apart from that, more common formats with sane magic bytes are also found +// at the top of the list to match the most common cases more quickly. +static constexpr FileFormatLoader ModuleFormatLoaders[] = +{ + MPT_DECLARE_FORMAT(XM), + MPT_DECLARE_FORMAT(IT), + MPT_DECLARE_FORMAT(S3M), + MPT_DECLARE_FORMAT(STM), + MPT_DECLARE_FORMAT(MED), + MPT_DECLARE_FORMAT(MTM), + MPT_DECLARE_FORMAT(MDL), + MPT_DECLARE_FORMAT(DBM), + MPT_DECLARE_FORMAT(FAR), + MPT_DECLARE_FORMAT(AMS), + MPT_DECLARE_FORMAT(AMS2), + MPT_DECLARE_FORMAT(OKT), + MPT_DECLARE_FORMAT(PTM), + MPT_DECLARE_FORMAT(ULT), + MPT_DECLARE_FORMAT(DMF), + MPT_DECLARE_FORMAT(DSM), + MPT_DECLARE_FORMAT(AMF_Asylum), + MPT_DECLARE_FORMAT(AMF_DSMI), + MPT_DECLARE_FORMAT(PSM), + MPT_DECLARE_FORMAT(PSM16), + MPT_DECLARE_FORMAT(MT2), + MPT_DECLARE_FORMAT(ITP), +#if defined(MODPLUG_TRACKER) || defined(MPT_FUZZ_TRACKER) + // These make little sense for a module player library + MPT_DECLARE_FORMAT(UAX), + MPT_DECLARE_FORMAT(WAV), + MPT_DECLARE_FORMAT(MID), +#endif // MODPLUG_TRACKER || MPT_FUZZ_TRACKER + MPT_DECLARE_FORMAT(GDM), + MPT_DECLARE_FORMAT(IMF), + MPT_DECLARE_FORMAT(DIGI), + MPT_DECLARE_FORMAT(DTM), + MPT_DECLARE_FORMAT(PLM), + MPT_DECLARE_FORMAT(AM), + MPT_DECLARE_FORMAT(J2B), + MPT_DECLARE_FORMAT(PT36), + MPT_DECLARE_FORMAT(SymMOD), + MPT_DECLARE_FORMAT(MUS_KM), + MPT_DECLARE_FORMAT(FMT), + MPT_DECLARE_FORMAT(SFX), + MPT_DECLARE_FORMAT(STP), + MPT_DECLARE_FORMAT(DSym), + MPT_DECLARE_FORMAT(STX), + MPT_DECLARE_FORMAT(MOD), + MPT_DECLARE_FORMAT(ICE), + MPT_DECLARE_FORMAT(669), + MPT_DECLARE_FORMAT(C67), + MPT_DECLARE_FORMAT(MO3), + MPT_DECLARE_FORMAT(M15), +}; + +#undef MPT_DECLARE_FORMAT + + +CSoundFile::ProbeResult CSoundFile::ProbeAdditionalSize(MemoryFileReader &file, const uint64 *pfilesize, uint64 minimumAdditionalSize) +{ + const uint64 availableFileSize = file.GetLength(); + const uint64 fileSize = (pfilesize ? *pfilesize : file.GetLength()); + //const uint64 validFileSize = std::min(fileSize, static_cast<uint64>(ProbeRecommendedSize)); + const uint64 goalSize = file.GetPosition() + minimumAdditionalSize; + //const uint64 goalMinimumSize = std::min(goalSize, static_cast<uint64>(ProbeRecommendedSize)); + if(pfilesize) + { + if(availableFileSize < std::min(fileSize, static_cast<uint64>(ProbeRecommendedSize))) + { + if(availableFileSize < goalSize) + { + return ProbeWantMoreData; + } + } else + { + if(fileSize < goalSize) + { + return ProbeFailure; + } + } + return ProbeSuccess; + } + return ProbeSuccess; +} + + +#define MPT_DO_PROBE( storedResult , call ) \ + do { \ + ProbeResult lastResult = call ; \ + if(lastResult == ProbeSuccess) { \ + return ProbeSuccess; \ + } else if(lastResult == ProbeWantMoreData) { \ + storedResult = ProbeWantMoreData; \ + } \ + } while(0) \ +/**/ + + +CSoundFile::ProbeResult CSoundFile::Probe(ProbeFlags flags, mpt::span<const std::byte> data, const uint64 *pfilesize) +{ + ProbeResult result = ProbeFailure; + if(pfilesize && (*pfilesize < data.size())) + { + throw std::out_of_range(""); + } + if(!data.data()) + { + throw std::invalid_argument(""); + } + MemoryFileReader file(data); + if(flags & ProbeContainers) + { +#if !defined(MPT_WITH_ANCIENT) + MPT_DO_PROBE(result, ProbeFileHeaderMMCMP(file, pfilesize)); + MPT_DO_PROBE(result, ProbeFileHeaderPP20(file, pfilesize)); + MPT_DO_PROBE(result, ProbeFileHeaderXPK(file, pfilesize)); +#endif // !MPT_WITH_ANCIENT + MPT_DO_PROBE(result, ProbeFileHeaderUMX(file, pfilesize)); + } + if(flags & ProbeModules) + { + for(const auto &format : ModuleFormatLoaders) + { + if(format.prober != nullptr) + { + MPT_DO_PROBE(result, format.prober(file, pfilesize)); + } + } + } + if(pfilesize) + { + if((result == ProbeWantMoreData) && (mpt::saturate_cast<std::size_t>(*pfilesize) <= data.size())) + { + // If the prober wants more data but we already reached EOF, + // probing must fail. + result = ProbeFailure; + } + } else + { + if((result == ProbeWantMoreData) && (data.size() >= ProbeRecommendedSize)) + { + // If the prober wants more daat but we already provided the recommended required maximum, + // just return success as this is the best we can do for the suggestesd probing size. + result = ProbeSuccess; + } + } + return result; +} + + +bool CSoundFile::Create(FileReader file, ModLoadingFlags loadFlags, CModDoc *pModDoc) +{ + m_nMixChannels = 0; +#ifdef MODPLUG_TRACKER + m_pModDoc = pModDoc; +#else + MPT_UNUSED(pModDoc); + m_nFreqFactor = m_nTempoFactor = 65536; +#endif // MODPLUG_TRACKER + + Clear(m_szNames); +#ifndef NO_PLUGINS + std::fill(std::begin(m_MixPlugins), std::end(m_MixPlugins), SNDMIXPLUGIN()); +#endif // NO_PLUGINS + + if(CreateInternal(file, loadFlags)) + return true; + +#ifndef NO_ARCHIVE_SUPPORT + if(!(loadFlags & skipContainer) && file.IsValid()) + { + CUnarchiver unarchiver(file); + if(unarchiver.ExtractBestFile(GetSupportedExtensions(true))) + { + if(CreateInternal(unarchiver.GetOutputFile(), loadFlags)) + { + // Read archive comment if there is no song comment + if(m_songMessage.empty()) + { + m_songMessage.assign(mpt::ToCharset(mpt::Charset::Locale, unarchiver.GetComment())); + } + return true; + } + } + } +#endif + + return false; +} + + +bool CSoundFile::CreateInternal(FileReader file, ModLoadingFlags loadFlags) +{ + if(file.IsValid()) + { + std::vector<ContainerItem> containerItems; + MODCONTAINERTYPE packedContainerType = MOD_CONTAINERTYPE_NONE; + if(!(loadFlags & skipContainer)) + { + ContainerLoadingFlags containerLoadFlags = (loadFlags == onlyVerifyHeader) ? ContainerOnlyVerifyHeader : ContainerUnwrapData; +#if !defined(MPT_WITH_ANCIENT) + if(packedContainerType == MOD_CONTAINERTYPE_NONE && UnpackXPK(containerItems, file, containerLoadFlags)) packedContainerType = MOD_CONTAINERTYPE_XPK; + if(packedContainerType == MOD_CONTAINERTYPE_NONE && UnpackPP20(containerItems, file, containerLoadFlags)) packedContainerType = MOD_CONTAINERTYPE_PP20; + if(packedContainerType == MOD_CONTAINERTYPE_NONE && UnpackMMCMP(containerItems, file, containerLoadFlags)) packedContainerType = MOD_CONTAINERTYPE_MMCMP; +#endif // !MPT_WITH_ANCIENT + if(packedContainerType == MOD_CONTAINERTYPE_NONE && UnpackUMX(containerItems, file, containerLoadFlags)) packedContainerType = MOD_CONTAINERTYPE_UMX; + if(packedContainerType != MOD_CONTAINERTYPE_NONE) + { + if(loadFlags == onlyVerifyHeader) + { + return true; + } + if(!containerItems.empty()) + { + // cppcheck false-positive + // cppcheck-suppress containerOutOfBounds + file = containerItems[0].file; + } + } + } + + if(loadFlags & skipModules) + { + return false; + } + + // Try all module format loaders + bool loaderSuccess = false; + for(const auto &format : ModuleFormatLoaders) + { + loaderSuccess = (this->*(format.loader))(file, loadFlags); + if(loaderSuccess) + break; + } + + if(!loaderSuccess) + { + m_nType = MOD_TYPE_NONE; + m_ContainerType = MOD_CONTAINERTYPE_NONE; + } + if(loadFlags == onlyVerifyHeader) + { + return loaderSuccess; + } + + if(packedContainerType != MOD_CONTAINERTYPE_NONE && m_ContainerType == MOD_CONTAINERTYPE_NONE) + { + m_ContainerType = packedContainerType; + } + + m_visitedRows.Initialize(true); + } else + { + // New song + InitializeGlobals(); + m_visitedRows.Initialize(true); + m_dwCreatedWithVersion = Version::Current(); + } + + // Adjust channels + const auto muteFlag = GetChannelMuteFlag(); + for(CHANNELINDEX chn = 0; chn < MAX_BASECHANNELS; chn++) + { + LimitMax(ChnSettings[chn].nVolume, uint16(64)); + if(ChnSettings[chn].nPan > 256) + ChnSettings[chn].nPan = 128; + if(ChnSettings[chn].nMixPlugin > MAX_MIXPLUGINS) + ChnSettings[chn].nMixPlugin = 0; + m_PlayState.Chn[chn].Reset(ModChannel::resetTotal, *this, chn, muteFlag); + } + + // Checking samples, load external samples + for(SAMPLEINDEX nSmp = 1; nSmp <= m_nSamples; nSmp++) + { + ModSample &sample = Samples[nSmp]; + +#ifdef MPT_EXTERNAL_SAMPLES + if(SampleHasPath(nSmp)) + { + mpt::PathString filename = GetSamplePath(nSmp); + if(file.GetOptionalFileName()) + { + filename = filename.RelativePathToAbsolute(file.GetOptionalFileName()->GetPath()); + } else if(GetpModDoc() != nullptr) + { + filename = filename.RelativePathToAbsolute(GetpModDoc()->GetPathNameMpt().GetPath()); + } + filename = filename.Simplify(); + if(!LoadExternalSample(nSmp, filename)) + { +#ifndef MODPLUG_TRACKER + // OpenMPT has its own way of reporting this error in CModDoc. + AddToLog(LogError, MPT_UFORMAT("Unable to load sample {}: {}")(i, filename.ToUnicode())); +#endif // MODPLUG_TRACKER + } + } else + { + sample.uFlags.reset(SMP_KEEPONDISK); + } +#endif // MPT_EXTERNAL_SAMPLES + + if(sample.HasSampleData()) + { + sample.PrecomputeLoops(*this, false); + } else if(!sample.uFlags[SMP_KEEPONDISK]) + { + sample.nLength = 0; + sample.nLoopStart = 0; + sample.nLoopEnd = 0; + sample.nSustainStart = 0; + sample.nSustainEnd = 0; + sample.uFlags.reset(CHN_LOOP | CHN_PINGPONGLOOP | CHN_SUSTAINLOOP | CHN_PINGPONGSUSTAIN); + } + if(sample.nGlobalVol > 64) sample.nGlobalVol = 64; + if(sample.uFlags[CHN_ADLIB] && m_opl == nullptr) InitOPL(); + } + // Check invalid instruments + INSTRUMENTINDEX maxInstr = 0; + for(INSTRUMENTINDEX i = 0; i <= m_nInstruments; i++) + { + if(Instruments[i] != nullptr) + { + maxInstr = i; + Instruments[i]->Sanitize(GetType()); + } + } + m_nInstruments = maxInstr; + + // Set default play state values + if(!m_nDefaultTempo.GetInt()) + m_nDefaultTempo.Set(125); + else + LimitMax(m_nDefaultTempo, TEMPO(uint16_max, 0)); + if(!m_nDefaultSpeed) + m_nDefaultSpeed = 6; + + if(m_nDefaultRowsPerMeasure < m_nDefaultRowsPerBeat) + m_nDefaultRowsPerMeasure = m_nDefaultRowsPerBeat; + LimitMax(m_nDefaultRowsPerBeat, MAX_ROWS_PER_BEAT); + LimitMax(m_nDefaultRowsPerMeasure, MAX_ROWS_PER_BEAT); + LimitMax(m_nDefaultGlobalVolume, MAX_GLOBAL_VOLUME); + if(!m_tempoSwing.empty()) + m_tempoSwing.resize(m_nDefaultRowsPerBeat); + + m_PlayState.m_nMusicSpeed = m_nDefaultSpeed; + m_PlayState.m_nMusicTempo = m_nDefaultTempo; + m_PlayState.m_nCurrentRowsPerBeat = m_nDefaultRowsPerBeat; + m_PlayState.m_nCurrentRowsPerMeasure = m_nDefaultRowsPerMeasure; + m_PlayState.m_nGlobalVolume = static_cast<int32>(m_nDefaultGlobalVolume); + m_PlayState.ResetGlobalVolumeRamping(); + m_PlayState.m_nNextOrder = 0; + m_PlayState.m_nCurrentOrder = 0; + m_PlayState.m_nPattern = 0; + m_PlayState.m_nBufferCount = 0; + m_PlayState.m_dBufferDiff = 0; + m_PlayState.m_nTickCount = TICKS_ROW_FINISHED; + m_PlayState.m_nNextRow = 0; + m_PlayState.m_nRow = 0; + m_PlayState.m_nPatternDelay = 0; + m_PlayState.m_nFrameDelay = 0; + m_PlayState.m_nextPatStartRow = 0; + m_PlayState.m_nSeqOverride = ORDERINDEX_INVALID; + + if(UseFinetuneAndTranspose()) + m_playBehaviour.reset(kPeriodsAreHertz); + + m_nMaxOrderPosition = 0; + + RecalculateSamplesPerTick(); + + for(auto &order : Order) + { + order.Shrink(); + if(order.GetRestartPos() >= order.size()) + { + order.SetRestartPos(0); + } + } + + if(GetType() == MOD_TYPE_NONE) + { + return false; + } + + SetModSpecsPointer(m_pModSpecs, GetBestSaveFormat()); + + // When reading a file made with an older version of MPT, it might be necessary to upgrade some settings automatically. + if(m_dwLastSavedWithVersion) + { + UpgradeModule(); + } + +#ifndef NO_PLUGINS + // Load plugins +#ifdef MODPLUG_TRACKER + mpt::ustring notFoundText; +#endif // MODPLUG_TRACKER + std::vector<const SNDMIXPLUGININFO *> notFoundIDs; + + if((loadFlags & (loadPluginData | loadPluginInstance)) == (loadPluginData | loadPluginInstance)) + { + for(PLUGINDEX plug = 0; plug < MAX_MIXPLUGINS; plug++) + { + auto &plugin = m_MixPlugins[plug]; + if(plugin.IsValidPlugin()) + { +#ifdef MODPLUG_TRACKER + // Provide some visual feedback + { + mpt::ustring s = MPT_UFORMAT("Loading Plugin FX{}: {} ({})")( + mpt::ufmt::dec0<2>(plug + 1), + mpt::ToUnicode(mpt::Charset::UTF8, plugin.Info.szLibraryName), + mpt::ToUnicode(mpt::Charset::Locale, plugin.Info.szName)); + CMainFrame::GetMainFrame()->SetHelpText(mpt::ToCString(s)); + } +#endif // MODPLUG_TRACKER + CreateMixPluginProc(plugin, *this); + if(plugin.pMixPlugin) + { + // Plugin was found + plugin.pMixPlugin->RestoreAllParameters(plugin.defaultProgram); + } else + { + // Plugin not found - add to list + bool found = std::find_if(notFoundIDs.cbegin(), notFoundIDs.cend(), + [&plugin](const SNDMIXPLUGININFO *info) { return info->dwPluginId2 == plugin.Info.dwPluginId2 && info->dwPluginId1 == plugin.Info.dwPluginId1; }) != notFoundIDs.cend(); + + if(!found) + { + notFoundIDs.push_back(&plugin.Info); +#ifdef MODPLUG_TRACKER + notFoundText.append(plugin.GetLibraryName()); + notFoundText.append(UL_("\n")); +#else + AddToLog(LogWarning, U_("Plugin not found: ") + plugin.GetLibraryName()); +#endif // MODPLUG_TRACKER + } + } + } + } + } + + // Set up mix levels (also recalculates plugin mix levels - must be done after plugins were loaded) + SetMixLevels(m_nMixLevels); + +#ifdef MODPLUG_TRACKER + // Display a nice message so the user sees which plugins are missing + // TODO: Use IDD_MODLOADING_WARNINGS dialog (NON-MODAL!) to display all warnings that are encountered when loading a module. + if(!notFoundIDs.empty()) + { + if(notFoundIDs.size() == 1) + { + notFoundText = UL_("The following plugin has not been found:\n\n") + notFoundText + UL_("\nDo you want to search for it online?"); + } else + { + notFoundText = UL_("The following plugins have not been found:\n\n") + notFoundText + UL_("\nDo you want to search for them online?"); + } + if(Reporting::Confirm(notFoundText, U_("OpenMPT - Plugins missing"), false, true) == cnfYes) + { + mpt::ustring url = U_("https://resources.openmpt.org/plugins/search.php?p="); + for(const auto &id : notFoundIDs) + { + url += mpt::ufmt::HEX0<8>(id->dwPluginId2.get()); + url += mpt::ToUnicode(mpt::Charset::UTF8, id->szLibraryName); + url += UL_("%0a"); + } + CTrackApp::OpenURL(mpt::PathString::FromUnicode(url)); + } + } +#endif // MODPLUG_TRACKER +#endif // NO_PLUGINS + + return true; +} + + +bool CSoundFile::Destroy() +{ + for(auto &chn : m_PlayState.Chn) + { + chn.pModInstrument = nullptr; + chn.pModSample = nullptr; + chn.pCurrentSample = nullptr; + chn.nLength = 0; + } + + Patterns.DestroyPatterns(); + + m_songName.clear(); + m_songArtist.clear(); + m_songMessage.clear(); + m_FileHistory.clear(); +#ifdef MPT_EXTERNAL_SAMPLES + m_samplePaths.clear(); +#endif // MPT_EXTERNAL_SAMPLES + + for(auto &smp : Samples) + { + smp.FreeSample(); + } + for(auto &ins : Instruments) + { + delete ins; + ins = nullptr; + } +#ifndef NO_PLUGINS + for(auto &plug : m_MixPlugins) + { + plug.Destroy(); + } +#endif // NO_PLUGINS + + m_nType = MOD_TYPE_NONE; + m_ContainerType = MOD_CONTAINERTYPE_NONE; + m_nChannels = m_nSamples = m_nInstruments = 0; + return true; +} + + +////////////////////////////////////////////////////////////////////////// +// Misc functions + + +void CSoundFile::SetDspEffects(uint32 DSPMask) +{ + m_MixerSettings.DSPMask = DSPMask; + InitPlayer(false); +} + + +void CSoundFile::SetPreAmp(uint32 nVol) +{ + if (nVol < 1) nVol = 1; + if (nVol > 0x200) nVol = 0x200; // x4 maximum +#ifndef NO_AGC + if ((nVol < m_MixerSettings.m_nPreAmp) && (nVol) && (m_MixerSettings.DSPMask & SNDDSP_AGC)) + { + m_AGC.Adjust(m_MixerSettings.m_nPreAmp, nVol); + } +#endif + m_MixerSettings.m_nPreAmp = nVol; +} + + +double CSoundFile::GetCurrentBPM() const +{ + double bpm; + + if (m_nTempoMode == TempoMode::Modern) + { + // With modern mode, we trust that true bpm is close enough to what user chose. + // This avoids oscillation due to tick-to-tick corrections. + bpm = m_PlayState.m_nMusicTempo.ToDouble(); + } else + { + //with other modes, we calculate it: + double ticksPerBeat = m_PlayState.m_nMusicSpeed * m_PlayState.m_nCurrentRowsPerBeat; //ticks/beat = ticks/row * rows/beat + double samplesPerBeat = m_PlayState.m_nSamplesPerTick * ticksPerBeat; //samps/beat = samps/tick * ticks/beat + bpm = m_MixerSettings.gdwMixingFreq / samplesPerBeat * 60; //beats/sec = samps/sec / samps/beat + } //beats/min = beats/sec * 60 + + return bpm; +} + + +void CSoundFile::ResetPlayPos() +{ + const auto muteFlag = GetChannelMuteFlag(); + for(CHANNELINDEX i = 0; i < MAX_CHANNELS; i++) + m_PlayState.Chn[i].Reset(ModChannel::resetSetPosFull, *this, i, muteFlag); + + m_visitedRows.Initialize(true); + m_SongFlags.reset(SONG_FADINGSONG | SONG_ENDREACHED); + + m_PlayState.m_nGlobalVolume = m_nDefaultGlobalVolume; + m_PlayState.m_nMusicSpeed = m_nDefaultSpeed; + m_PlayState.m_nMusicTempo = m_nDefaultTempo; + + // Do not ramp global volume when starting playback + m_PlayState.ResetGlobalVolumeRamping(); + + m_PlayState.m_nNextOrder = 0; + m_PlayState.m_nNextRow = 0; + m_PlayState.m_nTickCount = TICKS_ROW_FINISHED; + m_PlayState.m_nBufferCount = 0; + m_PlayState.m_nPatternDelay = 0; + m_PlayState.m_nFrameDelay = 0; + m_PlayState.m_nextPatStartRow = 0; + m_PlayState.m_lTotalSampleCount = 0; +} + + + +void CSoundFile::SetCurrentOrder(ORDERINDEX nOrder) +{ + while(nOrder < Order().size() && !Order().IsValidPat(nOrder)) + nOrder++; + if(nOrder >= Order().size()) + return; + + for(auto &chn : m_PlayState.Chn) + { + chn.nPeriod = 0; + chn.nNote = NOTE_NONE; + chn.nPortamentoDest = 0; + chn.nCommand = 0; + chn.nPatternLoopCount = 0; + chn.nPatternLoop = 0; + chn.nVibratoPos = chn.nTremoloPos = chn.nPanbrelloPos = 0; + //IT compatibility 15. Retrigger + if(m_playBehaviour[kITRetrigger]) + { + chn.nRetrigCount = 0; + chn.nRetrigParam = 1; + } + chn.nTremorCount = 0; + } + +#ifndef NO_PLUGINS + // Stop hanging notes from VST instruments as well + StopAllVsti(); +#endif // NO_PLUGINS + + if (!nOrder) + { + ResetPlayPos(); + } else + { + m_PlayState.m_nNextOrder = nOrder; + m_PlayState.m_nRow = m_PlayState.m_nNextRow = 0; + m_PlayState.m_nPattern = 0; + m_PlayState.m_nTickCount = TICKS_ROW_FINISHED; + m_PlayState.m_nBufferCount = 0; + m_PlayState.m_nPatternDelay = 0; + m_PlayState.m_nFrameDelay = 0; + m_PlayState.m_nextPatStartRow = 0; + } + + m_SongFlags.reset(SONG_FADINGSONG | SONG_ENDREACHED); +} + +void CSoundFile::SuspendPlugins() +{ +#ifndef NO_PLUGINS + for(auto &plug : m_MixPlugins) + { + IMixPlugin *pPlugin = plug.pMixPlugin; + if(pPlugin != nullptr && pPlugin->IsResumed()) + { + pPlugin->NotifySongPlaying(false); + pPlugin->HardAllNotesOff(); + pPlugin->Suspend(); + } + } +#endif // NO_PLUGINS +} + +void CSoundFile::ResumePlugins() +{ +#ifndef NO_PLUGINS + for(auto &plugin : m_MixPlugins) + { + IMixPlugin *pPlugin = plugin.pMixPlugin; + if(pPlugin != nullptr && !pPlugin->IsResumed()) + { + pPlugin->NotifySongPlaying(true); + pPlugin->Resume(); + } + } +#endif // NO_PLUGINS +} + + +void CSoundFile::StopAllVsti() +{ +#ifndef NO_PLUGINS + for(auto &plugin : m_MixPlugins) + { + IMixPlugin *pPlugin = plugin.pMixPlugin; + if(pPlugin != nullptr && pPlugin->IsResumed()) + { + pPlugin->HardAllNotesOff(); + } + } +#endif // NO_PLUGINS +} + + +void CSoundFile::SetMixLevels(MixLevels levels) +{ + m_nMixLevels = levels; + m_PlayConfig.SetMixLevels(m_nMixLevels); + RecalculateGainForAllPlugs(); +} + + +void CSoundFile::RecalculateGainForAllPlugs() +{ +#ifndef NO_PLUGINS + for(auto &plugin : m_MixPlugins) + { + if(plugin.pMixPlugin != nullptr) + plugin.pMixPlugin->RecalculateGain(); + } +#endif // NO_PLUGINS +} + + +void CSoundFile::ResetChannels() +{ + m_SongFlags.reset(SONG_FADINGSONG | SONG_ENDREACHED); + m_PlayState.m_nBufferCount = 0; + for(auto &chn : m_PlayState.Chn) + { + chn.nROfs = chn.nLOfs = 0; + chn.nLength = 0; + if(chn.dwFlags[CHN_ADLIB] && m_opl) + { + CHANNELINDEX c = static_cast<CHANNELINDEX>(std::distance(std::begin(m_PlayState.Chn), &chn)); + m_opl->NoteCut(c); + } + } +} + + +#ifdef MODPLUG_TRACKER + +void CSoundFile::PatternTranstionChnSolo(const CHANNELINDEX chnIndex) +{ + if(chnIndex >= m_nChannels) + return; + + for(CHANNELINDEX i = 0; i < m_nChannels; i++) + { + m_bChannelMuteTogglePending[i] = !ChnSettings[i].dwFlags[CHN_MUTE]; + } + m_bChannelMuteTogglePending[chnIndex] = ChnSettings[chnIndex].dwFlags[CHN_MUTE]; +} + + +void CSoundFile::PatternTransitionChnUnmuteAll() +{ + for(CHANNELINDEX i = 0; i < m_nChannels; i++) + { + m_bChannelMuteTogglePending[i] = ChnSettings[i].dwFlags[CHN_MUTE]; + } +} + +#endif // MODPLUG_TRACKER + + +void CSoundFile::LoopPattern(PATTERNINDEX nPat, ROWINDEX nRow) +{ + if(!Patterns.IsValidPat(nPat)) + { + m_SongFlags.reset(SONG_PATTERNLOOP); + } else + { + if(nRow >= Patterns[nPat].GetNumRows()) nRow = 0; + m_PlayState.m_nPattern = nPat; + m_PlayState.m_nRow = m_PlayState.m_nNextRow = nRow; + m_PlayState.m_nTickCount = TICKS_ROW_FINISHED; + m_PlayState.m_nPatternDelay = 0; + m_PlayState.m_nFrameDelay = 0; + m_PlayState.m_nextPatStartRow = 0; + m_SongFlags.set(SONG_PATTERNLOOP); + } + m_PlayState.m_nBufferCount = 0; +} + + +void CSoundFile::DontLoopPattern(PATTERNINDEX nPat, ROWINDEX nRow) +{ + if(!Patterns.IsValidPat(nPat)) nPat = 0; + if(nRow >= Patterns[nPat].GetNumRows()) nRow = 0; + m_PlayState.m_nPattern = nPat; + m_PlayState.m_nRow = m_PlayState.m_nNextRow = nRow; + m_PlayState.m_nTickCount = TICKS_ROW_FINISHED; + m_PlayState.m_nPatternDelay = 0; + m_PlayState.m_nFrameDelay = 0; + m_PlayState.m_nBufferCount = 0; + m_PlayState.m_nextPatStartRow = 0; + m_SongFlags.reset(SONG_PATTERNLOOP); +} + + +void CSoundFile::SetDefaultPlaybackBehaviour(MODTYPE type) +{ + m_playBehaviour = GetDefaultPlaybackBehaviour(type); +} + + +PlayBehaviourSet CSoundFile::GetSupportedPlaybackBehaviour(MODTYPE type) +{ + PlayBehaviourSet playBehaviour; + switch(type) + { + case MOD_TYPE_MPT: + case MOD_TYPE_IT: + playBehaviour.set(MSF_COMPATIBLE_PLAY); + playBehaviour.set(kPeriodsAreHertz); + playBehaviour.set(kTempoClamp); + playBehaviour.set(kPerChannelGlobalVolSlide); + playBehaviour.set(kPanOverride); + playBehaviour.set(kITInstrWithoutNote); + playBehaviour.set(kITVolColFinePortamento); + playBehaviour.set(kITArpeggio); + playBehaviour.set(kITOutOfRangeDelay); + playBehaviour.set(kITPortaMemoryShare); + playBehaviour.set(kITPatternLoopTargetReset); + playBehaviour.set(kITFT2PatternLoop); + playBehaviour.set(kITPingPongNoReset); + playBehaviour.set(kITEnvelopeReset); + playBehaviour.set(kITClearOldNoteAfterCut); + playBehaviour.set(kITVibratoTremoloPanbrello); + playBehaviour.set(kITTremor); + playBehaviour.set(kITRetrigger); + playBehaviour.set(kITMultiSampleBehaviour); + playBehaviour.set(kITPortaTargetReached); + playBehaviour.set(kITPatternLoopBreak); + playBehaviour.set(kITOffset); + playBehaviour.set(kITSwingBehaviour); + playBehaviour.set(kITNNAReset); + playBehaviour.set(kITSCxStopsSample); + playBehaviour.set(kITEnvelopePositionHandling); + playBehaviour.set(kITPortamentoInstrument); + playBehaviour.set(kITPingPongMode); + playBehaviour.set(kITRealNoteMapping); + playBehaviour.set(kITHighOffsetNoRetrig); + playBehaviour.set(kITFilterBehaviour); + playBehaviour.set(kITNoSurroundPan); + playBehaviour.set(kITShortSampleRetrig); + playBehaviour.set(kITPortaNoNote); + playBehaviour.set(kITFT2DontResetNoteOffOnPorta); + playBehaviour.set(kITVolColMemory); + playBehaviour.set(kITPortamentoSwapResetsPos); + playBehaviour.set(kITEmptyNoteMapSlot); + playBehaviour.set(kITFirstTickHandling); + playBehaviour.set(kITSampleAndHoldPanbrello); + playBehaviour.set(kITClearPortaTarget); + playBehaviour.set(kITPanbrelloHold); + playBehaviour.set(kITPanningReset); + playBehaviour.set(kITPatternLoopWithJumps); + playBehaviour.set(kITInstrWithNoteOff); + playBehaviour.set(kITMultiSampleInstrumentNumber); + playBehaviour.set(kRowDelayWithNoteDelay); + playBehaviour.set(kITInstrWithNoteOffOldEffects); + playBehaviour.set(kITDoNotOverrideChannelPan); + playBehaviour.set(kITDCTBehaviour); + playBehaviour.set(kITPitchPanSeparation); + if(type == MOD_TYPE_MPT) + { + playBehaviour.set(kOPLFlexibleNoteOff); + playBehaviour.set(kOPLwithNNA); + playBehaviour.set(kOPLNoteOffOnNoteChange); + } + break; + + case MOD_TYPE_XM: + playBehaviour.set(MSF_COMPATIBLE_PLAY); + playBehaviour.set(kFT2VolumeRamping); + playBehaviour.set(kTempoClamp); + playBehaviour.set(kPerChannelGlobalVolSlide); + playBehaviour.set(kPanOverride); + playBehaviour.set(kITFT2PatternLoop); + playBehaviour.set(kITFT2DontResetNoteOffOnPorta); + playBehaviour.set(kFT2Arpeggio); + playBehaviour.set(kFT2Retrigger); + playBehaviour.set(kFT2VolColVibrato); + playBehaviour.set(kFT2PortaNoNote); + playBehaviour.set(kFT2KeyOff); + playBehaviour.set(kFT2PanSlide); + playBehaviour.set(kFT2ST3OffsetOutOfRange); + playBehaviour.set(kFT2RestrictXCommand); + playBehaviour.set(kFT2RetrigWithNoteDelay); + playBehaviour.set(kFT2SetPanEnvPos); + playBehaviour.set(kFT2PortaIgnoreInstr); + playBehaviour.set(kFT2VolColMemory); + playBehaviour.set(kFT2LoopE60Restart); + playBehaviour.set(kFT2ProcessSilentChannels); + playBehaviour.set(kFT2ReloadSampleSettings); + playBehaviour.set(kFT2PortaDelay); + playBehaviour.set(kFT2Transpose); + playBehaviour.set(kFT2PatternLoopWithJumps); + playBehaviour.set(kFT2PortaTargetNoReset); + playBehaviour.set(kFT2EnvelopeEscape); + playBehaviour.set(kFT2Tremor); + playBehaviour.set(kFT2OutOfRangeDelay); + playBehaviour.set(kFT2Periods); + playBehaviour.set(kFT2PanWithDelayedNoteOff); + playBehaviour.set(kFT2VolColDelay); + playBehaviour.set(kFT2FinetunePrecision); + playBehaviour.set(kFT2NoteOffFlags); + playBehaviour.set(kRowDelayWithNoteDelay); + playBehaviour.set(kFT2MODTremoloRampWaveform); + playBehaviour.set(kFT2PortaUpDownMemory); + playBehaviour.set(kFT2PanSustainRelease); + playBehaviour.set(kFT2NoteDelayWithoutInstr); + playBehaviour.set(kFT2PortaResetDirection); + break; + + case MOD_TYPE_S3M: + playBehaviour.set(MSF_COMPATIBLE_PLAY); + playBehaviour.set(kTempoClamp); + playBehaviour.set(kPanOverride); + playBehaviour.set(kITPanbrelloHold); + playBehaviour.set(kFT2ST3OffsetOutOfRange); + playBehaviour.set(kST3NoMutedChannels); + playBehaviour.set(kST3PortaSampleChange); + playBehaviour.set(kST3EffectMemory); + playBehaviour.set(kST3VibratoMemory); + playBehaviour.set(KST3PortaAfterArpeggio); + playBehaviour.set(kRowDelayWithNoteDelay); + playBehaviour.set(kST3OffsetWithoutInstrument); + playBehaviour.set(kST3RetrigAfterNoteCut); + playBehaviour.set(kST3SampleSwap); + playBehaviour.set(kOPLNoteOffOnNoteChange); + playBehaviour.set(kApplyUpperPeriodLimit); + break; + + case MOD_TYPE_MOD: + playBehaviour.set(kMODVBlankTiming); + playBehaviour.set(kMODOneShotLoops); + playBehaviour.set(kMODIgnorePanning); + playBehaviour.set(kMODSampleSwap); + playBehaviour.set(kMODOutOfRangeNoteDelay); + playBehaviour.set(kMODTempoOnSecondTick); + playBehaviour.set(kRowDelayWithNoteDelay); + playBehaviour.set(kFT2MODTremoloRampWaveform); + break; + + default: + playBehaviour.set(MSF_COMPATIBLE_PLAY); + playBehaviour.set(kPeriodsAreHertz); + playBehaviour.set(kTempoClamp); + playBehaviour.set(kPanOverride); + break; + } + return playBehaviour; +} + + +PlayBehaviourSet CSoundFile::GetDefaultPlaybackBehaviour(MODTYPE type) +{ + PlayBehaviourSet playBehaviour; + switch(type) + { + case MOD_TYPE_MPT: + playBehaviour.set(kPeriodsAreHertz); + playBehaviour.set(kPerChannelGlobalVolSlide); + playBehaviour.set(kPanOverride); + playBehaviour.set(kITArpeggio); + playBehaviour.set(kITPortaMemoryShare); + playBehaviour.set(kITPatternLoopTargetReset); + playBehaviour.set(kITFT2PatternLoop); + playBehaviour.set(kITPingPongNoReset); + playBehaviour.set(kITClearOldNoteAfterCut); + playBehaviour.set(kITVibratoTremoloPanbrello); + playBehaviour.set(kITMultiSampleBehaviour); + playBehaviour.set(kITPortaTargetReached); + playBehaviour.set(kITPatternLoopBreak); + playBehaviour.set(kITSwingBehaviour); + playBehaviour.set(kITSCxStopsSample); + playBehaviour.set(kITEnvelopePositionHandling); + playBehaviour.set(kITPingPongMode); + playBehaviour.set(kITRealNoteMapping); + playBehaviour.set(kITPortaNoNote); + playBehaviour.set(kITVolColMemory); + playBehaviour.set(kITFirstTickHandling); + playBehaviour.set(kITClearPortaTarget); + playBehaviour.set(kITSampleAndHoldPanbrello); + playBehaviour.set(kITPanbrelloHold); + playBehaviour.set(kITPanningReset); + playBehaviour.set(kITInstrWithNoteOff); + playBehaviour.set(kOPLFlexibleNoteOff); + playBehaviour.set(kITDoNotOverrideChannelPan); + playBehaviour.set(kITDCTBehaviour); + playBehaviour.set(kOPLwithNNA); + playBehaviour.set(kITPitchPanSeparation); + break; + + case MOD_TYPE_S3M: + playBehaviour = GetSupportedPlaybackBehaviour(type); + // Default behaviour was chosen to follow GUS, so kST3PortaSampleChange is enabled and kST3SampleSwap is disabled. + // For SoundBlaster behaviour, those two flags would need to be swapped. + playBehaviour.reset(kST3SampleSwap); + break; + + case MOD_TYPE_XM: + playBehaviour = GetSupportedPlaybackBehaviour(type); + // Only set this explicitely for FT2-made XMs. + playBehaviour.reset(kFT2VolumeRamping); + break; + + case MOD_TYPE_MOD: + playBehaviour.set(kRowDelayWithNoteDelay); + break; + + default: + playBehaviour = GetSupportedPlaybackBehaviour(type); + break; + } + return playBehaviour; +} + + +MODTYPE CSoundFile::GetBestSaveFormat() const +{ + switch(GetType()) + { + case MOD_TYPE_MOD: + case MOD_TYPE_S3M: + case MOD_TYPE_XM: + case MOD_TYPE_IT: + case MOD_TYPE_MPT: + return GetType(); + case MOD_TYPE_AMF0: + case MOD_TYPE_DIGI: + case MOD_TYPE_SFX: + case MOD_TYPE_STP: + return MOD_TYPE_MOD; + case MOD_TYPE_MED: + if(!m_nInstruments) + { + for(const auto &pat : Patterns) + { + if(pat.IsValid() && pat.GetNumRows() != 64) + return MOD_TYPE_XM; + } + return MOD_TYPE_MOD; + } + return MOD_TYPE_XM; + case MOD_TYPE_PSM: + if(GetNumChannels() > 16) + return MOD_TYPE_IT; + for(CHANNELINDEX i = 0; i < GetNumChannels(); i++) + { + if(ChnSettings[i].dwFlags[CHN_SURROUND] || ChnSettings[i].nVolume != 64) + { + return MOD_TYPE_IT; + break; + } + } + return MOD_TYPE_S3M; + case MOD_TYPE_669: + case MOD_TYPE_FAR: + case MOD_TYPE_STM: + case MOD_TYPE_DSM: + case MOD_TYPE_AMF: + case MOD_TYPE_MTM: + return MOD_TYPE_S3M; + case MOD_TYPE_AMS: + case MOD_TYPE_DMF: + case MOD_TYPE_DBM: + case MOD_TYPE_IMF: + case MOD_TYPE_J2B: + case MOD_TYPE_ULT: + case MOD_TYPE_OKT: + case MOD_TYPE_MT2: + case MOD_TYPE_MDL: + case MOD_TYPE_PTM: + case MOD_TYPE_DTM: + default: + return MOD_TYPE_IT; + case MOD_TYPE_MID: + return MOD_TYPE_MPT; + } +} + + +const char *CSoundFile::GetSampleName(SAMPLEINDEX nSample) const +{ + MPT_ASSERT(nSample <= GetNumSamples()); + if (nSample < MAX_SAMPLES) + { + return m_szNames[nSample].buf; + } else + { + return ""; + } +} + + +const char *CSoundFile::GetInstrumentName(INSTRUMENTINDEX nInstr) const +{ + if((nInstr >= MAX_INSTRUMENTS) || (!Instruments[nInstr])) + return ""; + + MPT_ASSERT(nInstr <= GetNumInstruments()); + return Instruments[nInstr]->name.buf; +} + + +bool CSoundFile::InitChannel(CHANNELINDEX nChn) +{ + if(nChn >= MAX_BASECHANNELS) + return true; + + ChnSettings[nChn].Reset(); + m_PlayState.Chn[nChn].Reset(ModChannel::resetTotal, *this, nChn, GetChannelMuteFlag()); + +#ifdef MODPLUG_TRACKER + if(GetpModDoc() != nullptr) + { + GetpModDoc()->SetChannelRecordGroup(nChn, RecordGroup::NoGroup); + } +#endif // MODPLUG_TRACKER + +#ifdef MODPLUG_TRACKER + m_bChannelMuteTogglePending[nChn] = false; +#endif // MODPLUG_TRACKER + + return false; +} + + +void CSoundFile::InitAmigaResampler() +{ + if(m_SongFlags[SONG_ISAMIGA] && m_Resampler.m_Settings.emulateAmiga != Resampling::AmigaFilter::Off) + { + const Paula::State defaultState(GetSampleRate()); + for(auto &chn : m_PlayState.Chn) + { + chn.paulaState = defaultState; + } + } +} + + +void CSoundFile::InitOPL() +{ + if(!m_opl) + m_opl = std::make_unique<OPL>(m_MixerSettings.gdwMixingFreq); +} + + +// Detect samples that are referenced by an instrument, but actually not used in a song. +// Only works in instrument mode. Unused samples are marked as false in the vector. +SAMPLEINDEX CSoundFile::DetectUnusedSamples(std::vector<bool> &sampleUsed) const +{ + sampleUsed.assign(GetNumSamples() + 1, false); + + if(GetNumInstruments() == 0) + { + return 0; + } + SAMPLEINDEX unused = 0; + std::vector<ModCommand::INSTR> lastIns; + + for(const auto &pat : Patterns) if(pat.IsValid()) + { + lastIns.assign(GetNumChannels(), 0); + auto p = pat.cbegin(); + for(ROWINDEX row = 0; row < pat.GetNumRows(); row++) + { + for(CHANNELINDEX c = 0; c < GetNumChannels(); c++, p++) + { + if(p->IsNote()) + { + ModCommand::INSTR instr = p->instr; + if(!p->instr) + instr = lastIns[c]; + INSTRUMENTINDEX minInstr = 1, maxInstr = GetNumInstruments(); + if(instr > 0) + { + if(instr <= GetNumInstruments()) + { + minInstr = maxInstr = instr; + } + lastIns[c] = instr; + } else + { + // No idea which instrument this note belongs to, so mark it used in any instruments. + } + for(INSTRUMENTINDEX i = minInstr; i <= maxInstr; i++) + { + if(const auto *pIns = Instruments[i]; pIns != nullptr) + { + SAMPLEINDEX n = pIns->Keyboard[p->note - NOTE_MIN]; + if(n <= GetNumSamples()) + sampleUsed[n] = true; + } + } + } + } + } + } + for (SAMPLEINDEX ichk = GetNumSamples(); ichk >= 1; ichk--) + { + if ((!sampleUsed[ichk]) && (Samples[ichk].HasSampleData())) unused++; + } + + return unused; +} + + +// Destroy samples where keepSamples index is false. First sample is keepSamples[1]! +SAMPLEINDEX CSoundFile::RemoveSelectedSamples(const std::vector<bool> &keepSamples) +{ + if(keepSamples.empty()) + { + return 0; + } + + SAMPLEINDEX nRemoved = 0; + for(SAMPLEINDEX nSmp = std::min(GetNumSamples(), static_cast<SAMPLEINDEX>(keepSamples.size() - 1)); nSmp >= 1; nSmp--) + { + if(!keepSamples[nSmp]) + { + CriticalSection cs; + +#ifdef MODPLUG_TRACKER + if(GetpModDoc()) + { + GetpModDoc()->GetSampleUndo().PrepareUndo(nSmp, sundo_replace, "Remove Sample"); + } +#endif // MODPLUG_TRACKER + + if(DestroySample(nSmp)) + { + m_szNames[nSmp] = ""; + nRemoved++; + } + if((nSmp == GetNumSamples()) && (nSmp > 1)) m_nSamples--; + } + } + return nRemoved; +} + + +bool CSoundFile::DestroySample(SAMPLEINDEX nSample) +{ + if(!nSample || nSample >= MAX_SAMPLES) + { + return false; + } + if(!Samples[nSample].HasSampleData()) + { + return true; + } + + ModSample &sample = Samples[nSample]; + + for(auto &chn : m_PlayState.Chn) + { + if(chn.pModSample == &sample) + { + chn.position.Set(0); + chn.nLength = 0; + chn.pCurrentSample = nullptr; + } + } + + sample.FreeSample(); + sample.nLength = 0; + sample.uFlags.reset(CHN_16BIT | CHN_STEREO); + sample.SetAdlib(false); + +#ifdef MODPLUG_TRACKER + ResetSamplePath(nSample); +#endif + return true; +} + + +bool CSoundFile::DestroySampleThreadsafe(SAMPLEINDEX nSample) +{ + CriticalSection cs; + return DestroySample(nSample); +} + + +std::unique_ptr<CTuning> CSoundFile::CreateTuning12TET(const mpt::ustring &name) +{ + std::unique_ptr<CTuning> pT = CTuning::CreateGeometric(name, 12, 2, 15); + for(ModCommand::NOTE note = 0; note < 12; ++note) + { + pT->SetNoteName(note, mpt::ustring(NoteNamesSharp[note])); + } + return pT; +} + + +mpt::ustring CSoundFile::GetNoteName(const ModCommand::NOTE note, const INSTRUMENTINDEX inst) const +{ + // For MPTM instruments with custom tuning, find the appropriate note name. Else, use default note names. + if(ModCommand::IsNote(note) && GetType() == MOD_TYPE_MPT && inst >= 1 && inst <= GetNumInstruments() && Instruments[inst] && Instruments[inst]->pTuning) + { + return Instruments[inst]->pTuning->GetNoteName(note - NOTE_MIDDLEC); + } else + { + return GetNoteName(note); + } +} + + +mpt::ustring CSoundFile::GetNoteName(const ModCommand::NOTE note) const +{ + return GetNoteName(note, m_NoteNames); +} + + +mpt::ustring CSoundFile::GetNoteName(const ModCommand::NOTE note, const NoteName *noteNames) +{ + if(ModCommand::IsSpecialNote(note)) + { + // cppcheck false-positive + // cppcheck-suppress constStatement + const mpt::uchar specialNoteNames[][4] = { UL_("PCs"), UL_("PC "), UL_("~~~"), UL_("^^^"), UL_("===") }; + static_assert(mpt::array_size<decltype(specialNoteNames)>::size == NOTE_MAX_SPECIAL - NOTE_MIN_SPECIAL + 1); + return specialNoteNames[note - NOTE_MIN_SPECIAL]; + } else if(ModCommand::IsNote(note)) + { + return mpt::ustring() + .append(noteNames[(note - NOTE_MIN) % 12]) + .append(1, UC_('0') + (note - NOTE_MIN) / 12) + ; // e.g. "C#" + "5" + } else if(note == NOTE_NONE) + { + return UL_("..."); + } + return UL_("???"); +} + + +#ifdef MODPLUG_TRACKER + +void CSoundFile::SetDefaultNoteNames() +{ + m_NoteNames = TrackerSettings::Instance().accidentalFlats ? NoteNamesFlat : NoteNamesSharp; +} + +const NoteName *CSoundFile::GetDefaultNoteNames() +{ + return m_NoteNames; +} + +#endif // MODPLUG_TRACKER + + +void CSoundFile::SetModSpecsPointer(const CModSpecifications*& pModSpecs, const MODTYPE type) +{ + switch(type) + { + case MOD_TYPE_MPT: + pModSpecs = &ModSpecs::mptm; + break; + + case MOD_TYPE_IT: + pModSpecs = &ModSpecs::itEx; + break; + + case MOD_TYPE_XM: + pModSpecs = &ModSpecs::xmEx; + break; + + case MOD_TYPE_S3M: + pModSpecs = &ModSpecs::s3mEx; + break; + + case MOD_TYPE_MOD: + default: + pModSpecs = &ModSpecs::mod; + break; + } +} + + +void CSoundFile::SetType(MODTYPE type) +{ + m_nType = type; + m_playBehaviour = GetDefaultPlaybackBehaviour(GetBestSaveFormat()); + SetModSpecsPointer(m_pModSpecs, GetBestSaveFormat()); +} + + +#ifdef MODPLUG_TRACKER + +void CSoundFile::ChangeModTypeTo(const MODTYPE newType, bool adjust) +{ + const MODTYPE oldType = GetType(); + m_nType = newType; + SetModSpecsPointer(m_pModSpecs, m_nType); + + if(oldType == newType || !adjust) + return; + + SetupMODPanning(); // Setup LRRL panning scheme if needed + + // Only keep supported play behaviour flags + PlayBehaviourSet oldAllowedFlags = GetSupportedPlaybackBehaviour(oldType); + PlayBehaviourSet newAllowedFlags = GetSupportedPlaybackBehaviour(newType); + PlayBehaviourSet newDefaultFlags = GetDefaultPlaybackBehaviour(newType); + for(size_t i = 0; i < m_playBehaviour.size(); i++) + { + // If a flag is supported in both formats, keep its status + if(m_playBehaviour[i]) m_playBehaviour.set(i, newAllowedFlags[i]); + // Set allowed flags to their defaults if they were not supported in the old format + if(!oldAllowedFlags[i]) m_playBehaviour.set(i, newDefaultFlags[i]); + } + // Special case for OPL behaviour when converting from S3M to MPTM to retain S3M-like note-off behaviour + if(oldType == MOD_TYPE_S3M && newType == MOD_TYPE_MPT && m_opl) + m_playBehaviour.reset(kOPLFlexibleNoteOff); + + Order.OnModTypeChanged(oldType); + Patterns.OnModTypeChanged(oldType); + + m_modFormat.type = mpt::ToUnicode(mpt::Charset::UTF8, GetModSpecifications().fileExtension); +} + +#endif // MODPLUG_TRACKER + + +ModMessageHeuristicOrder CSoundFile::GetMessageHeuristic() const +{ + ModMessageHeuristicOrder result = ModMessageHeuristicOrder::Default; + switch(GetType()) + { + case MOD_TYPE_MPT: + result = ModMessageHeuristicOrder::Samples; + break; + case MOD_TYPE_IT: + result = ModMessageHeuristicOrder::Samples; + break; + case MOD_TYPE_XM: + result = ModMessageHeuristicOrder::InstrumentsSamples; + break; + case MOD_TYPE_MDL: + result = ModMessageHeuristicOrder::InstrumentsSamples; + break; + case MOD_TYPE_IMF: + result = ModMessageHeuristicOrder::InstrumentsSamples; + break; + default: + result = ModMessageHeuristicOrder::Default; + break; + } + return result; +} + + +bool CSoundFile::SetTitle(const std::string &newTitle) +{ + if(m_songName != newTitle) + { + m_songName = newTitle; + return true; + } + return false; +} + + +double CSoundFile::GetPlaybackTimeAt(ORDERINDEX ord, ROWINDEX row, bool updateVars, bool updateSamplePos) +{ + const GetLengthType t = GetLength(updateVars ? (updateSamplePos ? eAdjustSamplePositions : eAdjust) : eNoAdjust, GetLengthTarget(ord, row)).back(); + if(t.targetReached) return t.duration; + else return -1; //Given position not found from play sequence. +} + + +std::vector<SubSong> CSoundFile::GetAllSubSongs() +{ + std::vector<SubSong> subSongs; + for(SEQUENCEINDEX seq = 0; seq < Order.GetNumSequences(); seq++) + { + const auto subSongsSeq = GetLength(eNoAdjust, GetLengthTarget(true).StartPos(seq, 0, 0)); + subSongs.reserve(subSongs.size() + subSongsSeq.size()); + for(const auto &song : subSongsSeq) + { + subSongs.push_back({song.duration, song.startRow, song.endRow, song.lastRow, song.startOrder, song.endOrder, song.lastOrder, seq}); + } + } + return subSongs; +} + + +// Calculate the length of a tick, depending on the tempo mode. +// This differs from GetTickDuration() by not accumulating errors +// because this is not called once per tick but in unrelated +// circumstances. So this should not update error accumulation. +void CSoundFile::RecalculateSamplesPerTick() +{ + switch(m_nTempoMode) + { + case TempoMode::Classic: + default: + m_PlayState.m_nSamplesPerTick = Util::muldiv(m_MixerSettings.gdwMixingFreq, 5 * TEMPO::fractFact, std::max(TEMPO::store_t(1), m_PlayState.m_nMusicTempo.GetRaw() << 1)); + break; + + case TempoMode::Modern: + m_PlayState.m_nSamplesPerTick = static_cast<uint32>((Util::mul32to64_unsigned(m_MixerSettings.gdwMixingFreq, 60 * TEMPO::fractFact) / std::max(uint64(1), Util::mul32to64_unsigned(m_PlayState.m_nMusicSpeed, m_PlayState.m_nCurrentRowsPerBeat) * m_PlayState.m_nMusicTempo.GetRaw()))); + break; + + case TempoMode::Alternative: + m_PlayState.m_nSamplesPerTick = Util::muldiv(m_MixerSettings.gdwMixingFreq, TEMPO::fractFact, std::max(TEMPO::store_t(1), m_PlayState.m_nMusicTempo.GetRaw())); + break; + } +#ifndef MODPLUG_TRACKER + m_PlayState.m_nSamplesPerTick = Util::muldivr(m_PlayState.m_nSamplesPerTick, m_nTempoFactor, 65536); +#endif // !MODPLUG_TRACKER + if(!m_PlayState.m_nSamplesPerTick) + m_PlayState.m_nSamplesPerTick = 1; +} + + +// Get length of a tick in sample, with tick-to-tick tempo correction in modern tempo mode. +// This has to be called exactly once per tick because otherwise the error accumulation +// goes wrong. +uint32 CSoundFile::GetTickDuration(PlayState &playState) const +{ + uint32 retval = 0; + switch(m_nTempoMode) + { + case TempoMode::Classic: + default: + retval = Util::muldiv(m_MixerSettings.gdwMixingFreq, 5 * TEMPO::fractFact, std::max(TEMPO::store_t(1), playState.m_nMusicTempo.GetRaw() << 1)); + break; + + case TempoMode::Alternative: + retval = Util::muldiv(m_MixerSettings.gdwMixingFreq, TEMPO::fractFact, std::max(TEMPO::store_t(1), playState.m_nMusicTempo.GetRaw())); + break; + + case TempoMode::Modern: + { + double accurateBufferCount = static_cast<double>(m_MixerSettings.gdwMixingFreq) * (60.0 / (playState.m_nMusicTempo.ToDouble() * Util::mul32to64_unsigned(playState.m_nMusicSpeed, playState.m_nCurrentRowsPerBeat))); + const TempoSwing &swing = (Patterns.IsValidPat(playState.m_nPattern) && Patterns[playState.m_nPattern].HasTempoSwing()) + ? Patterns[playState.m_nPattern].GetTempoSwing() + : m_tempoSwing; + if(!swing.empty()) + { + // Apply current row's tempo swing factor + TempoSwing::value_type swingFactor = swing[playState.m_nRow % swing.size()]; + accurateBufferCount = accurateBufferCount * swingFactor / double(TempoSwing::Unity); + } + uint32 bufferCount = static_cast<int>(accurateBufferCount); + playState.m_dBufferDiff += accurateBufferCount - bufferCount; + + //tick-to-tick tempo correction: + if(playState.m_dBufferDiff >= 1) + { + bufferCount++; + playState.m_dBufferDiff--; + } else if(m_PlayState.m_dBufferDiff <= -1) + { + bufferCount--; + playState.m_dBufferDiff++; + } + MPT_ASSERT(std::abs(playState.m_dBufferDiff) < 1.0); + retval = bufferCount; + } + break; + } +#ifndef MODPLUG_TRACKER + // when the user modifies the tempo, we do not really care about accurate tempo error accumulation + retval = Util::muldivr_unsigned(retval, m_nTempoFactor, 65536); +#endif // !MODPLUG_TRACKER + if(!retval) + retval = 1; + return retval; +} + + +// Get the duration of a row in milliseconds, based on the current rows per beat and given speed and tempo settings. +double CSoundFile::GetRowDuration(TEMPO tempo, uint32 speed) const +{ + switch(m_nTempoMode) + { + case TempoMode::Classic: + default: + return static_cast<double>(2500 * speed) / tempo.ToDouble(); + + case TempoMode::Modern: + { + // If there are any row delay effects, the row length factor compensates for those. + return 60000.0 / tempo.ToDouble() / static_cast<double>(m_PlayState.m_nCurrentRowsPerBeat); + } + + case TempoMode::Alternative: + return static_cast<double>(1000 * speed) / tempo.ToDouble(); + } +} + + +const CModSpecifications& CSoundFile::GetModSpecifications(const MODTYPE type) +{ + const CModSpecifications* p = nullptr; + SetModSpecsPointer(p, type); + return *p; +} + + +ChannelFlags CSoundFile::GetChannelMuteFlag() +{ +#ifdef MODPLUG_TRACKER + return (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_SYNCMUTE) ? CHN_SYNCMUTE : CHN_MUTE; +#else + return CHN_SYNCMUTE; +#endif +} + + +// Resolve note/instrument combination to real sample index. Return value is guaranteed to be in [0, GetNumSamples()]. +SAMPLEINDEX CSoundFile::GetSampleIndex(ModCommand::NOTE note, uint32 instr) const noexcept +{ + SAMPLEINDEX smp = 0; + if(GetNumInstruments()) + { + if(ModCommand::IsNote(note) && instr <= GetNumInstruments() && Instruments[instr] != nullptr) + smp = Instruments[instr]->Keyboard[note - NOTE_MIN]; + } else + { + smp = static_cast<SAMPLEINDEX>(instr); + } + if(smp <= GetNumSamples()) + return smp; + else + return 0; +} + + +// Find an unused sample slot. If it is going to be assigned to an instrument, targetInstrument should be specified. +// SAMPLEINDEX_INVLAID is returned if no free sample slot could be found. +SAMPLEINDEX CSoundFile::GetNextFreeSample(INSTRUMENTINDEX targetInstrument, SAMPLEINDEX start) const +{ + // Find empty slot in two passes - in the first pass, we only search for samples with empty sample names, + // in the second pass we check all samples with non-empty sample names. + for(int passes = 0; passes < 2; passes++) + { + for(SAMPLEINDEX i = start; i <= GetModSpecifications().samplesMax; i++) + { + // Early exit for FM instruments + if(Samples[i].uFlags[CHN_ADLIB] && (targetInstrument == INSTRUMENTINDEX_INVALID || !IsSampleReferencedByInstrument(i, targetInstrument))) + continue; + + // When loading into an instrument, ignore non-empty sample names. Else, only use this slot if the sample name is empty or we're in second pass. + if((i > GetNumSamples() && passes == 1) + || (!Samples[i].HasSampleData() && (!m_szNames[i][0] || passes == 1 || targetInstrument != INSTRUMENTINDEX_INVALID)) + || (targetInstrument != INSTRUMENTINDEX_INVALID && IsSampleReferencedByInstrument(i, targetInstrument))) // Not empty, but already used by this instrument. XXX this should only be done when replacing an instrument with a single sample! Otherwise it will use an inconsistent sample map! + { + // Empty slot, so it's a good candidate already. + + // In instrument mode, check whether any instrument references this sample slot. If that is the case, we won't use it as it could lead to unwanted conflicts. + // If we are loading the sample *into* an instrument, we should also not consider that instrument's sample map, since it might be inconsistent at this time. + bool isReferenced = false; + for(INSTRUMENTINDEX ins = 1; ins <= GetNumInstruments(); ins++) + { + if(ins == targetInstrument) + { + continue; + } + if(IsSampleReferencedByInstrument(i, ins)) + { + isReferenced = true; + break; + } + } + if(!isReferenced) + { + return i; + } + } + } + } + + return SAMPLEINDEX_INVALID; +} + + +// Find an unused instrument slot. +// INSTRUMENTINDEX_INVALID is returned if no free instrument slot could be found. +INSTRUMENTINDEX CSoundFile::GetNextFreeInstrument(INSTRUMENTINDEX start) const +{ + for(INSTRUMENTINDEX i = start; i <= GetModSpecifications().instrumentsMax; i++) + { + if(Instruments[i] == nullptr) + { + return i; + } + } + + return INSTRUMENTINDEX_INVALID; +} + + +// Check whether a given sample is used by a given instrument. +bool CSoundFile::IsSampleReferencedByInstrument(SAMPLEINDEX sample, INSTRUMENTINDEX instr) const +{ + if(instr < 1 || instr > GetNumInstruments()) + return false; + + const ModInstrument *targetIns = Instruments[instr]; + if(targetIns == nullptr) + return false; + + return mpt::contains(mpt::as_span(targetIns->Keyboard).first(NOTE_MAX), sample); +} + + +ModInstrument *CSoundFile::AllocateInstrument(INSTRUMENTINDEX instr, SAMPLEINDEX assignedSample) +{ + if(instr == 0 || instr >= MAX_INSTRUMENTS) + { + return nullptr; + } + + ModInstrument *ins = Instruments[instr]; + if(ins != nullptr) + { + // Re-initialize instrument + *ins = ModInstrument(assignedSample); + } else + { + // Create new instrument + Instruments[instr] = ins = new (std::nothrow) ModInstrument(assignedSample); + } + if(ins != nullptr) + { + m_nInstruments = std::max(m_nInstruments, instr); + } + return ins; +} + + +void CSoundFile::PrecomputeSampleLoops(bool updateChannels) +{ + for(SAMPLEINDEX i = 1; i <= GetNumSamples(); i++) + { + Samples[i].PrecomputeLoops(*this, updateChannels); + } +} + + +#ifdef MPT_EXTERNAL_SAMPLES +// Load external waveform, but keep sample properties like frequency, panning, etc... +// Returns true if the file could be loaded. +bool CSoundFile::LoadExternalSample(SAMPLEINDEX smp, const mpt::PathString &filename) +{ + bool ok = false; + InputFile f(filename, SettingCacheCompleteFileBeforeLoading()); + + if(f.IsValid()) + { + const ModSample origSample = Samples[smp]; + mpt::charbuf<MAX_SAMPLENAME> origName; + origName = m_szNames[smp]; + + FileReader file = GetFileReader(f); + ok = ReadSampleFromFile(smp, file, false); + if(ok) + { + // Copy over old attributes, but keep new sample data + ModSample &sample = GetSample(smp); + SmpLength newLength = sample.nLength; + void *newData = sample.samplev(); + SampleFlags newFlags = sample.uFlags; + + sample = origSample; + sample.nLength = newLength; + sample.pData.pSample = newData; + sample.uFlags.set(CHN_16BIT, newFlags[CHN_16BIT]); + sample.uFlags.set(CHN_STEREO, newFlags[CHN_STEREO]); + sample.uFlags.reset(SMP_MODIFIED); + sample.SanitizeLoops(); + } + m_szNames[smp] = origName; + } + SetSamplePath(smp, filename); + return ok; +} +#endif // MPT_EXTERNAL_SAMPLES + + +// Set up channel panning and volume suitable for MOD + similar files. If the current mod type is not MOD, bForceSetup has to be set to true. +void CSoundFile::SetupMODPanning(bool bForceSetup) +{ + // Setup LRRL panning, max channel volume + if(!(GetType() & MOD_TYPE_MOD) && bForceSetup == false) return; + + for(CHANNELINDEX nChn = 0; nChn < MAX_BASECHANNELS; nChn++) + { + ChnSettings[nChn].nVolume = 64; + ChnSettings[nChn].dwFlags.reset(CHN_SURROUND); + if(m_MixerSettings.MixerFlags & SNDMIX_MAXDEFAULTPAN) + ChnSettings[nChn].nPan = (((nChn & 3) == 1) || ((nChn & 3) == 2)) ? 256 : 0; + else + ChnSettings[nChn].nPan = (((nChn & 3) == 1) || ((nChn & 3) == 2)) ? 0xC0 : 0x40; + } +} + + +void CSoundFile::PropagateXMAutoVibrato(INSTRUMENTINDEX ins, VibratoType type, uint8 sweep, uint8 depth, uint8 rate) +{ + if(ins > m_nInstruments || Instruments[ins] == nullptr) + return; + const std::set<SAMPLEINDEX> referencedSamples = Instruments[ins]->GetSamples(); + + // Propagate changes to all samples that belong to this instrument. + for(auto sample : referencedSamples) + { + if(sample <= m_nSamples) + { + Samples[sample].nVibDepth = depth; + Samples[sample].nVibType = type; + Samples[sample].nVibRate = rate; + Samples[sample].nVibSweep = sweep; + } + } +} + + +// Normalize the tempo swing coefficients so that they add up to exactly the specified tempo again +void TempoSwing::Normalize() +{ + if(empty()) return; + uint64 sum = 0; + for(auto &i : *this) + { + Limit(i, Unity / 4u, Unity * 4u); + sum += i; + } + sum /= size(); + MPT_ASSERT(sum > 0); // clang-analyzer false-positive + int64 remain = Unity * size(); + for(auto &i : *this) + { + i = Util::muldivr_unsigned(i, Unity, static_cast<int32>(sum)); + remain -= i; + } + //MPT_ASSERT(static_cast<uint32>(std::abs(static_cast<int32>(remain))) <= size()); + at(0) += static_cast<int32>(remain); +} + + +void TempoSwing::Serialize(std::ostream &oStrm, const TempoSwing &swing) +{ + mpt::IO::WriteIntLE<uint16>(oStrm, static_cast<uint16>(swing.size())); + for(std::size_t i = 0; i < swing.size(); i++) + { + mpt::IO::WriteIntLE<uint32>(oStrm, swing[i]); + } +} + + +void TempoSwing::Deserialize(std::istream &iStrm, TempoSwing &swing, const size_t) +{ + uint16 numEntries; + mpt::IO::ReadIntLE<uint16>(iStrm, numEntries); + swing.resize(numEntries); + for(uint16 i = 0; i < numEntries; i++) + { + mpt::IO::ReadIntLE<uint32>(iStrm, swing[i]); + } + swing.Normalize(); +} + + +OPENMPT_NAMESPACE_END |