1
0

XMTools.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. /*
  2. * XMTools.cpp
  3. * -----------
  4. * Purpose: Definition of XM file structures and helper functions
  5. * Notes : (currently none)
  6. * Authors: OpenMPT Devs
  7. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  8. */
  9. #include "stdafx.h"
  10. #include "Loaders.h"
  11. #include "XMTools.h"
  12. #include "Sndfile.h"
  13. #include "../common/version.h"
  14. #include <algorithm>
  15. OPENMPT_NAMESPACE_BEGIN
  16. // Convert OpenMPT's internal envelope representation to XM envelope data.
  17. void XMInstrument::ConvertEnvelopeToXM(const InstrumentEnvelope &mptEnv, uint8le &numPoints, uint8le &flags, uint8le &sustain, uint8le &loopStart, uint8le &loopEnd, EnvType env)
  18. {
  19. numPoints = static_cast<uint8>(std::min(std::size_t(12), static_cast<std::size_t>(mptEnv.size())));
  20. // Envelope Data
  21. for(uint8 i = 0; i < numPoints; i++)
  22. {
  23. switch(env)
  24. {
  25. case EnvTypeVol:
  26. volEnv[i * 2] = std::min(mptEnv[i].tick, uint16_max);
  27. volEnv[i * 2 + 1] = std::min(mptEnv[i].value, uint8(64));
  28. break;
  29. case EnvTypePan:
  30. panEnv[i * 2] = std::min(mptEnv[i].tick, uint16_max);
  31. panEnv[i * 2 + 1] = std::min(mptEnv[i].value, uint8(63));
  32. break;
  33. }
  34. }
  35. // Envelope Flags
  36. if(mptEnv.dwFlags[ENV_ENABLED]) flags |= XMInstrument::envEnabled;
  37. if(mptEnv.dwFlags[ENV_SUSTAIN]) flags |= XMInstrument::envSustain;
  38. if(mptEnv.dwFlags[ENV_LOOP]) flags |= XMInstrument::envLoop;
  39. // Envelope Loops
  40. sustain = std::min(uint8(12), mptEnv.nSustainStart);
  41. loopStart = std::min(uint8(12), mptEnv.nLoopStart);
  42. loopEnd = std::min(uint8(12), mptEnv.nLoopEnd);
  43. }
  44. // Convert OpenMPT's internal sample representation to an XMInstrument.
  45. uint16 XMInstrument::ConvertToXM(const ModInstrument &mptIns, bool compatibilityExport)
  46. {
  47. MemsetZero(*this);
  48. // FFF is maximum in the FT2 GUI, but it can also accept other values. MilkyTracker just allows 0...4095 and 32767 ("cut")
  49. volFade = static_cast<uint16>(std::min(mptIns.nFadeOut, uint32(32767)));
  50. // Convert envelopes
  51. ConvertEnvelopeToXM(mptIns.VolEnv, volPoints, volFlags, volSustain, volLoopStart, volLoopEnd, EnvTypeVol);
  52. ConvertEnvelopeToXM(mptIns.PanEnv, panPoints, panFlags, panSustain, panLoopStart, panLoopEnd, EnvTypePan);
  53. // Create sample assignment table
  54. auto sampleList = GetSampleList(mptIns, compatibilityExport);
  55. for(std::size_t i = 0; i < std::size(sampleMap); i++)
  56. {
  57. if(mptIns.Keyboard[i + 12] > 0)
  58. {
  59. auto sample = std::find(sampleList.begin(), sampleList.end(), mptIns.Keyboard[i + 12]);
  60. if(sample != sampleList.end())
  61. {
  62. // Yep, we want to export this sample.
  63. sampleMap[i] = static_cast<uint8>(sample - sampleList.begin());
  64. }
  65. }
  66. }
  67. if(mptIns.nMidiChannel != MidiNoChannel)
  68. {
  69. midiEnabled = 1;
  70. midiChannel = (mptIns.nMidiChannel != MidiMappedChannel ? (mptIns.nMidiChannel - MidiFirstChannel) : 0);
  71. }
  72. midiProgram = (mptIns.nMidiProgram != 0 ? mptIns.nMidiProgram - 1 : 0);
  73. pitchWheelRange = std::min(mptIns.midiPWD, int8(36));
  74. return static_cast<uint16>(sampleList.size());
  75. }
  76. // Get a list of samples that should be written to the file.
  77. std::vector<SAMPLEINDEX> XMInstrument::GetSampleList(const ModInstrument &mptIns, bool compatibilityExport) const
  78. {
  79. std::vector<SAMPLEINDEX> sampleList; // List of samples associated with this instrument
  80. std::vector<bool> addedToList; // Which samples did we already add to the sample list?
  81. uint8 numSamples = 0;
  82. for(std::size_t i = 0; i < std::size(sampleMap); i++)
  83. {
  84. const SAMPLEINDEX smp = mptIns.Keyboard[i + 12];
  85. if(smp > 0)
  86. {
  87. if(smp > addedToList.size())
  88. {
  89. addedToList.resize(smp, false);
  90. }
  91. if(!addedToList[smp - 1] && numSamples < (compatibilityExport ? 16 : 32))
  92. {
  93. // We haven't considered this sample yet.
  94. addedToList[smp - 1] = true;
  95. numSamples++;
  96. sampleList.push_back(smp);
  97. }
  98. }
  99. }
  100. return sampleList;
  101. }
  102. // Convert XM envelope data to an OpenMPT's internal envelope representation.
  103. void XMInstrument::ConvertEnvelopeToMPT(InstrumentEnvelope &mptEnv, uint8 numPoints, uint8 flags, uint8 sustain, uint8 loopStart, uint8 loopEnd, EnvType env) const
  104. {
  105. mptEnv.resize(std::min(numPoints, uint8(12)));
  106. // Envelope Data
  107. for(uint32 i = 0; i < mptEnv.size(); i++)
  108. {
  109. switch(env)
  110. {
  111. case EnvTypeVol:
  112. mptEnv[i].tick = volEnv[i * 2];
  113. mptEnv[i].value = static_cast<EnvelopeNode::value_t>(volEnv[i * 2 + 1]);
  114. break;
  115. case EnvTypePan:
  116. mptEnv[i].tick = panEnv[i * 2];
  117. mptEnv[i].value = static_cast<EnvelopeNode::value_t>(panEnv[i * 2 + 1]);
  118. break;
  119. }
  120. if(i > 0 && mptEnv[i].tick < mptEnv[i - 1].tick && !(mptEnv[i].tick & 0xFF00))
  121. {
  122. // libmikmod code says: "Some broken XM editing program will only save the low byte of the position
  123. // value. Try to compensate by adding the missing high byte."
  124. // Note: MPT 1.07's XI instrument saver omitted the high byte of envelope nodes.
  125. // This might be the source for some broken envelopes in IT and XM files.
  126. mptEnv[i].tick |= mptEnv[i - 1].tick & 0xFF00;
  127. if(mptEnv[i].tick < mptEnv[i - 1].tick)
  128. mptEnv[i].tick += 0x100;
  129. }
  130. }
  131. // Envelope Flags
  132. mptEnv.dwFlags.reset();
  133. if((flags & XMInstrument::envEnabled) != 0 && !mptEnv.empty()) mptEnv.dwFlags.set(ENV_ENABLED);
  134. // Envelope Loops
  135. if(sustain < 12)
  136. {
  137. if((flags & XMInstrument::envSustain) != 0) mptEnv.dwFlags.set(ENV_SUSTAIN);
  138. mptEnv.nSustainStart = mptEnv.nSustainEnd = sustain;
  139. }
  140. if(loopEnd < 12 && loopEnd >= loopStart)
  141. {
  142. if((flags & XMInstrument::envLoop) != 0) mptEnv.dwFlags.set(ENV_LOOP);
  143. mptEnv.nLoopStart = loopStart;
  144. mptEnv.nLoopEnd = loopEnd;
  145. }
  146. }
  147. // Convert an XMInstrument to OpenMPT's internal instrument representation.
  148. void XMInstrument::ConvertToMPT(ModInstrument &mptIns) const
  149. {
  150. mptIns.nFadeOut = volFade;
  151. // Convert envelopes
  152. ConvertEnvelopeToMPT(mptIns.VolEnv, volPoints, volFlags, volSustain, volLoopStart, volLoopEnd, EnvTypeVol);
  153. ConvertEnvelopeToMPT(mptIns.PanEnv, panPoints, panFlags, panSustain, panLoopStart, panLoopEnd, EnvTypePan);
  154. // Create sample assignment table
  155. for(std::size_t i = 0; i < std::size(sampleMap); i++)
  156. {
  157. mptIns.Keyboard[i + 12] = sampleMap[i];
  158. }
  159. if(midiEnabled)
  160. {
  161. mptIns.nMidiChannel = midiChannel + MidiFirstChannel;
  162. Limit(mptIns.nMidiChannel, uint8(MidiFirstChannel), uint8(MidiLastChannel));
  163. mptIns.nMidiProgram = static_cast<uint8>(std::min(static_cast<uint16>(midiProgram), uint16(127)) + 1);
  164. }
  165. mptIns.midiPWD = static_cast<int8>(pitchWheelRange);
  166. }
  167. // Apply auto-vibrato settings from sample to file.
  168. void XMInstrument::ApplyAutoVibratoToXM(const ModSample &mptSmp, MODTYPE fromType)
  169. {
  170. vibType = mptSmp.nVibType;
  171. vibSweep = mptSmp.nVibSweep;
  172. vibDepth = mptSmp.nVibDepth;
  173. vibRate = mptSmp.nVibRate;
  174. if((vibDepth | vibRate) != 0 && !(fromType & MOD_TYPE_XM))
  175. {
  176. if(mptSmp.nVibSweep != 0)
  177. vibSweep = mpt::saturate_cast<decltype(vibSweep)::base_type>(Util::muldivr_unsigned(mptSmp.nVibDepth, 256, mptSmp.nVibSweep));
  178. else
  179. vibSweep = 255;
  180. }
  181. }
  182. // Apply auto-vibrato settings from file to a sample.
  183. void XMInstrument::ApplyAutoVibratoToMPT(ModSample &mptSmp) const
  184. {
  185. mptSmp.nVibType = static_cast<VibratoType>(vibType.get());
  186. mptSmp.nVibSweep = vibSweep;
  187. mptSmp.nVibDepth = vibDepth;
  188. mptSmp.nVibRate = vibRate;
  189. }
  190. // Write stuff to the header that's always necessary (also for empty instruments)
  191. void XMInstrumentHeader::Finalise()
  192. {
  193. size = sizeof(XMInstrumentHeader);
  194. if(numSamples > 0)
  195. {
  196. sampleHeaderSize = sizeof(XMSample);
  197. } else
  198. {
  199. // TODO: FT2 completely ignores MIDI settings (and also the less important stuff) if not at least one (empty) sample is assigned to this instrument!
  200. size -= sizeof(XMInstrument);
  201. sampleHeaderSize = 0;
  202. }
  203. }
  204. // Convert OpenMPT's internal sample representation to an XMInstrumentHeader.
  205. void XMInstrumentHeader::ConvertToXM(const ModInstrument &mptIns, bool compatibilityExport)
  206. {
  207. numSamples = instrument.ConvertToXM(mptIns, compatibilityExport);
  208. mpt::String::WriteBuf(mpt::String::spacePadded, name) = mptIns.name;
  209. type = mptIns.nMidiProgram; // If FT2 writes crap here, we can do so, too! (we probably shouldn't, though. This is just for backwards compatibility with old MPT versions.)
  210. }
  211. // Convert an XMInstrumentHeader to OpenMPT's internal instrument representation.
  212. void XMInstrumentHeader::ConvertToMPT(ModInstrument &mptIns) const
  213. {
  214. instrument.ConvertToMPT(mptIns);
  215. // Create sample assignment table
  216. for(std::size_t i = 0; i < std::size(instrument.sampleMap); i++)
  217. {
  218. if(instrument.sampleMap[i] < numSamples)
  219. {
  220. mptIns.Keyboard[i + 12] = instrument.sampleMap[i];
  221. } else
  222. {
  223. mptIns.Keyboard[i + 12] = 0;
  224. }
  225. }
  226. mptIns.name = mpt::String::ReadBuf(mpt::String::spacePadded, name);
  227. // Old MPT backwards compatibility
  228. if(!instrument.midiEnabled)
  229. {
  230. mptIns.nMidiProgram = type;
  231. }
  232. }
  233. // Convert OpenMPT's internal sample representation to an XIInstrumentHeader.
  234. void XIInstrumentHeader::ConvertToXM(const ModInstrument &mptIns, bool compatibilityExport)
  235. {
  236. numSamples = instrument.ConvertToXM(mptIns, compatibilityExport);
  237. memcpy(signature, "Extended Instrument: ", 21);
  238. mpt::String::WriteBuf(mpt::String::spacePadded, name) = mptIns.name;
  239. eof = 0x1A;
  240. const std::string openMptTrackerName = mpt::ToCharset(mpt::Charset::CP437, Version::Current().GetOpenMPTVersionString());
  241. mpt::String::WriteBuf(mpt::String::spacePadded, trackerName) = openMptTrackerName;
  242. version = 0x102;
  243. }
  244. // Convert an XIInstrumentHeader to OpenMPT's internal instrument representation.
  245. void XIInstrumentHeader::ConvertToMPT(ModInstrument &mptIns) const
  246. {
  247. instrument.ConvertToMPT(mptIns);
  248. // Fix sample assignment table
  249. for(std::size_t i = 12; i < std::size(instrument.sampleMap) + 12; i++)
  250. {
  251. if(mptIns.Keyboard[i] >= numSamples)
  252. {
  253. mptIns.Keyboard[i] = 0;
  254. }
  255. }
  256. mptIns.name = mpt::String::ReadBuf(mpt::String::spacePadded, name);
  257. }
  258. // Convert OpenMPT's internal sample representation to an XMSample.
  259. void XMSample::ConvertToXM(const ModSample &mptSmp, MODTYPE fromType, bool compatibilityExport)
  260. {
  261. MemsetZero(*this);
  262. // Volume / Panning
  263. vol = static_cast<uint8>(std::min(mptSmp.nVolume / 4u, 64u));
  264. pan = static_cast<uint8>(std::min(mptSmp.nPan, uint16(255)));
  265. // Sample Frequency
  266. if((fromType & (MOD_TYPE_MOD | MOD_TYPE_XM)))
  267. {
  268. finetune = mptSmp.nFineTune;
  269. relnote = mptSmp.RelativeTone;
  270. } else
  271. {
  272. std::tie(relnote, finetune) = ModSample::FrequencyToTranspose(mptSmp.nC5Speed);
  273. }
  274. flags = 0;
  275. if(mptSmp.uFlags[CHN_PINGPONGLOOP])
  276. flags |= XMSample::sampleBidiLoop;
  277. else if(mptSmp.uFlags[CHN_LOOP])
  278. flags |= XMSample::sampleLoop;
  279. // Sample Length and Loops
  280. length = mpt::saturate_cast<uint32>(mptSmp.nLength);
  281. loopStart = mpt::saturate_cast<uint32>(mptSmp.nLoopStart);
  282. loopLength = mpt::saturate_cast<uint32>(mptSmp.nLoopEnd - mptSmp.nLoopStart);
  283. if(mptSmp.uFlags[CHN_16BIT])
  284. {
  285. flags |= XMSample::sample16Bit;
  286. length *= 2;
  287. loopStart *= 2;
  288. loopLength *= 2;
  289. }
  290. if(mptSmp.uFlags[CHN_STEREO] && !compatibilityExport)
  291. {
  292. flags |= XMSample::sampleStereo;
  293. length *= 2;
  294. loopStart *= 2;
  295. loopLength *= 2;
  296. }
  297. }
  298. // Convert an XMSample to OpenMPT's internal sample representation.
  299. void XMSample::ConvertToMPT(ModSample &mptSmp) const
  300. {
  301. mptSmp.Initialize(MOD_TYPE_XM);
  302. // Volume
  303. mptSmp.nVolume = vol * 4;
  304. LimitMax(mptSmp.nVolume, uint16(256));
  305. // Panning
  306. mptSmp.nPan = pan;
  307. mptSmp.uFlags = CHN_PANNING;
  308. // Sample Frequency
  309. mptSmp.nFineTune = finetune;
  310. mptSmp.RelativeTone = relnote;
  311. // Sample Length and Loops
  312. mptSmp.nLength = length;
  313. mptSmp.nLoopStart = loopStart;
  314. mptSmp.nLoopEnd = mptSmp.nLoopStart + loopLength;
  315. if((flags & XMSample::sample16Bit))
  316. {
  317. mptSmp.nLength /= 2;
  318. mptSmp.nLoopStart /= 2;
  319. mptSmp.nLoopEnd /= 2;
  320. }
  321. if((flags & XMSample::sampleStereo))
  322. {
  323. mptSmp.nLength /= 2;
  324. mptSmp.nLoopStart /= 2;
  325. mptSmp.nLoopEnd /= 2;
  326. }
  327. if((flags & (XMSample::sampleLoop | XMSample::sampleBidiLoop)) && mptSmp.nLoopEnd > mptSmp.nLoopStart)
  328. {
  329. mptSmp.uFlags.set(CHN_LOOP);
  330. if((flags & XMSample::sampleBidiLoop))
  331. {
  332. mptSmp.uFlags.set(CHN_PINGPONGLOOP);
  333. }
  334. }
  335. mptSmp.filename = "";
  336. }
  337. // Retrieve the internal sample format flags for this instrument.
  338. SampleIO XMSample::GetSampleFormat() const
  339. {
  340. if(reserved == sampleADPCM && !(flags & (XMSample::sample16Bit | XMSample::sampleStereo)))
  341. {
  342. // MODPlugin :(
  343. return SampleIO(SampleIO::_8bit, SampleIO::mono, SampleIO::littleEndian, SampleIO::ADPCM);
  344. }
  345. return SampleIO(
  346. (flags & XMSample::sample16Bit) ? SampleIO::_16bit : SampleIO::_8bit,
  347. (flags & XMSample::sampleStereo) ? SampleIO::stereoSplit : SampleIO::mono,
  348. SampleIO::littleEndian,
  349. SampleIO::deltaPCM);
  350. }
  351. OPENMPT_NAMESPACE_END