Bundle libOpenMPT as a dynamic framework, which should be safe once again, now that there is only one version to bundle. Also, now it is using the versions of libvorbisfile and libmpg123 that are bundled with the player, instead of compiling minimp3 and stbvorbis. Signed-off-by: Christopher Snowhill <kode54@gmail.com>
775 lines
31 KiB
C++
775 lines
31 KiB
C++
/*
|
|
* 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
|