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