Load_dmf.cpp 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148
  1. /*
  2. * load_dmf.cpp
  3. * ------------
  4. * Purpose: DMF module loader (X-Tracker by D-LUSiON).
  5. * Notes : If it wasn't already outdated when the tracker left beta state, this would be a rather interesting
  6. * and in some parts even sophisticated format - effect columns are separated by effect type, an easy to
  7. * understand BPM tempo mode, effect durations are always divided into a 256th row, vibrato effects are
  8. * specified by period length and the same 8-Bit granularity is used for both volume and panning.
  9. * Unluckily, this format does not offer any envelopes or multi-sample instruments, and bidi sample loops
  10. * are missing as well, so it was already well behind FT2 back then.
  11. * Authors: Johannes Schultz (mostly based on DMF.TXT, DMF_EFFC.TXT, trial and error and some invaluable hints by Zatzen)
  12. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  13. */
  14. #include "stdafx.h"
  15. #include "Loaders.h"
  16. #include "BitReader.h"
  17. OPENMPT_NAMESPACE_BEGIN
  18. // DMF header
  19. struct DMFFileHeader
  20. {
  21. char signature[4]; // "DDMF"
  22. uint8 version; // 1 - 7 are beta versions, 8 is the official thing, 10 is xtracker32
  23. char tracker[8]; // "XTRACKER"
  24. char songname[30];
  25. char composer[20];
  26. uint8 creationDay;
  27. uint8 creationMonth;
  28. uint8 creationYear;
  29. };
  30. MPT_BINARY_STRUCT(DMFFileHeader, 66)
  31. struct DMFChunk
  32. {
  33. // 32-Bit chunk identifiers
  34. enum ChunkIdentifiers
  35. {
  36. idCMSG = MagicLE("CMSG"), // Song message
  37. idSEQU = MagicLE("SEQU"), // Order list
  38. idPATT = MagicLE("PATT"), // Patterns
  39. idSMPI = MagicLE("SMPI"), // Sample headers
  40. idSMPD = MagicLE("SMPD"), // Sample data
  41. idSMPJ = MagicLE("SMPJ"), // Sample jump table (XTracker 32 only)
  42. idENDE = MagicLE("ENDE"), // Last four bytes of DMF file
  43. idSETT = MagicLE("SETT"), // Probably contains GUI settings
  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(DMFChunk, 8)
  57. // Pattern header (global)
  58. struct DMFPatterns
  59. {
  60. uint16le numPatterns; // 1..1024 patterns
  61. uint8le numTracks; // 1..32 channels
  62. };
  63. MPT_BINARY_STRUCT(DMFPatterns, 3)
  64. // Pattern header (for each pattern)
  65. struct DMFPatternHeader
  66. {
  67. uint8le numTracks; // 1..32 channels
  68. uint8le beat; // [hi|lo] -> hi = rows per beat, lo = reserved
  69. uint16le numRows;
  70. uint32le patternLength;
  71. // patttern data follows here ...
  72. };
  73. MPT_BINARY_STRUCT(DMFPatternHeader, 8)
  74. // Sample header
  75. struct DMFSampleHeader
  76. {
  77. enum SampleFlags
  78. {
  79. // Sample flags
  80. smpLoop = 0x01,
  81. smp16Bit = 0x02,
  82. smpCompMask = 0x0C,
  83. smpComp1 = 0x04, // Compression type 1
  84. smpComp2 = 0x08, // Compression type 2 (unused)
  85. smpComp3 = 0x0C, // Compression type 3 (ditto)
  86. smpLibrary = 0x80, // Sample is stored in a library
  87. };
  88. uint32le length;
  89. uint32le loopStart;
  90. uint32le loopEnd;
  91. uint16le c3freq; // 1000..45000hz
  92. uint8le volume; // 0 = ignore
  93. uint8le flags;
  94. // Convert an DMFSampleHeader to OpenMPT's internal sample representation.
  95. void ConvertToMPT(ModSample &mptSmp) const
  96. {
  97. mptSmp.Initialize();
  98. mptSmp.nLength = length;
  99. mptSmp.nSustainStart = loopStart;
  100. mptSmp.nSustainEnd = loopEnd;
  101. mptSmp.nC5Speed = c3freq;
  102. mptSmp.nGlobalVol = 64;
  103. if(volume)
  104. mptSmp.nVolume = volume + 1;
  105. else
  106. mptSmp.nVolume = 256;
  107. mptSmp.uFlags.set(SMP_NODEFAULTVOLUME, volume == 0);
  108. if((flags & smpLoop) != 0 && mptSmp.nSustainEnd > mptSmp.nSustainStart)
  109. {
  110. mptSmp.uFlags.set(CHN_SUSTAINLOOP);
  111. }
  112. if((flags & smp16Bit) != 0)
  113. {
  114. mptSmp.uFlags.set(CHN_16BIT);
  115. mptSmp.nLength /= 2;
  116. mptSmp.nSustainStart /= 2;
  117. mptSmp.nSustainEnd /= 2;
  118. }
  119. }
  120. };
  121. MPT_BINARY_STRUCT(DMFSampleHeader, 16)
  122. // Pattern translation memory
  123. struct DMFPatternSettings
  124. {
  125. struct ChannelState
  126. {
  127. ModCommand::NOTE noteBuffer = NOTE_NONE; // Note buffer
  128. ModCommand::NOTE lastNote = NOTE_NONE; // Last played note on channel
  129. uint8 vibratoType = 8; // Last used vibrato type on channel
  130. uint8 tremoloType = 4; // Last used tremolo type on channel
  131. uint8 highOffset = 6; // Last used high offset on channel
  132. bool playDir = false; // Sample play direction... false = forward (default)
  133. };
  134. std::vector<ChannelState> channels; // Memory for each channel's state
  135. bool realBPMmode = false; // true = BPM mode
  136. uint8 beat = 0; // Rows per beat
  137. uint8 tempoTicks = 32; // Tick mode param
  138. uint8 tempoBPM = 120; // BPM mode param
  139. uint8 internalTicks = 6; // Ticks per row in final pattern
  140. DMFPatternSettings(CHANNELINDEX numChannels)
  141. : channels(numChannels)
  142. { }
  143. };
  144. // Convert portamento value (not very accurate due to X-Tracker's higher granularity, to say the least)
  145. static uint8 DMFporta2MPT(uint8 val, const uint8 internalTicks, const bool hasFine)
  146. {
  147. if(val == 0)
  148. return 0;
  149. else if((val <= 0x0F && hasFine) || internalTicks < 2)
  150. return (val | 0xF0);
  151. else
  152. return std::max(uint8(1), static_cast<uint8>((val / (internalTicks - 1)))); // no porta on first tick!
  153. }
  154. // Convert portamento / volume slide value (not very accurate due to X-Tracker's higher granularity, to say the least)
  155. static uint8 DMFslide2MPT(uint8 val, const uint8 internalTicks, const bool up)
  156. {
  157. val = std::max(uint8(1), static_cast<uint8>(val / 4));
  158. const bool isFine = (val < 0x0F) || (internalTicks < 2);
  159. if(!isFine)
  160. val = std::max(uint8(1), static_cast<uint8>((val + internalTicks - 2) / (internalTicks - 1))); // no slides on first tick! "+ internalTicks - 2" for rounding precision
  161. if(up)
  162. return (isFine ? 0x0F : 0x00) | (val << 4);
  163. else
  164. return (isFine ? 0xF0 : 0x00) | (val & 0x0F);
  165. }
  166. // Calculate tremor on/off param
  167. static uint8 DMFtremor2MPT(uint8 val, const uint8 internalTicks)
  168. {
  169. uint8 ontime = (val >> 4);
  170. uint8 offtime = (val & 0x0F);
  171. ontime = static_cast<uint8>(Clamp(ontime * internalTicks / 15, 1, 15));
  172. offtime = static_cast<uint8>(Clamp(offtime * internalTicks / 15, 1, 15));
  173. return (ontime << 4) | offtime;
  174. }
  175. // Calculate delay parameter for note cuts / delays
  176. static uint8 DMFdelay2MPT(uint8 val, const uint8 internalTicks)
  177. {
  178. int newval = (int)val * (int)internalTicks / 255;
  179. Limit(newval, 0, 15);
  180. return (uint8)newval;
  181. }
  182. // Convert vibrato-style command parameters
  183. static uint8 DMFvibrato2MPT(uint8 val, const uint8 internalTicks)
  184. {
  185. // MPT: 1 vibrato period == 64 ticks... we have internalTicks ticks per row.
  186. // X-Tracker: Period length specified in rows!
  187. const int periodInTicks = std::max(1, (val >> 4)) * internalTicks;
  188. const uint8 matchingPeriod = static_cast<uint8>(Clamp((128 / periodInTicks), 1, 15));
  189. return (matchingPeriod << 4) | std::max(uint8(1), static_cast<uint8>(val & 0x0F));
  190. }
  191. // Try using effect memory (zero paramer) to give the effect swapper some optimization hints.
  192. static void ApplyEffectMemory(const ModCommand *m, ROWINDEX row, CHANNELINDEX numChannels, uint8 effect, uint8 &param)
  193. {
  194. if(effect == CMD_NONE || param == 0)
  195. return;
  196. const bool isTonePortaEffect = (effect == CMD_PORTAMENTOUP || effect == CMD_PORTAMENTODOWN || effect == CMD_TONEPORTAMENTO);
  197. const bool isVolSlideEffect = (effect == CMD_VOLUMESLIDE || effect == CMD_TONEPORTAVOL || effect == CMD_VIBRATOVOL);
  198. while(row > 0)
  199. {
  200. m -= numChannels;
  201. row--;
  202. // First, keep some extra rules in mind for portamento, where effect memory is shared between various commands.
  203. bool isSame = (effect == m->command);
  204. if(isTonePortaEffect && (m->command == CMD_PORTAMENTOUP || m->command == CMD_PORTAMENTODOWN || m->command == CMD_TONEPORTAMENTO))
  205. {
  206. if(m->param < 0xE0)
  207. {
  208. // Avoid effect param for fine slides, or else we could accidentally put this command in the volume column, where fine slides won't work!
  209. isSame = true;
  210. } else
  211. {
  212. return;
  213. }
  214. } else if(isVolSlideEffect && (m->command == CMD_VOLUMESLIDE || m->command == CMD_TONEPORTAVOL || m->command == CMD_VIBRATOVOL))
  215. {
  216. isSame = true;
  217. }
  218. if(isTonePortaEffect
  219. && (m->volcmd == VOLCMD_PORTAUP || m->volcmd == VOLCMD_PORTADOWN || m->volcmd == VOLCMD_TONEPORTAMENTO)
  220. && m->vol != 0)
  221. {
  222. // Uuh... Don't even try
  223. return;
  224. } else if(isVolSlideEffect
  225. && (m->volcmd == VOLCMD_FINEVOLUP || m->volcmd == VOLCMD_FINEVOLDOWN || m->volcmd == VOLCMD_VOLSLIDEUP || m->volcmd == VOLCMD_VOLSLIDEDOWN)
  226. && m->vol != 0)
  227. {
  228. // Same!
  229. return;
  230. }
  231. if(isSame)
  232. {
  233. if(param != m->param && m->param != 0)
  234. {
  235. // No way to optimize this
  236. return;
  237. } else if(param == m->param)
  238. {
  239. // Yay!
  240. param = 0;
  241. return;
  242. }
  243. }
  244. }
  245. }
  246. static PATTERNINDEX ConvertDMFPattern(FileReader &file, const uint8 fileVersion, DMFPatternSettings &settings, CSoundFile &sndFile)
  247. {
  248. // Pattern flags
  249. enum PatternFlags
  250. {
  251. // Global Track
  252. patGlobPack = 0x80, // Pack information for global track follows
  253. patGlobMask = 0x3F, // Mask for global effects
  254. // Note tracks
  255. patCounter = 0x80, // Pack information for current channel follows
  256. patInstr = 0x40, // Instrument number present
  257. patNote = 0x20, // Note present
  258. patVolume = 0x10, // Volume present
  259. patInsEff = 0x08, // Instrument effect present
  260. patNoteEff = 0x04, // Note effect present
  261. patVolEff = 0x02, // Volume effect stored
  262. };
  263. file.Rewind();
  264. DMFPatternHeader patHead;
  265. if(fileVersion < 3)
  266. {
  267. patHead.numTracks = file.ReadUint8();
  268. file.Skip(2); // not sure what this is, later X-Tracker versions just skip over it
  269. patHead.numRows = file.ReadUint16LE();
  270. patHead.patternLength = file.ReadUint32LE();
  271. } else
  272. {
  273. file.ReadStruct(patHead);
  274. }
  275. if(fileVersion < 6)
  276. patHead.beat = 0;
  277. const ROWINDEX numRows = Clamp(ROWINDEX(patHead.numRows), ROWINDEX(1), MAX_PATTERN_ROWS);
  278. const PATTERNINDEX pat = sndFile.Patterns.InsertAny(numRows);
  279. if(pat == PATTERNINDEX_INVALID)
  280. {
  281. return pat;
  282. }
  283. PatternRow m = sndFile.Patterns[pat].GetRow(0);
  284. const CHANNELINDEX numChannels = std::min(static_cast<CHANNELINDEX>(sndFile.GetNumChannels() - 1), static_cast<CHANNELINDEX>(patHead.numTracks));
  285. // When breaking to a pattern with less channels that the previous pattern,
  286. // all voices in the now unused channels are killed:
  287. for(CHANNELINDEX chn = numChannels + 1; chn < sndFile.GetNumChannels(); chn++)
  288. {
  289. m[chn].note = NOTE_NOTECUT;
  290. }
  291. // Initialize tempo stuff
  292. settings.beat = (patHead.beat >> 4);
  293. bool tempoChange = settings.realBPMmode;
  294. uint8 writeDelay = 0;
  295. // Counters for channel packing (including global track)
  296. std::vector<uint8> channelCounter(numChannels + 1, 0);
  297. for(ROWINDEX row = 0; row < numRows; row++)
  298. {
  299. // Global track info counter reached 0 => read global track data
  300. if(channelCounter[0] == 0)
  301. {
  302. uint8 globalInfo = file.ReadUint8();
  303. // 0x80: Packing counter (if not present, counter stays at 0)
  304. if((globalInfo & patGlobPack) != 0)
  305. {
  306. channelCounter[0] = file.ReadUint8();
  307. }
  308. globalInfo &= patGlobMask;
  309. uint8 globalData = 0;
  310. if(globalInfo != 0)
  311. {
  312. globalData = file.ReadUint8();
  313. }
  314. switch(globalInfo)
  315. {
  316. case 1: // Set Tick Frame Speed
  317. settings.realBPMmode = false;
  318. settings.tempoTicks = std::max(uint8(1), globalData); // Tempo in 1/4 rows per second
  319. settings.tempoBPM = 0; // Automatically updated by X-Tracker
  320. tempoChange = true;
  321. break;
  322. case 2: // Set BPM Speed (real BPM mode)
  323. if(globalData) // DATA = 0 doesn't do anything
  324. {
  325. settings.realBPMmode = true;
  326. settings.tempoBPM = globalData; // Tempo in real BPM (depends on rows per beat)
  327. if(settings.beat != 0)
  328. {
  329. settings.tempoTicks = (globalData * settings.beat * 15); // Automatically updated by X-Tracker
  330. }
  331. tempoChange = true;
  332. }
  333. break;
  334. case 3: // Set Beat
  335. settings.beat = (globalData >> 4);
  336. if(settings.beat != 0)
  337. {
  338. // Tempo changes only if we're in real BPM mode
  339. tempoChange = settings.realBPMmode;
  340. } else
  341. {
  342. // If beat is 0, change to tick speed mode, but keep current tempo
  343. settings.realBPMmode = false;
  344. }
  345. break;
  346. case 4: // Tick Delay
  347. writeDelay = globalData;
  348. break;
  349. case 5: // Set External Flag
  350. break;
  351. case 6: // Slide Speed Up
  352. if(globalData > 0)
  353. {
  354. uint8 &tempoData = (settings.realBPMmode) ? settings.tempoBPM : settings.tempoTicks;
  355. if(tempoData < 256 - globalData)
  356. {
  357. tempoData += globalData;
  358. } else
  359. {
  360. tempoData = 255;
  361. }
  362. tempoChange = true;
  363. }
  364. break;
  365. case 7: // Slide Speed Down
  366. if(globalData > 0)
  367. {
  368. uint8 &tempoData = (settings.realBPMmode) ? settings.tempoBPM : settings.tempoTicks;
  369. if(tempoData > 1 + globalData)
  370. {
  371. tempoData -= globalData;
  372. } else
  373. {
  374. tempoData = 1;
  375. }
  376. tempoChange = true;
  377. }
  378. break;
  379. }
  380. } else
  381. {
  382. channelCounter[0]--;
  383. }
  384. // These will eventually be written to the pattern
  385. int speed = 0, tempo = 0;
  386. if(tempoChange)
  387. {
  388. // Can't do anything if we're in BPM mode and there's no rows per beat set...
  389. if(!settings.realBPMmode || settings.beat)
  390. {
  391. // My approach to convert X-Tracker's "tick speed" (1/4 rows per second):
  392. // Tempo * 6 / Speed = Beats per Minute
  393. // => Tempo * 6 / (Speed * 60) = Beats per Second
  394. // => Tempo * 24 / (Speed * 60) = Rows per Second (4 rows per beat at tempo 6)
  395. // => Tempo = 60 * Rows per Second * Speed / 24
  396. // For some reason, using settings.tempoTicks + 1 gives more accurate results than just settings.tempoTicks... (same problem in the old libmodplug DMF loader)
  397. // Original unoptimized formula:
  398. //const int tickspeed = (tempoRealBPMmode) ? std::max(1, (tempoData * beat * 4) / 60) : tempoData;
  399. const int tickspeed = (settings.realBPMmode) ? std::max(1, settings.tempoBPM * settings.beat * 2) : ((settings.tempoTicks + 1) * 30);
  400. // Try to find matching speed - try higher speeds first, so that effects like arpeggio and tremor work better.
  401. for(speed = 255; speed >= 1; speed--)
  402. {
  403. // Original unoptimized formula:
  404. // tempo = 30 * tickspeed * speed / 48;
  405. tempo = tickspeed * speed / 48;
  406. if(tempo >= 32 && tempo <= 255)
  407. break;
  408. }
  409. Limit(tempo, 32, 255);
  410. settings.internalTicks = static_cast<uint8>(std::max(1, speed));
  411. } else
  412. {
  413. tempoChange = false;
  414. }
  415. }
  416. m = sndFile.Patterns[pat].GetpModCommand(row, 1); // Reserve first channel for global effects
  417. for(CHANNELINDEX chn = 1; chn <= numChannels; chn++, m++)
  418. {
  419. // Track info counter reached 0 => read track data
  420. if(channelCounter[chn] == 0)
  421. {
  422. const uint8 channelInfo = file.ReadUint8();
  423. ////////////////////////////////////////////////////////////////
  424. // 0x80: Packing counter (if not present, counter stays at 0)
  425. if((channelInfo & patCounter) != 0)
  426. {
  427. channelCounter[chn] = file.ReadUint8();
  428. }
  429. ////////////////////////////////////////////////////////////////
  430. // 0x40: Instrument
  431. bool slideNote = true; // If there is no instrument number next to a note, the note is not retriggered!
  432. if((channelInfo & patInstr) != 0)
  433. {
  434. m->instr = file.ReadUint8();
  435. if(m->instr != 0)
  436. {
  437. slideNote = false;
  438. }
  439. }
  440. ////////////////////////////////////////////////////////////////
  441. // 0x20: Note
  442. if((channelInfo & patNote) != 0)
  443. {
  444. m->note = file.ReadUint8();
  445. if(m->note >= 1 && m->note <= 108)
  446. {
  447. m->note = static_cast<uint8>(Clamp(m->note + 24, NOTE_MIN, NOTE_MAX));
  448. settings.channels[chn].lastNote = m->note;
  449. } else if(m->note >= 129 && m->note <= 236)
  450. {
  451. // "Buffer notes" for portamento (and other effects?) that are actually not played, but just "queued"...
  452. m->note = static_cast<uint8>(Clamp((m->note & 0x7F) + 24, NOTE_MIN, NOTE_MAX));
  453. settings.channels[chn].noteBuffer = m->note;
  454. m->note = NOTE_NONE;
  455. } else if(m->note == 255)
  456. {
  457. m->note = NOTE_NOTECUT;
  458. }
  459. }
  460. // If there's just an instrument number, but no note, retrigger sample.
  461. if(m->note == NOTE_NONE && m->instr > 0)
  462. {
  463. m->note = settings.channels[chn].lastNote;
  464. m->instr = 0;
  465. }
  466. if(m->IsNote())
  467. {
  468. settings.channels[chn].playDir = false;
  469. }
  470. uint8 effect1 = CMD_NONE, effect2 = CMD_NONE, effect3 = CMD_NONE;
  471. uint8 effectParam1 = 0, effectParam2 = 0, effectParam3 = 0;
  472. bool useMem2 = false, useMem3 = false; // Effect can use memory if necessary
  473. ////////////////////////////////////////////////////////////////
  474. // 0x10: Volume
  475. if((channelInfo & patVolume) != 0)
  476. {
  477. m->volcmd = VOLCMD_VOLUME;
  478. m->vol = (file.ReadUint8() + 2) / 4; // Should be + 3 instead of + 2, but volume 1 is silent in X-Tracker.
  479. }
  480. ////////////////////////////////////////////////////////////////
  481. // 0x08: Instrument effect
  482. if((channelInfo & patInsEff) != 0)
  483. {
  484. effect1 = file.ReadUint8();
  485. effectParam1 = file.ReadUint8();
  486. switch(effect1)
  487. {
  488. case 1: // Stop Sample
  489. m->note = NOTE_NOTECUT;
  490. effect1 = CMD_NONE;
  491. break;
  492. case 2: // Stop Sample Loop
  493. m->note = NOTE_KEYOFF;
  494. effect1 = CMD_NONE;
  495. break;
  496. case 3: // Instrument Volume Override (aka "Restart")
  497. m->note = settings.channels[chn].lastNote;
  498. settings.channels[chn].playDir = false;
  499. effect1 = CMD_NONE;
  500. break;
  501. case 4: // Sample Delay
  502. effectParam1 = DMFdelay2MPT(effectParam1, settings.internalTicks);
  503. if(effectParam1)
  504. {
  505. effect1 = CMD_S3MCMDEX;
  506. effectParam1 = 0xD0 | (effectParam1);
  507. } else
  508. {
  509. effect1 = CMD_NONE;
  510. }
  511. if(m->note == NOTE_NONE)
  512. {
  513. m->note = settings.channels[chn].lastNote;
  514. settings.channels[chn].playDir = false;
  515. }
  516. break;
  517. case 5: // Tremolo Retrig Sample (who invented those stupid effect names?)
  518. effectParam1 = std::max(uint8(1), DMFdelay2MPT(effectParam1, settings.internalTicks));
  519. effect1 = CMD_RETRIG;
  520. settings.channels[chn].playDir = false;
  521. break;
  522. case 6: // Offset
  523. case 7: // Offset + 64k
  524. case 8: // Offset + 128k
  525. case 9: // Offset + 192k
  526. // Put high offset on previous row
  527. if(row > 0 && effect1 != settings.channels[chn].highOffset)
  528. {
  529. if(sndFile.Patterns[pat].WriteEffect(EffectWriter(CMD_S3MCMDEX, (0xA0 | (effect1 - 6))).Row(row - 1).Channel(chn).RetryPreviousRow()))
  530. {
  531. settings.channels[chn].highOffset = effect1;
  532. }
  533. }
  534. effect1 = CMD_OFFSET;
  535. if(m->note == NOTE_NONE)
  536. {
  537. // Offset without note does also work in DMF.
  538. m->note = settings.channels[chn].lastNote;
  539. }
  540. settings.channels[chn].playDir = false;
  541. break;
  542. case 10: // Invert Sample play direction ("Tekkno Invert")
  543. effect1 = CMD_S3MCMDEX;
  544. if(settings.channels[chn].playDir == false)
  545. effectParam1 = 0x9F;
  546. else
  547. effectParam1 = 0x9E;
  548. settings.channels[chn].playDir = !settings.channels[chn].playDir;
  549. break;
  550. default:
  551. effect1 = CMD_NONE;
  552. break;
  553. }
  554. }
  555. ////////////////////////////////////////////////////////////////
  556. // 0x04: Note effect
  557. if((channelInfo & patNoteEff) != 0)
  558. {
  559. effect2 = file.ReadUint8();
  560. effectParam2 = file.ReadUint8();
  561. switch(effect2)
  562. {
  563. case 1: // Note Finetune (1/16th of a semitone signed 8-bit value, not 1/128th as the interface claims)
  564. {
  565. const auto fine = std::div(static_cast<int8>(effectParam2) * 8, 128);
  566. if(m->IsNote())
  567. m->note = static_cast<ModCommand::NOTE>(Clamp(m->note + fine.quot, NOTE_MIN, NOTE_MAX));
  568. effect2 = CMD_FINETUNE;
  569. effectParam2 = static_cast<uint8>(fine.rem) ^ 0x80;
  570. }
  571. break;
  572. case 2: // Note Delay (wtf is the difference to Sample Delay?)
  573. effectParam2 = DMFdelay2MPT(effectParam2, settings.internalTicks);
  574. if(effectParam2)
  575. {
  576. effect2 = CMD_S3MCMDEX;
  577. effectParam2 = 0xD0 | (effectParam2);
  578. } else
  579. {
  580. effect2 = CMD_NONE;
  581. }
  582. useMem2 = true;
  583. break;
  584. case 3: // Arpeggio
  585. effect2 = CMD_ARPEGGIO;
  586. useMem2 = true;
  587. break;
  588. case 4: // Portamento Up
  589. case 5: // Portamento Down
  590. effectParam2 = DMFporta2MPT(effectParam2, settings.internalTicks, true);
  591. effect2 = (effect2 == 4) ? CMD_PORTAMENTOUP : CMD_PORTAMENTODOWN;
  592. useMem2 = true;
  593. break;
  594. case 6: // Portamento to Note
  595. if(m->note == NOTE_NONE)
  596. {
  597. m->note = settings.channels[chn].noteBuffer;
  598. }
  599. effectParam2 = DMFporta2MPT(effectParam2, settings.internalTicks, false);
  600. effect2 = CMD_TONEPORTAMENTO;
  601. useMem2 = true;
  602. break;
  603. case 7: // Scratch to Note (neat! but we don't have such an effect...)
  604. m->note = static_cast<ModCommand::NOTE>(Clamp(effectParam2 + 25, NOTE_MIN, NOTE_MAX));
  605. effect2 = CMD_TONEPORTAMENTO;
  606. effectParam2 = 0xFF;
  607. useMem2 = true;
  608. break;
  609. case 8: // Vibrato Sine
  610. case 9: // Vibrato Triangle (ramp down should be close enough)
  611. case 10: // Vibrato Square
  612. // Put vibrato type on previous row
  613. if(row > 0 && effect2 != settings.channels[chn].vibratoType)
  614. {
  615. if(sndFile.Patterns[pat].WriteEffect(EffectWriter(CMD_S3MCMDEX, (0x30 | (effect2 - 8))).Row(row - 1).Channel(chn).RetryPreviousRow()))
  616. {
  617. settings.channels[chn].vibratoType = effect2;
  618. }
  619. }
  620. effect2 = CMD_VIBRATO;
  621. effectParam2 = DMFvibrato2MPT(effectParam2, settings.internalTicks);
  622. useMem2 = true;
  623. break;
  624. case 11: // Note Tremolo
  625. effectParam2 = DMFtremor2MPT(effectParam2, settings.internalTicks);
  626. effect2 = CMD_TREMOR;
  627. useMem2 = true;
  628. break;
  629. case 12: // Note Cut
  630. effectParam2 = DMFdelay2MPT(effectParam2, settings.internalTicks);
  631. if(effectParam2)
  632. {
  633. effect2 = CMD_S3MCMDEX;
  634. effectParam2 = 0xC0 | (effectParam2);
  635. } else
  636. {
  637. effect2 = CMD_NONE;
  638. m->note = NOTE_NOTECUT;
  639. }
  640. useMem2 = true;
  641. break;
  642. default:
  643. effect2 = CMD_NONE;
  644. break;
  645. }
  646. }
  647. ////////////////////////////////////////////////////////////////
  648. // 0x02: Volume effect
  649. if((channelInfo & patVolEff) != 0)
  650. {
  651. effect3 = file.ReadUint8();
  652. effectParam3 = file.ReadUint8();
  653. switch(effect3)
  654. {
  655. case 1: // Volume Slide Up
  656. case 2: // Volume Slide Down
  657. effectParam3 = DMFslide2MPT(effectParam3, settings.internalTicks, (effect3 == 1));
  658. effect3 = CMD_VOLUMESLIDE;
  659. useMem3 = true;
  660. break;
  661. case 3: // Volume Tremolo (actually this is Tremor)
  662. effectParam3 = DMFtremor2MPT(effectParam3, settings.internalTicks);
  663. effect3 = CMD_TREMOR;
  664. useMem3 = true;
  665. break;
  666. case 4: // Tremolo Sine
  667. case 5: // Tremolo Triangle (ramp down should be close enough)
  668. case 6: // Tremolo Square
  669. // Put tremolo type on previous row
  670. if(row > 0 && effect3 != settings.channels[chn].tremoloType)
  671. {
  672. if(sndFile.Patterns[pat].WriteEffect(EffectWriter(CMD_S3MCMDEX, (0x40 | (effect3 - 4))).Row(row - 1).Channel(chn).RetryPreviousRow()))
  673. {
  674. settings.channels[chn].tremoloType = effect3;
  675. }
  676. }
  677. effect3 = CMD_TREMOLO;
  678. effectParam3 = DMFvibrato2MPT(effectParam3, settings.internalTicks);
  679. useMem3 = true;
  680. break;
  681. case 7: // Set Balance
  682. effect3 = CMD_PANNING8;
  683. break;
  684. case 8: // Slide Balance Left
  685. case 9: // Slide Balance Right
  686. effectParam3 = DMFslide2MPT(effectParam3, settings.internalTicks, (effect3 == 8));
  687. effect3 = CMD_PANNINGSLIDE;
  688. useMem3 = true;
  689. break;
  690. case 10: // Balance Vibrato Left/Right (always sine modulated)
  691. effect3 = CMD_PANBRELLO;
  692. effectParam3 = DMFvibrato2MPT(effectParam3, settings.internalTicks);
  693. useMem3 = true;
  694. break;
  695. default:
  696. effect3 = CMD_NONE;
  697. break;
  698. }
  699. }
  700. // Let's see if we can help the effect swapper by reducing some effect parameters to "continue" parameters.
  701. if(useMem2)
  702. ApplyEffectMemory(m, row, sndFile.GetNumChannels(), effect2, effectParam2);
  703. if(useMem3)
  704. ApplyEffectMemory(m, row, sndFile.GetNumChannels(), effect3, effectParam3);
  705. // I guess this is close enough to "not retriggering the note"
  706. if(slideNote && m->IsNote())
  707. {
  708. if(effect2 == CMD_NONE)
  709. {
  710. effect2 = CMD_TONEPORTAMENTO;
  711. effectParam2 = 0xFF;
  712. } else if(effect3 == CMD_NONE && effect2 != CMD_TONEPORTAMENTO) // Tone portamentos normally go in effect #2
  713. {
  714. effect3 = CMD_TONEPORTAMENTO;
  715. effectParam3 = 0xFF;
  716. }
  717. }
  718. // If one of the effects is unused, temporarily put volume commands in there
  719. if(m->volcmd == VOLCMD_VOLUME)
  720. {
  721. if(effect2 == CMD_NONE)
  722. {
  723. effect2 = CMD_VOLUME;
  724. effectParam2 = m->vol;
  725. m->volcmd = VOLCMD_NONE;
  726. } else if(effect3 == CMD_NONE)
  727. {
  728. effect3 = CMD_VOLUME;
  729. effectParam3 = m->vol;
  730. m->volcmd = VOLCMD_NONE;
  731. }
  732. }
  733. ModCommand::TwoRegularCommandsToMPT(effect2, effectParam2, effect3, effectParam3);
  734. if(m->volcmd == VOLCMD_NONE && effect2 != VOLCMD_NONE)
  735. {
  736. m->volcmd = effect2;
  737. m->vol = effectParam2;
  738. }
  739. // Prefer instrument effects over any other effects
  740. if(effect1 != CMD_NONE)
  741. {
  742. ModCommand::TwoRegularCommandsToMPT(effect3, effectParam3, effect1, effectParam1);
  743. if(m->volcmd == VOLCMD_NONE && effect3 != VOLCMD_NONE)
  744. {
  745. m->volcmd = effect3;
  746. m->vol = effectParam3;
  747. }
  748. m->command = effect1;
  749. m->param = effectParam1;
  750. } else if(effect3 != CMD_NONE)
  751. {
  752. m->command = effect3;
  753. m->param = effectParam3;
  754. }
  755. } else
  756. {
  757. channelCounter[chn]--;
  758. }
  759. } // End for all channels
  760. // Now we can try to write tempo information.
  761. if(tempoChange)
  762. {
  763. tempoChange = false;
  764. sndFile.Patterns[pat].WriteEffect(EffectWriter(CMD_TEMPO, static_cast<ModCommand::PARAM>(tempo)).Row(row).Channel(0).RetryNextRow());
  765. sndFile.Patterns[pat].WriteEffect(EffectWriter(CMD_SPEED, static_cast<ModCommand::PARAM>(speed)).Row(row).RetryNextRow());
  766. }
  767. // Try to put delay effects somewhere as well
  768. if(writeDelay & 0xF0)
  769. {
  770. sndFile.Patterns[pat].WriteEffect(EffectWriter(CMD_S3MCMDEX, 0xE0 | (writeDelay >> 4)).Row(row).AllowMultiple());
  771. }
  772. if(writeDelay & 0x0F)
  773. {
  774. const uint8 param = (writeDelay & 0x0F) * settings.internalTicks / 15;
  775. sndFile.Patterns[pat].WriteEffect(EffectWriter(CMD_S3MCMDEX, 0x60u | Clamp(param, uint8(1), uint8(15))).Row(row).AllowMultiple());
  776. }
  777. writeDelay = 0;
  778. } // End for all rows
  779. return pat;
  780. }
  781. static bool ValidateHeader(const DMFFileHeader &fileHeader)
  782. {
  783. if(std::memcmp(fileHeader.signature, "DDMF", 4)
  784. || !fileHeader.version || fileHeader.version > 10)
  785. {
  786. return false;
  787. }
  788. return true;
  789. }
  790. CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderDMF(MemoryFileReader file, const uint64 *pfilesize)
  791. {
  792. DMFFileHeader fileHeader;
  793. if(!file.ReadStruct(fileHeader))
  794. {
  795. return ProbeWantMoreData;
  796. }
  797. if(!ValidateHeader(fileHeader))
  798. {
  799. return ProbeFailure;
  800. }
  801. MPT_UNREFERENCED_PARAMETER(pfilesize);
  802. return ProbeSuccess;
  803. }
  804. bool CSoundFile::ReadDMF(FileReader &file, ModLoadingFlags loadFlags)
  805. {
  806. file.Rewind();
  807. DMFFileHeader fileHeader;
  808. if(!file.ReadStruct(fileHeader))
  809. {
  810. return false;
  811. }
  812. if(!ValidateHeader(fileHeader))
  813. {
  814. return false;
  815. }
  816. if(loadFlags == onlyVerifyHeader)
  817. {
  818. return true;
  819. }
  820. InitializeGlobals(MOD_TYPE_DMF);
  821. m_modFormat.formatName = MPT_UFORMAT("X-Tracker v{}")(fileHeader.version);
  822. m_modFormat.type = U_("dmf");
  823. m_modFormat.charset = mpt::Charset::CP437;
  824. m_songName = mpt::String::ReadBuf(mpt::String::spacePadded, fileHeader.songname);
  825. m_songArtist = mpt::ToUnicode(mpt::Charset::CP437, mpt::String::ReadBuf(mpt::String::spacePadded, fileHeader.composer));
  826. FileHistory mptHistory;
  827. mptHistory.loadDate.tm_mday = Clamp(fileHeader.creationDay, uint8(1), uint8(31));
  828. mptHistory.loadDate.tm_mon = Clamp(fileHeader.creationMonth, uint8(1), uint8(12)) - 1;
  829. mptHistory.loadDate.tm_year = fileHeader.creationYear;
  830. m_FileHistory.clear();
  831. m_FileHistory.push_back(mptHistory);
  832. // Go through all chunks now... cannot use our standard IFF chunk reader here because early X-Tracker versions write some malformed chunk headers... fun code ahead!
  833. ChunkReader::ChunkList<DMFChunk> chunks;
  834. while(file.CanRead(sizeof(DMFChunk)))
  835. {
  836. DMFChunk chunkHeader;
  837. file.Read(chunkHeader);
  838. uint32 chunkLength = chunkHeader.length, chunkSkip = 0;
  839. // When loop start was added to version 3, the chunk size was not updated...
  840. if(fileHeader.version == 3 && chunkHeader.GetID() == DMFChunk::idSEQU)
  841. chunkSkip = 2;
  842. // ...and when the loop end was added to version 4, it was also note updated! Luckily they fixed it in version 5.
  843. else if(fileHeader.version == 4 && chunkHeader.GetID() == DMFChunk::idSEQU)
  844. chunkSkip = 4;
  845. // Earlier X-Tracker versions also write a garbage length for the SMPD chunk if samples are compressed.
  846. // I don't know when exactly this stopped, but I have no version 5-7 files to check (and no X-Tracker version that writes those versions).
  847. // Since this is practically always the last chunk in the file, the following code is safe for those versions, though.
  848. else if(fileHeader.version < 8 && chunkHeader.GetID() == DMFChunk::idSMPD)
  849. chunkLength = uint32_max;
  850. chunks.chunks.push_back(ChunkReader::Item<DMFChunk>{chunkHeader, file.ReadChunk(chunkLength)});
  851. file.Skip(chunkSkip);
  852. }
  853. FileReader chunk;
  854. // Read order list
  855. chunk = chunks.GetChunk(DMFChunk::idSEQU);
  856. ORDERINDEX seqLoopStart = 0, seqLoopEnd = ORDERINDEX_MAX;
  857. if(fileHeader.version >= 3)
  858. seqLoopStart = chunk.ReadUint16LE();
  859. if(fileHeader.version >= 4)
  860. seqLoopEnd = chunk.ReadUint16LE();
  861. // HIPOMATK.DMF has a loop end of 0, other v4 files have proper loop ends. Later X-Tracker versions import it as-is but it cannot be intentional.
  862. // We just assume that this feature might have been buggy in early v4 versions and ignore the loop end in that case.
  863. if(fileHeader.version == 4 && seqLoopEnd == 0)
  864. seqLoopEnd = ORDERINDEX_MAX;
  865. ReadOrderFromFile<uint16le>(Order(), chunk, chunk.BytesLeft() / 2);
  866. LimitMax(seqLoopStart, Order().GetLastIndex());
  867. LimitMax(seqLoopEnd, Order().GetLastIndex());
  868. // Read patterns
  869. chunk = chunks.GetChunk(DMFChunk::idPATT);
  870. if(chunk.IsValid() && (loadFlags & loadPatternData))
  871. {
  872. DMFPatterns patHeader;
  873. chunk.ReadStruct(patHeader);
  874. m_nChannels = Clamp<uint8, uint8>(patHeader.numTracks, 1, 32) + 1; // + 1 for global track (used for tempo stuff)
  875. // First, find out where all of our patterns are...
  876. std::vector<FileReader> patternChunks(patHeader.numPatterns);
  877. for(auto &patternChunk : patternChunks)
  878. {
  879. const uint8 headerSize = fileHeader.version < 3 ? 9 : 8;
  880. chunk.Skip(headerSize - sizeof(uint32le));
  881. const uint32 patLength = chunk.ReadUint32LE();
  882. chunk.SkipBack(headerSize);
  883. patternChunk = chunk.ReadChunk(headerSize + patLength);
  884. }
  885. // Now go through the order list and load them.
  886. DMFPatternSettings settings(GetNumChannels());
  887. Patterns.ResizeArray(Order().GetLength());
  888. for(PATTERNINDEX &pat : Order())
  889. {
  890. // Create one pattern for each order item, as the same pattern can be played with different settings
  891. if(pat < patternChunks.size())
  892. {
  893. pat = ConvertDMFPattern(patternChunks[pat], fileHeader.version, settings, *this);
  894. }
  895. }
  896. // Write loop end if necessary
  897. if(Order().IsValidPat(seqLoopEnd) && (seqLoopStart > 0 || seqLoopEnd < Order().GetLastIndex()))
  898. {
  899. PATTERNINDEX pat = Order()[seqLoopEnd];
  900. Patterns[pat].WriteEffect(EffectWriter(CMD_POSITIONJUMP, static_cast<ModCommand::PARAM>(seqLoopStart)).Row(Patterns[pat].GetNumRows() - 1).RetryPreviousRow());
  901. }
  902. }
  903. // Read song message
  904. chunk = chunks.GetChunk(DMFChunk::idCMSG);
  905. if(chunk.IsValid())
  906. {
  907. // The song message seems to start at a 1 byte offset.
  908. // The skipped byte seems to always be 0.
  909. // This also matches how XT 1.03 itself displays the song message.
  910. chunk.Skip(1);
  911. m_songMessage.ReadFixedLineLength(chunk, chunk.BytesLeft(), 40, 0);
  912. }
  913. // Read sample headers + data
  914. FileReader sampleDataChunk = chunks.GetChunk(DMFChunk::idSMPD);
  915. chunk = chunks.GetChunk(DMFChunk::idSMPI);
  916. m_nSamples = chunk.ReadUint8();
  917. for(SAMPLEINDEX smp = 1; smp <= GetNumSamples(); smp++)
  918. {
  919. const uint8 nameLength = (fileHeader.version < 2) ? 30 : chunk.ReadUint8();
  920. chunk.ReadString<mpt::String::spacePadded>(m_szNames[smp], nameLength);
  921. DMFSampleHeader sampleHeader;
  922. ModSample &sample = Samples[smp];
  923. chunk.ReadStruct(sampleHeader);
  924. sampleHeader.ConvertToMPT(sample);
  925. // Read library name in version 8 files
  926. if(fileHeader.version >= 8)
  927. chunk.ReadString<mpt::String::spacePadded>(sample.filename, 8);
  928. // Filler + CRC
  929. chunk.Skip(fileHeader.version > 1 ? 6 : 2);
  930. // Now read the sample data from the data chunk
  931. FileReader sampleData = sampleDataChunk.ReadChunk(sampleDataChunk.ReadUint32LE());
  932. if(sampleData.IsValid() && (loadFlags & loadSampleData))
  933. {
  934. SampleIO(
  935. sample.uFlags[CHN_16BIT] ? SampleIO::_16bit : SampleIO::_8bit,
  936. SampleIO::mono,
  937. SampleIO::littleEndian,
  938. (sampleHeader.flags & DMFSampleHeader::smpCompMask) == DMFSampleHeader::smpComp1 ? SampleIO::DMF : SampleIO::signedPCM)
  939. .ReadSample(sample, sampleData);
  940. }
  941. }
  942. InitializeChannels();
  943. m_SongFlags = SONG_LINEARSLIDES | SONG_ITCOMPATGXX; // this will be converted to IT format by MPT. SONG_ITOLDEFFECTS is not set because of tremor and vibrato.
  944. m_nDefaultSpeed = 6;
  945. m_nDefaultTempo.Set(120);
  946. m_nDefaultGlobalVolume = 256;
  947. m_nSamplePreAmp = m_nVSTiVolume = 48;
  948. m_playBehaviour.set(kApplyOffsetWithoutNote);
  949. return true;
  950. }
  951. ///////////////////////////////////////////////////////////////////////
  952. // DMF Compression
  953. struct DMFHNode
  954. {
  955. int16 left, right;
  956. uint8 value;
  957. };
  958. struct DMFHTree
  959. {
  960. BitReader file;
  961. int lastnode = 0, nodecount = 0;
  962. DMFHNode nodes[256]{};
  963. DMFHTree(FileReader &file)
  964. : file(file)
  965. {
  966. }
  967. //
  968. // tree: [8-bit value][12-bit index][12-bit index] = 32-bit
  969. //
  970. void DMFNewNode()
  971. {
  972. int actnode = nodecount;
  973. if(actnode > 255) return;
  974. nodes[actnode].value = static_cast<uint8>(file.ReadBits(7));
  975. bool isLeft = file.ReadBits(1) != 0;
  976. bool isRight = file.ReadBits(1) != 0;
  977. actnode = lastnode;
  978. if(actnode > 255) return;
  979. nodecount++;
  980. lastnode = nodecount;
  981. if(isLeft)
  982. {
  983. nodes[actnode].left = (int16)lastnode;
  984. DMFNewNode();
  985. } else
  986. {
  987. nodes[actnode].left = -1;
  988. }
  989. lastnode = nodecount;
  990. if(isRight)
  991. {
  992. nodes[actnode].right = (int16)lastnode;
  993. DMFNewNode();
  994. } else
  995. {
  996. nodes[actnode].right = -1;
  997. }
  998. }
  999. };
  1000. uintptr_t DMFUnpack(FileReader &file, uint8 *psample, uint32 maxlen)
  1001. {
  1002. DMFHTree tree(file);
  1003. uint8 value = 0, delta = 0;
  1004. try
  1005. {
  1006. tree.DMFNewNode();
  1007. if(tree.nodes[0].left < 0 || tree.nodes[0].right < 0)
  1008. return tree.file.GetPosition();
  1009. for(uint32 i = 0; i < maxlen; i++)
  1010. {
  1011. int actnode = 0;
  1012. bool sign = tree.file.ReadBits(1) != 0;
  1013. do
  1014. {
  1015. if(tree.file.ReadBits(1))
  1016. actnode = tree.nodes[actnode].right;
  1017. else
  1018. actnode = tree.nodes[actnode].left;
  1019. if(actnode > 255) break;
  1020. delta = tree.nodes[actnode].value;
  1021. } while((tree.nodes[actnode].left >= 0) && (tree.nodes[actnode].right >= 0));
  1022. if(sign) delta ^= 0xFF;
  1023. value += delta;
  1024. psample[i] = value;
  1025. }
  1026. } catch(const BitReader::eof &)
  1027. {
  1028. //AddToLog(LogWarning, "Truncated DMF sample block");
  1029. }
  1030. return tree.file.GetPosition();
  1031. }
  1032. OPENMPT_NAMESPACE_END