1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083 |
- /*
- * 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
|