1 /*
2  * InstrumentExtensions.cpp
3  * ------------------------
4  * Purpose: Instrument properties I/O
5  * Notes  : Welcome to the absolutely horrible abominations that are the "extended instrument properties"
6  *          which are some of the earliest additions OpenMPT did to the IT / XM format. They are ugly,
7  *          and the way they work even differs between IT/XM and ITI/XI/ITP.
8  *          Yes, the world would be a better place without this stuff.
9  * Authors: OpenMPT Devs
10  * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
11  */
12 
13 
14 #include "stdafx.h"
15 #include "Loaders.h"
16 
17 #ifndef MODPLUG_NO_FILESAVE
18 #include "mpt/io/base.hpp"
19 #include "mpt/io/io.hpp"
20 #include "mpt/io/io_stdstream.hpp"
21 #endif
22 
23 OPENMPT_NAMESPACE_BEGIN
24 
25 /*---------------------------------------------------------------------------------------------
26 -----------------------------------------------------------------------------------------------
27 MODULAR (in/out) ModInstrument :
28 -----------------------------------------------------------------------------------------------
29 
30 * to update:
31 ------------
32 
33 - both following functions need to be updated when adding a new member in ModInstrument :
34 
35 void WriteInstrumentHeaderStructOrField(ModInstrument * input, std::ostream &file, uint32 only_this_code, int16 fixedsize);
36 bool ReadInstrumentHeaderField(ModInstrument * input, uint32 fcode, int16 fsize, FileReader &file);
37 
38 - see below for body declaration.
39 
40 
41 * members:
42 ----------
43 
44 - 32bit identification CODE tag (must be unique)
45 - 16bit content SIZE in byte(s)
46 - member field
47 
48 
49 * CODE tag naming convention:
50 -----------------------------
51 
52 - have a look below in current tag dictionnary
53 - take the initial ones of the field name
54 - 4 caracters code (not more, not less)
55 - must be filled with '.' caracters if code has less than 4 caracters
56 - for arrays, must include a '[' caracter following significant caracters ('.' not significant!!!)
57 - use only caracters used in full member name, ordered as they appear in it
58 - match caracter attribute (small,capital)
59 
60 Example with "PanEnv.nLoopEnd" , "PitchEnv.nLoopEnd" & "VolEnv.Values[MAX_ENVPOINTS]" members :
61 - use 'PLE.' for PanEnv.nLoopEnd
62 - use 'PiLE' for PitchEnv.nLoopEnd
63 - use 'VE[.' for VolEnv.Values[MAX_ENVPOINTS]
64 
65 
66 * In use CODE tag dictionary (alphabetical order):
67 --------------------------------------------------
68 
69 						!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
70 						!!! SECTION TO BE UPDATED !!!
71 						!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
72 
73 		[EXT]	means external (not related) to ModInstrument content
74 
75 AUTH	[EXT]	Song artist
76 C...	[EXT]	nChannels
77 ChnS	[EXT]	IT/MPTM: Channel settings for channels 65-127 if needed (doesn't fit to IT header).
78 CS..			nCutSwing
79 CUES	[EXT]	Sample cue points
80 CWV.	[EXT]	dwCreatedWithVersion
81 DCT.			nDCT;
82 dF..			dwFlags;
83 DGV.	[EXT]	nDefaultGlobalVolume
84 DT..	[EXT]	nDefaultTempo;
85 DTFR	[EXT]	Fractional part of default tempo
86 DNA.			nDNA;
87 EBIH	[EXT]	embeded instrument header tag (ITP file format)
88 FM..			filterMode;
89 fn[.			filename[12];
90 FO..			nFadeOut;
91 GV..			nGlobalVol;
92 IFC.			nIFC;
93 IFR.			nIFR;
94 K[.				Keyboard[128];
95 LSWV	[EXT]	Last Saved With Version
96 MB..			wMidiBank;
97 MC..			nMidiChannel;
98 MDK.			nMidiDrumKey;
99 MIMA	[EXT]									MIdi MApping directives
100 MiP.			nMixPlug;
101 MP..			nMidiProgram;
102 MPTS	[EXT]									Extra song info tag
103 MPTX	[EXT]									EXTRA INFO tag
104 MSF.	[EXT]									Mod(Specific)Flags
105 n[..			name[32];
106 NNA.			nNNA;
107 NM[.			NoteMap[128];
108 P...			nPan;
109 PE..			PanEnv.nNodes;
110 PE[.			PanEnv.Values[MAX_ENVPOINTS];
111 PiE.			PitchEnv.nNodes;
112 PiE[			PitchEnv.Values[MAX_ENVPOINTS];
113 PiLE			PitchEnv.nLoopEnd;
114 PiLS			PitchEnv.nLoopStart;
115 PiP[			PitchEnv.Ticks[MAX_ENVPOINTS];
116 PiSB			PitchEnv.nSustainStart;
117 PiSE			PitchEnv.nSustainEnd;
118 PLE.			PanEnv.nLoopEnd;
119 PLS.			PanEnv.nLoopStart;
120 PMM.	[EXT]	nPlugMixMode;
121 PP[.			PanEnv.Ticks[MAX_ENVPOINTS];
122 PPC.			nPPC;
123 PPS.			nPPS;
124 PS..			nPanSwing;
125 PSB.			PanEnv.nSustainStart;
126 PSE.			PanEnv.nSustainEnd;
127 PTTL			pitchToTempoLock;
128 PTTF			pitchToTempoLock (fractional part);
129 PVEH			pluginVelocityHandling;
130 PVOH			pluginVolumeHandling;
131 R...			resampling;
132 RP..	[EXT]	nRestartPos;
133 RPB.	[EXT]	nRowsPerBeat;
134 RPM.	[EXT]	nRowsPerMeasure;
135 RS..			nResSwing;
136 RSMP	[EXT]	Global resampling
137 SEP@	[EXT]	chunk SEPARATOR tag
138 SPA.	[EXT]	m_nSamplePreAmp;
139 TM..	[EXT]	nTempoMode;
140 VE..			VolEnv.nNodes;
141 VE[.			VolEnv.Values[MAX_ENVPOINTS];
142 VLE.			VolEnv.nLoopEnd;
143 VLS.			VolEnv.nLoopStart;
144 VP[.			VolEnv.Ticks[MAX_ENVPOINTS];
145 VR..			nVolRampUp;
146 VS..			nVolSwing;
147 VSB.			VolEnv.nSustainStart;
148 VSE.			VolEnv.nSustainEnd;
149 VSTV	[EXT]	nVSTiVolume;
150 PERN			PitchEnv.nReleaseNode
151 AERN			PanEnv.nReleaseNode
152 VERN			VolEnv.nReleaseNode
153 PFLG			PitchEnv.dwFlag
154 AFLG			PanEnv.dwFlags
155 VFLG			VolEnv.dwFlags
156 MPWD			MIDI Pitch Wheel Depth
157 -----------------------------------------------------------------------------------------------
158 ---------------------------------------------------------------------------------------------*/
159 
160 #ifndef MODPLUG_NO_FILESAVE
161 
operator ()IsNegativeFunctor162 template<typename T, bool is_signed> struct IsNegativeFunctor { bool operator()(T val) const { return val < 0; } };
operator ()IsNegativeFunctor163 template<typename T> struct IsNegativeFunctor<T, true> { bool operator()(T val) const { return val < 0; } };
operator ()IsNegativeFunctor164 template<typename T> struct IsNegativeFunctor<T, false> { bool operator()(T /*val*/) const { return false; } };
165 
166 template<typename T>
IsNegative(const T & val)167 bool IsNegative(const T &val)
168 {
169 	return IsNegativeFunctor<T, std::numeric_limits<T>::is_signed>()(val);
170 }
171 
172 // ------------------------------------------------------------------------------------------
173 // Convenient macro to help WRITE_HEADER declaration for single type members ONLY (non-array)
174 // ------------------------------------------------------------------------------------------
175 #define WRITE_MPTHEADER_sized_member(name,type,code) \
176 	static_assert(sizeof(input->name) == sizeof(type), "Instrument property does match specified type!");\
177 	fcode = code;\
178 	fsize = sizeof( type );\
179 	if(writeAll) \
180 	{ \
181 		mpt::IO::WriteIntLE<uint32>(file, fcode); \
182 		mpt::IO::WriteIntLE<uint16>(file, fsize); \
183 	} else if(only_this_code == fcode)\
184 	{ \
185 		MPT_ASSERT(fixedsize == fsize); \
186 	} \
187 	if(only_this_code == fcode || only_this_code == Util::MaxValueOfType(only_this_code)) \
188 	{ \
189 		type tmp = (type)(input-> name ); \
190 		mpt::IO::WriteIntLE(file, tmp); \
191 	} \
192 /**/
193 
194 // -----------------------------------------------------------------------------------------------------
195 // Convenient macro to help WRITE_HEADER declaration for single type members which are written truncated
196 // -----------------------------------------------------------------------------------------------------
197 #define WRITE_MPTHEADER_trunc_member(name,type,code) \
198 	static_assert(sizeof(input->name) > sizeof(type), "Instrument property would not be truncated, use WRITE_MPTHEADER_sized_member instead!");\
199 	fcode = code;\
200 	fsize = sizeof( type );\
201 	if(writeAll) \
202 	{ \
203 		mpt::IO::WriteIntLE<uint32>(file, fcode); \
204 		mpt::IO::WriteIntLE<uint16>(file, fsize); \
205 		type tmp = (type)(input-> name ); \
206 		mpt::IO::WriteIntLE(file, tmp); \
207 	} else if(only_this_code == fcode)\
208 	{ \
209 		/* hackish workaround to resolve mismatched size values: */ \
210 		/* nResampling was a long time declared as uint32 but these macro tables used uint16 and UINT. */ \
211 		/* This worked fine on little-endian, on big-endian not so much. Thus support writing size-mismatched fields. */ \
212 		MPT_ASSERT(fixedsize >= fsize); \
213 		type tmp = (type)(input-> name ); \
214 		mpt::IO::WriteIntLE(file, tmp); \
215 		if(fixedsize > fsize) \
216 		{ \
217 			for(int16 i = 0; i < fixedsize - fsize; ++i) \
218 			{ \
219 				uint8 fillbyte = !IsNegative(tmp) ? 0 : 0xff; /* sign extend */ \
220 				mpt::IO::WriteIntLE(file, fillbyte); \
221 			} \
222 		} \
223 	} \
224 /**/
225 
226 // ------------------------------------------------------------------------
227 // Convenient macro to help WRITE_HEADER declaration for array members ONLY
228 // ------------------------------------------------------------------------
229 #define WRITE_MPTHEADER_array_member(name,type,code,arraysize) \
230 	static_assert(sizeof(type) == sizeof(input-> name [0])); \
231 	MPT_ASSERT(sizeof(input->name) >= sizeof(type) * arraysize);\
232 	fcode = code;\
233 	fsize = sizeof( type ) * arraysize;\
234 	if(writeAll) \
235 	{ \
236 		mpt::IO::WriteIntLE<uint32>(file, fcode); \
237 		mpt::IO::WriteIntLE<uint16>(file, fsize); \
238 	} else if(only_this_code == fcode)\
239 	{ \
240 		/* MPT_ASSERT(fixedsize <= fsize); */ \
241 		fsize = fixedsize; /* just trust the size we got passed */ \
242 	} \
243 	if(only_this_code == fcode || only_this_code == Util::MaxValueOfType(only_this_code)) \
244 	{ \
245 		for(std::size_t i = 0; i < fsize/sizeof(type); ++i) \
246 		{ \
247 			type tmp; \
248 			tmp = input-> name [i]; \
249 			mpt::IO::WriteIntLE(file, tmp); \
250 		} \
251 	} \
252 /**/
253 
254 // ------------------------------------------------------------------------
255 // Convenient macro to help WRITE_HEADER declaration for envelope members ONLY
256 // ------------------------------------------------------------------------
257 #define WRITE_MPTHEADER_envelope_member(envType,envField,type,code) \
258 	{\
259 		const InstrumentEnvelope &env = input->GetEnvelope(envType); \
260 		static_assert(sizeof(type) == sizeof(env[0]. envField)); \
261 		fcode = code;\
262 		fsize = mpt::saturate_cast<int16>(sizeof( type ) * env.size());\
263 		MPT_ASSERT(size_t(fsize) == sizeof( type ) * env.size()); \
264 		\
265 		if(writeAll) \
266 		{ \
267 			mpt::IO::WriteIntLE<uint32>(file, fcode); \
268 			mpt::IO::WriteIntLE<uint16>(file, fsize); \
269 		} else if(only_this_code == fcode)\
270 		{ \
271 			fsize = fixedsize; /* just trust the size we got passed */ \
272 		} \
273 		if(only_this_code == fcode || only_this_code == Util::MaxValueOfType(only_this_code)) \
274 		{ \
275 			uint32 maxNodes = std::min(static_cast<uint32>(fsize/sizeof(type)), static_cast<uint32>(env.size())); \
276 			for(uint32 i = 0; i < maxNodes; ++i) \
277 			{ \
278 				type tmp; \
279 				tmp = env[i]. envField ; \
280 				mpt::IO::WriteIntLE(file, tmp); \
281 			} \
282 			/* Not every instrument's envelope will be the same length. fill up with zeros. */ \
283 			for(uint32 i = maxNodes; i < fsize/sizeof(type); ++i) \
284 			{ \
285 				type tmp = 0; \
286 				mpt::IO::WriteIntLE(file, tmp); \
287 			} \
288 		} \
289 	}\
290 /**/
291 
292 
293 // Write (in 'file') 'input' ModInstrument with 'code' & 'size' extra field infos for each member
WriteInstrumentHeaderStructOrField(ModInstrument * input,std::ostream & file,uint32 only_this_code,uint16 fixedsize)294 void WriteInstrumentHeaderStructOrField(ModInstrument * input, std::ostream &file, uint32 only_this_code, uint16 fixedsize)
295 {
296 	uint32 fcode;
297 	uint16 fsize;
298 	// If true, all extension are written to the file; otherwise only the specified extension is written.
299 	// writeAll is true iff we are saving an instrument (or, hypothetically, the legacy ITP format)
300 	const bool writeAll = only_this_code == Util::MaxValueOfType(only_this_code);
301 
302 	if(!writeAll)
303 	{
304 		MPT_ASSERT(fixedsize > 0);
305 	}
306 
307 	// clang-format off
308 	WRITE_MPTHEADER_sized_member(	nFadeOut					, uint32	, MagicBE("FO..")	)
309 	WRITE_MPTHEADER_sized_member(	nPan						, uint32	, MagicBE("P...")	)
310 	WRITE_MPTHEADER_sized_member(	VolEnv.size()				, uint32	, MagicBE("VE..")	)
311 	WRITE_MPTHEADER_sized_member(	PanEnv.size()				, uint32	, MagicBE("PE..")	)
312 	WRITE_MPTHEADER_sized_member(	PitchEnv.size()				, uint32	, MagicBE("PiE.")	)
313 	WRITE_MPTHEADER_sized_member(	wMidiBank					, uint16	, MagicBE("MB..")	)
314 	WRITE_MPTHEADER_sized_member(	nMidiProgram				, uint8		, MagicBE("MP..")	)
315 	WRITE_MPTHEADER_sized_member(	nMidiChannel				, uint8		, MagicBE("MC..")	)
316 	WRITE_MPTHEADER_envelope_member(	ENV_VOLUME	, tick		, uint16	, MagicBE("VP[.")	)
317 	WRITE_MPTHEADER_envelope_member(	ENV_PANNING	, tick		, uint16	, MagicBE("PP[.")	)
318 	WRITE_MPTHEADER_envelope_member(	ENV_PITCH	, tick		, uint16	, MagicBE("PiP[")	)
319 	WRITE_MPTHEADER_envelope_member(	ENV_VOLUME	, value		, uint8		, MagicBE("VE[.")	)
320 	WRITE_MPTHEADER_envelope_member(	ENV_PANNING	, value		, uint8		, MagicBE("PE[.")	)
321 	WRITE_MPTHEADER_envelope_member(	ENV_PITCH	, value		, uint8		, MagicBE("PiE[")	)
322 	WRITE_MPTHEADER_sized_member(	nMixPlug					, uint8		, MagicBE("MiP.")	)
323 	WRITE_MPTHEADER_sized_member(	nVolRampUp					, uint16	, MagicBE("VR..")	)
324 	WRITE_MPTHEADER_sized_member(	resampling					, uint8		, MagicBE("R...")	)
325 	WRITE_MPTHEADER_sized_member(	nCutSwing					, uint8		, MagicBE("CS..")	)
326 	WRITE_MPTHEADER_sized_member(	nResSwing					, uint8		, MagicBE("RS..")	)
327 	WRITE_MPTHEADER_sized_member(	filterMode					, uint8		, MagicBE("FM..")	)
328 	WRITE_MPTHEADER_sized_member(	pluginVelocityHandling		, uint8		, MagicBE("PVEH")	)
329 	WRITE_MPTHEADER_sized_member(	pluginVolumeHandling		, uint8		, MagicBE("PVOH")	)
330 	WRITE_MPTHEADER_trunc_member(	pitchToTempoLock.GetInt()	, uint16	, MagicBE("PTTL")	)
331 	WRITE_MPTHEADER_trunc_member(	pitchToTempoLock.GetFract() , uint16	, MagicLE("PTTF")	)
332 	WRITE_MPTHEADER_sized_member(	PitchEnv.nReleaseNode		, uint8		, MagicBE("PERN")	)
333 	WRITE_MPTHEADER_sized_member(	PanEnv.nReleaseNode			, uint8		, MagicBE("AERN")	)
334 	WRITE_MPTHEADER_sized_member(	VolEnv.nReleaseNode			, uint8		, MagicBE("VERN")	)
335 	WRITE_MPTHEADER_sized_member(	PitchEnv.dwFlags			, uint8		, MagicBE("PFLG")	)
336 	WRITE_MPTHEADER_sized_member(	PanEnv.dwFlags				, uint8		, MagicBE("AFLG")	)
337 	WRITE_MPTHEADER_sized_member(	VolEnv.dwFlags				, uint8		, MagicBE("VFLG")	)
338 	WRITE_MPTHEADER_sized_member(	midiPWD						, int8		, MagicBE("MPWD")	)
339 	// clang-format on
340 
341 }
342 
343 
344 template<typename TIns, typename PropType>
IsPropertyNeeded(const TIns & Instruments,PropType ModInstrument::* Prop)345 static bool IsPropertyNeeded(const TIns &Instruments, PropType ModInstrument::*Prop)
346 {
347 	const ModInstrument defaultIns;
348 	for(const auto &ins : Instruments)
349 	{
350 		if(ins != nullptr && defaultIns.*Prop != ins->*Prop)
351 			return true;
352 	}
353 	return false;
354 }
355 
356 
357 template<typename PropType>
WritePropertyIfNeeded(const CSoundFile & sndFile,PropType ModInstrument::* Prop,uint32 code,uint16 size,std::ostream & f,INSTRUMENTINDEX numInstruments)358 static void WritePropertyIfNeeded(const CSoundFile &sndFile, PropType ModInstrument::*Prop, uint32 code, uint16 size, std::ostream &f, INSTRUMENTINDEX numInstruments)
359 {
360 	if(IsPropertyNeeded(sndFile.Instruments, Prop))
361 	{
362 		sndFile.WriteInstrumentPropertyForAllInstruments(code, size, f, numInstruments);
363 	}
364 }
365 
366 
367 // Used only when saving IT, XM and MPTM.
368 // ITI, ITP saves using Ericus' macros etc...
369 // The reason is that ITs and XMs save [code][size][ins1.Value][ins2.Value]...
370 // whereas ITP saves [code][size][ins1.Value][code][size][ins2.Value]...
371 // too late to turn back....
SaveExtendedInstrumentProperties(INSTRUMENTINDEX numInstruments,std::ostream & f) const372 void CSoundFile::SaveExtendedInstrumentProperties(INSTRUMENTINDEX numInstruments, std::ostream &f) const
373 {
374 	uint32 code = MagicBE("MPTX");	// write extension header code
375 	mpt::IO::WriteIntLE<uint32>(f, code);
376 
377 	if (numInstruments == 0)
378 		return;
379 
380 	WritePropertyIfNeeded(*this, &ModInstrument::nVolRampUp,   MagicBE("VR.."), sizeof(ModInstrument::nVolRampUp),   f, numInstruments);
381 	WritePropertyIfNeeded(*this, &ModInstrument::nMixPlug,     MagicBE("MiP."), sizeof(ModInstrument::nMixPlug),     f, numInstruments);
382 	WritePropertyIfNeeded(*this, &ModInstrument::nMidiChannel, MagicBE("MC.."), sizeof(ModInstrument::nMidiChannel), f, numInstruments);
383 	WritePropertyIfNeeded(*this, &ModInstrument::nMidiProgram, MagicBE("MP.."), sizeof(ModInstrument::nMidiProgram), f, numInstruments);
384 	WritePropertyIfNeeded(*this, &ModInstrument::wMidiBank,    MagicBE("MB.."), sizeof(ModInstrument::wMidiBank),    f, numInstruments);
385 	WritePropertyIfNeeded(*this, &ModInstrument::resampling,  MagicBE("R..."), sizeof(ModInstrument::resampling),  f, numInstruments);
386 	WritePropertyIfNeeded(*this, &ModInstrument::pluginVelocityHandling, MagicBE("PVEH"), sizeof(ModInstrument::pluginVelocityHandling), f, numInstruments);
387 	WritePropertyIfNeeded(*this, &ModInstrument::pluginVolumeHandling, MagicBE("PVOH"), sizeof(ModInstrument::pluginVolumeHandling), f, numInstruments);
388 
389 	if(!(GetType() & MOD_TYPE_XM))
390 	{
391 		// XM instrument headers already stores full-precision fade-out
392 		WritePropertyIfNeeded(*this, &ModInstrument::nFadeOut, MagicBE("FO.."), sizeof(ModInstrument::nFadeOut), f, numInstruments);
393 		// XM instrument headers already have support for this
394 		WritePropertyIfNeeded(*this, &ModInstrument::midiPWD, MagicBE("MPWD"), sizeof(ModInstrument::midiPWD), f, numInstruments);
395 		// We never supported these as hacks in XM (luckily!)
396 		WritePropertyIfNeeded(*this, &ModInstrument::nPan, MagicBE("P..."), sizeof(ModInstrument::nPan), f, numInstruments);
397 		WritePropertyIfNeeded(*this, &ModInstrument::nCutSwing, MagicBE("CS.."), sizeof(ModInstrument::nCutSwing), f, numInstruments);
398 		WritePropertyIfNeeded(*this, &ModInstrument::nResSwing, MagicBE("RS.."), sizeof(ModInstrument::nResSwing), f, numInstruments);
399 		WritePropertyIfNeeded(*this, &ModInstrument::filterMode, MagicBE("FM.."), sizeof(ModInstrument::filterMode), f, numInstruments);
400 		if(IsPropertyNeeded(Instruments, &ModInstrument::pitchToTempoLock))
401 		{
402 			WriteInstrumentPropertyForAllInstruments(MagicBE("PTTL"), sizeof(uint16), f, numInstruments);
403 			WriteInstrumentPropertyForAllInstruments(MagicLE("PTTF"), sizeof(uint16), f, numInstruments);
404 		}
405 	}
406 
407 	if(GetType() & MOD_TYPE_MPT)
408 	{
409 		uint32 maxNodes[3] = { 0, 0, 0 };
410 		bool hasReleaseNode[3] = { false, false, false };
411 		for(INSTRUMENTINDEX i = 1; i <= numInstruments; i++) if(Instruments[i] != nullptr)
412 		{
413 			maxNodes[0] = std::max(maxNodes[0], Instruments[i]->VolEnv.size());
414 			maxNodes[1] = std::max(maxNodes[1], Instruments[i]->PanEnv.size());
415 			maxNodes[2] = std::max(maxNodes[2], Instruments[i]->PitchEnv.size());
416 			hasReleaseNode[0] |= (Instruments[i]->VolEnv.nReleaseNode != ENV_RELEASE_NODE_UNSET);
417 			hasReleaseNode[1] |= (Instruments[i]->PanEnv.nReleaseNode != ENV_RELEASE_NODE_UNSET);
418 			hasReleaseNode[2] |= (Instruments[i]->PitchEnv.nReleaseNode != ENV_RELEASE_NODE_UNSET);
419 		}
420 		// write full envelope information for MPTM files (more env points)
421 		if(maxNodes[0] > 25)
422 		{
423 			WriteInstrumentPropertyForAllInstruments(MagicBE("VE.."), sizeof(ModInstrument::VolEnv.size()), f, numInstruments);
424 			WriteInstrumentPropertyForAllInstruments(MagicBE("VP[."), static_cast<uint16>(maxNodes[0] * sizeof(EnvelopeNode::tick)),  f, numInstruments);
425 			WriteInstrumentPropertyForAllInstruments(MagicBE("VE[."), static_cast<uint16>(maxNodes[0] * sizeof(EnvelopeNode::value)), f, numInstruments);
426 		}
427 		if(maxNodes[1] > 25)
428 		{
429 			WriteInstrumentPropertyForAllInstruments(MagicBE("PE.."), sizeof(ModInstrument::PanEnv.size()), f, numInstruments);
430 			WriteInstrumentPropertyForAllInstruments(MagicBE("PP[."), static_cast<uint16>(maxNodes[1] * sizeof(EnvelopeNode::tick)),  f, numInstruments);
431 			WriteInstrumentPropertyForAllInstruments(MagicBE("PE[."), static_cast<uint16>(maxNodes[1] * sizeof(EnvelopeNode::value)), f, numInstruments);
432 		}
433 		if(maxNodes[2] > 25)
434 		{
435 			WriteInstrumentPropertyForAllInstruments(MagicBE("PiE."), sizeof(ModInstrument::PitchEnv.size()), f, numInstruments);
436 			WriteInstrumentPropertyForAllInstruments(MagicBE("PiP["), static_cast<uint16>(maxNodes[2] * sizeof(EnvelopeNode::tick)),  f, numInstruments);
437 			WriteInstrumentPropertyForAllInstruments(MagicBE("PiE["), static_cast<uint16>(maxNodes[2] * sizeof(EnvelopeNode::value)), f, numInstruments);
438 		}
439 		if(hasReleaseNode[0])
440 			WriteInstrumentPropertyForAllInstruments(MagicBE("VERN"), sizeof(ModInstrument::VolEnv.nReleaseNode), f, numInstruments);
441 		if(hasReleaseNode[1])
442 			WriteInstrumentPropertyForAllInstruments(MagicBE("AERN"), sizeof(ModInstrument::PanEnv.nReleaseNode), f, numInstruments);
443 		if(hasReleaseNode[2])
444 			WriteInstrumentPropertyForAllInstruments(MagicBE("PERN"), sizeof(ModInstrument::PitchEnv.nReleaseNode), f, numInstruments);
445 	}
446 }
447 
WriteInstrumentPropertyForAllInstruments(uint32 code,uint16 size,std::ostream & f,INSTRUMENTINDEX nInstruments) const448 void CSoundFile::WriteInstrumentPropertyForAllInstruments(uint32 code, uint16 size, std::ostream &f, INSTRUMENTINDEX nInstruments) const
449 {
450 	mpt::IO::WriteIntLE<uint32>(f, code);		//write code
451 	mpt::IO::WriteIntLE<uint16>(f, size);		//write size
452 	for(INSTRUMENTINDEX i = 1; i <= nInstruments; i++)	//for all instruments...
453 	{
454 		if (Instruments[i])
455 		{
456 			WriteInstrumentHeaderStructOrField(Instruments[i], f, code, size);
457 		} else
458 		{
459 			ModInstrument emptyInstrument;
460 			WriteInstrumentHeaderStructOrField(&emptyInstrument, f, code, size);
461 		}
462 	}
463 }
464 
465 
466 #endif // !MODPLUG_NO_FILESAVE
467 
468 
469 // --------------------------------------------------------------------------------------------
470 // Convenient macro to help GET_HEADER declaration for single type members ONLY (non-array)
471 // --------------------------------------------------------------------------------------------
472 #define GET_MPTHEADER_sized_member(name,type,code) \
473 	case code: \
474 	{\
475 		if( fsize <= sizeof( type ) ) \
476 		{ \
477 			/* hackish workaround to resolve mismatched size values: */ \
478 			/* nResampling was a long time declared as uint32 but these macro tables used uint16 and UINT. */ \
479 			/* This worked fine on little-endian, on big-endian not so much. Thus support reading size-mismatched fields. */ \
480 			if(file.CanRead(fsize)) \
481 			{ \
482 				type tmp; \
483 				tmp = file.ReadTruncatedIntLE<type>(fsize); \
484 				static_assert(sizeof(tmp) == sizeof(input-> name )); \
485 				input-> name = decltype(input-> name )(tmp); \
486 				result = true; \
487 			} \
488 		} \
489 	} break;
490 
491 // --------------------------------------------------------------------------------------------
492 // Convenient macro to help GET_HEADER declaration for array members ONLY
493 // --------------------------------------------------------------------------------------------
494 #define GET_MPTHEADER_array_member(name,type,code) \
495 	case code: \
496 	{\
497 		if( fsize <= sizeof( type ) * std::size(input-> name) ) \
498 		{ \
499 			FileReader arrayChunk = file.ReadChunk(fsize); \
500 			for(std::size_t i = 0; i < std::size(input-> name); ++i) \
501 			{ \
502 				input-> name [i] = arrayChunk.ReadIntLE<type>(); \
503 			} \
504 			result = true; \
505 		} \
506 	} break;
507 
508 // --------------------------------------------------------------------------------------------
509 // Convenient macro to help GET_HEADER declaration for character buffer members ONLY
510 // --------------------------------------------------------------------------------------------
511 #define GET_MPTHEADER_charbuf_member(name,type,code) \
512 	case code: \
513 	{\
514 		if( fsize <= sizeof( type ) * input-> name .static_length() ) \
515 		{ \
516 			FileReader arrayChunk = file.ReadChunk(fsize); \
517 			std::string tmp; \
518 			for(std::size_t i = 0; i < fsize; ++i) \
519 			{ \
520 				tmp += arrayChunk.ReadChar(); \
521 			} \
522 			input-> name = tmp; \
523 			result = true; \
524 		} \
525 	} break;
526 
527 // --------------------------------------------------------------------------------------------
528 // Convenient macro to help GET_HEADER declaration for envelope tick/value members
529 // --------------------------------------------------------------------------------------------
530 #define GET_MPTHEADER_envelope_member(envType,envField,type,code) \
531 	case code: \
532 	{\
533 		FileReader arrayChunk = file.ReadChunk(fsize); \
534 		InstrumentEnvelope &env = input->GetEnvelope(envType); \
535 		for(uint32 i = 0; i < env.size(); i++) \
536 		{ \
537 			env[i]. envField = arrayChunk.ReadIntLE<type>(); \
538 		} \
539 		result = true; \
540 	} break;
541 
542 
543 // Return a pointer on the wanted field in 'input' ModInstrument given field code & size
ReadInstrumentHeaderField(ModInstrument * input,uint32 fcode,uint16 fsize,FileReader & file)544 bool ReadInstrumentHeaderField(ModInstrument *input, uint32 fcode, uint16 fsize, FileReader &file)
545 {
546 	if(input == nullptr) return false;
547 
548 	bool result = false;
549 
550 	// Members which can be found in this table but not in the write table are only required in the legacy ITP format.
551 	switch(fcode)
552 	{
553 	// clang-format off
554 	GET_MPTHEADER_sized_member(	nFadeOut				, uint32		, MagicBE("FO..")	)
555 	GET_MPTHEADER_sized_member(	dwFlags					, uint8			, MagicBE("dF..")	)
556 	GET_MPTHEADER_sized_member(	nGlobalVol				, uint32		, MagicBE("GV..")	)
557 	GET_MPTHEADER_sized_member(	nPan					, uint32		, MagicBE("P...")	)
558 	GET_MPTHEADER_sized_member(	VolEnv.nLoopStart		, uint8			, MagicBE("VLS.")	)
559 	GET_MPTHEADER_sized_member(	VolEnv.nLoopEnd			, uint8			, MagicBE("VLE.")	)
560 	GET_MPTHEADER_sized_member(	VolEnv.nSustainStart	, uint8			, MagicBE("VSB.")	)
561 	GET_MPTHEADER_sized_member(	VolEnv.nSustainEnd		, uint8			, MagicBE("VSE.")	)
562 	GET_MPTHEADER_sized_member(	PanEnv.nLoopStart		, uint8			, MagicBE("PLS.")	)
563 	GET_MPTHEADER_sized_member(	PanEnv.nLoopEnd			, uint8			, MagicBE("PLE.")	)
564 	GET_MPTHEADER_sized_member(	PanEnv.nSustainStart	, uint8			, MagicBE("PSB.")	)
565 	GET_MPTHEADER_sized_member(	PanEnv.nSustainEnd		, uint8			, MagicBE("PSE.")	)
566 	GET_MPTHEADER_sized_member(	PitchEnv.nLoopStart		, uint8			, MagicBE("PiLS")	)
567 	GET_MPTHEADER_sized_member(	PitchEnv.nLoopEnd		, uint8			, MagicBE("PiLE")	)
568 	GET_MPTHEADER_sized_member(	PitchEnv.nSustainStart	, uint8			, MagicBE("PiSB")	)
569 	GET_MPTHEADER_sized_member(	PitchEnv.nSustainEnd	, uint8			, MagicBE("PiSE")	)
570 	GET_MPTHEADER_sized_member(	nNNA					, uint8			, MagicBE("NNA.")	)
571 	GET_MPTHEADER_sized_member(	nDCT					, uint8			, MagicBE("DCT.")	)
572 	GET_MPTHEADER_sized_member(	nDNA					, uint8			, MagicBE("DNA.")	)
573 	GET_MPTHEADER_sized_member(	nPanSwing				, uint8			, MagicBE("PS..")	)
574 	GET_MPTHEADER_sized_member(	nVolSwing				, uint8			, MagicBE("VS..")	)
575 	GET_MPTHEADER_sized_member(	nIFC					, uint8			, MagicBE("IFC.")	)
576 	GET_MPTHEADER_sized_member(	nIFR					, uint8			, MagicBE("IFR.")	)
577 	GET_MPTHEADER_sized_member(	wMidiBank				, uint16		, MagicBE("MB..")	)
578 	GET_MPTHEADER_sized_member(	nMidiProgram			, uint8			, MagicBE("MP..")	)
579 	GET_MPTHEADER_sized_member(	nMidiChannel			, uint8			, MagicBE("MC..")	)
580 	GET_MPTHEADER_sized_member(	nPPS					, int8			, MagicBE("PPS.")	)
581 	GET_MPTHEADER_sized_member(	nPPC					, uint8			, MagicBE("PPC.")	)
582 	GET_MPTHEADER_envelope_member(ENV_VOLUME	, tick	, uint16		, MagicBE("VP[.")	)
583 	GET_MPTHEADER_envelope_member(ENV_PANNING	, tick	, uint16		, MagicBE("PP[.")	)
584 	GET_MPTHEADER_envelope_member(ENV_PITCH		, tick	, uint16		, MagicBE("PiP[")	)
585 	GET_MPTHEADER_envelope_member(ENV_VOLUME	, value	, uint8			, MagicBE("VE[.")	)
586 	GET_MPTHEADER_envelope_member(ENV_PANNING	, value	, uint8			, MagicBE("PE[.")	)
587 	GET_MPTHEADER_envelope_member(ENV_PITCH		, value	, uint8			, MagicBE("PiE[")	)
588 	GET_MPTHEADER_array_member(	NoteMap					, uint8			, MagicBE("NM[.")	)
589 	GET_MPTHEADER_array_member(	Keyboard				, uint16		, MagicBE("K[..")	)
590 	GET_MPTHEADER_charbuf_member(	name				, char			, MagicBE("n[..")	)
591 	GET_MPTHEADER_charbuf_member(	filename			, char			, MagicBE("fn[.")	)
592 	GET_MPTHEADER_sized_member(	nMixPlug				, uint8			, MagicBE("MiP.")	)
593 	GET_MPTHEADER_sized_member(	nVolRampUp				, uint16		, MagicBE("VR..")	)
594 	GET_MPTHEADER_sized_member(	nCutSwing				, uint8			, MagicBE("CS..")	)
595 	GET_MPTHEADER_sized_member(	nResSwing				, uint8			, MagicBE("RS..")	)
596 	GET_MPTHEADER_sized_member(	filterMode				, uint8			, MagicBE("FM..")	)
597 	GET_MPTHEADER_sized_member(	pluginVelocityHandling	, uint8			, MagicBE("PVEH")	)
598 	GET_MPTHEADER_sized_member(	pluginVolumeHandling	, uint8			, MagicBE("PVOH")	)
599 	GET_MPTHEADER_sized_member(	PitchEnv.nReleaseNode	, uint8			, MagicBE("PERN")	)
600 	GET_MPTHEADER_sized_member(	PanEnv.nReleaseNode		, uint8			, MagicBE("AERN")	)
601 	GET_MPTHEADER_sized_member(	VolEnv.nReleaseNode		, uint8			, MagicBE("VERN")	)
602 	GET_MPTHEADER_sized_member(	PitchEnv.dwFlags		, uint8			, MagicBE("PFLG")	)
603 	GET_MPTHEADER_sized_member(	PanEnv.dwFlags			, uint8			, MagicBE("AFLG")	)
604 	GET_MPTHEADER_sized_member(	VolEnv.dwFlags			, uint8			, MagicBE("VFLG")	)
605 	GET_MPTHEADER_sized_member(	midiPWD					, int8			, MagicBE("MPWD")	)
606 	// clang-format on
607 	case MagicBE("R..."):
608 	{
609 		// Resampling has been written as various sizes including uint16 and uint32 in the past
610 		uint32 tmp = file.ReadSizedIntLE<uint32>(fsize);
611 		if(Resampling::IsKnownMode(tmp))
612 			input->resampling = static_cast<ResamplingMode>(tmp);
613 		result = true;
614 	} break;
615 	case MagicBE("PTTL"):
616 	{
617 		// Integer part of pitch/tempo lock
618 		uint16 tmp = file.ReadSizedIntLE<uint16>(fsize);
619 		input->pitchToTempoLock.Set(tmp, input->pitchToTempoLock.GetFract());
620 		result = true;
621 	} break;
622 	case MagicLE("PTTF"):
623 	{
624 		// Fractional part of pitch/tempo lock
625 		uint16 tmp = file.ReadSizedIntLE<uint16>(fsize);
626 		input->pitchToTempoLock.Set(input->pitchToTempoLock.GetInt(), tmp);
627 		result = true;
628 	} break;
629 	case MagicBE("VE.."):
630 		input->VolEnv.resize(std::min(uint32(MAX_ENVPOINTS), file.ReadSizedIntLE<uint32>(fsize)));
631 		result = true;
632 		break;
633 	case MagicBE("PE.."):
634 		input->PanEnv.resize(std::min(uint32(MAX_ENVPOINTS), file.ReadSizedIntLE<uint32>(fsize)));
635 		result = true;
636 		break;
637 	case MagicBE("PiE."):
638 		input->PitchEnv.resize(std::min(uint32(MAX_ENVPOINTS), file.ReadSizedIntLE<uint32>(fsize)));
639 		result = true;
640 		break;
641 	}
642 
643 	return result;
644 }
645 
646 
647 // Convert instrument flags which were read from 'dF..' extension to proper internal representation.
ConvertReadExtendedFlags(ModInstrument * pIns)648 static void ConvertReadExtendedFlags(ModInstrument *pIns)
649 {
650 	// Flags of 'dF..' datafield in extended instrument properties.
651 	enum
652 	{
653 		dFdd_VOLUME 		= 0x0001,
654 		dFdd_VOLSUSTAIN 	= 0x0002,
655 		dFdd_VOLLOOP 		= 0x0004,
656 		dFdd_PANNING 		= 0x0008,
657 		dFdd_PANSUSTAIN 	= 0x0010,
658 		dFdd_PANLOOP 		= 0x0020,
659 		dFdd_PITCH 			= 0x0040,
660 		dFdd_PITCHSUSTAIN 	= 0x0080,
661 		dFdd_PITCHLOOP 		= 0x0100,
662 		dFdd_SETPANNING 	= 0x0200,
663 		dFdd_FILTER 		= 0x0400,
664 		dFdd_VOLCARRY 		= 0x0800,
665 		dFdd_PANCARRY 		= 0x1000,
666 		dFdd_PITCHCARRY 	= 0x2000,
667 		dFdd_MUTE 			= 0x4000,
668 	};
669 
670 	const uint32 dwOldFlags = pIns->dwFlags.GetRaw();
671 
672 	pIns->VolEnv.dwFlags.set(ENV_ENABLED, (dwOldFlags & dFdd_VOLUME) != 0);
673 	pIns->VolEnv.dwFlags.set(ENV_SUSTAIN, (dwOldFlags & dFdd_VOLSUSTAIN) != 0);
674 	pIns->VolEnv.dwFlags.set(ENV_LOOP, (dwOldFlags & dFdd_VOLLOOP) != 0);
675 	pIns->VolEnv.dwFlags.set(ENV_CARRY, (dwOldFlags & dFdd_VOLCARRY) != 0);
676 
677 	pIns->PanEnv.dwFlags.set(ENV_ENABLED, (dwOldFlags & dFdd_PANNING) != 0);
678 	pIns->PanEnv.dwFlags.set(ENV_SUSTAIN, (dwOldFlags & dFdd_PANSUSTAIN) != 0);
679 	pIns->PanEnv.dwFlags.set(ENV_LOOP, (dwOldFlags & dFdd_PANLOOP) != 0);
680 	pIns->PanEnv.dwFlags.set(ENV_CARRY, (dwOldFlags & dFdd_PANCARRY) != 0);
681 
682 	pIns->PitchEnv.dwFlags.set(ENV_ENABLED, (dwOldFlags & dFdd_PITCH) != 0);
683 	pIns->PitchEnv.dwFlags.set(ENV_SUSTAIN, (dwOldFlags & dFdd_PITCHSUSTAIN) != 0);
684 	pIns->PitchEnv.dwFlags.set(ENV_LOOP, (dwOldFlags & dFdd_PITCHLOOP) != 0);
685 	pIns->PitchEnv.dwFlags.set(ENV_CARRY, (dwOldFlags & dFdd_PITCHCARRY) != 0);
686 	pIns->PitchEnv.dwFlags.set(ENV_FILTER, (dwOldFlags & dFdd_FILTER) != 0);
687 
688 	pIns->dwFlags.reset();
689 	pIns->dwFlags.set(INS_SETPANNING, (dwOldFlags & dFdd_SETPANNING) != 0);
690 	pIns->dwFlags.set(INS_MUTE, (dwOldFlags & dFdd_MUTE) != 0);
691 }
692 
693 
ReadInstrumentExtensionField(ModInstrument * pIns,const uint32 code,const uint16 size,FileReader & file)694 void ReadInstrumentExtensionField(ModInstrument* pIns, const uint32 code, const uint16 size, FileReader &file)
695 {
696 	if(code == MagicBE("K[.."))
697 	{
698 		// skip keyboard mapping
699 		file.Skip(size);
700 		return;
701 	}
702 
703 	bool success = ReadInstrumentHeaderField(pIns, code, size, file);
704 
705 	if(!success)
706 	{
707 		file.Skip(size);
708 		return;
709 	}
710 
711 	if(code == MagicBE("dF..")) // 'dF..' field requires additional processing.
712 		ConvertReadExtendedFlags(pIns);
713 }
714 
715 
ReadExtendedInstrumentProperty(ModInstrument * pIns,const uint32 code,FileReader & file)716 void ReadExtendedInstrumentProperty(ModInstrument* pIns, const uint32 code, FileReader &file)
717 {
718 	uint16 size = file.ReadUint16LE();
719 	if(!file.CanRead(size))
720 	{
721 		return;
722 	}
723 	ReadInstrumentExtensionField(pIns, code, size, file);
724 }
725 
726 
ReadExtendedInstrumentProperties(ModInstrument * pIns,FileReader & file)727 void ReadExtendedInstrumentProperties(ModInstrument* pIns, FileReader &file)
728 {
729 	if(!file.ReadMagic("XTPM"))	// 'MPTX'
730 	{
731 		return;
732 	}
733 
734 	while(file.CanRead(7))
735 	{
736 		ReadExtendedInstrumentProperty(pIns, file.ReadUint32LE(), file);
737 	}
738 }
739 
740 
LoadExtendedInstrumentProperties(FileReader & file)741 bool CSoundFile::LoadExtendedInstrumentProperties(FileReader &file)
742 {
743 	if(!file.ReadMagic("XTPM"))	// 'MPTX'
744 	{
745 		return false;
746 	}
747 
748 	while(file.CanRead(6))
749 	{
750 		uint32 code = file.ReadUint32LE();
751 
752 		if(code == MagicBE("MPTS")	// Reached song extensions, break out of this loop
753 			|| code == MagicLE("228\x04")	// Reached MPTM extensions (in case there are no song extensions)
754 			|| (code & 0x80808080) || !(code & 0x60606060))	// Non-ASCII chunk ID
755 		{
756 			file.SkipBack(4);
757 			break;
758 		}
759 
760 		// Read size of this property for *one* instrument
761 		const uint16 size = file.ReadUint16LE();
762 
763 		for(INSTRUMENTINDEX i = 1; i <= GetNumInstruments(); i++)
764 		{
765 			if(Instruments[i])
766 			{
767 				ReadInstrumentExtensionField(Instruments[i], code, size, file);
768 			}
769 		}
770 	}
771 	return true;
772 }
773 
774 
775 OPENMPT_NAMESPACE_END
776