Load_stm.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618
  1. /*
  2. * Load_stm.cpp
  3. * ------------
  4. * Purpose: STM (Scream Tracker 2) and STX (Scream Tracker Music Interface Kit - a mixture of STM and S3M) module loaders
  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. #include "S3MTools.h"
  13. OPENMPT_NAMESPACE_BEGIN
  14. // STM sample header struct
  15. struct STMSampleHeader
  16. {
  17. char filename[12]; // Can't have long comments - just filename comments :)
  18. uint8le zero;
  19. uint8le disk; // A blast from the past
  20. uint16le offset; // 20-bit offset in file (lower 4 bits are zero)
  21. uint16le length; // Sample length
  22. uint16le loopStart; // Loop start point
  23. uint16le loopEnd; // Loop end point
  24. uint8le volume; // Volume
  25. uint8le reserved2;
  26. uint16le sampleRate;
  27. uint8le reserved3[6];
  28. // Convert an STM sample header to OpenMPT's internal sample header.
  29. void ConvertToMPT(ModSample &mptSmp) const
  30. {
  31. mptSmp.Initialize();
  32. mptSmp.filename = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, filename);
  33. mptSmp.nC5Speed = sampleRate;
  34. mptSmp.nVolume = std::min(volume.get(), uint8(64)) * 4;
  35. mptSmp.nLength = length;
  36. mptSmp.nLoopStart = loopStart;
  37. mptSmp.nLoopEnd = loopEnd;
  38. if(mptSmp.nLength < 2) mptSmp.nLength = 0;
  39. if(mptSmp.nLoopStart < mptSmp.nLength
  40. && mptSmp.nLoopEnd > mptSmp.nLoopStart
  41. && mptSmp.nLoopEnd != 0xFFFF)
  42. {
  43. mptSmp.uFlags = CHN_LOOP;
  44. mptSmp.nLoopEnd = std::min(mptSmp.nLoopEnd, mptSmp.nLength);
  45. }
  46. }
  47. };
  48. MPT_BINARY_STRUCT(STMSampleHeader, 32)
  49. // STM file header
  50. struct STMFileHeader
  51. {
  52. char songname[20];
  53. char trackerName[8]; // !Scream! for ST 2.xx
  54. uint8 dosEof; // 0x1A
  55. uint8 filetype; // 1=song, 2=module (only 2 is supported, of course) :)
  56. uint8 verMajor;
  57. uint8 verMinor;
  58. uint8 initTempo;
  59. uint8 numPatterns;
  60. uint8 globalVolume;
  61. uint8 reserved[13];
  62. bool Validate() const
  63. {
  64. if(filetype != 2
  65. || (dosEof != 0x1A && dosEof != 2) // ST2 ignores this, ST3 doesn't. Broken versions of putup10.stm / putup11.stm have dosEof = 2.
  66. || verMajor != 2
  67. || (verMinor != 0 && verMinor != 10 && verMinor != 20 && verMinor != 21)
  68. || numPatterns > 64
  69. || (globalVolume > 64 && globalVolume != 0x58)) // 0x58 may be a placeholder value in earlier ST2 versions.
  70. {
  71. return false;
  72. }
  73. return ValidateTrackerName(trackerName);
  74. }
  75. static bool ValidateTrackerName(const char (&trackerName)[8])
  76. {
  77. // Tracker string can be anything really (ST2 and ST3 won't check it),
  78. // but we do not want to generate too many false positives here, as
  79. // STM already has very few magic bytes anyway.
  80. // Magic bytes that have been found in the wild are !Scream!, BMOD2STM, WUZAMOD! and SWavePro.
  81. for(uint8 c : trackerName)
  82. {
  83. if(c < 0x20 || c >= 0x7F)
  84. return false;
  85. }
  86. return true;
  87. }
  88. uint64 GetHeaderMinimumAdditionalSize() const
  89. {
  90. return 31 * sizeof(STMSampleHeader) + (verMinor == 0 ? 64 : 128) + numPatterns * 64 * 4;
  91. }
  92. };
  93. MPT_BINARY_STRUCT(STMFileHeader, 48)
  94. static bool ValidateSTMOrderList(ModSequence &order)
  95. {
  96. for(auto &pat : order)
  97. {
  98. if(pat == 99 || pat == 255) // 99 is regular, sometimes a single 255 entry can be found too
  99. pat = order.GetInvalidPatIndex();
  100. else if(pat > 63)
  101. return false;
  102. }
  103. return true;
  104. }
  105. static void ConvertSTMCommand(ModCommand &m, const ROWINDEX row, const uint8 fileVerMinor, uint8 &newTempo, ORDERINDEX &breakPos, ROWINDEX &breakRow)
  106. {
  107. static constexpr EffectCommand stmEffects[] =
  108. {
  109. CMD_NONE, CMD_SPEED, CMD_POSITIONJUMP, CMD_PATTERNBREAK, // .ABC
  110. CMD_VOLUMESLIDE, CMD_PORTAMENTODOWN, CMD_PORTAMENTOUP, CMD_TONEPORTAMENTO, // DEFG
  111. CMD_VIBRATO, CMD_TREMOR, CMD_ARPEGGIO, CMD_NONE, // HIJK
  112. CMD_NONE, CMD_NONE, CMD_NONE, CMD_NONE, // LMNO
  113. // KLMNO can be entered in the editor but don't do anything
  114. };
  115. m.command = stmEffects[m.command & 0x0F];
  116. switch(m.command)
  117. {
  118. case CMD_VOLUMESLIDE:
  119. // Lower nibble always has precedence, and there are no fine slides.
  120. if(m.param & 0x0F)
  121. m.param &= 0x0F;
  122. else
  123. m.param &= 0xF0;
  124. break;
  125. case CMD_PATTERNBREAK:
  126. m.param = (m.param & 0xF0) * 10 + (m.param & 0x0F);
  127. if(breakPos != ORDERINDEX_INVALID && m.param == 0)
  128. {
  129. // Merge Bxx + C00 into just Bxx
  130. m.command = CMD_POSITIONJUMP;
  131. m.param = static_cast<ModCommand::PARAM>(breakPos);
  132. breakPos = ORDERINDEX_INVALID;
  133. }
  134. LimitMax(breakRow, row);
  135. break;
  136. case CMD_POSITIONJUMP:
  137. // This effect is also very weird.
  138. // Bxx doesn't appear to cause an immediate break -- it merely
  139. // sets the next order for when the pattern ends (either by
  140. // playing it all the way through, or via Cxx effect)
  141. breakPos = m.param;
  142. breakRow = 63;
  143. m.command = CMD_NONE;
  144. break;
  145. case CMD_TREMOR:
  146. // this actually does something with zero values, and has no
  147. // effect memory. which makes SENSE for old-effects tremor,
  148. // but ST3 went and screwed it all up by adding an effect
  149. // memory and IT followed that, and those are much more popular
  150. // than STM so we kind of have to live with this effect being
  151. // broken... oh well. not a big loss.
  152. break;
  153. case CMD_SPEED:
  154. if(fileVerMinor < 21)
  155. m.param = ((m.param / 10u) << 4u) + m.param % 10u;
  156. if(!m.param)
  157. {
  158. m.command = CMD_NONE;
  159. break;
  160. }
  161. #ifdef MODPLUG_TRACKER
  162. // ST2 has a very weird tempo mode where the length of a tick depends both
  163. // on the ticks per row and a scaling factor. Try to write the tempo into a separate command.
  164. newTempo = m.param;
  165. m.param >>= 4;
  166. #else
  167. MPT_UNUSED_VARIABLE(newTempo);
  168. #endif // MODPLUG_TRACKER
  169. break;
  170. default:
  171. // Anything not listed above is a no-op if there's no value, as ST2 doesn't have effect memory.
  172. if(!m.param)
  173. m.command = CMD_NONE;
  174. break;
  175. }
  176. }
  177. CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderSTM(MemoryFileReader file, const uint64 *pfilesize)
  178. {
  179. STMFileHeader fileHeader;
  180. if(!file.ReadStruct(fileHeader))
  181. return ProbeWantMoreData;
  182. if(!fileHeader.Validate())
  183. return ProbeFailure;
  184. return ProbeAdditionalSize(file, pfilesize, fileHeader.GetHeaderMinimumAdditionalSize());
  185. }
  186. bool CSoundFile::ReadSTM(FileReader &file, ModLoadingFlags loadFlags)
  187. {
  188. file.Rewind();
  189. STMFileHeader fileHeader;
  190. if(!file.ReadStruct(fileHeader))
  191. return false;
  192. if(!fileHeader.Validate())
  193. return false;
  194. if(!file.CanRead(mpt::saturate_cast<FileReader::off_t>(fileHeader.GetHeaderMinimumAdditionalSize())))
  195. return false;
  196. if(loadFlags == onlyVerifyHeader)
  197. return true;
  198. InitializeGlobals(MOD_TYPE_STM);
  199. m_songName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, fileHeader.songname);
  200. m_modFormat.formatName = U_("Scream Tracker 2");
  201. m_modFormat.type = U_("stm");
  202. m_modFormat.madeWithTracker = MPT_UFORMAT("Scream Tracker {}.{}")(fileHeader.verMajor, mpt::ufmt::dec0<2>(fileHeader.verMinor));
  203. m_modFormat.charset = mpt::Charset::CP437;
  204. m_playBehaviour.set(kST3SampleSwap);
  205. m_nSamples = 31;
  206. m_nChannels = 4;
  207. m_nMinPeriod = 64;
  208. m_nMaxPeriod = 0x7FFF;
  209. m_playBehaviour.set(kST3SampleSwap);
  210. uint8 initTempo = fileHeader.initTempo;
  211. if(fileHeader.verMinor < 21)
  212. initTempo = ((initTempo / 10u) << 4u) + initTempo % 10u;
  213. if(initTempo == 0)
  214. initTempo = 0x60;
  215. m_nDefaultTempo = ConvertST2Tempo(initTempo);
  216. m_nDefaultSpeed = initTempo >> 4;
  217. if(fileHeader.verMinor > 10)
  218. m_nDefaultGlobalVolume = std::min(fileHeader.globalVolume, uint8(64)) * 4u;
  219. // Setting up channels
  220. for(CHANNELINDEX chn = 0; chn < 4; chn++)
  221. {
  222. ChnSettings[chn].Reset();
  223. ChnSettings[chn].nPan = (chn & 1) ? 0x40 : 0xC0;
  224. }
  225. // Read samples
  226. uint16 sampleOffsets[31];
  227. for(SAMPLEINDEX smp = 1; smp <= 31; smp++)
  228. {
  229. STMSampleHeader sampleHeader;
  230. file.ReadStruct(sampleHeader);
  231. if(sampleHeader.zero != 0 && sampleHeader.zero != 46) // putup10.stm has zero = 46
  232. return false;
  233. sampleHeader.ConvertToMPT(Samples[smp]);
  234. m_szNames[smp] = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, sampleHeader.filename);
  235. sampleOffsets[smp - 1] = sampleHeader.offset;
  236. }
  237. // Read order list
  238. ReadOrderFromFile<uint8>(Order(), file, fileHeader.verMinor == 0 ? 64 : 128);
  239. if(!ValidateSTMOrderList(Order()))
  240. return false;
  241. if(loadFlags & loadPatternData)
  242. Patterns.ResizeArray(fileHeader.numPatterns);
  243. for(PATTERNINDEX pat = 0; pat < fileHeader.numPatterns; pat++)
  244. {
  245. if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, 64))
  246. {
  247. for(int i = 0; i < 64 * 4; i++)
  248. {
  249. uint8 note = file.ReadUint8();
  250. if(note < 0xFB || note > 0xFD)
  251. file.Skip(3);
  252. }
  253. continue;
  254. }
  255. auto m = Patterns[pat].begin();
  256. ORDERINDEX breakPos = ORDERINDEX_INVALID;
  257. ROWINDEX breakRow = 63; // Candidate row for inserting pattern break
  258. for(ROWINDEX row = 0; row < 64; row++)
  259. {
  260. uint8 newTempo = 0;
  261. for(CHANNELINDEX chn = 0; chn < 4; chn++, m++)
  262. {
  263. uint8 note = file.ReadUint8(), insVol, volCmd, cmdInf;
  264. switch(note)
  265. {
  266. case 0xFB:
  267. note = insVol = volCmd = cmdInf = 0x00;
  268. break;
  269. case 0xFC:
  270. continue;
  271. case 0xFD:
  272. m->note = NOTE_NOTECUT;
  273. continue;
  274. default:
  275. {
  276. const auto patData = file.ReadArray<uint8, 3>();
  277. insVol = patData[0];
  278. volCmd = patData[1];
  279. cmdInf = patData[2];
  280. }
  281. break;
  282. }
  283. if(note == 0xFE)
  284. m->note = NOTE_NOTECUT;
  285. else if(note < 0x60)
  286. m->note = (note >> 4) * 12 + (note & 0x0F) + 36 + NOTE_MIN;
  287. m->instr = insVol >> 3;
  288. if(m->instr > 31)
  289. {
  290. m->instr = 0;
  291. }
  292. uint8 vol = (insVol & 0x07) | ((volCmd & 0xF0) >> 1);
  293. if(vol <= 64)
  294. {
  295. m->volcmd = VOLCMD_VOLUME;
  296. m->vol = vol;
  297. }
  298. m->command = volCmd & 0x0F;
  299. m->param = cmdInf;
  300. ConvertSTMCommand(*m, row, fileHeader.verMinor, newTempo, breakPos, breakRow);
  301. }
  302. if(newTempo != 0)
  303. {
  304. Patterns[pat].WriteEffect(EffectWriter(CMD_TEMPO, mpt::saturate_round<ModCommand::PARAM>(ConvertST2Tempo(newTempo).ToDouble())).Row(row).RetryPreviousRow());
  305. }
  306. }
  307. if(breakPos != ORDERINDEX_INVALID)
  308. {
  309. Patterns[pat].WriteEffect(EffectWriter(CMD_POSITIONJUMP, static_cast<ModCommand::PARAM>(breakPos)).Row(breakRow).RetryPreviousRow());
  310. }
  311. }
  312. // Reading Samples
  313. if(loadFlags & loadSampleData)
  314. {
  315. const SampleIO sampleIO(
  316. SampleIO::_8bit,
  317. SampleIO::mono,
  318. SampleIO::littleEndian,
  319. SampleIO::signedPCM);
  320. for(SAMPLEINDEX smp = 1; smp <= 31; smp++)
  321. {
  322. ModSample &sample = Samples[smp];
  323. // ST2 just plays random noise for samples with a default volume of 0
  324. if(sample.nLength && sample.nVolume > 0)
  325. {
  326. FileReader::off_t sampleOffset = sampleOffsets[smp - 1] << 4;
  327. // acidlamb.stm has some bogus samples with sample offsets past EOF
  328. if(sampleOffset > sizeof(STMFileHeader) && file.Seek(sampleOffset))
  329. {
  330. sampleIO.ReadSample(sample, file);
  331. }
  332. }
  333. }
  334. }
  335. return true;
  336. }
  337. // STX file header
  338. struct STXFileHeader
  339. {
  340. char songName[20];
  341. char trackerName[8]; // Typically !Scream! but mustn't be relied upon, like for STM
  342. uint16le patternSize; // or EOF in newer file version (except for future brain.stx?!)
  343. uint16le unknown1;
  344. uint16le patTableOffset;
  345. uint16le smpTableOffset;
  346. uint16le chnTableOffset;
  347. uint32le unknown2;
  348. uint8 globalVolume;
  349. uint8 initTempo;
  350. uint32le unknown3;
  351. uint16le numPatterns;
  352. uint16le numSamples;
  353. uint16le numOrders;
  354. char unknown4[6];
  355. char magic[4];
  356. bool Validate() const
  357. {
  358. if(std::memcmp(magic, "SCRM", 4)
  359. || (patternSize < 64 && patternSize != 0x1A)
  360. || patternSize > 0x840
  361. || (globalVolume > 64 && globalVolume != 0x58) // 0x58 may be a placeholder value in earlier ST2 versions.
  362. || numPatterns > 64
  363. || numSamples > 96 // Some STX files have more sample slots than their STM counterpart for mysterious reasons
  364. || (numOrders > 0x81 && numOrders != 0x101)
  365. || unknown1 != 0 || unknown2 != 0 || unknown3 != 1)
  366. {
  367. return false;
  368. }
  369. return STMFileHeader::ValidateTrackerName(trackerName);
  370. }
  371. uint64 GetHeaderMinimumAdditionalSize() const
  372. {
  373. return std::max({(patTableOffset << 4) + numPatterns * 2, (smpTableOffset << 4) + numSamples * 2, (chnTableOffset << 4) + 32 + numOrders * 5 });
  374. }
  375. };
  376. MPT_BINARY_STRUCT(STXFileHeader, 64)
  377. CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderSTX(MemoryFileReader file, const uint64 *pfilesize)
  378. {
  379. STXFileHeader fileHeader;
  380. if(!file.ReadStruct(fileHeader))
  381. return ProbeWantMoreData;
  382. if(!fileHeader.Validate())
  383. return ProbeFailure;
  384. return ProbeAdditionalSize(file, pfilesize, fileHeader.GetHeaderMinimumAdditionalSize());
  385. }
  386. bool CSoundFile::ReadSTX(FileReader &file, ModLoadingFlags loadFlags)
  387. {
  388. file.Rewind();
  389. STXFileHeader fileHeader;
  390. if(!file.ReadStruct(fileHeader))
  391. return false;
  392. if(!fileHeader.Validate())
  393. return false;
  394. if (!file.CanRead(mpt::saturate_cast<FileReader::off_t>(fileHeader.GetHeaderMinimumAdditionalSize())))
  395. return false;
  396. if(loadFlags == onlyVerifyHeader)
  397. return true;
  398. InitializeGlobals(MOD_TYPE_STM);
  399. m_songName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, fileHeader.songName);
  400. m_nSamples = fileHeader.numSamples;
  401. m_nChannels = 4;
  402. m_nMinPeriod = 64;
  403. m_nMaxPeriod = 0x7FFF;
  404. m_playBehaviour.set(kST3SampleSwap);
  405. uint8 initTempo = fileHeader.initTempo;
  406. if(initTempo == 0)
  407. initTempo = 0x60;
  408. m_nDefaultTempo = ConvertST2Tempo(initTempo);
  409. m_nDefaultSpeed = initTempo >> 4;
  410. m_nDefaultGlobalVolume = std::min(fileHeader.globalVolume, uint8(64)) * 4u;
  411. // Setting up channels
  412. for(CHANNELINDEX chn = 0; chn < 4; chn++)
  413. {
  414. ChnSettings[chn].Reset();
  415. ChnSettings[chn].nPan = (chn & 1) ? 0x40 : 0xC0;
  416. }
  417. std::vector<uint16le> patternOffsets, sampleOffsets;
  418. file.Seek(fileHeader.patTableOffset << 4);
  419. file.ReadVector(patternOffsets, fileHeader.numPatterns);
  420. file.Seek(fileHeader.smpTableOffset << 4);
  421. file.ReadVector(sampleOffsets, fileHeader.numSamples);
  422. // Read order list
  423. file.Seek((fileHeader.chnTableOffset << 4) + 32);
  424. Order().resize(fileHeader.numOrders);
  425. for(auto &pat : Order())
  426. {
  427. pat = file.ReadUint8();
  428. file.Skip(4);
  429. }
  430. if(!ValidateSTMOrderList(Order()))
  431. return false;
  432. // Read samples
  433. for(SAMPLEINDEX smp = 1; smp <= fileHeader.numSamples; smp++)
  434. {
  435. if(!file.Seek(sampleOffsets[smp - 1] << 4))
  436. return false;
  437. S3MSampleHeader sampleHeader;
  438. file.ReadStruct(sampleHeader);
  439. sampleHeader.ConvertToMPT(Samples[smp]);
  440. m_szNames[smp] = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, sampleHeader.filename);
  441. const uint32 sampleOffset = sampleHeader.GetSampleOffset();
  442. if((loadFlags & loadSampleData) && sampleHeader.length != 0 && file.Seek(sampleOffset))
  443. {
  444. sampleHeader.GetSampleFormat(true).ReadSample(Samples[smp], file);
  445. }
  446. }
  447. // Read patterns
  448. uint8 formatVersion = 1;
  449. if(!patternOffsets.empty() && fileHeader.patternSize != 0x1A)
  450. {
  451. if(!file.Seek(patternOffsets.front() << 4))
  452. return false;
  453. // First two bytes describe pattern size, like in S3M
  454. if(file.ReadUint16LE() == fileHeader.patternSize)
  455. formatVersion = 0;
  456. }
  457. if(loadFlags & loadPatternData)
  458. Patterns.ResizeArray(fileHeader.numPatterns);
  459. for(PATTERNINDEX pat = 0; pat < fileHeader.numPatterns; pat++)
  460. {
  461. if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, 64))
  462. break;
  463. if(!file.Seek(patternOffsets[pat] << 4))
  464. return false;
  465. if(formatVersion == 0 && file.ReadUint16LE() > 0x840)
  466. return false;
  467. ORDERINDEX breakPos = ORDERINDEX_INVALID;
  468. ROWINDEX breakRow = 63; // Candidate row for inserting pattern break
  469. auto rowBase = Patterns[pat].GetRow(0);
  470. ROWINDEX row = 0;
  471. uint8 newTempo = 0;
  472. while(row < 64)
  473. {
  474. uint8 info = file.ReadUint8();
  475. if(info == s3mEndOfRow)
  476. {
  477. // End of row
  478. if(newTempo != 0)
  479. {
  480. Patterns[pat].WriteEffect(EffectWriter(CMD_TEMPO, mpt::saturate_round<ModCommand::PARAM>(ConvertST2Tempo(newTempo).ToDouble())).Row(row).RetryPreviousRow());
  481. newTempo = 0;
  482. }
  483. if(++row < 64)
  484. {
  485. rowBase = Patterns[pat].GetRow(row);
  486. }
  487. continue;
  488. }
  489. CHANNELINDEX channel = (info & s3mChannelMask);
  490. ModCommand dummy;
  491. ModCommand &m = (channel < GetNumChannels()) ? rowBase[channel] : dummy;
  492. if(info & s3mNotePresent)
  493. {
  494. const auto [note, instr] = file.ReadArray<uint8, 2>();
  495. if(note < 0xF0)
  496. m.note = static_cast<ModCommand::NOTE>(Clamp((note & 0x0F) + 12 * (note >> 4) + 36 + NOTE_MIN, NOTE_MIN, NOTE_MAX));
  497. else if(note == s3mNoteOff)
  498. m.note = NOTE_NOTECUT;
  499. else if(note == s3mNoteNone)
  500. m.note = NOTE_NONE;
  501. m.instr = instr;
  502. }
  503. if(info & s3mVolumePresent)
  504. {
  505. uint8 volume = file.ReadUint8();
  506. m.volcmd = VOLCMD_VOLUME;
  507. m.vol = std::min(volume, uint8(64));
  508. }
  509. if(info & s3mEffectPresent)
  510. {
  511. const auto [command, param] = file.ReadArray<uint8, 2>();
  512. m.command = command;
  513. m.param = param;
  514. ConvertSTMCommand(m, row, 0xFF, newTempo, breakPos, breakRow);
  515. }
  516. }
  517. if(breakPos != ORDERINDEX_INVALID)
  518. {
  519. Patterns[pat].WriteEffect(EffectWriter(CMD_POSITIONJUMP, static_cast<ModCommand::PARAM>(breakPos)).Row(breakRow).RetryPreviousRow());
  520. }
  521. }
  522. m_modFormat.formatName = U_("Scream Tracker Music Interface Kit");
  523. m_modFormat.type = U_("stx");
  524. m_modFormat.charset = mpt::Charset::CP437;
  525. m_modFormat.madeWithTracker = MPT_UFORMAT("STM2STX 1.{}")(formatVersion);
  526. return true;
  527. }
  528. OPENMPT_NAMESPACE_END