1
0

Load_c67.cpp 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. /*
  2. * Load_c67.cpp
  3. * ------------
  4. * Purpose: C67 (CDFM Composer) module loader
  5. * Notes : C67 is the composer format; 670 files can be converted back to C67 using the converter that comes with CDFM.
  6. * Authors: OpenMPT Devs
  7. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  8. */
  9. #include "stdafx.h"
  10. #include "Loaders.h"
  11. OPENMPT_NAMESPACE_BEGIN
  12. struct C67SampleHeader
  13. {
  14. uint32le unknown; // Probably placeholder for in-memory address, 0 on disk
  15. uint32le length;
  16. uint32le loopStart;
  17. uint32le loopEnd;
  18. };
  19. MPT_BINARY_STRUCT(C67SampleHeader, 16)
  20. struct C67FileHeader
  21. {
  22. uint8 speed;
  23. uint8 restartPos;
  24. char sampleNames[32][13];
  25. C67SampleHeader samples[32];
  26. char fmInstrNames[32][13];
  27. uint8 fmInstr[32][11];
  28. uint8 orders[256];
  29. };
  30. MPT_BINARY_STRUCT(C67FileHeader, 1954)
  31. static bool ValidateHeader(const C67FileHeader &fileHeader)
  32. {
  33. if(fileHeader.speed < 1 || fileHeader.speed > 15)
  34. return false;
  35. for(auto ord : fileHeader.orders)
  36. {
  37. if(ord >= 128 && ord != 0xFF)
  38. return false;
  39. }
  40. bool anyNonSilent = false;
  41. for(SAMPLEINDEX smp = 0; smp < 32; smp++)
  42. {
  43. if(fileHeader.sampleNames[smp][12] != 0
  44. || fileHeader.samples[smp].unknown != 0
  45. || fileHeader.samples[smp].length > 0xFFFFF
  46. || fileHeader.fmInstrNames[smp][12] != 0
  47. || (fileHeader.fmInstr[smp][0] & 0xF0) // No OPL3
  48. || (fileHeader.fmInstr[smp][5] & 0xFC) // No OPL3
  49. || (fileHeader.fmInstr[smp][10] & 0xFC)) // No OPL3
  50. {
  51. return false;
  52. }
  53. if(fileHeader.samples[smp].length != 0 && fileHeader.samples[smp].loopEnd < 0xFFFFF)
  54. {
  55. if(fileHeader.samples[smp].loopEnd > fileHeader.samples[smp].length
  56. || fileHeader.samples[smp].loopStart > fileHeader.samples[smp].loopEnd)
  57. {
  58. return false;
  59. }
  60. }
  61. if(!anyNonSilent && (fileHeader.samples[smp].length != 0 || memcmp(fileHeader.fmInstr[smp], "\0\0\0\0\0\0\0\0\0\0\0", 11)))
  62. {
  63. anyNonSilent = true;
  64. }
  65. }
  66. return anyNonSilent;
  67. }
  68. static uint64 GetHeaderMinimumAdditionalSize(const C67FileHeader &)
  69. {
  70. return 1024; // Pattern offsets and lengths
  71. }
  72. CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderC67(MemoryFileReader file, const uint64 *pfilesize)
  73. {
  74. C67FileHeader fileHeader;
  75. if(!file.ReadStruct(fileHeader))
  76. {
  77. return ProbeWantMoreData;
  78. }
  79. if(!ValidateHeader(fileHeader))
  80. {
  81. return ProbeFailure;
  82. }
  83. return ProbeAdditionalSize(file, pfilesize, GetHeaderMinimumAdditionalSize(fileHeader));
  84. }
  85. static void TranslateVolume(ModCommand &m, uint8 volume, bool isFM)
  86. {
  87. // CDFM uses a linear volume scale for FM instruments.
  88. // ScreamTracker, on the other hand, directly uses the OPL chip's logarithmic volume scale.
  89. // Neither FM nor PCM instruments can be fully muted in CDFM.
  90. static constexpr uint8 fmVolume[16] =
  91. {
  92. 0x08, 0x10, 0x18, 0x20, 0x28, 0x2C, 0x30, 0x34,
  93. 0x36, 0x38, 0x3A, 0x3C, 0x3D, 0x3E, 0x3F, 0x40,
  94. };
  95. volume &= 0x0F;
  96. m.volcmd = VOLCMD_VOLUME;
  97. m.vol = isFM ? fmVolume[volume] : (4u + volume * 4u);
  98. }
  99. bool CSoundFile::ReadC67(FileReader &file, ModLoadingFlags loadFlags)
  100. {
  101. C67FileHeader fileHeader;
  102. file.Rewind();
  103. if(!file.ReadStruct(fileHeader))
  104. {
  105. return false;
  106. }
  107. if(!ValidateHeader(fileHeader))
  108. {
  109. return false;
  110. }
  111. if(loadFlags == onlyVerifyHeader)
  112. {
  113. return true;
  114. }
  115. if(!file.CanRead(mpt::saturate_cast<FileReader::off_t>(GetHeaderMinimumAdditionalSize(fileHeader))))
  116. {
  117. return false;
  118. }
  119. // Validate pattern offsets and lengths
  120. uint32le patOffsets[128], patLengths[128];
  121. file.ReadArray(patOffsets);
  122. file.ReadArray(patLengths);
  123. for(PATTERNINDEX pat = 0; pat < 128; pat++)
  124. {
  125. if(patOffsets[pat] > 0xFFFFFF
  126. || patLengths[pat] < 3 // Smallest well-formed pattern consists of command 0x40 followed by command 0x60
  127. || patLengths[pat] > 0x1000 // Any well-formed pattern is smaller than this
  128. || !file.LengthIsAtLeast(2978 + patOffsets[pat] + patLengths[pat]))
  129. {
  130. return false;
  131. }
  132. }
  133. InitializeGlobals(MOD_TYPE_S3M);
  134. InitializeChannels();
  135. m_modFormat.formatName = U_("CDFM");
  136. m_modFormat.type = U_("c67");
  137. m_modFormat.madeWithTracker = U_("Composer 670");
  138. m_modFormat.charset = mpt::Charset::CP437;
  139. m_nDefaultSpeed = fileHeader.speed;
  140. m_nDefaultTempo.Set(143);
  141. Order().SetRestartPos(fileHeader.restartPos);
  142. m_nSamples = 64;
  143. m_nChannels = 4 + 9;
  144. m_playBehaviour.set(kOPLBeatingOscillators);
  145. m_SongFlags.set(SONG_IMPORTED);
  146. // Pan PCM channels only
  147. for(CHANNELINDEX chn = 0; chn < 4; chn++)
  148. {
  149. ChnSettings[chn].nPan = (chn & 1) ? 192 : 64;
  150. }
  151. // PCM instruments
  152. for(SAMPLEINDEX smp = 0; smp < 32; smp++)
  153. {
  154. ModSample &mptSmp = Samples[smp + 1];
  155. mptSmp.Initialize(MOD_TYPE_S3M);
  156. m_szNames[smp + 1] = mpt::String::ReadBuf(mpt::String::nullTerminated, fileHeader.sampleNames[smp]);
  157. mptSmp.nLength = fileHeader.samples[smp].length;
  158. if(fileHeader.samples[smp].loopEnd <= fileHeader.samples[smp].length)
  159. {
  160. mptSmp.nLoopStart = fileHeader.samples[smp].loopStart;
  161. mptSmp.nLoopEnd = fileHeader.samples[smp].loopEnd;
  162. mptSmp.uFlags = CHN_LOOP;
  163. }
  164. mptSmp.nC5Speed = 8287;
  165. }
  166. // OPL instruments
  167. for(SAMPLEINDEX smp = 0; smp < 32; smp++)
  168. {
  169. ModSample &mptSmp = Samples[smp + 33];
  170. mptSmp.Initialize(MOD_TYPE_S3M);
  171. m_szNames[smp + 33] = mpt::String::ReadBuf(mpt::String::nullTerminated, fileHeader.fmInstrNames[smp]);
  172. // Reorder OPL patch bytes (interleave modulator and carrier)
  173. const auto &fm = fileHeader.fmInstr[smp];
  174. OPLPatch patch{{}};
  175. patch[0] = fm[1]; patch[1] = fm[6];
  176. patch[2] = fm[2]; patch[3] = fm[7];
  177. patch[4] = fm[3]; patch[5] = fm[8];
  178. patch[6] = fm[4]; patch[7] = fm[9];
  179. patch[8] = fm[5]; patch[9] = fm[10];
  180. patch[10] = fm[0];
  181. mptSmp.SetAdlib(true, patch);
  182. }
  183. ReadOrderFromArray<uint8>(Order(), fileHeader.orders, 256, 0xFF);
  184. Patterns.ResizeArray(128);
  185. for(PATTERNINDEX pat = 0; pat < 128; pat++)
  186. {
  187. file.Seek(2978 + patOffsets[pat]);
  188. FileReader patChunk = file.ReadChunk(patLengths[pat]);
  189. if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, 64))
  190. {
  191. continue;
  192. }
  193. CPattern &pattern = Patterns[pat];
  194. ROWINDEX row = 0;
  195. while(row < 64 && patChunk.CanRead(1))
  196. {
  197. uint8 cmd = patChunk.ReadUint8();
  198. if(cmd <= 0x0C)
  199. {
  200. // Note, instrument, volume
  201. ModCommand &m = *pattern.GetpModCommand(row, cmd);
  202. const auto [note, instrVol] = patChunk.ReadArray<uint8, 2>();
  203. bool fmChn = (cmd >= 4);
  204. m.note = NOTE_MIN + (fmChn ? 12 : 36) + (note & 0x0F) + ((note >> 4) & 0x07) * 12;
  205. m.instr = (fmChn ? 33 : 1) + (instrVol >> 4) + ((note & 0x80) >> 3);
  206. TranslateVolume(m, instrVol, fmChn);
  207. } else if(cmd >= 0x20 && cmd <= 0x2C)
  208. {
  209. // Volume
  210. TranslateVolume(*pattern.GetpModCommand(row, cmd - 0x20), patChunk.ReadUint8(), cmd >= 0x24);
  211. } else if(cmd == 0x40)
  212. {
  213. // Delay (row done)
  214. row += patChunk.ReadUint8();
  215. } else if(cmd == 0x60)
  216. {
  217. // End of pattern
  218. if(row > 0)
  219. {
  220. pattern.GetpModCommand(row - 1, 0)->command = CMD_PATTERNBREAK;
  221. }
  222. break;
  223. } else
  224. {
  225. return false;
  226. }
  227. }
  228. }
  229. if(loadFlags & loadSampleData)
  230. {
  231. for(SAMPLEINDEX smp = 1; smp <= 32; smp++)
  232. {
  233. SampleIO(SampleIO::_8bit, SampleIO::mono, SampleIO::littleEndian, SampleIO::unsignedPCM).ReadSample(Samples[smp], file);
  234. }
  235. }
  236. return true;
  237. }
  238. OPENMPT_NAMESPACE_END