/* * 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" 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.. nFilterMode; 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 nPluginVelocityHandling; PVOH nPluginVolumeHandling; R... nResampling; 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 struct IsNegativeFunctor { bool operator()(T val) const { return val < 0; } }; template struct IsNegativeFunctor { bool operator()(T val) const { return val < 0; } }; template struct IsNegativeFunctor { bool operator()(T /*val*/) const { return false; } }; template bool IsNegative(const T &val) { return IsNegativeFunctor::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(file, fcode); \ mpt::IO::WriteIntLE(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 = 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(file, fcode); \ mpt::IO::WriteIntLE(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(file, fcode); \ mpt::IO::WriteIntLE(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(sizeof( type ) * env.size());\ MPT_ASSERT(size_t(fsize) == sizeof( type ) * env.size()); \ \ if(writeAll) \ { \ mpt::IO::WriteIntLE(file, fcode); \ mpt::IO::WriteIntLE(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(fsize/sizeof(type), 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); } 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_trunc_member( nResampling , uint16 , MagicBE("R...") ) WRITE_MPTHEADER_sized_member( nCutSwing , uint8 , MagicBE("CS..") ) WRITE_MPTHEADER_sized_member( nResSwing , uint8 , MagicBE("RS..") ) WRITE_MPTHEADER_sized_member( nFilterMode , uint8 , MagicBE("FM..") ) WRITE_MPTHEADER_sized_member( nPluginVelocityHandling , uint8 , MagicBE("PVEH") ) WRITE_MPTHEADER_sized_member( nPluginVolumeHandling , 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") ) } template 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 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(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::nResampling, MagicBE("R..."), sizeof(ModInstrument::nResampling), f, numInstruments); WritePropertyIfNeeded(*this, &ModInstrument::nPluginVelocityHandling, MagicBE("PVEH"), sizeof(ModInstrument::nPluginVelocityHandling), f, numInstruments); WritePropertyIfNeeded(*this, &ModInstrument::nPluginVolumeHandling, MagicBE("PVOH"), sizeof(ModInstrument::nPluginVolumeHandling), 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::nFilterMode, MagicBE("FM.."), sizeof(ModInstrument::nFilterMode), 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(maxNodes[0] * sizeof(EnvelopeNode::tick)), f, numInstruments); WriteInstrumentPropertyForAllInstruments(MagicBE("VE[."), static_cast(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(maxNodes[1] * sizeof(EnvelopeNode::tick)), f, numInstruments); WriteInstrumentPropertyForAllInstruments(MagicBE("PE[."), static_cast(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(maxNodes[2] * sizeof(EnvelopeNode::tick)), f, numInstruments); WriteInstrumentPropertyForAllInstruments(MagicBE("PiE["), static_cast(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(f, code); //write code mpt::IO::WriteIntLE(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(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 ) * CountOf(input-> name) ) \ { \ FileReader arrayChunk = file.ReadChunk(fsize); \ for(std::size_t i = 0; i < CountOf(input-> name); ++i) \ { \ input-> name [i] = arrayChunk.ReadIntLE(); \ } \ result = true; \ } \ } break; // -------------------------------------------------------------------------------------------- // Convenient macro to help GET_HEADER declaration for character array members ONLY // -------------------------------------------------------------------------------------------- #define GET_MPTHEADER_chararray_member(name,type,code) \ case code: \ {\ if( fsize <= sizeof( type ) * CountOf(input-> name) ) \ { \ FileReader arrayChunk = file.ReadChunk(fsize); \ for(std::size_t i = 0; i < CountOf(input-> name); ++i) \ { \ input-> name [i] = arrayChunk.ReadChar(); \ } \ 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(); \ } \ 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) { 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_chararray_member( name , char , MagicBE("n[..") ) GET_MPTHEADER_chararray_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( nResampling , uint32 , MagicBE("R...") ) GET_MPTHEADER_sized_member( nCutSwing , uint8 , MagicBE("CS..") ) GET_MPTHEADER_sized_member( nResSwing , uint8 , MagicBE("RS..") ) GET_MPTHEADER_sized_member( nFilterMode , uint8 , MagicBE("FM..") ) GET_MPTHEADER_sized_member( nPluginVelocityHandling , uint8 , MagicBE("PVEH") ) GET_MPTHEADER_sized_member( nPluginVolumeHandling , 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") ) case MagicBE("PTTL"): { // Integer part of pitch/tempo lock uint16 tmp = file.ReadTruncatedIntLE(fsize); input->pitchToTempoLock.Set(tmp, input->pitchToTempoLock.GetFract()); result = true; } break; case MagicLE("PTTF"): { // Fractional part of pitch/tempo lock uint16 tmp = file.ReadTruncatedIntLE(fsize); input->pitchToTempoLock.Set(input->pitchToTempoLock.GetInt(), tmp); result = true; } break; case MagicBE("VE.."): input->VolEnv.resize(std::min(MAX_ENVPOINTS, file.ReadTruncatedIntLE(fsize))); result = true; break; case MagicBE("PE.."): input->PanEnv.resize(std::min(MAX_ENVPOINTS, file.ReadTruncatedIntLE(fsize))); result = true; break; case MagicBE("PiE."): input->PitchEnv.resize(std::min(MAX_ENVPOINTS, file.ReadTruncatedIntLE(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("n[..")) mpt::String::SetNullTerminator(pIns->name); if(code == MagicBE("fn[.")) mpt::String::SetNullTerminator(pIns->filename); 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