Load_mus_km.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. /*
  2. * Load_mus_km.cpp
  3. * ---------------
  4. * Purpose: Karl Morton Music Format module loader
  5. * Notes : This is probably not the official name of this format.
  6. * Karl Morton's engine has been used in Psycho Pinball and Micro Machines 2 and also Back To Baghdad
  7. * but the latter game only uses its sound effect format, not the music format.
  8. * So there are only two known games using this music format, and no official tools or documentation are available.
  9. * Authors: OpenMPT Devs
  10. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  11. */
  12. #include "stdafx.h"
  13. #include "Loaders.h"
  14. OPENMPT_NAMESPACE_BEGIN
  15. struct KMChunkHeader
  16. {
  17. // 32-Bit chunk identifiers
  18. enum ChunkIdentifiers
  19. {
  20. idSONG = MagicLE("SONG"),
  21. idSMPL = MagicLE("SMPL"),
  22. };
  23. uint32le id; // See ChunkIdentifiers
  24. uint32le length; // Chunk size including header
  25. size_t GetLength() const
  26. {
  27. return length <= 8 ? 0 : (length - 8);
  28. }
  29. ChunkIdentifiers GetID() const
  30. {
  31. return static_cast<ChunkIdentifiers>(id.get());
  32. }
  33. };
  34. MPT_BINARY_STRUCT(KMChunkHeader, 8)
  35. struct KMSampleHeader
  36. {
  37. char name[32];
  38. uint32le loopStart;
  39. uint32le size;
  40. };
  41. MPT_BINARY_STRUCT(KMSampleHeader, 40)
  42. struct KMSampleReference
  43. {
  44. char name[32];
  45. uint8 finetune;
  46. uint8 volume;
  47. };
  48. MPT_BINARY_STRUCT(KMSampleReference, 34)
  49. struct KMSongHeader
  50. {
  51. char name[32];
  52. KMSampleReference samples[31];
  53. uint16le unknown; // always 0
  54. uint32le numChannels;
  55. uint32le restartPos;
  56. uint32le musicSize;
  57. };
  58. MPT_BINARY_STRUCT(KMSongHeader, 32 + 31 * 34 + 14)
  59. struct KMFileHeader
  60. {
  61. KMChunkHeader chunkHeader;
  62. KMSongHeader songHeader;
  63. };
  64. MPT_BINARY_STRUCT(KMFileHeader, sizeof(KMChunkHeader) + sizeof(KMSongHeader))
  65. static uint64 GetHeaderMinimumAdditionalSize(const KMFileHeader &fileHeader)
  66. {
  67. // Require room for at least one more sample chunk header
  68. return static_cast<uint64>(fileHeader.songHeader.musicSize) + sizeof(KMChunkHeader);
  69. }
  70. // Check if string only contains printable characters and doesn't contain any garbage after the required terminating null
  71. static bool IsValidKMString(const char (&str)[32])
  72. {
  73. bool nullFound = false;
  74. for(char c : str)
  75. {
  76. if(c > 0x00 && c < 0x20)
  77. return false;
  78. else if(c == 0x00)
  79. nullFound = true;
  80. else if(nullFound)
  81. return false;
  82. }
  83. return nullFound;
  84. }
  85. static bool ValidateHeader(const KMFileHeader &fileHeader)
  86. {
  87. if(fileHeader.chunkHeader.id != KMChunkHeader::idSONG
  88. || fileHeader.chunkHeader.length < sizeof(fileHeader)
  89. || fileHeader.chunkHeader.length - sizeof(fileHeader) != fileHeader.songHeader.musicSize
  90. || fileHeader.chunkHeader.length > 0x40000 // That's enough space for 256 crammed 64-row patterns ;)
  91. || fileHeader.songHeader.unknown != 0
  92. || fileHeader.songHeader.numChannels < 1
  93. || fileHeader.songHeader.numChannels > 4 // Engine rejects anything above 32, channels 5 to 32 are simply ignored
  94. || !IsValidKMString(fileHeader.songHeader.name))
  95. {
  96. return false;
  97. }
  98. for(const auto &sample : fileHeader.songHeader.samples)
  99. {
  100. if(sample.finetune > 15 || sample.volume > 64 || !IsValidKMString(sample.name))
  101. return false;
  102. }
  103. return true;
  104. }
  105. CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderMUS_KM(MemoryFileReader file, const uint64 *pfilesize)
  106. {
  107. KMFileHeader fileHeader;
  108. if(!file.Read(fileHeader))
  109. return ProbeWantMoreData;
  110. if(!ValidateHeader(fileHeader))
  111. return ProbeFailure;
  112. return ProbeAdditionalSize(file, pfilesize, GetHeaderMinimumAdditionalSize(fileHeader));
  113. }
  114. bool CSoundFile::ReadMUS_KM(FileReader &file, ModLoadingFlags loadFlags)
  115. {
  116. {
  117. file.Rewind();
  118. KMFileHeader fileHeader;
  119. if(!file.Read(fileHeader))
  120. return false;
  121. if(!ValidateHeader(fileHeader))
  122. return false;
  123. if(!file.CanRead(mpt::saturate_cast<FileReader::off_t>(GetHeaderMinimumAdditionalSize(fileHeader))))
  124. return false;
  125. if(loadFlags == onlyVerifyHeader)
  126. return true;
  127. }
  128. file.Rewind();
  129. const auto chunks = ChunkReader(file).ReadChunks<KMChunkHeader>(1);
  130. auto songChunks = chunks.GetAllChunks(KMChunkHeader::idSONG);
  131. auto sampleChunks = chunks.GetAllChunks(KMChunkHeader::idSMPL);
  132. if(songChunks.empty() || sampleChunks.empty())
  133. return false;
  134. InitializeGlobals(MOD_TYPE_MOD);
  135. InitializeChannels();
  136. m_SongFlags = SONG_AMIGALIMITS | SONG_IMPORTED | SONG_ISAMIGA; // Yes, those were not Amiga games but the format fully conforms to Amiga limits, so allow the Amiga Resampler to be used.
  137. m_nChannels = 4;
  138. m_nSamples = 0;
  139. static constexpr uint16 MUS_SAMPLE_UNUSED = 255; // Sentinel value to check if a sample needs to be duplicated
  140. for(auto &chunk : sampleChunks)
  141. {
  142. if(!CanAddMoreSamples())
  143. break;
  144. m_nSamples++;
  145. ModSample &mptSample = Samples[m_nSamples];
  146. mptSample.Initialize(MOD_TYPE_MOD);
  147. KMSampleHeader sampleHeader;
  148. if(!chunk.Read(sampleHeader)
  149. || !IsValidKMString(sampleHeader.name))
  150. return false;
  151. m_szNames[m_nSamples] = sampleHeader.name;
  152. mptSample.nLoopEnd = mptSample.nLength = sampleHeader.size;
  153. mptSample.nLoopStart = sampleHeader.loopStart;
  154. mptSample.uFlags.set(CHN_LOOP);
  155. mptSample.nVolume = MUS_SAMPLE_UNUSED;
  156. if(!(loadFlags & loadSampleData))
  157. continue;
  158. SampleIO(SampleIO::_8bit,
  159. SampleIO::mono,
  160. SampleIO::littleEndian,
  161. SampleIO::signedPCM)
  162. .ReadSample(mptSample, chunk);
  163. }
  164. bool firstSong = true;
  165. for(auto &chunk : songChunks)
  166. {
  167. if(!firstSong && !Order.AddSequence())
  168. break;
  169. firstSong = false;
  170. Order().clear();
  171. KMSongHeader songHeader;
  172. if(!chunk.Read(songHeader)
  173. || songHeader.unknown != 0
  174. || songHeader.numChannels < 1
  175. || songHeader.numChannels > 4)
  176. return false;
  177. Order().SetName(mpt::ToUnicode(mpt::Charset::CP437, songHeader.name));
  178. FileReader musicData = (loadFlags & loadPatternData) ? chunk.ReadChunk(songHeader.musicSize) : FileReader{};
  179. // Map the samples for this subsong
  180. std::array<SAMPLEINDEX, 32> sampleMap{};
  181. for(uint8 smp = 1; smp <= 31; smp++)
  182. {
  183. const auto &srcSample = songHeader.samples[smp - 1];
  184. const auto srcName = mpt::String::ReadAutoBuf(srcSample.name);
  185. if(srcName.empty())
  186. continue;
  187. if(srcSample.finetune > 15 || srcSample.volume > 64 || !IsValidKMString(srcSample.name))
  188. return false;
  189. const auto finetune = MOD2XMFineTune(srcSample.finetune);
  190. const uint16 volume = srcSample.volume * 4u;
  191. SAMPLEINDEX copyFrom = 0;
  192. for(SAMPLEINDEX srcSmp = 1; srcSmp <= m_nSamples; srcSmp++)
  193. {
  194. if(srcName != m_szNames[srcSmp])
  195. continue;
  196. auto &mptSample = Samples[srcSmp];
  197. sampleMap[smp] = srcSmp;
  198. if(mptSample.nVolume == MUS_SAMPLE_UNUSED
  199. || (mptSample.nFineTune == finetune && mptSample.nVolume == volume))
  200. {
  201. // Sample was not used yet, or it uses the same finetune and volume
  202. mptSample.nFineTune = finetune;
  203. mptSample.nVolume = volume;
  204. copyFrom = 0;
  205. break;
  206. } else
  207. {
  208. copyFrom = srcSmp;
  209. }
  210. }
  211. if(copyFrom && CanAddMoreSamples())
  212. {
  213. m_nSamples++;
  214. sampleMap[smp] = m_nSamples;
  215. const auto &smpFrom = Samples[copyFrom];
  216. auto &newSample = Samples[m_nSamples];
  217. newSample.FreeSample();
  218. newSample = smpFrom;
  219. newSample.nFineTune = finetune;
  220. newSample.nVolume = volume;
  221. newSample.CopyWaveform(smpFrom);
  222. m_szNames[m_nSamples] = m_szNames[copyFrom];
  223. }
  224. }
  225. struct ChannelState
  226. {
  227. ModCommand prevCommand;
  228. uint8 repeat = 0;
  229. };
  230. std::array<ChannelState, 4> chnStates{};
  231. static constexpr ROWINDEX MUS_PATTERN_LENGTH = 64;
  232. const CHANNELINDEX numChannels = static_cast<CHANNELINDEX>(songHeader.numChannels);
  233. PATTERNINDEX pat = PATTERNINDEX_INVALID;
  234. ROWINDEX row = MUS_PATTERN_LENGTH;
  235. ROWINDEX restartRow = 0;
  236. uint32 repeatsLeft = 0;
  237. while(repeatsLeft || musicData.CanRead(1))
  238. {
  239. row++;
  240. if(row >= MUS_PATTERN_LENGTH)
  241. {
  242. pat = Patterns.InsertAny(MUS_PATTERN_LENGTH);
  243. if(pat == PATTERNINDEX_INVALID)
  244. break;
  245. Order().push_back(pat);
  246. row = 0;
  247. }
  248. ModCommand *m = Patterns[pat].GetpModCommand(row, 0);
  249. for(CHANNELINDEX chn = 0; chn < numChannels; chn++, m++)
  250. {
  251. auto &chnState = chnStates[chn];
  252. if(chnState.repeat)
  253. {
  254. chnState.repeat--;
  255. repeatsLeft--;
  256. *m = chnState.prevCommand;
  257. continue;
  258. }
  259. if(!musicData.CanRead(1))
  260. continue;
  261. if(musicData.GetPosition() == songHeader.restartPos)
  262. {
  263. Order().SetRestartPos(Order().GetLastIndex());
  264. restartRow = row;
  265. }
  266. const uint8 note = musicData.ReadUint8();
  267. if(note & 0x80)
  268. {
  269. chnState.repeat = note & 0x7F;
  270. repeatsLeft += chnState.repeat;
  271. *m = chnState.prevCommand;
  272. continue;
  273. }
  274. if(note > 0 && note <= 3 * 12)
  275. m->note = note + NOTE_MIDDLEC - 13;
  276. const auto instr = musicData.ReadUint8();
  277. m->instr = static_cast<ModCommand::INSTR>(sampleMap[instr & 0x1F]);
  278. if(instr & 0x80)
  279. {
  280. m->command = chnState.prevCommand.command;
  281. m->param = chnState.prevCommand.param;
  282. } else
  283. {
  284. static constexpr struct { ModCommand::COMMAND command; uint8 mask; } effTrans[] =
  285. {
  286. {CMD_VOLUME, 0x00}, {CMD_MODCMDEX, 0xA0}, {CMD_MODCMDEX, 0xB0}, {CMD_MODCMDEX, 0x10},
  287. {CMD_MODCMDEX, 0x20}, {CMD_MODCMDEX, 0x50}, {CMD_OFFSET, 0x00}, {CMD_TONEPORTAMENTO, 0x00},
  288. {CMD_TONEPORTAVOL, 0x00}, {CMD_VIBRATO, 0x00}, {CMD_VIBRATOVOL, 0x00}, {CMD_ARPEGGIO, 0x00},
  289. {CMD_PORTAMENTOUP, 0x00}, {CMD_PORTAMENTODOWN, 0x00}, {CMD_VOLUMESLIDE, 0x00}, {CMD_MODCMDEX, 0x90},
  290. {CMD_TONEPORTAMENTO, 0xFF}, {CMD_MODCMDEX, 0xC0}, {CMD_SPEED, 0x00}, {CMD_TREMOLO, 0x00},
  291. };
  292. const auto [command, param] = musicData.ReadArray<uint8, 2>();
  293. if(command < std::size(effTrans))
  294. {
  295. m->command = effTrans[command].command;
  296. m->param = param;
  297. if(m->command == CMD_SPEED && m->param >= 0x20)
  298. m->command = CMD_TEMPO;
  299. else if(effTrans[command].mask)
  300. m->param = effTrans[command].mask | (m->param & 0x0F);
  301. }
  302. }
  303. chnState.prevCommand = *m;
  304. }
  305. }
  306. if((restartRow != 0 || row < (MUS_PATTERN_LENGTH - 1u)) && pat != PATTERNINDEX_INVALID)
  307. {
  308. Patterns[pat].WriteEffect(EffectWriter(CMD_PATTERNBREAK, static_cast<ModCommand::PARAM>(restartRow)).Row(row).RetryNextRow());
  309. }
  310. }
  311. Order.SetSequence(0);
  312. m_modFormat.formatName = U_("Karl Morton Music Format");
  313. m_modFormat.type = U_("mus");
  314. m_modFormat.charset = mpt::Charset::CP437;
  315. return true;
  316. }
  317. OPENMPT_NAMESPACE_END