diff options
author | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
---|---|---|
committer | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
commit | 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch) | |
tree | 12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/external_dependencies/openmpt-trunk/soundlib/InstrumentExtensions.cpp | |
parent | 537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff) | |
download | winamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz |
Initial community commit
Diffstat (limited to 'Src/external_dependencies/openmpt-trunk/soundlib/InstrumentExtensions.cpp')
-rw-r--r-- | Src/external_dependencies/openmpt-trunk/soundlib/InstrumentExtensions.cpp | 775 |
1 files changed, 775 insertions, 0 deletions
diff --git a/Src/external_dependencies/openmpt-trunk/soundlib/InstrumentExtensions.cpp b/Src/external_dependencies/openmpt-trunk/soundlib/InstrumentExtensions.cpp new file mode 100644 index 00000000..8698e73e --- /dev/null +++ b/Src/external_dependencies/openmpt-trunk/soundlib/InstrumentExtensions.cpp @@ -0,0 +1,775 @@ +/* + * 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 |