123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775 |
- /*
- * InstrumentExtensions.cpp
- * ------------------------
- * Purpose: Instrument properties I/O
- * Notes : Welcome to the absolutely horrible abominations that are the "extended instrument properties"
- * which are some of the earliest additions OpenMPT did to the IT / XM format. They are ugly,
- * and the way they work even differs between IT/XM and ITI/XI/ITP.
- * Yes, the world would be a better place without this stuff.
- * Authors: OpenMPT Devs
- * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
- */
- #include "stdafx.h"
- #include "Loaders.h"
- #ifndef MODPLUG_NO_FILESAVE
- #include "mpt/io/base.hpp"
- #include "mpt/io/io.hpp"
- #include "mpt/io/io_stdstream.hpp"
- #endif
- OPENMPT_NAMESPACE_BEGIN
- /*---------------------------------------------------------------------------------------------
- -----------------------------------------------------------------------------------------------
- MODULAR (in/out) ModInstrument :
- -----------------------------------------------------------------------------------------------
- * to update:
- ------------
- - both following functions need to be updated when adding a new member in ModInstrument :
- void WriteInstrumentHeaderStructOrField(ModInstrument * input, std::ostream &file, uint32 only_this_code, int16 fixedsize);
- bool ReadInstrumentHeaderField(ModInstrument * input, uint32 fcode, int16 fsize, FileReader &file);
- - see below for body declaration.
- * members:
- ----------
- - 32bit identification CODE tag (must be unique)
- - 16bit content SIZE in byte(s)
- - member field
- * CODE tag naming convention:
- -----------------------------
- - have a look below in current tag dictionnary
- - take the initial ones of the field name
- - 4 caracters code (not more, not less)
- - must be filled with '.' caracters if code has less than 4 caracters
- - for arrays, must include a '[' caracter following significant caracters ('.' not significant!!!)
- - use only caracters used in full member name, ordered as they appear in it
- - match caracter attribute (small,capital)
- Example with "PanEnv.nLoopEnd" , "PitchEnv.nLoopEnd" & "VolEnv.Values[MAX_ENVPOINTS]" members :
- - use 'PLE.' for PanEnv.nLoopEnd
- - use 'PiLE' for PitchEnv.nLoopEnd
- - use 'VE[.' for VolEnv.Values[MAX_ENVPOINTS]
- * In use CODE tag dictionary (alphabetical order):
- --------------------------------------------------
- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- !!! SECTION TO BE UPDATED !!!
- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- [EXT] means external (not related) to ModInstrument content
- AUTH [EXT] Song artist
- C... [EXT] nChannels
- ChnS [EXT] IT/MPTM: Channel settings for channels 65-127 if needed (doesn't fit to IT header).
- CS.. nCutSwing
- CUES [EXT] Sample cue points
- CWV. [EXT] dwCreatedWithVersion
- DCT. nDCT;
- dF.. dwFlags;
- DGV. [EXT] nDefaultGlobalVolume
- DT.. [EXT] nDefaultTempo;
- DTFR [EXT] Fractional part of default tempo
- DNA. nDNA;
- EBIH [EXT] embeded instrument header tag (ITP file format)
- FM.. filterMode;
- fn[. filename[12];
- FO.. nFadeOut;
- GV.. nGlobalVol;
- IFC. nIFC;
- IFR. nIFR;
- K[. Keyboard[128];
- LSWV [EXT] Last Saved With Version
- MB.. wMidiBank;
- MC.. nMidiChannel;
- MDK. nMidiDrumKey;
- MIMA [EXT] MIdi MApping directives
- MiP. nMixPlug;
- MP.. nMidiProgram;
- MPTS [EXT] Extra song info tag
- MPTX [EXT] EXTRA INFO tag
- MSF. [EXT] Mod(Specific)Flags
- n[.. name[32];
- NNA. nNNA;
- NM[. NoteMap[128];
- P... nPan;
- PE.. PanEnv.nNodes;
- PE[. PanEnv.Values[MAX_ENVPOINTS];
- PiE. PitchEnv.nNodes;
- PiE[ PitchEnv.Values[MAX_ENVPOINTS];
- PiLE PitchEnv.nLoopEnd;
- PiLS PitchEnv.nLoopStart;
- PiP[ PitchEnv.Ticks[MAX_ENVPOINTS];
- PiSB PitchEnv.nSustainStart;
- PiSE PitchEnv.nSustainEnd;
- PLE. PanEnv.nLoopEnd;
- PLS. PanEnv.nLoopStart;
- PMM. [EXT] nPlugMixMode;
- PP[. PanEnv.Ticks[MAX_ENVPOINTS];
- PPC. nPPC;
- PPS. nPPS;
- PS.. nPanSwing;
- PSB. PanEnv.nSustainStart;
- PSE. PanEnv.nSustainEnd;
- PTTL pitchToTempoLock;
- PTTF pitchToTempoLock (fractional part);
- PVEH pluginVelocityHandling;
- PVOH pluginVolumeHandling;
- R... resampling;
- RP.. [EXT] nRestartPos;
- RPB. [EXT] nRowsPerBeat;
- RPM. [EXT] nRowsPerMeasure;
- RS.. nResSwing;
- RSMP [EXT] Global resampling
- SEP@ [EXT] chunk SEPARATOR tag
- SPA. [EXT] m_nSamplePreAmp;
- TM.. [EXT] nTempoMode;
- VE.. VolEnv.nNodes;
- VE[. VolEnv.Values[MAX_ENVPOINTS];
- VLE. VolEnv.nLoopEnd;
- VLS. VolEnv.nLoopStart;
- VP[. VolEnv.Ticks[MAX_ENVPOINTS];
- VR.. nVolRampUp;
- VS.. nVolSwing;
- VSB. VolEnv.nSustainStart;
- VSE. VolEnv.nSustainEnd;
- VSTV [EXT] nVSTiVolume;
- PERN PitchEnv.nReleaseNode
- AERN PanEnv.nReleaseNode
- VERN VolEnv.nReleaseNode
- PFLG PitchEnv.dwFlag
- AFLG PanEnv.dwFlags
- VFLG VolEnv.dwFlags
- MPWD MIDI Pitch Wheel Depth
- -----------------------------------------------------------------------------------------------
- ---------------------------------------------------------------------------------------------*/
- #ifndef MODPLUG_NO_FILESAVE
- template<typename T, bool is_signed> struct IsNegativeFunctor { bool operator()(T val) const { return val < 0; } };
- template<typename T> struct IsNegativeFunctor<T, true> { bool operator()(T val) const { return val < 0; } };
- template<typename T> struct IsNegativeFunctor<T, false> { bool operator()(T /*val*/) const { return false; } };
- template<typename T>
- bool IsNegative(const T &val)
- {
- return IsNegativeFunctor<T, std::numeric_limits<T>::is_signed>()(val);
- }
- // ------------------------------------------------------------------------------------------
- // Convenient macro to help WRITE_HEADER declaration for single type members ONLY (non-array)
- // ------------------------------------------------------------------------------------------
- #define WRITE_MPTHEADER_sized_member(name,type,code) \
- static_assert(sizeof(input->name) == sizeof(type), "Instrument property does match specified type!");\
- fcode = code;\
- fsize = sizeof( type );\
- if(writeAll) \
- { \
- mpt::IO::WriteIntLE<uint32>(file, fcode); \
- mpt::IO::WriteIntLE<uint16>(file, fsize); \
- } else if(only_this_code == fcode)\
- { \
- MPT_ASSERT(fixedsize == fsize); \
- } \
- if(only_this_code == fcode || only_this_code == Util::MaxValueOfType(only_this_code)) \
- { \
- type tmp = (type)(input-> name ); \
- mpt::IO::WriteIntLE(file, tmp); \
- } \
- /**/
- // -----------------------------------------------------------------------------------------------------
- // Convenient macro to help WRITE_HEADER declaration for single type members which are written truncated
- // -----------------------------------------------------------------------------------------------------
- #define WRITE_MPTHEADER_trunc_member(name,type,code) \
- static_assert(sizeof(input->name) > sizeof(type), "Instrument property would not be truncated, use WRITE_MPTHEADER_sized_member instead!");\
- fcode = code;\
- fsize = sizeof( type );\
- if(writeAll) \
- { \
- mpt::IO::WriteIntLE<uint32>(file, fcode); \
- mpt::IO::WriteIntLE<uint16>(file, fsize); \
- type tmp = (type)(input-> name ); \
- mpt::IO::WriteIntLE(file, tmp); \
- } else if(only_this_code == fcode)\
- { \
- /* hackish workaround to resolve mismatched size values: */ \
- /* nResampling was a long time declared as uint32 but these macro tables used uint16 and UINT. */ \
- /* This worked fine on little-endian, on big-endian not so much. Thus support writing size-mismatched fields. */ \
- MPT_ASSERT(fixedsize >= fsize); \
- type tmp = (type)(input-> name ); \
- mpt::IO::WriteIntLE(file, tmp); \
- if(fixedsize > fsize) \
- { \
- for(int16 i = 0; i < fixedsize - fsize; ++i) \
- { \
- uint8 fillbyte = !IsNegative(tmp) ? 0 : 0xff; /* sign extend */ \
- mpt::IO::WriteIntLE(file, fillbyte); \
- } \
- } \
- } \
- /**/
- // ------------------------------------------------------------------------
- // Convenient macro to help WRITE_HEADER declaration for array members ONLY
- // ------------------------------------------------------------------------
- #define WRITE_MPTHEADER_array_member(name,type,code,arraysize) \
- static_assert(sizeof(type) == sizeof(input-> name [0])); \
- MPT_ASSERT(sizeof(input->name) >= sizeof(type) * arraysize);\
- fcode = code;\
- fsize = sizeof( type ) * arraysize;\
- if(writeAll) \
- { \
- mpt::IO::WriteIntLE<uint32>(file, fcode); \
- mpt::IO::WriteIntLE<uint16>(file, fsize); \
- } else if(only_this_code == fcode)\
- { \
- /* MPT_ASSERT(fixedsize <= fsize); */ \
- fsize = fixedsize; /* just trust the size we got passed */ \
- } \
- if(only_this_code == fcode || only_this_code == Util::MaxValueOfType(only_this_code)) \
- { \
- for(std::size_t i = 0; i < fsize/sizeof(type); ++i) \
- { \
- type tmp; \
- tmp = input-> name [i]; \
- mpt::IO::WriteIntLE(file, tmp); \
- } \
- } \
- /**/
- // ------------------------------------------------------------------------
- // Convenient macro to help WRITE_HEADER declaration for envelope members ONLY
- // ------------------------------------------------------------------------
- #define WRITE_MPTHEADER_envelope_member(envType,envField,type,code) \
- {\
- const InstrumentEnvelope &env = input->GetEnvelope(envType); \
- static_assert(sizeof(type) == sizeof(env[0]. envField)); \
- fcode = code;\
- fsize = mpt::saturate_cast<int16>(sizeof( type ) * env.size());\
- MPT_ASSERT(size_t(fsize) == sizeof( type ) * env.size()); \
- \
- if(writeAll) \
- { \
- mpt::IO::WriteIntLE<uint32>(file, fcode); \
- mpt::IO::WriteIntLE<uint16>(file, fsize); \
- } else if(only_this_code == fcode)\
- { \
- fsize = fixedsize; /* just trust the size we got passed */ \
- } \
- if(only_this_code == fcode || only_this_code == Util::MaxValueOfType(only_this_code)) \
- { \
- uint32 maxNodes = std::min(static_cast<uint32>(fsize/sizeof(type)), static_cast<uint32>(env.size())); \
- for(uint32 i = 0; i < maxNodes; ++i) \
- { \
- type tmp; \
- tmp = env[i]. envField ; \
- mpt::IO::WriteIntLE(file, tmp); \
- } \
- /* Not every instrument's envelope will be the same length. fill up with zeros. */ \
- for(uint32 i = maxNodes; i < fsize/sizeof(type); ++i) \
- { \
- type tmp = 0; \
- mpt::IO::WriteIntLE(file, tmp); \
- } \
- } \
- }\
- /**/
- // Write (in 'file') 'input' ModInstrument with 'code' & 'size' extra field infos for each member
- void WriteInstrumentHeaderStructOrField(ModInstrument * input, std::ostream &file, uint32 only_this_code, uint16 fixedsize)
- {
- uint32 fcode;
- uint16 fsize;
- // If true, all extension are written to the file; otherwise only the specified extension is written.
- // writeAll is true iff we are saving an instrument (or, hypothetically, the legacy ITP format)
- const bool writeAll = only_this_code == Util::MaxValueOfType(only_this_code);
- if(!writeAll)
- {
- MPT_ASSERT(fixedsize > 0);
- }
- // clang-format off
- WRITE_MPTHEADER_sized_member( nFadeOut , uint32 , MagicBE("FO..") )
- WRITE_MPTHEADER_sized_member( nPan , uint32 , MagicBE("P...") )
- WRITE_MPTHEADER_sized_member( VolEnv.size() , uint32 , MagicBE("VE..") )
- WRITE_MPTHEADER_sized_member( PanEnv.size() , uint32 , MagicBE("PE..") )
- WRITE_MPTHEADER_sized_member( PitchEnv.size() , uint32 , MagicBE("PiE.") )
- WRITE_MPTHEADER_sized_member( wMidiBank , uint16 , MagicBE("MB..") )
- WRITE_MPTHEADER_sized_member( nMidiProgram , uint8 , MagicBE("MP..") )
- WRITE_MPTHEADER_sized_member( nMidiChannel , uint8 , MagicBE("MC..") )
- WRITE_MPTHEADER_envelope_member( ENV_VOLUME , tick , uint16 , MagicBE("VP[.") )
- WRITE_MPTHEADER_envelope_member( ENV_PANNING , tick , uint16 , MagicBE("PP[.") )
- WRITE_MPTHEADER_envelope_member( ENV_PITCH , tick , uint16 , MagicBE("PiP[") )
- WRITE_MPTHEADER_envelope_member( ENV_VOLUME , value , uint8 , MagicBE("VE[.") )
- WRITE_MPTHEADER_envelope_member( ENV_PANNING , value , uint8 , MagicBE("PE[.") )
- WRITE_MPTHEADER_envelope_member( ENV_PITCH , value , uint8 , MagicBE("PiE[") )
- WRITE_MPTHEADER_sized_member( nMixPlug , uint8 , MagicBE("MiP.") )
- WRITE_MPTHEADER_sized_member( nVolRampUp , uint16 , MagicBE("VR..") )
- WRITE_MPTHEADER_sized_member( resampling , uint8 , MagicBE("R...") )
- WRITE_MPTHEADER_sized_member( nCutSwing , uint8 , MagicBE("CS..") )
- WRITE_MPTHEADER_sized_member( nResSwing , uint8 , MagicBE("RS..") )
- WRITE_MPTHEADER_sized_member( filterMode , uint8 , MagicBE("FM..") )
- WRITE_MPTHEADER_sized_member( pluginVelocityHandling , uint8 , MagicBE("PVEH") )
- WRITE_MPTHEADER_sized_member( pluginVolumeHandling , uint8 , MagicBE("PVOH") )
- WRITE_MPTHEADER_trunc_member( pitchToTempoLock.GetInt() , uint16 , MagicBE("PTTL") )
- WRITE_MPTHEADER_trunc_member( pitchToTempoLock.GetFract() , uint16 , MagicLE("PTTF") )
- WRITE_MPTHEADER_sized_member( PitchEnv.nReleaseNode , uint8 , MagicBE("PERN") )
- WRITE_MPTHEADER_sized_member( PanEnv.nReleaseNode , uint8 , MagicBE("AERN") )
- WRITE_MPTHEADER_sized_member( VolEnv.nReleaseNode , uint8 , MagicBE("VERN") )
- WRITE_MPTHEADER_sized_member( PitchEnv.dwFlags , uint8 , MagicBE("PFLG") )
- WRITE_MPTHEADER_sized_member( PanEnv.dwFlags , uint8 , MagicBE("AFLG") )
- WRITE_MPTHEADER_sized_member( VolEnv.dwFlags , uint8 , MagicBE("VFLG") )
- WRITE_MPTHEADER_sized_member( midiPWD , int8 , MagicBE("MPWD") )
- // clang-format on
- }
- template<typename TIns, typename PropType>
- static bool IsPropertyNeeded(const TIns &Instruments, PropType ModInstrument::*Prop)
- {
- const ModInstrument defaultIns;
- for(const auto &ins : Instruments)
- {
- if(ins != nullptr && defaultIns.*Prop != ins->*Prop)
- return true;
- }
- return false;
- }
- template<typename PropType>
- static void WritePropertyIfNeeded(const CSoundFile &sndFile, PropType ModInstrument::*Prop, uint32 code, uint16 size, std::ostream &f, INSTRUMENTINDEX numInstruments)
- {
- if(IsPropertyNeeded(sndFile.Instruments, Prop))
- {
- sndFile.WriteInstrumentPropertyForAllInstruments(code, size, f, numInstruments);
- }
- }
- // Used only when saving IT, XM and MPTM.
- // ITI, ITP saves using Ericus' macros etc...
- // The reason is that ITs and XMs save [code][size][ins1.Value][ins2.Value]...
- // whereas ITP saves [code][size][ins1.Value][code][size][ins2.Value]...
- // too late to turn back....
- void CSoundFile::SaveExtendedInstrumentProperties(INSTRUMENTINDEX numInstruments, std::ostream &f) const
- {
- uint32 code = MagicBE("MPTX"); // write extension header code
- mpt::IO::WriteIntLE<uint32>(f, code);
- if (numInstruments == 0)
- return;
- WritePropertyIfNeeded(*this, &ModInstrument::nVolRampUp, MagicBE("VR.."), sizeof(ModInstrument::nVolRampUp), f, numInstruments);
- WritePropertyIfNeeded(*this, &ModInstrument::nMixPlug, MagicBE("MiP."), sizeof(ModInstrument::nMixPlug), f, numInstruments);
- WritePropertyIfNeeded(*this, &ModInstrument::nMidiChannel, MagicBE("MC.."), sizeof(ModInstrument::nMidiChannel), f, numInstruments);
- WritePropertyIfNeeded(*this, &ModInstrument::nMidiProgram, MagicBE("MP.."), sizeof(ModInstrument::nMidiProgram), f, numInstruments);
- WritePropertyIfNeeded(*this, &ModInstrument::wMidiBank, MagicBE("MB.."), sizeof(ModInstrument::wMidiBank), f, numInstruments);
- WritePropertyIfNeeded(*this, &ModInstrument::resampling, MagicBE("R..."), sizeof(ModInstrument::resampling), f, numInstruments);
- WritePropertyIfNeeded(*this, &ModInstrument::pluginVelocityHandling, MagicBE("PVEH"), sizeof(ModInstrument::pluginVelocityHandling), f, numInstruments);
- WritePropertyIfNeeded(*this, &ModInstrument::pluginVolumeHandling, MagicBE("PVOH"), sizeof(ModInstrument::pluginVolumeHandling), f, numInstruments);
- if(!(GetType() & MOD_TYPE_XM))
- {
- // XM instrument headers already stores full-precision fade-out
- WritePropertyIfNeeded(*this, &ModInstrument::nFadeOut, MagicBE("FO.."), sizeof(ModInstrument::nFadeOut), f, numInstruments);
- // XM instrument headers already have support for this
- WritePropertyIfNeeded(*this, &ModInstrument::midiPWD, MagicBE("MPWD"), sizeof(ModInstrument::midiPWD), f, numInstruments);
- // We never supported these as hacks in XM (luckily!)
- WritePropertyIfNeeded(*this, &ModInstrument::nPan, MagicBE("P..."), sizeof(ModInstrument::nPan), f, numInstruments);
- WritePropertyIfNeeded(*this, &ModInstrument::nCutSwing, MagicBE("CS.."), sizeof(ModInstrument::nCutSwing), f, numInstruments);
- WritePropertyIfNeeded(*this, &ModInstrument::nResSwing, MagicBE("RS.."), sizeof(ModInstrument::nResSwing), f, numInstruments);
- WritePropertyIfNeeded(*this, &ModInstrument::filterMode, MagicBE("FM.."), sizeof(ModInstrument::filterMode), f, numInstruments);
- if(IsPropertyNeeded(Instruments, &ModInstrument::pitchToTempoLock))
- {
- WriteInstrumentPropertyForAllInstruments(MagicBE("PTTL"), sizeof(uint16), f, numInstruments);
- WriteInstrumentPropertyForAllInstruments(MagicLE("PTTF"), sizeof(uint16), f, numInstruments);
- }
- }
- if(GetType() & MOD_TYPE_MPT)
- {
- uint32 maxNodes[3] = { 0, 0, 0 };
- bool hasReleaseNode[3] = { false, false, false };
- for(INSTRUMENTINDEX i = 1; i <= numInstruments; i++) if(Instruments[i] != nullptr)
- {
- maxNodes[0] = std::max(maxNodes[0], Instruments[i]->VolEnv.size());
- maxNodes[1] = std::max(maxNodes[1], Instruments[i]->PanEnv.size());
- maxNodes[2] = std::max(maxNodes[2], Instruments[i]->PitchEnv.size());
- hasReleaseNode[0] |= (Instruments[i]->VolEnv.nReleaseNode != ENV_RELEASE_NODE_UNSET);
- hasReleaseNode[1] |= (Instruments[i]->PanEnv.nReleaseNode != ENV_RELEASE_NODE_UNSET);
- hasReleaseNode[2] |= (Instruments[i]->PitchEnv.nReleaseNode != ENV_RELEASE_NODE_UNSET);
- }
- // write full envelope information for MPTM files (more env points)
- if(maxNodes[0] > 25)
- {
- WriteInstrumentPropertyForAllInstruments(MagicBE("VE.."), sizeof(ModInstrument::VolEnv.size()), f, numInstruments);
- WriteInstrumentPropertyForAllInstruments(MagicBE("VP[."), static_cast<uint16>(maxNodes[0] * sizeof(EnvelopeNode::tick)), f, numInstruments);
- WriteInstrumentPropertyForAllInstruments(MagicBE("VE[."), static_cast<uint16>(maxNodes[0] * sizeof(EnvelopeNode::value)), f, numInstruments);
- }
- if(maxNodes[1] > 25)
- {
- WriteInstrumentPropertyForAllInstruments(MagicBE("PE.."), sizeof(ModInstrument::PanEnv.size()), f, numInstruments);
- WriteInstrumentPropertyForAllInstruments(MagicBE("PP[."), static_cast<uint16>(maxNodes[1] * sizeof(EnvelopeNode::tick)), f, numInstruments);
- WriteInstrumentPropertyForAllInstruments(MagicBE("PE[."), static_cast<uint16>(maxNodes[1] * sizeof(EnvelopeNode::value)), f, numInstruments);
- }
- if(maxNodes[2] > 25)
- {
- WriteInstrumentPropertyForAllInstruments(MagicBE("PiE."), sizeof(ModInstrument::PitchEnv.size()), f, numInstruments);
- WriteInstrumentPropertyForAllInstruments(MagicBE("PiP["), static_cast<uint16>(maxNodes[2] * sizeof(EnvelopeNode::tick)), f, numInstruments);
- WriteInstrumentPropertyForAllInstruments(MagicBE("PiE["), static_cast<uint16>(maxNodes[2] * sizeof(EnvelopeNode::value)), f, numInstruments);
- }
- if(hasReleaseNode[0])
- WriteInstrumentPropertyForAllInstruments(MagicBE("VERN"), sizeof(ModInstrument::VolEnv.nReleaseNode), f, numInstruments);
- if(hasReleaseNode[1])
- WriteInstrumentPropertyForAllInstruments(MagicBE("AERN"), sizeof(ModInstrument::PanEnv.nReleaseNode), f, numInstruments);
- if(hasReleaseNode[2])
- WriteInstrumentPropertyForAllInstruments(MagicBE("PERN"), sizeof(ModInstrument::PitchEnv.nReleaseNode), f, numInstruments);
- }
- }
- void CSoundFile::WriteInstrumentPropertyForAllInstruments(uint32 code, uint16 size, std::ostream &f, INSTRUMENTINDEX nInstruments) const
- {
- mpt::IO::WriteIntLE<uint32>(f, code); //write code
- mpt::IO::WriteIntLE<uint16>(f, size); //write size
- for(INSTRUMENTINDEX i = 1; i <= nInstruments; i++) //for all instruments...
- {
- if (Instruments[i])
- {
- WriteInstrumentHeaderStructOrField(Instruments[i], f, code, size);
- } else
- {
- ModInstrument emptyInstrument;
- WriteInstrumentHeaderStructOrField(&emptyInstrument, f, code, size);
- }
- }
- }
- #endif // !MODPLUG_NO_FILESAVE
- // --------------------------------------------------------------------------------------------
- // Convenient macro to help GET_HEADER declaration for single type members ONLY (non-array)
- // --------------------------------------------------------------------------------------------
- #define GET_MPTHEADER_sized_member(name,type,code) \
- case code: \
- {\
- if( fsize <= sizeof( type ) ) \
- { \
- /* hackish workaround to resolve mismatched size values: */ \
- /* nResampling was a long time declared as uint32 but these macro tables used uint16 and UINT. */ \
- /* This worked fine on little-endian, on big-endian not so much. Thus support reading size-mismatched fields. */ \
- if(file.CanRead(fsize)) \
- { \
- type tmp; \
- tmp = file.ReadTruncatedIntLE<type>(fsize); \
- static_assert(sizeof(tmp) == sizeof(input-> name )); \
- input-> name = decltype(input-> name )(tmp); \
- result = true; \
- } \
- } \
- } break;
- // --------------------------------------------------------------------------------------------
- // Convenient macro to help GET_HEADER declaration for array members ONLY
- // --------------------------------------------------------------------------------------------
- #define GET_MPTHEADER_array_member(name,type,code) \
- case code: \
- {\
- if( fsize <= sizeof( type ) * std::size(input-> name) ) \
- { \
- FileReader arrayChunk = file.ReadChunk(fsize); \
- for(std::size_t i = 0; i < std::size(input-> name); ++i) \
- { \
- input-> name [i] = arrayChunk.ReadIntLE<type>(); \
- } \
- result = true; \
- } \
- } break;
- // --------------------------------------------------------------------------------------------
- // Convenient macro to help GET_HEADER declaration for character buffer members ONLY
- // --------------------------------------------------------------------------------------------
- #define GET_MPTHEADER_charbuf_member(name,type,code) \
- case code: \
- {\
- if( fsize <= sizeof( type ) * input-> name .static_length() ) \
- { \
- FileReader arrayChunk = file.ReadChunk(fsize); \
- std::string tmp; \
- for(std::size_t i = 0; i < fsize; ++i) \
- { \
- tmp += arrayChunk.ReadChar(); \
- } \
- input-> name = tmp; \
- result = true; \
- } \
- } break;
- // --------------------------------------------------------------------------------------------
- // Convenient macro to help GET_HEADER declaration for envelope tick/value members
- // --------------------------------------------------------------------------------------------
- #define GET_MPTHEADER_envelope_member(envType,envField,type,code) \
- case code: \
- {\
- FileReader arrayChunk = file.ReadChunk(fsize); \
- InstrumentEnvelope &env = input->GetEnvelope(envType); \
- for(uint32 i = 0; i < env.size(); i++) \
- { \
- env[i]. envField = arrayChunk.ReadIntLE<type>(); \
- } \
- result = true; \
- } break;
- // Return a pointer on the wanted field in 'input' ModInstrument given field code & size
- bool ReadInstrumentHeaderField(ModInstrument *input, uint32 fcode, uint16 fsize, FileReader &file)
- {
- if(input == nullptr) return false;
- bool result = false;
- // Members which can be found in this table but not in the write table are only required in the legacy ITP format.
- switch(fcode)
- {
- // clang-format off
- GET_MPTHEADER_sized_member( nFadeOut , uint32 , MagicBE("FO..") )
- GET_MPTHEADER_sized_member( dwFlags , uint8 , MagicBE("dF..") )
- GET_MPTHEADER_sized_member( nGlobalVol , uint32 , MagicBE("GV..") )
- GET_MPTHEADER_sized_member( nPan , uint32 , MagicBE("P...") )
- GET_MPTHEADER_sized_member( VolEnv.nLoopStart , uint8 , MagicBE("VLS.") )
- GET_MPTHEADER_sized_member( VolEnv.nLoopEnd , uint8 , MagicBE("VLE.") )
- GET_MPTHEADER_sized_member( VolEnv.nSustainStart , uint8 , MagicBE("VSB.") )
- GET_MPTHEADER_sized_member( VolEnv.nSustainEnd , uint8 , MagicBE("VSE.") )
- GET_MPTHEADER_sized_member( PanEnv.nLoopStart , uint8 , MagicBE("PLS.") )
- GET_MPTHEADER_sized_member( PanEnv.nLoopEnd , uint8 , MagicBE("PLE.") )
- GET_MPTHEADER_sized_member( PanEnv.nSustainStart , uint8 , MagicBE("PSB.") )
- GET_MPTHEADER_sized_member( PanEnv.nSustainEnd , uint8 , MagicBE("PSE.") )
- GET_MPTHEADER_sized_member( PitchEnv.nLoopStart , uint8 , MagicBE("PiLS") )
- GET_MPTHEADER_sized_member( PitchEnv.nLoopEnd , uint8 , MagicBE("PiLE") )
- GET_MPTHEADER_sized_member( PitchEnv.nSustainStart , uint8 , MagicBE("PiSB") )
- GET_MPTHEADER_sized_member( PitchEnv.nSustainEnd , uint8 , MagicBE("PiSE") )
- GET_MPTHEADER_sized_member( nNNA , uint8 , MagicBE("NNA.") )
- GET_MPTHEADER_sized_member( nDCT , uint8 , MagicBE("DCT.") )
- GET_MPTHEADER_sized_member( nDNA , uint8 , MagicBE("DNA.") )
- GET_MPTHEADER_sized_member( nPanSwing , uint8 , MagicBE("PS..") )
- GET_MPTHEADER_sized_member( nVolSwing , uint8 , MagicBE("VS..") )
- GET_MPTHEADER_sized_member( nIFC , uint8 , MagicBE("IFC.") )
- GET_MPTHEADER_sized_member( nIFR , uint8 , MagicBE("IFR.") )
- GET_MPTHEADER_sized_member( wMidiBank , uint16 , MagicBE("MB..") )
- GET_MPTHEADER_sized_member( nMidiProgram , uint8 , MagicBE("MP..") )
- GET_MPTHEADER_sized_member( nMidiChannel , uint8 , MagicBE("MC..") )
- GET_MPTHEADER_sized_member( nPPS , int8 , MagicBE("PPS.") )
- GET_MPTHEADER_sized_member( nPPC , uint8 , MagicBE("PPC.") )
- GET_MPTHEADER_envelope_member(ENV_VOLUME , tick , uint16 , MagicBE("VP[.") )
- GET_MPTHEADER_envelope_member(ENV_PANNING , tick , uint16 , MagicBE("PP[.") )
- GET_MPTHEADER_envelope_member(ENV_PITCH , tick , uint16 , MagicBE("PiP[") )
- GET_MPTHEADER_envelope_member(ENV_VOLUME , value , uint8 , MagicBE("VE[.") )
- GET_MPTHEADER_envelope_member(ENV_PANNING , value , uint8 , MagicBE("PE[.") )
- GET_MPTHEADER_envelope_member(ENV_PITCH , value , uint8 , MagicBE("PiE[") )
- GET_MPTHEADER_array_member( NoteMap , uint8 , MagicBE("NM[.") )
- GET_MPTHEADER_array_member( Keyboard , uint16 , MagicBE("K[..") )
- GET_MPTHEADER_charbuf_member( name , char , MagicBE("n[..") )
- GET_MPTHEADER_charbuf_member( filename , char , MagicBE("fn[.") )
- GET_MPTHEADER_sized_member( nMixPlug , uint8 , MagicBE("MiP.") )
- GET_MPTHEADER_sized_member( nVolRampUp , uint16 , MagicBE("VR..") )
- GET_MPTHEADER_sized_member( nCutSwing , uint8 , MagicBE("CS..") )
- GET_MPTHEADER_sized_member( nResSwing , uint8 , MagicBE("RS..") )
- GET_MPTHEADER_sized_member( filterMode , uint8 , MagicBE("FM..") )
- GET_MPTHEADER_sized_member( pluginVelocityHandling , uint8 , MagicBE("PVEH") )
- GET_MPTHEADER_sized_member( pluginVolumeHandling , uint8 , MagicBE("PVOH") )
- GET_MPTHEADER_sized_member( PitchEnv.nReleaseNode , uint8 , MagicBE("PERN") )
- GET_MPTHEADER_sized_member( PanEnv.nReleaseNode , uint8 , MagicBE("AERN") )
- GET_MPTHEADER_sized_member( VolEnv.nReleaseNode , uint8 , MagicBE("VERN") )
- GET_MPTHEADER_sized_member( PitchEnv.dwFlags , uint8 , MagicBE("PFLG") )
- GET_MPTHEADER_sized_member( PanEnv.dwFlags , uint8 , MagicBE("AFLG") )
- GET_MPTHEADER_sized_member( VolEnv.dwFlags , uint8 , MagicBE("VFLG") )
- GET_MPTHEADER_sized_member( midiPWD , int8 , MagicBE("MPWD") )
- // clang-format on
- case MagicBE("R..."):
- {
- // Resampling has been written as various sizes including uint16 and uint32 in the past
- uint32 tmp = file.ReadSizedIntLE<uint32>(fsize);
- if(Resampling::IsKnownMode(tmp))
- input->resampling = static_cast<ResamplingMode>(tmp);
- result = true;
- } break;
- case MagicBE("PTTL"):
- {
- // Integer part of pitch/tempo lock
- uint16 tmp = file.ReadSizedIntLE<uint16>(fsize);
- input->pitchToTempoLock.Set(tmp, input->pitchToTempoLock.GetFract());
- result = true;
- } break;
- case MagicLE("PTTF"):
- {
- // Fractional part of pitch/tempo lock
- uint16 tmp = file.ReadSizedIntLE<uint16>(fsize);
- input->pitchToTempoLock.Set(input->pitchToTempoLock.GetInt(), tmp);
- result = true;
- } break;
- case MagicBE("VE.."):
- input->VolEnv.resize(std::min(uint32(MAX_ENVPOINTS), file.ReadSizedIntLE<uint32>(fsize)));
- result = true;
- break;
- case MagicBE("PE.."):
- input->PanEnv.resize(std::min(uint32(MAX_ENVPOINTS), file.ReadSizedIntLE<uint32>(fsize)));
- result = true;
- break;
- case MagicBE("PiE."):
- input->PitchEnv.resize(std::min(uint32(MAX_ENVPOINTS), file.ReadSizedIntLE<uint32>(fsize)));
- result = true;
- break;
- }
- return result;
- }
- // Convert instrument flags which were read from 'dF..' extension to proper internal representation.
- static void ConvertReadExtendedFlags(ModInstrument *pIns)
- {
- // Flags of 'dF..' datafield in extended instrument properties.
- enum
- {
- dFdd_VOLUME = 0x0001,
- dFdd_VOLSUSTAIN = 0x0002,
- dFdd_VOLLOOP = 0x0004,
- dFdd_PANNING = 0x0008,
- dFdd_PANSUSTAIN = 0x0010,
- dFdd_PANLOOP = 0x0020,
- dFdd_PITCH = 0x0040,
- dFdd_PITCHSUSTAIN = 0x0080,
- dFdd_PITCHLOOP = 0x0100,
- dFdd_SETPANNING = 0x0200,
- dFdd_FILTER = 0x0400,
- dFdd_VOLCARRY = 0x0800,
- dFdd_PANCARRY = 0x1000,
- dFdd_PITCHCARRY = 0x2000,
- dFdd_MUTE = 0x4000,
- };
- const uint32 dwOldFlags = pIns->dwFlags.GetRaw();
- pIns->VolEnv.dwFlags.set(ENV_ENABLED, (dwOldFlags & dFdd_VOLUME) != 0);
- pIns->VolEnv.dwFlags.set(ENV_SUSTAIN, (dwOldFlags & dFdd_VOLSUSTAIN) != 0);
- pIns->VolEnv.dwFlags.set(ENV_LOOP, (dwOldFlags & dFdd_VOLLOOP) != 0);
- pIns->VolEnv.dwFlags.set(ENV_CARRY, (dwOldFlags & dFdd_VOLCARRY) != 0);
- pIns->PanEnv.dwFlags.set(ENV_ENABLED, (dwOldFlags & dFdd_PANNING) != 0);
- pIns->PanEnv.dwFlags.set(ENV_SUSTAIN, (dwOldFlags & dFdd_PANSUSTAIN) != 0);
- pIns->PanEnv.dwFlags.set(ENV_LOOP, (dwOldFlags & dFdd_PANLOOP) != 0);
- pIns->PanEnv.dwFlags.set(ENV_CARRY, (dwOldFlags & dFdd_PANCARRY) != 0);
- pIns->PitchEnv.dwFlags.set(ENV_ENABLED, (dwOldFlags & dFdd_PITCH) != 0);
- pIns->PitchEnv.dwFlags.set(ENV_SUSTAIN, (dwOldFlags & dFdd_PITCHSUSTAIN) != 0);
- pIns->PitchEnv.dwFlags.set(ENV_LOOP, (dwOldFlags & dFdd_PITCHLOOP) != 0);
- pIns->PitchEnv.dwFlags.set(ENV_CARRY, (dwOldFlags & dFdd_PITCHCARRY) != 0);
- pIns->PitchEnv.dwFlags.set(ENV_FILTER, (dwOldFlags & dFdd_FILTER) != 0);
- pIns->dwFlags.reset();
- pIns->dwFlags.set(INS_SETPANNING, (dwOldFlags & dFdd_SETPANNING) != 0);
- pIns->dwFlags.set(INS_MUTE, (dwOldFlags & dFdd_MUTE) != 0);
- }
- void ReadInstrumentExtensionField(ModInstrument* pIns, const uint32 code, const uint16 size, FileReader &file)
- {
- if(code == MagicBE("K[.."))
- {
- // skip keyboard mapping
- file.Skip(size);
- return;
- }
- bool success = ReadInstrumentHeaderField(pIns, code, size, file);
- if(!success)
- {
- file.Skip(size);
- return;
- }
- if(code == MagicBE("dF..")) // 'dF..' field requires additional processing.
- ConvertReadExtendedFlags(pIns);
- }
- void ReadExtendedInstrumentProperty(ModInstrument* pIns, const uint32 code, FileReader &file)
- {
- uint16 size = file.ReadUint16LE();
- if(!file.CanRead(size))
- {
- return;
- }
- ReadInstrumentExtensionField(pIns, code, size, file);
- }
- void ReadExtendedInstrumentProperties(ModInstrument* pIns, FileReader &file)
- {
- if(!file.ReadMagic("XTPM")) // 'MPTX'
- {
- return;
- }
- while(file.CanRead(7))
- {
- ReadExtendedInstrumentProperty(pIns, file.ReadUint32LE(), file);
- }
- }
- bool CSoundFile::LoadExtendedInstrumentProperties(FileReader &file)
- {
- if(!file.ReadMagic("XTPM")) // 'MPTX'
- {
- return false;
- }
- while(file.CanRead(6))
- {
- uint32 code = file.ReadUint32LE();
- if(code == MagicBE("MPTS") // Reached song extensions, break out of this loop
- || code == MagicLE("228\x04") // Reached MPTM extensions (in case there are no song extensions)
- || (code & 0x80808080) || !(code & 0x60606060)) // Non-ASCII chunk ID
- {
- file.SkipBack(4);
- break;
- }
- // Read size of this property for *one* instrument
- const uint16 size = file.ReadUint16LE();
- for(INSTRUMENTINDEX i = 1; i <= GetNumInstruments(); i++)
- {
- if(Instruments[i])
- {
- ReadInstrumentExtensionField(Instruments[i], code, size, file);
- }
- }
- }
- return true;
- }
- OPENMPT_NAMESPACE_END
|