1
0

Load_psm.cpp 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421
  1. /*
  2. * Load_psm.cpp
  3. * ------------
  4. * Purpose: PSM16 and new PSM (ProTracker Studio / Epic MegaGames MASI) module loader
  5. * Notes : This is partly based on http://www.shikadi.net/moddingwiki/ProTracker_Studio_Module
  6. * and partly reverse-engineered. Also thanks to the author of foo_dumb, the source code gave me a few clues. :)
  7. * Authors: Johannes Schultz
  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. #ifdef LIBOPENMPT_BUILD
  13. #define MPT_PSM_USE_REAL_SUBSONGS
  14. #endif
  15. OPENMPT_NAMESPACE_BEGIN
  16. ////////////////////////////////////////////////////////////
  17. //
  18. // New PSM support starts here. PSM16 structs are below.
  19. //
  20. // PSM File Header
  21. struct PSMFileHeader
  22. {
  23. char formatID[4]; // "PSM " (new format)
  24. uint32le fileSize; // Filesize - 12
  25. char fileInfoID[4]; // "FILE"
  26. };
  27. MPT_BINARY_STRUCT(PSMFileHeader, 12)
  28. // RIFF-style Chunk
  29. struct PSMChunk
  30. {
  31. // 32-Bit chunk identifiers
  32. enum ChunkIdentifiers
  33. {
  34. idTITL = MagicLE("TITL"),
  35. idSDFT = MagicLE("SDFT"),
  36. idPBOD = MagicLE("PBOD"),
  37. idSONG = MagicLE("SONG"),
  38. idDATE = MagicLE("DATE"),
  39. idOPLH = MagicLE("OPLH"),
  40. idPPAN = MagicLE("PPAN"),
  41. idPATT = MagicLE("PATT"),
  42. idDSAM = MagicLE("DSAM"),
  43. idDSMP = MagicLE("DSMP"),
  44. };
  45. uint32le id;
  46. uint32le length;
  47. size_t GetLength() const
  48. {
  49. return length;
  50. }
  51. ChunkIdentifiers GetID() const
  52. {
  53. return static_cast<ChunkIdentifiers>(id.get());
  54. }
  55. };
  56. MPT_BINARY_STRUCT(PSMChunk, 8)
  57. // Song Information
  58. struct PSMSongHeader
  59. {
  60. char songType[9]; // Mostly "MAINSONG " (But not in Extreme Pinball!)
  61. uint8 compression; // 1 - uncompressed
  62. uint8 numChannels; // Number of channels
  63. };
  64. MPT_BINARY_STRUCT(PSMSongHeader, 11)
  65. // Regular sample header
  66. struct PSMSampleHeader
  67. {
  68. uint8le flags;
  69. char fileName[8]; // Filename of the original module (without extension)
  70. char sampleID[4]; // Identifier like "INS0" (only last digit of sample ID, i.e. sample 1 and sample 11 are equal) or "I0 "
  71. char sampleName[33];
  72. uint8le unknown1[6]; // 00 00 00 00 00 FF
  73. uint16le sampleNumber;
  74. uint32le sampleLength;
  75. uint32le loopStart;
  76. uint32le loopEnd; // FF FF FF FF = end of sample
  77. uint8le unknown3;
  78. uint8le finetune; // unused? always 0
  79. uint8le defaultVolume;
  80. uint32le unknown4;
  81. uint32le c5Freq; // MASI ignores the high 16 bits
  82. char padding[19];
  83. // Convert header data to OpenMPT's internal format
  84. void ConvertToMPT(ModSample &mptSmp) const
  85. {
  86. mptSmp.Initialize();
  87. mptSmp.filename = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, fileName);
  88. mptSmp.nC5Speed = c5Freq;
  89. mptSmp.nLength = sampleLength;
  90. mptSmp.nLoopStart = loopStart;
  91. // Note that we shouldn't add + 1 for MTM conversions here (e.g. the OMF 2097 music),
  92. // but I think there is no way to figure out the original format, and in the case of the OMF 2097 soundtrack
  93. // it doesn't make a huge audible difference anyway (no chip samples are used).
  94. // On the other hand, sample 8 of MUSIC_A.PSM from Extreme Pinball will sound detuned if we don't adjust the loop end here.
  95. if(loopEnd)
  96. mptSmp.nLoopEnd = loopEnd + 1;
  97. mptSmp.nVolume = (defaultVolume + 1) * 2;
  98. mptSmp.uFlags.set(CHN_LOOP, (flags & 0x80) != 0);
  99. LimitMax(mptSmp.nLoopEnd, mptSmp.nLength);
  100. LimitMax(mptSmp.nLoopStart, mptSmp.nLoopEnd);
  101. }
  102. };
  103. MPT_BINARY_STRUCT(PSMSampleHeader, 96)
  104. // Sinaria sample header (and possibly other games)
  105. struct PSMSinariaSampleHeader
  106. {
  107. uint8le flags;
  108. char fileName[8]; // Filename of the original module (without extension)
  109. char sampleID[8]; // INS0...INS99999
  110. char sampleName[33];
  111. uint8le unknown1[6]; // 00 00 00 00 00 FF
  112. uint16le sampleNumber;
  113. uint32le sampleLength;
  114. uint32le loopStart;
  115. uint32le loopEnd;
  116. uint16le unknown3;
  117. uint8le finetune; // Appears to be unused
  118. uint8le defaultVolume;
  119. uint32le unknown4;
  120. uint16le c5Freq;
  121. char padding[16];
  122. // Convert header data to OpenMPT's internal format
  123. void ConvertToMPT(ModSample &mptSmp) const
  124. {
  125. mptSmp.Initialize();
  126. mptSmp.filename = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, fileName);
  127. mptSmp.nC5Speed = c5Freq;
  128. mptSmp.nLength = sampleLength;
  129. mptSmp.nLoopStart = loopStart;
  130. mptSmp.nLoopEnd = loopEnd;
  131. mptSmp.nVolume = (defaultVolume + 1) * 2;
  132. mptSmp.uFlags.set(CHN_LOOP, (flags & 0x80) != 0);
  133. LimitMax(mptSmp.nLoopEnd, mptSmp.nLength);
  134. LimitMax(mptSmp.nLoopStart, mptSmp.nLoopEnd);
  135. }
  136. };
  137. MPT_BINARY_STRUCT(PSMSinariaSampleHeader, 96)
  138. struct PSMSubSong // For internal use (pattern conversion)
  139. {
  140. std::vector<uint8> channelPanning, channelVolume;
  141. std::vector<bool> channelSurround;
  142. ORDERINDEX startOrder = ORDERINDEX_INVALID, endOrder = ORDERINDEX_INVALID, restartPos = 0;
  143. uint8 defaultTempo = 125, defaultSpeed = 6;
  144. char songName[10] = {};
  145. PSMSubSong()
  146. : channelPanning(MAX_BASECHANNELS, 128)
  147. , channelVolume(MAX_BASECHANNELS, 64)
  148. , channelSurround(MAX_BASECHANNELS, false)
  149. { }
  150. };
  151. // Portamento effect conversion (depending on format version)
  152. static uint8 ConvertPSMPorta(uint8 param, bool sinariaFormat)
  153. {
  154. if(sinariaFormat)
  155. return param;
  156. if(param < 4)
  157. return (param | 0xF0);
  158. else
  159. return (param >> 2);
  160. }
  161. // Read a Pattern ID (something like "P0 " or "P13 ", or "PATT0 " in Sinaria)
  162. static PATTERNINDEX ReadPSMPatternIndex(FileReader &file, bool &sinariaFormat)
  163. {
  164. char patternID[5];
  165. uint8 offset = 1;
  166. file.ReadString<mpt::String::spacePadded>(patternID, 4);
  167. if(!memcmp(patternID, "PATT", 4))
  168. {
  169. file.ReadString<mpt::String::spacePadded>(patternID, 4);
  170. sinariaFormat = true;
  171. offset = 0;
  172. }
  173. return ConvertStrTo<uint16>(&patternID[offset]);
  174. }
  175. static bool ValidateHeader(const PSMFileHeader &fileHeader)
  176. {
  177. if(!std::memcmp(fileHeader.formatID, "PSM ", 4)
  178. && !std::memcmp(fileHeader.fileInfoID, "FILE", 4))
  179. {
  180. return true;
  181. }
  182. #ifdef MPT_PSM_DECRYPT
  183. if(!std::memcmp(fileHeader.formatID, "QUP$", 4)
  184. && !std::memcmp(fileHeader.fileInfoID, "OSWQ", 4))
  185. {
  186. return true;
  187. }
  188. #endif
  189. return false;
  190. }
  191. CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderPSM(MemoryFileReader file, const uint64 *pfilesize)
  192. {
  193. PSMFileHeader fileHeader;
  194. if(!file.ReadStruct(fileHeader))
  195. {
  196. return ProbeWantMoreData;
  197. }
  198. if(!ValidateHeader(fileHeader))
  199. {
  200. return ProbeFailure;
  201. }
  202. PSMChunk chunkHeader;
  203. if(!file.ReadStruct(chunkHeader))
  204. {
  205. return ProbeWantMoreData;
  206. }
  207. if(chunkHeader.length == 0)
  208. {
  209. return ProbeFailure;
  210. }
  211. if((chunkHeader.id & 0x7F7F7F7Fu) != chunkHeader.id) // ASCII?
  212. {
  213. return ProbeFailure;
  214. }
  215. MPT_UNREFERENCED_PARAMETER(pfilesize);
  216. return ProbeSuccess;
  217. }
  218. bool CSoundFile::ReadPSM(FileReader &file, ModLoadingFlags loadFlags)
  219. {
  220. file.Rewind();
  221. PSMFileHeader fileHeader;
  222. if(!file.ReadStruct(fileHeader))
  223. {
  224. return false;
  225. }
  226. #ifdef MPT_PSM_DECRYPT
  227. // CONVERT.EXE /K - I don't think any game ever used this.
  228. std::vector<std::byte> decrypted;
  229. if(!memcmp(fileHeader.formatID, "QUP$", 4)
  230. && !memcmp(fileHeader.fileInfoID, "OSWQ", 4))
  231. {
  232. if(loadFlags == onlyVerifyHeader)
  233. return true;
  234. file.Rewind();
  235. decrypted.resize(file.GetLength());
  236. file.ReadRaw(decrypted.data(), decrypted.size());
  237. uint8 i = 0;
  238. for(auto &c : decrypted)
  239. {
  240. c -= ++i;
  241. }
  242. file = FileReader(mpt::as_span(decrypted));
  243. file.ReadStruct(fileHeader);
  244. }
  245. #endif // MPT_PSM_DECRYPT
  246. // Check header
  247. if(!ValidateHeader(fileHeader))
  248. {
  249. return false;
  250. }
  251. ChunkReader chunkFile(file);
  252. ChunkReader::ChunkList<PSMChunk> chunks;
  253. if(loadFlags == onlyVerifyHeader)
  254. chunks = chunkFile.ReadChunksUntil<PSMChunk>(1, PSMChunk::idSDFT);
  255. else
  256. chunks = chunkFile.ReadChunks<PSMChunk>(1);
  257. // "SDFT" - Format info (song data starts here)
  258. if(!chunks.GetChunk(PSMChunk::idSDFT).ReadMagic("MAINSONG"))
  259. return false;
  260. else if(loadFlags == onlyVerifyHeader)
  261. return true;
  262. // Yep, this seems to be a valid file.
  263. InitializeGlobals(MOD_TYPE_PSM);
  264. m_SongFlags = SONG_ITOLDEFFECTS | SONG_ITCOMPATGXX;
  265. // "TITL" - Song Title
  266. FileReader titleChunk = chunks.GetChunk(PSMChunk::idTITL);
  267. titleChunk.ReadString<mpt::String::spacePadded>(m_songName, titleChunk.GetLength());
  268. Order().clear();
  269. // Subsong setup
  270. std::vector<PSMSubSong> subsongs;
  271. bool subsongPanningDiffers = false; // Do we have subsongs with different panning positions?
  272. bool sinariaFormat = false; // The game "Sinaria" uses a slightly modified PSM structure - in some ways it's more like PSM16 (e.g. effects).
  273. // "SONG" - Subsong information (channel count etc)
  274. auto songChunks = chunks.GetAllChunks(PSMChunk::idSONG);
  275. for(ChunkReader chunk : songChunks)
  276. {
  277. PSMSongHeader songHeader;
  278. if(!chunk.ReadStruct(songHeader)
  279. || songHeader.compression != 0x01) // No compression for PSM files
  280. {
  281. return false;
  282. }
  283. // Subsongs *might* have different channel count
  284. m_nChannels = Clamp(static_cast<CHANNELINDEX>(songHeader.numChannels), m_nChannels, MAX_BASECHANNELS);
  285. PSMSubSong subsong;
  286. mpt::String::WriteAutoBuf(subsong.songName) = mpt::String::ReadBuf(mpt::String::nullTerminated, songHeader.songType);
  287. #ifdef MPT_PSM_USE_REAL_SUBSONGS
  288. if(!Order().empty())
  289. {
  290. // Add a new sequence for this subsong
  291. if(Order.AddSequence() == SEQUENCEINDEX_INVALID)
  292. break;
  293. }
  294. Order().SetName(mpt::ToUnicode(mpt::Charset::CP437, subsong.songName));
  295. #endif // MPT_PSM_USE_REAL_SUBSONGS
  296. // Read "Sub chunks"
  297. auto subChunks = chunk.ReadChunks<PSMChunk>(1);
  298. for(const auto &subChunkIter : subChunks.chunks)
  299. {
  300. FileReader subChunk(subChunkIter.GetData());
  301. PSMChunk subChunkHead = subChunkIter.GetHeader();
  302. switch(subChunkHead.GetID())
  303. {
  304. #if 0
  305. case PSMChunk::idDATE: // "DATE" - Conversion date (YYMMDD)
  306. if(subChunkHead.GetLength() == 6)
  307. {
  308. char cversion[7];
  309. subChunk.ReadString<mpt::String::maybeNullTerminated>(cversion, 6);
  310. uint32 version = ConvertStrTo<uint32>(cversion);
  311. // Sinaria song dates (just to go sure...)
  312. if(version == 800211 || version == 940902 || version == 940903 ||
  313. version == 940906 || version == 940914 || version == 941213)
  314. sinariaFormat = true;
  315. }
  316. break;
  317. #endif
  318. case PSMChunk::idOPLH: // "OPLH" - Order list, channel + module settings
  319. if(subChunkHead.GetLength() >= 9)
  320. {
  321. // First two bytes = Number of chunks that follow
  322. //uint16 totalChunks = subChunk.ReadUint16LE();
  323. subChunk.Skip(2);
  324. // Now, the interesting part begins!
  325. uint16 chunkCount = 0, firstOrderChunk = uint16_max;
  326. // "Sub sub chunks" (grrrr, silly format)
  327. while(subChunk.CanRead(1))
  328. {
  329. uint8 opcode = subChunk.ReadUint8();
  330. if(!opcode)
  331. {
  332. // Last chunk was reached.
  333. break;
  334. }
  335. // Note: This is more like a playlist than a collection of global values.
  336. // In theory, a tempo item inbetween two order items should modify the
  337. // tempo when switching patterns. No module uses this feature in practice
  338. // though, so we can keep our loader simple.
  339. // Unimplemented opcodes do nothing or freeze MASI.
  340. switch(opcode)
  341. {
  342. case 0x01: // Play order list item
  343. {
  344. if(subsong.startOrder == ORDERINDEX_INVALID)
  345. subsong.startOrder = Order().GetLength();
  346. subsong.endOrder = Order().GetLength();
  347. PATTERNINDEX pat = ReadPSMPatternIndex(subChunk, sinariaFormat);
  348. if(pat == 0xFF)
  349. pat = Order.GetInvalidPatIndex();
  350. else if(pat == 0xFE)
  351. pat = Order.GetIgnoreIndex();
  352. Order().push_back(pat);
  353. // Decide whether this is the first order chunk or not (for finding out the correct restart position)
  354. if(firstOrderChunk == uint16_max)
  355. firstOrderChunk = chunkCount;
  356. }
  357. break;
  358. // 0x02: Play Range
  359. // 0x03: Jump Loop
  360. case 0x04: // Jump Line (Restart position)
  361. {
  362. uint16 restartChunk = subChunk.ReadUint16LE();
  363. if(restartChunk >= firstOrderChunk)
  364. subsong.restartPos = static_cast<ORDERINDEX>(restartChunk - firstOrderChunk); // Close enough - we assume that order list is continuous (like in any real-world PSM)
  365. Order().SetRestartPos(subsong.restartPos);
  366. }
  367. break;
  368. // 0x05: Channel Flip
  369. // 0x06: Transpose
  370. case 0x07: // Default Speed
  371. subsong.defaultSpeed = subChunk.ReadUint8();
  372. break;
  373. case 0x08: // Default Tempo
  374. subsong.defaultTempo = subChunk.ReadUint8();
  375. break;
  376. case 0x0C: // Sample map table
  377. // Never seems to be different, so...
  378. // This is probably a part of the never-implemented "mini programming language" mentioned in the PSM docs.
  379. // Output of PLAY.EXE: "SMapTabl from pos 0 to pos -1 starting at 0 and adding 1 to it each time"
  380. // It appears that this maps e.g. what is "I0" in the file to sample 1.
  381. // If we were being fancy, we could implement this, but in practice it won't matter.
  382. {
  383. uint8 mapTable[6];
  384. if(!subChunk.ReadArray(mapTable)
  385. || mapTable[0] != 0x00 || mapTable[1] != 0xFF // "0 to -1" (does not seem to do anything)
  386. || mapTable[2] != 0x00 || mapTable[3] != 0x00 // "at 0" (actually this appears to be the adding part - changing this to 0x01 0x00 offsets all samples by 1)
  387. || mapTable[4] != 0x01 || mapTable[5] != 0x00) // "adding 1" (does not seem to do anything)
  388. {
  389. return false;
  390. }
  391. }
  392. break;
  393. case 0x0D: // Channel panning table - can be set using CONVERT.EXE /E
  394. {
  395. const auto [chn, pan, type] = subChunk.ReadArray<uint8, 3>();
  396. if(chn < subsong.channelPanning.size())
  397. {
  398. switch(type)
  399. {
  400. case 0: // use panning
  401. subsong.channelPanning[chn] = pan ^ 128;
  402. subsong.channelSurround[chn] = false;
  403. break;
  404. case 2: // surround
  405. subsong.channelPanning[chn] = 128;
  406. subsong.channelSurround[chn] = true;
  407. break;
  408. case 4: // center
  409. subsong.channelPanning[chn] = 128;
  410. subsong.channelSurround[chn] = false;
  411. break;
  412. }
  413. if(subsongPanningDiffers == false && subsongs.size() > 0)
  414. {
  415. if(subsongs.back().channelPanning[chn] != subsong.channelPanning[chn]
  416. || subsongs.back().channelSurround[chn] != subsong.channelSurround[chn])
  417. subsongPanningDiffers = true;
  418. }
  419. }
  420. }
  421. break;
  422. case 0x0E: // Channel volume table (0...255) - can be set using CONVERT.EXE /E, is 255 in all "official" PSMs except for some OMF 2097 tracks
  423. {
  424. const auto [chn, vol] = subChunk.ReadArray<uint8, 2>();
  425. if(chn < subsong.channelVolume.size())
  426. {
  427. subsong.channelVolume[chn] = (vol / 4u) + 1;
  428. }
  429. }
  430. break;
  431. default:
  432. // Should never happen in "real" PSM files. But in this case, we have to quit as we don't know how big the chunk really is.
  433. return false;
  434. }
  435. chunkCount++;
  436. }
  437. }
  438. break;
  439. case PSMChunk::idPPAN: // PPAN - Channel panning table (used in Sinaria)
  440. // In some Sinaria tunes, this is actually longer than 2 * channels...
  441. MPT_ASSERT(subChunkHead.GetLength() >= m_nChannels * 2u);
  442. for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++)
  443. {
  444. if(!subChunk.CanRead(2))
  445. break;
  446. const auto [type, pan] = subChunk.ReadArray<uint8, 2>();
  447. switch(type)
  448. {
  449. case 0: // use panning
  450. subsong.channelPanning[chn] = pan ^ 128;
  451. subsong.channelSurround[chn] = false;
  452. break;
  453. case 2: // surround
  454. subsong.channelPanning[chn] = 128;
  455. subsong.channelSurround[chn] = true;
  456. break;
  457. case 4: // center
  458. subsong.channelPanning[chn] = 128;
  459. subsong.channelSurround[chn] = false;
  460. break;
  461. default:
  462. break;
  463. }
  464. }
  465. break;
  466. case PSMChunk::idPATT: // PATT - Pattern list
  467. // We don't really need this.
  468. break;
  469. case PSMChunk::idDSAM: // DSAM - Sample list
  470. // We don't need this either.
  471. break;
  472. default:
  473. break;
  474. }
  475. }
  476. // Attach this subsong to the subsong list - finally, all "sub sub sub ..." chunks are parsed.
  477. if(subsong.startOrder != ORDERINDEX_INVALID && subsong.endOrder != ORDERINDEX_INVALID)
  478. {
  479. // Separate subsongs by "---" patterns
  480. Order().push_back();
  481. subsongs.push_back(subsong);
  482. }
  483. }
  484. #ifdef MPT_PSM_USE_REAL_SUBSONGS
  485. Order.SetSequence(0);
  486. #endif // MPT_PSM_USE_REAL_SUBSONGS
  487. if(subsongs.empty())
  488. return false;
  489. // DSMP - Samples
  490. if(loadFlags & loadSampleData)
  491. {
  492. auto sampleChunks = chunks.GetAllChunks(PSMChunk::idDSMP);
  493. for(auto &chunk : sampleChunks)
  494. {
  495. SAMPLEINDEX smp;
  496. if(!sinariaFormat)
  497. {
  498. // Original header
  499. PSMSampleHeader sampleHeader;
  500. if(!chunk.ReadStruct(sampleHeader))
  501. continue;
  502. smp = static_cast<SAMPLEINDEX>(sampleHeader.sampleNumber + 1);
  503. if(smp > 0 && smp < MAX_SAMPLES)
  504. {
  505. m_nSamples = std::max(m_nSamples, smp);
  506. sampleHeader.ConvertToMPT(Samples[smp]);
  507. m_szNames[smp] = mpt::String::ReadBuf(mpt::String::nullTerminated, sampleHeader.sampleName);
  508. }
  509. } else
  510. {
  511. // Sinaria uses a slightly different sample header
  512. PSMSinariaSampleHeader sampleHeader;
  513. if(!chunk.ReadStruct(sampleHeader))
  514. continue;
  515. smp = static_cast<SAMPLEINDEX>(sampleHeader.sampleNumber + 1);
  516. if(smp > 0 && smp < MAX_SAMPLES)
  517. {
  518. m_nSamples = std::max(m_nSamples, smp);
  519. sampleHeader.ConvertToMPT(Samples[smp]);
  520. m_szNames[smp] = mpt::String::ReadBuf(mpt::String::nullTerminated, sampleHeader.sampleName);
  521. }
  522. }
  523. if(smp > 0 && smp < MAX_SAMPLES)
  524. {
  525. SampleIO(
  526. SampleIO::_8bit,
  527. SampleIO::mono,
  528. SampleIO::littleEndian,
  529. SampleIO::deltaPCM).ReadSample(Samples[smp], chunk);
  530. }
  531. }
  532. }
  533. // Make the default variables of the first subsong global
  534. m_nDefaultSpeed = subsongs[0].defaultSpeed;
  535. m_nDefaultTempo.Set(subsongs[0].defaultTempo);
  536. Order().SetRestartPos(subsongs[0].restartPos);
  537. for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++)
  538. {
  539. ChnSettings[chn].Reset();
  540. ChnSettings[chn].nVolume = subsongs[0].channelVolume[chn];
  541. ChnSettings[chn].nPan = subsongs[0].channelPanning[chn];
  542. ChnSettings[chn].dwFlags.set(CHN_SURROUND, subsongs[0].channelSurround[chn]);
  543. }
  544. m_modFormat.formatName = sinariaFormat ? U_("Epic MegaGames MASI (New Version / Sinaria)") : U_("Epic MegaGames MASI (New Version)");
  545. m_modFormat.type = U_("psm");
  546. m_modFormat.charset = mpt::Charset::CP437;
  547. if(!(loadFlags & loadPatternData) || m_nChannels == 0)
  548. {
  549. return true;
  550. }
  551. // "PBOD" - Pattern data of a single pattern
  552. // Now that we know the number of channels, we can go through all the patterns.
  553. auto pattChunks = chunks.GetAllChunks(PSMChunk::idPBOD);
  554. Patterns.ResizeArray(static_cast<PATTERNINDEX>(pattChunks.size()));
  555. for(auto &chunk : pattChunks)
  556. {
  557. if(chunk.GetLength() != chunk.ReadUint32LE() // Same value twice
  558. || !chunk.LengthIsAtLeast(8))
  559. {
  560. continue;
  561. }
  562. PATTERNINDEX pat = ReadPSMPatternIndex(chunk, sinariaFormat);
  563. uint16 numRows = chunk.ReadUint16LE();
  564. if(!Patterns.Insert(pat, numRows))
  565. {
  566. continue;
  567. }
  568. enum
  569. {
  570. noteFlag = 0x80,
  571. instrFlag = 0x40,
  572. volFlag = 0x20,
  573. effectFlag = 0x10,
  574. };
  575. // Read pattern.
  576. for(ROWINDEX row = 0; row < numRows; row++)
  577. {
  578. PatternRow rowBase = Patterns[pat].GetRow(row);
  579. uint16 rowSize = chunk.ReadUint16LE();
  580. if(rowSize <= 2)
  581. {
  582. continue;
  583. }
  584. FileReader rowChunk = chunk.ReadChunk(rowSize - 2);
  585. while(rowChunk.CanRead(3))
  586. {
  587. const auto [flags, channel] = rowChunk.ReadArray<uint8, 2>();
  588. // Point to the correct channel
  589. ModCommand &m = rowBase[std::min(static_cast<CHANNELINDEX>(m_nChannels - 1), static_cast<CHANNELINDEX>(channel))];
  590. if(flags & noteFlag)
  591. {
  592. // Note present
  593. uint8 note = rowChunk.ReadUint8();
  594. if(!sinariaFormat)
  595. {
  596. if(note == 0xFF) // Can be found in a few files but is apparently not supported by MASI
  597. note = NOTE_NOTECUT;
  598. else
  599. if(note < 129) note = (note & 0x0F) + 12 * (note >> 4) + 13;
  600. } else
  601. {
  602. if(note < 85) note += 36;
  603. }
  604. m.note = note;
  605. }
  606. if(flags & instrFlag)
  607. {
  608. // Instrument present
  609. m.instr = rowChunk.ReadUint8() + 1;
  610. }
  611. if(flags & volFlag)
  612. {
  613. // Volume present
  614. uint8 vol = rowChunk.ReadUint8();
  615. m.volcmd = VOLCMD_VOLUME;
  616. m.vol = (std::min(vol, uint8(127)) + 1) / 2;
  617. }
  618. if(flags & effectFlag)
  619. {
  620. // Effect present - convert
  621. const auto [command, param] = rowChunk.ReadArray<uint8, 2>();
  622. m.param = param;
  623. // This list is annoyingly similar to PSM16, but not quite identical.
  624. switch(command)
  625. {
  626. // Volslides
  627. case 0x01: // fine volslide up
  628. m.command = CMD_VOLUMESLIDE;
  629. if (sinariaFormat) m.param = (m.param << 4) | 0x0F;
  630. else m.param = ((m.param & 0x1E) << 3) | 0x0F;
  631. break;
  632. case 0x02: // volslide up
  633. m.command = CMD_VOLUMESLIDE;
  634. if (sinariaFormat) m.param = 0xF0 & (m.param << 4);
  635. else m.param = 0xF0 & (m.param << 3);
  636. break;
  637. case 0x03: // fine volslide down
  638. m.command = CMD_VOLUMESLIDE;
  639. if (sinariaFormat) m.param |= 0xF0;
  640. else m.param = 0xF0 | (m.param >> 1);
  641. break;
  642. case 0x04: // volslide down
  643. m.command = CMD_VOLUMESLIDE;
  644. if (sinariaFormat) m.param &= 0x0F;
  645. else if(m.param < 2) m.param |= 0xF0; else m.param = (m.param >> 1) & 0x0F;
  646. break;
  647. // Portamento
  648. case 0x0B: // fine portamento up
  649. m.command = CMD_PORTAMENTOUP;
  650. m.param = 0xF0 | ConvertPSMPorta(m.param, sinariaFormat);
  651. break;
  652. case 0x0C: // portamento up
  653. m.command = CMD_PORTAMENTOUP;
  654. m.param = ConvertPSMPorta(m.param, sinariaFormat);
  655. break;
  656. case 0x0D: // fine portamento down
  657. m.command = CMD_PORTAMENTODOWN;
  658. m.param = 0xF0 | ConvertPSMPorta(m.param, sinariaFormat);
  659. break;
  660. case 0x0E: // portamento down
  661. m.command = CMD_PORTAMENTODOWN;
  662. m.param = ConvertPSMPorta(m.param, sinariaFormat);
  663. break;
  664. case 0x0F: // tone portamento
  665. m.command = CMD_TONEPORTAMENTO;
  666. if(!sinariaFormat) m.param >>= 2;
  667. break;
  668. case 0x11: // glissando control
  669. m.command = CMD_S3MCMDEX;
  670. m.param = 0x10 | (m.param & 0x01);
  671. break;
  672. case 0x10: // tone portamento + volslide up
  673. m.command = CMD_TONEPORTAVOL;
  674. m.param = m.param & 0xF0;
  675. break;
  676. case 0x12: // tone portamento + volslide down
  677. m.command = CMD_TONEPORTAVOL;
  678. m.param = (m.param >> 4) & 0x0F;
  679. break;
  680. case 0x13: // ScreamTracker command S - actually hangs / crashes MASI
  681. m.command = CMD_S3MCMDEX;
  682. break;
  683. // Vibrato
  684. case 0x15: // vibrato
  685. m.command = CMD_VIBRATO;
  686. break;
  687. case 0x16: // vibrato waveform
  688. m.command = CMD_S3MCMDEX;
  689. m.param = 0x30 | (m.param & 0x0F);
  690. break;
  691. case 0x17: // vibrato + volslide up
  692. m.command = CMD_VIBRATOVOL;
  693. m.param = 0xF0 | m.param;
  694. break;
  695. case 0x18: // vibrato + volslide down
  696. m.command = CMD_VIBRATOVOL;
  697. break;
  698. // Tremolo
  699. case 0x1F: // tremolo
  700. m.command = CMD_TREMOLO;
  701. break;
  702. case 0x20: // tremolo waveform
  703. m.command = CMD_S3MCMDEX;
  704. m.param = 0x40 | (m.param & 0x0F);
  705. break;
  706. // Sample commands
  707. case 0x29: // 3-byte offset - we only support the middle byte.
  708. m.command = CMD_OFFSET;
  709. m.param = rowChunk.ReadUint8();
  710. rowChunk.Skip(1);
  711. break;
  712. case 0x2A: // retrigger
  713. m.command = CMD_RETRIG;
  714. break;
  715. case 0x2B: // note cut
  716. m.command = CMD_S3MCMDEX;
  717. m.param = 0xC0 | (m.param & 0x0F);
  718. break;
  719. case 0x2C: // note delay
  720. m.command = CMD_S3MCMDEX;
  721. m.param = 0xD0 | (m.param & 0x0F);
  722. break;
  723. // Position change
  724. case 0x33: // position jump - MASI seems to ignore this command, and CONVERT.EXE never writes it
  725. m.command = CMD_POSITIONJUMP;
  726. m.param /= 2u; // actually it is probably just an index into the order table
  727. rowChunk.Skip(1);
  728. break;
  729. case 0x34: // pattern break
  730. m.command = CMD_PATTERNBREAK;
  731. // When converting from S3M, the parameter is double-BDC-encoded (wtf!)
  732. // When converting from MOD, it's in binary.
  733. // MASI ignores the parameter entirely, and so do we.
  734. m.param = 0;
  735. break;
  736. case 0x35: // loop pattern
  737. m.command = CMD_S3MCMDEX;
  738. m.param = 0xB0 | (m.param & 0x0F);
  739. break;
  740. case 0x36: // pattern delay
  741. m.command = CMD_S3MCMDEX;
  742. m.param = 0xE0 | (m.param & 0x0F);
  743. break;
  744. // speed change
  745. case 0x3D: // set speed
  746. m.command = CMD_SPEED;
  747. break;
  748. case 0x3E: // set tempo
  749. m.command = CMD_TEMPO;
  750. break;
  751. // misc commands
  752. case 0x47: // arpeggio
  753. m.command = CMD_ARPEGGIO;
  754. break;
  755. case 0x48: // set finetune
  756. m.command = CMD_S3MCMDEX;
  757. m.param = 0x20 | (m.param & 0x0F);
  758. break;
  759. case 0x49: // set balance
  760. m.command = CMD_S3MCMDEX;
  761. m.param = 0x80 | (m.param & 0x0F);
  762. break;
  763. default:
  764. m.command = CMD_NONE;
  765. break;
  766. }
  767. }
  768. }
  769. }
  770. }
  771. if(subsongs.size() > 1)
  772. {
  773. // Write subsong "configuration" to patterns (only if there are multiple subsongs)
  774. for(size_t i = 0; i < subsongs.size(); i++)
  775. {
  776. #ifdef MPT_PSM_USE_REAL_SUBSONGS
  777. ModSequence &order = Order(static_cast<SEQUENCEINDEX>(i));
  778. #else
  779. ModSequence &order = Order();
  780. #endif // MPT_PSM_USE_REAL_SUBSONGS
  781. const PSMSubSong &subsong = subsongs[i];
  782. PATTERNINDEX startPattern = order[subsong.startOrder];
  783. if(Patterns.IsValidPat(startPattern))
  784. {
  785. startPattern = order.EnsureUnique(subsong.startOrder);
  786. // Subsongs with different panning setup -> write to pattern (MUSIC_C.PSM)
  787. // Don't write channel volume for now, as there is no real-world module which needs it.
  788. if(subsongPanningDiffers)
  789. {
  790. for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++)
  791. {
  792. if(subsong.channelSurround[chn])
  793. Patterns[startPattern].WriteEffect(EffectWriter(CMD_S3MCMDEX, 0x91).Row(0).Channel(chn).RetryNextRow());
  794. else
  795. Patterns[startPattern].WriteEffect(EffectWriter(CMD_PANNING8, subsong.channelPanning[chn]).Row(0).Channel(chn).RetryNextRow());
  796. }
  797. }
  798. // Write default tempo/speed to pattern
  799. Patterns[startPattern].WriteEffect(EffectWriter(CMD_SPEED, subsong.defaultSpeed).Row(0).RetryNextRow());
  800. Patterns[startPattern].WriteEffect(EffectWriter(CMD_TEMPO, subsong.defaultTempo).Row(0).RetryNextRow());
  801. }
  802. #ifndef MPT_PSM_USE_REAL_SUBSONGS
  803. // Add restart position to the last pattern
  804. PATTERNINDEX endPattern = order[subsong.endOrder];
  805. if(Patterns.IsValidPat(endPattern))
  806. {
  807. endPattern = order.EnsureUnique(subsong.endOrder);
  808. ROWINDEX lastRow = Patterns[endPattern].GetNumRows() - 1;
  809. auto m = Patterns[endPattern].cbegin();
  810. for(uint32 cell = 0; cell < m_nChannels * Patterns[endPattern].GetNumRows(); cell++, m++)
  811. {
  812. if(m->command == CMD_PATTERNBREAK || m->command == CMD_POSITIONJUMP)
  813. {
  814. lastRow = cell / m_nChannels;
  815. break;
  816. }
  817. }
  818. Patterns[endPattern].WriteEffect(EffectWriter(CMD_POSITIONJUMP, mpt::saturate_cast<ModCommand::PARAM>(subsong.startOrder + subsong.restartPos)).Row(lastRow).RetryPreviousRow());
  819. }
  820. // Set the subsong name to all pattern names
  821. for(ORDERINDEX ord = subsong.startOrder; ord <= subsong.endOrder; ord++)
  822. {
  823. if(Patterns.IsValidIndex(order[ord]))
  824. Patterns[order[ord]].SetName(subsong.songName);
  825. }
  826. #endif // MPT_PSM_USE_REAL_SUBSONGS
  827. }
  828. }
  829. return true;
  830. }
  831. ////////////////////////////////
  832. //
  833. // PSM16 support starts here.
  834. //
  835. struct PSM16FileHeader
  836. {
  837. char formatID[4]; // "PSM\xFE" (PSM16)
  838. char songName[59]; // Song title, padded with nulls
  839. uint8le lineEnd; // $1A
  840. uint8le songType; // Song Type bitfield
  841. uint8le formatVersion; // $10
  842. uint8le patternVersion; // 0 or 1
  843. uint8le songSpeed; // 1 ... 255
  844. uint8le songTempo; // 32 ... 255
  845. uint8le masterVolume; // 0 ... 255
  846. uint16le songLength; // 0 ... 255 (number of patterns to play in the song)
  847. uint16le songOrders; // 0 ... 255 (same as previous value as no subsongs are present)
  848. uint16le numPatterns; // 1 ... 255
  849. uint16le numSamples; // 1 ... 255
  850. uint16le numChannelsPlay; // 0 ... 32 (max. number of channels to play)
  851. uint16le numChannelsReal; // 0 ... 32 (max. number of channels to process)
  852. uint32le orderOffset; // Pointer to order list
  853. uint32le panOffset; // Pointer to pan table
  854. uint32le patOffset; // Pointer to pattern data
  855. uint32le smpOffset; // Pointer to sample headers
  856. uint32le commentsOffset; // Pointer to song comment
  857. uint32le patSize; // Size of all patterns
  858. char filler[40];
  859. };
  860. MPT_BINARY_STRUCT(PSM16FileHeader, 146)
  861. struct PSM16SampleHeader
  862. {
  863. enum SampleFlags
  864. {
  865. smpMask = 0x7F,
  866. smp16Bit = 0x04,
  867. smpUnsigned = 0x08,
  868. smpDelta = 0x10,
  869. smpPingPong = 0x20,
  870. smpLoop = 0x80,
  871. };
  872. char filename[13]; // null-terminated
  873. char name[24]; // ditto
  874. uint32le offset; // in file
  875. uint32le memoffset; // not used
  876. uint16le sampleNumber; // 1 ... 255
  877. uint8le flags; // sample flag bitfield
  878. uint32le length; // in bytes
  879. uint32le loopStart; // in samples?
  880. uint32le loopEnd; // in samples?
  881. uint8le finetune; // Low nibble = MOD finetune, high nibble = transpose (7 = center)
  882. uint8le volume; // default volume
  883. uint16le c2freq; // Middle-C frequency, which has to be combined with the finetune and transpose.
  884. // Convert sample header to OpenMPT's internal format
  885. void ConvertToMPT(ModSample &mptSmp) const
  886. {
  887. mptSmp.Initialize();
  888. mptSmp.filename = mpt::String::ReadBuf(mpt::String::nullTerminated, filename);
  889. mptSmp.nLength = length;
  890. mptSmp.nLoopStart = loopStart;
  891. mptSmp.nLoopEnd = loopEnd;
  892. // It seems like that finetune and transpose are added to the already given c2freq... That's a double WTF!
  893. // Why on earth would you want to use both systems at the same time?
  894. mptSmp.nC5Speed = c2freq;
  895. mptSmp.Transpose(((finetune ^ 0x08) - 0x78) / (12.0 * 16.0));
  896. mptSmp.nVolume = std::min(volume.get(), uint8(64)) * 4u;
  897. mptSmp.uFlags.reset();
  898. if(flags & PSM16SampleHeader::smp16Bit)
  899. {
  900. mptSmp.uFlags.set(CHN_16BIT);
  901. mptSmp.nLength /= 2u;
  902. }
  903. if(flags & PSM16SampleHeader::smpPingPong)
  904. {
  905. mptSmp.uFlags.set(CHN_PINGPONGLOOP);
  906. }
  907. if(flags & PSM16SampleHeader::smpLoop)
  908. {
  909. mptSmp.uFlags.set(CHN_LOOP);
  910. }
  911. }
  912. // Retrieve the internal sample format flags for this sample.
  913. SampleIO GetSampleFormat() const
  914. {
  915. SampleIO sampleIO(
  916. (flags & PSM16SampleHeader::smp16Bit) ? SampleIO::_16bit : SampleIO::_8bit,
  917. SampleIO::mono,
  918. SampleIO::littleEndian,
  919. SampleIO::signedPCM);
  920. if(flags & PSM16SampleHeader::smpUnsigned)
  921. {
  922. sampleIO |= SampleIO::unsignedPCM;
  923. } else if((flags & PSM16SampleHeader::smpDelta) || (flags & PSM16SampleHeader::smpMask) == 0)
  924. {
  925. sampleIO |= SampleIO::deltaPCM;
  926. }
  927. return sampleIO;
  928. }
  929. };
  930. MPT_BINARY_STRUCT(PSM16SampleHeader, 64)
  931. struct PSM16PatternHeader
  932. {
  933. uint16le size; // includes header bytes
  934. uint8le numRows; // 1 ... 64
  935. uint8le numChans; // 1 ... 32
  936. };
  937. MPT_BINARY_STRUCT(PSM16PatternHeader, 4)
  938. static bool ValidateHeader(const PSM16FileHeader &fileHeader)
  939. {
  940. if(std::memcmp(fileHeader.formatID, "PSM\xFE", 4)
  941. || fileHeader.lineEnd != 0x1A
  942. || (fileHeader.formatVersion != 0x10 && fileHeader.formatVersion != 0x01) // why is this sometimes 0x01?
  943. || fileHeader.patternVersion != 0 // 255ch pattern version not supported (did anyone use this?)
  944. || (fileHeader.songType & 3) != 0
  945. || fileHeader.numChannelsPlay > MAX_BASECHANNELS
  946. || fileHeader.numChannelsReal > MAX_BASECHANNELS
  947. || std::max(fileHeader.numChannelsPlay, fileHeader.numChannelsReal) == 0)
  948. {
  949. return false;
  950. }
  951. return true;
  952. }
  953. CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderPSM16(MemoryFileReader file, const uint64 *pfilesize)
  954. {
  955. PSM16FileHeader fileHeader;
  956. if(!file.ReadStruct(fileHeader))
  957. {
  958. return ProbeWantMoreData;
  959. }
  960. if(!ValidateHeader(fileHeader))
  961. {
  962. return ProbeFailure;
  963. }
  964. MPT_UNREFERENCED_PARAMETER(pfilesize);
  965. return ProbeSuccess;
  966. }
  967. bool CSoundFile::ReadPSM16(FileReader &file, ModLoadingFlags loadFlags)
  968. {
  969. file.Rewind();
  970. // Is it a valid PSM16 file?
  971. PSM16FileHeader fileHeader;
  972. if(!file.ReadStruct(fileHeader))
  973. {
  974. return false;
  975. }
  976. if(!ValidateHeader(fileHeader))
  977. {
  978. return false;
  979. }
  980. if(loadFlags == onlyVerifyHeader)
  981. {
  982. return true;
  983. }
  984. // Seems to be valid!
  985. InitializeGlobals(MOD_TYPE_PSM);
  986. m_modFormat.formatName = U_("Epic MegaGames MASI (Old Version)");
  987. m_modFormat.type = U_("psm");
  988. m_modFormat.charset = mpt::Charset::CP437;
  989. m_nChannels = Clamp(CHANNELINDEX(fileHeader.numChannelsPlay), CHANNELINDEX(fileHeader.numChannelsReal), MAX_BASECHANNELS);
  990. m_nSamplePreAmp = fileHeader.masterVolume;
  991. if(m_nSamplePreAmp == 255)
  992. {
  993. // Most of the time, the master volume value makes sense... Just not when it's 255.
  994. m_nSamplePreAmp = 48;
  995. }
  996. m_nDefaultSpeed = fileHeader.songSpeed;
  997. m_nDefaultTempo.Set(fileHeader.songTempo);
  998. m_songName = mpt::String::ReadBuf(mpt::String::spacePadded, fileHeader.songName);
  999. // Read orders
  1000. if(fileHeader.orderOffset > 4 && file.Seek(fileHeader.orderOffset - 4) && file.ReadMagic("PORD"))
  1001. {
  1002. ReadOrderFromFile<uint8>(Order(), file, fileHeader.songOrders);
  1003. }
  1004. // Read pan positions
  1005. if(fileHeader.panOffset > 4 && file.Seek(fileHeader.panOffset - 4) && file.ReadMagic("PPAN"))
  1006. {
  1007. for(CHANNELINDEX i = 0; i < 32; i++)
  1008. {
  1009. ChnSettings[i].Reset();
  1010. ChnSettings[i].nPan = ((15 - (file.ReadUint8() & 0x0F)) * 256 + 8) / 15; // 15 seems to be left and 0 seems to be right...
  1011. // ChnSettings[i].dwFlags = (i >= fileHeader.numChannelsPlay) ? CHN_MUTE : 0; // don't mute channels, as muted channels are completely ignored in S3M
  1012. }
  1013. }
  1014. // Read samples
  1015. if(fileHeader.smpOffset > 4 && file.Seek(fileHeader.smpOffset - 4) && file.ReadMagic("PSAH"))
  1016. {
  1017. FileReader sampleChunk = file.ReadChunk(fileHeader.numSamples * sizeof(PSM16SampleHeader));
  1018. for(SAMPLEINDEX fileSample = 0; fileSample < fileHeader.numSamples; fileSample++)
  1019. {
  1020. PSM16SampleHeader sampleHeader;
  1021. if(!sampleChunk.ReadStruct(sampleHeader))
  1022. {
  1023. break;
  1024. }
  1025. const SAMPLEINDEX smp = sampleHeader.sampleNumber;
  1026. if(smp > 0 && smp < MAX_SAMPLES && !Samples[smp].HasSampleData())
  1027. {
  1028. m_nSamples = std::max(m_nSamples, smp);
  1029. sampleHeader.ConvertToMPT(Samples[smp]);
  1030. m_szNames[smp] = mpt::String::ReadBuf(mpt::String::nullTerminated, sampleHeader.name);
  1031. if(loadFlags & loadSampleData)
  1032. {
  1033. file.Seek(sampleHeader.offset);
  1034. sampleHeader.GetSampleFormat().ReadSample(Samples[smp], file);
  1035. }
  1036. }
  1037. }
  1038. }
  1039. // Read patterns
  1040. if(!(loadFlags & loadPatternData))
  1041. {
  1042. return true;
  1043. }
  1044. if(fileHeader.patOffset > 4 && file.Seek(fileHeader.patOffset - 4) && file.ReadMagic("PPAT"))
  1045. {
  1046. Patterns.ResizeArray(fileHeader.numPatterns);
  1047. for(PATTERNINDEX pat = 0; pat < fileHeader.numPatterns; pat++)
  1048. {
  1049. PSM16PatternHeader patternHeader;
  1050. if(!file.ReadStruct(patternHeader))
  1051. {
  1052. break;
  1053. }
  1054. if(patternHeader.size < sizeof(PSM16PatternHeader))
  1055. {
  1056. continue;
  1057. }
  1058. // Patterns are padded to 16 Bytes
  1059. FileReader patternChunk = file.ReadChunk(((patternHeader.size + 15) & ~15) - sizeof(PSM16PatternHeader));
  1060. if(!Patterns.Insert(pat, patternHeader.numRows))
  1061. {
  1062. continue;
  1063. }
  1064. enum
  1065. {
  1066. channelMask = 0x1F,
  1067. noteFlag = 0x80,
  1068. volFlag = 0x40,
  1069. effectFlag = 0x20,
  1070. };
  1071. ROWINDEX curRow = 0;
  1072. while(patternChunk.CanRead(1) && curRow < patternHeader.numRows)
  1073. {
  1074. uint8 chnFlag = patternChunk.ReadUint8();
  1075. if(chnFlag == 0)
  1076. {
  1077. curRow++;
  1078. continue;
  1079. }
  1080. ModCommand &m = *Patterns[pat].GetpModCommand(curRow, std::min(static_cast<CHANNELINDEX>(chnFlag & channelMask), static_cast<CHANNELINDEX>(m_nChannels - 1)));
  1081. if(chnFlag & noteFlag)
  1082. {
  1083. // note + instr present
  1084. const auto [note, instr] = patternChunk.ReadArray<uint8, 2>();
  1085. m.note = note + 36;
  1086. m.instr = instr;
  1087. }
  1088. if(chnFlag & volFlag)
  1089. {
  1090. // volume present
  1091. m.volcmd = VOLCMD_VOLUME;
  1092. m.vol = std::min(patternChunk.ReadUint8(), uint8(64));
  1093. }
  1094. if(chnFlag & effectFlag)
  1095. {
  1096. // effect present - convert
  1097. const auto [command, param] = patternChunk.ReadArray<uint8, 2>();
  1098. m.param = param;
  1099. switch(command)
  1100. {
  1101. // Volslides
  1102. case 0x01: // fine volslide up
  1103. m.command = CMD_VOLUMESLIDE;
  1104. m.param = (m.param << 4) | 0x0F;
  1105. break;
  1106. case 0x02: // volslide up
  1107. m.command = CMD_VOLUMESLIDE;
  1108. m.param = (m.param << 4) & 0xF0;
  1109. break;
  1110. case 0x03: // fine voslide down
  1111. m.command = CMD_VOLUMESLIDE;
  1112. m.param = 0xF0 | m.param;
  1113. break;
  1114. case 0x04: // volslide down
  1115. m.command = CMD_VOLUMESLIDE;
  1116. m.param = m.param & 0x0F;
  1117. break;
  1118. // Portamento
  1119. case 0x0A: // fine portamento up
  1120. m.command = CMD_PORTAMENTOUP;
  1121. m.param |= 0xF0;
  1122. break;
  1123. case 0x0B: // portamento down
  1124. m.command = CMD_PORTAMENTOUP;
  1125. break;
  1126. case 0x0C: // fine portamento down
  1127. m.command = CMD_PORTAMENTODOWN;
  1128. m.param |= 0xF0;
  1129. break;
  1130. case 0x0D: // portamento down
  1131. m.command = CMD_PORTAMENTODOWN;
  1132. break;
  1133. case 0x0E: // tone portamento
  1134. m.command = CMD_TONEPORTAMENTO;
  1135. break;
  1136. case 0x0F: // glissando control
  1137. m.command = CMD_S3MCMDEX;
  1138. m.param |= 0x10;
  1139. break;
  1140. case 0x10: // tone portamento + volslide up
  1141. m.command = CMD_TONEPORTAVOL;
  1142. m.param <<= 4;
  1143. break;
  1144. case 0x11: // tone portamento + volslide down
  1145. m.command = CMD_TONEPORTAVOL;
  1146. m.param &= 0x0F;
  1147. break;
  1148. // Vibrato
  1149. case 0x14: // vibrato
  1150. m.command = CMD_VIBRATO;
  1151. break;
  1152. case 0x15: // vibrato waveform
  1153. m.command = CMD_S3MCMDEX;
  1154. m.param |= 0x30;
  1155. break;
  1156. case 0x16: // vibrato + volslide up
  1157. m.command = CMD_VIBRATOVOL;
  1158. m.param <<= 4;
  1159. break;
  1160. case 0x17: // vibrato + volslide down
  1161. m.command = CMD_VIBRATOVOL;
  1162. m.param &= 0x0F;
  1163. break;
  1164. // Tremolo
  1165. case 0x1E: // tremolo
  1166. m.command = CMD_TREMOLO;
  1167. break;
  1168. case 0x1F: // tremolo waveform
  1169. m.command = CMD_S3MCMDEX;
  1170. m.param |= 0x40;
  1171. break;
  1172. // Sample commands
  1173. case 0x28: // 3-byte offset - we only support the middle byte.
  1174. m.command = CMD_OFFSET;
  1175. m.param = patternChunk.ReadUint8();
  1176. patternChunk.Skip(1);
  1177. break;
  1178. case 0x29: // retrigger
  1179. m.command = CMD_RETRIG;
  1180. m.param &= 0x0F;
  1181. break;
  1182. case 0x2A: // note cut
  1183. m.command = CMD_S3MCMDEX;
  1184. #ifdef MODPLUG_TRACKER
  1185. if(m.param == 0) // in S3M mode, SC0 is ignored, so we convert it to a note cut.
  1186. {
  1187. if(m.note == NOTE_NONE)
  1188. {
  1189. m.note = NOTE_NOTECUT;
  1190. m.command = CMD_NONE;
  1191. } else
  1192. {
  1193. m.param = 1;
  1194. }
  1195. }
  1196. #endif // MODPLUG_TRACKER
  1197. m.param |= 0xC0;
  1198. break;
  1199. case 0x2B: // note delay
  1200. m.command = CMD_S3MCMDEX;
  1201. m.param |= 0xD0;
  1202. break;
  1203. // Position change
  1204. case 0x32: // position jump
  1205. m.command = CMD_POSITIONJUMP;
  1206. break;
  1207. case 0x33: // pattern break
  1208. m.command = CMD_PATTERNBREAK;
  1209. break;
  1210. case 0x34: // loop pattern
  1211. m.command = CMD_S3MCMDEX;
  1212. m.param |= 0xB0;
  1213. break;
  1214. case 0x35: // pattern delay
  1215. m.command = CMD_S3MCMDEX;
  1216. m.param |= 0xE0;
  1217. break;
  1218. // speed change
  1219. case 0x3C: // set speed
  1220. m.command = CMD_SPEED;
  1221. break;
  1222. case 0x3D: // set tempo
  1223. m.command = CMD_TEMPO;
  1224. break;
  1225. // misc commands
  1226. case 0x46: // arpeggio
  1227. m.command = CMD_ARPEGGIO;
  1228. break;
  1229. case 0x47: // set finetune
  1230. m.command = CMD_S3MCMDEX;
  1231. m.param = 0x20 | (m.param & 0x0F);
  1232. break;
  1233. case 0x48: // set balance (panning?)
  1234. m.command = CMD_S3MCMDEX;
  1235. m.param = 0x80 | (m.param & 0x0F);
  1236. break;
  1237. default:
  1238. m.command = CMD_NONE;
  1239. break;
  1240. }
  1241. }
  1242. }
  1243. // Pattern break for short patterns (so saving the modules as S3M won't break it)
  1244. if(patternHeader.numRows != 64)
  1245. {
  1246. Patterns[pat].WriteEffect(EffectWriter(CMD_PATTERNBREAK, 0).Row(patternHeader.numRows - 1).RetryNextRow());
  1247. }
  1248. }
  1249. }
  1250. if(fileHeader.commentsOffset != 0)
  1251. {
  1252. file.Seek(fileHeader.commentsOffset);
  1253. m_songMessage.Read(file, file.ReadUint16LE(), SongMessage::leAutodetect);
  1254. }
  1255. return true;
  1256. }
  1257. OPENMPT_NAMESPACE_END