Load_ptm.cpp 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. /*
  2. * Load_ptm.cpp
  3. * ------------
  4. * Purpose: PTM (PolyTracker) module loader
  5. * Notes : (currently none)
  6. * Authors: Olivier Lapicque
  7. * OpenMPT Devs
  8. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  9. */
  10. #include "stdafx.h"
  11. #include "Loaders.h"
  12. OPENMPT_NAMESPACE_BEGIN
  13. struct PTMFileHeader
  14. {
  15. char songname[28]; // Name of song, asciiz string
  16. uint8le dosEOF; // 26
  17. uint8le versionLo; // 03 version of file, currently 0203h
  18. uint8le versionHi; // 02
  19. uint8le reserved1; // Reserved, set to 0
  20. uint16le numOrders; // Number of orders (0..256)
  21. uint16le numSamples; // Number of instruments (1..255)
  22. uint16le numPatterns; // Number of patterns (1..128)
  23. uint16le numChannels; // Number of channels (voices) used (1..32)
  24. uint16le flags; // Set to 0
  25. uint8le reserved2[2]; // Reserved, set to 0
  26. char magic[4]; // Song identification, 'PTMF'
  27. uint8le reserved3[16]; // Reserved, set to 0
  28. uint8le chnPan[32]; // Channel panning settings, 0..15, 0 = left, 7 = middle, 15 = right
  29. uint8le orders[256]; // Order list, valid entries 0..nOrders-1
  30. uint16le patOffsets[128]; // Pattern offsets (*16)
  31. };
  32. MPT_BINARY_STRUCT(PTMFileHeader, 608)
  33. struct PTMSampleHeader
  34. {
  35. enum SampleFlags
  36. {
  37. smpTypeMask = 0x03,
  38. smpPCM = 0x01,
  39. smpLoop = 0x04,
  40. smpPingPong = 0x08,
  41. smp16Bit = 0x10,
  42. };
  43. uint8le flags; // Sample type (see SampleFlags)
  44. char filename[12]; // Name of external sample file
  45. uint8le volume; // Default volume
  46. uint16le c4speed; // C-4 speed (yep, not C-5)
  47. uint8le smpSegment[2]; // Sample segment (used internally)
  48. uint32le dataOffset; // Offset of sample data
  49. uint32le length; // Sample size (in bytes)
  50. uint32le loopStart; // Start of loop
  51. uint32le loopEnd; // End of loop
  52. uint8le gusdata[14];
  53. char samplename[28]; // Name of sample, ASCIIZ
  54. char magic[4]; // Sample identification, 'PTMS'
  55. // Convert an PTM sample header to OpenMPT's internal sample header.
  56. SampleIO ConvertToMPT(ModSample &mptSmp) const
  57. {
  58. mptSmp.Initialize(MOD_TYPE_S3M);
  59. mptSmp.nVolume = std::min(volume.get(), uint8(64)) * 4;
  60. mptSmp.nC5Speed = c4speed * 2;
  61. mptSmp.filename = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, filename);
  62. SampleIO sampleIO(
  63. SampleIO::_8bit,
  64. SampleIO::mono,
  65. SampleIO::littleEndian,
  66. SampleIO::deltaPCM);
  67. if((flags & smpTypeMask) == smpPCM)
  68. {
  69. mptSmp.nLength = length;
  70. mptSmp.nLoopStart = loopStart;
  71. mptSmp.nLoopEnd = loopEnd;
  72. if(mptSmp.nLoopEnd > mptSmp.nLoopStart)
  73. mptSmp.nLoopEnd--;
  74. if(flags & smpLoop) mptSmp.uFlags.set(CHN_LOOP);
  75. if(flags & smpPingPong) mptSmp.uFlags.set(CHN_PINGPONGLOOP);
  76. if(flags & smp16Bit)
  77. {
  78. sampleIO |= SampleIO::_16bit;
  79. sampleIO |= SampleIO::PTM8Dto16;
  80. mptSmp.nLength /= 2;
  81. mptSmp.nLoopStart /= 2;
  82. mptSmp.nLoopEnd /= 2;
  83. }
  84. }
  85. return sampleIO;
  86. }
  87. };
  88. MPT_BINARY_STRUCT(PTMSampleHeader, 80)
  89. static bool ValidateHeader(const PTMFileHeader &fileHeader)
  90. {
  91. if(std::memcmp(fileHeader.magic, "PTMF", 4)
  92. || fileHeader.dosEOF != 26
  93. || fileHeader.versionHi > 2
  94. || fileHeader.flags != 0
  95. || !fileHeader.numChannels
  96. || fileHeader.numChannels > 32
  97. || !fileHeader.numOrders || fileHeader.numOrders > 256
  98. || !fileHeader.numSamples || fileHeader.numSamples > 255
  99. || !fileHeader.numPatterns || fileHeader.numPatterns > 128
  100. )
  101. {
  102. return false;
  103. }
  104. return true;
  105. }
  106. static uint64 GetHeaderMinimumAdditionalSize(const PTMFileHeader &fileHeader)
  107. {
  108. return fileHeader.numSamples * sizeof(PTMSampleHeader);
  109. }
  110. CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderPTM(MemoryFileReader file, const uint64 *pfilesize)
  111. {
  112. PTMFileHeader fileHeader;
  113. if(!file.ReadStruct(fileHeader))
  114. {
  115. return ProbeWantMoreData;
  116. }
  117. if(!ValidateHeader(fileHeader))
  118. {
  119. return ProbeFailure;
  120. }
  121. return ProbeAdditionalSize(file, pfilesize, GetHeaderMinimumAdditionalSize(fileHeader));
  122. }
  123. bool CSoundFile::ReadPTM(FileReader &file, ModLoadingFlags loadFlags)
  124. {
  125. file.Rewind();
  126. PTMFileHeader fileHeader;
  127. if(!file.ReadStruct(fileHeader))
  128. {
  129. return false;
  130. }
  131. if(!ValidateHeader(fileHeader))
  132. {
  133. return false;
  134. }
  135. if(!file.CanRead(mpt::saturate_cast<FileReader::off_t>(GetHeaderMinimumAdditionalSize(fileHeader))))
  136. {
  137. return false;
  138. }
  139. if(loadFlags == onlyVerifyHeader)
  140. {
  141. return true;
  142. }
  143. InitializeGlobals(MOD_TYPE_PTM);
  144. m_songName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, fileHeader.songname);
  145. m_modFormat.formatName = U_("PolyTracker");
  146. m_modFormat.type = U_("ptm");
  147. m_modFormat.madeWithTracker = MPT_UFORMAT("PolyTracker {}.{}")(fileHeader.versionHi.get(), mpt::ufmt::hex0<2>(fileHeader.versionLo.get()));
  148. m_modFormat.charset = mpt::Charset::CP437;
  149. m_SongFlags = SONG_ITCOMPATGXX | SONG_ITOLDEFFECTS;
  150. m_nChannels = fileHeader.numChannels;
  151. m_nSamples = std::min(static_cast<SAMPLEINDEX>(fileHeader.numSamples), static_cast<SAMPLEINDEX>(MAX_SAMPLES - 1));
  152. ReadOrderFromArray(Order(), fileHeader.orders, fileHeader.numOrders, 0xFF, 0xFE);
  153. // Reading channel panning
  154. for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++)
  155. {
  156. ChnSettings[chn].Reset();
  157. ChnSettings[chn].nPan = ((fileHeader.chnPan[chn] & 0x0F) << 4) + 4;
  158. }
  159. // Reading samples
  160. FileReader sampleHeaderChunk = file.ReadChunk(fileHeader.numSamples * sizeof(PTMSampleHeader));
  161. for(SAMPLEINDEX smp = 0; smp < m_nSamples; smp++)
  162. {
  163. PTMSampleHeader sampleHeader;
  164. sampleHeaderChunk.ReadStruct(sampleHeader);
  165. ModSample &sample = Samples[smp + 1];
  166. m_szNames[smp + 1] = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, sampleHeader.samplename);
  167. SampleIO sampleIO = sampleHeader.ConvertToMPT(sample);
  168. if((loadFlags & loadSampleData) && sample.nLength && file.Seek(sampleHeader.dataOffset))
  169. {
  170. sampleIO.ReadSample(sample, file);
  171. }
  172. }
  173. // Reading Patterns
  174. if(!(loadFlags & loadPatternData))
  175. {
  176. return true;
  177. }
  178. Patterns.ResizeArray(fileHeader.numPatterns);
  179. for(PATTERNINDEX pat = 0; pat < fileHeader.numPatterns; pat++)
  180. {
  181. if(!Patterns.Insert(pat, 64)
  182. || fileHeader.patOffsets[pat] == 0
  183. || !file.Seek(fileHeader.patOffsets[pat] << 4))
  184. {
  185. continue;
  186. }
  187. ModCommand *rowBase = Patterns[pat].GetpModCommand(0, 0);
  188. ROWINDEX row = 0;
  189. while(row < 64 && file.CanRead(1))
  190. {
  191. uint8 b = file.ReadUint8();
  192. if(b == 0)
  193. {
  194. row++;
  195. rowBase += m_nChannels;
  196. continue;
  197. }
  198. CHANNELINDEX chn = (b & 0x1F);
  199. ModCommand dummy = ModCommand();
  200. ModCommand &m = chn < GetNumChannels() ? rowBase[chn] : dummy;
  201. if(b & 0x20)
  202. {
  203. const auto [note, instr] = file.ReadArray<uint8, 2>();
  204. m.note = note;
  205. m.instr = instr;
  206. if(m.note == 254)
  207. m.note = NOTE_NOTECUT;
  208. else if(!m.note || m.note > 120)
  209. m.note = NOTE_NONE;
  210. }
  211. if(b & 0x40)
  212. {
  213. const auto [command, param] = file.ReadArray<uint8, 2>();
  214. m.command = command;
  215. m.param = param;
  216. static constexpr EffectCommand effTrans[] = { CMD_GLOBALVOLUME, CMD_RETRIG, CMD_FINEVIBRATO, CMD_NOTESLIDEUP, CMD_NOTESLIDEDOWN, CMD_NOTESLIDEUPRETRIG, CMD_NOTESLIDEDOWNRETRIG, CMD_REVERSEOFFSET };
  217. if(m.command < 0x10)
  218. {
  219. // Beware: Effect letters are as in MOD, but portamento and volume slides behave like in S3M (i.e. fine slides share the same effect letters)
  220. ConvertModCommand(m);
  221. } else if(m.command < 0x10 + std::size(effTrans))
  222. {
  223. m.command = effTrans[m.command - 0x10];
  224. } else
  225. {
  226. m.command = CMD_NONE;
  227. }
  228. switch(m.command)
  229. {
  230. case CMD_PANNING8:
  231. // Don't be surprised about the strange formula, this is directly translated from original disassembly...
  232. m.command = CMD_S3MCMDEX;
  233. m.param = 0x80 | ((std::max<uint8>(m.param >> 3, 1u) - 1u) & 0x0F);
  234. break;
  235. case CMD_GLOBALVOLUME:
  236. m.param = std::min(m.param, uint8(0x40)) * 2u;
  237. break;
  238. }
  239. }
  240. if(b & 0x80)
  241. {
  242. m.volcmd = VOLCMD_VOLUME;
  243. m.vol = file.ReadUint8();
  244. }
  245. }
  246. }
  247. return true;
  248. }
  249. OPENMPT_NAMESPACE_END