WAVTools.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. /*
  2. * WAVTools.cpp
  3. * ------------
  4. * Purpose: Definition of WAV file structures and helper functions
  5. * Notes : (currently none)
  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. #include "WAVTools.h"
  12. #include "Tagging.h"
  13. #include "../common/version.h"
  14. #ifndef MODPLUG_NO_FILESAVE
  15. #include "mpt/io/io.hpp"
  16. #include "mpt/io/io_virtual_wrapper.hpp"
  17. #include "../common/mptFileIO.h"
  18. #endif
  19. OPENMPT_NAMESPACE_BEGIN
  20. ///////////////////////////////////////////////////////////
  21. // WAV Reading
  22. WAVReader::WAVReader(FileReader &inputFile) : file(inputFile)
  23. {
  24. file.Rewind();
  25. RIFFHeader fileHeader;
  26. codePage = 28591; // ISO 8859-1
  27. isDLS = false;
  28. subFormat = 0;
  29. mayBeCoolEdit16_8 = false;
  30. if(!file.ReadStruct(fileHeader)
  31. || (fileHeader.magic != RIFFHeader::idRIFF && fileHeader.magic != RIFFHeader::idLIST)
  32. || (fileHeader.type != RIFFHeader::idWAVE && fileHeader.type != RIFFHeader::idwave))
  33. {
  34. return;
  35. }
  36. isDLS = (fileHeader.magic == RIFFHeader::idLIST);
  37. auto chunks = file.ReadChunks<RIFFChunk>(2);
  38. if(chunks.chunks.size() >= 4
  39. && chunks.chunks[1].GetHeader().GetID() == RIFFChunk::iddata
  40. && chunks.chunks[1].GetHeader().GetLength() % 2u != 0
  41. && chunks.chunks[2].GetHeader().GetLength() == 0
  42. && chunks.chunks[3].GetHeader().GetID() == RIFFChunk::id____)
  43. {
  44. // Houston, we have a problem: Old versions of (Open)MPT didn't write RIFF padding bytes. -_-
  45. // Luckily, the only RIFF chunk with an odd size those versions would ever write would be the "data" chunk
  46. // (which contains the sample data), and its size is only odd iff the sample has an odd length and is in
  47. // 8-Bit mono format. In all other cases, the sample size (and thus the chunk size) is even.
  48. // And we're even more lucky: The versions of (Open)MPT in question will always write a relatively small
  49. // (smaller than 256 bytes) "smpl" chunk after the "data" chunk. This means that after an unpadded sample,
  50. // we will always read "mpl?" (? being the length of the "smpl" chunk) as the next chunk magic. The first two
  51. // 32-Bit members of the "smpl" chunk are always zero in our case, so we are going to read a chunk length of 0
  52. // next and the next chunk magic, which will always consist of four zero bytes. Hooray! We just checked for those
  53. // four zero bytes and can be pretty confident that we should not have applied padding.
  54. file.Seek(sizeof(RIFFHeader));
  55. chunks = file.ReadChunks<RIFFChunk>(1);
  56. }
  57. // Read format chunk
  58. FileReader formatChunk = chunks.GetChunk(RIFFChunk::idfmt_);
  59. if(!formatChunk.ReadStruct(formatInfo))
  60. {
  61. return;
  62. }
  63. if(formatInfo.format == WAVFormatChunk::fmtPCM && formatChunk.BytesLeft() == 4)
  64. {
  65. uint16 size = formatChunk.ReadIntLE<uint16>();
  66. uint16 value = formatChunk.ReadIntLE<uint16>();
  67. if(size == 2 && value == 1)
  68. {
  69. // May be Cool Edit 16.8 format.
  70. // See SampleFormats.cpp for details.
  71. mayBeCoolEdit16_8 = true;
  72. }
  73. } else if(formatInfo.format == WAVFormatChunk::fmtExtensible)
  74. {
  75. WAVFormatChunkExtension extFormat;
  76. if(!formatChunk.ReadStruct(extFormat))
  77. {
  78. return;
  79. }
  80. subFormat = static_cast<uint16>(mpt::UUID(extFormat.subFormat).GetData1());
  81. }
  82. // Read sample data
  83. sampleData = chunks.GetChunk(RIFFChunk::iddata);
  84. if(!sampleData.IsValid())
  85. {
  86. // The old IMA ADPCM loader code looked for the "pcm " chunk instead of the "data" chunk...
  87. // Dunno why (Windows XP's audio recorder saves IMA ADPCM files with a "data" chunk), but we will just look for both.
  88. sampleData = chunks.GetChunk(RIFFChunk::idpcm_);
  89. }
  90. // "fact" chunk should contain sample length of compressed samples.
  91. sampleLength = chunks.GetChunk(RIFFChunk::idfact).ReadUint32LE();
  92. if((formatInfo.format != WAVFormatChunk::fmtIMA_ADPCM || sampleLength == 0) && GetSampleSize() != 0)
  93. {
  94. if((GetBlockAlign() == 0) || (GetBlockAlign() / GetNumChannels() >= 2 * GetSampleSize()))
  95. {
  96. // Some samples have an incorrect blockAlign / sample size set (e.g. it's 8 in SQUARE.WAV while it should be 1), so let's better not always trust this value.
  97. // The idea here is, if block align is off by twice or more, it is unlikely to be describing sample padding inside the block.
  98. // Ignore it in this case and calculate the length based on the single sample size and number of channels instead.
  99. sampleLength = sampleData.GetLength() / GetSampleSize();
  100. } else
  101. {
  102. // Correct case (so that 20bit WAVEFORMATEX files work).
  103. sampleLength = sampleData.GetLength() / GetBlockAlign();
  104. }
  105. }
  106. // Determine string encoding
  107. codePage = GetFileCodePage(chunks);
  108. // Check for loop points, texts, etc...
  109. FindMetadataChunks(chunks);
  110. // DLS bank chunk
  111. wsmpChunk = chunks.GetChunk(RIFFChunk::idwsmp);
  112. }
  113. void WAVReader::FindMetadataChunks(FileReader::ChunkList<RIFFChunk> &chunks)
  114. {
  115. // Read sample loop points and other sampler information
  116. smplChunk = chunks.GetChunk(RIFFChunk::idsmpl);
  117. instChunk = chunks.GetChunk(RIFFChunk::idinst);
  118. // Read sample cues
  119. cueChunk = chunks.GetChunk(RIFFChunk::idcue_);
  120. // Read text chunks
  121. FileReader listChunk = chunks.GetChunk(RIFFChunk::idLIST);
  122. if(listChunk.ReadMagic("INFO"))
  123. {
  124. infoChunk = listChunk.ReadChunks<RIFFChunk>(2);
  125. }
  126. // Read MPT sample information
  127. xtraChunk = chunks.GetChunk(RIFFChunk::idxtra);
  128. }
  129. uint16 WAVReader::GetFileCodePage(FileReader::ChunkList<RIFFChunk> &chunks)
  130. {
  131. FileReader csetChunk = chunks.GetChunk(RIFFChunk::idCSET);
  132. if(!csetChunk.IsValid())
  133. {
  134. FileReader iSFT = infoChunk.GetChunk(RIFFChunk::idISFT);
  135. if(iSFT.ReadMagic("OpenMPT"))
  136. {
  137. std::string versionString;
  138. iSFT.ReadString<mpt::String::maybeNullTerminated>(versionString, iSFT.BytesLeft());
  139. versionString = mpt::trim(versionString);
  140. Version version = Version::Parse(mpt::ToUnicode(mpt::Charset::ISO8859_1, versionString));
  141. if(version && version < MPT_V("1.28.00.02"))
  142. {
  143. return 1252; // mpt::Charset::Windows1252; // OpenMPT up to and including 1.28.00.01 wrote metadata in windows-1252 encoding
  144. } else
  145. {
  146. return 28591; // mpt::Charset::ISO8859_1; // as per spec
  147. }
  148. } else
  149. {
  150. return 28591; // mpt::Charset::ISO8859_1; // as per spec
  151. }
  152. }
  153. if(!csetChunk.CanRead(2))
  154. {
  155. // chunk not parsable
  156. return 28591; // mpt::Charset::ISO8859_1;
  157. }
  158. uint16 codepage = csetChunk.ReadUint16LE();
  159. return codepage;
  160. }
  161. void WAVReader::ApplySampleSettings(ModSample &sample, mpt::Charset sampleCharset, mpt::charbuf<MAX_SAMPLENAME> &sampleName)
  162. {
  163. // Read sample name
  164. FileReader textChunk = infoChunk.GetChunk(RIFFChunk::idINAM);
  165. if(textChunk.IsValid())
  166. {
  167. std::string sampleNameEncoded;
  168. textChunk.ReadString<mpt::String::nullTerminated>(sampleNameEncoded, textChunk.GetLength());
  169. sampleName = mpt::ToCharset(sampleCharset, mpt::ToUnicode(codePage, mpt::Charset::Windows1252, sampleNameEncoded));
  170. }
  171. if(isDLS)
  172. {
  173. // DLS sample -> sample filename
  174. sample.filename = sampleName;
  175. }
  176. // Read software name
  177. const bool isOldMPT = infoChunk.GetChunk(RIFFChunk::idISFT).ReadMagic("Modplug Tracker");
  178. // Convert loops
  179. WAVSampleInfoChunk sampleInfo;
  180. smplChunk.Rewind();
  181. if(smplChunk.ReadStruct(sampleInfo))
  182. {
  183. WAVSampleLoop loopData;
  184. if(sampleInfo.numLoops > 1 && smplChunk.ReadStruct(loopData))
  185. {
  186. // First loop: Sustain loop
  187. loopData.ApplyToSample(sample.nSustainStart, sample.nSustainEnd, sample.nLength, sample.uFlags, CHN_SUSTAINLOOP, CHN_PINGPONGSUSTAIN, isOldMPT);
  188. }
  189. // First loop (if only one loop is present) or second loop (if more than one loop is present): Normal sample loop
  190. if(smplChunk.ReadStruct(loopData))
  191. {
  192. loopData.ApplyToSample(sample.nLoopStart, sample.nLoopEnd, sample.nLength, sample.uFlags, CHN_LOOP, CHN_PINGPONGLOOP, isOldMPT);
  193. }
  194. //sample.Transpose((60 - sampleInfo.baseNote) / 12.0);
  195. sample.rootNote = static_cast<uint8>(sampleInfo.baseNote);
  196. if(sample.rootNote < 128)
  197. sample.rootNote += NOTE_MIN;
  198. else
  199. sample.rootNote = NOTE_NONE;
  200. sample.SanitizeLoops();
  201. }
  202. if(sample.rootNote == NOTE_NONE && instChunk.LengthIsAtLeast(sizeof(WAVInstrumentChunk)))
  203. {
  204. WAVInstrumentChunk inst;
  205. instChunk.Rewind();
  206. if(instChunk.ReadStruct(inst))
  207. {
  208. sample.rootNote = inst.unshiftedNote;
  209. if(sample.rootNote < 128)
  210. sample.rootNote += NOTE_MIN;
  211. else
  212. sample.rootNote = NOTE_NONE;
  213. }
  214. }
  215. // Read cue points
  216. if(cueChunk.IsValid())
  217. {
  218. uint32 numPoints = cueChunk.ReadUint32LE();
  219. LimitMax(numPoints, mpt::saturate_cast<uint32>(std::size(sample.cues)));
  220. for(uint32 i = 0; i < numPoints; i++)
  221. {
  222. WAVCuePoint cuePoint;
  223. cueChunk.ReadStruct(cuePoint);
  224. sample.cues[i] = cuePoint.position;
  225. }
  226. std::fill(std::begin(sample.cues) + numPoints, std::end(sample.cues), MAX_SAMPLE_LENGTH);
  227. }
  228. // Read MPT extra info
  229. WAVExtraChunk mptInfo;
  230. xtraChunk.Rewind();
  231. if(xtraChunk.ReadStruct(mptInfo))
  232. {
  233. if(mptInfo.flags & WAVExtraChunk::setPanning) sample.uFlags.set(CHN_PANNING);
  234. sample.nPan = std::min(static_cast<uint16>(mptInfo.defaultPan), uint16(256));
  235. sample.nVolume = std::min(static_cast<uint16>(mptInfo.defaultVolume), uint16(256));
  236. sample.nGlobalVol = std::min(static_cast<uint16>(mptInfo.globalVolume), uint16(64));
  237. sample.nVibType = static_cast<VibratoType>(mptInfo.vibratoType.get());
  238. sample.nVibSweep = mptInfo.vibratoSweep;
  239. sample.nVibDepth = mptInfo.vibratoDepth;
  240. sample.nVibRate = mptInfo.vibratoRate;
  241. if(xtraChunk.CanRead(MAX_SAMPLENAME))
  242. {
  243. // Name present (clipboard only)
  244. // FIXME: When modules can have individual encoding in OpenMPT or when
  245. // internal metadata gets converted to Unicode, we must adjust this to
  246. // also specify encoding.
  247. xtraChunk.ReadString<mpt::String::nullTerminated>(sampleName, MAX_SAMPLENAME);
  248. xtraChunk.ReadString<mpt::String::nullTerminated>(sample.filename, xtraChunk.BytesLeft());
  249. }
  250. }
  251. }
  252. // Apply WAV loop information to a mod sample.
  253. void WAVSampleLoop::ApplyToSample(SmpLength &start, SmpLength &end, SmpLength sampleLength, SampleFlags &flags, ChannelFlags enableFlag, ChannelFlags bidiFlag, bool mptLoopFix) const
  254. {
  255. if(loopEnd == 0)
  256. {
  257. // Some WAV files seem to have loops going from 0 to 0... We should ignore those.
  258. return;
  259. }
  260. start = std::min(static_cast<SmpLength>(loopStart), sampleLength);
  261. end = Clamp(static_cast<SmpLength>(loopEnd), start, sampleLength);
  262. if(!mptLoopFix && end < sampleLength)
  263. {
  264. // RIFF loop end points are inclusive - old versions of MPT didn't consider this.
  265. end++;
  266. }
  267. flags.set(enableFlag);
  268. if(loopType == loopBidi)
  269. {
  270. flags.set(bidiFlag);
  271. }
  272. }
  273. // Convert internal loop information into a WAV loop.
  274. void WAVSampleLoop::ConvertToWAV(SmpLength start, SmpLength end, bool bidi)
  275. {
  276. identifier = 0;
  277. loopType = bidi ? loopBidi : loopForward;
  278. loopStart = mpt::saturate_cast<uint32>(start);
  279. // Loop ends are *inclusive* in the RIFF standard, while they're *exclusive* in OpenMPT.
  280. if(end > start)
  281. {
  282. loopEnd = mpt::saturate_cast<uint32>(end - 1);
  283. } else
  284. {
  285. loopEnd = loopStart;
  286. }
  287. fraction = 0;
  288. playCount = 0;
  289. }
  290. #ifndef MODPLUG_NO_FILESAVE
  291. ///////////////////////////////////////////////////////////
  292. // WAV Writing
  293. // Output to stream: Initialize with std::ostream*.
  294. WAVWriter::WAVWriter(mpt::IO::OFileBase &stream)
  295. : s(stream)
  296. {
  297. // Skip file header for now
  298. Seek(sizeof(RIFFHeader));
  299. }
  300. WAVWriter::~WAVWriter()
  301. {
  302. MPT_ASSERT(finalized);
  303. }
  304. // Finalize the file by closing the last open chunk and updating the file header. Returns total size of file.
  305. std::size_t WAVWriter::Finalize()
  306. {
  307. FinalizeChunk();
  308. RIFFHeader fileHeader;
  309. Clear(fileHeader);
  310. fileHeader.magic = RIFFHeader::idRIFF;
  311. fileHeader.length = static_cast<uint32>(totalSize - 8);
  312. fileHeader.type = RIFFHeader::idWAVE;
  313. Seek(0);
  314. Write(fileHeader);
  315. finalized = true;
  316. return totalSize;
  317. }
  318. // Write a new chunk header to the file.
  319. void WAVWriter::StartChunk(RIFFChunk::ChunkIdentifiers id)
  320. {
  321. FinalizeChunk();
  322. chunkStartPos = position;
  323. chunkHeader.id = id;
  324. Skip(sizeof(chunkHeader));
  325. }
  326. // End current chunk by updating the chunk header and writing a padding byte if necessary.
  327. void WAVWriter::FinalizeChunk()
  328. {
  329. if(chunkStartPos != 0)
  330. {
  331. const std::size_t chunkSize = position - (chunkStartPos + sizeof(RIFFChunk));
  332. chunkHeader.length = mpt::saturate_cast<uint32>(chunkSize);
  333. std::size_t curPos = position;
  334. Seek(chunkStartPos);
  335. Write(chunkHeader);
  336. Seek(curPos);
  337. if((chunkSize % 2u) != 0)
  338. {
  339. // Write padding
  340. uint8 padding = 0;
  341. Write(padding);
  342. }
  343. chunkStartPos = 0;
  344. }
  345. }
  346. // Seek to a position in file.
  347. void WAVWriter::Seek(std::size_t pos)
  348. {
  349. position = pos;
  350. totalSize = std::max(totalSize, position);
  351. mpt::IO::SeekAbsolute(s, pos);
  352. }
  353. // Write some data to the file.
  354. void WAVWriter::Write(mpt::const_byte_span data)
  355. {
  356. MPT_ASSERT(!finalized);
  357. auto success = mpt::IO::WriteRaw(s, data);
  358. MPT_ASSERT(success); // this assertion is useful to catch mis-calculation of required buffer size for pre-allocate in-memory file buffers (like in View_smp.cpp for clipboard)
  359. if(!success)
  360. {
  361. return;
  362. }
  363. position += data.size();
  364. totalSize = std::max(totalSize, position);
  365. }
  366. void WAVWriter::WriteBeforeDirect()
  367. {
  368. MPT_ASSERT(!finalized);
  369. }
  370. void WAVWriter::WriteAfterDirect(bool success, std::size_t count)
  371. {
  372. MPT_ASSERT(success); // this assertion is useful to catch mis-calculation of required buffer size for pre-allocate in-memory file buffers (like in View_smp.cpp for clipboard)
  373. if (!success)
  374. {
  375. return;
  376. }
  377. position += count;
  378. totalSize = std::max(totalSize, position);
  379. }
  380. // Write the WAV format to the file.
  381. void WAVWriter::WriteFormat(uint32 sampleRate, uint16 bitDepth, uint16 numChannels, WAVFormatChunk::SampleFormats encoding)
  382. {
  383. StartChunk(RIFFChunk::idfmt_);
  384. WAVFormatChunk wavFormat;
  385. Clear(wavFormat);
  386. bool extensible = (numChannels > 2);
  387. wavFormat.format = static_cast<uint16>(extensible ? WAVFormatChunk::fmtExtensible : encoding);
  388. wavFormat.numChannels = numChannels;
  389. wavFormat.sampleRate = sampleRate;
  390. wavFormat.blockAlign = (bitDepth * numChannels + 7) / 8;
  391. wavFormat.byteRate = wavFormat.sampleRate * wavFormat.blockAlign;
  392. wavFormat.bitsPerSample = bitDepth;
  393. Write(wavFormat);
  394. if(extensible)
  395. {
  396. WAVFormatChunkExtension extFormat;
  397. Clear(extFormat);
  398. extFormat.size = sizeof(WAVFormatChunkExtension) - sizeof(uint16);
  399. extFormat.validBitsPerSample = bitDepth;
  400. switch(numChannels)
  401. {
  402. case 1:
  403. extFormat.channelMask = 0x0004; // FRONT_CENTER
  404. break;
  405. case 2:
  406. extFormat.channelMask = 0x0003; // FRONT_LEFT | FRONT_RIGHT
  407. break;
  408. case 3:
  409. extFormat.channelMask = 0x0103; // FRONT_LEFT | FRONT_RIGHT | BACK_CENTER
  410. break;
  411. case 4:
  412. extFormat.channelMask = 0x0033; // FRONT_LEFT | FRONT_RIGHT | BACK_LEFT | BACK_RIGHT
  413. break;
  414. default:
  415. extFormat.channelMask = 0;
  416. break;
  417. }
  418. extFormat.subFormat = mpt::UUID(static_cast<uint16>(encoding), 0x0000, 0x0010, 0x800000AA00389B71ull);
  419. Write(extFormat);
  420. }
  421. }
  422. // Write text tags to the file.
  423. void WAVWriter::WriteMetatags(const FileTags &tags)
  424. {
  425. StartChunk(RIFFChunk::idCSET);
  426. Write(mpt::as_le(uint16(65001))); // code page (UTF-8)
  427. Write(mpt::as_le(uint16(0))); // country code (unset)
  428. Write(mpt::as_le(uint16(0))); // language (unset)
  429. Write(mpt::as_le(uint16(0))); // dialect (unset)
  430. StartChunk(RIFFChunk::idLIST);
  431. const char info[] = { 'I', 'N', 'F', 'O' };
  432. Write(info);
  433. WriteTag(RIFFChunk::idINAM, tags.title);
  434. WriteTag(RIFFChunk::idIART, tags.artist);
  435. WriteTag(RIFFChunk::idIPRD, tags.album);
  436. WriteTag(RIFFChunk::idICRD, tags.year);
  437. WriteTag(RIFFChunk::idICMT, tags.comments);
  438. WriteTag(RIFFChunk::idIGNR, tags.genre);
  439. WriteTag(RIFFChunk::idTURL, tags.url);
  440. WriteTag(RIFFChunk::idISFT, tags.encoder);
  441. //WriteTag(RIFFChunk:: , tags.bpm);
  442. WriteTag(RIFFChunk::idTRCK, tags.trackno);
  443. }
  444. // Write a single tag into a open idLIST chunk
  445. void WAVWriter::WriteTag(RIFFChunk::ChunkIdentifiers id, const mpt::ustring &utext)
  446. {
  447. std::string text = mpt::ToCharset(mpt::Charset::UTF8, utext);
  448. text = text.substr(0, uint32_max - 1u);
  449. if(!text.empty())
  450. {
  451. const uint32 length = mpt::saturate_cast<uint32>(text.length() + 1);
  452. RIFFChunk chunk;
  453. Clear(chunk);
  454. chunk.id = static_cast<uint32>(id);
  455. chunk.length = length;
  456. Write(chunk);
  457. Write(mpt::byte_cast<mpt::const_byte_span>(mpt::span(text.c_str(), length)));
  458. if((length % 2u) != 0)
  459. {
  460. uint8 padding = 0;
  461. Write(padding);
  462. }
  463. }
  464. }
  465. // Write a sample loop information chunk to the file.
  466. void WAVWriter::WriteLoopInformation(const ModSample &sample)
  467. {
  468. if(!sample.uFlags[CHN_LOOP | CHN_SUSTAINLOOP] && !ModCommand::IsNote(sample.rootNote))
  469. {
  470. return;
  471. }
  472. StartChunk(RIFFChunk::idsmpl);
  473. WAVSampleInfoChunk info;
  474. uint32 sampleRate = sample.nC5Speed;
  475. if(sampleRate == 0)
  476. {
  477. sampleRate = ModSample::TransposeToFrequency(sample.RelativeTone, sample.nFineTune);
  478. }
  479. info.ConvertToWAV(sampleRate, sample.rootNote);
  480. // Set up loops
  481. WAVSampleLoop loops[2];
  482. Clear(loops);
  483. if(sample.uFlags[CHN_SUSTAINLOOP])
  484. {
  485. loops[info.numLoops++].ConvertToWAV(sample.nSustainStart, sample.nSustainEnd, sample.uFlags[CHN_PINGPONGSUSTAIN]);
  486. }
  487. if(sample.uFlags[CHN_LOOP])
  488. {
  489. loops[info.numLoops++].ConvertToWAV(sample.nLoopStart, sample.nLoopEnd, sample.uFlags[CHN_PINGPONGLOOP]);
  490. } else if(sample.uFlags[CHN_SUSTAINLOOP])
  491. {
  492. // Since there are no "loop types" to distinguish between sustain and normal loops, OpenMPT assumes
  493. // that the first loop is a sustain loop if there are two loops. If we only want a sustain loop,
  494. // we will have to write a second bogus loop.
  495. loops[info.numLoops++].ConvertToWAV(0, 0, false);
  496. }
  497. Write(info);
  498. for(uint32 i = 0; i < info.numLoops; i++)
  499. {
  500. Write(loops[i]);
  501. }
  502. }
  503. // Write a sample's cue points to the file.
  504. void WAVWriter::WriteCueInformation(const ModSample &sample)
  505. {
  506. uint32 numMarkers = 0;
  507. for(const auto cue : sample.cues)
  508. {
  509. if(cue < sample.nLength)
  510. numMarkers++;
  511. }
  512. StartChunk(RIFFChunk::idcue_);
  513. Write(mpt::as_le(numMarkers));
  514. uint32 i = 0;
  515. for(const auto cue : sample.cues)
  516. {
  517. if(cue < sample.nLength)
  518. {
  519. WAVCuePoint cuePoint;
  520. cuePoint.ConvertToWAV(i++, cue);
  521. Write(cuePoint);
  522. }
  523. }
  524. }
  525. // Write MPT's sample information chunk to the file.
  526. void WAVWriter::WriteExtraInformation(const ModSample &sample, MODTYPE modType, const char *sampleName)
  527. {
  528. StartChunk(RIFFChunk::idxtra);
  529. WAVExtraChunk mptInfo;
  530. mptInfo.ConvertToWAV(sample, modType);
  531. Write(mptInfo);
  532. if(sampleName != nullptr)
  533. {
  534. // Write sample name (clipboard only)
  535. // FIXME: When modules can have individual encoding in OpenMPT or when
  536. // internal metadata gets converted to Unicode, we must adjust this to
  537. // also specify encoding.
  538. char name[MAX_SAMPLENAME];
  539. mpt::String::WriteBuf(mpt::String::nullTerminated, name) = sampleName;
  540. Write(name);
  541. char filename[MAX_SAMPLEFILENAME];
  542. mpt::String::WriteBuf(mpt::String::nullTerminated, filename) = sample.filename;
  543. Write(filename);
  544. }
  545. }
  546. #endif // MODPLUG_NO_FILESAVE
  547. OPENMPT_NAMESPACE_END